frontend(nav): add navbar, themeToggle, theme store

This commit is contained in:
Sam Chau
2025-10-18 04:37:47 +10:30
parent f3379b9ea4
commit 24a20fcf76
10 changed files with 136 additions and 8 deletions

View File

@@ -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
View File

@@ -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",

View File

@@ -4,6 +4,7 @@
"version": "2.0.0",
"type": "module",
"dependencies": {
"lucide-svelte": "^0.546.0",
"sveltekit-adapter-deno": "^0.16.1"
},
"devDependencies": {

View File

@@ -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;
}

View 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>

View 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>

View File

@@ -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 />

View File

@@ -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
View 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();

View File

@@ -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'
}
}
};