@dxos/react-ui 0.7.5-main.9d26e3a → 0.7.5-main.e9bb01b

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 (66) hide show
  1. package/dist/lib/browser/index.mjs +272 -200
  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 +646 -577
  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 +272 -200
  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/Dialog.d.ts.map +1 -1
  17. package/dist/types/src/components/Main/Main.d.ts +35 -24
  18. package/dist/types/src/components/Main/Main.d.ts.map +1 -1
  19. package/dist/types/src/components/Main/Main.stories.d.ts +1 -1
  20. package/dist/types/src/components/Menus/ContextMenu.d.ts.map +1 -1
  21. package/dist/types/src/components/Menus/DropdownMenu.d.ts +2 -6
  22. package/dist/types/src/components/Menus/DropdownMenu.d.ts.map +1 -1
  23. package/dist/types/src/components/Popover/Popover.d.ts.map +1 -1
  24. package/dist/types/src/components/Select/Select.d.ts.map +1 -1
  25. package/dist/types/src/components/Separator/Separator.d.ts +3 -1
  26. package/dist/types/src/components/Separator/Separator.d.ts.map +1 -1
  27. package/dist/types/src/components/Tag/Tag.d.ts.map +1 -1
  28. package/dist/types/src/components/Tag/Tag.stories.d.ts +12 -5
  29. package/dist/types/src/components/Tag/Tag.stories.d.ts.map +1 -1
  30. package/dist/types/src/components/ThemeProvider/ThemeProvider.d.ts +3 -1
  31. package/dist/types/src/components/ThemeProvider/ThemeProvider.d.ts.map +1 -1
  32. package/dist/types/src/components/Toolbar/Toolbar.d.ts +15 -5
  33. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  34. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +7 -2
  35. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  36. package/dist/types/src/components/Tooltip/Tooltip.d.ts.map +1 -1
  37. package/dist/types/src/hooks/index.d.ts +1 -0
  38. package/dist/types/src/hooks/index.d.ts.map +1 -1
  39. package/dist/types/src/hooks/useSafeArea.d.ts +9 -0
  40. package/dist/types/src/hooks/useSafeArea.d.ts.map +1 -0
  41. package/dist/types/src/hooks/useSafeCollisionPadding.d.ts +10 -0
  42. package/dist/types/src/hooks/useSafeCollisionPadding.d.ts.map +1 -0
  43. package/dist/types/src/hooks/useVisualViewport.d.ts +1 -1
  44. package/dist/types/src/hooks/useVisualViewport.d.ts.map +1 -1
  45. package/dist/types/tsconfig.tsbuildinfo +1 -1
  46. package/package.json +42 -42
  47. package/src/components/Avatars/Avatar.tsx +3 -6
  48. package/src/components/Buttons/IconButton.tsx +25 -7
  49. package/src/components/Dialogs/Dialog.tsx +1 -9
  50. package/src/components/Input/Input.tsx +1 -1
  51. package/src/components/Main/Main.stories.tsx +1 -1
  52. package/src/components/Main/Main.tsx +78 -108
  53. package/src/components/Menus/ContextMenu.tsx +4 -2
  54. package/src/components/Menus/DropdownMenu.tsx +4 -2
  55. package/src/components/Popover/Popover.tsx +4 -0
  56. package/src/components/Select/Select.tsx +4 -1
  57. package/src/components/Separator/Separator.tsx +14 -11
  58. package/src/components/Tag/Tag.stories.tsx +20 -31
  59. package/src/components/Tag/Tag.tsx +15 -6
  60. package/src/components/ThemeProvider/ThemeProvider.tsx +12 -3
  61. package/src/components/Toolbar/Toolbar.tsx +40 -10
  62. package/src/components/Tooltip/Tooltip.tsx +17 -13
  63. package/src/hooks/index.ts +1 -0
  64. package/src/hooks/useSafeArea.ts +25 -0
  65. package/src/hooks/useSafeCollisionPadding.ts +39 -0
  66. package/src/hooks/useVisualViewport.ts +11 -12
@@ -2,34 +2,48 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { forwardRef } from 'react';
5
+ import React, { forwardRef, type ReactNode, type MutableRefObject, useState } from 'react';
6
6
 
7
7
  import { Button, type ButtonProps } from './Button';
8
8
  import { useThemeContext } from '../../hooks';
9
9
  import { type ThemedClassName } from '../../util';
10
10
  import { Icon, type IconProps } from '../Icon';
11
- import { Tooltip } from '../Tooltip';
11
+ import { Tooltip, type TooltipContentProps } from '../Tooltip';
12
12
 
13
13
  type IconButtonProps = Omit<ButtonProps, 'children'> &
14
14
  Pick<IconProps, 'icon' | 'size'> & {
15
- label: string;
15
+ label: NonNullable<ReactNode>;
16
16
  iconOnly?: boolean;
17
+ caretDown?: boolean;
17
18
  // TODO(burdon): Create slots abstraction?
18
19
  iconClassNames?: ThemedClassName<any>['classNames'];
19
20
  tooltipPortal?: boolean;
20
21
  tooltipZIndex?: string;
22
+ tooltipSide?: TooltipContentProps['side'];
23
+ suppressNextTooltip?: MutableRefObject<boolean>;
21
24
  };
22
25
 
23
26
  const IconOnlyButton = forwardRef<HTMLButtonElement, IconButtonProps>(
24
- ({ tooltipPortal = true, tooltipZIndex: zIndex, ...props }, forwardedRef) => {
27
+ ({ tooltipPortal = true, tooltipZIndex: zIndex, tooltipSide, suppressNextTooltip, ...props }, forwardedRef) => {
28
+ const [triggerTooltipOpen, setTriggerTooltipOpen] = useState(false);
25
29
  const content = (
26
- <Tooltip.Content {...(zIndex && { style: { zIndex } })}>
30
+ <Tooltip.Content {...(zIndex && { style: { zIndex } })} side={tooltipSide}>
27
31
  {props.label}
28
32
  <Tooltip.Arrow />
29
33
  </Tooltip.Content>
30
34
  );
31
35
  return (
32
- <Tooltip.Root>
36
+ <Tooltip.Root
37
+ open={triggerTooltipOpen}
38
+ onOpenChange={(nextOpen) => {
39
+ if (suppressNextTooltip?.current) {
40
+ setTriggerTooltipOpen(false);
41
+ suppressNextTooltip.current = false;
42
+ } else {
43
+ setTriggerTooltipOpen(nextOpen);
44
+ }
45
+ }}
46
+ >
33
47
  <Tooltip.Trigger asChild>
34
48
  <LabelledIconButton {...props} ref={forwardedRef} />
35
49
  </Tooltip.Trigger>
@@ -40,12 +54,16 @@ const IconOnlyButton = forwardRef<HTMLButtonElement, IconButtonProps>(
40
54
  );
41
55
 
42
56
  const LabelledIconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
43
- ({ icon, size, iconOnly, label, classNames, iconClassNames, ...props }, forwardedRef) => {
57
+ (
58
+ { icon, size, iconOnly, label, classNames, iconClassNames, caretDown, suppressNextTooltip, ...props },
59
+ forwardedRef,
60
+ ) => {
44
61
  const { tx } = useThemeContext();
45
62
  return (
46
63
  <Button {...props} classNames={tx('iconButton.root', 'iconButton', {}, classNames)} ref={forwardedRef}>
47
64
  <Icon icon={icon} size={size} classNames={iconClassNames} />
48
65
  <span className={iconOnly ? 'sr-only' : undefined}>{label}</span>
66
+ {caretDown && <Icon size={3} icon='ph--caret-down--bold' />}
49
67
  </Button>
50
68
  );
51
69
  },
@@ -94,15 +94,7 @@ const DialogOverlay: ForwardRefExoticComponent<DialogOverlayProps> = forwardRef<
94
94
  return (
95
95
  <DialogOverlayPrimitive
96
96
  {...props}
97
- className={tx(
98
- 'dialog.overlay',
99
- 'dialog__overlay',
100
- {},
101
- classNames,
102
- 'data-[block-align=start]:justify-center',
103
- 'data-[block-align=start]:items-start',
104
- 'data-[block-align=center]:place-content-center',
105
- )}
97
+ className={tx('dialog.overlay', 'dialog__overlay', {}, classNames)}
106
98
  ref={forwardedRef}
107
99
  data-block-align={blockAlign}
108
100
  >
@@ -307,7 +307,7 @@ const Switch = forwardRef<HTMLInputElement, InputScopedProps<SwitchProps>>(
307
307
  return (
308
308
  <input
309
309
  type='checkbox'
310
- className='ch-checkbox--switch ch-focus-ring'
310
+ className='dx-checkbox--switch dx-focus-ring'
311
311
  checked={checked}
312
312
  onChange={(event) => {
313
313
  onCheckedChange(event.target.checked);
@@ -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';
@@ -36,12 +35,14 @@ const COMPLEMENTARY_SIDEBAR_NAME = 'ComplementarySidebar';
36
35
  const MAIN_NAME = 'Main';
37
36
  const GENERIC_CONSUMER_NAME = 'GenericConsumer';
38
37
 
38
+ type SidebarState = 'expanded' | 'collapsed' | 'closed';
39
+
39
40
  type MainContextValue = {
40
41
  resizing: boolean;
41
- navigationSidebarOpen: boolean;
42
- setNavigationSidebarOpen: Dispatch<SetStateAction<boolean | undefined>>;
43
- complementarySidebarOpen: boolean;
44
- setComplementarySidebarOpen: Dispatch<SetStateAction<boolean | undefined>>;
42
+ navigationSidebarState: SidebarState;
43
+ setNavigationSidebarState: Dispatch<SetStateAction<SidebarState | undefined>>;
44
+ complementarySidebarState: SidebarState;
45
+ setComplementarySidebarState: Dispatch<SetStateAction<SidebarState | undefined>>;
45
46
  };
46
47
 
47
48
  const landmarkAttr = 'data-main-landmark';
@@ -74,73 +75,77 @@ const useLandmarkMover = (propsOnKeyDown: ComponentPropsWithoutRef<'div'>['onKey
74
75
 
75
76
  const [MainProvider, useMainContext] = createContext<MainContextValue>(MAIN_NAME, {
76
77
  resizing: false,
77
- navigationSidebarOpen: false,
78
- setNavigationSidebarOpen: (nextOpen) => {
78
+ navigationSidebarState: 'closed',
79
+ setNavigationSidebarState: (nextState) => {
79
80
  // TODO(burdon): Standardize with other context missing errors using raise.
80
81
  log.warn('Attempt to set sidebar state without initializing `MainRoot`');
81
82
  },
82
- complementarySidebarOpen: false,
83
- setComplementarySidebarOpen: (nextOpen) => {
83
+ complementarySidebarState: 'closed',
84
+ setComplementarySidebarState: (nextState) => {
84
85
  // TODO(burdon): Standardize with other context missing errors using raise.
85
86
  log.warn('Attempt to set sidebar state without initializing `MainRoot`');
86
87
  },
87
88
  });
88
89
 
89
90
  const useSidebars = (consumerName = GENERIC_CONSUMER_NAME) => {
90
- const { setNavigationSidebarOpen, navigationSidebarOpen, setComplementarySidebarOpen, complementarySidebarOpen } =
91
+ const { setNavigationSidebarState, navigationSidebarState, setComplementarySidebarState, complementarySidebarState } =
91
92
  useMainContext(consumerName);
92
93
  return {
93
- navigationSidebarOpen,
94
- setNavigationSidebarOpen,
94
+ navigationSidebarState,
95
+ setNavigationSidebarState,
95
96
  toggleNavigationSidebar: useCallback(
96
- () => setNavigationSidebarOpen(!navigationSidebarOpen),
97
- [navigationSidebarOpen, setNavigationSidebarOpen],
97
+ () => setNavigationSidebarState(navigationSidebarState === 'expanded' ? 'closed' : 'expanded'),
98
+ [navigationSidebarState, setNavigationSidebarState],
98
99
  ),
99
- openNavigationSidebar: useCallback(() => setNavigationSidebarOpen(true), [setNavigationSidebarOpen]),
100
- closeNavigationSidebar: useCallback(() => setNavigationSidebarOpen(false), [setNavigationSidebarOpen]),
101
- complementarySidebarOpen,
102
- setComplementarySidebarOpen,
100
+ openNavigationSidebar: useCallback(() => setNavigationSidebarState('expanded'), []),
101
+ collapseNavigationSidebar: useCallback(() => setNavigationSidebarState('collapsed'), []),
102
+ closeNavigationSidebar: useCallback(() => setNavigationSidebarState('closed'), []),
103
+ complementarySidebarState,
104
+ setComplementarySidebarState,
103
105
  toggleComplementarySidebar: useCallback(
104
- () => setComplementarySidebarOpen(!complementarySidebarOpen),
105
- [complementarySidebarOpen, setComplementarySidebarOpen],
106
+ () => setComplementarySidebarState(complementarySidebarState === 'expanded' ? 'closed' : 'expanded'),
107
+ [complementarySidebarState, setComplementarySidebarState],
106
108
  ),
107
- openComplementarySidebar: useCallback(() => setComplementarySidebarOpen(true), [setComplementarySidebarOpen]),
108
- closeComplementarySidebar: useCallback(() => setComplementarySidebarOpen(false), [setComplementarySidebarOpen]),
109
+ openComplementarySidebar: useCallback(() => setComplementarySidebarState('expanded'), []),
110
+ collapseComplementarySidebar: useCallback(() => setComplementarySidebarState('collapsed'), []),
111
+ closeComplementarySidebar: useCallback(() => setComplementarySidebarState('closed'), []),
109
112
  };
110
113
  };
111
114
 
112
115
  type MainRootProps = PropsWithChildren<{
113
- navigationSidebarOpen?: boolean;
114
- defaultNavigationSidebarOpen?: boolean;
115
- onNavigationSidebarOpenChange?: (nextOpen: boolean) => void;
116
- complementarySidebarOpen?: boolean;
117
- defaultComplementarySidebarOpen?: boolean;
118
- 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;
119
122
  }>;
120
123
 
121
124
  const resizeDebounce = 3000;
122
125
 
123
126
  const MainRoot = ({
124
- navigationSidebarOpen: propsNavigationSidebarOpen,
125
- defaultNavigationSidebarOpen,
126
- onNavigationSidebarOpenChange,
127
- complementarySidebarOpen: propsComplementarySidebarOpen,
128
- defaultComplementarySidebarOpen,
129
- onComplementarySidebarOpenChange,
127
+ navigationSidebarState: propsNavigationSidebarState,
128
+ defaultNavigationSidebarState,
129
+ onNavigationSidebarStateChange,
130
+ complementarySidebarState: propsComplementarySidebarState,
131
+ defaultComplementarySidebarState,
132
+ onComplementarySidebarStateChange,
130
133
  children,
131
134
  ...props
132
135
  }: MainRootProps) => {
133
136
  const [isLg] = useMediaQuery('lg', { ssr: false });
134
- const [navigationSidebarOpen = isLg, setNavigationSidebarOpen] = useControllableState<boolean>({
135
- prop: propsNavigationSidebarOpen,
136
- defaultProp: defaultNavigationSidebarOpen,
137
- onChange: onNavigationSidebarOpenChange,
138
- });
139
- const [complementarySidebarOpen = false, setComplementarySidebarOpen] = useControllableState<boolean>({
140
- prop: propsComplementarySidebarOpen,
141
- defaultProp: defaultComplementarySidebarOpen,
142
- onChange: onComplementarySidebarOpenChange,
143
- });
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
+ });
144
149
 
145
150
  const [resizing, setResizing] = useState(false);
146
151
  const resizeInterval = useRef<ReturnType<typeof setTimeout> | null>(null);
@@ -165,10 +170,10 @@ const MainRoot = ({
165
170
  <MainProvider
166
171
  {...props}
167
172
  {...{
168
- navigationSidebarOpen,
169
- setNavigationSidebarOpen,
170
- complementarySidebarOpen,
171
- setComplementarySidebarOpen,
173
+ navigationSidebarState,
174
+ setNavigationSidebarState,
175
+ complementarySidebarState,
176
+ setComplementarySidebarState,
172
177
  }}
173
178
  resizing={resizing}
174
179
  >
@@ -185,15 +190,15 @@ const handleOpenAutoFocus = (event: Event) => {
185
190
 
186
191
  type MainSidebarProps = ThemedClassName<ComponentPropsWithRef<typeof DialogContent>> & {
187
192
  swipeToDismiss?: boolean;
188
- open: boolean;
193
+ state?: SidebarState;
189
194
  resizing?: boolean;
190
- setOpen: Dispatch<SetStateAction<boolean | undefined>>;
195
+ onStateChange?: (nextState: SidebarState) => void;
191
196
  side: 'inline-start' | 'inline-end';
192
197
  };
193
198
 
194
199
  const MainSidebar = forwardRef<HTMLDivElement, MainSidebarProps>(
195
200
  (
196
- { classNames, children, swipeToDismiss, onOpenAutoFocus, open, resizing, setOpen, side, ...props },
201
+ { classNames, children, swipeToDismiss, onOpenAutoFocus, state, resizing, onStateChange, side, ...props },
197
202
  forwardedRef,
198
203
  ) => {
199
204
  const [isLg] = useMediaQuery('lg', { ssr: false });
@@ -201,7 +206,7 @@ const MainSidebar = forwardRef<HTMLDivElement, MainSidebarProps>(
201
206
  const ref = useForwardedRef(forwardedRef);
202
207
  const noopRef = useRef(null);
203
208
  useSwipeToDismiss(swipeToDismiss ? ref : noopRef, {
204
- onDismiss: () => setOpen(false),
209
+ onDismiss: () => onStateChange?.('closed'),
205
210
  });
206
211
  const handleKeyDown = useCallback(
207
212
  (event: KeyboardEvent<HTMLDivElement>) => {
@@ -214,16 +219,16 @@ const MainSidebar = forwardRef<HTMLDivElement, MainSidebarProps>(
214
219
  );
215
220
  const Root = isLg ? Primitive.div : DialogContent;
216
221
  return (
217
- <DialogRoot open={open} modal={false}>
222
+ <DialogRoot open={state !== 'closed'} modal={false}>
218
223
  <Root
219
224
  {...(!isLg && { forceMount: true, tabIndex: -1, onOpenAutoFocus: onOpenAutoFocus ?? handleOpenAutoFocus })}
220
225
  {...props}
221
226
  data-side={side === 'inline-end' ? 'ie' : 'is'}
222
- data-state={open ? 'open' : 'closed'}
227
+ data-state={state}
223
228
  data-resizing={resizing ? 'true' : 'false'}
224
229
  className={tx('main.sidebar', 'main__sidebar', {}, classNames)}
225
230
  onKeyDown={handleKeyDown}
226
- {...(!open && { inert: 'true' })}
231
+ {...(state === 'closed' && { inert: 'true' })}
227
232
  ref={ref}
228
233
  >
229
234
  {children}
@@ -233,17 +238,17 @@ const MainSidebar = forwardRef<HTMLDivElement, MainSidebarProps>(
233
238
  },
234
239
  );
235
240
 
236
- type MainNavigationSidebarProps = Omit<MainSidebarProps, 'open' | 'setOpen' | 'side'>;
241
+ type MainNavigationSidebarProps = Omit<MainSidebarProps, 'expanded' | 'side'>;
237
242
 
238
243
  const MainNavigationSidebar = forwardRef<HTMLDivElement, MainNavigationSidebarProps>((props, forwardedRef) => {
239
- const { navigationSidebarOpen, setNavigationSidebarOpen, resizing } = useMainContext(NAVIGATION_SIDEBAR_NAME);
244
+ const { navigationSidebarState, setNavigationSidebarState, resizing } = useMainContext(NAVIGATION_SIDEBAR_NAME);
240
245
  const mover = useLandmarkMover(props.onKeyDown, '0');
241
246
  return (
242
247
  <MainSidebar
243
248
  {...mover}
244
249
  {...props}
245
- open={navigationSidebarOpen}
246
- setOpen={setNavigationSidebarOpen}
250
+ state={navigationSidebarState}
251
+ onStateChange={setNavigationSidebarState}
247
252
  resizing={resizing}
248
253
  side='inline-start'
249
254
  ref={forwardedRef}
@@ -253,18 +258,18 @@ const MainNavigationSidebar = forwardRef<HTMLDivElement, MainNavigationSidebarPr
253
258
 
254
259
  MainNavigationSidebar.displayName = NAVIGATION_SIDEBAR_NAME;
255
260
 
256
- type MainComplementarySidebarProps = Omit<MainSidebarProps, 'open' | 'setOpen' | 'side'>;
261
+ type MainComplementarySidebarProps = Omit<MainSidebarProps, 'expanded' | 'side'>;
257
262
 
258
263
  const MainComplementarySidebar = forwardRef<HTMLDivElement, MainComplementarySidebarProps>((props, forwardedRef) => {
259
- const { complementarySidebarOpen, setComplementarySidebarOpen, resizing } =
264
+ const { complementarySidebarState, setComplementarySidebarState, resizing } =
260
265
  useMainContext(COMPLEMENTARY_SIDEBAR_NAME);
261
266
  const mover = useLandmarkMover(props.onKeyDown, '2');
262
267
  return (
263
268
  <MainSidebar
264
269
  {...mover}
265
270
  {...props}
266
- open={complementarySidebarOpen}
267
- setOpen={setComplementarySidebarOpen}
271
+ state={complementarySidebarState}
272
+ onStateChange={setComplementarySidebarState}
268
273
  resizing={resizing}
269
274
  side='inline-end'
270
275
  ref={forwardedRef}
@@ -282,7 +287,7 @@ type MainProps = ThemedClassName<ComponentPropsWithRef<typeof Primitive.div>> &
282
287
 
283
288
  const MainContent = forwardRef<HTMLDivElement, MainProps>(
284
289
  ({ asChild, classNames, bounce, handlesFocus, children, role, ...props }: MainProps, forwardedRef) => {
285
- const { navigationSidebarOpen, complementarySidebarOpen } = useMainContext(MAIN_NAME);
290
+ const { navigationSidebarState, complementarySidebarState } = useMainContext(MAIN_NAME);
286
291
  const { tx } = useThemeContext();
287
292
  const Root = asChild ? Slot : role ? 'div' : 'main';
288
293
 
@@ -293,8 +298,8 @@ const MainContent = forwardRef<HTMLDivElement, MainProps>(
293
298
  role={role}
294
299
  {...(handlesFocus && { ...mover })}
295
300
  {...props}
296
- data-sidebar-inline-start-state={navigationSidebarOpen ? 'open' : 'closed'}
297
- data-sidebar-inline-end-state={complementarySidebarOpen ? 'open' : 'closed'}
301
+ data-sidebar-inline-start-state={navigationSidebarState}
302
+ data-sidebar-inline-end-state={complementarySidebarState}
298
303
  className={tx('main.content', 'main', { bounce, handlesFocus }, classNames)}
299
304
  ref={forwardedRef}
300
305
  >
@@ -310,72 +315,37 @@ type MainOverlayProps = ThemedClassName<Omit<ComponentPropsWithRef<typeof Primit
310
315
 
311
316
  const MainOverlay = forwardRef<HTMLDivElement, MainOverlayProps>(({ classNames, ...props }, forwardedRef) => {
312
317
  const [isLg] = useMediaQuery('lg', { ssr: false });
313
- const { navigationSidebarOpen, setNavigationSidebarOpen, complementarySidebarOpen, setComplementarySidebarOpen } =
318
+ const { navigationSidebarState, setNavigationSidebarState, complementarySidebarState, setComplementarySidebarState } =
314
319
  useMainContext(MAIN_NAME);
315
320
  const { tx } = useThemeContext();
316
321
  return (
317
322
  <div
318
323
  onClick={() => {
319
- setNavigationSidebarOpen(false);
320
- setComplementarySidebarOpen(false);
324
+ setNavigationSidebarState('collapsed');
325
+ setComplementarySidebarState('collapsed');
321
326
  }}
322
327
  {...props}
323
328
  className={tx(
324
329
  'main.overlay',
325
330
  'main__overlay',
326
- { isLg, inlineStartSidebarOpen: navigationSidebarOpen, inlineEndSidebarOpen: complementarySidebarOpen },
331
+ { isLg, inlineStartSidebarOpen: navigationSidebarState, inlineEndSidebarOpen: complementarySidebarState },
327
332
  classNames,
328
333
  )}
329
- data-state={navigationSidebarOpen || complementarySidebarOpen ? 'open' : 'closed'}
334
+ data-state={navigationSidebarState === 'expanded' || complementarySidebarState === 'expanded' ? 'open' : 'closed'}
330
335
  aria-hidden='true'
331
336
  ref={forwardedRef}
332
337
  />
333
338
  );
334
339
  });
335
340
 
336
- type MainNotchProps = ThemedClassName<ComponentPropsWithRef<typeof Primitive.div>>;
337
-
338
- const MainNotch = forwardRef<HTMLDivElement, MainNotchProps>(({ classNames, ...props }, forwardedRef) => {
339
- const { tx } = useThemeContext();
340
- // Notch is concerned with the nav sidebar, whichever side it might be on.
341
- const { navigationSidebarOpen } = useMainContext(MAIN_NAME);
342
- const notchElement = useRef<HTMLDivElement | null>(null);
343
- const ref = useComposedRefs(forwardedRef, notchElement);
344
-
345
- const handleKeyDown = useCallback(
346
- (event: KeyboardEvent<HTMLDivElement>) => {
347
- switch (event.key) {
348
- case 'Escape':
349
- props?.onKeyDown?.(event);
350
- notchElement.current?.focus();
351
- }
352
- },
353
- [props?.onKeyDown],
354
- );
355
-
356
- const mover = useLandmarkMover(handleKeyDown, '3');
357
-
358
- return (
359
- <div
360
- role='toolbar'
361
- {...mover}
362
- {...props}
363
- data-nav-sidebar-state={navigationSidebarOpen ? 'open' : 'closed'}
364
- className={tx('main.notch', 'main__notch', {}, classNames)}
365
- ref={ref}
366
- />
367
- );
368
- });
369
-
370
341
  export const Main = {
371
342
  Root: MainRoot,
372
343
  Content: MainContent,
373
344
  Overlay: MainOverlay,
374
345
  NavigationSidebar: MainNavigationSidebar,
375
346
  ComplementarySidebar: MainComplementarySidebar,
376
- Notch: MainNotch,
377
347
  };
378
348
 
379
- export { useMainContext, useSidebars };
349
+ export { useMainContext, useSidebars, useLandmarkMover };
380
350
 
381
- export type { MainRootProps, MainProps, MainOverlayProps, MainNavigationSidebarProps };
351
+ export type { MainRootProps, MainProps, MainOverlayProps, MainNavigationSidebarProps, SidebarState };
@@ -7,6 +7,7 @@ import { Slot } from '@radix-ui/react-slot';
7
7
  import React, { type ComponentPropsWithRef, forwardRef } from 'react';
8
8
 
9
9
  import { useElevationContext, useThemeContext } from '../../hooks';
10
+ import { useSafeCollisionPadding } from '../../hooks/useSafeCollisionPadding';
10
11
  import { type ThemedClassName } from '../../util';
11
12
 
12
13
  type ContextMenuRootProps = ContextMenuPrimitive.ContextMenuProps;
@@ -26,13 +27,14 @@ type ContextMenuContentProps = ThemedClassName<ContextMenuPrimitive.ContextMenuC
26
27
  };
27
28
 
28
29
  const ContextMenuContent = forwardRef<HTMLDivElement, ContextMenuContentProps>(
29
- ({ classNames, children, ...props }, forwardedRef) => {
30
+ ({ classNames, children, collisionPadding = 8, ...props }, forwardedRef) => {
30
31
  const { tx } = useThemeContext();
31
32
  const elevation = useElevationContext();
33
+ const safeCollisionPadding = useSafeCollisionPadding(collisionPadding);
32
34
  return (
33
35
  <ContextMenuPrimitive.Content
34
- collisionPadding={8}
35
36
  {...props}
37
+ collisionPadding={safeCollisionPadding}
36
38
  className={tx('menu.content', 'menu', { elevation }, classNames)}
37
39
  ref={forwardedRef}
38
40
  >
@@ -29,6 +29,7 @@ import React, {
29
29
  } from 'react';
30
30
 
31
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,19 +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);
238
239
  const elevation = useElevationContext();
239
240
  const menuScope = useMenuScope(__scopeDropdownMenu);
240
241
  const hasInteractedOutsideRef = useRef(false);
241
-
242
+ const safeCollisionPadding = useSafeCollisionPadding(collisionPadding);
242
243
  return (
243
244
  <MenuPrimitive.Content
244
245
  id={context.contentId}
245
246
  aria-labelledby={context.triggerId}
246
247
  {...menuScope}
247
248
  {...contentProps}
249
+ collisionPadding={safeCollisionPadding}
248
250
  ref={forwardedRef}
249
251
  onCloseAutoFocus={composeEventHandlers(props.onCloseAutoFocus, (event) => {
250
252
  if (!hasInteractedOutsideRef.current) {
@@ -37,6 +37,7 @@ import React, {
37
37
  import { RemoveScroll } from 'react-remove-scroll';
38
38
 
39
39
  import { useElevationContext, useThemeContext } from '../../hooks';
40
+ import { useSafeCollisionPadding } from '../../hooks/useSafeCollisionPadding';
40
41
  import { type ThemedClassName } from '../../util';
41
42
 
42
43
  /* -------------------------------------------------------------------------------------------------
@@ -428,6 +429,7 @@ const PopoverContentImpl = forwardRef<PopoverContentImplElement, PopoverContentI
428
429
  onPointerDownOutside,
429
430
  onFocusOutside,
430
431
  onInteractOutside,
432
+ collisionPadding = 8,
431
433
  classNames,
432
434
  ...contentProps
433
435
  } = props;
@@ -435,6 +437,7 @@ const PopoverContentImpl = forwardRef<PopoverContentImplElement, PopoverContentI
435
437
  const popperScope = usePopperScope(__scopePopover);
436
438
  const { tx } = useThemeContext();
437
439
  const elevation = useElevationContext();
440
+ const safeCollisionPadding = useSafeCollisionPadding(collisionPadding);
438
441
 
439
442
  // Make sure the whole tree has focus guards as our `Popover` may be
440
443
  // the last element in the DOM (because of the `Portal`)
@@ -463,6 +466,7 @@ const PopoverContentImpl = forwardRef<PopoverContentImplElement, PopoverContentI
463
466
  id={context.contentId}
464
467
  {...popperScope}
465
468
  {...contentProps}
469
+ collisionPadding={safeCollisionPadding}
466
470
  className={tx('popover.content', 'popover', { elevation }, classNames)}
467
471
  ref={forwardedRef}
468
472
  style={{
@@ -7,6 +7,7 @@ import * as SelectPrimitive from '@radix-ui/react-select';
7
7
  import React, { forwardRef } from 'react';
8
8
 
9
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,14 @@ 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();
58
59
  const elevation = useElevationContext();
60
+ const safeCollisionPadding = useSafeCollisionPadding(collisionPadding);
59
61
  return (
60
62
  <SelectPrimitive.Content
61
63
  {...props}
64
+ collisionPadding={safeCollisionPadding}
62
65
  className={tx('select.content', 'select__content', { elevation }, classNames)}
63
66
  position='popper'
64
67
  ref={forwardedRef}
@@ -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