Componenteditors1.21 KB gzipZero dependencies
Model Picker
Compact model selector with meta readout.
Preview
Live Preview
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add model-pickerOr install the full npm package:
npm install bigbulluiUsage
Open in StackBlitzimport { ModelPicker } from "@/components/ui/model-picker";
<ModelPicker />Usage guidance
Use Model Picker when a ticket stub surface needs Compact model selector with meta readout. Keep props minimal and prefer composition with sibling stubs.
Avoid Model Picker for long form flows or dense data grids where a dedicated layout block fits better.
Source code
model-picker.tsxZero dependencies (only React + utils). Copy and paste directly into your project:
"use client";
import * as React from "react";
import { cn } from "./lib/utils";
export interface ModelOption {
id: string;
name: string;
meta: string;
}
export interface ModelPickerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
models?: ModelOption[];
value?: string;
onChange?: (id: string) => void;
}
/** Compact model selector with popover list and mono meta readout. */
export function ModelPicker({
models = [
{ id: "stub-1", name: "Stub One", meta: "FAST · 4K" },
{ id: "stub-pro", name: "Stub Pro", meta: "SMART · 32K" },
{ id: "stub-ultra", name: "Stub Ultra", meta: "MAX · 128K" },
],
value,
onChange,
className,
...props
}: ModelPickerProps) {
const [open, setOpen] = React.useState(false);
const [inner, setInner] = React.useState(models[0]?.id ?? "");
const selected = models.find((m) => m.id === (value ?? inner));
const pick = (id: string) => {
setInner(id);
onChange?.(id);
setOpen(false);
};
return (
<div className={cn("relative inline-block", className)} {...props}>
<button
type="button"
onClick={() => setOpen((v) => !v)}
aria-haspopup="listbox"
aria-expanded={open}
className="flex cursor-pointer items-center gap-2 rounded border-2 border-foreground bg-card px-3 py-1.5 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
<span className="inline-flex size-6 items-center justify-center rounded border border-accent/50 bg-accent/10 font-mono text-[10px] font-bold text-accent" aria-hidden="true">
AI
</span>
<span>
<span className="block text-xs font-bold text-foreground">{selected?.name ?? "Select model"}</span>
<span className="block font-mono text-[10px] uppercase tracking-wider text-muted-foreground">
{selected?.meta ?? "—"}
</span>
</span>
<span aria-hidden="true" className={cn("ml-1 font-mono text-xs text-muted-foreground transition-transform motion-reduce:transition-none", open && "rotate-180")}>
▾
</span>
</button>
{open && (
<ul
role="listbox"
aria-label="Models"
className="absolute z-20 mt-1.5 w-56 rounded-lg border-2 border-foreground bg-card p-1.5 shadow-lg"
>
{models.map((m) => {
const active = m.id === selected?.id;
return (
<li key={m.id}>
<button
type="button"
role="option"
aria-selected={active}
onClick={() => pick(m.id)}
className={cn(
"flex w-full cursor-pointer items-center justify-between rounded px-2.5 py-2 text-left hover:bg-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
active && "bg-secondary",
)}
>
<span className="text-xs font-bold text-foreground">{m.name}</span>
<span className="font-mono text-[10px] uppercase text-muted-foreground">{m.meta}</span>
</button>
</li>
);
})}
</ul>
)}
</div>
);
}