@westpac/ui 1.18.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.
- package/CHANGELOG.md +20 -0
- package/dist/component-type.json +1 -1
- package/dist/components/button-group/button-group.component.d.ts +2 -1
- package/dist/components/button-group/button-group.component.js +6 -3
- package/dist/components/checkbox-group/checkbox-group.component.d.ts +11 -2
- package/dist/components/checkbox-group/checkbox-group.component.js +10 -3
- package/dist/components/checkbox-group/components/checkbox-group-checkbox/checkbox-group-checkbox.component.d.ts +1 -1
- package/dist/components/checkbox-group/components/checkbox-group-checkbox/checkbox-group-checkbox.component.js +5 -6
- package/dist/components/date-picker/components/date-field/components/date-segment/date-segment.component.d.ts +6 -5
- package/dist/components/date-picker/components/date-field/components/date-segment/date-segment.component.js +6 -4
- package/dist/components/date-picker/components/date-field/date-field.component.d.ts +5 -5
- package/dist/components/date-picker/components/date-field/date-field.component.js +7 -3
- package/dist/components/date-picker/date-picker.component.d.ts +19 -2
- package/dist/components/date-picker/date-picker.component.js +10 -5
- package/dist/components/date-picker/date-picker.styles.js +5 -5
- package/dist/components/multi-select/components/multi-select-list-box/components/multi-select-option/multi-select-option.component.js +3 -7
- 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
- package/dist/components/multi-select/components/multi-select-popover/multi-select-popover.component.js +5 -2
- package/dist/components/multi-select/components/multi-select-searchbar/multi-select-searchbar.component.js +4 -7
- package/dist/components/multi-select/multi-select.component.d.ts +14 -3
- package/dist/components/multi-select/multi-select.component.js +10 -16
- package/dist/components/radio-group/components/radio-group-radio/radio-group-radio.component.d.ts +1 -1
- package/dist/components/radio-group/components/radio-group-radio/radio-group-radio.component.js +5 -6
- package/dist/components/radio-group/radio-group.component.d.ts +10 -2
- package/dist/components/radio-group/radio-group.component.js +10 -3
- package/dist/components/selector/components/selector-button-group/selector-button-group.component.d.ts +9 -2
- package/dist/components/selector/components/selector-button-group/selector-button-group.component.js +8 -3
- package/dist/components/selector/components/selector-checkbox-group/selector-checkbox-group.component.d.ts +6 -2
- package/dist/components/selector/components/selector-checkbox-group/selector-checkbox-group.component.js +8 -3
- package/dist/components/selector/components/selector-radio-group/selector-radio-group.component.d.ts +6 -2
- package/dist/components/selector/components/selector-radio-group/selector-radio-group.component.js +8 -3
- package/dist/components/selector/selector.component.d.ts +3 -1
- package/dist/components/selector/selector.component.js +16 -5
- package/dist/components/switch/switch.component.d.ts +8 -2
- package/dist/components/switch/switch.component.js +6 -4
- package/dist/css/westpac-ui.css +12 -12
- package/dist/css/westpac-ui.min.css +12 -12
- package/dist/hook/focus-manager-ref.hook.d.ts +29 -0
- package/dist/hook/focus-manager-ref.hook.js +23 -0
- package/dist/hook/index.d.ts +1 -0
- package/dist/index.d.ts +1 -0
- package/package.json +5 -4
- package/src/components/button-group/button-group.component.tsx +26 -16
- package/src/components/checkbox-group/checkbox-group.component.tsx +24 -14
- package/src/components/checkbox-group/components/checkbox-group-checkbox/checkbox-group-checkbox.component.tsx +7 -7
- package/src/components/date-picker/components/date-field/components/date-segment/date-segment.component.tsx +10 -4
- package/src/components/date-picker/components/date-field/date-field.component.tsx +17 -3
- package/src/components/date-picker/date-picker.component.tsx +32 -21
- package/src/components/date-picker/date-picker.styles.ts +5 -5
- package/src/components/multi-select/components/multi-select-list-box/components/multi-select-option/multi-select-option.component.tsx +3 -5
- 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
- package/src/components/multi-select/components/multi-select-popover/multi-select-popover.component.tsx +8 -2
- package/src/components/multi-select/components/multi-select-searchbar/multi-select-searchbar.component.tsx +3 -6
- package/src/components/multi-select/multi-select.component.tsx +33 -28
- package/src/components/radio-group/components/radio-group-radio/radio-group-radio.component.tsx +10 -7
- package/src/components/radio-group/radio-group.component.tsx +24 -14
- package/src/components/selector/components/selector-button-group/selector-button-group.component.tsx +23 -14
- package/src/components/selector/components/selector-checkbox-group/selector-checkbox-group.component.tsx +12 -3
- package/src/components/selector/components/selector-radio-group/selector-radio-group.component.tsx +21 -11
- package/src/components/selector/selector.component.tsx +16 -5
- package/src/components/switch/switch.component.tsx +10 -12
- package/src/hook/focus-manager-ref.hook.ts +56 -0
- package/src/hook/index.ts +1 -0
- package/src/index.ts +1 -0
package/dist/components/selector/components/selector-button-group/selector-button-group.component.js
CHANGED
|
@@ -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
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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';
|
package/dist/components/selector/components/selector-radio-group/selector-radio-group.component.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
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
|
|
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>>;
|
package/dist/components/selector/components/selector-radio-group/selector-radio-group.component.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
4
|
+
function BaseSelector(props, ref) {
|
|
5
5
|
if (props.type === 'checkbox') {
|
|
6
|
-
return React.createElement(SelectorCheckboxGroup,
|
|
6
|
+
return React.createElement(SelectorCheckboxGroup, {
|
|
7
|
+
...props,
|
|
8
|
+
ref: ref
|
|
9
|
+
});
|
|
7
10
|
}
|
|
8
11
|
if (props.type === 'radio') {
|
|
9
|
-
return React.createElement(SelectorRadioGroup,
|
|
12
|
+
return React.createElement(SelectorRadioGroup, {
|
|
13
|
+
...props,
|
|
14
|
+
ref: ref
|
|
15
|
+
});
|
|
10
16
|
}
|
|
11
17
|
if (props.type === 'button') {
|
|
12
|
-
return React.createElement(SelectorButtonGroup,
|
|
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
|
|
2
|
-
export declare
|
|
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, {
|
|
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
|
-
|
|
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 =
|
|
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';
|
package/dist/css/westpac-ui.css
CHANGED
|
@@ -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';
|
package/dist/hook/index.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@westpac/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -259,14 +259,15 @@
|
|
|
259
259
|
"typescript": "^5.5.4",
|
|
260
260
|
"vite": "^7.3.5",
|
|
261
261
|
"vitest": "^3.2.6",
|
|
262
|
-
"@westpac/eslint-config": "~1.1.0",
|
|
263
|
-
"@westpac/style-config": "~1.0.2",
|
|
264
262
|
"@westpac/test-config": "~0.0.0",
|
|
265
|
-
"@westpac/ts-config": "~0.0.0"
|
|
263
|
+
"@westpac/ts-config": "~0.0.0",
|
|
264
|
+
"@westpac/style-config": "~1.0.2",
|
|
265
|
+
"@westpac/eslint-config": "~1.1.0"
|
|
266
266
|
},
|
|
267
267
|
"dependencies": {
|
|
268
268
|
"@internationalized/date": "^3.12.0",
|
|
269
269
|
"@react-aria/accordion": "3.0.0-alpha.37",
|
|
270
|
+
"@react-aria/focus": "~3.21.5",
|
|
270
271
|
"@react-aria/utils": "~3.29.1",
|
|
271
272
|
"@react-spectrum/utils": "~3.11.5",
|
|
272
273
|
"clsx": "^1.2.1",
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { useCallback, useMemo } from 'react';
|
|
3
|
+
import React, { ForwardedRef, forwardRef, useCallback, useMemo } from 'react';
|
|
4
4
|
import { useToggleButtonGroup } from 'react-aria';
|
|
5
5
|
import { useToggleGroupState } from 'react-stately';
|
|
6
6
|
|
|
7
|
+
import { FocusHandle, useFocusManagerRef } from '../../hook/focus-manager-ref.hook.js';
|
|
8
|
+
|
|
7
9
|
import { styles as buttonGroupStyles } from './button-group.styles.js';
|
|
8
10
|
|
|
9
11
|
import type { ButtonGroupProps } from './button-group.types.js';
|
|
@@ -19,20 +21,26 @@ export const ToggleButtonGroupContext = React.createContext<
|
|
|
19
21
|
| null
|
|
20
22
|
>(null);
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
function BaseButtonGroup(
|
|
25
|
+
{
|
|
26
|
+
size,
|
|
27
|
+
look,
|
|
28
|
+
block,
|
|
29
|
+
children,
|
|
30
|
+
onSelect,
|
|
31
|
+
orientation = 'horizontal',
|
|
32
|
+
selectionMode,
|
|
33
|
+
selectedKeys,
|
|
34
|
+
defaultSelectedKeys,
|
|
35
|
+
onSelectionChange,
|
|
36
|
+
className,
|
|
37
|
+
...props
|
|
38
|
+
}: ButtonGroupProps,
|
|
39
|
+
// `ref.current.focus()` (e.g. react-hook-form focusing a field with a validation error) moves
|
|
40
|
+
// focus to the first tabbable button in the group.
|
|
41
|
+
forwardedRef: ForwardedRef<FocusHandle>,
|
|
42
|
+
) {
|
|
43
|
+
const ref = useFocusManagerRef(forwardedRef);
|
|
36
44
|
/**
|
|
37
45
|
* Normalizes key sets depending on selection mode.
|
|
38
46
|
*/
|
|
@@ -71,7 +79,6 @@ export function ButtonGroup({
|
|
|
71
79
|
selectedKeys: finalSelectedKeys,
|
|
72
80
|
});
|
|
73
81
|
|
|
74
|
-
const ref = React.useRef<HTMLDivElement | null>(null);
|
|
75
82
|
const { groupProps } = useToggleButtonGroup(
|
|
76
83
|
{
|
|
77
84
|
...props,
|
|
@@ -95,3 +102,6 @@ export function ButtonGroup({
|
|
|
95
102
|
</div>
|
|
96
103
|
);
|
|
97
104
|
}
|
|
105
|
+
|
|
106
|
+
export const ButtonGroup = forwardRef(BaseButtonGroup);
|
|
107
|
+
ButtonGroup.displayName = 'ButtonGroup';
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { createContext, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import React, { ForwardedRef, createContext, forwardRef, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { useCheckboxGroup, useFocusRing } from 'react-aria';
|
|
5
5
|
import { useCheckboxGroupState } from 'react-stately';
|
|
6
6
|
|
|
7
7
|
import { FUNCTION_NOT_IMPLEMENTED } from '../../constants/message.js';
|
|
8
8
|
import { useBreakpoint } from '../../hook/breakpoints.hook.js';
|
|
9
|
+
import { FocusHandle, acceptInputs, useFocusManagerRef } from '../../hook/focus-manager-ref.hook.js';
|
|
9
10
|
import { resolveResponsiveVariant } from '../../utils/breakpoint.util.js';
|
|
10
11
|
import { Button } from '../button/index.js';
|
|
11
12
|
import { ExpandMoreIcon } from '../icon/index.js';
|
|
@@ -79,22 +80,28 @@ export const CheckboxGroupContext = createContext<CheckboxGroupContextState>({
|
|
|
79
80
|
},
|
|
80
81
|
},
|
|
81
82
|
});
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
83
|
+
function BaseCheckboxGroup(
|
|
84
|
+
{
|
|
85
|
+
className,
|
|
86
|
+
checkboxes,
|
|
87
|
+
label,
|
|
88
|
+
orientation = 'vertical',
|
|
89
|
+
showAmount = 0,
|
|
90
|
+
size = 'medium',
|
|
91
|
+
errorMessage,
|
|
92
|
+
hintMessage,
|
|
93
|
+
...props
|
|
94
|
+
}: CheckboxGroupProps,
|
|
95
|
+
// `ref.current.focus()` (e.g. react-hook-form focusing a field with a validation error) moves focus
|
|
96
|
+
// to the first enabled checkbox.
|
|
97
|
+
ref: ForwardedRef<FocusHandle>,
|
|
98
|
+
) {
|
|
99
|
+
const wrapperRef = useFocusManagerRef(ref, { accept: acceptInputs });
|
|
93
100
|
const state = useCheckboxGroupState({ ...props, label });
|
|
94
101
|
const { groupProps, labelProps, errorMessageProps, descriptionProps } = useCheckboxGroup({ ...props, label }, state);
|
|
95
102
|
const { isFocusVisible, focusProps } = useFocusRing();
|
|
96
103
|
const [hiddenOptions, setHiddenOptions] = useState<boolean>(showAmount > 0);
|
|
97
|
-
const firstNewCheckboxRef = useRef<
|
|
104
|
+
const firstNewCheckboxRef = useRef<HTMLInputElement>(null);
|
|
98
105
|
const revealAmount = checkboxes && checkboxes.length - showAmount;
|
|
99
106
|
const breakpoint = useBreakpoint();
|
|
100
107
|
const styles = checkboxStyles({ orientation: resolveResponsiveVariant(orientation, breakpoint), isFocusVisible });
|
|
@@ -118,7 +125,7 @@ export function CheckboxGroup({
|
|
|
118
125
|
<Label {...labelProps}>{label}</Label>
|
|
119
126
|
{hintMessage && <Hint {...descriptionProps}>{hintMessage}</Hint>}
|
|
120
127
|
{errorMessage && state.isInvalid && <ErrorMessage {...errorMessageProps} message={errorMessage} />}
|
|
121
|
-
<div className={styles.itemWrapper()} id={panelId}>
|
|
128
|
+
<div className={styles.itemWrapper()} id={panelId} ref={wrapperRef}>
|
|
122
129
|
<CheckboxGroupContext.Provider
|
|
123
130
|
value={{
|
|
124
131
|
state,
|
|
@@ -147,3 +154,6 @@ export function CheckboxGroup({
|
|
|
147
154
|
</div>
|
|
148
155
|
);
|
|
149
156
|
}
|
|
157
|
+
|
|
158
|
+
export const CheckboxGroup = forwardRef(BaseCheckboxGroup);
|
|
159
|
+
CheckboxGroup.displayName = 'CheckboxGroup';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, {
|
|
4
|
-
import { VisuallyHidden, mergeProps, useCheckboxGroupItem, useFocusRing } from 'react-aria';
|
|
3
|
+
import React, { ForwardedRef, forwardRef, useContext } from 'react';
|
|
4
|
+
import { VisuallyHidden, mergeProps, useCheckboxGroupItem, useFocusRing, useObjectRef } from 'react-aria';
|
|
5
5
|
|
|
6
6
|
import { Icon } from '../../../icon/icon.component.js';
|
|
7
7
|
import { IconProps } from '../../../icon/index.js';
|
|
@@ -33,22 +33,22 @@ function CheckIcon({ copyrightYear = '2025', size, ...props }: IconProps) {
|
|
|
33
33
|
|
|
34
34
|
function BaseCheckbox(
|
|
35
35
|
{ className, hint, label, value, ...props }: CheckboxGroupCheckboxProps,
|
|
36
|
-
ref:
|
|
36
|
+
ref: ForwardedRef<HTMLInputElement>,
|
|
37
37
|
) {
|
|
38
38
|
const { state, size, orientation } = useContext(CheckboxGroupContext);
|
|
39
|
-
const
|
|
39
|
+
const inputRef = useObjectRef(ref);
|
|
40
40
|
const { inputProps, labelProps, isDisabled, isSelected } = useCheckboxGroupItem(
|
|
41
41
|
{ ...props, value, children: label },
|
|
42
42
|
state,
|
|
43
|
-
|
|
43
|
+
inputRef,
|
|
44
44
|
);
|
|
45
45
|
const { isFocusVisible, focusProps } = useFocusRing();
|
|
46
46
|
const styles = checkboxItemStyles({ isDisabled, size, orientation, isFocusVisible });
|
|
47
47
|
|
|
48
48
|
return (
|
|
49
|
-
<label className={styles.base({ className })}
|
|
49
|
+
<label className={styles.base({ className })} {...labelProps}>
|
|
50
50
|
<VisuallyHidden elementType="span">
|
|
51
|
-
<input {...mergeProps(inputProps, focusProps)} ref={
|
|
51
|
+
<input {...mergeProps(inputProps, focusProps)} ref={inputRef} />
|
|
52
52
|
</VisuallyHidden>
|
|
53
53
|
<span className={styles.checkbox()}>
|
|
54
54
|
{isSelected && <CheckIcon className={styles.checkIcon()} size={size} color={isDisabled ? 'muted' : 'hero'} />}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, {
|
|
4
|
-
import { mergeProps, useDateSegment, useFocusRing } from 'react-aria';
|
|
3
|
+
import React, { ForwardedRef, forwardRef } from 'react';
|
|
4
|
+
import { mergeProps, useDateSegment, useFocusRing, useObjectRef } from 'react-aria';
|
|
5
5
|
|
|
6
6
|
import { styles as dateSegmentStyles } from './date-segment.styles.js';
|
|
7
7
|
import { DateSegmentProps } from './date-segment.types.js';
|
|
@@ -9,8 +9,11 @@ import { DateSegmentProps } from './date-segment.types.js';
|
|
|
9
9
|
/**
|
|
10
10
|
* @private
|
|
11
11
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
function BaseDateSegment(
|
|
13
|
+
{ segment, state, separator, ...props }: DateSegmentProps,
|
|
14
|
+
forwardedRef: ForwardedRef<HTMLSpanElement>,
|
|
15
|
+
) {
|
|
16
|
+
const ref = useObjectRef(forwardedRef);
|
|
14
17
|
const { focusProps, isFocusVisible } = useFocusRing();
|
|
15
18
|
const { segmentProps } = useDateSegment(segment, state, ref);
|
|
16
19
|
const styles = dateSegmentStyles({
|
|
@@ -23,3 +26,6 @@ export function DateSegment({ segment, state, separator, ...props }: DateSegment
|
|
|
23
26
|
</span>
|
|
24
27
|
);
|
|
25
28
|
}
|
|
29
|
+
|
|
30
|
+
export const DateSegment = forwardRef(BaseDateSegment);
|
|
31
|
+
DateSegment.displayName = 'DateSegment';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { createCalendar } from '@internationalized/date';
|
|
4
|
-
import React, { useRef } from 'react';
|
|
4
|
+
import React, { ForwardedRef, forwardRef, useRef } from 'react';
|
|
5
5
|
import { useDateField, useLocale } from 'react-aria';
|
|
6
6
|
import { useDateFieldState } from 'react-stately';
|
|
7
7
|
|
|
@@ -11,7 +11,11 @@ import { type DateFieldProps } from './date-field.types.js';
|
|
|
11
11
|
/**
|
|
12
12
|
* @private
|
|
13
13
|
*/
|
|
14
|
-
|
|
14
|
+
function BaseDateField(
|
|
15
|
+
{ separator, className, ...props }: DateFieldProps,
|
|
16
|
+
// Attached to the first editable segment, the same element react-aria focuses for this field.
|
|
17
|
+
forwardedRef: ForwardedRef<HTMLSpanElement>,
|
|
18
|
+
) {
|
|
15
19
|
const { locale } = useLocale();
|
|
16
20
|
const state = useDateFieldState({
|
|
17
21
|
...props,
|
|
@@ -21,12 +25,22 @@ export function DateField({ separator, className, ...props }: DateFieldProps) {
|
|
|
21
25
|
|
|
22
26
|
const ref = useRef(null);
|
|
23
27
|
const { fieldProps } = useDateField(props, state, ref);
|
|
28
|
+
const firstEditableIndex = state.segments.findIndex(segment => segment.isEditable);
|
|
24
29
|
|
|
25
30
|
return (
|
|
26
31
|
<div {...fieldProps} className={className} ref={ref}>
|
|
27
32
|
{state.segments.map((segment, i) => (
|
|
28
|
-
<DateSegment
|
|
33
|
+
<DateSegment
|
|
34
|
+
separator={separator}
|
|
35
|
+
key={i}
|
|
36
|
+
segment={segment}
|
|
37
|
+
state={state}
|
|
38
|
+
ref={i === firstEditableIndex ? forwardedRef : null}
|
|
39
|
+
/>
|
|
29
40
|
))}
|
|
30
41
|
</div>
|
|
31
42
|
);
|
|
32
43
|
}
|
|
44
|
+
|
|
45
|
+
export const DateField = forwardRef(BaseDateField);
|
|
46
|
+
DateField.displayName = 'DateField';
|