feat: ui theming & improved clickOutside handler (for modals)

This commit is contained in:
ae 2025-05-06 12:59:29 +03:00
parent a950da9412
commit b3b897be85
Signed by: ae
GPG Key ID: 995EFD5C1B532B3E
5 changed files with 293 additions and 264 deletions

View File

@ -13,7 +13,7 @@
:root {
--light-background: #f5f5f5;
--light-foreground: #e0e0e0;
--light-accent: #303052;
--light-accent: #2e2e88;
--light-text: rgb(34, 40, 49);
--light-highlight-text: #c28e4a;
--light-error-text: #4b0000;
@ -910,6 +910,14 @@
@apply fixed inset-0 z-40 flex items-center justify-center backdrop-blur-xs;
}
.modal-exit-button {
@apply flex h-8 w-8 items-center justify-center rounded-lg border border-[var(--light-accent)] bg-transparent p-0 text-[var(--light-text)] hover:bg-[var(--light-foreground)]/80;
}
.dark .modal-exit-button {
@apply hover:bg-[var(--dark-foreground)]/80;
}
.modal-content {
@apply mx-4 max-h-[90vh] w-full max-w-[90vw] overflow-y-auto rounded-lg border-2 border-[var(--light-accent)]/10 bg-[var(--light-background)] shadow-lg;
}

View File

@ -4,7 +4,7 @@
import { onMount } from "svelte"
import type { User } from "$lib/logic/model"
import Delete from "$lib/icons/sidebar/Delete.svelte"
import { formatDateShort } from "$lib/util/contentVisual"
import { clickOutside, formatDateShort } from "$lib/util/contentVisual"
// props
export let onClose: () => void
@ -46,14 +46,6 @@
}
}
const handleClickOutside = (event: MouseEvent) => {
// close the modal if user clicks outside of it
const target = event.target as HTMLElement
if (target.classList.contains("modal-backdrop")) {
onClose()
}
}
const handleModalContentKeydown = (_event: KeyboardEvent) => {
// accessibility compliance handler, actual handling is done by the global keydown handler
}
@ -71,13 +63,13 @@
<div
class="modal-backdrop"
on:click={handleClickOutside}
on:keydown={handleKeydown}
role="dialog"
aria-modal="true"
tabindex="-1"
>
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div use:clickOutside={() => onClose()}>
<div
bind:this={modalContent}
class="modal-content max-w-xl"
@ -89,7 +81,7 @@
<!-- Header (title + close button) -->
<div class="modal-section flex items-center justify-between" role="heading" aria-level="1">
<h2 class="text-xl font-bold">Administration</h2>
<button on:click={onClose} class="sidebar-button" aria-label="Close settings">
<button on:click={onClose} class="modal-exit-button" aria-label="Close settings">
<Close />
</button>
</div>
@ -139,4 +131,5 @@
</div>
</div>
</div>
</div>
</div>

View File

@ -3,7 +3,7 @@
import Close from "$lib/icons/Close.svelte"
import { isPasswordValid } from "$lib/util/authValidation"
import { onMount } from "svelte"
import { formatDateLong } from "$lib/util/contentVisual"
import { clickOutside, formatDateLong } from "$lib/util/contentVisual"
// props
export let onClose: () => void
@ -103,14 +103,6 @@
}
}
const handleClickOutside = (event: MouseEvent) => {
// close the modal if user clicks outside of it
const target = event.target as HTMLElement
if (target.classList.contains("modal-backdrop")) {
onClose()
}
}
const handleModalContentKeydown = (_event: KeyboardEvent) => {
// accessibility compliance handler, actual handling is done by the global keydown handler
}
@ -126,13 +118,13 @@
<div
class="modal-backdrop"
on:click={handleClickOutside}
on:keydown={handleKeydown}
role="dialog"
aria-modal="true"
tabindex="-1"
>
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div use:clickOutside={() => onClose()}>
<div
bind:this={modalContent}
class="modal-content max-w-xl"
@ -144,7 +136,7 @@
<!-- Header (title + close button) -->
<div class="modal-section flex items-center justify-between" role="heading" aria-level="1">
<h2 class="text-xl font-bold">Settings</h2>
<button on:click={onClose} class="sidebar-button" aria-label="Close settings">
<button on:click={onClose} class="modal-exit-button" aria-label="Close settings">
<Close />
</button>
</div>
@ -246,8 +238,8 @@
<div class="space-y-4">
<p class="text-sm">
Deleting your account will permanently remove all your notes and account information. This
action cannot be undone.
Deleting your account will permanently remove all your notes and account information.
This action cannot be undone.
</p>
<!-- Confirm Password -->
@ -283,4 +275,5 @@
</div>
</div>
</div>
</div>
</div>

View File

@ -12,7 +12,12 @@
import AdminWrench from "$lib/icons/sidebar/AdminShield.svelte"
import Exit from "$lib/icons/sidebar/Exit.svelte"
import ThemeToggle from "./ThemeToggle.svelte"
import { formatDateLong, formatDateShort, parseExpirationPrefix } from "$lib/util/contentVisual"
import {
clickOutside,
formatDateLong,
formatDateShort,
parseExpirationPrefix
} from "$lib/util/contentVisual"
import type { FullNote, NoteMetadata } from "$lib/logic/model"
// props
@ -210,6 +215,13 @@
<!-- User section (single button) with dropdown -->
<div class="relative flex flex-col px-2 py-3">
<div
use:clickOutside={() => {
if (isUserMenuOpen) {
isUserMenuOpen = false
}
}}
>
<button
on:click={toggleUserMenu}
class="sidebar-action-button flex-wrap justify-between px-4 py-4"
@ -260,5 +272,6 @@
</div>
{/if}
</div>
</div>
</aside>
</div>

View File

@ -50,3 +50,25 @@ export const formatTitleWithHighlight = (title: string): string => {
return `<span class="note-title-exp-highlight">${expirationPrefix}</span> ${cleanTitle}`
}
export const clickOutside = (
node: HTMLElement,
callbackFunction: () => void
): {
destroy: () => void
} => {
const handleClick = (event: MouseEvent): void => {
if (node && !node.contains(event.target as Node) && !event.defaultPrevented) {
callbackFunction()
}
}
// event listener with capture phase to ensure it runs before other click handlers
document.addEventListener("click", handleClick, true)
return {
destroy: () => {
document.removeEventListener("click", handleClick, true)
}
}
}