feat: implement theme selection and automatic theme application based on user preference. Update AppShell to include a dropdown for theme options and enhance layout to manage theme state. Adjust HTML language attribute to Russian and improve UI elements for better accessibility.
CI / changes (push) Successful in 5s
CI / openapi (push) Successful in 24s
CI / go (push) Successful in 21s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Successful in 1m4s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Successful in 1m2s
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Successful in 14s
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Successful in 1m1s
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Successful in 1m30s
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Successful in 1m31s
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Successful in 1m22s
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Successful in 1m28s
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Successful in 1m20s
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Successful in 1m22s
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Successful in 1m34s

This commit is contained in:
Denozordec
2026-04-05 22:43:43 +07:00
parent 1db6b5b7b2
commit b70a643016
6 changed files with 149 additions and 10 deletions
+45 -1
View File
@@ -2,9 +2,19 @@
import { resolve } from '$app/paths';
import { page } from '$app/state';
import { cn } from '$lib/utils.js';
import { Button } from '$lib/components/ui/button/index.js';
import { buttonVariants } from '$lib/components/ui/button/button.svelte';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger
} from '$lib/components/ui/dropdown-menu/index.js';
import { Separator } from '$lib/components/ui/separator/index.js';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '$lib/components/ui/tooltip/index.js';
import type { ThemePreference } from '$lib/theme.js';
import Activity from '@lucide/svelte/icons/activity';
import Boxes from '@lucide/svelte/icons/boxes';
@@ -17,6 +27,9 @@
import CalendarClock from '@lucide/svelte/icons/calendar-clock';
import PanelLeftClose from '@lucide/svelte/icons/panel-left-close';
import PanelLeftOpen from '@lucide/svelte/icons/panel-left-open';
import Sun from '@lucide/svelte/icons/sun';
import Moon from '@lucide/svelte/icons/moon';
import Monitor from '@lucide/svelte/icons/monitor';
const nav = [
{ href: '/', label: 'Обзор', icon: LayoutDashboard },
@@ -34,7 +47,7 @@
] as const;
let collapsed = $state(false);
let { children } = $props();
let { children, theme = $bindable<ThemePreference>('system') } = $props();
function isActive(href: string) {
const pathname = page.url.pathname;
@@ -60,6 +73,37 @@
<p class="text-sidebar-foreground/50 truncate text-xs">Control plane</p>
</div>
{/if}
<DropdownMenu>
<DropdownMenuTrigger
class={cn(
buttonVariants({ variant: 'ghost', size: 'icon-sm' }),
'text-sidebar-foreground/60 hover:text-sidebar-foreground shrink-0'
)}
aria-label="Тема оформления"
>
<span class="flex size-4 items-center justify-center">
<Sun class="size-4 dark:hidden" />
<Moon class="hidden size-4 dark:block" />
</span>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" class="w-44">
<DropdownMenuLabel>Тема</DropdownMenuLabel>
<DropdownMenuRadioGroup bind:value={theme}>
<DropdownMenuRadioItem value="light" class="gap-2">
<Sun class="size-4" />
Светлая
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="dark" class="gap-2">
<Moon class="size-4" />
Тёмная
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="system" class="gap-2">
<Monitor class="size-4" />
Как в системе
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<button
onclick={() => (collapsed = !collapsed)}
class={cn(
+25
View File
@@ -0,0 +1,25 @@
import { browser } from '$app/environment';
export const THEME_STORAGE_KEY = 'evobgp-theme';
export type ThemePreference = 'light' | 'dark' | 'system';
export function readTheme(): ThemePreference {
if (!browser) return 'system';
try {
const s = localStorage.getItem(THEME_STORAGE_KEY);
if (s === 'light' || s === 'dark' || s === 'system') return s;
} catch {
/* ignore */
}
return 'system';
}
/** Синхронизирует класс `dark` на `document.documentElement` с выбранным режимом. */
export function applyTheme(mode: ThemePreference): void {
if (!browser) return;
const dark =
mode === 'dark' ||
(mode === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
document.documentElement.classList.toggle('dark', dark);
}