PromptDialog
A modal asking for one text value with OK and Cancel, on the browser's own <dialog>; Enter submits, Escape cancels.
Demo
File: notes.txt. Nothing yet.
Get it
It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/prompt_dialog.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/prompt_dialog.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/prompt_dialog.qwe
component PromptDialogDemoLive() { state name = "notes.txt" state asking = false state said = "Nothing yet." <div.demo-stack> <div.demo-row> <PromptDialog id="prompt-demo-name" title="Rename file" label="New name" value={name} trigger="Rename" hint="Keep the ending." on_submit={(t) => { name = t; said = `Renamed to ${t}.` }} on_cancel={() => { said = "Rename cancelled." }}/> <Button variant="soft" on_click={() => { asking = true }}>Reject (from state)</Button> <PromptDialog id="prompt-demo-reason" title="Reject this request" label="Reason" placeholder="Tell them why" open={asking} on_submit={(t) => { asking = false; said = `Rejected: ${t}` }} on_cancel={() => { asking = false; said = "Not rejected." }}> <p>The reason goes to the person who asked.</p> </PromptDialog> </div> <p.demo-note>File: {name}. {said}</p> </div>}view PromptDialogDemo() = <PromptDialogDemoLive/>Source
ui/prompt_dialog.qwe
// PromptDialog: a modal that asks for one text value (a name for the file, the// reason, a code) with OK and Cancel, on the browser's own <dialog>: focus stays in// it, Escape cancels, Enter submits. The field is required unless told otherwise.//// <PromptDialog title="Rename file" label="New name" value={name} trigger="Rename" on_submit={(t) => { rename(t) }}/>// <PromptDialog title="Why?" label="Reason" open={asking} on_submit={(t) => { reject(t) }} on_cancel={() => { asking = false }}/>//// title: its heading, and its name for screen readers. label: the field's label.// value: what the field starts with; placeholder, hint, required (true), kind// ("text", or "email", "number", "url", "password") as for a field. children: text// above the field. trigger: the text of a button that opens it (styled as a Button// when button_css is on the page). Inside one of your components, open it from your// own state instead: open={...}, and on_cancel() is called when it closes without// an answer (Escape, Cancel, a click outside).// ok ("OK") and cancel ("Cancel"): the buttons' text. id: the field's id ("ui-prompt-dialog"),// unique on the page when there are several. on_submit(text): called with the text// when OK is pressed (then it closes).import ui/theme.qwe use ui_clslet prompt_dialog_css = css`.ui-prompt-dialog-text:empty { display: none; }.ui-prompt-dialog-host { display: contents; }.ui-prompt-dialog { width: min(440px, calc(100vw - 32px)); padding: 0; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); color: var(--ui-fg); box-shadow: var(--ui-shadow-lg); font: 15px/1.5 var(--ui-font);}.ui-prompt-dialog::backdrop { background: var(--ui-overlay); }.ui-prompt-dialog[open] { animation: ui-prompt-dialog-in .16s ease-out; }@keyframes ui-prompt-dialog-in { from { opacity: 0; transform: translateY(8px) scale(.98); } }.ui-prompt-dialog form { display: grid; gap: 14px; padding: 20px 22px 20px; }.ui-prompt-dialog h2 { margin: 0; font-size: 17px; font-weight: 600; line-height: 1.3; }.ui-prompt-dialog-text { margin: -6px 0 0; color: var(--ui-muted); }.ui-prompt-dialog-text > :first-child { margin-top: 0; }.ui-prompt-dialog-text > :last-child { margin-bottom: 0; }.ui-prompt-dialog-field { display: grid; gap: 6px; font-size: 13px; }.ui-prompt-dialog-field > span { font-weight: 500; }.ui-prompt-dialog-input { box-sizing: border-box; width: 100%; height: 38px; margin: 0; padding: 0 12px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); color: var(--ui-fg); font: 14px/1 var(--ui-font); transition: border-color .15s, box-shadow .15s;}.ui-prompt-dialog-input::placeholder { color: var(--ui-muted); opacity: 1; }.ui-prompt-dialog-input:focus { outline: none; border-color: var(--ui-accent); box-shadow: var(--ui-focus); }.ui-prompt-dialog-hint { color: var(--ui-muted); }.ui-prompt-dialog-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }.ui-prompt-dialog-btn { height: 36px; padding: 0 14px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: transparent; color: var(--ui-fg); font: 500 14px/1 var(--ui-font); cursor: pointer;}.ui-prompt-dialog-btn:hover { background: var(--ui-subtle); }.ui-prompt-dialog-btn:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-prompt-dialog-ok { border-color: transparent; background: var(--ui-accent); color: var(--ui-accent-fg); }.ui-prompt-dialog-ok:hover { background: color-mix(in srgb, var(--ui-accent) 88%, var(--ui-fg)); }@media (prefers-reduced-motion: reduce) { .ui-prompt-dialog[open] { animation: none; } .ui-prompt-dialog-input { transition: none; } }`component PromptDialog(title: str, label: str, value: str = "", placeholder: str? = null, hint: str? = null, required: bool = true, kind: str = "text", trigger: str? = null, open: bool = false, ok: str = "OK", cancel: str = "Cancel", id: str = "ui-prompt-dialog", on_submit = null, on_cancel = null, class: str? = null, children) { style global prompt_dialog_css state dlg = null state text = value state answered = false let show = () => { text = value answered = false dlg.showModal() dlg.querySelector("input").select() } if open && dlg != null && !dlg.open { show() } if !open && dlg != null && dlg.open && trigger == null { dlg.close() } <div.ui-prompt-dialog-host> {trigger != null && <button.ui-btn.ui-btn-outline type="button" aria-haspopup="dialog" @click={show}>{trigger}</button>} <dialog class={ui_cls(["ui-prompt-dialog", class])} aria-label={title} @mount={(el) => { dlg = el if open { show() } }} @close={() => { if !answered { on_cancel && on_cancel() } }} @click={(e) => { if e.target.tagName == "DIALOG" { dlg.close() } }}> <form method="dialog" @submit={(e) => { answered = true dlg.close() on_submit && on_submit(e.form.value) }}> <h2>{title}</h2> <div.ui-prompt-dialog-text>{children}</div> <label.ui-prompt-dialog-field> <span>{label}</span> <input.ui-prompt-dialog-input type={kind} id={id} name="value" value={text} placeholder={placeholder} required={required} autocomplete="off" aria-describedby={hint != null ? `${id}-hint` : null} @input={(e) => { text = e.value }}/> {hint != null && <span.ui-prompt-dialog-hint id={`${id}-hint`}>{hint}</span>} </label> <div.ui-prompt-dialog-actions> <button.ui-prompt-dialog-btn type="button" @click={() => dlg.close()}>{cancel}</button> <button.ui-prompt-dialog-btn.ui-prompt-dialog-ok type="submit">{ok}</button> </div> </form> </dialog> </div>}Related
AlertDialogA modal that only informs, with one OK button, on the browser's own <dialog> and no JavaScript.6 views · 7 downloadsConfirmButtonA button that asks first, in a dialog on the page, then submits its form, follows its link or calls back.5 views · 5 downloadsFullscreenModalA dialog taking the whole window, with a bar holding close, its title and a main action, on the browser's <dialog>.5 views · 4 downloadsModalA dialog over the page, on the browser's own <dialog>: focus, Escape and screen readers handled.6 views · 8 downloadsQuickViewA Quick view button on a card that opens a preview of the item, its picture, price, text and actions, in a dialog.5 views · 5 downloadsSearchOverlayA full-screen search with results as one types, grouped and marked, from a list or a server, opened with /.7 views · 5 downloads
Thread
Thread 0 posts
On PromptDialog: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.