feat(frontend): unify editorial UI and DRY form architecture
This commit is contained in:
parent
d4fbc1faf5
commit
693c6a9626
35 changed files with 2600 additions and 1180 deletions
189
docs/frontend-design-cookbook.md
Normal file
189
docs/frontend-design-cookbook.md
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
# Frontend Design Cookbook
|
||||
|
||||
This cookbook defines the visual system for the frontend so every new change extends the existing style instead of inventing a new one.
|
||||
|
||||
## Design intent
|
||||
|
||||
- Core tone: light editorial, calm and information-first.
|
||||
- Product feel: premium personal logbook, not generic SaaS dashboard.
|
||||
- Contrast model: neutral paper and ink do most of the work; accents are restrained.
|
||||
|
||||
## Non-negotiables
|
||||
|
||||
- Keep layouts readable first. Aesthetic details support hierarchy, not the other way around.
|
||||
- Use one visual language across the app shell, cards, forms, tables, and actions.
|
||||
- Prefer subtle depth (borders, layered paper, soft shadows) over loud gradients.
|
||||
- Keep motion purposeful and short; always preserve `prefers-reduced-motion` behavior.
|
||||
|
||||
## Typography
|
||||
|
||||
- Display/headings: `Cormorant Infant`.
|
||||
- Body/UI text: `Manrope`.
|
||||
- Use display typography for page titles and section heads only.
|
||||
- Keep paragraph text in body font for legibility.
|
||||
|
||||
## Color system
|
||||
|
||||
Global neutrals are defined in `frontend/src/app.css` using CSS variables.
|
||||
|
||||
- `--background`, `--card`, `--foreground`, `--muted-foreground`, `--border`
|
||||
- `--page-accent` drives route-level emphasis.
|
||||
- `--page-accent-soft` is the low-contrast companion tint.
|
||||
|
||||
### Domain accents (muted and professional)
|
||||
|
||||
- Dashboard: `--accent-dashboard`
|
||||
- Products: `--accent-products`
|
||||
- Routines: `--accent-routines`
|
||||
- Skin: `--accent-skin`
|
||||
- Health labs: `--accent-health-labs`
|
||||
- Health medications: `--accent-health-meds`
|
||||
|
||||
The app shell assigns a domain class per route and maps it to `--page-accent`.
|
||||
|
||||
## Where to use accent color
|
||||
|
||||
Use accent for:
|
||||
|
||||
- active navigation state
|
||||
- important buttons and key badges
|
||||
- focus ring tint and hover border tint
|
||||
- section separators and small status markers
|
||||
|
||||
Do not use accent for:
|
||||
|
||||
- full-page backgrounds
|
||||
- body text
|
||||
- large surfaces that reduce readability
|
||||
|
||||
Guideline: accent should occupy roughly 10-15% of visual area per screen.
|
||||
|
||||
## Layout rules
|
||||
|
||||
- Use the app shell spacing rhythm from `app.css` (`.app-main`, editorial cards).
|
||||
- Keep max content width constrained for readability.
|
||||
- Prefer asymmetry in hero/summary areas, but keep forms and dense data grids regular.
|
||||
|
||||
### Shared page wrappers
|
||||
|
||||
Use these wrappers before introducing route-specific structure:
|
||||
|
||||
- `editorial-page`: standard constrained content width for route pages.
|
||||
- `editorial-hero`: top summary strip for title, subtitle, and primary actions.
|
||||
- `editorial-panel`: primary surface for forms, tables, and ledgers.
|
||||
- `editorial-toolbar`: compact action row under hero copy.
|
||||
- `editorial-backlink`: standard top-left back navigation style.
|
||||
- `editorial-alert`, `editorial-alert--error`, `editorial-alert--success`: feedback banners.
|
||||
|
||||
## Component rules
|
||||
|
||||
- Reuse UI primitives under `frontend/src/lib/components/ui/*`.
|
||||
- Keep primitive APIs stable when changing visual treatment.
|
||||
- Style via tokens and shared classes, not one-off hardcoded colors.
|
||||
- New variants must be documented in this file.
|
||||
|
||||
### Existing shared utility patterns
|
||||
|
||||
These classes are already in use and should be reused:
|
||||
|
||||
- Lists and ledgers: `routine-ledger-row`, `products-mobile-card`, `health-entry-row`
|
||||
- Group headers: `products-section-title`
|
||||
- Table shell: `products-table-shell`
|
||||
- Tabs shell: `products-tabs`, `editorial-tabs`
|
||||
- Health semantic pills: `health-kind-pill*`, `health-flag-pill*`
|
||||
|
||||
## Forms and data views
|
||||
|
||||
- Inputs should remain high-contrast and calm.
|
||||
- Validation/error states should be explicit and never color-only.
|
||||
- Tables and dense lists should prioritize scanning: spacing, row separators, concise metadata.
|
||||
|
||||
### DRY form primitives
|
||||
|
||||
- Use shared form components for repeated native select markup:
|
||||
- `frontend/src/lib/components/forms/SimpleSelect.svelte`
|
||||
- `frontend/src/lib/components/forms/GroupedSelect.svelte`
|
||||
- Use shared checkbox helper for repeated label+hint toggles:
|
||||
- `frontend/src/lib/components/forms/HintCheckbox.svelte`
|
||||
- Use shared input field helper for repeated label+input rows:
|
||||
- `frontend/src/lib/components/forms/LabeledInputField.svelte`
|
||||
- Use shared section card helper for repeated titled form panels:
|
||||
- `frontend/src/lib/components/forms/FormSectionCard.svelte`
|
||||
- Use shared class tokens from:
|
||||
- `frontend/src/lib/components/forms/form-classes.ts`
|
||||
- Prefer passing option labels from route files via `m.*` to keep i18n explicit.
|
||||
|
||||
### Select policy (performance + maintainability)
|
||||
|
||||
- Default to native `<select>` for enum-style fields and simple pickers.
|
||||
- Use `SimpleSelect` / `GroupedSelect` for consistency and to reduce duplication.
|
||||
- Avoid `ui/select` primitives unless search, custom keyboard behavior, or richer popup UX is truly needed.
|
||||
- For grouped option sets (e.g. products by category), use `<optgroup>` via `GroupedSelect`.
|
||||
|
||||
### Chunk hygiene
|
||||
|
||||
- Large forms should be split into focused section components.
|
||||
- Lazy-load heavyweight modal and optional sections where practical.
|
||||
- After structural UI refactors, run `pnpm build` and inspect output for large chunks.
|
||||
- Track and prevent regressions in known hotspots (form-heavy pages and shared interaction primitives).
|
||||
|
||||
## Motion
|
||||
|
||||
- Entry animations: short upward reveal with slight stagger.
|
||||
- Hover interactions: subtle translation or tint only.
|
||||
- Never stack multiple strong animations in the same viewport.
|
||||
|
||||
## Accessibility baseline
|
||||
|
||||
- Keyboard navigation for all interactive elements.
|
||||
- Visible focus states on controls and links.
|
||||
- Respect reduced motion.
|
||||
- Maintain readable contrast for text, borders, and controls.
|
||||
|
||||
## i18n rules
|
||||
|
||||
- Do not add hardcoded UI labels in route components when a message key already exists.
|
||||
- Prefer existing `m.*` keys from paraglide for headings, labels, empty states, and helper text.
|
||||
- If a new label is required, add a new translation key instead of shipping English literals.
|
||||
- Keep fallback display values localized (`m.common_unknown()` over hardcoded `n/a`).
|
||||
- Avoid language-specific toggle text in reusable components unless fully localized.
|
||||
|
||||
## Implementation checklist for new UI work
|
||||
|
||||
1. Pick the route domain and rely on `--page-accent`.
|
||||
2. Use existing typography pair and spacing rhythm.
|
||||
3. Build with primitives first; add route-level wrappers only if needed.
|
||||
4. Validate mobile and desktop layout.
|
||||
5. Run:
|
||||
- `pnpm check`
|
||||
- `pnpm lint`
|
||||
- `pnpm build` (for significant component architecture changes)
|
||||
|
||||
## File touchpoints
|
||||
|
||||
- Core tokens and global look: `frontend/src/app.css`
|
||||
- App shell and route domain mapping: `frontend/src/routes/+layout.svelte`
|
||||
- Route examples using the pattern:
|
||||
- `frontend/src/routes/+page.svelte`
|
||||
- `frontend/src/routes/products/+page.svelte`
|
||||
- `frontend/src/routes/routines/+page.svelte`
|
||||
- `frontend/src/routes/health/lab-results/+page.svelte`
|
||||
- `frontend/src/routes/skin/+page.svelte`
|
||||
- Primitive visuals:
|
||||
- `frontend/src/lib/components/ui/button/button.svelte`
|
||||
- `frontend/src/lib/components/ui/card/card.svelte`
|
||||
- `frontend/src/lib/components/ui/input/input.svelte`
|
||||
- `frontend/src/lib/components/ui/badge/badge.svelte`
|
||||
|
||||
- Shared form DRY helpers:
|
||||
- `frontend/src/lib/components/forms/SimpleSelect.svelte`
|
||||
- `frontend/src/lib/components/forms/GroupedSelect.svelte`
|
||||
- `frontend/src/lib/components/forms/HintCheckbox.svelte`
|
||||
- `frontend/src/lib/components/forms/LabeledInputField.svelte`
|
||||
- `frontend/src/lib/components/forms/FormSectionCard.svelte`
|
||||
- `frontend/src/lib/components/forms/form-classes.ts`
|
||||
|
||||
- i18n message source:
|
||||
- `frontend/src/lib/paraglide/messages/*`
|
||||
|
||||
When introducing new visual patterns, update this cookbook in the same change.
|
||||
Loading…
Add table
Add a link
Reference in a new issue