Blogging

ClapButton

Applause for a post: each press adds a clap up to a limit per reader, a +3 rises for presses in a row, and a small × takes them back.

#blogging#social#button#reactions#interactive

Demo

Preview

No claps from you yet.

You gave 50 claps, the most you can.You gave 2 claps.

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

Example

The code of the preview above.

demos/clap_button.qwe
component ClapButtonDemoLive() {    state note = "No claps from you yet."    <div.demo-row style="gap: 20px">        <ClapButton count={1204} limit={5} on_change={(n) => { note = n == 0 ? "You took your claps back." : `You gave ${n} of 5.` }}/>        <p.demo-note id="clap-button-demo-note">{note}</p>    </div>}view ClapButtonDemo() =    <div.demo-stack style="padding-top: 34px">        <ClapButtonDemoLive/>        <form id="clap-button-demo-form" class="demo-row" style="gap: 28px" action="#clap_button">            <ClapButton count={48210} mine={50} name="claps" size="lg"/>            <ClapButton count={7} mine={2} size="sm" undo={false}/>            <ClapButton count={0} show_count={false} label="Applaud this post"/>        </form>    </div>

Source

ui/clap_button.qwe
// ClapButton: applause for a post, as Medium has it: each press adds a clap (up to a// limit for each reader), the total goes up with it and a "+3" rises over the button// for the presses in a row; a small × takes your claps back. Screen readers hear how// many you gave. In a form it sends how many claps you gave as `name`.////   <ClapButton count={1204}/>//   <ClapButton count={post.claps} mine={post.my_claps} limit={20} name="claps"/>//   <ClapButton count={claps} on_change={(n) => { save_claps(post.id, n) }}/>//// count: all the claps so far, yours included (written out to 9,999, then as 12.4K and// 1.2M). mine: how many of them are yours, to start with (0). limit:// the most one reader may give (50); the button then says so and takes no more. label:// the button's name for screen readers ("Clap"). name: a hidden input sending your// count, in a <form>. size: "sm", "md" (default) or "lg". show_count: the total beside// it (true). undo: offer the × once you clapped (true). on_change(mine): inside one of// your components, called with how many claps are yours after each press, and with 0// when they are taken back.import ui/theme.qwe use ui_clslet clap_button_css = css`.ui-bclap { position: relative; display: inline-flex; align-items: center; gap: 8px; font: 14px/1 var(--ui-font); color: var(--ui-fg);  font-variant-numeric: tabular-nums; --s: 44px; }.ui-bclap-sm { --s: 34px; font-size: 13px; } .ui-bclap-lg { --s: 56px; font-size: 16px; }.ui-bclap-btn { position: relative; display: inline-grid; place-items: center; width: var(--s); height: var(--s); padding: 0; border: 1px solid var(--ui-border);  border-radius: 50%; background: var(--ui-surface); color: var(--ui-muted); cursor: pointer; transition: border-color .15s, color .15s, transform .1s; }.ui-bclap-btn:hover { border-color: var(--ui-accent); color: var(--ui-accent); }.ui-bclap-btn:active { transform: scale(.92); }.ui-bclap-btn:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-bclap-btn svg { width: 52%; height: 52%; }.ui-bclap-on .ui-bclap-btn { border-color: var(--ui-accent); background: var(--ui-accent-soft); color: var(--ui-accent); }.ui-bclap-on .ui-bclap-btn svg .ui-bclap-palm { fill: color-mix(in srgb, var(--ui-accent) 25%, transparent); }.ui-bclap-full .ui-bclap-btn { cursor: not-allowed; }.ui-bclap-full .ui-bclap-btn:active { transform: none; }.ui-bclap-count { min-width: 2ch; color: var(--ui-muted); font-weight: 600; }.ui-bclap-on .ui-bclap-count { color: var(--ui-fg); }.ui-bclap-burst { position: absolute; left: calc(var(--s) / 2); bottom: calc(100% + 6px); display: grid; place-items: center; min-width: 34px; height: 34px;  padding: 0 6px; border-radius: 999px; background: var(--ui-accent); color: var(--ui-accent-fg); font-size: 13px; font-weight: 700;  transform: translateX(-50%); pointer-events: none; animation: ui-bclap-rise .25s ease-out; }@keyframes ui-bclap-rise { from { opacity: 0; transform: translate(-50%, 8px) scale(.8); } }.ui-bclap-undo { display: inline-grid; place-items: center; width: 24px; height: 24px; padding: 0; border: 0; border-radius: 50%; background: none;  color: var(--ui-muted); cursor: pointer; }.ui-bclap-undo:hover { background: var(--ui-danger-soft); color: var(--ui-danger); }.ui-bclap-undo:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-bclap-undo svg { width: 12px; height: 12px; }.ui-bclap-max { color: var(--ui-muted); font-size: 12px; }@media (prefers-reduced-motion: reduce) { .ui-bclap-btn, .ui-bclap-burst { transition: none; animation: none; } .ui-bclap-btn:active { transform: none; } }`// A count as it reads: 950, 9,950, 12.4K, 1.2M (from ints, so that the server and the// browser write it alike).fn clap_button_count(n: int) -> str {    let short = (unit: int, mark: str) => {        let tenths = round(n * 10 / unit)        tenths % 10 == 0 ? `${floor(tenths / 10)}${mark}` : `${floor(tenths / 10)}.${tenths % 10}${mark}`    }    if n >= 999950 { return short(1000000, "M") }    if n >= 10000 { return short(1000, "K") }    format_number(n, "en-US")}component ClapButton(count: int = 0, mine: int = 0, limit: int = 50, label: str = "Clap", name: str? = null, size: str = "md",                     show_count: bool = true, undo: bool = true, on_change = null, class: str? = null) {    style global clap_button_css    state given = mine    state burst = 0    if burst > 0 { every(1000, () => { burst = 0 }) }    let total = count - mine + given    let full = given >= limit    let said = given == 0 ? "" : (full ? `You gave ${given} claps, the most you can.` : `You gave ${given} ${given == 1 ? "clap" : "claps"}.`)    <span class={ui_cls(["ui-bclap", size != "md" && `ui-bclap-${size}`, given > 0 && "ui-bclap-on", full && "ui-bclap-full", class])}          data-mine={given}>        <button.ui-bclap-btn type="button" aria-label={show_count ? `${label}, ${format_number(total, "en-US")} ${total == 1 ? "clap" : "claps"}` : label}                aria-disabled={full ? "true" : null} title={full ? `You gave the most claps (${limit})` : label}                @click={() => {                    if given < limit {                        given += 1                        burst += 1                        on_change && on_change(given)                    }                }}>            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">                <path d="M10 2.5v2M6.2 3.6l1 1.6M13.8 3.6l-1 1.6"/>                <path.ui-bclap-palm d="M8 13.4 5.6 11a1.4 1.4 0 0 1 2-2l3 3M9.6 10.8 7.4 8.6a1.4 1.4 0 0 1 2-2l3.5 3.5M11.6 8.8l-.7-.7a1.4 1.4 0 0 1 2-2l4.3 4.3a6.2 6.2 0 0 1-8.8 8.8L5 15.8a1.4 1.4 0 0 1 2-2l2.3 2.3"/>                <path d="M17 6.5c.8.4 1.5 1 2.1 1.7a7.6 7.6 0 0 1 1.3 7.5"/>            </svg>        </button>        {burst > 0 && <span.ui-bclap-burst aria-hidden="true">+{burst}</span>}        {show_count && <span.ui-bclap-count aria-hidden="true">{clap_button_count(total)}</span>}        {full && <span.ui-bclap-max aria-hidden="true">Max</span>}        {undo && given > 0 && <button.ui-bclap-undo type="button" aria-label="Take back your claps" title="Take back your claps"                @click={() => {                    given = 0                    burst = 0                    on_change && on_change(0)                }}>            <svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M2.5 2.5l7 7M9.5 2.5l-7 7"/></svg>        </button>}        <span.ui-sr role="status">{said}</span>        {name != null && <input type="hidden" name={name} value={str(given)}/>}    </span>}
Thread
88Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.