feat: 100% width sidebar on mobile
This commit is contained in:
parent
c3f377c635
commit
52c94d22ad
@ -168,8 +168,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar */
|
/* Sidebar */
|
||||||
|
@media (max-width: 768px) {
|
||||||
.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 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 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;
|
||||||
}
|
}
|
||||||
|
@ -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">
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user