feat(ui) : add dark mode toggle and remove inline dark: classes
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s

- Add dark mode toggle button in top nav
- Add darkMode store with localStorage persistence
- Enable Tailwind class-based dark mode
- Import dark.css global overrides
- Remove inline dark: Tailwind classes (handled by global CSS)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-03-19 18:07:55 +01:00
parent 43304bebcc
commit 4ce0214ec9
7 changed files with 48 additions and 12 deletions

View File

@@ -1,6 +1,6 @@
<template> <template>
<div <div
class="cursor-pointer rounded-lg border border-neutral-200 bg-white p-3 shadow-sm transition hover:shadow-md dark:border-dark-border dark:bg-dark-card" class="cursor-pointer rounded-lg border border-neutral-200 bg-white p-3 shadow-sm transition hover:shadow-md"
draggable="true" draggable="true"
@dragstart="onDragStart" @dragstart="onDragStart"
@dragend="onDragEnd" @dragend="onDragEnd"
@@ -27,7 +27,7 @@
:title="$t('clientTicket.linkedTooltip', { number: 'CT-' + String(task.clientTicket.number).padStart(3, '0') })" :title="$t('clientTicket.linkedTooltip', { number: 'CT-' + String(task.clientTicket.number).padStart(3, '0') })"
/> />
</div> </div>
<h4 class="text-sm font-semibold text-neutral-900 dark:text-neutral-100">{{ task.title }}</h4> <h4 class="text-sm font-semibold text-neutral-900">{{ task.title }}</h4>
</div> </div>
<button <button
class="shrink-0 transition-colors" class="shrink-0 transition-colors"
@@ -85,7 +85,7 @@
/> />
<span <span
v-else v-else
class="ml-auto flex h-5 w-5 items-center justify-center rounded-full bg-neutral-200 text-neutral-400 dark:bg-dark-hover dark:text-neutral-400" class="ml-auto flex h-5 w-5 items-center justify-center rounded-full bg-neutral-200 text-neutral-400"
> >
<Icon name="mdi:account-outline" size="14" /> <Icon name="mdi:account-outline" size="14" />
</span> </span>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div <div
class="flex cursor-pointer items-stretch gap-3 rounded-[10px] bg-white px-3 py-2.5 transition-colors hover:shadow-sm sm:px-4 dark:bg-dark-card" class="flex cursor-pointer items-stretch gap-3 rounded-[10px] bg-white px-3 py-2.5 transition-colors hover:shadow-sm sm:px-4"
:class="selected ? 'ring-2 ring-primary-500' : ''" :class="selected ? 'ring-2 ring-primary-500' : ''"
@click="emit('click')" @click="emit('click')"
> >
@@ -30,7 +30,7 @@
/> />
</div> </div>
<!-- Row 2: title --> <!-- Row 2: title -->
<h4 class="mt-1 text-sm font-semibold text-neutral-900 dark:text-neutral-100">{{ task.title }}</h4> <h4 class="mt-1 text-sm font-semibold text-neutral-900">{{ task.title }}</h4>
<!-- Row 3: tags + status + deadline/calendar/recurrence --> <!-- Row 3: tags + status + deadline/calendar/recurrence -->
<div class="mt-2 flex flex-wrap items-center gap-1.5"> <div class="mt-2 flex flex-wrap items-center gap-1.5">
<span <span
@@ -92,7 +92,7 @@
/> />
<span <span
v-else v-else
class="flex h-5 w-5 items-center justify-center rounded-full bg-neutral-200 text-neutral-400 dark:bg-dark-hover" class="flex h-5 w-5 items-center justify-center rounded-full bg-neutral-200 text-neutral-400"
> >
<Icon name="mdi:account-outline" size="14" /> <Icon name="mdi:account-outline" size="14" />
</span> </span>

View File

@@ -19,6 +19,14 @@
</button> </button>
</div> </div>
<div class="ml-auto flex items-center gap-4 text-xl text-white sm:gap-8"> <div class="ml-auto flex items-center gap-4 text-xl text-white sm:gap-8">
<button
type="button"
class="rounded-md p-1.5 text-white/70 transition-colors hover:bg-primary-600 hover:text-white"
:title="ui.darkMode ? 'Mode clair' : 'Mode sombre'"
@click="ui.toggleDarkMode()"
>
<Icon :name="ui.darkMode ? 'mdi:weather-sunny' : 'mdi:weather-night'" size="22" />
</button>
<NotificationBell /> <NotificationBell />
<div class="group relative flex gap-2 sm:gap-4"> <div class="group relative flex gap-2 sm:gap-4">
<UserAvatar v-if="user" :user="user" size="md" class="cursor-pointer" /> <UserAvatar v-if="user" :user="user" size="md" class="cursor-pointer" />

View File

@@ -2,6 +2,7 @@ export default defineNuxtConfig({
compatibilityDate: '2025-07-15', compatibilityDate: '2025-07-15',
devtools: {enabled: false}, devtools: {enabled: false},
ssr: false, ssr: false,
css: ['~/assets/css/dark.css'],
app: { app: {
baseURL: process.env.NODE_ENV === 'production' baseURL: process.env.NODE_ENV === 'production'
? (process.env.NUXT_PUBLIC_APP_BASE || '/') ? (process.env.NUXT_PUBLIC_APP_BASE || '/')

View File

@@ -320,7 +320,7 @@ onMounted(async () => {
<template> <template>
<div class="min-w-0"> <div class="min-w-0">
<!-- Header + Filters --> <!-- Header + Filters -->
<div class="sticky top-8 z-20 bg-white pb-4 sm:top-12 dark:bg-dark-base"> <div class="sticky top-8 z-20 bg-white pb-4 sm:top-12">
<div class="flex items-center justify-between gap-3"> <div class="flex items-center justify-between gap-3">
<h1 class="text-xl font-bold text-primary-500 sm:text-2xl">{{ $t('myTasks.title') }}</h1> <h1 class="text-xl font-bold text-primary-500 sm:text-2xl">{{ $t('myTasks.title') }}</h1>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@@ -420,7 +420,7 @@ onMounted(async () => {
v-for="status in sortedStatuses" v-for="status in sortedStatuses"
:key="status.id" :key="status.id"
class="flex min-w-36 flex-1 flex-col rounded-lg transition-colors" class="flex min-w-36 flex-1 flex-col rounded-lg transition-colors"
:class="dragOverStatusId === status.id ? 'bg-neutral-200 dark:bg-dark-hover' : 'bg-neutral-50 dark:bg-dark-surface/50'" :class="dragOverStatusId === status.id ? 'bg-neutral-200' : 'bg-neutral-50'"
@dragover.prevent @dragover.prevent
@dragenter.prevent="onDragEnter(status.id)" @dragenter.prevent="onDragEnter(status.id)"
@dragleave="onDragLeave" @dragleave="onDragLeave"
@@ -455,13 +455,13 @@ onMounted(async () => {
<!-- Backlog below kanban --> <!-- Backlog below kanban -->
<div <div
class="mt-8 rounded-lg p-4 transition-colors" class="mt-8 rounded-lg p-4 transition-colors"
:class="dragOverStatusId === 0 ? 'bg-neutral-200 dark:bg-dark-hover' : 'bg-neutral-50 dark:bg-dark-surface'" :class="dragOverStatusId === 0 ? 'bg-neutral-200' : 'bg-neutral-50'"
@dragover.prevent @dragover.prevent
@dragenter.prevent="onDragEnter(0)" @dragenter.prevent="onDragEnter(0)"
@dragleave="onDragLeave" @dragleave="onDragLeave"
@drop.prevent="onDropBacklog($event)" @drop.prevent="onDropBacklog($event)"
> >
<h2 class="text-lg font-bold text-neutral-900 dark:text-neutral-100">{{ $t('myTasks.backlog') }} ({{ backlogTasks.length }})</h2> <h2 class="text-lg font-bold text-neutral-900">{{ $t('myTasks.backlog') }} ({{ backlogTasks.length }})</h2>
<div class="mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"> <div class="mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<TaskCard <TaskCard
v-for="task in backlogTasks" v-for="task in backlogTasks"
@@ -481,7 +481,7 @@ onMounted(async () => {
</div> </div>
<!-- List View --> <!-- List View -->
<div v-if="viewMode === 'list'" class="mt-6 flex flex-col gap-2.5 rounded-[10px] bg-tertiary-500 p-2.5 dark:bg-dark-surface"> <div v-if="viewMode === 'list'" class="mt-6 flex flex-col gap-2.5 rounded-[10px] bg-tertiary-500 p-2.5">
<TaskBulkActions <TaskBulkActions
:selected-count="selectedTaskIds.size" :selected-count="selectedTaskIds.size"
:total-count="tasks.length" :total-count="tasks.length"

View File

@@ -1,12 +1,19 @@
export const useUiStore = defineStore('ui', () => { export const useUiStore = defineStore('ui', () => {
const sidebarCollapsed = ref(false) const sidebarCollapsed = ref(false)
const sidebarOpen = ref(false) const sidebarOpen = ref(false)
const darkMode = ref(false)
if (import.meta.client) { if (import.meta.client) {
const saved = localStorage.getItem('ui-sidebar-collapsed') const saved = localStorage.getItem('ui-sidebar-collapsed')
if (saved !== null) { if (saved !== null) {
sidebarCollapsed.value = saved === 'true' sidebarCollapsed.value = saved === 'true'
} }
const savedDark = localStorage.getItem('ui-dark-mode')
if (savedDark !== null) {
darkMode.value = savedDark === 'true'
}
applyDarkClass(darkMode.value)
} }
watch(sidebarCollapsed, (val) => { watch(sidebarCollapsed, (val) => {
@@ -15,6 +22,25 @@ export const useUiStore = defineStore('ui', () => {
} }
}) })
watch(darkMode, (val) => {
if (import.meta.client) {
localStorage.setItem('ui-dark-mode', String(val))
applyDarkClass(val)
}
})
function applyDarkClass(dark: boolean) {
if (dark) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
}
function toggleDarkMode() {
darkMode.value = !darkMode.value
}
function toggleSidebar() { function toggleSidebar() {
sidebarCollapsed.value = !sidebarCollapsed.value sidebarCollapsed.value = !sidebarCollapsed.value
} }
@@ -27,5 +53,5 @@ export const useUiStore = defineStore('ui', () => {
sidebarOpen.value = false sidebarOpen.value = false
} }
return { sidebarCollapsed, sidebarOpen, toggleSidebar, openMobileSidebar, closeMobileSidebar } return { sidebarCollapsed, sidebarOpen, darkMode, toggleSidebar, openMobileSidebar, closeMobileSidebar, toggleDarkMode }
}) })

View File

@@ -1,6 +1,7 @@
import type {Config} from 'tailwindcss' import type {Config} from 'tailwindcss'
export default <Partial<Config>>{ export default <Partial<Config>>{
darkMode: 'class',
theme: { theme: {
extend: { extend: {
fontFamily: { fontFamily: {