dialog thingy yay

This commit is contained in:
Thomas G. Lopes 2025-06-16 21:16:46 +01:00
parent 9bc1b3766c
commit fff9f56fea
7 changed files with 115 additions and 15 deletions

View file

@ -315,16 +315,15 @@
}
.modal-box {
@apply col-start-1 row-start-1 max-h-screen w-11/12 max-w-[32rem] bg-neutral-100 p-6;
@apply bg-background border-border col-start-1 row-start-1 max-h-screen w-11/12 max-w-[32rem] p-6;
transition:
translate 0.3s ease-out,
scale 0.3s ease-out,
opacity 0.2s ease-out 0.05s,
box-shadow 0.3s ease-out;
border-top-left-radius: var(--modal-tl, var(--radius-box));
border-top-right-radius: var(--modal-tr, var(--radius-box));
border-bottom-left-radius: var(--modal-bl, var(--radius-box));
border-bottom-right-radius: var(--modal-br, var(--radius-box));
border-radius: var(--radius);
scale: 95%;
opacity: 0;
box-shadow: oklch(0% 0 0/ 0.25) 0px 25px 50px -12px;

View file

@ -0,0 +1,56 @@
<script lang="ts" module>
import type { ButtonVariant } from '../button';
// We can extend the generics to include form fields if needed
type CallModalArgs<Action extends string> = {
title: string;
description: string;
actions?: Record<Action, ButtonVariant>;
};
let modalArgs = $state(null) as null | CallModalArgs<string>;
let resolve: (v: string | null) => void;
export function callModal<Action extends string>(
args: CallModalArgs<Action>
): Promise<Action | null> {
modalArgs = args;
return new Promise<Action | null>((res) => {
resolve = res as (v: string | null) => void;
});
}
</script>
<script lang="ts">
import Button from '../button/button.svelte';
import Modal from './modal.svelte';
let open = $derived(!!modalArgs);
</script>
<Modal
bind:open={
() => open,
(v) => {
if (v) return;
open = false;
setTimeout(() => (modalArgs = null), 200);
resolve?.(null);
}
}
>
<h3 class="text-lg font-bold">{modalArgs?.title}</h3>
<p class="py-4">{modalArgs?.description}</p>
{#if modalArgs?.actions}
<div class="modal-action">
{#each Object.entries(modalArgs.actions) as [action, variant] (action)}
<form method="dialog" onsubmit={() => resolve(action)}>
<Button {variant} type="submit" class="capitalize">
{action}
</Button>
</form>
{/each}
</div>
{/if}
</Modal>

View file

@ -21,15 +21,12 @@
</script>
<dialog class="modal" bind:this={dialog} onclose={() => (open = false)}>
<div class="modal-body" {@attach clickOutside(() => (open = false))}>
<h3 class="text-lg font-bold">Hello!</h3>
<p class="py-4">Press ESC key or click the button below to close</p>
<div
class="modal-box"
{@attach clickOutside(() => {
if (open) open = false;
})}
>
{@render children()}
<div class="modal-action">
<form method="dialog">
<!-- if there is a button in form, it will close the modal -->
<button class="btn">Close</button>
</form>
</div>
</div>
</dialog>

View file

@ -20,3 +20,39 @@ export function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pi
K
>;
}
/**
* Transforms an object into a new object by applying a mapping function
* to each of its key-value pairs.
*
* @template KIn The type of the keys in the input object.
* @template VIn The type of the values in the input object.
* @template KOut The type of the keys in the output object.
* @template VOut The type of the values in the output object.
*
* @param obj The input object to transform.
* @param mapper A function that takes a key and its value from the input object
* and returns a tuple `[KOut, VOut]` representing the new key
* and new value for the output object.
* @returns A new object with the transformed keys and values.
*/
export function objectMap<
KIn extends string | number | symbol,
VIn,
KOut extends string | number | symbol,
VOut,
>(obj: Record<KIn, VIn>, mapper: (key: KIn, value: VIn) => [KOut, VOut]): Record<KOut, VOut> {
const result: Record<KOut, VOut> = {} as Record<KOut, VOut>;
for (const rawKey in obj) {
// Ensure we only process own properties (not inherited ones)
if (Object.prototype.hasOwnProperty.call(obj, rawKey)) {
const key = rawKey as KIn; // Cast to KIn as rawKey is initially string
const value = obj[key];
const [newKey, newValue] = mapper(key, value);
result[newKey] = newValue;
}
}
return result;
}

View file

@ -4,6 +4,7 @@
import { ModeWatcher } from 'mode-watcher';
import { PUBLIC_CONVEX_URL } from '$env/static/public';
import { models } from '$lib/state/models.svelte';
import GlobalModal from '$lib/components/ui/modal/global-modal.svelte';
let { children } = $props();
@ -13,3 +14,5 @@
<ModeWatcher />
{@render children()}
<GlobalModal />

View file

@ -10,6 +10,7 @@
import { LocalToasts } from '$lib/builders/local-toasts.svelte';
import { ResultAsync } from 'neverthrow';
import TrashIcon from '~icons/lucide/trash';
import { callModal } from '$lib/components/ui/modal/global-modal.svelte';
type Props = {
rule: Doc<'user_rules'>;
@ -57,6 +58,15 @@
}
async function deleteRule() {
const action = await callModal({
title: 'Delete Rule',
description: 'Are you sure you want to delete this rule?',
actions: {
delete: 'destructive',
},
});
if (action !== 'delete') return;
deleting = true;
await client.mutation(api.user_rules.remove, {

View file

@ -39,7 +39,6 @@
async function toggleEnabled(v: boolean) {
enabled = v; // Optimistic!
console.log('hi');
if (!session.current?.user.id) return;
const res = await ResultAsync.fromPromise(