# Garage UI Style Guide This document outlines the coding standards, naming conventions, and architectural patterns for the Garage UI project to ensure consistency across all development efforts. ## Table of Contents - [Code Style](#code-style) - [File and Folder Structure](#file-and-folder-structure) - [Component Architecture](#component-architecture) - [TypeScript Guidelines](#typescript-guidelines) - [React Patterns](#react-patterns) - [State Management](#state-management) - [API and Data Fetching](#api-and-data-fetching) - [Form Handling](#form-handling) - [UI and Styling](#ui-and-styling) - [Testing Guidelines](#testing-guidelines) - [Git and Commit Guidelines](#git-and-commit-guidelines) ## Code Style ### General Principles - **Consistency**: Follow established patterns in the codebase - **Readability**: Write self-documenting code with clear naming - **Simplicity**: Prefer simple, straightforward solutions - **Performance**: Consider React performance best practices ### Formatting - Use **2 spaces** for indentation - Use **double quotes** for strings in JSX attributes - Use **single quotes** for all other strings - Use **semicolons** consistently - Max line length: **100 characters** - Use trailing commas in objects and arrays ```typescript // ✅ Good const config = { apiUrl: 'http://localhost:8080', timeout: 5000, }; // ❌ Bad const config = { apiUrl: "http://localhost:8080", timeout: 5000 } ``` ### ESLint Configuration Follow the existing ESLint configuration: - TypeScript ESLint rules - React Hooks rules - React Refresh rules ## File and Folder Structure ### Naming Conventions - **Files**: Use kebab-case for file names (`user-profile.tsx`, `auth-hooks.ts`) - **Components**: Use PascalCase for component files (`UserProfile.tsx`, `NavigationBar.tsx`) - **Folders**: Use kebab-case for folder names (`user-settings/`, `api-utils/`) - **Assets**: Use kebab-case (`garage-logo.svg`, `user-avatar.png`) ### Folder Structure ``` src/ ├── app/ # App-level configuration │ ├── app.tsx # Main App component │ ├── router.tsx # Route definitions │ ├── styles.css # Global styles │ └── themes.ts # Theme configuration ├── assets/ # Static assets ├── components/ # Reusable components │ ├── containers/ # Container components │ ├── layouts/ # Layout components │ └── ui/ # Basic UI components ├── context/ # React contexts ├── hooks/ # Custom hooks ├── lib/ # Utility libraries ├── pages/ # Page components and related logic │ └── [page-name]/ │ ├── index.tsx # Main page component │ ├── components/ # Page-specific components │ ├── hooks.ts # Page-specific hooks │ ├── schema.ts # Validation schemas │ └── stores.ts # Page-specific stores ├── stores/ # Global state stores └── types/ # TypeScript type definitions ``` ### File Organization Rules 1. **Page Structure**: Each page should have its own folder with related components, hooks, schemas, and stores 2. **Component Isolation**: Page-specific components go in the page's `components/` folder 3. **Shared Components**: Reusable components go in `src/components/` 4. **Hooks**: Page-specific hooks in page folder, shared hooks in `src/hooks/` 5. **Types**: Domain-specific types in `src/types/`, component props types inline ## Component Architecture ### Component Types 1. **Page Components**: Top-level route components 2. **Layout Components**: Structural components (headers, sidebars, etc.) 3. **Container Components**: Components that manage state and logic 4. **UI Components**: Presentational components with minimal logic ### Component Structure ```typescript // ✅ Good component structure import { ComponentPropsWithoutRef, forwardRef } from 'react'; import { LucideIcon } from 'lucide-react'; import { Button as BaseButton } from 'react-daisyui'; // Types first type ButtonProps = ComponentPropsWithoutRef & { icon?: LucideIcon; href?: string; }; // Component with forwardRef for UI components const Button = forwardRef( ({ icon: Icon, children, ...props }, ref) => { return ( {Icon && } {children} ); } ); Button.displayName = 'Button'; export default Button; ``` ### Export Patterns - **Default exports** for main components - **Named exports** for utilities, hooks, and types - **Barrel exports** for component directories (index.ts files) ```typescript // utils.ts export const formatDate = (date: Date) => { /* ... */ }; export const formatBytes = (bytes: number) => { /* ... */ }; // components/index.ts export { default as Button } from './button'; export { default as Input } from './input'; ``` ## TypeScript Guidelines ### Type Definitions - Use **interfaces** for object shapes that might be extended - Use **types** for unions, primitives, and computed types - Use **const assertions** for readonly arrays and objects ```typescript // ✅ Good interface User { id: string; name: string; email: string; } type Theme = 'light' | 'dark' | 'auto'; const themes = ['light', 'dark', 'auto'] as const; type Theme = typeof themes[number]; ``` ### Generic Patterns ```typescript // API response wrapper type ApiResponse = { data: T; success: boolean; message?: string; }; // Component props with children type ComponentProps = T & { children?: React.ReactNode; className?: string; }; ``` ### Type Imports Use type-only imports when importing only types: ```typescript import type { User } from '@/types/user'; import type { ComponentProps } from 'react'; ``` ## React Patterns ### Hooks Usage - **Custom hooks** for reusable logic - **Built-in hooks** following React best practices - **Hook naming**: Always start with `use` ```typescript // ✅ Good custom hook export const useAuth = () => { const { data, isLoading } = useQuery({ queryKey: ['auth'], queryFn: () => api.get('/auth/status'), retry: false, }); return { isLoading, isEnabled: data?.enabled, isAuthenticated: data?.authenticated, }; }; ``` ### Component Patterns - Use **functional components** exclusively - Use **forwardRef** for UI components that need ref access - Destructure props in function parameters - Use **early returns** for conditional rendering ```typescript // ✅ Good component pattern const UserCard = ({ user, onEdit, className }: UserCardProps) => { if (!user) { return
No user data
; } return (

{user.name}

{user.email}

); }; ``` ## State Management ### Zustand Stores - Use Zustand for **global state** management - Keep stores **focused** and domain-specific - Use **immer** for complex state updates ```typescript // ✅ Good store pattern import { create } from 'zustand'; import { immer } from 'zustand/middleware/immer'; interface AppState { theme: Theme; sidebarOpen: boolean; setTheme: (theme: Theme) => void; toggleSidebar: () => void; } export const useAppStore = create()( immer((set) => ({ theme: 'light', sidebarOpen: false, setTheme: (theme) => set((state) => { state.theme = theme; }), toggleSidebar: () => set((state) => { state.sidebarOpen = !state.sidebarOpen; }), })) ); ``` ### Local State - Use **useState** for simple local state - Use **useReducer** for complex state logic - Use **React Query** state for server state ## API and Data Fetching ### React Query Patterns - Use **React Query** for all server state - Follow consistent **query key** patterns - Use **custom hooks** for API calls ```typescript // ✅ Good API hook pattern export const useUsers = (filters?: UserFilters) => { return useQuery({ queryKey: ['users', filters], queryFn: () => api.get('/users', { params: filters }), staleTime: 5 * 60 * 1000, // 5 minutes }); }; export const useCreateUser = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (userData: CreateUserInput) => api.post('/users', userData), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); }, }); }; ``` ### API Client - Use consistent **error handling** - Follow **RESTful** conventions - Use **TypeScript** for request/response types ```typescript // lib/api.ts const api = { get: (url: string, config?: AxiosRequestConfig) => axios.get(url, config).then(res => res.data), post: (url: string, data?: any, config?: AxiosRequestConfig) => axios.post(url, data, config).then(res => res.data), // ... other methods }; ``` ## Form Handling ### React Hook Form + Zod - Use **React Hook Form** for all forms - Use **Zod** for validation schemas - Use **@hookform/resolvers** for integration ```typescript // ✅ Good form pattern // schema.ts export const createUserSchema = z.object({ name: z.string().min(1, 'Name is required'), email: z.string().email('Invalid email'), role: z.enum(['admin', 'user']), }); export type CreateUserInput = z.infer; // component.tsx const CreateUserForm = ({ onSubmit }: CreateUserFormProps) => { const form = useForm({ resolver: zodResolver(createUserSchema), defaultValues: { name: '', email: '', role: 'user', }, }); return (
); }; ``` ## UI and Styling ### TailwindCSS + DaisyUI - Use **TailwindCSS** utility classes - Use **DaisyUI** components as base - Use **clsx** or **tailwind-merge** for conditional classes ```typescript import { cn } from '@/lib/utils'; // tailwind-merge utility const Button = ({ variant, size, className, ...props }: ButtonProps) => { return (