uikit-react-public 0.24.5 → 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/ActionMenuItem.d.ts +7 -0
- package/dist/components/MenuNew/Menu.d.ts +16 -15
- package/dist/components/MenuNew/MenuBaseItem.d.ts +7 -0
- package/dist/components/MenuNew/MenuDivider.d.ts +6 -0
- package/dist/components/MenuNew/MenuItemText.d.ts +9 -0
- package/dist/components/MenuNew/PrimaryMenuItem.d.ts +9 -0
- package/dist/components/MenuNew/SecondaryMenuItem.d.ts +8 -0
- package/dist/components/MenuNew/index.d.ts +1 -5
- package/dist/components/Text/Text.d.ts +10 -0
- package/dist/components/Text/index.d.ts +2 -0
- package/dist/components/index.d.ts +2 -4
- package/dist/index.js +5575 -5597
- package/lib/components/MenuNew/ActionMenuItem.tsx +31 -0
- package/lib/components/MenuNew/Menu.tsx +41 -44
- package/lib/components/MenuNew/MenuBaseItem.tsx +72 -0
- package/lib/components/MenuNew/MenuDivider.tsx +25 -0
- package/lib/components/MenuNew/MenuItemText.tsx +58 -0
- package/lib/components/MenuNew/PrimaryMenuItem.tsx +60 -0
- package/lib/components/MenuNew/SecondaryMenuItem.tsx +67 -0
- package/lib/components/MenuNew/__tests__/Menu.test.tsx +138 -0
- package/lib/components/MenuNew/index.ts +8 -7
- package/lib/components/Text/Text.tsx +171 -0
- package/lib/components/Text/index.ts +2 -0
- package/lib/components/index.ts +2 -7
- package/package.json +1 -1
- package/dist/components/MenuNew/Menu.context.d.ts +0 -14
- package/dist/components/MenuNew/MenuContent.d.ts +0 -9
- package/dist/components/MenuNew/MenuItem.d.ts +0 -10
- package/dist/components/MenuNew/MenuSection.d.ts +0 -7
- package/dist/components/MenuNew/trigger/ButtonMenuTrigger.d.ts +0 -8
- package/dist/components/MenuNew/trigger/IconMenuTrigger.d.ts +0 -8
- package/dist/components/SimpleMenu/SimpleMenu.d.ts +0 -7
- package/dist/components/SimpleMenu/index.d.ts +0 -2
- package/lib/components/MenuNew/Menu.context.tsx +0 -149
- package/lib/components/MenuNew/MenuContent.tsx +0 -140
- package/lib/components/MenuNew/MenuItem.tsx +0 -101
- package/lib/components/MenuNew/MenuSection.tsx +0 -47
- package/lib/components/MenuNew/trigger/ButtonMenuTrigger.tsx +0 -42
- package/lib/components/MenuNew/trigger/IconMenuTrigger.tsx +0 -40
- package/lib/components/SimpleMenu/SimpleMenu.tsx +0 -40
- package/lib/components/SimpleMenu/__tests__/SimpleMenu.test.tsx +0 -38
- package/lib/components/SimpleMenu/index.ts +0 -2
- /package/dist/components/{SimpleMenu/__tests__/SimpleMenu.test.d.ts → MenuNew/__tests__/Menu.test.d.ts} +0 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cx } from '@emotion/css';
|
|
2
|
+
import { MenuBaseItemProps } from './MenuBaseItem';
|
|
3
|
+
import MenuBaseItem from './MenuBaseItem';
|
|
4
|
+
import MenuItemText from './MenuItemText';
|
|
5
|
+
|
|
6
|
+
export const NAME = 'ucl-uikit-menu__action-item';
|
|
7
|
+
|
|
8
|
+
export interface ActionMenuItemProps extends MenuBaseItemProps {
|
|
9
|
+
asChild?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const ActionMenuItem = ({
|
|
13
|
+
className,
|
|
14
|
+
children,
|
|
15
|
+
asChild,
|
|
16
|
+
...props
|
|
17
|
+
}: ActionMenuItemProps) => {
|
|
18
|
+
const style = cx(NAME, className);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<MenuBaseItem
|
|
22
|
+
className={style}
|
|
23
|
+
{...props}
|
|
24
|
+
iconPosition='right'
|
|
25
|
+
>
|
|
26
|
+
<MenuItemText asChild={asChild}>{children}</MenuItemText>
|
|
27
|
+
</MenuBaseItem>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default ActionMenuItem;
|
|
@@ -1,44 +1,46 @@
|
|
|
1
|
-
import { HTMLAttributes, memo } from 'react';
|
|
1
|
+
import { HTMLAttributes, NamedExoticComponent, memo } from 'react';
|
|
2
2
|
import { css, cx } from '@emotion/css';
|
|
3
|
+
import MenuBaseItem, { MenuBaseItemProps } from './MenuBaseItem';
|
|
4
|
+
import MenuDivider, { MenuDividerProps } from './MenuDivider';
|
|
5
|
+
import ActionMenuItem, { ActionMenuItemProps } from './ActionMenuItem';
|
|
6
|
+
import PrimaryMenuItem, { PrimaryMenuItemProps } from './PrimaryMenuItem';
|
|
7
|
+
import SecondaryMenuItem, { SecondaryMenuItemProps } from './SecondaryMenuItem';
|
|
3
8
|
import { useTheme } from '../../theme';
|
|
4
|
-
import MenuProvider from './Menu.context';
|
|
5
|
-
import MenuContent from './MenuContent';
|
|
6
|
-
import MenuItem from './MenuItem';
|
|
7
|
-
import MenuSection from './MenuSection';
|
|
8
|
-
import ButtonMenuTrigger, { ButtonMenuTriggerProps } from './trigger/ButtonMenuTrigger';
|
|
9
9
|
|
|
10
10
|
export const NAME = 'ucl-uikit-menu';
|
|
11
11
|
|
|
12
12
|
export interface MenuProps extends HTMLAttributes<HTMLDivElement> {
|
|
13
|
-
defaultOpen?: boolean;
|
|
14
|
-
title?: string;
|
|
15
|
-
menuId?: string;
|
|
16
|
-
position?: 'left' | 'right';
|
|
17
|
-
trigger?: React.ComponentType<ButtonMenuTriggerProps>;
|
|
18
13
|
testId?: string;
|
|
14
|
+
border?: boolean;
|
|
19
15
|
}
|
|
20
16
|
|
|
17
|
+
export type {
|
|
18
|
+
MenuDividerProps,
|
|
19
|
+
MenuBaseItemProps,
|
|
20
|
+
PrimaryMenuItemProps,
|
|
21
|
+
SecondaryMenuItemProps,
|
|
22
|
+
ActionMenuItemProps,
|
|
23
|
+
};
|
|
24
|
+
|
|
21
25
|
const Menu = ({
|
|
22
|
-
defaultOpen = false,
|
|
23
|
-
title,
|
|
24
|
-
position = 'right',
|
|
25
|
-
trigger,
|
|
26
|
-
menuId = NAME,
|
|
27
26
|
testId = NAME,
|
|
28
|
-
children,
|
|
29
27
|
className,
|
|
28
|
+
children,
|
|
29
|
+
border = false,
|
|
30
30
|
...props
|
|
31
31
|
}: MenuProps) => {
|
|
32
32
|
const [theme] = useTheme();
|
|
33
|
-
|
|
34
33
|
const baseStyle = css`
|
|
35
|
-
|
|
36
|
-
font-family: ${theme.font.family.primary};
|
|
37
|
-
`;
|
|
34
|
+
padding: ${theme.padding.p16} ${theme.padding.p24};
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
@media screen and (min-width: ${theme.breakpoints.tablet}px) {
|
|
37
|
+
padding: ${theme.padding.p48} ${theme.padding.p64};
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
const borderStyle = css`
|
|
41
|
+
border: 1px solid ${theme.colour.border.subtle};
|
|
42
|
+
`;
|
|
43
|
+
const style = cx(baseStyle, border && borderStyle, className);
|
|
42
44
|
|
|
43
45
|
return (
|
|
44
46
|
<nav
|
|
@@ -46,30 +48,25 @@ const Menu = ({
|
|
|
46
48
|
data-testid={testId}
|
|
47
49
|
{...props}
|
|
48
50
|
>
|
|
49
|
-
|
|
50
|
-
<TriggerComp aria-controls={menuId} />
|
|
51
|
-
<MenuContent
|
|
52
|
-
id={menuId}
|
|
53
|
-
title={title}
|
|
54
|
-
position={position}
|
|
55
|
-
>
|
|
56
|
-
{children}
|
|
57
|
-
</MenuContent>
|
|
58
|
-
</MenuProvider>
|
|
51
|
+
{children}
|
|
59
52
|
</nav>
|
|
60
53
|
);
|
|
61
54
|
};
|
|
62
55
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
56
|
+
interface MenuComponent extends NamedExoticComponent<MenuProps> {
|
|
57
|
+
Divider: typeof MenuDivider;
|
|
58
|
+
BaseItem: typeof MenuBaseItem;
|
|
59
|
+
PrimaryItem: typeof PrimaryMenuItem;
|
|
60
|
+
SecondaryItem: typeof SecondaryMenuItem;
|
|
61
|
+
ActionItem: typeof ActionMenuItem;
|
|
66
62
|
}
|
|
67
63
|
|
|
68
|
-
const MemoMenu = memo(Menu)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
const MemoMenu: MenuComponent = Object.assign(memo(Menu), {
|
|
65
|
+
Divider: MenuDivider,
|
|
66
|
+
BaseItem: MenuBaseItem,
|
|
67
|
+
PrimaryItem: PrimaryMenuItem,
|
|
68
|
+
SecondaryItem: SecondaryMenuItem,
|
|
69
|
+
ActionItem: ActionMenuItem,
|
|
70
|
+
});
|
|
74
71
|
|
|
75
|
-
export default
|
|
72
|
+
export default MemoMenu;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { HTMLAttributes, KeyboardEvent, ReactNode } from 'react';
|
|
2
|
+
import { css, cx } from '@emotion/css';
|
|
3
|
+
import { useTheme } from '../../theme';
|
|
4
|
+
|
|
5
|
+
export interface MenuBaseItemProps extends HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
icon?: ReactNode;
|
|
7
|
+
iconPosition?: 'left' | 'right';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const MenuBaseItem = ({
|
|
11
|
+
icon,
|
|
12
|
+
iconPosition = 'left',
|
|
13
|
+
className,
|
|
14
|
+
children,
|
|
15
|
+
onClick,
|
|
16
|
+
onKeyDown,
|
|
17
|
+
role,
|
|
18
|
+
tabIndex,
|
|
19
|
+
...props
|
|
20
|
+
}: MenuBaseItemProps) => {
|
|
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
|
+
|
|
41
|
+
const baseStyle = css`
|
|
42
|
+
width: 360px;
|
|
43
|
+
box-sizing: border-box;
|
|
44
|
+
min-height: 48px;
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
gap: ${theme.margin.m32};
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
|
|
50
|
+
&:hover .ucl-uikit-menu__item-text {
|
|
51
|
+
text-decoration: underline;
|
|
52
|
+
}
|
|
53
|
+
`;
|
|
54
|
+
const style = cx(baseStyle, className);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
className={style}
|
|
59
|
+
onClick={onClick}
|
|
60
|
+
onKeyDown={handleKeyDown}
|
|
61
|
+
role={role ?? (isInteractive ? 'button' : undefined)}
|
|
62
|
+
tabIndex={tabIndex ?? (isInteractive ? 0 : undefined)}
|
|
63
|
+
{...props}
|
|
64
|
+
>
|
|
65
|
+
{iconPosition === 'left' && icon}
|
|
66
|
+
{children}
|
|
67
|
+
{iconPosition === 'right' && icon}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export default MenuBaseItem;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { HTMLAttributes } from 'react';
|
|
2
|
+
import { css, cx } from '@emotion/css';
|
|
3
|
+
import { useTheme } from '../../theme';
|
|
4
|
+
|
|
5
|
+
export const NAME = 'ucl-uikit-menu__divider';
|
|
6
|
+
|
|
7
|
+
export interface MenuDividerProps extends HTMLAttributes<HTMLHRElement> {}
|
|
8
|
+
|
|
9
|
+
const MenuDivider = ({ className, ...props }: MenuDividerProps) => {
|
|
10
|
+
const [theme] = useTheme();
|
|
11
|
+
const baseStyle = css`
|
|
12
|
+
border: 0;
|
|
13
|
+
border-top: 1px solid ${theme.colour.border.subtle};
|
|
14
|
+
`;
|
|
15
|
+
const style = cx(NAME, baseStyle, className);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<hr
|
|
19
|
+
className={style}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default MenuDivider;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { css, cx } from '@emotion/css';
|
|
2
|
+
import {
|
|
3
|
+
Children,
|
|
4
|
+
ReactElement,
|
|
5
|
+
ReactNode,
|
|
6
|
+
cloneElement,
|
|
7
|
+
isValidElement,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import Text from '../Text';
|
|
10
|
+
|
|
11
|
+
export const NAME = 'ucl-uikit-menu__item-text';
|
|
12
|
+
|
|
13
|
+
export interface MenuItemTextProps {
|
|
14
|
+
asChild?: boolean;
|
|
15
|
+
className?: string;
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const MenuItemText = ({
|
|
20
|
+
asChild = false,
|
|
21
|
+
className,
|
|
22
|
+
children,
|
|
23
|
+
}: MenuItemTextProps) => {
|
|
24
|
+
const asChildResetStyle = css`
|
|
25
|
+
font: inherit;
|
|
26
|
+
color: inherit;
|
|
27
|
+
text-decoration: none;
|
|
28
|
+
|
|
29
|
+
&:hover,
|
|
30
|
+
&:visited,
|
|
31
|
+
&:active,
|
|
32
|
+
&:focus {
|
|
33
|
+
color: inherit;
|
|
34
|
+
text-decoration: inherit;
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
const style = cx(NAME, className);
|
|
38
|
+
|
|
39
|
+
if (asChild) {
|
|
40
|
+
const child = Children.only(children) as ReactElement<{
|
|
41
|
+
className?: string;
|
|
42
|
+
}>;
|
|
43
|
+
|
|
44
|
+
if (isValidElement(child)) {
|
|
45
|
+
return (
|
|
46
|
+
<Text className={style}>
|
|
47
|
+
{cloneElement(child, {
|
|
48
|
+
className: cx(asChildResetStyle, child.props.className),
|
|
49
|
+
})}
|
|
50
|
+
</Text>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return <Text className={style}>{children}</Text>;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default MenuItemText;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { css, cx } from '@emotion/css';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { MenuBaseItemProps } from './MenuBaseItem';
|
|
4
|
+
import MenuBaseItem from './MenuBaseItem';
|
|
5
|
+
import MenuItemText from './MenuItemText';
|
|
6
|
+
import { useTheme } from '../../theme';
|
|
7
|
+
|
|
8
|
+
export const NAME = 'ucl-uikit-menu__primary-item';
|
|
9
|
+
|
|
10
|
+
export interface PrimaryMenuItemProps extends Omit<
|
|
11
|
+
MenuBaseItemProps,
|
|
12
|
+
'iconPosition'
|
|
13
|
+
> {
|
|
14
|
+
asChild?: boolean;
|
|
15
|
+
trailingContent?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const PrimaryMenuItem = ({
|
|
19
|
+
className,
|
|
20
|
+
children,
|
|
21
|
+
asChild,
|
|
22
|
+
trailingContent,
|
|
23
|
+
...props
|
|
24
|
+
}: PrimaryMenuItemProps) => {
|
|
25
|
+
const [theme] = useTheme();
|
|
26
|
+
|
|
27
|
+
const baseStyle = css`
|
|
28
|
+
color: ${theme.colour.text.default};
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
const style = cx(NAME, baseStyle, className);
|
|
32
|
+
|
|
33
|
+
const textStyle = css``;
|
|
34
|
+
|
|
35
|
+
const contentStyle = css`
|
|
36
|
+
display: inline-flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
gap: ${theme.margin.m16};
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<MenuBaseItem
|
|
43
|
+
className={style}
|
|
44
|
+
{...props}
|
|
45
|
+
iconPosition='left'
|
|
46
|
+
>
|
|
47
|
+
<span className={contentStyle}>
|
|
48
|
+
<MenuItemText
|
|
49
|
+
asChild={asChild}
|
|
50
|
+
className={textStyle}
|
|
51
|
+
>
|
|
52
|
+
{children}
|
|
53
|
+
</MenuItemText>
|
|
54
|
+
{trailingContent}
|
|
55
|
+
</span>
|
|
56
|
+
</MenuBaseItem>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default PrimaryMenuItem;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { css, cx } from '@emotion/css';
|
|
2
|
+
import { MenuBaseItemProps } from './MenuBaseItem';
|
|
3
|
+
import MenuBaseItem from './MenuBaseItem';
|
|
4
|
+
import MenuItemText from './MenuItemText';
|
|
5
|
+
import { useTheme } from '../../theme';
|
|
6
|
+
import Icon from '../Icon';
|
|
7
|
+
|
|
8
|
+
export const NAME = 'ucl-uikit-menu__secondary-item';
|
|
9
|
+
|
|
10
|
+
export interface SecondaryMenuItemProps extends Omit<
|
|
11
|
+
MenuBaseItemProps,
|
|
12
|
+
'iconPosition'
|
|
13
|
+
> {
|
|
14
|
+
asChild?: boolean;
|
|
15
|
+
externalLink?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const SecondaryMenuItem = ({
|
|
19
|
+
className,
|
|
20
|
+
children,
|
|
21
|
+
asChild,
|
|
22
|
+
externalLink,
|
|
23
|
+
...props
|
|
24
|
+
}: SecondaryMenuItemProps) => {
|
|
25
|
+
const [theme] = useTheme();
|
|
26
|
+
|
|
27
|
+
const baseStyle = css`
|
|
28
|
+
color: ${theme.colour.text.secondary};
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
const style = cx(NAME, baseStyle, className);
|
|
32
|
+
|
|
33
|
+
const textStyle = css`
|
|
34
|
+
color: ${theme.colour.text.secondary};
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
const contentStyle = css`
|
|
38
|
+
display: inline-flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
gap: ${theme.margin.m8};
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<MenuBaseItem
|
|
45
|
+
className={style}
|
|
46
|
+
{...props}
|
|
47
|
+
>
|
|
48
|
+
<span className={contentStyle}>
|
|
49
|
+
<MenuItemText
|
|
50
|
+
asChild={asChild}
|
|
51
|
+
className={textStyle}
|
|
52
|
+
>
|
|
53
|
+
{children}
|
|
54
|
+
</MenuItemText>
|
|
55
|
+
{externalLink && (
|
|
56
|
+
<Icon.ExternalLink
|
|
57
|
+
size={16}
|
|
58
|
+
aria-hidden='true'
|
|
59
|
+
focusable='false'
|
|
60
|
+
/>
|
|
61
|
+
)}
|
|
62
|
+
</span>
|
|
63
|
+
</MenuBaseItem>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default SecondaryMenuItem;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
3
|
+
import Menu from '../Menu';
|
|
4
|
+
import { ThemeContextProvider } from '../../../theme/useTheme';
|
|
5
|
+
|
|
6
|
+
const wrap = (ui: React.ReactElement) =>
|
|
7
|
+
render(<ThemeContextProvider>{ui}</ThemeContextProvider>);
|
|
8
|
+
|
|
9
|
+
describe('Menu', () => {
|
|
10
|
+
test('renders menu root with default test id', () => {
|
|
11
|
+
wrap(<Menu>Content</Menu>);
|
|
12
|
+
|
|
13
|
+
expect(screen.getByTestId('ucl-uikit-menu')).toBeInTheDocument();
|
|
14
|
+
});
|
|
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
|
+
|
|
32
|
+
test('supports click-handler items without asChild link component', () => {
|
|
33
|
+
const onClick = vi.fn();
|
|
34
|
+
|
|
35
|
+
wrap(
|
|
36
|
+
<Menu>
|
|
37
|
+
<Menu.PrimaryItem onClick={onClick}>Home</Menu.PrimaryItem>
|
|
38
|
+
</Menu>
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
fireEvent.click(screen.getByText('Home'));
|
|
42
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
43
|
+
});
|
|
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
|
+
|
|
66
|
+
test('renders asChild link content for primary item', () => {
|
|
67
|
+
wrap(
|
|
68
|
+
<Menu>
|
|
69
|
+
<Menu.PrimaryItem asChild>
|
|
70
|
+
<a href='/home'>Home</a>
|
|
71
|
+
</Menu.PrimaryItem>
|
|
72
|
+
</Menu>
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const link = screen.getByRole('link', { name: 'Home' });
|
|
76
|
+
expect(link).toBeInTheDocument();
|
|
77
|
+
expect(link.closest('.ucl-uikit-menu__item-text')).toBeInTheDocument();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('primary item enforces icon on the left', () => {
|
|
81
|
+
const { getByText } = wrap(
|
|
82
|
+
<Menu>
|
|
83
|
+
<Menu.PrimaryItem icon={<span data-testid='left-icon'>I</span>}>
|
|
84
|
+
Home
|
|
85
|
+
</Menu.PrimaryItem>
|
|
86
|
+
</Menu>
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const item = getByText('Home').closest('.ucl-uikit-menu__primary-item');
|
|
90
|
+
expect(item?.firstChild).toHaveAttribute('data-testid', 'left-icon');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('action item enforces icon on the right', () => {
|
|
94
|
+
const { getByText } = wrap(
|
|
95
|
+
<Menu>
|
|
96
|
+
<Menu.ActionItem icon={<span data-testid='right-icon'>I</span>}>
|
|
97
|
+
Sign out
|
|
98
|
+
</Menu.ActionItem>
|
|
99
|
+
</Menu>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const item = getByText('Sign out').closest('.ucl-uikit-menu__action-item');
|
|
103
|
+
expect(item?.lastChild).toHaveAttribute('data-testid', 'right-icon');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('secondary externalLink item renders external icon', () => {
|
|
107
|
+
const { container } = wrap(
|
|
108
|
+
<Menu>
|
|
109
|
+
<Menu.SecondaryItem externalLink>
|
|
110
|
+
Accessibility statement
|
|
111
|
+
</Menu.SecondaryItem>
|
|
112
|
+
</Menu>
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const externalItem = screen
|
|
116
|
+
.getByText('Accessibility statement')
|
|
117
|
+
.closest('.ucl-uikit-menu__secondary-item');
|
|
118
|
+
|
|
119
|
+
expect(externalItem).toBeInTheDocument();
|
|
120
|
+
expect(
|
|
121
|
+
container.querySelector('.ucl-uikit-menu__secondary-item svg')
|
|
122
|
+
).toBeTruthy();
|
|
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
|
+
});
|
|
138
|
+
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export { default } from './Menu';
|
|
2
|
-
export type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
export type {
|
|
3
|
+
MenuProps,
|
|
4
|
+
MenuDividerProps,
|
|
5
|
+
MenuBaseItemProps,
|
|
6
|
+
PrimaryMenuItemProps,
|
|
7
|
+
SecondaryMenuItemProps,
|
|
8
|
+
ActionMenuItemProps,
|
|
9
|
+
} from './Menu';
|