Rating
Stars to give a score (radio buttons underneath, half stars if wanted) or to show one, read-only.
Demo
Not rated 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/rating.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/rating.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/rating.qwe
component RatingDemoLive() { state given = null <div.demo-stack> <Rating name="rating-demo-live" label="How was your delivery?" labels={["Awful", "Poor", "OK", "Good", "Great"]} on_change={(v) => { given = v }}/> <p.demo-note>{given == null ? "Not rated yet." : `You gave ${rating_num(given)} of 5.`}</p> </div>}view RatingDemo() = <div.demo-stack.demo-narrow> <Rating name="rating-demo-stars" label="Your rating" required={true} hint="Use the arrow keys, or click a star."/> <Rating name="rating-demo-half" label="Overall (half stars)" value={3.5} half={true} size="lg"/> <RatingDemoLive/> <div.demo-row> <Rating value={4.3} readonly={true} count={128}/> <Rating value={2} readonly={true} size="sm"/> </div> <Rating name="rating-demo-locked" label="Locked" value={4} disabled={true} size="sm"/> </div>Source
ui/rating.qwe
// Rating: stars to give a score, or to show one. To give one it is a group of// real radio buttons drawn as stars (CSS does the rest, so it needs no script): a// plain <form> sends the chosen number as `name`, the arrow keys move along the// stars, and hovering shows what a click would give. Read-only, it is a picture// of the score that screen readers hear as "4.5 out of 5 stars".//// <Rating name="stars" label="Your rating" required={true}/>// <Rating name="score" label="How was it?" value={3.5} half={true}// labels={["Awful", "Poor", "OK", "Good", "Great"]}/>// <Rating value={4.3} readonly={true} count={128}/>//// value: the score to start with (or to show). max: how many stars (5). half: also// half stars (0.5, 1.5, ...), for a click on a star's left half. labels: a word for// each star (screen readers hear it, and it shows on hover).// readonly: only shows `value`, any fraction of a star filled; count: a number of// ratings shown after it, "(128)". size: "sm", "md" (default) or "lg".// name ("rating"), label, hint, error, required, disabled and field (the ids'// start, "rating-" + name by default) as for every field.// on_change(value): inside one of your components, given the number chosen.import ui/theme.qwe use ui_clslet rating_css = css`.ui-rating { --ui-rating-size: 24px; min-width: 0; margin: 0; padding: 0; border: 0; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-rating-sm { --ui-rating-size: 16px; }.ui-rating-lg { --ui-rating-size: 32px; }.ui-rating-legend { padding: 0; margin-bottom: 6px; font-size: 13px; font-weight: 500; }.ui-rating-req { margin-left: 2px; color: var(--ui-danger); }.ui-rating-stars { position: relative; display: inline-flex; align-items: center; }.ui-rating-stars input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; pointer-events: none; }.ui-rating-piece { display: block; flex: none; width: var(--ui-rating-size); height: var(--ui-rating-size); overflow: hidden; color: var(--ui-strong); cursor: pointer; border-radius: 3px; transition: color .12s, transform .12s;}.ui-rating-piece svg { display: block; width: var(--ui-rating-size); height: var(--ui-rating-size); padding: 1px; }.ui-rating-gap { margin-right: 4px; }.ui-rating-half .ui-rating-piece { width: calc(var(--ui-rating-size) / 2); }.ui-rating-half .ui-rating-right svg { margin-left: calc(var(--ui-rating-size) / -2); }.ui-rating-stars:not(:has(.ui-rating-piece:hover)) input:checked + .ui-rating-piece,.ui-rating-stars:not(:has(.ui-rating-piece:hover)) .ui-rating-piece:has(~ input:checked),.ui-rating:not(:disabled) .ui-rating-piece:hover,.ui-rating:not(:disabled) .ui-rating-piece:has(~ .ui-rating-piece:hover) { color: var(--ui-warning); }.ui-rating:not(:disabled) .ui-rating-piece:hover { transform: scale(1.08); }.ui-rating-stars input:focus-visible + .ui-rating-piece { box-shadow: var(--ui-focus); }.ui-rating-invalid .ui-rating-piece { color: color-mix(in srgb, var(--ui-danger) 45%, var(--ui-strong)); }.ui-rating-hint, .ui-rating-error { margin: 6px 0 0; font-size: 13px; color: var(--ui-muted); }.ui-rating-error { color: var(--ui-danger); }.ui-rating:disabled { opacity: .55; }.ui-rating:disabled .ui-rating-piece { cursor: not-allowed; }.ui-rating-show { display: inline-flex; align-items: center; gap: 6px; font: 14px/1 var(--ui-font); color: var(--ui-muted); }.ui-rating-show .ui-rating-shown { --ui-rating-size: 18px; display: inline-flex; gap: 2px; }.ui-rating-show.ui-rating-sm .ui-rating-shown { --ui-rating-size: 14px; }.ui-rating-show.ui-rating-lg .ui-rating-shown { --ui-rating-size: 26px; }.ui-rating-one { position: relative; width: var(--ui-rating-size); height: var(--ui-rating-size); color: var(--ui-strong); }.ui-rating-one svg { display: block; width: var(--ui-rating-size); height: var(--ui-rating-size); }.ui-rating-fill { position: absolute; inset: 0 auto 0 0; overflow: hidden; color: var(--ui-warning); }.ui-rating-num { color: var(--ui-fg); font-weight: 600; font-variant-numeric: tabular-nums; }@media (prefers-reduced-motion: reduce) { .ui-rating-piece { transition: none; } }`let rating_star = <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> <path d="M12 2.6l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.4l-5.8 3.1 1.1-6.5L2.6 9.4l6.5-.9z"/></svg>// "4", "4.5": a score as it is said and sent.fn rating_num(n: num) -> str = n == floor(n) ? str(floor(n)) : str(n)// How much of star i (from 0) a score of v fills, from 0 to 1.fn rating_fill(v: num, i: int) -> num = v - i < 0 ? 0 : v - i > 1 ? 1 : v - i// The scores that can be chosen: 1..max, or 0.5..max in halves.fn rating_steps(max: int, half: bool) -> list = half ? range(1, max * 2 + 1) | map((i) => i / 2) : range(1, max + 1)view Rating(name: str = "rating", label: str? = null, value: num? = null, max: int = 5, half: bool = false, labels: list = [], readonly: bool = false, count: int? = null, size: str = "md", hint: str? = null, error: str? = null, required: bool = false, disabled: bool = false, field: str? = null, on_change = null, class: str? = null) { style global rating_css let id = field ?? `rating-${name}` let size_cls = size != "md" && `ui-rating-${size}` let word = (n: num) => { let w = labels[ceil(n) - 1] let stars = `${rating_num(n)} ${n == 1 ? "star" : "stars"}` w != null && n == ceil(n) ? `${w}, ${stars}` : stars } if readonly { let v = value ?? 0 return <span class={ui_cls(["ui-rating-show", size_cls, class])}> <span.ui-rating-shown role="img" aria-label={`${rating_num(v)} out of ${max} stars${count != null ? `, ${count} ratings` : ""}`}> {for i in range(max) => <span.ui-rating-one> {rating_star} <span.ui-rating-fill style={`width: ${round(rating_fill(v, i) * 100, 1)}%`}>{rating_star}</span> </span>} </span> <span aria-hidden="true"><span.ui-rating-num>{rating_num(v)}</span>{count != null && ` (${count})`}</span> </span> } let described = ui_cls([hint != null && `${id}-hint`, error != null && `${id}-error`]) <fieldset class={ui_cls(["ui-rating", size_cls, half && "ui-rating-half", error != null && "ui-rating-invalid", class])} id={id} disabled={disabled} aria-describedby={described != "" ? described : null}> {label != null && <legend.ui-rating-legend> {label}{required && <span.ui-rating-req aria-hidden="true">*</span>} </legend>} <div.ui-rating-stars> {for n in rating_steps(max, half) => [ <input type="radio" id={`${id}-${replace(rating_num(n), ".", "-")}`} name={name} value={rating_num(n)} checked={value != null && n == value} required={required} aria-label={word(n)} aria-invalid={error != null ? "true" : null} @change={() => { on_change && on_change(n) }}/>, <label for={`${id}-${replace(rating_num(n), ".", "-")}`} title={word(n)} class={ui_cls(["ui-rating-piece", half && n != ceil(n) && "ui-rating-left", half && n == ceil(n) && "ui-rating-right", n == ceil(n) && n < max && "ui-rating-gap"])}> {rating_star} </label>, ]} </div> {hint != null && <p.ui-rating-hint id={`${id}-hint`}>{hint}</p>} {error != null && <p.ui-rating-error id={`${id}-error`} role="alert">{error}</p>} </fieldset>}Related
AddressFormStreet, city, region, postcode and country in the usual layout, with the autocomplete names browsers fill from.5 views · 7 downloadsAutocompleteA free-text box that offers matches as one types, with the arrow keys and Enter; whatever is typed may stay.5 views · 7 downloadsCardInputA payment card as it is typed: the number grouped, the brand, MM/YY and the code, with typos said.6 views · 6 downloadsCharacterCounterA text box or textarea with a live 120 / 280 count that warns near the limit, as a hard stop or a soft one that blocks sending.5 views · 6 downloadsCheckboxA checkbox drawn by CSS on a real input, with an indeterminate state, and a group of them in a fieldset.5 views · 6 downloadsCheckboxCardsAny number of choices as cards with a title, a line of detail and a check, each a real checkbox a form sends.5 views · 6 downloads
Thread
Thread 0 posts
On Rating: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.