Dock
A floating bar of icon buttons fixed to the bottom or a side of the page, with badges and the current one marked.
Demo
A page (the box stands in for the window): the dock floats over its bottom.
Tool: pen. Press one; the names are tooltips (labels={false}).
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/dock.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/dock.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/dock.qwe
let dock_demo_items = [ { label: "Home", href: "#dock", icon: "M3 11l9-8 9 8v9a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z" }, { label: "Search", href: "#search", icon: "M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14zM20 20l-4-4" }, { label: "Inbox", href: "#inbox", icon: "M4 4h16v16H4zM4 14h4l2 3h4l2-3h4", badge: 3 }, { label: "You", href: "#me", icon: "M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM4 20a8 8 0 0 1 16 0" }]component DockDemoLive() { state tool = "pen" <div.demo-stack> <Dock id="dock-demo-tools" label="Tools" side="bottom" fixed={false} labels={false} current={tool} items={[{ label: "Pen", value: "pen", icon: "✎" }, { label: "Eraser", value: "eraser", icon: "◻" }, { label: "Text", value: "text", icon: "T" }, { label: "Shapes", value: "shapes", icon: "◯", disabled: true }]} on_select={(v) => { tool = v }}/> <p.demo-note>Tool: {tool}. Press one; the names are tooltips (labels={"{false}"}).</p> </div>}view DockDemo() = <div.demo-stack> <div id="dock-demo-phone" style="position: relative; height: 260px; overflow: hidden; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-subtle); transform: translateZ(0)"> <p style="margin: 0; padding: 16px; color: var(--ui-muted); font-size: 13px">A page (the box stands in for the window): the dock floats over its bottom.</p> <Dock id="dock-demo-main" items={dock_demo_items} current="#dock"/> <Dock id="dock-demo-side" label="Shortcuts" side="left" labels={false} items={[{ label: "New", href: "#new", icon: "+" }, { label: "Star", href: "#star", icon: "★" }]}/> </div> <DockDemoLive/> </div>Source
ui/dock.qwe
// Dock: a floating bar of icon buttons fixed to the bottom of the page (or a// side): an app's main places on a phone, a drawing tool's modes. Each item is a// link or a button with an icon, its name under it (or as its tooltip), a badge// for a count, and the current one marked.//// <Dock current={request.path} items={[// { label: "Home", href: "/", icon: "M3 11l9-8 9 8v9a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z" },// { label: "Search", href: "/search", icon: "🔍" },// { label: "Inbox", href: "/inbox", icon: "✉", badge: 3 },// { label: "You", href: "/me", icon: "☺" }// ]}/>// <Dock side="left" labels={false} items={tools} current={tool} on_select={(v) => { tool = v }}/>//// items: { label, icon, href?, value?, badge?, disabled? }. icon: a character or an// emoji, or an SVG path (`d`, 24x24, stroked) when it starts with "M". An item with// an href is a link; otherwise a button that calls on_select(value), with its label// when it has no value. The one whose href or value is `current` is marked// (aria-current="page"). badge: a count or short text on the icon's corner.// side: "bottom" (default), "top", "left" or "right". labels: the names show under// the icons (true); false keeps them as tooltips and for screen readers.// fixed: it floats over the page at that side (true); false puts it in the flow,// for a toolbar in a box. label: what screen readers call it ("Dock").// on_select(value): inside one of your components, called when a button item is pressed.import ui/theme.qwe use ui_clslet dock_css = css`.ui-dock { position: fixed; z-index: 30; display: flex; gap: 2px; width: fit-content; padding: 6px; border: 1px solid var(--ui-border); border-radius: calc(var(--ui-radius) + 6px); background: color-mix(in srgb, var(--ui-surface) 92%, transparent); backdrop-filter: blur(12px); box-shadow: var(--ui-shadow-lg); color: var(--ui-fg); font: 500 11px/1.2 var(--ui-font);}.ui-dock-bottom { left: 50%; bottom: max(12px, env(safe-area-inset-bottom)); transform: translateX(-50%); }.ui-dock-top { left: 50%; top: 12px; transform: translateX(-50%); }.ui-dock-left { top: 50%; left: 12px; flex-direction: column; transform: translateY(-50%); }.ui-dock-right { top: 50%; right: 12px; flex-direction: column; transform: translateY(-50%); }.ui-dock-inline { position: relative; inset: auto; transform: none; box-shadow: var(--ui-shadow); }.ui-dock-item { position: relative; display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; min-width: 58px; height: 52px; margin: 0; padding: 0 10px; border: 0; border-radius: var(--ui-radius); background: transparent; color: var(--ui-muted); font: inherit; text-decoration: none; white-space: nowrap; cursor: pointer; transition: background .15s, color .15s;}.ui-dock-item:hover { background: var(--ui-subtle); color: var(--ui-fg); }.ui-dock-item[aria-current] { color: var(--ui-accent); background: var(--ui-accent-soft); }.ui-dock-item:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-dock-item:disabled, .ui-dock-item[aria-disabled="true"] { opacity: .5; pointer-events: none; }.ui-dock-icon { display: grid; place-items: center; width: 22px; height: 22px; font-size: 20px; line-height: 1; }.ui-dock-icon svg { width: 22px; height: 22px; }.ui-dock-badge { position: absolute; top: 4px; left: 50%; min-width: 16px; margin-left: 4px; padding: 0 4px; border-radius: 999px; background: var(--ui-danger); color: #fff; font-size: 10px; font-weight: 600; line-height: 16px; text-align: center;}.ui-dock-nolabels .ui-dock-item { min-width: 44px; height: 44px; padding: 0; }.ui-dock-nolabels .ui-dock-badge { top: 2px; }@media (prefers-reduced-motion: reduce) { .ui-dock-item { transition: none; } }`fn dock_icon(icon: str?) = <span.ui-dock-icon aria-hidden="true">{icon != null && starts_with(icon, "M") && len(icon) > 2 ? <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d={icon}/></svg> : icon}</span>view Dock(items: list = [], side: str = "bottom", current = null, labels: bool = true, fixed: bool = true, label: str = "Dock", on_select = null, id: str? = null, class: str? = null) { style global dock_css <nav class={ui_cls(["ui-dock", `ui-dock-${side}`, !fixed && "ui-dock-inline", !labels && "ui-dock-nolabels", class])} id={id} aria-label={label}> {for it in items => { let value = get(it, "value") ?? get(it, "href") ?? it.label let now = current != null && value == current let body = [ dock_icon(get(it, "icon")), get(it, "badge") != null && <span.ui-dock-badge>{get(it, "badge")}</span>, labels ? <span.ui-dock-label>{it.label}</span> : null ] get(it, "href") != null ? <a.ui-dock-item key={value} href={it.href} aria-current={now ? "page" : null} title={labels ? null : it.label} aria-label={labels ? null : it.label} aria-disabled={get(it, "disabled") == true ? "true" : null}>{body}</a> : <button.ui-dock-item key={value} type="button" aria-current={now ? "page" : null} title={labels ? null : it.label} aria-label={labels ? null : it.label} disabled={get(it, "disabled") == true} @click={() => { on_select && on_select(value) }}>{body}</button> }} </nav>}Related
MasterDetailA list on the left and the item picked in detail on the right; one at a time with a Back button when narrow.6 views · 6 downloadsActionBarA page's main action and a line about it, fixed to the bottom of the screen on a phone.13 views · 6 downloadsAppShellAn app's frame: a sidebar of links, a top bar and the page; the sidebar is a drawer on phones.10 views · 7 downloadsCollapsibleA block shown and hidden under a line you click, on the browser's own <details>; open by default or not.7 views · 6 downloadsFooterA site's footer: brand and tagline, columns of links under titles, and a bottom line with the small print.6 views · 6 downloadsNavbarThe bar along the top of a site: name, links and actions; the links fold into a menu when narrow.5 views · 6 downloads
Thread
Thread 0 posts
On Dock: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.