@transferwise/components 0.0.0-experimental-131773e → 0.0.0-experimental-1bec623

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 (35) hide show
  1. package/build/listItem/Button/ListItemButton.js +2 -2
  2. package/build/listItem/Button/ListItemButton.js.map +1 -1
  3. package/build/listItem/Button/ListItemButton.mjs +2 -2
  4. package/build/listItem/Button/ListItemButton.mjs.map +1 -1
  5. package/build/listItem/ListItem.js +1 -1
  6. package/build/listItem/ListItem.js.map +1 -1
  7. package/build/listItem/ListItem.mjs +1 -1
  8. package/build/listItem/ListItem.mjs.map +1 -1
  9. package/build/main.css +5 -0
  10. package/build/styles/listItem/ListItem.css +5 -0
  11. package/build/styles/main.css +5 -0
  12. package/build/types/listItem/Button/ListItemButton.d.ts.map +1 -1
  13. package/build/types/listItem/ListItem.d.ts.map +1 -1
  14. package/package.json +1 -1
  15. package/src/listItem/AdditionalInfo/ListItemAdditionalInfo.spec.tsx +56 -0
  16. package/src/listItem/AvatarLayout/ListItemAvatarLayout.spec.tsx +59 -0
  17. package/src/listItem/AvatarView/ListItemAvatarView.spec.tsx +75 -0
  18. package/src/listItem/AvatarView/ListItemAvatarView.story.tsx +2 -2
  19. package/src/listItem/Button/ListItemButton.tsx +2 -2
  20. package/src/listItem/Checkbox/ListItemCheckbox.spec.tsx +82 -0
  21. package/src/listItem/IconButton/ListItemIconButton.spec.tsx +119 -0
  22. package/src/listItem/Image/ListItemImage.spec.tsx +27 -0
  23. package/src/listItem/ListItem.css +5 -0
  24. package/src/listItem/ListItem.less +8 -1
  25. package/src/listItem/ListItem.spec.tsx +1406 -7
  26. package/src/listItem/ListItem.tsx +2 -1
  27. package/src/listItem/Navigation/ListItemNavigation.spec.tsx +59 -0
  28. package/src/listItem/Prompt/ListItemPrompt.spec.tsx +14 -27
  29. package/src/listItem/Prompt/ListItemPrompt.story.tsx +5 -3
  30. package/src/listItem/Radio/ListItemRadio.spec.tsx +66 -0
  31. package/src/listItem/Switch/ListItemSwitch.spec.tsx +47 -0
  32. package/src/listItem/_stories/ListItem.layout.test.story.tsx +21 -1
  33. package/src/listItem/_stories/ListItem.variants.test.story.tsx +1 -1
  34. package/src/main.css +5 -0
  35. package/src/test-utils/Parameters.d.ts +0 -77
@@ -216,7 +216,8 @@ export const ListItem = ({
216
216
  {
217
217
  'wds-list-item-interactive': isFullyInteractive,
218
218
  'wds-list-item-partially-interactive': isPartiallyInteractive,
219
- [`wds-list-item-spotlight-${spotlight}`]: isFullyInteractive && !!spotlight,
219
+ [`wds-list-item-spotlight wds-list-item-spotlight-${spotlight}`]:
220
+ isFullyInteractive && !!spotlight,
220
221
  disabled,
221
222
  },
222
223
  className,
@@ -0,0 +1,59 @@
1
+ import { render, screen } from '../../test-utils';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { ListItem, type Props as ListItemProps } from '../ListItem';
4
+
5
+ describe('ListItem.Navigation', () => {
6
+ const renderWith = (overrides: Partial<ListItemProps> = {}) =>
7
+ render(<ListItem title="Test Title" {...overrides} />);
8
+
9
+ describe('as button', () => {
10
+ it('renders as button if onClick is set but no href', () => {
11
+ renderWith({ control: <ListItem.Navigation onClick={() => {}} /> });
12
+ expect(screen.getByRole('button')).toBeInTheDocument();
13
+ });
14
+
15
+ it('handles onClick events', async () => {
16
+ const handleClick = jest.fn();
17
+ renderWith({ control: <ListItem.Navigation onClick={handleClick} /> });
18
+
19
+ await userEvent.click(screen.getByRole('button'));
20
+ expect(handleClick).toHaveBeenCalledTimes(1);
21
+ });
22
+
23
+ it('respects disabled state', async () => {
24
+ renderWith({
25
+ disabled: true,
26
+ control: <ListItem.Navigation onClick={jest.fn()} />,
27
+ });
28
+ expect(screen.getByTestId('backslash-circle-icon')).toBeInTheDocument();
29
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
30
+ expect(screen.queryByRole('link')).not.toBeInTheDocument();
31
+ });
32
+ });
33
+
34
+ describe('as link', () => {
35
+ it('renders a link if href is set', () => {
36
+ renderWith({ control: <ListItem.Navigation href="/test-path" /> });
37
+
38
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
39
+ expect(screen.getByRole('link')).toBeInTheDocument();
40
+ });
41
+
42
+ it('respects target prop', () => {
43
+ renderWith({
44
+ control: <ListItem.Navigation href="/test-path" target="_blank" />,
45
+ });
46
+ const link = screen.getByRole('link');
47
+ expect(link).toHaveAttribute('target', '_blank');
48
+ expect(link).toHaveAttribute('rel', 'noopener noreferrer');
49
+ });
50
+
51
+ it('renders disabled icon when ListItem is disabled', () => {
52
+ renderWith({
53
+ disabled: true,
54
+ control: <ListItem.Navigation href="wise.com" />,
55
+ });
56
+ expect(screen.getByTestId('backslash-circle-icon')).toBeInTheDocument();
57
+ });
58
+ });
59
+ });
@@ -1,34 +1,20 @@
1
- import { render, screen, mockMatchMedia } from '../../test-utils';
1
+ import { mockMatchMedia, render, screen } from '../../test-utils';
2
2
  import { Sentiment } from '../../common';
3
- import { type ListItemPromptProps, Prompt } from './ListItemPrompt';
4
- import { ListItemContext } from '../ListItemContext';
3
+ import { ListItem } from '../ListItem';
4
+ import type { ListItemPromptProps } from './ListItemPrompt';
5
5
 
6
6
  mockMatchMedia();
7
7
 
8
- const mockContext = {
9
- setControlType: jest.fn(),
10
- setControlProps: jest.fn(),
11
- ids: {
12
- title: 'title',
13
- control: 'control',
14
- prompt: 'prompt',
15
- additionalInfo: 'additionalInfo',
16
- valueTitle: 'valueTitle',
17
- valueSubtitle: 'valueSubtitle',
18
- subtitle: 'subtitle',
19
- },
20
- props: {},
21
- describedByIds: 'described-by-ids',
22
- };
23
-
24
8
  describe('ListItem.Prompt', () => {
25
- it('renders the children content', () => {
9
+ it('renders children content', () => {
26
10
  render(
27
- <ListItemContext.Provider value={mockContext}>
28
- <Prompt sentiment={Sentiment.POSITIVE}>This is a child prompt</Prompt>
29
- </ListItemContext.Provider>,
11
+ <ListItem
12
+ title="Test Title"
13
+ prompt={<ListItem.Prompt>This is a prompt message</ListItem.Prompt>}
14
+ />,
30
15
  );
31
- expect(screen.getByText('This is a child prompt')).toBeInTheDocument();
16
+
17
+ expect(screen.getByText('This is a prompt message')).toBeInTheDocument();
32
18
  });
33
19
 
34
20
  describe('render icon', () => {
@@ -39,9 +25,10 @@ describe('ListItem.Prompt', () => {
39
25
  [Sentiment.WARNING, 'alert-icon'],
40
26
  ] as [ListItemPromptProps['sentiment'], string][])('renders %s icon', (sentiment, iconId) => {
41
27
  render(
42
- <ListItemContext.Provider value={mockContext}>
43
- <Prompt sentiment={sentiment}>Neutral prompt</Prompt>
44
- </ListItemContext.Provider>,
28
+ <ListItem
29
+ title="Test Title"
30
+ prompt={<ListItem.Prompt sentiment={sentiment}>Message</ListItem.Prompt>}
31
+ />,
45
32
  );
46
33
  expect(screen.getByTestId(iconId)).toBeInTheDocument();
47
34
  });
@@ -1,5 +1,5 @@
1
1
  import { Meta, StoryObj } from '@storybook/react-webpack5';
2
- import { fn } from 'storybook/test';
2
+ import { action } from 'storybook/actions';
3
3
  import { lorem10, lorem5 } from '../../test-utils';
4
4
  import Link from '../../link';
5
5
  import { Sentiment as Sentiments } from '../../common';
@@ -147,8 +147,10 @@ export const Interactivity: Story = {
147
147
  prompt={
148
148
  <Prompt sentiment={args.sentiment}>
149
149
  {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
150
- This prompt includes an <Link onClick={fn()}>inline button</Link> than can e.g. trigger
151
- a modal.
150
+ This prompt includes an <Link onClick={action('inline button')}>
151
+ inline button
152
+ </Link>{' '}
153
+ than can e.g. trigger a modal.
152
154
  </Prompt>
153
155
  }
154
156
  />
@@ -0,0 +1,66 @@
1
+ import { render, screen } from '../../test-utils';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { ListItem, type Props as 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,47 @@
1
+ import { render, screen } from '../../test-utils';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { ListItem, type Props as 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
+ });
@@ -2,7 +2,8 @@ import { Meta, StoryObj } from '@storybook/react-webpack5';
2
2
  import { Bank, FastFlag, MultiCurrency, Receipt, Savings } from '@transferwise/icons';
3
3
  import Link from '../../link';
4
4
  import { ListItem, Props as ItemProps } from '../ListItem';
5
- import { lorem5 } from '../../test-utils';
5
+ import { lorem10, lorem5 } from '../../test-utils';
6
+ import { SB_LIST_ITEM_CONTROLS as CONTROLS, SB_LIST_ITEM_MEDIA as MEDIA } from './subcomponents';
6
7
 
7
8
  const withSizedContainer = (width: number) => (Story: any) => (
8
9
  <ol
@@ -167,3 +168,22 @@ export const Over400: Story = {
167
168
  render: () => <>{variants}</>,
168
169
  decorators: [withSizedContainer(400)],
169
170
  };
171
+
172
+ export const GapsBetweenItems: Story = {
173
+ render: () => {
174
+ const props = {
175
+ title: lorem5,
176
+ subtitle: lorem10,
177
+ media: MEDIA.image,
178
+ control: CONTROLS.switch,
179
+ };
180
+ return (
181
+ <ol className="list-unstyled">
182
+ <ListItem {...props} />
183
+ <ListItem {...props} spotlight="active" />
184
+ <ListItem {...props} spotlight="inactive" />
185
+ <ListItem {...props} />
186
+ </ol>
187
+ );
188
+ },
189
+ };
@@ -143,7 +143,7 @@ const generateVariantsForControl = (controlType: ControlType): Story => {
143
143
  return storyConfig(
144
144
  {
145
145
  render: () => (
146
- <ol>
146
+ <ol className="list-unstyled">
147
147
  {variants.map((variant, variantIndex) => (
148
148
  <ListItem
149
149
  key={`${controlType}-${variantIndex}-${Math.random()}`}
package/src/main.css CHANGED
@@ -3007,6 +3007,11 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
3007
3007
  padding: var(--size-12);
3008
3008
  container-type: inline-size;
3009
3009
  }
3010
+ .wds-list-item + .wds-list-item-spotlight,
3011
+ .wds-list-item-spotlight + .wds-list-item {
3012
+ margin-top: 12px;
3013
+ margin-top: var(--size-12);
3014
+ }
3010
3015
  .wds-list-item:focus-within {
3011
3016
  z-index: 1;
3012
3017
  }
@@ -1,77 +0,0 @@
1
- declare module '@storybook/react' {
2
- interface Parameters {
3
- /** Prefer using `storyConfig` for configuring variants */
4
- variants?: (
5
- | 'default'
6
- | 'light'
7
- | 'dark'
8
- | 'bright-green'
9
- | 'forest-green'
10
- | 'rtl'
11
- | (string & Record<string, unknown>)
12
- )[];
13
- /** Used by Chromatic */
14
- chromatic?: ChromaticParameters;
15
- viewport?: {
16
- viewports?: Record<string, unknown>;
17
- defaultViewport?: string;
18
- };
19
- }
20
- }
21
-
22
- /**
23
- * Derived from Chromatic's config type definitions
24
- *
25
- * @see https://github.com/chromaui/chromatic-cli/blob/main/storybook-addon.d.ts
26
- */
27
- export interface ChromaticParameters {
28
- /**
29
- * Prefer using `storyConfig` for configuring viewports if possible.
30
- *
31
- * To set a viewport, specify one or more screen widths to the
32
- * `chromatic.viewports` parameter. When left unspecified, Chromatic
33
- * uses a default value of `[1200]`. Use `[414]` for 'Large mobile'
34
- * viewport width.
35
- *
36
- * @default [1200]
37
- */
38
- viewports?: [414] | [414, 1200];
39
-
40
- /**
41
- * You can omit stories entirely from Chromatic testing using the disable story parameter.
42
- */
43
- disable?: boolean;
44
-
45
- /**
46
- * Chromatic will pause CSS animations and reset them to their beginning state.
47
- *
48
- * Some animations are used to "animate in" visible elements. To specify that Chromatic should pause the
49
- * animation at the end, use the `pauseAnimationAtEnd` story parameter.
50
- */
51
- pauseAnimationAtEnd?: boolean;
52
-
53
- /**
54
- * Use story-level delay to ensure a minimum amount of time (in milliseconds) has passed before Chromatic takes a
55
- * screenshot.
56
- */
57
- delay?: number;
58
-
59
- /**
60
- * The diffThreshold parameter allows you to fine tune the threshold for visual change between snapshots before
61
- * they’re flagged by Chromatic. Sometimes you need assurance to the sub-pixel and other times you want to skip
62
- * visual noise generated by non-deterministic rendering such as anti-aliasing.
63
- *
64
- * 0 is the most accurate. 1 is the least accurate.
65
- *
66
- * @default 0.063
67
- */
68
- diffThreshold?: number;
69
-
70
- /**
71
- * Modes for Chromatic testing.
72
- *
73
- * Specify the mode in which Chromatic should run tests. For example, 'light' for light mode,
74
- * 'dark' for dark mode, or `rtl` for right to left content.
75
- */
76
- modes?: object;
77
- }