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