{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "selection-halo",
  "title": "Selection Halo",
  "description": "Local-user selection halo with corner handles + label slot for spatial canvases.",
  "dependencies": [
    "@vllnt/ui@^0.3.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/selection-halo/selection-halo.tsx",
      "content": "\"use client\";\n\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\n\nimport { cn } from \"@vllnt/ui\";\n\n/**\n * Geometric bounds of the selection in container px.\n *\n * @public\n */\nexport type SelectionBounds = {\n  height: number;\n  width: number;\n  x: number;\n  y: number;\n};\n\n/**\n * Localizable strings.\n *\n * @public\n */\nexport type SelectionHaloLabels = {\n  /** Aria-label for the halo. Defaults to `\"Selection\"`. */\n  region?: string;\n};\n\nconst DEFAULT_LABELS = {\n  region: \"Selection\",\n} as const satisfies Required<SelectionHaloLabels>;\n\n/**\n * Props for {@link SelectionHalo}.\n *\n * @public\n */\nexport type SelectionHaloProps = {\n  /** Selection bounds in container pixels. */\n  bounds: SelectionBounds;\n  /** Optional label rendered above the top-left corner. */\n  label?: ReactNode;\n  /** Localizable strings. */\n  labels?: SelectionHaloLabels;\n  /** When `true`, the ring pulses (use during multi-step transitions). */\n  pulsing?: boolean;\n} & Omit<ComponentPropsWithoutRef<\"div\">, \"style\">;\n\n/**\n * Local-user selection halo for a canvas. Outlines a rectangular\n * region with a primary ring + handles at every corner. Pure\n * presentation — the host computes bounds (single object, group\n * bounding box, lasso result) and toggles the wrapper.\n *\n * Distinct from {@link SelectionPresence}: this halo represents the\n * **local** user's selection (with corner handles + label slot), while\n * `SelectionPresence` represents a remote participant's selection.\n *\n * @example\n * ```tsx\n * <SelectionHalo bounds={{ x: 80, y: 60, width: 200, height: 120 }} label=\"3 selected\" />\n * ```\n *\n * @public\n */\nexport const SelectionHalo = ({\n  ref,\n  ...props\n}: SelectionHaloProps & { ref?: React.Ref<HTMLDivElement> }) => {\n  const { bounds, className, label, labels, pulsing = false, ...rest } = props;\n  const resolvedLabels = { ...DEFAULT_LABELS, ...labels };\n  return (\n    <div\n      aria-label={resolvedLabels.region}\n      className={cn(\n        \"pointer-events-none absolute z-20 rounded-md ring-2 ring-primary\",\n        pulsing ? \"animate-pulse\" : \"\",\n        className,\n      )}\n      data-pulsing={pulsing ? \"true\" : undefined}\n      data-selection-halo\n      ref={ref}\n      style={{\n        height: `${bounds.height.toString()}px`,\n        left: `${bounds.x.toString()}px`,\n        top: `${bounds.y.toString()}px`,\n        width: `${bounds.width.toString()}px`,\n      }}\n      {...rest}\n    >\n      {([\"nw\", \"ne\", \"se\", \"sw\"] as const).map((corner) => (\n        <span\n          aria-hidden=\"true\"\n          className={cn(\n            \"absolute size-2 rounded-sm border-2 border-primary bg-background\",\n            corner === \"nw\" && \"-left-1 -top-1\",\n            corner === \"ne\" && \"-right-1 -top-1\",\n            corner === \"se\" && \"-bottom-1 -right-1\",\n            corner === \"sw\" && \"-bottom-1 -left-1\",\n          )}\n          data-handle-corner={corner}\n          key={corner}\n        />\n      ))}\n      {label ? (\n        <span\n          className=\"absolute -top-6 left-0 inline-flex items-center rounded-md bg-primary px-1.5 py-0.5 text-[10px] font-semibold text-primary-foreground shadow-sm\"\n          data-selection-label\n        >\n          {label}\n        </span>\n      ) : null}\n    </div>\n  );\n};\nSelectionHalo.displayName = \"SelectionHalo\";\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component",
  "version": "0.3.0",
  "stability": "stable"
}
