# UI Kit

## Purpose

The frontend uses the local UI kit in `frontend/src/components/ui/` as the shared view-layer foundation. Product components should compose these primitives instead of duplicating low-level button, input, table, and status markup.

## Current Foundation

- `button.tsx` with variants in `button-variants.ts`; it also owns shared loading and leading-icon button behavior.
- `input.tsx`, `textarea.tsx`, `native-select.tsx`, `select.tsx`, `checkbox.tsx`, `radio-group.tsx`, and `form.tsx` for form controls.
- `table.tsx` for structured tables.
- `card.tsx`, `alert.tsx`, `badge.tsx`, `tabs.tsx`, `dialog.tsx`, `tooltip.tsx`, and other shadcn-style primitives.
- `state-panel.tsx` with variants in `state-panel-variants.ts` for loading, error, and empty states.
- `module-header.tsx` for repeated module title, icon, and description headers.

## StatePanel Usage

Use `StatePanel` when a feature needs a standard loading, error, or empty block.

```tsx
<StatePanel tone="violet" size="lg" alignment="center" loading>
  Loading assessment content...
</StatePanel>
```

```tsx
<StatePanel tone="red" role="alert">
  Assessment content could not be loaded from the backend.
</StatePanel>
```

Feature-specific wrapper components may stay in their module folder when they carry product copy or workflow-specific props. Those wrappers should delegate the repeated shell to `StatePanel`.

## Loading States: PageSkeleton vs StatePanel

There are two loading scopes, and they must not be mixed within one navigation:

- **Page-scope load** — the whole page (or its primary content) is unavailable until the fetch resolves. Use `PageSkeleton` (`components/ui/page-skeleton.tsx`), early-returned from the page/wrapper. `ModuleRouteGuard` already renders the same `PageSkeleton` as the lazy-route Suspense fallback, so the chunk load and the data load read as **one continuous skeleton** instead of a skeleton that flips into a spinner. Do not use a `StatePanel` spinner for this.

  ```tsx
  if (page.isLoading) {
    return <PageSkeleton />;
  }
  ```

- **Local/section load** — the page chrome (header, filters, interactive controls) renders immediately and only a secondary region is still fetching. Keep a `StatePanel` spinner (or a `Button` busy state) scoped to that region. Examples: a results grid below interactive filters, a secondary compliance panel, a per-tab fetch.

Rule of thumb: if loading hides everything behind a static header, it is page-scope → `PageSkeleton`. If interactive chrome is already usable while one area loads, it is local → `StatePanel`.

## Button Usage

Use `Button` for new clickable commands. Prefer `leadingIcon`, `loading`, and `loadingLabel` instead of duplicating inline spinner branches.

```tsx
<Button loading={saving} loadingLabel="Saving..." leadingIcon={<Save size={14} />}>
  Save
</Button>
```

Feature components may pass product-specific `className` values while the shared component owns disabled and busy behavior.

## Table Usage

Use `Table`, `TableHeader`, `TableBody`, `TableRow`, `TableHead`, and `TableCell` for structured table markup. Preserve feature-specific density and colors with `className`; do not duplicate native `<table>` shells in new code.

## Form Controls

Use `Input` for text-like controls and `NativeSelect` for simple browser-native select fields. Use the Radix `Select` primitive only when the product requires custom dropdown behavior.

## Module Headers

Use `ModuleHeader` for repeated module-level title blocks with a square icon and short description. Feature headers may still render additional callouts or actions around it.

## Rules

- Keep UI variants and class maps in dedicated non-component files.
- Use `StatePanel` for repeated error/empty panels and **local/section** loading; use `PageSkeleton` for **page-scope** loading so it stays continuous with the route Suspense fallback (see Loading States above).
- Use existing `Button`, `Input`, `Textarea`, `Select`, and `Table` primitives for new view code.
- Use `NativeSelect` for simple select fields where native browser behavior is sufficient.
- Use `ModuleHeader` for repeated page/module headings instead of duplicating title/icon markup.
- Do not add another external UI kit unless the existing primitives cannot support a concrete product requirement.
- Do not move business rules into UI primitives. UI kit components should stay presentation-focused.

## Remaining Consolidation Candidates

- Repeated statistic cards can be promoted to a shared metric component after at least two active modules need the same prop shape.
- New structured table views should use `table.tsx`; existing feature tables can switch to it when product work touches those modules.
- Module-specific wrapper components should stay local until the same component contract is repeated across active modules.
