@transferwise/components 46.104.0 → 46.105.0
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.
- package/build/header/Header.js +60 -43
- package/build/header/Header.js.map +1 -1
- package/build/header/Header.mjs +57 -43
- package/build/header/Header.mjs.map +1 -1
- package/build/i18n/cs.json +2 -0
- package/build/i18n/cs.json.js +2 -0
- package/build/i18n/cs.json.js.map +1 -1
- package/build/i18n/cs.json.mjs +2 -0
- package/build/i18n/cs.json.mjs.map +1 -1
- package/build/i18n/es.json +2 -0
- package/build/i18n/es.json.js +2 -0
- package/build/i18n/es.json.js.map +1 -1
- package/build/i18n/es.json.mjs +2 -0
- package/build/i18n/es.json.mjs.map +1 -1
- package/build/i18n/th.json +2 -0
- package/build/i18n/th.json.js +2 -0
- package/build/i18n/th.json.js.map +1 -1
- package/build/i18n/th.json.mjs +2 -0
- package/build/i18n/th.json.mjs.map +1 -1
- package/build/index.js +1 -1
- package/build/index.mjs +1 -1
- package/build/inputs/SelectInput.js +1 -1
- package/build/inputs/SelectInput.js.map +1 -1
- package/build/inputs/SelectInput.mjs +1 -1
- package/build/listItem/ListItem.js +4 -2
- package/build/listItem/ListItem.js.map +1 -1
- package/build/listItem/ListItem.mjs +4 -2
- package/build/listItem/ListItem.mjs.map +1 -1
- package/build/main.css +24 -14
- package/build/styles/header/Header.css +21 -14
- package/build/styles/listItem/ListItem.css +3 -0
- package/build/styles/main.css +24 -14
- package/build/title/Title.js +10 -4
- package/build/title/Title.js.map +1 -1
- package/build/title/Title.mjs +6 -4
- package/build/title/Title.mjs.map +1 -1
- package/build/types/header/Header.d.ts +27 -11
- package/build/types/header/Header.d.ts.map +1 -1
- package/build/types/header/index.d.ts +1 -0
- package/build/types/header/index.d.ts.map +1 -1
- package/build/types/index.d.ts +1 -0
- package/build/types/index.d.ts.map +1 -1
- package/build/types/listItem/ListItem.d.ts.map +1 -1
- package/build/types/listItem/_stories/subcomponents.d.ts +1 -1
- package/build/types/listItem/_stories/subcomponents.d.ts.map +1 -1
- package/build/types/title/Title.d.ts +4 -5
- package/build/types/title/Title.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/actionButton/ActionButton.story.tsx +1 -1
- package/src/avatar/Avatar.story.tsx +1 -1
- package/src/avatarWrapper/AvatarWrapper.story.tsx +1 -1
- package/src/badge/Badge.story.tsx +1 -1
- package/src/button/Button.spec.tsx +25 -1
- package/src/button/Button.story.tsx +1 -1
- package/src/button/LegacyButton.story.tsx +1 -1
- package/src/header/Header.accessibility.docs.mdx +85 -0
- package/src/header/Header.css +21 -14
- package/src/header/Header.less +17 -10
- package/src/header/Header.spec.tsx +68 -50
- package/src/header/Header.story.tsx +190 -36
- package/src/header/Header.tsx +96 -65
- package/src/header/index.ts +1 -0
- package/src/i18n/cs.json +2 -0
- package/src/i18n/es.json +2 -0
- package/src/i18n/th.json +2 -0
- package/src/iconButton/iconButton.spec.tsx +31 -0
- package/src/index.ts +1 -0
- package/src/listItem/Button/ListItemButton.spec.tsx +23 -1
- package/src/listItem/IconButton/ListItemIconButton.spec.tsx +14 -2
- package/src/listItem/ListItem.css +3 -0
- package/src/listItem/ListItem.less +4 -0
- package/src/listItem/ListItem.tsx +4 -2
- package/src/listItem/Navigation/ListItemNavigation.spec.tsx +8 -0
- package/src/listItem/Navigation/ListItemNavigation.story.tsx +4 -2
- package/src/listItem/_stories/ListItem.layout.test.story.tsx +20 -0
- package/src/listItem/_stories/ListItem.story.tsx +1 -1
- package/src/listItem/_stories/ListItem.variants.test.story.tsx +3 -0
- package/src/listItem/_stories/subcomponents.tsx +2 -0
- package/src/main.css +24 -14
- package/src/primitives/PrimitiveAnchor/test/PrimitiveAnchor.spec.tsx +15 -4
- package/src/select/Select.story.tsx +1 -1
- package/src/title/Title.tsx +25 -12
package/src/header/Header.tsx
CHANGED
|
@@ -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 {
|
|
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
|
|
15
|
-
|
|
14
|
+
type ButtonActionProps = ActionProps;
|
|
16
15
|
type LinkActionProps = ActionProps & LinkProps;
|
|
17
16
|
|
|
18
|
-
export
|
|
17
|
+
export interface HeaderProps extends CommonProps {
|
|
19
18
|
/**
|
|
20
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
'aria-label': action['aria-label'],
|
|
35
|
-
};
|
|
38
|
+
/** Optional prop to specify the level of the Header */
|
|
39
|
+
level?: 'section' | 'group';
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
<
|
|
47
|
-
className="np-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
</
|
|
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
|
-
*
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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;
|
package/src/header/index.ts
CHANGED
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 '
|
|
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,
|
|
@@ -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
|
-
{
|
|
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:
|
|
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={
|
|
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 = {
|
|
@@ -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: (
|
package/src/main.css
CHANGED
|
@@ -2433,30 +2433,37 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
2433
2433
|
fill: var(--color-interactive-primary);
|
|
2434
2434
|
}
|
|
2435
2435
|
.np-header {
|
|
2436
|
-
display:
|
|
2437
|
-
|
|
2438
|
-
|
|
2436
|
+
display: grid;
|
|
2437
|
+
grid-template-columns: 1fr auto;
|
|
2438
|
+
grid-column-gap: 24px;
|
|
2439
|
+
grid-column-gap: var(--size-24);
|
|
2440
|
+
-moz-column-gap: 24px;
|
|
2441
|
+
column-gap: 24px;
|
|
2442
|
+
-moz-column-gap: var(--size-24);
|
|
2443
|
+
column-gap: var(--size-24);
|
|
2444
|
+
align-items: center;
|
|
2445
|
+
margin-bottom: 8px;
|
|
2446
|
+
margin-bottom: var(--size-8);
|
|
2439
2447
|
max-width: 100%;
|
|
2440
2448
|
padding: 8px 0;
|
|
2441
2449
|
padding: var(--size-8) 0;
|
|
2450
|
+
width: 100%;
|
|
2451
|
+
}
|
|
2452
|
+
.np-header--group {
|
|
2453
|
+
align-items: flex-end;
|
|
2442
2454
|
border-bottom: 1px solid rgba(0,0,0,0.10196);
|
|
2443
2455
|
border-bottom: 1px solid var(--color-border-neutral);
|
|
2444
|
-
margin-bottom: 8px;
|
|
2445
|
-
margin-bottom: var(--size-8);
|
|
2446
|
-
-moz-column-gap: 24px;
|
|
2447
|
-
column-gap: 24px;
|
|
2448
|
-
-moz-column-gap: var(--size-24);
|
|
2449
|
-
column-gap: var(--size-24);
|
|
2450
2456
|
}
|
|
2451
2457
|
.np-header__title {
|
|
2452
2458
|
color: #5d7079;
|
|
2453
2459
|
color: var(--color-content-secondary);
|
|
2460
|
+
margin: 0;
|
|
2454
2461
|
}
|
|
2455
|
-
.np-
|
|
2456
|
-
margin
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2462
|
+
.np-header__action {
|
|
2463
|
+
margin: 0;
|
|
2464
|
+
height: 20px;
|
|
2465
|
+
display: flex;
|
|
2466
|
+
align-items: center;
|
|
2460
2467
|
}
|
|
2461
2468
|
.tw-image {
|
|
2462
2469
|
max-width: none;
|
|
@@ -3324,6 +3331,9 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
3324
3331
|
margin-top: calc(4px * -1);
|
|
3325
3332
|
margin-top: calc(var(--size-4) * -1);
|
|
3326
3333
|
}
|
|
3334
|
+
.wds-list-item-additional-info button.np-link {
|
|
3335
|
+
text-align: start;
|
|
3336
|
+
}
|
|
3327
3337
|
.wds-list-item-control-wrapper {
|
|
3328
3338
|
grid-area: control;
|
|
3329
3339
|
align-content: center;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { render, screen } from '
|
|
1
|
+
import { render, screen, userEvent } from '../../../test-utils';
|
|
2
2
|
import PrimitiveAnchor from '..';
|
|
3
3
|
|
|
4
4
|
describe('PrimitiveAnchor', () => {
|
|
5
5
|
const defaultProps = {
|
|
6
6
|
children: 'Click me',
|
|
7
|
-
href: '
|
|
7
|
+
href: '#destination',
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
const renderAnchor = (props?: Partial<typeof defaultProps>) => {
|
|
@@ -15,7 +15,7 @@ describe('PrimitiveAnchor', () => {
|
|
|
15
15
|
renderAnchor();
|
|
16
16
|
expect(screen.getByRole('link')).toBeInTheDocument();
|
|
17
17
|
expect(screen.getByRole('link')).toHaveTextContent('Click me');
|
|
18
|
-
expect(screen.getByRole('link')).toHaveAttribute('href',
|
|
18
|
+
expect(screen.getByRole('link')).toHaveAttribute('href', defaultProps.href);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
it('applies the correct classes based on props', () => {
|
|
@@ -40,7 +40,7 @@ describe('PrimitiveAnchor', () => {
|
|
|
40
40
|
|
|
41
41
|
const link = screen.getByRole('link');
|
|
42
42
|
expect(link).toHaveAttribute('aria-disabled', 'true');
|
|
43
|
-
expect(link).not.toHaveAttribute('href',
|
|
43
|
+
expect(link).not.toHaveAttribute('href', defaultProps.href);
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
it('sets data-testid attribute', () => {
|
|
@@ -90,4 +90,15 @@ describe('PrimitiveAnchor', () => {
|
|
|
90
90
|
const link = screen.getByRole('link');
|
|
91
91
|
expect(link).toHaveStyle('color: red');
|
|
92
92
|
});
|
|
93
|
+
|
|
94
|
+
it('should respect click handlers', async () => {
|
|
95
|
+
const props = {
|
|
96
|
+
...defaultProps,
|
|
97
|
+
onClick: jest.fn(),
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
renderAnchor(props);
|
|
101
|
+
await userEvent.click(screen.getByRole('link'));
|
|
102
|
+
expect(props.onClick).toHaveBeenCalledTimes(1);
|
|
103
|
+
});
|
|
93
104
|
});
|
|
@@ -9,7 +9,7 @@ import Select, { SelectItem, SelectOptionItem } from './Select';
|
|
|
9
9
|
const meta: Meta<typeof Select> = {
|
|
10
10
|
component: Select,
|
|
11
11
|
title: 'Forms/Select',
|
|
12
|
-
tags: ['
|
|
12
|
+
tags: ['deprecated'],
|
|
13
13
|
argTypes: {
|
|
14
14
|
id: { control: 'text' },
|
|
15
15
|
size: { control: 'radio', options: ['sm', 'md', 'lg'] },
|