Actions

ToggleButton

A button that stays pressed, alone or in a group where one or any number are pressed; a form sends it.

#forms#button#interactive

Demo

Preview
Not following Ana.
Styles: b

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

Example

The code of the preview above.

demos/toggle_button.qwe
let toggle_button_demo_mic = "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3ZM19 10v2a7 7 0 0 1-14 0v-2M12 19v3"let toggle_button_demo_star = "M12 2l3.1 6.3 6.9 1-5 4.9 1.2 6.8-6.2-3.2-6.2 3.2 1.2-6.8-5-4.9 6.9-1z"component ToggleButtonDemoLive() {    state follows = false    state styles = ["b"]    <div.demo-stack>        <div.demo-row>            <ToggleButton label="Follow" pressed_label="Following" variant="soft" on_change={(on) => { follows = on }}/>            <span.demo-note>{follows ? "You follow Ana: her posts show in your feed." : "Not following Ana."}</span>        </div>        <div.demo-row>            <ToggleButtonGroup label="Text style" multiple={true} values={styles} icon_only={true}                               on_change={(vs) => { styles = vs }}                               options={[{ label: "Bold", value: "b", icon: "B" }, { label: "Italic", value: "i", icon: "I" },                                         { label: "Underline", value: "u", icon: "U" }]}/>            <span.demo-note>Styles: {len(styles) == 0 ? "none" : join(styles | sort(), ", ")}</span>        </div>    </div>}view ToggleButtonDemo() =    <div.demo-stack>        <div.demo-row>            <ToggleButton label="Mute" icon={toggle_button_demo_mic} name="mute"/>            <ToggleButton label="Star" icon={toggle_button_demo_star} icon_only={true} pressed={true}/>            <ToggleButton label="Notifications" variant="ghost" pressed={true}/>            <ToggleButton label="Small" size="sm"/>            <ToggleButton label="Locked" disabled={true}/>        </div>        <div.demo-row>            <ToggleButtonGroup name="align" label="Alignment" value="left"                               options={[{ label: "Left", value: "left" }, { label: "Center", value: "center" },                                         { label: "Right", value: "right" }]}/>            <ToggleButtonGroup name="days" label="Days" multiple={true} values={["mon", "wed"]} attached={false} size="sm"                               options={[{ label: "Mon", value: "mon" }, { label: "Tue", value: "tue" }, { label: "Wed", value: "wed" },                                         { label: "Thu", value: "thu" }, { label: "Fri", value: "fri" }]}/>        </div>        <ToggleButtonDemoLive/>    </div>

Source

ui/toggle_button.qwe
// ToggleButton: a button that stays pressed or not (bold, mute, follow), with// aria-pressed; and ToggleButtonGroup: a row of them where one is pressed (text// alignment) or any number (bold, italic, underline). Each keeps a hidden input// for what is pressed, so a plain <form> sends it.////   <ToggleButton label="Bold" icon="B" icon_only={true} name="bold"/>//   <ToggleButton label="Follow" pressed_label="Following" pressed={follows}//                 on_change={(on) => { follows = on }}/>//   <ToggleButtonGroup name="align" label="Alignment" value="left"//                      options={[{ label: "Left", value: "left" }, { label: "Center", value: "center" }]}/>//   <ToggleButtonGroup name="style" label="Style" multiple={true} values={["b"]} icon_only={true}//                      options={[{ label: "Bold", value: "b", icon: "B" }, { label: "Italic", value: "i", icon: "I" }]}/>//// ToggleButton: label (its name, always), pressed (to start with). pressed_label:// other text to show when pressed; then the text itself says the state, so the// button has no aria-pressed (screen readers would hear both). icon, pressed_icon:// a character or an emoji, or an SVG path (`d`, 24x24, stroked) when it starts// with "M". icon_only: only the icon shows, the label is its name and tooltip.// name, value ("on"): sent as name=value while it is pressed. variant: "outline"// (default), "soft" or "ghost"; size "sm", "md" or "lg"; disabled.// on_change(pressed): inside one of your components, given true or false.//// ToggleButtonGroup: options (strings, or { label, value, icon?, disabled? }),// multiple (any number pressed; else exactly one), value (the one pressed) or// values (the ones pressed, when multiple), name (each pressed value is sent as// name), label (what screen readers call the group), icon_only, variant, size,// attached (true: joined edge to edge). on_change(value), or on_change(values) when// multiple: inside one of your components, given the value or the list pressed.import ui/theme.qwe use ui_clslet toggle_button_css = css`.ui-toggle {  display: inline-flex; align-items: center; justify-content: center; gap: .45em; height: 38px; min-width: 38px;  padding: 0 14px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: transparent;  color: var(--ui-fg); font: 500 14px/1 var(--ui-font); white-space: nowrap; cursor: pointer; vertical-align: middle;  transition: background .15s, border-color .15s, color .15s, box-shadow .15s;}.ui-toggle:hover { background: var(--ui-subtle); border-color: var(--ui-strong); }.ui-toggle:focus-visible { outline: none; box-shadow: var(--ui-focus); position: relative; z-index: 2; }.ui-toggle:disabled { opacity: .55; cursor: not-allowed; }.ui-toggle-soft { border-color: transparent; background: var(--ui-subtle); }.ui-toggle-ghost { border-color: transparent; }.ui-toggle-on, .ui-toggle-on:hover {  border-color: color-mix(in srgb, var(--ui-accent) 45%, var(--ui-border)); background: var(--ui-accent-soft); color: var(--ui-accent);}.ui-toggle-ghost.ui-toggle-on { border-color: transparent; }.ui-toggle-icon { display: inline-grid; place-items: center; min-width: 1em; font-weight: 650; line-height: 1; }.ui-toggle-icon svg { width: 18px; height: 18px; }.ui-toggle-only { padding: 0; width: 38px; }.ui-toggle-sm { height: 30px; min-width: 30px; padding: 0 10px; font-size: 13px; }.ui-toggle-sm.ui-toggle-only { width: 30px; padding: 0; }.ui-toggle-sm .ui-toggle-icon svg { width: 16px; height: 16px; }.ui-toggle-lg { height: 46px; min-width: 46px; padding: 0 20px; font-size: 16px; }.ui-toggle-lg.ui-toggle-only { width: 46px; padding: 0; }.ui-toggle-group { display: inline-flex; flex-wrap: wrap; gap: 6px; vertical-align: middle; }.ui-toggle-group-attached { flex-wrap: nowrap; gap: 0; }.ui-toggle-group-attached > .ui-toggle + .ui-toggle { margin-left: -1px; }.ui-toggle-group-attached > .ui-toggle:not(:first-of-type) { border-top-left-radius: 0; border-bottom-left-radius: 0; }.ui-toggle-group-attached > .ui-toggle:not(:last-of-type) { border-top-right-radius: 0; border-bottom-right-radius: 0; }.ui-toggle-group-attached > .ui-toggle-on { position: relative; z-index: 1; }@media (prefers-reduced-motion: reduce) { .ui-toggle { transition: none; } }`// The icon: text, or an SVG path when it starts with "M".fn toggle_button_icon(icon: str? = null) =    icon == null ? null        : starts_with(icon, "M") && len(icon) > 2            ? <span.ui-toggle-icon aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"                   stroke-linecap="round" stroke-linejoin="round"><path d={icon}/></svg></span>            : <span.ui-toggle-icon aria-hidden="true">{icon}</span>component ToggleButton(label: str, pressed: bool = false, pressed_label: str? = null, icon: str? = null,                       pressed_icon: str? = null, icon_only: bool = false, name: str? = null, value: str = "on",                       variant: str = "outline", size: str = "md", disabled: bool = false, on_change = null,                       class: str? = null) {    style global toggle_button_css    state on = pressed    let says = on && pressed_label != null ? pressed_label : label    <button type="button"            class={ui_cls(["ui-toggle", variant != "outline" && `ui-toggle-${variant}`, size != "md" && `ui-toggle-${size}`,                           icon_only && "ui-toggle-only", on && "ui-toggle-on", class])}            aria-pressed={pressed_label == null ? (on ? "true" : "false") : null}            aria-label={icon_only ? says : null} title={icon_only ? says : null} disabled={disabled}            @click={() => {                on = !on                on_change && on_change(on)            }}>        {toggle_button_icon(on && pressed_icon != null ? pressed_icon : icon)}        {!icon_only && <span>{says}</span>}        {name != null && <input type="hidden" name={name} value={value} disabled={!on}/>}    </button>}fn toggle_button_options(options: list) -> list =    options | map((o) => type_of(o) == "map" ? o | set("value", str(o.value ?? o.label)) | set("label", o.label ?? str(o.value))                                             : { label: str(o), value: str(o) })component ToggleButtonGroup(options: list = [], multiple: bool = false, value: str? = null, values: list = [],                            name: str? = null, label: str? = null, icon_only: bool = false, variant: str = "outline",                            size: str = "md", attached: bool = true, on_change = null, class: str? = null) {    style global toggle_button_css    let opts = toggle_button_options(options)    state picked = multiple ? values : (value != null ? [value] : [])    let press = (v: str) => {        if multiple {            picked = contains(picked, v) ? picked | filter((x) => x != v) : picked | push(v)            on_change && on_change(picked)        } else if !contains(picked, v) {            picked = [v]            on_change && on_change(v)        }    }    <div class={ui_cls(["ui-toggle-group", attached && "ui-toggle-group-attached", class])} role="group" aria-label={label}>        {for o in opts => <button type="button" key={o.value}                class={ui_cls(["ui-toggle", variant != "outline" && `ui-toggle-${variant}`, size != "md" && `ui-toggle-${size}`,                               icon_only && get(o, "icon") != null && "ui-toggle-only", contains(picked, o.value) && "ui-toggle-on"])}                aria-pressed={contains(picked, o.value) ? "true" : "false"} disabled={get(o, "disabled") == true}                aria-label={icon_only && get(o, "icon") != null ? o.label : null}                title={icon_only && get(o, "icon") != null ? o.label : null}                @click={() => { press(o.value) }}>            {toggle_button_icon(get(o, "icon"))}            {!(icon_only && get(o, "icon") != null) && <span>{o.label}</span>}        </button>}        {for v in (name != null ? picked : []) => <input type="hidden" name={name} value={v}/>}    </div>}
Thread
67Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.