docs: add comprehensive Phase 2 documentation
- Create detailed README.md with quick start and demo accounts - Add complete UI primitives documentation with examples - Document architecture patterns and design decisions - Update REBUILD_PLAN.md marking Phase 2 as complete - Include component usage guides and testing documentation - Document accessibility compliance and performance considerations Documentation provides complete developer onboarding experience with practical examples and architectural guidance. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
746
reactrebuild0825/docs/architecture.md
Normal file
746
reactrebuild0825/docs/architecture.md
Normal file
@@ -0,0 +1,746 @@
|
||||
# Architecture Documentation
|
||||
|
||||
Comprehensive guide to the Black Canyon Tickets React rebuild architecture, design patterns, and technical decisions.
|
||||
|
||||
## Project Structure Overview
|
||||
|
||||
```
|
||||
src/
|
||||
├── components/
|
||||
│ ├── ui/ # Design system primitives
|
||||
│ ├── layout/ # Application layout system
|
||||
│ ├── auth/ # Authentication components
|
||||
│ ├── loading/ # Loading states and skeletons
|
||||
│ ├── errors/ # Error boundaries and fallbacks
|
||||
│ ├── events/ # Event domain components
|
||||
│ ├── tickets/ # Ticketing domain components
|
||||
│ ├── checkout/ # Purchase flow components
|
||||
│ ├── billing/ # Payment and fee components
|
||||
│ └── scanning/ # QR scanning components
|
||||
├── pages/ # Route-level components
|
||||
├── contexts/ # React Context providers
|
||||
├── hooks/ # Custom React hooks
|
||||
├── types/ # TypeScript type definitions
|
||||
├── design-tokens/ # Design system configuration
|
||||
├── styles/ # CSS files and utilities
|
||||
└── utils/ # Utility functions
|
||||
```
|
||||
|
||||
## Architectural Principles
|
||||
|
||||
### 1. Component Composition
|
||||
|
||||
**Philosophy**: Build complex UIs by composing smaller, focused components rather than creating monolithic components.
|
||||
|
||||
```tsx
|
||||
// Bad: Monolithic component
|
||||
function EventPage({ eventId }) {
|
||||
return (
|
||||
<div className="event-page">
|
||||
<header>...</header>
|
||||
<nav>...</nav>
|
||||
<main>
|
||||
<div className="event-details">...</div>
|
||||
<div className="ticket-selection">...</div>
|
||||
<div className="purchase-form">...</div>
|
||||
</main>
|
||||
<footer>...</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Good: Composed from smaller components
|
||||
function EventPage({ eventId }) {
|
||||
return (
|
||||
<AppLayout>
|
||||
<EventDetails eventId={eventId} />
|
||||
<TicketSelection eventId={eventId} />
|
||||
<PurchaseFlow eventId={eventId} />
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Design Token System
|
||||
|
||||
**Philosophy**: Centralize design decisions in a token system that enables consistent theming and maintainable styles.
|
||||
|
||||
```
|
||||
design-tokens/
|
||||
├── base.json # Core design tokens
|
||||
└── themes/
|
||||
├── light.json # Light theme overrides
|
||||
└── dark.json # Dark theme overrides
|
||||
```
|
||||
|
||||
**Token Categories**:
|
||||
- **Colors**: Semantic color system (primary, surface, text, border)
|
||||
- **Typography**: Font sizes, line heights, font families
|
||||
- **Spacing**: Consistent spacing scale (1-20)
|
||||
- **Border Radius**: Corner radius values (sm, md, lg, xl, 2xl)
|
||||
- **Shadows**: Elevation system with multiple levels
|
||||
|
||||
### 3. Type-Driven Development
|
||||
|
||||
**Philosophy**: Use TypeScript's type system to catch errors early and provide excellent developer experience.
|
||||
|
||||
```typescript
|
||||
// Comprehensive type definitions
|
||||
interface Event {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
date: string;
|
||||
venue: string;
|
||||
organization: Organization;
|
||||
ticketTypes: TicketType[];
|
||||
status: EventStatus;
|
||||
}
|
||||
|
||||
// Union types for controlled values
|
||||
type EventStatus = 'draft' | 'published' | 'active' | 'completed' | 'cancelled';
|
||||
type UserRole = 'user' | 'admin' | 'super_admin';
|
||||
|
||||
// Strict component props
|
||||
interface EventCardProps {
|
||||
event: Event;
|
||||
showActions?: boolean;
|
||||
onEdit?: (event: Event) => void;
|
||||
onDelete?: (eventId: string) => void;
|
||||
}
|
||||
```
|
||||
|
||||
## Design Patterns
|
||||
|
||||
### 1. Compound Components
|
||||
|
||||
**Use Case**: Complex components with multiple related parts that work together.
|
||||
|
||||
```tsx
|
||||
// Card component with sub-components
|
||||
function Card({ children, variant = 'default', ...props }) {
|
||||
return (
|
||||
<div className={cardVariants[variant]} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Card.Header = function CardHeader({ children, className = '', ...props }) {
|
||||
return (
|
||||
<div className={`card-header ${className}`} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Card.Content = function CardContent({ children, className = '', ...props }) {
|
||||
return (
|
||||
<div className={`card-content ${className}`} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Usage
|
||||
<Card variant="elevated">
|
||||
<Card.Header>
|
||||
<h3>Event Details</h3>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<EventInfo event={event} />
|
||||
</Card.Content>
|
||||
</Card>
|
||||
```
|
||||
|
||||
### 2. Render Props Pattern
|
||||
|
||||
**Use Case**: Sharing stateful logic between components while maintaining flexibility in rendering.
|
||||
|
||||
```tsx
|
||||
// ProtectedRoute component using render props
|
||||
function ProtectedRoute({
|
||||
permission,
|
||||
fallback,
|
||||
children
|
||||
}: ProtectedRouteProps) {
|
||||
const { user, hasPermission } = useAuth();
|
||||
|
||||
if (!user) {
|
||||
return <Navigate to="/login" />;
|
||||
}
|
||||
|
||||
if (permission && !hasPermission(permission)) {
|
||||
return fallback || <AccessDenied />;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
// Usage
|
||||
<ProtectedRoute
|
||||
permission="admin"
|
||||
fallback={<AdminAccessRequired />}
|
||||
>
|
||||
<AdminDashboard />
|
||||
</ProtectedRoute>
|
||||
```
|
||||
|
||||
### 3. Custom Hook Pattern
|
||||
|
||||
**Use Case**: Extracting and reusing stateful logic across components.
|
||||
|
||||
```tsx
|
||||
// useAuth hook encapsulates authentication logic
|
||||
function useAuth() {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const login = useCallback(async (credentials: LoginCredentials) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const user = await authService.login(credentials);
|
||||
setUser(user);
|
||||
return { success: true, user };
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const logout = useCallback(() => {
|
||||
setUser(null);
|
||||
authService.logout();
|
||||
}, []);
|
||||
|
||||
const hasPermission = useCallback((permission: Permission) => {
|
||||
return user?.permissions.includes(permission) ?? false;
|
||||
}, [user]);
|
||||
|
||||
return {
|
||||
user,
|
||||
loading,
|
||||
login,
|
||||
logout,
|
||||
hasPermission,
|
||||
isAuthenticated: !!user,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## State Management Strategy
|
||||
|
||||
### 1. Component State (useState)
|
||||
|
||||
**Use For**: Local component state that doesn't need to be shared.
|
||||
|
||||
```tsx
|
||||
function TicketSelector({ ticketType }) {
|
||||
const [quantity, setQuantity] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<QuantitySelector value={quantity} onChange={setQuantity} />
|
||||
<Button loading={loading} onClick={handlePurchase}>
|
||||
Add to Cart
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Context State (React Context)
|
||||
|
||||
**Use For**: Application-wide state that needs to be shared across many components.
|
||||
|
||||
```tsx
|
||||
// Theme context for global theme management
|
||||
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
||||
|
||||
function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
const [theme, setTheme] = useState<Theme>('dark');
|
||||
|
||||
const toggleTheme = useCallback(() => {
|
||||
setTheme(prev => prev === 'light' ? 'dark' : 'light');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, setTheme, toggleTheme }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
// Auth context for user authentication state
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const auth = useAuth(); // Custom hook with auth logic
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={auth}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. URL State (React Router)
|
||||
|
||||
**Use For**: State that should be reflected in the URL for bookmarking and sharing.
|
||||
|
||||
```tsx
|
||||
// Search and filter state in URL parameters
|
||||
function EventsPage() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const search = searchParams.get('search') || '';
|
||||
const category = searchParams.get('category') || 'all';
|
||||
|
||||
const updateSearch = (newSearch: string) => {
|
||||
setSearchParams(prev => {
|
||||
prev.set('search', newSearch);
|
||||
return prev;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SearchInput value={search} onChange={updateSearch} />
|
||||
<CategoryFilter value={category} onChange={updateCategory} />
|
||||
<EventList search={search} category={category} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling Architecture
|
||||
|
||||
### 1. Error Boundaries
|
||||
|
||||
**Strategy**: Catch React component errors and provide graceful fallbacks.
|
||||
|
||||
```tsx
|
||||
// App-level error boundary
|
||||
class AppErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
console.error('Error caught by boundary:', error, errorInfo);
|
||||
// Report to error tracking service
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return this.props.fallback || <ErrorFallback error={this.state.error} />;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
<AppErrorBoundary fallback={<GlobalErrorFallback />}>
|
||||
<App />
|
||||
</AppErrorBoundary>
|
||||
```
|
||||
|
||||
### 2. Loading States
|
||||
|
||||
**Strategy**: Provide consistent loading experiences across the application.
|
||||
|
||||
```tsx
|
||||
// Suspense for route-level loading
|
||||
function App() {
|
||||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/" element={
|
||||
<Suspense fallback={<RouteSuspense />}>
|
||||
<HomePage />
|
||||
</Suspense>
|
||||
} />
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
// Component-level loading with Skeleton
|
||||
function EventCard({ eventId }: { eventId: string }) {
|
||||
const { event, loading, error } = useEvent(eventId);
|
||||
|
||||
if (loading) return <EventCardSkeleton />;
|
||||
if (error) return <EventCardError error={error} />;
|
||||
if (!event) return <EventNotFound />;
|
||||
|
||||
return <EventCardContent event={event} />;
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication Architecture
|
||||
|
||||
### 1. Mock Authentication System
|
||||
|
||||
**Design**: Simulates real authentication without external dependencies.
|
||||
|
||||
```typescript
|
||||
// Mock auth service
|
||||
class MockAuthService {
|
||||
private static users: User[] = [
|
||||
{
|
||||
id: '1',
|
||||
email: 'demo@blackcanyontickets.com',
|
||||
role: 'user',
|
||||
permissions: ['events:read', 'tickets:purchase']
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
email: 'admin@blackcanyontickets.com',
|
||||
role: 'admin',
|
||||
permissions: ['events:read', 'events:write', 'users:read']
|
||||
}
|
||||
];
|
||||
|
||||
async login(credentials: LoginCredentials): Promise<User> {
|
||||
const user = this.users.find(u => u.email === credentials.email);
|
||||
if (!user) throw new Error('Invalid credentials');
|
||||
|
||||
// Simulate API delay
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Store in localStorage for persistence
|
||||
localStorage.setItem('auth_user', JSON.stringify(user));
|
||||
return user;
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
localStorage.removeItem('auth_user');
|
||||
}
|
||||
|
||||
getCurrentUser(): User | null {
|
||||
const stored = localStorage.getItem('auth_user');
|
||||
return stored ? JSON.parse(stored) : null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Permission System
|
||||
|
||||
**Design**: Role-based access control with granular permissions.
|
||||
|
||||
```typescript
|
||||
// Permission definitions
|
||||
type Permission =
|
||||
| 'events:read' | 'events:write' | 'events:delete'
|
||||
| 'tickets:read' | 'tickets:purchase' | 'tickets:scan'
|
||||
| 'users:read' | 'users:write'
|
||||
| 'analytics:read' | 'settings:write';
|
||||
|
||||
// Role definitions
|
||||
const ROLE_PERMISSIONS: Record<UserRole, Permission[]> = {
|
||||
user: [
|
||||
'events:read',
|
||||
'tickets:read',
|
||||
'tickets:purchase'
|
||||
],
|
||||
admin: [
|
||||
'events:read', 'events:write',
|
||||
'tickets:read', 'tickets:scan',
|
||||
'users:read',
|
||||
'analytics:read'
|
||||
],
|
||||
super_admin: [
|
||||
'events:read', 'events:write', 'events:delete',
|
||||
'tickets:read', 'tickets:scan',
|
||||
'users:read', 'users:write',
|
||||
'analytics:read',
|
||||
'settings:write'
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
## Component Testing Strategy
|
||||
|
||||
### 1. Unit Testing
|
||||
|
||||
**Focus**: Individual component behavior and props handling.
|
||||
|
||||
```typescript
|
||||
// Button component tests
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
|
||||
describe('Button Component', () => {
|
||||
test('renders with correct variant styles', () => {
|
||||
render(<Button variant="primary">Click me</Button>);
|
||||
const button = screen.getByRole('button');
|
||||
expect(button).toHaveClass('bg-primary');
|
||||
});
|
||||
|
||||
test('handles click events', () => {
|
||||
const handleClick = jest.fn();
|
||||
render(<Button onClick={handleClick}>Click me</Button>);
|
||||
|
||||
fireEvent.click(screen.getByRole('button'));
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('displays loading state', () => {
|
||||
render(<Button loading>Loading</Button>);
|
||||
expect(screen.getByRole('button')).toBeDisabled();
|
||||
expect(screen.getByText('Loading')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 2. Integration Testing with Playwright
|
||||
|
||||
**Focus**: End-to-end user workflows and cross-component interactions.
|
||||
|
||||
```typescript
|
||||
// Authentication flow test
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('user can log in and access dashboard', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
|
||||
// Fill login form
|
||||
await page.fill('[data-testid="email-input"]', 'demo@blackcanyontickets.com');
|
||||
await page.fill('[data-testid="password-input"]', 'demo123');
|
||||
|
||||
// Submit and verify redirect
|
||||
await page.click('[data-testid="login-button"]');
|
||||
await expect(page).toHaveURL('/dashboard');
|
||||
|
||||
// Verify user is authenticated
|
||||
await expect(page.getByText('Welcome back')).toBeVisible();
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Visual Regression Testing
|
||||
|
||||
**Focus**: Ensure UI changes don't break visual design.
|
||||
|
||||
```typescript
|
||||
// Visual tests with Playwright
|
||||
test('homepage renders correctly', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page).toHaveScreenshot('homepage.png');
|
||||
});
|
||||
|
||||
test('login form in both themes', async ({ page }) => {
|
||||
// Test light theme
|
||||
await page.goto('/login');
|
||||
await page.getByTestId('theme-toggle').click(); // Switch to light
|
||||
await expect(page.getByTestId('login-form')).toHaveScreenshot('login-light.png');
|
||||
|
||||
// Test dark theme
|
||||
await page.getByTestId('theme-toggle').click(); // Switch to dark
|
||||
await expect(page.getByTestId('login-form')).toHaveScreenshot('login-dark.png');
|
||||
});
|
||||
```
|
||||
|
||||
## Performance Architecture
|
||||
|
||||
### 1. Code Splitting
|
||||
|
||||
**Strategy**: Split code at route boundaries and for large dependencies.
|
||||
|
||||
```tsx
|
||||
// Route-based code splitting
|
||||
const HomePage = lazy(() => import('@/pages/HomePage'));
|
||||
const DashboardPage = lazy(() => import('@/pages/DashboardPage'));
|
||||
const EventsPage = lazy(() => import('@/pages/EventsPage'));
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/" element={
|
||||
<Suspense fallback={<RouteSuspense />}>
|
||||
<HomePage />
|
||||
</Suspense>
|
||||
} />
|
||||
<Route path="/dashboard" element={
|
||||
<Suspense fallback={<RouteSuspense />}>
|
||||
<DashboardPage />
|
||||
</Suspense>
|
||||
} />
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Component Optimization
|
||||
|
||||
**Strategy**: Use React.memo and useMemo to prevent unnecessary re-renders.
|
||||
|
||||
```tsx
|
||||
// Memoized component to prevent re-renders
|
||||
const EventCard = memo(function EventCard({ event, onEdit }: EventCardProps) {
|
||||
const formattedDate = useMemo(() => {
|
||||
return formatDate(event.date);
|
||||
}, [event.date]);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<h3>{event.title}</h3>
|
||||
<p>{formattedDate}</p>
|
||||
<Button onClick={() => onEdit?.(event)}>Edit</Button>
|
||||
</Card>
|
||||
);
|
||||
});
|
||||
|
||||
// Optimized list rendering
|
||||
function EventList({ events }: { events: Event[] }) {
|
||||
const sortedEvents = useMemo(() => {
|
||||
return [...events].sort((a, b) =>
|
||||
new Date(a.date).getTime() - new Date(b.date).getTime()
|
||||
);
|
||||
}, [events]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{sortedEvents.map(event => (
|
||||
<EventCard key={event.id} event={event} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Accessibility Architecture
|
||||
|
||||
### 1. Semantic HTML Foundation
|
||||
|
||||
**Strategy**: Use semantic HTML elements that provide built-in accessibility.
|
||||
|
||||
```tsx
|
||||
// Good: Semantic structure
|
||||
function EventForm() {
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<fieldset>
|
||||
<legend>Event Details</legend>
|
||||
|
||||
<label htmlFor="title">Event Title</label>
|
||||
<input id="title" type="text" required />
|
||||
|
||||
<label htmlFor="description">Description</label>
|
||||
<textarea id="description" />
|
||||
</fieldset>
|
||||
|
||||
<button type="submit">Create Event</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. ARIA Enhancement
|
||||
|
||||
**Strategy**: Enhance semantic HTML with ARIA attributes where needed.
|
||||
|
||||
```tsx
|
||||
// Complex component with ARIA
|
||||
function Select({ options, value, onChange, label }: SelectProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [focusedIndex, setFocusedIndex] = useState(-1);
|
||||
|
||||
return (
|
||||
<div className="select-container">
|
||||
<label id="select-label">{label}</label>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
aria-labelledby="select-label"
|
||||
aria-expanded={isOpen}
|
||||
aria-haspopup="listbox"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
{value || 'Select option'}
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<ul role="listbox" aria-labelledby="select-label">
|
||||
{options.map((option, index) => (
|
||||
<li
|
||||
key={option.value}
|
||||
role="option"
|
||||
aria-selected={value === option.value}
|
||||
className={focusedIndex === index ? 'focused' : ''}
|
||||
onClick={() => onChange(option.value)}
|
||||
>
|
||||
{option.label}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Build and Deployment Architecture
|
||||
|
||||
### 1. Vite Configuration
|
||||
|
||||
**Strategy**: Optimize builds for production with proper chunk splitting.
|
||||
|
||||
```typescript
|
||||
// vite.config.ts
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
vendor: ['react', 'react-dom'],
|
||||
router: ['react-router-dom'],
|
||||
ui: ['lucide-react']
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [tailwindcss, autoprefixer]
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 2. Environment Configuration
|
||||
|
||||
**Strategy**: Support multiple environments with appropriate configurations.
|
||||
|
||||
```typescript
|
||||
// Environment-specific configuration
|
||||
const config = {
|
||||
development: {
|
||||
apiUrl: 'http://localhost:3001',
|
||||
enableDevTools: true,
|
||||
logLevel: 'debug'
|
||||
},
|
||||
production: {
|
||||
apiUrl: 'https://api.blackcanyontickets.com',
|
||||
enableDevTools: false,
|
||||
logLevel: 'error'
|
||||
}
|
||||
};
|
||||
|
||||
export default config[process.env.NODE_ENV || 'development'];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Architecture designed with CrispyGoat principles - scalable, maintainable, and developer-friendly.**
|
||||
629
reactrebuild0825/docs/ui-primitives.md
Normal file
629
reactrebuild0825/docs/ui-primitives.md
Normal file
@@ -0,0 +1,629 @@
|
||||
# UI Primitives Documentation
|
||||
|
||||
A comprehensive guide to the Black Canyon Tickets component library featuring production-ready UI primitives with WCAG AA accessibility compliance.
|
||||
|
||||
## Design System Foundation
|
||||
|
||||
### Design Tokens Integration
|
||||
|
||||
All components use CSS custom properties from our design token system:
|
||||
|
||||
```css
|
||||
/* Automatically available in all components */
|
||||
--color-primary-50 through --color-primary-950
|
||||
--color-surface-primary, --color-surface-secondary
|
||||
--color-text-primary, --color-text-secondary
|
||||
--font-size-xs through --font-size-4xl
|
||||
--spacing-1 through --spacing-20
|
||||
--border-radius-sm through --border-radius-2xl
|
||||
```
|
||||
|
||||
### Theme Support
|
||||
|
||||
Every component automatically supports light and dark themes without additional configuration.
|
||||
|
||||
## Core UI Primitives
|
||||
|
||||
### Button Component
|
||||
|
||||
Production-ready button with multiple variants, sizes, and states.
|
||||
|
||||
#### Props Interface
|
||||
|
||||
```typescript
|
||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
|
||||
size?: 'sm' | 'md' | 'lg' | 'xl';
|
||||
loading?: boolean;
|
||||
leftIcon?: React.ReactNode;
|
||||
rightIcon?: React.ReactNode;
|
||||
fullWidth?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
#### Usage Examples
|
||||
|
||||
```tsx
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { PlusIcon, ArrowRightIcon } from 'lucide-react';
|
||||
|
||||
// Basic variants
|
||||
<Button variant="primary">Primary Action</Button>
|
||||
<Button variant="secondary">Secondary Action</Button>
|
||||
<Button variant="outline">Outline Button</Button>
|
||||
<Button variant="ghost">Ghost Button</Button>
|
||||
<Button variant="danger">Delete Item</Button>
|
||||
|
||||
// Sizes
|
||||
<Button size="sm">Small</Button>
|
||||
<Button size="md">Medium (default)</Button>
|
||||
<Button size="lg">Large</Button>
|
||||
<Button size="xl">Extra Large</Button>
|
||||
|
||||
// With icons
|
||||
<Button leftIcon={<PlusIcon size={16} />}>
|
||||
Add Event
|
||||
</Button>
|
||||
|
||||
<Button rightIcon={<ArrowRightIcon size={16} />}>
|
||||
Continue
|
||||
</Button>
|
||||
|
||||
// Loading state
|
||||
<Button loading>Processing...</Button>
|
||||
|
||||
// Full width
|
||||
<Button fullWidth variant="primary">
|
||||
Full Width Button
|
||||
</Button>
|
||||
```
|
||||
|
||||
#### Accessibility Features
|
||||
|
||||
- **Keyboard Navigation**: Full keyboard support with Enter/Space activation
|
||||
- **Focus Management**: Visible focus indicators with proper contrast
|
||||
- **Screen Reader**: Proper button semantics and loading state announcements
|
||||
- **Touch Targets**: Minimum 44px touch target size on mobile
|
||||
|
||||
---
|
||||
|
||||
### Input Component
|
||||
|
||||
Comprehensive form input with validation, labels, and help text.
|
||||
|
||||
#### Props Interface
|
||||
|
||||
```typescript
|
||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
label?: string;
|
||||
helperText?: string;
|
||||
error?: string;
|
||||
leftIcon?: React.ReactNode;
|
||||
rightIcon?: React.ReactNode;
|
||||
variant?: 'default' | 'filled';
|
||||
}
|
||||
```
|
||||
|
||||
#### Usage Examples
|
||||
|
||||
```tsx
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { MailIcon, EyeIcon, EyeOffIcon } from 'lucide-react';
|
||||
|
||||
// Basic input with label
|
||||
<Input
|
||||
label="Email Address"
|
||||
type="email"
|
||||
placeholder="enter your email"
|
||||
required
|
||||
/>
|
||||
|
||||
// With helper text
|
||||
<Input
|
||||
label="Password"
|
||||
type="password"
|
||||
helperText="Must be at least 8 characters"
|
||||
/>
|
||||
|
||||
// With error state
|
||||
<Input
|
||||
label="Username"
|
||||
error="Username is already taken"
|
||||
value={username}
|
||||
onChange={setUsername}
|
||||
/>
|
||||
|
||||
// With icons
|
||||
<Input
|
||||
label="Search Events"
|
||||
leftIcon={<SearchIcon size={16} />}
|
||||
placeholder="Search by name or venue"
|
||||
/>
|
||||
|
||||
// Filled variant
|
||||
<Input
|
||||
variant="filled"
|
||||
label="Event Description"
|
||||
placeholder="Describe your event"
|
||||
/>
|
||||
```
|
||||
|
||||
#### Validation Integration
|
||||
|
||||
```tsx
|
||||
// With React Hook Form
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
const { register, formState: { errors } } = useForm();
|
||||
|
||||
<Input
|
||||
label="Email"
|
||||
{...register('email', {
|
||||
required: 'Email is required',
|
||||
pattern: {
|
||||
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
||||
message: 'Invalid email address'
|
||||
}
|
||||
})}
|
||||
error={errors.email?.message}
|
||||
/>
|
||||
```
|
||||
|
||||
#### Accessibility Features
|
||||
|
||||
- **Label Association**: Proper label-input association with unique IDs
|
||||
- **Error Announcement**: Screen reader announcements for validation errors
|
||||
- **Required Indicators**: Visual and semantic required field indicators
|
||||
- **Keyboard Navigation**: Full keyboard support with Tab navigation
|
||||
|
||||
---
|
||||
|
||||
### Select Component
|
||||
|
||||
Accessible dropdown selection with search and custom styling.
|
||||
|
||||
#### Props Interface
|
||||
|
||||
```typescript
|
||||
interface SelectOption {
|
||||
value: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface SelectProps {
|
||||
options: SelectOption[];
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
placeholder?: string;
|
||||
label?: string;
|
||||
error?: string;
|
||||
disabled?: boolean;
|
||||
required?: boolean;
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
```
|
||||
|
||||
#### Usage Examples
|
||||
|
||||
```tsx
|
||||
import { Select } from '@/components/ui/Select';
|
||||
|
||||
const ticketTypes = [
|
||||
{ value: 'general', label: 'General Admission' },
|
||||
{ value: 'vip', label: 'VIP Access' },
|
||||
{ value: 'student', label: 'Student Discount' }
|
||||
];
|
||||
|
||||
// Basic select
|
||||
<Select
|
||||
label="Ticket Type"
|
||||
options={ticketTypes}
|
||||
placeholder="Choose ticket type"
|
||||
onChange={setSelectedType}
|
||||
/>
|
||||
|
||||
// With error state
|
||||
<Select
|
||||
label="Event Category"
|
||||
options={categories}
|
||||
error="Please select a category"
|
||||
required
|
||||
/>
|
||||
|
||||
// Disabled option
|
||||
const venues = [
|
||||
{ value: 'main', label: 'Main Hall' },
|
||||
{ value: 'ballroom', label: 'Grand Ballroom' },
|
||||
{ value: 'outdoor', label: 'Outdoor Stage', disabled: true }
|
||||
];
|
||||
```
|
||||
|
||||
#### Accessibility Features
|
||||
|
||||
- **Keyboard Navigation**: Arrow keys, Enter, Escape, Tab support
|
||||
- **Screen Reader**: Proper combobox semantics with expanded/collapsed states
|
||||
- **Focus Management**: Visible focus indicators for options
|
||||
- **ARIA Labels**: Comprehensive ARIA labeling for complex interactions
|
||||
|
||||
---
|
||||
|
||||
### Card Component
|
||||
|
||||
Flexible container component with multiple variants and compositional API.
|
||||
|
||||
#### Props Interface
|
||||
|
||||
```typescript
|
||||
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
variant?: 'default' | 'outlined' | 'elevated';
|
||||
padding?: 'none' | 'sm' | 'md' | 'lg';
|
||||
}
|
||||
```
|
||||
|
||||
#### Usage Examples
|
||||
|
||||
```tsx
|
||||
import { Card } from '@/components/ui/Card';
|
||||
|
||||
// Basic card
|
||||
<Card>
|
||||
<h3>Event Details</h3>
|
||||
<p>Join us for an unforgettable evening</p>
|
||||
</Card>
|
||||
|
||||
// Card variants
|
||||
<Card variant="outlined" padding="lg">
|
||||
<Card.Header>
|
||||
<h2>Premium Event</h2>
|
||||
<Badge variant="success">Available</Badge>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<p>Exclusive access to premium seating</p>
|
||||
</Card.Content>
|
||||
<Card.Footer>
|
||||
<Button variant="primary">Purchase Tickets</Button>
|
||||
</Card.Footer>
|
||||
</Card>
|
||||
|
||||
// Elevated card for important content
|
||||
<Card variant="elevated" className="hover:shadow-lg transition-shadow">
|
||||
<EventCard event={event} />
|
||||
</Card>
|
||||
```
|
||||
|
||||
#### Compositional API
|
||||
|
||||
```tsx
|
||||
// Using sub-components for structured layout
|
||||
<Card>
|
||||
<Card.Header className="border-b">
|
||||
<div className="flex justify-between items-center">
|
||||
<h3>Order Summary</h3>
|
||||
<Button variant="ghost" size="sm">Edit</Button>
|
||||
</div>
|
||||
</Card.Header>
|
||||
|
||||
<Card.Content className="space-y-4">
|
||||
<OrderLineItem />
|
||||
<OrderLineItem />
|
||||
</Card.Content>
|
||||
|
||||
<Card.Footer className="border-t bg-surface-secondary">
|
||||
<div className="flex justify-between font-semibold">
|
||||
<span>Total</span>
|
||||
<span>$149.00</span>
|
||||
</div>
|
||||
</Card.Footer>
|
||||
</Card>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Alert Component
|
||||
|
||||
Status messages and notifications with multiple severity levels.
|
||||
|
||||
#### Props Interface
|
||||
|
||||
```typescript
|
||||
interface AlertProps {
|
||||
variant?: 'info' | 'success' | 'warning' | 'error';
|
||||
title?: string;
|
||||
children: React.ReactNode;
|
||||
icon?: React.ReactNode;
|
||||
dismissible?: boolean;
|
||||
onDismiss?: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
#### Usage Examples
|
||||
|
||||
```tsx
|
||||
import { Alert } from '@/components/ui/Alert';
|
||||
import { CheckCircleIcon, AlertTriangleIcon } from 'lucide-react';
|
||||
|
||||
// Success alert
|
||||
<Alert variant="success" title="Order Confirmed">
|
||||
Your tickets have been purchased successfully. Check your email for confirmation.
|
||||
</Alert>
|
||||
|
||||
// Warning alert
|
||||
<Alert variant="warning" title="Limited Availability">
|
||||
Only 3 tickets remaining for this event.
|
||||
</Alert>
|
||||
|
||||
// Error alert with custom icon
|
||||
<Alert
|
||||
variant="error"
|
||||
icon={<AlertTriangleIcon size={20} />}
|
||||
dismissible
|
||||
onDismiss={hideAlert}
|
||||
>
|
||||
Payment processing failed. Please try again or contact support.
|
||||
</Alert>
|
||||
|
||||
// Info alert without title
|
||||
<Alert variant="info">
|
||||
Event details have been updated. Refresh to see changes.
|
||||
</Alert>
|
||||
```
|
||||
|
||||
#### Accessibility Features
|
||||
|
||||
- **ARIA Roles**: Proper alert/alertdialog roles for screen readers
|
||||
- **Color Independence**: Icons and text convey meaning beyond color
|
||||
- **Focus Management**: Dismissible alerts receive appropriate focus
|
||||
- **Live Regions**: Dynamic alerts announced to screen readers
|
||||
|
||||
---
|
||||
|
||||
### Badge Component
|
||||
|
||||
Small status indicators and labels with semantic meaning.
|
||||
|
||||
#### Props Interface
|
||||
|
||||
```typescript
|
||||
interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
|
||||
variant?: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'error';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
pill?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
#### Usage Examples
|
||||
|
||||
```tsx
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
|
||||
// Status badges
|
||||
<Badge variant="success">Available</Badge>
|
||||
<Badge variant="warning">Limited</Badge>
|
||||
<Badge variant="error">Sold Out</Badge>
|
||||
|
||||
// Different sizes
|
||||
<Badge size="sm">New</Badge>
|
||||
<Badge size="md">Featured</Badge>
|
||||
<Badge size="lg">Premium</Badge>
|
||||
|
||||
// Pill style
|
||||
<Badge variant="primary" pill>VIP Access</Badge>
|
||||
|
||||
// In context with event cards
|
||||
<EventCard>
|
||||
<div className="flex justify-between items-start">
|
||||
<h3>Concert Night</h3>
|
||||
<Badge variant="success">Available</Badge>
|
||||
</div>
|
||||
</EventCard>
|
||||
```
|
||||
|
||||
## Component Composition Patterns
|
||||
|
||||
### Form Composition
|
||||
|
||||
```tsx
|
||||
import { Card, Input, Select, Button, Alert } from '@/components/ui';
|
||||
|
||||
function EventForm() {
|
||||
return (
|
||||
<Card variant="outlined" padding="lg">
|
||||
<Card.Header>
|
||||
<h2>Create New Event</h2>
|
||||
</Card.Header>
|
||||
|
||||
<Card.Content className="space-y-6">
|
||||
{error && (
|
||||
<Alert variant="error" dismissible onDismiss={clearError}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Input
|
||||
label="Event Name"
|
||||
placeholder="Enter event name"
|
||||
required
|
||||
/>
|
||||
|
||||
<Select
|
||||
label="Event Category"
|
||||
options={categories}
|
||||
placeholder="Select category"
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Description"
|
||||
multiline
|
||||
rows={4}
|
||||
placeholder="Describe your event"
|
||||
/>
|
||||
</Card.Content>
|
||||
|
||||
<Card.Footer>
|
||||
<div className="flex gap-3">
|
||||
<Button variant="outline" fullWidth>
|
||||
Save Draft
|
||||
</Button>
|
||||
<Button variant="primary" fullWidth>
|
||||
Publish Event
|
||||
</Button>
|
||||
</div>
|
||||
</Card.Footer>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Status Display Pattern
|
||||
|
||||
```tsx
|
||||
import { Badge, Alert, Button } from '@/components/ui';
|
||||
|
||||
function TicketStatus({ ticket }) {
|
||||
const getStatusBadge = (status) => {
|
||||
const variants = {
|
||||
available: 'success',
|
||||
limited: 'warning',
|
||||
sold_out: 'error'
|
||||
};
|
||||
|
||||
return (
|
||||
<Badge variant={variants[status]}>
|
||||
{status.replace('_', ' ').toUpperCase()}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<h3>{ticket.name}</h3>
|
||||
{getStatusBadge(ticket.status)}
|
||||
</div>
|
||||
|
||||
{ticket.status === 'limited' && (
|
||||
<Alert variant="warning">
|
||||
Only {ticket.remaining} tickets left
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={ticket.status === 'sold_out'}
|
||||
fullWidth
|
||||
>
|
||||
{ticket.status === 'sold_out' ? 'Sold Out' : 'Purchase'}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Design Token Usage
|
||||
|
||||
### Color System
|
||||
|
||||
```tsx
|
||||
// Using semantic color tokens
|
||||
<div className="bg-surface-primary text-text-primary border border-border-primary">
|
||||
Content with theme-aware colors
|
||||
</div>
|
||||
|
||||
// Status colors
|
||||
<Alert variant="success"> // Uses --color-success-* tokens
|
||||
<Badge variant="error"> // Uses --color-error-* tokens
|
||||
```
|
||||
|
||||
### Typography Scale
|
||||
|
||||
```tsx
|
||||
// Using typography tokens
|
||||
<h1 className="text-4xl">Main Heading</h1> // --font-size-4xl
|
||||
<h2 className="text-2xl">Section Heading</h2> // --font-size-2xl
|
||||
<p className="text-base">Body text</p> // --font-size-base
|
||||
<small className="text-sm">Helper text</small> // --font-size-sm
|
||||
```
|
||||
|
||||
### Spacing System
|
||||
|
||||
```tsx
|
||||
// Using spacing tokens
|
||||
<div className="p-4 m-2 space-y-6"> // --spacing-4, --spacing-2, --spacing-6
|
||||
<Card padding="lg"> // --spacing-8 (internal)
|
||||
<div className="space-between-3"> // --spacing-3
|
||||
```
|
||||
|
||||
## Testing Components
|
||||
|
||||
All UI primitives include comprehensive test coverage:
|
||||
|
||||
```typescript
|
||||
// Example test for Button component
|
||||
test('Button renders with correct variant styles', async ({ page }) => {
|
||||
await page.goto('/ui-showcase');
|
||||
|
||||
// Test primary variant
|
||||
const primaryButton = page.getByTestId('button-primary');
|
||||
await expect(primaryButton).toHaveClass(/bg-primary/);
|
||||
|
||||
// Test accessibility
|
||||
await expect(primaryButton).toBeEnabled();
|
||||
await primaryButton.focus();
|
||||
await expect(primaryButton).toBeFocused();
|
||||
|
||||
// Test keyboard interaction
|
||||
await primaryButton.press('Enter');
|
||||
await expect(page.getByText('Button clicked')).toBeVisible();
|
||||
});
|
||||
```
|
||||
|
||||
## Accessibility Compliance
|
||||
|
||||
### WCAG AA Standards
|
||||
|
||||
All components meet WCAG AA requirements:
|
||||
|
||||
- **Color Contrast**: 4.5:1 minimum ratio for normal text, 3:1 for large text
|
||||
- **Keyboard Navigation**: Full keyboard support for all interactive elements
|
||||
- **Screen Reader Support**: Proper semantic HTML and ARIA labels
|
||||
- **Focus Management**: Visible focus indicators with sufficient contrast
|
||||
|
||||
### Testing Tools
|
||||
|
||||
```bash
|
||||
# Run accessibility tests
|
||||
npm run test:a11y
|
||||
|
||||
# Generate accessibility report
|
||||
npm run a11y:report
|
||||
|
||||
# Visual contrast validation
|
||||
npm run test:contrast
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Bundle Size Optimization
|
||||
|
||||
- **Tree Shaking**: Import only the components you need
|
||||
- **CSS Custom Properties**: Reduced CSS bundle size with design tokens
|
||||
- **Minimal Dependencies**: Core components have zero external dependencies
|
||||
|
||||
```tsx
|
||||
// Efficient imports
|
||||
import { Button, Input } from '@/components/ui'; // Tree-shaken
|
||||
|
||||
// Avoid importing entire library
|
||||
import * as UI from '@/components/ui'; // Not recommended
|
||||
```
|
||||
|
||||
### Runtime Performance
|
||||
|
||||
- **Memoization**: Components use React.memo where appropriate
|
||||
- **Event Handling**: Optimized event listeners with proper cleanup
|
||||
- **Re-render Optimization**: Props designed to minimize unnecessary re-renders
|
||||
|
||||
---
|
||||
|
||||
**Component library built with CrispyGoat quality standards - accessible, performant, and developer-friendly.**
|
||||
Reference in New Issue
Block a user