@transferwise/components 46.127.1 → 46.128.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/Button/ListItemButton.js +4 -3
- package/build/listItem/Button/ListItemButton.js.map +1 -1
- package/build/listItem/Button/ListItemButton.mjs +5 -4
- package/build/listItem/Button/ListItemButton.mjs.map +1 -1
- package/build/main.css +15 -7
- 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 +15 -7
- 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/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/Button/ListItemButton.d.ts +7 -4
- package/build/types/listItem/Button/ListItemButton.d.ts.map +1 -1
- package/build/types/listItem/ListItem.d.ts +4 -4
- 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 +5 -5
- 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/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/Button/ListItemButton.tsx +30 -28
- package/src/listItem/_stories/ListItem.story.tsx +0 -1
- package/src/main.css +15 -7
- 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 +25 -30
- package/src/prompt/InlinePrompt/InlinePrompt.test.story.tsx +21 -0
- package/src/prompt/InlinePrompt/InlinePrompt.test.tsx +10 -3
- 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.docs.mdx +1 -1
- package/src/sentimentSurface/SentimentSurface.story.tsx +1 -1
- package/src/sentimentSurface/SentimentSurface.test.story.tsx +1 -1
|
@@ -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
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useContext } from 'react';
|
|
1
|
+
import { useContext, forwardRef } from 'react';
|
|
2
2
|
import { clsx } from 'clsx';
|
|
3
3
|
import ButtonComp, { type ButtonAddonIcon, type NewButtonProps } from '../../button';
|
|
4
4
|
import { useListItemControl } from '../useListItemControl';
|
|
@@ -22,35 +22,37 @@ export type ListItemButtonProps = Omit<
|
|
|
22
22
|
* <br />
|
|
23
23
|
* Please refer to the [Design documentation](https://wise.design/components/list-item---button) for details.
|
|
24
24
|
*/
|
|
25
|
-
export const Button = (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
export const Button = forwardRef<HTMLButtonElement, ListItemButtonProps>(
|
|
26
|
+
(
|
|
27
|
+
{ priority = 'secondary-neutral', partiallyInteractive, ...props }: ListItemButtonProps,
|
|
28
|
+
ref,
|
|
29
|
+
) => {
|
|
30
|
+
const { baseItemProps } = useListItemControl('button', { partiallyInteractive, ...props });
|
|
31
|
+
const { ids, describedByIds } = useContext(ListItemContext);
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
const commonProps = {
|
|
34
|
+
...props,
|
|
35
|
+
className: clsx(
|
|
36
|
+
'wds-list-item-control',
|
|
37
|
+
!partiallyInteractive && props.href && 'wds-list-item-control_pseudo-element',
|
|
38
|
+
),
|
|
39
|
+
id: ids.control,
|
|
40
|
+
priority,
|
|
41
|
+
v2: true,
|
|
42
|
+
size: 'sm',
|
|
43
|
+
disabled: baseItemProps.disabled,
|
|
44
|
+
};
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
const buttonContentId = props.href || partiallyInteractive ? '' : `${ids.control}_content`;
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
return (
|
|
49
|
+
<ButtonComp
|
|
50
|
+
ref={ref}
|
|
51
|
+
aria-describedby={`${buttonContentId} ${describedByIds}`}
|
|
52
|
+
{...(commonProps as NewButtonProps)}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
},
|
|
56
|
+
);
|
|
55
57
|
|
|
56
58
|
Button.displayName = 'ListItem.Button';
|
|
@@ -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/main.css
CHANGED
|
@@ -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
|
+
}
|
|
@@ -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
|