{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "components-avatar",
  "type": "registry:component",
  "title": "Avatar",
  "description": "A person or team as a picture, falling back to initials",
  "author": "sudo-kw <https://github.com/sudo-kw>",
  "dependencies": [
    "@gpuix/native@0.11.0",
    "@gpuix/react@0.11.0",
    "effect@4.1.2"
  ],
  "registryDependencies": [
    "https://gpuixui.sudokw.com/gpuix-tokens.json",
    "https://gpuixui.sudokw.com/gpuix-tw.json"
  ],
  "files": [
    {
      "path": "packages/components/src/avatar.tsx",
      "target": "@ui/gpuixui/components/avatar.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { Props } from \"@gpuix/react\";\nimport { mergeStyles } from \"@gpuix/react/floating\";\nimport { AVATAR_DISC, AVATAR_FALLBACK, AVATAR_ROOT, cn } from \"../tw/index.js\";\nimport { Match } from \"effect\";\nimport { initials, isAvatarStatus } from \"./avatar/logic.js\";\nimport type { AvatarStatus } from \"./avatar/logic.js\";\nimport { recipeStyle } from \"./internal/recipe.js\";\nimport {\n  avatarPixels,\n  avatarSize,\n  statusTone,\n  useTheme,\n  type Theme,\n} from \"./internal/theme.js\";\n\nexport type AvatarSize = keyof typeof avatarSize;\n\nexport type { AvatarStatus };\n\nexport interface AvatarProps extends Props {\n  readonly name: string;\n\n  readonly src?: string;\n\n  readonly size?: AvatarSize;\n\n  readonly status?: AvatarStatus;\n\n  readonly color?: string;\n\n  readonly initials?: boolean;\n\n  readonly radius?: number;\n\n  readonly clickThrough?: boolean;\n\n  readonly className?: string;\n}\n\nconst DOT = 10;\n\nconst statusFill = (theme: Theme, status: AvatarStatus): string =>\n  Match.value(status).pipe(\n    Match.when(\"online\", () => theme.colors.success),\n    Match.when(\"away\", () => statusTone(theme, \"warning\").foreground),\n    Match.when(\"busy\", () => theme.colors.destructive),\n    Match.when(\"offline\", () => theme.colors.mutedForeground),\n    Match.exhaustive,\n  );\n\nexport const Avatar = ({\n  name,\n  src,\n  size = \"md\",\n  status: statusProp,\n  color,\n  initials: showInitials = true,\n  clickThrough: requestedClickThrough,\n  className,\n  radius,\n  style,\n  ...rest\n}: AvatarProps) => {\n  const clickThrough =\n    requestedClickThrough ??\n    (rest.onClick === undefined &&\n      rest.onMouseDown === undefined &&\n      rest.onMouseUp === undefined);\n  const theme = useTheme();\n  const diameter = avatarPixels(size);\n\n  const status = isAvatarStatus(statusProp) ? statusProp : undefined;\n\n  return (\n    <div\n      role=\"img\"\n      aria-label={name}\n      aria-description={status ?? rest[\"aria-description\"]}\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(AVATAR_ROOT, className), theme), {\n          width: diameter,\n          height: diameter,\n          ...(clickThrough ? { pointerEvents: \"none\" as const } : {}),\n        }),\n        style,\n      )}\n    >\n      {}\n      <div\n        style={mergeStyles(recipeStyle(AVATAR_DISC, theme), {\n          pointerEvents: \"none\",\n          ...(color === undefined ? {} : { backgroundColor: color }),\n          ...(radius === undefined ? {} : { borderRadius: radius }),\n        })}\n      >\n        {src === undefined && !showInitials ? null : src === undefined ? (\n          <text\n            role=\"presentation\"\n            style={mergeStyles(recipeStyle(AVATAR_FALLBACK, theme), {\n              fontSize: Math.round(diameter * 0.44),\n            })}\n          >\n            {initials(name)}\n          </text>\n        ) : (\n          <img\n            src={src}\n            alt=\"\"\n            role=\"presentation\"\n            objectFit=\"cover\"\n            style={{ width: \"100%\", height: \"100%\" }}\n          />\n        )}\n      </div>\n      {status === undefined ? null : (\n        <div\n          style={{\n            position: \"absolute\",\n            right: -1,\n            bottom: -1,\n            width: DOT,\n            height: DOT,\n            borderRadius: theme.radius.full,\n            backgroundColor: statusFill(theme, status),\n            borderWidth: theme.borderWidth.strong,\n            borderColor: theme.colors.card,\n            pointerEvents: \"none\",\n          }}\n        />\n      )}\n    </div>\n  );\n};\n\nexport const avatarMeta = {\n  name: \"Avatars\",\n  tier: 2,\n  status: \"done\",\n  gpuixSupport: \"yes\",\n  limitations: [\n    \"no image loading or error state in v1: the initials fallback is what shows until the picture paints\",\n  ],\n  docs: \"docs/avatar.mdx\",\n  example: \"examples/avatar.example.tsx\",\n  testFile: \"src/behaviour/avatar.test.tsx\",\n} as const;\n"
    },
    {
      "path": "packages/components/src/avatar/logic.ts",
      "target": "@ui/gpuixui/components/avatar/logic.ts",
      "type": "registry:lib",
      "content": "import { pipe } from \"effect\";\nimport * as Arr from \"effect/Array\";\nimport * as Option from \"effect/Option\";\nimport * as Schema from \"effect/Schema\";\nimport * as Str from \"effect/String\";\n\nexport const AVATAR_STATUSES = [\"online\", \"away\", \"busy\", \"offline\"] as const;\n\nexport const AvatarStatus = Schema.Literals(AVATAR_STATUSES);\n\nexport type AvatarStatus = typeof AvatarStatus.Type;\n\nexport const isAvatarStatus = Schema.is(AvatarStatus);\n\nconst SEPARATORS = /[\\s-]+/;\n\nconst parts = (name: string): ReadonlyArray<string> =>\n  pipe(Str.trim(name), Str.split(SEPARATORS), Arr.filter(Str.isNonEmpty));\n\nconst leadingLetters = (name: string): ReadonlyArray<string> =>\n  Arr.getSomes(\n    Arr.map(parts(name), (part) =>\n      Option.map(Arr.head(Arr.fromIterable(part)), Str.toUpperCase),\n    ),\n  );\n\nexport const initials = (name: string): string => {\n  const letters = leadingLetters(name);\n\n  const last = Option.getOrElse(Arr.last(letters), () => \"\");\n\n  return Option.match(Arr.head(letters), {\n    onNone: () => \"?\",\n    onSome: (first) => (first === last ? first : `${first}${last}`),\n  });\n};\n"
    },
    {
      "path": "packages/components/src/internal/recipe.ts",
      "target": "@ui/gpuixui/components/internal/recipe.ts",
      "type": "registry:lib",
      "content": "import type { StyleDesc } from \"@gpuix/react\";\nimport { createRecipeCache } from \"../../tw/index.js\";\nimport type { Theme } from \"./theme.js\";\n\nexport const recipeStyle: (recipe: string, theme: Theme) => StyleDesc =\n  createRecipeCache(512);\n"
    },
    {
      "path": "packages/components/src/internal/theme.ts",
      "target": "@ui/gpuixui/components/internal/theme.ts",
      "type": "registry:lib",
      "content": "import type { StyleDesc } from \"@gpuix/react\";\nimport * as Schema from \"effect/Schema\";\nimport {\n  alpha,\n  avatarPixels,\n  avatarSize,\n  brandTextOn,\n  controlFocusRing,\n  controlHeight,\n  controlPaddingX,\n  currentPlatform,\n  darkTheme,\n  disabledAction,\n  disabledOpacity,\n  elevationShadow,\n  filledHover,\n  filledPressed,\n  findHighlight,\n  focusRing,\n  keyboardFocusRing,\n  lightTheme,\n  nativeTextTheme,\n  pointerWash,\n  selectionOn,\n  statusTone,\n  syntaxColors,\n  targetSize,\n  textStyle,\n  type BoxShadow,\n  type ElevationLevel,\n  type FindHighlightSource,\n  type NativeTextFill,\n  type PlatformName,\n  type PointerWashSurface,\n  type StatusTone,\n  type SyntaxColors,\n  type SyntaxRole,\n  type TextRole,\n  type Theme,\n  type ThemeColors,\n} from \"../../tokens/index.js\";\n\nexport {\n  alpha,\n  avatarPixels,\n  avatarSize,\n  controlFocusRing,\n  controlHeight,\n  controlPaddingX,\n  currentPlatform,\n  darkTheme,\n  disabledAction,\n  disabledOpacity,\n  elevationShadow,\n  filledHover,\n  filledPressed,\n  findHighlight,\n  focusRing,\n  keyboardFocusRing,\n  lightTheme,\n  nativeTextTheme,\n  statusTone,\n  syntaxColors,\n  targetSize,\n  textStyle,\n};\nexport type {\n  BoxShadow,\n  ElevationLevel,\n  FindHighlightSource,\n  NativeTextFill,\n  PlatformName,\n  StatusTone,\n  SyntaxColors,\n  SyntaxRole,\n  TextRole,\n  Theme,\n};\nexport { ThemeProvider, useTheme } from \"../../tokens/react.js\";\n\nexport const keyboardRing = (theme: Theme, invalid = false): StyleDesc =>\n  controlFocusRing(theme, { invalid }) ?? {};\n\nexport interface ControlRingState {\n  readonly disabled?: boolean;\n\n  readonly invalid?: boolean;\n}\n\nexport const controlRing = (\n  theme: Theme,\n  state: ControlRingState = {},\n): StyleDesc => controlFocusRing(theme, state) ?? {};\n\nexport const pressWash = (theme: Theme, replaced: PointerWashSurface): string =>\n  pointerWash(theme, \"active\", replaced) ?? theme.colors.active;\n\nexport const selectionWash = (theme: Theme): StyleDesc => ({\n  selectionColor: theme.colors.selection,\n});\n\nexport const keepContent: StyleDesc = { keyboardFocusDim: false };\n\nexport const disabledFade = (\n  disabled: boolean,\n): { readonly opacity: number } | undefined =>\n  disabled ? { opacity: disabledOpacity } : undefined;\n\nconst Tones = Schema.Literals([\"info\", \"success\", \"warning\", \"danger\"]);\n\nexport type Tone = typeof Tones.Type;\n\nexport const toneOf = (value: unknown): Tone =>\n  Schema.is(Tones)(value) ? value : \"info\";\n\nexport interface ToneStyle {\n  readonly fg: string;\n  readonly bg: string;\n  readonly border: string;\n}\n\nconst TOKEN_TONE: Readonly<Record<Tone, StatusTone>> = {\n  info: \"info\",\n  success: \"success\",\n  warning: \"warning\",\n  danger: \"destructive\",\n};\n\nexport const tone = (theme: Theme, value: Tone): ToneStyle => {\n  const { background, border, foreground } = statusTone(\n    theme,\n    TOKEN_TONE[value],\n  );\n  return { bg: background, border, fg: foreground };\n};\n\nconst softRoles: Readonly<Record<Tone, keyof ThemeColors>> = {\n  info: \"infoSoft\",\n  success: \"successSoft\",\n  warning: \"warningSoft\",\n  danger: \"destructiveSoft\",\n};\n\nexport const messageLink = (theme: Theme, value: Tone): string =>\n  brandTextOn(theme, softRoles[value]);\n\nexport const messageSelection = (theme: Theme, value: Tone): string =>\n  selectionOn(theme, softRoles[value]);\n\nexport const toneSurface = (theme: Theme, value: Tone): StyleDesc => {\n  const { bg, border, fg } = tone(theme, value);\n  return {\n    backgroundColor: bg,\n    color: fg,\n    borderWidth: theme.borderWidth.thin,\n    borderColor: border,\n  };\n};\n"
    }
  ],
  "docs": "[Avatar](https://gpuixui.sudokw.com/components/avatar) — These are GPUix native components: the files render through GPUix's reconciler against `@gpuix/react`, not the DOM, and need the per-platform `@gpuix/native` binding. The CLI installs the `@gpuixui/*` runtime the components import as registry items, so the copied tree resolves on its own; wrap it in `ThemeProvider` before it renders.",
  "meta": {
    "url": "/components/avatar",
    "kind": "component",
    "runtime": "gpuix",
    "registry": "gpuixui"
  }
}