feat: ui theming & improved clickOutside handler (for modals)
This commit is contained in:
parent
a950da9412
commit
b3b897be85
@ -13,7 +13,7 @@
|
|||||||
:root {
|
:root {
|
||||||
--light-background: #f5f5f5;
|
--light-background: #f5f5f5;
|
||||||
--light-foreground: #e0e0e0;
|
--light-foreground: #e0e0e0;
|
||||||
--light-accent: #303052;
|
--light-accent: #2e2e88;
|
||||||
--light-text: rgb(34, 40, 49);
|
--light-text: rgb(34, 40, 49);
|
||||||
--light-highlight-text: #c28e4a;
|
--light-highlight-text: #c28e4a;
|
||||||
--light-error-text: #4b0000;
|
--light-error-text: #4b0000;
|
||||||
@ -910,6 +910,14 @@
|
|||||||
@apply fixed inset-0 z-40 flex items-center justify-center backdrop-blur-xs;
|
@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 {
|
.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;
|
@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;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import type { User } from "$lib/logic/model"
|
import type { User } from "$lib/logic/model"
|
||||||
import Delete from "$lib/icons/sidebar/Delete.svelte"
|
import Delete from "$lib/icons/sidebar/Delete.svelte"
|
||||||
import { formatDateShort } from "$lib/util/contentVisual"
|
import { clickOutside, formatDateShort } from "$lib/util/contentVisual"
|
||||||
|
|
||||||
// props
|
// props
|
||||||
export let onClose: () => void
|
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) => {
|
const handleModalContentKeydown = (_event: KeyboardEvent) => {
|
||||||
// accessibility compliance handler, actual handling is done by the global keydown handler
|
// accessibility compliance handler, actual handling is done by the global keydown handler
|
||||||
}
|
}
|
||||||
@ -71,13 +63,13 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="modal-backdrop"
|
class="modal-backdrop"
|
||||||
on:click={handleClickOutside}
|
|
||||||
on:keydown={handleKeydown}
|
on:keydown={handleKeydown}
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||||
|
<div use:clickOutside={() => onClose()}>
|
||||||
<div
|
<div
|
||||||
bind:this={modalContent}
|
bind:this={modalContent}
|
||||||
class="modal-content max-w-xl"
|
class="modal-content max-w-xl"
|
||||||
@ -89,7 +81,7 @@
|
|||||||
<!-- Header (title + close button) -->
|
<!-- Header (title + close button) -->
|
||||||
<div class="modal-section flex items-center justify-between" role="heading" aria-level="1">
|
<div class="modal-section flex items-center justify-between" role="heading" aria-level="1">
|
||||||
<h2 class="text-xl font-bold">Administration</h2>
|
<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 />
|
<Close />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -139,4 +131,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import Close from "$lib/icons/Close.svelte"
|
import Close from "$lib/icons/Close.svelte"
|
||||||
import { isPasswordValid } from "$lib/util/authValidation"
|
import { isPasswordValid } from "$lib/util/authValidation"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { formatDateLong } from "$lib/util/contentVisual"
|
import { clickOutside, formatDateLong } from "$lib/util/contentVisual"
|
||||||
|
|
||||||
// props
|
// props
|
||||||
export let onClose: () => void
|
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) => {
|
const handleModalContentKeydown = (_event: KeyboardEvent) => {
|
||||||
// accessibility compliance handler, actual handling is done by the global keydown handler
|
// accessibility compliance handler, actual handling is done by the global keydown handler
|
||||||
}
|
}
|
||||||
@ -126,13 +118,13 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="modal-backdrop"
|
class="modal-backdrop"
|
||||||
on:click={handleClickOutside}
|
|
||||||
on:keydown={handleKeydown}
|
on:keydown={handleKeydown}
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||||
|
<div use:clickOutside={() => onClose()}>
|
||||||
<div
|
<div
|
||||||
bind:this={modalContent}
|
bind:this={modalContent}
|
||||||
class="modal-content max-w-xl"
|
class="modal-content max-w-xl"
|
||||||
@ -144,7 +136,7 @@
|
|||||||
<!-- Header (title + close button) -->
|
<!-- Header (title + close button) -->
|
||||||
<div class="modal-section flex items-center justify-between" role="heading" aria-level="1">
|
<div class="modal-section flex items-center justify-between" role="heading" aria-level="1">
|
||||||
<h2 class="text-xl font-bold">Settings</h2>
|
<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 />
|
<Close />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -246,8 +238,8 @@
|
|||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
Deleting your account will permanently remove all your notes and account information. This
|
Deleting your account will permanently remove all your notes and account information.
|
||||||
action cannot be undone.
|
This action cannot be undone.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Confirm Password -->
|
<!-- Confirm Password -->
|
||||||
@ -283,4 +275,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,7 +12,12 @@
|
|||||||
import AdminWrench from "$lib/icons/sidebar/AdminShield.svelte"
|
import AdminWrench from "$lib/icons/sidebar/AdminShield.svelte"
|
||||||
import Exit from "$lib/icons/sidebar/Exit.svelte"
|
import Exit from "$lib/icons/sidebar/Exit.svelte"
|
||||||
import ThemeToggle from "./ThemeToggle.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"
|
import type { FullNote, NoteMetadata } from "$lib/logic/model"
|
||||||
|
|
||||||
// props
|
// props
|
||||||
@ -210,6 +215,13 @@
|
|||||||
|
|
||||||
<!-- User section (single button) with dropdown -->
|
<!-- User section (single button) with dropdown -->
|
||||||
<div class="relative flex flex-col px-2 py-3">
|
<div class="relative flex flex-col px-2 py-3">
|
||||||
|
<div
|
||||||
|
use:clickOutside={() => {
|
||||||
|
if (isUserMenuOpen) {
|
||||||
|
isUserMenuOpen = false
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
on:click={toggleUserMenu}
|
on:click={toggleUserMenu}
|
||||||
class="sidebar-action-button flex-wrap justify-between px-4 py-4"
|
class="sidebar-action-button flex-wrap justify-between px-4 py-4"
|
||||||
@ -260,5 +272,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,3 +50,25 @@ export const formatTitleWithHighlight = (title: string): string => {
|
|||||||
|
|
||||||
return `<span class="note-title-exp-highlight">${expirationPrefix}</span> ${cleanTitle}`
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user