base
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "../utils";
|
||||
import type { TButtonVariant, TButtonSizes } from "./helper";
|
||||
import { getIconStyling, getButtonStyling } from "./helper";
|
||||
|
||||
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: TButtonVariant;
|
||||
size?: TButtonSizes;
|
||||
className?: string;
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
appendIcon?: any;
|
||||
prependIcon?: any;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const Button = React.forwardRef(function Button(props: ButtonProps, ref: React.ForwardedRef<HTMLButtonElement>) {
|
||||
const {
|
||||
variant = "primary",
|
||||
size = "md",
|
||||
className = "",
|
||||
type = "button",
|
||||
loading = false,
|
||||
disabled = false,
|
||||
prependIcon = null,
|
||||
appendIcon = null,
|
||||
children,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const buttonStyle = getButtonStyling(variant, size, disabled || loading);
|
||||
const buttonIconStyle = getIconStyling(size);
|
||||
|
||||
return (
|
||||
<button ref={ref} type={type} className={cn(buttonStyle, className)} disabled={disabled || loading} {...rest}>
|
||||
{prependIcon && <div className={buttonIconStyle}>{React.cloneElement(prependIcon, { strokeWidth: 2 })}</div>}
|
||||
{children}
|
||||
{appendIcon && <div className={buttonIconStyle}>{React.cloneElement(appendIcon, { strokeWidth: 2 })}</div>}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
Button.displayName = "plane-ui-button";
|
||||
|
||||
export { Button };
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
export type TButtonVariant =
|
||||
| "primary"
|
||||
| "accent-primary"
|
||||
| "outline-primary"
|
||||
| "neutral-primary"
|
||||
| "link-primary"
|
||||
| "danger"
|
||||
| "accent-danger"
|
||||
| "outline-danger"
|
||||
| "link-danger"
|
||||
| "tertiary-danger"
|
||||
| "link-neutral";
|
||||
|
||||
export type TButtonSizes = "sm" | "md" | "lg" | "xl";
|
||||
|
||||
export interface IButtonStyling {
|
||||
[key: string]: {
|
||||
default: string;
|
||||
hover: string;
|
||||
pressed: string;
|
||||
disabled: string;
|
||||
};
|
||||
}
|
||||
|
||||
enum buttonSizeStyling {
|
||||
sm = `px-3 py-1.5 font-medium text-11 rounded-sm flex items-center gap-1.5 whitespace-nowrap transition-all justify-center`,
|
||||
md = `px-4 py-1.5 font-medium text-13 rounded-sm flex items-center gap-1.5 whitespace-nowrap transition-all justify-center`,
|
||||
lg = `px-5 py-2 font-medium text-13 rounded-sm flex items-center gap-1.5 whitespace-nowrap transition-all justify-center`,
|
||||
xl = `px-5 py-3.5 font-medium text-13 rounded-sm flex items-center gap-1.5 whitespace-nowrap transition-all justify-center`,
|
||||
}
|
||||
|
||||
enum buttonIconStyling {
|
||||
sm = "h-3 w-3 flex justify-center items-center overflow-hidden my-0.5 flex-shrink-0",
|
||||
md = "h-3.5 w-3.5 flex justify-center items-center overflow-hidden my-0.5 flex-shrink-0",
|
||||
lg = "h-4 w-4 flex justify-center items-center overflow-hidden my-0.5 flex-shrink-0",
|
||||
xl = "h-4 w-4 flex justify-center items-center overflow-hidden my-0.5 flex-shrink-0 ",
|
||||
}
|
||||
|
||||
export const buttonStyling: IButtonStyling = {
|
||||
primary: {
|
||||
default: `text-on-color bg-accent-primary`,
|
||||
hover: `hover:bg-accent-primary/80`,
|
||||
pressed: `focus:text-custom-brand-40 focus:bg-accent-primary/80`,
|
||||
disabled: `cursor-not-allowed !bg-layer-1 !text-on-color-disabled`,
|
||||
},
|
||||
"accent-primary": {
|
||||
default: `bg-accent-primary/20 text-accent-primary`,
|
||||
hover: `hover:bg-accent-primary/10 hover:text-accent-secondary`,
|
||||
pressed: `focus:bg-accent-primary/10`,
|
||||
disabled: `cursor-not-allowed !text-accent-primary/60`,
|
||||
},
|
||||
"outline-primary": {
|
||||
default: `text-accent-primary bg-transparent border border-accent-strong`,
|
||||
hover: `hover:bg-accent-primary/20`,
|
||||
pressed: `focus:text-accent-primary focus:bg-accent-primary/30`,
|
||||
disabled: `cursor-not-allowed !text-accent-primary/60 !border-accent-strong-60 `,
|
||||
},
|
||||
"neutral-primary": {
|
||||
default: `text-secondary bg-surface-1 border border-subtle`,
|
||||
hover: `hover:bg-surface-2`,
|
||||
pressed: `focus:text-tertiary focus:bg-surface-2`,
|
||||
disabled: `cursor-not-allowed !bg-layer-1 !text-placeholder`,
|
||||
},
|
||||
"link-primary": {
|
||||
default: `text-accent-primary bg-surface-1`,
|
||||
hover: `hover:text-accent-secondary`,
|
||||
pressed: `focus:text-accent-primary/80 `,
|
||||
disabled: `cursor-not-allowed !text-accent-primary/60`,
|
||||
},
|
||||
danger: {
|
||||
default: `bg-danger-primary text-on-color`,
|
||||
hover: ` hover:bg-danger-primary-hover`,
|
||||
pressed: `focus:bg-danger-primary-active`,
|
||||
disabled: `cursor-not-allowed bg-layer-disabled! text-disabled!`,
|
||||
},
|
||||
"accent-danger": {
|
||||
default: `text-danger-primary bg-red-50`,
|
||||
hover: `hover:text-danger-primary hover:bg-red-100`,
|
||||
pressed: `focus:text-danger-primary focus:bg-red-100`,
|
||||
disabled: `cursor-not-allowed !bg-layer-1 !text-placeholder`,
|
||||
},
|
||||
"outline-danger": {
|
||||
default: `bg-layer-2 text-danger-primary border border-danger-strong`,
|
||||
hover: `hover:bg-danger-subtle`,
|
||||
pressed: `focus:bg-danger-subtle-hover`,
|
||||
disabled: `cursor-not-allowed text-disabled! border-subtle-1!`,
|
||||
},
|
||||
"link-danger": {
|
||||
default: `text-danger-primary bg-surface-1`,
|
||||
hover: `hover:text-danger-primary`,
|
||||
pressed: `focus:text-danger-primary`,
|
||||
disabled: `cursor-not-allowed !bg-layer-1 !text-placeholder`,
|
||||
},
|
||||
"tertiary-danger": {
|
||||
default: `text-danger-primary bg-surface-1 border border-danger-subtle`,
|
||||
hover: `hover:bg-red-50 hover:border-danger-subtle`,
|
||||
pressed: `focus:text-danger-primary`,
|
||||
disabled: `cursor-not-allowed !bg-layer-1 !text-placeholder`,
|
||||
},
|
||||
"link-neutral": {
|
||||
default: `text-tertiary`,
|
||||
hover: `hover:text-secondary`,
|
||||
pressed: `focus:text-primary`,
|
||||
disabled: `cursor-not-allowed !bg-layer-1 !text-placeholder`,
|
||||
},
|
||||
};
|
||||
|
||||
export const getButtonStyling = (variant: TButtonVariant, size: TButtonSizes, disabled: boolean = false): string => {
|
||||
let tempVariant: string = ``;
|
||||
const currentVariant = buttonStyling[variant];
|
||||
|
||||
tempVariant = `${currentVariant.default} ${disabled ? currentVariant.disabled : currentVariant.hover} ${
|
||||
currentVariant.pressed
|
||||
}`;
|
||||
|
||||
let tempSize: string = ``;
|
||||
if (size) tempSize = buttonSizeStyling[size];
|
||||
return `${tempVariant} ${tempSize}`;
|
||||
};
|
||||
|
||||
export const getIconStyling = (size: TButtonSizes): string => {
|
||||
let icon: string = ``;
|
||||
if (size) icon = buttonIconStyling[size];
|
||||
return icon;
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
export * from "./button";
|
||||
export * from "./helper";
|
||||
export * from "./toggle-switch";
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { Switch } from "@headlessui/react";
|
||||
// helpers
|
||||
import { cn } from "../utils";
|
||||
|
||||
interface IToggleSwitchProps {
|
||||
value: boolean;
|
||||
onChange: (value: boolean) => void;
|
||||
label?: string;
|
||||
size?: "sm" | "md" | "lg";
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function ToggleSwitch(props: IToggleSwitchProps) {
|
||||
const { value, onChange, label, size = "sm", disabled, className } = props;
|
||||
|
||||
return (
|
||||
<Switch
|
||||
checked={value}
|
||||
disabled={disabled}
|
||||
onChange={onChange}
|
||||
className={cn(
|
||||
"relative inline-flex h-6 w-10 flex-shrink-0 cursor-pointer rounded-full border border-subtle bg-layer-1 transition-colors duration-200 ease-in-out focus:outline-none",
|
||||
{
|
||||
"h-4 w-7": size === "sm",
|
||||
"h-5 w-9": size === "md",
|
||||
"bg-accent-primary": value && !disabled,
|
||||
"bg-(--text-color-icon-placeholder)": !value && !disabled,
|
||||
"cursor-not-allowed bg-accent-primary opacity-50": value && disabled,
|
||||
"cursor-not-allowed bg-(--text-color-icon-placeholder) opacity-50": !value && disabled,
|
||||
},
|
||||
className
|
||||
)}
|
||||
>
|
||||
<span className="sr-only">{label}</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
"inline-block h-5 w-5 transform self-center rounded-full bg-(--text-color-icon-on-color) ring-0 transition duration-200 ease-in-out",
|
||||
{
|
||||
"h-3 w-3 translate-x-3.5": size === "sm" && value,
|
||||
"h-3 w-3 translate-x-0.5": size === "sm" && !value,
|
||||
"h-4 w-4 translate-x-4": size === "md" && value,
|
||||
"h-4 w-4 translate-x-0.5": size === "md" && !value,
|
||||
"translate-x-4": size === "lg" && value,
|
||||
"translate-x-0.5": size === "lg" && !value,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
ToggleSwitch.displayName = "plane-ui-toggle-switch";
|
||||
|
||||
export { ToggleSwitch };
|
||||
Reference in New Issue
Block a user