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:
@@ -1,7 +1,7 @@
|
|||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import { userPreferencesService } from '@/services/core/user-preferences';
|
import { userPreferencesService } from '@/services/core/user-preferences';
|
||||||
import { KanbanFilters, ViewPreferences, ColumnVisibility, TaskStatus } from '@/lib/types';
|
import { KanbanFilters, ViewPreferences, ColumnVisibility, TaskStatus, Theme } from '@/lib/types';
|
||||||
import { revalidatePath } from 'next/cache';
|
import { revalidatePath } from 'next/cache';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,9 +117,9 @@ export async function toggleObjectivesCollapse(): Promise<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change le thème (light/dark)
|
* Change le thème (light/dark/dracula/monokai/nord)
|
||||||
*/
|
*/
|
||||||
export async function setTheme(theme: 'light' | 'dark'): Promise<{
|
export async function setTheme(theme: Theme): Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
}> {
|
}> {
|
||||||
|
|||||||
@@ -48,6 +48,246 @@
|
|||||||
--gray-light: #374151; /* gray-700 */
|
--gray-light: #374151; /* gray-700 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dracula {
|
||||||
|
/* Dracula theme */
|
||||||
|
--background: #282a36; /* dracula background */
|
||||||
|
--foreground: #f8f8f2; /* dracula foreground */
|
||||||
|
--card: #44475a; /* dracula current line */
|
||||||
|
--card-hover: #6272a4; /* dracula comment */
|
||||||
|
--card-column: #21222c; /* darker background */
|
||||||
|
--border: #6272a4; /* dracula comment */
|
||||||
|
--input: #44475a; /* dracula current line */
|
||||||
|
--primary: #ff79c6; /* dracula pink */
|
||||||
|
--primary-foreground: #282a36; /* dracula background */
|
||||||
|
--muted: #6272a4; /* dracula comment */
|
||||||
|
--muted-foreground: #50fa7b; /* dracula green */
|
||||||
|
--accent: #ffb86c; /* dracula orange */
|
||||||
|
--destructive: #ff5555; /* dracula red */
|
||||||
|
--success: #50fa7b; /* dracula green */
|
||||||
|
--purple: #bd93f9; /* dracula purple */
|
||||||
|
--yellow: #f1fa8c; /* dracula yellow */
|
||||||
|
--green: #50fa7b; /* dracula green */
|
||||||
|
--blue: #8be9fd; /* dracula cyan */
|
||||||
|
--gray: #6272a4; /* dracula comment */
|
||||||
|
--gray-light: #44475a; /* dracula current line */
|
||||||
|
}
|
||||||
|
|
||||||
|
.monokai {
|
||||||
|
/* Monokai theme */
|
||||||
|
--background: #272822; /* monokai background */
|
||||||
|
--foreground: #f8f8f2; /* monokai foreground */
|
||||||
|
--card: #3e3d32; /* monokai selection */
|
||||||
|
--card-hover: #49483e; /* monokai line */
|
||||||
|
--card-column: #1e1f1c; /* darker background */
|
||||||
|
--border: #49483e; /* monokai line */
|
||||||
|
--input: #3e3d32; /* monokai selection */
|
||||||
|
--primary: #f92672; /* monokai pink */
|
||||||
|
--primary-foreground: #272822; /* monokai background */
|
||||||
|
--muted: #75715e; /* monokai comment */
|
||||||
|
--muted-foreground: #a6e22e; /* monokai green */
|
||||||
|
--accent: #fd971f; /* monokai orange */
|
||||||
|
--destructive: #f92672; /* monokai red */
|
||||||
|
--success: #a6e22e; /* monokai green */
|
||||||
|
--purple: #ae81ff; /* monokai purple */
|
||||||
|
--yellow: #e6db74; /* monokai yellow */
|
||||||
|
--green: #a6e22e; /* monokai green */
|
||||||
|
--blue: #66d9ef; /* monokai cyan */
|
||||||
|
--gray: #75715e; /* monokai comment */
|
||||||
|
--gray-light: #3e3d32; /* monokai selection */
|
||||||
|
}
|
||||||
|
|
||||||
|
.nord {
|
||||||
|
/* Nord theme */
|
||||||
|
--background: #2e3440; /* nord0 */
|
||||||
|
--foreground: #d8dee9; /* nord4 */
|
||||||
|
--card: #3b4252; /* nord1 */
|
||||||
|
--card-hover: #434c5e; /* nord2 */
|
||||||
|
--card-column: #242831; /* darker nord0 */
|
||||||
|
--border: #4c566a; /* nord3 */
|
||||||
|
--input: #3b4252; /* nord1 */
|
||||||
|
--primary: #88c0d0; /* nord7 */
|
||||||
|
--primary-foreground: #2e3440; /* nord0 */
|
||||||
|
--muted: #4c566a; /* nord3 */
|
||||||
|
--muted-foreground: #81a1c1; /* nord9 */
|
||||||
|
--accent: #d08770; /* nord12 */
|
||||||
|
--destructive: #bf616a; /* nord11 */
|
||||||
|
--success: #a3be8c; /* nord14 */
|
||||||
|
--purple: #b48ead; /* nord13 */
|
||||||
|
--yellow: #ebcb8b; /* nord15 */
|
||||||
|
--green: #a3be8c; /* nord14 */
|
||||||
|
--blue: #5e81ac; /* nord10 */
|
||||||
|
--gray: #4c566a; /* nord3 */
|
||||||
|
--gray-light: #3b4252; /* nord1 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.gruvbox {
|
||||||
|
/* Gruvbox theme */
|
||||||
|
--background: #282828; /* gruvbox bg0 */
|
||||||
|
--foreground: #ebdbb2; /* gruvbox fg */
|
||||||
|
--card: #3c3836; /* gruvbox bg1 */
|
||||||
|
--card-hover: #504945; /* gruvbox bg2 */
|
||||||
|
--card-column: #1d2021; /* gruvbox bg0_h */
|
||||||
|
--border: #665c54; /* gruvbox bg3 */
|
||||||
|
--input: #3c3836; /* gruvbox bg1 */
|
||||||
|
--primary: #fe8019; /* gruvbox orange */
|
||||||
|
--primary-foreground: #282828; /* gruvbox bg0 */
|
||||||
|
--muted: #665c54; /* gruvbox bg3 */
|
||||||
|
--muted-foreground: #a89984; /* gruvbox gray */
|
||||||
|
--accent: #fabd2f; /* gruvbox yellow */
|
||||||
|
--destructive: #fb4934; /* gruvbox red */
|
||||||
|
--success: #b8bb26; /* gruvbox green */
|
||||||
|
--purple: #d3869b; /* gruvbox purple */
|
||||||
|
--yellow: #fabd2f; /* gruvbox yellow */
|
||||||
|
--green: #b8bb26; /* gruvbox green */
|
||||||
|
--blue: #83a598; /* gruvbox blue */
|
||||||
|
--gray: #a89984; /* gruvbox gray */
|
||||||
|
--gray-light: #3c3836; /* gruvbox bg1 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tokyo_night {
|
||||||
|
/* Tokyo Night theme */
|
||||||
|
--background: #1a1b26; /* tokyo-night bg */
|
||||||
|
--foreground: #a9b1d6; /* tokyo-night fg */
|
||||||
|
--card: #24283b; /* tokyo-night bg_highlight */
|
||||||
|
--card-hover: #2f3349; /* tokyo-night bg_visual */
|
||||||
|
--card-column: #16161e; /* tokyo-night bg_dark */
|
||||||
|
--border: #565f89; /* tokyo-night comment */
|
||||||
|
--input: #24283b; /* tokyo-night bg_highlight */
|
||||||
|
--primary: #7aa2f7; /* tokyo-night blue */
|
||||||
|
--primary-foreground: #1a1b26; /* tokyo-night bg */
|
||||||
|
--muted: #565f89; /* tokyo-night comment */
|
||||||
|
--muted-foreground: #9aa5ce; /* tokyo-night fg_dark */
|
||||||
|
--accent: #ff9e64; /* tokyo-night orange */
|
||||||
|
--destructive: #f7768e; /* tokyo-night red */
|
||||||
|
--success: #9ece6a; /* tokyo-night green */
|
||||||
|
--purple: #bb9af7; /* tokyo-night purple */
|
||||||
|
--yellow: #e0af68; /* tokyo-night yellow */
|
||||||
|
--green: #9ece6a; /* tokyo-night green */
|
||||||
|
--blue: #7aa2f7; /* tokyo-night blue */
|
||||||
|
--gray: #565f89; /* tokyo-night comment */
|
||||||
|
--gray-light: #24283b; /* tokyo-night bg_highlight */
|
||||||
|
}
|
||||||
|
|
||||||
|
.catppuccin {
|
||||||
|
/* Catppuccin Mocha theme */
|
||||||
|
--background: #1e1e2e; /* catppuccin base */
|
||||||
|
--foreground: #cdd6f4; /* catppuccin text */
|
||||||
|
--card: #313244; /* catppuccin surface0 */
|
||||||
|
--card-hover: #45475a; /* catppuccin surface1 */
|
||||||
|
--card-column: #181825; /* catppuccin mantle */
|
||||||
|
--border: #6c7086; /* catppuccin overlay0 */
|
||||||
|
--input: #313244; /* catppuccin surface0 */
|
||||||
|
--primary: #cba6f7; /* catppuccin mauve */
|
||||||
|
--primary-foreground: #1e1e2e; /* catppuccin base */
|
||||||
|
--muted: #6c7086; /* catppuccin overlay0 */
|
||||||
|
--muted-foreground: #a6adc8; /* catppuccin subtext0 */
|
||||||
|
--accent: #fab387; /* catppuccin peach */
|
||||||
|
--destructive: #f38ba8; /* catppuccin red */
|
||||||
|
--success: #a6e3a1; /* catppuccin green */
|
||||||
|
--purple: #cba6f7; /* catppuccin mauve */
|
||||||
|
--yellow: #f9e2af; /* catppuccin yellow */
|
||||||
|
--green: #a6e3a1; /* catppuccin green */
|
||||||
|
--blue: #89b4fa; /* catppuccin blue */
|
||||||
|
--gray: #6c7086; /* catppuccin overlay0 */
|
||||||
|
--gray-light: #313244; /* catppuccin surface0 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.rose_pine {
|
||||||
|
/* Rose Pine theme */
|
||||||
|
--background: #191724; /* rose-pine base */
|
||||||
|
--foreground: #e0def4; /* rose-pine text */
|
||||||
|
--card: #26233a; /* rose-pine surface */
|
||||||
|
--card-hover: #312f44; /* rose-pine overlay */
|
||||||
|
--card-column: #16141f; /* rose-pine base */
|
||||||
|
--border: #6e6a86; /* rose-pine muted */
|
||||||
|
--input: #26233a; /* rose-pine surface */
|
||||||
|
--primary: #c4a7e7; /* rose-pine iris */
|
||||||
|
--primary-foreground: #191724; /* rose-pine base */
|
||||||
|
--muted: #6e6a86; /* rose-pine muted */
|
||||||
|
--muted-foreground: #908caa; /* rose-pine subtle */
|
||||||
|
--accent: #f6c177; /* rose-pine gold */
|
||||||
|
--destructive: #eb6f92; /* rose-pine love */
|
||||||
|
--success: #9ccfd8; /* rose-pine foam */
|
||||||
|
--purple: #c4a7e7; /* rose-pine iris */
|
||||||
|
--yellow: #f6c177; /* rose-pine gold */
|
||||||
|
--green: #9ccfd8; /* rose-pine foam */
|
||||||
|
--blue: #3e8fb0; /* rose-pine pine */
|
||||||
|
--gray: #6e6a86; /* rose-pine muted */
|
||||||
|
--gray-light: #26233a; /* rose-pine surface */
|
||||||
|
}
|
||||||
|
|
||||||
|
.one_dark {
|
||||||
|
/* One Dark theme */
|
||||||
|
--background: #282c34; /* one-dark bg */
|
||||||
|
--foreground: #abb2bf; /* one-dark fg */
|
||||||
|
--card: #3e4451; /* one-dark bg1 */
|
||||||
|
--card-hover: #4f5666; /* one-dark bg2 */
|
||||||
|
--card-column: #21252b; /* one-dark bg0 */
|
||||||
|
--border: #5c6370; /* one-dark bg3 */
|
||||||
|
--input: #3e4451; /* one-dark bg1 */
|
||||||
|
--primary: #61afef; /* one-dark blue */
|
||||||
|
--primary-foreground: #282c34; /* one-dark bg */
|
||||||
|
--muted: #5c6370; /* one-dark bg3 */
|
||||||
|
--muted-foreground: #828997; /* one-dark gray */
|
||||||
|
--accent: #e06c75; /* one-dark red */
|
||||||
|
--destructive: #e06c75; /* one-dark red */
|
||||||
|
--success: #98c379; /* one-dark green */
|
||||||
|
--purple: #c678dd; /* one-dark purple */
|
||||||
|
--yellow: #e5c07b; /* one-dark yellow */
|
||||||
|
--green: #98c379; /* one-dark green */
|
||||||
|
--blue: #61afef; /* one-dark blue */
|
||||||
|
--gray: #5c6370; /* one-dark bg3 */
|
||||||
|
--gray-light: #3e4451; /* one-dark bg1 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.material {
|
||||||
|
/* Material Design Dark theme */
|
||||||
|
--background: #121212; /* material bg */
|
||||||
|
--foreground: #ffffff; /* material on-bg */
|
||||||
|
--card: #1e1e1e; /* material surface */
|
||||||
|
--card-hover: #2c2c2c; /* material surface-variant */
|
||||||
|
--card-column: #0f0f0f; /* material surface-container */
|
||||||
|
--border: #3c3c3c; /* material outline */
|
||||||
|
--input: #1e1e1e; /* material surface */
|
||||||
|
--primary: #bb86fc; /* material primary */
|
||||||
|
--primary-foreground: #121212; /* material bg */
|
||||||
|
--muted: #3c3c3c; /* material outline */
|
||||||
|
--muted-foreground: #b3b3b3; /* material on-surface-variant */
|
||||||
|
--accent: #ffab40; /* material secondary */
|
||||||
|
--destructive: #cf6679; /* material error */
|
||||||
|
--success: #4caf50; /* material success */
|
||||||
|
--purple: #bb86fc; /* material primary */
|
||||||
|
--yellow: #ffab40; /* material secondary */
|
||||||
|
--green: #4caf50; /* material success */
|
||||||
|
--blue: #2196f3; /* material info */
|
||||||
|
--gray: #3c3c3c; /* material outline */
|
||||||
|
--gray-light: #1e1e1e; /* material surface */
|
||||||
|
}
|
||||||
|
|
||||||
|
.solarized {
|
||||||
|
/* Solarized Dark theme */
|
||||||
|
--background: #002b36; /* solarized base03 */
|
||||||
|
--foreground: #93a1a1; /* solarized base1 */
|
||||||
|
--card: #073642; /* solarized base02 */
|
||||||
|
--card-hover: #0a4b5a; /* solarized base01 */
|
||||||
|
--card-column: #001e26; /* solarized base03 darker */
|
||||||
|
--border: #586e75; /* solarized base01 */
|
||||||
|
--input: #073642; /* solarized base02 */
|
||||||
|
--primary: #268bd2; /* solarized blue */
|
||||||
|
--primary-foreground: #002b36; /* solarized base03 */
|
||||||
|
--muted: #586e75; /* solarized base01 */
|
||||||
|
--muted-foreground: #657b83; /* solarized base00 */
|
||||||
|
--accent: #b58900; /* solarized yellow */
|
||||||
|
--destructive: #dc322f; /* solarized red */
|
||||||
|
--success: #859900; /* solarized green */
|
||||||
|
--purple: #6c71c4; /* solarized violet */
|
||||||
|
--yellow: #b58900; /* solarized yellow */
|
||||||
|
--green: #859900; /* solarized green */
|
||||||
|
--blue: #268bd2; /* solarized blue */
|
||||||
|
--gray: #586e75; /* solarized base01 */
|
||||||
|
--gray-light: #073642; /* solarized base02 */
|
||||||
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
--color-background: var(--background);
|
--color-background: var(--background);
|
||||||
--color-foreground: var(--foreground);
|
--color-foreground: var(--foreground);
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ export default async function RootLayout({
|
|||||||
<body
|
<body
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
>
|
>
|
||||||
<ThemeProvider initialTheme={initialPreferences.viewPreferences.theme}>
|
<ThemeProvider
|
||||||
|
initialTheme={initialPreferences.viewPreferences.theme}
|
||||||
|
userPreferredTheme={initialPreferences.viewPreferences.theme === 'light' ? 'dark' : initialPreferences.viewPreferences.theme}
|
||||||
|
>
|
||||||
<JiraConfigProvider config={initialPreferences.jiraConfig}>
|
<JiraConfigProvider config={initialPreferences.jiraConfig}>
|
||||||
<UserPreferencesProvider initialPreferences={initialPreferences}>
|
<UserPreferencesProvider initialPreferences={initialPreferences}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
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 { Header } from '@/components/ui/Header';
|
||||||
import { Card, CardContent } from '@/components/ui/Card';
|
import { Card, CardContent } from '@/components/ui/Card';
|
||||||
import { TagsManagement } from './tags/TagsManagement';
|
import { TagsManagement } from './tags/TagsManagement';
|
||||||
|
import { ThemeSelector } from '@/components/ThemeSelector';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
interface GeneralSettingsPageClientProps {
|
interface GeneralSettingsPageClientProps {
|
||||||
@@ -46,7 +47,12 @@ export function GeneralSettingsPageClient({ initialTags }: GeneralSettingsPageCl
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</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 */}
|
{/* Gestion des tags */}
|
||||||
<TagsManagement
|
<TagsManagement
|
||||||
tags={tags}
|
tags={tags}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ interface HeaderProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Header({ title = "TowerControl", subtitle = "Task Management", syncing = false }: 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 { isConfigured: isJiraConfigured, config: jiraConfig } = useJiraConfig();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||||
@@ -87,13 +87,15 @@ export function Header({ title = "TowerControl", subtitle = "Task Management", s
|
|||||||
<button
|
<button
|
||||||
onClick={toggleTheme}
|
onClick={toggleTheme}
|
||||||
className="text-[var(--muted-foreground)] hover:text-[var(--primary)] transition-colors p-2 rounded-md hover:bg-[var(--card-hover)]"
|
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">
|
<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" />
|
<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>
|
</svg>
|
||||||
) : (
|
) : (
|
||||||
|
// Lune pour tous les thèmes sombres
|
||||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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" />
|
<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>
|
</svg>
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
||||||
import { updateViewPreferences } from '@/actions/preferences';
|
import { updateViewPreferences } from '@/actions/preferences';
|
||||||
|
import { Theme } from '@/lib/types';
|
||||||
type Theme = 'light' | 'dark';
|
|
||||||
|
|
||||||
interface ThemeContextType {
|
interface ThemeContextType {
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
toggleTheme: () => void;
|
toggleTheme: () => void;
|
||||||
setTheme: (theme: Theme) => void;
|
setTheme: (theme: Theme) => void;
|
||||||
|
userPreferredTheme: Theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
||||||
@@ -16,10 +16,12 @@ const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
|||||||
interface ThemeProviderProps {
|
interface ThemeProviderProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
initialTheme?: Theme;
|
initialTheme?: Theme;
|
||||||
|
userPreferredTheme?: Theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ThemeProvider({ children, initialTheme = 'dark' }: ThemeProviderProps) {
|
export function ThemeProvider({ children, initialTheme = 'dark', userPreferredTheme: initialUserPreferredTheme = 'dark' }: ThemeProviderProps) {
|
||||||
const [theme, setThemeState] = useState<Theme>(initialTheme);
|
const [theme, setThemeState] = useState<Theme>(initialTheme);
|
||||||
|
const [userPreferredTheme, setUserPreferredTheme] = useState<Theme>(initialUserPreferredTheme);
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
// Hydration safe initialization
|
// Hydration safe initialization
|
||||||
@@ -30,12 +32,16 @@ export function ThemeProvider({ children, initialTheme = 'dark' }: ThemeProvider
|
|||||||
// Apply theme class to document
|
// Apply theme class to document
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
document.documentElement.className = theme;
|
// Remove all existing theme classes
|
||||||
|
document.documentElement.classList.remove('light', 'dark', 'dracula', 'monokai', 'nord', 'gruvbox', 'tokyo_night', 'catppuccin', 'rose_pine', 'one_dark', 'material', 'solarized');
|
||||||
|
// Add the current theme class
|
||||||
|
document.documentElement.classList.add(theme);
|
||||||
}
|
}
|
||||||
}, [theme, mounted]);
|
}, [theme, mounted]);
|
||||||
|
|
||||||
const toggleTheme = async () => {
|
const toggleTheme = async () => {
|
||||||
const newTheme = theme === 'dark' ? 'light' : 'dark';
|
// Toggle between light and the user's chosen dark theme
|
||||||
|
const newTheme = theme === 'light' ? userPreferredTheme : 'light';
|
||||||
setThemeState(newTheme);
|
setThemeState(newTheme);
|
||||||
|
|
||||||
// Sauvegarder en base de façon asynchrone via server action
|
// Sauvegarder en base de façon asynchrone via server action
|
||||||
@@ -54,6 +60,11 @@ export function ThemeProvider({ children, initialTheme = 'dark' }: ThemeProvider
|
|||||||
const setTheme = async (newTheme: Theme) => {
|
const setTheme = async (newTheme: Theme) => {
|
||||||
setThemeState(newTheme);
|
setThemeState(newTheme);
|
||||||
|
|
||||||
|
// Si ce n'est pas le thème light, c'est le thème préféré de l'utilisateur
|
||||||
|
if (newTheme !== 'light') {
|
||||||
|
setUserPreferredTheme(newTheme);
|
||||||
|
}
|
||||||
|
|
||||||
// Sauvegarder en base de façon asynchrone via server action
|
// Sauvegarder en base de façon asynchrone via server action
|
||||||
try {
|
try {
|
||||||
const result = await updateViewPreferences({
|
const result = await updateViewPreferences({
|
||||||
@@ -68,7 +79,7 @@ export function ThemeProvider({ children, initialTheme = 'dark' }: ThemeProvider
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeContext.Provider value={{ theme, toggleTheme, setTheme }}>
|
<ThemeContext.Provider value={{ theme, toggleTheme, setTheme, userPreferredTheme }}>
|
||||||
<div className={mounted ? theme : initialTheme}>
|
<div className={mounted ? theme : initialTheme}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ export type TaskStatus =
|
|||||||
export type TaskPriority = 'low' | 'medium' | 'high' | 'urgent';
|
export type TaskPriority = 'low' | 'medium' | 'high' | 'urgent';
|
||||||
export type TaskSource = 'reminders' | 'jira' | 'tfs' | 'manual';
|
export type TaskSource = 'reminders' | 'jira' | 'tfs' | 'manual';
|
||||||
|
|
||||||
|
// Types de thèmes partagés
|
||||||
|
export type Theme = 'light' | 'dark' | 'dracula' | 'monokai' | 'nord' | 'gruvbox' | 'tokyo_night' | 'catppuccin' | 'rose_pine' | 'one_dark' | 'material' | 'solarized';
|
||||||
|
|
||||||
// Interface centralisée pour les statistiques
|
// Interface centralisée pour les statistiques
|
||||||
export interface TaskStats {
|
export interface TaskStats {
|
||||||
total: number;
|
total: number;
|
||||||
@@ -91,14 +94,13 @@ export interface ViewPreferences {
|
|||||||
showObjectives: boolean;
|
showObjectives: boolean;
|
||||||
showFilters: boolean;
|
showFilters: boolean;
|
||||||
objectivesCollapsed: boolean;
|
objectivesCollapsed: boolean;
|
||||||
theme: 'light' | 'dark';
|
theme: Theme;
|
||||||
fontSize: 'small' | 'medium' | 'large';
|
fontSize: 'small' | 'medium' | 'large';
|
||||||
[key: string]:
|
[key: string]:
|
||||||
| boolean
|
| boolean
|
||||||
| 'tags'
|
| 'tags'
|
||||||
| 'priority'
|
| 'priority'
|
||||||
| 'light'
|
| Theme
|
||||||
| 'dark'
|
|
||||||
| 'small'
|
| 'small'
|
||||||
| 'medium'
|
| 'medium'
|
||||||
| 'large'
|
| 'large'
|
||||||
|
|||||||
Reference in New Issue
Block a user