@dxos/plugin-simple-layout 0.8.4-main.bc674ce → 0.8.4-main.c85a9c8dae
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/{chunk-P77G4YTR.mjs → chunk-7VLT3S46.mjs} +3 -3
- package/dist/lib/browser/chunk-7VLT3S46.mjs.map +7 -0
- package/dist/lib/browser/chunk-TMZNLVT2.mjs +1170 -0
- package/dist/lib/browser/chunk-TMZNLVT2.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +20 -19
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{operation-resolver-775UYAC2.mjs → operation-resolver-BYRIQOQT.mjs} +30 -28
- package/dist/lib/browser/operation-resolver-BYRIQOQT.mjs.map +7 -0
- package/dist/lib/browser/{react-root-KM55OMGJ.mjs → react-root-MMB575WY.mjs} +5 -5
- package/dist/lib/browser/react-root-MMB575WY.mjs.map +7 -0
- package/dist/lib/browser/{react-surface-BABGAWGY.mjs → react-surface-M6CURANW.mjs} +10 -8
- package/dist/lib/browser/react-surface-M6CURANW.mjs.map +7 -0
- package/dist/lib/browser/{spotlight-dismiss-VSNOPETH.mjs → spotlight-dismiss-67PHYS5B.mjs} +3 -3
- package/dist/lib/browser/spotlight-dismiss-67PHYS5B.mjs.map +7 -0
- package/dist/lib/browser/{state-OUFTC2KV.mjs → state-A3PGDWWZ.mjs} +5 -4
- package/dist/lib/browser/state-A3PGDWWZ.mjs.map +7 -0
- package/dist/lib/browser/url-handler-HTIUY6WL.mjs +152 -0
- package/dist/lib/browser/url-handler-HTIUY6WL.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-FLOYBAHE.mjs +1171 -0
- package/dist/lib/node-esm/chunk-FLOYBAHE.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-F5TEKVJG.mjs → chunk-VIDE5UMB.mjs} +3 -3
- package/dist/lib/node-esm/chunk-VIDE5UMB.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +20 -19
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/{operation-resolver-LDNYS3DI.mjs → operation-resolver-BDTFNCS2.mjs} +30 -28
- package/dist/lib/node-esm/operation-resolver-BDTFNCS2.mjs.map +7 -0
- package/dist/lib/node-esm/{react-root-36UYFEEB.mjs → react-root-ENZKVSY4.mjs} +5 -5
- package/dist/lib/node-esm/react-root-ENZKVSY4.mjs.map +7 -0
- package/dist/lib/node-esm/{react-surface-CGHFVWU3.mjs → react-surface-ITVNQYLG.mjs} +10 -8
- package/dist/lib/node-esm/react-surface-ITVNQYLG.mjs.map +7 -0
- package/dist/lib/node-esm/{spotlight-dismiss-L5PCWIJG.mjs → spotlight-dismiss-RMLRZUVY.mjs} +3 -3
- package/dist/lib/node-esm/spotlight-dismiss-RMLRZUVY.mjs.map +7 -0
- package/dist/lib/node-esm/{state-Q2ZA26W5.mjs → state-ZCFZTTPL.mjs} +5 -4
- package/dist/lib/node-esm/state-ZCFZTTPL.mjs.map +7 -0
- package/dist/lib/node-esm/url-handler-WBVVKVPC.mjs +153 -0
- package/dist/lib/node-esm/url-handler-WBVVKVPC.mjs.map +7 -0
- package/dist/types/src/SimpleLayoutPlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +2 -2
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-root/react-root.d.ts +1 -1
- package/dist/types/src/capabilities/react-root/react-root.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface/index.d.ts +1 -1
- package/dist/types/src/capabilities/react-surface/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts +2 -2
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +1 -1
- package/dist/types/src/capabilities/spotlight-dismiss/index.d.ts +1 -1
- package/dist/types/src/capabilities/spotlight-dismiss/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/spotlight-dismiss/spotlight-dismiss.d.ts +1 -1
- package/dist/types/src/capabilities/spotlight-dismiss/spotlight-dismiss.d.ts.map +1 -1
- package/dist/types/src/capabilities/state/index.d.ts +1 -1
- package/dist/types/src/capabilities/state/state.d.ts +1 -1
- package/dist/types/src/capabilities/state/state.d.ts.map +1 -1
- package/dist/types/src/capabilities/url-handler/url-handler.d.ts +3 -1
- package/dist/types/src/capabilities/url-handler/url-handler.d.ts.map +1 -1
- package/dist/types/src/components/ContentError.stories.d.ts +1 -3
- package/dist/types/src/components/ContentError.stories.d.ts.map +1 -1
- package/dist/types/src/components/Home/Home.d.ts.map +1 -1
- package/dist/types/src/components/MobileLayout/MobileLayout.d.ts +35 -0
- package/dist/types/src/components/MobileLayout/MobileLayout.d.ts.map +1 -0
- package/dist/types/src/components/MobileLayout/MobileLayout.stories.d.ts +7 -0
- package/dist/types/src/components/MobileLayout/MobileLayout.stories.d.ts.map +1 -0
- package/dist/types/src/components/MobileLayout/index.d.ts +2 -0
- package/dist/types/src/components/MobileLayout/index.d.ts.map +1 -0
- package/dist/types/src/components/Popover/Popover.d.ts.map +1 -1
- package/dist/types/src/components/SimpleLayout/AppBar.d.ts +26 -0
- package/dist/types/src/components/SimpleLayout/AppBar.d.ts.map +1 -0
- package/dist/types/src/components/SimpleLayout/AppBar.stories.d.ts +47 -0
- package/dist/types/src/components/SimpleLayout/AppBar.stories.d.ts.map +1 -0
- package/dist/types/src/components/SimpleLayout/Drawer.d.ts +1 -1
- package/dist/types/src/components/SimpleLayout/Drawer.d.ts.map +1 -1
- package/dist/types/src/components/SimpleLayout/Main.d.ts.map +1 -1
- package/dist/types/src/components/SimpleLayout/NavBar.d.ts +10 -3
- package/dist/types/src/components/SimpleLayout/NavBar.d.ts.map +1 -1
- package/dist/types/src/components/SimpleLayout/NavBar.stories.d.ts +4 -4
- package/dist/types/src/components/SimpleLayout/NavBar.stories.d.ts.map +1 -1
- package/dist/types/src/components/SimpleLayout/SimpleLayout.d.ts.map +1 -1
- package/dist/types/src/components/SimpleLayout/SimpleLayout.stories.d.ts.map +1 -1
- package/dist/types/src/components/SimpleLayout/index.d.ts +3 -0
- package/dist/types/src/components/SimpleLayout/index.d.ts.map +1 -1
- package/dist/types/src/components/Workspace/Workspace.d.ts +3 -1
- package/dist/types/src/components/Workspace/Workspace.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/hooks/actions.d.ts +20 -0
- package/dist/types/src/hooks/actions.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +4 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAppBarProps.d.ts +7 -0
- package/dist/types/src/hooks/useAppBarProps.d.ts.map +1 -0
- package/dist/types/src/hooks/useCompanions.d.ts +5 -1
- package/dist/types/src/hooks/useCompanions.d.ts.map +1 -1
- package/dist/types/src/hooks/useDrawerActions.d.ts +13 -0
- package/dist/types/src/hooks/useDrawerActions.d.ts.map +1 -0
- package/dist/types/src/hooks/useNavbarActions.d.ts +14 -0
- package/dist/types/src/hooks/useNavbarActions.d.ts.map +1 -0
- package/dist/types/src/hooks/useSimpleLayoutState.d.ts +3 -3
- package/dist/types/src/hooks/useSimpleLayoutState.d.ts.map +1 -1
- package/dist/types/src/types/capabilities.d.ts +7 -6
- package/dist/types/src/types/capabilities.d.ts.map +1 -1
- package/dist/types/src/types/events.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +36 -30
- package/src/SimpleLayoutPlugin.ts +10 -9
- package/src/capabilities/operation-resolver/operation-resolver.ts +24 -22
- package/src/capabilities/react-root/react-root.tsx +2 -2
- package/src/capabilities/react-surface/react-surface.tsx +6 -5
- package/src/capabilities/spotlight-dismiss/spotlight-dismiss.ts +2 -2
- package/src/capabilities/state/state.tsx +4 -3
- package/src/capabilities/url-handler/url-handler.ts +111 -34
- package/src/components/ContentError.stories.tsx +8 -7
- package/src/components/ContentLoading.stories.tsx +2 -2
- package/src/components/ContentLoading.tsx +1 -1
- package/src/components/Dialog/Dialog.tsx +5 -5
- package/src/components/Home/Home.tsx +41 -33
- package/src/components/MobileLayout/MobileLayout.stories.tsx +129 -0
- package/src/components/MobileLayout/MobileLayout.tsx +305 -0
- package/src/components/MobileLayout/index.ts +5 -0
- package/src/components/Popover/Popover.tsx +17 -7
- package/src/components/SimpleLayout/AppBar.stories.tsx +144 -0
- package/src/components/SimpleLayout/AppBar.tsx +94 -0
- package/src/components/SimpleLayout/Drawer.tsx +22 -68
- package/src/components/SimpleLayout/Main.tsx +40 -29
- package/src/components/SimpleLayout/NavBar.stories.tsx +131 -23
- package/src/components/SimpleLayout/NavBar.tsx +15 -47
- package/src/components/SimpleLayout/SimpleLayout.stories.tsx +20 -11
- package/src/components/SimpleLayout/SimpleLayout.tsx +38 -19
- package/src/components/SimpleLayout/index.ts +3 -0
- package/src/components/Workspace/Workspace.tsx +34 -24
- package/src/components/hooks.ts +4 -4
- package/src/components/index.ts +1 -0
- package/src/hooks/actions.ts +85 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useAppBarProps.ts +116 -0
- package/src/hooks/useCompanions.ts +8 -5
- package/src/hooks/useDrawerActions.ts +98 -0
- package/src/hooks/useNavbarActions.ts +86 -0
- package/src/hooks/useSimpleLayoutState.ts +5 -5
- package/src/types/capabilities.ts +10 -6
- package/src/types/events.ts +3 -2
- package/dist/lib/browser/chunk-LR3EE3VB.mjs +0 -789
- package/dist/lib/browser/chunk-LR3EE3VB.mjs.map +0 -7
- package/dist/lib/browser/chunk-P77G4YTR.mjs.map +0 -7
- package/dist/lib/browser/operation-resolver-775UYAC2.mjs.map +0 -7
- package/dist/lib/browser/react-root-KM55OMGJ.mjs.map +0 -7
- package/dist/lib/browser/react-surface-BABGAWGY.mjs.map +0 -7
- package/dist/lib/browser/spotlight-dismiss-VSNOPETH.mjs.map +0 -7
- package/dist/lib/browser/state-OUFTC2KV.mjs.map +0 -7
- package/dist/lib/browser/url-handler-DOUFQIAC.mjs +0 -54
- package/dist/lib/browser/url-handler-DOUFQIAC.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-F5TEKVJG.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HB2B3LLG.mjs +0 -790
- package/dist/lib/node-esm/chunk-HB2B3LLG.mjs.map +0 -7
- package/dist/lib/node-esm/operation-resolver-LDNYS3DI.mjs.map +0 -7
- package/dist/lib/node-esm/react-root-36UYFEEB.mjs.map +0 -7
- package/dist/lib/node-esm/react-surface-CGHFVWU3.mjs.map +0 -7
- package/dist/lib/node-esm/spotlight-dismiss-L5PCWIJG.mjs.map +0 -7
- package/dist/lib/node-esm/state-Q2ZA26W5.mjs.map +0 -7
- package/dist/lib/node-esm/url-handler-DVAZZEUO.mjs +0 -55
- package/dist/lib/node-esm/url-handler-DVAZZEUO.mjs.map +0 -7
- package/dist/types/src/components/ContentError.d.ts +0 -5
- package/dist/types/src/components/ContentError.d.ts.map +0 -1
- package/dist/types/src/components/SimpleLayout/Banner.d.ts +0 -8
- package/dist/types/src/components/SimpleLayout/Banner.d.ts.map +0 -1
- package/src/components/ContentError.tsx +0 -23
- package/src/components/SimpleLayout/Banner.tsx +0 -113
|
@@ -2,64 +2,32 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type Atom } from '@effect-atom/atom-react';
|
|
5
6
|
import React from 'react';
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { Node, useActionRunner, useConnections } from '@dxos/plugin-graph';
|
|
10
|
-
import { IconButton, type ThemedClassName, Toolbar, Tooltip, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
11
|
-
import { DropdownMenu, MenuProvider } from '@dxos/react-ui-menu';
|
|
8
|
+
import { type ThemedClassName } from '@dxos/react-ui';
|
|
9
|
+
import { type ActionExecutor, type ActionGraphProps, Menu, useMenuActions } from '@dxos/react-ui-menu';
|
|
12
10
|
import { mx } from '@dxos/ui-theme';
|
|
13
11
|
|
|
14
|
-
import { useCompanions } from '../../hooks';
|
|
15
|
-
import { meta } from '../../meta';
|
|
16
|
-
|
|
17
12
|
const NAVBAR_NAME = 'SimpleLayout.NavBar';
|
|
18
13
|
|
|
19
14
|
export type NavBarProps = ThemedClassName<{
|
|
20
|
-
/**
|
|
21
|
-
|
|
15
|
+
/** Action graph atom for the toolbar. */
|
|
16
|
+
actions: Atom.Atom<ActionGraphProps>;
|
|
17
|
+
/** Action executor callback. */
|
|
18
|
+
onAction?: ActionExecutor;
|
|
22
19
|
}>;
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
const connections = useConnections(graph, Node.RootId);
|
|
31
|
-
const menuActions = connections.filter((node) => node.properties.disposition === 'menu');
|
|
32
|
-
|
|
33
|
-
const companions = useCompanions(activeId);
|
|
21
|
+
/**
|
|
22
|
+
* Presentational navbar component that renders a toolbar from an action graph.
|
|
23
|
+
*/
|
|
24
|
+
export const NavBar = ({ classNames, actions, onAction }: NavBarProps) => {
|
|
25
|
+
const menu = useMenuActions(actions);
|
|
34
26
|
|
|
35
27
|
return (
|
|
36
|
-
<
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
key={companion.id}
|
|
40
|
-
icon={companion.properties.icon ?? 'ph--placeholder--regular'}
|
|
41
|
-
iconOnly
|
|
42
|
-
label={toLocalizedString(companion.properties.label, t)}
|
|
43
|
-
onClick={() => {
|
|
44
|
-
void invokePromise(Common.LayoutOperation.Open, {
|
|
45
|
-
subject: [companion.id],
|
|
46
|
-
});
|
|
47
|
-
}}
|
|
48
|
-
/>
|
|
49
|
-
))}
|
|
50
|
-
|
|
51
|
-
<Toolbar.Separator variant='gap' />
|
|
52
|
-
|
|
53
|
-
<MenuProvider onAction={runAction}>
|
|
54
|
-
<DropdownMenu.Root items={menuActions}>
|
|
55
|
-
<Tooltip.Trigger asChild content={t('app menu label')}>
|
|
56
|
-
<DropdownMenu.Trigger asChild data-testid='simpleLayoutPlugin.addSpace'>
|
|
57
|
-
<IconButton icon='ph--plus--regular' iconOnly label={t('main menu label')} />
|
|
58
|
-
</DropdownMenu.Trigger>
|
|
59
|
-
</Tooltip.Trigger>
|
|
60
|
-
</DropdownMenu.Root>
|
|
61
|
-
</MenuProvider>
|
|
62
|
-
</Toolbar.Root>
|
|
28
|
+
<Menu.Root {...menu} alwaysActive onAction={onAction}>
|
|
29
|
+
<Menu.Toolbar density='coarse' classNames={mx(classNames)} />
|
|
30
|
+
</Menu.Root>
|
|
63
31
|
);
|
|
64
32
|
};
|
|
65
33
|
|
|
@@ -5,16 +5,17 @@
|
|
|
5
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
6
|
import * as Effect from 'effect/Effect';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { ActivationEvents, Capabilities, Capability, Plugin } from '@dxos/app-framework';
|
|
9
9
|
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
10
|
+
import { AppActivationEvents, AppPlugin } from '@dxos/app-toolkit';
|
|
11
|
+
import { Collection } from '@dxos/echo';
|
|
10
12
|
import { ClientOperation, ClientPlugin } from '@dxos/plugin-client';
|
|
11
13
|
import { SearchPlugin } from '@dxos/plugin-search';
|
|
12
14
|
import { SpacePlugin } from '@dxos/plugin-space';
|
|
13
15
|
import { SpaceOperation } from '@dxos/plugin-space/types';
|
|
14
16
|
import { corePlugins } from '@dxos/plugin-testing';
|
|
15
|
-
import { withTheme } from '@dxos/react-ui/testing';
|
|
17
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
16
18
|
import { translations as searchTranslation } from '@dxos/react-ui-searchlist';
|
|
17
|
-
import { Collection } from '@dxos/schema';
|
|
18
19
|
|
|
19
20
|
import { OperationResolver, type SimpleLayoutStateOptions, State } from '../../capabilities';
|
|
20
21
|
import { meta as pluginMeta } from '../../meta';
|
|
@@ -24,18 +25,18 @@ import { translations } from '../../translations';
|
|
|
24
25
|
import { SimpleLayout } from './SimpleLayout';
|
|
25
26
|
|
|
26
27
|
const TestPlugin = Plugin.define<SimpleLayoutPluginOptions>(pluginMeta).pipe(
|
|
28
|
+
AppPlugin.addOperationResolverModule({ activate: OperationResolver }),
|
|
27
29
|
Plugin.addModule(({ isPopover = false }) => ({
|
|
28
30
|
id: Capability.getModuleTag(State),
|
|
29
|
-
activatesOn:
|
|
30
|
-
activatesAfter: [
|
|
31
|
+
activatesOn: ActivationEvents.Startup,
|
|
32
|
+
activatesAfter: [AppActivationEvents.LayoutReady],
|
|
31
33
|
activate: () => State({ initialState: { isPopover } } satisfies SimpleLayoutStateOptions),
|
|
32
34
|
})),
|
|
33
|
-
Common.Plugin.addOperationResolverModule({ activate: OperationResolver }),
|
|
34
35
|
Plugin.addModule({
|
|
35
36
|
id: 'setup',
|
|
36
|
-
activatesOn:
|
|
37
|
+
activatesOn: ActivationEvents.OperationInvokerReady,
|
|
37
38
|
activate: Effect.fnUntraced(function* () {
|
|
38
|
-
const { invoke } = yield* Capability.get(
|
|
39
|
+
const { invoke } = yield* Capability.get(Capabilities.OperationInvoker);
|
|
39
40
|
yield* invoke(ClientOperation.CreateIdentity, {});
|
|
40
41
|
const { space: work } = yield* invoke(SpaceOperation.Create, { name: 'Work Space' });
|
|
41
42
|
const { space: sharedProject } = yield* invoke(SpaceOperation.Create, { name: 'Shared Project' });
|
|
@@ -79,7 +80,7 @@ const createPluginManager = ({ isPopover }: { isPopover: boolean }) => {
|
|
|
79
80
|
};
|
|
80
81
|
|
|
81
82
|
const meta = {
|
|
82
|
-
title: 'plugins/plugin-simple-layout/SimpleLayout',
|
|
83
|
+
title: 'plugins/plugin-simple-layout/components/SimpleLayout',
|
|
83
84
|
component: SimpleLayout,
|
|
84
85
|
parameters: {
|
|
85
86
|
layout: 'fullscreen',
|
|
@@ -96,9 +97,17 @@ type Story = StoryObj<typeof meta>;
|
|
|
96
97
|
* `moon run storybook-react:serve dev -H 0.0.0.0`
|
|
97
98
|
*/
|
|
98
99
|
export const Default: Story = {
|
|
99
|
-
decorators: [
|
|
100
|
+
decorators: [
|
|
101
|
+
withTheme(),
|
|
102
|
+
withLayout({ layout: 'column', classNames: 'relative' }),
|
|
103
|
+
createPluginManager({ isPopover: false }),
|
|
104
|
+
],
|
|
100
105
|
};
|
|
101
106
|
|
|
102
107
|
export const Popover: Story = {
|
|
103
|
-
decorators: [
|
|
108
|
+
decorators: [
|
|
109
|
+
withTheme(),
|
|
110
|
+
withLayout({ layout: 'column', classNames: 'relative' }),
|
|
111
|
+
createPluginManager({ isPopover: true }),
|
|
112
|
+
],
|
|
104
113
|
};
|
|
@@ -2,39 +2,58 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, {
|
|
5
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { Splitter, type SplitterMode } from '@dxos/react-ui';
|
|
8
8
|
import { Mosaic } from '@dxos/react-ui-mosaic';
|
|
9
9
|
|
|
10
10
|
import { useSimpleLayoutState } from '../../hooks';
|
|
11
11
|
import { Dialog } from '../Dialog';
|
|
12
|
+
import { MobileLayout } from '../MobileLayout';
|
|
12
13
|
import { PopoverContent, PopoverRoot } from '../Popover';
|
|
13
14
|
|
|
14
15
|
import { Drawer } from './Drawer';
|
|
15
16
|
import { Main } from './Main';
|
|
16
17
|
|
|
18
|
+
// TODO(burdon): Mobile/Desktop variance?
|
|
17
19
|
export const SimpleLayout = () => {
|
|
18
|
-
const { state
|
|
20
|
+
const { state } = useSimpleLayoutState();
|
|
21
|
+
const [keyboardOpen, setKeyboardOpen] = useState(false);
|
|
22
|
+
const [splitterMode, setSplitterMode] = useState<SplitterMode>('upper');
|
|
19
23
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
const drawerRef = useRef<HTMLDivElement>(null);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (keyboardOpen) {
|
|
27
|
+
// Determine which panel has focus and expand that one.
|
|
28
|
+
const activeElement = document.activeElement;
|
|
29
|
+
const drawerHasFocus = drawerRef.current?.contains(activeElement);
|
|
30
|
+
setSplitterMode(drawerHasFocus ? 'lower' : 'upper');
|
|
31
|
+
} else {
|
|
32
|
+
setSplitterMode(state.drawerState === 'closed' ? 'upper' : state.drawerState === 'open' ? 'both' : 'lower');
|
|
33
|
+
}
|
|
34
|
+
}, [state.drawerState, keyboardOpen]);
|
|
27
35
|
|
|
28
36
|
return (
|
|
29
|
-
<Mosaic.Root>
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
<Mosaic.Root classNames='contents'>
|
|
38
|
+
<MobileLayout.Root
|
|
39
|
+
classNames='bg-toolbar-surface'
|
|
40
|
+
onKeyboardOpenChange={(keyboardOpen: boolean) => setKeyboardOpen(keyboardOpen)}
|
|
41
|
+
>
|
|
42
|
+
<MobileLayout.Panel safe={{ top: true, bottom: splitterMode === 'upper' }}>
|
|
43
|
+
<PopoverRoot>
|
|
44
|
+
<Splitter.Root mode={splitterMode} ratio={0.55}>
|
|
45
|
+
<Splitter.Panel position='upper'>
|
|
46
|
+
<Main />
|
|
47
|
+
</Splitter.Panel>
|
|
48
|
+
<Splitter.Panel position='lower' ref={drawerRef}>
|
|
49
|
+
<Drawer />
|
|
50
|
+
</Splitter.Panel>
|
|
51
|
+
</Splitter.Root>
|
|
52
|
+
<Dialog />
|
|
53
|
+
<PopoverContent />
|
|
54
|
+
</PopoverRoot>
|
|
55
|
+
</MobileLayout.Panel>
|
|
56
|
+
</MobileLayout.Root>
|
|
38
57
|
</Mosaic.Root>
|
|
39
58
|
);
|
|
40
59
|
};
|
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { useCallback, useEffect, useRef } from 'react';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { useOperationInvoker } from '@dxos/app-framework/ui';
|
|
8
|
+
import { LayoutOperation } from '@dxos/app-toolkit';
|
|
9
|
+
import { useAppGraph } from '@dxos/app-toolkit/ui';
|
|
9
10
|
import { type Node, useConnections } from '@dxos/plugin-graph';
|
|
10
|
-
import { Avatar, Icon, Toolbar, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
11
|
-
import { Card
|
|
11
|
+
import { Avatar, Icon, Panel, ScrollArea, Toolbar, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
12
|
+
import { Card } from '@dxos/react-ui';
|
|
13
|
+
import { Mosaic, type MosaicStackTileComponent } from '@dxos/react-ui-mosaic';
|
|
12
14
|
import { SearchList, useSearchListItem, useSearchListResults } from '@dxos/react-ui-searchlist';
|
|
13
15
|
import { mx } from '@dxos/ui-theme';
|
|
14
16
|
|
|
@@ -20,9 +22,10 @@ export type WorkspaceProps = {
|
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
|
-
*
|
|
25
|
+
* Displays the contents of a workspace disposition graph node as a searchable list.
|
|
26
|
+
* Shows direct children of the workspace with icons and labels,
|
|
27
|
+
* allowing users to filter via search and navigate by selecting an item.
|
|
24
28
|
*/
|
|
25
|
-
// TODO(burdon): Rename or motivate name in comment.
|
|
26
29
|
export const Workspace = ({ id }: WorkspaceProps) => {
|
|
27
30
|
const { t } = useTranslation(meta.id);
|
|
28
31
|
const { graph } = useAppGraph();
|
|
@@ -31,7 +34,7 @@ export const Workspace = ({ id }: WorkspaceProps) => {
|
|
|
31
34
|
useLoadDescendents(id);
|
|
32
35
|
|
|
33
36
|
// Get direct children of the workspace node.
|
|
34
|
-
const children = useConnections(graph, id, '
|
|
37
|
+
const children = useConnections(graph, id, 'child');
|
|
35
38
|
|
|
36
39
|
const { results, handleSearch } = useSearchListResults({
|
|
37
40
|
items: children,
|
|
@@ -39,24 +42,31 @@ export const Workspace = ({ id }: WorkspaceProps) => {
|
|
|
39
42
|
});
|
|
40
43
|
|
|
41
44
|
return (
|
|
42
|
-
<
|
|
43
|
-
<
|
|
44
|
-
<Toolbar
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
<SearchList.Root onSearch={handleSearch}>
|
|
46
|
+
<Panel.Root>
|
|
47
|
+
<Panel.Toolbar asChild>
|
|
48
|
+
<Toolbar.Root>
|
|
49
|
+
<SearchList.Input placeholder={t('search placeholder')} autoFocus />
|
|
50
|
+
</Toolbar.Root>
|
|
51
|
+
</Panel.Toolbar>
|
|
52
|
+
<Panel.Content asChild>
|
|
53
|
+
<SearchList.Content>
|
|
54
|
+
<Mosaic.Container asChild>
|
|
55
|
+
<ScrollArea.Root orientation='vertical'>
|
|
56
|
+
<ScrollArea.Viewport classNames='p-2'>
|
|
57
|
+
<Mosaic.Stack items={results} getId={(child) => child.id} Tile={WorkspaceChildTile} />
|
|
58
|
+
</ScrollArea.Viewport>
|
|
59
|
+
</ScrollArea.Root>
|
|
60
|
+
</Mosaic.Container>
|
|
61
|
+
</SearchList.Content>
|
|
62
|
+
</Panel.Content>
|
|
63
|
+
</Panel.Root>
|
|
64
|
+
</SearchList.Root>
|
|
56
65
|
);
|
|
57
66
|
};
|
|
58
67
|
|
|
59
|
-
const WorkspaceChildTile:
|
|
68
|
+
const WorkspaceChildTile: MosaicStackTileComponent<Node.Node> = (props) => {
|
|
69
|
+
const data = props.data;
|
|
60
70
|
const { t } = useTranslation(meta.id);
|
|
61
71
|
const { invokeSync } = useOperationInvoker();
|
|
62
72
|
const ref = useRef<HTMLDivElement>(null);
|
|
@@ -66,7 +76,7 @@ const WorkspaceChildTile: StackTileComponent<Node.Node> = ({ data }) => {
|
|
|
66
76
|
const name = toLocalizedString(data.properties.label, t);
|
|
67
77
|
|
|
68
78
|
const handleSelect = useCallback(
|
|
69
|
-
() => invokeSync(
|
|
79
|
+
() => invokeSync(LayoutOperation.Open, { subject: [data.id] }),
|
|
70
80
|
[invokeSync, data.id],
|
|
71
81
|
);
|
|
72
82
|
|
|
@@ -93,7 +103,7 @@ const WorkspaceChildTile: StackTileComponent<Node.Node> = ({ data }) => {
|
|
|
93
103
|
fullWidth
|
|
94
104
|
tabIndex={-1} // TODO(burdon): Use Mosaic.Focus.
|
|
95
105
|
data-selected={isSelected}
|
|
96
|
-
classNames={mx('dx-focus-ring', isSelected && 'bg-
|
|
106
|
+
classNames={mx('dx-focus-ring', isSelected && 'bg-hover-overlay')}
|
|
97
107
|
onClick={handleSelect}
|
|
98
108
|
>
|
|
99
109
|
<Card.Toolbar density='coarse'>
|
package/src/components/hooks.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { useEffect } from 'react';
|
|
6
6
|
|
|
7
|
-
import { useAppGraph } from '@dxos/app-
|
|
7
|
+
import { useAppGraph } from '@dxos/app-toolkit/ui';
|
|
8
8
|
import { Graph } from '@dxos/plugin-graph';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -16,10 +16,10 @@ export const useLoadDescendents = (nodeId?: string) => {
|
|
|
16
16
|
useEffect(() => {
|
|
17
17
|
if (nodeId) {
|
|
18
18
|
// First level: expand the node itself.
|
|
19
|
-
Graph.expand(graph, nodeId, '
|
|
19
|
+
Graph.expand(graph, nodeId, 'child');
|
|
20
20
|
// Second level: expand each child.
|
|
21
|
-
Graph.getConnections(graph, nodeId, '
|
|
22
|
-
Graph.expand(graph, child.id, '
|
|
21
|
+
Graph.getConnections(graph, nodeId, 'child').forEach((child) => {
|
|
22
|
+
Graph.expand(graph, child.id, 'child');
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
}, [nodeId, graph]);
|
package/src/components/index.ts
CHANGED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Atom } from '@effect-atom/atom-react';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
|
|
8
|
+
import { type Capabilities } from '@dxos/app-framework';
|
|
9
|
+
import { type AppCapabilities, LayoutOperation } from '@dxos/app-toolkit';
|
|
10
|
+
import { Node } from '@dxos/plugin-graph';
|
|
11
|
+
import { ATTENDABLE_PATH_SEPARATOR } from '@dxos/react-ui-attention';
|
|
12
|
+
import { type ActionGraphProps } from '@dxos/react-ui-menu';
|
|
13
|
+
import { byPosition } from '@dxos/util';
|
|
14
|
+
|
|
15
|
+
import { type SimpleLayoutState } from '../types';
|
|
16
|
+
|
|
17
|
+
// TODO(wittjosiah): Factor out to shared location with plugin-deck.
|
|
18
|
+
export const PLANK_COMPANION_TYPE = 'dxos.org/plugin/deck/plank-companion';
|
|
19
|
+
|
|
20
|
+
export type CompanionActionsConfig = {
|
|
21
|
+
/** Prefix for companion action IDs (e.g. 'navbar' or 'drawer') */
|
|
22
|
+
idPrefix: string;
|
|
23
|
+
/** Optional: highlight companion with this variant */
|
|
24
|
+
selectedVariant?: string;
|
|
25
|
+
/** invokeSync function for dispatching operations */
|
|
26
|
+
invokeSync: Capabilities.OperationInvoker['invokeSync'];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates action graph nodes and edges for companion actions.
|
|
31
|
+
* Shared logic between useNavbarActions and useDrawerActions.
|
|
32
|
+
*/
|
|
33
|
+
// TODO(burdon): Use builder pattern.
|
|
34
|
+
export const createCompanionActions = (
|
|
35
|
+
graph: AppCapabilities.AppGraph['graph'],
|
|
36
|
+
stateAtom: Atom.Atom<SimpleLayoutState>,
|
|
37
|
+
get: (atom: Atom.Atom<any>) => any,
|
|
38
|
+
config: CompanionActionsConfig,
|
|
39
|
+
): Pick<ActionGraphProps, 'nodes' | 'edges'> => {
|
|
40
|
+
const { idPrefix, selectedVariant, invokeSync } = config;
|
|
41
|
+
|
|
42
|
+
// Derive activeId from state atom.
|
|
43
|
+
const state = get(stateAtom);
|
|
44
|
+
const activeId = state.active ?? state.workspace;
|
|
45
|
+
|
|
46
|
+
// Get companions from graph connections for activeId.
|
|
47
|
+
const activeConnections = activeId ? get(graph.connections(activeId, 'child')) : [];
|
|
48
|
+
const companions = activeConnections
|
|
49
|
+
.filter((node: Node.Node) => node.type === PLANK_COMPANION_TYPE)
|
|
50
|
+
.toSorted((a: Node.Node, b: Node.Node) => byPosition(a.properties, b.properties));
|
|
51
|
+
|
|
52
|
+
const nodes: ActionGraphProps['nodes'] = [];
|
|
53
|
+
const edges: ActionGraphProps['edges'] = [];
|
|
54
|
+
|
|
55
|
+
// Add companion actions.
|
|
56
|
+
// TODO(burdon): Cap at 6 items.
|
|
57
|
+
companions.forEach((companion: Node.Node) => {
|
|
58
|
+
// Extract variant for highlighting if needed.
|
|
59
|
+
const [, companionVariant] = companion.id.split(ATTENDABLE_PATH_SEPARATOR);
|
|
60
|
+
|
|
61
|
+
const companionAction = {
|
|
62
|
+
id: `${idPrefix}-companion-${companion.id}`,
|
|
63
|
+
type: Node.ActionType,
|
|
64
|
+
properties: {
|
|
65
|
+
icon: companion.properties.icon ?? 'ph--placeholder--regular',
|
|
66
|
+
label: companion.properties.label,
|
|
67
|
+
iconOnly: true,
|
|
68
|
+
// Conditionally add variant highlighting.
|
|
69
|
+
...(selectedVariant !== undefined && {
|
|
70
|
+
variant: selectedVariant === companionVariant ? 'primary' : 'ghost',
|
|
71
|
+
}),
|
|
72
|
+
},
|
|
73
|
+
data: () =>
|
|
74
|
+
Effect.sync(() =>
|
|
75
|
+
invokeSync(LayoutOperation.Open, {
|
|
76
|
+
subject: [companion.id],
|
|
77
|
+
}),
|
|
78
|
+
),
|
|
79
|
+
};
|
|
80
|
+
nodes.push(companionAction);
|
|
81
|
+
edges.push({ source: 'root', target: companionAction.id, relation: 'child' });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return { nodes, edges };
|
|
85
|
+
};
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Atom, useAtomValue } from '@effect-atom/atom-react';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Option from 'effect/Option';
|
|
8
|
+
import { useCallback, useMemo } from 'react';
|
|
9
|
+
|
|
10
|
+
import { useCapability, useOperationInvoker } from '@dxos/app-framework/ui';
|
|
11
|
+
import { LayoutOperation } from '@dxos/app-toolkit';
|
|
12
|
+
import { useAppGraph } from '@dxos/app-toolkit/ui';
|
|
13
|
+
import { Graph, Node, useActionRunner, useNode } from '@dxos/plugin-graph';
|
|
14
|
+
import { toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
15
|
+
import { type ActionGraphProps } from '@dxos/react-ui-menu';
|
|
16
|
+
|
|
17
|
+
import { type AppBarProps } from '../components';
|
|
18
|
+
import { meta } from '../meta';
|
|
19
|
+
import { SimpleLayoutState as SimpleLayoutStateCapability } from '../types';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Hook that computes all AppBar props from the app graph.
|
|
23
|
+
* Derives activeId from state atom. Returns props ready to spread into the AppBar component.
|
|
24
|
+
*/
|
|
25
|
+
export const useAppBarProps = (): Omit<AppBarProps, 'classNames'> => {
|
|
26
|
+
const { t } = useTranslation(meta.id);
|
|
27
|
+
const stateAtom = useCapability(SimpleLayoutStateCapability);
|
|
28
|
+
const state = useAtomValue(stateAtom);
|
|
29
|
+
const { graph } = useAppGraph();
|
|
30
|
+
const { invokeSync } = useOperationInvoker();
|
|
31
|
+
const runAction = useActionRunner();
|
|
32
|
+
|
|
33
|
+
// Derive activeId from state.
|
|
34
|
+
const activeId = state.active ?? state.workspace;
|
|
35
|
+
const node = useNode(graph, activeId);
|
|
36
|
+
|
|
37
|
+
// Compute title from node label.
|
|
38
|
+
const title = node ? toLocalizedString(node.properties.label, t) : undefined;
|
|
39
|
+
|
|
40
|
+
// Build actions atom filtering by disposition.
|
|
41
|
+
// Derive activeId from state atom so we don't need to recreate this atom when it changes.
|
|
42
|
+
const actionsAtom = useMemo(
|
|
43
|
+
() =>
|
|
44
|
+
Atom.make((get): ActionGraphProps => {
|
|
45
|
+
const state = get(stateAtom);
|
|
46
|
+
const activeId = state.active ?? state.workspace;
|
|
47
|
+
const allActions = activeId ? get(graph.actions(activeId)) : [];
|
|
48
|
+
const filtered = allActions.filter((action) =>
|
|
49
|
+
['list-item', 'list-item-primary', 'heading-list-item'].includes(action.properties.disposition),
|
|
50
|
+
);
|
|
51
|
+
const nodes: ActionGraphProps['nodes'] = filtered as ActionGraphProps['nodes'];
|
|
52
|
+
const edges: ActionGraphProps['edges'] = filtered.map((action) => ({
|
|
53
|
+
source: 'root',
|
|
54
|
+
target: action.id,
|
|
55
|
+
relation: 'child',
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
// Add alternate-tree action (e.g. Settings) from the workspace node.
|
|
59
|
+
const workspaceConnections = state.workspace ? get(graph.connections(state.workspace, 'child')) : [];
|
|
60
|
+
const alternateTreeNode = workspaceConnections.find(
|
|
61
|
+
(node: Node.Node) => node.properties.disposition === 'alternate-tree',
|
|
62
|
+
);
|
|
63
|
+
if (alternateTreeNode && activeId !== alternateTreeNode.id) {
|
|
64
|
+
const settingsAction = {
|
|
65
|
+
id: `appbar-settings-${alternateTreeNode.id}`,
|
|
66
|
+
type: Node.ActionType,
|
|
67
|
+
data: () => Effect.sync(() => invokeSync(LayoutOperation.Open, { subject: [alternateTreeNode.id] })),
|
|
68
|
+
properties: {
|
|
69
|
+
label: alternateTreeNode.properties.label ?? alternateTreeNode.id,
|
|
70
|
+
icon: alternateTreeNode.properties.icon ?? 'ph--placeholder--regular',
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
nodes.push(settingsAction);
|
|
74
|
+
edges.push({ source: 'root', target: settingsAction.id, relation: 'child' });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { nodes, edges };
|
|
78
|
+
}),
|
|
79
|
+
[graph, stateAtom],
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Back button logic.
|
|
83
|
+
const showBackButton = activeId !== undefined && activeId !== Node.RootId;
|
|
84
|
+
|
|
85
|
+
const onBack = useCallback(() => {
|
|
86
|
+
if (state.active) {
|
|
87
|
+
const isWorkspace = Graph.getNode(graph, state.active).pipe(
|
|
88
|
+
Option.map((node) => node.properties.disposition === 'workspace'),
|
|
89
|
+
Option.getOrElse(() => false),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// If history is empty and this is a workspace, go to home.
|
|
93
|
+
if (state.history.length === 0 && isWorkspace) {
|
|
94
|
+
invokeSync(LayoutOperation.SwitchWorkspace, { subject: Node.RootId });
|
|
95
|
+
} else {
|
|
96
|
+
// Otherwise, close (which will pop from history or clear active).
|
|
97
|
+
invokeSync(LayoutOperation.Close, { subject: [state.active] });
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
invokeSync(LayoutOperation.SwitchWorkspace, { subject: Node.RootId });
|
|
101
|
+
}
|
|
102
|
+
}, [graph, invokeSync, state.active, state.history.length]);
|
|
103
|
+
|
|
104
|
+
// Compute popover anchor ID.
|
|
105
|
+
const popoverAnchorId =
|
|
106
|
+
node && state.popoverAnchorId === `dxos.org/ui/${meta.id}/${node.id}` ? state.popoverAnchorId : undefined;
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
title,
|
|
110
|
+
actions: actionsAtom,
|
|
111
|
+
showBackButton,
|
|
112
|
+
popoverAnchorId,
|
|
113
|
+
onBack: onBack,
|
|
114
|
+
onAction: runAction,
|
|
115
|
+
};
|
|
116
|
+
};
|
|
@@ -4,16 +4,19 @@
|
|
|
4
4
|
|
|
5
5
|
import { useMemo } from 'react';
|
|
6
6
|
|
|
7
|
-
import { useAppGraph } from '@dxos/app-
|
|
7
|
+
import { useAppGraph } from '@dxos/app-toolkit/ui';
|
|
8
8
|
import { useConnections } from '@dxos/plugin-graph';
|
|
9
9
|
import { byPosition } from '@dxos/util';
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
const PLANK_COMPANION_TYPE = 'dxos.org/plugin/deck/plank-companion';
|
|
11
|
+
import { PLANK_COMPANION_TYPE } from './actions';
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated Adopt the pattern from useNavbarActions (deriving from graph atoms)
|
|
15
|
+
* or merge the Drawer companion display into the NavBar component.
|
|
16
|
+
*/
|
|
17
|
+
export const useCompanions = (nodeId?: string) => {
|
|
15
18
|
const { graph } = useAppGraph();
|
|
16
|
-
const nodes = useConnections(graph,
|
|
19
|
+
const nodes = useConnections(graph, nodeId, 'child');
|
|
17
20
|
const companions = nodes.filter((node) => node.type === PLANK_COMPANION_TYPE);
|
|
18
21
|
return useMemo(() => companions.toSorted((a, b) => byPosition(a.properties, b.properties)), [companions]);
|
|
19
22
|
};
|