@botiverse/raft-ui 0.95.0 → 0.95.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
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## [0.95.1](https://github.com/botiverse/raft-design/compare/v0.95.0...v0.95.1) (2026-11-12)
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **`SelectTrigger fit`: a select as wide as its longest option** ([Select › As wide as its longest option](https://raft-design.botiverse.build/components/select/#as-wide-as-its-longest-option)) ([#1206](https://github.com/botiverse/raft-design/pull/1206), [#1207](https://github.com/botiverse/raft-design/pull/1207)). With `fit`, the trigger is as wide as the longest label in the root's `items` or its placeholder, at least 11rem and at most 20rem; past 20rem the value ends in an ellipsis. `SelectValue` lays every label out in its own place, invisible, 0px high and hidden from screen readers, so the width needs no script and stays the same whichever value is chosen.
|
|
6
|
+
- Why: a settings row's select sits at the row's end, as wide as its longest option ([Forms › A setting's row](https://raft-design.botiverse.build/patterns/forms/#a-settings-row)); a trigger that only lays out its value would shrink to a short one and move the row's end with each choice.
|
|
7
|
+
- Affects: nothing until a trigger sets `fit`. It needs `items` on `Select` and a `SelectValue` in the trigger; without `items` it stays at 11rem. A trigger without `fit` renders as before.
|
|
8
|
+
- Docs: https://raft-design.botiverse.build/components/select/#as-wide-as-its-longest-option
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **An attached row under a row that is a target keeps its hairline and top inset** ([ListRow › A row that belongs to the setting above](https://raft-design.botiverse.build/components/list-row/#a-row-that-belongs-to-the-setting-above)) ([#1201](https://github.com/botiverse/raft-design/pull/1201)). A display-only `attached` row (`interactive={false}`) right under an interactive row now draws the hairline above it and, as a `plain` row, keeps its top inset. `ListRow` decides this from the row above (it now sets `data-interactive`), so callers change nothing.
|
|
13
|
+
- Why: under a target, dropping them left the target's hover and press fill ending on an edge you couldn't see, right against the attached row's own buttons (a pinned item with Remove and Replace under it).
|
|
14
|
+
- Affects: only that pair. An attached row under a display-only setting (quiet hours' times), and attached rows that are targets themselves (a level's per-channel rows), look as before.
|
|
15
|
+
- Docs: https://raft-design.botiverse.build/components/list-row/#a-row-that-belongs-to-the-setting-above
|
|
16
|
+
- **`ArtifactIcon` (`@botiverse/ricons` 0.1.1): its page is drawn 2 wider** ([#1210](https://github.com/botiverse/raft-design/pull/1210)). raft-ui 0.95.1 depends on ricons 0.1.1.
|
|
17
|
+
- Why: Lucide's portrait page is 16 wide where the icons beside it in a rail or a menu are 20, so at the same size it read one size smaller.
|
|
18
|
+
- Affects: the page spans x 3 to 21 of the 24 grid (was 4 to 20), same height; `FollowArtifactIcon` and `UnfollowArtifactIcon` use the same page. The `< >` is unchanged.
|
|
19
|
+
- Docs: https://raft-design.botiverse.build/components/icons/
|
|
20
|
+
|
|
1
21
|
## [0.95.0](https://github.com/botiverse/raft-design/compare/v0.94.0...v0.95.0) (2026-11-11)
|
|
2
22
|
|
|
3
23
|
### Breaking
|
|
@@ -36,6 +36,7 @@ function ListRow({ variant = "default", tone = "default", selected = false, inte
|
|
|
36
36
|
"data-tone": tone === "default" ? void 0 : tone,
|
|
37
37
|
"data-selected": selected ? "" : void 0,
|
|
38
38
|
"data-attached": attached ? "" : void 0,
|
|
39
|
+
"data-interactive": interactive ? "" : void 0,
|
|
39
40
|
className: root({ className }),
|
|
40
41
|
...interactive && !render ? { type: "button" } : {}
|
|
41
42
|
};
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { listCell, listCellFocus, listCellStates } from "../../lib/list-cell.mjs";
|
|
2
2
|
import { tv } from "../../lib/tv.mjs";
|
|
3
|
+
// src/components/list-row/list-row.recipe.ts
|
|
4
|
+
const hairlineLine = ["before:pointer-events-none before:absolute before:top-0 before:right-0 before:hidden", "before:left-(--list-row-rule-start) before:border-t before:border-line-hairline"];
|
|
5
|
+
const hairline = [...hairlineLine, "[[data-slot=list-row]+&]:before:block [li+li>&]:before:block"];
|
|
6
|
+
const attachedAfterTarget = [...hairlineLine, "[[data-slot=list-row][data-interactive]:not([data-attached])+&]:before:block"];
|
|
3
7
|
const listRow = tv({
|
|
4
8
|
slots: {
|
|
5
9
|
group: ["flex min-w-0 flex-col overflow-hidden", "rounded-container border border-line-hairline bg-layer-card text-foreground"],
|
|
@@ -70,17 +74,26 @@ const listRow = tv({
|
|
|
70
74
|
cell: false,
|
|
71
75
|
attached: false,
|
|
72
76
|
variant: ["default", "plain"],
|
|
73
|
-
class: { root:
|
|
74
|
-
"before:pointer-events-none before:absolute before:top-0 before:right-0 before:hidden",
|
|
75
|
-
"before:left-(--list-row-rule-start) before:border-t before:border-line-hairline",
|
|
76
|
-
"[[data-slot=list-row]+&]:before:block [li+li>&]:before:block"
|
|
77
|
-
] }
|
|
77
|
+
class: { root: hairline }
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
variant: "plain",
|
|
81
81
|
attached: true,
|
|
82
82
|
class: { root: "pt-0" }
|
|
83
83
|
},
|
|
84
|
+
{
|
|
85
|
+
cell: false,
|
|
86
|
+
attached: true,
|
|
87
|
+
interactive: false,
|
|
88
|
+
variant: ["default", "plain"],
|
|
89
|
+
class: { root: attachedAfterTarget }
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
variant: "plain",
|
|
93
|
+
attached: true,
|
|
94
|
+
interactive: false,
|
|
95
|
+
class: { root: "[[data-slot=list-row][data-interactive]:not([data-attached])+&]:pt-(--settings-row-inset)" }
|
|
96
|
+
},
|
|
84
97
|
{
|
|
85
98
|
variant: "plain",
|
|
86
99
|
attached: true,
|
|
@@ -11,9 +11,19 @@ export type SelectProps<Value, Multiple extends boolean | undefined = false> = S
|
|
|
11
11
|
*/
|
|
12
12
|
chrome?: SelectChrome;
|
|
13
13
|
};
|
|
14
|
-
declare function Select$1<Value, Multiple extends boolean | undefined = false>({ chrome, onValueChange, onOpenChange, isItemEqualToValue, itemToStringLabel, itemToStringValue, ...props }: SelectProps<Value, Multiple>): import("react").JSX.Element;
|
|
15
|
-
export type SelectTriggerProps = StyledProps<Select.Trigger.Props> & DataAttributes
|
|
16
|
-
|
|
14
|
+
declare function Select$1<Value, Multiple extends boolean | undefined = false>({ chrome, onValueChange, onOpenChange, isItemEqualToValue, itemToStringLabel, itemToStringValue, items, ...props }: SelectProps<Value, Multiple>): import("react").JSX.Element;
|
|
15
|
+
export type SelectTriggerProps = StyledProps<Select.Trigger.Props> & DataAttributes & {
|
|
16
|
+
/**
|
|
17
|
+
* Makes the trigger as wide as its longest option and its placeholder, at
|
|
18
|
+
* least 11rem and at most 20rem, past which the value ends in an ellipsis.
|
|
19
|
+
* The width comes from every label in the root's `items`, so it stays the
|
|
20
|
+
* same whichever value is chosen. For a select at the end of a settings
|
|
21
|
+
* row; needs `items` on `Select` and a `SelectValue` in the trigger.
|
|
22
|
+
* @default false
|
|
23
|
+
*/
|
|
24
|
+
fit?: boolean;
|
|
25
|
+
};
|
|
26
|
+
export declare function SelectTrigger({ className, children, render, fit, "data-invalid": dataInvalid, ...props }: SelectTriggerProps): import("react").JSX.Element;
|
|
17
27
|
export type SelectValueProps = StyledProps<Select.Value.Props>;
|
|
18
28
|
export declare function SelectValue({ className, ref, title, ...props }: SelectValueProps): import("react").JSX.Element;
|
|
19
29
|
export type SelectIconProps = StyledProps<Select.Icon.Props>;
|
|
@@ -39,6 +39,10 @@ const select = tv({
|
|
|
39
39
|
"[&>svg]:size-4 [&>svg]:stroke-2 [&>svg_*]:stroke-2"
|
|
40
40
|
],
|
|
41
41
|
value: ["flex min-w-0 flex-1 truncate text-left"],
|
|
42
|
+
fitFrame: ["grid min-w-0 flex-1 grid-cols-[minmax(0,1fr)]"],
|
|
43
|
+
sizer: ["col-start-1 row-start-1 flex h-0 flex-col overflow-hidden", "pointer-events-none invisible select-none"],
|
|
44
|
+
sizerLabel: ["whitespace-nowrap font-semibold"],
|
|
45
|
+
sizerPlaceholder: ["whitespace-nowrap font-medium"],
|
|
42
46
|
positioner: ["isolate z-popup outline-none"],
|
|
43
47
|
content: [
|
|
44
48
|
"relative max-h-(--available-height) min-w-[max(var(--anchor-width),220px)] overflow-hidden",
|
|
@@ -100,19 +104,24 @@ const select = tv({
|
|
|
100
104
|
],
|
|
101
105
|
empty: ["px-2.5 py-2 text-left text-callout text-foreground-hint"]
|
|
102
106
|
},
|
|
103
|
-
variants: {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
107
|
+
variants: {
|
|
108
|
+
chrome: {
|
|
109
|
+
default: {},
|
|
110
|
+
field: {
|
|
111
|
+
trigger: [
|
|
112
|
+
"px-3 text-field font-field",
|
|
113
|
+
"**:data-[slot='button-content']:font-field **:data-[slot='select-value']:font-field",
|
|
114
|
+
"h-control rounded-(--control-radius) py-0",
|
|
115
|
+
"data-placeholder:font-field"
|
|
116
|
+
],
|
|
117
|
+
item: ["text-field font-field", "**:data-[slot='select-item-text']:font-field"],
|
|
118
|
+
actionItem: ["text-field font-field"],
|
|
119
|
+
sizerLabel: ["font-field"],
|
|
120
|
+
sizerPlaceholder: ["font-field"]
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
fit: { true: { trigger: ["w-auto min-w-44 max-w-80"] } }
|
|
124
|
+
},
|
|
116
125
|
defaultVariants: { chrome: "default" }
|
|
117
126
|
});
|
|
118
127
|
/**
|
|
@@ -133,6 +142,19 @@ function useSelectChrome() {
|
|
|
133
142
|
return useContext(SelectChromeContext);
|
|
134
143
|
}
|
|
135
144
|
/**
|
|
145
|
+
* The root's `items`, for a `fit` trigger's sizer: Base UI keeps them in a
|
|
146
|
+
* store of its own that the trigger can't read.
|
|
147
|
+
*/
|
|
148
|
+
const SelectItemsContext = createContext(void 0);
|
|
149
|
+
/** Whether the trigger around a `SelectValue` sizes to the longest option. */
|
|
150
|
+
const SelectFitContext = createContext(false);
|
|
151
|
+
/** Every label in `items`, groups flattened: what a `fit` trigger may show. */
|
|
152
|
+
function itemLabels(items) {
|
|
153
|
+
if (items == null) return [];
|
|
154
|
+
if (!Array.isArray(items)) return Object.values(items);
|
|
155
|
+
return items.flatMap((entry) => Array.isArray(entry.items) ? entry.items : [entry]).map((item) => item.label);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
136
158
|
* The value a `SelectActionItem` gives Base UI, so the row joins the list's
|
|
137
159
|
* navigation and typeahead like any item. The root recognises it and never lets
|
|
138
160
|
* it become the value: a list action changes what the list shows, never what is
|
|
@@ -174,7 +196,7 @@ function keepActionHighlighted(event) {
|
|
|
174
196
|
if (lent) list.removeAttribute("tabindex");
|
|
175
197
|
});
|
|
176
198
|
}
|
|
177
|
-
function Select$1({ chrome = "default", onValueChange, onOpenChange, isItemEqualToValue, itemToStringLabel, itemToStringValue, ...props }) {
|
|
199
|
+
function Select$1({ chrome = "default", onValueChange, onOpenChange, isItemEqualToValue, itemToStringLabel, itemToStringValue, items, ...props }) {
|
|
178
200
|
const pendingActionClose = useRef(void 0);
|
|
179
201
|
const handleValueChange = useCallback((next, details) => {
|
|
180
202
|
const action = findActionValue(next);
|
|
@@ -203,21 +225,28 @@ function Select$1({ chrome = "default", onValueChange, onOpenChange, isItemEqual
|
|
|
203
225
|
const handleStringValue = useMemo(() => itemToStringValue && ((item) => item instanceof SelectActionValue ? "" : itemToStringValue(item)), [itemToStringValue]);
|
|
204
226
|
return /* @__PURE__ */ jsx(SelectChromeContext, {
|
|
205
227
|
value: chrome,
|
|
206
|
-
children: /* @__PURE__ */ jsx(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
228
|
+
children: /* @__PURE__ */ jsx(SelectItemsContext, {
|
|
229
|
+
value: items,
|
|
230
|
+
children: /* @__PURE__ */ jsx(Select.Root, {
|
|
231
|
+
"data-slot": "select",
|
|
232
|
+
"data-chrome": chrome,
|
|
233
|
+
onValueChange: handleValueChange,
|
|
234
|
+
onOpenChange: handleOpenChange,
|
|
235
|
+
isItemEqualToValue: handleItemEquality,
|
|
236
|
+
itemToStringLabel: handleStringLabel,
|
|
237
|
+
itemToStringValue: handleStringValue,
|
|
238
|
+
items,
|
|
239
|
+
...props
|
|
240
|
+
})
|
|
215
241
|
})
|
|
216
242
|
});
|
|
217
243
|
}
|
|
218
|
-
function SelectTrigger({ className, children, render, "data-invalid": dataInvalid, ...props }) {
|
|
244
|
+
function SelectTrigger({ className, children, render, fit = false, "data-invalid": dataInvalid, ...props }) {
|
|
219
245
|
const chrome = useSelectChrome();
|
|
220
|
-
const { trigger } = select({
|
|
246
|
+
const { trigger } = select({
|
|
247
|
+
chrome,
|
|
248
|
+
fit
|
|
249
|
+
});
|
|
221
250
|
return /* @__PURE__ */ jsx(Select.Trigger, {
|
|
222
251
|
"data-slot": "select-trigger",
|
|
223
252
|
render: render ?? /* @__PURE__ */ jsx(Button, {
|
|
@@ -232,20 +261,59 @@ function SelectTrigger({ className, children, render, "data-invalid": dataInvali
|
|
|
232
261
|
"data-slot": "select-trigger-content",
|
|
233
262
|
"data-picker-trigger-content": "select",
|
|
234
263
|
className: "flex min-w-0 flex-1 items-center gap-1.5",
|
|
235
|
-
children
|
|
264
|
+
children: /* @__PURE__ */ jsx(SelectFitContext, {
|
|
265
|
+
value: fit,
|
|
266
|
+
children
|
|
267
|
+
})
|
|
236
268
|
})
|
|
237
269
|
});
|
|
238
270
|
}
|
|
239
271
|
function SelectValue({ className, ref, title, ...props }) {
|
|
240
|
-
const
|
|
272
|
+
const fit = useContext(SelectFitContext);
|
|
273
|
+
const { value, fitFrame } = select();
|
|
241
274
|
const overflowRef = useOverflowTitle(ref, title);
|
|
242
|
-
|
|
275
|
+
const node = /* @__PURE__ */ jsx(Select.Value, {
|
|
243
276
|
ref: overflowRef,
|
|
244
277
|
title,
|
|
245
278
|
"data-slot": "select-value",
|
|
246
|
-
className: value({ className }),
|
|
279
|
+
className: value({ className: cn(fit && "col-start-1 row-start-1", className) }),
|
|
247
280
|
...props
|
|
248
281
|
});
|
|
282
|
+
if (!fit) return node;
|
|
283
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
284
|
+
"data-slot": "select-value-fit",
|
|
285
|
+
className: fitFrame(),
|
|
286
|
+
children: [node, /* @__PURE__ */ jsx(SelectValueSizer, { placeholder: props.placeholder })]
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Every option's label and the placeholder, stacked in the value's grid cell,
|
|
291
|
+
* invisible and 0px high: the cell is as wide as the widest of them, so the
|
|
292
|
+
* trigger is too, whichever value shows. Each takes the weight it shows at, so
|
|
293
|
+
* the width doesn't change when the placeholder gives way to a value.
|
|
294
|
+
*/
|
|
295
|
+
function SelectValueSizer({ placeholder }) {
|
|
296
|
+
const chrome = useSelectChrome();
|
|
297
|
+
const items = useContext(SelectItemsContext);
|
|
298
|
+
const { sizer, sizerLabel, sizerPlaceholder } = select({ chrome });
|
|
299
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
300
|
+
"aria-hidden": true,
|
|
301
|
+
"data-slot": "select-value-sizer",
|
|
302
|
+
className: sizer(),
|
|
303
|
+
children: [typeof placeholder === "string" ? /* @__PURE__ */ jsx("span", {
|
|
304
|
+
"data-label": placeholder,
|
|
305
|
+
className: sizerPlaceholder({ className: "after:content-[attr(data-label)]" })
|
|
306
|
+
}) : placeholder != null && /* @__PURE__ */ jsx("span", {
|
|
307
|
+
className: sizerPlaceholder(),
|
|
308
|
+
children: placeholder
|
|
309
|
+
}), itemLabels(items).map((label, index) => typeof label === "string" || typeof label === "number" ? /* @__PURE__ */ jsx("span", {
|
|
310
|
+
"data-label": label,
|
|
311
|
+
className: sizerLabel({ className: "after:content-[attr(data-label)]" })
|
|
312
|
+
}, index) : /* @__PURE__ */ jsx("span", {
|
|
313
|
+
className: sizerLabel(),
|
|
314
|
+
children: label
|
|
315
|
+
}, index))]
|
|
316
|
+
});
|
|
249
317
|
}
|
|
250
318
|
function SelectIcon({ className, children, ...props }) {
|
|
251
319
|
const { icon } = select();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botiverse/raft-ui",
|
|
3
|
-
"version": "0.95.
|
|
3
|
+
"version": "0.95.1",
|
|
4
4
|
"description": "React components and CSS foundations for the Raft Design System.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skills",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@base-ui/react": "^1.9.0",
|
|
66
|
-
"@botiverse/ricons": "^0.1.
|
|
66
|
+
"@botiverse/ricons": "^0.1.1",
|
|
67
67
|
"@dnd-kit/core": "^6.3.1",
|
|
68
68
|
"@dnd-kit/sortable": "^10.0.0",
|
|
69
69
|
"@dnd-kit/utilities": "^3.2.2",
|