@dxos/plugin-simple-layout 0.0.0 → 0.8.4-main.1c7ec43d41
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/SimpleLayoutPlugin-Q5BZE6KD.mjs +50 -0
- package/dist/lib/browser/SimpleLayoutPlugin-Q5BZE6KD.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +25 -0
- package/dist/lib/browser/index.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -0
- package/dist/lib/browser/translations.mjs +34 -0
- package/dist/lib/browser/translations.mjs.map +7 -0
- package/dist/lib/node-esm/SimpleLayoutPlugin-OD45TNPO.mjs +52 -0
- package/dist/lib/node-esm/SimpleLayoutPlugin-OD45TNPO.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +27 -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/translations.mjs +36 -0
- package/dist/lib/node-esm/translations.mjs.map +7 -0
- package/dist/types/src/SimpleLayoutPlugin.d.ts +8 -0
- package/dist/types/src/SimpleLayoutPlugin.d.ts.map +1 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts +6 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +22 -0
- package/dist/types/src/capabilities/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
- package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-root.d.ts +9 -0
- package/dist/types/src/capabilities/react-root.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface.d.ts +5 -0
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
- package/dist/types/src/capabilities/spotlight-dismiss.d.ts +14 -0
- package/dist/types/src/capabilities/spotlight-dismiss.d.ts.map +1 -0
- package/dist/types/src/capabilities/state.d.ts +19 -0
- package/dist/types/src/capabilities/state.d.ts.map +1 -0
- package/dist/types/src/capabilities/url-handler.d.ts +12 -0
- package/dist/types/src/capabilities/url-handler.d.ts.map +1 -0
- package/dist/types/src/components/ContentError.stories.d.ts +46 -0
- package/dist/types/src/components/ContentError.stories.d.ts.map +1 -0
- package/dist/types/src/components/DebugOverlay/DebugOverlay.d.ts +19 -0
- package/dist/types/src/components/DebugOverlay/DebugOverlay.d.ts.map +1 -0
- package/dist/types/src/components/DebugOverlay/index.d.ts +2 -0
- package/dist/types/src/components/DebugOverlay/index.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/Loading/Loading.d.ts +3 -0
- package/dist/types/src/components/Loading/Loading.d.ts.map +1 -0
- package/dist/types/src/components/Loading/Loading.stories.d.ts +13 -0
- package/dist/types/src/components/Loading/Loading.stories.d.ts.map +1 -0
- package/dist/types/src/components/Loading/index.d.ts +2 -0
- package/dist/types/src/components/Loading/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/NavBranch/NavBranch.d.ts +11 -0
- package/dist/types/src/components/NavBranch/NavBranch.d.ts.map +1 -0
- package/dist/types/src/components/NavBranch/index.d.ts +2 -0
- package/dist/types/src/components/NavBranch/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 +24 -0
- package/dist/types/src/components/SimpleLayout/AppBar.d.ts.map +1 -0
- package/dist/types/src/components/SimpleLayout/AppBar.stories.d.ts +54 -0
- package/dist/types/src/components/SimpleLayout/AppBar.stories.d.ts.map +1 -0
- package/dist/types/src/components/SimpleLayout/Drawer.d.ts +6 -0
- package/dist/types/src/components/SimpleLayout/Drawer.d.ts.map +1 -0
- package/dist/types/src/components/SimpleLayout/Main.d.ts +6 -0
- package/dist/types/src/components/SimpleLayout/Main.d.ts.map +1 -0
- package/dist/types/src/components/SimpleLayout/NavBar.d.ts +16 -0
- package/dist/types/src/components/SimpleLayout/NavBar.d.ts.map +1 -0
- package/dist/types/src/components/SimpleLayout/NavBar.stories.d.ts +49 -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 +48 -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/hooks.d.ts +7 -0
- package/dist/types/src/components/hooks.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +8 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/hooks/actions.d.ts +19 -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 +4 -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/operations/close.d.ts +5 -0
- package/dist/types/src/operations/close.d.ts.map +1 -0
- package/dist/types/src/operations/index.d.ts +3 -0
- package/dist/types/src/operations/index.d.ts.map +1 -0
- package/dist/types/src/operations/open.d.ts +5 -0
- package/dist/types/src/operations/open.d.ts.map +1 -0
- package/dist/types/src/operations/revert-workspace.d.ts +5 -0
- package/dist/types/src/operations/revert-workspace.d.ts.map +1 -0
- package/dist/types/src/operations/set-layout-mode.d.ts +5 -0
- package/dist/types/src/operations/set-layout-mode.d.ts.map +1 -0
- package/dist/types/src/operations/set.d.ts +5 -0
- package/dist/types/src/operations/set.d.ts.map +1 -0
- package/dist/types/src/operations/state-access.d.ts +8 -0
- package/dist/types/src/operations/state-access.d.ts.map +1 -0
- package/dist/types/src/operations/switch-workspace.d.ts +5 -0
- package/dist/types/src/operations/switch-workspace.d.ts.map +1 -0
- package/dist/types/src/operations/update-complementary.d.ts +5 -0
- package/dist/types/src/operations/update-complementary.d.ts.map +1 -0
- package/dist/types/src/operations/update-dialog.d.ts +5 -0
- package/dist/types/src/operations/update-dialog.d.ts.map +1 -0
- package/dist/types/src/operations/update-popover.d.ts +5 -0
- package/dist/types/src/operations/update-popover.d.ts.map +1 -0
- package/dist/types/src/operations/update-sidebar.d.ts +5 -0
- package/dist/types/src/operations/update-sidebar.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +32 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/types/capabilities.d.ts +44 -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 +58 -29
- package/src/SimpleLayoutPlugin.ts +39 -11
- package/src/capabilities/app-graph-builder.ts +21 -0
- package/src/capabilities/index.ts +13 -3
- package/src/capabilities/operation-handler.ts +14 -0
- package/src/capabilities/{react-root/react-root.tsx → react-root.tsx} +4 -4
- package/src/capabilities/react-surface.tsx +50 -0
- package/src/{hooks/useSpotlightDismiss.ts → capabilities/spotlight-dismiss.ts} +31 -40
- package/src/capabilities/state.tsx +52 -0
- package/src/capabilities/url-handler.ts +161 -0
- package/src/components/ContentError.stories.tsx +9 -8
- package/src/components/DebugOverlay/DebugOverlay.tsx +96 -0
- package/src/components/DebugOverlay/index.ts +5 -0
- package/src/components/Dialog/Dialog.tsx +26 -15
- package/src/components/Home/Home.tsx +80 -80
- package/src/components/{ContentLoading.stories.tsx → Loading/Loading.stories.tsx} +5 -5
- package/src/components/{ContentLoading.tsx → Loading/Loading.tsx} +2 -2
- package/src/components/Loading/index.ts +5 -0
- package/src/components/MobileLayout/MobileLayout.stories.tsx +133 -0
- package/src/components/MobileLayout/MobileLayout.tsx +374 -0
- package/src/components/MobileLayout/index.ts +5 -0
- package/src/components/NavBranch/NavBranch.tsx +124 -0
- package/src/components/NavBranch/index.ts +5 -0
- package/src/components/Popover/Popover.tsx +49 -27
- package/src/components/SimpleLayout/AppBar.stories.tsx +144 -0
- package/src/components/SimpleLayout/AppBar.tsx +94 -0
- package/src/components/SimpleLayout/Drawer.tsx +104 -0
- package/src/components/SimpleLayout/Main.tsx +54 -61
- package/src/components/SimpleLayout/NavBar.stories.tsx +164 -0
- package/src/components/SimpleLayout/NavBar.tsx +19 -83
- package/src/components/SimpleLayout/SimpleLayout.stories.tsx +44 -61
- package/src/components/SimpleLayout/SimpleLayout.tsx +45 -8
- package/src/components/SimpleLayout/index.ts +3 -0
- package/src/components/hooks.ts +26 -0
- package/src/components/index.ts +4 -1
- package/src/hooks/actions.ts +84 -0
- package/src/hooks/index.ts +6 -1
- package/src/hooks/useAppBarProps.ts +95 -0
- package/src/hooks/useCompanions.ts +22 -0
- package/src/hooks/useDrawerActions.ts +100 -0
- package/src/hooks/useNavbarActions.ts +87 -0
- package/src/hooks/useSimpleLayoutState.ts +30 -0
- package/src/index.ts +7 -1
- package/src/meta.ts +2 -1
- package/src/operations/close.ts +34 -0
- package/src/operations/index.ts +16 -0
- package/src/operations/open.ts +63 -0
- package/src/operations/revert-workspace.ts +22 -0
- package/src/operations/set-layout-mode.ts +12 -0
- package/src/operations/set.ts +23 -0
- package/src/operations/state-access.ts +19 -0
- package/src/operations/switch-workspace.ts +26 -0
- package/src/operations/update-complementary.ts +35 -0
- package/src/operations/update-dialog.ts +28 -0
- package/src/operations/update-popover.ts +35 -0
- package/src/operations/update-sidebar.ts +12 -0
- package/src/translations.ts +21 -13
- package/src/types/capabilities.ts +23 -7
- package/src/types/events.ts +15 -0
- package/src/types/index.ts +1 -0
- package/src/capabilities/operation-resolver/index.ts +0 -10
- package/src/capabilities/operation-resolver/operation-resolver.ts +0 -135
- package/src/capabilities/react-root/index.ts +0 -7
- package/src/capabilities/state/index.ts +0 -9
- package/src/capabilities/state/state.tsx +0 -60
- package/src/components/ContentError.tsx +0 -23
- package/src/components/SimpleLayout/Banner.tsx +0 -60
- package/src/components/SimpleLayout/NavBarstories.tsx +0 -59
|
@@ -4,28 +4,29 @@
|
|
|
4
4
|
|
|
5
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
6
|
|
|
7
|
+
import { ErrorFallback } from '@dxos/react-ui';
|
|
7
8
|
import { withTheme } from '@dxos/react-ui/testing';
|
|
8
9
|
|
|
9
|
-
import { translations } from '
|
|
10
|
-
|
|
11
|
-
import { ContentError } from './ContentError';
|
|
10
|
+
import { translations } from '#translations';
|
|
12
11
|
|
|
13
12
|
const meta = {
|
|
14
|
-
title: 'plugins/plugin-simple-layout/
|
|
15
|
-
component:
|
|
16
|
-
decorators: [withTheme],
|
|
13
|
+
title: 'plugins/plugin-simple-layout/components/ErrorFallback',
|
|
14
|
+
component: ErrorFallback,
|
|
15
|
+
decorators: [withTheme()],
|
|
17
16
|
parameters: {
|
|
18
17
|
layout: 'centered',
|
|
19
18
|
translations,
|
|
20
19
|
},
|
|
21
|
-
} satisfies Meta<typeof
|
|
20
|
+
} satisfies Meta<typeof ErrorFallback>;
|
|
22
21
|
|
|
23
22
|
export default meta;
|
|
24
23
|
|
|
25
24
|
type Story = StoryObj<typeof meta>;
|
|
26
25
|
|
|
27
26
|
export const Default: Story = {
|
|
28
|
-
args: {
|
|
27
|
+
args: {
|
|
28
|
+
error: new Error('An unexpected error occurred'),
|
|
29
|
+
},
|
|
29
30
|
};
|
|
30
31
|
|
|
31
32
|
export const WithError: Story = {
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { createContext } from '@radix-ui/react-context';
|
|
6
|
+
import React, { type PropsWithChildren, useCallback, useRef } from 'react';
|
|
7
|
+
|
|
8
|
+
const DEBUG_OVERLAY_NAME = 'DebugOverlay';
|
|
9
|
+
|
|
10
|
+
//
|
|
11
|
+
// Context
|
|
12
|
+
//
|
|
13
|
+
|
|
14
|
+
type DebugOverlayContextValue = {
|
|
15
|
+
/** Log a timestamped message to the on-screen debug overlay. */
|
|
16
|
+
dbg: (msg: string) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Default to a no-op so hooks can call useDebugLog() safely outside of a provider.
|
|
20
|
+
const [DebugOverlayProvider, useDebugLog] = createContext<DebugOverlayContextValue>(DEBUG_OVERLAY_NAME, {
|
|
21
|
+
dbg: () => {},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
//
|
|
25
|
+
// Root
|
|
26
|
+
//
|
|
27
|
+
|
|
28
|
+
type DebugOverlayRootProps = PropsWithChildren<{
|
|
29
|
+
/**
|
|
30
|
+
* When true (default), renders the on-screen log panel.
|
|
31
|
+
* Set to false to suppress the overlay while keeping the context available.
|
|
32
|
+
*/
|
|
33
|
+
enabled?: boolean;
|
|
34
|
+
maxLines?: number;
|
|
35
|
+
}>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Establishes a debug overlay context.
|
|
39
|
+
*
|
|
40
|
+
* When enabled, renders an on-screen monospaced log panel anchored just above
|
|
41
|
+
* the keyboard (via --kb-height CSS variable). Descendants can call
|
|
42
|
+
* useDebugLog() to obtain the dbg() function for logging.
|
|
43
|
+
*
|
|
44
|
+
* Intended for transient mobile debugging in the iOS Simulator where DevTools
|
|
45
|
+
* console output may not be accessible.
|
|
46
|
+
*/
|
|
47
|
+
const DebugOverlayRoot = ({ children, enabled = true, maxLines = 10 }: DebugOverlayRootProps) => {
|
|
48
|
+
const overlayRef = useRef<HTMLDivElement>(null);
|
|
49
|
+
|
|
50
|
+
const dbg = useCallback((msg: string) => {
|
|
51
|
+
if (!overlayRef.current) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const line = document.createElement('pre');
|
|
55
|
+
line.textContent = `${(performance.now() / 1000).toFixed(2).padStart(8, ' ')} ${msg}`;
|
|
56
|
+
overlayRef.current.prepend(line);
|
|
57
|
+
while (overlayRef.current.children.length > maxLines) {
|
|
58
|
+
overlayRef.current.lastChild?.remove();
|
|
59
|
+
}
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<DebugOverlayProvider dbg={dbg}>
|
|
64
|
+
{children}
|
|
65
|
+
{enabled && (
|
|
66
|
+
<div
|
|
67
|
+
ref={overlayRef}
|
|
68
|
+
style={{
|
|
69
|
+
position: 'fixed',
|
|
70
|
+
bottom: 'calc(var(--kb-height, 0px) + 8px)',
|
|
71
|
+
left: 8,
|
|
72
|
+
right: 8,
|
|
73
|
+
background: 'rgba(0,0,0,0.8)',
|
|
74
|
+
color: '#0f0',
|
|
75
|
+
fontSize: 10,
|
|
76
|
+
fontFamily: 'monospace',
|
|
77
|
+
padding: 6,
|
|
78
|
+
borderRadius: 4,
|
|
79
|
+
zIndex: 9999,
|
|
80
|
+
pointerEvents: 'none',
|
|
81
|
+
}}
|
|
82
|
+
/>
|
|
83
|
+
)}
|
|
84
|
+
</DebugOverlayProvider>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
//
|
|
89
|
+
// Exports
|
|
90
|
+
//
|
|
91
|
+
|
|
92
|
+
export const DebugOverlay = {
|
|
93
|
+
Root: DebugOverlayRoot,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export { useDebugLog };
|
|
@@ -4,33 +4,44 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
-
import { Surface
|
|
7
|
+
import { Surface } from '@dxos/app-framework/ui';
|
|
8
|
+
import { AppSurface } from '@dxos/app-toolkit/ui';
|
|
8
9
|
import { AlertDialog, Dialog as NaturalDialog } from '@dxos/react-ui';
|
|
10
|
+
import { ErrorFallback } from '@dxos/react-ui';
|
|
9
11
|
|
|
10
|
-
import {
|
|
11
|
-
import { ContentError } from '../ContentError';
|
|
12
|
+
import { useSimpleLayoutState } from '#hooks';
|
|
12
13
|
|
|
13
14
|
export const Dialog = () => {
|
|
14
|
-
const
|
|
15
|
+
const { state, updateState } = useSimpleLayoutState();
|
|
15
16
|
|
|
16
|
-
const DialogRoot =
|
|
17
|
-
const DialogOverlay =
|
|
17
|
+
const DialogRoot = state.dialogType === 'alert' ? AlertDialog.Root : NaturalDialog.Root;
|
|
18
|
+
const DialogOverlay = state.dialogType === 'alert' ? AlertDialog.Overlay : NaturalDialog.Overlay;
|
|
18
19
|
|
|
19
20
|
return (
|
|
20
21
|
<DialogRoot
|
|
21
|
-
modal={
|
|
22
|
-
open={
|
|
23
|
-
onOpenChange={(nextOpen) => (
|
|
22
|
+
modal={state.dialogBlockAlign !== 'end'}
|
|
23
|
+
open={state.dialogOpen}
|
|
24
|
+
onOpenChange={(nextOpen) => updateState((state) => ({ ...state, dialogOpen: nextOpen }))}
|
|
24
25
|
>
|
|
25
|
-
{
|
|
26
|
-
<Surface
|
|
26
|
+
{state.dialogBlockAlign === 'end' ? (
|
|
27
|
+
<Surface.Surface
|
|
28
|
+
type={AppSurface.Dialog}
|
|
29
|
+
data={state.dialogContent ?? undefined}
|
|
30
|
+
limit={1}
|
|
31
|
+
fallback={ErrorFallback}
|
|
32
|
+
/>
|
|
27
33
|
) : (
|
|
28
34
|
<DialogOverlay
|
|
29
|
-
blockAlign={
|
|
30
|
-
classNames={
|
|
31
|
-
style={
|
|
35
|
+
blockAlign={state.dialogBlockAlign}
|
|
36
|
+
classNames={state.dialogOverlayClasses}
|
|
37
|
+
style={state.dialogOverlayStyle}
|
|
32
38
|
>
|
|
33
|
-
<Surface
|
|
39
|
+
<Surface.Surface
|
|
40
|
+
type={AppSurface.Dialog}
|
|
41
|
+
data={state.dialogContent ?? undefined}
|
|
42
|
+
limit={1}
|
|
43
|
+
fallback={ErrorFallback}
|
|
44
|
+
/>
|
|
34
45
|
</DialogOverlay>
|
|
35
46
|
)}
|
|
36
47
|
</DialogRoot>
|
|
@@ -4,135 +4,135 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
7
|
+
import { useOperationInvoker } from '@dxos/app-framework/ui';
|
|
8
|
+
import { LayoutOperation } from '@dxos/app-toolkit';
|
|
9
|
+
import { useAppGraph } from '@dxos/app-toolkit/ui';
|
|
10
|
+
import { Node, useConnections } from '@dxos/plugin-graph';
|
|
11
|
+
import { Avatar, Icon, ScrollArea, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
12
|
+
import { Card } from '@dxos/react-ui';
|
|
13
|
+
import { Mosaic, type MosaicStackTileComponent } from '@dxos/react-ui-mosaic';
|
|
14
|
+
import { SearchPanel, useSearchListItem, useSearchListResults } from '@dxos/react-ui-search';
|
|
13
15
|
import { mx } from '@dxos/ui-theme';
|
|
16
|
+
import { byPosition, getHostPlatform, isTauri } from '@dxos/util';
|
|
14
17
|
|
|
15
|
-
import { meta } from '
|
|
18
|
+
import { meta } from '#meta';
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
import { useExpandPath } from '../hooks';
|
|
18
21
|
|
|
19
|
-
export
|
|
22
|
+
export type HomeProps = {};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Home screen.
|
|
26
|
+
*/
|
|
27
|
+
export const Home = (_: HomeProps) => {
|
|
20
28
|
const { t } = useTranslation(meta.id);
|
|
21
|
-
const
|
|
22
|
-
|
|
29
|
+
const userAccountItem = useItemsByDisposition('user-account')[0];
|
|
30
|
+
const pinnedItems = useItemsByDisposition('pin-end', true);
|
|
31
|
+
const workspaceItems = useItemsByDisposition('workspace');
|
|
32
|
+
useExpandPath(Node.RootId);
|
|
33
|
+
|
|
34
|
+
const items = useMemo(
|
|
35
|
+
() => [...(userAccountItem ? [userAccountItem] : []), ...pinnedItems, ...workspaceItems],
|
|
36
|
+
[userAccountItem, pinnedItems, workspaceItems],
|
|
37
|
+
);
|
|
23
38
|
|
|
24
39
|
const { results, handleSearch } = useSearchListResults({
|
|
25
|
-
items
|
|
40
|
+
items,
|
|
26
41
|
extract: (node) => toLocalizedString(node.properties.label, t),
|
|
27
42
|
});
|
|
28
43
|
|
|
44
|
+
const autoFocus = !isTauri() || getHostPlatform() !== 'ios';
|
|
45
|
+
|
|
29
46
|
return (
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
</
|
|
42
|
-
</
|
|
43
|
-
</
|
|
44
|
-
</
|
|
47
|
+
<SearchPanel onSearch={handleSearch}>
|
|
48
|
+
<Mosaic.Container asChild>
|
|
49
|
+
<ScrollArea.Root centered padding thin>
|
|
50
|
+
<ScrollArea.Viewport>
|
|
51
|
+
<Mosaic.Stack
|
|
52
|
+
classNames='gap-1'
|
|
53
|
+
draggable={false}
|
|
54
|
+
items={results}
|
|
55
|
+
getId={(item) => item.id}
|
|
56
|
+
Tile={WorkspaceTile}
|
|
57
|
+
/>
|
|
58
|
+
</ScrollArea.Viewport>
|
|
59
|
+
</ScrollArea.Root>
|
|
60
|
+
</Mosaic.Container>
|
|
61
|
+
</SearchPanel>
|
|
45
62
|
);
|
|
46
63
|
};
|
|
47
64
|
|
|
48
|
-
const
|
|
65
|
+
const WorkspaceTile: MosaicStackTileComponent<Node.Node> = (props) => {
|
|
66
|
+
const data = props.data;
|
|
49
67
|
const { t } = useTranslation(meta.id);
|
|
50
68
|
const { invokePromise } = useOperationInvoker();
|
|
51
69
|
const { selectedValue, registerItem, unregisterItem } = useSearchListItem();
|
|
52
|
-
const
|
|
70
|
+
const name = toLocalizedString(data.properties.label, t);
|
|
71
|
+
const isSelected = selectedValue === data.id;
|
|
72
|
+
const cardRef = useRef<HTMLDivElement>(null);
|
|
73
|
+
|
|
74
|
+
useExpandPath(data.id);
|
|
53
75
|
|
|
54
76
|
const handleSelect = useCallback(
|
|
55
|
-
() => invokePromise(
|
|
56
|
-
[invokePromise,
|
|
77
|
+
() => invokePromise(LayoutOperation.SwitchWorkspace, { subject: data.id }),
|
|
78
|
+
[invokePromise, data.id],
|
|
57
79
|
);
|
|
58
80
|
|
|
59
|
-
useLoadDescendents(node.id);
|
|
60
|
-
|
|
61
|
-
const name = toLocalizedString(node.properties.label, t);
|
|
62
|
-
const isSelected = selectedValue === node.id;
|
|
63
|
-
|
|
64
81
|
// Register this workspace with the search context.
|
|
65
82
|
useEffect(() => {
|
|
66
|
-
if (
|
|
67
|
-
registerItem(
|
|
83
|
+
if (cardRef.current) {
|
|
84
|
+
registerItem(data.id, cardRef.current, handleSelect);
|
|
68
85
|
}
|
|
69
86
|
|
|
70
|
-
return () => unregisterItem(
|
|
71
|
-
}, [
|
|
87
|
+
return () => unregisterItem(data.id);
|
|
88
|
+
}, [data.id, handleSelect, registerItem, unregisterItem]);
|
|
72
89
|
|
|
73
90
|
// Scroll into view when selected.
|
|
74
91
|
useEffect(() => {
|
|
75
|
-
if (isSelected &&
|
|
76
|
-
|
|
92
|
+
if (isSelected && cardRef.current) {
|
|
93
|
+
cardRef.current.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
77
94
|
}
|
|
78
95
|
}, [isSelected]);
|
|
79
96
|
|
|
80
97
|
return (
|
|
81
98
|
<Card.Root
|
|
82
|
-
ref={ref}
|
|
83
99
|
role='button'
|
|
84
|
-
|
|
100
|
+
fullWidth
|
|
101
|
+
tabIndex={-1} // TODO(burdon): Use Mosaic.Focus.
|
|
85
102
|
data-selected={isSelected}
|
|
86
|
-
classNames={mx('dx-focus-ring', isSelected && 'bg-
|
|
103
|
+
classNames={mx('dx-focus-ring', isSelected && 'bg-hover-overlay')}
|
|
87
104
|
onClick={handleSelect}
|
|
105
|
+
ref={cardRef}
|
|
88
106
|
>
|
|
89
|
-
<Card.
|
|
107
|
+
<Card.Toolbar density='fine'>
|
|
90
108
|
<Avatar.Root>
|
|
91
109
|
<Avatar.Content
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
hueVariant='
|
|
110
|
+
icon={data.properties.icon}
|
|
111
|
+
hue={data.properties.hue}
|
|
112
|
+
hueVariant='transparent'
|
|
95
113
|
variant='square'
|
|
96
|
-
size={
|
|
114
|
+
size={8}
|
|
97
115
|
fallback={name}
|
|
98
116
|
/>
|
|
99
|
-
<Avatar.Label>{name}</Avatar.Label>
|
|
117
|
+
<Avatar.Label classNames='cursor-pointer'>{name}</Avatar.Label>
|
|
100
118
|
<Icon icon='ph--caret-right--regular' />
|
|
101
119
|
</Avatar.Root>
|
|
102
|
-
</Card.
|
|
120
|
+
</Card.Toolbar>
|
|
103
121
|
</Card.Root>
|
|
104
122
|
);
|
|
105
123
|
};
|
|
106
124
|
|
|
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]);
|
|
125
|
+
/** Filters nodes by disposition. */
|
|
126
|
+
const filterItems = (node: Node.Node, disposition: string) => {
|
|
127
|
+
return node.properties.disposition === disposition;
|
|
122
128
|
};
|
|
123
129
|
|
|
124
|
-
|
|
130
|
+
/** Returns root-level items filtered by disposition. */
|
|
131
|
+
const useItemsByDisposition = (disposition: string, sort = false) => {
|
|
125
132
|
const { graph } = useAppGraph();
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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);
|
|
133
|
+
const connections = useConnections(graph, Node.RootId, 'child');
|
|
134
|
+
return useMemo(() => {
|
|
135
|
+
const filtered = connections.filter((node) => filterItems(node, disposition));
|
|
136
|
+
return sort ? filtered.toSorted((a, b) => byPosition(a.properties, b.properties)) : filtered;
|
|
137
|
+
}, [connections, disposition, sort]);
|
|
138
138
|
};
|
|
@@ -6,16 +6,16 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
|
6
6
|
|
|
7
7
|
import { withTheme } from '@dxos/react-ui/testing';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { Loading } from './Loading';
|
|
10
10
|
|
|
11
11
|
const meta = {
|
|
12
|
-
title: 'plugins/plugin-simple-layout/
|
|
13
|
-
component:
|
|
14
|
-
decorators: [withTheme],
|
|
12
|
+
title: 'plugins/plugin-simple-layout/components/Loading',
|
|
13
|
+
component: Loading,
|
|
14
|
+
decorators: [withTheme()],
|
|
15
15
|
parameters: {
|
|
16
16
|
layout: 'centered',
|
|
17
17
|
},
|
|
18
|
-
} satisfies Meta<typeof
|
|
18
|
+
} satisfies Meta<typeof Loading>;
|
|
19
19
|
|
|
20
20
|
export default meta;
|
|
21
21
|
|
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
7
|
// TODO(burdon): Show skeleton: https://github.com/dxos/dxos/issues/8259
|
|
8
|
-
export const
|
|
9
|
-
return <div role='none' className='grid place-items-center attention-surface' />;
|
|
8
|
+
export const Loading = () => {
|
|
9
|
+
return <div role='none' className='grid place-items-center dx-attention-surface' />;
|
|
10
10
|
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import React, { type PropsWithChildren, useEffect, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { addEventListener, combine } from '@dxos/async';
|
|
9
|
+
import { Column, Flex, Input, Panel, Splitter, type SplitterMode, Toolbar } from '@dxos/react-ui';
|
|
10
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
11
|
+
|
|
12
|
+
import { MobileLayout, type MobileLayoutRootProps } from './MobileLayout';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Simulate ios keyboard.
|
|
16
|
+
*/
|
|
17
|
+
const WithKeyboard = ({ children }: PropsWithChildren) => {
|
|
18
|
+
const [keyboardOpen, setKeyboardOpen] = useState(false);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
return combine(
|
|
22
|
+
addEventListener(document, 'focusin', (event: FocusEvent) => {
|
|
23
|
+
const target = event.target as HTMLElement;
|
|
24
|
+
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
|
|
25
|
+
setKeyboardOpen(true);
|
|
26
|
+
}
|
|
27
|
+
}),
|
|
28
|
+
addEventListener(document, 'focusout', (event: FocusEvent) => {
|
|
29
|
+
const target = event.target as HTMLElement;
|
|
30
|
+
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
|
|
31
|
+
setKeyboardOpen(false);
|
|
32
|
+
}
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const keyboardHeight = keyboardOpen ? 300 : 0;
|
|
39
|
+
document.documentElement.style.setProperty('--kb-height', `${keyboardHeight}px`);
|
|
40
|
+
document.documentElement.style.setProperty('--kb-open', keyboardOpen ? '1' : '0');
|
|
41
|
+
|
|
42
|
+
// Dispatch custom keyboard event that useIOSKeyboard listens for.
|
|
43
|
+
window.dispatchEvent(
|
|
44
|
+
new CustomEvent('keyboard', {
|
|
45
|
+
detail: {
|
|
46
|
+
type: keyboardOpen ? 'show' : 'hide',
|
|
47
|
+
height: keyboardHeight,
|
|
48
|
+
duration: 300,
|
|
49
|
+
},
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
}, [keyboardOpen]);
|
|
53
|
+
|
|
54
|
+
return <div className='h-screen relative'>{children}</div>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const StoryPanel = ({ children, label }: PropsWithChildren<{ label: string }>) => {
|
|
58
|
+
return (
|
|
59
|
+
<Panel.Root>
|
|
60
|
+
<Panel.Toolbar asChild>
|
|
61
|
+
<Toolbar.Root>
|
|
62
|
+
{label}
|
|
63
|
+
<Toolbar.Separator />
|
|
64
|
+
{children}
|
|
65
|
+
</Toolbar.Root>
|
|
66
|
+
</Panel.Toolbar>
|
|
67
|
+
<Panel.Content asChild>
|
|
68
|
+
<Column.Root gutter='xs' classNames='py-form-chrome'>
|
|
69
|
+
<Column.Center>
|
|
70
|
+
<Flex column>
|
|
71
|
+
<Input.Root>
|
|
72
|
+
<Input.TextInput placeholder={label} />
|
|
73
|
+
</Input.Root>
|
|
74
|
+
</Flex>
|
|
75
|
+
</Column.Center>
|
|
76
|
+
</Column.Root>
|
|
77
|
+
</Panel.Content>
|
|
78
|
+
</Panel.Root>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const DefaultStory = () => {
|
|
83
|
+
const [splitterMode, setSplitterMode] = useState<SplitterMode>('top');
|
|
84
|
+
const [keyboardOpen, setKeyboardOpen] = useState(false);
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
setSplitterMode(splitterMode === 'split' ? 'bottom' : splitterMode);
|
|
88
|
+
}, [keyboardOpen]);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<WithKeyboard>
|
|
92
|
+
<MobileLayout.Root onKeyboardOpenChange={setKeyboardOpen}>
|
|
93
|
+
<MobileLayout.Panel safe={{ top: true, bottom: splitterMode === 'top' }}>
|
|
94
|
+
<Splitter.Root mode={splitterMode} ratio={0.5}>
|
|
95
|
+
<Splitter.Panel position='top'>
|
|
96
|
+
<StoryPanel label='Main'>
|
|
97
|
+
{splitterMode === 'top' && (
|
|
98
|
+
<Toolbar.IconButton icon='ph--plus--regular' label='Open' onClick={() => setSplitterMode('split')} />
|
|
99
|
+
)}
|
|
100
|
+
</StoryPanel>
|
|
101
|
+
</Splitter.Panel>
|
|
102
|
+
<Splitter.Panel position='bottom'>
|
|
103
|
+
<StoryPanel label='Drawer'>
|
|
104
|
+
<Toolbar.IconButton
|
|
105
|
+
icon={splitterMode === 'bottom' ? 'ph--arrow-down--regular' : 'ph--arrow-up--regular'}
|
|
106
|
+
label={splitterMode === 'bottom' ? 'Collapse' : 'Expand'}
|
|
107
|
+
onClick={() => setSplitterMode((splitterMode) => (splitterMode === 'split' ? 'bottom' : 'split'))}
|
|
108
|
+
/>
|
|
109
|
+
<Toolbar.IconButton icon='ph--x--regular' label='Close' onClick={() => setSplitterMode('top')} />
|
|
110
|
+
</StoryPanel>
|
|
111
|
+
</Splitter.Panel>
|
|
112
|
+
</Splitter.Root>
|
|
113
|
+
</MobileLayout.Panel>
|
|
114
|
+
</MobileLayout.Root>
|
|
115
|
+
</WithKeyboard>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const meta: Meta<MobileLayoutRootProps> = {
|
|
120
|
+
title: 'plugins/plugin-simple-layout/components/MobileLayout',
|
|
121
|
+
component: MobileLayout.Root,
|
|
122
|
+
render: DefaultStory,
|
|
123
|
+
decorators: [withTheme(), withLayout({ layout: 'column', classNames: 'relative' })],
|
|
124
|
+
parameters: {
|
|
125
|
+
layout: 'fullscreen',
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export default meta;
|
|
130
|
+
|
|
131
|
+
type Story = StoryObj<MobileLayoutRootProps>;
|
|
132
|
+
|
|
133
|
+
export const Default: Story = {};
|