表单与选择
在线示例:/design-system/form。
表单组装
Input 的 start / end 插槽会自动把没写尺寸的图标压到 16px,不用每次自己加 size-4; 需要更大的图形再显式写 className="size-5" 覆盖。
表单页推荐的结构:Label + 控件 + 错误文案,底部用 FixedBar 收口操作。
<div className="grid gap-1.5">
<Label htmlFor="form-name">客户名称</Label>
<Input id="form-name" defaultValue="云和智能制造" clearable />
</div>
<div className="grid gap-1.5">
<Label htmlFor="form-invalid">联系电话(错误态)</Label>
<Input id="form-invalid" aria-invalid defaultValue="138" />
<p className="text-xs text-destructive">手机号格式不正确</p>
</div>
<FixedBar hint="表单校验通过后才会写入业务接口">
<Button variant="outline">取消</Button>
<Button>保存</Button>
</FixedBar>2
3
4
5
6
7
8
9
10
11
12
13
14
15
模板未内置表单库封装,页面可以直接用 react-hook-form + zod(已在依赖里), 也可以受控手写;两者都要把错误态落到 aria-invalid 与 text-destructive。
Combobox
可搜索下拉,适合人员、客户这类长列表;支持分组与清空。
<Combobox
label="负责人"
value={owner}
onValueChange={setOwner}
clearable
placeholder="搜索负责人"
options={customers.map((c) => ({ value: c.id, label: c.owner, description: c.name }))}
/>2
3
4
5
6
7
8
| prop | 类型 | 说明 |
|---|---|---|
options | (ComboboxOption | { group, options })[] | 平铺或分组选项 |
value / onValueChange | string | undefined | 受控值 |
label | string | 必填,作为访问名称 |
clearable | boolean | 显示清空 |
emptyText | string | 无匹配时的提示 |
Cascader
逐列下钻,选中叶子后回填完整路径。
<Cascader
label="归属组织"
value={org}
onValueChange={(value, path) => setOrg(value)}
options={organizationOptions}
/>2
3
4
5
6
CascaderOption 是 { value, label, children? };onValueChange 第二个参数是完整路径数组, 需要展示"公司 / 中心 / 组"时直接取它。
MultiSelect
多选 + 搜索,触发器里用 Badge 展示已选项,触发器是一个 button,内部不再嵌套 button。
<MultiSelect
label="客户状态"
values={statuses}
onValuesChange={setStatuses}
placeholder="全部状态"
options={[
{ value: 'ACTIVE', label: '正常' },
{ value: 'PENDING', label: '待处理' },
]}
/>2
3
4
5
6
7
8
9
10
日期与时间
| 组件 | 用途 | 关键 prop |
|---|---|---|
DateInput | 单日选择,支持月 / 年视图切换 | value / defaultValue(YYYY-MM-DD)、onValueChange |
DateRangeInput | 区间选择,支持拖选 | value: { from?, to? }、onValueChange |
DateTimeInput | 日期 + 时间 | value(YYYY-MM-DDTHH:mm)、onValueChange |
<DateRangeInput label="签约区间" value={range} onValueChange={setRange} />
<DateTimeInput label="排期" value={schedule} onValueChange={setSchedule} />2
区间工具在 src/lib/range.ts:rangeShortcuts() 生成常用快捷区间(今天 / 近 7 天 / 本月…), invalidRange() 判断起止颠倒,rangeLabel() 输出展示文案。
浮层里点快捷区间会立即生效并关闭(不用再点「确定」);点日历选日期仍然要点「确定」确认。
InputOTP
分格验证码输入,支持直接粘贴 6 位验证码;内部是一个真实 input,分格只负责展示。
<InputOTP
label="验证码"
value={code}
onValueChange={setCode}
fill
invalid={hasError}
describedBy="code-error"
/>2
3
4
5
6
7
8
| prop | 类型 | 说明 |
|---|---|---|
label | string | 必填,输入框的访问名称 |
length | number | 位数,默认 6 |
fill | boolean | 铺满整行:格子等分拉伸,与其它输入框等宽对齐 |
invalid | boolean | 错误态:格子描红,同时把 aria-invalid 交给读屏 |
describedBy | string | 错误说明元素的 id,与 aria-describedby 一起用 |
注册页的手机 / 邮箱验证就是用它:先选验证方式,再填验证码,验证通过才允许提交; 错误提示挂在字段下方,由 invalid + describedBy 关联到控件。
PasswordStrength
<Input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
<PasswordStrength password={password} />
{/* 表单页更常用紧凑版:只留强度条 + 问号提示 */}
<PasswordStrength password={password} compact />2
3
4
规则清单在 src/lib/password.ts(长度、大小写、数字、符号),组件只做展示; 业务要换规则改 passwordRules 即可,注册页与修改密码共用同一套。 compact 把规则收进强度条右侧的问号(悬停或聚焦展开),注册页用的就是这一版; 组件总览里的示例用默认形态,规则一次看全。
FileUpload
<FileUpload
label="上传附件"
accept=".pdf,.png,.jpg"
maxSize={2 * 1024 * 1024}
concurrency={3}
files={uploads}
onFilesChange={setUploads}
uploader={async (file, signal, onProgress) => {
await api.upload(file, { signal, onProgress }) // 业务实现
}}
/>2
3
4
5
6
7
8
9
10
11
| prop | 类型 | 说明 |
|---|---|---|
files / onFilesChange | UploadFile[] | 受控列表,含进度、错误、取消状态 |
accept / maxSize | string / number | 客户端校验,不合法直接拦下并提示 |
multiple | boolean | 是否多选 |
concurrency | number | 同时上传数上限,超出的排队 |
uploader | (file, signal, onProgress) => Promise<void> | 业务上传实现;不传时只走本地校验与进度演示 |
支持重试与取消:取消通过 AbortSignal 传给 uploader,重试会重新调用同一个实现。
ImagePreview
<ImagePreview alt="品牌方标" src="/brand/mark.svg" />
<ImagePreview alt="合同" src={contracts[0].src} images={contracts} />2
点击打开浮层,支持缩放、拖动与多图左右切换。
Mention
contentEditable 的提及输入,@ 触发候选,采纳后插入不可编辑的内联标签。
<Mention
label="跟进记录"
value={comment}
onValueChange={setComment}
options={customers.slice(0, 5).map((c) => ({ value: c.id, label: c.owner, description: c.name }))}
/>2
3
4
5
6
键盘:@ 打开候选,↑/↓ 选择,Enter 采纳,Esc 关闭。
FixedBar
长表单的粘底操作条,sticky bottom-0 + 半透明背景,滚动时不遮挡内容。
<FixedBar hint="表单校验通过后才会写入业务接口">
<Button variant="outline">取消</Button>
<Button>保存</Button>
</FixedBar>2
3
4