import type { ReactElement } from 'react'; import type { Meta, StoryObj } from '@storybook/react-webpack5'; import { action } from 'storybook/actions'; import { Clock, Taxi, Travel } from '@transferwise/icons'; import { lorem5 } from '../../test-utils'; import Link from '../../link'; import { InlinePrompt, InlinePromptProps } from './InlinePrompt'; const withComponentGrid = ({ maxWidth = 'auto', gap = '1rem' } = {}) => (Story: () => ReactElement) => (
); export default { title: 'Prompts/InlinePrompt', component: InlinePrompt, tags: ['new'], decorators: [withComponentGrid()], args: { loading: false, muted: false, children: lorem5, }, argTypes: { sentiment: { control: 'select', options: ['positive', 'negative', 'neutral', 'warning', 'proposition'], }, loading: { control: 'boolean', }, muted: { control: 'boolean', }, children: { control: 'text', table: { type: { summary: 'ReactNode' }, }, }, }, } satisfies Meta; /** * Convenience controls for previewing rich markup, * not otherwise possible via Storybook */ type PreviewStoryArgs = InlinePromptProps & { previewMedia: InlinePromptProps['media']; }; const previewArgGroup = { category: 'Storybook Preview options', type: { summary: undefined, }, }; const previewArgTypes = { previewMedia: { name: 'Preview with `media`', control: 'boolean', mapping: { false: undefined, true: , }, table: previewArgGroup, }, } as const; const getPropsForPreview = ( args: PreviewStoryArgs, ): [InlinePromptProps, Partial] => { const { previewMedia, ...props } = args; return [ props, { media: previewMedia, }, ]; }; export const Playground: StoryObj = { argTypes: previewArgTypes, args: { previewMedia: false, }, render: (args: PreviewStoryArgs) => { const [props, previewProps] = getPropsForPreview(args); return ; }, }; /** * Aside from the known `neutral`, `negative`, `warning` and `positive` sentiments, `InlinePrompt` is the first of the prompts to introduce a new `proposition` sentiment, which can be used to encourage the user to take an action that might benefit them. * * By default, its associated icon for the prompt is `GiftBox`. */ export const Sentiments: StoryObj = { argTypes: previewArgTypes, args: { previewMedia: false, }, render: (args: PreviewStoryArgs) => { const [props, previewProps] = getPropsForPreview(args); return ( <> This is a neutral prompt. This is a negative prompt. This is a warning prompt. This is a positive prompt. This is a proposition prompt. ); }, }; /** * Inline prompts are also used when forms need to check something in the background, like * verifying an account number is real. Turning on the loading state will replace the icon with * a spinner while waiting for the check to finish. */ export const Loading: StoryObj = { argTypes: previewArgTypes, args: { loading: true, previewMedia: false, }, render: (args: PreviewStoryArgs) => { const [props, previewProps] = getPropsForPreview(args); return ; }, }; /** * Inline prompt is usually associated with a different component, such as `Input` or `ListItem`. * When those components are disabled, the prompt is often used to communicate to the user why they * cannot interact with them. For that reason, the prompt cannot inherit the usual disabled visual * style, and it must retain its interactivity. To bring these 2 components visually closer to each * other, `muted` prop introduces slightly reduced opacity and luminosity as well as a special * backslash circle icon. */ export const Muted: StoryObj = { argTypes: previewArgTypes, args: { muted: true, previewMedia: false, }, render: (args: PreviewStoryArgs) => { const [props, previewProps] = getPropsForPreview(args); return ( Please confirm your residential address to activate this feature. ); }, }; /** * `InlinePrompt` can include a single instance of the `Link` component, which can be rendered as * either HTML anchor or button. That element will spread across the whole surface of the Prompt * so it's more accessible for motor–impaired and touch devices users. * * > ⚠️ For that reason it's crucial to only include **one** interactive element inside a prompt. */ export const Interactivity: StoryObj = { argTypes: previewArgTypes, args: { muted: true, previewMedia: false, }, render: (args: PreviewStoryArgs) => ( <> This prompt includes a{' '} link to some resource {' '} to help the user in their journey. {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */} This prompt includes an inline button than can e.g. trigger a modal. ), }; /** * While all main sentiments (`warning`, `negative`, `positive` and`neutral`) are associated with a * default `StatusIcon`s, we also allow for Icon overrides to bring the prompt's visual language * closer to the prompt's content.

* It's also possible to override the default StatusIcon's accessible name announced by screen * readers via `mediaLabel` prop, which is especially useful for the `proposition` sentiment. *

* **NB**: If you're setting a label on a custom Icon, the accessible name should be provided via * Icon's `title` prop instead. */ export const IconOverrides: StoryObj = { render: (args: PreviewStoryArgs) => { return ( <> This prompt uses default Icon, but with a custom label for screen readers. } sentiment="warning"> The account verification is taking longer than usual. } sentiment="positive"> Your travel account is set up and ready to use. } sentiment="proposition"> Connect Wise with your taxi app to get exclusive discounts. ); }, }; /** * `InlinePrompt` can either hug its content or take the full width of its container, * depending on the `width` prop. * Components like `ListItem`, `ExpressiveMoneyInput` or similar will often set it to * `auto`, while plain inputs will prefer `full` to visually match their boundaries. * * **NB**: `InlinePrompt` in its default – `auto` – width will expand to the full width of its container when the content spans across multiple lines. */ export const SizingStrategies: StoryObj = { render: (args: PreviewStoryArgs) => { return ( <> } sentiment="positive" width="full"> This prompt will take the full width of its container. } sentiment="positive"> This prompt will hug its content. } sentiment="positive"> This prompt is configured to hug its content, but since the content is long enough to span across multiple lines, it will expand to the full width of its container. And no, you should not put pages of text here in the first place, but we recognise that some translations are longer than others, and people also use accessibility features that increase font size. ); }, }; /** * When configured with any of the supported sentiments, the colour scheme of the component will propagate to all of its supported descendants, such as instances of a `Link`, `Icon`, and `StatusIcon`. */ export const SentimentAwareness: StoryObj = { argTypes: previewArgTypes, args: { previewMedia: false, }, render: (args: PreviewStoryArgs) => { const [props, previewProps] = getPropsForPreview(args); return ( <> This is a negative prompt with an inline link. This is a positive prompt with an inline link. This is a proposition prompt with an inline link. ); }, };