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.
@@ -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 = ({ testId = NAME, className, children, ...props }: MenuProps) => {
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 style = cx(baseStyle, className);
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
- badge?: ReactNode;
15
+ trailingContent?: ReactNode;
16
16
  }
17
17
 
18
18
  const PrimaryMenuItem = ({
19
19
  className,
20
20
  children,
21
21
  asChild,
22
- badge,
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
- {badge}
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 && <Icon.ExternalLink size={16} />}
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
  });
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.25.0",
5
+ "version": "0.25.1",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",