@transferwise/components 0.0.0-experimental-e06e456 → 0.0.0-experimental-e3978a5

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 (201) 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 +313 -0
  34. package/build/listItem/ListItem.js.map +1 -0
  35. package/build/listItem/ListItem.mjs +308 -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 +771 -1
  66. package/build/styles/button/Button.css +1 -1
  67. package/build/styles/listItem/ListItem.css +770 -0
  68. package/build/styles/listItem/ListItem.grid.css +370 -0
  69. package/build/styles/main.css +771 -1
  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 +113 -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 +1 -1
  133. package/src/button/Button.css +1 -1
  134. package/src/button/Button.less +1 -1
  135. package/src/button/Button.story.tsx +4 -9
  136. package/src/index.ts +15 -0
  137. package/src/list/List.story.tsx +13 -3
  138. package/src/listItem/AdditionalInfo/ListItemAdditionalInfo.spec.tsx +56 -0
  139. package/src/listItem/AdditionalInfo/ListItemAdditionalInfo.story.tsx +198 -0
  140. package/src/listItem/AdditionalInfo/ListItemAdditionalInfo.tsx +36 -0
  141. package/src/listItem/AdditionalInfo/index.ts +2 -0
  142. package/src/listItem/AvatarLayout/ListItemAvatarLayout.spec.tsx +59 -0
  143. package/src/listItem/AvatarLayout/ListItemAvatarLayout.story.tsx +124 -0
  144. package/src/listItem/AvatarLayout/ListItemAvatarLayout.tsx +27 -0
  145. package/src/listItem/AvatarLayout/index.ts +2 -0
  146. package/src/listItem/AvatarView/ListItemAvatarView.spec.tsx +75 -0
  147. package/src/listItem/AvatarView/ListItemAvatarView.story.tsx +339 -0
  148. package/src/listItem/AvatarView/ListItemAvatarView.tsx +27 -0
  149. package/src/listItem/AvatarView/index.ts +2 -0
  150. package/src/listItem/Button/ListItemButton.spec.tsx +68 -0
  151. package/src/listItem/Button/ListItemButton.story.tsx +473 -0
  152. package/src/listItem/Button/ListItemButton.tsx +56 -0
  153. package/src/listItem/Button/index.ts +2 -0
  154. package/src/listItem/Checkbox/ListItemCheckbox.spec.tsx +82 -0
  155. package/src/listItem/Checkbox/ListItemCheckbox.story.tsx +128 -0
  156. package/src/listItem/Checkbox/ListItemCheckbox.tsx +33 -0
  157. package/src/listItem/Checkbox/index.ts +2 -0
  158. package/src/listItem/IconButton/ListItemIconButton.spec.tsx +119 -0
  159. package/src/listItem/IconButton/ListItemIconButton.story.tsx +284 -0
  160. package/src/listItem/IconButton/ListItemIconButton.tsx +73 -0
  161. package/src/listItem/IconButton/index.ts +2 -0
  162. package/src/listItem/Image/ListItemImage.spec.tsx +30 -0
  163. package/src/listItem/Image/ListItemImage.story.tsx +80 -0
  164. package/src/listItem/Image/ListItemImage.tsx +46 -0
  165. package/src/listItem/Image/index.ts +2 -0
  166. package/src/listItem/ListItem.css +770 -0
  167. package/src/listItem/ListItem.grid.css +370 -0
  168. package/src/listItem/ListItem.grid.less +622 -0
  169. package/src/listItem/ListItem.less +421 -0
  170. package/src/listItem/ListItem.spec.tsx +1521 -0
  171. package/src/listItem/ListItem.tsx +444 -0
  172. package/src/listItem/ListItemContext.tsx +26 -0
  173. package/src/listItem/Navigation/ListItemNavigation.spec.tsx +59 -0
  174. package/src/listItem/Navigation/ListItemNavigation.story.tsx +112 -0
  175. package/src/listItem/Navigation/ListItemNavigation.tsx +39 -0
  176. package/src/listItem/Navigation/index.ts +2 -0
  177. package/src/listItem/Prompt/ListItemPrompt.spec.tsx +36 -0
  178. package/src/listItem/Prompt/ListItemPrompt.story.tsx +204 -0
  179. package/src/listItem/Prompt/ListItemPrompt.tsx +32 -0
  180. package/src/listItem/Prompt/index.ts +2 -0
  181. package/src/listItem/Radio/ListItemRadio.spec.tsx +66 -0
  182. package/src/listItem/Radio/ListItemRadio.story.tsx +111 -0
  183. package/src/listItem/Radio/ListItemRadio.tsx +33 -0
  184. package/src/listItem/Radio/index.ts +2 -0
  185. package/src/listItem/Switch/ListItemSwitch.spec.tsx +47 -0
  186. package/src/listItem/Switch/ListItemSwitch.story.tsx +79 -0
  187. package/src/listItem/Switch/ListItemSwitch.tsx +33 -0
  188. package/src/listItem/Switch/index.ts +2 -0
  189. package/src/listItem/_stories/ListItem.focus.test.story.tsx +265 -0
  190. package/src/listItem/_stories/ListItem.layout.test.story.tsx +354 -0
  191. package/src/listItem/_stories/ListItem.scenarios.story.tsx +228 -0
  192. package/src/listItem/_stories/ListItem.story.tsx +775 -0
  193. package/src/listItem/_stories/ListItem.variants.test.story.tsx +271 -0
  194. package/src/listItem/_stories/helpers.tsx +53 -0
  195. package/src/listItem/_stories/subcomponents.tsx +139 -0
  196. package/src/listItem/index.ts +14 -0
  197. package/src/listItem/test-utils.tsx +33 -0
  198. package/src/listItem/useListItemControl.tsx +18 -0
  199. package/src/listItem/useListItemMedia.tsx +16 -0
  200. package/src/main.css +771 -1
  201. package/src/main.less +1 -0
@@ -0,0 +1,36 @@
1
+ import { mockMatchMedia, render, screen } from '../../test-utils';
2
+ import { Sentiment } from '../../common';
3
+ import { ListItem } from '../ListItem';
4
+ import type { ListItemPromptProps } from './ListItemPrompt';
5
+
6
+ mockMatchMedia();
7
+
8
+ describe('ListItem.Prompt', () => {
9
+ it('renders children content', () => {
10
+ render(
11
+ <ListItem
12
+ title="Test Title"
13
+ prompt={<ListItem.Prompt>This is a prompt message</ListItem.Prompt>}
14
+ />,
15
+ );
16
+
17
+ expect(screen.getByText('This is a prompt message')).toBeInTheDocument();
18
+ });
19
+
20
+ describe('render icon', () => {
21
+ it.each([
22
+ [Sentiment.NEUTRAL, 'info-icon'],
23
+ [Sentiment.POSITIVE, 'check-icon'],
24
+ [Sentiment.NEGATIVE, 'cross-icon'],
25
+ [Sentiment.WARNING, 'alert-icon'],
26
+ ] as [ListItemPromptProps['sentiment'], string][])('renders %s icon', (sentiment, iconId) => {
27
+ render(
28
+ <ListItem
29
+ title="Test Title"
30
+ prompt={<ListItem.Prompt sentiment={sentiment}>Message</ListItem.Prompt>}
31
+ />,
32
+ );
33
+ expect(screen.getByTestId(iconId)).toBeInTheDocument();
34
+ });
35
+ });
36
+ });
@@ -0,0 +1,204 @@
1
+ import { Meta, StoryObj } from '@storybook/react-webpack5';
2
+ import { action } from 'storybook/actions';
3
+ import { lorem10, lorem5 } from '../../test-utils';
4
+ import Link from '../../link';
5
+ import List from '../../list';
6
+ import { Sentiment as Sentiments } from '../../common';
7
+ import { ListItem } from '../ListItem';
8
+ import { Prompt, type ListItemPromptProps } from './ListItemPrompt';
9
+ import {
10
+ SB_LIST_ITEM_CONTROLS as CONTROLS,
11
+ SB_LIST_ITEM_MEDIA as MEDIA,
12
+ } from '../_stories/subcomponents';
13
+
14
+ const meta: Meta<ListItemPromptProps> = {
15
+ component: Prompt,
16
+ title: 'Content/ListItem/ListItem.Prompt',
17
+ parameters: {
18
+ docs: {
19
+ toc: true,
20
+ },
21
+ },
22
+ args: {
23
+ sentiment: undefined,
24
+ children: 'You have done a terrible thing',
25
+ },
26
+ argTypes: {
27
+ sentiment: {
28
+ options: [
29
+ 'unset (undefined)',
30
+ Sentiments.POSITIVE,
31
+ Sentiments.NEGATIVE,
32
+ Sentiments.NEUTRAL,
33
+ Sentiments.WARNING,
34
+ ],
35
+ mapping: {
36
+ 'unset (undefined)': undefined,
37
+ },
38
+ control: { type: 'radio' },
39
+ },
40
+ children: {
41
+ table: {
42
+ type: { summary: 'ReactNode' },
43
+ },
44
+ },
45
+ },
46
+ };
47
+
48
+ export default meta;
49
+
50
+ type Story = StoryObj<ListItemPromptProps>;
51
+
52
+ export const Playground: Story = {
53
+ tags: ['!autodocs'],
54
+ parameters: {
55
+ docs: {
56
+ source: 'dynamic',
57
+ },
58
+ },
59
+ render: (args) => (
60
+ <List>
61
+ <ListItem
62
+ title={lorem5}
63
+ subtitle={lorem10}
64
+ media={MEDIA.avatarSingle}
65
+ control={CONTROLS.switch}
66
+ prompt={<Prompt {...args} />}
67
+ />
68
+ </List>
69
+ ),
70
+ };
71
+
72
+ export const Sentiment: Story = {
73
+ parameters: {
74
+ controls: { disable: true },
75
+ actions: { disable: true },
76
+ a11y: { disable: true },
77
+ knobs: { disable: true },
78
+ },
79
+ render: (args) => (
80
+ <List>
81
+ <ListItem
82
+ title={lorem5}
83
+ subtitle={lorem10}
84
+ media={MEDIA.avatarSingle}
85
+ control={CONTROLS.switch}
86
+ prompt={<Prompt sentiment={Sentiments.NEUTRAL}>This is a neutral prompt.</Prompt>}
87
+ />
88
+ <ListItem
89
+ title={lorem5}
90
+ subtitle={lorem10}
91
+ media={MEDIA.avatarSingle}
92
+ control={CONTROLS.switch}
93
+ prompt={<Prompt sentiment={Sentiments.POSITIVE}>This is a positive prompt.</Prompt>}
94
+ />
95
+ <ListItem
96
+ title={lorem5}
97
+ subtitle={lorem10}
98
+ media={MEDIA.avatarSingle}
99
+ control={CONTROLS.switch}
100
+ prompt={<Prompt sentiment={Sentiments.WARNING}>This is a warning prompt.</Prompt>}
101
+ />
102
+ <ListItem
103
+ title={lorem5}
104
+ subtitle={lorem10}
105
+ media={MEDIA.avatarSingle}
106
+ control={CONTROLS.switch}
107
+ prompt={<Prompt sentiment={Sentiments.NEGATIVE}>This is a negative prompt.</Prompt>}
108
+ />
109
+ </List>
110
+ ),
111
+ };
112
+
113
+ /**
114
+ * `ListItem.Prompt` is rendered on a separate branch of the Accessibility Tree from the item's
115
+ * control, so it can include a single instance of `Link` component, which can be rendered as
116
+ * either HTML anchor or button. That element will spread across the whole surface of the Prompt
117
+ * so it's easily accessible for all users.
118
+ */
119
+ export const Interactivity: Story = {
120
+ argTypes: {
121
+ children: {
122
+ table: {
123
+ disable: true,
124
+ },
125
+ },
126
+ },
127
+ render: (args) => (
128
+ <List>
129
+ <ListItem
130
+ title={lorem5}
131
+ subtitle={lorem10}
132
+ media={MEDIA.avatarSingle}
133
+ control={CONTROLS.switch}
134
+ prompt={
135
+ <Prompt sentiment={args.sentiment}>
136
+ This prompt includes a{' '}
137
+ <Link href="https://wise.com" target="_blank" rel="noreferrer">
138
+ link to some resource
139
+ </Link>{' '}
140
+ to help the user in their journey.
141
+ </Prompt>
142
+ }
143
+ />
144
+
145
+ <ListItem
146
+ title={lorem5}
147
+ subtitle={lorem10}
148
+ media={MEDIA.avatarSingle}
149
+ control={CONTROLS.switch}
150
+ prompt={
151
+ <Prompt sentiment={args.sentiment}>
152
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
153
+ This prompt includes an <Link onClick={action('inline button')}>
154
+ inline button
155
+ </Link>{' '}
156
+ than can e.g. trigger a modal.
157
+ </Prompt>
158
+ }
159
+ />
160
+ </List>
161
+ ),
162
+ };
163
+
164
+ /**
165
+ * By default, the Prompt will try to occupy as little space as possible, but as soon as you make it more than 1 line long, it will stretch to the full available width.
166
+ *
167
+ * **NB:** While Prompt supports multi-line text, its content should be concise and have no more than 2 short sentences so users can quickly understand the message.
168
+ */
169
+ export const Sizing: Story = {
170
+ parameters: {
171
+ docs: {
172
+ canvas: {
173
+ sourceState: 'hidden',
174
+ },
175
+ },
176
+ },
177
+ render: (args) => (
178
+ <List>
179
+ <ListItem
180
+ title={lorem5}
181
+ subtitle={lorem10}
182
+ media={MEDIA.image}
183
+ control={CONTROLS.switch}
184
+ prompt={
185
+ <ListItem.Prompt sentiment={Sentiments.POSITIVE}>This is a short text</ListItem.Prompt>
186
+ }
187
+ />
188
+ <ListItem
189
+ title={lorem5}
190
+ subtitle={lorem10}
191
+ media={MEDIA.image}
192
+ control={CONTROLS.switch}
193
+ prompt={
194
+ <ListItem.Prompt sentiment={Sentiments.WARNING}>
195
+ This is a very, very, very, very, very long text that will wrap into more than 1 line
196
+ and will make the prompt stretch to a full available width. Technically it can be as
197
+ long as Vim manual, but we recommend keeping it concise and no more than 2 short
198
+ sentences.
199
+ </ListItem.Prompt>
200
+ }
201
+ />
202
+ </List>
203
+ ),
204
+ };
@@ -0,0 +1,32 @@
1
+ import { useContext } from 'react';
2
+ import { clsx } from 'clsx';
3
+ import { Sentiment } from '../../common';
4
+ import StatusIcon from '../../statusIcon';
5
+ import Body from '../../body';
6
+ import { ListItemContext, type ListItemContextData } from '../ListItemContext';
7
+
8
+ export type ListItemPromptProps = {
9
+ sentiment?: `${Sentiment.POSITIVE | Sentiment.NEGATIVE | Sentiment.NEUTRAL | Sentiment.WARNING}`;
10
+ children: React.ReactNode;
11
+ };
12
+
13
+ /**
14
+ * This component allows for rendering an Inline Prompt. <br />In the future it will be a thin wrapper around a standalone component.<br />
15
+ *
16
+ * Please refer to the [Design documentation](https://wise.design/components/list-item#prompt) for details.
17
+ */
18
+ export const Prompt = ({ sentiment = Sentiment.NEUTRAL, children }: ListItemPromptProps) => {
19
+ const { ids } = useContext<ListItemContextData>(ListItemContext);
20
+
21
+ return (
22
+ <div id={ids.prompt} className={clsx('wds-list-item-prompt', sentiment)}>
23
+ <div className="np-prompt-icon">
24
+ <StatusIcon size={16} sentiment={sentiment} />
25
+ </div>
26
+ <Body>{children}</Body>
27
+ </div>
28
+ );
29
+ };
30
+
31
+ Prompt.displayName = 'ListItem.Prompt';
32
+ export default Prompt;
@@ -0,0 +1,2 @@
1
+ export type { ListItemPromptProps } from './ListItemPrompt';
2
+ export { Prompt } from './ListItemPrompt';
@@ -0,0 +1,66 @@
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.Radio', () => {
6
+ const renderWith = (overrides: Partial<ListItemProps> = {}) =>
7
+ render(<ListItem title="Test Title" {...overrides} />);
8
+
9
+ it('renders radio button', () => {
10
+ renderWith({
11
+ control: <ListItem.Radio name="test-radio" value="option1" onChange={() => {}} />,
12
+ });
13
+
14
+ expect(screen.getByRole('radio')).toBeInTheDocument();
15
+ });
16
+
17
+ describe('checked state', () => {
18
+ it('reflects checked state', () => {
19
+ renderWith({
20
+ control: <ListItem.Radio name="test-radio" value="option1" checked />,
21
+ });
22
+
23
+ expect(screen.getByRole('radio')).toBeChecked();
24
+ });
25
+
26
+ it('reflects unchecked state', () => {
27
+ renderWith({
28
+ control: <ListItem.Radio name="test-radio" value="option1" checked={false} />,
29
+ });
30
+
31
+ expect(screen.getByRole('radio')).not.toBeChecked();
32
+ });
33
+ });
34
+
35
+ it('handles onChange events', async () => {
36
+ const handleChange = jest.fn();
37
+ renderWith({
38
+ control: <ListItem.Radio name="test-radio" value="option1" onChange={handleChange} />,
39
+ });
40
+
41
+ await userEvent.click(screen.getByRole('radio'));
42
+ expect(handleChange).toHaveBeenCalledTimes(1);
43
+ });
44
+
45
+ it('is disabled when ListItem is disabled', async () => {
46
+ const handleChange = jest.fn();
47
+ renderWith({
48
+ disabled: true,
49
+ control: <ListItem.Radio name="test-radio" value="option1" onChange={handleChange} />,
50
+ });
51
+
52
+ const radio = screen.getByRole('radio');
53
+ expect(radio).toBeDisabled();
54
+ await userEvent.click(radio);
55
+ expect(handleChange).not.toHaveBeenCalled();
56
+ });
57
+
58
+ it('supports name and value attributes', () => {
59
+ renderWith({ control: <ListItem.Radio name="test-radio" value="option1" /> });
60
+
61
+ const radio = screen.getByRole('radio');
62
+ expect(radio).toHaveAttribute('name', 'test-radio');
63
+ // eslint-disable-next-line jest-dom/prefer-to-have-value
64
+ expect(radio).toHaveAttribute('value', 'option1');
65
+ });
66
+ });
@@ -0,0 +1,111 @@
1
+ import { Meta, StoryObj } from '@storybook/react-webpack5';
2
+ import { useState } from 'react';
3
+ import List from '../../list';
4
+ import { ListItem } from '../ListItem';
5
+ import {
6
+ SB_LIST_ITEM_ADDITIONAL_INFO as INFO,
7
+ SB_LIST_ITEM_MEDIA as MEDIA,
8
+ } from '../_stories/subcomponents';
9
+ import type { ListItemRadioProps } from './ListItemRadio';
10
+ import { fn } from 'storybook/test';
11
+ import { ProfileType } from '../../common';
12
+
13
+ const meta: Meta<ListItemRadioProps> = {
14
+ component: ListItem.Radio,
15
+ title: 'Content/ListItem/ListItem.Radio',
16
+ parameters: {
17
+ docs: {
18
+ toc: true,
19
+ },
20
+ },
21
+ args: {
22
+ name: 'radio-group',
23
+ value: 'option1',
24
+ checked: false,
25
+ onChange: fn(),
26
+ },
27
+ argTypes: {
28
+ onChange: {
29
+ table: {
30
+ type: { summary: '(value: string | number) => void' },
31
+ },
32
+ },
33
+ },
34
+ } satisfies Meta<ListItemRadioProps>;
35
+
36
+ export default meta;
37
+
38
+ type Story = StoryObj<ListItemRadioProps>;
39
+
40
+ export const Playground: Story = {
41
+ tags: ['!autodocs'],
42
+ render: (args: ListItemRadioProps) => {
43
+ return (
44
+ <List>
45
+ <ListItem
46
+ title="List item with radio"
47
+ subtitle="Select this option"
48
+ media={MEDIA.avatarSingle}
49
+ control={<ListItem.Radio {...args} />}
50
+ additionalInfo={INFO.nonInteractive}
51
+ />
52
+ </List>
53
+ );
54
+ },
55
+ };
56
+
57
+ /**
58
+ * Radio controls follow the native HTML behaviour and can be grouped using the `name` prop.
59
+ */
60
+ export const RadioGroup: Story = {
61
+ parameters: {
62
+ controls: { disable: true },
63
+ },
64
+ render: function Render() {
65
+ const [selectedValue, setSelectedValue] = useState('2');
66
+
67
+ return (
68
+ <List>
69
+ <ListItem
70
+ control={
71
+ <ListItem.Radio
72
+ name="group"
73
+ value="1"
74
+ checked={selectedValue === '1'}
75
+ onChange={setSelectedValue}
76
+ />
77
+ }
78
+ title="First option"
79
+ subtitle="This is the first choice"
80
+ media={<ListItem.AvatarView profileType={ProfileType.BUSINESS} />}
81
+ />
82
+ <ListItem
83
+ control={
84
+ <ListItem.Radio
85
+ name="group"
86
+ value="2"
87
+ checked={selectedValue === '2'}
88
+ onChange={setSelectedValue}
89
+ />
90
+ }
91
+ title="Second option"
92
+ subtitle="This is the second choice"
93
+ media={<ListItem.AvatarView profileType={ProfileType.BUSINESS} />}
94
+ />
95
+ <ListItem
96
+ control={
97
+ <ListItem.Radio
98
+ name="group"
99
+ value="3"
100
+ checked={selectedValue === '3'}
101
+ onChange={setSelectedValue}
102
+ />
103
+ }
104
+ title="Third option"
105
+ subtitle="This is the third choice"
106
+ media={<ListItem.AvatarView profileType={ProfileType.BUSINESS} />}
107
+ />
108
+ </List>
109
+ );
110
+ },
111
+ };
@@ -0,0 +1,33 @@
1
+ import { useContext } from 'react';
2
+ import RadioButton, { type RadioButtonProps } from '../../common/RadioButton/RadioButton';
3
+ import { useListItemControl } from '../useListItemControl';
4
+ import { ListItemContext } from '../ListItemContext';
5
+
6
+ export type ListItemRadioProps = Omit<
7
+ RadioButtonProps,
8
+ 'disabled' | 'readOnly' | 'className' | 'id'
9
+ >;
10
+
11
+ /**
12
+ * This component allows for rendering a Button control. It's a thin wrapper around the
13
+ * [Button component](https://storybook.wise.design/?path=/docs/content-button--docs), but offers only
14
+ * a subset of its features in line with the ListItem's constraints. <br />
15
+ * <br />
16
+ * Please refer to the [Design documentation](https://wise.design/components/list-item---button) for details.
17
+ */
18
+ export const Radio = function (props: ListItemRadioProps) {
19
+ const { baseItemProps } = useListItemControl('radio', { ...props });
20
+ const { ids, describedByIds } = useContext(ListItemContext);
21
+
22
+ return (
23
+ <RadioButton
24
+ {...props}
25
+ aria-describedby={describedByIds}
26
+ className="wds-list-item-control"
27
+ disabled={baseItemProps.disabled}
28
+ id={ids.control}
29
+ />
30
+ );
31
+ };
32
+
33
+ Radio.displayName = 'ListItem.Radio';
@@ -0,0 +1,2 @@
1
+ export type { ListItemRadioProps } from './ListItemRadio';
2
+ export { Radio } from './ListItemRadio';
@@ -0,0 +1,47 @@
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.Switch', () => {
6
+ const renderWith = (overrides: Partial<ListItemProps> = {}) =>
7
+ render(<ListItem title="Test title" {...overrides} />);
8
+
9
+ it('renders switch with correct role', () => {
10
+ renderWith({ control: <ListItem.Switch checked={false} onClick={() => {}} /> });
11
+ expect(screen.getByRole('switch')).toBeInTheDocument();
12
+ });
13
+
14
+ describe('checked state', () => {
15
+ it('reflects checked state', () => {
16
+ renderWith({ control: <ListItem.Switch checked onClick={() => {}} /> });
17
+ expect(screen.getByRole('switch')).toBeChecked();
18
+ });
19
+
20
+ it('reflects unchecked state', () => {
21
+ renderWith({ control: <ListItem.Switch checked={false} onClick={() => {}} /> });
22
+ expect(screen.getByRole('switch')).not.toBeChecked();
23
+ });
24
+ });
25
+
26
+ it('handles onClick events', async () => {
27
+ const handleClick = jest.fn();
28
+ renderWith({ control: <ListItem.Switch checked={false} onClick={handleClick} /> });
29
+
30
+ await userEvent.click(screen.getByRole('switch'));
31
+ expect(handleClick).toHaveBeenCalledTimes(1);
32
+ });
33
+
34
+ it('is disabled when ListItem is disabled', async () => {
35
+ const handleClick = jest.fn();
36
+ renderWith({
37
+ disabled: true,
38
+ control: <ListItem.Switch checked={false} onClick={handleClick} />,
39
+ });
40
+
41
+ const switchElement = screen.getByRole('switch');
42
+ expect(switchElement).toBeDisabled();
43
+
44
+ await userEvent.click(screen.getByRole('switch'));
45
+ expect(handleClick).not.toHaveBeenCalled();
46
+ });
47
+ });
@@ -0,0 +1,79 @@
1
+ import { Meta, StoryObj } from '@storybook/react-webpack5';
2
+ import { fn } from 'storybook/test';
3
+ import { lorem10, lorem5 } from '../../test-utils';
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 type { ListItemSwitchProps } from './ListItemSwitch';
11
+
12
+ const meta: Meta<ListItemSwitchProps> = {
13
+ component: ListItem.Switch,
14
+ title: 'Content/ListItem/ListItem.Switch',
15
+ parameters: {
16
+ docs: {
17
+ toc: true,
18
+ },
19
+ },
20
+ args: {
21
+ checked: false,
22
+ onClick: fn(),
23
+ },
24
+ argTypes: {
25
+ checked: {
26
+ control: 'boolean',
27
+ },
28
+ onClick: {
29
+ table: {
30
+ type: { summary: '(event?: MouseEvent<HTMLButtonElement>) => void' },
31
+ },
32
+ },
33
+ },
34
+ } satisfies Meta<ListItemSwitchProps>;
35
+
36
+ export default meta;
37
+
38
+ type Story = StoryObj<ListItemSwitchProps>;
39
+
40
+ export const Playground: Story = {
41
+ tags: ['!autodocs'],
42
+ render: (args: ListItemSwitchProps) => {
43
+ return (
44
+ <List>
45
+ <ListItem
46
+ title="List item with switch"
47
+ subtitle="Toggle this setting"
48
+ media={MEDIA.avatarSingle}
49
+ control={<ListItem.Switch {...args} />}
50
+ additionalInfo={INFO.nonInteractive}
51
+ />
52
+ </List>
53
+ );
54
+ },
55
+ };
56
+
57
+ export const States: Story = {
58
+ parameters: {
59
+ controls: { disable: true },
60
+ },
61
+ render: (args) => {
62
+ return (
63
+ <List>
64
+ <ListItem
65
+ title={lorem5}
66
+ subtitle={lorem10}
67
+ media={MEDIA.avatarSingle}
68
+ control={<ListItem.Switch {...args} checked />}
69
+ />
70
+ <ListItem
71
+ key={lorem5}
72
+ title={lorem10}
73
+ media={MEDIA.avatarSingle}
74
+ control={<ListItem.Switch {...args} />}
75
+ />
76
+ </List>
77
+ );
78
+ },
79
+ };
@@ -0,0 +1,33 @@
1
+ import { useContext } from 'react';
2
+ import SwitchComp, { type SwitchProps } from '../../switch';
3
+ import { useListItemControl } from '../useListItemControl';
4
+ import { ListItemContext } from '../ListItemContext';
5
+
6
+ export type ListItemSwitchProps = Omit<
7
+ SwitchProps,
8
+ 'disabled' | 'id' | 'aria-labelledby' | 'aria-label'
9
+ >;
10
+
11
+ /**
12
+ * This component allows for rendering a switch control within a fully interactive ListItem. <br />It's a thin wrapper around the
13
+ * [CheckboxButton component](https://storybook.wise.design/?path=/docs/actions-switch--docs),
14
+ * but offers only a subset of its features in line with the ListItem's constraints. <br />
15
+ *
16
+ * Please refer to the [Design documentation](https://wise.design/components/list-item---switch) for details.
17
+ */
18
+ export const Switch = function (props: ListItemSwitchProps) {
19
+ const { baseItemProps } = useListItemControl('switch', { ...props });
20
+ const { ids, describedByIds } = useContext(ListItemContext);
21
+
22
+ return (
23
+ <SwitchComp
24
+ {...props}
25
+ aria-describedby={describedByIds}
26
+ className="wds-list-item-control"
27
+ disabled={baseItemProps.disabled}
28
+ id={ids.control}
29
+ />
30
+ );
31
+ };
32
+
33
+ Switch.displayName = 'ListItem.Switch';
@@ -0,0 +1,2 @@
1
+ export type { ListItemSwitchProps } from './ListItemSwitch';
2
+ export { Switch } from './ListItemSwitch';