mirror of
https://github.com/ReVanced/revanced-website.git
synced 2026-01-11 05:36:17 +00:00
feat: keep modal hierarchy
This commit is contained in:
28
src/lib/components/atoms/ModalBackground.svelte
Normal file
28
src/lib/components/atoms/ModalBackground.svelte
Normal file
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { modalsStack } from '$stores/modals.svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
let hasModals = $derived(modalsStack.getStack().length > 0);
|
||||
</script>
|
||||
|
||||
{#if hasModals}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="modal-background"
|
||||
transition:fade={{ duration: 300 }}
|
||||
onclick={() => modalsStack.closeTop()}
|
||||
></div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.modal-background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
z-index: 9998;
|
||||
}
|
||||
</style>
|
||||
@@ -1,15 +1,35 @@
|
||||
<script lang="ts">
|
||||
import type { WithChildren } from '$types';
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { WithChildren } from '$types';
|
||||
import { modalsStack } from '$stores/modals.svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
open?: boolean;
|
||||
buttons?: Snippet;
|
||||
} & WithChildren;
|
||||
let { buttons, children }: Props = $props();
|
||||
let { id, buttons, children, open = $bindable(true) }: Props = $props();
|
||||
|
||||
let isTopModal = $derived(modalsStack.isTopModal(id));
|
||||
|
||||
$effect(() => {
|
||||
if (open)
|
||||
modalsStack.push(id, () => {
|
||||
open = false;
|
||||
});
|
||||
else modalsStack.pop(id);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="background">
|
||||
<div class="modal rounded">
|
||||
{#if isTopModal}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<dialog
|
||||
class="modal rounded"
|
||||
transition:fade={{ duration: 300 }}
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div class="content">
|
||||
{@render children()}
|
||||
</div>
|
||||
@@ -18,24 +38,15 @@
|
||||
{@render buttons()}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 2rem;
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
@@ -43,6 +54,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import logo from '$assets/logo.svg';
|
||||
import Notifications from 'virtual:icons/material-symbols/notifications-outline';
|
||||
import Settings from 'virtual:icons/material-symbols/settings-outline';
|
||||
import Modal from '$components/molecules/Modal.svelte';
|
||||
|
||||
const navItems = [
|
||||
{ label: 'Home', href: '/' },
|
||||
@@ -11,6 +12,9 @@
|
||||
{ label: 'Contributors', href: '/contributors' },
|
||||
{ label: 'Donate', href: '/donate' }
|
||||
] as const satisfies { label: string; href: string }[];
|
||||
|
||||
let settingsOpen = $state(false);
|
||||
let loginOpen = $state(false);
|
||||
</script>
|
||||
|
||||
<nav>
|
||||
@@ -32,10 +36,25 @@
|
||||
class="rounded nav-button unselectable"
|
||||
class:active={page.url.pathname === '/announcements'}><Notifications /></a
|
||||
>
|
||||
<button class="rounded nav-button unselectable" type="button"><Settings /></button>
|
||||
<button
|
||||
class="rounded nav-button unselectable"
|
||||
type="button"
|
||||
onclick={() => (settingsOpen = true)}><Settings /></button
|
||||
>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<Modal id="settings" bind:open={settingsOpen}>
|
||||
<h2>Settings</h2>
|
||||
{#snippet buttons()}
|
||||
<button type="button" onclick={() => (loginOpen = true)}>Login</button>
|
||||
{/snippet}
|
||||
</Modal>
|
||||
|
||||
<Modal id="login" bind:open={loginOpen}>
|
||||
<h2>Login</h2>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
nav {
|
||||
display: flex;
|
||||
@@ -46,7 +65,7 @@
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 9998;
|
||||
z-index: 9997;
|
||||
}
|
||||
|
||||
.nav-group {
|
||||
|
||||
37
src/lib/stores/modals.svelte.ts
Normal file
37
src/lib/stores/modals.svelte.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { SvelteMap } from 'svelte/reactivity';
|
||||
|
||||
class ModalStack {
|
||||
private stack = $state<string[]>([]);
|
||||
private modals = new SvelteMap<string, () => void>();
|
||||
|
||||
push(id: string, closeCallback: () => void) {
|
||||
if (!this.modals.has(id)) {
|
||||
this.stack.push(id);
|
||||
this.modals.set(id, closeCallback);
|
||||
}
|
||||
}
|
||||
|
||||
pop(id: string) {
|
||||
const index = this.stack.indexOf(id);
|
||||
if (index !== -1) {
|
||||
this.stack.splice(index, 1);
|
||||
this.modals.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
closeTop() {
|
||||
if (this.stack.length < 0) return;
|
||||
const topId = this.stack[this.stack.length - 1];
|
||||
this.modals.get(topId)?.();
|
||||
}
|
||||
|
||||
isTopModal(id: string): boolean {
|
||||
return this.stack.length > 0 && this.stack[this.stack.length - 1] === id;
|
||||
}
|
||||
|
||||
getStack(): readonly string[] {
|
||||
return this.stack;
|
||||
}
|
||||
}
|
||||
|
||||
export const modalsStack = new ModalStack();
|
||||
@@ -2,6 +2,7 @@
|
||||
import '../app.css';
|
||||
import favicon from '$assets/favicon.ico';
|
||||
import NavBar from '$components/molecules/NavBar.svelte';
|
||||
import ModalBackground from '$components/atoms/ModalBackground.svelte';
|
||||
import type { WithChildren } from '$types';
|
||||
|
||||
let { children }: WithChildren = $props();
|
||||
@@ -12,4 +13,7 @@
|
||||
</svelte:head>
|
||||
|
||||
<NavBar />
|
||||
|
||||
<ModalBackground />
|
||||
|
||||
{@render children()}
|
||||
|
||||
@@ -12,6 +12,7 @@ const config = {
|
||||
$components: 'src/lib/components',
|
||||
$assets: 'src/lib/assets',
|
||||
$types: 'src/lib/types.ts',
|
||||
$stores: 'src/lib/stores',
|
||||
$lib: 'src/lib'
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user