@botiverse/raft-ui 0.76.1 → 0.77.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/CHANGELOG.md +18 -0
- package/dist/components/combobox/combobox.d.mts +7 -1
- package/dist/components/combobox/combobox.mjs +35 -8
- package/dist/components/combobox-select/combobox-select.css +5 -4
- package/dist/components/combobox-select/combobox-select.mjs +27 -3
- package/dist/components/select/select.d.mts +8 -1
- package/dist/components/select/select.mjs +36 -8
- package/dist/lint/recommended.json +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## [0.77.1](https://github.com/botiverse/raft-design/compare/v0.77.0...v0.77.1) (2026-11-08)
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
|
|
5
|
+
- **The lint plugin's recommended config keeps the app's own Oxlint plugins**: `recommended.json` sets `"plugins": []`. ([#1076](https://github.com/botiverse/raft-design/pull/1076))
|
|
6
|
+
- Why: an extended config without `plugins` counts as Oxlint's default plugins, so extending it turned `unicorn` back on in an app that lists its plugins without it (36 new warnings in slock, Kirby, #proj-raft-ui:#209). Same on Oxlint 1.85, 1.87 and 1.91.
|
|
7
|
+
- Affects: an app that extends `recommended.json` keeps exactly the `plugins` it lists; an app that lists none keeps Oxlint's defaults, as before. The ESLint config is unchanged.
|
|
8
|
+
- Docs: https://raft-design.botiverse.build/about/use-raft-ui/#lint-rules
|
|
9
|
+
|
|
10
|
+
## [0.77.0](https://github.com/botiverse/raft-design/compare/v0.76.1...v0.77.0) (2026-11-08)
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **Select, Combobox and ComboboxSelect lists scroll in a ScrollArea**: each list is a `useScrollAreaContainer` container with absolute gutters, inside a frame (`data-slot="select-list-frame"`, `combobox-list-frame`) that holds the overlay scrollbar. The list is never a Tab stop: focus stays in the input or on the options. ([#1074](https://github.com/botiverse/raft-design/pull/1074))
|
|
15
|
+
- Why: they were the last native-scrolling exception for Base UI lists; on Windows they drew a classic scrollbar with arrows that took width from the options (Artea's runtimes list, #proj-raft-ui:20a421bd).
|
|
16
|
+
- Affects: the scrollbar is the thin overlay thumb on every platform. `SelectList` / `ComboboxList` render a frame `<div>` around the list element; `ref`, `className`, `style` and the other props still go to the list. CSS or tests that expected the list to be the popup's direct child, or waited on `document.getAnimations()` to finish (the thumb runs on a scroll timeline), need updating.
|
|
17
|
+
- Docs: https://raft-design.botiverse.build/components/scroll-area/
|
|
18
|
+
|
|
1
19
|
## [0.76.1](https://github.com/botiverse/raft-design/compare/v0.76.0...v0.76.1) (2026-11-08)
|
|
2
20
|
|
|
3
21
|
### Fixed
|
|
@@ -42,7 +42,13 @@ export type ComboboxContentProps = StyledProps<Combobox.Popup.Props> & Pick<Comb
|
|
|
42
42
|
};
|
|
43
43
|
export declare function ComboboxContent({ align, alignOffset, anchor, arrowPadding, collisionAvoidance, collisionBoundary, collisionPadding, disableAnchorTracking, positionMethod, side, sideOffset, sticky, portalProps, className, initialFocus, finalFocus, ...props }: ComboboxContentProps): import("react").JSX.Element;
|
|
44
44
|
export type ComboboxListProps = StyledProps<Combobox.List.Props>;
|
|
45
|
-
|
|
45
|
+
/**
|
|
46
|
+
* The scrolling list, a ScrollArea container with absolute gutters: the
|
|
47
|
+
* overlay scrollbar lies on a frame around it, since Base UI owns the list's
|
|
48
|
+
* children (items can be a function of each item). The list is never a Tab
|
|
49
|
+
* stop: focus stays in the input, as Base UI manages it.
|
|
50
|
+
*/
|
|
51
|
+
export declare function ComboboxList({ className, ref, style, ...props }: ComboboxListProps): import("react").JSX.Element;
|
|
46
52
|
export type ComboboxItemProps = StyledProps<Combobox.Item.Props>;
|
|
47
53
|
export declare function ComboboxItem({ className, ...props }: ComboboxItemProps): import("react").JSX.Element;
|
|
48
54
|
export type ComboboxItemIndicatorProps = StyledProps<Combobox.ItemIndicator.Props>;
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import { cn } from "../../cn.mjs";
|
|
1
2
|
import { tv } from "../../lib/tv.mjs";
|
|
2
3
|
import { useThemeScopeProps } from "../../lib/theme/theme-scope.mjs";
|
|
3
4
|
import { Button } from "../button/button.mjs";
|
|
5
|
+
import { ScrollAreaGutters } from "../scroll-area/scroll-area-gutters.mjs";
|
|
6
|
+
import { useScrollAreaContainer } from "../scroll-area/use-scroll-area-container.mjs";
|
|
4
7
|
import { usePopupCollision } from "../../lib/frame-insets.mjs";
|
|
5
8
|
import { useOverflowTitle } from "../../lib/use-overflow-title.mjs";
|
|
6
9
|
import { chipRemoveBase } from "../../lib/chip-remove.mjs";
|
|
7
10
|
import { Check, X } from "lucide-react";
|
|
8
11
|
import { createContext, use, useCallback, useMemo, useState } from "react";
|
|
9
|
-
import { jsx } from "react/jsx-runtime";
|
|
12
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
13
|
import { Combobox } from "@base-ui/react/combobox";
|
|
11
14
|
//#region src/components/combobox/combobox.tsx
|
|
12
15
|
const combobox = tv({
|
|
@@ -67,7 +70,8 @@ const combobox = tv({
|
|
|
67
70
|
],
|
|
68
71
|
header: ["flex items-center justify-between px-3", "gap-2 py-1.5 pr-1.5 pl-4"],
|
|
69
72
|
separator: ["h-px shrink-0 border-0", "bg-line-hairline"],
|
|
70
|
-
|
|
73
|
+
listFrame: ["min-h-0"],
|
|
74
|
+
list: ["max-h-64 min-h-0", "flex flex-col p-1 scroll-py-1"],
|
|
71
75
|
item: [
|
|
72
76
|
"flex w-full shrink-0 cursor-default select-none items-center justify-between overflow-hidden",
|
|
73
77
|
"text-left outline-none transition-colors",
|
|
@@ -319,12 +323,35 @@ function ComboboxContent({ align = "start", alignOffset = 0, anchor, arrowPaddin
|
|
|
319
323
|
})
|
|
320
324
|
});
|
|
321
325
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
326
|
+
/**
|
|
327
|
+
* The scrolling list, a ScrollArea container with absolute gutters: the
|
|
328
|
+
* overlay scrollbar lies on a frame around it, since Base UI owns the list's
|
|
329
|
+
* children (items can be a function of each item). The list is never a Tab
|
|
330
|
+
* stop: focus stays in the input, as Base UI manages it.
|
|
331
|
+
*/
|
|
332
|
+
function ComboboxList({ className, ref, style, ...props }) {
|
|
333
|
+
const { list, listFrame } = combobox();
|
|
334
|
+
const { containerProps, frameProps, guttersProps } = useScrollAreaContainer({
|
|
335
|
+
containerRef: ref,
|
|
336
|
+
guttersPosition: "absolute",
|
|
337
|
+
overscroll: "contain"
|
|
338
|
+
});
|
|
339
|
+
const ownStyle = useMemo(() => ({
|
|
340
|
+
...containerProps.style,
|
|
341
|
+
...style
|
|
342
|
+
}), [containerProps.style, style]);
|
|
343
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
344
|
+
...frameProps,
|
|
345
|
+
"data-slot": "combobox-list-frame",
|
|
346
|
+
className: listFrame({ className: frameProps.className }),
|
|
347
|
+
children: [/* @__PURE__ */ jsx(Combobox.List, {
|
|
348
|
+
"data-slot": "combobox-list",
|
|
349
|
+
...containerProps,
|
|
350
|
+
tabIndex: -1,
|
|
351
|
+
...props,
|
|
352
|
+
style: ownStyle,
|
|
353
|
+
className: list({ className: cn(containerProps.className, className) })
|
|
354
|
+
}), /* @__PURE__ */ jsx(ScrollAreaGutters, { ...guttersProps })]
|
|
328
355
|
});
|
|
329
356
|
}
|
|
330
357
|
function ComboboxItem({ className, ...props }) {
|
|
@@ -181,13 +181,14 @@
|
|
|
181
181
|
var(--elevation-float);
|
|
182
182
|
outline: 0;
|
|
183
183
|
}
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
.r-combobox-select__list-frame {
|
|
185
|
+
min-height: 0;
|
|
186
|
+
}
|
|
187
|
+
/* A ScrollArea container (absolute gutters): useScrollAreaContainer gives it its overflow. */
|
|
186
188
|
.r-combobox-select__list {
|
|
187
189
|
box-sizing: border-box;
|
|
188
190
|
max-height: var(--combobox-select-list-max-height, 16rem);
|
|
189
|
-
|
|
190
|
-
overscroll-behavior: contain;
|
|
191
|
+
min-height: 0;
|
|
191
192
|
scroll-padding-block: 4px;
|
|
192
193
|
padding: 4px;
|
|
193
194
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useTheme } from "../../lib/theme/use-theme.mjs";
|
|
2
2
|
import { useThemeScopeProps } from "../../lib/theme/theme-scope.mjs";
|
|
3
|
+
import { ScrollAreaGutters } from "../scroll-area/scroll-area-gutters.mjs";
|
|
4
|
+
import { useScrollAreaContainer } from "../scroll-area/use-scroll-area-container.mjs";
|
|
3
5
|
import { usePopupCollision } from "../../lib/frame-insets.mjs";
|
|
4
6
|
import { useOverflowTitle } from "../../lib/use-overflow-title.mjs";
|
|
5
7
|
import { usePhoneMedia } from "../../lib/phone-media.mjs";
|
|
@@ -457,9 +459,8 @@ function ComboboxSelect({ options, multiple, value, defaultValue, onValueChange,
|
|
|
457
459
|
className: popupClassName,
|
|
458
460
|
children: [
|
|
459
461
|
/* @__PURE__ */ jsx(Combobox.Status, { children: loadingMessage }),
|
|
460
|
-
/* @__PURE__ */ jsx(
|
|
461
|
-
|
|
462
|
-
"aria-busy": loading,
|
|
462
|
+
/* @__PURE__ */ jsx(ComboboxSelectList, {
|
|
463
|
+
loading,
|
|
463
464
|
children: renderGroup
|
|
464
465
|
}),
|
|
465
466
|
/* @__PURE__ */ jsx(Combobox.Empty, { children: emptyMessage })
|
|
@@ -469,5 +470,28 @@ function ComboboxSelect({ options, multiple, value, defaultValue, onValueChange,
|
|
|
469
470
|
})]
|
|
470
471
|
});
|
|
471
472
|
}
|
|
473
|
+
/**
|
|
474
|
+
* The scrolling list, a ScrollArea container with absolute gutters: the
|
|
475
|
+
* overlay scrollbar lies on a frame around it, since Base UI owns the list's
|
|
476
|
+
* children (a function of each group). The list is never a Tab stop: focus
|
|
477
|
+
* stays in the input, as Base UI manages it.
|
|
478
|
+
*/
|
|
479
|
+
function ComboboxSelectList({ children, loading }) {
|
|
480
|
+
const { containerProps, frameProps, guttersProps } = useScrollAreaContainer({
|
|
481
|
+
guttersPosition: "absolute",
|
|
482
|
+
overscroll: "contain"
|
|
483
|
+
});
|
|
484
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
485
|
+
...frameProps,
|
|
486
|
+
className: `${frameProps.className} r-combobox-select__list-frame`,
|
|
487
|
+
children: [/* @__PURE__ */ jsx(Combobox.List, {
|
|
488
|
+
...containerProps,
|
|
489
|
+
tabIndex: -1,
|
|
490
|
+
className: `${containerProps.className} r-combobox-select__list`,
|
|
491
|
+
"aria-busy": loading,
|
|
492
|
+
children
|
|
493
|
+
}), /* @__PURE__ */ jsx(ScrollAreaGutters, { ...guttersProps })]
|
|
494
|
+
});
|
|
495
|
+
}
|
|
472
496
|
//#endregion
|
|
473
497
|
export { ComboboxSelect };
|
|
@@ -35,7 +35,14 @@ export type SelectContentProps = StyledProps<Select.Popup.Props> & Pick<Select.P
|
|
|
35
35
|
};
|
|
36
36
|
export declare function SelectContent({ align, alignItemWithTrigger, alignOffset, anchor, arrowPadding, collisionAvoidance, collisionBoundary, collisionPadding, disableAnchorTracking, positionMethod, side, sideOffset, sticky, portalProps, className, ...props }: SelectContentProps): import("react").JSX.Element;
|
|
37
37
|
export type SelectListProps = StyledProps<Select.List.Props>;
|
|
38
|
-
|
|
38
|
+
/**
|
|
39
|
+
* The scrolling list, a ScrollArea container with absolute gutters: its frame
|
|
40
|
+
* reaches into the popup's 4px inset and the list pads it back, so a
|
|
41
|
+
* separator meets the popup's edge, and the overlay scrollbar takes no room
|
|
42
|
+
* from the options, on every platform. The list is never a Tab stop: focus
|
|
43
|
+
* stays on the options, as Base UI manages it.
|
|
44
|
+
*/
|
|
45
|
+
export declare function SelectList({ className, ref, style, ...props }: SelectListProps): import("react").JSX.Element;
|
|
39
46
|
export type SelectItemProps = StyledProps<Select.Item.Props>;
|
|
40
47
|
export declare function SelectItem({ className, ...props }: SelectItemProps): import("react").JSX.Element;
|
|
41
48
|
export type SelectItemLeadingProps = StyledProps<ComponentPropsWithRef<"span">>;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { cn } from "../../cn.mjs";
|
|
1
2
|
import { tv } from "../../lib/tv.mjs";
|
|
2
3
|
import { useThemeScopeProps } from "../../lib/theme/theme-scope.mjs";
|
|
3
4
|
import { Button } from "../button/button.mjs";
|
|
5
|
+
import { ScrollAreaGutters } from "../scroll-area/scroll-area-gutters.mjs";
|
|
6
|
+
import { useScrollAreaContainer } from "../scroll-area/use-scroll-area-container.mjs";
|
|
4
7
|
import { usePopupCollision } from "../../lib/frame-insets.mjs";
|
|
5
8
|
import { useOverflowTitle } from "../../lib/use-overflow-title.mjs";
|
|
6
9
|
import { Check, ChevronDown } from "lucide-react";
|
|
7
10
|
import { createContext, useCallback, useContext, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
8
|
-
import { jsx } from "react/jsx-runtime";
|
|
11
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
12
|
import { Select } from "@base-ui/react/select";
|
|
10
13
|
//#region src/components/select/select.tsx
|
|
11
14
|
const select = tv({
|
|
@@ -44,7 +47,8 @@ const select = tv({
|
|
|
44
47
|
"rounded-container bg-layer-popover p-1 text-foreground",
|
|
45
48
|
"shadow-float ring-1 ring-line-seam"
|
|
46
49
|
],
|
|
47
|
-
|
|
50
|
+
listFrame: ["-mx-1 min-h-0"],
|
|
51
|
+
list: ["max-h-64 min-h-0 scroll-py-6", "flex flex-col px-1"],
|
|
48
52
|
item: [
|
|
49
53
|
"flex w-full shrink-0 cursor-default select-none items-center justify-between overflow-hidden",
|
|
50
54
|
"text-left outline-none transition-colors",
|
|
@@ -306,12 +310,36 @@ function SelectContent({ align = "start", alignItemWithTrigger = false, alignOff
|
|
|
306
310
|
children: positionerNode
|
|
307
311
|
});
|
|
308
312
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
313
|
+
/**
|
|
314
|
+
* The scrolling list, a ScrollArea container with absolute gutters: its frame
|
|
315
|
+
* reaches into the popup's 4px inset and the list pads it back, so a
|
|
316
|
+
* separator meets the popup's edge, and the overlay scrollbar takes no room
|
|
317
|
+
* from the options, on every platform. The list is never a Tab stop: focus
|
|
318
|
+
* stays on the options, as Base UI manages it.
|
|
319
|
+
*/
|
|
320
|
+
function SelectList({ className, ref, style, ...props }) {
|
|
321
|
+
const { list, listFrame } = select();
|
|
322
|
+
const { containerProps, frameProps, guttersProps } = useScrollAreaContainer({
|
|
323
|
+
containerRef: ref,
|
|
324
|
+
guttersPosition: "absolute",
|
|
325
|
+
overscroll: "contain"
|
|
326
|
+
});
|
|
327
|
+
const ownStyle = useMemo(() => ({
|
|
328
|
+
...containerProps.style,
|
|
329
|
+
...style
|
|
330
|
+
}), [containerProps.style, style]);
|
|
331
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
332
|
+
...frameProps,
|
|
333
|
+
"data-slot": "select-list-frame",
|
|
334
|
+
className: listFrame({ className: frameProps.className }),
|
|
335
|
+
children: [/* @__PURE__ */ jsx(Select.List, {
|
|
336
|
+
"data-slot": "select-list",
|
|
337
|
+
...containerProps,
|
|
338
|
+
tabIndex: -1,
|
|
339
|
+
...props,
|
|
340
|
+
style: ownStyle,
|
|
341
|
+
className: list({ className: cn(containerProps.className, className) })
|
|
342
|
+
}), /* @__PURE__ */ jsx(ScrollAreaGutters, { ...guttersProps })]
|
|
315
343
|
});
|
|
316
344
|
}
|
|
317
345
|
const SelectItemPartsContext = createContext(null);
|