Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
# Palette Journal

## 2024-05-22 - Standardized Button Loading State
**Learning:** The application previously used manual text updates (e.g., "Processing...") for loading states, which causes layout shifts and lacks visual consistency.
**Action:** Implemented a standardized `isLoading` prop on the `Button` component that automatically disables the button and renders a `Spinner`. This pattern should be used for all async actions going forward to ensure a consistent Neobrutalism look and prevent multiple submissions.
7 changes: 7 additions & 0 deletions web/components/ui/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import React from 'react';
import { THEMES } from '../../constants';
import { useTheme } from '../../contexts/ThemeContext';
import { Spinner } from './Spinner';

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'danger' | 'ghost';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
}

export const Button: React.FC<ButtonProps> = ({
children,
variant = 'primary',
size = 'md',
isLoading = false,
className = '',
disabled,
...props
}) => {
const { style } = useTheme();
Expand Down Expand Up @@ -47,8 +51,11 @@ export const Button: React.FC<ButtonProps> = ({
return (
<button
className={`${baseStyles} ${sizeStyles[size]} ${themeStyles} ${className}`}
disabled={isLoading || disabled}
aria-disabled={isLoading || disabled}
{...props}
>
{isLoading && <Spinner size={size === 'sm' ? 16 : 20} />}
{children}
</button>
);
Expand Down
23 changes: 23 additions & 0 deletions web/components/ui/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import { Loader2 } from 'lucide-react';

interface SpinnerProps {
size?: number;
className?: string;
ariaLabel?: string;
}

export const Spinner: React.FC<SpinnerProps> = ({
size = 24,
className = '',
ariaLabel = 'Loading'
}) => {
return (
<Loader2
size={size}
className={`animate-spin ${className}`}
aria-label={ariaLabel}
role="status"
/>
);
};
17 changes: 11 additions & 6 deletions web/pages/Auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button } from '../components/ui/Button';
import { Input } from '../components/ui/Input';
import { Spinner } from '../components/ui/Spinner';
import { THEMES } from '../constants';
import { useAuth } from '../contexts/AuthContext';
import { useTheme } from '../contexts/ThemeContext';
Expand Down Expand Up @@ -170,10 +171,10 @@ export const Auth = () => {
}`}
>
{googleLoading ? (
<div
className="w-5 h-5 border-2 border-black/20 border-t-black rounded-full animate-spin"
role="status"
aria-label="Signing in with Google"
<Spinner
size={20}
className={isNeo ? 'text-black' : 'text-gray-600'}
ariaLabel="Signing in with Google"
/>
) : (
<svg className="w-5 h-5" viewBox="0 0 24 24" role="img" aria-labelledby="google-logo-title">
Expand Down Expand Up @@ -251,8 +252,12 @@ export const Auth = () => {
</motion.div>
)}

<Button type="submit" disabled={loading} className={`w-full py-4 text-lg ${isNeo ? 'rounded-none' : ''}`}>
{loading ? 'Processing...' : isLogin ? 'Log In' : 'Create Account'} <ArrowRight size={20} />
<Button
type="submit"
isLoading={loading}
className={`w-full py-4 text-lg ${isNeo ? 'rounded-none' : ''}`}
>
{isLogin ? 'Log In' : 'Create Account'} <ArrowRight size={20} />
</Button>
</form>

Expand Down
Loading