diff --git a/src/components/ui/Button/Button.stories.tsx b/src/components/ui/Button/Button.stories.tsx index 357ca0d..a338cd9 100644 --- a/src/components/ui/Button/Button.stories.tsx +++ b/src/components/ui/Button/Button.stories.tsx @@ -10,14 +10,15 @@ const meta: Meta = { control: 'select', options: ['primary', 'secondary', 'tertiary'], }, - color: { + intent: { control: 'select', - options: ['navy', 'red', 'light', 'surface'], + options: ['default', 'danger', 'subtle', 'neutral'], }, size: { control: 'select', options: ['default', 'comfortable', 'compact'], }, + loading: { control: 'boolean' }, disabled: { control: 'boolean' }, children: { control: 'text' }, }, @@ -36,45 +37,25 @@ export const Default: Story = { args: { children: 'Button' }, } -export const PrimaryNavy: Story = { - args: { children: 'Button', variant: 'primary', color: 'navy' }, +// --- Intents --- + +export const IntentDefault: Story = { + args: { children: 'Submit', intent: 'default' }, } -export const PrimaryRed: Story = { - args: { children: 'Delete', variant: 'primary', color: 'red' }, +export const IntentDanger: Story = { + args: { children: 'Delete', intent: 'danger' }, } -export const PrimaryLight: Story = { - args: { children: 'Button', variant: 'primary', color: 'light' }, +export const IntentSubtle: Story = { + args: { children: 'Learn more', intent: 'subtle' }, } -export const PrimarySurface: Story = { - args: { children: 'Button', variant: 'primary', color: 'surface' }, +export const IntentNeutral: Story = { + args: { children: 'Cancel', intent: 'neutral' }, } -export const SecondaryNavy: Story = { - args: { children: 'Button', variant: 'secondary', color: 'navy' }, -} - -export const SecondaryRed: Story = { - args: { children: 'Cancel', variant: 'secondary', color: 'red' }, -} - -export const SecondarySurface: Story = { - args: { children: 'Button', variant: 'secondary', color: 'surface' }, -} - -export const TertiaryNavy: Story = { - args: { children: 'Button', variant: 'tertiary', color: 'navy' }, -} - -export const TertiaryRed: Story = { - args: { children: 'Remove', variant: 'tertiary', color: 'red' }, -} - -export const TertiarySurface: Story = { - args: { children: 'Button', variant: 'tertiary', color: 'surface' }, -} +// --- Icons --- const ArrowIcon = () => ( ( ) export const WithLeftIcon: Story = { - args: { children: 'Button', leftIcon: }, + args: { children: 'Secure', leftIcon: }, } export const WithRightIcon: Story = { - args: { children: 'Button', rightIcon: }, + args: { children: 'Continue', rightIcon: }, } export const WithBothIcons: Story = { args: { - children: 'Button', + children: 'Secure', leftIcon: , rightIcon: , }, } -export const Comfortable: Story = { - args: { children: 'Button', size: 'comfortable' }, +// --- Loading --- + +export const Loading: Story = { + args: { children: 'Submitting...', loading: true }, } -export const Compact: Story = { - args: { children: 'Button', size: 'compact' }, +export const LoadingDanger: Story = { + args: { children: 'Deleting...', loading: true, intent: 'danger' }, } +export const LoadingSecondary: Story = { + args: { children: 'Loading...', loading: true, variant: 'secondary' }, +} + +// --- Sizes --- + export const AllSizes: Story = { render: () => (
@@ -140,6 +129,8 @@ export const AllSizes: Story = { ), } +// --- Variants --- + export const AllVariants: Story = { render: () => (
@@ -150,31 +141,35 @@ export const AllVariants: Story = { ), } -export const AllColours: Story = { +// --- Full matrix --- + +export const AllIntents: Story = { render: () => (
- - - - + + + +
- - - - + + + +
- - - - + + + +
), } +// --- Disabled --- + export const Disabled: Story = { render: () => (
diff --git a/src/components/ui/Button/Button.tsx b/src/components/ui/Button/Button.tsx index c5b0a26..d68eed1 100644 --- a/src/components/ui/Button/Button.tsx +++ b/src/components/ui/Button/Button.tsx @@ -3,30 +3,37 @@ import { cn } from '@/lib/utils' export interface ButtonProps extends ButtonHTMLAttributes { variant?: 'primary' | 'secondary' | 'tertiary' - color?: 'navy' | 'red' | 'light' | 'surface' + intent?: 'default' | 'danger' | 'subtle' | 'neutral' size?: 'default' | 'comfortable' | 'compact' + loading?: boolean leftIcon?: React.ReactNode rightIcon?: React.ReactNode } -const variantColorStyles: Record> = { +const variantIntentStyles: Record> = { primary: { - navy: 'bg-blue-01 text-white hover:bg-blue-01/90 active:bg-blue-01/80', - red: 'bg-red-02 text-white hover:bg-red-02/90 active:bg-red-02/80', - light: 'bg-blue-04 text-blue-01 hover:bg-blue-04/80 active:bg-blue-04/60', - surface: 'bg-grey-01 text-white hover:bg-grey-01/90 active:bg-grey-01/80', + default: 'bg-button-default text-white hover:bg-button-default/90 active:bg-button-default/80', + danger: 'bg-button-danger text-white hover:bg-button-danger/90 active:bg-button-danger/80', + subtle: + 'bg-button-subtle-bg text-button-subtle-text hover:bg-button-subtle-bg/80 active:bg-button-subtle-bg/60', + neutral: 'bg-button-neutral text-white hover:bg-button-neutral/90 active:bg-button-neutral/80', }, secondary: { - navy: 'border-2 border-blue-01 text-blue-01 hover:bg-blue-01/5 active:bg-blue-01/10', - red: 'border-2 border-red-02 text-red-02 hover:bg-red-02/5 active:bg-red-02/10', - light: 'border-2 border-blue-01 text-blue-01 hover:bg-blue-01/5 active:bg-blue-01/10', - surface: 'border-2 border-grey-01 text-grey-01 hover:bg-grey-01/5 active:bg-grey-01/10', + default: + 'border-2 border-button-default text-button-default hover:bg-button-default/5 active:bg-button-default/10', + danger: + 'border-2 border-button-danger text-button-danger hover:bg-button-danger/5 active:bg-button-danger/10', + subtle: + 'border border-button-default/40 text-button-subtle-text hover:bg-button-default/5 active:bg-button-default/10', + neutral: + 'border-2 border-button-neutral text-button-neutral hover:bg-button-neutral/5 active:bg-button-neutral/10', }, tertiary: { - navy: 'text-blue-01 hover:bg-blue-01/5 active:bg-blue-01/10', - red: 'text-red-02 hover:bg-red-02/5 active:bg-red-02/10', - light: 'text-blue-01 hover:bg-blue-01/5 active:bg-blue-01/10', - surface: 'text-grey-01 hover:bg-grey-01/5 active:bg-grey-01/10', + default: 'text-button-default hover:bg-button-default/5 active:bg-button-default/10', + danger: 'text-button-danger hover:bg-button-danger/5 active:bg-button-danger/10', + subtle: + 'text-button-subtle-text/70 hover:text-button-subtle-text hover:bg-button-default/5 active:bg-button-default/10', + neutral: 'text-button-neutral hover:bg-button-neutral/5 active:bg-button-neutral/10', }, } @@ -42,12 +49,29 @@ const iconSizeStyles: Record = { compact: 'size-5', } +const Spinner = ({ className }: { className?: string }) => ( + +) + export const Button = forwardRef( ( { variant = 'primary', - color = 'navy', + intent = 'default', size = 'default', + loading = false, leftIcon, rightIcon, disabled, @@ -57,26 +81,40 @@ export const Button = forwardRef( }, ref, ) => { + const isDisabled = disabled || loading + return ( ) diff --git a/src/components/ui/Checkbox/Checkbox.tsx b/src/components/ui/Checkbox/Checkbox.tsx index dde526c..140452f 100644 --- a/src/components/ui/Checkbox/Checkbox.tsx +++ b/src/components/ui/Checkbox/Checkbox.tsx @@ -64,7 +64,8 @@ export const Checkbox = forwardRef( 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-control-focus-ring focus-visible:ring-offset-1', 'active:scale-95', 'disabled:pointer-events-none disabled:opacity-50', - hasError && 'border-control-error checked:border-control-error checked:bg-control-error', + hasError && + 'border-control-error hover:border-control-error checked:border-control-error checked:bg-control-error checked:hover:border-control-error checked:hover:bg-control-error focus-visible:ring-red-03', )} {...props} /> diff --git a/src/components/ui/Radio/Radio.tsx b/src/components/ui/Radio/Radio.tsx index 03d3cb7..c987bdf 100644 --- a/src/components/ui/Radio/Radio.tsx +++ b/src/components/ui/Radio/Radio.tsx @@ -168,7 +168,8 @@ export const Radio = forwardRef( 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-control-focus-ring focus-visible:ring-offset-1', 'active:scale-95', 'disabled:pointer-events-none disabled:opacity-50', - hasError && 'border-control-error checked:border-control-error', + hasError && + 'border-control-error hover:border-control-error checked:border-control-error checked:hover:border-control-error focus-visible:ring-red-03', )} {...props} /> diff --git a/src/tokens/tokens.css b/src/tokens/tokens.css index b0ef36a..1e2edd7 100644 --- a/src/tokens/tokens.css +++ b/src/tokens/tokens.css @@ -82,6 +82,13 @@ --color-control-bg: var(--color-white); --color-control-bg-readonly: var(--color-off-white); + /* Button */ + --color-button-default: var(--color-blue-01); + --color-button-danger: var(--color-red-02); + --color-button-neutral: var(--color-grey-01); + --color-button-subtle-bg: var(--color-blue-04); + --color-button-subtle-text: var(--color-blue-01); + /* Radius */ --radius-sm: 4px; --radius-default: 6px;