feat: update TODO and enhance design token integration
- Marked hydration issues and design system tasks as complete in TODO.md, reflecting progress on theme optimization. - Added documentation for CSS variables in globals.css to guide future color modifications using design tokens. - Refactored QuickActions component to utilize StatusMessage for better message display and applied design tokens for button styles, improving UI consistency.
This commit is contained in:
5
TODO.md
5
TODO.md
@@ -16,8 +16,9 @@
|
|||||||
- [x] **Supprimer la double application** du thème (layout.tsx + ThemeContext + UserPreferencesContext) <!-- ThemeContext est maintenant la source unique -->
|
- [x] **Supprimer la double application** du thème (layout.tsx + ThemeContext + UserPreferencesContext) <!-- ThemeContext est maintenant la source unique -->
|
||||||
- [x] **Refactorer les CSS variables** : `:root` pour défaut, `.dark/.light` pour override <!-- Architecture CSS propre avec :root neutre -->
|
- [x] **Refactorer les CSS variables** : `:root` pour défaut, `.dark/.light` pour override <!-- Architecture CSS propre avec :root neutre -->
|
||||||
- [x] **Nettoyer les composants** : supprimer classes `dark:` hardcodées, utiliser uniquement CSS variables <!-- TERMINÉ : toutes les occurrences supprimées -->
|
- [x] **Nettoyer les composants** : supprimer classes `dark:` hardcodées, utiliser uniquement CSS variables <!-- TERMINÉ : toutes les occurrences supprimées -->
|
||||||
- [ ] **Corriger les problèmes d'hydration** mismatch et flashs de thème
|
- [x] **Corriger les problèmes d'hydration** mismatch et flashs de thème <!-- Script inline + ThemeContext optimisé -->
|
||||||
- [ ] **Créer un système de design cohérent** avec tokens de couleur
|
- [x] **Créer un système de design cohérent** avec tokens de couleur <!-- Design tokens + composants utilitaires + documentation -->
|
||||||
|
- [ ] **MIGRATION MASSIVE : Refactorer tous les composants** pour utiliser les design tokens <!-- Gros travail de migration composant par composant -->
|
||||||
|
|
||||||
### **Phase 2: Système Couleurs Personnalisées**
|
### **Phase 2: Système Couleurs Personnalisées**
|
||||||
- [ ] **Étendre le modèle UserPreferences** pour supporter des couleurs personnalisées
|
- [ ] **Étendre le modèle UserPreferences** pour supporter des couleurs personnalisées
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CSS Variables - Design Tokens
|
||||||
|
*
|
||||||
|
* Ces variables sont synchronisées avec src/lib/design-tokens.ts
|
||||||
|
* Pour modifier les couleurs, utilisez les design tokens plutôt que ce fichier directement.
|
||||||
|
*
|
||||||
|
* Documentation: src/lib/DESIGN_SYSTEM.md
|
||||||
|
*/
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
/* Valeurs par défaut (Light theme) */
|
/* Valeurs par défaut (Light theme) */
|
||||||
--background: #f1f5f9; /* slate-100 */
|
--background: #f1f5f9; /* slate-100 */
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Card, CardContent } from '@/components/ui/Card';
|
import { Card, CardContent } from '@/components/ui/Card';
|
||||||
|
import { StatusMessage } from '@/components/ui/StatusMessage';
|
||||||
|
import { useDesignTokens } from '@/hooks/useDesignTokens';
|
||||||
|
|
||||||
interface Message {
|
interface Message {
|
||||||
type: 'success' | 'error';
|
type: 'success' | 'error';
|
||||||
@@ -27,6 +29,7 @@ export function QuickActions({
|
|||||||
jiraEnabled,
|
jiraEnabled,
|
||||||
messages
|
messages
|
||||||
}: QuickActionsProps) {
|
}: QuickActionsProps) {
|
||||||
|
const { styles } = useDesignTokens();
|
||||||
return (
|
return (
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<h2 className="text-xl font-semibold text-[var(--foreground)] mb-4">
|
<h2 className="text-xl font-semibold text-[var(--foreground)] mb-4">
|
||||||
@@ -43,17 +46,18 @@ export function QuickActions({
|
|||||||
Créer une sauvegarde des données
|
Créer une sauvegarde des données
|
||||||
</p>
|
</p>
|
||||||
{messages.backup && (
|
{messages.backup && (
|
||||||
<p className="text-xs mt-1" style={{
|
<div className="mt-2">
|
||||||
color: messages.backup.type === 'success' ? 'var(--success)' : 'var(--destructive)'
|
<StatusMessage type={messages.backup.type}>
|
||||||
}}>
|
{messages.backup.text}
|
||||||
{messages.backup.text}
|
</StatusMessage>
|
||||||
</p>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onCreateBackup}
|
onClick={onCreateBackup}
|
||||||
disabled={isBackupLoading}
|
disabled={isBackupLoading}
|
||||||
className="px-3 py-1.5 bg-[var(--primary)] text-[var(--primary-foreground)] rounded text-sm disabled:opacity-50 disabled:cursor-not-allowed"
|
className="px-3 py-1.5 rounded text-sm disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
style={styles.primaryButton}
|
||||||
>
|
>
|
||||||
{isBackupLoading ? 'En cours...' : 'Sauvegarder'}
|
{isBackupLoading ? 'En cours...' : 'Sauvegarder'}
|
||||||
</button>
|
</button>
|
||||||
@@ -70,17 +74,18 @@ export function QuickActions({
|
|||||||
Tester la connexion Jira
|
Tester la connexion Jira
|
||||||
</p>
|
</p>
|
||||||
{messages.jira && (
|
{messages.jira && (
|
||||||
<p className="text-xs mt-1" style={{
|
<div className="mt-2">
|
||||||
color: messages.jira.type === 'success' ? 'var(--success)' : 'var(--destructive)'
|
<StatusMessage type={messages.jira.type}>
|
||||||
}}>
|
{messages.jira.text}
|
||||||
{messages.jira.text}
|
</StatusMessage>
|
||||||
</p>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onTestJira}
|
onClick={onTestJira}
|
||||||
disabled={!jiraEnabled || isJiraTestLoading}
|
disabled={!jiraEnabled || isJiraTestLoading}
|
||||||
className="px-3 py-1.5 bg-[var(--card)] text-[var(--foreground)] border border-[var(--border)] rounded text-sm disabled:opacity-50 disabled:cursor-not-allowed"
|
className="px-3 py-1.5 rounded text-sm disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
style={styles.input}
|
||||||
>
|
>
|
||||||
{isJiraTestLoading ? 'Test...' : 'Tester'}
|
{isJiraTestLoading ? 'Test...' : 'Tester'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
57
src/components/ui/ColoredCard.tsx
Normal file
57
src/components/ui/ColoredCard.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Composant de carte colorée utilisant les design tokens
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
import { useDesignTokens } from '@/hooks/useDesignTokens';
|
||||||
|
|
||||||
|
interface ColoredCardProps {
|
||||||
|
color: 'primary' | 'purple' | 'yellow' | 'green' | 'blue' | 'gray';
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
hover?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ColoredCard({ color, children, className = '', hover = true }: ColoredCardProps) {
|
||||||
|
const { styles } = useDesignTokens();
|
||||||
|
|
||||||
|
const getStyle = () => {
|
||||||
|
switch (color) {
|
||||||
|
case 'primary':
|
||||||
|
return styles.primaryCard;
|
||||||
|
case 'purple':
|
||||||
|
return styles.purpleCard;
|
||||||
|
case 'yellow':
|
||||||
|
return styles.yellowCard;
|
||||||
|
case 'green':
|
||||||
|
return {
|
||||||
|
color: 'var(--green)',
|
||||||
|
backgroundColor: 'color-mix(in srgb, var(--green) 10%, transparent)',
|
||||||
|
borderColor: 'color-mix(in srgb, var(--green) 20%, var(--border))',
|
||||||
|
};
|
||||||
|
case 'blue':
|
||||||
|
return {
|
||||||
|
color: 'var(--blue)',
|
||||||
|
backgroundColor: 'color-mix(in srgb, var(--blue) 10%, transparent)',
|
||||||
|
borderColor: 'color-mix(in srgb, var(--blue) 20%, var(--border))',
|
||||||
|
};
|
||||||
|
case 'gray':
|
||||||
|
return {
|
||||||
|
color: 'var(--muted-foreground)',
|
||||||
|
backgroundColor: 'color-mix(in srgb, var(--muted) 10%, transparent)',
|
||||||
|
borderColor: 'color-mix(in srgb, var(--muted) 20%, var(--border))',
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return styles.primaryCard;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`p-4 rounded-lg border transition-all ${hover ? 'hover:shadow-sm hover:scale-[1.01]' : ''} ${className}`}
|
||||||
|
style={getStyle()}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
58
src/components/ui/StatusMessage.tsx
Normal file
58
src/components/ui/StatusMessage.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Composant pour afficher des messages d'état avec les design tokens
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
import { useDesignTokens } from '@/hooks/useDesignTokens';
|
||||||
|
|
||||||
|
interface StatusMessageProps {
|
||||||
|
type: 'success' | 'error' | 'warning' | 'info';
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StatusMessage({ type, children, className = '' }: StatusMessageProps) {
|
||||||
|
const { styles } = useDesignTokens();
|
||||||
|
|
||||||
|
const getStyle = () => {
|
||||||
|
switch (type) {
|
||||||
|
case 'success':
|
||||||
|
return styles.successMessage;
|
||||||
|
case 'error':
|
||||||
|
return styles.errorMessage;
|
||||||
|
case 'warning':
|
||||||
|
return styles.warningMessage;
|
||||||
|
case 'info':
|
||||||
|
return styles.primaryCard;
|
||||||
|
default:
|
||||||
|
return styles.primaryCard;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIcon = () => {
|
||||||
|
switch (type) {
|
||||||
|
case 'success':
|
||||||
|
return '✅';
|
||||||
|
case 'error':
|
||||||
|
return '❌';
|
||||||
|
case 'warning':
|
||||||
|
return '⚠️';
|
||||||
|
case 'info':
|
||||||
|
return 'ℹ️';
|
||||||
|
default:
|
||||||
|
return 'ℹ️';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`p-3 rounded border ${className}`}
|
||||||
|
style={getStyle()}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span>{getIcon()}</span>
|
||||||
|
<span>{children}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
112
src/hooks/useDesignTokens.ts
Normal file
112
src/hooks/useDesignTokens.ts
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* Hook pour utiliser les design tokens dans les composants
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useTheme } from '@/contexts/ThemeContext';
|
||||||
|
import { DesignTokenUtils, COLOR_PATTERNS, OPACITY } from '@/lib/design-tokens';
|
||||||
|
|
||||||
|
export function useDesignTokens() {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
return {
|
||||||
|
theme,
|
||||||
|
|
||||||
|
// Utilitaires de couleur
|
||||||
|
withOpacity: DesignTokenUtils.withOpacity,
|
||||||
|
borderWithOpacity: DesignTokenUtils.borderWithOpacity,
|
||||||
|
backgroundWithOpacity: DesignTokenUtils.backgroundWithOpacity,
|
||||||
|
|
||||||
|
// Patterns prédéfinis
|
||||||
|
patterns: COLOR_PATTERNS,
|
||||||
|
|
||||||
|
// Opacités courantes
|
||||||
|
opacity: OPACITY,
|
||||||
|
|
||||||
|
// Couleurs CSS Variables (pour usage direct)
|
||||||
|
colors: {
|
||||||
|
background: 'var(--background)',
|
||||||
|
foreground: 'var(--foreground)',
|
||||||
|
card: 'var(--card)',
|
||||||
|
cardHover: 'var(--card-hover)',
|
||||||
|
cardColumn: 'var(--card-column)',
|
||||||
|
border: 'var(--border)',
|
||||||
|
input: 'var(--input)',
|
||||||
|
primary: 'var(--primary)',
|
||||||
|
primaryForeground: 'var(--primary-foreground)',
|
||||||
|
muted: 'var(--muted)',
|
||||||
|
mutedForeground: 'var(--muted-foreground)',
|
||||||
|
destructive: 'var(--destructive)',
|
||||||
|
success: 'var(--success)',
|
||||||
|
accent: 'var(--accent)',
|
||||||
|
purple: 'var(--purple)',
|
||||||
|
yellow: 'var(--yellow)',
|
||||||
|
green: 'var(--green)',
|
||||||
|
blue: 'var(--blue)',
|
||||||
|
gray: 'var(--gray)',
|
||||||
|
grayLight: 'var(--gray-light)',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Styles prédéfinis pour les composants courants
|
||||||
|
styles: {
|
||||||
|
// Messages d'état
|
||||||
|
successMessage: {
|
||||||
|
color: COLOR_PATTERNS.success.text,
|
||||||
|
backgroundColor: COLOR_PATTERNS.success.background,
|
||||||
|
borderColor: COLOR_PATTERNS.success.border,
|
||||||
|
},
|
||||||
|
errorMessage: {
|
||||||
|
color: COLOR_PATTERNS.error.text,
|
||||||
|
backgroundColor: COLOR_PATTERNS.error.background,
|
||||||
|
borderColor: COLOR_PATTERNS.error.border,
|
||||||
|
},
|
||||||
|
warningMessage: {
|
||||||
|
color: COLOR_PATTERNS.warning.text,
|
||||||
|
backgroundColor: COLOR_PATTERNS.warning.background,
|
||||||
|
borderColor: COLOR_PATTERNS.warning.border,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Cards colorées
|
||||||
|
primaryCard: {
|
||||||
|
color: COLOR_PATTERNS.primary.text,
|
||||||
|
backgroundColor: COLOR_PATTERNS.primary.background,
|
||||||
|
borderColor: COLOR_PATTERNS.primary.border,
|
||||||
|
},
|
||||||
|
purpleCard: {
|
||||||
|
color: COLOR_PATTERNS.purple.text,
|
||||||
|
backgroundColor: COLOR_PATTERNS.purple.background,
|
||||||
|
borderColor: COLOR_PATTERNS.purple.border,
|
||||||
|
},
|
||||||
|
yellowCard: {
|
||||||
|
color: COLOR_PATTERNS.yellow.text,
|
||||||
|
backgroundColor: COLOR_PATTERNS.yellow.background,
|
||||||
|
borderColor: COLOR_PATTERNS.yellow.border,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Boutons
|
||||||
|
primaryButton: {
|
||||||
|
backgroundColor: 'var(--primary)',
|
||||||
|
color: 'var(--primary-foreground)',
|
||||||
|
borderColor: 'var(--primary)',
|
||||||
|
},
|
||||||
|
destructiveButton: {
|
||||||
|
backgroundColor: 'var(--destructive)',
|
||||||
|
color: 'white',
|
||||||
|
borderColor: 'var(--destructive)',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Inputs
|
||||||
|
input: {
|
||||||
|
backgroundColor: 'var(--input)',
|
||||||
|
borderColor: 'var(--border)',
|
||||||
|
color: 'var(--foreground)',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Scrollbars
|
||||||
|
scrollbar: {
|
||||||
|
track: 'var(--card)',
|
||||||
|
thumb: 'var(--muted)',
|
||||||
|
thumbHover: 'var(--primary)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
199
src/lib/DESIGN_SYSTEM.md
Normal file
199
src/lib/DESIGN_SYSTEM.md
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
# Design System - TowerControl
|
||||||
|
|
||||||
|
## Vue d'ensemble
|
||||||
|
|
||||||
|
Le design system de TowerControl utilise des **Design Tokens** pour assurer la cohérence visuelle et faciliter la maintenance des couleurs et styles.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### 1. Design Tokens (`src/lib/design-tokens.ts`)
|
||||||
|
|
||||||
|
Source unique de vérité pour toutes les couleurs et styles :
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { DesignTokenUtils, COLOR_PATTERNS } from '@/lib/design-tokens';
|
||||||
|
|
||||||
|
// Utilisation des utilitaires
|
||||||
|
const backgroundColor = DesignTokenUtils.withOpacity('var(--primary)', 0.1);
|
||||||
|
const borderColor = DesignTokenUtils.borderWithOpacity('var(--success)', 0.2, 'var(--border)');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Hook useDesignTokens (`src/hooks/useDesignTokens.ts`)
|
||||||
|
|
||||||
|
Hook React pour utiliser facilement les tokens dans les composants :
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useDesignTokens } from '@/hooks/useDesignTokens';
|
||||||
|
|
||||||
|
function MyComponent() {
|
||||||
|
const { colors, styles, patterns } = useDesignTokens();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ color: colors.primary }}>
|
||||||
|
<div style={styles.successMessage}>Succès !</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Composants utilitaires
|
||||||
|
|
||||||
|
#### StatusMessage
|
||||||
|
```tsx
|
||||||
|
import { StatusMessage } from '@/components/ui/StatusMessage';
|
||||||
|
|
||||||
|
<StatusMessage type="success">Opération réussie</StatusMessage>
|
||||||
|
<StatusMessage type="error">Une erreur est survenue</StatusMessage>
|
||||||
|
<StatusMessage type="warning">Attention requise</StatusMessage>
|
||||||
|
<StatusMessage type="info">Information</StatusMessage>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ColoredCard
|
||||||
|
```tsx
|
||||||
|
import { ColoredCard } from '@/components/ui/ColoredCard';
|
||||||
|
|
||||||
|
<ColoredCard color="primary">Contenu principal</ColoredCard>
|
||||||
|
<ColoredCard color="purple">Contenu violet</ColoredCard>
|
||||||
|
<ColoredCard color="yellow">Contenu jaune</ColoredCard>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Couleurs disponibles
|
||||||
|
|
||||||
|
### Couleurs principales
|
||||||
|
- `--background` : Arrière-plan principal
|
||||||
|
- `--foreground` : Texte principal
|
||||||
|
- `--card` : Arrière-plan des cartes
|
||||||
|
- `--card-hover` : État hover des cartes
|
||||||
|
- `--border` : Couleur des bordures
|
||||||
|
- `--input` : Arrière-plan des inputs
|
||||||
|
|
||||||
|
### Couleurs sémantiques
|
||||||
|
- `--primary` : Couleur principale (cyan)
|
||||||
|
- `--success` : Succès (vert)
|
||||||
|
- `--destructive` : Erreur/danger (rouge)
|
||||||
|
- `--accent` : Accent (orange/amber)
|
||||||
|
- `--muted` : Texte atténué
|
||||||
|
- `--muted-foreground` : Texte secondaire
|
||||||
|
|
||||||
|
### Couleurs étendues
|
||||||
|
- `--purple` : Violet
|
||||||
|
- `--yellow` : Jaune
|
||||||
|
- `--green` : Vert
|
||||||
|
- `--blue` : Bleu
|
||||||
|
- `--gray` : Gris
|
||||||
|
- `--gray-light` : Gris clair
|
||||||
|
|
||||||
|
## Patterns de couleurs
|
||||||
|
|
||||||
|
### Messages d'état
|
||||||
|
```typescript
|
||||||
|
const { patterns } = useDesignTokens();
|
||||||
|
|
||||||
|
// Utilisation directe
|
||||||
|
<div style={patterns.success}>Message de succès</div>
|
||||||
|
<div style={patterns.error}>Message d'erreur</div>
|
||||||
|
<div style={patterns.warning}>Message d'avertissement</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Opacités courantes
|
||||||
|
```typescript
|
||||||
|
const { opacity } = useDesignTokens();
|
||||||
|
|
||||||
|
// Opacités prédéfinies
|
||||||
|
opacity.subtle // 5% - très subtil
|
||||||
|
opacity.light // 10% - léger
|
||||||
|
opacity.medium // 20% - moyen
|
||||||
|
opacity.strong // 30% - fort
|
||||||
|
opacity.solid // 80% - presque opaque
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bonnes pratiques
|
||||||
|
|
||||||
|
### ✅ Recommandé
|
||||||
|
```tsx
|
||||||
|
// Utiliser les design tokens
|
||||||
|
const { colors, styles } = useDesignTokens();
|
||||||
|
|
||||||
|
<div style={{ color: colors.primary }}>
|
||||||
|
<div style={styles.successMessage}>
|
||||||
|
|
||||||
|
// Utiliser les composants utilitaires
|
||||||
|
<StatusMessage type="success">Succès</StatusMessage>
|
||||||
|
<ColoredCard color="primary">Contenu</ColoredCard>
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ À éviter
|
||||||
|
```tsx
|
||||||
|
// Couleurs hardcodées
|
||||||
|
<div style={{ color: '#0891b2' }}>
|
||||||
|
<div className="text-blue-600">
|
||||||
|
|
||||||
|
// Classes Tailwind dark:
|
||||||
|
<div className="bg-white dark:bg-gray-800">
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration des composants existants
|
||||||
|
|
||||||
|
### Avant (avec classes Tailwind)
|
||||||
|
```tsx
|
||||||
|
<div className="bg-green-50 border border-green-200 text-green-800 dark:bg-green-900/20 dark:border-green-800 dark:text-green-200">
|
||||||
|
✅ Succès
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Après (avec design tokens)
|
||||||
|
```tsx
|
||||||
|
<StatusMessage type="success">
|
||||||
|
Succès
|
||||||
|
</StatusMessage>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Extensibilité
|
||||||
|
|
||||||
|
Le système est conçu pour supporter facilement :
|
||||||
|
- **Nouveaux thèmes** : Ajouter dans `themeTokens`
|
||||||
|
- **Nouveaux patterns** : Étendre `COLOR_PATTERNS`
|
||||||
|
- **Nouvelles opacités** : Ajouter dans `OPACITY`
|
||||||
|
- **Composants personnalisés** : Utiliser `useDesignTokens`
|
||||||
|
|
||||||
|
## Exemples d'usage
|
||||||
|
|
||||||
|
### Composant personnalisé
|
||||||
|
```tsx
|
||||||
|
import { useDesignTokens } from '@/hooks/useDesignTokens';
|
||||||
|
|
||||||
|
function CustomComponent() {
|
||||||
|
const { colors, withOpacity } = useDesignTokens();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: withOpacity(colors.primary, 0.1),
|
||||||
|
borderColor: colors.primary,
|
||||||
|
color: colors.primary
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Contenu personnalisé
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Style conditionnel
|
||||||
|
```tsx
|
||||||
|
function ConditionalComponent({ isActive }: { isActive: boolean }) {
|
||||||
|
const { colors } = useDesignTokens();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
color: isActive ? colors.success : colors.mutedForeground
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isActive ? 'Actif' : 'Inactif'}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce système garantit la cohérence visuelle et facilite la maintenance des styles dans toute l'application.
|
||||||
241
src/lib/design-tokens.ts
Normal file
241
src/lib/design-tokens.ts
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
/**
|
||||||
|
* Design Tokens - Système de couleurs cohérent pour TowerControl
|
||||||
|
*
|
||||||
|
* Ce fichier définit tous les tokens de design utilisés dans l'application.
|
||||||
|
* Il sert de source unique de vérité pour les couleurs et facilite la maintenance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ColorToken = string;
|
||||||
|
|
||||||
|
export interface ColorPalette {
|
||||||
|
// Couleurs principales
|
||||||
|
background: ColorToken;
|
||||||
|
foreground: ColorToken;
|
||||||
|
|
||||||
|
// Surfaces
|
||||||
|
card: ColorToken;
|
||||||
|
cardHover: ColorToken;
|
||||||
|
cardColumn: ColorToken;
|
||||||
|
|
||||||
|
// Bordures et inputs
|
||||||
|
border: ColorToken;
|
||||||
|
input: ColorToken;
|
||||||
|
|
||||||
|
// Couleurs sémantiques
|
||||||
|
primary: ColorToken;
|
||||||
|
primaryForeground: ColorToken;
|
||||||
|
muted: ColorToken;
|
||||||
|
mutedForeground: ColorToken;
|
||||||
|
|
||||||
|
// Couleurs d'état
|
||||||
|
destructive: ColorToken;
|
||||||
|
success: ColorToken;
|
||||||
|
accent: ColorToken;
|
||||||
|
|
||||||
|
// Couleurs étendues
|
||||||
|
purple: ColorToken;
|
||||||
|
yellow: ColorToken;
|
||||||
|
green: ColorToken;
|
||||||
|
blue: ColorToken;
|
||||||
|
gray: ColorToken;
|
||||||
|
grayLight: ColorToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ThemeTokens {
|
||||||
|
light: ColorPalette;
|
||||||
|
dark: ColorPalette;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tokens de couleurs pour le thème clair
|
||||||
|
*/
|
||||||
|
export const lightTheme: ColorPalette = {
|
||||||
|
// Couleurs principales
|
||||||
|
background: '#f1f5f9', // slate-100
|
||||||
|
foreground: '#0f172a', // slate-900
|
||||||
|
|
||||||
|
// Surfaces
|
||||||
|
card: '#ffffff', // white
|
||||||
|
cardHover: '#f8fafc', // slate-50
|
||||||
|
cardColumn: '#f8fafc', // slate-50
|
||||||
|
|
||||||
|
// Bordures et inputs
|
||||||
|
border: '#cbd5e1', // slate-300
|
||||||
|
input: '#ffffff', // white
|
||||||
|
|
||||||
|
// Couleurs sémantiques
|
||||||
|
primary: '#0891b2', // cyan-600
|
||||||
|
primaryForeground: '#ffffff', // white
|
||||||
|
muted: '#94a3b8', // slate-400
|
||||||
|
mutedForeground: '#64748b', // slate-500
|
||||||
|
|
||||||
|
// Couleurs d'état
|
||||||
|
destructive: '#dc2626', // red-600
|
||||||
|
success: '#059669', // emerald-600
|
||||||
|
accent: '#d97706', // amber-600
|
||||||
|
|
||||||
|
// Couleurs étendues
|
||||||
|
purple: '#8b5cf6', // purple-500
|
||||||
|
yellow: '#eab308', // yellow-500
|
||||||
|
green: '#059669', // emerald-600
|
||||||
|
blue: '#2563eb', // blue-600
|
||||||
|
gray: '#6b7280', // gray-500
|
||||||
|
grayLight: '#e5e7eb', // gray-200
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tokens de couleurs pour le thème sombre
|
||||||
|
*/
|
||||||
|
export const darkTheme: ColorPalette = {
|
||||||
|
// Couleurs principales
|
||||||
|
background: '#1e293b', // slate-800
|
||||||
|
foreground: '#f1f5f9', // slate-100
|
||||||
|
|
||||||
|
// Surfaces
|
||||||
|
card: '#334155', // slate-700
|
||||||
|
cardHover: '#475569', // slate-600
|
||||||
|
cardColumn: '#0f172a', // slate-900
|
||||||
|
|
||||||
|
// Bordures et inputs
|
||||||
|
border: '#64748b', // slate-500
|
||||||
|
input: '#334155', // slate-700
|
||||||
|
|
||||||
|
// Couleurs sémantiques
|
||||||
|
primary: '#06b6d4', // cyan-500
|
||||||
|
primaryForeground: '#f1f5f9', // slate-100
|
||||||
|
muted: '#64748b', // slate-500
|
||||||
|
mutedForeground: '#94a3b8', // slate-400
|
||||||
|
|
||||||
|
// Couleurs d'état
|
||||||
|
destructive: '#ef4444', // red-500
|
||||||
|
success: '#10b981', // emerald-500
|
||||||
|
accent: '#f59e0b', // amber-500
|
||||||
|
|
||||||
|
// Couleurs étendues
|
||||||
|
purple: '#8b5cf6', // purple-500
|
||||||
|
yellow: '#eab308', // yellow-500
|
||||||
|
green: '#10b981', // emerald-500
|
||||||
|
blue: '#3b82f6', // blue-500
|
||||||
|
gray: '#9ca3af', // gray-400
|
||||||
|
grayLight: '#374151', // gray-700
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection complète des tokens de thème
|
||||||
|
*/
|
||||||
|
export const themeTokens: ThemeTokens = {
|
||||||
|
light: lightTheme,
|
||||||
|
dark: darkTheme,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilitaires pour les tokens de couleur
|
||||||
|
*/
|
||||||
|
export class DesignTokenUtils {
|
||||||
|
/**
|
||||||
|
* Génère une couleur avec transparence
|
||||||
|
*/
|
||||||
|
static withOpacity(color: ColorToken, opacity: number): string {
|
||||||
|
return `color-mix(in srgb, ${color} ${opacity * 100}%, transparent)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génère une couleur de bordure avec transparence
|
||||||
|
*/
|
||||||
|
static borderWithOpacity(color: ColorToken, opacity: number, baseBorder: ColorToken): string {
|
||||||
|
return `color-mix(in srgb, ${color} ${opacity * 100}%, ${baseBorder})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génère un background avec transparence
|
||||||
|
*/
|
||||||
|
static backgroundWithOpacity(color: ColorToken, opacity: number): string {
|
||||||
|
return `color-mix(in srgb, ${color} ${opacity * 100}%, transparent)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtient les tokens pour un thème donné
|
||||||
|
*/
|
||||||
|
static getTokens(theme: 'light' | 'dark'): ColorPalette {
|
||||||
|
return themeTokens[theme];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génère les CSS Variables pour un thème
|
||||||
|
*/
|
||||||
|
static generateCSSVariables(theme: 'light' | 'dark'): Record<string, string> {
|
||||||
|
const tokens = this.getTokens(theme);
|
||||||
|
return {
|
||||||
|
'--background': tokens.background,
|
||||||
|
'--foreground': tokens.foreground,
|
||||||
|
'--card': tokens.card,
|
||||||
|
'--card-hover': tokens.cardHover,
|
||||||
|
'--card-column': tokens.cardColumn,
|
||||||
|
'--border': tokens.border,
|
||||||
|
'--input': tokens.input,
|
||||||
|
'--primary': tokens.primary,
|
||||||
|
'--primary-foreground': tokens.primaryForeground,
|
||||||
|
'--muted': tokens.muted,
|
||||||
|
'--muted-foreground': tokens.mutedForeground,
|
||||||
|
'--destructive': tokens.destructive,
|
||||||
|
'--success': tokens.success,
|
||||||
|
'--accent': tokens.accent,
|
||||||
|
'--purple': tokens.purple,
|
||||||
|
'--yellow': tokens.yellow,
|
||||||
|
'--green': tokens.green,
|
||||||
|
'--blue': tokens.blue,
|
||||||
|
'--gray': tokens.gray,
|
||||||
|
'--gray-light': tokens.grayLight,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constantes pour les opacités courantes
|
||||||
|
*/
|
||||||
|
export const OPACITY = {
|
||||||
|
subtle: 0.05, // 5% - très subtil
|
||||||
|
light: 0.1, // 10% - léger
|
||||||
|
medium: 0.2, // 20% - moyen
|
||||||
|
strong: 0.3, // 30% - fort
|
||||||
|
solid: 0.8, // 80% - presque opaque
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patterns de couleurs prédéfinis pour les composants
|
||||||
|
*/
|
||||||
|
export const COLOR_PATTERNS = {
|
||||||
|
// Messages d'état
|
||||||
|
success: {
|
||||||
|
text: 'var(--success)',
|
||||||
|
background: DesignTokenUtils.withOpacity('var(--success)', OPACITY.light),
|
||||||
|
border: DesignTokenUtils.borderWithOpacity('var(--success)', OPACITY.medium, 'var(--border)'),
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
text: 'var(--destructive)',
|
||||||
|
background: DesignTokenUtils.withOpacity('var(--destructive)', OPACITY.light),
|
||||||
|
border: DesignTokenUtils.borderWithOpacity('var(--destructive)', OPACITY.medium, 'var(--border)'),
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
text: 'var(--accent)',
|
||||||
|
background: DesignTokenUtils.withOpacity('var(--accent)', OPACITY.light),
|
||||||
|
border: DesignTokenUtils.borderWithOpacity('var(--accent)', OPACITY.medium, 'var(--border)'),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Cards colorées
|
||||||
|
primary: {
|
||||||
|
text: 'var(--primary)',
|
||||||
|
background: DesignTokenUtils.withOpacity('var(--primary)', OPACITY.light),
|
||||||
|
border: DesignTokenUtils.borderWithOpacity('var(--primary)', OPACITY.medium, 'var(--border)'),
|
||||||
|
},
|
||||||
|
purple: {
|
||||||
|
text: 'var(--purple)',
|
||||||
|
background: DesignTokenUtils.withOpacity('var(--purple)', OPACITY.light),
|
||||||
|
border: DesignTokenUtils.borderWithOpacity('var(--purple)', OPACITY.medium, 'var(--border)'),
|
||||||
|
},
|
||||||
|
yellow: {
|
||||||
|
text: 'var(--yellow)',
|
||||||
|
background: DesignTokenUtils.withOpacity('var(--yellow)', OPACITY.light),
|
||||||
|
border: DesignTokenUtils.borderWithOpacity('var(--yellow)', OPACITY.medium, 'var(--border)'),
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
Reference in New Issue
Block a user