NpsInput
The eleven scores 0 to 10 of a Net Promoter question in a row, with the words at the ends: real radios posted with a form.
Demo
No score yet.
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/nps_input.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/nps_input.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/nps_input.qwe
component NpsInputDemoLive() { state score = null <div.demo-stack> <NpsInput name="nps_live" label="How likely are you to recommend Quillo to a friend?" field="nps-input-demo-live" value={score} on_change={(n) => { score = n }}/> <p.demo-note>{score == null ? "No score yet." : `Score ${score}: a ${nps_input_group(score)}.`}</p> </div>}view NpsInputDemo() = <div.demo-stack> <NpsInput name="nps" value={9} field="nps-input-demo-plain" hint="0 to 6 count against, 9 and 10 for." required={true}/> <NpsInputDemoLive/> <NpsInput name="nps_plain" label="Without the colours" tones={false} low="Never" high="Always" field="nps-input-demo-neutral" error="Choose a score."/> </div>Source
ui/nps_input.qwe
// NpsInput: "How likely are you to recommend us?" as the eleven scores 0 to 10 in// a row, with the words at the two ends. They are real radio buttons in a// <fieldset>, so a plain <form> sends the score as `name`, and the arrow keys move// between them. The scores are coloured as the Net Promoter Score reads them:// 0 to 6 detractors, 7 and 8 passives, 9 and 10 promoters.//// <NpsInput name="nps"/>// <NpsInput name="nps" label="How likely are you to recommend Quillo to a friend?" value={9}// on_change={(n) => { score = n }}/>//// label: the question ("How likely are you to recommend us to a friend or// colleague?"). low and high: the words at the ends ("Not at all likely", "Extremely// likely"). value: the score chosen to start with. tones (true): the colours by// group; false keeps them neutral. hint, error, required, disabled and field (the// ids' start, default "nps-input-" + name) as in RadioGroup.// on_change(score): inside one of your components, given the score as a number.import ui/theme.qwe use ui_clslet nps_input_css = css`.ui-nps-input { min-width: 0; margin: 0; padding: 0; border: 0; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-nps-input-legend { padding: 0; margin-bottom: 10px; font-weight: 500; }.ui-nps-input-req { margin-left: 2px; color: var(--ui-danger); }.ui-nps-input-scores { display: grid; grid-template-columns: repeat(11, minmax(0, 1fr)); gap: 4px; }.ui-nps-input-opt { position: relative; display: block; cursor: pointer; }.ui-nps-input-opt input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }.ui-nps-input-score { display: grid; place-items: center; height: 38px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); color: var(--ui-fg); font-weight: 500; font-variant-numeric: tabular-nums; --t: var(--ui-accent); --ts: var(--ui-accent-soft); transition: background .15s, border-color .15s, color .15s, box-shadow .15s;}.ui-nps-input-tones .ui-nps-input-detractor .ui-nps-input-score { --t: var(--ui-danger); --ts: var(--ui-danger-soft); }.ui-nps-input-tones .ui-nps-input-passive .ui-nps-input-score { --t: var(--ui-warning); --ts: var(--ui-warning-soft); }.ui-nps-input-tones .ui-nps-input-promoter .ui-nps-input-score { --t: var(--ui-success); --ts: var(--ui-success-soft); }.ui-nps-input-opt:hover .ui-nps-input-score { border-color: var(--t); background: var(--ts); }.ui-nps-input-opt input:checked + .ui-nps-input-score { border-color: var(--t); background: var(--t); color: #fff; }.ui-nps-input-opt input:focus-visible + .ui-nps-input-score { box-shadow: var(--ui-focus); }.ui-nps-input-opt:has(input:disabled) { opacity: .55; cursor: not-allowed; }.ui-nps-input-ends { display: flex; justify-content: space-between; gap: 12px; margin-top: 6px; font-size: 12px; color: var(--ui-muted); }.ui-nps-input-invalid .ui-nps-input-score { border-color: var(--ui-danger); }.ui-nps-input-hint, .ui-nps-input-error { margin: 8px 0 0; font-size: 13px; color: var(--ui-muted); }.ui-nps-input-error { color: var(--ui-danger); }@media (max-width: 480px) { .ui-nps-input-score { height: 32px; font-size: 13px; } }@media (prefers-reduced-motion: reduce) { .ui-nps-input-score { transition: none; } }`fn nps_input_group(n: int) -> str = n <= 6 ? "detractor" : n <= 8 ? "passive" : "promoter"view NpsInput(name: str, label: str? = "How likely are you to recommend us to a friend or colleague?", low: str = "Not at all likely", high: str = "Extremely likely", value: int? = null, tones: bool = true, hint: str? = null, error: str? = null, required: bool = false, disabled: bool = false, field: str? = null, on_change = null, class: str? = null) { style global nps_input_css let id = field ?? `nps-input-${name}` let described = ui_cls([hint != null && `${id}-hint`, error != null && `${id}-error`]) <fieldset class={ui_cls(["ui-nps-input", tones && "ui-nps-input-tones", error != null && "ui-nps-input-invalid", class])} id={id} disabled={disabled} role="radiogroup" aria-required={required ? "true" : null} aria-invalid={error != null ? "true" : null} aria-describedby={described != "" ? described : null}> {label != null && <legend.ui-nps-input-legend> {label}{required && <span.ui-nps-input-req aria-hidden="true">*</span>} </legend>} <div.ui-nps-input-scores> {for n in range(11) => <label class={`ui-nps-input-opt ui-nps-input-${nps_input_group(n)}`} for={`${id}-${n}`}> <input type="radio" id={`${id}-${n}`} name={name} value={n} checked={n == value} required={required} aria-label={n == 0 ? `0, ${low}` : n == 10 ? `10, ${high}` : str(n)} @change={(e) => { on_change && on_change(int(e.value)) }}/> <span.ui-nps-input-score aria-hidden="true">{n}</span> </label>} </div> <div.ui-nps-input-ends aria-hidden="true"><span>{low}</span><span>{high}</span></div> {hint != null && <p.ui-nps-input-hint id={`${id}-hint`}>{hint}</p>} {error != null && <p.ui-nps-input-error id={`${id}-error`} role="alert">{error}</p>} </fieldset>}Related
PasswordStrengthA new-password field with a meter that fills as it gets stronger and a checklist of rules ticked as they are met.5 views · 4 downloadsAddressFormStreet, 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 downloads
Thread
Thread 0 posts
On NpsInput: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.