import { ReactElement, useState } from 'react'; import { Meta, StoryObj } from '@storybook/react-webpack5'; import { fn } from 'storybook/test'; import { Bank, Star, Travel, Briefcase } from '@transferwise/icons'; import { lorem10, lorem20 } from '../../test-utils'; import Button from '../../button'; import Title from '../../title'; import { ActionPrompt, type ActionPromptProps } from './ActionPrompt'; const withComponentGrid = ({ maxWidth = 'auto', gap = '1rem' } = {}) => (Story: () => ReactElement) => (
); const meta: Meta = { title: 'Prompts/ActionPrompt', component: ActionPrompt, tags: ['new'], decorators: [withComponentGrid()], args: { title: 'Action Required', description: 'Please complete the following action to continue.', action: { label: 'Continue', onClick: fn() }, }, argTypes: { sentiment: { control: 'select', options: ['success', 'negative', 'neutral', 'warning', 'proposition'], }, title: { control: 'text', table: { type: { summary: 'ReactNode' }, }, }, description: { control: 'text', table: { type: { summary: 'ReactNode' }, }, }, media: { control: false, table: { type: { summary: 'ActionPromptMedia', detail: '{ imgSrc?: string; avatar?: { imgSrc?: string; profileName?: string; profileType?: string; asset?: ReactNode; badge?: { flagCode: string } }; aria-label?: string }', }, }, }, action: { control: false, table: { type: { summary: 'ActionPromptAction', detail: '{ label: ReactNode; onClick?: () => void; href?: string; target?: string }', }, }, }, actionSecondary: { control: false, table: { type: { summary: 'ActionPromptActionSecondary', detail: '{ label: ReactNode; onClick?: () => void; href?: string; target?: string }', }, }, }, }, }; export default meta; type Story = StoryObj; /** * Convenience controls for previewing rich markup, * not otherwise possible via Storybook */ type PreviewStoryArgs = ActionPromptProps & { previewMedia: ActionPromptProps['media']; previewOnDismiss: boolean; previewSecondaryAction: boolean; }; const previewArgGroup = { category: 'Storybook Preview options', type: { summary: undefined, }, }; const MEDIA_OPTIONS: Record = { undefined, 'Custom image (no badge)': { imgSrc: '../../wise-card.svg' }, 'Avatar: Profile image': { avatar: { imgSrc: '../../avatar-rectangle-fox.webp' } }, 'Avatar: Business (GB badge)': { avatar: { profileType: 'BUSINESS', badge: { flagCode: 'GB' } }, }, 'Avatar: Personal (EU badge)': { avatar: { profileType: 'PERSONAL', badge: { flagCode: 'EU' } }, }, 'Avatar: Icon (Bank)': { avatar: { asset: } }, 'Avatar: Icon (Star)': { avatar: { asset: } }, 'Avatar: Icon (Travel)': { avatar: { asset: } }, 'Avatar: Icon (Briefcase)': { avatar: { asset: } }, 'Avatar: Initials': { avatar: { profileName: 'John Doe' } }, }; const previewArgTypes = { previewMedia: { name: 'Preview with `media`', control: 'select', options: [ 'undefined', 'Custom image (no badge)', 'Avatar: Profile image', 'Avatar: Business (GB badge)', 'Avatar: Personal (EU badge)', 'Avatar: Icon (Bank)', 'Avatar: Icon (Star)', 'Avatar: Icon (Travel)', 'Avatar: Icon (Briefcase)', 'Avatar: Initials', ], mapping: MEDIA_OPTIONS, table: previewArgGroup, }, previewOnDismiss: { name: 'Preview with `onDismiss`', control: 'boolean', table: previewArgGroup, }, previewSecondaryAction: { name: 'Preview with `actionSecondary`', control: 'boolean', table: previewArgGroup, }, } as const; const getPropsForPreview = ( args: PreviewStoryArgs, ): [ActionPromptProps, Partial] => { const { previewMedia, previewOnDismiss, previewSecondaryAction, ...props } = args; return [ props, { media: previewMedia, onDismiss: previewOnDismiss ? fn() : undefined, actionSecondary: previewSecondaryAction ? { label: 'Cancel', onClick: fn() } : undefined, }, ]; }; export const Playground: StoryObj = { argTypes: { sentiment: { control: 'radio', options: ['success', 'negative', 'neutral', 'warning', 'proposition'], }, ...previewArgTypes, }, args: { previewMedia: { avatar: undefined }, previewOnDismiss: true, previewSecondaryAction: true, }, render: (args: PreviewStoryArgs) => { const [props, previewProps] = getPropsForPreview(args); return ; }, }; /** * ActionPrompt supports multiple sentiments to communicate different types of messages: * - `neutral` (default): General prompts * - `success`: Success confirmations * - `negative`: Error or critical actions * - `warning`: Warning messages * - `proposition`: Promotional or feature prompts */ export const Sentiments: Story = { render: (args) => ( <> {(['success', 'warning', 'negative', 'neutral', 'proposition'] as const).map((sentiment) => ( ))} ), }; /** * ActionPrompt can have just a primary action, or both primary and secondary actions. */ export const Actions: Story = { render: () => ( <> ), }; /** * ActionPrompt supports various media types to enhance visual communication. * * **Default**: Each sentiment has a default status icon that appears when no media is provided. * * **Icon Overrides**: Replace default icons with custom images, avatars with images, avatars with initials, * avatars with custom icons, or avatars with profile types and badges. */ export const MediaTypes: Story = { render: () => { return ( <> Default Icon Overrides } }} action={{ label: 'Connect bank', onClick: fn() }} /> ); }, }; /** * When `onDismiss` is provided, a close button appears allowing users to dismiss the prompt. * Note: The component itself is not automatically removed - you must handle state management. */ export const Dismissible: Story = { render: (args) => { const [showPrompt, setShowPrompt] = useState(true); return ( <> {showPrompt ? ( setShowPrompt(false)} /> ) : ( )} ); }, }; /** * While the component itself will stretch to the full available width, the text container will be * capped at `480px` to ensure optimal readability. * * [Visit wise.design](https://wise.design/components/info-prompt#writing-an-info-prompt) for guidance on writing effective prompt messages that are concise and easy to understand. */ export const ParagraphWidth: Story = { parameters: { docs: { canvas: { sourceState: 'hidden', }, }, }, args: { title: lorem10, description: lorem20, sentiment: 'success', action: { label: 'View details', onClick: fn() }, onDismiss: () => {}, }, };