← All proposals

Agentic Button

A separate component, `AgenticButton`, for an action the agent does:

Net-new componentOpen in Storybook

proposals/AgenticButton/AgenticButton.stories.tsx188 lines · View on GitHub
import { Meta, StoryObj } from '@storybook/react-vite';

import { AgenticButton } from './AgenticButton';

const meta: Meta<typeof AgenticButton> = {
	tags: ['new-pattern', 'ready-for-dev'],
	title: 'Component Proposals/Buttons/Agentic Button',
	component: AgenticButton,
	parameters: {
		docs: {
			description: {
				component:
					'**New pattern:** not in PDS v2 today. A button for an action the agent does: the secondary Button look with the AI gradient as its border and a gradient working spinner. It takes every Button prop except `variant`.',
			},
		},
	},
	argTypes: {
		label: { control: 'text', table: { category: 'Content' } },
		iconName: { table: { category: 'Content' } },
		tooltipText: { table: { category: 'Content' } },
		tally: { table: { category: 'Content' } },
		statusDot: { control: 'select', table: { category: 'Content' } },
		size: {
			control: 'select',
			table: { category: 'Component configuration' },
		},
		displayType: {
			control: 'select',
			table: { category: 'Component configuration' },
		},
		isAnimated: {
			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: { table: { category: 'Status' } },
		disabled: { table: { category: 'Base attributes' } },
		buttonType: { table: { category: 'Base attributes' } },
		className: { table: { category: 'Base attributes' } },
		onClick: { table: { category: 'Functions' } },
	},
	args: {
		label: 'Button',
		size: 's',
		displayType: 'label-only',
		buttonType: 'button',
		disabled: false,
		isAnimated: true,
		isLoading: false,
		isWorking: false,
		disableWhenWorking: true,
	},
};

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {};

export const WithIcon: Story = {
	args: {
		displayType: 'icon-start',
		iconName: 'zappy',
	},
};

export const WithTally: Story = {
	args: {
		tally: { label: 4, type: 'neutral' },
	},
};

export const WithStatusDot: Story = {
	args: {
		statusDot: 'discovery',
	},
};

export const LoadingState: Story = {
	args: {
		isLoading: true,
		tooltipText: 'Loading...',
	},
};

export const WorkingState: Story = {
	parameters: {
		docs: {
			description: {
				story:
					'`isWorking` shows the gradient spinner alongside the label while the agent works.',
			},
		},
	},
	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 where the button must stay clickable (for example to stop it).',
			},
		},
	},
	args: {
		displayType: 'icon-end',
		iconName: 'pause',
		isWorking: true,
		disableWhenWorking: false,
		label: 'Investigating',
	},
};

export const AllStates: Story = {
	parameters: {
		docs: {
			description: {
				story: 'Default, disabled, with icon and working.',
			},
		},
	},
	render: (args) => (
		<div
			style={{
				display: 'flex',
				flexWrap: 'wrap',
				gap: 'var(--pds-spacing-m)',
				padding: 'var(--pds-spacing-m)',
			}}
		>
			<AgenticButton {...args} label='Button' />
			<AgenticButton {...args} disabled label='Button' />
			<AgenticButton
				{...args}
				displayType='icon-start'
				iconName='zappy'
				label='Button'
			/>
			<AgenticButton {...args} isWorking label='Button' />
		</div>
	),
};

export const AllSizes: Story = {
	parameters: {
		docs: {
			description: {
				story: 'Quick reference for all button sizes, smallest to largest.',
			},
		},
	},
	render: (args) => {
		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 }) => (
					<AgenticButton {...args} key={size} label={label} size={size} />
				))}
			</div>
		);
	},
};