julien 10c80f189d
CI / commits (push) Has been skipped
CI / scan (push) Successful in 2m50s
CI / check (push) Successful in 3m24s
CI / a11y (push) Successful in 1m9s
CI / perf (push) Successful in 4m59s
feat(portal-shell): align theme switcher trigger with locale switcher shape (#164)
## Summary

Aligns the theme-switcher trigger on the same chip shape as the locale-switcher. Both controls live side-by-side in the footer's device-prefs cluster (#162); the visual mismatch (round icon button vs. chip) made them read as two unrelated widgets rather than one family.

```
Before:  [☀]                       (round icon button, 44×44)
After:   [☀ System ▾]              (chip — icon + label + chevron)
```

The leading icon stays driven by `currentIcon()` so a **sun / moon / monitor** glyph still flips with the selected mode — that's the visual feedback the original icon-only design existed for, and it's preserved.

## What lands

### [`theme-switcher.html`](apps/portal-shell/src/app/components/theme-switcher/theme-switcher.html)

The round Tailwind-utility icon button becomes a `.theme-switcher__trigger` chip with three children — current-mode icon (size 14), localised mode label, `chevron-down` (size 12). Same children layout as `.locale-switcher__trigger`.

### [`theme-switcher.scss`](apps/portal-shell/src/app/components/theme-switcher/theme-switcher.scss)

Adds `.theme-switcher__trigger` mirroring `.locale-switcher__trigger` rule-for-rule: same chip metrics (min-height 2.75rem for the AAA 44×44 tap target via vertical-padding overflow inside the thin footer), same hover/focus tokens, same dark-mode swap.

Two copies rather than a shared `_chip-trigger.scss` partial — two switchers in one app is below the "three similar things" threshold CLAUDE.md sets for extraction. Promotes when a third switcher lands.

## Notes for the reviewer

- **No spec changes**. The existing assertions check `button[aria-haspopup="menu"]` + the aria-label content (`"Theme: <current> (open menu)"`). Both preserved — the trigger button still has `aria-haspopup="menu"` from `cdkMenuTriggerFor`, and `triggerAriaLabel()` is unchanged.
- **No new i18n strings**. The chip text reuses `currentLabel()`, which already returns `Light` / `Dark` / `System` from the existing `theme.mode.{light,dark,system}` translation units (they were used in the dropdown menu items before this PR; now they also drive the trigger label). Prod i18n-strict build passes.
- **Why mirror, not refactor into a shared partial?** Two switchers, identical chip shape — extracting now would be premature abstraction per [CLAUDE.md](CLAUDE.md). When a third switcher lands (an accessibility-panel toggle is the most likely candidate per [ADR-0016](docs/decisions/0016-accessibility-baseline-wcag-aa-targeted-aaa.md)'s preferences-panel plan), `_chip-trigger.scss` or a `<lib-chip-trigger>` component starts to pay off. The two copies today are mechanical and live next door — easy to keep in sync.
- **No `portal-admin` impact.** No theme switcher there (admin chrome is brand-primary-600 hardcoded for now); no change needed.

## Test plan

- [x] `pnpm nx run-many -t lint test build --projects=portal-shell` — green; **43 specs pass** (unchanged from main).
- [x] `pnpm nx build portal-shell --configuration=production` — i18n-strict prod build passes.
- [ ] **Manual visual smoke** — `pnpm nx serve portal-shell`, open the home page in a browser, confirm:
  - The theme switcher in the bottom-right of the footer is now a chip with the current mode icon + label + chevron, matching the locale chip beside it.
  - Hovering both switchers shows the same brand-primary hover color (light + dark mode).
  - Clicking the theme chip still opens the menu with three options (Light / Dark / System), the current option still carries a `✓` check.
  - Selecting Dark → trigger icon flips to moon + label flips to "Dark" + page goes dark. Switching to System: trigger icon flips to monitor + label flips to "System".
  - Tabbing across the footer hits accessibility link → locale → theme in that order, focus rings are identical between the two switchers.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #164
2026-05-16 02:52:53 +02:00

Documentation index

This is the entry point to all project documentation. It is maintained automatically: any addition, rename, or removal of a .md file under docs/ must be reflected here in the same change.

Conventions

  • Documentation is written in English.
  • One topic per file. Group related files into a folder when there are three or more.
  • Cross-reference with relative links so they keep working in GitHub, IDEs, and exported sites.
  • For architectural decisions, do not add them here — they belong in decisions/ as MADR 4.0.0 ADRs.

Sections

Daily development

  • development.md — repo layout, prerequisites, initial setup, daily commands, observability dev-loop (Jaeger UI, log ↔ trace correlation), dependency updates (Renovate), conventional commit cycle. Day-to-day reference for working on the project.

Architecture

  • architecture.md — cross-cutting Mermaid diagrams: C4 system context, C4 containers, Nx module boundaries, CI/CD pipeline. Single-decision diagrams (auth sequence, ERD, etc.) live inline in their ADR.

Onboarding & environment

Setup guides for new contributors:

Operations & runbooks

Empty — to be populated when we deploy.

Security, performance, accessibility

Empty — placeholders to be filled with rationale docs alongside their corresponding ADRs.

S
Description
No description provided
Readme 42 MiB
Languages
TypeScript 85.3%
JavaScript 5.4%
SCSS 4.3%
HTML 3.9%
Shell 0.8%
Other 0.3%