@dxos/react-ui 0.7.4 → 0.7.5-labs.071a3e2

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 (101) hide show
  1. package/dist/lib/browser/index.mjs +510 -347
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +766 -614
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/lib/node-esm/index.mjs +510 -347
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/types/src/components/Avatars/Avatar.d.ts +5 -9
  11. package/dist/types/src/components/Avatars/Avatar.d.ts.map +1 -1
  12. package/dist/types/src/components/Avatars/Avatar.stories.d.ts +1 -2
  13. package/dist/types/src/components/Avatars/Avatar.stories.d.ts.map +1 -1
  14. package/dist/types/src/components/Buttons/IconButton.d.ts +6 -2
  15. package/dist/types/src/components/Buttons/IconButton.d.ts.map +1 -1
  16. package/dist/types/src/components/Dialogs/AlertDialog.d.ts.map +1 -1
  17. package/dist/types/src/components/Dialogs/Dialog.d.ts.map +1 -1
  18. package/dist/types/src/components/Input/Input.d.ts +5 -6
  19. package/dist/types/src/components/Input/Input.d.ts.map +1 -1
  20. package/dist/types/src/components/Input/Input.stories.d.ts +1 -3
  21. package/dist/types/src/components/Input/Input.stories.d.ts.map +1 -1
  22. package/dist/types/src/components/Lists/List.d.ts +2 -0
  23. package/dist/types/src/components/Lists/List.d.ts.map +1 -1
  24. package/dist/types/src/components/Lists/ListDropIndicator.d.ts +13 -0
  25. package/dist/types/src/components/Lists/ListDropIndicator.d.ts.map +1 -0
  26. package/dist/types/src/components/Lists/Tree.d.ts +2 -0
  27. package/dist/types/src/components/Lists/Tree.d.ts.map +1 -1
  28. package/dist/types/src/components/Lists/TreeDropIndicator.d.ts +8 -0
  29. package/dist/types/src/components/Lists/TreeDropIndicator.d.ts.map +1 -0
  30. package/dist/types/src/components/Main/Main.d.ts +35 -24
  31. package/dist/types/src/components/Main/Main.d.ts.map +1 -1
  32. package/dist/types/src/components/Main/Main.stories.d.ts +1 -1
  33. package/dist/types/src/components/Menus/ContextMenu.d.ts.map +1 -1
  34. package/dist/types/src/components/Menus/DropdownMenu.d.ts +2 -6
  35. package/dist/types/src/components/Menus/DropdownMenu.d.ts.map +1 -1
  36. package/dist/types/src/components/Popover/Popover.d.ts.map +1 -1
  37. package/dist/types/src/components/Select/Select.d.ts.map +1 -1
  38. package/dist/types/src/components/Separator/Separator.d.ts +3 -1
  39. package/dist/types/src/components/Separator/Separator.d.ts.map +1 -1
  40. package/dist/types/src/components/Tag/Tag.d.ts.map +1 -1
  41. package/dist/types/src/components/Tag/Tag.stories.d.ts +12 -5
  42. package/dist/types/src/components/Tag/Tag.stories.d.ts.map +1 -1
  43. package/dist/types/src/components/ThemeProvider/ThemeProvider.d.ts +4 -2
  44. package/dist/types/src/components/ThemeProvider/ThemeProvider.d.ts.map +1 -1
  45. package/dist/types/src/components/ThemeProvider/TranslationsProvider.d.ts +1 -0
  46. package/dist/types/src/components/ThemeProvider/TranslationsProvider.d.ts.map +1 -1
  47. package/dist/types/src/components/Toolbar/Toolbar.d.ts +15 -5
  48. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  49. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +7 -2
  50. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  51. package/dist/types/src/components/Tooltip/Tooltip.d.ts.map +1 -1
  52. package/dist/types/src/components/Tooltip/Tooltip.stories.d.ts +13 -1
  53. package/dist/types/src/components/Tooltip/Tooltip.stories.d.ts.map +1 -1
  54. package/dist/types/src/hooks/index.d.ts +1 -0
  55. package/dist/types/src/hooks/index.d.ts.map +1 -1
  56. package/dist/types/src/hooks/useSafeArea.d.ts +9 -0
  57. package/dist/types/src/hooks/useSafeArea.d.ts.map +1 -0
  58. package/dist/types/src/hooks/useSafeCollisionPadding.d.ts +10 -0
  59. package/dist/types/src/hooks/useSafeCollisionPadding.d.ts.map +1 -0
  60. package/dist/types/src/hooks/useVisualViewport.d.ts +1 -1
  61. package/dist/types/src/hooks/useVisualViewport.d.ts.map +1 -1
  62. package/dist/types/src/util/ThemedClassName.d.ts +1 -1
  63. package/dist/types/src/util/ThemedClassName.d.ts.map +1 -1
  64. package/dist/types/tsconfig.tsbuildinfo +1 -0
  65. package/package.json +43 -42
  66. package/src/components/Avatars/Avatar.tsx +3 -6
  67. package/src/components/Buttons/IconButton.tsx +25 -7
  68. package/src/components/Clipboard/CopyButton.tsx +1 -1
  69. package/src/components/Dialogs/AlertDialog.tsx +6 -2
  70. package/src/components/Dialogs/Dialog.tsx +7 -11
  71. package/src/components/Input/Input.stories.tsx +4 -6
  72. package/src/components/Input/Input.tsx +29 -44
  73. package/src/components/Lists/List.stories.tsx +2 -2
  74. package/src/components/Lists/List.tsx +3 -0
  75. package/src/components/Lists/ListDropIndicator.tsx +70 -0
  76. package/src/components/Lists/Tree.tsx +3 -0
  77. package/src/components/Lists/TreeDropIndicator.tsx +70 -0
  78. package/src/components/Main/Main.stories.tsx +1 -1
  79. package/src/components/Main/Main.tsx +79 -110
  80. package/src/components/Menus/ContextMenu.tsx +8 -6
  81. package/src/components/Menus/DropdownMenu.tsx +7 -4
  82. package/src/components/Popover/Popover.tsx +8 -2
  83. package/src/components/ScrollArea/ScrollArea.stories.tsx +2 -2
  84. package/src/components/Select/Select.tsx +7 -3
  85. package/src/components/Separator/Separator.tsx +14 -11
  86. package/src/components/Tag/Tag.stories.tsx +20 -31
  87. package/src/components/Tag/Tag.tsx +15 -6
  88. package/src/components/ThemeProvider/ThemeProvider.tsx +13 -5
  89. package/src/components/Toast/Toast.tsx +1 -1
  90. package/src/components/Toolbar/Toolbar.tsx +40 -10
  91. package/src/components/Tooltip/Tooltip.stories.tsx +13 -2
  92. package/src/components/Tooltip/Tooltip.tsx +18 -13
  93. package/src/hooks/index.ts +1 -0
  94. package/src/hooks/useSafeArea.ts +25 -0
  95. package/src/hooks/useSafeCollisionPadding.ts +39 -0
  96. package/src/hooks/useVisualViewport.ts +11 -12
  97. package/src/testing/decorators/withVariants.tsx +4 -4
  98. package/src/util/ThemedClassName.ts +1 -1
  99. package/dist/types/src/playground/Surfaces.stories.d.ts +0 -21
  100. package/dist/types/src/playground/Surfaces.stories.d.ts.map +0 -1
  101. package/src/playground/Surfaces.stories.tsx +0 -73
@@ -0,0 +1,70 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
6
+ import React, { type HTMLAttributes, type CSSProperties } from 'react';
7
+
8
+ // Tree item hitbox
9
+ // https://github.com/atlassian/pragmatic-drag-and-drop/blob/main/packages/hitbox/constellation/index/about.mdx#tree-item
10
+
11
+ type InstructionType = Exclude<Instruction, { type: 'instruction-blocked' }>['type'];
12
+ type Orientation = 'sibling' | 'child';
13
+
14
+ const edgeToOrientationMap: Record<InstructionType, Orientation> = {
15
+ 'reorder-above': 'sibling',
16
+ 'reorder-below': 'sibling',
17
+ 'make-child': 'child',
18
+ reparent: 'child',
19
+ };
20
+
21
+ const orientationStyles: Record<Orientation, HTMLAttributes<HTMLElement>['className']> = {
22
+ // TODO(wittjosiah): Stop using left/right here.
23
+ sibling:
24
+ 'bs-[--line-thickness] left-[--horizontal-indent] right-0 bg-accentSurface before:left-[--negative-terminal-size]',
25
+ child: 'is-full block-start-0 block-end-0 border-[length:--line-thickness] before:invisible',
26
+ };
27
+
28
+ const instructionStyles: Record<InstructionType, HTMLAttributes<HTMLElement>['className']> = {
29
+ 'reorder-above': 'block-start-[--line-offset] before:block-start-[--offset-terminal]',
30
+ 'reorder-below': 'block-end-[--line-offset] before:block-end-[--offset-terminal]',
31
+ 'make-child': 'border-accentSurface',
32
+ // TODO(wittjosiah): This is not occurring in the current implementation.
33
+ reparent: '',
34
+ };
35
+
36
+ const strokeSize = 2;
37
+ const terminalSize = 8;
38
+ const offsetToAlignTerminalWithLine = (strokeSize - terminalSize) / 2;
39
+
40
+ export type DropIndicatorProps = {
41
+ instruction: Instruction;
42
+ gap?: number;
43
+ };
44
+
45
+ export const TreeDropIndicator = ({ instruction, gap = 0 }: DropIndicatorProps) => {
46
+ const lineOffset = `calc(-0.5 * (${gap}px + ${strokeSize}px))`;
47
+ const isBlocked = instruction.type === 'instruction-blocked';
48
+ const desiredInstruction = isBlocked ? instruction.desired : instruction;
49
+ const orientation = edgeToOrientationMap[desiredInstruction.type];
50
+ if (isBlocked) {
51
+ return null;
52
+ }
53
+
54
+ return (
55
+ <div
56
+ style={
57
+ {
58
+ '--line-thickness': `${strokeSize}px`,
59
+ '--line-offset': `${lineOffset}`,
60
+ '--terminal-size': `${terminalSize}px`,
61
+ '--terminal-radius': `${terminalSize / 2}px`,
62
+ '--negative-terminal-size': `-${terminalSize}px`,
63
+ '--offset-terminal': `${offsetToAlignTerminalWithLine}px`,
64
+ '--horizontal-indent': `${desiredInstruction.currentLevel * desiredInstruction.indentPerLevel + 4}px`,
65
+ } as CSSProperties
66
+ }
67
+ className={`absolute z-10 pointer-events-none before:is-[--terminal-size] before:bs-[--terminal-size] box-border before:absolute before:border-[length:--line-thickness] before:border-solid before:border-accentSurface before:rounded-full ${orientationStyles[orientation]} ${instructionStyles[desiredInstruction.type]}`}
68
+ ></div>
69
+ );
70
+ };
@@ -24,7 +24,7 @@ const ComplementarySidebarToggle = () => {
24
24
 
25
25
  const StoryMain = (_args: StoryMainArgs) => {
26
26
  return (
27
- <Main.Root defaultComplementarySidebarOpen>
27
+ <Main.Root>
28
28
  <Main.Overlay />
29
29
  <Main.NavigationSidebar classNames='p-4'>
30
30
  <p>Navigation sidebar content, hi!</p>
@@ -3,7 +3,6 @@
3
3
  //
4
4
 
5
5
  import { useFocusableGroup } from '@fluentui/react-tabster';
6
- import { useComposedRefs } from '@radix-ui/react-compose-refs';
7
6
  import { createContext } from '@radix-ui/react-context';
8
7
  import { Root as DialogRoot, DialogContent } from '@radix-ui/react-dialog';
9
8
  import { Primitive } from '@radix-ui/react-primitive';
@@ -29,7 +28,6 @@ import { useMediaQuery, useForwardedRef } from '@dxos/react-hooks';
29
28
  import { useSwipeToDismiss } from './useSwipeToDismiss';
30
29
  import { useThemeContext } from '../../hooks';
31
30
  import { type ThemedClassName } from '../../util';
32
- import { ElevationProvider } from '../ElevationProvider';
33
31
 
34
32
  const MAIN_ROOT_NAME = 'MainRoot';
35
33
  const NAVIGATION_SIDEBAR_NAME = 'NavigationSidebar';
@@ -37,12 +35,14 @@ const COMPLEMENTARY_SIDEBAR_NAME = 'ComplementarySidebar';
37
35
  const MAIN_NAME = 'Main';
38
36
  const GENERIC_CONSUMER_NAME = 'GenericConsumer';
39
37
 
38
+ type SidebarState = 'expanded' | 'collapsed' | 'closed';
39
+
40
40
  type MainContextValue = {
41
41
  resizing: boolean;
42
- navigationSidebarOpen: boolean;
43
- setNavigationSidebarOpen: Dispatch<SetStateAction<boolean | undefined>>;
44
- complementarySidebarOpen: boolean;
45
- setComplementarySidebarOpen: Dispatch<SetStateAction<boolean | undefined>>;
42
+ navigationSidebarState: SidebarState;
43
+ setNavigationSidebarState: Dispatch<SetStateAction<SidebarState | undefined>>;
44
+ complementarySidebarState: SidebarState;
45
+ setComplementarySidebarState: Dispatch<SetStateAction<SidebarState | undefined>>;
46
46
  };
47
47
 
48
48
  const landmarkAttr = 'data-main-landmark';
@@ -75,73 +75,77 @@ const useLandmarkMover = (propsOnKeyDown: ComponentPropsWithoutRef<'div'>['onKey
75
75
 
76
76
  const [MainProvider, useMainContext] = createContext<MainContextValue>(MAIN_NAME, {
77
77
  resizing: false,
78
- navigationSidebarOpen: false,
79
- setNavigationSidebarOpen: (nextOpen) => {
78
+ navigationSidebarState: 'closed',
79
+ setNavigationSidebarState: (nextState) => {
80
80
  // TODO(burdon): Standardize with other context missing errors using raise.
81
81
  log.warn('Attempt to set sidebar state without initializing `MainRoot`');
82
82
  },
83
- complementarySidebarOpen: false,
84
- setComplementarySidebarOpen: (nextOpen) => {
83
+ complementarySidebarState: 'closed',
84
+ setComplementarySidebarState: (nextState) => {
85
85
  // TODO(burdon): Standardize with other context missing errors using raise.
86
86
  log.warn('Attempt to set sidebar state without initializing `MainRoot`');
87
87
  },
88
88
  });
89
89
 
90
90
  const useSidebars = (consumerName = GENERIC_CONSUMER_NAME) => {
91
- const { setNavigationSidebarOpen, navigationSidebarOpen, setComplementarySidebarOpen, complementarySidebarOpen } =
91
+ const { setNavigationSidebarState, navigationSidebarState, setComplementarySidebarState, complementarySidebarState } =
92
92
  useMainContext(consumerName);
93
93
  return {
94
- navigationSidebarOpen,
95
- setNavigationSidebarOpen,
94
+ navigationSidebarState,
95
+ setNavigationSidebarState,
96
96
  toggleNavigationSidebar: useCallback(
97
- () => setNavigationSidebarOpen(!navigationSidebarOpen),
98
- [navigationSidebarOpen, setNavigationSidebarOpen],
97
+ () => setNavigationSidebarState(navigationSidebarState === 'expanded' ? 'closed' : 'expanded'),
98
+ [navigationSidebarState, setNavigationSidebarState],
99
99
  ),
100
- openNavigationSidebar: useCallback(() => setNavigationSidebarOpen(true), [setNavigationSidebarOpen]),
101
- closeNavigationSidebar: useCallback(() => setNavigationSidebarOpen(false), [setNavigationSidebarOpen]),
102
- complementarySidebarOpen,
103
- setComplementarySidebarOpen,
100
+ openNavigationSidebar: useCallback(() => setNavigationSidebarState('expanded'), []),
101
+ collapseNavigationSidebar: useCallback(() => setNavigationSidebarState('collapsed'), []),
102
+ closeNavigationSidebar: useCallback(() => setNavigationSidebarState('closed'), []),
103
+ complementarySidebarState,
104
+ setComplementarySidebarState,
104
105
  toggleComplementarySidebar: useCallback(
105
- () => setComplementarySidebarOpen(!complementarySidebarOpen),
106
- [complementarySidebarOpen, setComplementarySidebarOpen],
106
+ () => setComplementarySidebarState(complementarySidebarState === 'expanded' ? 'closed' : 'expanded'),
107
+ [complementarySidebarState, setComplementarySidebarState],
107
108
  ),
108
- openComplementarySidebar: useCallback(() => setComplementarySidebarOpen(true), [setComplementarySidebarOpen]),
109
- closeComplementarySidebar: useCallback(() => setComplementarySidebarOpen(false), [setComplementarySidebarOpen]),
109
+ openComplementarySidebar: useCallback(() => setComplementarySidebarState('expanded'), []),
110
+ collapseComplementarySidebar: useCallback(() => setComplementarySidebarState('collapsed'), []),
111
+ closeComplementarySidebar: useCallback(() => setComplementarySidebarState('closed'), []),
110
112
  };
111
113
  };
112
114
 
113
115
  type MainRootProps = PropsWithChildren<{
114
- navigationSidebarOpen?: boolean;
115
- defaultNavigationSidebarOpen?: boolean;
116
- onNavigationSidebarOpenChange?: (nextOpen: boolean) => void;
117
- complementarySidebarOpen?: boolean;
118
- defaultComplementarySidebarOpen?: boolean;
119
- onComplementarySidebarOpenChange?: (nextOpen: boolean) => void;
116
+ navigationSidebarState?: SidebarState;
117
+ defaultNavigationSidebarState?: SidebarState;
118
+ onNavigationSidebarStateChange?: (nextState: SidebarState) => void;
119
+ complementarySidebarState?: SidebarState;
120
+ defaultComplementarySidebarState?: SidebarState;
121
+ onComplementarySidebarStateChange?: (nextState: SidebarState) => void;
120
122
  }>;
121
123
 
122
124
  const resizeDebounce = 3000;
123
125
 
124
126
  const MainRoot = ({
125
- navigationSidebarOpen: propsNavigationSidebarOpen,
126
- defaultNavigationSidebarOpen,
127
- onNavigationSidebarOpenChange,
128
- complementarySidebarOpen: propsComplementarySidebarOpen,
129
- defaultComplementarySidebarOpen,
130
- onComplementarySidebarOpenChange,
127
+ navigationSidebarState: propsNavigationSidebarState,
128
+ defaultNavigationSidebarState,
129
+ onNavigationSidebarStateChange,
130
+ complementarySidebarState: propsComplementarySidebarState,
131
+ defaultComplementarySidebarState,
132
+ onComplementarySidebarStateChange,
131
133
  children,
132
134
  ...props
133
135
  }: MainRootProps) => {
134
136
  const [isLg] = useMediaQuery('lg', { ssr: false });
135
- const [navigationSidebarOpen = isLg, setNavigationSidebarOpen] = useControllableState<boolean>({
136
- prop: propsNavigationSidebarOpen,
137
- defaultProp: defaultNavigationSidebarOpen,
138
- onChange: onNavigationSidebarOpenChange,
139
- });
140
- const [complementarySidebarOpen = false, setComplementarySidebarOpen] = useControllableState<boolean>({
141
- prop: propsComplementarySidebarOpen,
142
- defaultProp: defaultComplementarySidebarOpen,
143
- onChange: onComplementarySidebarOpenChange,
144
- });
137
+ const [navigationSidebarState = isLg ? 'expanded' : 'collapsed', setNavigationSidebarState] =
138
+ useControllableState<SidebarState>({
139
+ prop: propsNavigationSidebarState,
140
+ defaultProp: defaultNavigationSidebarState,
141
+ onChange: onNavigationSidebarStateChange,
142
+ });
143
+ const [complementarySidebarState = isLg ? 'expanded' : 'collapsed', setComplementarySidebarState] =
144
+ useControllableState<SidebarState>({
145
+ prop: propsComplementarySidebarState,
146
+ defaultProp: defaultComplementarySidebarState,
147
+ onChange: onComplementarySidebarStateChange,
148
+ });
145
149
 
146
150
  const [resizing, setResizing] = useState(false);
147
151
  const resizeInterval = useRef<ReturnType<typeof setTimeout> | null>(null);
@@ -166,10 +170,10 @@ const MainRoot = ({
166
170
  <MainProvider
167
171
  {...props}
168
172
  {...{
169
- navigationSidebarOpen,
170
- setNavigationSidebarOpen,
171
- complementarySidebarOpen,
172
- setComplementarySidebarOpen,
173
+ navigationSidebarState,
174
+ setNavigationSidebarState,
175
+ complementarySidebarState,
176
+ setComplementarySidebarState,
173
177
  }}
174
178
  resizing={resizing}
175
179
  >
@@ -186,15 +190,15 @@ const handleOpenAutoFocus = (event: Event) => {
186
190
 
187
191
  type MainSidebarProps = ThemedClassName<ComponentPropsWithRef<typeof DialogContent>> & {
188
192
  swipeToDismiss?: boolean;
189
- open: boolean;
193
+ state?: SidebarState;
190
194
  resizing?: boolean;
191
- setOpen: Dispatch<SetStateAction<boolean | undefined>>;
195
+ onStateChange?: (nextState: SidebarState) => void;
192
196
  side: 'inline-start' | 'inline-end';
193
197
  };
194
198
 
195
199
  const MainSidebar = forwardRef<HTMLDivElement, MainSidebarProps>(
196
200
  (
197
- { classNames, children, swipeToDismiss, onOpenAutoFocus, open, resizing, setOpen, side, ...props },
201
+ { classNames, children, swipeToDismiss, onOpenAutoFocus, state, resizing, onStateChange, side, ...props },
198
202
  forwardedRef,
199
203
  ) => {
200
204
  const [isLg] = useMediaQuery('lg', { ssr: false });
@@ -202,7 +206,7 @@ const MainSidebar = forwardRef<HTMLDivElement, MainSidebarProps>(
202
206
  const ref = useForwardedRef(forwardedRef);
203
207
  const noopRef = useRef(null);
204
208
  useSwipeToDismiss(swipeToDismiss ? ref : noopRef, {
205
- onDismiss: () => setOpen(false),
209
+ onDismiss: () => onStateChange?.('closed'),
206
210
  });
207
211
  const handleKeyDown = useCallback(
208
212
  (event: KeyboardEvent<HTMLDivElement>) => {
@@ -215,36 +219,36 @@ const MainSidebar = forwardRef<HTMLDivElement, MainSidebarProps>(
215
219
  );
216
220
  const Root = isLg ? Primitive.div : DialogContent;
217
221
  return (
218
- <DialogRoot open={open} modal={false}>
222
+ <DialogRoot open={state !== 'closed'} modal={false}>
219
223
  <Root
220
224
  {...(!isLg && { forceMount: true, tabIndex: -1, onOpenAutoFocus: onOpenAutoFocus ?? handleOpenAutoFocus })}
221
225
  {...props}
222
226
  data-side={side === 'inline-end' ? 'ie' : 'is'}
223
- data-state={open ? 'open' : 'closed'}
227
+ data-state={state}
224
228
  data-resizing={resizing ? 'true' : 'false'}
225
229
  className={tx('main.sidebar', 'main__sidebar', {}, classNames)}
226
230
  onKeyDown={handleKeyDown}
227
- {...(!open && { inert: 'true' })}
231
+ {...(state === 'closed' && { inert: 'true' })}
228
232
  ref={ref}
229
233
  >
230
- <ElevationProvider elevation='group'>{children}</ElevationProvider>
234
+ {children}
231
235
  </Root>
232
236
  </DialogRoot>
233
237
  );
234
238
  },
235
239
  );
236
240
 
237
- type MainNavigationSidebarProps = Omit<MainSidebarProps, 'open' | 'setOpen' | 'side'>;
241
+ type MainNavigationSidebarProps = Omit<MainSidebarProps, 'expanded' | 'side'>;
238
242
 
239
243
  const MainNavigationSidebar = forwardRef<HTMLDivElement, MainNavigationSidebarProps>((props, forwardedRef) => {
240
- const { navigationSidebarOpen, setNavigationSidebarOpen, resizing } = useMainContext(NAVIGATION_SIDEBAR_NAME);
244
+ const { navigationSidebarState, setNavigationSidebarState, resizing } = useMainContext(NAVIGATION_SIDEBAR_NAME);
241
245
  const mover = useLandmarkMover(props.onKeyDown, '0');
242
246
  return (
243
247
  <MainSidebar
244
248
  {...mover}
245
249
  {...props}
246
- open={navigationSidebarOpen}
247
- setOpen={setNavigationSidebarOpen}
250
+ state={navigationSidebarState}
251
+ onStateChange={setNavigationSidebarState}
248
252
  resizing={resizing}
249
253
  side='inline-start'
250
254
  ref={forwardedRef}
@@ -254,18 +258,18 @@ const MainNavigationSidebar = forwardRef<HTMLDivElement, MainNavigationSidebarPr
254
258
 
255
259
  MainNavigationSidebar.displayName = NAVIGATION_SIDEBAR_NAME;
256
260
 
257
- type MainComplementarySidebarProps = Omit<MainSidebarProps, 'open' | 'setOpen' | 'side'>;
261
+ type MainComplementarySidebarProps = Omit<MainSidebarProps, 'expanded' | 'side'>;
258
262
 
259
263
  const MainComplementarySidebar = forwardRef<HTMLDivElement, MainComplementarySidebarProps>((props, forwardedRef) => {
260
- const { complementarySidebarOpen, setComplementarySidebarOpen, resizing } =
264
+ const { complementarySidebarState, setComplementarySidebarState, resizing } =
261
265
  useMainContext(COMPLEMENTARY_SIDEBAR_NAME);
262
266
  const mover = useLandmarkMover(props.onKeyDown, '2');
263
267
  return (
264
268
  <MainSidebar
265
269
  {...mover}
266
270
  {...props}
267
- open={complementarySidebarOpen}
268
- setOpen={setComplementarySidebarOpen}
271
+ state={complementarySidebarState}
272
+ onStateChange={setComplementarySidebarState}
269
273
  resizing={resizing}
270
274
  side='inline-end'
271
275
  ref={forwardedRef}
@@ -283,7 +287,7 @@ type MainProps = ThemedClassName<ComponentPropsWithRef<typeof Primitive.div>> &
283
287
 
284
288
  const MainContent = forwardRef<HTMLDivElement, MainProps>(
285
289
  ({ asChild, classNames, bounce, handlesFocus, children, role, ...props }: MainProps, forwardedRef) => {
286
- const { navigationSidebarOpen, complementarySidebarOpen } = useMainContext(MAIN_NAME);
290
+ const { navigationSidebarState, complementarySidebarState } = useMainContext(MAIN_NAME);
287
291
  const { tx } = useThemeContext();
288
292
  const Root = asChild ? Slot : role ? 'div' : 'main';
289
293
 
@@ -294,8 +298,8 @@ const MainContent = forwardRef<HTMLDivElement, MainProps>(
294
298
  role={role}
295
299
  {...(handlesFocus && { ...mover })}
296
300
  {...props}
297
- data-sidebar-inline-start-state={navigationSidebarOpen ? 'open' : 'closed'}
298
- data-sidebar-inline-end-state={complementarySidebarOpen ? 'open' : 'closed'}
301
+ data-sidebar-inline-start-state={navigationSidebarState}
302
+ data-sidebar-inline-end-state={complementarySidebarState}
299
303
  className={tx('main.content', 'main', { bounce, handlesFocus }, classNames)}
300
304
  ref={forwardedRef}
301
305
  >
@@ -311,72 +315,37 @@ type MainOverlayProps = ThemedClassName<Omit<ComponentPropsWithRef<typeof Primit
311
315
 
312
316
  const MainOverlay = forwardRef<HTMLDivElement, MainOverlayProps>(({ classNames, ...props }, forwardedRef) => {
313
317
  const [isLg] = useMediaQuery('lg', { ssr: false });
314
- const { navigationSidebarOpen, setNavigationSidebarOpen, complementarySidebarOpen, setComplementarySidebarOpen } =
318
+ const { navigationSidebarState, setNavigationSidebarState, complementarySidebarState, setComplementarySidebarState } =
315
319
  useMainContext(MAIN_NAME);
316
320
  const { tx } = useThemeContext();
317
321
  return (
318
322
  <div
319
323
  onClick={() => {
320
- setNavigationSidebarOpen(false);
321
- setComplementarySidebarOpen(false);
324
+ setNavigationSidebarState('collapsed');
325
+ setComplementarySidebarState('collapsed');
322
326
  }}
323
327
  {...props}
324
328
  className={tx(
325
329
  'main.overlay',
326
330
  'main__overlay',
327
- { isLg, inlineStartSidebarOpen: navigationSidebarOpen, inlineEndSidebarOpen: complementarySidebarOpen },
331
+ { isLg, inlineStartSidebarOpen: navigationSidebarState, inlineEndSidebarOpen: complementarySidebarState },
328
332
  classNames,
329
333
  )}
330
- data-state={navigationSidebarOpen || complementarySidebarOpen ? 'open' : 'closed'}
334
+ data-state={navigationSidebarState === 'expanded' || complementarySidebarState === 'expanded' ? 'open' : 'closed'}
331
335
  aria-hidden='true'
332
336
  ref={forwardedRef}
333
337
  />
334
338
  );
335
339
  });
336
340
 
337
- type MainNotchProps = ThemedClassName<ComponentPropsWithRef<typeof Primitive.div>>;
338
-
339
- const MainNotch = forwardRef<HTMLDivElement, MainNotchProps>(({ classNames, ...props }, forwardedRef) => {
340
- const { tx } = useThemeContext();
341
- // Notch is concerned with the nav sidebar, whichever side it might be on.
342
- const { navigationSidebarOpen } = useMainContext(MAIN_NAME);
343
- const notchElement = useRef<HTMLDivElement | null>(null);
344
- const ref = useComposedRefs(forwardedRef, notchElement);
345
-
346
- const handleKeyDown = useCallback(
347
- (event: KeyboardEvent<HTMLDivElement>) => {
348
- switch (event.key) {
349
- case 'Escape':
350
- props?.onKeyDown?.(event);
351
- notchElement.current?.focus();
352
- }
353
- },
354
- [props?.onKeyDown],
355
- );
356
-
357
- const mover = useLandmarkMover(handleKeyDown, '3');
358
-
359
- return (
360
- <div
361
- role='toolbar'
362
- {...mover}
363
- {...props}
364
- data-nav-sidebar-state={navigationSidebarOpen ? 'open' : 'closed'}
365
- className={tx('main.notch', 'main__notch', {}, classNames)}
366
- ref={ref}
367
- />
368
- );
369
- });
370
-
371
341
  export const Main = {
372
342
  Root: MainRoot,
373
343
  Content: MainContent,
374
344
  Overlay: MainOverlay,
375
345
  NavigationSidebar: MainNavigationSidebar,
376
346
  ComplementarySidebar: MainComplementarySidebar,
377
- Notch: MainNotch,
378
347
  };
379
348
 
380
- export { useMainContext, useSidebars };
349
+ export { useMainContext, useSidebars, useLandmarkMover };
381
350
 
382
- export type { MainRootProps, MainProps, MainOverlayProps, MainNavigationSidebarProps };
351
+ export type { MainRootProps, MainProps, MainOverlayProps, MainNavigationSidebarProps, SidebarState };
@@ -6,9 +6,9 @@ import { Primitive } from '@radix-ui/react-primitive';
6
6
  import { Slot } from '@radix-ui/react-slot';
7
7
  import React, { type ComponentPropsWithRef, forwardRef } from 'react';
8
8
 
9
- import { useThemeContext } from '../../hooks';
9
+ import { useElevationContext, useThemeContext } from '../../hooks';
10
+ import { useSafeCollisionPadding } from '../../hooks/useSafeCollisionPadding';
10
11
  import { type ThemedClassName } from '../../util';
11
- import { ElevationProvider } from '../ElevationProvider';
12
12
 
13
13
  type ContextMenuRootProps = ContextMenuPrimitive.ContextMenuProps;
14
14
 
@@ -27,16 +27,18 @@ type ContextMenuContentProps = ThemedClassName<ContextMenuPrimitive.ContextMenuC
27
27
  };
28
28
 
29
29
  const ContextMenuContent = forwardRef<HTMLDivElement, ContextMenuContentProps>(
30
- ({ classNames, children, ...props }, forwardedRef) => {
30
+ ({ classNames, children, collisionPadding = 8, ...props }, forwardedRef) => {
31
31
  const { tx } = useThemeContext();
32
+ const elevation = useElevationContext();
33
+ const safeCollisionPadding = useSafeCollisionPadding(collisionPadding);
32
34
  return (
33
35
  <ContextMenuPrimitive.Content
34
- collisionPadding={8}
35
36
  {...props}
36
- className={tx('menu.content', 'menu', {}, classNames)}
37
+ collisionPadding={safeCollisionPadding}
38
+ className={tx('menu.content', 'menu', { elevation }, classNames)}
37
39
  ref={forwardedRef}
38
40
  >
39
- <ElevationProvider elevation='chrome'>{children}</ElevationProvider>
41
+ {children}
40
42
  </ContextMenuPrimitive.Content>
41
43
  );
42
44
  },
@@ -28,7 +28,8 @@ import React, {
28
28
  type RefObject,
29
29
  } from 'react';
30
30
 
31
- import { useThemeContext } from '../../hooks';
31
+ import { useElevationContext, useThemeContext } from '../../hooks';
32
+ import { useSafeCollisionPadding } from '../../hooks/useSafeCollisionPadding';
32
33
  import { type ThemedClassName } from '../../util';
33
34
 
34
35
  type Direction = 'ltr' | 'rtl';
@@ -232,18 +233,20 @@ interface DropdownMenuContentProps extends Omit<MenuContentProps, 'onEntryFocus'
232
233
 
233
234
  const DropdownMenuContent = forwardRef<DropdownMenuContentElement, DropdownMenuContentProps>(
234
235
  (props: ScopedProps<DropdownMenuContentProps>, forwardedRef) => {
235
- const { __scopeDropdownMenu, classNames, ...contentProps } = props;
236
+ const { __scopeDropdownMenu, classNames, collisionPadding = 8, ...contentProps } = props;
236
237
  const { tx } = useThemeContext();
237
238
  const context = useDropdownMenuContext(CONTENT_NAME, __scopeDropdownMenu);
239
+ const elevation = useElevationContext();
238
240
  const menuScope = useMenuScope(__scopeDropdownMenu);
239
241
  const hasInteractedOutsideRef = useRef(false);
240
-
242
+ const safeCollisionPadding = useSafeCollisionPadding(collisionPadding);
241
243
  return (
242
244
  <MenuPrimitive.Content
243
245
  id={context.contentId}
244
246
  aria-labelledby={context.triggerId}
245
247
  {...menuScope}
246
248
  {...contentProps}
249
+ collisionPadding={safeCollisionPadding}
247
250
  ref={forwardedRef}
248
251
  onCloseAutoFocus={composeEventHandlers(props.onCloseAutoFocus, (event) => {
249
252
  if (!hasInteractedOutsideRef.current) {
@@ -261,7 +264,7 @@ const DropdownMenuContent = forwardRef<DropdownMenuContentElement, DropdownMenuC
261
264
  hasInteractedOutsideRef.current = true;
262
265
  }
263
266
  })}
264
- className={tx('menu.content', 'menu', {}, classNames)}
267
+ className={tx('menu.content', 'menu', { elevation }, classNames)}
265
268
  style={{
266
269
  ...props.style,
267
270
  // re-namespace exposed content custom properties
@@ -36,7 +36,8 @@ import React, {
36
36
  } from 'react';
37
37
  import { RemoveScroll } from 'react-remove-scroll';
38
38
 
39
- import { useThemeContext } from '../../hooks';
39
+ import { useElevationContext, useThemeContext } from '../../hooks';
40
+ import { useSafeCollisionPadding } from '../../hooks/useSafeCollisionPadding';
40
41
  import { type ThemedClassName } from '../../util';
41
42
 
42
43
  /* -------------------------------------------------------------------------------------------------
@@ -258,6 +259,7 @@ const PopoverContent = forwardRef<PopoverContentTypeElement, PopoverContentProps
258
259
  const portalContext = usePortalContext(CONTENT_NAME, props.__scopePopover);
259
260
  const { forceMount = portalContext.forceMount, ...contentProps } = props;
260
261
  const context = usePopoverContext(CONTENT_NAME, props.__scopePopover);
262
+
261
263
  return (
262
264
  <Presence present={forceMount || context.open}>
263
265
  {context.modal ? (
@@ -427,12 +429,15 @@ const PopoverContentImpl = forwardRef<PopoverContentImplElement, PopoverContentI
427
429
  onPointerDownOutside,
428
430
  onFocusOutside,
429
431
  onInteractOutside,
432
+ collisionPadding = 8,
430
433
  classNames,
431
434
  ...contentProps
432
435
  } = props;
433
436
  const context = usePopoverContext(CONTENT_NAME, __scopePopover);
434
437
  const popperScope = usePopperScope(__scopePopover);
435
438
  const { tx } = useThemeContext();
439
+ const elevation = useElevationContext();
440
+ const safeCollisionPadding = useSafeCollisionPadding(collisionPadding);
436
441
 
437
442
  // Make sure the whole tree has focus guards as our `Popover` may be
438
443
  // the last element in the DOM (because of the `Portal`)
@@ -461,7 +466,8 @@ const PopoverContentImpl = forwardRef<PopoverContentImplElement, PopoverContentI
461
466
  id={context.contentId}
462
467
  {...popperScope}
463
468
  {...contentProps}
464
- className={tx('popover.content', 'popover', {}, classNames)}
469
+ collisionPadding={safeCollisionPadding}
470
+ className={tx('popover.content', 'popover', { elevation }, classNames)}
465
471
  ref={forwardedRef}
466
472
  style={{
467
473
  ...contentProps.style,
@@ -7,7 +7,7 @@ import '@dxos-theme';
7
7
  import React, { type PropsWithChildren } from 'react';
8
8
 
9
9
  import { faker } from '@dxos/random';
10
- import { groupSurface, surfaceElevation } from '@dxos/react-ui-theme';
10
+ import { groupSurface, surfaceShadow } from '@dxos/react-ui-theme';
11
11
 
12
12
  import { ScrollArea } from './ScrollArea';
13
13
  import { withTheme } from '../../testing';
@@ -17,7 +17,7 @@ faker.seed(1234);
17
17
  const StorybookScrollArea = ({ children }: PropsWithChildren<{}>) => {
18
18
  return (
19
19
  <ScrollArea.Root
20
- classNames={['is-[300px] bs-[400px] rounded', groupSurface, surfaceElevation({ elevation: 'group' })]}
20
+ classNames={['is-[300px] bs-[400px] rounded', groupSurface, surfaceShadow({ elevation: 'positioned' })]}
21
21
  >
22
22
  <ScrollArea.Viewport classNames='rounded p-4'>
23
23
  <p>{children}</p>
@@ -6,7 +6,8 @@ import { CaretDown, CaretUp } from '@phosphor-icons/react';
6
6
  import * as SelectPrimitive from '@radix-ui/react-select';
7
7
  import React, { forwardRef } from 'react';
8
8
 
9
- import { useThemeContext } from '../../hooks';
9
+ import { useElevationContext, useThemeContext } from '../../hooks';
10
+ import { useSafeCollisionPadding } from '../../hooks/useSafeCollisionPadding';
10
11
  import { type ThemedClassName } from '../../util';
11
12
  import { Button, type ButtonProps } from '../Buttons';
12
13
  import { Icon } from '../Icon';
@@ -53,12 +54,15 @@ const SelectTriggerButton = forwardRef<HTMLButtonElement, SelectTriggerButtonPro
53
54
  type SelectContentProps = ThemedClassName<SelectPrimitive.SelectContentProps>;
54
55
 
55
56
  const SelectContent = forwardRef<HTMLDivElement, SelectContentProps>(
56
- ({ classNames, children, ...props }, forwardedRef) => {
57
+ ({ classNames, children, collisionPadding = 8, ...props }, forwardedRef) => {
57
58
  const { tx } = useThemeContext();
59
+ const elevation = useElevationContext();
60
+ const safeCollisionPadding = useSafeCollisionPadding(collisionPadding);
58
61
  return (
59
62
  <SelectPrimitive.Content
60
63
  {...props}
61
- className={tx('select.content', 'select__content', {}, classNames)}
64
+ collisionPadding={safeCollisionPadding}
65
+ className={tx('select.content', 'select__content', { elevation }, classNames)}
62
66
  position='popper'
63
67
  ref={forwardedRef}
64
68
  >
@@ -5,23 +5,26 @@ import {
5
5
  Separator as SeparatorPrimitive,
6
6
  type SeparatorProps as SeparatorPrimitiveProps,
7
7
  } from '@radix-ui/react-separator';
8
- import React from 'react';
8
+ import React, { forwardRef } from 'react';
9
9
 
10
10
  import { useThemeContext } from '../../hooks';
11
11
  import { type ThemedClassName } from '../../util';
12
12
 
13
13
  type SeparatorProps = ThemedClassName<SeparatorPrimitiveProps>;
14
14
 
15
- const Separator = ({ classNames, orientation = 'horizontal', ...props }: SeparatorProps) => {
16
- const { tx } = useThemeContext();
17
- return (
18
- <SeparatorPrimitive
19
- orientation={orientation}
20
- {...props}
21
- className={tx('separator.root', 'separator', { orientation }, classNames)}
22
- />
23
- );
24
- };
15
+ const Separator = forwardRef<HTMLDivElement, SeparatorProps>(
16
+ ({ classNames, orientation = 'horizontal', ...props }, forwardedRef) => {
17
+ const { tx } = useThemeContext();
18
+ return (
19
+ <SeparatorPrimitive
20
+ orientation={orientation}
21
+ {...props}
22
+ className={tx('separator.root', 'separator', { orientation }, classNames)}
23
+ ref={forwardedRef}
24
+ />
25
+ );
26
+ },
27
+ );
25
28
 
26
29
  export type { SeparatorProps };
27
30