表单
ToggleGroup - 分段控件
分段控件:若干选项,一条。
用法
什么时候用它
import { ToggleGroup } from '@misoto22/folio'说明
一个分段控件:若干选项,一条。
type="single" 是一次选择——Radix 给它单选语义,视图切换器或密度设置就该是这个形状。type="multiple" 是一组互相独立的开关,那是另一回事,播报方式也不一样;选错了,一个“按标签筛选”的控件就会告诉读屏软件:选中一个标签会取消其他标签。
这两者刻意长得不一样。单值的一条会把一个填充药丸在选项之间移动,眼睛跟着一个在走的东西;多值的一条则各自填充每个按下的选项,因为没有一个“唯一选中”可以移动。两个人看同一张截图,应该能分辨出自己看的是哪一种,而在这之前他们分辨不出来。
和 Tabs 不同,那个切换的是面板,并且拥有 tabpanel 关系。切换组改变的是一个值。
放在 Field 里时,这一条的名字取自那个标签,做法是反向指回它——根节点是一个 div,而 <label for> 绑不到它上面,所以上方那些文字点不进去。单独使用时,它仍然需要自己的 aria-label。
结构
| 部件 | 说明 |
|---|---|
| 条必填 | 装着各段的那个带边框的胶囊,除了 inline-flex 还带 w-fit——没有它,flex 或 grid 的父容器会把这一条拉伸到最宽的兄弟元素那么宽,最后一段之后留下一块死区。 |
| 滑动色块 | 选中项背后那块 --accent,只在单值的组里有,而且要等它量到一个被选中的段之后才有。它是移动,不是交叉淡入淡出,所以眼睛跟的始终是同一个东西。 |
| 分段必填 | 一个 --control-h-sm 高的按钮。在单值的组里,它只改字色,填充交给背后那块色块;在多值的组里,它自己填充,因为没有任何东西在滑。 |
| 分段内容 | children,排成一行 gap-2。这里没有 Button 那样的 iconOnly 通道,所以一个只有图标、没有文字的段,就是一个没有名字的段。 |
实践建议
推荐
- 单值的组要给一个 defaultValue 或 value。色块要等量到一个被选中的段之后才出现,所以一个初始为空的组,就是一条什么都没标记的空条。
- 给这一条一个名字。放在 Field 里时由它的 label 通过 aria-labelledby 来给——根节点是 div,htmlFor 没有任何东西可绑——单独站着时它需要自己的 aria-label,而在 type="single" 下,radiogroup 正是靠这个被播报出来的。
- 只有图标的段要给它自己的 aria-label:这里没有任何东西会替你去掉文字或补上名字,所以一条由三个图形组成的条,播报出来是三个没有名字的按钮。
避免
- type="single" 有 radio 的语义,却没有 radio 的行为:按一下已经选中的那一段会取消选中并提交一个空字符串,所以拿它做的视图切换器,可以被切到没有任何视图的状态。
- 每一段都是 --control-h-sm——舒适密度下 36px,data-density="compact" 下 30px——都低于 44px 的指针目标下限(WCAG 2.5.5)。要给拇指用的条,得自己另设高度。
- 不要往里放六个选项。这一条既不换行也不滚动,所以超过五段左右它就直接溢出容器了——何况那种东西本来就该是 Select 或 Combobox。
- Field 的 required 标得住单值的条,标不住多值的条:多值的那条是一个 role="toolbar",压根收不下 aria-required,所以在 type="multiple" 下,那个星号就是全部的标记。
示例
default
同一条分段控件的两种模式。single 带的是单选语义,会把一颗填充的药丸在选项之间挪来挪去,于是眼睛跟着一个移动的东西走;multiple 则各自填充每一段被按下的,因为这里没有一个「唯一选中项」可供移动。这个选择不是装饰——选错了,就等于在告诉读屏软件:选中一种格式会把其他的取消掉。一个 single 的组需要 defaultValue 或 value:那颗药丸只有在量到一个被选中的分段之后才会出现,所以一个空着开场的组,就是一条什么标记都没有的光秃秃的条。它既不换行也不滚动,所以最多到五段左右为止。
single — one of these
multiple — any of these
icon only
四个分段,只带图形、不带文字,用在文字只会把这一行已经说过的话再说一遍的工具栏里。这里没有 Button 那样的 iconOnly 路径——没有任何东西替你剥掉文字或补上一个名字——所以每一段都需要自己的 aria-label,而一条没有它们的分段控件,播报出来是四个没有名字的按钮。高度也要留神:分段是 --control-h-sm,宽松下 36px、紧凑下 30px,低于 44px 的指针下限,所以一条给拇指用的分段控件,得自己定一个高度。
never switched off
type="single" 有单选的语义,却没有单选的行为:按下已选中的那一段会把它取消,并提交一个空字符串,所以拿它搭出来的视图切换器,可以被切到「没有任何视图」上去。做成受控的,防线就是一行——忽略那个空字符串,这条分段控件就保住它原有的值。按一下已选中的那一段,看着它待在原地。
Showing the grid view.
组成部分
在调用处组合,而不是通过 props 配置——所以这个组件没有预料到的布局,依然表达得出来。
ToggleGroupItem
一段。
在单值组里,这一项自己不画背景——背后那个移动的药丸在画——所以它只改变墨色。在多值组里它自己填充,因为没有东西在移动。
同时接受 ComponentProps<typeof ToggleGroupPrimitive.Item> 里的全部属性,它们会直接透传给底层元素,不再逐条列出。
键盘操作
| 按键 | 作用 |
|---|---|
| Tab | 进入这一条——整组一个停靠点。 |
| ←→ | 在各段之间移动。 |
| EnterSpace | 切换当前聚焦的那一段。 |
无障碍
- type="single" 有 radio 语义,type="multiple" 是彼此独立的开关。选错会告诉读屏用户:选中一个就会取消另一个。