@dxos/plugin-deck 0.6.8-main.046e6cf

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.
Files changed (106) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +15 -0
  3. package/dist/lib/browser/chunk-YVHGFQQR.mjs +12 -0
  4. package/dist/lib/browser/chunk-YVHGFQQR.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +1657 -0
  6. package/dist/lib/browser/index.mjs.map +7 -0
  7. package/dist/lib/browser/meta.json +1 -0
  8. package/dist/lib/browser/meta.mjs +9 -0
  9. package/dist/lib/browser/meta.mjs.map +7 -0
  10. package/dist/types/src/DeckPlugin.d.ts +15 -0
  11. package/dist/types/src/DeckPlugin.d.ts.map +1 -0
  12. package/dist/types/src/components/DeckContext.d.ts +8 -0
  13. package/dist/types/src/components/DeckContext.d.ts.map +1 -0
  14. package/dist/types/src/components/DeckLayout/ActiveNode.d.ts +5 -0
  15. package/dist/types/src/components/DeckLayout/ActiveNode.d.ts.map +1 -0
  16. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts +9 -0
  17. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts.map +1 -0
  18. package/dist/types/src/components/DeckLayout/ContentEmpty.d.ts +3 -0
  19. package/dist/types/src/components/DeckLayout/ContentEmpty.d.ts.map +1 -0
  20. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts +25 -0
  21. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -0
  22. package/dist/types/src/components/DeckLayout/Fallback.d.ts +3 -0
  23. package/dist/types/src/components/DeckLayout/Fallback.d.ts.map +1 -0
  24. package/dist/types/src/components/DeckLayout/Fullscreen.d.ts +5 -0
  25. package/dist/types/src/components/DeckLayout/Fullscreen.d.ts.map +1 -0
  26. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts +14 -0
  27. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts.map +1 -0
  28. package/dist/types/src/components/DeckLayout/Plank.d.ts +14 -0
  29. package/dist/types/src/components/DeckLayout/Plank.d.ts.map +1 -0
  30. package/dist/types/src/components/DeckLayout/PlankError.d.ts +14 -0
  31. package/dist/types/src/components/DeckLayout/PlankError.d.ts.map +1 -0
  32. package/dist/types/src/components/DeckLayout/PlankLoading.d.ts +3 -0
  33. package/dist/types/src/components/DeckLayout/PlankLoading.d.ts.map +1 -0
  34. package/dist/types/src/components/DeckLayout/Sidebar.d.ts +8 -0
  35. package/dist/types/src/components/DeckLayout/Sidebar.d.ts.map +1 -0
  36. package/dist/types/src/components/DeckLayout/Toast.d.ts +5 -0
  37. package/dist/types/src/components/DeckLayout/Toast.d.ts.map +1 -0
  38. package/dist/types/src/components/DeckLayout/constants.d.ts +3 -0
  39. package/dist/types/src/components/DeckLayout/constants.d.ts.map +1 -0
  40. package/dist/types/src/components/DeckLayout/index.d.ts +3 -0
  41. package/dist/types/src/components/DeckLayout/index.d.ts.map +1 -0
  42. package/dist/types/src/components/LayoutContext.d.ts +5 -0
  43. package/dist/types/src/components/LayoutContext.d.ts.map +1 -0
  44. package/dist/types/src/components/LayoutSettings.d.ts +6 -0
  45. package/dist/types/src/components/LayoutSettings.d.ts.map +1 -0
  46. package/dist/types/src/components/index.d.ts +5 -0
  47. package/dist/types/src/components/index.d.ts.map +1 -0
  48. package/dist/types/src/hooks/index.d.ts +3 -0
  49. package/dist/types/src/hooks/index.d.ts.map +1 -0
  50. package/dist/types/src/hooks/useNode.d.ts +11 -0
  51. package/dist/types/src/hooks/useNode.d.ts.map +1 -0
  52. package/dist/types/src/hooks/useNodeActionExpander.d.ts +3 -0
  53. package/dist/types/src/hooks/useNodeActionExpander.d.ts.map +1 -0
  54. package/dist/types/src/index.d.ts +4 -0
  55. package/dist/types/src/index.d.ts.map +1 -0
  56. package/dist/types/src/layout.d.ts +25 -0
  57. package/dist/types/src/layout.d.ts.map +1 -0
  58. package/dist/types/src/layout.test.d.ts +2 -0
  59. package/dist/types/src/layout.test.d.ts.map +1 -0
  60. package/dist/types/src/meta.d.ts +7 -0
  61. package/dist/types/src/meta.d.ts.map +1 -0
  62. package/dist/types/src/translations.d.ts +41 -0
  63. package/dist/types/src/translations.d.ts.map +1 -0
  64. package/dist/types/src/types.d.ts +16 -0
  65. package/dist/types/src/types.d.ts.map +1 -0
  66. package/dist/types/src/util/check-app-scheme.d.ts +2 -0
  67. package/dist/types/src/util/check-app-scheme.d.ts.map +1 -0
  68. package/dist/types/src/util/index.d.ts +4 -0
  69. package/dist/types/src/util/index.d.ts.map +1 -0
  70. package/dist/types/src/util/layout-parts.d.ts +7 -0
  71. package/dist/types/src/util/layout-parts.d.ts.map +1 -0
  72. package/dist/types/src/util/overscroll.d.ts +7 -0
  73. package/dist/types/src/util/overscroll.d.ts.map +1 -0
  74. package/package.json +76 -0
  75. package/src/DeckPlugin.tsx +629 -0
  76. package/src/components/DeckContext.ts +14 -0
  77. package/src/components/DeckLayout/ActiveNode.tsx +24 -0
  78. package/src/components/DeckLayout/ComplementarySidebar.tsx +58 -0
  79. package/src/components/DeckLayout/ContentEmpty.tsx +21 -0
  80. package/src/components/DeckLayout/DeckLayout.tsx +270 -0
  81. package/src/components/DeckLayout/Fallback.tsx +28 -0
  82. package/src/components/DeckLayout/Fullscreen.tsx +32 -0
  83. package/src/components/DeckLayout/NodePlankHeading.tsx +160 -0
  84. package/src/components/DeckLayout/Plank.tsx +142 -0
  85. package/src/components/DeckLayout/PlankError.tsx +64 -0
  86. package/src/components/DeckLayout/PlankLoading.tsx +15 -0
  87. package/src/components/DeckLayout/Sidebar.tsx +43 -0
  88. package/src/components/DeckLayout/Toast.tsx +48 -0
  89. package/src/components/DeckLayout/constants.ts +6 -0
  90. package/src/components/DeckLayout/index.ts +6 -0
  91. package/src/components/LayoutContext.ts +12 -0
  92. package/src/components/LayoutSettings.tsx +86 -0
  93. package/src/components/index.ts +8 -0
  94. package/src/hooks/index.ts +6 -0
  95. package/src/hooks/useNode.ts +40 -0
  96. package/src/hooks/useNodeActionExpander.ts +24 -0
  97. package/src/index.ts +9 -0
  98. package/src/layout.test.ts +380 -0
  99. package/src/layout.ts +245 -0
  100. package/src/meta.ts +10 -0
  101. package/src/translations.ts +47 -0
  102. package/src/types.ts +38 -0
  103. package/src/util/check-app-scheme.ts +21 -0
  104. package/src/util/index.ts +7 -0
  105. package/src/util/layout-parts.ts +12 -0
  106. package/src/util/overscroll.ts +97 -0
@@ -0,0 +1,58 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { type LayoutParts, SLUG_PATH_SEPARATOR, Surface } from '@dxos/app-framework';
8
+ import { useGraph } from '@dxos/plugin-graph';
9
+ import { Main } from '@dxos/react-ui';
10
+ import { createAttendableAttributes } from '@dxos/react-ui-attention';
11
+ import { deckGrid } from '@dxos/react-ui-deck';
12
+ import { mx } from '@dxos/react-ui-theme';
13
+
14
+ import { NodePlankHeading } from './NodePlankHeading';
15
+ import { PlankContentError } from './PlankError';
16
+ import { PlankLoading } from './PlankLoading';
17
+ import { useNode, useNodeActionExpander } from '../../hooks';
18
+ import { useLayout } from '../LayoutContext';
19
+
20
+ export type ComplementarySidebarProps = {
21
+ id?: string;
22
+ layoutParts: LayoutParts;
23
+ flatDeck?: boolean;
24
+ };
25
+
26
+ export const ComplementarySidebar = ({ id, layoutParts, flatDeck }: ComplementarySidebarProps) => {
27
+ const { popoverAnchorId } = useLayout();
28
+ const { graph } = useGraph();
29
+ const node = useNode(graph, id);
30
+ // const complementaryAvailable = useMemo(() => id === NAV_ID || !!node, [id, node]);
31
+ const complementaryAttrs = createAttendableAttributes(id?.split(SLUG_PATH_SEPARATOR)[0] ?? 'never');
32
+
33
+ useNodeActionExpander(node);
34
+
35
+ return (
36
+ <Main.ComplementarySidebar {...complementaryAttrs}>
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
+ />
47
+ <Surface
48
+ role='article'
49
+ data={{ subject: node.data, part: 'complementary', popoverAnchorId }}
50
+ limit={1}
51
+ fallback={PlankContentError}
52
+ placeholder={<PlankLoading />}
53
+ />
54
+ </div>
55
+ ) : null}
56
+ </Main.ComplementarySidebar>
57
+ );
58
+ };
@@ -0,0 +1,21 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { Surface } from '@dxos/app-framework';
8
+
9
+ export const ContentEmpty = () => {
10
+ return (
11
+ <div
12
+ role='none'
13
+ className='min-bs-screen is-dvw sm:is-full flex items-center justify-center p-8'
14
+ data-testid='layoutPlugin.firstRunMessage'
15
+ >
16
+ <div role='none' className='grid place-items-center grid-rows-[min-content_min-content]'>
17
+ <Surface role='keyshortcuts' />
18
+ </div>
19
+ </div>
20
+ );
21
+ };
@@ -0,0 +1,270 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { Sidebar as MenuIcon } from '@phosphor-icons/react';
6
+ import React, { useMemo } from 'react';
7
+
8
+ import {
9
+ SLUG_PATH_SEPARATOR,
10
+ type Attention,
11
+ type LayoutParts,
12
+ Surface,
13
+ type Toast as ToastSchema,
14
+ firstIdInPart,
15
+ usePlugin,
16
+ } from '@dxos/app-framework';
17
+ import { Button, Dialog, Main, Popover, useTranslation } from '@dxos/react-ui';
18
+ import { Deck } from '@dxos/react-ui-deck';
19
+ import { getSize, mx } from '@dxos/react-ui-theme';
20
+
21
+ import { ActiveNode } from './ActiveNode';
22
+ import { ComplementarySidebar } from './ComplementarySidebar';
23
+ import { ContentEmpty } from './ContentEmpty';
24
+ import { Fullscreen } from './Fullscreen';
25
+ import { Plank } from './Plank';
26
+ import { Sidebar } from './Sidebar';
27
+ import { Toast } from './Toast';
28
+ import { DECK_PLUGIN } from '../../meta';
29
+ import { type Overscroll } from '../../types';
30
+ import { calculateOverscroll } from '../../util';
31
+ import { useDeckContext } from '../DeckContext';
32
+ import { useLayout } from '../LayoutContext';
33
+
34
+ export type DeckLayoutProps = {
35
+ showHintsFooter: boolean;
36
+ overscroll: Overscroll;
37
+ flatDeck?: boolean;
38
+ toasts: ToastSchema[];
39
+ onDismissToast: (id: string) => void;
40
+ layoutParts: LayoutParts;
41
+ attention: Attention;
42
+ // TODO(Zan): Deprecate slots.
43
+ slots?: {
44
+ wallpaper?: { classNames?: string };
45
+ deck?: { classNames?: string };
46
+ plank?: { classNames?: string };
47
+ };
48
+ };
49
+
50
+ export const DeckLayout = ({
51
+ showHintsFooter,
52
+ toasts,
53
+ onDismissToast,
54
+ flatDeck,
55
+ attention,
56
+ layoutParts,
57
+ slots,
58
+ overscroll,
59
+ }: DeckLayoutProps) => {
60
+ const context = useLayout();
61
+ const {
62
+ layoutMode,
63
+ sidebarOpen,
64
+ complementarySidebarOpen,
65
+ dialogOpen,
66
+ dialogContent,
67
+ dialogBlockAlign,
68
+ popoverOpen,
69
+ popoverContent,
70
+ popoverAnchorId,
71
+ } = context;
72
+ const { t } = useTranslation(DECK_PLUGIN);
73
+ const { plankSizing } = useDeckContext();
74
+
75
+ const fullScreenSlug = useMemo(() => firstIdInPart(layoutParts, 'fullScreen'), [layoutParts]);
76
+
77
+ const complementarySlug = useMemo(() => {
78
+ const entry = layoutParts.complementary?.at(0);
79
+ if (entry) {
80
+ return entry.path ? `${entry.id}${SLUG_PATH_SEPARATOR}${entry.path}` : entry.id;
81
+ }
82
+ }, [layoutParts]);
83
+
84
+ const searchEnabled = !!usePlugin('dxos.org/plugin/search');
85
+ const activeId = useMemo(() => Array.from(attention.attended ?? [])[0], [attention.attended]);
86
+
87
+ const overscrollAmount = calculateOverscroll(
88
+ layoutMode,
89
+ sidebarOpen,
90
+ complementarySidebarOpen,
91
+ layoutParts,
92
+ plankSizing,
93
+ overscroll,
94
+ );
95
+
96
+ if (layoutMode === 'fullscreen') {
97
+ return <Fullscreen id={fullScreenSlug} />;
98
+ }
99
+
100
+ return (
101
+ <Popover.Root
102
+ modal
103
+ open={!!(popoverAnchorId && popoverOpen)}
104
+ onOpenChange={(nextOpen) => {
105
+ if (nextOpen && popoverAnchorId) {
106
+ context.popoverOpen = true;
107
+ } else {
108
+ context.popoverOpen = false;
109
+ context.popoverAnchorId = undefined;
110
+ }
111
+ }}
112
+ >
113
+ <ActiveNode id={activeId} />
114
+
115
+ <Main.Root
116
+ navigationSidebarOpen={context.sidebarOpen}
117
+ onNavigationSidebarOpenChange={(next) => (context.sidebarOpen = next)}
118
+ {...(complementarySidebarOpen !== null && {
119
+ complementarySidebarOpen: /* complementaryAvailable && */ context.complementarySidebarOpen as boolean,
120
+ onComplementarySidebarOpenChange: (next) => (context.complementarySidebarOpen = next),
121
+ })}
122
+ >
123
+ {/* Notch */}
124
+ <Main.Notch classNames='z-[21]'>
125
+ <Surface role='notch-start' />
126
+ <Button
127
+ // disabled={!sidebarAvailable}
128
+ onClick={() => (context.sidebarOpen = !context.sidebarOpen)}
129
+ variant='ghost'
130
+ classNames='p-1'
131
+ >
132
+ <span className='sr-only'>{t('open navigation sidebar label')}</span>
133
+ <MenuIcon weight='light' className={getSize(5)} />
134
+ </Button>
135
+ <Button
136
+ // disabled={!complementaryAvailable}
137
+ onClick={() => (context.complementarySidebarOpen = !context.complementarySidebarOpen)}
138
+ variant='ghost'
139
+ classNames='p-1'
140
+ >
141
+ <span className='sr-only'>{t('open complementary sidebar label')}</span>
142
+ <MenuIcon mirrored weight='light' className={getSize(5)} />
143
+ </Button>
144
+ <Surface role='notch-end' />
145
+ </Main.Notch>
146
+
147
+ {/* Sidebars */}
148
+ <Sidebar attention={attention} layoutParts={layoutParts} />
149
+
150
+ <ComplementarySidebar id={complementarySlug} layoutParts={layoutParts} flatDeck={flatDeck} />
151
+
152
+ {/* Dialog overlay to dismiss dialogs. */}
153
+ <Main.Overlay />
154
+
155
+ {/* Main content surface. */}
156
+ {layoutMode === 'deck' && layoutParts.main && layoutParts.main.length > 0 && (
157
+ <Main.Content bounce classNames={['grid', 'block-end-[--statusbar-size]']}>
158
+ <div role='none' className='relative'>
159
+ <Deck.Root
160
+ classNames={mx(
161
+ 'absolute inset-0',
162
+ !flatDeck && 'surface-deck',
163
+ slots?.wallpaper?.classNames,
164
+ slots?.deck?.classNames,
165
+ 'transition-[padding] duration-200 ease-in-out',
166
+ )}
167
+ style={{ ...overscrollAmount }}
168
+ >
169
+ {layoutParts.main.map((layoutEntry) => {
170
+ return (
171
+ <Plank
172
+ key={layoutEntry.id}
173
+ entry={layoutEntry}
174
+ layoutParts={layoutParts}
175
+ part='main'
176
+ resizeable
177
+ flatDeck={flatDeck}
178
+ searchEnabled={searchEnabled}
179
+ />
180
+ );
181
+ })}
182
+ </Deck.Root>
183
+ </div>
184
+ </Main.Content>
185
+ )}
186
+
187
+ {/* Solo main content surface. */}
188
+ {layoutMode === 'solo' && layoutParts.solo && layoutParts.solo.length > 0 && (
189
+ <Main.Content bounce classNames={['grid', 'block-end-[--statusbar-size]']}>
190
+ <Deck.Root
191
+ classNames={[!flatDeck && 'surface-deck', slots?.wallpaper?.classNames, slots?.deck?.classNames]}
192
+ solo={true}
193
+ >
194
+ {layoutParts.solo.map((layoutEntry) => {
195
+ return (
196
+ <Plank
197
+ key={layoutEntry.id}
198
+ entry={layoutEntry}
199
+ layoutParts={layoutParts}
200
+ part='solo'
201
+ flatDeck={flatDeck}
202
+ classNames={slots?.plank?.classNames}
203
+ />
204
+ );
205
+ })}
206
+ </Deck.Root>
207
+ </Main.Content>
208
+ )}
209
+
210
+ {((layoutMode === 'solo' && (!layoutParts.solo || layoutParts.solo.length === 0)) ||
211
+ (layoutMode === 'deck' && (!layoutParts.main || layoutParts.main.length === 0))) && (
212
+ <Main.Content>
213
+ <ContentEmpty />
214
+ </Main.Content>
215
+ )}
216
+
217
+ {/* Note: This is not Main.Content */}
218
+ <Main.Content role='none' classNames={['fixed inset-inline-0 block-end-0 z-[2]']}>
219
+ <Surface role='status-bar' limit={1} />
220
+ </Main.Content>
221
+
222
+ {/* Help hints. */}
223
+ {/* TODO(burdon): Make surface roles/names fully-qualified. */}
224
+ {showHintsFooter && (
225
+ <div className='fixed bottom-0 left-0 right-0 h-[32px] z-[1] flex justify-center'>
226
+ <Surface role='hints' limit={1} />
227
+ </div>
228
+ )}
229
+
230
+ {/* Global popovers. */}
231
+ <Popover.Portal>
232
+ <Popover.Content
233
+ classNames='z-[60]'
234
+ onEscapeKeyDown={() => {
235
+ context.popoverOpen = false;
236
+ context.popoverAnchorId = undefined;
237
+ }}
238
+ >
239
+ <Popover.Viewport>
240
+ <Surface role='popover' data={popoverContent} />
241
+ </Popover.Viewport>
242
+ <Popover.Arrow />
243
+ </Popover.Content>
244
+ </Popover.Portal>
245
+
246
+ {/* Global dialog. */}
247
+ <Dialog.Root open={dialogOpen} onOpenChange={(nextOpen) => (context.dialogOpen = nextOpen)}>
248
+ <Dialog.Overlay blockAlign={dialogBlockAlign}>
249
+ <Surface role='dialog' data={dialogContent} />
250
+ </Dialog.Overlay>
251
+ </Dialog.Root>
252
+
253
+ {/* Global toasts. */}
254
+ {toasts?.map((toast) => (
255
+ <Toast
256
+ {...toast}
257
+ key={toast.id}
258
+ onOpenChange={(open) => {
259
+ if (!open) {
260
+ onDismissToast(toast.id);
261
+ }
262
+
263
+ return open;
264
+ }}
265
+ />
266
+ ))}
267
+ </Main.Root>
268
+ </Popover.Root>
269
+ );
270
+ };
@@ -0,0 +1,28 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { useTranslation } from '@dxos/react-ui';
8
+ import { errorText, mx } from '@dxos/react-ui-theme';
9
+
10
+ import { DECK_PLUGIN } from '../../meta';
11
+
12
+ export const Fallback = () => {
13
+ const { t } = useTranslation(DECK_PLUGIN);
14
+
15
+ return (
16
+ <div role='none' className='min-bs-screen is-full flex items-center justify-center p-8'>
17
+ <p
18
+ role='alert'
19
+ className={mx(
20
+ errorText,
21
+ 'border border-error-400/50 rounded-lg flex items-center justify-center p-8 font-normal text-lg',
22
+ )}
23
+ >
24
+ {t('plugin error message')}
25
+ </p>
26
+ </div>
27
+ );
28
+ };
@@ -0,0 +1,32 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { Surface } from '@dxos/app-framework';
8
+ import { useGraph } from '@dxos/plugin-graph';
9
+ import { fixedInsetFlexLayout } from '@dxos/react-ui-theme';
10
+
11
+ import { Fallback } from './Fallback';
12
+ import { SURFACE_PREFIX } from './constants';
13
+ import { useNode } from '../../hooks';
14
+
15
+ export const Fullscreen = ({ id }: { id?: string }) => {
16
+ const { graph } = useGraph();
17
+ const fullScreenNode = useNode(graph, id);
18
+
19
+ return (
20
+ <div role='none' className={fixedInsetFlexLayout}>
21
+ <Surface
22
+ role='main'
23
+ limit={1}
24
+ fallback={Fallback}
25
+ data={{
26
+ active: fullScreenNode?.data,
27
+ component: id?.startsWith(SURFACE_PREFIX) ? id.slice(SURFACE_PREFIX.length) : undefined,
28
+ }}
29
+ />
30
+ </div>
31
+ );
32
+ };
@@ -0,0 +1,160 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Placeholder } from '@phosphor-icons/react';
6
+ import React, { Fragment, useEffect } from 'react';
7
+
8
+ import {
9
+ LayoutAction,
10
+ NavigationAction,
11
+ SLUG_COLLECTION_INDICATOR,
12
+ SLUG_PATH_SEPARATOR,
13
+ Surface,
14
+ useIntentDispatcher,
15
+ indexInPart,
16
+ partLength,
17
+ type LayoutParts,
18
+ type LayoutPart,
19
+ type LayoutEntry,
20
+ } from '@dxos/app-framework';
21
+ import { type Node, useGraph } from '@dxos/plugin-graph';
22
+ import { Popover, toLocalizedString, useMediaQuery, useTranslation } from '@dxos/react-ui';
23
+ import { PlankHeading, plankHeadingIconProps } from '@dxos/react-ui-deck';
24
+ import { TextTooltip } from '@dxos/react-ui-text-tooltip';
25
+
26
+ import { DECK_PLUGIN } from '../../meta';
27
+
28
+ export const NodePlankHeading = ({
29
+ node,
30
+ id,
31
+ layoutParts,
32
+ layoutPart,
33
+ // TODO(wittjosiah): Unused?
34
+ layoutEntry,
35
+ popoverAnchorId,
36
+ pending,
37
+ flatDeck,
38
+ }: {
39
+ node?: Node;
40
+ id?: string;
41
+ layoutParts?: LayoutParts;
42
+ layoutPart?: LayoutPart;
43
+ layoutEntry?: LayoutEntry;
44
+ popoverAnchorId?: string;
45
+ pending?: boolean;
46
+ flatDeck?: boolean;
47
+ }) => {
48
+ const { t } = useTranslation(DECK_PLUGIN);
49
+ const { graph } = useGraph();
50
+ const Icon = node?.properties?.icon ?? Placeholder;
51
+ const label = pending
52
+ ? t('pending heading')
53
+ : toLocalizedString(node?.properties?.label ?? ['plank heading fallback label', { ns: DECK_PLUGIN }], t);
54
+ const dispatch = useIntentDispatcher();
55
+ const ActionRoot = node && popoverAnchorId === `dxos.org/ui/${DECK_PLUGIN}/${node.id}` ? Popover.Anchor : Fragment;
56
+ const [isNotMobile] = useMediaQuery('md');
57
+
58
+ useEffect(() => {
59
+ const frame = requestAnimationFrame(() => {
60
+ // Load actions for the node.
61
+ node && graph.actions(node);
62
+ });
63
+
64
+ return () => cancelAnimationFrame(frame);
65
+ }, [node]);
66
+
67
+ // NOTE(Zan): Node ids may now contain a path like `${space}:${id}~comments`
68
+ const attendableId = id?.split(SLUG_PATH_SEPARATOR).at(0);
69
+
70
+ const layoutCoordinate = layoutPart !== undefined && id !== undefined ? { part: layoutPart, entryId: id } : undefined;
71
+ const index = indexInPart(layoutParts, layoutCoordinate);
72
+ const length = partLength(layoutParts, layoutPart);
73
+
74
+ const canIncrementStart =
75
+ layoutPart === 'main' && index !== undefined && index > 0 && length !== undefined && length > 1;
76
+ const canIncrementEnd = layoutPart === 'main' && index !== undefined && index < length - 1 && length !== undefined;
77
+
78
+ return (
79
+ <PlankHeading.Root
80
+ {...((layoutPart !== 'main' || !flatDeck) && { classNames: 'pie-1 border-b separator-separator' })}
81
+ >
82
+ <ActionRoot>
83
+ {node ? (
84
+ <PlankHeading.ActionsMenu
85
+ Icon={Icon}
86
+ attendableId={attendableId}
87
+ triggerLabel={t('actions menu label')}
88
+ actions={graph.actions(node)}
89
+ onAction={(action) =>
90
+ typeof action.data === 'function' && action.data?.({ node: action as Node, caller: DECK_PLUGIN })
91
+ }
92
+ >
93
+ <Surface role='menu-footer' data={{ object: node.data }} />
94
+ </PlankHeading.ActionsMenu>
95
+ ) : (
96
+ <PlankHeading.Button>
97
+ <span className='sr-only'>{label}</span>
98
+ <Icon {...plankHeadingIconProps} />
99
+ </PlankHeading.Button>
100
+ )}
101
+ </ActionRoot>
102
+ <TextTooltip text={label} onlyWhenTruncating>
103
+ <PlankHeading.Label attendableId={node?.id} {...(pending && { classNames: 'fg-description' })}>
104
+ {label}
105
+ </PlankHeading.Label>
106
+ </TextTooltip>
107
+ {node && layoutPart !== 'complementary' && (
108
+ // TODO(Zan): What are we doing with layout coordinate here?
109
+ <Surface role='navbar-end' direction='inline-reverse' data={{ object: node.data }} />
110
+ )}
111
+ {/* NOTE(thure): Pinning & unpinning are temporarily disabled */}
112
+ <PlankHeading.Controls
113
+ capabilities={{
114
+ solo: (layoutPart === 'solo' || layoutPart === 'main') && isNotMobile,
115
+ incrementStart: canIncrementStart,
116
+ incrementEnd: canIncrementEnd,
117
+ }}
118
+ isSolo={layoutPart === 'solo'}
119
+ onClick={(eventType) => {
120
+ if (!layoutPart) {
121
+ return;
122
+ }
123
+
124
+ if (eventType === 'solo') {
125
+ return dispatch([
126
+ {
127
+ action: NavigationAction.ADJUST,
128
+ data: { type: eventType, layoutCoordinate: { part: 'main', entryId: id } },
129
+ },
130
+ ]);
131
+ }
132
+
133
+ // TODO(Zan): Update this to use the new layout actions.
134
+ return dispatch(
135
+ eventType === 'close'
136
+ ? layoutPart === 'complementary'
137
+ ? {
138
+ action: LayoutAction.SET_LAYOUT,
139
+ data: {
140
+ element: 'complementary',
141
+ state: false,
142
+ },
143
+ }
144
+ : {
145
+ action: NavigationAction.CLOSE,
146
+ data: {
147
+ activeParts: {
148
+ complementary: [`${id}${SLUG_PATH_SEPARATOR}comments${SLUG_COLLECTION_INDICATOR}`],
149
+ [layoutPart]: [id],
150
+ },
151
+ },
152
+ }
153
+ : { action: NavigationAction.ADJUST, data: { type: eventType, layoutCoordinate } },
154
+ );
155
+ }}
156
+ close={layoutCoordinate?.part === 'complementary' ? 'minify-end' : true}
157
+ />
158
+ </PlankHeading.Root>
159
+ );
160
+ };