← All proposals

Icon Button

Starting point for an IconButton proposal.

Extends a PDS componentOpen in Storybook

proposals/IconButton/IconButton.stories.tsx171 lines · View on GitHub
import React, { useState } from 'react';

import { Meta, StoryObj } from '@storybook/react-vite';

import { FlexContainer, iconList } from '@pantheon-systems/pds-toolkit-react';

import { IconButton } from './IconButton';

const SIZES = ['xs', 's', 'm'] as const;

const meta: Meta<typeof IconButton> = {
	title: 'Proposals/Buttons/Icon Button',
	component: IconButton,
	parameters: {
		docs: {
			description: {
				component: 'Icon-only button implementation',
			},
		},
	},
	argTypes: {
		ariaLabel: {
			control: 'text',
			table: { category: 'Content' },
		},
		iconName: {
			control: 'select',
			options: iconList,
			table: { category: 'Component configuration' },
		},
		buttonType: {
			control: 'select',
			table: { category: 'Base attributes' },
		},
		disabled: {
			control: 'boolean',
			table: { category: 'Base attributes' },
		},
		onClick: {
			table: { category: 'Functions' },
		},
		className: {
			table: {
				category: 'Base attributes',
			},
		},
		hasBorder: {
			control: 'boolean',
			table: { category: 'Component configuration' },
		},
		hasTooltip: {
			control: 'boolean',
			table: { category: 'Component configuration' },
		},
		size: {
			control: 'select',
			options: SIZES,
			table: { category: 'Component configuration' },
		},
		variant: {
			control: 'select',
			table: { category: 'Component configuration' },
		},
		icon2Name: {
			control: 'select',
			options: iconList,
			table: { category: 'Component configuration' },
		},
		isAgentic: {
			control: 'boolean',
			table: { category: 'Component configuration' },
		},
		isPressed: {
			control: 'boolean',
			table: { category: 'State' },
		},
		pressedIconName: {
			control: 'select',
			options: iconList,
			table: { category: 'Component configuration' },
		},
	},
	args: {
		ariaLabel: 'Ask Zappy',
		buttonType: 'button',
		disabled: false,
		iconName: 'zappy',
		hasBorder: true,
		isAgentic: true,
		onClick: () => {
			console.log('Icon button clicked');
		},
		size: 'm',
		hasTooltip: true,
		variant: 'standard',
	},
};

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {};

export const Sizes: Story = {
	parameters: {
		docs: {
			description: {
				story:
					'All available `size` values, smallest to largest: `xs`, `s`, `m`, borderless (the default) and with a border. The bordered row follows the `isAgentic` control: on gives the AI gradient border, off gives the default gray border.',
			},
		},
	},
	render: (args) => {
		const looks = [
			{ hasBorder: false, isAgentic: false, name: 'Borderless' },
			{ hasBorder: true, isAgentic: args.isAgentic, name: 'With border' },
		];

		return (
			<FlexContainer flexDirection='column' gap='m'>
				{looks.map(({ hasBorder, isAgentic, name }) => (
					<FlexContainer alignItems='center' gap='m' key={name}>
						<span style={{ width: '8rem' }}>{name}</span>
						{SIZES.map((size) => (
							<IconButton
								key={size}
								{...args}
								hasBorder={hasBorder}
								isAgentic={isAgentic}
								size={size}
							/>
						))}
					</FlexContainer>
				))}
			</FlexContainer>
		);
	},
};

export const WithBorder: Story = {
	args: {
		hasBorder: true,
	},
};

export const Pressed: Story = {
	parameters: {
		docs: {
			description: {
				story:
					'Demonstrates the `isPressed` toggle pattern. Pressed fills the button, and is announced to screen readers through `aria-pressed`. Pass `pressedIconName` to swap the icon as well. Use the `isAgentic` control to toggle the agentic treatment.',
			},
		},
	},
	render: ({ isAgentic }) => {
		const [isPressed, setIsPressed] = useState(false);

		return (
			<IconButton
				hasBorder
				ariaLabel='Ask Zappy'
				iconName='zappy'
				isAgentic={isAgentic}
				isPressed={isPressed}
				onClick={() => setIsPressed((v) => !v)}
			/>
		);
	},
};