Skip to content

Commit 6d75cfb

Browse files
feat: add password visibility toggle to Input component
- Added Eye/EyeOff icons from lucide-react - Implemented state to toggle input type - Added proper aria-labels for accessibility - Styled for both Neo and Glass themes
1 parent a035ec0 commit 6d75cfb

File tree

2 files changed

+33
-3
lines changed

2 files changed

+33
-3
lines changed

.Jules/palette.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Palette Journal

web/components/ui/Input.tsx

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import React from 'react';
1+
import React, { useState } from 'react';
2+
import { Eye, EyeOff } from 'lucide-react';
23
import { useTheme } from '../../contexts/ThemeContext';
34
import { THEMES } from '../../constants';
45

@@ -7,8 +8,12 @@ interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
78
error?: string;
89
}
910

10-
export const Input: React.FC<InputProps> = ({ label, error, className = '', ...props }) => {
11+
export const Input: React.FC<InputProps> = ({ label, error, className = '', type, ...props }) => {
1112
const { style, mode } = useTheme();
13+
const [showPassword, setShowPassword] = useState(false);
14+
15+
const isPassword = type === 'password';
16+
const inputType = isPassword ? (showPassword ? 'text' : 'password') : type;
1217

1318
let inputStyles = "w-full outline-none transition-all duration-200";
1419

@@ -18,10 +23,34 @@ export const Input: React.FC<InputProps> = ({ label, error, className = '', ...p
1823
inputStyles += ` p-3 rounded-lg border border-white/20 bg-white/5 backdrop-blur-sm focus:bg-white/10 focus:border-blue-400 focus:ring-2 focus:ring-blue-500/20 ${mode === 'dark' ? 'text-white placeholder-white/40' : 'text-gray-900 placeholder-gray-500'}`;
1924
}
2025

26+
if (isPassword) {
27+
inputStyles += " pr-10";
28+
}
29+
2130
return (
2231
<div className="flex flex-col gap-1 w-full">
2332
{label && <label className={`text-sm font-semibold ${style === THEMES.NEOBRUTALISM ? 'uppercase' : 'ml-1 opacity-80'}`}>{label}</label>}
24-
<input className={`${inputStyles} ${className}`} {...props} />
33+
<div className="relative">
34+
<input
35+
type={inputType}
36+
className={`${inputStyles} ${className}`}
37+
{...props}
38+
/>
39+
{isPassword && (
40+
<button
41+
type="button"
42+
onClick={() => setShowPassword(!showPassword)}
43+
className={`absolute right-3 top-1/2 -translate-y-1/2 p-1 rounded-md transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-1 ${
44+
style === THEMES.NEOBRUTALISM
45+
? (mode === 'dark' ? 'text-white opacity-80 focus:ring-white' : 'text-black opacity-60 focus:ring-black')
46+
: 'text-white/60 hover:text-white focus:ring-white/50'
47+
}`}
48+
aria-label={showPassword ? "Hide password" : "Show password"}
49+
>
50+
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
51+
</button>
52+
)}
53+
</div>
2554
{error && <span className="text-red-500 text-xs font-bold mt-1">{error}</span>}
2655
</div>
2756
);

0 commit comments

Comments
 (0)