'use client'
import { useState } from 'react'
import { Bold, Italic, Underline } from 'lucide-react'
import { Inline, Toggle } from '@hina-ui/react'
export default function Demo() {
const [bold, setBold] = useState(true)
const [italic, setItalic] = useState(false)
const [underline, setUnderline] = useState(false)
return (
<Inline gap="xs">
<Toggle value={bold} onValueChange={setBold} label="加粗" renderIcon={() => <Bold />} />
<Toggle value={italic} onValueChange={setItalic} label="斜体" renderIcon={() => <Italic />} />
<Toggle
value={underline}
onValueChange={setUnderline}
label="下划线"
renderIcon={() => <Underline />}
/>
</Inline>
)
}
tsx
用法
import { Toggle } from '@hina-ui/react'
ts
切换按钮是一个可以保持按下状态的按钮,用于加粗、收藏、筛选这类随时可以打开或者关闭的操作。value / onValueChange 绑定是否按下,children 是文字,renderIcon 放前置图标。外观取自 Button 的 ghost 与 outline 两种形态,按下后字色与墨转为品牌色。未声明的属性都会传给按钮元素。
显示全部作品
'use client'
import { useState } from 'react'
import { ListFilter } from 'lucide-react'
import { Stack, Text, Toggle } from '@hina-ui/react'
export default function Demo() {
const [onlyDone, setOnlyDone] = useState(false)
return (
<Stack gap="sm" align="start">
<Toggle value={onlyDone} onValueChange={setOnlyDone} renderIcon={() => <ListFilter />}>
仅看已完结
</Toggle>
<Text tone="muted" size="sm">
{onlyDone ? '只显示已完结的作品' : '显示全部作品'}
</Text>
</Stack>
)
}
tsx
它与 Switch 的区别在于场合:开关表示一项设置,总是带文字说明,独立成行;切换按钮是一个动作,常常只有图标,成排出现在工具栏里。与 Chip 的可选中形态相比,Chip 是行内的条目,胶囊形且尺寸更小。
示例
图标型
label 提供无障碍名称。没有 children 文字时,按钮使用正方形的图标型尺寸;同时提供文字时,保留常规按钮内边距。在 TooltipProvider 内,label 还会作为 Tooltip 的文字提示。pressedIcon 给出按下时的图标,两个图标之间交叉淡变。
'use client'
import { useState } from 'react'
import { Bookmark, Star } from 'lucide-react'
import { Inline, Toggle } from '@hina-ui/react'
export default function Demo() {
const [starred, setStarred] = useState(true)
const [saved, setSaved] = useState(false)
return (
<Inline gap="xs">
<Toggle
value={starred}
onValueChange={setStarred}
label="收藏"
pill
renderIcon={() => <Star />}
pressedIcon={<Star fill="currentColor" />}
/>
<Toggle
value={saved}
onValueChange={setSaved}
label="稍后再看"
pill
renderIcon={() => <Bookmark />}
pressedIcon={<Bookmark fill="currentColor" />}
/>
</Inline>
)
}
tsx
工具栏
成排的图标型切换按钮各自持有一个布尔值,互不影响。需要恰好一项被选中时应使用 SegmentedControl。
'use client'
import { useState } from 'react'
import { Bold, Italic, Strikethrough, Underline } from 'lucide-react'
import { Inline, Toggle } from '@hina-ui/react'
export default function Demo() {
const [marks, setMarks] = useState({
bold: false,
italic: false,
underline: false,
strike: false,
})
return (
<Inline gap="xs">
<Toggle
value={marks.bold}
onValueChange={bold => setMarks({ ...marks, bold })}
label="加粗"
size="sm"
renderIcon={() => <Bold />}
/>
<Toggle
value={marks.italic}
onValueChange={italic => setMarks({ ...marks, italic })}
label="斜体"
size="sm"
renderIcon={() => <Italic />}
/>
<Toggle
value={marks.underline}
onValueChange={underline => setMarks({ ...marks, underline })}
label="下划线"
size="sm"
renderIcon={() => <Underline />}
/>
<Toggle
value={marks.strike}
onValueChange={strike => setMarks({ ...marks, strike })}
label="删除线"
size="sm"
renderIcon={() => <Strikethrough />}
/>
</Inline>
)
}
tsx
形态
ghost 为默认,outline 带发丝线边框,适合单独放在内容里。
'use client'
import { useState } from 'react'
import { Pin } from 'lucide-react'
import { Inline, Toggle } from '@hina-ui/react'
export default function Demo() {
const [ghost, setGhost] = useState(true)
const [outline, setOutline] = useState(true)
return (
<Inline gap="sm">
<Toggle value={ghost} onValueChange={setGhost} variant="ghost" renderIcon={() => <Pin />}>
置顶
</Toggle>
<Toggle
value={outline}
onValueChange={setOutline}
variant="outline"
renderIcon={() => <Pin />}
>
置顶
</Toggle>
</Inline>
)
}
tsx
尺寸
size 有 sm、md、lg 三档,与 Button 逐档相同。
'use client'
import { Heart } from 'lucide-react'
import { Inline, Toggle } from '@hina-ui/react'
export default function Demo() {
return (
<Inline gap="sm" align="center">
<Toggle size="sm" variant="outline" value renderIcon={() => <Heart />}>
小号
</Toggle>
<Toggle size="md" variant="outline" value renderIcon={() => <Heart />}>
中号
</Toggle>
<Toggle size="lg" variant="outline" value renderIcon={() => <Heart />}>
大号
</Toggle>
</Inline>
)
}
tsx
状态
disabled 禁用按钮,禁用时保留按下状态。
'use client'
import { Bold } from 'lucide-react'
import { Inline, Toggle } from '@hina-ui/react'
export default function Demo() {
return (
<Inline gap="sm">
<Toggle variant="outline" disabled renderIcon={() => <Bold />}>
已禁用
</Toggle>
<Toggle variant="outline" disabled value renderIcon={() => <Bold />}>
禁用且按下
</Toggle>
</Inline>
)
}
tsx
在表单中
放进 FormField 后,错误信息由字段渲染并关联到按钮;校验规则与提交交给 Form。切换按钮自带文字,字段不必再写标签;它的值决定其他字段是否必填时,规则写在对象层,再指定错误落在哪个字段。
'use client'
import { useState } from 'react'
import * as v from 'valibot'
import { Button, Form, FormField, Input, Text, Toggle } from '@hina-ui/react'
const schema = v.pipe(
v.object({
pinned: v.boolean(),
title: v.string(),
}),
v.forward(
v.check(input => !input.pinned || input.title.trim().length > 0, '置顶的公告需要标题'),
['title'],
),
)
export default function Demo() {
const [values, setValues] = useState({ pinned: true, title: '' })
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="title" label="标题">
<Input value={values.title} onValueChange={title => setValues({ ...values, title })} />
</FormField>
<FormField name="pinned">
<Toggle
value={values.pinned}
onValueChange={pinned => setValues({ ...values, pinned })}
variant="outline"
>
置顶
</Toggle>
</FormField>
<Button type="submit" loading={submitting} className="self-start">
发布
</Button>
{saved && (
<Text tone="muted" size="sm">
已发布:{saved}
</Text>
)}
</>
)}
</Form>
)
}
tsx
行为
- 点击、空格或者 Enter 在按下与松开之间切换。
- 按下态由品牌色的墨与字色表达,悬停与按压的墨叠加在其上。
- 有
pressedIcon时两个图标交叉淡变,不硬切。
无障碍
- 根元素是带
aria-pressed的按钮,读屏软件读作「切换按钮,已按下」。 - 图标型必须提供
label;文字型默认以文字命名,也可通过label补充完整名称。
API
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | boolean | false | 是否按下 |
label | string | — | 无障碍名称;无 children 时使用图标型尺寸 |
tooltip | boolean | true | 是否显示 label 对应的文字提示 |
side | 'top' | 'right' | 'bottom' | 'left' | 'top' | 文字提示的位置 |
variant | 'ghost' | 'outline' | 'ghost' | 形态 |
size | 'sm' | 'md' | 'lg' | 'md' | 尺寸 |
pill | boolean | false | 是否为胶囊形 |
disabled | boolean | false | 是否禁用 |
ripple | boolean | true | 是否显示按压波纹 |
className | string | — | 追加至按钮元素的类名 |
内容属性
| 属性 | 参数 | 说明 |
|---|---|---|
children | — | 文字 |
renderIcon | { pressed: boolean } | 前置图标 |
pressedIcon | — | 按下时的前置图标 |
回调
| 回调 | 参数 | 说明 |
|---|---|---|
onValueChange | value: boolean | 按下状态变化 |