{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"avatar","type":"registry:ui","title":"Avatar","description":"A compact identity marker for people and accounts.","categories":["data"],"dependencies":["motion"],"registryDependencies":["https://uiarc.dev/r/arc-motion-tokens.json"],"files":[{"path":"registry/components/avatar/avatar.tsx","type":"registry:component","target":"@components/arc/avatar/avatar.tsx","content":"\"use client\";\n\nimport { useLayoutEffect, useRef, useState, type HTMLAttributes } from \"react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { ArcImage } from \"../lib/arc-provider\";\nimport { motionTokens } from \"../lib/motion-tokens\";\nimport styles from \"./avatar.module.css\";\nexport interface AvatarProps extends HTMLAttributes<HTMLSpanElement> { name: string; src?: string; size?: \"sm\" | \"md\" | \"lg\" | \"xl\"; status?: \"online\" | \"offline\" }\nexport function Avatar({ name, src, size = \"md\", status, className, ...props }: AvatarProps) {\n  const initials = name.trim().split(/\\s+/).slice(0, 2).map(part => part[0]?.toUpperCase()).join(\"\");\n  const reduceMotion = !!useReducedMotion();\n  const image = useRef<HTMLImageElement>(null);\n  const [failedSrc, setFailedSrc] = useState<string>();\n  // A photo that is already decoded shows at once. One that is still loading waits, then fades in from a soft blur.\n  useLayoutEffect(() => { const node = image.current; if (node && !node.complete) node.dataset.loading = \"\"; }, [src]);\n  const showImage = src && failedSrc !== src;\n  return <span {...props} className={[styles.avatar, styles[size], className].filter(Boolean).join(\" \")} role=\"img\" aria-label={`${name}${status ? `, ${status}` : \"\"}`}>\n    {showImage ? <ArcImage key={src} ref={image} src={src} alt=\"\" fill sizes={size === \"xl\" ? \"88px\" : size === \"lg\" ? \"48px\" : size === \"md\" ? \"36px\" : \"28px\"} onLoad={event => { delete event.currentTarget.dataset.loading; }} onError={() => setFailedSrc(src)} /> : <span className={src ? styles.fallback : undefined} aria-hidden=\"true\">{initials}</span>}\n    <AnimatePresence initial={false}>{status && <motion.i key={status} className={[styles.status, styles[status]].join(\" \")} aria-hidden=\"true\" initial={{ opacity: 0, scale: .6 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: .6, transition: { duration: reduceMotion ? 0 : motionTokens.duration.fast } }} transition={reduceMotion ? { duration: 0 } : motionTokens.spring.snappy} />}</AnimatePresence>\n  </span>;\n}\n"},{"path":"registry/components/avatar/avatar.module.css","type":"registry:component","target":"@components/arc/avatar/avatar.module.css","content":".avatar {\n  position: relative;\n  display: inline-grid;\n  flex: 0 0 auto;\n  place-items: center;\n  overflow: visible;\n  border: 1px solid var(--border);\n  border-radius: var(--radius-pill);\n  background: var(--surface-muted);\n  color: var(--foreground);\n  font-size: var(--text-xs);\n  font-weight: 500;\n}\n\n.avatar img {\n  pointer-events: none;\n  border-radius: inherit;\n  object-fit: cover;\n  transition: opacity var(--duration-standard) var(--ease-enter), filter var(--duration-standard) var(--ease-enter);\n}\n\n/* Set only while a photo is still downloading after mount, so cached photos never fade. */\n.avatar img[data-loading] {\n  opacity: 0;\n  filter: blur(4px);\n  transition: none;\n}\n\n.fallback { animation: avatar-fallback-in var(--duration-standard) var(--ease-enter) both; }\n\n.sm { width: 28px; height: 28px; font-size: 10px; }\n.md { width: 36px; height: 36px; }\n.lg { width: 48px; height: 48px; font-size: var(--text-sm); }\n.xl { width: 88px; height: 88px; font-size: var(--text-xl); }\n\n.status {\n  position: absolute;\n  right: 0;\n  bottom: 0;\n  z-index: 2;\n  width: 10px;\n  height: 10px;\n  border: 2px solid var(--surface);\n  border-radius: var(--radius-pill);\n  box-shadow: 0 0 0 1px color-mix(in oklab, var(--border) 70%, transparent);\n}\n\n.sm .status { width: 8px; height: 8px; border-width: 1.5px; }\n.lg .status { width: 12px; height: 12px; }\n.xl .status { width: 14px; height: 14px; right: 4px; bottom: 4px; }\n.online { background: var(--success); }\n.offline { background: var(--text-muted); }\n\n@keyframes avatar-fallback-in { from { opacity: 0; } to { opacity: 1; } }\n\n@media (prefers-reduced-motion: reduce) {\n  .avatar img { transition: none; }\n  .avatar img[data-loading] { filter: none; }\n  .fallback { animation: none; }\n}\n"},{"path":"lib/arc-provider.tsx","type":"registry:lib","target":"@components/arc/lib/arc-provider.tsx","content":"\"use client\";\n\n/**\n * Lets an app swap the link and image elements Arc components render (kuratlielia/arc-library#13).\n *\n * Without a provider, Arc renders a plain `<a>` and `<img>`, so components work in any React app (Vite, Remix, Astro,\n * plain React). Wrap the app once to use your framework's or your own components instead:\n *\n *   import Link from \"next/link\";\n *   import Image from \"next/image\";\n *   import { ArcProvider } from \"@/components/arc/lib/arc-provider\";\n *\n *   <ArcProvider link={Link} image={Image}>{children}</ArcProvider>\n *\n * Any component that accepts these props works, such as a locale-aware Link from next-intl or a React Router Link\n * wrapped to map `href` to `to`.\n */\nimport { createContext, createElement, useContext, useMemo, type AnchorHTMLAttributes, type ComponentType, type ImgHTMLAttributes, type ReactNode, type Ref } from \"react\";\n\n/** The props Arc passes to a link: an href plus ordinary anchor attributes. */\nexport interface ArcLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {\n  href: string;\n  ref?: Ref<HTMLAnchorElement>;\n}\n\n/**\n * The props Arc passes to an image. A subset of next/image's props, so next/image can be passed as is: either `fill`\n * (cover the positioned parent) or `width` and `height`, plus `sizes` and `priority` as hints.\n */\nexport interface ArcImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, \"src\" | \"alt\" | \"width\" | \"height\" | \"loading\" | \"srcSet\"> {\n  src: string;\n  alt: string;\n  width?: number;\n  height?: number;\n  /** Fills the nearest positioned parent, like next/image's `fill`. */\n  fill?: boolean;\n  /** Loads the image eagerly with high priority, like next/image's `priority`. */\n  priority?: boolean;\n  loading?: \"eager\" | \"lazy\";\n  ref?: Ref<HTMLImageElement>;\n}\n\nexport type ArcLinkComponent = ComponentType<ArcLinkProps>;\nexport type ArcImageComponent = ComponentType<ArcImageProps>;\n\n/** The default link: a plain anchor. */\nexport function ArcAnchor(props: ArcLinkProps) {\n  return <a {...props} />;\n}\n\nconst fillStyle = { position: \"absolute\", inset: 0, width: \"100%\", height: \"100%\" } as const;\n\n/** The default image: a plain, lazily decoded `<img>` that understands `fill` and `priority`. */\nexport function ArcImg({ alt, fill, priority, loading, style, ...props }: ArcImageProps) {\n  // eslint-disable-next-line @next/next/no-img-element -- the framework-free default; pass next/image through ArcProvider instead.\n  return <img alt={alt} decoding=\"async\" {...props} loading={loading ?? (priority ? \"eager\" : \"lazy\")} fetchPriority={priority ? \"high\" : props.fetchPriority} style={fill ? { ...fillStyle, ...style } : style} />;\n}\n\ninterface ArcComponents {\n  link: ArcLinkComponent;\n  image: ArcImageComponent;\n}\n\nconst ArcContext = createContext<ArcComponents>({ link: ArcAnchor, image: ArcImg });\n\nexport interface ArcProviderProps {\n  /** The link component Arc renders for navigation, such as next/link or your own wrapped Link. Defaults to `<a>`. */\n  link?: ArcLinkComponent;\n  /** The image component Arc renders for photos, such as next/image. Defaults to `<img>`. */\n  image?: ArcImageComponent;\n  children: ReactNode;\n}\n\n/** Sets the link and image components for every Arc component inside it. Nested providers inherit what they do not set. */\nexport function ArcProvider({ link, image, children }: ArcProviderProps) {\n  const parent = useContext(ArcContext);\n  const value = useMemo(() => ({ link: link ?? parent.link, image: image ?? parent.image }), [link, image, parent]);\n  return <ArcContext.Provider value={value}>{children}</ArcContext.Provider>;\n}\n\n/** The link component from the nearest ArcProvider, or a plain anchor. */\nexport const useArcLink = () => useContext(ArcContext).link;\n/** The image component from the nearest ArcProvider, or a plain img. */\nexport const useArcImage = () => useContext(ArcContext).image;\n\n/** What Arc components render for a link: the provider's link component, or a plain anchor. */\nexport function ArcLink(props: ArcLinkProps) {\n  return createElement(useArcLink(), props);\n}\n\n/** What Arc components render for an image: the provider's image component, or a plain img. */\nexport function ArcImage(props: ArcImageProps) {\n  return createElement(useArcImage(), props);\n}\n"}],"docs":"Docs and live preview: https://uiarc.dev/components/avatar","meta":{"tier":"free","kind":"component","tags":["identity","people"],"docs":"https://uiarc.dev/components/avatar","markdown":"https://uiarc.dev/components/avatar/markdown"}}