@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.
@@ -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, useAttendedIds } from '@dxos/react-ui-attention';
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
- context?: string;
21
+ id?: string;
22
22
  layoutParts: LayoutParts;
23
23
  flatDeck?: boolean;
24
24
  };
25
25
 
26
- export const ComplementarySidebar = ({ context, layoutParts, flatDeck }: ComplementarySidebarProps) => {
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
- <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
- />
47
- {/* TODO(wittjosiah): Render some placeholder when node is undefined. */}
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
- </div>
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, useOnTransition, useTranslation } from '@dxos/react-ui';
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 scrollLeftRef = useRef<number | null>();
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
- scrollLeftRef.current = null;
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
- const restoreScroll = useCallback(() => {
101
- if (deckRef.current && scrollLeftRef.current != null) {
102
- deckRef.current.scrollLeft = scrollLeftRef.current;
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
- scrollLeftRef.current = (event.target as HTMLDivElement).scrollLeft;
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 = useMemo(() => {
145
- if (layoutMode === 'deck' && overscroll === 'centering') {
146
- return calculateOverscroll(layoutParts.main, plankSizing, sidebarOpen, complementarySidebarOpen);
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={context.complementarySidebarOpen}
175
- onComplementarySidebarOpenChange={(next) => (context.complementarySidebarOpen = next)}
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 onClick={() => (context.sidebarOpen = !context.sidebarOpen)} variant='ghost' classNames='p-1'>
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
- {/* TODO(wittjosiah): Get context from layout parts. */}
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 { Icon, Popover, toLocalizedString, useMediaQuery, useTranslation } from '@dxos/react-ui';
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 icon = node?.properties?.icon ?? 'ph--placeholder--regular';
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
- icon={icon}
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 icon={icon} size={5} />
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={layoutPart === 'complementary' ? 'minify-end' : true}
154
+ close={layoutCoordinate?.part === 'complementary' ? 'minify-end' : true}
152
155
  />
153
156
  </PlankHeading.Root>
154
157
  );
@@ -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, false) : undefined);
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
  }
package/src/layout.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  //
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
-
5
4
  import { produce } from 'immer';
6
5
 
7
6
  import {