Files
syndarix/frontend/docs/COMPONENT_GUIDE.md
Felipe Cardoso 19ecd04a41 Add foundational API client, UI components, and state management setup
- Created `generate-api-client.sh` for OpenAPI-based TypeScript client generation.
- Added `src/lib/api` with Axios-based API client, error handling utilities, and placeholder for generated types.
- Implemented Zustand-based `authStore` for user authentication and token management.
- Integrated reusable UI components (e.g., `Dialog`, `Select`, `Textarea`, `Sheet`, `Separator`, `Checkbox`) using Radix UI and utility functions.
- Established groundwork for client-server integration, state management, and modular UI development.
2025-10-31 21:46:03 +01:00

16 KiB
Executable File

Component Guide

Project: Next.js + FastAPI Template Version: 1.0 Last Updated: 2025-10-31


Table of Contents

  1. shadcn/ui Components
  2. Custom Components
  3. Component Composition
  4. Customization
  5. Accessibility

1. shadcn/ui Components

1.1 Overview

This project uses shadcn/ui, a collection of accessible, customizable components built on Radix UI primitives. Components are copied into the project (not installed as npm dependencies), giving you full control.

Installation Method:

npx shadcn@latest add button card input table dialog

1.2 Core Components

Button

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

// Variants
<Button variant="default">Default</Button>
<Button variant="destructive">Delete</Button>
<Button variant="outline">Cancel</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>

// Sizes
<Button size="default">Default</Button>
<Button size="sm">Small</Button>
<Button size="lg">Large</Button>
<Button size="icon"><IconName /></Button>

// States
<Button disabled>Disabled</Button>
<Button loading>Loading...</Button>

// As Link
<Button asChild>
  <Link href="/users">View Users</Link>
</Button>

Card

import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '@/components/ui/card';

<Card>
  <CardHeader>
    <CardTitle>Users</CardTitle>
    <CardDescription>Manage system users</CardDescription>
  </CardHeader>
  <CardContent>
    <p>Card content goes here</p>
  </CardContent>
  <CardFooter>
    <Button>Action</Button>
  </CardFooter>
</Card>

Dialog / Modal

import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogTrigger } from '@/components/ui/dialog';

<Dialog>
  <DialogTrigger asChild>
    <Button>Open Dialog</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Delete User</DialogTitle>
      <DialogDescription>
        Are you sure you want to delete this user? This action cannot be undone.
      </DialogDescription>
    </DialogHeader>
    <DialogFooter>
      <Button variant="outline" onClick={onCancel}>Cancel</Button>
      <Button variant="destructive" onClick={onConfirm}>Delete</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Form

import { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { useForm } from 'react-hook-form';

const form = useForm();

<Form {...form}>
  <form onSubmit={form.handleSubmit(onSubmit)}>
    <FormField
      control={form.control}
      name="email"
      render={({ field }) => (
        <FormItem>
          <FormLabel>Email</FormLabel>
          <FormControl>
            <Input placeholder="email@example.com" {...field} />
          </FormControl>
          <FormDescription>Your email address</FormDescription>
          <FormMessage />
        </FormItem>
      )}
    />
    <Button type="submit">Submit</Button>
  </form>
</Form>

Table

import { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell } from '@/components/ui/table';

<Table>
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Email</TableHead>
      <TableHead>Role</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    {users.map((user) => (
      <TableRow key={user.id}>
        <TableCell>{user.name}</TableCell>
        <TableCell>{user.email}</TableCell>
        <TableCell>{user.role}</TableCell>
      </TableRow>
    ))}
  </TableBody>
</Table>

Toast / Notifications

import { toast } from 'sonner';

// Success
toast.success('User created successfully');

// Error
toast.error('Failed to delete user');

// Info
toast.info('Processing your request...');

// Loading
toast.loading('Saving changes...');

// Custom
toast('Event has been created', {
  description: 'Monday, January 3rd at 6:00pm',
  action: {
    label: 'Undo',
    onClick: () => console.log('Undo'),
  },
});

Tabs

import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';

<Tabs defaultValue="profile">
  <TabsList>
    <TabsTrigger value="profile">Profile</TabsTrigger>
    <TabsTrigger value="password">Password</TabsTrigger>
    <TabsTrigger value="sessions">Sessions</TabsTrigger>
  </TabsList>
  <TabsContent value="profile">
    <ProfileSettings />
  </TabsContent>
  <TabsContent value="password">
    <PasswordSettings />
  </TabsContent>
  <TabsContent value="sessions">
    <SessionManagement />
  </TabsContent>
</Tabs>

2. Custom Components

2.1 Layout Components

Header

import { Header } from '@/components/layout/Header';

// Usage (in layout.tsx)
<Header />

// Features:
// - Logo/brand
// - Navigation links
// - User menu (avatar, name, dropdown)
// - Theme toggle
// - Mobile menu button

PageContainer

import { PageContainer } from '@/components/layout/PageContainer';

<PageContainer>
  <h1>Page Title</h1>
  <p>Page content...</p>
</PageContainer>

// Provides:
// - Consistent padding
// - Max-width container
// - Responsive layout

PageHeader

import { PageHeader } from '@/components/common/PageHeader';

<PageHeader
  title="Users"
  description="Manage system users"
  action={<Button>Create User</Button>}
/>

2.2 Data Display Components

DataTable

Generic, reusable data table with sorting, filtering, and pagination.

import { DataTable } from '@/components/common/DataTable';
import { ColumnDef } from '@tanstack/react-table';

// Define columns
const columns: ColumnDef<User>[] = [
  {
    accessorKey: 'name',
    header: 'Name',
  },
  {
    accessorKey: 'email',
    header: 'Email',
  },
  {
    id: 'actions',
    cell: ({ row }) => (
      <Button onClick={() => handleEdit(row.original)}>Edit</Button>
    ),
  },
];

// Use DataTable
<DataTable
  columns={columns}
  data={users}
  searchKey="name"
  searchPlaceholder="Search users..."
/>

LoadingSpinner

import { LoadingSpinner } from '@/components/common/LoadingSpinner';

// Sizes
<LoadingSpinner size="sm" />
<LoadingSpinner size="md" />
<LoadingSpinner size="lg" />

// With text
<LoadingSpinner size="md" className="my-8">
  <p className="mt-2 text-sm text-muted-foreground">Loading users...</p>
</LoadingSpinner>

EmptyState

import { EmptyState } from '@/components/common/EmptyState';

<EmptyState
  icon={<Users className="h-12 w-12" />}
  title="No users found"
  description="Get started by creating a new user"
  action={
    <Button onClick={() => router.push('/admin/users/new')}>
      Create User
    </Button>
  }
/>

2.3 Admin Components

UserTable

import { UserTable } from '@/components/admin/UserTable';

<UserTable
  filters={{ search: 'john', is_active: true }}
  onUserSelect={(user) => console.log(user)}
/>

// Features:
// - Search
// - Filters (role, status)
// - Sorting
// - Pagination
// - Bulk selection
// - Bulk actions (activate, deactivate, delete)

UserForm

import { UserForm } from '@/components/admin/UserForm';

// Create mode
<UserForm
  mode="create"
  onSuccess={() => router.push('/admin/users')}
/>

// Edit mode
<UserForm
  mode="edit"
  user={user}
  onSuccess={() => toast.success('User updated')}
/>

// Features:
// - Validation with Zod
// - Field errors
// - Loading states
// - Cancel/Submit actions

OrganizationTable

import { OrganizationTable } from '@/components/admin/OrganizationTable';

<OrganizationTable />

// Features:
// - Search
// - Member count display
// - Actions (edit, delete, view members)

BulkActionBar

import { BulkActionBar } from '@/components/admin/BulkActionBar';

<BulkActionBar
  selectedIds={selectedUserIds}
  onAction={(action) => handleBulkAction(action, selectedUserIds)}
  onClearSelection={() => setSelectedUserIds([])}
  actions={[
    { value: 'activate', label: 'Activate' },
    { value: 'deactivate', label: 'Deactivate' },
    { value: 'delete', label: 'Delete', variant: 'destructive' },
  ]}
/>

// Displays:
// - Selection count
// - Action dropdown
// - Confirmation dialogs
// - Progress indicators

2.4 Settings Components

ProfileSettings

import { ProfileSettings } from '@/components/settings/ProfileSettings';

<ProfileSettings
  user={currentUser}
  onUpdate={(updatedUser) => console.log('Updated:', updatedUser)}
/>

// Fields:
// - First name, last name
// - Email (readonly)
// - Phone number
// - Avatar upload (optional)
// - Preferences

PasswordSettings

import { PasswordSettings } from '@/components/settings/PasswordSettings';

<PasswordSettings />

// Fields:
// - Current password
// - New password
// - Confirm password
// - Option to logout all other devices

SessionManagement

import { SessionManagement } from '@/components/settings/SessionManagement';

<SessionManagement />

// Features:
// - List all active sessions
// - Current session badge
// - Device icons
// - Location display
// - Last used timestamp
// - Revoke session button
// - Logout all other devices button

SessionCard

import { SessionCard } from '@/components/settings/SessionCard';

<SessionCard
  session={session}
  isCurrent={session.is_current}
  onRevoke={() => revokeSession(session.id)}
/>

// Displays:
// - Device icon (desktop/mobile/tablet)
// - Device name
// - Location (city, country)
// - IP address
// - Last used (relative time)
// - "This device" badge if current
// - Revoke button (disabled for current)

2.5 Chart Components

BarChartCard

import { BarChartCard } from '@/components/charts/BarChartCard';

<BarChartCard
  title="User Registrations"
  description="Monthly user registrations"
  data={[
    { month: 'Jan', count: 45 },
    { month: 'Feb', count: 52 },
    { month: 'Mar', count: 61 },
  ]}
  dataKey="count"
  xAxisKey="month"
/>

LineChartCard

import { LineChartCard } from '@/components/charts/LineChartCard';

<LineChartCard
  title="Active Users"
  description="Daily active users over time"
  data={dailyActiveUsers}
  dataKey="count"
  xAxisKey="date"
  color="hsl(var(--primary))"
/>

PieChartCard

import { PieChartCard } from '@/components/charts/PieChartCard';

<PieChartCard
  title="Users by Role"
  description="Distribution of user roles"
  data={[
    { name: 'Admin', value: 10 },
    { name: 'User', value: 245 },
    { name: 'Guest', value: 56 },
  ]}
/>

3. Component Composition

3.1 Form + Dialog Pattern

<Dialog open={isOpen} onOpenChange={setIsOpen}>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Create User</DialogTitle>
      <DialogDescription>
        Add a new user to the system
      </DialogDescription>
    </DialogHeader>
    <UserForm
      mode="create"
      onSuccess={() => {
        setIsOpen(false);
        queryClient.invalidateQueries(['users']);
      }}
      onCancel={() => setIsOpen(false)}
    />
  </DialogContent>
</Dialog>

3.2 Card + Table Pattern

<Card>
  <CardHeader>
    <div className="flex items-center justify-between">
      <div>
        <CardTitle>Users</CardTitle>
        <CardDescription>Manage system users</CardDescription>
      </div>
      <Button onClick={() => router.push('/admin/users/new')}>
        Create User
      </Button>
    </div>
  </CardHeader>
  <CardContent>
    <UserTable />
  </CardContent>
</Card>

3.3 Tabs + Settings Pattern

<Card>
  <CardHeader>
    <CardTitle>Account Settings</CardTitle>
  </CardHeader>
  <CardContent>
    <Tabs defaultValue="profile">
      <TabsList className="grid w-full grid-cols-3">
        <TabsTrigger value="profile">Profile</TabsTrigger>
        <TabsTrigger value="password">Password</TabsTrigger>
        <TabsTrigger value="sessions">Sessions</TabsTrigger>
      </TabsList>
      <TabsContent value="profile">
        <ProfileSettings />
      </TabsContent>
      <TabsContent value="password">
        <PasswordSettings />
      </TabsContent>
      <TabsContent value="sessions">
        <SessionManagement />
      </TabsContent>
    </Tabs>
  </CardContent>
</Card>

3.4 Bulk Actions Pattern

function UserList() {
  const [selectedIds, setSelectedIds] = useState<string[]>([]);
  const { data: users } = useUsers();

  return (
    <div>
      {selectedIds.length > 0 && (
        <BulkActionBar
          selectedIds={selectedIds}
          onAction={handleBulkAction}
          onClearSelection={() => setSelectedIds([])}
        />
      )}
      <DataTable
        data={users}
        columns={columns}
        enableRowSelection
        onRowSelectionChange={setSelectedIds}
      />
    </div>
  );
}

4. Customization

4.1 Theming

Colors are defined in tailwind.config.ts using CSS variables:

// tailwind.config.ts
export default {
  theme: {
    extend: {
      colors: {
        border: 'hsl(var(--border))',
        background: 'hsl(var(--background))',
        foreground: 'hsl(var(--foreground))',
        primary: {
          DEFAULT: 'hsl(var(--primary))',
          foreground: 'hsl(var(--primary-foreground))',
        },
        // ...
      },
    },
  },
};

Customize colors in globals.css:

@layer base {
  :root {
    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;
    /* ... */
  }

  .dark {
    --primary: 210 40% 98%;
    --primary-foreground: 222.2 47.4% 11.2%;
    /* ... */
  }
}

4.2 Component Variants

Add new variants to existing components:

// components/ui/button.tsx
const buttonVariants = cva(
  'base-classes',
  {
    variants: {
      variant: {
        default: '...',
        destructive: '...',
        outline: '...',
        // Add custom variant
        success: 'bg-green-600 text-white hover:bg-green-700',
      },
    },
  }
);

// Usage
<Button variant="success">Activate</Button>

4.3 Extending Components

Create wrapper components:

// components/common/ConfirmDialog.tsx
interface ConfirmDialogProps {
  title: string;
  description: string;
  confirmLabel?: string;
  onConfirm: () => void;
  onCancel: () => void;
}

export function ConfirmDialog({
  title,
  description,
  confirmLabel = 'Confirm',
  onConfirm,
  onCancel,
}: ConfirmDialogProps) {
  return (
    <Dialog open onOpenChange={onCancel}>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>{title}</DialogTitle>
          <DialogDescription>{description}</DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <Button variant="outline" onClick={onCancel}>
            Cancel
          </Button>
          <Button variant="destructive" onClick={onConfirm}>
            {confirmLabel}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

5. Accessibility

5.1 Keyboard Navigation

All shadcn/ui components support keyboard navigation:

  • Tab: Move focus
  • Enter/Space: Activate
  • Escape: Close dialogs/dropdowns
  • Arrow keys: Navigate lists/menus

5.2 Screen Reader Support

Components include proper ARIA labels:

<button aria-label="Close dialog">
  <X className="h-4 w-4" />
</button>

<div role="status" aria-live="polite">
  Loading users...
</div>

<input
  aria-invalid={!!errors.email}
  aria-describedby="email-error"
/>

5.3 Focus Management

Dialog components automatically manage focus:

  • Focus trap inside dialog
  • Return focus on close
  • Focus first focusable element

5.4 Color Contrast

All theme colors meet WCAG 2.1 Level AA standards:

  • Normal text: 4.5:1 contrast ratio
  • Large text: 3:1 contrast ratio

Conclusion

This guide covers the essential components in the project. For more details:

For implementation examples, see FEATURE_EXAMPLES.md.