adds mobile drawer

This commit is contained in:
Christian Beutel
2024-02-24 16:01:09 +01:00
parent 90a683f268
commit 275874fba9
13 changed files with 215 additions and 67 deletions

View File

@@ -23,7 +23,7 @@
}
.btn-icon {
@apply rounded-full aspect-square h-7 hover:bg-secondary-hover focus:ring-4 ring-input-ring transition-colors;
@apply rounded-full aspect-square h-8 text-lg hover:bg-secondary-hover focus:ring-4 ring-input-ring transition-colors;
}
.tab {

View File

@@ -40,7 +40,7 @@
--menu-item-background-hover: 43, 46, 69;
--menu-item-background-focus: 25, 26, 41;
--footer-background: 10, 14, 48;
--footer-background: 26, 29, 51;
--separator: 108, 115, 150;

View File

@@ -0,0 +1,41 @@
<script lang="ts">
import { browser } from "$app/environment";
import { beforeNavigate } from "$app/navigation";
import { backIn, backOut } from "svelte/easing";
import { slide } from "svelte/transition";
export let open: boolean = false;
$: if (open && browser) {
document.body.style.position = "fixed";
document.body.style.top = `-${window.scrollY}px`;
} else if (browser) {
const scrollY = document.body.style.top;
document.body.style.position = "";
document.body.style.top = "";
window.scrollTo(0, parseInt(scrollY || "0") * -1);
}
beforeNavigate(() => {
open = false;
});
</script>
{#if open}
<div
class="fixed drawer-overlay h-screen w-screen backdrop-blur-md z-50 bg-gray-500/50 cursor-pointer"
on:click={() => (open = false)}
on:keydown={(e) => {
if (e.key == "Escape") open = false;
}}
role="button"
tabindex="0"
></div>
<div
class="absolute flex flex-col overflow-y-auto w-72 h-screen bg-background right-0 top-0 z-50"
in:slide={{ axis: "x", easing: backOut }}
out:slide={{ axis: "x", easing: backIn }}
>
<slot />
</div>
{/if}

View File

@@ -2,10 +2,19 @@
export let id: string;
export let title: string;
export function openModal() {
document.body.style.position = "fixed";
document.body.style.width = "100%";
document.body.style.top = `-${window.scrollY}px`;
(document.getElementById(id) as HTMLDialogElement).showModal();
}
export function closeModal() {
const scrollY = document.body.style.top;
document.body.style.position = "";
document.body.style.top = "";
window.scrollTo(0, parseInt(scrollY || "0") * -1);
(document.getElementById(id) as HTMLDialogElement).close();
}
</script>
@@ -40,13 +49,14 @@
<slot name="content" />
</div>
<!-- Modal footer -->
<div
class="p-4 md:p-5 border-t border-separator rounded-b"
>
<div class="p-4 md:p-5 border-t border-separator rounded-b">
<slot name="footer" {closeModal} />
</div>
</div>
</dialog>
<style>
dialog::backdrop {
@apply bg-gray-500/50;
}
</style>

View File

@@ -6,7 +6,7 @@
</script>
<div
class="category-card relative rounded-2xl shadow-md w-48 h-48 overflow-hidden cursor-pointer"
class="category-card relative rounded-2xl shadow-md max-h-48 aspect-video overflow-hidden cursor-pointer"
>
<img
class="w-full h-full"

View File

@@ -7,6 +7,7 @@
import { tweened } from "svelte/motion";
import Dropdown from "./base/dropdown.svelte";
import LogoTextLight from "./logo/logo_text_light.svelte";
import Drawer from "./base/drawer.svelte";
const navBarItems = [
{ text: "Home", value: "/" },
@@ -35,6 +36,8 @@
easing: backInOut,
});
let drawerOpen: boolean = false;
afterNavigate((e) => {
const routeId = e.to?.route.id;
const navBarLinks = document.getElementById("nav-bar-links");
@@ -76,18 +79,63 @@
}
</script>
<Drawer bind:open={drawerOpen}>
<div class="flex gap-4 items-center m-4">
<div class="basis-full"></div>
<button
class="btn-icon fa-regular fa-{$theme === 'light' ? 'sun' : 'moon'}"
on:click={() => toggleTheme()}
></button>
<button
class="btn-icon block fa fa-close float-right"
on:click={() => (drawerOpen = false)}
></button>
</div>
<div class="flex flex-col px-12 gap-8">
{#each navBarItems as item}
<a
class="font-semibold text-xl"
href={item.value}
data-sveltekit-preload-data="off">{item.text}</a
>
{/each}
</div>
<hr class="my-6 border-input-border" />
<div class="flex flex-col basis-full gap-y-3">
<a class="btn-primary btn-large text-center mx-4" href="/trail/edit/new"
><i class="fa fa-plus mr-2"></i>New Trail</a
>
{#if $currentUser}
<div class="basis-full"></div>
<hr class="border-input-border" />
<div class="flex gap-4 items-center mx-4">
<img
class="rounded-full w-8 aspect-square"
src="https://api.dicebear.com/7.x/initials/svg?seed={$currentUser.username}&backgroundType=gradientLinear"
alt=""
/>
<div>
<p class="text-sm">{$currentUser.username}</p>
<p class="text-sm text-gray-500">{$currentUser.email}</p>
</div>
<button class="btn-icon tooltip" data-title="Logout"
><i class="fa-solid fa-arrow-right-from-bracket"
></i></button
>
</div>
{/if}
</div>
</Drawer>
<nav class="flex justify-between items-center p-6">
<a href="/" data-sveltekit-preload-data="tap">
<a href="/" data-sveltekit-preload-data="off">
{#if $theme == "light"}
<LogoText></LogoText>
{:else}
<LogoTextLight></LogoTextLight>
{/if}
</a>
<menu
id="nav-bar-links"
class="hidden md:flex gap-8 relative py-1 px-2 "
>
<menu id="nav-bar-links" class="hidden md:flex gap-8 relative py-1 px-2">
<div
class="absolute h-full w-16 bg-menu-item-background-hover rounded-xl top-0 z-0"
style="width: {$indicatorWidth}px; left: {$indicatorPosition}px; scale: {$indicatorScale}"
@@ -96,7 +144,7 @@
<a
class="font-semibold z-10"
href={item.value}
data-sveltekit-preload-data="tap">{item.text}</a
data-sveltekit-preload-data="off">{item.text}</a
>
{/each}
</menu>
@@ -133,4 +181,8 @@
<a class="btn-primary btn-large" href="/login">Login</a>
</div>
{/if}
<button
class="btn-icon fa fa-bars md:hidden"
on:click={() => (drawerOpen = !drawerOpen)}
></button>
</nav>

View File

@@ -134,6 +134,7 @@
<input
id="{category.name}-checkbox"
type="checkbox"
checked={filter.category.includes(category.id)}
value={category.id}
class="w-4 h-4 bg-input-background accent-primary border-input-border focus:ring-input-ring focus:ring-2"
on:change={() => setCategoryFilter(category)}

View File

@@ -7,9 +7,11 @@
import "../css/app.css";
import "../css/components.css";
import "../css/theme.css";
</script>
<svelte:head>
<title> | wanderer</title>
</svelte:head>
<div class="theme {$theme}">
<NavBar></NavBar>
<Toast></Toast>

View File

@@ -64,7 +64,7 @@
style="height: calc(100vh - 112px)"
>
<div
class="flex flex-col justify-center gap-8 max-w-md mx-8 md:mx-auto -mt-24"
class="flex flex-col justify-center gap-8 max-w-md mx-8 md:mx-auto lg:-mt-24"
>
<h2 class="text-7xl font-bold">
Welcome to <span class="-tracking-[0.075em]">wanderer</span>
@@ -90,7 +90,10 @@
<section
class="max-w-7xl mx-auto mt-8 px-8 xl:px-0 grid grid-cols-1 md:grid-cols-2 items-center"
>
<div id="trails" class="flex flex-wrap justify-items-center gap-8 py-8 order-1 md:order-none">
<div
id="trails"
class="flex flex-wrap justify-items-center gap-8 py-8 order-1 md:order-none"
>
{#each $trails as trail}
<a href="/trail/view/{trail.id}">
<TrailCard {trail}></TrailCard></a
@@ -108,7 +111,7 @@
<a
class="inline-block btn-primary btn-large"
href="/trails"
role="button">All Trails</a
role="button">Explore</a
>
</div>
</section>
@@ -118,12 +121,18 @@
<div class="max-w-md md:mx-auto space-y-8">
<h2 class="text-4xl md:text-5xl font-bold">Categories</h2>
<h5>
Did you know? You cannot only save you hiking trails. There are many categories for all your adventures.
Did you know? You cannot only save you hiking trails. There are many
categories for all your adventures.
</h5>
</div>
<div id="categories" class="flex flex-wrap justify-items-center gap-8 py-8">
<div
id="categories"
class="grid grid-cols-1 lg:grid-cols-2 justify-items-center gap-8 py-8"
>
{#each $categories as category}
<CategoryCard {category}></CategoryCard>
<a href="/trails?category={category.id}">
<CategoryCard {category}></CategoryCard>
</a>
{/each}
</div>
</section>

View File

@@ -29,6 +29,7 @@
import TrailFilterPanel from "$lib/components/trail/trail_filter_panel.svelte";
import { categories } from "$lib/stores/category_store";
import { slide } from "svelte/transition";
import { browser } from "$app/environment";
let L: any;
let map: Map;
@@ -38,6 +39,7 @@
let searchDropdownItems: SearchItem[] = [];
let showFilter: boolean = false;
let showMap: boolean = true;
let filter: TrailFilter;
@@ -232,11 +234,11 @@
<main class="grid grid-cols-1 md:grid-cols-[400px_1fr]">
<div
class="overflow-y-auto overflow-x-hidden flex flex-col items-stretch gap-4 px-8"
style="height: calc(100vh - 124px)"
id="trail-list"
class="md:overflow-y-auto md:overflow-x-hidden flex flex-col items-stretch gap-4 px-8"
>
<div class="sticky top-0 z-10 bg-background pb-4 space-y-4">
<div class="flex items-center gap-4">
<div class="flex items-center gap-2 md:gap-4">
<Search
extraClasses="w-full"
on:update={(e) => search(e.detail)}
@@ -249,6 +251,11 @@
on:click={() => (showFilter = !showFilter)}
><i class="fa fa-sliders"></i></button
>
<button
class="btn-icon md:hidden"
on:click={() => (showMap = !showMap)}
><i class="fa-regular fa-{showMap ? 'rectangle-list' : 'map'}"></i></button
>
</div>
{#if showFilter}
<div in:slide out:slide>
@@ -263,27 +270,34 @@
{/if}
</div>
{#if $trails.length == 0}
<EmptyStateSearch></EmptyStateSearch>
{#if !showMap || (browser && window.innerWidth >= 768)}
{#if $trails.length == 0}
<EmptyStateSearch></EmptyStateSearch>
{/if}
{#each $trails as trail}
<a href="/trail/view/{trail.id}">
<TrailCard
{trail}
on:mouseenter={() => handleTrailCardMouseEnter(trail)}
on:mouseleave={() => handleTrailCardMouseLeave(trail)}
></TrailCard>
</a>
{/each}
{/if}
{#each $trails as trail}
<a href="/trail/view/{trail.id}">
<TrailCard
{trail}
on:mouseenter={() => handleTrailCardMouseEnter(trail)}
on:mouseleave={() => handleTrailCardMouseLeave(trail)}
></TrailCard>
</a>
{/each}
</div>
<div class="rounded-xl" id="map"></div>
<div id="map" class="rounded-xl z-0" class:hidden={!showMap && browser && window.innerWidth < 768}></div>
</main>
<style>
#map {
height: calc(100vh - 124px);
height: calc(100vh - 180px);
}
@media only screen and (min-width: 768px) {
#map,
#trail-list {
height: calc(100vh - 124px);
}
}
:global(.leaflet-popup-content) {
width: max-content !important;
max-width: 100%;

View File

@@ -81,7 +81,10 @@
) as HTMLFormElement;
const formData = new FormData(htmlForm);
if (!submittedTrail.id) {
const createdTrail = await trails_create(submittedTrail, formData);
const createdTrail = await trails_create(
submittedTrail,
formData,
);
$form.id = createdTrail.id;
} else {
await trails_update($trail, submittedTrail, formData);
@@ -194,11 +197,13 @@
reader.onload = async function (e) {
await addGPXLayer(e.target?.result as string);
const closestCity = (await ms.index("cities500").search("", {
filter: [`_geoRadius(${$form.lat}, ${$form.lon}, 10000)`],
sort: [`_geoPoint(${$form.lat}, ${$form.lon}):asc`],
limit: 1,
})).hits[0];
const closestCity = (
await ms.index("cities500").search("", {
filter: [`_geoRadius(${$form.lat}, ${$form.lon}, 10000)`],
sort: [`_geoPoint(${$form.lat}, ${$form.lon}):asc`],
limit: 1,
})
).hits[0];
$form.location = closestCity.name;
};
@@ -339,10 +344,10 @@
}
</script>
<main class="grid grid-cols-[400px_1fr]">
<main class="grid grid-cols-1 md:grid-cols-[400px_1fr]">
<form
id="trail-form"
class="overflow-y-auto overflow-x-hidden flex flex-col gap-4 px-8"
class="overflow-y-auto overflow-x-hidden flex flex-col gap-4 px-8 order-1 md:order-none mt-8 md:mt-0"
on:submit={handleSubmit}
>
<h3 class="text-xl font-semibold">Pick a trail</h3>
@@ -412,7 +417,7 @@
></Select>
{/if}
<Toggle name="public" label="Public" bind:value={$form.public}></Toggle>
<hr class="border-separator"/>
<hr class="border-separator" />
<h3 class="text-xl font-semibold">Waypoints</h3>
<ul>
{#each $form.expand.waypoints ?? [] as waypoint, i}
@@ -432,7 +437,7 @@
on:click={beforeWaypointModalOpen}
><i class="fa fa-plus mr-2"></i>Add Waypoint</button
>
<hr class="border-separator"/>
<hr class="border-separator" />
<h3 class="text-xl font-semibold">Photos</h3>
<div class="flex gap-4 max-w-full overflow-x-auto shrink-0">
<button
@@ -459,7 +464,7 @@
</div>
{/each}
</div>
<hr class="border-separator"/>
<hr class="border-separator" />
<h3 class="text-xl font-semibold">Summit Book</h3>
<ul>
{#each $form.expand.summit_logs ?? [] as log, i}
@@ -478,7 +483,7 @@
on:click={beforeSummitLogModalOpen}
><i class="fa fa-plus mr-2"></i>Add Entry</button
>
<hr class="border-separator"/>
<hr class="border-separator" />
<Button
primary={true}
large={true}
@@ -495,8 +500,13 @@
></SummitLogModal>
<style>
#map,
form {
height: calc(100vh - 124px);
#map {
height: calc(400px);
}
@media only screen and (min-width: 768px) {
#map,
form {
height: calc(100vh - 124px);
}
}
</style>

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { page } from "$app/stores";
import Select, {
type SelectItem,
} from "$lib/components/base/select.svelte";
@@ -26,19 +27,7 @@
let filterExpanded: boolean = true;
export const filter: TrailFilter = {
q: "",
category: [],
near: {
radius: 2000,
},
distanceMin: 0,
distanceMax: 20000,
elevationGainMin: 0,
elevationGainMax: 4000,
sort: "created",
sortOrder: "+",
};
const filter: TrailFilter = $page.data.filter;
onMount(() => {
const storedDisplayOption = localStorage.getItem("displayOption");

View File

@@ -1,8 +1,28 @@
import type { TrailFilter } from "$lib/models/trail";
import { categories_index } from "$lib/stores/category_store";
import { trails_index } from "$lib/stores/trail_store";
import { trails_search_filter } from "$lib/stores/trail_store";
import type { ServerLoad } from "@sveltejs/kit";
export const load: ServerLoad = async ({ params, locals }) => {
await trails_index()
export const load: ServerLoad = async ({ params, locals, url }) => {
const filter: TrailFilter = {
q: "",
category: [],
near: {
radius: 2000,
},
distanceMin: 0,
distanceMax: 20000,
elevationGainMin: 0,
elevationGainMax: 4000,
sort: "created",
sortOrder: "+",
};
const paramCategory = url.searchParams.get("category");
if (paramCategory) {
filter.category.push(paramCategory);
}
await trails_search_filter(filter);
await categories_index()
return {filter};
};