Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions demo/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"vite": "^7.0.4"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^7.1.0",
"@wjfe/n-savant": "^0.12.0",
"@wjfe/n-savant-sk": "file:../wjfe-n-savant-sk-0.1.0.tgz"
}
Expand Down
95 changes: 63 additions & 32 deletions demo/src/lib/NavBar.svelte
Original file line number Diff line number Diff line change
@@ -1,37 +1,68 @@
<script lang="ts">
import theme from '$lib/state/theme.svelte.js';
import { calculateSkHref } from "@wjfe/n-savant-sk";
import { NavBar } from '$lib/bulma/NavBar';
import { calculateSkHref } from '@wjfe/n-savant-sk';
import ThemePicker from './ThemePicker.svelte';
import logo from '@wjfe/n-savant/logo64';
import { isSkRouteActive } from './isSkRouteActive';
import { location } from '@wjfe/n-savant';
import theme from './state/theme.svelte';

</script>
<nav class="has-background-info-light is-flex is-flex-direction-row is-align-items-center is-justify-content-space-between">
<ul>
<li><a href={calculateSkHref({ preserveQuery: true }, "/")}>Home</a></li>
<li><a href={calculateSkHref({ preserveQuery: true }, "/demo")}>Start Demo</a></li>
</ul>
<div>
<select bind:value={theme.current} class="select">
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="system">System</option>
</select>
</div>
</nav>

<style>
nav {
padding: 1rem;
border-bottom: 1px solid #dee2e6;
}
const allPositions = [undefined, 'top', 'bottom'] as const;
let posIndex = $state(0);
const posIconData = [
{
icon: 'fa-circle-dot',
title: 'Remove fixed position',
},
{
icon: 'fa-chevron-up',
title: 'Fix to top',
},
{
icon: 'fa-chevron-down',
title: 'Fix to bottom',
},
];
let iconDataIndex = $derived((posIndex + 1) % allPositions.length);
let navbarBg = $derived(theme.current === 'dark' ? 'is-dark' : 'is-light');

ul {
list-style: none;
display: flex;
gap: 1rem;
margin: 0;
padding: 0;
function nextPosition() {
posIndex = (posIndex + 1) % allPositions.length;
}
</script>

li {
margin: 0;
}
</style>
<NavBar.Root fixed={allPositions[posIndex]} class={navbarBg} style="padding-top: 0.5rem; padding-bottom: 0.5rem;">
<NavBar.Brand>
<NavBar.Item>
<img src={logo} alt="Logo" />
</NavBar.Item>
<NavBar.Item>
<h1 class="title is-4">@wjfe/n-savant-sk Demo</h1>
</NavBar.Item>
</NavBar.Brand>
<NavBar.Burger />
<NavBar.Menu>
<NavBar.Item tag="a" isTab isActive={isSkRouteActive('/')} href={calculateSkHref({ preserveQuery: true }, '/')}
>Home</NavBar.Item
>
<NavBar.Item
tag="a"
isTab
isActive={isSkRouteActive('/demo')}
href={calculateSkHref({ preserveQuery: true }, '/demo')}>Start Demo</NavBar.Item
>
{#snippet end()}
<NavBar.Item>
Sveltekit Path: <code>{location.url.pathname}</code>
</NavBar.Item>
<NavBar.Item>
<button type="button" title={posIconData[iconDataIndex].title} onclick={nextPosition}>
<i class={['fas', posIconData[iconDataIndex].icon]}></i>
</button>
</NavBar.Item>
<NavBar.Item>
<ThemePicker />
</NavBar.Item>
{/snippet}
</NavBar.Menu>
</NavBar.Root>
28 changes: 28 additions & 0 deletions demo/src/lib/ThemePicker.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script lang="ts">
import theme from "$lib/state/theme.svelte.js";
import { fly, slide } from "svelte/transition";

const themeIcons = {
light: "fa-sun",
dark: "fa-moon",
system: "fa-desktop",
};

let label = $derived(`${theme.current} theme`);
</script>

<button onclick={() => theme.nextTheme()} aria-label={label} title={label}>
{#key theme.current}
<i class={['fas', themeIcons[theme.current]]} transition:fly={{ y: 30, duration: 600 }}></i>
{/key}
</button>

<style>
button {
display: grid;
padding: 0.7em;
& > i {
grid-area: 1 / 1 / 2 / 2;
}
}
</style>
15 changes: 15 additions & 0 deletions demo/src/lib/bulma/NavBar/Brand.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";

type Props = HTMLAttributes<HTMLDivElement>;

let {
class: cssClass,
children,
...restProps
}: Props = $props();
</script>

<div class={["navbar-brand", cssClass]} {...restProps}>
{@render children?.()}
</div>
24 changes: 24 additions & 0 deletions demo/src/lib/bulma/NavBar/Burger.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script lang="ts">
import type { HTMLButtonAttributes } from "svelte/elements";

type Props = Omit<HTMLButtonAttributes, 'children'> & {
lineCount?: number;
};

let {
lineCount = 4,
class: cssClass,
...restProps
}: Props = $props();
</script>

<button
class={["navbar-burger", cssClass]}
{...restProps}
aria-label="menu"
aria-expanded="false"
>
{#each { length: lineCount } as _, i}
<span aria-hidden="true"></span>
{/each}
</button>
15 changes: 15 additions & 0 deletions demo/src/lib/bulma/NavBar/Item.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script lang="ts" generics="T extends 'a' | 'div' = 'div'">
import type { HTMLAttributes, HTMLAnchorAttributes } from 'svelte/elements';

type Props = (T extends 'a' ? HTMLAnchorAttributes : HTMLAttributes<HTMLDivElement>) & {
tag?: T;
isActive?: boolean;
isTab?: boolean;
};

let { tag = 'div' as T, isActive, isTab, class: cssClass, children, ...restProps }: Props = $props();
</script>

<svelte:element this={tag} class={['navbar-item', cssClass, { 'is-active': isActive, 'is-tab': isTab }]} {...restProps}>
{@render children?.()}
</svelte:element>
15 changes: 15 additions & 0 deletions demo/src/lib/bulma/NavBar/Link.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script lang="ts">
import type { HTMLAnchorAttributes } from "svelte/elements";

type Props = HTMLAnchorAttributes;

let {
class: cssClass,
children,
...restProps
}: Props = $props();
</script>

<a class={['navbar-link', cssClass]} {...restProps}>
{@render children?.()}
</a>
26 changes: 26 additions & 0 deletions demo/src/lib/bulma/NavBar/Menu.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script lang="ts">
import type { Snippet } from "svelte";
import type { HTMLAttributes } from "svelte/elements";

type Props = HTMLAttributes<HTMLDivElement> & {
end?: Snippet;
};

let {
class: cssClass,
end,
children,
...restProps
}: Props = $props();
</script>

<div class={["navbar-menu", cssClass]} {...restProps}>
<div class="navbar-start">
{@render children?.()}
</div>
{#if end}
<div class="navbar-end">
{@render end()}
</div>
{/if}
</div>
28 changes: 28 additions & 0 deletions demo/src/lib/bulma/NavBar/Root.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';

type Props = HTMLAttributes<HTMLElement> & {
fixed?: 'top' | 'bottom' | undefined;
transparent?: boolean;
};

let { fixed, transparent, class: cssClass, children, ...restProps }: Props = $props();

$effect(() => {
if (!fixed) {
return;
}
const appliedClass = `has-navbar-fixed-${fixed}`;
document.body.classList.add(appliedClass);
return () => {
document.body.classList.remove(appliedClass);
};
});
</script>

<nav
class={['navbar', { [`is-fixed-${fixed}`]: !!fixed, 'is-transparent': transparent }, cssClass]}
{...restProps}
>
{@render children?.()}
</nav>
15 changes: 15 additions & 0 deletions demo/src/lib/bulma/NavBar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Root from "./Root.svelte"
import Brand from "./Brand.svelte"
import Burger from "./Burger.svelte"
import Item from "./Item.svelte"
import Menu from "./Menu.svelte"
import Link from "./Link.svelte"

export const NavBar = {
Root,
Brand,
Burger,
Menu,
Item,
Link
};
7 changes: 5 additions & 2 deletions demo/src/lib/demo/Demo.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@

<Section>
<header class="is-flex is-justify-content-space-between">
<span><strong>Path (Sveltekit):</strong> <code>{location.url.pathname}</code></span>
<span><strong>Hash path:</strong> <code>{hashPath}</code></span>
<span>&nbsp;</span>
<span>
<strong>Hash path:</strong>
<code>{typeof hash === 'string' ? `${hash}=` : ''}{hashPath || '/'}</code>
</span>
</header>
<div class="box">
<Router {hash}>
Expand Down
5 changes: 5 additions & 0 deletions demo/src/lib/isSkRouteActive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { page } from "$app/state";

export function isSkRouteActive(route: string): boolean {
return page.url.pathname === route;
}
35 changes: 34 additions & 1 deletion demo/src/lib/state/theme.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,38 @@
const themeOptions = ['system', 'light', 'dark'] as const;

type ThemeIndex = 0 | 1 | 2;

function indexOfTheme(theme: 'system' | 'light' | 'dark'): ThemeIndex {
const index = themeOptions.indexOf(theme);
if (index === -1) {
throw new Error(`Invalid theme: ${theme}`);
}
return index as ThemeIndex;
}

export class ThemeState {
current: 'light' | 'dark' | 'system' = $state('system');
#current: ThemeIndex = $state(0);

get current() {
return themeOptions[this.#current];
}
set current(value: 'light' | 'dark' | 'system') {
this.#current = indexOfTheme(value);
this.#updateTheme();
}

#updateTheme() {
if (this.#current === 0) {
document.documentElement.removeAttribute('data-theme');
return;
}
document.documentElement.setAttribute('data-theme', themeOptions[this.#current]);
}

nextTheme() {
this.#current = (this.#current + 1) % themeOptions.length as ThemeIndex;
this.#updateTheme();
}
}

export default new ThemeState();
2 changes: 1 addition & 1 deletion demo/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<script lang="ts">
import '@fortawesome/fontawesome-free/css/all.css';
import favicon from '$lib/assets/favicon.svg';
import './main.scss';
import { init, type SkInitOptions } from '@wjfe/n-savant-sk';
import { location } from '@wjfe/n-savant';
import NavBar from '$lib/NavBar.svelte';
import theme from '$lib/state/theme.svelte.js';
import { page } from '$app/state';
Expand Down