import { Meta, StoryObj } from '@storybook/react-webpack5';
import { fn } from 'storybook/test';
import { Freeze, ArrowRight, ChevronRight } from '@transferwise/icons';
import { Flag } from '@wise/art';
import { lorem10, lorem20 } from '../../test-utils';
import SentimentSurface from '../../sentimentSurface';
import type { ButtonProps } from '../Button.types';
import Button from '../Button.resolver';
const withContainer = (Story: any) => (
);
/**
* Used for showing multiple components within a Canvas.
* @decorator
*/
const withComponentGrid =
(maxWidth = 'auto') =>
(Story: any) => (
);
/**
* Not all stories need access to all controls as it causes unnecessary UI noise.
*/
const hideControls = (args: string[]) => {
const hidden = [
'addonStart',
'addonEnd',
'onClick',
'onBlur',
'onFocus',
'onKeyDown',
'onMouseEnter',
'onMouseLeave',
...args,
];
return Object.fromEntries(hidden.map((item) => [item, { table: { disable: true } }]));
};
/**
* Convenience controls for previewing rich markup,
* not otherwise possible via Storybook
*/
type PreviewStoryArgs = Parameters[0] & {
previewAddonStart: boolean | ButtonProps['addonStart'];
previewAddonEnd: boolean | ButtonProps['addonEnd'];
};
const previewArgTypes = {
previewAddonStart: {
control: 'select',
options: [
'undefined',
'icon',
'avatar: flag',
'avatar: initials',
'avatar: icon',
'avatar: image',
'avatar: double',
],
name: 'Preview with `addonStart`',
mapping: {
undefined,
icon: {
type: 'icon',
value: ,
},
'avatar: flag': {
type: 'avatar',
value: [{ asset: }],
},
'avatar: initials': {
type: 'avatar',
value: [{ profileName: 'Jay Jay' }],
},
'avatar: icon': {
type: 'avatar',
value: [{ asset: }],
},
'avatar: image': {
type: 'avatar',
value: [{ imgSrc: '../avatar-square-dude.webp' }],
},
'avatar: double': {
type: 'avatar',
value: [{ asset: }, { imgSrc: '../avatar-square-dude.webp' }],
},
},
table: {
category: 'Preview options',
type: {
summary: undefined,
},
},
description:
'**NB:** The `lg` button does not support any addons. `md` button accepts either icons or avatars, while `sm` is restricted to icons only.',
},
previewAddonEnd: {
control: 'boolean',
name: 'Preview with `addonEnd`',
mapping: {
true: { type: 'icon', value: },
},
description:
'**NB:** The `lg` button does not support any addons. `md` and `sm` allow for the use of icons only.',
table: {
category: 'Preview options',
type: {
summary: undefined,
},
},
},
} as const;
const getPropsForPreview = (args: PreviewStoryArgs) => {
const { previewAddonStart, previewAddonEnd, ...props } = args as {
previewAddonStart: ButtonProps['addonStart'];
previewAddonEnd: ButtonProps['addonEnd'];
props: typeof Button;
};
return [
props,
{
addonStart: previewAddonStart,
addonEnd: previewAddonEnd,
},
];
};
/**
* The stories below document the new Button API, which requires `v2` prop to function.
For more details please refer to the [release notes](https://transferwise.atlassian.net/wiki/spaces/DS/pages/3542158737/Button+Updates+New+sizes+Loader+and+Cue) and the [design spec](https://wise.design/components/button).
* You can still find the old Button documentation under [Legacy Button](?path=/docs/actions-button-legacy--docs)
*
*
*/
const meta: Meta = {
component: Button,
title: 'Actions/Button',
argTypes: {
v2: {
table: {
readonly: true,
},
},
size: {
type: {
name: 'enum',
value: ['lg', 'md', 'sm'],
},
table: {
type: {
summary: 'ButtonSize',
},
},
},
priority: {
type: {
name: 'enum',
value: ['primary', 'secondary', 'secondary-neutral', 'tertiary'],
},
table: {
type: {
summary: 'ButtonPriority',
},
defaultValue: { summary: '"primary"' },
},
},
sentiment: {
type: {
name: 'enum',
value: ['default', 'negative'],
},
table: {
type: {
summary: 'ButtonSentiment',
},
},
},
disabled: {
table: {
defaultValue: { summary: 'false' },
},
},
loading: {
table: {
defaultValue: { summary: 'false' },
},
},
block: {
table: {
defaultValue: { summary: 'false' },
},
},
href: {
type: {
name: 'string',
},
},
target: {
type: {
name: 'string',
},
table: {
type: {
summary: 'string',
},
},
},
addonStart: {
control: 'object',
},
addonEnd: {
control: 'object',
},
type: {
control: 'text',
table: {
type: {
summary: 'string',
},
},
},
htmlType: {
table: {
disable: true,
},
},
children: {
table: {
type: {
summary: 'ReactNode',
},
},
},
id: {
table: {
category: 'Common',
},
},
className: {
table: {
category: 'Common',
},
},
onClick: {
table: {
category: 'Common',
},
},
},
args: {
v2: true,
size: undefined,
priority: undefined,
sentiment: undefined,
disabled: false,
loading: false,
block: false,
href: undefined,
target: undefined,
as: undefined,
type: undefined,
addonStart: undefined,
addonEnd: undefined,
className: undefined,
children: 'Button text',
onClick: fn(),
},
decorators: [withContainer],
};
export default meta;
type Story = StoryObj;
export const Playground: StoryObj = {
render: function Render(args: PreviewStoryArgs) {
const [props, previewProps] = getPropsForPreview(args);
return ;
},
args: {
onBlur: fn(),
onFocus: fn(),
onKeyDown: fn(),
onMouseEnter: fn(),
onMouseLeave: fn(),
previewAddonStart: false,
previewAddonEnd: false,
},
argTypes: {
onClick: { table: { disable: true } },
onBlur: { table: { disable: true } },
onFocus: { table: { disable: true } },
onKeyDown: { table: { disable: true } },
onMouseEnter: { table: { disable: true } },
onMouseLeave: { table: { disable: true } },
...previewArgTypes,
},
};
/**
* There are two different types of button – default and negative – designed to emphasise the nature of the action.
* **NB:** Sentiment only applies to `primary` and `secondary` priorities.
* [Design documentation](https://wise.design/components/button#types)
*/
export const Sentiment: StoryObj = {
render: function Render(args: PreviewStoryArgs) {
const [props, previewProps] = getPropsForPreview(args);
return (
<>
>
);
},
argTypes: {
...hideControls([
'sentiment',
'priority',
'block',
'href',
'target',
'children',
'type',
'className',
]),
...previewArgTypes,
},
args: {
previewAddonStart: false,
previewAddonEnd: false,
},
decorators: [withComponentGrid('30rem')],
};
/**
* Priorities set a visual hierarchy amongst the buttons displayed on the
* screen to help more important buttons to take precedence over others.
* [Design documentation](https://wise.design/components/button#priorities)
*/
export const Priority: StoryObj = {
render: function Render(args: PreviewStoryArgs) {
const [props, previewProps] = getPropsForPreview(args);
return (
<>
>
);
},
argTypes: {
...hideControls(['priority', 'block', 'href', 'target', 'children', 'type', 'className']),
...previewArgTypes,
},
args: {
previewAddonStart: false,
previewAddonEnd: false,
},
decorators: [withComponentGrid()],
};
/**
* There are three different button sizes – small (`sm`), medium (`md`) and large (`lg`) – each used for different purposes.
* [Design documentation](https://wise.design/components/button#sizes)
*/
export const Size: StoryObj = {
render: function Render(args: PreviewStoryArgs) {
const [props, previewProps] = getPropsForPreview(args);
return (
<>
>
);
},
argTypes: {
...hideControls(['size', 'block', 'href', 'target', 'children', 'type', 'className']),
...previewArgTypes,
},
args: {
previewAddonStart: false,
previewAddonEnd: false,
},
decorators: [withComponentGrid()],
};
/**
* If `href` prop is set, the component will be automatically rendered as an HTML anchor element.
*/
export const AsAnchor: StoryObj = {
render: function Render(args: PreviewStoryArgs) {
const [props, previewProps] = getPropsForPreview(args);
return (
);
},
argTypes: {
...hideControls(['block']),
...previewArgTypes,
},
args: {
href: '#',
previewAddonStart: false,
previewAddonEnd: false,
onClick: undefined,
},
};
/**
* Buttons can be in `disabled` or `loading` states to indicate they're not currently actionable.
*/
export const States: StoryObj = {
render: function Render(args: PreviewStoryArgs) {
const [props, previewProps] = getPropsForPreview(args);
return (
<>
>
);
},
argTypes: {
...hideControls(['disabled', 'loading', 'block']),
...previewArgTypes,
},
args: {
previewAddonStart: false,
previewAddonEnd: false,
},
decorators: [withComponentGrid()],
};
/**
* A Button that takes up the full width of its container (`display: block`).
*/
export const DisplayBlock: StoryObj = {
render: function Render(args: PreviewStoryArgs) {
const [props, previewProps] = getPropsForPreview(args);
return (
);
},
argTypes: {
...hideControls([
'href',
'target',
'priority',
'sentiment',
'disabled',
'children',
'type',
'className',
]),
...previewArgTypes,
},
args: {
block: true,
previewAddonStart: false,
previewAddonEnd: false,
},
};
/**
* Icons are only supported for `sm` and `md` size Buttons.
* [Design documentation](https://wise.design/components/button#accessories)
*/
export const WithIcons: StoryObj = {
render: function Render(args: PreviewStoryArgs) {
const [props] = getPropsForPreview(args);
return (
<>
}}>
With start icon
}}>
With end icon
}}
addonEnd={{ type: 'icon', value: }}
>
With both icons
>
);
},
argTypes: {
...hideControls(['href', 'target', 'sentiment', 'disabled', 'children', 'type', 'className']),
},
args: {
size: 'md',
},
parameters: {
docs: {
source: {
code: `
<>
}}>
With start icon
}}>
With end icon
}} addonEnd={{ type: 'icon', value: }}>
With both icons
>
`,
},
},
},
decorators: [withComponentGrid()],
};
/**
* Avatars are only supported by the `md` size Buttons and are only allowed before the label.
* [Design documentation](https://wise.design/components/button#accessories)
*/
export const WithAvatars: StoryObj = {
render: function Render(args: PreviewStoryArgs) {
const [props] = getPropsForPreview(args);
return (
<>
}] }}>
With single avatar
}, { asset: }],
}}
>
With double avatar
>
);
},
argTypes: hideControls([
'href',
'target',
'sentiment',
'disabled',
'children',
'type',
'className',
]),
args: {
size: 'md',
},
parameters: {
docs: {
source: {
code: `
<>
}]
}}
>
With single avatar
}, { asset: }]
}}
>
With double avatar
>
`,
},
},
},
decorators: [withComponentGrid()],
};
/**
* **NB:** The button doesn't know how long its label is, how many words it consists of and
* how zoomed in it is. That is likely to lead to scenarios in which the text will overflow
* its container. This is being investigated.
*/
export const DealingWithLongText: StoryObj = {
render: function Render(args: PreviewStoryArgs) {
const [props, previewProps] = getPropsForPreview(args);
return (
{lorem20}
{lorem20}
);
},
argTypes: {
...hideControls([
'href',
'target',
'priority',
'sentiment',
'disabled',
'loading',
'children',
'as',
'type',
'className',
]),
...previewArgTypes,
},
args: {
block: true,
previewAddonStart: false,
previewAddonEnd: false,
},
};
/**
* `Button` is sentiment-aware and will automatically adjust its colours if wrapped inside
* the [SentimentSurface](?path=/docs/foundations-sentimentsurface--docs) component
*/
export const SentimentAwareness: Story = {
render: (args) => {
return (
<>
{(['success', 'warning', 'negative', 'neutral', 'proposition'] as const).map(
(sentiment) => (
}}
addonStart={{ type: 'avatar', value: [{ asset: }] }}
>
Primary
}}
addonStart={{ type: 'avatar', value: [{ asset: }] }}
>
Secondary
}}
addonStart={{ type: 'avatar', value: [{ asset: }] }}
>
Secondary Neutral
}}
addonStart={{ type: 'avatar', value: [{ asset: }] }}
>
Disabled
),
)}
>
);
},
parameters: {
docs: {
source: { type: 'dynamic' },
canvas: {
sourceState: 'hidden',
},
},
},
decorators: [
(Story) => (
),
],
};