324 lines
11 KiB
TypeScript
324 lines
11 KiB
TypeScript
import {
|
|
AnchorHTMLAttributes,
|
|
ButtonHTMLAttributes,
|
|
ComponentProps,
|
|
DetailedHTMLProps,
|
|
HTMLAttributeAnchorTarget,
|
|
HTMLAttributes,
|
|
} from "react";
|
|
import { twMerge } from "tailwind-merge";
|
|
import Loading from "../elements/Loading";
|
|
|
|
export type TWUIButtonProps = DetailedHTMLProps<
|
|
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
HTMLButtonElement
|
|
> & {
|
|
title: string;
|
|
variant?: "normal" | "ghost" | "outlined";
|
|
color?:
|
|
| "primary"
|
|
| "secondary"
|
|
| "text"
|
|
| "white"
|
|
| "accent"
|
|
| "gray"
|
|
| "error"
|
|
| "warning"
|
|
| "success";
|
|
size?: "small" | "smaller" | "normal" | "large" | "larger";
|
|
loadingIconSize?: React.ComponentProps<typeof Loading>["size"];
|
|
href?: string;
|
|
target?: HTMLAttributeAnchorTarget;
|
|
loading?: boolean;
|
|
linkProps?: DetailedHTMLProps<
|
|
AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
HTMLAnchorElement
|
|
>;
|
|
beforeIcon?: React.ReactNode;
|
|
afterIcon?: React.ReactNode;
|
|
buttonContentProps?: DetailedHTMLProps<
|
|
HTMLAttributes<HTMLDivElement>,
|
|
HTMLDivElement
|
|
>;
|
|
loadingProps?: ComponentProps<typeof Loading>;
|
|
};
|
|
|
|
/**
|
|
* # Buttons
|
|
* @className twui-button-general
|
|
* @className twui-button-content-wrapper
|
|
* @className twui-button-primary
|
|
* @className twui-button-primary-outlined
|
|
* @className twui-button-primary-ghost
|
|
* @className twui-button-secondary
|
|
* @className twui-button-secondary-outlined
|
|
* @className twui-button-secondary-ghost
|
|
* @className twui-button-white
|
|
* @className twui-button-white-outlined
|
|
* @className twui-button-white-ghost
|
|
* @className twui-button-accent
|
|
* @className twui-button-accent-outlined
|
|
* @className twui-button-accent-ghost
|
|
* @className twui-button-gray
|
|
* @className twui-button-gray-outlined
|
|
* @className twui-button-gray-ghost
|
|
*
|
|
* @example
|
|
```css
|
|
CSS directive:
|
|
|
|
//@theme inline {
|
|
--breakpoint-xs: 350px;
|
|
--color-primary: #000000;
|
|
--color-primary-hover: #29292b;
|
|
--color-primary-outline: #29292b;
|
|
--color-primary-text: #29292b;
|
|
--color-primary-dark: #29292b;
|
|
--color-primary-dark-hover: #4b4b4b;
|
|
--color-primary-dark-outline: #4b4b4b;
|
|
--color-primary-dark-text: #4b4b4b;
|
|
--color-secondary: #000000;
|
|
--color-secondary-hover: #dddddd;
|
|
--color-secondary-outline: #dddddd;
|
|
--color-secondary-text: #dddddd;
|
|
--color-secondary-dark: #000000;
|
|
--color-secondary-dark-hover: #dddddd;
|
|
--color-secondary-dark-outline: #dddddd;
|
|
--color-secondary-dark-text: #dddddd;
|
|
--color-accent: #000000;
|
|
--color-accent-hover: #dddddd;
|
|
--color-accent-outline: #dddddd;
|
|
--color-accent-text: #dddddd;
|
|
--color-accent-dark: #000000;
|
|
--color-accent-dark-hover: #dddddd;
|
|
--color-accent-dark-outline: #dddddd;
|
|
--color-accent-dark-text: #dddddd;
|
|
}
|
|
```
|
|
*/
|
|
export default function Button({
|
|
href,
|
|
target,
|
|
variant,
|
|
color,
|
|
size,
|
|
buttonContentProps,
|
|
linkProps,
|
|
beforeIcon,
|
|
afterIcon,
|
|
loading,
|
|
loadingIconSize,
|
|
loadingProps,
|
|
...props
|
|
}: TWUIButtonProps) {
|
|
const finalClassName: string = (() => {
|
|
if (variant == "normal" || !variant) {
|
|
if (color == "primary" || !color)
|
|
return twMerge(
|
|
"bg-primary hover:bg-primary-hover text-white",
|
|
"dark:bg-primary-dark hover:dark:bg-primary-dark-hover text-white",
|
|
"twui-button-primary"
|
|
);
|
|
if (color == "secondary")
|
|
return twMerge(
|
|
"bg-secondary hover:bg-secondary-hover text-white",
|
|
"twui-button-secondary"
|
|
);
|
|
if (color == "white")
|
|
return twMerge(
|
|
"!bg-white hover:!bg-slate-200 !text-slate-800",
|
|
"twui-button-white"
|
|
);
|
|
if (color == "accent")
|
|
return twMerge(
|
|
"bg-accent hover:bg-accent-hover text-white",
|
|
"twui-button-accent"
|
|
);
|
|
if (color == "gray")
|
|
return twMerge(
|
|
"bg-gray hover:bg-gray-hover text-foreground-light",
|
|
"dark:bg-gray-dark hover:dark:bg-gray-dark-hover dark:text-foreground-dark",
|
|
"twui-button-gray"
|
|
);
|
|
if (color == "success")
|
|
return twMerge(
|
|
"bg-success hover:bg-success-hover text-white",
|
|
"dark:bg-success hover:dark:bg-success-hover text-white",
|
|
"twui-button-success"
|
|
);
|
|
if (color == "error")
|
|
return twMerge(
|
|
"bg-error hover:bg-error-hover text-white",
|
|
"dark:bg-error hover:dark:bg-error-hover text-white",
|
|
"twui-button-error"
|
|
);
|
|
} else if (variant == "outlined") {
|
|
if (color == "primary" || !color)
|
|
return twMerge(
|
|
"bg-transparent outline outline-1 outline-primary",
|
|
"text-primary-text dark:text-primary-dark-text dark:outline-primary-dark-outline",
|
|
"twui-button-primary-outlined"
|
|
);
|
|
if (color == "secondary")
|
|
return twMerge(
|
|
"bg-transparent outline outline-1 outline-secondary",
|
|
"text-secondary",
|
|
"twui-button-secondary-outlined"
|
|
);
|
|
if (color == "accent")
|
|
return twMerge(
|
|
"bg-transparent outline outline-1 outline-accent",
|
|
"text-accent",
|
|
"twui-button-accent-outlined"
|
|
);
|
|
if (color == "gray")
|
|
return twMerge(
|
|
"bg-transparent outline outline-1 outline-slate-300",
|
|
"text-slate-600 dark:text-white/60 dark:outline-white/30",
|
|
"twui-button-gray-outlined"
|
|
);
|
|
if (color == "white")
|
|
return twMerge(
|
|
"bg-transparent outline outline-1 outline-white/50",
|
|
"text-white",
|
|
"twui-button-white-outlined"
|
|
);
|
|
if (color == "error")
|
|
return twMerge(
|
|
"bg-transparent outline outline-1 outline-error text-error",
|
|
"dark:outline-error dark:text-error-dark",
|
|
"twui-button-error-outlined"
|
|
);
|
|
} else if (variant == "ghost") {
|
|
if (color == "primary" || !color)
|
|
return twMerge(
|
|
"bg-transparent dark:bg-transparent outline-none p-2",
|
|
"text-primary-text dark:text-primary-dark-text hover:bg-transparent dark:hover:bg-transparent",
|
|
"twui-button-primary-ghost"
|
|
);
|
|
if (color == "secondary")
|
|
return twMerge(
|
|
"bg-transparent dark:bg-transparent outline-none p-2",
|
|
"text-secondary hover:bg-transparent dark:hover:bg-transparent",
|
|
"twui-button-secondary-ghost"
|
|
);
|
|
if (color == "text")
|
|
return twMerge(
|
|
"bg-transparent dark:bg-transparent outline-none p-2 dark:text-foreground-dark",
|
|
"text-foreground-light hover:bg-transparent dark:hover:bg-transparent",
|
|
"twui-button-secondary-ghost"
|
|
);
|
|
if (color == "accent")
|
|
return twMerge(
|
|
"bg-transparent dark:bg-transparent outline-none p-2",
|
|
"text-accent hover:bg-transparent dark:hover:bg-transparent",
|
|
"twui-button-accent-ghost"
|
|
);
|
|
if (color == "gray")
|
|
return twMerge(
|
|
"bg-transparent dark:bg-transparent outline-none p-2 hover:bg-transparent dark:hover:bg-transparent",
|
|
"text-slate-600 dark:text-white/70 hover:opacity-80",
|
|
"twui-button-gray-ghost"
|
|
);
|
|
if (color == "error")
|
|
return twMerge(
|
|
"bg-transparent outline-none p-2",
|
|
"text-red-600 dark:text-red-400",
|
|
"twui-button-error-ghost"
|
|
);
|
|
if (color == "warning")
|
|
return twMerge(
|
|
"bg-transparent outline-none p-2",
|
|
"text-yellow-600",
|
|
"twui-button-warning-ghost"
|
|
);
|
|
if (color == "success")
|
|
return twMerge(
|
|
"bg-transparent outline-none p-2",
|
|
"text-success",
|
|
"twui-button-success-ghost"
|
|
);
|
|
if (color == "white")
|
|
return twMerge(
|
|
"bg-transparent outline-none p-2",
|
|
"text-white",
|
|
"twui-button-white-ghost"
|
|
);
|
|
}
|
|
|
|
return "";
|
|
})();
|
|
|
|
const buttonComponent = (
|
|
<button
|
|
{...props}
|
|
className={twMerge(
|
|
"bg-primary text-white font-medium px-4 py-2 rounded-default",
|
|
"flex items-center justify-center relative transition-all cursor-pointer",
|
|
props.disabled ? "opacity-40 cursor-not-allowed" : "",
|
|
"twui-button-general",
|
|
size == "small"
|
|
? "px-3 py-1.5 text-sm twui-button-small"
|
|
: size == "smaller"
|
|
? "px-2 py-1 text-xs twui-button-smaller"
|
|
: size == "large"
|
|
? "text-lg twui-button-large"
|
|
: size == "larger"
|
|
? "px-5 py-3 text-xl twui-button-larger"
|
|
: "twui-button-base",
|
|
finalClassName,
|
|
loading ? "pointer-events-none opacity-80" : "",
|
|
props.className
|
|
)}
|
|
aria-label={props.title}
|
|
>
|
|
<div
|
|
{...buttonContentProps}
|
|
className={twMerge(
|
|
"flex flex-row items-center gap-2 whitespace-nowrap",
|
|
loading ? "opacity-0" : "",
|
|
"twui-button-content-wrapper",
|
|
buttonContentProps?.className
|
|
)}
|
|
>
|
|
{beforeIcon && beforeIcon}
|
|
{props.children}
|
|
{afterIcon && afterIcon}
|
|
</div>
|
|
|
|
{loading && (
|
|
<Loading
|
|
size={(() => {
|
|
if (loadingIconSize) return loadingIconSize;
|
|
switch (size) {
|
|
case "small":
|
|
return "small";
|
|
case "smaller":
|
|
return "smaller";
|
|
|
|
default:
|
|
return "normal";
|
|
}
|
|
})()}
|
|
{...loadingProps}
|
|
className={twMerge("absolute", loadingProps?.className)}
|
|
/>
|
|
)}
|
|
</button>
|
|
);
|
|
|
|
if (href)
|
|
return (
|
|
<a
|
|
{...linkProps}
|
|
href={href}
|
|
target={target}
|
|
title={props.title}
|
|
aria-label={props.title}
|
|
>
|
|
{buttonComponent}
|
|
</a>
|
|
);
|
|
return buttonComponent;
|
|
}
|