Data

ProfileCard

A person at a glance: picture, name, title, bio, a row of stats and action buttons, stacked or in a row.

#card#profile#user#social#no-js

Demo

Preview

Ada Lovelace

@adaAnalytical engineerLondon

Wrote the first program for a machine that was never built.

248
Posts
12.4k
Followers
180
Following

Grace Hopper

Rear admiral

Compilers, COBOL, and a nanosecond of wire.

61
Talks
19
Papers

Linus Torvalds

@torvaldsMaintainerPortland, OR

Just for fun.

32k
Commits
6
Repos

Get it

It needs theme.qwe, as every component does, and avatar.qwe; 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$ curl -O https://quillolang.org/components/ui/profile_card.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/profile_card.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/profile_card.qwe
view ProfileCardDemo() =    <div.demo-stack>        <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px; align-items: start">            <ProfileCard id="profile-card-demo-ada" name="Ada Lovelace" handle="@ada" title="Analytical engineer" location="London"                         cover_band={true} href="#profile_card" bio="Wrote the first program for a machine that was never built."                         stats={[{ label: "Posts", value: "248" }, { label: "Followers", value: "12.4k" }, { label: "Following", value: "180" }]}>                <Button size="sm">Follow</Button>                <Button size="sm" variant="outline">Message</Button>            </ProfileCard>            <ProfileCard name="Grace Hopper" title="Rear admiral" src="https://i.pravatar.cc/144?img=47"                         cover="https://picsum.photos/seed/sea/600/200" bio="Compilers, COBOL, and a nanosecond of wire."                         stats={[{ label: "Talks", value: "61" }, { label: "Papers", value: "19" }]}>                <Button size="sm" variant="soft">Follow</Button>            </ProfileCard>        </div>        <ProfileCard layout="row" name="Linus Torvalds" handle="@torvalds" title="Maintainer" location="Portland, OR"                     bio="Just for fun." stats={[{ label: "Commits", value: "32k" }, { label: "Repos", value: "6" }]}>            <Button size="sm" variant="outline">Sponsor</Button>        </ProfileCard>    </div>

Source

ui/profile_card.qwe
// ProfileCard: a person at a glance: their picture, name, title, a line about them,// a few numbers, and what to do next (follow, message) in a row of buttons.////   <ProfileCard name="Ada Lovelace" title="Analytical engineer" handle="@ada" src="/img/ada.jpg"//                bio="Writes the first programs." stats={[{ label: "Followers", value: "12.4k" }]}>//       <Button size="sm">Follow</Button>//   </ProfileCard>//// name, title, bio: who they are. src: their picture; initials show without one.// handle: "@ada", under the name. location: where they are, with a pin.// cover: an image across the top (a plain band without one when cover_band is true).// stats: a list of { label, value }, in a row under the bio, each value as it should// read ("12.4k"). href: makes the name a link. children: the actions, in a row at the// bottom. layout: "stacked" (default, centred) or "row" (the picture to the left). id: the card's id.// Needs avatar.qwe (Avatar draws the picture).import ui/avatar.qwe use Avatarimport ui/theme.qwe use ui_clslet profile_card_css = css`.ui-profile-card { position: relative; display: flex; flex-direction: column; min-width: 0; overflow: hidden; border: 1px solid var(--ui-border);  border-radius: var(--ui-radius); background: var(--ui-surface); color: var(--ui-fg); font: 14px/1.5 var(--ui-font); text-align: center; }.ui-profile-card-cover { height: 84px; background: linear-gradient(120deg, var(--ui-accent-soft), color-mix(in srgb, var(--ui-accent) 55%, var(--ui-accent-soft))); }.ui-profile-card-cover img { display: block; width: 100%; height: 100%; object-fit: cover; }.ui-profile-card-body { display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 20px; }.ui-profile-card-cover + .ui-profile-card-body { padding-top: 0; }.ui-profile-card-cover + .ui-profile-card-body .ui-profile-card-pic { margin-top: -36px; }.ui-profile-card-pic { flex: none; border-radius: 50%; box-shadow: 0 0 0 4px var(--ui-surface); }.ui-profile-card-name { margin: 8px 0 0; font-size: 17px; font-weight: 650; line-height: 1.3; }.ui-profile-card-name a { color: inherit; text-decoration: none; }.ui-profile-card-name a:hover { color: var(--ui-accent); text-decoration: underline; }.ui-profile-card-name a:focus-visible { outline: none; border-radius: 4px; box-shadow: var(--ui-focus); }.ui-profile-card-handle { color: var(--ui-muted); font-size: 13px; }.ui-profile-card-title { font-weight: 500; }.ui-profile-card-where { display: inline-flex; align-items: center; gap: 4px; color: var(--ui-muted); font-size: 13px; }.ui-profile-card-where svg { width: 13px; height: 13px; }.ui-profile-card-bio { max-width: 36em; margin: 6px 0 0; color: var(--ui-muted); }.ui-profile-card-stats { display: flex; flex-wrap: wrap; justify-content: center; gap: 0; width: 100%; margin: 14px 0 0; padding: 12px 0 0;  border-top: 1px solid var(--ui-border); }.ui-profile-card-stat { flex: 1 1 0; display: grid; gap: 1px; min-width: 70px; margin: 0; padding: 0 8px; }.ui-profile-card-stat + .ui-profile-card-stat { border-left: 1px solid var(--ui-border); }.ui-profile-card-stat dd { margin: 0; font-size: 17px; font-weight: 650; font-variant-numeric: tabular-nums; }.ui-profile-card-stat dt { color: var(--ui-muted); font-size: 12px; }.ui-profile-card-acts:empty { display: none; }.ui-profile-card-acts { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-top: 16px; }.ui-profile-card-row { text-align: left; }.ui-profile-card-row .ui-profile-card-body { display: grid; grid-template-columns: auto 1fr; column-gap: 16px; align-items: start; }.ui-profile-card-row .ui-profile-card-pic { grid-row: 1 / span 5; margin-top: 0 !important; }.ui-profile-card-row .ui-profile-card-name { margin-top: 0; }.ui-profile-card-row .ui-profile-card-stats, .ui-profile-card-row .ui-profile-card-acts { grid-column: 1 / -1; justify-content: flex-start; }.ui-profile-card-row .ui-profile-card-stat { flex: 0 1 auto; padding: 0 16px 0 0; }.ui-profile-card-row .ui-profile-card-stat + .ui-profile-card-stat { padding-left: 16px; }.ui-profile-card-row .ui-profile-card-cover + .ui-profile-card-body { padding-top: 20px; }`view ProfileCard(name: str, title: str? = null, bio: str? = null, src: str? = null, handle: str? = null,                 location: str? = null, cover: str? = null, cover_band: bool = false, stats: list = [],                 href: str? = null, layout: str = "stacked", id: str? = null, class: str? = null, children) {    style global profile_card_css    <article class={ui_cls(["ui-profile-card", layout == "row" && "ui-profile-card-row", class])} id={id}>        {(cover != null || cover_band) && <div.ui-profile-card-cover aria-hidden="true">            {cover != null && <img src={cover} alt=""/>}        </div>}        <div.ui-profile-card-body>            <Avatar name={name} src={src} size="xl" class="ui-profile-card-pic"/>            <h3.ui-profile-card-name>{href != null ? <a href={href}>{name}</a> : name}</h3>            {handle != null && <span.ui-profile-card-handle>{handle}</span>}            {title != null && <span.ui-profile-card-title>{title}</span>}            {location != null && <span.ui-profile-card-where>                <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="M8 14.5s4.5-4.2 4.5-8a4.5 4.5 0 1 0-9 0c0 3.8 4.5 8 4.5 8Z"/><circle cx="8" cy="6.5" r="1.6"/></svg>                {location}            </span>}            {bio != null && <p.ui-profile-card-bio>{bio}</p>}            {len(stats) > 0 && <dl.ui-profile-card-stats>                {for s in stats => <div.ui-profile-card-stat key={s.label}><dd>{s.value}</dd><dt>{s.label}</dt></div>}            </dl>}            <div.ui-profile-card-acts>{children}</div>        </div>    </article>}
Thread
74Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.