Poll
Vote for one option with a tap and see the results as bars with each one's share; the vote also goes in a hidden input.
Demo
Which language do you write most?
Pick one to vote
No vote yet.
Tabs or spaces?
40 people answered
Best season for a release? (closed)
0 votes
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/poll.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/poll.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/poll.qwe
let poll_demo_options = [ { label: "Quillo", value: "quillo", votes: 42 }, { label: "Rust", value: "rust", votes: 31 }, { label: "TypeScript", value: "ts", votes: 18 }, { label: "Python", value: "py", votes: 9 },]component PollDemoLive() { state vote = null <div.demo-stack> <Poll id="poll-demo-live" question="Which language do you write most?" options={poll_demo_options} change={true} on_vote={(v) => { vote = v }}/> <p.demo-note>{vote == null ? "No vote yet." : `on_vote got ${vote}.`}</p> </div>}view PollDemo() = <div.demo-stack> <PollDemoLive/> <Poll id="poll-demo-voted" question="Tabs or spaces?" voted="spaces" total_text="{n} people answered" options={[{ label: "Tabs", value: "tabs", votes: 12 }, { label: "Spaces", value: "spaces", votes: 28 }]}/> <Poll id="poll-demo-closed" question="Best season for a release? (closed)" results={true} options={["Spring", "Summer"]}/> </div>Source
ui/poll.qwe
// Poll: a question with options to vote for, one tap each; once someone has// voted the options turn into bars with each one's share. The choice goes in a// hidden input named `name`, so a plain <form> can send it too.//// <Poll id="lang" question="Which language do you write most?" on_vote={(v) => { save_vote(v) }}// options={[{ label: "Quillo", value: "quillo", votes: 42 }, { label: "Rust", value: "rust", votes: 31 }]}/>// <Poll id="lang" question="..." options={opts} voted="rust"/>// <Poll id="lang" question="..." options={opts} results={true}/>//// id: a prefix for its ids, unique on the page. question: the question asked.// options: maps of { label, value, votes? } (votes: the count so far, 0 when// left out), or strings. voted: the value this person chose already, which shows// the results with it marked. results: show the results without a vote (a closed// poll, or one that shows them first). name: the hidden input's name ("vote").// change (false): a vote can be changed afterwards. total_text: the line under// the bars ("{n} votes"; "{n}" is the count).// on_vote(value): inside one of your components, called with the value chosen// when someone votes (and again when they change it, with change={true}).import ui/theme.qwe use ui_clslet poll_css = css`.ui-poll { display: grid; gap: 12px; min-width: 0; max-width: 480px; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-poll-question { margin: 0; font-size: 16px; font-weight: 600; line-height: 1.3; }.ui-poll-options { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }.ui-poll-option { display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%; padding: 10px 14px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); color: var(--ui-fg); font: inherit; text-align: left; cursor: pointer; transition: border-color .15s, background .15s, box-shadow .15s;}.ui-poll-option:hover { border-color: var(--ui-accent); background: var(--ui-accent-soft); }.ui-poll-option:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-poll-result { position: relative; display: grid; gap: 4px; padding: 8px 12px; border-radius: var(--ui-radius-sm); overflow: hidden; background: var(--ui-subtle); }.ui-poll-result::before { content: ""; position: absolute; inset: 0; width: var(--p); background: var(--ui-accent-soft); transition: width .4s; }.ui-poll-mine::before { background: color-mix(in srgb, var(--ui-accent) 28%, var(--ui-accent-soft)); }.ui-poll-mine { box-shadow: inset 0 0 0 1px var(--ui-accent); }.ui-poll-line { position: relative; display: flex; align-items: center; justify-content: space-between; gap: 10px; }.ui-poll-name { display: flex; align-items: center; gap: 8px; font-weight: 500; }.ui-poll-name svg { width: 14px; height: 14px; color: var(--ui-accent); }.ui-poll-pct { font-variant-numeric: tabular-nums; font-weight: 600; }.ui-poll-count { position: relative; font-size: 12px; color: var(--ui-muted); }.ui-poll-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; font-size: 13px; color: var(--ui-muted); }.ui-poll-change { padding: 0; border: 0; background: none; color: var(--ui-accent); font: 500 13px var(--ui-font); cursor: pointer; border-radius: 3px; }.ui-poll-change:hover { text-decoration: underline; }.ui-poll-change:focus-visible { outline: none; box-shadow: var(--ui-focus); }@media (prefers-reduced-motion: reduce) { .ui-poll-option, .ui-poll-result::before { transition: none; } }`fn poll_option(o) = type_of(o) == "map" ? { label: o.label ?? str(o.value), value: str(o.value ?? o.label), votes: o.votes ?? 0 } : { label: str(o), value: str(o), votes: 0 }component Poll(id: str, question: str, options: list = [], voted: str? = null, results: bool = false, name: str = "vote", change: bool = false, total_text: str = "{n} votes", on_vote = null, class: str? = null) { style global poll_css let opts = options | map(poll_option) state mine = voted state changing = false // The counts with this person's vote added, when it was cast here. state added = null let counts = opts | map((o) => o.votes + (o.value == added ? 1 : 0)) let total = sum(counts) ?? 0 let show = (mine != null || results) && !changing let pct = (n: int) => total == 0 ? 0 : round(n * 100 / total) let count_text = replace(total_text, "{n}", str(total)) <div class={ui_cls(["ui-poll", class])} role="group" aria-labelledby={`${id}-q`}> <p.ui-poll-question id={`${id}-q`}>{question}</p> {show ? <ul.ui-poll-options aria-label="Results" aria-live="polite"> {for [i, o] in enumerate(opts) => <li key={o.value} class={ui_cls(["ui-poll-result", o.value == mine && "ui-poll-mine"])} style={`--p: ${pct(counts[i])}%`} aria-current={o.value == mine ? "true" : null}> <div.ui-poll-line> <span.ui-poll-name> {o.value == mine && <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 8.5l3 3 6-7"/></svg>} {o.label}{o.value == mine && <span.ui-sr> (your vote)</span>} </span> <span.ui-poll-pct>{pct(counts[i])}%</span> </div> <span.ui-poll-count>{counts[i]} {counts[i] == 1 ? "vote" : "votes"}</span> </li>} </ul> : <ul.ui-poll-options aria-label="Options"> {for o in opts => <li key={o.value}> <button.ui-poll-option type="button" @click={() => { if added != null && mine != null { added = o.value } else if mine == null { added = o.value } mine = o.value changing = false on_vote && on_vote(o.value) }}> <span>{o.label}</span> <span.ui-sr>Vote for {o.label}</span> </button> </li>} </ul>} <input type="hidden" name={name} value={mine ?? ""}/> <div.ui-poll-foot> <span>{show ? count_text : mine != null ? "Pick another option" : "Pick one to vote"}</span> {show && change && mine != null && <button.ui-poll-change type="button" @click={() => { changing = true }}>Change vote</button>} </div> </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 · 4 downloadsShareButtonShares a link through the system's share sheet, or copies it where the browser has none, and says which.6 views · 5 downloadsVoteButtonsUp and down votes around a score that follows them, as on a forum, in a column or a row.5 views · 6 downloadsEmojiPickerPick an emoji from tabs of categories or by searching their names; each is named for screen readers and a form gets the one picked.4 views · 7 downloads
Thread
Thread 0 posts
On Poll: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.