@dxos/plugin-stack 0.9.0 → 0.9.1-staging.ee54ba693a
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/neutral/StackArticle-DDKRSUE7.mjs +199 -0
- package/dist/lib/neutral/StackArticle-DDKRSUE7.mjs.map +7 -0
- package/dist/lib/neutral/StackPlugin.mjs +6 -4
- package/dist/lib/neutral/StackPlugin.mjs.map +4 -4
- package/dist/lib/neutral/capabilities/index.mjs +3 -1
- package/dist/lib/neutral/capabilities/index.mjs.map +1 -1
- package/dist/lib/neutral/chunk-FDNICBZZ.mjs +18 -0
- package/dist/lib/neutral/chunk-FDNICBZZ.mjs.map +7 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
- package/dist/lib/neutral/chunk-ZOIZTRMG.mjs +44 -0
- package/dist/lib/neutral/chunk-ZOIZTRMG.mjs.map +7 -0
- package/dist/lib/neutral/components/index.mjs +191 -147
- package/dist/lib/neutral/components/index.mjs.map +4 -4
- package/dist/lib/neutral/containers/index.mjs +3 -1
- package/dist/lib/neutral/containers/index.mjs.map +1 -1
- package/dist/lib/neutral/index.mjs +5 -6
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/meta.mjs +2 -1
- package/dist/lib/neutral/plugin.mjs +2 -1
- package/dist/lib/neutral/plugin.mjs.map +1 -1
- package/dist/lib/neutral/{react-surface-MD3CVY2N.mjs → react-surface-IL4CW7ND.mjs} +3 -1
- package/dist/lib/neutral/{react-surface-MD3CVY2N.mjs.map → react-surface-IL4CW7ND.mjs.map} +1 -1
- package/dist/lib/neutral/translations.mjs +9 -7
- package/dist/lib/neutral/translations.mjs.map +3 -3
- package/dist/lib/neutral/types/index.mjs +4 -5
- package/dist/types/dx.config.d.ts +28 -0
- package/dist/types/dx.config.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +1 -1
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +2 -2
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/components/Stack/Stack.d.ts +67 -0
- package/dist/types/src/components/Stack/Stack.d.ts.map +1 -0
- package/dist/types/src/components/Stack/index.d.ts +2 -0
- package/dist/types/src/components/Stack/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -3
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/containers/StackArticle/StackArticle.d.ts +1 -2
- package/dist/types/src/containers/StackArticle/StackArticle.d.ts.map +1 -1
- package/dist/types/src/containers/StackArticle/StackArticle.stories.d.ts +7 -0
- package/dist/types/src/containers/StackArticle/StackArticle.stories.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +32 -2
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +5 -16
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Stack.d.ts +6 -0
- package/dist/types/src/types/Stack.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +1 -2
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dx.config.ts +32 -0
- package/package.json +34 -28
- package/src/StackPlugin.ts +3 -3
- package/src/components/Stack/Stack.tsx +319 -0
- package/src/components/{StackContext → Stack}/index.ts +1 -1
- package/src/components/index.ts +1 -3
- package/src/containers/StackArticle/StackArticle.stories.tsx +122 -0
- package/src/containers/StackArticle/StackArticle.tsx +181 -119
- package/src/meta.ts +3 -26
- package/src/translations.ts +6 -6
- package/src/types/Stack.ts +12 -0
- package/src/types/index.ts +1 -2
- package/dist/lib/neutral/StackArticle-R7IS6TYR.mjs +0 -124
- package/dist/lib/neutral/StackArticle-R7IS6TYR.mjs.map +0 -7
- package/dist/lib/neutral/chunk-RGJSGQGF.mjs +0 -36
- package/dist/lib/neutral/chunk-RGJSGQGF.mjs.map +0 -7
- package/dist/lib/neutral/chunk-T4NSFSPH.mjs +0 -24
- package/dist/lib/neutral/chunk-T4NSFSPH.mjs.map +0 -7
- package/dist/types/src/components/StackContext/StackContext.d.ts +0 -11
- package/dist/types/src/components/StackContext/StackContext.d.ts.map +0 -1
- package/dist/types/src/components/StackContext/index.d.ts +0 -2
- package/dist/types/src/components/StackContext/index.d.ts.map +0 -1
- package/dist/types/src/components/StackSection/CaretDownUp.d.ts +0 -3
- package/dist/types/src/components/StackSection/CaretDownUp.d.ts.map +0 -1
- package/dist/types/src/components/StackSection/StackSection.d.ts +0 -5
- package/dist/types/src/components/StackSection/StackSection.d.ts.map +0 -1
- package/dist/types/src/components/StackSection/index.d.ts +0 -2
- package/dist/types/src/components/StackSection/index.d.ts.map +0 -1
- package/dist/types/src/components/StackSettings/StackSettings.d.ts +0 -6
- package/dist/types/src/components/StackSettings/StackSettings.d.ts.map +0 -1
- package/dist/types/src/components/StackSettings/index.d.ts +0 -2
- package/dist/types/src/components/StackSettings/index.d.ts.map +0 -1
- package/dist/types/src/types/stack.d.ts +0 -18
- package/dist/types/src/types/stack.d.ts.map +0 -1
- package/dist/types/src/types/types.d.ts +0 -44
- package/dist/types/src/types/types.d.ts.map +0 -1
- package/src/components/StackContext/StackContext.tsx +0 -25
- package/src/components/StackSection/CaretDownUp.tsx +0 -30
- package/src/components/StackSection/StackSection.tsx +0 -123
- package/src/components/StackSection/index.ts +0 -5
- package/src/components/StackSettings/StackSettings.tsx +0 -28
- package/src/components/StackSettings/index.ts +0 -5
- package/src/types/stack.ts +0 -26
- package/src/types/types.ts +0 -46
package/dx.config.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Config2 } from '@dxos/app-framework/config';
|
|
6
|
+
import { trim } from '@dxos/util';
|
|
7
|
+
|
|
8
|
+
export default Config2.make({
|
|
9
|
+
plugin: {
|
|
10
|
+
key: 'org.dxos.plugin.stack',
|
|
11
|
+
name: 'Stacks',
|
|
12
|
+
author: 'DXOS',
|
|
13
|
+
description: trim`
|
|
14
|
+
The Stack plugin renders any Collection as a vertically scrollable stack of collapsible sections in the
|
|
15
|
+
Composer article surface. Each section wraps an arbitrary ECHO object and delegates its content rendering
|
|
16
|
+
to the section surface role, enabling any other plugin to contribute its own section renderer.
|
|
17
|
+
|
|
18
|
+
Sections can be individually collapsed to show only their title, expanded to reveal full content, navigated
|
|
19
|
+
to directly, or removed from the collection. New sections can be inserted before or after any existing
|
|
20
|
+
section via a per-section dropdown menu, or appended via the toolbar Add button.
|
|
21
|
+
|
|
22
|
+
The plugin registers StackViewType with the ECHO schema module so that per-section view metadata
|
|
23
|
+
(height overrides, custom display data) can be stored and replicated alongside the collection.
|
|
24
|
+
|
|
25
|
+
A settings panel exposes a separation toggle that controls whether visual dividers are rendered between
|
|
26
|
+
sections.
|
|
27
|
+
`,
|
|
28
|
+
source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-stack',
|
|
29
|
+
icon: { key: 'ph--stack-simple--regular' },
|
|
30
|
+
spec: 'PLUGIN.mdl',
|
|
31
|
+
},
|
|
32
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/plugin-stack",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1-staging.ee54ba693a",
|
|
4
4
|
"description": "DXOS Surface plugin for Stack entities",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -70,47 +70,53 @@
|
|
|
70
70
|
"types": "dist/types/src/index.d.ts",
|
|
71
71
|
"files": [
|
|
72
72
|
"dist",
|
|
73
|
+
"dx.config.ts",
|
|
73
74
|
"src",
|
|
74
75
|
"PLUGIN.mdl"
|
|
75
76
|
],
|
|
76
77
|
"dependencies": {
|
|
77
78
|
"@effect-atom/atom-react": "^0.5.0",
|
|
78
79
|
"@radix-ui/react-collapsible": "1.1.3",
|
|
79
|
-
"effect": "3.21.
|
|
80
|
-
"@dxos/app-framework": "0.9.
|
|
81
|
-
"@dxos/
|
|
82
|
-
"@dxos/app-toolkit": "0.9.
|
|
83
|
-
"@dxos/
|
|
84
|
-
"@dxos/
|
|
85
|
-
"@dxos/operation": "0.9.
|
|
86
|
-
"@dxos/plugin-client": "0.9.
|
|
87
|
-
"@dxos/plugin-deck": "0.9.
|
|
88
|
-
"@dxos/
|
|
89
|
-
"@dxos/
|
|
90
|
-
"@dxos/
|
|
91
|
-
"@dxos/react-ui-
|
|
92
|
-
"@dxos/util": "0.9.
|
|
93
|
-
"@dxos/
|
|
80
|
+
"effect": "3.21.4",
|
|
81
|
+
"@dxos/app-framework": "0.9.1-staging.ee54ba693a",
|
|
82
|
+
"@dxos/echo": "0.9.1-staging.ee54ba693a",
|
|
83
|
+
"@dxos/app-toolkit": "0.9.1-staging.ee54ba693a",
|
|
84
|
+
"@dxos/keys": "0.9.1-staging.ee54ba693a",
|
|
85
|
+
"@dxos/invariant": "0.9.1-staging.ee54ba693a",
|
|
86
|
+
"@dxos/operation": "0.9.1-staging.ee54ba693a",
|
|
87
|
+
"@dxos/plugin-client": "0.9.1-staging.ee54ba693a",
|
|
88
|
+
"@dxos/plugin-deck": "0.9.1-staging.ee54ba693a",
|
|
89
|
+
"@dxos/plugin-graph": "0.9.1-staging.ee54ba693a",
|
|
90
|
+
"@dxos/plugin-space": "0.9.1-staging.ee54ba693a",
|
|
91
|
+
"@dxos/react-ui-attention": "0.9.1-staging.ee54ba693a",
|
|
92
|
+
"@dxos/react-ui-form": "0.9.1-staging.ee54ba693a",
|
|
93
|
+
"@dxos/util": "0.9.1-staging.ee54ba693a",
|
|
94
|
+
"@dxos/react-ui-mosaic": "0.9.1-staging.ee54ba693a",
|
|
95
|
+
"@dxos/react-ui-menu": "0.9.1-staging.ee54ba693a"
|
|
94
96
|
},
|
|
95
97
|
"devDependencies": {
|
|
96
98
|
"@babel/core": "^7.18.13",
|
|
97
|
-
"@types/react": "~19.2.
|
|
99
|
+
"@types/react": "~19.2.17",
|
|
98
100
|
"@types/react-dom": "~19.2.3",
|
|
99
|
-
"react": "~19.2.
|
|
100
|
-
"react-dom": "~19.2.
|
|
101
|
+
"react": "~19.2.7",
|
|
102
|
+
"react-dom": "~19.2.7",
|
|
101
103
|
"vite": "^8.0.16",
|
|
102
|
-
"@dxos/plugin-
|
|
103
|
-
"@dxos/
|
|
104
|
-
"@dxos/
|
|
105
|
-
"@dxos/
|
|
104
|
+
"@dxos/plugin-sketch": "0.9.1-staging.ee54ba693a",
|
|
105
|
+
"@dxos/plugin-markdown": "0.9.1-staging.ee54ba693a",
|
|
106
|
+
"@dxos/react-client": "0.9.1-staging.ee54ba693a",
|
|
107
|
+
"@dxos/plugin-testing": "0.9.1-staging.ee54ba693a",
|
|
108
|
+
"@dxos/ui-theme": "0.9.1-staging.ee54ba693a",
|
|
109
|
+
"@dxos/storybook-utils": "0.9.1-staging.ee54ba693a",
|
|
110
|
+
"@dxos/random": "0.9.1-staging.ee54ba693a",
|
|
111
|
+
"@dxos/react-ui": "0.9.1-staging.ee54ba693a"
|
|
106
112
|
},
|
|
107
113
|
"peerDependencies": {
|
|
108
114
|
"@effect-atom/atom-react": "^0.5.0",
|
|
109
|
-
"effect": "3.21.
|
|
110
|
-
"react": "~19.2.
|
|
111
|
-
"react-dom": "~19.2.
|
|
112
|
-
"@dxos/react-ui": "0.9.
|
|
113
|
-
"@dxos/ui-theme": "0.9.
|
|
115
|
+
"effect": "3.21.4",
|
|
116
|
+
"react": "~19.2.7",
|
|
117
|
+
"react-dom": "~19.2.7",
|
|
118
|
+
"@dxos/react-ui": "0.9.1-staging.ee54ba693a",
|
|
119
|
+
"@dxos/ui-theme": "0.9.1-staging.ee54ba693a"
|
|
114
120
|
},
|
|
115
121
|
"publishConfig": {
|
|
116
122
|
"access": "public"
|
package/src/StackPlugin.ts
CHANGED
|
@@ -8,17 +8,17 @@ import { AppPlugin } from '@dxos/app-toolkit';
|
|
|
8
8
|
import { ReactSurface } from '#capabilities';
|
|
9
9
|
import { meta } from '#meta';
|
|
10
10
|
import { translations } from '#translations';
|
|
11
|
-
import {
|
|
11
|
+
import { Stack } from '#types';
|
|
12
12
|
|
|
13
13
|
// eslint-disable-next-line import/no-relative-packages
|
|
14
14
|
import pluginSpec from '../PLUGIN.mdl?raw';
|
|
15
15
|
|
|
16
16
|
export const StackPlugin = Plugin.define(meta).pipe(
|
|
17
|
-
AppPlugin.addSchemaModule({ schema: [
|
|
17
|
+
AppPlugin.addSchemaModule({ schema: [Stack.Stack] }),
|
|
18
18
|
AppPlugin.addSurfaceModule({ activate: ReactSurface }),
|
|
19
19
|
AppPlugin.addTranslationsModule({ translations }),
|
|
20
20
|
AppPlugin.addPluginAssetModule({
|
|
21
|
-
asset: { pluginId: meta.
|
|
21
|
+
asset: { pluginId: meta.profile.key, path: 'PLUGIN.mdl', content: pluginSpec, mimeType: 'application/x-mdl' },
|
|
22
22
|
}),
|
|
23
23
|
Plugin.make,
|
|
24
24
|
);
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, {
|
|
6
|
+
type ComponentPropsWithoutRef,
|
|
7
|
+
type ForwardedRef,
|
|
8
|
+
type PropsWithChildren,
|
|
9
|
+
createContext,
|
|
10
|
+
forwardRef,
|
|
11
|
+
useCallback,
|
|
12
|
+
useContext,
|
|
13
|
+
useMemo,
|
|
14
|
+
useState,
|
|
15
|
+
} from 'react';
|
|
16
|
+
|
|
17
|
+
import { Surface } from '@dxos/app-framework/ui';
|
|
18
|
+
import { Paths } from '@dxos/app-toolkit';
|
|
19
|
+
import { AppSurface, AttentionSigilButton } from '@dxos/app-toolkit/ui';
|
|
20
|
+
import { Obj } from '@dxos/echo';
|
|
21
|
+
import {
|
|
22
|
+
DropdownMenu,
|
|
23
|
+
Icon,
|
|
24
|
+
ScrollArea,
|
|
25
|
+
ScrollAreaRootProps,
|
|
26
|
+
type ThemedClassName,
|
|
27
|
+
useTranslation,
|
|
28
|
+
} from '@dxos/react-ui';
|
|
29
|
+
import { useAttentionAttributes } from '@dxos/react-ui-attention';
|
|
30
|
+
import { Mosaic, type MosaicEventHandler, type MosaicTileProps } from '@dxos/react-ui-mosaic';
|
|
31
|
+
|
|
32
|
+
import { meta } from '#meta';
|
|
33
|
+
|
|
34
|
+
//
|
|
35
|
+
// Types
|
|
36
|
+
//
|
|
37
|
+
|
|
38
|
+
export type StackSectionItem = {
|
|
39
|
+
id: string;
|
|
40
|
+
object: Obj.Unknown;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
//
|
|
44
|
+
// Context
|
|
45
|
+
//
|
|
46
|
+
|
|
47
|
+
/** Section-level callbacks consumed by stack sections. */
|
|
48
|
+
export type StackContextValue = {
|
|
49
|
+
attendableId: string;
|
|
50
|
+
/** Ids of sections that are currently collapsed. */
|
|
51
|
+
collapsed: ReadonlySet<string>;
|
|
52
|
+
onCollapse: (id: string, collapsed: boolean) => void;
|
|
53
|
+
/** Add a new section immediately after the given section. */
|
|
54
|
+
onAdd: (id: string) => void;
|
|
55
|
+
onMoveUp: (id: string) => void;
|
|
56
|
+
onMoveDown: (id: string) => void;
|
|
57
|
+
onDelete: (id: string) => void;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type StackContextType = StackContextValue & {
|
|
61
|
+
/** Container id used to scope Mosaic drag/drop. */
|
|
62
|
+
id: string;
|
|
63
|
+
eventHandler: MosaicEventHandler;
|
|
64
|
+
/** Scroll viewport element; threaded to the Mosaic container for autoscroll. */
|
|
65
|
+
viewport: HTMLElement | null;
|
|
66
|
+
setViewport: (element: HTMLElement | null) => void;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const StackContext = createContext<StackContextType | undefined>(undefined);
|
|
70
|
+
|
|
71
|
+
const useStackContext = (consumer: string): StackContextType => {
|
|
72
|
+
const context = useContext(StackContext);
|
|
73
|
+
if (!context) {
|
|
74
|
+
throw new Error(`\`${consumer}\` must be used within \`Stack.Root\`.`);
|
|
75
|
+
}
|
|
76
|
+
return context;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** Section-level callbacks consumed by stack sections. */
|
|
80
|
+
export const useStack = (): StackContextValue => useStackContext('useStack');
|
|
81
|
+
|
|
82
|
+
//
|
|
83
|
+
// Root
|
|
84
|
+
//
|
|
85
|
+
|
|
86
|
+
type StackRootProps = PropsWithChildren<
|
|
87
|
+
StackContextValue & {
|
|
88
|
+
/** Container id used to scope Mosaic drag/drop. */
|
|
89
|
+
id: string;
|
|
90
|
+
/** Drag/drop event handler; defaults to a read-only (no-drop) handler. */
|
|
91
|
+
eventHandler?: MosaicEventHandler;
|
|
92
|
+
}
|
|
93
|
+
>;
|
|
94
|
+
|
|
95
|
+
const StackRoot = ({
|
|
96
|
+
id,
|
|
97
|
+
eventHandler,
|
|
98
|
+
children,
|
|
99
|
+
attendableId,
|
|
100
|
+
collapsed,
|
|
101
|
+
onCollapse,
|
|
102
|
+
onAdd,
|
|
103
|
+
onMoveUp,
|
|
104
|
+
onMoveDown,
|
|
105
|
+
onDelete,
|
|
106
|
+
}: StackRootProps) => {
|
|
107
|
+
const [viewport, setViewport] = useState<HTMLElement | null>(null);
|
|
108
|
+
const value = useMemo<StackContextType>(
|
|
109
|
+
() => ({
|
|
110
|
+
id,
|
|
111
|
+
eventHandler: eventHandler ?? { id, canDrop: () => false },
|
|
112
|
+
viewport,
|
|
113
|
+
setViewport,
|
|
114
|
+
attendableId,
|
|
115
|
+
collapsed,
|
|
116
|
+
onCollapse,
|
|
117
|
+
onAdd,
|
|
118
|
+
onMoveUp,
|
|
119
|
+
onMoveDown,
|
|
120
|
+
onDelete,
|
|
121
|
+
}),
|
|
122
|
+
[id, eventHandler, viewport, attendableId, collapsed, onCollapse, onAdd, onMoveUp, onMoveDown, onDelete],
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<StackContext.Provider value={value}>
|
|
127
|
+
<Mosaic.Root>{children}</Mosaic.Root>
|
|
128
|
+
</StackContext.Provider>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
StackRoot.displayName = 'Stack.Root';
|
|
133
|
+
|
|
134
|
+
//
|
|
135
|
+
// Content
|
|
136
|
+
//
|
|
137
|
+
|
|
138
|
+
type StackContentProps = ThemedClassName<
|
|
139
|
+
ComponentPropsWithoutRef<'div'> & Pick<ScrollAreaRootProps, 'centered' | 'thin' | 'padding'>
|
|
140
|
+
>;
|
|
141
|
+
|
|
142
|
+
const StackContent = forwardRef<HTMLDivElement, StackContentProps>(({ children, ...props }, forwardedRef) => {
|
|
143
|
+
const { eventHandler, viewport } = useStackContext('Stack.Content');
|
|
144
|
+
return (
|
|
145
|
+
<Mosaic.Container asChild orientation='vertical' autoScroll={viewport} eventHandler={eventHandler}>
|
|
146
|
+
<ScrollArea.Root {...props} orientation='vertical' ref={forwardedRef}>
|
|
147
|
+
{children}
|
|
148
|
+
</ScrollArea.Root>
|
|
149
|
+
</Mosaic.Container>
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
StackContent.displayName = 'Stack.Content';
|
|
154
|
+
|
|
155
|
+
//
|
|
156
|
+
// Viewport
|
|
157
|
+
//
|
|
158
|
+
|
|
159
|
+
type StackViewportProps = ThemedClassName<PropsWithChildren>;
|
|
160
|
+
|
|
161
|
+
const StackViewport = forwardRef<HTMLDivElement, StackViewportProps>(
|
|
162
|
+
({ classNames, children }, forwardedRef: ForwardedRef<HTMLDivElement>) => {
|
|
163
|
+
const { setViewport } = useStackContext('Stack.Viewport');
|
|
164
|
+
// Capture the viewport element for autoscroll while still honouring a forwarded ref.
|
|
165
|
+
const setRefs = useCallback(
|
|
166
|
+
(element: HTMLDivElement | null) => {
|
|
167
|
+
setViewport(element);
|
|
168
|
+
if (typeof forwardedRef === 'function') {
|
|
169
|
+
forwardedRef(element);
|
|
170
|
+
} else if (forwardedRef) {
|
|
171
|
+
forwardedRef.current = element;
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
[setViewport, forwardedRef],
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<ScrollArea.Viewport classNames={classNames} ref={setRefs}>
|
|
179
|
+
{children}
|
|
180
|
+
</ScrollArea.Viewport>
|
|
181
|
+
);
|
|
182
|
+
},
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
StackViewport.displayName = 'Stack.Viewport';
|
|
186
|
+
|
|
187
|
+
//
|
|
188
|
+
// Section
|
|
189
|
+
//
|
|
190
|
+
|
|
191
|
+
type StackSectionProps = MosaicTileProps<StackSectionItem>;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* A stack section rendered as a Mosaic tile: a 40px left rail (drag handle + section menu) beside the
|
|
195
|
+
* main content area, which renders the object's content surface (or its title when collapsed). The
|
|
196
|
+
* rail sticks to the top of the scroll viewport while the section is in view.
|
|
197
|
+
*/
|
|
198
|
+
// TODO(burdon): All sections are intrinsic (content-sized) for now. Extrinsic content (e.g. a sketch
|
|
199
|
+
// with no intrinsic height) should later get a Mosaic-native resizable height affordance.
|
|
200
|
+
|
|
201
|
+
// Inline grip glyph for the drag preview. The native drag image does not rasterize external SVG sprite
|
|
202
|
+
// `<use>` icons (e.g. `@dxos/react-ui` `Icon`), so the preview uses plain inline SVG circles instead.
|
|
203
|
+
const DragHandleGlyph = () => (
|
|
204
|
+
<svg width={10} height={16} viewBox='0 0 10 16' aria-hidden className='shrink-0 text-description'>
|
|
205
|
+
{[3, 8, 13].flatMap((cy) =>
|
|
206
|
+
[2.5, 7.5].map((cx) => <circle key={`${cx}-${cy}`} cx={cx} cy={cy} r={1.1} fill='currentColor' />),
|
|
207
|
+
)}
|
|
208
|
+
</svg>
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const StackSection = ({ data, ...tileProps }: StackSectionProps) => {
|
|
212
|
+
const { id, object } = data;
|
|
213
|
+
const { t } = useTranslation(meta.profile.key);
|
|
214
|
+
const { attendableId: parentAttendableId, collapsed, onAdd, onMoveUp, onMoveDown, onCollapse, onDelete } = useStack();
|
|
215
|
+
const [optionsMenuOpen, setOptionsMenuOpen] = useState(false);
|
|
216
|
+
const attendableId = Paths.getCollectionObjectPath(parentAttendableId, object.id);
|
|
217
|
+
const attentionAttrs = useAttentionAttributes(attendableId);
|
|
218
|
+
const surfaceData = useMemo(() => ({ attendableId, subject: object }), [object, attendableId]);
|
|
219
|
+
const isCollapsed = collapsed.has(id);
|
|
220
|
+
const icon = Obj.getIcon(object)?.icon ?? 'ph--circle-dashed--regular';
|
|
221
|
+
const title = Obj.getLabel(object, { fallback: 'typename' }) ?? t('untitled-section.title');
|
|
222
|
+
|
|
223
|
+
const rail = (
|
|
224
|
+
<div className='grid grid-rows-[min-content_1fr]'>
|
|
225
|
+
<div className='p-1 bg-toolbar-surface'>
|
|
226
|
+
<DropdownMenu.Root open={optionsMenuOpen} onOpenChange={setOptionsMenuOpen}>
|
|
227
|
+
<DropdownMenu.Trigger asChild>
|
|
228
|
+
<AttentionSigilButton size='md' attendableId={attendableId}>
|
|
229
|
+
<Icon icon={icon} classNames='transition-opacity' />
|
|
230
|
+
</AttentionSigilButton>
|
|
231
|
+
</DropdownMenu.Trigger>
|
|
232
|
+
<DropdownMenu.Portal>
|
|
233
|
+
<DropdownMenu.Content>
|
|
234
|
+
<DropdownMenu.Viewport>
|
|
235
|
+
{isCollapsed ? (
|
|
236
|
+
<DropdownMenu.Item onClick={() => onCollapse(id, false)} data-testid='section.expand'>
|
|
237
|
+
<Icon icon='ph--arrows-out-line-vertical--regular' />
|
|
238
|
+
<span className='ms-2 grow'>{t('expand.label')}</span>
|
|
239
|
+
</DropdownMenu.Item>
|
|
240
|
+
) : (
|
|
241
|
+
<DropdownMenu.Item onClick={() => onCollapse(id, true)} data-testid='section.collapse'>
|
|
242
|
+
<Icon icon='ph--arrows-in-line-vertical--regular' />
|
|
243
|
+
<span className='ms-2 grow'>{t('collapse.label')}</span>
|
|
244
|
+
</DropdownMenu.Item>
|
|
245
|
+
)}
|
|
246
|
+
<DropdownMenu.Separator />
|
|
247
|
+
<DropdownMenu.Item onClick={() => onAdd(id)} data-testid='section.add'>
|
|
248
|
+
<Icon icon='ph--plus--regular' />
|
|
249
|
+
<span className='ms-2 grow'>{t('add-section.label')}</span>
|
|
250
|
+
</DropdownMenu.Item>
|
|
251
|
+
<DropdownMenu.Item onClick={() => onMoveUp(id)} data-testid='section.move-up'>
|
|
252
|
+
<Icon icon='ph--arrow-line-up--regular' />
|
|
253
|
+
<span className='ms-2 grow'>{t('move-up.label')}</span>
|
|
254
|
+
</DropdownMenu.Item>
|
|
255
|
+
<DropdownMenu.Item onClick={() => onMoveDown(id)} data-testid='section.move-down'>
|
|
256
|
+
<Icon icon='ph--arrow-line-down--regular' />
|
|
257
|
+
<span className='ms-2 grow'>{t('move-down.label')}</span>
|
|
258
|
+
</DropdownMenu.Item>
|
|
259
|
+
<DropdownMenu.Separator />
|
|
260
|
+
<DropdownMenu.Item onClick={() => onDelete(id)} data-testid='section.remove'>
|
|
261
|
+
<Icon icon='ph--trash--regular' />
|
|
262
|
+
<span className='ms-2 grow'>{t('remove-section.label')}</span>
|
|
263
|
+
</DropdownMenu.Item>
|
|
264
|
+
</DropdownMenu.Viewport>
|
|
265
|
+
<DropdownMenu.Arrow />
|
|
266
|
+
</DropdownMenu.Content>
|
|
267
|
+
</DropdownMenu.Portal>
|
|
268
|
+
</DropdownMenu.Root>
|
|
269
|
+
</div>
|
|
270
|
+
<div className='p-1'>
|
|
271
|
+
{/* Inline glyph (not a sprite `Icon`) so the handle stays visible in the tile's native drag image. */}
|
|
272
|
+
<Mosaic.DragHandle
|
|
273
|
+
label={t('drag-handle.label')}
|
|
274
|
+
classNames='p-1 min-h-0 w-(--dx-rail-item) h-(--dx-rail-item)'
|
|
275
|
+
testId='section.drag-handle'
|
|
276
|
+
>
|
|
277
|
+
<DragHandleGlyph />
|
|
278
|
+
</Mosaic.DragHandle>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
return (
|
|
284
|
+
<Mosaic.Tile
|
|
285
|
+
{...tileProps}
|
|
286
|
+
data={data}
|
|
287
|
+
classNames='grid grid-cols-[var(--dx-rail-action)_1fr] dx-attention-surface border border-subdued-separator'
|
|
288
|
+
>
|
|
289
|
+
<div className='border-e border-subdued-separator'>
|
|
290
|
+
<div className='sticky top-0 flex flex-col items-center'>{rail}</div>
|
|
291
|
+
</div>
|
|
292
|
+
<div {...attentionAttrs} className='min-w-0'>
|
|
293
|
+
<span className='sr-only'>{title}</span>
|
|
294
|
+
{isCollapsed ? (
|
|
295
|
+
<div className='h-(--dx-toolbar-size) flex p-1'>
|
|
296
|
+
<h2 className='flex items-center font-medium'>{title}</h2>
|
|
297
|
+
</div>
|
|
298
|
+
) : (
|
|
299
|
+
<Surface.Surface type={AppSurface.Section} data={surfaceData} limit={1} />
|
|
300
|
+
)}
|
|
301
|
+
</div>
|
|
302
|
+
</Mosaic.Tile>
|
|
303
|
+
);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
StackSection.displayName = 'Stack.Section';
|
|
307
|
+
|
|
308
|
+
//
|
|
309
|
+
// Stack
|
|
310
|
+
//
|
|
311
|
+
|
|
312
|
+
export const Stack = {
|
|
313
|
+
Root: StackRoot,
|
|
314
|
+
Content: StackContent,
|
|
315
|
+
Viewport: StackViewport,
|
|
316
|
+
Section: StackSection,
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
export type { StackContentProps, StackRootProps, StackSectionProps, StackViewportProps };
|
package/src/components/index.ts
CHANGED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import React from 'react';
|
|
8
|
+
|
|
9
|
+
import { Capability } from '@dxos/app-framework';
|
|
10
|
+
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
11
|
+
import { AppActivationEvents, AppCapabilities } from '@dxos/app-toolkit';
|
|
12
|
+
import { AppSurface } from '@dxos/app-toolkit/ui';
|
|
13
|
+
import { Collection, Filter, Ref } from '@dxos/echo';
|
|
14
|
+
import { ClientPlugin, initializeIdentity } from '@dxos/plugin-client/testing';
|
|
15
|
+
import { Markdown, MarkdownEvents } from '@dxos/plugin-markdown';
|
|
16
|
+
import { MarkdownPlugin } from '@dxos/plugin-markdown/testing';
|
|
17
|
+
import { Sketch } from '@dxos/plugin-sketch';
|
|
18
|
+
import { SketchPlugin } from '@dxos/plugin-sketch/plugin';
|
|
19
|
+
import { SketchBuilder } from '@dxos/plugin-sketch/testing';
|
|
20
|
+
import { SpacePlugin } from '@dxos/plugin-space/testing';
|
|
21
|
+
import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
|
|
22
|
+
import { random } from '@dxos/random';
|
|
23
|
+
import { useClient } from '@dxos/react-client';
|
|
24
|
+
import { useQuery } from '@dxos/react-client/echo';
|
|
25
|
+
import { Loading, withLayout } from '@dxos/react-ui/testing';
|
|
26
|
+
|
|
27
|
+
import { translations } from '#translations';
|
|
28
|
+
|
|
29
|
+
import { StackArticle, type StackArticleProps } from './StackArticle';
|
|
30
|
+
|
|
31
|
+
// A minimal sketch (tldraw `tldraw.com/2`) snapshot, used as a test image.
|
|
32
|
+
const SKETCH_CONTENT = new SketchBuilder()
|
|
33
|
+
.rectangle({ id: 'rect', x: 0, y: 0, text: 'DXOS', color: 'blue', fill: 'solid', size: 'l' })
|
|
34
|
+
.ellipse({ id: 'echo', x: 320, y: 0, text: 'ECHO', color: 'green' })
|
|
35
|
+
.arrow({ from: 'rect', to: 'echo' })
|
|
36
|
+
.build();
|
|
37
|
+
|
|
38
|
+
const DefaultStory = (args: StackArticleProps) => {
|
|
39
|
+
const client = useClient();
|
|
40
|
+
const [space] = client.spaces.get();
|
|
41
|
+
const [collection] = useQuery(space?.db, Filter.type(Collection.Collection));
|
|
42
|
+
if (!collection) {
|
|
43
|
+
return <Loading />;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return <StackArticle {...args} subject={collection} attendableId='test' />;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const meta: Meta<typeof StackArticle> = {
|
|
50
|
+
title: 'plugins/plugin-stack/containers/StackArticle',
|
|
51
|
+
component: StackArticle,
|
|
52
|
+
render: DefaultStory,
|
|
53
|
+
decorators: [
|
|
54
|
+
withLayout({ layout: 'fullscreen' }),
|
|
55
|
+
withPluginManager({
|
|
56
|
+
setupEvents: [AppActivationEvents.SetupSettings, MarkdownEvents.SetupExtensions],
|
|
57
|
+
capabilities: [Capability.contributes(AppCapabilities.Translations, translations)],
|
|
58
|
+
plugins: [
|
|
59
|
+
...corePlugins(),
|
|
60
|
+
ClientPlugin({
|
|
61
|
+
types: [Collection.Collection, Markdown.Document, Sketch.Sketch, Sketch.Canvas],
|
|
62
|
+
onClientInitialized: ({ client }) =>
|
|
63
|
+
Effect.gen(function* () {
|
|
64
|
+
const { personalSpace: space } = yield* initializeIdentity(client);
|
|
65
|
+
|
|
66
|
+
const documents = Array.from({ length: 5 }).map(() =>
|
|
67
|
+
Ref.make(
|
|
68
|
+
space.db.add(
|
|
69
|
+
Markdown.make({
|
|
70
|
+
name: random.lorem.sentence(5),
|
|
71
|
+
content:
|
|
72
|
+
[
|
|
73
|
+
`# ${random.lorem.sentence(5)}`,
|
|
74
|
+
random.lorem.paragraph(),
|
|
75
|
+
random.lorem.paragraph(),
|
|
76
|
+
random.lorem.paragraph(),
|
|
77
|
+
].join('\n\n') + '\n',
|
|
78
|
+
}),
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const sketches = [
|
|
84
|
+
Ref.make(
|
|
85
|
+
space.db.add(
|
|
86
|
+
Sketch.make({
|
|
87
|
+
name: random.lorem.sentence(2),
|
|
88
|
+
canvas: {
|
|
89
|
+
content: SKETCH_CONTENT,
|
|
90
|
+
},
|
|
91
|
+
}),
|
|
92
|
+
),
|
|
93
|
+
),
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
const insertAt = 1;
|
|
97
|
+
space.db.add(
|
|
98
|
+
Collection.make({
|
|
99
|
+
name: random.lorem.sentence(5),
|
|
100
|
+
objects: [...documents.slice(0, insertAt), ...sketches, ...documents.slice(insertAt + 1)],
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
}),
|
|
104
|
+
}),
|
|
105
|
+
MarkdownPlugin(),
|
|
106
|
+
SketchPlugin(),
|
|
107
|
+
SpacePlugin({}),
|
|
108
|
+
StorybookPlugin({}),
|
|
109
|
+
],
|
|
110
|
+
}),
|
|
111
|
+
],
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export default meta;
|
|
115
|
+
|
|
116
|
+
type Story = StoryObj<typeof meta>;
|
|
117
|
+
|
|
118
|
+
export const Default: Story = {
|
|
119
|
+
args: {
|
|
120
|
+
role: AppSurface.Article.role,
|
|
121
|
+
},
|
|
122
|
+
};
|