Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .Jules/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
## [Unreleased]

### Added
- **Consistent Focus States:** Added `focus-visible` styles to all buttons in the web application.
- **Features:**
- Modified `Button` component to support explicit keyboard focus rings for both Glassmorphism and Neobrutalism themes.
- Updated generic components (Inputs, Toasts, Modals) with visible focus indicators.
- Updated interactive elements across pages (Auth, Dashboard, Friends, GroupDetails, Profile, SplitwiseImport, SplitwiseGroupSelection).
- **Technical:** Used Tailwind's `focus-visible:` pseudo-class to ensure focus rings are only shown for keyboard navigation, maintaining a clean visual UI for mouse users.

- **Password Strength Meter:** Added a visual password strength indicator to the signup form.
- **Features:**
- Real-time strength calculation (Length, Uppercase, Lowercase, Number, Symbol).
Expand Down
3 changes: 2 additions & 1 deletion .Jules/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@

### Web

- [ ] **[style]** Consistent hover/focus states across all buttons
- [x] **[style]** Consistent hover/focus states across all buttons
- Completed: 2026-02-08
- Files: `web/components/ui/Button.tsx`, usage across pages
- Context: Ensure all buttons have proper hover + focus-visible styles
- Impact: Professional feel, keyboard users know where they are
Expand Down
12 changes: 6 additions & 6 deletions web/components/ui/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const Button: React.FC<ButtonProps> = ({
}) => {
const { style } = useTheme();

const baseStyles = "transition-all duration-200 font-bold flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed";
const baseStyles = "transition-all duration-200 font-bold flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2";

const sizeStyles = {
sm: "px-3 py-1.5 text-sm",
Expand All @@ -31,7 +31,7 @@ export const Button: React.FC<ButtonProps> = ({
let themeStyles = "";

if (style === THEMES.NEOBRUTALISM) {
themeStyles = "border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-x-[4px] active:translate-y-[4px] active:shadow-none rounded-none uppercase tracking-wider font-mono";
themeStyles = "border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-x-[4px] active:translate-y-[4px] active:shadow-none rounded-none uppercase tracking-wider font-mono focus-visible:ring-black focus-visible:ring-offset-white";

if (variant === 'primary') themeStyles += " bg-neo-main text-white";
if (variant === 'secondary') themeStyles += " bg-neo-second text-black";
Expand All @@ -42,10 +42,10 @@ export const Button: React.FC<ButtonProps> = ({
// Glassmorphism
themeStyles = "rounded-xl backdrop-blur-md border border-white/20 shadow-lg hover:shadow-xl active:scale-95";

if (variant === 'primary') themeStyles += " bg-gradient-to-r from-blue-500 to-purple-600 text-white shadow-blue-500/30";
if (variant === 'secondary') themeStyles += " bg-white/10 text-white hover:bg-white/20";
if (variant === 'danger') themeStyles += " bg-gradient-to-r from-red-500 to-pink-600 text-white shadow-red-500/30";
if (variant === 'ghost') themeStyles += " bg-transparent border-none shadow-none hover:bg-white/10 text-current";
if (variant === 'primary') themeStyles += " bg-gradient-to-r from-blue-500 to-purple-600 text-white shadow-blue-500/30 focus-visible:ring-blue-400 focus-visible:ring-offset-transparent";
if (variant === 'secondary') themeStyles += " bg-white/10 text-white hover:bg-white/20 focus-visible:ring-white focus-visible:ring-offset-transparent";
if (variant === 'danger') themeStyles += " bg-gradient-to-r from-red-500 to-pink-600 text-white shadow-red-500/30 focus-visible:ring-red-400 focus-visible:ring-offset-transparent";
if (variant === 'ghost') themeStyles += " bg-transparent border-none shadow-none hover:bg-white/10 text-current focus-visible:ring-white/50 focus-visible:ring-offset-transparent";
}

return (
Expand Down
6 changes: 3 additions & 3 deletions web/components/ui/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ export const Input: React.FC<InputProps> = ({ label, error, className = '', type
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
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 ${
className={`absolute right-3 top-1/2 -translate-y-1/2 p-1 rounded-md transition-opacity hover:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 ${
style === THEMES.NEOBRUTALISM
? (mode === 'dark' ? 'text-white opacity-80 focus:ring-white' : 'text-black opacity-60 focus:ring-black')
: 'text-white/60 hover:text-white focus:ring-white/50'
? (mode === 'dark' ? 'text-white opacity-80 focus-visible:ring-white' : 'text-black opacity-60 focus-visible:ring-black')
: 'text-white/60 hover:text-white focus-visible:ring-white/50'
}`}
Comment on lines +49 to 53
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Password toggle focus contrast is too weak in light mode (glass theme).

The non-neobrutal branch uses white-tinted icon and ring for both modes; in light mode this can make the keyboard focus state hard to perceive.

Suggested fix
-                : 'text-white/60 hover:text-white focus-visible:ring-white/50'
+                : mode === 'dark'
+                  ? 'text-white/60 hover:text-white focus-visible:ring-white/50 focus-visible:ring-offset-gray-900'
+                  : 'text-gray-500 hover:text-gray-700 focus-visible:ring-blue-500/60 focus-visible:ring-offset-white'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
className={`absolute right-3 top-1/2 -translate-y-1/2 p-1 rounded-md transition-opacity hover:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 ${
style === THEMES.NEOBRUTALISM
? (mode === 'dark' ? 'text-white opacity-80 focus:ring-white' : 'text-black opacity-60 focus:ring-black')
: 'text-white/60 hover:text-white focus:ring-white/50'
? (mode === 'dark' ? 'text-white opacity-80 focus-visible:ring-white' : 'text-black opacity-60 focus-visible:ring-black')
: 'text-white/60 hover:text-white focus-visible:ring-white/50'
}`}
className={`absolute right-3 top-1/2 -translate-y-1/2 p-1 rounded-md transition-opacity hover:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 ${
style === THEMES.NEOBRUTALISM
? (mode === 'dark' ? 'text-white opacity-80 focus-visible:ring-white' : 'text-black opacity-60 focus-visible:ring-black')
: mode === 'dark'
? 'text-white/60 hover:text-white focus-visible:ring-white/50 focus-visible:ring-offset-gray-900'
: 'text-gray-500 hover:text-gray-700 focus-visible:ring-blue-500/60 focus-visible:ring-offset-white'
}`}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/components/ui/Input.tsx` around lines 49 - 53, The password toggle's
focus contrast is too weak in light mode for non-NEOBRUTALISM themes; update the
className logic in Input.tsx (the element using style === THEMES.NEOBRUTALISM
and mode) so the non-neobrutal branch chooses darker text and ring in light mode
(e.g., when mode === 'light' use a darker text class and darker
focus-visible:ring instead of the current white-tinted classes) while keeping
the existing white-tinted classes for dark mode.

aria-label={showPassword ? "Hide password" : "Show password"}
>
Expand Down
2 changes: 1 addition & 1 deletion web/components/ui/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const Modal: React.FC<ModalProps> = ({ isOpen, onClose, title, children,
{/* Header */}
<div className={`p-6 flex justify-between items-center ${style === THEMES.NEOBRUTALISM ? 'border-b-2 border-black bg-neo-main text-white' : 'border-b border-white/10 bg-white/5'}`}>
<h3 id={titleId} className={`text-2xl font-bold ${style === THEMES.NEOBRUTALISM ? 'uppercase font-mono tracking-tighter' : ''}`}>{title}</h3>
<button type="button" onClick={onClose} className="hover:rotate-90 transition-transform duration-200" aria-label="Close modal">
<button type="button" onClick={onClose} className="hover:rotate-90 transition-transform duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-black dark:focus-visible:ring-white rounded-sm" aria-label="Close modal">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Dark-mode override can hide close-button focus ring in Neobrutalism.

Line 69 uses dark:focus-visible:ring-white globally, but Neobrutalism modal surfaces are white; this can make keyboard focus nearly invisible in dark mode.

Suggested fix
-              <button type="button" onClick={onClose} className="hover:rotate-90 transition-transform duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-black dark:focus-visible:ring-white rounded-sm" aria-label="Close modal">
+              <button
+                type="button"
+                onClick={onClose}
+                className={`hover:rotate-90 transition-transform duration-200 focus:outline-none focus-visible:ring-2 rounded-sm ${
+                  style === THEMES.NEOBRUTALISM ? 'focus-visible:ring-black' : 'focus-visible:ring-white'
+                }`}
+                aria-label="Close modal"
+              >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button type="button" onClick={onClose} className="hover:rotate-90 transition-transform duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-black dark:focus-visible:ring-white rounded-sm" aria-label="Close modal">
<button
type="button"
onClick={onClose}
className={`hover:rotate-90 transition-transform duration-200 focus:outline-none focus-visible:ring-2 rounded-sm ${
style === THEMES.NEOBRUTALISM ? 'focus-visible:ring-black' : 'focus-visible:ring-white'
}`}
aria-label="Close modal"
>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/components/ui/Modal.tsx` at line 69, The close button's focus-visible
ring uses dark:focus-visible:ring-white which can vanish against white
Neobrutalism modal surfaces; update the button in Modal.tsx (the element with
onClick={onClose} and aria-label="Close modal") to use a dark-mode ring that
remains visible (e.g., dark:focus-visible:ring-black or remove the dark:
override so focus-visible:ring-black always applies), ensuring the focus-visible
class remains clearly visible in both themes.

<X size={24} />
</button>
</div>
Expand Down
2 changes: 1 addition & 1 deletion web/components/ui/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const ToastItem: React.FC<{ toast: Toast }> = ({ toast }) => {
<button
type="button"
onClick={() => removeToast(toast.id)}
className="shrink-0 hover:opacity-70 transition-opacity"
className="shrink-0 hover:opacity-70 transition-opacity focus:outline-none focus-visible:ring-2 focus-visible:ring-white rounded-sm"
aria-label="Close notification"
>
<X className="w-4 h-4" />
Expand Down
Loading
Loading