← All proposals

Model Selector

A new component built on the local MenuButton (trigger, popover, keyboard and focus behavior come from it).

Net-new componentOpen in Storybook

proposals/ModelSelector/ModelSelector.stories.tsx134 lines · View on GitHub
import { useState } from 'react';

import { FlexContainer } from '@pantheon-systems/pds-toolkit-react';
import { Meta, StoryObj } from '@storybook/react-vite';

import { ModelSelector, ModelSelectorOption } from './ModelSelector';

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

// Sample models: Zappy first, then other agents.
const MODELS: ModelSelectorOption[] = [
	{ id: 'zappy', label: 'Zappy' },
	{ id: 'haiku', label: 'Claude Haiku 5.5' },
	{ id: 'sonnet', label: 'Claude Sonnet 5.5' },
	{ id: 'opus', label: 'Claude Opus 5.5' },
];

const MODELS_WITH_DETAILS: ModelSelectorOption[] = [
	{
		description: 'Pantheon agent for your sites',
		id: 'zappy',
		label: 'Zappy',
	},
	{
		description: 'Fastest, for quick answers',
		id: 'haiku',
		label: 'Claude Haiku 5.5',
	},
	{
		description: 'Balanced speed and depth',
		id: 'sonnet',
		label: 'Claude Sonnet 5.5',
	},
	{
		description: 'Most capable, slower',
		disabled: true,
		id: 'opus',
		label: 'Claude Opus 5.5',
	},
];

const meta: Meta<typeof ModelSelector> = {
	tags: ['new-pattern'],
	title: 'Proposals/Buttons/Model Selector',
	component: ModelSelector,
	parameters: {
		docs: {
			description: {
				component:
					'**New pattern:** not in PDS v2 today. Picks which model or agent answers, from a chat input or similar. Built on `MenuButton` (menu, keyboard and focus behavior), with a trigger that has no fill in any state and a check on the selected model.',
			},
		},
	},
	argTypes: {
		className: { table: { category: 'Base attributes' } },
		disabled: { control: 'boolean', table: { category: 'Base attributes' } },
		id: { control: 'text', table: { category: 'Base attributes' } },
		onValueChange: { table: { category: 'Functions' } },
		options: { control: 'object', table: { category: 'Content' } },
		size: {
			control: 'select',
			options: SIZES,
			table: { category: 'Component configuration' },
		},
		value: { control: 'text', table: { category: 'Content' } },
	},
	args: {
		disabled: false,
		id: 'model-selector',
		options: MODELS,
		size: 's',
		value: 'zappy',
	},
};

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
	render: (args) => {
		const [value, setValue] = useState(args.value);
		return <ModelSelector {...args} onValueChange={setValue} value={value} />;
	},
};

export const Sizes: Story = {
	parameters: {
		docs: {
			description: {
				story: 'The three trigger sizes, matching `MenuButton`.',
			},
		},
	},
	render: (args) => (
		<FlexContainer alignItems='center' gap='l'>
			{SIZES.map((size) => (
				<ModelSelector
					{...args}
					id={`model-selector-${size}`}
					key={size}
					size={size}
				/>
			))}
		</FlexContainer>
	),
};

export const WithDescriptions: Story = {
	parameters: {
		docs: {
			description: {
				story:
					'Options can carry a short description (and an `iconName`). One option is `disabled`.',
			},
		},
	},
	render: (args) => {
		const [value, setValue] = useState('zappy');
		return (
			<ModelSelector
				{...args}
				onValueChange={setValue}
				options={MODELS_WITH_DETAILS}
				value={value}
			/>
		);
	},
};

export const Disabled: Story = {
	args: { disabled: true },
};