ph/components

Dialog

Modal dialog.

RSVP

You're in for Hack Night this Friday. Bring a laptop and an idea.

Code

Copy into src/components/ph/ .

dialog.css
.ph-dialog {
  width: min(480px, calc(100vw - 32px));
  max-height: calc(100dvh - 64px);
  margin: auto;
  padding: 0;
  border: 1px solid var(--foreground);
  border-radius: 0;
  background: var(--background);
  color: var(--foreground);
  opacity: 0;
  scale: 0.97;
  transition:
    opacity 150ms var(--ph-ease),
    scale 150ms var(--ph-ease),
    overlay 150ms allow-discrete,
    display 150ms allow-discrete;
}

.ph-dialog[open] {
  opacity: 1;
  scale: 1;
}

@starting-style {
  .ph-dialog[open] {
    opacity: 0;
    scale: 0.97;
  }
}

.ph-dialog::backdrop {
  background: rgb(0 0 0 / 0);
  transition:
    background 150ms var(--ph-ease),
    overlay 150ms allow-discrete,
    display 150ms allow-discrete;
}

.ph-dialog[open]::backdrop {
  background: rgb(0 0 0 / 0.4);
}

@starting-style {
  .ph-dialog[open]::backdrop {
    background: rgb(0 0 0 / 0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .ph-dialog,
  .ph-dialog::backdrop {
    transition: none;
  }
}
Dialog.astro
---
import Icon from "./Icon.astro";
import "./dialog.css";

type Props = {
  id: string;
  title: string;
  class?: string;
};

const { id, title, class: className } = Astro.props;
---

<dialog id={id} data-ph-dialog aria-labelledby={`${id}-title`} class:list={["ph-dialog", className]}>
  <div class="flex items-center justify-between gap-4 border-b border-foreground px-5 py-3">
    <h2 id={`${id}-title`} class="ph-subheadline text-[13px]">{title}</h2>
    <button type="button" data-ph-dialog-close aria-label="Close" class="-mr-1.5 cursor-pointer p-1.5 hover:bg-foreground/[0.08]">
      <Icon name="times" class="size-3.5" />
    </button>
  </div>
  <div class="flex flex-col gap-3 px-5 py-4 leading-[1.5]"><slot /></div>
</dialog>

<script>
  document.addEventListener("click", (e) => {
    const target = e.target as HTMLElement;
    const opener = target.closest<HTMLElement>("[data-ph-dialog-open]");
    if (opener) {
      document.querySelector<HTMLDialogElement>(`#${opener.dataset.phDialogOpen}`)?.showModal();
      return;
    }
    if (target.closest("[data-ph-dialog-close]")) {
      target.closest("dialog")?.close();
      return;
    }
    if (target instanceof HTMLDialogElement && target.matches("[data-ph-dialog]")) target.close();
  });
</script>

Also needs