@transferwise/components 0.0.0-experimental-8d46704 → 0.0.0-experimental-1067f73
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/inputs/SelectInput.js +33 -51
- package/build/inputs/SelectInput.js.map +1 -1
- package/build/inputs/SelectInput.mjs +34 -52
- package/build/inputs/SelectInput.mjs.map +1 -1
- package/build/main.css +0 -3
- package/build/stepper/Stepper.js +5 -2
- package/build/stepper/Stepper.js.map +1 -1
- package/build/stepper/Stepper.mjs +5 -2
- package/build/stepper/Stepper.mjs.map +1 -1
- package/build/styles/inputs/SelectInput.css +0 -3
- package/build/styles/main.css +0 -3
- package/build/types/inputs/SelectInput.d.ts.map +1 -1
- package/build/types/stepper/Stepper.d.ts +4 -0
- package/build/types/stepper/Stepper.d.ts.map +1 -1
- package/package.json +4 -5
- package/src/flowNavigation/__snapshots__/FlowNavigation.spec.js.snap +4 -4
- package/src/inputs/SelectInput.css +0 -3
- package/src/inputs/SelectInput.less +0 -4
- package/src/inputs/SelectInput.story.tsx +0 -20
- package/src/inputs/SelectInput.tsx +40 -82
- package/src/main.css +0 -3
- package/src/stepper/Stepper.less +2 -2
- package/src/stepper/Stepper.story.tsx +49 -0
- package/src/stepper/Stepper.tsx +7 -3
- package/src/stepper/Stepper.story.js +0 -40
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
createContext,
|
|
7
7
|
forwardRef,
|
|
8
8
|
useContext,
|
|
9
|
-
useDeferredValue,
|
|
10
9
|
useEffect,
|
|
11
10
|
useId,
|
|
12
11
|
useMemo,
|
|
@@ -14,7 +13,6 @@ import {
|
|
|
14
13
|
useState,
|
|
15
14
|
} from 'react';
|
|
16
15
|
import { useIntl } from 'react-intl';
|
|
17
|
-
import { VList } from 'virtua';
|
|
18
16
|
|
|
19
17
|
import { useEffectEvent } from '../common/hooks/useEffectEvent';
|
|
20
18
|
import { useScreenSize } from '../common/hooks/useScreenSize';
|
|
@@ -31,8 +29,6 @@ import { InputGroup } from './InputGroup';
|
|
|
31
29
|
import { SearchInput } from './SearchInput';
|
|
32
30
|
import messages from './SelectInput.messages';
|
|
33
31
|
|
|
34
|
-
const MAX_ITEMS_WITHOUT_VIRTUALIZATION = 50;
|
|
35
|
-
|
|
36
32
|
function searchableString(value: string) {
|
|
37
33
|
return value.trim().replace(/\s+/gu, ' ').normalize('NFKC').toLowerCase();
|
|
38
34
|
}
|
|
@@ -44,7 +40,7 @@ function inferSearchableStrings(value: unknown) {
|
|
|
44
40
|
|
|
45
41
|
if (typeof value === 'object' && value != null) {
|
|
46
42
|
return Object.values(value)
|
|
47
|
-
.filter((innerValue) => typeof innerValue === 'string')
|
|
43
|
+
.filter((innerValue): innerValue is string => typeof innerValue === 'string')
|
|
48
44
|
.map((innerValue) => searchableString(innerValue));
|
|
49
45
|
}
|
|
50
46
|
|
|
@@ -116,23 +112,20 @@ function dedupeSelectInputItems<T>(
|
|
|
116
112
|
});
|
|
117
113
|
}
|
|
118
114
|
|
|
119
|
-
function
|
|
115
|
+
function filterSelectInputOptionItem<T>(item: SelectInputOptionItem<T>, needle: string) {
|
|
120
116
|
return inferSearchableStrings(item.filterMatchers ?? item.value).some((haystack) =>
|
|
121
117
|
haystack.includes(needle),
|
|
122
118
|
);
|
|
123
119
|
}
|
|
124
120
|
|
|
125
|
-
function filterSelectInputItems<T>(
|
|
126
|
-
items: readonly SelectInputItem<T>[],
|
|
127
|
-
predicate: (item: SelectInputOptionItem<T>) => boolean,
|
|
128
|
-
) {
|
|
121
|
+
function filterSelectInputItems<T>(items: readonly SelectInputItem<T>[], needle: string) {
|
|
129
122
|
return items.filter((item) => {
|
|
130
123
|
switch (item.type) {
|
|
131
124
|
case 'option': {
|
|
132
|
-
return
|
|
125
|
+
return filterSelectInputOptionItem(item, needle);
|
|
133
126
|
}
|
|
134
127
|
case 'group': {
|
|
135
|
-
return item.options.some((option) =>
|
|
128
|
+
return item.options.some((option) => filterSelectInputOptionItem(option, needle));
|
|
136
129
|
}
|
|
137
130
|
default:
|
|
138
131
|
}
|
|
@@ -278,15 +271,12 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
278
271
|
}, [handleClose, open]);
|
|
279
272
|
|
|
280
273
|
const [filterQuery, _setFilterQuery] = useState('');
|
|
281
|
-
const deferredFilterQuery = useDeferredValue(filterQuery);
|
|
282
274
|
const setFilterQuery = useEffectEvent((query: string) => {
|
|
283
275
|
_setFilterQuery(query);
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
});
|
|
289
|
-
}
|
|
276
|
+
onFilterChange({
|
|
277
|
+
query,
|
|
278
|
+
queryNormalized: query ? searchableString(query) : null,
|
|
279
|
+
});
|
|
290
280
|
});
|
|
291
281
|
|
|
292
282
|
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
|
@@ -304,7 +294,9 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
304
294
|
multiple={multiple}
|
|
305
295
|
defaultValue={defaultValue}
|
|
306
296
|
value={controlledValue}
|
|
307
|
-
|
|
297
|
+
// TODO: Remove assertion when upgrading TypeScript to v5
|
|
298
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
299
|
+
by={compareValues as any}
|
|
308
300
|
disabled={disabled}
|
|
309
301
|
onChange={
|
|
310
302
|
((value) => {
|
|
@@ -357,8 +349,8 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
357
349
|
content: !placeholderShown ? (
|
|
358
350
|
<SelectInputOptionContentWithinTriggerContext.Provider value>
|
|
359
351
|
{multiple && Array.isArray(value)
|
|
360
|
-
?
|
|
361
|
-
.map((option) => renderValue(option, true))
|
|
352
|
+
? value
|
|
353
|
+
.map((option: NonNullable<T>) => renderValue(option, true))
|
|
362
354
|
.filter((node) => node != null)
|
|
363
355
|
.join(', ')
|
|
364
356
|
: renderValue(value as NonNullable<T>, true)}
|
|
@@ -387,7 +379,9 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
387
379
|
setOpen(false);
|
|
388
380
|
}}
|
|
389
381
|
onCloseEnd={() => {
|
|
390
|
-
|
|
382
|
+
if (filterQuery !== '') {
|
|
383
|
+
setFilterQuery('');
|
|
384
|
+
}
|
|
391
385
|
}}
|
|
392
386
|
>
|
|
393
387
|
<SelectInputOptions
|
|
@@ -398,8 +392,7 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
398
392
|
filterPlaceholder={filterPlaceholder}
|
|
399
393
|
searchInputRef={searchInputRef}
|
|
400
394
|
listboxRef={listboxRef}
|
|
401
|
-
|
|
402
|
-
filterQuery={deferredFilterQuery}
|
|
395
|
+
filterQuery={filterQuery}
|
|
403
396
|
onFilterChange={setFilterQuery}
|
|
404
397
|
/>
|
|
405
398
|
</OptionsOverlay>
|
|
@@ -488,7 +481,6 @@ interface SelectInputOptionsProps<T = string>
|
|
|
488
481
|
> {
|
|
489
482
|
searchInputRef: React.MutableRefObject<HTMLInputElement | null>;
|
|
490
483
|
listboxRef: React.MutableRefObject<HTMLDivElement | null>;
|
|
491
|
-
value: readonly T[] | T;
|
|
492
484
|
filterQuery: string;
|
|
493
485
|
onFilterChange: (query: string) => void;
|
|
494
486
|
}
|
|
@@ -501,7 +493,6 @@ function SelectInputOptions<T = string>({
|
|
|
501
493
|
filterPlaceholder,
|
|
502
494
|
searchInputRef,
|
|
503
495
|
listboxRef,
|
|
504
|
-
value,
|
|
505
496
|
filterQuery,
|
|
506
497
|
onFilterChange,
|
|
507
498
|
}: SelectInputOptionsProps<T>) {
|
|
@@ -515,12 +506,7 @@ function SelectInputOptions<T = string>({
|
|
|
515
506
|
}
|
|
516
507
|
return undefined;
|
|
517
508
|
}, [filterQuery, filterable]);
|
|
518
|
-
|
|
519
|
-
const filteredItems =
|
|
520
|
-
needle != null
|
|
521
|
-
? filterSelectInputItems(items, (item) => selectInputOptionItemIncludesNeedle(item, needle))
|
|
522
|
-
: items;
|
|
523
|
-
const resultsEmpty = needle != null && filteredItems.length === 0;
|
|
509
|
+
const resultsEmpty = needle != null && filterSelectInputItems(items, needle).length === 0;
|
|
524
510
|
|
|
525
511
|
const listboxContainerRef = useRef<HTMLDivElement>(null);
|
|
526
512
|
useEffect(() => {
|
|
@@ -536,30 +522,6 @@ function SelectInputOptions<T = string>({
|
|
|
536
522
|
const statusId = useId();
|
|
537
523
|
const listboxId = useId();
|
|
538
524
|
|
|
539
|
-
const virtualized = filteredItems.length > MAX_ITEMS_WITHOUT_VIRTUALIZATION;
|
|
540
|
-
|
|
541
|
-
const values = useMemo(
|
|
542
|
-
() => new Set(Array.isArray(value) ? (value as readonly T[]) : [value as T]),
|
|
543
|
-
[value],
|
|
544
|
-
);
|
|
545
|
-
|
|
546
|
-
const listboxProps = {
|
|
547
|
-
id: listboxId,
|
|
548
|
-
role: 'listbox',
|
|
549
|
-
'aria-orientation': 'vertical',
|
|
550
|
-
tabIndex: 0,
|
|
551
|
-
className: 'np-select-input-listbox',
|
|
552
|
-
children: (needle != null ? dedupeSelectInputItems(items) : items).map((item, index) => (
|
|
553
|
-
<SelectInputItemView
|
|
554
|
-
// eslint-disable-next-line react/no-array-index-key
|
|
555
|
-
key={index}
|
|
556
|
-
item={item}
|
|
557
|
-
renderValue={renderValue}
|
|
558
|
-
needle={needle}
|
|
559
|
-
/>
|
|
560
|
-
)),
|
|
561
|
-
} as const;
|
|
562
|
-
|
|
563
525
|
return (
|
|
564
526
|
<ListboxBase.Options
|
|
565
527
|
as={SelectInputOptionsContainer}
|
|
@@ -587,7 +549,7 @@ function SelectInputOptions<T = string>({
|
|
|
587
549
|
ref={searchInputRef}
|
|
588
550
|
shape="rectangle"
|
|
589
551
|
placeholder={filterPlaceholder}
|
|
590
|
-
|
|
552
|
+
value={filterQuery}
|
|
591
553
|
aria-controls={listboxId}
|
|
592
554
|
aria-describedby={showStatus ? statusId : undefined}
|
|
593
555
|
onKeyDown={(event) => {
|
|
@@ -609,9 +571,7 @@ function SelectInputOptions<T = string>({
|
|
|
609
571
|
tabIndex={-1}
|
|
610
572
|
className={classNames(
|
|
611
573
|
'np-select-input-listbox-container',
|
|
612
|
-
|
|
613
|
-
needle == null &&
|
|
614
|
-
items.some((item) => item.type === 'group') &&
|
|
574
|
+
items.some((item) => item.type === 'group') &&
|
|
615
575
|
'np-select-input-listbox-container--has-group',
|
|
616
576
|
)}
|
|
617
577
|
>
|
|
@@ -622,23 +582,24 @@ function SelectInputOptions<T = string>({
|
|
|
622
582
|
</div>
|
|
623
583
|
) : null}
|
|
624
584
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
585
|
+
<div
|
|
586
|
+
ref={listboxRef}
|
|
587
|
+
id={listboxId}
|
|
588
|
+
role="listbox"
|
|
589
|
+
aria-orientation="vertical"
|
|
590
|
+
tabIndex={0}
|
|
591
|
+
className="np-select-input-listbox"
|
|
592
|
+
>
|
|
593
|
+
{(needle != null ? dedupeSelectInputItems(items) : items).map((item, index) => (
|
|
594
|
+
<SelectInputItemView
|
|
595
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
596
|
+
key={index}
|
|
597
|
+
item={item}
|
|
598
|
+
renderValue={renderValue}
|
|
599
|
+
needle={needle}
|
|
600
|
+
/>
|
|
601
|
+
))}
|
|
602
|
+
</div>
|
|
642
603
|
|
|
643
604
|
{renderFooter != null ? (
|
|
644
605
|
<footer className="np-select-input-footer">
|
|
@@ -678,10 +639,7 @@ function SelectInputItemView<T = string>({
|
|
|
678
639
|
}: SelectInputItemViewProps<T>) {
|
|
679
640
|
switch (item.type) {
|
|
680
641
|
case 'option': {
|
|
681
|
-
if (
|
|
682
|
-
item.value != null &&
|
|
683
|
-
(needle == null || selectInputOptionItemIncludesNeedle(item, needle))
|
|
684
|
-
) {
|
|
642
|
+
if (item.value != null && (needle == null || filterSelectInputOptionItem(item, needle))) {
|
|
685
643
|
return (
|
|
686
644
|
<SelectInputOption value={item.value} disabled={item.disabled}>
|
|
687
645
|
{renderValue(item.value, false)}
|
package/src/main.css
CHANGED
|
@@ -2655,9 +2655,6 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
2655
2655
|
height: auto;
|
|
2656
2656
|
}
|
|
2657
2657
|
}
|
|
2658
|
-
.np-select-input-listbox-container--virtualized {
|
|
2659
|
-
height: 100vh;
|
|
2660
|
-
}
|
|
2661
2658
|
.np-select-input-listbox-container--has-group {
|
|
2662
2659
|
scroll-padding-top: 32px;
|
|
2663
2660
|
scroll-padding-top: var(--size-32);
|
package/src/stepper/Stepper.less
CHANGED
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
|
|
80
80
|
.progress-bar {
|
|
81
81
|
.float(left);
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
-webkit-backface-visibility: hidden;
|
|
84
84
|
height: 100%;
|
|
85
85
|
background-color: var(--color-interactive-primary);
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
border-bottom-left-radius: 1px;
|
|
88
88
|
transition: width 0.6s ease-in-out;
|
|
89
89
|
will-change: width;
|
|
90
|
-
|
|
90
|
+
|
|
91
91
|
&::after {
|
|
92
92
|
.float(right);
|
|
93
93
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { fn } from '@storybook/test';
|
|
3
|
+
import Stepper from './Stepper';
|
|
4
|
+
|
|
5
|
+
const STEPS = [
|
|
6
|
+
{
|
|
7
|
+
label: 'One',
|
|
8
|
+
onClick: fn(),
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
label: 'Two',
|
|
12
|
+
hoverLabel: (
|
|
13
|
+
<>
|
|
14
|
+
<div>
|
|
15
|
+
<strong>Diana Jaramillo</strong>
|
|
16
|
+
</div>
|
|
17
|
+
dianajarm123@gmail.com
|
|
18
|
+
</>
|
|
19
|
+
),
|
|
20
|
+
onClick: fn(),
|
|
21
|
+
},
|
|
22
|
+
{ label: 'Recipient', onClick: fn() },
|
|
23
|
+
{ label: 'Smellification', onClick: fn() },
|
|
24
|
+
{ label: 'Battle', onClick: fn() },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
component: Stepper,
|
|
29
|
+
title: 'Navigation/Stepper',
|
|
30
|
+
argTypes: {
|
|
31
|
+
activeStep: {
|
|
32
|
+
control: 'radio',
|
|
33
|
+
options: [...Array(STEPS.length).keys()],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
tags: ['autodocs'],
|
|
37
|
+
} satisfies Meta<typeof Stepper>;
|
|
38
|
+
|
|
39
|
+
type Story = StoryObj<typeof Stepper>;
|
|
40
|
+
|
|
41
|
+
export const Basic: Story = {
|
|
42
|
+
render: ({ activeStep, steps }) => {
|
|
43
|
+
return <Stepper activeStep={activeStep} steps={steps} />;
|
|
44
|
+
},
|
|
45
|
+
args: {
|
|
46
|
+
activeStep: 2,
|
|
47
|
+
steps: STEPS,
|
|
48
|
+
},
|
|
49
|
+
};
|
package/src/stepper/Stepper.tsx
CHANGED
|
@@ -23,7 +23,10 @@ export interface StepperProps {
|
|
|
23
23
|
className?: string;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* This component is considered user-unfriendly and inaccessible on its own and will likely be made internal in the future. Please use `FlowNavigation` instead.
|
|
28
|
+
* @see https://storybook.wise.design/?path=/story/navigation-flownavigation--variants
|
|
29
|
+
*/
|
|
27
30
|
const Stepper = ({ steps, activeStep = 0, className }: StepperProps) => {
|
|
28
31
|
const { isRTL } = useDirection();
|
|
29
32
|
|
|
@@ -50,6 +53,7 @@ const Stepper = ({ steps, activeStep = 0, className }: StepperProps) => {
|
|
|
50
53
|
) : (
|
|
51
54
|
<span className="tw-stepper__step-label">{step.label}</span>
|
|
52
55
|
);
|
|
56
|
+
|
|
53
57
|
return (
|
|
54
58
|
<li
|
|
55
59
|
key={index}
|
|
@@ -83,10 +87,10 @@ const Stepper = ({ steps, activeStep = 0, className }: StepperProps) => {
|
|
|
83
87
|
<div className="progress">
|
|
84
88
|
<div className="progress-bar" style={{ width: `${percentageCompleted * 100}%` }} />
|
|
85
89
|
</div>
|
|
86
|
-
|
|
90
|
+
|
|
91
|
+
<ol className="tw-stepper-steps p-t-1 m-b-0">{steps.map(renderStep)}</ol>
|
|
87
92
|
</div>
|
|
88
93
|
);
|
|
89
94
|
};
|
|
90
|
-
/* eslint-enable react/no-array-index-key */
|
|
91
95
|
|
|
92
96
|
export default Stepper;
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { action } from '@storybook/addon-actions';
|
|
2
|
-
import { select } from '@storybook/addon-knobs';
|
|
3
|
-
|
|
4
|
-
import Stepper from './Stepper';
|
|
5
|
-
|
|
6
|
-
export default {
|
|
7
|
-
component: Stepper,
|
|
8
|
-
title: 'Navigation/Stepper',
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export const Basic = () => {
|
|
12
|
-
const activeStep = select('activeStep', [0, 1, 2, 3, 4], 0);
|
|
13
|
-
return (
|
|
14
|
-
<Stepper
|
|
15
|
-
activeStep={activeStep}
|
|
16
|
-
steps={[
|
|
17
|
-
{
|
|
18
|
-
label: 'One',
|
|
19
|
-
onClick() {
|
|
20
|
-
action('You clicked on step 1, which triggered this function, which alerted you.');
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
label: 'Two',
|
|
25
|
-
hoverLabel: (
|
|
26
|
-
<>
|
|
27
|
-
<div>
|
|
28
|
-
<strong>Diana Jaramillo</strong>
|
|
29
|
-
</div>
|
|
30
|
-
dianajarm123@gmail.com
|
|
31
|
-
</>
|
|
32
|
-
),
|
|
33
|
-
},
|
|
34
|
-
{ label: 'Recipient' },
|
|
35
|
-
{ label: 'Smellification' },
|
|
36
|
-
{ label: 'Battle' },
|
|
37
|
-
]}
|
|
38
|
-
/>
|
|
39
|
-
);
|
|
40
|
-
};
|