@transferwise/components 46.103.1 → 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 +3 -1
- package/build/index.js.map +1 -1
- package/build/index.mjs +2 -1
- package/build/index.mjs.map +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/AdditionalInfo/ListItemAdditionalInfo.js +56 -0
- package/build/listItem/AdditionalInfo/ListItemAdditionalInfo.js.map +1 -0
- package/build/listItem/AdditionalInfo/ListItemAdditionalInfo.mjs +54 -0
- package/build/listItem/AdditionalInfo/ListItemAdditionalInfo.mjs.map +1 -0
- package/build/listItem/AvatarLayout/ListItemAvatarLayout.js +23 -0
- package/build/listItem/AvatarLayout/ListItemAvatarLayout.js.map +1 -0
- package/build/listItem/AvatarLayout/ListItemAvatarLayout.mjs +21 -0
- package/build/listItem/AvatarLayout/ListItemAvatarLayout.mjs.map +1 -0
- package/build/listItem/AvatarView/ListItemAvatarView.js +23 -0
- package/build/listItem/AvatarView/ListItemAvatarView.js.map +1 -0
- package/build/listItem/AvatarView/ListItemAvatarView.mjs +21 -0
- package/build/listItem/AvatarView/ListItemAvatarView.mjs.map +1 -0
- package/build/listItem/Button/ListItemButton.js +43 -0
- package/build/listItem/Button/ListItemButton.js.map +1 -0
- package/build/listItem/Button/ListItemButton.mjs +41 -0
- package/build/listItem/Button/ListItemButton.mjs.map +1 -0
- package/build/listItem/Checkbox/ListItemCheckbox.js +30 -0
- package/build/listItem/Checkbox/ListItemCheckbox.js.map +1 -0
- package/build/listItem/Checkbox/ListItemCheckbox.mjs +28 -0
- package/build/listItem/Checkbox/ListItemCheckbox.mjs.map +1 -0
- package/build/listItem/IconButton/ListItemIconButton.js +56 -0
- package/build/listItem/IconButton/ListItemIconButton.js.map +1 -0
- package/build/listItem/IconButton/ListItemIconButton.mjs +54 -0
- package/build/listItem/IconButton/ListItemIconButton.mjs.map +1 -0
- package/build/listItem/Image/ListItemImage.js +31 -0
- package/build/listItem/Image/ListItemImage.js.map +1 -0
- package/build/listItem/Image/ListItemImage.mjs +29 -0
- package/build/listItem/Image/ListItemImage.mjs.map +1 -0
- package/build/listItem/ListItem.js +311 -0
- package/build/listItem/ListItem.js.map +1 -0
- package/build/listItem/ListItem.mjs +306 -0
- package/build/listItem/ListItem.mjs.map +1 -0
- package/build/listItem/ListItemContext.js +8 -0
- package/build/listItem/ListItemContext.js.map +1 -0
- package/build/listItem/ListItemContext.mjs +6 -0
- package/build/listItem/ListItemContext.mjs.map +1 -0
- package/build/listItem/Navigation/ListItemNavigation.js +44 -0
- package/build/listItem/Navigation/ListItemNavigation.js.map +1 -0
- package/build/listItem/Navigation/ListItemNavigation.mjs +42 -0
- package/build/listItem/Navigation/ListItemNavigation.mjs.map +1 -0
- package/build/listItem/Prompt/ListItemPrompt.js +59 -0
- package/build/listItem/Prompt/ListItemPrompt.js.map +1 -0
- package/build/listItem/Prompt/ListItemPrompt.mjs +54 -0
- package/build/listItem/Prompt/ListItemPrompt.mjs.map +1 -0
- package/build/listItem/Radio/ListItemRadio.js +30 -0
- package/build/listItem/Radio/ListItemRadio.js.map +1 -0
- package/build/listItem/Radio/ListItemRadio.mjs +28 -0
- package/build/listItem/Radio/ListItemRadio.mjs.map +1 -0
- package/build/listItem/Switch/ListItemSwitch.js +30 -0
- package/build/listItem/Switch/ListItemSwitch.js.map +1 -0
- package/build/listItem/Switch/ListItemSwitch.mjs +28 -0
- package/build/listItem/Switch/ListItemSwitch.mjs.map +1 -0
- package/build/listItem/useListItemControl.js +22 -0
- package/build/listItem/useListItemControl.js.map +1 -0
- package/build/listItem/useListItemControl.mjs +20 -0
- package/build/listItem/useListItemControl.mjs.map +1 -0
- package/build/listItem/useListItemMedia.js +21 -0
- package/build/listItem/useListItemMedia.js.map +1 -0
- package/build/listItem/useListItemMedia.mjs +19 -0
- package/build/listItem/useListItemMedia.mjs.map +1 -0
- package/build/main.css +794 -14
- package/build/styles/header/Header.css +21 -14
- package/build/styles/listItem/ListItem.css +773 -0
- package/build/styles/listItem/ListItem.grid.css +370 -0
- package/build/styles/listItem/Prompt/ListItemPrompt.css +157 -0
- package/build/styles/main.css +794 -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 +3 -0
- package/build/types/index.d.ts.map +1 -1
- package/build/types/listItem/AdditionalInfo/ListItemAdditionalInfo.d.ts +15 -0
- package/build/types/listItem/AdditionalInfo/ListItemAdditionalInfo.d.ts.map +1 -0
- package/build/types/listItem/AdditionalInfo/index.d.ts +3 -0
- package/build/types/listItem/AdditionalInfo/index.d.ts.map +1 -0
- package/build/types/listItem/AvatarLayout/ListItemAvatarLayout.d.ts +18 -0
- package/build/types/listItem/AvatarLayout/ListItemAvatarLayout.d.ts.map +1 -0
- package/build/types/listItem/AvatarLayout/index.d.ts +3 -0
- package/build/types/listItem/AvatarLayout/index.d.ts.map +1 -0
- package/build/types/listItem/AvatarView/ListItemAvatarView.d.ts +16 -0
- package/build/types/listItem/AvatarView/ListItemAvatarView.d.ts.map +1 -0
- package/build/types/listItem/AvatarView/index.d.ts +3 -0
- package/build/types/listItem/AvatarView/index.d.ts.map +1 -0
- package/build/types/listItem/Button/ListItemButton.d.ts +20 -0
- package/build/types/listItem/Button/ListItemButton.d.ts.map +1 -0
- package/build/types/listItem/Button/index.d.ts +3 -0
- package/build/types/listItem/Button/index.d.ts.map +1 -0
- package/build/types/listItem/Checkbox/ListItemCheckbox.d.ts +14 -0
- package/build/types/listItem/Checkbox/ListItemCheckbox.d.ts.map +1 -0
- package/build/types/listItem/Checkbox/index.d.ts +3 -0
- package/build/types/listItem/Checkbox/index.d.ts.map +1 -0
- package/build/types/listItem/IconButton/ListItemIconButton.d.ts +18 -0
- package/build/types/listItem/IconButton/ListItemIconButton.d.ts.map +1 -0
- package/build/types/listItem/IconButton/index.d.ts +3 -0
- package/build/types/listItem/IconButton/index.d.ts.map +1 -0
- package/build/types/listItem/Image/ListItemImage.d.ts +25 -0
- package/build/types/listItem/Image/ListItemImage.d.ts.map +1 -0
- package/build/types/listItem/Image/index.d.ts +3 -0
- package/build/types/listItem/Image/index.d.ts.map +1 -0
- package/build/types/listItem/ListItem.d.ts +111 -0
- package/build/types/listItem/ListItem.d.ts.map +1 -0
- package/build/types/listItem/ListItemContext.d.ts +21 -0
- package/build/types/listItem/ListItemContext.d.ts.map +1 -0
- package/build/types/listItem/Navigation/ListItemNavigation.d.ts +15 -0
- package/build/types/listItem/Navigation/ListItemNavigation.d.ts.map +1 -0
- package/build/types/listItem/Navigation/index.d.ts +3 -0
- package/build/types/listItem/Navigation/index.d.ts.map +1 -0
- package/build/types/listItem/Prompt/ListItemPrompt.d.ts +16 -0
- package/build/types/listItem/Prompt/ListItemPrompt.d.ts.map +1 -0
- package/build/types/listItem/Prompt/index.d.ts +3 -0
- package/build/types/listItem/Prompt/index.d.ts.map +1 -0
- package/build/types/listItem/Radio/ListItemRadio.d.ts +14 -0
- package/build/types/listItem/Radio/ListItemRadio.d.ts.map +1 -0
- package/build/types/listItem/Radio/index.d.ts +3 -0
- package/build/types/listItem/Radio/index.d.ts.map +1 -0
- package/build/types/listItem/Switch/ListItemSwitch.d.ts +14 -0
- package/build/types/listItem/Switch/ListItemSwitch.d.ts.map +1 -0
- package/build/types/listItem/Switch/index.d.ts +3 -0
- package/build/types/listItem/Switch/index.d.ts.map +1 -0
- package/build/types/listItem/_stories/helpers.d.ts +27 -0
- package/build/types/listItem/_stories/helpers.d.ts.map +1 -0
- package/build/types/listItem/_stories/subcomponents.d.ts +18 -0
- package/build/types/listItem/_stories/subcomponents.d.ts.map +1 -0
- package/build/types/listItem/index.d.ts +14 -0
- package/build/types/listItem/index.d.ts.map +1 -0
- package/build/types/listItem/test-utils.d.ts +7 -0
- package/build/types/listItem/test-utils.d.ts.map +1 -0
- package/build/types/listItem/useListItemControl.d.ts +5 -0
- package/build/types/listItem/useListItemControl.d.ts.map +1 -0
- package/build/types/listItem/useListItemMedia.d.ts +6 -0
- package/build/types/listItem/useListItemMedia.d.ts.map +1 -0
- package/build/types/title/Title.d.ts +4 -5
- package/build/types/title/Title.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/button/Button.spec.tsx +25 -1
- package/src/button/Button.story.tsx +1 -0
- 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 +16 -0
- package/src/legacylistItem/LegacyListItem.story.tsx +1 -1
- package/src/legacylistItem/LegacyListItem.tests.story.tsx +2 -1
- package/src/list/List.story.tsx +13 -3
- package/src/listItem/AdditionalInfo/ListItemAdditionalInfo.spec.tsx +56 -0
- package/src/listItem/AdditionalInfo/ListItemAdditionalInfo.story.tsx +198 -0
- package/src/listItem/AdditionalInfo/ListItemAdditionalInfo.tsx +36 -0
- package/src/listItem/AdditionalInfo/index.ts +2 -0
- package/src/listItem/AvatarLayout/ListItemAvatarLayout.spec.tsx +59 -0
- package/src/listItem/AvatarLayout/ListItemAvatarLayout.story.tsx +124 -0
- package/src/listItem/AvatarLayout/ListItemAvatarLayout.tsx +27 -0
- package/src/listItem/AvatarLayout/index.ts +2 -0
- package/src/listItem/AvatarView/ListItemAvatarView.spec.tsx +75 -0
- package/src/listItem/AvatarView/ListItemAvatarView.story.tsx +339 -0
- package/src/listItem/AvatarView/ListItemAvatarView.tsx +27 -0
- package/src/listItem/AvatarView/index.ts +2 -0
- package/src/listItem/Button/ListItemButton.spec.tsx +90 -0
- package/src/listItem/Button/ListItemButton.story.tsx +473 -0
- package/src/listItem/Button/ListItemButton.tsx +56 -0
- package/src/listItem/Button/index.ts +2 -0
- package/src/listItem/Checkbox/ListItemCheckbox.spec.tsx +82 -0
- package/src/listItem/Checkbox/ListItemCheckbox.story.tsx +128 -0
- package/src/listItem/Checkbox/ListItemCheckbox.tsx +33 -0
- package/src/listItem/Checkbox/index.ts +2 -0
- package/src/listItem/IconButton/ListItemIconButton.spec.tsx +131 -0
- package/src/listItem/IconButton/ListItemIconButton.story.tsx +284 -0
- package/src/listItem/IconButton/ListItemIconButton.tsx +73 -0
- package/src/listItem/IconButton/index.ts +2 -0
- package/src/listItem/Image/ListItemImage.spec.tsx +30 -0
- package/src/listItem/Image/ListItemImage.story.tsx +80 -0
- package/src/listItem/Image/ListItemImage.tsx +46 -0
- package/src/listItem/Image/index.ts +2 -0
- package/src/listItem/ListItem.css +773 -0
- package/src/listItem/ListItem.grid.css +370 -0
- package/src/listItem/ListItem.grid.less +622 -0
- package/src/listItem/ListItem.less +291 -0
- package/src/listItem/ListItem.spec.tsx +1511 -0
- package/src/listItem/ListItem.tsx +440 -0
- package/src/listItem/ListItemContext.tsx +26 -0
- package/src/listItem/Navigation/ListItemNavigation.spec.tsx +67 -0
- package/src/listItem/Navigation/ListItemNavigation.story.tsx +114 -0
- package/src/listItem/Navigation/ListItemNavigation.tsx +39 -0
- package/src/listItem/Navigation/index.ts +2 -0
- package/src/listItem/Prompt/ListItemPrompt.css +157 -0
- package/src/listItem/Prompt/ListItemPrompt.less +134 -0
- package/src/listItem/Prompt/ListItemPrompt.spec.tsx +36 -0
- package/src/listItem/Prompt/ListItemPrompt.story.tsx +204 -0
- package/src/listItem/Prompt/ListItemPrompt.tsx +32 -0
- package/src/listItem/Prompt/index.ts +2 -0
- package/src/listItem/Radio/ListItemRadio.spec.tsx +66 -0
- package/src/listItem/Radio/ListItemRadio.story.tsx +111 -0
- package/src/listItem/Radio/ListItemRadio.tsx +33 -0
- package/src/listItem/Radio/index.ts +2 -0
- package/src/listItem/Switch/ListItemSwitch.spec.tsx +47 -0
- package/src/listItem/Switch/ListItemSwitch.story.tsx +79 -0
- package/src/listItem/Switch/ListItemSwitch.tsx +33 -0
- package/src/listItem/Switch/index.ts +2 -0
- package/src/listItem/_stories/ListItem.focus.test.story.tsx +265 -0
- package/src/listItem/_stories/ListItem.layout.test.story.tsx +374 -0
- package/src/listItem/_stories/ListItem.scenarios.story.tsx +228 -0
- package/src/listItem/_stories/ListItem.story.tsx +774 -0
- package/src/listItem/_stories/ListItem.variants.test.story.tsx +274 -0
- package/src/listItem/_stories/helpers.tsx +53 -0
- package/src/listItem/_stories/subcomponents.tsx +141 -0
- package/src/listItem/index.ts +14 -0
- package/src/listItem/test-utils.tsx +33 -0
- package/src/listItem/useListItemControl.tsx +18 -0
- package/src/listItem/useListItemMedia.tsx +16 -0
- package/src/main.css +794 -14
- package/src/main.less +1 -0
- package/src/primitives/PrimitiveAnchor/test/PrimitiveAnchor.spec.tsx +15 -4
- package/src/title/Title.tsx +25 -12
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useContext,
|
|
3
|
+
useId,
|
|
4
|
+
useMemo,
|
|
5
|
+
useState,
|
|
6
|
+
type PropsWithChildren,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import { Typography } from '../common';
|
|
10
|
+
import Body from '../body';
|
|
11
|
+
import { AdditionalInfo } from './AdditionalInfo';
|
|
12
|
+
import { IconButton, type ListItemIconButtonProps } from './IconButton';
|
|
13
|
+
import { Checkbox, type ListItemCheckboxProps } from './Checkbox';
|
|
14
|
+
import { Navigation, type ListItemNavigationProps } from './Navigation';
|
|
15
|
+
import { clsx } from 'clsx';
|
|
16
|
+
import { Button, type ListItemButtonProps } from './Button';
|
|
17
|
+
import { Radio, type ListItemRadioProps } from './Radio';
|
|
18
|
+
import { Switch, type ListItemSwitchProps } from './Switch';
|
|
19
|
+
import { AvatarLayout } from './AvatarLayout';
|
|
20
|
+
import { AvatarView } from './AvatarView';
|
|
21
|
+
import { Image } from './Image';
|
|
22
|
+
import { Prompt } from './Prompt';
|
|
23
|
+
import { PrimitiveAnchor, type PrimitiveAnchorProps } from '../primitives';
|
|
24
|
+
import {
|
|
25
|
+
ListItemContext,
|
|
26
|
+
type ListItemContextData,
|
|
27
|
+
type ListItemMediaSize,
|
|
28
|
+
} from './ListItemContext';
|
|
29
|
+
|
|
30
|
+
export type ListItemTypes =
|
|
31
|
+
| 'non-interactive'
|
|
32
|
+
| 'navigation'
|
|
33
|
+
| 'radio'
|
|
34
|
+
| 'checkbox'
|
|
35
|
+
| 'switch'
|
|
36
|
+
| 'button'
|
|
37
|
+
| 'icon-button';
|
|
38
|
+
|
|
39
|
+
export type ListItemControlProps =
|
|
40
|
+
| ListItemNavigationProps
|
|
41
|
+
| ListItemCheckboxProps
|
|
42
|
+
| ListItemButtonProps
|
|
43
|
+
| ListItemIconButtonProps
|
|
44
|
+
| ListItemRadioProps
|
|
45
|
+
| ListItemSwitchProps;
|
|
46
|
+
|
|
47
|
+
export type ListItemProps = {
|
|
48
|
+
as?: 'li' | 'div';
|
|
49
|
+
/**
|
|
50
|
+
* Swaps vertical hierarchy of title and subtitle and their corresponding right values.
|
|
51
|
+
*/
|
|
52
|
+
inverted?: boolean;
|
|
53
|
+
disabled?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Highlights the list item as an action to be taken or already taken. <br />
|
|
56
|
+
*/
|
|
57
|
+
spotlight?: 'active' | 'inactive';
|
|
58
|
+
title: ReactNode;
|
|
59
|
+
subtitle?: ReactNode;
|
|
60
|
+
/**
|
|
61
|
+
* Requires `<ListItem.AdditionalInfo />` component as a sole child. <br />
|
|
62
|
+
* Can be only rendered if `subtitle` is also provided.
|
|
63
|
+
*/
|
|
64
|
+
additionalInfo?: ReactNode;
|
|
65
|
+
valueTitle?: ReactNode;
|
|
66
|
+
valueSubtitle?: ReactNode;
|
|
67
|
+
/**
|
|
68
|
+
* Requires one of the following as a sole child: <br />
|
|
69
|
+
* `<ListItem.AvatarView />`,
|
|
70
|
+
* `<ListItem.AvatarLayout />` or
|
|
71
|
+
* `<ListItem.Image />`
|
|
72
|
+
*/
|
|
73
|
+
media?: ReactNode;
|
|
74
|
+
/**
|
|
75
|
+
* Requires one of the following as a sole child: <br/>
|
|
76
|
+
* `<ListItem.Button />`, <br/>
|
|
77
|
+
* `<ListItem.Checkbox />`, <br/>
|
|
78
|
+
* `<ListItem.IconButton />`, <br/>
|
|
79
|
+
* `<ListItem.Navigation />`, <br/>
|
|
80
|
+
* `<ListItem.Radio />`, or
|
|
81
|
+
* `<ListItem.Switch />`
|
|
82
|
+
*/
|
|
83
|
+
control?: ReactNode;
|
|
84
|
+
/**
|
|
85
|
+
* Requires `<ListItem.Prompt />` component as a sole child.
|
|
86
|
+
*/
|
|
87
|
+
prompt?: ReactNode;
|
|
88
|
+
className?: string;
|
|
89
|
+
/**
|
|
90
|
+
* A number between `0–100` which resolves to a `fr` value of a `grid-template-columns` declaration. E.g. `valueColumnWidth={25}` will result in a `75fr 25fr`. <br />
|
|
91
|
+
* Controls the width ratio of left side content (title and subtitle) to the right side content.
|
|
92
|
+
*/
|
|
93
|
+
valueColumnWidth?: number;
|
|
94
|
+
id?: string;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @see [Design documentation](https://wise.design/components/list-item)
|
|
99
|
+
* @see [Storybook documentation](https://storybook.wise.design/?path=/docs/content-listitem--docs)
|
|
100
|
+
*/
|
|
101
|
+
export const ListItem = ({
|
|
102
|
+
as: ListItemElement = 'li',
|
|
103
|
+
title,
|
|
104
|
+
subtitle,
|
|
105
|
+
additionalInfo,
|
|
106
|
+
prompt,
|
|
107
|
+
inverted,
|
|
108
|
+
media,
|
|
109
|
+
spotlight,
|
|
110
|
+
valueTitle,
|
|
111
|
+
valueSubtitle,
|
|
112
|
+
control = null,
|
|
113
|
+
disabled,
|
|
114
|
+
className,
|
|
115
|
+
valueColumnWidth,
|
|
116
|
+
id,
|
|
117
|
+
}: ListItemProps) => {
|
|
118
|
+
const idPrefix = useId();
|
|
119
|
+
const [controlProps, setControlProps] = useState<ListItemControlProps>({});
|
|
120
|
+
const [controlType, setControlType] = useState<ListItemTypes>('non-interactive');
|
|
121
|
+
const [mediaSize, setMediaSize] = useState<ListItemMediaSize | undefined>();
|
|
122
|
+
|
|
123
|
+
const ids: ListItemContextData['ids'] = {
|
|
124
|
+
title: `${idPrefix}_title`,
|
|
125
|
+
...(subtitle ? { subtitle: `${idPrefix}_subtitle` } : {}),
|
|
126
|
+
...(valueTitle ? { valueTitle: `${idPrefix}_value-title` } : {}),
|
|
127
|
+
...(valueSubtitle ? { valueSubtitle: `${idPrefix}_value-subtitle` } : {}),
|
|
128
|
+
control: `${idPrefix}_control`,
|
|
129
|
+
...(prompt ? { prompt: `${idPrefix}_prompt` } : {}),
|
|
130
|
+
...(additionalInfo ? { additionalInfo: `${idPrefix}_additional-info` } : {}),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const isPartiallyInteractive = Boolean(
|
|
134
|
+
(controlType === 'button' || controlType === 'icon-button') &&
|
|
135
|
+
(controlProps as ListItemButtonProps | ListItemIconButtonProps)?.partiallyInteractive,
|
|
136
|
+
);
|
|
137
|
+
const isFullyInteractive = controlType !== 'non-interactive' && !isPartiallyInteractive;
|
|
138
|
+
const isButtonAsLink =
|
|
139
|
+
(controlType === 'button' || controlType === 'icon-button') &&
|
|
140
|
+
Boolean((controlProps as ListItemButtonProps | ListItemIconButtonProps)?.href);
|
|
141
|
+
|
|
142
|
+
const titlesAndValues = [
|
|
143
|
+
inverted ? ids.subtitle : ids.title,
|
|
144
|
+
inverted ? ids.title : ids.subtitle,
|
|
145
|
+
inverted ? ids.valueSubtitle : ids.valueTitle,
|
|
146
|
+
inverted ? ids.valueTitle : ids.valueSubtitle,
|
|
147
|
+
].join(' ');
|
|
148
|
+
const additionalInfoPrompt = [ids.additionalInfo, ids.prompt].filter(Boolean).join(' ');
|
|
149
|
+
|
|
150
|
+
const describedByIds = useMemo(() => {
|
|
151
|
+
return isFullyInteractive && !isButtonAsLink
|
|
152
|
+
? additionalInfoPrompt
|
|
153
|
+
: `${titlesAndValues} ${additionalInfoPrompt}`;
|
|
154
|
+
}, [isFullyInteractive]);
|
|
155
|
+
|
|
156
|
+
const listItemContext = useMemo(
|
|
157
|
+
() => ({
|
|
158
|
+
setControlType,
|
|
159
|
+
setControlProps,
|
|
160
|
+
setMediaSize,
|
|
161
|
+
ids,
|
|
162
|
+
props: { disabled, inverted },
|
|
163
|
+
mediaSize,
|
|
164
|
+
describedByIds,
|
|
165
|
+
}),
|
|
166
|
+
[describedByIds, mediaSize],
|
|
167
|
+
);
|
|
168
|
+
const gridColumnsStyle = {
|
|
169
|
+
'--wds-list-item-body-left': valueColumnWidth ? `${100 - valueColumnWidth}fr` : '50fr',
|
|
170
|
+
'--wds-list-item-body-right': valueColumnWidth ? `${valueColumnWidth}fr` : '50fr',
|
|
171
|
+
} as React.CSSProperties;
|
|
172
|
+
|
|
173
|
+
const getFeatureClassName = () => {
|
|
174
|
+
const partials = [];
|
|
175
|
+
const hasMedia = Boolean(media);
|
|
176
|
+
const hasControl = Boolean(control);
|
|
177
|
+
const hasInfo = Boolean(additionalInfo);
|
|
178
|
+
const hasPrompt = Boolean(prompt);
|
|
179
|
+
|
|
180
|
+
/* eslint-disable functional/immutable-data */
|
|
181
|
+
if (hasMedia && hasControl) {
|
|
182
|
+
partials.push('wds-list-item-hasMedia-hasControl');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (hasMedia && !hasControl) {
|
|
186
|
+
partials.push('wds-list-item-hasMedia-noControl');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!hasMedia && hasControl) {
|
|
190
|
+
partials.push('wds-list-item-noMedia-hasControl');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!hasMedia && !hasControl) {
|
|
194
|
+
partials.push('wds-list-item-noMedia-noControl');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (hasInfo && hasPrompt) {
|
|
198
|
+
partials.push('wds-list-item-hasInfo-hasPrompt');
|
|
199
|
+
}
|
|
200
|
+
if (hasInfo && !hasPrompt) {
|
|
201
|
+
partials.push('wds-list-item-hasInfo-noPrompt');
|
|
202
|
+
}
|
|
203
|
+
if (!hasInfo && hasPrompt) {
|
|
204
|
+
partials.push('wds-list-item-noInfo-hasPrompt');
|
|
205
|
+
}
|
|
206
|
+
if (!hasInfo && !hasPrompt) {
|
|
207
|
+
partials.push('wds-list-item-noInfo-noPrompt');
|
|
208
|
+
}
|
|
209
|
+
/* eslint-enable functional/immutable-data */
|
|
210
|
+
|
|
211
|
+
return partials.join(' ');
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<ListItemContext.Provider value={listItemContext}>
|
|
216
|
+
<ListItemElement
|
|
217
|
+
className={clsx(
|
|
218
|
+
'wds-list-item',
|
|
219
|
+
`wds-list-item-${controlType}`,
|
|
220
|
+
getFeatureClassName(),
|
|
221
|
+
{
|
|
222
|
+
'wds-list-item-interactive': isFullyInteractive,
|
|
223
|
+
'wds-list-item-partially-interactive': isPartiallyInteractive,
|
|
224
|
+
[`wds-list-item-spotlight wds-list-item-spotlight-${spotlight}`]:
|
|
225
|
+
isFullyInteractive && !!spotlight,
|
|
226
|
+
disabled,
|
|
227
|
+
},
|
|
228
|
+
className,
|
|
229
|
+
)}
|
|
230
|
+
id={id}
|
|
231
|
+
aria-disabled={disabled}
|
|
232
|
+
>
|
|
233
|
+
{spotlight === 'inactive' && (
|
|
234
|
+
<svg aria-hidden="true" className="wds-list-item-spotlight__border">
|
|
235
|
+
<rect />
|
|
236
|
+
</svg>
|
|
237
|
+
)}
|
|
238
|
+
|
|
239
|
+
<View
|
|
240
|
+
{...{
|
|
241
|
+
isPartiallyInteractive,
|
|
242
|
+
subtitle,
|
|
243
|
+
additionalInfo,
|
|
244
|
+
disabled,
|
|
245
|
+
prompt,
|
|
246
|
+
controlType,
|
|
247
|
+
controlProps,
|
|
248
|
+
}}
|
|
249
|
+
className={getFeatureClassName()}
|
|
250
|
+
>
|
|
251
|
+
{media && <div className="wds-list-item-media">{media}</div>}
|
|
252
|
+
|
|
253
|
+
{/* Title + Subtitle + Values - Group */}
|
|
254
|
+
<div
|
|
255
|
+
className="wds-list-item-body"
|
|
256
|
+
style={valueColumnWidth ? gridColumnsStyle : undefined}
|
|
257
|
+
>
|
|
258
|
+
{/* Title + Subtitle + Values - Group */}
|
|
259
|
+
<span
|
|
260
|
+
className={clsx({
|
|
261
|
+
'wds-list-item-body-center': title && !subtitle,
|
|
262
|
+
})}
|
|
263
|
+
>
|
|
264
|
+
{(() => {
|
|
265
|
+
const titles = [
|
|
266
|
+
<Body
|
|
267
|
+
key={ids.title}
|
|
268
|
+
id={ids.title}
|
|
269
|
+
type={Typography.BODY_LARGE_BOLD}
|
|
270
|
+
className="wds-list-item-title"
|
|
271
|
+
>
|
|
272
|
+
{title}
|
|
273
|
+
</Body>,
|
|
274
|
+
];
|
|
275
|
+
if (subtitle) {
|
|
276
|
+
titles.push(
|
|
277
|
+
<Body key={ids.subtitle} id={ids.subtitle} className="wds-list-item-subtitle">
|
|
278
|
+
{subtitle}
|
|
279
|
+
</Body>,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
return inverted ? [...titles].reverse() : titles;
|
|
283
|
+
})()}
|
|
284
|
+
</span>
|
|
285
|
+
|
|
286
|
+
{(valueTitle || valueSubtitle) && (
|
|
287
|
+
<span
|
|
288
|
+
className={clsx('wds-list-item-value', {
|
|
289
|
+
'flex-column': valueTitle !== undefined || valueSubtitle !== undefined,
|
|
290
|
+
'wds-list-item-body-center':
|
|
291
|
+
(valueTitle && !valueSubtitle) || (!valueTitle && valueSubtitle),
|
|
292
|
+
})}
|
|
293
|
+
>
|
|
294
|
+
{(() => {
|
|
295
|
+
const values = [];
|
|
296
|
+
if (valueTitle) {
|
|
297
|
+
values.push(
|
|
298
|
+
<Body
|
|
299
|
+
key={ids.valueTitle}
|
|
300
|
+
id={ids.valueTitle}
|
|
301
|
+
type={Typography.BODY_LARGE_BOLD}
|
|
302
|
+
className="wds-list-item-title-value"
|
|
303
|
+
>
|
|
304
|
+
{valueTitle}
|
|
305
|
+
</Body>,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
if (valueSubtitle) {
|
|
309
|
+
values.push(
|
|
310
|
+
<Body
|
|
311
|
+
key={ids.valueSubtitle}
|
|
312
|
+
id={ids.valueSubtitle}
|
|
313
|
+
className="wds-list-item-subtitle-value"
|
|
314
|
+
>
|
|
315
|
+
{valueSubtitle}
|
|
316
|
+
</Body>,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
return inverted ? [...values].reverse() : values;
|
|
320
|
+
})()}
|
|
321
|
+
</span>
|
|
322
|
+
)}
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
{control === null ? null : (
|
|
326
|
+
<Body
|
|
327
|
+
className={clsx('wds-list-item-control-wrapper', {
|
|
328
|
+
'wds-list-item-button-control': controlType === 'button',
|
|
329
|
+
})}
|
|
330
|
+
style={
|
|
331
|
+
{
|
|
332
|
+
'--wds-list-item-control-wrapper-height': mediaSize ? `${mediaSize}px` : 'auto',
|
|
333
|
+
} as React.CSSProperties
|
|
334
|
+
}
|
|
335
|
+
>
|
|
336
|
+
{control}
|
|
337
|
+
</Body>
|
|
338
|
+
)}
|
|
339
|
+
</View>
|
|
340
|
+
</ListItemElement>
|
|
341
|
+
</ListItemContext.Provider>
|
|
342
|
+
);
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
type ViewProps = PropsWithChildren<{
|
|
346
|
+
isPartiallyInteractive: boolean;
|
|
347
|
+
controlType?: ListItemTypes;
|
|
348
|
+
controlProps?: ListItemControlProps;
|
|
349
|
+
}> &
|
|
350
|
+
Pick<ListItemProps, 'subtitle' | 'additionalInfo' | 'disabled' | 'prompt' | 'className'>;
|
|
351
|
+
|
|
352
|
+
function View({
|
|
353
|
+
children,
|
|
354
|
+
subtitle,
|
|
355
|
+
additionalInfo,
|
|
356
|
+
prompt,
|
|
357
|
+
disabled,
|
|
358
|
+
isPartiallyInteractive,
|
|
359
|
+
controlType = 'non-interactive',
|
|
360
|
+
controlProps,
|
|
361
|
+
className = '',
|
|
362
|
+
}: ViewProps) {
|
|
363
|
+
const { ids, describedByIds } = useContext<ListItemContextData>(ListItemContext);
|
|
364
|
+
const isLinkControl = ['navigation'].includes(controlType);
|
|
365
|
+
|
|
366
|
+
const isHrefProvided = isLinkControl && !!(controlProps as ListItemNavigationProps)?.href;
|
|
367
|
+
|
|
368
|
+
const renderExtras = () => (
|
|
369
|
+
<>
|
|
370
|
+
{additionalInfo}
|
|
371
|
+
{prompt}
|
|
372
|
+
</>
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
if (isLinkControl && isHrefProvided) {
|
|
376
|
+
return (
|
|
377
|
+
// for link instances of .Navigation, .IconButton, .Button
|
|
378
|
+
<div className={clsx('wds-list-item-gridWrapper', className)}>
|
|
379
|
+
<PrimitiveAnchor
|
|
380
|
+
aria-describedby={describedByIds}
|
|
381
|
+
href={(controlProps as ListItemNavigationProps)?.href}
|
|
382
|
+
target={(controlProps as ListItemNavigationProps)?.target}
|
|
383
|
+
className={clsx('wds-list-item-view d-flex flex-row', {
|
|
384
|
+
'wds-list-item-control': controlType === 'navigation',
|
|
385
|
+
fullyInteractive: !isPartiallyInteractive,
|
|
386
|
+
})}
|
|
387
|
+
disabled={disabled}
|
|
388
|
+
onClick={(controlProps as PrimitiveAnchorProps | undefined)?.onClick}
|
|
389
|
+
>
|
|
390
|
+
{children}
|
|
391
|
+
</PrimitiveAnchor>
|
|
392
|
+
|
|
393
|
+
{renderExtras()}
|
|
394
|
+
</div>
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (isPartiallyInteractive || controlType === 'non-interactive') {
|
|
399
|
+
return (
|
|
400
|
+
<div className={clsx('wds-list-item-gridWrapper', className)}>
|
|
401
|
+
<div className={clsx('wds-list-item-view d-flex flex-row')}>{children}</div>
|
|
402
|
+
|
|
403
|
+
{renderExtras()}
|
|
404
|
+
</div>
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// for form control instances of .Radio, .Checkbox, .Switch, .Button, .Navigation etc
|
|
409
|
+
// Radio cannot be wrapped in a <fieldset> element to announce it as a group.
|
|
410
|
+
const InputWrapper = controlType === 'radio' ? 'div' : 'fieldset';
|
|
411
|
+
return (
|
|
412
|
+
<InputWrapper className={clsx('wds-list-item-gridWrapper', className)}>
|
|
413
|
+
<label
|
|
414
|
+
htmlFor={ids.control}
|
|
415
|
+
className={clsx('wds-list-item-view', {
|
|
416
|
+
clickable: !disabled,
|
|
417
|
+
fullyInteractive: !isPartiallyInteractive,
|
|
418
|
+
})}
|
|
419
|
+
>
|
|
420
|
+
{children}
|
|
421
|
+
</label>
|
|
422
|
+
|
|
423
|
+
{renderExtras()}
|
|
424
|
+
</InputWrapper>
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
ListItem.Image = Image;
|
|
429
|
+
ListItem.AvatarView = AvatarView;
|
|
430
|
+
ListItem.AvatarLayout = AvatarLayout;
|
|
431
|
+
ListItem.AdditionalInfo = AdditionalInfo;
|
|
432
|
+
ListItem.Checkbox = Checkbox;
|
|
433
|
+
ListItem.Radio = Radio;
|
|
434
|
+
ListItem.IconButton = IconButton;
|
|
435
|
+
ListItem.Navigation = Navigation;
|
|
436
|
+
ListItem.Button = Button;
|
|
437
|
+
ListItem.Switch = Switch;
|
|
438
|
+
ListItem.Prompt = Prompt;
|
|
439
|
+
|
|
440
|
+
export default ListItem;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
import type { ListItemTypes, ListItemControlProps, ListItemProps } from './ListItem';
|
|
3
|
+
|
|
4
|
+
export type ListItemMediaSize = 32 | 40 | 48 | 56 | 72;
|
|
5
|
+
|
|
6
|
+
export type ListItemContextData = {
|
|
7
|
+
setControlType: (type: ListItemTypes) => void;
|
|
8
|
+
setControlProps: (props: ListItemControlProps) => void;
|
|
9
|
+
setMediaSize: (size: ListItemMediaSize | undefined) => void;
|
|
10
|
+
ids: {
|
|
11
|
+
title: string;
|
|
12
|
+
subtitle?: string;
|
|
13
|
+
valueTitle?: string;
|
|
14
|
+
valueSubtitle?: string;
|
|
15
|
+
additionalInfo?: string;
|
|
16
|
+
control: string;
|
|
17
|
+
prompt?: string;
|
|
18
|
+
};
|
|
19
|
+
props: Pick<ListItemProps, 'disabled' | 'inverted'>;
|
|
20
|
+
mediaSize?: ListItemMediaSize;
|
|
21
|
+
describedByIds: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const ListItemContext = createContext<ListItemContextData>(
|
|
25
|
+
null as unknown as ListItemContextData,
|
|
26
|
+
);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { render, screen } from '../../test-utils';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { ListItem, type 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
|
+
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
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
+
import { fn } from 'storybook/test';
|
|
3
|
+
import { lorem10 } from '../../test-utils';
|
|
4
|
+
import List from '../../list';
|
|
5
|
+
import { ListItem } from '../ListItem';
|
|
6
|
+
import {
|
|
7
|
+
SB_LIST_ITEM_ADDITIONAL_INFO as INFO,
|
|
8
|
+
SB_LIST_ITEM_PROMPTS as PROMPTS,
|
|
9
|
+
SB_LIST_ITEM_MEDIA as MEDIA,
|
|
10
|
+
} from '../_stories/subcomponents';
|
|
11
|
+
import type { ListItemNavigationProps } from './ListItemNavigation';
|
|
12
|
+
|
|
13
|
+
const meta: Meta<ListItemNavigationProps> = {
|
|
14
|
+
component: ListItem.Navigation,
|
|
15
|
+
title: 'Content/ListItem/ListItem.Navigation',
|
|
16
|
+
parameters: {
|
|
17
|
+
docs: {
|
|
18
|
+
toc: true,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
args: {
|
|
22
|
+
href: 'https://wise.com',
|
|
23
|
+
onClick: fn(),
|
|
24
|
+
target: undefined,
|
|
25
|
+
},
|
|
26
|
+
argTypes: {
|
|
27
|
+
onClick: {
|
|
28
|
+
description:
|
|
29
|
+
'If `href` is falsy and `onClick` is set, the component will render as an HTML button.',
|
|
30
|
+
},
|
|
31
|
+
target: {
|
|
32
|
+
control: 'select',
|
|
33
|
+
options: [undefined, '_blank', '_self', '_parent', '_top'],
|
|
34
|
+
description:
|
|
35
|
+
'The `target` attribute for HTML anchor. If set to `_blank`, `rel="noopener noreferrer"` is automatically added to the rendered node.',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
} satisfies Meta<ListItemNavigationProps>;
|
|
39
|
+
|
|
40
|
+
export default meta;
|
|
41
|
+
|
|
42
|
+
type Story = StoryObj<ListItemNavigationProps>;
|
|
43
|
+
|
|
44
|
+
export const Playground: Story = {
|
|
45
|
+
tags: ['!autodocs'],
|
|
46
|
+
render: (args: ListItemNavigationProps) => {
|
|
47
|
+
return (
|
|
48
|
+
<List>
|
|
49
|
+
<ListItem
|
|
50
|
+
title="List item title"
|
|
51
|
+
subtitle="Subtitle goes here"
|
|
52
|
+
media={MEDIA.avatarSingle}
|
|
53
|
+
control={<ListItem.Navigation {...args} />}
|
|
54
|
+
additionalInfo={INFO.nonInteractive}
|
|
55
|
+
/>
|
|
56
|
+
</List>
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Navigation control can be rendered as HTML anchor or a button
|
|
63
|
+
*/
|
|
64
|
+
export const AsButton: Story = {
|
|
65
|
+
args: {
|
|
66
|
+
onClick: fn(),
|
|
67
|
+
},
|
|
68
|
+
parameters: {
|
|
69
|
+
controls: { disable: true },
|
|
70
|
+
},
|
|
71
|
+
render: (args) => {
|
|
72
|
+
return (
|
|
73
|
+
<List>
|
|
74
|
+
<ListItem
|
|
75
|
+
control={
|
|
76
|
+
<ListItem.Navigation href="https://wise.com" target="_blank" onClick={args.onClick} />
|
|
77
|
+
}
|
|
78
|
+
title="Navigation as link"
|
|
79
|
+
subtitle="This will navigate to an external URL"
|
|
80
|
+
media={MEDIA.avatarSingle}
|
|
81
|
+
/>
|
|
82
|
+
|
|
83
|
+
<ListItem
|
|
84
|
+
control={<ListItem.Navigation onClick={args.onClick} />}
|
|
85
|
+
title="Navigation as button"
|
|
86
|
+
subtitle="This will trigger a click handler"
|
|
87
|
+
media={MEDIA.avatarSingle}
|
|
88
|
+
/>
|
|
89
|
+
</List>
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Unlike other controls, the Navigation control has a custom disabled state
|
|
96
|
+
* for improved discoverability, accessibility and overall UX.
|
|
97
|
+
*/
|
|
98
|
+
export const Disabled: Story = {
|
|
99
|
+
render: (args) => {
|
|
100
|
+
return (
|
|
101
|
+
<List>
|
|
102
|
+
<ListItem
|
|
103
|
+
disabled
|
|
104
|
+
control={<ListItem.Navigation {...args} />}
|
|
105
|
+
title="This option is disabled"
|
|
106
|
+
subtitle={lorem10}
|
|
107
|
+
additionalInfo={INFO.nonInteractive}
|
|
108
|
+
prompt={PROMPTS.interactive}
|
|
109
|
+
media={MEDIA.avatarSingle}
|
|
110
|
+
/>
|
|
111
|
+
</List>
|
|
112
|
+
);
|
|
113
|
+
},
|
|
114
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ChevronRight, BackslashCircle } from '@transferwise/icons';
|
|
2
|
+
import type { ButtonProps } from '../../button/Button.types';
|
|
3
|
+
import { useListItemControl } from '../useListItemControl';
|
|
4
|
+
import { PrimitiveButton } from '../../primitives';
|
|
5
|
+
import { useContext } from 'react';
|
|
6
|
+
import { ListItemContext } from '../ListItemContext';
|
|
7
|
+
|
|
8
|
+
export type ListItemNavigationProps = Pick<ButtonProps, 'onClick' | 'href' | 'target'>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* This component allows for rendering a control functionally synonymous with HTML `anchor` or a
|
|
12
|
+
* `button`, giving users a rich way to choose between options and navigate to another screen or
|
|
13
|
+
* step in a flow. It offers only a subset of features of the HTML element in line with the
|
|
14
|
+
* ListItem's constraints.<br />
|
|
15
|
+
* <br />
|
|
16
|
+
* Please refer to the [Design documentation](https://wise.design/components/list-item---navigation) for details.
|
|
17
|
+
*/
|
|
18
|
+
export const Navigation = function Navigation({ href, ...props }: ListItemNavigationProps) {
|
|
19
|
+
const { baseItemProps } = useListItemControl('navigation', { href, ...props });
|
|
20
|
+
const { ids, describedByIds } = useContext(ListItemContext);
|
|
21
|
+
const icon = <ChevronRight size={16} />;
|
|
22
|
+
|
|
23
|
+
if (baseItemProps.disabled) return <BackslashCircle size={24} />;
|
|
24
|
+
|
|
25
|
+
return href ? (
|
|
26
|
+
<>{icon}</>
|
|
27
|
+
) : (
|
|
28
|
+
<PrimitiveButton
|
|
29
|
+
aria-describedby={describedByIds}
|
|
30
|
+
id={ids.control}
|
|
31
|
+
className="btn-unstyled wds-list-item-control"
|
|
32
|
+
onClick={props.onClick as React.MouseEventHandler<HTMLButtonElement> | undefined}
|
|
33
|
+
>
|
|
34
|
+
{icon}
|
|
35
|
+
</PrimitiveButton>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
Navigation.displayName = 'ListItem.Navigation';
|