# Frontend Apps Shared patterns and conventions for all frontend applications (dashboard, editor, runtime). ## UI Components ### shadcn/ui (Base) Location: `src/workspaces/components/ui/` Standard shadcn/ui components with Tailwind CSS. Import via: ```tsx import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; ``` ### Custom Components Location: `src/workspaces/components/` Built on top of shadcn primitives: - `Avatar.tsx` - User avatars - `Card.tsx` - Custom card wrapper - `Combobox.tsx` - Searchable select - `ColorPicker.tsx` - Color selection - `DataTable/` - Table with sorting, filtering, pagination (see below) - `Dialogs/` - Common dialog patterns - `ErrorDialogs/` - Error display dialogs - `MetricCard.tsx` - Stats display card - `SearchInput.tsx` - Search with debounce - `Select.tsx` - Enhanced select - `Slider/` - Custom slider - `Tabs/` - Enhanced tabs ### DataTable + useDataControl A complete data table solution with sorting, filtering, and pagination. **Pattern:** ```tsx import { DataTable, useDataControl } from '@/components/DataTable'; function ExperimentsLibrary() { const { experiments } = useExperimentsList(); const dataController = useDataControl(experiments || []); return ( <> {/* Wire search input to controller */} dataController={dataController} pageSize={10} columns={[ { field: 'id', label: 'ID', sortKey: 'id' }, { field: 'name', label: 'Name', sortKey: 'name' }, { field: 'status', label: 'Status', condition: view === 'All', // conditional column format: ({ value, item }) => {value} }, { label: 'Actions', format: ({ item }) => }, ]} /> ); } ``` **useDataControl returns:** - `data` - Current page of filtered/sorted data - `rawData` - Original unfiltered data - `searchQuery`, `setSearchQuery` - Search state - `searchKeys`, `setSearchKeys` - Which fields to search - `sortedBy`, `setSortedBy`, `sortBy` - Sort state - `sortKey`, `sortDirection` - Parsed sort info - `currentPage`, `changePage`, `pageCount` - Pagination - `pageSize`, `setPageSize` - Items per page - `setCustomSort` - Custom sort function **Column options:** - `field` - Key in data object - `label` / `labelMobile` - Header text - `sortKey` - Enable sorting on this column - `format` - Custom render: `({ value, item, data, idx }) => ReactNode` - `condition` - Show/hide column: `boolean | () => boolean` - `tooltip` - Header tooltip text - `headerClassName` / `cellClassName` - Styling **Features:** - Auto-generates columns from data if not specified - Resets to page 1 on search/sort change - Search strips diacritics for accent-insensitive matching - Built-in pagination bar (shows when data exceeds pageSize) ## Hooks Location: `src/workspaces/hooks/src/` ### Data Fetching - `useClient` HTTP client with auth token handling: ```tsx const client = useClient(); const data = await client.get("/users"); await client.post("/experiments", payload); ``` Methods: `get`, `getText`, `getBlob`, `post`, `put`, `patch`, `delete` Auto-attaches Bearer token from localStorage/sessionStorage. ### Forms - `useForm` Custom form hook (not react-hook-form): ```tsx const { state, formRef, update, reset, isValid } = useForm( initialState, validateFn ); return
...
; ``` Features: - Auto-syncs form inputs with state via `name` attribute - Handles checkboxes, radios, selects, number inputs - Nested object support via data attributes - Validation function support ### Other Hooks - `useAuth/` - Authentication state and methods - `useDebounce` - Debounced values - `useDragAndDrop` - Drag and drop functionality - `useFullscreen/` - Fullscreen API wrapper - `useIsMobile` - Responsive breakpoint detection - `useLocalStorageState` - Persisted state - `useMounted` - Component mount status - `usePopover` - Popover state management - `useTimeout` - Timeout management - `useTimer` - Interval-based timer - `useWebsockets` - WebSocket connection ## State Management Uses React Query cache as both server and client state manager. ### Global State - `useGlobal` Uses React Query cache as a global state store (no Context providers needed): ```tsx const [value, setValue, refresh, reset] = useGlobal("SIDEBAR_STATE", "expanded"); // Any component using the same key shares state and reacts to changes setValue("collapsed"); ``` How it works: - `enabled: false` + `staleTime: Infinity` = never fetches, just stores - Automatic re-renders when state changes - Visible in React Query DevTools - Supports functional updates: `setValue(prev => ...)` ### URL Query State - `useQueryState` Syncs state with URL query parameters: ```tsx const [page, setPage, reset, clear] = useQueryState("page", 1); const [filter, setFilter] = useQueryState("filter", null, true); // isGlobal = true ``` Features: - Updates URL via `history.replaceState` (no page reload) - `isGlobal = true`: persists across navigation, uses `useGlobal` internally - `isGlobal = false`: local to component, uses `useState` - Auto-rebuilds URL when pathname changes (preserves global query state) - Type coercion based on `defaultValue` type ### Server State Domain-specific hooks in `state/` directories wrap React Query: ```tsx // src/apps/dashboard/state/experiments/useExperiment.ts const { data, isLoading } = useExperiment(experimentId); ``` ### When to Use What | Scenario | Hook | |----------|------| | API data | Domain hooks (`useExperiment`, etc.) | | Shared UI state | `useGlobal` | | URL-driven state (filters, pagination) | `useQueryState` | | Component-only state | `useState` | | Persisted to localStorage | `useLocalStorageState` | ## Event Handlers **Always use `ev` for event parameters**, not `e`: ```tsx // ✅ Good onChange={(ev) => setName(ev.target.value)} onKeyDown={(ev) => { if (ev.key === 'Enter') { ev.preventDefault(); handleSubmit(); } }} const handleSubmit = async (ev: React.FormEvent) => { ev.preventDefault(); // ... }; // ❌ Bad onChange={(e) => setName(e.target.value)} ``` ## React 19 Patterns **RefObject includes null**: In React 19, `useRef` returns `RefObject`. Update prop types accordingly: ```tsx // ✅ Good - allow null in ref type type PanelProps = { triggerRef: React.RefObject; }; // ❌ Bad - will error when passing useRef result type PanelProps = { triggerRef: React.RefObject; }; ``` **Hook return types**: Use `ReturnType` for typing hook returns in props: ```tsx import { useExperimentsList } from '@/state/experiments/useExperimentsList'; type TopHeaderProps = { manager: ReturnType; }; ``` ## Error Handling **Toast notifications** via Sonner: ```tsx import { toast } from "sonner"; toast.error("Something went wrong"); toast.success("Saved successfully"); ``` API errors automatically trigger via `useClient.config.onError`. ## App-Specific Docs - [Dashboard](./dashboard/CLAUDE.md) - Admin UI specifics - [Editor](./editor/CLAUDE.md) - Visual editor specifics - [Runtime](./runtime/CLAUDE.md) - Injected scripts specifics