@transferwise/components 0.0.0-experimental-9741cfd → 0.0.0-experimental-2d733a4

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 (68) hide show
  1. package/build/dateInput/DateInput.js +1 -1
  2. package/build/dateInput/DateInput.js.map +1 -1
  3. package/build/dateInput/DateInput.mjs +1 -1
  4. package/build/dateInput/DateInput.mjs.map +1 -1
  5. package/build/index.js +1 -0
  6. package/build/index.js.map +1 -1
  7. package/build/index.mjs +1 -1
  8. package/build/main.css +189 -8
  9. package/build/styles/dateInput/DateInput.css +1 -8
  10. package/build/styles/listItem/ListItem.css +188 -0
  11. package/build/styles/main.css +189 -8
  12. package/build/test-utils/assets/apple-pay-logo.svg +84 -0
  13. package/build/types/index.d.ts +2 -0
  14. package/build/types/index.d.ts.map +1 -1
  15. package/build/types/listItem/ListItem.d.ts +45 -0
  16. package/build/types/listItem/ListItem.d.ts.map +1 -0
  17. package/build/types/listItem/ListItemAdditionalInfo.d.ts +9 -0
  18. package/build/types/listItem/ListItemAdditionalInfo.d.ts.map +1 -0
  19. package/build/types/listItem/ListItemButton.d.ts +4 -0
  20. package/build/types/listItem/ListItemButton.d.ts.map +1 -0
  21. package/build/types/listItem/ListItemCheckbox.d.ts +4 -0
  22. package/build/types/listItem/ListItemCheckbox.d.ts.map +1 -0
  23. package/build/types/listItem/ListItemIconButton.d.ts +7 -0
  24. package/build/types/listItem/ListItemIconButton.d.ts.map +1 -0
  25. package/build/types/listItem/ListItemMedia.d.ts +19 -0
  26. package/build/types/listItem/ListItemMedia.d.ts.map +1 -0
  27. package/build/types/listItem/ListItemNavigation.d.ts +4 -0
  28. package/build/types/listItem/ListItemNavigation.d.ts.map +1 -0
  29. package/build/types/listItem/ListItemSwitch.d.ts +3 -0
  30. package/build/types/listItem/ListItemSwitch.d.ts.map +1 -0
  31. package/build/types/listItem/index.d.ts +6 -0
  32. package/build/types/listItem/index.d.ts.map +1 -0
  33. package/build/types/listItem/prompt/Prompt.d.ts +12 -0
  34. package/build/types/listItem/prompt/Prompt.d.ts.map +1 -0
  35. package/build/types/listItem/useItemControl.d.ts +12 -0
  36. package/build/types/listItem/useItemControl.d.ts.map +1 -0
  37. package/build/types/test-utils/fake-data.d.ts +2 -0
  38. package/build/types/test-utils/fake-data.d.ts.map +1 -1
  39. package/package.json +1 -1
  40. package/src/dateInput/DateInput.css +1 -8
  41. package/src/dateInput/DateInput.less +3 -15
  42. package/src/dateInput/DateInput.tests.story.tsx +2 -8
  43. package/src/dateInput/DateInput.tsx +1 -1
  44. package/src/index.ts +2 -0
  45. package/src/legacylistItem/LegacyListItem.story.tsx +5 -5
  46. package/src/legacylistItem/LegacyListItem.tests.story.tsx +6 -6
  47. package/src/listItem/ListItem.css +188 -0
  48. package/src/listItem/ListItem.less +180 -0
  49. package/src/listItem/ListItem.spec.tsx +35 -0
  50. package/src/listItem/ListItem.story.tsx +329 -0
  51. package/src/listItem/ListItem.tsx +200 -0
  52. package/src/listItem/ListItemAdditionalInfo.tsx +31 -0
  53. package/src/listItem/ListItemButton.spec.tsx +90 -0
  54. package/src/listItem/ListItemButton.tsx +18 -0
  55. package/src/listItem/ListItemCheckbox.tsx +14 -0
  56. package/src/listItem/ListItemIconButton.tsx +13 -0
  57. package/src/listItem/ListItemMedia.tsx +52 -0
  58. package/src/listItem/ListItemNavigation.tsx +11 -0
  59. package/src/listItem/ListItemSwitch.tsx +8 -0
  60. package/src/listItem/index.ts +10 -0
  61. package/src/listItem/prompt/Prompt.spec.tsx +77 -0
  62. package/src/listItem/prompt/Prompt.story.tsx +170 -0
  63. package/src/listItem/prompt/Prompt.tsx +44 -0
  64. package/src/listItem/useItemControl.tsx +16 -0
  65. package/src/main.css +189 -8
  66. package/src/main.less +1 -0
  67. package/src/test-utils/assets/apple-pay-logo.svg +84 -0
  68. package/src/test-utils/fake-data.ts +5 -0
@@ -0,0 +1,35 @@
1
+ import { render, screen } from '../test-utils';
2
+ import ListItem from './ListItem';
3
+
4
+ describe('ListItem', () => {
5
+ describe('spotlight', () => {
6
+ it('only fully interactive variant can have spotlight:active', () => {
7
+ render(<ListItem title="Test Title" spotlight="active" control={<ListItem.Navigation />} />);
8
+ const listItem = screen.getByRole('listitem');
9
+ expect(listItem).toHaveClass('wds-list-item-interactive');
10
+ expect(listItem).toHaveClass('wds-list-item-spotlight-active');
11
+ });
12
+
13
+ it('only fully interactive variant can have spotlight:inactive', () => {
14
+ render(
15
+ <ListItem
16
+ title="Test Title"
17
+ spotlight="inactive"
18
+ control={<ListItem.Switch onClick={() => {}} />}
19
+ />,
20
+ );
21
+ const listItem = screen.getByRole('listitem');
22
+ expect(listItem).toHaveClass('wds-list-item-interactive');
23
+ expect(listItem).toHaveClass('wds-list-item-spotlight-inactive');
24
+ });
25
+
26
+ it('non interactive variant can have spotlight', () => {
27
+ render(<ListItem title="Test Title" spotlight="inactive" />);
28
+ const listItem = screen.getByRole('listitem');
29
+ expect(listItem).toHaveClass('wds-list-item-non-interactive');
30
+ expect(listItem).not.toHaveClass('wds-list-item-interactive');
31
+ expect(listItem).not.toHaveClass('wds-list-item-spotlight-inactive');
32
+ expect(listItem).not.toHaveClass('wds-list-item-spotlight-active');
33
+ });
34
+ });
35
+ });
@@ -0,0 +1,329 @@
1
+ import ListItem, { Props as ItemProps } from './ListItem';
2
+ import { Meta, StoryObj } from '@storybook/react';
3
+ import React, { useState } from 'react';
4
+ import {
5
+ Bank,
6
+ BankTransfer,
7
+ FastFlag,
8
+ MultiCurrency,
9
+ Plus,
10
+ Receipt,
11
+ Savings,
12
+ } from '@transferwise/icons';
13
+
14
+ import { lorem10, lorem20, lorem40, lorem5 } from '../test-utils';
15
+ import { Flag } from '@wise/art';
16
+ import { List } from '../legacylistItem/List';
17
+ import Money from '../money';
18
+ import Section from '../section';
19
+ import Header from '../header';
20
+
21
+ export default {
22
+ component: ListItem,
23
+ title: 'Content/ListItem',
24
+ } satisfies Meta<ItemProps>;
25
+
26
+ type Story = StoryObj<ItemProps>;
27
+
28
+ // TODO: delete this story later when we have more peace of the List Item puzzle
29
+ export const TempPlayground: Story = {
30
+ render: () => {
31
+ const [checked, setChecked] = useState(false);
32
+
33
+ return (
34
+ <div>
35
+ <ListItem
36
+ media={
37
+ <ListItem.AvatarView badge={{ type: 'action' }}>
38
+ <Flag code="BBD" />
39
+ </ListItem.AvatarView>
40
+ }
41
+ title="Test title"
42
+ subtitle="Test subtitle"
43
+ additionalInfo={<ListItem.AdditionalInfo>{lorem10}</ListItem.AdditionalInfo>}
44
+ />
45
+ <ListItem
46
+ media={
47
+ <ListItem.AvatarLayout
48
+ avatars={[{ asset: <Flag code="BBD" /> }, { asset: <Flag code="MXN" /> }]}
49
+ />
50
+ }
51
+ title="Test title"
52
+ subtitle={lorem10}
53
+ additionalInfo={<ListItem.AdditionalInfo>{lorem20}</ListItem.AdditionalInfo>}
54
+ />
55
+ <ListItem
56
+ media={
57
+ <ListItem.AvatarLayout
58
+ size={72}
59
+ orientation="diagonal"
60
+ avatars={[{ asset: <Flag code="BBD" /> }, { asset: <Flag code="MXN" /> }]}
61
+ />
62
+ }
63
+ title="Test title"
64
+ subtitle={lorem10}
65
+ valueTitle="100 GBP"
66
+ valueSubtitle="100 USD"
67
+ additionalInfo={<ListItem.AdditionalInfo>{lorem20}</ListItem.AdditionalInfo>}
68
+ />
69
+ <ListItem
70
+ media={<ListItem.AvatarView profileType="BUSINESS" notification />}
71
+ title="Test title"
72
+ subtitle="Test subtitle"
73
+ additionalInfo={
74
+ <ListItem.AdditionalInfo
75
+ action={{ label: 'Learn more', href: 'https://wise.com', target: '_blank' }}
76
+ >
77
+ {lorem10}
78
+ </ListItem.AdditionalInfo>
79
+ }
80
+ prompt={<ListItem.Prompt type="negative">You have done something wrong</ListItem.Prompt>}
81
+ />
82
+ <ListItem
83
+ media={<ListItem.AvatarView imgSrc="../avatar-square-dude.webp" selected />}
84
+ title="Test title"
85
+ additionalInfo={<ListItem.AdditionalInfo>{lorem10}</ListItem.AdditionalInfo>}
86
+ prompt={
87
+ <ListItem.Prompt
88
+ type="discount"
89
+ action={{
90
+ href: 'https://wise.com',
91
+ target: '_blank',
92
+ 'aria-label': 'clickable prompt',
93
+ }}
94
+ >
95
+ The whole prompt is secretly clickable
96
+ </ListItem.Prompt>
97
+ }
98
+ />
99
+
100
+ <ListItem
101
+ media={<ListItem.Image src="../apple-pay-logo.svg" />}
102
+ title="Accepting Apple Pay"
103
+ subtitle={lorem10}
104
+ />
105
+
106
+ <ListItem
107
+ media={<ListItem.Image src="../wise-card.svg" />}
108
+ title="Wise Business Card"
109
+ subtitle={lorem5}
110
+ />
111
+
112
+ <ListItem
113
+ title="Test title"
114
+ disabled
115
+ subtitle={lorem10}
116
+ additionalInfo={<ListItem.AdditionalInfo>{lorem20}</ListItem.AdditionalInfo>}
117
+ control={<ListItem.Button priority="secondary-neutral">as Button</ListItem.Button>}
118
+ />
119
+ <ListItem
120
+ title="Test title"
121
+ disabled
122
+ subtitle={lorem10}
123
+ additionalInfo={<ListItem.AdditionalInfo>{lorem20}</ListItem.AdditionalInfo>}
124
+ control={
125
+ <ListItem.IconButton>
126
+ <Plus />
127
+ </ListItem.IconButton>
128
+ }
129
+ />
130
+
131
+ <ListItem
132
+ title="Test title"
133
+ subtitle={lorem10}
134
+ additionalInfo={<ListItem.AdditionalInfo>{lorem20}</ListItem.AdditionalInfo>}
135
+ control={
136
+ <ListItem.Button
137
+ priority="secondary-neutral"
138
+ as="a"
139
+ target="dfdf"
140
+ aria-label="adsfasfd"
141
+ href="wise.com"
142
+ >
143
+ as Link
144
+ </ListItem.Button>
145
+ }
146
+ />
147
+
148
+ <ListItem
149
+ media={
150
+ <ListItem.AvatarView>
151
+ <Bank />
152
+ </ListItem.AvatarView>
153
+ }
154
+ spotlight="inactive"
155
+ title="Test title"
156
+ subtitle={lorem5}
157
+ additionalInfo={<ListItem.AdditionalInfo>{lorem10}</ListItem.AdditionalInfo>}
158
+ control={<ListItem.Checkbox indeterminate />}
159
+ className="m-t-2"
160
+ />
161
+
162
+ <ListItem
163
+ media={
164
+ <ListItem.AvatarLayout
165
+ orientation="diagonal"
166
+ avatars={[{ asset: <BankTransfer /> }, { asset: <Bank /> }]}
167
+ />
168
+ }
169
+ spotlight="active"
170
+ title="Test title"
171
+ subtitle={lorem5}
172
+ additionalInfo={<ListItem.AdditionalInfo>{lorem10}</ListItem.AdditionalInfo>}
173
+ control={
174
+ <ListItem.Switch
175
+ checked={checked}
176
+ onClick={() => {
177
+ setChecked(!checked);
178
+ }}
179
+ />
180
+ }
181
+ className="m-t-2 m-b-4"
182
+ />
183
+ </div>
184
+ );
185
+ },
186
+ };
187
+
188
+ export const Radio: Story = {
189
+ render: () => (
190
+ <div>
191
+ {/* Basic */}
192
+ {/* with additional info */}
193
+ {/* with prompt */}
194
+ {/* Disabled */}
195
+ {/* Spotlight */}
196
+ {/* Active */}
197
+ </div>
198
+ ),
199
+ };
200
+
201
+ export const Button: Story = {
202
+ render: () => (
203
+ <div>
204
+ {/* Basic */}
205
+ {/* with additional info */}
206
+ {/* with prompt */}
207
+ {/* partially interactive */}
208
+ {/* Disabled */}
209
+ {/* Spotlight */}
210
+ {/* Active */}
211
+ </div>
212
+ ),
213
+ };
214
+
215
+ export const ButtonInteractivity: Story = {
216
+ render: () => {
217
+ const [clicked, setClicked] = useState(0);
218
+
219
+ return (
220
+ <>
221
+ <label>Count: {clicked}</label>
222
+ <ListItem
223
+ title="Fully interactive"
224
+ subtitle={lorem10}
225
+ additionalInfo={<ListItem.AdditionalInfo>{lorem20}</ListItem.AdditionalInfo>}
226
+ control={
227
+ <ListItem.Button priority="secondary-neutral" onClick={() => setClicked(clicked + 1)}>
228
+ as Button
229
+ </ListItem.Button>
230
+ }
231
+ />
232
+ <ListItem
233
+ partiallyInteractive
234
+ title="Partially interactive"
235
+ subtitle={lorem10}
236
+ additionalInfo={<ListItem.AdditionalInfo>{lorem20}</ListItem.AdditionalInfo>}
237
+ control={
238
+ <ListItem.Button priority="secondary-neutral" onClick={() => setClicked(clicked + 1)}>
239
+ as Button
240
+ </ListItem.Button>
241
+ }
242
+ />
243
+ </>
244
+ );
245
+ },
246
+ };
247
+
248
+ export const NonInteractive: Story = {
249
+ render: () => (
250
+ <div>
251
+ <Section>
252
+ <Header title="Transcation Details" />
253
+ <List>
254
+ <ListItem
255
+ media={
256
+ <ListItem.AvatarView>
257
+ <MultiCurrency />
258
+ </ListItem.AvatarView>
259
+ }
260
+ title="Estimated costs"
261
+ subtitle="Other providers"
262
+ valueTitle={<Money amount={100} currency="GBP" />}
263
+ />
264
+ <ListItem
265
+ media={
266
+ <ListItem.AvatarView>
267
+ <FastFlag />
268
+ </ListItem.AvatarView>
269
+ }
270
+ title="Total paid"
271
+ subtitle="Wise"
272
+ valueTitle={<Money amount={12.15} currency="GBP" />}
273
+ valueSubtitle={<Money amount={100.9} currency="GBP" />}
274
+ />
275
+ <ListItem
276
+ media={
277
+ <ListItem.AvatarView>
278
+ <Receipt />
279
+ </ListItem.AvatarView>
280
+ }
281
+ title="Total fees"
282
+ subtitle={lorem5}
283
+ valueSubtitle={<Money amount={5.5} currency="GBP" />}
284
+ />
285
+ <ListItem
286
+ media={
287
+ <ListItem.AvatarView>
288
+ <Savings />
289
+ </ListItem.AvatarView>
290
+ }
291
+ title="You saved"
292
+ valueSubtitle={<Money amount={1000} currency="GBP" />}
293
+ />
294
+ </List>
295
+ </Section>
296
+
297
+ <ListItem
298
+ as="div"
299
+ title="Bank name and address"
300
+ subtitle="Bank Ltd, 2 Street Boulevard, Singapore, 1213423"
301
+ additionalInfo={
302
+ <ListItem.AdditionalInfo
303
+ action={{ label: 'Learn more', href: 'https://wise.com', target: '_blank' }}
304
+ >
305
+ This is our partner bank in Singapore.
306
+ </ListItem.AdditionalInfo>
307
+ }
308
+ />
309
+ <ListItem
310
+ as="div"
311
+ title="Bank name and address"
312
+ subtitle="Capital Ltd, 2 Canal Street, London, E14 111"
313
+ additionalInfo={
314
+ <ListItem.AdditionalInfo
315
+ action={{ label: 'Learn more', href: 'https://wise.com', target: '_blank' }}
316
+ />
317
+ }
318
+ />
319
+ {/* Basic */}
320
+ {/* with additional info */}
321
+ {/* with prompt */}
322
+ {/* Disabled */}
323
+ {/* Spotlight */}
324
+ {/* Active */}
325
+ </div>
326
+ ),
327
+ };
328
+
329
+ // add intaces with dark mode
@@ -0,0 +1,200 @@
1
+ import { createContext, ReactNode, useId, useMemo, useState } from 'react';
2
+ import { Typography } from '../common';
3
+ import Body from '../body';
4
+ import { AdditionalInfo } from './ListItemAdditionalInfo';
5
+ import { IconButton } from './ListItemIconButton';
6
+ import { Checkbox } from './ListItemCheckbox';
7
+ import { Navigation } from './ListItemNavigation';
8
+ import { clsx } from 'clsx';
9
+ import { Button } from './ListItemButton';
10
+ import { Switch } from './ListItemSwitch';
11
+ import { AvatarLayout, AvatarView, Image } from './ListItemMedia';
12
+ import Prompt from './prompt/Prompt';
13
+
14
+ export type ListItemTypes =
15
+ | 'non-interactive'
16
+ | 'navigation'
17
+ | 'radio'
18
+ | 'checkbox'
19
+ | 'switch'
20
+ | 'button'
21
+ | 'icon-button';
22
+
23
+ export type Props = {
24
+ as?: 'li' | 'div' | 'span';
25
+ inverted?: boolean;
26
+ disabled?: boolean;
27
+ spotlight?: 'active' | 'inactive';
28
+ title: ReactNode;
29
+ subtitle?: ReactNode;
30
+ additionalInfo?: ReactNode;
31
+ valueTitle?: ReactNode;
32
+ valueSubtitle?: ReactNode;
33
+ media?: ReactNode;
34
+ control?: ReactNode;
35
+ prompt?: ReactNode;
36
+ className?: string;
37
+ partiallyInteractive?: boolean;
38
+ };
39
+
40
+ export type ListItemContextData = {
41
+ setControlType: (type: ListItemTypes) => void;
42
+ ids: {
43
+ label: string;
44
+ additionalInfo: string;
45
+ value: string;
46
+ control: string;
47
+ prompt: string;
48
+ };
49
+ props: Pick<Props, 'as' | 'disabled' | 'inverted'>;
50
+ };
51
+
52
+ // @ts-expect-error for now let's mock it with `null` value
53
+ // but actually by default we should specify `setControlType('none')`
54
+ export const ListItemContext = createContext<ListItemContextData>(null);
55
+
56
+ export const ListItem = ({
57
+ as: View = 'li',
58
+ title,
59
+ subtitle,
60
+ additionalInfo,
61
+ prompt,
62
+ inverted,
63
+ media,
64
+ spotlight = undefined,
65
+ valueTitle,
66
+ valueSubtitle,
67
+ control = null,
68
+ disabled,
69
+ className,
70
+ partiallyInteractive,
71
+ }: Props) => {
72
+ /*
73
+ const returnType = (): ReactNode => {
74
+ switch (type) {
75
+ case 'Navigation':
76
+ return <Chevron orientation={Position.RIGHT} disabled />;
77
+ case 'Radio':
78
+ return <RadioButton name="Hello" checked />;
79
+ case 'Checkbox':
80
+ return <CheckboxButton name="Hello" checked />;
81
+ case 'Switch':
82
+ return <Switch onClick={() => console.log('clicked')} />;
83
+ case 'Button':
84
+ return <Button v2>Hello</Button>;
85
+ case 'IconButton':
86
+ return (
87
+ <IconButton size={40} priority="minimal">
88
+ <InfoCircle />
89
+ </IconButton>
90
+ );
91
+ }
92
+ };
93
+ */
94
+ const idPrefix = useId();
95
+
96
+ const [controlType, setControlType] = useState<ListItemTypes>('non-interactive');
97
+ const ids = {
98
+ label: `${idPrefix}_label`,
99
+ value: `${idPrefix}_value`,
100
+ control: `${idPrefix}_control`,
101
+ prompt: `${idPrefix}_prompt`,
102
+ additionalInfo: `${idPrefix}_additional-info`,
103
+ };
104
+
105
+ // TODO: add partially interactive check from Button and IconButtons
106
+ // (https://transferwise.atlassian.net/browse/DS-7562)
107
+ const isFullyInteractive = controlType !== 'non-interactive';
108
+
109
+ const listItemContext = useMemo(
110
+ () => ({ setControlType, ids, props: { as: View, disabled, inverted } }),
111
+ [],
112
+ );
113
+
114
+ const content = (
115
+ <View
116
+ className={clsx(
117
+ 'wds-list-item',
118
+ { 'wds-list-item-interactive': controlType !== 'non-interactive' && !partiallyInteractive },
119
+ `wds-list-item-${controlType}`,
120
+ 'd-flex flex-row',
121
+ { 'align-items-center': !subtitle },
122
+ {
123
+ [`wds-list-item-spotlight-${spotlight}`]: isFullyInteractive && !!spotlight,
124
+ },
125
+ className,
126
+ )}
127
+ aria-describedby={[ids.additionalInfo].join(' ')}
128
+ >
129
+ {media && <div className="wds-list-item-media">{media}</div>}
130
+
131
+ {/* Title + Subtitle + Values + Additional Info - Group */}
132
+ <div className="wds-list-item-body">
133
+ {/* Title + Subtitle + Values - Group */}
134
+ <div className="d-flex justify-content-between">
135
+ <span>
136
+ {/* @ts-expect-error div can have role and aria-lavel props */}
137
+ <Body
138
+ type={Typography.BODY_LARGE_BOLD}
139
+ className="wds-list-item-title"
140
+ // for a11y this needs to be a header but for SEO it shouldn't be `h*` tag
141
+ // so we enable header semantics via `role` and `aria-level` attrs
142
+ role="heading"
143
+ aria-level="4"
144
+ >
145
+ {title}
146
+ </Body>
147
+ <Body className="wds-list-item-subtitle">{subtitle}</Body>
148
+ </span>
149
+ {(valueTitle || valueSubtitle) && (
150
+ <span
151
+ id={ids.value}
152
+ className={clsx('wds-list-item-value', 'd-flex align-items-center', {
153
+ 'flex-column': valueTitle !== undefined && valueSubtitle !== undefined,
154
+ })}
155
+ >
156
+ {valueTitle && (
157
+ <Body type={Typography.BODY_LARGE_BOLD} className="wds-list-item-title-value">
158
+ {valueTitle}
159
+ </Body>
160
+ )}
161
+ {valueSubtitle && (
162
+ <Body className="wds-list-item-subtitle-value">{valueSubtitle}</Body>
163
+ )}
164
+ </span>
165
+ )}
166
+ </div>
167
+
168
+ {/* Additional Info and Prompt here */}
169
+ {Boolean(subtitle) && additionalInfo}
170
+ </div>
171
+ {control === null ? null : <Body className="wds-list-item-control">{control}</Body>}
172
+ {prompt}
173
+ </View>
174
+ );
175
+
176
+ if (!partiallyInteractive && controlType === 'button' && ids.control) {
177
+ return (
178
+ <ListItemContext.Provider value={listItemContext}>
179
+ <label htmlFor={ids.control}>{content}</label>
180
+ </ListItemContext.Provider>
181
+ );
182
+ }
183
+
184
+ return <ListItemContext.Provider value={listItemContext}>{content}</ListItemContext.Provider>;
185
+ };
186
+
187
+ /* eslint-disable functional/immutable-data */
188
+ ListItem.Image = Image;
189
+ ListItem.AvatarView = AvatarView;
190
+ ListItem.AvatarLayout = AvatarLayout;
191
+ ListItem.AdditionalInfo = AdditionalInfo;
192
+ ListItem.Checkbox = Checkbox;
193
+ ListItem.IconButton = IconButton;
194
+ ListItem.Navigation = Navigation;
195
+ ListItem.Button = Button;
196
+ ListItem.Switch = Switch;
197
+ ListItem.Prompt = Prompt;
198
+ /* eslint-enable functional/immutable-data */
199
+
200
+ export default ListItem;
@@ -0,0 +1,31 @@
1
+ import { PropsWithChildren, useContext } from 'react';
2
+ import { ListItemContext, ListItemContextData } from './ListItem';
3
+ import Body from '../body';
4
+ import Link, { LinkProps } from '../link';
5
+ import { Typography } from '../common';
6
+
7
+ export type ListItemAdditionalInfoProps = PropsWithChildren<{
8
+ action?: Pick<LinkProps, 'href' | 'onClick' | 'target'> & { label?: string };
9
+ }>;
10
+
11
+ export const AdditionalInfo = function ({ children, action }: ListItemAdditionalInfoProps) {
12
+ const { ids } = useContext<ListItemContextData>(ListItemContext);
13
+
14
+ return (
15
+ <Body
16
+ type={Typography.BODY_DEFAULT}
17
+ id={ids.additionalInfo}
18
+ className="wds-list-item-additional-info"
19
+ >
20
+ {children}
21
+ {action ? (
22
+ <>
23
+ {' '}
24
+ <Link href={action.href} target={action.target} onClick={action.onClick}>
25
+ {action.label}
26
+ </Link>
27
+ </>
28
+ ) : null}
29
+ </Body>
30
+ );
31
+ };
@@ -0,0 +1,90 @@
1
+ import { render, screen, mockMatchMedia } from '../test-utils';
2
+ import { Button as ItemButton } from './ListItemButton';
3
+ import { ButtonPriority } from '../button/Button.types';
4
+ import { ListItemContext } from './ListItem';
5
+
6
+ mockMatchMedia();
7
+
8
+ describe('ItemButton', () => {
9
+ const mockSetControlType = jest.fn();
10
+
11
+ const renderWithItemContext = (ui: React.ReactNode) => {
12
+ return render(
13
+ <ListItemContext.Provider
14
+ value={{
15
+ setControlType: mockSetControlType,
16
+ ids: {
17
+ label: 'label',
18
+ additionalInfo: 'additional Info',
19
+ value: 'value',
20
+ control: 'control',
21
+ prompt: 'prompt',
22
+ },
23
+ props: {},
24
+ }}
25
+ >
26
+ {ui}
27
+ </ListItemContext.Provider>,
28
+ );
29
+ };
30
+
31
+ beforeEach(() => {
32
+ jest.clearAllMocks();
33
+ });
34
+
35
+ it('always sets v2 to true', () => {
36
+ renderWithItemContext(<ItemButton priority="primary">Test Button</ItemButton>);
37
+ const button = screen.getByRole('button');
38
+ expect(button).toBeInTheDocument();
39
+ expect(mockSetControlType).toHaveBeenCalledWith('button');
40
+ });
41
+
42
+ it('always sets size to "md"', () => {
43
+ renderWithItemContext(<ItemButton priority="primary">Test Button</ItemButton>);
44
+ const button = screen.getByRole('button');
45
+ expect(button).toHaveClass('wds-Button--medium');
46
+ });
47
+
48
+ it('supports all priorities', () => {
49
+ const priorities: ButtonPriority[] = ['primary', 'secondary', 'tertiary'];
50
+ priorities.forEach((priority) => {
51
+ renderWithItemContext(<ItemButton priority={priority}>Test {priority}</ItemButton>);
52
+ const button = screen.getByRole('button', { name: `Test ${priority}` });
53
+ expect(button).toBeInTheDocument();
54
+ });
55
+ });
56
+
57
+ it('renders as a button by default', () => {
58
+ renderWithItemContext(<ItemButton>Click me</ItemButton>);
59
+ const button = screen.getByRole('button');
60
+ expect(button).toBeInTheDocument();
61
+ expect(button.tagName).toBe('BUTTON');
62
+ });
63
+
64
+ it('renders as an anchor when href is provided', () => {
65
+ renderWithItemContext(<ItemButton href="https://example.com">Go to Example</ItemButton>);
66
+ const link = screen.getByRole('link', { name: 'Go to Example' });
67
+ expect(link).toBeInTheDocument();
68
+ expect(link.tagName).toBe('A');
69
+ expect(link).toHaveAttribute('href', 'https://example.com');
70
+ });
71
+
72
+ it('spreads additional props to the button', () => {
73
+ renderWithItemContext(<ItemButton aria-label="Custom Button">Custom</ItemButton>);
74
+ const button = screen.getByRole('button', { name: 'Custom Button' });
75
+ expect(button).toBeInTheDocument();
76
+ expect(button).toHaveAttribute('aria-label', 'Custom Button');
77
+ });
78
+
79
+ it('spreads additional props to the anchor', () => {
80
+ renderWithItemContext(
81
+ <ItemButton href="https://example.com" target="_blank" aria-label="Custom Link">
82
+ Custom Link
83
+ </ItemButton>,
84
+ );
85
+ const link = screen.getByRole('link', { name: 'Custom Link' });
86
+ expect(link).toBeInTheDocument();
87
+ expect(link).toHaveAttribute('href', 'https://example.com');
88
+ expect(link).toHaveAttribute('target', '_blank');
89
+ });
90
+ });