ProgressNav
Numbered steps as links: the ones done link back, this one is marked, and the ones to come wait their turn.
Demo
Step 2 of 4: Delivery. Every step is a link (ahead), and clicking one goes there.
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/progress_nav.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/progress_nav.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/progress_nav.qwe
let progress_nav_demo_steps = [ { label: "Cart", href: "#pn-cart", hint: "3 items" }, { label: "Delivery", href: "#pn-delivery", hint: "Lisbon" }, { label: "Payment", href: "#pn-payment" }, { label: "Review", href: "#pn-review" }]component ProgressNavDemoLive() { state at = 1 <div.demo-stack> <ProgressNav id="progress-nav-demo-live" steps={progress_nav_demo_steps} current={at} ahead={true} on_select={(i) => { at = i }}/> <p.demo-note>Step {at + 1} of 4: {progress_nav_demo_steps[at].label}. Every step is a link (ahead), and clicking one goes there.</p> </div>}view ProgressNavDemo() = <div.demo-stack style="gap: 28px"> <ProgressNav id="progress-nav-demo" steps={progress_nav_demo_steps} current={2}/> <ProgressNavDemoLive/> <div style="max-width: 280px"> <ProgressNav vertical={true} current={4} label="Setup" steps={[ { label: "Create account", href: "#pn-1" }, { label: "Verify email", href: "#pn-2" }, { label: "Pick a plan", href: "#pn-3" }, { label: "Invite the team", href: "#pn-4" } ]}/> </div> </div>Source
ui/progress_nav.qwe
// ProgressNav: the steps of a process as numbered links: those done (with a check// mark, and a link back to them), this one, and those to come (not yet links). For// a checkout, a sign-up, a wizard whose steps are pages.//// <ProgressNav current={1} steps={[// { label: "Cart", href: "/checkout/cart" },// { label: "Delivery", href: "/checkout/delivery" },// { label: "Payment", href: "/checkout/payment" },// { label: "Review", href: "/checkout/review" }// ]}/>//// steps: { label, href?, hint? }, in order. current: the index of this step, from 0;// those before it are done and link back (to go and change something), this one is// marked aria-current="step", and those after it are plain text, as they cannot be// reached yet. Set current to len(steps) when everything is done. ahead: let the// coming steps be links too (when the pages can be visited in any order).// vertical: one under another. label: what screen readers call it ("Progress"). id: the nav's id.// on_select(index): inside one of your components, called with the step clicked,// instead of following its link.import ui/theme.qwe use ui_clslet progress_nav_css = css`.ui-prognav ol { display: flex; margin: 0; padding: 0; list-style: none; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-prognav-item { position: relative; flex: 1 1 0; min-width: 0; display: flex; }.ui-prognav-item:not(:last-child)::after { content: ""; position: absolute; top: 15px; left: calc(100% - 50% + 22px); right: calc(-50% + 22px); height: 2px; border-radius: 2px; background: var(--ui-border);}.ui-prognav-done:not(:last-child)::after { background: var(--ui-accent); }.ui-prognav-step { display: flex; flex: 1; flex-direction: column; align-items: center; gap: 8px; min-width: 0; padding: 0 4px 2px; border-radius: var(--ui-radius-sm); color: var(--ui-muted); text-align: center; text-decoration: none;}a.ui-prognav-step:hover .ui-prognav-label { color: var(--ui-accent); text-decoration: underline; text-underline-offset: 3px; }.ui-prognav-step:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-prognav-mark { position: relative; z-index: 1; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 50%; border: 2px solid var(--ui-border); background: var(--ui-surface); color: var(--ui-muted); font: 600 13px/1 var(--ui-font); font-variant-numeric: tabular-nums;}.ui-prognav-mark svg { width: 15px; height: 15px; }.ui-prognav-done .ui-prognav-mark { border-color: var(--ui-accent); background: var(--ui-accent); color: var(--ui-accent-fg); }.ui-prognav-current .ui-prognav-mark { border-color: var(--ui-accent); color: var(--ui-accent); box-shadow: var(--ui-focus); }.ui-prognav-current .ui-prognav-step { color: var(--ui-fg); }.ui-prognav-label { font-weight: 500; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.ui-prognav-current .ui-prognav-label { font-weight: 600; }.ui-prognav-hint { color: var(--ui-muted); font-size: 12px; }.ui-prognav-vertical ol { flex-direction: column; }.ui-prognav-vertical .ui-prognav-item { flex: none; }.ui-prognav-vertical .ui-prognav-item:not(:last-child)::after { top: 38px; bottom: -4px; left: 15px; right: auto; width: 2px; height: auto; }.ui-prognav-vertical .ui-prognav-step { flex-direction: row; align-items: flex-start; gap: 12px; padding: 0 0 20px; text-align: left; }.ui-prognav-vertical .ui-prognav-item:last-child .ui-prognav-step { padding-bottom: 0; }.ui-prognav-vertical .ui-prognav-mark { flex: none; }.ui-prognav-vertical .ui-prognav-text { padding-top: 6px; }@media (max-width: 600px) { .ui-prognav:not(.ui-prognav-vertical) .ui-prognav-hint { display: none; } .ui-prognav:not(.ui-prognav-vertical) .ui-prognav-label { font-size: 12px; }}`view ProgressNav(steps: list = [], current: int = 0, ahead: bool = false, vertical: bool = false, label: str = "Progress", id: str? = null, on_select = null, class: str? = null) { style global progress_nav_css <nav class={ui_cls(["ui-prognav", vertical && "ui-prognav-vertical", class])} id={id} aria-label={label}> <ol> {for [i, s] in enumerate(steps) => { let phase = i < current ? "done" : i == current ? "current" : "coming" let linked = get(s, "href") != null && (phase == "done" || (ahead && phase == "coming")) let body = [ <span.ui-prognav-mark aria-hidden="true">{phase == "done" ? <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 8.5l3 3 6-7"/></svg> : i + 1}</span>, <span.ui-prognav-text> <span.ui-prognav-label>{s.label}{phase == "done" && <span.ui-sr> (done)</span>}</span> {get(s, "hint") != null && <span.ui-prognav-hint>{s.hint}</span>} </span>, ] <li class={`ui-prognav-item ui-prognav-${phase}`}> {linked ? <a.ui-prognav-step href={s.href} aria-current={phase == "current" ? "step" : null} @click={(e) => { if on_select != null { e.preventDefault(); on_select(i) } }}>{body}</a> : <span.ui-prognav-step aria-current={phase == "current" ? "step" : null}>{body}</span>} </li> }} </ol> </nav>}Related
AlphabetIndexA to Z links into a list sorted by name; letters with nothing under them stay in place, disabled.4 views · 6 downloadsBreadcrumbsWhere this page sits: a trail of links up to the home page, the last one marked as this page.5 views · 6 downloadsCollapsibleNavSide navigation in groups that fold under their titles, the browser remembering which are open.6 views · 7 downloadsDirectoryNavA long list of names grouped under sticky letter headers, with a row of letters at the top that jumps to each group.5 views · 5 downloadsFooterNavColumns of grouped links for a site's footer, each under its title, wrapping on narrow screens.5 views · 7 downloadsJumpLinksA row of links to the sections of the page, scrolling sideways when narrow and sticking to the top if asked.6 views · 6 downloads
Thread
Thread 0 posts
On ProgressNav: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.