uikit-react-public 0.25.0 → 0.25.2
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/DropdownMenu/DropdownMenu.d.ts +3 -2
- package/dist/components/DropdownMenu/DropdownMenuTrigger.d.ts +15 -0
- package/dist/components/MenuNew/ActionMenuButton.d.ts +6 -0
- package/dist/components/MenuNew/Menu.d.ts +10 -3
- package/dist/components/MenuNew/MenuAccordion/MenuAccordion.d.ts +15 -0
- package/dist/components/MenuNew/MenuAccordion/MenuAccordionChevron.d.ts +6 -0
- package/dist/components/MenuNew/MenuAccordion/MenuAccordionItem.d.ts +8 -0
- package/dist/components/MenuNew/MenuAccordion/index.d.ts +2 -0
- package/dist/components/MenuNew/MenuBaseItem.d.ts +4 -1
- package/dist/components/MenuNew/MenuHeading.d.ts +7 -0
- package/dist/components/MenuNew/MenuSection.d.ts +7 -0
- package/dist/components/MenuNew/PrimaryMenuItem.d.ts +2 -2
- package/dist/components/MenuNew/index.d.ts +1 -1
- package/dist/index.js +5101 -4838
- package/lib/components/Button/Button.tsx +4 -1
- package/lib/components/Button/__tests__/__snapshots__/Button.test.tsx.snap +9 -9
- package/lib/components/DropdownMenu/DropdownMenu.tsx +12 -24
- package/lib/components/DropdownMenu/DropdownMenuTrigger.tsx +74 -0
- package/lib/components/DropdownMenu/__tests__/DropdownMenu.test.tsx +58 -1
- package/lib/components/MenuNew/ActionMenuButton.tsx +39 -0
- package/lib/components/MenuNew/Menu.tsx +34 -7
- package/lib/components/MenuNew/MenuAccordion/MenuAccordion.tsx +104 -0
- package/lib/components/MenuNew/MenuAccordion/MenuAccordionChevron.tsx +30 -0
- package/lib/components/MenuNew/MenuAccordion/MenuAccordionItem.tsx +47 -0
- package/lib/components/MenuNew/MenuAccordion/index.ts +5 -0
- package/lib/components/MenuNew/MenuBaseItem.tsx +62 -5
- package/lib/components/MenuNew/MenuDivider.tsx +3 -1
- package/lib/components/MenuNew/MenuHeading.tsx +56 -0
- package/lib/components/MenuNew/MenuItemText.tsx +6 -0
- package/lib/components/MenuNew/MenuSection.tsx +36 -0
- package/lib/components/MenuNew/PrimaryMenuItem.tsx +9 -4
- package/lib/components/MenuNew/SecondaryMenuItem.tsx +9 -1
- package/lib/components/MenuNew/__tests__/Menu.test.tsx +180 -6
- package/lib/components/MenuNew/index.ts +4 -1
- package/lib/components/Table/subcomponents/Cell/__tests__/__snapshots__/Cell.test.tsx.snap +1 -1
- package/package.json +1 -1
- package/dist/components/MenuNew/ActionMenuItem.d.ts +0 -7
- package/lib/components/MenuNew/ActionMenuItem.tsx +0 -31
|
@@ -1,43 +1,100 @@
|
|
|
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
|
|
|
5
5
|
export interface MenuBaseItemProps extends HTMLAttributes<HTMLDivElement> {
|
|
6
6
|
icon?: ReactNode;
|
|
7
7
|
iconPosition?: 'left' | 'right';
|
|
8
|
+
endAdornment?: ReactNode;
|
|
9
|
+
underlineOnHover?: boolean;
|
|
10
|
+
asChild?: boolean;
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
const MenuBaseItem = ({
|
|
11
14
|
icon,
|
|
12
15
|
iconPosition = 'left',
|
|
16
|
+
endAdornment,
|
|
13
17
|
className,
|
|
14
18
|
children,
|
|
19
|
+
onClick,
|
|
20
|
+
onKeyDown,
|
|
21
|
+
role,
|
|
22
|
+
tabIndex,
|
|
23
|
+
underlineOnHover = true,
|
|
24
|
+
asChild = false,
|
|
15
25
|
...props
|
|
16
26
|
}: MenuBaseItemProps) => {
|
|
17
27
|
const [theme] = useTheme();
|
|
28
|
+
const isInteractive = typeof onClick === 'function';
|
|
29
|
+
const shouldUseButtonSemantics = !asChild;
|
|
30
|
+
|
|
31
|
+
const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
32
|
+
onKeyDown?.(event);
|
|
33
|
+
|
|
34
|
+
if (event.defaultPrevented || !isInteractive) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (event.key === 'Enter') {
|
|
39
|
+
event.currentTarget.click();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (event.key === ' ') {
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
event.currentTarget.click();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
18
48
|
const baseStyle = css`
|
|
19
|
-
width:
|
|
49
|
+
width: 100%;
|
|
20
50
|
box-sizing: border-box;
|
|
21
51
|
min-height: 48px;
|
|
52
|
+
border-radius: ${theme.radius.r2};
|
|
22
53
|
display: flex;
|
|
23
54
|
align-items: center;
|
|
24
|
-
gap: ${theme.margin.
|
|
55
|
+
gap: ${theme.margin.m16};
|
|
25
56
|
cursor: pointer;
|
|
57
|
+
outline: none;
|
|
58
|
+
|
|
59
|
+
&:focus-visible {
|
|
60
|
+
box-shadow: ${theme.boxShadow.focus};
|
|
61
|
+
}
|
|
26
62
|
|
|
27
|
-
&:
|
|
28
|
-
|
|
63
|
+
&:focus-within {
|
|
64
|
+
box-shadow: ${theme.boxShadow.focus};
|
|
29
65
|
}
|
|
66
|
+
|
|
67
|
+
${underlineOnHover &&
|
|
68
|
+
`
|
|
69
|
+
&:hover .ucl-uikit-menu__item-text {
|
|
70
|
+
text-decoration: underline;
|
|
71
|
+
}
|
|
72
|
+
`}
|
|
30
73
|
`;
|
|
31
74
|
const style = cx(baseStyle, className);
|
|
32
75
|
|
|
76
|
+
const endAdornmentStyle = css`
|
|
77
|
+
margin-left: auto;
|
|
78
|
+
display: inline-flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
flex-shrink: 0;
|
|
81
|
+
`;
|
|
82
|
+
|
|
33
83
|
return (
|
|
34
84
|
<div
|
|
35
85
|
className={style}
|
|
86
|
+
onClick={onClick}
|
|
87
|
+
onKeyDown={handleKeyDown}
|
|
88
|
+
role={role ?? (shouldUseButtonSemantics ? 'button' : undefined)}
|
|
89
|
+
tabIndex={tabIndex ?? (shouldUseButtonSemantics ? 0 : undefined)}
|
|
36
90
|
{...props}
|
|
37
91
|
>
|
|
38
92
|
{iconPosition === 'left' && icon}
|
|
39
93
|
{children}
|
|
40
94
|
{iconPosition === 'right' && icon}
|
|
95
|
+
{endAdornment && (
|
|
96
|
+
<span className={endAdornmentStyle}>{endAdornment}</span>
|
|
97
|
+
)}
|
|
41
98
|
</div>
|
|
42
99
|
);
|
|
43
100
|
};
|
|
@@ -10,7 +10,9 @@ const MenuDivider = ({ className, ...props }: MenuDividerProps) => {
|
|
|
10
10
|
const [theme] = useTheme();
|
|
11
11
|
const baseStyle = css`
|
|
12
12
|
border: 0;
|
|
13
|
-
|
|
13
|
+
height: 1px;
|
|
14
|
+
background-color: ${theme.colour.border.subtle};
|
|
15
|
+
margin: ${theme.margin.m16} 0;
|
|
14
16
|
`;
|
|
15
17
|
const style = cx(NAME, baseStyle, className);
|
|
16
18
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { HTMLAttributes } from 'react';
|
|
2
|
+
import { css, cx } from '@emotion/css';
|
|
3
|
+
import Heading from '../Heading';
|
|
4
|
+
import { useTheme } from '../../theme';
|
|
5
|
+
|
|
6
|
+
export const NAME = 'ucl-uikit-menu__heading';
|
|
7
|
+
|
|
8
|
+
export interface MenuHeadingProps extends HTMLAttributes<HTMLDivElement> {
|
|
9
|
+
testId?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const MenuHeading = ({
|
|
13
|
+
testId = NAME,
|
|
14
|
+
className,
|
|
15
|
+
children,
|
|
16
|
+
...props
|
|
17
|
+
}: MenuHeadingProps) => {
|
|
18
|
+
const [theme] = useTheme();
|
|
19
|
+
|
|
20
|
+
const baseStyle = css`
|
|
21
|
+
box-sizing: border-box;
|
|
22
|
+
margin: ${theme.margin.m32} 0 ${theme.margin.m16};
|
|
23
|
+
text-transform: uppercase;
|
|
24
|
+
|
|
25
|
+
@media screen and (min-width: ${theme.breakpoints.tablet}px) {
|
|
26
|
+
padding: 0 ${theme.padding.p16};
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const style = cx(NAME, baseStyle, className);
|
|
31
|
+
|
|
32
|
+
const headingStyle = css`
|
|
33
|
+
color: ${theme.colour.text.secondary};
|
|
34
|
+
text-transform: uppercase;
|
|
35
|
+
font-size: 14px;
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div
|
|
40
|
+
className={style}
|
|
41
|
+
data-testid={testId}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
<Heading
|
|
45
|
+
as='h3'
|
|
46
|
+
level='xs'
|
|
47
|
+
margins={false}
|
|
48
|
+
className={headingStyle}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
</Heading>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default MenuHeading;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { HTMLAttributes } from 'react';
|
|
2
|
+
import { css, cx } from '@emotion/css';
|
|
3
|
+
|
|
4
|
+
export const NAME = 'ucl-uikit-menu__section';
|
|
5
|
+
|
|
6
|
+
export interface MenuSectionProps extends HTMLAttributes<HTMLElement> {
|
|
7
|
+
testId?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const MenuSection = ({
|
|
11
|
+
testId = NAME,
|
|
12
|
+
className,
|
|
13
|
+
children,
|
|
14
|
+
...props
|
|
15
|
+
}: MenuSectionProps) => {
|
|
16
|
+
const style = cx(
|
|
17
|
+
NAME,
|
|
18
|
+
css`
|
|
19
|
+
margin: 0;
|
|
20
|
+
padding: 0;
|
|
21
|
+
`,
|
|
22
|
+
className
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<section
|
|
27
|
+
className={style}
|
|
28
|
+
data-testid={testId}
|
|
29
|
+
{...props}
|
|
30
|
+
>
|
|
31
|
+
{children}
|
|
32
|
+
</section>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default MenuSection;
|
|
@@ -12,20 +12,24 @@ 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();
|
|
26
26
|
|
|
27
27
|
const baseStyle = css`
|
|
28
28
|
color: ${theme.colour.text.default};
|
|
29
|
+
|
|
30
|
+
@media screen and (min-width: ${theme.breakpoints.tablet}px) {
|
|
31
|
+
padding: 0 ${theme.padding.p16};
|
|
32
|
+
}
|
|
29
33
|
`;
|
|
30
34
|
|
|
31
35
|
const style = cx(NAME, baseStyle, className);
|
|
@@ -41,8 +45,9 @@ const PrimaryMenuItem = ({
|
|
|
41
45
|
return (
|
|
42
46
|
<MenuBaseItem
|
|
43
47
|
className={style}
|
|
44
|
-
{
|
|
48
|
+
asChild={asChild}
|
|
45
49
|
iconPosition='left'
|
|
50
|
+
{...props}
|
|
46
51
|
>
|
|
47
52
|
<span className={contentStyle}>
|
|
48
53
|
<MenuItemText
|
|
@@ -51,7 +56,7 @@ const PrimaryMenuItem = ({
|
|
|
51
56
|
>
|
|
52
57
|
{children}
|
|
53
58
|
</MenuItemText>
|
|
54
|
-
{
|
|
59
|
+
{trailingContent}
|
|
55
60
|
</span>
|
|
56
61
|
</MenuBaseItem>
|
|
57
62
|
);
|
|
@@ -25,6 +25,7 @@ const SecondaryMenuItem = ({
|
|
|
25
25
|
const [theme] = useTheme();
|
|
26
26
|
|
|
27
27
|
const baseStyle = css`
|
|
28
|
+
padding-inline: ${theme.margin.m16};
|
|
28
29
|
color: ${theme.colour.text.secondary};
|
|
29
30
|
`;
|
|
30
31
|
|
|
@@ -43,6 +44,7 @@ const SecondaryMenuItem = ({
|
|
|
43
44
|
return (
|
|
44
45
|
<MenuBaseItem
|
|
45
46
|
className={style}
|
|
47
|
+
asChild={asChild}
|
|
46
48
|
{...props}
|
|
47
49
|
>
|
|
48
50
|
<span className={contentStyle}>
|
|
@@ -52,7 +54,13 @@ const SecondaryMenuItem = ({
|
|
|
52
54
|
>
|
|
53
55
|
{children}
|
|
54
56
|
</MenuItemText>
|
|
55
|
-
{externalLink &&
|
|
57
|
+
{externalLink && (
|
|
58
|
+
<Icon.ExternalLink
|
|
59
|
+
size={16}
|
|
60
|
+
aria-hidden='true'
|
|
61
|
+
focusable='false'
|
|
62
|
+
/>
|
|
63
|
+
)}
|
|
56
64
|
</span>
|
|
57
65
|
</MenuBaseItem>
|
|
58
66
|
);
|
|
@@ -13,6 +13,108 @@ describe('Menu', () => {
|
|
|
13
13
|
expect(screen.getByTestId('ucl-uikit-menu')).toBeInTheDocument();
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
+
test('renders heading item', () => {
|
|
17
|
+
wrap(
|
|
18
|
+
<Menu>
|
|
19
|
+
<Menu.Heading>Main navigation</Menu.Heading>
|
|
20
|
+
</Menu>
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const heading = screen.getByTestId('ucl-uikit-menu__heading');
|
|
24
|
+
expect(heading).toBeInTheDocument();
|
|
25
|
+
expect(heading).toHaveTextContent('Main navigation');
|
|
26
|
+
expect(heading).not.toHaveAttribute('role', 'button');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('renders semantic section wrapper', () => {
|
|
30
|
+
wrap(
|
|
31
|
+
<Menu>
|
|
32
|
+
<Menu.Section>
|
|
33
|
+
<Menu.Heading>Support</Menu.Heading>
|
|
34
|
+
</Menu.Section>
|
|
35
|
+
</Menu>
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const section = screen.getByTestId('ucl-uikit-menu__section');
|
|
39
|
+
expect(section.tagName).toBe('SECTION');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('accordion renders like a primary item', () => {
|
|
43
|
+
const { getByText } = wrap(
|
|
44
|
+
<Menu>
|
|
45
|
+
<Menu.Accordion
|
|
46
|
+
label='Services'
|
|
47
|
+
icon={<span data-testid='accordion-left-icon'>I</span>}
|
|
48
|
+
>
|
|
49
|
+
<Menu.Accordion.Item>Library</Menu.Accordion.Item>
|
|
50
|
+
</Menu.Accordion>
|
|
51
|
+
</Menu>
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const item = getByText('Services').closest('.ucl-uikit-menu__accordion');
|
|
55
|
+
expect(item?.firstChild).toHaveAttribute(
|
|
56
|
+
'data-testid',
|
|
57
|
+
'accordion-left-icon'
|
|
58
|
+
);
|
|
59
|
+
expect(
|
|
60
|
+
screen.getByTestId('ucl-uikit-menu__accordion__icon--chevron-down')
|
|
61
|
+
).toBeInTheDocument();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('accordion toggles to expanded state with up chevron', () => {
|
|
65
|
+
wrap(
|
|
66
|
+
<Menu>
|
|
67
|
+
<Menu.Accordion
|
|
68
|
+
label='Services'
|
|
69
|
+
icon={<span data-testid='accordion-left-icon'>I</span>}
|
|
70
|
+
>
|
|
71
|
+
<Menu.Accordion.Item>Library</Menu.Accordion.Item>
|
|
72
|
+
</Menu.Accordion>
|
|
73
|
+
</Menu>
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
expect(screen.queryByText('Library')).not.toBeInTheDocument();
|
|
77
|
+
fireEvent.click(screen.getByText('Services'));
|
|
78
|
+
expect(
|
|
79
|
+
screen.getByTestId('ucl-uikit-menu__accordion__icon--chevron-up')
|
|
80
|
+
).toBeInTheDocument();
|
|
81
|
+
expect(screen.getByText('Library')).toBeInTheDocument();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('accordion item supports asChild link content', () => {
|
|
85
|
+
wrap(
|
|
86
|
+
<Menu>
|
|
87
|
+
<Menu.Accordion
|
|
88
|
+
label='Services'
|
|
89
|
+
icon={<span data-testid='accordion-left-icon'>I</span>}
|
|
90
|
+
defaultExpanded
|
|
91
|
+
>
|
|
92
|
+
<Menu.Accordion.Item asChild>
|
|
93
|
+
<a href='/library'>Library</a>
|
|
94
|
+
</Menu.Accordion.Item>
|
|
95
|
+
</Menu.Accordion>
|
|
96
|
+
</Menu>
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
expect(screen.getByRole('link', { name: 'Library' })).toBeInTheDocument();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('has no border by default', () => {
|
|
103
|
+
wrap(<Menu>Content</Menu>);
|
|
104
|
+
|
|
105
|
+
const menu = screen.getByTestId('ucl-uikit-menu');
|
|
106
|
+
const computedStyles = window.getComputedStyle(menu);
|
|
107
|
+
expect(computedStyles.borderTopStyle).toBe('');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('allows border to be enabled with prop override', () => {
|
|
111
|
+
wrap(<Menu border>Content</Menu>);
|
|
112
|
+
|
|
113
|
+
const menu = screen.getByTestId('ucl-uikit-menu');
|
|
114
|
+
const computedStyles = window.getComputedStyle(menu);
|
|
115
|
+
expect(computedStyles.borderTopStyle).toBe('solid');
|
|
116
|
+
});
|
|
117
|
+
|
|
16
118
|
test('supports click-handler items without asChild link component', () => {
|
|
17
119
|
const onClick = vi.fn();
|
|
18
120
|
|
|
@@ -26,6 +128,42 @@ describe('Menu', () => {
|
|
|
26
128
|
expect(onClick).toHaveBeenCalledTimes(1);
|
|
27
129
|
});
|
|
28
130
|
|
|
131
|
+
test('click-handler items are keyboard accessible', () => {
|
|
132
|
+
const onClick = vi.fn();
|
|
133
|
+
|
|
134
|
+
wrap(
|
|
135
|
+
<Menu>
|
|
136
|
+
<Menu.PrimaryItem onClick={onClick}>Home</Menu.PrimaryItem>
|
|
137
|
+
</Menu>
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const item = screen
|
|
141
|
+
.getByText('Home')
|
|
142
|
+
.closest('.ucl-uikit-menu__primary-item');
|
|
143
|
+
|
|
144
|
+
expect(item).toHaveAttribute('role', 'button');
|
|
145
|
+
expect(item).toHaveAttribute('tabindex', '0');
|
|
146
|
+
|
|
147
|
+
fireEvent.keyDown(item as Element, { key: 'Enter' });
|
|
148
|
+
fireEvent.keyDown(item as Element, { key: ' ' });
|
|
149
|
+
expect(onClick).toHaveBeenCalledTimes(2);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('non-asChild items expose button semantics', () => {
|
|
153
|
+
wrap(
|
|
154
|
+
<Menu>
|
|
155
|
+
<Menu.PrimaryItem>Home</Menu.PrimaryItem>
|
|
156
|
+
</Menu>
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const item = screen
|
|
160
|
+
.getByText('Home')
|
|
161
|
+
.closest('.ucl-uikit-menu__primary-item');
|
|
162
|
+
|
|
163
|
+
expect(item).toHaveAttribute('role', 'button');
|
|
164
|
+
expect(item).toHaveAttribute('tabindex', '0');
|
|
165
|
+
});
|
|
166
|
+
|
|
29
167
|
test('renders asChild link content for primary item', () => {
|
|
30
168
|
wrap(
|
|
31
169
|
<Menu>
|
|
@@ -38,6 +176,10 @@ describe('Menu', () => {
|
|
|
38
176
|
const link = screen.getByRole('link', { name: 'Home' });
|
|
39
177
|
expect(link).toBeInTheDocument();
|
|
40
178
|
expect(link.closest('.ucl-uikit-menu__item-text')).toBeInTheDocument();
|
|
179
|
+
expect(link.closest('.ucl-uikit-menu__primary-item')).not.toHaveAttribute(
|
|
180
|
+
'role',
|
|
181
|
+
'button'
|
|
182
|
+
);
|
|
41
183
|
});
|
|
42
184
|
|
|
43
185
|
test('primary item enforces icon on the left', () => {
|
|
@@ -53,17 +195,35 @@ describe('Menu', () => {
|
|
|
53
195
|
expect(item?.firstChild).toHaveAttribute('data-testid', 'left-icon');
|
|
54
196
|
});
|
|
55
197
|
|
|
56
|
-
test('
|
|
57
|
-
|
|
198
|
+
test('can disable hover underline', () => {
|
|
199
|
+
wrap(
|
|
200
|
+
<Menu>
|
|
201
|
+
<Menu.PrimaryItem underlineOnHover={false}>Home</Menu.PrimaryItem>
|
|
202
|
+
</Menu>
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const item = screen
|
|
206
|
+
.getByText('Home')
|
|
207
|
+
.closest('.ucl-uikit-menu__primary-item');
|
|
208
|
+
const text = screen.getByText('Home');
|
|
209
|
+
|
|
210
|
+
fireEvent.mouseEnter(item as Element);
|
|
211
|
+
|
|
212
|
+
expect(window.getComputedStyle(text).textDecorationLine).not.toBe(
|
|
213
|
+
'underline'
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('action button renders icon', () => {
|
|
218
|
+
wrap(
|
|
58
219
|
<Menu>
|
|
59
|
-
<Menu.
|
|
220
|
+
<Menu.ActionButton icon={<span data-testid='right-icon'>I</span>}>
|
|
60
221
|
Sign out
|
|
61
|
-
</Menu.
|
|
222
|
+
</Menu.ActionButton>
|
|
62
223
|
</Menu>
|
|
63
224
|
);
|
|
64
225
|
|
|
65
|
-
|
|
66
|
-
expect(item?.lastChild).toHaveAttribute('data-testid', 'right-icon');
|
|
226
|
+
expect(screen.getByTestId('right-icon')).toBeInTheDocument();
|
|
67
227
|
});
|
|
68
228
|
|
|
69
229
|
test('secondary externalLink item renders external icon', () => {
|
|
@@ -84,4 +244,18 @@ describe('Menu', () => {
|
|
|
84
244
|
container.querySelector('.ucl-uikit-menu__secondary-item svg')
|
|
85
245
|
).toBeTruthy();
|
|
86
246
|
});
|
|
247
|
+
|
|
248
|
+
test('secondary externalLink icon is hidden from assistive tech', () => {
|
|
249
|
+
const { container } = wrap(
|
|
250
|
+
<Menu>
|
|
251
|
+
<Menu.SecondaryItem externalLink>
|
|
252
|
+
Accessibility statement
|
|
253
|
+
</Menu.SecondaryItem>
|
|
254
|
+
</Menu>
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const icon = container.querySelector('.ucl-uikit-menu__secondary-item svg');
|
|
258
|
+
expect(icon).toHaveAttribute('aria-hidden', 'true');
|
|
259
|
+
expect(icon).toHaveAttribute('focusable', 'false');
|
|
260
|
+
});
|
|
87
261
|
});
|
|
@@ -2,8 +2,11 @@ export { default } from './Menu';
|
|
|
2
2
|
export type {
|
|
3
3
|
MenuProps,
|
|
4
4
|
MenuDividerProps,
|
|
5
|
+
MenuHeadingProps,
|
|
6
|
+
MenuSectionProps,
|
|
7
|
+
MenuAccordionProps,
|
|
5
8
|
MenuBaseItemProps,
|
|
6
9
|
PrimaryMenuItemProps,
|
|
7
10
|
SecondaryMenuItemProps,
|
|
8
|
-
|
|
11
|
+
ActionMenuButtonProps,
|
|
9
12
|
} from './Menu';
|
package/package.json
CHANGED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { MenuBaseItemProps } from './MenuBaseItem';
|
|
2
|
-
export declare const NAME = "ucl-uikit-menu__action-item";
|
|
3
|
-
export interface ActionMenuItemProps extends MenuBaseItemProps {
|
|
4
|
-
asChild?: boolean;
|
|
5
|
-
}
|
|
6
|
-
declare const ActionMenuItem: ({ className, children, asChild, ...props }: ActionMenuItemProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
-
export default ActionMenuItem;
|
|
@@ -1,31 +0,0 @@
|
|
|
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;
|