From 887c01eb871786c9c666710dece901095ec76e5b Mon Sep 17 00:00:00 2001 From: Lewis Date: Sun, 21 Apr 2024 14:37:26 +0700 Subject: [PATCH] Add icon button, adjust spinner, button --- lib/assets/icons/LoadingIcon.tsx | 2 +- .../icon-button/icon-button.stories.tsx | 38 ++++ lib/components/icon-button/icon-button.tsx | 45 ++++ .../icon-button/icon-button.types.ts | 10 + lib/components/tabs/tabs.tsx | 5 +- .../styles/components/button.styles.ts | 8 +- .../styles/components/icon-button.styles.ts | 196 ++++++++++++++++++ lib/customization/styles/components/index.ts | 1 + .../styles/components/tabs.styles.ts | 2 +- lib/customization/styles/theme.context.tsx | 2 + package.json | 2 +- 11 files changed, 300 insertions(+), 11 deletions(-) create mode 100644 lib/components/icon-button/icon-button.stories.tsx create mode 100644 lib/components/icon-button/icon-button.tsx create mode 100644 lib/components/icon-button/icon-button.types.ts create mode 100644 lib/customization/styles/components/icon-button.styles.ts diff --git a/lib/assets/icons/LoadingIcon.tsx b/lib/assets/icons/LoadingIcon.tsx index 636ae90..3ecd361 100644 --- a/lib/assets/icons/LoadingIcon.tsx +++ b/lib/assets/icons/LoadingIcon.tsx @@ -29,7 +29,7 @@ const LoadingIcon = ({ y2="6.86385e-07" gradientUnits="userSpaceOnUse" > - + diff --git a/lib/components/icon-button/icon-button.stories.tsx b/lib/components/icon-button/icon-button.stories.tsx new file mode 100644 index 0000000..5ba598b --- /dev/null +++ b/lib/components/icon-button/icon-button.stories.tsx @@ -0,0 +1,38 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import { IconButton } from "./icon-button"; +import { Add } from "iconsax-react"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export +const meta = { + title: "FORMS/IconButton", + component: IconButton, + parameters: { + layout: "centered", + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs + tags: ["autodocs"], + // More on argTypes: https://storybook.js.org/docs/react/api/argtypes + argTypes: { + // backgroundColor: { control: 'color' }, + variant: { + control: "radio", + options: ["solid", "subtle", "outline", "link", "text"], + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args +export const Primary: Story = { + args: { + size: "md", + variant: "solid", + isLoading: false, + isDisabled: false, + icon: , + isDanger: false, + }, +}; diff --git a/lib/components/icon-button/icon-button.tsx b/lib/components/icon-button/icon-button.tsx new file mode 100644 index 0000000..5fdc293 --- /dev/null +++ b/lib/components/icon-button/icon-button.tsx @@ -0,0 +1,45 @@ +import { useMemo } from "react"; +import { twMerge } from "tailwind-merge"; +import LoadingIcon from "../../assets/icons/LoadingIcon"; +import { useComponentStyle } from "../../customization/styles/theme.context"; +import { IIconButton } from "./icon-button.types"; + +export const IconButton = (props: IIconButton) => { + const theme = useComponentStyle("IconButton"); + + const { + className = "", + variant = "solid", + size = "md", + isDisabled, + isLoading, + icon, + spinner, + isDanger, + ...restProps + } = props; + + const classes = useMemo(() => { + return twMerge(theme.base({ size, variant, isDanger }), className); + }, [className, variant, size, isDanger, theme]); + + const spinnerRender = spinner ?? ( + + ); + + return ( + + ); +}; diff --git a/lib/components/icon-button/icon-button.types.ts b/lib/components/icon-button/icon-button.types.ts new file mode 100644 index 0000000..7ff1620 --- /dev/null +++ b/lib/components/icon-button/icon-button.types.ts @@ -0,0 +1,10 @@ +export interface IIconButton + extends React.ButtonHTMLAttributes { + isDisabled?: boolean; + isLoading?: boolean; + icon?: React.ReactNode; + size?: "xs" | "sm" | "md" | "lg"; + spinner?: React.ReactNode; + variant?: "text" | "outline" | "subtle" | "solid" | "link"; + isDanger?: boolean; +} diff --git a/lib/components/tabs/tabs.tsx b/lib/components/tabs/tabs.tsx index 64bc7fb..9d51f07 100644 --- a/lib/components/tabs/tabs.tsx +++ b/lib/components/tabs/tabs.tsx @@ -89,10 +89,7 @@ export const Tabs = (props: ITabs) => { {item.icon} {item.label} {item.key === active && variant === "underline" ? ( - +
) : null} ))} diff --git a/lib/customization/styles/components/button.styles.ts b/lib/customization/styles/components/button.styles.ts index c42f358..3c54c62 100644 --- a/lib/customization/styles/components/button.styles.ts +++ b/lib/customization/styles/components/button.styles.ts @@ -82,10 +82,10 @@ const base = cva( ], }, size: { - xs: ["zn-text-xs", "zn-h-[24px]", "zn-py-1", "zn-px-2"], - sm: ["zn-text-xs", "zn-h-[32px]", "zn-py-1", "zn-px-4"], - md: ["zn-text-sm", "zn-h-[44px]", "zn-py-1", "zn-px-[20px]"], - lg: ["zn-text-base", "zn-h-[48px]", "zn-py-1", "zn-px-[24px]"], + xs: ["zn-text-xs", "zn-h-6", "zn-py-1", "zn-px-2"], + sm: ["zn-text-sm", "zn-h-8", "zn-py-1", "zn-px-3"], + md: ["zn-text-sm", "zn-h-10", "zn-py-1", "zn-px-[20px]"], + lg: ["zn-text-base", "zn-h-12", "zn-py-1", "zn-px-[24px]"], }, isDanger: { true: [], diff --git a/lib/customization/styles/components/icon-button.styles.ts b/lib/customization/styles/components/icon-button.styles.ts new file mode 100644 index 0000000..d2599e6 --- /dev/null +++ b/lib/customization/styles/components/icon-button.styles.ts @@ -0,0 +1,196 @@ +import { cva } from "class-variance-authority"; + +const base = cva( + [ + "zn-rounded-base", + "disabled:zn-cursor-not-allowed", + "zn-transition-opacity", + "zn-duration-400", + "zn-ease-in-out", + "zn-font-semibold", + "zn-py-1", + "zn-px-1", + "zn-flex", + "zn-justify-center", + "zn-items-center", + ], + { + variants: { + variant: { + solid: [ + "zn-border", + "zn-bg-primary-base", + "zn-border-transparent", + "zn-text-white", + + "hover:zn-bg-primary-50", + + "focus:zn-bg-primary-60", + + "active:zn-bg-primary-80", + + "disabled:zn-bg-neutral-10", + "disabled:zn-text-neutral-40", + ], + subtle: [ + "zn-bg-primary-20", + "zn-border", + "zn-border-transparent", + + "hover:zn-bg-primary-10", + + "focus:zn-bg-primary-30", + + "active:zn-bg-primary-40", + + "disabled:zn-bg-neutral-10", + "disabled:zn-text-neutral-40", + + "disabled:zn-bg-neutral-10", + "disabled:zn-text-neutral-40", + ], + outline: [ + "zn-border", + "zn-bg-neutral-5", + "zn-border-primary-10", + "zn-text-neutral-100", + + "hover:zn-border-neutral-10", + "hover:zn-text-primary-base", + + "active:zn-border-neutral-10", + "active:zn-text-primary-base", + + "disabled:zn-bg-neutral-10", + "disabled:zn-text-neutral-40", + ], + text: [ + "zn-border", + "zn-bg-transparent", + "zn-border-transparent", + + "hover:zn-bg-primary-10", + "hover:zn-text-primary-base", + + "active:zn-bg-primary-10", + "active:zn-text-primary-base", + + "disabled:zn-text-neutral-40", + ], + link: [ + "zn-border", + "zn-bg-transparent", + "zn-border-transparent", + "zn-underline", + "zn-text-neutral-100", + "disabled:zn-text-neutral-40", + ], + }, + size: { + xs: ["zn-text-xs", "zn-h-6", "zn-w-6"], + sm: ["zn-text-sm", "zn-h-8", "zn-w-8"], + md: ["zn-text-sm", "zn-h-10", "zn-w-10"], + lg: ["zn-text-base", "zn-h-12", "zn-w-12"], + }, + isDanger: { + true: [], + false: [], + }, + }, + compoundVariants: [ + { + variant: "solid", + isDanger: true, + class: [ + "!zn-bg-error-base", + "hover:!zn-bg-error-50", + "focus:!zn-bg-error-60", + "active:!zn-bg-error-80", + ], + }, + { + variant: "subtle", + isDanger: true, + class: [ + "!zn-bg-error-20", + "hover:!zn-bg-error-10", + "focus:!zn-bg-error-30", + "active:!zn-bg-error-40", + ], + }, + { + variant: "outline", + isDanger: true, + class: [ + "!zn-border-error-base", + "!zn-text-error-base", + "!zn-bg-neutral-5", + + "hover:!zn-border-error-60", + "hover:!zn-text-error-60", + "hover:!zn-bg-neutral-5", + + "focus:!zn-border-error-60", + "focus:!zn-text-error-60", + "focus:!zn-bg-neutral-5", + + "active:!zn-border-error-60", + "active:!zn-text-error-60", + "active:!zn-bg-neutral-5", + ], + }, + { + variant: "text", + isDanger: true, + class: [ + "!zn-text-error-base", + "hover:!zn-text-error-base", + "hover:!zn-bg-error-10", + + "active:!zn-text-error-base", + "active:!zn-bg-error-10", + ], + }, + { + variant: "link", + isDanger: true, + class: [ + "!zn-text-error-base", + "hover:!zn-text-error-base", + "hover:!zn-underline", + ], + }, + ], + + defaultVariants: { + size: "md", + variant: "solid", + }, + } +); + +const container = cva([ + "zn-flex", + "zn-justify-center", + "zn-items-center", + "zn-gap-2", +]); + +const spinner = cva(["zn-animate-spin"], { + variants: { + size: { + xs: ["zn-w-[12px]", "zn-h-[12px]"], + sm: ["zn-w-[14px]", "zn-h-[14px]"], + md: ["zn-w-[18px]", "zn-h-[18px]"], + lg: ["zn-w-[20px]", "zn-h-[20px]"], + }, + }, +}); + +const iconButtonStyles = { + base, + container, + spinner, +}; + +export { iconButtonStyles }; diff --git a/lib/customization/styles/components/index.ts b/lib/customization/styles/components/index.ts index 712e1df..ad3594a 100644 --- a/lib/customization/styles/components/index.ts +++ b/lib/customization/styles/components/index.ts @@ -2,6 +2,7 @@ export * from "./alert.styles"; export * from "./avatar.styles"; export * from "./box.styles"; export * from "./button.styles"; +export * from "./icon-button.styles"; export * from "./center.styles"; export * from "./checkbox.styles"; export * from "./flex.styles"; diff --git a/lib/customization/styles/components/tabs.styles.ts b/lib/customization/styles/components/tabs.styles.ts index df42a63..4572ffe 100644 --- a/lib/customization/styles/components/tabs.styles.ts +++ b/lib/customization/styles/components/tabs.styles.ts @@ -76,7 +76,7 @@ const indicator = cva([ "zn-left-0", "zn-right-0", "zn-w-full", - "zn-h-[1.5px]", + "zn-h-[1px]", "zn-bg-primary-base", ]); const content = cva(["zn-block", "zn-py-4"]); diff --git a/lib/customization/styles/theme.context.tsx b/lib/customization/styles/theme.context.tsx index 0e3f58c..2f6d7bd 100644 --- a/lib/customization/styles/theme.context.tsx +++ b/lib/customization/styles/theme.context.tsx @@ -7,6 +7,7 @@ export interface Style { interface StyleComponents { Button: typeof styles.buttonStyles; + IconButton: typeof styles.iconButtonStyles; Box: typeof styles.boxStyles; Text: typeof styles.textStyles; Tooltip: typeof styles.tooltipStyles; @@ -36,6 +37,7 @@ interface StyleComponents { const defaultStyle: Style = { components: { Button: styles.buttonStyles, + IconButton: styles.iconButtonStyles, Box: styles.boxStyles, Text: styles.textStyles, Tooltip: styles.tooltipStyles, diff --git a/package.json b/package.json index f28ca42..9af80de 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "zeni-ui", "private": false, - "version": "1.2.17-beta", + "version": "1.2.18-beta", "type": "module", "main": "dist/main.js", "types": "dist/main.d.ts",