{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "link",
  "type": "registry:ui",
  "title": "Link",
  "description": "Token-backed native Link component for navigation and router composition.",
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://components.dethink.co.uk/r/dethink-base.json"
  ],
  "files": [
    {
      "path": "packages/components/src/components/link/link.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/link/link.tsx",
      "content": "import {\n  Children,\n  cloneElement,\n  forwardRef,\n  isValidElement,\n  type AnchorHTMLAttributes,\n  type ForwardedRef,\n  type MouseEventHandler,\n  type ReactElement,\n  type ReactNode,\n  type Ref,\n} from \"react\";\nimport { cn } from \"../../utils/cn\";\n\nexport type LinkVariant = \"default\" | \"muted\" | \"nav\" | \"destructive\";\nexport type LinkUnderline = \"hover\" | \"always\" | \"none\";\n\ntype LinkBaseProps = {\n  asChild?: boolean;\n  variant?: LinkVariant;\n  underline?: LinkUnderline;\n};\n\ntype NativeLinkProps = Omit<\n  AnchorHTMLAttributes<HTMLAnchorElement>,\n  \"children\" | \"href\"\n> &\n  LinkBaseProps & {\n    asChild?: false;\n    href: string;\n    children: ReactNode;\n  };\n\ntype ChildLinkProps = Omit<\n  AnchorHTMLAttributes<HTMLAnchorElement>,\n  \"children\" | \"href\"\n> &\n  LinkBaseProps & {\n    asChild: true;\n    href?: string;\n    children: ReactElement<LinkSlotProps>;\n  };\n\nexport type LinkProps = NativeLinkProps | ChildLinkProps;\n\ntype LinkSlotProps = Record<string, unknown> & {\n  children?: ReactNode;\n  className?: string;\n  href?: string;\n  onClick?: MouseEventHandler<HTMLElement>;\n  ref?: Ref<HTMLElement>;\n  rel?: string;\n  target?: AnchorHTMLAttributes<HTMLAnchorElement>[\"target\"];\n  \"aria-current\"?: AnchorHTMLAttributes<HTMLAnchorElement>[\"aria-current\"];\n};\n\nconst linkBaseClasses =\n  \"inline rounded-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\";\n\nconst linkVariantClasses: Record<LinkVariant, string> = {\n  default: \"text-primary hover:text-primary/80 active:text-primary/70\",\n  muted: \"text-muted-foreground hover:text-foreground active:text-foreground\",\n  nav: \"text-muted-foreground hover:text-foreground active:text-foreground data-[current=true]:text-foreground data-[current=true]:font-semibold\",\n  destructive:\n    \"text-destructive hover:text-destructive/85 active:text-destructive/75\",\n};\n\nconst linkUnderlineClasses: Record<LinkUnderline, string> = {\n  hover: \"no-underline hover:underline focus-visible:underline\",\n  always: \"underline\",\n  none: \"no-underline\",\n};\n\nexport function linkClassNames({\n  variant = \"default\",\n  underline = \"hover\",\n  className,\n}: Pick<LinkProps, \"variant\" | \"underline\" | \"className\"> = {}) {\n  return cn(\n    linkBaseClasses,\n    linkVariantClasses[variant],\n    linkUnderlineClasses[underline],\n    className,\n  );\n}\n\nfunction setRef<T>(ref: Ref<T> | undefined, node: T | null) {\n  if (typeof ref === \"function\") {\n    ref(node);\n    return;\n  }\n\n  if (ref) {\n    ref.current = node;\n  }\n}\n\nfunction composeRefs<T>(...refs: Array<Ref<T> | undefined>) {\n  return (node: T | null) => {\n    for (const ref of refs) {\n      setRef(ref, node);\n    }\n  };\n}\n\nfunction composeClickHandlers(\n  componentHandler: MouseEventHandler<HTMLElement> | undefined,\n  childHandler: MouseEventHandler<HTMLElement> | undefined,\n) {\n  if (!componentHandler && !childHandler) {\n    return undefined;\n  }\n\n  return (event: React.MouseEvent<HTMLElement>) => {\n    componentHandler?.(event);\n\n    if (!event.defaultPrevented) {\n      childHandler?.(event);\n    }\n  };\n}\n\nfunction getChildRef(child: ReactElement<LinkSlotProps>) {\n  return child.props.ref;\n}\n\nfunction mergeRelForTarget(\n  rel: string | undefined,\n  target: AnchorHTMLAttributes<HTMLAnchorElement>[\"target\"] | undefined,\n) {\n  if (target !== \"_blank\") {\n    return rel;\n  }\n\n  const tokens = new Set((rel ?? \"\").split(/\\s+/).filter(Boolean));\n  tokens.add(\"noopener\");\n\n  return Array.from(tokens).join(\" \");\n}\n\nfunction hasCurrentState(\n  ariaCurrent: AnchorHTMLAttributes<HTMLAnchorElement>[\"aria-current\"],\n) {\n  return (\n    ariaCurrent !== undefined &&\n    ariaCurrent !== false &&\n    ariaCurrent !== \"false\"\n  );\n}\n\nexport const Link = forwardRef<HTMLAnchorElement, LinkProps>(\n  (\n    {\n      asChild = false,\n      children,\n      className,\n      href,\n      onClick,\n      rel,\n      target,\n      underline = \"hover\",\n      variant = \"default\",\n      \"aria-current\": ariaCurrent,\n      ...props\n    },\n    ref,\n  ) => {\n    const classes = linkClassNames({ variant, underline, className });\n\n    if (asChild) {\n      const child = Children.only(children);\n\n      if (!isValidElement<LinkSlotProps>(child)) {\n        throw new Error(\n          \"Link with asChild expects a single React element child.\",\n        );\n      }\n\n      const childRef = getChildRef(child);\n      const resolvedHref = child.props.href ?? href;\n      const resolvedTarget = child.props.target ?? target;\n      const resolvedRel = mergeRelForTarget(\n        child.props.rel ?? rel,\n        resolvedTarget,\n      );\n      const resolvedAriaCurrent = child.props[\"aria-current\"] ?? ariaCurrent;\n      const isCurrent = hasCurrentState(resolvedAriaCurrent);\n      const clonedProps: LinkSlotProps = {\n        ...props,\n        ...child.props,\n        ref: composeRefs(ref as Ref<HTMLElement>, childRef),\n        \"data-slot\": \"link\",\n        \"data-variant\": variant,\n        \"data-underline\": underline,\n        \"data-current\": isCurrent ? \"true\" : undefined,\n        className: cn(classes, child.props.className),\n        onClick: composeClickHandlers(\n          onClick as MouseEventHandler<HTMLElement> | undefined,\n          child.props.onClick,\n        ),\n      };\n\n      if (resolvedHref !== undefined) {\n        clonedProps.href = resolvedHref;\n      }\n\n      if (resolvedTarget !== undefined) {\n        clonedProps.target = resolvedTarget;\n      }\n\n      if (resolvedRel !== undefined) {\n        clonedProps.rel = resolvedRel;\n      }\n\n      if (resolvedAriaCurrent !== undefined) {\n        clonedProps[\"aria-current\"] = resolvedAriaCurrent;\n      }\n\n      return cloneElement(child, clonedProps);\n    }\n\n    const isCurrent = hasCurrentState(ariaCurrent);\n\n    return (\n      <a\n        {...props}\n        ref={ref as ForwardedRef<HTMLAnchorElement>}\n        href={href}\n        rel={mergeRelForTarget(rel, target)}\n        target={target}\n        aria-current={ariaCurrent}\n        data-slot=\"link\"\n        data-variant={variant}\n        data-underline={underline}\n        data-current={isCurrent ? \"true\" : undefined}\n        className={classes}\n        onClick={onClick}\n      >\n        {children}\n      </a>\n    );\n  },\n);\n\nLink.displayName = \"Link\";\n"
    },
    {
      "path": "packages/components/src/components/link/index.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/link/index.ts",
      "content": "export {\n  Link,\n  linkClassNames,\n  type LinkProps,\n  type LinkUnderline,\n  type LinkVariant,\n} from \"./link\";\n"
    },
    {
      "path": "packages/components/src/utils/cn.ts",
      "type": "registry:file",
      "target": "~/components/dethink/utils/cn.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"
    }
  ]
}
