'use client'
import { useState } from 'react'
import { SegmentedControl } from '@hina-ui/react'
const options = [
{ value: 'all', label: '全部' },
{ value: 'ongoing', label: '连载中' },
{ value: 'done', label: '已完结' },
]
export default function Demo() {
const [status, setStatus] = useState<string | number>('all')
return (
<SegmentedControl
value={status}
onValueChange={setStatus}
options={options}
aria-label="连载状态"
/>
)
}
tsx
用法
import { SegmentedControl } from '@hina-ui/react'
ts
分段控制器把几个并列的选项排成一行,任何时刻恰有一项被选中,选中项由一块滑块标示。options 的类型见 Select,value / onValueChange 绑定选中项的值;未绑定值时默认选中第一个可用项。未声明的属性都会传给根元素,请用 aria-label 或者 aria-labelledby 为整组命名。
当前排序:latest
'use client'
import { useState } from 'react'
import { SegmentedControl, Stack, Text } from '@hina-ui/react'
const options = [
{ value: 'latest', label: '最新' },
{ value: 'popular', label: '最热' },
{ value: 'rating', label: '评分' },
]
export default function Demo() {
const [sort, setSort] = useState<string | number>('latest')
return (
<Stack gap="sm" align="start">
<SegmentedControl
value={sort}
onValueChange={setSort}
options={options}
aria-label="排序方式"
/>
<Text tone="muted" size="sm">
当前排序:{sort}
</Text>
</Stack>
)
}
tsx
它与 RadioGroup 表达同一种选择,区别在于场合:选项不多于五个、文字简短、切换立即生效时用分段控制器;选项需要说明文字,或者选择需要提交时用单选框组。与 Tabs 的区别是它改变的是一个值,不是切换显示的内容。
示例
自定义内容
renderOption 属性替换每一项的内容。只放图标时,项的名称仍取 label,读屏软件照常读出。
组件从 options 推断完整选项类型,渲染函数中的 option 保留额外字段及其类型;value / onValueChange 仍绑定 value。类型定义见 Select。
'use client'
import { useState } from 'react'
import { LayoutGrid, List, Rows3 } from 'lucide-react'
import { SegmentedControl } from '@hina-ui/react'
const icons = { grid: LayoutGrid, list: List, rows: Rows3 }
const options = [
{ value: 'grid', label: '网格' },
{ value: 'list', label: '列表' },
{ value: 'rows', label: '详情' },
]
export default function Demo() {
const [view, setView] = useState<string | number>('grid')
return (
<SegmentedControl
value={view}
onValueChange={setView}
options={options}
aria-label="视图"
renderOption={({ option }) => {
const Icon = icons[option.value as keyof typeof icons]
return <Icon />
}}
/>
)
}
tsx
尺寸
size 有 sm、md、lg 三档,整体高度与同档的输入框相等,可以与输入框、按钮排在同一行。
import { SegmentedControl, Stack } from '@hina-ui/react'
const options = [
{ value: 'day', label: '日' },
{ value: 'week', label: '周' },
{ value: 'month', label: '月' },
]
export default function Demo() {
return (
<Stack gap="sm" align="start">
<SegmentedControl size="sm" options={options} value="day" aria-label="小号" />
<SegmentedControl size="md" options={options} value="day" aria-label="中号" />
<SegmentedControl size="lg" options={options} value="day" aria-label="大号" />
</Stack>
)
}
tsx
撑满
block 让控件占满父元素的宽度,各项等宽。
'use client'
import { useState } from 'react'
import { SegmentedControl } from '@hina-ui/react'
const options = [
{ value: 'day', label: '今日' },
{ value: 'week', label: '本周' },
{ value: 'month', label: '本月' },
{ value: 'all', label: '全部' },
]
export default function Demo() {
const [range, setRange] = useState<string | number>('week')
return (
<SegmentedControl
value={range}
onValueChange={setRange}
options={options}
block
aria-label="统计范围"
className="max-w-md"
/>
)
}
tsx
竖排
orientation="vertical" 把各项竖向排列,滑块随之上下移动。
'use client'
import { useState } from 'react'
import { SegmentedControl } from '@hina-ui/react'
const options = [
{ value: 'left', label: '左对齐' },
{ value: 'center', label: '居中' },
{ value: 'right', label: '右对齐' },
]
export default function Demo() {
const [align, setAlign] = useState<string | number>('left')
return (
<SegmentedControl
value={align}
onValueChange={setAlign}
options={options}
orientation="vertical"
aria-label="对齐"
/>
)
}
tsx
状态
disabled 禁用整组;单项的 disabled 只禁用那一项,键盘导航会跳过它。
import { SegmentedControl, Stack } from '@hina-ui/react'
const options = [
{ value: 'all', label: '全部' },
{ value: 'ongoing', label: '连载中' },
{ value: 'done', label: '已完结' },
]
const partial = [
{ value: 'all', label: '全部' },
{ value: 'ongoing', label: '连载中' },
{ value: 'done', label: '已完结', disabled: true },
]
export default function Demo() {
return (
<Stack gap="sm" align="start">
<SegmentedControl options={options} value="all" disabled aria-label="整组禁用" />
<SegmentedControl options={partial} value="all" aria-label="单项禁用" />
</Stack>
)
}
tsx
在表单中
放进 FormField 后,标签通过 aria-labelledby 关联到整组;校验规则与提交交给 Form。分段控制器总有一个值,它常常决定其他字段是否必填,这类规则写在对象层,再指定错误落在哪个字段。
'use client'
import { useState } from 'react'
import * as v from 'valibot'
import { Button, DateField, Form, FormField, SegmentedControl, Text } from '@hina-ui/react'
const modes = [
{ label: '立即发布', value: 'now' },
{ label: '定时发布', value: 'scheduled' },
]
const schema = v.pipe(
v.object({
mode: v.picklist(['now', 'scheduled'], '请选择发布方式'),
publishAt: v.nullable(v.string()),
}),
v.forward(
v.check(input => input.mode !== 'scheduled' || !!input.publishAt, '请选择发布日期'),
['publishAt'],
),
)
export default function Demo() {
const [values, setValues] = useState({
mode: 'now' as string | number,
publishAt: null as string | null,
})
const [saved, setSaved] = useState('')
async function save(data: unknown) {
await new Promise(resolve => setTimeout(resolve, 600))
setSaved(JSON.stringify(data))
}
return (
<Form values={values} rules={schema} className="w-80" onSubmit={save}>
{({ submitting }) => (
<>
<FormField name="mode" label="发布方式">
<SegmentedControl
value={values.mode}
onValueChange={mode => setValues({ ...values, mode })}
options={modes}
/>
</FormField>
<FormField name="publishAt" label="发布日期" disabled={values.mode !== 'scheduled'}>
<DateField
value={values.publishAt}
onValueChange={publishAt => setValues({ ...values, publishAt })}
/>
</FormField>
<Button type="submit" loading={submitting} className="self-start">
发布
</Button>
{saved && (
<Text tone="muted" size="sm">
已发布:{saved}
</Text>
)}
</>
)}
</Form>
)
}
tsx
行为
- 点击某项即选中,滑块平移到该项;再点已选项不会取消选择。
- 键盘 Tab 落在已选项上,方向键在各项之间移动焦点,空格或者 Enter 选中当前项,到达两端后回绕。
- 悬停与按下的墨落在项上,已选项的墨落在滑块上。
无障碍
- 根元素是
role="group",每一项是带aria-pressed的按钮。 - 整组通过
aria-label或者aria-labelledby命名;使用renderOption属性时,每一项以label命名。
API
Props
T extends SelectOption 从 options 推断,默认是 SelectOption。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | string | number | 第一个可用项的值 | 选中项的值 |
options | T[] | — | 选项,类型见 Select |
size | 'sm' | 'md' | 'lg' | 'md' | 尺寸 |
orientation | 'horizontal' | 'vertical' | 'horizontal' | 排列方向 |
block | boolean | false | 是否占满父元素宽度 |
disabled | boolean | false | 是否禁用整组 |
className | string | — | 追加至根元素的类名 |
内容属性
| 属性 | 参数 | 说明 |
|---|---|---|
renderOption | { option: T } | 每一项的内容 |
回调
| 回调 | 参数 | 说明 |
|---|---|---|
onValueChange | value: string | number | 选中项变化 |