# Component Patterns This guide documents the component patterns used across the FrenoCorp agent ecosystem. ## Architecture Overview All components follow a consistent pattern: ```typescript interface ComponentProps { // Props with types and defaults } /** * Component description * * @param props - Component props * @returns Rendered element */ export function ComponentName(props: ComponentProps) { // Implementation } ``` ## Standard Patterns ### 1. Button Components **Pattern:** Stateless, prop-driven buttons with variants ```typescript interface ButtonProps { variant?: 'primary' | 'secondary' | 'danger'; size?: 'sm' | 'md' | 'lg'; loading?: boolean; disabled?: boolean; onClick?: () => void; children: React.ReactNode; } export function Button({ variant = 'primary', size = 'md', loading, disabled, onClick, children }: ButtonProps) { const buttonClasses = classNames( 'btn', `btn-${variant}`, `btn-${size}`, { 'btn-disabled': disabled } ); return ; } ``` **When to use:** User actions, form submissions, navigation triggers. ### 2. Form Inputs **Pattern:** Controlled inputs with error handling ```typescript interface TextFieldProps { label?: string; placeholder?: string; value: string | number; onChange: (value: string) => void; error?: string; required?: boolean; disabled?: boolean; type?: 'text' | 'email' | 'password' | 'number'; } export function TextField({ label, placeholder = 'Enter value', value, onChange, error, required, disabled, type = 'text' }: TextFieldProps) { return (
{label && } onChange(e.target.value)} placeholder={placeholder} required={required} disabled={disabled} className={classNames('form-input', { 'form-error': error })} /> {error && {error}}
); } ``` **When to use:** User data entry, search boxes, configuration inputs. ### 3. Card Components **Pattern:** Container with header, body, and optional footer ```typescript interface CardProps { title?: string; subtitle?: string; children: React.ReactNode; actions?: React.ReactNode; footer?: React.ReactNode; className?: string; } export function Card({ title, subtitle, children, actions, footer, className = '' }: CardProps) { return (
{(title || subtitle) && ( )} {children} {actions && {actions}} {footer && {footer}}
); } ``` **When to use:** Content containers, data displays, action groups. ### 4. Tab Components **Pattern:** Multi-pane navigation with state management ```typescript interface TabsProps { items: { key: string; label: string }[]; defaultKey?: string; onChange?: (key: string) => void; } export function Tabs({ items, defaultKey = '', onChange }: TabsProps) { const [activeKey, setActiveKey] = useState(defaultKey); const handleTabChange = (key: string) => { onChange?.(key); setActiveKey(key); }; return (
{items.map(({ key, label }) => ( ))}
{/* Tab content */}
); } ``` **When to use:** Multi-section views, navigation groups. ### 5. Modal Components **Pattern:** Overlay with centered content and escape handling ```typescript interface ModalProps { isOpen: boolean; onClose: () => void; title?: string; children: React.ReactNode; actions?: React.ReactNode; } export function Modal({ isOpen, onClose, title, children, actions }: ModalProps) { useEffect(() => { const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; if (isOpen) { document.addEventListener('keydown', handleEscape); document.body.style.overflow = 'hidden'; } return () => { document.removeEventListener('keydown', handleEscape); document.body.style.overflow = ''; }; }, [isOpen, onClose]); if (!isOpen) return null; return (
e.stopPropagation()}> {title && {title}} {children} {actions && {actions}}
); } ``` **When to use:** Confirmations, forms, detailed views. ## Layout Patterns ### Page Wrapper ```typescript interface PageWrapperProps { children: React.ReactNode; title?: string; breadcrumbs?: string[]; actions?: React.ReactNode; } export function PageWrapper({ children, title, breadcrumbs = [], actions }: PageWrapperProps) { return (
{(title || breadcrumbs.length > 0) && ( )}
{children}
); } ``` ### Navigation Pane ```typescript interface NavigationPaneProps { children: React.ReactNode; collapsed?: boolean; } export function NavigationPane({ children, collapsed = false }: NavigationPaneProps) { return ( ); } ``` ## Data Display Patterns ### Table Component ```typescript interface TableProps { columns: Array<{ key: keyof T; label: string }>; data: T[]; onRowClick?: (row: T) => void; emptyMessage?: string; } export function Table({ columns, data, onRowClick, emptyMessage = 'No data' }: TableProps) { if (data.length === 0) { return
{emptyMessage}
; } return ( {columns.map(({ key, label }) => ( ))} {data.map((row) => ( onRowClick?.(row)} > {columns.map(({ key, label }) => ( ))} ))}
{label}
{String(row[key])}
); } ``` ### Status Badge ```typescript interface StatusBadgeProps { status: 'success' | 'warning' | 'error' | 'info' | 'neutral'; children: React.ReactNode; } export function StatusBadge({ status, children }: StatusBadgeProps) { const styles = { success: 'bg-green-100 text-green-800', warning: 'bg-yellow-100 text-yellow-800', error: 'bg-red-100 text-red-800', info: 'bg-blue-100 text-blue-800', neutral: 'bg-gray-100 text-gray-800' }; return ( {children} ); } ``` ## Best Practices ### 1. Props Order Always follow this order: 1. Boolean flags 2. Number values 3. String values 4. Arrays/Objects 5. Children (at the end) ### 2. Default Values Provide sensible defaults for all optional props: ```typescript interface ButtonProps { variant?: 'primary' | 'secondary'; // default: 'primary' size?: 'sm' | 'md' | 'lg'; // default: 'md' disabled?: boolean; // default: false } ``` ### 3. Type Safety Use discriminated unions for state: ```typescript type Status = | { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: T }; | { status: 'error'; error: string }; ``` ### 4. Error Boundaries Wrap all user-facing components: ```typescript class ErrorBoundary extends React.Component< { children: React.ReactNode }, { hasError: boolean } > { constructor(props: { children: React.ReactNode }) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError() { return { hasError: true }; } render() { if (this.state.hasError) { return
Something went wrong
; } return this.props.children; } } ``` ### 5. Accessibility - Use semantic HTML elements - Add ARIA labels where needed - Ensure keyboard navigation works - Test with screen readers ## Testing Guidelines Every component must have: 1. **Unit tests** for logic and rendering 2. **Integration tests** for prop passing 3. **Accessibility tests** for a11y compliance Example unit test: ```typescript describe('Button', () => { it('renders children correctly', () => { const { getByText } = render(); expect(getByText('Click me')).toBeInTheDocument(); }); it('applies correct variant classes', () => { const { container } = render(); expect(container.firstChild).toHaveClass('btn-secondary'); }); }); ``` ## Adding New Components When adding a new component: 1. Create the component file in `src/components/` 2. Add TypeScript interface for props 3. Write JSDoc with @param and @returns 4. Add unit tests 5. Update this document if a new pattern emerges 6. Ensure it follows existing patterns ## Component Naming Conventions - PascalCase for component names: `Button`, `TextField` - Prefix with functional type: `Card`, `Modal`, `Table` - Use descriptive names: `UserAvatar`, not just `Icon` - Avoid single-letter components ## When to Create a New Component Create a new component when: - The same UI pattern appears 3+ times - The logic is reusable across features - The component needs its own props interface Don't create a new component when: - It's used only once - It can be composed from existing components - It's too simple (use HTML elements) ## Review Checklist Before committing component code: - [ ] Props are typed with TypeScript - [ ] JSDoc comments present on exports - [ ] Default values provided for optional props - [ ] Unit tests written - [ ] Accessibility considerations addressed - [ ] Follows existing patterns - [ ] No console.log or debug statements - [ ] Error boundaries where appropriate