← All proposals

Chain of Thought

A collapsible list.

Net-new componentOpen in Storybook

proposals/ChainOfThought/ChainOfThought.stories.tsx217 lines · View on GitHub
import {
	CodeBlock,
	FlexContainer,
	Tag,
} from '@pantheon-systems/pds-toolkit-react';
import { Meta, StoryObj } from '@storybook/react-vite';

import { TimelineItem } from '../Timeline/TimelineItem';
import { SlotPlaceholder } from '../_story-helpers/SlotPlaceholder';

import { ChainOfThought } from './ChainOfThought';

const meta: Meta<typeof ChainOfThought> = {
	tags: ['new-pattern', 'ready-for-dev'],
	title: 'Component Proposals/Chain of Thought',
	component: ChainOfThought,
	parameters: {
		docs: {
			description: {
				component:
					'**New pattern:** not in PDS v2 today. A collapsible record of what an agent did. The summary line is a Thinking State rendered from `label`, `isActive` and `time`; the body depends on the variant: plain steps, reasoning text, a web search, or coding tool calls. Rows are Timeline Item.',
			},
		},
	},
	argTypes: {
		className: { table: { category: 'Base attributes' } },
		isOpen: { control: 'boolean', table: { category: 'State' } },
		expandWhenActive: {
			control: 'boolean',
			table: { category: 'Component configuration' },
		},
		iconName: {
			control: 'select',
			options: ['zappy', 'sparklesSolid'],
			table: { category: 'Content' },
		},
		isActive: { control: 'boolean', table: { category: 'State' } },
		label: { control: 'text', table: { category: 'Content' } },
		size: {
			control: 'select',
			options: ['s', 'm'],
			table: { category: 'Component configuration' },
		},
		variant: {
			control: 'select',
			options: ['steps', 'reasoning', 'search', 'coding'],
			table: { category: 'Component configuration' },
		},
	},
	args: {
		isOpen: true,
		label: 'Thought for',
		showTimer: true,
		size: 's',
		time: '4s',
	},
};

export default meta;

type Story = StoryObj<typeof meta>;

export const Steps: Story = {
	render: (args) => (
		<ChainOfThought {...args}>
			<TimelineItem label='Confirmed edit permission' status='done' />
			<TimelineItem label='Read the page' secondaryText='home' status='done' />
			<TimelineItem label='Reserved the page for editing' status='done' />
			<TimelineItem
				detail='Updated the heading, two paragraphs and two links.'
				label='Applied changes'
				secondaryText='5 edits'
				status='done'
			/>
			<TimelineItem label='Saved changes' status='done' />
		</ChainOfThought>
	),
};

export const Working: Story = {
	args: {
		expandWhenActive: true,
		isActive: true,
		isOpen: false,
		showTimer: false,
	},
	render: (args) => (
		<ChainOfThought {...args} label='Working...'>
			<TimelineItem label='Confirmed edit permission' status='done' />
			<TimelineItem label='Read the page' secondaryText='home' status='done' />
			<TimelineItem label='Reserved the page for editing' status='active' />
			<TimelineItem label='Applied changes' status='pending' />
			<TimelineItem label='Saved changes' status='pending' />
		</ChainOfThought>
	),
};

export const Reasoning: Story = {
	args: { variant: 'reasoning' },
	render: (args) => (
		<ChainOfThought {...args}>
			<p>
				Summer demand spikes for stone-fruit flavors — peach and apricot lead.
			</p>
			<p>
				I should check cone inventory before promoting a waffle-bowl special.
			</p>
		</ChainOfThought>
	),
};

export const Search: Story = {
	args: { variant: 'search' },
	render: (args) => (
		<ChainOfThought {...args} label='Searched the web'>
			<TimelineItem
				iconName='magnifyingGlass'
				isQuiet
				label='best waffle cone supplier'
			/>
			<TimelineItem
				iconName='globe'
				label='Joy Cone'
				secondaryText='joycone.com'
			/>
			<TimelineItem
				iconName='globe'
				label='WebstaurantStore'
				secondaryText='webstaurantstore.com'
			/>
			<TimelineItem
				iconName='globe'
				label='The Konery'
				secondaryText='thekonery.com'
			/>
			<TimelineItem isQuiet label='+7 more' />
		</ChainOfThought>
	),
};

export const Coding: Story = {
	args: { variant: 'coding' },
	render: (args) => (
		<ChainOfThought {...args} label='Ran 3 tools'>
			<TimelineItem label='Read' secondaryText={<code>flavors.ts</code>} />
			<TimelineItem
				label='Edit'
				secondaryText={
					<>
						<code>ChurnSchedule.tsx</code>{' '}
						<span className='pds-timeline-item__added'>+74</span>{' '}
						<span className='pds-timeline-item__removed'>-41</span>
					</>
				}
			/>
			<TimelineItem label='Run' secondaryText={<code>npm run freeze</code>} />
		</ChainOfThought>
	),
};

export const Slot: Story = {
	args: { isActive: true, label: 'Zappy is investigating errors' },
	parameters: {
		docs: {
			description: {
				story:
					'Each `TimelineItem` has a `children` slot under it, always visible. What goes in it is up to the screen. The first list shows the slots. The second is an example: the finished step keeps its output in `detail`, collapsed, and the active step shows its content open.',
			},
		},
	},
	render: (args) => (
		<FlexContainer flexDirection='column' gap='xl'>
			<ChainOfThought {...args}>
				<TimelineItem label='Reading the failed build logs' status='done'>
					<SlotPlaceholder label='children' style={{ minHeight: '5rem' }} />
				</TimelineItem>
				<TimelineItem label='Comparing composer.json' status='active'>
					<SlotPlaceholder label='children' style={{ minHeight: '5rem' }} />
				</TimelineItem>
			</ChainOfThought>
			<ChainOfThought {...args}>
				<TimelineItem
					detail={
						<FlexContainer flexDirection='column' gap='xs'>
							<CodeBlock
								code={
									'ERROR: Your requirements could not be resolved to an installable set of packages.\n  drupal/pathauto is locked to ^1.11 and an update was not requested.'
								}
								hasCopyButton={false}
								language='bash'
							/>
							<FlexContainer gap='xs'>
								<Tag size='xs' tagColor='slate' tagLabel='composer.json' />
								<Tag size='xs' tagColor='slate' tagLabel='composer.lock' />
							</FlexContainer>
						</FlexContainer>
					}
					label='Reading the failed build logs and error trace'
					status='done'
				/>
				<TimelineItem
					label='Comparing composer.json against the last known-good commit'
					status='active'
				>
					<CodeBlock
						code={
							'-  "drupal/pathauto": "^1.11"\n+  "drupal/pathauto": "^1.13"'
						}
						hasCopyButton={false}
						language='diff'
					/>
				</TimelineItem>
			</ChainOfThought>
		</FlexContainer>
	),
};