@transferwise/components 0.0.0-experimental-d7fd1c8 → 0.0.0-experimental-55ad5c7

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 (63) hide show
  1. package/build/listItem/AvatarLayout/ListItemAvatarLayout.js +2 -0
  2. package/build/listItem/AvatarLayout/ListItemAvatarLayout.js.map +1 -1
  3. package/build/listItem/AvatarLayout/ListItemAvatarLayout.mjs +2 -0
  4. package/build/listItem/AvatarLayout/ListItemAvatarLayout.mjs.map +1 -1
  5. package/build/listItem/AvatarView/ListItemAvatarView.js +2 -0
  6. package/build/listItem/AvatarView/ListItemAvatarView.js.map +1 -1
  7. package/build/listItem/AvatarView/ListItemAvatarView.mjs +2 -0
  8. package/build/listItem/AvatarView/ListItemAvatarView.mjs.map +1 -1
  9. package/build/listItem/Image/ListItemImage.js +3 -0
  10. package/build/listItem/Image/ListItemImage.js.map +1 -1
  11. package/build/listItem/Image/ListItemImage.mjs +3 -0
  12. package/build/listItem/Image/ListItemImage.mjs.map +1 -1
  13. package/build/listItem/ListItem.js +13 -7
  14. package/build/listItem/ListItem.js.map +1 -1
  15. package/build/listItem/ListItem.mjs +13 -7
  16. package/build/listItem/ListItem.mjs.map +1 -1
  17. package/build/listItem/ListItemContext.js.map +1 -1
  18. package/build/listItem/ListItemContext.mjs.map +1 -1
  19. package/build/listItem/Navigation/ListItemNavigation.js +1 -1
  20. package/build/listItem/Navigation/ListItemNavigation.js.map +1 -1
  21. package/build/listItem/Navigation/ListItemNavigation.mjs +1 -1
  22. package/build/listItem/Navigation/ListItemNavigation.mjs.map +1 -1
  23. package/build/listItem/useListItemMedia.js +21 -0
  24. package/build/listItem/useListItemMedia.js.map +1 -0
  25. package/build/listItem/useListItemMedia.mjs +19 -0
  26. package/build/listItem/useListItemMedia.mjs.map +1 -0
  27. package/build/main.css +23 -12
  28. package/build/styles/listItem/ListItem.css +23 -12
  29. package/build/styles/listItem/ListItem.grid.css +7 -7
  30. package/build/styles/main.css +23 -12
  31. package/build/types/listItem/AvatarLayout/ListItemAvatarLayout.d.ts.map +1 -1
  32. package/build/types/listItem/AvatarView/ListItemAvatarView.d.ts.map +1 -1
  33. package/build/types/listItem/Image/ListItemImage.d.ts +5 -1
  34. package/build/types/listItem/Image/ListItemImage.d.ts.map +1 -1
  35. package/build/types/listItem/ListItem.d.ts +1 -1
  36. package/build/types/listItem/ListItem.d.ts.map +1 -1
  37. package/build/types/listItem/ListItemContext.d.ts +3 -0
  38. package/build/types/listItem/ListItemContext.d.ts.map +1 -1
  39. package/build/types/listItem/_stories/subcomponents.d.ts.map +1 -1
  40. package/build/types/listItem/test-utils.d.ts +7 -0
  41. package/build/types/listItem/test-utils.d.ts.map +1 -0
  42. package/build/types/listItem/useListItemMedia.d.ts +5 -0
  43. package/build/types/listItem/useListItemMedia.d.ts.map +1 -0
  44. package/package.json +4 -4
  45. package/src/listItem/AvatarLayout/ListItemAvatarLayout.tsx +3 -0
  46. package/src/listItem/AvatarView/ListItemAvatarView.tsx +3 -0
  47. package/src/listItem/Button/ListItemButton.spec.tsx +10 -35
  48. package/src/listItem/Image/ListItemImage.spec.tsx +8 -5
  49. package/src/listItem/Image/ListItemImage.story.tsx +2 -1
  50. package/src/listItem/Image/ListItemImage.tsx +8 -1
  51. package/src/listItem/ListItem.css +23 -12
  52. package/src/listItem/ListItem.grid.css +7 -7
  53. package/src/listItem/ListItem.grid.less +11 -11
  54. package/src/listItem/ListItem.less +20 -7
  55. package/src/listItem/ListItem.tsx +24 -12
  56. package/src/listItem/ListItemContext.tsx +5 -0
  57. package/src/listItem/Navigation/ListItemNavigation.tsx +1 -1
  58. package/src/listItem/_stories/ListItem.story.tsx +2 -39
  59. package/src/listItem/_stories/ListItem.variants.test.story.tsx +7 -2
  60. package/src/listItem/_stories/subcomponents.tsx +1 -0
  61. package/src/listItem/test-utils.tsx +33 -0
  62. package/src/listItem/useListItemMedia.tsx +12 -0
  63. package/src/main.css +23 -12
@@ -1,49 +1,24 @@
1
- import { render, screen, mockMatchMedia } from '../../test-utils';
1
+ import { screen, mockMatchMedia } from '../../test-utils';
2
2
  import { Button as ItemButton } from './ListItemButton';
3
3
  import { ButtonPriority } from '../../button/Button.types';
4
- import { ListItemContext } from '../ListItemContext';
4
+ import { renderWithListItemContext, clearListItemMocks, mockSetControlType } from '../test-utils';
5
5
 
6
6
  mockMatchMedia();
7
7
 
8
8
  describe('ItemButton', () => {
9
- const mockSetControlType = jest.fn();
10
- const mockSetControlProps = jest.fn();
11
-
12
- const renderWithItemContext = (ui: React.ReactNode) => {
13
- return render(
14
- <ListItemContext.Provider
15
- value={{
16
- setControlType: mockSetControlType,
17
- setControlProps: mockSetControlProps,
18
- ids: {
19
- title: 'title',
20
- additionalInfo: 'additional Info',
21
- valueTitle: 'value title',
22
- control: 'control',
23
- prompt: 'prompt',
24
- },
25
- props: {},
26
- describedByIds: 'described-by-ids',
27
- }}
28
- >
29
- {ui}
30
- </ListItemContext.Provider>,
31
- );
32
- };
33
-
34
9
  beforeEach(() => {
35
- jest.clearAllMocks();
10
+ clearListItemMocks();
36
11
  });
37
12
 
38
13
  it('always sets v2 to true', () => {
39
- renderWithItemContext(<ItemButton priority="primary">Test Button</ItemButton>);
14
+ renderWithListItemContext(<ItemButton priority="primary">Test Button</ItemButton>);
40
15
  const button = screen.getByRole('button');
41
16
  expect(button).toBeInTheDocument();
42
17
  expect(mockSetControlType).toHaveBeenCalledWith('button');
43
18
  });
44
19
 
45
20
  it('always sets size to "sm"', () => {
46
- renderWithItemContext(<ItemButton priority="primary">Test Button</ItemButton>);
21
+ renderWithListItemContext(<ItemButton priority="primary">Test Button</ItemButton>);
47
22
  const button = screen.getByRole('button');
48
23
  expect(button).toHaveClass('wds-Button--small');
49
24
  });
@@ -51,21 +26,21 @@ describe('ItemButton', () => {
51
26
  it('supports all priorities', () => {
52
27
  const priorities: ButtonPriority[] = ['primary', 'secondary', 'tertiary'];
53
28
  priorities.forEach((priority) => {
54
- renderWithItemContext(<ItemButton priority={priority}>Test {priority}</ItemButton>);
29
+ renderWithListItemContext(<ItemButton priority={priority}>Test {priority}</ItemButton>);
55
30
  const button = screen.getByRole('button', { name: `Test ${priority}` });
56
31
  expect(button).toBeInTheDocument();
57
32
  });
58
33
  });
59
34
 
60
35
  it('renders as a button by default', () => {
61
- renderWithItemContext(<ItemButton>Click me</ItemButton>);
36
+ renderWithListItemContext(<ItemButton>Click me</ItemButton>);
62
37
  const button = screen.getByRole('button');
63
38
  expect(button).toBeInTheDocument();
64
39
  expect(button.tagName).toBe('BUTTON');
65
40
  });
66
41
 
67
42
  it('renders as an anchor when href is provided', () => {
68
- renderWithItemContext(<ItemButton href="https://example.com">Go to Example</ItemButton>);
43
+ renderWithListItemContext(<ItemButton href="https://example.com">Go to Example</ItemButton>);
69
44
  const link = screen.getByRole('link', { name: 'Go to Example' });
70
45
  expect(link).toBeInTheDocument();
71
46
  expect(link.tagName).toBe('A');
@@ -73,14 +48,14 @@ describe('ItemButton', () => {
73
48
  });
74
49
 
75
50
  it('spreads additional props to the button', () => {
76
- renderWithItemContext(<ItemButton aria-label="Custom Button">Custom</ItemButton>);
51
+ renderWithListItemContext(<ItemButton aria-label="Custom Button">Custom</ItemButton>);
77
52
  const button = screen.getByRole('button', { name: 'Custom Button' });
78
53
  expect(button).toBeInTheDocument();
79
54
  expect(button).toHaveAttribute('aria-label', 'Custom Button');
80
55
  });
81
56
 
82
57
  it('spreads additional props to the anchor', () => {
83
- renderWithItemContext(
58
+ renderWithListItemContext(
84
59
  <ItemButton href="https://example.com" target="_blank" aria-label="Custom Link">
85
60
  Custom Link
86
61
  </ItemButton>,
@@ -1,14 +1,17 @@
1
1
  import { render, screen } from '../../test-utils';
2
- import { ListItem } from '../ListItem';
2
+ import { ListItem, type Props as ListItemProps } from '../ListItem';
3
+
4
+ const renderWithMedia = (media: ListItemProps['media']) =>
5
+ render(<ListItem title="Test Title" media={media} />);
3
6
 
4
7
  describe('ListItem.Image', () => {
5
8
  it('renders image with presentation role when no alt text', () => {
6
- render(<ListItem.Image src="test-image.jpg" />);
9
+ renderWithMedia(<ListItem.Image src="test-image.jpg" />);
7
10
  expect(screen.getByRole('presentation')).toHaveAttribute('src', 'test-image.jpg');
8
11
  });
9
12
 
10
13
  it('renders image with graphics-symbol role when alt text provided', () => {
11
- render(<ListItem.Image src="test-image.jpg" alt="Test image description" />);
14
+ renderWithMedia(<ListItem.Image src="test-image.jpg" alt="Test image description" />);
12
15
 
13
16
  const image = screen.getByRole('graphics-symbol');
14
17
  expect(image).toHaveAttribute('alt', 'Test image description');
@@ -16,12 +19,12 @@ describe('ListItem.Image', () => {
16
19
  });
17
20
 
18
21
  it('renders image with loading prop', () => {
19
- render(<ListItem.Image src="test-image.jpg" loading="lazy" />);
22
+ renderWithMedia(<ListItem.Image src="test-image.jpg" loading="lazy" />);
20
23
  expect(screen.getByRole('presentation')).toHaveAttribute('loading', 'lazy');
21
24
  });
22
25
 
23
26
  it('renders image with className prop', () => {
24
- render(<ListItem.Image src="test-image.jpg" className="custom-image-class" />);
27
+ renderWithMedia(<ListItem.Image src="test-image.jpg" className="custom-image-class" />);
25
28
  expect(screen.getByRole('presentation')).toHaveClass('custom-image-class');
26
29
  });
27
30
  });
@@ -16,7 +16,8 @@ export default {
16
16
  },
17
17
  args: {
18
18
  src: 'https://wise.com/public-resources/assets/spend/card-asset/medium/v2/personal_green_wise_2023_active.svg',
19
- size: undefined,
19
+ size: 72,
20
+ height: 47,
20
21
  loading: undefined,
21
22
  alt: undefined,
22
23
  },
@@ -1,5 +1,6 @@
1
1
  import ImageComp, { type ImageProps } from '../../image';
2
2
  import { clsx } from 'clsx';
3
+ import { useListItemMedia } from '../useListItemMedia';
3
4
 
4
5
  type SizeProp = {};
5
6
 
@@ -8,6 +9,10 @@ export type ListItemImageProps = Omit<ImageProps, 'stretch' | 'shrink' | 'id' |
8
9
  * The size of square container for the image, matching available avatar sizes.
9
10
  */
10
11
  size?: 32 | 40 | 48 | 56 | 72;
12
+ /**
13
+ * If you're using an image that isn't a square or circle, also tell us the height so that we can align the control to it properly.
14
+ */
15
+ height?: number;
11
16
  /**
12
17
  * When unset, it will force `role="presentation"` on the image. Otherwise, it will set `role="graphics-symbol"`.
13
18
  */
@@ -23,7 +28,9 @@ export type ListItemImageProps = Omit<ImageProps, 'stretch' | 'shrink' | 'id' |
23
28
  *
24
29
  * > **NB**: This component is [not intended for use with illustrations](https://wise.design/foundations/illustration#scale).
25
30
  */
26
- export const Image = ({ alt = '', size = 48, ...props }: ListItemImageProps) => {
31
+ export const Image = ({ alt = '', size = 48, height, ...props }: ListItemImageProps) => {
32
+ useListItemMedia(height ?? size);
33
+
27
34
  return (
28
35
  <div
29
36
  className={clsx('wds-list-item-media-image')}
@@ -9,17 +9,17 @@
9
9
  .wds-list-item-gridWrapper.wds-list-item-hasMedia-hasControl.wds-list-item-hasInfo-hasPrompt {
10
10
  grid-template-columns: auto 1fr auto;
11
11
  grid-template-rows: auto auto auto;
12
- grid-template-areas: "media body control" "media info control" "media prompt prompt";
12
+ grid-template-areas: "media body control" "media info ." ". prompt prompt";
13
13
  }
14
14
  .wds-list-item-gridWrapper.wds-list-item-hasMedia-hasControl.wds-list-item-hasInfo-noPrompt {
15
15
  grid-template-columns: auto 1fr auto;
16
16
  grid-template-rows: auto auto;
17
- grid-template-areas: "media body control" "media info control";
17
+ grid-template-areas: "media body control" "media info .";
18
18
  }
19
19
  .wds-list-item-gridWrapper.wds-list-item-hasMedia-hasControl.wds-list-item-noInfo-hasPrompt {
20
20
  grid-template-columns: auto 1fr auto;
21
21
  grid-template-rows: auto auto;
22
- grid-template-areas: "media body control" "media prompt prompt";
22
+ grid-template-areas: "media body control" ". prompt prompt";
23
23
  }
24
24
  .wds-list-item-gridWrapper.wds-list-item-hasMedia-hasControl.wds-list-item-noInfo-noPrompt {
25
25
  grid-template-columns: auto 1fr auto;
@@ -29,7 +29,7 @@
29
29
  .wds-list-item-gridWrapper.wds-list-item-hasMedia-noControl.wds-list-item-hasInfo-hasPrompt {
30
30
  grid-template-columns: auto 1fr;
31
31
  grid-template-rows: auto auto;
32
- grid-template-areas: "media body" "media info" "media prompt";
32
+ grid-template-areas: "media body" "media info" ". prompt";
33
33
  }
34
34
  .wds-list-item-gridWrapper.wds-list-item-hasMedia-noControl.wds-list-item-hasInfo-noPrompt {
35
35
  grid-template-columns: auto 1fr;
@@ -52,12 +52,12 @@
52
52
  .wds-list-item-gridWrapper.wds-list-item-noMedia-hasControl.wds-list-item-hasInfo-hasPrompt {
53
53
  grid-template-columns: 1fr auto;
54
54
  grid-template-rows: auto auto auto;
55
- grid-template-areas: "body control" "info control" "prompt prompt";
55
+ grid-template-areas: "body control" "info ." "prompt prompt";
56
56
  }
57
57
  .wds-list-item-gridWrapper.wds-list-item-noMedia-hasControl.wds-list-item-hasInfo-noPrompt {
58
58
  grid-template-columns: 1fr auto;
59
59
  grid-template-rows: auto auto;
60
- grid-template-areas: "body control" "info control";
60
+ grid-template-areas: "body control" "info .";
61
61
  }
62
62
  .wds-list-item-gridWrapper.wds-list-item-noMedia-hasControl.wds-list-item-noInfo-hasPrompt {
63
63
  grid-template-columns: 1fr auto;
@@ -221,7 +221,7 @@
221
221
  grid-template-areas: "body";
222
222
  }
223
223
  }
224
- @container (max-width: 296px) {
224
+ @container (max-width: 297px) {
225
225
  .wds-list-item-gridWrapper.wds-list-item-hasMedia-hasControl.wds-list-item-hasInfo-hasPrompt {
226
226
  grid-template-columns: auto 1fr;
227
227
  grid-template-rows: auto auto auto;
@@ -450,7 +450,8 @@
450
450
  }
451
451
  .wds-list-item-media-image {
452
452
  width: var(--item-media-image-size);
453
- height: var(--item-media-image-size);
453
+ height: auto;
454
+ height: var(--wds-list-item-control-wrapper-height, auto);
454
455
  }
455
456
  .wds-list-item-body {
456
457
  grid-area: body;
@@ -468,6 +469,14 @@
468
469
  color: #37517e;
469
470
  color: var(--color-content-primary);
470
471
  }
472
+ .wds-list-item-value {
473
+ text-align: right;
474
+ }
475
+ .wds-list-item-body-center {
476
+ display: flex;
477
+ flex-direction: row;
478
+ align-items: center;
479
+ }
471
480
  .wds-list-item-additional-info {
472
481
  grid-area: info;
473
482
  color: #768e9c;
@@ -475,12 +484,14 @@
475
484
  margin-top: calc(4px * -1);
476
485
  margin-top: calc(var(--size-4) * -1);
477
486
  }
478
- .wds-list-item-value {
479
- align-items: end;
480
- text-align: right;
481
- }
482
487
  .wds-list-item-control-wrapper {
483
488
  grid-area: control;
489
+ align-content: center;
490
+ max-height: var(--wds-list-item-control-wrapper-height);
491
+ }
492
+ .wds-list-item-navigation .tw-icon-chevron-right {
493
+ color: #c9cbce;
494
+ color: var(--color-interactive-secondary);
484
495
  }
485
496
  .wds-list-item-control {
486
497
  flex: 0 0 auto;
@@ -9,17 +9,17 @@
9
9
  .wds-list-item-gridWrapper.wds-list-item-hasMedia-hasControl.wds-list-item-hasInfo-hasPrompt {
10
10
  grid-template-columns: auto 1fr auto;
11
11
  grid-template-rows: auto auto auto;
12
- grid-template-areas: "media body control" "media info control" "media prompt prompt";
12
+ grid-template-areas: "media body control" "media info ." ". prompt prompt";
13
13
  }
14
14
  .wds-list-item-gridWrapper.wds-list-item-hasMedia-hasControl.wds-list-item-hasInfo-noPrompt {
15
15
  grid-template-columns: auto 1fr auto;
16
16
  grid-template-rows: auto auto;
17
- grid-template-areas: "media body control" "media info control";
17
+ grid-template-areas: "media body control" "media info .";
18
18
  }
19
19
  .wds-list-item-gridWrapper.wds-list-item-hasMedia-hasControl.wds-list-item-noInfo-hasPrompt {
20
20
  grid-template-columns: auto 1fr auto;
21
21
  grid-template-rows: auto auto;
22
- grid-template-areas: "media body control" "media prompt prompt";
22
+ grid-template-areas: "media body control" ". prompt prompt";
23
23
  }
24
24
  .wds-list-item-gridWrapper.wds-list-item-hasMedia-hasControl.wds-list-item-noInfo-noPrompt {
25
25
  grid-template-columns: auto 1fr auto;
@@ -29,7 +29,7 @@
29
29
  .wds-list-item-gridWrapper.wds-list-item-hasMedia-noControl.wds-list-item-hasInfo-hasPrompt {
30
30
  grid-template-columns: auto 1fr;
31
31
  grid-template-rows: auto auto;
32
- grid-template-areas: "media body" "media info" "media prompt";
32
+ grid-template-areas: "media body" "media info" ". prompt";
33
33
  }
34
34
  .wds-list-item-gridWrapper.wds-list-item-hasMedia-noControl.wds-list-item-hasInfo-noPrompt {
35
35
  grid-template-columns: auto 1fr;
@@ -52,12 +52,12 @@
52
52
  .wds-list-item-gridWrapper.wds-list-item-noMedia-hasControl.wds-list-item-hasInfo-hasPrompt {
53
53
  grid-template-columns: 1fr auto;
54
54
  grid-template-rows: auto auto auto;
55
- grid-template-areas: "body control" "info control" "prompt prompt";
55
+ grid-template-areas: "body control" "info ." "prompt prompt";
56
56
  }
57
57
  .wds-list-item-gridWrapper.wds-list-item-noMedia-hasControl.wds-list-item-hasInfo-noPrompt {
58
58
  grid-template-columns: 1fr auto;
59
59
  grid-template-rows: auto auto;
60
- grid-template-areas: "body control" "info control";
60
+ grid-template-areas: "body control" "info .";
61
61
  }
62
62
  .wds-list-item-gridWrapper.wds-list-item-noMedia-hasControl.wds-list-item-noInfo-hasPrompt {
63
63
  grid-template-columns: 1fr auto;
@@ -221,7 +221,7 @@
221
221
  grid-template-areas: "body";
222
222
  }
223
223
  }
224
- @container (max-width: 296px) {
224
+ @container (max-width: 297px) {
225
225
  .wds-list-item-gridWrapper.wds-list-item-hasMedia-hasControl.wds-list-item-hasInfo-hasPrompt {
226
226
  grid-template-columns: auto 1fr;
227
227
  grid-template-rows: auto auto auto;
@@ -10,8 +10,8 @@
10
10
  grid-template-rows: auto auto auto;
11
11
  grid-template-areas:
12
12
  "media body control"
13
- "media info control"
14
- "media prompt prompt";
13
+ "media info ."
14
+ ". prompt prompt";
15
15
  }
16
16
 
17
17
  &.wds-list-item-hasInfo-noPrompt {
@@ -19,7 +19,7 @@
19
19
  grid-template-rows: auto auto;
20
20
  grid-template-areas:
21
21
  "media body control"
22
- "media info control"
22
+ "media info ."
23
23
  }
24
24
 
25
25
  &.wds-list-item-noInfo-hasPrompt {
@@ -27,7 +27,7 @@
27
27
  grid-template-rows: auto auto;
28
28
  grid-template-areas:
29
29
  "media body control"
30
- "media prompt prompt";
30
+ ". prompt prompt";
31
31
  }
32
32
 
33
33
  &.wds-list-item-noInfo-noPrompt {
@@ -45,7 +45,7 @@
45
45
  grid-template-areas:
46
46
  "media body"
47
47
  "media info"
48
- "media prompt";
48
+ ". prompt";
49
49
  }
50
50
 
51
51
  &.wds-list-item-hasInfo-noPrompt {
@@ -53,7 +53,7 @@
53
53
  grid-template-rows: auto auto auto;
54
54
  grid-template-areas:
55
55
  "media body"
56
- "media info"
56
+ "media info"
57
57
  }
58
58
 
59
59
  &.wds-list-item-noInfo-hasPrompt {
@@ -82,7 +82,7 @@
82
82
  grid-template-rows: auto auto auto;
83
83
  grid-template-areas:
84
84
  "body control"
85
- "info control"
85
+ "info ."
86
86
  "prompt prompt";
87
87
  }
88
88
 
@@ -91,7 +91,7 @@
91
91
  grid-template-rows: auto auto;
92
92
  grid-template-areas:
93
93
  "body control"
94
- "info control"
94
+ "info ."
95
95
  }
96
96
 
97
97
  &.wds-list-item-noInfo-hasPrompt {
@@ -246,7 +246,7 @@
246
246
  grid-template-rows: auto auto auto;
247
247
  grid-template-areas:
248
248
  "media media"
249
- "body body"
249
+ "body body"
250
250
  "info info";
251
251
  }
252
252
 
@@ -378,7 +378,7 @@
378
378
  }
379
379
  }
380
380
 
381
- @container (max-width: 296px) {
381
+ @container (max-width: 297px) {
382
382
  &.wds-list-item-hasMedia-hasControl {
383
383
  &.wds-list-item-hasInfo-hasPrompt {
384
384
  grid-template-columns: auto 1fr;
@@ -461,7 +461,7 @@
461
461
  grid-template-rows: auto auto auto;
462
462
  grid-template-areas:
463
463
  "body"
464
- "info"
464
+ "info"
465
465
  }
466
466
 
467
467
  &.wds-list-item-noInfo-hasPrompt {
@@ -110,7 +110,7 @@
110
110
 
111
111
  &-image {
112
112
  width: var(--item-media-image-size);
113
- height: var(--item-media-image-size);
113
+ height: var(--wds-list-item-control-wrapper-height, auto);
114
114
  }
115
115
  }
116
116
 
@@ -124,7 +124,17 @@
124
124
  }
125
125
 
126
126
  &-title {
127
- color: var(--color-content-primary);
127
+ color: var(--color-content-primary);
128
+ }
129
+
130
+ &-value {
131
+ text-align: right;
132
+ }
133
+
134
+ &-body-center {
135
+ display: flex;
136
+ flex-direction: row;
137
+ align-items: center;
128
138
  }
129
139
 
130
140
  &-additional-info {
@@ -133,13 +143,16 @@
133
143
  margin-top: calc(var(--size-4) * -1);
134
144
  }
135
145
 
136
- &-value {
137
- align-items: end;
138
- text-align: right;
139
- }
140
-
141
146
  &-control-wrapper {
142
147
  grid-area: control;
148
+ align-content: center;
149
+ max-height: var(--wds-list-item-control-wrapper-height);
150
+ }
151
+
152
+ &-navigation {
153
+ .tw-icon-chevron-right {
154
+ color: var(--color-interactive-secondary);
155
+ }
143
156
  }
144
157
 
145
158
  &-control {
@@ -4,6 +4,7 @@ import {
4
4
  useId,
5
5
  useMemo,
6
6
  useState,
7
+ useEffect,
7
8
  type PropsWithChildren,
8
9
  type ReactNode,
9
10
  } from 'react';
@@ -22,7 +23,11 @@ import { AvatarView } from './AvatarView';
22
23
  import { Image } from './Image';
23
24
  import { Prompt } from './Prompt';
24
25
  import { PrimitiveAnchor } from '../primitives';
25
- import { ListItemContext, type ListItemContextData } from './ListItemContext';
26
+ import {
27
+ ListItemContext,
28
+ type ListItemContextData,
29
+ type ListItemMediaSize,
30
+ } from './ListItemContext';
26
31
 
27
32
  export type ListItemTypes =
28
33
  | 'non-interactive'
@@ -119,6 +124,8 @@ export const ListItem = ({
119
124
  const idPrefix = useId();
120
125
  const [controlProps, setControlProps] = useState<ListItemControlProps>({});
121
126
  const [controlType, setControlType] = useState<ListItemTypes>('non-interactive');
127
+ const [mediaSize, setMediaSize] = useState<number | undefined>();
128
+
122
129
  const ids: ListItemContextData['ids'] = {
123
130
  title: `${idPrefix}_title`,
124
131
  ...(subtitle ? { subtitle: `${idPrefix}_subtitle` } : {}),
@@ -156,11 +163,13 @@ export const ListItem = ({
156
163
  () => ({
157
164
  setControlType,
158
165
  setControlProps,
166
+ setMediaSize,
159
167
  ids,
160
168
  props: { disabled, inverted },
169
+ mediaSize,
161
170
  describedByIds,
162
171
  }),
163
- [describedByIds],
172
+ [describedByIds, mediaSize],
164
173
  );
165
174
  const gridColumnsStyle = {
166
175
  '--wds-list-item-body-left': valueColumnWidth ? `${100 - valueColumnWidth}fr` : '50fr',
@@ -247,7 +256,11 @@ export const ListItem = ({
247
256
  style={valueColumnWidth ? gridColumnsStyle : undefined}
248
257
  >
249
258
  {/* Title + Subtitle + Values - Group */}
250
- <span>
259
+ <span
260
+ className={clsx({
261
+ 'wds-list-item-body-center': title && !subtitle,
262
+ })}
263
+ >
251
264
  {(() => {
252
265
  const titles = [
253
266
  <Body
@@ -274,6 +287,8 @@ export const ListItem = ({
274
287
  <span
275
288
  className={clsx('wds-list-item-value', {
276
289
  'flex-column': valueTitle !== undefined || valueSubtitle !== undefined,
290
+ 'wds-list-item-body-center':
291
+ (valueTitle && !valueSubtitle) || (!valueTitle && valueSubtitle),
277
292
  })}
278
293
  >
279
294
  {(() => {
@@ -312,6 +327,11 @@ export const ListItem = ({
312
327
  className={clsx('wds-list-item-control-wrapper', {
313
328
  'wds-list-item-button-control': controlType === 'button',
314
329
  })}
330
+ style={
331
+ {
332
+ '--wds-list-item-control-wrapper-height': mediaSize ? `${mediaSize}px` : 'auto',
333
+ } as React.CSSProperties
334
+ }
315
335
  >
316
336
  {control}
317
337
  </Body>
@@ -364,7 +384,6 @@ function View({
364
384
  href={(controlProps as ListItemNavigationProps)?.href}
365
385
  target={(controlProps as ListItemNavigationProps)?.target}
366
386
  className={clsx('wds-list-item-view d-flex flex-row', {
367
- 'align-items-center': !subtitle,
368
387
  'wds-list-item-control': controlType === 'navigation',
369
388
  })}
370
389
  disabled={disabled}
@@ -380,13 +399,7 @@ function View({
380
399
  if (isPartiallyInteractive || controlType === 'non-interactive') {
381
400
  return (
382
401
  <div className={clsx('wds-list-item-gridWrapper', className)}>
383
- <div
384
- className={clsx('wds-list-item-view d-flex flex-row', {
385
- 'align-items-center': !subtitle,
386
- })}
387
- >
388
- {children}
389
- </div>
402
+ <div className={clsx('wds-list-item-view d-flex flex-row')}>{children}</div>
390
403
 
391
404
  {renderExtras()}
392
405
  </div>
@@ -401,7 +414,6 @@ function View({
401
414
  <label
402
415
  htmlFor={ids.control}
403
416
  className={clsx('wds-list-item-view', {
404
- 'align-items-center': !subtitle,
405
417
  clickable: !disabled,
406
418
  fullyInteractive: !isPartiallyInteractive,
407
419
  })}
@@ -1,9 +1,13 @@
1
1
  import { createContext } from 'react';
2
2
  import type { ListItemTypes, ListItemControlProps, Props as ListItemProps } from './ListItem';
3
+ import type { ListItemAvatarViewProps } from './AvatarView';
4
+
5
+ export type ListItemMediaSize = 32 | 40 | 48 | 56 | 72;
3
6
 
4
7
  export type ListItemContextData = {
5
8
  setControlType: (type: ListItemTypes) => void;
6
9
  setControlProps: (props: ListItemControlProps) => void;
10
+ setMediaSize: (size: number | undefined) => void;
7
11
  ids: {
8
12
  title: string;
9
13
  subtitle?: string;
@@ -14,6 +18,7 @@ export type ListItemContextData = {
14
18
  prompt?: string;
15
19
  };
16
20
  props: Pick<ListItemProps, 'disabled' | 'inverted'>;
21
+ mediaSize?: number;
17
22
  describedByIds: string;
18
23
  };
19
24
 
@@ -19,7 +19,7 @@ export const Navigation = function Navigation({ href = '#', ...props }: ListItem
19
19
  const { baseItemProps } = useListItemControl('navigation', { href, ...props });
20
20
  const { ids, describedByIds } = useContext(ListItemContext);
21
21
  const isLink = href !== '#';
22
- const icon = <ChevronRight size={24} />;
22
+ const icon = <ChevronRight size={16} />;
23
23
 
24
24
  if (baseItemProps.disabled) return <BackslashCircle size={24} />;
25
25
 
@@ -2,7 +2,6 @@ import { useState } from 'react';
2
2
  import { Meta, StoryObj } from '@storybook/react-webpack5';
3
3
  import { MultiCurrency, Plus } from '@transferwise/icons';
4
4
  import { lorem5, lorem10 } from '../../test-utils';
5
- import Emphasis from '../../emphasis';
6
5
  import { ListItem, Props as ItemProps } from '../ListItem';
7
6
  import {
8
7
  SB_LIST_ITEM_CONTROLS as CONTROLS,
@@ -242,7 +241,7 @@ export const Playground: StoryObj<PreviewStoryArgs> = {
242
241
  const [props, previewProps] = getPropsForPreview(args);
243
242
 
244
243
  return (
245
- <ol key={args.disabled ? 'disabled' : 'enabled'} className="list-unstyled">
244
+ <ol className="list-unstyled">
246
245
  <ListItem {...props} {...previewProps} />
247
246
  </ol>
248
247
  );
@@ -426,7 +425,7 @@ export const Spotlight: Story = {
426
425
  * All of these limitations were put in place to ensure that the list item is compliant with
427
426
  * accessibility guidance and offers consistent experience across all engineering platforms.
428
427
  *
429
- * Please refer to the [design documentation](https://wise.design/components/list-item#interaction) for more details.
428
+ * Please refer to the [design documentation](https://wise.design/components/list-item#:~:text=specific%20list%20item.-,Interaction,-The%20list%20item) for more details.
430
429
  */
431
430
  export const Interactivity: Story = {
432
431
  render: function Render() {
@@ -502,8 +501,6 @@ export const Interactivity: Story = {
502
501
  * to a new icon for improved user experience and accessibility. <br />
503
502
  * For the same reason, only the control part of the List Item is dimmed, while all the content
504
503
  * retains high contrast and interactivity.
505
- *
506
- * Please refer to the [design documentation](https://wise.design/components/list-item#accessibility:~:text=-,Disabled%20states,-) for more details
507
504
  */
508
505
  export const Disabled: Story = {
509
506
  parameters: {
@@ -671,37 +668,3 @@ export const Disabled: Story = {
671
668
  </div>
672
669
  ),
673
670
  };
674
-
675
- /**
676
- * For backwards compatibility, all of the ListItem's content elements accept `ReactNode`.
677
- * That said, most of them should be fed with plain text only. The exception is `valueTitle`
678
- * and `valueSubtitle` which can be augmented with sentiment colours, strikethrough or bold
679
- * styles. <br />
680
- *
681
- * Please refer to the [design documentation](https://wise.design/components/list-item#content) for more details.
682
- */
683
- export const StylingLabels: Story = {
684
- args: {
685
- title: lorem5,
686
- subtitle: lorem10,
687
- media: MEDIA.image,
688
- control: CONTROLS.iconButton,
689
- prompt: PROMPTS.interactive,
690
- },
691
- render: function Render(args) {
692
- return (
693
- <ol className="list-unstyled">
694
- <ListItem
695
- {...args}
696
- valueTitle={<Emphasis text="<negative>100 GBP</negative>" />}
697
- valueSubtitle={<s>125 GBP</s>}
698
- />
699
- <ListItem
700
- {...args}
701
- valueTitle={<Emphasis text="<positive>100 GBP</positive>" />}
702
- valueSubtitle={<strong>125 GBP</strong>}
703
- />
704
- </ol>
705
- );
706
- },
707
- };