{
  "name": "avatar",
  "layer": "base",
  "type": "component",
  "title": "Avatar components",
  "description": "Free and open-source React avatar components built for modern applications and websites. These avatars are built using React Aria and styled with Tailwind CSS.",
  "files": [
    {
      "path": "components/base/avatar/avatar-label-group.tsx",
      "target": "components/base/avatar/avatar-label-group.tsx",
      "type": "component",
      "content": "\"use client\";\n\nimport { type ReactNode } from \"react\";\nimport { cx, sortCx } from \"@/utils/cx\";\nimport { Avatar, type AvatarProps } from \"./avatar\";\n\nconst styles = sortCx({\n    sm: { title: \"text-sm \", subtitle: \"text-xs\" },\n    md: { title: \"text-sm \", subtitle: \"text-sm\" },\n    lg: { title: \"text-md \", subtitle: \"text-md\" },\n});\n\ninterface AvatarLabelGroupProps extends AvatarProps {\n    size: \"sm\" | \"md\" | \"lg\";\n    rounded?: boolean;\n    title: string | ReactNode;\n    subtitle: string | ReactNode;\n    avatarClassName?: string;\n}\n\nexport const AvatarLabelGroup = ({ title, subtitle, className, rounded, avatarClassName, ...props }: AvatarLabelGroupProps) => {\n    return (\n        <figure className={cx(\"group flex min-w-0 flex-1 items-center gap-2\", className)}>\n            <Avatar border rounded={rounded} className={avatarClassName} {...props} />\n            <figcaption className=\"min-w-0 flex-1\">\n                <p className={cx(\"text-primary font-semibold\", styles[props.size].title)}>{title}</p>\n                <p className={cx(\"text-tertiary truncate\", styles[props.size].subtitle)}>{subtitle}</p>\n            </figcaption>\n        </figure>\n    );\n};\n",
      "dependencies": [
        "react"
      ]
    },
    {
      "path": "components/base/avatar/avatar-profile-photo.tsx",
      "target": "components/base/avatar/avatar-profile-photo.tsx",
      "type": "component",
      "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { User01 } from \"@properui/icons\";\nimport { cx, sortCx } from \"@/utils/cx\";\nimport { type AvatarProps } from \"./avatar\";\nimport { AvatarOnlineIndicator, VerifiedTick } from \"./base-components\";\n\nconst styles = sortCx({\n    sm: {\n        root: \"size-18 p-0.75\",\n        rootWithPlaceholder: \"p-1\",\n        content: \"outline-[0.5px] -outline-offset-[0.5px] before:border\",\n        icon: \"size-9\",\n        initials: \"text-display-sm font-semibold\",\n        badge: \"bottom-0.5 end-0.5\",\n    },\n    md: {\n        root: \"size-24 p-1\",\n        rootWithPlaceholder: \"p-1.25\",\n        content: \"shadow-xl outline-[0.75px] -outline-offset-[0.75px] before:border-[1.5px]\",\n        icon: \"size-12\",\n        initials: \"text-display-md font-semibold\",\n        badge: \"bottom-1 end-1\",\n    },\n    lg: {\n        root: \"size-40 p-1.5\",\n        rootWithPlaceholder: \"p-1.75\",\n        content: \"shadow-2xl outline-[0.75px] -outline-offset-[0.75px] before:border-[1.5px]\",\n        icon: \"size-20\",\n        initials: \"text-display-xl font-semibold\",\n        badge: \"bottom-2 end-2\",\n    },\n});\n\nconst tickSizeMap = {\n    sm: \"2xl\",\n    md: \"3xl\",\n    lg: \"4xl\",\n} as const;\n\ninterface AvatarProfilePhotoProps extends AvatarProps {\n    size: \"sm\" | \"md\" | \"lg\";\n}\n\nexport const AvatarProfilePhoto = ({\n    size = \"md\",\n    src,\n    alt,\n    initials,\n    placeholder,\n    placeholderIcon: PlaceholderIcon,\n    verified,\n    badge,\n    status,\n    className,\n}: AvatarProfilePhotoProps) => {\n    const [isFailed, setIsFailed] = useState(false);\n\n    const renderMainContent = () => {\n        if (src && !isFailed) {\n            return (\n                <div\n                    className={cx(\n                        \"relative size-full overflow-hidden rounded-full outline-black/16 before:absolute before:inset-0 before:rounded-full before:border-white/32 before:mask-[linear-gradient(to_bottom,black_0%,transparent_25%,transparent_75%,black_100%)]\",\n                        styles[size].content,\n                    )}\n                >\n                    <img src={src} alt={alt} onError={() => setIsFailed(true)} className=\"size-full object-cover\" />\n                </div>\n            );\n        }\n\n        if (initials) {\n            return (\n                <div\n                    className={cx(\n                        \"bg-tertiary ring-secondary_alt flex size-full items-center justify-center rounded-full ring-1 outline-transparent before:hidden\",\n                        styles[size].content,\n                    )}\n                >\n                    <span className={cx(\"text-quaternary\", styles[size].initials)}>{initials}</span>\n                </div>\n            );\n        }\n\n        if (PlaceholderIcon) {\n            return (\n                <div\n                    className={cx(\n                        \"bg-tertiary ring-secondary_alt flex size-full items-center justify-center rounded-full ring-1 outline-transparent before:hidden\",\n                        styles[size].content,\n                    )}\n                >\n                    <PlaceholderIcon className={cx(\"text-fg-quaternary\", styles[size].icon)} />\n                </div>\n            );\n        }\n\n        return (\n            <div\n                className={cx(\n                    \"bg-tertiary ring-secondary_alt flex size-full items-center justify-center rounded-full ring-1 outline-transparent before:hidden\",\n                    styles[size].content,\n                )}\n            >\n                {placeholder || <User01 className={cx(\"text-fg-quaternary\", styles[size].icon)} />}\n            </div>\n        );\n    };\n\n    const renderBadgeContent = () => {\n        if (status) {\n            return <AvatarOnlineIndicator status={status} size={tickSizeMap[size]} className={styles[size].badge} />;\n        }\n\n        if (verified) {\n            return <VerifiedTick size={tickSizeMap[size]} className={cx(\"absolute\", styles[size].badge)} />;\n        }\n\n        return badge;\n    };\n\n    return (\n        <div\n            className={cx(\n                \"bg-primary ring-secondary_alt relative flex shrink-0 items-center justify-center rounded-full ring-1\",\n                styles[size].root,\n                (!src || isFailed) && styles[size].rootWithPlaceholder,\n                className,\n            )}\n        >\n            {renderMainContent()}\n            {renderBadgeContent()}\n        </div>\n    );\n};\n",
      "dependencies": [
        "@properui/icons",
        "react"
      ]
    },
    {
      "path": "components/base/avatar/avatar.tsx",
      "target": "components/base/avatar/avatar.tsx",
      "type": "component",
      "content": "\"use client\";\n\nimport { type FC, type ReactNode, useState } from \"react\";\nimport { User01 } from \"@properui/icons\";\nimport { cx, sortCx } from \"@/utils/cx\";\nimport { AvatarOnlineIndicator, VerifiedTick } from \"./base-components\";\nimport { AvatarCount } from \"./base-components/avatar-count\";\n\nexport interface AvatarProps {\n    size?: \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\";\n    className?: string;\n    /**\n     * The class name for the main child of the avatar.\n     */\n    contentClassName?: string;\n    src?: string | null;\n    alt?: string;\n    /**\n     * Display an inner contrast border around the avatar image.\n     */\n    contrastBorder?: boolean;\n    /**\n     * Whether the avatar should be rounded.\n     * @default true\n     */\n    rounded?: boolean;\n    /**\n     * Display an outer border around the avatar.\n     */\n    border?: boolean;\n    /**\n     * Display a badge (i.e. company logo).\n     */\n    badge?: ReactNode;\n    /**\n     * Display a status indicator.\n     */\n    status?: \"online\" | \"offline\";\n    /**\n     * Display a verified tick icon.\n     *\n     * @default false\n     */\n    verified?: boolean;\n    /**\n     * Display a count badge.\n     */\n    count?: number;\n    /**\n     * The initials of the user to display if no image is available.\n     */\n    initials?: string;\n    /**\n     * An icon to display if no image is available.\n     */\n    placeholderIcon?: FC<{ className?: string }>;\n    /**\n     * A placeholder to display if no image is available.\n     */\n    placeholder?: ReactNode;\n\n    /**\n     * Whether the avatar should show a focus ring when the parent group is in focus.\n     * For example, when the avatar is wrapped inside a link.\n     *\n     * @default false\n     */\n    focusable?: boolean;\n}\n\nconst styles = sortCx({\n    xs: { root: \"size-6\", rootWithBorder: \"p-px\", initials: \"text-xs font-semibold\", icon: \"size-4\" },\n    sm: { root: \"size-8\", rootWithBorder: \"p-px\", initials: \"text-sm font-semibold\", icon: \"size-5\" },\n    md: { root: \"size-10\", rootWithBorder: \"p-px\", initials: \"text-md font-semibold\", icon: \"size-6\" },\n    lg: { root: \"size-12\", rootWithBorder: \"p-[1.5px]\", initials: \"text-lg font-semibold\", icon: \"size-7\" },\n    xl: { root: \"size-14\", rootWithBorder: \"p-0.5\", initials: \"text-xl font-semibold\", icon: \"size-8\" },\n    \"2xl\": { root: \"size-16\", rootWithBorder: \"p-0.5\", initials: \"text-display-xs font-semibold\", icon: \"size-8\" },\n});\n\nexport const Avatar = ({\n    size = \"md\",\n    src,\n    alt,\n    initials,\n    placeholder,\n    placeholderIcon: PlaceholderIcon,\n    border,\n    badge,\n    status,\n    verified,\n    count,\n    focusable = false,\n    rounded = true,\n    className,\n    contentClassName,\n}: AvatarProps) => {\n    const [isFailed, setIsFailed] = useState(false);\n\n    const canShowImage = src && !isFailed;\n\n    const renderMainContent = () => {\n        if (canShowImage) {\n            return <img data-avatar-img className=\"size-full object-cover\" src={src} alt={alt} onError={() => setIsFailed(true)} />;\n        }\n\n        if (initials) {\n            return <span className={cx(\"text-quaternary\", styles[size].initials)}>{initials}</span>;\n        }\n\n        if (PlaceholderIcon) {\n            return <PlaceholderIcon className={cx(\"text-fg-quaternary\", styles[size].icon)} />;\n        }\n\n        return placeholder || <User01 className={cx(\"text-fg-quaternary\", styles[size].icon)} />;\n    };\n\n    const renderBadgeContent = () => {\n        if (status) {\n            return <AvatarOnlineIndicator status={status} size={size} />;\n        }\n\n        if (verified) {\n            return <VerifiedTick size={size} className={cx(\"absolute end-0 bottom-0\", size === \"xs\" && \"-right-px -bottom-px\")} />;\n        }\n\n        if (count) {\n            return <AvatarCount count={count} />;\n        }\n\n        return badge;\n    };\n\n    return (\n        <div\n            data-avatar\n            className={cx(\n                \"relative inline-flex shrink-0 rounded-[7px]\",\n                rounded && \"rounded-full\",\n                // Focus styles\n                focusable && \"group-focus-visible:outline-focus-ring outline-transparent group-focus-visible:outline-2 group-focus-visible:outline-offset-2\",\n                border && \"ring-secondary_alt ring-1\",\n                border && styles[size].rootWithBorder,\n                styles[size].root,\n                className,\n            )}\n        >\n            <div\n                className={cx(\n                    \"bg-tertiary relative inline-flex size-full shrink-0 items-center justify-center overflow-hidden rounded-md outline-[0.5px] -outline-offset-[0.5px] outline-black/16 before:inset-[0.5px]\",\n                    rounded && \"rounded-full\",\n                    canShowImage &&\n                        size !== \"xs\" &&\n                        \"before:absolute before:inset-0 before:rounded-[inherit] before:border before:border-white/32 before:mask-[linear-gradient(to_bottom,black_0%,transparent_25%,transparent_75%,black_100%)]\",\n                    contentClassName,\n                )}\n            >\n                {renderMainContent()}\n            </div>\n            {renderBadgeContent()}\n        </div>\n    );\n};\n",
      "dependencies": [
        "@properui/icons",
        "react"
      ]
    },
    {
      "path": "components/base/avatar/base-components/avatar-add-button.tsx",
      "target": "components/base/avatar/base-components/avatar-add-button.tsx",
      "type": "component",
      "content": "\"use client\";\n\nimport type { ButtonProps as AriaButtonProps } from \"react-aria-components\";\nimport { Button as AriaButton, Tooltip as AriaTooltip, TooltipTrigger as AriaTooltipTrigger } from \"react-aria-components\";\nimport { Plus } from \"@properui/icons\";\nimport { cx } from \"@/utils/cx\";\n\nconst sizes = {\n    xs: { root: \"size-6\", icon: \"size-4\" },\n    sm: { root: \"size-8\", icon: \"size-4\" },\n    md: { root: \"size-10\", icon: \"size-5\" },\n};\n\ninterface AvatarAddButtonProps extends AriaButtonProps {\n    size: \"xs\" | \"sm\" | \"md\";\n    title?: string;\n    className?: string;\n}\n\n/**\n * NOTE(orchestrator): the MIT reference wraps this in the shared `base/tooltip` component,\n * which does not exist in this repo yet. This inlines an equivalent tooltip directly on top\n * of `react-aria-components` so the button stays fully keyboard/AT accessible without reaching\n * outside this task's owned files. Swap back to the shared `Tooltip` once `base/tooltip` lands.\n */\nexport const AvatarAddButton = ({ size, className, title = \"Add user\", ...props }: AvatarAddButtonProps) => (\n    <AriaTooltipTrigger delay={300} closeDelay={0}>\n        <AriaButton\n            {...props}\n            aria-label={title}\n            className={cx(\n                \"border-primary bg-primary text-fg-quaternary outline-focus-ring hover:bg-primary_hover hover:text-fg-quaternary_hover flex cursor-pointer items-center justify-center rounded-full border border-dashed transition duration-100 ease-linear focus-visible:outline-2 focus-visible:outline-offset-2 disabled:opacity-50\",\n                sizes[size].root,\n                className,\n            )}\n        >\n            <Plus className={cx(\"transition-inherit-all text-current\", sizes[size].icon)} />\n        </AriaButton>\n        <AriaTooltip\n            offset={6}\n            placement=\"top\"\n            className={({ isEntering, isExiting }) => cx(isEntering && \"animate-in ease-out\", isExiting && \"animate-out ease-in\")}\n        >\n            <div className=\"bg-primary-solid z-50 flex max-w-xs origin-(--trigger-anchor-point) flex-col items-start gap-1 rounded-lg px-3 py-2 shadow-lg will-change-transform\">\n                <span className=\"text-xs font-semibold text-white\">{title}</span>\n            </div>\n        </AriaTooltip>\n    </AriaTooltipTrigger>\n);\n",
      "dependencies": [
        "@properui/icons",
        "react-aria-components"
      ]
    },
    {
      "path": "components/base/avatar/base-components/avatar-company-icon.tsx",
      "target": "components/base/avatar/base-components/avatar-company-icon.tsx",
      "type": "component",
      "content": "\"use client\";\n\nimport { cx } from \"@/utils/cx\";\n\nconst sizes = {\n    xs: \"size-2\",\n    sm: \"size-3\",\n    md: \"size-3.5\",\n    lg: \"size-4\",\n    xl: \"size-4.5\",\n    \"2xl\": \"size-5 ring-[1.67px]\",\n};\n\ninterface AvatarCompanyIconProps {\n    size: \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\";\n    src: string;\n    alt?: string;\n}\n\nexport const AvatarCompanyIcon = ({ size, src, alt }: AvatarCompanyIconProps) => (\n    <img\n        src={src}\n        alt={alt}\n        className={cx(\"bg-brand-50 ring-bg-primary absolute -right-0.5 -bottom-0.5 rounded-full object-cover ring-[1.5px]\", sizes[size])}\n    />\n);\n",
      "dependencies": []
    },
    {
      "path": "components/base/avatar/base-components/avatar-count.tsx",
      "target": "components/base/avatar/base-components/avatar-count.tsx",
      "type": "component",
      "content": "\"use client\";\n\nimport { cx } from \"@/utils/cx\";\n\ninterface AvatarCountProps {\n    count: number;\n    className?: string;\n}\n\nexport const AvatarCount = ({ count, className }: AvatarCountProps) => (\n    <div className={cx(\"absolute end-0 bottom-0 p-px\", className)}>\n        <div className=\"bg-fg-error-primary flex size-3.5 items-center justify-center rounded-full text-center text-[10px] leading-[13px] font-bold text-white\">\n            {count}\n        </div>\n    </div>\n);\n",
      "dependencies": []
    },
    {
      "path": "components/base/avatar/base-components/avatar-online-indicator.tsx",
      "target": "components/base/avatar/base-components/avatar-online-indicator.tsx",
      "type": "component",
      "content": "\"use client\";\n\nimport { cx } from \"@/utils/cx\";\n\nconst sizes = {\n    xs: \"size-1.5\",\n    sm: \"size-2\",\n    md: \"size-2.5\",\n    lg: \"size-3\",\n    xl: \"size-3.5\",\n    \"2xl\": \"size-4\",\n    \"3xl\": \"size-4.5\",\n    \"4xl\": \"size-5\",\n};\n\ninterface AvatarOnlineIndicatorProps {\n    size: \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\" | \"3xl\" | \"4xl\";\n    status: \"online\" | \"offline\";\n    className?: string;\n}\n\nexport const AvatarOnlineIndicator = ({ size, status, className }: AvatarOnlineIndicatorProps) => (\n    <span\n        className={cx(\n            \"ring-bg-primary absolute end-0 bottom-0 flex justify-center rounded-full ring-[1.5px]\",\n            status === \"online\" ? \"bg-fg-success-secondary\" : \"bg-utility-neutral-300\",\n            sizes[size],\n            className,\n        )}\n        style={{\n            backgroundImage:\n                \"radial-gradient(43.75% 43.75% at 50% 28.75%, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.00) 100%), radial-gradient(50% 50% at 50% 50%, rgba(255, 255, 255, 0.00) 74.66%, rgba(255, 255, 255, 0.18) 100%), radial-gradient(75% 75% at 50% 0%, rgba(255, 255, 255, 0.00) 0%, rgba(255, 255, 255, 0.00) 50%, rgba(255, 255, 255, 0.08) 99%, rgba(255, 255, 255, 0.00) 100%)\",\n        }}\n    >\n        {/* Reflection */}\n        <svg viewBox=\"0 0 7.2 2.85\" fill=\"none\" className=\"mt-[10%] h-[20%] w-[60%]\">\n            <path\n                d=\"M7.2 1.83107C7.2 2.84235 5.58823 2.19729 3.6 2.19729C1.61177 2.19729 0 2.84235 0 1.83107C0 0.8198 1.61177 0 3.6 0C5.58823 0 7.2 0.8198 7.2 1.83107Z\"\n                fill=\"url(#reflection-gradient)\"\n                fillOpacity=\"0.4\"\n            />\n            <defs>\n                <linearGradient id=\"reflection-gradient\" x1=\"3.6\" y1=\"0\" x2=\"3.6\" y2=\"2.4\" gradientUnits=\"userSpaceOnUse\">\n                    <stop stopColor=\"white\" />\n                    <stop offset=\"1\" stopColor=\"white\" stopOpacity=\"0.1\" />\n                </linearGradient>\n            </defs>\n        </svg>\n    </span>\n);\n",
      "dependencies": []
    },
    {
      "path": "components/base/avatar/base-components/index.tsx",
      "target": "components/base/avatar/base-components/index.tsx",
      "type": "component",
      "content": "export * from \"./avatar-add-button\";\nexport * from \"./avatar-company-icon\";\nexport * from \"./avatar-online-indicator\";\nexport * from \"./verified-tick\";\n",
      "dependencies": []
    },
    {
      "path": "components/base/avatar/base-components/verified-tick.tsx",
      "target": "components/base/avatar/base-components/verified-tick.tsx",
      "type": "component",
      "content": "\"use client\";\n\nimport { cx } from \"@/utils/cx\";\n\nconst sizes = {\n    xs: \"size-2.5\",\n    sm: \"size-3\",\n    md: \"size-3.5\",\n    lg: \"size-4\",\n    xl: \"size-4.5\",\n    \"2xl\": \"size-5\",\n    \"3xl\": \"size-6\",\n    \"4xl\": \"size-8\",\n};\n\ninterface VerifiedTickProps {\n    size: \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\" | \"3xl\" | \"4xl\";\n    className?: string;\n}\n\nexport const VerifiedTick = ({ size, className }: VerifiedTickProps) => (\n    <svg className={cx(\"text-utility-blue-500 z-10\", sizes[size], className)} viewBox=\"0 0 10 10\" fill=\"none\">\n        <path\n            d=\"M7.72237 1.77098C7.81734 2.00068 7.99965 2.18326 8.2292 2.27858L9.03413 2.61199C9.26384 2.70714 9.44635 2.88965 9.5415 3.11936C9.63665 3.34908 9.63665 3.60718 9.5415 3.83689L9.20833 4.64125C9.11313 4.87106 9.113 5.12943 9.20863 5.35913L9.54122 6.16325C9.58839 6.27702 9.61268 6.39897 9.6127 6.52214C9.61272 6.6453 9.58847 6.76726 9.54134 6.88105C9.4942 6.99484 9.42511 7.09823 9.33801 7.18531C9.2509 7.27238 9.14749 7.34144 9.03369 7.38854L8.22934 7.72171C7.99964 7.81669 7.81706 7.99899 7.72174 8.22855L7.38833 9.03348C7.29318 9.26319 7.11067 9.4457 6.88096 9.54085C6.65124 9.636 6.39314 9.636 6.16343 9.54085L5.35907 9.20767C5.12935 9.11276 4.87134 9.11295 4.64177 9.20821L3.83684 9.54115C3.60725 9.63608 3.34937 9.636 3.11984 9.54092C2.89032 9.44585 2.70791 9.26356 2.6127 9.03409L2.27918 8.22892C2.18421 7.99923 2.0019 7.81665 1.77235 7.72133L0.967421 7.38792C0.737807 7.29281 0.555355 7.11041 0.460169 6.88083C0.364983 6.65125 0.364854 6.39327 0.45981 6.16359L0.792984 5.35924C0.8879 5.12952 0.887707 4.87151 0.792445 4.64193L0.459749 3.83642C0.41258 3.72265 0.388291 3.60069 0.388272 3.47753C0.388252 3.35436 0.412501 3.2324 0.459634 3.11861C0.506767 3.00482 0.57586 2.90144 0.662965 2.81436C0.75007 2.72728 0.853479 2.65822 0.967283 2.61113L1.77164 2.27795C2.00113 2.18306 2.1836 2.00099 2.27899 1.7717L2.6124 0.966768C2.70755 0.737054 2.89006 0.554547 3.11978 0.459397C3.34949 0.364246 3.60759 0.364246 3.83731 0.459397L4.64166 0.792571C4.87138 0.887487 5.12939 0.887293 5.35897 0.792031L6.16424 0.459913C6.39392 0.364816 6.65197 0.364836 6.88164 0.459968C7.11131 0.555099 7.29379 0.737554 7.38895 0.967208L7.72247 1.77238L7.72237 1.77098Z\"\n            className=\"fill-current\"\n        />\n        <path\n            fillRule=\"evenodd\"\n            clipRule=\"evenodd\"\n            d=\"M6.95829 3.68932C7.02509 3.58439 7.04747 3.45723 7.02051 3.3358C6.99356 3.21437 6.91946 3.10862 6.81454 3.04182C6.70961 2.97502 6.58245 2.95264 6.46102 2.97959C6.33959 3.00655 6.23384 3.08064 6.16704 3.18557L4.33141 6.06995L3.49141 5.01995C3.41375 4.92281 3.30069 4.8605 3.17709 4.84673C3.05349 4.83296 2.92949 4.86885 2.83235 4.94651C2.73522 5.02417 2.67291 5.13723 2.65914 5.26083C2.64536 5.38443 2.68125 5.50843 2.75891 5.60557L4.00891 7.16807C4.0555 7.22638 4.11533 7.27271 4.18344 7.30323C4.25154 7.33375 4.32595 7.34757 4.40047 7.34353C4.47499 7.3395 4.54747 7.31773 4.61188 7.28004C4.67629 7.24234 4.73077 7.18981 4.77079 7.12682L6.95829 3.68932Z\"\n            fill=\"white\"\n        />\n    </svg>\n);\n",
      "dependencies": []
    },
    {
      "path": "components/base/avatar/utils.ts",
      "target": "components/base/avatar/utils.ts",
      "type": "component",
      "content": "/**\n * Extracts the initials from a full name.\n *\n * @param name - The full name from which to extract initials.\n * @returns The initials of the provided name. If the name contains only one word,\n *          it returns the first character of that word. If the name contains two words,\n *          it returns the first character of each word.\n */\nexport const getInitials = (name: string) => {\n    const [firstName = \"\", lastName] = name.split(\" \");\n    return firstName.charAt(0) + (lastName ? lastName.charAt(0) : \"\");\n};\n",
      "dependencies": []
    }
  ],
  "registryDependencies": [
    "cx"
  ],
  "optionalRegistryDependencies": [],
  "dependencies": [
    "@properui/icons",
    "react",
    "react-aria-components"
  ],
  "cssVars": [],
  "examples": [
    "avatar",
    "avatar-example",
    "border-example",
    "company-logo-example",
    "group-example",
    "initials-example",
    "label-group-example",
    "placeholder-example",
    "profile-photo",
    "profile-photo-initials-example",
    "profile-photo-placeholder-example",
    "status-indicator-example",
    "verified-badge-example"
  ],
  "docs": "/components/avatars",
  "intent": "Show who or what an item belongs to (a user, team, or company) as a compact image or initials.",
  "avoid_when": [
    "identifying more than one person at once (use avatar-label-group or a stacked list)",
    "the only way to identify a user (pair it with a visible name)"
  ],
  "composes_with": [
    "avatar-label-group",
    "dropdown",
    "table",
    "activity-feed",
    "messaging"
  ],
  "a11y_contract": [
    "caller must supply `alt` describing the person/entity; the component does not infer one",
    "falls back to initials or an icon when `src` is missing or fails to load, but does not auto-generate accessible text for that fallback"
  ],
  "requires_data": [
    "src (image URL) or initials, so the avatar renders a meaningful identity; the placeholder icon alone carries none"
  ],
  "token_contract": [
    "bg-fg-error-primary",
    "bg-fg-success-secondary",
    "bg-primary",
    "bg-primary-solid",
    "bg-primary_hover",
    "bg-tertiary",
    "bg-utility-neutral-300",
    "border-primary",
    "text-fg-quaternary",
    "text-fg-quaternary_hover",
    "text-primary",
    "text-quaternary",
    "text-tertiary",
    "text-utility-blue-500"
  ],
  "changelog": [
    {
      "version": "0.3.0",
      "changes": [
        "Add the `ai-elements` group for AI chat interfaces: `AIConversation` (stick-to-bottom log with a scroll-to-latest button), `AIMessage` (user and assistant messages with avatar, timestamp, actions and a streaming shimmer and caret), `AIResponse` (a small built-in markdown renderer with highlighted code blocks), `AIReasoning`, `AIToolCall`, `AISources`, `AISuggestions`, `AIPromptInput` (auto-growing, Enter to send, attachments, model selector and counter slots, Stop while streaming), `AIModelSelector` and `AIBranch`. They take plain props and callbacks, so they work with any streaming source and need no AI SDK."
      ]
    }
  ],
  "platforms": [
    "react",
    "next"
  ]
}
