diff --git a/docs/STYLE_GUIDE.md b/docs/STYLE_GUIDE.md new file mode 100644 index 0000000..1f8bcb0 --- /dev/null +++ b/docs/STYLE_GUIDE.md @@ -0,0 +1,1210 @@ +# 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 ( +