← All proposals

Regenerate a suggestion

A recipe, not a component: one interactive story that composes the Suggestion Card (single choice), the Button and the PDS Compact Pagination.

Extends a PDS componentOpen in Storybook

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

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

import { Button } from '../_base/Button/Button';
import {
	SuggestionCard,
	SuggestionCardState,
	SuggestionChoice,
} from '../SuggestionCard/SuggestionCard';

const meta: Meta = {
	tags: ['new-pattern', 'ready-for-dev'],
	title: 'Recipes/Regenerate a suggestion',
	parameters: {
		docs: {
			description: {
				component:
					'**Recipe:** a composition of existing proposals, not a component. A Suggestion Card with a Regenerate button and a pager to browse the sets the agent has offered.',
			},
		},
	},
};

export default meta;

type Story = StoryObj<typeof meta>;

// Each generation of the suggestion is a set of headline options. Regenerate
// adds the next set, cycling through these.
const HEADLINE_SETS = [
	[
		{
			description: 'Bold and confident.',
			label: 'Ship faster with confidence',
		},
		{ description: 'Reassuring.', label: 'Deploy with confidence, every time' },
		{ description: 'Short and simple.', label: 'Your site, live in seconds' },
	],
	[
		{
			description: 'Benefit first.',
			label: 'Launch without the launch-day stress',
		},
		{ description: 'Direct.', label: 'Push to production in one click' },
		{ description: 'Playful.', label: 'Hit deploy. Go grab a coffee.' },
	],
	[
		{
			description: 'Outcome focused.',
			label: 'Your best site, always up to date',
		},
		{ description: 'Team angle.', label: 'Every change, shipped together' },
		{ description: 'Plain.', label: 'Deploy your site, simply' },
	],
];

export const Interactive: Story = {
	parameters: {
		docs: {
			description: {
				story:
					'Retry and browse. **Regenerate** asks the agent for a new set of options: the button shows its working state, then the card shows the new set and jumps to it. The PDS Compact Pagination (static, no page jump) sits in `actionsEnd` once there is more than one set, so people can step back to an earlier set. Each set remembers its own selection. Story-only wiring: the screen owns the sets, the selection and what Regenerate does; the card only provides the slots.',
			},
		},
	},
	render: () => {
		// The index of the HEADLINE_SETS entry behind each page, in order.
		const [pages, setPages] = useState<number[]>([0]);
		const [page, setPage] = useState(1);
		const [picked, setPicked] = useState<Record<number, string>>({ 1: '0' });
		const [isRegenerating, setIsRegenerating] = useState(false);
		const [state, setState] = useState<SuggestionCardState>('pending');

		const options = HEADLINE_SETS[pages[page - 1]];

		const regenerate = () => {
			setIsRegenerating(true);
			// Stands in for the agent's reply.
			setTimeout(() => {
				const next = pages.length + 1;
				setPages((prev) => [...prev, prev.length % HEADLINE_SETS.length]);
				setPicked((prev) => ({ ...prev, [next]: '0' }));
				setPage(next);
				setIsRegenerating(false);
			}, 1200);
		};

		return (
			<SuggestionCard
				actionsEnd={
					pages.length > 1 ? (
						<PaginationCompact
							currentPage={page}
							itemsPerPage={1}
							onPageChange={setPage}
							pageControlType='static'
							totalItemCount={pages.length}
						/>
					) : undefined
				}
				actionsStart={
					<>
						<Button
							displayType='icon-start'
							iconName='check'
							label='Use selected'
							onClick={() => setState('accepted')}
							variant='primary'
						/>
						<Button
							displayType='icon-start'
							iconName='rotate'
							isWorking={isRegenerating}
							label={isRegenerating ? 'Regenerating' : 'Regenerate'}
							onClick={regenerate}
							variant='secondary'
						/>
						<Button
							label='Dismiss'
							onClick={() => setState('dismissed')}
							variant='subtle'
						/>
					</>
				}
				description='The current headline is “Deploy your site”. Pick one to replace it.'
				state={state}
				statusLabel={state === 'dismissed' ? 'Dismissed' : 'Done'}
				title='Which headline should I use?'
				type='choice'
			>
				{options.map((option, i) => (
					<SuggestionChoice
						checked={picked[page] === String(i)}
						description={option.description}
						id={`regenerate-${page}-${i}`}
						key={`${page}-${i}`}
						label={option.label}
						name={`regenerate-${page}`}
						onChange={() =>
							setPicked((prev) => ({ ...prev, [page]: String(i) }))
						}
						selectionType='radio'
						value={String(i)}
					/>
				))}
			</SuggestionCard>
		);
	},
};