🚸 Navbar usability improvements
This commit is contained in:
+235
-151
@@ -9,9 +9,10 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
import { getLocalization } from '$lib/i18n';
|
import { getLocalization } from '$lib/i18n';
|
||||||
import { signedIn, pathname } from '$lib/stores';
|
import { signedIn, pathname } from '$lib/stores';
|
||||||
import { createTippy } from 'svelte-tippy';
|
import { createTippy } from 'svelte-tippy';
|
||||||
import { browser } from '$app/environment';
|
|
||||||
import BrownButton from '$lib/components/buttons/brown.svelte';
|
import BrownButton from '$lib/components/buttons/brown.svelte';
|
||||||
// import { fade } from 'svelte/transition';
|
import { browser } from '$app/environment';
|
||||||
|
import { beforeNavigate } from '$app/navigation';
|
||||||
|
import { draw, slide } from 'svelte/transition';
|
||||||
|
|
||||||
const tippy = createTippy({
|
const tippy = createTippy({
|
||||||
arrow: true,
|
arrow: true,
|
||||||
@@ -21,10 +22,15 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
|
|
||||||
const { t } = getLocalization();
|
const { t } = getLocalization();
|
||||||
|
|
||||||
let openMenu = true;
|
let menuIsClosed = true;
|
||||||
const toggleMenu = () => {
|
const toggleMenu = () => {
|
||||||
openMenu = !openMenu;
|
menuIsClosed = !menuIsClosed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
beforeNavigate(() => {
|
||||||
|
menuIsClosed = true; // Closes menu to let the user see the page beneath
|
||||||
|
});
|
||||||
|
|
||||||
let darkMode = false;
|
let darkMode = false;
|
||||||
if (browser) {
|
if (browser) {
|
||||||
darkMode =
|
darkMode =
|
||||||
@@ -33,166 +39,77 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
window.matchMedia('(prefers-color-scheme: dark)').matches);
|
window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||||
}
|
}
|
||||||
|
|
||||||
const switchDarkMode = (on: boolean): void => {
|
const switchDarkMode = () => {
|
||||||
if (on) {
|
!darkMode ? localStorage.setItem('theme', 'dark') : localStorage.setItem('theme', 'light');
|
||||||
localStorage.setItem('theme', 'dark');
|
|
||||||
} else {
|
|
||||||
localStorage.setItem('theme', 'light');
|
|
||||||
}
|
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav
|
<nav class="w-screen px-4 lg:px-10 py-2 fixed backdrop-blur-2xl bg-white/70 shadow-md z-30 top-0">
|
||||||
class="w-screen px-4 lg:px-10 py-2 flex flex-col lg:flex-row lg:items-center fixed backdrop-blur-2xl bg-white/70 shadow-md z-30 top-0"
|
<!-- Desktop navbar -->
|
||||||
>
|
<div class="hidden lg:flex lg:items-center lg:flex-row lg:justify-between">
|
||||||
<section class="w-full lg:w-max grid grid-cols-3 lg:grid-cols-2">
|
<div class="lg:flex lg:items-center lg:flex-row gap-1">
|
||||||
<a href="/" class="font-black tracking-tight text-xl text-black marck-script link-hover"
|
<a
|
||||||
|
href="/"
|
||||||
|
class="font-black tracking-tight text-xl lg:text-2xl text-black marck-script link-hover px-3 lg:px-5"
|
||||||
>ClassQuiz</a
|
>ClassQuiz</a
|
||||||
>
|
>
|
||||||
<div class="flex justify-center">
|
<a class="btn-nav border-2 rounded" href="/play">{$t('words.play')}</a>
|
||||||
|
<a class="btn-nav" href="/explore">{$t('words.explore')}</a>
|
||||||
|
<a class="btn-nav" href="/search">{$t('words.search')}</a>
|
||||||
|
{#if $signedIn}
|
||||||
|
<a class="btn-nav" href="/dashboard">{$t('words.dashboard')}</a>
|
||||||
|
{:else}
|
||||||
|
<a class="btn-nav" href="/docs">{$t('words.docs')}</a>
|
||||||
<a
|
<a
|
||||||
class="lg:pl-6 text-lg font-medium lg:px-4 text-gray-600 hover:text-green-600 link-hover"
|
target="_blank"
|
||||||
href="/play">{$t('words.play')}</a
|
class="btn-nav flex items-center gap-1"
|
||||||
>
|
href="https://github.com/mawoka-myblock/ClassQuiz"
|
||||||
</div>
|
>GitHub
|
||||||
<div class="flex justify-end">
|
|
||||||
<button
|
|
||||||
class="lg:hidden dark:text-black"
|
|
||||||
id="open-menu"
|
|
||||||
on:click={toggleMenu}
|
|
||||||
class:hidden={!openMenu}
|
|
||||||
aria-label="Open navbar"
|
|
||||||
>
|
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="24"
|
width="17"
|
||||||
height="24"
|
height="17"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
display="block"
|
class="lucide lucide-external-link"
|
||||||
id="TextAlignJustified"
|
><path
|
||||||
|
d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"
|
||||||
|
/><polyline points="15 3 21 3 21 9" /><line
|
||||||
|
x1="10"
|
||||||
|
x2="21"
|
||||||
|
y1="14"
|
||||||
|
y2="3"
|
||||||
|
/></svg
|
||||||
>
|
>
|
||||||
<path d="M3 6h18M3 12h18M3 18h18" />
|
</a>
|
||||||
</svg>
|
{/if}
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Close menu -->
|
|
||||||
<button
|
|
||||||
class="hidden dark:text-black"
|
|
||||||
id="close-menu"
|
|
||||||
class:hidden={openMenu}
|
|
||||||
on:click={toggleMenu}
|
|
||||||
aria-label="Close navbar"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="14"
|
|
||||||
height="14"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="3"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
display="block"
|
|
||||||
id="Cross"
|
|
||||||
>
|
|
||||||
<path d="M20 20L4 4m16 0L4 20" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div class="lg:flex lg:items-center lg:flex-row gap-1">
|
||||||
<ul id="menu-items" class="lg:flex w-full flex-col lg:flex-row" class:hidden={openMenu}>
|
|
||||||
<li class="py-2 lg:hidden">
|
|
||||||
<BrownButton href="https://mawoka.eu/donate" target="_blank"
|
|
||||||
>{$t('navbar.donate')} <span class="">❤️</span></BrownButton
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
{#if $signedIn}
|
{#if $signedIn}
|
||||||
<li class="py-2">
|
<a class="btn-nav" href="/api/v1/users/logout">{$t('words.logout')}</a>
|
||||||
<a
|
|
||||||
class="text-lg font-medium lg:px-4 text-gray-600 hover:text-green-600 link-hover"
|
|
||||||
href="/dashboard">{$t('words.dashboard')}</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li class="py-2">
|
|
||||||
<a
|
|
||||||
class="text-lg font-medium lg:px-4 text-gray-600 hover:text-green-600 link-hover"
|
|
||||||
href="/explore">{$t('words.explore')}</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li class="py-2">
|
|
||||||
<a
|
|
||||||
class="text-lg font-medium lg:px-4 text-gray-600 hover:text-green-600 link-hover"
|
|
||||||
href="/search">{$t('words.search')}</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li class="py-2">
|
|
||||||
<a
|
|
||||||
class="text-lg font-medium lg:px-4 text-gray-600 hover:text-green-600 link-hover"
|
|
||||||
href="/api/v1/users/logout">{$t('words.logout')}</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
{:else}
|
{:else}
|
||||||
{#if !import.meta.env.VITE_REGISTRATION_DISABLED}
|
{#if !import.meta.env.VITE_REGISTRATION_DISABLED}
|
||||||
<li class="py-2">
|
<a class="btn-nav" href="/account/register">{$t('words.register')}</a>
|
||||||
<a
|
|
||||||
class="text-lg font-medium lg:px-4 text-gray-600 hover:text-green-600 link-hover"
|
|
||||||
href="/account/register">{$t('words.register')}</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<li class="py-2">
|
<a class="btn-nav" href="/account/login?returnTo={$pathname}">{$t('words.login')}</a
|
||||||
<a
|
|
||||||
class="text-lg font-medium lg:px-4 text-gray-600 hover:text-green-600 link-hover"
|
|
||||||
href="/account/login?returnTo={$pathname}">{$t('words.login')}</a
|
|
||||||
>
|
>
|
||||||
</li>
|
|
||||||
<li class="py-2">
|
|
||||||
<a
|
|
||||||
class="text-lg font-medium lg:px-4 text-gray-600 hover:text-green-600 link-hover"
|
|
||||||
href="/explore">{$t('words.explore')}</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li class="py-2">
|
|
||||||
<a
|
|
||||||
class="text-lg font-medium lg:px-4 text-gray-600 hover:text-green-600 link-hover"
|
|
||||||
href="/search">{$t('words.search')}</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li class="py-2">
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
class="text-lg font-medium lg:px-4 text-gray-600 hover:text-green-600 link-hover"
|
|
||||||
href="https://github.com/mawoka-myblock/ClassQuiz">GitHub</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li class="py-2">
|
|
||||||
<a
|
|
||||||
class="text-lg font-medium lg:px-4 text-gray-600 hover:text-green-600 link-hover"
|
|
||||||
href="/docs">{$t('words.docs')}</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
{/if}
|
{/if}
|
||||||
</ul>
|
|
||||||
<div
|
<div class="fit-content flex items-center justify-center gap-2">
|
||||||
class="justify-self-end gap-2 h-full flex justify-center items-center lg:relative fixed right-12 -top-1/2 pt-11 lg:right-0 lg:top-0 lg:p-0"
|
|
||||||
>
|
|
||||||
<div class="whitespace-nowrap hidden lg:block">
|
|
||||||
<BrownButton href="https://mawoka.eu/donate" target="_blank"
|
<BrownButton href="https://mawoka.eu/donate" target="_blank"
|
||||||
>{$t('navbar.donate')} <span class="">❤️</span></BrownButton
|
>{$t('navbar.donate')} <span class="">❤️</span></BrownButton
|
||||||
>
|
>
|
||||||
</div>
|
<div class="lg:flex items-center justify-center">
|
||||||
{#if darkMode}
|
{#if darkMode}
|
||||||
<button
|
<button
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
switchDarkMode(false);
|
switchDarkMode();
|
||||||
}}
|
}}
|
||||||
use:tippy={{ content: 'Switch light mode on' }}
|
use:tippy={{ content: 'Switch light mode on' }}
|
||||||
aria-label="Activate light mode"
|
aria-label="Activate light mode"
|
||||||
@@ -218,7 +135,7 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
switchDarkMode(true);
|
switchDarkMode();
|
||||||
}}
|
}}
|
||||||
aria-label="Activate darkmode"
|
aria-label="Activate darkmode"
|
||||||
use:tippy={{ content: 'Switch dark mode on' }}
|
use:tippy={{ content: 'Switch dark mode on' }}
|
||||||
@@ -242,23 +159,190 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile navbar -->
|
||||||
|
<div class="lg:hidden">
|
||||||
|
<!-- Navbar header -->
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
class="font-black tracking-tight text-xl lg:text-2xl text-black marck-script link-hover px-3 lg:px-5"
|
||||||
|
>ClassQuiz</a
|
||||||
|
>
|
||||||
|
<a class="btn-nav flex" href="/play">{$t('words.play')}</a>
|
||||||
|
|
||||||
|
<!-- Dark/Light mode toggle + Open/Close menu -->
|
||||||
|
<div class="flex items-center">
|
||||||
|
{#if darkMode}
|
||||||
|
<!-- Sun icon -->
|
||||||
|
<button
|
||||||
|
class="px-3"
|
||||||
|
on:click={() => {
|
||||||
|
switchDarkMode();
|
||||||
|
}}
|
||||||
|
use:tippy={{ content: 'Switch light mode on' }}
|
||||||
|
aria-label="Activate light mode"
|
||||||
|
>
|
||||||
|
<!-- Heroicons: sun -->
|
||||||
|
<svg
|
||||||
|
class="w-6 h-6 text-black"
|
||||||
|
fill="none"
|
||||||
|
aria-label="Sun-Icon"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke="currentColor"
|
||||||
|
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{:else}
|
||||||
|
<!-- Moon icon -->
|
||||||
|
<button
|
||||||
|
class="px-3"
|
||||||
|
on:click={() => {
|
||||||
|
switchDarkMode();
|
||||||
|
}}
|
||||||
|
aria-label="Activate darkmode"
|
||||||
|
use:tippy={{ content: 'Switch dark mode on' }}
|
||||||
|
>
|
||||||
|
<!-- Heroicons: moon -->
|
||||||
|
<svg
|
||||||
|
aria-label="Moon-Icon"
|
||||||
|
class="w-6 h-6"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if menuIsClosed}
|
||||||
|
<button
|
||||||
|
class="px-3"
|
||||||
|
id="open-menu"
|
||||||
|
on:click={toggleMenu}
|
||||||
|
aria-label="Open navbar"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M3 6h18M3 12h18M3 18h18" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
class="px-3"
|
||||||
|
id="close-menu"
|
||||||
|
on:click={toggleMenu}
|
||||||
|
aria-label="Close navbar"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
><path in:draw={{ duration: 300 }} d="M18 6 6 18" /><path
|
||||||
|
in:draw={{ duration: 300 }}
|
||||||
|
d="m6 6 12 12"
|
||||||
|
/></svg
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navbar content -->
|
||||||
|
{#if !menuIsClosed}
|
||||||
|
<div class="flex flex-col" transition:slide={{ duration: 400 }}>
|
||||||
|
<a class="btn-nav" href="/explore">{$t('words.explore')}</a>
|
||||||
|
<a class="btn-nav" href="/search">{$t('words.search')}</a>
|
||||||
|
{#if $signedIn}
|
||||||
|
<a class="btn-nav" href="/dashboard">{$t('words.dashboard')}</a>
|
||||||
|
{:else}
|
||||||
|
<a class="btn-nav" href="/docs">{$t('words.docs')}</a>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
class="btn-nav flex items-center gap-1"
|
||||||
|
href="https://github.com/mawoka-myblock/ClassQuiz"
|
||||||
|
>GitHub
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="17"
|
||||||
|
height="17"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="lucide lucide-external-link"
|
||||||
|
><path
|
||||||
|
d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"
|
||||||
|
/><polyline points="15 3 21 3 21 9" /><line
|
||||||
|
x1="10"
|
||||||
|
x2="21"
|
||||||
|
y1="14"
|
||||||
|
y2="3"
|
||||||
|
/></svg
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<hr class="my-1 border" />
|
||||||
|
{#if $signedIn}
|
||||||
|
<a class="btn-nav" href="/api/v1/users/logout">{$t('words.logout')}</a>
|
||||||
|
{:else}
|
||||||
|
{#if !import.meta.env.VITE_REGISTRATION_DISABLED}
|
||||||
|
<a class="btn-nav" href="/account/register">{$t('words.register')}</a>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<a class="btn-nav" href="/account/login?returnTo={$pathname}"
|
||||||
|
>{$t('words.login')}</a
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="fit-content flex items-center justify-center my-2">
|
||||||
|
<BrownButton href="https://mawoka.eu/donate" target="_blank"
|
||||||
|
>{$t('navbar.donate')} <span class="">❤️</span></BrownButton
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<style>
|
<style lang="scss">
|
||||||
.heart {
|
.btn-nav {
|
||||||
animation: heartbeat 1s linear infinite;
|
@apply text-lg font-medium px-3 text-gray-600 hover:text-green-600 py-1.5 transition-all duration-300;
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes heartbeat {
|
|
||||||
0% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: scale(2);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: scale(2);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user