{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "map-frame",
  "title": "Map Frame",
  "description": "A labelled map region that hosts any map library, anchors overlays to its edges, and shows a placeholder basemap.",
  "dependencies": [
    "class-variance-authority"
  ],
  "registryDependencies": [
    "https://uaiblocks.dev/r/uai-theme.json",
    "https://uaiblocks.dev/r/uai-utils.json"
  ],
  "files": [
    {
      "path": "src/registry/uai/components/map-frame.tsx",
      "content": "\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { type ComponentProps, createContext, useContext } from \"react\";\nimport { cn } from \"@/lib/uai-utils\";\n\nexport const MAP_FRAME_VARIANTS = [\"card\", \"inset\", \"compact\"] as const;\nexport type MapFrameVariant = (typeof MAP_FRAME_VARIANTS)[number];\nexport const MAP_FRAME_POSITIONS = [\n  \"top-left\",\n  \"top\",\n  \"top-right\",\n  \"bottom-left\",\n  \"bottom\",\n  \"bottom-right\",\n] as const;\nexport type MapFramePosition = (typeof MAP_FRAME_POSITIONS)[number];\nexport type MapFrameProps = ComponentProps<\"section\"> & {\n  variant?: MapFrameVariant;\n  /** Marks the map as loading while tiles or data arrive. */\n  busy?: boolean;\n};\n\ntype FrameContext = { variant: MapFrameVariant };\nconst Context = createContext<FrameContext | null>(null);\nfunction useFrame(part: string) {\n  const context = useContext(Context);\n  if (!context) throw new Error(`${part} must be used within MapFrame`);\n  return context;\n}\n\nconst mapFrameVariants = cva(\n  \"relative isolate min-h-60 w-full min-w-0 overflow-hidden bg-muted text-[13px]/[18px] text-foreground\",\n  {\n    variants: {\n      variant: {\n        card: \"rounded-[14px] shadow-[0_0_0_1px_var(--border)]\",\n        inset: \"rounded-none\",\n        compact: \"min-h-44 rounded-xl shadow-[0_0_0_1px_var(--border)]\",\n      },\n    },\n  },\n);\n\n/** A labelled map region that hosts any map library and anchors overlays to its edges. */\nexport function MapFrame({\n  variant = \"card\",\n  busy = false,\n  \"aria-label\": label = \"Map\",\n  className,\n  children,\n  ...props\n}: MapFrameProps) {\n  return (\n    <Context.Provider value={{ variant }}>\n      <section\n        data-slot=\"map-frame\"\n        aria-label={label}\n        aria-busy={busy || undefined}\n        className={cn(mapFrameVariants({ variant }), className)}\n        {...props}\n        data-variant={variant}\n      >\n        {children}\n      </section>\n    </Context.Provider>\n  );\n}\n\n/** Fills the frame. Mount the map library's canvas or container here. */\nexport function MapFrameSurface({ className, ...props }: ComponentProps<\"div\">) {\n  useFrame(\"MapFrameSurface\");\n  return (\n    <div\n      data-slot=\"map-frame-surface\"\n      className={cn(\"absolute inset-0 -z-10 [&>*]:size-full\", className)}\n      {...props}\n    />\n  );\n}\n\nexport const MAP_FRAME_PLACEHOLDER_STATUSES = [\"loading\", \"error\"] as const;\nexport type MapFramePlaceholderStatus = (typeof MAP_FRAME_PLACEHOLDER_STATUSES)[number];\nexport type MapFramePlaceholderProps = ComponentProps<\"svg\"> & {\n  /**\n   * Replaces the basemap with a designed state. \"loading\" shows a shimmering grid while tiles\n   * stream in; \"error\" explains that the map failed and offers a retry.\n   */\n  status?: MapFramePlaceholderStatus;\n  /** Shows a \"Try again\" button in the error state. */\n  onRetry?: () => void;\n};\n\n/**\n * A decorative street grid shown before tiles load or where no map provider is configured.\n * It is hidden from assistive technology; label the frame instead. Pass `status` for the\n * loading and error states; those sit above the map surface and below overlays.\n */\nexport function MapFramePlaceholder({\n  status,\n  onRetry,\n  className,\n  style,\n  ...props\n}: MapFramePlaceholderProps) {\n  const context = useFrame(\"MapFramePlaceholder\");\n  if (status === \"loading\") {\n    return (\n      <div\n        data-slot=\"map-frame-placeholder\"\n        data-status=\"loading\"\n        className={cn(\n          \"pointer-events-none absolute inset-0 z-0 grid place-items-center bg-muted\",\n          className,\n        )}\n        style={style}\n      >\n        <div\n          aria-hidden=\"true\"\n          className=\"absolute inset-0 bg-[linear-gradient(var(--border)_1px,transparent_1px),linear-gradient(90deg,var(--border)_1px,transparent_1px)] bg-size-[32px_32px] opacity-60\"\n        />\n        <div\n          aria-hidden=\"true\"\n          className=\"absolute inset-0 animate-[skeleton-shimmer_1.6s_ease-in-out_infinite] bg-[linear-gradient(90deg,transparent_25%,var(--muted)_50%,transparent_75%)] bg-size-[200%_100%] bg-no-repeat motion-reduce:animate-none\"\n        />\n        <div className=\"relative grid justify-items-center gap-2\">\n          <span\n            aria-hidden=\"true\"\n            className=\"h-2.5 w-30 animate-[skeleton-shimmer_1.6s_ease-in-out_infinite] rounded-full bg-border bg-[linear-gradient(90deg,transparent_0%,color-mix(in_oklab,var(--foreground)_7%,transparent)_50%,transparent_100%)] bg-size-[200%_100%] bg-no-repeat motion-reduce:animate-none motion-reduce:bg-none\"\n          />\n          <span className=\"text-[12px]/4 text-subtle-foreground\">Loading map tiles…</span>\n        </div>\n      </div>\n    );\n  }\n  if (status === \"error\") {\n    return (\n      <div\n        data-slot=\"map-frame-placeholder\"\n        data-status=\"error\"\n        role=\"alert\"\n        className={cn(\n          \"absolute inset-0 z-0 grid place-items-center bg-muted text-center\",\n          context.variant === \"compact\" ? \"p-4\" : \"p-6\",\n          className,\n        )}\n        style={style}\n      >\n        <div className=\"grid justify-items-center gap-1.5\">\n          <p className=\"m-0 text-[13px]/[18px] font-medium text-foreground\">The map didn’t load</p>\n          <p className=\"m-0 text-pretty text-[12.5px]/[18px] text-muted-foreground\">\n            Check your connection and try again.\n          </p>\n          {onRetry && (\n            <button\n              type=\"button\"\n              onClick={onRetry}\n              className=\"mt-0.5 inline-flex h-6.5 cursor-pointer items-center rounded-full border-0 bg-card px-3 text-[12px]/4 font-medium text-foreground shadow-[0_0_0_1px_var(--border-strong)] transition-[background-color,scale] duration-[120ms,140ms] ease-[ease-out,cubic-bezier(0.23,1,0.32,1)] outline-none hover:bg-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:outline-solid active:scale-[0.97] motion-reduce:transition-none motion-reduce:active:scale-100\"\n            >\n              Try again\n            </button>\n          )}\n        </div>\n      </div>\n    );\n  }\n  return (\n    <svg\n      data-slot=\"map-frame-placeholder\"\n      aria-hidden=\"true\"\n      focusable=\"false\"\n      viewBox=\"0 0 640 400\"\n      preserveAspectRatio=\"xMidYMid slice\"\n      className={cn(\"absolute inset-0 -z-10 size-full\", className)}\n      style={style}\n      {...props}\n    >\n      <rect width=\"640\" height=\"400\" className=\"fill-muted\" />\n      <path\n        d=\"M-20 300 C 90 270, 150 340, 250 318 S 420 250, 520 286 S 640 330, 680 300 L 680 420 L -20 420 Z\"\n        className=\"fill-primary/12\"\n      />\n      <path\n        d=\"M392 36 h132 a14 14 0 0 1 14 14 v76 a14 14 0 0 1 -14 14 h-132 a14 14 0 0 1 -14 -14 v-76 a14 14 0 0 1 14 -14 Z\"\n        className=\"fill-success/14\"\n      />\n      <path\n        d=\"M70 92 h86 a10 10 0 0 1 10 10 v52 a10 10 0 0 1 -10 10 h-86 a10 10 0 0 1 -10 -10 v-52 a10 10 0 0 1 10 -10 Z\"\n        className=\"fill-success/10\"\n      />\n      <g className=\"fill-none stroke-card\" strokeLinecap=\"round\">\n        <path\n          d=\"M-10 60 H650 M-10 182 H650 M-10 248 H650 M40 -10 V410 M210 -10 V410 M350 -10 V410 M560 -10 V410\"\n          strokeWidth=\"5\"\n        />\n        <path\n          d=\"M-10 120 H650 M120 -10 V410 M280 -10 V410 M460 -10 V410 M-10 360 H650\"\n          strokeWidth=\"2.5\"\n        />\n        <path d=\"M-20 400 L 300 140 L 660 -20\" strokeWidth=\"9\" />\n      </g>\n      <path\n        d=\"M-20 400 L 300 140 L 660 -20\"\n        className=\"fill-none stroke-border-strong\"\n        strokeWidth=\"1\"\n        strokeDasharray=\"2 6\"\n      />\n    </svg>\n  );\n}\n\nconst overlayPositions: Record<MapFramePosition, string> = {\n  \"top-left\": \"top-0 left-0 items-start\",\n  top: \"top-0 left-1/2 -translate-x-1/2 items-center\",\n  \"top-right\": \"top-0 right-0 items-end\",\n  \"bottom-left\": \"bottom-0 left-0 items-start\",\n  bottom: \"bottom-0 left-1/2 -translate-x-1/2 items-center\",\n  \"bottom-right\": \"right-0 bottom-0 items-end\",\n};\n\n/**\n * Map controls placed in an overlay float above the tiles: a translucent popover surface with a\n * strong ring and a soft drop shadow, 28px buttons, and a hairline between grouped buttons.\n */\nconst floatingControls =\n  \"[&_[data-slot=map-controls]:not([data-variant=bar])>[data-slot=map-controls-group]]:gap-0.5 [&_[data-slot=map-controls]:not([data-variant=bar])>[data-slot=map-controls-group]]:overflow-visible [&_[data-slot=map-controls]:not([data-variant=bar])>[data-slot=map-controls-group]]:rounded-xl [&_[data-slot=map-controls]:not([data-variant=bar])>[data-slot=map-controls-group]]:bg-[color-mix(in_oklab,var(--popover)_92%,transparent)] [&_[data-slot=map-controls]:not([data-variant=bar])>[data-slot=map-controls-group]]:p-[3px] [&_[data-slot=map-controls]:not([data-variant=bar])>[data-slot=map-controls-group]]:shadow-[0_0_0_1px_var(--border-strong),0_10px_28px_oklch(0_0_0/0.12)] [&_[data-slot=map-controls]:not([data-variant=bar])>[data-slot=map-controls-group]]:backdrop-blur-[12px] dark:[&_[data-slot=map-controls]:not([data-variant=bar])>[data-slot=map-controls-group]]:shadow-[0_0_0_1px_var(--border-strong),0_10px_28px_oklch(0_0_0/0.42)] [&_[data-slot=map-controls][data-variant=stacked]_[data-slot=map-controls-button]]:size-7 [&_[data-slot=map-controls]:not([data-variant=bar])_[data-slot=map-controls-button]]:rounded-lg [&_[data-slot=map-controls]:not([data-variant=bar])_[data-slot=map-controls-button]:hover]:bg-accent [&_[data-slot=map-controls]:not([data-variant=bar])_[data-slot=map-controls-button]+[data-slot=map-controls-button]]:relative [&_[data-slot=map-controls]:not([data-variant=bar])_[data-slot=map-controls-button]+[data-slot=map-controls-button]]:before:pointer-events-none [&_[data-slot=map-controls]:not([data-variant=bar])_[data-slot=map-controls-button]+[data-slot=map-controls-button]]:before:absolute [&_[data-slot=map-controls]:not([data-variant=bar])_[data-slot=map-controls-button]+[data-slot=map-controls-button]]:before:inset-x-1 [&_[data-slot=map-controls]:not([data-variant=bar])_[data-slot=map-controls-button]+[data-slot=map-controls-button]]:before:-top-[1.5px] [&_[data-slot=map-controls]:not([data-variant=bar])_[data-slot=map-controls-button]+[data-slot=map-controls-button]]:before:h-px [&_[data-slot=map-controls]:not([data-variant=bar])_[data-slot=map-controls-button]+[data-slot=map-controls-button]]:before:bg-border\";\n\nexport type MapFrameOverlayProps = ComponentProps<\"div\"> & { position?: MapFramePosition };\n\n/** Pins controls, legends, or cards to one edge of the frame. */\nexport function MapFrameOverlay({\n  position = \"top-right\",\n  className,\n  ...props\n}: MapFrameOverlayProps) {\n  const context = useFrame(\"MapFrameOverlay\");\n  return (\n    <div\n      data-slot=\"map-frame-overlay\"\n      data-position={position}\n      className={cn(\n        \"pointer-events-none absolute z-10 flex max-w-full flex-col gap-2 [&>*]:pointer-events-auto\",\n        floatingControls,\n        context.variant === \"compact\" ? \"p-2\" : \"p-3\",\n        overlayPositions[position],\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\n/** Data and tile credits. Required by most map providers. */\nexport function MapFrameAttribution({ className, ...props }: ComponentProps<\"p\">) {\n  const context = useFrame(\"MapFrameAttribution\");\n  return (\n    <p\n      data-slot=\"map-frame-attribution\"\n      className={cn(\n        \"absolute z-10 m-0 h-5 max-w-[calc(100%-1rem)] truncate rounded-full bg-[color-mix(in_oklab,var(--popover)_94%,transparent)] px-2 text-[10.5px]/5 text-muted-foreground shadow-[0_0_0_1px_var(--border)] backdrop-blur-sm [&_a]:text-muted-foreground [&_a]:underline-offset-2 [&_a:hover]:text-foreground [&_a:hover]:underline\",\n        context.variant === \"compact\" ? \"right-1.5 bottom-1.5\" : \"right-2 bottom-2\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/uai/map-frame.tsx"
    }
  ],
  "categories": [
    "maps"
  ],
  "type": "registry:ui"
}