@dxos/plugin-deck 0.8.4-main.5ad4a44 → 0.8.4-main.66e292d

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 (108) hide show
  1. package/dist/lib/browser/{app-graph-builder-YYP67JHW.mjs → app-graph-builder-D74NTOMK.mjs} +8 -33
  2. package/dist/lib/browser/app-graph-builder-D74NTOMK.mjs.map +7 -0
  3. package/dist/lib/browser/{check-app-scheme-GCOL6YDT.mjs → check-app-scheme-HIEVFAAX.mjs} +2 -2
  4. package/dist/lib/browser/{chunk-HUWUYTOI.mjs → chunk-5KMJPIQC.mjs} +2 -2
  5. package/dist/lib/browser/{chunk-A4SRCSJX.mjs → chunk-F3VCCHVL.mjs} +5 -3
  6. package/dist/lib/browser/chunk-F3VCCHVL.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-YRDCQS7E.mjs → chunk-QKCGZ45E.mjs} +17 -16
  8. package/dist/lib/browser/chunk-QKCGZ45E.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-UXLU6CMW.mjs +16 -0
  10. package/dist/lib/browser/chunk-UXLU6CMW.mjs.map +7 -0
  11. package/dist/lib/browser/{chunk-JH4ZCG5I.mjs → chunk-VBYJ664A.mjs} +8 -5
  12. package/dist/lib/browser/chunk-VBYJ664A.mjs.map +7 -0
  13. package/dist/lib/browser/{chunk-3SV2V3AN.mjs → chunk-VUJ6UNIJ.mjs} +384 -326
  14. package/dist/lib/browser/chunk-VUJ6UNIJ.mjs.map +7 -0
  15. package/dist/lib/browser/index.mjs +7 -6
  16. package/dist/lib/browser/index.mjs.map +2 -2
  17. package/dist/lib/browser/{intent-resolver-2NFDKRFG.mjs → intent-resolver-UA4YQGAC.mjs} +6 -6
  18. package/dist/lib/browser/{intent-resolver-2NFDKRFG.mjs.map → intent-resolver-UA4YQGAC.mjs.map} +2 -2
  19. package/dist/lib/browser/meta.json +1 -1
  20. package/dist/lib/browser/{react-root-JTWYCDJT.mjs → react-root-JAMHKYWN.mjs} +9 -8
  21. package/dist/lib/browser/react-root-JAMHKYWN.mjs.map +7 -0
  22. package/dist/lib/browser/{react-surface-PZKJ73JS.mjs → react-surface-6LW337ZT.mjs} +7 -7
  23. package/dist/lib/browser/{settings-JK7UIZSB.mjs → settings-SDPTOCCM.mjs} +5 -4
  24. package/dist/lib/browser/{settings-JK7UIZSB.mjs.map → settings-SDPTOCCM.mjs.map} +3 -3
  25. package/dist/lib/browser/state-7IFAGZQO.mjs +12 -0
  26. package/dist/lib/browser/toolkit-L5CFXJCF.mjs +52 -0
  27. package/dist/lib/browser/toolkit-L5CFXJCF.mjs.map +7 -0
  28. package/dist/lib/browser/types/index.mjs +2 -2
  29. package/dist/lib/browser/{url-handler-MRYBE3HF.mjs → url-handler-QEYGYE2H.mjs} +4 -4
  30. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  31. package/dist/types/src/capabilities/react-root.d.ts.map +1 -1
  32. package/dist/types/src/capabilities/settings.d.ts.map +1 -1
  33. package/dist/types/src/capabilities/state.d.ts +3 -0
  34. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  35. package/dist/types/src/capabilities/toolkit.d.ts +21 -2
  36. package/dist/types/src/capabilities/toolkit.d.ts.map +1 -1
  37. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts +2 -3
  38. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
  39. package/dist/types/src/components/DeckLayout/DeckLayout.stories.d.ts +74 -0
  40. package/dist/types/src/components/DeckLayout/DeckLayout.stories.d.ts.map +1 -0
  41. package/dist/types/src/components/DeckLayout/DeckMain.d.ts +3 -0
  42. package/dist/types/src/components/DeckLayout/DeckMain.d.ts.map +1 -0
  43. package/dist/types/src/components/DeckLayout/Popover.d.ts.map +1 -1
  44. package/dist/types/src/components/DeckLayout/Toast.d.ts +5 -0
  45. package/dist/types/src/components/DeckLayout/Toast.d.ts.map +1 -1
  46. package/dist/types/src/components/DeckSettings/DeckSettings.d.ts.map +1 -1
  47. package/dist/types/src/components/Plank/Plank.d.ts.map +1 -1
  48. package/dist/types/src/components/Plank/Plank.stories.d.ts +2 -1
  49. package/dist/types/src/components/Plank/Plank.stories.d.ts.map +1 -1
  50. package/dist/types/src/components/Plank/PlankControls.d.ts.map +1 -1
  51. package/dist/types/src/components/Plank/PlankHeading.d.ts.map +1 -1
  52. package/dist/types/src/components/Sidebar/ComplementarySidebar.d.ts.map +1 -1
  53. package/dist/types/src/components/Sidebar/SidebarButton.d.ts.map +1 -1
  54. package/dist/types/src/hooks/useDeckCompanions.d.ts.map +1 -1
  55. package/dist/types/src/hooks/useHoistStatusbar.d.ts.map +1 -1
  56. package/dist/types/src/meta.d.ts.map +1 -1
  57. package/dist/types/src/translations.d.ts +2 -1
  58. package/dist/types/src/translations.d.ts.map +1 -1
  59. package/dist/types/src/types/schema.d.ts +1 -0
  60. package/dist/types/src/types/schema.d.ts.map +1 -1
  61. package/dist/types/tsconfig.tsbuildinfo +1 -1
  62. package/package.json +33 -33
  63. package/src/capabilities/app-graph-builder.ts +22 -22
  64. package/src/capabilities/intent-resolver.ts +0 -1
  65. package/src/capabilities/react-root.tsx +2 -1
  66. package/src/capabilities/settings.ts +1 -0
  67. package/src/capabilities/state.ts +8 -0
  68. package/src/capabilities/toolkit.ts +23 -14
  69. package/src/components/DeckLayout/ActiveNode.tsx +1 -1
  70. package/src/components/DeckLayout/Banner.tsx +1 -1
  71. package/src/components/DeckLayout/ContentEmpty.tsx +1 -1
  72. package/src/components/DeckLayout/DeckLayout.stories.tsx +63 -0
  73. package/src/components/DeckLayout/DeckLayout.tsx +8 -278
  74. package/src/components/DeckLayout/DeckMain.tsx +281 -0
  75. package/src/components/DeckLayout/Dialog.tsx +1 -1
  76. package/src/components/DeckLayout/Fallback.tsx +1 -1
  77. package/src/components/DeckLayout/Popover.tsx +4 -13
  78. package/src/components/DeckLayout/StatusBar.tsx +1 -1
  79. package/src/components/DeckLayout/Toast.tsx +26 -1
  80. package/src/components/DeckSettings/DeckSettings.tsx +6 -0
  81. package/src/components/Plank/Plank.tsx +65 -31
  82. package/src/components/Plank/PlankControls.tsx +3 -2
  83. package/src/components/Plank/PlankHeading.tsx +5 -4
  84. package/src/components/Sidebar/ComplementarySidebar.tsx +31 -11
  85. package/src/components/Sidebar/Sidebar.tsx +1 -1
  86. package/src/components/Sidebar/SidebarButton.tsx +10 -9
  87. package/src/hooks/useCompanions.ts +1 -1
  88. package/src/hooks/useDeckCompanions.ts +2 -1
  89. package/src/hooks/useHoistStatusbar.ts +5 -3
  90. package/src/meta.ts +5 -0
  91. package/src/translations.ts +2 -1
  92. package/src/types/schema.ts +2 -0
  93. package/dist/lib/browser/app-graph-builder-YYP67JHW.mjs.map +0 -7
  94. package/dist/lib/browser/chunk-3SV2V3AN.mjs.map +0 -7
  95. package/dist/lib/browser/chunk-A4SRCSJX.mjs.map +0 -7
  96. package/dist/lib/browser/chunk-JH4ZCG5I.mjs.map +0 -7
  97. package/dist/lib/browser/chunk-MHP4GPX5.mjs +0 -11
  98. package/dist/lib/browser/chunk-MHP4GPX5.mjs.map +0 -7
  99. package/dist/lib/browser/chunk-YRDCQS7E.mjs.map +0 -7
  100. package/dist/lib/browser/react-root-JTWYCDJT.mjs.map +0 -7
  101. package/dist/lib/browser/state-IFKFOBBX.mjs +0 -12
  102. package/dist/lib/browser/toolkit-L7C3UAEU.mjs +0 -63
  103. package/dist/lib/browser/toolkit-L7C3UAEU.mjs.map +0 -7
  104. /package/dist/lib/browser/{check-app-scheme-GCOL6YDT.mjs.map → check-app-scheme-HIEVFAAX.mjs.map} +0 -0
  105. /package/dist/lib/browser/{chunk-HUWUYTOI.mjs.map → chunk-5KMJPIQC.mjs.map} +0 -0
  106. /package/dist/lib/browser/{react-surface-PZKJ73JS.mjs.map → react-surface-6LW337ZT.mjs.map} +0 -0
  107. /package/dist/lib/browser/{state-IFKFOBBX.mjs.map → state-7IFAGZQO.mjs.map} +0 -0
  108. /package/dist/lib/browser/{url-handler-MRYBE3HF.mjs.map → url-handler-QEYGYE2H.mjs.map} +0 -0
@@ -35,7 +35,7 @@ export const Toast = ({
35
35
  <NaturalToast.Root data-testid={id} defaultOpen duration={duration} onOpenChange={onOpenChange}>
36
36
  <NaturalToast.Body>
37
37
  <NaturalToast.Title classNames='items-center'>
38
- {icon && <Icon icon={icon} size={5} classNames='inline mr-1' />}
38
+ {icon && <Icon icon={icon} classNames='inline mr-1' />}
39
39
  {title && <span>{toLocalizedString(title, t)}</span>}
40
40
  </NaturalToast.Title>
41
41
  {description && (
@@ -59,3 +59,28 @@ export const Toast = ({
59
59
  </NaturalToast.Root>
60
60
  );
61
61
  };
62
+
63
+ export type ToasterProps = {
64
+ toasts?: LayoutAction.Toast[];
65
+ onDismissToast?: (id: string) => void;
66
+ };
67
+
68
+ export const Toaster = ({ toasts, onDismissToast }: ToasterProps) => {
69
+ return (
70
+ <>
71
+ {toasts?.map((toast) => (
72
+ <Toast
73
+ {...toast}
74
+ key={toast.id}
75
+ onOpenChange={(open) => {
76
+ if (!open) {
77
+ onDismissToast?.(toast.id);
78
+ }
79
+
80
+ return open;
81
+ }}
82
+ />
83
+ ))}
84
+ </>
85
+ );
86
+ };
@@ -31,6 +31,12 @@ export const DeckSettings = ({ settings }: { settings: DeckSettingsProps }) => {
31
31
  onCheckedChange={(checked) => (settings.enableDeck = checked)}
32
32
  />
33
33
  </ControlItemInput>
34
+ <ControlItemInput title={t('settings encapsulated planks label')}>
35
+ <Input.Switch
36
+ checked={settings.encapsulatedPlanks ?? false}
37
+ onCheckedChange={(checked) => (settings.encapsulatedPlanks = checked)}
38
+ />
39
+ </ControlItemInput>
34
40
  <ControlItemInput title={t('select new plank positioning label')}>
35
41
  <Select.Root
36
42
  disabled={!settings.enableDeck}
@@ -2,6 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { useFocusFinders } from '@fluentui/react-tabster';
5
6
  import React, {
6
7
  type KeyboardEvent,
7
8
  type PropsWithChildren,
@@ -12,14 +13,8 @@ import React, {
12
13
  useRef,
13
14
  } from 'react';
14
15
 
15
- import {
16
- LayoutAction,
17
- Surface,
18
- createIntent,
19
- useAppGraph,
20
- useCapability,
21
- useIntentDispatcher,
22
- } from '@dxos/app-framework';
16
+ import { LayoutAction, createIntent } from '@dxos/app-framework';
17
+ import { Surface, useAppGraph, useCapability, useIntentDispatcher } from '@dxos/app-framework/react';
23
18
  import { debounce } from '@dxos/async';
24
19
  import { type Node, useNode } from '@dxos/plugin-graph';
25
20
  import { ATTENDABLE_PATH_SEPARATOR, useAttentionAttributes } from '@dxos/react-ui-attention';
@@ -37,6 +32,10 @@ import { PlankLoading } from './PlankLoading';
37
32
 
38
33
  const UNKNOWN_ID = 'unknown_id';
39
34
 
35
+ //
36
+ // Plank
37
+ //
38
+
40
39
  export type PlankProps = Pick<PlankComponentProps, 'layoutMode' | 'part' | 'path' | 'order' | 'active' | 'settings'> & {
41
40
  id?: string;
42
41
  companionId?: string;
@@ -68,7 +67,11 @@ export const Plank = memo(({ id = UNKNOWN_ID, companionId, ...props }: PlankProp
68
67
  const hasCompanion = !!(companionId && currentCompanion);
69
68
 
70
69
  return (
71
- <PlankContainer solo={props.part === 'solo'} companion={hasCompanion}>
70
+ <PlankContainer
71
+ solo={props.part === 'solo'}
72
+ companion={hasCompanion}
73
+ encapsulate={!!props.settings?.encapsulatedPlanks}
74
+ >
72
75
  <PlankComponent
73
76
  id={id}
74
77
  node={node}
@@ -92,7 +95,13 @@ export const Plank = memo(({ id = UNKNOWN_ID, companionId, ...props }: PlankProp
92
95
  );
93
96
  });
94
97
 
95
- const PlankContainer = ({ children, solo, companion }: PropsWithChildren<{ solo: boolean; companion: boolean }>) => {
98
+ //
99
+ // PlankContainer
100
+ //
101
+
102
+ type PlankContainerProps = PropsWithChildren<{ solo: boolean; companion: boolean; encapsulate: boolean }>;
103
+
104
+ const PlankContainer = ({ children, solo, companion, encapsulate }: PlankContainerProps) => {
96
105
  const sizeAttrs = useMainSize();
97
106
  if (!solo) {
98
107
  return children;
@@ -102,7 +111,14 @@ const PlankContainer = ({ children, solo, companion }: PropsWithChildren<{ solo:
102
111
  return (
103
112
  <div
104
113
  role='none'
105
- className={mx('absolute inset-0 grid', companion && 'grid-cols-[6fr_4fr]', railGridHorizontal, mainIntrinsicSize)}
114
+ data-popover-collision-boundary={true}
115
+ className={mx(
116
+ 'absolute inset-[--main-spacing] grid',
117
+ encapsulate && 'border border-separator rounded overflow-hidden',
118
+ companion && 'grid-cols-[6fr_4fr]', // TODO(burdon): Resize.
119
+ railGridHorizontal,
120
+ mainIntrinsicSize,
121
+ )}
106
122
  {...sizeAttrs}
107
123
  >
108
124
  {children}
@@ -110,6 +126,10 @@ const PlankContainer = ({ children, solo, companion }: PropsWithChildren<{ solo:
110
126
  );
111
127
  };
112
128
 
129
+ //
130
+ // PlankComponent
131
+ //
132
+
113
133
  type PlankComponentProps = {
114
134
  layoutMode: LayoutMode;
115
135
  id: string;
@@ -117,7 +137,6 @@ type PlankComponentProps = {
117
137
  path?: string[];
118
138
  order?: number;
119
139
  active?: string[];
120
- // TODO(burdon): Change to role?
121
140
  companioned?: 'primary' | 'companion';
122
141
  node?: Node;
123
142
  primary?: Node;
@@ -141,6 +160,7 @@ const PlankComponent = memo(
141
160
  }: PlankComponentProps) => {
142
161
  const { dispatchPromise: dispatch } = useIntentDispatcher();
143
162
  const { deck, popoverAnchorId, scrollIntoView } = useCapability(DeckCapabilities.DeckState);
163
+ const { findFirstFocusable } = useFocusFinders();
144
164
  const canResize = layoutMode === 'deck';
145
165
 
146
166
  const attentionAttrs = useAttentionAttributes(primary?.id ?? id);
@@ -164,8 +184,15 @@ const PlankComponent = memo(
164
184
 
165
185
  // TODO(thure): Tabster’s focus group should handle moving focus to Main, but something is blocking it.
166
186
  const handleKeyDown = useCallback((event: KeyboardEvent) => {
167
- if (event.target === event.currentTarget && event.key === 'Escape') {
168
- rootElement.current?.closest('main')?.focus();
187
+ if (event.target === event.currentTarget) {
188
+ switch (event.key) {
189
+ case 'Escape':
190
+ rootElement.current?.closest('main')?.focus();
191
+ break;
192
+ case 'Enter':
193
+ rootElement.current && findFirstFocusable(rootElement.current)?.focus();
194
+ break;
195
+ }
169
196
  }
170
197
  }, []);
171
198
 
@@ -201,21 +228,27 @@ const PlankComponent = memo(
201
228
  const placeholder = useMemo(() => <PlankLoading />, []);
202
229
 
203
230
  const Root = part.startsWith('solo') ? 'article' : StackItem.Root;
231
+ const fullscreen = layoutMode === 'solo--fullscreen';
204
232
  const className = mx(
205
233
  'attention-surface relative dx-focus-ring-inset-over-all density-coarse',
206
- isSolo && mainIntrinsicSize,
207
- isSolo && railGridHorizontal,
208
234
  isSolo && 'absolute inset-0',
235
+ isSolo && mainIntrinsicSize,
236
+ railGridHorizontal,
209
237
  part.startsWith('solo') && 'grid',
238
+ part.startsWith('solo-') && 'grid-rows-subgrid row-span-2 min-is-0',
239
+ fullscreen && 'grid-rows-1',
210
240
  part === 'deck' && (companioned === 'companion' ? '!border-separator border-ie' : '!border-separator border-li'),
211
- part.startsWith('solo-') && 'row-span-2 grid-rows-subgrid min-is-0',
212
241
  part === 'solo-companion' && '!border-separator border-is',
242
+ settings?.encapsulatedPlanks &&
243
+ !part.startsWith('solo') &&
244
+ 'mli-[--main-spacing] !border-separator border rounded overflow-hidden',
213
245
  );
214
246
 
215
247
  return (
216
248
  <Root
217
249
  ref={rootElement}
218
250
  data-testid='deck.plank'
251
+ data-popover-collision-boundary={true}
219
252
  tabIndex={0}
220
253
  {...(part.startsWith('solo')
221
254
  ? ({ ...sizeAttrs, className } as any)
@@ -232,19 +265,21 @@ const PlankComponent = memo(
232
265
  >
233
266
  {node ? (
234
267
  <>
235
- <PlankHeading
236
- id={id}
237
- part={part.startsWith('solo-') ? 'solo' : part}
238
- node={node}
239
- layoutMode={layoutMode}
240
- deckEnabled={settings?.enableDeck}
241
- canIncrementStart={canIncrementStart}
242
- canIncrementEnd={canIncrementEnd}
243
- popoverAnchorId={popoverAnchorId}
244
- primaryId={primary?.id}
245
- companioned={companioned}
246
- companions={companions}
247
- />
268
+ {!fullscreen && (
269
+ <PlankHeading
270
+ id={id}
271
+ part={part.startsWith('solo-') ? 'solo' : part}
272
+ node={node}
273
+ layoutMode={layoutMode}
274
+ deckEnabled={settings?.enableDeck}
275
+ canIncrementStart={canIncrementStart}
276
+ canIncrementEnd={canIncrementEnd}
277
+ popoverAnchorId={popoverAnchorId}
278
+ primaryId={primary?.id}
279
+ companioned={companioned}
280
+ companions={companions}
281
+ />
282
+ )}
248
283
  <Surface
249
284
  key={node.id}
250
285
  role='article'
@@ -257,7 +292,6 @@ const PlankComponent = memo(
257
292
  ) : (
258
293
  <PlankError id={id} part={part} />
259
294
  )}
260
-
261
295
  {canResize && <StackItem.ResizeHandle />}
262
296
  </Root>
263
297
  );
@@ -4,7 +4,8 @@
4
4
 
5
5
  import React, { forwardRef, useCallback } from 'react';
6
6
 
7
- import { createIntent, useIntentDispatcher } from '@dxos/app-framework';
7
+ import { createIntent } from '@dxos/app-framework';
8
+ import { useIntentDispatcher } from '@dxos/app-framework/react';
8
9
  import { invariant } from '@dxos/invariant';
9
10
  import { ButtonGroup, type ButtonGroupProps, type ButtonProps, IconButton, useTranslation } from '@dxos/react-ui';
10
11
 
@@ -32,7 +33,7 @@ export type PlankControlsProps = Omit<ButtonGroupProps, 'onClick'> & {
32
33
  };
33
34
 
34
35
  const PlankControl = ({ icon, label, ...props }: Omit<ButtonProps, 'children'> & { label: string; icon: string }) => {
35
- return <IconButton iconOnly label={label} icon={icon} size={5} variant='ghost' tooltipSide='bottom' {...props} />;
36
+ return <IconButton label={label} icon={icon} iconOnly variant='ghost' tooltipSide='bottom' {...props} />;
36
37
  };
37
38
 
38
39
  const plankControlSpacing = 'pli-2';
@@ -4,7 +4,8 @@
4
4
 
5
5
  import React, { Fragment, type MouseEvent, memo, useCallback, useEffect, useMemo } from 'react';
6
6
 
7
- import { LayoutAction, Surface, createIntent, useAppGraph, useIntentDispatcher } from '@dxos/app-framework';
7
+ import { LayoutAction, createIntent } from '@dxos/app-framework';
8
+ import { Surface, useAppGraph, useIntentDispatcher } from '@dxos/app-framework/react';
8
9
  import { type Node } from '@dxos/plugin-graph';
9
10
  import { Icon, IconButton, Popover, toLocalizedString, useTranslation } from '@dxos/react-ui';
10
11
  import { StackItem, type StackItemSigilAction } from '@dxos/react-ui-stack';
@@ -162,7 +163,8 @@ export const PlankHeading = memo(
162
163
  ? [
163
164
  hoverableControls,
164
165
  hoverableFocusedWithinControls,
165
- '*:transition-opacity *:opacity-[--controls-opacity] bg-transparent border-transparent transition-[background-color,border-color] hover-hover:hover:bg-headerSurface focus-within:bg-headerSurface hover-hover:hover:border-subduedSeparator focus-within:border-subduedSeparator',
166
+ '*:transition-opacity *:opacity-[--controls-opacity] bg-transparent border-transparent transition-[background-color,border-color]',
167
+ 'hover-hover:hover:bg-headerSurface focus-within:bg-headerSurface hover-hover:hover:border-subduedSeparator focus-within:border-subduedSeparator',
166
168
  ]
167
169
  : []),
168
170
  ]}
@@ -178,7 +180,6 @@ export const PlankHeading = memo(
178
180
  icon={icon}
179
181
  iconOnly={companions.length > MAX_COMPANIONS && node?.id !== id}
180
182
  label={toLocalizedString(label, t)}
181
- size={5}
182
183
  variant={node?.id === id ? 'primary' : 'ghost'}
183
184
  onClick={handleTabClick}
184
185
  />
@@ -201,7 +202,7 @@ export const PlankHeading = memo(
201
202
  ) : (
202
203
  <StackItem.SigilButton>
203
204
  <span className='sr-only'>{label}</span>
204
- <Icon icon={icon} size={5} />
205
+ <Icon icon={icon} />
205
206
  </StackItem.SigilButton>
206
207
  )}
207
208
  </ActionRoot>
@@ -12,9 +12,11 @@ import React, {
12
12
  useState,
13
13
  } from 'react';
14
14
 
15
- import { LayoutAction, Surface, createIntent, useCapability, useIntentDispatcher } from '@dxos/app-framework';
15
+ import { LayoutAction, createIntent } from '@dxos/app-framework';
16
+ import { Surface, useCapability, useIntentDispatcher } from '@dxos/app-framework/react';
16
17
  import { IconButton, type Label, Main, toLocalizedString, useTranslation } from '@dxos/react-ui';
17
18
  import { Tabs } from '@dxos/react-ui-tabs';
19
+ import { mx } from '@dxos/react-ui-theme';
18
20
 
19
21
  import { DeckCapabilities } from '../../capabilities';
20
22
  import { type DeckCompanion, getCompanionId, useBreakpoints, useDeckCompanions, useHoistStatusbar } from '../../hooks';
@@ -91,7 +93,11 @@ export const ComplementarySidebar = ({ current }: ComplementarySidebarProps) =>
91
93
  <Tabs.Root orientation='vertical' verticalVariant='stateless' value={internalValue} classNames='contents'>
92
94
  <div
93
95
  role='none'
94
- className='absolute z-[1] inset-block-0 inline-end-0 !is-[--r0-size] pbs-[env(safe-area-inset-top)] pbe-[env(safe-area-inset-bottom)] border-is border-subduedSeparator grid grid-cols-1 grid-rows-[1fr_min-content] bg-baseSurface contain-layout app-drag'
96
+ className={mx(
97
+ 'absolute z-[1] inset-block-0 inline-end-0 !is-[--r0-size]',
98
+ 'pbs-[env(safe-area-inset-top)] pbe-[env(safe-area-inset-bottom)] border-is border-subduedSeparator',
99
+ 'grid grid-cols-1 grid-rows-[1fr_min-content] bg-baseSurface contain-layout app-drag',
100
+ )}
95
101
  >
96
102
  <Tabs.Tablist classNames='grid grid-cols-1 auto-rows-[--rail-action] p-1 gap-1 !overflow-y-auto'>
97
103
  {companions.map((companion) => (
@@ -99,7 +105,6 @@ export const ComplementarySidebar = ({ current }: ComplementarySidebarProps) =>
99
105
  <IconButton
100
106
  label={toLocalizedString(companion.properties.label, t)}
101
107
  icon={companion.properties.icon}
102
- size={5}
103
108
  iconOnly
104
109
  tooltipSide='left'
105
110
  data-value={getCompanionId(companion.id)}
@@ -129,7 +134,11 @@ export const ComplementarySidebar = ({ current }: ComplementarySidebarProps) =>
129
134
  <Tabs.Tabpanel
130
135
  key={getCompanionId(companion.id)}
131
136
  value={getCompanionId(companion.id)}
132
- classNames='absolute data-[state="inactive"]:-z-[1] inset-block-0 inline-start-0 is-[calc(100%-var(--r0-size))] lg:is-[--r1-size] grid grid-cols-1 grid-rows-[var(--rail-size)_1fr_min-content] pbs-[env(safe-area-inset-top)]'
137
+ classNames={[
138
+ 'absolute data-[state="inactive"]:-z-[1] overflow-hidden',
139
+ 'inset-block-0 inline-start-0 is-[calc(100%-var(--r0-size))] lg:is-[--r1-size]',
140
+ 'grid grid-cols-1 grid-rows-[var(--rail-size)_1fr_min-content] pbs-[env(safe-area-inset-top)]',
141
+ ]}
133
142
  {...(layout.complementarySidebarState !== 'expanded' && { inert: true })}
134
143
  >
135
144
  <ComplementarySidebarPanel
@@ -155,10 +164,6 @@ type ComplementarySidebarPanelProps = {
155
164
  hoistStatusbar: boolean;
156
165
  };
157
166
 
158
- const ScrollArea = ({ children }: PropsWithChildren) => {
159
- return <div className='flex flex-col grow overflow-x-hidden overflow-y-auto scrollbar-thin'>{children}</div>;
160
- };
161
-
162
167
  const ComplementarySidebarPanel = ({ companion, activeId, data, hoistStatusbar }: ComplementarySidebarPanelProps) => {
163
168
  const { t } = useTranslation(meta.id);
164
169
 
@@ -170,9 +175,20 @@ const ComplementarySidebarPanel = ({ companion, activeId, data, hoistStatusbar }
170
175
 
171
176
  return (
172
177
  <>
173
- <h2 className='flex items-center pli-2 border-subduedSeparator border-be font-medium'>
174
- {toLocalizedString(companion.properties.label, t)}
175
- </h2>
178
+ <div role='none' className='flex items-center p-1 gap-1 border-be border-subduedSeparator'>
179
+ <IconButton
180
+ label={toLocalizedString(companion.properties.label, t)}
181
+ icon={companion.properties.icon}
182
+ iconOnly
183
+ tooltipSide='left'
184
+ data-value={getCompanionId(companion.id)}
185
+ classNames='bs-10 is-10'
186
+ variant='default'
187
+ />
188
+ <div role='none' className='pli-1'>
189
+ {toLocalizedString(companion.properties.label, t)}
190
+ </div>
191
+ </div>
176
192
  <Wrapper>
177
193
  <Surface
178
194
  role={`deck-companion--${getCompanionId(companion.id)}`}
@@ -192,3 +208,7 @@ const ComplementarySidebarPanel = ({ companion, activeId, data, hoistStatusbar }
192
208
  </>
193
209
  );
194
210
  };
211
+
212
+ const ScrollArea = ({ children }: PropsWithChildren) => {
213
+ return <div className='flex flex-col grow overflow-x-hidden overflow-y-auto scrollbar-thin'>{children}</div>;
214
+ };
@@ -4,7 +4,7 @@
4
4
 
5
5
  import React, { useMemo } from 'react';
6
6
 
7
- import { Surface, useCapability } from '@dxos/app-framework';
7
+ import { Surface, useCapability } from '@dxos/app-framework/react';
8
8
  import { type Label, Main } from '@dxos/react-ui';
9
9
 
10
10
  import { DeckCapabilities } from '../../capabilities';
@@ -4,7 +4,8 @@
4
4
 
5
5
  import React, { useCallback } from 'react';
6
6
 
7
- import { LayoutAction, createIntent, useCapability, useIntentDispatcher } from '@dxos/app-framework';
7
+ import { LayoutAction, createIntent } from '@dxos/app-framework';
8
+ import { useCapability, useIntentDispatcher } from '@dxos/app-framework/react';
8
9
  import { IconButton, type IconButtonProps, type ThemedClassName, useTranslation } from '@dxos/react-ui';
9
10
 
10
11
  import { DeckCapabilities } from '../../capabilities';
@@ -20,8 +21,8 @@ export const ToggleSidebarButton = ({
20
21
  return (
21
22
  <IconButton
22
23
  variant={variant}
23
- iconOnly
24
24
  icon='ph--sidebar--regular'
25
+ iconOnly
25
26
  size={4}
26
27
  label={t('open navigation sidebar label')}
27
28
  onClick={() =>
@@ -38,8 +39,8 @@ export const CloseSidebarButton = () => {
38
39
  return (
39
40
  <IconButton
40
41
  variant='ghost'
41
- iconOnly
42
42
  icon='ph--caret-line-left--regular'
43
+ iconOnly
43
44
  size={4}
44
45
  label={t('close navigation sidebar label')}
45
46
  onClick={() => (layoutContext.sidebarState = 'collapsed')}
@@ -61,12 +62,12 @@ export const ToggleComplementarySidebarButton = ({
61
62
  const handleClick = useCallback(async () => {
62
63
  layoutContext.complementarySidebarState =
63
64
  layoutContext.complementarySidebarState === 'expanded' ? 'collapsed' : 'expanded';
64
- const firstCompanion = companions[0];
65
- if (layoutContext.complementarySidebarState === 'expanded' && !current && firstCompanion) {
65
+ const subject = layoutContext.complementarySidebarPanel ?? (companions[0] && getCompanionId(companions[0].id));
66
+ if (layoutContext.complementarySidebarState === 'expanded' && !current && subject) {
66
67
  await dispatch(
67
68
  createIntent(LayoutAction.UpdateComplementary, {
68
69
  part: 'complementary',
69
- subject: getCompanionId(firstCompanion.id),
70
+ subject,
70
71
  }),
71
72
  );
72
73
  }
@@ -74,14 +75,14 @@ export const ToggleComplementarySidebarButton = ({
74
75
 
75
76
  return (
76
77
  <IconButton
77
- iconOnly
78
- onClick={handleClick}
79
78
  variant='ghost'
80
- label={t('open complementary sidebar label')}
81
79
  classNames={['[&>svg]:-scale-x-100', classNames]}
82
80
  icon='ph--sidebar-simple--regular'
81
+ iconOnly
82
+ label={t('open complementary sidebar label')}
83
83
  size={inR0 ? 5 : 4}
84
84
  tooltipSide={inR0 ? 'left' : undefined}
85
+ onClick={handleClick}
85
86
  />
86
87
  );
87
88
  };
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { useMemo } from 'react';
6
6
 
7
- import { useAppGraph } from '@dxos/app-framework';
7
+ import { useAppGraph } from '@dxos/app-framework/react';
8
8
  import { useConnections } from '@dxos/plugin-graph';
9
9
  import { byPosition } from '@dxos/util';
10
10
 
@@ -2,7 +2,8 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type Label, useAppGraph } from '@dxos/app-framework';
5
+ import { type Label } from '@dxos/app-framework';
6
+ import { useAppGraph } from '@dxos/app-framework/react';
6
7
  import { type Node, ROOT_ID, useConnections } from '@dxos/plugin-graph';
7
8
  import { type Position, byPosition } from '@dxos/util';
8
9
 
@@ -4,16 +4,18 @@
4
4
 
5
5
  import { useMemo } from 'react';
6
6
 
7
- import { Capabilities, useCapability } from '@dxos/app-framework';
7
+ import { Capabilities } from '@dxos/app-framework';
8
+ import { useCapability } from '@dxos/app-framework/react';
8
9
  import { useThemeContext } from '@dxos/react-ui';
9
10
 
10
11
  import { meta } from '../meta';
11
12
  import type { DeckSettingsProps, LayoutMode } from '../types';
12
13
 
13
14
  export const useHoistStatusbar = (breakpoint: string, layoutMode?: LayoutMode): boolean => {
14
- const enableStatusbar = useCapability(Capabilities.SettingsStore).getStore<DeckSettingsProps>(meta.id)!.value
15
- .enableStatusbar;
16
15
  const { safeAreaPadding } = useThemeContext();
16
+ const enableStatusbar = useCapability(Capabilities.SettingsStore).getStore<DeckSettingsProps>(meta.id)?.value
17
+ .enableStatusbar;
18
+
17
19
  return useMemo(() => {
18
20
  return (
19
21
  breakpoint === 'desktop' &&
package/src/meta.ts CHANGED
@@ -3,9 +3,14 @@
3
3
  //
4
4
 
5
5
  import { type PluginMeta } from '@dxos/app-framework';
6
+ import { trim } from '@dxos/util';
6
7
 
7
8
  export const meta: PluginMeta = {
8
9
  id: 'dxos.org/plugin/deck',
9
10
  name: 'Layout',
11
+ description: trim`
12
+ Flexible layout system for arranging workspace views in tabs, splits, and panels.
13
+ Customize your workspace organization with drag-and-drop layout management.
14
+ `,
10
15
  icon: 'ph--layout--regular',
11
16
  };
@@ -34,7 +34,7 @@ export const translations = [
34
34
  'undo action label': 'Undo',
35
35
  'undo action alt': 'Undo previous action',
36
36
  'undo close label': 'Dismiss',
37
- 'error fallback message': 'Unable to open this item',
37
+ 'error fallback message': 'Unable to open this object',
38
38
  'plank heading fallback label': 'Untitled',
39
39
  'actions menu label': 'Options',
40
40
  'settings deck label': 'Disable deck',
@@ -59,6 +59,7 @@ export const translations = [
59
59
  'settings overscroll none label': 'None',
60
60
  'settings enable statusbar label': 'Show status bar',
61
61
  'settings enable deck label': 'Enable Deck',
62
+ 'settings encapsulated planks label': 'Encapsulated planks',
62
63
  'close current label': 'Close current plank',
63
64
  'close others label': 'Close other planks',
64
65
  'close all label': 'Close all planks',
@@ -31,6 +31,8 @@ export const DeckSettingsSchema = Schema.Struct({
31
31
  enableNativeRedirect: Schema.optional(Schema.Boolean),
32
32
  newPlankPositioning: Schema.optional(Schema.Literal(...NewPlankPositions)),
33
33
  overscroll: Schema.optional(Schema.Literal(...OverscrollOptions)),
34
+ // TODO(burdon): Rename layoutMode? (e.g., bento | encapsulated?)
35
+ encapsulatedPlanks: Schema.optional(Schema.Boolean),
34
36
  }).pipe(Schema.mutable);
35
37
  export type DeckSettingsProps = Schema.Schema.Type<typeof DeckSettingsSchema>;
36
38
 
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/capabilities/app-graph-builder.ts"],
4
- "sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\nimport { Rx } from '@effect-rx/rx-react';\nimport * as Function from 'effect/Function';\nimport * as Option from 'effect/Option';\n\nimport { Capabilities, LayoutAction, type PluginContext, contributes, createIntent } from '@dxos/app-framework';\nimport { AttentionCapabilities } from '@dxos/plugin-attention';\nimport { ROOT_ID, createExtension, rxFromSignal } from '@dxos/plugin-graph';\n\nimport { meta } from '../meta';\n\nimport { DeckCapabilities } from './capabilities';\n\nexport default (context: PluginContext) =>\n contributes(\n Capabilities.AppGraphBuilder,\n createExtension({\n id: meta.id,\n actions: (node) =>\n Rx.make((get) =>\n Function.pipe(\n get(node),\n Option.flatMap((node) => (node.id === ROOT_ID ? Option.some(node) : Option.none())),\n Option.map(() => {\n const state = context.getCapability(DeckCapabilities.MutableDeckState);\n\n // NOTE(Zan): This is currently disabled.\n // TODO(Zan): Fullscreen needs to know the active node and provide that to the layout part.\n const _fullscreen = {\n id: `${LayoutAction.UpdateLayout._tag}/fullscreen`,\n data: async () => {\n const { dispatchPromise: dispatch } = context.getCapability(Capabilities.IntentDispatcher);\n await dispatch(\n createIntent(LayoutAction.SetLayoutMode, { part: 'mode', options: { mode: 'fullscreen' } }),\n );\n },\n properties: {\n label: ['toggle fullscreen label', { ns: meta.id }],\n icon: 'ph--arrows-out--regular',\n keyBinding: {\n macos: 'ctrl+meta+f',\n windows: 'shift+ctrl+f',\n },\n },\n };\n\n const closeCurrent = {\n id: `${LayoutAction.Close._tag}/current`,\n data: async () => {\n const attention = context.getCapability(AttentionCapabilities.Attention);\n const attended = attention.current.at(-1);\n if (attended) {\n const { dispatchPromise: dispatch } = context.getCapability(Capabilities.IntentDispatcher);\n await dispatch(\n createIntent(LayoutAction.Close, {\n part: 'main',\n subject: [attended],\n options: { state: false },\n }),\n );\n }\n },\n properties: {\n label: ['close current label', { ns: meta.id }],\n icon: 'ph--x--regular',\n },\n };\n\n const closeOthers = {\n id: `${LayoutAction.Close._tag}/others`,\n data: async () => {\n const { dispatchPromise: dispatch } = context.getCapability(Capabilities.IntentDispatcher);\n const attention = context.getCapability(AttentionCapabilities.Attention);\n const attended = attention.current.at(-1);\n const ids = state.deck.active.filter((id) => id !== attended) ?? [];\n await dispatch(\n createIntent(LayoutAction.Close, { part: 'main', subject: ids, options: { state: false } }),\n );\n },\n properties: {\n label: ['close others label', { ns: meta.id }],\n icon: 'ph--x-square--regular',\n },\n };\n\n const closeAll = {\n id: `${LayoutAction.Close._tag}/all`,\n data: async () => {\n const { dispatchPromise: dispatch } = context.getCapability(Capabilities.IntentDispatcher);\n await dispatch(\n createIntent(LayoutAction.Close, {\n part: 'main',\n subject: state.deck.active,\n options: { state: false },\n }),\n );\n },\n properties: {\n label: ['close all label', { ns: meta.id }],\n icon: 'ph--x-circle--regular',\n },\n };\n\n const toggleSidebar = {\n id: `${LayoutAction.UpdateSidebar._tag}/nav`,\n data: async () => {\n state.sidebarState = state.sidebarState === 'expanded' ? 'collapsed' : 'expanded';\n },\n properties: {\n label: [\n get(\n rxFromSignal(() =>\n state.sidebarState === 'expanded'\n ? 'collapse navigation sidebar label'\n : 'open navigation sidebar label',\n ),\n ),\n { ns: meta.id },\n ],\n icon: 'ph--sidebar--regular',\n keyBinding: {\n macos: \"meta+'\",\n },\n disposition: 'pin-end',\n position: 'hoist',\n l0Breakpoint: 'lg',\n },\n };\n\n return get(\n rxFromSignal(() =>\n !state.deck.solo ? [closeCurrent, closeOthers, closeAll, toggleSidebar] : [toggleSidebar],\n ),\n );\n }),\n Option.getOrElse(() => []),\n ),\n ),\n }),\n );\n"],
5
- "mappings": "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",
6
- "names": ["Rx", "Function", "Option", "Capabilities", "LayoutAction", "contributes", "createIntent", "AttentionCapabilities", "ROOT_ID", "createExtension", "rxFromSignal", "context", "contributes", "Capabilities", "AppGraphBuilder", "createExtension", "id", "meta", "actions", "node", "Rx", "make", "get", "pipe", "flatMap", "ROOT_ID", "some", "none", "map", "state", "getCapability", "DeckCapabilities", "MutableDeckState", "_fullscreen", "LayoutAction", "UpdateLayout", "_tag", "data", "dispatchPromise", "dispatch", "IntentDispatcher", "createIntent", "SetLayoutMode", "part", "options", "mode", "properties", "label", "ns", "icon", "keyBinding", "macos", "windows", "closeCurrent", "Close", "attention", "AttentionCapabilities", "Attention", "attended", "current", "at", "subject", "closeOthers", "ids", "deck", "active", "filter", "closeAll", "toggleSidebar", "UpdateSidebar", "sidebarState", "rxFromSignal", "disposition", "position", "l0Breakpoint", "solo", "getOrElse"]
7
- }