@dxos/plugin-deck 0.6.14-main.f49f251 → 0.6.14-staging.3e2eaca

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 (40) hide show
  1. package/dist/lib/browser/chunk-NIRHDTX4.mjs +17 -0
  2. package/dist/lib/browser/chunk-NIRHDTX4.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +387 -379
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/types.mjs +11 -0
  7. package/dist/lib/browser/types.mjs.map +7 -0
  8. package/dist/types/src/DeckPlugin.d.ts.map +1 -1
  9. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts +4 -3
  10. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts.map +1 -1
  11. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts +3 -8
  12. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
  13. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts +3 -4
  14. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts.map +1 -1
  15. package/dist/types/src/components/DeckLayout/Plank.d.ts +4 -4
  16. package/dist/types/src/components/DeckLayout/Plank.d.ts.map +1 -1
  17. package/dist/types/src/components/DeckLayout/PlankControls.d.ts +19 -0
  18. package/dist/types/src/components/DeckLayout/PlankControls.d.ts.map +1 -0
  19. package/dist/types/src/components/DeckLayout/PlankError.d.ts +1 -2
  20. package/dist/types/src/components/DeckLayout/PlankError.d.ts.map +1 -1
  21. package/dist/types/src/translations.d.ts +7 -4
  22. package/dist/types/src/translations.d.ts.map +1 -1
  23. package/dist/types/src/types.d.ts +13 -1
  24. package/dist/types/src/types.d.ts.map +1 -1
  25. package/dist/types/src/util/overscroll.d.ts +1 -2
  26. package/dist/types/src/util/overscroll.d.ts.map +1 -1
  27. package/package.json +34 -27
  28. package/src/DeckPlugin.tsx +18 -32
  29. package/src/components/DeckLayout/ComplementarySidebar.tsx +27 -45
  30. package/src/components/DeckLayout/DeckLayout.tsx +141 -137
  31. package/src/components/DeckLayout/NodePlankHeading.tsx +30 -17
  32. package/src/components/DeckLayout/Plank.tsx +62 -96
  33. package/src/components/DeckLayout/PlankControls.tsx +133 -0
  34. package/src/components/DeckLayout/PlankError.tsx +6 -8
  35. package/src/components/DeckLayout/PlankLoading.tsx +1 -1
  36. package/src/components/DeckLayout/Toast.tsx +1 -1
  37. package/src/components/LayoutSettings.tsx +13 -13
  38. package/src/translations.ts +11 -9
  39. package/src/types.ts +15 -0
  40. package/src/util/overscroll.ts +9 -30
@@ -29,6 +29,7 @@ import {
29
29
  openIds,
30
30
  type LayoutMode,
31
31
  type IntentData,
32
+ filterPlugins,
32
33
  } from '@dxos/app-framework';
33
34
  import { type UnsubscribeCallback } from '@dxos/async';
34
35
  import { create, getTypename, isReactiveObject } from '@dxos/echo-schema';
@@ -36,21 +37,12 @@ import { scheduledEffect } from '@dxos/echo-signals/core';
36
37
  import { LocalStorageStore } from '@dxos/local-storage';
37
38
  import { log } from '@dxos/log';
38
39
  import { parseAttentionPlugin, type AttentionPluginProvides } from '@dxos/plugin-attention';
39
- import { parseClientPlugin, type ClientPluginProvides } from '@dxos/plugin-client';
40
40
  import { createExtension, type Node } from '@dxos/plugin-graph';
41
41
  import { ObservabilityAction } from '@dxos/plugin-observability/meta';
42
42
  import { fullyQualifiedId } from '@dxos/react-client/echo';
43
- import { translations as deckTranslations } from '@dxos/react-ui-deck';
43
+ import { translations as stackTranslations } from '@dxos/react-ui-stack';
44
44
 
45
- import {
46
- DeckLayout,
47
- type DeckLayoutProps,
48
- LayoutContext,
49
- LayoutSettings,
50
- NAV_ID,
51
- DeckContext,
52
- type DeckContextType,
53
- } from './components';
45
+ import { DeckLayout, LayoutContext, LayoutSettings, NAV_ID, DeckContext, type DeckContextType } from './components';
54
46
  import {
55
47
  closeEntry,
56
48
  incrementPlank,
@@ -62,7 +54,14 @@ import {
62
54
  } from './layout';
63
55
  import meta, { DECK_PLUGIN } from './meta';
64
56
  import translations from './translations';
65
- import { type NewPlankPositioning, type DeckPluginProvides, type DeckSettingsProps, type Overscroll } from './types';
57
+ import {
58
+ type NewPlankPositioning,
59
+ type DeckPluginProvides,
60
+ type DeckSettingsProps,
61
+ type Overscroll,
62
+ type Panel,
63
+ parsePanelPlugin,
64
+ } from './types';
66
65
  import { checkAppScheme, getEffectivePart } from './util';
67
66
 
68
67
  const isSocket = !!(globalThis as any).__args;
@@ -70,14 +69,6 @@ const isSocket = !!(globalThis as any).__args;
70
69
  // TODO(mjamesderocher): Can we get this directly from Socket?
71
70
  const appScheme = 'composer://';
72
71
 
73
- // TODO(burdon): Evolve into customizable prefs.
74
- const customSlots: DeckLayoutProps['slots'] = {
75
- wallpaper: {
76
- classNames:
77
- 'bg-cover bg-no-repeat dark:bg-[url(https://cdn.midjourney.com/3865ba61-f98a-4d94-b91a-1763ead01f4f/0_0.jpeg)]',
78
- },
79
- };
80
-
81
72
  // NOTE(Zan): When producing values with immer, we shouldn't auto-freeze them because
82
73
  // our signal implementation needs to add some hidden properties to the produced values.
83
74
  // TODO(Zan): Move this to a more global location if we use immer more broadly.
@@ -105,10 +96,10 @@ export const DeckPlugin = ({
105
96
  // TODO(burdon): GraphPlugin vs. IntentPluginProvides? (@wittjosiah).
106
97
  let intentPlugin: Plugin<IntentPluginProvides> | undefined;
107
98
  let attentionPlugin: Plugin<AttentionPluginProvides> | undefined;
108
- let clientPlugin: Plugin<ClientPluginProvides> | undefined;
109
99
  const unsubscriptionCallbacks = [] as (UnsubscribeCallback | undefined)[];
110
100
  let currentUndoId: string | undefined;
111
101
  let handleNavigation: () => Promise<void> | undefined;
102
+ const panels: Panel[] = [];
112
103
 
113
104
  const settings = new LocalStorageStore<DeckSettingsProps>('dxos.org/settings/layout', {
114
105
  showHints: false,
@@ -217,7 +208,6 @@ export const DeckPlugin = ({
217
208
  intentPlugin = resolvePlugin(plugins, parseIntentPlugin);
218
209
  graphPlugin = resolvePlugin(plugins, parseGraphPlugin);
219
210
  attentionPlugin = resolvePlugin(plugins, parseAttentionPlugin);
220
- clientPlugin = resolvePlugin(plugins, parseClientPlugin);
221
211
 
222
212
  layout
223
213
  .prop({ key: 'layoutMode', type: LocalStorageStore.enum<LayoutMode>() })
@@ -230,12 +220,8 @@ export const DeckPlugin = ({
230
220
  .prop({ key: 'active', type: LocalStorageStore.json<LayoutParts>() })
231
221
  .prop({ key: 'closed', type: LocalStorageStore.json<string[]>() });
232
222
 
233
- unsubscriptionCallbacks.push(
234
- clientPlugin?.provides.client.shell.onReset(() => {
235
- layout.expunge();
236
- location.expunge();
237
- deck.expunge();
238
- }),
223
+ panels.push(
224
+ ...filterPlugins(plugins, parsePanelPlugin).flatMap((plugin) => plugin.provides.complementary.panels),
239
225
  );
240
226
 
241
227
  settings
@@ -261,12 +247,13 @@ export const DeckPlugin = ({
261
247
  return;
262
248
  }
263
249
 
250
+ const startingLayout = removePart(location.values.active, 'solo');
264
251
  const layoutFromUri = uriToSoloPart(pathname);
265
252
  if (!layoutFromUri) {
253
+ handleSetLocation(startingLayout);
266
254
  return;
267
255
  }
268
256
 
269
- const startingLayout = removePart(location.values.active, 'solo');
270
257
  handleSetLocation(mergeLayoutParts(layoutFromUri, startingLayout));
271
258
  layout.values.layoutMode = 'solo';
272
259
  };
@@ -296,7 +283,7 @@ export const DeckPlugin = ({
296
283
  settings: settings.values,
297
284
  layout: layout.values,
298
285
  location: location.values,
299
- translations: [...translations, ...deckTranslations],
286
+ translations: [...translations, ...stackTranslations],
300
287
  graph: {
301
288
  builder: () => {
302
289
  // TODO(burdon): Root menu isn't visible so nothing bound.
@@ -339,9 +326,8 @@ export const DeckPlugin = ({
339
326
  layoutParts={location.values.active}
340
327
  showHints={settings.values.showHints}
341
328
  overscroll={settings.values.overscroll}
342
- flatDeck={settings.values.flatDeck}
343
- slots={settings.values.customSlots ? customSlots : undefined}
344
329
  toasts={layout.values.toasts}
330
+ panels={panels}
345
331
  onDismissToast={(id) => {
346
332
  const index = layout.values.toasts.findIndex((toast) => toast.id === id);
347
333
  if (index !== -1) {
@@ -14,41 +14,26 @@ import {
14
14
  import { useGraph } from '@dxos/plugin-graph';
15
15
  import { Main } from '@dxos/react-ui';
16
16
  import { useAttended } from '@dxos/react-ui-attention';
17
- import { deckGrid } from '@dxos/react-ui-deck';
17
+ import { railGridHorizontal, StackContext } from '@dxos/react-ui-stack';
18
18
  import { mx } from '@dxos/react-ui-theme';
19
19
 
20
20
  import { NodePlankHeading } from './NodePlankHeading';
21
21
  import { PlankContentError } from './PlankError';
22
22
  import { PlankLoading } from './PlankLoading';
23
23
  import { useNode, useNodeActionExpander } from '../../hooks';
24
- import { DECK_PLUGIN } from '../../meta';
24
+ import { type Panel } from '../../types';
25
25
  import { useLayout } from '../LayoutContext';
26
26
 
27
27
  export type ComplementarySidebarProps = {
28
- panel?: string;
29
- flatDeck?: boolean;
28
+ panels: Panel[];
29
+ current?: string;
30
30
  };
31
31
 
32
- type Panel = { id: string; icon: string };
33
-
34
- // TODO(burdon): Should be provided by plugins.
35
- const panels: Panel[] = [
36
- { id: 'settings', icon: 'ph--gear--regular' },
37
- { id: 'comments', icon: 'ph--chat-text--regular' },
38
- { id: 'automation', icon: 'ph--atom--regular' },
39
- { id: 'debug', icon: 'ph--bug--regular' },
40
- ];
41
-
42
- const getPanel = (id?: string): Panel['id'] => {
43
- const panel = panels.find((p) => p.id === id) ?? panels[0];
44
- return panel.id;
45
- };
46
-
47
- export const ComplementarySidebar = ({ panel, flatDeck }: ComplementarySidebarProps) => {
32
+ export const ComplementarySidebar = ({ panels, current }: ComplementarySidebarProps) => {
48
33
  const { popoverAnchorId } = useLayout();
49
34
  const attended = useAttended();
50
- const part = getPanel(panel);
51
- const id = attended[0] ? `${attended[0]}${SLUG_PATH_SEPARATOR}${part}` : undefined;
35
+ const panel = (panels.find((p) => p.id === current) ?? panels[0])?.id;
36
+ const id = attended[0] ? `${attended[0]}${SLUG_PATH_SEPARATOR}${panel}` : undefined;
52
37
  const { graph } = useGraph();
53
38
  const node = useNode(graph, id);
54
39
  const dispatch = useIntentDispatcher();
@@ -56,19 +41,19 @@ export const ComplementarySidebar = ({ panel, flatDeck }: ComplementarySidebarPr
56
41
 
57
42
  const actions = useMemo(
58
43
  () =>
59
- panels.map(({ id, icon }) => ({
44
+ panels.map(({ id, label, icon }) => ({
60
45
  id: `complementary-${id}`,
61
46
  data: () => {
62
47
  void dispatch({ action: NavigationAction.OPEN, data: { activeParts: { complementary: id } } });
63
48
  },
64
49
  properties: {
65
- label: [`open ${id} label`, { ns: DECK_PLUGIN }],
50
+ label,
66
51
  icon,
67
52
  menuItemType: 'toggle',
68
- isChecked: part === id,
53
+ isChecked: panel === id,
69
54
  },
70
55
  })),
71
- [part],
56
+ [panel],
72
57
  );
73
58
 
74
59
  // TODO(wittjosiah): Ensure that id is always defined.
@@ -77,26 +62,23 @@ export const ComplementarySidebar = ({ panel, flatDeck }: ComplementarySidebarPr
77
62
  // TODO(burdon): Debug panel doesn't change when switching even though id has chagned.
78
63
  return (
79
64
  <Main.ComplementarySidebar>
80
- <div role='none' className={mx(deckGrid, 'grid-cols-1 bs-full')}>
81
- <NodePlankHeading
82
- coordinate={coordinate}
83
- node={node}
84
- popoverAnchorId={popoverAnchorId}
85
- flatDeck={flatDeck}
86
- actions={actions}
87
- />
88
- <div className='row-span-2 divide-y divide-separator overflow-x-hidden overflow-y-scroll'>
89
- {node && (
90
- <Surface
91
- key={id}
92
- role={`complementary--${part}`}
93
- data={{ id, subject: node.properties.object ?? node.properties.space, popoverAnchorId }}
94
- fallback={PlankContentError}
95
- placeholder={<PlankLoading />}
96
- />
97
- )}
65
+ <StackContext.Provider value={{ size: 'contain', orientation: 'horizontal', separators: true, rail: true }}>
66
+ <div role='none' className={mx(railGridHorizontal, 'grid-cols-1 bs-full divide-y divide-separator')}>
67
+ <NodePlankHeading coordinate={coordinate} node={node} popoverAnchorId={popoverAnchorId} actions={actions} />
68
+ <div className='divide-y divide-separator overflow-x-hidden overflow-y-scroll'>
69
+ {node && (
70
+ <Surface
71
+ key={id}
72
+ role={`complementary--${panel}`}
73
+ limit={1}
74
+ data={{ id, subject: node.properties.object ?? node.properties.space, popoverAnchorId }}
75
+ fallback={PlankContentError}
76
+ placeholder={<PlankLoading />}
77
+ />
78
+ )}
79
+ </div>
98
80
  </div>
99
- </div>
81
+ </StackContext.Provider>
100
82
  </Main.ComplementarySidebar>
101
83
  );
102
84
  };
@@ -4,16 +4,16 @@
4
4
 
5
5
  import { Sidebar as MenuIcon } from '@phosphor-icons/react';
6
6
  import { untracked } from '@preact/signals-core';
7
- import React, { useCallback, useEffect, useMemo, useRef, type UIEvent } from 'react';
7
+ import React, { useCallback, useEffect, useMemo, useRef, type UIEvent, Fragment } from 'react';
8
8
 
9
9
  import { type LayoutParts, Surface, type Toast as ToastSchema, firstIdInPart, usePlugin } from '@dxos/app-framework';
10
10
  import { type AttentionPluginProvides } from '@dxos/plugin-attention';
11
- import { Button, Dialog, Main, Popover, useOnTransition, useTranslation } from '@dxos/react-ui';
12
- import { Deck } from '@dxos/react-ui-deck';
11
+ import { Button, Dialog, Main, Popover, useOnTransition, useTranslation, type MainProps } from '@dxos/react-ui';
12
+ import { Stack, StackContext, DEFAULT_HORIZONTAL_SIZE } from '@dxos/react-ui-stack';
13
13
  import { getSize, mainPaddingTransitions } from '@dxos/react-ui-theme';
14
14
 
15
15
  import { ActiveNode } from './ActiveNode';
16
- import { ComplementarySidebar } from './ComplementarySidebar';
16
+ import { ComplementarySidebar, type ComplementarySidebarProps } from './ComplementarySidebar';
17
17
  import { ContentEmpty } from './ContentEmpty';
18
18
  import { Fullscreen } from './Fullscreen';
19
19
  import { Plank } from './Plank';
@@ -29,24 +29,15 @@ import { useLayout } from '../LayoutContext';
29
29
  export type DeckLayoutProps = {
30
30
  layoutParts: LayoutParts;
31
31
  toasts: ToastSchema[];
32
- flatDeck?: boolean;
33
32
  overscroll: Overscroll;
34
33
  showHints: boolean;
35
- slots?: {
36
- wallpaper?: { classNames?: string };
37
- };
38
34
  onDismissToast: (id: string) => void;
39
- };
35
+ } & Pick<ComplementarySidebarProps, 'panels'>;
36
+
37
+ const PlankSeparator = ({ index }: { index: number }) =>
38
+ index > 0 ? <span role='separator' className='row-span-2 bg-deck is-4' style={{ gridColumn: index * 2 }} /> : null;
40
39
 
41
- export const DeckLayout = ({
42
- layoutParts,
43
- toasts,
44
- flatDeck,
45
- overscroll,
46
- showHints,
47
- slots,
48
- onDismissToast,
49
- }: DeckLayoutProps) => {
40
+ export const DeckLayout = ({ layoutParts, toasts, overscroll, showHints, panels, onDismissToast }: DeckLayoutProps) => {
50
41
  const context = useLayout();
51
42
  const {
52
43
  layoutMode,
@@ -63,16 +54,17 @@ export const DeckLayout = ({
63
54
  const { plankSizing } = useDeckContext();
64
55
  // NOTE: Not `useAttended` so that the layout component is not re-rendered when the attended list changes.
65
56
  const attentionPlugin = usePlugin<AttentionPluginProvides>('dxos.org/plugin/attention');
66
- const searchPlugin = usePlugin('dxos.org/plugin/search');
67
57
  const fullScreenSlug = useMemo(() => firstIdInPart(layoutParts, 'fullScreen'), [layoutParts]);
68
58
 
69
59
  const scrollLeftRef = useRef<number | null>();
70
60
  const deckRef = useRef<HTMLDivElement>(null);
71
61
 
62
+ const isSoloModeLoaded = layoutMode === 'solo' && Boolean(layoutParts.solo?.[0]);
63
+
72
64
  // Ensure the first plank is attended when the deck is first rendered.
73
65
  useEffect(() => {
74
66
  const attended = untracked(() => attentionPlugin?.provides.attention.attended ?? []);
75
- const firstId = layoutMode === 'solo' ? firstIdInPart(layoutParts, 'solo') : firstIdInPart(layoutParts, 'main');
67
+ const firstId = isSoloModeLoaded ? firstIdInPart(layoutParts, 'solo') : firstIdInPart(layoutParts, 'main');
76
68
  if (attended.length === 0 && firstId) {
77
69
  // TODO(wittjosiah): Focusing the type button is a workaround.
78
70
  // If the plank is directly focused on first load the focus ring appears.
@@ -112,18 +104,14 @@ export const DeckLayout = ({
112
104
  [layoutMode],
113
105
  );
114
106
 
115
- const isEmpty = layoutParts.main?.length === 0 && layoutParts.solo?.length === 0;
107
+ const isEmpty = (layoutParts.main?.length ?? 0) === 0 && (layoutParts.solo?.length ?? 0) === 0;
116
108
 
117
109
  const padding = useMemo(() => {
118
110
  if (layoutMode === 'deck' && overscroll === 'centering') {
119
- return calculateOverscroll(layoutParts.main, plankSizing, sidebarOpen, complementarySidebarOpen);
111
+ return calculateOverscroll(layoutParts.main?.length ?? 0);
120
112
  }
121
113
  return {};
122
- }, [layoutMode, overscroll, layoutParts.main, plankSizing, sidebarOpen, complementarySidebarOpen]);
123
-
124
- if (layoutMode === 'fullscreen') {
125
- return <Fullscreen id={fullScreenSlug} />;
126
- }
114
+ }, [layoutMode, overscroll, layoutParts.main]);
127
115
 
128
116
  return (
129
117
  <Popover.Root
@@ -140,127 +128,143 @@ export const DeckLayout = ({
140
128
  >
141
129
  <ActiveNode />
142
130
 
143
- <Main.Root
144
- navigationSidebarOpen={context.sidebarOpen}
145
- onNavigationSidebarOpenChange={(next) => (context.sidebarOpen = next)}
146
- complementarySidebarOpen={context.complementarySidebarOpen}
147
- onComplementarySidebarOpenChange={(next) => (context.complementarySidebarOpen = next)}
148
- >
149
- {/* Notch */}
150
- <Main.Notch classNames='z-[21]'>
151
- <Surface role='notch-start' />
152
- <Button onClick={() => (context.sidebarOpen = !context.sidebarOpen)} variant='ghost' classNames='p-1'>
153
- <span className='sr-only'>{t('open navigation sidebar label')}</span>
154
- <MenuIcon weight='light' className={getSize(5)} />
155
- </Button>
156
- <Button
157
- onClick={() => (context.complementarySidebarOpen = !context.complementarySidebarOpen)}
158
- variant='ghost'
159
- classNames='p-1'
160
- >
161
- <span className='sr-only'>{t('open complementary sidebar label')}</span>
162
- <MenuIcon mirrored weight='light' className={getSize(5)} />
163
- </Button>
164
- <Surface role='notch-end' />
165
- </Main.Notch>
131
+ {layoutMode === 'fullscreen' && <Fullscreen id={fullScreenSlug} />}
166
132
 
167
- {/* Left sidebar. */}
168
- <Sidebar layoutParts={layoutParts} />
133
+ {layoutMode !== 'fullscreen' && (
134
+ <Main.Root
135
+ navigationSidebarOpen={context.sidebarOpen}
136
+ onNavigationSidebarOpenChange={(next) => (context.sidebarOpen = next)}
137
+ complementarySidebarOpen={context.complementarySidebarOpen}
138
+ onComplementarySidebarOpenChange={(next) => (context.complementarySidebarOpen = next)}
139
+ >
140
+ {/* Notch */}
141
+ <Main.Notch classNames='z-[21]'>
142
+ <Surface role='notch-start' />
143
+ <Button onClick={() => (context.sidebarOpen = !context.sidebarOpen)} variant='ghost' classNames='p-1'>
144
+ <span className='sr-only'>{t('open navigation sidebar label')}</span>
145
+ <MenuIcon weight='light' className={getSize(5)} />
146
+ </Button>
147
+ <Surface role='notch-end' />
148
+ </Main.Notch>
169
149
 
170
- {/* Right sidebar. */}
171
- <ComplementarySidebar panel={layoutParts.complementary?.[0].id} flatDeck={flatDeck} />
150
+ {/* Left sidebar. */}
151
+ <Sidebar layoutParts={layoutParts} />
172
152
 
173
- {/* Dialog overlay to dismiss dialogs. */}
174
- <Main.Overlay />
153
+ {/* Right sidebar. */}
154
+ <ComplementarySidebar panels={panels} current={layoutParts.complementary?.[0].id} />
175
155
 
176
- {/* No content. */}
177
- {isEmpty && (
178
- <Main.Content handlesFocus>
179
- <ContentEmpty />
180
- </Main.Content>
181
- )}
156
+ {/* Dialog overlay to dismiss dialogs. */}
157
+ <Main.Overlay />
182
158
 
183
- {/* Solo/deck mode. */}
184
- {!isEmpty && (
185
- <Main.Content bounce classNames='grid block-end-[--statusbar-size]' handlesFocus>
186
- <div role='none' className='relative'>
187
- <Deck.Root
188
- style={padding}
189
- classNames={[
190
- !flatDeck && 'bg-deck',
191
- mainPaddingTransitions,
192
- 'absolute inset-0',
193
- slots?.wallpaper?.classNames,
194
- ]}
195
- solo={layoutMode === 'solo'}
196
- onScroll={handleScroll}
197
- ref={deckRef}
159
+ {/* No content. */}
160
+ {isEmpty && (
161
+ <Main.Content handlesFocus>
162
+ <ContentEmpty />
163
+ </Main.Content>
164
+ )}
165
+
166
+ {/* Solo/deck mode. */}
167
+ {!isEmpty && (
168
+ <Main.Content
169
+ bounce
170
+ classNames='grid block-end-[--statusbar-size]'
171
+ handlesFocus
172
+ style={
173
+ {
174
+ '--dx-main-sidebarWidth': sidebarOpen ? 'var(--nav-sidebar-size)' : '0px',
175
+ '--dx-main-complementaryWidth': complementarySidebarOpen
176
+ ? 'var(--complementary-sidebar-size)'
177
+ : '0px',
178
+ '--dx-main-contentFirstWidth': `${plankSizing[layoutParts.main?.[0]?.id ?? 'never'] ?? DEFAULT_HORIZONTAL_SIZE}rem`,
179
+ '--dx-main-contentLastWidth': `${plankSizing[layoutParts.main?.[(layoutParts.main?.length ?? 1) - 1]?.id ?? 'never'] ?? DEFAULT_HORIZONTAL_SIZE}rem`,
180
+ } as MainProps['style']
181
+ }
182
+ >
183
+ <div
184
+ role='none'
185
+ className={!isSoloModeLoaded ? 'relative bg-deck overflow-hidden' : 'sr-only'}
186
+ {...(isSoloModeLoaded && { inert: '' })}
198
187
  >
199
- <Plank
200
- entry={layoutParts.solo?.[0] ?? { id: 'unknown-solo' }}
201
- layoutParts={layoutParts}
202
- part='solo'
203
- layoutMode={layoutMode}
204
- flatDeck={flatDeck}
205
- searchEnabled={!!searchPlugin}
206
- />
207
- {layoutParts.main?.map((layoutEntry) => (
208
- <Plank
209
- key={layoutEntry.id}
210
- entry={layoutEntry}
211
- layoutParts={layoutParts}
212
- part='main'
213
- layoutMode={layoutMode}
214
- flatDeck={flatDeck}
215
- searchEnabled={!!searchPlugin}
216
- />
217
- ))}
218
- </Deck.Root>
219
- </div>
220
- </Main.Content>
221
- )}
188
+ <Stack
189
+ orientation='horizontal'
190
+ size='contain'
191
+ classNames={['absolute inset-block-0 -inset-inline-px', mainPaddingTransitions]}
192
+ onScroll={handleScroll}
193
+ itemsCount={2 * (layoutParts.main?.length ?? 0) - 1}
194
+ style={padding}
195
+ ref={deckRef}
196
+ >
197
+ {layoutParts.main?.map((layoutEntry, index) => (
198
+ <Fragment key={layoutEntry.id}>
199
+ <PlankSeparator index={index} />
200
+ <Plank
201
+ entry={layoutEntry}
202
+ layoutParts={layoutParts}
203
+ part='main'
204
+ layoutMode={layoutMode}
205
+ order={index * 2 + 1}
206
+ last={index === layoutParts.main!.length - 1}
207
+ />
208
+ </Fragment>
209
+ ))}
210
+ </Stack>
211
+ </div>
212
+ <div
213
+ role='none'
214
+ className={isSoloModeLoaded ? 'relative bg-deck overflow-hidden' : 'sr-only'}
215
+ {...(!isSoloModeLoaded && { inert: '' })}
216
+ >
217
+ <StackContext.Provider
218
+ value={{ size: 'contain', orientation: 'horizontal', separators: true, rail: true }}
219
+ >
220
+ <Plank entry={layoutParts.solo?.[0]} layoutParts={layoutParts} part='solo' layoutMode={layoutMode} />
221
+ </StackContext.Provider>
222
+ </div>
223
+ </Main.Content>
224
+ )}
222
225
 
223
- {/* Footer status. */}
224
- <StatusBar showHints={showHints} />
226
+ {/* Footer status. */}
227
+ <StatusBar showHints={showHints} />
228
+ </Main.Root>
229
+ )}
225
230
 
226
- {/* Global popovers. */}
227
- <Popover.Portal>
228
- <Popover.Content
229
- classNames='z-[60]'
230
- onEscapeKeyDown={() => {
231
- context.popoverOpen = false;
232
- context.popoverAnchorId = undefined;
233
- }}
234
- >
235
- <Popover.Viewport>
236
- <Surface role='popover' data={popoverContent} />
237
- </Popover.Viewport>
238
- <Popover.Arrow />
239
- </Popover.Content>
240
- </Popover.Portal>
231
+ {/* Global popovers. */}
232
+ <Popover.Portal>
233
+ <Popover.Content
234
+ classNames='z-[60]'
235
+ onEscapeKeyDown={() => {
236
+ context.popoverOpen = false;
237
+ context.popoverAnchorId = undefined;
238
+ }}
239
+ >
240
+ <Popover.Viewport>
241
+ <Surface role='popover' data={popoverContent} />
242
+ </Popover.Viewport>
243
+ <Popover.Arrow />
244
+ </Popover.Content>
245
+ </Popover.Portal>
241
246
 
242
- {/* Global dialog. */}
243
- <Dialog.Root open={dialogOpen} onOpenChange={(nextOpen) => (context.dialogOpen = nextOpen)}>
244
- <Dialog.Overlay blockAlign={dialogBlockAlign}>
245
- <Surface role='dialog' data={dialogContent} />
246
- </Dialog.Overlay>
247
- </Dialog.Root>
247
+ {/* Global dialog. */}
248
+ <Dialog.Root open={dialogOpen} onOpenChange={(nextOpen) => (context.dialogOpen = nextOpen)}>
249
+ <Dialog.Overlay blockAlign={dialogBlockAlign}>
250
+ <Surface role='dialog' data={dialogContent} />
251
+ </Dialog.Overlay>
252
+ </Dialog.Root>
248
253
 
249
- {/* Global toasts. */}
250
- {toasts?.map((toast) => (
251
- <Toast
252
- {...toast}
253
- key={toast.id}
254
- onOpenChange={(open) => {
255
- if (!open) {
256
- onDismissToast(toast.id);
257
- }
254
+ {/* Global toasts. */}
255
+ {toasts?.map((toast) => (
256
+ <Toast
257
+ {...toast}
258
+ key={toast.id}
259
+ onOpenChange={(open) => {
260
+ if (!open) {
261
+ onDismissToast(toast.id);
262
+ }
258
263
 
259
- return open;
260
- }}
261
- />
262
- ))}
263
- </Main.Root>
264
+ return open;
265
+ }}
266
+ />
267
+ ))}
264
268
  </Popover.Root>
265
269
  );
266
270
  };