started implementing navbar

This commit is contained in:
2025-08-18 00:36:52 +02:00
parent 38b48453f0
commit 48b3c8170b
7 changed files with 176 additions and 42 deletions

View File

@@ -115,8 +115,8 @@ async function main() {
.toLowerCase() .toLowerCase()
.replace(/[^a-z0-9]+/g, '-') .replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, ''); .replace(/^-+|-+$/g, '');
const rootDir = path.resolve(process.cwd(), kebabName); const rootDir = path.join(process.cwd(), kebabName);
const pmx = project.packageManager === 'bun' ? 'bunx' : 'npx'; const pmx = project.packageManager === 'bun' ? 'bun x' : 'npx';
const pm = project.packageManager === 'bun' ? 'bun' : 'npm'; const pm = project.packageManager === 'bun' ? 'bun' : 'npm';
const studioDir = path.join(rootDir, 'apps', 'studio'); const studioDir = path.join(rootDir, 'apps', 'studio');
@@ -124,8 +124,7 @@ async function main() {
{ {
title: `${color.yellow('📁 Copying template contents to root')}`, title: `${color.yellow('📁 Copying template contents to root')}`,
task: async () => { task: async () => {
const __dirname = path.dirname(new URL(import.meta.url).pathname); const templateDir = path.join(process.cwd(), 'template');
const templateDir = path.resolve(__dirname, 'template');
// Ensure root directory exists with proper permissions // Ensure root directory exists with proper permissions
await fs.ensureDir(rootDir); await fs.ensureDir(rootDir);

View File

@@ -0,0 +1,129 @@
<script context="module" lang="ts">
export interface NavigationSubItem {
name: string;
url: string;
}
export interface NavigationItem {
name: string;
url?: string;
subitems?: NavigationSubItem[];
}
</script>
<script lang="ts">
import {
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuRoot,
NavigationMenuTrigger
} from '$lib/components/ui/navigation-menu';
import { cn } from '$lib/utils';
import { navigationMenuTriggerStyle } from './ui/navigation-menu/navigation-menu-trigger.svelte';
import { page } from '$app/stores';
import { onMount, tick } from 'svelte';
import { tweened } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
export let items: NavigationItem[] = [
{ name: 'Home', url: '/' },
{ name: 'Test', url: '/test' },
{
name: 'Test Subitems',
subitems: [
{ name: 'Test Page', url: '#' }
]
}
];
let activeIndex = 0;
let navigationList: HTMLElement;
const indicatorPosition = tweened({ left: 0, width: 0 }, {
duration: 300,
easing: cubicOut
});
$: {
const currentPath = $page.url.pathname;
let newActiveIndex = -1;
items.forEach((item, index) => {
if (item.url === currentPath) {
newActiveIndex = index;
} else if (item.subitems) {
const hasActiveSubitem = item.subitems.some(subitem =>
currentPath === subitem.url || currentPath.startsWith(subitem.url + '/')
);
if (hasActiveSubitem) {
newActiveIndex = index;
}
}
});
if (newActiveIndex !== -1) {
activeIndex = newActiveIndex;
tick().then(() => updateIndicator());
}
}
function updateIndicator() {
if (!navigationList || activeIndex === -1) return;
const menuItems = navigationList.querySelectorAll('[data-navigation-menu-item]');
const activeMenuItem = menuItems[activeIndex];
if (activeMenuItem) {
const activeButton = activeMenuItem.querySelector('a, button');
if (activeButton) {
const listRect = navigationList.getBoundingClientRect();
const buttonRect = activeButton.getBoundingClientRect();
indicatorPosition.set({
left: buttonRect.left - listRect.left,
width: buttonRect.width
});
}
}
}
onMount(() => {
setTimeout(updateIndicator, 200);
});
</script>
<NavigationMenuRoot class="px-2 py-1 min-w-full bg-primary" viewport={false}>
<div bind:this={navigationList}>
<NavigationMenuList class="relative">
<div
class="absolute top-0 h-full bg-white/10 rounded-md pointer-events-none z-10"
style="left: {$indicatorPosition.left}px; width: {$indicatorPosition.width}px;"
></div>
{#each items as item}
<NavigationMenuItem data-navigation-menu-item>
{#if item.url && !item.subitems}
<NavigationMenuLink
class={cn(navigationMenuTriggerStyle(), "relative z-20")}
href={item.url}
>
{item.name}
</NavigationMenuLink>
{:else if item.subitems}
<NavigationMenuTrigger class="relative z-20">
{item.name}
</NavigationMenuTrigger>
<NavigationMenuContent class="absolute left-1/2 -translate-x-1/2 mt-2 z-50 min-w-full border border-white/20 bg-primary shadow-lg rounded-md">
{#each item.subitems as subitem}
<NavigationMenuLink href={subitem.url}>
{subitem.name}
</NavigationMenuLink>
{/each}
</NavigationMenuContent>
{/if}
</NavigationMenuItem>
{/each}
</NavigationMenuList>
</div>
</NavigationMenuRoot>

View File

@@ -13,8 +13,8 @@
bind:ref bind:ref
data-slot="navigation-menu-content" data-slot="navigation-menu-content"
class={cn( class={cn(
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 left-0 top-0 w-full md:absolute md:w-auto", "data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 left-0 top-0 w-full md:absolute md:w-auto md:left-1/2 md:-translate-x-1/2",
"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200", "group-data-[viewport=false]/navigation-menu:bg-primary group-data-[viewport=false]/navigation-menu:text-white group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border-white/20 group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200",
className className
)} )}
{...restProps} {...restProps}

View File

@@ -13,7 +13,7 @@
bind:ref bind:ref
data-slot="navigation-menu-link" data-slot="navigation-menu-link"
class={cn( class={cn(
"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm outline-none transition-all focus-visible:outline-1 focus-visible:ring-[3px] [&_svg:not([class*='size-'])]:size-4", "data-[active=true]:focus:bg-white/5 cursor-pointer data-[active=true]:hover:bg-white/5 data-[active=true]:bg-primary data-[active=true]:text-white hover:bg-white/5 hover:text-white focus:bg-white/5 focus:text-white active:bg-white/8 focus-visible:ring-white/20 [&_svg:not([class*='text-'])]:text-white flex flex-col gap-1 rounded-sm p-2 text-sm text-white outline-none transition-all focus-visible:outline-1 focus-visible:ring-[3px] [&_svg:not([class*='size-'])]:size-4",
className className
)} )}
{...restProps} {...restProps}

View File

@@ -3,7 +3,7 @@
import { tv } from "tailwind-variants"; import { tv } from "tailwind-variants";
export const navigationMenuTriggerStyle = tv({ export const navigationMenuTriggerStyle = tv({
base: "bg-background hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 group inline-flex h-9 w-max items-center justify-center rounded-md px-4 py-2 text-sm font-medium outline-none transition-[color,box-shadow] focus-visible:outline-1 focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50", base: "text-white hover:bg-white/5 hover:text-white focus:bg-white/5 focus:text-white data-[state=open]:hover:bg-white/5 data-[state=open]:text-white data-[state=open]:focus:bg-white/5 data-[state=open]:bg-primary active:bg-white/8 focus-visible:ring-white/20 group inline-flex h-9 w-max items-center justify-center rounded-md px-4 py-2 text-sm font-medium outline-none transition-[color,box-shadow] focus-visible:outline-1 focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50",
}); });
</script> </script>
@@ -22,7 +22,11 @@
<NavigationMenuPrimitive.Trigger <NavigationMenuPrimitive.Trigger
bind:ref bind:ref
data-slot="navigation-menu-trigger" data-slot="navigation-menu-trigger"
class={cn(navigationMenuTriggerStyle(), "group", className)} class={cn(
navigationMenuTriggerStyle(),
"group",
className
)}
{...restProps} {...restProps}
> >
{@render children?.()} {@render children?.()}

View File

@@ -1,35 +1,36 @@
<script lang="ts"> <script lang="ts">
import {VisualEditing} from '@sanity/visual-editing/svelte' import { VisualEditing } from '@sanity/visual-editing/svelte';
import {LiveMode} from '@sanity/svelte-loader' import { LiveMode } from '@sanity/svelte-loader';
import {client} from '$lib/sanity' import { client } from '$lib/sanity';
import Footer from '$lib/components/footer.svelte' import Footer from '$lib/components/footer.svelte';
import '../app.css' import '../app.css';
import Navbar, { type NavigationItem } from '$lib/components/navbar.svelte';
let { children, data } = $props();
</script>
let {children, data} = $props(); <svelte:head>
<title>{data.settings?.title || 'Website'}</title>
<meta name="description" content={data.settings?.description || ''} />
<meta property="og:title" content={data.settings?.longTitle || data.settings?.title || ''} />
<meta property="og:description" content={data.settings?.description || ''} />
<meta name="twitter:title" content={data.settings?.longTitle || data.settings?.title || ''} />
<meta name="twitter:description" content={data.settings?.description || ''} />
<meta name="robots" content="index, follow" />
{#if data.logo}
<link rel="icon" href={data.logo.url} />
<link rel="apple-touch-icon" href={data.logo.url} />
{/if}
<link rel="manifest" href="/manifest.webmanifest" />
</svelte:head>
<div class="scroll-smooth font-sans antialiased">
<svelte:head> <Navbar />
<title>{data.settings?.title || 'Website'}</title> {@render children()}
<meta name="description" content={data.settings?.description || ''} /> {#if data.settings}
<meta property="og:title" content={data.settings?.longTitle || data.settings?.title || ''} /> <Footer settings={data.settings} />
<meta property="og:description" content={data.settings?.description || ''} /> {/if}
<meta name="twitter:title" content={data.settings?.longTitle || data.settings?.title || ''} /> {#if data.preview}
<meta name="twitter:description" content={data.settings?.description || ''} /> <VisualEditing />
<meta name="robots" content="index, follow" /> <LiveMode {client} />
{#if data.logo} {/if}
<link rel="icon" href={data.logo.url} /> </div>
<link rel="apple-touch-icon" href={data.logo.url} />
{/if}
<link rel="manifest" href="/manifest.webmanifest" />
</svelte:head>
<div class="scroll-smooth antialiased font-sans">
{@render children()}
{#if data.settings}
<Footer settings={data.settings} />
{/if}
{#if data.preview}
<VisualEditing />
<LiveMode {client} />
{/if}

View File

@@ -0,0 +1 @@
<p>Test</p>