feat: enhance theme management and customization options
- Added support for multiple themes (dracula, monokai, nord, gruvbox, tokyo_night, catppuccin, rose_pine, one_dark, material, solarized) in the application. - Updated `setTheme` function to accept the new `Theme` type, allowing for more flexible theme selection. - Introduced `ThemeSelector` component in GeneralSettingsPage for user-friendly theme selection. - Modified `ThemeProvider` to handle user preferred themes and improved theme toggling logic. - Updated CSS variables in `globals.css` to support new themes, enhancing visual consistency across the app.
This commit is contained in:
188
src/components/ThemeSelector.tsx
Normal file
188
src/components/ThemeSelector.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
'use client';
|
||||
|
||||
import { useTheme } from '@/contexts/ThemeContext';
|
||||
import { Theme } from '@/lib/types';
|
||||
|
||||
const themes: { id: Theme; name: string; description: string }[] = [
|
||||
{ id: 'dark', name: 'Dark', description: 'Thème sombre par défaut' },
|
||||
{ id: 'dracula', name: 'Dracula', description: 'Thème Dracula coloré' },
|
||||
{ id: 'monokai', name: 'Monokai', description: 'Thème Monokai vibrant' },
|
||||
{ id: 'nord', name: 'Nord', description: 'Thème Nord minimaliste' },
|
||||
{ id: 'gruvbox', name: 'Gruvbox', description: 'Thème Gruvbox chaleureux' },
|
||||
{ id: 'tokyo_night', name: 'Tokyo Night', description: 'Thème Tokyo Night moderne' },
|
||||
{ id: 'catppuccin', name: 'Catppuccin', description: 'Thème Catppuccin pastel' },
|
||||
{ id: 'rose_pine', name: 'Rose Pine', description: 'Thème Rose Pine élégant' },
|
||||
{ id: 'one_dark', name: 'One Dark', description: 'Thème One Dark populaire' },
|
||||
{ id: 'material', name: 'Material', description: 'Thème Material Design' },
|
||||
{ id: 'solarized', name: 'Solarized', description: 'Thème Solarized scientifique' },
|
||||
];
|
||||
|
||||
export function ThemeSelector() {
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-mono font-semibold text-[var(--foreground)]">Thème de l'interface</h3>
|
||||
<p className="text-sm text-[var(--muted-foreground)] mt-1">
|
||||
Choisissez l'apparence de TowerControl
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-sm text-[var(--muted-foreground)]">
|
||||
Actuel: <span className="font-medium text-[var(--primary)] capitalize">{theme}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
{themes.map((themeOption) => (
|
||||
<button
|
||||
key={themeOption.id}
|
||||
onClick={() => setTheme(themeOption.id)}
|
||||
className={`
|
||||
p-4 rounded-lg border text-left transition-all duration-200 group
|
||||
${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">
|
||||
{/* Aperçu du thème */}
|
||||
<div className="flex-shrink-0">
|
||||
<div
|
||||
className="w-16 h-12 rounded-lg border-2 overflow-hidden"
|
||||
style={{
|
||||
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 className="flex-1 min-w-0">
|
||||
<div className="font-medium text-[var(--foreground)] mb-1">
|
||||
{themeOption.name}
|
||||
</div>
|
||||
<div className="text-xs text-[var(--muted-foreground)] leading-relaxed">
|
||||
{themeOption.description}
|
||||
</div>
|
||||
{theme === themeOption.id && (
|
||||
<div className="mt-2 text-xs text-[var(--primary)] font-medium">
|
||||
✓ Sélectionné
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { useTags } from '@/hooks/useTags';
|
||||
import { Header } from '@/components/ui/Header';
|
||||
import { Card, CardContent } from '@/components/ui/Card';
|
||||
import { TagsManagement } from './tags/TagsManagement';
|
||||
import { ThemeSelector } from '@/components/ThemeSelector';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface GeneralSettingsPageClientProps {
|
||||
@@ -46,7 +47,12 @@ export function GeneralSettingsPageClient({ initialTags }: GeneralSettingsPageCl
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
{/* Sélection de thème */}
|
||||
<div className="bg-[var(--card)]/30 border border-[var(--border)]/50 rounded-lg p-6 backdrop-blur-sm">
|
||||
<ThemeSelector />
|
||||
</div>
|
||||
|
||||
{/* Gestion des tags */}
|
||||
<TagsManagement
|
||||
tags={tags}
|
||||
|
||||
@@ -13,7 +13,7 @@ interface HeaderProps {
|
||||
}
|
||||
|
||||
export function Header({ title = "TowerControl", subtitle = "Task Management", syncing = false }: HeaderProps) {
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
const { theme, toggleTheme, userPreferredTheme } = useTheme();
|
||||
const { isConfigured: isJiraConfigured, config: jiraConfig } = useJiraConfig();
|
||||
const pathname = usePathname();
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
@@ -87,13 +87,15 @@ export function Header({ title = "TowerControl", subtitle = "Task Management", s
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className="text-[var(--muted-foreground)] hover:text-[var(--primary)] transition-colors p-2 rounded-md hover:bg-[var(--card-hover)]"
|
||||
title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} theme`}
|
||||
title={theme === 'light' ? `Switch to ${userPreferredTheme} theme` : 'Switch to light theme'}
|
||||
>
|
||||
{theme === 'dark' ? (
|
||||
{theme === 'light' ? (
|
||||
// Soleil pour le thème clair
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
) : (
|
||||
// Lune pour tous les thèmes sombres
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
||||
</svg>
|
||||
|
||||
Reference in New Issue
Block a user