# VoIdea WebUI Style Guide ## Tech Stack | Category | Choice | Version | |----------|--------|---------| | Framework | React | 18.3.x (LTS) | | Language | TypeScript | 5.5+ (strict mode) | | Routing | react-router-dom | 6.26.x | | Styling | Tailwind CSS | 3.4.x | | State | Zustand (new stores) + Context (existing) | 5.x | | Forms | react-hook-form + zod | latest | | i18n | react-i18next (future) / constants/strings.ts (now) | — | | Testing | Vitest + @testing-library/react | latest | | PWA | vite-plugin-pwa | 0.20.x | | Build | Vite | 5.4.x | ## Project Structure ``` webui/src/ ├── api/ # HTTP client + endpoint modules ├── auth/ # Zustand store (future), Context (migration target) ├── components/ # Shared UI (ErrorBoundary, Layout, VoiceChat, etc.) ├── constants/ # strings.ts (i18n-ready), enums ├── hooks/ # Custom hooks ├── pages/ # Route pages (one file per route) ├── stores/ # Zustand stores (auth, ideas, settings...) ├── types/ # Shared TypeScript types ├── utils/ # Pure utility functions ├── App.tsx # Router setup └── main.tsx # Entry point ``` ## Coding Rules ### General - `any` запрещён. Всегда явный тип. - Импорты: абсолютные через `@/` alias. Относительные только для соседних файлов. - Файл — не более 300 строк. Сервисы/компоненты больше — разбить. - Prettier: 100 символов, двойные кавычки, точка с запятой. ### State Management (Zustand) ```ts // stores/auth.ts import { create } from "zustand"; interface AuthState { user: User | null; token: string | null; login: (email: string, password: string) => Promise; logout: () => void; } export const useAuthStore = create((set) => ({ user: null, token: null, login: async (email, password) => { /* ... */ }, logout: () => { clearTokens(); set({ user: null, token: null }); }, })); ``` - Один store — одна доменная область (auth, ideas, settings, voice). - Store НЕ содержит UI-логику (только состояние + действия). - Использовать `useAuthStore()` в компонентах напрямую (без Provider). - Context API — только для редких случаев (< 3 подписчиков, Legacy). ### Forms (react-hook-form + zod) ```ts const schema = z.object({ email: z.string().email(), password: z.string().min(8), }); type FormData = z.infer; const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(schema), }); ``` - Каждая сложная форма (3+ поля) — свой schema-файл в `pages/.schema.ts`. - Простые формы (1–2 поля, логин) — schema внутри компонента. - Ошибки выводить ``. - Кнопку сабмита дизейблить пока `isSubmitting`. ### Accessibility (WCAG AA) **Обязательно:** - `aria-label` на всех кнопках без текста (иконки: Menu, ThemeToggle, Logout, Close, VoiceChat mic) - `htmlFor` + `id` на всех `