mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-11 09:51:40 +00:00
* feat: init * fix * fix * fix * feat * feat * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * feat: implement icons * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * feat: update init command * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * feat: dialog * feat * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * feat: add registry:base item type * feat: rename frame to canva * fix * feat * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fi * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * feat: add all colors * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * feat: add outfit font * fix * fix * fix * fix * fix * chore: changeset * fix * fix * fix * fix * fix * fix * fix * fix
163 lines
5.1 KiB
TypeScript
163 lines
5.1 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import { type PopoverProps } from "@radix-ui/react-popover"
|
|
import { Check, ChevronsUpDown } from "lucide-react"
|
|
|
|
import { cn } from "@/lib/utils"
|
|
import { useMutationObserver } from "@/hooks/use-mutation-observer"
|
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
import {
|
|
Command,
|
|
CommandEmpty,
|
|
CommandGroup,
|
|
CommandInput,
|
|
CommandItem,
|
|
CommandList,
|
|
} from "@/registry/new-york-v4/ui/command"
|
|
import {
|
|
HoverCard,
|
|
HoverCardContent,
|
|
HoverCardTrigger,
|
|
} from "@/registry/new-york-v4/ui/hover-card"
|
|
import { Label } from "@/registry/new-york-v4/ui/label"
|
|
import {
|
|
Popover,
|
|
PopoverContent,
|
|
PopoverTrigger,
|
|
} from "@/registry/new-york-v4/ui/popover"
|
|
|
|
import { type Model, type ModelType } from "../data/models"
|
|
|
|
interface ModelSelectorProps extends PopoverProps {
|
|
types: readonly ModelType[]
|
|
models: Model[]
|
|
}
|
|
|
|
export function ModelSelector({ models, types, ...props }: ModelSelectorProps) {
|
|
const [open, setOpen] = React.useState(false)
|
|
const [selectedModel, setSelectedModel] = React.useState<Model>(models[0])
|
|
const [peekedModel, setPeekedModel] = React.useState<Model>(models[0])
|
|
|
|
return (
|
|
<div className="grid gap-3">
|
|
<HoverCard openDelay={200}>
|
|
<HoverCardTrigger asChild>
|
|
<Label htmlFor="model">Model</Label>
|
|
</HoverCardTrigger>
|
|
<HoverCardContent
|
|
align="start"
|
|
className="w-[260px] text-sm"
|
|
side="left"
|
|
>
|
|
The model which will generate the completion. Some models are suitable
|
|
for natural language tasks, others specialize in code. Learn more.
|
|
</HoverCardContent>
|
|
</HoverCard>
|
|
<Popover open={open} onOpenChange={setOpen} {...props}>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant="outline"
|
|
role="combobox"
|
|
aria-expanded={open}
|
|
aria-label="Select a model"
|
|
className="w-full justify-between"
|
|
>
|
|
{selectedModel ? selectedModel.name : "Select a model..."}
|
|
<ChevronsUpDown className="text-muted-foreground" />
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent align="end" className="w-[250px] p-0">
|
|
<HoverCard>
|
|
<HoverCardContent
|
|
side="left"
|
|
align="start"
|
|
forceMount
|
|
className="min-h-[280px]"
|
|
>
|
|
<div className="grid gap-2">
|
|
<h4 className="leading-none font-medium">{peekedModel.name}</h4>
|
|
<div className="text-muted-foreground text-sm">
|
|
{peekedModel.description}
|
|
</div>
|
|
{peekedModel.strengths ? (
|
|
<div className="mt-4 grid gap-2">
|
|
<h5 className="text-sm leading-none font-medium">
|
|
Strengths
|
|
</h5>
|
|
<ul className="text-muted-foreground text-sm">
|
|
{peekedModel.strengths}
|
|
</ul>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</HoverCardContent>
|
|
<Command loop>
|
|
<CommandList className="h-(--cmdk-list-height) max-h-[400px]">
|
|
<CommandInput placeholder="Search Models..." />
|
|
<CommandEmpty>No Models found.</CommandEmpty>
|
|
<HoverCardTrigger />
|
|
{types.map((type) => (
|
|
<CommandGroup key={type} heading={type}>
|
|
{models
|
|
.filter((model) => model.type === type)
|
|
.map((model) => (
|
|
<ModelItem
|
|
key={model.id}
|
|
model={model}
|
|
isSelected={selectedModel?.id === model.id}
|
|
onPeek={(model) => setPeekedModel(model)}
|
|
onSelect={() => {
|
|
setSelectedModel(model)
|
|
setOpen(false)
|
|
}}
|
|
/>
|
|
))}
|
|
</CommandGroup>
|
|
))}
|
|
</CommandList>
|
|
</Command>
|
|
</HoverCard>
|
|
</PopoverContent>
|
|
</Popover>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
interface ModelItemProps {
|
|
model: Model
|
|
isSelected: boolean
|
|
onSelect: () => void
|
|
onPeek: (model: Model) => void
|
|
}
|
|
|
|
function ModelItem({ model, isSelected, onSelect, onPeek }: ModelItemProps) {
|
|
const ref = React.useRef<HTMLDivElement>(null)
|
|
|
|
useMutationObserver(ref, (mutations) => {
|
|
mutations.forEach((mutation) => {
|
|
if (
|
|
mutation.type === "attributes" &&
|
|
mutation.attributeName === "aria-selected" &&
|
|
ref.current?.getAttribute("aria-selected") === "true"
|
|
) {
|
|
onPeek(model)
|
|
}
|
|
})
|
|
})
|
|
|
|
return (
|
|
<CommandItem
|
|
key={model.id}
|
|
onSelect={onSelect}
|
|
ref={ref}
|
|
className="data-[selected=true]:bg-primary data-[selected=true]:text-primary-foreground"
|
|
>
|
|
{model.name}
|
|
<Check
|
|
className={cn("ml-auto", isSelected ? "opacity-100" : "opacity-0")}
|
|
/>
|
|
</CommandItem>
|
|
)
|
|
}
|