Added "Dynamic modals"; fixed title modals not allowing child modals

This commit is contained in:
Aleksi Lassila
2023-08-09 23:08:46 +03:00
parent a52e96e0e1
commit 25c018174d
24 changed files with 507 additions and 558 deletions

View File

@@ -0,0 +1,54 @@
<script lang="ts">
import classNames from 'classnames';
import { modalStack } from './Modal';
import { fade } from 'svelte/transition';
import { onDestroy } from 'svelte';
function handleShortcuts(event: KeyboardEvent) {
if (event.key === 'Escape' && $modalStack.top) {
modalStack.close($modalStack.top.id);
}
}
onDestroy(() => {
modalStack.reset();
});
</script>
<svelte:window on:keydown={handleShortcuts} />
<svelte:head>
{#if $modalStack.top}
<style>
body {
overflow: hidden;
}
</style>
{/if}
</svelte:head>
{#each $modalStack.stack || [] as modal (modal.id)}
{@const hidden = $modalStack.top?.group === modal.group && $modalStack.top?.id !== modal.id}
{#if modal.group === modal.id}
<div
class="fixed inset-0 bg-stone-950 bg-opacity-80 z-20"
transition:fade={{ duration: 150 }}
/>
{/if}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class={classNames(
'fixed inset-0 justify-center items-center z-20 overflow-hidden flex transition-opacity reltaive',
{
'opacity-0': hidden
}
)}
on:click|self={() => modalStack.close(modal.id)}
transition:fade|global={{ duration: 100 }}
>
<svelte:component this={modal.component} {...modal.props} modalId={modal.id} />
</div>
{/each}

View File

@@ -1,40 +0,0 @@
<script lang="ts">
import { onMount } from 'svelte';
import { modalStack } from './Modal';
// export let visible = false;
export let close: () => void;
export let id: Symbol;
function handleShortcuts(event: KeyboardEvent) {
if (event.key === 'Escape' && $modalStack.top === id) {
close();
}
}
onMount(() => {
modalStack.push(id);
});
</script>
<svelte:window on:keydown={handleShortcuts} />
<svelte:head>
{#if $modalStack.top}
<style>
body {
overflow: hidden;
}
</style>
{/if}
</svelte:head>
{#if $modalStack.top === id}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="fixed inset-0 justify-center items-center z-20 overflow-hidden flex"
on:click|self={close}
>
<slot />
</div>
{/if}

View File

@@ -1,56 +1,71 @@
import type { TitleType } from '$lib/types';
import { writable } from 'svelte/store';
import TitlePageModal from '../TitlePageLayout/TitlePageModal.svelte';
function createModalStack() {
const store = writable<{ stack: Symbol[]; top: Symbol | undefined }>({
type ModalItem = {
id: Symbol;
group: Symbol;
component: ConstructorOfATypedSvelteComponent;
props: Record<string, any>;
};
function createDynamicModalStack() {
const store = writable<{ stack: ModalItem[]; top: ModalItem | undefined }>({
stack: [],
top: undefined
});
store.subscribe(console.log);
function close(symbol: Symbol) {
store.update((s) => {
s.stack = s.stack.filter((i) => i.id !== symbol);
s.top = s.stack[s.stack.length - 1];
return s;
});
}
function closeGroup(group: Symbol) {
store.update((s) => {
s.stack = s.stack.filter((i) => i.group !== group);
s.top = s.stack[s.stack.length - 1];
return s;
});
}
function create(
component: ConstructorOfATypedSvelteComponent,
props: Record<string, any>,
group: Symbol | undefined = undefined
) {
const id = Symbol();
const item = { id, component, props, group: group || id };
store.update((s) => {
s.stack.push(item);
s.top = item;
return s;
});
return id;
}
function reset() {
store.set({ stack: [], top: undefined });
}
return {
...store,
push: (symbol: Symbol) => {
store.update((s) => {
if (s.stack.includes(symbol)) {
return s;
}
s.stack.push(symbol);
s.top = symbol;
return s;
});
},
remove: (symbol: Symbol) => {
store.update((s) => {
s.stack = s.stack.filter((x) => x !== symbol);
s.top = s.stack[s.stack.length - 1];
return s;
});
}
};
}
export const modalStack = createModalStack();
export type ModalProps = ReturnType<typeof createModalProps>;
export function createModalProps(onClose: () => void, onBack?: () => void) {
const id = Symbol();
function close() {
onClose(); // ORDER MATTERS HERE
modalStack.remove(id);
}
function back() {
onBack?.();
modalStack.remove(id);
}
return {
create,
close,
back: onBack ? back : undefined,
id
closeGroup,
reset
};
}
export const modalStack = createDynamicModalStack();
let lastTitleModal: Symbol | undefined = undefined;
export function openTitleModal(tmdbId: number, type: TitleType) {
if (lastTitleModal) {
modalStack.close(lastTitleModal);
}
lastTitleModal = modalStack.create(TitlePageModal, { tmdbId, type });
}

View File

@@ -1,11 +0,0 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import { modalStack } from './Modal';
</script>
{#if $modalStack.top}
<div
class="fixed inset-0 bg-stone-900 bg-opacity-50 z-[19] overflow-hidden"
transition:fade|global={{ duration: 100 }}
/>
{/if}