组件
下拉框 Select
带景深入场、分组与键盘导航的选项选择器。
基础示例
Loading…
pnpm dlx wui@latest add @wui/select"use client"
import * as React from "react"
import { Select as SelectPrimitive } from "radix-ui"
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
import { motion, useReducedMotion } from "motion/react"
import { cva } from "class-variance-authority"
import { cn } from "@/lib/utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const selectTriggerVariants = cva(
"group flex w-fit items-center justify-between gap-3 rounded-md border border-input bg-background text-sm shadow-xs outline-none transition-[border-color,box-shadow,background-color] duration-200 ease-out hover:bg-accent/50 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/30 disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0",
{
variants: {
size: {
sm: "h-8 min-w-32 px-3",
default: "h-10 min-w-44 px-3.5",
lg: "h-12 min-w-52 px-4 text-base",
},
},
defaultVariants: { size: "default" },
}
)
export interface SelectTriggerProps
extends React.ComponentProps<typeof SelectPrimitive.Trigger> {
/** Height and minimum-width preset. @default "default" */
size?: "sm" | "default" | "lg"
}
function SelectTrigger({ className, size = "default", children, ...props }: SelectTriggerProps) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(selectTriggerVariants({ size }), className)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="size-4 text-muted-foreground transition-transform duration-300 group-data-[state=open]:rotate-180" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
}
function SelectContent({
className,
children,
position = "popper",
sideOffset = 6,
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
const reduceMotion = useReducedMotion()
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
position={position}
sideOffset={sideOffset}
className={cn(
"relative z-50 max-h-[var(--radix-select-content-available-height)] min-w-[10rem] overflow-hidden rounded-lg border bg-popover text-popover-foreground shadow-md outline-none",
position === "popper" && "w-[var(--radix-select-trigger-width)]",
className
)}
{...props}
>
<motion.div
initial={reduceMotion ? false : { opacity: 0, y: -4 }}
animate={{ opacity: 1, y: 0 }}
transition={
reduceMotion
? { duration: 0 }
: { type: "spring", stiffness: 500, damping: 36, mass: 0.7 }
}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport className="p-1">
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</motion.div>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
)
}
function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn("px-2.5 py-1.5 text-xs font-semibold tracking-wide text-muted-foreground", className)}
{...props}
/>
)
}
function SelectItem({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Item>) {
const reduceMotion = useReducedMotion()
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-md py-2 pl-8 pr-3 text-sm outline-none transition-colors duration-150 focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-40",
className
)}
{...props}
>
<span className="absolute left-2 flex size-5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<motion.span
initial={reduceMotion ? false : { scale: 0.5, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
>
<CheckIcon className="size-4" />
</motion.span>
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
)
}
function SelectSeparator({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return <SelectPrimitive.Separator data-slot="select-separator" className={cn("-mx-1 my-1 h-px bg-border", className)} {...props} />
}
function SelectScrollUpButton({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton data-slot="select-scroll-up-button" className={cn("flex h-7 items-center justify-center text-muted-foreground", className)} {...props}>
<ChevronUpIcon className="size-4" />
</SelectPrimitive.ScrollUpButton>
)
}
function SelectScrollDownButton({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton data-slot="select-scroll-down-button" className={cn("flex h-7 items-center justify-center text-muted-foreground", className)} {...props}>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.ScrollDownButton>
)
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
selectTriggerVariants,
}
组件作用
Select 从一组互斥选项中选择一个值。浮层使用克制的淡入与短距离弹簧位移建立层级,不额外堆叠玻璃材质或重阴影;搜索型或允许自由输入的场景应使用 Combobox,而不是 Select。
组件属性
核心部件包括 Select、SelectTrigger、SelectValue、SelectContent、SelectItem、SelectGroup 与 SelectLabel。Trigger 额外提供 size="sm | default | lg"。
| Prop | Type | Default | Description |
|---|---|---|---|
| disabled | boolean | — |
事件
onValueChange(value):选中项变化后触发。onOpenChange(open):浮层打开或关闭时触发。SelectItem支持 Radix 提供的选择、焦点与键盘事件。
拓展使用
分组与分隔线
Loading…
通过 disabled 禁用整个 Select,或在单个 SelectItem 上禁用特定选项。