uikit-react-public 0.30.0 → 0.32.3
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/Dialog/BaseDialog.d.ts +1 -1
- package/dist/components/Dialog/Dialog.d.ts +1 -1
- package/dist/components/Dialog/Dialog.stories.d.ts +1 -1
- package/dist/components/Heading/Heading.stories.d.ts +5 -7
- package/dist/components/MenuNew/Menu.context.d.ts +9 -0
- package/dist/components/MenuNew/Menu.d.ts +2 -1
- package/dist/components/MenuNew/MenuHeading.d.ts +1 -1
- package/dist/components/MenuNew/MenuItemText.d.ts +2 -1
- package/dist/components/MenuNew/PrimaryMenuItem.d.ts +1 -1
- package/dist/components/Select/Select.types.d.ts +1 -1
- package/dist/components/Sidebar/Sidebar.d.ts +12 -0
- package/dist/components/Sidebar/Sidebar.stories.d.ts +38 -0
- package/dist/components/Sidebar/__tests__/Sidebar.test.d.ts +1 -0
- package/dist/components/Sidebar/index.d.ts +2 -0
- package/dist/components/Snackbar/Snackbar.d.ts +9 -5
- package/dist/components/Snackbar/Snackbar.stories.d.ts +2 -1
- package/dist/components/Snackbar/Snackbars.d.ts +19 -0
- package/dist/components/Snackbar/Snackbars.stories.d.ts +52 -0
- package/dist/components/Snackbar/__tests__/Snackbars.test.d.ts +1 -0
- package/dist/components/Snackbar/index.d.ts +2 -0
- package/dist/components/index.d.ts +4 -1
- package/dist/index.js +5897 -5358
- package/dist/utils/announce.d.ts +2 -1
- package/lib/components/Dialog/BaseDialog.tsx +180 -161
- package/lib/components/Dialog/Dialog.tsx +15 -11
- package/lib/components/Dropdown/Dropdown.stories.tsx +69 -74
- package/lib/components/Heading/Documentation.mdx +4 -14
- package/lib/components/Heading/Heading.stories.tsx +16 -30
- package/lib/components/MenuNew/Menu.context.tsx +18 -0
- package/lib/components/MenuNew/Menu.tsx +14 -9
- package/lib/components/MenuNew/MenuDivider.tsx +12 -1
- package/lib/components/MenuNew/MenuHeading.tsx +6 -0
- package/lib/components/MenuNew/MenuItemText.tsx +27 -3
- package/lib/components/MenuNew/MenuSection.tsx +11 -0
- package/lib/components/MenuNew/PrimaryMenuItem.tsx +71 -2
- package/lib/components/Select/Select.types.ts +1 -7
- package/lib/components/Select/__tests__/Select.test.tsx +112 -0
- package/lib/components/Select/__tests__/__snapshots__/Select.test.tsx.snap +5 -0
- package/lib/components/Select/subcomponents/CustomSelect.tsx +75 -3
- package/lib/components/Select/subcomponents/FilterInput.tsx +1 -1
- package/lib/components/Sidebar/Sidebar.stories.tsx +94 -0
- package/lib/components/Sidebar/Sidebar.tsx +208 -0
- package/lib/components/Sidebar/__tests__/Sidebar.test.tsx +96 -0
- package/lib/components/Sidebar/__tests__/__snapshots__/Sidebar.test.tsx.snap +272 -0
- package/lib/components/Sidebar/index.ts +2 -0
- package/lib/components/Snackbar/Snackbar.stories.tsx +11 -0
- package/lib/components/Snackbar/Snackbar.tsx +162 -118
- package/lib/components/Snackbar/Snackbars.stories.tsx +130 -0
- package/lib/components/Snackbar/Snackbars.tsx +377 -0
- package/lib/components/Snackbar/__tests__/Snackbar.test.tsx +104 -0
- package/lib/components/Snackbar/__tests__/Snackbars.test.tsx +377 -0
- package/lib/components/Snackbar/__tests__/__snapshots__/Snackbar.test.tsx.snap +36 -19
- package/lib/components/Snackbar/index.ts +6 -0
- package/lib/components/index.ts +10 -1
- package/lib/utils/__tests__/announce.test.ts +53 -0
- package/lib/utils/announce.ts +33 -10
- package/package.json +1 -1
|
@@ -21,45 +21,31 @@ export const Default: Story = {
|
|
|
21
21
|
},
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
export const
|
|
24
|
+
export const Level1: Story = {
|
|
25
25
|
args: {
|
|
26
|
-
level:
|
|
27
|
-
children: '
|
|
26
|
+
level: 1,
|
|
27
|
+
children: 'Level 1 Heading',
|
|
28
28
|
},
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
export const
|
|
31
|
+
export const Level2: Story = {
|
|
32
32
|
args: {
|
|
33
|
-
level:
|
|
34
|
-
children: '
|
|
33
|
+
level: 2,
|
|
34
|
+
children: 'Level 2 Heading',
|
|
35
35
|
},
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
export const
|
|
38
|
+
export const Level3: Story = {
|
|
39
39
|
args: {
|
|
40
|
-
level:
|
|
41
|
-
children: '
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export const LevelMd: Story = {
|
|
46
|
-
args: {
|
|
47
|
-
level: 'md',
|
|
48
|
-
children: 'Heading level MD',
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export const LevelSm: Story = {
|
|
53
|
-
args: {
|
|
54
|
-
level: 'sm',
|
|
55
|
-
children: 'Heading level SM',
|
|
40
|
+
level: 3,
|
|
41
|
+
children: 'Level 3 Heading',
|
|
56
42
|
},
|
|
57
43
|
};
|
|
58
44
|
|
|
59
|
-
export const
|
|
45
|
+
export const Level4: Story = {
|
|
60
46
|
args: {
|
|
61
|
-
level:
|
|
62
|
-
children: '
|
|
47
|
+
level: 4,
|
|
48
|
+
children: 'Level 4 Heading',
|
|
63
49
|
},
|
|
64
50
|
};
|
|
65
51
|
|
|
@@ -71,12 +57,12 @@ export const AsSpan: Story = {
|
|
|
71
57
|
},
|
|
72
58
|
};
|
|
73
59
|
|
|
74
|
-
export const
|
|
75
|
-
name: '
|
|
60
|
+
export const withMargins: Story = {
|
|
61
|
+
name: 'With margins',
|
|
76
62
|
args: {
|
|
77
|
-
|
|
63
|
+
mb: '24',
|
|
78
64
|
level: 'md',
|
|
79
|
-
children: 'Heading
|
|
65
|
+
children: 'Heading with bottom margin',
|
|
80
66
|
},
|
|
81
67
|
decorators: [
|
|
82
68
|
(Story) => (
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ReactNode, createContext, use } from 'react';
|
|
2
|
+
|
|
3
|
+
interface MenuContextValue {
|
|
4
|
+
collapsed: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const MenuContext = createContext<MenuContextValue>({
|
|
8
|
+
collapsed: false,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const MenuProvider = ({
|
|
12
|
+
collapsed,
|
|
13
|
+
children,
|
|
14
|
+
}: MenuContextValue & { children: ReactNode }) => (
|
|
15
|
+
<MenuContext value={{ collapsed }}>{children}</MenuContext>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export const useMenu = () => use(MenuContext);
|
|
@@ -13,12 +13,14 @@ import ActionMenuButton, { ActionMenuButtonProps } from './ActionMenuButton';
|
|
|
13
13
|
import PrimaryMenuItem, { PrimaryMenuItemProps } from './PrimaryMenuItem';
|
|
14
14
|
import SecondaryMenuItem, { SecondaryMenuItemProps } from './SecondaryMenuItem';
|
|
15
15
|
import { useTheme } from '../../theme';
|
|
16
|
+
import { MenuProvider } from './Menu.context';
|
|
16
17
|
|
|
17
18
|
export const NAME = 'ucl-uikit-menu';
|
|
18
19
|
|
|
19
|
-
export interface MenuProps extends HTMLAttributes<
|
|
20
|
+
export interface MenuProps extends HTMLAttributes<HTMLElement> {
|
|
20
21
|
testId?: string;
|
|
21
22
|
border?: boolean;
|
|
23
|
+
collapsed?: boolean;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export type {
|
|
@@ -39,6 +41,7 @@ const Menu = ({
|
|
|
39
41
|
className,
|
|
40
42
|
children,
|
|
41
43
|
border = false,
|
|
44
|
+
collapsed = false,
|
|
42
45
|
...props
|
|
43
46
|
}: MenuProps) => {
|
|
44
47
|
const [theme] = useTheme();
|
|
@@ -58,14 +61,16 @@ const Menu = ({
|
|
|
58
61
|
const style = cx(baseStyle, border && borderStyle, className);
|
|
59
62
|
|
|
60
63
|
return (
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
<MenuProvider collapsed={collapsed}>
|
|
65
|
+
<nav
|
|
66
|
+
className={style}
|
|
67
|
+
data-testid={testId}
|
|
68
|
+
role='menu'
|
|
69
|
+
{...props}
|
|
70
|
+
>
|
|
71
|
+
{children}
|
|
72
|
+
</nav>
|
|
73
|
+
</MenuProvider>
|
|
69
74
|
);
|
|
70
75
|
};
|
|
71
76
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { HTMLAttributes } from 'react';
|
|
2
2
|
import { css, cx } from '@emotion/css';
|
|
3
3
|
import { useTheme } from '../../theme';
|
|
4
|
+
import { useMenu } from './Menu.context';
|
|
4
5
|
|
|
5
6
|
export const NAME = 'ucl-uikit-menu__divider';
|
|
6
7
|
|
|
@@ -8,13 +9,23 @@ export interface MenuDividerProps extends HTMLAttributes<HTMLHRElement> {}
|
|
|
8
9
|
|
|
9
10
|
const MenuDivider = ({ className, ...props }: MenuDividerProps) => {
|
|
10
11
|
const [theme] = useTheme();
|
|
12
|
+
const { collapsed } = useMenu();
|
|
11
13
|
const baseStyle = css`
|
|
14
|
+
flex: 0 0 1px;
|
|
15
|
+
min-height: 1px;
|
|
16
|
+
box-sizing: border-box;
|
|
17
|
+
width: 100%;
|
|
18
|
+
align-self: stretch;
|
|
12
19
|
border: 0;
|
|
13
20
|
height: 1px;
|
|
14
21
|
background-color: ${theme.colour.border.subtle};
|
|
15
22
|
margin: ${theme.margin.m16} 0;
|
|
16
23
|
`;
|
|
17
|
-
const
|
|
24
|
+
const collapsedStyle = css`
|
|
25
|
+
width: 32px;
|
|
26
|
+
align-self: center;
|
|
27
|
+
`;
|
|
28
|
+
const style = cx(NAME, baseStyle, collapsed && collapsedStyle, className);
|
|
18
29
|
|
|
19
30
|
return (
|
|
20
31
|
<hr
|
|
@@ -2,6 +2,7 @@ import { HTMLAttributes } from 'react';
|
|
|
2
2
|
import { css, cx } from '@emotion/css';
|
|
3
3
|
import Heading from '../Heading';
|
|
4
4
|
import { useTheme } from '../../theme';
|
|
5
|
+
import { useMenu } from './Menu.context';
|
|
5
6
|
|
|
6
7
|
export const NAME = 'ucl-uikit-menu__heading';
|
|
7
8
|
|
|
@@ -16,6 +17,7 @@ const MenuHeading = ({
|
|
|
16
17
|
...props
|
|
17
18
|
}: MenuHeadingProps) => {
|
|
18
19
|
const [theme] = useTheme();
|
|
20
|
+
const { collapsed } = useMenu();
|
|
19
21
|
|
|
20
22
|
const baseStyle = css`
|
|
21
23
|
box-sizing: border-box;
|
|
@@ -30,6 +32,10 @@ const MenuHeading = ({
|
|
|
30
32
|
|
|
31
33
|
const style = cx(NAME, baseStyle, className);
|
|
32
34
|
|
|
35
|
+
if (collapsed) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
const headingStyle = css`
|
|
34
40
|
color: ${theme.colour.text.secondary};
|
|
35
41
|
text-transform: uppercase;
|
|
@@ -13,12 +13,14 @@ export const NAME = 'ucl-uikit-menu__item-text';
|
|
|
13
13
|
export interface MenuItemTextProps {
|
|
14
14
|
asChild?: boolean;
|
|
15
15
|
className?: string;
|
|
16
|
+
visuallyHidden?: boolean;
|
|
16
17
|
children?: ReactNode;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
const MenuItemText = ({
|
|
20
21
|
asChild = false,
|
|
21
22
|
className,
|
|
23
|
+
visuallyHidden = false,
|
|
22
24
|
children,
|
|
23
25
|
}: MenuItemTextProps) => {
|
|
24
26
|
const asChildResetStyle = css`
|
|
@@ -46,18 +48,40 @@ const MenuItemText = ({
|
|
|
46
48
|
box-shadow: none;
|
|
47
49
|
}
|
|
48
50
|
`;
|
|
49
|
-
const
|
|
51
|
+
const visuallyHiddenStyle = css`
|
|
52
|
+
position: absolute;
|
|
53
|
+
width: 1px;
|
|
54
|
+
height: 1px;
|
|
55
|
+
padding: 0;
|
|
56
|
+
margin: -1px;
|
|
57
|
+
overflow: hidden;
|
|
58
|
+
clip: rect(0 0 0 0);
|
|
59
|
+
white-space: nowrap;
|
|
60
|
+
border: 0;
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
const style = cx(NAME, visuallyHidden && visuallyHiddenStyle, className);
|
|
50
64
|
|
|
51
65
|
if (asChild) {
|
|
66
|
+
const visuallyHiddenAsChildStyle = css`
|
|
67
|
+
color: transparent;
|
|
68
|
+
font-size: 0;
|
|
69
|
+
line-height: 0;
|
|
70
|
+
`;
|
|
71
|
+
|
|
52
72
|
const child = Children.only(children) as ReactElement<{
|
|
53
73
|
className?: string;
|
|
54
74
|
}>;
|
|
55
75
|
|
|
56
76
|
if (isValidElement(child)) {
|
|
57
77
|
return (
|
|
58
|
-
<Text className={
|
|
78
|
+
<Text className={cx(NAME, className)}>
|
|
59
79
|
{cloneElement(child, {
|
|
60
|
-
className: cx(
|
|
80
|
+
className: cx(
|
|
81
|
+
asChildResetStyle,
|
|
82
|
+
visuallyHidden && visuallyHiddenAsChildStyle,
|
|
83
|
+
child.props.className
|
|
84
|
+
),
|
|
61
85
|
})}
|
|
62
86
|
</Text>
|
|
63
87
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HTMLAttributes } from 'react';
|
|
2
2
|
import { css, cx } from '@emotion/css';
|
|
3
|
+
import { useMenu } from './Menu.context';
|
|
3
4
|
|
|
4
5
|
export const NAME = 'ucl-uikit-menu__section';
|
|
5
6
|
|
|
@@ -13,12 +14,22 @@ const MenuSection = ({
|
|
|
13
14
|
children,
|
|
14
15
|
...props
|
|
15
16
|
}: MenuSectionProps) => {
|
|
17
|
+
const { collapsed } = useMenu();
|
|
18
|
+
|
|
16
19
|
const style = cx(
|
|
17
20
|
NAME,
|
|
18
21
|
css`
|
|
22
|
+
width: 100%;
|
|
23
|
+
box-sizing: border-box;
|
|
19
24
|
margin: 0;
|
|
20
25
|
padding: 0;
|
|
21
26
|
`,
|
|
27
|
+
collapsed &&
|
|
28
|
+
css`
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
align-items: center;
|
|
32
|
+
`,
|
|
22
33
|
className
|
|
23
34
|
);
|
|
24
35
|
|
|
@@ -4,6 +4,7 @@ import { MenuBaseItemProps } from './MenuBaseItem';
|
|
|
4
4
|
import MenuBaseItem from './MenuBaseItem';
|
|
5
5
|
import MenuItemText from './MenuItemText';
|
|
6
6
|
import { useTheme } from '../../theme';
|
|
7
|
+
import { useMenu } from './Menu.context';
|
|
7
8
|
|
|
8
9
|
export const NAME = 'ucl-uikit-menu__primary-item';
|
|
9
10
|
|
|
@@ -20,20 +21,84 @@ const PrimaryMenuItem = ({
|
|
|
20
21
|
children,
|
|
21
22
|
asChild,
|
|
22
23
|
trailingContent,
|
|
24
|
+
icon: iconProp,
|
|
25
|
+
active = false,
|
|
23
26
|
...props
|
|
24
27
|
}: PrimaryMenuItemProps) => {
|
|
25
28
|
const [theme] = useTheme();
|
|
29
|
+
const { collapsed } = useMenu();
|
|
26
30
|
|
|
27
31
|
const baseStyle = css`
|
|
28
32
|
color: ${theme.colour.text.default};
|
|
29
33
|
padding: 0 ${theme.padding.p16};
|
|
34
|
+
white-space: nowrap;
|
|
35
|
+
border-radius: 4px;
|
|
36
|
+
|
|
37
|
+
> svg {
|
|
38
|
+
width: 20px;
|
|
39
|
+
height: 20px;
|
|
40
|
+
flex: 0 0 20px;
|
|
41
|
+
}
|
|
30
42
|
|
|
31
43
|
@media screen and (min-width: ${theme.breakpoints.tablet}px) {
|
|
32
44
|
padding: 0 ${theme.padding.p16};
|
|
33
45
|
}
|
|
46
|
+
|
|
47
|
+
&:focus-visible,
|
|
48
|
+
&:focus-within {
|
|
49
|
+
border-radius: 4px;
|
|
50
|
+
box-shadow: none !important;
|
|
51
|
+
z-index: 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
&:focus-visible::after,
|
|
55
|
+
&:focus-within::after {
|
|
56
|
+
content: '';
|
|
57
|
+
position: absolute;
|
|
58
|
+
inset: 0;
|
|
59
|
+
box-sizing: border-box;
|
|
60
|
+
border-radius: 4px;
|
|
61
|
+
border: 2px solid ${theme.colour.border.brand};
|
|
62
|
+
pointer-events: none;
|
|
63
|
+
}
|
|
34
64
|
`;
|
|
35
65
|
|
|
36
|
-
const
|
|
66
|
+
const collapsedStyle = css`
|
|
67
|
+
width: 32px;
|
|
68
|
+
min-height: 32px;
|
|
69
|
+
height: 32px;
|
|
70
|
+
padding: 0;
|
|
71
|
+
margin-bottom: ${theme.margin.m8};
|
|
72
|
+
justify-content: center;
|
|
73
|
+
gap: 0;
|
|
74
|
+
|
|
75
|
+
&:focus-visible,
|
|
76
|
+
&:focus-within {
|
|
77
|
+
box-shadow: ${theme.boxShadow.focus} !important;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
&:focus-visible::after,
|
|
81
|
+
&:focus-within::after {
|
|
82
|
+
content: none;
|
|
83
|
+
}
|
|
84
|
+
`;
|
|
85
|
+
|
|
86
|
+
const collapsedActiveStyle = css`
|
|
87
|
+
background-color: ${theme.colour.fill.brand};
|
|
88
|
+
color: ${theme.colour.fill.inverse};
|
|
89
|
+
|
|
90
|
+
&:hover {
|
|
91
|
+
background-color: ${theme.colour.fill.brandHover};
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
94
|
+
|
|
95
|
+
const style = cx(
|
|
96
|
+
NAME,
|
|
97
|
+
baseStyle,
|
|
98
|
+
collapsed && collapsedStyle,
|
|
99
|
+
collapsed && active && collapsedActiveStyle,
|
|
100
|
+
className
|
|
101
|
+
);
|
|
37
102
|
|
|
38
103
|
const textStyle = css``;
|
|
39
104
|
|
|
@@ -48,16 +113,20 @@ const PrimaryMenuItem = ({
|
|
|
48
113
|
className={style}
|
|
49
114
|
asChild={asChild}
|
|
50
115
|
iconPosition='left'
|
|
116
|
+
icon={iconProp}
|
|
117
|
+
active={!collapsed && active}
|
|
118
|
+
aria-current={active ? 'page' : props['aria-current']}
|
|
51
119
|
{...props}
|
|
52
120
|
>
|
|
53
121
|
<span className={contentStyle}>
|
|
54
122
|
<MenuItemText
|
|
55
123
|
asChild={asChild}
|
|
56
124
|
className={textStyle}
|
|
125
|
+
visuallyHidden={collapsed}
|
|
57
126
|
>
|
|
58
127
|
{children}
|
|
59
128
|
</MenuItemText>
|
|
60
|
-
{trailingContent}
|
|
129
|
+
{!collapsed && trailingContent}
|
|
61
130
|
</span>
|
|
62
131
|
</MenuBaseItem>
|
|
63
132
|
);
|
|
@@ -28,13 +28,7 @@ export type OptionData<T> = {
|
|
|
28
28
|
*/
|
|
29
29
|
export type FilterInputProps = Omit<
|
|
30
30
|
React.InputHTMLAttributes<HTMLInputElement>,
|
|
31
|
-
| '
|
|
32
|
-
| 'onChange'
|
|
33
|
-
| 'disabled'
|
|
34
|
-
| 'ref'
|
|
35
|
-
| 'role'
|
|
36
|
-
| 'aria-autocomplete'
|
|
37
|
-
| 'aria-label'
|
|
31
|
+
'value' | 'onChange' | 'disabled' | 'ref' | 'role' | 'aria-autocomplete'
|
|
38
32
|
>;
|
|
39
33
|
|
|
40
34
|
export type SelectDropdownWidth = 'content' | 'match-select';
|
|
@@ -518,6 +518,76 @@ describe('Select', () => {
|
|
|
518
518
|
);
|
|
519
519
|
});
|
|
520
520
|
|
|
521
|
+
test('selectionBehaviour=commit returns focus to the combobox on Enter (filterable)', async () => {
|
|
522
|
+
const user = userEvent.setup();
|
|
523
|
+
|
|
524
|
+
const ControlledSelect = () => {
|
|
525
|
+
const [value, setValue] = useState('');
|
|
526
|
+
return (
|
|
527
|
+
<Select
|
|
528
|
+
filterable
|
|
529
|
+
options={defaultOptions}
|
|
530
|
+
value={value}
|
|
531
|
+
selectionBehaviour='commit'
|
|
532
|
+
onValueChange={(next) => setValue(next as string)}
|
|
533
|
+
/>
|
|
534
|
+
);
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
const result = render(
|
|
538
|
+
<ThemeContextProvider>
|
|
539
|
+
<ControlledSelect />
|
|
540
|
+
</ThemeContextProvider>
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
await user.click(result.getByTestId('ucl-uikit-select'));
|
|
544
|
+
// Filter input has focus while the panel is open
|
|
545
|
+
expect(result.getByRole('searchbox')).toHaveFocus();
|
|
546
|
+
|
|
547
|
+
await user.keyboard('{ArrowDown}');
|
|
548
|
+
await user.keyboard('{Enter}');
|
|
549
|
+
|
|
550
|
+
// Panel (and its filter input) has closed, and focus is back on the
|
|
551
|
+
// combobox rather than lost to the document body.
|
|
552
|
+
expect(
|
|
553
|
+
result.queryByTestId('ucl-uikit-select__panel')
|
|
554
|
+
).not.toBeInTheDocument();
|
|
555
|
+
expect(result.getByRole('combobox')).toHaveFocus();
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
test('selectionBehaviour=commit announces the committed option via a live region', async () => {
|
|
559
|
+
const user = userEvent.setup();
|
|
560
|
+
|
|
561
|
+
const ControlledSelect = () => {
|
|
562
|
+
const [value, setValue] = useState('');
|
|
563
|
+
return (
|
|
564
|
+
<Select
|
|
565
|
+
filterable
|
|
566
|
+
options={defaultOptions}
|
|
567
|
+
value={value}
|
|
568
|
+
selectionBehaviour='commit'
|
|
569
|
+
onValueChange={(next) => setValue(next as string)}
|
|
570
|
+
/>
|
|
571
|
+
);
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
const result = render(
|
|
575
|
+
<ThemeContextProvider>
|
|
576
|
+
<ControlledSelect />
|
|
577
|
+
</ThemeContextProvider>
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
const status = result.getByRole('status');
|
|
581
|
+
expect(status).toHaveTextContent('');
|
|
582
|
+
|
|
583
|
+
await user.click(result.getByTestId('ucl-uikit-select'));
|
|
584
|
+
await user.keyboard('{ArrowDown}');
|
|
585
|
+
await user.keyboard('{ArrowDown}');
|
|
586
|
+
await user.keyboard('{Enter}');
|
|
587
|
+
|
|
588
|
+
expect(status).toHaveTextContent('Option 2 selected');
|
|
589
|
+
});
|
|
590
|
+
|
|
521
591
|
test('keyboard navigation does not get stuck on duplicate values', async () => {
|
|
522
592
|
const user = userEvent.setup();
|
|
523
593
|
const options = [
|
|
@@ -969,6 +1039,48 @@ describe('Select', () => {
|
|
|
969
1039
|
).not.toBeInTheDocument();
|
|
970
1040
|
});
|
|
971
1041
|
|
|
1042
|
+
test('aria-activedescendant is absent immediately after opening with a preselected value', async () => {
|
|
1043
|
+
const user = userEvent.setup();
|
|
1044
|
+
const result = render(
|
|
1045
|
+
<ThemeContextProvider>
|
|
1046
|
+
<Select
|
|
1047
|
+
options={defaultOptions}
|
|
1048
|
+
value='2'
|
|
1049
|
+
onValueChange={() => {}}
|
|
1050
|
+
/>
|
|
1051
|
+
</ThemeContextProvider>
|
|
1052
|
+
);
|
|
1053
|
+
|
|
1054
|
+
const combobox = result.getByRole('combobox');
|
|
1055
|
+
await user.click(combobox);
|
|
1056
|
+
|
|
1057
|
+
expect(combobox).not.toHaveAttribute('aria-activedescendant');
|
|
1058
|
+
|
|
1059
|
+
await user.keyboard('{ArrowDown}');
|
|
1060
|
+
expect(combobox).toHaveAttribute('aria-activedescendant');
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
test('filterable: aria-activedescendant is absent immediately after opening with a preselected value', async () => {
|
|
1064
|
+
const user = userEvent.setup();
|
|
1065
|
+
const result = render(
|
|
1066
|
+
<ThemeContextProvider>
|
|
1067
|
+
<Select
|
|
1068
|
+
filterable
|
|
1069
|
+
options={defaultOptions}
|
|
1070
|
+
value='2'
|
|
1071
|
+
onValueChange={() => {}}
|
|
1072
|
+
/>
|
|
1073
|
+
</ThemeContextProvider>
|
|
1074
|
+
);
|
|
1075
|
+
|
|
1076
|
+
await user.click(result.getByRole('combobox'));
|
|
1077
|
+
const filterInput = result.getByRole('searchbox');
|
|
1078
|
+
expect(filterInput).not.toHaveAttribute('aria-activedescendant');
|
|
1079
|
+
|
|
1080
|
+
await user.keyboard('{ArrowDown}');
|
|
1081
|
+
expect(filterInput).toHaveAttribute('aria-activedescendant');
|
|
1082
|
+
});
|
|
1083
|
+
|
|
972
1084
|
test('disabled clearable select does not show clear button', () => {
|
|
973
1085
|
const result = render(
|
|
974
1086
|
<ThemeContextProvider>
|