Forms

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.

#forms#input#feedback#no-js

Demo

Preview
How likely are you to recommend us to a friend or colleague?

0 to 6 count against, 9 and 10 for.

How likely are you to recommend Quillo to a friend?

No score yet.

Without the colours

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.qwe
main.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>}
Thread
57Useful? Like it, or add to its thread below.

Thread 0 posts

On NpsInput: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.