154 lines
10 KiB
Markdown
154 lines
10 KiB
Markdown
# Junior Developer — Styling Plan
|
||
|
||
**Owner:** Junior developer (the filename says it — this is frontend craft, not backend)
|
||
**Status:** Ready to implement (best **after** `frontend_mvp_complete.md` Tier 1, so the header/nav exist to style; the foundation in S1 can start anytime)
|
||
**Goal:** Turn the unstyled MVP into a calm, trustworthy, readable app — without rewriting components.
|
||
|
||
> **Big win you already have:** every component uses consistent BEM-style class names (`dashboard__section`, `calendar__grid`, `progress-chart__fill`, `settings-page__message--success`, …) but there is **no CSS at all** and nothing is imported. So you can style the *entire* app from stylesheets keyed to those existing classes with **almost zero JSX changes**. Don't refactor markup to style it — write CSS that targets what's there.
|
||
|
||
---
|
||
|
||
## S0 — Decisions (don't introduce a new build dependency)
|
||
|
||
- **Use plain CSS with design tokens (CSS custom properties).** ARCHITECTURE already anticipates `src/styles/global.css` + `src/styles/components.module.css`. Match that.
|
||
- **Do NOT add Tailwind / styled-components / a UI kit** mid-MVP. They'd mean rewriting every `className` and adding build config — high churn, no payoff right now. (If the team later wants a framework, that's a separate, deliberate migration.)
|
||
- Keep it light: one global stylesheet for tokens + base + layout + shared component classes. Reach for CSS Modules (`*.module.css`) only if a component needs scoping that the BEM names don't already give you.
|
||
- **Tone:** this app touches family + money, so aim for *calm and reassuring*, not flashy — generous whitespace, soft neutral background, one trustworthy accent color, clear hierarchy. ARCHITECTURE calls the calendar "Google Calendar-like," so use that as the visual north star for the calendar.
|
||
|
||
---
|
||
|
||
## S1 — Foundation: tokens, reset, typography
|
||
|
||
**Create `src/styles/global.css` and import it once** at the top of `src/main.tsx` (`import './styles/global.css'`).
|
||
|
||
**Define design tokens** as CSS variables on `:root` so everything else references them (change once, update everywhere). Starter set — tune the palette to taste:
|
||
|
||
```css
|
||
:root {
|
||
/* Color */
|
||
--c-bg: #f7f8fa;
|
||
--c-surface: #ffffff;
|
||
--c-border: #e3e6eb;
|
||
--c-text: #1c2733;
|
||
--c-text-muted: #5b6675;
|
||
--c-accent: #2f6f4f; /* calm green — trustworthy, on-brand for "leave/time at home" */
|
||
--c-accent-weak: #e8f1ec;
|
||
--c-danger: #b3261e;
|
||
--c-danger-weak: #fdeceb;
|
||
--c-success: #1e7d4f;
|
||
--c-success-weak: #e7f4ec;
|
||
--c-warning: #8a6d1f;
|
||
|
||
/* Spacing scale (8px base) */
|
||
--sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
|
||
--sp-5: 24px; --sp-6: 32px; --sp-8: 48px;
|
||
|
||
/* Type scale */
|
||
--fs-sm: 0.875rem; --fs-base: 1rem; --fs-lg: 1.25rem;
|
||
--fs-xl: 1.5rem; --fs-2xl: 2rem;
|
||
--lh: 1.5;
|
||
|
||
/* Shape & depth */
|
||
--radius: 8px;
|
||
--shadow-sm: 0 1px 2px rgba(16,24,40,0.06);
|
||
--shadow-md: 0 4px 12px rgba(16,24,40,0.08);
|
||
|
||
--maxw: 960px; /* content column width */
|
||
color-scheme: light;
|
||
}
|
||
```
|
||
|
||
Then a small reset + base: `*, *::before, *::after { box-sizing: border-box }`, zero default margins, set `body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; background: var(--c-bg); color: var(--c-text); line-height: var(--lh); }`, a sensible type scale on `h1–h3`, and a centered content container.
|
||
|
||
**Acceptance:** importing one file visibly transforms the whole app — proper font, background, spacing, readable headings — with no component edits.
|
||
|
||
---
|
||
|
||
## S2 — App shell: header, nav, footer, page container
|
||
|
||
Targets the layout classes (`[data-testid="layout"]`, `[data-testid="header"]`, `header-nav`, `[data-testid="footer"]`, `.dashboard`, `.settings-page`, etc.) and the header built in `frontend_mvp_complete.md` T1.1.
|
||
|
||
**Do:**
|
||
- Sticky top header: app name/title left, primary nav center/right (Översikt / Kalender / Tips / Inställningar), and the user + child selector + "Logga ut" on the far right. Give the **active nav link** a clear state (use `NavLink`'s active class).
|
||
- Style the **child selector** as a clean dropdown so "which child am I viewing" reads at a glance.
|
||
- Constrain main content to `--maxw`, centered, with comfortable page padding.
|
||
- Quiet footer (muted text, top border).
|
||
- Make every page sit inside this shell consistently (Layout already wraps them).
|
||
|
||
**Acceptance:** A coherent top bar on every page with obvious current-page, current-child, and logout; content is centered and comfortably padded.
|
||
|
||
---
|
||
|
||
## S3 — Cards, sections, metrics, tables
|
||
|
||
Targets `.dashboard__section`, `[data-testid="overview-metrics"]`, `.days-breakdown__table`, `.dashboard__break-list/__break-item`.
|
||
|
||
**Do:**
|
||
- Turn each `dashboard__section` / `settings-page__section` into a **card**: `background: var(--c-surface)`, `border: 1px solid var(--c-border)`, `border-radius: var(--radius)`, padding `--sp-5`, `box-shadow: var(--shadow-sm)`, vertical rhythm between cards.
|
||
- **Overview metrics:** lay the metric list out as a responsive row of stat blocks — big number (`--fs-2xl`, accent or bold), small muted label beneath. This is the hero of the dashboard; make the numbers legible.
|
||
- **Tables** (`days-breakdown__table`): full width, subtle row separators, padded cells, muted bold header row, right-align numeric columns.
|
||
- Style the **provenance/freshness line** from polish P1 as small muted text.
|
||
|
||
**Acceptance:** The dashboard reads as a set of clean cards; the key day numbers stand out; tables are tidy.
|
||
|
||
---
|
||
|
||
## S4 — Buttons, forms, inputs, messages
|
||
|
||
Targets `.form-group`, `.error-message`, `.settings-page__message--success/--error`, `.parent-profile-input*`, `.planning-goal-selector*`, `.break-periods-input*`, `.vacation-days-input` (and the bare `<button>`s on the dashboard).
|
||
|
||
**Do:**
|
||
- A shared button style: primary (accent bg, white text), secondary/ghost (border, surface bg), and a disabled state (the FK button is disabled until a child is selected — make that visually obvious). Add `:hover`, `:active`, and a visible `:focus-visible` outline.
|
||
- Inputs/selects: consistent height, padding, border, radius; clear `:focus` ring using the accent; label spacing via `.form-group`.
|
||
- Message banners: `--success-weak`/`--danger-weak` backgrounds with matching text + a left accent border. Style `error-message` and the settings save messages.
|
||
- Fieldsets (planning goal, parent profile) as labeled groups with breathing room.
|
||
|
||
**Acceptance:** All buttons/inputs share one consistent look; success/error feedback is color-coded and unmistakable; focus states are visible for keyboard users.
|
||
|
||
---
|
||
|
||
## S5 — Calendar (the "Google Calendar-like" view) — ⚠️ SUPERSEDED
|
||
|
||
> **SUPERSEDED by `junior_calendar_fix.md`.** The calendar is being fixed as its own focused task, which already covers the 7-column grid, day cells, today/weekend/adjacent-month states, the legend, plus correctness fixes (Monday-first Swedish week, default-to-current-month) and tests. **Do not restyle the calendar from this section** — you'll collide with that work.
|
||
>
|
||
> **Only remaining action here:** once `junior_calendar_fix.md` has shipped *and* `global.css` tokens (S1) exist, reconcile the calendar's co-located `calendar.css` to reference the tokens (`--c-*`, `--sp-*`, `--radius`) and define per-type color tokens (`--type-sjukpenning`, `--type-lagsta`, `--type-vacation`, plus a dubbeldagar marker) so cell chips and the legend stay in sync with the rest of the app. No layout or markup changes.
|
||
|
||
The original styling targets, kept for reference during that token reconciliation: `.calendar`, `.calendar__header`, `.calendar__grid`, `.calendar__weekday`, `.calendar-day*`, `.calendar-event*`, `.calendar__legend*`.
|
||
|
||
**Acceptance:** Calendar colors/spacing reference S1 tokens; leave-type colors match between cells and legend. (Layout, states, and behavior are owned by `junior_calendar_fix.md`.)
|
||
|
||
---
|
||
|
||
## S6 — Modal & loading states
|
||
|
||
Targets the `QRCodeModal` (built in the MVP rounds) and `LoadingSpinner`.
|
||
|
||
**Do:**
|
||
- Modal: centered card over a dimmed full-screen overlay (`position: fixed`, semi-transparent backdrop), `--shadow-md`, max-width, padding; the **QR image sized large and crisp** (don't let it blur — it must be scannable); clear status text and a cancel/close button. Disable/hide close while a session is running (the component already gates this).
|
||
- Spinner: simple CSS keyframe spinner using the accent, with the muted message beneath, centered in the content area.
|
||
|
||
**Acceptance:** The FK fetch modal looks intentional and the QR is comfortably scannable; loading states are centered and calm.
|
||
|
||
---
|
||
|
||
## S7 — Responsive & accessibility pass
|
||
|
||
**Do:**
|
||
- One mobile breakpoint (~640px): header nav collapses to a simple stacked/wrapped layout; metric row and form grids stack to one column; calendar stays usable (allow horizontal scroll or shrink cells — don't break the 7-column grid).
|
||
- A11y: ensure text/background contrast meets WCAG AA (check the accent on white and white on accent), keep a visible `:focus-visible` outline everywhere, and respect `@media (prefers-reduced-motion: reduce)` by disabling the spinner/transition animations.
|
||
|
||
**Acceptance:** Usable on a narrow viewport without horizontal chaos; keyboard focus is always visible; contrast passes AA; reduced-motion is honored.
|
||
|
||
---
|
||
|
||
## Sequencing & guardrails
|
||
1. **S1 → S2 → S3 → S4** give the biggest visible lift fastest (foundation + shell + cards + controls).
|
||
2. **S5 (calendar) is superseded** by `junior_calendar_fix.md` — which is being delivered **first**, before this styling plan. Don't restyle the calendar here; the only S5 task left is the token reconciliation noted in that section, done *after* both this plan's S1 and the calendar fix have landed.
|
||
3. **S6, S7** finish it off.
|
||
|
||
- **Don't change component markup or `data-testid`s** to style — tests rely on them. Style via the existing classes; only add a `className` where one is genuinely missing.
|
||
- Keep all colors/spacing referencing the **tokens** from S1 — no hardcoded hex scattered through the file.
|
||
- After each section: `npm run lint`, `npm test` (should stay green — CSS shouldn't break tests; if a test asserts on inline styles, reconcile), and eyeball the page.
|
||
|
||
**Out of scope:** dark mode, animations beyond basic transitions, a component-library migration, custom fonts/branding assets — all separate later passes if desired.
|