feat(shared): ThemeToggle + Logo + design system rules (Sprint 0.5 étape 8)
This commit is contained in:
parent
7dfd0df6b3
commit
ee6d679950
4 changed files with 120 additions and 18 deletions
|
|
@ -143,6 +143,24 @@ Voir `SECURITY.md` pour le détail.
|
|||
Claude Code ne crée jamais de worktree Git.
|
||||
Toutes les modifications se font directement dans le dossier du projet principal.
|
||||
|
||||
### Règle L — Tokens du design system, jamais de valeurs brutes
|
||||
Toutes les couleurs dans le JSX passent exclusivement par les tokens Direction H :
|
||||
- Utilitaires Tailwind : `bg-canvas`, `text-ink-2`, `border-line`, `bg-expria`, `text-danger`, etc.
|
||||
- Jamais de classes couleur Tailwind par défaut : `bg-slate-100`, `text-gray-500`, `blue-600`…
|
||||
- Jamais de valeurs inline brutes : `#1B4FD8`, `oklch(…)`, `rgb(…)` dans les className ou style
|
||||
- Pour les inline styles dynamiques uniquement : `style={{ background: 'var(--color-expria)' }}`
|
||||
- Tout nouveau token est ajouté exclusivement dans `@theme {}` (et `.dark {}`) de `src/index.css`
|
||||
|
||||
```tsx
|
||||
// ❌ JAMAIS
|
||||
<div className="bg-blue-600 text-gray-500" />
|
||||
<div style={{ color: '#1B4FD8' }} />
|
||||
|
||||
// ✅ TOUJOURS
|
||||
<div className="bg-expria text-ink-3" />
|
||||
<div style={{ color: 'var(--color-expria)' }} /> {/* inline style dynamique uniquement */}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Structure du code — conventions
|
||||
|
|
@ -461,3 +479,4 @@ Avant chaque session Claude Code, vérifier :
|
|||
| Version | Date | Changements |
|
||||
|---|---|---|
|
||||
| 1.0 | 2026-04-17 | Création, adaptée de la version backend |
|
||||
| 1.1 | 2026-04-18 | Ajout Règle L — tokens du design system (Sprint 0.5) |
|
||||
|
|
|
|||
|
|
@ -123,30 +123,41 @@ Vient du pattern `c.json(result, result.status)` où `result` contient déjà `s
|
|||
|
||||
---
|
||||
|
||||
### FTD-11 — `@theme` Tailwind 4 non défini
|
||||
**Priorité :** 🟢 Mineur
|
||||
**Statut :** Ouvert — à faire dans session design system
|
||||
**Estimation de session :** 1 jour (palette + typo + itérations design)
|
||||
**Description :** `src/index.css` a été nettoyé à l'étape 11 du Sprint 0 et réduit à la seule ligne `@import 'tailwindcss';`. L'ADR 006 (§Configuration Tailwind 4) décrit le bloc `@theme { ... }` comme le mécanisme officiel de configuration Tailwind 4 (CSS-first) :
|
||||
### FTD-14 — Anti-FOUC thème : script inline manquant dans `<head>`
|
||||
**Priorité :** 🟡 Important
|
||||
**Statut :** Ouvert — à faire avant déploiement production
|
||||
**Estimation de session :** 30 min
|
||||
**Description :** Le `ThemeProvider` applique la classe `.dark` sur `<html>` après l'hydratation React (`useEffect`). Entre le premier paint du navigateur et l'exécution de React, la page s'affiche brièvement en mode clair même si l'utilisateur a choisi le mode sombre — c'est le FOUC (Flash Of Unstyled Content).
|
||||
|
||||
```css
|
||||
@theme {
|
||||
--color-primary: #1B4FD8;
|
||||
--font-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
|
||||
}
|
||||
**Fix :** ajouter un script inline bloquant dans le `<head>` de `index.html` qui lit `localStorage.getItem('expria-theme')` (et `prefers-color-scheme` en fallback) et applique `.dark` sur `document.documentElement` avant le premier paint. Ce script doit être minifié et inliné (non-async, non-defer) pour garantir l'exécution avant le CSS.
|
||||
|
||||
```html
|
||||
<script>
|
||||
(function(){var t=localStorage.getItem('expria-theme');
|
||||
if(t==='dark'||(t!=='light'&&matchMedia('(prefers-color-scheme:dark)').matches))
|
||||
document.documentElement.classList.add('dark')})()
|
||||
</script>
|
||||
```
|
||||
|
||||
La palette brand Expria et la typographie ne sont pas encore décidées, donc `@theme` est volontairement absent pour ne pas poser de valeurs placeholder qu'il faudrait repasser plus tard.
|
||||
**Impact actuel :** visible uniquement pour les utilisateurs en mode sombre — bref flash de fond clair au chargement. Acceptable en dev, indésirable en production.
|
||||
|
||||
**Impact actuel :** les composants utilisent les couleurs Tailwind par défaut (`bg-slate-*`, `text-gray-*`). Visuellement cohérent mais pas brand.
|
||||
**Condition de résolution :** avant la première mise en production (Sprint 1 ou avant).
|
||||
|
||||
**À faire au Sprint 1 (design system) :**
|
||||
- Définir la palette brand Expria (primary, secondary, neutrals, danger, success)
|
||||
- Choisir + installer la typo (Plus Jakarta Sans ou autre) via `<link>` dans `index.html` ou `@import` dans `index.css`
|
||||
- Ajouter le bloc `@theme` dans `src/index.css`
|
||||
- Mettre à jour ADR 006 avec les valeurs retenues
|
||||
---
|
||||
|
||||
**Condition de résolution :** Sprint 1 — session dédiée au design system.
|
||||
### FTD-15 — Option `'system'` manquante dans ThemeProvider
|
||||
**Priorité :** 🟢 Mineur
|
||||
**Statut :** Reporté — après MVP
|
||||
**Estimation de session :** 2h
|
||||
**Description :** Le `ThemeProvider` est bi-state (`'light' | 'dark'`). L'option `'system'` (qui suit `prefers-color-scheme` en temps réel via `MediaQueryList.addEventListener`) a été volontairement différée (décision Sprint 0.5).
|
||||
|
||||
**À faire :**
|
||||
- Étendre le type `Theme` à `'light' | 'dark' | 'system'`
|
||||
- Dans `ThemeProvider`, si `theme === 'system'` : écouter `matchMedia('(prefers-color-scheme: dark)')` et appliquer/retirer `.dark` dynamiquement
|
||||
- `ThemeToggle` : cycle light → dark → system (ou un sélecteur 3 états)
|
||||
- Mettre à jour `getInitialTheme()` pour retourner `'system'` si aucune préférence stockée
|
||||
|
||||
**Condition de résolution :** après MVP — confort utilisateur, pas bloquant.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -244,6 +255,7 @@ La palette brand Expria et la typographie ne sont pas encore décidées, donc `@
|
|||
|
||||
| ID | Description | Résolu le | Comment |
|
||||
|---|---|---|---|
|
||||
| FTD-11 | `@theme` Tailwind 4 non défini — palette et typographie absentes | 2026-04-18 | Résolu au Sprint 0.5 (design system). Palette Direction H complète (canvas/surface/ink/expria/deep/semantic) + typo Plus Jakarta Sans définis dans `src/index.css` via `@theme {}` et `.dark {}`. shadcn/ui remappé sur ces tokens. Règle L ajoutée dans `DEVELOPMENT_PRINCIPLES.md` pour garantir l'usage exclusif des tokens. |
|
||||
| FTD-13 | Incompatibilité Vitest 3 / Vite 8 (conflit de types `Plugin<any>` entre le Vite 8 top-level avec Rolldown et le Vite 7 pinné de Vitest 3.2.4 ; `npm run build` cassé) | 2026-04-17 | Résolu par upgrade Vitest `3.2.4 → 4.1.4` (et `@vitest/coverage-v8` idem) à l'étape 12-bis du Sprint 0. Vitest 4.x supporte nativement Vite 8 Rolldown. Correctif complémentaire : script `typecheck` passé de `tsc --noEmit -p tsconfig.app.json` à `tsc -b --noEmit` pour couvrir aussi `tsconfig.node.json` (d'où `vite.config.ts`) et éviter qu'un bug similaire échappe à la CI. |
|
||||
|
||||
---
|
||||
|
|
@ -255,3 +267,4 @@ La palette brand Expria et la typographie ne sont pas encore décidées, donc `@
|
|||
| 1.0 | 2026-04-17 | Création initiale avec 9 FTD identifiées depuis l'audit backend et les décisions d'architecture |
|
||||
| 1.1 | 2026-04-17 | Ajout FTD-10 (Semgrep CI), FTD-11 (`@theme` Tailwind 4), FTD-12 (tests `api-client`) suite à l'étape 11 du Sprint 0 |
|
||||
| 1.2 | 2026-04-17 | Ajout FTD-13 résolu (incompatibilité Vitest 3 / Vite 8) suite à l'étape 12-bis du Sprint 0 |
|
||||
| 1.3 | 2026-04-18 | FTD-11 résolu (design system Sprint 0.5) ; ajout FTD-14 (anti-FOUC), FTD-15 (option 'system' thème) |
|
||||
|
|
|
|||
46
src/shared/components/Logo.tsx
Normal file
46
src/shared/components/Logo.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { cn } from '@/shared/lib/utils'
|
||||
|
||||
type LogoSize = 'sm' | 'md'
|
||||
type LogoVariant = 'icon' | 'full'
|
||||
|
||||
interface LogoProps {
|
||||
size?: LogoSize
|
||||
variant?: LogoVariant
|
||||
className?: string
|
||||
}
|
||||
|
||||
const markStyles: Record<LogoSize, string> = {
|
||||
sm: 'size-6 text-[11px]',
|
||||
md: 'size-8 text-[13px]',
|
||||
}
|
||||
|
||||
const wordmarkStyles: Record<LogoSize, string> = {
|
||||
sm: 'text-sm',
|
||||
md: 'text-base',
|
||||
}
|
||||
|
||||
export function Logo({ size = 'md', variant = 'full', className }: LogoProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2.5 font-bold tracking-tight text-ink-1',
|
||||
className
|
||||
)}
|
||||
role={variant === 'icon' ? 'img' : undefined}
|
||||
aria-label={variant === 'icon' ? 'Expria' : undefined}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'flex shrink-0 items-center justify-center rounded-sm bg-expria font-bold tracking-tight text-white',
|
||||
markStyles[size]
|
||||
)}
|
||||
aria-hidden="true"
|
||||
>
|
||||
EX
|
||||
</span>
|
||||
{variant === 'full' && (
|
||||
<span className={cn('leading-none', wordmarkStyles[size])}>Expria</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
24
src/shared/components/ThemeToggle.tsx
Normal file
24
src/shared/components/ThemeToggle.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { Moon, Sun } from 'lucide-react'
|
||||
import { useTheme } from '@/shared/hooks/useTheme'
|
||||
import { Button } from '@/shared/components/ui/button'
|
||||
|
||||
interface ThemeToggleProps {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function ThemeToggle({ className }: ThemeToggleProps) {
|
||||
const { theme, setTheme } = useTheme()
|
||||
const isDark = theme === 'dark'
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={className}
|
||||
aria-label={isDark ? 'Passer en mode clair' : 'Passer en mode sombre'}
|
||||
onClick={() => setTheme(isDark ? 'light' : 'dark')}
|
||||
>
|
||||
{isDark ? <Sun className="size-4" /> : <Moon className="size-4" />}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue