@transferwise/components 0.0.0-experimental-333df2c → 0.0.0-experimental-e4e09f5
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 +8 -4
- package/build/dateLookup/dateTrigger/DateTrigger.js.map +1 -1
- package/build/dateLookup/dateTrigger/DateTrigger.mjs +8 -4
- package/build/dateLookup/dateTrigger/DateTrigger.mjs.map +1 -1
- package/build/field/Field.js +9 -2
- package/build/field/Field.js.map +1 -1
- package/build/field/Field.mjs +9 -2
- package/build/field/Field.mjs.map +1 -1
- package/build/i18n/en.json +3 -1
- package/build/i18n/en.json.js +3 -1
- package/build/i18n/en.json.js.map +1 -1
- package/build/i18n/en.json.mjs +3 -1
- package/build/i18n/en.json.mjs.map +1 -1
- package/build/inputs/SelectInput.js +34 -86
- package/build/inputs/SelectInput.js.map +1 -1
- package/build/inputs/SelectInput.mjs +36 -88
- package/build/inputs/SelectInput.mjs.map +1 -1
- package/build/label/Label.js +29 -1
- package/build/label/Label.js.map +1 -1
- package/build/label/Label.messages.js +15 -0
- package/build/label/Label.messages.js.map +1 -0
- package/build/label/Label.messages.mjs +13 -0
- package/build/label/Label.messages.mjs.map +1 -0
- package/build/label/Label.mjs +30 -2
- package/build/label/Label.mjs.map +1 -1
- package/build/main.css +0 -18
- package/build/styles/dateLookup/dateTrigger/DateTrigger.css +0 -8
- package/build/styles/inputs/SelectInput.css +0 -10
- package/build/styles/main.css +0 -18
- package/build/types/dateLookup/dateTrigger/DateTrigger.d.ts.map +1 -1
- package/build/types/field/Field.d.ts +4 -2
- 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 +10 -1
- package/build/types/label/Label.d.ts.map +1 -1
- package/build/types/label/Label.messages.d.ts +12 -0
- package/build/types/label/Label.messages.d.ts.map +1 -0
- package/build/types/label/index.d.ts +3 -0
- package/build/types/label/index.d.ts.map +1 -0
- package/package.json +4 -5
- package/src/dateInput/DateInput.tests.story.tsx +8 -32
- package/src/dateLookup/DateLookup.rtl.spec.tsx +1 -1
- package/src/dateLookup/dateTrigger/DateTrigger.css +0 -8
- package/src/dateLookup/dateTrigger/DateTrigger.less +0 -8
- package/src/dateLookup/dateTrigger/DateTrigger.spec.js +1 -1
- package/src/dateLookup/dateTrigger/DateTrigger.tsx +9 -4
- package/src/field/Field.spec.tsx +3 -3
- package/src/field/Field.story.tsx +81 -3
- package/src/field/Field.tsx +10 -4
- package/src/i18n/en.json +3 -1
- package/src/index.ts +1 -1
- package/src/inlineAlert/InlineAlert.story.tsx +8 -21
- package/src/inputs/InputGroup.spec.tsx +1 -1
- package/src/inputs/SearchInput.spec.tsx +1 -1
- package/src/inputs/SelectInput.css +0 -10
- package/src/inputs/SelectInput.less +0 -12
- package/src/inputs/SelectInput.spec.tsx +1 -1
- package/src/inputs/SelectInput.story.tsx +0 -20
- package/src/inputs/SelectInput.tsx +37 -116
- package/src/label/Label.messages.tsx +12 -0
- package/src/label/Label.story.tsx +30 -21
- package/src/label/Label.tsx +43 -2
- package/src/label/index.ts +2 -0
- package/src/main.css +0 -18
- 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/src/field/Field.tests.story.tsx +0 -33
|
@@ -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 { Virtualizer } 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
|
|
|
@@ -93,11 +89,6 @@ function dedupeSelectInputOptionItem<T>(
|
|
|
93
89
|
return { ...item, value: undefined };
|
|
94
90
|
}
|
|
95
91
|
|
|
96
|
-
/**
|
|
97
|
-
* Sets the `value` of duplicate option items to `undefined`, hiding them when
|
|
98
|
-
* rendered. Indexes are kept intact within groups to preserve the active item
|
|
99
|
-
* between filter changes when possible.
|
|
100
|
-
*/
|
|
101
92
|
function dedupeSelectInputItems<T>(
|
|
102
93
|
items: readonly SelectInputItem<T>[],
|
|
103
94
|
): SelectInputItem<T | undefined>[] {
|
|
@@ -121,23 +112,20 @@ function dedupeSelectInputItems<T>(
|
|
|
121
112
|
});
|
|
122
113
|
}
|
|
123
114
|
|
|
124
|
-
function
|
|
115
|
+
function filterSelectInputOptionItem<T>(item: SelectInputOptionItem<T>, needle: string) {
|
|
125
116
|
return inferSearchableStrings(item.filterMatchers ?? item.value).some((haystack) =>
|
|
126
117
|
haystack.includes(needle),
|
|
127
118
|
);
|
|
128
119
|
}
|
|
129
120
|
|
|
130
|
-
function filterSelectInputItems<T>(
|
|
131
|
-
items: readonly SelectInputItem<T>[],
|
|
132
|
-
predicate: (item: SelectInputOptionItem<T>) => boolean,
|
|
133
|
-
) {
|
|
121
|
+
function filterSelectInputItems<T>(items: readonly SelectInputItem<T>[], needle: string) {
|
|
134
122
|
return items.filter((item) => {
|
|
135
123
|
switch (item.type) {
|
|
136
124
|
case 'option': {
|
|
137
|
-
return
|
|
125
|
+
return filterSelectInputOptionItem(item, needle);
|
|
138
126
|
}
|
|
139
127
|
case 'group': {
|
|
140
|
-
return item.options.some((option) =>
|
|
128
|
+
return item.options.some((option) => filterSelectInputOptionItem(option, needle));
|
|
141
129
|
}
|
|
142
130
|
default:
|
|
143
131
|
}
|
|
@@ -283,15 +271,12 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
283
271
|
}, [handleClose, open]);
|
|
284
272
|
|
|
285
273
|
const [filterQuery, _setFilterQuery] = useState('');
|
|
286
|
-
const deferredFilterQuery = useDeferredValue(filterQuery);
|
|
287
274
|
const setFilterQuery = useEffectEvent((query: string) => {
|
|
288
275
|
_setFilterQuery(query);
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
});
|
|
294
|
-
}
|
|
276
|
+
onFilterChange({
|
|
277
|
+
query,
|
|
278
|
+
queryNormalized: query ? searchableString(query) : null,
|
|
279
|
+
});
|
|
295
280
|
});
|
|
296
281
|
|
|
297
282
|
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
|
@@ -309,7 +294,9 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
309
294
|
multiple={multiple}
|
|
310
295
|
defaultValue={defaultValue}
|
|
311
296
|
value={controlledValue}
|
|
312
|
-
|
|
297
|
+
// TODO: Remove assertion when upgrading TypeScript to v5
|
|
298
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
299
|
+
by={compareValues as any}
|
|
313
300
|
disabled={disabled}
|
|
314
301
|
onChange={
|
|
315
302
|
((value) => {
|
|
@@ -362,8 +349,8 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
362
349
|
content: !placeholderShown ? (
|
|
363
350
|
<SelectInputOptionContentWithinTriggerContext.Provider value>
|
|
364
351
|
{multiple && Array.isArray(value)
|
|
365
|
-
?
|
|
366
|
-
.map((option) => renderValue(option, true))
|
|
352
|
+
? value
|
|
353
|
+
.map((option: NonNullable<T>) => renderValue(option, true))
|
|
367
354
|
.filter((node) => node != null)
|
|
368
355
|
.join(', ')
|
|
369
356
|
: renderValue(value as NonNullable<T>, true)}
|
|
@@ -392,7 +379,9 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
392
379
|
setOpen(false);
|
|
393
380
|
}}
|
|
394
381
|
onCloseEnd={() => {
|
|
395
|
-
|
|
382
|
+
if (filterQuery !== '') {
|
|
383
|
+
setFilterQuery('');
|
|
384
|
+
}
|
|
396
385
|
}}
|
|
397
386
|
>
|
|
398
387
|
<SelectInputOptions
|
|
@@ -403,7 +392,7 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
403
392
|
filterPlaceholder={filterPlaceholder}
|
|
404
393
|
searchInputRef={searchInputRef}
|
|
405
394
|
listboxRef={listboxRef}
|
|
406
|
-
filterQuery={
|
|
395
|
+
filterQuery={filterQuery}
|
|
407
396
|
onFilterChange={setFilterQuery}
|
|
408
397
|
/>
|
|
409
398
|
</OptionsOverlay>
|
|
@@ -517,48 +506,7 @@ function SelectInputOptions<T = string>({
|
|
|
517
506
|
}
|
|
518
507
|
return undefined;
|
|
519
508
|
}, [filterQuery, filterable]);
|
|
520
|
-
|
|
521
|
-
if (needle) {
|
|
522
|
-
// Ensure having an active option while filtering
|
|
523
|
-
requestAnimationFrame(() => {
|
|
524
|
-
if (
|
|
525
|
-
controllerRef.current != null &&
|
|
526
|
-
!controllerRef.current.hasAttribute('aria-activedescendant')
|
|
527
|
-
) {
|
|
528
|
-
// Activate first option via synthetic key press
|
|
529
|
-
controllerRef.current.dispatchEvent(
|
|
530
|
-
new KeyboardEvent('keydown', { key: 'Home', bubbles: true }),
|
|
531
|
-
);
|
|
532
|
-
}
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
}, [controllerRef, needle]);
|
|
536
|
-
|
|
537
|
-
const filteredItems: readonly SelectInputItem<NonNullable<T> | undefined>[] =
|
|
538
|
-
needle != null
|
|
539
|
-
? filterSelectInputItems(dedupeSelectInputItems(items), (item) =>
|
|
540
|
-
selectInputOptionItemIncludesNeedle(item, needle),
|
|
541
|
-
)
|
|
542
|
-
: items;
|
|
543
|
-
const resultsEmpty = needle != null && filteredItems.length === 0;
|
|
544
|
-
|
|
545
|
-
const virtualized = filteredItems.length > MAX_ITEMS_WITHOUT_VIRTUALIZATION;
|
|
546
|
-
|
|
547
|
-
// Items shown once shall be kept mounted until the needle changes, otherwise
|
|
548
|
-
// the scroll position may jump around inadvertently. Pattern adopted from:
|
|
549
|
-
// https://inokawa.github.io/virtua/?path=/story/advanced-keep-offscreen-items--append-only
|
|
550
|
-
const [mountedIndexes, setMountedIndexes] = useState<number[]>([]);
|
|
551
|
-
useEffect(() => {
|
|
552
|
-
// Ensure the 'End' key works as intended by keeping the last item mounted
|
|
553
|
-
setMountedIndexes((prevMountedIndexes) => {
|
|
554
|
-
const indexes = new Set(prevMountedIndexes);
|
|
555
|
-
indexes.add(filteredItems.length - 1);
|
|
556
|
-
return [...indexes]; // Sorting is redundant by nature here
|
|
557
|
-
});
|
|
558
|
-
}, [
|
|
559
|
-
needle, // Needed as `filteredItems.length` may be equal between two updates
|
|
560
|
-
filteredItems.length,
|
|
561
|
-
]);
|
|
509
|
+
const resultsEmpty = needle != null && filterSelectInputItems(items, needle).length === 0;
|
|
562
510
|
|
|
563
511
|
const listboxContainerRef = useRef<HTMLDivElement>(null);
|
|
564
512
|
useEffect(() => {
|
|
@@ -574,19 +522,6 @@ function SelectInputOptions<T = string>({
|
|
|
574
522
|
const statusId = useId();
|
|
575
523
|
const listboxId = useId();
|
|
576
524
|
|
|
577
|
-
const getItemNode = (index: number) => {
|
|
578
|
-
const item = filteredItems[index];
|
|
579
|
-
return (
|
|
580
|
-
<SelectInputItemView
|
|
581
|
-
// eslint-disable-next-line react/no-array-index-key
|
|
582
|
-
key={index}
|
|
583
|
-
item={item}
|
|
584
|
-
renderValue={renderValue}
|
|
585
|
-
needle={needle}
|
|
586
|
-
/>
|
|
587
|
-
);
|
|
588
|
-
};
|
|
589
|
-
|
|
590
525
|
return (
|
|
591
526
|
<ListboxBase.Options
|
|
592
527
|
as={SelectInputOptionsContainer}
|
|
@@ -598,6 +533,12 @@ function SelectInputOptions<T = string>({
|
|
|
598
533
|
controllerRef.current.setAttribute('aria-activedescendant', value);
|
|
599
534
|
} else {
|
|
600
535
|
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
|
+
}
|
|
601
542
|
}
|
|
602
543
|
}
|
|
603
544
|
}}
|
|
@@ -608,7 +549,7 @@ function SelectInputOptions<T = string>({
|
|
|
608
549
|
ref={searchInputRef}
|
|
609
550
|
shape="rectangle"
|
|
610
551
|
placeholder={filterPlaceholder}
|
|
611
|
-
|
|
552
|
+
value={filterQuery}
|
|
612
553
|
aria-controls={listboxId}
|
|
613
554
|
aria-describedby={showStatus ? statusId : undefined}
|
|
614
555
|
onKeyDown={(event) => {
|
|
@@ -619,9 +560,6 @@ function SelectInputOptions<T = string>({
|
|
|
619
560
|
}
|
|
620
561
|
}}
|
|
621
562
|
onChange={(event) => {
|
|
622
|
-
// Free up resources and ensure not to go out of bounds when the
|
|
623
|
-
// resulting item count is less than before
|
|
624
|
-
setMountedIndexes([]);
|
|
625
563
|
onFilterChange(event.currentTarget.value);
|
|
626
564
|
}}
|
|
627
565
|
/>
|
|
@@ -633,9 +571,7 @@ function SelectInputOptions<T = string>({
|
|
|
633
571
|
tabIndex={-1}
|
|
634
572
|
className={clsx(
|
|
635
573
|
'np-select-input-listbox-container',
|
|
636
|
-
|
|
637
|
-
needle == null && // Groups aren't shown when filtering
|
|
638
|
-
items.some((item) => item.type === 'group') &&
|
|
574
|
+
items.some((item) => item.type === 'group') &&
|
|
639
575
|
'np-select-input-listbox-container--has-group',
|
|
640
576
|
)}
|
|
641
577
|
>
|
|
@@ -654,27 +590,15 @@ function SelectInputOptions<T = string>({
|
|
|
654
590
|
tabIndex={0}
|
|
655
591
|
className="np-select-input-listbox"
|
|
656
592
|
>
|
|
657
|
-
{
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
setMountedIndexes((prevMountedIndexes) => {
|
|
667
|
-
const indexes = new Set(prevMountedIndexes);
|
|
668
|
-
for (let index = startIndex; index <= endIndex; index += 1) {
|
|
669
|
-
indexes.add(index);
|
|
670
|
-
}
|
|
671
|
-
return [...indexes].sort((a, b) => a - b);
|
|
672
|
-
});
|
|
673
|
-
}}
|
|
674
|
-
>
|
|
675
|
-
{(index) => getItemNode(index)}
|
|
676
|
-
</Virtualizer>
|
|
677
|
-
)}
|
|
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
|
+
))}
|
|
678
602
|
</div>
|
|
679
603
|
|
|
680
604
|
{renderFooter != null ? (
|
|
@@ -715,10 +639,7 @@ function SelectInputItemView<T = string>({
|
|
|
715
639
|
}: SelectInputItemViewProps<T>) {
|
|
716
640
|
switch (item.type) {
|
|
717
641
|
case 'option': {
|
|
718
|
-
if (
|
|
719
|
-
item.value != null &&
|
|
720
|
-
(needle == null || selectInputOptionItemIncludesNeedle(item, needle))
|
|
721
|
-
) {
|
|
642
|
+
if (item.value != null && (needle == null || filterSelectInputOptionItem(item, needle))) {
|
|
722
643
|
return (
|
|
723
644
|
<SelectInputOption value={item.value} disabled={item.disabled}>
|
|
724
645
|
{renderValue(item.value, false)}
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
});
|
|
@@ -1,37 +1,46 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
|
|
3
|
-
import Info from '../info/Info';
|
|
4
3
|
import { Input } from '../inputs/Input';
|
|
5
4
|
import { Label } from './Label';
|
|
5
|
+
import InlineAlert from '../inlineAlert/InlineAlert';
|
|
6
|
+
import { lorem10 } from '../test-utils';
|
|
6
7
|
|
|
7
8
|
export default {
|
|
8
9
|
component: Label,
|
|
9
10
|
title: 'Label',
|
|
11
|
+
tags: ['autodocs'],
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
export const Basic = () => {
|
|
13
15
|
const [value, setValue] = useState<string | undefined>('This is some text');
|
|
14
16
|
return (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
};
|
|
17
|
+
<>
|
|
18
|
+
<Label className="m-b-2">
|
|
19
|
+
Phone number
|
|
20
|
+
<Input value={value} id="input" onChange={({ target }) => setValue(target.value)} />
|
|
21
|
+
</Label>
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
+
/>
|
|
44
|
+
</>
|
|
36
45
|
);
|
|
37
46
|
};
|
package/src/label/Label.tsx
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
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';
|
|
2
7
|
|
|
3
8
|
export type LabelProps = {
|
|
4
9
|
id?: string;
|
|
@@ -7,14 +12,50 @@ export type LabelProps = {
|
|
|
7
12
|
children?: React.ReactNode;
|
|
8
13
|
};
|
|
9
14
|
|
|
10
|
-
|
|
15
|
+
const Label = ({ id, htmlFor, className, children }: LabelProps) => {
|
|
11
16
|
return (
|
|
12
17
|
<label
|
|
13
18
|
id={id}
|
|
14
19
|
htmlFor={htmlFor}
|
|
15
|
-
className={clsx(
|
|
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
|
+
)}
|
|
16
29
|
>
|
|
17
30
|
{children}
|
|
18
31
|
</label>
|
|
19
32
|
);
|
|
20
33
|
};
|
|
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', className)}>{children}</Body> : null;
|
|
57
|
+
|
|
58
|
+
Label.Optional = Optional;
|
|
59
|
+
Label.Description = Description;
|
|
60
|
+
|
|
61
|
+
export { Label };
|
package/src/main.css
CHANGED
|
@@ -1719,18 +1719,10 @@ 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
|
-
}
|
|
1726
1722
|
.np-theme-personal .np-date-trigger {
|
|
1727
1723
|
padding-left: 16px;
|
|
1728
1724
|
padding-left: var(--size-16);
|
|
1729
1725
|
}
|
|
1730
|
-
.np-theme-personal .np-date-trigger .control-label + span {
|
|
1731
|
-
font-weight: 400;
|
|
1732
|
-
font-weight: var(--font-weight-regular);
|
|
1733
|
-
}
|
|
1734
1726
|
.clear-btn {
|
|
1735
1727
|
transition: color 0.15s ease-in-out;
|
|
1736
1728
|
color: #c9cbce;
|
|
@@ -2655,10 +2647,6 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
2655
2647
|
height: auto;
|
|
2656
2648
|
}
|
|
2657
2649
|
}
|
|
2658
|
-
.np-select-input-listbox-container--virtualized {
|
|
2659
|
-
/* The wrapping element shrinks this as needed */
|
|
2660
|
-
height: 100vh;
|
|
2661
|
-
}
|
|
2662
2650
|
.np-select-input-listbox-container--has-group {
|
|
2663
2651
|
scroll-padding-top: 32px;
|
|
2664
2652
|
scroll-padding-top: var(--size-32);
|
|
@@ -2677,12 +2665,6 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
2677
2665
|
outline: var(--ring-outline-color) solid var(--ring-outline-width);
|
|
2678
2666
|
outline-offset: var(--ring-outline-offset);
|
|
2679
2667
|
}
|
|
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
|
-
}
|
|
2686
2668
|
.np-select-input-separator-item {
|
|
2687
2669
|
margin: 8px;
|
|
2688
2670
|
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,33 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
|
|
3
|
-
import { Input } from '../inputs/Input';
|
|
4
|
-
import { Field } from './Field';
|
|
5
|
-
import { Sentiment } from '../common';
|
|
6
|
-
|
|
7
|
-
export default {
|
|
8
|
-
component: Field,
|
|
9
|
-
title: 'Field/Tests',
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export const WithHelpAndErrorOnBlur = () => {
|
|
13
|
-
const [value, setValue] = useState('This is some text');
|
|
14
|
-
const [error, setError] = useState<string | undefined>(undefined);
|
|
15
|
-
return (
|
|
16
|
-
<Field
|
|
17
|
-
label="Phone number"
|
|
18
|
-
sentiment={error ? Sentiment.NEGATIVE : Sentiment.NEUTRAL}
|
|
19
|
-
message={error || 'Please include country code'}
|
|
20
|
-
>
|
|
21
|
-
<Input
|
|
22
|
-
value={value}
|
|
23
|
-
onChange={({ target }) => {
|
|
24
|
-
setValue(target.value);
|
|
25
|
-
setError(undefined);
|
|
26
|
-
}}
|
|
27
|
-
onBlur={() => {
|
|
28
|
-
setError('Something went wrong');
|
|
29
|
-
}}
|
|
30
|
-
/>
|
|
31
|
-
</Field>
|
|
32
|
-
);
|
|
33
|
-
};
|