feat: implement theme system and UI updates

- Added theme context and provider for light/dark mode support.
- Integrated theme toggle button in the Header component.
- Updated UI components to utilize CSS variables for consistent theming.
- Enhanced Kanban components and forms with new theme styles for better visual coherence.
- Adjusted global styles to define color variables for both themes, improving maintainability.
This commit is contained in:
Julien Froidefond
2025-09-15 11:49:54 +02:00
parent dce11e0569
commit 07cd3bde3b
23 changed files with 298 additions and 160 deletions

View File

@@ -15,7 +15,7 @@ function HomePageContent() {
const { stats, syncing } = useTasksContext();
return (
<div className="min-h-screen bg-slate-950">
<div className="min-h-screen bg-[var(--background)]">
<Header
title="TowerControl"
subtitle="Gestionnaire de tâches moderne"

View File

@@ -89,7 +89,7 @@ export function CreateTaskForm({ isOpen, onClose, onSubmit, loading = false }: C
{/* Description */}
<div className="space-y-2">
<label className="block text-sm font-mono font-medium text-slate-300 uppercase tracking-wider">
<label className="block text-sm font-mono font-medium text-[var(--muted-foreground)] uppercase tracking-wider">
Description
</label>
<textarea
@@ -98,11 +98,11 @@ export function CreateTaskForm({ isOpen, onClose, onSubmit, loading = false }: C
placeholder="Description détaillée..."
rows={4}
disabled={loading}
className="w-full px-3 py-2 bg-slate-800/50 border border-slate-700/50 rounded-lg text-slate-100 font-mono text-sm placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500/50 hover:border-slate-600/50 transition-all duration-200 backdrop-blur-sm resize-none"
className="w-full px-3 py-2 bg-[var(--input)] border border-[var(--border)]/50 rounded-lg text-[var(--foreground)] font-mono text-sm placeholder-[var(--muted-foreground)] 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 resize-none"
/>
{errors.description && (
<p className="text-xs font-mono text-red-400 flex items-center gap-1">
<span className="text-red-500"></span>
<p className="text-xs font-mono text-[var(--destructive)] flex items-center gap-1">
<span className="text-[var(--destructive)]"></span>
{errors.description}
</p>
)}
@@ -111,14 +111,14 @@ export function CreateTaskForm({ isOpen, onClose, onSubmit, loading = false }: C
{/* Priorité et Statut */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<label className="block text-sm font-mono font-medium text-slate-300 uppercase tracking-wider">
<label className="block text-sm font-mono font-medium text-[var(--muted-foreground)] uppercase tracking-wider">
Priorité
</label>
<select
value={formData.priority}
onChange={(e) => setFormData((prev: CreateTaskData) => ({ ...prev, priority: e.target.value as TaskPriority }))}
disabled={loading}
className="w-full px-3 py-2 bg-slate-800/50 border border-slate-700/50 rounded-lg text-slate-100 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500/50 hover:border-slate-600/50 transition-all duration-200 backdrop-blur-sm"
className="w-full px-3 py-2 bg-[var(--input)] border border-[var(--border)]/50 rounded-lg text-[var(--foreground)] font-mono text-sm 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"
>
{getAllPriorities().map(priorityConfig => (
<option key={priorityConfig.key} value={priorityConfig.key}>
@@ -129,14 +129,14 @@ export function CreateTaskForm({ isOpen, onClose, onSubmit, loading = false }: C
</div>
<div className="space-y-2">
<label className="block text-sm font-mono font-medium text-slate-300 uppercase tracking-wider">
<label className="block text-sm font-mono font-medium text-[var(--muted-foreground)] uppercase tracking-wider">
Statut
</label>
<select
value={formData.status}
onChange={(e) => setFormData((prev: CreateTaskData) => ({ ...prev, status: e.target.value as TaskStatus }))}
disabled={loading}
className="w-full px-3 py-2 bg-slate-800/50 border border-slate-700/50 rounded-lg text-slate-100 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500/50 hover:border-slate-600/50 transition-all duration-200 backdrop-blur-sm"
className="w-full px-3 py-2 bg-[var(--input)] border border-[var(--border)]/50 rounded-lg text-[var(--foreground)] font-mono text-sm 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"
>
{getAllStatuses().map(statusConfig => (
<option key={statusConfig.key} value={statusConfig.key}>
@@ -161,7 +161,7 @@ export function CreateTaskForm({ isOpen, onClose, onSubmit, loading = false }: C
{/* Tags */}
<div className="space-y-3">
<label className="block text-sm font-mono font-medium text-slate-300 uppercase tracking-wider">
<label className="block text-sm font-mono font-medium text-[var(--muted-foreground)] uppercase tracking-wider">
Tags
</label>
@@ -174,7 +174,7 @@ export function CreateTaskForm({ isOpen, onClose, onSubmit, loading = false }: C
</div>
{/* Actions */}
<div className="flex justify-end gap-3 pt-4 border-t border-slate-700/50">
<div className="flex justify-end gap-3 pt-4 border-t border-[var(--border)]/50">
<Button
type="button"
variant="ghost"

View File

@@ -101,7 +101,7 @@ export function EditTaskForm({ isOpen, onClose, onSubmit, task, loading = false
{/* Description */}
<div className="space-y-2">
<label className="block text-sm font-mono font-medium text-slate-300 uppercase tracking-wider">
<label className="block text-sm font-mono font-medium text-[var(--muted-foreground)] uppercase tracking-wider">
Description
</label>
<textarea
@@ -110,7 +110,7 @@ export function EditTaskForm({ isOpen, onClose, onSubmit, task, loading = false
placeholder="Description détaillée..."
rows={4}
disabled={loading}
className="w-full px-3 py-2 bg-slate-800/50 border border-slate-700/50 rounded-lg text-slate-100 font-mono text-sm placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500/50 hover:border-slate-600/50 transition-all duration-200 backdrop-blur-sm resize-none"
className="w-full px-3 py-2 bg-[var(--input)] border border-[var(--border)]/50 rounded-lg text-[var(--foreground)] font-mono text-sm placeholder-[var(--muted-foreground)] 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 resize-none"
/>
{errors.description && (
<p className="text-xs font-mono text-red-400 flex items-center gap-1">
@@ -123,14 +123,14 @@ export function EditTaskForm({ isOpen, onClose, onSubmit, task, loading = false
{/* Priorité et Statut */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<label className="block text-sm font-mono font-medium text-slate-300 uppercase tracking-wider">
<label className="block text-sm font-mono font-medium text-[var(--muted-foreground)] uppercase tracking-wider">
Priorité
</label>
<select
value={formData.priority}
onChange={(e) => setFormData(prev => ({ ...prev, priority: e.target.value as TaskPriority }))}
disabled={loading}
className="w-full px-3 py-2 bg-slate-800/50 border border-slate-700/50 rounded-lg text-slate-100 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500/50 hover:border-slate-600/50 transition-all duration-200 backdrop-blur-sm"
className="w-full px-3 py-2 bg-[var(--input)] border border-[var(--border)]/50 rounded-lg text-[var(--foreground)] font-mono text-sm 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"
>
{getAllPriorities().map(priorityConfig => (
<option key={priorityConfig.key} value={priorityConfig.key}>
@@ -141,14 +141,14 @@ export function EditTaskForm({ isOpen, onClose, onSubmit, task, loading = false
</div>
<div className="space-y-2">
<label className="block text-sm font-mono font-medium text-slate-300 uppercase tracking-wider">
<label className="block text-sm font-mono font-medium text-[var(--muted-foreground)] uppercase tracking-wider">
Statut
</label>
<select
value={formData.status}
onChange={(e) => setFormData(prev => ({ ...prev, status: e.target.value as TaskStatus }))}
disabled={loading}
className="w-full px-3 py-2 bg-slate-800/50 border border-slate-700/50 rounded-lg text-slate-100 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500/50 hover:border-slate-600/50 transition-all duration-200 backdrop-blur-sm"
className="w-full px-3 py-2 bg-[var(--input)] border border-[var(--border)]/50 rounded-lg text-[var(--foreground)] font-mono text-sm 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"
>
{getAllStatuses().map(statusConfig => (
<option key={statusConfig.key} value={statusConfig.key}>
@@ -173,7 +173,7 @@ export function EditTaskForm({ isOpen, onClose, onSubmit, task, loading = false
{/* Tags */}
<div className="space-y-3">
<label className="block text-sm font-mono font-medium text-slate-300 uppercase tracking-wider">
<label className="block text-sm font-mono font-medium text-[var(--muted-foreground)] uppercase tracking-wider">
Tags
</label>
@@ -186,7 +186,7 @@ export function EditTaskForm({ isOpen, onClose, onSubmit, task, loading = false
</div>
{/* Actions */}
<div className="flex justify-end gap-3 pt-4 border-t border-slate-700/50">
<div className="flex justify-end gap-3 pt-4 border-t border-[var(--border)]/50">
<Button
type="button"
variant="ghost"

View File

@@ -110,12 +110,12 @@ export function KanbanBoard({ tasks, onCreateTask, onDeleteTask, onEditTask, onU
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
<div className="h-full flex flex-col bg-slate-950">
<div className="h-full flex flex-col bg-[var(--background)]">
{/* Header avec bouton nouvelle tâche */}
<div className="flex justify-between items-center p-6 pb-0">
<div className="flex items-center gap-3">
<div className="w-2 h-2 bg-cyan-400 rounded-full animate-pulse"></div>
<h2 className="text-lg font-mono font-bold text-slate-100 uppercase tracking-wider">
<div className="w-2 h-2 bg-[var(--primary)] rounded-full animate-pulse"></div>
<h2 className="text-lg font-mono font-bold text-[var(--foreground)] uppercase tracking-wider">
Kanban Board
</h2>
</div>

View File

@@ -35,9 +35,9 @@ export function KanbanColumn({ id, tasks, onCreateTask, onDeleteTask, onEditTask
<div className="flex-shrink-0 w-80 md:w-1/4 md:flex-1 h-full">
<Card
ref={setNodeRef}
variant="elevated"
variant="column"
className={`h-full flex flex-col transition-all duration-200 ${
isOver ? 'ring-2 ring-cyan-400/50 bg-slate-800/60' : ''
isOver ? 'ring-2 ring-[var(--primary)]/50 bg-[var(--card-hover)]' : ''
}`}
>
<CardHeader className="pb-3">
@@ -55,7 +55,7 @@ export function KanbanColumn({ id, tasks, onCreateTask, onDeleteTask, onEditTask
{onCreateTask && (
<button
onClick={() => setShowQuickAdd(true)}
className={`w-5 h-5 rounded-full border border-dashed ${style.border} ${style.accent} hover:bg-slate-800/50 transition-colors flex items-center justify-center text-xs font-mono`}
className={`w-5 h-5 rounded-full border border-dashed ${style.border} ${style.accent} hover:bg-[var(--card-hover)] transition-colors flex items-center justify-center text-xs font-mono`}
title="Ajouter une tâche rapide"
>
+
@@ -81,10 +81,10 @@ export function KanbanColumn({ id, tasks, onCreateTask, onDeleteTask, onEditTask
{tasks.length === 0 && !showQuickAdd ? (
<div className="text-center py-20">
<div className={`w-16 h-16 mx-auto mb-4 rounded-full bg-slate-800 border-2 border-dashed ${style.border} flex items-center justify-center`}>
<div className={`w-16 h-16 mx-auto mb-4 rounded-full bg-[var(--card)] border-2 border-dashed ${style.border} flex items-center justify-center`}>
<span className={`text-2xl ${style.accent} opacity-50`}>{statusConfig.icon}</span>
</div>
<p className="text-xs font-mono text-slate-500 uppercase tracking-wide">NO DATA</p>
<p className="text-xs font-mono text-[var(--muted-foreground)] uppercase tracking-wide">NO DATA</p>
<div className="mt-2 flex justify-center">
<div className={`w-8 h-0.5 ${style.accent.replace('text-', 'bg-')} opacity-30`}></div>
</div>

View File

@@ -30,7 +30,7 @@ export function ColumnVisibilityToggle({
return (
<div className={`flex items-center gap-2 ${className}`}>
<span className="text-sm font-mono font-medium text-slate-400">
<span className="text-sm font-mono font-medium text-[var(--muted-foreground)]">
Colonnes :
</span>
{statuses.map(statusConfig => (
@@ -39,8 +39,8 @@ export function ColumnVisibilityToggle({
onClick={() => onToggleStatus(statusConfig.key)}
className={`px-3 py-1 rounded-lg text-xs font-mono font-medium transition-colors ${
hiddenStatuses.has(statusConfig.key)
? 'bg-slate-700/50 text-slate-500 hover:bg-slate-700'
: 'bg-blue-600/20 text-blue-300 border border-blue-500/30 hover:bg-blue-600/30'
? 'bg-[var(--muted)]/20 text-[var(--muted)] hover:bg-[var(--muted)]/30'
: 'bg-[var(--primary)]/20 text-[var(--primary)] border border-[var(--primary)]/30 hover:bg-[var(--primary)]/30'
}`}
title={hiddenStatuses.has(statusConfig.key) ? `Afficher ${statusConfig.label}` : `Masquer ${statusConfig.label}`}
>

View File

@@ -182,7 +182,7 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
}, [availableTags, tagCounts]);
return (
<div className="bg-slate-900/50 border-b border-slate-700/50 backdrop-blur-sm">
<div className="bg-[var(--card)]/50 border-b border-[var(--border)]/50 backdrop-blur-sm">
<div className="container mx-auto px-6 py-4">
{/* Header avec recherche et bouton expand */}
<div className="flex items-center gap-4">
@@ -192,7 +192,7 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
value={filters.search || ''}
onChange={(e) => handleSearchChange(e.target.value)}
placeholder="Rechercher des tâches..."
className="bg-slate-800/50 border-slate-600"
className="bg-[var(--card)] border-[var(--border)]"
/>
</div>
@@ -309,7 +309,7 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
</svg>
Filtres
{activeFiltersCount > 0 && (
<span className="bg-cyan-500 text-slate-900 text-xs px-2 py-0.5 rounded-full font-medium">
<span className="bg-[var(--primary)] text-[var(--primary-foreground)] text-xs px-2 py-0.5 rounded-full font-medium">
{activeFiltersCount}
</span>
)}
@@ -327,7 +327,7 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
<Button
variant="ghost"
onClick={handleClearFilters}
className="text-slate-400 hover:text-red-400"
className="text-[var(--muted-foreground)] hover:text-[var(--destructive)]"
>
Effacer
</Button>
@@ -336,12 +336,12 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
{/* Filtres étendus */}
{isExpanded && (
<div className="mt-4 border-t border-slate-700/50 pt-4">
<div className="mt-4 border-t border-[var(--border)]/50 pt-4">
{/* Grille responsive pour les filtres */}
<div className="grid grid-cols-1 lg:grid-cols-[auto_1fr] gap-6 lg:gap-8">
{/* Filtres par priorité */}
<div className="space-y-3">
<label className="block text-xs font-mono font-medium text-slate-300 uppercase tracking-wider">
<label className="block text-xs font-mono font-medium text-[var(--muted-foreground)] uppercase tracking-wider">
Priorités
</label>
<div className="grid grid-cols-2 gap-2">
@@ -352,7 +352,7 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
className={`flex items-center gap-2 px-3 py-2 rounded-lg border transition-all text-xs font-medium whitespace-nowrap ${
filters.priorities?.includes(priority.value)
? 'border-cyan-400 bg-cyan-400/10 text-cyan-400'
: 'border-slate-600 bg-slate-800/50 text-slate-400 hover:border-slate-500'
: 'border-[var(--border)] bg-[var(--card)] text-[var(--muted-foreground)] hover:border-[var(--border)]'
}`}
>
<div
@@ -368,7 +368,7 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
{/* Filtres par tags */}
{availableTags.length > 0 && (
<div className="space-y-3">
<label className="block text-xs font-mono font-medium text-slate-300 uppercase tracking-wider">
<label className="block text-xs font-mono font-medium text-[var(--muted-foreground)] uppercase tracking-wider">
Tags
</label>
<div className="flex flex-wrap gap-2 max-h-32 overflow-y-auto">
@@ -379,7 +379,7 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
className={`flex items-center gap-2 px-3 py-2 rounded-lg border transition-all text-xs font-medium ${
filters.tags?.includes(tag.name)
? 'border-cyan-400 bg-cyan-400/10 text-cyan-400'
: 'border-slate-600 bg-slate-800/50 text-slate-400 hover:border-slate-500'
: 'border-[var(--border)] bg-[var(--card)] text-[var(--muted-foreground)] hover:border-[var(--border)]'
}`}
>
<div
@@ -395,7 +395,7 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
</div>
{/* Visibilité des colonnes */}
<div className="col-span-full border-t border-slate-700/50 pt-6 mt-4">
<div className="col-span-full border-t border-[var(--border)]/50 pt-6 mt-4">
<ColumnVisibilityToggle
hiddenStatuses={hiddenStatuses}
onToggleStatus={toggleStatusVisibility}
@@ -406,23 +406,23 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
{/* Résumé des filtres actifs */}
{activeFiltersCount > 0 && (
<div className="bg-slate-800/30 rounded-lg p-3 border border-slate-700/50">
<div className="text-xs text-slate-400 font-mono uppercase tracking-wider mb-2">
<div className="bg-[var(--card)]/30 rounded-lg p-3 border border-[var(--border)]/50">
<div className="text-xs text-[var(--muted-foreground)] font-mono uppercase tracking-wider mb-2">
Filtres actifs
</div>
<div className="space-y-1 text-xs">
{filters.search && (
<div className="text-slate-300">
<div className="text-[var(--muted-foreground)]">
Recherche: <span className="text-cyan-400">&ldquo;{filters.search}&rdquo;</span>
</div>
)}
{filters.priorities?.length && (
<div className="text-slate-300">
<div className="text-[var(--muted-foreground)]">
Priorités: <span className="text-cyan-400">{filters.priorities.join(', ')}</span>
</div>
)}
{filters.tags?.length && (
<div className="text-slate-300">
<div className="text-[var(--muted-foreground)]">
Tags: <span className="text-cyan-400">{filters.tags.join(', ')}</span>
</div>
)}
@@ -437,7 +437,7 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
{isSortExpanded && typeof window !== 'undefined' && createPortal(
<div
ref={sortDropdownRef}
className="fixed w-80 bg-slate-800 border border-slate-700 rounded-lg shadow-xl z-[9999] max-h-64 overflow-y-auto"
className="fixed w-80 bg-[var(--card)] border border-[var(--border)] rounded-lg shadow-xl z-[9999] max-h-64 overflow-y-auto"
style={{
top: dropdownPosition.top,
left: dropdownPosition.left
@@ -450,10 +450,10 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
handleSortChange(option.key);
setIsSortExpanded(false);
}}
className={`w-full px-3 py-2 text-left text-xs font-mono hover:bg-slate-700 transition-colors flex items-center gap-2 ${
className={`w-full px-3 py-2 text-left text-xs font-mono hover:bg-[var(--card-hover)] transition-colors flex items-center gap-2 ${
(filters.sortBy || 'priority-desc') === option.key
? 'bg-cyan-600/20 text-cyan-400 border-l-2 border-cyan-400'
: 'text-slate-300'
: 'text-[var(--muted-foreground)]'
}`}
>
<span className="text-base">{option.icon}</span>
@@ -473,7 +473,7 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
{isSwimlaneModeExpanded && typeof window !== 'undefined' && createPortal(
<div
ref={swimlaneModeDropdownRef}
className="fixed bg-slate-800 border border-slate-700 rounded-lg shadow-xl z-[9999] min-w-[140px]"
className="fixed bg-[var(--card)] border border-[var(--border)] rounded-lg shadow-xl z-[9999] min-w-[140px]"
style={{
top: dropdownPosition.top,
left: dropdownPosition.left,
@@ -481,16 +481,16 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
>
<button
onClick={() => handleSwimlaneModeChange('tags')}
className={`w-full px-3 py-2 text-left text-xs hover:bg-slate-700 transition-colors flex items-center gap-2 first:rounded-t-lg ${
(!filters.swimlanesMode || filters.swimlanesMode === 'tags') ? 'bg-slate-700 text-cyan-400' : 'text-slate-300'
className={`w-full px-3 py-2 text-left text-xs hover:bg-[var(--card-hover)] transition-colors flex items-center gap-2 first:rounded-t-lg ${
(!filters.swimlanesMode || filters.swimlanesMode === 'tags') ? 'bg-[var(--card-hover)] text-[var(--primary)]' : 'text-[var(--muted-foreground)]'
}`}
>
🏷 Par tags
</button>
<button
onClick={() => handleSwimlaneModeChange('priority')}
className={`w-full px-3 py-2 text-left text-xs hover:bg-slate-700 transition-colors flex items-center gap-2 last:rounded-b-lg ${
filters.swimlanesMode === 'priority' ? 'bg-slate-700 text-cyan-400' : 'text-slate-300'
className={`w-full px-3 py-2 text-left text-xs hover:bg-[var(--card-hover)] transition-colors flex items-center gap-2 last:rounded-b-lg ${
filters.swimlanesMode === 'priority' ? 'bg-[var(--card-hover)] text-[var(--primary)]' : 'text-[var(--muted-foreground)]'
}`}
>
🎯 Par priorité

View File

@@ -32,7 +32,7 @@ export function ObjectivesBoard({
return (
<div className="bg-gradient-to-r from-purple-900/20 to-blue-900/20 border-b border-purple-500/30">
<div className="container mx-auto px-6 py-4">
<Card className="bg-slate-800/50 border-purple-500/30 shadow-purple-500/10 shadow-lg">
<Card className="bg-[var(--card)] border-[var(--accent)]/30 shadow-[var(--accent)]/10 shadow-lg">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<button
@@ -105,13 +105,13 @@ export function ObjectivesBoard({
En cours / À faire
</h3>
<div className="flex-1"></div>
<span className="text-xs text-slate-400 bg-slate-800/50 px-2 py-1 rounded">
<span className="text-xs text-[var(--muted-foreground)] bg-[var(--card)] px-2 py-1 rounded">
{activeTasks.length}
</span>
</div>
{activeTasks.length === 0 ? (
<div className="text-center py-8 text-slate-400 text-sm">
<div className="text-center py-8 text-[var(--muted-foreground)] text-sm">
<div className="text-2xl mb-2">🎯</div>
Aucun objectif actif
</div>
@@ -140,13 +140,13 @@ export function ObjectivesBoard({
Terminé
</h3>
<div className="flex-1"></div>
<span className="text-xs text-slate-400 bg-slate-800/50 px-2 py-1 rounded">
<span className="text-xs text-[var(--muted-foreground)] bg-[var(--card)] px-2 py-1 rounded">
{completedTasks.length}
</span>
</div>
{completedTasks.length === 0 ? (
<div className="text-center py-8 text-slate-400 text-sm">
<div className="text-center py-8 text-[var(--muted-foreground)] text-sm">
<div className="text-2xl mb-2"></div>
Aucun objectif terminé
</div>

View File

@@ -115,7 +115,7 @@ export function QuickAddTask({ status, onSubmit, onCancel }: QuickAddTaskProps)
return (
<div onBlur={handleBlur}>
<Card className="p-3 border-dashed border-cyan-500/30 bg-slate-800/50 hover:border-cyan-500/50 transition-all duration-300">
<Card className="p-3 border-dashed border-[var(--primary)]/30 bg-[var(--card)]/50 hover:border-[var(--primary)]/50 transition-all duration-300">
{/* Header avec titre et priorité */}
<div className="flex items-start gap-2 mb-2">
<input
@@ -127,7 +127,7 @@ export function QuickAddTask({ status, onSubmit, onCancel }: QuickAddTaskProps)
onFocus={() => setActiveField('title')}
placeholder="Titre de la tâche..."
disabled={isSubmitting}
className="flex-1 bg-transparent border-none outline-none text-slate-100 font-mono text-sm font-medium placeholder-slate-500 leading-tight"
className="flex-1 bg-transparent border-none outline-none text-[var(--foreground)] font-mono text-sm font-medium placeholder-[var(--muted-foreground)] leading-tight"
/>
{/* Indicateur de priorité */}
@@ -135,7 +135,7 @@ export function QuickAddTask({ status, onSubmit, onCancel }: QuickAddTaskProps)
value={formData.priority}
onChange={(e) => setFormData(prev => ({ ...prev, priority: e.target.value as TaskPriority }))}
disabled={isSubmitting}
className="bg-transparent border-none outline-none text-xs font-mono text-slate-400"
className="bg-transparent border-none outline-none text-xs font-mono text-[var(--muted-foreground)]"
>
{getAllPriorities().map(priorityConfig => (
<option key={priorityConfig.key} value={priorityConfig.key}>
@@ -154,7 +154,7 @@ export function QuickAddTask({ status, onSubmit, onCancel }: QuickAddTaskProps)
placeholder="Description..."
rows={2}
disabled={isSubmitting}
className="w-full bg-transparent border-none outline-none text-xs text-slate-400 font-mono placeholder-slate-500 resize-none mb-2"
className="w-full bg-transparent border-none outline-none text-xs text-[var(--muted-foreground)] font-mono placeholder-[var(--muted-foreground)] resize-none mb-2"
/>
{/* Tags */}
@@ -169,7 +169,7 @@ export function QuickAddTask({ status, onSubmit, onCancel }: QuickAddTaskProps)
</div>
{/* Footer avec date et actions */}
<div className="pt-2 border-t border-slate-700/50">
<div className="pt-2 border-t border-[var(--border)]/50">
<div className="flex items-center justify-between text-xs min-w-0">
<input
type="datetime-local"
@@ -180,12 +180,12 @@ export function QuickAddTask({ status, onSubmit, onCancel }: QuickAddTaskProps)
}))}
onFocus={() => setActiveField('date')}
disabled={isSubmitting}
className="bg-transparent border-none outline-none text-slate-400 font-mono text-xs flex-shrink min-w-0"
className="bg-transparent border-none outline-none text-[var(--muted-foreground)] font-mono text-xs flex-shrink min-w-0"
/>
{isSubmitting && (
<div className="flex items-center gap-1 text-cyan-400 font-mono text-xs flex-shrink-0">
<div className="w-3 h-3 border border-cyan-400 border-t-transparent rounded-full animate-spin"></div>
<div className="flex items-center gap-1 text-[var(--primary)] font-mono text-xs flex-shrink-0">
<div className="w-3 h-3 border border-[var(--primary)] border-t-transparent rounded-full animate-spin"></div>
<span>...</span>
</div>
)}

View File

@@ -83,7 +83,7 @@ function DroppableColumn({
className="w-full p-2 flex justify-center transition-colors"
title="Ajouter une tâche"
>
<div className="w-5 h-5 rounded-full bg-slate-800/30 hover:bg-slate-700/50 flex items-center justify-center text-slate-600 hover:text-slate-300 transition-all text-sm">
<div className="w-5 h-5 rounded-full bg-[var(--card)] hover:bg-[var(--card-hover)] flex items-center justify-center text-[var(--muted-foreground)] hover:text-[var(--foreground)] transition-all text-sm">
+
</div>
</button>
@@ -209,11 +209,11 @@ export function SwimlanesBase({
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
<div className="flex flex-col h-full bg-slate-950">
<div className="flex flex-col h-full bg-[var(--background)]">
{/* Header */}
<div className="flex-shrink-0 px-6 py-4 border-b border-slate-700/50">
<div className="flex-shrink-0 px-6 py-4 border-b border-[var(--border)]/50">
<div className="flex items-center justify-between">
<h2 className="text-lg font-mono font-bold text-slate-200 uppercase tracking-wider">
<h2 className="text-lg font-mono font-bold text-[var(--foreground)] uppercase tracking-wider">
{title}
</h2>
@@ -239,7 +239,7 @@ export function SwimlanesBase({
const statusConfig = allStatuses.find(s => s.key === status);
return (
<div key={status} className="text-center">
<h3 className="text-sm font-mono font-bold text-slate-300 uppercase tracking-wider">
<h3 className="text-sm font-mono font-bold text-[var(--foreground)] uppercase tracking-wider">
{statusConfig?.icon} {statusConfig?.label}
</h3>
</div>
@@ -254,15 +254,15 @@ export function SwimlanesBase({
const isCollapsed = collapsedSwimlanes.has(swimlane.key);
return (
<div key={swimlane.key} className="border border-slate-700/50 rounded-lg bg-slate-900/30">
<div key={swimlane.key} className="border border-[var(--border)]/50 rounded-lg bg-[var(--card)]/30">
{/* Header de la swimlane */}
<div className="flex items-center p-2 border-b border-slate-700/50">
<div className="flex items-center p-2 border-b border-[var(--border)]/50">
<button
onClick={() => toggleSwimlane(swimlane.key)}
className="flex items-center gap-2 hover:bg-slate-800/50 rounded p-1 -m-1 transition-colors"
className="flex items-center gap-2 hover:bg-[var(--card-hover)] rounded p-1 -m-1 transition-colors"
>
<svg
className={`w-4 h-4 text-slate-400 transition-transform ${isCollapsed ? '' : 'rotate-90'}`}
className={`w-4 h-4 text-[var(--muted-foreground)] transition-transform ${isCollapsed ? '' : 'rotate-90'}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
@@ -275,7 +275,7 @@ export function SwimlanesBase({
style={{ backgroundColor: swimlane.color }}
/>
)}
<span className="text-slate-200 font-medium text-sm">
<span className="text-[var(--foreground)] font-medium text-sm">
{swimlane.icon && `${swimlane.icon} `}
{swimlane.label} ({swimlane.tasks.length})
</span>

View File

@@ -100,7 +100,7 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
<Card
ref={setNodeRef}
style={style}
className={`p-2 hover:border-cyan-500/30 hover:shadow-lg hover:shadow-cyan-500/10 transition-all duration-300 cursor-pointer group ${
className={`p-2 hover:border-[var(--primary)]/30 hover:shadow-lg hover:shadow-[var(--primary)]/10 transition-all duration-300 cursor-pointer group ${
isDragging ? 'opacity-50 rotate-3 scale-105' : ''
}`}
{...attributes}
@@ -132,11 +132,11 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
onKeyDown={handleTitleKeyPress}
onBlur={handleTitleSave}
autoFocus
className="flex-1 bg-transparent border-none outline-none text-slate-100 font-mono text-sm font-medium leading-tight"
className="flex-1 bg-transparent border-none outline-none text-[var(--foreground)] font-mono text-sm font-medium leading-tight"
/>
) : (
<h4
className="font-mono text-xs font-medium text-slate-100 leading-tight line-clamp-2 flex-1 cursor-pointer hover:text-cyan-300 transition-colors"
className="font-mono text-xs font-medium text-[var(--foreground)] leading-tight line-clamp-2 flex-1 cursor-pointer hover:text-[var(--primary)] transition-colors"
onClick={handleTitleClick}
title={onUpdateTitle ? "Cliquer pour éditer" : undefined}
>
@@ -182,7 +182,7 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
<Card
ref={setNodeRef}
style={style}
className={`p-3 hover:border-cyan-500/30 hover:shadow-lg hover:shadow-cyan-500/10 transition-all duration-300 cursor-pointer group ${
className={`p-3 hover:border-[var(--primary)]/30 hover:shadow-lg hover:shadow-[var(--primary)]/10 transition-all duration-300 cursor-pointer group ${
isDragging ? 'opacity-50 rotate-3 scale-105' : ''
}`}
{...attributes}
@@ -215,11 +215,11 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
onKeyDown={handleTitleKeyPress}
onBlur={handleTitleSave}
autoFocus
className="flex-1 bg-transparent border-none outline-none text-slate-100 font-mono text-sm font-medium leading-tight"
className="flex-1 bg-transparent border-none outline-none text-[var(--foreground)] font-mono text-sm font-medium leading-tight"
/>
) : (
<h4
className="font-mono text-sm font-medium text-slate-100 leading-tight line-clamp-2 flex-1 cursor-pointer hover:text-cyan-300 transition-colors"
className="font-mono text-sm font-medium text-[var(--foreground)] leading-tight line-clamp-2 flex-1 cursor-pointer hover:text-[var(--primary)] transition-colors"
onClick={handleTitleClick}
title={onUpdateTitle ? "Cliquer pour éditer" : undefined}
>
@@ -232,7 +232,7 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
{onEdit && (
<button
onClick={handleEdit}
className="opacity-0 group-hover:opacity-100 w-4 h-4 rounded-full bg-blue-900/50 hover:bg-blue-800/80 border border-blue-500/30 hover:border-blue-400/50 flex items-center justify-center transition-all duration-200 text-blue-400 hover:text-blue-300 text-xs"
className="opacity-0 group-hover:opacity-100 w-4 h-4 rounded-full bg-[var(--primary)]/20 hover:bg-[var(--primary)]/30 border border-[var(--primary)]/30 hover:border-[var(--primary)]/50 flex items-center justify-center transition-all duration-200 text-[var(--primary)] hover:text-[var(--primary)] text-xs"
title="Modifier la tâche"
>
@@ -243,7 +243,7 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
{onDelete && (
<button
onClick={handleDelete}
className="opacity-0 group-hover:opacity-100 w-4 h-4 rounded-full bg-red-900/50 hover:bg-red-800/80 border border-red-500/30 hover:border-red-400/50 flex items-center justify-center transition-all duration-200 text-red-400 hover:text-red-300 text-xs"
className="opacity-0 group-hover:opacity-100 w-4 h-4 rounded-full bg-[var(--destructive)]/20 hover:bg-[var(--destructive)]/30 border border-[var(--destructive)]/30 hover:border-[var(--destructive)]/50 flex items-center justify-center transition-all duration-200 text-[var(--destructive)] hover:text-[var(--destructive)] text-xs"
title="Supprimer la tâche"
>
×
@@ -263,7 +263,7 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
{/* Description tech */}
{task.description && (
<p className="text-xs text-slate-400 mb-3 line-clamp-1 font-mono">
<p className="text-xs text-[var(--muted-foreground)] mb-3 line-clamp-1 font-mono">
{task.description}
</p>
)}
@@ -287,10 +287,10 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
{/* Footer tech avec séparateur néon - seulement si des données à afficher */}
{(task.dueDate || (task.source && task.source !== 'manual') || task.completedAt) && (
<div className="pt-2 border-t border-slate-700/50">
<div className="pt-2 border-t border-[var(--border)]/50">
<div className="flex items-center justify-between text-xs">
{task.dueDate ? (
<span className="flex items-center gap-1 text-slate-400 font-mono">
<span className="flex items-center gap-1 text-[var(--muted-foreground)] font-mono">
<span className="text-cyan-400"></span>
{formatDistanceToNow(new Date(task.dueDate), {
addSuffix: true,

View File

@@ -11,12 +11,12 @@ const Badge = forwardRef<HTMLSpanElement, BadgeProps>(
const baseStyles = 'inline-flex items-center font-mono font-medium transition-all duration-200';
const variants = {
default: 'bg-slate-800/50 text-slate-300 border border-slate-600/50',
primary: 'bg-cyan-950/50 text-cyan-300 border border-cyan-500/30',
success: 'bg-emerald-950/50 text-emerald-300 border border-emerald-500/30',
warning: 'bg-yellow-950/50 text-yellow-300 border border-yellow-500/30',
danger: 'bg-red-950/50 text-red-300 border border-red-500/30',
outline: 'bg-transparent text-slate-400 border border-slate-600 hover:bg-slate-800/30 hover:text-slate-300'
default: 'bg-[var(--card)] text-[var(--muted-foreground)] border border-[var(--border)]',
primary: 'bg-[var(--primary)]/20 text-[var(--primary)] border border-[var(--primary)]/30',
success: 'bg-[var(--success)]/20 text-[var(--success)] border border-[var(--success)]/30',
warning: 'bg-[var(--accent)]/20 text-[var(--accent)] border border-[var(--accent)]/30',
danger: 'bg-[var(--destructive)]/20 text-[var(--destructive)] border border-[var(--destructive)]/30',
outline: 'bg-transparent text-[var(--muted-foreground)] border border-[var(--border)] hover:bg-[var(--card-hover)] hover:text-[var(--foreground)]'
};
const sizes = {

View File

@@ -8,13 +8,13 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ 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-slate-950 disabled:opacity-50 disabled:cursor-not-allowed';
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 = {
primary: 'bg-cyan-600 hover:bg-cyan-500 text-white border border-cyan-500/30 shadow-cyan-500/20 shadow-lg hover:shadow-cyan-500/30 focus:ring-cyan-500',
secondary: 'bg-slate-800 hover:bg-slate-700 text-slate-100 border border-slate-600 shadow-slate-500/20 shadow-lg hover:shadow-slate-500/30 focus:ring-slate-500',
danger: 'bg-red-600 hover:bg-red-500 text-white border border-red-500/30 shadow-red-500/20 shadow-lg hover:shadow-red-500/30 focus:ring-red-500',
ghost: 'bg-transparent hover:bg-slate-800/50 text-slate-300 hover:text-slate-100 border border-slate-700/50 hover:border-slate-600 focus:ring-slate-500'
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)]',
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)]',
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: '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)]'
};
const sizes = {

View File

@@ -2,15 +2,16 @@ import { HTMLAttributes, forwardRef } from 'react';
import { cn } from '@/lib/utils';
interface CardProps extends HTMLAttributes<HTMLDivElement> {
variant?: 'default' | 'elevated' | 'bordered';
variant?: 'default' | 'elevated' | 'bordered' | 'column';
}
const Card = forwardRef<HTMLDivElement, CardProps>(
({ className, variant = 'default', ...props }, ref) => {
const variants = {
default: 'bg-slate-800/50 border border-slate-700/50',
elevated: 'bg-slate-800/80 border border-slate-700/50 shadow-lg shadow-slate-900/20',
bordered: 'bg-slate-900/50 border border-cyan-500/30 shadow-cyan-500/10 shadow-lg'
default: 'bg-[var(--card)]/50 border border-[var(--border)]/50',
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'
};
return (
@@ -33,7 +34,7 @@ const CardHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('p-4 border-b border-slate-700/50', className)}
className={cn('p-4 border-b border-[var(--border)]/50', className)}
{...props}
/>
)
@@ -45,7 +46,7 @@ const CardTitle = forwardRef<HTMLHeadingElement, HTMLAttributes<HTMLHeadingEleme
({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn('font-mono font-semibold text-slate-100 tracking-wide', className)}
className={cn('font-mono font-semibold text-[var(--foreground)] tracking-wide', className)}
{...props}
/>
)
@@ -69,7 +70,7 @@ const CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('p-4 border-t border-slate-700/50', className)}
className={cn('p-4 border-t border-[var(--border)]/50', className)}
{...props}
/>
)

View File

@@ -1,5 +1,6 @@
import { Card, CardContent } from '@/components/ui/Card';
import { TaskStats } from '@/lib/types';
import { useTheme } from '@/contexts/ThemeContext';
import Link from 'next/link';
interface HeaderProps {
@@ -10,8 +11,10 @@ interface HeaderProps {
}
export function Header({ title, subtitle, stats, syncing = false }: HeaderProps) {
const { theme, toggleTheme } = useTheme();
return (
<header className="bg-slate-900/80 backdrop-blur-sm border-b border-slate-700/50 shadow-lg shadow-slate-900/20">
<header className="bg-[var(--card)]/80 backdrop-blur-sm border-b border-[var(--border)]/50 shadow-lg shadow-[var(--card)]/20">
<div className="container mx-auto px-6 py-4">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-6">
{/* Titre tech avec glow */}
@@ -23,10 +26,10 @@ export function Header({ title, subtitle, stats, syncing = false }: HeaderProps)
: 'bg-cyan-400 animate-pulse shadow-cyan-400/50'
}`}></div>
<div>
<h1 className="text-2xl font-mono font-bold text-slate-100 tracking-wider">
<h1 className="text-2xl font-mono font-bold text-[var(--foreground)] tracking-wider">
{title}
</h1>
<p className="text-slate-400 mt-1 font-mono text-sm">
<p className="text-[var(--muted-foreground)] mt-1 font-mono text-sm">
{subtitle} {syncing && '• Synchronisation...'}
</p>
</div>
@@ -36,16 +39,33 @@ export function Header({ title, subtitle, stats, syncing = false }: HeaderProps)
<nav className="hidden sm:flex items-center gap-4">
<Link
href="/"
className="text-slate-400 hover:text-cyan-400 transition-colors font-mono text-sm uppercase tracking-wider"
className="text-[var(--muted-foreground)] hover:text-[var(--primary)] transition-colors font-mono text-sm uppercase tracking-wider"
>
Kanban
</Link>
<Link
href="/tags"
className="text-slate-400 hover:text-purple-400 transition-colors font-mono text-sm uppercase tracking-wider"
className="text-[var(--muted-foreground)] hover:text-[var(--accent)] transition-colors font-mono text-sm uppercase tracking-wider"
>
Tags
</Link>
{/* Theme Toggle */}
<button
onClick={toggleTheme}
className="text-[var(--muted-foreground)] hover:text-[var(--primary)] transition-colors p-1 rounded-md hover:bg-[var(--card-hover)]"
title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} theme`}
>
{theme === 'dark' ? (
<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>
) : (
<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>
)}
</button>
</nav>
</div>
@@ -119,11 +139,11 @@ interface StatCardProps {
function StatCard({ label, value, color }: StatCardProps) {
const textColors = {
blue: 'text-cyan-300',
green: 'text-emerald-300',
yellow: 'text-yellow-300',
gray: 'text-slate-300',
purple: 'text-purple-300'
blue: 'text-[var(--primary)]',
green: 'text-[var(--success)]',
yellow: 'text-[var(--accent)]',
gray: 'text-[var(--muted-foreground)]',
purple: 'text-[var(--accent)]'
};
return (

View File

@@ -11,26 +11,26 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
return (
<div className="space-y-2">
{label && (
<label className="block text-sm font-mono font-medium text-slate-300 uppercase tracking-wider">
<label className="block text-sm font-mono font-medium text-[var(--muted-foreground)] uppercase tracking-wider">
{label}
</label>
)}
<input
className={cn(
'w-full px-3 py-2 bg-slate-800/50 border border-slate-700/50 rounded-lg',
'text-slate-100 font-mono text-sm placeholder-slate-500',
'focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500/50',
'hover:border-slate-600/50 transition-all duration-200',
'w-full px-3 py-2 bg-[var(--input)] border border-[var(--border)]/50 rounded-lg',
'text-[var(--foreground)] font-mono text-sm placeholder-[var(--muted-foreground)]',
'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-red-500/50 focus:ring-red-500/50 focus:border-red-500/50',
error && 'border-[var(--destructive)]/50 focus:ring-[var(--destructive)]/50 focus:border-[var(--destructive)]/50',
className
)}
ref={ref}
{...props}
/>
{error && (
<p className="text-xs font-mono text-red-400 flex items-center gap-1">
<span className="text-red-500"></span>
<p className="text-xs font-mono text-[var(--destructive)] flex items-center gap-1">
<span className="text-[var(--destructive)]"></span>
{error}
</p>
)}

View File

@@ -42,24 +42,24 @@ export function Modal({ isOpen, onClose, children, title, size = 'md' }: ModalPr
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div
className="absolute inset-0 bg-slate-950/80 backdrop-blur-sm"
className="absolute inset-0 bg-[var(--background)]/80 backdrop-blur-sm"
onClick={onClose}
/>
{/* Modal */}
<div className={cn(
'relative w-full mx-4 bg-slate-900/95 border border-slate-700/50 rounded-lg shadow-2xl shadow-slate-900/50 backdrop-blur-sm',
'relative w-full mx-4 bg-[var(--card)]/95 border border-[var(--border)]/50 rounded-lg shadow-2xl shadow-[var(--card)]/50 backdrop-blur-sm',
sizes[size]
)}>
{/* Header */}
{title && (
<div className="flex items-center justify-between p-4 border-b border-slate-700/50">
<h2 className="text-lg font-mono font-semibold text-slate-100 tracking-wide">
<div className="flex items-center justify-between p-4 border-b border-[var(--border)]/50">
<h2 className="text-lg font-mono font-semibold text-[var(--foreground)] tracking-wide">
{title}
</h2>
<button
onClick={onClose}
className="text-slate-400 hover:text-slate-100 transition-colors p-1"
className="text-[var(--muted-foreground)] hover:text-[var(--foreground)] transition-colors p-1"
>
<span className="sr-only">Fermer</span>

View File

@@ -108,7 +108,7 @@ export function TagInput({
return (
<div className={`relative ${className}`}>
{/* Container des tags et input */}
<div className="min-h-[42px] p-2 border border-slate-600 rounded-lg bg-slate-800 focus-within:border-cyan-400 focus-within:ring-1 focus-within:ring-cyan-400/20 transition-colors">
<div className="min-h-[42px] p-2 border border-[var(--border)] rounded-lg bg-[var(--input)] focus-within:border-[var(--primary)] focus-within:ring-1 focus-within:ring-[var(--primary)]/20 transition-colors">
<div className="flex flex-wrap gap-1 items-center">
{/* Tags existants */}
{tags.map((tag, index) => (
@@ -121,7 +121,7 @@ export function TagInput({
<button
type="button"
onClick={() => removeTag(tag)}
className="text-slate-400 hover:text-slate-200 ml-1"
className="text-[var(--muted-foreground)] hover:text-[var(--foreground)] ml-1"
aria-label={`Supprimer le tag ${tag}`}
>
×
@@ -140,7 +140,7 @@ export function TagInput({
onBlur={handleBlur}
onFocus={handleFocus}
placeholder={tags.length === 0 ? placeholder : ""}
className="flex-1 min-w-[120px] bg-transparent border-none outline-none text-slate-100 placeholder-slate-400 text-sm"
className="flex-1 min-w-[120px] bg-transparent border-none outline-none text-[var(--foreground)] placeholder-[var(--muted-foreground)] text-sm"
/>
)}
</div>
@@ -150,10 +150,10 @@ export function TagInput({
{showSuggestions && (suggestions.length > 0 || loading) && (
<div
ref={suggestionsRef}
className="absolute top-full left-0 right-0 mt-1 bg-slate-800 border border-slate-600 rounded-lg shadow-lg z-50 max-h-64 overflow-y-auto"
className="absolute top-full left-0 right-0 mt-1 bg-[var(--card)] border border-[var(--border)] rounded-lg shadow-lg z-50 max-h-64 overflow-y-auto"
>
{loading ? (
<div className="p-3 text-center text-slate-400 text-sm">
<div className="p-3 text-center text-[var(--muted-foreground)] text-sm">
Recherche...
</div>
) : (
@@ -165,8 +165,8 @@ export function TagInput({
onClick={() => handleSuggestionClick(tag)}
className={`flex items-center gap-2 px-2 py-1.5 text-xs rounded-md transition-colors ${
index === selectedIndex
? 'bg-slate-700 text-cyan-300 ring-1 ring-cyan-400'
: 'text-slate-200 hover:bg-slate-700'
? 'bg-[var(--card-hover)] text-[var(--primary)] ring-1 ring-[var(--primary)]'
: 'text-[var(--foreground)] hover:bg-[var(--card-hover)]'
} ${tags.includes(tag.name) ? 'opacity-50 cursor-not-allowed' : ''}`}
disabled={tags.includes(tag.name)}
title={tags.includes(tag.name) ? 'Déjà ajouté' : `Ajouter ${tag.name}`}
@@ -177,7 +177,7 @@ export function TagInput({
/>
<span className="truncate">{tag.name}</span>
{tags.includes(tag.name) && (
<span className="text-slate-400 ml-auto"></span>
<span className="text-[var(--muted-foreground)] ml-auto"></span>
)}
</button>
))}
@@ -188,7 +188,7 @@ export function TagInput({
{/* Indicateur de limite */}
{tags.length >= maxTags && (
<div className="text-xs text-slate-400 mt-1">
<div className="text-xs text-[var(--muted-foreground)] mt-1">
Limite de {maxTags} tags atteinte
</div>
)}