@transferwise/components 46.111.0 → 46.112.0

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 (170) hide show
  1. package/build/avatarLayout/AvatarLayout.js.map +1 -1
  2. package/build/avatarLayout/AvatarLayout.mjs.map +1 -1
  3. package/build/avatarView/AvatarView.js +27 -29
  4. package/build/avatarView/AvatarView.js.map +1 -1
  5. package/build/avatarView/AvatarView.mjs +27 -29
  6. package/build/avatarView/AvatarView.mjs.map +1 -1
  7. package/build/avatarView/{NotificationDot.js → Dot.js} +14 -12
  8. package/build/avatarView/Dot.js.map +1 -0
  9. package/build/avatarView/{NotificationDot.mjs → Dot.mjs} +14 -12
  10. package/build/avatarView/Dot.mjs.map +1 -0
  11. package/build/badge/BadgeAssets.js.map +1 -1
  12. package/build/badge/BadgeAssets.mjs.map +1 -1
  13. package/build/common/panel/Panel.js +1 -0
  14. package/build/common/panel/Panel.js.map +1 -1
  15. package/build/common/panel/Panel.mjs +1 -0
  16. package/build/common/panel/Panel.mjs.map +1 -1
  17. package/build/common/responsivePanel/ResponsivePanel.js +6 -1
  18. package/build/common/responsivePanel/ResponsivePanel.js.map +1 -1
  19. package/build/common/responsivePanel/ResponsivePanel.mjs +6 -1
  20. package/build/common/responsivePanel/ResponsivePanel.mjs.map +1 -1
  21. package/build/dateInput/DateInput.js +46 -24
  22. package/build/dateInput/DateInput.js.map +1 -1
  23. package/build/dateInput/DateInput.mjs +48 -26
  24. package/build/dateInput/DateInput.mjs.map +1 -1
  25. package/build/dateLookup/DateLookup.js +5 -2
  26. package/build/dateLookup/DateLookup.js.map +1 -1
  27. package/build/dateLookup/DateLookup.mjs +5 -2
  28. package/build/dateLookup/DateLookup.mjs.map +1 -1
  29. package/build/dateLookup/dateTrigger/DateTrigger.js +2 -0
  30. package/build/dateLookup/dateTrigger/DateTrigger.js.map +1 -1
  31. package/build/dateLookup/dateTrigger/DateTrigger.mjs +2 -0
  32. package/build/dateLookup/dateTrigger/DateTrigger.mjs.map +1 -1
  33. package/build/field/Field.js +7 -2
  34. package/build/field/Field.js.map +1 -1
  35. package/build/field/Field.mjs +13 -8
  36. package/build/field/Field.mjs.map +1 -1
  37. package/build/inputs/InputGroup.js +1 -1
  38. package/build/inputs/InputGroup.js.map +1 -1
  39. package/build/inputs/InputGroup.mjs +2 -2
  40. package/build/inputs/InputGroup.mjs.map +1 -1
  41. package/build/inputs/SelectInput.js +54 -5
  42. package/build/inputs/SelectInput.js.map +1 -1
  43. package/build/inputs/SelectInput.mjs +54 -5
  44. package/build/inputs/SelectInput.mjs.map +1 -1
  45. package/build/inputs/contexts.js +8 -4
  46. package/build/inputs/contexts.js.map +1 -1
  47. package/build/inputs/contexts.mjs +7 -4
  48. package/build/inputs/contexts.mjs.map +1 -1
  49. package/build/label/Label.js +14 -8
  50. package/build/label/Label.js.map +1 -1
  51. package/build/label/Label.mjs +14 -8
  52. package/build/label/Label.mjs.map +1 -1
  53. package/build/listItem/Prompt/ListItemPrompt.js +1 -1
  54. package/build/listItem/Prompt/ListItemPrompt.js.map +1 -1
  55. package/build/listItem/Prompt/ListItemPrompt.mjs +1 -1
  56. package/build/listItem/Prompt/ListItemPrompt.mjs.map +1 -1
  57. package/build/main.css +180 -164
  58. package/build/moneyInput/MoneyInput.js +6 -5
  59. package/build/moneyInput/MoneyInput.js.map +1 -1
  60. package/build/moneyInput/MoneyInput.mjs +6 -5
  61. package/build/moneyInput/MoneyInput.mjs.map +1 -1
  62. package/build/phoneNumberInput/PhoneNumberInput.js +25 -3
  63. package/build/phoneNumberInput/PhoneNumberInput.js.map +1 -1
  64. package/build/phoneNumberInput/PhoneNumberInput.mjs +27 -5
  65. package/build/phoneNumberInput/PhoneNumberInput.mjs.map +1 -1
  66. package/build/{listItem/Prompt → prompt}/InlinePrompt/InlinePrompt.js +23 -23
  67. package/build/prompt/InlinePrompt/InlinePrompt.js.map +1 -0
  68. package/build/{listItem/Prompt → prompt}/InlinePrompt/InlinePrompt.mjs +23 -23
  69. package/build/prompt/InlinePrompt/InlinePrompt.mjs.map +1 -0
  70. package/build/styles/avatarView/AvatarView.css +17 -11
  71. package/build/styles/avatarView/Dot.css +26 -0
  72. package/build/styles/inputs/Input.css +5 -0
  73. package/build/styles/inputs/TextArea.css +5 -0
  74. package/build/styles/listItem/ListItem.css +5 -153
  75. package/build/styles/listItem/Prompt/ListItemPrompt.css +0 -153
  76. package/build/styles/main.css +180 -164
  77. package/build/types/avatarLayout/AvatarLayout.d.ts +1 -1
  78. package/build/types/avatarLayout/AvatarLayout.d.ts.map +1 -1
  79. package/build/types/avatarView/AvatarView.d.ts +1 -2
  80. package/build/types/avatarView/AvatarView.d.ts.map +1 -1
  81. package/build/types/avatarView/Dot.d.ts +8 -0
  82. package/build/types/avatarView/Dot.d.ts.map +1 -0
  83. package/build/types/badge/BadgeAssets.d.ts +1 -1
  84. package/build/types/badge/BadgeAssets.d.ts.map +1 -1
  85. package/build/types/common/panel/Panel.d.ts +2 -0
  86. package/build/types/common/panel/Panel.d.ts.map +1 -1
  87. package/build/types/common/responsivePanel/ResponsivePanel.d.ts +1 -0
  88. package/build/types/common/responsivePanel/ResponsivePanel.d.ts.map +1 -1
  89. package/build/types/dateInput/DateInput.d.ts +2 -2
  90. package/build/types/dateInput/DateInput.d.ts.map +1 -1
  91. package/build/types/dateLookup/DateLookup.d.ts.map +1 -1
  92. package/build/types/dateLookup/dateTrigger/DateTrigger.d.ts +1 -0
  93. package/build/types/dateLookup/dateTrigger/DateTrigger.d.ts.map +1 -1
  94. package/build/types/field/Field.d.ts.map +1 -1
  95. package/build/types/inputs/InputGroup.d.ts.map +1 -1
  96. package/build/types/inputs/SelectInput.d.ts +27 -1
  97. package/build/types/inputs/SelectInput.d.ts.map +1 -1
  98. package/build/types/inputs/contexts.d.ts +6 -1
  99. package/build/types/inputs/contexts.d.ts.map +1 -1
  100. package/build/types/label/Label.d.ts +5 -15
  101. package/build/types/label/Label.d.ts.map +1 -1
  102. package/build/types/listItem/Prompt/ListItemPrompt.d.ts +1 -1
  103. package/build/types/listItem/Prompt/ListItemPrompt.d.ts.map +1 -1
  104. package/build/types/moneyInput/MoneyInput.d.ts.map +1 -1
  105. package/build/types/phoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
  106. package/build/types/{listItem/Prompt → prompt}/InlinePrompt/InlinePrompt.d.ts +1 -1
  107. package/build/types/prompt/InlinePrompt/InlinePrompt.d.ts.map +1 -0
  108. package/build/types/prompt/InlinePrompt/index.d.ts.map +1 -0
  109. package/build/types/prompt/index.d.ts +3 -0
  110. package/build/types/prompt/index.d.ts.map +1 -0
  111. package/package.json +1 -1
  112. package/src/DisabledComponents.story.tsx +156 -0
  113. package/src/avatarLayout/AvatarLayout.tsx +1 -1
  114. package/src/avatarView/AvatarView.css +17 -11
  115. package/src/avatarView/AvatarView.less +1 -1
  116. package/src/avatarView/AvatarView.story.tsx +92 -36
  117. package/src/avatarView/AvatarView.tsx +35 -30
  118. package/src/avatarView/Dot.css +26 -0
  119. package/src/avatarView/Dot.less +31 -0
  120. package/src/avatarView/Dot.tsx +42 -0
  121. package/src/badge/BadgeAssets.tsx +1 -1
  122. package/src/common/panel/Panel.tsx +2 -0
  123. package/src/common/responsivePanel/ResponsivePanel.tsx +7 -1
  124. package/src/dateInput/DateInput.spec.tsx +45 -7
  125. package/src/dateInput/DateInput.story.tsx +2 -0
  126. package/src/dateInput/DateInput.tsx +65 -30
  127. package/src/dateLookup/DateLookup.spec.tsx +16 -0
  128. package/src/dateLookup/DateLookup.tsx +6 -3
  129. package/src/dateLookup/dateTrigger/DateTrigger.tsx +3 -0
  130. package/src/field/Field.tsx +6 -5
  131. package/src/inputs/Input.css +5 -0
  132. package/src/inputs/InputGroup.tsx +3 -4
  133. package/src/inputs/SelectInput.story.tsx +101 -0
  134. package/src/inputs/SelectInput.tsx +113 -5
  135. package/src/inputs/TextArea.css +5 -0
  136. package/src/inputs/_common.less +5 -0
  137. package/src/inputs/contexts.tsx +12 -3
  138. package/src/label/Label.tsx +26 -20
  139. package/src/listItem/AvatarView/ListItemAvatarView.story.tsx +89 -25
  140. package/src/listItem/ListItem.css +5 -153
  141. package/src/listItem/ListItem.less +5 -0
  142. package/src/listItem/Prompt/ListItemPrompt.css +0 -153
  143. package/src/listItem/Prompt/ListItemPrompt.less +0 -2
  144. package/src/listItem/Prompt/ListItemPrompt.tsx +1 -1
  145. package/src/main.css +180 -164
  146. package/src/main.less +1 -0
  147. package/src/moneyInput/MoneyInput.spec.tsx +16 -1
  148. package/src/moneyInput/MoneyInput.tsx +7 -6
  149. package/src/phoneNumberInput/PhoneNumberInput.spec.tsx +32 -0
  150. package/src/phoneNumberInput/PhoneNumberInput.tsx +32 -11
  151. package/src/{listItem/Prompt → prompt}/InlinePrompt/InlinePrompt.spec.tsx +2 -2
  152. package/src/{listItem/Prompt → prompt}/InlinePrompt/InlinePrompt.tsx +4 -4
  153. package/src/prompt/index.ts +6 -0
  154. package/build/avatarView/NotificationDot.js.map +0 -1
  155. package/build/avatarView/NotificationDot.mjs.map +0 -1
  156. package/build/listItem/Prompt/InlinePrompt/InlinePrompt.js.map +0 -1
  157. package/build/listItem/Prompt/InlinePrompt/InlinePrompt.mjs.map +0 -1
  158. package/build/styles/avatarView/NotificationDot.css +0 -20
  159. package/build/types/avatarView/NotificationDot.d.ts +0 -8
  160. package/build/types/avatarView/NotificationDot.d.ts.map +0 -1
  161. package/build/types/listItem/Prompt/InlinePrompt/InlinePrompt.d.ts.map +0 -1
  162. package/build/types/listItem/Prompt/InlinePrompt/index.d.ts.map +0 -1
  163. package/src/avatarView/NotificationDot.css +0 -20
  164. package/src/avatarView/NotificationDot.less +0 -24
  165. package/src/avatarView/NotificationDot.tsx +0 -35
  166. /package/build/styles/{listItem/Prompt → prompt}/InlinePrompt/InlinePrompt.css +0 -0
  167. /package/build/types/{listItem/Prompt → prompt}/InlinePrompt/index.d.ts +0 -0
  168. /package/src/{listItem/Prompt → prompt}/InlinePrompt/InlinePrompt.css +0 -0
  169. /package/src/{listItem/Prompt → prompt}/InlinePrompt/InlinePrompt.less +0 -0
  170. /package/src/{listItem/Prompt → prompt}/InlinePrompt/index.ts +0 -0
@@ -146,10 +146,34 @@ function filterSelectInputItems<T>(
146
146
 
147
147
  export interface SelectInputProps<T = string, M extends boolean = false> {
148
148
  id?: string;
149
+ /**
150
+ * Sets the `data-wds-parent` attribute on the listbox container, which is needed for complex components like DateInput to correctly manage event handling.
151
+ * @internal
152
+ */
153
+ parentId?: string;
149
154
  name?: string;
150
155
  multiple?: M;
151
156
  placeholder?: string;
152
157
  items: readonly SelectInputItem<NonNullable<T>>[];
158
+ /**
159
+ * Enables browser autocomplete integration through the search input.
160
+ * Accepts standard HTML autocomplete values (e.g., "country-name", "address-level1").
161
+ *
162
+ * Requires `filterable={true}` to enable the search input.
163
+ *
164
+ * @example
165
+ * <SelectInput
166
+ * name="country"
167
+ * autocomplete="country-name"
168
+ * filterable={true}
169
+ * items={[{
170
+ * type: 'option',
171
+ * value: 'GB',
172
+ * filterMatchers: ['United Kingdom', 'UK']
173
+ * }]}
174
+ * />
175
+ */
176
+ autocomplete?: string;
153
177
  defaultValue?: M extends true ? readonly T[] : T;
154
178
  value?: M extends true ? readonly T[] : T;
155
179
  compareValues?:
@@ -176,6 +200,8 @@ export interface SelectInputProps<T = string, M extends boolean = false> {
176
200
  UNSAFE_triggerButtonProps?: WithInputAttributesProps['inputAttributes'] & {
177
201
  'aria-label'?: string;
178
202
  };
203
+ /** Ref to the select trigger button element. */
204
+ triggerRef?: React.MutableRefObject<HTMLButtonElement | null>;
179
205
  onFilterChange?: (args: { query: string; queryNormalized: string | null }) => void;
180
206
  onChange?: (value: M extends true ? T[] : T) => void;
181
207
  onOpen?: () => void;
@@ -246,9 +272,11 @@ const noop = () => {};
246
272
 
247
273
  export function SelectInput<T = string, M extends boolean = false>({
248
274
  id: idProp,
275
+ parentId,
249
276
  name,
250
277
  multiple,
251
278
  placeholder,
279
+ autocomplete,
252
280
  items,
253
281
  defaultValue,
254
282
  value: controlledValue,
@@ -262,6 +290,7 @@ export function SelectInput<T = string, M extends boolean = false>({
262
290
  size = 'md',
263
291
  className,
264
292
  UNSAFE_triggerButtonProps,
293
+ triggerRef: externalTriggerRef,
265
294
  onFilterChange = noop,
266
295
  onChange,
267
296
  onOpen,
@@ -300,7 +329,7 @@ export function SelectInput<T = string, M extends boolean = false>({
300
329
  }
301
330
  });
302
331
 
303
- const triggerRef = useRef<HTMLButtonElement | null>(null);
332
+ const internalTriggerRef = useRef<HTMLButtonElement | null>(null);
304
333
 
305
334
  const screenSm = useScreenSize(Breakpoint.SMALL);
306
335
  const OptionsOverlay = screenSm ? Popover : BottomSheet;
@@ -368,7 +397,12 @@ export function SelectInput<T = string, M extends boolean = false>({
368
397
  value={{
369
398
  ref: (node) => {
370
399
  ref(node);
371
- triggerRef.current = node;
400
+ if (externalTriggerRef) {
401
+ // eslint-disable-next-line no-param-reassign
402
+ externalTriggerRef.current = node;
403
+ } else {
404
+ internalTriggerRef.current = node;
405
+ }
372
406
  },
373
407
  ...inputAttributes,
374
408
  ...UNSAFE_triggerButtonProps,
@@ -411,7 +445,9 @@ export function SelectInput<T = string, M extends boolean = false>({
411
445
  onClear != null
412
446
  ? () => {
413
447
  onClear();
414
- triggerRef.current?.focus({ preventScroll: true });
448
+ (externalTriggerRef?.current ?? internalTriggerRef.current)?.focus({
449
+ preventScroll: true,
450
+ });
415
451
  }
416
452
  : undefined,
417
453
  disabled: uiDisabled,
@@ -432,6 +468,7 @@ export function SelectInput<T = string, M extends boolean = false>({
432
468
  >
433
469
  <SelectInputOptions
434
470
  id={id ? `${id}Search` : undefined}
471
+ parentId={parentId}
435
472
  items={items}
436
473
  renderValue={renderValue}
437
474
  renderFooter={renderFooter}
@@ -440,7 +477,15 @@ export function SelectInput<T = string, M extends boolean = false>({
440
477
  searchInputRef={searchInputRef}
441
478
  listboxRef={listboxRef}
442
479
  filterQuery={deferredFilterQuery}
480
+ autocomplete={autocomplete}
481
+ name={name}
443
482
  onFilterChange={setFilterQuery}
483
+ onAutocompleteSelect={(matchedValue) => {
484
+ onChange?.(matchedValue as M extends true ? T[] : T);
485
+ if (!multiple) {
486
+ setOpen(false);
487
+ }
488
+ }}
444
489
  {...getListBoxLabelProps()}
445
490
  />
446
491
  </OptionsOverlay>
@@ -534,7 +579,13 @@ const SelectInputOptionsContainer = forwardRef(function SelectInputOptionsContai
534
579
  interface SelectInputOptionsProps<T = string>
535
580
  extends Pick<
536
581
  SelectInputProps<T>,
537
- 'items' | 'renderValue' | 'renderFooter' | 'filterable' | 'filterPlaceholder' | 'id'
582
+ | 'items'
583
+ | 'renderValue'
584
+ | 'renderFooter'
585
+ | 'filterable'
586
+ | 'filterPlaceholder'
587
+ | 'id'
588
+ | 'parentId'
538
589
  > {
539
590
  searchInputRef: React.MutableRefObject<HTMLInputElement | null>;
540
591
  listboxRef: React.MutableRefObject<HTMLDivElement | null>;
@@ -542,10 +593,14 @@ interface SelectInputOptionsProps<T = string>
542
593
  onFilterChange: (query: string) => void;
543
594
  listBoxLabel?: string;
544
595
  listBoxLabelledBy?: string;
596
+ autocomplete?: string;
597
+ name?: string;
598
+ onAutocompleteSelect?: (value: T) => void;
545
599
  }
546
600
 
547
601
  function SelectInputOptions<T = string>({
548
602
  id,
603
+ parentId,
549
604
  items,
550
605
  renderValue = String,
551
606
  renderFooter,
@@ -557,6 +612,9 @@ function SelectInputOptions<T = string>({
557
612
  onFilterChange,
558
613
  listBoxLabel,
559
614
  listBoxLabelledBy,
615
+ autocomplete,
616
+ name,
617
+ onAutocompleteSelect,
560
618
  }: SelectInputOptionsProps<T>) {
561
619
  const intl = useIntl();
562
620
  const virtualiserHandlerRef = useRef<VirtualizerHandle>(null);
@@ -640,6 +698,35 @@ function SelectInputOptions<T = string>({
640
698
  );
641
699
  };
642
700
 
701
+ const findMatchingItem = (autocompleteValue: string): T | null => {
702
+ const flatOptions = items
703
+ .flatMap((item) =>
704
+ item.type === 'group' ? item.options : item.type === 'option' ? [item] : [],
705
+ )
706
+ .filter(
707
+ (item): item is SelectInputOptionItem<NonNullable<T>> =>
708
+ item.type === 'option' && item.value != null,
709
+ );
710
+
711
+ const exactMatch = flatOptions.find(
712
+ (option) =>
713
+ String(option.value) === autocompleteValue ||
714
+ option.filterMatchers?.some((matcher) => matcher === autocompleteValue),
715
+ );
716
+
717
+ if (exactMatch) {
718
+ return exactMatch.value;
719
+ }
720
+
721
+ const fuzzyMatch = flatOptions.find((option) =>
722
+ option.filterMatchers?.some((matcher) =>
723
+ matcher.toLowerCase().includes(autocompleteValue.toLowerCase()),
724
+ ),
725
+ );
726
+
727
+ return fuzzyMatch ? fuzzyMatch.value : null;
728
+ };
729
+
643
730
  return (
644
731
  <ListboxBase.Options
645
732
  as={SelectInputOptionsContainer}
@@ -660,6 +747,8 @@ function SelectInputOptions<T = string>({
660
747
  <SearchInput
661
748
  ref={searchInputRef}
662
749
  id={id}
750
+ name={name}
751
+ autoComplete={autocomplete}
663
752
  role="combobox"
664
753
  shape="rectangle"
665
754
  placeholder={filterPlaceholder}
@@ -679,8 +768,26 @@ function SelectInputOptions<T = string>({
679
768
  onChange={(event) => {
680
769
  // Free up resources and ensure not to go out of bounds when the
681
770
  // resulting item count is less than before
771
+ const inputValue = event.currentTarget.value;
772
+
773
+ // Free up resources and ensure not to go out of bounds
682
774
  setMountedIndexes([]);
683
- onFilterChange(event.currentTarget.value);
775
+ onFilterChange(inputValue);
776
+ }}
777
+ onInput={(event) => {
778
+ const inputValue = event.currentTarget.value;
779
+ const inputElement = event.currentTarget;
780
+
781
+ if (autocomplete && onAutocompleteSelect && inputValue) {
782
+ setTimeout(() => {
783
+ if (inputElement.value === inputValue && inputValue.length > 2) {
784
+ const matchedValue = findMatchingItem(inputValue);
785
+ if (matchedValue !== null) {
786
+ onAutocompleteSelect(matchedValue);
787
+ }
788
+ }
789
+ }, 50);
790
+ }
684
791
  }}
685
792
  />
686
793
  </div>
@@ -696,6 +803,7 @@ function SelectInputOptions<T = string>({
696
803
  items.some((item) => item.type === 'group') &&
697
804
  'np-select-input-listbox-container--has-group',
698
805
  )}
806
+ data-wds-parent={parentId ?? undefined}
699
807
  >
700
808
  {resultsEmpty ? (
701
809
  <div id={statusId} className="np-select-input-options-status">
@@ -18,6 +18,11 @@
18
18
  transition-duration: 300ms;
19
19
  /* TODO: Remove these overrides once `.form-control` isn’t used anymore */
20
20
  }
21
+ .disabled .np-form-control,
22
+ :disabled .np-form-control {
23
+ opacity: 1;
24
+ opacity: initial;
25
+ }
21
26
  .np-form-control:focus-visible {
22
27
  outline: none;
23
28
  }
@@ -15,6 +15,11 @@
15
15
  transition-timing-function: ease-in-out;
16
16
  transition-duration: 300ms;
17
17
 
18
+ .disabled &,
19
+ :disabled & {
20
+ opacity: unset;
21
+ }
22
+
18
23
  &:focus-visible {
19
24
  outline: none;
20
25
  }
@@ -1,7 +1,12 @@
1
1
  import { createContext, useContext } from 'react';
2
2
 
3
- const FieldLabelIdContext = createContext<string | undefined>(undefined);
4
- export const FieldLabelIdContextProvider = FieldLabelIdContext.Provider;
3
+ type FieldLabelContextType = {
4
+ id?: string;
5
+ ref?: React.RefObject<HTMLLabelElement>;
6
+ };
7
+
8
+ const FieldLabelContext = createContext<FieldLabelContextType | undefined>(undefined);
9
+ export const FieldLabelContextProvider = FieldLabelContext.Provider;
5
10
 
6
11
  const InputIdContext = createContext<string | undefined>(undefined);
7
12
  export const InputIdContextProvider = InputIdContext.Provider;
@@ -18,7 +23,7 @@ interface UseInputAttributesArgs {
18
23
  }
19
24
 
20
25
  export function useInputAttributes({ nonLabelable }: UseInputAttributesArgs = {}) {
21
- const labelId = useContext(FieldLabelIdContext);
26
+ const labelId = useContext(FieldLabelContext)?.id;
22
27
  return {
23
28
  id: useContext(InputIdContext),
24
29
  'aria-labelledby': nonLabelable ? labelId : undefined,
@@ -27,6 +32,10 @@ export function useInputAttributes({ nonLabelable }: UseInputAttributesArgs = {}
27
32
  } satisfies React.HTMLAttributes<HTMLElement>;
28
33
  }
29
34
 
35
+ export function useFieldLabelRef() {
36
+ return useContext(FieldLabelContext)?.ref;
37
+ }
38
+
30
39
  export interface WithInputAttributesProps {
31
40
  inputAttributes: ReturnType<typeof useInputAttributes>;
32
41
  }
@@ -3,7 +3,7 @@ import messages from './Label.messages';
3
3
  import { useIntl } from 'react-intl';
4
4
  import Body from '../body';
5
5
  import { CommonProps } from '../common';
6
- import { PropsWithChildren } from 'react';
6
+ import { forwardRef, PropsWithChildren } from 'react';
7
7
 
8
8
  export type LabelProps = {
9
9
  id?: string;
@@ -21,25 +21,29 @@ export type LabelProps = {
21
21
  * <Field label={..} description={..} required={..}>..</Field>
22
22
  * ```
23
23
  */
24
- const Label = ({ className, children, htmlFor, id }: LabelProps) => {
25
- return (
26
- <label
27
- id={id}
28
- htmlFor={htmlFor}
29
- className={clsx(
30
- 'np-label d-flex flex-column np-text-body-default-bold text-primary m-b-0',
31
- className,
32
- )}
33
- >
34
- {children}
35
- </label>
36
- );
37
- };
24
+ const Label = forwardRef<HTMLLabelElement, LabelProps>(
25
+ ({ className, children, htmlFor, id }: LabelProps, ref) => {
26
+ return (
27
+ <label
28
+ ref={ref}
29
+ id={id}
30
+ htmlFor={htmlFor}
31
+ className={clsx(
32
+ 'np-label d-flex flex-column np-text-body-default-bold text-primary m-b-0',
33
+ className,
34
+ )}
35
+ >
36
+ {children}
37
+ </label>
38
+ );
39
+ },
40
+ );
41
+
42
+ Label.displayName = 'Label';
38
43
 
39
44
  export type LabelOptionalProps = PropsWithChildren<CommonProps>;
40
45
 
41
- // eslint-disable-next-line functional/immutable-data
42
- Label.Optional = function Optional({ children, className }: LabelOptionalProps) {
46
+ const Optional = function Optional({ children, className }: LabelOptionalProps) {
43
47
  const { formatMessage } = useIntl();
44
48
  return (
45
49
  <div>
@@ -53,8 +57,7 @@ Label.Optional = function Optional({ children, className }: LabelOptionalProps)
53
57
 
54
58
  export type LabelDescriptionProps = PropsWithChildren<CommonProps> & { id?: string };
55
59
 
56
- // eslint-disable-next-line functional/immutable-data
57
- Label.Description = function Description({ id, children, className }: LabelDescriptionProps) {
60
+ const Description = function Description({ id, children, className }: LabelDescriptionProps) {
58
61
  return children ? (
59
62
  <Body id={id} className={clsx('text-secondary', className)}>
60
63
  {children}
@@ -62,4 +65,7 @@ Label.Description = function Description({ id, children, className }: LabelDescr
62
65
  ) : null;
63
66
  };
64
67
 
65
- export { Label };
68
+ // eslint-disable-next-line functional/immutable-data
69
+ const LabelNamespace = Object.assign(Label, { Optional, Description });
70
+
71
+ export { LabelNamespace as Label };
@@ -47,6 +47,8 @@ const BADGES = {
47
47
  </div>
48
48
  ),
49
49
  },
50
+ 'Notification badge': { type: 'notification' },
51
+ 'Online badge': { type: 'online' },
50
52
  } as const;
51
53
 
52
54
  export default {
@@ -61,7 +63,6 @@ export default {
61
63
  size: 48,
62
64
  selected: false,
63
65
  badge: { type: 'action' },
64
- notification: false,
65
66
  profileName: undefined,
66
67
  profileType: undefined,
67
68
  imgSrc: undefined,
@@ -88,20 +89,23 @@ export default {
88
89
  type: { summary: 'ProfileType' },
89
90
  },
90
91
  },
91
- notification: {
92
- control: 'boolean',
93
- description: 'Shows notification dot',
94
- },
95
- selected: {
96
- control: 'boolean',
97
- description: 'Toggles selected state',
98
- },
99
92
  badge: {
100
- description: 'Badge configuration object',
93
+ control: 'select',
94
+ options: ['action', 'notification', 'online'],
95
+ description: 'Badge type',
96
+ mapping: {
97
+ action: { type: 'action' },
98
+ notification: { type: 'notification' },
99
+ online: { type: 'online' },
100
+ },
101
101
  table: {
102
102
  type: { summary: 'AvatarViewBadgeProps' },
103
103
  },
104
104
  },
105
+ selected: {
106
+ control: 'boolean',
107
+ description: 'Toggles selected state',
108
+ },
105
109
  children: {
106
110
  table: {
107
111
  type: { summary: 'ReactNode' },
@@ -243,8 +247,48 @@ export const Sizes: Story = {
243
247
  };
244
248
 
245
249
  /**
246
- * Similarly, AvatarView also support a notification dot, which also adjusts to the Avatar's size. <br />
247
- * **NB:** You cannot use notification and badge at the same time badge will always take precedence over notification.
250
+ * AvatarView supports different types of badges for additional context and information. <br />
251
+ * Refer to the [design documentation](https://wise.design/components/avatar#:~:text=support%20the%20information.-,With%20badge,-Badges%20contain%20additional) for details.
252
+ */
253
+ export const Badges: Story = {
254
+ args: {
255
+ imgSrc: '../avatar-square-dude.webp',
256
+ },
257
+ argTypes: hideControls([
258
+ 'profileName',
259
+ 'imgSrc',
260
+ 'profileType',
261
+ 'notification',
262
+ 'dot',
263
+ 'selected',
264
+ ]),
265
+ parameters: {
266
+ docs: {
267
+ canvas: {
268
+ sourceState: 'hidden',
269
+ },
270
+ },
271
+ },
272
+ render: (args) => {
273
+ return (
274
+ <List>
275
+ {Object.entries(BADGES).map(([title, badge]) => (
276
+ <ListItem
277
+ key={title}
278
+ title={title}
279
+ subtitle={lorem10}
280
+ media={<ListItem.AvatarView {...args} badge={badge} />}
281
+ control={CONTROLS.iconButton}
282
+ />
283
+ ))}
284
+ </List>
285
+ );
286
+ },
287
+ };
288
+
289
+ /**
290
+ * `notification` is a particular `type` of Badge that appears in the top right corner as a red dot. It also adjusts to the Avatar's size. <br />
291
+ * **NB:** You can only choose one badge at a time, so the notification badge cannot be combined with any other badge type.
248
292
  */
249
293
  export const Notification: Story = {
250
294
  parameters: {
@@ -263,6 +307,7 @@ export const Notification: Story = {
263
307
  'profileType',
264
308
  'size',
265
309
  'notification',
310
+ 'dot',
266
311
  'selected',
267
312
  ]),
268
313
  render: (args) => {
@@ -274,7 +319,7 @@ export const Notification: Story = {
274
319
  title={`Size ${size}`}
275
320
  subtitle={lorem10}
276
321
  media={
277
- <ListItem.AvatarView {...args} size={size} notification>
322
+ <ListItem.AvatarView {...args} size={size} badge={{ type: 'notification' }}>
278
323
  <Taxi />
279
324
  </ListItem.AvatarView>
280
325
  }
@@ -287,14 +332,11 @@ export const Notification: Story = {
287
332
  };
288
333
 
289
334
  /**
290
- * AvatarView supports different type of badges for additional context and information. <br />
291
- * Refer to the [design documentation](https://wise.design/components/avatar#:~:text=support%20the%20information.-,With%20badge,-Badges%20contain%20additional) for details.
335
+ * Similarly, Badge also has an `online` `type` which is green and also adjusts to the Avatar's size. <br />
336
+ * **NB:** You can only choose one badge at a time, so the online badge cannot be combined with any other badge type.
292
337
  */
293
- export const Badges: Story = {
294
- args: {
295
- imgSrc: '../avatar-square-dude.webp',
296
- },
297
- argTypes: hideControls(['profileName', 'imgSrc', 'profileType', 'notification', 'selected']),
338
+ export const Online: Story = {
339
+ tags: ['new'],
298
340
  parameters: {
299
341
  docs: {
300
342
  canvas: {
@@ -302,15 +344,30 @@ export const Badges: Story = {
302
344
  },
303
345
  },
304
346
  },
347
+ args: {
348
+ badge: undefined,
349
+ },
350
+ argTypes: hideControls([
351
+ 'profileName',
352
+ 'imgSrc',
353
+ 'profileType',
354
+ 'size',
355
+ 'notification',
356
+ 'selected',
357
+ ]),
305
358
  render: (args) => {
306
359
  return (
307
360
  <List>
308
- {Object.entries(BADGES).map(([title, badge]) => (
361
+ {SIZES.map((size) => (
309
362
  <ListItem
310
- key={title}
311
- title={title}
363
+ key={size}
364
+ title={`Size ${size}`}
312
365
  subtitle={lorem10}
313
- media={<ListItem.AvatarView {...args} badge={badge} />}
366
+ media={
367
+ <ListItem.AvatarView {...args} size={size} badge={{ type: 'online' }}>
368
+ <Taxi />
369
+ </ListItem.AvatarView>
370
+ }
314
371
  control={CONTROLS.iconButton}
315
372
  />
316
373
  ))}
@@ -323,7 +380,14 @@ export const Badges: Story = {
323
380
  * AvatarView supports selected state for interactive contexts.
324
381
  */
325
382
  export const Selected: Story = {
326
- argTypes: hideControls(['profileName', 'imgSrc', 'profileType', 'notification', 'selected']),
383
+ argTypes: hideControls([
384
+ 'profileName',
385
+ 'imgSrc',
386
+ 'profileType',
387
+ 'notification',
388
+ 'dot',
389
+ 'selected',
390
+ ]),
327
391
  render: (args) => {
328
392
  return (
329
393
  <List>