feat: improve theme selector and UI components
- Updated `ThemeSelector` to use a new `ThemePreview` component for better theme visualization. - Refactored button implementation in `ThemeSelector` to utilize the new `Button` component, enhancing consistency. - Added a UI showcase section in `GeneralSettingsPageClient` to display available UI components with different themes. - Enhanced `Badge`, `Button`, and `Input` components with new variants and improved styling for better usability and visual appeal. - Updated CSS variables in `globals.css` for improved contrast and accessibility across themes.
This commit is contained in:
106
UI_COMPONENTS_GUIDE.md
Normal file
106
UI_COMPONENTS_GUIDE.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# Guide des Composants UI
|
||||||
|
|
||||||
|
## 🎯 Principe
|
||||||
|
|
||||||
|
**Les composants métier ne doivent JAMAIS utiliser directement les variables CSS.** Ils doivent utiliser les composants UI abstraits.
|
||||||
|
|
||||||
|
## ❌ MAUVAIS
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// ❌ Composant métier avec variables CSS directes
|
||||||
|
function TaskCard({ task }) {
|
||||||
|
return (
|
||||||
|
<div className="bg-[var(--card)] border border-[var(--border)] p-4 rounded-lg">
|
||||||
|
<button className="bg-[var(--primary)] text-[var(--primary-foreground)] px-4 py-2 rounded">
|
||||||
|
{task.title}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ BON
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// ✅ Composant métier utilisant les composants UI
|
||||||
|
import { Card, CardContent, Button } from '@/components/ui';
|
||||||
|
|
||||||
|
function TaskCard({ task }) {
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
<Button variant="primary">
|
||||||
|
{task.title}
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📦 Composants UI Disponibles
|
||||||
|
|
||||||
|
### Button
|
||||||
|
```tsx
|
||||||
|
<Button variant="primary" size="md">Action</Button>
|
||||||
|
<Button variant="secondary">Secondaire</Button>
|
||||||
|
<Button variant="destructive">Supprimer</Button>
|
||||||
|
<Button variant="ghost">Ghost</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Badge
|
||||||
|
```tsx
|
||||||
|
<Badge variant="primary">Tag</Badge>
|
||||||
|
<Badge variant="success">Succès</Badge>
|
||||||
|
<Badge variant="destructive">Erreur</Badge>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Alert
|
||||||
|
```tsx
|
||||||
|
<Alert variant="success">
|
||||||
|
<AlertTitle>Succès</AlertTitle>
|
||||||
|
<AlertDescription>Opération réussie</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Input
|
||||||
|
```tsx
|
||||||
|
<Input placeholder="Saisir..." />
|
||||||
|
<Input variant="error" placeholder="Erreur" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### StyledCard
|
||||||
|
```tsx
|
||||||
|
<StyledCard variant="outline" color="primary">
|
||||||
|
Contenu avec style coloré
|
||||||
|
</StyledCard>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 Migration
|
||||||
|
|
||||||
|
### Étape 1: Identifier les patterns
|
||||||
|
- Rechercher `var(--` dans les composants métier
|
||||||
|
- Identifier les patterns répétés (boutons, cartes, badges)
|
||||||
|
|
||||||
|
### Étape 2: Créer des composants UI
|
||||||
|
- Encapsuler les styles dans des composants UI
|
||||||
|
- Utiliser des variants pour les variations
|
||||||
|
|
||||||
|
### Étape 3: Remplacer dans les composants métier
|
||||||
|
- Importer les composants UI
|
||||||
|
- Remplacer les éléments HTML par les composants UI
|
||||||
|
|
||||||
|
## 🎨 Avantages
|
||||||
|
|
||||||
|
1. **Consistance** - Même apparence partout
|
||||||
|
2. **Maintenance** - Changements centralisés
|
||||||
|
3. **Réutilisabilité** - Composants réutilisables
|
||||||
|
4. **Type Safety** - Props typées
|
||||||
|
5. **Performance** - Styles optimisés
|
||||||
|
|
||||||
|
## 📝 Règles
|
||||||
|
|
||||||
|
1. **JAMAIS** de variables CSS dans les composants métier
|
||||||
|
2. **TOUJOURS** utiliser les composants UI
|
||||||
|
3. **CRÉER** de nouveaux composants UI si nécessaire
|
||||||
|
4. **DOCUMENTER** les nouveaux composants UI
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
--border: #64748b; /* slate-500 - encore plus clair */
|
--border: #64748b; /* slate-500 - encore plus clair */
|
||||||
--input: #334155; /* slate-700 - plus clair */
|
--input: #334155; /* slate-700 - plus clair */
|
||||||
--primary: #06b6d4; /* cyan-500 */
|
--primary: #06b6d4; /* cyan-500 */
|
||||||
--primary-foreground: #f1f5f9; /* slate-100 */
|
--primary-foreground: #ffffff; /* white - better contrast with cyan */
|
||||||
--muted: #64748b; /* slate-500 */
|
--muted: #64748b; /* slate-500 */
|
||||||
--muted-foreground: #94a3b8; /* slate-400 */
|
--muted-foreground: #94a3b8; /* slate-400 */
|
||||||
--accent: #f59e0b; /* amber-500 */
|
--accent: #f59e0b; /* amber-500 */
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
--border: #6272a4; /* dracula comment */
|
--border: #6272a4; /* dracula comment */
|
||||||
--input: #44475a; /* dracula current line */
|
--input: #44475a; /* dracula current line */
|
||||||
--primary: #ff79c6; /* dracula pink */
|
--primary: #ff79c6; /* dracula pink */
|
||||||
--primary-foreground: #282a36; /* dracula background */
|
--primary-foreground: #ffffff; /* white for contrast */
|
||||||
--muted: #6272a4; /* dracula comment */
|
--muted: #6272a4; /* dracula comment */
|
||||||
--muted-foreground: #50fa7b; /* dracula green */
|
--muted-foreground: #50fa7b; /* dracula green */
|
||||||
--accent: #ffb86c; /* dracula orange */
|
--accent: #ffb86c; /* dracula orange */
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
--border: #49483e; /* monokai line */
|
--border: #49483e; /* monokai line */
|
||||||
--input: #3e3d32; /* monokai selection */
|
--input: #3e3d32; /* monokai selection */
|
||||||
--primary: #f92672; /* monokai pink */
|
--primary: #f92672; /* monokai pink */
|
||||||
--primary-foreground: #272822; /* monokai background */
|
--primary-foreground: #ffffff; /* white for contrast */
|
||||||
--muted: #75715e; /* monokai comment */
|
--muted: #75715e; /* monokai comment */
|
||||||
--muted-foreground: #a6e22e; /* monokai green */
|
--muted-foreground: #a6e22e; /* monokai green */
|
||||||
--accent: #fd971f; /* monokai orange */
|
--accent: #fd971f; /* monokai orange */
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
--border: #4c566a; /* nord3 */
|
--border: #4c566a; /* nord3 */
|
||||||
--input: #3b4252; /* nord1 */
|
--input: #3b4252; /* nord1 */
|
||||||
--primary: #88c0d0; /* nord7 */
|
--primary: #88c0d0; /* nord7 */
|
||||||
--primary-foreground: #2e3440; /* nord0 */
|
--primary-foreground: #ffffff; /* white for contrast */
|
||||||
--muted: #4c566a; /* nord3 */
|
--muted: #4c566a; /* nord3 */
|
||||||
--muted-foreground: #81a1c1; /* nord9 */
|
--muted-foreground: #81a1c1; /* nord9 */
|
||||||
--accent: #d08770; /* nord12 */
|
--accent: #d08770; /* nord12 */
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
--border: #665c54; /* gruvbox bg3 */
|
--border: #665c54; /* gruvbox bg3 */
|
||||||
--input: #3c3836; /* gruvbox bg1 */
|
--input: #3c3836; /* gruvbox bg1 */
|
||||||
--primary: #fe8019; /* gruvbox orange */
|
--primary: #fe8019; /* gruvbox orange */
|
||||||
--primary-foreground: #282828; /* gruvbox bg0 */
|
--primary-foreground: #ffffff; /* white for contrast */
|
||||||
--muted: #665c54; /* gruvbox bg3 */
|
--muted: #665c54; /* gruvbox bg3 */
|
||||||
--muted-foreground: #a89984; /* gruvbox gray */
|
--muted-foreground: #a89984; /* gruvbox gray */
|
||||||
--accent: #fabd2f; /* gruvbox yellow */
|
--accent: #fabd2f; /* gruvbox yellow */
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
--border: #565f89; /* tokyo-night comment */
|
--border: #565f89; /* tokyo-night comment */
|
||||||
--input: #24283b; /* tokyo-night bg_highlight */
|
--input: #24283b; /* tokyo-night bg_highlight */
|
||||||
--primary: #7aa2f7; /* tokyo-night blue */
|
--primary: #7aa2f7; /* tokyo-night blue */
|
||||||
--primary-foreground: #1a1b26; /* tokyo-night bg */
|
--primary-foreground: #ffffff; /* white for contrast */
|
||||||
--muted: #565f89; /* tokyo-night comment */
|
--muted: #565f89; /* tokyo-night comment */
|
||||||
--muted-foreground: #9aa5ce; /* tokyo-night fg_dark */
|
--muted-foreground: #9aa5ce; /* tokyo-night fg_dark */
|
||||||
--accent: #ff9e64; /* tokyo-night orange */
|
--accent: #ff9e64; /* tokyo-night orange */
|
||||||
@@ -178,7 +178,7 @@
|
|||||||
--border: #6c7086; /* catppuccin overlay0 */
|
--border: #6c7086; /* catppuccin overlay0 */
|
||||||
--input: #313244; /* catppuccin surface0 */
|
--input: #313244; /* catppuccin surface0 */
|
||||||
--primary: #cba6f7; /* catppuccin mauve */
|
--primary: #cba6f7; /* catppuccin mauve */
|
||||||
--primary-foreground: #1e1e2e; /* catppuccin base */
|
--primary-foreground: #ffffff; /* white for contrast */
|
||||||
--muted: #6c7086; /* catppuccin overlay0 */
|
--muted: #6c7086; /* catppuccin overlay0 */
|
||||||
--muted-foreground: #a6adc8; /* catppuccin subtext0 */
|
--muted-foreground: #a6adc8; /* catppuccin subtext0 */
|
||||||
--accent: #fab387; /* catppuccin peach */
|
--accent: #fab387; /* catppuccin peach */
|
||||||
@@ -202,7 +202,7 @@
|
|||||||
--border: #6e6a86; /* rose-pine muted */
|
--border: #6e6a86; /* rose-pine muted */
|
||||||
--input: #26233a; /* rose-pine surface */
|
--input: #26233a; /* rose-pine surface */
|
||||||
--primary: #c4a7e7; /* rose-pine iris */
|
--primary: #c4a7e7; /* rose-pine iris */
|
||||||
--primary-foreground: #191724; /* rose-pine base */
|
--primary-foreground: #ffffff; /* white for contrast */
|
||||||
--muted: #6e6a86; /* rose-pine muted */
|
--muted: #6e6a86; /* rose-pine muted */
|
||||||
--muted-foreground: #908caa; /* rose-pine subtle */
|
--muted-foreground: #908caa; /* rose-pine subtle */
|
||||||
--accent: #f6c177; /* rose-pine gold */
|
--accent: #f6c177; /* rose-pine gold */
|
||||||
@@ -226,7 +226,7 @@
|
|||||||
--border: #5c6370; /* one-dark bg3 */
|
--border: #5c6370; /* one-dark bg3 */
|
||||||
--input: #3e4451; /* one-dark bg1 */
|
--input: #3e4451; /* one-dark bg1 */
|
||||||
--primary: #61afef; /* one-dark blue */
|
--primary: #61afef; /* one-dark blue */
|
||||||
--primary-foreground: #282c34; /* one-dark bg */
|
--primary-foreground: #ffffff; /* white for contrast */
|
||||||
--muted: #5c6370; /* one-dark bg3 */
|
--muted: #5c6370; /* one-dark bg3 */
|
||||||
--muted-foreground: #828997; /* one-dark gray */
|
--muted-foreground: #828997; /* one-dark gray */
|
||||||
--accent: #e06c75; /* one-dark red */
|
--accent: #e06c75; /* one-dark red */
|
||||||
@@ -250,7 +250,7 @@
|
|||||||
--border: #3c3c3c; /* material outline */
|
--border: #3c3c3c; /* material outline */
|
||||||
--input: #1e1e1e; /* material surface */
|
--input: #1e1e1e; /* material surface */
|
||||||
--primary: #bb86fc; /* material primary */
|
--primary: #bb86fc; /* material primary */
|
||||||
--primary-foreground: #121212; /* material bg */
|
--primary-foreground: #ffffff; /* white for contrast */
|
||||||
--muted: #3c3c3c; /* material outline */
|
--muted: #3c3c3c; /* material outline */
|
||||||
--muted-foreground: #b3b3b3; /* material on-surface-variant */
|
--muted-foreground: #b3b3b3; /* material on-surface-variant */
|
||||||
--accent: #ffab40; /* material secondary */
|
--accent: #ffab40; /* material secondary */
|
||||||
@@ -274,7 +274,7 @@
|
|||||||
--border: #586e75; /* solarized base01 */
|
--border: #586e75; /* solarized base01 */
|
||||||
--input: #073642; /* solarized base02 */
|
--input: #073642; /* solarized base02 */
|
||||||
--primary: #268bd2; /* solarized blue */
|
--primary: #268bd2; /* solarized blue */
|
||||||
--primary-foreground: #002b36; /* solarized base03 */
|
--primary-foreground: #ffffff; /* white for contrast */
|
||||||
--muted: #586e75; /* solarized base01 */
|
--muted: #586e75; /* solarized base01 */
|
||||||
--muted-foreground: #657b83; /* solarized base00 */
|
--muted-foreground: #657b83; /* solarized base00 */
|
||||||
--accent: #b58900; /* solarized yellow */
|
--accent: #b58900; /* solarized yellow */
|
||||||
|
|||||||
5
src/app/ui-showcase/page.tsx
Normal file
5
src/app/ui-showcase/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { UIShowcaseClient } from '@/components/ui-showcase/UIShowcaseClient';
|
||||||
|
|
||||||
|
export default function UIShowcasePage() {
|
||||||
|
return <UIShowcaseClient />;
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import { useTheme } from '@/contexts/ThemeContext';
|
import { useTheme } from '@/contexts/ThemeContext';
|
||||||
import { Theme } from '@/lib/types';
|
import { Theme } from '@/lib/types';
|
||||||
|
import { Button } from '@/components/ui/Button';
|
||||||
|
|
||||||
|
|
||||||
const themes: { id: Theme; name: string; description: string }[] = [
|
const themes: { id: Theme; name: string; description: string }[] = [
|
||||||
{ id: 'dark', name: 'Dark', description: 'Thème sombre par défaut' },
|
{ id: 'dark', name: 'Dark', description: 'Thème sombre par défaut' },
|
||||||
@@ -17,6 +19,50 @@ const themes: { id: Theme; name: string; description: string }[] = [
|
|||||||
{ id: 'solarized', name: 'Solarized', description: 'Thème Solarized scientifique' },
|
{ id: 'solarized', name: 'Solarized', description: 'Thème Solarized scientifique' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Composant pour l'aperçu du thème
|
||||||
|
function ThemePreview({ themeId, isSelected }: { themeId: Theme; isSelected: boolean }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`w-16 h-12 rounded-lg border-2 overflow-hidden ${themeId}`}
|
||||||
|
style={{
|
||||||
|
borderColor: isSelected ? 'var(--primary)' : 'var(--border)',
|
||||||
|
backgroundColor: 'var(--background)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Barre de titre */}
|
||||||
|
<div
|
||||||
|
className="h-3 w-full"
|
||||||
|
style={{ backgroundColor: 'var(--card)' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Contenu avec couleurs du thème */}
|
||||||
|
<div className="p-1 h-9 flex flex-col gap-0.5">
|
||||||
|
{/* Ligne de texte */}
|
||||||
|
<div
|
||||||
|
className="h-1 rounded-sm"
|
||||||
|
style={{ backgroundColor: 'var(--foreground)' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Couleurs d'accent */}
|
||||||
|
<div className="flex gap-0.5">
|
||||||
|
<div
|
||||||
|
className="h-1 flex-1 rounded-sm"
|
||||||
|
style={{ backgroundColor: 'var(--primary)' }}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="h-1 flex-1 rounded-sm"
|
||||||
|
style={{ backgroundColor: 'var(--accent)' }}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="h-1 flex-1 rounded-sm"
|
||||||
|
style={{ backgroundColor: 'var(--success)' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function ThemeSelector() {
|
export function ThemeSelector() {
|
||||||
const { theme, setTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
|
|
||||||
@@ -36,134 +82,19 @@ export function ThemeSelector() {
|
|||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||||
{themes.map((themeOption) => (
|
{themes.map((themeOption) => (
|
||||||
<button
|
<Button
|
||||||
key={themeOption.id}
|
key={themeOption.id}
|
||||||
onClick={() => setTheme(themeOption.id)}
|
onClick={() => setTheme(themeOption.id)}
|
||||||
className={`
|
variant={theme === themeOption.id ? 'selected' : 'secondary'}
|
||||||
p-4 rounded-lg border text-left transition-all duration-200 group
|
className="p-4 h-auto text-left justify-start"
|
||||||
${theme === themeOption.id
|
|
||||||
? 'border-[var(--primary)] bg-[color-mix(in_srgb,var(--primary)_15%,transparent)] shadow-lg shadow-[var(--primary)]/20'
|
|
||||||
: 'border-[var(--border)] hover:border-[var(--primary)] hover:bg-[color-mix(in_srgb,var(--primary)_8%,transparent)] hover:shadow-md'
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
{/* Aperçu du thème */}
|
{/* Aperçu du thème */}
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<div
|
<ThemePreview
|
||||||
className="w-16 h-12 rounded-lg border-2 overflow-hidden"
|
themeId={themeOption.id}
|
||||||
style={{
|
isSelected={theme === themeOption.id}
|
||||||
borderColor: theme === themeOption.id ? 'var(--primary)' : 'var(--border)',
|
|
||||||
backgroundColor: themeOption.id === 'light' ? '#f1f5f9' :
|
|
||||||
themeOption.id === 'dark' ? '#1e293b' :
|
|
||||||
themeOption.id === 'dracula' ? '#282a36' :
|
|
||||||
themeOption.id === 'monokai' ? '#272822' :
|
|
||||||
themeOption.id === 'nord' ? '#2e3440' :
|
|
||||||
themeOption.id === 'gruvbox' ? '#282828' :
|
|
||||||
themeOption.id === 'tokyo_night' ? '#1a1b26' :
|
|
||||||
themeOption.id === 'catppuccin' ? '#1e1e2e' :
|
|
||||||
themeOption.id === 'rose_pine' ? '#191724' :
|
|
||||||
themeOption.id === 'one_dark' ? '#282c34' :
|
|
||||||
themeOption.id === 'material' ? '#121212' :
|
|
||||||
'#002b36'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Barre de titre */}
|
|
||||||
<div
|
|
||||||
className="h-3 w-full"
|
|
||||||
style={{
|
|
||||||
backgroundColor: themeOption.id === 'light' ? '#ffffff' :
|
|
||||||
themeOption.id === 'dark' ? '#334155' :
|
|
||||||
themeOption.id === 'dracula' ? '#44475a' :
|
|
||||||
themeOption.id === 'monokai' ? '#3e3d32' :
|
|
||||||
themeOption.id === 'nord' ? '#3b4252' :
|
|
||||||
themeOption.id === 'gruvbox' ? '#3c3836' :
|
|
||||||
themeOption.id === 'tokyo_night' ? '#24283b' :
|
|
||||||
themeOption.id === 'catppuccin' ? '#313244' :
|
|
||||||
themeOption.id === 'rose_pine' ? '#26233a' :
|
|
||||||
themeOption.id === 'one_dark' ? '#3e4451' :
|
|
||||||
themeOption.id === 'material' ? '#1e1e1e' :
|
|
||||||
'#073642'
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Contenu avec couleurs du thème */}
|
|
||||||
<div className="p-1 h-9 flex flex-col gap-0.5">
|
|
||||||
{/* Ligne de texte */}
|
|
||||||
<div
|
|
||||||
className="h-1 rounded-sm"
|
|
||||||
style={{
|
|
||||||
backgroundColor: themeOption.id === 'light' ? '#0f172a' :
|
|
||||||
themeOption.id === 'dark' ? '#f1f5f9' :
|
|
||||||
themeOption.id === 'dracula' ? '#f8f8f2' :
|
|
||||||
themeOption.id === 'monokai' ? '#f8f8f2' :
|
|
||||||
themeOption.id === 'nord' ? '#d8dee9' :
|
|
||||||
themeOption.id === 'gruvbox' ? '#ebdbb2' :
|
|
||||||
themeOption.id === 'tokyo_night' ? '#a9b1d6' :
|
|
||||||
themeOption.id === 'catppuccin' ? '#cdd6f4' :
|
|
||||||
themeOption.id === 'rose_pine' ? '#e0def4' :
|
|
||||||
themeOption.id === 'one_dark' ? '#abb2bf' :
|
|
||||||
themeOption.id === 'material' ? '#ffffff' :
|
|
||||||
'#93a1a1'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Couleurs d'accent */}
|
|
||||||
<div className="flex gap-0.5">
|
|
||||||
<div
|
|
||||||
className="h-1 flex-1 rounded-sm"
|
|
||||||
style={{
|
|
||||||
backgroundColor: themeOption.id === 'light' ? '#0891b2' :
|
|
||||||
themeOption.id === 'dark' ? '#06b6d4' :
|
|
||||||
themeOption.id === 'dracula' ? '#ff79c6' :
|
|
||||||
themeOption.id === 'monokai' ? '#f92672' :
|
|
||||||
themeOption.id === 'nord' ? '#88c0d0' :
|
|
||||||
themeOption.id === 'gruvbox' ? '#fe8019' :
|
|
||||||
themeOption.id === 'tokyo_night' ? '#7aa2f7' :
|
|
||||||
themeOption.id === 'catppuccin' ? '#cba6f7' :
|
|
||||||
themeOption.id === 'rose_pine' ? '#c4a7e7' :
|
|
||||||
themeOption.id === 'one_dark' ? '#61afef' :
|
|
||||||
themeOption.id === 'material' ? '#bb86fc' :
|
|
||||||
'#268bd2'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="h-1 flex-1 rounded-sm"
|
|
||||||
style={{
|
|
||||||
backgroundColor: themeOption.id === 'light' ? '#d97706' :
|
|
||||||
themeOption.id === 'dark' ? '#f59e0b' :
|
|
||||||
themeOption.id === 'dracula' ? '#ffb86c' :
|
|
||||||
themeOption.id === 'monokai' ? '#fd971f' :
|
|
||||||
themeOption.id === 'nord' ? '#d08770' :
|
|
||||||
themeOption.id === 'gruvbox' ? '#fabd2f' :
|
|
||||||
themeOption.id === 'tokyo_night' ? '#ff9e64' :
|
|
||||||
themeOption.id === 'catppuccin' ? '#fab387' :
|
|
||||||
themeOption.id === 'rose_pine' ? '#f6c177' :
|
|
||||||
themeOption.id === 'one_dark' ? '#e06c75' :
|
|
||||||
themeOption.id === 'material' ? '#ffab40' :
|
|
||||||
'#b58900'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="h-1 flex-1 rounded-sm"
|
|
||||||
style={{
|
|
||||||
backgroundColor: themeOption.id === 'light' ? '#059669' :
|
|
||||||
themeOption.id === 'dark' ? '#10b981' :
|
|
||||||
themeOption.id === 'dracula' ? '#50fa7b' :
|
|
||||||
themeOption.id === 'monokai' ? '#a6e22e' :
|
|
||||||
themeOption.id === 'nord' ? '#a3be8c' :
|
|
||||||
themeOption.id === 'gruvbox' ? '#b8bb26' :
|
|
||||||
themeOption.id === 'tokyo_night' ? '#9ece6a' :
|
|
||||||
themeOption.id === 'catppuccin' ? '#a6e3a1' :
|
|
||||||
themeOption.id === 'rose_pine' ? '#9ccfd8' :
|
|
||||||
themeOption.id === 'one_dark' ? '#98c379' :
|
|
||||||
themeOption.id === 'material' ? '#4caf50' :
|
|
||||||
'#859900'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
@@ -180,7 +111,7 @@ export function ThemeSelector() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -53,6 +53,28 @@ export function GeneralSettingsPageClient({ initialTags }: GeneralSettingsPageCl
|
|||||||
<ThemeSelector />
|
<ThemeSelector />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* UI Showcase */}
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-medium text-[var(--foreground)] mb-2">
|
||||||
|
🎨 UI Components Showcase
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-[var(--muted-foreground)]">
|
||||||
|
Visualisez tous les composants UI disponibles avec différents thèmes
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
href="/ui-showcase"
|
||||||
|
className="inline-flex items-center px-4 py-2 bg-[var(--primary)] text-[var(--primary-foreground)] rounded-md hover:bg-[color-mix(in_srgb,var(--primary)_90%,transparent)] transition-colors font-medium"
|
||||||
|
>
|
||||||
|
Voir la démo
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Gestion des tags */}
|
{/* Gestion des tags */}
|
||||||
<TagsManagement
|
<TagsManagement
|
||||||
tags={tags}
|
tags={tags}
|
||||||
|
|||||||
327
src/components/ui-showcase/UIShowcaseClient.tsx
Normal file
327
src/components/ui-showcase/UIShowcaseClient.tsx
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Button } from '@/components/ui/Button';
|
||||||
|
import { Badge } from '@/components/ui/Badge';
|
||||||
|
import { Alert, AlertTitle, AlertDescription } from '@/components/ui/Alert';
|
||||||
|
import { Input } from '@/components/ui/Input';
|
||||||
|
import { StyledCard } from '@/components/ui/StyledCard';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
|
||||||
|
import { ThemeSelector } from '@/components/ThemeSelector';
|
||||||
|
|
||||||
|
export function UIShowcaseClient() {
|
||||||
|
const [inputValue, setInputValue] = useState('');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-[var(--background)] p-8">
|
||||||
|
<div className="max-w-6xl mx-auto space-y-16">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="text-center space-y-6">
|
||||||
|
<h1 className="text-4xl font-mono font-bold text-[var(--foreground)]">
|
||||||
|
🎨 UI Components Showcase
|
||||||
|
</h1>
|
||||||
|
<p className="text-lg text-[var(--muted-foreground)]">
|
||||||
|
Démonstration de tous les composants UI disponibles
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Theme Selector */}
|
||||||
|
<section className="space-y-8">
|
||||||
|
<div className="text-center">
|
||||||
|
<h2 className="text-2xl font-mono font-semibold text-[var(--foreground)] mb-6">
|
||||||
|
🎨 Sélecteur de Thèmes
|
||||||
|
</h2>
|
||||||
|
<p className="text-[var(--muted-foreground)] mb-8">
|
||||||
|
Changez de thème pour voir comment tous les composants s'adaptent
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
<div className="bg-[var(--card)]/30 border border-[var(--border)]/50 rounded-lg p-6 backdrop-blur-sm">
|
||||||
|
<ThemeSelector />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Buttons Section */}
|
||||||
|
<section className="space-y-8">
|
||||||
|
<h2 className="text-2xl font-mono font-semibold text-[var(--foreground)] border-b border-[var(--border)] pb-3">
|
||||||
|
Buttons
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<h3 className="text-lg font-medium text-[var(--foreground)]">Variants</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Button variant="primary">Primary Button</Button>
|
||||||
|
<Button variant="secondary">Secondary Button</Button>
|
||||||
|
<Button variant="ghost">Ghost Button</Button>
|
||||||
|
<Button variant="destructive">Destructive Button</Button>
|
||||||
|
<Button variant="success">Success Button</Button>
|
||||||
|
<Button variant="selected">Selected Button</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<h3 className="text-lg font-medium text-[var(--foreground)]">Sizes</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Button size="sm">Small Button</Button>
|
||||||
|
<Button size="md">Medium Button</Button>
|
||||||
|
<Button size="lg">Large Button</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<h3 className="text-lg font-medium text-[var(--foreground)]">States</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Button>Normal State</Button>
|
||||||
|
<Button disabled>Disabled State</Button>
|
||||||
|
<Button className="opacity-50">Loading State</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Badges Section */}
|
||||||
|
<section className="space-y-8">
|
||||||
|
<h2 className="text-2xl font-mono font-semibold text-[var(--foreground)] border-b border-[var(--border)] pb-3">
|
||||||
|
Badges
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex flex-wrap gap-4">
|
||||||
|
<Badge variant="default">Default Badge</Badge>
|
||||||
|
<Badge variant="primary">Primary Badge</Badge>
|
||||||
|
<Badge variant="success">Success Badge</Badge>
|
||||||
|
<Badge variant="destructive">Destructive Badge</Badge>
|
||||||
|
<Badge variant="accent">Accent Badge</Badge>
|
||||||
|
<Badge variant="purple">Purple Badge</Badge>
|
||||||
|
<Badge variant="yellow">Yellow Badge</Badge>
|
||||||
|
<Badge variant="green">Green Badge</Badge>
|
||||||
|
<Badge variant="blue">Blue Badge</Badge>
|
||||||
|
<Badge variant="gray">Gray Badge</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Alerts Section */}
|
||||||
|
<section className="space-y-8">
|
||||||
|
<h2 className="text-2xl font-mono font-semibold text-[var(--foreground)] border-b border-[var(--border)] pb-3">
|
||||||
|
Alerts
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<Alert variant="default">
|
||||||
|
<AlertTitle>Information</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
Ceci est une alerte par défaut avec des informations importantes.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<Alert variant="success">
|
||||||
|
<AlertTitle>Succès</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
Opération terminée avec succès ! Toutes les données ont été sauvegardées.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertTitle>Erreur</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
Une erreur s'est produite lors du traitement de votre demande.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<Alert variant="warning">
|
||||||
|
<AlertTitle>Attention</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
Veuillez vérifier vos informations avant de continuer.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<Alert variant="info">
|
||||||
|
<AlertTitle>Conseil</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
Astuce : Vous pouvez utiliser les raccourcis clavier pour naviguer plus rapidement.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Inputs Section */}
|
||||||
|
<section className="space-y-8">
|
||||||
|
<h2 className="text-2xl font-mono font-semibold text-[var(--foreground)] border-b border-[var(--border)] pb-3">
|
||||||
|
Inputs
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<h3 className="text-lg font-medium text-[var(--foreground)]">Types</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Input placeholder="Texte normal" />
|
||||||
|
<Input type="email" placeholder="Adresse email" />
|
||||||
|
<Input type="password" placeholder="Mot de passe" />
|
||||||
|
<Input type="number" placeholder="Nombre" />
|
||||||
|
<Input type="search" placeholder="Rechercher..." />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<h3 className="text-lg font-medium text-[var(--foreground)]">États</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Input placeholder="État normal" />
|
||||||
|
<Input placeholder="État focus" autoFocus />
|
||||||
|
<Input placeholder="État désactivé" disabled />
|
||||||
|
<Input variant="error" placeholder="État erreur" />
|
||||||
|
<Input value={inputValue} onChange={(e) => setInputValue(e.target.value)} placeholder="État avec valeur" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Cards Section */}
|
||||||
|
<section className="space-y-6">
|
||||||
|
<h2 className="text-2xl font-mono font-semibold text-[var(--foreground)] border-b border-[var(--border)] pb-2">
|
||||||
|
Cards
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{/* Standard Cards */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Card Standard</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p className="text-[var(--muted-foreground)]">
|
||||||
|
Ceci est une carte standard avec header et contenu.
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card variant="elevated">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Card Élevée</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p className="text-[var(--muted-foreground)]">
|
||||||
|
Cette carte a une ombre plus prononcée.
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card variant="bordered">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Card Bordée</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p className="text-[var(--muted-foreground)]">
|
||||||
|
Cette carte a une bordure colorée.
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Styled Cards */}
|
||||||
|
<StyledCard color="primary" className="p-4">
|
||||||
|
<h3 className="font-medium mb-2">Styled Card Primary</h3>
|
||||||
|
<p className="text-sm opacity-80">
|
||||||
|
Carte avec couleur primaire et fond subtil.
|
||||||
|
</p>
|
||||||
|
</StyledCard>
|
||||||
|
|
||||||
|
<StyledCard color="success" className="p-4">
|
||||||
|
<h3 className="font-medium mb-2">Styled Card Success</h3>
|
||||||
|
<p className="text-sm opacity-80">
|
||||||
|
Carte avec couleur de succès.
|
||||||
|
</p>
|
||||||
|
</StyledCard>
|
||||||
|
|
||||||
|
<StyledCard color="destructive" className="p-4">
|
||||||
|
<h3 className="font-medium mb-2">Styled Card Destructive</h3>
|
||||||
|
<p className="text-sm opacity-80">
|
||||||
|
Carte avec couleur destructive.
|
||||||
|
</p>
|
||||||
|
</StyledCard>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Interactive Demo */}
|
||||||
|
<section className="space-y-6">
|
||||||
|
<h2 className="text-2xl font-mono font-semibold text-[var(--foreground)] border-b border-[var(--border)] pb-2">
|
||||||
|
Démonstration Interactive
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Formulaire d'exemple</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-[var(--foreground)] mb-2">
|
||||||
|
Nom d'utilisateur
|
||||||
|
</label>
|
||||||
|
<Input placeholder="Entrez votre nom" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-[var(--foreground)] mb-2">
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
<Input type="email" placeholder="votre@email.com" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-[var(--foreground)] mb-2">
|
||||||
|
Statut
|
||||||
|
</label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Badge variant="success">Actif</Badge>
|
||||||
|
<Badge variant="gray">En attente</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-3 pt-4">
|
||||||
|
<Button variant="primary">Enregistrer</Button>
|
||||||
|
<Button variant="secondary">Annuler</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Notifications</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<Alert variant="success">
|
||||||
|
<AlertTitle>Bienvenue !</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
Votre compte a été créé avec succès.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<Alert variant="warning">
|
||||||
|
<AlertTitle>Mise à jour disponible</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
Une nouvelle version de l'application est disponible.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button size="sm">Voir les détails</Button>
|
||||||
|
<Button size="sm" variant="ghost">Ignorer</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<div className="text-center pt-8 border-t border-[var(--border)]">
|
||||||
|
<p className="text-[var(--muted-foreground)]">
|
||||||
|
Cette page est accessible via <code className="bg-[var(--card)] px-2 py-1 rounded text-sm">/ui-showcase</code>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
58
src/components/ui/Alert.tsx
Normal file
58
src/components/ui/Alert.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { HTMLAttributes, forwardRef } from 'react';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
interface AlertProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
|
variant?: 'default' | 'success' | 'destructive' | 'warning' | 'info';
|
||||||
|
}
|
||||||
|
|
||||||
|
const Alert = forwardRef<HTMLDivElement, AlertProps>(
|
||||||
|
({ className, variant = 'default', ...props }, ref) => {
|
||||||
|
const variants = {
|
||||||
|
default: 'bg-[var(--card)] text-[var(--foreground)] border border-[var(--border)]',
|
||||||
|
success: 'bg-[color-mix(in_srgb,var(--success)_10%,transparent)] text-[var(--success)] border border-[color-mix(in_srgb,var(--success)_20%,var(--border))]',
|
||||||
|
destructive: 'bg-[color-mix(in_srgb,var(--destructive)_10%,transparent)] text-[var(--destructive)] border border-[color-mix(in_srgb,var(--destructive)_20%,var(--border))]',
|
||||||
|
warning: 'bg-[color-mix(in_srgb,var(--accent)_10%,transparent)] text-[var(--accent)] border border-[color-mix(in_srgb,var(--accent)_20%,var(--border))]',
|
||||||
|
info: 'bg-[color-mix(in_srgb,var(--primary)_10%,transparent)] text-[var(--primary)] border border-[color-mix(in_srgb,var(--primary)_20%,var(--border))]'
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'relative w-full rounded-lg border p-4',
|
||||||
|
variants[variant],
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Alert.displayName = 'Alert';
|
||||||
|
|
||||||
|
const AlertTitle = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLHeadingElement>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<h5
|
||||||
|
ref={ref}
|
||||||
|
className={cn('mb-1 font-medium leading-none tracking-tight', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
AlertTitle.displayName = 'AlertTitle';
|
||||||
|
|
||||||
|
const AlertDescription = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn('text-sm [&_p]:leading-relaxed', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
AlertDescription.displayName = 'AlertDescription';
|
||||||
|
|
||||||
|
export { Alert, AlertTitle, AlertDescription };
|
||||||
@@ -1,38 +1,33 @@
|
|||||||
import { HTMLAttributes, forwardRef } from 'react';
|
import { HTMLAttributes, forwardRef } from 'react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
interface BadgeProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
variant?: 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'outline';
|
variant?: 'default' | 'primary' | 'success' | 'destructive' | 'accent' | 'purple' | 'yellow' | 'green' | 'blue' | 'gray';
|
||||||
size?: 'sm' | 'md';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Badge = forwardRef<HTMLSpanElement, BadgeProps>(
|
const Badge = forwardRef<HTMLDivElement, BadgeProps>(
|
||||||
({ className, variant = 'default', size = 'md', ...props }, ref) => {
|
({ className, variant = 'default', ...props }, ref) => {
|
||||||
const baseStyles = 'inline-flex items-center font-mono font-medium transition-all duration-200';
|
|
||||||
|
|
||||||
const variants = {
|
const variants = {
|
||||||
default: 'bg-[var(--card)] text-[var(--muted-foreground)] border border-[var(--border)]',
|
default: 'bg-[var(--card)] text-[var(--foreground)] border border-[var(--border)]',
|
||||||
primary: 'bg-[var(--primary)]/20 text-[var(--primary)] border border-[var(--primary)]/30',
|
primary: 'bg-[color-mix(in_srgb,var(--primary)_10%,transparent)] text-[var(--primary)] border border-[color-mix(in_srgb,var(--primary)_25%,var(--border))]',
|
||||||
success: 'bg-[var(--success)]/20 text-[var(--success)] border border-[var(--success)]/30',
|
success: 'bg-[color-mix(in_srgb,var(--success)_10%,transparent)] text-[var(--success)] border border-[color-mix(in_srgb,var(--success)_25%,var(--border))]',
|
||||||
warning: 'bg-[var(--accent)]/20 text-[var(--accent)] border border-[var(--accent)]/30',
|
destructive: 'bg-[color-mix(in_srgb,var(--destructive)_10%,transparent)] text-[var(--destructive)] border border-[color-mix(in_srgb,var(--destructive)_25%,var(--border))]',
|
||||||
danger: 'bg-[var(--destructive)]/20 text-[var(--destructive)] border border-[var(--destructive)]/30',
|
accent: 'bg-[color-mix(in_srgb,var(--accent)_10%,transparent)] text-[var(--accent)] border border-[color-mix(in_srgb,var(--accent)_25%,var(--border))]',
|
||||||
outline: 'bg-transparent text-[var(--muted-foreground)] border border-[var(--border)] hover:bg-[var(--card-hover)] hover:text-[var(--foreground)]'
|
purple: 'bg-[color-mix(in_srgb,var(--purple)_10%,transparent)] text-[var(--purple)] border border-[color-mix(in_srgb,var(--purple)_25%,var(--border))]',
|
||||||
};
|
yellow: 'bg-[color-mix(in_srgb,var(--yellow)_10%,transparent)] text-[var(--yellow)] border border-[color-mix(in_srgb,var(--yellow)_25%,var(--border))]',
|
||||||
|
green: 'bg-[color-mix(in_srgb,var(--green)_10%,transparent)] text-[var(--green)] border border-[color-mix(in_srgb,var(--green)_25%,var(--border))]',
|
||||||
const sizes = {
|
blue: 'bg-[color-mix(in_srgb,var(--blue)_10%,transparent)] text-[var(--blue)] border border-[color-mix(in_srgb,var(--blue)_25%,var(--border))]',
|
||||||
sm: 'px-1.5 py-0.5 text-xs rounded',
|
gray: 'bg-[color-mix(in_srgb,var(--gray)_10%,transparent)] text-[var(--gray)] border border-[color-mix(in_srgb,var(--gray)_25%,var(--border))]'
|
||||||
md: 'px-2 py-1 text-xs rounded-md'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<div
|
||||||
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
baseStyles,
|
'inline-flex items-center rounded-md px-2.5 py-0.5 text-xs font-medium transition-colors',
|
||||||
variants[variant],
|
variants[variant],
|
||||||
sizes[size],
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,36 +2,36 @@ import { ButtonHTMLAttributes, forwardRef } from 'react';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
variant?: 'primary' | 'secondary' | 'danger' | 'ghost';
|
variant?: 'primary' | 'secondary' | 'ghost' | 'destructive' | 'success' | 'selected';
|
||||||
size?: 'sm' | 'md' | 'lg';
|
size?: 'sm' | 'md' | 'lg';
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
({ className, variant = 'primary', size = 'md', ...props }, ref) => {
|
({ className, variant = 'primary', size = 'md', ...props }, ref) => {
|
||||||
const baseStyles = 'inline-flex items-center justify-center font-mono font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-[var(--background)] disabled:opacity-50 disabled:cursor-not-allowed';
|
|
||||||
|
|
||||||
const variants = {
|
const variants = {
|
||||||
primary: 'bg-[var(--primary)] hover:bg-[var(--primary)]/80 text-[var(--primary-foreground)] border border-[var(--primary)]/30 shadow-[var(--primary)]/20 shadow-lg hover:shadow-[var(--primary)]/30 focus:ring-[var(--primary)]',
|
primary: 'bg-[var(--primary)] text-[var(--primary-foreground)] hover:bg-[color-mix(in_srgb,var(--primary)_90%,transparent)]',
|
||||||
secondary: 'bg-[var(--card)] hover:bg-[var(--card-hover)] text-[var(--foreground)] border border-[var(--border)] shadow-[var(--muted)]/20 shadow-lg hover:shadow-[var(--muted)]/30 focus:ring-[var(--muted)]',
|
secondary: 'bg-[var(--card)] text-[var(--foreground)] border border-[var(--border)] hover:bg-[var(--card-hover)]',
|
||||||
danger: 'bg-[var(--destructive)] hover:bg-[var(--destructive)]/80 text-white border border-[var(--destructive)]/30 shadow-[var(--destructive)]/20 shadow-lg hover:shadow-[var(--destructive)]/30 focus:ring-[var(--destructive)]',
|
ghost: 'text-[var(--foreground)] hover:bg-[var(--card-hover)]',
|
||||||
ghost: 'bg-transparent hover:bg-[var(--card)]/50 text-[var(--muted-foreground)] hover:text-[var(--foreground)] border border-[var(--border)]/50 hover:border-[var(--border)] focus:ring-[var(--muted)]'
|
destructive: 'bg-[var(--destructive)] text-white hover:bg-[color-mix(in_srgb,var(--destructive)_90%,transparent)]',
|
||||||
|
success: 'bg-[var(--success)] text-white hover:bg-[color-mix(in_srgb,var(--success)_90%,transparent)]',
|
||||||
|
selected: 'bg-[color-mix(in_srgb,var(--primary)_15%,transparent)] text-[var(--foreground)] border border-[var(--primary)] hover:bg-[color-mix(in_srgb,var(--primary)_20%,transparent)]'
|
||||||
};
|
};
|
||||||
|
|
||||||
const sizes = {
|
const sizes = {
|
||||||
sm: 'px-3 py-1.5 text-xs rounded-md',
|
sm: 'px-3 py-1.5 text-sm',
|
||||||
md: 'px-4 py-2 text-sm rounded-lg',
|
md: 'px-4 py-2 text-sm',
|
||||||
lg: 'px-6 py-3 text-base rounded-lg'
|
lg: 'px-6 py-3 text-base'
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
baseStyles,
|
'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--primary)] focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||||
variants[variant],
|
variants[variant],
|
||||||
sizes[size],
|
sizes[size],
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,39 +2,27 @@ import { InputHTMLAttributes, forwardRef } from 'react';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||||
label?: string;
|
variant?: 'default' | 'error';
|
||||||
error?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Input = forwardRef<HTMLInputElement, InputProps>(
|
const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||||
({ className, label, error, ...props }, ref) => {
|
({ className, variant = 'default', type, ...props }, ref) => {
|
||||||
|
const variants = {
|
||||||
|
default: 'border border-[var(--border)]/50 bg-[var(--input)] text-[var(--foreground)] placeholder:text-[var(--muted-foreground)] focus:border-[var(--primary)]/70 focus:ring-1 focus:ring-[var(--primary)]/20',
|
||||||
|
error: 'border border-[var(--destructive)]/50 bg-[var(--input)] text-[var(--foreground)] placeholder:text-[var(--muted-foreground)] focus:border-[var(--destructive)]/70 focus:ring-1 focus:ring-[var(--destructive)]/20'
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
|
||||||
{label && (
|
|
||||||
<label className="block text-sm font-mono font-medium text-[var(--muted-foreground)] uppercase tracking-wider">
|
|
||||||
{label}
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
<input
|
<input
|
||||||
|
type={type}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full px-3 py-2 bg-[var(--input)] border border-[var(--border)]/50 rounded-lg',
|
'flex h-10 w-full rounded-md px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 transition-colors',
|
||||||
'text-[var(--foreground)] font-mono text-sm placeholder-[var(--muted-foreground)]',
|
variants[variant],
|
||||||
'focus:outline-none focus:ring-2 focus:ring-[var(--primary)]/50 focus:border-[var(--primary)]/50',
|
|
||||||
'hover:border-[var(--border)] transition-all duration-200',
|
|
||||||
'backdrop-blur-sm',
|
|
||||||
error && 'border-[var(--destructive)]/50 focus:ring-[var(--destructive)]/50 focus:border-[var(--destructive)]/50',
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
{error && (
|
|
||||||
<p className="text-xs font-mono text-[var(--destructive)] flex items-center gap-1">
|
|
||||||
<span className="text-[var(--destructive)]">⚠</span>
|
|
||||||
{error}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
49
src/components/ui/StyledCard.tsx
Normal file
49
src/components/ui/StyledCard.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { HTMLAttributes, forwardRef } from 'react';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
interface StyledCardProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
|
variant?: 'default' | 'outline' | 'elevated' | 'bordered' | 'column';
|
||||||
|
color?: 'default' | 'primary' | 'success' | 'destructive' | 'accent' | 'purple' | 'yellow' | 'green' | 'blue' | 'gray';
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledCard = forwardRef<HTMLDivElement, StyledCardProps>(
|
||||||
|
({ className, variant = 'default', color = 'default', ...props }, ref) => {
|
||||||
|
const variants = {
|
||||||
|
default: 'bg-[var(--card)]/50 border border-[var(--border)]/50',
|
||||||
|
outline: 'bg-transparent border border-[var(--border)]',
|
||||||
|
elevated: 'bg-[var(--card)]/80 border border-[var(--border)]/50 shadow-lg shadow-[var(--card)]/20',
|
||||||
|
bordered: 'bg-[var(--card)]/50 border border-[var(--primary)]/30 shadow-[var(--primary)]/10 shadow-lg',
|
||||||
|
column: 'bg-[var(--card-column)] border border-[var(--border)]/50 shadow-lg shadow-[var(--card)]/20'
|
||||||
|
};
|
||||||
|
|
||||||
|
const colors = {
|
||||||
|
default: '',
|
||||||
|
primary: 'bg-[color-mix(in_srgb,var(--primary)_8%,transparent)] border-[color-mix(in_srgb,var(--primary)_25%,var(--border))] text-[var(--primary)]',
|
||||||
|
success: 'bg-[color-mix(in_srgb,var(--success)_8%,transparent)] border-[color-mix(in_srgb,var(--success)_25%,var(--border))] text-[var(--success)]',
|
||||||
|
destructive: 'bg-[color-mix(in_srgb,var(--destructive)_8%,transparent)] border-[color-mix(in_srgb,var(--destructive)_25%,var(--border))] text-[var(--destructive)]',
|
||||||
|
accent: 'bg-[color-mix(in_srgb,var(--accent)_8%,transparent)] border-[color-mix(in_srgb,var(--accent)_25%,var(--border))] text-[var(--accent)]',
|
||||||
|
purple: 'bg-[color-mix(in_srgb,var(--purple)_8%,transparent)] border-[color-mix(in_srgb,var(--purple)_25%,var(--border))] text-[var(--purple)]',
|
||||||
|
yellow: 'bg-[color-mix(in_srgb,var(--yellow)_8%,transparent)] border-[color-mix(in_srgb,var(--yellow)_25%,var(--border))] text-[var(--yellow)]',
|
||||||
|
green: 'bg-[color-mix(in_srgb,var(--green)_8%,transparent)] border-[color-mix(in_srgb,var(--green)_25%,var(--border))] text-[var(--green)]',
|
||||||
|
blue: 'bg-[color-mix(in_srgb,var(--blue)_8%,transparent)] border-[color-mix(in_srgb,var(--blue)_25%,var(--border))] text-[var(--blue)]',
|
||||||
|
gray: 'bg-[color-mix(in_srgb,var(--gray)_8%,transparent)] border-[color-mix(in_srgb,var(--gray)_25%,var(--border))] text-[var(--gray)]'
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'rounded-lg backdrop-blur-sm transition-all duration-200',
|
||||||
|
variants[variant],
|
||||||
|
colors[color],
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
StyledCard.displayName = 'StyledCard';
|
||||||
|
|
||||||
|
export { StyledCard };
|
||||||
11
src/components/ui/index.ts
Normal file
11
src/components/ui/index.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// Composants UI de base
|
||||||
|
export { Button } from './Button';
|
||||||
|
export { Badge } from './Badge';
|
||||||
|
export { Alert, AlertTitle, AlertDescription } from './Alert';
|
||||||
|
export { Input } from './Input';
|
||||||
|
export { StyledCard } from './StyledCard';
|
||||||
|
|
||||||
|
// Composants existants
|
||||||
|
export { Card, CardHeader, CardTitle, CardContent, CardFooter } from './Card';
|
||||||
|
export { Header } from './Header';
|
||||||
|
export { FontSizeToggle } from './FontSizeToggle';
|
||||||
Reference in New Issue
Block a user