193 lines
4.9 KiB
Markdown
193 lines
4.9 KiB
Markdown
# 表单与输入
|
||
|
||
## 目录
|
||
|
||
- 表单使用 FieldGroup + Field
|
||
- InputGroup 需配合 InputGroupInput/InputGroupTextarea
|
||
- 输入框内的按钮使用 InputGroup + InputGroupAddon
|
||
- 选项集合(2–7 个选项)使用 ToggleGroup
|
||
- FieldSet + FieldLegend 用于对相关字段进行分组
|
||
- 字段校验与禁用状态
|
||
|
||
---
|
||
|
||
## 表单使用 FieldGroup + Field
|
||
|
||
始终使用 `FieldGroup` + `Field` —— 切勿使用 `space-y-*` 的原始 `div`:
|
||
|
||
```tsx
|
||
<FieldGroup>
|
||
<Field>
|
||
<FieldLabel htmlFor="email">邮箱</FieldLabel>
|
||
<Input id="email" type="email" />
|
||
</Field>
|
||
<Field>
|
||
<FieldLabel htmlFor="password">密码</FieldLabel>
|
||
<Input id="password" type="password" />
|
||
</Field>
|
||
</FieldGroup>
|
||
```
|
||
|
||
在设置页面中使用 `Field orientation="horizontal"`。对于视觉上隐藏的标签,使用 `FieldLabel className="sr-only"`。
|
||
|
||
**选择表单控件:**
|
||
|
||
- 简单文本输入 → `Input`
|
||
- 带预定义选项的下拉框 → `Select`
|
||
- 可搜索下拉框 → `Combobox`
|
||
- 原生 HTML 选择(无 JS)→ `native-select`
|
||
- 布尔切换 → `Switch`(用于设置)或 `Checkbox`(用于表单)
|
||
- 少数选项中的单选 → `RadioGroup`
|
||
- 在 2–5 个选项之间切换 → `ToggleGroup` + `ToggleGroupItem`
|
||
- OTP/验证码 → `InputOTP`
|
||
- 多行文本 → `Textarea`
|
||
|
||
---
|
||
|
||
## InputGroup 需配合 InputGroupInput/InputGroupTextarea
|
||
|
||
切勿在 `InputGroup` 内部使用原始的 `Input` 或 `Textarea`。
|
||
|
||
**错误示例:**
|
||
|
||
```tsx
|
||
<InputGroup>
|
||
<Input placeholder="搜索..." />
|
||
</InputGroup>
|
||
```
|
||
|
||
**正确示例:**
|
||
|
||
```tsx
|
||
import { InputGroup, InputGroupInput } from "@/components/ui/input-group"
|
||
|
||
<InputGroup>
|
||
<InputGroupInput placeholder="搜索..." />
|
||
</InputGroup>
|
||
```
|
||
|
||
---
|
||
|
||
## 输入框内的按钮使用 InputGroup + InputGroupAddon
|
||
|
||
切勿通过自定义定位将 `Button` 直接放在 `Input` 内部或紧邻其旁。
|
||
|
||
**错误示例:**
|
||
|
||
```tsx
|
||
<div className="relative">
|
||
<Input placeholder="搜索..." className="pr-10" />
|
||
<Button className="absolute right-0 top-0" size="icon">
|
||
<SearchIcon />
|
||
</Button>
|
||
</div>
|
||
```
|
||
|
||
**正确示例:**
|
||
|
||
```tsx
|
||
import { InputGroup, InputGroupInput, InputGroupAddon } from "@/components/ui/input-group"
|
||
|
||
<InputGroup>
|
||
<InputGroupInput placeholder="搜索..." />
|
||
<InputGroupAddon>
|
||
<Button size="icon">
|
||
<SearchIcon data-icon="inline-start" />
|
||
</Button>
|
||
</InputGroupAddon>
|
||
</InputGroup>
|
||
```
|
||
|
||
---
|
||
|
||
## 选项集合(2–7 个选项)使用 ToggleGroup
|
||
|
||
不要手动循环 `Button` 组件并自行管理激活状态。
|
||
|
||
**错误示例:**
|
||
|
||
```tsx
|
||
const [selected, setSelected] = useState("daily")
|
||
|
||
<div className="flex gap-2">
|
||
{["daily", "weekly", "monthly"].map((option) => (
|
||
<Button
|
||
key={option}
|
||
variant={selected === option ? "default" : "outline"}
|
||
onClick={() => setSelected(option)}
|
||
>
|
||
{option}
|
||
</Button>
|
||
))}
|
||
</div>
|
||
```
|
||
|
||
**正确示例:**
|
||
|
||
```tsx
|
||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
|
||
|
||
<ToggleGroup spacing={2}>
|
||
<ToggleGroupItem value="daily">每日</ToggleGroupItem>
|
||
<ToggleGroupItem value="weekly">每周</ToggleGroupItem>
|
||
<ToggleGroupItem value="monthly">每月</ToggleGroupItem>
|
||
</ToggleGroup>
|
||
```
|
||
|
||
结合 `Field` 使用,创建带标签的切换组:
|
||
|
||
```tsx
|
||
<Field orientation="horizontal">
|
||
<FieldTitle id="theme-label">主题</FieldTitle>
|
||
<ToggleGroup aria-labelledby="theme-label" spacing={2}>
|
||
<ToggleGroupItem value="light">浅色</ToggleGroupItem>
|
||
<ToggleGroupItem value="dark">深色</ToggleGroupItem>
|
||
<ToggleGroupItem value="system">跟随系统</ToggleGroupItem>
|
||
</ToggleGroup>
|
||
</Field>
|
||
```
|
||
|
||
> **注意:** `defaultValue` 和 `type`/`multiple` 属性在 base 与 radix 之间存在差异。详见 [base-vs-radix.md](./base-vs-radix.md#togglegroup)。
|
||
|
||
---
|
||
|
||
## FieldSet + FieldLegend 用于对相关字段进行分组
|
||
|
||
对相关的复选框、单选按钮或开关,使用 `FieldSet` + `FieldLegend` —— 而不是在 `div` 中加一个标题:
|
||
|
||
```tsx
|
||
<FieldSet>
|
||
<FieldLegend variant="label">偏好设置</FieldLegend>
|
||
<FieldDescription>请选择所有适用的选项。</FieldDescription>
|
||
<FieldGroup className="gap-3">
|
||
<Field orientation="horizontal">
|
||
<Checkbox id="dark" />
|
||
<FieldLabel htmlFor="dark" className="font-normal">深色模式</FieldLabel>
|
||
</Field>
|
||
</FieldGroup>
|
||
</FieldSet>
|
||
```
|
||
|
||
---
|
||
|
||
## 字段校验与禁用状态
|
||
|
||
两个属性都需要 —— `data-invalid`/`data-disabled` 用于设置字段的样式(标签、描述),而 `aria-invalid`/`disabled` 用于设置控件的样式。
|
||
|
||
```tsx
|
||
// 无效状态
|
||
<Field data-invalid>
|
||
<FieldLabel htmlFor="email">邮箱</FieldLabel>
|
||
<Input id="email" aria-invalid />
|
||
<FieldDescription>邮箱地址无效。</FieldDescription>
|
||
</Field>
|
||
|
||
// 禁用状态
|
||
<Field data-disabled>
|
||
<FieldLabel htmlFor="email">邮箱</FieldLabel>
|
||
<Input id="email" disabled />
|
||
</Field>
|
||
```
|
||
|
||
适用于所有控件:`Input`、`Textarea`、`Select`、`Checkbox`、`RadioGroupItem`、`Switch`、`Slider`、`NativeSelect`、`InputOTP`。
|