Files
CNWei ebe4af3dab fix(ui): PasswordInput 眼睛移入输入框,隐藏 Edge 原生显隐按钮
- Input 新增 suffix 插槽,眼睛不再并排在外
- 隐藏 ::-ms-reveal / ::-ms-clear,避免 Edge 聚焦时出现两个眼睛
- 展示页补 suffix 示例;COMPONENTS.md / README 同步;版本 0.3.1
2026-10-30 08:59:22 +08:00

227 lines
9.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react'
import { Button, FormField, Icon, IconButton, Input, PasswordInput, Space, Text, Textarea } from '../../src'
import { Demo } from '../components/Demo'
import { PropsTable } from '../components/PropsTable'
import { Section } from '../components/Section'
export function Inputs() {
const [keyword, setKeyword] = useState('')
const [host, setHost] = useState('127.0.0.1:8080')
const [email, setEmail] = useState('not-an-email')
const [password, setPassword] = useState('')
return (
<Section
id="inputs"
title="输入框"
api="Input"
desc="左侧图标 + 清除按钮 + 快捷键提示的组合式输入。事件语义与原生 input 一致,需要字符串时在使用方包一层。"
>
<Demo
title="带图标与清除"
desc="清空按钮只在有内容且传了 onClear 时出现,避免不可用的空按钮。"
layout="column"
code={`<Input
icon="search_3_line"
placeholder="搜索本地项目…"
value={keyword}
onChange={(event) => setKeyword(event.target.value)}
clearable
onClear={() => setKeyword('')}
/>`}
>
<Space gap="md" wrap>
<Input
icon="search_3_line"
placeholder="搜索本地项目…"
value={keyword}
onChange={(event) => setKeyword(event.target.value)}
clearable
onClear={() => setKeyword('')}
style={{ width: 320 }}
/>
<span style={{ alignSelf: 'center', color: 'var(--su-text-muted)', fontSize: 12 }}>
当前值:{keyword === '' ? '(空)' : keyword}
</span>
</Space>
</Demo>
<Demo
title="快捷键提示"
desc="shortcut 只是右侧展示,键盘监听由页面自己挂(例如 Ctrl/Cmd + K 聚焦)。"
layout="column"
code={`<Input icon="search_3_line" shortcut="⌘K" placeholder="搜索…" />
<Input icon="link_line" value={host} onChange={(event) => setHost(event.target.value)} />`}
>
<Input icon="search_3_line" shortcut="⌘K" placeholder="搜索…" style={{ width: 320 }} />
<Input
icon="link_line"
value={host}
onChange={(event) => setHost(event.target.value)}
style={{ width: 320 }}
/>
</Demo>
<Demo
title="禁用与表单行"
desc="输入框与按钮可以并排成一行操作区;禁用态整块降透明度,仍保留可读性。"
layout="column"
code={`<Space gap="sm" wrap>
<Input placeholder="输入项目名…" />
<Button icon={<Icon name="search_3_line" />}>搜索</Button>
</Space>
<Input disabled value="只读" />`}
>
<Space gap="sm" wrap>
<Input placeholder="输入项目名…" style={{ width: 240 }} />
<Button icon={<Icon name="search_3_line" />}>搜索</Button>
</Space>
<Input disabled value="不可编辑" style={{ width: 240 }} />
</Demo>
<Demo
title="尺寸"
desc="32 / 36 / 44 三档;搜索这类主入口用 lg,表单行用 md,表格内筛选用 sm。"
layout="column"
code={`<Input size="sm" placeholder="小号 32" />
<Input size="md" placeholder="默认 36" />
<Input size="lg" placeholder="大号 44(搜索框)" />`}
>
<Input size="sm" placeholder="小号 32" style={{ width: 320 }} />
<Input size="md" placeholder="默认 36" style={{ width: 320 }} />
<Input size="lg" placeholder="大号 44(搜索框)" style={{ width: 320 }} />
</Demo>
<Demo
title="右侧插槽"
desc="suffix 放在输入框内部右端,适合放复制、显隐、单位这类单个小动作;和 clearable 同用时排在清除按钮之前。"
layout="column"
code={`<Input
placeholder="https://…"
suffix={<IconButton label="复制" size="xs" icon={<Icon name="copy_line" />} />}
/>`}
>
<Input
value={host}
onChange={(event) => setHost(event.target.value)}
suffix={
<IconButton
label="复制"
size="xs"
icon={<Icon name="copy_line" />}
onClick={() => void navigator.clipboard.writeText(host)}
/>
}
style={{ width: 320 }}
/>
</Demo>
<Demo
title="表单字段"
desc="FormField 负责「标签 + 控件 + 提示/错误」的排版;校验逻辑留在页面,错误出现时自动 role=alert。"
layout="column"
code={`<FormField label="邮箱" htmlFor="email" hint="我们只用它发周报" error={error}>
<Input id="email" value={email} onChange={...} />
</FormField>`}
>
<div style={{ width: 320, display: 'flex', flexDirection: 'column', gap: 16 }}>
<FormField label="项目名" htmlFor="project" required hint="显示在侧栏与首页卡片标题">
<Input id="project" placeholder="例如:常用站点" />
</FormField>
<FormField
label="邮箱"
htmlFor="email"
hint="我们只用它发周报"
error={email.includes('@') ? undefined : '邮箱格式不正确'}
>
<Input id="email" value={email} onChange={(event) => setEmail(event.target.value)} />
</FormField>
<Text size="xs" tone="muted">
当前值:{email}
</Text>
</div>
</Demo>
<Demo
title="多行文本"
desc="Textarea 用于 Markdown 正文、简介这类长文本;行数可调,默认只允许竖向拖拽。"
layout="column"
code={`<Textarea rows={5} placeholder="支持 Markdown…" />
<Textarea rows={3} resize="none" invalid />`}
>
<div style={{ width: 420, display: 'flex', flexDirection: 'column', gap: 12 }}>
<Textarea rows={5} placeholder="写点什么,支持 Markdown:## 标题、**粗体**、[链接](url)" />
<Textarea rows={3} resize="none" invalid placeholder="校验失败时的样子(invalid)" />
</div>
</Demo>
<Demo
title="密码框"
desc="PasswordInput 在 Input 右侧固定一个眼睛按钮,显隐切换与无障碍名称都封装在组件里;登录、改密这类页面直接用它,不要漏掉切换按钮。"
layout="column"
code={`<FormField label="密码" htmlFor="demo-password" required hint="至少 8 位">
<PasswordInput
id="demo-password"
value={password}
onChange={(event) => setPassword(event.target.value)}
placeholder="输入密码"
/>
</FormField>`}
>
<div style={{ width: 320 }}>
<FormField label="密码" htmlFor="demo-password" required hint="至少 8 位">
<PasswordInput
id="demo-password"
value={password}
onChange={(event) => setPassword(event.target.value)}
placeholder="输入密码"
/>
</FormField>
</div>
</Demo>
<PropsTable
rows={[
{ name: 'icon', type: 'IconName', desc: '左侧前缀图标' },
{ name: 'size', type: "'sm' | 'md' | 'lg'", defaultValue: "'md'", desc: '32 / 36 / 44px' },
{ name: 'suffix', type: 'ReactNode', desc: '右侧插槽(图标按钮、单位),排在清除按钮之前' },
{ name: 'clearable', type: 'boolean', defaultValue: 'false', desc: '有内容时显示清除按钮' },
{ name: 'onClear', type: '() => void', desc: '点击清除;不传则不显示按钮' },
{ name: 'shortcut', type: 'string', desc: '右侧快捷键提示(仅展示)' },
{ name: 'inputRef', type: 'Ref<HTMLInputElement>', desc: '拿到真实 input,用于聚焦' },
{ name: 'className / style', type: 'string / CSSProperties', desc: '作用在外层容器上(宽度、外边距)' },
{ name: '…rest', type: 'InputHTMLAttributes', desc: '透传原生属性,onChange 是原生事件' },
]}
/>
<Text size="sm" tone="muted" style={{ marginTop: 24 }}>
Textarea
</Text>
<PropsTable
rows={[
{ name: 'rows', type: 'number', defaultValue: '4', desc: '初始可见行数' },
{ name: 'resize', type: "'none' | 'vertical'", defaultValue: "'vertical'", desc: '是否允许拖拽改高' },
{ name: 'invalid', type: 'boolean', defaultValue: 'false', desc: '红边框 + aria-invalid' },
{ name: '…rest', type: 'TextareaHTMLAttributes', desc: '透传原生属性' },
]}
/>
<Text size="sm" tone="muted" style={{ marginTop: 24 }}>
PasswordInput
</Text>
<PropsTable
rows={[
{ name: 'icon', type: 'IconName | null', defaultValue: "'lock_line'", desc: '左侧图标;null 表示不显示' },
{ name: 'size', type: "'sm' | 'md' | 'lg'", defaultValue: "'md'", desc: '32 / 36 / 44px,与 Input 一致' },
{ name: 'toggle', type: 'boolean', defaultValue: 'true', desc: '是否显示右侧显隐按钮' },
{ name: 'className / style', type: 'string / CSSProperties', desc: '作用在最外层容器上' },
{ name: '…rest', type: 'InputHTMLAttributes', desc: '透传原生属性(type / size / icon 由组件接管)' },
]}
/>
</Section>
)
}