跳到正文
册页

导航

Accordion - 手风琴

就地展开的折叠行。

用法

什么时候用它

标记是加号不是尖角:加号说“这会展开”,尖角说“下面还有”。
TSX
import { Accordion } from '@misoto22/folio'

说明

Radix 的根节点,原样再导出。FAQ 用 type="single" collapsible,设置项堆叠用 type="multiple";Radix 自己的可辨识联合类型会据此把 value 的类型定对。

结构

Accordion anatomy
部件说明
行必填AccordionItem——一条由发丝线分隔的记录,键就是 Radix 用来开合它的那个 value。集合是它上面那个根节点:常见问题用 type="single" 配 collapsible,一叠设置项用 type="multiple"。
标题必填Radix 的 Accordion.Header,它是一个 <h3>,而且不收 level 属性。所以无论这个手风琴恰好放在哪儿,每一行都往文档大纲里加一个 h3。
触发器必填那个标题里面的满宽按钮:title 靠着起始边,记号靠着末端边,py-4。展开的那个面板,名字也是由它给的。
记号必填一个 16px 的加号,aria-hidden,行展开时旋转 45° 变成减号。它画出来的这个状态,对其他所有人是由触发器上的 aria-expanded 承担的。
面板Radix 的 Content——一个由它的触发器命名的 role="region",只在展开时挂载,overflow-hidden 好让量出来的高度能做动画,里面 pb-4 pe-8,这样文字停在记号那一列之前。它带着 data-folio-animated,所以对要求少一点动效的读者,展开和收起是被整个去掉的。

实践建议

推荐

  • type="single" 要连着 collapsible 一起传:不传,就没有一个空值可以回去,于是读者展开的第一行,是一行他再也关不上的行。
  • 每一项的键要用稳定的东西,而不是它的位置——Radix 是按 value 记住哪一行开着的,所以列表一重排或者一过滤,现在坐在那个位置上的不管是什么,都会开着。
  • title 要写成完整的那个问题:它既是触发器的无障碍名称,也是面板的——所以一行标题写「More」,展开的就是一个叫「More」的区域。
  • 两行需要互相对着读时,用 type="multiple"——single 会为了打开读者想拿来对比的那一行,把他正按着的那一行关掉。

避免

  • 收起来那一行的内容不在 DOM 里,所以用它搭出来的常见问题页,对页内查找是看不见的,打印出来就是一列问题——任何必须能搜、能打印的东西,都该直接放在页面上。
  • 触发器的层级被 Radix 的 header 钉死在 h3,所以放在一个 <h3> 底下的手风琴,它的行是作为那个标题的同级列出来的,大纲恰恰在本该嵌套的地方压平了。
  • 面板是 overflow-hidden 的——展开的高度能做动画正是靠这个——所以里面任何必须跑出这一行盒子的东西,都得 portal 出去;就地渲染的菜单会被切在行的边上。

示例

default

一份 FAQ:type 用 single 配 collapsible,好让读者打开的那一行还能再关上。没有 collapsible,就没有一个「空」值可以退回去,于是他们打开的第一行,是一行永远关不掉的行。那个标记是加号而不是人字形,这是刻意的——加号说的是「这会展开」,人字形说的是「下面还有」,而在一摞行里,这决定了读者预期的是展开还是导航。给每一行用稳定的东西做 key,别用位置:Radix 是按 value 追踪那个打开项的,所以过滤或重排列表之后,现在坐在那个槽位上的无论是谁,都会站在那儿开着。

a settings stack

type 用 multiple,因为这两行必须相互对照着读——single 会为了打开读者想拿来比较的那一行,把他正握着的那一行关掉。defaultValue 打开他此行的来意所在的那一组。面板是 overflow-hidden 的,正是这一点让它量出来的高度可以做动画,所以里面任何必须逃出这一行盒子的东西——一个 select 的菜单、一个 popover——都必须 portal 出去,否则会在行的边缘被切掉。

How many times a failed job is tried again.

the whole question

title 既是触发器的无障碍名称,也是面板的,所以一行叫「更多」的行,打开的是一个叫「更多」的区域——而一个在区域之间移动的读者,拿到的是一份什么都没命名的清单。把整个问题写出来。右边那些行在被打开之前就说清了背后是什么,而这也正是那一摞收起来的行仍然可读的原因:一列只有一个词的触发器,是一本没有章节名的书的目录。

Opens a region called More

Opens a region called by its question

组成部分

在调用处组合,而不是通过 props 配置——所以这个组件没有预料到的布局,依然表达得出来。

AccordionItem

一个展开行:一条细线分隔的触发器和它的面板。

标记是加号转成减号,不是尖角。尖角说“下面还有”,加号说“这个能展开”——在一叠行里,这个差别决定了读者预期的是跳转还是展开。

面板用 Radix 自己的 --radix-accordion-content-height 做动画,所以它能展开到真实高度,调用处不用去测量任何东西。

AccordionItem props
属性类型默认值说明
children必填ReactNode—
title必填ReactNode这一行的摘要——读者点的就是它。叫 `title` 而不是沿用同名的那个 DOM 属性,那个是气泡提示。

同时接受 Omit<ComponentProps<typeof AccordionPrimitive.Item>, 'children' | 'title'> 里的全部属性,它们会直接透传给底层元素,不再逐条列出。

再导出

Accordion = AccordionPrimitive.Root

Radix 的根节点,原样再导出。FAQ 用 type="single" collapsible,设置项堆叠用 type="multiple";Radix 自己的可辨识联合类型会据此把 value 的类型定对。

键盘操作

Accordion keyboard interactions
按键作用
Tab在各行之间移动。
EnterSpace展开或收起当前聚焦的那一行。