PlanToggle
Monthly or yearly billing as a pill of two radios, with the saving shown on the yearly side.
Demo
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.qwemain.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>}Related
CartItemOne line of a cart: picture, name and options, a quantity control, the line's total and Remove, as inputs a form sends.6 views · 6 downloadsPaymentMethodsWays to pay as radio cards with their marks: card (with the brands taken), PayPal, Apple Pay, Google Pay, bank, cash.6 views · 5 downloadsShippingOptionsShipping methods as radio cards, each with its price and how long it takes; a plain form sends the choice.7 views · 7 downloadsVariantPickerSizes as buttons and colours as swatches, sold-out ones crossed out: radio groups for the keyboard, hidden inputs for forms.6 views · 6 downloadsCurrencyInputAn amount with its currency's symbol, thousands grouped as it is typed and the decimals the currency has, sent as plain digits.5 views · 5 downloadsCartSummarySubtotal, shipping, tax, a discount and the total added up, with a Checkout button as a link, a form or a callback.6 views · 7 downloads
Thread
Thread 0 posts
On PlanToggle: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.