# Component Development Guidelines This document covers component development patterns including Server vs Client components, semantic HTML, and UI best practices. ## Server vs Client Components ### Default to Server Components Next.js App Router defaults to Server Components. Use them for: - Data fetching - Accessing backend resources directly - Keeping sensitive data on the server - Reducing client-side JavaScript ```typescript // app/(app)/dashboard/page.tsx (Server Component) import { DashboardStats } from '@/modules/dashboard/components'; export default async function DashboardPage() { // Can fetch data directly const stats = await fetchDashboardStats(); return (

Dashboard

); } ``` ### When to Use Client Components Add `'use client'` directive only when you need: - Event handlers (onClick, onChange, etc.) - useState, useEffect, or other React hooks - Browser-only APIs (localStorage, window) - Class components with lifecycle methods ```typescript 'use client'; import { useState } from 'react'; export function Counter() { const [count, setCount] = useState(0); return ( ); } ``` ### Composition Pattern Keep Server Components at the top, push Client Components down: ```typescript // Server Component (page.tsx) import { ProductList } from './ProductList'; import { FilterSidebar } from './FilterSidebar'; // Client export default async function ProductsPage() { const products = await fetchProducts(); return (
{/* Client component for interactivity */} {/* Can be server or client */}
); } ``` ### Passing Server Data to Client Components ```typescript // Server Component export default async function Page() { const initialData = await fetchData(); return ; } // Client Component 'use client'; export function InteractiveWidget({ initialData }: { initialData: Data }) { const [data, setData] = useState(initialData); // Interactive logic... } ``` ## Semantic HTML ## Kumo UI Convention Use `components/ui/*` as the project-owned adapter layer for Cloudflare Kumo components. Feature modules and route files should import `Button`, `Input`, `Select`, `Textarea`, `Table`, `Checkbox`, `Badge`, `Card`, and `Dialog` from `@/components/ui/...` instead of importing `@cloudflare/kumo` directly. ```typescript // Good: project adapter keeps Kumo API differences local import { Select } from "@/components/ui/select"; import { Table } from "@/components/ui/table"; // Avoid: feature code should not hand-style native controls // Avoid: this can reintroduce production overlay regressions. import { Select } from "@cloudflare/kumo/components/select"; ``` ### Dialog Adapter Centering Contract `components/ui/dialog.tsx` may use Kumo Dialog primitives for accessibility, but the project adapter owns panel geometry. Kumo Dialog includes fixed `left-1/2 top-1/2` and translate classes, and those transforms can interact with project animations or production CSS ordering. The project Dialog adapter must: - Center the panel without relying on translate transforms. Use `fixed inset-0 m-auto` plus explicit width, max-height, and `h-fit`. - Override Kumo minimum width so mobile dialogs stay inside `100vw`. - Keep `transform: none` and `translate: 0 0` on `.teatea-dialog`; use opacity/scale-only motion if animation is needed. - Browser validation should assert the panel center is within 2px of the viewport center on desktop and mobile. ### Static Module Data Contract Static or reserved module pages must not fabricate operational data. If a module does not have a Drizzle-backed query/API and real persisted records, render a clear empty or not-connected state instead of generated counters, fake workflows, sample records, or mock priority/status rows. `modules/shared/components/ModulePage.tsx` is the shared placeholder for these modules. Its props should stay descriptive only: ```typescript type ModulePageProps = { title: string; eyebrow: string; description: string; icon: LucideIcon; phase: "待接入" | "二期预留" | "三期预留"; }; ``` ```typescript // Bad: static pages pretending to have live business data. // Good: no fabricated records until a real data source exists. ``` When a module becomes real, replace the placeholder with a Server Component that loads data from the server boundary and pass only persisted, permission-filtered data into client components. ### Use Proper Elements ```typescript // Bad: div for everything
Click me
Item 1
Item 2
// Good: semantic elements
  • Item 1
  • Item 2
``` ### Button vs Div Always use ` ``` ### Form Elements ```typescript // Bad: Missing labels, wrong elements
Email
// Good: Proper form structure
{error && }
``` ### Navigation ```typescript // Bad
router.push('/about')}>About
// Good About // For programmatic navigation with button appearance About ``` ## Next.js Image Component ### Always Use next/image ```typescript // Bad: Raw img tag Hero // Good: Optimized Image component import Image from 'next/image'; Hero image ``` ### Responsive Images ```typescript // Fill container
Banner
``` ### Remote Images Configure domains in `next.config.js`: ```javascript // next.config.js module.exports = { images: { remotePatterns: [ { protocol: 'https', hostname: 'images.example.com', }, ], }, }; ``` ## Command Palette (cmdk) ### Basic Implementation ```typescript 'use client'; import { Command } from 'cmdk'; import { useState, useEffect } from 'react'; export function CommandPalette() { const [open, setOpen] = useState(false); // Toggle with keyboard shortcut useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); setOpen((open) => !open); } }; document.addEventListener('keydown', down); return () => document.removeEventListener('keydown', down); }, []); return ( No results found. router.push('/dashboard')}> Go to Dashboard router.push('/settings')}> Go to Settings Create New Order ); } ``` ### With Search Results ```typescript export function SearchCommandPalette() { const [search, setSearch] = useState(''); const { data: results, isLoading } = useSearch(search); return ( {isLoading && Searching...} No results found. {results?.map((item) => ( handleSelect(item)} > {item.title} ))} ); } ``` ## Styling with Tailwind ### Component Styling Pattern ```typescript // Use className for styling export function Card({ children, className, }: { children: ReactNode; className?: string; }) { return (
{children}
); } ``` ### Conditional Styles ```typescript import { cn } from '@/lib/utils'; export function Button({ variant = 'primary', size = 'md', className, ...props }: ButtonProps) { return ( ); } ``` ### ARIA Labels ```typescript ``` ## Best Practices 1. **Server First**: Default to Server Components 2. **Semantic HTML**: Use the right element for the job 3. **Optimize Images**: Always use next/image 4. **Accessibility**: Include ARIA labels and keyboard support 5. **Type Props**: Define TypeScript interfaces for all props 6. **Composition**: Break large components into smaller pieces ## Anti-Patterns - Using `div` for buttons and links - Using `img` instead of `next/image` - Adding `'use client'` at the top of every file - Inline styles instead of Tailwind classes - Missing accessibility attributes - Components with too many responsibilities