UserBadge
A small chip with a person's avatar and name, plus a role label or a verified tick; a link when given an href.
Demo
Sam PatelReviewerPlato
Assigned to Ana Silva yesterday.
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/user_badge.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/user_badge.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/user_badge.qwe
view UserBadgeDemo() = <div.demo-stack> <div.demo-row> <UserBadge id="user-badge-demo-ana" name="Ana Silva" src="https://i.pravatar.cc/80?img=47" verified={true}/> <UserBadge id="user-badge-demo-ben" name="Ben Okafor" role="Admin" href="#user_badge"/> <UserBadge name="Lee Chen" tone="accent" role="Author"/> <UserBadge name="Mia Rossi" status="online"/> </div> <div.demo-row> <UserBadge id="user-badge-demo-sm" name="Sam Patel" size="sm" role="Reviewer"/> <UserBadge name="Plato" size="sm" tone="accent"/> <p.demo-note style="margin: 0">Assigned to <UserBadge id="user-badge-demo-plain" name="Ana Silva" size="sm" tone="plain"/> yesterday.</p> </div> </div>Source
ui/user_badge.qwe
// UserBadge: a small chip naming a person: their avatar (or initials) and name, with// a role after it or a verified tick, for an author line, an assignee, a mention.//// <UserBadge name="Ana Silva" src="/img/ana.jpg" verified={true}/>// <UserBadge name="Ben Okafor" role="Admin" href="/people/ben"/>// <UserBadge name="Lee Chen" size="sm" tone="accent"/>//// name: whose it is; src: their picture (else their initials, on a colour of the// name's own). role: a small label after the name ("Admin", "Author"). verified: a tick// after the name, told to screen readers. href makes it a link. size: "sm" or "md"// (default). tone: "neutral" (default), "accent" or "plain" (no border, in a line of// text). status: "online", "away", "busy" or "offline", a dot on the avatar.import ui/theme.qwe use ui_clslet user_badge_css = css`.ui-user-badge { display: inline-flex; align-items: center; gap: 7px; max-width: 100%; height: 30px; padding: 0 10px 0 3px; border: 1px solid var(--ui-border); border-radius: 999px; background: var(--ui-surface); color: var(--ui-fg); font: 500 13px/1 var(--ui-font); text-decoration: none; vertical-align: middle; white-space: nowrap; --s: 24px; --h: #6e56cf;}a.ui-user-badge:hover { border-color: var(--ui-strong); background: var(--ui-subtle); }a.ui-user-badge:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-user-badge-accent { border-color: transparent; background: var(--ui-accent-soft); color: var(--ui-accent); }.ui-user-badge-plain { height: auto; padding: 0; border: 0; background: none; }.ui-user-badge-avatar { position: relative; display: inline-grid; place-items: center; flex: none; width: var(--s); height: var(--s); border-radius: 50%; background: color-mix(in srgb, var(--h) 24%, var(--ui-bg)); color: color-mix(in srgb, var(--h) 70%, var(--ui-fg)); font: 600 calc(var(--s) * .4)/1 var(--ui-font); user-select: none;}.ui-user-badge-avatar img { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }.ui-user-badge-c0 { --h: #e5484d; } .ui-user-badge-c1 { --h: #f76b15; } .ui-user-badge-c2 { --h: #ad7f00; }.ui-user-badge-c3 { --h: #30a46c; } .ui-user-badge-c4 { --h: #12a594; } .ui-user-badge-c5 { --h: #0090ff; }.ui-user-badge-c6 { --h: #6e56cf; } .ui-user-badge-c7 { --h: #d6409f; }.ui-user-badge-dot { position: absolute; right: -1px; bottom: -1px; width: 9px; height: 9px; border-radius: 50%; border: 2px solid var(--ui-surface); background: var(--ui-strong); }.ui-user-badge-online { background: var(--ui-success); }.ui-user-badge-away { background: var(--ui-warning); }.ui-user-badge-busy { background: var(--ui-danger); }.ui-user-badge-name { overflow: hidden; text-overflow: ellipsis; }.ui-user-badge-role { padding: 2px 6px; border-radius: 999px; background: var(--ui-subtle); color: var(--ui-muted); font-size: 11px; font-weight: 600; }.ui-user-badge-accent .ui-user-badge-role { background: color-mix(in srgb, var(--ui-accent) 15%, transparent); color: var(--ui-accent); }.ui-user-badge-tick { flex: none; width: 14px; height: 14px; color: var(--ui-info); }.ui-user-badge-sm { height: 24px; padding: 0 8px 0 2px; gap: 5px; font-size: 12px; --s: 20px; }.ui-user-badge-sm .ui-user-badge-role { padding: 1px 5px; font-size: 10px; }.ui-user-badge-sm .ui-user-badge-tick { width: 12px; height: 12px; }.ui-user-badge-plain.ui-user-badge-sm { height: auto; padding: 0; }`// The initials of a name: "Ada Lovelace" is "AL", "Plato" is "PL", "" is "?".fn user_badge_initials(name: str) -> str { let words = split(trim(name), " ") | filter((w) => w != "") let letters = len(words) == 0 ? "?" : (len(words) == 1 ? slice(words[0], 0, 2) : words[0][0] + words[-1][0]) upper(letters)}// One of eight colours, chosen from the name's letters, so a name always gets the same.fn user_badge_hue(name: str) -> int { let chars = "abcdefghijklmnopqrstuvwxyz0123456789" let codes = split(lower(name), "") | map((c) => (index_of(chars, c) ?? 36) + 1) floor(reduce(codes, 5381, (h, c) => (h * 33 + c) % 1000003) / 13) % 8}view UserBadge(name: str, src: str? = null, role: str? = null, verified: bool = false, href: str? = null, size: str = "md", tone: str = "neutral", status: str? = null, id: str? = null, class: str? = null) { style global user_badge_css let cls = ui_cls(["ui-user-badge", size == "sm" && "ui-user-badge-sm", tone != "neutral" && `ui-user-badge-${tone}`, class]) let body = [ <span class={ui_cls(["ui-user-badge-avatar", `ui-user-badge-c${user_badge_hue(name)}`])} aria-hidden="true"> {user_badge_initials(name)} {src != null && <img src={src} alt="" loading="lazy"/>} {status != null && <span class={`ui-user-badge-dot ui-user-badge-${status}`}></span>} </span>, <span.ui-user-badge-name>{name}</span>, verified && <svg.ui-user-badge-tick viewBox="0 0 24 24" fill="currentColor" role="img" aria-label="Verified"><path d="M12 2l2.4 2.1 3.2-.4 1 3 2.9 1.5-.6 3.2L22 14l-2.1 2.4.4 3.2-3 1L15.8 23.5l-3.2-.6L9.6 23.5l-1.5-2.9-3-1 .4-3.2L3.4 14l1.1-2.6-.6-3.2L6.8 6.7l1-3 3.2.4z"/><path d="M8.5 12.5l2.5 2.5 5-5" fill="none" stroke="var(--ui-surface)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>, role != null && <span.ui-user-badge-role>{role}</span>, status != null && <span.ui-sr>({status})</span>, ] href != null ? <a class={cls} id={id} href={href}>{body}</a> : <span class={cls} id={id}>{body}</span>}Related
ProfileHeaderThe top of a profile: cover, avatar, name, handle, bio, facts, counts and a Follow button that becomes Following.6 views · 5 downloadsProfileCardA person at a glance: picture, name, title, bio, a row of stats and action buttons, stacked or in a row.7 views · 4 downloadsCommentOne comment: avatar, name, role, time, text, Like and Reply, with its replies nested inside it.6 views · 9 downloadsPostCardA post in a feed: author, time, text, picture, like, comment and share counts, and the three buttons.5 views · 5 downloadsStoryRingA row of avatars in rings, bright for stories not yet seen and grey once seen, with your own first to add one.6 views · 6 downloadsFollowButtonFollow becomes Following once pressed, and says Unfollow on hover, as the social sites do.5 views · 7 downloads
Thread
Thread 0 posts
On UserBadge: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.