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,171 @@
|
|
|
1
|
+
import { HTMLAttributes, Ref, memo } from 'react';
|
|
2
|
+
import { css, cx } from '@emotion/css';
|
|
3
|
+
import useTheme from '../../theme/useTheme';
|
|
4
|
+
|
|
5
|
+
export const NAME = 'ucl-uikit-text';
|
|
6
|
+
|
|
7
|
+
export type TextLevel =
|
|
8
|
+
| 'lg'
|
|
9
|
+
| 'lg-medium'
|
|
10
|
+
| 'lg-semibold'
|
|
11
|
+
| 'lg-bold'
|
|
12
|
+
| 'md'
|
|
13
|
+
| 'md-medium'
|
|
14
|
+
| 'md-semibold'
|
|
15
|
+
| 'md-medium-numeric'
|
|
16
|
+
| 'sm'
|
|
17
|
+
| 'sm-medium'
|
|
18
|
+
| 'sm-semibold'
|
|
19
|
+
| 'xs'
|
|
20
|
+
| 'xs-medium';
|
|
21
|
+
|
|
22
|
+
export interface TextProps extends HTMLAttributes<HTMLSpanElement> {
|
|
23
|
+
level?: TextLevel;
|
|
24
|
+
testId?: string;
|
|
25
|
+
ref?: Ref<HTMLSpanElement>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const Text = ({
|
|
29
|
+
level = 'md',
|
|
30
|
+
testId = NAME,
|
|
31
|
+
className,
|
|
32
|
+
ref,
|
|
33
|
+
...props
|
|
34
|
+
}: TextProps) => {
|
|
35
|
+
const [theme] = useTheme();
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
typography: {
|
|
39
|
+
body: {
|
|
40
|
+
lg,
|
|
41
|
+
lgMedium,
|
|
42
|
+
lgSemibold,
|
|
43
|
+
lgBold,
|
|
44
|
+
md,
|
|
45
|
+
mdMedium,
|
|
46
|
+
mdSemibold,
|
|
47
|
+
mdMediumNumeric,
|
|
48
|
+
sm,
|
|
49
|
+
smMedium,
|
|
50
|
+
smSemibold,
|
|
51
|
+
xs,
|
|
52
|
+
xsMedium,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
} = theme;
|
|
56
|
+
|
|
57
|
+
const baseStyle = css`
|
|
58
|
+
font-family: ${md.fontFamily};
|
|
59
|
+
font-feature-settings: ${md.fontSettings};
|
|
60
|
+
color: ${theme.colour.text.default};
|
|
61
|
+
margin: 0;
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
const lgStyle = css`
|
|
65
|
+
font-size: ${lg.fontSize}px;
|
|
66
|
+
font-weight: ${lg.fontWeight};
|
|
67
|
+
line-height: ${lg.lineHeight}%;
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
const lgMediumStyle = css`
|
|
71
|
+
font-size: ${lgMedium.fontSize}px;
|
|
72
|
+
font-weight: ${lgMedium.fontWeight};
|
|
73
|
+
line-height: ${lgMedium.lineHeight}%;
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
const lgSemiboldStyle = css`
|
|
77
|
+
font-size: ${lgSemibold.fontSize}px;
|
|
78
|
+
font-weight: ${lgSemibold.fontWeight};
|
|
79
|
+
line-height: ${lgSemibold.lineHeight}%;
|
|
80
|
+
`;
|
|
81
|
+
|
|
82
|
+
const lgBoldStyle = css`
|
|
83
|
+
font-size: ${lgBold.fontSize}px;
|
|
84
|
+
font-weight: ${lgBold.fontWeight};
|
|
85
|
+
line-height: ${lgBold.lineHeight}%;
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
const mdStyle = css`
|
|
89
|
+
font-size: ${md.fontSize}px;
|
|
90
|
+
font-weight: ${md.fontWeight};
|
|
91
|
+
line-height: ${md.lineHeight}%;
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
const mdMediumStyle = css`
|
|
95
|
+
font-size: ${mdMedium.fontSize}px;
|
|
96
|
+
font-weight: ${mdMedium.fontWeight};
|
|
97
|
+
line-height: ${mdMedium.lineHeight}%;
|
|
98
|
+
`;
|
|
99
|
+
|
|
100
|
+
const mdSemiboldStyle = css`
|
|
101
|
+
font-size: ${mdSemibold.fontSize}px;
|
|
102
|
+
font-weight: ${mdSemibold.fontWeight};
|
|
103
|
+
line-height: ${mdSemibold.lineHeight}%;
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
const mdMediumNumericStyle = css`
|
|
107
|
+
font-size: ${mdMediumNumeric.fontSize}px;
|
|
108
|
+
font-weight: ${mdMediumNumeric.fontWeight};
|
|
109
|
+
line-height: ${mdMediumNumeric.lineHeight}%;
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
const smStyle = css`
|
|
113
|
+
font-size: ${sm.fontSize}px;
|
|
114
|
+
font-weight: ${sm.fontWeight};
|
|
115
|
+
line-height: ${sm.lineHeight}%;
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
const smMediumStyle = css`
|
|
119
|
+
font-size: ${smMedium.fontSize}px;
|
|
120
|
+
font-weight: ${smMedium.fontWeight};
|
|
121
|
+
line-height: ${smMedium.lineHeight}%;
|
|
122
|
+
`;
|
|
123
|
+
|
|
124
|
+
const smSemiboldStyle = css`
|
|
125
|
+
font-size: ${smSemibold.fontSize}px;
|
|
126
|
+
font-weight: ${smSemibold.fontWeight};
|
|
127
|
+
line-height: ${smSemibold.lineHeight}%;
|
|
128
|
+
`;
|
|
129
|
+
|
|
130
|
+
const xsStyle = css`
|
|
131
|
+
font-size: ${xs.fontSize}px;
|
|
132
|
+
font-weight: ${xs.fontWeight};
|
|
133
|
+
line-height: ${xs.lineHeight}%;
|
|
134
|
+
`;
|
|
135
|
+
|
|
136
|
+
const xsMediumStyle = css`
|
|
137
|
+
font-size: ${xsMedium.fontSize}px;
|
|
138
|
+
font-weight: ${xsMedium.fontWeight};
|
|
139
|
+
line-height: ${xsMedium.lineHeight}%;
|
|
140
|
+
`;
|
|
141
|
+
|
|
142
|
+
const style = cx(
|
|
143
|
+
NAME,
|
|
144
|
+
baseStyle,
|
|
145
|
+
level === 'lg' && lgStyle,
|
|
146
|
+
level === 'lg-medium' && lgMediumStyle,
|
|
147
|
+
level === 'lg-semibold' && lgSemiboldStyle,
|
|
148
|
+
level === 'lg-bold' && lgBoldStyle,
|
|
149
|
+
level === 'md' && mdStyle,
|
|
150
|
+
level === 'md-medium' && mdMediumStyle,
|
|
151
|
+
level === 'md-semibold' && mdSemiboldStyle,
|
|
152
|
+
level === 'md-medium-numeric' && mdMediumNumericStyle,
|
|
153
|
+
level === 'sm' && smStyle,
|
|
154
|
+
level === 'sm-medium' && smMediumStyle,
|
|
155
|
+
level === 'sm-semibold' && smSemiboldStyle,
|
|
156
|
+
level === 'xs' && xsStyle,
|
|
157
|
+
level === 'xs-medium' && xsMediumStyle,
|
|
158
|
+
className
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<span
|
|
163
|
+
ref={ref}
|
|
164
|
+
className={style}
|
|
165
|
+
data-testid={testId}
|
|
166
|
+
{...props}
|
|
167
|
+
/>
|
|
168
|
+
);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export default memo(Text);
|
package/lib/components/index.ts
CHANGED
|
@@ -52,10 +52,8 @@ export type { ParagraphProps } from './Paragraph';
|
|
|
52
52
|
export { default as ParagraphNew } from './Paragraph';
|
|
53
53
|
export type { ParagraphProps as ParagraphNewProps } from './Paragraph';
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
export { default as Text } from './Paragraph';
|
|
58
|
-
export type { TextProps } from './Paragraph';
|
|
55
|
+
export { default as Text } from './Text';
|
|
56
|
+
export type { TextProps, TextLevel } from './Text';
|
|
59
57
|
|
|
60
58
|
export { default as Modal } from './Modal';
|
|
61
59
|
export type { ModalProps } from './Modal';
|
|
@@ -93,9 +91,6 @@ export type { MenuProps } from './Menu';
|
|
|
93
91
|
export { default as MenuNew } from './MenuNew';
|
|
94
92
|
export type { MenuProps as MenuNewProps } from './MenuNew';
|
|
95
93
|
|
|
96
|
-
export { default as SimpleMenu } from './SimpleMenu';
|
|
97
|
-
export type { SimpleMenuProps } from './SimpleMenu';
|
|
98
|
-
|
|
99
94
|
export { default as Select } from './Select';
|
|
100
95
|
export type { SelectProps } from './Select';
|
|
101
96
|
|
package/package.json
CHANGED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { default as React } from 'react';
|
|
2
|
-
interface IContext {
|
|
3
|
-
isOpen: boolean;
|
|
4
|
-
toggleMenu: () => void;
|
|
5
|
-
triggerRef: React.RefObject<HTMLButtonElement | null>;
|
|
6
|
-
contentRef: React.RefObject<HTMLDivElement | null>;
|
|
7
|
-
}
|
|
8
|
-
export declare const AppMenuContext: React.Context<IContext>;
|
|
9
|
-
export declare const useAppMenu: () => IContext;
|
|
10
|
-
declare const MenuProvider: ({ defaultOpen, children, }: {
|
|
11
|
-
defaultOpen?: boolean;
|
|
12
|
-
children: React.ReactNode;
|
|
13
|
-
}) => import("react/jsx-runtime").JSX.Element;
|
|
14
|
-
export default MenuProvider;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { default as React, HTMLAttributes } from 'react';
|
|
2
|
-
export declare const NAME = "ucl-uikit-menu__content";
|
|
3
|
-
export interface MenuContentProps extends HTMLAttributes<HTMLUListElement> {
|
|
4
|
-
title?: string;
|
|
5
|
-
testId?: string;
|
|
6
|
-
position?: 'left' | 'right';
|
|
7
|
-
}
|
|
8
|
-
declare const _default: React.NamedExoticComponent<MenuContentProps>;
|
|
9
|
-
export default _default;
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { default as React, LiHTMLAttributes } from 'react';
|
|
2
|
-
export declare const NAME = "ucl-uikit-menu__item";
|
|
3
|
-
export interface MenuItemProps extends LiHTMLAttributes<HTMLLIElement> {
|
|
4
|
-
testId?: string;
|
|
5
|
-
icon?: React.ReactNode;
|
|
6
|
-
secondary?: boolean;
|
|
7
|
-
externalLink?: boolean;
|
|
8
|
-
}
|
|
9
|
-
declare const _default: React.NamedExoticComponent<MenuItemProps>;
|
|
10
|
-
export default _default;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { default as React, HTMLAttributes } from 'react';
|
|
2
|
-
export declare const NAME = "ucl-uikit-menu__section";
|
|
3
|
-
export interface MenuSectionProps extends HTMLAttributes<HTMLLIElement> {
|
|
4
|
-
testId?: string;
|
|
5
|
-
}
|
|
6
|
-
declare const MenuSection: React.FC<MenuSectionProps>;
|
|
7
|
-
export default MenuSection;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { IconButtonProps } from '../../IconButton';
|
|
2
|
-
export declare const NAME = "ucl-uikit-menu__trigger--button";
|
|
3
|
-
export declare const DEFAULT_ARIA_LABEL = "Menu button";
|
|
4
|
-
export interface ButtonMenuTriggerProps extends IconButtonProps {
|
|
5
|
-
ariaLabel?: string;
|
|
6
|
-
}
|
|
7
|
-
declare const _default: import('react').MemoExoticComponent<({ id, ariaLabel, className, ...props }: ButtonMenuTriggerProps) => import("react/jsx-runtime").JSX.Element>;
|
|
8
|
-
export default _default;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { IconButtonProps } from '../../IconButton';
|
|
2
|
-
export declare const NAME = "ucl-uikit-menu__trigger--icon";
|
|
3
|
-
export declare const DEFAULT_ARIA_LABEL = "Menu button";
|
|
4
|
-
export interface IconMenuTriggerProps extends IconButtonProps {
|
|
5
|
-
ariaLabel?: string;
|
|
6
|
-
}
|
|
7
|
-
declare const _default: import('react').MemoExoticComponent<({ id, ariaLabel, className, ...props }: IconMenuTriggerProps) => import("react/jsx-runtime").JSX.Element>;
|
|
8
|
-
export default _default;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { HTMLAttributes } from 'react';
|
|
2
|
-
export declare const NAME = "ucl-uikit-simple-menu";
|
|
3
|
-
export interface SimpleMenuProps extends HTMLAttributes<HTMLUListElement> {
|
|
4
|
-
testId?: string;
|
|
5
|
-
}
|
|
6
|
-
declare const _default: import('react').MemoExoticComponent<({ testId, className, children, ...props }: SimpleMenuProps) => import("react/jsx-runtime").JSX.Element>;
|
|
7
|
-
export default _default;
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
createContext,
|
|
3
|
-
useContext,
|
|
4
|
-
useState,
|
|
5
|
-
useRef,
|
|
6
|
-
useEffect,
|
|
7
|
-
} from 'react';
|
|
8
|
-
import useMediaQuery from '../../hooks/useMediaQuery';
|
|
9
|
-
import { useTheme } from '../../theme';
|
|
10
|
-
|
|
11
|
-
interface IContext {
|
|
12
|
-
isOpen: boolean;
|
|
13
|
-
toggleMenu: () => void;
|
|
14
|
-
triggerRef: React.RefObject<HTMLButtonElement | null>;
|
|
15
|
-
contentRef: React.RefObject<HTMLDivElement | null>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const AppMenuContext = createContext({} as IContext);
|
|
19
|
-
|
|
20
|
-
export const useAppMenu = () => useContext(AppMenuContext);
|
|
21
|
-
|
|
22
|
-
const MenuProvider = ({
|
|
23
|
-
defaultOpen = false,
|
|
24
|
-
children,
|
|
25
|
-
}: {
|
|
26
|
-
defaultOpen?: boolean;
|
|
27
|
-
children: React.ReactNode;
|
|
28
|
-
}) => {
|
|
29
|
-
const [isOpen, setIsOpen] = useState(defaultOpen);
|
|
30
|
-
const [theme] = useTheme();
|
|
31
|
-
const isDesktop = useMediaQuery(
|
|
32
|
-
`(min-width: ${theme.breakpoints.desktop}px)`
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
36
|
-
const contentRef = useRef<HTMLDivElement>(null);
|
|
37
|
-
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
const originalStyle = document.body.style.overflow;
|
|
40
|
-
if (isOpen && !isDesktop) {
|
|
41
|
-
// Prevent background scrolling when menu is open and on mobile
|
|
42
|
-
document.body.style.overflow = 'hidden';
|
|
43
|
-
} else {
|
|
44
|
-
document.body.style.overflow = originalStyle;
|
|
45
|
-
}
|
|
46
|
-
return () => {
|
|
47
|
-
document.body.style.overflow = originalStyle;
|
|
48
|
-
};
|
|
49
|
-
}, [isOpen, isDesktop]);
|
|
50
|
-
|
|
51
|
-
useEffect(() => {
|
|
52
|
-
const closeMenuOutsideClick = (event: MouseEvent) => {
|
|
53
|
-
if (
|
|
54
|
-
triggerRef.current &&
|
|
55
|
-
!triggerRef.current.contains(event.target as Node) &&
|
|
56
|
-
contentRef.current &&
|
|
57
|
-
!contentRef.current.contains(event.target as Node)
|
|
58
|
-
) {
|
|
59
|
-
setIsOpen(false);
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const handleKeyDown = (event: KeyboardEvent) => {
|
|
64
|
-
if (event.key === 'Escape') {
|
|
65
|
-
setIsOpen(false);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (!isOpen || !contentRef.current) return;
|
|
69
|
-
|
|
70
|
-
const focusableElements = Array.from(
|
|
71
|
-
contentRef.current.querySelectorAll(
|
|
72
|
-
'[role="menuitem"], [aria-label="Close menu"]'
|
|
73
|
-
)
|
|
74
|
-
).filter((el) => {
|
|
75
|
-
const element = el as HTMLElement;
|
|
76
|
-
return (
|
|
77
|
-
element.getAttribute('aria-disabled') !== 'true' &&
|
|
78
|
-
element.tabIndex >= 0 &&
|
|
79
|
-
element.offsetParent !== null
|
|
80
|
-
);
|
|
81
|
-
}) as HTMLElement[];
|
|
82
|
-
|
|
83
|
-
if (focusableElements.length === 0) return;
|
|
84
|
-
|
|
85
|
-
const firstElement = focusableElements[0];
|
|
86
|
-
const lastElement = focusableElements[focusableElements.length - 1];
|
|
87
|
-
|
|
88
|
-
const currentIndex = focusableElements.indexOf(
|
|
89
|
-
document.activeElement as HTMLElement
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
if (event.key === 'Tab') {
|
|
93
|
-
// Loop focus on Tab/Shift+Tab
|
|
94
|
-
if (event.shiftKey && document.activeElement === firstElement) {
|
|
95
|
-
lastElement.focus();
|
|
96
|
-
event.preventDefault();
|
|
97
|
-
} else if (!event.shiftKey && document.activeElement === lastElement) {
|
|
98
|
-
firstElement.focus();
|
|
99
|
-
event.preventDefault();
|
|
100
|
-
}
|
|
101
|
-
} else if (event.key === 'ArrowDown') {
|
|
102
|
-
// Move focus to next item
|
|
103
|
-
if (currentIndex !== -1) {
|
|
104
|
-
const nextIndex = (currentIndex + 1) % focusableElements.length;
|
|
105
|
-
focusableElements[nextIndex].focus();
|
|
106
|
-
event.preventDefault();
|
|
107
|
-
} else {
|
|
108
|
-
firstElement.focus();
|
|
109
|
-
event.preventDefault();
|
|
110
|
-
}
|
|
111
|
-
} else if (event.key === 'ArrowUp') {
|
|
112
|
-
// Move focus to previous item
|
|
113
|
-
if (currentIndex !== -1) {
|
|
114
|
-
const prevIndex =
|
|
115
|
-
(currentIndex - 1 + focusableElements.length) %
|
|
116
|
-
focusableElements.length;
|
|
117
|
-
focusableElements[prevIndex].focus();
|
|
118
|
-
event.preventDefault();
|
|
119
|
-
} else {
|
|
120
|
-
lastElement.focus();
|
|
121
|
-
event.preventDefault();
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
document.addEventListener('mousedown', closeMenuOutsideClick);
|
|
127
|
-
document.addEventListener('keydown', handleKeyDown);
|
|
128
|
-
|
|
129
|
-
return () => {
|
|
130
|
-
document.removeEventListener('mousedown', closeMenuOutsideClick);
|
|
131
|
-
document.removeEventListener('keydown', handleKeyDown);
|
|
132
|
-
};
|
|
133
|
-
}, [isOpen]);
|
|
134
|
-
|
|
135
|
-
const toggleMenu = () => setIsOpen((prevOpen) => !prevOpen);
|
|
136
|
-
|
|
137
|
-
const value = {
|
|
138
|
-
isOpen,
|
|
139
|
-
toggleMenu,
|
|
140
|
-
triggerRef,
|
|
141
|
-
contentRef,
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
return (
|
|
145
|
-
<AppMenuContext.Provider value={value}>{children}</AppMenuContext.Provider>
|
|
146
|
-
);
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
export default MenuProvider;
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import React, { HTMLAttributes, memo, useEffect } from 'react';
|
|
2
|
-
import { createPortal } from 'react-dom';
|
|
3
|
-
import { css, cx } from '@emotion/css';
|
|
4
|
-
import useTheme from '../../theme/useTheme';
|
|
5
|
-
import useMediaQuery from '../../hooks/useMediaQuery';
|
|
6
|
-
import Icon from '../Icon';
|
|
7
|
-
import IconButton from '../IconButton';
|
|
8
|
-
import { useAppMenu } from './Menu.context';
|
|
9
|
-
|
|
10
|
-
export const NAME = 'ucl-uikit-menu__content';
|
|
11
|
-
|
|
12
|
-
export interface MenuContentProps extends HTMLAttributes<HTMLUListElement> {
|
|
13
|
-
title?: string;
|
|
14
|
-
testId?: string;
|
|
15
|
-
position?: 'left' | 'right';
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const MenuContent: React.FC<MenuContentProps> = ({
|
|
19
|
-
title,
|
|
20
|
-
testId = NAME,
|
|
21
|
-
position = 'right',
|
|
22
|
-
children,
|
|
23
|
-
className,
|
|
24
|
-
...props
|
|
25
|
-
}) => {
|
|
26
|
-
const { isOpen, contentRef, toggleMenu } = useAppMenu();
|
|
27
|
-
|
|
28
|
-
const [theme] = useTheme();
|
|
29
|
-
|
|
30
|
-
const { padding } = theme;
|
|
31
|
-
|
|
32
|
-
const isDesktop = useMediaQuery(
|
|
33
|
-
`(min-width: ${theme.breakpoints.desktop}px)`
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
const baseStyle = css`
|
|
37
|
-
display: none;
|
|
38
|
-
z-index: 100;
|
|
39
|
-
${position === 'left' ? 'left: 0;' : 'right: 0;'}
|
|
40
|
-
white-space: nowrap;
|
|
41
|
-
background-color: ${theme.color.neutral.white};
|
|
42
|
-
color: ${theme.color.text.primary};
|
|
43
|
-
|
|
44
|
-
position: fixed;
|
|
45
|
-
height: 100vh;
|
|
46
|
-
border: none;
|
|
47
|
-
box-shadow: none;
|
|
48
|
-
padding: 0;
|
|
49
|
-
margin: 0;
|
|
50
|
-
overflow-y: auto;
|
|
51
|
-
min-width: 100%;
|
|
52
|
-
|
|
53
|
-
a {
|
|
54
|
-
height: auto;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
@media (min-width: ${theme.breakpoints.desktop}px) {
|
|
58
|
-
position: absolute;
|
|
59
|
-
height: auto;
|
|
60
|
-
min-width: 600px;
|
|
61
|
-
border: ${theme.border.b1} solid ${theme.color.neutral.grey20};
|
|
62
|
-
box-shadow: ${theme.boxShadow.y1};
|
|
63
|
-
}
|
|
64
|
-
`;
|
|
65
|
-
|
|
66
|
-
const openStyle = css`
|
|
67
|
-
display: block;
|
|
68
|
-
`;
|
|
69
|
-
|
|
70
|
-
const style = cx(NAME, baseStyle, isOpen && openStyle, className);
|
|
71
|
-
|
|
72
|
-
const headerStyle = css`
|
|
73
|
-
display: flex;
|
|
74
|
-
justify-content: space-between;
|
|
75
|
-
padding: ${padding.p64} ${padding.p24} ${padding.p16};
|
|
76
|
-
|
|
77
|
-
@media (min-width: ${theme.breakpoints.desktop}px) {
|
|
78
|
-
padding: ${padding.p32} ${padding.p48} ${padding.p16} ${padding.p64};
|
|
79
|
-
justify-content: flex-end;
|
|
80
|
-
}
|
|
81
|
-
`;
|
|
82
|
-
|
|
83
|
-
const titleStyle = css`
|
|
84
|
-
@media (min-width: ${theme.breakpoints.desktop}px) {
|
|
85
|
-
display: none;
|
|
86
|
-
}
|
|
87
|
-
`;
|
|
88
|
-
|
|
89
|
-
const bodyStyle = css`
|
|
90
|
-
padding: 0 ${padding.p24};
|
|
91
|
-
list-style: none;
|
|
92
|
-
|
|
93
|
-
@media (min-width: ${theme.breakpoints.desktop}px) {
|
|
94
|
-
margin: 0 ${padding.p64};
|
|
95
|
-
padding: 0;
|
|
96
|
-
}
|
|
97
|
-
`;
|
|
98
|
-
|
|
99
|
-
useEffect(() => {
|
|
100
|
-
const rootDiv = document.getElementById('root');
|
|
101
|
-
if (rootDiv) {
|
|
102
|
-
if (!isDesktop && isOpen) {
|
|
103
|
-
rootDiv.style.display = 'none';
|
|
104
|
-
} else {
|
|
105
|
-
rootDiv.style.display = '';
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}, [isOpen, isDesktop]);
|
|
109
|
-
|
|
110
|
-
const menuContent = (
|
|
111
|
-
<div
|
|
112
|
-
ref={contentRef}
|
|
113
|
-
className={style}
|
|
114
|
-
>
|
|
115
|
-
<header className={headerStyle}>
|
|
116
|
-
<h3 className={titleStyle}>{title}</h3>
|
|
117
|
-
<IconButton
|
|
118
|
-
onClick={toggleMenu}
|
|
119
|
-
aria-label='Close menu'
|
|
120
|
-
>
|
|
121
|
-
<Icon.X size={40} />
|
|
122
|
-
</IconButton>
|
|
123
|
-
</header>
|
|
124
|
-
<ul
|
|
125
|
-
role='menu'
|
|
126
|
-
aria-labelledby='app-menu-button'
|
|
127
|
-
className={bodyStyle}
|
|
128
|
-
data-testid={testId}
|
|
129
|
-
tabIndex={-1}
|
|
130
|
-
{...props}
|
|
131
|
-
>
|
|
132
|
-
{children}
|
|
133
|
-
</ul>
|
|
134
|
-
</div>
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
return !isDesktop ? createPortal(menuContent, document.body) : menuContent;
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
export default memo(MenuContent);
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import React, { LiHTMLAttributes, memo } from 'react';
|
|
2
|
-
import { css, cx } from '@emotion/css';
|
|
3
|
-
import useTheme from '../../theme/useTheme';
|
|
4
|
-
import Icon from '../Icon/Icon';
|
|
5
|
-
|
|
6
|
-
export const NAME = 'ucl-uikit-menu__item';
|
|
7
|
-
|
|
8
|
-
export interface MenuItemProps extends LiHTMLAttributes<HTMLLIElement> {
|
|
9
|
-
testId?: string;
|
|
10
|
-
icon?: React.ReactNode;
|
|
11
|
-
secondary?: boolean;
|
|
12
|
-
externalLink?: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const MenuItem: React.FC<MenuItemProps> = ({
|
|
16
|
-
testId = NAME,
|
|
17
|
-
icon,
|
|
18
|
-
children,
|
|
19
|
-
className,
|
|
20
|
-
onClick,
|
|
21
|
-
secondary,
|
|
22
|
-
externalLink,
|
|
23
|
-
...props
|
|
24
|
-
}) => {
|
|
25
|
-
const isClickable = !!onClick;
|
|
26
|
-
const [theme] = useTheme();
|
|
27
|
-
|
|
28
|
-
const baseStyle = css`
|
|
29
|
-
height: 40px;
|
|
30
|
-
margin-bottom: ${theme.margin.m16};
|
|
31
|
-
display: flex;
|
|
32
|
-
align-items: center;
|
|
33
|
-
font-family: ${theme.font.family.primary};
|
|
34
|
-
font-size: ${theme.font.size.f16};
|
|
35
|
-
font-weight: ${secondary
|
|
36
|
-
? `${theme.font.weight.medium}`
|
|
37
|
-
: `${theme.font.weight.bold}`};
|
|
38
|
-
color: ${theme.color.text.secondary};
|
|
39
|
-
`;
|
|
40
|
-
|
|
41
|
-
const clickableMenuItemStyle = css`
|
|
42
|
-
color: ${secondary
|
|
43
|
-
? `${theme.color.text.secondary}`
|
|
44
|
-
: `${theme.color.text.primary}`};
|
|
45
|
-
cursor: pointer;
|
|
46
|
-
&:hover {
|
|
47
|
-
background-color: ${theme.color.interaction.blue5};
|
|
48
|
-
}
|
|
49
|
-
&:focus-visible {
|
|
50
|
-
outline: none;
|
|
51
|
-
box-shadow: ${theme.boxShadow.focus};
|
|
52
|
-
z-index: 1;
|
|
53
|
-
}
|
|
54
|
-
`;
|
|
55
|
-
|
|
56
|
-
const iconContainerStyle = css`
|
|
57
|
-
display: inline-flex;
|
|
58
|
-
align-items: center;
|
|
59
|
-
margin-right: ${theme.margin.m32};
|
|
60
|
-
`;
|
|
61
|
-
|
|
62
|
-
const style = cx(
|
|
63
|
-
NAME,
|
|
64
|
-
baseStyle,
|
|
65
|
-
isClickable && clickableMenuItemStyle,
|
|
66
|
-
className
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
const content = (
|
|
70
|
-
<>
|
|
71
|
-
{React.isValidElement(icon) && (
|
|
72
|
-
<span className={iconContainerStyle}>{icon}</span>
|
|
73
|
-
)}
|
|
74
|
-
{children}
|
|
75
|
-
</>
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
return (
|
|
79
|
-
<li
|
|
80
|
-
tabIndex={isClickable ? 0 : -1}
|
|
81
|
-
role='menuitem'
|
|
82
|
-
aria-disabled={!isClickable}
|
|
83
|
-
className={style}
|
|
84
|
-
data-testid={testId}
|
|
85
|
-
onClick={onClick}
|
|
86
|
-
{...props}
|
|
87
|
-
>
|
|
88
|
-
{content}
|
|
89
|
-
{externalLink && (
|
|
90
|
-
<Icon.ExternalLink
|
|
91
|
-
size={16}
|
|
92
|
-
className={css`
|
|
93
|
-
margin-left: 8px;
|
|
94
|
-
`}
|
|
95
|
-
/>
|
|
96
|
-
)}
|
|
97
|
-
</li>
|
|
98
|
-
);
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
export default memo(MenuItem);
|