mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-26 20:59:13 +01:00
frontend(nav): add navbar, themeToggle, theme store
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"imports": {
|
||||
"$config": "./src/utils/config/config.ts"
|
||||
"$config": "./src/utils/config/config.ts",
|
||||
"$stores": "./src/stores",
|
||||
"$components": "./src/components",
|
||||
"$static": "./src/static"
|
||||
},
|
||||
"tasks": {
|
||||
"dev": "APP_BASE_PATH=./temp vite dev",
|
||||
|
||||
8
deno.lock
generated
8
deno.lock
generated
@@ -12,6 +12,7 @@
|
||||
"npm:eslint-plugin-svelte@^3.12.4": "3.12.4_eslint@9.37.0_svelte@5.40.2__acorn@8.15.0_postcss@8.5.6",
|
||||
"npm:eslint@^9.36.0": "9.37.0",
|
||||
"npm:globals@^16.4.0": "16.4.0",
|
||||
"npm:lucide-svelte@0.546": "0.546.0_svelte@5.40.2__acorn@8.15.0",
|
||||
"npm:prettier-plugin-svelte@^3.4.0": "3.4.0_prettier@3.6.2_svelte@5.40.2__acorn@8.15.0",
|
||||
"npm:prettier-plugin-tailwindcss@~0.6.14": "0.6.14_prettier@3.6.2_prettier-plugin-svelte@3.4.0__prettier@3.6.2__svelte@5.40.2___acorn@8.15.0_svelte@5.40.2__acorn@8.15.0",
|
||||
"npm:prettier@^3.6.2": "3.6.2",
|
||||
@@ -1369,6 +1370,12 @@
|
||||
"lodash.merge@4.6.2": {
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
|
||||
},
|
||||
"lucide-svelte@0.546.0_svelte@5.40.2__acorn@8.15.0": {
|
||||
"integrity": "sha512-vCvBUlFapD59ivX1b/i7wdUadSgC/3gQGvrGEZjSecOlThT+UR+X5UxdVEakHuhniTrSX0nJ2WrY5r25SVDtyQ==",
|
||||
"dependencies": [
|
||||
"svelte"
|
||||
]
|
||||
},
|
||||
"magic-string@0.30.19": {
|
||||
"integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
|
||||
"dependencies": [
|
||||
@@ -1817,6 +1824,7 @@
|
||||
"npm:eslint-plugin-svelte@^3.12.4",
|
||||
"npm:eslint@^9.36.0",
|
||||
"npm:globals@^16.4.0",
|
||||
"npm:lucide-svelte@0.546",
|
||||
"npm:prettier-plugin-svelte@^3.4.0",
|
||||
"npm:prettier-plugin-tailwindcss@~0.6.14",
|
||||
"npm:prettier@^3.6.2",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"version": "2.0.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"lucide-svelte": "^0.546.0",
|
||||
"sveltekit-adapter-deno": "^0.16.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
11
src/app.css
11
src/app.css
@@ -1,2 +1,13 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap');
|
||||
@import 'tailwindcss';
|
||||
@plugin '@tailwindcss/forms';
|
||||
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
@theme {
|
||||
--font-sans: 'DM Sans', ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: 'DM Sans', ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
|
||||
17
src/components/navigation/navbar/navbar.svelte
Normal file
17
src/components/navigation/navbar/navbar.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import ThemeToggle from './themeToggle.svelte';
|
||||
import logo from '$static/logo.svg';
|
||||
</script>
|
||||
|
||||
<nav class="border-b border-neutral-200 bg-neutral-50 dark:border-neutral-800 dark:bg-neutral-900">
|
||||
<div class="flex items-center justify-between px-4 py-4">
|
||||
<!-- Left: Brand name with logo -->
|
||||
<div class="flex items-center gap-2">
|
||||
<img src={logo} alt="Profilarr logo" class="h-5 w-5 translate-y-[3px]" />
|
||||
<div class="text-xl font-bold text-neutral-900 dark:text-neutral-100">profilarr</div>
|
||||
</div>
|
||||
|
||||
<!-- Right: Theme toggle -->
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</nav>
|
||||
30
src/components/navigation/navbar/themeToggle.svelte
Normal file
30
src/components/navigation/navbar/themeToggle.svelte
Normal file
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { themeStore } from '$stores/theme.ts';
|
||||
import { Moon, Sun } from 'lucide-svelte';
|
||||
|
||||
$: isDark = $themeStore === 'dark';
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click={() => themeStore.toggle()}
|
||||
class="relative flex h-9 w-9 items-center justify-center rounded-md transition-colors hover:bg-neutral-200 dark:hover:bg-neutral-800"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
<!-- Sun icon (visible in dark mode) -->
|
||||
<div
|
||||
class="absolute transition-all duration-300 {isDark
|
||||
? 'rotate-0 scale-100 opacity-100'
|
||||
: 'rotate-180 scale-75 opacity-0'}"
|
||||
>
|
||||
<Sun class="h-[18px] w-[18px] text-neutral-700 dark:text-neutral-300" />
|
||||
</div>
|
||||
|
||||
<!-- Moon icon (visible in light mode) -->
|
||||
<div
|
||||
class="absolute transition-all duration-300 {isDark
|
||||
? '-rotate-180 scale-75 opacity-0'
|
||||
: 'rotate-0 scale-100 opacity-100'}"
|
||||
>
|
||||
<Moon class="h-[18px] w-[18px] text-neutral-700 dark:text-neutral-300" />
|
||||
</div>
|
||||
</button>
|
||||
@@ -1,8 +1,7 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import logo from '../static/logo.svg';
|
||||
|
||||
let { children } = $props();
|
||||
import logo from '$static/logo.svg';
|
||||
import Navbar from '$components/navigation/navbar/navbar.svelte';
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -10,4 +9,6 @@
|
||||
<title>Profilarr</title>
|
||||
</svelte:head>
|
||||
|
||||
{@render children?.()}
|
||||
<Navbar />
|
||||
|
||||
<slot />
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
<h1>Welcome to SvelteKit</h1>
|
||||
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
|
||||
<h1>wip</h1>
|
||||
55
src/stores/theme.ts
Normal file
55
src/stores/theme.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Theme store for light/dark mode
|
||||
*/
|
||||
|
||||
import { writable } from 'svelte/store';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
type Theme = 'light' | 'dark';
|
||||
|
||||
function createThemeStore() {
|
||||
// Initialize theme from localStorage or system preference
|
||||
let initialTheme: Theme = 'dark';
|
||||
if (browser) {
|
||||
const stored = localStorage.getItem('theme') as Theme | null;
|
||||
if (stored) {
|
||||
initialTheme = stored;
|
||||
} else {
|
||||
initialTheme = globalThis.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
? 'dark'
|
||||
: 'light';
|
||||
}
|
||||
}
|
||||
|
||||
const { subscribe, set, update } = writable<Theme>(initialTheme);
|
||||
|
||||
// Apply theme on initialization
|
||||
if (browser) {
|
||||
applyTheme(initialTheme);
|
||||
}
|
||||
|
||||
function applyTheme(newTheme: Theme) {
|
||||
if (browser) {
|
||||
document.documentElement.classList.remove('light', 'dark');
|
||||
document.documentElement.classList.add(newTheme);
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
update((current) => {
|
||||
const newTheme = current === 'light' ? 'dark' : 'light';
|
||||
applyTheme(newTheme);
|
||||
if (browser) {
|
||||
localStorage.setItem('theme', newTheme);
|
||||
}
|
||||
return newTheme;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
toggle
|
||||
};
|
||||
}
|
||||
|
||||
export const themeStore = createThemeStore();
|
||||
@@ -10,7 +10,10 @@ const config = {
|
||||
usage: 'deno-compile'
|
||||
}),
|
||||
alias: {
|
||||
$config: './src/utils/config/config.ts'
|
||||
$config: './src/utils/config/config.ts',
|
||||
$stores: './src/stores',
|
||||
$components: './src/components',
|
||||
$static: './src/static'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user