Files
gh-human-frontier-labs-inc-…/references/component-standards.md
2025-11-29 18:47:35 +08:00

9.1 KiB

Component Standards - CareBridge

Critical Rule: No Custom Components

NEVER create custom UI components from scratch. ALWAYS use shadcn/ui.

Using shadcn/ui

Adding Components

# List available components
npx shadcn@latest add

# Add a specific component
npx shadcn@latest add button
npx shadcn@latest add card
npx shadcn@latest add dialog
npx shadcn@latest add form

This adds components to src/components/ui/ with proper TypeScript types and styling.

Example: Adding a Button

npx shadcn@latest add button

Then use it:

import { Button } from '@/components/ui/button'

export function MyComponent() {
  return <Button variant="default">Click me</Button>
}

Available Variants

shadcn components come with built-in variants. Check the component file for options:

// Button variants
<Button variant="default">Default</Button>
<Button variant="destructive">Delete</Button>
<Button variant="outline">Outline</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>

// Card components
<Card>
  <CardHeader>
    <CardTitle>Title</CardTitle>
    <CardDescription>Description</CardDescription>
  </CardHeader>
  <CardContent>
    Content here
  </CardContent>
  <CardFooter>
    Footer actions
  </CardFooter>
</Card>

Component Organization

Location

src/
├── components/
│   ├── ui/              # shadcn components (DON'T EDIT)
│   │   ├── button.tsx
│   │   ├── card.tsx
│   │   └── ...
│   ├── dashboard/       # Dashboard-specific components
│   ├── calendar/        # Calendar-specific components
│   ├── concierge/       # Concierge-specific components
│   └── nav-main.tsx     # Global navigation components

Naming Conventions

  • UI components: kebab-case.tsx (button.tsx, card.tsx)
  • Feature components: PascalCase.tsx (DashboardHeader.tsx, CaseList.tsx)
  • Use descriptive names: ConciergeBookingForm.tsx not Form.tsx

Styling Rules

CSS Variables (READ-ONLY)

NEVER create custom CSS variables. ONLY use existing ones from globals.css.

Available CSS variables:

/* From globals.css */
--background
--foreground
--card
--card-foreground
--popover
--popover-foreground
--primary
--primary-foreground
--secondary
--secondary-foreground
--muted
--muted-foreground
--accent
--accent-foreground
--destructive
--destructive-foreground
--border
--input
--ring

Using CSS Variables

// ✅ CORRECT - Use existing variables
<div className="bg-background text-foreground">
  Content
</div>

// ✅ CORRECT - Use Tailwind utility classes
<div className="bg-primary text-primary-foreground rounded-lg p-4">
  Card content
</div>

// ❌ WRONG - Custom CSS variables
<div style={{ backgroundColor: 'var(--my-custom-color)' }}>
  Content
</div>

// ❌ WRONG - Inline styles
<div style={{ backgroundColor: '#3498db', padding: '16px' }}>
  Content
</div>

Tailwind Classes

Use Tailwind utility classes for styling:

// ✅ CORRECT
<div className="flex items-center gap-4 p-6 rounded-lg border">
  <div className="flex-1">Content</div>
  <Button variant="default">Action</Button>
</div>

// ❌ WRONG - Custom CSS classes
<div className="my-custom-container">
  Content
</div>

Form Components

Using React Hook Form + Zod

CareBridge uses React Hook Form with Zod validation:

import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'

const formSchema = z.object({
  name: z.string().min(1, 'Name is required'),
  email: z.string().email('Invalid email'),
})

export function MyForm() {
  const form = useForm({
    resolver: zodResolver(formSchema),
    defaultValues: {
      name: '',
      email: '',
    },
  })

  const onSubmit = async (values: z.infer<typeof formSchema>) => {
    // Handle submission
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
        <FormField
          control={form.control}
          name="name"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Name</FormLabel>
              <FormControl>
                <Input {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />

        <Button type="submit">Submit</Button>
      </form>
    </Form>
  )
}

Dialog/Modal Pattern

import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'

export function MyDialog() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button>Open Dialog</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Dialog Title</DialogTitle>
        </DialogHeader>
        <div>Dialog content here</div>
      </DialogContent>
    </Dialog>
  )
}

Empty States Pattern

Use the EmptyState component for "no data" scenarios:

import { EmptyState } from '@/components/dashboard/empty-state'
import { Users } from 'lucide-react'

export function MyComponent({ data }) {
  if (!data || data.length === 0) {
    return (
      <EmptyState
        icon={Users}
        title="No Cases Found"
        description="Create your first care case to get started"
      />
    )
  }

  return <div>{/* Render data */}</div>
}

Loading States

import { Skeleton } from '@/components/ui/skeleton'

export function LoadingState() {
  return (
    <div className="space-y-4">
      <Skeleton className="h-12 w-full" />
      <Skeleton className="h-12 w-full" />
      <Skeleton className="h-12 w-full" />
    </div>
  )
}

Icons

Use Lucide React for icons:

import { Calendar, User, Settings, CreditCard } from 'lucide-react'

export function MyComponent() {
  return (
    <div className="flex items-center gap-2">
      <Calendar className="h-4 w-4" />
      <span>Calendar</span>
    </div>
  )
}

Common Component Patterns

Card with Actions

<Card>
  <CardHeader>
    <div className="flex items-center justify-between">
      <CardTitle>Title</CardTitle>
      <Button variant="ghost" size="sm">
        <Settings className="h-4 w-4" />
      </Button>
    </div>
    <CardDescription>Description</CardDescription>
  </CardHeader>
  <CardContent>
    Content here
  </CardContent>
  <CardFooter className="flex justify-end gap-2">
    <Button variant="outline">Cancel</Button>
    <Button>Save</Button>
  </CardFooter>
</Card>

List with Items

<div className="space-y-2">
  {items.map((item) => (
    <Card key={item.id}>
      <CardContent className="flex items-center justify-between p-4">
        <div>
          <h3 className="font-medium">{item.title}</h3>
          <p className="text-sm text-muted-foreground">{item.description}</p>
        </div>
        <Button variant="ghost" size="sm">
          View
        </Button>
      </CardContent>
    </Card>
  ))}
</div>

Responsive Design

Use Tailwind responsive prefixes:

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  {/* Responsive grid */}
</div>

<div className="flex flex-col sm:flex-row gap-4">
  {/* Stack vertically on mobile, horizontally on larger screens */}
</div>

Common Mistakes

Mistake #1: Creating custom components

// WRONG - Don't create custom buttons
export function CustomButton({ children }) {
  return (
    <button className="custom-btn">
      {children}
    </button>
  )
}

// CORRECT - Use shadcn button
import { Button } from '@/components/ui/button'

<Button variant="default">{children}</Button>

Mistake #2: Custom CSS variables

// WRONG - Don't create custom variables
<div style={{ color: 'var(--my-custom-color)' }}>
  Content
</div>

// CORRECT - Use existing variables
<div className="text-primary">
  Content
</div>

Mistake #3: Inline styles

// WRONG - Avoid inline styles
<div style={{ padding: '16px', margin: '8px' }}>
  Content
</div>

// CORRECT - Use Tailwind classes
<div className="p-4 m-2">
  Content
</div>

Mistake #4: Not using TypeScript types

// WRONG - No types
export function MyComponent({ user }) {
  return <div>{user.name}</div>
}

// CORRECT - Proper TypeScript types
type Props = {
  user: {
    name: string
    email: string
  }
}

export function MyComponent({ user }: Props) {
  return <div>{user.name}</div>
}

Component Checklist

Before creating a component:

  • Is there a shadcn component for this? (Use npx shadcn@latest add)
  • Am I using existing CSS variables?
  • Am I using Tailwind classes instead of inline styles?
  • Do I have proper TypeScript types?
  • Is the component in the right directory?
  • Am I preserving case context in navigation?