@dxos/react-ui-menu 0.9.1-main.c7dcc2e112 → 0.10.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-menu",
3
- "version": "0.9.1-main.c7dcc2e112",
3
+ "version": "0.10.0",
4
4
  "description": "Components for facilitating reactive and consistent menus.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -37,39 +37,39 @@
37
37
  "dependencies": {
38
38
  "@radix-ui/react-context": "1.1.1",
39
39
  "@radix-ui/react-use-controllable-state": "1.1.0",
40
- "@dxos/app-graph": "0.9.1-main.c7dcc2e112",
41
- "@dxos/effect": "0.9.1-main.c7dcc2e112",
42
- "@dxos/invariant": "0.9.1-main.c7dcc2e112",
43
- "@dxos/log": "0.9.1-main.c7dcc2e112",
44
- "@dxos/react-ui-attention": "0.9.1-main.c7dcc2e112",
45
- "@dxos/ui-types": "0.9.1-main.c7dcc2e112",
46
- "@dxos/util": "0.9.1-main.c7dcc2e112",
47
- "@dxos/keyboard": "0.9.1-main.c7dcc2e112"
40
+ "@dxos/app-graph": "0.10.0",
41
+ "@dxos/invariant": "0.10.0",
42
+ "@dxos/effect": "0.10.0",
43
+ "@dxos/keyboard": "0.10.0",
44
+ "@dxos/log": "0.10.0",
45
+ "@dxos/ui-types": "0.10.0",
46
+ "@dxos/util": "0.10.0",
47
+ "@dxos/react-ui-attention": "0.10.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@effect-atom/atom-react": "^0.5.0",
51
- "@effect/platform": "0.96.1",
51
+ "@effect/platform": "0.96.2",
52
52
  "@testing-library/jest-dom": "^6.9.1",
53
- "@testing-library/react": "^16.3.0",
54
- "@types/react": "~19.2.7",
53
+ "@testing-library/react": "^16.3.2",
54
+ "@types/react": "~19.2.17",
55
55
  "@types/react-dom": "~19.2.3",
56
- "effect": "3.21.3",
57
- "react": "~19.2.3",
58
- "react-dom": "~19.2.3",
56
+ "effect": "3.21.4",
57
+ "react": "~19.2.7",
58
+ "react-dom": "~19.2.7",
59
59
  "vite": "^8.0.16",
60
- "@dxos/react-ui": "0.9.1-main.c7dcc2e112",
61
- "@dxos/random": "0.9.1-main.c7dcc2e112",
62
- "@dxos/ui-theme": "0.9.1-main.c7dcc2e112",
63
- "@dxos/storybook-utils": "0.9.1-main.c7dcc2e112"
60
+ "@dxos/random": "0.10.0",
61
+ "@dxos/ui-theme": "0.10.0",
62
+ "@dxos/react-ui": "0.10.0",
63
+ "@dxos/storybook-utils": "0.10.0"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "@effect-atom/atom-react": "^0.5.0",
67
- "@effect/platform": "0.96.1",
68
- "effect": "3.21.3",
69
- "react": "~19.2.3",
70
- "react-dom": "~19.2.3",
71
- "@dxos/ui-theme": "0.9.1-main.c7dcc2e112",
72
- "@dxos/react-ui": "0.9.1-main.c7dcc2e112"
67
+ "@effect/platform": "0.96.2",
68
+ "effect": "3.21.4",
69
+ "react": "~19.2.7",
70
+ "react-dom": "~19.2.7",
71
+ "@dxos/react-ui": "0.10.0",
72
+ "@dxos/ui-theme": "0.10.0"
73
73
  },
74
74
  "publishConfig": {
75
75
  "access": "public"
package/src/builder.ts CHANGED
@@ -41,6 +41,9 @@ export interface ActionGroupBuilder {
41
41
  /** Add an action node as a child of the current group. */
42
42
  action<P extends {} = {}>(id: string, props: P & MenuActionProperties, invoke: () => void): this;
43
43
 
44
+ /** Add a switch action rendered as a labeled Input.Switch. */
45
+ switch(id: string, props: Omit<MenuActionProperties, 'variant'> & { checked: boolean }, invoke: () => void): this;
46
+
44
47
  /** Add a nested action group. */
45
48
  group(id: string, props: MenuItemGroupProperties, cb: ActionGroupBuilderFn): this;
46
49
 
@@ -94,6 +97,10 @@ class MenuBuilderImpl implements MenuBuilder {
94
97
  return this;
95
98
  }
96
99
 
100
+ switch(id: string, props: Omit<MenuActionProperties, 'variant'> & { checked: boolean }, invoke: () => void): this {
101
+ return this.action(id, { ...props, variant: 'switch' }, invoke);
102
+ }
103
+
97
104
  group(id: string, props: MenuItemGroupProperties, cb: ActionGroupBuilderFn): this {
98
105
  this._data.nodes.push(createMenuItemGroup(id, props));
99
106
  this._data.edges.push({ source: this._rootId, target: id, relation: 'child' });
@@ -5,7 +5,7 @@
5
5
  import { Atom, RegistryContext, useAtomValue } from '@effect-atom/atom-react';
6
6
  import { type Scope, createContextScope } from '@radix-ui/react-context';
7
7
  import { useControllableState } from '@radix-ui/react-use-controllable-state';
8
- import React, { createContext, type MouseEvent, type PropsWithChildren, useCallback, useContext, useMemo } from 'react';
8
+ import React, { type MouseEvent, type PropsWithChildren, createContext, useCallback, useContext, useMemo } from 'react';
9
9
 
10
10
  import { log } from '@dxos/log';
11
11
  import { type DropdownMenuRootProps, Icon, DropdownMenu as NaturalDropdownMenu } from '@dxos/react-ui';
@@ -334,10 +334,10 @@ export { Menu, menuContextDefaults, useMenu, useMenuItems, useMenuScoped };
334
334
  export type { MenuContentProps, MenuRootProps, MenuScopedProps };
335
335
 
336
336
  export type {
337
- ToolbarMenuProps,
338
337
  ToolbarMenuActionGroupProperties,
339
338
  ToolbarMenuActionGroupProps,
340
339
  ToolbarMenuActionProps,
341
340
  ToolbarMenuDropdownMenuActionGroup,
341
+ ToolbarMenuProps,
342
342
  ToolbarMenuToggleGroupActionGroup,
343
343
  } from './ToolbarMenu';
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { Atom, RegistryContext } from '@effect-atom/atom-react';
6
6
  import { type Meta, type StoryObj } from '@storybook/react-vite';
7
- import React, { useContext, useMemo } from 'react';
7
+ import React, { useContext, useMemo, useState } from 'react';
8
8
 
9
9
  import { random } from '@dxos/random';
10
10
  import { IconButton } from '@dxos/react-ui';
@@ -13,8 +13,9 @@ import { withRegistry } from '@dxos/storybook-utils';
13
13
 
14
14
  import { translations } from '#translations';
15
15
 
16
+ import { MenuBuilder } from '../builder';
16
17
  import { Menu } from '../components';
17
- import { type ActionGraphProps, useMenuActions } from '../hooks';
18
+ import { type ActionGraphProps, useMenuActions, useMenuBuilder } from '../hooks';
18
19
  import { createActions, createNestedActions, createNestedActionsResolver, useMutateActions } from '../testing';
19
20
 
20
21
  random.seed(1234);
@@ -93,3 +94,31 @@ export const UseMenuActionsToolbar: Story = {
93
94
  );
94
95
  },
95
96
  };
97
+
98
+ /**
99
+ * Toolbar with both labeled and tooltip-only (iconOnly) Input.Switch items.
100
+ */
101
+ export const SwitchToolbar: Story = {
102
+ render: () => {
103
+ const [wordWrap, setWordWrap] = useState(false);
104
+ const [lineNumbers, setLineNumbers] = useState(true);
105
+
106
+ const menuActions = useMenuBuilder(
107
+ () =>
108
+ MenuBuilder.make()
109
+ .root({ label: 'Editor settings' })
110
+ .switch('word-wrap', { label: 'Word wrap', checked: wordWrap }, () => setWordWrap((v) => !v))
111
+ .switch('line-numbers', { label: 'Line numbers', iconOnly: true, checked: lineNumbers }, () =>
112
+ setLineNumbers((v) => !v),
113
+ )
114
+ .build(),
115
+ [wordWrap, lineNumbers],
116
+ );
117
+
118
+ return (
119
+ <Menu.Root {...menuActions} alwaysActive>
120
+ <Menu.Toolbar />
121
+ </Menu.Root>
122
+ );
123
+ },
124
+ };
@@ -4,7 +4,14 @@
4
4
 
5
5
  import React, { useCallback } from 'react';
6
6
 
7
- import { Toolbar as NaturalToolbar, type ToolbarRootProps, useTranslation } from '@dxos/react-ui';
7
+ import {
8
+ Input,
9
+ Toolbar as NaturalToolbar,
10
+ type ToolbarRootProps,
11
+ Tooltip,
12
+ toLocalizedString,
13
+ useTranslation,
14
+ } from '@dxos/react-ui';
8
15
  import { composable, composableProps } from '@dxos/react-ui';
9
16
  import { useAttention } from '@dxos/react-ui-attention';
10
17
  import { type DropdownMenuItemGroupProperties, type ToggleGroupMenuItemGroupProperties } from '@dxos/ui-types';
@@ -90,6 +97,48 @@ const ActionToolbarItem = ({ __menuScope, action }: MenuScopedProps<{ action: Me
90
97
  );
91
98
  };
92
99
 
100
+ const SwitchToolbarItem = ({ __menuScope, action }: MenuScopedProps<{ action: MenuAction }>) => {
101
+ const { onAction } = useMenuScoped('SwitchToolbarItem', __menuScope);
102
+ const { t } = useTranslation(translationKey);
103
+ const { label, iconOnly, disabled, testId, hidden, checked } = action.properties;
104
+ const labelStr = toLocalizedString(label, t);
105
+
106
+ const handleCheckedChange = useCallback(() => {
107
+ if (onAction) {
108
+ onAction(action, {});
109
+ } else {
110
+ void executeMenuAction(action);
111
+ }
112
+ }, [action, onAction]);
113
+
114
+ if (hidden) {
115
+ return null;
116
+ }
117
+
118
+ const switchInput = (
119
+ <Input.Switch
120
+ checked={checked}
121
+ disabled={disabled}
122
+ aria-label={iconOnly ? labelStr : undefined}
123
+ onCheckedChange={handleCheckedChange}
124
+ {...(testId && { 'data-testid': testId })}
125
+ />
126
+ );
127
+
128
+ return (
129
+ <Input.Root>
130
+ {iconOnly ? (
131
+ <Tooltip.Trigger asChild content={labelStr}>
132
+ <Input.Block>{switchInput}</Input.Block>
133
+ </Tooltip.Trigger>
134
+ ) : (
135
+ <Input.Block>{switchInput}</Input.Block>
136
+ )}
137
+ {!iconOnly && <Input.Label>{labelStr}</Input.Label>}
138
+ </Input.Root>
139
+ );
140
+ };
141
+
93
142
  const DropdownMenuToolbarItem = ({
94
143
  __menuScope,
95
144
  group,
@@ -257,5 +306,15 @@ const ToolbarMenuItem = ({ __menuScope, item }: MenuScopedProps<{ item: MenuItem
257
306
  );
258
307
  }
259
308
 
260
- return <ActionToolbarItem __menuScope={__menuScope} action={item as MenuAction} />;
309
+ const action = item as MenuAction;
310
+ if (action.properties?.variant === 'switch') {
311
+ return <SwitchToolbarItem __menuScope={__menuScope} action={action} />;
312
+ }
313
+
314
+ // The contributor owns the rendered element (interactions the action model cannot express).
315
+ if (action.properties?.variant === 'custom' && action.properties.render) {
316
+ return <>{action.properties.render()}</>;
317
+ }
318
+
319
+ return <ActionToolbarItem __menuScope={__menuScope} action={action} />;
261
320
  };
package/src/index.ts CHANGED
@@ -5,5 +5,5 @@
5
5
  export * from './components';
6
6
  export * from './hooks';
7
7
  export * from './types';
8
- export { createMenuAction, createMenuItemGroup, createLineSeparator, createGapSeparator } from './util';
8
+ export { createGapSeparator, createLineSeparator, createMenuAction, createMenuItemGroup } from './util';
9
9
  export { type ActionGroupBuilder, type ActionGroupBuilderFn, MenuBuilder } from './builder';