Dark mode by default
This commit is contained in:
@@ -1,13 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Monitor, Moon, Sun } from 'lucide-vue-next';
|
import { Moon } from 'lucide-vue-next';
|
||||||
import { useAppearance } from '@/composables/useAppearance';
|
import { useAppearance } from '@/composables/useAppearance';
|
||||||
|
|
||||||
const { appearance, updateAppearance } = useAppearance();
|
const { appearance, updateAppearance } = useAppearance();
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ value: 'light', Icon: Sun, label: 'Light' },
|
|
||||||
{ value: 'dark', Icon: Moon, label: 'Dark' },
|
{ value: 'dark', Icon: Moon, label: 'Dark' },
|
||||||
{ value: 'system', Icon: Monitor, label: 'System' },
|
|
||||||
] as const;
|
] as const;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -18,7 +16,7 @@ const tabs = [
|
|||||||
<button
|
<button
|
||||||
v-for="{ value, Icon, label } in tabs"
|
v-for="{ value, Icon, label } in tabs"
|
||||||
:key="value"
|
:key="value"
|
||||||
@click="updateAppearance(value)"
|
@click="updateAppearance()"
|
||||||
:class="[
|
:class="[
|
||||||
'flex items-center rounded-md px-3.5 py-1.5 transition-colors',
|
'flex items-center rounded-md px-3.5 py-1.5 transition-colors',
|
||||||
appearance === value
|
appearance === value
|
||||||
|
|||||||
@@ -10,24 +10,12 @@ export type UseAppearanceReturn = {
|
|||||||
updateAppearance: (value: Appearance) => void;
|
updateAppearance: (value: Appearance) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function updateTheme(value: Appearance): void {
|
export function updateTheme(): void {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value === 'system') {
|
document.documentElement.classList.add('dark');
|
||||||
const mediaQueryList = window.matchMedia(
|
|
||||||
'(prefers-color-scheme: dark)',
|
|
||||||
);
|
|
||||||
const systemTheme = mediaQueryList.matches ? 'dark' : 'light';
|
|
||||||
|
|
||||||
document.documentElement.classList.toggle(
|
|
||||||
'dark',
|
|
||||||
systemTheme === 'dark',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
document.documentElement.classList.toggle('dark', value === 'dark');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const setCookie = (name: string, value: string, days = 365) => {
|
const setCookie = (name: string, value: string, days = 365) => {
|
||||||
@@ -40,80 +28,30 @@ const setCookie = (name: string, value: string, days = 365) => {
|
|||||||
document.cookie = `${name}=${value};path=/;max-age=${maxAge};SameSite=Lax`;
|
document.cookie = `${name}=${value};path=/;max-age=${maxAge};SameSite=Lax`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mediaQuery = () => {
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return window.matchMedia('(prefers-color-scheme: dark)');
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStoredAppearance = () => {
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return localStorage.getItem('appearance') as Appearance | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const prefersDark = (): boolean => {
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSystemThemeChange = () => {
|
|
||||||
const currentAppearance = getStoredAppearance();
|
|
||||||
|
|
||||||
updateTheme(currentAppearance || 'system');
|
|
||||||
};
|
|
||||||
|
|
||||||
export function initializeTheme(): void {
|
export function initializeTheme(): void {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize theme from saved preference or default to system...
|
updateTheme();
|
||||||
const savedAppearance = getStoredAppearance();
|
|
||||||
updateTheme(savedAppearance || 'system');
|
|
||||||
|
|
||||||
// Set up system theme change listener...
|
|
||||||
mediaQuery()?.addEventListener('change', handleSystemThemeChange);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const appearance = ref<Appearance>('system');
|
const appearance = ref<Appearance>('dark');
|
||||||
|
|
||||||
export function useAppearance(): UseAppearanceReturn {
|
export function useAppearance(): UseAppearanceReturn {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const savedAppearance = localStorage.getItem(
|
appearance.value = 'dark';
|
||||||
'appearance',
|
|
||||||
) as Appearance | null;
|
|
||||||
|
|
||||||
if (savedAppearance) {
|
|
||||||
appearance.value = savedAppearance;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const resolvedAppearance = computed<ResolvedAppearance>(() => {
|
const resolvedAppearance = computed<ResolvedAppearance>(() => {
|
||||||
if (appearance.value === 'system') {
|
return 'dark';
|
||||||
return prefersDark() ? 'dark' : 'light';
|
|
||||||
}
|
|
||||||
|
|
||||||
return appearance.value;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateAppearance(value: Appearance) {
|
function updateAppearance() {
|
||||||
appearance.value = value;
|
appearance.value = 'dark';
|
||||||
|
localStorage.setItem('appearance', 'dark');
|
||||||
// Store in localStorage for client-side persistence...
|
setCookie('appearance', 'dark');
|
||||||
localStorage.setItem('appearance', value);
|
updateTheme();
|
||||||
|
|
||||||
// Store in cookie for SSR...
|
|
||||||
setCookie('appearance', value);
|
|
||||||
|
|
||||||
updateTheme(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AppLayout from '@/layouts/app/AppSidebarLayout.vue';
|
import AppLayout from '@/layouts/app/AppSidebarLayout.vue';
|
||||||
import type { BreadcrumbItem } from '@/types';
|
import type { BreadcrumbItem } from '@/types';
|
||||||
|
import { useColorMode, useDark } from '@vueuse/core';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
breadcrumbs?: BreadcrumbItem[];
|
breadcrumbs?: BreadcrumbItem[];
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type Appearance = 'light' | 'dark' | 'system';
|
export type Appearance = 'dark';
|
||||||
export type ResolvedAppearance = 'light' | 'dark';
|
export type ResolvedAppearance = 'dark';
|
||||||
|
|
||||||
export type AppShellVariant = 'header' | 'sidebar';
|
export type AppShellVariant = 'header' | 'sidebar';
|
||||||
|
|||||||
@@ -1,34 +1,15 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" @class(['dark' => ($appearance ?? 'system') == 'dark'])>
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
{{-- Inline script to detect system dark mode preference and apply it immediately --}}
|
|
||||||
<script>
|
|
||||||
(function() {
|
|
||||||
const appearance = '{{ $appearance ?? "system" }}';
|
|
||||||
|
|
||||||
if (appearance === 'system') {
|
|
||||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
||||||
|
|
||||||
if (prefersDark) {
|
|
||||||
document.documentElement.classList.add('dark');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{{-- Anonymous analytics --}}
|
{{-- Anonymous analytics --}}
|
||||||
<script defer src="https://analytics.matthiasg.dev/recorder.js" data-website-id="e616767f-e0d3-4ce9-b551-33c7c3806fc0"></script>
|
<script defer src="https://analytics.matthiasg.dev/recorder.js" data-website-id="e616767f-e0d3-4ce9-b551-33c7c3806fc0"></script>
|
||||||
|
|
||||||
{{-- Inline style to set the HTML background color based on our theme in app.css --}}
|
{{-- Inline style to set the HTML background color based on our theme in app.css --}}
|
||||||
<style>
|
<style>
|
||||||
html {
|
html {
|
||||||
background-color: oklch(1 0 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark {
|
|
||||||
background-color: oklch(0.145 0 0);
|
background-color: oklch(0.145 0 0);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user