@transferwise/components 46.104.0 → 46.105.1

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 (85) hide show
  1. package/build/header/Header.js +60 -43
  2. package/build/header/Header.js.map +1 -1
  3. package/build/header/Header.mjs +57 -43
  4. package/build/header/Header.mjs.map +1 -1
  5. package/build/i18n/cs.json +2 -0
  6. package/build/i18n/cs.json.js +2 -0
  7. package/build/i18n/cs.json.js.map +1 -1
  8. package/build/i18n/cs.json.mjs +2 -0
  9. package/build/i18n/cs.json.mjs.map +1 -1
  10. package/build/i18n/es.json +2 -0
  11. package/build/i18n/es.json.js +2 -0
  12. package/build/i18n/es.json.js.map +1 -1
  13. package/build/i18n/es.json.mjs +2 -0
  14. package/build/i18n/es.json.mjs.map +1 -1
  15. package/build/i18n/th.json +2 -0
  16. package/build/i18n/th.json.js +2 -0
  17. package/build/i18n/th.json.js.map +1 -1
  18. package/build/i18n/th.json.mjs +2 -0
  19. package/build/i18n/th.json.mjs.map +1 -1
  20. package/build/index.js +1 -1
  21. package/build/index.mjs +1 -1
  22. package/build/inputs/SelectInput.js +1 -1
  23. package/build/inputs/SelectInput.js.map +1 -1
  24. package/build/inputs/SelectInput.mjs +1 -1
  25. package/build/listItem/ListItem.js +4 -2
  26. package/build/listItem/ListItem.js.map +1 -1
  27. package/build/listItem/ListItem.mjs +4 -2
  28. package/build/listItem/ListItem.mjs.map +1 -1
  29. package/build/main.css +24 -22
  30. package/build/styles/header/Header.css +21 -14
  31. package/build/styles/listItem/ListItem.css +3 -8
  32. package/build/styles/listItem/ListItem.grid.css +0 -8
  33. package/build/styles/main.css +24 -22
  34. package/build/title/Title.js +10 -4
  35. package/build/title/Title.js.map +1 -1
  36. package/build/title/Title.mjs +6 -4
  37. package/build/title/Title.mjs.map +1 -1
  38. package/build/types/header/Header.d.ts +27 -11
  39. package/build/types/header/Header.d.ts.map +1 -1
  40. package/build/types/header/index.d.ts +1 -0
  41. package/build/types/header/index.d.ts.map +1 -1
  42. package/build/types/index.d.ts +1 -0
  43. package/build/types/index.d.ts.map +1 -1
  44. package/build/types/listItem/ListItem.d.ts.map +1 -1
  45. package/build/types/listItem/_stories/subcomponents.d.ts +1 -1
  46. package/build/types/listItem/_stories/subcomponents.d.ts.map +1 -1
  47. package/build/types/title/Title.d.ts +4 -5
  48. package/build/types/title/Title.d.ts.map +1 -1
  49. package/package.json +1 -1
  50. package/src/actionButton/ActionButton.story.tsx +1 -1
  51. package/src/avatar/Avatar.story.tsx +1 -1
  52. package/src/avatarWrapper/AvatarWrapper.story.tsx +1 -1
  53. package/src/badge/Badge.story.tsx +1 -1
  54. package/src/button/Button.spec.tsx +25 -1
  55. package/src/button/Button.story.tsx +1 -1
  56. package/src/button/LegacyButton.story.tsx +1 -1
  57. package/src/header/Header.accessibility.docs.mdx +85 -0
  58. package/src/header/Header.css +21 -14
  59. package/src/header/Header.less +17 -10
  60. package/src/header/Header.spec.tsx +68 -50
  61. package/src/header/Header.story.tsx +190 -36
  62. package/src/header/Header.tsx +96 -65
  63. package/src/header/index.ts +1 -0
  64. package/src/i18n/cs.json +2 -0
  65. package/src/i18n/es.json +2 -0
  66. package/src/i18n/th.json +2 -0
  67. package/src/iconButton/iconButton.spec.tsx +31 -0
  68. package/src/index.ts +1 -0
  69. package/src/listItem/Button/ListItemButton.spec.tsx +23 -1
  70. package/src/listItem/IconButton/ListItemIconButton.spec.tsx +14 -2
  71. package/src/listItem/ListItem.css +3 -8
  72. package/src/listItem/ListItem.grid.css +0 -8
  73. package/src/listItem/ListItem.grid.less +0 -8
  74. package/src/listItem/ListItem.less +4 -0
  75. package/src/listItem/ListItem.tsx +4 -2
  76. package/src/listItem/Navigation/ListItemNavigation.spec.tsx +8 -0
  77. package/src/listItem/Navigation/ListItemNavigation.story.tsx +4 -2
  78. package/src/listItem/_stories/ListItem.layout.test.story.tsx +20 -0
  79. package/src/listItem/_stories/ListItem.story.tsx +1 -1
  80. package/src/listItem/_stories/ListItem.variants.test.story.tsx +3 -0
  81. package/src/listItem/_stories/subcomponents.tsx +2 -0
  82. package/src/main.css +24 -22
  83. package/src/primitives/PrimitiveAnchor/test/PrimitiveAnchor.spec.tsx +15 -4
  84. package/src/select/Select.story.tsx +1 -1
  85. package/src/title/Title.tsx +25 -12
@@ -1,102 +1,133 @@
1
1
  import { clsx } from 'clsx';
2
2
 
3
- import { ActionButtonProps } from '../actionButton/ActionButton';
4
- import Button from '../button';
5
3
  import { AriaLabelProperty, CommonProps, Heading, LinkProps, Typography } from '../common';
4
+ import Button from '../button';
6
5
  import Link from '../link';
7
6
  import Title from '../title';
8
- import { HTMLAttributes } from 'react';
7
+ import React, { useEffect, useRef, FunctionComponent } from 'react';
9
8
 
10
9
  type ActionProps = AriaLabelProperty & {
11
10
  text: string;
11
+ onClick?: (event: React.MouseEvent) => void;
12
12
  };
13
13
 
14
- type ButtonActionProps = ActionProps & ActionButtonProps;
15
-
14
+ type ButtonActionProps = ActionProps;
16
15
  type LinkActionProps = ActionProps & LinkProps;
17
16
 
18
- export type HeaderProps = CommonProps & {
17
+ export interface HeaderProps extends CommonProps {
19
18
  /**
20
- * When the `href` property is provided to the `action`, we will render a `Link` instead of a `ActionButton`.
19
+ * Optional prop to define the action for the header. If the `href` property
20
+ * is provided, a `Link` will be rendered instead of a `Button`.
21
21
  */
22
22
  action?: ButtonActionProps | LinkActionProps;
23
+
23
24
  /**
24
- * Override the heading element rendered for the title, useful to specify the semantics of your header.
25
+ * Option prop to specify DOM render element of the title
26
+ *
27
+ * - When `as="legend"`, the `Header` will render as a `<legend>` element.
28
+ * **Note:** `<legend>` elements must be the first child of a `<fieldset>` to comply with HTML semantics.
29
+ * If this condition is not met, a warning will be logged to the console.
25
30
  *
26
- * @default "h5"
31
+ * - Other valid values include standard heading tags (`h1` to `h6`) or `header`.
27
32
  */
28
33
  as?: Heading | 'legend' | 'header';
34
+
35
+ /** Required prop to set the title of the Header. */
29
36
  title: string;
30
- } & Pick<HTMLAttributes<HTMLDivElement>, 'role' | 'id'>;
31
37
 
32
- const HeaderAction = ({ action }: { action: ButtonActionProps | LinkActionProps }) => {
33
- const props = {
34
- 'aria-label': action['aria-label'],
35
- };
38
+ /** Optional prop to specify the level of the Header */
39
+ level?: 'section' | 'group';
36
40
 
37
- if ('href' in action) {
38
- return (
39
- <Link href={action.href} target={action.target} onClick={action.onClick} {...props}>
40
- {action.text}
41
- </Link>
42
- );
43
- }
41
+ className?: string;
42
+ testId?: string;
43
+ }
44
44
 
45
+ /**
46
+ * Renders a header action which can be either a button or a link.
47
+ *
48
+ * @param {Object} props - The properties object.
49
+ * @param {ButtonActionProps | LinkActionProps} props.action - The action object which can be either a button or a link.
50
+ * @returns {JSX.Element} The rendered header action component.
51
+ */
52
+ const HeaderAction = React.forwardRef<
53
+ HTMLButtonElement | HTMLAnchorElement,
54
+ { action: ButtonActionProps | LinkActionProps }
55
+ >(({ action }, ref) => {
45
56
  return (
46
- <Button
47
- className="np-header__button"
48
- priority="tertiary"
49
- size="sm"
57
+ <Link
58
+ className="np-header__action-button"
59
+ aria-label={action['aria-label']}
60
+ href={'href' in action ? action.href : undefined}
61
+ target={'target' in action ? action.target : undefined}
50
62
  onClick={action.onClick}
51
- {...props}
52
63
  >
53
64
  {action.text}
54
- </Button>
65
+ </Link>
55
66
  );
56
- };
67
+ });
68
+
69
+ HeaderAction.displayName = 'HeaderAction';
57
70
 
58
71
  /**
72
+ * @param {ButtonActionProps | LinkActionProps} [action] - Optional prop to specify the action button or link.
73
+ * @param {Heading | 'legend'} [as='h5'] - Optional prop to override the heading element rendered for the title.
74
+ * @param {string} title - Required prop to set the title of the section header.
75
+ * @param {'group' | 'section'} [level='group'] - Optional prop to specify the level of the section header.
76
+ * @param {string} [className]
77
+ * @param {string} [testId]
59
78
  *
60
- * Neptune Web: https://transferwise.github.io/neptune-web/components/content/Header
61
- *
79
+ * @see {@link Header } for further information.
80
+ * @see {@link https://storybook.wise.design/?path=/docs/typography-header--docs|Storybook Wise Design}
62
81
  */
63
- export const Header = ({
64
- id,
65
- action,
66
- as = 'h5',
67
- title,
68
- className,
69
- role = undefined,
70
- }: HeaderProps) => {
71
- if (!action) {
72
- return (
73
- <Title
74
- as={as}
75
- id={id}
76
- role={role}
77
- type={Typography.TITLE_GROUP}
78
- className={clsx('np-header', 'np-header__title', className)}
79
- >
80
- {title}
81
- </Title>
82
- );
83
- }
82
+ const Header: FunctionComponent<HeaderProps> = React.forwardRef(
83
+ (
84
+ { as = 'h5', action, className, testId, title, level = 'group', ...props },
85
+ ref: React.Ref<HTMLDivElement | HTMLHeadingElement | HTMLLegendElement>,
86
+ ) => {
87
+ const internalRef = useRef<HTMLLegendElement>(null);
88
+ const levelTypography =
89
+ level === 'group' ? Typography.TITLE_GROUP : Typography.TITLE_SUBSECTION;
90
+ const isLegendOrNoAction = !action || as === 'legend';
91
+ const headerClasses = clsx('np-header', className, {
92
+ 'np-header--group': level === 'group',
93
+ 'np-header__title': isLegendOrNoAction,
94
+ });
95
+
96
+ const commonProps = {
97
+ className: headerClasses,
98
+ 'data-testid': testId,
99
+ };
100
+
101
+ useEffect(() => {
102
+ if (as === 'legend' && internalRef.current) {
103
+ const { parentElement } = internalRef.current;
104
+ if (!parentElement || parentElement.tagName.toLowerCase() !== 'fieldset') {
105
+ console.warn(
106
+ 'Legends should be the first child in a fieldset, and this is not possible when including an action',
107
+ );
108
+ }
109
+ }
110
+ }, [as]);
84
111
 
85
- if (as === 'legend') {
86
- // eslint-disable-next-line no-console
87
- console.warn(
88
- 'Legends should be the first child in a fieldset, and this is not possible when including an action',
112
+ if (!action) {
113
+ return (
114
+ <Title ref={internalRef} as={as} type={levelTypography} {...commonProps} {...props}>
115
+ {title}
116
+ </Title>
117
+ );
118
+ }
119
+
120
+ return (
121
+ <div {...commonProps} {...props} ref={ref as React.Ref<HTMLDivElement>}>
122
+ <Title as={as} type={levelTypography} className="np-header__title">
123
+ {title}
124
+ </Title>
125
+ <HeaderAction action={action} />
126
+ </div>
89
127
  );
90
- }
128
+ },
129
+ );
91
130
 
92
- return (
93
- <div className={clsx('np-header', className)}>
94
- <Title as={as} type={Typography.TITLE_GROUP} id={id} className="np-header__title">
95
- {title}
96
- </Title>
97
- <HeaderAction action={action} />
98
- </div>
99
- );
100
- };
131
+ Header.displayName = 'Header';
101
132
 
102
133
  export default Header;
@@ -1 +1,2 @@
1
1
  export { default } from './Header';
2
+ export type { HeaderProps } from './Header';
package/src/i18n/cs.json CHANGED
@@ -45,6 +45,7 @@
45
45
  "neptune.Upload.csFailureText": "Nahrání se nezdařilo. Zkuste to prosím později",
46
46
  "neptune.Upload.csSuccessText": "Úspěšně nahráno!",
47
47
  "neptune.Upload.csTooLargeMessage": "Nahrajte soubor menší než {maxSize} MB",
48
+ "neptune.Upload.csTooLargeNoLimitMessage": "Nahrajte menší soubor",
48
49
  "neptune.Upload.csWrongTypeMessage": "Tento formát souboru není podporován. Zkuste to znovu s jiným souborem",
49
50
  "neptune.Upload.psButtonText": "Zrušit",
50
51
  "neptune.Upload.psProcessingText": "Načítání...",
@@ -52,6 +53,7 @@
52
53
  "neptune.Upload.usButtonText": "Nebo vyberte soubor",
53
54
  "neptune.Upload.usDropMessage": "Přetáhněte soubor a zahajte nahrávání",
54
55
  "neptune.Upload.usPlaceholder": "Přetáhněte soubor menší než {maxSize} MB",
56
+ "neptune.Upload.usPlaceholderNoLimit": "Přetáhněte soubor",
55
57
  "neptune.UploadButton.allFileTypes": "Všechny typy souborů",
56
58
  "neptune.UploadButton.dropFiles": "Přetáhněte soubor a zahajte nahrávání",
57
59
  "neptune.UploadButton.instructions": "{fileTypes}, menší než {size} MB",
package/src/i18n/es.json CHANGED
@@ -45,6 +45,7 @@
45
45
  "neptune.Upload.csFailureText": "La carga del archivo ha fallado. Por favor, inténtalo de nuevo",
46
46
  "neptune.Upload.csSuccessText": "¡Se ha subido el archivo!",
47
47
  "neptune.Upload.csTooLargeMessage": "Proporciona un archivo menor de {maxSize} MB",
48
+ "neptune.Upload.csTooLargeNoLimitMessage": "Proporciona un archivo más pequeño",
48
49
  "neptune.Upload.csWrongTypeMessage": "Tipo de archivo no aceptado. Por favor, inténtalo de nuevo con un archivo diferente",
49
50
  "neptune.Upload.psButtonText": "Cancela",
50
51
  "neptune.Upload.psProcessingText": "Subiendo...",
@@ -52,6 +53,7 @@
52
53
  "neptune.Upload.usButtonText": "O selecciona un archivo",
53
54
  "neptune.Upload.usDropMessage": "Arrastra un archivo para subirlo",
54
55
  "neptune.Upload.usPlaceholder": "Arrastra y suelta un archivo de menos de {maxSize} MB",
56
+ "neptune.Upload.usPlaceholderNoLimit": "Arrastra y suelta un archivo",
55
57
  "neptune.UploadButton.allFileTypes": "Todos los tipos de archivos",
56
58
  "neptune.UploadButton.dropFiles": "Arrastra un archivo para subirlo",
57
59
  "neptune.UploadButton.instructions": "{fileTypes}, menor que {size}MB",
package/src/i18n/th.json CHANGED
@@ -45,6 +45,7 @@
45
45
  "neptune.Upload.csFailureText": "การอัปโหลดล้มเหลว กรุณาลองอีกครั้ง",
46
46
  "neptune.Upload.csSuccessText": "อัปโหลดเรียบร้อย!",
47
47
  "neptune.Upload.csTooLargeMessage": "กรุณาใช้ไฟล์ที่มีขนาดเล็กกว่า {maxSize} MB",
48
+ "neptune.Upload.csTooLargeNoLimitMessage": "โปรดอัปโหลดไฟล์ที่เล็กลง",
48
49
  "neptune.Upload.csWrongTypeMessage": "ไม่รองรับประเภทไฟล์ โปรดลองอีกครั้งโดยใช้ไฟล์อื่น",
49
50
  "neptune.Upload.psButtonText": "ยกเลิก",
50
51
  "neptune.Upload.psProcessingText": "กำลังอัปโหลด...",
@@ -52,6 +53,7 @@
52
53
  "neptune.Upload.usButtonText": "หรือเลือกไฟล์",
53
54
  "neptune.Upload.usDropMessage": "วางไฟล์เพื่อเริ่มการอัปโหลด",
54
55
  "neptune.Upload.usPlaceholder": "ลากและวางไฟล์ที่น้อยกว่า {maxSize} MB",
56
+ "neptune.Upload.usPlaceholderNoLimit": "ลากและวางไฟล์",
55
57
  "neptune.UploadButton.allFileTypes": "ไฟล์ทุกประเภท",
56
58
  "neptune.UploadButton.dropFiles": "วางไฟล์เพื่อเริ่มการอัปโหลด",
57
59
  "neptune.UploadButton.instructions": "{fileTypes} น้อยกว่า {size} MB",
@@ -0,0 +1,31 @@
1
+ import IconButton from './IconButton';
2
+ import { mockMatchMedia, render, screen, userEvent } from '../test-utils';
3
+ import { Edit } from '@transferwise/icons';
4
+
5
+ mockMatchMedia();
6
+
7
+ describe('IconButton', () => {
8
+ describe('onClick', () => {
9
+ it('should respect onClick if rendered as HTML button', async () => {
10
+ const handleClick = jest.fn();
11
+ render(
12
+ <IconButton onClick={handleClick}>
13
+ <Edit />
14
+ </IconButton>,
15
+ );
16
+ await userEvent.click(screen.getByRole('button'));
17
+ expect(handleClick).toHaveBeenCalledTimes(1);
18
+ });
19
+
20
+ it('should respect onClick if rendered as HTML anchor', async () => {
21
+ const handleClick = jest.fn();
22
+ render(
23
+ <IconButton href="#target" onClick={handleClick}>
24
+ <Edit />
25
+ </IconButton>,
26
+ );
27
+ await userEvent.click(screen.getByRole('link'));
28
+ expect(handleClick).toHaveBeenCalledTimes(1);
29
+ });
30
+ });
31
+ });
package/src/index.ts CHANGED
@@ -31,6 +31,7 @@ export type { DefinitionListProps, DefinitionListDefinition } from './definition
31
31
  export type { DimmerProps } from './dimmer';
32
32
  export type { DrawerProps } from './drawer';
33
33
  export type { DividerProps } from './divider';
34
+ export type { HeaderProps } from './header';
34
35
  export type { EmphasisProps } from './emphasis';
35
36
  export type { FieldProps } from './field/Field';
36
37
  export type { InfoProps } from './info';
@@ -1,4 +1,4 @@
1
- import { screen, mockMatchMedia } from '../../test-utils';
1
+ import { screen, mockMatchMedia, userEvent } from '../../test-utils';
2
2
  import { Button as ItemButton } from './ListItemButton';
3
3
  import { ButtonPriority } from '../../button/Button.types';
4
4
  import { renderWithListItemContext, clearListItemMocks, mockSetControlType } from '../test-utils';
@@ -65,4 +65,26 @@ describe('ItemButton', () => {
65
65
  expect(link).toHaveAttribute('href', 'https://example.com');
66
66
  expect(link).toHaveAttribute('target', '_blank');
67
67
  });
68
+
69
+ describe('onClick', () => {
70
+ it('handles onClick events when rendered as HTML button', async () => {
71
+ const handleClick = jest.fn();
72
+ renderWithListItemContext(<ItemButton onClick={handleClick}>Go to Example</ItemButton>);
73
+
74
+ await userEvent.click(screen.getByRole('button'));
75
+ expect(handleClick).toHaveBeenCalledTimes(1);
76
+ });
77
+
78
+ it('handles onClick events when rendered as HTML anchor', async () => {
79
+ const handleClick = jest.fn();
80
+ renderWithListItemContext(
81
+ <ItemButton href="#target" onClick={handleClick}>
82
+ Go to Example
83
+ </ItemButton>,
84
+ );
85
+
86
+ await userEvent.click(screen.getByRole('link'));
87
+ expect(handleClick).toHaveBeenCalledTimes(1);
88
+ });
89
+ });
68
90
  });
@@ -1,6 +1,5 @@
1
1
  import { Edit } from '@transferwise/icons';
2
- import userEvent from '@testing-library/user-event';
3
- import { mockMatchMedia, render, screen } from '../../test-utils';
2
+ import { mockMatchMedia, render, screen, userEvent } from '../../test-utils';
4
3
  import { ListItem, type ListItemProps } from '../ListItem';
5
4
 
6
5
  mockMatchMedia();
@@ -92,6 +91,19 @@ describe('ListItem.IconButton', () => {
92
91
  expect(link).toHaveAttribute('rel', 'noopener noreferrer');
93
92
  });
94
93
 
94
+ it('handles onClick events', async () => {
95
+ const handleClick = jest.fn();
96
+ renderWith({
97
+ control: (
98
+ <ListItem.IconButton href="#test" onClick={handleClick}>
99
+ <Edit />
100
+ </ListItem.IconButton>
101
+ ),
102
+ });
103
+ await userEvent.click(screen.getByRole('link'));
104
+ expect(handleClick).toHaveBeenCalledTimes(1);
105
+ });
106
+
95
107
  it('is disabled when ListItem is disabled', async () => {
96
108
  renderWith({
97
109
  disabled: true,
@@ -102,10 +102,6 @@
102
102
  -o-object-position: bottom left;
103
103
  object-position: bottom left;
104
104
  }
105
- .wds-list-item-gridWrapper.wds-list-item-hasMedia-hasControl .wds-list-item-media {
106
- margin-bottom: 12px;
107
- margin-bottom: var(--size-12);
108
- }
109
105
  .wds-list-item-gridWrapper.wds-list-item-hasMedia-hasControl.wds-list-item-hasInfo-hasPrompt {
110
106
  grid-template-columns: 1fr auto;
111
107
  grid-template-rows: auto auto auto auto;
@@ -142,10 +138,6 @@
142
138
  grid-template-columns: 1fr;
143
139
  grid-template-areas: "media" "body" "control";
144
140
  }
145
- .wds-list-item-gridWrapper.wds-list-item-hasMedia-noControl .wds-list-item-media {
146
- margin-bottom: 12px;
147
- margin-bottom: var(--size-12);
148
- }
149
141
  .wds-list-item-gridWrapper.wds-list-item-hasMedia-noControl.wds-list-item-hasInfo-hasPrompt {
150
142
  grid-template-columns: auto auto;
151
143
  grid-template-rows: auto auto auto auto;
@@ -687,6 +679,9 @@
687
679
  margin-top: calc(4px * -1);
688
680
  margin-top: calc(var(--size-4) * -1);
689
681
  }
682
+ .wds-list-item-additional-info button.np-link {
683
+ text-align: start;
684
+ }
690
685
  .wds-list-item-control-wrapper {
691
686
  grid-area: control;
692
687
  align-content: center;
@@ -102,10 +102,6 @@
102
102
  -o-object-position: bottom left;
103
103
  object-position: bottom left;
104
104
  }
105
- .wds-list-item-gridWrapper.wds-list-item-hasMedia-hasControl .wds-list-item-media {
106
- margin-bottom: 12px;
107
- margin-bottom: var(--size-12);
108
- }
109
105
  .wds-list-item-gridWrapper.wds-list-item-hasMedia-hasControl.wds-list-item-hasInfo-hasPrompt {
110
106
  grid-template-columns: 1fr auto;
111
107
  grid-template-rows: auto auto auto auto;
@@ -142,10 +138,6 @@
142
138
  grid-template-columns: 1fr;
143
139
  grid-template-areas: "media" "body" "control";
144
140
  }
145
- .wds-list-item-gridWrapper.wds-list-item-hasMedia-noControl .wds-list-item-media {
146
- margin-bottom: 12px;
147
- margin-bottom: var(--size-12);
148
- }
149
141
  .wds-list-item-gridWrapper.wds-list-item-hasMedia-noControl.wds-list-item-hasInfo-hasPrompt {
150
142
  grid-template-columns: auto auto;
151
143
  grid-template-rows: auto auto auto auto;
@@ -159,10 +159,6 @@
159
159
  }
160
160
 
161
161
  &.wds-list-item-hasMedia-hasControl {
162
- .wds-list-item-media {
163
- margin-bottom: var(--size-12);
164
- }
165
-
166
162
  &.wds-list-item-hasInfo-hasPrompt {
167
163
  grid-template-columns: 1fr auto;
168
164
  grid-template-rows: auto auto auto auto;
@@ -237,10 +233,6 @@
237
233
  }
238
234
 
239
235
  &.wds-list-item-hasMedia-noControl {
240
- .wds-list-item-media {
241
- margin-bottom: var(--size-12);
242
- }
243
-
244
236
  &.wds-list-item-hasInfo-hasPrompt {
245
237
  grid-template-columns: auto auto;
246
238
  grid-template-rows: auto auto auto auto;
@@ -201,6 +201,10 @@
201
201
  grid-area: info;
202
202
  color: var(--color-content-tertiary);
203
203
  margin-top: calc(var(--size-4) * -1);
204
+
205
+ button.np-link {
206
+ text-align: start;
207
+ }
204
208
  }
205
209
 
206
210
  &-control-wrapper {
@@ -20,7 +20,7 @@ import { AvatarLayout } from './AvatarLayout';
20
20
  import { AvatarView } from './AvatarView';
21
21
  import { Image } from './Image';
22
22
  import { Prompt } from './Prompt';
23
- import { PrimitiveAnchor } from '../primitives';
23
+ import { PrimitiveAnchor, type PrimitiveAnchorProps } from '../primitives';
24
24
  import {
25
25
  ListItemContext,
26
26
  type ListItemContextData,
@@ -367,7 +367,7 @@ function View({
367
367
 
368
368
  const renderExtras = () => (
369
369
  <>
370
- {Boolean(subtitle) && additionalInfo}
370
+ {additionalInfo}
371
371
  {prompt}
372
372
  </>
373
373
  );
@@ -382,8 +382,10 @@ function View({
382
382
  target={(controlProps as ListItemNavigationProps)?.target}
383
383
  className={clsx('wds-list-item-view d-flex flex-row', {
384
384
  'wds-list-item-control': controlType === 'navigation',
385
+ fullyInteractive: !isPartiallyInteractive,
385
386
  })}
386
387
  disabled={disabled}
388
+ onClick={(controlProps as PrimitiveAnchorProps | undefined)?.onClick}
387
389
  >
388
390
  {children}
389
391
  </PrimitiveAnchor>
@@ -55,5 +55,13 @@ describe('ListItem.Navigation', () => {
55
55
  });
56
56
  expect(screen.getByTestId('backslash-circle-icon')).toBeInTheDocument();
57
57
  });
58
+
59
+ it('handles onClick events', async () => {
60
+ const handleClick = jest.fn();
61
+ renderWith({ control: <ListItem.Navigation href="#target" onClick={handleClick} /> });
62
+
63
+ await userEvent.click(screen.getByRole('link'));
64
+ expect(handleClick).toHaveBeenCalledTimes(1);
65
+ });
58
66
  });
59
67
  });
@@ -20,7 +20,7 @@ const meta: Meta<ListItemNavigationProps> = {
20
20
  },
21
21
  args: {
22
22
  href: 'https://wise.com',
23
- onClick: undefined,
23
+ onClick: fn(),
24
24
  target: undefined,
25
25
  },
26
26
  argTypes: {
@@ -72,7 +72,9 @@ export const AsButton: Story = {
72
72
  return (
73
73
  <List>
74
74
  <ListItem
75
- control={<ListItem.Navigation href="https://wise.com" target="_blank" />}
75
+ control={
76
+ <ListItem.Navigation href="https://wise.com" target="_blank" onClick={args.onClick} />
77
+ }
76
78
  title="Navigation as link"
77
79
  subtitle="This will navigate to an external URL"
78
80
  media={MEDIA.avatarSingle}
@@ -173,6 +173,26 @@ export const Over400: Story = {
173
173
  decorators: [withSizedContainer(400)],
174
174
  };
175
175
 
176
+ export const LongButton: Story = {
177
+ render: () => (
178
+ <ListItem
179
+ title="Additional info button better align left"
180
+ additionalInfo={
181
+ <ListItem.AdditionalInfo
182
+ action={{
183
+ label: 'Additional info Additional info Additional info (as button)',
184
+ onClick: () => {},
185
+ }}
186
+ />
187
+ }
188
+ control={
189
+ <ListItem.Button onClick={() => {}}>Click me Click me Click me Click me</ListItem.Button>
190
+ }
191
+ />
192
+ ),
193
+ decorators: [withSizedContainer(400)],
194
+ };
195
+
176
196
  export const GapsBetweenItems: Story = {
177
197
  render: () => {
178
198
  const props = {
@@ -46,7 +46,7 @@ export default {
46
46
  'ListItem.Image': ListItem.Image,
47
47
  },
48
48
  title: 'Content/ListItem',
49
- tags: ['wds:early-access'],
49
+ tags: ['early-access'],
50
50
  parameters: {
51
51
  docs: {
52
52
  toc: true,
@@ -73,6 +73,7 @@ const generateVariantsForControl = (controlType: ControlType): Story => {
73
73
  { title },
74
74
  { title, valueTitle },
75
75
  { title, subtitle },
76
+ { title, additionalInfo },
76
77
  { title, valueTitle, valueSubtitle },
77
78
  { title, subtitle, inverted: true },
78
79
  { title, subtitle, valueTitle },
@@ -86,6 +87,7 @@ const generateVariantsForControl = (controlType: ControlType): Story => {
86
87
  { media, title, valueTitle },
87
88
  { media, title, valueSubtitle },
88
89
  { media, title, subtitle },
90
+ { media, title, additionalInfo },
89
91
  { media, title, subtitle, valueTitle },
90
92
  { media, title, subtitle, valueTitle, valueSubtitle },
91
93
  { media, title, subtitle, additionalInfo: infoWithoutLink },
@@ -179,6 +181,7 @@ export const NavigationAsButton = generateVariantsForControl('navigationAsButton
179
181
  export const Checkbox = generateVariantsForControl('checkbox');
180
182
  export const Radio = generateVariantsForControl('radio');
181
183
  export const Switch = generateVariantsForControl('switch');
184
+ export const NonInteractive = generateVariantsForControl('non-interactive');
182
185
 
183
186
  export const ButtonControlLabel: Story = {
184
187
  render: () => (
@@ -6,6 +6,7 @@ import Link from '../../link';
6
6
  import { Flag } from '@wise/art';
7
7
 
8
8
  export type SB_ListItem_ControlType =
9
+ | 'non-interactive'
9
10
  | 'button'
10
11
  | 'buttonAsLink'
11
12
  | 'partialButton'
@@ -25,6 +26,7 @@ export type SB_ListItem_ControlType =
25
26
  | 'switch';
26
27
 
27
28
  export const SB_LIST_ITEM_CONTROLS: Record<SB_ListItem_ControlType, ReactNode> = {
29
+ 'non-interactive': null,
28
30
  button: <ListItem.Button onClick={() => {}}>Click me</ListItem.Button>,
29
31
  buttonAsLink: <ListItem.Button href="https://wise.com">Click me</ListItem.Button>,
30
32
  partialButton: (