← All proposals

SpinnerSequence

A new component, SpinnerSequence, that composes the existing PDS v2 Spinner and Illustration (the Spinner is not changed).

Net-new componentOpen in Storybook

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

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

import { SpinnerSequence } from './SpinnerSequence';

const businessSteps = [
	{ label: 'Creating business account...' },
	{ label: 'Creating workspace...' },
];

const meta: Meta<typeof SpinnerSequence> = {
	tags: ['new-pattern', 'ready-for-dev'],
	title: 'Component Proposals/Spinner Sequence',
	component: SpinnerSequence,
	parameters: {
		docs: {
			description: {
				component:
					'A Spinner that walks through a list of steps, each with its own text, then ends on an illustration and a final text. You set the texts, how long each shows and the illustration.',
			},
		},
	},
	argTypes: {
		activeStep: { table: { category: 'Component configuration' } },
		className: { table: { category: 'Base attributes' } },
		defaultDuration: { table: { category: 'Component configuration' } },
		finalLabel: { table: { category: 'Content' } },
		illustration: { table: { category: 'Content' } },
		illustrationName: {
			control: 'select',
			options: [
				'task',
				'createEnvLive',
				'createEnvMultidev',
				'underConstruction',
			],
			table: { category: 'Content' },
		},
		onComplete: { table: { category: 'Component configuration' } },
		size: { control: 'select', table: { category: 'Component configuration' } },
		steps: { table: { category: 'Content' } },
	},
	args: {
		defaultDuration: 2000,
		finalLabel: 'Taking you to your business account',
		illustrationName: 'task',
		size: '5xl',
		steps: businessSteps,
	},
};

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
	parameters: {
		docs: {
			description: {
				story:
					'Two steps of 2 seconds each, then the final state, which stays. Change a control to run it again.',
			},
		},
	},
	// Remount when a control changes so the sequence starts again.
	render: (args) => (
		<SpinnerSequence
			key={JSON.stringify([args.steps, args.defaultDuration, args.size])}
			{...args}
		/>
	),
};

export const OwnTimingAndSteps: Story = {
	parameters: {
		docs: {
			description: {
				story:
					'Any number of steps, each with its own `duration`. Steps without one use `defaultDuration`.',
			},
		},
	},
	args: {
		defaultDuration: 1500,
		finalLabel: 'All set',
		steps: [
			{ label: 'Checking your details...' },
			{ duration: 3000, label: 'Setting up your account...' },
			{ duration: 1000, label: 'Almost there...' },
			{ label: 'Adding your team...' },
		],
	},
};

export const AllSizes: Story = {
	parameters: {
		docs: {
			description: {
				story:
					'Every size, at the final state, so the illustration and the spinner can be compared. The illustration is drawn at the spinner size.',
			},
		},
	},
	render: (args) => (
		<FlexContainer alignItems='flex-start' flexWrap='wrap' gap='2xl'>
			{(['m', 'xl', '3xl', '5xl', '6xl'] as const).map((size) => (
				<SpinnerSequence
					{...args}
					activeStep={args.steps.length}
					finalLabel={size}
					key={size}
					size={size}
				/>
			))}
		</FlexContainer>
	),
};

export const ControlledByTheWork: Story = {
	parameters: {
		docs: {
			description: {
				story:
					'`activeStep` moves the sequence when the real work does, instead of a timer. Here the buttons stand in for the work.',
			},
		},
	},
	render: function Render(args) {
		const [activeStep, setActiveStep] = useState(0);
		return (
			<FlexContainer flexDirection='column' gap='xl'>
				<SpinnerSequence {...args} activeStep={activeStep} />
				<FlexContainer gap='m'>
					<Button
						disabled={activeStep >= args.steps.length}
						label='Next step'
						onClick={() => setActiveStep(activeStep + 1)}
						variant='secondary'
					/>
					<Button
						disabled={activeStep === 0}
						label='Start again'
						onClick={() => setActiveStep(0)}
						variant='secondary'
					/>
				</FlexContainer>
			</FlexContainer>
		);
	},
};

export const OtherIllustration: Story = {
	parameters: {
		docs: {
			description: {
				story:
					'`illustrationName` picks another PDS illustration. `illustration` takes your own node, drawn at the spinner size.',
			},
		},
	},
	args: { illustrationName: 'underConstruction' },
	render: (args) => (
		<SpinnerSequence {...args} activeStep={args.steps.length} />
	),
};