From 08cd0801598612388261a3e61e28a8cc22856012 Mon Sep 17 00:00:00 2001 From: Gaurav Singh Date: Mon, 3 Feb 2025 09:45:26 +0530 Subject: [PATCH] Fix/button v1 props (#236) * fix: maxWidth instead of width * refactor: remove danger and secondary props, rename primary to solid * style: add styles for variants * style: css fixes * chore: remove comment * docs: update mdx * feat: add box shadow * chore: change css * feat: add width prop with style * chore: update mdx --- .../content/primitives/components/button.mdx | 113 ++++---- apps/www/examples/shield-ts/assets.tsx | 43 ++- .../v1/components/button/button.module.css | 244 +++++++++++------- .../raystack/v1/components/button/button.tsx | 40 ++- 4 files changed, 288 insertions(+), 152 deletions(-) diff --git a/apps/www/content/primitives/components/button.mdx b/apps/www/content/primitives/components/button.mdx index fbdf816b..76cdd8a6 100644 --- a/apps/www/content/primitives/components/button.mdx +++ b/apps/www/content/primitives/components/button.mdx @@ -5,7 +5,9 @@ description: Triggers a click action usually performed by the user to trigger an --- - + ## Usages @@ -26,54 +28,46 @@ Import all parts and piece them together. Click here!`} border /> +`} border /> - ## Props The `Button` component accepts the following props: -- `variant`: Visual style variant (`"primary"` | `"secondary"` | `"outline"` | `"ghost"` | `"danger"` | `"text"`, default: "primary") +- `variant`: Visual style variant (`"solid"` | `"outline"` | `"ghost"` | `"text"`, default: "solid") +- `color`: Color theme (`"accent"` | `"danger"` | `"neutral"` | `"success"`, default: "accent") - `size`: Size of the button (`"small"` | `"normal"`, default: "normal") - `disabled`: Whether the button is disabled (boolean, default: false) - `loading`: Shows a loading spinner inside the button (boolean) - `loaderText`: Optional text to display next to the loading spinner (ReactNode) - `leadingIcon`: Icon element to display before button text (ReactNode) - `trailingIcon`: Icon element to display after button text (ReactNode) +- `maxWidth`: Custom maximum width for the button (string | number) - `width`: Custom width for the button (string | number) - `asChild`: Boolean to merge props onto child element - `className`: Additional CSS class names - All other ButtonHTMLAttributes from React +## Variants -## Variant - -Variants allow you to customize the button's style, making it visually distinctive and suitable for different purposes. Default is `Primary`. +Variants allow you to customize the button's style, making it visually distinctive and suitable for different purposes. Default is `solid`. Primary button`, - }, - { - name: "Secondary", - code: ``, + name: "Solid", + code: ``, }, { name: "Outline", - code: ``, + code: ``, }, { name: "Ghost", code: ``, }, - { - name: "Danger", - code: ``, - }, { name: "Text", code: ``, @@ -81,54 +75,85 @@ Variants allow you to customize the button's style, making it visually distincti ]} /> +## Colors + +Colors help convey the purpose and importance of actions. The color prop only affects solid and outline variants. Default is `accent`. + + + + +`, + }, + { + name: "Danger", + code: ` +
+ + +
`, + }, + { + name: "Neutral", + code: ` +
+ + +
`, + }, + { + name: "Success", + code: ` +
+ + +
`, + }, + ]} +/> ## Scale -The Button component offers two size options to accommodate various design requirements. By selecting the `normal` size, you can create buttons that are visually larger, making it more prominent and noticeable within the user interface. Default size is `normal`. +The Button component offers two size options. Default size is `normal`. primary button`, + code: ``, }, { name: "Normal", - code: ``, + code: ``, }, ]} /> ## Disabled -The Button component provides a "disabled" state, which prevents the button from responding to any user actions. When a button is disabled, it becomes unclickable and does not trigger any associated actions or events. - -This feature is useful when you want to indicate that a button is temporarily inactive or unavailable for interaction. Default is `false`. +The Button component provides a "disabled" state, which prevents the button from responding to any user actions. Default is `false`. Primary button`, - }, - { - name: "Secondary", - code: ``, + name: "Solid", + code: ``, }, { name: "Outline", - code: ``, + code: ``, }, { name: "Ghost", code: ``, }, - { - name: "Danger", - code: ``, - }, { name: "Text", code: ``, @@ -138,32 +163,30 @@ This feature is useful when you want to indicate that a button is temporarily in ## Loading -The Button component offers inbuilt loading states. Loading state is used to signify an ongoing process after the user has clicked on the button. Loading is an optional prop. - -Note: A loading as `true` only disables the button click and displays a spinner within the button. `loading` does not changes the button appearance as disabled. For changing the appearance to disabled state, a `disabled` prop as `true` has to passed. +The Button component offers inbuilt loading states. Loading state is used to signify an ongoing process after the user has clicked on the button. - - - + + + `} /> ## With leading and trailing icons -The button component accepts optional leading and/or trailing icons. A `ReactNode` can be passed as a prop. +The button component accepts optional leading and/or trailing icons. - - - + + + `} /> diff --git a/apps/www/examples/shield-ts/assets.tsx b/apps/www/examples/shield-ts/assets.tsx index f0a109bc..ecf26af2 100644 --- a/apps/www/examples/shield-ts/assets.tsx +++ b/apps/www/examples/shield-ts/assets.tsx @@ -182,9 +182,44 @@ export const Assets = () => {
- - Assets - + + Solid Buttons + + + + + + + + Outline Buttons + + + + + + + + Ghost & Text Buttons + + + + + + Loading State + + + + + + + + Disabled State + + + + + + { style={{ width: "100%", padding: "4px", paddingTop: "48px" }} > - setSearchValue(e.target.value)} diff --git a/packages/raystack/v1/components/button/button.module.css b/packages/raystack/v1/components/button/button.module.css index 08aceb66..24978768 100644 --- a/packages/raystack/v1/components/button/button.module.css +++ b/packages/raystack/v1/components/button/button.module.css @@ -40,76 +40,33 @@ font-size: var(--fs-200); } -.button-primary { +.button-solid { color: var(--rs-color-text-accent-emphasis); background-color: var(--rs-color-background-accent-emphasis); } -.button-primary:hover, -.button-primary:active, -.button-primary[data-radix-popover-trigger][data-state="open"], -.button-primary[data-radix-dropdown-menu-trigger][data-state="open"] { +.button-solid:hover, +.button-solid:active, +.button-solid[data-radix-popover-trigger][data-state="open"], +.button-solid[data-radix-dropdown-menu-trigger][data-state="open"] { background-color: var(--rs-color-background-accent-emphasis-hover); } -.button-primary.button-disabled:hover, -.button-primary.button-disabled:active, -.button-primary[data-radix-popover-trigger][data-state="open"], -.button-primary[data-radix-dropdown-menu-trigger][data-state="open"] { +.button-solid.button-disabled:hover, +.button-solid.button-disabled:active, +.button-solid[data-radix-popover-trigger][data-state="open"], +.button-solid.button-disabled[data-radix-dropdown-menu-trigger][data-state="open"] { color: var(--rs-color-text-accent-emphasis); background-color: var(--rs-color-background-accent-emphasis); } -.button-primary.button-loading:hover, -.button-primary.button-loading:active, -.button-primary.button-loading[data-radix-popover-trigger][data-state="open"], -.button-primary.button-loading[data-radix-dropdown-menu-trigger][data-state="open"] { +.button-solid.button-loading:hover, +.button-solid.button-loading:active, +.button-solid.button-loading[data-radix-popover-trigger][data-state="open"], +.button-solid.button-loading[data-radix-dropdown-menu-trigger][data-state="open"] { background-color: var(--rs-color-background-accent-emphasis); } -.button-secondary { - color: var(--rs-color-text-base-primary); - background-color: var(--rs-color-background-base-primary); - border: 1px solid var(--rs-color-border-base-secondary); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); -} - -.button-secondary:hover, -.button-secondary:active, -.button-secondary[data-radix-popover-trigger][data-state="open"], -.button-secondary[data-radix-dropdown-menu-trigger][data-state="open"] { - background-color: var(--rs-color-background-base-primary-hover); - border-color: var(--rs-color-border-base-focus); -} - -.button-secondary:disabled, -.button-secondary.button-disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.button-secondary:disabled:hover, -.button-secondary.button-disabled:hover, -.button-secondary:disabled:active, -.button-secondary.button-disabled:active, -.button-secondary:disabled[data-radix-popover-trigger][data-state="open"], -.button-secondary.button-disabled[data-radix-popover-trigger][data-state="open"], -.button-secondary:disabled[data-radix-dropdown-menu-trigger][data-state="open"], -.button-secondary.button-disabled[data-radix-dropdown-menu-trigger][data-state="open"] { - color: var(--rs-color-text-base-primary); - background-color: var(--rs-color-background-base-primary); - border-color: var(--rs-color-border-base-secondary); -} - -.button-secondary.button-loading:hover, -.button-secondary.button-loading:active, -.button-secondary.button-loading[data-radix-popover-trigger][data-state="open"], -.button-secondary.button-loading[data-radix-dropdown-menu-trigger][data-state="open"] { - background-color: var(--rs-color-background-base-primary); - border-color: var(--rs-color-border-base-primary); - cursor: not-allowed; -} - .button-outline { background-color: var(--rs-color-background-base-primary); color: var(--rs-color-text-accent-primary); @@ -157,6 +114,13 @@ cursor: not-allowed; } +.button-outline-accent.button-loading:hover, +.button-outline-accent.button-loading:active { + background-color: transparent; + color: var(--rs-color-text-accent-primary); + border-color: var(--rs-color-border-accent-emphasis); +} + .button-ghost { color: var(--rs-color-text-base-primary); background-color: transparent; @@ -199,44 +163,6 @@ cursor: not-allowed; } -.button-danger { - color: var(--rs-color-text-danger-emphasis); - background-color: var(--rs-color-background-danger-emphasis); -} - -.button-danger:hover, -.button-danger:active, -.button-danger[data-radix-popover-trigger][data-state="open"], -.button-danger[data-radix-dropdown-menu-trigger][data-state="open"] { - background-color: var(--rs-color-background-danger-emphasis-hover); -} - -.button-danger:disabled, -.button-danger.button-disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.button-danger.button-loading:hover, -.button-danger.button-loading:active, -.button-danger.button-loading[data-radix-popover-trigger][data-state="open"], -.button-danger.button-loading[data-radix-dropdown-menu-trigger][data-state="open"] { - background-color: var(--rs-color-background-danger-emphasis); - cursor: not-allowed; -} - -.button-danger:disabled:hover, -.button-danger.button-disabled:hover, -.button-danger:disabled:active, -.button-danger.button-disabled:active, -.button-danger:disabled[data-radix-popover-trigger][data-state="open"], -.button-danger.button-disabled[data-radix-popover-trigger][data-state="open"], -.button-danger:disabled[data-radix-dropdown-menu-trigger][data-state="open"], -.button-danger.button-disabled[data-radix-dropdown-menu-trigger][data-state="open"] { - color: var(--rs-color-text-danger-emphasis); - background-color: var(--rs-color-background-danger-emphasis); -} - .button-text { color: var(--rs-color-text-base-primary); background-color: transparent; @@ -322,3 +248,133 @@ .icon-trailing { margin-left: var(--rs-space-3); } + +/* Solid variant colors */ +.button-solid-accent { + background-color: var(--rs-color-background-accent-emphasis); + color: var(--rs-color-text-accent-emphasis); +} + +.button-solid-accent:hover { + background-color: var(--rs-color-background-accent-emphasis-hover); +} + +.button-solid-accent:active { + background-color: var(--rs-color-background-accent-emphasis-hover); +} + +.button-solid-danger { + background-color: var(--rs-color-background-danger-emphasis); + color: var(--rs-color-text-danger-emphasis); +} + +.button-solid-danger:hover { + background-color: var(--rs-color-background-danger-emphasis-hover); +} + +.button-solid-danger:active { + background-color: var(--rs-color-background-danger-emphasis-hover); +} + +.button-solid-danger.button-loading:hover { + background-color: var(--rs-color-background-danger-emphasis); +} + +.button-solid-neutral { + background-color: var(--rs-color-background-neutral-secondary); + color: var(--rs-color-text-base-primary); +} + +.button-solid-neutral:hover { + background-color: var(--rs-color-background-neutral-secondary-hover); +} + +.button-solid-neutral:active { + background-color: var(--rs-color-background-neutral-secondary-hover); +} + +.button-solid-neutral.button-loading:hover { + background-color: var(--rs-color-background-neutral-secondary); +} + +.button-solid-success { + background-color: var(--rs-color-background-success-emphasis); + color: var(--rs-color-text-success-emphasis); +} + +.button-solid-success:hover { + background-color: var(--rs-color-background-success-emphasis-hover); +} + +.button-solid-success:active { + background-color: var(--rs-color-background-success-emphasis-hover); +} + +/* Outline variant colors */ +.button-outline-accent { + border-color: var(--rs-color-border-accent-emphasis); + color: var(--rs-color-text-accent-primary); +} + +.button-outline-accent:hover { + background-color: var(--rs-color-background-accent-primary); +} + +.button-outline-accent:active { + background-color: var(--rs-color-background-accent-emphasis); + color: var(--rs-color-text-accent-emphasis); +} + +.button-outline-danger { + border-color: var(--rs-color-border-danger-emphasis); + color: var(--rs-color-text-danger-primary); +} + +.button-outline-danger:hover { + border-color: var(--rs-color-border-danger-emphasis-hover); + background-color: var(--rs-color-background-danger-primary); +} + +.button-outline-danger:active { + background-color: var(--rs-color-background-danger-emphasis); + color: var(--rs-color-text-danger-emphasis); + border-color: transparent; +} + +.button-outline-neutral { + background-color: var(--rs-color-background-base-primary); + color: var(--rs-color-text-base-primary); + border: 1px solid var(--rs-color-border-base-tertiary); +} + +.button-outline-neutral:hover { + background-color: var(--rs-color-background-base-primary-hover); + border-color: var(--rs-color-border-base-tertiary-hover); +} + +.button-outline-neutral:active { + background-color: var(--rs-color-background-base-primary-hover); + color: var(--rs-color-text-base-primary); + border-color: transparent; +} + +.button-outline-success { + border-color: var(--rs-color-border-success-emphasis); + color: var(--rs-color-text-success-primary); +} + +.button-outline-success:hover { + background-color: var(--rs-color-background-success-primary); + border-color: var(--rs-color-border-success-emphasis-hover); +} + +.button-outline-success:active { + background-color: var(--rs-color-background-success-emphasis); + color: var(--rs-color-text-success-emphasis); + border-color: transparent; +} + +.button-solid, +.button-outline { + box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.06), 0px 4px 4px -1px rgba(0, 0, 0, 0.02); +} diff --git a/packages/raystack/v1/components/button/button.tsx b/packages/raystack/v1/components/button/button.tsx index 246efa34..0f9ae4c0 100644 --- a/packages/raystack/v1/components/button/button.tsx +++ b/packages/raystack/v1/components/button/button.tsx @@ -8,11 +8,9 @@ import styles from "./button.module.css"; const button = cva(styles['button'], { variants: { variant: { - primary: styles["button-primary"], - secondary: styles["button-secondary"], + solid: styles["button-solid"], outline: styles["button-outline"], ghost: styles["button-ghost"], - danger: styles["button-danger"], text: styles["button-text"] }, size: { @@ -24,8 +22,29 @@ const button = cva(styles['button'], { }, loading: { true: styles["button-loading"], + }, + color: { + accent: styles["button-color-accent"], + danger: styles["button-color-danger"], + neutral: styles["button-color-neutral"], + success: styles["button-color-success"], } }, + compoundVariants: [ + { variant: 'solid', color: 'accent', className: styles['button-solid-accent'] }, + { variant: 'solid', color: 'danger', className: styles['button-solid-danger'] }, + { variant: 'solid', color: 'neutral', className: styles['button-solid-neutral'] }, + { variant: 'solid', color: 'success', className: styles['button-solid-success'] }, + { variant: 'outline', color: 'accent', className: styles['button-outline-accent'] }, + { variant: 'outline', color: 'danger', className: styles['button-outline-danger'] }, + { variant: 'outline', color: 'neutral', className: styles['button-outline-neutral'] }, + { variant: 'outline', color: 'success', className: styles['button-outline-success'] }, + ], + defaultVariants: { + variant: 'solid', + size: 'normal', + color: 'accent' + } }); const getLoaderOnlyClass = (size: 'small' | 'normal' | null) => @@ -38,23 +57,26 @@ type ButtonProps = PropsWithChildren> & loaderText?: ReactNode; leadingIcon?: ReactNode; trailingIcon?: ReactNode; - width?: string | number + maxWidth?: string | number; + width?: string | number; + style?: React.CSSProperties; }; export const Button = forwardRef( - ({ className, variant = 'primary', size = 'normal', asChild = false, disabled, loading, loaderText, leadingIcon, trailingIcon, width, children, ...props }, ref) => { + ({ className, variant = 'solid', color = 'accent', size = 'normal', asChild = false, disabled, loading, loaderText, leadingIcon, trailingIcon, maxWidth, width, style = {}, children, ...props }, ref) => { const Comp = asChild ? Slot : "button"; const isLoaderOnly = loading && !loaderText; - const widthStyle = width ? { width } : {}; + const widthStyle = { maxWidth, width }; + const buttonStyle = { ...widthStyle, ...style }; - const spinnerColor = variant === 'primary' || variant === 'danger' ? 'inverted' : 'default'; + const spinnerColor = variant === 'solid' ? 'inverted' : 'default'; return ( {loading ? (