FormSummary
A read-only review of the values entered, in sections each with an Edit link, for the last step before sending.
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/form_summary.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/form_summary.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/form_summary.qwe
component FormSummaryDemoLive() { state editing = null <div.demo-stack> <FormSummary columns={1} on_edit={(k) => { editing = k }} sections={[ { title: "Shipping", key: "ship", items: [{ label: "Address", value: "1 Rue de Rivoli, 75001 Paris" }] }, { title: "Payment", key: "pay", items: [{ label: "Card", value: "Visa ending 4242" }] }, ]}/> <p.demo-note>{editing == null ? "Press Edit on a section." : `on_edit got "${editing}".`}</p> </div>}view FormSummaryDemo() = <div.demo-stack> <FormSummary sections={[ { title: "Account", href: "#account", items: [{ label: "Name", value: "Ada Lovelace" }, { label: "Email", value: "ada@example.com" }, { label: "Newsletter", value: true }] }, { title: "Plan", href: "#plan", items: [{ label: "Plan", value: "Team" }, { label: "Seats", value: 4 }, { label: "Add-ons", value: ["Audit log", "SSO"] }, { label: "Coupon", value: null }] }, ]}/> <FormSummaryDemoLive/> </div>Source
ui/form_summary.qwe
// FormSummary: a read-only review of what was entered, in sections each with an// Edit link, for the last step of a sign-up or a checkout before it is sent.//// <FormSummary sections={[// { title: "Account", href: "/signup/account", items: [{ label: "Name", value: "Ada" }, { label: "Email", value: "ada@example.com" }] },// { title: "Plan", href: "/signup/plan", items: [{ label: "Plan", value: "Team" }, { label: "Seats", value: 4 }] },// ]}/>// <FormSummary items={[{ label: "Name", value: "Ada" }]} on_edit={(key) => { step = key }}/>//// sections: maps of { title, items, href?, key? }: href makes the Edit link; key// (the title by default) is what on_edit is given when there is no href. items:// one section's items, without a title. An item is { label, value }: a list is// written with commas, true and false as Yes and No, a number as it is, and null// or "" as empty ("—"). edit_label ("Edit"). columns: 1 (label above value) or// 2 (default, side by side where there is room). label: what screen readers call// the whole ("Summary").// on_edit(key): inside one of your components, called with the section's key// when its Edit button is pressed (sections with href are links instead).import ui/theme.qwe use ui_clslet form_summary_css = css`.ui-form-summary { display: grid; gap: 18px; min-width: 0; font: 14px/1.45 var(--ui-font); color: var(--ui-fg); }.ui-form-summary-section { display: grid; gap: 10px; }.ui-form-summary-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding-bottom: 8px; border-bottom: 1px solid var(--ui-border); }.ui-form-summary-title { margin: 0; font-size: 15px; font-weight: 600; }.ui-form-summary-edit { padding: 0; border: 0; background: none; color: var(--ui-accent); font: 500 13px var(--ui-font); text-decoration: none; cursor: pointer; border-radius: 3px;}.ui-form-summary-edit:hover { text-decoration: underline; }.ui-form-summary-edit:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-form-summary-list { display: grid; gap: 8px; margin: 0; }.ui-form-summary-item { display: grid; grid-template-columns: minmax(120px, 1fr) 2fr; gap: 4px 16px; margin: 0; }.ui-form-summary-one .ui-form-summary-item { grid-template-columns: 1fr; }.ui-form-summary-item dt { color: var(--ui-muted); }.ui-form-summary-item dd { margin: 0; overflow-wrap: anywhere; }.ui-form-summary-empty { color: var(--ui-muted); }@media (max-width: 480px) { .ui-form-summary-item { grid-template-columns: 1fr; } }`// A value as it is written in the summary.fn form_summary_text(v) -> str = match v { null => "" true => "Yes" false => "No" list as xs => xs | map((x) => form_summary_text(x)) | filter((s) => s != "") | join(", ") _ => str(v)}view FormSummary(sections: list? = null, items: list = [], edit_label: str = "Edit", columns: int = 2, label: str = "Summary", on_edit = null, class: str? = null) { style global form_summary_css let parts = sections ?? [{ title: null, items: items }] <div class={ui_cls(["ui-form-summary", columns == 1 && "ui-form-summary-one", class])} role="group" aria-label={label}> {for [i, s] in enumerate(parts) => { let key = s.key ?? s.title ?? str(i) let edit = s.href != null ? <a.ui-form-summary-edit href={s.href} aria-label={s.title != null ? `${edit_label} ${s.title}` : null}>{edit_label}</a> : on_edit != null ? <button.ui-form-summary-edit type="button" aria-label={s.title != null ? `${edit_label} ${s.title}` : null} @click={() => { on_edit(key) }}>{edit_label}</button> : null <section.ui-form-summary-section aria-label={s.title}> {(s.title != null || edit != null) && <div.ui-form-summary-head> {s.title != null && <h3.ui-form-summary-title>{s.title}</h3>} {edit} </div>} <dl.ui-form-summary-list> {for it in s.items ?? [] => { let text = form_summary_text(it.value) <div.ui-form-summary-item> <dt>{it.label}</dt> <dd>{text != "" ? text : <span.ui-form-summary-empty aria-label="empty">—</span>}</dd> </div> }} </dl> </section> }} </div>}Related
ChecklistInputCheckboxes under a progress line that says how many are done and fills as they are ticked; each posts with a form.5 views · 7 downloadsSortableListA list put in order by dragging or with the keyboard, each move announced, the order sent with a form.5 views · 5 downloadsTransferListTwo filterable lists to move options between, several at a time, by mouse or keyboard.5 views · 5 downloadsAddressFormStreet, city, region, postcode and country in the usual layout, with the autocomplete names browsers fill from.5 views · 7 downloadsAmountInputReads 1.5k, 2m, 1,234.50, 1.234,50, $12 and EUR 40, decides which mark groups, shows the normalised amount, flags what could go two ways, and sends a real decimal.3 views · 8 downloadsAuthFormSign in, sign up, forgot and reset password in one form that switches in place, each posting to its own route.7 views · 4 downloads
Thread
Thread 0 posts
On FormSummary: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.