组件
折叠面板 Collapsible
按需展开一段补充内容,并保留清晰的状态与键盘交互。
基础示例
Loading…
pnpm dlx wui@latest add @wui/collapsible"use client"
import * as React from "react"
import { Collapsible as CollapsiblePrimitive } from "radix-ui"
import { ChevronDownIcon } from "lucide-react"
import { motion, useReducedMotion } from "motion/react"
import { cn } from "@/lib/utils"
const CollapsibleContext = React.createContext(false)
export interface CollapsibleProps
extends React.ComponentProps<typeof CollapsiblePrimitive.Root> {}
/** A disclosure region with controlled and uncontrolled open state. */
function Collapsible({
open,
defaultOpen = false,
onOpenChange,
...props
}: CollapsibleProps) {
const [internalOpen, setInternalOpen] = React.useState(defaultOpen)
const resolvedOpen = open ?? internalOpen
return (
<CollapsibleContext.Provider value={resolvedOpen}>
<CollapsiblePrimitive.Root
data-slot="collapsible"
open={resolvedOpen}
onOpenChange={(next) => {
if (open === undefined) setInternalOpen(next)
onOpenChange?.(next)
}}
{...props}
/>
</CollapsibleContext.Provider>
)
}
export interface CollapsibleTriggerProps
extends React.ComponentProps<typeof CollapsiblePrimitive.Trigger> {
/** Show the built-in rotating chevron after the label. @default true */
showIndicator?: boolean
}
function CollapsibleTrigger({
className,
children,
showIndicator = true,
asChild = false,
...props
}: CollapsibleTriggerProps) {
return (
<CollapsiblePrimitive.Trigger
data-slot="collapsible-trigger"
asChild={asChild}
className={cn(
"group flex w-full items-center gap-3 rounded-md px-3 py-2 text-left text-sm font-medium outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/35 disabled:pointer-events-none disabled:opacity-50",
className
)}
{...props}
>
{asChild ? (
children
) : (
<>
<span className="min-w-0 flex-1">{children}</span>
{showIndicator ? (
<ChevronDownIcon
aria-hidden
data-slot="collapsible-indicator"
className="size-4 shrink-0 text-muted-foreground transition-transform duration-200 group-data-[state=open]:rotate-180 motion-reduce:transition-none"
/>
) : null}
</>
)}
</CollapsiblePrimitive.Trigger>
)
}
function CollapsibleContent({
className,
children,
...props
}: Omit<
React.ComponentProps<typeof CollapsiblePrimitive.Content>,
"asChild" | "forceMount"
>) {
const open = React.useContext(CollapsibleContext)
const reduceMotion = useReducedMotion()
return (
<CollapsiblePrimitive.Content forceMount asChild {...props}>
<motion.div
data-slot="collapsible-content"
aria-hidden={!open}
inert={!open}
className={cn("overflow-hidden", className)}
initial={false}
animate={{ height: open ? "auto" : 0, opacity: open ? 1 : 0 }}
transition={
reduceMotion
? { duration: 0 }
: { duration: 0.24, ease: [0.22, 1, 0.36, 1] }
}
>
{children}
</motion.div>
</CollapsiblePrimitive.Content>
)
}
export { Collapsible, CollapsibleContent, CollapsibleTrigger }
组件作用
Collapsible 用于隐藏暂时不需要的补充信息,例如高级设置、详情和次级导航。触发器始终可见,内容展开时使用短促的高度与透明度过渡;用户偏好减少动态效果时会直接切换状态。不要用它隐藏完成当前任务所必需的信息。
组件属性
| Prop | Type | Default | Description |
|---|---|---|---|
| disabled | boolean | — | |
| asChild | boolean | — |
Collapsible透传 Radix Root 的open、defaultOpen、disabled与onOpenChange。CollapsibleTrigger支持asChild,并可通过showIndicator={false}隐藏默认箭头。CollapsibleContent负责展开动效,收起时会从键盘导航和辅助技术中移除内部内容。
事件
onOpenChange(open):用户点击触发器或受控状态变化时触发。- 触发器使用原生按钮语义,支持 Enter 与 Space,并同步
aria-expanded和内容关联关系。
拓展使用
受控状态
Loading…
当页面其他区域需要读取或改变展开状态时,传入 open 与 onOpenChange。如果折叠内容彼此互斥,应使用 Accordion,而不是手动同步多个 Collapsible。