@dxos/plugin-deck 0.6.12-main.f9d0246 → 0.6.12-staging.0b4bb48
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/index.mjs +76 -87
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/DeckPlugin.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts +2 -2
- package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts.map +1 -1
- package/dist/types/src/hooks/useNode.d.ts.map +1 -1
- package/dist/types/src/layout.d.ts.map +1 -1
- package/package.json +29 -29
- package/src/DeckPlugin.tsx +51 -80
- package/src/components/DeckLayout/ComplementarySidebar.tsx +16 -18
- package/src/components/DeckLayout/DeckLayout.tsx +43 -34
- package/src/components/DeckLayout/NodePlankHeading.tsx +9 -6
- package/src/hooks/useNode.ts +1 -5
- package/src/layout.ts +0 -1
|
@@ -7,7 +7,7 @@ import React from 'react';
|
|
|
7
7
|
import { type LayoutParts, SLUG_PATH_SEPARATOR, Surface } from '@dxos/app-framework';
|
|
8
8
|
import { useGraph } from '@dxos/plugin-graph';
|
|
9
9
|
import { Main } from '@dxos/react-ui';
|
|
10
|
-
import { createAttendableAttributes
|
|
10
|
+
import { createAttendableAttributes } from '@dxos/react-ui-attention';
|
|
11
11
|
import { deckGrid } from '@dxos/react-ui-deck';
|
|
12
12
|
import { mx } from '@dxos/react-ui-theme';
|
|
13
13
|
|
|
@@ -18,34 +18,32 @@ import { useNode, useNodeActionExpander } from '../../hooks';
|
|
|
18
18
|
import { useLayout } from '../LayoutContext';
|
|
19
19
|
|
|
20
20
|
export type ComplementarySidebarProps = {
|
|
21
|
-
|
|
21
|
+
id?: string;
|
|
22
22
|
layoutParts: LayoutParts;
|
|
23
23
|
flatDeck?: boolean;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
export const ComplementarySidebar = ({
|
|
26
|
+
export const ComplementarySidebar = ({ id, layoutParts, flatDeck }: ComplementarySidebarProps) => {
|
|
27
27
|
const { popoverAnchorId } = useLayout();
|
|
28
|
-
const attended = useAttendedIds();
|
|
29
|
-
const id = attended[0] ? `${attended[0]}${SLUG_PATH_SEPARATOR}${context}` : undefined;
|
|
30
28
|
const { graph } = useGraph();
|
|
31
29
|
const node = useNode(graph, id);
|
|
30
|
+
// const complementaryAvailable = useMemo(() => id === NAV_ID || !!node, [id, node]);
|
|
32
31
|
const complementaryAttrs = createAttendableAttributes(id?.split(SLUG_PATH_SEPARATOR)[0] ?? 'never');
|
|
33
32
|
|
|
34
33
|
useNodeActionExpander(node);
|
|
35
34
|
|
|
36
35
|
return (
|
|
37
36
|
<Main.ComplementarySidebar {...complementaryAttrs}>
|
|
38
|
-
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
{node && (
|
|
37
|
+
{node ? (
|
|
38
|
+
<div role='none' className={mx(deckGrid, 'grid-cols-1 bs-full')}>
|
|
39
|
+
<NodePlankHeading
|
|
40
|
+
node={node}
|
|
41
|
+
id={id}
|
|
42
|
+
layoutParts={layoutParts}
|
|
43
|
+
layoutPart='complementary'
|
|
44
|
+
popoverAnchorId={popoverAnchorId}
|
|
45
|
+
flatDeck={flatDeck}
|
|
46
|
+
/>
|
|
49
47
|
<Surface
|
|
50
48
|
role='article'
|
|
51
49
|
data={{ subject: node.data, part: 'complementary', popoverAnchorId }}
|
|
@@ -53,8 +51,8 @@ export const ComplementarySidebar = ({ context, layoutParts, flatDeck }: Complem
|
|
|
53
51
|
fallback={PlankContentError}
|
|
54
52
|
placeholder={<PlankLoading />}
|
|
55
53
|
/>
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
</div>
|
|
55
|
+
) : null}
|
|
58
56
|
</Main.ComplementarySidebar>
|
|
59
57
|
);
|
|
60
58
|
};
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Sidebar as MenuIcon } from '@phosphor-icons/react';
|
|
6
|
-
import React, { useCallback, useEffect, useMemo, useRef, type UIEvent } from 'react';
|
|
6
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState, useLayoutEffect, type UIEvent } from 'react';
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
+
SLUG_PATH_SEPARATOR,
|
|
9
10
|
type Attention,
|
|
10
11
|
type LayoutEntry,
|
|
11
12
|
type LayoutParts,
|
|
@@ -14,7 +15,7 @@ import {
|
|
|
14
15
|
firstIdInPart,
|
|
15
16
|
usePlugin,
|
|
16
17
|
} from '@dxos/app-framework';
|
|
17
|
-
import { Button, Dialog, Main, Popover,
|
|
18
|
+
import { Button, Dialog, Main, Popover, useTranslation } from '@dxos/react-ui';
|
|
18
19
|
import { Deck } from '@dxos/react-ui-deck';
|
|
19
20
|
import { getSize } from '@dxos/react-ui-theme';
|
|
20
21
|
|
|
@@ -72,38 +73,33 @@ export const DeckLayout = ({
|
|
|
72
73
|
const searchPlugin = usePlugin('dxos.org/plugin/search');
|
|
73
74
|
const fullScreenSlug = useMemo(() => firstIdInPart(layoutParts, 'fullScreen'), [layoutParts]);
|
|
74
75
|
|
|
75
|
-
const
|
|
76
|
-
const deckRef = useRef<HTMLDivElement>(null);
|
|
77
|
-
|
|
78
|
-
// Ensure the first plank is attended when the deck is first rendered.
|
|
79
|
-
useEffect(() => {
|
|
80
|
-
const firstId = layoutMode === 'solo' ? firstIdInPart(layoutParts, 'solo') : firstIdInPart(layoutParts, 'main');
|
|
81
|
-
if (attention.attended.size === 0 && firstId) {
|
|
82
|
-
// TODO(wittjosiah): Focusing the type button is a workaround.
|
|
83
|
-
// If the plank is directly focused on first load the focus ring appears.
|
|
84
|
-
document.querySelector<HTMLElement>(`article[data-attendable-id="${firstId}"] button`)?.focus();
|
|
85
|
-
}
|
|
86
|
-
}, []);
|
|
76
|
+
const [scrollLeft, setScrollLeft] = useState<number | null>(null);
|
|
77
|
+
const deckRef = useRef<HTMLDivElement | null>(null);
|
|
78
|
+
const restoreScrollRef = useRef<boolean>(false);
|
|
87
79
|
|
|
88
80
|
/**
|
|
89
81
|
* Clear scroll restoration state if the window is resized
|
|
90
82
|
*/
|
|
91
83
|
const handleResize = useCallback(() => {
|
|
92
|
-
|
|
84
|
+
setScrollLeft(null);
|
|
93
85
|
}, []);
|
|
94
|
-
|
|
95
86
|
useEffect(() => {
|
|
96
87
|
window.addEventListener('resize', handleResize);
|
|
97
88
|
return () => window.removeEventListener('resize', handleResize);
|
|
98
89
|
}, [handleResize]);
|
|
99
90
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Restore scroll when returning to deck mode
|
|
93
|
+
*/
|
|
94
|
+
useLayoutEffect(() => {
|
|
95
|
+
if (layoutMode !== 'deck') {
|
|
96
|
+
restoreScrollRef.current = true;
|
|
97
|
+
} else if (restoreScrollRef.current && deckRef.current && scrollLeft) {
|
|
98
|
+
// console.log('[restoring scrollLeft]', scrollLeft);
|
|
99
|
+
deckRef.current.scrollLeft = scrollLeft;
|
|
100
|
+
restoreScrollRef.current = false;
|
|
103
101
|
}
|
|
104
|
-
}, []);
|
|
105
|
-
|
|
106
|
-
useOnTransition(layoutMode, (mode) => mode !== 'deck', 'deck', restoreScroll);
|
|
102
|
+
}, [layoutMode, deckRef.current, scrollLeft]);
|
|
107
103
|
|
|
108
104
|
/**
|
|
109
105
|
* Save scroll position as the user scrolls
|
|
@@ -111,12 +107,20 @@ export const DeckLayout = ({
|
|
|
111
107
|
const handleScroll = useCallback(
|
|
112
108
|
(event: UIEvent) => {
|
|
113
109
|
if (layoutMode === 'deck' && event.currentTarget === event.target) {
|
|
114
|
-
|
|
110
|
+
// console.log('[save scroll left]', (event.target as HTMLDivElement).scrollLeft);
|
|
111
|
+
setScrollLeft((event.target as HTMLDivElement).scrollLeft);
|
|
115
112
|
}
|
|
116
113
|
},
|
|
117
114
|
[layoutMode],
|
|
118
115
|
);
|
|
119
116
|
|
|
117
|
+
const complementarySlug = useMemo(() => {
|
|
118
|
+
const entry = layoutParts.complementary?.at(0);
|
|
119
|
+
if (entry) {
|
|
120
|
+
return entry.path ? `${entry.id}${SLUG_PATH_SEPARATOR}${entry.path}` : entry.id;
|
|
121
|
+
}
|
|
122
|
+
}, [layoutParts]);
|
|
123
|
+
|
|
120
124
|
const firstAttendedId = useMemo(() => Array.from(attention.attended ?? [])[0], [attention.attended]);
|
|
121
125
|
|
|
122
126
|
useEffect(() => {
|
|
@@ -141,12 +145,10 @@ export const DeckLayout = ({
|
|
|
141
145
|
return parts;
|
|
142
146
|
}, [layoutParts.main, layoutParts.solo]);
|
|
143
147
|
|
|
144
|
-
const padding =
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return {};
|
|
149
|
-
}, [layoutMode, overscroll, layoutParts.main, plankSizing, sidebarOpen, complementarySidebarOpen]);
|
|
148
|
+
const padding =
|
|
149
|
+
layoutMode === 'deck' && overscroll === 'centering'
|
|
150
|
+
? calculateOverscroll(layoutParts.main, plankSizing, sidebarOpen, complementarySidebarOpen)
|
|
151
|
+
: {};
|
|
150
152
|
|
|
151
153
|
if (layoutMode === 'fullscreen') {
|
|
152
154
|
return <Fullscreen id={fullScreenSlug} />;
|
|
@@ -171,17 +173,25 @@ export const DeckLayout = ({
|
|
|
171
173
|
<Main.Root
|
|
172
174
|
navigationSidebarOpen={context.sidebarOpen}
|
|
173
175
|
onNavigationSidebarOpenChange={(next) => (context.sidebarOpen = next)}
|
|
174
|
-
complementarySidebarOpen
|
|
175
|
-
|
|
176
|
+
{...(complementarySidebarOpen !== null && {
|
|
177
|
+
complementarySidebarOpen: /* complementaryAvailable && */ context.complementarySidebarOpen as boolean,
|
|
178
|
+
onComplementarySidebarOpenChange: (next) => (context.complementarySidebarOpen = next),
|
|
179
|
+
})}
|
|
176
180
|
>
|
|
177
181
|
{/* Notch */}
|
|
178
182
|
<Main.Notch classNames='z-[21]'>
|
|
179
183
|
<Surface role='notch-start' />
|
|
180
|
-
<Button
|
|
184
|
+
<Button
|
|
185
|
+
// disabled={!sidebarAvailable}
|
|
186
|
+
onClick={() => (context.sidebarOpen = !context.sidebarOpen)}
|
|
187
|
+
variant='ghost'
|
|
188
|
+
classNames='p-1'
|
|
189
|
+
>
|
|
181
190
|
<span className='sr-only'>{t('open navigation sidebar label')}</span>
|
|
182
191
|
<MenuIcon weight='light' className={getSize(5)} />
|
|
183
192
|
</Button>
|
|
184
193
|
<Button
|
|
194
|
+
// disabled={!complementaryAvailable}
|
|
185
195
|
onClick={() => (context.complementarySidebarOpen = !context.complementarySidebarOpen)}
|
|
186
196
|
variant='ghost'
|
|
187
197
|
classNames='p-1'
|
|
@@ -196,8 +206,7 @@ export const DeckLayout = ({
|
|
|
196
206
|
<Sidebar attention={attention} layoutParts={layoutParts} />
|
|
197
207
|
|
|
198
208
|
{/* Right sidebar. */}
|
|
199
|
-
{
|
|
200
|
-
<ComplementarySidebar context='comments' layoutParts={layoutParts} flatDeck={flatDeck} />
|
|
209
|
+
<ComplementarySidebar id={complementarySlug} layoutParts={layoutParts} flatDeck={flatDeck} />
|
|
201
210
|
|
|
202
211
|
{/* Dialog overlay to dismiss dialogs. */}
|
|
203
212
|
<Main.Overlay />
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { Placeholder } from '@phosphor-icons/react';
|
|
5
6
|
import React, { Fragment, useEffect } from 'react';
|
|
6
7
|
|
|
7
8
|
import {
|
|
8
9
|
LayoutAction,
|
|
9
10
|
NavigationAction,
|
|
11
|
+
SLUG_COLLECTION_INDICATOR,
|
|
10
12
|
SLUG_PATH_SEPARATOR,
|
|
11
13
|
Surface,
|
|
12
14
|
useIntentDispatcher,
|
|
@@ -17,8 +19,8 @@ import {
|
|
|
17
19
|
type LayoutEntry,
|
|
18
20
|
} from '@dxos/app-framework';
|
|
19
21
|
import { type Node, useGraph } from '@dxos/plugin-graph';
|
|
20
|
-
import {
|
|
21
|
-
import { PlankHeading } from '@dxos/react-ui-deck';
|
|
22
|
+
import { Popover, toLocalizedString, useMediaQuery, useTranslation } from '@dxos/react-ui';
|
|
23
|
+
import { PlankHeading, plankHeadingIconProps } from '@dxos/react-ui-deck';
|
|
22
24
|
import { TextTooltip } from '@dxos/react-ui-text-tooltip';
|
|
23
25
|
|
|
24
26
|
import { DECK_PLUGIN } from '../../meta';
|
|
@@ -45,7 +47,7 @@ export const NodePlankHeading = ({
|
|
|
45
47
|
}) => {
|
|
46
48
|
const { t } = useTranslation(DECK_PLUGIN);
|
|
47
49
|
const { graph } = useGraph();
|
|
48
|
-
const
|
|
50
|
+
const Icon = node?.properties?.icon ?? Placeholder;
|
|
49
51
|
const label = pending
|
|
50
52
|
? t('pending heading')
|
|
51
53
|
: toLocalizedString(node?.properties?.label ?? ['plank heading fallback label', { ns: DECK_PLUGIN }], t);
|
|
@@ -78,7 +80,7 @@ export const NodePlankHeading = ({
|
|
|
78
80
|
<ActionRoot>
|
|
79
81
|
{node ? (
|
|
80
82
|
<PlankHeading.ActionsMenu
|
|
81
|
-
|
|
83
|
+
Icon={Icon}
|
|
82
84
|
attendableId={attendableId}
|
|
83
85
|
triggerLabel={t('actions menu label')}
|
|
84
86
|
actions={graph.actions(node)}
|
|
@@ -91,7 +93,7 @@ export const NodePlankHeading = ({
|
|
|
91
93
|
) : (
|
|
92
94
|
<PlankHeading.Button>
|
|
93
95
|
<span className='sr-only'>{label}</span>
|
|
94
|
-
<Icon
|
|
96
|
+
<Icon {...plankHeadingIconProps} />
|
|
95
97
|
</PlankHeading.Button>
|
|
96
98
|
)}
|
|
97
99
|
</ActionRoot>
|
|
@@ -141,6 +143,7 @@ export const NodePlankHeading = ({
|
|
|
141
143
|
action: NavigationAction.CLOSE,
|
|
142
144
|
data: {
|
|
143
145
|
activeParts: {
|
|
146
|
+
complementary: [`${id}${SLUG_PATH_SEPARATOR}comments${SLUG_COLLECTION_INDICATOR}`],
|
|
144
147
|
[layoutPart]: [id],
|
|
145
148
|
},
|
|
146
149
|
},
|
|
@@ -148,7 +151,7 @@ export const NodePlankHeading = ({
|
|
|
148
151
|
: { action: NavigationAction.ADJUST, data: { type: eventType, layoutCoordinate } },
|
|
149
152
|
);
|
|
150
153
|
}}
|
|
151
|
-
close={
|
|
154
|
+
close={layoutCoordinate?.part === 'complementary' ? 'minify-end' : true}
|
|
152
155
|
/>
|
|
153
156
|
</PlankHeading.Root>
|
|
154
157
|
);
|
package/src/hooks/useNode.ts
CHANGED
|
@@ -16,13 +16,9 @@ import { type Graph, type Node } from '@dxos/plugin-graph';
|
|
|
16
16
|
*/
|
|
17
17
|
// TODO(wittjosiah): Factor out.
|
|
18
18
|
export const useNode = <T = any>(graph: Graph, id?: string, timeout?: number): Node<T> | undefined => {
|
|
19
|
-
const [nodeState, setNodeState] = useState<Node<T> | undefined>(id ? graph.findNode(id
|
|
19
|
+
const [nodeState, setNodeState] = useState<Node<T> | undefined>(id ? graph.findNode(id) : undefined);
|
|
20
20
|
|
|
21
21
|
useEffect(() => {
|
|
22
|
-
if (!id && nodeState) {
|
|
23
|
-
setNodeState(undefined);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
22
|
if (nodeState?.id === id || !id) {
|
|
27
23
|
return;
|
|
28
24
|
}
|