VoteButtons
Up and down votes around a score that follows them, as on a forum, in a column or a row.
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/vote_buttons.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/vote_buttons.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/vote_buttons.qwe
component VoteButtonsDemoLive() { state v = 0 <div.demo-row> <VoteButtons score={41} on_change={(x) => { v = x }}/> <span.demo-note>{v == 1 ? "Voted up." : v == -1 ? "Voted down." : "No vote."}</span> </div>}view VoteButtonsDemo() = <div.demo-stack> <VoteButtonsDemoLive/> <div.demo-row> <VoteButtons score={128} vote={1} name="vote" layout="horizontal"/> <VoteButtons score={-3} vote={-1} layout="horizontal" size="sm"/> <VoteButtons score={7} size="lg"/> <VoteButtons score={2} disabled={true}/> </div> </div>Source
ui/vote_buttons.qwe
// VoteButtons: an up and a down arrow around a score, as on a forum: one vote each// way, pressed again to take it back, the score following it. In a form it sends// `name` as 1, -1 or 0.//// <VoteButtons score={41}/>// <VoteButtons score={41} vote={1} name="vote" layout="horizontal" on_change={(v) => { vote(post, v) }}/>//// score: the score before your vote. vote: yours to start with, 1, -1 or 0 (none).// layout: "vertical" (default: up, score, down in a column) or "horizontal".// name: in a <form>, sent as name=1, -1 or 0. label ("Votes"): the group's name for// screen readers. size: "sm", "md" (default) or "lg".// on_change(vote): inside one of your components, given 1, -1 or 0.import ui/theme.qwe use ui_clslet vote_buttons_css = css`.ui-vote-buttons { display: inline-flex; flex-direction: column; align-items: center; gap: 2px; vertical-align: middle; font: 600 14px/1 var(--ui-font); }.ui-vote-buttons-horizontal { flex-direction: row; gap: 4px; padding: 2px; border: 1px solid var(--ui-border); border-radius: 999px; }.ui-vote-buttons-btn { display: inline-grid; place-items: center; width: 30px; height: 30px; padding: 0; border: 0; border-radius: 999px; background: transparent; color: var(--ui-muted); cursor: pointer; transition: background .15s, color .15s;}.ui-vote-buttons-btn svg { width: 18px; height: 18px; }.ui-vote-buttons-btn:hover { background: var(--ui-subtle); color: var(--ui-fg); }.ui-vote-buttons-btn:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-vote-buttons-btn:disabled { opacity: .55; cursor: not-allowed; }.ui-vote-buttons-up[aria-pressed="true"] { color: var(--ui-accent); background: var(--ui-accent-soft); }.ui-vote-buttons-down[aria-pressed="true"] { color: var(--ui-danger); background: var(--ui-danger-soft); }.ui-vote-buttons-score { min-width: 2ch; text-align: center; font-variant-numeric: tabular-nums; color: var(--ui-fg); }.ui-vote-buttons-plus .ui-vote-buttons-score { color: var(--ui-accent); }.ui-vote-buttons-minus .ui-vote-buttons-score { color: var(--ui-danger); }.ui-vote-buttons-sm { font-size: 13px; }.ui-vote-buttons-sm .ui-vote-buttons-btn { width: 26px; height: 26px; }.ui-vote-buttons-sm .ui-vote-buttons-btn svg { width: 16px; height: 16px; }.ui-vote-buttons-lg { font-size: 16px; }.ui-vote-buttons-lg .ui-vote-buttons-btn { width: 36px; height: 36px; }.ui-vote-buttons-lg .ui-vote-buttons-btn svg { width: 22px; height: 22px; }@media (prefers-reduced-motion: reduce) { .ui-vote-buttons-btn { transition: none; } }`component VoteButtons(score: int = 0, vote: int = 0, layout: str = "vertical", name: str? = null, label: str = "Votes", size: str = "md", disabled: bool = false, on_change = null, class: str? = null) { style global vote_buttons_css state mine = vote let shown = score - vote + mine let cast = (v: int) => { mine = mine == v ? 0 : v on_change && on_change(mine) } <div class={ui_cls(["ui-vote-buttons", layout == "horizontal" && "ui-vote-buttons-horizontal", size != "md" && `ui-vote-buttons-${size}`, mine == 1 && "ui-vote-buttons-plus", mine == -1 && "ui-vote-buttons-minus", class])} role="group" aria-label={`${label}: ${shown}`}> <button.ui-vote-buttons-btn.ui-vote-buttons-up type="button" aria-label="Vote up" title="Vote up" aria-pressed={mine == 1 ? "true" : "false"} disabled={disabled} @click={() => { cast(1) }}> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 19V5M5 12l7-7 7 7"/></svg> </button> <span.ui-vote-buttons-score aria-hidden="true">{shown}</span> <button.ui-vote-buttons-btn.ui-vote-buttons-down type="button" aria-label="Vote down" title="Vote down" aria-pressed={mine == -1 ? "true" : "false"} disabled={disabled} @click={() => { cast(-1) }}> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12l7 7 7-7"/></svg> </button> {name != null && <input type="hidden" name={name} value={str(mine)}/>} </div>}Related
BookmarkButtonA 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 · 5 downloadsShareButtonShares a link through the system's share sheet, or copies it where the browser has none, and says which.6 views · 6 downloadsButtonA button, or a link that looks like one: four variants, three tones, three sizes, loading.7 views · 8 downloadsButtonGroupButtons joined edge to edge into one control, or spaced in a row; across or upright.6 views · 7 downloads
Thread
Thread 0 posts
On VoteButtons: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.