mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-13 13:48:28 +00:00
254 lines
14 KiB
Plaintext
254 lines
14 KiB
Plaintext
# Dialog
|
|
|
|
<Preview>
|
|
<button type="button" onclick="document.getElementById('demo-dialog-edit-profile').showModal()" class="btn-outline">Edit Profile</button>
|
|
<dialog id="demo-dialog-edit-profile" class="dialog" aria-labelledby="demo-dialog-edit-profile-title" aria-describedby="demo-dialog-edit-profile-description" onclick="if (event.target === this) this.close()">
|
|
<div>
|
|
<header>
|
|
<h2 id="demo-dialog-edit-profile-title">Edit profile</h2>
|
|
<p id="demo-dialog-edit-profile-description">Make changes to your profile here. Click save when you're done.</p>
|
|
</header>
|
|
<section>
|
|
<form class="grid gap-4">
|
|
<div class="grid gap-3">
|
|
<label class="label" for="demo-dialog-edit-profile-name">Name</label>
|
|
<input class="input" type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" autofocus />
|
|
</div>
|
|
<div class="grid gap-3">
|
|
<label class="label" for="demo-dialog-edit-profile-username">Username</label>
|
|
<input class="input" type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
|
|
</div>
|
|
</form>
|
|
</section>
|
|
<footer>
|
|
<button class="btn-outline" onclick="this.closest('dialog').close()">Cancel</button>
|
|
<button class="btn" onclick="this.closest('dialog').close()">Save changes</button>
|
|
</footer>
|
|
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
|
|
</button>
|
|
</div>
|
|
</dialog>
|
|
</Preview>
|
|
|
|
## Usage
|
|
|
|
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#dialog" }}>
|
|
This component ships a `dialog()` macro for Jinja and Nunjucks.
|
|
</Callout>
|
|
|
|
Basecoat uses the native `<dialog>` element and `showModal()`. This differs from shadcn/ui's portalled Base UI implementation, but preserves native modality, focus handling, and inert page content without component JavaScript.
|
|
|
|
```html
|
|
<button type="button" onclick="document.getElementById('demo-dialog-edit-profile').showModal()" class="btn-outline">Edit Profile</button>
|
|
<dialog id="demo-dialog-edit-profile" class="dialog" aria-labelledby="demo-dialog-edit-profile-title" aria-describedby="demo-dialog-edit-profile-description" onclick="if (event.target === this) this.close()">
|
|
<div>
|
|
<header>
|
|
<h2 id="demo-dialog-edit-profile-title">Edit profile</h2>
|
|
<p id="demo-dialog-edit-profile-description">Make changes to your profile here. Click save when you're done.</p>
|
|
</header>
|
|
<section>
|
|
<form class="grid gap-4">
|
|
<div class="grid gap-3">
|
|
<label class="label" for="demo-dialog-edit-profile-name">Name</label>
|
|
<input class="input" type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" autofocus />
|
|
</div>
|
|
<div class="grid gap-3">
|
|
<label class="label" for="demo-dialog-edit-profile-username">Username</label>
|
|
<input class="input" type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
|
|
</div>
|
|
</form>
|
|
</section>
|
|
<footer>
|
|
<button class="btn-outline" onclick="this.closest('dialog').close()">Cancel</button>
|
|
<button class="btn" onclick="this.closest('dialog').close()">Save changes</button>
|
|
</footer>
|
|
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
|
|
</button>
|
|
</div>
|
|
</dialog>
|
|
```
|
|
|
|
### HTML structure
|
|
|
|
<dl>
|
|
<dt><code class="highlight language-html"><button type="button" onclick="dialog.showModal()"></code> <span class="badge-secondary">Optional</span></dt>
|
|
<dd>Trigger button. Basecoat intentionally uses the native <code>HTMLDialogElement.showModal()</code> method.</dd>
|
|
<dt><code class="highlight language-html"><dialog class="dialog" id="{ DIALOG_ID }"></code></dt>
|
|
<dd>
|
|
Native modal dialog. Add <code>aria-labelledby="{ TITLE_ID }"</code> and <code>aria-describedby="{ DESCRIPTION_ID }"</code> when title and description are present. The macro also adds backdrop-click close handling.
|
|
<dl>
|
|
<dt><code class="highlight language-html"><div></code></dt>
|
|
<dd>
|
|
Dialog content surface.
|
|
<dl>
|
|
<dt><code class="highlight language-html"><header></code> <span class="badge-secondary">Optional</span></dt>
|
|
<dd>
|
|
Dialog header.
|
|
<dl>
|
|
<dt><code class="highlight language-html"><h2 id="{ TITLE_ID }"></code></dt>
|
|
<dd>Dialog title. Reference it from <code>aria-labelledby</code>.</dd>
|
|
<dt><code class="highlight language-html"><p id="{ DESCRIPTION_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
|
<dd>Dialog description. Reference it from <code>aria-describedby</code>.</dd>
|
|
</dl>
|
|
</dd>
|
|
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
|
<dd>Dialog body/content area. Add overflow utilities when the body should scroll.</dd>
|
|
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
|
<dd>Action area. It stacks actions on small screens and aligns them to the end on larger screens.</dd>
|
|
<dt><code class="highlight language-html"><button type="button" onclick="this.closest('dialog').close()"></code> <span class="badge-secondary">Optional</span></dt>
|
|
<dd>Close button. You can also wrap a button in <code><form method="dialog"></code>.</dd>
|
|
</dl>
|
|
</dd>
|
|
</dl>
|
|
</dd>
|
|
</dl>
|
|
|
|
## Examples
|
|
|
|
### Custom Close Button
|
|
|
|
<Preview>
|
|
<button type="button" onclick="document.getElementById('dialog-custom-close').showModal()" class="btn-outline">Open</button>
|
|
<dialog id="dialog-custom-close" class="dialog" aria-labelledby="dialog-custom-close-title" aria-describedby="dialog-custom-close-description" onclick="if (event.target === this) this.close()">
|
|
<div>
|
|
<header>
|
|
<h2 id="dialog-custom-close-title">Custom close button</h2>
|
|
<p id="dialog-custom-close-description">The default close icon is hidden and the footer provides the close action.</p>
|
|
</header>
|
|
<section>
|
|
<p class="text-sm text-muted-foreground">
|
|
Use
|
|
<code>close_button=false</code>
|
|
and add a close control where it fits your layout.
|
|
</p>
|
|
</section>
|
|
<footer>
|
|
<button class="btn-outline" onclick="this.closest('dialog').close()">Close</button>
|
|
<button class="btn" onclick="this.closest('dialog').close()">Continue</button>
|
|
</footer>
|
|
</div>
|
|
</dialog>
|
|
</Preview>
|
|
|
|
### No Close Button
|
|
|
|
<Preview>
|
|
<button type="button" onclick="document.getElementById('dialog-no-close').showModal()" class="btn-outline">Open</button>
|
|
<dialog id="dialog-no-close" class="dialog" aria-labelledby="dialog-no-close-title" aria-describedby="dialog-no-close-description" onclick="if (event.target === this) this.close()">
|
|
<div>
|
|
<header>
|
|
<h2 id="dialog-no-close-title">No close button</h2>
|
|
<p id="dialog-no-close-description">The dialog can still be closed with Escape or a custom action.</p>
|
|
</header>
|
|
<section>
|
|
<p class="text-sm text-muted-foreground">Native dialogs remain keyboard-accessible even without the default close icon.</p>
|
|
</section>
|
|
<footer>
|
|
<button class="btn" onclick="this.closest('dialog').close()">I understand</button>
|
|
</footer>
|
|
</div>
|
|
</dialog>
|
|
</Preview>
|
|
|
|
### Sticky Footer
|
|
|
|
<Preview>
|
|
<button type="button" onclick="document.getElementById('dialog-sticky-footer').showModal()" class="btn-outline">Sticky Footer</button>
|
|
<dialog id="dialog-sticky-footer" class="dialog" aria-labelledby="dialog-sticky-footer-title" aria-describedby="dialog-sticky-footer-description" onclick="if (event.target === this) this.close()">
|
|
<div>
|
|
<header>
|
|
<h2 id="dialog-sticky-footer-title">Sticky footer</h2>
|
|
<p id="dialog-sticky-footer-description">Keep actions visible while the body scrolls.</p>
|
|
</header>
|
|
<section class="overflow-y-auto scrollbar">
|
|
<div class="space-y-4 text-sm text-muted-foreground">
|
|
<p>Dialog content row 1. Add enough content to make the body scroll while the footer remains available.</p>
|
|
<p>Dialog content row 2. Add enough content to make the body scroll while the footer remains available.</p>
|
|
<p>Dialog content row 3. Add enough content to make the body scroll while the footer remains available.</p>
|
|
<p>Dialog content row 4. Add enough content to make the body scroll while the footer remains available.</p>
|
|
<p>Dialog content row 5. Add enough content to make the body scroll while the footer remains available.</p>
|
|
<p>Dialog content row 6. Add enough content to make the body scroll while the footer remains available.</p>
|
|
<p>Dialog content row 7. Add enough content to make the body scroll while the footer remains available.</p>
|
|
<p>Dialog content row 8. Add enough content to make the body scroll while the footer remains available.</p>
|
|
</div>
|
|
</section>
|
|
<footer class="sticky bottom-0">
|
|
<button class="btn-outline" onclick="this.closest('dialog').close()">Cancel</button>
|
|
<button class="btn" onclick="this.closest('dialog').close()">Save</button>
|
|
</footer>
|
|
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
|
|
</button>
|
|
</div>
|
|
</dialog>
|
|
</Preview>
|
|
|
|
### Scrollable Content
|
|
|
|
<Preview>
|
|
<button type="button" onclick="document.getElementById('dialog-scrollable').showModal()" class="btn-outline">Scrollable Content</button>
|
|
<dialog id="dialog-scrollable" class="dialog" aria-labelledby="dialog-scrollable-title" aria-describedby="dialog-scrollable-description" onclick="if (event.target === this) this.close()">
|
|
<div>
|
|
<header>
|
|
<h2 id="dialog-scrollable-title">Scrollable Content</h2>
|
|
<p id="dialog-scrollable-description">Long content can scroll inside the dialog body.</p>
|
|
</header>
|
|
<section class="overflow-y-auto scrollbar">
|
|
<div class="space-y-4 text-sm text-muted-foreground">
|
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 1.</p>
|
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 2.</p>
|
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 3.</p>
|
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 4.</p>
|
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 5.</p>
|
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 6.</p>
|
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 7.</p>
|
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 8.</p>
|
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 9.</p>
|
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 10.</p>
|
|
</div>
|
|
</section>
|
|
<footer>
|
|
<button class="btn-outline" onclick="this.closest('dialog').close()">Close</button>
|
|
</footer>
|
|
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
|
|
</button>
|
|
</div>
|
|
</dialog>
|
|
</Preview>
|
|
|
|
### RTL
|
|
|
|
Dialog positioning and close button placement use logical properties. Set `dir="rtl"` on the dialog or a parent element.
|
|
|
|
<Preview>
|
|
<div dir="rtl">
|
|
<button type="button" onclick="document.getElementById('dialog-rtl').showModal()" class="btn-outline">فتح</button>
|
|
<dialog id="dialog-rtl" class="dialog" aria-labelledby="dialog-rtl-title" aria-describedby="dialog-rtl-description" onclick="if (event.target === this) this.close()">
|
|
<div>
|
|
<header>
|
|
<h2 id="dialog-rtl-title">تعديل الملف الشخصي</h2>
|
|
<p id="dialog-rtl-description">قم بتحديث بياناتك ثم احفظ التغييرات.</p>
|
|
</header>
|
|
<section>
|
|
<form class="grid gap-4">
|
|
<div class="grid gap-3">
|
|
<label class="label" for="dialog-rtl-name">الاسم</label>
|
|
<input class="input" id="dialog-rtl-name" value="ليلى" />
|
|
</div>
|
|
</form>
|
|
</section>
|
|
<footer>
|
|
<button class="btn-outline" onclick="this.closest('dialog').close()">إلغاء</button>
|
|
<button class="btn" onclick="this.closest('dialog').close()">حفظ</button>
|
|
</footer>
|
|
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
|
|
</button>
|
|
</div>
|
|
</dialog>
|
|
</div>
|
|
</Preview>
|