@adecore/ui 0.53.3 → 0.54.1

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 (184) hide show
  1. package/dist/Banner.d.ts +1 -1
  2. package/dist/ButtonGroup.js +1 -1
  3. package/dist/Checkbox.js +1 -1
  4. package/dist/ChoiceCards.d.ts +1 -1
  5. package/dist/ChoiceCards.js +2 -2
  6. package/dist/CloseButton.js +1 -1
  7. package/dist/ColorSwatch.d.ts +1 -1
  8. package/dist/ColorSwatch.js +1 -1
  9. package/dist/ColorWheelPicker.js +1 -1
  10. package/dist/CopyButton.js +1 -1
  11. package/dist/DockShell.js +1 -1
  12. package/dist/EmptyState.d.ts +1 -1
  13. package/dist/EmptyState.js +1 -1
  14. package/dist/ErrorBoundary.js +122 -112
  15. package/dist/Field.d.ts +1 -1
  16. package/dist/Field.js +2 -2
  17. package/dist/FileIcon.d.ts +1 -1
  18. package/dist/FileIcon.js +44 -36
  19. package/dist/Icon.d.ts +1 -1
  20. package/dist/IconButton.d.ts +3 -3
  21. package/dist/IconButton.js +2 -2
  22. package/dist/IconPicker.js +35 -32
  23. package/dist/Input.d.ts +1 -1
  24. package/dist/ListRow.js +1 -1
  25. package/dist/MiddleTruncate.js +2 -2
  26. package/dist/PanelEmpty.d.ts +1 -1
  27. package/dist/PanelHeader.d.ts +1 -1
  28. package/dist/PanelHeader.js +1 -1
  29. package/dist/ProjectSwitcher.js +1 -1
  30. package/dist/PromptDialog.d.ts +1 -1
  31. package/dist/ScrollArea.d.ts +12 -0
  32. package/dist/ScrollArea.js +144 -0
  33. package/dist/SectionLabel.js +1 -1
  34. package/dist/SegmentBar.js +1 -1
  35. package/dist/Segmented.d.ts +1 -1
  36. package/dist/Select.d.ts +1 -1
  37. package/dist/Select.js +22 -16
  38. package/dist/SelectSearch.js +49 -33
  39. package/dist/ShortcutHints.js +1 -1
  40. package/dist/Slider.js +2 -2
  41. package/dist/SlidingColumn.js +1 -1
  42. package/dist/SplitButton.js +1 -1
  43. package/dist/Surface.js +1 -1
  44. package/dist/Switch.js +1 -1
  45. package/dist/TextMenu.js +2 -2
  46. package/dist/Toasts.js +2 -2
  47. package/dist/Tooltip.d.ts +1 -1
  48. package/dist/Tooltip.js +1 -1
  49. package/dist/UIProvider.d.ts +1 -1
  50. package/dist/Waveform.js +1 -1
  51. package/dist/Wipe.js +2 -2
  52. package/dist/combobox/parts.d.ts +3 -3
  53. package/dist/combobox/parts.js +53 -32
  54. package/dist/command-palette/parts.d.ts +1 -1
  55. package/dist/command-palette/parts.js +22 -7
  56. package/dist/command-palette/virtual.js +4 -4
  57. package/dist/dialog/parts.d.ts +1 -1
  58. package/dist/dialog/parts.js +2 -2
  59. package/dist/file-icon.d.ts +2 -0
  60. package/dist/file-icon.js +12 -0
  61. package/dist/file-tree/parts.d.ts +2 -2
  62. package/dist/file-tree/parts.js +3 -3
  63. package/dist/file-tree/use-file-tree-shift.d.ts +1 -1
  64. package/dist/file-tree/use-file-tree-shift.js +1 -1
  65. package/dist/index.d.ts +2 -0
  66. package/dist/index.js +2 -0
  67. package/dist/key-value-list/parts.js +1 -1
  68. package/dist/menu/parts.d.ts +3 -3
  69. package/dist/menu/parts.js +46 -30
  70. package/dist/popover/parts.d.ts +3 -3
  71. package/dist/popover/parts.js +110 -71
  72. package/dist/project-switcher-row.js +1 -1
  73. package/dist/scrollbar-geometry.d.ts +15 -0
  74. package/dist/scrollbar-geometry.js +48 -0
  75. package/dist/settings/MasterDetail.js +40 -30
  76. package/dist/settings/SettingsDialog.d.ts +1 -1
  77. package/dist/settings/SettingsDialog.js +27 -11
  78. package/dist/settings/SettingsRow.d.ts +1 -1
  79. package/dist/settings/SettingsRow.js +1 -1
  80. package/dist/settings/SettingsSection.d.ts +1 -1
  81. package/dist/tabs/parts.d.ts +1 -1
  82. package/dist/tabs/parts.js +3 -3
  83. package/dist/testing/dedupe.js +1 -1
  84. package/dist/theme.css +91 -1
  85. package/dist/tree/parts.js +2 -2
  86. package/dist/useOverlayScrollbars.d.ts +7 -0
  87. package/dist/useOverlayScrollbars.js +281 -0
  88. package/dist/workspace/PaneBar.js +2 -2
  89. package/dist/workspace/SplitView.d.ts +1 -1
  90. package/dist/workspace/SplitView.js +4 -4
  91. package/dist/workspace/TabStrip.d.ts +1 -1
  92. package/dist/workspace/TabStrip.js +2 -2
  93. package/dist/workspace/Workspace.d.ts +1 -1
  94. package/dist/workspace/Workspace.js +2 -2
  95. package/dist/workspace/WorkspaceRail.js +1 -1
  96. package/dist/workspace/WorkspaceTopRow.js +1 -1
  97. package/package.json +1 -1
  98. package/src/AccentSwatches.tsx +1 -1
  99. package/src/Banner.tsx +1 -1
  100. package/src/Button.tsx +1 -1
  101. package/src/ButtonGroup.tsx +1 -1
  102. package/src/Checkbox.tsx +2 -2
  103. package/src/ChoiceCards.tsx +3 -3
  104. package/src/CloseButton.tsx +2 -2
  105. package/src/ColorSwatch.tsx +2 -2
  106. package/src/ColorWheelPicker.tsx +1 -1
  107. package/src/ColumnResizeHandle.tsx +1 -1
  108. package/src/CopyButton.tsx +1 -1
  109. package/src/DockShell.tsx +1 -1
  110. package/src/EmptyState.tsx +2 -2
  111. package/src/ErrorBoundary.tsx +4 -3
  112. package/src/Field.tsx +2 -2
  113. package/src/FileIcon.tsx +15 -6
  114. package/src/Icon.tsx +2 -2
  115. package/src/IconButton.tsx +3 -3
  116. package/src/IconPicker.tsx +7 -6
  117. package/src/Input.tsx +2 -2
  118. package/src/Kbd.tsx +1 -1
  119. package/src/ListRow.tsx +1 -1
  120. package/src/Meter.tsx +1 -1
  121. package/src/MiddleTruncate.tsx +2 -2
  122. package/src/PanelEmpty.tsx +2 -2
  123. package/src/PanelHeader.tsx +2 -2
  124. package/src/Pill.tsx +1 -1
  125. package/src/ProjectSwitcher.tsx +2 -2
  126. package/src/PromptDialog.tsx +1 -1
  127. package/src/RelativeTime.tsx +1 -1
  128. package/src/ScrollArea.tsx +47 -0
  129. package/src/SectionLabel.tsx +1 -1
  130. package/src/SegmentBar.tsx +1 -1
  131. package/src/Segmented.tsx +1 -1
  132. package/src/Select.tsx +6 -2
  133. package/src/SelectSearch.tsx +19 -12
  134. package/src/Separator.tsx +1 -1
  135. package/src/ShortcutHints.tsx +1 -1
  136. package/src/Skeleton.tsx +1 -1
  137. package/src/Slider.tsx +2 -2
  138. package/src/SlidingColumn.tsx +1 -1
  139. package/src/Spinner.tsx +1 -1
  140. package/src/SplitButton.tsx +1 -1
  141. package/src/Stepper.tsx +1 -1
  142. package/src/Surface.tsx +1 -1
  143. package/src/Switch.tsx +2 -2
  144. package/src/TextMenu.tsx +2 -2
  145. package/src/Tile.tsx +1 -1
  146. package/src/Toasts.tsx +2 -2
  147. package/src/Tooltip.tsx +2 -2
  148. package/src/UIProvider.tsx +1 -1
  149. package/src/Waveform.tsx +1 -1
  150. package/src/Wipe.tsx +2 -2
  151. package/src/ZoomControls.tsx +1 -1
  152. package/src/combobox/parts.tsx +9 -4
  153. package/src/command-palette/parts.tsx +25 -19
  154. package/src/command-palette/virtual.tsx +4 -4
  155. package/src/dialog/parts.tsx +2 -2
  156. package/src/file-icon.ts +15 -0
  157. package/src/file-tree/parts.tsx +6 -6
  158. package/src/file-tree/use-empty-lines.ts +1 -1
  159. package/src/file-tree/use-file-tree-shift.tsx +2 -2
  160. package/src/file-tree/use-slots.ts +2 -2
  161. package/src/index.ts +2 -0
  162. package/src/key-value-list/parts.tsx +1 -1
  163. package/src/menu/parts.tsx +11 -6
  164. package/src/popover/parts.tsx +13 -5
  165. package/src/project-switcher-row.tsx +2 -2
  166. package/src/scrollbar-geometry.ts +67 -0
  167. package/src/select-shared.ts +1 -1
  168. package/src/settings/DetailHeader.tsx +1 -1
  169. package/src/settings/MasterDetail.tsx +9 -6
  170. package/src/settings/SettingsDialog.tsx +16 -9
  171. package/src/settings/SettingsRow.tsx +1 -1
  172. package/src/settings/SettingsSection.tsx +1 -1
  173. package/src/tabs/parts.tsx +4 -4
  174. package/src/testing/dedupe.ts +1 -1
  175. package/src/theme.css +91 -1
  176. package/src/tree/parts.tsx +2 -2
  177. package/src/useOverlayScrollbars.tsx +263 -0
  178. package/src/workspace/DropPreview.tsx +1 -1
  179. package/src/workspace/PaneBar.tsx +2 -2
  180. package/src/workspace/SplitView.tsx +5 -5
  181. package/src/workspace/TabStrip.tsx +3 -3
  182. package/src/workspace/Workspace.tsx +2 -2
  183. package/src/workspace/WorkspaceRail.tsx +1 -1
  184. package/src/workspace/WorkspaceTopRow.tsx +1 -1
@@ -1,6 +1,6 @@
1
- import type { Ref } from 'react';
2
1
  import clsx from 'clsx';
3
2
  import { Ellipsis } from 'lucide-react';
3
+ import type { Ref } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { ColorSwatch } from './ColorSwatch.tsx';
6
6
  import { Icon } from './Icon.tsx';
package/src/Banner.tsx CHANGED
@@ -1,6 +1,6 @@
1
- import type { ReactNode, Ref } from 'react';
2
1
  import clsx from 'clsx';
3
2
  import type { LucideIcon } from 'lucide-react';
3
+ import type { ReactNode, Ref } from 'react';
4
4
  import { Icon } from './Icon.tsx';
5
5
  import { Surface } from './Surface.tsx';
6
6
 
package/src/Button.tsx CHANGED
@@ -1,5 +1,5 @@
1
- import type { AnchorHTMLAttributes, ComponentProps, Ref } from 'react';
2
1
  import clsx from 'clsx';
2
+ import type { AnchorHTMLAttributes, ComponentProps, Ref } from 'react';
3
3
  import { buttonClass, type ButtonShape, type ButtonSize, type ButtonVariant } from './button-class.ts';
4
4
 
5
5
  export type { ButtonShape, ButtonSize, ButtonVariant } from './button-class.ts';
@@ -1,5 +1,5 @@
1
- import clsx from 'clsx';
2
1
  import { useRender } from '@base-ui/react/use-render';
2
+ import clsx from 'clsx';
3
3
 
4
4
  export type ButtonGroupProps = useRender.ComponentProps<'div'>;
5
5
 
package/src/Checkbox.tsx CHANGED
@@ -1,7 +1,7 @@
1
- import type { Ref } from 'react';
1
+ import { Checkbox as BaseCheckbox } from '@base-ui/react/checkbox';
2
2
  import clsx from 'clsx';
3
3
  import { Check, Minus } from 'lucide-react';
4
- import { Checkbox as BaseCheckbox } from '@base-ui/react/checkbox';
4
+ import type { Ref } from 'react';
5
5
  import { Icon } from './Icon.tsx';
6
6
 
7
7
  export interface CheckboxProps {
@@ -1,8 +1,8 @@
1
- import { Fragment, useId, type ReactNode, type Ref } from 'react';
2
- import clsx from 'clsx';
3
- import type { LucideIcon } from 'lucide-react';
4
1
  import { Radio } from '@base-ui/react/radio';
5
2
  import { RadioGroup } from '@base-ui/react/radio-group';
3
+ import clsx from 'clsx';
4
+ import type { LucideIcon } from 'lucide-react';
5
+ import { Fragment, useId, type ReactNode, type Ref } from 'react';
6
6
  import { Icon } from './Icon.tsx';
7
7
 
8
8
  export interface Choice<Value extends string | number> {
@@ -1,6 +1,6 @@
1
- import type { Ref } from 'react';
2
- import { X } from 'lucide-react';
3
1
  import { Dialog } from '@base-ui/react/dialog';
2
+ import { X } from 'lucide-react';
3
+ import type { Ref } from 'react';
4
4
  import { IconButton, type IconButtonSize } from './IconButton.tsx';
5
5
  import type { Shortcut } from './shortcut.ts';
6
6
 
@@ -1,7 +1,7 @@
1
- import type { ComponentProps } from 'react';
1
+ import { useRender } from '@base-ui/react/use-render';
2
2
  import clsx from 'clsx';
3
3
  import { Check } from 'lucide-react';
4
- import { useRender } from '@base-ui/react/use-render';
4
+ import type { ComponentProps } from 'react';
5
5
  import { Icon } from './Icon.tsx';
6
6
 
7
7
  /* `focus-ring` for the swatch that is a menu item rather than a button, which the keyboard reaches all the same. */
@@ -1,5 +1,5 @@
1
- import { useEffect, useId, useRef, useState, type KeyboardEvent, type Ref } from 'react';
2
1
  import { ChevronDown } from 'lucide-react';
2
+ import { useEffect, useId, useRef, useState, type KeyboardEvent, type Ref } from 'react';
3
3
  import { ColorSwatch } from './ColorSwatch.tsx';
4
4
  import { Icon } from './Icon.tsx';
5
5
  import { PopoverPopup, PopoverRoot, PopoverTrigger } from './popover/parts.tsx';
@@ -1,5 +1,5 @@
1
- import type { CSSProperties, PointerEvent as ReactPointerEvent, Ref } from 'react';
2
1
  import clsx from 'clsx';
2
+ import type { CSSProperties, PointerEvent as ReactPointerEvent, Ref } from 'react';
3
3
  import type { ColumnEdge } from './useColumnResize.ts';
4
4
 
5
5
  export interface ColumnResizeHandleProps {
@@ -1,6 +1,6 @@
1
+ import { Check, Copy } from 'lucide-react';
1
2
  import { useEffect, useState, type Ref } from 'react';
2
3
  import { useTranslation } from 'react-i18next';
3
- import { Check, Copy } from 'lucide-react';
4
4
  import { Button, type ButtonSize, type ButtonVariant } from './Button.tsx';
5
5
  import { copyText } from './clipboard.ts';
6
6
  import { Icon } from './Icon.tsx';
package/src/DockShell.tsx CHANGED
@@ -1,5 +1,5 @@
1
- import { useEffect, useRef, useState, type HTMLAttributes, type ReactNode, type Ref } from 'react';
2
1
  import clsx from 'clsx';
2
+ import { useEffect, useRef, useState, type HTMLAttributes, type ReactNode, type Ref } from 'react';
3
3
  import { Surface } from './Surface.tsx';
4
4
 
5
5
  /* How close to the bottom of the window the pointer has to come before a hidden dock slides back. */
@@ -1,6 +1,6 @@
1
- import { cloneElement, isValidElement, type ReactElement, type ReactNode, type Ref } from 'react';
2
- import type { LucideIcon } from 'lucide-react';
3
1
  import clsx from 'clsx';
2
+ import type { LucideIcon } from 'lucide-react';
3
+ import { cloneElement, isValidElement, type ReactElement, type ReactNode, type Ref } from 'react';
4
4
  import { Icon } from './Icon.tsx';
5
5
  import { Spinner } from './Spinner.tsx';
6
6
 
@@ -1,12 +1,13 @@
1
- import { Component, type ErrorInfo, type ReactNode } from 'react';
2
1
  import clsx from 'clsx';
3
2
  import { RotateCw, TriangleAlert } from 'lucide-react';
3
+ import { Component, type ErrorInfo, type ReactNode } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { Button } from './Button.tsx';
6
6
  import { ButtonGroup } from './ButtonGroup.tsx';
7
7
  import { CopyButton } from './CopyButton.tsx';
8
8
  import { errorMessageOf, errorReport, shouldReset, type ResetKeys } from './error-boundary.ts';
9
9
  import { Icon } from './Icon.tsx';
10
+ import { ScrollArea } from './ScrollArea.tsx';
10
11
 
11
12
  export interface ErrorBoundaryProps {
12
13
  /* The first line of the message: "Could not show this view". */
@@ -49,7 +50,7 @@ function ErrorFallback({ label, error, componentStack, compact, reload, classNam
49
50
  const { t } = useTranslation('ui');
50
51
 
51
52
  return (
52
- <div role="alert" className={clsx('grid place-items-center overflow-auto bg-surface', compact ? 'p-3' : 'p-6', className)}>
53
+ <ScrollArea role="alert" className={clsx('bg-surface', className)} viewportClassName={clsx('grid place-items-center', compact ? 'p-3' : 'p-6')}>
53
54
  <div className={clsx('flex max-w-[360px] flex-col items-center text-center', compact ? 'gap-1' : 'gap-2')}>
54
55
  {!compact && <Icon icon={TriangleAlert} size={20} className="text-status-error" />}
55
56
  <p className="text-sm font-medium text-text">{label}</p>
@@ -69,7 +70,7 @@ function ErrorFallback({ label, error, componentStack, compact, reload, classNam
69
70
  </ButtonGroup>
70
71
  </div>
71
72
  </div>
72
- </div>
73
+ </ScrollArea>
73
74
  );
74
75
  }
75
76
 
package/src/Field.tsx CHANGED
@@ -1,6 +1,6 @@
1
- import { useId, type ReactNode, type Ref } from 'react';
2
- import clsx from 'clsx';
3
1
  import { useRender } from '@base-ui/react/use-render';
2
+ import clsx from 'clsx';
3
+ import { useId, type ReactNode, type Ref } from 'react';
4
4
  import { FieldContext } from './field-context.ts';
5
5
  import { SectionLabel } from './SectionLabel.tsx';
6
6
 
package/src/FileIcon.tsx CHANGED
@@ -1,6 +1,6 @@
1
- import { useInsertionEffect, type Ref } from 'react';
2
1
  import clsx from 'clsx';
3
- import { fileIconFor, fileIconOverrideFor, mountFileIconSprite } from './file-icon.ts';
2
+ import { useInsertionEffect, useSyncExternalStore, type Ref } from 'react';
3
+ import { fileIconFor, fileIconOverrideFor, fileIconOverrideVersion, mountFileIconSprite, subscribeFileIconOverride } from './file-icon.ts';
4
4
 
5
5
  export interface FileIconProps {
6
6
  /* Absolute or relative; only the last segment decides which icon this is. */
@@ -8,7 +8,9 @@ export interface FileIconProps {
8
8
 
9
9
  size?: number;
10
10
  className?: string;
11
- ref?: Ref<SVGSVGElement>;
11
+
12
+ /* An `<img>` when an override gives the file an icon of the app's own, the `<svg>` otherwise. */
13
+ ref?: Ref<SVGSVGElement | HTMLImageElement>;
12
14
  }
13
15
 
14
16
  /*
@@ -19,15 +21,22 @@ export function FileIcon({ path, size = 16, className, ref }: FileIconProps) {
19
21
  // Before the browser paints, so a `<use>` never points at a symbol that is not in the document yet.
20
22
  useInsertionEffect(mountFileIconSprite, []);
21
23
 
22
- const own = fileIconOverrideFor(path);
24
+ const version = useSyncExternalStore(subscribeFileIconOverride, fileIconOverrideVersion, fileIconOverrideVersion);
25
+ const own = overrideFor(path, version);
23
26
 
27
+ // React types a readonly ref object, so the union passes to neither element without a cast; each holds one of its members.
24
28
  if (own !== null) {
25
- return <img src={own} alt="" width={size} height={size} className={clsx('file-icon', className)} />;
29
+ return <img ref={ref as Ref<HTMLImageElement>} src={own} alt="" width={size} height={size} className={clsx('file-icon', className)} />;
26
30
  }
27
31
 
28
32
  return (
29
- <svg ref={ref} className={clsx('file-icon', className)} width={size} height={size} viewBox="0 0 16 16" aria-hidden>
33
+ <svg ref={ref as Ref<SVGSVGElement>} className={clsx('file-icon', className)} width={size} height={size} viewBox="0 0 16 16" aria-hidden>
30
34
  <use href={`#${fileIconFor(path)}`} />
31
35
  </svg>
32
36
  );
33
37
  }
38
+
39
+ /* Takes the version so the compiler caches the result on it as well as on the path. */
40
+ function overrideFor(path: string, _version: number): string | null {
41
+ return fileIconOverrideFor(path);
42
+ }
package/src/Icon.tsx CHANGED
@@ -1,6 +1,6 @@
1
- import type { Ref } from 'react';
2
- import type { LucideIcon } from 'lucide-react';
3
1
  import clsx from 'clsx';
2
+ import type { LucideIcon } from 'lucide-react';
3
+ import type { Ref } from 'react';
4
4
 
5
5
  // The whole client draws at this weight. 2 is Lucide's default, but at 12 and 14 pixels next to
6
6
  // text of the same size a hair thinner keeps the glyph from outweighing the words beside it.
@@ -1,12 +1,12 @@
1
- import type { ComponentProps, ReactElement, ReactNode } from 'react';
1
+ import { useRender } from '@base-ui/react/use-render';
2
2
  import clsx from 'clsx';
3
3
  import type { LucideIcon } from 'lucide-react';
4
- import { useRender } from '@base-ui/react/use-render';
4
+ import type { ComponentProps, ReactElement, ReactNode } from 'react';
5
+ import { ICON_BUTTON_CLASS, ICON_BUTTON_ICON_SIZE, type IconButtonSize } from './icon-button-size.ts';
5
6
  import { Icon } from './Icon.tsx';
6
7
  import type { Shortcut } from './shortcut.ts';
7
8
  import { Spinner } from './Spinner.tsx';
8
9
  import { Tooltip, type TooltipSide } from './Tooltip.tsx';
9
- import { ICON_BUTTON_CLASS, ICON_BUTTON_ICON_SIZE, type IconButtonSize } from './icon-button-size.ts';
10
10
 
11
11
  export type { IconButtonSize };
12
12
 
@@ -1,14 +1,15 @@
1
- import { useId, useLayoutEffect, useRef, useState, type KeyboardEvent, type Ref } from 'react';
2
1
  import clsx from 'clsx';
3
2
  import { Ban, ChevronDown, Search, X } from 'lucide-react';
3
+ import { useId, useLayoutEffect, useRef, useState, type KeyboardEvent, type Ref } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { Button } from './Button.tsx';
6
6
  import { EmptyState } from './EmptyState.tsx';
7
7
  import { formatNumber } from './format/number.ts';
8
+ import { cellForKey, escapeClearsSearch, filterSections, isGrouped, sectionsOf, tabStopOf, type IconPickerIcons, type IconSection } from './icon-picker.ts';
8
9
  import { Icon } from './Icon.tsx';
9
10
  import { IconButton } from './IconButton.tsx';
10
- import { cellForKey, escapeClearsSearch, filterSections, isGrouped, sectionsOf, tabStopOf, type IconPickerIcons, type IconSection } from './icon-picker.ts';
11
11
  import { PopoverPopup, PopoverRoot, PopoverTrigger } from './popover/parts.tsx';
12
+ import { ScrollArea } from './ScrollArea.tsx';
12
13
  import { SectionLabel } from './SectionLabel.tsx';
13
14
  import { Tooltip } from './Tooltip.tsx';
14
15
 
@@ -277,12 +278,12 @@ function IconPickerPanel({ id, name, sections, headed, value, keywords, searchab
277
278
  )}
278
279
  </div>
279
280
  )}
280
- <div
281
- ref={area}
281
+ <ScrollArea
282
+ viewportRef={area}
282
283
  id={`${id}-grid`}
283
284
  role="radiogroup"
284
285
  aria-label={name}
285
- className={clsx('relative overflow-y-auto overscroll-contain', headed ? 'scroll-pt-7' : 'px-1.5 pt-1.5 pb-2')}
286
+ viewportClassName={clsx('relative overscroll-contain', headed ? 'scroll-pt-7' : 'px-1.5 pt-1.5 pb-2')}
286
287
  style={rows === undefined ? undefined : { height: rows * ROW_HEIGHT + (headed ? HEADED_EXTRA : BARE_EXTRA) }}
287
288
  onScroll={(event) => markStuckHeadings(event.currentTarget)}
288
289
  onKeyDown={moveFocus}
@@ -342,7 +343,7 @@ function IconPickerPanel({ id, name, sections, headed, value, keywords, searchab
342
343
  );
343
344
  })
344
345
  )}
345
- </div>
346
+ </ScrollArea>
346
347
  {onClear && (
347
348
  <div className="flex border-t border-border p-1">
348
349
  <Button size="sm" disabled={disabled || value === null} onClick={onClear}>
package/src/Input.tsx CHANGED
@@ -1,6 +1,6 @@
1
- import type { ComponentProps, ReactNode } from 'react';
2
- import type { LucideIcon } from 'lucide-react';
3
1
  import clsx from 'clsx';
2
+ import type { LucideIcon } from 'lucide-react';
3
+ import type { ComponentProps, ReactNode } from 'react';
4
4
  import { fieldControlProps, useFieldControl } from './field-context.ts';
5
5
  import { Icon } from './Icon.tsx';
6
6
 
package/src/Kbd.tsx CHANGED
@@ -1,5 +1,5 @@
1
- import type { ReactNode, Ref } from 'react';
2
1
  import clsx from 'clsx';
2
+ import type { ReactNode, Ref } from 'react';
3
3
  import { isApplePlatform } from './platform.ts';
4
4
  import { formatShortcut, shortcutParts, type Shortcut } from './shortcut.ts';
5
5
 
package/src/ListRow.tsx CHANGED
@@ -1,5 +1,5 @@
1
- import clsx from 'clsx';
2
1
  import { useRender } from '@base-ui/react/use-render';
2
+ import clsx from 'clsx';
3
3
 
4
4
  const VARIANT = {
5
5
  /* A row of a list that reads as a table and fills the width of its panel: commits, processes,
package/src/Meter.tsx CHANGED
@@ -1,5 +1,5 @@
1
- import type { Ref } from 'react';
2
1
  import clsx from 'clsx';
2
+ import type { Ref } from 'react';
3
3
 
4
4
  export interface MeterProps {
5
5
  /* In the unit of `min` and `max`, held to them. Null draws an empty track, for a level not measured yet. */
@@ -1,7 +1,7 @@
1
- import { useLayoutEffect, useState, type Ref } from 'react';
2
1
  import clsx from 'clsx';
3
- import { fitMiddle, type MiddleCut } from './middle-truncate.ts';
2
+ import { useLayoutEffect, useState, type Ref } from 'react';
4
3
  import { mergeRefs } from './merge-refs.ts';
4
+ import { fitMiddle, type MiddleCut } from './middle-truncate.ts';
5
5
  import { Tooltip } from './Tooltip.tsx';
6
6
 
7
7
  export interface MiddleTruncateProps {
@@ -1,6 +1,6 @@
1
- import type { ReactNode, Ref } from 'react';
2
- import type { LucideIcon } from 'lucide-react';
3
1
  import clsx from 'clsx';
2
+ import type { LucideIcon } from 'lucide-react';
3
+ import type { ReactNode, Ref } from 'react';
4
4
  import { EmptyState } from './EmptyState.tsx';
5
5
 
6
6
  export interface PanelEmptyProps {
@@ -1,6 +1,6 @@
1
- import type { ReactNode } from 'react';
2
- import clsx from 'clsx';
3
1
  import { useRender } from '@base-ui/react/use-render';
2
+ import clsx from 'clsx';
3
+ import type { ReactNode } from 'react';
4
4
  import { SectionLabel } from './SectionLabel.tsx';
5
5
 
6
6
  export type PanelHeaderProps = useRender.ComponentProps<'header'> & {
package/src/Pill.tsx CHANGED
@@ -1,5 +1,5 @@
1
- import type { ReactNode, Ref } from 'react';
2
1
  import clsx from 'clsx';
2
+ import type { ReactNode, Ref } from 'react';
3
3
 
4
4
  /* Every ground is an alpha over whatever the pill stands on, the way a border is: a solid sunken ground read as a hole on a dark row. */
5
5
  const TONES = {
@@ -1,7 +1,7 @@
1
- import type { ComponentProps, MouseEvent, ReactNode, Ref } from 'react';
2
1
  import clsx from 'clsx';
3
- import { useTranslation } from 'react-i18next';
4
2
  import { ChevronDown, History } from 'lucide-react';
3
+ import type { ComponentProps, MouseEvent, ReactNode, Ref } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
5
  import { Icon } from './Icon.tsx';
6
6
  import * as Menu from './menu/index.parts.ts';
7
7
  import { ProjectSwitcherRow } from './project-switcher-row.tsx';
@@ -1,6 +1,6 @@
1
+ import type { LucideIcon } from 'lucide-react';
1
2
  import { useState, type ReactNode, type Ref } from 'react';
2
3
  import { useTranslation } from 'react-i18next';
3
- import type { LucideIcon } from 'lucide-react';
4
4
  import { Button } from './Button.tsx';
5
5
  import { DialogDescription, DialogFooter, DialogPopup, DialogRoot, DialogTitle } from './dialog/parts.tsx';
6
6
  import { Field, FormError } from './Field.tsx';
@@ -1,5 +1,5 @@
1
- import type { Ref } from 'react';
2
1
  import clsx from 'clsx';
2
+ import type { Ref } from 'react';
3
3
  import { formatAge, formatDayClockWithYear, useFormatLocale } from './format/index.ts';
4
4
  import { Tooltip, type TooltipSide } from './Tooltip.tsx';
5
5
  import { useNow } from './useNow.ts';
@@ -0,0 +1,47 @@
1
+ import { useRender } from '@base-ui/react/use-render';
2
+ import clsx from 'clsx';
3
+ import { useState, type CSSProperties, type Ref } from 'react';
4
+ import { mergeRefs } from './merge-refs.ts';
5
+ import { useOverlayScrollbars, type ScrollAreaAxis } from './useOverlayScrollbars.tsx';
6
+
7
+ export type ScrollAreaProps = Omit<useRender.ComponentProps<'div'>, 'className' | 'ref'> & {
8
+ /* The directions the content scrolls in; the other one clips. */
9
+ axis?: ScrollAreaAxis;
10
+
11
+ /* The outer box, which holds the bars and takes the size. */
12
+ className?: string;
13
+ ref?: Ref<HTMLDivElement>;
14
+
15
+ /* The element that scrolls, for a virtualizer or code that sets the scroll position. */
16
+ viewportRef?: Ref<HTMLElement>;
17
+
18
+ /* Padding and layout of the content. `render` and the remaining props land on the viewport too. */
19
+ viewportClassName?: string;
20
+
21
+ /* Pixels of the bottom edge a layer over the area covers, such as a docked input. The bars end above it. */
22
+ coveredBottom?: number;
23
+ };
24
+
25
+ /* A box that scrolls natively under thin bars that float over its content and show only while it scrolls. */
26
+ export function ScrollArea({ axis = 'y', className, viewportClassName, viewportRef, coveredBottom, ref, render, ...props }: ScrollAreaProps) {
27
+ const [viewport, setViewport] = useState<HTMLElement | null>(null);
28
+ const scrollbars = useOverlayScrollbars(viewport, { axis });
29
+
30
+ const content = useRender<Record<string, never>, HTMLElement>({
31
+ render,
32
+ ref: mergeRefs<HTMLElement>(setViewport, viewportRef),
33
+ defaultTagName: 'div',
34
+ props: { ...props, 'data-axis': axis, className: clsx('ade-scroll-viewport', viewportClassName) }
35
+ });
36
+
37
+ return (
38
+ <div
39
+ ref={ref}
40
+ className={clsx('ade-scroll-area', className)}
41
+ style={coveredBottom ? ({ '--scrollbar-covered-bottom': `${coveredBottom}px` } as CSSProperties) : undefined}
42
+ >
43
+ {content}
44
+ {scrollbars}
45
+ </div>
46
+ );
47
+ }
@@ -1,5 +1,5 @@
1
- import clsx from 'clsx';
2
1
  import { useRender } from '@base-ui/react/use-render';
2
+ import clsx from 'clsx';
3
3
 
4
4
  /* A label takes the line height of the row it sits in, which is why the size carries `/[inherit]`. */
5
5
  const SECTION_LABEL = 'text-xs/[inherit] font-medium text-text-faint';
@@ -1,5 +1,5 @@
1
- import { useEffect, useRef, useState, type Ref } from 'react';
2
1
  import clsx from 'clsx';
2
+ import { useEffect, useRef, useState, type Ref } from 'react';
3
3
  import { mergeRefs } from './merge-refs.ts';
4
4
  import { Tooltip } from './Tooltip.tsx';
5
5
  import { useMeasuredWidth } from './useMeasuredWidth.ts';
package/src/Segmented.tsx CHANGED
@@ -1,6 +1,6 @@
1
- import type { KeyboardEvent, Ref } from 'react';
2
1
  import clsx from 'clsx';
3
2
  import type { LucideIcon } from 'lucide-react';
3
+ import type { KeyboardEvent, Ref } from 'react';
4
4
  import { Icon } from './Icon.tsx';
5
5
  import { segmentedStop, segmentedTarget } from './segmented-keys.ts';
6
6
  import { Tooltip } from './Tooltip.tsx';
package/src/Select.tsx CHANGED
@@ -1,7 +1,7 @@
1
- import type { Ref } from 'react';
2
1
  import { Select as BaseSelect } from '@base-ui/react/select';
3
2
  import clsx from 'clsx';
4
3
  import { ChevronDown } from 'lucide-react';
4
+ import { useState, type Ref } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { Icon } from './Icon.tsx';
7
7
  import { MENU_LABEL } from './menu/parts.tsx';
@@ -9,6 +9,7 @@ import { renderPositioner } from './popup-layer.ts';
9
9
  import { RowContent } from './select-row.tsx';
10
10
  import { isGroup, rowClassName, triggerClassName, type SelectGroup, type SelectItem } from './select-shared.ts';
11
11
  import { SelectSearch } from './SelectSearch.tsx';
12
+ import { useOverlayScrollbars } from './useOverlayScrollbars.tsx';
12
13
 
13
14
  export type { SelectGroup, SelectItem };
14
15
 
@@ -74,6 +75,8 @@ function PlainSelect<T extends string>({
74
75
  const groups = items.length > 0 && isGroup(items[0]!) ? (items as SelectGroup<T>[]) : null;
75
76
  const flat = groups ? groups.flatMap((group) => group.items) : (items as SelectItem<T>[]);
76
77
  const selected = flat.find((item) => item.value === value) ?? null;
78
+ const [list, setList] = useState<HTMLDivElement | null>(null);
79
+ const scrollbars = useOverlayScrollbars(list);
77
80
 
78
81
  return (
79
82
  <BaseSelect.Root
@@ -99,7 +102,7 @@ function PlainSelect<T extends string>({
99
102
  alignItemWithTrigger={false}
100
103
  >
101
104
  <BaseSelect.Popup className="menu-popup">
102
- <BaseSelect.List className="max-h-80 overflow-auto">
105
+ <BaseSelect.List ref={setList} className="max-h-80 overflow-auto">
103
106
  {groups
104
107
  ? groups.map((group) => (
105
108
  <BaseSelect.Group key={group.label}>
@@ -112,6 +115,7 @@ function PlainSelect<T extends string>({
112
115
  : flat.map((item) => <Row key={item.value} item={item} picked={item.value === value} />)}
113
116
  </BaseSelect.List>
114
117
  </BaseSelect.Popup>
118
+ {scrollbars}
115
119
  </BaseSelect.Positioner>
116
120
  </BaseSelect.Portal>
117
121
  </BaseSelect.Root>
@@ -1,14 +1,15 @@
1
- import { useState } from 'react';
2
1
  import { Combobox } from '@base-ui/react/combobox';
3
2
  import clsx from 'clsx';
4
3
  import { ChevronDown, Search } from 'lucide-react';
4
+ import { useState } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { Icon } from './Icon.tsx';
7
7
  import { MENU_LABEL } from './menu/parts.tsx';
8
8
  import { renderPositioner } from './popup-layer.ts';
9
- import type { SelectProps } from './Select.tsx';
10
9
  import { RowContent } from './select-row.tsx';
11
10
  import { isGroup, rowClassName, triggerClassName, type SelectGroup, type SelectItem } from './select-shared.ts';
11
+ import type { SelectProps } from './Select.tsx';
12
+ import { useOverlayScrollbars } from './useOverlayScrollbars.tsx';
12
13
 
13
14
  /* The `Select` with a search field on top of its options, for lists too long to scan. A combobox of Base UI brings the filtering and the keyboard. */
14
15
  export function SelectSearch<T extends string>({
@@ -38,6 +39,8 @@ export function SelectSearch<T extends string>({
38
39
  : flat.map((item) => item.value);
39
40
 
40
41
  const searchLabel = searchPlaceholder ?? t('select.search');
42
+ const [list, setList] = useState<HTMLDivElement | null>(null);
43
+ const scrollbars = useOverlayScrollbars(list);
41
44
 
42
45
  const renderRow = (itemValue: T) => {
43
46
  const item = byValue.get(itemValue)!;
@@ -87,16 +90,20 @@ export function SelectSearch<T extends string>({
87
90
  />
88
91
  </label>
89
92
  <Combobox.Empty className="px-3 py-4 text-center text-xs text-text-faint empty:hidden">{t('select.noMatch', { query })}</Combobox.Empty>
90
- <Combobox.List className="min-h-0 overflow-auto">
91
- {groups
92
- ? (group: { value: string; items: T[] }) => (
93
- <Combobox.Group key={group.value} items={group.items}>
94
- <Combobox.GroupLabel className={MENU_LABEL}>{group.value}</Combobox.GroupLabel>
95
- <Combobox.Collection>{renderRow}</Combobox.Collection>
96
- </Combobox.Group>
97
- )
98
- : renderRow}
99
- </Combobox.List>
93
+ {/* Out to the popup's edge on the right, so the bar lies where a menu's does. */}
94
+ <div className="relative -mr-1 flex min-h-0 flex-col">
95
+ <Combobox.List ref={setList} className="min-h-0 overflow-auto pr-1">
96
+ {groups
97
+ ? (group: { value: string; items: T[] }) => (
98
+ <Combobox.Group key={group.value} items={group.items}>
99
+ <Combobox.GroupLabel className={MENU_LABEL}>{group.value}</Combobox.GroupLabel>
100
+ <Combobox.Collection>{renderRow}</Combobox.Collection>
101
+ </Combobox.Group>
102
+ )
103
+ : renderRow}
104
+ </Combobox.List>
105
+ {scrollbars}
106
+ </div>
100
107
  </Combobox.Popup>
101
108
  </Combobox.Positioner>
102
109
  </Combobox.Portal>
package/src/Separator.tsx CHANGED
@@ -1,5 +1,5 @@
1
- import type { Ref } from 'react';
2
1
  import clsx from 'clsx';
2
+ import type { Ref } from 'react';
3
3
 
4
4
  export interface SeparatorProps {
5
5
  orientation?: 'vertical' | 'horizontal';
@@ -1,8 +1,8 @@
1
1
  import { useEffect, useLayoutEffect, useRef, useState, type CSSProperties } from 'react';
2
2
  import { createPortal } from 'react-dom';
3
3
  import { isApplePlatform } from './platform.ts';
4
- import { formatShortcut } from './shortcut.ts';
5
4
  import { ModifierHold, placeHint, shortcutHintTargets } from './shortcut-hints.ts';
5
+ import { formatShortcut } from './shortcut.ts';
6
6
 
7
7
  interface PlacedHint {
8
8
  id: number;
package/src/Skeleton.tsx CHANGED
@@ -1,5 +1,5 @@
1
- import type { Ref } from 'react';
2
1
  import clsx from 'clsx';
2
+ import type { Ref } from 'react';
3
3
 
4
4
  export interface SkeletonProps {
5
5
  /* The width, and a height other than the one line of 16px. */
package/src/Slider.tsx CHANGED
@@ -1,6 +1,6 @@
1
- import { useId, type Ref } from 'react';
2
- import clsx from 'clsx';
3
1
  import { Slider as BaseSlider } from '@base-ui/react/slider';
2
+ import clsx from 'clsx';
3
+ import { useId, type Ref } from 'react';
4
4
  import { formatFixed } from './format/number.ts';
5
5
  import { placesOf } from './step-places.ts';
6
6
 
@@ -1,5 +1,5 @@
1
- import { useEffect, useRef, useState, type KeyboardEvent as ReactKeyboardEvent, type ReactNode, type Ref, type RefObject } from 'react';
2
1
  import clsx from 'clsx';
2
+ import { useEffect, useRef, useState, type KeyboardEvent as ReactKeyboardEvent, type ReactNode, type Ref, type RefObject } from 'react';
3
3
  import { ColumnResizeHandle } from './ColumnResizeHandle.tsx';
4
4
  import { mergeRefs } from './merge-refs.ts';
5
5
  import { useColumnResize } from './useColumnResize.ts';
package/src/Spinner.tsx CHANGED
@@ -1,5 +1,5 @@
1
- import type { ComponentProps, CSSProperties } from 'react';
2
1
  import clsx from 'clsx';
2
+ import type { ComponentProps, CSSProperties } from 'react';
3
3
 
4
4
  const DOTS = [0, 1, 2];
5
5
 
@@ -1,6 +1,6 @@
1
- import { useState, type ComponentProps, type ReactNode } from 'react';
2
1
  import clsx from 'clsx';
3
2
  import { ChevronDown } from 'lucide-react';
3
+ import { useState, type ComponentProps, type ReactNode } from 'react';
4
4
  import { splitFrameClass, splitHalfClass, type ButtonSize, type ButtonVariant } from './button-class.ts';
5
5
  import { Icon } from './Icon.tsx';
6
6
  import { MenuPopup, MenuRoot, MenuTrigger, type MenuPopupProps } from './menu/parts.tsx';