@dxos/plugin-deck 0.8.1-staging.5be625a → 0.8.1-staging.9eaf14f

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 (161) hide show
  1. package/dist/lib/browser/{app-graph-builder-K4KVSHNT.mjs → app-graph-builder-VYZ4IWI3.mjs} +3 -3
  2. package/dist/lib/browser/{check-app-scheme-6SS6I3RN.mjs → check-app-scheme-SEYECDHI.mjs} +2 -2
  3. package/dist/lib/browser/{chunk-RZLH5F56.mjs → chunk-6ZSOFCPP.mjs} +26 -6
  4. package/dist/lib/browser/chunk-6ZSOFCPP.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-B4LOJUWW.mjs +24 -0
  6. package/dist/lib/browser/{chunk-MWUT66KV.mjs → chunk-FJBMNSUC.mjs} +547 -493
  7. package/dist/lib/browser/chunk-FJBMNSUC.mjs.map +7 -0
  8. package/dist/lib/browser/chunk-FLOVGNYB.mjs +81 -0
  9. package/dist/lib/browser/chunk-FLOVGNYB.mjs.map +7 -0
  10. package/dist/lib/browser/{chunk-2WTHB3TG.mjs → chunk-NSATFAEE.mjs} +3 -3
  11. package/dist/lib/browser/{chunk-2WTHB3TG.mjs.map → chunk-NSATFAEE.mjs.map} +1 -1
  12. package/dist/lib/browser/{chunk-7X43JKZG.mjs → chunk-RJNCG4ND.mjs} +41 -45
  13. package/dist/lib/browser/chunk-RJNCG4ND.mjs.map +7 -0
  14. package/dist/lib/browser/{chunk-WCNPMAR4.mjs → chunk-XMCG42ID.mjs} +2 -3
  15. package/dist/lib/browser/chunk-XMCG42ID.mjs.map +7 -0
  16. package/dist/lib/browser/index.mjs +12 -8
  17. package/dist/lib/browser/index.mjs.map +3 -3
  18. package/dist/lib/browser/{intent-resolver-MEBOMCYI.mjs → intent-resolver-UDYKO2QW.mjs} +39 -78
  19. package/dist/lib/browser/intent-resolver-UDYKO2QW.mjs.map +7 -0
  20. package/dist/lib/browser/meta.json +1 -1
  21. package/dist/lib/browser/{react-root-USUAHDML.mjs → react-root-XLXN2VEW.mjs} +8 -10
  22. package/dist/lib/browser/react-root-XLXN2VEW.mjs.map +7 -0
  23. package/dist/lib/browser/{react-surface-TQG4YYES.mjs → react-surface-WNGMZL7I.mjs} +11 -10
  24. package/dist/lib/browser/react-surface-WNGMZL7I.mjs.map +7 -0
  25. package/dist/lib/browser/{settings-DYS3FFMN.mjs → settings-HMDGSBGO.mjs} +5 -4
  26. package/dist/lib/browser/settings-HMDGSBGO.mjs.map +7 -0
  27. package/dist/lib/browser/{state-DRRCGMU2.mjs → state-7TN26M42.mjs} +4 -4
  28. package/dist/lib/browser/{state-DRRCGMU2.mjs.map → state-7TN26M42.mjs.map} +2 -2
  29. package/dist/lib/browser/{tools-NDEUSO4R.mjs → tools-SC6QEN7R.mjs} +3 -3
  30. package/dist/lib/browser/types.mjs +12 -12
  31. package/dist/lib/browser/{url-handler-4BCN7AYC.mjs → url-handler-ODG4B6NX.mjs} +4 -4
  32. package/dist/types/src/DeckPlugin.d.ts.map +1 -1
  33. package/dist/types/src/capabilities/capabilities.d.ts +10 -12
  34. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  35. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  36. package/dist/types/src/capabilities/react-root.d.ts.map +1 -1
  37. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  38. package/dist/types/src/capabilities/settings.d.ts.map +1 -1
  39. package/dist/types/src/capabilities/state.d.ts +5 -5
  40. package/dist/types/src/components/DeckLayout/ActiveNode.d.ts.map +1 -1
  41. package/dist/types/src/components/DeckLayout/Banner.d.ts.map +1 -1
  42. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts +1 -4
  43. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
  44. package/dist/types/src/components/DeckLayout/Toast.d.ts.map +1 -1
  45. package/dist/types/src/components/DeckLayout/index.d.ts +1 -0
  46. package/dist/types/src/components/DeckLayout/index.d.ts.map +1 -1
  47. package/dist/types/src/components/DeckSettings/DeckSettings.d.ts +6 -0
  48. package/dist/types/src/components/DeckSettings/DeckSettings.d.ts.map +1 -0
  49. package/dist/types/src/components/DeckSettings/index.d.ts +2 -0
  50. package/dist/types/src/components/DeckSettings/index.d.ts.map +1 -0
  51. package/dist/types/src/components/Plank/Plank.d.ts +14 -0
  52. package/dist/types/src/components/Plank/Plank.d.ts.map +1 -0
  53. package/dist/types/src/components/Plank/Plank.stories.d.ts +8 -0
  54. package/dist/types/src/components/Plank/Plank.stories.d.ts.map +1 -0
  55. package/dist/types/src/components/{DeckLayout → Plank}/PlankControls.d.ts +2 -0
  56. package/dist/types/src/components/Plank/PlankControls.d.ts.map +1 -0
  57. package/dist/types/src/components/{DeckLayout → Plank}/PlankError.d.ts +2 -2
  58. package/dist/types/src/components/Plank/PlankError.d.ts.map +1 -0
  59. package/dist/types/src/components/{DeckLayout/NodePlankHeading.d.ts → Plank/PlankHeading.d.ts} +7 -6
  60. package/dist/types/src/components/Plank/PlankHeading.d.ts.map +1 -0
  61. package/dist/types/src/components/Plank/PlankLoading.d.ts.map +1 -0
  62. package/dist/types/src/components/Plank/index.d.ts +6 -0
  63. package/dist/types/src/components/Plank/index.d.ts.map +1 -0
  64. package/dist/types/src/components/Sidebar/ComplementarySidebar.d.ts.map +1 -0
  65. package/dist/types/src/components/Sidebar/Sidebar.d.ts.map +1 -0
  66. package/dist/types/src/components/Sidebar/SidebarButton.d.ts.map +1 -0
  67. package/dist/types/src/components/Sidebar/index.d.ts +4 -0
  68. package/dist/types/src/components/Sidebar/index.d.ts.map +1 -0
  69. package/dist/types/src/components/index.d.ts +1 -1
  70. package/dist/types/src/components/index.d.ts.map +1 -1
  71. package/dist/types/src/events.d.ts +0 -1
  72. package/dist/types/src/events.d.ts.map +1 -1
  73. package/dist/types/src/hooks/index.d.ts +0 -1
  74. package/dist/types/src/hooks/index.d.ts.map +1 -1
  75. package/dist/types/src/index.d.ts +1 -0
  76. package/dist/types/src/index.d.ts.map +1 -1
  77. package/dist/types/src/layout.d.ts +7 -1
  78. package/dist/types/src/layout.d.ts.map +1 -1
  79. package/dist/types/src/translations.d.ts +3 -0
  80. package/dist/types/src/translations.d.ts.map +1 -1
  81. package/dist/types/src/types.d.ts +31 -60
  82. package/dist/types/src/types.d.ts.map +1 -1
  83. package/dist/types/src/util/index.d.ts +1 -0
  84. package/dist/types/src/util/index.d.ts.map +1 -1
  85. package/dist/types/src/util/set-active.d.ts +2 -2
  86. package/dist/types/src/util/set-active.d.ts.map +1 -1
  87. package/dist/types/src/util/useCompanions.d.ts +8 -0
  88. package/dist/types/src/util/useCompanions.d.ts.map +1 -0
  89. package/dist/types/src/util/useHoistStatusbar.d.ts.map +1 -1
  90. package/package.json +28 -29
  91. package/src/DeckPlugin.ts +0 -1
  92. package/src/capabilities/capabilities.ts +3 -4
  93. package/src/capabilities/intent-resolver.ts +35 -7
  94. package/src/capabilities/react-root.tsx +1 -9
  95. package/src/capabilities/react-surface.tsx +3 -4
  96. package/src/capabilities/settings.ts +7 -2
  97. package/src/capabilities/state.ts +3 -3
  98. package/src/components/DeckLayout/ActiveNode.tsx +2 -1
  99. package/src/components/DeckLayout/Banner.tsx +5 -3
  100. package/src/components/DeckLayout/ContentEmpty.tsx +1 -1
  101. package/src/components/DeckLayout/DeckLayout.tsx +27 -16
  102. package/src/components/DeckLayout/Fullscreen.tsx +1 -1
  103. package/src/components/DeckLayout/Toast.tsx +1 -1
  104. package/src/components/DeckLayout/index.ts +2 -0
  105. package/src/components/{LayoutSettings.tsx → DeckSettings/DeckSettings.tsx} +15 -10
  106. package/src/components/DeckSettings/index.ts +5 -0
  107. package/src/components/Plank/Plank.stories.tsx +43 -0
  108. package/src/components/{DeckLayout → Plank}/Plank.tsx +46 -37
  109. package/src/components/{DeckLayout → Plank}/PlankControls.tsx +40 -25
  110. package/src/components/{DeckLayout → Plank}/PlankError.tsx +3 -3
  111. package/src/components/{DeckLayout/NodePlankHeading.tsx → Plank/PlankHeading.tsx} +98 -59
  112. package/src/components/Plank/index.ts +9 -0
  113. package/src/components/{DeckLayout → Sidebar}/ComplementarySidebar.tsx +65 -81
  114. package/src/components/Sidebar/index.ts +7 -0
  115. package/src/components/index.ts +1 -1
  116. package/src/events.ts +0 -1
  117. package/src/hooks/index.ts +0 -1
  118. package/src/index.ts +1 -0
  119. package/src/layout.ts +19 -2
  120. package/src/meta.ts +2 -2
  121. package/src/translations.ts +3 -0
  122. package/src/types.ts +59 -86
  123. package/src/util/index.ts +1 -0
  124. package/src/util/set-active.ts +2 -2
  125. package/src/util/useCompanions.ts +18 -0
  126. package/src/util/useHoistStatusbar.ts +2 -2
  127. package/dist/lib/browser/chunk-7X43JKZG.mjs.map +0 -7
  128. package/dist/lib/browser/chunk-MWUT66KV.mjs.map +0 -7
  129. package/dist/lib/browser/chunk-NSNAYFAX.mjs +0 -24
  130. package/dist/lib/browser/chunk-RZLH5F56.mjs.map +0 -7
  131. package/dist/lib/browser/chunk-WCNPMAR4.mjs.map +0 -7
  132. package/dist/lib/browser/intent-resolver-MEBOMCYI.mjs.map +0 -7
  133. package/dist/lib/browser/react-root-USUAHDML.mjs.map +0 -7
  134. package/dist/lib/browser/react-surface-TQG4YYES.mjs.map +0 -7
  135. package/dist/lib/browser/settings-DYS3FFMN.mjs.map +0 -7
  136. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts.map +0 -1
  137. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts.map +0 -1
  138. package/dist/types/src/components/DeckLayout/Plank.d.ts +0 -13
  139. package/dist/types/src/components/DeckLayout/Plank.d.ts.map +0 -1
  140. package/dist/types/src/components/DeckLayout/PlankControls.d.ts.map +0 -1
  141. package/dist/types/src/components/DeckLayout/PlankError.d.ts.map +0 -1
  142. package/dist/types/src/components/DeckLayout/PlankLoading.d.ts.map +0 -1
  143. package/dist/types/src/components/DeckLayout/Sidebar.d.ts.map +0 -1
  144. package/dist/types/src/components/DeckLayout/SidebarButton.d.ts.map +0 -1
  145. package/dist/types/src/components/LayoutSettings.d.ts +0 -6
  146. package/dist/types/src/components/LayoutSettings.d.ts.map +0 -1
  147. package/dist/types/src/hooks/useNode.d.ts +0 -11
  148. package/dist/types/src/hooks/useNode.d.ts.map +0 -1
  149. package/src/hooks/useNode.ts +0 -46
  150. /package/dist/lib/browser/{app-graph-builder-K4KVSHNT.mjs.map → app-graph-builder-VYZ4IWI3.mjs.map} +0 -0
  151. /package/dist/lib/browser/{check-app-scheme-6SS6I3RN.mjs.map → check-app-scheme-SEYECDHI.mjs.map} +0 -0
  152. /package/dist/lib/browser/{chunk-NSNAYFAX.mjs.map → chunk-B4LOJUWW.mjs.map} +0 -0
  153. /package/dist/lib/browser/{tools-NDEUSO4R.mjs.map → tools-SC6QEN7R.mjs.map} +0 -0
  154. /package/dist/lib/browser/{url-handler-4BCN7AYC.mjs.map → url-handler-ODG4B6NX.mjs.map} +0 -0
  155. /package/dist/types/src/components/{DeckLayout → Plank}/PlankLoading.d.ts +0 -0
  156. /package/dist/types/src/components/{DeckLayout → Sidebar}/ComplementarySidebar.d.ts +0 -0
  157. /package/dist/types/src/components/{DeckLayout → Sidebar}/Sidebar.d.ts +0 -0
  158. /package/dist/types/src/components/{DeckLayout → Sidebar}/SidebarButton.d.ts +0 -0
  159. /package/src/components/{DeckLayout → Plank}/PlankLoading.tsx +0 -0
  160. /package/src/components/{DeckLayout → Sidebar}/Sidebar.tsx +0 -0
  161. /package/src/components/{DeckLayout → Sidebar}/SidebarButton.tsx +0 -0
@@ -2,64 +2,61 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { Fragment, memo, useCallback, useEffect, useMemo } from 'react';
5
+ import React, { Fragment, memo, useCallback, useEffect, useMemo, type MouseEvent } from 'react';
6
6
 
7
7
  import { createIntent, LayoutAction, Surface, useAppGraph, useIntentDispatcher } from '@dxos/app-framework';
8
8
  import { type Node } from '@dxos/plugin-graph';
9
- import { Icon, Popover, toLocalizedString, useTranslation } from '@dxos/react-ui';
9
+ import { Icon, IconButton, Popover, toLocalizedString, useTranslation } from '@dxos/react-ui';
10
10
  import { StackItem, type StackItemSigilAction } from '@dxos/react-ui-stack';
11
11
  import { TextTooltip } from '@dxos/react-ui-text-tooltip';
12
12
 
13
13
  import { PlankCompanionControls, PlankControls } from './PlankControls';
14
+ import { parseEntryId } from '../../layout';
14
15
  import { DECK_PLUGIN } from '../../meta';
15
- import { DeckAction, type ResolvedPart, SLUG_PATH_SEPARATOR } from '../../types';
16
+ import { PLANK_COMPANION_TYPE, DeckAction, type ResolvedPart } from '../../types';
16
17
  import { useBreakpoints } from '../../util';
17
18
  import { soloInlinePadding } from '../fragments';
18
19
 
19
- export type NodePlankHeadingProps = {
20
+ export type PlankHeadingProps = {
20
21
  id: string;
21
22
  part: ResolvedPart;
22
23
  node?: Node;
24
+ deckEnabled?: boolean;
23
25
  canIncrementStart?: boolean;
24
26
  canIncrementEnd?: boolean;
25
27
  popoverAnchorId?: string;
28
+ primaryId?: string;
26
29
  pending?: boolean;
27
- actions?: StackItemSigilAction[];
28
30
  companioned?: 'primary' | 'companion';
29
- primaryId?: string;
30
- surfaceVariant?: string;
31
+ companions?: Node[];
32
+ actions?: StackItemSigilAction[];
31
33
  };
32
34
 
33
- export const NodePlankHeading = memo(
35
+ export const PlankHeading = memo(
34
36
  ({
35
37
  id,
36
38
  part,
37
39
  node,
40
+ deckEnabled,
38
41
  canIncrementStart,
39
42
  canIncrementEnd,
40
43
  popoverAnchorId,
44
+ primaryId,
41
45
  pending,
42
- actions = [],
43
46
  companioned,
44
- primaryId,
45
- surfaceVariant,
46
- }: NodePlankHeadingProps) => {
47
+ companions,
48
+ actions = [],
49
+ }: PlankHeadingProps) => {
47
50
  const { t } = useTranslation(DECK_PLUGIN);
51
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
48
52
  const { graph } = useAppGraph();
49
53
  const breakpoint = useBreakpoints();
50
54
  const icon = node?.properties?.icon ?? 'ph--placeholder--regular';
51
55
  const label = pending
52
56
  ? t('pending heading')
53
- : toLocalizedString(
54
- (surfaceVariant
55
- ? Array.isArray(node?.properties?.label)
56
- ? [`${surfaceVariant} plank heading`, node.properties.label[1]]
57
- : ['companion plank heading fallback label', { ns: DECK_PLUGIN }]
58
- : node?.properties?.label) ?? ['plank heading fallback label', { ns: DECK_PLUGIN }],
59
- t,
60
- );
61
- const { dispatchPromise: dispatch } = useIntentDispatcher();
62
- const ActionRoot = node && popoverAnchorId === `dxos.org/ui/${DECK_PLUGIN}/${node.id}` ? Popover.Anchor : Fragment;
57
+ : toLocalizedString(node?.properties?.label ?? ['plank heading fallback label', { ns: DECK_PLUGIN }], t);
58
+
59
+ const isCompanionNode = node?.type === PLANK_COMPANION_TYPE;
63
60
 
64
61
  useEffect(() => {
65
62
  const frame = requestAnimationFrame(() => {
@@ -70,21 +67,29 @@ export const NodePlankHeading = memo(
70
67
  return () => cancelAnimationFrame(frame);
71
68
  }, [node]);
72
69
 
73
- // NOTE(Zan): Node ids may now contain a path like `${space}:${id}~comments`
74
- const attendableId = id.split(SLUG_PATH_SEPARATOR).at(0);
70
+ const attendableId = primaryId ?? id;
75
71
  const capabilities = useMemo(
76
72
  () => ({
73
+ deck: deckEnabled ?? true,
77
74
  solo: breakpoint !== 'mobile' && (part === 'solo' || part === 'deck'),
78
75
  incrementStart: canIncrementStart,
79
76
  incrementEnd: canIncrementEnd,
77
+ companion: !isCompanionNode && companions && companions.length > 0,
80
78
  }),
81
- [breakpoint, part, canIncrementStart, canIncrementEnd],
79
+ [breakpoint, part, companions, canIncrementStart, canIncrementEnd, isCompanionNode, deckEnabled],
82
80
  );
83
81
 
84
- const sigilActions = useMemo(
85
- () => node && [actions, graph.actions(node)].filter((a) => a.length > 0),
86
- [actions, node, graph],
87
- );
82
+ const { variant } = parseEntryId(id);
83
+ const sigilActions = useMemo(() => {
84
+ if (!node) {
85
+ return undefined;
86
+ } else if (variant) {
87
+ return [];
88
+ } else {
89
+ return [actions, graph.actions(node)].filter((a) => a.length > 0);
90
+ }
91
+ }, [actions, node, variant, graph]);
92
+
88
93
  const handleAction = useCallback((action: StackItemSigilAction) => {
89
94
  typeof action.data === 'function' && action.data?.({ node: action as Node, caller: DECK_PLUGIN });
90
95
  }, []);
@@ -113,53 +118,87 @@ export const NodePlankHeading = memo(
113
118
  [dispatch, id, part],
114
119
  );
115
120
 
121
+ const ActionRoot = node && popoverAnchorId === `dxos.org/ui/${DECK_PLUGIN}/${node.id}` ? Popover.Anchor : Fragment;
122
+
123
+ const handleTabClick = useCallback(
124
+ (event: MouseEvent) => {
125
+ const target = (event.target as HTMLElement).closest('[data-id]') as HTMLElement | null;
126
+ const tabId = target?.dataset?.id;
127
+ if (primaryId && tabId) {
128
+ void dispatch(
129
+ createIntent(DeckAction.ChangeCompanion, {
130
+ primary: primaryId,
131
+ companion: tabId,
132
+ }),
133
+ );
134
+ }
135
+ },
136
+ [primaryId],
137
+ );
138
+
116
139
  return (
117
140
  <StackItem.Heading
118
141
  classNames={[
119
- 'plb-1 border-be border-separator items-stretch gap-1 sticky inline-start-12 app-drag',
142
+ 'plb-1 border-be border-separator items-stretch gap-1 sticky inline-start-12 app-drag min-is-0 layout-contain',
120
143
  part === 'solo' ? soloInlinePadding : 'pli-1',
121
- surfaceVariant && 'pis-3',
122
144
  ]}
123
145
  >
124
- {!surfaceVariant && (
125
- <ActionRoot>
126
- {node && sigilActions ? (
127
- <StackItem.Sigil
146
+ {companions && isCompanionNode ? (
147
+ <div role='none' className='flex-1 min-is-0 overflow-x-auto scrollbar-thin flex gap-1'>
148
+ {companions.map(({ id, properties: { icon, label } }) => (
149
+ <IconButton
150
+ key={id}
151
+ data-id={id}
128
152
  icon={icon}
129
- related={part === 'complementary'}
153
+ iconOnly={node?.id !== id}
154
+ label={toLocalizedString(label, t)}
155
+ size={5}
156
+ variant={node?.id === id ? 'primary' : 'default'}
157
+ onClick={handleTabClick}
158
+ />
159
+ ))}
160
+ </div>
161
+ ) : (
162
+ <>
163
+ <ActionRoot>
164
+ {node && sigilActions ? (
165
+ <StackItem.Sigil
166
+ icon={icon}
167
+ related={part === 'complementary'}
168
+ attendableId={attendableId}
169
+ triggerLabel={t('actions menu label')}
170
+ actions={sigilActions}
171
+ onAction={handleAction}
172
+ >
173
+ <Surface role='menu-footer' data={{ subject: node.data }} />
174
+ </StackItem.Sigil>
175
+ ) : (
176
+ <StackItem.SigilButton>
177
+ <span className='sr-only'>{label}</span>
178
+ <Icon icon={icon} size={5} />
179
+ </StackItem.SigilButton>
180
+ )}
181
+ </ActionRoot>
182
+ <TextTooltip text={label} onlyWhenTruncating>
183
+ <StackItem.HeadingLabel
130
184
  attendableId={attendableId}
131
- triggerLabel={t('actions menu label')}
132
- actions={sigilActions}
133
- onAction={handleAction}
185
+ related={part === 'complementary'}
186
+ {...(pending && { classNames: 'text-description' })}
134
187
  >
135
- <Surface role='menu-footer' data={{ subject: node.data }} />
136
- </StackItem.Sigil>
137
- ) : (
138
- <StackItem.SigilButton>
139
- <span className='sr-only'>{label}</span>
140
- <Icon icon={icon} size={5} />
141
- </StackItem.SigilButton>
142
- )}
143
- </ActionRoot>
188
+ {label}
189
+ </StackItem.HeadingLabel>
190
+ </TextTooltip>
191
+ </>
144
192
  )}
145
- <TextTooltip text={label} onlyWhenTruncating>
146
- <StackItem.HeadingLabel
147
- attendableId={attendableId}
148
- related={part === 'complementary'}
149
- {...(pending && { classNames: 'text-description' })}
150
- >
151
- {label}
152
- </StackItem.HeadingLabel>
153
- </TextTooltip>
154
193
  {node && part !== 'complementary' && <Surface role='navbar-end' data={{ subject: node.data }} />}
155
194
  {companioned === 'companion' ? (
156
- <PlankCompanionControls primary={surfaceVariant ? id : primaryId} />
195
+ <PlankCompanionControls primary={primaryId} />
157
196
  ) : (
158
197
  <PlankControls
159
198
  capabilities={capabilities}
160
199
  isSolo={part === 'solo'}
161
- onClick={handlePlankAction}
162
200
  close={part === 'complementary' ? 'minify-end' : true}
201
+ onClick={handlePlankAction}
163
202
  />
164
203
  )}
165
204
  </StackItem.Heading>
@@ -0,0 +1,9 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './Plank';
6
+ export * from './PlankControls';
7
+ export * from './PlankError';
8
+ export * from './PlankHeading';
9
+ export * from './PlankLoading';
@@ -3,83 +3,85 @@
3
3
  //
4
4
 
5
5
  import React, {
6
+ Fragment,
7
+ type MouseEvent,
6
8
  type PropsWithChildren,
7
9
  useCallback,
8
10
  useEffect,
9
11
  useMemo,
10
12
  useState,
11
- type MouseEvent,
12
- Fragment,
13
13
  } from 'react';
14
14
 
15
15
  import {
16
- createIntent,
17
16
  LayoutAction,
18
17
  Surface,
18
+ createIntent,
19
19
  useAppGraph,
20
- useCapabilities,
21
20
  useCapability,
22
21
  useIntentDispatcher,
23
22
  } from '@dxos/app-framework';
24
- import {
25
- Main,
26
- useTranslation,
27
- toLocalizedString,
28
- IconButton,
29
- ScrollArea as NaturalScrollArea,
30
- type Label,
31
- } from '@dxos/react-ui';
32
- import { useAttended } from '@dxos/react-ui-attention';
23
+ import { type Node } from '@dxos/plugin-graph';
24
+ import { Main, useTranslation, toLocalizedString, IconButton, type Label } from '@dxos/react-ui';
33
25
  import { Tabs } from '@dxos/react-ui-tabs';
34
- import { byPosition } from '@dxos/util';
26
+ import { byPosition, type Position } from '@dxos/util';
35
27
 
36
- import { PlankContentError } from './PlankError';
37
- import { PlankLoading } from './PlankLoading';
38
28
  import { ToggleComplementarySidebarButton } from './SidebarButton';
39
29
  import { DeckCapabilities } from '../../capabilities';
40
- import { useNode } from '../../hooks';
41
30
  import { DECK_PLUGIN } from '../../meta';
42
- import { type Panel } from '../../types';
31
+ import { ATTENDABLE_PATH_SEPARATOR, DECK_COMPANION_TYPE } from '../../types';
43
32
  import { layoutAppliesTopbar, useBreakpoints, useHoistStatusbar } from '../../util';
33
+ import { PlankContentError, PlankLoading } from '../Plank';
34
+
35
+ const label = ['complementary sidebar title', { ns: DECK_PLUGIN }] satisfies Label;
36
+
37
+ const getCompanionId = (id: string) => {
38
+ const [_, companionId] = id.split(ATTENDABLE_PATH_SEPARATOR);
39
+ return companionId ?? 'never';
40
+ };
41
+
42
+ type DeckCompanion = Node<
43
+ any,
44
+ {
45
+ label: Label;
46
+ icon: string;
47
+ // TODO(burdon): Scroll area should be controlled by surface.
48
+ /** If true, the panel will not be wrapped in a scroll area. */
49
+ fixed?: boolean;
50
+ position?: Position;
51
+ }
52
+ >;
53
+
54
+ const useDeckCompanions = (): DeckCompanion[] => {
55
+ const { graph } = useAppGraph();
56
+ const companions = graph.nodes(graph.root, { type: DECK_COMPANION_TYPE }) as DeckCompanion[];
57
+ return companions.toSorted((a, b) => byPosition(a.properties, b.properties));
58
+ };
44
59
 
45
60
  export type ComplementarySidebarProps = {
46
61
  current?: string;
47
62
  };
48
63
 
49
- const label = ['complementary sidebar title', { ns: DECK_PLUGIN }] satisfies Label;
50
-
51
64
  export const ComplementarySidebar = ({ current }: ComplementarySidebarProps) => {
52
65
  const { t } = useTranslation(DECK_PLUGIN);
53
66
  const { dispatchPromise: dispatch } = useIntentDispatcher();
54
67
  const layout = useCapability(DeckCapabilities.MutableDeckState);
55
- const attended = useAttended();
56
- const { graph } = useAppGraph();
57
- const node = useNode(graph, attended[0]);
58
68
  const breakpoint = useBreakpoints();
59
69
  const topbar = layoutAppliesTopbar(breakpoint);
60
70
  const hoistStatusbar = useHoistStatusbar(breakpoint);
61
71
 
62
- const panels = useCapabilities(DeckCapabilities.ComplementaryPanel);
63
- const availablePanels = panels
64
- .filter((panel) => {
65
- if (!node || !panel.filter) {
66
- return true;
67
- }
68
-
69
- return panel.filter(node);
70
- })
71
- .toSorted(byPosition);
72
- const activePanelId = availablePanels.find((panel) => panel.id === current)?.id ?? availablePanels[0]?.id;
73
- const [internalValue, setInternalValue] = useState(activePanelId);
72
+ const companions = useDeckCompanions();
73
+ const activeCompanion = companions.find((companion) => getCompanionId(companion.id) === current) ?? companions.at(0);
74
+ const activeId = getCompanionId(activeCompanion?.id ?? 'never');
75
+ const [internalValue, setInternalValue] = useState(activeId);
74
76
 
75
77
  useEffect(() => {
76
- setInternalValue(activePanelId);
77
- }, [activePanelId]);
78
+ setInternalValue(activeId);
79
+ }, [activeId]);
78
80
 
79
81
  const handleTabClick = useCallback(
80
82
  (event: MouseEvent) => {
81
83
  const nextValue = event.currentTarget.getAttribute('data-value') as string;
82
- if (nextValue === activePanelId) {
84
+ if (nextValue === activeId) {
83
85
  layout.complementarySidebarState = layout.complementarySidebarState === 'expanded' ? 'collapsed' : 'expanded';
84
86
  } else {
85
87
  setInternalValue(nextValue);
@@ -87,21 +89,18 @@ export const ComplementarySidebar = ({ current }: ComplementarySidebarProps) =>
87
89
  void dispatch(createIntent(LayoutAction.UpdateComplementary, { part: 'complementary', subject: nextValue }));
88
90
  }
89
91
  },
90
- [layout, activePanelId, dispatch],
92
+ [layout, activeId, dispatch],
91
93
  );
92
94
 
93
95
  const data = useMemo(
94
96
  () =>
95
- node && {
96
- id: node.id,
97
- subject: node.data,
98
- workspace: layout.activeDeck,
99
- popoverAnchorId: layout.popoverAnchorId,
97
+ activeCompanion && {
98
+ id: activeCompanion.id,
99
+ subject: activeCompanion.data,
100
100
  },
101
- [node, layout.popoverAnchorId],
101
+ [activeCompanion?.id, activeCompanion?.data],
102
102
  );
103
103
 
104
- // TODO(burdon): Scroll area should be controlled by surface.
105
104
  return (
106
105
  <Main.ComplementarySidebar
107
106
  label={label}
@@ -110,29 +109,23 @@ export const ComplementarySidebar = ({ current }: ComplementarySidebarProps) =>
110
109
  hoistStatusbar && 'block-end-[--statusbar-size]',
111
110
  ]}
112
111
  >
113
- <Tabs.Root
114
- orientation='vertical'
115
- verticalVariant='stateless'
116
- value={internalValue}
117
- attendableId={attended[0]}
118
- classNames='contents'
119
- >
112
+ <Tabs.Root orientation='vertical' verticalVariant='stateless' value={internalValue} classNames='contents'>
120
113
  <div
121
114
  role='none'
122
115
  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-separator grid grid-cols-1 grid-rows-[1fr_min-content] bg-baseSurface contain-layout app-drag'
123
116
  >
124
117
  <Tabs.Tablist classNames='grid grid-cols-1 auto-rows-[--rail-action] p-1 gap-1 !overflow-y-auto'>
125
- {availablePanels.map((panel) => (
126
- <Tabs.Tab key={panel.id} value={panel.id} asChild>
118
+ {companions.map((companion) => (
119
+ <Tabs.Tab key={getCompanionId(companion.id)} value={getCompanionId(companion.id)} asChild>
127
120
  <IconButton
128
- label={toLocalizedString(panel.label, t)}
129
- icon={panel.icon}
121
+ label={toLocalizedString(companion.properties.label, t)}
122
+ icon={companion.properties.icon}
130
123
  size={5}
131
124
  iconOnly
132
125
  tooltipSide='left'
133
- data-value={panel.id}
126
+ data-value={getCompanionId(companion.id)}
134
127
  variant={
135
- activePanelId === panel.id
128
+ activeId === getCompanionId(companion.id)
136
129
  ? layout.complementarySidebarState === 'expanded'
137
130
  ? 'primary'
138
131
  : 'default'
@@ -152,16 +145,16 @@ export const ComplementarySidebar = ({ current }: ComplementarySidebarProps) =>
152
145
  <ToggleComplementarySidebarButton />
153
146
  </div>
154
147
  </div>
155
- {availablePanels.map((panel) => (
148
+ {companions.map((companion) => (
156
149
  <Tabs.Tabpanel
157
- key={panel.id}
158
- value={panel.id}
150
+ key={getCompanionId(companion.id)}
151
+ value={getCompanionId(companion.id)}
159
152
  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)]'
160
153
  {...(layout.complementarySidebarState !== 'expanded' && { inert: 'true' })}
161
154
  >
162
155
  <ComplementarySidebarPanel
163
- panel={panel}
164
- activePanelId={activePanelId}
156
+ companion={companion}
157
+ activeId={activeId}
165
158
  data={data}
166
159
  hoistStatusbar={hoistStatusbar}
167
160
  />
@@ -173,45 +166,36 @@ export const ComplementarySidebar = ({ current }: ComplementarySidebarProps) =>
173
166
  };
174
167
 
175
168
  type ComplementarySidebarPanelProps = {
176
- panel: Panel;
177
- activePanelId: string;
169
+ companion: DeckCompanion;
170
+ activeId: string;
178
171
  data?: {
179
172
  id: string;
180
173
  subject: any;
181
- workspace: string;
182
- popoverAnchorId?: string;
183
174
  };
184
175
  hoistStatusbar: boolean;
185
176
  };
186
177
 
187
178
  const ScrollArea = ({ children }: PropsWithChildren) => {
188
- return (
189
- <NaturalScrollArea.Root>
190
- <NaturalScrollArea.Viewport>{children}</NaturalScrollArea.Viewport>
191
- <NaturalScrollArea.Scrollbar orientation='vertical'>
192
- <NaturalScrollArea.Thumb />
193
- </NaturalScrollArea.Scrollbar>
194
- </NaturalScrollArea.Root>
195
- );
179
+ return <div className='flex flex-col grow overflow-x-hidden overflow-y-auto scrollbar-thin'>{children}</div>;
196
180
  };
197
181
 
198
- const ComplementarySidebarPanel = ({ panel, activePanelId, data, hoistStatusbar }: ComplementarySidebarPanelProps) => {
182
+ const ComplementarySidebarPanel = ({ companion, activeId, data, hoistStatusbar }: ComplementarySidebarPanelProps) => {
199
183
  const { t } = useTranslation(DECK_PLUGIN);
200
184
 
201
- if (panel.id !== activePanelId || !data) {
185
+ if (getCompanionId(companion.id) !== activeId && !data) {
202
186
  return null;
203
187
  }
204
188
 
205
- const Wrapper = panel.fixed ? Fragment : ScrollArea;
189
+ const Wrapper = companion.properties.fixed ? Fragment : ScrollArea;
206
190
 
207
191
  return (
208
192
  <>
209
193
  <h2 className='flex items-center pli-2 border-separator border-be font-medium'>
210
- {toLocalizedString(panel.label, t)}
194
+ {toLocalizedString(companion.properties.label, t)}
211
195
  </h2>
212
196
  <Wrapper>
213
197
  <Surface
214
- role={`complementary--${activePanelId}`}
198
+ role={`deck-companion--${getCompanionId(companion.id)}`}
215
199
  data={data}
216
200
  fallback={PlankContentError}
217
201
  placeholder={<PlankLoading />}
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './ComplementarySidebar';
6
+ export * from './Sidebar';
7
+ export * from './SidebarButton';
@@ -3,4 +3,4 @@
3
3
  //
4
4
 
5
5
  export * from './DeckLayout';
6
- export * from './LayoutSettings';
6
+ export * from './DeckSettings';
package/src/events.ts CHANGED
@@ -7,6 +7,5 @@ import { Events } from '@dxos/app-framework';
7
7
  import { DECK_PLUGIN } from './meta';
8
8
 
9
9
  export namespace DeckEvents {
10
- export const SetupComplementaryPanels = Events.createStateEvent(`${DECK_PLUGIN}/setup-complementary-panels`);
11
10
  export const StateReady = Events.createStateEvent(`${DECK_PLUGIN}/state-ready`);
12
11
  }
@@ -2,6 +2,5 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- export * from './useNode';
6
5
  export * from './useNodeActionExpander';
7
6
  export * from './useMainSize';
package/src/index.ts CHANGED
@@ -6,3 +6,4 @@ export { DeckCapabilities } from './capabilities';
6
6
  export { DeckEvents } from './events';
7
7
  export * from './DeckPlugin';
8
8
  export * from './meta';
9
+ export { useCompanions } from './util';
package/src/layout.ts CHANGED
@@ -4,12 +4,29 @@
4
4
 
5
5
  import { produce } from 'immer';
6
6
 
7
+ import { ATTENDABLE_PATH_SEPARATOR } from '@dxos/react-ui-attention';
8
+
7
9
  import { type DeckAction, type NewPlankPositioning } from './types';
8
10
 
9
- type OpenLayoutEntryOptions = { key?: string; positioning?: NewPlankPositioning; pivotId?: string };
11
+ export const createEntryId = (entryId: string, variant?: string) =>
12
+ variant ? `${entryId}${ATTENDABLE_PATH_SEPARATOR}${variant}` : entryId;
13
+
14
+ export const parseEntryId = (entryId: string) => {
15
+ const [id, variant] = entryId.split(ATTENDABLE_PATH_SEPARATOR);
16
+ return { id, variant };
17
+ };
18
+
19
+ type OpenLayoutEntryOptions = {
20
+ key?: string;
21
+ positioning?: NewPlankPositioning;
22
+ pivotId?: string;
23
+ variant?: string;
24
+ };
10
25
 
11
- export const openEntry = (deck: string[], entryId: string, options?: OpenLayoutEntryOptions): string[] => {
26
+ export const openEntry = (deck: string[], _entryId: string, options?: OpenLayoutEntryOptions): string[] => {
12
27
  return produce(deck, (draft) => {
28
+ const entryId = createEntryId(_entryId, options?.variant);
29
+
13
30
  // Check that the entry is not already in the part
14
31
  if (draft.find((id) => id === entryId)) {
15
32
  return;
package/src/meta.ts CHANGED
@@ -8,6 +8,6 @@ export const DECK_PLUGIN = 'dxos.org/plugin/deck' as const;
8
8
 
9
9
  export const meta: PluginMeta = {
10
10
  id: DECK_PLUGIN,
11
- name: 'Deck',
12
- icon: 'ph--columns--regular',
11
+ name: 'Layout',
12
+ icon: 'ph--layout--regular',
13
13
  };
@@ -47,11 +47,14 @@ export default [
47
47
  'show solo plank label': 'Maximize',
48
48
  'close label': 'Close',
49
49
  'minify label': 'Minify',
50
+ 'open companion label': 'Open companion',
51
+ 'close companion label': 'Close companion',
50
52
  'settings overscroll label': 'Plank scrolling',
51
53
  'select overscroll placeholder': 'Select plank scrolling behavior',
52
54
  'settings overscroll centering label': 'Centering',
53
55
  'settings overscroll none label': 'None',
54
56
  'settings enable statusbar label': 'Show status bar',
57
+ 'settings enable deck label': 'Enable Deck',
55
58
  'close current label': 'Close current plank',
56
59
  'close others label': 'Close other planks',
57
60
  'close all label': 'Close all planks',