import React from 'react';
import { Meta, StoryObj } from '@storybook/react-vite';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
title: 'Proposals/Buttons/Button',
component: Button,
parameters: {
docs: {
description: {
component: 'A Component used to render a Button',
},
},
},
argTypes: {
label: {
control: 'text',
table: {
category: 'Content',
},
},
iconName: {
table: {
category: 'Content',
},
},
tooltipText: {
table: {
category: 'Content',
},
},
size: {
control: 'select',
table: {
category: 'Component configuration',
},
},
variant: {
control: 'select',
options: [
'primary',
'secondary',
'subtle',
'brand',
'highlight',
'critical',
'critical-secondary',
'link',
'reverse',
'reverse-secondary',
],
table: {
category: 'Component configuration',
},
},
displayType: {
control: 'select',
table: {
category: 'Component configuration',
},
},
isAgentic: {
control: 'boolean',
table: {
category: 'Component configuration',
},
},
isFullWidth: {
control: 'boolean',
table: {
category: 'Component configuration',
},
},
isLoading: {
control: 'boolean',
table: {
category: 'Status',
},
},
isWorking: {
control: 'boolean',
table: {
category: 'Status',
},
},
disableWhenWorking: {
control: 'boolean',
table: {
category: 'Status',
},
},
onClick: {
table: { category: 'Functions' },
},
ariaLabel: {
control: 'select',
table: {
category: 'Base attributes',
},
},
buttonType: {
control: 'select',
table: {
category: 'Base attributes',
},
},
disabled: {
table: {
category: 'Base attributes',
},
},
className: {
table: {
category: 'Base attributes',
},
},
isSplitButton: {
table: {
disable: true,
},
},
tally: {
table: {
category: 'Content',
},
},
statusDot: {
control: 'select',
table: {
category: 'Content',
},
},
},
args: {
label: 'Button',
variant: 'primary',
size: 's',
displayType: 'label-only',
buttonType: 'button',
disabled: false,
isAgentic: true,
isLoading: false,
isWorking: false,
disableWhenWorking: true,
},
};
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {},
};
export const WithIcon: Story = {
parameters: {
docs: {
description: {
story:
'Buttons can display an icon before or after the label using `displayType` (`icon-start`, `icon-end`, `icon-only`). Use icons to reinforce the action, not to replace a clear label.',
},
},
},
args: {
displayType: 'icon-start',
iconName: 'zappy',
},
};
export const WithTally: Story = {
parameters: {
docs: {
description: {
story:
'A `Tally` badge can be attached to a button to surface a count (e.g. pending items, notifications). Pass `tally.type` to choose the status color.',
},
},
},
args: {
tally: { label: 4, type: 'neutral' },
},
};
export const WithStatusDot: Story = {
parameters: {
docs: {
description: {
story:
'A `StatusDot` can be overlaid on the button to communicate real-time status (e.g. a connection that is online or degraded). Use when the button itself is the primary way users identify the resource.',
},
},
},
args: {
statusDot: 'discovery',
},
};
export const LoadingState: Story = {
parameters: {
docs: {
description: {
story:
'`isLoading` replaces the label with a spinner to indicate that data is being fetched before the action is available. Pair with `tooltipText` to explain the wait to keyboard and screen-reader users.',
},
},
},
args: {
isLoading: true,
tooltipText: 'Loading...',
},
};
export const WorkingState: Story = {
parameters: {
docs: {
description: {
story:
'`isWorking` shows a spinner alongside the label to indicate an in-progress operation triggered by a prior click. Unlike `isLoading`, the label remains visible so the user knows what action is running.',
},
},
},
args: {
isWorking: true,
tooltipText: 'This button is waiting for an action to complete',
},
};
export const WorkingStateInteractive: Story = {
parameters: {
docs: {
description: {
story:
'Set `disableWhenWorking` to `false` for an ongoing, interactive working state — such as a toggle that starts and stops a live process — where the button must stay clickable (e.g. to stop it) while `isWorking` is true.',
},
},
},
args: {
displayType: 'icon-end',
iconName: 'pause',
isWorking: true,
disableWhenWorking: false,
label: 'Investigating',
},
};
export const AllVariants: Story = {
parameters: {
docs: {
description: {
story:
'Quick reference for all button variants. Reverse variants require a dark or brand-colored background and are shown here over `--pds-color-surface-reverse`. Use the `isAgentic` control to toggle the agentic treatment; it applies to `primary` and `secondary` only.',
},
},
},
render: ({ isAgentic }) => {
const wrapStyle = {
display: 'flex',
flexWrap: 'wrap' as const,
gap: 'var(--pds-spacing-m)',
padding: 'var(--pds-spacing-m)',
};
return (
<>
<div style={wrapStyle}>
<Button
isAgentic={isAgentic}
label='Primary'
onClick={() => {}}
variant='primary'
/>
<Button
isAgentic={isAgentic}
label='Secondary'
onClick={() => {}}
variant='secondary'
/>
<Button label='Subtle' onClick={() => {}} variant='subtle' />
<Button label='Brand' onClick={() => {}} variant='brand' />
<Button label='Highlight' onClick={() => {}} variant='highlight' />
<Button label='Critical' onClick={() => {}} variant='critical' />
<Button
label='Critical Secondary'
onClick={() => {}}
variant='critical-secondary'
/>
<Button label='Link' onClick={() => {}} variant='link' />
</div>
<div
style={{
...wrapStyle,
backgroundColor: 'var(--pds-color-surface-reverse)',
}}
>
<Button label='Reverse' onClick={() => {}} variant='reverse' />
<Button
label='Reverse Secondary'
onClick={() => {}}
variant='reverse-secondary'
/>
</div>
</>
);
},
};
export const AgenticStates: Story = {
parameters: {
docs: {
description: {
story:
'`isAgentic` adds the AI gradient border and the gradient working spinner. It is supported on `primary` and `secondary`. Columns: default, disabled, working (leading icon), working (spinner).',
},
},
},
render: () => {
const variants = ['primary', 'secondary'] as const;
return (
<div
style={{
display: 'grid',
gap: 'var(--pds-spacing-m)',
gridTemplateColumns: 'repeat(4, max-content)',
padding: 'var(--pds-spacing-m)',
}}
>
{variants.map((variant) => (
<React.Fragment key={variant}>
<Button isAgentic label='Button' variant={variant} />
<Button disabled isAgentic label='Button' variant={variant} />
<Button
displayType='icon-start'
iconName='zappy'
isAgentic
label='Button'
variant={variant}
/>
<Button isAgentic isWorking label='Button' variant={variant} />
</React.Fragment>
))}
</div>
);
},
};
export const AllSizes: Story = {
parameters: {
docs: {
description: {
story:
'Quick reference for all button sizes, smallest to largest. Use the `isAgentic` control to toggle the agentic treatment.',
},
},
},
render: ({ isAgentic }) => {
const sizes = [
{ label: 'Extra small button', size: 'xs' },
{ label: 'Small button', size: 's' },
{ label: 'Medium button', size: 'm' },
{ label: 'Large button', size: 'l' },
] as const;
return (
<div
style={{
alignItems: 'center',
display: 'flex',
flexWrap: 'wrap',
gap: 'var(--pds-spacing-m)',
padding: 'var(--pds-spacing-m)',
}}
>
{sizes.map(({ label, size }) => (
<Button
isAgentic={isAgentic}
key={size}
label={label}
onClick={() => {}}
size={size}
/>
))}
</div>
);
},
};
Button
Starting point for a Button proposal.
Extends a PDS componentOpen in Storybook