Plan UI premium 6–8 semaines (design system, shell, Storybook, a11y): - Design system: DESIGN_TOKENS.md, APP_SHELL.md, FULL_LAYOUT_PAGE.md. Single source for layout/shell (index.css), shadows (design-system.css), durations/easing. - Tokens: shadow-cover-depth, shadow-gold-glow, shadow-fab-glow; layout max-height (max-h-layout-drawer, max-h-layout-panel, max-h-layout-list). All duration-200/300/500 replaced by --duration-fast/normal/slow. Arbitrary shadows replaced by token classes. - Shell & player: Sidebar, Header, GlobalPlayer, MiniPlayer, PlayerQueue, PlayerControls, AudioPlayer use tokens; focus-visible on Sidebar, PlayerQueue, DropdownMenuTrigger/Item, TabsTrigger. Typography: text-[10px]/[9px] → text-xs where applicable. - ESLint: no-restricted-syntax (warn) for w-/h-/rounded-/shadow-/text-/spacing arbitrary. - Scripts: report-arbitrary-values.mjs, capture/compare/generate visual; visual-complete.spec.ts. - Stories full layout: Dashboard, Playlists, Library, Settings, Profile in DashboardLayout.stories. - .cursorrules + README: DESIGN_TOKENS, APP_SHELL, visual commands, no arbitrary without justification. - apps/web/.gitignore: e2e test artifacts (test-results-visual, playwright-report-visual). Co-authored-by: Cursor <cursoragent@cursor.com>
172 lines
No EOL
13 KiB
Markdown
172 lines
No EOL
13 KiB
Markdown
---
|
||
name: Shell complet et vues full layout
|
||
overview: Définir un shell applicatif 100% token-based (sidebar, header, main, player), documenter sa spec, puis ajouter 2–3 stories Storybook "full layout" (App shell placeholder, Dashboard, Playlists) avec Dashboard comme référence visuelle.
|
||
todos: []
|
||
isProject: false
|
||
---
|
||
|
||
# Plan : Shell ultra complet + 2–3 vues full layout (Dashboard comme guide)
|
||
|
||
## Contexte
|
||
|
||
- Le shell actuel est dans [DashboardLayout.tsx](apps/web/src/components/layout/DashboardLayout.tsx) et [Header.tsx](apps/web/src/components/layout/Header.tsx) mais utilise des valeurs Tailwind en dur (`lg:ml-72`, `lg:ml-28`, `pt-20`, `pb-32`, `left-72`, `left-20`, `h-16`) au lieu des tokens déjà définis dans [index.css](apps/web/src/index.css) (sidebar, layout primitives).
|
||
- Les tokens sidebar existent (`.w-sidebar-expanded`, `.left-sidebar`, etc.) mais les **marges du main** et la **position du header** ne sont pas en tokens, ce qui crée un écart (ex. `ml-72` = 18rem alors que `--sidebar-width-expanded` = 15rem).
|
||
- Une story [DashboardLayout.stories.tsx](apps/web/src/components/layout/DashboardLayout.stories.tsx) existe avec du contenu placeholder ; il n’y a pas de story "full layout" avec le vrai **DashboardPage** ni avec **PlaylistListPage**.
|
||
|
||
---
|
||
|
||
## Phase 1 — Tokens du shell et classes utilitaires
|
||
|
||
**Objectif** : Toute dimension du shell (header, main, player) est pilotée par des variables CSS ; plus aucune valeur arbitraire dans le layout.
|
||
|
||
### 1.1 Ajouter les variables shell dans `index.css`
|
||
|
||
Dans la section "Layout primitives" (après `--layout-gap-lg`), ajouter :
|
||
|
||
- `--header-height: 4rem` (équivalent actuel `h-16`)
|
||
- `--main-offset-top: 5rem` (équivalent `pt-20`, espace sous le header fixe)
|
||
- `--main-offset-bottom: 8rem` (équivalent `pb-32`, réserve pour le player)
|
||
- `--main-margin-left-expanded: 18rem` (marge gauche du main quand sidebar ouverte : 15rem sidebar + 3rem gap)
|
||
- `--main-margin-left-collapsed: 7rem` (marge gauche quand sidebar fermée : 5rem + 2rem gap)
|
||
- `--header-left-expanded: 18rem` (bord gauche de la barre header quand sidebar ouverte)
|
||
- `--header-left-collapsed: 5rem` (bord gauche quand sidebar fermée)
|
||
|
||
Côté main, on garde le padding horizontal en échelle Tailwind (`px-4 md:px-8`) sauf si tu veux un token dédié plus tard (optionnel).
|
||
|
||
### 1.2 Ajouter les classes utilitaires shell dans `index.css`
|
||
|
||
Dans la même zone que `.left-sidebar` / `.z-sidebar` (layout sidebar), ajouter :
|
||
|
||
- `.h-header` → `height: var(--header-height)`
|
||
- `.pt-main` → `padding-top: var(--main-offset-top)`
|
||
- `.pb-main` → `padding-bottom: var(--main-offset-bottom)`
|
||
- `.ml-main-expanded` → `margin-left: var(--main-margin-left-expanded)` (à utiliser avec `lg:`)
|
||
- `.ml-main-collapsed` → `margin-left: var(--main-margin-left-collapsed)` (avec `lg:`)
|
||
- `.left-header-expanded` → `left: var(--header-left-expanded)`
|
||
- `.left-header-collapsed` → `left: var(--header-left-collapsed)`
|
||
|
||
Commente brièvement chaque variable (comme pour `--sidebar-width-expanded`) pour faciliter la maintenance.
|
||
|
||
---
|
||
|
||
## Phase 2 — Remplacer les valeurs en dur dans le layout
|
||
|
||
**Objectif** : DashboardLayout et Header n’utilisent plus que des classes basées sur les tokens du shell.
|
||
|
||
### 2.1 [DashboardLayout.tsx](apps/web/src/components/layout/DashboardLayout.tsx)
|
||
|
||
- Remplacer la `className` du conteneur principal (la `div` qui a `flex-1 flex flex-col...`) :
|
||
- `lg:ml-72` → `lg:ml-main-expanded` quand `sidebarOpen`
|
||
- `lg:ml-28` → `lg:ml-main-collapsed` quand `!sidebarOpen`
|
||
- Conserver `ml-0` pour mobile.
|
||
- Sur la balise `<main>` :
|
||
- `pt-20` → `pt-main`
|
||
- `pb-32` → `pb-main`
|
||
- Garder `px-4 md:px-8` et le reste tel quel.
|
||
|
||
Utiliser `cn()` pour appliquer `ml-main-expanded` / `ml-main-collapsed` selon `sidebarOpen` (avec préfixe `lg:`), sans réintroduire de valeurs en dur.
|
||
|
||
### 2.2 [Header.tsx](apps/web/src/components/layout/Header.tsx)
|
||
|
||
- Sur l’élément `<header>` : `h-16` → `h-header`.
|
||
- Sur la `div` interne qui positionne la barre (celle avec `left-72` / `left-20`) :
|
||
- `left-72` → `left-header-expanded` quand `sidebarOpen`
|
||
- `left-20` → `left-header-collapsed` quand `!sidebarOpen`
|
||
- Conserver `h-16` → `h-header` sur cette même div.
|
||
- Garder `max-lg:left-0`.
|
||
|
||
Vérifier qu’il n’y a pas d’autres `h-16` / `left-*` liés au shell dans ce fichier.
|
||
|
||
---
|
||
|
||
## Phase 3 — Documentation du shell
|
||
|
||
**Objectif** : Une seule référence écrite qui décrit le shell et son usage pour les prochaines évolutions.
|
||
|
||
### 3.1 Créer `docs/APP_SHELL.md`
|
||
|
||
Contenu minimal :
|
||
|
||
- **Vue d’ensemble** : rôle du shell (sidebar, header, main, player), fichier principal `DashboardLayout.tsx` et `Header.tsx`.
|
||
- **Variables CSS** : tableau ou liste des variables ajoutées en Phase 1 avec leur valeur et leur rôle (header height, main offset top/bottom, main margin-left expanded/collapsed, header left expanded/collapsed).
|
||
- **Classes utilitaires** : liste des classes (`.h-header`, `.pt-main`, `.pb-main`, `.ml-main-expanded`, `.ml-main-collapsed`, `.left-header-expanded`, `.left-header-collapsed`) et quand les utiliser.
|
||
- **Comportement responsive** : sur `lg` (1024px+) utilisation des marges/positions tokenisées ; en dessous, sidebar en overlay et `ml-0` / `left-0` pour le header.
|
||
- **Référence croisée** : lien vers les tokens sidebar existants dans `index.css` (`--sidebar-width-expanded`, etc.) pour garder une cohérence sidebar ↔ main.
|
||
|
||
Pas de code dupliqué : le doc pointe vers `index.css` et les composants, il ne recopie pas tout le CSS.
|
||
|
||
---
|
||
|
||
## Phase 4 — Stories full layout dans Storybook
|
||
|
||
**Objectif** : Avoir 2–3 vues "app entière" dans Storybook (shell + contenu réel ou réaliste), avec Dashboard comme écran pilote/guide.
|
||
|
||
### 4.1 Story "App shell" (placeholder)
|
||
|
||
- Fichier : [DashboardLayout.stories.tsx](apps/web/src/components/layout/DashboardLayout.stories.tsx).
|
||
- Conserver ou renommer la story existante **Default** en **AppShellWithPlaceholder** (ou garder "Default" et ajouter un sous-titre / description indiquant "Shell with placeholder content").
|
||
- Contenu : le même placeholder que aujourd’hui (titre, paragraphe, grille de cartes, liste d’items) pour valider scroll et proportions.
|
||
- Paramètres : `layout: 'fullscreen'` (déjà présent), viewport optionnel (ex. 1280x720) pour cohérence avec les captures visuelles.
|
||
- S’assurer que la story utilise bien le décorateur global (aucun décorateur local qui enlève le shell). Aucun changement de décorateur nécessaire si le preview applique déjà `StorybookDecorator` à toutes les stories.
|
||
|
||
### 4.2 Story "Dashboard – full layout"
|
||
|
||
- Même fichier **ou** fichier dédié selon préférence (recommandation : même fichier `DashboardLayout.stories.tsx` pour garder toutes les vues "full layout" au même endroit).
|
||
- Nouvelle story **DashboardFullLayout** :
|
||
- Rendu : `<DashboardLayout><DashboardPage /></DashboardLayout>`.
|
||
- Passer `parameters.router.initialEntries = ['/dashboard']` pour que `useLocation`, `useNavigate` et d’éventuels liens soient cohérents.
|
||
- Dépendances : `DashboardPage` utilise `useUser()`, `useLibraryItems()` / store library, etc. Les handlers MSW existants ([handlers.ts](apps/web/src/mocks/handlers.ts)) fournissent déjà `auth/me` et `dashboard`. Vérifier que les appels utilisés par `DashboardPage` (ex. library items si affichés) sont bien mockés, sinon ajouter ou étendre les handlers pour que la page affiche un état "données chargées" et pas un loading infini.
|
||
- Paramètres : `layout: 'fullscreen'`, même viewport que la story shell si besoin.
|
||
- Cette story sert de **référence visuelle** pour le rendu "Dashboard dans l’app" ; à utiliser pour les comparaisons visuelles et les régressions.
|
||
|
||
### 4.3 Story "Playlists – full layout"
|
||
|
||
- Même fichier **ou** nouveau fichier (ex. `PlaylistListPage.stories.tsx` avec une story full layout qui wrap dans `DashboardLayout`). Recommandation : une story **PlaylistsFullLayout** dans [DashboardLayout.stories.tsx](apps/web/src/components/layout/DashboardLayout.stories.tsx) pour centraliser les vues "app entière".
|
||
- Alternative propre : créer [apps/web/src/features/playlists/pages/PlaylistListPage.stories.tsx](apps/web/src/features/playlists/pages/PlaylistListPage.stories.tsx) (s’il n’existe pas déjà) et y ajouter une story "FullLayout" qui rend `<DashboardLayout><PlaylistListPage /></PlaylistListPage>` avec `initialEntries: ['/playlists']`. Si tu préfères tout au même endroit, garder dans `DashboardLayout.stories.tsx`.
|
||
- Rendu : `<DashboardLayout><PlaylistListPage /></DashboardLayout>` (importer `PlaylistListPage` depuis le module playlists).
|
||
- `parameters.router.initialEntries = ['/playlists']`.
|
||
- Vérifier les handlers MSW pour les listes de playlists ; ajouter ou adapter si nécessaire pour que la page affiche du contenu (liste ou empty state) et pas un loading infini.
|
||
- Paramètres : `layout: 'fullscreen'`, viewport cohérent.
|
||
|
||
### 4.4 Cohérence décorateurs et providers
|
||
|
||
- Les stories full layout doivent s’exécuter avec le **décorateur global** ([.storybook/decorators.tsx](apps/web/.storybook/decorators.tsx)) : ThemeProvider, QueryClient, Toast, Audio, AuthProvider, MemoryRouter. Ne pas dupliquer ces providers dans les stories ; utiliser uniquement `parameters.router.initialEntries` pour la route.
|
||
- Si `DashboardPage` ou `PlaylistListPage` dépendent de l’auth Zustand (ex. `useAuthStore`) en plus de l’AuthProvider, documenter ou mocker l’état auth (ex. via MSW ou un décorateur minimal qui initialise le store) pour que les pages ne redirigent pas vers login. Idéalement, les pages utilisent les mêmes mécanismes qu’en app (AuthProvider + API mockée), pour éviter des écarts de comportement.
|
||
|
||
---
|
||
|
||
## Phase 5 — Vérification et régression
|
||
|
||
- **Lint** : après les changements, s’assurer qu’aucune règle ESLint n’est cassée sur les fichiers modifiés.
|
||
- **Storybook** : lancer `npm run build-storybook` (ou `storybook dev`) depuis `apps/web` et ouvrir les stories **App shell**, **Dashboard full layout** et **Playlists full layout** ; vérifier que le shell s’affiche correctement (sidebar, header, main, player), que le contenu scroll et que les données mockées s’affichent.
|
||
- **App réelle** : lancer l’app (`npm run dev`) et comparer visuellement Dashboard et Playlists avec les mêmes vues dans Storybook pour confirmer l’alignement (pas de décalage de marges, hauteurs, etc.).
|
||
- **Tests existants** : exécuter les tests unitaires qui touchent à `DashboardLayout` ou `Header` ([DashboardLayout.test.tsx](apps/web/src/components/layout/DashboardLayout.test.tsx)) et corriger si des classNames ou sélecteurs cassent.
|
||
|
||
---
|
||
|
||
## Ordre d’exécution recommandé
|
||
|
||
1. Phase 1 (tokens + classes dans `index.css`)
|
||
2. Phase 2 (remplacement dans `DashboardLayout.tsx` et `Header.tsx`)
|
||
3. Phase 3 (rédaction de `docs/APP_SHELL.md`)
|
||
4. Phase 4.1 (story App shell / placeholder)
|
||
5. Phase 4.2 (story Dashboard full layout + MSW si besoin)
|
||
6. Phase 4.3 (story Playlists full layout + MSW si besoin)
|
||
7. Phase 5 (vérifications et tests)
|
||
|
||
---
|
||
|
||
## Fichiers impactés (résumé)
|
||
|
||
|
||
| Fichier | Action |
|
||
| ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------- |
|
||
| [apps/web/src/index.css](apps/web/src/index.css) | Ajout variables shell + classes utilitaires |
|
||
| [apps/web/src/components/layout/DashboardLayout.tsx](apps/web/src/components/layout/DashboardLayout.tsx) | Remplacer ml/pt/pb par classes token |
|
||
| [apps/web/src/components/layout/Header.tsx](apps/web/src/components/layout/Header.tsx) | Remplacer h-16/left-* par classes token |
|
||
| [apps/web/docs/APP_SHELL.md](apps/web/docs/APP_SHELL.md) | Création doc shell |
|
||
| [apps/web/src/components/layout/DashboardLayout.stories.tsx](apps/web/src/components/layout/DashboardLayout.stories.tsx) | Stories App shell, Dashboard full layout, Playlists full layout |
|
||
| [apps/web/src/mocks/handlers.ts](apps/web/src/mocks/handlers.ts) | Ajustements optionnels si endpoints dashboard/playlists/library manquants |
|
||
|
||
|
||
Aucun changement à la logique métier des pages ; uniquement layout, tokens et ajout de stories pour valider le rendu "app entière". |