@transferwise/components 0.0.0-experimental-497bd2d → 0.0.0-experimental-ea80215
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/dateLookup/dateTrigger/DateTrigger.js +4 -8
- package/build/dateLookup/dateTrigger/DateTrigger.js.map +1 -1
- package/build/dateLookup/dateTrigger/DateTrigger.mjs +4 -8
- package/build/dateLookup/dateTrigger/DateTrigger.mjs.map +1 -1
- package/build/field/Field.js +2 -9
- package/build/field/Field.js.map +1 -1
- package/build/field/Field.mjs +2 -9
- package/build/field/Field.mjs.map +1 -1
- package/build/i18n/en.json +1 -3
- package/build/i18n/en.json.js +1 -3
- package/build/i18n/en.json.js.map +1 -1
- package/build/i18n/en.json.mjs +1 -3
- package/build/i18n/en.json.mjs.map +1 -1
- package/build/inputs/SelectInput.js +100 -36
- package/build/inputs/SelectInput.js.map +1 -1
- package/build/inputs/SelectInput.mjs +102 -38
- package/build/inputs/SelectInput.mjs.map +1 -1
- package/build/label/Label.js +1 -29
- package/build/label/Label.js.map +1 -1
- package/build/label/Label.mjs +2 -30
- package/build/label/Label.mjs.map +1 -1
- package/build/main.css +18 -0
- package/build/styles/dateLookup/dateTrigger/DateTrigger.css +8 -0
- package/build/styles/inputs/SelectInput.css +10 -0
- package/build/styles/main.css +18 -0
- package/build/types/dateLookup/dateTrigger/DateTrigger.d.ts.map +1 -1
- package/build/types/field/Field.d.ts +2 -4
- package/build/types/field/Field.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.map +1 -1
- package/build/types/label/Label.d.ts +1 -10
- package/build/types/label/Label.d.ts.map +1 -1
- package/package.json +6 -5
- package/src/dateInput/DateInput.tests.story.tsx +32 -8
- package/src/dateLookup/DateLookup.rtl.spec.tsx +1 -1
- package/src/dateLookup/dateTrigger/DateTrigger.css +8 -0
- package/src/dateLookup/dateTrigger/DateTrigger.less +8 -0
- package/src/dateLookup/dateTrigger/DateTrigger.spec.js +1 -1
- package/src/dateLookup/dateTrigger/DateTrigger.tsx +4 -9
- package/src/field/Field.spec.tsx +3 -3
- package/src/field/Field.story.tsx +3 -81
- package/src/field/Field.tests.story.tsx +33 -0
- package/src/field/Field.tsx +4 -10
- package/src/i18n/en.json +1 -3
- package/src/index.ts +1 -1
- package/src/inlineAlert/InlineAlert.story.tsx +21 -8
- package/src/inputs/InputGroup.spec.tsx +1 -1
- package/src/inputs/SearchInput.spec.tsx +1 -1
- package/src/inputs/SelectInput.css +10 -0
- package/src/inputs/SelectInput.less +12 -0
- package/src/inputs/SelectInput.spec.tsx +1 -1
- package/src/inputs/SelectInput.story.tsx +20 -0
- package/src/inputs/SelectInput.tsx +139 -46
- package/src/label/Label.story.tsx +21 -37
- package/src/label/Label.tsx +2 -43
- package/src/main.css +18 -0
- package/src/radioGroup/RadioGroup.rtl.spec.tsx +1 -1
- package/src/select/Select.rtl.spec.tsx +1 -1
- package/src/switch/Switch.spec.tsx +1 -1
- package/build/label/Label.messages.js +0 -15
- package/build/label/Label.messages.js.map +0 -1
- package/build/label/Label.messages.mjs +0 -13
- package/build/label/Label.messages.mjs.map +0 -1
- package/build/types/label/Label.messages.d.ts +0 -12
- package/build/types/label/Label.messages.d.ts.map +0 -1
- package/build/types/label/index.d.ts +0 -3
- package/build/types/label/index.d.ts.map +0 -1
- package/src/label/Label.messages.tsx +0 -12
- package/src/label/index.ts +0 -2
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
createContext,
|
|
7
7
|
forwardRef,
|
|
8
8
|
useContext,
|
|
9
|
+
useDeferredValue,
|
|
9
10
|
useEffect,
|
|
10
11
|
useId,
|
|
11
12
|
useMemo,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
useState,
|
|
14
15
|
} from 'react';
|
|
15
16
|
import { useIntl } from 'react-intl';
|
|
17
|
+
import { Virtualizer } from 'virtua';
|
|
16
18
|
|
|
17
19
|
import { useEffectEvent } from '../common/hooks/useEffectEvent';
|
|
18
20
|
import { useScreenSize } from '../common/hooks/useScreenSize';
|
|
@@ -29,6 +31,8 @@ import { InputGroup } from './InputGroup';
|
|
|
29
31
|
import { SearchInput } from './SearchInput';
|
|
30
32
|
import messages from './SelectInput.messages';
|
|
31
33
|
|
|
34
|
+
const MAX_ITEMS_WITHOUT_VIRTUALIZATION = 50;
|
|
35
|
+
|
|
32
36
|
function searchableString(value: string) {
|
|
33
37
|
return value.trim().replace(/\s+/gu, ' ').normalize('NFKC').toLowerCase();
|
|
34
38
|
}
|
|
@@ -40,22 +44,13 @@ function inferSearchableStrings(value: unknown) {
|
|
|
40
44
|
|
|
41
45
|
if (typeof value === 'object' && value != null) {
|
|
42
46
|
return Object.values(value)
|
|
43
|
-
.filter((innerValue)
|
|
47
|
+
.filter((innerValue) => typeof innerValue === 'string')
|
|
44
48
|
.map((innerValue) => searchableString(innerValue));
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
return [];
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
const SelectInputTriggerButtonPropsContext = createContext<{
|
|
51
|
-
ref?: React.ForwardedRef<HTMLButtonElement | null>;
|
|
52
|
-
id?: string;
|
|
53
|
-
onClick?: (event: React.MouseEvent) => void;
|
|
54
|
-
onKeyDown?: (event: React.KeyboardEvent) => void;
|
|
55
|
-
[key: string]: unknown;
|
|
56
|
-
}>({});
|
|
57
|
-
const SelectInputOptionContentWithinTriggerContext = createContext(false);
|
|
58
|
-
|
|
59
54
|
export interface SelectInputOptionItem<T = string> {
|
|
60
55
|
type: 'option';
|
|
61
56
|
value: T;
|
|
@@ -89,6 +84,11 @@ function dedupeSelectInputOptionItem<T>(
|
|
|
89
84
|
return { ...item, value: undefined };
|
|
90
85
|
}
|
|
91
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Sets the `value` of duplicate option items to `undefined`, hiding them when
|
|
89
|
+
* rendered. Indexes are kept intact within groups to preserve the active item
|
|
90
|
+
* between filter changes when possible.
|
|
91
|
+
*/
|
|
92
92
|
function dedupeSelectInputItems<T>(
|
|
93
93
|
items: readonly SelectInputItem<T>[],
|
|
94
94
|
): SelectInputItem<T | undefined>[] {
|
|
@@ -112,20 +112,23 @@ function dedupeSelectInputItems<T>(
|
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
function
|
|
115
|
+
function selectInputOptionItemIncludesNeedle<T>(item: SelectInputOptionItem<T>, needle: string) {
|
|
116
116
|
return inferSearchableStrings(item.filterMatchers ?? item.value).some((haystack) =>
|
|
117
117
|
haystack.includes(needle),
|
|
118
118
|
);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
function filterSelectInputItems<T>(
|
|
121
|
+
function filterSelectInputItems<T>(
|
|
122
|
+
items: readonly SelectInputItem<T>[],
|
|
123
|
+
predicate: (item: SelectInputOptionItem<T>) => boolean,
|
|
124
|
+
) {
|
|
122
125
|
return items.filter((item) => {
|
|
123
126
|
switch (item.type) {
|
|
124
127
|
case 'option': {
|
|
125
|
-
return
|
|
128
|
+
return predicate(item);
|
|
126
129
|
}
|
|
127
130
|
case 'group': {
|
|
128
|
-
return item.options.some((option) =>
|
|
131
|
+
return item.options.some((option) => predicate(option));
|
|
129
132
|
}
|
|
130
133
|
default:
|
|
131
134
|
}
|
|
@@ -271,12 +274,15 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
271
274
|
}, [handleClose, open]);
|
|
272
275
|
|
|
273
276
|
const [filterQuery, _setFilterQuery] = useState('');
|
|
277
|
+
const deferredFilterQuery = useDeferredValue(filterQuery);
|
|
274
278
|
const setFilterQuery = useEffectEvent((query: string) => {
|
|
275
279
|
_setFilterQuery(query);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
+
if (query !== filterQuery) {
|
|
281
|
+
onFilterChange({
|
|
282
|
+
query,
|
|
283
|
+
queryNormalized: query ? searchableString(query) : null,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
280
286
|
});
|
|
281
287
|
|
|
282
288
|
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
|
@@ -294,9 +300,7 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
294
300
|
multiple={multiple}
|
|
295
301
|
defaultValue={defaultValue}
|
|
296
302
|
value={controlledValue}
|
|
297
|
-
|
|
298
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
299
|
-
by={compareValues as any}
|
|
303
|
+
by={compareValues}
|
|
300
304
|
disabled={disabled}
|
|
301
305
|
onChange={
|
|
302
306
|
((value) => {
|
|
@@ -349,8 +353,8 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
349
353
|
content: !placeholderShown ? (
|
|
350
354
|
<SelectInputOptionContentWithinTriggerContext.Provider value>
|
|
351
355
|
{multiple && Array.isArray(value)
|
|
352
|
-
? value
|
|
353
|
-
.map((option
|
|
356
|
+
? (value as readonly NonNullable<T>[])
|
|
357
|
+
.map((option) => renderValue(option, true))
|
|
354
358
|
.filter((node) => node != null)
|
|
355
359
|
.join(', ')
|
|
356
360
|
: renderValue(value as NonNullable<T>, true)}
|
|
@@ -379,9 +383,7 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
379
383
|
setOpen(false);
|
|
380
384
|
}}
|
|
381
385
|
onCloseEnd={() => {
|
|
382
|
-
|
|
383
|
-
setFilterQuery('');
|
|
384
|
-
}
|
|
386
|
+
setFilterQuery('');
|
|
385
387
|
}}
|
|
386
388
|
>
|
|
387
389
|
<SelectInputOptions
|
|
@@ -392,7 +394,7 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
392
394
|
filterPlaceholder={filterPlaceholder}
|
|
393
395
|
searchInputRef={searchInputRef}
|
|
394
396
|
listboxRef={listboxRef}
|
|
395
|
-
filterQuery={
|
|
397
|
+
filterQuery={deferredFilterQuery}
|
|
396
398
|
onFilterChange={setFilterQuery}
|
|
397
399
|
/>
|
|
398
400
|
</OptionsOverlay>
|
|
@@ -402,6 +404,14 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
402
404
|
);
|
|
403
405
|
}
|
|
404
406
|
|
|
407
|
+
const SelectInputTriggerButtonPropsContext = createContext<{
|
|
408
|
+
ref?: React.ForwardedRef<HTMLButtonElement | null>;
|
|
409
|
+
id?: string;
|
|
410
|
+
onClick?: (event: React.MouseEvent) => void;
|
|
411
|
+
onKeyDown?: (event: React.KeyboardEvent) => void;
|
|
412
|
+
[key: string]: unknown;
|
|
413
|
+
}>({});
|
|
414
|
+
|
|
405
415
|
type SelectInputTriggerButtonElementType = 'button' | React.ComponentType;
|
|
406
416
|
|
|
407
417
|
export type SelectInputTriggerButtonProps<
|
|
@@ -506,7 +516,48 @@ function SelectInputOptions<T = string>({
|
|
|
506
516
|
}
|
|
507
517
|
return undefined;
|
|
508
518
|
}, [filterQuery, filterable]);
|
|
509
|
-
|
|
519
|
+
useEffect(() => {
|
|
520
|
+
if (needle) {
|
|
521
|
+
// Ensure having an active option while filtering
|
|
522
|
+
requestAnimationFrame(() => {
|
|
523
|
+
if (
|
|
524
|
+
controllerRef.current != null &&
|
|
525
|
+
!controllerRef.current.hasAttribute('aria-activedescendant')
|
|
526
|
+
) {
|
|
527
|
+
// Activate first option via synthetic key press
|
|
528
|
+
controllerRef.current.dispatchEvent(
|
|
529
|
+
new KeyboardEvent('keydown', { key: 'Home', bubbles: true }),
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
}, [controllerRef, needle]);
|
|
535
|
+
|
|
536
|
+
const filteredItems: readonly SelectInputItem<NonNullable<T> | undefined>[] =
|
|
537
|
+
needle != null
|
|
538
|
+
? filterSelectInputItems(dedupeSelectInputItems(items), (item) =>
|
|
539
|
+
selectInputOptionItemIncludesNeedle(item, needle),
|
|
540
|
+
)
|
|
541
|
+
: items;
|
|
542
|
+
const resultsEmpty = needle != null && filteredItems.length === 0;
|
|
543
|
+
|
|
544
|
+
const virtualized = filteredItems.length > MAX_ITEMS_WITHOUT_VIRTUALIZATION;
|
|
545
|
+
|
|
546
|
+
// Items shown once shall be kept mounted until the needle changes, otherwise
|
|
547
|
+
// the scroll position may jump around inadvertently. Pattern adopted from:
|
|
548
|
+
// https://inokawa.github.io/virtua/?path=/story/advanced-keep-offscreen-items--append-only
|
|
549
|
+
const [mountedIndexes, setMountedIndexes] = useState<number[]>([]);
|
|
550
|
+
useEffect(() => {
|
|
551
|
+
// Ensure the 'End' key works as intended by keeping the last item mounted
|
|
552
|
+
setMountedIndexes((prevMountedIndexes) => {
|
|
553
|
+
const indexes = new Set(prevMountedIndexes);
|
|
554
|
+
indexes.add(filteredItems.length - 1);
|
|
555
|
+
return [...indexes]; // Sorting is redundant by nature here
|
|
556
|
+
});
|
|
557
|
+
}, [
|
|
558
|
+
needle, // Needed as `filteredItems.length` may be equal between two updates
|
|
559
|
+
filteredItems.length,
|
|
560
|
+
]);
|
|
510
561
|
|
|
511
562
|
const listboxContainerRef = useRef<HTMLDivElement>(null);
|
|
512
563
|
useEffect(() => {
|
|
@@ -522,6 +573,19 @@ function SelectInputOptions<T = string>({
|
|
|
522
573
|
const statusId = useId();
|
|
523
574
|
const listboxId = useId();
|
|
524
575
|
|
|
576
|
+
const getItemNode = (index: number) => {
|
|
577
|
+
const item = filteredItems[index];
|
|
578
|
+
return (
|
|
579
|
+
<SelectInputItemView
|
|
580
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
581
|
+
key={index}
|
|
582
|
+
item={item}
|
|
583
|
+
renderValue={renderValue}
|
|
584
|
+
needle={needle}
|
|
585
|
+
/>
|
|
586
|
+
);
|
|
587
|
+
};
|
|
588
|
+
|
|
525
589
|
return (
|
|
526
590
|
<ListboxBase.Options
|
|
527
591
|
as={SelectInputOptionsContainer}
|
|
@@ -533,12 +597,6 @@ function SelectInputOptions<T = string>({
|
|
|
533
597
|
controllerRef.current.setAttribute('aria-activedescendant', value);
|
|
534
598
|
} else {
|
|
535
599
|
controllerRef.current.removeAttribute('aria-activedescendant');
|
|
536
|
-
if (filterQuery) {
|
|
537
|
-
// Ensure having an active option while filtering
|
|
538
|
-
controllerRef.current.dispatchEvent(
|
|
539
|
-
new KeyboardEvent('keydown', { key: 'Home', bubbles: true }),
|
|
540
|
-
);
|
|
541
|
-
}
|
|
542
600
|
}
|
|
543
601
|
}
|
|
544
602
|
}}
|
|
@@ -549,7 +607,7 @@ function SelectInputOptions<T = string>({
|
|
|
549
607
|
ref={searchInputRef}
|
|
550
608
|
shape="rectangle"
|
|
551
609
|
placeholder={filterPlaceholder}
|
|
552
|
-
|
|
610
|
+
defaultValue={filterQuery}
|
|
553
611
|
aria-controls={listboxId}
|
|
554
612
|
aria-describedby={showStatus ? statusId : undefined}
|
|
555
613
|
onKeyDown={(event) => {
|
|
@@ -560,6 +618,9 @@ function SelectInputOptions<T = string>({
|
|
|
560
618
|
}
|
|
561
619
|
}}
|
|
562
620
|
onChange={(event) => {
|
|
621
|
+
// Free up resources and ensure not to go out of bounds when the
|
|
622
|
+
// resulting item count is less than before
|
|
623
|
+
setMountedIndexes([]);
|
|
563
624
|
onFilterChange(event.currentTarget.value);
|
|
564
625
|
}}
|
|
565
626
|
/>
|
|
@@ -571,7 +632,9 @@ function SelectInputOptions<T = string>({
|
|
|
571
632
|
tabIndex={-1}
|
|
572
633
|
className={clsx(
|
|
573
634
|
'np-select-input-listbox-container',
|
|
574
|
-
|
|
635
|
+
virtualized && 'np-select-input-listbox-container--virtualized',
|
|
636
|
+
needle == null && // Groups aren't shown when filtering
|
|
637
|
+
items.some((item) => item.type === 'group') &&
|
|
575
638
|
'np-select-input-listbox-container--has-group',
|
|
576
639
|
)}
|
|
577
640
|
>
|
|
@@ -590,15 +653,33 @@ function SelectInputOptions<T = string>({
|
|
|
590
653
|
tabIndex={0}
|
|
591
654
|
className="np-select-input-listbox"
|
|
592
655
|
>
|
|
593
|
-
{
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
656
|
+
{!virtualized ? (
|
|
657
|
+
filteredItems.map((_, index) => getItemNode(index))
|
|
658
|
+
) : (
|
|
659
|
+
<Virtualizer
|
|
660
|
+
key={needle}
|
|
661
|
+
count={filteredItems.length}
|
|
662
|
+
keepMounted={mountedIndexes}
|
|
663
|
+
scrollRef={listboxRef} // `VList` doesn't expose this
|
|
664
|
+
onRangeChange={(startIndex, endIndex) => {
|
|
665
|
+
setMountedIndexes((prevMountedIndexes) => {
|
|
666
|
+
const indexes = new Set(prevMountedIndexes);
|
|
667
|
+
for (let index = startIndex; index <= endIndex; index += 1) {
|
|
668
|
+
indexes.add(index);
|
|
669
|
+
}
|
|
670
|
+
return [...indexes].sort((a, b) => a - b);
|
|
671
|
+
});
|
|
672
|
+
}}
|
|
673
|
+
>
|
|
674
|
+
{(index) => (
|
|
675
|
+
<SelectInputItemsCountContext.Provider value={filteredItems.length}>
|
|
676
|
+
<SelectInputItemPositionContext.Provider value={index + 1}>
|
|
677
|
+
{getItemNode(index)}
|
|
678
|
+
</SelectInputItemPositionContext.Provider>
|
|
679
|
+
</SelectInputItemsCountContext.Provider>
|
|
680
|
+
)}
|
|
681
|
+
</Virtualizer>
|
|
682
|
+
)}
|
|
602
683
|
</div>
|
|
603
684
|
|
|
604
685
|
{renderFooter != null ? (
|
|
@@ -639,7 +720,10 @@ function SelectInputItemView<T = string>({
|
|
|
639
720
|
}: SelectInputItemViewProps<T>) {
|
|
640
721
|
switch (item.type) {
|
|
641
722
|
case 'option': {
|
|
642
|
-
if (
|
|
723
|
+
if (
|
|
724
|
+
item.value != null &&
|
|
725
|
+
(needle == null || selectInputOptionItemIncludesNeedle(item, needle))
|
|
726
|
+
) {
|
|
643
727
|
return (
|
|
644
728
|
<SelectInputOption value={item.value} disabled={item.disabled}>
|
|
645
729
|
{renderValue(item.value, false)}
|
|
@@ -701,6 +785,9 @@ function SelectInputGroupItemView<T = string>({
|
|
|
701
785
|
);
|
|
702
786
|
}
|
|
703
787
|
|
|
788
|
+
const SelectInputItemsCountContext = createContext<number | undefined>(undefined);
|
|
789
|
+
const SelectInputItemPositionContext = createContext<number | undefined>(undefined);
|
|
790
|
+
|
|
704
791
|
interface SelectInputOptionProps<T = string> {
|
|
705
792
|
value: T;
|
|
706
793
|
disabled?: boolean;
|
|
@@ -708,10 +795,14 @@ interface SelectInputOptionProps<T = string> {
|
|
|
708
795
|
}
|
|
709
796
|
|
|
710
797
|
function SelectInputOption<T = string>({ value, disabled, children }: SelectInputOptionProps<T>) {
|
|
798
|
+
const itemsCount = useContext(SelectInputItemsCountContext);
|
|
799
|
+
const itemPosition = useContext(SelectInputItemPositionContext);
|
|
711
800
|
return (
|
|
712
801
|
<ListboxBase.Option
|
|
713
802
|
as="div"
|
|
714
803
|
value={value}
|
|
804
|
+
aria-setsize={itemsCount}
|
|
805
|
+
aria-posinset={itemPosition}
|
|
715
806
|
disabled={disabled}
|
|
716
807
|
className={({ active, disabled: uiDisabled }) =>
|
|
717
808
|
clsx(
|
|
@@ -737,6 +828,8 @@ function SelectInputOption<T = string>({ value, disabled, children }: SelectInpu
|
|
|
737
828
|
);
|
|
738
829
|
}
|
|
739
830
|
|
|
831
|
+
const SelectInputOptionContentWithinTriggerContext = createContext(false);
|
|
832
|
+
|
|
740
833
|
export interface SelectInputOptionContentProps {
|
|
741
834
|
title: string;
|
|
742
835
|
note?: string;
|
|
@@ -1,53 +1,37 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
|
|
3
|
+
import Info from '../info/Info';
|
|
3
4
|
import { Input } from '../inputs/Input';
|
|
4
5
|
import { Label } from './Label';
|
|
5
|
-
import InlineAlert from '../inlineAlert/InlineAlert';
|
|
6
|
-
import { lorem10 } from '../test-utils';
|
|
7
6
|
|
|
8
7
|
export default {
|
|
9
8
|
component: Label,
|
|
10
9
|
title: 'Label',
|
|
11
|
-
tags: ['autodocs'],
|
|
12
10
|
};
|
|
13
11
|
|
|
14
12
|
export const Basic = () => {
|
|
15
13
|
const [value, setValue] = useState<string | undefined>('This is some text');
|
|
16
14
|
return (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
<Label className="m-b-2">
|
|
24
|
-
<Label.Optional>Phone number</Label.Optional>
|
|
25
|
-
<Input value={value} id="input" onChange={({ target }) => setValue(target.value)} />
|
|
26
|
-
</Label>
|
|
27
|
-
|
|
28
|
-
<Label className="m-b-2">
|
|
29
|
-
<Label.Optional>Phone number</Label.Optional>
|
|
30
|
-
<Label.Description>This an field Description</Label.Description>
|
|
31
|
-
<Input value={value} id="input" onChange={({ target }) => setValue(target.value)} />
|
|
32
|
-
</Label>
|
|
33
|
-
|
|
34
|
-
<Label htmlFor="phone-number-1">
|
|
35
|
-
<Label.Optional>Phone number</Label.Optional>
|
|
36
|
-
<Label.Description>This an field Description</Label.Description>
|
|
37
|
-
</Label>
|
|
38
|
-
<Input
|
|
39
|
-
id="phone-number-1"
|
|
40
|
-
className="m-b-2"
|
|
41
|
-
value={value}
|
|
42
|
-
onChange={({ target }) => setValue(target.value)}
|
|
43
|
-
/>
|
|
15
|
+
<Label>
|
|
16
|
+
Phone number
|
|
17
|
+
<Input value={value} id="input" onChange={({ target }) => setValue(target.value)} />
|
|
18
|
+
</Label>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
44
21
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
22
|
+
export const WithInfo = () => {
|
|
23
|
+
const [value, setValue] = useState<string | undefined>('This is some text');
|
|
24
|
+
return (
|
|
25
|
+
<Label>
|
|
26
|
+
<span className="d-flex">
|
|
27
|
+
Phone number{' '}
|
|
28
|
+
<Info
|
|
29
|
+
content="This is some help in popover"
|
|
30
|
+
aria-label="The aria label"
|
|
31
|
+
className="m-l-1"
|
|
32
|
+
/>
|
|
33
|
+
</span>
|
|
34
|
+
<Input value={value} id="input" onChange={({ target }) => setValue(target.value)} />
|
|
35
|
+
</Label>
|
|
52
36
|
);
|
|
53
37
|
};
|
package/src/label/Label.tsx
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import { clsx } from 'clsx';
|
|
2
|
-
import messages from './Label.messages';
|
|
3
|
-
import { useIntl } from 'react-intl';
|
|
4
|
-
import Body from '../body';
|
|
5
|
-
import { CommonProps } from '../common';
|
|
6
|
-
import { PropsWithChildren } from 'react';
|
|
7
2
|
|
|
8
3
|
export type LabelProps = {
|
|
9
4
|
id?: string;
|
|
@@ -12,50 +7,14 @@ export type LabelProps = {
|
|
|
12
7
|
children?: React.ReactNode;
|
|
13
8
|
};
|
|
14
9
|
|
|
15
|
-
const Label = ({ id, htmlFor, className, children }: LabelProps) => {
|
|
10
|
+
export const Label = ({ id, htmlFor, className, children }: LabelProps) => {
|
|
16
11
|
return (
|
|
17
12
|
<label
|
|
18
13
|
id={id}
|
|
19
14
|
htmlFor={htmlFor}
|
|
20
|
-
className={clsx(
|
|
21
|
-
'd-flex',
|
|
22
|
-
'flex-column',
|
|
23
|
-
'gap-y-4',
|
|
24
|
-
'm-b-0',
|
|
25
|
-
'np-text-body-default-bold',
|
|
26
|
-
'text-primary',
|
|
27
|
-
className,
|
|
28
|
-
)}
|
|
15
|
+
className={clsx('control-label d-flex flex-column gap-y-1 m-b-0', className)}
|
|
29
16
|
>
|
|
30
17
|
{children}
|
|
31
18
|
</label>
|
|
32
19
|
);
|
|
33
20
|
};
|
|
34
|
-
|
|
35
|
-
export type LabelOptionalProps = PropsWithChildren<CommonProps>;
|
|
36
|
-
|
|
37
|
-
const Optional = ({ children, className }: LabelOptionalProps) => {
|
|
38
|
-
const { formatMessage } = useIntl();
|
|
39
|
-
return (
|
|
40
|
-
<div>
|
|
41
|
-
{children}
|
|
42
|
-
<Body
|
|
43
|
-
as="span"
|
|
44
|
-
aria-label={formatMessage(messages.optionalAriaLabel)}
|
|
45
|
-
className={clsx('text-secondary', 'm-l-1', className)}
|
|
46
|
-
>
|
|
47
|
-
{formatMessage(messages.optionalLabel)}
|
|
48
|
-
</Body>
|
|
49
|
-
</div>
|
|
50
|
-
);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export type LabelDescriptionProps = PropsWithChildren<CommonProps>;
|
|
54
|
-
|
|
55
|
-
const Description = ({ children, className }: LabelDescriptionProps) =>
|
|
56
|
-
children ? <Body className={clsx('text-secondary', 'm-b-1', className)}>{children}</Body> : null;
|
|
57
|
-
|
|
58
|
-
Label.Optional = Optional;
|
|
59
|
-
Label.Description = Description;
|
|
60
|
-
|
|
61
|
-
export { Label };
|
package/src/main.css
CHANGED
|
@@ -1719,10 +1719,18 @@ button.np-option {
|
|
|
1719
1719
|
white-space: nowrap;
|
|
1720
1720
|
width: 100%;
|
|
1721
1721
|
}
|
|
1722
|
+
.np-date-trigger .control-label {
|
|
1723
|
+
font-weight: 400;
|
|
1724
|
+
font-weight: var(--font-weight-regular);
|
|
1725
|
+
}
|
|
1722
1726
|
.np-theme-personal .np-date-trigger {
|
|
1723
1727
|
padding-left: 16px;
|
|
1724
1728
|
padding-left: var(--size-16);
|
|
1725
1729
|
}
|
|
1730
|
+
.np-theme-personal .np-date-trigger .control-label + span {
|
|
1731
|
+
font-weight: 400;
|
|
1732
|
+
font-weight: var(--font-weight-regular);
|
|
1733
|
+
}
|
|
1726
1734
|
.clear-btn {
|
|
1727
1735
|
transition: color 0.15s ease-in-out;
|
|
1728
1736
|
color: #c9cbce;
|
|
@@ -2647,6 +2655,10 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
2647
2655
|
height: auto;
|
|
2648
2656
|
}
|
|
2649
2657
|
}
|
|
2658
|
+
.np-select-input-listbox-container--virtualized {
|
|
2659
|
+
/* The wrapping element shrinks this as needed */
|
|
2660
|
+
height: 100vh;
|
|
2661
|
+
}
|
|
2650
2662
|
.np-select-input-listbox-container--has-group {
|
|
2651
2663
|
scroll-padding-top: 32px;
|
|
2652
2664
|
scroll-padding-top: var(--size-32);
|
|
@@ -2665,6 +2677,12 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
2665
2677
|
outline: var(--ring-outline-color) solid var(--ring-outline-width);
|
|
2666
2678
|
outline-offset: var(--ring-outline-offset);
|
|
2667
2679
|
}
|
|
2680
|
+
.np-select-input-listbox-container--virtualized .np-select-input-listbox {
|
|
2681
|
+
/* Adopted from `VList` in virtua: https://github.com/inokawa/virtua/blob/7f6ed5b37df6b480d4ff350f3960067c5b3519d2/src/react/VList.tsx#L113-L116 */
|
|
2682
|
+
overflow-y: auto;
|
|
2683
|
+
contain: strict;
|
|
2684
|
+
height: 100%;
|
|
2685
|
+
}
|
|
2668
2686
|
.np-select-input-separator-item {
|
|
2669
2687
|
margin: 8px;
|
|
2670
2688
|
margin: var(--size-8);
|
|
@@ -12,6 +12,6 @@ describe('Select', () => {
|
|
|
12
12
|
<Select options={options} selected={options[0]} onChange={() => {}} />
|
|
13
13
|
</Field>,
|
|
14
14
|
);
|
|
15
|
-
expect(screen.getByLabelText(
|
|
15
|
+
expect(screen.getByLabelText('Currency')).toHaveTextContent('USD');
|
|
16
16
|
});
|
|
17
17
|
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var reactIntl = require('react-intl');
|
|
4
|
-
|
|
5
|
-
var messages = reactIntl.defineMessages({
|
|
6
|
-
optionalLabel: {
|
|
7
|
-
id: "neptune.Label.optional"
|
|
8
|
-
},
|
|
9
|
-
optionalAriaLabel: {
|
|
10
|
-
id: "neptune.aria.Label.optional"
|
|
11
|
-
}
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
module.exports = messages;
|
|
15
|
-
//# sourceMappingURL=Label.messages.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Label.messages.js","sources":["../../src/label/Label.messages.tsx"],"sourcesContent":["import { defineMessages } from 'react-intl';\n\nexport default defineMessages({\n optionalLabel: {\n id: 'neptune.Label.optional',\n defaultMessage: '(Optional)',\n },\n optionalAriaLabel: {\n id: 'neptune.aria.Label.optional',\n defaultMessage: 'This field is optional',\n },\n});\n"],"names":["defineMessages","optionalLabel","id","optionalAriaLabel"],"mappings":";;;;AAEA,eAAeA,wBAAc,CAAC;AAC5BC,EAAAA,aAAa,EAAE;IACbC,EAAE,EAAA,wBAAA;GAEH;AACDC,EAAAA,iBAAiB,EAAE;IACjBD,EAAE,EAAA,6BAAA;AAEH,GAAA;AACF,CAAA,CAAC;;;;"}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { defineMessages } from 'react-intl';
|
|
2
|
-
|
|
3
|
-
var messages = defineMessages({
|
|
4
|
-
optionalLabel: {
|
|
5
|
-
id: "neptune.Label.optional"
|
|
6
|
-
},
|
|
7
|
-
optionalAriaLabel: {
|
|
8
|
-
id: "neptune.aria.Label.optional"
|
|
9
|
-
}
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
export { messages as default };
|
|
13
|
-
//# sourceMappingURL=Label.messages.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Label.messages.mjs","sources":["../../src/label/Label.messages.tsx"],"sourcesContent":["import { defineMessages } from 'react-intl';\n\nexport default defineMessages({\n optionalLabel: {\n id: 'neptune.Label.optional',\n defaultMessage: '(Optional)',\n },\n optionalAriaLabel: {\n id: 'neptune.aria.Label.optional',\n defaultMessage: 'This field is optional',\n },\n});\n"],"names":["defineMessages","optionalLabel","id","optionalAriaLabel"],"mappings":";;AAEA,eAAeA,cAAc,CAAC;AAC5BC,EAAAA,aAAa,EAAE;IACbC,EAAE,EAAA,wBAAA;GAEH;AACDC,EAAAA,iBAAiB,EAAE;IACjBD,EAAE,EAAA,6BAAA;AAEH,GAAA;AACF,CAAA,CAAC;;;;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Label.messages.d.ts","sourceRoot":"","sources":["../../../src/label/Label.messages.tsx"],"names":[],"mappings":";;;;;;;;;;AAEA,wBASG"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/label/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC"}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { defineMessages } from 'react-intl';
|
|
2
|
-
|
|
3
|
-
export default defineMessages({
|
|
4
|
-
optionalLabel: {
|
|
5
|
-
id: 'neptune.Label.optional',
|
|
6
|
-
defaultMessage: '(Optional)',
|
|
7
|
-
},
|
|
8
|
-
optionalAriaLabel: {
|
|
9
|
-
id: 'neptune.aria.Label.optional',
|
|
10
|
-
defaultMessage: 'This field is optional',
|
|
11
|
-
},
|
|
12
|
-
});
|
package/src/label/index.ts
DELETED