ToggleButton
A button that stays pressed, alone or in a group where one or any number are pressed; a form sends it.
Demo
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.qwemain.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>}Related
BookmarkButtonA save-for-later toggle whose bookmark fills when pressed, with or without its label.8 views · 8 downloadsButtonA button, or a link that looks like one: four variants, three tones, three sizes, loading.7 views · 7 downloadsButtonGroupButtons joined edge to edge into one control, or spaced in a row; across or upright.6 views · 6 downloadsFabA round floating action button fixed to a corner of the screen, with an icon and an optional label.5 views · 7 downloadsFollowButtonFollow becomes Following once pressed, and says Unfollow on hover, as the social sites do.5 views · 7 downloadsIconButtonA square button with an icon, labelled for screen readers and on hover; built-in icons or your own.6 views · 9 downloads
Thread
Thread 0 posts
On ToggleButton: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.