mirror of
https://github.com/ReVanced/revanced-website.git
synced 2026-01-11 13:46: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">
|
<script lang="ts">
|
||||||
import type { WithChildren } from '$types';
|
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
|
import type { WithChildren } from '$types';
|
||||||
|
import { modalsStack } from '$stores/modals.svelte';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
id: string;
|
||||||
|
open?: boolean;
|
||||||
buttons?: Snippet;
|
buttons?: Snippet;
|
||||||
} & WithChildren;
|
} & 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>
|
</script>
|
||||||
|
|
||||||
<div class="background">
|
{#if isTopModal}
|
||||||
<div class="modal rounded">
|
<!-- 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">
|
<div class="content">
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
@@ -18,24 +38,15 @@
|
|||||||
{@render buttons()}
|
{@render buttons()}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</dialog>
|
||||||
</div>
|
{/if}
|
||||||
|
|
||||||
<style>
|
<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 {
|
.modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
max-width: 90vw;
|
max-width: 90vw;
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
@@ -43,6 +54,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import logo from '$assets/logo.svg';
|
import logo from '$assets/logo.svg';
|
||||||
import Notifications from 'virtual:icons/material-symbols/notifications-outline';
|
import Notifications from 'virtual:icons/material-symbols/notifications-outline';
|
||||||
import Settings from 'virtual:icons/material-symbols/settings-outline';
|
import Settings from 'virtual:icons/material-symbols/settings-outline';
|
||||||
|
import Modal from '$components/molecules/Modal.svelte';
|
||||||
|
|
||||||
const navItems = [
|
const navItems = [
|
||||||
{ label: 'Home', href: '/' },
|
{ label: 'Home', href: '/' },
|
||||||
@@ -11,6 +12,9 @@
|
|||||||
{ label: 'Contributors', href: '/contributors' },
|
{ label: 'Contributors', href: '/contributors' },
|
||||||
{ label: 'Donate', href: '/donate' }
|
{ label: 'Donate', href: '/donate' }
|
||||||
] as const satisfies { label: string; href: string }[];
|
] as const satisfies { label: string; href: string }[];
|
||||||
|
|
||||||
|
let settingsOpen = $state(false);
|
||||||
|
let loginOpen = $state(false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav>
|
<nav>
|
||||||
@@ -32,10 +36,25 @@
|
|||||||
class="rounded nav-button unselectable"
|
class="rounded nav-button unselectable"
|
||||||
class:active={page.url.pathname === '/announcements'}><Notifications /></a
|
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>
|
</div>
|
||||||
</nav>
|
</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>
|
<style>
|
||||||
nav {
|
nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -46,7 +65,7 @@
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 9998;
|
z-index: 9997;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-group {
|
.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 '../app.css';
|
||||||
import favicon from '$assets/favicon.ico';
|
import favicon from '$assets/favicon.ico';
|
||||||
import NavBar from '$components/molecules/NavBar.svelte';
|
import NavBar from '$components/molecules/NavBar.svelte';
|
||||||
|
import ModalBackground from '$components/atoms/ModalBackground.svelte';
|
||||||
import type { WithChildren } from '$types';
|
import type { WithChildren } from '$types';
|
||||||
|
|
||||||
let { children }: WithChildren = $props();
|
let { children }: WithChildren = $props();
|
||||||
@@ -12,4 +13,7 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<NavBar />
|
<NavBar />
|
||||||
|
|
||||||
|
<ModalBackground />
|
||||||
|
|
||||||
{@render children()}
|
{@render children()}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const config = {
|
|||||||
$components: 'src/lib/components',
|
$components: 'src/lib/components',
|
||||||
$assets: 'src/lib/assets',
|
$assets: 'src/lib/assets',
|
||||||
$types: 'src/lib/types.ts',
|
$types: 'src/lib/types.ts',
|
||||||
|
$stores: 'src/lib/stores',
|
||||||
$lib: 'src/lib'
|
$lib: 'src/lib'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user