Add AlertDialog to replace confirm() and alert()

`AlertDialog.confirm()` and `AlertDialog.alert()` open a small modal from
JavaScript and return a Promise. Options: `title`, `message`, `confirmText`,
`cancelText`, `variant`, `icon`, `align` and `focus`.

Also fixes `.modal-status` so the top bar follows the rounded corners, raises
the `tabler.js` size budget from 58 kB to 60 kB, and adds a docs page, a demo
page and a screenshot page.
This commit is contained in:
Bartek committed 2026-09-30 12:49:26 +02:00
1 parent 9c7afc5002
commit 7118fa31ce
12 files changed
+578 -4

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
---
"@tabler/core": minor
"@tabler/preview": minor
"@tabler/docs": minor
---
Added `AlertDialog` with `confirm()` and `alert()` methods, a modal replacement for `window.confirm()` and `window.alert()`.
+5
View File
@@ -0,0 +1,5 @@
---
"@tabler/core": patch
---
Fixed `.modal-status` so the colored top bar follows the rounded corners of `.modal-content`.
+90
View File
@@ -0,0 +1,90 @@
import { Modal } from './bootstrap'
export type AlertDialogOptions = {
title?: string
message?: string
confirmText?: string
cancelText?: string
variant?: 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'danger'
focus?: 'confirm' | 'cancel'
icon?: string
align?: 'center' | 'start'
}
const ICONS: Record<string, string> = {
'trash': 'M4 7h16M10 11v6M14 11v6M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-12M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3',
'alert-triangle': 'M12 9v4M10.363 3.591l-8.106 13.534a1.914 1.914 0 0 0 1.636 2.871h16.214a1.914 1.914 0 0 0 1.636-2.87l-8.106-13.536a1.914 1.914 0 0 0-3.274 0M12 16h.01',
'circle-check': 'M3 12a9 9 0 1 0 18 0a9 9 0 1 0-18 0M9 12l2 2l4-4',
'info-circle': 'M3 12a9 9 0 1 0 18 0a9 9 0 0 0-18 0M12 9h.01M11 12h1v4h1',
'help-circle': 'M3 12a9 9 0 1 0 18 0a9 9 0 0 0-18 0M12 16v.01M12 13a2 2 0 0 0 .914-3.782a1.98 1.98 0 0 0-2.414.483',
}
const iconHtml = (icon: string, variant: string): string => {
const svg = icon.startsWith('<') ? icon : `<svg class="icon icon-lg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="${ICONS[icon] ?? ''}"/></svg>`
return `<span class="avatar avatar-lg bg-${variant}-lt flex-shrink-0">${svg}</span>`
}
let counter = 0
const open = (input: string | AlertDialogOptions, withCancel: boolean): Promise<boolean> =>
new Promise((resolve) => {
const { title = withCancel ? 'Are you sure?' : 'Notice', message = '', confirmText = 'OK', cancelText = 'Cancel', variant = 'primary', focus = 'confirm', icon = '', align = 'center' } = typeof input === 'string' ? { title: input } : input
const id = `tblr-alert-dialog-${++counter}`
const root = document.createElement('div')
const start = align === 'start'
const media = icon ? iconHtml(icon, variant) : ''
const text = `<h3 id="${id}-title"></h3><div id="${id}-description" class="text-secondary"></div>`
const btn = start ? 'btn' : 'btn w-100'
const cancel = withCancel ? `<div class="col${start ? '-auto' : ''}"><button type="button" class="${btn}" data-cancel></button></div>` : ''
root.className = 'modal modal-blur fade'
root.tabIndex = -1
root.setAttribute('aria-labelledby', `${id}-title`)
root.setAttribute('aria-describedby', `${id}-description`)
root.innerHTML = `<div class="modal-dialog ${start ? '' : 'modal-sm '}modal-dialog-centered" style="transition-duration: 0.15s"><div class="modal-content"><div class="modal-body py-4 ${start ? 'd-flex gap-3' : 'text-center'}">${start ? `${media}<div>${text}</div>` : `${media ? `<div class="mb-3">${media}</div>` : ''}${text}`}</div><div class="modal-footer" style="--tblr-modal-footer-padding-y: 0.375rem"><div class="row gx-2 ${start ? 'ms-auto' : 'w-100'}">${cancel}<div class="col${start ? '-auto' : ''}"><button type="button" class="${btn} btn-${variant}" data-confirm></button></div></div></div></div></div>`
const set = (selector: string, text: string) => {
root.querySelector(selector)!.textContent = text
}
set('h3', title)
set(`#${id}-description`, message)
set('[data-confirm]', confirmText)
if (withCancel) {
set('[data-cancel]', cancelText)
}
document.body.append(root)
const modal = new Modal(root, { backdrop: 'static', keyboard: true })
let result = false
root.addEventListener('click', (event) => {
const button = (event.target as Element).closest<HTMLElement>('[data-confirm], [data-cancel]')
if (button) {
result = 'confirm' in button.dataset
modal.hide()
}
})
root.addEventListener('shown.bs.modal', () => {
root.setAttribute('role', 'alertdialog')
root.querySelector<HTMLElement>(focus === 'cancel' ? '[data-cancel]' : '[data-confirm]')?.focus()
})
root.addEventListener('hidden.bs.modal', () => {
modal.dispose()
root.remove()
resolve(result)
})
modal.show()
})
// js-docs-start alert-dialog-init
const AlertDialog = {
confirm: (options: string | AlertDialogOptions): Promise<boolean> => open(options, true),
alert: async (options: string | AlertDialogOptions): Promise<void> => {
await open(options, false)
},
}
// js-docs-end alert-dialog-init
export default AlertDialog
+1
View File
@@ -20,6 +20,7 @@ import './src/strength'
export * from './src/bootstrap'
// Tabler's own components
export { default as AlertDialog } from './src/alert-dialog'
export { default as Autosize } from './src/autosize'
export { default as Clipboard } from './src/clipboard'
export { default as Confetti } from './src/confetti'
+83
View File
@@ -0,0 +1,83 @@
import { describe, it, expect, afterEach } from 'vitest'
import AlertDialog from '../../src/alert-dialog'
const waitFor = async (selector: string): Promise<HTMLElement> => {
for (let i = 0; i < 100; i++) {
const el = document.querySelector<HTMLElement>(selector)
if (el) return el
await new Promise((r) => setTimeout(r, 20))
}
throw new Error(`missing ${selector}`)
}
const shown = async () => {
const el = await waitFor('.modal.show')
await new Promise((r) => setTimeout(r, 400))
return el
}
describe('AlertDialog', () => {
afterEach(() => {
document.body.innerHTML = ''
document.body.className = ''
})
it('confirm resolves true on confirm click', async () => {
const promise = AlertDialog.confirm({ title: 'Delete?', message: '<b>x</b>', confirmText: 'Delete', variant: 'danger' })
const el = await shown()
expect(el.getAttribute('role')).toBe('alertdialog')
expect(el.querySelector('b')).toBeNull()
expect(el.querySelector('.btn-danger')?.textContent).toBe('Delete')
el.querySelector<HTMLElement>('[data-confirm]')!.click()
expect(await promise).toBe(true)
expect(document.querySelector('.modal')).toBeNull()
})
it('confirm resolves false on cancel and on Escape', async () => {
const first = AlertDialog.confirm('Sure?')
const el = await shown()
el.querySelector<HTMLElement>('[data-cancel]')!.click()
expect(await first).toBe(false)
const second = AlertDialog.confirm('Sure?')
const el2 = await shown()
el2.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))
expect(await second).toBe(false)
})
it('does not close on backdrop click', async () => {
const promise = AlertDialog.confirm('Sure?')
const el = await shown()
el.click()
await new Promise((r) => setTimeout(r, 400))
expect(document.querySelector('.modal.show')).not.toBeNull()
el.querySelector<HTMLElement>('[data-cancel]')!.click()
await promise
})
it('alert has a single button', async () => {
const promise = AlertDialog.alert('Saved')
const el = await shown()
expect(el.querySelectorAll('button')).toHaveLength(1)
el.querySelector('button')!.click()
await promise
})
it('renders a built-in icon and the start layout', async () => {
const promise = AlertDialog.confirm({ title: 'Delete?', icon: 'trash', variant: 'danger', align: 'start' })
const el = await shown()
expect(el.querySelector('.avatar.bg-danger-lt svg path')).not.toBeNull()
expect(el.querySelector('.modal-body')?.classList.contains('d-flex')).toBe(true)
expect(el.querySelector('.modal-dialog')?.classList.contains('modal-sm')).toBe(false)
el.querySelector<HTMLElement>('[data-cancel]')!.click()
await promise
})
it('accepts icon markup', async () => {
const promise = AlertDialog.alert({ title: 'Hi', icon: '<svg id="custom-icon"></svg>' })
const el = await shown()
expect(el.querySelector('#custom-icon')).not.toBeNull()
el.querySelector('button')!.click()
await promise
})
})
+2 -2
View File
@@ -212,7 +212,7 @@
},
{
"path": "./dist/js/tabler.js",
"maxSize": "58 kB"
"maxSize": "60 kB"
},
{
"path": "./dist/js/tabler.min.js",
@@ -220,7 +220,7 @@
},
{
"path": "./dist/js/tabler.esm.js",
"maxSize": "58 kB"
"maxSize": "60 kB"
},
{
"path": "./dist/js/tabler.esm.min.js",
+4 -2
View File
@@ -32,9 +32,11 @@
inset-inline-start: 0;
inset-inline-end: 0;
top: 0;
height: $modal-status-size;
bottom: 0;
pointer-events: none;
background: var(--secondary);
@include border-radius($modal-content-border-radius $modal-content-border-radius 0 0);
clip-path: inset(0 0 calc(100% - #{$modal-status-size}) 0);
@include border-radius($modal-content-inner-border-radius $modal-content-inner-border-radius 0 0);
}
.modal-header {
+137
View File
@@ -0,0 +1,137 @@
---
title: Alert dialog
summary: An alert dialog is a small modal that interrupts the user to confirm an action or to show a short message. Use it instead of the browser's confirm() and alert().
description: Replace window.confirm() and window.alert() with a styled modal opened from JavaScript that returns a Promise.
added-in: '1.7.0'
related: [/ui/components/modal, /ui/components/alert, /ui/components/toast]
source: core/js/src/alert-dialog.ts
---
import Example from '@components/Example.astro'
## Overview
`AlertDialog` builds a small modal at run time and gives you the answer as a Promise. There is no markup to write. It is made for two jobs: asking the user to confirm something, and telling the user something they must acknowledge.
Unlike a normal [modal](/ui/components/modal), an alert dialog does not close when the user clicks the backdrop. The user has to choose a button, or press <kbd>Esc</kbd> to cancel.
<Example centered>
<button type="button" class="btn btn-primary" onclick="tabler.AlertDialog.confirm({ title: 'Delete this project?', message: 'This cannot be undone.', confirmText: 'Delete', variant: 'danger' })">
Delete project
</button>
</Example>
## Usage
`AlertDialog.confirm()` returns a Promise that resolves to `true` when the user confirms and to `false` when the user cancels or presses <kbd>Esc</kbd>.
```js
const ok = await tabler.AlertDialog.confirm({
title: 'Delete 84 items?',
message: 'What you have done cannot be undone.',
confirmText: 'Delete',
variant: 'danger',
})
if (ok) {
deleteItems()
}
```
`AlertDialog.alert()` shows one button and resolves when the user closes the dialog.
```js
await tabler.AlertDialog.alert({ title: 'Saved', message: 'Your changes are live.', variant: 'success' })
```
Both methods also accept a string. It is used as the title.
```js
if (await tabler.AlertDialog.confirm('Discard the draft?')) {
discard()
}
```
## Variants
The `variant` option sets the color of the confirm button. It accepts `primary` (default), `secondary`, `success`, `info`, `warning` and `danger`. Use `danger` for actions that destroy data.
<Example centered>
<button type="button" class="btn btn-danger" onclick="tabler.AlertDialog.confirm({ title: 'Delete account?', message: 'All your data will be removed.', confirmText: 'Delete', variant: 'danger', focus: 'cancel' })">
Danger
</button>
<button type="button" class="btn btn-warning" onclick="tabler.AlertDialog.confirm({ title: 'Leave this page?', message: 'You have unsaved changes.', confirmText: 'Leave', variant: 'warning' })">
Warning
</button>
<button type="button" class="btn btn-success" onclick="tabler.AlertDialog.alert({ title: 'Payment received', message: 'Thank you for your order.', variant: 'success' })">
Success
</button>
</Example>
## Icons
Set `icon` to add a round icon above the text. The icon takes the color of the variant. Use one of the built-in names: `trash`, `alert-triangle`, `circle-check`, `info-circle` or `help-circle`.
<Example centered>
<button type="button" class="btn" onclick="tabler.AlertDialog.confirm({ icon: 'help-circle', title: 'Send feedback?', message: 'We read every message.', confirmText: 'Send' })">
Default
</button>
<button type="button" class="btn btn-danger" onclick="tabler.AlertDialog.confirm({ icon: 'trash', title: 'Delete account?', message: 'All your data will be removed.', confirmText: 'Delete', variant: 'danger', focus: 'cancel' })">
Danger
</button>
<button type="button" class="btn btn-warning" onclick="tabler.AlertDialog.confirm({ icon: 'alert-triangle', title: 'Leave this page?', message: 'You have unsaved changes.', confirmText: 'Leave', variant: 'warning' })">
Warning
</button>
<button type="button" class="btn btn-success" onclick="tabler.AlertDialog.alert({ icon: 'circle-check', title: 'Payment received', message: 'Thank you for your order.', variant: 'success' })">
Success
</button>
</Example>
To use another icon, pass its markup, for example an inline `<svg>`. The string is inserted as HTML, so pass only markup you wrote yourself.
```js
tabler.AlertDialog.alert({ icon: '<svg class="icon icon-lg">…</svg>', title: 'Done' })
```
## Layout
By default the dialog is small and centered: the icon is on top, the text is centered and the buttons fill the width. Set `align: 'start'` for a wider dialog with the text aligned to the left, the icon next to the text and the buttons on the right.
<Example centered>
<button type="button" class="btn" onclick="tabler.AlertDialog.confirm({ align: 'start', title: 'Sign out?', message: 'You will need to sign in again.', confirmText: 'Sign out' })">
Left aligned
</button>
<button type="button" class="btn btn-danger" onclick="tabler.AlertDialog.confirm({ align: 'start', icon: 'trash', title: 'Delete 84 items?', message: 'What you have done cannot be undone.', confirmText: 'Delete', variant: 'danger', focus: 'cancel' })">
With icon
</button>
</Example>
The message is optional. A dialog with only a title is fine for a short question.
<Example centered>
<button type="button" class="btn" onclick="tabler.AlertDialog.confirm({ title: 'Discard the draft?', confirmText: 'Discard' })">
Title only
</button>
</Example>
## Options
| Option | Default | Description |
| ------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `title` | `Are you sure?` for `confirm()`, `Notice` for `alert()` | The heading of the dialog. |
| `message` | none | A line of text under the title. It is set as plain text, so HTML is not rendered. |
| `confirmText` | `OK` | The label of the confirm button. |
| `cancelText` | `Cancel` | The label of the cancel button. `confirm()` only. |
| `variant` | `primary` | The color of the confirm button. |
| `icon` | none | A built-in icon name or icon markup, shown in a round badge in the color of the variant. |
| `align` | `center` | `center` or `start`. See [Layout](#layout). |
| `focus` | `confirm` | Which button gets focus when the dialog opens: `confirm` or `cancel`. `confirm()` only. |
For a destructive action, set `focus: 'cancel'` so a stray <kbd>Enter</kbd> does not confirm it.
## Accessibility
- The dialog has `role="alertdialog"` and is named and described by its title and message with `aria-labelledby` and `aria-describedby`.
- Focus moves into the dialog when it opens and stays inside until it closes, then returns to the element that opened it.
- <kbd>Esc</kbd> cancels the dialog. Clicking the backdrop does nothing, so an answer is never given by accident.
- Keep the title short and the button labels specific, for example `Delete` instead of `Yes`.
+154
View File
@@ -0,0 +1,154 @@
---
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import CaptureScript from '@shared/components/CaptureScript.astro'
import Card from '@ui/Card.astro'
import CardBody from '@ui/CardBody.astro'
import CardTitle from '@ui/CardTitle.astro'
import CardSubtitle from '@ui/CardSubtitle.astro'
import DocsLink from '@ui/DocsLink.astro'
import Button from '@ui/Button.astro'
import ButtonList from '@ui/ButtonList.astro'
---
<DefaultLayout title="Alert dialogs" pageMenu="base.alert-dialogs" description="Alert dialogs replace the browser's confirm() and alert() with a modal that returns a Promise.">
<DocsLink slot="page-header-actions" path="/ui/components/alert-dialog" />
<CaptureScript>
<!-- BEGIN ALERT DIALOG -->
<script is:inline>
function initAlertDialogs() {
const result = document.getElementById('alert-dialog-result')
document.querySelectorAll('[data-alert-dialog]').forEach((button) => {
button.addEventListener('click', () => {
const options = JSON.parse(button.dataset.alertDialog)
if (options.alert) {
tabler.AlertDialog.alert(options).then(() => {
result.textContent = 'Alert closed'
})
return
}
tabler.AlertDialog.confirm(options).then((ok) => {
result.textContent = ok ? 'Confirmed' : 'Cancelled'
})
})
})
}
document.readyState !== 'loading' ? initAlertDialogs() : document.addEventListener('DOMContentLoaded', initAlertDialogs, { once: true })
</script>
<!-- END ALERT DIALOG -->
</CaptureScript>
<div class="row row-cards">
<div class="col-md-6 col-lg-4">
<Card>
<CardBody>
<CardTitle>Basic</CardTitle>
<CardSubtitle>Title, message and two buttons. <code>confirm()</code> resolves to <code>true</code> or <code>false</code>.</CardSubtitle>
<ButtonList>
<Button text="Confirm" data-alert-dialog={JSON.stringify({ title: 'Publish this page?', message: 'It will be visible to everyone.', confirmText: 'Publish' })} />
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6 col-lg-4">
<Card>
<CardBody>
<CardTitle>Alert</CardTitle>
<CardSubtitle><code>alert()</code> shows one button and resolves when it is closed.</CardSubtitle>
<ButtonList>
<Button text="Alert" data-alert-dialog={JSON.stringify({ alert: true, title: 'Changes saved', message: 'Your changes are live.' })} />
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6 col-lg-4">
<Card>
<CardBody>
<CardTitle>Title only</CardTitle>
<CardSubtitle>The message is optional.</CardSubtitle>
<ButtonList>
<Button text="Title only" data-alert-dialog={JSON.stringify({ title: 'Discard the draft?', confirmText: 'Discard' })} />
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6 col-lg-4">
<Card>
<CardBody>
<CardTitle>With icon</CardTitle>
<CardSubtitle>Set <code>icon</code> to a built-in icon name. The icon sits above the text.</CardSubtitle>
<ButtonList>
<Button text="Help" data-alert-dialog={JSON.stringify({ icon: 'help-circle', title: 'Send feedback?', message: 'We read every message.', confirmText: 'Send' })} />
<Button text="Info" data-alert-dialog={JSON.stringify({ alert: true, icon: 'info-circle', title: 'New version available', message: 'Refresh the page to update.' })} />
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6 col-lg-4">
<Card>
<CardBody>
<CardTitle>Icon on the left</CardTitle>
<CardSubtitle>Set <code>align: 'start'</code> for a wider dialog with the icon next to the text and the buttons on the right.</CardSubtitle>
<ButtonList>
<Button color="danger" text="Delete" data-alert-dialog={JSON.stringify({ icon: 'trash', variant: 'danger', align: 'start', title: 'Delete 84 items?', message: 'What you have done cannot be undone.', confirmText: 'Delete', focus: 'cancel' })} />
<Button text="Info" data-alert-dialog={JSON.stringify({ icon: 'info-circle', align: 'start', title: 'Enable notifications?', message: 'We will only send important updates.', confirmText: 'Enable' })} />
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6 col-lg-4">
<Card>
<CardBody>
<CardTitle>Left aligned</CardTitle>
<CardSubtitle>Without an icon, <code>align: 'start'</code> left aligns the text.</CardSubtitle>
<ButtonList>
<Button text="Left aligned" data-alert-dialog={JSON.stringify({ align: 'start', title: 'Sign out?', message: 'You will need to sign in again.', confirmText: 'Sign out' })} />
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6 col-lg-4">
<Card>
<CardBody>
<CardTitle>Variants</CardTitle>
<CardSubtitle>Set <code>variant</code> to color the confirm button.</CardSubtitle>
<ButtonList>
<Button color="danger" text="Danger" data-alert-dialog={JSON.stringify({ title: 'Delete 84 items?', message: 'What you have done cannot be undone.', confirmText: 'Delete', variant: 'danger', focus: 'cancel' })} />
<Button color="warning" text="Warning" data-alert-dialog={JSON.stringify({ title: 'Leave this page?', message: 'You have unsaved changes.', confirmText: 'Leave', variant: 'warning' })} />
<Button color="success" text="Success" data-alert-dialog={JSON.stringify({ alert: true, title: 'Payment received', message: 'Thank you for your order.', variant: 'success' })} />
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6 col-lg-4">
<Card>
<CardBody>
<CardTitle>Variants with icon</CardTitle>
<CardSubtitle>The icon takes the color of the variant.</CardSubtitle>
<ButtonList>
<Button color="danger" text="Danger" data-alert-dialog={JSON.stringify({ icon: 'trash', title: 'Delete account?', message: 'All your data will be removed.', confirmText: 'Delete', variant: 'danger', focus: 'cancel' })} />
<Button color="warning" text="Warning" data-alert-dialog={JSON.stringify({ icon: 'alert-triangle', title: 'Leave this page?', message: 'You have unsaved changes.', confirmText: 'Leave', variant: 'warning' })} />
<Button color="success" text="Success" data-alert-dialog={JSON.stringify({ alert: true, icon: 'circle-check', title: 'Payment received', message: 'Thank you for your order.', variant: 'success' })} />
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
<CardTitle>Result</CardTitle>
<CardSubtitle>The answer of the last dialog.</CardSubtitle>
<div id="alert-dialog-result" class="text-secondary" aria-live="polite">No dialog opened yet</div>
</CardBody>
</Card>
</div>
</div>
</DefaultLayout>
+86
View File
@@ -0,0 +1,86 @@
---
// Alert dialogs drawn in place, not opened: the capture never clicks, and a real
// dialog sits on a backdrop over the page. The markup is the one `AlertDialog`
// builds, so the picture shows four of its layouts side by side: a plain confirm,
// a danger confirm with an icon, a success alert, and the left aligned one.
import ScreenshotLayout from '../layouts/ScreenshotLayout.astro'
import Icon from '@ui/Icon.astro'
const dialogs = [
{ title: 'Publish this page?', message: 'It will be visible to everyone.', confirm: 'Publish', cancel: 'Cancel', variant: 'primary' },
{ title: 'Delete 84 items?', message: 'What you have done cannot be undone.', confirm: 'Delete', cancel: 'Cancel', variant: 'danger', icon: 'trash' },
{ title: 'Payment received', message: 'Thank you for your order.', confirm: 'OK', variant: 'success', icon: 'circle-check' },
]
const wide = { title: 'Enable notifications?', message: 'We will only send important updates.', confirm: 'Enable', cancel: 'Not now', variant: 'primary', icon: 'info-circle' }
---
<ScreenshotLayout title="Alert dialog" columns={3}>
<div class="row g-3 align-items-start">
{
dialogs.map((dialog) => (
<div class="col-4">
<div class="modal position-static d-block h-auto overflow-visible" style="--tblr-modal-border-color: var(--tblr-border-color)">
<div class="modal-dialog modal-sm m-0">
<div class="modal-content" role="alertdialog">
<div class="modal-body py-4 text-center">
{dialog.icon && (
<div class="mb-3">
<span class={`avatar avatar-lg bg-${dialog.variant}-lt flex-shrink-0`}>
<Icon name={dialog.icon} class="icon-lg" />
</span>
</div>
)}
<h3>{dialog.title}</h3>
<div class="text-secondary">{dialog.message}</div>
</div>
<div class="modal-footer" style="--tblr-modal-footer-padding-y: 0.375rem">
<div class="row gx-2 w-100">
{dialog.cancel && (
<div class="col">
<button type="button" class="btn w-100">
{dialog.cancel}
</button>
</div>
)}
<div class="col">
<button type="button" class={`btn w-100 btn-${dialog.variant}`}>
{dialog.confirm}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
))
}
<div class="col-12">
<div class="modal position-static d-block h-auto overflow-visible" style="--tblr-modal-border-color: var(--tblr-border-color)">
<div class="modal-dialog m-0 mx-auto">
<div class="modal-content" role="alertdialog">
<div class="modal-body py-4 d-flex gap-3">
<span class={`avatar avatar-lg bg-${wide.variant}-lt flex-shrink-0`}>
<Icon name={wide.icon} class="icon-lg" />
</span>
<div>
<h3>{wide.title}</h3>
<div class="text-secondary">{wide.message}</div>
</div>
</div>
<div class="modal-footer" style="--tblr-modal-footer-padding-y: 0.375rem">
<div class="row gx-2 ms-auto">
<div class="col-auto">
<button type="button" class="btn">{wide.cancel}</button>
</div>
<div class="col-auto">
<button type="button" class={`btn btn-${wide.variant}`}>{wide.confirm}</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</ScreenshotLayout>
+4
View File
@@ -170,6 +170,10 @@
"title": "Alert",
"url": "/ui/components/alert"
},
{
"title": "Alert dialog",
"url": "/ui/components/alert-dialog"
},
{
"title": "Avatar",
"url": "/ui/components/avatar"
+5
View File
@@ -39,6 +39,11 @@
"title": "Alerts",
"url": "alerts.html"
},
"alert-dialogs": {
"title": "Alert dialogs",
"url": "alert-dialogs.html",
"badge": "New"
},
"authentication": {
"title": "Authentication",
"children": {