表单组件
批次 1:输入族 7 个组件(input / select / slider / tag-input / switch / segmented / upload),全部消费 --token-* 设计变量。
input
input · 输入框
wrapper 式:三档尺寸;前后缀、清除钮、密码可见切换、错误态、禁用态。
<div class="input-wrap control input-wrap--sm">
<input class="input" placeholder="小号(32px)" />
</div>
<div class="input-wrap control">
<input class="input" placeholder="默认(36px)" />
</div>
<div class="input-wrap control input-wrap--lg">
<input class="input" placeholder="大号(40px)" />
</div>
¥
元
<div class="input-wrap control">
<span class="input-leading">¥</span>
<input class="input" placeholder="0.00" />
<span class="input-trailing">元</span>
</div>
<div class="input-wrap control">
<input class="input" value="点右侧 × 可清空" />
<button class="input-clear" type="button" aria-label="清空"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg></button>
</div>
<div class="input-wrap control">
<input class="input" type="password" value="secret-123" />
<button class="input-pw-toggle" type="button"><svg data-icon="show" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg><svg data-icon="hide" hidden width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"/><path d="M10.73 5.08A10.4 10.4 0 0 1 12 5c6.5 0 10 7 10 7a17.6 17.6 0 0 1-2.16 3.19"/><path d="M6.61 6.61A17.4 17.4 0 0 0 2 12s3.5 7 10 7a10.5 10.5 0 0 0 5.17-1.34"/><line x1="2" x2="22" y1="2" y2="22"/></svg></button>
</div>
<div class="input-wrap control is-error">
<input class="input" value="校验未通过的内容" />
</div>
<div class="input-wrap control is-disabled">
<input class="input" disabled placeholder="禁用态" />
</div> import { initInput } from '@icen.ai/ui/behaviors/input';
initInput(); // 清除钮 / 密码切换 / textarea autosize / OTP textarea
textarea · 多行输入
同一 wrapper 视觉;data-autosize 自适应高度,data-min-rows / data-max-rows 钳制。
<div class="textarea-wrap control">
<textarea class="textarea" data-autosize data-min-rows="3" data-max-rows="6" placeholder="输入多行文本,高度随内容自适应(3–6 行)"></textarea>
</div> search
search · 搜索框
input-wrap--search 结构约定:leading 放大镜 + 清除钮。
<div class="input-wrap control input-wrap--search">
<span class="input-leading"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg></span>
<input class="input" type="search" placeholder="搜索文档、组件、令牌…" />
<button class="input-clear" type="button" aria-label="清空"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg></button>
</div> select
select · 选择器
触发器与 input 同视觉;弹层支持 ↑↓ 移动、Enter 选中、Esc / 外点关闭,选中值同步隐藏 input。
<div class="select" data-select>
<button class="select-trigger pressable focus-ring" type="button" aria-haspopup="listbox" aria-expanded="false">
<span class="select-value is-empty">选择一种水果</span>
<svg class="select-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
</button>
<div class="select-panel" hidden>
<button class="select-option" type="button" data-value="apple">苹果</button>
<button class="select-option" type="button" data-value="banana">香蕉</button>
<button class="select-option" type="button" data-value="orange">橙子</button>
</div>
<input type="hidden" data-select-value name="fruit" />
</div> import { initSelect } from '@icen.ai/ui/behaviors/select';
initSelect(); slider
slider · 滑块
透明原生 range 覆盖层驱动填充与滑块;双滑块带选中段与 lo ≤ hi − step 钳制。
<div class="slider-row"><span>音量</span><span id="demo-slider-val">40</span></div>
<div class="slider">
<div class="slider-track"></div>
<div class="slider-fill" style="width:40%"></div>
<div class="slider-thumb" style="left:calc(40% - 8px)"></div>
<input class="slider-native" id="demo-slider" type="range" min="0" max="100" value="40" aria-label="音量" />
</div> <div class="slider-row"><span>价格区间</span><span id="demo-dual-val">20 – 80</span></div>
<div class="slider slider--dual" id="demo-dual">
<div class="slider-track"></div>
<div class="slider-range" style="left:20%; right:20%"></div>
<div class="slider-thumb slider-thumb--lo" style="left:calc(20% - 8px)"></div>
<div class="slider-thumb slider-thumb--hi" style="left:calc(80% - 8px)"></div>
<input class="slider-native" type="range" data-thumb="lo" min="0" max="100" value="20" aria-label="最小值" />
<input class="slider-native" type="range" data-thumb="hi" min="0" max="100" value="80" aria-label="最大值" />
</div> import { initSlider } from '@icen.ai/ui/behaviors/slider';
initSlider(); tag-input
tag-input · 标签输入
Enter / 逗号提交、重复静默清空、空草稿 Backspace 删尾、blur 提交;达到 data-max 边框转 warning 色并拒加。
<div class="tag-input control" data-tags="设计,前端" data-max="5">
<input class="tag-input-field" placeholder="输入后回车添加(最多 5 个)" />
</div> import { initTagInput } from '@icen.ai/ui/behaviors/tag-input';
initTagInput(); switch
switch · 开关 / 勾选 / 单选
纯 CSS 状态组件:开关两档尺寸;勾选框三态(未选 / 已选 / 半选);单选行组排他。
<button class="switch" type="button" role="switch" aria-checked="true" aria-label="标准开关(开)"><span class="switch-thumb"></span></button>
<button class="switch" type="button" role="switch" aria-checked="false" aria-label="标准开关(关)"><span class="switch-thumb"></span></button>
<button class="switch switch--sm" type="button" role="switch" aria-checked="true" aria-label="小号开关"><span class="switch-thumb"></span></button> <button class="checkbox focus-ring" type="button" role="checkbox" aria-checked="false" aria-label="未选中"></button>
<button class="checkbox focus-ring" type="button" role="checkbox" aria-checked="true" aria-label="已选中"></button>
<button class="checkbox focus-ring" type="button" role="checkbox" aria-checked="mixed" aria-label="半选"></button> <div class="radio-group" role="radiogroup" aria-label="套餐">
<button class="radio-item motion is-selected" type="button" role="radio" aria-checked="true"><span class="radio-dot"></span>基础版</button>
<button class="radio-item motion" type="button" role="radio" aria-checked="false"><span class="radio-dot"></span>专业版</button>
<button class="radio-item motion" type="button" role="radio" aria-checked="false"><span class="radio-dot"></span>旗舰版</button>
</div> segmented
segmented · 分段 / 切换组 / 步进器 / OTP
同族四件:分段控件(两档尺寸)、toggle-group、数字步进器、OTP 单元格(输入跳格 / 粘贴分摊)。
<div class="segmented control" role="radiogroup" aria-label="统计周期">
<button class="segmented-item motion pressable" type="button">日</button>
<button class="segmented-item motion pressable is-active" type="button">周</button>
<button class="segmented-item motion pressable" type="button">月</button>
</div>
<div class="segmented segmented--sm control" role="radiogroup" aria-label="小号分段">
<button class="segmented-item motion pressable is-active" type="button">列表</button>
<button class="segmented-item motion pressable" type="button">网格</button>
</div> <div class="toggle-group control" role="group" aria-label="对齐方式">
<button class="toggle-item motion pressable is-active" type="button">左对齐</button>
<button class="toggle-item motion pressable" type="button">居中</button>
<button class="toggle-item motion pressable" type="button">右对齐</button>
</div> <div class="stepper control">
<button class="stepper-btn pressable" type="button" data-step="-1" aria-label="减少"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg></button>
<input class="stepper-input" type="number" value="3" min="0" max="10" step="1" aria-label="数量" />
<button class="stepper-btn pressable" type="button" data-step="1" aria-label="增加"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg></button>
</div> <div class="otp" role="group" aria-label="短信验证码">
<input class="otp-cell" maxlength="1" inputmode="numeric" aria-label="第 1 位" />
<input class="otp-cell" maxlength="1" inputmode="numeric" aria-label="第 2 位" />
<input class="otp-cell" maxlength="1" inputmode="numeric" aria-label="第 3 位" />
<input class="otp-cell" maxlength="1" inputmode="numeric" aria-label="第 4 位" />
</div> upload
upload · 拖放上传
点击 / Enter / Space 打开文件选择;拖入高亮,drop 后文件写入 input.files 并派发 change。
点击或拖拽文件到此处上传
支持多选,单文件不超过 2GB
<div class="upload control lift focus-ring" role="button" tabindex="0" aria-label="上传文件">
<input type="file" multiple />
<div class="upload-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg></div>
<p class="upload-title">点击或拖拽文件到此处上传</p>
<p class="upload-desc">支持多选,单文件不超过 2GB</p>
<p class="upload-error" id="demo-upload-files" hidden></p>
</div> import { initUpload } from '@icen.ai/ui/behaviors/upload';
initUpload();