Simplify design
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import Modal from './Modal.svelte';
|
||||
import Dialog from './Dialog.svelte';
|
||||
import Button from '../primitives/Button.svelte';
|
||||
|
||||
interface Props {
|
||||
@@ -14,13 +14,11 @@
|
||||
let { open, onClose, title, description, confirmLabel, action }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Modal {open} {onClose} maxWidth="max-w-sm">
|
||||
<h2 class="text-display-3 text-fg">{title}</h2>
|
||||
<p class="mt-2.5 text-body text-fg-2">{description}</p>
|
||||
<div class="mt-6 flex items-center justify-end gap-3">
|
||||
<Dialog {open} {onClose} {title} {description}>
|
||||
{#snippet actions()}
|
||||
<Button variant="secondary" onclick={onClose}>Cancelar</Button>
|
||||
<form method="POST" {action}>
|
||||
<Button type="submit" variant="danger">{confirmLabel}</Button>
|
||||
</form>
|
||||
</div>
|
||||
</Modal>
|
||||
{/snippet}
|
||||
</Dialog>
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
class="flex items-center gap-3 rounded-control px-2 py-2.5 text-sm font-medium text-fg-2 transition group-hover/addmenu:bg-hover"
|
||||
>
|
||||
<span class="flex-1 cursor-default">{playlistAction.label}</span>
|
||||
<ChevronRight class="size-icon-sm shrink-0 text-fg-3" />
|
||||
<ChevronRight class="size-icon-sm shrink-0 text-fg-2" />
|
||||
</div>
|
||||
<div
|
||||
class="invisible absolute top-0 z-(--z-submenu) opacity-0 transition group-hover/addmenu:visible group-hover/addmenu:opacity-100 max-sm:top-full max-sm:right-0 max-sm:left-0 max-sm:px-0 max-sm:pt-1.5 {openLeft
|
||||
@@ -75,7 +75,7 @@
|
||||
>
|
||||
<div class="max-h-80 overflow-y-auto">
|
||||
{#if playlists.length === 0}
|
||||
<p class="px-2 py-2.5 text-sm text-fg-3">Todavía no tienes playlists.</p>
|
||||
<p class="px-2 py-2.5 text-sm text-fg-2">Todavía no tienes playlists.</p>
|
||||
{/if}
|
||||
{#each playlists as playlist (playlist.id)}
|
||||
<form
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import CircleAlert from '@lucide/svelte/icons/circle-alert';
|
||||
import Modal from './Modal.svelte';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
description?: string;
|
||||
tone?: 'neutral' | 'danger';
|
||||
children?: Snippet;
|
||||
actions?: Snippet;
|
||||
}
|
||||
|
||||
let { open, onClose, title, description, tone = 'neutral', children, actions }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
{open}
|
||||
{onClose}
|
||||
maxWidth="max-w-sm"
|
||||
panelClass={tone === 'danger' ? 'glass-panel-danger' : ''}
|
||||
>
|
||||
{#if tone === 'danger'}
|
||||
<span class="mb-4 grid size-10 place-items-center rounded-full bg-danger/12 text-danger-fg">
|
||||
<CircleAlert class="size-icon-md" strokeWidth={2} />
|
||||
</span>
|
||||
{/if}
|
||||
<h2 class="text-display-3 text-fg">{title}</h2>
|
||||
{#if description}
|
||||
<p class="mt-2.5 text-body text-fg-2">{description}</p>
|
||||
{/if}
|
||||
{@render children?.()}
|
||||
{#if actions}
|
||||
<div class="mt-6 flex flex-wrap items-center justify-end gap-3">
|
||||
{@render actions()}
|
||||
</div>
|
||||
{/if}
|
||||
</Modal>
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import Dialog from './Dialog.svelte';
|
||||
import Button from '../primitives/Button.svelte';
|
||||
import { dialog, type DialogAction, type DialogOptions } from '$lib/state/dialog.svelte';
|
||||
|
||||
// Keep the last dialog's content so the exit transition doesn't render an empty panel.
|
||||
let last = $state<DialogOptions | null>(null);
|
||||
$effect.pre(() => {
|
||||
if (dialog.current) last = dialog.current;
|
||||
});
|
||||
const current = $derived(dialog.current ?? last);
|
||||
const buttons: DialogAction[] = $derived(
|
||||
current?.actions?.length ? current.actions : [{ label: 'Entendido' }]
|
||||
);
|
||||
</script>
|
||||
|
||||
<Dialog
|
||||
open={dialog.current !== null}
|
||||
onClose={() => dialog.close()}
|
||||
title={current?.title ?? ''}
|
||||
description={current?.description}
|
||||
tone={current?.tone}
|
||||
>
|
||||
{#snippet actions()}
|
||||
{#each buttons as action, i (action.label)}
|
||||
<Button
|
||||
variant={action.variant ?? (i === buttons.length - 1 ? 'primary' : 'secondary')}
|
||||
onclick={() => dialog.run(action)}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
{/each}
|
||||
{/snippet}
|
||||
</Dialog>
|
||||
@@ -12,7 +12,9 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="glass-panel animate-pop rounded-panel p-1.5 {border ? 'border border-hairline' : ''} {klass}"
|
||||
class="glass-panel animate-pop rounded-panel p-1.5 {border
|
||||
? 'border border-hairline'
|
||||
: ''} {klass}"
|
||||
{style}
|
||||
>
|
||||
{@render children()}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
import type { Snippet } from 'svelte';
|
||||
import X from '@lucide/svelte/icons/x';
|
||||
import IconButton from '../primitives/IconButton.svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { expoOut } from 'svelte/easing';
|
||||
import type { TransitionConfig } from 'svelte/transition';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
@@ -45,6 +48,15 @@
|
||||
);
|
||||
}
|
||||
|
||||
function pop(node: Element, { duration = 200 }: { duration?: number } = {}): TransitionConfig {
|
||||
return {
|
||||
duration,
|
||||
easing: expoOut,
|
||||
css: (t) =>
|
||||
`opacity: ${t}; transform: scale(${0.98 + 0.02 * t}) translateY(${0.25 * (1 - t)}rem);`
|
||||
};
|
||||
}
|
||||
|
||||
function onWindowKeydown(event: KeyboardEvent) {
|
||||
if (!open) return;
|
||||
if (event.key === 'Escape') {
|
||||
@@ -70,13 +82,15 @@
|
||||
|
||||
{#if open}
|
||||
<div
|
||||
class="animate-fade fixed inset-0 z-(--z-modal) grid place-items-center bg-scrim/70 p-4 backdrop-blur-sm"
|
||||
transition:fade={{ duration: 200 }}
|
||||
class="fixed inset-0 z-(--z-modal) grid place-items-center bg-scrim/70 p-4 backdrop-blur-sm"
|
||||
role="presentation"
|
||||
onclick={onBackdropClick}
|
||||
>
|
||||
<div
|
||||
bind:this={panel}
|
||||
class="glass-panel animate-pop max-h-[85dvh] w-full overflow-y-auto {maxWidth} rounded-panel p-5 outline-none sm:p-6 {panelClass}"
|
||||
transition:pop={{ duration: 200 }}
|
||||
class="glass-panel max-h-[85dvh] w-full overflow-y-auto {maxWidth} rounded-panel p-5 outline-none sm:p-6 {panelClass}"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={title}
|
||||
@@ -86,7 +100,7 @@
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="min-w-0">
|
||||
{#if eyebrow}
|
||||
<p class="mb-2.5 text-eyebrow text-muted">
|
||||
<p class="mb-2.5 text-eyebrow text-fg-2">
|
||||
{eyebrow}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user