Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
Svelte
at icy/tmrrpn
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192<script module lang="ts"> import { tv, type VariantProps } from "tailwind-variants";
// the sizes the design system names, from Figma's User component (node 723:254). // Size=Header is not a rung here: it draws the same 26px circle as Size=Large // and differs only in the text style beside it. export const avatar = tv({ base: "shrink-0 rounded-full border border-border-default", variants: { size: { mini: "size-3.25", small: "size-4.25", regular: "size-5.25", large: "size-6.5", // for a size off the scale: `full` fills the parent, and a size class // passed by the caller replaces it, since tv() merges the two rather // than leaving both to fight in the class string full: "size-full" } }, defaultVariants: { size: "regular" } });
export type AvatarSize = NonNullable<VariantProps<typeof avatar>["size"]>;
// the spacing unit is 4px, so these are the class numbers times four. the avatar is // fetched at twice this so that it stays sharp at 2x. `full` has no size of its own, // the layout decides it, so there is nothing to fetch at. const sizePx = { mini: 13, small: 17, regular: 21, large: 26, full: undefined } satisfies Record<AvatarSize, number | undefined>;
export const avatarSizeNames = Object.keys(sizePx) as AvatarSize[];</script>
<script lang="ts"> import UserRound from "$icon/user-round"; import { avatarUrl } from "$lib/avatar";
interface Props { did?: string; src?: string; handle?: string; size?: AvatarSize; class?: string; }
let { did, src, handle, size = "regular", class: className = "" }: Props = $props();
const px = $derived(sizePx[size]); const source = $derived( src ?? (did ? avatarUrl(did, px === undefined ? undefined : px * 2) : undefined) );
// hidden with a style, not state, a page full of 404s would write state mid-render const hide = (event: Event) => { (event.currentTarget as HTMLImageElement).style.display = "none"; };
// a reused element has to recover from a previous failure const show = (event: Event) => { (event.currentTarget as HTMLImageElement).style.display = ""; };</script>
<span class={avatar({ size, class: `relative flex items-center justify-center overflow-hidden bg-background-canvas text-foreground-subtle ${className}` })} role={handle ? "img" : undefined} aria-label={handle ? `${handle}'s avatar` : undefined} aria-hidden={handle ? undefined : "true"}> <UserRound class="size-1/2" /> {#if source} <img src={source} alt="" class="absolute inset-0 size-full object-cover" onerror={hide} onload={show} /> {/if}</span>