All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 32m3s
271 lines
8.2 KiB
Markdown
271 lines
8.2 KiB
Markdown
# ADR-012: Frontend Component Library and Design System
|
|
|
|
**Date**: 2025-12-12
|
|
|
|
**Status**: Partially Implemented
|
|
|
|
## Context
|
|
|
|
The frontend is built with React, but there is no formal strategy for component reuse, styling consistency, or UI documentation. As more features are added, this can lead to a fragmented user experience, duplicated effort, and a codebase that is difficult to maintain.
|
|
|
|
## Decision
|
|
|
|
We will establish a formal Design System and Component Library. This will involve using a tool like **Storybook** to develop, document, and test UI components in isolation. It will establish clear guidelines for styling, theming (e.g., dark mode), and accessibility.
|
|
|
|
## Consequences
|
|
|
|
- **Positive**: Ensures a consistent and high-quality user interface. Accelerates frontend development by providing reusable, well-documented components. Improves maintainability and reduces technical debt.
|
|
- **Negative**: Requires an initial investment in setting up Storybook and migrating existing components. Adds a new dependency and a new workflow for frontend development.
|
|
|
|
## Implementation Status
|
|
|
|
### What's Implemented
|
|
|
|
The codebase has a solid foundation for a design system:
|
|
|
|
- ✅ **Tailwind CSS v4.1.17** as the styling solution
|
|
- ✅ **Dark mode** fully implemented with system preference detection
|
|
- ✅ **55 custom icon components** for consistent iconography
|
|
- ✅ **Component organization** with shared vs. feature-specific separation
|
|
- ✅ **Accessibility patterns** with ARIA attributes and focus management
|
|
|
|
### What's Not Yet Implemented
|
|
|
|
- ❌ **Storybook** is not yet installed or configured
|
|
- ❌ **Formal design token documentation** (colors, typography, spacing)
|
|
- ❌ **Visual regression testing** for component changes
|
|
|
|
## Implementation Details
|
|
|
|
### Component Library Structure
|
|
|
|
```text
|
|
src/
|
|
├── components/ # 30+ shared UI components
|
|
│ ├── icons/ # 55 SVG icon components
|
|
│ ├── Header.tsx
|
|
│ ├── Footer.tsx
|
|
│ ├── LoadingSpinner.tsx
|
|
│ ├── ErrorDisplay.tsx
|
|
│ ├── ConfirmationModal.tsx
|
|
│ ├── DarkModeToggle.tsx
|
|
│ ├── StatCard.tsx
|
|
│ ├── PasswordInput.tsx
|
|
│ └── ...
|
|
├── features/ # Feature-specific components
|
|
│ ├── charts/ # PriceChart, PriceHistoryChart
|
|
│ ├── flyer/ # FlyerDisplay, FlyerList, FlyerUploader
|
|
│ ├── shopping/ # ShoppingListComponent, WatchedItemsList
|
|
│ └── voice-assistant/ # VoiceAssistant
|
|
├── layouts/ # Page layouts
|
|
│ └── MainLayout.tsx
|
|
├── pages/ # Page components
|
|
│ └── admin/components/ # Admin-specific components
|
|
└── providers/ # Context providers
|
|
```
|
|
|
|
### Styling Approach
|
|
|
|
**Tailwind CSS** with utility-first classes:
|
|
|
|
```typescript
|
|
// Component example with consistent styling patterns
|
|
<button className="px-4 py-2 bg-brand-primary text-white rounded-lg
|
|
hover:bg-brand-dark transition-colors duration-200
|
|
focus:outline-none focus:ring-2 focus:ring-brand-primary
|
|
focus:ring-offset-2 dark:focus:ring-offset-gray-800">
|
|
Click me
|
|
</button>
|
|
```
|
|
|
|
**Common Utility Patterns**:
|
|
|
|
| Pattern | Classes |
|
|
| ------- | ------- |
|
|
| Card container | `bg-white dark:bg-gray-800 rounded-lg shadow-md p-6` |
|
|
| Primary button | `bg-brand-primary hover:bg-brand-dark text-white rounded-lg px-4 py-2` |
|
|
| Secondary button | `bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200` |
|
|
| Input field | `border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2` |
|
|
| Focus ring | `focus:outline-none focus:ring-2 focus:ring-brand-primary` |
|
|
|
|
### Color System
|
|
|
|
**Brand Colors** (Tailwind theme extensions):
|
|
|
|
- `brand-primary` - Primary brand color (blue/teal)
|
|
- `brand-light` - Lighter variant
|
|
- `brand-dark` - Darker variant for hover states
|
|
- `brand-secondary` - Secondary accent color
|
|
|
|
**Semantic Colors**:
|
|
|
|
- Gray scale: `gray-50` through `gray-950`
|
|
- Error: `red-500`, `red-600`
|
|
- Success: `green-500`, `green-600`
|
|
- Warning: `yellow-500`, `orange-500`
|
|
- Info: `blue-500`, `blue-600`
|
|
|
|
### Dark Mode Implementation
|
|
|
|
Dark mode is fully implemented using Tailwind's `dark:` variant:
|
|
|
|
```typescript
|
|
// Initialization in useAppInitialization hook
|
|
const initializeDarkMode = () => {
|
|
// Priority: user profile > localStorage > system preference
|
|
const stored = localStorage.getItem('darkMode');
|
|
const systemPreference = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
const isDarkMode = stored ? stored === 'true' : systemPreference;
|
|
|
|
document.documentElement.classList.toggle('dark', isDarkMode);
|
|
return isDarkMode;
|
|
};
|
|
```
|
|
|
|
**Usage in components**:
|
|
|
|
```typescript
|
|
<div className="bg-white dark:bg-gray-800 text-gray-900 dark:text-white">
|
|
Content adapts to theme
|
|
</div>
|
|
```
|
|
|
|
### Icon System
|
|
|
|
**55 custom SVG icon components** in `src/components/icons/`:
|
|
|
|
```typescript
|
|
// Icon component pattern
|
|
interface IconProps extends React.SVGProps<SVGSVGElement> {
|
|
title?: string;
|
|
}
|
|
|
|
export const CheckCircleIcon: React.FC<IconProps> = ({ title, ...props }) => (
|
|
<svg {...props} fill="currentColor" viewBox="0 0 24 24">
|
|
{title && <title>{title}</title>}
|
|
<path d="..." />
|
|
</svg>
|
|
);
|
|
```
|
|
|
|
**Usage**:
|
|
|
|
```typescript
|
|
<CheckCircleIcon className="w-5 h-5 text-green-500" title="Success" />
|
|
```
|
|
|
|
**External icons**: Lucide React (`lucide-react` v0.555.0) used for additional icons.
|
|
|
|
### Accessibility Patterns
|
|
|
|
**ARIA Attributes**:
|
|
|
|
```typescript
|
|
// Modal pattern
|
|
<div role="dialog" aria-modal="true" aria-labelledby="modal-title">
|
|
<h2 id="modal-title">Modal Title</h2>
|
|
</div>
|
|
|
|
// Button with label
|
|
<button aria-label="Close modal">
|
|
<XMarkIcon aria-hidden="true" />
|
|
</button>
|
|
|
|
// Loading state
|
|
<div role="status" aria-live="polite">
|
|
<LoadingSpinner />
|
|
</div>
|
|
```
|
|
|
|
**Focus Management**:
|
|
|
|
- Consistent focus rings: `focus:ring-2 focus:ring-brand-primary focus:ring-offset-2`
|
|
- Dark mode offset: `dark:focus:ring-offset-gray-800`
|
|
- No outline: `focus:outline-none` (using ring instead)
|
|
|
|
### State Management
|
|
|
|
**Context Providers** (see ADR-005):
|
|
|
|
| Provider | Purpose |
|
|
| -------- | ------- |
|
|
| `AuthProvider` | Authentication state |
|
|
| `ModalProvider` | Modal open/close state |
|
|
| `FlyersProvider` | Flyer data |
|
|
| `MasterItemsProvider` | Grocery items |
|
|
| `UserDataProvider` | User-specific data |
|
|
|
|
**Provider Hierarchy** in `AppProviders.tsx`:
|
|
|
|
```typescript
|
|
<QueryClientProvider>
|
|
<ModalProvider>
|
|
<AuthProvider>
|
|
<FlyersProvider>
|
|
<MasterItemsProvider>
|
|
<UserDataProvider>
|
|
{children}
|
|
</UserDataProvider>
|
|
</MasterItemsProvider>
|
|
</FlyersProvider>
|
|
</AuthProvider>
|
|
</ModalProvider>
|
|
</QueryClientProvider>
|
|
```
|
|
|
|
## Key Files
|
|
|
|
- `tailwind.config.js` - Tailwind CSS configuration
|
|
- `src/index.css` - Tailwind CSS entry point
|
|
- `src/components/` - Shared UI components
|
|
- `src/components/icons/` - Icon component library (55 icons)
|
|
- `src/providers/AppProviders.tsx` - Context provider composition
|
|
- `src/hooks/useAppInitialization.ts` - Dark mode initialization
|
|
|
|
## Component Guidelines
|
|
|
|
### When to Create Shared Components
|
|
|
|
Create a shared component in `src/components/` when:
|
|
|
|
1. Used in 3+ places across the application
|
|
2. Represents a reusable UI pattern (buttons, cards, modals)
|
|
3. Has consistent styling/behavior requirements
|
|
|
|
### Naming Conventions
|
|
|
|
- **Components**: PascalCase (`LoadingSpinner.tsx`)
|
|
- **Icons**: PascalCase with `Icon` suffix (`CheckCircleIcon.tsx`)
|
|
- **Hooks**: camelCase with `use` prefix (`useModal.ts`)
|
|
- **Contexts**: PascalCase with `Context` suffix (`AuthContext.tsx`)
|
|
|
|
### Styling Guidelines
|
|
|
|
1. Use Tailwind utility classes exclusively
|
|
2. Include dark mode variants for all colors: `bg-white dark:bg-gray-800`
|
|
3. Add focus states for interactive elements
|
|
4. Use semantic color names from the design system
|
|
|
|
## Future Enhancements (Storybook Setup)
|
|
|
|
To complete ADR-012 implementation:
|
|
|
|
1. **Install Storybook**:
|
|
|
|
```bash
|
|
npx storybook@latest init
|
|
```
|
|
|
|
2. **Create stories for core components**:
|
|
- Button variants
|
|
- Form inputs (PasswordInput, etc.)
|
|
- Modal components
|
|
- Loading states
|
|
- Icon showcase
|
|
|
|
3. **Add visual regression testing** with Chromatic or Percy
|
|
|
|
4. **Document design tokens** formally in Storybook
|
|
|
|
5. **Create component composition guidelines**
|