Demo
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/drawer.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/drawer.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/drawer.qwe
component DrawerDemoLive() { state showing = false <div.demo-row> <Button variant="soft" on_click={() => { showing = true }}>Details (from state)</Button> <Drawer title="Order #1042" open={showing} side="left" size="sm" on_close={() => { showing = false }}> <p>Opened from the component's own state, on the left; closing it sets the state back.</p> </Drawer> </div>}view DrawerDemo() = <div.demo-row> <Drawer title="Filters" trigger="Filters"> <p>Narrow the list down. Press Escape or click beside the panel to close it.</p> <div.demo-stack> <Switch label="In stock only" checked={true}/> <Switch label="Free shipping"/> </div> </Drawer> <Drawer title="Cart" trigger="From the bottom" side="bottom"> <p>A bottom sheet, as tall as it needs to be, at most 70% of the screen.</p> </Drawer> <DrawerDemoLive/> </div>Source
ui/drawer.qwe
// Drawer: a panel that slides in from the edge of the screen, over the page:// filters, a cart, details of a row. It is the browser's own <dialog>, so focus// stays in it, Escape closes it and screen readers announce it.//// <Drawer title="Filters" trigger="Filters">// <form> ... </form>// </Drawer>//// 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: <Drawer title="..." open={showing} on_close={() => { showing = false }}>.// side: "right" (default), "left" or "bottom". size: "sm", "md" (default) or "lg",// its width (or, from the bottom, how tall it may grow). Clicking beside it// closes it, unless dismissable={false}.import ui/theme.qwe use ui_clslet drawer_css = css`.ui-drawer-host { display: contents; }.ui-drawer { box-sizing: border-box; margin: 0; padding: 0; max-width: 100vw; max-height: 100dvh; overflow: hidden; border: 0; background: var(--ui-surface); color: var(--ui-fg); box-shadow: var(--ui-shadow-lg); font: 15px/1.5 var(--ui-font);}.ui-drawer::backdrop { background: var(--ui-overlay); }.ui-drawer-right, .ui-drawer-left { --w: 400px; top: 0; bottom: 0; width: min(var(--w), calc(100vw - 40px)); height: 100dvh; }.ui-drawer-right { left: auto; right: 0; border-left: 1px solid var(--ui-border); }.ui-drawer-left { right: auto; left: 0; border-right: 1px solid var(--ui-border); }.ui-drawer-sm { --w: 320px; }.ui-drawer-lg { --w: 560px; }.ui-drawer-bottom { --h: 70dvh; top: auto; bottom: 0; left: 0; right: 0; width: 100%; max-height: var(--h); border-top: 1px solid var(--ui-border); border-radius: var(--ui-radius) var(--ui-radius) 0 0;}.ui-drawer-bottom.ui-drawer-sm { --h: 45dvh; }.ui-drawer-bottom.ui-drawer-lg { --h: 92dvh; }.ui-drawer-box { display: flex; flex-direction: column; height: 100%; max-height: inherit; }.ui-drawer-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex: none; padding: 16px 20px; border-bottom: 1px solid var(--ui-border);}.ui-drawer-head h2 { margin: 0; font-size: 17px; font-weight: 600; line-height: 1.3; }.ui-drawer-x { display: grid; place-items: center; flex: none; width: 30px; height: 30px; margin: -4px -6px -4px 0; border: 0; border-radius: var(--ui-radius-sm); background: transparent; color: var(--ui-muted); cursor: pointer;}.ui-drawer-x:hover { background: var(--ui-subtle); color: var(--ui-fg); }.ui-drawer-x:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-drawer-body { flex: 1; min-height: 0; overflow: auto; padding: 18px 20px 22px; }.ui-drawer-body > :first-child { margin-top: 0; }.ui-drawer-body > :last-child { margin-bottom: 0; }.ui-drawer-right[open] { animation: ui-drawer-right .22s ease-out; }.ui-drawer-left[open] { animation: ui-drawer-left .22s ease-out; }.ui-drawer-bottom[open] { animation: ui-drawer-bottom .22s ease-out; }@keyframes ui-drawer-right { from { transform: translateX(100%); } }@keyframes ui-drawer-left { from { transform: translateX(-100%); } }@keyframes ui-drawer-bottom { from { transform: translateY(100%); } }@media (prefers-reduced-motion: reduce) { .ui-drawer[open] { animation: none; } }`component Drawer(title: str, trigger: str? = null, open: bool = false, side: str = "right", size: str = "md", dismissable: bool = true, on_close = null, children) { style global drawer_css state dlg = null if open && dlg != null && !dlg.open { dlg.showModal() } if !open && dlg != null && dlg.open && trigger == null { dlg.close() } <div.ui-drawer-host> {trigger != null && <button.ui-btn.ui-btn-outline type="button" aria-haspopup="dialog" @click={() => dlg.showModal()}>{trigger}</button>} <dialog class={ui_cls(["ui-drawer", `ui-drawer-${side}`, size != "md" && `ui-drawer-${size}`])} aria-label={title} @mount={(el) => { dlg = el if open { el.showModal() } }} @close={() => { on_close && on_close() }} @click={(e) => { if dismissable && e.target.tagName == "DIALOG" { dlg.close() } }}> <div.ui-drawer-box> <div.ui-drawer-head> <h2>{title}</h2> <button.ui-drawer-x type="button" aria-label="Close" @click={() => dlg.close()}><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg></button> </div> <div.ui-drawer-body>{children}</div> </div> </dialog> </div>}Related
AlertDialogA modal that only informs, with one OK button, on the browser's own <dialog> and no JavaScript.6 views · 7 downloadsChatWidgetA chat button in the corner that opens a small chat panel, with replies from your server and a count for unseen ones.4 views · 7 downloadsCommandPaletteCtrl+K opens a fuzzy search of commands and pages, grouped, with keys, recent picks and the arrow keys.6 views · 8 downloadsFlyoutA panel that opens beside its button at a chosen side, keeps open while you work in it, and closes on a click outside.5 views · 10 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 downloadsHoverCardA card of details on hover or focus, after a short wait, that stays under the pointer; Escape hides it.5 views · 6 downloads
Thread
Thread 0 posts
On Drawer: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.