@dxos/plugin-simple-layout 0.0.0 → 0.8.4-main.69d29f4
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-CLPGTNWJ.mjs +29 -0
- package/dist/lib/browser/chunk-CLPGTNWJ.mjs.map +7 -0
- package/dist/lib/browser/chunk-FK4M7GJV.mjs +613 -0
- package/dist/lib/browser/chunk-FK4M7GJV.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +94 -0
- package/dist/lib/browser/index.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -0
- package/dist/lib/browser/operation-resolver-LTB63NKP.mjs +168 -0
- package/dist/lib/browser/operation-resolver-LTB63NKP.mjs.map +7 -0
- package/dist/lib/browser/react-root-6ARAPH3O.mjs +21 -0
- package/dist/lib/browser/react-root-6ARAPH3O.mjs.map +7 -0
- package/dist/lib/browser/react-surface-SO7B23GS.mjs +39 -0
- package/dist/lib/browser/react-surface-SO7B23GS.mjs.map +7 -0
- package/dist/lib/browser/spotlight-dismiss-VSNOPETH.mjs +66 -0
- package/dist/lib/browser/spotlight-dismiss-VSNOPETH.mjs.map +7 -0
- package/dist/lib/browser/state-H4IGICBB.mjs +45 -0
- package/dist/lib/browser/state-H4IGICBB.mjs.map +7 -0
- package/dist/lib/browser/url-handler-7CFGTLNG.mjs +54 -0
- package/dist/lib/browser/url-handler-7CFGTLNG.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-EGFZAVBD.mjs +614 -0
- package/dist/lib/node-esm/chunk-EGFZAVBD.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-MUVVYBUE.mjs +31 -0
- package/dist/lib/node-esm/chunk-MUVVYBUE.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +95 -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-7O6O7T4Q.mjs +169 -0
- package/dist/lib/node-esm/operation-resolver-7O6O7T4Q.mjs.map +7 -0
- package/dist/lib/node-esm/react-root-2CPA2ZUS.mjs +22 -0
- package/dist/lib/node-esm/react-root-2CPA2ZUS.mjs.map +7 -0
- package/dist/lib/node-esm/react-surface-FKAV56MO.mjs +40 -0
- package/dist/lib/node-esm/react-surface-FKAV56MO.mjs.map +7 -0
- package/dist/lib/node-esm/spotlight-dismiss-L5PCWIJG.mjs +68 -0
- package/dist/lib/node-esm/spotlight-dismiss-L5PCWIJG.mjs.map +7 -0
- package/dist/lib/node-esm/state-QIU2LMLT.mjs +46 -0
- package/dist/lib/node-esm/state-QIU2LMLT.mjs.map +7 -0
- package/dist/lib/node-esm/url-handler-4LYP3JM7.mjs +55 -0
- package/dist/lib/node-esm/url-handler-4LYP3JM7.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 +10 -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 +35 -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/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/Banner.d.ts +8 -0
- package/dist/types/src/components/SimpleLayout/Banner.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 +8 -0
- package/dist/types/src/components/SimpleLayout/NavBar.d.ts.map +1 -0
- package/dist/types/src/components/SimpleLayout/NavBar.stories.d.ts +39 -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 +37 -0
- package/dist/types/src/components/SimpleLayout/SimpleLayout.stories.d.ts.map +1 -0
- package/dist/types/src/components/SimpleLayout/index.d.ts +2 -0
- package/dist/types/src/components/SimpleLayout/index.d.ts.map +1 -0
- package/dist/types/src/components/Workspace/Workspace.d.ts +9 -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 +6 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +2 -0
- package/dist/types/src/hooks/index.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 +20 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/types/capabilities.d.ts +31 -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 +29 -24
- package/src/SimpleLayoutPlugin.ts +20 -4
- package/src/capabilities/index.ts +3 -0
- package/src/capabilities/operation-resolver/operation-resolver.ts +82 -39
- package/src/capabilities/react-surface/index.ts +7 -0
- package/src/capabilities/react-surface/react-surface.tsx +40 -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 +21 -32
- package/src/capabilities/url-handler/index.ts +7 -0
- package/src/capabilities/url-handler/url-handler.ts +80 -0
- package/src/components/Dialog/Dialog.tsx +14 -14
- package/src/components/Home/Home.tsx +53 -61
- package/src/components/Popover/Popover.tsx +45 -27
- package/src/components/SimpleLayout/Banner.tsx +50 -28
- package/src/components/SimpleLayout/Main.tsx +40 -44
- package/src/components/SimpleLayout/NavBar.tsx +18 -41
- package/src/components/SimpleLayout/SimpleLayout.stories.tsx +2 -9
- package/src/components/SimpleLayout/SimpleLayout.tsx +0 -1
- package/src/components/Workspace/Workspace.tsx +115 -0
- package/src/components/Workspace/index.ts +5 -0
- package/src/components/hooks.ts +30 -0
- package/src/components/index.ts +1 -0
- package/src/hooks/index.ts +1 -1
- package/src/hooks/useSimpleLayoutState.ts +30 -0
- package/src/types/capabilities.ts +8 -1
- package/src/types/events.ts +14 -0
- package/src/types/index.ts +1 -0
- /package/src/components/SimpleLayout/{NavBarstories.tsx → NavBar.stories.tsx} +0 -0
|
@@ -4,33 +4,33 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
-
import { Surface
|
|
7
|
+
import { Surface } from '@dxos/app-framework/react';
|
|
8
8
|
import { AlertDialog, Dialog as NaturalDialog } from '@dxos/react-ui';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { useSimpleLayoutState } from '../../hooks';
|
|
11
11
|
import { ContentError } from '../ContentError';
|
|
12
12
|
|
|
13
13
|
export const Dialog = () => {
|
|
14
|
-
const
|
|
14
|
+
const { state, updateState } = useSimpleLayoutState();
|
|
15
15
|
|
|
16
|
-
const DialogRoot =
|
|
17
|
-
const DialogOverlay =
|
|
16
|
+
const DialogRoot = state.dialogType === 'alert' ? AlertDialog.Root : NaturalDialog.Root;
|
|
17
|
+
const DialogOverlay = state.dialogType === 'alert' ? AlertDialog.Overlay : NaturalDialog.Overlay;
|
|
18
18
|
|
|
19
19
|
return (
|
|
20
20
|
<DialogRoot
|
|
21
|
-
modal={
|
|
22
|
-
open={
|
|
23
|
-
onOpenChange={(nextOpen) => (
|
|
21
|
+
modal={state.dialogBlockAlign !== 'end'}
|
|
22
|
+
open={state.dialogOpen}
|
|
23
|
+
onOpenChange={(nextOpen) => updateState((s) => ({ ...s, dialogOpen: nextOpen }))}
|
|
24
24
|
>
|
|
25
|
-
{
|
|
26
|
-
<Surface role='dialog' data={
|
|
25
|
+
{state.dialogBlockAlign === 'end' ? (
|
|
26
|
+
<Surface role='dialog' data={state.dialogContent} limit={1} fallback={ContentError} />
|
|
27
27
|
) : (
|
|
28
28
|
<DialogOverlay
|
|
29
|
-
blockAlign={
|
|
30
|
-
classNames={
|
|
31
|
-
style={
|
|
29
|
+
blockAlign={state.dialogBlockAlign}
|
|
30
|
+
classNames={state.dialogOverlayClasses}
|
|
31
|
+
style={state.dialogOverlayStyle}
|
|
32
32
|
>
|
|
33
|
-
<Surface role='dialog' data={
|
|
33
|
+
<Surface role='dialog' data={state.dialogContent} limit={1} fallback={ContentError} />
|
|
34
34
|
</DialogOverlay>
|
|
35
35
|
)}
|
|
36
36
|
</DialogRoot>
|
|
@@ -6,69 +6,80 @@ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { Common } from '@dxos/app-framework';
|
|
8
8
|
import { useAppGraph, useOperationInvoker } from '@dxos/app-framework/react';
|
|
9
|
-
import {
|
|
10
|
-
import { Avatar, Icon,
|
|
11
|
-
import { Card } from '@dxos/react-ui-mosaic';
|
|
9
|
+
import { Node, useConnections } from '@dxos/plugin-graph';
|
|
10
|
+
import { Avatar, Icon, Toolbar, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
11
|
+
import { Card, Layout, Mosaic, type StackTileComponent } from '@dxos/react-ui-mosaic';
|
|
12
12
|
import { SearchList, useSearchListItem, useSearchListResults } from '@dxos/react-ui-searchlist';
|
|
13
13
|
import { mx } from '@dxos/ui-theme';
|
|
14
|
+
import { byPosition } from '@dxos/util';
|
|
14
15
|
|
|
15
16
|
import { meta } from '../../meta';
|
|
17
|
+
import { useLoadDescendents } from '../hooks';
|
|
16
18
|
|
|
17
|
-
type HomeProps =
|
|
19
|
+
export type HomeProps = {};
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Home screen.
|
|
23
|
+
*/
|
|
24
|
+
export const Home = (_: HomeProps) => {
|
|
20
25
|
const { t } = useTranslation(meta.id);
|
|
21
|
-
const
|
|
26
|
+
const userAccountItem = useItemsByDisposition('user-account')[0];
|
|
27
|
+
const pinnedItems = useItemsByDisposition('pin-end', true);
|
|
28
|
+
const workspaceItems = useItemsByDisposition('workspace');
|
|
22
29
|
useLoadDescendents(Node.RootId);
|
|
23
30
|
|
|
31
|
+
const items = useMemo(
|
|
32
|
+
() => [...(userAccountItem ? [userAccountItem] : []), ...pinnedItems, ...workspaceItems],
|
|
33
|
+
[userAccountItem, pinnedItems, workspaceItems],
|
|
34
|
+
);
|
|
35
|
+
|
|
24
36
|
const { results, handleSearch } = useSearchListResults({
|
|
25
|
-
items
|
|
37
|
+
items,
|
|
26
38
|
extract: (node) => toLocalizedString(node.properties.label, t),
|
|
27
39
|
});
|
|
28
40
|
|
|
29
41
|
return (
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
<div className='plb-3'>
|
|
42
|
+
<Layout.Main toolbar>
|
|
43
|
+
<SearchList.Root onSearch={handleSearch}>
|
|
44
|
+
<Toolbar.Root>
|
|
34
45
|
<SearchList.Input placeholder={t('search placeholder')} autoFocus />
|
|
35
|
-
</
|
|
46
|
+
</Toolbar.Root>
|
|
36
47
|
<SearchList.Content>
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
</
|
|
48
|
+
<Mosaic.Container asChild>
|
|
49
|
+
<Mosaic.Viewport padding>
|
|
50
|
+
<Mosaic.Stack items={results} getId={(node) => node.id} Tile={WorkspaceTile} />
|
|
51
|
+
</Mosaic.Viewport>
|
|
52
|
+
</Mosaic.Container>
|
|
42
53
|
</SearchList.Content>
|
|
43
54
|
</SearchList.Root>
|
|
44
|
-
</
|
|
55
|
+
</Layout.Main>
|
|
45
56
|
);
|
|
46
57
|
};
|
|
47
58
|
|
|
48
|
-
const
|
|
59
|
+
const WorkspaceTile: StackTileComponent<Node.Node> = ({ data }) => {
|
|
49
60
|
const { t } = useTranslation(meta.id);
|
|
50
61
|
const { invokePromise } = useOperationInvoker();
|
|
51
62
|
const { selectedValue, registerItem, unregisterItem } = useSearchListItem();
|
|
52
63
|
const ref = useRef<HTMLDivElement>(null);
|
|
53
64
|
|
|
54
65
|
const handleSelect = useCallback(
|
|
55
|
-
() => invokePromise(Common.LayoutOperation.SwitchWorkspace, { subject:
|
|
56
|
-
[invokePromise,
|
|
66
|
+
() => invokePromise(Common.LayoutOperation.SwitchWorkspace, { subject: data.id }),
|
|
67
|
+
[invokePromise, data.id],
|
|
57
68
|
);
|
|
58
69
|
|
|
59
|
-
useLoadDescendents(
|
|
70
|
+
useLoadDescendents(data.id);
|
|
60
71
|
|
|
61
|
-
const name = toLocalizedString(
|
|
62
|
-
const isSelected = selectedValue ===
|
|
72
|
+
const name = toLocalizedString(data.properties.label, t);
|
|
73
|
+
const isSelected = selectedValue === data.id;
|
|
63
74
|
|
|
64
75
|
// Register this workspace with the search context.
|
|
65
76
|
useEffect(() => {
|
|
66
77
|
if (ref.current) {
|
|
67
|
-
registerItem(
|
|
78
|
+
registerItem(data.id, ref.current, handleSelect);
|
|
68
79
|
}
|
|
69
80
|
|
|
70
|
-
return () => unregisterItem(
|
|
71
|
-
}, [
|
|
81
|
+
return () => unregisterItem(data.id);
|
|
82
|
+
}, [data.id, handleSelect, registerItem, unregisterItem]);
|
|
72
83
|
|
|
73
84
|
// Scroll into view when selected.
|
|
74
85
|
useEffect(() => {
|
|
@@ -81,17 +92,18 @@ const Workspace = ({ node }: { node: Node.Node }) => {
|
|
|
81
92
|
<Card.Root
|
|
82
93
|
ref={ref}
|
|
83
94
|
role='button'
|
|
84
|
-
|
|
95
|
+
fullWidth
|
|
96
|
+
tabIndex={-1} // TODO(burdon): Use Mosaic.Focus.
|
|
85
97
|
data-selected={isSelected}
|
|
86
98
|
classNames={mx('dx-focus-ring', isSelected && 'bg-hoverOverlay')}
|
|
87
99
|
onClick={handleSelect}
|
|
88
100
|
>
|
|
89
|
-
<Card.
|
|
101
|
+
<Card.Toolbar density='coarse'>
|
|
90
102
|
<Avatar.Root>
|
|
91
103
|
<Avatar.Content
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
hueVariant='
|
|
104
|
+
icon={data.properties.icon}
|
|
105
|
+
hue={data.properties.hue}
|
|
106
|
+
hueVariant='transparent'
|
|
95
107
|
variant='square'
|
|
96
108
|
size={12}
|
|
97
109
|
fallback={name}
|
|
@@ -99,40 +111,20 @@ const Workspace = ({ node }: { node: Node.Node }) => {
|
|
|
99
111
|
<Avatar.Label>{name}</Avatar.Label>
|
|
100
112
|
<Icon icon='ph--caret-right--regular' />
|
|
101
113
|
</Avatar.Root>
|
|
102
|
-
</Card.
|
|
114
|
+
</Card.Toolbar>
|
|
103
115
|
</Card.Root>
|
|
104
116
|
);
|
|
105
117
|
};
|
|
106
118
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
useEffect(() => {
|
|
111
|
-
const frame = requestAnimationFrame(() => {
|
|
112
|
-
if (nodeId) {
|
|
113
|
-
Graph.expand(graph, nodeId, 'outbound');
|
|
114
|
-
Graph.getConnections(graph, nodeId, 'outbound').forEach((child) => {
|
|
115
|
-
Graph.expand(graph, child.id, 'outbound');
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
return () => cancelAnimationFrame(frame);
|
|
121
|
-
}, [nodeId, graph]);
|
|
119
|
+
/** Filters nodes by disposition. */
|
|
120
|
+
const filterItems = (node: Node.Node, disposition: string) => {
|
|
121
|
+
return node.properties.disposition === disposition;
|
|
122
122
|
};
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
/** Returns root-level items filtered by disposition. */
|
|
125
|
+
const useItemsByDisposition = (disposition: string, sort = false) => {
|
|
125
126
|
const { graph } = useAppGraph();
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const collections = useMemo(
|
|
130
|
-
() => rootConnections.filter((node) => node.properties.disposition === 'collection'),
|
|
131
|
-
[rootConnections],
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
// Get first collection's children as workspaces.
|
|
135
|
-
// TODO(wittjosiah): Support multiple collections or nested workspaces if needed.
|
|
136
|
-
const firstCollection = collections[0];
|
|
137
|
-
return useConnections(graph, firstCollection?.id);
|
|
127
|
+
const connections = useConnections(graph, Node.RootId);
|
|
128
|
+
const filtered = connections.filter((node) => filterItems(node, disposition));
|
|
129
|
+
return sort ? filtered.toSorted((a, b) => byPosition(a.properties, b.properties)) : filtered;
|
|
138
130
|
};
|
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
import { createContext } from '@radix-ui/react-context';
|
|
6
6
|
import React, { type PropsWithChildren, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
7
|
|
|
8
|
-
import { Surface
|
|
9
|
-
import { Popover, type PopoverContentInteractOutsideEvent } from '@dxos/react-ui';
|
|
8
|
+
import { Surface } from '@dxos/app-framework/react';
|
|
9
|
+
import { Popover, type PopoverContentInteractOutsideEvent, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
10
|
+
import { Card } from '@dxos/react-ui-mosaic';
|
|
10
11
|
|
|
11
|
-
import {
|
|
12
|
+
import { useSimpleLayoutState } from '../../hooks';
|
|
13
|
+
import { meta } from '../../meta';
|
|
12
14
|
|
|
13
15
|
const DEBOUNCE_DELAY = 40;
|
|
14
16
|
|
|
@@ -19,7 +21,7 @@ type LayoutPopoverContextValue = {
|
|
|
19
21
|
const [LayoutPopoverProvider, useLayoutPopoverContext] = createContext<LayoutPopoverContextValue>('LayoutPopover');
|
|
20
22
|
|
|
21
23
|
export const PopoverRoot = ({ children }: PropsWithChildren) => {
|
|
22
|
-
const
|
|
24
|
+
const { state } = useSimpleLayoutState();
|
|
23
25
|
const [open, setOpen] = useState(false);
|
|
24
26
|
const virtualRef = useRef<HTMLButtonElement | null>(null);
|
|
25
27
|
const [virtualIter, setVirtualIter] = useState(0);
|
|
@@ -29,22 +31,22 @@ export const PopoverRoot = ({ children }: PropsWithChildren) => {
|
|
|
29
31
|
// the anchor further down the tree or measuring the virtual trigger's client rect.
|
|
30
32
|
useEffect(() => {
|
|
31
33
|
setOpen(false);
|
|
32
|
-
if (
|
|
34
|
+
if (state.popoverOpen) {
|
|
33
35
|
if (debounceRef.current) {
|
|
34
36
|
clearTimeout(debounceRef.current);
|
|
35
37
|
}
|
|
36
|
-
if (
|
|
37
|
-
virtualRef.current =
|
|
38
|
+
if (state.popoverAnchor && virtualRef.current !== state.popoverAnchor) {
|
|
39
|
+
virtualRef.current = state.popoverAnchor ?? null;
|
|
38
40
|
setVirtualIter((iter) => iter + 1);
|
|
39
41
|
}
|
|
40
42
|
debounceRef.current = setTimeout(() => setOpen(true), DEBOUNCE_DELAY);
|
|
41
43
|
}
|
|
42
|
-
}, [
|
|
44
|
+
}, [state.popoverOpen, state.popoverAnchorId, state.popoverAnchor, state.popoverContent]);
|
|
43
45
|
|
|
44
46
|
return (
|
|
45
47
|
<LayoutPopoverProvider setOpen={setOpen}>
|
|
46
48
|
<Popover.Root modal={false} open={open}>
|
|
47
|
-
{
|
|
49
|
+
{state.popoverAnchor && <Popover.VirtualTrigger key={virtualIter} virtualRef={virtualRef} />}
|
|
48
50
|
{children}
|
|
49
51
|
</Popover.Root>
|
|
50
52
|
</LayoutPopoverProvider>
|
|
@@ -52,10 +54,22 @@ export const PopoverRoot = ({ children }: PropsWithChildren) => {
|
|
|
52
54
|
};
|
|
53
55
|
|
|
54
56
|
export const PopoverContent = () => {
|
|
55
|
-
const
|
|
57
|
+
const { t } = useTranslation(meta.id);
|
|
58
|
+
const { state, updateState } = useSimpleLayoutState();
|
|
56
59
|
const { setOpen } = useLayoutPopoverContext('PopoverContent');
|
|
57
60
|
|
|
58
|
-
const handleClose = useCallback(
|
|
61
|
+
const handleClose = useCallback(() => {
|
|
62
|
+
setOpen(false);
|
|
63
|
+
updateState((s) => ({
|
|
64
|
+
...s,
|
|
65
|
+
popoverOpen: false,
|
|
66
|
+
popoverAnchor: undefined,
|
|
67
|
+
popoverAnchorId: undefined,
|
|
68
|
+
popoverSide: undefined,
|
|
69
|
+
}));
|
|
70
|
+
}, [setOpen, updateState]);
|
|
71
|
+
|
|
72
|
+
const handleInteractOutside = useCallback(
|
|
59
73
|
(event: KeyboardEvent | PopoverContentInteractOutsideEvent) => {
|
|
60
74
|
if (
|
|
61
75
|
// TODO(thure): CodeMirror should not focus itself when it updates.
|
|
@@ -64,36 +78,40 @@ export const PopoverContent = () => {
|
|
|
64
78
|
) {
|
|
65
79
|
event.preventDefault();
|
|
66
80
|
} else {
|
|
67
|
-
|
|
68
|
-
layout.popoverOpen = false;
|
|
69
|
-
layout.popoverAnchor = undefined;
|
|
70
|
-
layout.popoverAnchorId = undefined;
|
|
71
|
-
layout.popoverSide = undefined;
|
|
81
|
+
handleClose();
|
|
72
82
|
}
|
|
73
83
|
},
|
|
74
|
-
[
|
|
84
|
+
[handleClose],
|
|
75
85
|
);
|
|
76
86
|
|
|
77
87
|
const collisionBoundaries: HTMLElement[] = useMemo(() => {
|
|
78
|
-
const closest =
|
|
79
|
-
| HTMLElement
|
|
80
|
-
| null
|
|
81
|
-
| undefined;
|
|
88
|
+
const closest = state.popoverAnchor?.closest('[data-popover-collision-boundary]') as HTMLElement | null | undefined;
|
|
82
89
|
return closest ? [closest] : [];
|
|
83
|
-
}, [
|
|
90
|
+
}, [state.popoverAnchor]);
|
|
84
91
|
|
|
85
92
|
return (
|
|
86
93
|
<Popover.Portal>
|
|
87
94
|
<Popover.Content
|
|
88
|
-
side={
|
|
89
|
-
onInteractOutside={handleClose}
|
|
90
|
-
onEscapeKeyDown={handleClose}
|
|
91
|
-
collisionBoundary={collisionBoundaries}
|
|
95
|
+
side={state.popoverSide}
|
|
92
96
|
sticky='always'
|
|
93
97
|
hideWhenDetached
|
|
98
|
+
collisionBoundary={collisionBoundaries}
|
|
99
|
+
onInteractOutside={handleInteractOutside}
|
|
100
|
+
onEscapeKeyDown={handleInteractOutside}
|
|
94
101
|
>
|
|
95
102
|
<Popover.Viewport>
|
|
96
|
-
|
|
103
|
+
{state.popoverKind === 'card' && (
|
|
104
|
+
<Card.Root>
|
|
105
|
+
<Card.Toolbar>
|
|
106
|
+
{/* TODO(wittjosiah): Cleaner way to handle no drag handle in toolbar? */}
|
|
107
|
+
<span />
|
|
108
|
+
{state.popoverTitle ? <Card.Title>{toLocalizedString(state.popoverTitle, t)}</Card.Title> : <span />}
|
|
109
|
+
<Card.Close onClick={handleClose} />
|
|
110
|
+
</Card.Toolbar>
|
|
111
|
+
<Surface role='card--content' data={state.popoverContent} limit={1} />
|
|
112
|
+
</Card.Root>
|
|
113
|
+
)}
|
|
114
|
+
{state.popoverKind === 'base' && <Surface role='popover' data={state.popoverContent} limit={1} />}
|
|
97
115
|
</Popover.Viewport>
|
|
98
116
|
<Popover.Arrow />
|
|
99
117
|
</Popover.Content>
|
|
@@ -2,47 +2,69 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { useCallback } from 'react';
|
|
5
|
+
import React, { useCallback, useMemo } from 'react';
|
|
6
6
|
|
|
7
7
|
import { Common } from '@dxos/app-framework';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { IconButton, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
11
|
-
import { mx, osTranslations
|
|
8
|
+
import { useAppGraph, useOperationInvoker } from '@dxos/app-framework/react';
|
|
9
|
+
import { Graph, Node } from '@dxos/plugin-graph';
|
|
10
|
+
import { IconButton, type ThemedClassName, Toolbar, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
11
|
+
import { mx, osTranslations } from '@dxos/ui-theme';
|
|
12
12
|
|
|
13
|
+
import { useSimpleLayoutState } from '../../hooks';
|
|
13
14
|
import { meta } from '../../meta';
|
|
14
|
-
import { SimpleLayoutState } from '../../types';
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Check if an item is a direct child of a workspace or collection.
|
|
18
|
+
* Returns true if any parent node has disposition 'workspace' or 'collection'.
|
|
19
|
+
*/
|
|
20
|
+
const isWorkspaceOrCollectionChild = (graph: Graph.ReadableGraph, itemId: string): boolean => {
|
|
21
|
+
const parents = Graph.getConnections(graph, itemId, 'inbound');
|
|
22
|
+
return parents.some(
|
|
23
|
+
(node) => node.properties.disposition === 'workspace' || node.properties.disposition === 'collection',
|
|
24
|
+
);
|
|
18
25
|
};
|
|
19
26
|
|
|
20
|
-
export
|
|
27
|
+
export type BannerProps = ThemedClassName<{
|
|
28
|
+
node?: Node.Node;
|
|
29
|
+
}>;
|
|
30
|
+
|
|
31
|
+
export const Banner = ({ node, classNames }: BannerProps) => {
|
|
21
32
|
const { t } = useTranslation(meta.id);
|
|
22
|
-
const
|
|
33
|
+
const { state } = useSimpleLayoutState();
|
|
23
34
|
const { invokePromise } = useOperationInvoker();
|
|
24
|
-
const
|
|
35
|
+
const { graph } = useAppGraph();
|
|
36
|
+
|
|
37
|
+
const label = (node && toLocalizedString(node.properties.label, t)) ?? t('current app name', { ns: osTranslations });
|
|
38
|
+
|
|
39
|
+
// Check if current active item is a top-level workspace/collection child.
|
|
40
|
+
const isTopLevelItem = useMemo(() => {
|
|
41
|
+
if (!state.active) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return isWorkspaceOrCollectionChild(graph, state.active);
|
|
45
|
+
}, [graph, state.active]);
|
|
25
46
|
|
|
26
47
|
const handleClick = useCallback(async () => {
|
|
27
|
-
if (
|
|
28
|
-
|
|
48
|
+
if (state.active) {
|
|
49
|
+
// If history is empty and this is a top-level item, go to home.
|
|
50
|
+
if (state.history.length === 0 && isTopLevelItem) {
|
|
51
|
+
await invokePromise(Common.LayoutOperation.SwitchWorkspace, { subject: Node.RootId });
|
|
52
|
+
} else {
|
|
53
|
+
// Otherwise, close (which will pop from history or clear active).
|
|
54
|
+
await invokePromise(Common.LayoutOperation.Close, { subject: [state.active] });
|
|
55
|
+
}
|
|
29
56
|
} else {
|
|
30
|
-
await invokePromise(Common.LayoutOperation.SwitchWorkspace, { subject:
|
|
57
|
+
await invokePromise(Common.LayoutOperation.SwitchWorkspace, { subject: Node.RootId });
|
|
31
58
|
}
|
|
32
|
-
}, [invokePromise,
|
|
59
|
+
}, [invokePromise, state.active, state.history.length, isTopLevelItem]);
|
|
60
|
+
|
|
61
|
+
if (!node) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
33
64
|
|
|
34
65
|
return (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// TODO(burdon): Fixed or not?
|
|
38
|
-
<header
|
|
39
|
-
className={mx(
|
|
40
|
-
'_fixed flex items-center gap-2 pli-2 block-start-0 inset-inline-0 bs-[--dx-mobile-topbar-content-height,48px] bg-baseSurface border-be border-separator',
|
|
41
|
-
'grid grid-cols-[min-content_1fr_min-content]',
|
|
42
|
-
surfaceZIndex({ level: 'menu' }),
|
|
43
|
-
)}
|
|
44
|
-
>
|
|
45
|
-
{node ? (
|
|
66
|
+
<Toolbar.Root role='banner' classNames={mx('grid grid-cols-[var(--rail-size)_1fr_var(--rail-size)]', classNames)}>
|
|
67
|
+
{node.id !== Node.RootId ? (
|
|
46
68
|
<IconButton
|
|
47
69
|
iconOnly
|
|
48
70
|
variant='ghost'
|
|
@@ -54,7 +76,7 @@ export const Banner = ({ node }: BannerProps) => {
|
|
|
54
76
|
<div />
|
|
55
77
|
)}
|
|
56
78
|
<h1 className={'grow text-center truncate font-medium'}>{label}</h1>
|
|
57
|
-
{
|
|
58
|
-
</
|
|
79
|
+
<IconButton iconOnly variant='ghost' icon='ph--dots-three-vertical--regular' label={t('menu label')} />
|
|
80
|
+
</Toolbar.Root>
|
|
59
81
|
);
|
|
60
82
|
};
|
|
@@ -2,79 +2,73 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, {
|
|
5
|
+
import React, { useCallback, useMemo } from 'react';
|
|
6
6
|
|
|
7
|
-
import { Surface, useAppGraph
|
|
7
|
+
import { Surface, useAppGraph } from '@dxos/app-framework/react';
|
|
8
|
+
import { log } from '@dxos/log';
|
|
8
9
|
import { useNode } from '@dxos/plugin-graph';
|
|
9
10
|
import { Main as NaturalMain } from '@dxos/react-ui';
|
|
10
11
|
import { ATTENDABLE_PATH_SEPARATOR } from '@dxos/react-ui-attention';
|
|
12
|
+
import { Mosaic } from '@dxos/react-ui-mosaic';
|
|
11
13
|
import { mx } from '@dxos/ui-theme';
|
|
12
14
|
|
|
13
|
-
import {
|
|
15
|
+
import { useSimpleLayoutState } from '../../hooks';
|
|
14
16
|
import { ContentError } from '../ContentError';
|
|
15
17
|
import { ContentLoading } from '../ContentLoading';
|
|
16
|
-
import { Home } from '../Home';
|
|
17
18
|
|
|
18
19
|
import { Banner } from './Banner';
|
|
19
20
|
import { NavBar } from './NavBar';
|
|
20
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Main root component.
|
|
24
|
+
*/
|
|
21
25
|
export const Main = () => {
|
|
22
|
-
const
|
|
23
|
-
const id =
|
|
26
|
+
const { state } = useSimpleLayoutState();
|
|
27
|
+
const id = state.active ?? state.workspace;
|
|
28
|
+
const showNavBar = !state.isPopover;
|
|
24
29
|
const { graph } = useAppGraph();
|
|
25
30
|
const node = useNode(graph, id);
|
|
26
31
|
|
|
27
32
|
const placeholder = useMemo(() => <ContentLoading />, []);
|
|
28
33
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
(
|
|
34
|
+
const data = useMemo(() => {
|
|
35
|
+
const { variant } = parseEntryId(id);
|
|
36
|
+
return (
|
|
32
37
|
node && {
|
|
33
38
|
attendableId: id,
|
|
34
39
|
subject: node.data,
|
|
35
40
|
properties: node.properties,
|
|
41
|
+
popoverAnchorId: state.popoverAnchorId,
|
|
36
42
|
variant,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
const handleActiveIdChange = (nextActiveId: string | null) => {
|
|
43
|
-
// eslint-disable-next-line no-console
|
|
44
|
-
console.log('[navigate]', nextActiveId);
|
|
45
|
-
};
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
}, [id, node, node?.data, node?.properties, state.popoverAnchorId]);
|
|
46
46
|
|
|
47
|
-
const
|
|
47
|
+
const handleActiveIdChange = useCallback((nextActiveId: string | null) => {
|
|
48
|
+
log.info('navigate', { nextActiveId });
|
|
49
|
+
}, []);
|
|
48
50
|
|
|
49
51
|
return (
|
|
50
|
-
<
|
|
51
|
-
<NaturalMain.
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
<Mosaic.Root>
|
|
53
|
+
<NaturalMain.Root complementarySidebarState='closed' navigationSidebarState='closed'>
|
|
54
|
+
<NaturalMain.Content
|
|
55
|
+
bounce
|
|
56
|
+
classNames={mx(
|
|
57
|
+
'dx-mobile-main dx-mobile-main-scroll-area--flush',
|
|
58
|
+
'grid bs-full overflow-hidden',
|
|
55
59
|
showNavBar ? 'grid-rows-[min-content_1fr_min-content]' : 'grid-rows-[min-content_1fr]',
|
|
56
60
|
)}
|
|
57
61
|
>
|
|
58
|
-
<Banner node={node} />
|
|
59
|
-
<
|
|
60
|
-
<
|
|
61
|
-
</
|
|
62
|
-
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
limit={1}
|
|
69
|
-
fallback={ContentError}
|
|
70
|
-
placeholder={placeholder}
|
|
71
|
-
/>
|
|
72
|
-
</section>
|
|
73
|
-
</Activity>
|
|
74
|
-
{showNavBar && <NavBar activeId={id} onActiveIdChange={handleActiveIdChange} />}
|
|
75
|
-
</div>
|
|
76
|
-
</NaturalMain.Content>
|
|
77
|
-
</NaturalMain.Root>
|
|
62
|
+
<Banner classNames='border-be border-separator' node={node} />
|
|
63
|
+
<article className='contents'>
|
|
64
|
+
<Surface key={id} role='article' data={data} limit={1} fallback={ContentError} placeholder={placeholder} />
|
|
65
|
+
</article>
|
|
66
|
+
{showNavBar && (
|
|
67
|
+
<NavBar classNames='border-bs border-separator' activeId={id} onActiveIdChange={handleActiveIdChange} />
|
|
68
|
+
)}
|
|
69
|
+
</NaturalMain.Content>
|
|
70
|
+
</NaturalMain.Root>
|
|
71
|
+
</Mosaic.Root>
|
|
78
72
|
);
|
|
79
73
|
};
|
|
80
74
|
|
|
@@ -83,3 +77,5 @@ const parseEntryId = (entryId: string) => {
|
|
|
83
77
|
const [id, variant] = entryId.split(ATTENDABLE_PATH_SEPARATOR);
|
|
84
78
|
return { id, variant };
|
|
85
79
|
};
|
|
80
|
+
|
|
81
|
+
Main.displayName = 'SimpleLayout.Main';
|