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 */ /* 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 { .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 { .dark .sidebar {
@ -453,6 +469,18 @@
} }
/* Main layout */ /* 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 { .main-header {
@apply flex items-center justify-between bg-[var(--light-foreground)] p-4 shadow-sm; @apply flex items-center justify-between bg-[var(--light-foreground)] p-4 shadow-sm;
} }

View File

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

View File

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { NoteMetadata, FullNote } from "$lib/client" import type { NoteMetadata, FullNote } from "$lib/client"
import Close from "$lib/icons/Close.svelte"
import CreateNew from "$lib/icons/CreateNew.svelte" import CreateNew from "$lib/icons/CreateNew.svelte"
import Logout from "$lib/icons/Logout.svelte" import Logout from "$lib/icons/Logout.svelte"
import Search from "$lib/icons/Search.svelte" import Search from "$lib/icons/Search.svelte"
@ -14,6 +15,8 @@
export let createNewNote: () => Promise<void> export let createNewNote: () => Promise<void>
export let selectNote: (noteId: string) => 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 => { const formatDate = (dateString: string | Date): string => {
if (!dateString) { if (!dateString) {
return "" return ""
@ -30,6 +33,10 @@
}) })
} }
const closeSidebar = () => {
sidebarOpen = false
}
const handleNoteKeydown = (event: KeyboardEvent, noteID: string) => { const handleNoteKeydown = (event: KeyboardEvent, noteID: string) => {
if (event.key === "Enter" || event.key === " ") { if (event.key === "Enter" || event.key === " ") {
event.preventDefault() // Prevent page scroll on space event.preventDefault() // Prevent page scroll on space
@ -47,7 +54,13 @@
<!-- TODO: make the sidebar take up whole screen width on mobile --> <!-- 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) --> <!-- 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 --> <!-- Sidebar header -->
<div class="sidebar-header"> <div class="sidebar-header">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
@ -64,6 +77,17 @@
<input type="text" placeholder="Search" bind:value={searchQuery} class="search-bar" /> <input type="text" placeholder="Search" bind:value={searchQuery} class="search-bar" />
<Search /> <Search />
</div> </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>
</div> </div>
@ -98,6 +122,12 @@
<!-- Sidebar footer --> <!-- Sidebar footer -->
<div class="sidebar-footer"> <div class="sidebar-footer">
<div class="flex justify-between"> <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 <button
on:click={toggleSettings} on:click={toggleSettings}
class="btn-secondary rounded-full p-2" class="btn-secondary rounded-full p-2"
@ -105,10 +135,6 @@
> >
<Settings /> <Settings />
</button> </button>
<button on:click={logout} class="btn-secondary rounded-full p-2" aria-label="Logout">
<Logout />
</button>
</div> </div>
</div> </div>
</aside> </aside>