FullscreenModal
A dialog taking the whole window, with a bar holding close, its title and a main action, on the browser's <dialog>.
Demo
Not saved 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/fullscreen_modal.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/fullscreen_modal.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/fullscreen_modal.qwe
component FullscreenModalDemoLive() { state editing = false state saved = 0 <div.demo-row> <Button variant="soft" on_click={() => { editing = true }}>Edit profile (from state)</Button> <span.demo-note>{saved == 0 ? "Not saved yet." : `Saved ${saved} ${saved == 1 ? "time" : "times"}.`}</span> <FullscreenModal title="Edit profile" open={editing} done="Save" on_done={() => { saved += 1 }} on_close={() => { editing = false }}> <div.demo-stack> <TextField label="Name" name="name" field="fullscreen-modal-demo-name" value="Ada Lovelace"/> <TextArea label="Bio" name="bio" field="fullscreen-modal-demo-bio" value="Engineer in London."/> </div> </FullscreenModal> </div>}view FullscreenModalDemo() = <div.demo-stack> <div.demo-row> <FullscreenModal title="Terms of service" trigger="Read the terms"> {for i in range(1, 13) => <section> <h3>{i}. Section {i}</h3> <p>These terms say what you may do with the service and what we promise in return. The body scrolls under the bar, which stays.</p> </section>} </FullscreenModal> <FullscreenModalDemoLive/> </div> </div>Source
ui/fullscreen_modal.qwe
// FullscreenModal: a dialog that takes the whole window, with a bar across the top// holding a close button, its title and, if given, a main action ("Save", "Done"):// for an editor, a long form or a preview on a phone. It is the browser's own// <dialog>, so focus stays in it, Escape closes it and screen readers announce it;// the page behind does not scroll while it is open.//// <FullscreenModal title="New post" trigger="Write a post">// <form id="post"> ... </form>// </FullscreenModal>// <FullscreenModal title="Edit photo" open={editing} done="Save" on_done={() => { save() }}// on_close={() => { editing = false }}> ... </FullscreenModal>//// title: the bar's heading, and its name for screen readers. children: the body,// which scrolls under the bar. 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_close().// done: the text of a main action in the bar; it calls on_done() and closes it.// close_label ("Close"): the close button's name. width: how wide the body may be,// in pixels (720), centred under the bar.import ui/theme.qwe use ui_clslet fullscreen_modal_css = css`.ui-fullscreen-modal-host { display: contents; }.ui-fullscreen-modal { box-sizing: border-box; width: 100vw; height: 100dvh; max-width: none; max-height: none; margin: 0; padding: 0; border: 0; background: var(--ui-bg); color: var(--ui-fg); font: 15px/1.5 var(--ui-font);}.ui-fullscreen-modal[open] { display: flex; flex-direction: column; animation: ui-fullscreen-modal-in .2s cubic-bezier(.2, .8, .2, 1); }@keyframes ui-fullscreen-modal-in { from { opacity: 0; transform: translateY(24px); } }.ui-fullscreen-modal::backdrop { background: var(--ui-overlay); }:root:has(.ui-fullscreen-modal[open]) { overflow: hidden; }.ui-fullscreen-modal-bar { display: flex; align-items: center; gap: 10px; flex: none; min-height: 56px; padding: 8px max(12px, env(safe-area-inset-right)) 8px max(12px, env(safe-area-inset-left)); border-bottom: 1px solid var(--ui-border); background: var(--ui-surface);}.ui-fullscreen-modal-bar h2 { flex: 1; min-width: 0; margin: 0; overflow: hidden; font-size: 17px; font-weight: 600; line-height: 1.3; text-overflow: ellipsis; white-space: nowrap; }.ui-fullscreen-modal-x { display: grid; place-items: center; flex: none; width: 38px; height: 38px; padding: 0; border: 0; border-radius: var(--ui-radius-sm); background: transparent; color: var(--ui-fg); cursor: pointer;}.ui-fullscreen-modal-x:hover { background: var(--ui-subtle); }.ui-fullscreen-modal-x:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-fullscreen-modal-x svg { width: 20px; height: 20px; }.ui-fullscreen-modal-done { flex: none; height: 36px; padding: 0 16px; border: 0; border-radius: var(--ui-radius-sm); background: var(--ui-accent); color: var(--ui-accent-fg); font: 600 14px/1 var(--ui-font); cursor: pointer;}.ui-fullscreen-modal-done:hover { background: color-mix(in srgb, var(--ui-accent) 88%, var(--ui-fg)); }.ui-fullscreen-modal-done:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-fullscreen-modal-body { flex: 1; min-height: 0; overflow: auto; overscroll-behavior: contain; }.ui-fullscreen-modal-inner { box-sizing: border-box; width: min(100%, var(--w, 720px)); margin: 0 auto; padding: 24px 20px 40px; }.ui-fullscreen-modal-inner > :first-child { margin-top: 0; }.ui-fullscreen-modal-inner > :last-child { margin-bottom: 0; }@media (prefers-reduced-motion: reduce) { .ui-fullscreen-modal[open] { animation: none; } }`component FullscreenModal(title: str, trigger: str? = null, open: bool = false, done: str? = null, close_label: str = "Close", width: int = 720, on_done = null, on_close = null, class: str? = null, children) { style global fullscreen_modal_css state dlg = null if open && dlg != null && !dlg.open { dlg.showModal() } if !open && dlg != null && dlg.open && trigger == null { dlg.close() } <div.ui-fullscreen-modal-host> {trigger != null && <button.ui-btn.ui-btn-outline type="button" aria-haspopup="dialog" @click={() => dlg.showModal()}>{trigger}</button>} <dialog class={ui_cls(["ui-fullscreen-modal", class])} aria-label={title} style={`--w: ${width}px`} @mount={(el) => { dlg = el if open { el.showModal() } }} @close={() => { on_close && on_close() }}> <div.ui-fullscreen-modal-bar> <button.ui-fullscreen-modal-x type="button" aria-label={close_label} title={close_label} @click={() => dlg.close()}> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg> </button> <h2>{title}</h2> {done != null && <button.ui-fullscreen-modal-done type="button" @click={() => { on_done && on_done() dlg.close() }}>{done}</button>} </div> <div.ui-fullscreen-modal-body><div.ui-fullscreen-modal-inner>{children}</div></div> </dialog> </div>}Related
ShareSheetA bottom sheet with the ways to share a link: copy it, email, X, LinkedIn, WhatsApp and more, and the system's own.6 views · 4 downloadsSheetA bottom sheet on the browser's <dialog>: half or full height by its handle or a drag, Escape to close.6 views · 5 downloadsAlertDialogA modal that only informs, with one OK button, on the browser's own <dialog> and no JavaScript.6 views · 7 downloadsModalA dialog over the page, on the browser's own <dialog>: focus, Escape and screen readers handled.6 views · 8 downloadsPromptDialogA modal asking for one text value with OK and Cancel, on the browser's own <dialog>; Enter submits, Escape cancels.4 views · 5 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 downloads
Thread
Thread 0 posts
On FullscreenModal: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.