@westpac/ui 1.16.0 → 1.19.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 (79) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/component-type.json +1 -1
  3. package/dist/components/button/button.component.d.ts +2 -17
  4. package/dist/components/button/button.component.js +9 -4
  5. package/dist/components/button/button.types.d.ts +16 -6
  6. package/dist/components/button-group/button-group.component.d.ts +2 -1
  7. package/dist/components/button-group/button-group.component.js +6 -3
  8. package/dist/components/checkbox-group/checkbox-group.component.d.ts +11 -2
  9. package/dist/components/checkbox-group/checkbox-group.component.js +10 -3
  10. package/dist/components/checkbox-group/components/checkbox-group-checkbox/checkbox-group-checkbox.component.d.ts +1 -1
  11. package/dist/components/checkbox-group/components/checkbox-group-checkbox/checkbox-group-checkbox.component.js +5 -6
  12. package/dist/components/date-picker/components/date-field/components/date-segment/date-segment.component.d.ts +6 -5
  13. package/dist/components/date-picker/components/date-field/components/date-segment/date-segment.component.js +6 -4
  14. package/dist/components/date-picker/components/date-field/date-field.component.d.ts +5 -5
  15. package/dist/components/date-picker/components/date-field/date-field.component.js +7 -3
  16. package/dist/components/date-picker/date-picker.component.d.ts +19 -2
  17. package/dist/components/date-picker/date-picker.component.js +10 -5
  18. package/dist/components/date-picker/date-picker.styles.js +5 -5
  19. package/dist/components/link/link.component.d.ts +4 -12
  20. package/dist/components/link/link.component.js +30 -7
  21. package/dist/components/link/link.types.d.ts +26 -3
  22. package/dist/components/modal/components/modal-dialog/components/modal-dialog-footer/modal-dialog-footer.component.d.ts +1 -1
  23. package/dist/components/modal/components/modal-dialog/components/modal-dialog-footer/modal-dialog-footer.component.js +14 -7
  24. package/dist/components/modal/components/modal-dialog/components/modal-dialog-footer/modal-dialog-footer.types.d.ts +11 -0
  25. package/dist/components/multi-select/components/multi-select-list-box/components/multi-select-option/multi-select-option.component.js +3 -7
  26. package/dist/components/multi-select/components/multi-select-list-box/components/multi-select-select-all-option/multi-select-select-all-option.component.js +3 -3
  27. package/dist/components/multi-select/components/multi-select-popover/multi-select-popover.component.js +5 -2
  28. package/dist/components/multi-select/components/multi-select-searchbar/multi-select-searchbar.component.js +4 -7
  29. package/dist/components/multi-select/multi-select.component.d.ts +14 -3
  30. package/dist/components/multi-select/multi-select.component.js +10 -16
  31. package/dist/components/radio-group/components/radio-group-radio/radio-group-radio.component.d.ts +1 -1
  32. package/dist/components/radio-group/components/radio-group-radio/radio-group-radio.component.js +5 -6
  33. package/dist/components/radio-group/radio-group.component.d.ts +10 -2
  34. package/dist/components/radio-group/radio-group.component.js +10 -3
  35. package/dist/components/selector/components/selector-button-group/selector-button-group.component.d.ts +9 -2
  36. package/dist/components/selector/components/selector-button-group/selector-button-group.component.js +8 -3
  37. package/dist/components/selector/components/selector-checkbox-group/selector-checkbox-group.component.d.ts +6 -2
  38. package/dist/components/selector/components/selector-checkbox-group/selector-checkbox-group.component.js +8 -3
  39. package/dist/components/selector/components/selector-radio-group/selector-radio-group.component.d.ts +6 -2
  40. package/dist/components/selector/components/selector-radio-group/selector-radio-group.component.js +8 -3
  41. package/dist/components/selector/selector.component.d.ts +3 -1
  42. package/dist/components/selector/selector.component.js +16 -5
  43. package/dist/components/switch/switch.component.d.ts +8 -2
  44. package/dist/components/switch/switch.component.js +6 -4
  45. package/dist/css/westpac-ui.css +12 -12
  46. package/dist/css/westpac-ui.min.css +12 -12
  47. package/dist/hook/focus-manager-ref.hook.d.ts +29 -0
  48. package/dist/hook/focus-manager-ref.hook.js +23 -0
  49. package/dist/hook/index.d.ts +1 -0
  50. package/dist/index.d.ts +1 -0
  51. package/package.json +5 -3
  52. package/src/components/button/button.component.tsx +10 -9
  53. package/src/components/button/button.types.ts +21 -8
  54. package/src/components/button-group/button-group.component.tsx +26 -16
  55. package/src/components/checkbox-group/checkbox-group.component.tsx +24 -14
  56. package/src/components/checkbox-group/components/checkbox-group-checkbox/checkbox-group-checkbox.component.tsx +7 -7
  57. package/src/components/date-picker/components/date-field/components/date-segment/date-segment.component.tsx +10 -4
  58. package/src/components/date-picker/components/date-field/date-field.component.tsx +17 -3
  59. package/src/components/date-picker/date-picker.component.tsx +32 -21
  60. package/src/components/date-picker/date-picker.styles.ts +5 -5
  61. package/src/components/link/link.component.tsx +60 -12
  62. package/src/components/link/link.types.ts +31 -4
  63. package/src/components/modal/components/modal-dialog/components/modal-dialog-footer/modal-dialog-footer.component.tsx +16 -2
  64. package/src/components/modal/components/modal-dialog/components/modal-dialog-footer/modal-dialog-footer.types.ts +12 -0
  65. package/src/components/multi-select/components/multi-select-list-box/components/multi-select-option/multi-select-option.component.tsx +3 -5
  66. package/src/components/multi-select/components/multi-select-list-box/components/multi-select-select-all-option/multi-select-select-all-option.component.tsx +4 -3
  67. package/src/components/multi-select/components/multi-select-popover/multi-select-popover.component.tsx +8 -2
  68. package/src/components/multi-select/components/multi-select-searchbar/multi-select-searchbar.component.tsx +3 -6
  69. package/src/components/multi-select/multi-select.component.tsx +33 -28
  70. package/src/components/radio-group/components/radio-group-radio/radio-group-radio.component.tsx +10 -7
  71. package/src/components/radio-group/radio-group.component.tsx +24 -14
  72. package/src/components/selector/components/selector-button-group/selector-button-group.component.tsx +23 -14
  73. package/src/components/selector/components/selector-checkbox-group/selector-checkbox-group.component.tsx +12 -3
  74. package/src/components/selector/components/selector-radio-group/selector-radio-group.component.tsx +21 -11
  75. package/src/components/selector/selector.component.tsx +16 -5
  76. package/src/components/switch/switch.component.tsx +10 -12
  77. package/src/hook/focus-manager-ref.hook.ts +56 -0
  78. package/src/hook/index.ts +1 -0
  79. package/src/index.ts +1 -0
@@ -1,4 +1,5 @@
1
1
  'use client';
2
+ import { focusWithoutScrolling } from '@react-aria/utils';
2
3
  import React, { useCallback, useContext, useMemo } from 'react';
3
4
  import { useOption, useFocusRing, mergeProps } from 'react-aria';
4
5
  import { TickIcon } from '../../../../../icon/index.js';
@@ -40,12 +41,11 @@ export function MultiSelectSelectAllOption() {
40
41
  var _listBoxRef_current;
41
42
  e.preventDefault();
42
43
  const firstItem = (_listBoxRef_current = listBoxRef.current) === null || _listBoxRef_current === void 0 ? void 0 : _listBoxRef_current.querySelector('[data-key]');
43
- firstItem === null || firstItem === void 0 ? void 0 : firstItem.focus();
44
+ if (firstItem) focusWithoutScrolling(firstItem);
44
45
  }
45
46
  if (e.key === 'ArrowUp') {
46
- var _inputRef_current;
47
47
  e.preventDefault();
48
- (_inputRef_current = inputRef.current) === null || _inputRef_current === void 0 ? void 0 : _inputRef_current.focus();
48
+ if (inputRef.current) focusWithoutScrolling(inputRef.current);
49
49
  }
50
50
  if (e.key === 'Enter' || e.key === ' ') {
51
51
  e.preventDefault();
@@ -14,6 +14,10 @@ export function MultiSelectPopover({ children, className, ...props }) {
14
14
  setIsPopoverSmaller(popoverWidth < buttonWidth);
15
15
  }
16
16
  }, []);
17
+ const positioningState = {
18
+ ...overlayState,
19
+ close: ()=>undefined
20
+ };
17
21
  const { popoverProps } = usePopover({
18
22
  ...props,
19
23
  placement: placement,
@@ -23,7 +27,7 @@ export function MultiSelectPopover({ children, className, ...props }) {
23
27
  shouldFlip: true,
24
28
  shouldCloseOnInteractOutside: ()=>false,
25
29
  offset: 6
26
- }, overlayState);
30
+ }, positioningState);
27
31
  const brandContainer = useMemo(()=>{
28
32
  if (typeof window !== 'undefined') {
29
33
  return document.querySelector('[data-brand]') || document.querySelector('[class^="theme-"], [class*=" theme-"]') || document.body;
@@ -57,7 +61,6 @@ export function MultiSelectPopover({ children, className, ...props }) {
57
61
  }
58
62
  },
59
63
  role: "dialog",
60
- "aria-modal": "true",
61
64
  "aria-label": "Options list with filter"
62
65
  }, children, React.createElement(DismissButton, {
63
66
  onDismiss: ()=>overlayState.close()
@@ -1,3 +1,4 @@
1
+ import { focusWithoutScrolling } from '@react-aria/utils';
1
2
  import React, { useContext, useCallback } from 'react';
2
3
  import { Button } from '../../../../components/button/index.js';
3
4
  import { ClearIcon, SearchIcon } from '../../../../components/icon/index.js';
@@ -10,14 +11,10 @@ export function MultiSelectSearchbar({ filterText, setFilterText, closeBtnRef })
10
11
  const styles = searchbarStyles();
11
12
  const handleInputKeyDown = useCallback((e)=>{
12
13
  if (e.key === 'ArrowDown') {
14
+ var _listBoxRef_current;
13
15
  e.preventDefault();
14
- if (selectAllRef.current) {
15
- selectAllRef.current.focus();
16
- } else {
17
- var _listBoxRef_current;
18
- const firstItem = (_listBoxRef_current = listBoxRef.current) === null || _listBoxRef_current === void 0 ? void 0 : _listBoxRef_current.querySelector('[data-key]');
19
- firstItem === null || firstItem === void 0 ? void 0 : firstItem.focus();
20
- }
16
+ const target = selectAllRef.current || ((_listBoxRef_current = listBoxRef.current) === null || _listBoxRef_current === void 0 ? void 0 : _listBoxRef_current.querySelector('[data-key]'));
17
+ if (target) focusWithoutScrolling(target);
21
18
  }
22
19
  if (e.key === 'Escape' && filterText.length > 0) {
23
20
  e.stopPropagation();
@@ -1,7 +1,18 @@
1
- import React from 'react';
1
+ import React, { ForwardedRef } from 'react';
2
2
  import type { MultiSelectContextProps, MultiSelectItemProps, MultiSelectProps, MultiSelectValue } from './multi-select.types.js';
3
3
  export declare const MultiSelectContext: React.Context<MultiSelectContextProps>;
4
- export declare function BaseMultiSelect<T extends MultiSelectValue = MultiSelectValue>({ size, listBoxProps, selectionMode, selectedKeys, onSelectionChange, placeholder, showSingleSectionTitle, placement, portalContainer, id, hideFilter, hideSelectAll, width, ...props }: MultiSelectProps<T>): import("react/jsx-runtime").JSX.Element;
5
- export declare const MultiSelect: React.MemoExoticComponent<typeof BaseMultiSelect>;
4
+ export declare function BaseMultiSelect<T extends MultiSelectValue = MultiSelectValue>({ size, listBoxProps, selectionMode, selectedKeys, onSelectionChange, placeholder, showSingleSectionTitle, placement, portalContainer, id, hideFilter, hideSelectAll, width, ...props }: MultiSelectProps<T>, forwardedRef: ForwardedRef<HTMLButtonElement>): import("react/jsx-runtime").JSX.Element;
5
+ export declare const MultiSelect: React.MemoExoticComponent<React.ForwardRefExoticComponent<{
6
+ hideFilter?: boolean;
7
+ hideSelectAll?: boolean;
8
+ listBoxProps?: Omit<import("react-aria").AriaListBoxOptions<MultiSelectValue>, "state" | "selectionMode"> | undefined;
9
+ id?: string;
10
+ placeholder?: string;
11
+ placement?: import("react-aria").AriaPopoverProps["placement"];
12
+ portalContainer?: Element;
13
+ showSingleSectionTitle?: boolean;
14
+ size?: import("./components/multi-select-list-box-trigger/multi-select-list-box-trigger.types.js").MultiSelectSize;
15
+ width?: import("../../types/responsive-variants.types.js").ResponsiveVariants<2 | 1 | 3 | "full" | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 20 | 30 | undefined>;
16
+ } & import("react-stately").ListProps<MultiSelectValue> & React.RefAttributes<HTMLButtonElement>>>;
6
17
  export declare const MultiSelectItem: (props: MultiSelectItemProps) => JSX.Element;
7
18
  export { Section as MultiSelectSection } from 'react-stately';
@@ -1,6 +1,7 @@
1
1
  'use client';
2
- import React, { useRef, useState, memo, createContext } from 'react';
3
- import { useFilter, useOverlayTrigger } from 'react-aria';
2
+ import { focusWithoutScrolling } from '@react-aria/utils';
3
+ import React, { forwardRef, useRef, useState, memo, createContext } from 'react';
4
+ import { useFilter, useObjectRef, useOverlayTrigger } from 'react-aria';
4
5
  import { Item, useListState, useOverlayTriggerState } from 'react-stately';
5
6
  import { MultiSelectDropdown } from './components/multi-select-dropdown/multi-select-dropdown.component.js';
6
7
  import { MultiSelectListBoxTrigger } from './components/multi-select-list-box-trigger/multi-select-list-box-trigger.component.js';
@@ -28,7 +29,7 @@ export const MultiSelectContext = createContext({
28
29
  overlayProps: {},
29
30
  hideSelectAll: false
30
31
  });
31
- export function BaseMultiSelect({ size = 'medium', listBoxProps, selectionMode = 'multiple', selectedKeys, onSelectionChange, placeholder = 'Select', showSingleSectionTitle = false, placement = 'bottom left', portalContainer, id, hideFilter = false, hideSelectAll = false, width = 'full', ...props }) {
32
+ export function BaseMultiSelect({ size = 'medium', listBoxProps, selectionMode = 'multiple', selectedKeys, onSelectionChange, placeholder = 'Select', showSingleSectionTitle = false, placement = 'bottom left', portalContainer, id, hideFilter = false, hideSelectAll = false, width = 'full', ...props }, forwardedRef) {
32
33
  const [filterText, setFilterText] = useState('');
33
34
  const filter = useFilter({
34
35
  sensitivity: 'base'
@@ -41,7 +42,7 @@ export function BaseMultiSelect({ size = 'medium', listBoxProps, selectionMode =
41
42
  filter: (nodes)=>filterNodes(nodes, filterText, (value, string)=>filter.contains(value, string))
42
43
  });
43
44
  const inputRef = useRef(null);
44
- const buttonRef = useRef(null);
45
+ const buttonRef = useObjectRef(forwardedRef);
45
46
  const popoverRef = useRef(null);
46
47
  const selectAllRef = useRef(null);
47
48
  const listBoxRef = useRef(null);
@@ -49,17 +50,9 @@ export function BaseMultiSelect({ size = 'medium', listBoxProps, selectionMode =
49
50
  onOpenChange: (isOpen)=>{
50
51
  if (isOpen) {
51
52
  requestAnimationFrame(()=>{
52
- if (!hideFilter) {
53
- var _inputRef_current;
54
- (_inputRef_current = inputRef.current) === null || _inputRef_current === void 0 ? void 0 : _inputRef_current.focus();
55
- } else if (selectionMode === 'multiple' && !hideSelectAll) {
56
- var _selectAllRef_current;
57
- (_selectAllRef_current = selectAllRef.current) === null || _selectAllRef_current === void 0 ? void 0 : _selectAllRef_current.focus();
58
- } else {
59
- var _listBoxRef_current;
60
- const firstItem = (_listBoxRef_current = listBoxRef.current) === null || _listBoxRef_current === void 0 ? void 0 : _listBoxRef_current.querySelector('[data-key]');
61
- firstItem === null || firstItem === void 0 ? void 0 : firstItem.focus();
62
- }
53
+ var _listBoxRef_current;
54
+ const target = !hideFilter && inputRef.current || selectionMode === 'multiple' && !hideSelectAll && selectAllRef.current || ((_listBoxRef_current = listBoxRef.current) === null || _listBoxRef_current === void 0 ? void 0 : _listBoxRef_current.querySelector('[data-key]'));
55
+ if (target) focusWithoutScrolling(target);
63
56
  });
64
57
  }
65
58
  if (!isOpen) {
@@ -103,6 +96,7 @@ export function BaseMultiSelect({ size = 'medium', listBoxProps, selectionMode =
103
96
  ...listBoxProps
104
97
  })));
105
98
  }
106
- export const MultiSelect = memo(BaseMultiSelect);
99
+ export const MultiSelect = memo(forwardRef(BaseMultiSelect));
100
+ MultiSelect.displayName = 'MultiSelect';
107
101
  export const MultiSelectItem = Item;
108
102
  export { Section as MultiSelectSection } from 'react-stately';
@@ -3,4 +3,4 @@ export declare const RadioGroupRadio: React.ForwardRefExoticComponent<{
3
3
  className?: string;
4
4
  hint?: React.ReactNode;
5
5
  label: React.ReactNode;
6
- } & Omit<import("react-aria").AriaRadioProps, "children"> & React.RefAttributes<HTMLLabelElement>>;
6
+ } & Omit<import("react-aria").AriaRadioProps, "children"> & React.RefAttributes<HTMLInputElement>>;
@@ -1,15 +1,15 @@
1
1
  'use client';
2
- import React, { forwardRef, useContext, useRef } from 'react';
3
- import { VisuallyHidden, mergeProps, useFocusRing, useRadio } from 'react-aria';
2
+ import React, { forwardRef, useContext } from 'react';
3
+ import { VisuallyHidden, mergeProps, useFocusRing, useObjectRef, useRadio } from 'react-aria';
4
4
  import { RadioGroupContext } from '../../radio-group.component.js';
5
5
  import { styles as radioStyles } from './radio-group-radio.styles.js';
6
6
  function BaseRadioGroupRadio({ className, hint, label, ...props }, ref) {
7
7
  const { state, size, orientation } = useContext(RadioGroupContext);
8
- const localRef = useRef(null);
8
+ const inputRef = useObjectRef(ref);
9
9
  const { inputProps, labelProps, isSelected, isDisabled } = useRadio({
10
10
  ...props,
11
11
  children: label
12
- }, state, localRef);
12
+ }, state, inputRef);
13
13
  const { isFocusVisible, focusProps } = useFocusRing();
14
14
  const styles = radioStyles({
15
15
  isDisabled,
@@ -22,13 +22,12 @@ function BaseRadioGroupRadio({ className, hint, label, ...props }, ref) {
22
22
  className: styles.base({
23
23
  className
24
24
  }),
25
- ref: ref,
26
25
  ...labelProps
27
26
  }, React.createElement(VisuallyHidden, {
28
27
  elementType: "span"
29
28
  }, React.createElement("input", {
30
29
  ...mergeProps(inputProps, focusProps),
31
- ref: localRef
30
+ ref: inputRef
32
31
  })), React.createElement("span", {
33
32
  className: styles.selector()
34
33
  }), React.createElement("span", {
@@ -1,4 +1,12 @@
1
1
  import React from 'react';
2
- import { type RadioGroupContextState, type RadioGroupProps } from './radio-group.types.js';
2
+ import { FocusHandle } from '../../hook/focus-manager-ref.hook.js';
3
+ import { type RadioGroupContextState } from './radio-group.types.js';
3
4
  export declare const RadioGroupContext: React.Context<RadioGroupContextState>;
4
- export declare function RadioGroup({ className, radios, label, orientation, showAmount, size, errorMessage, hintMessage, ...props }: RadioGroupProps): import("react/jsx-runtime").JSX.Element;
5
+ export declare const RadioGroup: React.ForwardRefExoticComponent<{
6
+ className?: string;
7
+ errorMessage?: string | string[];
8
+ hintMessage?: import("../index.js").HintProps["children"];
9
+ radios: import("./components/radio-group-radio/radio-group-radio.types.js").RadioGroupRadioProps[];
10
+ showAmount?: number;
11
+ size?: "medium" | "large";
12
+ } & Omit<import("react-aria").AriaRadioGroupProps, "errorMessage" | "description"> & React.RefAttributes<FocusHandle>>;
@@ -1,9 +1,10 @@
1
1
  'use client';
2
- import React, { createContext, useEffect, useId, useMemo, useRef, useState } from 'react';
2
+ import React, { createContext, forwardRef, useEffect, useId, useMemo, useRef, useState } from 'react';
3
3
  import { useFocusRing, useRadioGroup } from 'react-aria';
4
4
  import { useRadioGroupState } from 'react-stately';
5
5
  import { FUNCTION_NOT_IMPLEMENTED } from '../../constants/message.js';
6
6
  import { useBreakpoint } from '../../hook/breakpoints.hook.js';
7
+ import { acceptInputs, useFocusManagerRef } from '../../hook/focus-manager-ref.hook.js';
7
8
  import { resolveResponsiveVariant } from '../../utils/breakpoint.util.js';
8
9
  import { Button } from '../button/index.js';
9
10
  import { ExpandMoreIcon } from '../icon/index.js';
@@ -69,7 +70,10 @@ export const RadioGroupContext = createContext({
69
70
  }
70
71
  }
71
72
  });
72
- export function RadioGroup({ className, radios, label, orientation = 'vertical', showAmount = 0, size = 'medium', errorMessage, hintMessage, ...props }) {
73
+ function BaseRadioGroup({ className, radios, label, orientation = 'vertical', showAmount = 0, size = 'medium', errorMessage, hintMessage, ...props }, ref) {
74
+ const wrapperRef = useFocusManagerRef(ref, {
75
+ accept: acceptInputs
76
+ });
73
77
  const breakpoint = useBreakpoint();
74
78
  const resolvedOrientation = resolveResponsiveVariant(orientation, breakpoint);
75
79
  const resolvedSize = resolveResponsiveVariant(size, breakpoint);
@@ -123,7 +127,8 @@ export function RadioGroup({ className, radios, label, orientation = 'vertical',
123
127
  message: errorMessage
124
128
  }), React.createElement("div", {
125
129
  className: styles.radioWrapper(),
126
- id: panelId
130
+ id: panelId,
131
+ ref: wrapperRef
127
132
  }, React.createElement(RadioGroupContext.Provider, {
128
133
  value: {
129
134
  state,
@@ -145,3 +150,5 @@ export function RadioGroup({ className, radios, label, orientation = 'vertical',
145
150
  className: styles.buttonText()
146
151
  }, `Show ${revealAmount} more ${revealAmount === 1 ? 'item' : 'items'}`))));
147
152
  }
153
+ export const RadioGroup = forwardRef(BaseRadioGroup);
154
+ RadioGroup.displayName = 'RadioGroup';
@@ -1,4 +1,11 @@
1
1
  import React from 'react';
2
- import { SelectorButtonGroupContextState, SelectorButtonGroupProps } from './selector-button-group.types.js';
2
+ import { FocusHandle } from '../../../../hook/focus-manager-ref.hook.js';
3
+ import { SelectorButtonGroupContextState } from './selector-button-group.types.js';
3
4
  export declare const SelectorButtonContext: React.Context<SelectorButtonGroupContextState>;
4
- export declare function SelectorButtonGroup({ className, children, label, orientation, errorMessage, description, value, onChange, isDisabled, ...props }: SelectorButtonGroupProps): import("react/jsx-runtime").JSX.Element;
5
+ export declare const SelectorButtonGroup: React.ForwardRefExoticComponent<{
6
+ isDisabled?: boolean;
7
+ orientation?: import("../../../../types/responsive-variants.types.js").ResponsiveVariants<"horizontal" | "vertical" | undefined>;
8
+ tag?: keyof JSX.IntrinsicElements;
9
+ value?: string;
10
+ onChange?: (value: string) => void;
11
+ } & import("react-aria").AriaFieldProps & Omit<React.HTMLAttributes<Element>, "onChange"> & React.RefAttributes<FocusHandle>>;
@@ -1,7 +1,8 @@
1
1
  'use client';
2
- import React, { createContext, useCallback, useMemo, useState } from 'react';
2
+ import React, { createContext, forwardRef, useCallback, useMemo, useState } from 'react';
3
3
  import { useField } from 'react-aria';
4
4
  import { useBreakpoint } from '../../../../hook/breakpoints.hook.js';
5
+ import { useFocusManagerRef } from '../../../../hook/focus-manager-ref.hook.js';
5
6
  import { resolveResponsiveVariant } from '../../../../utils/breakpoint.util.js';
6
7
  import { ErrorMessage, Hint, Label } from '../../../index.js';
7
8
  import { styles } from './selector-button-group.styles.js';
@@ -11,7 +12,8 @@ export const SelectorButtonContext = createContext({
11
12
  validationState: 'valid',
12
13
  isDisabled: undefined
13
14
  });
14
- export function SelectorButtonGroup({ className, children, label, orientation = 'vertical', errorMessage, description, value = '', onChange, isDisabled, ...props }) {
15
+ function BaseSelectorButtonGroup({ className, children, label, orientation = 'vertical', errorMessage, description, value = '', onChange, isDisabled, ...props }, ref) {
16
+ const wrapperRef = useFocusManagerRef(ref);
15
17
  const isControlled = onChange !== undefined;
16
18
  const onChangeCallback = onChange;
17
19
  const [selected, setSelected] = useState(value);
@@ -55,8 +57,11 @@ export function SelectorButtonGroup({ className, children, label, orientation =
55
57
  className,
56
58
  orientation: resolvedOrientation
57
59
  }),
58
- ...fieldProps
60
+ ...fieldProps,
61
+ ref: wrapperRef
59
62
  }, React.createElement(SelectorButtonContext.Provider, {
60
63
  value: state
61
64
  }, children)));
62
65
  }
66
+ export const SelectorButtonGroup = forwardRef(BaseSelectorButtonGroup);
67
+ SelectorButtonGroup.displayName = 'SelectorButtonGroup';
@@ -1,4 +1,8 @@
1
1
  import React from 'react';
2
- import { type SelectorCheckboxGroupProps } from './selector-checkbox-group.types.js';
2
+ import { FocusHandle } from '../../../../hook/focus-manager-ref.hook.js';
3
3
  export declare const SelectorCheckboxGroupContext: React.Context<import("react-stately").CheckboxGroupState>;
4
- export declare function SelectorCheckboxGroup(props: SelectorCheckboxGroupProps): import("react/jsx-runtime").JSX.Element;
4
+ export declare const SelectorCheckboxGroup: React.ForwardRefExoticComponent<{
5
+ className?: string;
6
+ orientation?: "horizontal" | "vertical";
7
+ size?: "medium" | "large";
8
+ } & import("react-aria").AriaCheckboxGroupProps & Omit<React.HTMLAttributes<Element>, "onChange"> & React.RefAttributes<FocusHandle>>;
@@ -1,8 +1,9 @@
1
1
  'use client';
2
- import React, { createContext } from 'react';
2
+ import React, { createContext, forwardRef } from 'react';
3
3
  import { useCheckboxGroup } from 'react-aria';
4
4
  import { useCheckboxGroupState } from 'react-stately';
5
5
  import { FUNCTION_NOT_IMPLEMENTED } from '../../../../constants/message.js';
6
+ import { useFocusManagerRef } from '../../../../hook/focus-manager-ref.hook.js';
6
7
  import { ErrorMessage, Hint, Label } from '../../../index.js';
7
8
  import { styles } from './selector-checkbox-group.styles.js';
8
9
  export const SelectorCheckboxGroupContext = createContext({
@@ -65,8 +66,9 @@ export const SelectorCheckboxGroupContext = createContext({
65
66
  throw new Error(FUNCTION_NOT_IMPLEMENTED);
66
67
  }
67
68
  });
68
- export function SelectorCheckboxGroup(props) {
69
+ function BaseSelectorCheckboxGroup(props, ref) {
69
70
  const { children, label, description, errorMessage } = props;
71
+ const wrapperRef = useFocusManagerRef(ref);
70
72
  const state = useCheckboxGroupState(props);
71
73
  const { groupProps, labelProps, descriptionProps, errorMessageProps } = useCheckboxGroup(props, state);
72
74
  return React.createElement(React.Fragment, null, label && React.createElement(Label, labelProps, label), description && React.createElement(Hint, descriptionProps, description), errorMessage && state.isInvalid && React.createElement(ErrorMessage, {
@@ -76,8 +78,11 @@ export function SelectorCheckboxGroup(props) {
76
78
  ...groupProps,
77
79
  className: styles({
78
80
  className: groupProps.className
79
- })
81
+ }),
82
+ ref: wrapperRef
80
83
  }, React.createElement(SelectorCheckboxGroupContext.Provider, {
81
84
  value: state
82
85
  }, children)));
83
86
  }
87
+ export const SelectorCheckboxGroup = forwardRef(BaseSelectorCheckboxGroup);
88
+ SelectorCheckboxGroup.displayName = 'SelectorCheckboxGroup';
@@ -1,4 +1,8 @@
1
1
  import React from 'react';
2
- import { type SelectorRadioGroupContextState, type SelectorRadioGroupProps } from './selector-radio-group.types.js';
2
+ import { FocusHandle } from '../../../../hook/focus-manager-ref.hook.js';
3
+ import { type SelectorRadioGroupContextState } from './selector-radio-group.types.js';
3
4
  export declare const SelectorRadioGroupContext: React.Context<SelectorRadioGroupContextState>;
4
- export declare function SelectorRadioGroup({ className, children, label, orientation, errorMessage, description, ...props }: SelectorRadioGroupProps): import("react/jsx-runtime").JSX.Element;
5
+ export declare const SelectorRadioGroup: React.ForwardRefExoticComponent<{
6
+ orientation?: "horizontal" | "vertical" | undefined;
7
+ tag?: keyof JSX.IntrinsicElements;
8
+ } & import("react-aria").AriaRadioGroupProps & Omit<React.HTMLAttributes<Element>, "onChange"> & React.RefAttributes<FocusHandle>>;
@@ -1,9 +1,10 @@
1
1
  'use client';
2
- import React, { createContext } from 'react';
2
+ import React, { createContext, forwardRef } from 'react';
3
3
  import { useRadioGroup } from 'react-aria';
4
4
  import { useRadioGroupState } from 'react-stately';
5
5
  import { FUNCTION_NOT_IMPLEMENTED } from '../../../../constants/message.js';
6
6
  import { useBreakpoint } from '../../../../hook/breakpoints.hook.js';
7
+ import { useFocusManagerRef } from '../../../../hook/focus-manager-ref.hook.js';
7
8
  import { resolveResponsiveVariant } from '../../../../utils/breakpoint.util.js';
8
9
  import { ErrorMessage, Hint, Label } from '../../../index.js';
9
10
  import { styles } from './selector-radio-group.styles.js';
@@ -66,7 +67,8 @@ export const SelectorRadioGroupContext = createContext({
66
67
  }
67
68
  }
68
69
  });
69
- export function SelectorRadioGroup({ className, children, label, orientation = 'vertical', errorMessage, description, ...props }) {
70
+ function BaseSelectorRadioGroup({ className, children, label, orientation = 'vertical', errorMessage, description, ...props }, ref) {
71
+ const wrapperRef = useFocusManagerRef(ref);
70
72
  const breakpoint = useBreakpoint();
71
73
  const resolvedOrientation = resolveResponsiveVariant(orientation, breakpoint);
72
74
  const state = useRadioGroupState({
@@ -88,7 +90,8 @@ export function SelectorRadioGroup({ className, children, label, orientation = '
88
90
  className,
89
91
  orientation: resolvedOrientation
90
92
  }),
91
- ...radioGroupProps
93
+ ...radioGroupProps,
94
+ ref: wrapperRef
92
95
  }, React.createElement(SelectorRadioGroupContext.Provider, {
93
96
  value: {
94
97
  state,
@@ -96,3 +99,5 @@ export function SelectorRadioGroup({ className, children, label, orientation = '
96
99
  }
97
100
  }, children)));
98
101
  }
102
+ export const SelectorRadioGroup = forwardRef(BaseSelectorRadioGroup);
103
+ SelectorRadioGroup.displayName = 'SelectorRadioGroup';
@@ -1,2 +1,4 @@
1
+ import React from 'react';
2
+ import { FocusHandle } from '../../hook/focus-manager-ref.hook.js';
1
3
  import { type SelectorProps } from './selector.types.js';
2
- export declare function Selector(props: SelectorProps): import("react/jsx-runtime").JSX.Element | undefined;
4
+ export declare const Selector: React.ForwardRefExoticComponent<SelectorProps & React.RefAttributes<FocusHandle>>;
@@ -1,17 +1,28 @@
1
1
  'use client';
2
- import React from 'react';
2
+ import React, { forwardRef } from 'react';
3
3
  import { SelectorButtonGroup, SelectorCheckboxGroup, SelectorLinkGroup, SelectorRadioGroup } from './components/index.js';
4
- export function Selector(props) {
4
+ function BaseSelector(props, ref) {
5
5
  if (props.type === 'checkbox') {
6
- return React.createElement(SelectorCheckboxGroup, props);
6
+ return React.createElement(SelectorCheckboxGroup, {
7
+ ...props,
8
+ ref: ref
9
+ });
7
10
  }
8
11
  if (props.type === 'radio') {
9
- return React.createElement(SelectorRadioGroup, props);
12
+ return React.createElement(SelectorRadioGroup, {
13
+ ...props,
14
+ ref: ref
15
+ });
10
16
  }
11
17
  if (props.type === 'button') {
12
- return React.createElement(SelectorButtonGroup, props);
18
+ return React.createElement(SelectorButtonGroup, {
19
+ ...props,
20
+ ref: ref
21
+ });
13
22
  }
14
23
  if (props.type === 'link') {
15
24
  return React.createElement(SelectorLinkGroup, props);
16
25
  }
17
26
  }
27
+ export const Selector = forwardRef(BaseSelector);
28
+ Selector.displayName = 'Selector';
@@ -1,2 +1,8 @@
1
- import { type SwitchProps } from './switch.types.js';
2
- export declare function Switch({ className, label, size, block, checked, isDisabled, ...props }: SwitchProps): import("react/jsx-runtime").JSX.Element;
1
+ import React from 'react';
2
+ export declare const Switch: React.ForwardRefExoticComponent<{
3
+ block?: import("../../types/responsive-variants.types.js").ResponsiveVariants<boolean | undefined>;
4
+ checked?: boolean;
5
+ className?: string;
6
+ label: string;
7
+ size?: import("../../types/responsive-variants.types.js").ResponsiveVariants<"small" | "xlarge" | "medium" | "large" | undefined>;
8
+ } & Omit<import("react-aria").AriaCheckboxProps, "children"> & React.RefAttributes<HTMLInputElement>>;
@@ -1,17 +1,17 @@
1
1
  'use client';
2
- import React, { useId, useRef } from 'react';
3
- import { VisuallyHidden, mergeProps, useCheckbox, useFocusRing } from 'react-aria';
2
+ import React, { forwardRef, useId } from 'react';
3
+ import { VisuallyHidden, mergeProps, useCheckbox, useFocusRing, useObjectRef } from 'react-aria';
4
4
  import { useToggleState } from 'react-stately';
5
5
  import { useBreakpoint } from '../../hook/breakpoints.hook.js';
6
6
  import { resolveResponsiveVariant } from '../../utils/breakpoint.util.js';
7
7
  import { styles as switchStyles } from './switch.styles.js';
8
- export function Switch({ className, label, size = 'medium', block = false, checked = false, isDisabled, ...props }) {
8
+ function BaseSwitch({ className, label, size = 'medium', block = false, checked = false, isDisabled, ...props }, forwardedRef) {
9
9
  const state = useToggleState({
10
10
  ...props,
11
11
  defaultSelected: checked
12
12
  });
13
13
  const labelId = useId();
14
- const ref = useRef(null);
14
+ const ref = useObjectRef(forwardedRef);
15
15
  const { isSelected } = state;
16
16
  const { inputProps, labelProps } = useCheckbox({
17
17
  isDisabled,
@@ -45,3 +45,5 @@ export function Switch({ className, label, size = 'medium', block = false, check
45
45
  className: styles.switchDiv()
46
46
  }));
47
47
  }
48
+ export const Switch = forwardRef(BaseSwitch);
49
+ Switch.displayName = 'Switch';
@@ -1093,27 +1093,15 @@
1093
1093
  .w-\[116px\] {
1094
1094
  width: 116px;
1095
1095
  }
1096
- .w-\[120px\] {
1097
- width: 120px;
1098
- }
1099
1096
  .w-\[122px\] {
1100
1097
  width: 122px;
1101
1098
  }
1102
1099
  .w-\[135px\] {
1103
1100
  width: 135px;
1104
1101
  }
1105
- .w-\[140px\] {
1106
- width: 140px;
1107
- }
1108
- .w-\[145px\] {
1109
- width: 145px;
1110
- }
1111
1102
  .w-\[150px\] {
1112
1103
  width: 150px;
1113
1104
  }
1114
- .w-\[165px\] {
1115
- width: 165px;
1116
- }
1117
1105
  .w-\[170px\] {
1118
1106
  width: 170px;
1119
1107
  }
@@ -1339,6 +1327,18 @@
1339
1327
  .min-w-\[100vw\] {
1340
1328
  min-width: 100vw;
1341
1329
  }
1330
+ .min-w-\[120px\] {
1331
+ min-width: 120px;
1332
+ }
1333
+ .min-w-\[140px\] {
1334
+ min-width: 140px;
1335
+ }
1336
+ .min-w-\[145px\] {
1337
+ min-width: 145px;
1338
+ }
1339
+ .min-w-\[165px\] {
1340
+ min-width: 165px;
1341
+ }
1342
1342
  .min-w-\[200px\] {
1343
1343
  min-width: 200px;
1344
1344
  }
@@ -1093,27 +1093,15 @@
1093
1093
  .w-\[116px\] {
1094
1094
  width: 116px;
1095
1095
  }
1096
- .w-\[120px\] {
1097
- width: 120px;
1098
- }
1099
1096
  .w-\[122px\] {
1100
1097
  width: 122px;
1101
1098
  }
1102
1099
  .w-\[135px\] {
1103
1100
  width: 135px;
1104
1101
  }
1105
- .w-\[140px\] {
1106
- width: 140px;
1107
- }
1108
- .w-\[145px\] {
1109
- width: 145px;
1110
- }
1111
1102
  .w-\[150px\] {
1112
1103
  width: 150px;
1113
1104
  }
1114
- .w-\[165px\] {
1115
- width: 165px;
1116
- }
1117
1105
  .w-\[170px\] {
1118
1106
  width: 170px;
1119
1107
  }
@@ -1339,6 +1327,18 @@
1339
1327
  .min-w-\[100vw\] {
1340
1328
  min-width: 100vw;
1341
1329
  }
1330
+ .min-w-\[120px\] {
1331
+ min-width: 120px;
1332
+ }
1333
+ .min-w-\[140px\] {
1334
+ min-width: 140px;
1335
+ }
1336
+ .min-w-\[145px\] {
1337
+ min-width: 145px;
1338
+ }
1339
+ .min-w-\[165px\] {
1340
+ min-width: 165px;
1341
+ }
1342
1342
  .min-w-\[200px\] {
1343
1343
  min-width: 200px;
1344
1344
  }
@@ -0,0 +1,29 @@
1
+ import { ForwardedRef, RefObject } from 'react';
2
+ /**
3
+ * Imperative handle exposed by group fields (`RadioGroup`, `CheckboxGroup`, `Selector`, `ButtonGroup`) whose
4
+ * focusable control is not a single element the group can point a DOM ref at.
5
+ *
6
+ * `focus()` moves focus to the group's first tabbable option at call time, which is what form libraries
7
+ * such as react-hook-form need (`field.ref` → `ref.focus()` on validation error).
8
+ */
9
+ export type FocusHandle = {
10
+ focus: () => void;
11
+ };
12
+ export type UseFocusManagerRefOptions = {
13
+ /**
14
+ * Restricts which descendants of the wrapper may receive focus, e.g. only `<input>`s when the wrapper also
15
+ * contains other controls (such as a "show more" button).
16
+ */
17
+ accept?: (node: Element) => boolean;
18
+ };
19
+ /**
20
+ * Bridges a forwarded {@link FocusHandle} ref to a wrapper element. Returns the ref to attach to the
21
+ * wrapper; `focus()` on the forwarded handle focuses the first tabbable descendant via react-aria's
22
+ * focus manager. Resolution happens when `focus()` is called, so options that render, enable or
23
+ * disable after mount are handled, and roving-tabindex groups (radios) focus their tab stop
24
+ * (the selected option) rather than the first option. When no option is tabbable (e.g. a radio
25
+ * group whose `value` matches no radio, such as `''`), the first focusable option is used instead.
26
+ */
27
+ export declare function useFocusManagerRef<T extends HTMLElement = HTMLDivElement>(forwardedRef: ForwardedRef<FocusHandle>, { accept }?: UseFocusManagerRefOptions): RefObject<T>;
28
+ /** `accept` predicate for groups whose options are `<input>` elements. */
29
+ export declare const acceptInputs: (node: Element) => boolean;
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+ import { createFocusManager } from '@react-aria/focus';
3
+ import { useImperativeHandle, useRef } from 'react';
4
+ export function useFocusManagerRef(forwardedRef, { accept } = {}) {
5
+ const wrapperRef = useRef(null);
6
+ useImperativeHandle(forwardedRef, ()=>({
7
+ focus: ()=>{
8
+ const focusManager = createFocusManager(wrapperRef);
9
+ if (!focusManager.focusFirst({
10
+ tabbable: true,
11
+ accept
12
+ })) {
13
+ focusManager.focusFirst({
14
+ accept
15
+ });
16
+ }
17
+ }
18
+ }), [
19
+ accept
20
+ ]);
21
+ return wrapperRef;
22
+ }
23
+ export const acceptInputs = (node)=>node.tagName === 'INPUT';
@@ -1,4 +1,5 @@
1
1
  export * from './dark-mode.hook.js';
2
2
  export * from './breakpoints.hook.js';
3
+ export { type FocusHandle } from './focus-manager-ref.hook.js';
3
4
  export * from '../components/pagination/pagination.hooks.js';
4
5
  export * from './breakpoints.hook.js';