started implementing navbar
This commit is contained in:
5
index.js
5
index.js
@@ -115,7 +115,7 @@ async function main() {
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
const rootDir = path.resolve(process.cwd(), kebabName);
|
||||
const rootDir = path.join(process.cwd(), kebabName);
|
||||
const pmx = project.packageManager === 'bun' ? 'bun x' : 'npx';
|
||||
const pm = project.packageManager === 'bun' ? 'bun' : 'npm';
|
||||
const studioDir = path.join(rootDir, 'apps', 'studio');
|
||||
@@ -124,8 +124,7 @@ async function main() {
|
||||
{
|
||||
title: `${color.yellow('📁 Copying template contents to root')}`,
|
||||
task: async () => {
|
||||
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
||||
const templateDir = path.resolve(__dirname, 'template');
|
||||
const templateDir = path.join(process.cwd(), 'template');
|
||||
|
||||
// Ensure root directory exists with proper permissions
|
||||
await fs.ensureDir(rootDir);
|
||||
|
||||
129
template/apps/client/src/lib/components/navbar.svelte
Normal file
129
template/apps/client/src/lib/components/navbar.svelte
Normal 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>
|
||||
@@ -13,8 +13,8 @@
|
||||
bind:ref
|
||||
data-slot="navigation-menu-content"
|
||||
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",
|
||||
"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",
|
||||
"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-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
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
bind:ref
|
||||
data-slot="navigation-menu-link"
|
||||
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
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { tv } from "tailwind-variants";
|
||||
|
||||
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>
|
||||
|
||||
@@ -22,7 +22,11 @@
|
||||
<NavigationMenuPrimitive.Trigger
|
||||
bind:ref
|
||||
data-slot="navigation-menu-trigger"
|
||||
class={cn(navigationMenuTriggerStyle(), "group", className)}
|
||||
class={cn(
|
||||
navigationMenuTriggerStyle(),
|
||||
"group",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import {VisualEditing} from '@sanity/visual-editing/svelte'
|
||||
import {LiveMode} from '@sanity/svelte-loader'
|
||||
import {client} from '$lib/sanity'
|
||||
import Footer from '$lib/components/footer.svelte'
|
||||
import '../app.css'
|
||||
|
||||
import { VisualEditing } from '@sanity/visual-editing/svelte';
|
||||
import { LiveMode } from '@sanity/svelte-loader';
|
||||
import { client } from '$lib/sanity';
|
||||
import Footer from '$lib/components/footer.svelte';
|
||||
import '../app.css';
|
||||
import Navbar, { type NavigationItem } from '$lib/components/navbar.svelte';
|
||||
let { children, data } = $props();
|
||||
</script>
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
</svelte:head>
|
||||
|
||||
|
||||
<div class="scroll-smooth font-sans antialiased">
|
||||
<Navbar />
|
||||
{@render children()}
|
||||
{#if data.settings}
|
||||
<Footer settings={data.settings} />
|
||||
|
||||
1
template/apps/client/src/routes/test/+page.svelte
Normal file
1
template/apps/client/src/routes/test/+page.svelte
Normal file
@@ -0,0 +1 @@
|
||||
<p>Test</p>
|
||||
Reference in New Issue
Block a user