@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.
Files changed (70) hide show
  1. package/build/dateLookup/dateTrigger/DateTrigger.js +4 -8
  2. package/build/dateLookup/dateTrigger/DateTrigger.js.map +1 -1
  3. package/build/dateLookup/dateTrigger/DateTrigger.mjs +4 -8
  4. package/build/dateLookup/dateTrigger/DateTrigger.mjs.map +1 -1
  5. package/build/field/Field.js +2 -9
  6. package/build/field/Field.js.map +1 -1
  7. package/build/field/Field.mjs +2 -9
  8. package/build/field/Field.mjs.map +1 -1
  9. package/build/i18n/en.json +1 -3
  10. package/build/i18n/en.json.js +1 -3
  11. package/build/i18n/en.json.js.map +1 -1
  12. package/build/i18n/en.json.mjs +1 -3
  13. package/build/i18n/en.json.mjs.map +1 -1
  14. package/build/inputs/SelectInput.js +100 -36
  15. package/build/inputs/SelectInput.js.map +1 -1
  16. package/build/inputs/SelectInput.mjs +102 -38
  17. package/build/inputs/SelectInput.mjs.map +1 -1
  18. package/build/label/Label.js +1 -29
  19. package/build/label/Label.js.map +1 -1
  20. package/build/label/Label.mjs +2 -30
  21. package/build/label/Label.mjs.map +1 -1
  22. package/build/main.css +18 -0
  23. package/build/styles/dateLookup/dateTrigger/DateTrigger.css +8 -0
  24. package/build/styles/inputs/SelectInput.css +10 -0
  25. package/build/styles/main.css +18 -0
  26. package/build/types/dateLookup/dateTrigger/DateTrigger.d.ts.map +1 -1
  27. package/build/types/field/Field.d.ts +2 -4
  28. package/build/types/field/Field.d.ts.map +1 -1
  29. package/build/types/index.d.ts +1 -1
  30. package/build/types/index.d.ts.map +1 -1
  31. package/build/types/inputs/SelectInput.d.ts.map +1 -1
  32. package/build/types/label/Label.d.ts +1 -10
  33. package/build/types/label/Label.d.ts.map +1 -1
  34. package/package.json +6 -5
  35. package/src/dateInput/DateInput.tests.story.tsx +32 -8
  36. package/src/dateLookup/DateLookup.rtl.spec.tsx +1 -1
  37. package/src/dateLookup/dateTrigger/DateTrigger.css +8 -0
  38. package/src/dateLookup/dateTrigger/DateTrigger.less +8 -0
  39. package/src/dateLookup/dateTrigger/DateTrigger.spec.js +1 -1
  40. package/src/dateLookup/dateTrigger/DateTrigger.tsx +4 -9
  41. package/src/field/Field.spec.tsx +3 -3
  42. package/src/field/Field.story.tsx +3 -81
  43. package/src/field/Field.tests.story.tsx +33 -0
  44. package/src/field/Field.tsx +4 -10
  45. package/src/i18n/en.json +1 -3
  46. package/src/index.ts +1 -1
  47. package/src/inlineAlert/InlineAlert.story.tsx +21 -8
  48. package/src/inputs/InputGroup.spec.tsx +1 -1
  49. package/src/inputs/SearchInput.spec.tsx +1 -1
  50. package/src/inputs/SelectInput.css +10 -0
  51. package/src/inputs/SelectInput.less +12 -0
  52. package/src/inputs/SelectInput.spec.tsx +1 -1
  53. package/src/inputs/SelectInput.story.tsx +20 -0
  54. package/src/inputs/SelectInput.tsx +139 -46
  55. package/src/label/Label.story.tsx +21 -37
  56. package/src/label/Label.tsx +2 -43
  57. package/src/main.css +18 -0
  58. package/src/radioGroup/RadioGroup.rtl.spec.tsx +1 -1
  59. package/src/select/Select.rtl.spec.tsx +1 -1
  60. package/src/switch/Switch.spec.tsx +1 -1
  61. package/build/label/Label.messages.js +0 -15
  62. package/build/label/Label.messages.js.map +0 -1
  63. package/build/label/Label.messages.mjs +0 -13
  64. package/build/label/Label.messages.mjs.map +0 -1
  65. package/build/types/label/Label.messages.d.ts +0 -12
  66. package/build/types/label/Label.messages.d.ts.map +0 -1
  67. package/build/types/label/index.d.ts +0 -3
  68. package/build/types/label/index.d.ts.map +0 -1
  69. package/src/label/Label.messages.tsx +0 -12
  70. 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): innerValue is string => typeof innerValue === 'string')
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 filterSelectInputOptionItem<T>(item: SelectInputOptionItem<T>, needle: string) {
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>(items: readonly SelectInputItem<T>[], needle: string) {
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 filterSelectInputOptionItem(item, needle);
128
+ return predicate(item);
126
129
  }
127
130
  case 'group': {
128
- return item.options.some((option) => filterSelectInputOptionItem(option, needle));
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
- onFilterChange({
277
- query,
278
- queryNormalized: query ? searchableString(query) : null,
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
- // TODO: Remove assertion when upgrading TypeScript to v5
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: NonNullable<T>) => renderValue(option, true))
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
- if (filterQuery !== '') {
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={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
- const resultsEmpty = needle != null && filterSelectInputItems(items, needle).length === 0;
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
- value={filterQuery}
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
- items.some((item) => item.type === 'group') &&
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
- {(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
- ))}
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 (item.value != null && (needle == null || filterSelectInputOptionItem(item, needle))) {
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
- <Label className="m-b-2">
19
- Phone number
20
- <Input value={value} id="input" onChange={({ target }) => setValue(target.value)} />
21
- </Label>
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
- {/* Instance of legacy and deprecated way of doing labels for visual testing */}
46
- <label className="control-label" htmlFor="phone-number-1">
47
- Phone number
48
- </label>
49
- <Input id="phone-number-1" value={value} onChange={({ target }) => setValue(target.value)} />
50
- <InlineAlert>{lorem10}</InlineAlert>
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
  };
@@ -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);
@@ -1,4 +1,4 @@
1
- import { render, screen } from '../test-utils';
1
+ import { render, screen } from '@testing-library/react';
2
2
 
3
3
  import RadioGroup from '.';
4
4
  import { Field } from '../field/Field';
@@ -12,6 +12,6 @@ describe('Select', () => {
12
12
  <Select options={options} selected={options[0]} onChange={() => {}} />
13
13
  </Field>,
14
14
  );
15
- expect(screen.getByLabelText(/Currency/)).toHaveTextContent('USD');
15
+ expect(screen.getByLabelText('Currency')).toHaveTextContent('USD');
16
16
  });
17
17
  });
@@ -85,7 +85,7 @@ describe('Switch', () => {
85
85
 
86
86
  it('supports `Field` for labeling', () => {
87
87
  render(
88
- <Field label="Dark mode" required>
88
+ <Field label="Dark mode">
89
89
  <Switch checked onClick={props.onClick} />
90
90
  </Field>,
91
91
  );
@@ -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,12 +0,0 @@
1
- declare const _default: {
2
- optionalLabel: {
3
- id: string;
4
- defaultMessage: string;
5
- };
6
- optionalAriaLabel: {
7
- id: string;
8
- defaultMessage: string;
9
- };
10
- };
11
- export default _default;
12
- //# sourceMappingURL=Label.messages.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Label.messages.d.ts","sourceRoot":"","sources":["../../../src/label/Label.messages.tsx"],"names":[],"mappings":";;;;;;;;;;AAEA,wBASG"}
@@ -1,3 +0,0 @@
1
- export { Label } from './Label';
2
- export type { LabelProps, LabelOptionalProps, LabelDescriptionProps } from './Label';
3
- //# sourceMappingURL=index.d.ts.map
@@ -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
- });
@@ -1,2 +0,0 @@
1
- export { Label } from './Label';
2
- export type { LabelProps, LabelOptionalProps, LabelDescriptionProps } from './Label';