Data

Avatar

A picture, or initials on a colour of the person's own, with a status dot; and overlapping groups with +N.

#user#image#profile#no-js

Demo

Preview
+3

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

Example

The code of the preview above.

demos/avatar.qwe
let avatar_demo_team = [    { name: "Ada Lovelace", status: "online" },    { name: "Grace Hopper", src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40'%3E%3Crect width='40' height='40' fill='%23c9b8f5'/%3E%3Ccircle cx='20' cy='16' r='7' fill='%236e56cf'/%3E%3Cpath d='M6 40c2-9 8-13 14-13s12 4 14 13z' fill='%236e56cf'/%3E%3C/svg%3E" },    { name: "Alan Turing" },    { name: "Katherine Johnson" },    { name: "Linus Torvalds" },    { name: "Margaret Hamilton" },    { name: "Dennis Ritchie" },]view AvatarDemo() =    <div.demo-stack>        <div.demo-row>            <Avatar name="Ada Lovelace" size="xs"/>            <Avatar name="Grace Hopper" size="sm"/>            <Avatar name="Alan Turing"/>            <Avatar name="Katherine Johnson" size="lg"/>            <Avatar name="Plato" size="xl"/>            <Avatar name="Margaret Hamilton" shape="square" size="lg"/>        </div>        <div.demo-row>            <Avatar name="Ada Lovelace" status="online"/>            <Avatar name="Linus Torvalds" status="away"/>            <Avatar name="Dennis Ritchie" status="busy"/>            <Avatar name="Barbara Liskov" status="offline"/>            <Avatar name="Grace Hopper" src={avatar_demo_team[1].src} size="lg" status="online"/>            <Avatar name="Broken Picture" src="data:image/png;base64,bm90IGEgcGljdHVyZQ==" size="lg"/>        </div>        <div.demo-row>            <AvatarGroup people={avatar_demo_team} max={4} label="Project members"/>            <AvatarGroup people={take(avatar_demo_team, 3)} size="sm"/>        </div>    </div>

Source

ui/avatar.qwe
// Avatar: a person's picture, or their initials on a colour of their own.////   <Avatar name="Ada Lovelace" src="/img/ada.jpg" status="online"/>//   <Avatar name="Grace Hopper" size="lg"/>                    shows "GH"//   <AvatarGroup people={team} max={4}/>                       four, then "+3"//// name: whose it is; it gives the initials (the first and last words' first// letters, or a single word's first two) and the colour, the same one every time// for a name. src: a picture; the initials show while it loads, and if it fails.// size: "xs" (24px), "sm" (32px), "md" (40px, default), "lg" (56px) or "xl"// (72px). status: "online", "away", "busy" or "offline", a dot on its corner,// also said to screen readers. shape: "circle" (default) or "square".// AvatarGroup: people is a list of { name, src? }, overlapped; past max// the rest are counted in a "+N". label names the group for screen readers.import ui/theme.qwe use ui_clslet avatar_css = css`.ui-avatar {  position: relative; display: inline-grid; place-items: center; flex: none; vertical-align: middle;  width: var(--s); height: var(--s); border-radius: 50%; --s: 40px; --h: #6e56cf;  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) * .38)/1 var(--ui-font); letter-spacing: .02em; user-select: none;}.ui-avatar-square { border-radius: calc(var(--s) * .22); }.ui-avatar img { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: inherit;  object-fit: cover; color: transparent; font-size: 0; }.ui-avatar img::after { content: attr(data-initials); position: absolute; inset: 0; display: grid; place-items: center;  border-radius: inherit; 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) * .38)/1 var(--ui-font); }.ui-avatar-xs { --s: 24px; }.ui-avatar-sm { --s: 32px; }.ui-avatar-lg { --s: 56px; }.ui-avatar-xl { --s: 72px; }.ui-avatar-c0 { --h: #e5484d; } .ui-avatar-c1 { --h: #f76b15; } .ui-avatar-c2 { --h: #ad7f00; }.ui-avatar-c3 { --h: #30a46c; } .ui-avatar-c4 { --h: #12a594; } .ui-avatar-c5 { --h: #0090ff; }.ui-avatar-c6 { --h: #6e56cf; } .ui-avatar-c7 { --h: #d6409f; }.ui-avatar-status { position: absolute; right: 0; bottom: 0; width: max(8px, calc(var(--s) * .27));  height: max(8px, calc(var(--s) * .27)); border-radius: 50%; border: 2px solid var(--ui-bg); background: var(--ui-strong); }.ui-avatar-online { background: var(--ui-success); }.ui-avatar-away { background: var(--ui-warning); }.ui-avatar-busy { background: var(--ui-danger); }.ui-avatar-group { display: inline-flex; align-items: center; }.ui-avatar-group > .ui-avatar { box-shadow: 0 0 0 2px var(--ui-bg); }.ui-avatar-group > .ui-avatar + .ui-avatar { margin-left: calc(var(--s) * -.28); }.ui-avatar-more { background: var(--ui-subtle); color: var(--ui-muted); font-size: calc(var(--s) * .32); }`// The initials of a name: "Ada Lovelace" is "AL", "Plato" is "PL", "" is "?".fn avatar_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 the eight colours, chosen from the name's letters, so a name always gets the same.fn avatar_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 Avatar(name: str = "", src: str? = null, size: str = "md", status: str? = null, shape: str = "circle",            class: str? = null) {    style global avatar_css    <span class={ui_cls(["ui-avatar", `ui-avatar-c${avatar_hue(name)}`, size != "md" && `ui-avatar-${size}`,                         shape == "square" && "ui-avatar-square", class])}          role="img" aria-label={status != null ? `${name} (${status})` : name}>        <span aria-hidden="true">{avatar_initials(name)}</span>        {src != null && <img src={src} alt="" loading="lazy" data-initials={avatar_initials(name)}/>}        {status != null && <span class={`ui-avatar-status ui-avatar-${status}`}></span>}    </span>}view AvatarGroup(people: list, max: int = 4, size: str = "md", label: str? = null, class: str? = null) {    style global avatar_css    let shown = len(people) > max ? take(people, max) : people    let rest = len(people) - len(shown)    <span class={ui_cls(["ui-avatar-group", class])} role="group" aria-label={label}>        {for p in shown => <Avatar name={p.name ?? ""} src={p.src} size={size}/>}        {rest > 0 && <span class={ui_cls(["ui-avatar", "ui-avatar-more", size != "md" && `ui-avatar-${size}`])}                           role="img" aria-label={`and ${rest} more`}>+{rest}</span>}    </span>}
Thread
65Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.