feat(#35): add new mobile menu

This commit is contained in:
OllyNicholass
2024-10-24 00:28:28 +01:00
parent 6ed1b25583
commit 3a5e961d54
6 changed files with 264 additions and 201 deletions
+6 -1
View File
@@ -1,5 +1,7 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { cubicInOut } from 'svelte/easing';
import { slide } from 'svelte/transition';
export let cookiesAccepted; export let cookiesAccepted;
let showBanner = false; let showBanner = false;
@@ -18,7 +20,10 @@
</script> </script>
{#if showBanner} {#if showBanner}
<div class="fixed bottom-0 left-0 w-full bg-gray-800 text-white p-4"> <div
class="fixed bottom-0 left-0 w-full bg-gray-800 text-white p-4"
transition:slide={{ duration: 200, easing: cubicInOut }}
>
<p class="text-sm"> <p class="text-sm">
We use cookies to ensure you get the best experience on our website. By continuing, you agree We use cookies to ensure you get the best experience on our website. By continuing, you agree
to our use of cookies. to our use of cookies.
+12 -4
View File
@@ -1,5 +1,7 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { cubicInOut } from 'svelte/easing';
import { slide } from 'svelte/transition';
export let indicator: boolean = false; export let indicator: boolean = false;
export let posRight: boolean = false; export let posRight: boolean = false;
@@ -28,16 +30,22 @@
}); });
</script> </script>
<div bind:this={dropdown} class="relative"> <div bind:this={dropdown} class="relative block">
<div class="dropdown-title"> <div class="dropdown-title block w-full">
<button class="flex" on:click={() => isOpen = !isOpen}> <button class="flex w-full" on:click={() => (isOpen = !isOpen)}>
{#if indicator} {#if indicator}
<span class="mr-1">{@html isOpen ? DropdownIcon.open : DropdownIcon.closed}</span> <span class="mr-1">{@html isOpen ? DropdownIcon.open : DropdownIcon.closed}</span>
{/if} {/if}
<slot name="dropdown"></slot> <slot name="dropdown"></slot>
</button> </button>
</div> </div>
<ul class:flex={isOpen} class:hidden={!isOpen} class:right-0={posRight} class="absolute rounded-box min-w-52 shadow z-[1] menu p-2 bg-base-100 text-base-content"> {#if isOpen}
<ul
class:right-0={posRight}
class="absolute rounded-box min-w-52 shadow z-[1] m-0 menu p-2 bg-base-100 text-base-content block"
transition:slide={{ duration: 200, easing: cubicInOut }}
>
<slot /> <slot />
</ul> </ul>
{/if}
</div> </div>
+48 -22
View File
@@ -1,20 +1,14 @@
<script lang="ts"> <script lang="ts">
import type { Session, SupabaseClient } from "@supabase/supabase-js"; import type { Session, SupabaseClient } from '@supabase/supabase-js';
import MagicLink from "./MagicLink.svelte"; import MagicLink from './MagicLink.svelte';
import NavLinks from "./NavLinks.svelte"; import NavLinks from './NavLinks.svelte';
import SignOut from "./SignOut.svelte"; import SignOut from './SignOut.svelte';
import Dropdown from "./Dropdown.svelte"; import Dropdown from './Dropdown.svelte';
import { basket, type Basket } from '$lib/stores/basket.js'; import { basket, type Basket } from '$lib/stores/basket.js';
import { onDestroy } from "svelte"; import { onDestroy } from 'svelte';
export let session: Session; export let session: Session | null;
export let supabase: SupabaseClient<any, "public", any>; export let supabase: SupabaseClient<any, 'public', any>;
let isMobileMenuOpen = false;
function toggleMobileMenu() {
isMobileMenuOpen = !isMobileMenuOpen;
}
let localBasket: Basket; let localBasket: Basket;
const unsubscribeToBasket = basket.subscribe((value) => { const unsubscribeToBasket = basket.subscribe((value) => {
@@ -26,11 +20,13 @@
}); });
</script> </script>
<nav class="bg-primary text-primary-content p-4"> <nav class="bg-primary text-primary-content p-4">
<div class="container mx-auto flex justify-between items-center"> <div class="container mx-auto flex justify-between items-center">
<!-- Hamburger Menu Button (Mobile) --> <!-- Hamburger Menu Button (Mobile) -->
<button on:click={toggleMobileMenu} class="block md:hidden text-white focus:outline-none"> <div class="lg:hidden mr-4">
<Dropdown>
<div slot="dropdown">
<button class="block text-white focus:outline-none">
<svg <svg
class="w-6 h-6" class="w-6 h-6"
fill="none" fill="none"
@@ -46,14 +42,19 @@
></path> ></path>
</svg> </svg>
</button> </button>
</div>
<!-- Dropdown content -->
<NavLinks isMobile />
</Dropdown>
</div>
<a href="/" class="text-white font-bold text-xl">SvelteKit SaaS Boilerplate</a> <a href="/" class="text-white font-bold text-xl">SvelteKit SaaS Boilerplate</a>
<div class="hidden lg:block">
<NavLinks /> <NavLinks />
</div>
<!-- Right Side: Basket & User Account --> <!-- Right Side: Basket & User Account -->
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
<!-- Basket/Checkout Icon --> <!-- Basket/Checkout Icon -->
<a href="/checkout" class="btn btn-ghost btn-circle"> <a href="/checkout" class="btn btn-ghost btn-circle hidden lg:flex">
<div class="indicator"> <div class="indicator">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -74,7 +75,6 @@
{/if} {/if}
</div> </div>
</a> </a>
<!-- User Account Dropdown --> <!-- User Account Dropdown -->
<Dropdown posRight> <Dropdown posRight>
<div slot="dropdown"> <div slot="dropdown">
@@ -82,7 +82,9 @@
<div class="w-10 rounded-full"> <div class="w-10 rounded-full">
{#if session?.user} {#if session?.user}
<div class="user-circle text-primary-content border-primary-content"> <div class="user-circle text-primary-content border-primary-content">
<span class="text-xl">{session?.user.email ? session?.user.email[0].toUpperCase() : '?'}</span> <span class="text-xl"
>{session?.user.email ? session?.user.email[0].toUpperCase() : '?'}</span
>
</div> </div>
{:else} {:else}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
@@ -96,8 +98,32 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Dropdown content -->
{#if session?.user} {#if session?.user}
<li class="lg:hidden">
<a href="/checkout">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"
/>
</svg>
Basket
{#if localBasket.items.length > 0}
<span class="badge badge-md indicator-item bg-primary text-primary-content"
>{localBasket.items.length}</span
>
{/if}
</a>
</li>
<li><a href="/account">Account</a></li> <li><a href="/account">Account</a></li>
<li><a href="/settings">Settings</a></li> <li><a href="/settings">Settings</a></li>
<li><SignOut {supabase} /></li> <li><SignOut {supabase} /></li>
+25
View File
@@ -1,14 +1,39 @@
<script lang="ts"> <script lang="ts">
import { type NavLinkItem } from '$lib/types/Nav/NavLinkItem'; import { type NavLinkItem } from '$lib/types/Nav/NavLinkItem';
import { cubicInOut } from 'svelte/easing';
import Dropdown from './Dropdown.svelte'; import Dropdown from './Dropdown.svelte';
import { slide } from 'svelte/transition';
export let text: string; export let text: string;
export let children: NavLinkItem[] | undefined; export let children: NavLinkItem[] | undefined;
export let isMobile: boolean;
let isOpen = false;
</script> </script>
{#if !isMobile}
<Dropdown indicator> <Dropdown indicator>
<div slot="dropdown">{text}</div> <div slot="dropdown">{text}</div>
{#each children ?? [] as child} {#each children ?? [] as child}
<li><a href={child.href}>{child.text}</a></li> <li><a href={child.href}>{child.text}</a></li>
{/each} {/each}
</Dropdown> </Dropdown>
{:else}
<button
class:menu-dropdown-show={isOpen}
class="menu-dropdown-toggle"
on:click={() => (isOpen = !isOpen)}
>
{text}
</button>
{#if isOpen}
<ul
class="menu-dropdown menu-dropdown-show"
transition:slide={{ duration: 200, easing: cubicInOut }}
>
{#each children ?? [] as child}
<li><a href={child.href}>{child.text}</a></li>
{/each}
</ul>
{/if}
{/if}
+11 -9
View File
@@ -2,10 +2,12 @@
import { type NavLinkItem } from '$lib/types/Nav/NavLinkItem'; import { type NavLinkItem } from '$lib/types/Nav/NavLinkItem';
import NavLinkParent from './NavLinkParent.svelte'; import NavLinkParent from './NavLinkParent.svelte';
export let isMobile = false;
const links: NavLinkItem[] = [ const links: NavLinkItem[] = [
{ {
text: 'Products', text: 'Products',
href: '/products', href: '/products'
}, },
{ {
text: 'Parent', text: 'Parent',
@@ -13,26 +15,26 @@
children: [ children: [
{ {
text: 'Submenu 1', text: 'Submenu 1',
href: '/', href: '/'
}, },
{ {
text: 'Submenu 2', text: 'Submenu 2',
href: '/', href: '/'
}, }
], ]
}, },
{ {
text: 'Example Product', text: 'Example Product',
href: '/tools/exampleproduct', href: '/tools/exampleproduct'
}, }
]; ];
</script> </script>
<ul class="flex space-x-4"> <ul class:flex={!isMobile} class:space-x-4={!isMobile}>
{#each links as link} {#each links as link}
<li> <li>
{#if link.isParent} {#if link.isParent}
<NavLinkParent text={link.text} children={link.children} /> <NavLinkParent text={link.text} children={link.children} {isMobile} />
{:else} {:else}
<a href={link.href}>{link.text}</a> <a href={link.href}>{link.text}</a>
{/if} {/if}
+6 -9
View File
@@ -6,13 +6,13 @@
import { invalidate } from '$app/navigation'; import { invalidate } from '$app/navigation';
import CookieConsent from '$lib/components/CookieConsent.svelte'; import CookieConsent from '$lib/components/CookieConsent.svelte';
import Nav from '$lib/components/Nav.svelte'; import Nav from '$lib/components/Nav.svelte';
import { slide } from 'svelte/transition';
import { cubicInOut } from 'svelte/easing';
export let data; export let data;
let { supabase, session } = data; let { supabase, session } = data;
$: ({ supabase, session } = data); $: ({ supabase, session } = data);
let localGeneral: any; let localGeneral: any;
const unsubscribeToGeneralStore = general.subscribe((value) => { const unsubscribeToGeneralStore = general.subscribe((value) => {
localGeneral = value; localGeneral = value;
@@ -40,10 +40,6 @@
return () => data.subscription.unsubscribe(); return () => data.subscription.unsubscribe();
}); });
</script> </script>
<svelte:head> <svelte:head>
@@ -126,14 +122,14 @@
</nav> </nav>
</footer> </footer>
{#if !localGeneral.hideToast}
<div <div
id={localGeneral.toastType == 'success' ? 'success-toast' : 'error-toast'} id={localGeneral.toastType == 'success' ? 'success-toast' : 'error-toast'}
role="alert" role="alert"
class="alert {localGeneral.toastType == 'success' class="alert {localGeneral.toastType == 'success'
? 'alert-success' ? 'alert-success'
: 'alert-error'} fixed top-20 right-3 max-w-52 text-white {localGeneral.hideToast : 'alert-error'} fixed top-20 right-3 max-w-52 text-white"
? 'hidden' transition:slide={{ duration: 200, easing: cubicInOut }}
: ''}"
> >
{#if localGeneral.toastType == 'success'} {#if localGeneral.toastType == 'success'}
<svg <svg
@@ -172,6 +168,7 @@
<span>{localGeneral.toastMessage == '' ? 'Added to basket' : localGeneral.toastMessage}</span> <span>{localGeneral.toastMessage == '' ? 'Added to basket' : localGeneral.toastMessage}</span>
</div> </div>
{/if}
<style scoped lang="postcss"> <style scoped lang="postcss">
.user-circle { .user-circle {