diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 215fb91..0846f82 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,7 +18,7 @@ }, "devDependencies": { "@internationalized/date": "^3.12.1", - "@lucide/svelte": "^1.16.0", + "@lucide/svelte": "^1.42.0", "@sveltejs/adapter-node": "^5.5.7", "@sveltejs/kit": "^2.43.2", "@sveltejs/vite-plugin-svelte": "^6.2.0", @@ -32,7 +32,7 @@ "svelte": "^5.39.5", "svelte-check": "^4.3.2", "tailwind-merge": "^3.3.1", - "tailwind-variants": "^3.2.2", + "tailwind-variants": "^3.3.1", "tailwindcss": "^4.1.13", "tw-animate-css": "^1.4.0", "typescript": "^5.9.2", @@ -812,9 +812,9 @@ } }, "node_modules/@lucide/svelte": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-1.16.0.tgz", - "integrity": "sha512-AvvPJnaWxeiNkAljI5MsSEc84yHPLMaWQIAJOcbX7k9au/f9ITS7cxTTQiautDiOFKVOXiYdZ+d6mtl88J+Kbg==", + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-1.42.0.tgz", + "integrity": "sha512-9vOi3EdzJht5aD7F5OraoxrK2GyyL1Y+kVXEtIg4+IAdKmDBrwYK2d/x7akC89uA+cvkfjcw+IuzH0vfd/TuzA==", "dev": true, "license": "ISC", "peerDependencies": { @@ -5737,13 +5737,13 @@ } }, "node_modules/tailwind-variants": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-3.2.2.tgz", - "integrity": "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-3.3.1.tgz", + "integrity": "sha512-4pAvwUtM4HKBiRZftncAbpn6V9Hhwoa5Fl7O2u5zbp7Z5Cvu+/o/6+176WY3WCEES209543quG8zFIcXCsc5Jw==", "dev": true, "license": "MIT", "engines": { - "node": ">=16.x", + "node": ">=16.9.x", "pnpm": ">=7.x" }, "peerDependencies": { @@ -5753,6 +5753,9 @@ "peerDependenciesMeta": { "tailwind-merge": { "optional": true + }, + "tailwindcss": { + "optional": true } } }, diff --git a/frontend/package.json b/frontend/package.json index 2c27b66..8b768e0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@internationalized/date": "^3.12.1", - "@lucide/svelte": "^1.16.0", + "@lucide/svelte": "^1.42.0", "@sveltejs/adapter-node": "^5.5.7", "@sveltejs/kit": "^2.43.2", "@sveltejs/vite-plugin-svelte": "^6.2.0", @@ -33,7 +33,7 @@ "svelte": "^5.39.5", "svelte-check": "^4.3.2", "tailwind-merge": "^3.3.1", - "tailwind-variants": "^3.2.2", + "tailwind-variants": "^3.3.1", "tailwindcss": "^4.1.13", "tw-animate-css": "^1.4.0", "typescript": "^5.9.2", diff --git a/frontend/src/lib/components/TeamMenu.svelte b/frontend/src/lib/components/TeamMenu.svelte index 72ff5e9..6bb86a4 100644 --- a/frontend/src/lib/components/TeamMenu.svelte +++ b/frontend/src/lib/components/TeamMenu.svelte @@ -4,18 +4,28 @@ * Ueberfahren aufklappt: darunter der Wechsel zwischen den eigenen Teams * und der Weg zu einem neuen. * - * Der Punkt selbst ist ein Link auf die Teamseite. Ein eigener Eintrag - * „Uebersicht" waere die zweite Tuer zu derselben Seite, und eine - * Ueberschrift ueber drei Teamnamen sagt nichts, was die Namen nicht - * schon sagen. + * Traegt bits-uis NavigationMenu und nicht das DropdownMenu. Ein Dropdown + * ist ein Menue, das auf Ansage aufgeht; eine Navigationsleiste ist etwas + * anderes, und die Unterschiede sind genau die, die hier gebraucht werden: + * Aufklappen beim Ueberfahren samt Verzoegerung, ein Klick, der nach dem + * Hover-Aufklappen nicht wieder zuklappt, kein Fokusfang, keine + * Scrollsperre, kein Fokusring nach dem Zeigerbesuch. Das alles stand hier + * vorher als Handarbeit — Zeitgeber, ueberschriebene Zeigerhandler, + * unterdrueckter Fokus. Die Komponente kann es von sich aus. + * + * Der Punkt selbst bleibt ein Link auf die Teamseite: Der Trigger wird + * ueber das child-Snippet als gezeichnet. bits-ui haelt seinen eigenen + * onclick zurueck, wenn per Zeiger aufgeklappt wurde („if opened via + * pointer move, we prevent the click event"), der Klick landet also beim + * Link. * * Auf dem Touchscreen gibt es kein Ueberfahren: Dort fuehrt der Tipp auf - * die Teamseite, und gewechselt wird in deren Liste „Meine Teams". + * die Teamseite, und gewechselt wird in deren Liste. */ import { goto } from '$app/navigation' import { getTeamContext } from '$lib/stores/teams.svelte' import CreateTeamDialog from '$lib/components/CreateTeamDialog.svelte' - import * as DropdownMenu from '@/components/ui/dropdown-menu' + import * as NavigationMenu from '@/components/ui/navigation-menu' import { ChevronDown, Plus, Users } from 'lucide-svelte' let { @@ -26,21 +36,6 @@ const teams = getTeamContext() let createDialog = $state(false) - let menuOpen = $state(false) - - // Zwischen Knopf und aufgeklapptem Menue liegt ein Spalt. Ohne die kurze - // Frist fiele das Menue zu, sobald der Zeiger ihn ueberquert. - let closeTimer: ReturnType | null = null - - function enter() { - if (closeTimer) clearTimeout(closeTimer) - menuOpen = true - } - - function leave() { - if (closeTimer) clearTimeout(closeTimer) - closeTimer = setTimeout(() => (menuOpen = false), 150) - } /** * Team wechseln heisst hier auch: hinsehen. Der Wechsel allein aendert @@ -52,86 +47,49 @@ goto('/dashboard/team') onnavigate?.() } + + const itemClass = + 'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm text-left transition-colors hover:bg-accent' - - + + {#snippet child({ props })} - - { - menuOpen = false - onnavigate?.() - }} - > + onnavigate?.()}> Team {/snippet} - + - - e.preventDefault()} - onCloseAutoFocus={(e) => e.preventDefault()} - onmouseenter={enter} - onmouseleave={leave} - > + {#each teams.records as t (t.id)} - switchTo(t.id)} - class={t.id === teams.activeId ? 'bg-accent' : ''} + class="{itemClass} {t.id === teams.activeId ? 'bg-accent' : ''}" > - {t.name} + {t.name} {#if teams.isOwner(t)} Owner {:else if teams.isAdmin(t)} Admin {/if} - + {:else} - Noch in keinem Team +

Noch in keinem Team

{/each} - - (createDialog = true)}> - - Team erstellen - -
- +
- + + + + + diff --git a/frontend/src/lib/components/ui/navigation-menu/index.ts b/frontend/src/lib/components/ui/navigation-menu/index.ts new file mode 100644 index 0000000..208cb93 --- /dev/null +++ b/frontend/src/lib/components/ui/navigation-menu/index.ts @@ -0,0 +1,28 @@ +import Content from "./navigation-menu-content.svelte"; +import Indicator from "./navigation-menu-indicator.svelte"; +import Item from "./navigation-menu-item.svelte"; +import Link from "./navigation-menu-link.svelte"; +import List from "./navigation-menu-list.svelte"; +import Trigger from "./navigation-menu-trigger.svelte"; +import Viewport from "./navigation-menu-viewport.svelte"; +import Root from "./navigation-menu.svelte"; + +export { + Root, + Content, + Indicator, + Item, + Link, + List, + Trigger, + Viewport, + // + Root as NavigationMenuRoot, + Content as NavigationMenuContent, + Indicator as NavigationMenuIndicator, + Item as NavigationMenuItem, + Link as NavigationMenuLink, + List as NavigationMenuList, + Trigger as NavigationMenuTrigger, + Viewport as NavigationMenuViewport, +}; diff --git a/frontend/src/lib/components/ui/navigation-menu/navigation-menu-content.svelte b/frontend/src/lib/components/ui/navigation-menu/navigation-menu-content.svelte new file mode 100644 index 0000000..0b6b6c5 --- /dev/null +++ b/frontend/src/lib/components/ui/navigation-menu/navigation-menu-content.svelte @@ -0,0 +1,20 @@ + + + diff --git a/frontend/src/lib/components/ui/navigation-menu/navigation-menu-indicator.svelte b/frontend/src/lib/components/ui/navigation-menu/navigation-menu-indicator.svelte new file mode 100644 index 0000000..f4cf060 --- /dev/null +++ b/frontend/src/lib/components/ui/navigation-menu/navigation-menu-indicator.svelte @@ -0,0 +1,22 @@ + + + +
+
diff --git a/frontend/src/lib/components/ui/navigation-menu/navigation-menu-item.svelte b/frontend/src/lib/components/ui/navigation-menu/navigation-menu-item.svelte new file mode 100644 index 0000000..4760458 --- /dev/null +++ b/frontend/src/lib/components/ui/navigation-menu/navigation-menu-item.svelte @@ -0,0 +1,17 @@ + + + diff --git a/frontend/src/lib/components/ui/navigation-menu/navigation-menu-link.svelte b/frontend/src/lib/components/ui/navigation-menu/navigation-menu-link.svelte new file mode 100644 index 0000000..742d9bd --- /dev/null +++ b/frontend/src/lib/components/ui/navigation-menu/navigation-menu-link.svelte @@ -0,0 +1,17 @@ + + + diff --git a/frontend/src/lib/components/ui/navigation-menu/navigation-menu-list.svelte b/frontend/src/lib/components/ui/navigation-menu/navigation-menu-list.svelte new file mode 100644 index 0000000..6f36137 --- /dev/null +++ b/frontend/src/lib/components/ui/navigation-menu/navigation-menu-list.svelte @@ -0,0 +1,20 @@ + + + diff --git a/frontend/src/lib/components/ui/navigation-menu/navigation-menu-trigger.svelte b/frontend/src/lib/components/ui/navigation-menu/navigation-menu-trigger.svelte new file mode 100644 index 0000000..64e269a --- /dev/null +++ b/frontend/src/lib/components/ui/navigation-menu/navigation-menu-trigger.svelte @@ -0,0 +1,29 @@ + + + + + + {@render children?.()} + diff --git a/frontend/src/lib/components/ui/navigation-menu/navigation-menu-viewport.svelte b/frontend/src/lib/components/ui/navigation-menu/navigation-menu-viewport.svelte new file mode 100644 index 0000000..b2ce064 --- /dev/null +++ b/frontend/src/lib/components/ui/navigation-menu/navigation-menu-viewport.svelte @@ -0,0 +1,22 @@ + + +
+ +
diff --git a/frontend/src/lib/components/ui/navigation-menu/navigation-menu.svelte b/frontend/src/lib/components/ui/navigation-menu/navigation-menu.svelte new file mode 100644 index 0000000..27cd0d6 --- /dev/null +++ b/frontend/src/lib/components/ui/navigation-menu/navigation-menu.svelte @@ -0,0 +1,31 @@ + + + + {@render children?.()} + {#if viewport} + + {/if} + diff --git a/frontend/src/routes/dashboard/+layout.svelte b/frontend/src/routes/dashboard/+layout.svelte index 8a350e1..1d48acf 100644 --- a/frontend/src/routes/dashboard/+layout.svelte +++ b/frontend/src/routes/dashboard/+layout.svelte @@ -20,9 +20,11 @@ import { Separator } from '@/components/ui/separator' import Logo from '$lib/components/Logo.svelte' import TeamMenu from '$lib/components/TeamMenu.svelte' + import CreateTeamDialog from '$lib/components/CreateTeamDialog.svelte' + import * as NavigationMenu from '@/components/ui/navigation-menu' import Avatar from '$lib/components/Avatar.svelte' import RunningTimesToast from '$lib/components/RunningTimesToast.svelte' - import { Home, Calendar, LogOut, Menu, X, Moon, Sun, Route, Settings, Users } from 'lucide-svelte' + import { Home, Calendar, LogOut, Menu, X, Moon, Sun, Plus, Route, Settings, Users } from 'lucide-svelte' import { toggleMode, mode } from 'mode-watcher' import { onDestroy, onMount } from 'svelte' @@ -47,6 +49,10 @@ let isMobileMenuOpen = $state(false) + // Der Dialog gehoert nicht ins mobile Menue: Das schliesst sich beim + // Klick, und der Dialog verschwaende mit ihm. + let createTeamDialog = $state(false) + onMount(async () => { if (!auth.user) { goto('/login', { replaceState: true }) @@ -180,24 +186,34 @@ - + + {#if item.submenu} - (isMobileMenuOpen = false)} - /> - {:else} - (isMobileMenuOpen = false)} - class="w-full {navClass(item.href)}" - > - - {item.label} - +
+ {#each teams.records as t (t.id)} + + {/each} + +
{/if} {/each} @@ -313,5 +363,7 @@ + + {/if}