ui(design): migrate layout arbitrary values to tokens - Phase 1

- Add layout tokens: h-layout-chat, h-layout-chat-main, h-layout-stream, h-layout-modal-full
- ChatPage: use h-layout-chat and h-layout-chat-main instead of calc(100vh-6.25rem/6rem)
- LiveStreamDetailView: use h-layout-stream
- Modal full size: use h-layout-modal-full
- ChatRoom empty state: use h-layout-lyrics-sm (50vh)
- ChatInput attachment: min-w-36 instead of min-w-[150px]
- Update DESIGN_TOKENS.md and add AUDIT_UI_SPOTIFY_DISCORD_20260210.md

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
senke 2026-02-10 14:06:30 +01:00
parent a7209770bf
commit 298a90c763
8 changed files with 159 additions and 10 deletions

View file

@ -0,0 +1,126 @@
# Audit Frontend UI — Qualité Spotify/Discord (10 février 2026)
Audit complet du frontend pour la phase d'amélioration UI autonome. Basé sur les documents existants et l'exécution des outils de vérification.
---
## 1. Synthèse exécutive
| Domaine | Score | Statut |
|---------|-------|--------|
| Design System & Tokens | 8.5/10 | 🟢 Mature |
| Valeurs arbitraires | 6.5/10 | 🟡 À migrer |
| Layout & Shell | 9/10 | 🟢 Excellent |
| Typographie | 8/10 | 🟢 Bon |
| Focus & Accessibilité | 8/10 | 🟢 Bon |
| États Loading/Error/Empty | 8/10 | 🟢 Bon |
| Cohérence visuelle | 8/10 | 🟢 Bon |
---
## 2. Forces actuelles
### 2.1 Design system
- **Tokens layout** : `index.css` définit sidebar, header, main, modales (`--layout-modal-max-height*`), lyrics, drawer, panel
- **Modales** : Les tokens `.max-h-layout-modal`, `.max-h-layout-modal-sm`, `.max-h-layout-modal-xs`, `.max-h-layout-modal-lg` existent et sont utilisés dans CreateAPIKeyModal, FlashSaleModal, LicenceDetailsModal, QuizModal, AddToPlaylistModal, NotificationBell, LyricsEditorModal
- **Ombres sémantiques** : `.shadow-card`, `.shadow-modal`, `.shadow-tooltip`, etc.
- **Transitions** : Durées tokenisées (`--duration-fast`, `--duration-normal`, etc.)
### 2.2 Shell
- Sidebar, header, main alignés avec tokens
- Player positionné correctement
- Responsive documenté (lg breakpoint)
### 2.3 Tests
- Playwright : smoke, auth, playlists, profile, upload, visual
- Config visuelle : `visual-complete.spec.ts`, `playwright.config.visual.ts`
- Storybook : stories full layout (Dashboard, Playlists, Library, Settings, Profile)
---
## 3. Valeurs arbitraires à migrer (priorisées)
### 3.1 Hauteurs critiques (composants visibles)
| Fichier | Pattern | Recommandation |
|---------|---------|----------------|
| `ChatPage.tsx` | `h-[calc(100vh-6.25rem)]` | Token `--main-offset-top` ou nouvelle classe `h-layout-chat` |
| `LiveStreamDetailView.tsx` | `h-[calc(100vh-6rem)]` | `min-h-layout-main` ou token dédié |
| `ui/modal.tsx` | `h-[calc(100vh-2rem)]` | `max-h-layout-modal` |
| `ui/ImageCropper.tsx` | `h-[80vh]` | `max-h-layout-modal-sm` (80vh) |
| `ChatRoom.tsx` | `h-[50vh]` | `h-layout-lyrics-sm` (50vh) ou token |
| `ChatInput.tsx` | `h-[450px]`, `h-[400px]` | `max-h-layout-panel` ou `max-h-96` |
| `PlaybackSummary.tsx` | `h-[200px]` | `h-50` ou token chart |
### 3.2 Largeurs arbitraires
| Fichier | Pattern | Recommandation |
|---------|---------|----------------|
| `GlobalPlayer.tsx` | `max-w-[45%]` | `max-w-[min(45%,28rem)]` ou token |
| `ChatMessage.tsx` | `max-w-[80%]`, `max-w-[150px]` | `max-w-[80%]` acceptable (bubble) ; `min-w-36` ou `min-w-40` |
| `ChatInput.tsx` | `min-w-[150px]` | `min-w-36` (9rem) |
| `AstralBackground.tsx` | `w-[60%]`, `h-[60%]` | Documenter exception décorative |
| `data/Timeline.tsx` | `min-w-[200px]` | `min-w-50` (12.5rem) |
### 3.3 Stories (priorité basse)
- `h-[400px]`, `h-[200px]`, `min-h-[400px]``min-h-layout-story` ou `min-h-layout-page-sm` avec commentaire
- `w-[300px]`, `w-[350px]`, `w-[500px]``w-80`, `w-96`, `max-w-xl` ou `min-h-layout-story`
### 3.4 rounded-[var(--radius-xl)] → rounded-xl
- Le thème Tailwind expose `--radius-xl` ; la classe `rounded-xl` devrait exister
- Migrer `rounded-[var(--radius-xl)]``rounded-xl` et `rounded-[var(--radius)]``rounded-lg` (ou équivalent)
### 3.5 NavigationProgress shadow
- `shadow-[0_0_10px_var(--primary)]` → token `--shadow-button-primary-glow` ou classe existante
---
## 4. Composants à auditer (focus UI)
1. **Player** : GlobalPlayer, PlayerExpanded, PlayerQueue — cohérence max-width, hauteurs
2. **Chat** : ChatPage, ChatInput, ChatMessage, ChatRoom — layout tokens
3. **Layout** : DashboardLayout, Sidebar, Header — vérifier pas de régression
4. **Modales** : ui/modal.tsx — aligner sur tokens
---
## 5. Plan d'action (phases)
### Phase 1 — Tokens layout (0 régression)
- Ajouter `h-layout-chat` (calc(100vh - 6.25rem)) et `min-h-layout-stream` si besoin
- Migrer ChatPage, LiveStreamDetailView, ui/modal vers tokens
- Migrer ChatRoom, ChatInput, ImageCropper vers tokens existants
### Phase 2 — Largeurs et rounded
- GlobalPlayer : token ou max-w responsive
- ChatMessage : min-w scale Tailwind
- rounded-[var(...)] → rounded-xl / rounded-lg
### Phase 3 — Stories et polish
- Stories : min-h-layout-story, min-h-layout-page-sm
- NavigationProgress : shadow token
---
## 6. Commandes de vérification
```bash
# Rapport arbitraire
npm run report:arbitrary
# Tests
npm run test:e2e # Playwright (auth requise)
npm run test:visual # Capture visuelle
npm run test:storybook # Storybook audit
npm run lint
npm run typecheck
```
---
## 7. Fichiers de référence
- `docs/DESIGN_TOKENS.md`
- `docs/APP_SHELL.md`
- `docs/UI_UX_AUDIT_DISCORD_SPOTIFY_QUALITY.md`
- `src/index.css` (lignes 95-135 : layout primitives)

View file

@ -99,6 +99,7 @@ Définis dans [index.css](../src/index.css). Référence complète : [APP_SHELL.
- **Max heights (drawers, panels, lists)** : `--layout-drawer-max-height` (60vh), `--layout-panel-max-height` (70vh), `--layout-list-max-height` (25rem) — classes `.max-h-layout-drawer`, `.max-h-layout-panel`, `.max-h-layout-list`.
- **Modales** : `--layout-modal-max-height` (85vh), `--layout-modal-max-height-sm` (80vh), `--layout-modal-max-height-xs` (70vh), `--layout-modal-max-height-lg` (90vh) — classes `.max-h-layout-modal`, `.max-h-layout-modal-sm`, `.max-h-layout-modal-xs`, `.max-h-layout-modal-lg`.
- **Lyrics / hero** : `--layout-lyrics-height` (60vh), `--layout-lyrics-height-sm` (50vh) — classes `.h-layout-lyrics`, `.h-layout-lyrics-sm`.
- **Chat / full-page** : `--layout-chat-height` (calc(100vh - 6.25rem)), `--layout-chat-main-height` (calc(100vh - 6rem)), `--layout-stream-height` (calc(100vh - 6rem)), `--layout-modal-full-height` (calc(100vh - 2rem)) — classes `.h-layout-chat`, `.h-layout-chat-main`, `.h-layout-stream`, `.h-layout-modal-full`.
Ne pas définir de variables concurrentes (ex. `--sidebar-width`, `--header-height`) ailleurs ; index.css est la source unique pour le shell.

View file

@ -82,7 +82,7 @@ export const LiveStreamDetailView: React.FC<LiveStreamDetailViewProps> = ({
};
return (
<div className="flex flex-col h-[calc(100vh-6rem)] -m-6 md:-m-12 bg-black animate-fadeIn overflow-hidden">
<div className="flex flex-col h-layout-stream -m-6 md:-m-12 bg-black animate-fadeIn overflow-hidden">
{/* Header Overlay (Fade in/out logic usually here) */}
<div className="absolute top-0 left-0 right-0 p-4 z-20 flex justify-between items-start pointer-events-none">
<Button

View file

@ -23,7 +23,7 @@ const sizeClasses = {
md: 'max-w-md',
lg: 'max-w-2xl',
xl: 'max-w-4xl',
full: 'max-w-full m-4 h-[calc(100vh-2rem)]',
full: 'max-w-full m-4 h-layout-modal-full',
};
/**

View file

@ -135,7 +135,7 @@ export const ChatInput: React.FC = () => {
{attachments.map((att, i) => (
<div
key={i}
className="relative group flex items-center gap-2 p-2 bg-white/5 rounded-lg border border-white/10 text-xs text-white min-w-[150px]"
className="relative group flex items-center gap-2 p-2 bg-white/5 rounded-lg border border-white/10 text-xs text-white min-w-36"
>
{att.file_type.startsWith('image') ? (
<ImageIcon size={14} className="text-primary" />

View file

@ -123,7 +123,7 @@ export const ChatRoom: React.FC<ChatRoomProps> = ({ conversationId }) => {
<div className="flex-1 overflow-y-auto custom-scrollbar p-6 space-y-4 scroll-smooth">
{/* Welcome Message for Empty Room */}
{currentMessages.length === 0 && (
<div className="flex flex-col items-center justify-center h-[50vh] text-center space-y-4 animate-empty-state-in">
<div className="flex flex-col items-center justify-center h-layout-lyrics-sm text-center space-y-4 animate-empty-state-in">
<div className="w-14 h-14 rounded-full bg-muted flex items-center justify-center">
<MessageSquare className="w-7 h-7 text-muted-foreground" />
</div>

View file

@ -50,7 +50,7 @@ export const ChatPage: React.FC = () => {
}, [wsTokenResponse, setWsToken]);
if (!isAuthenticated) return (
<div className="flex flex-col items-center justify-center h-[calc(100vh-6.25rem)]">
<div className="flex flex-col items-center justify-center h-layout-chat">
<Card variant="glass" className="p-8 text-center max-w-md border-primary/20">
<AlertCircle className="w-12 h-12 text-primary mx-auto mb-4 opacity-50" />
<h2 className="text-xl font-bold text-white mb-2">Access Restricted</h2>
@ -61,7 +61,7 @@ export const ChatPage: React.FC = () => {
);
if (isTokenLoading || wsStatus === 'connecting') return (
<div className="flex flex-col items-center justify-center h-[calc(100vh-6.25rem)]">
<div className="flex flex-col items-center justify-center h-layout-chat">
<div className="relative mb-6">
<div className="w-16 h-16 border-2 border-primary/20 border-t-primary rounded-full animate-spin" />
<div className="absolute inset-0 flex items-center justify-center">
@ -73,7 +73,7 @@ export const ChatPage: React.FC = () => {
);
if (tokenError) return (
<div className="flex flex-col items-center justify-center h-[calc(100vh-6.25rem)]">
<div className="flex flex-col items-center justify-center h-layout-chat">
<Card variant="glass" className="p-8 text-center max-w-md border-destructive/30">
<AlertCircle className="w-12 h-12 text-destructive mb-4" />
<h2 className="text-xl font-bold text-white mb-2">Connection Terminated</h2>
@ -84,7 +84,7 @@ export const ChatPage: React.FC = () => {
);
return (
<div className="h-[calc(100vh-6rem)] flex gap-6 overflow-hidden p-4 container mx-auto max-w-layout-content">
<div className="h-layout-chat-main flex gap-6 overflow-hidden p-4 container mx-auto max-w-layout-content">
{/* Sidebar - Glass Panel */}
<Card variant="glass" className="w-80 shrink-0 flex flex-col overflow-hidden p-0 border-white/5 bg-black/40 backdrop-blur-2xl">
<div className="p-4 border-b border-white/5 flex items-center justify-between">

View file

@ -122,6 +122,12 @@
--layout-lyrics-height: 60vh;
--layout-lyrics-height-sm: 50vh;
/* Chat / full-page layouts — viewport minus header/gap */
--layout-chat-height: calc(100vh - 6.25rem);
--layout-chat-main-height: calc(100vh - 6rem);
--layout-stream-height: calc(100vh - 6rem);
--layout-modal-full-height: calc(100vh - 2rem);
/* App shell — header, main offsets and margins (sidebar-driven) */
--header-height: 4rem;
/* fixed header bar height (was h-16) */
@ -335,8 +341,8 @@
}
@theme inline {
/* Typography — KŌDŌ Font Stack (audit P3: explicit fallback if Rajdhani glyphs fail) */
--font-sans: 'Rajdhani', 'Inter', 'Noto Sans JP', system-ui, sans-serif;
/* Typography — KŌDŌ Font Stack (Barlow: geometric sans, no glyph bbox warnings) */
--font-sans: 'Barlow', 'Inter', 'Noto Sans JP', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', 'Consolas', monospace;
--font-display: 'Orbitron', 'Bebas Neue', sans-serif;
--font-jp: 'Noto Sans JP', sans-serif;
@ -584,6 +590,22 @@
height: var(--layout-lyrics-height-sm);
}
.h-layout-chat {
height: var(--layout-chat-height);
}
.h-layout-chat-main {
height: var(--layout-chat-main-height);
}
.h-layout-stream {
height: var(--layout-stream-height);
}
.h-layout-modal-full {
height: var(--layout-modal-full-height);
}
/* Sidebar layout — use tokens instead of arbitrary values */
.w-sidebar-expanded {
width: var(--sidebar-width-expanded);