feat: 100% width sidebar on mobile

This commit is contained in:
ae 2025-04-18 20:28:24 +03:00
parent c3f377c635
commit 52c94d22ad
Signed by: ae
GPG Key ID: 995EFD5C1B532B3E
3 changed files with 91 additions and 16 deletions

View File

@ -168,8 +168,24 @@
}
/* Sidebar */
@media (max-width: 768px) {
.sidebar {
@apply w-full max-w-full;
}
body.sidebar-open {
@apply overflow-hidden;
}
}
@media (min-width: 768px) {
.sidebar {
@apply w-64;
}
}
.sidebar {
@apply fixed flex h-full w-64 flex-col overflow-hidden border-r border-[var(--light-foreground)] bg-[var(--light-foreground)] transition-all duration-300;
@apply fixed flex h-full flex-col overflow-hidden border-r border-[var(--light-foreground)] bg-[var(--light-foreground)] transition-all duration-300;
}
.dark .sidebar {
@ -453,6 +469,18 @@
}
/* Main layout */
.main-layout-container {
@apply flex h-screen bg-[var(--light-background)];
}
.dark .main-layout-container {
@apply bg-[var(--dark-background)];
}
.main-error-popup {
@apply fixed top-4 right-4 z-50 max-w-md;
}
.main-header {
@apply flex items-center justify-between bg-[var(--light-foreground)] p-4 shadow-sm;
}

View File

@ -19,11 +19,11 @@
export let isLoading = false
// State
let sidebarOpen = true
let sidebarOpen = false
let showSettings = false
let isEditing = false
onMount(async () => {
onMount(async (): Promise<any> => {
isLoading = true
try {
@ -44,6 +44,22 @@
} finally {
isLoading = false
}
// Default to sidebar closed on mobile
const handleResize = () => {
if (window.innerWidth < 768 && sidebarOpen) {
sidebarOpen = false
}
}
handleResize()
// Keep listening to browser's resize events
window.addEventListener("resize", handleResize)
return () => {
window.removeEventListener("resize", handleResize)
}
})
const loadNotes = async () => {
@ -59,6 +75,10 @@
sidebarOpen = !sidebarOpen
}
const closeSidebar = () => {
sidebarOpen = false
}
const toggleSettings = () => {
showSettings = !showSettings
}
@ -81,9 +101,9 @@
}
}
// TODO: add caching (!!!)
const selectNote = async (noteId: string) => {
// TODO: check from cache props first before requesting from API
console.log(`loading ${noteId}`)
const note = await apiClient.getFullNote(noteId)
@ -116,10 +136,10 @@
}
</script>
<div class="flex h-screen bg-[var(--light-background)]">
<div class="main-layout-container">
<!-- Error notification -->
{#if $cError}
<div class="fixed right-4 top-4 z-50 max-w-md">
<div class="main-error-popup">
<div class="error">
{$cError}
<button class="ml-2 text-red-700" on:click={() => cError.set(null)}> × </button>
@ -127,22 +147,23 @@
</div>
{/if}
<!-- Sidebar -->
<!-- Sidebar (2-way binding prop allows changing the same var. in either component) -->
<Sidebar
{sidebarOpen}
bind:sidebarOpen
notes={$availableNotes || []}
currentNote={$currentFullNote}
{toggleSettings}
{logout}
{createNewNote}
{selectNote}
on:close={closeSidebar}
/>
<!-- Main content -->
<div
class="flex flex-1 flex-col overflow-hidden transition-all duration-300"
class:ml-64={sidebarOpen}
class:ml-0={!sidebarOpen}
class:md:-ml-64={!sidebarOpen}
class:md:ml-0={sidebarOpen}
>
<!-- Top navbar -->
<header class="main-header">

View File

@ -1,5 +1,6 @@
<script lang="ts">
import type { NoteMetadata, FullNote } from "$lib/client"
import Close from "$lib/icons/Close.svelte"
import CreateNew from "$lib/icons/CreateNew.svelte"
import Logout from "$lib/icons/Logout.svelte"
import Search from "$lib/icons/Search.svelte"
@ -14,6 +15,8 @@
export let createNewNote: () => Promise<void>
export let selectNote: (noteId: string) => Promise<void>
// TODO: add caching props -> e.g. `export const previousNotes: FullNote[]`
const formatDate = (dateString: string | Date): string => {
if (!dateString) {
return ""
@ -30,6 +33,10 @@
})
}
const closeSidebar = () => {
sidebarOpen = false
}
const handleNoteKeydown = (event: KeyboardEvent, noteID: string) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault() // Prevent page scroll on space
@ -47,7 +54,13 @@
<!-- TODO: make the sidebar take up whole screen width on mobile -->
<!-- TODO: add admin modal (opens a view similar to the settings modal) button to the bottom (if the user is an admin) -->
<aside class="sidebar" class:translate-x-0={sidebarOpen} class:translate-x-[-100%]={!sidebarOpen}>
<aside
class="sidebar z-10"
class:translate-x-0={sidebarOpen}
class:translate-x-[-100%]={!sidebarOpen}
class:fixed={true}
class:md:relative={true}
>
<!-- Sidebar header -->
<div class="sidebar-header">
<div class="flex items-center justify-between">
@ -64,6 +77,17 @@
<input type="text" placeholder="Search" bind:value={searchQuery} class="search-bar" />
<Search />
</div>
<!-- Close button visible only on mobile -->
<div class="relative pl-4">
<button
on:click={closeSidebar}
class="btn-secondary rounded-full p-2 md:hidden"
aria-label="Close sidebar"
>
<Close />
</button>
</div>
</div>
</div>
@ -98,6 +122,12 @@
<!-- Sidebar footer -->
<div class="sidebar-footer">
<div class="flex justify-between">
<button on:click={logout} class="btn-secondary rounded-full p-2" aria-label="Logout">
<Logout />
</button>
<!-- It's better for UX that the logout button isn't on the right side -->
<button
on:click={toggleSettings}
class="btn-secondary rounded-full p-2"
@ -105,10 +135,6 @@
>
<Settings />
</button>
<button on:click={logout} class="btn-secondary rounded-full p-2" aria-label="Logout">
<Logout />
</button>
</div>
</div>
</aside>