Fix password visibility toggle UX

- Change confusing placeholder from bullets to "Saved - enter new value to replace"
- Only show eye toggle when field has actual content to reveal
- Add z-index and improved click handling for toggle button

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
clucraft 2026-01-22 20:39:47 -05:00
parent 81bbd8538f
commit b885e4ef57
2 changed files with 21 additions and 13 deletions

View file

@ -4,23 +4,30 @@ interface PasswordInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>,
// All standard input props are inherited
}
export default function PasswordInput({ style, ...props }: PasswordInputProps) {
export default function PasswordInput({ style, value, ...props }: PasswordInputProps) {
const [visible, setVisible] = useState(false);
const hasValue = value !== undefined && value !== null && String(value).length > 0;
return (
<div style={{ position: 'relative', display: 'flex' }}>
<div style={{ position: 'relative' }}>
<input
{...props}
value={value}
type={visible ? 'text' : 'password'}
style={{
...style,
width: '100%',
paddingRight: '2.5rem',
paddingRight: hasValue ? '2.5rem' : '0.75rem',
boxSizing: 'border-box',
}}
/>
<button
{hasValue && <button
type="button"
onClick={() => setVisible(!visible)}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setVisible(!visible);
}}
style={{
position: 'absolute',
right: '0.5rem',
@ -33,7 +40,8 @@ export default function PasswordInput({ style, ...props }: PasswordInputProps) {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'var(--text-muted)',
color: visible ? 'var(--primary)' : 'var(--text-muted)',
zIndex: 10,
}}
title={visible ? 'Hide' : 'Show'}
aria-label={visible ? 'Hide password' : 'Show password'}
@ -71,7 +79,7 @@ export default function PasswordInput({ style, ...props }: PasswordInputProps) {
<circle cx="12" cy="12" r="3" />
</svg>
)}
</button>
</button>}
</div>
);
}

View file

@ -987,7 +987,7 @@ export default function Settings() {
<PasswordInput
value={telegramBotToken}
onChange={(e) => setTelegramBotToken(e.target.value)}
placeholder={notificationSettings?.telegram_configured ? '••••••••••••••••' : 'Enter your bot token'}
placeholder={notificationSettings?.telegram_configured ? 'Saved - enter new value to replace' : 'Enter your bot token'}
/>
<p className="hint">Create a bot via @BotFather on Telegram to get a token</p>
</div>
@ -1056,7 +1056,7 @@ export default function Settings() {
<PasswordInput
value={discordWebhookUrl}
onChange={(e) => setDiscordWebhookUrl(e.target.value)}
placeholder={notificationSettings?.discord_configured ? '••••••••••••••••' : 'https://discord.com/api/webhooks/...'}
placeholder={notificationSettings?.discord_configured ? 'Saved - enter new value to replace' : 'https://discord.com/api/webhooks/...'}
/>
<p className="hint">Server Settings Integrations Webhooks New Webhook</p>
</div>
@ -1114,7 +1114,7 @@ export default function Settings() {
<PasswordInput
value={pushoverUserKey}
onChange={(e) => setPushoverUserKey(e.target.value)}
placeholder={notificationSettings?.pushover_configured ? '••••••••••••••••' : 'Enter your user key'}
placeholder={notificationSettings?.pushover_configured ? 'Saved - enter new value to replace' : 'Enter your user key'}
/>
<p className="hint">Find your User Key on the Pushover dashboard after logging in</p>
</div>
@ -1124,7 +1124,7 @@ export default function Settings() {
<PasswordInput
value={pushoverAppToken}
onChange={(e) => setPushoverAppToken(e.target.value)}
placeholder={notificationSettings?.pushover_configured ? '••••••••••••••••' : 'Enter your app token'}
placeholder={notificationSettings?.pushover_configured ? 'Saved - enter new value to replace' : 'Enter your app token'}
/>
<p className="hint">Create an application at pushover.net/apps to get an API token</p>
</div>
@ -1208,7 +1208,7 @@ export default function Settings() {
<PasswordInput
value={anthropicApiKey}
onChange={(e) => setAnthropicApiKey(e.target.value)}
placeholder={aiSettings?.anthropic_configured ? '••••••••••••••••' : 'sk-ant-...'}
placeholder={aiSettings?.anthropic_configured ? 'Saved - enter new value to replace' : 'sk-ant-...'}
/>
<p className="hint">
Get your API key from{' '}
@ -1226,7 +1226,7 @@ export default function Settings() {
<PasswordInput
value={openaiApiKey}
onChange={(e) => setOpenaiApiKey(e.target.value)}
placeholder={aiSettings?.openai_configured ? '••••••••••••••••' : 'sk-...'}
placeholder={aiSettings?.openai_configured ? 'Saved - enter new value to replace' : 'sk-...'}
/>
<p className="hint">
Get your API key from{' '}