Skip to content

Commit e499964

Browse files
Implement appearance handling and theme management improvements
1 parent b647c28 commit e499964

6 files changed

Lines changed: 63 additions & 26 deletions

File tree

app/Bootstrap/MiddlewareBootstrapper.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ final class MiddlewareBootstrapper
1111
public function __invoke(Middleware $middleware): void
1212
{
1313
$middleware
14+
->encryptCookies(['appearance'])
1415
->web(append: [
16+
\App\Http\Middleware\HandleAppearance::class,
1517
\Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class,
1618
])
1719
->alias([
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Http\Middleware;
6+
7+
use Closure;
8+
use Illuminate\Http\Request;
9+
use Illuminate\Support\Facades\View;
10+
use Symfony\Component\HttpFoundation\Response;
11+
12+
final class HandleAppearance
13+
{
14+
/**
15+
* Handle an incoming request.
16+
*
17+
* @param Closure(Request): (Response) $next
18+
*/
19+
public function handle(Request $request, Closure $next): Response
20+
{
21+
View::share('appearance', $request->cookie('appearance', 'system'));
22+
23+
return $next($request);
24+
}
25+
}

resources/js/dashboard/composables/useAppearance.ts

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
1+
import { setCookie } from '@/dashboard/lib/cookie';
12
import { onMounted, ref } from 'vue';
23

34
type Appearance = 'light' | 'dark' | 'system';
45

5-
const getCurrentTheme = (): Appearance => {
6-
return document.documentElement.classList.contains('dark') ? 'dark' : 'light';
7-
};
8-
96
export function updateTheme(value: Appearance) {
10-
if (typeof window === 'undefined') {
11-
return;
12-
}
13-
14-
const systemTheme = mediaQuery()?.matches ? 'dark' : 'light';
7+
const systemTheme = mediaQuery().matches ? 'dark' : 'light';
158
const desiredTheme = value === 'system' ? systemTheme : value;
169
const currentTheme = getCurrentTheme();
1710

@@ -30,19 +23,15 @@ export function updateTheme(value: Appearance) {
3023
}
3124
}
3225

33-
const mediaQuery = () => {
34-
if (typeof window === 'undefined') {
35-
return null;
36-
}
26+
const getCurrentTheme = (): Appearance => {
27+
return document.documentElement.classList.contains('dark') ? 'dark' : 'light';
28+
};
3729

30+
const mediaQuery = () => {
3831
return window.matchMedia('(prefers-color-scheme: dark)');
3932
};
4033

4134
const getStoredAppearance = () => {
42-
if (typeof window === 'undefined') {
43-
return null;
44-
}
45-
4635
return localStorage.getItem('appearance') as Appearance | null;
4736
};
4837

@@ -53,22 +42,16 @@ const handleSystemThemeChange = () => {
5342
};
5443

5544
export const initializeTheme = () => {
56-
if (typeof window === 'undefined') {
57-
return;
58-
}
59-
6045
const savedAppearance = getStoredAppearance();
6146
updateTheme(savedAppearance || 'system');
6247

63-
mediaQuery()?.addEventListener('change', handleSystemThemeChange);
48+
mediaQuery().addEventListener('change', handleSystemThemeChange);
6449
};
6550

6651
export function useAppearance() {
6752
const appearance = ref<Appearance>('system');
6853

6954
onMounted(() => {
70-
initializeTheme();
71-
7255
const savedAppearance = localStorage.getItem('appearance') as Appearance | null;
7356

7457
if (savedAppearance) {
@@ -81,6 +64,8 @@ export function useAppearance() {
8164

8265
localStorage.setItem('appearance', value);
8366

67+
setCookie('appearance', value);
68+
8469
updateTheme(value);
8570
};
8671

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const setCookie = (name: string, value: string, days = 365) => {
2+
if (typeof document === 'undefined') {
3+
return;
4+
}
5+
6+
const maxAge = days * 24 * 60 * 60;
7+
8+
document.cookie = `${name}=${value};path=/;max-age=${maxAge};SameSite=Lax`;
9+
};
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { clsx, type ClassValue } from 'clsx';
22
import { twMerge } from 'tailwind-merge';
33

4-
export function cn(...inputs: ClassValue[]) {
4+
export const cn = (...inputs: ClassValue[]) => {
55
return twMerge(clsx(inputs));
6-
}
6+
};

resources/views/layouts/dashboard/app.blade.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<html
33
lang="{{ LaravelLocalization::getCurrentLocale() }}"
44
dir="{{ LaravelLocalization::getCurrentLocaleDirection() }}"
5+
@class(['dark' => ($appearance ?? 'system') == 'dark'])
56
>
67
<head>
78
<meta charset="UTF-8" />
@@ -41,6 +42,21 @@
4142

4243
<title inertia>{{ config('app.name', 'Laravel') }}</title>
4344

45+
{{-- Inline script to detect system dark mode preference and apply it immediately --}}
46+
<script>
47+
(function () {
48+
const appearance = '{{ $appearance }}' ?? 'system';
49+
50+
if (appearance === 'system') {
51+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
52+
53+
if (prefersDark) {
54+
document.documentElement.classList.add('dark');
55+
}
56+
}
57+
})();
58+
</script>
59+
4460
<!-- Scripts -->
4561
@routes
4662
<x-dashboard.initial-shared-data />

0 commit comments

Comments
 (0)