uikit-react-public 0.50.4 → 0.50.6
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/dist/components/Alert/Alert.d.ts +14 -5
- package/dist/components/Alert/Alert.stories.d.ts +10 -2
- package/dist/components/Alert/index.d.ts +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.d.ts +5 -3
- package/dist/components/ToggleGroup/ToggleGroup.stories.d.ts +2 -2
- package/dist/components/ToggleGroup/ToggleGroupContext.d.ts +2 -0
- package/dist/components/ToggleGroup/index.d.ts +1 -2
- package/dist/components/ToggleGroup/subcomponents/ToggleGroupItem.d.ts +13 -0
- package/dist/components/ToggleGroup/subcomponents/{Item.stories.d.ts → ToggleGroupItem.stories.d.ts} +2 -1
- package/dist/components/index.d.ts +2 -2
- package/dist/index.js +7146 -7061
- package/lib/components/Alert/Alert.mdx +4 -0
- package/lib/components/Alert/Alert.stories.tsx +17 -11
- package/lib/components/Alert/Alert.tsx +41 -19
- package/lib/components/Alert/__tests__/Alert.test.tsx +40 -12
- package/lib/components/Alert/__tests__/__snapshots__/Alert.test.tsx.snap +4 -4
- package/lib/components/Alert/index.ts +1 -1
- package/lib/components/ToggleGroup/ToggleGroup.tsx +51 -10
- package/lib/components/ToggleGroup/ToggleGroupContext.tsx +2 -0
- package/lib/components/ToggleGroup/__tests__/ToggleGroup.test.tsx +105 -6
- package/lib/components/ToggleGroup/index.ts +1 -2
- package/lib/components/ToggleGroup/subcomponents/{Item.stories.tsx → ToggleGroupItem.stories.tsx} +2 -3
- package/lib/components/ToggleGroup/subcomponents/{Item.tsx → ToggleGroupItem.tsx} +126 -52
- package/lib/components/ToggleGroup/subcomponents/__tests__/{Item.test.tsx → ToggleGroupItem.test.tsx} +50 -6
- package/lib/components/index.ts +2 -2
- package/package.json +1 -1
- package/dist/components/ToggleGroup/subcomponents/Item.d.ts +0 -11
- /package/dist/components/ToggleGroup/subcomponents/__tests__/{Item.test.d.ts → ToggleGroupItem.test.d.ts} +0 -0
|
@@ -20,6 +20,10 @@ The `<Alert>` component is used to convey contextual messages with visual hierar
|
|
|
20
20
|
|
|
21
21
|
Use the component with `Alert.Title` and `Alert.Message` as children to maintain semantic separation of content.
|
|
22
22
|
|
|
23
|
+
Use the `variant` prop to select `info`, `success`, `warning`, or `error`.
|
|
24
|
+
The legacy `type` prop remains available for backwards compatibility but is
|
|
25
|
+
deprecated.
|
|
26
|
+
|
|
23
27
|
## Action button
|
|
24
28
|
|
|
25
29
|
Pass a `Button` to `actionButton` to add an action. Alert sets the button to the extra-small size and applies the colour associated with the alert type.
|
|
@@ -29,10 +29,16 @@ const meta = {
|
|
|
29
29
|
layout: 'centered',
|
|
30
30
|
},
|
|
31
31
|
argTypes: {
|
|
32
|
+
variant: {
|
|
33
|
+
options: ['info', 'success', 'warning', 'error'],
|
|
34
|
+
control: { type: 'select' },
|
|
35
|
+
type: 'string',
|
|
36
|
+
},
|
|
32
37
|
type: {
|
|
33
38
|
options: ['info', 'success', 'warning', 'error'],
|
|
34
39
|
control: { type: 'select' },
|
|
35
40
|
type: 'string',
|
|
41
|
+
description: 'Deprecated. Use variant instead.',
|
|
36
42
|
},
|
|
37
43
|
className: { control: { type: 'text' } },
|
|
38
44
|
testId: { control: { type: 'text' } },
|
|
@@ -60,7 +66,7 @@ const meta = {
|
|
|
60
66
|
},
|
|
61
67
|
},
|
|
62
68
|
args: {
|
|
63
|
-
|
|
69
|
+
variant: 'info',
|
|
64
70
|
testId: 'ucl-uikit-alert',
|
|
65
71
|
},
|
|
66
72
|
tags: ['autodocs'],
|
|
@@ -71,36 +77,36 @@ type Story = StoryObj<typeof meta>;
|
|
|
71
77
|
|
|
72
78
|
const Template = (args: any) => (
|
|
73
79
|
<Alert {...args}>
|
|
74
|
-
<Alert.Title>Title of {args.
|
|
75
|
-
<Alert.Message>This is a {args.
|
|
80
|
+
<Alert.Title>Title of {args.variant} alert</Alert.Title>
|
|
81
|
+
<Alert.Message>This is a {args.variant} alert message.</Alert.Message>
|
|
76
82
|
</Alert>
|
|
77
83
|
);
|
|
78
84
|
|
|
79
85
|
export const Info: Story = {
|
|
80
86
|
render: Template,
|
|
81
87
|
args: {
|
|
82
|
-
|
|
88
|
+
variant: 'info',
|
|
83
89
|
},
|
|
84
90
|
};
|
|
85
91
|
|
|
86
92
|
export const Success: Story = {
|
|
87
93
|
render: Template,
|
|
88
94
|
args: {
|
|
89
|
-
|
|
95
|
+
variant: 'success',
|
|
90
96
|
},
|
|
91
97
|
};
|
|
92
98
|
|
|
93
99
|
export const Warning: Story = {
|
|
94
100
|
render: Template,
|
|
95
101
|
args: {
|
|
96
|
-
|
|
102
|
+
variant: 'warning',
|
|
97
103
|
},
|
|
98
104
|
};
|
|
99
105
|
|
|
100
106
|
export const Error: Story = {
|
|
101
107
|
render: Template,
|
|
102
108
|
args: {
|
|
103
|
-
|
|
109
|
+
variant: 'error',
|
|
104
110
|
},
|
|
105
111
|
};
|
|
106
112
|
|
|
@@ -118,12 +124,12 @@ export const WithActionButton: Story = {
|
|
|
118
124
|
</Button>
|
|
119
125
|
}
|
|
120
126
|
>
|
|
121
|
-
<Alert.Title>Title of {args.
|
|
122
|
-
<Alert.Message>This is a {args.
|
|
127
|
+
<Alert.Title>Title of {args.variant} alert</Alert.Title>
|
|
128
|
+
<Alert.Message>This is a {args.variant} alert message.</Alert.Message>
|
|
123
129
|
</Alert>
|
|
124
130
|
),
|
|
125
131
|
args: {
|
|
126
|
-
|
|
132
|
+
variant: 'info',
|
|
127
133
|
},
|
|
128
134
|
};
|
|
129
135
|
|
|
@@ -131,7 +137,7 @@ export const CommonMargins: Story = {
|
|
|
131
137
|
render: Template,
|
|
132
138
|
name: 'With common margins',
|
|
133
139
|
args: {
|
|
134
|
-
|
|
140
|
+
variant: 'info',
|
|
135
141
|
m: '24',
|
|
136
142
|
},
|
|
137
143
|
parameters: {
|
|
@@ -16,10 +16,13 @@ import capitalise from '../../utils/capitalise';
|
|
|
16
16
|
|
|
17
17
|
export const NAME = 'ucl-uikit-alert';
|
|
18
18
|
|
|
19
|
-
export type
|
|
19
|
+
export type AlertVariant = 'info' | 'success' | 'warning' | 'error';
|
|
20
|
+
|
|
21
|
+
/** @deprecated Use AlertVariant instead. */
|
|
22
|
+
export type AlertType = AlertVariant;
|
|
20
23
|
|
|
21
24
|
export interface AlertContextProps {
|
|
22
|
-
type:
|
|
25
|
+
type: AlertVariant;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
export const AlertContext = createContext<AlertContextProps | null>(null);
|
|
@@ -33,17 +36,30 @@ export function useAlertContext() {
|
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
export interface AlertBaseProps extends HTMLAttributes<HTMLDivElement> {
|
|
36
|
-
|
|
39
|
+
variant?: AlertVariant;
|
|
40
|
+
/** @deprecated Use variant instead. */
|
|
41
|
+
type?: AlertVariant;
|
|
37
42
|
iconProps?: Omit<IconProps, 'svg'>;
|
|
38
43
|
contentProps?: HTMLAttributes<HTMLDivElement>;
|
|
39
44
|
actionButton?: ReactElement<ButtonProps>;
|
|
40
45
|
testId?: string;
|
|
41
46
|
}
|
|
42
47
|
|
|
43
|
-
export type AlertProps = AlertBaseProps &
|
|
48
|
+
export type AlertProps = AlertBaseProps &
|
|
49
|
+
MarginProps &
|
|
50
|
+
(
|
|
51
|
+
| {
|
|
52
|
+
variant: AlertVariant;
|
|
53
|
+
}
|
|
54
|
+
| {
|
|
55
|
+
variant?: undefined;
|
|
56
|
+
type: AlertVariant;
|
|
57
|
+
}
|
|
58
|
+
);
|
|
44
59
|
|
|
45
60
|
const Alert = ({
|
|
46
|
-
|
|
61
|
+
variant,
|
|
62
|
+
type: deprecatedType,
|
|
47
63
|
iconProps,
|
|
48
64
|
contentProps,
|
|
49
65
|
actionButton,
|
|
@@ -52,7 +68,13 @@ const Alert = ({
|
|
|
52
68
|
className,
|
|
53
69
|
...props
|
|
54
70
|
}: AlertProps) => {
|
|
55
|
-
const
|
|
71
|
+
const resolvedVariant = variant ?? deprecatedType;
|
|
72
|
+
|
|
73
|
+
if (!resolvedVariant) {
|
|
74
|
+
throw new Error('Alert requires a variant');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const variantName = capitalise(resolvedVariant);
|
|
56
78
|
|
|
57
79
|
const [theme] = useTheme();
|
|
58
80
|
|
|
@@ -91,27 +113,27 @@ const Alert = ({
|
|
|
91
113
|
NAME,
|
|
92
114
|
baseStyles,
|
|
93
115
|
marginsStyle(props, theme),
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
116
|
+
resolvedVariant === 'info' && infoStyle,
|
|
117
|
+
resolvedVariant === 'success' && successStyle,
|
|
118
|
+
resolvedVariant === 'warning' && warningStyle,
|
|
119
|
+
resolvedVariant === 'error' && errorStyle,
|
|
98
120
|
className
|
|
99
121
|
);
|
|
100
122
|
|
|
101
123
|
const iconStyle = cx(
|
|
102
|
-
|
|
124
|
+
resolvedVariant === 'info' &&
|
|
103
125
|
css`
|
|
104
126
|
color: ${theme.colour.icon.brand};
|
|
105
127
|
`,
|
|
106
|
-
|
|
128
|
+
resolvedVariant === 'success' &&
|
|
107
129
|
css`
|
|
108
130
|
color: ${theme.colour.icon.success};
|
|
109
131
|
`,
|
|
110
|
-
|
|
132
|
+
resolvedVariant === 'warning' &&
|
|
111
133
|
css`
|
|
112
134
|
color: ${theme.colour.icon.warning};
|
|
113
135
|
`,
|
|
114
|
-
|
|
136
|
+
resolvedVariant === 'error' &&
|
|
115
137
|
css`
|
|
116
138
|
color: ${theme.colour.icon.critical};
|
|
117
139
|
`
|
|
@@ -141,9 +163,9 @@ const Alert = ({
|
|
|
141
163
|
success: Icon.CheckCircle,
|
|
142
164
|
warning: Icon.AlertTriangle,
|
|
143
165
|
error: Icon.XCircle,
|
|
144
|
-
}[
|
|
166
|
+
}[resolvedVariant];
|
|
145
167
|
|
|
146
|
-
const actionButtonVariant: Record<
|
|
168
|
+
const actionButtonVariant: Record<AlertVariant, ButtonVariant> = {
|
|
147
169
|
info: 'accent',
|
|
148
170
|
success: 'primary-success',
|
|
149
171
|
warning: 'primary-warning',
|
|
@@ -152,7 +174,7 @@ const Alert = ({
|
|
|
152
174
|
|
|
153
175
|
const renderedActionButton = actionButton
|
|
154
176
|
? cloneElement(actionButton, {
|
|
155
|
-
variant: actionButtonVariant[
|
|
177
|
+
variant: actionButtonVariant[resolvedVariant],
|
|
156
178
|
size: 'xsmall',
|
|
157
179
|
className: cx(actionButtonStyle, actionButton.props.className),
|
|
158
180
|
})
|
|
@@ -162,7 +184,7 @@ const Alert = ({
|
|
|
162
184
|
<>
|
|
163
185
|
<IconComp
|
|
164
186
|
className={iconStyle}
|
|
165
|
-
aria-label={
|
|
187
|
+
aria-label={variantName}
|
|
166
188
|
{...iconProps}
|
|
167
189
|
/>
|
|
168
190
|
<div
|
|
@@ -175,7 +197,7 @@ const Alert = ({
|
|
|
175
197
|
);
|
|
176
198
|
|
|
177
199
|
return (
|
|
178
|
-
<AlertContext.Provider value={{ type }}>
|
|
200
|
+
<AlertContext.Provider value={{ type: resolvedVariant }}>
|
|
179
201
|
<div
|
|
180
202
|
className={style}
|
|
181
203
|
data-testid={testId}
|
|
@@ -18,10 +18,10 @@ const hexToRgb = (hex: string) => {
|
|
|
18
18
|
describe('Alert', () => {
|
|
19
19
|
// Snapshot tests
|
|
20
20
|
|
|
21
|
-
test('snapshot:
|
|
21
|
+
test('snapshot: variant=info, no title', () => {
|
|
22
22
|
const renderResult = render(
|
|
23
23
|
<ThemeContextProvider>
|
|
24
|
-
<Alert
|
|
24
|
+
<Alert variant='info'>
|
|
25
25
|
<Alert.Message>This is an informational alert.</Alert.Message>
|
|
26
26
|
</Alert>
|
|
27
27
|
</ThemeContextProvider>
|
|
@@ -29,10 +29,10 @@ describe('Alert', () => {
|
|
|
29
29
|
expect(renderResult.container.firstChild).toMatchSnapshot();
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
test('snapshot:
|
|
32
|
+
test('snapshot: variant=success', () => {
|
|
33
33
|
const renderResult = render(
|
|
34
34
|
<ThemeContextProvider>
|
|
35
|
-
<Alert
|
|
35
|
+
<Alert variant='success'>
|
|
36
36
|
<Alert.Title>Success</Alert.Title>
|
|
37
37
|
<Alert.Message>Your operation completed successfully.</Alert.Message>
|
|
38
38
|
</Alert>
|
|
@@ -41,10 +41,10 @@ describe('Alert', () => {
|
|
|
41
41
|
expect(renderResult.container.firstChild).toMatchSnapshot();
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
test('snapshot:
|
|
44
|
+
test('snapshot: variant=warning', () => {
|
|
45
45
|
const renderResult = render(
|
|
46
46
|
<ThemeContextProvider>
|
|
47
|
-
<Alert
|
|
47
|
+
<Alert variant='warning'>
|
|
48
48
|
<Alert.Title>Warning</Alert.Title>
|
|
49
49
|
<Alert.Message>
|
|
50
50
|
This operation may have unintended consequences.
|
|
@@ -55,10 +55,10 @@ describe('Alert', () => {
|
|
|
55
55
|
expect(renderResult.container.firstChild).toMatchSnapshot();
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
test('snapshot:
|
|
58
|
+
test('snapshot: variant=error', () => {
|
|
59
59
|
const renderResult = render(
|
|
60
60
|
<ThemeContextProvider>
|
|
61
|
-
<Alert
|
|
61
|
+
<Alert variant='error'>
|
|
62
62
|
<Alert.Title>Error Alert</Alert.Title>
|
|
63
63
|
<Alert.Message>The operation failed.</Alert.Message>
|
|
64
64
|
</Alert>
|
|
@@ -74,11 +74,11 @@ describe('Alert', () => {
|
|
|
74
74
|
['error', theme.colour.fill.critical],
|
|
75
75
|
] as const)(
|
|
76
76
|
'uses the semantic %s colour for its action button',
|
|
77
|
-
(
|
|
77
|
+
(variant, expectedColour) => {
|
|
78
78
|
render(
|
|
79
79
|
<ThemeContextProvider>
|
|
80
80
|
<Alert
|
|
81
|
-
|
|
81
|
+
variant={variant}
|
|
82
82
|
actionButton={<Button>Action</Button>}
|
|
83
83
|
>
|
|
84
84
|
<Alert.Message>Alert message</Alert.Message>
|
|
@@ -101,7 +101,7 @@ describe('Alert', () => {
|
|
|
101
101
|
render(
|
|
102
102
|
<ThemeContextProvider>
|
|
103
103
|
<Alert
|
|
104
|
-
|
|
104
|
+
variant='info'
|
|
105
105
|
actionButton={
|
|
106
106
|
<Button
|
|
107
107
|
leftIcon={<Icon.ArrowRight />}
|
|
@@ -127,7 +127,7 @@ describe('Alert', () => {
|
|
|
127
127
|
render(
|
|
128
128
|
<ThemeContextProvider>
|
|
129
129
|
<Alert
|
|
130
|
-
|
|
130
|
+
variant='info'
|
|
131
131
|
actionButton={<Button>Action</Button>}
|
|
132
132
|
>
|
|
133
133
|
<Alert.Message>Alert message</Alert.Message>
|
|
@@ -138,4 +138,32 @@ describe('Alert', () => {
|
|
|
138
138
|
const alert = screen.getByRole('alert');
|
|
139
139
|
expect(getComputedStyle(alert).gap).toBe('32px');
|
|
140
140
|
});
|
|
141
|
+
|
|
142
|
+
test('supports the deprecated type prop', () => {
|
|
143
|
+
render(
|
|
144
|
+
<ThemeContextProvider>
|
|
145
|
+
<Alert type='error'>
|
|
146
|
+
<Alert.Message>Legacy alert</Alert.Message>
|
|
147
|
+
</Alert>
|
|
148
|
+
</ThemeContextProvider>
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
expect(screen.getByLabelText('Error')).toBeDefined();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('prefers variant when variant and deprecated type are both provided', () => {
|
|
155
|
+
render(
|
|
156
|
+
<ThemeContextProvider>
|
|
157
|
+
<Alert
|
|
158
|
+
variant='success'
|
|
159
|
+
type='error'
|
|
160
|
+
>
|
|
161
|
+
<Alert.Message>Alert message</Alert.Message>
|
|
162
|
+
</Alert>
|
|
163
|
+
</ThemeContextProvider>
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
expect(screen.getByLabelText('Success')).toBeDefined();
|
|
167
|
+
expect(screen.queryByLabelText('Error')).toBeNull();
|
|
168
|
+
});
|
|
141
169
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
|
-
exports[`Alert > snapshot:
|
|
3
|
+
exports[`Alert > snapshot: variant=error 1`] = `
|
|
4
4
|
<div
|
|
5
5
|
class="ucl-uikit-alert css-1eemwvm"
|
|
6
6
|
data-testid="ucl-uikit-alert"
|
|
@@ -54,7 +54,7 @@ exports[`Alert > snapshot: type=error 1`] = `
|
|
|
54
54
|
</div>
|
|
55
55
|
`;
|
|
56
56
|
|
|
57
|
-
exports[`Alert > snapshot:
|
|
57
|
+
exports[`Alert > snapshot: variant=info, no title 1`] = `
|
|
58
58
|
<div
|
|
59
59
|
class="ucl-uikit-alert css-1d2q0wf"
|
|
60
60
|
data-testid="ucl-uikit-alert"
|
|
@@ -97,7 +97,7 @@ exports[`Alert > snapshot: type=info, no title 1`] = `
|
|
|
97
97
|
</div>
|
|
98
98
|
`;
|
|
99
99
|
|
|
100
|
-
exports[`Alert > snapshot:
|
|
100
|
+
exports[`Alert > snapshot: variant=success 1`] = `
|
|
101
101
|
<div
|
|
102
102
|
class="ucl-uikit-alert css-1p0xqd2"
|
|
103
103
|
data-testid="ucl-uikit-alert"
|
|
@@ -149,7 +149,7 @@ exports[`Alert > snapshot: type=success 1`] = `
|
|
|
149
149
|
</div>
|
|
150
150
|
`;
|
|
151
151
|
|
|
152
|
-
exports[`Alert > snapshot:
|
|
152
|
+
exports[`Alert > snapshot: variant=warning 1`] = `
|
|
153
153
|
<div
|
|
154
154
|
class="ucl-uikit-alert css-1ks795a"
|
|
155
155
|
data-testid="ucl-uikit-alert"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { default } from './Alert';
|
|
2
2
|
export { default as AlertTitle } from './AlertTitle';
|
|
3
3
|
export { default as AlertMessage } from './AlertMessage';
|
|
4
|
-
export type { AlertProps } from './Alert';
|
|
4
|
+
export type { AlertProps, AlertVariant, AlertType } from './Alert';
|
|
5
5
|
export type { AlertTitleProps } from './AlertTitle';
|
|
6
6
|
export type { AlertMessageProps } from './AlertMessage';
|
|
@@ -7,18 +7,23 @@ import {
|
|
|
7
7
|
useState,
|
|
8
8
|
} from 'react';
|
|
9
9
|
|
|
10
|
-
import { css } from '@emotion/css';
|
|
10
|
+
import { css, cx } from '@emotion/css';
|
|
11
11
|
import useTheme from '../../theme/useTheme';
|
|
12
12
|
import useMediaQuery from '../../hooks/useMediaQuery';
|
|
13
13
|
import Select, { NonClearableSelectProps } from '../Select';
|
|
14
14
|
import ToggleGroupContext from './ToggleGroupContext';
|
|
15
|
-
import
|
|
15
|
+
import ToggleGroupItem, {
|
|
16
|
+
ToggleGroupItemProps,
|
|
17
|
+
} from './subcomponents/ToggleGroupItem';
|
|
18
|
+
|
|
19
|
+
export const NAME = 'ucl-uikit-toggle-group';
|
|
16
20
|
|
|
17
21
|
export interface ToggleGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
18
22
|
children: React.ReactNode;
|
|
19
23
|
value?: string;
|
|
20
24
|
defaultValue?: string;
|
|
21
25
|
onValueChange?: (value: string) => void;
|
|
26
|
+
testId?: string;
|
|
22
27
|
selectProps?: Omit<
|
|
23
28
|
NonClearableSelectProps<string>,
|
|
24
29
|
'options' | 'value' | 'onValueChange'
|
|
@@ -27,7 +32,14 @@ export interface ToggleGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
27
32
|
|
|
28
33
|
const MOBILE_SELECT_ITEM_THRESHOLD = 2;
|
|
29
34
|
|
|
30
|
-
const NAVIGATION_KEYS = [
|
|
35
|
+
const NAVIGATION_KEYS = [
|
|
36
|
+
'ArrowRight',
|
|
37
|
+
'ArrowDown',
|
|
38
|
+
'ArrowLeft',
|
|
39
|
+
'ArrowUp',
|
|
40
|
+
'Home',
|
|
41
|
+
'End',
|
|
42
|
+
];
|
|
31
43
|
|
|
32
44
|
const getEnabledItems = (container: HTMLElement) =>
|
|
33
45
|
Array.from(
|
|
@@ -38,13 +50,13 @@ const getEnabledItems = (container: HTMLElement) =>
|
|
|
38
50
|
|
|
39
51
|
const getItemsFromChildren = (
|
|
40
52
|
children: React.ReactNode
|
|
41
|
-
): React.ReactElement<
|
|
53
|
+
): React.ReactElement<ToggleGroupItemProps>[] =>
|
|
42
54
|
Children.toArray(children).flatMap((child) => {
|
|
43
55
|
if (!isValidElement<{ children?: React.ReactNode }>(child)) {
|
|
44
56
|
return [];
|
|
45
57
|
}
|
|
46
|
-
if (child.type ===
|
|
47
|
-
return [child as React.ReactElement<
|
|
58
|
+
if (child.type === ToggleGroupItem) {
|
|
59
|
+
return [child as React.ReactElement<ToggleGroupItemProps>];
|
|
48
60
|
}
|
|
49
61
|
return getItemsFromChildren(child.props.children);
|
|
50
62
|
});
|
|
@@ -55,6 +67,10 @@ const ToggleGroup = ({
|
|
|
55
67
|
defaultValue,
|
|
56
68
|
onValueChange,
|
|
57
69
|
selectProps,
|
|
70
|
+
className,
|
|
71
|
+
onKeyDown,
|
|
72
|
+
testId = NAME,
|
|
73
|
+
...htmlAttributes
|
|
58
74
|
}: ToggleGroupProps) => {
|
|
59
75
|
const [theme] = useTheme();
|
|
60
76
|
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
|
|
@@ -63,6 +79,14 @@ const ToggleGroup = ({
|
|
|
63
79
|
const isMobile = !useMediaQuery(`(min-width: ${theme.breakpoints.tablet}px)`);
|
|
64
80
|
const items = getItemsFromChildren(children);
|
|
65
81
|
const name = useId();
|
|
82
|
+
const hasSublabel = items.some((item) => item.props.sublabel);
|
|
83
|
+
const firstEnabledValue = items.find((item) => !item.props.disabled)?.props
|
|
84
|
+
.value;
|
|
85
|
+
const tabbableValue = items.some(
|
|
86
|
+
(item) => item.props.value === selectedValue && !item.props.disabled
|
|
87
|
+
)
|
|
88
|
+
? selectedValue
|
|
89
|
+
: firstEnabledValue;
|
|
66
90
|
|
|
67
91
|
const setValue = (nextValue: string) => {
|
|
68
92
|
if (!isControlled) {
|
|
@@ -73,6 +97,12 @@ const ToggleGroup = ({
|
|
|
73
97
|
};
|
|
74
98
|
|
|
75
99
|
const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
100
|
+
onKeyDown?.(event);
|
|
101
|
+
|
|
102
|
+
if (event.defaultPrevented) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
76
106
|
if (!NAVIGATION_KEYS.includes(event.key)) {
|
|
77
107
|
return;
|
|
78
108
|
}
|
|
@@ -91,8 +121,10 @@ const ToggleGroup = ({
|
|
|
91
121
|
const nextIndex =
|
|
92
122
|
{
|
|
93
123
|
ArrowRight: (currentIndex + 1) % enabledItems.length,
|
|
124
|
+
ArrowDown: (currentIndex + 1) % enabledItems.length,
|
|
94
125
|
ArrowLeft:
|
|
95
126
|
(currentIndex - 1 + enabledItems.length) % enabledItems.length,
|
|
127
|
+
ArrowUp: (currentIndex - 1 + enabledItems.length) % enabledItems.length,
|
|
96
128
|
Home: 0,
|
|
97
129
|
End: enabledItems.length - 1,
|
|
98
130
|
}[event.key] ?? currentIndex;
|
|
@@ -104,7 +136,6 @@ const ToggleGroup = ({
|
|
|
104
136
|
|
|
105
137
|
const toggleGroupStyle = css`
|
|
106
138
|
display: flex;
|
|
107
|
-
gap: -1px;
|
|
108
139
|
padding: ${theme.padding.p4};
|
|
109
140
|
background-color: ${theme.colour.fill.subtle};
|
|
110
141
|
border-radius: ${theme.radius.r4};
|
|
@@ -114,6 +145,8 @@ const ToggleGroup = ({
|
|
|
114
145
|
return (
|
|
115
146
|
<Select
|
|
116
147
|
{...selectProps}
|
|
148
|
+
className={cx(NAME, selectProps?.className, className)}
|
|
149
|
+
testId={testId}
|
|
117
150
|
options={items.map((item) => ({
|
|
118
151
|
label: item.props.label,
|
|
119
152
|
value: item.props.value,
|
|
@@ -126,10 +159,18 @@ const ToggleGroup = ({
|
|
|
126
159
|
|
|
127
160
|
return (
|
|
128
161
|
<ToggleGroupContext.Provider
|
|
129
|
-
value={{
|
|
162
|
+
value={{
|
|
163
|
+
value: selectedValue,
|
|
164
|
+
setValue,
|
|
165
|
+
name,
|
|
166
|
+
tabbableValue,
|
|
167
|
+
hasSublabel,
|
|
168
|
+
}}
|
|
130
169
|
>
|
|
131
170
|
<div
|
|
132
|
-
|
|
171
|
+
{...htmlAttributes}
|
|
172
|
+
className={cx(NAME, toggleGroupStyle, className)}
|
|
173
|
+
data-testid={testId}
|
|
133
174
|
role='radiogroup'
|
|
134
175
|
onKeyDown={handleKeyDown}
|
|
135
176
|
>
|
|
@@ -139,6 +180,6 @@ const ToggleGroup = ({
|
|
|
139
180
|
);
|
|
140
181
|
};
|
|
141
182
|
|
|
142
|
-
ToggleGroup.Item =
|
|
183
|
+
ToggleGroup.Item = ToggleGroupItem;
|
|
143
184
|
|
|
144
185
|
export default ToggleGroup;
|
|
@@ -4,6 +4,8 @@ export interface ToggleGroupContextType {
|
|
|
4
4
|
value: string | undefined;
|
|
5
5
|
setValue: (value: string) => void;
|
|
6
6
|
name: string;
|
|
7
|
+
tabbableValue: string | undefined;
|
|
8
|
+
hasSublabel: boolean;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
const ToggleGroupContext = createContext<ToggleGroupContextType | undefined>(
|