NumberInput
A number box with − and + buttons that stop at its limits, and keeps what is typed between them.
Demo
Six at most per order.
Total: $36
At least one guest.
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/number_input.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/number_input.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/number_input.qwe
component NumberInputDemoLive() { state qty = 2 <div.demo-stack> <NumberInput name="qty" label="Tickets" value={qty} min={1} max={6} hint="Six at most per order." on_change={(n) => { qty = n }}/> <p.demo-note>Total: ${qty != null ? qty * 18 : 0}</p> </div>}view NumberInputDemo() = <div.demo-stack> <NumberInputDemoLive/> <NumberInput name="guests" label="Guests" value={0} min={1} max={12} required={true} error="At least one guest."/> <div.demo-row> <NumberInput name="weight" label="Weight (kg)" value={2.5} min={0} step={0.5}/> <NumberInput name="copies" label="Copies" size="sm" placeholder="1"/> <NumberInput name="locked" label="Seats" size="sm" value={4} disabled={true}/> </div> </div>Source
ui/number_input.qwe
// NumberInput: a labelled number box with − and + buttons around it. It is a real// <input type="number">, so it works in a plain <form>, and ArrowUp / ArrowDown// step it; what is typed is kept between min and max when it is left.//// <NumberInput name="guests" label="Guests" value={2} min={1} max={12}/>// <NumberInput name="qty" label="Quantity" value={qty} min={0} on_change={(n) => { qty = n }}/>//// value: where it starts (null: empty). min, max: the limits (none by default);// the buttons are disabled at them. step: how much a button or arrow adds (1).// placeholder, hint, error, required, disabled, field (the id, default// "number-input-" + name) and size ("sm" or "md") as in TextField.// on_change(value): inside one of your components, given the number (null when// it is emptied), after each step or when what was typed is left.import ui/theme.qwe use ui_clslet number_input_css = css`.ui-number-input { display: grid; gap: 6px; min-width: 0; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-number-input-label { font-size: 13px; font-weight: 500; color: var(--ui-fg); }.ui-number-input-req { margin-left: 2px; color: var(--ui-danger); }.ui-number-input-box { display: flex; align-items: stretch; height: 38px; width: 180px; max-width: 100%; background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); overflow: hidden; transition: border-color .15s, box-shadow .15s;}.ui-number-input-box:hover { border-color: var(--ui-strong); }.ui-number-input-box:focus-within { border-color: var(--ui-accent); box-shadow: var(--ui-focus); }.ui-number-input-box input { flex: 1; min-width: 0; padding: 0 8px; border: 0; outline: none; background: transparent; color: var(--ui-fg); font: 14px var(--ui-font); text-align: center; font-variant-numeric: tabular-nums; appearance: textfield; -moz-appearance: textfield;}.ui-number-input-box input::-webkit-inner-spin-button, .ui-number-input-box input::-webkit-outer-spin-button { appearance: none; margin: 0; }.ui-number-input-box input::placeholder { color: var(--ui-muted); opacity: 1; }.ui-number-input-step { flex: none; width: 36px; border: 0; background: var(--ui-subtle); color: var(--ui-fg); font: 500 16px/1 var(--ui-font); cursor: pointer; transition: background .15s, color .15s;}.ui-number-input-step:first-child { border-right: 1px solid var(--ui-border); }.ui-number-input-step:last-child { border-left: 1px solid var(--ui-border); }.ui-number-input-step:hover:not(:disabled) { background: var(--ui-accent-soft); color: var(--ui-accent); }.ui-number-input-step:disabled { color: var(--ui-muted); opacity: .5; cursor: not-allowed; }.ui-number-input-sm .ui-number-input-box { height: 30px; width: 140px; }.ui-number-input-sm .ui-number-input-box input { font-size: 13px; }.ui-number-input-sm .ui-number-input-step { width: 28px; font-size: 14px; }.ui-number-input-invalid .ui-number-input-box, .ui-number-input-invalid .ui-number-input-box:hover { border-color: var(--ui-danger); }.ui-number-input-invalid .ui-number-input-box:focus-within { box-shadow: 0 0 0 3px color-mix(in srgb, var(--ui-danger) 30%, transparent); }.ui-number-input-hint, .ui-number-input-error { margin: 0; font-size: 13px; color: var(--ui-muted); }.ui-number-input-error { color: var(--ui-danger); }.ui-number-input-disabled { opacity: .55; }.ui-number-input-disabled .ui-number-input-box:hover { border-color: var(--ui-border); }.ui-number-input-disabled input { cursor: not-allowed; }@media (prefers-reduced-motion: reduce) { .ui-number-input-box, .ui-number-input-step { transition: none; } }`// n kept between lo and hi (either may be null), and rounded to step's decimals.fn number_input_clamp(n: num, lo: num?, hi: num?, step: num) { let places = len(split(str(step), ".")[1] ?? "") let r = places > 0 ? round(n, places) : n lo != null && r < lo ? lo : hi != null && r > hi ? hi : r}component NumberInput(name: str? = null, label: str? = null, value: num? = null, placeholder: str? = null, hint: str? = null, error: str? = null, required: bool = false, disabled: bool = false, field: str? = null, size: str = "md", min: num? = null, max: num? = null, step: num = 1, on_change = null, class: str? = null) { style global number_input_css state n = value state shown = 0 let id = field ?? `number-input-${name ?? re.replace(lower(label ?? "number"), "[^a-z0-9]+", "-")}` let described = ui_cls([hint != null && `${id}-hint`, error != null && `${id}-error`]) let set_to = (x) => { let c = x == null ? null : number_input_clamp(x, min, max, step) n = c // The box may show what was typed (150) when n was already the limit (100): // `shown` changing puts n back in it. shown += 1 on_change && on_change(c) } <div class={ui_cls(["ui-number-input", size == "sm" && "ui-number-input-sm", error != null && "ui-number-input-invalid", disabled && "ui-number-input-disabled", class])}> {label != null && <label.ui-number-input-label for={id}> {label}{required && <span.ui-number-input-req aria-hidden="true">*</span>} </label>} <div.ui-number-input-box> <button.ui-number-input-step type="button" tabindex="-1" aria-label="Decrease" aria-controls={id} disabled={disabled || (min != null && n != null && n <= min)} @click={() => set_to((n ?? (min ?? 0) + step) - step)}>−</button> <input type="number" id={id} name={name} value={shown >= 0 ? n : null} min={min} max={max} step={step} inputmode="decimal" placeholder={placeholder} required={required} disabled={disabled} aria-invalid={error != null ? "true" : null} aria-describedby={described != "" ? described : null} @change={(e) => { set_to(e.value == "" || is_err(float(e.value)) ? null : float(e.value)) }}/> <button.ui-number-input-step type="button" tabindex="-1" aria-label="Increase" aria-controls={id} disabled={disabled || (max != null && n != null && n >= max)} @click={() => set_to((n ?? (min ?? 0) - step) + step)}>+</button> </div> {hint != null && <p.ui-number-input-hint id={`${id}-hint`}>{hint}</p>} {error != null && <p.ui-number-input-error id={`${id}-error`} role="alert">{error}</p>} </div>}Related
AddressFormStreet, city, region, postcode and country in the usual layout, with the autocomplete names browsers fill from.5 views · 7 downloadsAutocompleteA free-text box that offers matches as one types, with the arrow keys and Enter; whatever is typed may stay.5 views · 7 downloadsCardInputA payment card as it is typed: the number grouped, the brand, MM/YY and the code, with typos said.6 views · 6 downloadsCharacterCounterA text box or textarea with a live 120 / 280 count that warns near the limit, as a hard stop or a soft one that blocks sending.5 views · 6 downloadsCheckboxA checkbox drawn by CSS on a real input, with an indeterminate state, and a group of them in a fieldset.5 views · 6 downloadsCheckboxCardsAny number of choices as cards with a title, a line of detail and a check, each a real checkbox a form sends.5 views · 6 downloads
Thread
Thread 0 posts
On NumberInput: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.