Commerce

PlanToggle

Monthly or yearly billing as a pill of two radios, with the saving shown on the yearly side.

#commerce#price#forms#select#interactive

Demo

Preview

Pro costs $12 a month (on_change).

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/plan_toggle.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/plan_toggle.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/plan_toggle.qwe
component PlanToggleDemoLive() {    state period = "monthly"    <div.demo-stack>        <PlanToggle name="plan-toggle-demo-live" saving={20} on_change={(p) => { period = p }}/>        <p.demo-note>Pro costs {period == "yearly" ? "$115 a year ($9.60 a month)" : "$12 a month"} (on_change).</p>    </div>}view PlanToggleDemo() =    <div.demo-stack>        <PlanToggleDemoLive/>        <div.demo-row style="gap: 20px">            <PlanToggle name="plan-toggle-demo-free" value="yearly" saving="2 months free"/>            <PlanToggle name="plan-toggle-demo-sm" size="sm" labels={["Pay monthly", "Pay yearly"]} values={["m", "y"]}/>        </div>    </div>

Source

ui/plan_toggle.qwe
// PlanToggle: monthly or yearly billing, as a pill with the saving on the yearly// side. They are real radio buttons, so a plain <form> sends the one chosen as// `name`, and the arrow keys move between them.////   <PlanToggle saving={20}/>//   <PlanToggle value={period} saving="2 months free" on_change={(p) => { period = p }}/>//// value: "monthly" (default) or "yearly". saving: a number (shown "Save 20%")// or text ("2 months free"), on the yearly side. labels: the two words// (["Monthly", "Yearly"]). values: what they send (["monthly", "yearly"]).// name: the radios' name ("billing"). label: the group's name for screen// readers ("Billing period"). size: "sm" or "md" (default). on_change(value):// inside one of your components, given the value chosen.import ui/theme.qwe use ui_clslet plan_toggle_css = css`.ui-plan-toggle { display: inline-flex; align-items: center; gap: 2px; padding: 3px; border: 1px solid var(--ui-border); border-radius: 999px; background: var(--ui-subtle);  font: 500 14px/1 var(--ui-font); color: var(--ui-muted); }.ui-plan-toggle-opt { position: relative; display: flex; cursor: pointer; }.ui-plan-toggle-opt input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }.ui-plan-toggle-text { display: inline-flex; align-items: center; gap: 8px; height: 32px; padding: 0 16px; border-radius: 999px; white-space: nowrap;  transition: background .15s, color .15s, box-shadow .15s; }.ui-plan-toggle-opt:hover .ui-plan-toggle-text { color: var(--ui-fg); }.ui-plan-toggle-opt input:checked + .ui-plan-toggle-text { background: var(--ui-surface); color: var(--ui-fg); box-shadow: var(--ui-shadow); }.ui-plan-toggle-opt input:focus-visible + .ui-plan-toggle-text { box-shadow: var(--ui-focus); }.ui-plan-toggle-opt input:checked:focus-visible + .ui-plan-toggle-text { box-shadow: var(--ui-shadow), var(--ui-focus); }.ui-plan-toggle-save { display: inline-flex; align-items: center; height: 20px; padding: 0 7px; border-radius: 999px; background: var(--ui-success-soft); color: var(--ui-success); font: 600 11px/1 var(--ui-font); }.ui-plan-toggle-sm .ui-plan-toggle-text { height: 26px; padding: 0 12px; font-size: 13px; }.ui-plan-toggle-sm .ui-plan-toggle-save { height: 18px; font-size: 10px; }@media (prefers-reduced-motion: reduce) { .ui-plan-toggle-text { transition: none; } }`component PlanToggle(value: str = "monthly", saving = null, labels: list = ["Monthly", "Yearly"], values: list = ["monthly", "yearly"],                     name: str = "billing", label: str = "Billing period", size: str = "md", on_change = null, class: str? = null) {    style global plan_toggle_css    state period = value    let save_text = saving == null ? null : type_of(saving) == "str" ? saving : `Save ${saving}%`    <div class={ui_cls(["ui-plan-toggle", size == "sm" && "ui-plan-toggle-sm", class])} role="radiogroup" aria-label={label}>        {for [i, v] in enumerate(values) => <label.ui-plan-toggle-opt key={v}>            <input type="radio" name={name} value={v} checked={v == period}                   @change={(e) => { period = e.value; on_change && on_change(e.value) }}/>            <span.ui-plan-toggle-text>                {labels[i] ?? v}                {i == 1 && save_text != null && <span.ui-plan-toggle-save>{save_text}</span>}            </span>        </label>}    </div>}
Thread
64Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.