@dxos/react-ui-menu 0.8.4-main.c85a9c8dae → 0.8.4-main.d05539e30a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/browser/index.mjs +115 -136
  3. package/dist/lib/browser/index.mjs.map +4 -4
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/browser/translations.mjs +14 -0
  6. package/dist/lib/browser/translations.mjs.map +7 -0
  7. package/dist/lib/node-esm/index.mjs +115 -136
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/lib/node-esm/translations.mjs +16 -0
  11. package/dist/lib/node-esm/translations.mjs.map +7 -0
  12. package/dist/types/src/builder.d.ts +23 -4
  13. package/dist/types/src/builder.d.ts.map +1 -1
  14. package/dist/types/src/builder.test.d.ts +2 -0
  15. package/dist/types/src/builder.test.d.ts.map +1 -0
  16. package/dist/types/src/components/ActionLabel.d.ts.map +1 -1
  17. package/dist/types/src/components/DropdownMenu.d.ts +3 -1
  18. package/dist/types/src/components/DropdownMenu.d.ts.map +1 -1
  19. package/dist/types/src/components/Menu.d.ts +8 -4
  20. package/dist/types/src/components/Menu.d.ts.map +1 -1
  21. package/dist/types/src/components/{MenuItems.stories.d.ts → Menu.stories.d.ts} +1 -1
  22. package/dist/types/src/components/Menu.stories.d.ts.map +1 -0
  23. package/dist/types/src/components/ToolbarMenu.d.ts +4 -4
  24. package/dist/types/src/components/ToolbarMenu.d.ts.map +1 -1
  25. package/dist/types/src/components/ToolbarMenu.stories.d.ts +15 -1
  26. package/dist/types/src/components/ToolbarMenu.stories.d.ts.map +1 -1
  27. package/dist/types/src/hooks/useMenuActions.d.ts +9 -1
  28. package/dist/types/src/hooks/useMenuActions.d.ts.map +1 -1
  29. package/dist/types/src/index.d.ts +1 -1
  30. package/dist/types/src/index.d.ts.map +1 -1
  31. package/dist/types/src/testing/generator.d.ts +8 -4
  32. package/dist/types/src/testing/generator.d.ts.map +1 -1
  33. package/dist/types/src/types.d.ts +2 -1
  34. package/dist/types/src/types.d.ts.map +1 -1
  35. package/dist/types/src/util.d.ts +2 -2
  36. package/dist/types/src/util.d.ts.map +1 -1
  37. package/dist/types/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +28 -22
  39. package/src/builder.test.ts +224 -0
  40. package/src/builder.ts +41 -18
  41. package/src/components/ActionLabel.tsx +2 -1
  42. package/src/components/DropdownMenu.tsx +0 -1
  43. package/src/components/{MenuItems.stories.tsx → Menu.stories.tsx} +2 -1
  44. package/src/components/Menu.test.tsx +0 -1
  45. package/src/components/Menu.tsx +9 -10
  46. package/src/components/ToolbarMenu.stories.tsx +27 -12
  47. package/src/components/ToolbarMenu.tsx +118 -85
  48. package/src/hooks/useMenuActions.ts +19 -8
  49. package/src/index.ts +1 -1
  50. package/src/testing/generator.ts +15 -10
  51. package/src/types.ts +1 -0
  52. package/dist/types/src/components/MenuItems.stories.d.ts.map +0 -1
@@ -4,7 +4,7 @@ import { createRequire } from 'node:module';const require = createRequire(import
4
4
  import { Atom, RegistryContext, useAtomValue } from "@effect-atom/atom-react";
5
5
  import { createContextScope } from "@radix-ui/react-context";
6
6
  import { useControllableState as useControllableState2 } from "@radix-ui/react-use-controllable-state";
7
- import React4, { useCallback as useCallback3, useContext, useMemo } from "react";
7
+ import React4, { createContext, useCallback as useCallback3, useContext, useMemo } from "react";
8
8
  import { log } from "@dxos/log";
9
9
  import { Icon as Icon3, DropdownMenu as NaturalDropdownMenu2 } from "@dxos/react-ui";
10
10
 
@@ -79,18 +79,7 @@ import React from "react";
79
79
  import { keySymbols } from "@dxos/keyboard";
80
80
  import { toLocalizedString, useTranslation } from "@dxos/react-ui";
81
81
  import { mx } from "@dxos/ui-theme";
82
-
83
- // src/translations.ts
84
- var translationKey = "@dxos/react-ui-menu";
85
- var translations = [
86
- {
87
- "en-US": {
88
- [translationKey]: {}
89
- }
90
- }
91
- ];
92
-
93
- // src/components/ActionLabel.tsx
82
+ import { translationKey } from "#translations";
94
83
  var ActionLabel = ({ action }) => {
95
84
  const { t } = useTranslation(translationKey);
96
85
  const shortcut = getShortcut(action);
@@ -106,10 +95,11 @@ var actionLabel = (action, t) => {
106
95
  };
107
96
 
108
97
  // src/components/ToolbarMenu.tsx
109
- import React3, { Fragment, useCallback as useCallback2 } from "react";
98
+ import React3, { useCallback as useCallback2 } from "react";
110
99
  import { Icon as Icon2, Toolbar as NaturalToolbar, useTranslation as useTranslation2 } from "@dxos/react-ui";
111
100
  import { useAttention } from "@dxos/react-ui-attention";
112
- import { mx as mx2, textBlockWidth, toolbarLayout } from "@dxos/ui-theme";
101
+ import { composable, composableProps } from "@dxos/ui-theme";
102
+ import { translationKey as translationKey2 } from "#translations";
113
103
 
114
104
  // src/types.ts
115
105
  import { Node as Node2 } from "@dxos/app-graph";
@@ -189,21 +179,11 @@ var DropdownMenu = {
189
179
  };
190
180
 
191
181
  // src/components/ToolbarMenu.tsx
192
- var ActionToolbarItem = ({ action, __menuScope }) => {
182
+ var ActionToolbarItem = ({ __menuScope, action }) => {
193
183
  const { iconSize, onAction } = useMenuScoped("ActionToolbarItem", __menuScope);
194
- const { t } = useTranslation2(translationKey);
195
- const { icon, iconOnly = true, disabled, testId, hidden, classNames } = action.properties;
196
- const Root = icon ? NaturalToolbar.IconButton : NaturalToolbar.Button;
197
- const rootProps = icon ? {
198
- icon,
199
- size: iconSize,
200
- iconOnly,
201
- label: actionLabel(action, t)
202
- } : {
203
- children: /* @__PURE__ */ React3.createElement(ActionLabel, {
204
- action
205
- })
206
- };
184
+ const { t } = useTranslation2(translationKey2);
185
+ const { icon, iconOnly = true, disabled, testId, hidden, classNames, iconClassNames } = action.properties;
186
+ const buttonVariant = action.properties.variant === "primary" ? "primary" : "ghost";
207
187
  const handleClick = useCallback2(() => {
208
188
  if (onAction) {
209
189
  onAction(action, {});
@@ -214,32 +194,49 @@ var ActionToolbarItem = ({ action, __menuScope }) => {
214
194
  action,
215
195
  onAction
216
196
  ]);
217
- return hidden ? null : /* @__PURE__ */ React3.createElement(Root, {
218
- key: action.id,
197
+ const commonProps = {
198
+ variant: buttonVariant,
219
199
  disabled,
200
+ classNames,
220
201
  onClick: handleClick,
221
- variant: "ghost",
222
202
  ...testId && {
223
203
  "data-testid": testId
224
- },
225
- ...rootProps,
226
- classNames
227
- });
204
+ }
205
+ };
206
+ if (hidden) {
207
+ return null;
208
+ }
209
+ return icon ? /* @__PURE__ */ React3.createElement(NaturalToolbar.IconButton, {
210
+ key: action.id,
211
+ ...commonProps,
212
+ icon,
213
+ size: iconSize,
214
+ iconOnly,
215
+ iconClassNames,
216
+ label: actionLabel(action, t)
217
+ }) : /* @__PURE__ */ React3.createElement(NaturalToolbar.Button, {
218
+ key: action.id,
219
+ ...commonProps
220
+ }, /* @__PURE__ */ React3.createElement(ActionLabel, {
221
+ action
222
+ }));
228
223
  };
229
- var DropdownMenuToolbarItem = ({ group, items: propsItems, __menuScope }) => {
224
+ var DropdownMenuToolbarItem = ({ __menuScope, group, items: propsItems }) => {
230
225
  const { iconOnly, disabled, testId } = group.properties;
231
- const { t } = useTranslation2(translationKey);
226
+ const { t } = useTranslation2(translationKey2);
232
227
  const { iconSize } = useMenuScoped("DropdownMenuToolbarItem", __menuScope);
233
228
  const items = useMenuItems(group, propsItems, "DropdownMenuToolbarItem", __menuScope);
234
229
  const activeItem = items?.find((item) => !!item.properties.checked);
235
230
  const icon = group.properties.applyActive && // TODO(thure): Handle other menu item types.
236
231
  activeItem?.properties.icon || group.properties.icon;
232
+ const iconClassNames = group.properties.applyActive && activeItem?.properties.iconClassNames || group.properties.iconClassNames;
237
233
  const Root = icon ? NaturalToolbar.IconButton : NaturalToolbar.Button;
238
234
  const labelAction = group.properties.applyActive && activeItem ? activeItem : group;
239
235
  const rootProps = icon ? {
240
236
  icon,
241
237
  size: iconSize,
242
238
  iconOnly,
239
+ iconClassNames,
243
240
  label: actionLabel(labelAction, t),
244
241
  caretDown: true
245
242
  } : {
@@ -265,10 +262,10 @@ var DropdownMenuToolbarItem = ({ group, items: propsItems, __menuScope }) => {
265
262
  }
266
263
  })));
267
264
  };
268
- var ToggleGroupItem = ({ group, action, __menuScope }) => {
265
+ var ToggleGroupItem = ({ __menuScope, group, action }) => {
269
266
  const { iconSize, onAction } = useMenuScoped("ToggleGroupItem", __menuScope);
270
- const { t } = useTranslation2(translationKey);
271
- const { icon, iconOnly = true, disabled, testId, hidden, classNames } = action.properties;
267
+ const { t } = useTranslation2(translationKey2);
268
+ const { icon, iconOnly = true, disabled, testId, hidden, classNames, iconClassNames } = action.properties;
272
269
  const handleClick = useCallback2(() => {
273
270
  if (onAction) {
274
271
  onAction(action, {
@@ -284,74 +281,64 @@ var ToggleGroupItem = ({ group, action, __menuScope }) => {
284
281
  group,
285
282
  onAction
286
283
  ]);
287
- const Root = icon ? NaturalToolbar.ToggleGroupIconItem : NaturalToolbar.ToggleGroupItem;
288
- const rootProps = icon ? {
289
- icon,
290
- size: iconSize,
291
- iconOnly,
292
- label: actionLabel(action, t)
293
- } : {
294
- children: /* @__PURE__ */ React3.createElement(ActionLabel, {
295
- action
296
- })
297
- };
298
- return hidden ? null : /* @__PURE__ */ React3.createElement(Root, {
299
- key: action.id,
284
+ const commonProps = {
300
285
  value: action.id,
301
286
  disabled,
302
- onClick: handleClick,
303
287
  variant: "ghost",
288
+ classNames,
289
+ onClick: handleClick,
304
290
  ...testId && {
305
291
  "data-testid": testId
306
- },
307
- ...rootProps,
308
- classNames
309
- });
292
+ }
293
+ };
294
+ return hidden ? null : icon ? /* @__PURE__ */ React3.createElement(NaturalToolbar.ToggleGroupIconItem, {
295
+ ...commonProps,
296
+ icon,
297
+ size: iconSize,
298
+ iconOnly,
299
+ iconClassNames,
300
+ label: actionLabel(action, t)
301
+ }) : /* @__PURE__ */ React3.createElement(NaturalToolbar.ToggleGroupItem, commonProps, /* @__PURE__ */ React3.createElement(ActionLabel, {
302
+ action
303
+ }));
310
304
  };
311
- var ToggleGroupToolbarItem = ({ group, items: propsItems, __menuScope }) => {
312
- const items = useMenuItems(group, propsItems, "ToggleGroupToolbarItem", __menuScope);
313
- const { selectCardinality, value } = group.properties;
314
- return (
315
- // TODO(thure): Fix.
316
- // @ts-ignore
317
- /* @__PURE__ */ React3.createElement(
318
- NaturalToolbar.ToggleGroup,
319
- {
320
- type: selectCardinality,
321
- value
322
- },
323
- // TODO(thure): Handle other menu item types.
324
- items.map((action) => /* @__PURE__ */ React3.createElement(ToggleGroupItem, {
325
- key: action.id,
326
- group,
327
- action
328
- }))
329
- )
330
- );
305
+ var ToggleGroupToolbarItem = ({ __menuScope, group, items: itemsProp }) => {
306
+ const items = useMenuItems(group, itemsProp, "ToggleGroupToolbarItem", __menuScope);
307
+ const { selectCardinality } = group.properties;
308
+ const children = items.map((action) => /* @__PURE__ */ React3.createElement(ToggleGroupItem, {
309
+ key: action.id,
310
+ group,
311
+ action
312
+ }));
313
+ if (selectCardinality === "multiple") {
314
+ return /* @__PURE__ */ React3.createElement(NaturalToolbar.ToggleGroup, {
315
+ type: "multiple",
316
+ value: group.properties.value
317
+ }, children);
318
+ } else {
319
+ return /* @__PURE__ */ React3.createElement(NaturalToolbar.ToggleGroup, {
320
+ type: "single",
321
+ value: group.properties.value
322
+ }, children);
323
+ }
331
324
  };
332
- var ToolbarMenu = ({ __menuScope, classNames, textBlockWidth: textBlockWidthProp, ...props }) => {
325
+ var ToolbarMenu = composable(({ __menuScope, children, ...props }, forwardedRef) => {
333
326
  const items = useMenuItems(void 0, void 0, "ToolbarMenu", __menuScope);
334
327
  const { attendableId, alwaysActive } = useMenuScoped("ToolbarMenu", __menuScope);
335
328
  const { hasAttention } = useAttention(attendableId);
336
- const InnerRoot = textBlockWidthProp ? "div" : Fragment;
337
- const innerRootProps = textBlockWidthProp ? {
338
- role: "none",
339
- className: mx2(textBlockWidth, toolbarLayout, "h-full")
340
- } : {};
341
329
  return /* @__PURE__ */ React3.createElement(NaturalToolbar.Root, {
342
- ...props,
343
- classNames: [
344
- attendableId,
345
- classNames
346
- ],
330
+ ...composableProps(props, {
331
+ classNames: attendableId
332
+ }),
347
333
  disabled: !alwaysActive && !hasAttention,
348
- layoutManaged: textBlockWidthProp
349
- }, /* @__PURE__ */ React3.createElement(InnerRoot, innerRootProps, items?.map((item, i) => /* @__PURE__ */ React3.createElement(ToolbarMenuItem, {
350
- key: item.id ?? i,
334
+ ref: forwardedRef
335
+ }, items?.map((item) => /* @__PURE__ */ React3.createElement(ToolbarMenuItem, {
336
+ key: item.id,
337
+ __menuScope,
351
338
  item
352
- }))));
353
- };
354
- var ToolbarMenuItem = ({ item }) => {
339
+ })), children);
340
+ });
341
+ var ToolbarMenuItem = ({ __menuScope, item }) => {
355
342
  if (isSeparator(item)) {
356
343
  return /* @__PURE__ */ React3.createElement(NaturalToolbar.Separator, {
357
344
  variant: item.properties.variant
@@ -360,15 +347,18 @@ var ToolbarMenuItem = ({ item }) => {
360
347
  if (isMenuGroup(item)) {
361
348
  if (item.properties.variant === "dropdownMenu") {
362
349
  return /* @__PURE__ */ React3.createElement(DropdownMenuToolbarItem, {
350
+ __menuScope,
363
351
  group: item
364
352
  });
365
353
  } else {
366
354
  return /* @__PURE__ */ React3.createElement(ToggleGroupToolbarItem, {
355
+ __menuScope,
367
356
  group: item
368
357
  });
369
358
  }
370
359
  }
371
360
  return /* @__PURE__ */ React3.createElement(ActionToolbarItem, {
361
+ __menuScope,
372
362
  action: item
373
363
  });
374
364
  };
@@ -391,7 +381,7 @@ var menuContextDefaults = {
391
381
  };
392
382
  var [MenuContextProvider, useMenuScoped] = createMenuContext(MENU_NAME, menuContextDefaults);
393
383
  var useMenuScope = createMenuScope();
394
- var MenuDropdownContext = /* @__PURE__ */ React4.createContext({
384
+ var MenuDropdownContext = /* @__PURE__ */ createContext({
395
385
  closeMenu: () => {
396
386
  }
397
387
  });
@@ -456,12 +446,7 @@ var resolveItems = (baseItems, group, entries) => {
456
446
  log.warn("multiple replacement entries found", {
457
447
  ids: replacements.map((r) => r.id).join(", "),
458
448
  using: replacements[0].id
459
- }, {
460
- F: __dxlog_file,
461
- L: 152,
462
- S: void 0,
463
- C: (f, a) => f(...a)
464
- });
449
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 94, S: void 0 });
465
450
  }
466
451
  return replacements[0].items;
467
452
  }
@@ -576,24 +561,22 @@ var Menu = {
576
561
  };
577
562
 
578
563
  // src/hooks/useMenuActions.ts
579
- import { RegistryContext as RegistryContext2, useAtomValue as useAtomValue2 } from "@effect-atom/atom-react";
580
- import { useCallback as useCallback4, useContext as useContext2, useRef } from "react";
564
+ import { Atom as Atom2, RegistryContext as RegistryContext2, useAtomValue as useAtomValue2 } from "@effect-atom/atom-react";
565
+ import { useCallback as useCallback4, useContext as useContext2, useMemo as useMemo2 } from "react";
581
566
  import { Graph, Node as Node3 } from "@dxos/app-graph";
582
567
  var useMenuActions = (props) => {
583
568
  const registry = useContext2(RegistryContext2);
584
569
  const menuGraphProps = useAtomValue2(props);
585
- const graphRef = useRef(null);
586
- if (!graphRef.current || graphRef.current.props !== menuGraphProps) {
570
+ const graph = useMemo2(() => {
587
571
  const newGraph = Graph.make({
588
572
  registry
589
573
  });
590
574
  newGraph.pipe(Graph.addNodes(menuGraphProps.nodes), Graph.addEdges(menuGraphProps.edges));
591
- graphRef.current = {
592
- graph: newGraph,
593
- props: menuGraphProps
594
- };
595
- }
596
- const graph = graphRef.current.graph;
575
+ return newGraph;
576
+ }, [
577
+ registry,
578
+ menuGraphProps
579
+ ]);
597
580
  const items = useCallback4((group) => {
598
581
  return graph.connections(group?.id || Node3.RootId, "child");
599
582
  }, [
@@ -603,6 +586,10 @@ var useMenuActions = (props) => {
603
586
  items
604
587
  };
605
588
  };
589
+ var useMenuBuilder = (build, deps) => {
590
+ const atom = useMemo2(() => Atom2.make(build), deps);
591
+ return useMenuActions(atom);
592
+ };
606
593
 
607
594
  // src/builder.ts
608
595
  import { invariant } from "@dxos/invariant";
@@ -616,24 +603,8 @@ var MenuBuilderImpl = class _MenuBuilderImpl {
616
603
  this._rootId = _rootId;
617
604
  }
618
605
  root(props) {
619
- invariant(this._rootId === "root", "Root group can only be at the top level", {
620
- F: __dxlog_file2,
621
- L: 41,
622
- S: this,
623
- A: [
624
- "this._rootId === 'root'",
625
- "'Root group can only be at the top level'"
626
- ]
627
- });
628
- invariant(this._data.nodes.find((node) => node.id === "root") === void 0, "Root group can only be created once", {
629
- F: __dxlog_file2,
630
- L: 42,
631
- S: this,
632
- A: [
633
- "this._data.nodes.find((node) => node.id === 'root') === undefined",
634
- "'Root group can only be created once'"
635
- ]
636
- });
606
+ invariant(this._rootId === "root", "Root group can only be at the top level", { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 16, S: this, A: ["this._rootId === 'root'", "'Root group can only be at the top level'"] });
607
+ invariant(this._data.nodes.find((node) => node.id === "root") === void 0, "Root group can only be created once", { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 17, S: this, A: ["this._data.nodes.find((node)=>node.id === 'root') === undefined", "'Root group can only be created once'"] });
637
608
  this._data.nodes.push(createMenuItemGroup("root", props));
638
609
  return this;
639
610
  }
@@ -659,13 +630,20 @@ var MenuBuilderImpl = class _MenuBuilderImpl {
659
630
  cb(new _MenuBuilderImpl(this._data, id));
660
631
  return this;
661
632
  }
662
- subgraph(subgraph) {
663
- this._data.nodes.push(...subgraph.nodes);
664
- this._data.edges.push(...subgraph.edges);
633
+ subgraph(subgraphOrCb) {
634
+ if (!subgraphOrCb) {
635
+ return this;
636
+ }
637
+ if (typeof subgraphOrCb === "function") {
638
+ subgraphOrCb(new _MenuBuilderImpl(this._data, this._rootId));
639
+ } else {
640
+ this._data.nodes.push(...subgraphOrCb.nodes);
641
+ this._data.edges.push(...subgraphOrCb.edges);
642
+ }
665
643
  return this;
666
644
  }
667
- separator(id, variant = "gap") {
668
- id ??= `separator-${++this._separatorCount}`;
645
+ separator(variant = "gap") {
646
+ const id = `separator-${++this._separatorCount}`;
669
647
  this._data.nodes.push({
670
648
  id,
671
649
  type: MenuSeparatorType,
@@ -701,6 +679,7 @@ export {
701
679
  menuContextDefaults,
702
680
  useMenu,
703
681
  useMenuActions,
682
+ useMenuBuilder,
704
683
  useMenuItems,
705
684
  useMenuScoped
706
685
  };