@dxos/react-ui-menu 0.8.4-main.69d29f4 → 0.8.4-main.6fa680abb7
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/lib/browser/index.mjs +320 -145
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +320 -145
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/builder.d.ts +6 -0
- package/dist/types/src/builder.d.ts.map +1 -1
- package/dist/types/src/components/DropdownMenu.d.ts +1 -1
- package/dist/types/src/components/DropdownMenu.d.ts.map +1 -1
- package/dist/types/src/components/Menu.d.ts +46 -0
- package/dist/types/src/components/Menu.d.ts.map +1 -0
- package/dist/types/src/components/Menu.test.d.ts +2 -0
- package/dist/types/src/components/Menu.test.d.ts.map +1 -0
- package/dist/types/src/components/MenuItems.stories.d.ts +21 -0
- package/dist/types/src/components/MenuItems.stories.d.ts.map +1 -0
- package/dist/types/src/components/ToolbarMenu.d.ts +1 -1
- package/dist/types/src/components/ToolbarMenu.d.ts.map +1 -1
- package/dist/types/src/components/ToolbarMenu.stories.d.ts +1 -1
- package/dist/types/src/components/ToolbarMenu.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -3
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useMenuActions.d.ts +2 -2
- package/dist/types/src/hooks/useMenuActions.d.ts.map +1 -1
- package/dist/types/src/testing/generator.d.ts +3 -3
- package/dist/types/src/testing/generator.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +25 -2
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +2 -0
- package/dist/types/src/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +24 -21
- package/src/builder.ts +35 -8
- package/src/components/ActionLabel.tsx +2 -2
- package/src/components/DropdownMenu.tsx +3 -3
- package/src/components/Menu.test.tsx +448 -0
- package/src/components/Menu.tsx +349 -0
- package/src/components/MenuItems.stories.tsx +265 -0
- package/src/components/ToolbarMenu.stories.tsx +17 -18
- package/src/components/ToolbarMenu.tsx +12 -11
- package/src/components/index.ts +1 -3
- package/src/hooks/useMenuActions.ts +15 -50
- package/src/testing/generator.ts +9 -9
- package/src/types.ts +27 -2
- package/src/util.ts +2 -1
- package/dist/types/src/components/MenuContext.d.ts +0 -15
- package/dist/types/src/components/MenuContext.d.ts.map +0 -1
- package/src/components/MenuContext.tsx +0 -62
|
@@ -6,7 +6,7 @@ import React, { Fragment, useCallback } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { Icon, Toolbar as NaturalToolbar, type ToolbarRootProps, useTranslation } from '@dxos/react-ui';
|
|
8
8
|
import { useAttention } from '@dxos/react-ui-attention';
|
|
9
|
-
import { mx, textBlockWidth,
|
|
9
|
+
import { mx, textBlockWidth, toolbarLayout } from '@dxos/ui-theme';
|
|
10
10
|
import { type MenuActionProperties } from '@dxos/ui-types';
|
|
11
11
|
|
|
12
12
|
import { translationKey } from '../translations';
|
|
@@ -23,7 +23,7 @@ import { executeMenuAction } from '../util';
|
|
|
23
23
|
|
|
24
24
|
import { ActionLabel, actionLabel } from './ActionLabel';
|
|
25
25
|
import { DropdownMenu } from './DropdownMenu';
|
|
26
|
-
import { type MenuScopedProps,
|
|
26
|
+
import { type MenuScopedProps, useMenuItems, useMenuScoped } from './Menu';
|
|
27
27
|
|
|
28
28
|
export type ToolbarMenuDropdownMenuActionGroup = Omit<MenuActionProperties, 'variant' | 'icon'> & {
|
|
29
29
|
variant: 'dropdownMenu';
|
|
@@ -54,10 +54,10 @@ export type ToolbarMenuActionProps = {
|
|
|
54
54
|
};
|
|
55
55
|
|
|
56
56
|
const ActionToolbarItem = ({ action, __menuScope }: MenuScopedProps<{ action: MenuAction }>) => {
|
|
57
|
-
const { iconSize, onAction } =
|
|
57
|
+
const { iconSize, onAction } = useMenuScoped('ActionToolbarItem', __menuScope);
|
|
58
58
|
const { t } = useTranslation(translationKey);
|
|
59
59
|
|
|
60
|
-
const { icon, iconOnly = true, disabled, testId, hidden, classNames } = action.properties;
|
|
60
|
+
const { icon, iconOnly = true, disabled, testId, hidden, classNames, variant } = action.properties;
|
|
61
61
|
const Root = icon ? NaturalToolbar.IconButton : NaturalToolbar.Button;
|
|
62
62
|
const rootProps = icon
|
|
63
63
|
? { icon, size: iconSize, iconOnly, label: actionLabel(action, t) }
|
|
@@ -76,7 +76,7 @@ const ActionToolbarItem = ({ action, __menuScope }: MenuScopedProps<{ action: Me
|
|
|
76
76
|
key={action.id}
|
|
77
77
|
disabled={disabled}
|
|
78
78
|
onClick={handleClick}
|
|
79
|
-
variant='ghost'
|
|
79
|
+
variant={variant ?? 'ghost'}
|
|
80
80
|
{...(testId && { 'data-testid': testId })}
|
|
81
81
|
{...(rootProps as any)}
|
|
82
82
|
classNames={classNames}
|
|
@@ -91,7 +91,7 @@ const DropdownMenuToolbarItem = ({
|
|
|
91
91
|
}: MenuScopedProps<ToolbarMenuActionGroupProps>) => {
|
|
92
92
|
const { iconOnly, disabled, testId } = group.properties;
|
|
93
93
|
const { t } = useTranslation(translationKey);
|
|
94
|
-
const { iconSize } =
|
|
94
|
+
const { iconSize } = useMenuScoped('DropdownMenuToolbarItem', __menuScope);
|
|
95
95
|
const items = useMenuItems(group, propsItems, 'DropdownMenuToolbarItem', __menuScope);
|
|
96
96
|
const activeItem = items?.find((item) => !!(item as MenuAction).properties.checked);
|
|
97
97
|
const icon =
|
|
@@ -113,7 +113,7 @@ const DropdownMenuToolbarItem = ({
|
|
|
113
113
|
children: (
|
|
114
114
|
<>
|
|
115
115
|
<ActionLabel action={labelAction} />
|
|
116
|
-
<Icon size={3} icon='ph--caret-down--bold' classNames='
|
|
116
|
+
<Icon size={3} icon='ph--caret-down--bold' classNames='ms-1' />
|
|
117
117
|
</>
|
|
118
118
|
),
|
|
119
119
|
};
|
|
@@ -128,7 +128,7 @@ const DropdownMenuToolbarItem = ({
|
|
|
128
128
|
};
|
|
129
129
|
|
|
130
130
|
const ToggleGroupItem = ({ group, action, __menuScope }: MenuScopedProps<ToolbarMenuActionProps>) => {
|
|
131
|
-
const { iconSize, onAction } =
|
|
131
|
+
const { iconSize, onAction } = useMenuScoped('ToggleGroupItem', __menuScope);
|
|
132
132
|
const { t } = useTranslation(translationKey);
|
|
133
133
|
const { icon, iconOnly = true, disabled, testId, hidden, classNames } = action.properties;
|
|
134
134
|
const handleClick = useCallback(() => {
|
|
@@ -187,17 +187,18 @@ export const ToolbarMenu = ({
|
|
|
187
187
|
...props
|
|
188
188
|
}: MenuScopedProps<ToolbarMenuProps>) => {
|
|
189
189
|
const items = useMenuItems(undefined, undefined, 'ToolbarMenu', __menuScope);
|
|
190
|
-
const { attendableId } =
|
|
190
|
+
const { attendableId, alwaysActive } = useMenuScoped('ToolbarMenu', __menuScope);
|
|
191
191
|
const { hasAttention } = useAttention(attendableId);
|
|
192
192
|
const InnerRoot = textBlockWidthProp ? 'div' : Fragment;
|
|
193
193
|
const innerRootProps = textBlockWidthProp
|
|
194
|
-
? { role: 'none', className: mx(textBlockWidth, toolbarLayout, '
|
|
194
|
+
? { role: 'none', className: mx(textBlockWidth, toolbarLayout, 'h-full') }
|
|
195
195
|
: {};
|
|
196
196
|
|
|
197
197
|
return (
|
|
198
198
|
<NaturalToolbar.Root
|
|
199
199
|
{...props}
|
|
200
|
-
classNames={[attendableId
|
|
200
|
+
classNames={[attendableId, classNames]}
|
|
201
|
+
disabled={!alwaysActive && !hasAttention}
|
|
201
202
|
layoutManaged={textBlockWidthProp}
|
|
202
203
|
>
|
|
203
204
|
<InnerRoot {...innerRootProps}>
|
package/src/components/index.ts
CHANGED
|
@@ -7,34 +7,7 @@ import { useCallback, useContext, useRef } from 'react';
|
|
|
7
7
|
|
|
8
8
|
import { Graph, Node } from '@dxos/app-graph';
|
|
9
9
|
|
|
10
|
-
import { type MenuItem, type MenuItemGroup } from '../types';
|
|
11
|
-
|
|
12
|
-
const edgesArrayToRecord = (edges: Graph.Edge[]): Record<string, Graph.Edges> => {
|
|
13
|
-
return Object.fromEntries(
|
|
14
|
-
Object.entries(
|
|
15
|
-
edges.reduce((acc: Record<string, { inbound: string[]; outbound: string[] }>, { source, target }) => {
|
|
16
|
-
if (!acc[source]) {
|
|
17
|
-
acc[source] = { inbound: [], outbound: [] };
|
|
18
|
-
}
|
|
19
|
-
if (!acc[target]) {
|
|
20
|
-
acc[target] = { inbound: [], outbound: [] };
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const sourceEdges = acc[source];
|
|
24
|
-
if (!sourceEdges.outbound.includes(target)) {
|
|
25
|
-
sourceEdges.outbound.push(target);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const targetEdges = acc[target];
|
|
29
|
-
if (!targetEdges.inbound.includes(source)) {
|
|
30
|
-
targetEdges.inbound.push(source);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return acc;
|
|
34
|
-
}, {}),
|
|
35
|
-
),
|
|
36
|
-
);
|
|
37
|
-
};
|
|
10
|
+
import { type MenuItem, type MenuItemGroup, type MenuItemsAccessor } from '../types';
|
|
38
11
|
|
|
39
12
|
export type ActionGraphNodes = Node.NodeArg<any>[];
|
|
40
13
|
export type ActionGraphEdges = Graph.Edge[];
|
|
@@ -44,39 +17,31 @@ export type ActionGraphProps = {
|
|
|
44
17
|
};
|
|
45
18
|
|
|
46
19
|
export type MenuActions = {
|
|
47
|
-
|
|
20
|
+
items: MenuItemsAccessor;
|
|
48
21
|
};
|
|
49
22
|
|
|
50
23
|
export const useMenuActions = (props: Atom.Atom<ActionGraphProps>): MenuActions => {
|
|
51
24
|
const registry = useContext(RegistryContext);
|
|
52
25
|
const menuGraphProps = useAtomValue(props);
|
|
53
26
|
|
|
54
|
-
// Create graph
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
});
|
|
27
|
+
// Create a new graph whenever props change to preserve correct order.
|
|
28
|
+
// (Graph.addEdges appends rather than replaces, which breaks ordering on updates.)
|
|
29
|
+
const graphRef = useRef<{ graph: Graph.WritableGraph; props: ActionGraphProps } | null>(null);
|
|
30
|
+
if (!graphRef.current || graphRef.current.props !== menuGraphProps) {
|
|
31
|
+
const newGraph = Graph.make({ registry });
|
|
32
|
+
newGraph.pipe(Graph.addNodes(menuGraphProps.nodes as Node.NodeArg<any>[]), Graph.addEdges(menuGraphProps.edges));
|
|
33
|
+
graphRef.current = { graph: newGraph, props: menuGraphProps };
|
|
62
34
|
}
|
|
63
|
-
const graph = graphRef.current;
|
|
64
35
|
|
|
65
|
-
|
|
66
|
-
// Using a ref to track the previous value to detect changes.
|
|
67
|
-
const prevPropsRef = useRef(menuGraphProps);
|
|
68
|
-
if (prevPropsRef.current !== menuGraphProps) {
|
|
69
|
-
prevPropsRef.current = menuGraphProps;
|
|
70
|
-
graph.pipe(Graph.addNodes(menuGraphProps.nodes), Graph.addEdges(menuGraphProps.edges));
|
|
71
|
-
}
|
|
36
|
+
const graph = graphRef.current.graph;
|
|
72
37
|
|
|
73
|
-
const
|
|
74
|
-
(
|
|
75
|
-
|
|
76
|
-
return
|
|
38
|
+
const items: MenuItemsAccessor = useCallback(
|
|
39
|
+
(group?: MenuItemGroup) => {
|
|
40
|
+
// TODO(wittjosiah): Migrate to using action relation instead of child.
|
|
41
|
+
return graph.connections(group?.id || Node.RootId, 'child') as Atom.Atom<MenuItem[] | null>;
|
|
77
42
|
},
|
|
78
43
|
[graph],
|
|
79
44
|
);
|
|
80
45
|
|
|
81
|
-
return {
|
|
46
|
+
return { items };
|
|
82
47
|
};
|
package/src/testing/generator.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { Graph, Node } from '@dxos/app-graph';
|
|
|
9
9
|
import { faker } from '@dxos/random';
|
|
10
10
|
|
|
11
11
|
import { type ActionGraphProps } from '../hooks/useMenuActions';
|
|
12
|
-
import { type MenuItem, type MenuItemGroup } from '../types';
|
|
12
|
+
import { type MenuItem, type MenuItemGroup, type MenuItemsAccessor } from '../types';
|
|
13
13
|
|
|
14
14
|
export type CreateActionsProps = Partial<{
|
|
15
15
|
type?: typeof Node.ActionType | typeof Node.ActionGroupType;
|
|
@@ -61,8 +61,8 @@ const buildNestedActions = (): ActionGraphProps => {
|
|
|
61
61
|
const actions = createActions();
|
|
62
62
|
result.nodes.push(group, ...actions);
|
|
63
63
|
result.edges.push(
|
|
64
|
-
{ source: 'root', target: group.id },
|
|
65
|
-
...actions.map((action) => ({ source: group.id, target: action.id })),
|
|
64
|
+
{ source: 'root', target: group.id, relation: 'child' },
|
|
65
|
+
...actions.map((action) => ({ source: group.id, target: action.id, relation: 'child' })),
|
|
66
66
|
);
|
|
67
67
|
});
|
|
68
68
|
return result;
|
|
@@ -78,15 +78,15 @@ export const createNestedActionsResolver = (groupParams?: CreateActionsProps, pa
|
|
|
78
78
|
graph.pipe(
|
|
79
79
|
Graph.addNodes([group as Node.NodeArg<any>, ...(actions as Node.NodeArg<any>[])]),
|
|
80
80
|
Graph.addEdges([
|
|
81
|
-
{ source: 'root', target: group.id },
|
|
82
|
-
...actions.map((action) => ({ source: group.id, target: action.id })),
|
|
81
|
+
{ source: 'root', target: group.id, relation: 'child' },
|
|
82
|
+
...actions.map((action) => ({ source: group.id, target: action.id, relation: 'child' })),
|
|
83
83
|
]),
|
|
84
|
-
Graph.expand(group.id),
|
|
84
|
+
Graph.expand(group.id, 'child'),
|
|
85
85
|
);
|
|
86
86
|
});
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
return {
|
|
87
|
+
const items: MenuItemsAccessor = (group?: MenuItemGroup) =>
|
|
88
|
+
graph.connections(group?.id ?? Node.RootId, 'child') as Atom.Atom<MenuItem[] | null>;
|
|
89
|
+
return { items };
|
|
90
90
|
};
|
|
91
91
|
|
|
92
92
|
/**
|
package/src/types.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type Atom } from '@effect-atom/atom-react';
|
|
6
|
+
|
|
5
7
|
import { Node } from '@dxos/app-graph';
|
|
6
8
|
import { type IconButtonProps, type ToolbarSeparatorProps } from '@dxos/react-ui';
|
|
7
9
|
import { type MenuActionProperties } from '@dxos/ui-types';
|
|
@@ -25,14 +27,37 @@ export const isMenuGroup = (node: Node.Node): node is MenuItemGroup => node.type
|
|
|
25
27
|
|
|
26
28
|
export type MenuItem = MenuSeparator | MenuAction | MenuItemGroup;
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
/** Atom-family-style accessor: returns an atom of items for a given group (or root when undefined). */
|
|
31
|
+
export type MenuItemsAccessor = (group?: MenuItemGroup) => Atom.Atom<MenuItem[] | null>;
|
|
29
32
|
|
|
30
33
|
export type ActionExecutor = (action: MenuAction, params: Node.InvokeProps) => void;
|
|
31
34
|
|
|
35
|
+
export type MenuItemsMode = 'additive' | 'replacement';
|
|
36
|
+
|
|
37
|
+
export type AddMenuItemsProps = {
|
|
38
|
+
id: string;
|
|
39
|
+
mode: MenuItemsMode;
|
|
40
|
+
priority?: number;
|
|
41
|
+
items: MenuItem[];
|
|
42
|
+
groupFilter?: (group?: MenuItemGroup) => boolean;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type MenuItems = Omit<AddMenuItemsProps, 'priority'> & { priority: number };
|
|
46
|
+
export type MenuItemsMap = Map<string, MenuItems>;
|
|
47
|
+
|
|
32
48
|
export type MenuContextValue = {
|
|
33
|
-
|
|
49
|
+
/** Atom-family accessor for base menu items, keyed by group (similar to Tree model). */
|
|
50
|
+
items: MenuItemsAccessor;
|
|
34
51
|
iconSize: IconButtonProps['size'];
|
|
35
52
|
attendableId?: string;
|
|
53
|
+
/** If true, the menu is always active regardless of attention state. */
|
|
54
|
+
alwaysActive?: boolean;
|
|
55
|
+
/** Atom holding the current set of imperatively added menu items. */
|
|
56
|
+
menuItemsAtom: Atom.Atom<MenuItemsMap>;
|
|
57
|
+
/** Imperatively add menu items to the nearest MenuProvider. */
|
|
58
|
+
addMenuItems: (props: AddMenuItemsProps) => void;
|
|
59
|
+
/** Remove previously added menu items by id. */
|
|
60
|
+
removeMenuItems: (id: string) => void;
|
|
36
61
|
/** Optional action executor. If provided, will be used instead of default execution. */
|
|
37
62
|
onAction?: ActionExecutor;
|
|
38
63
|
};
|
package/src/util.ts
CHANGED
|
@@ -72,6 +72,7 @@ export const createGapSeparator = (id: string = 'gap', source: string = 'root')
|
|
|
72
72
|
{
|
|
73
73
|
source,
|
|
74
74
|
target: id,
|
|
75
|
+
relation: 'child' as const,
|
|
75
76
|
},
|
|
76
77
|
],
|
|
77
78
|
});
|
|
@@ -85,5 +86,5 @@ export const createLineSeparator = (id: string = 'line', source: string = 'root'
|
|
|
85
86
|
data: undefined as never,
|
|
86
87
|
} satisfies MenuSeparator,
|
|
87
88
|
],
|
|
88
|
-
edges: [{ source, target: id }],
|
|
89
|
+
edges: [{ source, target: id, relation: 'child' as const }],
|
|
89
90
|
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { type Scope } from '@radix-ui/react-context';
|
|
2
|
-
import React, { type PropsWithChildren } from 'react';
|
|
3
|
-
import { type MenuContextValue, type MenuItem, type MenuItemGroup } from '../types';
|
|
4
|
-
export type MenuScopedProps<P> = P & {
|
|
5
|
-
__menuScope?: Scope;
|
|
6
|
-
};
|
|
7
|
-
declare const createMenuScope: import("@radix-ui/react-context").CreateScope;
|
|
8
|
-
declare const useMenu: (consumerName: string, scope: Scope<MenuContextValue | undefined>) => MenuContextValue;
|
|
9
|
-
export declare const menuContextDefaults: MenuContextValue;
|
|
10
|
-
type MenuProviderProps = PropsWithChildren<Partial<MenuContextValue>>;
|
|
11
|
-
declare const MenuProvider: ({ children, useGroupItems, iconSize, attendableId, onAction, }: MenuProviderProps) => React.JSX.Element;
|
|
12
|
-
export declare const useMenuItems: (group?: MenuItemGroup, propsItems?: MenuItem[], consumerName?: string, __menuScope?: Scope) => MenuItem[] | undefined;
|
|
13
|
-
export { useMenu, createMenuScope, MenuProvider };
|
|
14
|
-
export type { MenuProviderProps };
|
|
15
|
-
//# sourceMappingURL=MenuContext.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MenuContext.d.ts","sourceRoot":"","sources":["../../../../src/components/MenuContext.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,KAAK,EAAsB,MAAM,yBAAyB,CAAC;AACzE,OAAO,KAAK,EAAE,EAAE,KAAK,iBAAiB,EAAW,MAAM,OAAO,CAAC;AAE/D,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAEpF,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG;IAAE,WAAW,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AAI7D,QAAA,MAA0B,eAAe,+CAAqC,CAAC;AAE/E,QAAA,MAA4B,OAAO,wFAAkD,CAAC;AAEtF,eAAO,MAAM,mBAAmB,EAAE,gBAIjC,CAAC;AAIF,KAAK,iBAAiB,GAAG,iBAAiB,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC;AAEtE,QAAA,MAAM,YAAY,GAAI,gEAMnB,iBAAiB,sBAanB,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,QAAQ,aAAa,EACrB,aAAa,QAAQ,EAAE,EACvB,eAAc,MAA8B,EAC5C,cAAc,KAAK,2BAKpB,CAAC;AAEF,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC;AAElD,YAAY,EAAE,iBAAiB,EAAE,CAAC"}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2025 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { type Scope, createContextScope } from '@radix-ui/react-context';
|
|
6
|
-
import React, { type PropsWithChildren, useMemo } from 'react';
|
|
7
|
-
|
|
8
|
-
import { type MenuContextValue, type MenuItem, type MenuItemGroup } from '../types';
|
|
9
|
-
|
|
10
|
-
export type MenuScopedProps<P> = P & { __menuScope?: Scope };
|
|
11
|
-
|
|
12
|
-
const MENU_NAME = 'Menu';
|
|
13
|
-
|
|
14
|
-
const [createMenuContext, createMenuScope] = createContextScope(MENU_NAME, []);
|
|
15
|
-
|
|
16
|
-
const [MenuContextProvider, useMenu] = createMenuContext<MenuContextValue>(MENU_NAME);
|
|
17
|
-
|
|
18
|
-
export const menuContextDefaults: MenuContextValue = {
|
|
19
|
-
iconSize: 5,
|
|
20
|
-
useGroupItems: () => null,
|
|
21
|
-
onAction: undefined,
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const useMenuScope = createMenuScope();
|
|
25
|
-
|
|
26
|
-
type MenuProviderProps = PropsWithChildren<Partial<MenuContextValue>>;
|
|
27
|
-
|
|
28
|
-
const MenuProvider = ({
|
|
29
|
-
children,
|
|
30
|
-
useGroupItems = menuContextDefaults.useGroupItems,
|
|
31
|
-
iconSize = menuContextDefaults.iconSize,
|
|
32
|
-
attendableId,
|
|
33
|
-
onAction,
|
|
34
|
-
}: MenuProviderProps) => {
|
|
35
|
-
const { scope } = useMenuScope(undefined);
|
|
36
|
-
return (
|
|
37
|
-
<MenuContextProvider
|
|
38
|
-
useGroupItems={useGroupItems}
|
|
39
|
-
iconSize={iconSize}
|
|
40
|
-
attendableId={attendableId}
|
|
41
|
-
onAction={onAction}
|
|
42
|
-
scope={scope}
|
|
43
|
-
>
|
|
44
|
-
{children}
|
|
45
|
-
</MenuContextProvider>
|
|
46
|
-
);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export const useMenuItems = (
|
|
50
|
-
group?: MenuItemGroup,
|
|
51
|
-
propsItems?: MenuItem[],
|
|
52
|
-
consumerName: string = 'useMenuItemConsumer',
|
|
53
|
-
__menuScope?: Scope,
|
|
54
|
-
) => {
|
|
55
|
-
const { useGroupItems } = useMenu(consumerName, __menuScope);
|
|
56
|
-
const groupItems = useGroupItems(group);
|
|
57
|
-
return useMemo(() => propsItems ?? groupItems ?? undefined, [propsItems, groupItems]);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export { useMenu, createMenuScope, MenuProvider };
|
|
61
|
-
|
|
62
|
-
export type { MenuProviderProps };
|