uikit-react-public 0.25.0 → 0.25.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/MenuNew/Menu.d.ts +1 -0
- package/dist/components/MenuNew/MenuBaseItem.d.ts +1 -1
- package/dist/components/MenuNew/PrimaryMenuItem.d.ts +2 -2
- package/dist/index.js +461 -437
- package/lib/components/MenuNew/Menu.tsx +12 -3
- package/lib/components/MenuNew/MenuBaseItem.tsx +28 -1
- package/lib/components/MenuNew/PrimaryMenuItem.tsx +3 -3
- package/lib/components/MenuNew/SecondaryMenuItem.tsx +7 -1
- package/lib/components/MenuNew/__tests__/Menu.test.tsx +51 -0
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ export const NAME = 'ucl-uikit-menu';
|
|
|
11
11
|
|
|
12
12
|
export interface MenuProps extends HTMLAttributes<HTMLDivElement> {
|
|
13
13
|
testId?: string;
|
|
14
|
+
border?: boolean;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export type {
|
|
@@ -21,17 +22,25 @@ export type {
|
|
|
21
22
|
ActionMenuItemProps,
|
|
22
23
|
};
|
|
23
24
|
|
|
24
|
-
const Menu = ({
|
|
25
|
+
const Menu = ({
|
|
26
|
+
testId = NAME,
|
|
27
|
+
className,
|
|
28
|
+
children,
|
|
29
|
+
border = false,
|
|
30
|
+
...props
|
|
31
|
+
}: MenuProps) => {
|
|
25
32
|
const [theme] = useTheme();
|
|
26
33
|
const baseStyle = css`
|
|
27
34
|
padding: ${theme.padding.p16} ${theme.padding.p24};
|
|
28
35
|
|
|
29
36
|
@media screen and (min-width: ${theme.breakpoints.tablet}px) {
|
|
30
37
|
padding: ${theme.padding.p48} ${theme.padding.p64};
|
|
31
|
-
border: 1px solid ${theme.colour.border.subtle};
|
|
32
38
|
}
|
|
33
39
|
`;
|
|
34
|
-
const
|
|
40
|
+
const borderStyle = css`
|
|
41
|
+
border: 1px solid ${theme.colour.border.subtle};
|
|
42
|
+
`;
|
|
43
|
+
const style = cx(baseStyle, border && borderStyle, className);
|
|
35
44
|
|
|
36
45
|
return (
|
|
37
46
|
<nav
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HTMLAttributes, ReactNode } from 'react';
|
|
1
|
+
import { HTMLAttributes, KeyboardEvent, ReactNode } from 'react';
|
|
2
2
|
import { css, cx } from '@emotion/css';
|
|
3
3
|
import { useTheme } from '../../theme';
|
|
4
4
|
|
|
@@ -12,9 +12,32 @@ const MenuBaseItem = ({
|
|
|
12
12
|
iconPosition = 'left',
|
|
13
13
|
className,
|
|
14
14
|
children,
|
|
15
|
+
onClick,
|
|
16
|
+
onKeyDown,
|
|
17
|
+
role,
|
|
18
|
+
tabIndex,
|
|
15
19
|
...props
|
|
16
20
|
}: MenuBaseItemProps) => {
|
|
17
21
|
const [theme] = useTheme();
|
|
22
|
+
const isInteractive = typeof onClick === 'function';
|
|
23
|
+
|
|
24
|
+
const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
25
|
+
onKeyDown?.(event);
|
|
26
|
+
|
|
27
|
+
if (event.defaultPrevented || !isInteractive) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (event.key === 'Enter') {
|
|
32
|
+
event.currentTarget.click();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (event.key === ' ') {
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
event.currentTarget.click();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
18
41
|
const baseStyle = css`
|
|
19
42
|
width: 360px;
|
|
20
43
|
box-sizing: border-box;
|
|
@@ -33,6 +56,10 @@ const MenuBaseItem = ({
|
|
|
33
56
|
return (
|
|
34
57
|
<div
|
|
35
58
|
className={style}
|
|
59
|
+
onClick={onClick}
|
|
60
|
+
onKeyDown={handleKeyDown}
|
|
61
|
+
role={role ?? (isInteractive ? 'button' : undefined)}
|
|
62
|
+
tabIndex={tabIndex ?? (isInteractive ? 0 : undefined)}
|
|
36
63
|
{...props}
|
|
37
64
|
>
|
|
38
65
|
{iconPosition === 'left' && icon}
|
|
@@ -12,14 +12,14 @@ export interface PrimaryMenuItemProps extends Omit<
|
|
|
12
12
|
'iconPosition'
|
|
13
13
|
> {
|
|
14
14
|
asChild?: boolean;
|
|
15
|
-
|
|
15
|
+
trailingContent?: ReactNode;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const PrimaryMenuItem = ({
|
|
19
19
|
className,
|
|
20
20
|
children,
|
|
21
21
|
asChild,
|
|
22
|
-
|
|
22
|
+
trailingContent,
|
|
23
23
|
...props
|
|
24
24
|
}: PrimaryMenuItemProps) => {
|
|
25
25
|
const [theme] = useTheme();
|
|
@@ -51,7 +51,7 @@ const PrimaryMenuItem = ({
|
|
|
51
51
|
>
|
|
52
52
|
{children}
|
|
53
53
|
</MenuItemText>
|
|
54
|
-
{
|
|
54
|
+
{trailingContent}
|
|
55
55
|
</span>
|
|
56
56
|
</MenuBaseItem>
|
|
57
57
|
);
|
|
@@ -52,7 +52,13 @@ const SecondaryMenuItem = ({
|
|
|
52
52
|
>
|
|
53
53
|
{children}
|
|
54
54
|
</MenuItemText>
|
|
55
|
-
{externalLink &&
|
|
55
|
+
{externalLink && (
|
|
56
|
+
<Icon.ExternalLink
|
|
57
|
+
size={16}
|
|
58
|
+
aria-hidden='true'
|
|
59
|
+
focusable='false'
|
|
60
|
+
/>
|
|
61
|
+
)}
|
|
56
62
|
</span>
|
|
57
63
|
</MenuBaseItem>
|
|
58
64
|
);
|
|
@@ -13,6 +13,22 @@ describe('Menu', () => {
|
|
|
13
13
|
expect(screen.getByTestId('ucl-uikit-menu')).toBeInTheDocument();
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
+
test('has no border by default', () => {
|
|
17
|
+
wrap(<Menu>Content</Menu>);
|
|
18
|
+
|
|
19
|
+
const menu = screen.getByTestId('ucl-uikit-menu');
|
|
20
|
+
const computedStyles = window.getComputedStyle(menu);
|
|
21
|
+
expect(computedStyles.borderTopStyle).toBe('');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('allows border to be enabled with prop override', () => {
|
|
25
|
+
wrap(<Menu border>Content</Menu>);
|
|
26
|
+
|
|
27
|
+
const menu = screen.getByTestId('ucl-uikit-menu');
|
|
28
|
+
const computedStyles = window.getComputedStyle(menu);
|
|
29
|
+
expect(computedStyles.borderTopStyle).toBe('solid');
|
|
30
|
+
});
|
|
31
|
+
|
|
16
32
|
test('supports click-handler items without asChild link component', () => {
|
|
17
33
|
const onClick = vi.fn();
|
|
18
34
|
|
|
@@ -26,6 +42,27 @@ describe('Menu', () => {
|
|
|
26
42
|
expect(onClick).toHaveBeenCalledTimes(1);
|
|
27
43
|
});
|
|
28
44
|
|
|
45
|
+
test('click-handler items are keyboard accessible', () => {
|
|
46
|
+
const onClick = vi.fn();
|
|
47
|
+
|
|
48
|
+
wrap(
|
|
49
|
+
<Menu>
|
|
50
|
+
<Menu.PrimaryItem onClick={onClick}>Home</Menu.PrimaryItem>
|
|
51
|
+
</Menu>
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const item = screen
|
|
55
|
+
.getByText('Home')
|
|
56
|
+
.closest('.ucl-uikit-menu__primary-item');
|
|
57
|
+
|
|
58
|
+
expect(item).toHaveAttribute('role', 'button');
|
|
59
|
+
expect(item).toHaveAttribute('tabindex', '0');
|
|
60
|
+
|
|
61
|
+
fireEvent.keyDown(item as Element, { key: 'Enter' });
|
|
62
|
+
fireEvent.keyDown(item as Element, { key: ' ' });
|
|
63
|
+
expect(onClick).toHaveBeenCalledTimes(2);
|
|
64
|
+
});
|
|
65
|
+
|
|
29
66
|
test('renders asChild link content for primary item', () => {
|
|
30
67
|
wrap(
|
|
31
68
|
<Menu>
|
|
@@ -84,4 +121,18 @@ describe('Menu', () => {
|
|
|
84
121
|
container.querySelector('.ucl-uikit-menu__secondary-item svg')
|
|
85
122
|
).toBeTruthy();
|
|
86
123
|
});
|
|
124
|
+
|
|
125
|
+
test('secondary externalLink icon is hidden from assistive tech', () => {
|
|
126
|
+
const { container } = wrap(
|
|
127
|
+
<Menu>
|
|
128
|
+
<Menu.SecondaryItem externalLink>
|
|
129
|
+
Accessibility statement
|
|
130
|
+
</Menu.SecondaryItem>
|
|
131
|
+
</Menu>
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const icon = container.querySelector('.ucl-uikit-menu__secondary-item svg');
|
|
135
|
+
expect(icon).toHaveAttribute('aria-hidden', 'true');
|
|
136
|
+
expect(icon).toHaveAttribute('focusable', 'false');
|
|
137
|
+
});
|
|
87
138
|
});
|