diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 000000000..29585572e --- /dev/null +++ b/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "shadcn": { + "command": "npx", + "args": ["-y", "shadcn@latest", "mcp"] + } + } +} diff --git a/docs/ui-guidelines.md b/docs/ui-guidelines.md new file mode 100644 index 000000000..224a8c21d --- /dev/null +++ b/docs/ui-guidelines.md @@ -0,0 +1,464 @@ +# HealthLog UI Guidelines + +> Single source of truth for visual & interaction patterns in the HealthLog +> PWA. Consult this document before adding a new component, page, or +> setting. If a pattern below doesn't fit your case, propose an addition +> here in the same PR. + +Last updated: 2026-05-08 (v1.4 cycle) + +--- + +## 1. Foundations + +### 1.1 Theme + +HealthLog is a Dracula-themed app with a light counterpart (Alucard). Both +themes share the same six-color signal palette (purple, pink, green, cyan, +orange, red, yellow, lavender), exposed as CSS variables and as Tailwind +`dracula-*` color tokens. Use the semantic tokens (`--primary`, +`--destructive`, `--card`, …) for chrome and the `--dracula-*` tokens for +data viz only. + +| Surface | Token | +| ---------------------------- | ------------------ | +| App background | `--background` | +| Card / popover surface | `--card` | +| Primary action | `--primary` | +| Destructive action | `--destructive` | +| Secondary / muted background | `--secondary` | +| Border | `--border` | +| Focus ring | `--ring` | +| Chart series 1–5 | `--chart-1`…`-5` | +| Brand signal: purple | `--dracula-purple` | +| Brand signal: green (good) | `--dracula-green` | +| Brand signal: orange (warn) | `--dracula-orange` | +| Brand signal: red (alert) | `--dracula-red` | +| Brand signal: cyan (info) | `--dracula-cyan` | + +Dark mode is the default. The theme switcher in the sidebar three-dot menu +toggles `.dark` / `.light` on `:root`. Always test both modes before +shipping a component. + +### 1.2 Spacing + +Tailwind's default 4-pixel scale. Reach for these stops first: + +| Use | Token | Pixels | +| ------------------------------------ | ------------- | ------ | +| Inline gap between icon and label | `gap-1` | 4 | +| Tight stack inside a card | `gap-2` | 8 | +| Default form-field stack | `space-y-3` | 12 | +| Section block (heading + body) | `space-y-4` | 16 | +| Page section separator | `space-y-6` | 24 | +| Dashboard tile-row gap, hero spacing | `gap-4` | 16 | +| Page-edge padding (mobile / desktop) | `px-4 / px-6` | | + +Avoid arbitrary `space-y-5`, `gap-3.5`, etc. unless aligning to a fixed +external constraint. + +### 1.3 Typography + +The app uses the Tailwind defaults inherited via shadcn/ui. Reach for +these classes first: + +| Use | Class | +| ---------------------------------- | --------------------------------------- | +| Page title (h1) | `text-2xl font-semibold tracking-tight` | +| Section heading (h2) | `text-lg font-semibold` | +| Card title | `text-base font-medium` | +| Body text | `text-sm` | +| Muted helper / metadata | `text-sm text-muted-foreground` | +| Microcopy / footer | `text-xs text-muted-foreground` | +| Tabular data (latency, large nums) | `font-mono tabular-nums` | + +Never use `font-bold` for emphasis — bump to `font-semibold` instead and +let the design system breathe. + +### 1.4 Radius and elevation + +`--radius: 0.625rem` (10px) is the default. The Tailwind tokens +`rounded-md` (`--radius-md` ≈ 8px) and `rounded-lg` (`--radius` ≈ 10px) +cover 95% of cases. Use `rounded-2xl` only for hero cards or large +modals. + +Elevation is achieved via `shadow-xs`, `shadow-sm`, or +`shadow-[0_1px_0_rgba(255,255,255,0.04)]` for very subtle separators on +dark backgrounds. Avoid Tailwind's heavy shadows (`shadow-lg`+) in chrome. + +--- + +## 2. Components + +This section is **prescriptive, not exhaustive**. Each entry pairs a +Radix/shadcn primitive with the cases it's the right answer for and the +cases where another primitive should win. + +### 2.1 Buttons + +Every button comes from `@/components/ui/button`. Pick by purpose: + +| Purpose | Variant | Size | +| ----------------------------------------------------- | ------------- | ----------------- | +| Primary action on a page or dialog | `default` | `default` | +| Cancel, dismiss, secondary action next to primary | `outline` | `default` | +| Tertiary in-row action (edit, copy, snooze) | `ghost` | `sm` | +| Destructive (delete, revoke, disconnect) | `destructive` | `default` or `sm` | +| Icon-only action (toolbar, chart toggles, table rows) | `ghost` | `icon-sm` | +| Inline link to external help docs | `link` | `sm` | +| Toolbar / table row action when space is tight | `outline` | `xs` | + +**Position rules.** + +- Forms (full-width or modal): primary on the **right**, cancel/back on + the **left**. Match shadcn dialog defaults. +- Settings sections: a single sticky `Save` button at the **right end** + of the section header _or_ per-row inline `Save`. Never both in the + same section. +- Test buttons (Telegram test, AI ping, etc.): immediately to the right + of the related Save / Apply control. Never below — keeps the eye + motion horizontal. +- Destructive actions live in their own confirm dialog with a + type-to-confirm input when the action is irreversible (delete account, + revoke API token, drop user data). + +**Disabled state.** A disabled button must always come with a tooltip or +helper text explaining why. Silent disabled buttons frustrate users. + +### 2.2 Inputs and forms + +We use `react-hook-form` + Zod schemas from `src/lib/validations/`. +Wrap inputs in `