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.
Demo
No claps from you yet.
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.qwemain.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>}Related
ShareLinksLinks that share a post on X, Bluesky, Mastodon, LinkedIn, Reddit, Hacker News and more, or by email: plain links, nothing loaded from the networks.7 views · 6 downloadsBookmarkButtonA save-for-later toggle whose bookmark fills when pressed, with or without its label.8 views · 8 downloadsFollowButtonFollow becomes Following once pressed, and says Unfollow on hover, as the social sites do.5 views · 7 downloadsLikeButtonA heart that toggles with a count that follows it, a real toggle for screen readers and forms.5 views · 4 downloadsReactionPickerA row of emoji reactions with counts, one tap to react or take it back, and a menu to add another.5 views · 5 downloadsShareButtonShares a link through the system's share sheet, or copies it where the browser has none, and says which.6 views · 5 downloads
Thread
Thread 0 posts
On ClapButton: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.