导航
Collapsible - 折叠面板
一个自己开合的东西。
用法
什么时候用它
import { Collapsible } from '@misoto22/folio'说明
Radix Collapsible 的根节点,带类型的原样透传。
结构
| 部件 | 说明 |
|---|---|
| 根节点必填 | Collapsible——Radix 的根节点,拿着 open 或 defaultOpen,什么都不画。CollapsibleSection 是同一个根节点,只是触发器和面板已经组好了,多数调用处要的是它。 |
| 触发器必填 | 一个带着 aria-expanded 和 aria-controls 的普通 <button>——而且和手风琴的行不同,它外面根本没有包任何标题。这里没有任何东西会出现在文档大纲里。 |
| 记号必填 | 一个旋转 180° 的尖角,特意选来和手风琴的加号分开:这里展开的是同一件东西的更多部分,而手风琴的一行打开的是一个独立的答案。 |
| 面板 | Radix 的 Content,收起时卸载,按量出来的 --radix-collapsible-content-height 做动画,所以一组很长的和一组很短的用时一样。它是一个光秃秃的 div:没有 region role,也没有自己的名字,这一点同样和手风琴不同。它和散着用的 CollapsibleContent 都带着 data-folio-animated,所以在 prefers-reduced-motion 下两者是一致的,而不是只有其中一个遵守。 |
实践建议
推荐
- 这一段真的是一个段落时,自己给触发器套一个标题:Accordion 把每个触发器都包进 <h3>,而这里是刻意一个都不包——所以一个由 CollapsibleSection 搭起来的页面,按标题导航时没有任何东西可以停。
- 只有当那行头部要装的不只是一个标题时——一边一个计数、另一边一个开关——才去用散着的 CollapsibleTrigger 和 CollapsibleContent。它们存在,是为了让那样的调用处不必跑去 Radix 手工重推一遍 aria-expanded。
- 它藏起来的东西正是读者来的目的时,设 defaultOpen:收起的面板是被卸载而不是被隐藏,所以它的文字不在页面上——页内查找、打印,以及任何读取渲染后 DOM 的东西,都拿不到。
- 需要由外面的东西来把它打开时,用 open 和 onOpenChange 受控——一个必须为住在它里面的那条路由展开的侧边栏分组,是没法从一个自己管着自己状态的组件那里被通知到的。
避免
- 别用它们拼一个集合:两个段落没法互相关闭,读者最后拿到的是全部展开、外加一长列要滚过去的内容。那份协同,正是 Accordion 那一个 value 买来的全部。
- 别让标题在「Show more」和「Show less」之间来回翻:触发器上的 aria-expanded 已经带着这个状态了,于是这一行的状态被播报两遍,而且每按一次名字就换一个。
示例
default
一样东西,自己会打开。CollapsibleSection 是那个组装好的根——触发器、标记和面板都已经接好——也是大多数调用处真正想要的。那个标记是人字形而不是手风琴的加号,这是刻意的:这里揭开的是同一样东西的更多部分,而一个手风琴的行打开的是一个各自独立的答案。这个触发器完全没有被包进任何标题里,和手风琴的不一样,所以拿它们搭出来的页面,会让标题导航无处可停——当这一节确实是一节时,自己加一个标题。另外,别让 title 在「显示更多」和「显示更少」之间来回切:aria-expanded 已经带着状态了,那样这一行会被把状态播报两遍,而且每按一次名字都变。
a custom header
松散的触发器和面板,用于一个必须装下不止一个标题的页头——一边一个计数,另一边是那个控件。它们存在,是为了让这个调用处不必直接去找 Radix、再手工把 aria-expanded 和 aria-controls 推导一遍。离开那个组装好的 section,有两件事会变:触发器是一个图标按钮,所以它需要一个自己的无障碍名称;而面板是一个光秃秃的 div,既没有 region 角色,也没有名字。这里设了 defaultOpen,因为它藏起来的东西正是读者来的理由——一块关着的面板是被卸载的,不是被隐藏的,所以它的文字不在页面里,页内查找和打印都够不着。
- typecheck — 2 errors in apps/docs/src/lib/docs.ts
- lint — unused import in Toolbar.tsx
- visual diff — 1 changed screenshot
sidebar group
做成受控的,因为必须有外面的东西能把它打开:装着当前路由的那一组,在读者到达时就得已经展开,而一个自己持有状态的组件,是没法被这么告知的。别拿它们搭出一整套——两个 section 之间没法互相关闭,所以读者最后会面对每一组都开着、需要一路滚过去的一列。那份协同正是手风琴的单一 value 买到的全部,也正是一旦有了三组就该改用它的理由。
组成部分
在调用处组合,而不是通过 props 配置——所以这个组件没有预料到的布局,依然表达得出来。
CollapsibleContent
这个组件没有自己的属性。
CollapsibleSection
一个自己开合的东西。
和 Accordion 的差别是算术上的:手风琴是一个集合,集合才能协同——展开一个就收起另一个。可折叠块只有一处展开,没有可协同的对象。为了一项去用手风琴,你得到的是一个在管理你从不读取的值的组件。
这里的标记是尖角而不是手风琴的加号,也是刻意的:这里展开的是同一件事的更多内容,而手风琴的一行打开的是另一个独立的答案。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| children必填 | ReactNode | — | |
| title必填 | ReactNode | 触发器上写什么。 |
同时接受 Omit<ComponentProps<typeof CollapsiblePrimitive.Root>, 'children' | 'title'> 里的全部属性,它们会直接透传给底层元素,不再逐条列出。
再导出
Collapsible = CollapsiblePrimitive.RootRadix Collapsible 的根节点,带类型的原样透传。
CollapsibleTrigger = CollapsiblePrimitive.Trigger触发器和面板,不带样式,留给需要自己排版的展开项——比如侧边栏里一个左边是尖角、右边是计数的分组标题。CollapsibleSection 是组合好的那个版本,绝大多数调用处要的是它;这两个存在,是为了让少数不合适的场景不必直接去碰 Radix、再把键盘和 aria-expanded 那套接线重写一遍。
面板按 --radix-collapsible-content-height 做动画,这个高度是 Radix 量出来的——所以它展开到的是真实高度,而不是一个猜出来的 max-height,这正是长分组和短分组耗时一致、而不是长的那个看起来卡住的原因。
键盘操作
| 按键 | 作用 |
|---|---|
| EnterSpace | 展开或收起。 |