Commerce

Price

An amount written in its currency, with the old price struck through and the discount worked out.

#commerce#price#no-js#typography

Demo

Preview
$19.99$29.99was $39.99, −25% off1.234,50 €was 1.499,00 €, −18% off¥1,250Free
£12.50was £20.00$49.00was $79.00, −38% off$249.00$9.50was $12.00

Numbers are written for their currency and locale; text is shown as it is. The old price is struck through and the discount worked out.

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

Example

The code of the preview above.

demos/price.qwe
view PriceDemo() =    <div.demo-stack>        <div.demo-row style="gap: 28px">            <Price amount={19.99}/>            <Price amount={29.99} old={39.99}/>            <Price amount={1234.5} old={1499} currency="EUR" locale="de-DE"/>            <Price amount={1250} currency="JPY" locale="ja-JP"/>            <Price amount={0}/>        </div>        <div.demo-row style="gap: 28px">            <Price amount="£12.50" old="£20.00" size="sm"/>            <Price amount={49} old={79} size="lg" tone="danger"/>            <Price amount={249} size="xl" tone="accent"/>            <Price amount={9.5} old={12} discount={false}/>        </div>        <p.demo-note>Numbers are written for their currency and locale; text is shown as it is. The old price is struck through and the discount worked out.</p>    </div>

Source

ui/price.qwe
// Price: an amount written in its currency, with the old price struck through and// the discount beside it when there was one. A number is formatted for the// currency and locale (money.format does it: "$19.99", "1.234,50 €", "¥1,250");// text is shown as it is, for amounts formatted elsewhere.////   <Price amount={19.99}/>//   <Price amount={29.99} old={39.99} currency="EUR" locale="de-DE"/>//   <Price amount="£12.50" old="£20.00" size="lg" discount={false}/>//// amount: the price, a number or text. old: the price before, struck through// (screen readers hear "was ..."). discount: a "−25%" beside it, worked out from// the two when both are numbers (true). currency: ISO code ("USD"). locale: how// to write it ("en-US"; the program's locale when it has one). size: "sm", "md"// (default), "lg" or "xl". tone: "fg" (default), "accent" or "danger" (red when// discounted, as shops do). free: what to show for 0 ("Free"; null shows "$0.00").// cents: false drops the ".00" of a whole amount ("$12"; true, so "$12.00").//// price_text(amount, currency, locale, cents) and price_percent(amount, old) are what it// uses, for other components: product_card, cart_item, cart_summary and others in// this kit need this file.import ui/theme.qwe use ui_clslet price_css = css`.ui-price { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: .4em; font: 600 15px/1.2 var(--ui-font); color: var(--ui-fg); font-variant-numeric: tabular-nums; white-space: nowrap; }.ui-price-now { font-size: 1em; }.ui-price-old { font-weight: 400; font-size: .85em; color: var(--ui-muted); text-decoration: line-through; }.ui-price-off { display: inline-flex; align-items: center; height: 1.4em; padding: 0 .45em; border-radius: 999px; font-size: .72em; font-weight: 600; color: var(--ui-danger); background: var(--ui-danger-soft); }.ui-price-sm { font-size: 13px; }.ui-price-lg { font-size: 20px; }.ui-price-xl { font-size: 28px; letter-spacing: -.01em; }.ui-price-accent .ui-price-now { color: var(--ui-accent); }.ui-price-danger .ui-price-now { color: var(--ui-danger); }`// An amount as text in its currency: a number formatted for the locale, text as it// is. cents: false drops the ".00" of a whole amount ("$12", "1.000 €").fn price_text(amount, currency: str = "USD", locale: str? = null, cents: bool = true) -> str {    if type_of(amount) == "str" { return amount }    if amount == null { return "" }    let m = money.of(dec(amount), currency, "half_up")    if is_err(m) { return str(amount) }    let text = locale != null ? money.format(m, locale) : money.format(m)    let units = money.minor_units(currency)    if cents || units == 0 || m.amount != floor(m.amount) { return text }    re.replace(text, `[.,]0{${units}}(\\D*)$`, "$1")}// The discount as a whole percentage ("25" for 29.99 from 39.99), or null when// either is text or the old price is not above the new one.fn price_percent(amount, old) -> int? {    if type_of(amount) == "str" || type_of(old) == "str" || amount == null || old == null { return null }    let a = dec(amount)    let o = dec(old)    if o <= 0 || a >= o { return null }    int(round((o - a) * 100 / o))}view Price(amount, old = null, currency: str = "USD", locale: str? = null, discount: bool = true,           size: str = "md", tone: str = "fg", free: str? = "Free", cents: bool = true, label: str? = null,           class: str? = null) {    style global price_css    let zero = type_of(amount) != "str" && amount != null && dec(amount) == 0 && free != null    let now_text = zero ? free : price_text(amount, currency, locale, cents)    let old_text = old != null ? price_text(old, currency, locale, cents) : null    let pct = discount ? price_percent(amount, old) : null    <span class={ui_cls(["ui-price", size != "md" && `ui-price-${size}`, tone != "fg" && `ui-price-${tone}`, class])}          aria-label={label}>        <span.ui-price-now>{now_text}</span>        {old_text != null && <s.ui-price-old><span.ui-sr>was </span>{old_text}</s>}        {pct != null && <span.ui-price-off><span.ui-sr>, </span>−{pct}%<span.ui-sr> off</span></span>}    </span>}
Thread
65Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.