Finance

AccountCard

A bank account at a glance: type, nickname, masked number, current and available balance, what is on hold, and quick actions.

#finance#banking#card#dashboard#no-js

Demo

Preview

Checking

Everyday

Checking ending in 4821

USD
Current balance
$2,450.18
Available
$2,310.18
On hold
$140.00

Updated 2 min ago

Savings

Rainy day

Savings ending in 4931

EUR
Current balance
12.500,00 €

Joint checking

House

Joint checking ending in 1266

GBP
Current balance
-£84.50 (overdrawn)
Available
£415.50

Overdraft limit £500.00

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

Example

The code of the preview above.

demos/account_card.qwe
view AccountCardDemo() =    <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 12px">        <AccountCard id="account-card-demo-everyday" kind="Checking" name="Everyday" number="0012 0048 21" balance={2450.18m}                     available={2310.18m} href="#everyday" transfer_href="#transfer" pay_href="#pay" statements_href="#statements"                     note="Updated 2 min ago"/>        <AccountCard id="account-card-demo-savings" kind="Savings" name="Rainy day" number="DE44 5001 0517 5407 3249 31"                     balance={12500} currency="EUR" locale="de-DE" tone="success" transfer_href="#transfer" statements_href="#statements"/>        <AccountCard id="account-card-demo-joint" kind="Joint checking" name="House" number="7730091266" balance={-84.5m}                     available={415.5m} currency="GBP" locale="en-GB" tone="warning" pay_href="#pay"                     note="Overdraft limit £500.00"/>    </div>

Source

ui/account_card.qwe
// AccountCard: a bank account at a glance: its type and nickname, the number// masked to its last four, the current balance and what is available to spend// (the difference shown as on hold), the currency, and quick links to transfer,// pay and see statements.////   <AccountCard kind="Checking" name="Everyday" number="0012004821" balance={2450.18m} available={2310.18m}//                currency="USD" href="/accounts/1" transfer_href="/transfer?from=1" pay_href="/pay?from=1"//                statements_href="/accounts/1/statements"/>//   <AccountCard kind="Savings" name="Rainy day" number="DE44 5001 0517 5407 3249 31" balance={12500} currency="EUR" locale="de-DE"/>//// kind: the account's type ("Checking", "Savings", "Credit card"). name: its// nickname. number: the account number, any spacing; only the last four show// ("•••• 4821", read as "ending in 4821"). balance: the current balance, a number// or decimal (below 0, it shows in red and is read as overdrawn). available: what// can be spent now; when it is below the balance, the difference shows as on// hold (left out, the balance is all available). currency: ISO code ("USD");// locale: how amounts are written (the program's, else en-US). href: the account's// page, on the name. transfer_href, pay_href, statements_href: the quick actions,// each shown when given. note: a line at the foot ("Updated 2 min ago"). tone:// the colour of the stripe at the top, "accent" (default), "success", "info",// "warning", "danger" or "neutral". id: the element's id.import ui/theme.qwe use ui_clslet account_card_css = css`.ui-acard { --t: var(--ui-accent); position: relative; display: grid; gap: 14px; min-width: 0; padding: 18px 18px 14px; overflow: hidden;  border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); color: var(--ui-fg); font: 14px/1.4 var(--ui-font); }.ui-acard::before { content: ""; position: absolute; inset: 0 0 auto; height: 3px; background: var(--t); }.ui-acard-success { --t: var(--ui-success); }.ui-acard-info { --t: var(--ui-info); }.ui-acard-warning { --t: var(--ui-warning); }.ui-acard-danger { --t: var(--ui-danger); }.ui-acard-neutral { --t: var(--ui-strong); }.ui-acard-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }.ui-acard-kind { margin: 0; color: var(--ui-muted); font-size: 12px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; }.ui-acard-name { margin: 2px 0 0; font-size: 16px; font-weight: 600; line-height: 1.3; }.ui-acard-name a { color: inherit; text-decoration: none; }.ui-acard-name a:hover { color: var(--ui-accent); }.ui-acard-name a:focus-visible { outline: none; box-shadow: var(--ui-focus); border-radius: 3px; }.ui-acard-number { margin: 2px 0 0; color: var(--ui-muted); font: 13px var(--ui-mono); letter-spacing: .04em; }.ui-acard-currency { flex: none; padding: 2px 8px; border-radius: 999px; background: var(--ui-subtle); color: var(--ui-muted);  font-size: 11px; font-weight: 600; letter-spacing: .05em; }.ui-acard-money { display: flex; flex-wrap: wrap; align-content: flex-start; gap: 10px 22px; margin: 0; }.ui-acard-money > div { display: grid; gap: 1px; }.ui-acard-money > div:first-child { flex-basis: 100%; }.ui-acard-money dt { color: var(--ui-muted); font-size: 12px; }.ui-acard-money dd { margin: 0; font-variant-numeric: tabular-nums; }.ui-acard-balance { font-size: 28px; font-weight: 650; line-height: 1.15; letter-spacing: -.01em; }.ui-acard-negative .ui-acard-balance { color: var(--ui-danger); }.ui-acard-avail dd { font-weight: 600; }.ui-acard-hold dd { color: var(--ui-warning); font-weight: 500; }.ui-acard-actions { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 12px 0 0; border-top: 1px solid var(--ui-border); list-style: none; }.ui-acard-actions a { display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 11px; border-radius: var(--ui-radius-sm);  background: var(--ui-subtle); color: var(--ui-fg); font-size: 13px; font-weight: 500; text-decoration: none; transition: background .15s, color .15s; }.ui-acard-actions a:hover { background: var(--ui-accent-soft); color: var(--ui-accent); }.ui-acard-actions a:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-acard-actions svg { width: 14px; height: 14px; flex: none; }.ui-acard-note { margin: 0; color: var(--ui-muted); font-size: 12px; }@media (prefers-reduced-motion: reduce) { .ui-acard-actions a { transition: none; } }`// An amount as text in its currency: a number or decimal formatted, text as it is.fn account_card_money(amount, currency: str, locale: str?) -> 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) }    locale != null ? money.format(m, locale) : money.format(m)}// The last four letters or digits of an account number: "0012 0048 21" gives "4821".fn account_card_last4(number: str) -> str = slice(re.replace(number, "[^0-9A-Za-z]", ""), -4)view AccountCard(name: str, kind: str = "Account", number: str = "", balance = 0, available = null, currency: str = "USD",                 locale: str? = null, href: str? = null, transfer_href: str? = null, pay_href: str? = null,                 statements_href: str? = null, note: str? = null, tone: str = "accent", id: str? = null, class: str? = null) {    style global account_card_css    let code = upper(currency)    let bal = dec(balance)    let avail = available != null ? dec(available) : null    let hold = avail != null && avail < bal ? bal - avail : null    let last4 = account_card_last4(number)    let head_id = `${id ?? "account-card-" + re.replace(lower(name), "[^a-z0-9]+", "-")}-name`    let has_actions = transfer_href != null || pay_href != null || statements_href != null    <article class={ui_cls(["ui-acard", tone != "accent" && `ui-acard-${tone}`, bal < 0 && "ui-acard-negative", class])} id={id}             aria-labelledby={head_id}>        <header.ui-acard-head>            <div>                <p.ui-acard-kind>{kind}</p>                <h3.ui-acard-name id={head_id}>{href != null ? <a href={href}>{name}</a> : name}</h3>                {last4 != "" && <p.ui-acard-number><span aria-hidden="true">•••• {last4}</span><span.ui-sr>{kind} ending in {last4}</span></p>}            </div>            <span.ui-acard-currency title={`Held in ${code}`}>{code}</span>        </header>        <dl.ui-acard-money>            <div><dt>Current balance</dt>            <dd.ui-acard-balance>{account_card_money(bal, code, locale)}{bal < 0 && <span.ui-sr> (overdrawn)</span>}</dd></div>            {avail != null && <div.ui-acard-avail><dt>Available</dt><dd>{account_card_money(avail, code, locale)}</dd></div>}            {hold != null && <div.ui-acard-hold><dt>On hold</dt><dd>{account_card_money(hold, code, locale)}</dd></div>}        </dl>        {has_actions && <ul.ui-acard-actions aria-label={`${name}: actions`}>            {transfer_href != null && <li><a href={transfer_href}>                <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 5h11l-3-3M14 11H3l3 3"/></svg>Transfer</a></li>}            {pay_href != null && <li><a href={pay_href}>                <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="1.5" y="3.5" width="13" height="9" rx="1.5"/><path d="M1.5 6.5h13M4 10h3"/></svg>Pay</a></li>}            {statements_href != null && <li><a href={statements_href}>                <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 1.5h5.5L12.5 4.5v10h-8.5z"/><path d="M6 8h4.5M6 11h4.5"/></svg>Statements</a></li>}        </ul>}        {note != null && <p.ui-acard-note>{note}</p>}    </article>}
Thread
87Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.