@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.
- package/CHANGELOG.md +37 -0
- package/dist/component-type.json +1 -1
- package/dist/components/button/button.component.d.ts +2 -17
- package/dist/components/button/button.component.js +9 -4
- package/dist/components/button/button.types.d.ts +16 -6
- 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/link/link.component.d.ts +4 -12
- package/dist/components/link/link.component.js +30 -7
- package/dist/components/link/link.types.d.ts +26 -3
- package/dist/components/modal/components/modal-dialog/components/modal-dialog-footer/modal-dialog-footer.component.d.ts +1 -1
- package/dist/components/modal/components/modal-dialog/components/modal-dialog-footer/modal-dialog-footer.component.js +14 -7
- package/dist/components/modal/components/modal-dialog/components/modal-dialog-footer/modal-dialog-footer.types.d.ts +11 -0
- 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 -3
- package/src/components/button/button.component.tsx +10 -9
- package/src/components/button/button.types.ts +21 -8
- 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/link/link.component.tsx +60 -12
- package/src/components/link/link.types.ts +31 -4
- package/src/components/modal/components/modal-dialog/components/modal-dialog-footer/modal-dialog-footer.component.tsx +16 -2
- package/src/components/modal/components/modal-dialog/components/modal-dialog-footer/modal-dialog-footer.types.ts +12 -0
- 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/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
262
|
"@westpac/test-config": "~0.0.0",
|
|
264
263
|
"@westpac/ts-config": "~0.0.0",
|
|
265
|
-
"@westpac/style-config": "~1.0.2"
|
|
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",
|
|
@@ -280,6 +281,7 @@
|
|
|
280
281
|
},
|
|
281
282
|
"peerDependencies": {
|
|
282
283
|
"react": ">=18.2.0",
|
|
284
|
+
"react-dom": ">=18.2.0",
|
|
283
285
|
"tailwindcss": "~4.1.12"
|
|
284
286
|
},
|
|
285
287
|
"repository": {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, {
|
|
3
|
+
import React, { ForwardedRef, forwardRef, useMemo } from 'react';
|
|
4
4
|
import { mergeProps, useFocusRing } from 'react-aria';
|
|
5
5
|
|
|
6
6
|
import { useBreakpoint } from '../../hook/breakpoints.hook.js';
|
|
7
7
|
import { resolveResponsiveVariant } from '../../utils/breakpoint.util.js';
|
|
8
8
|
|
|
9
9
|
import { styles as buttonStyles } from './button.styles.js';
|
|
10
|
-
import {
|
|
10
|
+
import { ButtonImplementationProps, ButtonComponent } from './button.types.js';
|
|
11
11
|
import { getIconSize } from './button.utils.js';
|
|
12
12
|
|
|
13
13
|
function BaseButton(
|
|
@@ -18,7 +18,7 @@ function BaseButton(
|
|
|
18
18
|
soft,
|
|
19
19
|
block = false,
|
|
20
20
|
justify,
|
|
21
|
-
tag: Tag
|
|
21
|
+
tag: Tag,
|
|
22
22
|
iconBefore: IconBefore,
|
|
23
23
|
iconAfter: IconAfter,
|
|
24
24
|
iconLook,
|
|
@@ -26,11 +26,12 @@ function BaseButton(
|
|
|
26
26
|
iconSize,
|
|
27
27
|
children,
|
|
28
28
|
removeLinkPadding,
|
|
29
|
-
type = Tag === 'button' ? 'button' : undefined,
|
|
30
29
|
...props
|
|
31
|
-
}:
|
|
32
|
-
ref:
|
|
30
|
+
}: ButtonImplementationProps,
|
|
31
|
+
ref: ForwardedRef<unknown>,
|
|
33
32
|
) {
|
|
33
|
+
const Component = Tag ?? 'button';
|
|
34
|
+
const renderProps = Component === 'button' ? { ...props, type: props.type ?? 'button' } : props;
|
|
34
35
|
const { isFocusVisible, focusProps } = useFocusRing();
|
|
35
36
|
const btnIconSize = useMemo(() => iconSize || getIconSize(size), [iconSize, size]);
|
|
36
37
|
const breakpoint = useBreakpoint();
|
|
@@ -76,7 +77,7 @@ function BaseButton(
|
|
|
76
77
|
}, [iconColor, look, soft]);
|
|
77
78
|
|
|
78
79
|
return (
|
|
79
|
-
<
|
|
80
|
+
<Component ref={ref} className={styles.base({ className })} {...mergeProps(renderProps, focusProps)}>
|
|
80
81
|
{IconBefore && (
|
|
81
82
|
<IconBefore
|
|
82
83
|
look={iconLook}
|
|
@@ -96,8 +97,8 @@ function BaseButton(
|
|
|
96
97
|
aria-hidden
|
|
97
98
|
/>
|
|
98
99
|
)}
|
|
99
|
-
</
|
|
100
|
+
</Component>
|
|
100
101
|
);
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
export const Button = forwardRef(BaseButton);
|
|
104
|
+
export const Button = forwardRef(BaseButton) as unknown as ButtonComponent;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
2
|
import { type VariantProps } from 'tailwind-variants';
|
|
3
3
|
|
|
4
4
|
import { ResponsiveVariants } from '../../types/responsive-variants.types.js';
|
|
@@ -8,9 +8,7 @@ import { styles } from './button.styles.js';
|
|
|
8
8
|
|
|
9
9
|
type Variants = VariantProps<typeof styles>;
|
|
10
10
|
|
|
11
|
-
export type
|
|
12
|
-
|
|
13
|
-
export type ButtonProps = {
|
|
11
|
+
export type BaseButtonProps = {
|
|
14
12
|
/**
|
|
15
13
|
* Fit button width to its parent width.
|
|
16
14
|
* @default false
|
|
@@ -69,10 +67,25 @@ export type ButtonProps = {
|
|
|
69
67
|
* Removes background colour and adjusts text colour.
|
|
70
68
|
*/
|
|
71
69
|
soft?: ResponsiveVariants<Variants['soft']>;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type ButtonRef<C extends React.ElementType = 'button'> = React.ComponentRef<C>;
|
|
73
|
+
|
|
74
|
+
export type PolymorphicRef<C extends React.ElementType> = React.ComponentPropsWithRef<C>['ref'];
|
|
75
|
+
|
|
76
|
+
export type ButtonProps<C extends React.ElementType = 'button'> = BaseButtonProps & {
|
|
72
77
|
/**
|
|
73
|
-
*
|
|
78
|
+
* Type to render
|
|
74
79
|
* @default button
|
|
75
80
|
*/
|
|
76
|
-
tag?:
|
|
77
|
-
} &
|
|
78
|
-
|
|
81
|
+
tag?: C;
|
|
82
|
+
} & Omit<React.ComponentPropsWithoutRef<C>, keyof BaseButtonProps | 'tag'>;
|
|
83
|
+
|
|
84
|
+
export type ButtonImplementationProps = BaseButtonProps & {
|
|
85
|
+
className?: string;
|
|
86
|
+
tag?: React.ElementType;
|
|
87
|
+
} & Record<string, unknown>;
|
|
88
|
+
|
|
89
|
+
export type ButtonComponent = <C extends React.ElementType = 'button'>(
|
|
90
|
+
props: ButtonProps<C> & { ref?: PolymorphicRef<C> },
|
|
91
|
+
) => React.ReactElement | null;
|
|
@@ -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';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { DateValue, getDayOfWeek, isWeekend } from '@internationalized/date';
|
|
4
4
|
import { Breakpoint } from '@westpac/style-config/constants';
|
|
5
|
-
import React, { useMemo, useRef } from 'react';
|
|
5
|
+
import React, { ForwardedRef, forwardRef, useMemo, useRef } from 'react';
|
|
6
6
|
import { useButton, useDatePicker, useLocale } from 'react-aria';
|
|
7
7
|
import { useDatePickerState } from 'react-stately';
|
|
8
8
|
|
|
@@ -19,19 +19,24 @@ import { styles as datePickerStyles } from './date-picker.styles.js';
|
|
|
19
19
|
import { type DatePickerProps } from './date-picker.types.js';
|
|
20
20
|
|
|
21
21
|
const BREAKPOINTS_DECRECENT = ['xl', 'lg', 'md', 'sm', 'xsl', 'initial'] as const;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
22
|
+
function BaseDatePicker(
|
|
23
|
+
{
|
|
24
|
+
size = 'medium',
|
|
25
|
+
className,
|
|
26
|
+
bottomSheetView = { initial: true, xsl: false },
|
|
27
|
+
block,
|
|
28
|
+
isDateUnavailable,
|
|
29
|
+
disableDaysOfWeek,
|
|
30
|
+
disableWeekends,
|
|
31
|
+
separator,
|
|
32
|
+
portalContainer,
|
|
33
|
+
placement = 'bottom left',
|
|
34
|
+
...props
|
|
35
|
+
}: DatePickerProps,
|
|
36
|
+
// Attached to the first editable date segment, so `ref.current.focus()` (e.g. react-hook-form
|
|
37
|
+
// focusing a field with a validation error) lands on a focusable control.
|
|
38
|
+
forwardedRef: ForwardedRef<HTMLSpanElement>,
|
|
39
|
+
) {
|
|
35
40
|
const { locale } = useLocale();
|
|
36
41
|
|
|
37
42
|
const enhancedIsDateUnavailable = useMemo(() => {
|
|
@@ -60,7 +65,7 @@ export function DatePicker({
|
|
|
60
65
|
isReadOnly: props.isReadOnly,
|
|
61
66
|
isDisabled: props.isDisabled,
|
|
62
67
|
});
|
|
63
|
-
const ref = useRef(null);
|
|
68
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
64
69
|
|
|
65
70
|
const { groupProps, labelProps, fieldProps, buttonProps, dialogProps, calendarProps } = useDatePicker(
|
|
66
71
|
{ isDateUnavailable: enhancedIsDateUnavailable, ...props },
|
|
@@ -84,12 +89,15 @@ export function DatePicker({
|
|
|
84
89
|
|
|
85
90
|
const { buttonProps: newButtonProps } = useButton(buttonProps, buttonRef);
|
|
86
91
|
|
|
92
|
+
// This is required so branding applies correctly by default due to portal location, can be overridden with portalContainer prop
|
|
87
93
|
const brandContainer = useMemo(() => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
if (typeof window !== 'undefined') {
|
|
95
|
+
return (
|
|
96
|
+
document.querySelector('[data-brand]') ||
|
|
97
|
+
document.querySelector('[class^="theme-"], [class*=" theme-"]') ||
|
|
98
|
+
undefined
|
|
99
|
+
);
|
|
100
|
+
}
|
|
93
101
|
}, []);
|
|
94
102
|
|
|
95
103
|
return (
|
|
@@ -107,7 +115,7 @@ export function DatePicker({
|
|
|
107
115
|
}}
|
|
108
116
|
className={styles.input({ className })}
|
|
109
117
|
>
|
|
110
|
-
<DateField className={styles.dateField()} separator={separator} {...fieldProps} />
|
|
118
|
+
<DateField className={styles.dateField()} separator={separator} {...fieldProps} ref={forwardedRef} />
|
|
111
119
|
<Button
|
|
112
120
|
look="faint"
|
|
113
121
|
className={styles.button()}
|
|
@@ -134,3 +142,6 @@ export function DatePicker({
|
|
|
134
142
|
</>
|
|
135
143
|
);
|
|
136
144
|
}
|
|
145
|
+
|
|
146
|
+
export const DatePicker = forwardRef(BaseDatePicker);
|
|
147
|
+
DatePicker.displayName = 'DatePicker';
|
|
@@ -3,26 +3,26 @@ import { tv } from 'tailwind-variants';
|
|
|
3
3
|
export const styles = tv({
|
|
4
4
|
slots: {
|
|
5
5
|
input: 'form-control flex items-center border-0',
|
|
6
|
-
dateField: 'rounded rounded-r-none border border-r-0 border-border-muted-strong',
|
|
6
|
+
dateField: 'rounded rounded-r-none border border-r-0 border-border-muted-strong whitespace-nowrap',
|
|
7
7
|
button: 'flex items-center justify-center rounded-l-none border-border-muted-strong bg-surface-muted-pale',
|
|
8
8
|
},
|
|
9
9
|
variants: {
|
|
10
10
|
size: {
|
|
11
11
|
small: {
|
|
12
12
|
input: 'max-h-5',
|
|
13
|
-
dateField: 'h-5 w-[120px] form-control-small',
|
|
13
|
+
dateField: 'h-5 min-w-[120px] form-control-small',
|
|
14
14
|
},
|
|
15
15
|
medium: {
|
|
16
16
|
input: 'max-h-6',
|
|
17
|
-
dateField: 'h-6 w-[140px] form-control-medium',
|
|
17
|
+
dateField: 'h-6 min-w-[140px] form-control-medium',
|
|
18
18
|
},
|
|
19
19
|
large: {
|
|
20
20
|
input: 'max-h-7',
|
|
21
|
-
dateField: 'h-7 w-[145px] form-control-large',
|
|
21
|
+
dateField: 'h-7 min-w-[145px] form-control-large',
|
|
22
22
|
},
|
|
23
23
|
xlarge: {
|
|
24
24
|
input: 'max-h-8',
|
|
25
|
-
dateField: 'h-8 w-[165px] form-control-xlarge',
|
|
25
|
+
dateField: 'h-8 min-w-[165px] form-control-xlarge',
|
|
26
26
|
},
|
|
27
27
|
},
|
|
28
28
|
block: {
|
|
@@ -1,30 +1,77 @@
|
|
|
1
|
-
// TODO: This component will not work with the Next Link component in Next.js 16+ as legacyBehavior as has been deprecated in 15. Need to find a solution.
|
|
2
1
|
'use client';
|
|
3
2
|
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
3
|
+
import { type FocusableElement } from '@react-types/shared';
|
|
4
|
+
import React, { ForwardedRef, forwardRef } from 'react';
|
|
5
|
+
import { mergeProps, useFocusRing, useLink, useObjectRef } from 'react-aria';
|
|
6
6
|
|
|
7
7
|
import { ArrowRightIcon } from '../icon/index.js';
|
|
8
8
|
|
|
9
9
|
import { styles as linkStyles } from './link.styles.js';
|
|
10
|
-
import {
|
|
10
|
+
import { LinkComponent, LinkImplementationProps } from './link.types.js';
|
|
11
11
|
|
|
12
12
|
export function BaseLink(
|
|
13
13
|
{
|
|
14
14
|
className,
|
|
15
15
|
children,
|
|
16
16
|
href,
|
|
17
|
+
tag: Tag,
|
|
17
18
|
iconBefore: IconBefore,
|
|
18
19
|
iconAfter: IconAfter,
|
|
19
20
|
iconSize = 'small',
|
|
20
21
|
target,
|
|
21
22
|
type = 'standalone',
|
|
22
23
|
underline = true,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
autoFocus,
|
|
25
|
+
elementType,
|
|
26
|
+
onBlur,
|
|
27
|
+
onClick,
|
|
28
|
+
onFocus,
|
|
29
|
+
onFocusChange,
|
|
30
|
+
onKeyDown,
|
|
31
|
+
onKeyUp,
|
|
32
|
+
onPress,
|
|
33
|
+
onPressChange,
|
|
34
|
+
onPressEnd,
|
|
35
|
+
onPressStart,
|
|
36
|
+
onPressUp,
|
|
37
|
+
download,
|
|
38
|
+
ping,
|
|
39
|
+
referrerPolicy,
|
|
40
|
+
rel,
|
|
41
|
+
routerOptions,
|
|
42
|
+
...componentProps
|
|
43
|
+
}: LinkImplementationProps,
|
|
44
|
+
ref: ForwardedRef<unknown>,
|
|
26
45
|
) {
|
|
27
|
-
const
|
|
46
|
+
const Component = Tag ?? 'a';
|
|
47
|
+
const ariaElementType = typeof Component === 'string' ? Component : (elementType ?? 'a');
|
|
48
|
+
const linkRef = useObjectRef(ref as ForwardedRef<FocusableElement>);
|
|
49
|
+
const { linkProps } = useLink(
|
|
50
|
+
{
|
|
51
|
+
...componentProps,
|
|
52
|
+
autoFocus,
|
|
53
|
+
download,
|
|
54
|
+
elementType: ariaElementType,
|
|
55
|
+
href: typeof href === 'string' ? href : undefined,
|
|
56
|
+
onBlur,
|
|
57
|
+
onClick,
|
|
58
|
+
onFocus,
|
|
59
|
+
onFocusChange,
|
|
60
|
+
onKeyDown,
|
|
61
|
+
onKeyUp,
|
|
62
|
+
onPress,
|
|
63
|
+
onPressChange,
|
|
64
|
+
onPressEnd,
|
|
65
|
+
onPressStart,
|
|
66
|
+
onPressUp,
|
|
67
|
+
ping,
|
|
68
|
+
referrerPolicy,
|
|
69
|
+
rel,
|
|
70
|
+
routerOptions,
|
|
71
|
+
target,
|
|
72
|
+
},
|
|
73
|
+
linkRef,
|
|
74
|
+
);
|
|
28
75
|
const { isFocusVisible, focusProps } = useFocusRing();
|
|
29
76
|
const styles = linkStyles({ type, underline, isFocusVisible });
|
|
30
77
|
|
|
@@ -33,9 +80,10 @@ export function BaseLink(
|
|
|
33
80
|
}
|
|
34
81
|
|
|
35
82
|
return (
|
|
36
|
-
<
|
|
83
|
+
<Component
|
|
84
|
+
{...componentProps}
|
|
37
85
|
{...mergeProps(linkProps, focusProps)}
|
|
38
|
-
ref={
|
|
86
|
+
ref={linkRef}
|
|
39
87
|
href={href}
|
|
40
88
|
target={target}
|
|
41
89
|
className={styles.base({ className })}
|
|
@@ -43,8 +91,8 @@ export function BaseLink(
|
|
|
43
91
|
{IconBefore && <IconBefore size={iconSize} color="primary" className={styles.iconBefore()} />}
|
|
44
92
|
<span>{children}</span>
|
|
45
93
|
{IconAfter && <IconAfter size={iconSize} color="primary" className={styles.iconAfter()} />}
|
|
46
|
-
</
|
|
94
|
+
</Component>
|
|
47
95
|
);
|
|
48
96
|
}
|
|
49
97
|
|
|
50
|
-
export const Link = forwardRef(BaseLink);
|
|
98
|
+
export const Link = forwardRef(BaseLink) as unknown as LinkComponent;
|