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

View file

@ -987,7 +987,7 @@ export default function Settings() {
<PasswordInput <PasswordInput
value={telegramBotToken} value={telegramBotToken}
onChange={(e) => setTelegramBotToken(e.target.value)} 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> <p className="hint">Create a bot via @BotFather on Telegram to get a token</p>
</div> </div>
@ -1056,7 +1056,7 @@ export default function Settings() {
<PasswordInput <PasswordInput
value={discordWebhookUrl} value={discordWebhookUrl}
onChange={(e) => setDiscordWebhookUrl(e.target.value)} 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> <p className="hint">Server Settings Integrations Webhooks New Webhook</p>
</div> </div>
@ -1114,7 +1114,7 @@ export default function Settings() {
<PasswordInput <PasswordInput
value={pushoverUserKey} value={pushoverUserKey}
onChange={(e) => setPushoverUserKey(e.target.value)} 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> <p className="hint">Find your User Key on the Pushover dashboard after logging in</p>
</div> </div>
@ -1124,7 +1124,7 @@ export default function Settings() {
<PasswordInput <PasswordInput
value={pushoverAppToken} value={pushoverAppToken}
onChange={(e) => setPushoverAppToken(e.target.value)} 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> <p className="hint">Create an application at pushover.net/apps to get an API token</p>
</div> </div>
@ -1208,7 +1208,7 @@ export default function Settings() {
<PasswordInput <PasswordInput
value={anthropicApiKey} value={anthropicApiKey}
onChange={(e) => setAnthropicApiKey(e.target.value)} 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"> <p className="hint">
Get your API key from{' '} Get your API key from{' '}
@ -1226,7 +1226,7 @@ export default function Settings() {
<PasswordInput <PasswordInput
value={openaiApiKey} value={openaiApiKey}
onChange={(e) => setOpenaiApiKey(e.target.value)} 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"> <p className="hint">
Get your API key from{' '} Get your API key from{' '}