feat: ui theming & improved clickOutside handler (for modals)
This commit is contained in:
parent
a950da9412
commit
b3b897be85
@ -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;
|
||||
}
|
||||
|
@ -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,71 +63,72 @@
|
||||
|
||||
<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
|
||||
bind:this={modalContent}
|
||||
class="modal-content max-w-xl"
|
||||
on:click|stopPropagation
|
||||
on:keydown={handleModalContentKeydown}
|
||||
role="document"
|
||||
tabindex="-1"
|
||||
>
|
||||
<!-- 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">
|
||||
<Close />
|
||||
</button>
|
||||
</div>
|
||||
<div use:clickOutside={() => onClose()}>
|
||||
<div
|
||||
bind:this={modalContent}
|
||||
class="modal-content max-w-xl"
|
||||
on:click|stopPropagation
|
||||
on:keydown={handleModalContentKeydown}
|
||||
role="document"
|
||||
tabindex="-1"
|
||||
>
|
||||
<!-- 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="modal-exit-button" aria-label="Close settings">
|
||||
<Close />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Account information -->
|
||||
<div class="modal-section border-b-0">
|
||||
<h3 class="modal-section-title">Active users</h3>
|
||||
<div class="flex flex-col justify-center">
|
||||
{#if users !== null && users.length > 0}
|
||||
<ul class="flex flex-col items-center space-y-1" role="listbox">
|
||||
{#each users as user}
|
||||
<li class="modal-list-item">
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<h3 class="modal-list-item-title">{user.username}</h3>
|
||||
<!-- Account information -->
|
||||
<div class="modal-section border-b-0">
|
||||
<h3 class="modal-section-title">Active users</h3>
|
||||
<div class="flex flex-col justify-center">
|
||||
{#if users !== null && users.length > 0}
|
||||
<ul class="flex flex-col items-center space-y-1" role="listbox">
|
||||
{#each users as user}
|
||||
<li class="modal-list-item">
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<h3 class="modal-list-item-title">{user.username}</h3>
|
||||
|
||||
<!-- Delete button -->
|
||||
{#if $currentUser !== null && user.id === $currentUser.id}
|
||||
<button
|
||||
disabled
|
||||
class="sidebar-button sidebar-list-item-delete-button"
|
||||
aria-label="Delete note"
|
||||
>
|
||||
<Delete classString="h-3 w-3" />
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
on:click={(e) => handleDeleteUser(e, user.id, user.username)}
|
||||
class="sidebar-button sidebar-list-item-delete-button"
|
||||
aria-label="Delete note"
|
||||
>
|
||||
<Delete classString="h-3 w-3" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- Delete button -->
|
||||
{#if $currentUser !== null && user.id === $currentUser.id}
|
||||
<button
|
||||
disabled
|
||||
class="sidebar-button sidebar-list-item-delete-button"
|
||||
aria-label="Delete note"
|
||||
>
|
||||
<Delete classString="h-3 w-3" />
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
on:click={(e) => handleDeleteUser(e, user.id, user.username)}
|
||||
class="sidebar-button sidebar-list-item-delete-button"
|
||||
aria-label="Delete note"
|
||||
>
|
||||
<Delete classString="h-3 w-3" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<p class="modal-list-item-metadata">{user.id}</p>
|
||||
<p class="modal-list-item-metadata">{user.id}</p>
|
||||
|
||||
<p class="modal-list-item-metadata">
|
||||
Created: {formatDateShort(user.createdAt)}, Updated: {formatDateShort(
|
||||
user.updatedAt
|
||||
)}
|
||||
</p>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
<p class="modal-list-item-metadata">
|
||||
Created: {formatDateShort(user.createdAt)}, Updated: {formatDateShort(
|
||||
user.updatedAt
|
||||
)}
|
||||
</p>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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,161 +118,162 @@
|
||||
|
||||
<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
|
||||
bind:this={modalContent}
|
||||
class="modal-content max-w-xl"
|
||||
on:click|stopPropagation
|
||||
on:keydown={handleModalContentKeydown}
|
||||
role="document"
|
||||
tabindex="-1"
|
||||
>
|
||||
<!-- 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">
|
||||
<Close />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Account information -->
|
||||
<div class="modal-section">
|
||||
<h3 class="modal-section-title">Account Data</h3>
|
||||
<table class="modal-table">
|
||||
<tbody>
|
||||
<tr class="modal-table-row">
|
||||
<th class="modal-table-row-item modal-table-head">Username</th>
|
||||
<td class="modal-table-row-item">{$currentUser?.username}</td>
|
||||
</tr>
|
||||
<tr class="modal-table-row">
|
||||
<th class="modal-table-row-item modal-table-head">Account created</th>
|
||||
<td class="modal-table-row-item"
|
||||
>{$currentUser?.createdAt !== undefined
|
||||
? formatDateLong($currentUser?.createdAt)
|
||||
: ""}</td
|
||||
>
|
||||
</tr>
|
||||
<tr class="modal-table-row">
|
||||
<th class="modal-table-row-item modal-table-head">Password updated</th>
|
||||
<td class="modal-table-row-item"
|
||||
>{$currentUser?.createdAt !== undefined
|
||||
? formatDateLong($currentUser?.updatedAt)
|
||||
: ""}</td
|
||||
>
|
||||
</tr>
|
||||
{#if $currentUser?.isAdmin}
|
||||
<tr class="modal-table-row">
|
||||
<th class="modal-table-row-item modal-table-head">Admin permissions</th>
|
||||
<td class="modal-table-row-item">{$currentUser?.isAdmin}</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Account settings -->
|
||||
<div class="modal-section">
|
||||
<h3 class="modal-section-title">Account Settings</h3>
|
||||
|
||||
{#if changePasswordModalError}
|
||||
<div class="error mb-4" role="alert">
|
||||
{changePasswordModalError}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="space-y-4">
|
||||
<h4 class="font-bold">Change Password</h4>
|
||||
|
||||
<!-- Current Password -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="currentPassword">Current Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="currentPassword"
|
||||
bind:value={currentPassword}
|
||||
class="auth-input-field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- New Password -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="newPassword">New Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="newPassword"
|
||||
bind:value={newPassword}
|
||||
class="auth-input-field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Confirm New Password -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="confirmPassword">Confirm New Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="confirmPassword"
|
||||
bind:value={confirmPassword}
|
||||
class="auth-input-field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button on:click={changePassword} class="auth-button"> Change Password </button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- "Danger zone" -->
|
||||
<div class="modal-section border-b-0">
|
||||
<h3 class="modal-section-title text-red-500">Danger Zone</h3>
|
||||
|
||||
{#if deleteUserModalError}
|
||||
<div class="error mb-4" role="alert">
|
||||
{deleteUserModalError}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<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.
|
||||
</p>
|
||||
|
||||
<!-- Confirm Password -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="deleteConfirmPassword">Your Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="deleteConfirmPassword"
|
||||
bind:value={deleteConfirmPassword}
|
||||
class="auth-input-field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Type DELETE to confirm -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="deleteConfirmText">Type DELETE to confirm</label>
|
||||
<input
|
||||
type="text"
|
||||
id="deleteConfirmText"
|
||||
bind:value={deleteConfirmText}
|
||||
class="auth-input-field"
|
||||
placeholder="DELETE"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
on:click={deleteAccount}
|
||||
class="auth-button bg-red-500 text-white hover:bg-red-600 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={deleteConfirmText !== "DELETE" || !deleteConfirmPassword}
|
||||
>
|
||||
Delete My Account
|
||||
<div use:clickOutside={() => onClose()}>
|
||||
<div
|
||||
bind:this={modalContent}
|
||||
class="modal-content max-w-xl"
|
||||
on:click|stopPropagation
|
||||
on:keydown={handleModalContentKeydown}
|
||||
role="document"
|
||||
tabindex="-1"
|
||||
>
|
||||
<!-- 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="modal-exit-button" aria-label="Close settings">
|
||||
<Close />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Account information -->
|
||||
<div class="modal-section">
|
||||
<h3 class="modal-section-title">Account Data</h3>
|
||||
<table class="modal-table">
|
||||
<tbody>
|
||||
<tr class="modal-table-row">
|
||||
<th class="modal-table-row-item modal-table-head">Username</th>
|
||||
<td class="modal-table-row-item">{$currentUser?.username}</td>
|
||||
</tr>
|
||||
<tr class="modal-table-row">
|
||||
<th class="modal-table-row-item modal-table-head">Account created</th>
|
||||
<td class="modal-table-row-item"
|
||||
>{$currentUser?.createdAt !== undefined
|
||||
? formatDateLong($currentUser?.createdAt)
|
||||
: ""}</td
|
||||
>
|
||||
</tr>
|
||||
<tr class="modal-table-row">
|
||||
<th class="modal-table-row-item modal-table-head">Password updated</th>
|
||||
<td class="modal-table-row-item"
|
||||
>{$currentUser?.createdAt !== undefined
|
||||
? formatDateLong($currentUser?.updatedAt)
|
||||
: ""}</td
|
||||
>
|
||||
</tr>
|
||||
{#if $currentUser?.isAdmin}
|
||||
<tr class="modal-table-row">
|
||||
<th class="modal-table-row-item modal-table-head">Admin permissions</th>
|
||||
<td class="modal-table-row-item">{$currentUser?.isAdmin}</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Account settings -->
|
||||
<div class="modal-section">
|
||||
<h3 class="modal-section-title">Account Settings</h3>
|
||||
|
||||
{#if changePasswordModalError}
|
||||
<div class="error mb-4" role="alert">
|
||||
{changePasswordModalError}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="space-y-4">
|
||||
<h4 class="font-bold">Change Password</h4>
|
||||
|
||||
<!-- Current Password -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="currentPassword">Current Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="currentPassword"
|
||||
bind:value={currentPassword}
|
||||
class="auth-input-field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- New Password -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="newPassword">New Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="newPassword"
|
||||
bind:value={newPassword}
|
||||
class="auth-input-field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Confirm New Password -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="confirmPassword">Confirm New Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="confirmPassword"
|
||||
bind:value={confirmPassword}
|
||||
class="auth-input-field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button on:click={changePassword} class="auth-button"> Change Password </button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- "Danger zone" -->
|
||||
<div class="modal-section border-b-0">
|
||||
<h3 class="modal-section-title text-red-500">Danger Zone</h3>
|
||||
|
||||
{#if deleteUserModalError}
|
||||
<div class="error mb-4" role="alert">
|
||||
{deleteUserModalError}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<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.
|
||||
</p>
|
||||
|
||||
<!-- Confirm Password -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="deleteConfirmPassword">Your Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="deleteConfirmPassword"
|
||||
bind:value={deleteConfirmPassword}
|
||||
class="auth-input-field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Type DELETE to confirm -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="deleteConfirmText">Type DELETE to confirm</label>
|
||||
<input
|
||||
type="text"
|
||||
id="deleteConfirmText"
|
||||
bind:value={deleteConfirmText}
|
||||
class="auth-input-field"
|
||||
placeholder="DELETE"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
on:click={deleteAccount}
|
||||
class="auth-button bg-red-500 text-white hover:bg-red-600 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={deleteConfirmText !== "DELETE" || !deleteConfirmPassword}
|
||||
>
|
||||
Delete My Account
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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,55 +215,63 @@
|
||||
|
||||
<!-- User section (single button) with dropdown -->
|
||||
<div class="relative flex flex-col px-2 py-3">
|
||||
<button
|
||||
on:click={toggleUserMenu}
|
||||
class="sidebar-action-button flex-wrap justify-between px-4 py-4"
|
||||
<div
|
||||
use:clickOutside={() => {
|
||||
if (isUserMenuOpen) {
|
||||
isUserMenuOpen = false
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="sidebar-user-button-content-container">
|
||||
<User classString="h-6 w-6 flex-shrink-0" />
|
||||
<button
|
||||
on:click={toggleUserMenu}
|
||||
class="sidebar-action-button flex-wrap justify-between px-4 py-4"
|
||||
>
|
||||
<div class="sidebar-user-button-content-container">
|
||||
<User classString="h-6 w-6 flex-shrink-0" />
|
||||
|
||||
<div class="sidebar-user-button-username-container">
|
||||
<span class="sidebar-user-button-username-text">Logged in as {username}</span>
|
||||
<div class="sidebar-user-button-username-container">
|
||||
<span class="sidebar-user-button-username-text">Logged in as {username}</span>
|
||||
</div>
|
||||
|
||||
{#if isUserMenuOpen}
|
||||
<ChevronDown classString="h-6 w-6 flex-shrink-0" />
|
||||
{:else}
|
||||
<ChevronUp classString="h-6 w-6 flex-shrink-0" />
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{#if isUserMenuOpen}
|
||||
<ChevronDown classString="h-6 w-6 flex-shrink-0" />
|
||||
{:else}
|
||||
<ChevronUp classString="h-6 w-6 flex-shrink-0" />
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
<!-- User actions dropdown menu -->
|
||||
{#if isUserMenuOpen}
|
||||
<div class="sidebar-user-dropdown">
|
||||
<div class="flex flex-col p-0">
|
||||
<button
|
||||
on:click={toggleSettingsModal}
|
||||
class="sidebar-action-button rounded-none rounded-t-lg border-0 border-b"
|
||||
>
|
||||
<SettingsGear classString="mr-3 h-5 w-5" />
|
||||
Settings
|
||||
</button>
|
||||
|
||||
<!-- User actions dropdown menu -->
|
||||
{#if isUserMenuOpen}
|
||||
<div class="sidebar-user-dropdown">
|
||||
<div class="flex flex-col p-0">
|
||||
<button
|
||||
on:click={toggleSettingsModal}
|
||||
class="sidebar-action-button rounded-none rounded-t-lg border-0 border-b"
|
||||
>
|
||||
<SettingsGear classString="mr-3 h-5 w-5" />
|
||||
Settings
|
||||
</button>
|
||||
<button
|
||||
on:click={toggleAdminModal}
|
||||
class="sidebar-action-button rounded-none border-0 border-y"
|
||||
>
|
||||
<AdminWrench classString="mr-3 h-5 w-5" />
|
||||
Admin view
|
||||
</button>
|
||||
|
||||
<button
|
||||
on:click={toggleAdminModal}
|
||||
class="sidebar-action-button rounded-none border-0 border-y"
|
||||
>
|
||||
<AdminWrench classString="mr-3 h-5 w-5" />
|
||||
Admin view
|
||||
</button>
|
||||
|
||||
<button
|
||||
on:click={logout}
|
||||
class="sidebar-action-button rounded-none rounded-b-lg border-0 border-t"
|
||||
>
|
||||
<Exit classString="mr-3 h-5 w-5" />
|
||||
Log out
|
||||
</button>
|
||||
<button
|
||||
on:click={logout}
|
||||
class="sidebar-action-button rounded-none rounded-b-lg border-0 border-t"
|
||||
>
|
||||
<Exit classString="mr-3 h-5 w-5" />
|
||||
Log out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user