@dxos/react-ui-menu 0.7.5-feature-compute.4d9d99a
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/LICENSE +8 -0
- package/README.md +3 -0
- package/dist/lib/browser/index.mjs +438 -0
- package/dist/lib/browser/index.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -0
- package/dist/lib/node/index.cjs +465 -0
- package/dist/lib/node/index.cjs.map +7 -0
- package/dist/lib/node/meta.json +1 -0
- package/dist/lib/node-esm/index.mjs +440 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/types/src/components/ActionLabel.d.ts +7 -0
- package/dist/types/src/components/ActionLabel.d.ts.map +1 -0
- package/dist/types/src/components/DropdownMenu.d.ts +20 -0
- package/dist/types/src/components/DropdownMenu.d.ts.map +1 -0
- package/dist/types/src/components/MenuContext.d.ts +13 -0
- package/dist/types/src/components/MenuContext.d.ts.map +1 -0
- package/dist/types/src/components/ToolbarMenu.d.ts +20 -0
- package/dist/types/src/components/ToolbarMenu.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +4 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/defs.d.ts +45 -0
- package/dist/types/src/defs.d.ts.map +1 -0
- package/dist/types/src/hooks.d.ts +17 -0
- package/dist/types/src/hooks.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +5 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/testing/Menus.stories.d.ts +25 -0
- package/dist/types/src/testing/Menus.stories.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +26 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +8 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/util.d.ts +45 -0
- package/dist/types/src/util.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +55 -0
- package/src/components/ActionLabel.tsx +30 -0
- package/src/components/DropdownMenu.tsx +112 -0
- package/src/components/MenuContext.tsx +57 -0
- package/src/components/ToolbarMenu.tsx +185 -0
- package/src/components/index.ts +7 -0
- package/src/defs.ts +54 -0
- package/src/hooks.ts +72 -0
- package/src/index.ts +8 -0
- package/src/testing/Menus.stories.tsx +67 -0
- package/src/testing/index.ts +111 -0
- package/src/translations.ts +13 -0
- package/src/util.ts +71 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type Node, type Action, type ActionGroup } from '@dxos/app-graph';
|
|
2
|
+
import { type ToolbarSeparatorProps, type Label, type IconButtonProps, type ClassNameValue } from '@dxos/react-ui';
|
|
3
|
+
export type MenuActionProperties = {
|
|
4
|
+
label: Label;
|
|
5
|
+
icon?: string;
|
|
6
|
+
value?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
hidden?: boolean;
|
|
9
|
+
iconOnly?: boolean;
|
|
10
|
+
testId?: string;
|
|
11
|
+
variant?: 'action' | 'toggle';
|
|
12
|
+
checked?: boolean;
|
|
13
|
+
classNames?: ClassNameValue;
|
|
14
|
+
};
|
|
15
|
+
export type MenuItemGroupProperties = {
|
|
16
|
+
label: Label;
|
|
17
|
+
icon?: string;
|
|
18
|
+
iconOnly?: boolean;
|
|
19
|
+
};
|
|
20
|
+
export type MenuAction<P extends {} = {}> = Action<P & MenuActionProperties>;
|
|
21
|
+
export type MenuActionHandler<A extends Node = MenuAction> = (action: A) => void;
|
|
22
|
+
export declare const MenuSeparatorType: "@dxos/react-ui-toolbar/separator";
|
|
23
|
+
export type MenuSeparator = Node<never, Pick<ToolbarSeparatorProps, 'variant'>> & {
|
|
24
|
+
type: typeof MenuSeparatorType;
|
|
25
|
+
};
|
|
26
|
+
export declare const isSeparator: (node: Node) => node is MenuSeparator;
|
|
27
|
+
export type MenuSingleSelectActionGroup = {
|
|
28
|
+
selectCardinality: 'single';
|
|
29
|
+
value: string;
|
|
30
|
+
};
|
|
31
|
+
export type MenuMultipleSelectActionGroup = {
|
|
32
|
+
selectCardinality: 'multiple';
|
|
33
|
+
value: string[];
|
|
34
|
+
};
|
|
35
|
+
export type MenuItemGroup<P extends Record<string, any> = Record<string, any>> = ActionGroup<P>;
|
|
36
|
+
export declare const isMenuGroup: (node: Node) => node is MenuItemGroup;
|
|
37
|
+
export type MenuItem = MenuSeparator | MenuAction | MenuItemGroup;
|
|
38
|
+
export type MenuItemsResolver = (group?: MenuItemGroup) => MenuItem[] | null;
|
|
39
|
+
export type MenuContextValue<A extends Node = MenuAction> = {
|
|
40
|
+
resolveGroupItems: MenuItemsResolver;
|
|
41
|
+
iconSize: IconButtonProps['size'];
|
|
42
|
+
onAction: MenuActionHandler<A>;
|
|
43
|
+
attendableId?: string;
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=defs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defs.d.ts","sourceRoot":"","sources":["../../../src/defs.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,MAAM,EAAE,KAAK,WAAW,EAAqB,MAAM,iBAAiB,CAAC;AAC9F,OAAO,EAAE,KAAK,qBAAqB,EAAE,KAAK,KAAK,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEnH,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,KAAK,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,KAAK,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE,IAAI,MAAM,CAAC,CAAC,GAAG,oBAAoB,CAAC,CAAC;AAE7E,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,IAAI,GAAG,UAAU,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;AAEjF,eAAO,MAAM,iBAAiB,EAAG,kCAA2C,CAAC;AAE7E,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,qBAAqB,EAAE,SAAS,CAAC,CAAC,GAAG;IAChF,IAAI,EAAE,OAAO,iBAAiB,CAAC;CAChC,CAAC;AAEF,eAAO,MAAM,WAAW,SAAU,IAAI,KAAG,IAAI,IAAI,aAAgD,CAAC;AAElG,MAAM,MAAM,2BAA2B,GAAG;IAAE,iBAAiB,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AACzF,MAAM,MAAM,6BAA6B,GAAG;IAAE,iBAAiB,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE/F,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC;AAEhG,eAAO,MAAM,WAAW,SAAU,IAAI,KAAG,IAAI,IAAI,aAAgD,CAAC;AAElG,MAAM,MAAM,QAAQ,GAAG,aAAa,GAAG,UAAU,GAAG,aAAa,CAAC;AAElE,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,CAAC,EAAE,aAAa,KAAK,QAAQ,EAAE,GAAG,IAAI,CAAC;AAE7E,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,IAAI,GAAG,UAAU,IAAI;IAC1D,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,QAAQ,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAClC,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type NodeArg } from '@dxos/app-graph';
|
|
2
|
+
import { type MenuItem, type MenuItemGroup } from './defs';
|
|
3
|
+
type Edge = {
|
|
4
|
+
source: string;
|
|
5
|
+
target: string;
|
|
6
|
+
};
|
|
7
|
+
export type ActionGraphNodes = NodeArg<any>[];
|
|
8
|
+
export type ActionGraphEdges = Edge[];
|
|
9
|
+
export type ActionGraphProps = {
|
|
10
|
+
nodes: ActionGraphNodes;
|
|
11
|
+
edges: ActionGraphEdges;
|
|
12
|
+
};
|
|
13
|
+
export declare const useMenuActions: (actionCreator: () => ActionGraphProps) => {
|
|
14
|
+
resolveGroupItems: (sourceNode?: MenuItemGroup) => MenuItem[] | null;
|
|
15
|
+
};
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../src/hooks.ts"],"names":[],"mappings":"AAOA,OAAO,EAAS,KAAK,OAAO,EAAa,MAAM,iBAAiB,CAAC;AAEjE,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;AAE3D,KAAK,IAAI,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AA6B/C,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;AAC9C,MAAM,MAAM,gBAAgB,GAAG,IAAI,EAAE,CAAC;AACtC,MAAM,MAAM,gBAAgB,GAAG;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAEpF,eAAO,MAAM,cAAc,kBAAmB,MAAM,gBAAgB;qCAgBnD,aAAa;CAW7B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAIA,cAAc,cAAc,CAAC;AAC7B,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import '@dxos-theme';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
declare const _default: {
|
|
4
|
+
title: string;
|
|
5
|
+
component: ({ __menuScope, classNames, ...props }: import("../components").MenuScopedProps<import("../components").ToolbarMenuProps>) => React.JSX.Element;
|
|
6
|
+
decorators: import("@storybook/react/*").Decorator[];
|
|
7
|
+
parameters: {
|
|
8
|
+
translations: readonly [{
|
|
9
|
+
readonly 'en-US': {
|
|
10
|
+
readonly menus: {};
|
|
11
|
+
};
|
|
12
|
+
}];
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export default _default;
|
|
16
|
+
export declare const DropdownMenu: {
|
|
17
|
+
render: () => React.JSX.Element;
|
|
18
|
+
};
|
|
19
|
+
export declare const Toolbar: {
|
|
20
|
+
render: () => React.JSX.Element;
|
|
21
|
+
};
|
|
22
|
+
export declare const UseMenuActionsToolbar: {
|
|
23
|
+
render: () => React.JSX.Element;
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=Menus.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Menus.stories.d.ts","sourceRoot":"","sources":["../../../../src/testing/Menus.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,aAAa,CAAC;AACrB,OAAO,KAAsB,MAAM,OAAO,CAAC;;;;;;;;;;;;;AAc3C,wBAKE;AAOF,eAAO,MAAM,YAAY;;CAcxB,CAAC;AAEF,eAAO,MAAM,OAAO;;CAQnB,CAAC;AAEF,eAAO,MAAM,qBAAqB;;CASjC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type Action, ACTION_TYPE, ACTION_GROUP_TYPE } from '@dxos/app-graph';
|
|
2
|
+
import { type MenuAction, type MenuItemGroup, type MenuItem } from '../defs';
|
|
3
|
+
import { type ActionGraphProps } from '../hooks';
|
|
4
|
+
export type CreateActionsParams = Partial<{
|
|
5
|
+
type?: typeof ACTION_TYPE | typeof ACTION_GROUP_TYPE;
|
|
6
|
+
callback: () => void;
|
|
7
|
+
count: number;
|
|
8
|
+
}>;
|
|
9
|
+
export declare const createActions: (params?: CreateActionsParams) => import("@dxos/live-object").ReactiveObject<{
|
|
10
|
+
id: string;
|
|
11
|
+
type: "dxos.org/type/GraphActionGroup" | "dxos.org/type/GraphAction";
|
|
12
|
+
data: symbol | (() => void);
|
|
13
|
+
properties: {
|
|
14
|
+
variant?: string | undefined;
|
|
15
|
+
label: string;
|
|
16
|
+
icon: string;
|
|
17
|
+
disabled: boolean;
|
|
18
|
+
};
|
|
19
|
+
}>[];
|
|
20
|
+
export declare const createNestedActions: () => ActionGraphProps;
|
|
21
|
+
export declare const createNestedActionsResolver: (groupParams?: CreateActionsParams, params?: CreateActionsParams) => {
|
|
22
|
+
resolveGroupItems: (groupNode?: MenuItemGroup) => MenuItem[] | null;
|
|
23
|
+
};
|
|
24
|
+
export declare const mutateActionsOnInterval: (actions: Action[]) => NodeJS.Timeout;
|
|
25
|
+
export declare const useMutateActions: (actions: MenuAction[]) => void;
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/testing/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,MAAM,EAAuB,WAAW,EAAE,iBAAiB,EAAqB,MAAM,iBAAiB,CAAC;AAKtH,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAEjD,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC;IACxC,IAAI,CAAC,EAAE,OAAO,WAAW,GAAG,OAAO,iBAAiB,CAAC;IACrD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAAC;AAqBH,eAAO,MAAM,aAAa,YAAa,mBAAmB;;;0BAvBxC,IAAI;;;;;;;IAyCrB,CAAC;AAEF,eAAO,MAAM,mBAAmB,wBAY/B,CAAC;AAEF,eAAO,MAAM,2BAA2B,iBAAkB,mBAAmB,WAAW,mBAAmB;oCAgBlE,aAAa,KACE,QAAQ,EAAE,GAAG,IAAI;CAExE,CAAC;AAEF,eAAO,MAAM,uBAAuB,YAAa,MAAM,EAAE,mBAUxD,CAAC;AAEF,eAAO,MAAM,gBAAgB,YAAa,UAAU,EAAE,SAI9C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/translations.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,cAAc,UAAU,CAAC;;;;;;AAEtC,wBAMW"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { actionGroupSymbol, type ActionLike } from '@dxos/app-graph';
|
|
2
|
+
import { type MenuActionProperties, type MenuItemGroupProperties } from './defs';
|
|
3
|
+
export declare const getShortcut: (action: ActionLike) => any;
|
|
4
|
+
export declare const fallbackIcon = "ph--placeholder--regular";
|
|
5
|
+
export declare const createMenuAction: <P extends {} = {}>(id: string, properties: P & MenuActionProperties) => {
|
|
6
|
+
id: string;
|
|
7
|
+
type: string;
|
|
8
|
+
properties: P & MenuActionProperties;
|
|
9
|
+
data: () => void;
|
|
10
|
+
};
|
|
11
|
+
export declare const createMenuItemGroup: <P extends MenuItemGroupProperties = MenuItemGroupProperties & Partial<MenuActionProperties>>(id: string, properties: P) => {
|
|
12
|
+
id: string;
|
|
13
|
+
type: string;
|
|
14
|
+
properties: P;
|
|
15
|
+
data: typeof actionGroupSymbol;
|
|
16
|
+
};
|
|
17
|
+
export declare const createGapSeparator: (id?: string, source?: string) => {
|
|
18
|
+
nodes: {
|
|
19
|
+
id: string;
|
|
20
|
+
type: "@dxos/react-ui-toolbar/separator";
|
|
21
|
+
properties: {
|
|
22
|
+
variant: "gap";
|
|
23
|
+
};
|
|
24
|
+
data: never;
|
|
25
|
+
}[];
|
|
26
|
+
edges: {
|
|
27
|
+
source: string;
|
|
28
|
+
target: string;
|
|
29
|
+
}[];
|
|
30
|
+
};
|
|
31
|
+
export declare const createLineSeparator: (id?: string, source?: string) => {
|
|
32
|
+
nodes: {
|
|
33
|
+
id: string;
|
|
34
|
+
type: "@dxos/react-ui-toolbar/separator";
|
|
35
|
+
properties: {
|
|
36
|
+
variant: "line";
|
|
37
|
+
};
|
|
38
|
+
data: never;
|
|
39
|
+
}[];
|
|
40
|
+
edges: {
|
|
41
|
+
source: string;
|
|
42
|
+
target: string;
|
|
43
|
+
}[];
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../src/util.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkC,iBAAiB,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGrG,OAAO,EAEL,KAAK,oBAAoB,EAEzB,KAAK,uBAAuB,EAE7B,MAAM,QAAQ,CAAC;AAEhB,eAAO,MAAM,WAAW,WAAY,UAAU,QAI7C,CAAC;AAEF,eAAO,MAAM,YAAY,6BAA6B,CAAC;AAMvD,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,EAAE,WAAW,MAAM,cAAc,CAAC,GAAG,oBAAoB;;;;;CAM3E,CAAC;AAE1B,eAAO,MAAM,mBAAmB,GAC9B,CAAC,SAAS,uBAAuB,gEAE7B,MAAM,cACE,CAAC;;;;;CAOa,CAAC;AAE7B,eAAO,MAAM,kBAAkB,QAAQ,MAAM,WAAkB,MAAM;;;;;;;cAM5C,KAAK;;;;;;CAI5B,CAAC;AAEH,eAAO,MAAM,mBAAmB,QAAQ,MAAM,WAAmB,MAAM;;;;;;;cAM9C,KAAK;;;;;;CAI5B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"5.7.2"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dxos/react-ui-menu",
|
|
3
|
+
"version": "0.7.5-feature-compute.4d9d99a",
|
|
4
|
+
"description": "Components for facilitating reactive and consistent menus.",
|
|
5
|
+
"homepage": "https://dxos.org",
|
|
6
|
+
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "DXOS.org",
|
|
9
|
+
"sideEffects": true,
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/types/src/index.d.ts",
|
|
13
|
+
"browser": "./dist/lib/browser/index.mjs",
|
|
14
|
+
"node": "./dist/lib/node-esm/index.mjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"types": "dist/types/src/index.d.ts",
|
|
18
|
+
"typesVersions": {
|
|
19
|
+
"*": {}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"src"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@preact/signals-react": "^1.3.8",
|
|
27
|
+
"@radix-ui/react-context": "^1.0.0",
|
|
28
|
+
"@radix-ui/react-use-controllable-state": "^1.0.0",
|
|
29
|
+
"@dxos/app-graph": "0.7.5-feature-compute.4d9d99a",
|
|
30
|
+
"@dxos/react-ui-attention": "0.7.5-feature-compute.4d9d99a",
|
|
31
|
+
"@dxos/keyboard": "0.7.5-feature-compute.4d9d99a",
|
|
32
|
+
"@dxos/util": "0.7.5-feature-compute.4d9d99a"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/react": "~18.2.0",
|
|
36
|
+
"@types/react-dom": "~18.2.0",
|
|
37
|
+
"react": "~18.2.0",
|
|
38
|
+
"react-dom": "~18.2.0",
|
|
39
|
+
"vite": "5.4.7",
|
|
40
|
+
"@dxos/live-object": "0.7.5-feature-compute.4d9d99a",
|
|
41
|
+
"@dxos/random": "0.7.5-feature-compute.4d9d99a",
|
|
42
|
+
"@dxos/react-ui": "0.7.5-feature-compute.4d9d99a",
|
|
43
|
+
"@dxos/react-ui-theme": "0.7.5-feature-compute.4d9d99a",
|
|
44
|
+
"@dxos/storybook-utils": "0.7.5-feature-compute.4d9d99a"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"react": "~18.2.0",
|
|
48
|
+
"react-dom": "~18.2.0",
|
|
49
|
+
"@dxos/react-ui-theme": "0.7.5-feature-compute.4d9d99a",
|
|
50
|
+
"@dxos/react-ui": "0.7.5-feature-compute.4d9d99a"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { type Action, type ActionGroup } from '@dxos/app-graph';
|
|
8
|
+
import { keySymbols } from '@dxos/keyboard';
|
|
9
|
+
import { toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
10
|
+
import { mx, descriptionText } from '@dxos/react-ui-theme';
|
|
11
|
+
|
|
12
|
+
import { type MenuActionProperties } from '../defs';
|
|
13
|
+
import { translationKey } from '../translations';
|
|
14
|
+
import { getShortcut } from '../util';
|
|
15
|
+
|
|
16
|
+
export const ActionLabel = ({
|
|
17
|
+
action,
|
|
18
|
+
}: {
|
|
19
|
+
action: Action<MenuActionProperties> | ActionGroup<Omit<MenuActionProperties, 'variant'>>;
|
|
20
|
+
}) => {
|
|
21
|
+
const shortcut = getShortcut(action);
|
|
22
|
+
const { t } = useTranslation(translationKey);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
<span className='grow truncate'>{toLocalizedString(action.properties!.label, t)}</span>
|
|
27
|
+
{shortcut && <span className={mx('shrink-0', descriptionText)}>{keySymbols(shortcut).join('')}</span>}
|
|
28
|
+
</>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useControllableState } from '@radix-ui/react-use-controllable-state';
|
|
6
|
+
import React, { type MouseEvent, type MutableRefObject, useCallback } from 'react';
|
|
7
|
+
|
|
8
|
+
import { DropdownMenu as NaturalDropdownMenu, Icon, type DropdownMenuRootProps } from '@dxos/react-ui';
|
|
9
|
+
|
|
10
|
+
import { ActionLabel } from './ActionLabel';
|
|
11
|
+
import { type MenuScopedProps, useMenu, useMenuItems } from './MenuContext';
|
|
12
|
+
import { type MenuAction, type MenuItem, type MenuItemGroup } from '../defs';
|
|
13
|
+
|
|
14
|
+
export type DropdownMenuProps = DropdownMenuRootProps & {
|
|
15
|
+
group?: MenuItemGroup;
|
|
16
|
+
items?: MenuItem[];
|
|
17
|
+
suppressNextTooltip?: MutableRefObject<boolean>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const DropdownMenuItem = ({
|
|
21
|
+
item,
|
|
22
|
+
onClick,
|
|
23
|
+
__menuScope,
|
|
24
|
+
}: MenuScopedProps<{
|
|
25
|
+
item: MenuItem;
|
|
26
|
+
onClick: (action: MenuAction, event: MouseEvent) => void;
|
|
27
|
+
}>) => {
|
|
28
|
+
// TODO(thure): handle other items.
|
|
29
|
+
const action = item as MenuAction;
|
|
30
|
+
const handleClick = useCallback((event: MouseEvent) => onClick(action, event), [action, onClick]);
|
|
31
|
+
const { iconSize } = useMenu('DropdownMenuItem', __menuScope);
|
|
32
|
+
return (
|
|
33
|
+
<NaturalDropdownMenu.Item
|
|
34
|
+
onClick={handleClick}
|
|
35
|
+
classNames='gap-2'
|
|
36
|
+
disabled={action.properties?.disabled}
|
|
37
|
+
{...(action.properties?.testId && { 'data-testid': action.properties.testId })}
|
|
38
|
+
>
|
|
39
|
+
{action.properties?.icon && <Icon icon={action.properties!.icon} size={iconSize} />}
|
|
40
|
+
<ActionLabel action={action} />
|
|
41
|
+
</NaturalDropdownMenu.Item>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const DropdownMenuRoot = ({
|
|
46
|
+
items: propsItems,
|
|
47
|
+
group,
|
|
48
|
+
open,
|
|
49
|
+
defaultOpen,
|
|
50
|
+
onOpenChange,
|
|
51
|
+
suppressNextTooltip,
|
|
52
|
+
children,
|
|
53
|
+
__menuScope,
|
|
54
|
+
...naturalProps
|
|
55
|
+
}: MenuScopedProps<DropdownMenuProps>) => {
|
|
56
|
+
const [optionsMenuOpen, setOptionsMenuOpen] = useControllableState({
|
|
57
|
+
prop: open,
|
|
58
|
+
defaultProp: defaultOpen,
|
|
59
|
+
onChange: onOpenChange,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const { onAction } = useMenu('DropdownMenuRoot', __menuScope);
|
|
63
|
+
|
|
64
|
+
const handleActionClick = useCallback(
|
|
65
|
+
(action: MenuAction, event: MouseEvent) => {
|
|
66
|
+
if (action.properties?.disabled) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
event.stopPropagation();
|
|
70
|
+
// TODO(thure): Why does Dialog’s modal-ness cause issues if we don’t explicitly close the menu here?
|
|
71
|
+
if (suppressNextTooltip) {
|
|
72
|
+
suppressNextTooltip.current = true;
|
|
73
|
+
}
|
|
74
|
+
setOptionsMenuOpen(false);
|
|
75
|
+
onAction?.(action);
|
|
76
|
+
},
|
|
77
|
+
[onAction],
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const items = useMenuItems(group, propsItems);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<NaturalDropdownMenu.Root
|
|
84
|
+
{...{
|
|
85
|
+
open: optionsMenuOpen,
|
|
86
|
+
onOpenChange: (nextOpen: boolean) => {
|
|
87
|
+
if (!nextOpen && suppressNextTooltip) {
|
|
88
|
+
suppressNextTooltip.current = true;
|
|
89
|
+
}
|
|
90
|
+
return setOptionsMenuOpen(nextOpen);
|
|
91
|
+
},
|
|
92
|
+
}}
|
|
93
|
+
{...naturalProps}
|
|
94
|
+
>
|
|
95
|
+
{children}
|
|
96
|
+
<NaturalDropdownMenu.Portal>
|
|
97
|
+
<NaturalDropdownMenu.Content>
|
|
98
|
+
<NaturalDropdownMenu.Viewport>
|
|
99
|
+
{items?.map((item) => <DropdownMenuItem key={item.id} item={item} onClick={handleActionClick} />)}
|
|
100
|
+
</NaturalDropdownMenu.Viewport>
|
|
101
|
+
<NaturalDropdownMenu.Arrow />
|
|
102
|
+
</NaturalDropdownMenu.Content>
|
|
103
|
+
</NaturalDropdownMenu.Portal>
|
|
104
|
+
</NaturalDropdownMenu.Root>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const DropdownMenu = {
|
|
109
|
+
Root: DropdownMenuRoot,
|
|
110
|
+
Trigger: NaturalDropdownMenu.Trigger,
|
|
111
|
+
VirtualTrigger: NaturalDropdownMenu.VirtualTrigger,
|
|
112
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { createContextScope, type Scope } from '@radix-ui/react-context';
|
|
6
|
+
import React, { type PropsWithChildren, useMemo } from 'react';
|
|
7
|
+
|
|
8
|
+
import { type MenuContextValue, type MenuItem, type MenuItemGroup } from '../defs';
|
|
9
|
+
|
|
10
|
+
export type MenuScopedProps<P> = P & { __menuScope?: Scope };
|
|
11
|
+
|
|
12
|
+
const MENU_NAME = 'GraphMenu';
|
|
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
|
+
onAction: () => {},
|
|
21
|
+
resolveGroupItems: () => null,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const useMenuScope = createMenuScope();
|
|
25
|
+
|
|
26
|
+
const MenuProvider = ({
|
|
27
|
+
children,
|
|
28
|
+
resolveGroupItems = menuContextDefaults.resolveGroupItems,
|
|
29
|
+
onAction = menuContextDefaults.onAction,
|
|
30
|
+
iconSize = menuContextDefaults.iconSize,
|
|
31
|
+
attendableId,
|
|
32
|
+
}: PropsWithChildren<Partial<MenuContextValue>>) => {
|
|
33
|
+
const { scope } = useMenuScope(undefined);
|
|
34
|
+
return (
|
|
35
|
+
<MenuContextProvider
|
|
36
|
+
resolveGroupItems={resolveGroupItems}
|
|
37
|
+
onAction={onAction}
|
|
38
|
+
iconSize={iconSize}
|
|
39
|
+
attendableId={attendableId}
|
|
40
|
+
scope={scope}
|
|
41
|
+
>
|
|
42
|
+
{children}
|
|
43
|
+
</MenuContextProvider>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const useMenuItems = (
|
|
48
|
+
group?: MenuItemGroup,
|
|
49
|
+
propsItems?: MenuItem[],
|
|
50
|
+
consumerName: string = 'useMenuItemConsumer',
|
|
51
|
+
__menuScope?: Scope,
|
|
52
|
+
) => {
|
|
53
|
+
const { resolveGroupItems } = useMenu(consumerName, __menuScope);
|
|
54
|
+
return useMemo(() => propsItems ?? resolveGroupItems?.(group) ?? undefined, [propsItems, group, resolveGroupItems]);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export { useMenu, createMenuScope, MenuProvider };
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback, useRef } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Icon, type IconButtonProps, Toolbar as NaturalToolbar, type ToolbarRootProps } from '@dxos/react-ui';
|
|
8
|
+
import { useAttention } from '@dxos/react-ui-attention';
|
|
9
|
+
|
|
10
|
+
import { ActionLabel } from './ActionLabel';
|
|
11
|
+
import { DropdownMenu } from './DropdownMenu';
|
|
12
|
+
import { type MenuScopedProps, useMenu, useMenuItems } from './MenuContext';
|
|
13
|
+
import {
|
|
14
|
+
type MenuAction,
|
|
15
|
+
type MenuItem,
|
|
16
|
+
isMenuGroup,
|
|
17
|
+
isSeparator,
|
|
18
|
+
type MenuItemGroup,
|
|
19
|
+
type MenuActionProperties,
|
|
20
|
+
type MenuMultipleSelectActionGroup,
|
|
21
|
+
type MenuSingleSelectActionGroup,
|
|
22
|
+
} from '../defs';
|
|
23
|
+
|
|
24
|
+
export type ToolbarMenuDropdownMenuActionGroup = Omit<MenuActionProperties, 'variant' | 'icon'> & {
|
|
25
|
+
variant: 'dropdownMenu';
|
|
26
|
+
icon: string;
|
|
27
|
+
applyActive?: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type ToolbarMenuToggleGroupActionGroup = Omit<MenuActionProperties, 'variant'> & {
|
|
31
|
+
variant: 'toggleGroup';
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type ToolbarMenuActionGroupProperties = (
|
|
35
|
+
| ToolbarMenuDropdownMenuActionGroup
|
|
36
|
+
| ToolbarMenuToggleGroupActionGroup
|
|
37
|
+
) &
|
|
38
|
+
(MenuSingleSelectActionGroup | MenuMultipleSelectActionGroup);
|
|
39
|
+
|
|
40
|
+
export type ToolbarMenuProps = ToolbarRootProps;
|
|
41
|
+
|
|
42
|
+
export type ToolbarMenuActionGroupProps = {
|
|
43
|
+
group: MenuItemGroup<ToolbarMenuActionGroupProperties>;
|
|
44
|
+
items?: MenuItem[];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const ActionToolbarItem = ({ action, __menuScope }: MenuScopedProps<{ action: MenuAction }>) => {
|
|
48
|
+
const { onAction, iconSize } = useMenu('ActionToolbarItem', __menuScope);
|
|
49
|
+
const handleClick = useCallback(() => {
|
|
50
|
+
onAction?.(action);
|
|
51
|
+
}, [action, onAction]);
|
|
52
|
+
const { icon, iconOnly = true, disabled, testId, hidden, classNames } = action.properties;
|
|
53
|
+
const Root = icon ? NaturalToolbar.IconButton : NaturalToolbar.Button;
|
|
54
|
+
const rootProps = icon
|
|
55
|
+
? { icon, size: iconSize, iconOnly, label: <ActionLabel action={action} /> }
|
|
56
|
+
: { children: <ActionLabel action={action} /> };
|
|
57
|
+
return hidden ? null : (
|
|
58
|
+
<Root
|
|
59
|
+
key={action.id}
|
|
60
|
+
disabled={disabled}
|
|
61
|
+
onClick={handleClick}
|
|
62
|
+
variant='ghost'
|
|
63
|
+
{...(testId && { 'data-testid': testId })}
|
|
64
|
+
{...(rootProps as IconButtonProps)}
|
|
65
|
+
classNames={classNames}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const DropdownMenuToolbarItem = ({
|
|
71
|
+
group,
|
|
72
|
+
items: propsItems,
|
|
73
|
+
__menuScope,
|
|
74
|
+
}: MenuScopedProps<ToolbarMenuActionGroupProps>) => {
|
|
75
|
+
const { iconOnly, disabled, testId } = group.properties;
|
|
76
|
+
const suppressNextTooltip = useRef(false);
|
|
77
|
+
const { iconSize } = useMenu('DropdownMenuToolbarItem', __menuScope);
|
|
78
|
+
const items = useMenuItems(group, propsItems, 'DropdownMenuToolbarItem', __menuScope);
|
|
79
|
+
const activeItem = items?.find((item) => !!(item as MenuAction).properties.checked);
|
|
80
|
+
const icon =
|
|
81
|
+
((group.properties as any).applyActive &&
|
|
82
|
+
// TODO(thure): Handle other menu item types.
|
|
83
|
+
(activeItem as MenuAction)?.properties.icon) ||
|
|
84
|
+
group.properties.icon;
|
|
85
|
+
const Root = icon ? NaturalToolbar.IconButton : NaturalToolbar.Button;
|
|
86
|
+
const label = (
|
|
87
|
+
<ActionLabel action={(group.properties as any).applyActive && activeItem ? (activeItem as MenuAction) : group} />
|
|
88
|
+
);
|
|
89
|
+
const rootProps = icon
|
|
90
|
+
? { icon, size: iconSize, iconOnly, label, caretDown: true, suppressNextTooltip }
|
|
91
|
+
: {
|
|
92
|
+
children: (
|
|
93
|
+
<>
|
|
94
|
+
{label}
|
|
95
|
+
<Icon size={3} icon='ph--caret-down--bold' classNames='mis-1' />
|
|
96
|
+
</>
|
|
97
|
+
),
|
|
98
|
+
};
|
|
99
|
+
return (
|
|
100
|
+
<DropdownMenu.Root group={group} items={items} suppressNextTooltip={suppressNextTooltip}>
|
|
101
|
+
<DropdownMenu.Trigger asChild>
|
|
102
|
+
<Root
|
|
103
|
+
variant='ghost'
|
|
104
|
+
disabled={disabled}
|
|
105
|
+
{...(rootProps as IconButtonProps)}
|
|
106
|
+
{...(testId && { 'data-testid': testId })}
|
|
107
|
+
/>
|
|
108
|
+
</DropdownMenu.Trigger>
|
|
109
|
+
</DropdownMenu.Root>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const ToggleGroupItem = ({ action, __menuScope }: MenuScopedProps<{ action: MenuAction }>) => {
|
|
114
|
+
const { iconSize, onAction } = useMenu('ToggleGroupItem', __menuScope);
|
|
115
|
+
const { icon, iconOnly = true, disabled, testId, hidden, classNames } = action.properties;
|
|
116
|
+
const handleClick = useCallback(() => {
|
|
117
|
+
onAction?.(action);
|
|
118
|
+
}, [action, onAction]);
|
|
119
|
+
const Root = icon ? NaturalToolbar.ToggleGroupIconItem : NaturalToolbar.ToggleGroupItem;
|
|
120
|
+
const rootProps = icon
|
|
121
|
+
? { icon, size: iconSize, iconOnly, label: <ActionLabel action={action} /> }
|
|
122
|
+
: { children: <ActionLabel action={action} /> };
|
|
123
|
+
return hidden ? null : (
|
|
124
|
+
<Root
|
|
125
|
+
key={action.id}
|
|
126
|
+
value={action.id as any}
|
|
127
|
+
disabled={disabled}
|
|
128
|
+
onClick={handleClick}
|
|
129
|
+
variant='ghost'
|
|
130
|
+
{...(testId && { 'data-testid': testId })}
|
|
131
|
+
{...(rootProps as IconButtonProps)}
|
|
132
|
+
classNames={classNames}
|
|
133
|
+
/>
|
|
134
|
+
);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const ToggleGroupToolbarItem = ({
|
|
138
|
+
group,
|
|
139
|
+
items: propsItems,
|
|
140
|
+
__menuScope,
|
|
141
|
+
}: MenuScopedProps<ToolbarMenuActionGroupProps>) => {
|
|
142
|
+
const items = useMenuItems(group, propsItems, 'ToggleGroupToolbarItem', __menuScope);
|
|
143
|
+
const { selectCardinality, value } = group.properties;
|
|
144
|
+
return (
|
|
145
|
+
// TODO(thure): The type here is difficult to manage, what do?
|
|
146
|
+
// @ts-ignore
|
|
147
|
+
<NaturalToolbar.ToggleGroup type={selectCardinality} value={value}>
|
|
148
|
+
{
|
|
149
|
+
// TODO(thure): Handle other menu item types.
|
|
150
|
+
(items as MenuAction[]).map((action) => (
|
|
151
|
+
<ToggleGroupItem key={action.id} action={action} />
|
|
152
|
+
))
|
|
153
|
+
}
|
|
154
|
+
</NaturalToolbar.ToggleGroup>
|
|
155
|
+
);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export const ToolbarMenu = ({ __menuScope, classNames, ...props }: MenuScopedProps<ToolbarMenuProps>) => {
|
|
159
|
+
const items = useMenuItems(undefined, undefined, 'ToolbarMenu', __menuScope);
|
|
160
|
+
const { attendableId } = useMenu('ToolbarMenu', __menuScope);
|
|
161
|
+
const { hasAttention } = useAttention(attendableId);
|
|
162
|
+
return (
|
|
163
|
+
<NaturalToolbar.Root
|
|
164
|
+
{...props}
|
|
165
|
+
classNames={['p-0 pointer-fine:p-1 attention-surface', !hasAttention && 'opacity-20', classNames]}
|
|
166
|
+
>
|
|
167
|
+
{items?.map((item: MenuItem, i: number) =>
|
|
168
|
+
isSeparator(item) ? (
|
|
169
|
+
<NaturalToolbar.Separator key={i} variant={item.properties.variant} />
|
|
170
|
+
) : isMenuGroup(item) ? (
|
|
171
|
+
item.properties.variant === 'dropdownMenu' ? (
|
|
172
|
+
// TODO(thure): Figure out type narrowing that doesn’t require so much use of `as`.
|
|
173
|
+
<DropdownMenuToolbarItem key={item.id} group={item as MenuItemGroup<ToolbarMenuActionGroupProperties>} />
|
|
174
|
+
) : (
|
|
175
|
+
// TODO(thure): Figure out type narrowing that doesn’t require so much use of `as`.
|
|
176
|
+
<ToggleGroupToolbarItem key={item.id} group={item as MenuItemGroup<ToolbarMenuActionGroupProperties>} />
|
|
177
|
+
)
|
|
178
|
+
) : (
|
|
179
|
+
// TODO(thure): Figure out type narrowing that doesn’t require so much use of `as`.
|
|
180
|
+
<ActionToolbarItem key={item.id} action={item as MenuAction} />
|
|
181
|
+
),
|
|
182
|
+
)}
|
|
183
|
+
</NaturalToolbar.Root>
|
|
184
|
+
);
|
|
185
|
+
};
|