@dxos/plugin-simple-layout 0.0.0 → 0.8.4-main.52d7546f51
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-7VLT3S46.mjs +29 -0
- package/dist/lib/browser/chunk-7VLT3S46.mjs.map +7 -0
- package/dist/lib/browser/chunk-O3BQBYMW.mjs +1165 -0
- package/dist/lib/browser/chunk-O3BQBYMW.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +101 -0
- package/dist/lib/browser/index.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -0
- package/dist/lib/browser/operation-resolver-BYRIQOQT.mjs +205 -0
- package/dist/lib/browser/operation-resolver-BYRIQOQT.mjs.map +7 -0
- package/dist/lib/browser/react-root-GPTKI5H2.mjs +21 -0
- package/dist/lib/browser/react-root-GPTKI5H2.mjs.map +7 -0
- package/dist/lib/browser/react-surface-LT5JJTPR.mjs +41 -0
- package/dist/lib/browser/react-surface-LT5JJTPR.mjs.map +7 -0
- package/dist/lib/browser/spotlight-dismiss-67PHYS5B.mjs +66 -0
- package/dist/lib/browser/spotlight-dismiss-67PHYS5B.mjs.map +7 -0
- package/dist/lib/browser/state-A3PGDWWZ.mjs +48 -0
- 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-UAWM4B2S.mjs +1166 -0
- package/dist/lib/node-esm/chunk-UAWM4B2S.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-VIDE5UMB.mjs +31 -0
- package/dist/lib/node-esm/chunk-VIDE5UMB.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +102 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/lib/node-esm/operation-resolver-BDTFNCS2.mjs +206 -0
- package/dist/lib/node-esm/operation-resolver-BDTFNCS2.mjs.map +7 -0
- package/dist/lib/node-esm/react-root-GRG2OAI2.mjs +22 -0
- package/dist/lib/node-esm/react-root-GRG2OAI2.mjs.map +7 -0
- package/dist/lib/node-esm/react-surface-TCUSDIN2.mjs +42 -0
- package/dist/lib/node-esm/react-surface-TCUSDIN2.mjs.map +7 -0
- package/dist/lib/node-esm/spotlight-dismiss-RMLRZUVY.mjs +68 -0
- package/dist/lib/node-esm/spotlight-dismiss-RMLRZUVY.mjs.map +7 -0
- package/dist/lib/node-esm/state-ZCFZTTPL.mjs +49 -0
- 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 +7 -0
- package/dist/types/src/SimpleLayoutPlugin.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +7 -0
- package/dist/types/src/capabilities/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/operation-resolver/index.d.ts +3 -0
- package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +5 -0
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-root/index.d.ts +6 -0
- package/dist/types/src/capabilities/react-root/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-root/react-root.d.ts +9 -0
- package/dist/types/src/capabilities/react-root/react-root.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface/index.d.ts +3 -0
- package/dist/types/src/capabilities/react-surface/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts +5 -0
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +1 -0
- package/dist/types/src/capabilities/spotlight-dismiss/index.d.ts +3 -0
- package/dist/types/src/capabilities/spotlight-dismiss/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/spotlight-dismiss/spotlight-dismiss.d.ts +14 -0
- package/dist/types/src/capabilities/spotlight-dismiss/spotlight-dismiss.d.ts.map +1 -0
- package/dist/types/src/capabilities/state/index.d.ts +13 -0
- package/dist/types/src/capabilities/state/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/state/state.d.ts +19 -0
- package/dist/types/src/capabilities/state/state.d.ts.map +1 -0
- package/dist/types/src/capabilities/url-handler/index.d.ts +3 -0
- package/dist/types/src/capabilities/url-handler/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/url-handler/url-handler.d.ts +12 -0
- package/dist/types/src/capabilities/url-handler/url-handler.d.ts.map +1 -0
- package/dist/types/src/components/ContentError.d.ts +5 -0
- package/dist/types/src/components/ContentError.d.ts.map +1 -0
- package/dist/types/src/components/ContentError.stories.d.ts +41 -0
- package/dist/types/src/components/ContentError.stories.d.ts.map +1 -0
- package/dist/types/src/components/ContentLoading.d.ts +3 -0
- package/dist/types/src/components/ContentLoading.d.ts.map +1 -0
- package/dist/types/src/components/ContentLoading.stories.d.ts +13 -0
- package/dist/types/src/components/ContentLoading.stories.d.ts.map +1 -0
- package/dist/types/src/components/Dialog/Dialog.d.ts +3 -0
- package/dist/types/src/components/Dialog/Dialog.d.ts.map +1 -0
- package/dist/types/src/components/Dialog/index.d.ts +2 -0
- package/dist/types/src/components/Dialog/index.d.ts.map +1 -0
- package/dist/types/src/components/Home/Home.d.ts +7 -0
- package/dist/types/src/components/Home/Home.d.ts.map +1 -0
- package/dist/types/src/components/Home/index.d.ts +2 -0
- package/dist/types/src/components/Home/index.d.ts.map +1 -0
- 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 +4 -0
- package/dist/types/src/components/Popover/Popover.d.ts.map +1 -0
- package/dist/types/src/components/Popover/index.d.ts +2 -0
- package/dist/types/src/components/Popover/index.d.ts.map +1 -0
- 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 +9 -0
- package/dist/types/src/components/SimpleLayout/Drawer.d.ts.map +1 -0
- package/dist/types/src/components/SimpleLayout/Main.d.ts +9 -0
- package/dist/types/src/components/SimpleLayout/Main.d.ts.map +1 -0
- package/dist/types/src/components/SimpleLayout/NavBar.d.ts +18 -0
- package/dist/types/src/components/SimpleLayout/NavBar.d.ts.map +1 -0
- package/dist/types/src/components/SimpleLayout/NavBar.stories.d.ts +43 -0
- package/dist/types/src/components/SimpleLayout/NavBar.stories.d.ts.map +1 -0
- package/dist/types/src/components/SimpleLayout/SimpleLayout.d.ts +3 -0
- package/dist/types/src/components/SimpleLayout/SimpleLayout.d.ts.map +1 -0
- package/dist/types/src/components/SimpleLayout/SimpleLayout.stories.d.ts +47 -0
- package/dist/types/src/components/SimpleLayout/SimpleLayout.stories.d.ts.map +1 -0
- package/dist/types/src/components/SimpleLayout/index.d.ts +5 -0
- package/dist/types/src/components/SimpleLayout/index.d.ts.map +1 -0
- package/dist/types/src/components/Workspace/Workspace.d.ts +11 -0
- package/dist/types/src/components/Workspace/Workspace.d.ts.map +1 -0
- package/dist/types/src/components/Workspace/index.d.ts +2 -0
- package/dist/types/src/components/Workspace/index.d.ts.map +1 -0
- package/dist/types/src/components/hooks.d.ts +5 -0
- package/dist/types/src/components/hooks.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +7 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- 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 +7 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -0
- 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 +12 -0
- package/dist/types/src/hooks/useCompanions.d.ts.map +1 -0
- 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 +7 -0
- package/dist/types/src/hooks/useSimpleLayoutState.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +3 -0
- package/dist/types/src/meta.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +26 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/types/capabilities.d.ts +36 -0
- package/dist/types/src/types/capabilities.d.ts.map +1 -0
- package/dist/types/src/types/events.d.ts +6 -0
- package/dist/types/src/types/events.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +3 -0
- package/dist/types/src/types/index.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +39 -29
- package/src/SimpleLayoutPlugin.ts +25 -8
- package/src/capabilities/index.ts +3 -0
- package/src/capabilities/operation-resolver/operation-resolver.ts +135 -53
- package/src/capabilities/react-root/react-root.tsx +2 -2
- package/src/capabilities/react-surface/index.ts +7 -0
- package/src/capabilities/react-surface/react-surface.tsx +41 -0
- package/src/capabilities/spotlight-dismiss/index.ts +7 -0
- package/src/{hooks/useSpotlightDismiss.ts → capabilities/spotlight-dismiss/spotlight-dismiss.ts} +31 -40
- package/src/capabilities/state/state.tsx +25 -33
- package/src/capabilities/url-handler/index.ts +7 -0
- package/src/capabilities/url-handler/url-handler.ts +157 -0
- package/src/components/ContentError.stories.tsx +1 -1
- package/src/components/ContentLoading.stories.tsx +1 -1
- package/src/components/Dialog/Dialog.tsx +14 -14
- package/src/components/Home/Home.tsx +64 -70
- package/src/components/MobileLayout/MobileLayout.stories.tsx +125 -0
- package/src/components/MobileLayout/MobileLayout.tsx +305 -0
- package/src/components/MobileLayout/index.ts +5 -0
- package/src/components/Popover/Popover.tsx +45 -27
- package/src/components/SimpleLayout/AppBar.stories.tsx +144 -0
- package/src/components/SimpleLayout/AppBar.tsx +101 -0
- package/src/components/SimpleLayout/Drawer.tsx +102 -0
- package/src/components/SimpleLayout/Main.tsx +53 -57
- package/src/components/SimpleLayout/NavBar.stories.tsx +164 -0
- package/src/components/SimpleLayout/NavBar.tsx +29 -86
- package/src/components/SimpleLayout/SimpleLayout.stories.tsx +24 -18
- package/src/components/SimpleLayout/SimpleLayout.tsx +45 -7
- package/src/components/SimpleLayout/index.ts +3 -0
- package/src/components/Workspace/Workspace.tsx +119 -0
- package/src/components/Workspace/index.ts +5 -0
- package/src/components/hooks.ts +26 -0
- package/src/components/index.ts +2 -0
- package/src/hooks/actions.ts +85 -0
- package/src/hooks/index.ts +6 -1
- package/src/hooks/useAppBarProps.ts +112 -0
- package/src/hooks/useCompanions.ts +22 -0
- package/src/hooks/useDrawerActions.ts +98 -0
- package/src/hooks/useNavbarActions.ts +86 -0
- package/src/hooks/useSimpleLayoutState.ts +30 -0
- package/src/translations.ts +6 -0
- package/src/types/capabilities.ts +20 -4
- package/src/types/events.ts +15 -0
- package/src/types/index.ts +1 -0
- package/src/components/SimpleLayout/Banner.tsx +0 -60
- package/src/components/SimpleLayout/NavBarstories.tsx +0 -59
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useMemo } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Surface } from '@dxos/app-framework/ui';
|
|
8
|
+
import { useAppGraph } from '@dxos/app-toolkit/ui';
|
|
9
|
+
import { type Node, useNode } from '@dxos/plugin-graph';
|
|
10
|
+
import { Layout } from '@dxos/react-ui';
|
|
11
|
+
import { ATTENDABLE_PATH_SEPARATOR } from '@dxos/react-ui-attention';
|
|
12
|
+
import { MenuProvider, ToolbarMenu, useMenuActions } from '@dxos/react-ui-menu';
|
|
13
|
+
|
|
14
|
+
import { useCompanions, useDrawerActions, useSimpleLayoutState } from '../../hooks';
|
|
15
|
+
import { ContentError } from '../ContentError';
|
|
16
|
+
import { ContentLoading } from '../ContentLoading';
|
|
17
|
+
|
|
18
|
+
const DRAWER_NAME = 'SimpleLayout.Drawer';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Companion drawer component.
|
|
22
|
+
*/
|
|
23
|
+
export const Drawer = () => {
|
|
24
|
+
const { graph } = useAppGraph();
|
|
25
|
+
const { state: layoutState } = useSimpleLayoutState();
|
|
26
|
+
|
|
27
|
+
const placeholder = useMemo(() => <ContentLoading />, []);
|
|
28
|
+
|
|
29
|
+
// Get all companions for the current active (primary) item.
|
|
30
|
+
const activeId = layoutState.active ?? layoutState.workspace;
|
|
31
|
+
const companions = useCompanions(activeId);
|
|
32
|
+
const { companionId, variant } = useSelectedCompanion(companions, layoutState.companionVariant);
|
|
33
|
+
|
|
34
|
+
// Get node for the selected companion.
|
|
35
|
+
const node = useNode(graph, companionId);
|
|
36
|
+
const parentNode = useNode(graph, activeId);
|
|
37
|
+
|
|
38
|
+
// Build Surface data for the companion content.
|
|
39
|
+
const data = useMemo(() => {
|
|
40
|
+
return (
|
|
41
|
+
node && {
|
|
42
|
+
attendableId: companionId,
|
|
43
|
+
subject: node.data,
|
|
44
|
+
companionTo: parentNode?.data,
|
|
45
|
+
properties: node.properties,
|
|
46
|
+
variant,
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
}, [companionId, node, parentNode, variant]);
|
|
50
|
+
|
|
51
|
+
// Get drawer actions (tabs + toolbar buttons).
|
|
52
|
+
const { actions, onAction } = useDrawerActions(DRAWER_NAME);
|
|
53
|
+
const menu = useMenuActions(actions);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Layout.Main toolbar>
|
|
57
|
+
<MenuProvider {...menu} onAction={onAction} alwaysActive>
|
|
58
|
+
<ToolbarMenu density='coarse' />
|
|
59
|
+
</MenuProvider>
|
|
60
|
+
<Surface.Surface role='article' data={data} limit={1} fallback={ContentError} placeholder={placeholder} />
|
|
61
|
+
</Layout.Main>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
Drawer.displayName = DRAWER_NAME;
|
|
66
|
+
|
|
67
|
+
/** Parse entry ID to extract primary ID and variant. */
|
|
68
|
+
const parseEntryId = (entryId: string) => {
|
|
69
|
+
const [id, variant] = entryId.split(ATTENDABLE_PATH_SEPARATOR);
|
|
70
|
+
return { id, variant };
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Resolves which companion to show based on variant preference.
|
|
75
|
+
* Falls back to first available if preferred variant not available.
|
|
76
|
+
*/
|
|
77
|
+
const useSelectedCompanion = (companions: Node.Node[], preferredVariant?: string) => {
|
|
78
|
+
const selectedCompanion = useMemo(() => {
|
|
79
|
+
if (companions.length === 0) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Try to find companion matching the preferred variant.
|
|
84
|
+
if (preferredVariant) {
|
|
85
|
+
const preferred = companions.find((c) => {
|
|
86
|
+
const { variant } = parseEntryId(c.id);
|
|
87
|
+
return variant === preferredVariant;
|
|
88
|
+
});
|
|
89
|
+
if (preferred) {
|
|
90
|
+
return preferred;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Fallback to first companion.
|
|
95
|
+
return companions[0];
|
|
96
|
+
}, [companions, preferredVariant]);
|
|
97
|
+
|
|
98
|
+
const companionId = selectedCompanion?.id;
|
|
99
|
+
const { variant } = parseEntryId(companionId ?? '');
|
|
100
|
+
|
|
101
|
+
return { selectedCompanion, companionId, variant };
|
|
102
|
+
};
|
|
@@ -2,84 +2,80 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, {
|
|
5
|
+
import React, { useMemo } from 'react';
|
|
6
6
|
|
|
7
|
-
import { Surface
|
|
7
|
+
import { Surface } from '@dxos/app-framework/ui';
|
|
8
|
+
import { useAppGraph } from '@dxos/app-toolkit/ui';
|
|
8
9
|
import { useNode } from '@dxos/plugin-graph';
|
|
9
|
-
import {
|
|
10
|
-
import { ATTENDABLE_PATH_SEPARATOR } from '@dxos/react-ui-attention';
|
|
10
|
+
import { useAttentionAttributes } from '@dxos/react-ui-attention';
|
|
11
11
|
import { mx } from '@dxos/ui-theme';
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { useAppBarProps, useNavbarActions, useSimpleLayoutState } from '../../hooks';
|
|
14
14
|
import { ContentError } from '../ContentError';
|
|
15
15
|
import { ContentLoading } from '../ContentLoading';
|
|
16
|
-
import {
|
|
16
|
+
import { useLoadDescendents } from '../hooks';
|
|
17
|
+
import { useMobileLayout } from '../MobileLayout/MobileLayout';
|
|
17
18
|
|
|
18
|
-
import {
|
|
19
|
+
import { AppBar } from './AppBar';
|
|
19
20
|
import { NavBar } from './NavBar';
|
|
20
21
|
|
|
22
|
+
const MAIN_NAME = 'SimpleLayout.Main';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Main content component.
|
|
26
|
+
*/
|
|
21
27
|
export const Main = () => {
|
|
22
|
-
const
|
|
23
|
-
const id =
|
|
24
|
-
const
|
|
25
|
-
const
|
|
28
|
+
const { state } = useSimpleLayoutState();
|
|
29
|
+
const id = state.active ?? state.workspace;
|
|
30
|
+
const attentionAttrs = useAttentionAttributes(id);
|
|
31
|
+
const { keyboardOpen } = useMobileLayout(MAIN_NAME);
|
|
32
|
+
const { actions, onAction } = useNavbarActions();
|
|
33
|
+
const appBarProps = useAppBarProps();
|
|
26
34
|
|
|
27
35
|
const placeholder = useMemo(() => <ContentLoading />, []);
|
|
28
36
|
|
|
29
|
-
const {
|
|
30
|
-
const
|
|
31
|
-
|
|
37
|
+
const { graph } = useAppGraph();
|
|
38
|
+
const node = useNode(graph, id);
|
|
39
|
+
const data = useMemo(() => {
|
|
40
|
+
return (
|
|
32
41
|
node && {
|
|
33
42
|
attendableId: id,
|
|
34
43
|
subject: node.data,
|
|
35
44
|
properties: node.properties,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
);
|
|
45
|
+
popoverAnchorId: state.popoverAnchorId,
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
}, [id, node, node?.data, node?.properties, state.popoverAnchorId]);
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
console.log('[navigate]', nextActiveId);
|
|
45
|
-
};
|
|
50
|
+
// Ensures that children are loaded so that they are available to navigate to.
|
|
51
|
+
useLoadDescendents(id);
|
|
46
52
|
|
|
47
|
-
|
|
53
|
+
// TODO(burdon): BUG: When showing ANY statusbar the size progressively shrinks when the keyboard opens/closes.
|
|
54
|
+
const showNavBar = !keyboardOpen && !state.isPopover && state.drawerState === 'closed';
|
|
48
55
|
|
|
49
56
|
return (
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
/>
|
|
72
|
-
</section>
|
|
73
|
-
</Activity>
|
|
74
|
-
{showNavBar && <NavBar activeId={id} onActiveIdChange={handleActiveIdChange} />}
|
|
75
|
-
</div>
|
|
76
|
-
</NaturalMain.Content>
|
|
77
|
-
</NaturalMain.Root>
|
|
57
|
+
<div
|
|
58
|
+
role='none'
|
|
59
|
+
className={mx(
|
|
60
|
+
'bs-full grid overflow-hidden bg-toolbarSurface',
|
|
61
|
+
showNavBar ? 'grid-rows-[var(--rail-action)_1fr_var(--toolbar-size)]' : 'grid-rows-[var(--rail-action)_1fr]',
|
|
62
|
+
)}
|
|
63
|
+
{...attentionAttrs}
|
|
64
|
+
>
|
|
65
|
+
<AppBar {...appBarProps} />
|
|
66
|
+
<article className='bs-full overflow-hidden bg-baseSurface'>
|
|
67
|
+
<Surface.Surface
|
|
68
|
+
key={id}
|
|
69
|
+
role='article'
|
|
70
|
+
data={data}
|
|
71
|
+
limit={1}
|
|
72
|
+
fallback={ContentError}
|
|
73
|
+
placeholder={placeholder}
|
|
74
|
+
/>
|
|
75
|
+
</article>
|
|
76
|
+
{showNavBar && <NavBar classNames='border-bs border-subduedSeparator' actions={actions} onAction={onAction} />}
|
|
77
|
+
</div>
|
|
78
78
|
);
|
|
79
79
|
};
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
const parseEntryId = (entryId: string) => {
|
|
83
|
-
const [id, variant] = entryId.split(ATTENDABLE_PATH_SEPARATOR);
|
|
84
|
-
return { id, variant };
|
|
85
|
-
};
|
|
81
|
+
Main.displayName = MAIN_NAME;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Atom } from '@effect-atom/atom-react';
|
|
6
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
7
|
+
import React, { useMemo } from 'react';
|
|
8
|
+
import { type Mock, expect, fn, screen, userEvent, within } from 'storybook/test';
|
|
9
|
+
|
|
10
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
11
|
+
import { type ActionGraphProps, createGapSeparator, createMenuAction, createMenuItemGroup } from '@dxos/react-ui-menu';
|
|
12
|
+
import { withRegistry } from '@dxos/storybook-utils';
|
|
13
|
+
|
|
14
|
+
import { translations } from '../../translations';
|
|
15
|
+
|
|
16
|
+
import { NavBar } from './NavBar';
|
|
17
|
+
|
|
18
|
+
const MAIN_MENU_GROUP_ID = 'navbar-main-menu';
|
|
19
|
+
|
|
20
|
+
const buildEmptyActions = (): ActionGraphProps => ({ nodes: [], edges: [] });
|
|
21
|
+
|
|
22
|
+
const buildCompanionOnlyActions = (): ActionGraphProps => {
|
|
23
|
+
const result: ActionGraphProps = { nodes: [], edges: [] };
|
|
24
|
+
const companions = [
|
|
25
|
+
createMenuAction('companion-browse', () => console.log('Browse'), {
|
|
26
|
+
icon: 'ph--house--regular',
|
|
27
|
+
label: 'Browse',
|
|
28
|
+
iconOnly: true,
|
|
29
|
+
}),
|
|
30
|
+
createMenuAction('companion-notifications', () => console.log('Notifications'), {
|
|
31
|
+
icon: 'ph--bell--regular',
|
|
32
|
+
label: 'Notifications',
|
|
33
|
+
iconOnly: true,
|
|
34
|
+
}),
|
|
35
|
+
createMenuAction('companion-profile', () => console.log('Profile'), {
|
|
36
|
+
icon: 'ph--user--regular',
|
|
37
|
+
label: 'Profile',
|
|
38
|
+
iconOnly: true,
|
|
39
|
+
}),
|
|
40
|
+
];
|
|
41
|
+
result.nodes.push(...companions);
|
|
42
|
+
result.edges.push(...companions.map((c) => ({ source: 'root', target: c.id })));
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const buildDefaultActions = (): ActionGraphProps => {
|
|
47
|
+
const result: ActionGraphProps = { nodes: [], edges: [] };
|
|
48
|
+
const gapSeparator = createGapSeparator('navbar-gap');
|
|
49
|
+
const mainMenuGroup = createMenuItemGroup(MAIN_MENU_GROUP_ID, {
|
|
50
|
+
variant: 'dropdownMenu',
|
|
51
|
+
icon: 'ph--plus--regular',
|
|
52
|
+
iconOnly: true,
|
|
53
|
+
label: 'Main menu',
|
|
54
|
+
testId: 'simpleLayoutPlugin.addSpace',
|
|
55
|
+
});
|
|
56
|
+
const companions = [
|
|
57
|
+
createMenuAction('companion-browse', () => console.log('Browse'), {
|
|
58
|
+
icon: 'ph--house--regular',
|
|
59
|
+
label: 'Browse',
|
|
60
|
+
iconOnly: true,
|
|
61
|
+
}),
|
|
62
|
+
createMenuAction('companion-notifications', () => console.log('Notifications'), {
|
|
63
|
+
icon: 'ph--bell--regular',
|
|
64
|
+
label: 'Notifications',
|
|
65
|
+
iconOnly: true,
|
|
66
|
+
}),
|
|
67
|
+
];
|
|
68
|
+
const menuActions = [
|
|
69
|
+
createMenuAction('action-create-space', () => console.log('Create space'), {
|
|
70
|
+
icon: 'ph--planet--regular',
|
|
71
|
+
label: 'Create space',
|
|
72
|
+
}),
|
|
73
|
+
createMenuAction('action-join-space', () => console.log('Join space'), {
|
|
74
|
+
icon: 'ph--sign-in--regular',
|
|
75
|
+
label: 'Join space',
|
|
76
|
+
}),
|
|
77
|
+
createMenuAction('action-settings', () => console.log('Settings'), {
|
|
78
|
+
icon: 'ph--gear--regular',
|
|
79
|
+
label: 'Settings',
|
|
80
|
+
}),
|
|
81
|
+
];
|
|
82
|
+
result.nodes.push(...companions, ...gapSeparator.nodes, mainMenuGroup, ...menuActions);
|
|
83
|
+
result.edges.push(
|
|
84
|
+
...companions.map((c) => ({ source: 'root', target: c.id })),
|
|
85
|
+
...gapSeparator.edges,
|
|
86
|
+
{ source: 'root', target: mainMenuGroup.id },
|
|
87
|
+
...menuActions.map((action) => ({ source: MAIN_MENU_GROUP_ID, target: action.id })),
|
|
88
|
+
);
|
|
89
|
+
return result;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const meta = {
|
|
93
|
+
title: 'plugins/plugin-simple-layout/NavBar',
|
|
94
|
+
component: NavBar,
|
|
95
|
+
decorators: [withTheme(), withRegistry],
|
|
96
|
+
parameters: {
|
|
97
|
+
layout: 'fullscreen',
|
|
98
|
+
translations,
|
|
99
|
+
},
|
|
100
|
+
} satisfies Meta<typeof NavBar>;
|
|
101
|
+
|
|
102
|
+
export default meta;
|
|
103
|
+
|
|
104
|
+
type Story = StoryObj<typeof meta>;
|
|
105
|
+
|
|
106
|
+
const DefaultStory = ({ onAction }: { onAction: (action: { id: string }) => void }) => {
|
|
107
|
+
const actions = useMemo(() => Atom.make(buildDefaultActions()).pipe(Atom.keepAlive), []);
|
|
108
|
+
|
|
109
|
+
return <NavBar classNames='border-bs border-separator' actions={actions} onAction={onAction} />;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const Default: Story = {
|
|
113
|
+
tags: ['test'],
|
|
114
|
+
args: {
|
|
115
|
+
onAction: fn(),
|
|
116
|
+
} as any,
|
|
117
|
+
render: (args: any) => <DefaultStory onAction={args.onAction} />,
|
|
118
|
+
play: async ({ args, canvasElement }) => {
|
|
119
|
+
const canvas = within(canvasElement);
|
|
120
|
+
|
|
121
|
+
// Verify the navbar renders with the toolbar.
|
|
122
|
+
await expect(canvas.getByRole('toolbar')).toBeInTheDocument();
|
|
123
|
+
|
|
124
|
+
// Test companion action click (Browse button).
|
|
125
|
+
const browseButton = canvas.getByRole('button', { name: /browse/i });
|
|
126
|
+
await expect(browseButton).toBeInTheDocument();
|
|
127
|
+
await userEvent.click(browseButton);
|
|
128
|
+
await expect(args.onAction).toHaveBeenCalledTimes(1);
|
|
129
|
+
await expect((args.onAction as Mock).mock.calls[0][0]).toHaveProperty('id', 'companion-browse');
|
|
130
|
+
|
|
131
|
+
// Test dropdown menu opens and action fires.
|
|
132
|
+
const menuTrigger = canvas.getByRole('button', { name: /main menu/i });
|
|
133
|
+
await expect(menuTrigger).toBeInTheDocument();
|
|
134
|
+
await userEvent.click(menuTrigger);
|
|
135
|
+
|
|
136
|
+
// Wait for menu to open and click an action (menu items render in a portal).
|
|
137
|
+
const createSpaceAction = await screen.findByRole('menuitem', { name: /create space/i });
|
|
138
|
+
await userEvent.click(createSpaceAction);
|
|
139
|
+
await expect(args.onAction).toHaveBeenCalledTimes(2);
|
|
140
|
+
await expect((args.onAction as Mock).mock.calls[1][0]).toHaveProperty('id', 'action-create-space');
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const CompanionsOnlyStory = () => {
|
|
145
|
+
const actions = useMemo(() => Atom.make(buildCompanionOnlyActions()).pipe(Atom.keepAlive), []);
|
|
146
|
+
|
|
147
|
+
return <NavBar actions={actions} onAction={(action) => console.log('Action:', action.id)} />;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export const CompanionsOnly: Story = {
|
|
151
|
+
args: {} as any,
|
|
152
|
+
render: () => <CompanionsOnlyStory />,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const EmptyStory = () => {
|
|
156
|
+
const actions = useMemo(() => Atom.make(buildEmptyActions()).pipe(Atom.keepAlive), []);
|
|
157
|
+
|
|
158
|
+
return <NavBar actions={actions} onAction={(action) => console.log('Action:', action.id)} />;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const Empty: Story = {
|
|
162
|
+
args: {} as any,
|
|
163
|
+
render: () => <EmptyStory />,
|
|
164
|
+
};
|
|
@@ -2,96 +2,39 @@
|
|
|
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 { Node, useActionRunner, useConnections } from '@dxos/plugin-graph';
|
|
8
|
+
import { type ThemedClassName } from '@dxos/react-ui';
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
export
|
|
31
|
-
|
|
32
|
-
onActiveIdChange?: (nextActiveId: string | null) => void;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export const NavBar = ({ activeId, onActiveIdChange }: NavBarProps) => {
|
|
36
|
-
const { t } = useTranslation(meta.id);
|
|
37
|
-
const { graph } = useAppGraph();
|
|
38
|
-
const runAction = useActionRunner();
|
|
39
|
-
|
|
40
|
-
const connections = useConnections(graph, Node.RootId);
|
|
41
|
-
const menuActions = connections.filter((node) => node.properties.disposition === 'menu');
|
|
42
|
-
|
|
43
|
-
const isBrowseActive = activeId !== 'notifications' && activeId !== 'profile';
|
|
10
|
+
type ActionExecutor,
|
|
11
|
+
type ActionGraphProps,
|
|
12
|
+
MenuProvider,
|
|
13
|
+
ToolbarMenu,
|
|
14
|
+
useMenuActions,
|
|
15
|
+
} from '@dxos/react-ui-menu';
|
|
16
|
+
import { mx } from '@dxos/ui-theme';
|
|
17
|
+
|
|
18
|
+
const NAVBAR_NAME = 'SimpleLayout.NavBar';
|
|
19
|
+
|
|
20
|
+
export type NavBarProps = ThemedClassName<{
|
|
21
|
+
/** Action graph atom for the toolbar. */
|
|
22
|
+
actions: Atom.Atom<ActionGraphProps>;
|
|
23
|
+
/** Action executor callback. */
|
|
24
|
+
onAction?: ActionExecutor;
|
|
25
|
+
}>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Presentational navbar component that renders a toolbar from an action graph.
|
|
29
|
+
*/
|
|
30
|
+
export const NavBar = ({ classNames, actions, onAction }: NavBarProps) => {
|
|
31
|
+
const menu = useMenuActions(actions);
|
|
44
32
|
|
|
45
33
|
return (
|
|
46
|
-
<
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
'fixed inset-inline-0',
|
|
50
|
-
'grid grid-cols-[min-content_min-content] gap-2 place-content-center',
|
|
51
|
-
'block-end-[--dx-mobile-bottombar-inset-bottom,0px] bs-[--dx-mobile-bottombar-content-height,64px]',
|
|
52
|
-
'bg-baseSurface border-bs border-separator',
|
|
53
|
-
surfaceZIndex({ level: 'menu' }),
|
|
54
|
-
)}
|
|
55
|
-
>
|
|
56
|
-
<ButtonGroup>
|
|
57
|
-
<IconButton
|
|
58
|
-
{...buttonProps}
|
|
59
|
-
label={t('browse label')}
|
|
60
|
-
icon='ph--squares-four--regular'
|
|
61
|
-
onClick={() => onActiveIdChange?.(null)}
|
|
62
|
-
variant={isBrowseActive ? 'primary' : 'default'}
|
|
63
|
-
{...(isBrowseActive && { 'aria-current': 'location' })}
|
|
64
|
-
/>
|
|
65
|
-
<IconButton
|
|
66
|
-
{...buttonProps}
|
|
67
|
-
label={t('notifications label')}
|
|
68
|
-
icon='ph--bell-simple--regular'
|
|
69
|
-
onClick={() => onActiveIdChange?.('notifications')}
|
|
70
|
-
variant={activeId === 'notifications' ? 'primary' : 'default'}
|
|
71
|
-
{...(activeId === 'notifications' && { 'aria-current': 'location' })}
|
|
72
|
-
/>
|
|
73
|
-
<Button
|
|
74
|
-
variant={activeId === 'profile' ? 'primary' : 'default'}
|
|
75
|
-
onClick={() => onActiveIdChange?.('profile')}
|
|
76
|
-
classNames={buttonProps.classNames}
|
|
77
|
-
>
|
|
78
|
-
<span className='sr-only'>{t('profile label')}</span>
|
|
79
|
-
<Avatar.Root>
|
|
80
|
-
<Avatar.Label classNames='sr-only'>Profile display name</Avatar.Label>
|
|
81
|
-
<Avatar.Content size={8} status='active' hue='cyan' fallback='🗿' />
|
|
82
|
-
</Avatar.Root>
|
|
83
|
-
</Button>
|
|
84
|
-
</ButtonGroup>
|
|
85
|
-
<MenuProvider onAction={runAction}>
|
|
86
|
-
<DropdownMenu.Root items={menuActions}>
|
|
87
|
-
<Tooltip.Trigger asChild content={t('app menu label')} side='right'>
|
|
88
|
-
<DropdownMenu.Trigger asChild data-testid='spacePlugin.addSpace'>
|
|
89
|
-
<IconButton {...buttonProps} icon='ph--plus--regular' label={t('main menu label')} />
|
|
90
|
-
</DropdownMenu.Trigger>
|
|
91
|
-
</Tooltip.Trigger>
|
|
92
|
-
</DropdownMenu.Root>
|
|
93
|
-
</MenuProvider>
|
|
94
|
-
</nav>
|
|
95
|
-
</DensityProvider>
|
|
34
|
+
<MenuProvider {...menu} onAction={onAction} alwaysActive>
|
|
35
|
+
<ToolbarMenu density='coarse' classNames={mx(classNames)} />
|
|
36
|
+
</MenuProvider>
|
|
96
37
|
);
|
|
97
38
|
};
|
|
39
|
+
|
|
40
|
+
NavBar.displayName = NAVBAR_NAME;
|
|
@@ -5,14 +5,15 @@
|
|
|
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';
|
|
10
11
|
import { ClientOperation, ClientPlugin } from '@dxos/plugin-client';
|
|
11
12
|
import { SearchPlugin } from '@dxos/plugin-search';
|
|
12
13
|
import { SpacePlugin } from '@dxos/plugin-space';
|
|
13
14
|
import { SpaceOperation } from '@dxos/plugin-space/types';
|
|
14
15
|
import { corePlugins } from '@dxos/plugin-testing';
|
|
15
|
-
import { withTheme } from '@dxos/react-ui/testing';
|
|
16
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
16
17
|
import { translations as searchTranslation } from '@dxos/react-ui-searchlist';
|
|
17
18
|
import { Collection } from '@dxos/schema';
|
|
18
19
|
|
|
@@ -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' });
|
|
@@ -69,17 +70,10 @@ const createPluginManager = ({ isPopover }: { isPopover: boolean }) => {
|
|
|
69
70
|
plugins: [
|
|
70
71
|
...corePlugins(),
|
|
71
72
|
ClientPlugin({
|
|
72
|
-
|
|
73
|
-
Effect.gen(function* () {
|
|
74
|
-
yield* Effect.promise(() => client.halo.createIdentity());
|
|
75
|
-
yield* Effect.promise(async () => {
|
|
76
|
-
await client.spaces.create({ name: 'Work Space' });
|
|
77
|
-
await client.spaces.create({ name: 'Shared Project' });
|
|
78
|
-
});
|
|
79
|
-
}),
|
|
73
|
+
types: [Collection.Collection],
|
|
80
74
|
}),
|
|
81
|
-
SpacePlugin({}),
|
|
82
75
|
SearchPlugin(),
|
|
76
|
+
SpacePlugin({}),
|
|
83
77
|
TestPlugin({ isPopover }),
|
|
84
78
|
],
|
|
85
79
|
});
|
|
@@ -98,10 +92,22 @@ export default meta;
|
|
|
98
92
|
|
|
99
93
|
type Story = StoryObj<typeof meta>;
|
|
100
94
|
|
|
95
|
+
/**
|
|
96
|
+
* NOTE: To expose to iphone on network:
|
|
97
|
+
* `moon run storybook-react:serve dev -H 0.0.0.0`
|
|
98
|
+
*/
|
|
101
99
|
export const Default: Story = {
|
|
102
|
-
decorators: [
|
|
100
|
+
decorators: [
|
|
101
|
+
withTheme(),
|
|
102
|
+
withLayout({ layout: 'column', classNames: 'relative' }),
|
|
103
|
+
createPluginManager({ isPopover: false }),
|
|
104
|
+
],
|
|
103
105
|
};
|
|
104
106
|
|
|
105
107
|
export const Popover: Story = {
|
|
106
|
-
decorators: [
|
|
108
|
+
decorators: [
|
|
109
|
+
withTheme(),
|
|
110
|
+
withLayout({ layout: 'column', classNames: 'relative' }),
|
|
111
|
+
createPluginManager({ isPopover: true }),
|
|
112
|
+
],
|
|
107
113
|
};
|