@dxos/plugin-deck 0.6.14-staging.8758a12 → 0.6.14-staging.9b873ce

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 (32) hide show
  1. package/dist/lib/browser/index.mjs +354 -314
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/DeckPlugin.d.ts.map +1 -1
  5. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts +1 -2
  6. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts.map +1 -1
  7. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts +1 -7
  8. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
  9. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts +3 -4
  10. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts.map +1 -1
  11. package/dist/types/src/components/DeckLayout/Plank.d.ts +3 -3
  12. package/dist/types/src/components/DeckLayout/Plank.d.ts.map +1 -1
  13. package/dist/types/src/components/DeckLayout/PlankControls.d.ts +19 -0
  14. package/dist/types/src/components/DeckLayout/PlankControls.d.ts.map +1 -0
  15. package/dist/types/src/components/DeckLayout/PlankError.d.ts +1 -2
  16. package/dist/types/src/components/DeckLayout/PlankError.d.ts.map +1 -1
  17. package/dist/types/src/translations.d.ts +7 -0
  18. package/dist/types/src/translations.d.ts.map +1 -1
  19. package/dist/types/src/util/overscroll.d.ts +1 -2
  20. package/dist/types/src/util/overscroll.d.ts.map +1 -1
  21. package/package.json +27 -27
  22. package/src/DeckPlugin.tsx +5 -33
  23. package/src/components/DeckLayout/ComplementarySidebar.tsx +18 -23
  24. package/src/components/DeckLayout/DeckLayout.tsx +139 -136
  25. package/src/components/DeckLayout/NodePlankHeading.tsx +30 -17
  26. package/src/components/DeckLayout/Plank.tsx +50 -91
  27. package/src/components/DeckLayout/PlankControls.tsx +133 -0
  28. package/src/components/DeckLayout/PlankError.tsx +6 -8
  29. package/src/components/DeckLayout/PlankLoading.tsx +1 -1
  30. package/src/components/DeckLayout/Toast.tsx +1 -1
  31. package/src/translations.ts +11 -3
  32. package/src/util/overscroll.ts +9 -30
@@ -14,7 +14,7 @@ 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';
@@ -27,10 +27,9 @@ import { useLayout } from '../LayoutContext';
27
27
  export type ComplementarySidebarProps = {
28
28
  panels: Panel[];
29
29
  current?: string;
30
- flatDeck?: boolean;
31
30
  };
32
31
 
33
- export const ComplementarySidebar = ({ panels, current, flatDeck }: ComplementarySidebarProps) => {
32
+ export const ComplementarySidebar = ({ panels, current }: ComplementarySidebarProps) => {
34
33
  const { popoverAnchorId } = useLayout();
35
34
  const attended = useAttended();
36
35
  const panel = (panels.find((p) => p.id === current) ?? panels[0])?.id;
@@ -63,27 +62,23 @@ export const ComplementarySidebar = ({ panels, current, flatDeck }: Complementar
63
62
  // TODO(burdon): Debug panel doesn't change when switching even though id has chagned.
64
63
  return (
65
64
  <Main.ComplementarySidebar>
66
- <div role='none' className={mx(deckGrid, 'grid-cols-1 bs-full')}>
67
- <NodePlankHeading
68
- coordinate={coordinate}
69
- node={node}
70
- popoverAnchorId={popoverAnchorId}
71
- flatDeck={flatDeck}
72
- actions={actions}
73
- />
74
- <div className='row-span-2 divide-y divide-separator overflow-x-hidden overflow-y-scroll'>
75
- {node && (
76
- <Surface
77
- key={id}
78
- role={`complementary--${panel}`}
79
- limit={1}
80
- data={{ id, subject: node.properties.object ?? node.properties.space, popoverAnchorId }}
81
- fallback={PlankContentError}
82
- placeholder={<PlankLoading />}
83
- />
84
- )}
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>
85
80
  </div>
86
- </div>
81
+ </StackContext.Provider>
87
82
  </Main.ComplementarySidebar>
88
83
  );
89
84
  };
@@ -4,12 +4,12 @@
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';
@@ -29,25 +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'>;
40
36
 
41
- export const DeckLayout = ({
42
- layoutParts,
43
- toasts,
44
- flatDeck,
45
- overscroll,
46
- showHints,
47
- slots,
48
- panels,
49
- onDismissToast,
50
- }: DeckLayoutProps) => {
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;
39
+
40
+ export const DeckLayout = ({ layoutParts, toasts, overscroll, showHints, panels, onDismissToast }: DeckLayoutProps) => {
51
41
  const context = useLayout();
52
42
  const {
53
43
  layoutMode,
@@ -64,16 +54,17 @@ export const DeckLayout = ({
64
54
  const { plankSizing } = useDeckContext();
65
55
  // NOTE: Not `useAttended` so that the layout component is not re-rendered when the attended list changes.
66
56
  const attentionPlugin = usePlugin<AttentionPluginProvides>('dxos.org/plugin/attention');
67
- const searchPlugin = usePlugin('dxos.org/plugin/search');
68
57
  const fullScreenSlug = useMemo(() => firstIdInPart(layoutParts, 'fullScreen'), [layoutParts]);
69
58
 
70
59
  const scrollLeftRef = useRef<number | null>();
71
60
  const deckRef = useRef<HTMLDivElement>(null);
72
61
 
62
+ const isSoloModeLoaded = layoutMode === 'solo' && Boolean(layoutParts.solo?.[0]);
63
+
73
64
  // Ensure the first plank is attended when the deck is first rendered.
74
65
  useEffect(() => {
75
66
  const attended = untracked(() => attentionPlugin?.provides.attention.attended ?? []);
76
- const firstId = layoutMode === 'solo' ? firstIdInPart(layoutParts, 'solo') : firstIdInPart(layoutParts, 'main');
67
+ const firstId = isSoloModeLoaded ? firstIdInPart(layoutParts, 'solo') : firstIdInPart(layoutParts, 'main');
77
68
  if (attended.length === 0 && firstId) {
78
69
  // TODO(wittjosiah): Focusing the type button is a workaround.
79
70
  // If the plank is directly focused on first load the focus ring appears.
@@ -113,18 +104,14 @@ export const DeckLayout = ({
113
104
  [layoutMode],
114
105
  );
115
106
 
116
- const isEmpty = layoutParts.main?.length === 0 && layoutParts.solo?.length === 0;
107
+ const isEmpty = (layoutParts.main?.length ?? 0) === 0 && (layoutParts.solo?.length ?? 0) === 0;
117
108
 
118
109
  const padding = useMemo(() => {
119
110
  if (layoutMode === 'deck' && overscroll === 'centering') {
120
- return calculateOverscroll(layoutParts.main, plankSizing, sidebarOpen, complementarySidebarOpen);
111
+ return calculateOverscroll(layoutParts.main?.length ?? 0);
121
112
  }
122
113
  return {};
123
- }, [layoutMode, overscroll, layoutParts.main, plankSizing, sidebarOpen, complementarySidebarOpen]);
124
-
125
- if (layoutMode === 'fullscreen') {
126
- return <Fullscreen id={fullScreenSlug} />;
127
- }
114
+ }, [layoutMode, overscroll, layoutParts.main]);
128
115
 
129
116
  return (
130
117
  <Popover.Root
@@ -141,127 +128,143 @@ export const DeckLayout = ({
141
128
  >
142
129
  <ActiveNode />
143
130
 
144
- <Main.Root
145
- navigationSidebarOpen={context.sidebarOpen}
146
- onNavigationSidebarOpenChange={(next) => (context.sidebarOpen = next)}
147
- complementarySidebarOpen={context.complementarySidebarOpen}
148
- onComplementarySidebarOpenChange={(next) => (context.complementarySidebarOpen = next)}
149
- >
150
- {/* Notch */}
151
- <Main.Notch classNames='z-[21]'>
152
- <Surface role='notch-start' />
153
- <Button onClick={() => (context.sidebarOpen = !context.sidebarOpen)} variant='ghost' classNames='p-1'>
154
- <span className='sr-only'>{t('open navigation sidebar label')}</span>
155
- <MenuIcon weight='light' className={getSize(5)} />
156
- </Button>
157
- <Button
158
- onClick={() => (context.complementarySidebarOpen = !context.complementarySidebarOpen)}
159
- variant='ghost'
160
- classNames='p-1'
161
- >
162
- <span className='sr-only'>{t('open complementary sidebar label')}</span>
163
- <MenuIcon mirrored weight='light' className={getSize(5)} />
164
- </Button>
165
- <Surface role='notch-end' />
166
- </Main.Notch>
131
+ {layoutMode === 'fullscreen' && <Fullscreen id={fullScreenSlug} />}
132
+
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>
167
149
 
168
- {/* Left sidebar. */}
169
- <Sidebar layoutParts={layoutParts} />
150
+ {/* Left sidebar. */}
151
+ <Sidebar layoutParts={layoutParts} />
170
152
 
171
- {/* Right sidebar. */}
172
- <ComplementarySidebar panels={panels} current={layoutParts.complementary?.[0].id} flatDeck={flatDeck} />
153
+ {/* Right sidebar. */}
154
+ <ComplementarySidebar panels={panels} current={layoutParts.complementary?.[0].id} />
173
155
 
174
- {/* Dialog overlay to dismiss dialogs. */}
175
- <Main.Overlay />
156
+ {/* Dialog overlay to dismiss dialogs. */}
157
+ <Main.Overlay />
176
158
 
177
- {/* No content. */}
178
- {isEmpty && (
179
- <Main.Content handlesFocus>
180
- <ContentEmpty />
181
- </Main.Content>
182
- )}
159
+ {/* No content. */}
160
+ {isEmpty && (
161
+ <Main.Content handlesFocus>
162
+ <ContentEmpty />
163
+ </Main.Content>
164
+ )}
183
165
 
184
- {/* Solo/deck mode. */}
185
- {!isEmpty && (
186
- <Main.Content bounce classNames='grid block-end-[--statusbar-size]' handlesFocus>
187
- <div role='none' className='relative'>
188
- <Deck.Root
189
- style={padding}
190
- classNames={[
191
- !flatDeck && 'bg-deck',
192
- mainPaddingTransitions,
193
- 'absolute inset-0',
194
- slots?.wallpaper?.classNames,
195
- ]}
196
- solo={layoutMode === 'solo'}
197
- onScroll={handleScroll}
198
- ref={deckRef}
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: '' })}
187
+ >
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: '' })}
199
216
  >
200
- <Plank
201
- entry={layoutParts.solo?.[0]}
202
- layoutParts={layoutParts}
203
- part='solo'
204
- layoutMode={layoutMode}
205
- flatDeck={flatDeck}
206
- searchEnabled={!!searchPlugin}
207
- />
208
- {layoutParts.main?.map((layoutEntry) => (
209
- <Plank
210
- key={layoutEntry.id}
211
- entry={layoutEntry}
212
- layoutParts={layoutParts}
213
- part='main'
214
- layoutMode={layoutMode}
215
- flatDeck={flatDeck}
216
- searchEnabled={!!searchPlugin}
217
- />
218
- ))}
219
- </Deck.Root>
220
- </div>
221
- </Main.Content>
222
- )}
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
+ )}
223
225
 
224
- {/* Footer status. */}
225
- <StatusBar showHints={showHints} />
226
+ {/* Footer status. */}
227
+ <StatusBar showHints={showHints} />
228
+ </Main.Root>
229
+ )}
226
230
 
227
- {/* Global popovers. */}
228
- <Popover.Portal>
229
- <Popover.Content
230
- classNames='z-[60]'
231
- onEscapeKeyDown={() => {
232
- context.popoverOpen = false;
233
- context.popoverAnchorId = undefined;
234
- }}
235
- >
236
- <Popover.Viewport>
237
- <Surface role='popover' data={popoverContent} />
238
- </Popover.Viewport>
239
- <Popover.Arrow />
240
- </Popover.Content>
241
- </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>
242
246
 
243
- {/* Global dialog. */}
244
- <Dialog.Root open={dialogOpen} onOpenChange={(nextOpen) => (context.dialogOpen = nextOpen)}>
245
- <Dialog.Overlay blockAlign={dialogBlockAlign}>
246
- <Surface role='dialog' data={dialogContent} />
247
- </Dialog.Overlay>
248
- </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>
249
253
 
250
- {/* Global toasts. */}
251
- {toasts?.map((toast) => (
252
- <Toast
253
- {...toast}
254
- key={toast.id}
255
- onOpenChange={(open) => {
256
- if (!open) {
257
- onDismissToast(toast.id);
258
- }
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
+ }
259
263
 
260
- return open;
261
- }}
262
- />
263
- ))}
264
- </Main.Root>
264
+ return open;
265
+ }}
266
+ />
267
+ ))}
265
268
  </Popover.Root>
266
269
  );
267
270
  };
@@ -13,11 +13,13 @@ import {
13
13
  type LayoutCoordinate,
14
14
  } from '@dxos/app-framework';
15
15
  import { type Node, useGraph } from '@dxos/plugin-graph';
16
- import { Icon, Popover, toLocalizedString, useMediaQuery, useTranslation } from '@dxos/react-ui';
17
- import { PlankHeading, type PlankHeadingAction } from '@dxos/react-ui-deck';
16
+ import { Icon, Popover, toLocalizedString, useMediaQuery, useTranslation, IconButton } from '@dxos/react-ui';
17
+ import { StackItem, type StackItemSigilAction } from '@dxos/react-ui-stack';
18
18
  import { TextTooltip } from '@dxos/react-ui-text-tooltip';
19
19
 
20
+ import { PlankControls } from './PlankControls';
20
21
  import { DECK_PLUGIN } from '../../meta';
22
+ import { useLayout } from '../LayoutContext';
21
23
 
22
24
  export type NodePlankHeadingProps = {
23
25
  coordinate: LayoutCoordinate;
@@ -26,8 +28,7 @@ export type NodePlankHeadingProps = {
26
28
  canIncrementEnd?: boolean;
27
29
  popoverAnchorId?: string;
28
30
  pending?: boolean;
29
- flatDeck?: boolean;
30
- actions?: PlankHeadingAction[];
31
+ actions?: StackItemSigilAction[];
31
32
  };
32
33
 
33
34
  export const NodePlankHeading = memo(
@@ -38,9 +39,9 @@ export const NodePlankHeading = memo(
38
39
  canIncrementEnd,
39
40
  popoverAnchorId,
40
41
  pending,
41
- flatDeck,
42
42
  actions = [],
43
43
  }: NodePlankHeadingProps) => {
44
+ const layoutContext = useLayout();
44
45
  const { t } = useTranslation(DECK_PLUGIN);
45
46
  const { graph } = useGraph();
46
47
  const icon = node?.properties?.icon ?? 'ph--placeholder--regular';
@@ -73,12 +74,10 @@ export const NodePlankHeading = memo(
73
74
  );
74
75
 
75
76
  return (
76
- <PlankHeading.Root
77
- {...((layoutPart !== 'main' || !flatDeck) && { classNames: 'pie-1 border-b border-separator' })}
78
- >
77
+ <StackItem.Heading classNames='pie-1'>
79
78
  <ActionRoot>
80
79
  {node ? (
81
- <PlankHeading.ActionsMenu
80
+ <StackItem.Sigil
82
81
  icon={icon}
83
82
  related={layoutPart === 'complementary'}
84
83
  attendableId={attendableId}
@@ -89,31 +88,32 @@ export const NodePlankHeading = memo(
89
88
  }
90
89
  >
91
90
  <Surface role='menu-footer' data={{ object: node.data }} />
92
- </PlankHeading.ActionsMenu>
91
+ </StackItem.Sigil>
93
92
  ) : (
94
- <PlankHeading.Button>
93
+ <StackItem.SigilButton>
95
94
  <span className='sr-only'>{label}</span>
96
95
  <Icon icon={icon} size={5} />
97
- </PlankHeading.Button>
96
+ </StackItem.SigilButton>
98
97
  )}
99
98
  </ActionRoot>
100
99
  <TextTooltip text={label} onlyWhenTruncating>
101
- <PlankHeading.Label
100
+ <StackItem.HeadingLabel
102
101
  attendableId={attendableId}
103
102
  related={layoutPart === 'complementary'}
104
103
  {...(pending && { classNames: 'text-description' })}
105
104
  >
106
105
  {label}
107
- </PlankHeading.Label>
106
+ </StackItem.HeadingLabel>
108
107
  </TextTooltip>
109
108
  {node && layoutPart !== 'complementary' && (
110
109
  // TODO(Zan): What are we doing with layout coordinate here?
111
110
  <Surface role='navbar-end' direction='inline-reverse' data={{ object: node.data }} />
112
111
  )}
113
112
  {/* NOTE(thure): Pinning & unpinning are temporarily disabled */}
114
- <PlankHeading.Controls
113
+ <PlankControls
115
114
  capabilities={capabilities}
116
115
  isSolo={layoutPart === 'solo'}
116
+ classNames='mx-1'
117
117
  onClick={(eventType) => {
118
118
  if (!layoutPart) {
119
119
  return;
@@ -154,8 +154,21 @@ export const NodePlankHeading = memo(
154
154
  }
155
155
  }}
156
156
  close={layoutPart === 'complementary' ? 'minify-end' : true}
157
- />
158
- </PlankHeading.Root>
157
+ >
158
+ {/* TODO(wittjosiah): This doesn't behave exactly the same as the rest of the button group. */}
159
+ {layoutPart !== 'complementary' && (
160
+ <IconButton
161
+ iconOnly
162
+ onClick={() => (layoutContext.complementarySidebarOpen = !layoutContext.complementarySidebarOpen)}
163
+ variant='ghost'
164
+ label={t('open complementary sidebar label')}
165
+ classNames='!p-1 -scale-x-100'
166
+ icon='ph--sidebar--regular'
167
+ tooltipZIndex='70'
168
+ />
169
+ )}
170
+ </PlankControls>
171
+ </StackItem.Heading>
159
172
  );
160
173
  },
161
174
  );