{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "media",
  "title": "Media",
  "description": "A picture slot that reserves its space, lazy-loads, takes a URL, an image with alt text and a dark-mode file, or your own element, and falls back to artwork when there is no image or it fails.",
  "files": [
    {
      "path": "registry/tealmac/media.tsx",
      "content": "// Adapted from Ballmac UI (https://github.com/vamsiy78/ballmac-ui), MIT License, Copyright (c) 2026 Ballmac.\n\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/** An image with the text that describes it. `alt` is required: use \"\" only when the picture is pure decoration. */\nexport type MediaImage = {\n  src: string\n  alt: string\n  /** Shown instead of `src` in dark mode (when an ancestor has the `dark` class). */\n  srcDark?: string\n  srcSet?: string\n  sizes?: string\n  /** Intrinsic size, so the browser can reserve space before the file arrives. */\n  width?: number\n  height?: number\n  /** CSS object-position, for example \"top\" or \"50% 20%\". */\n  position?: string\n}\n\n/** What a slot accepts: an image URL, an image with its alt text, or your own element (a next/image, a video, a component). */\nexport type MediaSource = string | MediaImage | React.ReactElement\n\nconst ASPECTS = {\n  square: \"1 / 1\",\n  video: \"16 / 9\",\n  photo: \"4 / 3\",\n  wide: \"21 / 9\",\n  portrait: \"3 / 4\",\n  cinema: \"2 / 1\",\n} as const\nexport type MediaAspect =\n  | \"auto\"\n  | keyof typeof ASPECTS\n  | `${number}/${number}`\n  | `${number} / ${number}`\n\ntype MediaProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  /** The picture, or your own element. Leave it out to show `fallback`. */\n  media?: MediaSource | null\n  /** Describes a picture passed as a plain URL. Required for informative images; use \"\" for decoration. */\n  alt?: string\n  /** What shows when there is no `media` or the file fails to load. Usually the block's built-in artwork. */\n  fallback?: React.ReactNode\n  /** Reserves the box before the image loads, so the page does not jump. `auto` keeps the image's own proportions. */\n  aspect?: MediaAspect\n  /** `cover` fills and crops, `contain` shows all of it, `fill` stretches. */\n  fit?: \"cover\" | \"contain\" | \"fill\"\n  /** Above the fold: load now and with high priority instead of lazily. */\n  priority?: boolean\n  /** Fill a parent that has its own size (an absolutely positioned panel) instead of reserving space by aspect ratio. */\n  fill?: boolean\n  /** A card frame (rounded corners, border, soft shadow) around a real image or element. The built-in artwork keeps its own. */\n  frame?: boolean\n  /** Called when the file fails to load (the fallback is shown). */\n  onImageError?: () => void\n}\n\nconst FIT = {\n  cover: \"object-cover\",\n  contain: \"object-contain\",\n  fill: \"object-fill\",\n} as const\n\nfunction normalize(\n  media: MediaProps[\"media\"],\n  alt: string | undefined\n): MediaImage | null {\n  if (typeof media === \"string\") {\n    if (alt === undefined && process.env.NODE_ENV !== \"production\") {\n      console.warn(\n        `Media: \"${media}\" has no alt text. Pass alt=\"…\" to describe it, or alt=\"\" if it is decoration.`\n      )\n    }\n    return { src: media, alt: alt ?? \"\" }\n  }\n  if (media && typeof media === \"object\" && \"src\" in media)\n    return alt === undefined ? media : { ...media, alt }\n  return null\n}\n\n/**\n * A picture slot: it holds the space, loads lazily, falls back to the artwork if there is no image or it fails,\n * and takes a plain URL, an image with alt text (and an optional dark-mode file), or any element.\n */\nfunction Media({\n  media,\n  alt,\n  fallback,\n  aspect = \"auto\",\n  fit = \"cover\",\n  priority = false,\n  frame = false,\n  fill: fillParent = false,\n  onImageError,\n  className,\n  style,\n  ...props\n}: MediaProps) {\n  const [failed, setFailed] = React.useState(false)\n  const image = normalize(media, alt)\n  const custom = !image && React.isValidElement(media) ? media : null\n  const failedKey = image ? `${image.src}|${image.srcDark ?? \"\"}` : \"\"\n  const [lastKey, setLastKey] = React.useState(failedKey)\n  if (failedKey !== lastKey) {\n    // A new file gets another chance.\n    setLastKey(failedKey)\n    setFailed(false)\n  }\n  const ratio =\n    aspect === \"auto\"\n      ? undefined\n      : aspect in ASPECTS\n        ? ASPECTS[aspect as keyof typeof ASPECTS]\n        : aspect.replace(/\\s*\\/\\s*/, \" / \")\n  const fill = ratio !== undefined || fillParent\n  const showImage = image && !failed\n  const handleError = () => {\n    setFailed(true)\n    onImageError?.()\n  }\n  // Images that failed before React attached its handlers never fire onError again.\n  const check = React.useCallback(\n    (node: HTMLImageElement | null) => {\n      if (node && node.complete && node.naturalWidth === 0 && node.currentSrc) {\n        setFailed(true)\n        onImageError?.()\n      }\n    },\n    [onImageError]\n  )\n  const img = (src: string, extra?: string) => (\n    <img\n      ref={check}\n      src={src}\n      alt={image!.alt}\n      srcSet={src === image!.src ? image!.srcSet : undefined}\n      sizes={image!.sizes}\n      width={image!.width}\n      height={image!.height}\n      loading={priority ? \"eager\" : \"lazy\"}\n      decoding=\"async\"\n      fetchPriority={priority ? \"high\" : \"auto\"}\n      onError={handleError}\n      style={image!.position ? { objectPosition: image!.position } : undefined}\n      className={cn(\n        fill ? \"absolute inset-0 size-full\" : \"block h-auto w-full\",\n        FIT[fit],\n        extra\n      )}\n    />\n  )\n  return (\n    <div\n      data-slot=\"media\"\n      data-state={showImage ? \"image\" : custom ? \"custom\" : \"fallback\"}\n      className={cn(\n        \"relative overflow-hidden data-[state=fallback]:overflow-visible\",\n        fill && \"bg-muted/40 data-[state=fallback]:bg-transparent\",\n        frame &&\n          \"data-[state=custom]:rounded-2xl data-[state=custom]:border data-[state=custom]:shadow-[0_30px_80px_-40px_rgb(0_0_0/0.35)] data-[state=image]:rounded-2xl data-[state=image]:border data-[state=image]:shadow-[0_30px_80px_-40px_rgb(0_0_0/0.35)]\",\n        custom &&\n          \"[&>img]:absolute [&>img]:inset-0 [&>img]:size-full [&>img]:object-cover\",\n        className\n      )}\n      style={{ ...(ratio ? { aspectRatio: ratio } : {}), ...style }}\n      {...props}\n    >\n      {showImage ? (\n        image.srcDark ? (\n          <>\n            {img(image.src, \"dark:hidden\")}\n            {img(image.srcDark, \"hidden dark:block\")}\n          </>\n        ) : (\n          img(image.src)\n        )\n      ) : custom ? (\n        custom\n      ) : (\n        (fallback ?? null)\n      )}\n    </div>\n  )\n}\n\n/** True for an image URL or a `{ src, alt }` object, false for an element or nothing: lets a slot that used to take any element accept pictures too. */\nfunction isMediaImage(value: unknown): value is string | MediaImage {\n  return (\n    typeof value === \"string\" ||\n    (typeof value === \"object\" &&\n      value !== null &&\n      !React.isValidElement(value) &&\n      \"src\" in value)\n  )\n}\n\nexport { Media, isMediaImage, type MediaProps }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}