feat(login): add password show/hide (eye) toggle on login form

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zio 2026-07-12 22:31:34 +09:00
parent 7316cd99c5
commit 55a3114141
2 changed files with 63 additions and 9 deletions

View File

@ -31,6 +31,7 @@ export function LoginPage() {
const [phase, setPhase] = useState<Phase>('login'); const [phase, setPhase] = useState<Phase>('login');
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [showPw, setShowPw] = useState(false);
const [remember, setRemember] = useState(false); const [remember, setRemember] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
@ -157,15 +158,39 @@ export function LoginPage() {
</div> </div>
<div className="kx-field"> <div className="kx-field">
<label htmlFor="password">{t('login.password')}</label> <label htmlFor="password">{t('login.password')}</label>
<div className="kx-field__pw">
<input <input
id="password" id="password"
type="password" type={showPw ? 'text' : 'password'}
autoComplete="current-password" autoComplete="current-password"
placeholder="••••••••" placeholder="••••••••"
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
required required
/> />
<button
type="button"
className="kx-field__pw-toggle"
onClick={() => setShowPw((v) => !v)}
aria-label={showPw ? '비밀번호 숨기기' : '비밀번호 표시'}
aria-pressed={showPw}
tabIndex={-1}
>
{showPw ? (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M3 3l18 18" />
<path d="M10.6 10.6a2 2 0 0 0 2.8 2.8" />
<path d="M9.4 5.2A9.5 9.5 0 0 1 12 5c6 0 10 7 10 7a17.6 17.6 0 0 1-3 3.6" />
<path d="M6.1 6.7A17.4 17.4 0 0 0 2 12s4 7 10 7a9.3 9.3 0 0 0 3.6-.7" />
</svg>
) : (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7z" />
<circle cx="12" cy="12" r="3" />
</svg>
)}
</button>
</div>
</div> </div>
<div className="kx-login__row"> <div className="kx-login__row">

View File

@ -364,6 +364,35 @@
border-color: var(--color-primary-600); border-color: var(--color-primary-600);
box-shadow: 0 0 0 3px rgba(0, 102, 179, 0.15); box-shadow: 0 0 0 3px rgba(0, 102, 179, 0.15);
} }
/* 비밀번호 표시/숨김 토글 (눈 아이콘) */
.kx-field__pw {
position: relative;
}
.kx-field__pw input {
width: 100%;
padding-right: 44px;
}
.kx-field__pw-toggle {
position: absolute;
top: 50%;
right: 6px;
transform: translateY(-50%);
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: none;
background: transparent;
color: var(--color-neutral-500);
cursor: pointer;
border-radius: var(--radius-sm);
transition: color 0.15s ease, background 0.15s ease;
}
.kx-field__pw-toggle:hover {
color: var(--color-primary-600);
background: var(--color-neutral-050);
}
/* ── 워크스페이스 선택 ── */ /* ── 워크스페이스 선택 ── */
.kx-ws { .kx-ws {