feat(ui) : add dark mode toggle and remove inline dark: classes
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
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:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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 || '/')
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
Reference in New Issue
Block a user