Popups

Setup

Jhana UI uses a singleton Svelte component. You have to include this once in your root layout. Then you can use the popup system across your whole app.

/routes/+layout.svelte

<script lang="ts">
	import { PopupRoot } from '$lib/popup';
</script>

<PopupRoot />

In case you're not happy with the default popup config, you can just set the values of the config object.

/lib/popups.ts

import { popupDefaults } from '$lib/popup';

// These values are the defaults:
popupDefaults.id = function genId() { return "X" + String(Math.random()).slice(-10); };
popupDefaults.size = 'full';
popupDefaults.class = '';
popupDefaults.style = '';
popupDefaults.closeButton = true;
popupDefaults.buttons = [];

// You might want this:
popupDefaults.buttons = [{ text: 'OK', primary: true }];

Usage

Once set up, you can just use the pop function anywhere. Either directly in the components or you might set up one file that contains all the popups your app uses.

/lib/popups/popup1.svelte

<script lang="ts">
	import { pop } from '$lib/popup';

	function shopPopup() {
		pop({
			heading: 'Test Popup',
			// size: 'auto',
		});
	}
</script>

<button class="btn" onclick={shopPopup}>Show popup</button>

Instead of the `pop` function you can also use Svelte syntax using the OpenPopup component. It reacts to mount and unmount.

/lib/popups/popup2.svelte

<script lang="ts">
	import { OpenPopup } from '$lib/popup';
	import Toggle from '$lib/Toggle.svelte';

	let isOpen = $state(false);
</script>

<Toggle label="Popup open" bind:checked={isOpen} />

{#if isOpen}
	<OpenPopup
		heading="I'm open now"
		size="auto"
		onclose={() => (isOpen = false)}
		buttons={[{ text: 'OK', primary: true, onclick: () => (isOpen = false) }]}
	>
		{#snippet content()}
			<div>
				<Toggle label="Popup open" bind:checked={isOpen} />
			</div>
		{/snippet}
	</OpenPopup>
{/if}

Here you can see nested popups in use. This shows a way to organize a collection of popups that work together.

/lib/popups/popup3.svelte

<script lang="ts">
	import { pop } from '$lib/popup';

	const defaultNote = 'Hello Universe!';
	let notes = $state([defaultNote]);
	let newNote = $state('');

	function showNoteManager() {
		pop({
			heading: 'Note Manager',
			size: 'full',
			content: noteManagerContent,
			buttons: [
				{ text: 'Add', onclick: showAddNote },
				{ text: 'Reset', onclick: showConfirmReset },
				{ text: 'Save', primary: true },
			],
		});
	}

	function showAddNote() {
		pop({
			heading: 'New Note',
			size: 'auto',
			content: addNoteContent,
			buttons: [{ text: 'Cancel' }, { text: 'Add', primary: true, onclick: addNewNote }],
		});
		return true;
	}

	function addNewNote() {
		if (newNote) notes.push(newNote);
		newNote = '';
	}

	function showConfirmReset() {
		pop({
			heading: 'Reset all Notes?',
			size: 'auto',
			content: 'This action is irreversible. Do you truely want to reset all your notes?',
			class: 'theme-error',
			buttons: [{ text: 'Cancel' }, { text: 'Reset', primary: true, onclick: resetAllNotes }],
		});
		return true;
	}

	function resetAllNotes() {
		notes = [defaultNote];
	}
</script>

<button class="btn" onclick={showNoteManager}>Open note manager</button>

{#snippet noteManagerContent()}
	<ul>
		{#each notes as note}
			<li class="note">{note}</li>
		{/each}
	</ul>
{/snippet}

{#snippet addNoteContent()}
	<input type="text" bind:value={newNote} class="input" />
{/snippet}

<style>
	:global(.theme-error) {
		--cAcc: var(--cErr);
	}

	li {
		display: block;
		padding: 0.5em 0.9em;
		margin-bottom: 1em;
	}
</style>