@transferwise/components 46.103.1 → 46.104.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/build/index.js +2 -0
  2. package/build/index.js.map +1 -1
  3. package/build/index.mjs +1 -0
  4. package/build/index.mjs.map +1 -1
  5. package/build/listItem/AdditionalInfo/ListItemAdditionalInfo.js +56 -0
  6. package/build/listItem/AdditionalInfo/ListItemAdditionalInfo.js.map +1 -0
  7. package/build/listItem/AdditionalInfo/ListItemAdditionalInfo.mjs +54 -0
  8. package/build/listItem/AdditionalInfo/ListItemAdditionalInfo.mjs.map +1 -0
  9. package/build/listItem/AvatarLayout/ListItemAvatarLayout.js +23 -0
  10. package/build/listItem/AvatarLayout/ListItemAvatarLayout.js.map +1 -0
  11. package/build/listItem/AvatarLayout/ListItemAvatarLayout.mjs +21 -0
  12. package/build/listItem/AvatarLayout/ListItemAvatarLayout.mjs.map +1 -0
  13. package/build/listItem/AvatarView/ListItemAvatarView.js +23 -0
  14. package/build/listItem/AvatarView/ListItemAvatarView.js.map +1 -0
  15. package/build/listItem/AvatarView/ListItemAvatarView.mjs +21 -0
  16. package/build/listItem/AvatarView/ListItemAvatarView.mjs.map +1 -0
  17. package/build/listItem/Button/ListItemButton.js +43 -0
  18. package/build/listItem/Button/ListItemButton.js.map +1 -0
  19. package/build/listItem/Button/ListItemButton.mjs +41 -0
  20. package/build/listItem/Button/ListItemButton.mjs.map +1 -0
  21. package/build/listItem/Checkbox/ListItemCheckbox.js +30 -0
  22. package/build/listItem/Checkbox/ListItemCheckbox.js.map +1 -0
  23. package/build/listItem/Checkbox/ListItemCheckbox.mjs +28 -0
  24. package/build/listItem/Checkbox/ListItemCheckbox.mjs.map +1 -0
  25. package/build/listItem/IconButton/ListItemIconButton.js +56 -0
  26. package/build/listItem/IconButton/ListItemIconButton.js.map +1 -0
  27. package/build/listItem/IconButton/ListItemIconButton.mjs +54 -0
  28. package/build/listItem/IconButton/ListItemIconButton.mjs.map +1 -0
  29. package/build/listItem/Image/ListItemImage.js +31 -0
  30. package/build/listItem/Image/ListItemImage.js.map +1 -0
  31. package/build/listItem/Image/ListItemImage.mjs +29 -0
  32. package/build/listItem/Image/ListItemImage.mjs.map +1 -0
  33. package/build/listItem/ListItem.js +309 -0
  34. package/build/listItem/ListItem.js.map +1 -0
  35. package/build/listItem/ListItem.mjs +304 -0
  36. package/build/listItem/ListItem.mjs.map +1 -0
  37. package/build/listItem/ListItemContext.js +8 -0
  38. package/build/listItem/ListItemContext.js.map +1 -0
  39. package/build/listItem/ListItemContext.mjs +6 -0
  40. package/build/listItem/ListItemContext.mjs.map +1 -0
  41. package/build/listItem/Navigation/ListItemNavigation.js +44 -0
  42. package/build/listItem/Navigation/ListItemNavigation.js.map +1 -0
  43. package/build/listItem/Navigation/ListItemNavigation.mjs +42 -0
  44. package/build/listItem/Navigation/ListItemNavigation.mjs.map +1 -0
  45. package/build/listItem/Prompt/ListItemPrompt.js +59 -0
  46. package/build/listItem/Prompt/ListItemPrompt.js.map +1 -0
  47. package/build/listItem/Prompt/ListItemPrompt.mjs +54 -0
  48. package/build/listItem/Prompt/ListItemPrompt.mjs.map +1 -0
  49. package/build/listItem/Radio/ListItemRadio.js +30 -0
  50. package/build/listItem/Radio/ListItemRadio.js.map +1 -0
  51. package/build/listItem/Radio/ListItemRadio.mjs +28 -0
  52. package/build/listItem/Radio/ListItemRadio.mjs.map +1 -0
  53. package/build/listItem/Switch/ListItemSwitch.js +30 -0
  54. package/build/listItem/Switch/ListItemSwitch.js.map +1 -0
  55. package/build/listItem/Switch/ListItemSwitch.mjs +28 -0
  56. package/build/listItem/Switch/ListItemSwitch.mjs.map +1 -0
  57. package/build/listItem/useListItemControl.js +22 -0
  58. package/build/listItem/useListItemControl.js.map +1 -0
  59. package/build/listItem/useListItemControl.mjs +20 -0
  60. package/build/listItem/useListItemControl.mjs.map +1 -0
  61. package/build/listItem/useListItemMedia.js +21 -0
  62. package/build/listItem/useListItemMedia.js.map +1 -0
  63. package/build/listItem/useListItemMedia.mjs +19 -0
  64. package/build/listItem/useListItemMedia.mjs.map +1 -0
  65. package/build/main.css +770 -0
  66. package/build/styles/listItem/ListItem.css +770 -0
  67. package/build/styles/listItem/ListItem.grid.css +370 -0
  68. package/build/styles/listItem/Prompt/ListItemPrompt.css +157 -0
  69. package/build/styles/main.css +770 -0
  70. package/build/types/index.d.ts +2 -0
  71. package/build/types/index.d.ts.map +1 -1
  72. package/build/types/listItem/AdditionalInfo/ListItemAdditionalInfo.d.ts +15 -0
  73. package/build/types/listItem/AdditionalInfo/ListItemAdditionalInfo.d.ts.map +1 -0
  74. package/build/types/listItem/AdditionalInfo/index.d.ts +3 -0
  75. package/build/types/listItem/AdditionalInfo/index.d.ts.map +1 -0
  76. package/build/types/listItem/AvatarLayout/ListItemAvatarLayout.d.ts +18 -0
  77. package/build/types/listItem/AvatarLayout/ListItemAvatarLayout.d.ts.map +1 -0
  78. package/build/types/listItem/AvatarLayout/index.d.ts +3 -0
  79. package/build/types/listItem/AvatarLayout/index.d.ts.map +1 -0
  80. package/build/types/listItem/AvatarView/ListItemAvatarView.d.ts +16 -0
  81. package/build/types/listItem/AvatarView/ListItemAvatarView.d.ts.map +1 -0
  82. package/build/types/listItem/AvatarView/index.d.ts +3 -0
  83. package/build/types/listItem/AvatarView/index.d.ts.map +1 -0
  84. package/build/types/listItem/Button/ListItemButton.d.ts +20 -0
  85. package/build/types/listItem/Button/ListItemButton.d.ts.map +1 -0
  86. package/build/types/listItem/Button/index.d.ts +3 -0
  87. package/build/types/listItem/Button/index.d.ts.map +1 -0
  88. package/build/types/listItem/Checkbox/ListItemCheckbox.d.ts +14 -0
  89. package/build/types/listItem/Checkbox/ListItemCheckbox.d.ts.map +1 -0
  90. package/build/types/listItem/Checkbox/index.d.ts +3 -0
  91. package/build/types/listItem/Checkbox/index.d.ts.map +1 -0
  92. package/build/types/listItem/IconButton/ListItemIconButton.d.ts +18 -0
  93. package/build/types/listItem/IconButton/ListItemIconButton.d.ts.map +1 -0
  94. package/build/types/listItem/IconButton/index.d.ts +3 -0
  95. package/build/types/listItem/IconButton/index.d.ts.map +1 -0
  96. package/build/types/listItem/Image/ListItemImage.d.ts +25 -0
  97. package/build/types/listItem/Image/ListItemImage.d.ts.map +1 -0
  98. package/build/types/listItem/Image/index.d.ts +3 -0
  99. package/build/types/listItem/Image/index.d.ts.map +1 -0
  100. package/build/types/listItem/ListItem.d.ts +111 -0
  101. package/build/types/listItem/ListItem.d.ts.map +1 -0
  102. package/build/types/listItem/ListItemContext.d.ts +21 -0
  103. package/build/types/listItem/ListItemContext.d.ts.map +1 -0
  104. package/build/types/listItem/Navigation/ListItemNavigation.d.ts +15 -0
  105. package/build/types/listItem/Navigation/ListItemNavigation.d.ts.map +1 -0
  106. package/build/types/listItem/Navigation/index.d.ts +3 -0
  107. package/build/types/listItem/Navigation/index.d.ts.map +1 -0
  108. package/build/types/listItem/Prompt/ListItemPrompt.d.ts +16 -0
  109. package/build/types/listItem/Prompt/ListItemPrompt.d.ts.map +1 -0
  110. package/build/types/listItem/Prompt/index.d.ts +3 -0
  111. package/build/types/listItem/Prompt/index.d.ts.map +1 -0
  112. package/build/types/listItem/Radio/ListItemRadio.d.ts +14 -0
  113. package/build/types/listItem/Radio/ListItemRadio.d.ts.map +1 -0
  114. package/build/types/listItem/Radio/index.d.ts +3 -0
  115. package/build/types/listItem/Radio/index.d.ts.map +1 -0
  116. package/build/types/listItem/Switch/ListItemSwitch.d.ts +14 -0
  117. package/build/types/listItem/Switch/ListItemSwitch.d.ts.map +1 -0
  118. package/build/types/listItem/Switch/index.d.ts +3 -0
  119. package/build/types/listItem/Switch/index.d.ts.map +1 -0
  120. package/build/types/listItem/_stories/helpers.d.ts +27 -0
  121. package/build/types/listItem/_stories/helpers.d.ts.map +1 -0
  122. package/build/types/listItem/_stories/subcomponents.d.ts +18 -0
  123. package/build/types/listItem/_stories/subcomponents.d.ts.map +1 -0
  124. package/build/types/listItem/index.d.ts +14 -0
  125. package/build/types/listItem/index.d.ts.map +1 -0
  126. package/build/types/listItem/test-utils.d.ts +7 -0
  127. package/build/types/listItem/test-utils.d.ts.map +1 -0
  128. package/build/types/listItem/useListItemControl.d.ts +5 -0
  129. package/build/types/listItem/useListItemControl.d.ts.map +1 -0
  130. package/build/types/listItem/useListItemMedia.d.ts +6 -0
  131. package/build/types/listItem/useListItemMedia.d.ts.map +1 -0
  132. package/package.json +3 -3
  133. package/src/actionButton/ActionButton.story.tsx +1 -1
  134. package/src/avatar/Avatar.story.tsx +1 -1
  135. package/src/avatarWrapper/AvatarWrapper.story.tsx +1 -1
  136. package/src/badge/Badge.story.tsx +1 -1
  137. package/src/button/Button.story.tsx +1 -0
  138. package/src/button/LegacyButton.story.tsx +1 -1
  139. package/src/index.ts +15 -0
  140. package/src/legacylistItem/LegacyListItem.story.tsx +1 -1
  141. package/src/legacylistItem/LegacyListItem.tests.story.tsx +2 -1
  142. package/src/list/List.story.tsx +13 -3
  143. package/src/listItem/AdditionalInfo/ListItemAdditionalInfo.spec.tsx +56 -0
  144. package/src/listItem/AdditionalInfo/ListItemAdditionalInfo.story.tsx +198 -0
  145. package/src/listItem/AdditionalInfo/ListItemAdditionalInfo.tsx +36 -0
  146. package/src/listItem/AdditionalInfo/index.ts +2 -0
  147. package/src/listItem/AvatarLayout/ListItemAvatarLayout.spec.tsx +59 -0
  148. package/src/listItem/AvatarLayout/ListItemAvatarLayout.story.tsx +124 -0
  149. package/src/listItem/AvatarLayout/ListItemAvatarLayout.tsx +27 -0
  150. package/src/listItem/AvatarLayout/index.ts +2 -0
  151. package/src/listItem/AvatarView/ListItemAvatarView.spec.tsx +75 -0
  152. package/src/listItem/AvatarView/ListItemAvatarView.story.tsx +339 -0
  153. package/src/listItem/AvatarView/ListItemAvatarView.tsx +27 -0
  154. package/src/listItem/AvatarView/index.ts +2 -0
  155. package/src/listItem/Button/ListItemButton.spec.tsx +68 -0
  156. package/src/listItem/Button/ListItemButton.story.tsx +473 -0
  157. package/src/listItem/Button/ListItemButton.tsx +56 -0
  158. package/src/listItem/Button/index.ts +2 -0
  159. package/src/listItem/Checkbox/ListItemCheckbox.spec.tsx +82 -0
  160. package/src/listItem/Checkbox/ListItemCheckbox.story.tsx +128 -0
  161. package/src/listItem/Checkbox/ListItemCheckbox.tsx +33 -0
  162. package/src/listItem/Checkbox/index.ts +2 -0
  163. package/src/listItem/IconButton/ListItemIconButton.spec.tsx +119 -0
  164. package/src/listItem/IconButton/ListItemIconButton.story.tsx +284 -0
  165. package/src/listItem/IconButton/ListItemIconButton.tsx +73 -0
  166. package/src/listItem/IconButton/index.ts +2 -0
  167. package/src/listItem/Image/ListItemImage.spec.tsx +30 -0
  168. package/src/listItem/Image/ListItemImage.story.tsx +80 -0
  169. package/src/listItem/Image/ListItemImage.tsx +46 -0
  170. package/src/listItem/Image/index.ts +2 -0
  171. package/src/listItem/ListItem.css +770 -0
  172. package/src/listItem/ListItem.grid.css +370 -0
  173. package/src/listItem/ListItem.grid.less +622 -0
  174. package/src/listItem/ListItem.less +287 -0
  175. package/src/listItem/ListItem.spec.tsx +1511 -0
  176. package/src/listItem/ListItem.tsx +438 -0
  177. package/src/listItem/ListItemContext.tsx +26 -0
  178. package/src/listItem/Navigation/ListItemNavigation.spec.tsx +59 -0
  179. package/src/listItem/Navigation/ListItemNavigation.story.tsx +112 -0
  180. package/src/listItem/Navigation/ListItemNavigation.tsx +39 -0
  181. package/src/listItem/Navigation/index.ts +2 -0
  182. package/src/listItem/Prompt/ListItemPrompt.css +157 -0
  183. package/src/listItem/Prompt/ListItemPrompt.less +134 -0
  184. package/src/listItem/Prompt/ListItemPrompt.spec.tsx +36 -0
  185. package/src/listItem/Prompt/ListItemPrompt.story.tsx +204 -0
  186. package/src/listItem/Prompt/ListItemPrompt.tsx +32 -0
  187. package/src/listItem/Prompt/index.ts +2 -0
  188. package/src/listItem/Radio/ListItemRadio.spec.tsx +66 -0
  189. package/src/listItem/Radio/ListItemRadio.story.tsx +111 -0
  190. package/src/listItem/Radio/ListItemRadio.tsx +33 -0
  191. package/src/listItem/Radio/index.ts +2 -0
  192. package/src/listItem/Switch/ListItemSwitch.spec.tsx +47 -0
  193. package/src/listItem/Switch/ListItemSwitch.story.tsx +79 -0
  194. package/src/listItem/Switch/ListItemSwitch.tsx +33 -0
  195. package/src/listItem/Switch/index.ts +2 -0
  196. package/src/listItem/_stories/ListItem.focus.test.story.tsx +265 -0
  197. package/src/listItem/_stories/ListItem.layout.test.story.tsx +354 -0
  198. package/src/listItem/_stories/ListItem.scenarios.story.tsx +228 -0
  199. package/src/listItem/_stories/ListItem.story.tsx +774 -0
  200. package/src/listItem/_stories/ListItem.variants.test.story.tsx +271 -0
  201. package/src/listItem/_stories/helpers.tsx +53 -0
  202. package/src/listItem/_stories/subcomponents.tsx +139 -0
  203. package/src/listItem/index.ts +14 -0
  204. package/src/listItem/test-utils.tsx +33 -0
  205. package/src/listItem/useListItemControl.tsx +18 -0
  206. package/src/listItem/useListItemMedia.tsx +16 -0
  207. package/src/main.css +770 -0
  208. package/src/main.less +1 -0
  209. package/src/select/Select.story.tsx +1 -1
@@ -0,0 +1,473 @@
1
+ import { Meta, StoryObj } from '@storybook/react-webpack5';
2
+ import { fn } from 'storybook/test';
3
+ import { Freeze, ArrowRight } from '@transferwise/icons';
4
+ import List from '../../list';
5
+ import { ListItem } from '../ListItem';
6
+ import {
7
+ SB_LIST_ITEM_ADDITIONAL_INFO as INFO,
8
+ SB_LIST_ITEM_MEDIA as MEDIA,
9
+ } from '../_stories/subcomponents';
10
+ import { disableControls } from '../_stories/helpers';
11
+ import type { ListItemButtonProps } from './ListItemButton';
12
+
13
+ const hideControls = disableControls([
14
+ 'onClick',
15
+ 'children',
16
+ 'className',
17
+ 'addonStart',
18
+ 'addonEnd',
19
+ ]);
20
+
21
+ /**
22
+ * Convenience controls for previewing rich markup,
23
+ * not otherwise possible via Storybook
24
+ */
25
+ type PreviewStoryArgs = ListItemButtonProps & {
26
+ previewAddonStart: boolean | ListItemButtonProps['addonStart'];
27
+ previewAddonEnd: boolean | ListItemButtonProps['addonEnd'];
28
+ };
29
+
30
+ const previewArgTypes = {
31
+ previewAddonStart: {
32
+ control: 'boolean',
33
+ name: 'Preview with `addonStart`',
34
+ mapping: {
35
+ true: { type: 'icon', value: <Freeze /> },
36
+ },
37
+ description: '**NB:** ListItem.Button only supports icon addons.',
38
+ table: {
39
+ category: 'Preview options',
40
+ type: {
41
+ summary: undefined,
42
+ },
43
+ },
44
+ },
45
+ previewAddonEnd: {
46
+ control: 'boolean',
47
+ name: 'Preview with `addonEnd`',
48
+ mapping: {
49
+ true: { type: 'icon', value: <ArrowRight /> },
50
+ },
51
+ description: '**NB:** ListItem.Button only supports icon addons.',
52
+ table: {
53
+ category: 'Preview options',
54
+ type: {
55
+ summary: undefined,
56
+ },
57
+ },
58
+ },
59
+ } as const;
60
+
61
+ const getPropsForPreview = (args: PreviewStoryArgs) => {
62
+ const { previewAddonStart, previewAddonEnd, ...props } = args as {
63
+ previewAddonStart: ListItemButtonProps['addonStart'];
64
+ previewAddonEnd: ListItemButtonProps['addonEnd'];
65
+ [key: string]: any;
66
+ };
67
+
68
+ return [
69
+ props,
70
+ {
71
+ addonStart: previewAddonStart,
72
+ addonEnd: previewAddonEnd,
73
+ },
74
+ ] as const;
75
+ };
76
+
77
+ const meta: Meta<typeof ListItem.Button> = {
78
+ component: ListItem.Button,
79
+ title: 'Content/ListItem/ListItem.Button',
80
+ parameters: {
81
+ docs: {
82
+ toc: true,
83
+ },
84
+ },
85
+ args: {
86
+ partiallyInteractive: false,
87
+ priority: 'secondary-neutral',
88
+ sentiment: undefined,
89
+ loading: undefined,
90
+ href: undefined,
91
+ target: undefined,
92
+ as: undefined,
93
+ addonStart: undefined,
94
+ addonEnd: undefined,
95
+ className: undefined,
96
+ children: 'Click me',
97
+ onClick: fn(),
98
+ },
99
+ argTypes: {
100
+ children: {
101
+ table: {
102
+ type: { summary: 'ReactNode' },
103
+ },
104
+ },
105
+ loading: {
106
+ control: 'boolean',
107
+ description: 'Toggles the loading state',
108
+ },
109
+ priority: {
110
+ control: 'radio',
111
+ options: ['unset (undefined)', 'primary', 'secondary', 'secondary-neutral', 'tertiary'],
112
+ mapping: {
113
+ 'unset (undefined)': undefined,
114
+ },
115
+ description: 'Sets a visual hierarchy amongst the buttons displayed on the screen',
116
+ },
117
+ sentiment: {
118
+ options: ['unset (undefined)', 'default', 'negative'],
119
+ mapping: {
120
+ 'unset (undefined)': undefined,
121
+ },
122
+ description:
123
+ 'Sets a visual hierarchy amongst the buttons displayed on the screen. <br /> **NB:** `negative` variant only affects the `primary` and `secondary` priorities.',
124
+ },
125
+ href: {
126
+ control: 'text',
127
+ description: 'If set, the component will render as an HTML anchor.',
128
+ },
129
+ target: {
130
+ control: 'select',
131
+ options: [undefined, '_blank', '_self', '_parent', '_top'],
132
+ description:
133
+ 'The `target` attribute for HTML anchor. If set to `_blank`, `rel="noopener noreferrer"` is automatically added to the rendered node.',
134
+ },
135
+ addonStart: {
136
+ description: 'Icon accessory to be displayed before the label',
137
+ },
138
+ addonEnd: {
139
+ description: 'Icon accessory to be displayed after the label',
140
+ },
141
+ onClick: {
142
+ table: {
143
+ type: { summary: '(event: MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void' },
144
+ },
145
+ },
146
+ },
147
+ } satisfies Meta<ListItemButtonProps>;
148
+
149
+ export default meta;
150
+
151
+ type Story = StoryObj<ListItemButtonProps>;
152
+
153
+ export const Playground: StoryObj<PreviewStoryArgs> = {
154
+ tags: ['!autodocs'],
155
+ render: (args: PreviewStoryArgs) => {
156
+ const [props, previewProps] = getPropsForPreview(args);
157
+
158
+ return (
159
+ <List>
160
+ <ListItem
161
+ title="List item title"
162
+ subtitle="Subtitle goes here"
163
+ media={MEDIA.avatarSingle}
164
+ control={<ListItem.Button {...props} {...previewProps} />}
165
+ additionalInfo={INFO.nonInteractive}
166
+ />
167
+ </List>
168
+ );
169
+ },
170
+ args: {
171
+ previewAddonStart: false,
172
+ previewAddonEnd: false,
173
+ },
174
+ argTypes: {
175
+ ...previewArgTypes,
176
+ },
177
+ };
178
+
179
+ /**
180
+ * By default, ListItem is fully interactive, which means its whole surface is clickable
181
+ * and triggers the control. To remain WCAG-compliant there can be no nested interactive
182
+ * elements inside the item.<br />
183
+ *
184
+ * To work around this limitation, this control also allows for a partially interactive mode,
185
+ * which can be enabled via `partiallyInteractive` prop. Once set, only the control will
186
+ * remain interactive, which allows for interactive element to be attached to the
187
+ * `ListItem.AdditionalInfo`. This allows for more complex layouts while still providing a
188
+ * clear, accessible action point for users. <br />
189
+ *
190
+ * If you still require a fully interactive ListItem, you can alternatively use `ListItem.Prompt`
191
+ * which allows for a single instance of a link or an inline button.
192
+ *
193
+ * Refer to the [design documentation](https://wise.design/components/list-item#interaction) for details.
194
+ */
195
+ export const Interactivity: StoryObj<PreviewStoryArgs> = {
196
+ args: {
197
+ children: 'Action',
198
+ previewAddonStart: false,
199
+ previewAddonEnd: false,
200
+ },
201
+ argTypes: {
202
+ ...hideControls(['partiallyInteractive']),
203
+ ...previewArgTypes,
204
+ },
205
+ render: (args: PreviewStoryArgs) => {
206
+ const [props, previewProps] = getPropsForPreview(args);
207
+
208
+ return (
209
+ <List>
210
+ <ListItem
211
+ title="Fully interactive ListItem"
212
+ subtitle="Button in fully interactive context"
213
+ media={MEDIA.avatarSingle}
214
+ control={<ListItem.Button {...props} {...previewProps} />}
215
+ />
216
+
217
+ <ListItem
218
+ title="Partially interactive ListItem"
219
+ subtitle="Button in partially interactive context"
220
+ media={MEDIA.avatarSingle}
221
+ control={<ListItem.Button {...props} {...previewProps} partiallyInteractive />}
222
+ />
223
+ </List>
224
+ );
225
+ },
226
+ };
227
+
228
+ /**
229
+ * If `href` prop is set, the component will be automatically rendered as an HTML anchor element.
230
+ */
231
+ export const AsAnchor: StoryObj<PreviewStoryArgs> = {
232
+ args: {
233
+ children: 'Visit link',
234
+ href: 'https://wise.com',
235
+ target: '_blank',
236
+ previewAddonStart: false,
237
+ previewAddonEnd: false,
238
+ },
239
+ argTypes: {
240
+ ...hideControls(['partiallyInteractive']),
241
+ ...previewArgTypes,
242
+ },
243
+ render: (args: PreviewStoryArgs) => {
244
+ const [props, previewProps] = getPropsForPreview(args);
245
+
246
+ return (
247
+ <List>
248
+ <ListItem
249
+ title="List item with a Button control"
250
+ subtitle="But it renders as HTML anchor element"
251
+ media={MEDIA.avatarSingle}
252
+ control={<ListItem.Button {...props} {...previewProps} />}
253
+ />
254
+ </List>
255
+ );
256
+ },
257
+ };
258
+
259
+ /**
260
+ * There are two different types of button – default and negative – designed to emphasise the nature of the action. <br />
261
+ * **NB:** Sentiment only applies to `primary` and `secondary` priorities. <br />
262
+ * [Design documentation](https://wise.design/components/button#types)
263
+ */
264
+ export const Sentiment: StoryObj<PreviewStoryArgs> = {
265
+ args: {
266
+ children: 'Button text',
267
+ previewAddonStart: false,
268
+ previewAddonEnd: false,
269
+ },
270
+ argTypes: {
271
+ ...hideControls(['sentiment', 'priority']),
272
+ ...previewArgTypes,
273
+ },
274
+ render: (args: PreviewStoryArgs) => {
275
+ const [props, previewProps] = getPropsForPreview(args);
276
+
277
+ return (
278
+ <List>
279
+ <ListItem
280
+ title="Default Sentiment - Primary"
281
+ subtitle="Default sentiment with primary priority"
282
+ media={MEDIA.avatarSingle}
283
+ control={
284
+ <ListItem.Button {...props} {...previewProps} priority="primary">
285
+ Default Sentiment
286
+ </ListItem.Button>
287
+ }
288
+ />
289
+ <ListItem
290
+ title="Negative Sentiment - Primary"
291
+ subtitle="Negative sentiment with primary priority"
292
+ media={MEDIA.avatarSingle}
293
+ control={
294
+ <ListItem.Button {...props} {...previewProps} priority="primary" sentiment="negative">
295
+ Negative Sentiment
296
+ </ListItem.Button>
297
+ }
298
+ />
299
+ <ListItem
300
+ title="Default Sentiment - Secondary"
301
+ subtitle="Default sentiment with secondary priority"
302
+ media={MEDIA.avatarSingle}
303
+ control={
304
+ <ListItem.Button {...props} {...previewProps} priority="secondary">
305
+ Default Sentiment
306
+ </ListItem.Button>
307
+ }
308
+ />
309
+ <ListItem
310
+ title="Negative Sentiment - Secondary"
311
+ subtitle="Negative sentiment with secondary priority"
312
+ media={MEDIA.avatarSingle}
313
+ control={
314
+ <ListItem.Button {...props} {...previewProps} priority="secondary" sentiment="negative">
315
+ Negative Sentiment
316
+ </ListItem.Button>
317
+ }
318
+ />
319
+ </List>
320
+ );
321
+ },
322
+ };
323
+
324
+ /**
325
+ * Priorities set a visual hierarchy amongst the buttons displayed on the
326
+ * screen to help more important buttons to take precedence over others. <br />
327
+ * [Design documentation](https://wise.design/components/button#priorities)
328
+ */
329
+ export const Priority: StoryObj<PreviewStoryArgs> = {
330
+ args: {
331
+ children: 'Button text',
332
+ previewAddonStart: false,
333
+ previewAddonEnd: false,
334
+ },
335
+ argTypes: {
336
+ ...hideControls(['priority']),
337
+ ...previewArgTypes,
338
+ },
339
+ render: (args: PreviewStoryArgs) => {
340
+ const [props, previewProps] = getPropsForPreview(args);
341
+
342
+ return (
343
+ <List>
344
+ <ListItem
345
+ title="Primary Priority"
346
+ subtitle="Primary priority (default)"
347
+ media={MEDIA.avatarSingle}
348
+ control={
349
+ <ListItem.Button {...props} {...previewProps} priority="primary">
350
+ Primary Priority
351
+ </ListItem.Button>
352
+ }
353
+ />
354
+ <ListItem
355
+ title="Secondary Priority"
356
+ subtitle="Secondary priority"
357
+ media={MEDIA.avatarSingle}
358
+ control={
359
+ <ListItem.Button {...props} {...previewProps} priority="secondary">
360
+ Secondary Priority
361
+ </ListItem.Button>
362
+ }
363
+ />
364
+ <ListItem
365
+ title="Secondary Neutral Priority"
366
+ subtitle="Secondary neutral priority"
367
+ media={MEDIA.avatarSingle}
368
+ control={
369
+ <ListItem.Button {...props} {...previewProps} priority="secondary-neutral">
370
+ Secondary Neutral Priority
371
+ </ListItem.Button>
372
+ }
373
+ />
374
+ <ListItem
375
+ title="Tertiary Priority"
376
+ subtitle="Tertiary priority"
377
+ media={MEDIA.avatarSingle}
378
+ control={
379
+ <ListItem.Button {...props} {...previewProps} priority="tertiary">
380
+ Tertiary Priority
381
+ </ListItem.Button>
382
+ }
383
+ />
384
+ </List>
385
+ );
386
+ },
387
+ };
388
+
389
+ /**
390
+ * Button in loading state.
391
+ */
392
+ export const Loading: StoryObj<PreviewStoryArgs> = {
393
+ args: {
394
+ loading: true,
395
+ previewAddonStart: false,
396
+ previewAddonEnd: false,
397
+ },
398
+ argTypes: {
399
+ ...hideControls(['loading']),
400
+ ...previewArgTypes,
401
+ },
402
+ render: (args: PreviewStoryArgs) => {
403
+ const [props, previewProps] = getPropsForPreview(args);
404
+
405
+ return (
406
+ <List>
407
+ <ListItem
408
+ title="Loading Button"
409
+ subtitle="Button in loading state"
410
+ media={MEDIA.avatarSingle}
411
+ control={
412
+ <ListItem.Button {...props} {...previewProps}>
413
+ Loading Button
414
+ </ListItem.Button>
415
+ }
416
+ additionalInfo={INFO.nonInteractive}
417
+ />
418
+ </List>
419
+ );
420
+ },
421
+ };
422
+
423
+ const addonProps = {
424
+ addonStart: { type: 'icon', value: <Freeze /> },
425
+ addonEnd: { type: 'icon', value: <ArrowRight /> },
426
+ } as const;
427
+
428
+ /**
429
+ * Icons can be displayed before and after the button label. <br />
430
+ * **NB:** ListItem.Button only supports icon addons.
431
+ */
432
+ export const WithIcons: Story = {
433
+ args: {
434
+ children: 'Button text',
435
+ },
436
+ argTypes: hideControls(),
437
+ render: (args: ListItemButtonProps) => {
438
+ return (
439
+ <List>
440
+ <ListItem
441
+ title="With Start Icon"
442
+ subtitle="Button with icon before the label"
443
+ media={MEDIA.avatarSingle}
444
+ control={
445
+ <ListItem.Button {...args} addonStart={addonProps.addonStart}>
446
+ With start icon
447
+ </ListItem.Button>
448
+ }
449
+ />
450
+ <ListItem
451
+ title="With End Icon"
452
+ subtitle="Button with icon after the label"
453
+ media={MEDIA.avatarSingle}
454
+ control={
455
+ <ListItem.Button {...args} addonEnd={addonProps.addonEnd}>
456
+ With end icon
457
+ </ListItem.Button>
458
+ }
459
+ />
460
+ <ListItem
461
+ title="With Both Icons"
462
+ subtitle="Button with icons before and after the label"
463
+ media={MEDIA.avatarSingle}
464
+ control={
465
+ <ListItem.Button {...args} {...addonProps}>
466
+ With both icons
467
+ </ListItem.Button>
468
+ }
469
+ />
470
+ </List>
471
+ );
472
+ },
473
+ };
@@ -0,0 +1,56 @@
1
+ import { useContext } from 'react';
2
+ import { clsx } from 'clsx';
3
+ import ButtonComp, { type ButtonAddonIcon, type NewButtonProps } from '../../button';
4
+ import { useListItemControl } from '../useListItemControl';
5
+ import { ListItemContext } from '../ListItemContext';
6
+
7
+ export type ListItemButtonProps = Omit<
8
+ NewButtonProps,
9
+ 'v2' | 'size' | 'disabled' | 'block' | 'addonStart'
10
+ > & {
11
+ /**
12
+ * Toggles the [interactivity strategy](https://storybook.wise.design/?path=/docs/content-listitem--docs#interactivity) for the whole ListItem.
13
+ */
14
+ partiallyInteractive?: boolean;
15
+ addonStart?: ButtonAddonIcon;
16
+ };
17
+
18
+ /**
19
+ * This component allows for rendering a Button control. It's a thin wrapper around the
20
+ * [Button component](https://storybook.wise.design/?path=/docs/content-button--docs), but offers only
21
+ * a subset of its features in line with the ListItem's constraints. <br />
22
+ * <br />
23
+ * Please refer to the [Design documentation](https://wise.design/components/list-item---button) for details.
24
+ */
25
+ export const Button = ({
26
+ priority = 'secondary-neutral',
27
+ partiallyInteractive,
28
+ ...props
29
+ }: ListItemButtonProps) => {
30
+ const { baseItemProps } = useListItemControl('button', { partiallyInteractive, ...props });
31
+ const { ids, describedByIds } = useContext(ListItemContext);
32
+
33
+ const commonProps = {
34
+ ...props,
35
+ className: clsx(
36
+ 'wds-list-item-control',
37
+ !partiallyInteractive && props.href && 'wds-list-item-control_pseudo-element',
38
+ ),
39
+ id: ids.control,
40
+ priority,
41
+ v2: true,
42
+ size: 'sm',
43
+ disabled: baseItemProps.disabled,
44
+ };
45
+
46
+ const buttonContentId = props.href || partiallyInteractive ? '' : `${ids.control}_content`;
47
+
48
+ return (
49
+ <ButtonComp
50
+ aria-describedby={`${buttonContentId} ${describedByIds}`}
51
+ {...(commonProps as NewButtonProps)}
52
+ />
53
+ );
54
+ };
55
+
56
+ Button.displayName = 'ListItem.Button';
@@ -0,0 +1,2 @@
1
+ export type { ListItemButtonProps } from './ListItemButton';
2
+ export { Button } from './ListItemButton';
@@ -0,0 +1,82 @@
1
+ import { render, screen } from '../../test-utils';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { ListItem, type ListItemProps } from '../ListItem';
4
+
5
+ describe('ListItem.Checkbox', () => {
6
+ const renderWith = (overrides: Partial<ListItemProps> = {}) =>
7
+ render(<ListItem title="Test title" {...overrides} />);
8
+
9
+ it('renders checkbox', () => {
10
+ renderWith({ control: <ListItem.Checkbox /> });
11
+ expect(screen.getByRole('checkbox')).toBeInTheDocument();
12
+ });
13
+
14
+ describe('checked state', () => {
15
+ it('reflects checked state', () => {
16
+ renderWith({ control: <ListItem.Checkbox checked onChange={jest.fn()} /> });
17
+ expect(screen.getByRole('checkbox')).toBeChecked();
18
+ });
19
+
20
+ it('reflects unchecked state', () => {
21
+ renderWith({ control: <ListItem.Checkbox checked={false} onChange={jest.fn()} /> });
22
+ expect(screen.getByRole('checkbox')).not.toBeChecked();
23
+ });
24
+
25
+ it('supports indeterminate state', () => {
26
+ renderWith({ control: <ListItem.Checkbox indeterminate /> });
27
+ expect(screen.getByRole('checkbox')).toHaveProperty('indeterminate', true);
28
+ });
29
+ });
30
+
31
+ describe('interactivity', () => {
32
+ it('handles onChange events', async () => {
33
+ const handleChange = jest.fn();
34
+ renderWith({ control: <ListItem.Checkbox checked={false} onChange={handleChange} /> });
35
+
36
+ await userEvent.click(screen.getByRole('checkbox'));
37
+ expect(handleChange).toHaveBeenCalledTimes(1);
38
+ });
39
+
40
+ it('is disabled when ListItem is disabled', () => {
41
+ renderWith({
42
+ disabled: true,
43
+ control: <ListItem.Checkbox checked={false} onChange={jest.fn()} />,
44
+ });
45
+
46
+ expect(screen.getByRole('checkbox')).toBeDisabled();
47
+ });
48
+
49
+ it('handles onFocus events', async () => {
50
+ const handleFocus = jest.fn();
51
+ renderWith({
52
+ control: <ListItem.Checkbox checked={false} onFocus={handleFocus} onChange={() => {}} />,
53
+ });
54
+
55
+ await userEvent.tab();
56
+ expect(handleFocus).toHaveBeenCalledTimes(1);
57
+ });
58
+
59
+ it('handles onBlur events', async () => {
60
+ const handleBlur = jest.fn();
61
+ renderWith({
62
+ control: <ListItem.Checkbox checked={false} onBlur={handleBlur} onChange={() => {}} />,
63
+ });
64
+
65
+ const checkbox = screen.getByRole('checkbox');
66
+ await userEvent.click(checkbox);
67
+ await userEvent.tab();
68
+ expect(handleBlur).toHaveBeenCalledTimes(1);
69
+ });
70
+ });
71
+
72
+ it('supports name and value attributes', () => {
73
+ const checkboxName = 'test-checkbox';
74
+ const checkboxValue = 'test-value';
75
+ renderWith({ control: <ListItem.Checkbox name={checkboxName} value={checkboxValue} /> });
76
+
77
+ const checkbox = screen.getByRole('checkbox');
78
+ expect(checkbox).toHaveAttribute('name', checkboxName);
79
+ // eslint-disable-next-line jest-dom/prefer-to-have-value
80
+ expect(checkbox).toHaveAttribute('value', checkboxValue);
81
+ });
82
+ });