uikit-react-public 0.22.2 → 0.24.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.
Files changed (50) hide show
  1. package/dist/components/Avatar/Avatar.d.ts +1 -1
  2. package/dist/components/Dropdown/Dropdown.context.d.ts +15 -0
  3. package/dist/components/Dropdown/Dropdown.d.ts +15 -0
  4. package/dist/components/Dropdown/Dropdown.stories.d.ts +36 -0
  5. package/dist/components/Dropdown/DropdownContent.d.ts +8 -0
  6. package/dist/components/Dropdown/DropdownTrigger.d.ts +7 -0
  7. package/dist/components/Dropdown/__tests__/Dropdown.test.d.ts +1 -0
  8. package/dist/components/Dropdown/index.d.ts +4 -0
  9. package/dist/components/DropdownMenu/DropdownMenu.d.ts +13 -0
  10. package/dist/components/DropdownMenu/__tests__/DropdownMenu.test.d.ts +1 -0
  11. package/dist/components/DropdownMenu/index.d.ts +2 -0
  12. package/dist/components/DropdownUserMenu/DropdownUserMenu.d.ts +19 -0
  13. package/dist/components/DropdownUserMenu/__tests__/DropdownUserMenu.test.d.ts +1 -0
  14. package/dist/components/DropdownUserMenu/index.d.ts +2 -0
  15. package/dist/components/HeaderNew/Header.d.ts +2 -0
  16. package/dist/components/HeaderNew/HeaderItem.d.ts +7 -0
  17. package/dist/components/HeaderNew/index.d.ts +1 -0
  18. package/dist/components/SimpleMenu/SimpleMenu.d.ts +7 -0
  19. package/dist/components/SimpleMenu/__tests__/SimpleMenu.test.d.ts +1 -0
  20. package/dist/components/SimpleMenu/index.d.ts +2 -0
  21. package/dist/components/index.d.ts +8 -0
  22. package/dist/index.js +3948 -3609
  23. package/lib/components/Avatar/Avatar.tsx +1 -1
  24. package/lib/components/Dropdown/Dropdown.context.tsx +62 -0
  25. package/lib/components/Dropdown/Dropdown.stories.tsx +184 -0
  26. package/lib/components/Dropdown/Dropdown.tsx +91 -0
  27. package/lib/components/Dropdown/DropdownContent.tsx +70 -0
  28. package/lib/components/Dropdown/DropdownTrigger.tsx +62 -0
  29. package/lib/components/Dropdown/__tests__/Dropdown.test.tsx +63 -0
  30. package/lib/components/Dropdown/index.ts +4 -0
  31. package/lib/components/DropdownMenu/DropdownMenu.tsx +82 -0
  32. package/lib/components/DropdownMenu/__tests__/DropdownMenu.test.tsx +96 -0
  33. package/lib/components/DropdownMenu/index.ts +2 -0
  34. package/lib/components/DropdownUserMenu/DropdownUserMenu.tsx +124 -0
  35. package/lib/components/DropdownUserMenu/__tests__/DropdownUserMenu.test.tsx +97 -0
  36. package/lib/components/DropdownUserMenu/index.ts +2 -0
  37. package/lib/components/HeaderNew/Header.tsx +5 -2
  38. package/lib/components/HeaderNew/HeaderBorder.tsx +1 -0
  39. package/lib/components/HeaderNew/HeaderItem.tsx +43 -0
  40. package/lib/components/HeaderNew/HeaderLogo.tsx +3 -0
  41. package/lib/components/HeaderNew/HeaderMenuContainer.tsx +3 -1
  42. package/lib/components/HeaderNew/HeaderTitle.tsx +8 -1
  43. package/lib/components/HeaderNew/__tests__/__snapshots__/Header.test.tsx.snap +8 -8
  44. package/lib/components/HeaderNew/index.ts +1 -0
  45. package/lib/components/Icon/svgImports.ts +2 -0
  46. package/lib/components/SimpleMenu/SimpleMenu.tsx +40 -0
  47. package/lib/components/SimpleMenu/__tests__/SimpleMenu.test.tsx +38 -0
  48. package/lib/components/SimpleMenu/index.ts +2 -0
  49. package/lib/components/index.ts +16 -0
  50. 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,2 @@
1
+ export { default } from './DropdownMenu';
2
+ export type { DropdownMenuProps } from './DropdownMenu';
@@ -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
+ });
@@ -0,0 +1,2 @@
1
+ export { default } from './DropdownUserMenu';
2
+ export type { DropdownUserMenuProps } from './DropdownUserMenu';
@@ -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';
@@ -47,11 +48,11 @@ const Header = ({
47
48
  height: ${HEADER_MOBILE_HEIGHT_PX}px;
48
49
  box-sizing: border-box;
49
50
  padding: 0 ${theme.padding.p16} 0 ${theme.padding.p40};
50
- border-bottom: ${theme.colour.border.subtle};
51
51
 
52
52
  @media (min-width: ${theme.breakpoints.desktop}px) {
53
53
  height: ${HEADER_TABLET_HEIGHT_PX}px;
54
- padding: 0 ${theme.padding.p64};
54
+ padding: 0 ${theme.padding.p48} 0 ${theme.padding.p64};
55
+ border-bottom: 1px solid ${theme.colour.border.default};
55
56
  }
56
57
  `;
57
58
 
@@ -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;
@@ -24,6 +24,7 @@ const HeaderBorder = ({
24
24
 
25
25
  @media (min-width: ${theme.breakpoints.desktop}px) {
26
26
  left: 0;
27
+ height: calc(100% + 1px);
27
28
  }
28
29
  `;
29
30
 
@@ -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
 
@@ -2,11 +2,11 @@
2
2
 
3
3
  exports[`Header > snapshot: variant=avatar 1`] = `
4
4
  <header
5
- class="ucl-uikit-header css-7smbab"
5
+ class="ucl-uikit-header css-13623bd"
6
6
  data-testid="ucl-uikit-header"
7
7
  >
8
8
  <div
9
- class="ucl-uikit-header__border css-miu54s"
9
+ class="ucl-uikit-header__border css-5jjnsi"
10
10
  data-test="ucl-uikit-header__border"
11
11
  >
12
12
  <div
@@ -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-h3fm3u"
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-118cw9t"
34
+ class="ucl-uikit-header__title css-1ck59nw"
35
35
  data-test="ucl-uikit-header__title"
36
36
  >
37
37
  LIDS
@@ -41,11 +41,11 @@ exports[`Header > snapshot: variant=avatar 1`] = `
41
41
 
42
42
  exports[`Header > snapshot: variant=breadcrumbs 1`] = `
43
43
  <header
44
- class="ucl-uikit-header css-7smbab"
44
+ class="ucl-uikit-header css-13623bd"
45
45
  data-testid="ucl-uikit-header"
46
46
  >
47
47
  <div
48
- class="ucl-uikit-header__border css-miu54s"
48
+ class="ucl-uikit-header__border css-5jjnsi"
49
49
  data-test="ucl-uikit-header__border"
50
50
  >
51
51
  <div
@@ -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-h3fm3u"
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-118cw9t"
73
+ class="ucl-uikit-header__title css-1ck59nw"
74
74
  data-test="ucl-uikit-header__title"
75
75
  >
76
76
  LIDS
@@ -5,3 +5,4 @@ export {
5
5
  DEFAULT_Z_INDEX,
6
6
  } from './constants';
7
7
  export type { HeaderProps } from './Header';
8
+ export type { HeaderItemProps } from './HeaderItem';
@@ -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
+ });
@@ -0,0 +1,2 @@
1
+ export { default } from './SimpleMenu';
2
+ export type { SimpleMenuProps } from './SimpleMenu';
@@ -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.22.2",
5
+ "version": "0.24.2",
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.37"
46
+ "design-system-foundations-public": "0.0.40"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "react": "^19.0.0",