← All proposals

Thinking State

A small inline status: the agent icon (Zappy by default), a label ("Thinking..."), and the elapsed time in the code font ("17.1s").

Net-new componentOpen in Storybook

proposals/ThinkingState/ThinkingState.stories.tsx117 lines · View on GitHub
import { useEffect, useState } from 'react';

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

import { ThinkingState } from './ThinkingState';

const meta: Meta<typeof ThinkingState> = {
	tags: ['new-pattern'],
	title: 'Proposals/Thinking State',
	component: ThinkingState,
	parameters: {
		docs: {
			description: {
				component:
					'**New pattern:** not in PDS v2 today. Shows that an agent is working: an icon, what it is doing, and how long it has been at it.',
			},
		},
	},
	argTypes: {
		className: { table: { category: 'Base attributes' } },
		iconName: {
			control: 'select',
			options: ['zappy', 'sparklesSolid'],
			table: { category: 'Content' },
		},
		label: { control: 'text', table: { category: 'Content' } },
		size: {
			control: 'select',
			options: ['xs', 's', 'm'],
			table: { category: 'Component configuration' },
		},
		showTimer: {
			control: 'boolean',
			table: { category: 'Component configuration' },
		},
		state: {
			control: 'select',
			options: ['active', 'completed', 'stopped'],
			table: { category: 'State' },
		},
		time: { control: 'text', table: { category: 'Content' } },
	},
	args: {
		iconName: 'zappy',
		label: 'Thinking...',
		showTimer: false,
		size: 's',
		state: 'active',
		time: '17.1s',
	},
};

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {};

export const Completed: Story = {
	args: {
		label: 'Thought for 17.1s',
		state: 'completed',
	},
	parameters: {
		docs: {
			description: {
				story:
					'When the agent has finished, set `state` to `completed` and pass a past-tense label. The shimmer and the icon pulse stop. The consumer writes the wording, including the duration.',
			},
		},
	},
};

export const Stopped: Story = {
	args: {
		label: 'Stopped after 17.1s',
		showTimer: false,
		state: 'stopped',
	},
	parameters: {
		docs: {
			description: {
				story:
					'When the user stops the agent, set `state` to `stopped`. There is no motion, and the icon and label are muted so it reads as cut off, not finished. The consumer writes the wording.',
			},
		},
	},
};

export const LiveTimer: Story = {
	parameters: {
		docs: {
			description: {
				story:
					'The consumer owns the timer and passes the formatted time. Here a story-only counter ticks every 100ms while `state` is `active`.',
			},
		},
	},
	render: (args) => {
		const [tenths, setTenths] = useState(0);

		useEffect(() => {
			if (args.state !== 'active') return;
			const timer = setInterval(() => setTenths((t) => t + 1), 100);
			return () => clearInterval(timer);
		}, [args.state]);

		return (
			<ThinkingState
				{...args}
				showTimer
				time={`${(tenths / 10).toFixed(1)}s`}
			/>
		);
	},
};