You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
400 lines
15 KiB
400 lines
15 KiB
<script lang="ts">
|
|
import { AlertDialog } from '$uix/acel';
|
|
|
|
let controlledOpen = $state(false);
|
|
let lastAction = $state<string | null>(null);
|
|
|
|
function setAction(label: string) {
|
|
lastAction = label;
|
|
setTimeout(() => lastAction = null, 2500);
|
|
}
|
|
</script>
|
|
|
|
<div class="page">
|
|
|
|
<header class="ph">
|
|
<div class="ph-inner">
|
|
<div class="ph-brand">
|
|
<span class="ph-dot"></span>
|
|
<span class="ph-title">alert<em>dialog</em></span>
|
|
</div>
|
|
<span class="ph-sub">acel · primitivo de confirmación</span>
|
|
</div>
|
|
</header>
|
|
|
|
<main>
|
|
|
|
<!-- ── STATUS BAR ─────────────────────────────────────────────────── -->
|
|
<div class="status-bar" class:status-bar--active={lastAction}>
|
|
{#if lastAction}
|
|
<span class="status-dot status-dot--on"></span>
|
|
<span>acción ejecutada: <strong>{lastAction}</strong></span>
|
|
{:else}
|
|
<span class="status-dot"></span>
|
|
<span class="muted">esperando interacción…</span>
|
|
{/if}
|
|
</div>
|
|
|
|
<div class="grid">
|
|
|
|
<!-- ── 1. DESTRUCTIVE ──────────────────────────────────────────── -->
|
|
<section class="card" data-color="red">
|
|
<header class="card-hd">
|
|
<span class="tag">01</span>
|
|
<span class="card-title">Acción destructiva</span>
|
|
</header>
|
|
<p class="card-desc">Caso clásico: eliminar cuenta. No se puede cerrar con Escape.</p>
|
|
|
|
<AlertDialog.Root>
|
|
<AlertDialog.Trigger class="btn btn--red">
|
|
Eliminar cuenta
|
|
</AlertDialog.Trigger>
|
|
<AlertDialog.Portal>
|
|
<AlertDialog.Overlay class="overlay" />
|
|
<AlertDialog.Content class="dialog">
|
|
<AlertDialog.Title class="dialog-title">
|
|
¿Eliminar cuenta permanentemente?
|
|
</AlertDialog.Title>
|
|
<AlertDialog.Description class="dialog-desc">
|
|
Esta acción no se puede deshacer. Se eliminarán todos tus datos,
|
|
configuraciones e historial de forma permanente.
|
|
</AlertDialog.Description>
|
|
<div class="dialog-footer">
|
|
<AlertDialog.Cancel class="btn btn--ghost" onclick={() => setAction('cancel — delete account')}>
|
|
Cancelar
|
|
</AlertDialog.Cancel>
|
|
<AlertDialog.Action class="btn btn--red" onclick={() => setAction('confirm — delete account')}>
|
|
Sí, eliminar
|
|
</AlertDialog.Action>
|
|
</div>
|
|
</AlertDialog.Content>
|
|
</AlertDialog.Portal>
|
|
</AlertDialog.Root>
|
|
</section>
|
|
|
|
<!-- ── 2. CONFIRMACIÓN NEUTRAL ────────────────────────────────── -->
|
|
<section class="card" data-color="amber">
|
|
<header class="card-hd">
|
|
<span class="tag">02</span>
|
|
<span class="card-title">Confirmación neutral</span>
|
|
</header>
|
|
<p class="card-desc">Confirmar una acción significativa sin ser destructiva.</p>
|
|
|
|
<AlertDialog.Root>
|
|
<AlertDialog.Trigger class="btn btn--amber">
|
|
Publicar cambios
|
|
</AlertDialog.Trigger>
|
|
<AlertDialog.Portal>
|
|
<AlertDialog.Overlay class="overlay" />
|
|
<AlertDialog.Content class="dialog">
|
|
<AlertDialog.Title class="dialog-title">
|
|
¿Publicar estos cambios?
|
|
</AlertDialog.Title>
|
|
<AlertDialog.Description class="dialog-desc">
|
|
Los cambios serán visibles para todos los usuarios en tiempo real.
|
|
Asegúrate de haberlos revisado antes de continuar.
|
|
</AlertDialog.Description>
|
|
<div class="dialog-footer">
|
|
<AlertDialog.Cancel class="btn btn--ghost" onclick={() => setAction('cancel — publish')}>
|
|
Revisar antes
|
|
</AlertDialog.Cancel>
|
|
<AlertDialog.Action class="btn btn--amber" onclick={() => setAction('confirm — publish')}>
|
|
Publicar ahora
|
|
</AlertDialog.Action>
|
|
</div>
|
|
</AlertDialog.Content>
|
|
</AlertDialog.Portal>
|
|
</AlertDialog.Root>
|
|
</section>
|
|
|
|
<!-- ── 3. SOLO CANCEL ─────────────────────────────────────────── -->
|
|
<section class="card" data-color="blue">
|
|
<header class="card-hd">
|
|
<span class="tag">03</span>
|
|
<span class="card-title">Solo cancelar (aviso)</span>
|
|
</header>
|
|
<p class="card-desc">Diálogo informativo que solo permite confirmar lectura.</p>
|
|
|
|
<AlertDialog.Root>
|
|
<AlertDialog.Trigger class="btn btn--blue">
|
|
Ver aviso legal
|
|
</AlertDialog.Trigger>
|
|
<AlertDialog.Portal>
|
|
<AlertDialog.Overlay class="overlay" />
|
|
<AlertDialog.Content class="dialog">
|
|
<AlertDialog.Title class="dialog-title">
|
|
Aviso importante
|
|
</AlertDialog.Title>
|
|
<AlertDialog.Description class="dialog-desc">
|
|
Este sistema está en fase beta. Algunos datos pueden no estar
|
|
disponibles hasta la versión final. Las funcionalidades pueden
|
|
cambiar sin previo aviso.
|
|
</AlertDialog.Description>
|
|
<div class="dialog-footer dialog-footer--center">
|
|
<AlertDialog.Cancel class="btn btn--blue" onclick={() => setAction('acknowledged — notice')}>
|
|
Entendido
|
|
</AlertDialog.Cancel>
|
|
</div>
|
|
</AlertDialog.Content>
|
|
</AlertDialog.Portal>
|
|
</AlertDialog.Root>
|
|
</section>
|
|
|
|
<!-- ── 4. CONTROLLED ─────────────────────────────────────────── -->
|
|
<section class="card" data-color="violet">
|
|
<header class="card-hd">
|
|
<span class="tag">04</span>
|
|
<span class="card-title">Controlado externamente</span>
|
|
</header>
|
|
<p class="card-desc">El estado <code>open</code> es controlado desde fuera del componente.</p>
|
|
|
|
<div class="state-row">
|
|
<span class="state-label">open:</span>
|
|
<span class="state-val" class:state-val--on={controlledOpen}>{controlledOpen}</span>
|
|
</div>
|
|
|
|
<div class="btn-row">
|
|
<button class="btn btn--violet" onclick={() => controlledOpen = true}>
|
|
Abrir
|
|
</button>
|
|
<button class="btn btn--ghost" onclick={() => controlledOpen = false}>
|
|
Cerrar
|
|
</button>
|
|
</div>
|
|
|
|
<AlertDialog.Root bind:open={controlledOpen}>
|
|
<AlertDialog.Portal>
|
|
<AlertDialog.Overlay class="overlay" />
|
|
<AlertDialog.Content class="dialog">
|
|
<AlertDialog.Title class="dialog-title">
|
|
Diálogo controlado
|
|
</AlertDialog.Title>
|
|
<AlertDialog.Description class="dialog-desc">
|
|
Este diálogo fue abierto programáticamente. Su estado
|
|
<code>open</code> es gestionado por la variable reactiva
|
|
del componente padre.
|
|
</AlertDialog.Description>
|
|
<div class="dialog-footer">
|
|
<AlertDialog.Cancel class="btn btn--ghost" onclick={() => setAction('cancel — controlled')}>
|
|
Cerrar
|
|
</AlertDialog.Cancel>
|
|
<AlertDialog.Action class="btn btn--violet" onclick={() => setAction('confirm — controlled')}>
|
|
Confirmar
|
|
</AlertDialog.Action>
|
|
</div>
|
|
</AlertDialog.Content>
|
|
</AlertDialog.Portal>
|
|
</AlertDialog.Root>
|
|
</section>
|
|
|
|
<!-- ── 5. SIN PORTAL ─────────────────────────────────────────── -->
|
|
<section class="card" data-color="green">
|
|
<header class="card-hd">
|
|
<span class="tag">05</span>
|
|
<span class="card-title">Sin Portal</span>
|
|
</header>
|
|
<p class="card-desc">Content renderizado inline (sin teleport), útil para pruebas.</p>
|
|
|
|
<AlertDialog.Root>
|
|
<AlertDialog.Trigger class="btn btn--green">
|
|
Abrir inline
|
|
</AlertDialog.Trigger>
|
|
<AlertDialog.Overlay class="overlay" />
|
|
<AlertDialog.Content class="dialog">
|
|
<AlertDialog.Title class="dialog-title">
|
|
Diálogo inline
|
|
</AlertDialog.Title>
|
|
<AlertDialog.Description class="dialog-desc">
|
|
Este diálogo no usa Portal, el DOM se inserta en el árbol del
|
|
componente actual en lugar de en el body.
|
|
</AlertDialog.Description>
|
|
<div class="dialog-footer">
|
|
<AlertDialog.Cancel class="btn btn--ghost" onclick={() => setAction('cancel — inline')}>
|
|
Cancelar
|
|
</AlertDialog.Cancel>
|
|
<AlertDialog.Action class="btn btn--green" onclick={() => setAction('confirm — inline')}>
|
|
OK
|
|
</AlertDialog.Action>
|
|
</div>
|
|
</AlertDialog.Content>
|
|
</AlertDialog.Root>
|
|
</section>
|
|
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<style>
|
|
:global(*, *::before, *::after) { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
.page {
|
|
min-height : 100vh;
|
|
background : #0e0e14;
|
|
color : #d4d2cc;
|
|
font-family : 'DM Mono', 'Fira Code', monospace;
|
|
font-size : 13px;
|
|
}
|
|
|
|
/* ── PAGE HEADER ─────────────────────────────────────────────────────── */
|
|
.ph {
|
|
border-bottom: 2px solid #e5a100;
|
|
background : #080810;
|
|
}
|
|
.ph-inner {
|
|
max-width : 1200px; margin: 0 auto;
|
|
padding : .75rem 1.5rem;
|
|
display : flex; align-items: center; justify-content: space-between;
|
|
}
|
|
.ph-brand { display: flex; align-items: center; gap: .5rem; }
|
|
.ph-dot { width: 8px; height: 8px; border-radius: 50%; background: #e5a100; }
|
|
.ph-title {
|
|
font-size: .85rem; font-weight: 700;
|
|
letter-spacing: .15em; text-transform: uppercase; color: #f4f3ef;
|
|
}
|
|
.ph-title em { color: #e5a100; font-style: normal; }
|
|
.ph-sub { font-size: .65rem; color: #555; letter-spacing: .1em; }
|
|
|
|
/* ── MAIN ────────────────────────────────────────────────────────────── */
|
|
main {
|
|
max-width: 1200px; margin: 0 auto;
|
|
padding : 1.5rem;
|
|
display : flex; flex-direction: column; gap: 1rem;
|
|
}
|
|
|
|
/* ── STATUS BAR ──────────────────────────────────────────────────────── */
|
|
.status-bar {
|
|
display : flex; align-items: center; gap: .6rem;
|
|
padding : .5rem .85rem;
|
|
background : #16161f;
|
|
border : 1px solid #26262f;
|
|
border-radius: 4px;
|
|
font-size : .72rem;
|
|
transition : border-color .2s;
|
|
}
|
|
.status-bar--active { border-color: #e5a100; }
|
|
.status-bar strong { color: #e5a100; }
|
|
|
|
.status-dot {
|
|
width: 6px; height: 6px; border-radius: 50%;
|
|
background: #333; flex-shrink: 0;
|
|
transition: background .2s;
|
|
}
|
|
.status-dot--on { background: #e5a100; box-shadow: 0 0 6px #e5a100; }
|
|
.muted { color: #444; }
|
|
|
|
/* ── GRID ────────────────────────────────────────────────────────────── */
|
|
.grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
gap: .75rem;
|
|
}
|
|
|
|
/* ── CARDS ───────────────────────────────────────────────────────────── */
|
|
.card {
|
|
background : #16161f;
|
|
border : 1px solid #26262f;
|
|
border-radius: 6px;
|
|
padding : 1.1rem;
|
|
display : flex; flex-direction: column; gap: .75rem;
|
|
border-top : 3px solid transparent;
|
|
}
|
|
.card[data-color="red"] { border-top-color: #f87171; }
|
|
.card[data-color="amber"] { border-top-color: #e5a100; }
|
|
.card[data-color="blue"] { border-top-color: #60a5fa; }
|
|
.card[data-color="violet"] { border-top-color: #a78bfa; }
|
|
.card[data-color="green"] { border-top-color: #4ade80; }
|
|
|
|
.card-hd {
|
|
display : flex; align-items: center; gap: .5rem;
|
|
padding-bottom: .6rem;
|
|
border-bottom: 1px solid #26262f;
|
|
}
|
|
.tag {
|
|
font-size : .6rem; font-weight: 700; letter-spacing: .15em;
|
|
color : #555;
|
|
}
|
|
.card-title { font-size: .75rem; font-weight: 600; color: #aaa; }
|
|
.card-desc { font-size: .72rem; color: #555; line-height: 1.5; }
|
|
|
|
/* ── STATE ROW ───────────────────────────────────────────────────────── */
|
|
.state-row { display: flex; align-items: center; gap: .5rem; font-size: .72rem; }
|
|
.state-label { color: #555; }
|
|
.state-val { color: #444; font-weight: 700; transition: color .2s; }
|
|
.state-val--on { color: #a78bfa; }
|
|
|
|
.btn-row { display: flex; gap: .4rem; }
|
|
|
|
/* ── BUTTONS ─────────────────────────────────────────────────────────── */
|
|
:global(.btn) {
|
|
padding : .35rem .8rem;
|
|
border : none; border-radius: 3px;
|
|
font-family: 'DM Mono', 'Fira Code', monospace;
|
|
font-size : .72rem; font-weight: 700;
|
|
letter-spacing: .05em; cursor: pointer;
|
|
transition : opacity .12s;
|
|
}
|
|
:global(.btn:hover) { opacity: .8; }
|
|
|
|
:global(.btn--red) { background: #7f1d1d; color: #fca5a5; }
|
|
:global(.btn--amber) { background: #78350f; color: #fcd34d; }
|
|
:global(.btn--blue) { background: #1e3a5f; color: #93c5fd; }
|
|
:global(.btn--violet) { background: #2e1065; color: #c4b5fd; }
|
|
:global(.btn--green) { background: #14532d; color: #86efac; }
|
|
:global(.btn--ghost) {
|
|
background: transparent;
|
|
border : 1px solid #333; color: #666;
|
|
font-weight: 500;
|
|
}
|
|
:global(.btn--ghost:hover) { border-color: #555; color: #999; opacity: 1; }
|
|
|
|
/* ── OVERLAY ─────────────────────────────────────────────────────────── */
|
|
:global(.overlay) {
|
|
position : fixed; inset: 0;
|
|
background: rgba(0, 0, 0, .65);
|
|
backdrop-filter: blur(2px);
|
|
z-index : 50;
|
|
}
|
|
|
|
/* ── DIALOG ──────────────────────────────────────────────────────────── */
|
|
:global(.dialog) {
|
|
position : fixed;
|
|
top : 50%; left: 50%;
|
|
transform : translate(-50%, -50%);
|
|
z-index : 51;
|
|
width : min(480px, calc(100vw - 2rem));
|
|
background: #1a1a24;
|
|
border : 1px solid #2e2e3e;
|
|
border-radius: 8px;
|
|
padding : 1.75rem;
|
|
display : flex; flex-direction: column; gap: 1rem;
|
|
box-shadow: 0 24px 60px rgba(0,0,0,.6);
|
|
}
|
|
|
|
:global(.dialog-title) {
|
|
font-size : .95rem; font-weight: 700;
|
|
color : #f0ede6;
|
|
letter-spacing: .02em;
|
|
}
|
|
|
|
:global(.dialog-desc) {
|
|
font-size : .78rem; color: #777;
|
|
line-height: 1.65;
|
|
}
|
|
|
|
:global(.dialog-footer) {
|
|
display : flex; justify-content: flex-end; gap: .5rem;
|
|
padding-top: .5rem;
|
|
border-top : 1px solid #26262f;
|
|
}
|
|
:global(.dialog-footer--center) { justify-content: center; }
|
|
|
|
code {
|
|
background : #26262f;
|
|
padding : .1rem .35rem;
|
|
border-radius: 2px;
|
|
font-size : .7rem;
|
|
color : #a78bfa;
|
|
font-family : inherit;
|
|
}
|
|
</style>
|