@transferwise/components 46.126.0 → 46.128.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/alert/Alert.js +3 -0
- package/build/alert/Alert.js.map +1 -1
- package/build/alert/Alert.mjs +3 -0
- package/build/alert/Alert.mjs.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/index.mjs +1 -1
- package/build/inputs/SelectInput.js +81 -12
- package/build/inputs/SelectInput.js.map +1 -1
- package/build/inputs/SelectInput.mjs +81 -13
- package/build/inputs/SelectInput.mjs.map +1 -1
- package/build/listItem/Prompt/ListItemPrompt.js +5 -4
- package/build/listItem/Prompt/ListItemPrompt.js.map +1 -1
- package/build/listItem/Prompt/ListItemPrompt.mjs +6 -2
- package/build/listItem/Prompt/ListItemPrompt.mjs.map +1 -1
- package/build/main.css +36 -28
- package/build/prompt/ActionPrompt/ActionPrompt.js +6 -4
- package/build/prompt/ActionPrompt/ActionPrompt.js.map +1 -1
- package/build/prompt/ActionPrompt/ActionPrompt.mjs +6 -4
- package/build/prompt/ActionPrompt/ActionPrompt.mjs.map +1 -1
- package/build/prompt/InfoPrompt/InfoPrompt.js.map +1 -1
- package/build/prompt/InfoPrompt/InfoPrompt.mjs.map +1 -1
- package/build/prompt/InlinePrompt/InlinePrompt.js +1 -1
- package/build/prompt/InlinePrompt/InlinePrompt.js.map +1 -1
- package/build/prompt/InlinePrompt/InlinePrompt.mjs +1 -1
- package/build/prompt/InlinePrompt/InlinePrompt.mjs.map +1 -1
- package/build/styles/main.css +36 -28
- package/build/styles/prompt/ActionPrompt/ActionPrompt.css +4 -0
- package/build/styles/prompt/InfoPrompt/InfoPrompt.css +7 -5
- package/build/styles/prompt/InlinePrompt/InlinePrompt.css +3 -2
- package/build/styles/prompt/PrimitivePrompt/PrimitivePrompt.css +1 -0
- package/build/styles/sentimentSurface/SentimentSurface.css +21 -21
- package/build/types/alert/Alert.d.ts +15 -0
- package/build/types/alert/Alert.d.ts.map +1 -1
- package/build/types/index.d.ts +1 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/inputs/SelectInput.d.ts +19 -0
- package/build/types/inputs/SelectInput.d.ts.map +1 -1
- package/build/types/listItem/ListItem.d.ts +1 -1
- package/build/types/listItem/Prompt/ListItemPrompt.d.ts +2 -3
- package/build/types/listItem/Prompt/ListItemPrompt.d.ts.map +1 -1
- package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts +7 -0
- package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts.map +1 -1
- package/build/types/prompt/InfoPrompt/InfoPrompt.d.ts +4 -2
- package/build/types/prompt/InfoPrompt/InfoPrompt.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/accordion/Accordion.test.js +0 -6
- package/src/accordion/AccordionItem/AccordionItem.test.js +0 -10
- package/src/actionButton/ActionButton.test.tsx +0 -4
- package/src/alert/Alert.story.tsx +4 -0
- package/src/alert/Alert.test.story.tsx +1 -1
- package/src/alert/Alert.tsx +16 -0
- package/src/avatarWrapper/AvatarWrapper.test.tsx +0 -53
- package/src/checkbox/Checkbox.test.tsx +0 -5
- package/src/chevron/Chevron.test.tsx +0 -7
- package/src/chips/Chips.test.tsx +0 -8
- package/src/common/RadioButton/RadioButton.test.tsx +0 -18
- package/src/common/bottomSheet/BottomSheet.test.tsx +0 -9
- package/src/common/card/Card.test.tsx +0 -6
- package/src/common/closeButton/CloseButton.test.tsx +0 -4
- package/src/common/panel/Panel.test.tsx +0 -6
- package/src/flowNavigation/FlowNavigation.test.js +0 -10
- package/src/iconButton/IconButton.story.tsx +173 -48
- package/src/iconButton/IconButton.test.story.tsx +194 -0
- package/src/index.ts +1 -0
- package/src/inputs/SelectInput.story.tsx +33 -20
- package/src/inputs/SelectInput.test.story.tsx +1285 -5
- package/src/inputs/SelectInput.tsx +93 -15
- package/src/listItem/Prompt/ListItemPrompt.story.tsx +71 -9
- package/src/listItem/Prompt/ListItemPrompt.test.tsx +31 -0
- package/src/listItem/Prompt/ListItemPrompt.tsx +8 -2
- package/src/listItem/_stories/ListItem.story.tsx +0 -1
- package/src/logo/Logo.story.tsx +24 -5
- package/src/main.css +36 -28
- package/src/overlayHeader/OverlayHeader.test.tsx +0 -3
- package/src/popover/Popover.test.tsx +0 -25
- package/src/promoCard/PromoCard.test.tsx +0 -6
- package/src/promoCard/PromoCardGroup.test.tsx +0 -5
- package/src/prompt/ActionPrompt/ActionPrompt.accessibility.docs.mdx +2 -18
- package/src/prompt/ActionPrompt/ActionPrompt.css +4 -0
- package/src/prompt/ActionPrompt/ActionPrompt.less +5 -1
- package/src/prompt/ActionPrompt/ActionPrompt.story.tsx +323 -108
- package/src/prompt/ActionPrompt/ActionPrompt.test.story.tsx +86 -3
- package/src/prompt/ActionPrompt/ActionPrompt.tsx +17 -6
- package/src/prompt/InfoPrompt/InfoPrompt.accessibility.docs.mdx +79 -0
- package/src/prompt/InfoPrompt/InfoPrompt.css +7 -5
- package/src/prompt/InfoPrompt/InfoPrompt.less +8 -8
- package/src/prompt/InfoPrompt/InfoPrompt.story.tsx +112 -82
- package/src/prompt/InfoPrompt/InfoPrompt.test.story.tsx +54 -1
- package/src/prompt/InfoPrompt/InfoPrompt.tsx +4 -2
- package/src/prompt/InlinePrompt/InlinePrompt.accessibility.docs.mdx +63 -0
- package/src/prompt/InlinePrompt/InlinePrompt.css +3 -2
- package/src/prompt/InlinePrompt/InlinePrompt.less +2 -2
- package/src/prompt/InlinePrompt/InlinePrompt.story.tsx +38 -40
- package/src/prompt/InlinePrompt/InlinePrompt.test.story.tsx +21 -0
- package/src/prompt/InlinePrompt/InlinePrompt.test.tsx +23 -4
- package/src/prompt/InlinePrompt/InlinePrompt.tsx +1 -1
- package/src/prompt/PrimitivePrompt/PrimitivePrompt.css +1 -0
- package/src/prompt/PrimitivePrompt/PrimitivePrompt.less +2 -1
- package/src/sentimentSurface/SentimentSurface.css +21 -21
- package/src/sentimentSurface/SentimentSurface.docs.mdx +1 -1
- package/src/sentimentSurface/SentimentSurface.less +13 -13
- package/src/sentimentSurface/SentimentSurface.test.story.tsx +47 -0
- package/src/tile/Tile.test.tsx +0 -10
- package/src/tooltip/Tooltip.test.tsx +0 -10
- package/src/accordion/AccordionItem/__snapshots__/AccordionItem.test.js.snap +0 -124
- package/src/accordion/__snapshots__/Accordion.test.js.snap +0 -3
- package/src/actionButton/__snapshots__/ActionButton.test.tsx.snap +0 -12
- package/src/avatarWrapper/__snapshots__/AvatarWrapper.test.tsx.snap +0 -156
- package/src/checkbox/__snapshots__/Checkbox.test.tsx.snap +0 -40
- package/src/chevron/__snapshots__/Chevron.test.tsx.snap +0 -24
- package/src/chips/__snapshots__/Chips.test.tsx.snap +0 -153
- package/src/common/RadioButton/__snapshots__/RadioButton.test.tsx.snap +0 -58
- package/src/common/bottomSheet/__snapshots__/BottomSheet.test.tsx.snap +0 -80
- package/src/common/card/__snapshots__/Card.test.tsx.snap +0 -10
- package/src/common/closeButton/__snapshots__/CloseButton.test.tsx.snap +0 -30
- package/src/common/flowHeader/FlowHeader.test.tsx +0 -22
- package/src/common/flowHeader/__snapshots__/FlowHeader.test.tsx.snap +0 -33
- package/src/common/panel/__snapshots__/Panel.test.tsx.snap +0 -3
- package/src/flowNavigation/__snapshots__/FlowNavigation.test.js.snap +0 -262
- package/src/logo/Logo.test.tsx +0 -55
- package/src/logo/__snapshots__/Logo.test.tsx.snap +0 -281
- package/src/overlayHeader/__snapshots__/OverlayHeader.test.tsx.snap +0 -65
- package/src/popover/__snapshots__/Popover.test.tsx.snap +0 -51
- package/src/promoCard/__snapshots__/PromoCard.test.tsx.snap +0 -40
- package/src/promoCard/__snapshots__/PromoCardGroup.test.tsx.snap +0 -80
- package/src/tile/__snapshots__/Tile.test.tsx.snap +0 -55
- package/src/tooltip/__snapshots__/Tooltip.test.tsx.snap +0 -32
|
@@ -197,6 +197,52 @@ function sortSelectInputItems<T>(
|
|
|
197
197
|
return flattenedOption.sort((a, b) => compareFn(a, b, searchQuery));
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
/**
|
|
201
|
+
* A prebuilt sort function for `sortFilteredOptions` that sorts options by relevance to the search query.
|
|
202
|
+
* Prioritizes: exact matches > starts with > contains > alphabetical.
|
|
203
|
+
*
|
|
204
|
+
* @param getLabel - Function to extract the label string from the option value. Defaults to using `title` property.
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```tsx
|
|
208
|
+
* <SelectInput
|
|
209
|
+
* filterable
|
|
210
|
+
* sortFilteredOptions={sortByRelevance((value) => value.name)}
|
|
211
|
+
* // ...
|
|
212
|
+
* />
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
export function sortByRelevance<T>(
|
|
216
|
+
getLabel: (value: T) => string = (value) => (value as { title: string }).title,
|
|
217
|
+
): (a: SelectInputOptionItem<T>, b: SelectInputOptionItem<T>, searchQuery: string) => number {
|
|
218
|
+
return (a, b, searchQuery) => {
|
|
219
|
+
const normalizedQuery = searchQuery.toLowerCase();
|
|
220
|
+
const labelA = getLabel(a.value).toLowerCase();
|
|
221
|
+
const labelB = getLabel(b.value).toLowerCase();
|
|
222
|
+
|
|
223
|
+
// Prioritize exact matches
|
|
224
|
+
const aExactMatch = labelA === normalizedQuery;
|
|
225
|
+
const bExactMatch = labelB === normalizedQuery;
|
|
226
|
+
if (aExactMatch && !bExactMatch) return -1;
|
|
227
|
+
if (!aExactMatch && bExactMatch) return 1;
|
|
228
|
+
|
|
229
|
+
// Then prioritize options where label starts with the search query
|
|
230
|
+
const aStartsWith = labelA.startsWith(normalizedQuery);
|
|
231
|
+
const bStartsWith = labelB.startsWith(normalizedQuery);
|
|
232
|
+
if (aStartsWith && !bStartsWith) return -1;
|
|
233
|
+
if (!aStartsWith && bStartsWith) return 1;
|
|
234
|
+
|
|
235
|
+
// Then prioritize options where label contains the search query
|
|
236
|
+
const aContains = labelA.includes(normalizedQuery);
|
|
237
|
+
const bContains = labelB.includes(normalizedQuery);
|
|
238
|
+
if (aContains && !bContains) return -1;
|
|
239
|
+
if (!aContains && bContains) return 1;
|
|
240
|
+
|
|
241
|
+
// Finally sort alphabetically
|
|
242
|
+
return labelA.localeCompare(labelB);
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
200
246
|
export interface SelectInputProps<T = string, M extends boolean = false> {
|
|
201
247
|
id?: string;
|
|
202
248
|
/**
|
|
@@ -558,6 +604,8 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
558
604
|
);
|
|
559
605
|
}
|
|
560
606
|
|
|
607
|
+
SelectInput.sortByRelevance = sortByRelevance;
|
|
608
|
+
|
|
561
609
|
const SelectInputTriggerButtonPropsContext = createContext<{
|
|
562
610
|
ref?: React.ForwardedRef<HTMLButtonElement | null>;
|
|
563
611
|
id?: string;
|
|
@@ -741,15 +789,35 @@ function SelectInputOptions<T = string>({
|
|
|
741
789
|
return items;
|
|
742
790
|
}
|
|
743
791
|
|
|
744
|
-
const
|
|
745
|
-
selectInputOptionItemIncludesNeedle(item, needle),
|
|
746
|
-
);
|
|
792
|
+
const dedupedItems = dedupeSelectInputItems(items, compareValues);
|
|
747
793
|
|
|
748
794
|
if (sortFilteredOptions) {
|
|
795
|
+
// When sorting, filter out non-matching items completely to avoid ghost items
|
|
796
|
+
const filtered = dedupedItems.map((item) => {
|
|
797
|
+
if (item.type === 'option') {
|
|
798
|
+
return selectInputOptionItemIncludesNeedle(item, needle)
|
|
799
|
+
? item
|
|
800
|
+
: { ...item, value: undefined };
|
|
801
|
+
}
|
|
802
|
+
if (item.type === 'group') {
|
|
803
|
+
return {
|
|
804
|
+
...item,
|
|
805
|
+
options: item.options.map((option) =>
|
|
806
|
+
selectInputOptionItemIncludesNeedle(option, needle)
|
|
807
|
+
? option
|
|
808
|
+
: { ...option, value: undefined },
|
|
809
|
+
),
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
return item;
|
|
813
|
+
});
|
|
814
|
+
|
|
749
815
|
return sortSelectInputItems(filtered, sortFilteredOptions, filterQuery);
|
|
750
816
|
}
|
|
751
817
|
|
|
752
|
-
return
|
|
818
|
+
return filterSelectInputItems(dedupedItems, (item) =>
|
|
819
|
+
selectInputOptionItemIncludesNeedle(item, needle),
|
|
820
|
+
);
|
|
753
821
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
754
822
|
}, [needle, items, compareValues]);
|
|
755
823
|
const resultsEmpty = needle != null && filteredItems.length === 0;
|
|
@@ -760,17 +828,28 @@ function SelectInputOptions<T = string>({
|
|
|
760
828
|
// the scroll position may jump around inadvertently. Pattern adopted from:
|
|
761
829
|
// https://inokawa.github.io/virtua/?path=/story/advanced-keep-offscreen-items--append-only
|
|
762
830
|
const [mountedIndexes, setMountedIndexes] = useState<number[]>([]);
|
|
831
|
+
const prevNeedleRef = useRef(needle);
|
|
832
|
+
|
|
763
833
|
useEffect(() => {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
834
|
+
const needleChanged = prevNeedleRef.current !== needle;
|
|
835
|
+
prevNeedleRef.current = needle;
|
|
836
|
+
|
|
837
|
+
if (needleChanged) {
|
|
838
|
+
// Reset mounted indexes when search changes to avoid stale scroll positions
|
|
839
|
+
setMountedIndexes([]);
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Ensure the 'End' key works as intended by keeping the last item mounted.
|
|
844
|
+
// Skipped on needle change to prevent auto-scrolling on search.
|
|
845
|
+
if (filteredItems.length > 0) {
|
|
846
|
+
setMountedIndexes((prevMountedIndexes) => {
|
|
847
|
+
const indexes = new Set(prevMountedIndexes);
|
|
848
|
+
indexes.add(filteredItems.length - 1);
|
|
849
|
+
return [...indexes]; // Sorting is redundant by nature here
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
}, [needle, filteredItems.length]);
|
|
774
853
|
|
|
775
854
|
const listboxContainerRef = useRef<HTMLDivElement>(null);
|
|
776
855
|
useEffect(() => {
|
|
@@ -927,7 +1006,6 @@ function SelectInputOptions<T = string>({
|
|
|
927
1006
|
) : (
|
|
928
1007
|
<Virtualizer
|
|
929
1008
|
ref={virtualiserHandlerRef}
|
|
930
|
-
key={needle}
|
|
931
1009
|
data={filteredItems}
|
|
932
1010
|
keepMounted={mountedIndexes}
|
|
933
1011
|
scrollRef={listboxRef} // `VList` doesn't expose this
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from '../_stories/subcomponents';
|
|
12
12
|
import { ListItem } from '../ListItem';
|
|
13
13
|
import { Prompt, type ListItemPromptProps } from './ListItemPrompt';
|
|
14
|
+
import { Clock } from '@transferwise/icons';
|
|
14
15
|
|
|
15
16
|
const meta: Meta<ListItemPromptProps> = {
|
|
16
17
|
component: Prompt,
|
|
@@ -66,7 +67,7 @@ export const Playground: Story = {
|
|
|
66
67
|
subtitle={lorem10}
|
|
67
68
|
media={MEDIA.avatarSingle}
|
|
68
69
|
control={CONTROLS.switch}
|
|
69
|
-
prompt={<Prompt {...args} />}
|
|
70
|
+
prompt={<ListItem.Prompt {...args} />}
|
|
70
71
|
/>
|
|
71
72
|
</List>
|
|
72
73
|
),
|
|
@@ -86,28 +87,89 @@ export const Sentiment: Story = {
|
|
|
86
87
|
subtitle={lorem10}
|
|
87
88
|
media={MEDIA.avatarSingle}
|
|
88
89
|
control={CONTROLS.switch}
|
|
89
|
-
prompt={
|
|
90
|
+
prompt={
|
|
91
|
+
<ListItem.Prompt sentiment={Sentiments.NEUTRAL}>
|
|
92
|
+
This is a neutral prompt.
|
|
93
|
+
</ListItem.Prompt>
|
|
94
|
+
}
|
|
90
95
|
/>
|
|
91
96
|
<ListItem
|
|
92
97
|
title={lorem5}
|
|
93
98
|
subtitle={lorem10}
|
|
94
99
|
media={MEDIA.avatarSingle}
|
|
95
100
|
control={CONTROLS.switch}
|
|
96
|
-
prompt={
|
|
101
|
+
prompt={
|
|
102
|
+
<ListItem.Prompt sentiment={Sentiments.POSITIVE}>
|
|
103
|
+
This is a positive prompt.
|
|
104
|
+
</ListItem.Prompt>
|
|
105
|
+
}
|
|
97
106
|
/>
|
|
98
107
|
<ListItem
|
|
99
108
|
title={lorem5}
|
|
100
109
|
subtitle={lorem10}
|
|
101
110
|
media={MEDIA.avatarSingle}
|
|
102
111
|
control={CONTROLS.switch}
|
|
103
|
-
prompt={
|
|
112
|
+
prompt={
|
|
113
|
+
<ListItem.Prompt sentiment={Sentiments.WARNING}>
|
|
114
|
+
This is a warning prompt.
|
|
115
|
+
</ListItem.Prompt>
|
|
116
|
+
}
|
|
104
117
|
/>
|
|
105
118
|
<ListItem
|
|
106
119
|
title={lorem5}
|
|
107
120
|
subtitle={lorem10}
|
|
108
121
|
media={MEDIA.avatarSingle}
|
|
109
122
|
control={CONTROLS.switch}
|
|
110
|
-
prompt={
|
|
123
|
+
prompt={
|
|
124
|
+
<ListItem.Prompt sentiment={Sentiments.NEGATIVE}>
|
|
125
|
+
This is a negative prompt.
|
|
126
|
+
</ListItem.Prompt>
|
|
127
|
+
}
|
|
128
|
+
/>
|
|
129
|
+
</List>
|
|
130
|
+
),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* While all main sentiments (`warning`, `negative`, `positive` and `neutral`) are associated with a
|
|
135
|
+
* default `StatusIcon`s, we also allow for Icon overrides to bring the prompt's visual language
|
|
136
|
+
* closer to the prompt's content. <br /><br />
|
|
137
|
+
* It's also possible to override the default StatusIcon's accessible name announced by screen
|
|
138
|
+
* readers via `mediaLabel` prop, which is especially useful for the `proposition` sentiment.
|
|
139
|
+
* <br /><br />
|
|
140
|
+
* **NB**: If you're setting a label on a custom Icon, the accessible name should be provided via
|
|
141
|
+
* Icon's `title` prop instead.
|
|
142
|
+
*/
|
|
143
|
+
export const IconOverrides: Story = {
|
|
144
|
+
parameters: {
|
|
145
|
+
controls: { disable: true },
|
|
146
|
+
actions: { disable: true },
|
|
147
|
+
a11y: { disable: true },
|
|
148
|
+
knobs: { disable: true },
|
|
149
|
+
},
|
|
150
|
+
render: (args) => (
|
|
151
|
+
<List>
|
|
152
|
+
<ListItem
|
|
153
|
+
title={lorem5}
|
|
154
|
+
subtitle={lorem10}
|
|
155
|
+
media={MEDIA.avatarSingle}
|
|
156
|
+
control={CONTROLS.switch}
|
|
157
|
+
prompt={
|
|
158
|
+
<ListItem.Prompt sentiment={Sentiments.WARNING} mediaLabel="Processing: ">
|
|
159
|
+
This prompt uses default Icon, but with a custom label for screen readers.
|
|
160
|
+
</ListItem.Prompt>
|
|
161
|
+
}
|
|
162
|
+
/>
|
|
163
|
+
<ListItem
|
|
164
|
+
title={lorem5}
|
|
165
|
+
subtitle={lorem10}
|
|
166
|
+
media={MEDIA.avatarSingle}
|
|
167
|
+
control={CONTROLS.switch}
|
|
168
|
+
prompt={
|
|
169
|
+
<ListItem.Prompt sentiment={Sentiments.WARNING} media={<Clock title="Processing: " />}>
|
|
170
|
+
This prompt uses custom Icon with a custom label for screen readers.
|
|
171
|
+
</ListItem.Prompt>
|
|
172
|
+
}
|
|
111
173
|
/>
|
|
112
174
|
</List>
|
|
113
175
|
),
|
|
@@ -135,13 +197,13 @@ export const Interactivity: Story = {
|
|
|
135
197
|
media={MEDIA.avatarSingle}
|
|
136
198
|
control={CONTROLS.switch}
|
|
137
199
|
prompt={
|
|
138
|
-
<Prompt sentiment={args.sentiment}>
|
|
200
|
+
<ListItem.Prompt sentiment={args.sentiment}>
|
|
139
201
|
This prompt includes a{' '}
|
|
140
202
|
<Link href="https://wise.com" target="_blank" rel="noreferrer">
|
|
141
203
|
link to some resource
|
|
142
204
|
</Link>{' '}
|
|
143
205
|
to help the user in their journey.
|
|
144
|
-
</Prompt>
|
|
206
|
+
</ListItem.Prompt>
|
|
145
207
|
}
|
|
146
208
|
/>
|
|
147
209
|
|
|
@@ -151,13 +213,13 @@ export const Interactivity: Story = {
|
|
|
151
213
|
media={MEDIA.avatarSingle}
|
|
152
214
|
control={CONTROLS.switch}
|
|
153
215
|
prompt={
|
|
154
|
-
<Prompt sentiment={args.sentiment}>
|
|
216
|
+
<ListItem.Prompt sentiment={args.sentiment}>
|
|
155
217
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
|
|
156
218
|
This prompt includes an <Link onClick={action('inline button')}>
|
|
157
219
|
inline button
|
|
158
220
|
</Link>{' '}
|
|
159
221
|
than can e.g. trigger a modal.
|
|
160
|
-
</Prompt>
|
|
222
|
+
</ListItem.Prompt>
|
|
161
223
|
}
|
|
162
224
|
/>
|
|
163
225
|
</List>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Clock } from '@transferwise/icons';
|
|
1
2
|
import { mockMatchMedia, render, screen } from '../../test-utils';
|
|
2
3
|
import { Sentiment } from '../../common';
|
|
3
4
|
import { ListItem } from '../ListItem';
|
|
@@ -18,6 +19,8 @@ describe('ListItem.Prompt', () => {
|
|
|
18
19
|
});
|
|
19
20
|
|
|
20
21
|
describe('render icon', () => {
|
|
22
|
+
const customLabel = 'Custom icon label';
|
|
23
|
+
|
|
21
24
|
it.each([
|
|
22
25
|
[Sentiment.NEUTRAL, 'info-icon'],
|
|
23
26
|
[Sentiment.POSITIVE, 'check-icon'],
|
|
@@ -32,6 +35,34 @@ describe('ListItem.Prompt', () => {
|
|
|
32
35
|
);
|
|
33
36
|
expect(screen.getByTestId(iconId)).toBeInTheDocument();
|
|
34
37
|
});
|
|
38
|
+
|
|
39
|
+
it('should accept accessible name override for a status icon', () => {
|
|
40
|
+
render(
|
|
41
|
+
<ListItem
|
|
42
|
+
title="Test Title"
|
|
43
|
+
prompt={
|
|
44
|
+
<ListItem.Prompt sentiment={Sentiment.NEGATIVE} mediaLabel={customLabel}>
|
|
45
|
+
Message
|
|
46
|
+
</ListItem.Prompt>
|
|
47
|
+
}
|
|
48
|
+
/>,
|
|
49
|
+
);
|
|
50
|
+
expect(screen.getByLabelText(customLabel)).toBeInTheDocument();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should accept icon accessible name override', () => {
|
|
54
|
+
render(
|
|
55
|
+
<ListItem
|
|
56
|
+
title="Test Title"
|
|
57
|
+
prompt={
|
|
58
|
+
<ListItem.Prompt sentiment={Sentiment.NEGATIVE} media={<Clock title={customLabel} />}>
|
|
59
|
+
Message
|
|
60
|
+
</ListItem.Prompt>
|
|
61
|
+
}
|
|
62
|
+
/>,
|
|
63
|
+
);
|
|
64
|
+
expect(screen.getByLabelText(customLabel)).toBeInTheDocument();
|
|
65
|
+
});
|
|
35
66
|
});
|
|
36
67
|
|
|
37
68
|
describe('muted state', () => {
|
|
@@ -3,7 +3,10 @@ import { Sentiment } from '../../common';
|
|
|
3
3
|
import { ListItemContext, type ListItemContextData } from '../ListItemContext';
|
|
4
4
|
import { InlinePrompt, type InlinePromptProps } from '../../prompt';
|
|
5
5
|
|
|
6
|
-
export type ListItemPromptProps = Pick<
|
|
6
|
+
export type ListItemPromptProps = Pick<
|
|
7
|
+
InlinePromptProps,
|
|
8
|
+
'children' | 'sentiment' | 'mediaLabel' | 'media' | 'loading'
|
|
9
|
+
>;
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
12
|
* This component allows for rendering an Inline Prompt. <br />
|
|
@@ -15,6 +18,8 @@ export const Prompt = ({
|
|
|
15
18
|
sentiment = Sentiment.NEUTRAL,
|
|
16
19
|
mediaLabel,
|
|
17
20
|
children,
|
|
21
|
+
media,
|
|
22
|
+
loading,
|
|
18
23
|
}: ListItemPromptProps) => {
|
|
19
24
|
const { ids, props } = useContext<ListItemContextData>(ListItemContext);
|
|
20
25
|
const isLongLivedMuted = props.disabled && Boolean(props.disabledPromptMessage);
|
|
@@ -23,6 +28,8 @@ export const Prompt = ({
|
|
|
23
28
|
<InlinePrompt
|
|
24
29
|
id={ids.prompt}
|
|
25
30
|
sentiment={sentiment}
|
|
31
|
+
media={media}
|
|
32
|
+
loading={loading}
|
|
26
33
|
mediaLabel={mediaLabel}
|
|
27
34
|
muted={isLongLivedMuted}
|
|
28
35
|
className="wds-list-item-prompt"
|
|
@@ -33,4 +40,3 @@ export const Prompt = ({
|
|
|
33
40
|
};
|
|
34
41
|
|
|
35
42
|
Prompt.displayName = 'ListItem.Prompt';
|
|
36
|
-
export default Prompt;
|
|
@@ -252,7 +252,6 @@ const getPropsForPreview = (args: PreviewStoryArgs): [ListItemProps, Partial<Lis
|
|
|
252
252
|
};
|
|
253
253
|
|
|
254
254
|
export const Playground: StoryObj<PreviewStoryArgs> = {
|
|
255
|
-
tags: ['!autodocs'],
|
|
256
255
|
render: (args: PreviewStoryArgs) => {
|
|
257
256
|
const [props, previewProps] = getPropsForPreview(args);
|
|
258
257
|
|
package/src/logo/Logo.story.tsx
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
2
|
|
|
3
|
-
import Logo from '.';
|
|
3
|
+
import Logo, { LogoType } from '.';
|
|
4
|
+
import { withVariantConfig } from '../../.storybook/helpers';
|
|
4
5
|
|
|
5
6
|
export default {
|
|
6
7
|
component: Logo,
|
|
7
8
|
title: 'Content/Logo',
|
|
8
|
-
render: (
|
|
9
|
+
render: () => {
|
|
9
10
|
return (
|
|
10
|
-
|
|
11
|
-
<
|
|
12
|
-
|
|
11
|
+
<>
|
|
12
|
+
<div>
|
|
13
|
+
{Object.values(LogoType).map((type) => (
|
|
14
|
+
<div key={type} className="m-a-2">
|
|
15
|
+
<Logo type={type} />
|
|
16
|
+
</div>
|
|
17
|
+
))}
|
|
18
|
+
</div>
|
|
19
|
+
<div className="bg--dark">
|
|
20
|
+
{Object.values(LogoType).map((type) => (
|
|
21
|
+
<div key={type} className="m-a-2">
|
|
22
|
+
<Logo type={type} inverse />
|
|
23
|
+
</div>
|
|
24
|
+
))}
|
|
25
|
+
</div>
|
|
26
|
+
</>
|
|
13
27
|
);
|
|
14
28
|
},
|
|
15
29
|
} satisfies Meta<typeof Logo>;
|
|
@@ -22,3 +36,8 @@ export const Basic: Story = {
|
|
|
22
36
|
inverse: false,
|
|
23
37
|
},
|
|
24
38
|
};
|
|
39
|
+
|
|
40
|
+
export const Mobile: Story = {
|
|
41
|
+
...Basic,
|
|
42
|
+
...withVariantConfig(['mobile']),
|
|
43
|
+
};
|
package/src/main.css
CHANGED
|
@@ -304,18 +304,18 @@
|
|
|
304
304
|
--color-sentiment-interactive-primary: #454745;
|
|
305
305
|
--color-sentiment-interactive-primary-hover: #353635;
|
|
306
306
|
--color-sentiment-interactive-primary-active: #232423;
|
|
307
|
-
--color-sentiment-interactive-secondary:
|
|
308
|
-
--color-sentiment-interactive-secondary-hover:
|
|
309
|
-
--color-sentiment-interactive-secondary-active:
|
|
310
|
-
--color-sentiment-interactive-secondary-neutral:
|
|
311
|
-
--color-sentiment-interactive-secondary-neutral-hover:
|
|
312
|
-
--color-sentiment-interactive-secondary-neutral-active:
|
|
307
|
+
--color-sentiment-interactive-secondary: rgba(62, 59, 7, 0.07);
|
|
308
|
+
--color-sentiment-interactive-secondary-hover: rgba(62, 59, 7, 0.12);
|
|
309
|
+
--color-sentiment-interactive-secondary-active: rgba(62, 59, 7, 0.17);
|
|
310
|
+
--color-sentiment-interactive-secondary-neutral: rgba(62, 59, 7, 0.07);
|
|
311
|
+
--color-sentiment-interactive-secondary-neutral-hover: rgba(62, 59, 7, 0.12);
|
|
312
|
+
--color-sentiment-interactive-secondary-neutral-active: rgba(62, 59, 7, 0.17);
|
|
313
313
|
--color-sentiment-interactive-control: #F1F1ED;
|
|
314
314
|
--color-sentiment-interactive-control-hover: #E7E7E1;
|
|
315
315
|
--color-sentiment-interactive-control-active: #DFDED5;
|
|
316
|
-
--color-sentiment-background-surface:
|
|
317
|
-
--color-sentiment-background-surface-hover:
|
|
318
|
-
--color-sentiment-background-surface-active:
|
|
316
|
+
--color-sentiment-background-surface: rgba(62, 59, 7, 0.07);
|
|
317
|
+
--color-sentiment-background-surface-hover: rgba(62, 59, 7, 0.12);
|
|
318
|
+
--color-sentiment-background-surface-active: rgba(62, 59, 7, 0.17);
|
|
319
319
|
}
|
|
320
320
|
.np-theme-personal .wds-sentiment-surface-neutral-elevated,
|
|
321
321
|
.np-theme-business .wds-sentiment-surface-neutral-elevated,
|
|
@@ -331,9 +331,9 @@
|
|
|
331
331
|
--color-sentiment-interactive-secondary: #454745;
|
|
332
332
|
--color-sentiment-interactive-secondary-hover: #353635;
|
|
333
333
|
--color-sentiment-interactive-secondary-active: #232423;
|
|
334
|
-
--color-sentiment-interactive-secondary-neutral: #
|
|
335
|
-
--color-sentiment-interactive-secondary-neutral-hover: #
|
|
336
|
-
--color-sentiment-interactive-secondary-neutral-active: #
|
|
334
|
+
--color-sentiment-interactive-secondary-neutral: #585958;
|
|
335
|
+
--color-sentiment-interactive-secondary-neutral-hover: #6A6C6A;
|
|
336
|
+
--color-sentiment-interactive-secondary-neutral-active: #7D7E7D;
|
|
337
337
|
--color-sentiment-interactive-control: #454745;
|
|
338
338
|
--color-sentiment-interactive-control-hover: #353635;
|
|
339
339
|
--color-sentiment-interactive-control-active: #232423;
|
|
@@ -352,18 +352,18 @@
|
|
|
352
352
|
--color-sentiment-interactive-primary: #F1F1ED;
|
|
353
353
|
--color-sentiment-interactive-primary-hover: #E7E7E1;
|
|
354
354
|
--color-sentiment-interactive-primary-active: #DFDED5;
|
|
355
|
-
--color-sentiment-interactive-secondary:
|
|
356
|
-
--color-sentiment-interactive-secondary-hover:
|
|
357
|
-
--color-sentiment-interactive-secondary-active:
|
|
358
|
-
--color-sentiment-interactive-secondary-neutral:
|
|
359
|
-
--color-sentiment-interactive-secondary-neutral-hover:
|
|
360
|
-
--color-sentiment-interactive-secondary-neutral-active:
|
|
355
|
+
--color-sentiment-interactive-secondary: rgba(255, 255, 255, 0.1);
|
|
356
|
+
--color-sentiment-interactive-secondary-hover: rgba(255, 255, 255, 0.2);
|
|
357
|
+
--color-sentiment-interactive-secondary-active: rgba(255, 255, 255, 0.3);
|
|
358
|
+
--color-sentiment-interactive-secondary-neutral: rgba(255, 255, 255, 0.1);
|
|
359
|
+
--color-sentiment-interactive-secondary-neutral-hover: rgba(255, 255, 255, 0.2);
|
|
360
|
+
--color-sentiment-interactive-secondary-neutral-active: rgba(255, 255, 255, 0.3);
|
|
361
361
|
--color-sentiment-interactive-control: #2A2C29;
|
|
362
362
|
--color-sentiment-interactive-control-hover: #414441;
|
|
363
363
|
--color-sentiment-interactive-control-active: #595B58;
|
|
364
|
-
--color-sentiment-background-surface:
|
|
365
|
-
--color-sentiment-background-surface-hover:
|
|
366
|
-
--color-sentiment-background-surface-active:
|
|
364
|
+
--color-sentiment-background-surface: rgba(255, 255, 255, 0.1);
|
|
365
|
+
--color-sentiment-background-surface-hover: rgba(255, 255, 255, 0.2);
|
|
366
|
+
--color-sentiment-background-surface-active: rgba(255, 255, 255, 0.3);
|
|
367
367
|
}
|
|
368
368
|
.np-theme-personal--dark .wds-sentiment-surface-neutral-elevated,
|
|
369
369
|
.np-theme-business--dark .wds-sentiment-surface-neutral-elevated,
|
|
@@ -5346,6 +5346,7 @@ html:not([dir="rtl"]) .np-navigation-option {
|
|
|
5346
5346
|
padding: var(--Prompt-padding, var(--padding-x-small));
|
|
5347
5347
|
text-align: left;
|
|
5348
5348
|
word-break: break-word;
|
|
5349
|
+
width: 100%;
|
|
5349
5350
|
}
|
|
5350
5351
|
.wds-prompt__content-wrapper {
|
|
5351
5352
|
display: grid;
|
|
@@ -5405,8 +5406,9 @@ html:not([dir="rtl"]) .np-navigation-option {
|
|
|
5405
5406
|
.wds-inline-prompt:has(button):active {
|
|
5406
5407
|
background-color: var(--color-sentiment-background-surface-active);
|
|
5407
5408
|
}
|
|
5408
|
-
.wds-inline-prompt--
|
|
5409
|
-
width:
|
|
5409
|
+
.wds-inline-prompt--auto-width {
|
|
5410
|
+
width: auto;
|
|
5411
|
+
width: initial;
|
|
5410
5412
|
}
|
|
5411
5413
|
.wds-inline-prompt--muted {
|
|
5412
5414
|
opacity: 0.93;
|
|
@@ -5442,13 +5444,16 @@ html:not([dir="rtl"]) .np-navigation-option {
|
|
|
5442
5444
|
stroke: currentColor;
|
|
5443
5445
|
}
|
|
5444
5446
|
.wds-info-prompt {
|
|
5447
|
+
--Prompt-border-radius: var(--radius-medium);
|
|
5445
5448
|
--Prompt-gap: var(--size-8);
|
|
5446
|
-
--Prompt-padding:
|
|
5449
|
+
--Prompt-padding: var(--size-12);
|
|
5447
5450
|
}
|
|
5448
5451
|
.wds-info-prompt__content {
|
|
5449
5452
|
display: flex;
|
|
5450
5453
|
flex-direction: column;
|
|
5451
5454
|
justify-content: center;
|
|
5455
|
+
max-width: calc(48px * 10);
|
|
5456
|
+
max-width: calc(var(--size-48) * 10);
|
|
5452
5457
|
}
|
|
5453
5458
|
.wds-info-prompt__content:has(.wds-info-prompt__title) {
|
|
5454
5459
|
justify-content: flex-start;
|
|
@@ -5457,17 +5462,16 @@ html:not([dir="rtl"]) .np-navigation-option {
|
|
|
5457
5462
|
.wds-info-prompt__title,
|
|
5458
5463
|
.wds-info-prompt__description {
|
|
5459
5464
|
display: block;
|
|
5460
|
-
color: var(--color-sentiment-primary);
|
|
5461
5465
|
}
|
|
5462
5466
|
.wds-info-prompt__action {
|
|
5467
|
+
align-self: flex-start;
|
|
5463
5468
|
margin-top: var(--Prompt-gap);
|
|
5464
5469
|
}
|
|
5465
|
-
.wds-info-prompt__media {
|
|
5466
|
-
display: flex;
|
|
5467
|
-
}
|
|
5468
5470
|
.wds-info-prompt__media svg {
|
|
5469
5471
|
width: 24px;
|
|
5472
|
+
width: var(--size-24);
|
|
5470
5473
|
height: 24px;
|
|
5474
|
+
height: var(--size-24);
|
|
5471
5475
|
}
|
|
5472
5476
|
.wds-info-prompt .wds-prompt__media-wrapper {
|
|
5473
5477
|
padding: 0;
|
|
@@ -7484,3 +7488,7 @@ html:not([dir="rtl"]) .np-navigation-option {
|
|
|
7484
7488
|
min-width: fit-content;
|
|
7485
7489
|
}
|
|
7486
7490
|
}
|
|
7491
|
+
.wds-action-prompt__content {
|
|
7492
|
+
max-width: calc(48px * 10);
|
|
7493
|
+
max-width: calc(var(--size-48) * 10);
|
|
7494
|
+
}
|
|
@@ -11,9 +11,6 @@ describe('OverlayHeader', () => {
|
|
|
11
11
|
logo: <img alt="logo_desktop" src="img_desktop" width="138" height="24" />,
|
|
12
12
|
onClose: jest.fn(),
|
|
13
13
|
};
|
|
14
|
-
it('renders as expected', () => {
|
|
15
|
-
expect(render(<OverlayHeader {...props} />).container).toMatchSnapshot();
|
|
16
|
-
});
|
|
17
14
|
|
|
18
15
|
it('renders separator only if avatar and onClose are provided', () => {
|
|
19
16
|
const { container } = render(<OverlayHeader {...props} />);
|
|
@@ -22,19 +22,6 @@ describe('Popover', () => {
|
|
|
22
22
|
let rerender;
|
|
23
23
|
|
|
24
24
|
describe('on desktop', () => {
|
|
25
|
-
it('renders when is open', async () => {
|
|
26
|
-
render(
|
|
27
|
-
<Popover {...props}>
|
|
28
|
-
<button type="button">Open</button>
|
|
29
|
-
</Popover>,
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
await userEvent.click(screen.getByText('Open'));
|
|
33
|
-
|
|
34
|
-
await waitForPanel();
|
|
35
|
-
expect(getPanel()).toMatchSnapshot();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
25
|
describe('title', () => {
|
|
39
26
|
it('renders title', async () => {
|
|
40
27
|
({ container, rerender } = render(
|
|
@@ -122,18 +109,6 @@ describe('Popover', () => {
|
|
|
122
109
|
global.innerWidth = Breakpoint.SMALL - 1;
|
|
123
110
|
});
|
|
124
111
|
|
|
125
|
-
it('renders when is open', async () => {
|
|
126
|
-
({ container } = render(
|
|
127
|
-
<Popover {...props}>
|
|
128
|
-
<button type="button">Open</button>
|
|
129
|
-
</Popover>,
|
|
130
|
-
));
|
|
131
|
-
|
|
132
|
-
await userEvent.click(screen.getByText('Open'));
|
|
133
|
-
|
|
134
|
-
expect(container).toMatchSnapshot();
|
|
135
|
-
});
|
|
136
|
-
|
|
137
112
|
it('renders BottomSheet onClick', async () => {
|
|
138
113
|
render(
|
|
139
114
|
<Popover {...props}>
|
|
@@ -26,12 +26,6 @@ describe('PromoCard', () => {
|
|
|
26
26
|
};
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
it('matches snapshot', () => {
|
|
30
|
-
const { container } = render(<PromoCard {...defaultProps} id="test-promo-card" />);
|
|
31
|
-
|
|
32
|
-
expect(container.firstChild).toMatchSnapshot();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
29
|
it('renders', () => {
|
|
36
30
|
const props = {
|
|
37
31
|
...defaultProps,
|
|
@@ -41,11 +41,6 @@ describe('PromoCardGroup', () => {
|
|
|
41
41
|
};
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
it('matches snapshot', () => {
|
|
45
|
-
const { container } = render(<PromoCardGroup {...defaultProps} />);
|
|
46
|
-
expect(container.firstChild).toMatchSnapshot();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
44
|
it('renders', () => {
|
|
50
45
|
const props = {
|
|
51
46
|
...defaultProps,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Meta, Canvas, Source } from '@storybook/addon-docs/blocks';
|
|
2
2
|
|
|
3
|
-
<Meta title="Prompts/ActionPrompt/Accessibility" />
|
|
3
|
+
<Meta title="Prompts/ActionPrompt/Accessibility" tags={['new']} />
|
|
4
4
|
|
|
5
5
|
# Accessibility
|
|
6
6
|
|
|
@@ -31,23 +31,7 @@ If you want to provide a custom label for screen readers, you can use the `aria-
|
|
|
31
31
|
|
|
32
32
|
## Media
|
|
33
33
|
|
|
34
|
-
You can use
|
|
35
|
-
|
|
36
|
-
<Source
|
|
37
|
-
dark
|
|
38
|
-
code={`
|
|
39
|
-
<ActionPrompt
|
|
40
|
-
media={{
|
|
41
|
-
'aria-hidden': true,
|
|
42
|
-
avatar: { asset: <People /> },
|
|
43
|
-
}}
|
|
44
|
-
title="Sync contacts for a faster experience"
|
|
45
|
-
...
|
|
46
|
-
/>
|
|
47
|
-
`}
|
|
48
|
-
/>
|
|
49
|
-
|
|
50
|
-
You can also use `aria-label` on media assets to provide a custom label for screen readers.
|
|
34
|
+
You can use `aria-label` on media assets to provide a custom label for screen readers.
|
|
51
35
|
|
|
52
36
|
<Source
|
|
53
37
|
dark
|