@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.
- package/dist/Banner.d.ts +1 -1
- package/dist/ButtonGroup.js +1 -1
- package/dist/Checkbox.js +1 -1
- package/dist/ChoiceCards.d.ts +1 -1
- package/dist/ChoiceCards.js +2 -2
- package/dist/CloseButton.js +1 -1
- package/dist/ColorSwatch.d.ts +1 -1
- package/dist/ColorSwatch.js +1 -1
- package/dist/ColorWheelPicker.js +1 -1
- package/dist/CopyButton.js +1 -1
- package/dist/DockShell.js +1 -1
- package/dist/EmptyState.d.ts +1 -1
- package/dist/EmptyState.js +1 -1
- package/dist/ErrorBoundary.js +122 -112
- package/dist/Field.d.ts +1 -1
- package/dist/Field.js +2 -2
- package/dist/FileIcon.d.ts +1 -1
- package/dist/FileIcon.js +44 -36
- package/dist/Icon.d.ts +1 -1
- package/dist/IconButton.d.ts +3 -3
- package/dist/IconButton.js +2 -2
- package/dist/IconPicker.js +35 -32
- package/dist/Input.d.ts +1 -1
- package/dist/ListRow.js +1 -1
- package/dist/MiddleTruncate.js +2 -2
- package/dist/PanelEmpty.d.ts +1 -1
- package/dist/PanelHeader.d.ts +1 -1
- package/dist/PanelHeader.js +1 -1
- package/dist/ProjectSwitcher.js +1 -1
- package/dist/PromptDialog.d.ts +1 -1
- package/dist/ScrollArea.d.ts +12 -0
- package/dist/ScrollArea.js +144 -0
- package/dist/SectionLabel.js +1 -1
- package/dist/SegmentBar.js +1 -1
- package/dist/Segmented.d.ts +1 -1
- package/dist/Select.d.ts +1 -1
- package/dist/Select.js +22 -16
- package/dist/SelectSearch.js +49 -33
- package/dist/ShortcutHints.js +1 -1
- package/dist/Slider.js +2 -2
- package/dist/SlidingColumn.js +1 -1
- package/dist/SplitButton.js +1 -1
- package/dist/Surface.js +1 -1
- package/dist/Switch.js +1 -1
- package/dist/TextMenu.js +2 -2
- package/dist/Toasts.js +2 -2
- package/dist/Tooltip.d.ts +1 -1
- package/dist/Tooltip.js +1 -1
- package/dist/UIProvider.d.ts +1 -1
- package/dist/Waveform.js +1 -1
- package/dist/Wipe.js +2 -2
- package/dist/combobox/parts.d.ts +3 -3
- package/dist/combobox/parts.js +53 -32
- package/dist/command-palette/parts.d.ts +1 -1
- package/dist/command-palette/parts.js +22 -7
- package/dist/command-palette/virtual.js +4 -4
- package/dist/dialog/parts.d.ts +1 -1
- package/dist/dialog/parts.js +2 -2
- package/dist/file-icon.d.ts +2 -0
- package/dist/file-icon.js +12 -0
- package/dist/file-tree/parts.d.ts +2 -2
- package/dist/file-tree/parts.js +3 -3
- package/dist/file-tree/use-file-tree-shift.d.ts +1 -1
- package/dist/file-tree/use-file-tree-shift.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/key-value-list/parts.js +1 -1
- package/dist/menu/parts.d.ts +3 -3
- package/dist/menu/parts.js +46 -30
- package/dist/popover/parts.d.ts +3 -3
- package/dist/popover/parts.js +110 -71
- package/dist/project-switcher-row.js +1 -1
- package/dist/scrollbar-geometry.d.ts +15 -0
- package/dist/scrollbar-geometry.js +48 -0
- package/dist/settings/MasterDetail.js +40 -30
- package/dist/settings/SettingsDialog.d.ts +1 -1
- package/dist/settings/SettingsDialog.js +27 -11
- package/dist/settings/SettingsRow.d.ts +1 -1
- package/dist/settings/SettingsRow.js +1 -1
- package/dist/settings/SettingsSection.d.ts +1 -1
- package/dist/tabs/parts.d.ts +1 -1
- package/dist/tabs/parts.js +3 -3
- package/dist/testing/dedupe.js +1 -1
- package/dist/theme.css +91 -1
- package/dist/tree/parts.js +2 -2
- package/dist/useOverlayScrollbars.d.ts +7 -0
- package/dist/useOverlayScrollbars.js +281 -0
- package/dist/workspace/PaneBar.js +2 -2
- package/dist/workspace/SplitView.d.ts +1 -1
- package/dist/workspace/SplitView.js +4 -4
- package/dist/workspace/TabStrip.d.ts +1 -1
- package/dist/workspace/TabStrip.js +2 -2
- package/dist/workspace/Workspace.d.ts +1 -1
- package/dist/workspace/Workspace.js +2 -2
- package/dist/workspace/WorkspaceRail.js +1 -1
- package/dist/workspace/WorkspaceTopRow.js +1 -1
- package/package.json +1 -1
- package/src/AccentSwatches.tsx +1 -1
- package/src/Banner.tsx +1 -1
- package/src/Button.tsx +1 -1
- package/src/ButtonGroup.tsx +1 -1
- package/src/Checkbox.tsx +2 -2
- package/src/ChoiceCards.tsx +3 -3
- package/src/CloseButton.tsx +2 -2
- package/src/ColorSwatch.tsx +2 -2
- package/src/ColorWheelPicker.tsx +1 -1
- package/src/ColumnResizeHandle.tsx +1 -1
- package/src/CopyButton.tsx +1 -1
- package/src/DockShell.tsx +1 -1
- package/src/EmptyState.tsx +2 -2
- package/src/ErrorBoundary.tsx +4 -3
- package/src/Field.tsx +2 -2
- package/src/FileIcon.tsx +15 -6
- package/src/Icon.tsx +2 -2
- package/src/IconButton.tsx +3 -3
- package/src/IconPicker.tsx +7 -6
- package/src/Input.tsx +2 -2
- package/src/Kbd.tsx +1 -1
- package/src/ListRow.tsx +1 -1
- package/src/Meter.tsx +1 -1
- package/src/MiddleTruncate.tsx +2 -2
- package/src/PanelEmpty.tsx +2 -2
- package/src/PanelHeader.tsx +2 -2
- package/src/Pill.tsx +1 -1
- package/src/ProjectSwitcher.tsx +2 -2
- package/src/PromptDialog.tsx +1 -1
- package/src/RelativeTime.tsx +1 -1
- package/src/ScrollArea.tsx +47 -0
- package/src/SectionLabel.tsx +1 -1
- package/src/SegmentBar.tsx +1 -1
- package/src/Segmented.tsx +1 -1
- package/src/Select.tsx +6 -2
- package/src/SelectSearch.tsx +19 -12
- package/src/Separator.tsx +1 -1
- package/src/ShortcutHints.tsx +1 -1
- package/src/Skeleton.tsx +1 -1
- package/src/Slider.tsx +2 -2
- package/src/SlidingColumn.tsx +1 -1
- package/src/Spinner.tsx +1 -1
- package/src/SplitButton.tsx +1 -1
- package/src/Stepper.tsx +1 -1
- package/src/Surface.tsx +1 -1
- package/src/Switch.tsx +2 -2
- package/src/TextMenu.tsx +2 -2
- package/src/Tile.tsx +1 -1
- package/src/Toasts.tsx +2 -2
- package/src/Tooltip.tsx +2 -2
- package/src/UIProvider.tsx +1 -1
- package/src/Waveform.tsx +1 -1
- package/src/Wipe.tsx +2 -2
- package/src/ZoomControls.tsx +1 -1
- package/src/combobox/parts.tsx +9 -4
- package/src/command-palette/parts.tsx +25 -19
- package/src/command-palette/virtual.tsx +4 -4
- package/src/dialog/parts.tsx +2 -2
- package/src/file-icon.ts +15 -0
- package/src/file-tree/parts.tsx +6 -6
- package/src/file-tree/use-empty-lines.ts +1 -1
- package/src/file-tree/use-file-tree-shift.tsx +2 -2
- package/src/file-tree/use-slots.ts +2 -2
- package/src/index.ts +2 -0
- package/src/key-value-list/parts.tsx +1 -1
- package/src/menu/parts.tsx +11 -6
- package/src/popover/parts.tsx +13 -5
- package/src/project-switcher-row.tsx +2 -2
- package/src/scrollbar-geometry.ts +67 -0
- package/src/select-shared.ts +1 -1
- package/src/settings/DetailHeader.tsx +1 -1
- package/src/settings/MasterDetail.tsx +9 -6
- package/src/settings/SettingsDialog.tsx +16 -9
- package/src/settings/SettingsRow.tsx +1 -1
- package/src/settings/SettingsSection.tsx +1 -1
- package/src/tabs/parts.tsx +4 -4
- package/src/testing/dedupe.ts +1 -1
- package/src/theme.css +91 -1
- package/src/tree/parts.tsx +2 -2
- package/src/useOverlayScrollbars.tsx +263 -0
- package/src/workspace/DropPreview.tsx +1 -1
- package/src/workspace/PaneBar.tsx +2 -2
- package/src/workspace/SplitView.tsx +5 -5
- package/src/workspace/TabStrip.tsx +3 -3
- package/src/workspace/Workspace.tsx +2 -2
- package/src/workspace/WorkspaceRail.tsx +1 -1
- package/src/workspace/WorkspaceTopRow.tsx +1 -1
package/src/Stepper.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { Ref } from 'react';
|
|
2
1
|
import clsx from 'clsx';
|
|
3
2
|
import { Minus, Plus } from 'lucide-react';
|
|
3
|
+
import type { Ref } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { ButtonGroup } from './ButtonGroup.tsx';
|
|
6
6
|
import { formatFixed } from './format/number.ts';
|
package/src/Surface.tsx
CHANGED
package/src/Switch.tsx
CHANGED
package/src/TextMenu.tsx
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { useRef, useState, type ComponentProps, type KeyboardEvent as ReactKeyboardEvent, type ReactNode } from 'react';
|
|
2
1
|
import { Copy, Scan } from 'lucide-react';
|
|
2
|
+
import { useRef, useState, type ComponentProps, type KeyboardEvent as ReactKeyboardEvent, type ReactNode } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { copyText } from './clipboard.ts';
|
|
5
5
|
import { Icon } from './Icon.tsx';
|
|
6
6
|
import { Kbd } from './Kbd.tsx';
|
|
7
|
-
import { mergeRefs } from './merge-refs.ts';
|
|
8
7
|
import { ContextMenuRoot, ContextMenuTrigger, MenuItem, MenuPopup, MenuSeparator } from './menu/parts.tsx';
|
|
8
|
+
import { mergeRefs } from './merge-refs.ts';
|
|
9
9
|
import { isApplePlatform } from './platform.ts';
|
|
10
10
|
import { selectAllWithin, selectionWithin } from './selection.ts';
|
|
11
11
|
import { EDIT_SHORTCUTS, matchesShortcut } from './shortcut.ts';
|
package/src/Tile.tsx
CHANGED
package/src/Toasts.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useState, type ReactNode, type Ref } from 'react';
|
|
2
1
|
import clsx from 'clsx';
|
|
3
|
-
import { useTranslation } from 'react-i18next';
|
|
4
2
|
import { CircleAlert, CircleCheck, Trash, X } from 'lucide-react';
|
|
3
|
+
import { useState, type ReactNode, type Ref } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { useStore } from 'zustand';
|
|
6
6
|
import { Button } from './Button.tsx';
|
|
7
7
|
import { Icon } from './Icon.tsx';
|
package/src/Tooltip.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type ComponentProps, type ReactElement, type ReactNode } from 'react';
|
|
2
1
|
import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip';
|
|
2
|
+
import { type ComponentProps, type ReactElement, type ReactNode } from 'react';
|
|
3
3
|
import { Kbd } from './Kbd.tsx';
|
|
4
|
-
import type { Shortcut } from './shortcut.ts';
|
|
5
4
|
import { registerShortcutHint } from './shortcut-hints.ts';
|
|
5
|
+
import type { Shortcut } from './shortcut.ts';
|
|
6
6
|
|
|
7
7
|
export type TooltipSide = 'top' | 'bottom' | 'left' | 'right';
|
|
8
8
|
|
package/src/UIProvider.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useLayoutEffect, useState, type ReactNode } from 'react';
|
|
2
1
|
import type { i18n as I18n } from 'i18next';
|
|
2
|
+
import { useLayoutEffect, useState, type ReactNode } from 'react';
|
|
3
3
|
import { I18nextProvider } from 'react-i18next';
|
|
4
4
|
import { setFormatSource, type FormatSource } from './format/locale.ts';
|
|
5
5
|
import { addUiResources } from './locales.ts';
|
package/src/Waveform.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useRef, type KeyboardEvent, type PointerEvent, type Ref } from 'react';
|
|
2
1
|
import clsx from 'clsx';
|
|
2
|
+
import { useRef, type KeyboardEvent, type PointerEvent, type Ref } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { formatClockDuration } from './format/duration.ts';
|
|
5
5
|
import { mergeRefs } from './merge-refs.ts';
|
package/src/Wipe.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { useRef, type KeyboardEvent, type PointerEvent, type ReactNode, type Ref } from 'react';
|
|
2
1
|
import clsx from 'clsx';
|
|
3
2
|
import { ChevronsLeftRight } from 'lucide-react';
|
|
4
|
-
import {
|
|
3
|
+
import { useRef, type KeyboardEvent, type PointerEvent, type ReactNode, type Ref } from 'react';
|
|
5
4
|
import { formatPercent } from './format/number.ts';
|
|
5
|
+
import { Icon } from './Icon.tsx';
|
|
6
6
|
import { mergeRefs } from './merge-refs.ts';
|
|
7
7
|
import { splitAt, splitForKey } from './wipe-split.ts';
|
|
8
8
|
|
package/src/ZoomControls.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { Ref } from 'react';
|
|
2
1
|
import clsx from 'clsx';
|
|
3
2
|
import { Maximize, Minus, Plus, Scan, type LucideIcon } from 'lucide-react';
|
|
3
|
+
import type { Ref } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { ButtonGroup } from './ButtonGroup.tsx';
|
|
6
6
|
import { Icon } from './Icon.tsx';
|
package/src/combobox/parts.tsx
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Autocomplete } from '@base-ui/react/autocomplete';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import type { LucideIcon } from 'lucide-react';
|
|
4
|
+
import { useState, type ComponentProps, type ReactNode } from 'react';
|
|
4
5
|
import { useTranslation } from 'react-i18next';
|
|
5
|
-
import { Autocomplete } from '@base-ui/react/autocomplete';
|
|
6
6
|
import { withClass } from '../class-name.ts';
|
|
7
7
|
import { fieldControlProps, useFieldControl } from '../field-context.ts';
|
|
8
8
|
import { Icon } from '../Icon.tsx';
|
|
9
9
|
import { Kbd } from '../Kbd.tsx';
|
|
10
10
|
import { MENU_LABEL } from '../menu/parts.tsx';
|
|
11
|
+
import { mergeRefs } from '../merge-refs.ts';
|
|
11
12
|
import { splitPlacement, type PlacementKey } from '../placement.ts';
|
|
12
13
|
import { renderPositioner } from '../popup-layer.ts';
|
|
13
14
|
import type { Shortcut } from '../shortcut.ts';
|
|
15
|
+
import { useOverlayScrollbars } from '../useOverlayScrollbars.tsx';
|
|
14
16
|
|
|
15
17
|
/* It takes no room while it has nothing to say. */
|
|
16
18
|
const LIST_NOTE = 'text-center text-xs text-text-faint not-empty:px-3 not-empty:py-4';
|
|
@@ -63,13 +65,16 @@ type Placement = Pick<ComponentProps<typeof Autocomplete.Positioner>, PlacementK
|
|
|
63
65
|
|
|
64
66
|
export type ComboboxPopupProps = ComponentProps<typeof Autocomplete.Popup> & Placement;
|
|
65
67
|
|
|
66
|
-
export function ComboboxPopup({ side = 'bottom', align = 'start', sideOffset = 6, className, ...props }: ComboboxPopupProps) {
|
|
68
|
+
export function ComboboxPopup({ side = 'bottom', align = 'start', sideOffset = 6, className, ref, ...props }: ComboboxPopupProps) {
|
|
67
69
|
const [placement, popupProps] = splitPlacement(props);
|
|
70
|
+
const [popup, setPopup] = useState<HTMLDivElement | null>(null);
|
|
71
|
+
const scrollbars = useOverlayScrollbars(popup);
|
|
68
72
|
|
|
69
73
|
return (
|
|
70
74
|
<Autocomplete.Portal>
|
|
71
75
|
<Autocomplete.Positioner className="popup-positioner" render={renderPositioner} {...placement} side={side} align={align} sideOffset={sideOffset}>
|
|
72
|
-
<Autocomplete.Popup className={withClass('menu-popup min-w-(--anchor-width)', className)} {...popupProps} />
|
|
76
|
+
<Autocomplete.Popup ref={mergeRefs(setPopup, ref)} className={withClass('menu-popup min-w-(--anchor-width)', className)} {...popupProps} />
|
|
77
|
+
{scrollbars}
|
|
73
78
|
</Autocomplete.Positioner>
|
|
74
79
|
</Autocomplete.Portal>
|
|
75
80
|
);
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Autocomplete } from '@base-ui/react/autocomplete';
|
|
2
|
+
import { useRender } from '@base-ui/react/use-render';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
4
|
import { Search } from 'lucide-react';
|
|
5
|
+
import { useContext, useLayoutEffect, useRef, useState, type ComponentProps, type ReactNode } from 'react';
|
|
6
|
+
import { flushSync } from 'react-dom';
|
|
5
7
|
import { useTranslation } from 'react-i18next';
|
|
6
|
-
import { Autocomplete } from '@base-ui/react/autocomplete';
|
|
7
|
-
import { useRender } from '@base-ui/react/use-render';
|
|
8
8
|
import { withClass } from '../class-name.ts';
|
|
9
9
|
import { DialogPopup, DialogRoot, type DialogPopupProps } from '../dialog/parts.tsx';
|
|
10
10
|
import { Icon } from '../Icon.tsx';
|
|
11
11
|
import { Kbd } from '../Kbd.tsx';
|
|
12
12
|
import { mergeRefs } from '../merge-refs.ts';
|
|
13
13
|
import { KEY_SHORTCUTS } from '../shortcut.ts';
|
|
14
|
+
import { useOverlayScrollbars } from '../useOverlayScrollbars.tsx';
|
|
14
15
|
import { PaletteRefsContext } from './context.ts';
|
|
15
16
|
|
|
16
17
|
const NO_ITEMS: readonly never[] = [];
|
|
@@ -188,25 +189,30 @@ export function CommandPaletteInput({ leading, trailing, escapeHint = true, mono
|
|
|
188
189
|
export function CommandPaletteList({ className, ref, onFocus, ...props }: ComponentProps<typeof Autocomplete.List>) {
|
|
189
190
|
const { t } = useTranslation('ui');
|
|
190
191
|
const refs = useContext(PaletteRefsContext);
|
|
192
|
+
const [list, setList] = useState<HTMLDivElement | null>(null);
|
|
193
|
+
const scrollbars = useOverlayScrollbars(list);
|
|
191
194
|
|
|
192
195
|
return (
|
|
193
|
-
<
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
onFocus
|
|
200
|
-
|
|
196
|
+
<div className="relative flex min-h-0 flex-col">
|
|
197
|
+
<Autocomplete.List
|
|
198
|
+
ref={mergeRefs(ref, refs?.list, setList)}
|
|
199
|
+
aria-label={t('palette.results')}
|
|
200
|
+
className={withClass('max-h-[50vh] min-h-0 overflow-y-auto overscroll-contain scroll-py-1.5 not-empty:p-1.5', className)}
|
|
201
|
+
{...props}
|
|
202
|
+
onFocus={(event) => {
|
|
203
|
+
onFocus?.(event);
|
|
204
|
+
const row = event.target.closest('[role="option"]');
|
|
201
205
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
206
|
+
if (row === null || !event.currentTarget.contains(row) || refs?.input.current == null) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
205
209
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
+
refs.highlight(row);
|
|
211
|
+
refs.input.current.focus({ preventScroll: true });
|
|
212
|
+
}}
|
|
213
|
+
/>
|
|
214
|
+
{scrollbars}
|
|
215
|
+
</div>
|
|
210
216
|
);
|
|
211
217
|
}
|
|
212
218
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Fragment, useLayoutEffect, useMemo, useRef, type ComponentProps, type ReactNode } from 'react';
|
|
2
|
-
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
3
1
|
import { Autocomplete } from '@base-ui/react/autocomplete';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
3
|
+
import { Fragment, useLayoutEffect, useMemo, useRef, type ComponentProps, type ReactNode } from 'react';
|
|
6
4
|
import { MENU_LABEL } from '../menu/parts.tsx';
|
|
7
5
|
import { mergeRefs } from '../merge-refs.ts';
|
|
6
|
+
import { useCommandPaletteVirtualScroll } from './context.ts';
|
|
7
|
+
import { CommandPaletteList } from './parts.tsx';
|
|
8
8
|
|
|
9
9
|
export interface CommandPaletteVirtualItemProps {
|
|
10
10
|
index: number;
|
package/src/dialog/parts.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createContext, useContext, useState, type ComponentProps } from 'react';
|
|
2
|
-
import clsx from 'clsx';
|
|
3
1
|
import { Dialog as BaseDialog } from '@base-ui/react/dialog';
|
|
4
2
|
import { useRender } from '@base-ui/react/use-render';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { createContext, useContext, useState, type ComponentProps } from 'react';
|
|
5
5
|
import { withClass } from '../class-name.ts';
|
|
6
6
|
import { useDialogLayer } from '../dialog-layer.ts';
|
|
7
7
|
|
package/src/file-icon.ts
CHANGED
|
@@ -30,12 +30,27 @@ const resolveIcon: ReturnType<typeof createFileTreeIconResolver>['resolveIcon']
|
|
|
30
30
|
export const fileIconFor = (path: string): string => resolveIcon('file-tree-icon-file', path).name;
|
|
31
31
|
|
|
32
32
|
let override: ((path: string) => string | null) | null = null;
|
|
33
|
+
let overrideVersion = 0;
|
|
34
|
+
const overrideListeners = new Set<() => void>();
|
|
33
35
|
|
|
34
36
|
/* What an app knows better about a file's icon: the address of an svg, or null for the icon this set has. */
|
|
35
37
|
export function setFileIconOverride(next: ((path: string) => string | null) | null): void {
|
|
36
38
|
override = next;
|
|
39
|
+
overrideVersion++;
|
|
40
|
+
overrideListeners.forEach((listener) => listener());
|
|
37
41
|
}
|
|
38
42
|
|
|
43
|
+
export function subscribeFileIconOverride(onChange: () => void): () => void {
|
|
44
|
+
overrideListeners.add(onChange);
|
|
45
|
+
|
|
46
|
+
return () => {
|
|
47
|
+
overrideListeners.delete(onChange);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* A mounted icon reads this so it draws again once the override changes. */
|
|
52
|
+
export const fileIconOverrideVersion = (): number => overrideVersion;
|
|
53
|
+
|
|
39
54
|
export function fileIconOverrideFor(path: string): string | null {
|
|
40
55
|
return override?.(path) ?? null;
|
|
41
56
|
}
|
package/src/file-tree/parts.tsx
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore, type ReactNode } from 'react';
|
|
2
|
-
import { createPortal } from 'react-dom';
|
|
3
1
|
import { useRender } from '@base-ui/react/use-render';
|
|
4
|
-
import { FileTree as PierreFileTree, type FileTreeProps as PierreProps } from '@pierre/trees/react';
|
|
5
2
|
import type { FileTree, FileTreeVisibleRow } from '@pierre/trees';
|
|
6
|
-
import {
|
|
7
|
-
import type { LucideIcon } from 'lucide-react';
|
|
3
|
+
import { FileTree as PierreFileTree, type FileTreeProps as PierreProps } from '@pierre/trees/react';
|
|
8
4
|
import clsx from 'clsx';
|
|
5
|
+
import type { LucideIcon } from 'lucide-react';
|
|
6
|
+
import { useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore, type ReactNode } from 'react';
|
|
7
|
+
import { createPortal } from 'react-dom';
|
|
8
|
+
import { useTranslation } from 'react-i18next';
|
|
9
9
|
import { Icon } from '../Icon.tsx';
|
|
10
10
|
import { mergeRefs } from '../merge-refs.ts';
|
|
11
|
-
import { directoryHandle, extendsSelection, followFocus, movesFocus, menuTargetsOf, rowPathOf, visibleRows } from './helpers.ts';
|
|
12
11
|
import type { FileTreeEmptyLabel } from './empty-lines.ts';
|
|
12
|
+
import { directoryHandle, extendsSelection, followFocus, movesFocus, menuTargetsOf, rowPathOf, visibleRows } from './helpers.ts';
|
|
13
13
|
import { useEmptyLines } from './use-empty-lines.ts';
|
|
14
14
|
import { useFileTreeShift } from './use-file-tree-shift.tsx';
|
|
15
15
|
import { useFileTreeSlots, type FileTreeDecorationOrder } from './use-slots.ts';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useEffect, useLayoutEffect, useRef } from 'react';
|
|
2
1
|
import type { FileTree } from '@pierre/trees';
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef } from 'react';
|
|
3
3
|
import { emptyLineChanges, isEmptyLine, pastEmptyLine, skipEmptyLine, type FileTreeEmptyLabel } from './empty-lines.ts';
|
|
4
4
|
import { movesFocus, visibleRows } from './helpers.ts';
|
|
5
5
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type ReactElement } from 'react';
|
|
2
1
|
import type { FileTree } from '@pierre/trees';
|
|
3
|
-
import {
|
|
2
|
+
import { type ReactElement } from 'react';
|
|
4
3
|
import { shiftNeed } from '../tree/shift.ts';
|
|
4
|
+
import { useTreeShift, type TreeShiftSource } from '../tree/use-tree-shift.tsx';
|
|
5
5
|
|
|
6
6
|
const ROWS = '[data-type="item"]:not([data-item-parked="true"])';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useEffect, useId, useLayoutEffect, useRef, useState } from 'react';
|
|
2
|
-
import { flushSync } from 'react-dom';
|
|
3
1
|
import type { FileTree, FileTreeVisibleRow } from '@pierre/trees';
|
|
4
2
|
import type { LucideIcon } from 'lucide-react';
|
|
3
|
+
import { useEffect, useId, useLayoutEffect, useRef, useState } from 'react';
|
|
4
|
+
import { flushSync } from 'react-dom';
|
|
5
5
|
import { directoryOfEmptyLine, isEmptyLine, type FileTreeEmptyLabel } from './empty-lines.ts';
|
|
6
6
|
import { pathOfRow, visibleRows, visibleRowsAround } from './helpers.ts';
|
|
7
7
|
import { matchHighlight, rangesOfMatches } from './highlight.ts';
|
package/src/index.ts
CHANGED
|
@@ -66,6 +66,7 @@ export { Pill, type PillProps } from './Pill.tsx';
|
|
|
66
66
|
export { PromptDialog, type PromptDialogProps } from './PromptDialog.tsx';
|
|
67
67
|
export { RelativeTime, type RelativeTimeProps } from './RelativeTime.tsx';
|
|
68
68
|
export { ProjectSwitcher, ProjectSwitcherGroups, type ProjectSwitcherItem, type ProjectSwitcherProps } from './ProjectSwitcher.tsx';
|
|
69
|
+
export { ScrollArea, type ScrollAreaProps } from './ScrollArea.tsx';
|
|
69
70
|
export { SectionLabel, type SectionLabelProps } from './SectionLabel.tsx';
|
|
70
71
|
export { SegmentBar, type SegmentBarPart, type SegmentBarProps } from './SegmentBar.tsx';
|
|
71
72
|
export { Segmented, type SegmentedOption, type SegmentedProps } from './Segmented.tsx';
|
|
@@ -124,6 +125,7 @@ export { clampColumnSize, useColumnResize, type ColumnEdge, type ColumnResize, t
|
|
|
124
125
|
export { useContentSize, type ContentSize } from './useContentSize.ts';
|
|
125
126
|
export { useMeasuredWidth } from './useMeasuredWidth.ts';
|
|
126
127
|
export { useNow, useTickingText } from './useNow.ts';
|
|
128
|
+
export { useOverlayScrollbars, type OverlayScrollbarsOptions, type ScrollAreaAxis } from './useOverlayScrollbars.tsx';
|
|
127
129
|
|
|
128
130
|
export { useFileTree, type FileTreeOptions } from './file-tree/use-file-tree.ts';
|
|
129
131
|
export type { FileTreeRootProps } from './file-tree/parts.tsx';
|
|
@@ -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 KeyValueListRootProps = useRender.ComponentProps<'dl'> & {
|
|
5
5
|
/* A hairline between rows, for a long list such as the headers of a message. Without it the rows sit close, for a few facts in a card. */
|
package/src/menu/parts.tsx
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { createContext, useContext, type ComponentProps, type ReactNode, type Ref } from 'react';
|
|
2
|
-
import clsx from 'clsx';
|
|
3
|
-
import { Check, ChevronRight, type LucideIcon } from 'lucide-react';
|
|
4
1
|
import { ContextMenu as BaseContextMenu } from '@base-ui/react/context-menu';
|
|
5
2
|
import { Menu as BaseMenu } from '@base-ui/react/menu';
|
|
6
3
|
import { Separator as BaseSeparator } from '@base-ui/react/separator';
|
|
7
4
|
import { useRender } from '@base-ui/react/use-render';
|
|
5
|
+
import clsx from 'clsx';
|
|
6
|
+
import { Check, ChevronRight, type LucideIcon } from 'lucide-react';
|
|
7
|
+
import { createContext, useContext, useState, type ComponentProps, type ReactNode, type Ref } from 'react';
|
|
8
8
|
import { withClass } from '../class-name.ts';
|
|
9
9
|
import { Icon } from '../Icon.tsx';
|
|
10
|
-
import {
|
|
10
|
+
import { mergeRefs } from '../merge-refs.ts';
|
|
11
11
|
import { splitPlacement, type PlacementKey } from '../placement.ts';
|
|
12
12
|
import { renderPositioner } from '../popup-layer.ts';
|
|
13
|
+
import { Tooltip } from '../Tooltip.tsx';
|
|
14
|
+
import { useOverlayScrollbars } from '../useOverlayScrollbars.tsx';
|
|
13
15
|
|
|
14
16
|
/* Where a popup opens from decides where it goes when a call site says nothing. */
|
|
15
17
|
type MenuKind = 'menu' | 'context' | 'submenu';
|
|
@@ -66,9 +68,11 @@ export const ContextMenuTrigger = BaseContextMenu.Trigger;
|
|
|
66
68
|
|
|
67
69
|
export type MenuPopupProps = ComponentProps<typeof BaseMenu.Popup> & Placement;
|
|
68
70
|
|
|
69
|
-
export function MenuPopup({ className, ...props }: MenuPopupProps) {
|
|
71
|
+
export function MenuPopup({ className, ref, ...props }: MenuPopupProps) {
|
|
70
72
|
const defaults = PLACEMENT[useContext(MenuKindContext)];
|
|
71
73
|
const [placement, popupProps] = splitPlacement(props);
|
|
74
|
+
const [popup, setPopup] = useState<HTMLDivElement | null>(null);
|
|
75
|
+
const scrollbars = useOverlayScrollbars(popup);
|
|
72
76
|
|
|
73
77
|
return (
|
|
74
78
|
<BaseMenu.Portal>
|
|
@@ -81,7 +85,8 @@ export function MenuPopup({ className, ...props }: MenuPopupProps) {
|
|
|
81
85
|
sideOffset={placement.sideOffset ?? defaults.sideOffset}
|
|
82
86
|
alignOffset={placement.alignOffset ?? defaults.alignOffset}
|
|
83
87
|
>
|
|
84
|
-
<BaseMenu.Popup className={withClass('menu-popup', className)} {...popupProps} />
|
|
88
|
+
<BaseMenu.Popup ref={mergeRefs(setPopup, ref)} className={withClass('menu-popup', className)} {...popupProps} />
|
|
89
|
+
{scrollbars}
|
|
85
90
|
</BaseMenu.Positioner>
|
|
86
91
|
</BaseMenu.Portal>
|
|
87
92
|
);
|
package/src/popover/parts.tsx
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type { ComponentProps } from 'react';
|
|
2
1
|
import { Popover as BasePopover } from '@base-ui/react/popover';
|
|
3
2
|
import { PreviewCard as BasePreviewCard } from '@base-ui/react/preview-card';
|
|
3
|
+
import { useState, type ComponentProps } from 'react';
|
|
4
4
|
import { withClass } from '../class-name.ts';
|
|
5
|
+
import { mergeRefs } from '../merge-refs.ts';
|
|
5
6
|
import { splitPlacement, type PlacementKey } from '../placement.ts';
|
|
6
7
|
import { renderPositioner } from '../popup-layer.ts';
|
|
8
|
+
import { useOverlayScrollbars } from '../useOverlayScrollbars.tsx';
|
|
7
9
|
|
|
8
10
|
/* `picker` is a card whose content runs to its edges, `plain` leaves the surface to the caller. */
|
|
9
11
|
const POPUP_VARIANT = {
|
|
@@ -31,13 +33,16 @@ export type PopoverPopupProps = ComponentProps<typeof BasePopover.Popup> &
|
|
|
31
33
|
variant?: PopupVariant;
|
|
32
34
|
};
|
|
33
35
|
|
|
34
|
-
export function PopoverPopup({ variant = 'menu', side = 'bottom', align = 'start', sideOffset = 8, className, ...props }: PopoverPopupProps) {
|
|
36
|
+
export function PopoverPopup({ variant = 'menu', side = 'bottom', align = 'start', sideOffset = 8, className, ref, ...props }: PopoverPopupProps) {
|
|
35
37
|
const [placement, popupProps] = splitPlacement(props);
|
|
38
|
+
const [popup, setPopup] = useState<HTMLDivElement | null>(null);
|
|
39
|
+
const scrollbars = useOverlayScrollbars(popup);
|
|
36
40
|
|
|
37
41
|
return (
|
|
38
42
|
<BasePopover.Portal>
|
|
39
43
|
<BasePopover.Positioner className="popup-positioner" render={renderPositioner} {...placement} side={side} align={align} sideOffset={sideOffset}>
|
|
40
|
-
<BasePopover.Popup className={withClass(POPUP_VARIANT[variant], className)} {...popupProps} />
|
|
44
|
+
<BasePopover.Popup ref={mergeRefs(setPopup, ref)} className={withClass(POPUP_VARIANT[variant], className)} {...popupProps} />
|
|
45
|
+
{scrollbars}
|
|
41
46
|
</BasePopover.Positioner>
|
|
42
47
|
</BasePopover.Portal>
|
|
43
48
|
);
|
|
@@ -53,13 +58,16 @@ export type PreviewCardPopupProps = ComponentProps<typeof BasePreviewCard.Popup>
|
|
|
53
58
|
};
|
|
54
59
|
|
|
55
60
|
/* A card that opens while the pointer rests on its trigger, drawn like a popover. */
|
|
56
|
-
export function PreviewCardPopup({ variant = 'menu', side = 'bottom', align = 'start', sideOffset = 8, className, ...props }: PreviewCardPopupProps) {
|
|
61
|
+
export function PreviewCardPopup({ variant = 'menu', side = 'bottom', align = 'start', sideOffset = 8, className, ref, ...props }: PreviewCardPopupProps) {
|
|
57
62
|
const [placement, popupProps] = splitPlacement(props);
|
|
63
|
+
const [popup, setPopup] = useState<HTMLDivElement | null>(null);
|
|
64
|
+
const scrollbars = useOverlayScrollbars(popup);
|
|
58
65
|
|
|
59
66
|
return (
|
|
60
67
|
<BasePreviewCard.Portal>
|
|
61
68
|
<BasePreviewCard.Positioner className="z-(--z-popup)" {...placement} side={side} align={align} sideOffset={sideOffset}>
|
|
62
|
-
<BasePreviewCard.Popup className={withClass(POPUP_VARIANT[variant], className)} {...popupProps} />
|
|
69
|
+
<BasePreviewCard.Popup ref={mergeRefs(setPopup, ref)} className={withClass(POPUP_VARIANT[variant], className)} {...popupProps} />
|
|
70
|
+
{scrollbars}
|
|
63
71
|
</BasePreviewCard.Positioner>
|
|
64
72
|
</BasePreviewCard.Portal>
|
|
65
73
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { MouseEvent } from 'react';
|
|
2
1
|
import clsx from 'clsx';
|
|
3
|
-
import { useTranslation } from 'react-i18next';
|
|
4
2
|
import { MoreHorizontal } from 'lucide-react';
|
|
3
|
+
import type { MouseEvent } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import * as Menu from './menu/index.parts.ts';
|
|
6
6
|
import type { ProjectSwitcherItem } from './ProjectSwitcher.tsx';
|
|
7
7
|
import { Tooltip } from './Tooltip.tsx';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* A thumb never gets shorter than this, so a long list keeps one a pointer can grab. */
|
|
2
|
+
export const MIN_THUMB = 24;
|
|
3
|
+
|
|
4
|
+
/* A press on the track moves this share of the visible length, so a line of the old view stays in sight. */
|
|
5
|
+
export const PAGE_SHARE = 0.9;
|
|
6
|
+
|
|
7
|
+
export interface ScrollMetrics {
|
|
8
|
+
/* The length of the bar the thumb moves along, in pixels. */
|
|
9
|
+
readonly track: number;
|
|
10
|
+
|
|
11
|
+
/* The visible length of the scroller (`clientHeight` or `clientWidth`). */
|
|
12
|
+
readonly view: number;
|
|
13
|
+
|
|
14
|
+
/* The full length of what scrolls (`scrollHeight` or `scrollWidth`). */
|
|
15
|
+
readonly content: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface Thumb {
|
|
19
|
+
readonly size: number;
|
|
20
|
+
readonly offset: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Whether an axis has anything to scroll; a pixel of rounding is not worth a bar. */
|
|
24
|
+
export function overflows(view: number, content: number): boolean {
|
|
25
|
+
return content - view > 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* The thumb's length and its distance from the start of the track, in whole pixels. */
|
|
29
|
+
export function thumbOf({ track, view, content }: ScrollMetrics, position: number): Thumb {
|
|
30
|
+
const range = content - view;
|
|
31
|
+
|
|
32
|
+
if (range <= 0 || track <= 0) {
|
|
33
|
+
return { size: Math.max(0, Math.round(track)), offset: 0 };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const size = Math.min(Math.round(track), Math.max(MIN_THUMB, Math.round((track * view) / content)));
|
|
37
|
+
const progress = Math.min(1, Math.max(0, position / range));
|
|
38
|
+
|
|
39
|
+
return { size, offset: Math.round((track - size) * progress) };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* The scroll position after the thumb moved `moved` pixels from where a drag started. */
|
|
43
|
+
export function scrollAfterDrag(metrics: ScrollMetrics, start: number, moved: number): number {
|
|
44
|
+
const range = metrics.content - metrics.view;
|
|
45
|
+
const free = metrics.track - thumbOf(metrics, start).size;
|
|
46
|
+
|
|
47
|
+
if (range <= 0 || free <= 0) {
|
|
48
|
+
return start;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return Math.min(range, Math.max(0, start + (moved * range) / free));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* How far a press on the track at `pointer` scrolls: a page toward the pointer, nothing on the thumb itself. */
|
|
55
|
+
export function pageToward(pointer: number, thumbStart: number, thumbEnd: number, view: number): number {
|
|
56
|
+
const page = Math.round(view * PAGE_SHARE);
|
|
57
|
+
|
|
58
|
+
if (pointer < thumbStart) {
|
|
59
|
+
return -page;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (pointer > thumbEnd) {
|
|
63
|
+
return page;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
package/src/select-shared.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { ReactNode, Ref } from 'react';
|
|
2
1
|
import clsx from 'clsx';
|
|
2
|
+
import type { ReactNode, Ref } from 'react';
|
|
3
|
+
import { ScrollArea } from '../ScrollArea.tsx';
|
|
3
4
|
|
|
4
5
|
const LIST_WIDTHS = { 280: 'w-70', 320: 'w-80', 340: 'w-85' } as const;
|
|
5
6
|
|
|
@@ -18,18 +19,20 @@ export interface MasterDetailProps {
|
|
|
18
19
|
export function MasterDetail({ list, listWidth, listLabel, detail, className, ref }: MasterDetailProps) {
|
|
19
20
|
return (
|
|
20
21
|
<div ref={ref} className={clsx('flex min-h-0 min-w-0 grow border-t border-border max-[640px]:flex-col', className)}>
|
|
21
|
-
<
|
|
22
|
+
<ScrollArea
|
|
23
|
+
render={<nav />}
|
|
22
24
|
aria-label={listLabel}
|
|
23
25
|
className={clsx(
|
|
24
|
-
'
|
|
26
|
+
'shrink-0 border-r border-border max-[640px]:max-h-60 max-[640px]:w-auto max-[640px]:border-r-0 max-[640px]:border-b',
|
|
25
27
|
LIST_WIDTHS[listWidth]
|
|
26
28
|
)}
|
|
29
|
+
viewportClassName="flex flex-col gap-0.5 p-2"
|
|
27
30
|
>
|
|
28
31
|
{list}
|
|
29
|
-
</
|
|
30
|
-
<
|
|
32
|
+
</ScrollArea>
|
|
33
|
+
<ScrollArea className="grow">
|
|
31
34
|
<div className="flex min-w-0 flex-col gap-7 px-8 pt-6 pb-10 max-[960px]:px-4">{detail}</div>
|
|
32
|
-
</
|
|
35
|
+
</ScrollArea>
|
|
33
36
|
</div>
|
|
34
37
|
);
|
|
35
38
|
}
|