@transferwise/components 0.0.0-experimental-c239b44 → 0.0.0-experimental-e0993c8

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 (64) hide show
  1. package/build/header/Header.js +79 -41
  2. package/build/header/Header.js.map +1 -1
  3. package/build/header/Header.mjs +76 -41
  4. package/build/header/Header.mjs.map +1 -1
  5. package/build/i18n/es.json +2 -0
  6. package/build/i18n/es.json.js +2 -0
  7. package/build/i18n/es.json.js.map +1 -1
  8. package/build/i18n/es.json.mjs +2 -0
  9. package/build/i18n/es.json.mjs.map +1 -1
  10. package/build/index.js +1 -1
  11. package/build/index.mjs +1 -1
  12. package/build/inputs/SelectInput.js +1 -1
  13. package/build/inputs/SelectInput.js.map +1 -1
  14. package/build/inputs/SelectInput.mjs +1 -1
  15. package/build/listItem/IconButton/ListItemIconButton.js +1 -1
  16. package/build/listItem/IconButton/ListItemIconButton.js.map +1 -1
  17. package/build/listItem/IconButton/ListItemIconButton.mjs +1 -1
  18. package/build/listItem/IconButton/ListItemIconButton.mjs.map +1 -1
  19. package/build/listItem/ListItem.js +18 -18
  20. package/build/listItem/ListItem.js.map +1 -1
  21. package/build/listItem/ListItem.mjs +18 -18
  22. package/build/listItem/ListItem.mjs.map +1 -1
  23. package/build/listItem/ListItemContext.js.map +1 -1
  24. package/build/listItem/ListItemContext.mjs.map +1 -1
  25. package/build/main.css +32 -14
  26. package/build/styles/header/Header.css +32 -14
  27. package/build/styles/main.css +32 -14
  28. package/build/title/Title.js +10 -4
  29. package/build/title/Title.js.map +1 -1
  30. package/build/title/Title.mjs +6 -4
  31. package/build/title/Title.mjs.map +1 -1
  32. package/build/types/header/Header.d.ts +28 -11
  33. package/build/types/header/Header.d.ts.map +1 -1
  34. package/build/types/header/index.d.ts +1 -0
  35. package/build/types/header/index.d.ts.map +1 -1
  36. package/build/types/index.d.ts +1 -0
  37. package/build/types/index.d.ts.map +1 -1
  38. package/build/types/listItem/ListItem.d.ts +3 -3
  39. package/build/types/listItem/ListItem.d.ts.map +1 -1
  40. package/build/types/listItem/ListItemContext.d.ts +2 -2
  41. package/build/types/listItem/ListItemContext.d.ts.map +1 -1
  42. package/build/types/title/Title.d.ts +4 -5
  43. package/build/types/title/Title.d.ts.map +1 -1
  44. package/package.json +4 -4
  45. package/src/header/Header.accessibility.docs.mdx +85 -0
  46. package/src/header/Header.css +32 -14
  47. package/src/header/Header.less +28 -9
  48. package/src/header/Header.spec.tsx +68 -50
  49. package/src/header/Header.story.tsx +190 -36
  50. package/src/header/Header.tsx +122 -70
  51. package/src/header/index.ts +1 -0
  52. package/src/i18n/es.json +2 -0
  53. package/src/index.ts +1 -0
  54. package/src/listItem/IconButton/ListItemIconButton.story.tsx +2 -2
  55. package/src/listItem/IconButton/ListItemIconButton.tsx +2 -2
  56. package/src/listItem/ListItem.spec.tsx +279 -221
  57. package/src/listItem/ListItem.tsx +20 -19
  58. package/src/listItem/ListItemContext.tsx +2 -2
  59. package/src/listItem/_stories/ListItem.layout.test.story.tsx +6 -6
  60. package/src/listItem/_stories/ListItem.story.tsx +19 -12
  61. package/src/listItem/_stories/ListItem.variants.test.story.tsx +43 -43
  62. package/src/listItem/test-utils.tsx +1 -1
  63. package/src/main.css +32 -14
  64. package/src/title/Title.tsx +25 -12
@@ -1,53 +1,207 @@
1
- import Header from './Header';
1
+ import { Meta, StoryObj } from '@storybook/react-webpack5';
2
+ import Header, { HeaderProps } from './Header';
3
+ import { storyConfig } from '../test-utils';
2
4
 
3
- export default {
4
- component: Header,
5
- title: 'Typography/Header',
6
- };
5
+ const withContainer = (Story: any) => (
6
+ <div style={{ display: 'flex', justifyContent: 'center' }}>
7
+ <Story />
8
+ </div>
9
+ );
10
+
11
+ /**
12
+ * Not all stories need access to all controls as it causes unnecessary UI noise.
13
+ */
14
+ const hideControls = (args: string[]) =>
15
+ Object.fromEntries(args.map((item) => [item, { table: { disable: true } }]));
7
16
 
8
- export const Basic = () => {
9
- return <Header title="Header title" />;
17
+ /**
18
+ * Reusable render logic for wrapping `Header` in a `<fieldset>` if `as` is `legend`.
19
+ */
20
+ const renderHeader = (args: HeaderProps) => {
21
+ if (args.as === 'legend') {
22
+ return (
23
+ <fieldset style={{ width: '100%' }}>
24
+ <Header {...args} />
25
+ </fieldset>
26
+ );
27
+ }
28
+ return <Header {...args} />;
10
29
  };
11
30
 
12
- export const WithAction = () => {
13
- return (
31
+ /**
32
+ * Helper to generate variants for `AllVariants` story.
33
+ */
34
+ const renderVariants = () => (
35
+ <div
36
+ className="header-variants"
37
+ style={{ display: 'flex', flexWrap: 'wrap', gap: '16px', maxWidth: '1200px' }}
38
+ >
39
+ {(['h1', 'h2', 'h3', 'legend'] as const).map((as) => (
40
+ <Header key={as} as={as} title={`Header as ${as}`} />
41
+ ))}
42
+ {(['section', 'group'] as const).map((level) => (
43
+ <Header key={level} level={level} title={`Header level ${level}`} />
44
+ ))}
14
45
  <Header
15
- title="Header title"
46
+ as="h2"
47
+ title="Header with Action"
16
48
  action={{
17
- 'aria-label': 'Magic',
18
- text: 'Click me!',
19
- onClick: () => alert('Action!'),
49
+ 'aria-label': 'Action',
50
+ text: 'Action',
51
+ onClick: () => alert('Action clicked!'),
20
52
  }}
21
53
  />
22
- );
23
- };
24
-
25
- export const WithActionAsLink = () => {
26
- return (
27
54
  <Header
28
- title="Header title"
55
+ as="h2"
56
+ title="Header with link"
29
57
  action={{
30
- text: 'This is a link',
58
+ 'aria-label': 'Action',
59
+ text: 'Action',
31
60
  href: 'https://wise.com',
32
61
  }}
33
62
  />
34
- );
63
+ </div>
64
+ );
65
+
66
+ /**
67
+ * The stories below document the `Header` component, which is used to structure content and convey hierarchy. <br />
68
+ * For more details, refer to the [design documentation](https://wise.design/components/section-header).
69
+ */
70
+ const meta: Meta<typeof Header> = {
71
+ component: Header,
72
+ title: 'Typography/Header',
73
+ argTypes: {
74
+ level: {
75
+ type: {
76
+ name: 'enum',
77
+ value: ['section', 'group'],
78
+ },
79
+ table: {
80
+ type: {
81
+ summary: 'HeaderLevel',
82
+ },
83
+ },
84
+ description: 'Defines the hierarchical level of the header.',
85
+ },
86
+ as: {
87
+ type: {
88
+ name: 'enum',
89
+ value: ['h1', 'h2', 'h3', 'legend'],
90
+ },
91
+ table: {
92
+ type: {
93
+ summary: 'HeaderAs',
94
+ },
95
+ },
96
+ description:
97
+ 'Defines which HTML element the Header will render as. Use `legend` only as the first child of a `<fieldset>` — otherwise a warning will appear. The `action` prop is not supported with `legend`. You can also use heading tags like `h1`, `h2`, `h3`, or `header`.',
98
+ },
99
+ action: {
100
+ table: {
101
+ type: {
102
+ summary: 'HeaderAction',
103
+ },
104
+ },
105
+ description: 'Defines an optional action (e.g., button or link) associated with the header.',
106
+ },
107
+ },
108
+ args: {
109
+ title: 'Default Header',
110
+ level: 'group',
111
+ as: 'h1',
112
+ action: undefined,
113
+ },
114
+ tags: ['autodocs'],
115
+ decorators: [withContainer],
116
+ parameters: {
117
+ docs: {
118
+ toc: true,
119
+ },
120
+ },
35
121
  };
36
122
 
37
- export const WithActionAsLinkPreventingNavigationWithTracking = () => {
38
- return (
39
- <Header
40
- title="Header title"
41
- action={{
42
- text: 'This is a link',
43
- href: 'https://wise.com',
44
- onClick: (event: React.MouseEvent<HTMLElement>) => {
45
- alert('Running onClick handler');
123
+ export default meta;
46
124
 
47
- // we can stop the navigation from happening as onClick will always run before href redirect
48
- event.preventDefault();
49
- },
50
- }}
51
- />
52
- );
125
+ type Story = StoryObj<typeof Header>;
126
+
127
+ export const Playground: Story = {
128
+ render: renderHeader,
129
+ args: {
130
+ title: 'Playground Header',
131
+ level: 'group',
132
+ as: 'h2',
133
+ action: {
134
+ 'aria-label': 'Action',
135
+ text: 'Click me',
136
+ onClick: () => alert('Action clicked!'),
137
+ },
138
+ },
139
+ };
140
+
141
+ /**
142
+ * Demonstrates a `Header` with an associated action.
143
+ */
144
+ export const Action: Story = {
145
+ args: {
146
+ title: 'Header with Action',
147
+ action: {
148
+ 'aria-label': 'Action',
149
+ text: 'Action',
150
+ onClick: () => alert('Action clicked!'),
151
+ },
152
+ },
153
+ argTypes: hideControls(['as', 'className', 'level', 'title', 'testId']),
53
154
  };
155
+
156
+ /**
157
+ * Demonstrates a `Header` with an associated action as a link.
158
+ */
159
+ export const ActionAsLink: Story = {
160
+ args: {
161
+ title: 'Header with Action as Link',
162
+ action: {
163
+ 'aria-label': 'Learn more about this section',
164
+ text: 'Learn more',
165
+ href: 'https://wise.com',
166
+ target: '_blank',
167
+ },
168
+ },
169
+ argTypes: hideControls(['as', 'className', 'level', 'title', 'testId']),
170
+ };
171
+
172
+ /**
173
+ * Demonstrates a `Header` rendered as a custom HTML element.
174
+ */
175
+ export const CustomElement: Story = {
176
+ render: renderHeader,
177
+ args: {
178
+ title: 'Legend Header',
179
+ as: 'legend',
180
+ },
181
+ argTypes: hideControls(['action', 'className', 'level', 'testId']),
182
+ };
183
+
184
+ /**
185
+ * Demonstrates a `Header` with a specific hierarchical level.
186
+ */
187
+ export const SectionLevel: Story = {
188
+ args: {
189
+ title: 'Section Header',
190
+ level: 'section',
191
+ },
192
+ argTypes: hideControls(['action', 'as', 'className', 'title', 'testId']),
193
+ };
194
+
195
+ /**
196
+ * Displays all variants of the `Header` component, including different levels, tags, and actions.
197
+ */
198
+ export const AllVariants = storyConfig(
199
+ {
200
+ tags: ['!autodocs'],
201
+ parameters: {
202
+ padding: '0',
203
+ },
204
+ render: renderVariants,
205
+ },
206
+ { variants: ['default', 'dark', 'bright-green', 'forest-green', 'rtl'] },
207
+ );
@@ -1,102 +1,154 @@
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';
6
4
  import Link from '../link';
5
+ import Button from '../button';
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?: () => 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.
30
+ * Additionally, the `action` prop is not supported when `as="legend"`.
25
31
  *
26
- * @default "h5"
32
+ * - Other valid values include standard heading tags (`h1` to `h6`) or `header`.
27
33
  */
28
34
  as?: Heading | 'legend' | 'header';
35
+
36
+ /** Required prop to set the title of the Header. */
29
37
  title: string;
30
- } & Pick<HTMLAttributes<HTMLDivElement>, 'role' | 'id'>;
31
38
 
32
- const HeaderAction = ({ action }: { action: ButtonActionProps | LinkActionProps }) => {
33
- const props = {
34
- 'aria-label': action['aria-label'],
35
- };
39
+ /** Optional prop to specify the level of the Header */
40
+ level?: 'section' | 'group';
36
41
 
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
- }
44
-
45
- return (
46
- <Button
47
- className="np-header__button"
48
- priority="tertiary"
49
- size="sm"
50
- onClick={action.onClick}
51
- {...props}
52
- >
53
- {action.text}
54
- </Button>
55
- );
56
- };
42
+ className?: string;
43
+ testId?: string;
44
+ }
57
45
 
58
46
  /**
47
+ * Renders a header action which can be either a button or a link.
59
48
  *
60
- * Neptune Web: https://transferwise.github.io/neptune-web/components/content/Header
49
+ * @param {Object} props - The properties object.
50
+ * @param {ButtonActionProps | LinkActionProps} props.action - The action object which can be either a button or a link.
51
+ * @returns {JSX.Element} The rendered header action component.
52
+ */
53
+ const HeaderAction = React.forwardRef(
54
+ (
55
+ { action }: { action: ButtonActionProps | LinkActionProps },
56
+ ref: React.Ref<HTMLButtonElement | HTMLAnchorElement>,
57
+ ) => {
58
+ const { 'aria-label': ariaLabel, text, onClick } = action;
59
+
60
+ const inner =
61
+ 'href' in action ? (
62
+ <Link
63
+ ref={ref as React.Ref<HTMLAnchorElement>}
64
+ className="np-header__action-link"
65
+ href={action.href}
66
+ target={action.target}
67
+ aria-label={action['aria-label']}
68
+ onClick={action.onClick}
69
+ >
70
+ {action.text}
71
+ </Link>
72
+ ) : (
73
+ <Button
74
+ ref={ref as React.Ref<HTMLButtonElement>}
75
+ className="np-header__action-button"
76
+ priority="tertiary"
77
+ size="sm"
78
+ aria-label={action['aria-label']}
79
+ onClick={action.onClick}
80
+ >
81
+ {action.text}
82
+ </Button>
83
+ );
84
+
85
+ return <div className="np-header__action">{inner}</div>;
86
+ },
87
+ );
88
+
89
+ HeaderAction.displayName = 'HeaderAction';
90
+
91
+ /**
92
+ * @param {ButtonActionProps | LinkActionProps} [action] - Optional prop to specify the action button or link.
93
+ * @param {Heading | 'legend'} [as='h5'] - Optional prop to override the heading element rendered for the title.
94
+ * @param {string} title - Required prop to set the title of the section header.
95
+ * @param {'group' | 'section'} [level='group'] - Optional prop to specify the level of the section header.
96
+ * @param {string} [className]
97
+ * @param {string} [testId]
61
98
  *
99
+ * @see {@link Header } for further information.
100
+ * @see {@link https://storybook.wise.design/?path=/docs/typography-header--docs|Storybook Wise Design}
62
101
  */
63
- export const Header = ({
64
- id,
65
- action,
66
- as = 'h5',
67
- title,
68
- className,
69
- role = undefined,
70
- }: HeaderProps) => {
71
- if (!action) {
102
+ const Header: FunctionComponent<HeaderProps> = React.forwardRef(
103
+ (
104
+ { as = 'h5', action, className, testId, title, level = 'group', ...props },
105
+ ref: React.Ref<HTMLDivElement | HTMLHeadingElement | HTMLLegendElement>,
106
+ ) => {
107
+ const internalRef = useRef<HTMLLegendElement>(null);
108
+ const actionRef = useRef<HTMLButtonElement | HTMLAnchorElement>(null);
109
+ const levelTypography =
110
+ level === 'group' ? Typography.TITLE_GROUP : Typography.TITLE_SUBSECTION;
111
+ const isLegendOrNoAction = !action || as === 'legend';
112
+ const headerClasses = clsx('np-header', className, {
113
+ 'np-header--group': level === 'group',
114
+ 'np-header__title': isLegendOrNoAction,
115
+ });
116
+
117
+ const commonProps = {
118
+ className: headerClasses,
119
+ 'data-testid': testId,
120
+ };
121
+
122
+ useEffect(() => {
123
+ if (as === 'legend' && internalRef.current) {
124
+ const { parentElement } = internalRef.current;
125
+ if (!parentElement || parentElement.tagName.toLowerCase() !== 'fieldset') {
126
+ console.warn(
127
+ 'Legends should be the first child in a fieldset, and this is not possible when including an action',
128
+ );
129
+ }
130
+ }
131
+ }, [as]);
132
+
133
+ if (!action) {
134
+ return (
135
+ <Title ref={internalRef} as={as} type={levelTypography} {...commonProps} {...props}>
136
+ {title}
137
+ </Title>
138
+ );
139
+ }
140
+
72
141
  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>
142
+ <div {...commonProps} {...props} ref={ref as React.Ref<HTMLDivElement>}>
143
+ <Title as={as} type={levelTypography} className="np-header__title">
144
+ {title}
145
+ </Title>
146
+ <HeaderAction ref={actionRef} action={action} />
147
+ </div>
82
148
  );
83
- }
149
+ },
150
+ );
84
151
 
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',
89
- );
90
- }
91
-
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
- };
152
+ Header.displayName = 'Header';
101
153
 
102
154
  export default Header;
@@ -1 +1,2 @@
1
1
  export { default } from './Header';
2
+ export type { HeaderProps } from './Header';
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/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';
@@ -108,8 +108,8 @@ export const Playground: Story = {
108
108
  /**
109
109
  * It's strongly encouraged to provide `aria-label` prop for the `IconButton` control to ensure it
110
110
  * follows accessibility best practices.
111
- * If not set, the component will provide accessible name via the `title`, `subtitle`, `value`
112
- * and `subvalue` props and accessible description via `additionalInfo` and `prompt`, which
111
+ * If not set, the component will provide accessible name via the `title`, `subtitle`, `valueTitle`
112
+ * and `valueSubtitle` props and accessible description via `additionalInfo` and `prompt`, which
113
113
  * may not be descriptive enough for the screen readers users.
114
114
  */
115
115
  export const AccessibleName: Story = {
@@ -36,8 +36,8 @@ export const IconButton = function ({
36
36
  const labelIds = [
37
37
  itemProps.inverted ? ids.subtitle : ids.title,
38
38
  itemProps.inverted ? ids.title : ids.subtitle,
39
- itemProps.inverted ? ids.subvalue : ids.value,
40
- itemProps.inverted ? ids.value : ids.subvalue,
39
+ itemProps.inverted ? ids.valueSubtitle : ids.valueTitle,
40
+ itemProps.inverted ? ids.valueTitle : ids.valueSubtitle,
41
41
  ].join(' ');
42
42
  const descriptorIds = [ids.additionalInfo, ids.prompt].join(' ');
43
43