@akinon/ui-menu 0.5.0 → 1.0.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.
@@ -1,5 +1,5 @@
1
- import type { MenuProps as AntMenuProps } from 'antd';
2
1
  import * as React from 'react';
3
- export type MenuProps = Omit<AntMenuProps, 'className' | 'classNames' | 'rootClassName' | 'styles' | 'prefixCls'>;
4
- export declare const Menu: ({ defaultOpenKeys, theme, ...restMenuProps }: MenuProps) => React.JSX.Element;
2
+ import type { IMenuProps } from './types';
3
+ export declare const Menu: ({ defaultOpenKeys, ...restMenuProps }: IMenuProps) => React.JSX.Element;
4
+ export type * from './types';
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,IAAI,YAAY,EAAE,MAAM,MAAM,CAAC;AAEtD,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,MAAM,SAAS,GAAG,IAAI,CAC1B,YAAY,EACZ,WAAW,GAAG,YAAY,GAAG,eAAe,GAAG,QAAQ,GAAG,WAAW,CACtE,CAAC;AAEF,eAAO,MAAM,IAAI,iDAId,SAAS,sBAQX,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,SAAS,CAAC;AAqBzD,eAAO,MAAM,IAAI,0CAA2C,UAAU,sBA0MrE,CAAC;AAEF,mBAAmB,SAAS,CAAC"}
package/dist/cjs/index.js CHANGED
@@ -12,10 +12,187 @@ var __rest = (this && this.__rest) || function (s, e) {
12
12
  };
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.Menu = void 0;
15
+ const icons_1 = require("@akinon/icons");
16
+ const ui_theme_1 = require("@akinon/ui-theme");
17
+ const cssinjs_1 = require("@ant-design/cssinjs");
15
18
  const antd_1 = require("antd");
16
19
  const React = require("react");
20
+ /**
21
+ * Menu component for navigation and hierarchical data presentation.
22
+ *
23
+ * The Menu component is designed to provide users with a structured navigation system or a display of hierarchical data.
24
+ * It supports different modes, themes, and configurations, enabling flexibility for various use cases.
25
+ *
26
+ * Features:
27
+ * - Horizontal, vertical, and inline modes for different layouts.
28
+ * - Light and dark themes for visual adaptability.
29
+ * - Submenu nesting with configurable triggers (click or hover).
30
+ * - Multi-select support and custom icons for menu items.
31
+ * - Grouping and dividers for better organization.
32
+ * - Fully customizable behavior with callbacks for item selection, submenu expansion, and more.
33
+ *
34
+ * The Menu component ensures a user-friendly and visually coherent way to navigate complex datasets or application structures.
35
+ */
36
+ const MAX_DEPTH = 3;
17
37
  const Menu = (_a) => {
18
- var { defaultOpenKeys, theme } = _a, restMenuProps = __rest(_a, ["defaultOpenKeys", "theme"]);
19
- return (React.createElement(antd_1.Menu, Object.assign({ defaultOpenKeys: defaultOpenKeys, theme: theme || 'dark' }, restMenuProps)));
38
+ var { defaultOpenKeys } = _a, restMenuProps = __rest(_a, ["defaultOpenKeys"]);
39
+ const { getPrefixCls, theme } = React.useContext(antd_1.ConfigProvider.ConfigContext);
40
+ const { token, hashId } = (0, ui_theme_1.useToken)();
41
+ const menuToken = token.Menu;
42
+ const prefixWithoutHash = `${getPrefixCls()}-menu`;
43
+ const prefixClsWithoutHash = `.${prefixWithoutHash}`;
44
+ const customTokens = theme.CustomTokens || {};
45
+ const useStyle = (0, cssinjs_1.useStyleRegister)({
46
+ token: token,
47
+ path: ['Menu'],
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ theme: theme
50
+ }, () => {
51
+ const prefixCls = `:where(.${hashId})${prefixClsWithoutHash}`;
52
+ return {
53
+ [prefixCls]: {
54
+ minWidth: menuToken.minWidth,
55
+ [`&${prefixClsWithoutHash}-inline-collapsed`]: {
56
+ minWidth: customTokens.others.valueUnset
57
+ },
58
+ [`&${prefixClsWithoutHash}-dark`]: {
59
+ [`${prefixClsWithoutHash}-title, ${prefixClsWithoutHash}-submenu-title`]: {
60
+ paddingInlineEnd: '1rem',
61
+ color: menuToken.mainMenuColor,
62
+ fontWeight: customTokens.typography.fontWeightBold
63
+ },
64
+ [`${prefixClsWithoutHash}-item`]: {
65
+ color: menuToken.mainMenuColor,
66
+ fontWeight: customTokens.typography.fontWeightBold
67
+ },
68
+ [`${prefixClsWithoutHash}-sub`]: {
69
+ fontWeight: customTokens.typography.fontWeightMedium,
70
+ [`${prefixClsWithoutHash}-item`]: {
71
+ color: menuToken.subMenuTitleColor,
72
+ fontWeight: customTokens.typography.fontWeightMedium
73
+ },
74
+ [`${prefixClsWithoutHash}-submenu`]: {
75
+ [`${prefixClsWithoutHash}-submenu-title`]: {
76
+ color: menuToken.subMenuTitleColor,
77
+ ['svg']: {
78
+ position: customTokens.layout.positionAbsolute,
79
+ marginLeft: menuToken.subMenuTitleIconMargin,
80
+ insetInlineEnd: customTokens.others.valueUnset
81
+ }
82
+ },
83
+ [`${prefixClsWithoutHash}-sub`]: {
84
+ [`${prefixClsWithoutHash}-item-selected`]: {
85
+ [`&${prefixClsWithoutHash}-item-only-child`]: {
86
+ ['&::after']: {
87
+ background: menuToken.subMenuItemSelectedChildColor
88
+ }
89
+ }
90
+ },
91
+ [`${prefixClsWithoutHash}-item-only-child`]: {
92
+ ['&::after']: {
93
+ width: menuToken.childMenuItemSelectedChildWidth,
94
+ height: menuToken.childMenuItemSelectedChildHeight,
95
+ borderRadius: menuToken.childMenuItemSelectedChildRadius,
96
+ marginLeft: menuToken.childMenuItemSelectedChildMargin,
97
+ opacity: customTokens.others.opacityFull,
98
+ insetBlock: customTokens.others.valueUnset,
99
+ insetInlineEnd: customTokens.others.valueUnset,
100
+ transform: customTokens.layout.displayNone,
101
+ background: menuToken.childMenuItemSelectedChildBgColor
102
+ }
103
+ }
104
+ }
105
+ },
106
+ [`${prefixClsWithoutHash}-item-only-child`]: {
107
+ ['&::after']: {
108
+ width: menuToken.subMenuOnlyChildSize,
109
+ height: menuToken.subMenuOnlyChildSize,
110
+ borderRadius: customTokens.sizing.valueHalf,
111
+ background: menuToken.subMenuOnlyChildBgColor,
112
+ marginLeft: menuToken.subMenuOnlyChildMargin,
113
+ opacity: customTokens.others.opacityFull,
114
+ insetBlock: customTokens.others.valueUnset,
115
+ insetInlineEnd: customTokens.others.valueUnset,
116
+ transform: customTokens.layout.displayNone
117
+ }
118
+ }
119
+ }
120
+ },
121
+ [`&${prefixClsWithoutHash}-light`]: {
122
+ background: menuToken.lightItemBg,
123
+ [`${prefixClsWithoutHash}-submenu`]: {
124
+ [`svg:first-child`]: {
125
+ color: menuToken.lightItemMainIconColor,
126
+ fill: menuToken.lightItemMainIconColor
127
+ },
128
+ ['&-title']: {
129
+ color: menuToken.lightItemMainTitle
130
+ },
131
+ [`${prefixClsWithoutHash}-sub`]: {
132
+ background: menuToken.LightSubItemBg,
133
+ [`${prefixClsWithoutHash}-submenu`]: {
134
+ [`${prefixClsWithoutHash}-submenu-title`]: {
135
+ color: menuToken.lightSubTitleColor,
136
+ ['i']: {
137
+ background: menuToken.lightSubIconColor
138
+ }
139
+ },
140
+ [`${prefixClsWithoutHash}-sub`]: {
141
+ [`${prefixClsWithoutHash}-item-selected`]: {
142
+ [`&${prefixClsWithoutHash}-item-only-child`]: {
143
+ background: menuToken.lightchildItemBg,
144
+ color: menuToken.lightChildItemColor,
145
+ ['&::after']: {
146
+ background: menuToken.lightChildItemColor
147
+ }
148
+ }
149
+ },
150
+ [`${prefixClsWithoutHash}-item-only-child`]: {
151
+ color: menuToken.lightSubTitleColor,
152
+ ['&::after']: {
153
+ background: menuToken.lightSubTitleColor
154
+ }
155
+ }
156
+ }
157
+ },
158
+ [`${prefixClsWithoutHash}-item-only-child`]: {
159
+ color: menuToken.lightSubTitleColor,
160
+ fontWeight: customTokens.typography.fontWeightMedium,
161
+ ['&::after']: {
162
+ background: menuToken.lightSubTitleColor
163
+ }
164
+ }
165
+ }
166
+ }
167
+ }
168
+ },
169
+ [prefixClsWithoutHash]: {}
170
+ };
171
+ });
172
+ const processMenuItems = (items, depth = 0) => {
173
+ if (!items || depth >= MAX_DEPTH)
174
+ return items;
175
+ return items.map(item => {
176
+ const processedItem = Object.assign({}, item);
177
+ // Process icon if present
178
+ if ('icon' in processedItem && processedItem.icon) {
179
+ processedItem.icon = (React.createElement(icons_1.Icon, { icon: processedItem.icon, size: depth === 0 ? 18 : 10 }));
180
+ }
181
+ // Process submenu items recursively
182
+ if ('children' in processedItem &&
183
+ Array.isArray(processedItem.children)) {
184
+ processedItem.children = processMenuItems(processedItem.children, depth + 1);
185
+ }
186
+ return processedItem;
187
+ });
188
+ };
189
+ const processedItems = processMenuItems(restMenuProps.items);
190
+ const expandIcon = (props) => {
191
+ return (React.createElement(icons_1.Icon, { icon: "chevron_down", size: 14, style: {
192
+ transform: props.isOpen ? 'rotate(180deg)' : 'rotate(0deg)',
193
+ transition: 'transform 0.3s ease-in-out'
194
+ } }));
195
+ };
196
+ return useStyle(React.createElement(antd_1.Menu, Object.assign({ defaultOpenKeys: defaultOpenKeys }, restMenuProps, { items: processedItems, expandIcon: expandIcon })));
20
197
  };
21
198
  exports.Menu = Menu;
@@ -0,0 +1,296 @@
1
+ import { IconName } from '@akinon/icons';
2
+ import { DO_NOT_USE_OR_YOU_WILL_BE_FIRED_INTERNAL_STYLE } from '@akinon/ui-theme';
3
+ import { ReactNode } from 'react';
4
+
5
+ export type TMenuExpandIcon =
6
+ | ReactNode
7
+ | ((props: SubMenuProps & { isSubMenu: boolean }) => ReactNode);
8
+
9
+ export type TMenuMode = 'horizontal' | 'vertical' | 'inline';
10
+
11
+ export type TMenuTheme = 'light' | 'dark';
12
+
13
+ export type TMenuTriggerSubMenuAction = 'click' | 'hover';
14
+
15
+ export type TMenuOnClick = ({
16
+ item,
17
+ key,
18
+ keyPath,
19
+ domEvent
20
+ }: {
21
+ item: ReactNode;
22
+ key: string;
23
+ keyPath: string[];
24
+ domEvent: React.MouseEvent;
25
+ }) => void;
26
+
27
+ export type TMenuOnDeSelect = ({
28
+ item,
29
+ key,
30
+ keyPath,
31
+ selectedKeys,
32
+ domEvent
33
+ }: {
34
+ item: ReactNode;
35
+ key: string;
36
+ keyPath: string[];
37
+ selectedKeys: string[];
38
+ domEvent: React.MouseEvent;
39
+ }) => void;
40
+
41
+ export type TMenuOnOpenChange = (openKeys: string[]) => void;
42
+
43
+ export type TMenuOnSelect = ({
44
+ item,
45
+ key,
46
+ keyPath,
47
+ selectedKeys,
48
+ domEvent
49
+ }: {
50
+ item: ReactNode;
51
+ key: string;
52
+ keyPath: string[];
53
+ selectedKeys: string[];
54
+ domEvent: React.MouseEvent;
55
+ }) => void;
56
+
57
+ export type TMenuItemType =
58
+ | IMenuItemType
59
+ | ISubMenuType
60
+ | IMenuItemGroupType
61
+ | IMenuDividerType;
62
+
63
+ export interface IMenuProps {
64
+ /**
65
+ * Array with the keys of default opened sub menus
66
+ */
67
+ defaultOpenKeys?: string[];
68
+
69
+ /**
70
+ * Array with the keys of default selected menu items
71
+ */
72
+ defaultSelectedKeys?: string[];
73
+
74
+ /**
75
+ * Custom expand icon of submenu
76
+ */
77
+ expandIcon?: TMenuExpandIcon;
78
+
79
+ /**
80
+ * Render submenu into DOM before it becomes visible
81
+ */
82
+ forceSubMenuRender?: boolean;
83
+
84
+ /**
85
+ * Specifies the collapsed status when menu is in inline mode
86
+ */
87
+ inlineCollapsed?: boolean;
88
+
89
+ /**
90
+ * Indent (in pixels) of inline menu items on each level
91
+ */
92
+ inlineIndent?: number;
93
+
94
+ /**
95
+ * Menu item content
96
+ */
97
+ items?: TMenuItemType[];
98
+
99
+ /**
100
+ * Type of menu
101
+ */
102
+ mode?: TMenuMode;
103
+
104
+ /**
105
+ * Allows selection of multiple items
106
+ */
107
+ multiple?: boolean;
108
+
109
+ /**
110
+ * Array with the keys of currently opened sub-menus
111
+ */
112
+ openKeys?: string[];
113
+
114
+ /**
115
+ * Customized the ellipsis icon when menu is collapsed horizontally
116
+ */
117
+ overflowedIndicator?: ReactNode;
118
+
119
+ /**
120
+ * Allows selecting menu items
121
+ */
122
+ selectable?: boolean;
123
+
124
+ /**
125
+ * Array with the keys of currently selected menu items
126
+ */
127
+ selectedKeys?: string[];
128
+
129
+ /**
130
+ * Delay time to hide submenu when mouse leaves (in seconds)
131
+ */
132
+ subMenuCloseDelay?: number;
133
+
134
+ /**
135
+ * Delay time to show submenu when mouse enters, (in seconds)
136
+ */
137
+ subMenuOpenDelay?: number;
138
+
139
+ /**
140
+ * Color theme of the menu
141
+ */
142
+ theme?: TMenuTheme;
143
+
144
+ /**
145
+ * Which action can trigger submenu open/close
146
+ */
147
+ triggerSubMenuAction?: TMenuTriggerSubMenuAction;
148
+
149
+ /**
150
+ * Called when a menu item is clicked
151
+ */
152
+ onClick?: TMenuOnClick;
153
+
154
+ /**
155
+ * Called when a menu item is deselected (multiple mode only)
156
+ */
157
+ onDeselect?: TMenuOnDeSelect;
158
+
159
+ /**
160
+ * Called when sub-menus are opened or closed
161
+ */
162
+ onOpenChange?: TMenuOnOpenChange;
163
+
164
+ /**
165
+ * Called when a menu item is selected
166
+ */
167
+ onSelect?: TMenuOnSelect;
168
+
169
+ /**
170
+ * Never use this prop. Akinon design system does not allow custom styles.
171
+ * @ignore
172
+ */
173
+ style?: DO_NOT_USE_OR_YOU_WILL_BE_FIRED_INTERNAL_STYLE;
174
+ }
175
+
176
+ export interface IMenuItemType {
177
+ /**
178
+ * Display the danger style
179
+ * @default false
180
+ */
181
+ danger?: boolean;
182
+
183
+ /**
184
+ * Whether menu item is disabled
185
+ * @default false
186
+ */
187
+ disabled?: boolean;
188
+
189
+ /**
190
+ * The extra of the menu item
191
+ */
192
+ extra?: ReactNode;
193
+
194
+ /**
195
+ * The icon of the menu item
196
+ */
197
+ icon?: IconName;
198
+
199
+ /**
200
+ * Unique ID of the menu item
201
+ */
202
+ key?: string;
203
+
204
+ /**
205
+ * Menu label
206
+ */
207
+ label?: ReactNode;
208
+
209
+ /**
210
+ * Set display title for collapsed item
211
+ */
212
+ title?: string;
213
+
214
+ /**
215
+ * Sub-menus or sub-menu items
216
+ */
217
+ children?: TMenuItemType[];
218
+ }
219
+
220
+ export interface ISubMenuType {
221
+ /**
222
+ * Sub-menus or sub-menu items
223
+ */
224
+ children?: TMenuItemType[];
225
+
226
+ /**
227
+ * Whether sub-menu is disabled
228
+ * @default false
229
+ */
230
+ disabled?: boolean;
231
+
232
+ /**
233
+ * Icon of sub menu
234
+ */
235
+ icon?: IconName;
236
+
237
+ /**
238
+ * Unique ID of the sub-menu
239
+ */
240
+ key?: string;
241
+
242
+ /**
243
+ * Menu label
244
+ */
245
+ label?: ReactNode;
246
+
247
+ /**
248
+ * Sub-menu class name, not working when mode="inline"
249
+ */
250
+ popupClassName?: string;
251
+
252
+ /**
253
+ * Sub-menu offset, not working when mode="inline"
254
+ */
255
+ popupOffset?: [number, number];
256
+
257
+ /**
258
+ * Color theme of the SubMenu (inherits from Menu by default)
259
+ */
260
+ theme?: TMenuTheme;
261
+
262
+ /**
263
+ * Callback executed when the sub-menu title is clicked
264
+ */
265
+ onTitleClick?: (params: { key: string; domEvent: React.MouseEvent }) => void;
266
+ }
267
+
268
+ export interface IMenuItemGroupType {
269
+ /**
270
+ * Must have | Define type as group to make as group
271
+ */
272
+ type: 'group';
273
+
274
+ /**
275
+ * Sub-menu items
276
+ */
277
+ children?: IMenuItemType[];
278
+
279
+ /**
280
+ * The title of the group
281
+ */
282
+ label?: ReactNode;
283
+ }
284
+
285
+ export interface IMenuDividerType {
286
+ /**
287
+ * Must have | Need define the type as divider
288
+ */
289
+ type: 'divider';
290
+
291
+ /**
292
+ * Whether line is dashed
293
+ * @default false
294
+ */
295
+ dashed?: boolean;
296
+ }
@@ -1,5 +1,5 @@
1
- import type { MenuProps as AntMenuProps } from 'antd';
2
1
  import * as React from 'react';
3
- export type MenuProps = Omit<AntMenuProps, 'className' | 'classNames' | 'rootClassName' | 'styles' | 'prefixCls'>;
4
- export declare const Menu: ({ defaultOpenKeys, theme, ...restMenuProps }: MenuProps) => React.JSX.Element;
2
+ import type { IMenuProps } from './types';
3
+ export declare const Menu: ({ defaultOpenKeys, ...restMenuProps }: IMenuProps) => React.JSX.Element;
4
+ export type * from './types';
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,IAAI,YAAY,EAAE,MAAM,MAAM,CAAC;AAEtD,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,MAAM,SAAS,GAAG,IAAI,CAC1B,YAAY,EACZ,WAAW,GAAG,YAAY,GAAG,eAAe,GAAG,QAAQ,GAAG,WAAW,CACtE,CAAC;AAEF,eAAO,MAAM,IAAI,iDAId,SAAS,sBAQX,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,SAAS,CAAC;AAqBzD,eAAO,MAAM,IAAI,0CAA2C,UAAU,sBA0MrE,CAAC;AAEF,mBAAmB,SAAS,CAAC"}
package/dist/esm/index.js CHANGED
@@ -9,9 +9,186 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import { Menu as AntMenu } from 'antd';
12
+ import { Icon } from '@akinon/icons';
13
+ import { useToken } from '@akinon/ui-theme';
14
+ import { useStyleRegister } from '@ant-design/cssinjs';
15
+ import { ConfigProvider, Menu as AntMenu } from 'antd';
13
16
  import * as React from 'react';
17
+ /**
18
+ * Menu component for navigation and hierarchical data presentation.
19
+ *
20
+ * The Menu component is designed to provide users with a structured navigation system or a display of hierarchical data.
21
+ * It supports different modes, themes, and configurations, enabling flexibility for various use cases.
22
+ *
23
+ * Features:
24
+ * - Horizontal, vertical, and inline modes for different layouts.
25
+ * - Light and dark themes for visual adaptability.
26
+ * - Submenu nesting with configurable triggers (click or hover).
27
+ * - Multi-select support and custom icons for menu items.
28
+ * - Grouping and dividers for better organization.
29
+ * - Fully customizable behavior with callbacks for item selection, submenu expansion, and more.
30
+ *
31
+ * The Menu component ensures a user-friendly and visually coherent way to navigate complex datasets or application structures.
32
+ */
33
+ const MAX_DEPTH = 3;
14
34
  export const Menu = (_a) => {
15
- var { defaultOpenKeys, theme } = _a, restMenuProps = __rest(_a, ["defaultOpenKeys", "theme"]);
16
- return (React.createElement(AntMenu, Object.assign({ defaultOpenKeys: defaultOpenKeys, theme: theme || 'dark' }, restMenuProps)));
35
+ var { defaultOpenKeys } = _a, restMenuProps = __rest(_a, ["defaultOpenKeys"]);
36
+ const { getPrefixCls, theme } = React.useContext(ConfigProvider.ConfigContext);
37
+ const { token, hashId } = useToken();
38
+ const menuToken = token.Menu;
39
+ const prefixWithoutHash = `${getPrefixCls()}-menu`;
40
+ const prefixClsWithoutHash = `.${prefixWithoutHash}`;
41
+ const customTokens = theme.CustomTokens || {};
42
+ const useStyle = useStyleRegister({
43
+ token: token,
44
+ path: ['Menu'],
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
+ theme: theme
47
+ }, () => {
48
+ const prefixCls = `:where(.${hashId})${prefixClsWithoutHash}`;
49
+ return {
50
+ [prefixCls]: {
51
+ minWidth: menuToken.minWidth,
52
+ [`&${prefixClsWithoutHash}-inline-collapsed`]: {
53
+ minWidth: customTokens.others.valueUnset
54
+ },
55
+ [`&${prefixClsWithoutHash}-dark`]: {
56
+ [`${prefixClsWithoutHash}-title, ${prefixClsWithoutHash}-submenu-title`]: {
57
+ paddingInlineEnd: '1rem',
58
+ color: menuToken.mainMenuColor,
59
+ fontWeight: customTokens.typography.fontWeightBold
60
+ },
61
+ [`${prefixClsWithoutHash}-item`]: {
62
+ color: menuToken.mainMenuColor,
63
+ fontWeight: customTokens.typography.fontWeightBold
64
+ },
65
+ [`${prefixClsWithoutHash}-sub`]: {
66
+ fontWeight: customTokens.typography.fontWeightMedium,
67
+ [`${prefixClsWithoutHash}-item`]: {
68
+ color: menuToken.subMenuTitleColor,
69
+ fontWeight: customTokens.typography.fontWeightMedium
70
+ },
71
+ [`${prefixClsWithoutHash}-submenu`]: {
72
+ [`${prefixClsWithoutHash}-submenu-title`]: {
73
+ color: menuToken.subMenuTitleColor,
74
+ ['svg']: {
75
+ position: customTokens.layout.positionAbsolute,
76
+ marginLeft: menuToken.subMenuTitleIconMargin,
77
+ insetInlineEnd: customTokens.others.valueUnset
78
+ }
79
+ },
80
+ [`${prefixClsWithoutHash}-sub`]: {
81
+ [`${prefixClsWithoutHash}-item-selected`]: {
82
+ [`&${prefixClsWithoutHash}-item-only-child`]: {
83
+ ['&::after']: {
84
+ background: menuToken.subMenuItemSelectedChildColor
85
+ }
86
+ }
87
+ },
88
+ [`${prefixClsWithoutHash}-item-only-child`]: {
89
+ ['&::after']: {
90
+ width: menuToken.childMenuItemSelectedChildWidth,
91
+ height: menuToken.childMenuItemSelectedChildHeight,
92
+ borderRadius: menuToken.childMenuItemSelectedChildRadius,
93
+ marginLeft: menuToken.childMenuItemSelectedChildMargin,
94
+ opacity: customTokens.others.opacityFull,
95
+ insetBlock: customTokens.others.valueUnset,
96
+ insetInlineEnd: customTokens.others.valueUnset,
97
+ transform: customTokens.layout.displayNone,
98
+ background: menuToken.childMenuItemSelectedChildBgColor
99
+ }
100
+ }
101
+ }
102
+ },
103
+ [`${prefixClsWithoutHash}-item-only-child`]: {
104
+ ['&::after']: {
105
+ width: menuToken.subMenuOnlyChildSize,
106
+ height: menuToken.subMenuOnlyChildSize,
107
+ borderRadius: customTokens.sizing.valueHalf,
108
+ background: menuToken.subMenuOnlyChildBgColor,
109
+ marginLeft: menuToken.subMenuOnlyChildMargin,
110
+ opacity: customTokens.others.opacityFull,
111
+ insetBlock: customTokens.others.valueUnset,
112
+ insetInlineEnd: customTokens.others.valueUnset,
113
+ transform: customTokens.layout.displayNone
114
+ }
115
+ }
116
+ }
117
+ },
118
+ [`&${prefixClsWithoutHash}-light`]: {
119
+ background: menuToken.lightItemBg,
120
+ [`${prefixClsWithoutHash}-submenu`]: {
121
+ [`svg:first-child`]: {
122
+ color: menuToken.lightItemMainIconColor,
123
+ fill: menuToken.lightItemMainIconColor
124
+ },
125
+ ['&-title']: {
126
+ color: menuToken.lightItemMainTitle
127
+ },
128
+ [`${prefixClsWithoutHash}-sub`]: {
129
+ background: menuToken.LightSubItemBg,
130
+ [`${prefixClsWithoutHash}-submenu`]: {
131
+ [`${prefixClsWithoutHash}-submenu-title`]: {
132
+ color: menuToken.lightSubTitleColor,
133
+ ['i']: {
134
+ background: menuToken.lightSubIconColor
135
+ }
136
+ },
137
+ [`${prefixClsWithoutHash}-sub`]: {
138
+ [`${prefixClsWithoutHash}-item-selected`]: {
139
+ [`&${prefixClsWithoutHash}-item-only-child`]: {
140
+ background: menuToken.lightchildItemBg,
141
+ color: menuToken.lightChildItemColor,
142
+ ['&::after']: {
143
+ background: menuToken.lightChildItemColor
144
+ }
145
+ }
146
+ },
147
+ [`${prefixClsWithoutHash}-item-only-child`]: {
148
+ color: menuToken.lightSubTitleColor,
149
+ ['&::after']: {
150
+ background: menuToken.lightSubTitleColor
151
+ }
152
+ }
153
+ }
154
+ },
155
+ [`${prefixClsWithoutHash}-item-only-child`]: {
156
+ color: menuToken.lightSubTitleColor,
157
+ fontWeight: customTokens.typography.fontWeightMedium,
158
+ ['&::after']: {
159
+ background: menuToken.lightSubTitleColor
160
+ }
161
+ }
162
+ }
163
+ }
164
+ }
165
+ },
166
+ [prefixClsWithoutHash]: {}
167
+ };
168
+ });
169
+ const processMenuItems = (items, depth = 0) => {
170
+ if (!items || depth >= MAX_DEPTH)
171
+ return items;
172
+ return items.map(item => {
173
+ const processedItem = Object.assign({}, item);
174
+ // Process icon if present
175
+ if ('icon' in processedItem && processedItem.icon) {
176
+ processedItem.icon = (React.createElement(Icon, { icon: processedItem.icon, size: depth === 0 ? 18 : 10 }));
177
+ }
178
+ // Process submenu items recursively
179
+ if ('children' in processedItem &&
180
+ Array.isArray(processedItem.children)) {
181
+ processedItem.children = processMenuItems(processedItem.children, depth + 1);
182
+ }
183
+ return processedItem;
184
+ });
185
+ };
186
+ const processedItems = processMenuItems(restMenuProps.items);
187
+ const expandIcon = (props) => {
188
+ return (React.createElement(Icon, { icon: "chevron_down", size: 14, style: {
189
+ transform: props.isOpen ? 'rotate(180deg)' : 'rotate(0deg)',
190
+ transition: 'transform 0.3s ease-in-out'
191
+ } }));
192
+ };
193
+ return useStyle(React.createElement(AntMenu, Object.assign({ defaultOpenKeys: defaultOpenKeys }, restMenuProps, { items: processedItems, expandIcon: expandIcon })));
17
194
  };
@@ -0,0 +1,296 @@
1
+ import { IconName } from '@akinon/icons';
2
+ import { DO_NOT_USE_OR_YOU_WILL_BE_FIRED_INTERNAL_STYLE } from '@akinon/ui-theme';
3
+ import { ReactNode } from 'react';
4
+
5
+ export type TMenuExpandIcon =
6
+ | ReactNode
7
+ | ((props: SubMenuProps & { isSubMenu: boolean }) => ReactNode);
8
+
9
+ export type TMenuMode = 'horizontal' | 'vertical' | 'inline';
10
+
11
+ export type TMenuTheme = 'light' | 'dark';
12
+
13
+ export type TMenuTriggerSubMenuAction = 'click' | 'hover';
14
+
15
+ export type TMenuOnClick = ({
16
+ item,
17
+ key,
18
+ keyPath,
19
+ domEvent
20
+ }: {
21
+ item: ReactNode;
22
+ key: string;
23
+ keyPath: string[];
24
+ domEvent: React.MouseEvent;
25
+ }) => void;
26
+
27
+ export type TMenuOnDeSelect = ({
28
+ item,
29
+ key,
30
+ keyPath,
31
+ selectedKeys,
32
+ domEvent
33
+ }: {
34
+ item: ReactNode;
35
+ key: string;
36
+ keyPath: string[];
37
+ selectedKeys: string[];
38
+ domEvent: React.MouseEvent;
39
+ }) => void;
40
+
41
+ export type TMenuOnOpenChange = (openKeys: string[]) => void;
42
+
43
+ export type TMenuOnSelect = ({
44
+ item,
45
+ key,
46
+ keyPath,
47
+ selectedKeys,
48
+ domEvent
49
+ }: {
50
+ item: ReactNode;
51
+ key: string;
52
+ keyPath: string[];
53
+ selectedKeys: string[];
54
+ domEvent: React.MouseEvent;
55
+ }) => void;
56
+
57
+ export type TMenuItemType =
58
+ | IMenuItemType
59
+ | ISubMenuType
60
+ | IMenuItemGroupType
61
+ | IMenuDividerType;
62
+
63
+ export interface IMenuProps {
64
+ /**
65
+ * Array with the keys of default opened sub menus
66
+ */
67
+ defaultOpenKeys?: string[];
68
+
69
+ /**
70
+ * Array with the keys of default selected menu items
71
+ */
72
+ defaultSelectedKeys?: string[];
73
+
74
+ /**
75
+ * Custom expand icon of submenu
76
+ */
77
+ expandIcon?: TMenuExpandIcon;
78
+
79
+ /**
80
+ * Render submenu into DOM before it becomes visible
81
+ */
82
+ forceSubMenuRender?: boolean;
83
+
84
+ /**
85
+ * Specifies the collapsed status when menu is in inline mode
86
+ */
87
+ inlineCollapsed?: boolean;
88
+
89
+ /**
90
+ * Indent (in pixels) of inline menu items on each level
91
+ */
92
+ inlineIndent?: number;
93
+
94
+ /**
95
+ * Menu item content
96
+ */
97
+ items?: TMenuItemType[];
98
+
99
+ /**
100
+ * Type of menu
101
+ */
102
+ mode?: TMenuMode;
103
+
104
+ /**
105
+ * Allows selection of multiple items
106
+ */
107
+ multiple?: boolean;
108
+
109
+ /**
110
+ * Array with the keys of currently opened sub-menus
111
+ */
112
+ openKeys?: string[];
113
+
114
+ /**
115
+ * Customized the ellipsis icon when menu is collapsed horizontally
116
+ */
117
+ overflowedIndicator?: ReactNode;
118
+
119
+ /**
120
+ * Allows selecting menu items
121
+ */
122
+ selectable?: boolean;
123
+
124
+ /**
125
+ * Array with the keys of currently selected menu items
126
+ */
127
+ selectedKeys?: string[];
128
+
129
+ /**
130
+ * Delay time to hide submenu when mouse leaves (in seconds)
131
+ */
132
+ subMenuCloseDelay?: number;
133
+
134
+ /**
135
+ * Delay time to show submenu when mouse enters, (in seconds)
136
+ */
137
+ subMenuOpenDelay?: number;
138
+
139
+ /**
140
+ * Color theme of the menu
141
+ */
142
+ theme?: TMenuTheme;
143
+
144
+ /**
145
+ * Which action can trigger submenu open/close
146
+ */
147
+ triggerSubMenuAction?: TMenuTriggerSubMenuAction;
148
+
149
+ /**
150
+ * Called when a menu item is clicked
151
+ */
152
+ onClick?: TMenuOnClick;
153
+
154
+ /**
155
+ * Called when a menu item is deselected (multiple mode only)
156
+ */
157
+ onDeselect?: TMenuOnDeSelect;
158
+
159
+ /**
160
+ * Called when sub-menus are opened or closed
161
+ */
162
+ onOpenChange?: TMenuOnOpenChange;
163
+
164
+ /**
165
+ * Called when a menu item is selected
166
+ */
167
+ onSelect?: TMenuOnSelect;
168
+
169
+ /**
170
+ * Never use this prop. Akinon design system does not allow custom styles.
171
+ * @ignore
172
+ */
173
+ style?: DO_NOT_USE_OR_YOU_WILL_BE_FIRED_INTERNAL_STYLE;
174
+ }
175
+
176
+ export interface IMenuItemType {
177
+ /**
178
+ * Display the danger style
179
+ * @default false
180
+ */
181
+ danger?: boolean;
182
+
183
+ /**
184
+ * Whether menu item is disabled
185
+ * @default false
186
+ */
187
+ disabled?: boolean;
188
+
189
+ /**
190
+ * The extra of the menu item
191
+ */
192
+ extra?: ReactNode;
193
+
194
+ /**
195
+ * The icon of the menu item
196
+ */
197
+ icon?: IconName;
198
+
199
+ /**
200
+ * Unique ID of the menu item
201
+ */
202
+ key?: string;
203
+
204
+ /**
205
+ * Menu label
206
+ */
207
+ label?: ReactNode;
208
+
209
+ /**
210
+ * Set display title for collapsed item
211
+ */
212
+ title?: string;
213
+
214
+ /**
215
+ * Sub-menus or sub-menu items
216
+ */
217
+ children?: TMenuItemType[];
218
+ }
219
+
220
+ export interface ISubMenuType {
221
+ /**
222
+ * Sub-menus or sub-menu items
223
+ */
224
+ children?: TMenuItemType[];
225
+
226
+ /**
227
+ * Whether sub-menu is disabled
228
+ * @default false
229
+ */
230
+ disabled?: boolean;
231
+
232
+ /**
233
+ * Icon of sub menu
234
+ */
235
+ icon?: IconName;
236
+
237
+ /**
238
+ * Unique ID of the sub-menu
239
+ */
240
+ key?: string;
241
+
242
+ /**
243
+ * Menu label
244
+ */
245
+ label?: ReactNode;
246
+
247
+ /**
248
+ * Sub-menu class name, not working when mode="inline"
249
+ */
250
+ popupClassName?: string;
251
+
252
+ /**
253
+ * Sub-menu offset, not working when mode="inline"
254
+ */
255
+ popupOffset?: [number, number];
256
+
257
+ /**
258
+ * Color theme of the SubMenu (inherits from Menu by default)
259
+ */
260
+ theme?: TMenuTheme;
261
+
262
+ /**
263
+ * Callback executed when the sub-menu title is clicked
264
+ */
265
+ onTitleClick?: (params: { key: string; domEvent: React.MouseEvent }) => void;
266
+ }
267
+
268
+ export interface IMenuItemGroupType {
269
+ /**
270
+ * Must have | Define type as group to make as group
271
+ */
272
+ type: 'group';
273
+
274
+ /**
275
+ * Sub-menu items
276
+ */
277
+ children?: IMenuItemType[];
278
+
279
+ /**
280
+ * The title of the group
281
+ */
282
+ label?: ReactNode;
283
+ }
284
+
285
+ export interface IMenuDividerType {
286
+ /**
287
+ * Must have | Need define the type as divider
288
+ */
289
+ type: 'divider';
290
+
291
+ /**
292
+ * Whether line is dashed
293
+ * @default false
294
+ */
295
+ dashed?: boolean;
296
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/ui-menu",
3
- "version": "0.5.0",
3
+ "version": "1.0.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "dist/esm/index.js",
@@ -9,14 +9,16 @@
9
9
  "dist"
10
10
  ],
11
11
  "dependencies": {
12
- "antd": "5.17.0"
12
+ "antd": "5.22.6",
13
+ "@akinon/icons": "1.0.1",
14
+ "@akinon/ui-theme": "1.0.1"
13
15
  },
14
16
  "devDependencies": {
15
17
  "clean-package": "2.2.0",
16
18
  "copyfiles": "^2.4.1",
17
19
  "rimraf": "^5.0.5",
18
- "typescript": "^5.2.2",
19
- "@akinon/typescript-config": "0.4.0"
20
+ "typescript": "*",
21
+ "@akinon/typescript-config": "1.0.1"
20
22
  },
21
23
  "peerDependencies": {
22
24
  "react": ">=18",