fix(ui): PasswordInput 眼睛移入输入框,隐藏 Edge 原生显隐按钮

- Input 新增 suffix 插槽,眼睛不再并排在外
- 隐藏 ::-ms-reveal / ::-ms-clear,避免 Edge 聚焦时出现两个眼睛
- 展示页补 suffix 示例;COMPONENTS.md / README 同步;版本 0.3.1
This commit is contained in:
2026-10-30 08:59:22 +08:00
parent 93b1e41d28
commit ebe4af3dab
9 changed files with 76 additions and 44 deletions
+13
View File
@@ -62,6 +62,19 @@
color: var(--su-text-muted);
}
.suffix {
display: inline-flex;
flex-shrink: 0;
align-items: center;
}
/* Edge 会给 type=password 的输入框塞一个原生显隐按钮(聚焦时出现),
和 PasswordInput 自己的按钮叠成两个眼睛;这里把它隐藏。 */
.input::-ms-reveal,
.input::-ms-clear {
display: none;
}
.clear {
--clear-size: 20px;
+6 -1
View File
@@ -1,4 +1,4 @@
import type { CSSProperties, InputHTMLAttributes, Ref } from 'react'
import type { CSSProperties, InputHTMLAttributes, ReactNode, Ref } from 'react'
import { Icon, type IconName } from './Icon'
import styles from './Input.module.css'
@@ -23,6 +23,8 @@ export type InputProps = {
onClear?: () => void
/** 右侧提示,如 `⌘K`;仅作展示,不参与键盘处理 */
shortcut?: string
/** 右侧插槽:放图标按钮、单位等(例如密码显隐、复制)。与 `clearable` 同时存在时排在清除按钮之前 */
suffix?: ReactNode
inputRef?: Ref<HTMLInputElement>
className?: string
style?: CSSProperties
@@ -34,6 +36,7 @@ export function Input({
clearable = false,
onClear,
shortcut,
suffix,
inputRef,
className,
style,
@@ -52,6 +55,8 @@ export function Input({
<input ref={inputRef} className={styles.input} value={value} {...rest} />
{suffix !== undefined && <span className={styles.suffix}>{suffix}</span>}
{shortcut !== undefined && <kbd className={styles.shortcut}>{shortcut}</kbd>}
{showClear && (
-12
View File
@@ -1,12 +0,0 @@
.root {
display: flex;
align-items: center;
gap: var(--su-space-2);
width: 100%;
}
/* 输入框吃掉剩余宽度,眼睛按钮保持自身尺寸 */
.input {
flex: 1;
min-width: 0;
}
+23 -24
View File
@@ -2,15 +2,17 @@ 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` + 右侧显隐切换。
* 密码输入框:`Input` + 输入框内部的显隐切换。
*
* 为什么单独成组件而不是每个页面拼一遍:密码框的「锁图标 + 眼睛按钮 +
* aria 名称 + type 切换」是固定搭配,手拼时很容易漏掉显隐按钮(Stash 的
* 系统改密页就漏过一次),而漏掉之后用户无法确认自己输的是什么。
*
* 眼睛放在输入框内部(`Input` 的 suffix 槽)而不是外面并排:
* 并排时会和 Edge 原生的密码显隐按钮叠成两个眼睛,视觉上也更像一个控件。
*
* 事件语义与 `Input` 一致:`onChange` 收到的是原生事件,Enter 等键盘逻辑
* 用 `onKeyDown`。输入框本身不知道「密码是否正确」这类业务状态。
*/
@@ -39,28 +41,25 @@ export function PasswordInput({
}: 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>
<Input
{...rest}
className={className}
style={style}
size={size}
type={visible ? 'text' : 'password'}
icon={icon ?? undefined}
suffix={
toggle ? (
<IconButton
label={visible ? '隐藏密码' : '显示密码'}
aria-pressed={visible}
size="xs"
icon={<Icon name={visible ? 'eye_close_line' : 'eye_line'} />}
onClick={() => setVisible((current) => !current)}
/>
) : undefined
}
/>
)
}