uikit-react-public 0.29.6 → 0.30.1
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/Accordion/Accordion.stories.d.ts +1 -1
- package/dist/components/Alert/Alert.stories.d.ts +1 -1
- package/dist/components/AppHeader/AppHeader.stories.d.ts +1 -1
- package/dist/components/AppMenu/AppMenu.stories.d.ts +1 -1
- package/dist/components/Avatar/Avatar.stories.d.ts +1 -1
- package/dist/components/Badge/Badge.stories.d.ts +1 -1
- package/dist/components/BaseCheckbox/BaseCheckbox.stories.d.ts +1 -1
- package/dist/components/Blanket/Blanket.stories.d.ts +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.stories.d.ts +1 -1
- package/dist/components/Button/Button.stories.d.ts +1 -1
- package/dist/components/Calendar/Calendar.stories.d.ts +1 -1
- package/dist/components/Calendar/subcomponents/Day.stories.d.ts +1 -1
- package/dist/components/Checkbox/Checkbox.stories.d.ts +1 -1
- package/dist/components/Chip/Chip.stories.d.ts +1 -1
- package/dist/components/Datepicker/Datepicker.stories.d.ts +1 -1
- package/dist/components/Dialog/BaseDialog.d.ts +1 -1
- package/dist/components/Dialog/Dialog.d.ts +1 -1
- package/dist/components/Dialog/Dialog.stories.d.ts +2 -2
- package/dist/components/Divider/Divider.stories.d.ts +1 -1
- package/dist/components/Dropdown/Dropdown.stories.d.ts +1 -1
- package/dist/components/FeedbackDialog/FeedbackDialog.stories.d.ts +1 -1
- package/dist/components/Field/Field.stories.d.ts +1 -1
- package/dist/components/FileInput/FileInput.stories.d.ts +1 -1
- package/dist/components/Footer/Footer.stories.d.ts +1 -1
- package/dist/components/Header/Header.stories.d.ts +1 -1
- package/dist/components/Heading/Heading.stories.d.ts +1 -1
- package/dist/components/Icon/Icon.stories.d.ts +1 -1
- package/dist/components/IconButton/IconButton.stories.d.ts +1 -1
- package/dist/components/Input/Input.stories.d.ts +1 -1
- package/dist/components/Label/Label.stories.d.ts +1 -1
- package/dist/components/Layout/Layout.stories.d.ts +1 -1
- package/dist/components/Link/Link.stories.d.ts +1 -1
- package/dist/components/Main/Main.stories.d.ts +1 -1
- package/dist/components/Modal/Modal.stories.d.ts +1 -1
- package/dist/components/NativeDatepicker/NativeDatepicker.stories.d.ts +1 -1
- package/dist/components/Overlay/Overlay.stories.d.ts +2 -2
- package/dist/components/Pagination/Pagination.stories.d.ts +1 -1
- package/dist/components/Paragraph/Paragraph.stories.d.ts +1 -1
- package/dist/components/Radio/Radio.stories.d.ts +1 -1
- package/dist/components/Search/Search.stories.d.ts +1 -1
- package/dist/components/Select/Select.stories.d.ts +1 -1
- package/dist/components/Snackbar/Snackbar.stories.d.ts +1 -1
- package/dist/components/Spinner/Spinner.stories.d.ts +1 -1
- package/dist/components/StandaloneLink/StandaloneLink.stories.d.ts +1 -1
- package/dist/components/Table/Table.stories.d.ts +1 -1
- package/dist/components/Table/subcomponents/Cell/Cell.stories.d.ts +2 -2
- package/dist/components/Table/subcomponents/HeadCell/HeadCell.stories.d.ts +2 -2
- package/dist/components/Tabs/Tab.d.ts +11 -5
- package/dist/components/Tabs/TabContext.d.ts +14 -8
- package/dist/components/Tabs/Tabs.d.ts +25 -8
- package/dist/components/Tabs/Tabs.stories.d.ts +5 -9
- package/dist/components/Tabs/TabsList.d.ts +9 -0
- package/dist/components/Tabs/TabsPanel.d.ts +10 -0
- package/dist/components/Tabs/index.d.ts +2 -1
- package/dist/components/Textarea/Textarea.stories.d.ts +1 -1
- package/dist/components/Timepicker/Timepicker.stories.d.ts +1 -1
- package/dist/components/Toggle/Toggle.stories.d.ts +1 -1
- package/dist/components/Tooltip/Tooltip.stories.d.ts +1 -1
- package/dist/components/UclLogo/UclLogo.stories.d.ts +1 -1
- package/dist/components/WeekPicker/WeekPicker.stories.d.ts +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/index.js +4392 -4123
- package/dist/utils/announce.d.ts +2 -1
- package/lib/Welcome.mdx +1 -1
- package/lib/components/Accordion/Accordion.stories.tsx +1 -1
- package/lib/components/Alert/Alert.mdx +1 -1
- package/lib/components/Alert/Alert.stories.tsx +1 -1
- package/lib/components/AppHeader/AppHeader.stories.tsx +1 -1
- package/lib/components/AppMenu/AppMenu.stories.tsx +1 -1
- package/lib/components/Avatar/Avatar.mdx +1 -1
- package/lib/components/Avatar/Avatar.stories.tsx +1 -1
- package/lib/components/Badge/Badge.stories.tsx +1 -1
- package/lib/components/BaseCheckbox/BaseCheckbox.stories.tsx +2 -2
- package/lib/components/Blanket/Blanket.stories.tsx +1 -1
- package/lib/components/Breadcrumbs/Breadcrumbs.stories.tsx +1 -1
- package/lib/components/Button/Button.mdx +1 -1
- package/lib/components/Button/Button.stories.tsx +2 -2
- package/lib/components/Calendar/Calendar.stories.tsx +2 -2
- package/lib/components/Calendar/subcomponents/Day.stories.tsx +1 -1
- package/lib/components/Checkbox/Checkbox.stories.tsx +2 -2
- package/lib/components/Chip/Chip.stories.tsx +2 -2
- package/lib/components/Datepicker/Datepicker.stories.tsx +2 -2
- package/lib/components/Dialog/BaseDialog.tsx +180 -161
- package/lib/components/Dialog/Dialog.stories.tsx +1 -1
- package/lib/components/Dialog/Dialog.tsx +15 -11
- package/lib/components/Divider/Divider.stories.tsx +1 -1
- package/lib/components/Dropdown/Dropdown.stories.tsx +1 -1
- package/lib/components/FeedbackDialog/FeedbackDialog.stories.tsx +1 -1
- package/lib/components/Field/Field.stories.tsx +2 -2
- package/lib/components/FileInput/FileInput.stories.tsx +1 -1
- package/lib/components/Footer/Footer.stories.tsx +1 -1
- package/lib/components/Header/Header.mdx +1 -1
- package/lib/components/Header/Header.stories.tsx +1 -1
- package/lib/components/Heading/Documentation.mdx +1 -1
- package/lib/components/Heading/Heading.stories.tsx +1 -1
- package/lib/components/Icon/Icon.stories.tsx +1 -1
- package/lib/components/IconButton/IconButton.stories.tsx +1 -1
- package/lib/components/Input/Documentation.mdx +1 -1
- package/lib/components/Input/Input.stories.tsx +1 -1
- package/lib/components/Label/Label.stories.tsx +1 -1
- package/lib/components/Layout/Layout.stories.tsx +1 -1
- package/lib/components/Link/Link.stories.tsx +1 -1
- package/lib/components/Main/Main.stories.tsx +1 -1
- package/lib/components/Modal/Modal.stories.tsx +1 -1
- package/lib/components/NativeDatepicker/NativeDatepicker.stories.tsx +2 -2
- package/lib/components/Overlay/Overlay.stories.tsx +1 -1
- package/lib/components/Overlay/Overlay.tsx +1 -4
- package/lib/components/Pagination/Pagination.stories.tsx +1 -1
- package/lib/components/Paragraph/Paragraph.stories.tsx +1 -1
- package/lib/components/Radio/Radio.stories.tsx +2 -2
- package/lib/components/Search/Search.stories.tsx +1 -1
- package/lib/components/Select/Select.mdx +1 -1
- package/lib/components/Select/Select.stories.tsx +2 -2
- package/lib/components/Select/Select.types.ts +1 -2
- package/lib/components/Snackbar/Snackbar.stories.tsx +1 -1
- package/lib/components/Spinner/Spinner.stories.tsx +1 -1
- package/lib/components/StandaloneLink/StandaloneLink.stories.tsx +1 -1
- package/lib/components/Table/Table.stories.tsx +1 -1
- package/lib/components/Table/subcomponents/Cell/Cell.stories.tsx +2 -2
- package/lib/components/Table/subcomponents/HeadCell/HeadCell.stories.tsx +1 -1
- package/lib/components/Tabs/Tab.tsx +209 -36
- package/lib/components/Tabs/TabContext.tsx +20 -7
- package/lib/components/Tabs/Tabs.stories.tsx +87 -68
- package/lib/components/Tabs/Tabs.tsx +129 -37
- package/lib/components/Tabs/TabsList.tsx +134 -0
- package/lib/components/Tabs/TabsPanel.tsx +55 -0
- package/lib/components/Tabs/__tests__/Tabs.test.tsx +173 -105
- package/lib/components/Tabs/index.ts +8 -1
- package/lib/components/Textarea/Textarea.stories.tsx +1 -1
- package/lib/components/Timepicker/Timepicker.stories.tsx +1 -1
- package/lib/components/Toggle/Documentation.mdx +1 -1
- package/lib/components/Toggle/Toggle.stories.tsx +1 -1
- package/lib/components/Tooltip/Tooltip.stories.tsx +1 -1
- package/lib/components/UclLogo/UclLogo.stories.tsx +1 -1
- package/lib/components/WeekPicker/WeekPicker.stories.tsx +2 -2
- package/lib/components/common/Common.mdx +1 -1
- package/lib/components/index.ts +7 -1
- package/lib/theme/Icons.mdx +1 -1
- package/lib/theme/Typography.mdx +1 -1
- package/lib/utils/__tests__/announce.test.ts +53 -0
- package/lib/utils/announce.ts +33 -10
- package/package.json +8 -11
- package/lib/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap +0 -185
|
@@ -1,122 +1,190 @@
|
|
|
1
|
-
import { describe, expect, test } from 'vitest';
|
|
2
|
-
import { render } from '@testing-library/react';
|
|
1
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
3
3
|
import Tabs from '../Tabs';
|
|
4
4
|
import { ThemeContextProvider } from '../../../theme/useTheme';
|
|
5
5
|
|
|
6
|
+
const wrap = (ui: React.ReactElement) =>
|
|
7
|
+
render(<ThemeContextProvider>{ui}</ThemeContextProvider>);
|
|
8
|
+
|
|
6
9
|
describe('Tabs', () => {
|
|
7
|
-
|
|
10
|
+
test('renders a tablist with selectable button tabs', () => {
|
|
11
|
+
const onValueChange = vi.fn();
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
13
|
+
wrap(
|
|
14
|
+
<Tabs
|
|
15
|
+
defaultValue='overview'
|
|
16
|
+
onValueChange={onValueChange}
|
|
17
|
+
>
|
|
18
|
+
<Tabs.List aria-label='Course sections'>
|
|
19
|
+
<Tabs.Tab value='overview'>Overview</Tabs.Tab>
|
|
20
|
+
<Tabs.Tab value='modules'>Modules</Tabs.Tab>
|
|
21
|
+
</Tabs.List>
|
|
22
|
+
</Tabs>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
expect(
|
|
26
|
+
screen.getByRole('tablist', { name: 'Course sections' })
|
|
27
|
+
).toBeInTheDocument();
|
|
28
|
+
expect(screen.getByRole('tab', { name: 'Overview' })).toHaveAttribute(
|
|
29
|
+
'aria-selected',
|
|
30
|
+
'true'
|
|
31
|
+
);
|
|
32
|
+
expect(screen.getByRole('tab', { name: 'Overview' })).not.toHaveAttribute(
|
|
33
|
+
'aria-controls'
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
fireEvent.click(screen.getByRole('tab', { name: 'Modules' }));
|
|
37
|
+
|
|
38
|
+
expect(onValueChange).toHaveBeenCalledWith('modules');
|
|
39
|
+
expect(screen.getByRole('tab', { name: 'Modules' })).toHaveAttribute(
|
|
40
|
+
'aria-selected',
|
|
41
|
+
'true'
|
|
31
42
|
);
|
|
32
|
-
expect(renderResult.container.firstChild).toMatchSnapshot();
|
|
33
43
|
});
|
|
34
44
|
|
|
35
|
-
test('
|
|
36
|
-
|
|
37
|
-
<
|
|
38
|
-
<Tabs
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
label='Tab Item 4'
|
|
56
|
-
value='4'
|
|
57
|
-
/>
|
|
58
|
-
</Tabs>
|
|
59
|
-
</ThemeContextProvider>
|
|
45
|
+
test('supports panels linked to their tabs', () => {
|
|
46
|
+
wrap(
|
|
47
|
+
<Tabs defaultValue='overview'>
|
|
48
|
+
<Tabs.List>
|
|
49
|
+
<Tabs.Tab value='overview'>Overview</Tabs.Tab>
|
|
50
|
+
<Tabs.Tab value='modules'>Modules</Tabs.Tab>
|
|
51
|
+
</Tabs.List>
|
|
52
|
+
<Tabs.Panel value='overview'>Overview content</Tabs.Panel>
|
|
53
|
+
<Tabs.Panel value='modules'>Modules content</Tabs.Panel>
|
|
54
|
+
</Tabs>
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const overviewTab = screen.getByRole('tab', { name: 'Overview' });
|
|
58
|
+
const overviewPanel = screen.getByRole('tabpanel', {
|
|
59
|
+
name: 'Overview',
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(overviewTab).toHaveAttribute(
|
|
63
|
+
'aria-controls',
|
|
64
|
+
overviewPanel.getAttribute('id')
|
|
60
65
|
);
|
|
61
|
-
expect(
|
|
66
|
+
expect(screen.queryByText('Modules content')).not.toBeInTheDocument();
|
|
62
67
|
});
|
|
63
68
|
|
|
64
|
-
test('
|
|
65
|
-
|
|
66
|
-
<
|
|
67
|
-
<Tabs
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
/>
|
|
83
|
-
<Tabs.Tab
|
|
84
|
-
label='A longer tab item showing auto width'
|
|
85
|
-
value='4'
|
|
86
|
-
/>
|
|
87
|
-
</Tabs>
|
|
88
|
-
</ThemeContextProvider>
|
|
69
|
+
test('supports asChild link tabs', () => {
|
|
70
|
+
wrap(
|
|
71
|
+
<Tabs value='overview'>
|
|
72
|
+
<Tabs.List>
|
|
73
|
+
<Tabs.Tab
|
|
74
|
+
value='overview'
|
|
75
|
+
asChild
|
|
76
|
+
>
|
|
77
|
+
<a href='/overview'>Overview</a>
|
|
78
|
+
</Tabs.Tab>
|
|
79
|
+
<Tabs.Tab
|
|
80
|
+
value='modules'
|
|
81
|
+
asChild
|
|
82
|
+
>
|
|
83
|
+
<a href='/modules'>Modules</a>
|
|
84
|
+
</Tabs.Tab>
|
|
85
|
+
</Tabs.List>
|
|
86
|
+
</Tabs>
|
|
89
87
|
);
|
|
90
|
-
|
|
88
|
+
|
|
89
|
+
const overview = screen.getByRole('tab', { name: 'Overview' });
|
|
90
|
+
|
|
91
|
+
expect(overview.tagName).toBe('A');
|
|
92
|
+
expect(overview).toHaveAttribute('href', '/overview');
|
|
93
|
+
expect(overview).toHaveAttribute('aria-selected', 'true');
|
|
91
94
|
});
|
|
92
95
|
|
|
93
|
-
test('
|
|
94
|
-
|
|
95
|
-
<
|
|
96
|
-
<Tabs
|
|
97
|
-
<Tabs.Tab
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
96
|
+
test('renders counts as plain tab text', () => {
|
|
97
|
+
wrap(
|
|
98
|
+
<Tabs defaultValue='overview'>
|
|
99
|
+
<Tabs.List>
|
|
100
|
+
<Tabs.Tab
|
|
101
|
+
count={12}
|
|
102
|
+
value='overview'
|
|
103
|
+
>
|
|
104
|
+
Overview
|
|
105
|
+
</Tabs.Tab>
|
|
106
|
+
</Tabs.List>
|
|
107
|
+
</Tabs>
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
expect(screen.getByRole('tab', { name: 'Overview 12' })).toHaveTextContent(
|
|
111
|
+
'Overview 12'
|
|
112
|
+
);
|
|
113
|
+
expect(screen.queryByRole('tab', { name: 'Overview (12)' })).toBe(null);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('moves focus with arrow keys and activates automatically', () => {
|
|
117
|
+
wrap(
|
|
118
|
+
<Tabs defaultValue='overview'>
|
|
119
|
+
<Tabs.List>
|
|
120
|
+
<Tabs.Tab value='overview'>Overview</Tabs.Tab>
|
|
121
|
+
<Tabs.Tab value='modules'>Modules</Tabs.Tab>
|
|
122
|
+
<Tabs.Tab value='people'>People</Tabs.Tab>
|
|
123
|
+
</Tabs.List>
|
|
124
|
+
</Tabs>
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const overview = screen.getByRole('tab', { name: 'Overview' });
|
|
128
|
+
const modules = screen.getByRole('tab', { name: 'Modules' });
|
|
129
|
+
|
|
130
|
+
overview.focus();
|
|
131
|
+
fireEvent.keyDown(screen.getByRole('tablist'), { key: 'ArrowRight' });
|
|
132
|
+
|
|
133
|
+
expect(modules).toHaveFocus();
|
|
134
|
+
expect(modules).toHaveAttribute('aria-selected', 'true');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('keeps deprecated activeTab/onChange/label props working', () => {
|
|
138
|
+
const onChange = vi.fn();
|
|
139
|
+
|
|
140
|
+
wrap(
|
|
141
|
+
<Tabs
|
|
142
|
+
activeTab='overview'
|
|
143
|
+
onChange={onChange}
|
|
144
|
+
>
|
|
145
|
+
<Tabs.Tab
|
|
146
|
+
label='Overview'
|
|
147
|
+
value='overview'
|
|
148
|
+
/>
|
|
149
|
+
<Tabs.Tab
|
|
150
|
+
label='Modules'
|
|
151
|
+
value='modules'
|
|
152
|
+
/>
|
|
153
|
+
</Tabs>
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
fireEvent.click(screen.getByRole('tab', { name: 'Modules' }));
|
|
157
|
+
|
|
158
|
+
expect(screen.getByRole('tablist')).toHaveClass('ucl-uikit-tabs__list');
|
|
159
|
+
expect(onChange).toHaveBeenCalledWith('modules');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('does not activate disabled tabs', () => {
|
|
163
|
+
const onValueChange = vi.fn();
|
|
164
|
+
|
|
165
|
+
wrap(
|
|
166
|
+
<Tabs
|
|
167
|
+
defaultValue='overview'
|
|
168
|
+
onValueChange={onValueChange}
|
|
169
|
+
>
|
|
170
|
+
<Tabs.List>
|
|
171
|
+
<Tabs.Tab value='overview'>Overview</Tabs.Tab>
|
|
172
|
+
<Tabs.Tab
|
|
173
|
+
value='modules'
|
|
174
|
+
disabled
|
|
175
|
+
>
|
|
176
|
+
Modules
|
|
177
|
+
</Tabs.Tab>
|
|
178
|
+
</Tabs.List>
|
|
179
|
+
</Tabs>
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
fireEvent.click(screen.getByRole('tab', { name: 'Modules' }));
|
|
183
|
+
|
|
184
|
+
expect(onValueChange).not.toHaveBeenCalled();
|
|
185
|
+
expect(screen.getByRole('tab', { name: 'Overview' })).toHaveAttribute(
|
|
186
|
+
'aria-selected',
|
|
187
|
+
'true'
|
|
119
188
|
);
|
|
120
|
-
expect(renderResult.container.firstChild).toMatchSnapshot();
|
|
121
189
|
});
|
|
122
190
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
-
import { useArgs } from '
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { useArgs } from 'storybook/preview-api';
|
|
3
3
|
import WeekPicker from './WeekPicker';
|
|
4
4
|
|
|
5
5
|
const meta = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Meta, Title, Subtitle, Canvas, Controls, ArgTypes } from '@storybook/blocks';
|
|
1
|
+
import { Meta, Title, Subtitle, Canvas, Controls, ArgTypes } from '@storybook/addon-docs/blocks';
|
|
2
2
|
import marginsStyle from './marginsStyle';
|
|
3
3
|
import { CommonMargins } from './../Button/Button.stories';
|
|
4
4
|
import { theme } from '../../theme';
|
package/lib/components/index.ts
CHANGED
|
@@ -134,7 +134,13 @@ export { default as Alert } from './Alert';
|
|
|
134
134
|
export type { AlertProps } from './Alert';
|
|
135
135
|
|
|
136
136
|
export { default as Tabs } from './Tabs';
|
|
137
|
-
export type {
|
|
137
|
+
export type {
|
|
138
|
+
TabsActivationMode,
|
|
139
|
+
TabsListProps,
|
|
140
|
+
TabsOrientation,
|
|
141
|
+
TabsPanelProps,
|
|
142
|
+
TabsProps,
|
|
143
|
+
} from './Tabs';
|
|
138
144
|
export type { TabProps } from './Tabs/Tab';
|
|
139
145
|
|
|
140
146
|
export { default as Accordion } from './Accordion';
|
package/lib/theme/Icons.mdx
CHANGED
package/lib/theme/Typography.mdx
CHANGED
|
@@ -60,6 +60,59 @@ describe('announce (ARIA live region)', () => {
|
|
|
60
60
|
expect(allRegions.length).toBe(1);
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
+
test('appends the live region to the provided container', async () => {
|
|
64
|
+
const container = document.createElement('section');
|
|
65
|
+
container.setAttribute('data-testid', 'announce-container');
|
|
66
|
+
document.body.appendChild(container);
|
|
67
|
+
|
|
68
|
+
announce('Container message', false, container);
|
|
69
|
+
|
|
70
|
+
const region = await screen.findByTestId('aria-live-region');
|
|
71
|
+
|
|
72
|
+
expect(container.contains(region)).toBe(true);
|
|
73
|
+
expect(document.body.contains(region)).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('falls back to document.body when no container is provided', async () => {
|
|
77
|
+
announce('Default container message');
|
|
78
|
+
|
|
79
|
+
const region = await screen.findByTestId('aria-live-region');
|
|
80
|
+
|
|
81
|
+
expect(region.parentElement).toBe(document.body);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('moves live region when a new container is provided later', async () => {
|
|
85
|
+
const firstContainer = document.createElement('div');
|
|
86
|
+
const secondContainer = document.createElement('div');
|
|
87
|
+
document.body.appendChild(firstContainer);
|
|
88
|
+
document.body.appendChild(secondContainer);
|
|
89
|
+
|
|
90
|
+
announce('First container message', false, firstContainer);
|
|
91
|
+
|
|
92
|
+
const region = await screen.findByTestId('aria-live-region');
|
|
93
|
+
expect(firstContainer.contains(region)).toBe(true);
|
|
94
|
+
|
|
95
|
+
announce('Second container message', false, secondContainer);
|
|
96
|
+
|
|
97
|
+
expect(firstContainer.contains(region)).toBe(false);
|
|
98
|
+
expect(secondContainer.contains(region)).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('recreates live region when previous one was removed from the DOM', async () => {
|
|
102
|
+
announce('Initial message');
|
|
103
|
+
|
|
104
|
+
const initialRegion = await screen.findByTestId('aria-live-region');
|
|
105
|
+
initialRegion.remove();
|
|
106
|
+
|
|
107
|
+
expect(screen.queryByTestId('aria-live-region')).toBeNull();
|
|
108
|
+
|
|
109
|
+
announce('Message after removal');
|
|
110
|
+
|
|
111
|
+
const recreatedRegion = await screen.findByTestId('aria-live-region');
|
|
112
|
+
expect(recreatedRegion).not.toBe(initialRegion);
|
|
113
|
+
expect(recreatedRegion.parentElement).toBe(document.body);
|
|
114
|
+
});
|
|
115
|
+
|
|
63
116
|
test('queues multiple announcements and processes sequentially', async () => {
|
|
64
117
|
vi.useFakeTimers();
|
|
65
118
|
|
package/lib/utils/announce.ts
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @param force - If true, forces the announcement even if it's the same as the last one.
|
|
13
13
|
*
|
|
14
|
+
* @param container - Optional element to contain the live region. Accepts `null`
|
|
15
|
+
* to support passing values like `ref.current`; falls back to `document.body`
|
|
16
|
+
* when no container is available.
|
|
17
|
+
*
|
|
14
18
|
* @example
|
|
15
19
|
* announce("Item removed");
|
|
16
20
|
*
|
|
@@ -26,14 +30,28 @@ const SR_DETECTION_DELAY = 100; // Time for SR to detect change
|
|
|
26
30
|
const SR_PROCESSING_DELAY = 1000; // Time for SR to finish announcement
|
|
27
31
|
|
|
28
32
|
/**
|
|
29
|
-
* Creates the live region if it does not already exist
|
|
33
|
+
* Creates the live region if it does not already exist, or moves it to a new
|
|
34
|
+
* container if one is provided. If the existing region has been removed from
|
|
35
|
+
* the DOM (e.g. a dialog unmounted), it will be recreated.
|
|
30
36
|
*/
|
|
31
|
-
const initLiveRegion = (): void => {
|
|
32
|
-
if (typeof document === 'undefined'
|
|
37
|
+
const initLiveRegion = (container?: HTMLElement | null): void => {
|
|
38
|
+
if (typeof document === 'undefined') return;
|
|
33
39
|
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
// Reset if existing region has been removed from the DOM
|
|
41
|
+
if (liveRegion && !document.contains(liveRegion)) {
|
|
42
|
+
liveRegion = null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const target = container || document.body || document.documentElement;
|
|
46
|
+
if (!target) return;
|
|
47
|
+
|
|
48
|
+
// If a specific container is requested and the region is elsewhere, move it
|
|
49
|
+
if (liveRegion && container && liveRegion.parentNode !== container) {
|
|
50
|
+
container.appendChild(liveRegion);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (liveRegion) return;
|
|
37
55
|
|
|
38
56
|
const region = document.createElement('div');
|
|
39
57
|
region.setAttribute('aria-live', 'polite');
|
|
@@ -48,7 +66,7 @@ const initLiveRegion = (): void => {
|
|
|
48
66
|
region.style.height = '1px';
|
|
49
67
|
region.style.overflow = 'hidden';
|
|
50
68
|
|
|
51
|
-
|
|
69
|
+
target.appendChild(region);
|
|
52
70
|
liveRegion = region;
|
|
53
71
|
};
|
|
54
72
|
|
|
@@ -86,13 +104,18 @@ const processQueue = (): void => {
|
|
|
86
104
|
|
|
87
105
|
/**
|
|
88
106
|
* Announces a message to screen readers.
|
|
107
|
+
*
|
|
89
108
|
*/
|
|
90
|
-
const announce = (
|
|
109
|
+
const announce = (
|
|
110
|
+
message: string,
|
|
111
|
+
force = false,
|
|
112
|
+
container?: HTMLElement | null
|
|
113
|
+
): void => {
|
|
91
114
|
// SSR guard: do nothing if document is unavailable
|
|
92
115
|
if (typeof document === 'undefined') return;
|
|
93
116
|
|
|
94
|
-
//
|
|
95
|
-
|
|
117
|
+
// Ensure live region exists and is attached to the requested container when provided
|
|
118
|
+
initLiveRegion(container);
|
|
96
119
|
|
|
97
120
|
// If forcing, clear the queue to prevent old messages
|
|
98
121
|
if (force) {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "uikit-react-public",
|
|
3
3
|
"private": false,
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.30.1",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
@@ -51,18 +51,15 @@
|
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@azure/msal-browser": "^4.7.0",
|
|
54
|
-
"@chromatic-com/storybook": "^
|
|
54
|
+
"@chromatic-com/storybook": "^5.2.1",
|
|
55
55
|
"@eslint/compat": "^1.2.7",
|
|
56
56
|
"@eslint/eslintrc": "^3.3.0",
|
|
57
57
|
"@eslint/js": "^9.22.0",
|
|
58
|
-
"@storybook/addon-
|
|
59
|
-
"@storybook/addon-
|
|
60
|
-
"@storybook/
|
|
61
|
-
"@storybook/addon-
|
|
62
|
-
"
|
|
63
|
-
"@storybook/react": "^8.6.4",
|
|
64
|
-
"@storybook/react-vite": "^8.6.4",
|
|
65
|
-
"@storybook/test": "^8.6.4",
|
|
58
|
+
"@storybook/addon-links": "10.4.6",
|
|
59
|
+
"@storybook/addon-onboarding": "10.4.6",
|
|
60
|
+
"@storybook/react-vite": "10.4.6",
|
|
61
|
+
"@storybook/addon-docs": "10.4.6",
|
|
62
|
+
"eslint-plugin-storybook": "10.4.6",
|
|
66
63
|
"@testing-library/jest-dom": "^6.6.3",
|
|
67
64
|
"@testing-library/react": "^16.2.0",
|
|
68
65
|
"@testing-library/user-event": "^14.6.1",
|
|
@@ -92,7 +89,7 @@
|
|
|
92
89
|
"react": "^19.1.0",
|
|
93
90
|
"react-dom": "^19.1.0",
|
|
94
91
|
"react-router": "^7.13.1",
|
|
95
|
-
"storybook": "
|
|
92
|
+
"storybook": "10.4.6",
|
|
96
93
|
"typescript": "^5.8.2",
|
|
97
94
|
"typescript-eslint": "^8.26.1",
|
|
98
95
|
"vite": "^6.2.1",
|