@nucleux/model-selection 0.9.0 → 0.10.0

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/index.cjs CHANGED
@@ -6,6 +6,7 @@ var utils = require('@nucleux/utils');
6
6
  var badge = require('@nucleux/badge');
7
7
  var radioGroup = require('@nucleux/radio-group');
8
8
  var _switch = require('@nucleux/switch');
9
+ var openInput = require('@nucleux/open-input');
9
10
  var lucideReact = require('lucide-react');
10
11
  var jsxRuntime = require('react/jsx-runtime');
11
12
 
@@ -39,7 +40,7 @@ function tradeoffLine(m) {
39
40
  ] }, i));
40
41
  }
41
42
  var RecommendedBadge = () => /* @__PURE__ */ jsxRuntime.jsx(badge.Badge, { variant: "outline", className: "border-brand/40 px-1.5 py-0 text-[10px] uppercase tracking-wide text-brand", children: "Recommended" });
42
- function ModelDropdown({ models, current, select, label, className }) {
43
+ function ModelDropdown({ models, current, select, label, className, trigger = "button", triggerDetail, menuSide = "bottom" }) {
43
44
  const [open, setOpen] = React__namespace.useState(false);
44
45
  const rootRef = React__namespace.useRef(null);
45
46
  const triggerRef = React__namespace.useRef(null);
@@ -79,23 +80,35 @@ function ModelDropdown({ models, current, select, label, className }) {
79
80
  else if (e.key === "Tab") close(false);
80
81
  };
81
82
  const selected = models.find((m) => m.value === current);
83
+ const triggerProps = {
84
+ // The chip shows the model, so its name carries it too ("Model: Sonnet").
85
+ "aria-label": trigger === "chip" && typeof selected?.label === "string" ? `${label}: ${selected.label}` : label,
86
+ "aria-haspopup": "listbox",
87
+ "aria-expanded": open,
88
+ "aria-controls": open ? listId : void 0,
89
+ onClick: () => setOpen((o) => !o),
90
+ onKeyDown: (e) => {
91
+ if ((e.key === "ArrowDown" || e.key === "ArrowUp") && !open) {
92
+ e.preventDefault();
93
+ setOpen(true);
94
+ }
95
+ }
96
+ };
82
97
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, className: utils.cn("relative inline-block", className), children: [
83
- /* @__PURE__ */ jsxRuntime.jsxs(
98
+ trigger === "chip" ? /* @__PURE__ */ jsxRuntime.jsx(
99
+ openInput.OpenInputModelChip,
100
+ {
101
+ ref: triggerRef,
102
+ name: selected?.label ?? "Select model",
103
+ detail: triggerDetail,
104
+ ...triggerProps
105
+ }
106
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(
84
107
  "button",
85
108
  {
86
109
  ref: triggerRef,
87
110
  type: "button",
88
- "aria-label": label,
89
- "aria-haspopup": "listbox",
90
- "aria-expanded": open,
91
- "aria-controls": open ? listId : void 0,
92
- onClick: () => setOpen((o) => !o),
93
- onKeyDown: (e) => {
94
- if ((e.key === "ArrowDown" || e.key === "ArrowUp") && !open) {
95
- e.preventDefault();
96
- setOpen(true);
97
- }
98
- },
111
+ ...triggerProps,
99
112
  className: "inline-flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2 text-sm font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring [&>svg]:size-4",
100
113
  children: [
101
114
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "text-brand", "aria-hidden": "true" }),
@@ -112,7 +125,11 @@ function ModelDropdown({ models, current, select, label, className }) {
112
125
  role: "listbox",
113
126
  "aria-label": label,
114
127
  onKeyDown: onListKeyDown,
115
- className: "absolute z-50 mt-1 flex min-w-64 flex-col gap-0.5 rounded-xl border border-border bg-background p-1.5 shadow-md animate-nx-fade-in motion-reduce:animate-none",
128
+ className: utils.cn(
129
+ "absolute z-50 flex min-w-64 flex-col gap-0.5 rounded-xl border border-border bg-background p-1.5 shadow-md animate-nx-fade-in motion-reduce:animate-none",
130
+ menuSide === "top" ? "bottom-full mb-1" : "top-full mt-1",
131
+ trigger === "chip" && "right-0 w-80"
132
+ ),
116
133
  children: models.map((model) => {
117
134
  const active = model.value === current;
118
135
  const line = tradeoffLine(model);
@@ -155,6 +172,68 @@ function ModelDropdown({ models, current, select, label, className }) {
155
172
  )
156
173
  ] });
157
174
  }
175
+ function ModelList({ models, current, select, label, headingId }) {
176
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
177
+ /* @__PURE__ */ jsxRuntime.jsx("p", { id: headingId, className: "sr-only", children: label }),
178
+ /* @__PURE__ */ jsxRuntime.jsx(
179
+ radioGroup.RadioGroup,
180
+ {
181
+ "aria-labelledby": headingId,
182
+ value: current,
183
+ onValueChange: select,
184
+ className: "gap-0 overflow-hidden rounded-xl border border-border bg-background",
185
+ children: models.map((model) => {
186
+ const id = `${headingId}-${model.value}`;
187
+ const active = model.value === current;
188
+ const sub = model.description ?? model.quality;
189
+ return /* @__PURE__ */ jsxRuntime.jsxs(
190
+ "label",
191
+ {
192
+ className: utils.cn(
193
+ "flex cursor-pointer items-center gap-3 border-b border-border px-3.5 py-2.5 transition-colors last:border-b-0 hover:bg-accent/60",
194
+ active && "bg-accent/60",
195
+ model.disabled && "cursor-not-allowed opacity-60 hover:bg-transparent"
196
+ ),
197
+ children: [
198
+ /* @__PURE__ */ jsxRuntime.jsx(
199
+ radioGroup.Radio,
200
+ {
201
+ value: model.value,
202
+ disabled: model.disabled,
203
+ "aria-labelledby": id,
204
+ className: utils.cn("size-2 border-0 shadow-none [&>span]:hidden", active ? "bg-warning" : "bg-border")
205
+ }
206
+ ),
207
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { id, className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
208
+ /* @__PURE__ */ jsxRuntime.jsxs(
209
+ "span",
210
+ {
211
+ className: utils.cn(
212
+ "flex items-center gap-2 text-sm",
213
+ active ? "font-semibold text-foreground" : "text-foreground/80"
214
+ ),
215
+ children: [
216
+ model.label,
217
+ model.recommended && /* @__PURE__ */ jsxRuntime.jsx(RecommendedBadge, {})
218
+ ]
219
+ }
220
+ ),
221
+ sub && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: sub }),
222
+ model.disabled && model.unavailableReason && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-foreground", children: model.unavailableReason })
223
+ ] }),
224
+ (model.speed || model.cost) && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex shrink-0 flex-col items-end gap-0.5 text-xs", children: [
225
+ model.speed && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: model.speed }),
226
+ model.cost && /* @__PURE__ */ jsxRuntime.jsx("span", { className: utils.cn("font-semibold", active ? "text-warning" : "text-muted-foreground"), children: model.cost })
227
+ ] })
228
+ ]
229
+ },
230
+ model.value
231
+ );
232
+ })
233
+ }
234
+ )
235
+ ] });
236
+ }
158
237
  var ModelSelection = React__namespace.forwardRef(
159
238
  ({
160
239
  models,
@@ -168,6 +247,10 @@ var ModelSelection = React__namespace.forwardRef(
168
247
  onAutoRouteChange,
169
248
  routedModel,
170
249
  agentName = "Aria",
250
+ trigger = "button",
251
+ triggerDetail,
252
+ menuSide = "bottom",
253
+ picker = "dropdown",
171
254
  className,
172
255
  ...props
173
256
  }, ref) => {
@@ -185,69 +268,57 @@ var ModelSelection = React__namespace.forwardRef(
185
268
  };
186
269
  const autoLabelId = React__namespace.useId();
187
270
  const headingId = React__namespace.useId();
271
+ const list = /* @__PURE__ */ jsxRuntime.jsx(ModelList, { models, current, select, label, headingId });
272
+ const dropdown = /* @__PURE__ */ jsxRuntime.jsx(
273
+ ModelDropdown,
274
+ {
275
+ models,
276
+ current,
277
+ select,
278
+ label,
279
+ trigger,
280
+ triggerDetail,
281
+ menuSide
282
+ }
283
+ );
188
284
  if (variant === "tradeoff") {
189
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: utils.cn("flex flex-col gap-2", className), ...props, children: [
190
- /* @__PURE__ */ jsxRuntime.jsx("p", { id: headingId, className: "sr-only", children: label }),
191
- /* @__PURE__ */ jsxRuntime.jsx(
192
- radioGroup.RadioGroup,
193
- {
194
- "aria-labelledby": headingId,
195
- value: current,
196
- onValueChange: select,
197
- className: "gap-1.5",
198
- children: models.map((model) => {
199
- const id = `${headingId}-${model.value}`;
200
- const line = tradeoffLine(model);
201
- return /* @__PURE__ */ jsxRuntime.jsxs(
202
- "label",
203
- {
204
- className: utils.cn(
205
- "flex cursor-pointer items-center gap-3 rounded-lg border border-border bg-background px-3 py-2 transition-colors hover:bg-accent",
206
- "has-[[aria-checked=true]]:border-brand has-[[aria-checked=true]]:bg-brand-muted/40",
207
- model.disabled && "cursor-not-allowed opacity-60 hover:bg-background"
208
- ),
209
- children: [
210
- /* @__PURE__ */ jsxRuntime.jsx(radioGroup.Radio, { value: model.value, disabled: model.disabled, "aria-labelledby": id }),
211
- /* @__PURE__ */ jsxRuntime.jsxs("span", { id, className: "flex min-w-0 flex-1 flex-col", children: [
212
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2 text-sm font-semibold text-foreground", children: [
213
- model.label,
214
- model.recommended && /* @__PURE__ */ jsxRuntime.jsx(RecommendedBadge, {})
215
- ] }),
216
- (line ?? model.description) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: line ?? model.description }),
217
- model.disabled && model.unavailableReason && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-foreground", children: model.unavailableReason })
218
- ] }),
219
- model.cost && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-xs font-semibold text-muted-foreground", children: model.cost })
220
- ]
221
- },
222
- model.value
223
- );
224
- })
225
- }
226
- )
227
- ] });
285
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: utils.cn("flex flex-col gap-2", className), ...props, children: list });
228
286
  }
229
287
  if (variant === "auto-route") {
230
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: utils.cn("flex flex-col gap-2", className), ...props, children: [
288
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: utils.cn("flex flex-col gap-2.5", className), ...props, children: [
231
289
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 rounded-lg border border-border bg-background px-3.5 py-2.5", children: [
232
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col", children: [
290
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [
233
291
  /* @__PURE__ */ jsxRuntime.jsx("span", { id: autoLabelId, className: "text-sm font-semibold text-foreground", children: "Auto-route" }),
234
292
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
235
293
  agentName,
236
- " picks the model per task"
294
+ " picks the model per task",
295
+ auto && routedModel && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
296
+ " \xB7 ",
297
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { "aria-live": "polite", children: [
298
+ "currently ",
299
+ routedModel
300
+ ] })
301
+ ] })
237
302
  ] })
238
303
  ] }),
239
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
240
- auto && routedModel && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", "aria-live": "polite", children: [
241
- "currently: ",
242
- routedModel
243
- ] }),
244
- /* @__PURE__ */ jsxRuntime.jsx(_switch.Switch, { checked: auto, onCheckedChange: setAuto, "aria-labelledby": autoLabelId })
245
- ] })
304
+ /* @__PURE__ */ jsxRuntime.jsx(_switch.Switch, { checked: auto, onCheckedChange: setAuto, "aria-labelledby": autoLabelId, className: "shrink-0" })
246
305
  ] }),
247
- !auto && /* @__PURE__ */ jsxRuntime.jsx(ModelDropdown, { models, current, select, label })
306
+ picker === "list" ? /* @__PURE__ */ jsxRuntime.jsx(
307
+ ModelList,
308
+ {
309
+ models,
310
+ current,
311
+ select: (next) => {
312
+ select(next);
313
+ if (auto) setAuto(false);
314
+ },
315
+ label,
316
+ headingId
317
+ }
318
+ ) : !auto && dropdown
248
319
  ] });
249
320
  }
250
- return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: utils.cn("relative inline-block", className), ...props, children: /* @__PURE__ */ jsxRuntime.jsx(ModelDropdown, { models, current, select, label }) });
321
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: utils.cn("relative inline-block", className), ...props, children: dropdown });
251
322
  }
252
323
  );
253
324
  ModelSelection.displayName = "ModelSelection";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx"],"names":["jsxs","React","jsx","Badge","cn","Sparkles","ChevronDown","Check","RadioGroup","Radio","Switch"],"mappings":"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","file":"index.cjs","sourcesContent":["import * as React from \"react\";\nimport { cn } from \"@nucleux/utils\";\nimport { Badge } from \"@nucleux/badge\";\nimport { Radio, RadioGroup } from \"@nucleux/radio-group\";\nimport { Switch } from \"@nucleux/switch\";\nimport { Check, ChevronDown, Sparkles } from \"lucide-react\";\n\n/** Layout from the spec: a compact dropdown, tradeoff cards, or an auto-route toggle that reveals the picker. */\nexport type ModelSelectionVariant = \"dropdown\" | \"tradeoff\" | \"auto-route\";\n\nexport interface ModelOption {\n /** Stable value passed to `onValueChange`. */\n value: string;\n /** Display label, e.g. `\"Nebula 5.5 Instant\"`. */\n label: React.ReactNode;\n /** Supporting line under the label — what the model is *for*, e.g. `\"Everyday tasks\"`. */\n description?: React.ReactNode;\n /** Optional leading icon. */\n icon?: React.ReactNode;\n /** Marks the suggested default with a \"Recommended\" badge. */\n recommended?: boolean;\n /** Speed in human terms, e.g. `\"Fastest\"`. */\n speed?: React.ReactNode;\n /** Quality in human terms, e.g. `\"Best quality\"`. */\n quality?: React.ReactNode;\n /** Cost in human terms, e.g. `\"$$\"` or `\"Uses more of your allowance\"`. */\n cost?: React.ReactNode;\n /** The option can't be chosen right now (allowance used up, not in your plan). The row stays. */\n disabled?: boolean;\n /** Why it is disabled and what answers instead, e.g. `\"Limit reached until 3 pm — Sonnet answers instead\"`. */\n unavailableReason?: React.ReactNode;\n}\n\nexport interface ModelSelectionProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** The available models. */\n models: ModelOption[];\n /** Selected value (controlled). */\n value?: string;\n /** Initial value (uncontrolled). */\n defaultValue?: string;\n /** Called when a model is chosen. */\n onValueChange?: (value: string) => void;\n /** Accessible label for the trigger. Defaults to `\"Model\"`. */\n label?: string;\n /** Layout. Defaults to `\"dropdown\"`. */\n variant?: ModelSelectionVariant;\n /** Auto-route on (controlled), for `variant=\"auto-route\"`. */\n autoRoute?: boolean;\n /** Initial auto-route state (uncontrolled). Defaults to `true`: auto-route is the default. */\n defaultAutoRoute?: boolean;\n /** Called when auto-route is switched. */\n onAutoRouteChange?: (on: boolean) => void;\n /** The model auto-route is using now, shown as \"currently: …\". */\n routedModel?: React.ReactNode;\n /** Who routes, for the description. Defaults to `\"Aria\"`. */\n agentName?: React.ReactNode;\n}\n\n/** \"Fastest · Best quality · $$\" from whichever tradeoff facts are given. */\nfunction tradeoffLine(m: ModelOption): React.ReactNode {\n const parts = [m.speed, m.quality].filter((p) => p != null && p !== \"\");\n if (!parts.length) return null;\n return parts.map((p, i) => (\n <React.Fragment key={i}>\n {i > 0 && \" · \"}\n {p}\n </React.Fragment>\n ));\n}\n\nconst RecommendedBadge = () => (\n <Badge variant=\"outline\" className=\"border-brand/40 px-1.5 py-0 text-[10px] uppercase tracking-wide text-brand\">\n Recommended\n </Badge>\n);\n\ninterface PickerProps {\n models: ModelOption[];\n current: string | undefined;\n select: (value: string) => void;\n label: string;\n className?: string;\n}\n\n/** The composer-anchored dropdown (listbox) used by the \"dropdown\" and \"auto-route\" variants. */\nfunction ModelDropdown({ models, current, select, label, className }: PickerProps) {\n const [open, setOpen] = React.useState(false);\n const rootRef = React.useRef<HTMLDivElement>(null);\n const triggerRef = React.useRef<HTMLButtonElement>(null);\n const listRef = React.useRef<HTMLDivElement>(null);\n const listId = React.useId();\n\n const close = React.useCallback((refocus: boolean) => {\n setOpen(false);\n if (refocus) triggerRef.current?.focus();\n }, []);\n\n React.useEffect(() => {\n if (!open) return;\n const onDown = (e: MouseEvent) => {\n if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false);\n };\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && close(true);\n document.addEventListener(\"mousedown\", onDown);\n document.addEventListener(\"keydown\", onKey);\n return () => {\n document.removeEventListener(\"mousedown\", onDown);\n document.removeEventListener(\"keydown\", onKey);\n };\n }, [open, close]);\n\n const options = () =>\n Array.from(listRef.current?.querySelectorAll<HTMLElement>('[role=\"option\"]:not([aria-disabled=\"true\"])') ?? []);\n\n // Focus the selected option (or the first available) when the list opens.\n React.useEffect(() => {\n if (!open) return;\n const all = options();\n (all.find((o) => o.getAttribute(\"aria-selected\") === \"true\") ?? all[0])?.focus();\n }, [open]);\n\n const onListKeyDown = (e: React.KeyboardEvent) => {\n const all = options();\n const i = all.indexOf(document.activeElement as HTMLElement);\n const go = (n: number) => all[(n + all.length) % all.length]?.focus();\n if (e.key === \"ArrowDown\") (e.preventDefault(), go(i + 1));\n else if (e.key === \"ArrowUp\") (e.preventDefault(), go(i - 1));\n else if (e.key === \"Home\") (e.preventDefault(), go(0));\n else if (e.key === \"End\") (e.preventDefault(), go(all.length - 1));\n else if (e.key === \"Tab\") close(false);\n };\n\n const selected = models.find((m) => m.value === current);\n\n return (\n <div ref={rootRef} className={cn(\"relative inline-block\", className)}>\n <button\n ref={triggerRef}\n type=\"button\"\n aria-label={label}\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-controls={open ? listId : undefined}\n onClick={() => setOpen((o) => !o)}\n onKeyDown={(e) => {\n if ((e.key === \"ArrowDown\" || e.key === \"ArrowUp\") && !open) {\n e.preventDefault();\n setOpen(true);\n }\n }}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2 text-sm font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring [&>svg]:size-4\"\n >\n <Sparkles className=\"text-brand\" aria-hidden=\"true\" />\n <span>{selected?.label ?? \"Select model\"}</span>\n <ChevronDown className={cn(\"text-muted-foreground transition-transform\", open && \"rotate-180\")} aria-hidden=\"true\" />\n </button>\n {open && (\n <div\n ref={listRef}\n id={listId}\n role=\"listbox\"\n aria-label={label}\n onKeyDown={onListKeyDown}\n className=\"absolute z-50 mt-1 flex min-w-64 flex-col gap-0.5 rounded-xl border border-border bg-background p-1.5 shadow-md animate-nx-fade-in motion-reduce:animate-none\"\n >\n {models.map((model) => {\n const active = model.value === current;\n const line = tradeoffLine(model);\n return (\n <button\n key={model.value}\n type=\"button\"\n role=\"option\"\n aria-selected={active}\n aria-disabled={model.disabled || undefined}\n tabIndex={-1}\n onClick={() => {\n if (model.disabled) return;\n select(model.value);\n close(true);\n }}\n className={cn(\n \"flex items-start gap-2.5 rounded-lg px-2 py-2 text-left transition-colors focus-visible:outline-none\",\n model.disabled ? \"cursor-not-allowed opacity-60\" : \"hover:bg-accent focus-visible:bg-accent\",\n active && \"bg-brand-muted/40\",\n )}\n >\n <span className=\"mt-0.5 inline-flex size-4 shrink-0 items-center justify-center text-brand [&_svg]:size-4\">\n {active ? <Check aria-hidden=\"true\" /> : model.icon}\n </span>\n <span className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"flex items-center gap-2 text-sm font-medium text-foreground\">\n {model.label}\n {model.recommended && <RecommendedBadge />}\n </span>\n {model.description && <span className=\"text-xs text-muted-foreground\">{model.description}</span>}\n {line && <span className=\"text-xs text-muted-foreground\">{line}</span>}\n {model.disabled && model.unavailableReason && (\n <span className=\"text-xs text-foreground\">{model.unavailableReason}</span>\n )}\n </span>\n {model.cost && <span className=\"shrink-0 text-xs font-semibold text-muted-foreground\">{model.cost}</span>}\n </button>\n );\n })}\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Lets the user pick the model for the task. `variant=\"dropdown\"` (default) is\n * a composer-anchored listbox (arrow keys, Home/End, Escape returns focus);\n * `\"tradeoff\"` lays models out as radio cards with speed, quality and cost in\n * human terms; `\"auto-route\"` defaults to letting the agent route per task,\n * shows which model it is using, and reveals the picker when switched off.\n * Recommended and unavailable models are marked in words; an unavailable model\n * keeps its row and says what answers instead.\n * Source: Figma \"Context Expansion / Model Selection\".\n */\nexport const ModelSelection = React.forwardRef<HTMLDivElement, ModelSelectionProps>(\n (\n {\n models,\n value,\n defaultValue,\n onValueChange,\n label = \"Model\",\n variant = \"dropdown\",\n autoRoute,\n defaultAutoRoute = true,\n onAutoRouteChange,\n routedModel,\n agentName = \"Aria\",\n className,\n ...props\n },\n ref,\n ) => {\n const [internal, setInternal] = React.useState<string | undefined>(defaultValue ?? models[0]?.value);\n const current = value !== undefined ? value : internal;\n const select = (next: string) => {\n if (value === undefined) setInternal(next);\n onValueChange?.(next);\n };\n\n const [autoInternal, setAutoInternal] = React.useState(defaultAutoRoute);\n const auto = autoRoute !== undefined ? autoRoute : autoInternal;\n const setAuto = (on: boolean) => {\n if (autoRoute === undefined) setAutoInternal(on);\n onAutoRouteChange?.(on);\n };\n const autoLabelId = React.useId();\n const headingId = React.useId();\n\n if (variant === \"tradeoff\") {\n return (\n <div ref={ref} className={cn(\"flex flex-col gap-2\", className)} {...props}>\n <p id={headingId} className=\"sr-only\">\n {label}\n </p>\n <RadioGroup\n aria-labelledby={headingId}\n value={current}\n onValueChange={select}\n className=\"gap-1.5\"\n >\n {models.map((model) => {\n const id = `${headingId}-${model.value}`;\n const line = tradeoffLine(model);\n return (\n <label\n key={model.value}\n className={cn(\n \"flex cursor-pointer items-center gap-3 rounded-lg border border-border bg-background px-3 py-2 transition-colors hover:bg-accent\",\n \"has-[[aria-checked=true]]:border-brand has-[[aria-checked=true]]:bg-brand-muted/40\",\n model.disabled && \"cursor-not-allowed opacity-60 hover:bg-background\",\n )}\n >\n <Radio value={model.value} disabled={model.disabled} aria-labelledby={id} />\n <span id={id} className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"flex items-center gap-2 text-sm font-semibold text-foreground\">\n {model.label}\n {model.recommended && <RecommendedBadge />}\n </span>\n {(line ?? model.description) && (\n <span className=\"text-xs text-muted-foreground\">{line ?? model.description}</span>\n )}\n {model.disabled && model.unavailableReason && (\n <span className=\"text-xs text-foreground\">{model.unavailableReason}</span>\n )}\n </span>\n {model.cost && (\n <span className=\"shrink-0 text-xs font-semibold text-muted-foreground\">{model.cost}</span>\n )}\n </label>\n );\n })}\n </RadioGroup>\n </div>\n );\n }\n\n if (variant === \"auto-route\") {\n return (\n <div ref={ref} className={cn(\"flex flex-col gap-2\", className)} {...props}>\n <div className=\"flex items-center justify-between gap-3 rounded-lg border border-border bg-background px-3.5 py-2.5\">\n <div className=\"flex min-w-0 flex-col\">\n <span id={autoLabelId} className=\"text-sm font-semibold text-foreground\">\n Auto-route\n </span>\n <span className=\"text-xs text-muted-foreground\">{agentName} picks the model per task</span>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n {auto && routedModel && (\n <span className=\"text-xs text-muted-foreground\" aria-live=\"polite\">\n currently: {routedModel}\n </span>\n )}\n <Switch checked={auto} onCheckedChange={setAuto} aria-labelledby={autoLabelId} />\n </div>\n </div>\n {!auto && <ModelDropdown models={models} current={current} select={select} label={label} />}\n </div>\n );\n }\n\n return (\n <div ref={ref} className={cn(\"relative inline-block\", className)} {...props}>\n <ModelDropdown models={models} current={current} select={select} label={label} />\n </div>\n );\n },\n);\nModelSelection.displayName = \"ModelSelection\";\n"]}
1
+ {"version":3,"sources":["../src/index.tsx"],"names":["jsxs","React","jsx","Badge","cn","OpenInputModelChip","Sparkles","ChevronDown","Check","Fragment","RadioGroup","Radio","Switch"],"mappings":"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","file":"index.cjs","sourcesContent":["import * as React from \"react\";\nimport { cn } from \"@nucleux/utils\";\nimport { Badge } from \"@nucleux/badge\";\nimport { Radio, RadioGroup } from \"@nucleux/radio-group\";\nimport { Switch } from \"@nucleux/switch\";\nimport { OpenInputModelChip } from \"@nucleux/open-input\";\nimport { Check, ChevronDown, Sparkles } from \"lucide-react\";\n\n/** Layout from the spec: a compact dropdown, tradeoff cards, or an auto-route toggle that reveals the picker. */\nexport type ModelSelectionVariant = \"dropdown\" | \"tradeoff\" | \"auto-route\";\n\n/**\n * How the dropdown's trigger is drawn. \"button\": a bordered button with a\n * sparkle, the model and a chevron. \"chip\": the composer's own model chip\n * (`OpenInputModelChip` / `Md3OpenInputModelChip`, \"Sonnet ▾\", \"Balanced High\")\n * — pass the whole `ModelSelection` to the composer's `model` slot.\n */\nexport type ModelSelectionTrigger = \"button\" | \"chip\";\n\n/** Which picker accompanies the auto-route switch: a dropdown revealed when it is off, or the tradeoff list, always shown. */\nexport type ModelSelectionPicker = \"dropdown\" | \"list\";\n\nexport interface ModelOption {\n /** Stable value passed to `onValueChange`. */\n value: string;\n /** Display label, e.g. `\"Nebula 5.5 Instant\"`. */\n label: React.ReactNode;\n /** Supporting line under the label — what the model is *for*, e.g. `\"Everyday tasks\"`. */\n description?: React.ReactNode;\n /** Optional leading icon. */\n icon?: React.ReactNode;\n /** Marks the suggested default with a \"Recommended\" badge. */\n recommended?: boolean;\n /** Speed in human terms, e.g. `\"Fastest\"`. */\n speed?: React.ReactNode;\n /** Quality in human terms, e.g. `\"Best quality\"`. */\n quality?: React.ReactNode;\n /** Cost in human terms, e.g. `\"$$\"` or `\"Uses more of your allowance\"`. */\n cost?: React.ReactNode;\n /** The option can't be chosen right now (allowance used up, not in your plan). The row stays. */\n disabled?: boolean;\n /** Why it is disabled and what answers instead, e.g. `\"Limit reached until 3 pm — Sonnet answers instead\"`. */\n unavailableReason?: React.ReactNode;\n}\n\nexport interface ModelSelectionProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** The available models. */\n models: ModelOption[];\n /** Selected value (controlled). */\n value?: string;\n /** Initial value (uncontrolled). */\n defaultValue?: string;\n /** Called when a model is chosen. */\n onValueChange?: (value: string) => void;\n /** Accessible label for the trigger. Defaults to `\"Model\"`. */\n label?: string;\n /** Layout. Defaults to `\"dropdown\"`. */\n variant?: ModelSelectionVariant;\n /** Auto-route on (controlled), for `variant=\"auto-route\"`. */\n autoRoute?: boolean;\n /** Initial auto-route state (uncontrolled). Defaults to `true`: auto-route is the default. */\n defaultAutoRoute?: boolean;\n /** Called when auto-route is switched. */\n onAutoRouteChange?: (on: boolean) => void;\n /** The model auto-route is using now, shown as \"currently …\". */\n routedModel?: React.ReactNode;\n /** Who routes, for the description. Defaults to `\"Aria\"`. */\n agentName?: React.ReactNode;\n /** Trigger of the dropdown (\"dropdown\" and \"auto-route\" variants). Defaults to `\"button\"`. */\n trigger?: ModelSelectionTrigger;\n /** With `trigger=\"chip\"`: the chip's quieter second value after the model, e.g. `\"Instant\"` or the effort `\"High\"`. */\n triggerDetail?: React.ReactNode;\n /** Which side of the trigger the menu opens on. Defaults to `\"bottom\"`; use `\"top\"` in a composer docked at the bottom. */\n menuSide?: \"top\" | \"bottom\";\n /**\n * `variant=\"auto-route\"` only: `\"dropdown\"` (default) reveals a dropdown when\n * auto-route is switched off; `\"list\"` always shows the tradeoff list under\n * the switch, marking the model in use, and picking a row switches auto-route off.\n */\n picker?: ModelSelectionPicker;\n}\n\n/** \"Fastest · Best quality · $$\" from whichever tradeoff facts are given. */\nfunction tradeoffLine(m: ModelOption): React.ReactNode {\n const parts = [m.speed, m.quality].filter((p) => p != null && p !== \"\");\n if (!parts.length) return null;\n return parts.map((p, i) => (\n <React.Fragment key={i}>\n {i > 0 && \" · \"}\n {p}\n </React.Fragment>\n ));\n}\n\nconst RecommendedBadge = () => (\n <Badge variant=\"outline\" className=\"border-brand/40 px-1.5 py-0 text-[10px] uppercase tracking-wide text-brand\">\n Recommended\n </Badge>\n);\n\ninterface PickerProps {\n models: ModelOption[];\n current: string | undefined;\n select: (value: string) => void;\n label: string;\n className?: string;\n trigger?: ModelSelectionTrigger;\n triggerDetail?: React.ReactNode;\n menuSide?: \"top\" | \"bottom\";\n}\n\n/** The composer-anchored dropdown (listbox) used by the \"dropdown\" and \"auto-route\" variants. */\nfunction ModelDropdown({ models, current, select, label, className, trigger = \"button\", triggerDetail, menuSide = \"bottom\" }: PickerProps) {\n const [open, setOpen] = React.useState(false);\n const rootRef = React.useRef<HTMLDivElement>(null);\n const triggerRef = React.useRef<HTMLButtonElement>(null);\n const listRef = React.useRef<HTMLDivElement>(null);\n const listId = React.useId();\n\n const close = React.useCallback((refocus: boolean) => {\n setOpen(false);\n if (refocus) triggerRef.current?.focus();\n }, []);\n\n React.useEffect(() => {\n if (!open) return;\n const onDown = (e: MouseEvent) => {\n if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false);\n };\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && close(true);\n document.addEventListener(\"mousedown\", onDown);\n document.addEventListener(\"keydown\", onKey);\n return () => {\n document.removeEventListener(\"mousedown\", onDown);\n document.removeEventListener(\"keydown\", onKey);\n };\n }, [open, close]);\n\n const options = () =>\n Array.from(listRef.current?.querySelectorAll<HTMLElement>('[role=\"option\"]:not([aria-disabled=\"true\"])') ?? []);\n\n // Focus the selected option (or the first available) when the list opens.\n React.useEffect(() => {\n if (!open) return;\n const all = options();\n (all.find((o) => o.getAttribute(\"aria-selected\") === \"true\") ?? all[0])?.focus();\n }, [open]);\n\n const onListKeyDown = (e: React.KeyboardEvent) => {\n const all = options();\n const i = all.indexOf(document.activeElement as HTMLElement);\n const go = (n: number) => all[(n + all.length) % all.length]?.focus();\n if (e.key === \"ArrowDown\") (e.preventDefault(), go(i + 1));\n else if (e.key === \"ArrowUp\") (e.preventDefault(), go(i - 1));\n else if (e.key === \"Home\") (e.preventDefault(), go(0));\n else if (e.key === \"End\") (e.preventDefault(), go(all.length - 1));\n else if (e.key === \"Tab\") close(false);\n };\n\n const selected = models.find((m) => m.value === current);\n const triggerProps = {\n // The chip shows the model, so its name carries it too (\"Model: Sonnet\").\n \"aria-label\": trigger === \"chip\" && typeof selected?.label === \"string\" ? `${label}: ${selected.label}` : label,\n \"aria-haspopup\": \"listbox\" as const,\n \"aria-expanded\": open,\n \"aria-controls\": open ? listId : undefined,\n onClick: () => setOpen((o) => !o),\n onKeyDown: (e: React.KeyboardEvent) => {\n if ((e.key === \"ArrowDown\" || e.key === \"ArrowUp\") && !open) {\n e.preventDefault();\n setOpen(true);\n }\n },\n };\n\n return (\n <div ref={rootRef} className={cn(\"relative inline-block\", className)}>\n {trigger === \"chip\" ? (\n <OpenInputModelChip\n ref={triggerRef}\n name={selected?.label ?? \"Select model\"}\n detail={triggerDetail}\n {...triggerProps}\n />\n ) : (\n <button\n ref={triggerRef}\n type=\"button\"\n {...triggerProps}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2 text-sm font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring [&>svg]:size-4\"\n >\n <Sparkles className=\"text-brand\" aria-hidden=\"true\" />\n <span>{selected?.label ?? \"Select model\"}</span>\n <ChevronDown className={cn(\"text-muted-foreground transition-transform\", open && \"rotate-180\")} aria-hidden=\"true\" />\n </button>\n )}\n {open && (\n <div\n ref={listRef}\n id={listId}\n role=\"listbox\"\n aria-label={label}\n onKeyDown={onListKeyDown}\n className={cn(\n \"absolute z-50 flex min-w-64 flex-col gap-0.5 rounded-xl border border-border bg-background p-1.5 shadow-md animate-nx-fade-in motion-reduce:animate-none\",\n menuSide === \"top\" ? \"bottom-full mb-1\" : \"top-full mt-1\",\n trigger === \"chip\" && \"right-0 w-80\",\n )}\n >\n {models.map((model) => {\n const active = model.value === current;\n const line = tradeoffLine(model);\n return (\n <button\n key={model.value}\n type=\"button\"\n role=\"option\"\n aria-selected={active}\n aria-disabled={model.disabled || undefined}\n tabIndex={-1}\n onClick={() => {\n if (model.disabled) return;\n select(model.value);\n close(true);\n }}\n className={cn(\n \"flex items-start gap-2.5 rounded-lg px-2 py-2 text-left transition-colors focus-visible:outline-none\",\n model.disabled ? \"cursor-not-allowed opacity-60\" : \"hover:bg-accent focus-visible:bg-accent\",\n active && \"bg-brand-muted/40\",\n )}\n >\n <span className=\"mt-0.5 inline-flex size-4 shrink-0 items-center justify-center text-brand [&_svg]:size-4\">\n {active ? <Check aria-hidden=\"true\" /> : model.icon}\n </span>\n <span className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"flex items-center gap-2 text-sm font-medium text-foreground\">\n {model.label}\n {model.recommended && <RecommendedBadge />}\n </span>\n {model.description && <span className=\"text-xs text-muted-foreground\">{model.description}</span>}\n {line && <span className=\"text-xs text-muted-foreground\">{line}</span>}\n {model.disabled && model.unavailableReason && (\n <span className=\"text-xs text-foreground\">{model.unavailableReason}</span>\n )}\n </span>\n {model.cost && <span className=\"shrink-0 text-xs font-semibold text-muted-foreground\">{model.cost}</span>}\n </button>\n );\n })}\n </div>\n )}\n </div>\n );\n}\n\ninterface ModelListProps extends PickerProps {\n headingId: string;\n}\n\n/**\n * The tradeoff list: one bordered card of radio rows — a dot, the model and\n * what it is for, and speed over cost at the right. The chosen row is tinted\n * and its dot and cost take the warm accent (`warning`), as on nucleux.in.\n */\nfunction ModelList({ models, current, select, label, headingId }: ModelListProps) {\n return (\n <>\n <p id={headingId} className=\"sr-only\">\n {label}\n </p>\n <RadioGroup\n aria-labelledby={headingId}\n value={current}\n onValueChange={select}\n className=\"gap-0 overflow-hidden rounded-xl border border-border bg-background\"\n >\n {models.map((model) => {\n const id = `${headingId}-${model.value}`;\n const active = model.value === current;\n const sub = model.description ?? model.quality;\n return (\n <label\n key={model.value}\n className={cn(\n \"flex cursor-pointer items-center gap-3 border-b border-border px-3.5 py-2.5 transition-colors last:border-b-0 hover:bg-accent/60\",\n active && \"bg-accent/60\",\n model.disabled && \"cursor-not-allowed opacity-60 hover:bg-transparent\",\n )}\n >\n <Radio\n value={model.value}\n disabled={model.disabled}\n aria-labelledby={id}\n className={cn(\"size-2 border-0 shadow-none [&>span]:hidden\", active ? \"bg-warning\" : \"bg-border\")}\n />\n <span id={id} className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span\n className={cn(\n \"flex items-center gap-2 text-sm\",\n active ? \"font-semibold text-foreground\" : \"text-foreground/80\",\n )}\n >\n {model.label}\n {model.recommended && <RecommendedBadge />}\n </span>\n {sub && <span className=\"text-xs text-muted-foreground\">{sub}</span>}\n {model.disabled && model.unavailableReason && (\n <span className=\"text-xs text-foreground\">{model.unavailableReason}</span>\n )}\n </span>\n {(model.speed || model.cost) && (\n <span className=\"flex shrink-0 flex-col items-end gap-0.5 text-xs\">\n {model.speed && <span className=\"text-muted-foreground\">{model.speed}</span>}\n {model.cost && (\n <span className={cn(\"font-semibold\", active ? \"text-warning\" : \"text-muted-foreground\")}>\n {model.cost}\n </span>\n )}\n </span>\n )}\n </label>\n );\n })}\n </RadioGroup>\n </>\n );\n}\n\n/**\n * Lets the user pick the model for the task. `variant=\"dropdown\"` (default) is\n * a composer-anchored listbox (arrow keys, Home/End, Escape returns focus);\n * `\"tradeoff\"` lays models out as radio cards with speed, quality and cost in\n * human terms; `\"auto-route\"` defaults to letting the agent route per task,\n * shows which model it is using, and reveals the picker when switched off.\n * Recommended and unavailable models are marked in words; an unavailable model\n * keeps its row and says what answers instead. With `trigger=\"chip\"` the\n * dropdown's trigger is the composer's model chip (\"Sonnet ▾\"), so the whole\n * component goes in `OpenInput`'s `model` slot (`menuSide=\"top\"` there).\n * Source: Figma \"Context Expansion / Model Selection\".\n */\nexport const ModelSelection = React.forwardRef<HTMLDivElement, ModelSelectionProps>(\n (\n {\n models,\n value,\n defaultValue,\n onValueChange,\n label = \"Model\",\n variant = \"dropdown\",\n autoRoute,\n defaultAutoRoute = true,\n onAutoRouteChange,\n routedModel,\n agentName = \"Aria\",\n trigger = \"button\",\n triggerDetail,\n menuSide = \"bottom\",\n picker = \"dropdown\",\n className,\n ...props\n },\n ref,\n ) => {\n const [internal, setInternal] = React.useState<string | undefined>(defaultValue ?? models[0]?.value);\n const current = value !== undefined ? value : internal;\n const select = (next: string) => {\n if (value === undefined) setInternal(next);\n onValueChange?.(next);\n };\n\n const [autoInternal, setAutoInternal] = React.useState(defaultAutoRoute);\n const auto = autoRoute !== undefined ? autoRoute : autoInternal;\n const setAuto = (on: boolean) => {\n if (autoRoute === undefined) setAutoInternal(on);\n onAutoRouteChange?.(on);\n };\n const autoLabelId = React.useId();\n const headingId = React.useId();\n\n const list = (\n <ModelList models={models} current={current} select={select} label={label} headingId={headingId} />\n );\n const dropdown = (\n <ModelDropdown\n models={models}\n current={current}\n select={select}\n label={label}\n trigger={trigger}\n triggerDetail={triggerDetail}\n menuSide={menuSide}\n />\n );\n\n if (variant === \"tradeoff\") {\n return (\n <div ref={ref} className={cn(\"flex flex-col gap-2\", className)} {...props}>\n {list}\n </div>\n );\n }\n\n if (variant === \"auto-route\") {\n return (\n <div ref={ref} className={cn(\"flex flex-col gap-2.5\", className)} {...props}>\n <div className=\"flex items-center justify-between gap-3 rounded-lg border border-border bg-background px-3.5 py-2.5\">\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <span id={autoLabelId} className=\"text-sm font-semibold text-foreground\">\n Auto-route\n </span>\n <span className=\"text-xs text-muted-foreground\">\n {agentName} picks the model per task\n {auto && routedModel && (\n <>\n {\" · \"}\n <span aria-live=\"polite\">currently {routedModel}</span>\n </>\n )}\n </span>\n </div>\n <Switch checked={auto} onCheckedChange={setAuto} aria-labelledby={autoLabelId} className=\"shrink-0\" />\n </div>\n {picker === \"list\" ? (\n <ModelList\n models={models}\n current={current}\n select={(next) => {\n select(next);\n if (auto) setAuto(false);\n }}\n label={label}\n headingId={headingId}\n />\n ) : (\n !auto && dropdown\n )}\n </div>\n );\n }\n\n return (\n <div ref={ref} className={cn(\"relative inline-block\", className)} {...props}>\n {dropdown}\n </div>\n );\n },\n);\nModelSelection.displayName = \"ModelSelection\";\n"]}
package/dist/index.d.cts CHANGED
@@ -2,6 +2,15 @@ import * as React from 'react';
2
2
 
3
3
  /** Layout from the spec: a compact dropdown, tradeoff cards, or an auto-route toggle that reveals the picker. */
4
4
  type ModelSelectionVariant = "dropdown" | "tradeoff" | "auto-route";
5
+ /**
6
+ * How the dropdown's trigger is drawn. "button": a bordered button with a
7
+ * sparkle, the model and a chevron. "chip": the composer's own model chip
8
+ * (`OpenInputModelChip` / `Md3OpenInputModelChip`, "Sonnet ▾", "Balanced High")
9
+ * — pass the whole `ModelSelection` to the composer's `model` slot.
10
+ */
11
+ type ModelSelectionTrigger = "button" | "chip";
12
+ /** Which picker accompanies the auto-route switch: a dropdown revealed when it is off, or the tradeoff list, always shown. */
13
+ type ModelSelectionPicker = "dropdown" | "list";
5
14
  interface ModelOption {
6
15
  /** Stable value passed to `onValueChange`. */
7
16
  value: string;
@@ -43,10 +52,22 @@ interface ModelSelectionProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
43
52
  defaultAutoRoute?: boolean;
44
53
  /** Called when auto-route is switched. */
45
54
  onAutoRouteChange?: (on: boolean) => void;
46
- /** The model auto-route is using now, shown as "currently: …". */
55
+ /** The model auto-route is using now, shown as "currently …". */
47
56
  routedModel?: React.ReactNode;
48
57
  /** Who routes, for the description. Defaults to `"Aria"`. */
49
58
  agentName?: React.ReactNode;
59
+ /** Trigger of the dropdown ("dropdown" and "auto-route" variants). Defaults to `"button"`. */
60
+ trigger?: ModelSelectionTrigger;
61
+ /** With `trigger="chip"`: the chip's quieter second value after the model, e.g. `"Instant"` or the effort `"High"`. */
62
+ triggerDetail?: React.ReactNode;
63
+ /** Which side of the trigger the menu opens on. Defaults to `"bottom"`; use `"top"` in a composer docked at the bottom. */
64
+ menuSide?: "top" | "bottom";
65
+ /**
66
+ * `variant="auto-route"` only: `"dropdown"` (default) reveals a dropdown when
67
+ * auto-route is switched off; `"list"` always shows the tradeoff list under
68
+ * the switch, marking the model in use, and picking a row switches auto-route off.
69
+ */
70
+ picker?: ModelSelectionPicker;
50
71
  }
51
72
  /**
52
73
  * Lets the user pick the model for the task. `variant="dropdown"` (default) is
@@ -55,9 +76,11 @@ interface ModelSelectionProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
55
76
  * human terms; `"auto-route"` defaults to letting the agent route per task,
56
77
  * shows which model it is using, and reveals the picker when switched off.
57
78
  * Recommended and unavailable models are marked in words; an unavailable model
58
- * keeps its row and says what answers instead.
79
+ * keeps its row and says what answers instead. With `trigger="chip"` the
80
+ * dropdown's trigger is the composer's model chip ("Sonnet ▾"), so the whole
81
+ * component goes in `OpenInput`'s `model` slot (`menuSide="top"` there).
59
82
  * Source: Figma "Context Expansion / Model Selection".
60
83
  */
61
84
  declare const ModelSelection: React.ForwardRefExoticComponent<ModelSelectionProps & React.RefAttributes<HTMLDivElement>>;
62
85
 
63
- export { type ModelOption, ModelSelection, type ModelSelectionProps, type ModelSelectionVariant };
86
+ export { type ModelOption, ModelSelection, type ModelSelectionPicker, type ModelSelectionProps, type ModelSelectionTrigger, type ModelSelectionVariant };
package/dist/index.d.ts CHANGED
@@ -2,6 +2,15 @@ import * as React from 'react';
2
2
 
3
3
  /** Layout from the spec: a compact dropdown, tradeoff cards, or an auto-route toggle that reveals the picker. */
4
4
  type ModelSelectionVariant = "dropdown" | "tradeoff" | "auto-route";
5
+ /**
6
+ * How the dropdown's trigger is drawn. "button": a bordered button with a
7
+ * sparkle, the model and a chevron. "chip": the composer's own model chip
8
+ * (`OpenInputModelChip` / `Md3OpenInputModelChip`, "Sonnet ▾", "Balanced High")
9
+ * — pass the whole `ModelSelection` to the composer's `model` slot.
10
+ */
11
+ type ModelSelectionTrigger = "button" | "chip";
12
+ /** Which picker accompanies the auto-route switch: a dropdown revealed when it is off, or the tradeoff list, always shown. */
13
+ type ModelSelectionPicker = "dropdown" | "list";
5
14
  interface ModelOption {
6
15
  /** Stable value passed to `onValueChange`. */
7
16
  value: string;
@@ -43,10 +52,22 @@ interface ModelSelectionProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
43
52
  defaultAutoRoute?: boolean;
44
53
  /** Called when auto-route is switched. */
45
54
  onAutoRouteChange?: (on: boolean) => void;
46
- /** The model auto-route is using now, shown as "currently: …". */
55
+ /** The model auto-route is using now, shown as "currently …". */
47
56
  routedModel?: React.ReactNode;
48
57
  /** Who routes, for the description. Defaults to `"Aria"`. */
49
58
  agentName?: React.ReactNode;
59
+ /** Trigger of the dropdown ("dropdown" and "auto-route" variants). Defaults to `"button"`. */
60
+ trigger?: ModelSelectionTrigger;
61
+ /** With `trigger="chip"`: the chip's quieter second value after the model, e.g. `"Instant"` or the effort `"High"`. */
62
+ triggerDetail?: React.ReactNode;
63
+ /** Which side of the trigger the menu opens on. Defaults to `"bottom"`; use `"top"` in a composer docked at the bottom. */
64
+ menuSide?: "top" | "bottom";
65
+ /**
66
+ * `variant="auto-route"` only: `"dropdown"` (default) reveals a dropdown when
67
+ * auto-route is switched off; `"list"` always shows the tradeoff list under
68
+ * the switch, marking the model in use, and picking a row switches auto-route off.
69
+ */
70
+ picker?: ModelSelectionPicker;
50
71
  }
51
72
  /**
52
73
  * Lets the user pick the model for the task. `variant="dropdown"` (default) is
@@ -55,9 +76,11 @@ interface ModelSelectionProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
55
76
  * human terms; `"auto-route"` defaults to letting the agent route per task,
56
77
  * shows which model it is using, and reveals the picker when switched off.
57
78
  * Recommended and unavailable models are marked in words; an unavailable model
58
- * keeps its row and says what answers instead.
79
+ * keeps its row and says what answers instead. With `trigger="chip"` the
80
+ * dropdown's trigger is the composer's model chip ("Sonnet ▾"), so the whole
81
+ * component goes in `OpenInput`'s `model` slot (`menuSide="top"` there).
59
82
  * Source: Figma "Context Expansion / Model Selection".
60
83
  */
61
84
  declare const ModelSelection: React.ForwardRefExoticComponent<ModelSelectionProps & React.RefAttributes<HTMLDivElement>>;
62
85
 
63
- export { type ModelOption, ModelSelection, type ModelSelectionProps, type ModelSelectionVariant };
86
+ export { type ModelOption, ModelSelection, type ModelSelectionPicker, type ModelSelectionProps, type ModelSelectionTrigger, type ModelSelectionVariant };
package/dist/index.js CHANGED
@@ -4,8 +4,9 @@ import { cn } from '@nucleux/utils';
4
4
  import { Badge } from '@nucleux/badge';
5
5
  import { RadioGroup, Radio } from '@nucleux/radio-group';
6
6
  import { Switch } from '@nucleux/switch';
7
+ import { OpenInputModelChip } from '@nucleux/open-input';
7
8
  import { Sparkles, ChevronDown, Check } from 'lucide-react';
8
- import { jsxs, jsx } from 'react/jsx-runtime';
9
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
10
 
10
11
  // src/index.tsx
11
12
  function tradeoffLine(m) {
@@ -17,7 +18,7 @@ function tradeoffLine(m) {
17
18
  ] }, i));
18
19
  }
19
20
  var RecommendedBadge = () => /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "border-brand/40 px-1.5 py-0 text-[10px] uppercase tracking-wide text-brand", children: "Recommended" });
20
- function ModelDropdown({ models, current, select, label, className }) {
21
+ function ModelDropdown({ models, current, select, label, className, trigger = "button", triggerDetail, menuSide = "bottom" }) {
21
22
  const [open, setOpen] = React.useState(false);
22
23
  const rootRef = React.useRef(null);
23
24
  const triggerRef = React.useRef(null);
@@ -57,23 +58,35 @@ function ModelDropdown({ models, current, select, label, className }) {
57
58
  else if (e.key === "Tab") close(false);
58
59
  };
59
60
  const selected = models.find((m) => m.value === current);
61
+ const triggerProps = {
62
+ // The chip shows the model, so its name carries it too ("Model: Sonnet").
63
+ "aria-label": trigger === "chip" && typeof selected?.label === "string" ? `${label}: ${selected.label}` : label,
64
+ "aria-haspopup": "listbox",
65
+ "aria-expanded": open,
66
+ "aria-controls": open ? listId : void 0,
67
+ onClick: () => setOpen((o) => !o),
68
+ onKeyDown: (e) => {
69
+ if ((e.key === "ArrowDown" || e.key === "ArrowUp") && !open) {
70
+ e.preventDefault();
71
+ setOpen(true);
72
+ }
73
+ }
74
+ };
60
75
  return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("relative inline-block", className), children: [
61
- /* @__PURE__ */ jsxs(
76
+ trigger === "chip" ? /* @__PURE__ */ jsx(
77
+ OpenInputModelChip,
78
+ {
79
+ ref: triggerRef,
80
+ name: selected?.label ?? "Select model",
81
+ detail: triggerDetail,
82
+ ...triggerProps
83
+ }
84
+ ) : /* @__PURE__ */ jsxs(
62
85
  "button",
63
86
  {
64
87
  ref: triggerRef,
65
88
  type: "button",
66
- "aria-label": label,
67
- "aria-haspopup": "listbox",
68
- "aria-expanded": open,
69
- "aria-controls": open ? listId : void 0,
70
- onClick: () => setOpen((o) => !o),
71
- onKeyDown: (e) => {
72
- if ((e.key === "ArrowDown" || e.key === "ArrowUp") && !open) {
73
- e.preventDefault();
74
- setOpen(true);
75
- }
76
- },
89
+ ...triggerProps,
77
90
  className: "inline-flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2 text-sm font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring [&>svg]:size-4",
78
91
  children: [
79
92
  /* @__PURE__ */ jsx(Sparkles, { className: "text-brand", "aria-hidden": "true" }),
@@ -90,7 +103,11 @@ function ModelDropdown({ models, current, select, label, className }) {
90
103
  role: "listbox",
91
104
  "aria-label": label,
92
105
  onKeyDown: onListKeyDown,
93
- className: "absolute z-50 mt-1 flex min-w-64 flex-col gap-0.5 rounded-xl border border-border bg-background p-1.5 shadow-md animate-nx-fade-in motion-reduce:animate-none",
106
+ className: cn(
107
+ "absolute z-50 flex min-w-64 flex-col gap-0.5 rounded-xl border border-border bg-background p-1.5 shadow-md animate-nx-fade-in motion-reduce:animate-none",
108
+ menuSide === "top" ? "bottom-full mb-1" : "top-full mt-1",
109
+ trigger === "chip" && "right-0 w-80"
110
+ ),
94
111
  children: models.map((model) => {
95
112
  const active = model.value === current;
96
113
  const line = tradeoffLine(model);
@@ -133,6 +150,68 @@ function ModelDropdown({ models, current, select, label, className }) {
133
150
  )
134
151
  ] });
135
152
  }
153
+ function ModelList({ models, current, select, label, headingId }) {
154
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
155
+ /* @__PURE__ */ jsx("p", { id: headingId, className: "sr-only", children: label }),
156
+ /* @__PURE__ */ jsx(
157
+ RadioGroup,
158
+ {
159
+ "aria-labelledby": headingId,
160
+ value: current,
161
+ onValueChange: select,
162
+ className: "gap-0 overflow-hidden rounded-xl border border-border bg-background",
163
+ children: models.map((model) => {
164
+ const id = `${headingId}-${model.value}`;
165
+ const active = model.value === current;
166
+ const sub = model.description ?? model.quality;
167
+ return /* @__PURE__ */ jsxs(
168
+ "label",
169
+ {
170
+ className: cn(
171
+ "flex cursor-pointer items-center gap-3 border-b border-border px-3.5 py-2.5 transition-colors last:border-b-0 hover:bg-accent/60",
172
+ active && "bg-accent/60",
173
+ model.disabled && "cursor-not-allowed opacity-60 hover:bg-transparent"
174
+ ),
175
+ children: [
176
+ /* @__PURE__ */ jsx(
177
+ Radio,
178
+ {
179
+ value: model.value,
180
+ disabled: model.disabled,
181
+ "aria-labelledby": id,
182
+ className: cn("size-2 border-0 shadow-none [&>span]:hidden", active ? "bg-warning" : "bg-border")
183
+ }
184
+ ),
185
+ /* @__PURE__ */ jsxs("span", { id, className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
186
+ /* @__PURE__ */ jsxs(
187
+ "span",
188
+ {
189
+ className: cn(
190
+ "flex items-center gap-2 text-sm",
191
+ active ? "font-semibold text-foreground" : "text-foreground/80"
192
+ ),
193
+ children: [
194
+ model.label,
195
+ model.recommended && /* @__PURE__ */ jsx(RecommendedBadge, {})
196
+ ]
197
+ }
198
+ ),
199
+ sub && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: sub }),
200
+ model.disabled && model.unavailableReason && /* @__PURE__ */ jsx("span", { className: "text-xs text-foreground", children: model.unavailableReason })
201
+ ] }),
202
+ (model.speed || model.cost) && /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 flex-col items-end gap-0.5 text-xs", children: [
203
+ model.speed && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: model.speed }),
204
+ model.cost && /* @__PURE__ */ jsx("span", { className: cn("font-semibold", active ? "text-warning" : "text-muted-foreground"), children: model.cost })
205
+ ] })
206
+ ]
207
+ },
208
+ model.value
209
+ );
210
+ })
211
+ }
212
+ )
213
+ ] });
214
+ }
136
215
  var ModelSelection = React.forwardRef(
137
216
  ({
138
217
  models,
@@ -146,6 +225,10 @@ var ModelSelection = React.forwardRef(
146
225
  onAutoRouteChange,
147
226
  routedModel,
148
227
  agentName = "Aria",
228
+ trigger = "button",
229
+ triggerDetail,
230
+ menuSide = "bottom",
231
+ picker = "dropdown",
149
232
  className,
150
233
  ...props
151
234
  }, ref) => {
@@ -163,69 +246,57 @@ var ModelSelection = React.forwardRef(
163
246
  };
164
247
  const autoLabelId = React.useId();
165
248
  const headingId = React.useId();
249
+ const list = /* @__PURE__ */ jsx(ModelList, { models, current, select, label, headingId });
250
+ const dropdown = /* @__PURE__ */ jsx(
251
+ ModelDropdown,
252
+ {
253
+ models,
254
+ current,
255
+ select,
256
+ label,
257
+ trigger,
258
+ triggerDetail,
259
+ menuSide
260
+ }
261
+ );
166
262
  if (variant === "tradeoff") {
167
- return /* @__PURE__ */ jsxs("div", { ref, className: cn("flex flex-col gap-2", className), ...props, children: [
168
- /* @__PURE__ */ jsx("p", { id: headingId, className: "sr-only", children: label }),
169
- /* @__PURE__ */ jsx(
170
- RadioGroup,
171
- {
172
- "aria-labelledby": headingId,
173
- value: current,
174
- onValueChange: select,
175
- className: "gap-1.5",
176
- children: models.map((model) => {
177
- const id = `${headingId}-${model.value}`;
178
- const line = tradeoffLine(model);
179
- return /* @__PURE__ */ jsxs(
180
- "label",
181
- {
182
- className: cn(
183
- "flex cursor-pointer items-center gap-3 rounded-lg border border-border bg-background px-3 py-2 transition-colors hover:bg-accent",
184
- "has-[[aria-checked=true]]:border-brand has-[[aria-checked=true]]:bg-brand-muted/40",
185
- model.disabled && "cursor-not-allowed opacity-60 hover:bg-background"
186
- ),
187
- children: [
188
- /* @__PURE__ */ jsx(Radio, { value: model.value, disabled: model.disabled, "aria-labelledby": id }),
189
- /* @__PURE__ */ jsxs("span", { id, className: "flex min-w-0 flex-1 flex-col", children: [
190
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-sm font-semibold text-foreground", children: [
191
- model.label,
192
- model.recommended && /* @__PURE__ */ jsx(RecommendedBadge, {})
193
- ] }),
194
- (line ?? model.description) && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: line ?? model.description }),
195
- model.disabled && model.unavailableReason && /* @__PURE__ */ jsx("span", { className: "text-xs text-foreground", children: model.unavailableReason })
196
- ] }),
197
- model.cost && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs font-semibold text-muted-foreground", children: model.cost })
198
- ]
199
- },
200
- model.value
201
- );
202
- })
203
- }
204
- )
205
- ] });
263
+ return /* @__PURE__ */ jsx("div", { ref, className: cn("flex flex-col gap-2", className), ...props, children: list });
206
264
  }
207
265
  if (variant === "auto-route") {
208
- return /* @__PURE__ */ jsxs("div", { ref, className: cn("flex flex-col gap-2", className), ...props, children: [
266
+ return /* @__PURE__ */ jsxs("div", { ref, className: cn("flex flex-col gap-2.5", className), ...props, children: [
209
267
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 rounded-lg border border-border bg-background px-3.5 py-2.5", children: [
210
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col", children: [
268
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [
211
269
  /* @__PURE__ */ jsx("span", { id: autoLabelId, className: "text-sm font-semibold text-foreground", children: "Auto-route" }),
212
270
  /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
213
271
  agentName,
214
- " picks the model per task"
272
+ " picks the model per task",
273
+ auto && routedModel && /* @__PURE__ */ jsxs(Fragment, { children: [
274
+ " \xB7 ",
275
+ /* @__PURE__ */ jsxs("span", { "aria-live": "polite", children: [
276
+ "currently ",
277
+ routedModel
278
+ ] })
279
+ ] })
215
280
  ] })
216
281
  ] }),
217
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
218
- auto && routedModel && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", "aria-live": "polite", children: [
219
- "currently: ",
220
- routedModel
221
- ] }),
222
- /* @__PURE__ */ jsx(Switch, { checked: auto, onCheckedChange: setAuto, "aria-labelledby": autoLabelId })
223
- ] })
282
+ /* @__PURE__ */ jsx(Switch, { checked: auto, onCheckedChange: setAuto, "aria-labelledby": autoLabelId, className: "shrink-0" })
224
283
  ] }),
225
- !auto && /* @__PURE__ */ jsx(ModelDropdown, { models, current, select, label })
284
+ picker === "list" ? /* @__PURE__ */ jsx(
285
+ ModelList,
286
+ {
287
+ models,
288
+ current,
289
+ select: (next) => {
290
+ select(next);
291
+ if (auto) setAuto(false);
292
+ },
293
+ label,
294
+ headingId
295
+ }
296
+ ) : !auto && dropdown
226
297
  ] });
227
298
  }
228
- return /* @__PURE__ */ jsx("div", { ref, className: cn("relative inline-block", className), ...props, children: /* @__PURE__ */ jsx(ModelDropdown, { models, current, select, label }) });
299
+ return /* @__PURE__ */ jsx("div", { ref, className: cn("relative inline-block", className), ...props, children: dropdown });
229
300
  }
230
301
  );
231
302
  ModelSelection.displayName = "ModelSelection";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import * as React from \"react\";\nimport { cn } from \"@nucleux/utils\";\nimport { Badge } from \"@nucleux/badge\";\nimport { Radio, RadioGroup } from \"@nucleux/radio-group\";\nimport { Switch } from \"@nucleux/switch\";\nimport { Check, ChevronDown, Sparkles } from \"lucide-react\";\n\n/** Layout from the spec: a compact dropdown, tradeoff cards, or an auto-route toggle that reveals the picker. */\nexport type ModelSelectionVariant = \"dropdown\" | \"tradeoff\" | \"auto-route\";\n\nexport interface ModelOption {\n /** Stable value passed to `onValueChange`. */\n value: string;\n /** Display label, e.g. `\"Nebula 5.5 Instant\"`. */\n label: React.ReactNode;\n /** Supporting line under the label — what the model is *for*, e.g. `\"Everyday tasks\"`. */\n description?: React.ReactNode;\n /** Optional leading icon. */\n icon?: React.ReactNode;\n /** Marks the suggested default with a \"Recommended\" badge. */\n recommended?: boolean;\n /** Speed in human terms, e.g. `\"Fastest\"`. */\n speed?: React.ReactNode;\n /** Quality in human terms, e.g. `\"Best quality\"`. */\n quality?: React.ReactNode;\n /** Cost in human terms, e.g. `\"$$\"` or `\"Uses more of your allowance\"`. */\n cost?: React.ReactNode;\n /** The option can't be chosen right now (allowance used up, not in your plan). The row stays. */\n disabled?: boolean;\n /** Why it is disabled and what answers instead, e.g. `\"Limit reached until 3 pm — Sonnet answers instead\"`. */\n unavailableReason?: React.ReactNode;\n}\n\nexport interface ModelSelectionProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** The available models. */\n models: ModelOption[];\n /** Selected value (controlled). */\n value?: string;\n /** Initial value (uncontrolled). */\n defaultValue?: string;\n /** Called when a model is chosen. */\n onValueChange?: (value: string) => void;\n /** Accessible label for the trigger. Defaults to `\"Model\"`. */\n label?: string;\n /** Layout. Defaults to `\"dropdown\"`. */\n variant?: ModelSelectionVariant;\n /** Auto-route on (controlled), for `variant=\"auto-route\"`. */\n autoRoute?: boolean;\n /** Initial auto-route state (uncontrolled). Defaults to `true`: auto-route is the default. */\n defaultAutoRoute?: boolean;\n /** Called when auto-route is switched. */\n onAutoRouteChange?: (on: boolean) => void;\n /** The model auto-route is using now, shown as \"currently: …\". */\n routedModel?: React.ReactNode;\n /** Who routes, for the description. Defaults to `\"Aria\"`. */\n agentName?: React.ReactNode;\n}\n\n/** \"Fastest · Best quality · $$\" from whichever tradeoff facts are given. */\nfunction tradeoffLine(m: ModelOption): React.ReactNode {\n const parts = [m.speed, m.quality].filter((p) => p != null && p !== \"\");\n if (!parts.length) return null;\n return parts.map((p, i) => (\n <React.Fragment key={i}>\n {i > 0 && \" · \"}\n {p}\n </React.Fragment>\n ));\n}\n\nconst RecommendedBadge = () => (\n <Badge variant=\"outline\" className=\"border-brand/40 px-1.5 py-0 text-[10px] uppercase tracking-wide text-brand\">\n Recommended\n </Badge>\n);\n\ninterface PickerProps {\n models: ModelOption[];\n current: string | undefined;\n select: (value: string) => void;\n label: string;\n className?: string;\n}\n\n/** The composer-anchored dropdown (listbox) used by the \"dropdown\" and \"auto-route\" variants. */\nfunction ModelDropdown({ models, current, select, label, className }: PickerProps) {\n const [open, setOpen] = React.useState(false);\n const rootRef = React.useRef<HTMLDivElement>(null);\n const triggerRef = React.useRef<HTMLButtonElement>(null);\n const listRef = React.useRef<HTMLDivElement>(null);\n const listId = React.useId();\n\n const close = React.useCallback((refocus: boolean) => {\n setOpen(false);\n if (refocus) triggerRef.current?.focus();\n }, []);\n\n React.useEffect(() => {\n if (!open) return;\n const onDown = (e: MouseEvent) => {\n if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false);\n };\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && close(true);\n document.addEventListener(\"mousedown\", onDown);\n document.addEventListener(\"keydown\", onKey);\n return () => {\n document.removeEventListener(\"mousedown\", onDown);\n document.removeEventListener(\"keydown\", onKey);\n };\n }, [open, close]);\n\n const options = () =>\n Array.from(listRef.current?.querySelectorAll<HTMLElement>('[role=\"option\"]:not([aria-disabled=\"true\"])') ?? []);\n\n // Focus the selected option (or the first available) when the list opens.\n React.useEffect(() => {\n if (!open) return;\n const all = options();\n (all.find((o) => o.getAttribute(\"aria-selected\") === \"true\") ?? all[0])?.focus();\n }, [open]);\n\n const onListKeyDown = (e: React.KeyboardEvent) => {\n const all = options();\n const i = all.indexOf(document.activeElement as HTMLElement);\n const go = (n: number) => all[(n + all.length) % all.length]?.focus();\n if (e.key === \"ArrowDown\") (e.preventDefault(), go(i + 1));\n else if (e.key === \"ArrowUp\") (e.preventDefault(), go(i - 1));\n else if (e.key === \"Home\") (e.preventDefault(), go(0));\n else if (e.key === \"End\") (e.preventDefault(), go(all.length - 1));\n else if (e.key === \"Tab\") close(false);\n };\n\n const selected = models.find((m) => m.value === current);\n\n return (\n <div ref={rootRef} className={cn(\"relative inline-block\", className)}>\n <button\n ref={triggerRef}\n type=\"button\"\n aria-label={label}\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-controls={open ? listId : undefined}\n onClick={() => setOpen((o) => !o)}\n onKeyDown={(e) => {\n if ((e.key === \"ArrowDown\" || e.key === \"ArrowUp\") && !open) {\n e.preventDefault();\n setOpen(true);\n }\n }}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2 text-sm font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring [&>svg]:size-4\"\n >\n <Sparkles className=\"text-brand\" aria-hidden=\"true\" />\n <span>{selected?.label ?? \"Select model\"}</span>\n <ChevronDown className={cn(\"text-muted-foreground transition-transform\", open && \"rotate-180\")} aria-hidden=\"true\" />\n </button>\n {open && (\n <div\n ref={listRef}\n id={listId}\n role=\"listbox\"\n aria-label={label}\n onKeyDown={onListKeyDown}\n className=\"absolute z-50 mt-1 flex min-w-64 flex-col gap-0.5 rounded-xl border border-border bg-background p-1.5 shadow-md animate-nx-fade-in motion-reduce:animate-none\"\n >\n {models.map((model) => {\n const active = model.value === current;\n const line = tradeoffLine(model);\n return (\n <button\n key={model.value}\n type=\"button\"\n role=\"option\"\n aria-selected={active}\n aria-disabled={model.disabled || undefined}\n tabIndex={-1}\n onClick={() => {\n if (model.disabled) return;\n select(model.value);\n close(true);\n }}\n className={cn(\n \"flex items-start gap-2.5 rounded-lg px-2 py-2 text-left transition-colors focus-visible:outline-none\",\n model.disabled ? \"cursor-not-allowed opacity-60\" : \"hover:bg-accent focus-visible:bg-accent\",\n active && \"bg-brand-muted/40\",\n )}\n >\n <span className=\"mt-0.5 inline-flex size-4 shrink-0 items-center justify-center text-brand [&_svg]:size-4\">\n {active ? <Check aria-hidden=\"true\" /> : model.icon}\n </span>\n <span className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"flex items-center gap-2 text-sm font-medium text-foreground\">\n {model.label}\n {model.recommended && <RecommendedBadge />}\n </span>\n {model.description && <span className=\"text-xs text-muted-foreground\">{model.description}</span>}\n {line && <span className=\"text-xs text-muted-foreground\">{line}</span>}\n {model.disabled && model.unavailableReason && (\n <span className=\"text-xs text-foreground\">{model.unavailableReason}</span>\n )}\n </span>\n {model.cost && <span className=\"shrink-0 text-xs font-semibold text-muted-foreground\">{model.cost}</span>}\n </button>\n );\n })}\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Lets the user pick the model for the task. `variant=\"dropdown\"` (default) is\n * a composer-anchored listbox (arrow keys, Home/End, Escape returns focus);\n * `\"tradeoff\"` lays models out as radio cards with speed, quality and cost in\n * human terms; `\"auto-route\"` defaults to letting the agent route per task,\n * shows which model it is using, and reveals the picker when switched off.\n * Recommended and unavailable models are marked in words; an unavailable model\n * keeps its row and says what answers instead.\n * Source: Figma \"Context Expansion / Model Selection\".\n */\nexport const ModelSelection = React.forwardRef<HTMLDivElement, ModelSelectionProps>(\n (\n {\n models,\n value,\n defaultValue,\n onValueChange,\n label = \"Model\",\n variant = \"dropdown\",\n autoRoute,\n defaultAutoRoute = true,\n onAutoRouteChange,\n routedModel,\n agentName = \"Aria\",\n className,\n ...props\n },\n ref,\n ) => {\n const [internal, setInternal] = React.useState<string | undefined>(defaultValue ?? models[0]?.value);\n const current = value !== undefined ? value : internal;\n const select = (next: string) => {\n if (value === undefined) setInternal(next);\n onValueChange?.(next);\n };\n\n const [autoInternal, setAutoInternal] = React.useState(defaultAutoRoute);\n const auto = autoRoute !== undefined ? autoRoute : autoInternal;\n const setAuto = (on: boolean) => {\n if (autoRoute === undefined) setAutoInternal(on);\n onAutoRouteChange?.(on);\n };\n const autoLabelId = React.useId();\n const headingId = React.useId();\n\n if (variant === \"tradeoff\") {\n return (\n <div ref={ref} className={cn(\"flex flex-col gap-2\", className)} {...props}>\n <p id={headingId} className=\"sr-only\">\n {label}\n </p>\n <RadioGroup\n aria-labelledby={headingId}\n value={current}\n onValueChange={select}\n className=\"gap-1.5\"\n >\n {models.map((model) => {\n const id = `${headingId}-${model.value}`;\n const line = tradeoffLine(model);\n return (\n <label\n key={model.value}\n className={cn(\n \"flex cursor-pointer items-center gap-3 rounded-lg border border-border bg-background px-3 py-2 transition-colors hover:bg-accent\",\n \"has-[[aria-checked=true]]:border-brand has-[[aria-checked=true]]:bg-brand-muted/40\",\n model.disabled && \"cursor-not-allowed opacity-60 hover:bg-background\",\n )}\n >\n <Radio value={model.value} disabled={model.disabled} aria-labelledby={id} />\n <span id={id} className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"flex items-center gap-2 text-sm font-semibold text-foreground\">\n {model.label}\n {model.recommended && <RecommendedBadge />}\n </span>\n {(line ?? model.description) && (\n <span className=\"text-xs text-muted-foreground\">{line ?? model.description}</span>\n )}\n {model.disabled && model.unavailableReason && (\n <span className=\"text-xs text-foreground\">{model.unavailableReason}</span>\n )}\n </span>\n {model.cost && (\n <span className=\"shrink-0 text-xs font-semibold text-muted-foreground\">{model.cost}</span>\n )}\n </label>\n );\n })}\n </RadioGroup>\n </div>\n );\n }\n\n if (variant === \"auto-route\") {\n return (\n <div ref={ref} className={cn(\"flex flex-col gap-2\", className)} {...props}>\n <div className=\"flex items-center justify-between gap-3 rounded-lg border border-border bg-background px-3.5 py-2.5\">\n <div className=\"flex min-w-0 flex-col\">\n <span id={autoLabelId} className=\"text-sm font-semibold text-foreground\">\n Auto-route\n </span>\n <span className=\"text-xs text-muted-foreground\">{agentName} picks the model per task</span>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n {auto && routedModel && (\n <span className=\"text-xs text-muted-foreground\" aria-live=\"polite\">\n currently: {routedModel}\n </span>\n )}\n <Switch checked={auto} onCheckedChange={setAuto} aria-labelledby={autoLabelId} />\n </div>\n </div>\n {!auto && <ModelDropdown models={models} current={current} select={select} label={label} />}\n </div>\n );\n }\n\n return (\n <div ref={ref} className={cn(\"relative inline-block\", className)} {...props}>\n <ModelDropdown models={models} current={current} select={select} label={label} />\n </div>\n );\n },\n);\nModelSelection.displayName = \"ModelSelection\";\n"]}
1
+ {"version":3,"sources":["../src/index.tsx"],"names":["Fragment"],"mappings":"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","file":"index.js","sourcesContent":["import * as React from \"react\";\nimport { cn } from \"@nucleux/utils\";\nimport { Badge } from \"@nucleux/badge\";\nimport { Radio, RadioGroup } from \"@nucleux/radio-group\";\nimport { Switch } from \"@nucleux/switch\";\nimport { OpenInputModelChip } from \"@nucleux/open-input\";\nimport { Check, ChevronDown, Sparkles } from \"lucide-react\";\n\n/** Layout from the spec: a compact dropdown, tradeoff cards, or an auto-route toggle that reveals the picker. */\nexport type ModelSelectionVariant = \"dropdown\" | \"tradeoff\" | \"auto-route\";\n\n/**\n * How the dropdown's trigger is drawn. \"button\": a bordered button with a\n * sparkle, the model and a chevron. \"chip\": the composer's own model chip\n * (`OpenInputModelChip` / `Md3OpenInputModelChip`, \"Sonnet ▾\", \"Balanced High\")\n * — pass the whole `ModelSelection` to the composer's `model` slot.\n */\nexport type ModelSelectionTrigger = \"button\" | \"chip\";\n\n/** Which picker accompanies the auto-route switch: a dropdown revealed when it is off, or the tradeoff list, always shown. */\nexport type ModelSelectionPicker = \"dropdown\" | \"list\";\n\nexport interface ModelOption {\n /** Stable value passed to `onValueChange`. */\n value: string;\n /** Display label, e.g. `\"Nebula 5.5 Instant\"`. */\n label: React.ReactNode;\n /** Supporting line under the label — what the model is *for*, e.g. `\"Everyday tasks\"`. */\n description?: React.ReactNode;\n /** Optional leading icon. */\n icon?: React.ReactNode;\n /** Marks the suggested default with a \"Recommended\" badge. */\n recommended?: boolean;\n /** Speed in human terms, e.g. `\"Fastest\"`. */\n speed?: React.ReactNode;\n /** Quality in human terms, e.g. `\"Best quality\"`. */\n quality?: React.ReactNode;\n /** Cost in human terms, e.g. `\"$$\"` or `\"Uses more of your allowance\"`. */\n cost?: React.ReactNode;\n /** The option can't be chosen right now (allowance used up, not in your plan). The row stays. */\n disabled?: boolean;\n /** Why it is disabled and what answers instead, e.g. `\"Limit reached until 3 pm — Sonnet answers instead\"`. */\n unavailableReason?: React.ReactNode;\n}\n\nexport interface ModelSelectionProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** The available models. */\n models: ModelOption[];\n /** Selected value (controlled). */\n value?: string;\n /** Initial value (uncontrolled). */\n defaultValue?: string;\n /** Called when a model is chosen. */\n onValueChange?: (value: string) => void;\n /** Accessible label for the trigger. Defaults to `\"Model\"`. */\n label?: string;\n /** Layout. Defaults to `\"dropdown\"`. */\n variant?: ModelSelectionVariant;\n /** Auto-route on (controlled), for `variant=\"auto-route\"`. */\n autoRoute?: boolean;\n /** Initial auto-route state (uncontrolled). Defaults to `true`: auto-route is the default. */\n defaultAutoRoute?: boolean;\n /** Called when auto-route is switched. */\n onAutoRouteChange?: (on: boolean) => void;\n /** The model auto-route is using now, shown as \"currently …\". */\n routedModel?: React.ReactNode;\n /** Who routes, for the description. Defaults to `\"Aria\"`. */\n agentName?: React.ReactNode;\n /** Trigger of the dropdown (\"dropdown\" and \"auto-route\" variants). Defaults to `\"button\"`. */\n trigger?: ModelSelectionTrigger;\n /** With `trigger=\"chip\"`: the chip's quieter second value after the model, e.g. `\"Instant\"` or the effort `\"High\"`. */\n triggerDetail?: React.ReactNode;\n /** Which side of the trigger the menu opens on. Defaults to `\"bottom\"`; use `\"top\"` in a composer docked at the bottom. */\n menuSide?: \"top\" | \"bottom\";\n /**\n * `variant=\"auto-route\"` only: `\"dropdown\"` (default) reveals a dropdown when\n * auto-route is switched off; `\"list\"` always shows the tradeoff list under\n * the switch, marking the model in use, and picking a row switches auto-route off.\n */\n picker?: ModelSelectionPicker;\n}\n\n/** \"Fastest · Best quality · $$\" from whichever tradeoff facts are given. */\nfunction tradeoffLine(m: ModelOption): React.ReactNode {\n const parts = [m.speed, m.quality].filter((p) => p != null && p !== \"\");\n if (!parts.length) return null;\n return parts.map((p, i) => (\n <React.Fragment key={i}>\n {i > 0 && \" · \"}\n {p}\n </React.Fragment>\n ));\n}\n\nconst RecommendedBadge = () => (\n <Badge variant=\"outline\" className=\"border-brand/40 px-1.5 py-0 text-[10px] uppercase tracking-wide text-brand\">\n Recommended\n </Badge>\n);\n\ninterface PickerProps {\n models: ModelOption[];\n current: string | undefined;\n select: (value: string) => void;\n label: string;\n className?: string;\n trigger?: ModelSelectionTrigger;\n triggerDetail?: React.ReactNode;\n menuSide?: \"top\" | \"bottom\";\n}\n\n/** The composer-anchored dropdown (listbox) used by the \"dropdown\" and \"auto-route\" variants. */\nfunction ModelDropdown({ models, current, select, label, className, trigger = \"button\", triggerDetail, menuSide = \"bottom\" }: PickerProps) {\n const [open, setOpen] = React.useState(false);\n const rootRef = React.useRef<HTMLDivElement>(null);\n const triggerRef = React.useRef<HTMLButtonElement>(null);\n const listRef = React.useRef<HTMLDivElement>(null);\n const listId = React.useId();\n\n const close = React.useCallback((refocus: boolean) => {\n setOpen(false);\n if (refocus) triggerRef.current?.focus();\n }, []);\n\n React.useEffect(() => {\n if (!open) return;\n const onDown = (e: MouseEvent) => {\n if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false);\n };\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && close(true);\n document.addEventListener(\"mousedown\", onDown);\n document.addEventListener(\"keydown\", onKey);\n return () => {\n document.removeEventListener(\"mousedown\", onDown);\n document.removeEventListener(\"keydown\", onKey);\n };\n }, [open, close]);\n\n const options = () =>\n Array.from(listRef.current?.querySelectorAll<HTMLElement>('[role=\"option\"]:not([aria-disabled=\"true\"])') ?? []);\n\n // Focus the selected option (or the first available) when the list opens.\n React.useEffect(() => {\n if (!open) return;\n const all = options();\n (all.find((o) => o.getAttribute(\"aria-selected\") === \"true\") ?? all[0])?.focus();\n }, [open]);\n\n const onListKeyDown = (e: React.KeyboardEvent) => {\n const all = options();\n const i = all.indexOf(document.activeElement as HTMLElement);\n const go = (n: number) => all[(n + all.length) % all.length]?.focus();\n if (e.key === \"ArrowDown\") (e.preventDefault(), go(i + 1));\n else if (e.key === \"ArrowUp\") (e.preventDefault(), go(i - 1));\n else if (e.key === \"Home\") (e.preventDefault(), go(0));\n else if (e.key === \"End\") (e.preventDefault(), go(all.length - 1));\n else if (e.key === \"Tab\") close(false);\n };\n\n const selected = models.find((m) => m.value === current);\n const triggerProps = {\n // The chip shows the model, so its name carries it too (\"Model: Sonnet\").\n \"aria-label\": trigger === \"chip\" && typeof selected?.label === \"string\" ? `${label}: ${selected.label}` : label,\n \"aria-haspopup\": \"listbox\" as const,\n \"aria-expanded\": open,\n \"aria-controls\": open ? listId : undefined,\n onClick: () => setOpen((o) => !o),\n onKeyDown: (e: React.KeyboardEvent) => {\n if ((e.key === \"ArrowDown\" || e.key === \"ArrowUp\") && !open) {\n e.preventDefault();\n setOpen(true);\n }\n },\n };\n\n return (\n <div ref={rootRef} className={cn(\"relative inline-block\", className)}>\n {trigger === \"chip\" ? (\n <OpenInputModelChip\n ref={triggerRef}\n name={selected?.label ?? \"Select model\"}\n detail={triggerDetail}\n {...triggerProps}\n />\n ) : (\n <button\n ref={triggerRef}\n type=\"button\"\n {...triggerProps}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2 text-sm font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring [&>svg]:size-4\"\n >\n <Sparkles className=\"text-brand\" aria-hidden=\"true\" />\n <span>{selected?.label ?? \"Select model\"}</span>\n <ChevronDown className={cn(\"text-muted-foreground transition-transform\", open && \"rotate-180\")} aria-hidden=\"true\" />\n </button>\n )}\n {open && (\n <div\n ref={listRef}\n id={listId}\n role=\"listbox\"\n aria-label={label}\n onKeyDown={onListKeyDown}\n className={cn(\n \"absolute z-50 flex min-w-64 flex-col gap-0.5 rounded-xl border border-border bg-background p-1.5 shadow-md animate-nx-fade-in motion-reduce:animate-none\",\n menuSide === \"top\" ? \"bottom-full mb-1\" : \"top-full mt-1\",\n trigger === \"chip\" && \"right-0 w-80\",\n )}\n >\n {models.map((model) => {\n const active = model.value === current;\n const line = tradeoffLine(model);\n return (\n <button\n key={model.value}\n type=\"button\"\n role=\"option\"\n aria-selected={active}\n aria-disabled={model.disabled || undefined}\n tabIndex={-1}\n onClick={() => {\n if (model.disabled) return;\n select(model.value);\n close(true);\n }}\n className={cn(\n \"flex items-start gap-2.5 rounded-lg px-2 py-2 text-left transition-colors focus-visible:outline-none\",\n model.disabled ? \"cursor-not-allowed opacity-60\" : \"hover:bg-accent focus-visible:bg-accent\",\n active && \"bg-brand-muted/40\",\n )}\n >\n <span className=\"mt-0.5 inline-flex size-4 shrink-0 items-center justify-center text-brand [&_svg]:size-4\">\n {active ? <Check aria-hidden=\"true\" /> : model.icon}\n </span>\n <span className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"flex items-center gap-2 text-sm font-medium text-foreground\">\n {model.label}\n {model.recommended && <RecommendedBadge />}\n </span>\n {model.description && <span className=\"text-xs text-muted-foreground\">{model.description}</span>}\n {line && <span className=\"text-xs text-muted-foreground\">{line}</span>}\n {model.disabled && model.unavailableReason && (\n <span className=\"text-xs text-foreground\">{model.unavailableReason}</span>\n )}\n </span>\n {model.cost && <span className=\"shrink-0 text-xs font-semibold text-muted-foreground\">{model.cost}</span>}\n </button>\n );\n })}\n </div>\n )}\n </div>\n );\n}\n\ninterface ModelListProps extends PickerProps {\n headingId: string;\n}\n\n/**\n * The tradeoff list: one bordered card of radio rows — a dot, the model and\n * what it is for, and speed over cost at the right. The chosen row is tinted\n * and its dot and cost take the warm accent (`warning`), as on nucleux.in.\n */\nfunction ModelList({ models, current, select, label, headingId }: ModelListProps) {\n return (\n <>\n <p id={headingId} className=\"sr-only\">\n {label}\n </p>\n <RadioGroup\n aria-labelledby={headingId}\n value={current}\n onValueChange={select}\n className=\"gap-0 overflow-hidden rounded-xl border border-border bg-background\"\n >\n {models.map((model) => {\n const id = `${headingId}-${model.value}`;\n const active = model.value === current;\n const sub = model.description ?? model.quality;\n return (\n <label\n key={model.value}\n className={cn(\n \"flex cursor-pointer items-center gap-3 border-b border-border px-3.5 py-2.5 transition-colors last:border-b-0 hover:bg-accent/60\",\n active && \"bg-accent/60\",\n model.disabled && \"cursor-not-allowed opacity-60 hover:bg-transparent\",\n )}\n >\n <Radio\n value={model.value}\n disabled={model.disabled}\n aria-labelledby={id}\n className={cn(\"size-2 border-0 shadow-none [&>span]:hidden\", active ? \"bg-warning\" : \"bg-border\")}\n />\n <span id={id} className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span\n className={cn(\n \"flex items-center gap-2 text-sm\",\n active ? \"font-semibold text-foreground\" : \"text-foreground/80\",\n )}\n >\n {model.label}\n {model.recommended && <RecommendedBadge />}\n </span>\n {sub && <span className=\"text-xs text-muted-foreground\">{sub}</span>}\n {model.disabled && model.unavailableReason && (\n <span className=\"text-xs text-foreground\">{model.unavailableReason}</span>\n )}\n </span>\n {(model.speed || model.cost) && (\n <span className=\"flex shrink-0 flex-col items-end gap-0.5 text-xs\">\n {model.speed && <span className=\"text-muted-foreground\">{model.speed}</span>}\n {model.cost && (\n <span className={cn(\"font-semibold\", active ? \"text-warning\" : \"text-muted-foreground\")}>\n {model.cost}\n </span>\n )}\n </span>\n )}\n </label>\n );\n })}\n </RadioGroup>\n </>\n );\n}\n\n/**\n * Lets the user pick the model for the task. `variant=\"dropdown\"` (default) is\n * a composer-anchored listbox (arrow keys, Home/End, Escape returns focus);\n * `\"tradeoff\"` lays models out as radio cards with speed, quality and cost in\n * human terms; `\"auto-route\"` defaults to letting the agent route per task,\n * shows which model it is using, and reveals the picker when switched off.\n * Recommended and unavailable models are marked in words; an unavailable model\n * keeps its row and says what answers instead. With `trigger=\"chip\"` the\n * dropdown's trigger is the composer's model chip (\"Sonnet ▾\"), so the whole\n * component goes in `OpenInput`'s `model` slot (`menuSide=\"top\"` there).\n * Source: Figma \"Context Expansion / Model Selection\".\n */\nexport const ModelSelection = React.forwardRef<HTMLDivElement, ModelSelectionProps>(\n (\n {\n models,\n value,\n defaultValue,\n onValueChange,\n label = \"Model\",\n variant = \"dropdown\",\n autoRoute,\n defaultAutoRoute = true,\n onAutoRouteChange,\n routedModel,\n agentName = \"Aria\",\n trigger = \"button\",\n triggerDetail,\n menuSide = \"bottom\",\n picker = \"dropdown\",\n className,\n ...props\n },\n ref,\n ) => {\n const [internal, setInternal] = React.useState<string | undefined>(defaultValue ?? models[0]?.value);\n const current = value !== undefined ? value : internal;\n const select = (next: string) => {\n if (value === undefined) setInternal(next);\n onValueChange?.(next);\n };\n\n const [autoInternal, setAutoInternal] = React.useState(defaultAutoRoute);\n const auto = autoRoute !== undefined ? autoRoute : autoInternal;\n const setAuto = (on: boolean) => {\n if (autoRoute === undefined) setAutoInternal(on);\n onAutoRouteChange?.(on);\n };\n const autoLabelId = React.useId();\n const headingId = React.useId();\n\n const list = (\n <ModelList models={models} current={current} select={select} label={label} headingId={headingId} />\n );\n const dropdown = (\n <ModelDropdown\n models={models}\n current={current}\n select={select}\n label={label}\n trigger={trigger}\n triggerDetail={triggerDetail}\n menuSide={menuSide}\n />\n );\n\n if (variant === \"tradeoff\") {\n return (\n <div ref={ref} className={cn(\"flex flex-col gap-2\", className)} {...props}>\n {list}\n </div>\n );\n }\n\n if (variant === \"auto-route\") {\n return (\n <div ref={ref} className={cn(\"flex flex-col gap-2.5\", className)} {...props}>\n <div className=\"flex items-center justify-between gap-3 rounded-lg border border-border bg-background px-3.5 py-2.5\">\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <span id={autoLabelId} className=\"text-sm font-semibold text-foreground\">\n Auto-route\n </span>\n <span className=\"text-xs text-muted-foreground\">\n {agentName} picks the model per task\n {auto && routedModel && (\n <>\n {\" · \"}\n <span aria-live=\"polite\">currently {routedModel}</span>\n </>\n )}\n </span>\n </div>\n <Switch checked={auto} onCheckedChange={setAuto} aria-labelledby={autoLabelId} className=\"shrink-0\" />\n </div>\n {picker === \"list\" ? (\n <ModelList\n models={models}\n current={current}\n select={(next) => {\n select(next);\n if (auto) setAuto(false);\n }}\n label={label}\n headingId={headingId}\n />\n ) : (\n !auto && dropdown\n )}\n </div>\n );\n }\n\n return (\n <div ref={ref} className={cn(\"relative inline-block\", className)} {...props}>\n {dropdown}\n </div>\n );\n },\n);\nModelSelection.displayName = \"ModelSelection\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nucleux/model-selection",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Model Selection — choose which model handles the request — Nucleux agentic UI for React.",
@@ -33,11 +33,12 @@
33
33
  "react-dom": ">=18"
34
34
  },
35
35
  "dependencies": {
36
- "@nucleux/utils": "0.9.0",
37
- "@nucleux/badge": "0.9.0",
38
- "@nucleux/radio-group": "0.9.0",
39
- "@nucleux/switch": "0.9.0",
40
- "lucide-react": "^0.469.0"
36
+ "@nucleux/utils": "0.10.0",
37
+ "@nucleux/badge": "0.10.0",
38
+ "@nucleux/radio-group": "0.10.0",
39
+ "@nucleux/switch": "0.10.0",
40
+ "lucide-react": "^0.469.0",
41
+ "@nucleux/open-input": "0.10.0"
41
42
  },
42
43
  "devDependencies": {
43
44
  "tsup": "^8.3.5",