feat(ui): add PasswordInput(锁图标 + 显隐切换)

- Input + IconButton 组合,type 切换、aria-pressed、尺寸对齐内建
- 展示页补「密码框」示例与 props 表;COMPONENTS.md / README 同步
- 版本 0.3.0
This commit is contained in:
2026-10-29 20:46:07 +08:00
parent 0d5d7753e2
commit 93b1e41d28
8 changed files with 147 additions and 5 deletions
+12
View File
@@ -0,0 +1,12 @@
.root {
display: flex;
align-items: center;
gap: var(--su-space-2);
width: 100%;
}
/* 输入框吃掉剩余宽度,眼睛按钮保持自身尺寸 */
.input {
flex: 1;
min-width: 0;
}
+66
View File
@@ -0,0 +1,66 @@
import { useState, type CSSProperties, type InputHTMLAttributes } from 'react'
import { Icon, type IconName } from './Icon'
import { IconButton } from './IconButton'
import { Input, type InputSize } from './Input'
import styles from './PasswordInput.module.css'
/**
* 密码输入框:`Input` + 右侧显隐切换。
*
* 为什么单独成组件而不是每个页面拼一遍:密码框的「锁图标 + 眼睛按钮 +
* aria 名称 + type 切换」是固定搭配,手拼时很容易漏掉显隐按钮(Stash 的
* 系统改密页就漏过一次),而漏掉之后用户无法确认自己输的是什么。
*
* 事件语义与 `Input` 一致:`onChange` 收到的是原生事件,Enter 等键盘逻辑
* 用 `onKeyDown`。输入框本身不知道「密码是否正确」这类业务状态。
*/
export type PasswordInputProps = {
/** 左侧图标,默认 `lock_line`;传 `null` 去掉 */
icon?: IconName | null
/** 尺寸 32 / 36 / 44,与 `Input` 一致 */
size?: InputSize
/** 是否显示右侧显隐按钮,默认显示 */
toggle?: boolean
/** `className` / `style` 作用在最外层(负责宽度与间距) */
className?: string
style?: CSSProperties
} & Omit<
InputHTMLAttributes<HTMLInputElement>,
'type' | 'size' | 'className' | 'style' | 'icon'
>
export function PasswordInput({
icon = 'lock_line',
size = 'md',
toggle = true,
className,
style,
...rest
}: PasswordInputProps) {
const [visible, setVisible] = useState(false)
// 眼睛按钮的高度跟随输入框,避免并排时上下错位
const buttonSize = size === 'lg' ? 'md' : size === 'sm' ? 'xs' : 'sm'
return (
<div className={[styles.root, className].filter(Boolean).join(' ')} style={style}>
<Input
{...rest}
className={styles.input}
size={size}
type={visible ? 'text' : 'password'}
icon={icon ?? undefined}
/>
{toggle && (
<IconButton
label={visible ? '隐藏密码' : '显示密码'}
aria-pressed={visible}
size={buttonSize}
icon={<Icon name={visible ? 'eye_close_line' : 'eye_line'} />}
onClick={() => setVisible((current) => !current)}
/>
)}
</div>
)
}
+3
View File
@@ -64,6 +64,9 @@ export type { IconButtonProps, IconButtonSize } from './components/IconButton'
export { Input } from './components/Input'
export type { InputProps } from './components/Input'
export { PasswordInput } from './components/PasswordInput'
export type { PasswordInputProps } from './components/PasswordInput'
export { Layout } from './components/Layout'
export type { LayoutProps } from './components/Layout'