uikit-react-public 0.22.1 → 0.24.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/Avatar/Avatar.d.ts +1 -1
- package/dist/components/Dropdown/Dropdown.context.d.ts +15 -0
- package/dist/components/Dropdown/Dropdown.d.ts +15 -0
- package/dist/components/Dropdown/Dropdown.stories.d.ts +36 -0
- package/dist/components/Dropdown/DropdownContent.d.ts +8 -0
- package/dist/components/Dropdown/DropdownTrigger.d.ts +7 -0
- package/dist/components/Dropdown/__tests__/Dropdown.test.d.ts +1 -0
- package/dist/components/Dropdown/index.d.ts +4 -0
- package/dist/components/DropdownMenu/DropdownMenu.d.ts +13 -0
- package/dist/components/DropdownMenu/__tests__/DropdownMenu.test.d.ts +1 -0
- package/dist/components/DropdownMenu/index.d.ts +2 -0
- package/dist/components/DropdownUserMenu/DropdownUserMenu.d.ts +19 -0
- package/dist/components/DropdownUserMenu/__tests__/DropdownUserMenu.test.d.ts +1 -0
- package/dist/components/DropdownUserMenu/index.d.ts +2 -0
- package/dist/components/HeaderNew/Header.d.ts +2 -0
- package/dist/components/HeaderNew/HeaderItem.d.ts +7 -0
- package/dist/components/HeaderNew/index.d.ts +1 -0
- package/dist/components/SimpleMenu/SimpleMenu.d.ts +7 -0
- package/dist/components/SimpleMenu/__tests__/SimpleMenu.test.d.ts +1 -0
- package/dist/components/SimpleMenu/index.d.ts +2 -0
- package/dist/components/index.d.ts +8 -0
- package/dist/index.js +3945 -3607
- package/lib/components/Avatar/Avatar.tsx +1 -1
- package/lib/components/Dropdown/Dropdown.context.tsx +62 -0
- package/lib/components/Dropdown/Dropdown.stories.tsx +184 -0
- package/lib/components/Dropdown/Dropdown.tsx +91 -0
- package/lib/components/Dropdown/DropdownContent.tsx +70 -0
- package/lib/components/Dropdown/DropdownTrigger.tsx +62 -0
- package/lib/components/Dropdown/__tests__/Dropdown.test.tsx +63 -0
- package/lib/components/Dropdown/index.ts +4 -0
- package/lib/components/DropdownMenu/DropdownMenu.tsx +82 -0
- package/lib/components/DropdownMenu/__tests__/DropdownMenu.test.tsx +96 -0
- package/lib/components/DropdownMenu/index.ts +2 -0
- package/lib/components/DropdownUserMenu/DropdownUserMenu.tsx +124 -0
- package/lib/components/DropdownUserMenu/__tests__/DropdownUserMenu.test.tsx +97 -0
- package/lib/components/DropdownUserMenu/index.ts +2 -0
- package/lib/components/HeaderNew/Header.tsx +3 -0
- package/lib/components/HeaderNew/HeaderItem.tsx +43 -0
- package/lib/components/HeaderNew/HeaderLogo.tsx +3 -0
- package/lib/components/HeaderNew/HeaderMenuContainer.tsx +3 -1
- package/lib/components/HeaderNew/HeaderTitle.tsx +8 -1
- package/lib/components/HeaderNew/__tests__/__snapshots__/Header.test.tsx.snap +4 -4
- package/lib/components/HeaderNew/index.ts +1 -0
- package/lib/components/Icon/svgImports.ts +2 -0
- package/lib/components/SimpleMenu/SimpleMenu.tsx +40 -0
- package/lib/components/SimpleMenu/__tests__/SimpleMenu.test.tsx +38 -0
- package/lib/components/SimpleMenu/index.ts +2 -0
- package/lib/components/index.ts +16 -0
- package/package.json +2 -2
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
3
|
+
import DropdownMenu from '../DropdownMenu';
|
|
4
|
+
import { ThemeContextProvider } from '../../../theme/useTheme';
|
|
5
|
+
|
|
6
|
+
const mockMatchMedia = (matchesDesktop: boolean) => {
|
|
7
|
+
globalThis.matchMedia = vi.fn().mockImplementation((query) => ({
|
|
8
|
+
matches: query.includes('min-width') ? matchesDesktop : false,
|
|
9
|
+
media: query,
|
|
10
|
+
addListener: vi.fn(),
|
|
11
|
+
removeListener: vi.fn(),
|
|
12
|
+
addEventListener: vi.fn(),
|
|
13
|
+
removeEventListener: vi.fn(),
|
|
14
|
+
dispatchEvent: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
describe('DropdownMenu', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
mockMatchMedia(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('renders text button trigger on desktop', () => {
|
|
24
|
+
render(
|
|
25
|
+
<ThemeContextProvider>
|
|
26
|
+
<DropdownMenu label='Menu'>
|
|
27
|
+
<div>Menu content</div>
|
|
28
|
+
</DropdownMenu>
|
|
29
|
+
</ThemeContextProvider>
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
expect(
|
|
33
|
+
screen.getByRole('button', { name: 'Open menu' })
|
|
34
|
+
).toBeInTheDocument();
|
|
35
|
+
expect(screen.getByText('Menu')).toBeInTheDocument();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('renders icon-only trigger on smaller screens', () => {
|
|
39
|
+
mockMatchMedia(false);
|
|
40
|
+
|
|
41
|
+
render(
|
|
42
|
+
<ThemeContextProvider>
|
|
43
|
+
<DropdownMenu>
|
|
44
|
+
<div>Menu content</div>
|
|
45
|
+
</DropdownMenu>
|
|
46
|
+
</ThemeContextProvider>
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
expect(
|
|
50
|
+
screen.getByRole('button', { name: 'Open menu' })
|
|
51
|
+
).toBeInTheDocument();
|
|
52
|
+
expect(screen.queryByText('Menu')).not.toBeInTheDocument();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('supports className overrides for trigger and content', () => {
|
|
56
|
+
render(
|
|
57
|
+
<ThemeContextProvider>
|
|
58
|
+
<DropdownMenu
|
|
59
|
+
className='trigger-override'
|
|
60
|
+
contentClassName='content-override'
|
|
61
|
+
defaultOpen
|
|
62
|
+
>
|
|
63
|
+
<div>Menu content</div>
|
|
64
|
+
</DropdownMenu>
|
|
65
|
+
</ThemeContextProvider>
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
expect(screen.getByRole('button', { name: 'Open menu' })).toHaveClass(
|
|
69
|
+
'trigger-override'
|
|
70
|
+
);
|
|
71
|
+
expect(screen.getByTestId('ucl-uikit-dropdown__content')).toHaveClass(
|
|
72
|
+
'content-override'
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('opens and closes content when trigger is clicked', () => {
|
|
77
|
+
render(
|
|
78
|
+
<ThemeContextProvider>
|
|
79
|
+
<DropdownMenu>
|
|
80
|
+
<div>Menu content</div>
|
|
81
|
+
</DropdownMenu>
|
|
82
|
+
</ThemeContextProvider>
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const trigger = screen.getByRole('button', { name: 'Open menu' });
|
|
86
|
+
const content = screen.getByTestId('ucl-uikit-dropdown__content');
|
|
87
|
+
|
|
88
|
+
expect(content).not.toBeVisible();
|
|
89
|
+
|
|
90
|
+
fireEvent.click(trigger);
|
|
91
|
+
expect(content).toBeVisible();
|
|
92
|
+
|
|
93
|
+
fireEvent.click(trigger);
|
|
94
|
+
expect(content).not.toBeVisible();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import { css, cx } from '@emotion/css';
|
|
3
|
+
import Dropdown, { DropdownProps } from '../Dropdown';
|
|
4
|
+
import Avatar, { AvatarProps } from '../Avatar';
|
|
5
|
+
import Icon from '../Icon';
|
|
6
|
+
import useTheme from '../../theme/useTheme';
|
|
7
|
+
import useMediaQuery from '../../hooks/useMediaQuery';
|
|
8
|
+
|
|
9
|
+
export const NAME = 'ucl-uikit-dropdown-user-menu';
|
|
10
|
+
|
|
11
|
+
export interface DropdownUserMenuProps
|
|
12
|
+
extends
|
|
13
|
+
Omit<DropdownProps, 'children'>,
|
|
14
|
+
Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
userName: string;
|
|
17
|
+
imageUrl?: string;
|
|
18
|
+
avatarVariant?: AvatarProps['variant'];
|
|
19
|
+
avatarSize?: AvatarProps['size'];
|
|
20
|
+
triggerAriaLabel?: string;
|
|
21
|
+
dropdownClassName?: string;
|
|
22
|
+
triggerClassName?: string;
|
|
23
|
+
contentClassName?: string;
|
|
24
|
+
avatarClassName?: string;
|
|
25
|
+
userNameClassName?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const DropdownUserMenu = ({
|
|
29
|
+
children,
|
|
30
|
+
userName,
|
|
31
|
+
imageUrl,
|
|
32
|
+
avatarVariant = 'image',
|
|
33
|
+
avatarSize = 32,
|
|
34
|
+
triggerAriaLabel = 'Open user menu',
|
|
35
|
+
dropdownClassName,
|
|
36
|
+
triggerClassName,
|
|
37
|
+
contentClassName,
|
|
38
|
+
avatarClassName,
|
|
39
|
+
userNameClassName,
|
|
40
|
+
className,
|
|
41
|
+
...props
|
|
42
|
+
}: DropdownUserMenuProps) => {
|
|
43
|
+
const [theme] = useTheme();
|
|
44
|
+
const isDesktop = useMediaQuery(
|
|
45
|
+
`(min-width: ${theme.breakpoints.desktop}px)`
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const triggerStyle = css`
|
|
49
|
+
display: inline-flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
gap: ${theme.padding.p8};
|
|
52
|
+
min-height: 48px;
|
|
53
|
+
padding: 0 ${theme.padding.p8};
|
|
54
|
+
border-radius: ${theme.radius.r4};
|
|
55
|
+
color: ${theme.colour.text.brandPrimary};
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
outline: none;
|
|
58
|
+
box-sizing: border-box;
|
|
59
|
+
|
|
60
|
+
&:hover {
|
|
61
|
+
background-color: ${theme.colour.fill.brandSubtleHover};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
&:focus-visible {
|
|
65
|
+
box-shadow: ${theme.boxShadow.focus};
|
|
66
|
+
}
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
const userNameStyle = css`
|
|
70
|
+
font-family: ${theme.typography.body.md.fontFamily};
|
|
71
|
+
font-feature-settings: ${theme.typography.body.md.fontSettings};
|
|
72
|
+
font-size: ${theme.typography.body.md.fontSize}px;
|
|
73
|
+
line-height: 1.2;
|
|
74
|
+
white-space: nowrap;
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
const avatarStyle = css`
|
|
78
|
+
pointer-events: none;
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
const dropdownStyle = cx(NAME, dropdownClassName);
|
|
82
|
+
const resolvedTriggerClassName = cx(
|
|
83
|
+
triggerStyle,
|
|
84
|
+
className,
|
|
85
|
+
triggerClassName
|
|
86
|
+
);
|
|
87
|
+
const resolvedUserNameClassName = cx(userNameStyle, userNameClassName);
|
|
88
|
+
const resolvedAvatarClassName = cx(avatarStyle, avatarClassName);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Dropdown
|
|
92
|
+
className={dropdownStyle}
|
|
93
|
+
{...props}
|
|
94
|
+
>
|
|
95
|
+
<Dropdown.Trigger>
|
|
96
|
+
<div
|
|
97
|
+
tabIndex={0}
|
|
98
|
+
role='button'
|
|
99
|
+
aria-label={triggerAriaLabel}
|
|
100
|
+
className={resolvedTriggerClassName}
|
|
101
|
+
>
|
|
102
|
+
{isDesktop ? (
|
|
103
|
+
<span className={resolvedUserNameClassName}>{userName}</span>
|
|
104
|
+
) : null}
|
|
105
|
+
<Avatar
|
|
106
|
+
tabIndex={-1}
|
|
107
|
+
aria-hidden='true'
|
|
108
|
+
className={resolvedAvatarClassName}
|
|
109
|
+
name={userName}
|
|
110
|
+
imageUrl={imageUrl}
|
|
111
|
+
variant={avatarVariant}
|
|
112
|
+
size={avatarSize}
|
|
113
|
+
/>
|
|
114
|
+
<Icon.ChevronDown size={20} />
|
|
115
|
+
</div>
|
|
116
|
+
</Dropdown.Trigger>
|
|
117
|
+
<Dropdown.Content className={contentClassName}>
|
|
118
|
+
{children}
|
|
119
|
+
</Dropdown.Content>
|
|
120
|
+
</Dropdown>
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export default DropdownUserMenu;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
3
|
+
import DropdownUserMenu from '../DropdownUserMenu';
|
|
4
|
+
import { ThemeContextProvider } from '../../../theme/useTheme';
|
|
5
|
+
|
|
6
|
+
const mockMatchMedia = (matchesDesktop: boolean) => {
|
|
7
|
+
globalThis.matchMedia = vi.fn().mockImplementation((query) => ({
|
|
8
|
+
matches: query.includes('min-width') ? matchesDesktop : false,
|
|
9
|
+
media: query,
|
|
10
|
+
addListener: vi.fn(),
|
|
11
|
+
removeListener: vi.fn(),
|
|
12
|
+
addEventListener: vi.fn(),
|
|
13
|
+
removeEventListener: vi.fn(),
|
|
14
|
+
dispatchEvent: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
describe('DropdownUserMenu', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
mockMatchMedia(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('shows user name on desktop', () => {
|
|
24
|
+
render(
|
|
25
|
+
<ThemeContextProvider>
|
|
26
|
+
<DropdownUserMenu userName='Ada Lovelace'>
|
|
27
|
+
<div>Menu content</div>
|
|
28
|
+
</DropdownUserMenu>
|
|
29
|
+
</ThemeContextProvider>
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
expect(screen.getByText('Ada Lovelace')).toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('hides user name on smaller screens', () => {
|
|
36
|
+
mockMatchMedia(false);
|
|
37
|
+
|
|
38
|
+
render(
|
|
39
|
+
<ThemeContextProvider>
|
|
40
|
+
<DropdownUserMenu userName='Ada Lovelace'>
|
|
41
|
+
<div>Menu content</div>
|
|
42
|
+
</DropdownUserMenu>
|
|
43
|
+
</ThemeContextProvider>
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
expect(screen.queryByText('Ada Lovelace')).not.toBeInTheDocument();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('supports className overrides for trigger, content, avatar and userName', () => {
|
|
50
|
+
render(
|
|
51
|
+
<ThemeContextProvider>
|
|
52
|
+
<DropdownUserMenu
|
|
53
|
+
userName='Ada Lovelace'
|
|
54
|
+
className='trigger-override'
|
|
55
|
+
contentClassName='content-override'
|
|
56
|
+
avatarClassName='avatar-override'
|
|
57
|
+
userNameClassName='name-override'
|
|
58
|
+
defaultOpen
|
|
59
|
+
>
|
|
60
|
+
<div>Menu content</div>
|
|
61
|
+
</DropdownUserMenu>
|
|
62
|
+
</ThemeContextProvider>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
expect(screen.getByRole('button', { name: 'Open user menu' })).toHaveClass(
|
|
66
|
+
'trigger-override'
|
|
67
|
+
);
|
|
68
|
+
expect(screen.getByTestId('ucl-uikit-dropdown__content')).toHaveClass(
|
|
69
|
+
'content-override'
|
|
70
|
+
);
|
|
71
|
+
expect(screen.getByTestId('ucl-uikit-avatar')).toHaveClass(
|
|
72
|
+
'avatar-override'
|
|
73
|
+
);
|
|
74
|
+
expect(screen.getByText('Ada Lovelace')).toHaveClass('name-override');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('opens and closes content when trigger is clicked', () => {
|
|
78
|
+
render(
|
|
79
|
+
<ThemeContextProvider>
|
|
80
|
+
<DropdownUserMenu userName='Ada Lovelace'>
|
|
81
|
+
<div>Menu content</div>
|
|
82
|
+
</DropdownUserMenu>
|
|
83
|
+
</ThemeContextProvider>
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const trigger = screen.getByRole('button', { name: 'Open user menu' });
|
|
87
|
+
const content = screen.getByTestId('ucl-uikit-dropdown__content');
|
|
88
|
+
|
|
89
|
+
expect(content).not.toBeVisible();
|
|
90
|
+
|
|
91
|
+
fireEvent.click(trigger);
|
|
92
|
+
expect(content).toBeVisible();
|
|
93
|
+
|
|
94
|
+
fireEvent.click(trigger);
|
|
95
|
+
expect(content).not.toBeVisible();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -2,6 +2,7 @@ import { memo, HTMLAttributes } from 'react';
|
|
|
2
2
|
import { css, cx } from '@emotion/css';
|
|
3
3
|
import { useTheme } from '../..';
|
|
4
4
|
import HeaderMenuContainer from './HeaderMenuContainer';
|
|
5
|
+
import HeaderItem from './HeaderItem';
|
|
5
6
|
import HeaderTitle from './HeaderTitle';
|
|
6
7
|
import HeaderLogo from './HeaderLogo';
|
|
7
8
|
import HeaderBorder from './HeaderBorder';
|
|
@@ -83,11 +84,13 @@ const MemoAppHeader = memo(Header);
|
|
|
83
84
|
|
|
84
85
|
export interface IHeaderSubComponents {
|
|
85
86
|
MenuContainer: typeof HeaderMenuContainer;
|
|
87
|
+
Item: typeof HeaderItem;
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
const HeaderWithSubComponents = MemoAppHeader as typeof MemoAppHeader &
|
|
89
91
|
IHeaderSubComponents;
|
|
90
92
|
|
|
91
93
|
HeaderWithSubComponents.MenuContainer = HeaderMenuContainer;
|
|
94
|
+
HeaderWithSubComponents.Item = HeaderItem;
|
|
92
95
|
|
|
93
96
|
export default HeaderWithSubComponents;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { HTMLAttributes } from 'react';
|
|
2
|
+
import { css, cx } from '@emotion/css';
|
|
3
|
+
import { useTheme } from '../..';
|
|
4
|
+
|
|
5
|
+
export const NAME = 'ucl-uikit-header__item';
|
|
6
|
+
|
|
7
|
+
export interface HeaderItemProps extends HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
testId?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const HeaderItem = ({
|
|
12
|
+
testId = NAME,
|
|
13
|
+
className,
|
|
14
|
+
children,
|
|
15
|
+
...props
|
|
16
|
+
}: HeaderItemProps) => {
|
|
17
|
+
const [theme] = useTheme();
|
|
18
|
+
|
|
19
|
+
const baseStyle = css`
|
|
20
|
+
display: inline-flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
align-self: center;
|
|
23
|
+
min-height: 40px;
|
|
24
|
+
|
|
25
|
+
&:not(:first-of-type) {
|
|
26
|
+
margin-left: ${theme.margin.m8};
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const style = cx(NAME, baseStyle, className);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
className={style}
|
|
35
|
+
data-testid={testId}
|
|
36
|
+
{...props}
|
|
37
|
+
>
|
|
38
|
+
{children}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default HeaderItem;
|
|
@@ -20,6 +20,7 @@ const HeaderLogo = ({
|
|
|
20
20
|
|
|
21
21
|
const baseStyle = css`
|
|
22
22
|
display: none;
|
|
23
|
+
flex: 0 0 auto;
|
|
23
24
|
height: 29px;
|
|
24
25
|
color: ${theme.colour.fill.brand};
|
|
25
26
|
|
|
@@ -31,6 +32,8 @@ const HeaderLogo = ({
|
|
|
31
32
|
const style = cx(NAME, baseStyle, className);
|
|
32
33
|
|
|
33
34
|
const linkStyle = css`
|
|
35
|
+
display: inline-flex;
|
|
36
|
+
flex: 0 0 auto;
|
|
34
37
|
color: inherit;
|
|
35
38
|
text-decoration: none;
|
|
36
39
|
|
|
@@ -13,8 +13,10 @@ const HeaderMenuContainer: React.FC<HeaderMenuContainerProps> = ({
|
|
|
13
13
|
children,
|
|
14
14
|
...props
|
|
15
15
|
}) => {
|
|
16
|
-
|
|
17
16
|
const baseStyle = css`
|
|
17
|
+
display: inline-flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
align-self: center;
|
|
18
20
|
width: fit-content;
|
|
19
21
|
margin-left: auto;
|
|
20
22
|
`;
|
|
@@ -20,18 +20,25 @@ const HeaderTitle = ({
|
|
|
20
20
|
const [theme] = useTheme();
|
|
21
21
|
|
|
22
22
|
const baseStyle = css`
|
|
23
|
+
display: block;
|
|
24
|
+
flex: 1 1 auto;
|
|
25
|
+
min-width: 0;
|
|
23
26
|
margin: 0 ${theme.margin.m4};
|
|
24
|
-
height: 29px;
|
|
25
27
|
color: ${theme.colour.text.brandPrimary};
|
|
26
28
|
font-family: ${theme.typography.heading.lg.md.fontFamily};
|
|
27
29
|
font-feature-settings: ${theme.typography.heading.lg.md.fontSettings};
|
|
28
30
|
font-size: ${theme.typography.heading.xs.md.fontSize}px;
|
|
29
31
|
font-weight: ${theme.typography.heading.xs.md.fontWeight};
|
|
32
|
+
line-height: ${theme.typography.heading.xs.md.lineHeight}px;
|
|
33
|
+
white-space: nowrap;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
text-overflow: ellipsis;
|
|
30
36
|
|
|
31
37
|
@media (min-width: ${theme.breakpoints.desktop}px) {
|
|
32
38
|
margin: 0 ${theme.margin.m20};
|
|
33
39
|
font-size: ${theme.typography.heading.lg.md.fontSize}px;
|
|
34
40
|
font-weight: ${theme.typography.heading.lg.md.fontWeight};
|
|
41
|
+
line-height: ${theme.typography.heading.lg.md.lineHeight}px;
|
|
35
42
|
}
|
|
36
43
|
`;
|
|
37
44
|
|
|
@@ -14,7 +14,7 @@ exports[`Header > snapshot: variant=avatar 1`] = `
|
|
|
14
14
|
/>
|
|
15
15
|
</div>
|
|
16
16
|
<svg
|
|
17
|
-
class="ucl-uikit-header__logo css-
|
|
17
|
+
class="ucl-uikit-header__logo css-1tplbsk"
|
|
18
18
|
data-test="ucl-uikit-header__logo"
|
|
19
19
|
fill="none"
|
|
20
20
|
focusable="false"
|
|
@@ -31,7 +31,7 @@ exports[`Header > snapshot: variant=avatar 1`] = `
|
|
|
31
31
|
/>
|
|
32
32
|
</svg>
|
|
33
33
|
<div
|
|
34
|
-
class="ucl-uikit-header__title css-
|
|
34
|
+
class="ucl-uikit-header__title css-1ck59nw"
|
|
35
35
|
data-test="ucl-uikit-header__title"
|
|
36
36
|
>
|
|
37
37
|
LIDS
|
|
@@ -53,7 +53,7 @@ exports[`Header > snapshot: variant=breadcrumbs 1`] = `
|
|
|
53
53
|
/>
|
|
54
54
|
</div>
|
|
55
55
|
<svg
|
|
56
|
-
class="ucl-uikit-header__logo css-
|
|
56
|
+
class="ucl-uikit-header__logo css-1tplbsk"
|
|
57
57
|
data-test="ucl-uikit-header__logo"
|
|
58
58
|
fill="none"
|
|
59
59
|
focusable="false"
|
|
@@ -70,7 +70,7 @@ exports[`Header > snapshot: variant=breadcrumbs 1`] = `
|
|
|
70
70
|
/>
|
|
71
71
|
</svg>
|
|
72
72
|
<div
|
|
73
|
-
class="ucl-uikit-header__title css-
|
|
73
|
+
class="ucl-uikit-header__title css-1ck59nw"
|
|
74
74
|
data-test="ucl-uikit-header__title"
|
|
75
75
|
>
|
|
76
76
|
LIDS
|
|
@@ -165,6 +165,7 @@ const {
|
|
|
165
165
|
Maximize,
|
|
166
166
|
Meh,
|
|
167
167
|
Menu,
|
|
168
|
+
Menu2,
|
|
168
169
|
MessageCircle,
|
|
169
170
|
MessageSquare,
|
|
170
171
|
MicOff,
|
|
@@ -482,6 +483,7 @@ const iconSvgs: Record<string, IconComponent> = {
|
|
|
482
483
|
Maximize,
|
|
483
484
|
Meh,
|
|
484
485
|
Menu,
|
|
486
|
+
Menu2,
|
|
485
487
|
MessageCircle,
|
|
486
488
|
MessageSquare,
|
|
487
489
|
MicOff,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { HTMLAttributes, memo } from 'react';
|
|
2
|
+
import { css, cx } from '@emotion/css';
|
|
3
|
+
import useTheme from '../../theme/useTheme';
|
|
4
|
+
|
|
5
|
+
export const NAME = 'ucl-uikit-simple-menu';
|
|
6
|
+
|
|
7
|
+
export interface SimpleMenuProps extends HTMLAttributes<HTMLUListElement> {
|
|
8
|
+
testId?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const SimpleMenu = ({
|
|
12
|
+
testId = NAME,
|
|
13
|
+
className,
|
|
14
|
+
children,
|
|
15
|
+
...props
|
|
16
|
+
}: SimpleMenuProps) => {
|
|
17
|
+
const [theme] = useTheme();
|
|
18
|
+
|
|
19
|
+
const baseStyle = css`
|
|
20
|
+
list-style: none;
|
|
21
|
+
margin: 0;
|
|
22
|
+
padding: ${theme.padding.p8};
|
|
23
|
+
border-radius: ${theme.radius.r4};
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
const style = cx(NAME, baseStyle, className);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<ul
|
|
30
|
+
role='menu'
|
|
31
|
+
data-testid={testId}
|
|
32
|
+
className={style}
|
|
33
|
+
{...props}
|
|
34
|
+
>
|
|
35
|
+
{children}
|
|
36
|
+
</ul>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default memo(SimpleMenu);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import SimpleMenu from '../SimpleMenu';
|
|
4
|
+
import { ThemeContextProvider } from '../../../theme/useTheme';
|
|
5
|
+
|
|
6
|
+
describe('SimpleMenu', () => {
|
|
7
|
+
test('renders with default test id', () => {
|
|
8
|
+
render(
|
|
9
|
+
<ThemeContextProvider>
|
|
10
|
+
<SimpleMenu>
|
|
11
|
+
<li role='menuitem'>Item one</li>
|
|
12
|
+
</SimpleMenu>
|
|
13
|
+
</ThemeContextProvider>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
expect(screen.getByTestId('ucl-uikit-simple-menu')).toBeInTheDocument();
|
|
17
|
+
expect(screen.getByRole('menu')).toBeInTheDocument();
|
|
18
|
+
expect(
|
|
19
|
+
screen.getByRole('menuitem', { name: 'Item one' })
|
|
20
|
+
).toBeInTheDocument();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('supports custom test id and className', () => {
|
|
24
|
+
render(
|
|
25
|
+
<ThemeContextProvider>
|
|
26
|
+
<SimpleMenu
|
|
27
|
+
testId='custom-simple-menu'
|
|
28
|
+
className='menu-override'
|
|
29
|
+
>
|
|
30
|
+
<li role='menuitem'>Item two</li>
|
|
31
|
+
</SimpleMenu>
|
|
32
|
+
</ThemeContextProvider>
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const menu = screen.getByTestId('custom-simple-menu');
|
|
36
|
+
expect(menu).toHaveClass('menu-override');
|
|
37
|
+
});
|
|
38
|
+
});
|
package/lib/components/index.ts
CHANGED
|
@@ -93,6 +93,9 @@ export type { MenuProps } from './Menu';
|
|
|
93
93
|
export { default as MenuNew } from './MenuNew';
|
|
94
94
|
export type { MenuProps as MenuNewProps } from './MenuNew';
|
|
95
95
|
|
|
96
|
+
export { default as SimpleMenu } from './SimpleMenu';
|
|
97
|
+
export type { SimpleMenuProps } from './SimpleMenu';
|
|
98
|
+
|
|
96
99
|
export { default as Select } from './Select';
|
|
97
100
|
export type { SelectProps } from './Select';
|
|
98
101
|
|
|
@@ -175,6 +178,19 @@ export type { CookieNoticeProps } from './CookieNotice';
|
|
|
175
178
|
export { default as Search } from './Search';
|
|
176
179
|
export type { SearchProps } from './Search';
|
|
177
180
|
|
|
181
|
+
export { default as Dropdown } from './Dropdown';
|
|
182
|
+
export type {
|
|
183
|
+
DropdownProps,
|
|
184
|
+
DropdownTriggerProps,
|
|
185
|
+
DropdownContentProps,
|
|
186
|
+
} from './Dropdown';
|
|
187
|
+
|
|
188
|
+
export { default as DropdownMenu } from './DropdownMenu';
|
|
189
|
+
export type { DropdownMenuProps } from './DropdownMenu';
|
|
190
|
+
|
|
191
|
+
export { default as DropdownUserMenu } from './DropdownUserMenu';
|
|
192
|
+
export type { DropdownUserMenuProps } from './DropdownUserMenu';
|
|
193
|
+
|
|
178
194
|
export { default as Layout } from './Layout';
|
|
179
195
|
export type { LayoutProps } from './Layout';
|
|
180
196
|
|
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.
|
|
5
|
+
"version": "0.24.1",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@emotion/css": "^11.13.5",
|
|
44
44
|
"@emotion/react": "^11.14.0",
|
|
45
45
|
"@floating-ui/react-dom": "^2.1.2",
|
|
46
|
-
"design-system-foundations-public": "0.0.
|
|
46
|
+
"design-system-foundations-public": "0.0.40"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"react": "^19.0.0",
|