feat(ui): add PasswordInput(锁图标 + 显隐切换)
- Input + IconButton 组合,type 切换、aria-pressed、尺寸对齐内建 - 展示页补「密码框」示例与 props 表;COMPONENTS.md / README 同步 - 版本 0.3.0
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
.root {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--su-space-2);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 输入框吃掉剩余宽度,眼睛按钮保持自身尺寸 */
|
||||
.input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user