Media

VolumeControl

A mute button and a level slider with an icon that shows how loud it is; a real range, so forms and keyboards work.

#media#audio#input#forms#interactive

Demo

Preview

Level: 60%

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

Example

The code of the preview above.

demos/volume_control.qwe
component VolumeControlDemoLive() {    state level = 0.6    <div.demo-row>        <VolumeControl level={0.6} on_change={(v) => { level = v }}/>        <p.demo-note id="volume-control-demo-note">Level: {round(level * 100)}%</p>    </div>}view VolumeControlDemo() =    <div.demo-stack>        <VolumeControlDemoLive/>        <div.demo-row id="volume-control-demo-others" style="align-items: flex-end">            <VolumeControl name="alerts" label="Alert volume" level={1} size="sm"/>            <VolumeControl level={0.3} muted={true}/>            <VolumeControl level={0.8} vertical={true} label="Vertical"/>        </div>    </div>

Source

ui/volume_control.qwe
// VolumeControl: a mute button and a slider for a level from 0 to 1, with an// icon that shows how loud it is. The slider is a real range input, so it works// with the keyboard and screen readers, and a form sends `name` when there is one.////   <VolumeControl level={0.6} on_change={(v) => { player.volume = v }}/>//   <VolumeControl name="volume" label="Alert volume" level={1} size="sm"/>//// level: where it starts, 0 to 1 (1). muted: starts muted (false).// name: the slider's name in a form. label: what the slider is called// ("Volume"). size: "sm" or "md" (default). vertical: the slider stands up.// on_change(level): inside one of your components, called with the level as it// moves, 0 when muted, and the level kept when unmuted.import ui/theme.qwe use ui_clslet volume_control_css = css`.ui-volume-control { display: inline-flex; align-items: center; gap: 6px; color: var(--ui-fg); font: 13px/1 var(--ui-font); }.ui-volume-control-btn { display: grid; place-items: center; flex: none; width: 34px; height: 34px; padding: 0; border: 0; border-radius: var(--ui-radius-sm); background: transparent; color: inherit; cursor: pointer; }.ui-volume-control-btn:hover { background: var(--ui-subtle); }.ui-volume-control-btn:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-volume-control-btn svg { width: 20px; height: 20px; }.ui-volume-control-btn[aria-pressed="true"] { color: var(--ui-muted); }.ui-volume-control input { appearance: none; width: 100px; height: 20px; margin: 0; background: transparent; cursor: pointer; }.ui-volume-control input::-webkit-slider-runnable-track { height: 5px; border-radius: 999px; background: linear-gradient(to right, var(--ui-accent) var(--f, 0%), var(--ui-subtle) var(--f, 0%)); box-shadow: inset 0 0 0 1px var(--ui-border); }.ui-volume-control input::-moz-range-track { height: 5px; border-radius: 999px; background: var(--ui-subtle); }.ui-volume-control input::-moz-range-progress { height: 5px; border-radius: 999px; background: var(--ui-accent); }.ui-volume-control input::-webkit-slider-thumb { appearance: none; width: 14px; height: 14px; margin-top: -4.5px; border-radius: 50%; background: var(--ui-surface); border: 2px solid var(--ui-accent); box-shadow: var(--ui-shadow); }.ui-volume-control input::-moz-range-thumb { width: 10px; height: 10px; border-radius: 50%; background: var(--ui-surface); border: 2px solid var(--ui-accent); }.ui-volume-control input:focus-visible { outline: none; box-shadow: var(--ui-focus); border-radius: 999px; }.ui-volume-control-sm .ui-volume-control-btn { width: 28px; height: 28px; }.ui-volume-control-sm .ui-volume-control-btn svg { width: 16px; height: 16px; }.ui-volume-control-sm input { width: 72px; }.ui-volume-control-vertical { flex-direction: column-reverse; }.ui-volume-control-vertical input { writing-mode: vertical-lr; direction: rtl; width: 20px; height: 100px; }.ui-volume-control-vertical input::-webkit-slider-runnable-track { width: 5px; height: auto; background: linear-gradient(to top, var(--ui-accent) var(--f, 0%), var(--ui-subtle) var(--f, 0%)); }.ui-volume-control-vertical input::-webkit-slider-thumb { margin-top: 0; margin-left: -4.5px; }`component VolumeControl(level: num = 1, muted: bool = false, name: str? = null, label: str = "Volume", size: str = "md",                        vertical: bool = false, on_change = null, class: str? = null) {    style global volume_control_css    state v = level < 0 ? 0 : level > 1 ? 1 : level    state quiet = muted    let shown = quiet ? 0 : v    let pct = round(shown * 100)    let tell = () => { on_change && on_change(shown) }    <div class={ui_cls(["ui-volume-control", size == "sm" && "ui-volume-control-sm", vertical && "ui-volume-control-vertical", class])}>        <button.ui-volume-control-btn type="button" aria-label={quiet ? "Unmute" : "Mute"} aria-pressed={quiet ? "true" : "false"}                @click={() => {                    quiet = !quiet                    if !quiet && v == 0 { v = 0.5 }                    tell()                }}>            {shown == 0                ? <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 5 6 9H2v6h4l5 4V5ZM23 9l-6 6M17 9l6 6"/></svg>                : shown < 0.5                    ? <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 5 6 9H2v6h4l5 4V5ZM15.54 8.46a5 5 0 0 1 0 7.07"/></svg>                    : <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 5 6 9H2v6h4l5 4V5ZM19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>}        </button>        <input type="range" min="0" max="1" step="0.01" value={shown} name={name} aria-label={label} aria-valuetext={`${pct}%`}               style={`--f: ${pct}%`} aria-orientation={vertical ? "vertical" : null}               @input={(e) => {                   let n = float(e.value)                   v = n                   quiet = false                   tell()               }}/>    </div>}
Thread
67Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.