{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "link",
  "title": "Link",
  "description": "An inline link on the M3 type and color roles.",
  "dependencies": [
    "@base-ui/react@^1.7.0",
    "class-variance-authority@^0.7.1"
  ],
  "registryDependencies": [
    "https://guillermo-rebolledo.github.io/materialcn/r/materialcn-theme.json",
    "https://guillermo-rebolledo.github.io/materialcn/r/utils.json"
  ],
  "files": [
    {
      "path": "src/components/ui/link.tsx",
      "content": "import { mergeProps } from \"@base-ui/react/merge-props\"\nimport { useRender } from \"@base-ui/react/use-render\"\nimport { type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { linkVariants } from \"./link-variants\"\n\ntype LinkProps = useRender.ComponentProps<\"a\"> &\n  VariantProps<typeof linkVariants> & {\n    /** Where it goes. Its presence is what makes this a link rather than a button. */\n    href?: string\n    /** Visually clear and genuinely non-interactive — see the note below. */\n    disabled?: boolean\n  }\n\n/**\n * A link inside a paragraph.\n *\n * The role comes from the props, not from a prop that names it: something with\n * a destination announces as a link, something with only an action announces as\n * a button. Getting that backwards is the most common accessibility defect in a\n * component like this one — a \"link\" that runs a callback tells a screen-reader\n * user that a new page is coming, and nothing arrives.\n *\n * A disabled link renders as a `span`. Removing `href` from an anchor would\n * leave it in the tab order with no role; `aria-disabled` alone would leave it\n * still activating. The span carries `role=\"link\"` so the announcement stays\n * accurate about what it *would* be.\n *\n * An icon-only link is not a supported shape. Without a label there is nothing\n * for the underline to sit under and nothing to click but the glyph — that is\n * an icon button, which is what `Button` is for.\n */\nfunction Link({\n  className,\n  color,\n  disabled = false,\n  href,\n  render,\n  ...props\n}: LinkProps) {\n  const isAction = href === undefined\n\n  return useRender({\n    defaultTagName: disabled ? \"span\" : isAction ? \"button\" : \"a\",\n    props: mergeProps<\"a\">(\n      {\n        className: cn(linkVariants({ color }), className),\n        ...(disabled\n          ? { \"aria-disabled\": true, role: isAction ? \"button\" : \"link\" }\n          : { href }),\n        // A `button` defaults to type=\"submit\", which turns an in-form link\n        // into an accidental submit.\n        ...(isAction && !disabled ? { type: \"button\" } : {}),\n      } as useRender.ElementProps<\"a\">,\n      props\n    ),\n    render,\n    state: {\n      slot: \"link\",\n      color: color ?? \"inherit\",\n      disabled,\n    },\n  })\n}\n\nexport { Link, linkVariants }\nexport type { LinkProps }\n",
      "type": "registry:ui"
    },
    {
      "path": "src/components/ui/link-variants.ts",
      "content": "/**\n * Variant definitions live beside the component rather than inside it — see\n * badge-variants.ts for why (React Fast Refresh boundaries).\n */\n\nimport { cva } from \"class-variance-authority\"\n\n/**\n * A link is `display: inline`, and that is load-bearing rather than incidental.\n *\n * The obvious way to align an optional icon is `inline-flex`, which turns the\n * link into a single unbreakable box: set one mid-paragraph and the whole link\n * jumps to the next line, leaving a ragged hole above it. Staying inline lets\n * the link fragment across lines the way the text around it does, and the rule\n * below aligns any nested `Icon` with `vertical-align` instead — which costs\n * nothing and needs no prop, since an icon in a link is just a child.\n *\n * `decoration-from-font` takes the underline position and thickness from the\n * font's own metrics rather than the browser's guess, and `[text-underline-position:from-font]`\n * keeps descenders from being struck through.\n */\nexport const linkVariants = cva(\n  [\n    \"inline cursor-pointer rounded-m3-xs underline decoration-from-font [text-underline-position:from-font]\",\n    // An optional icon beside the label, aligned optically to the text rather\n    // than sitting on the baseline. `vertical-align` and not flex, because the\n    // link has to stay inline to wrap.\n    \"[&>[data-slot=icon]]:mx-[0.15em] [&>[data-slot=icon]]:align-[-0.15em]\",\n    \"underline-offset-2 transition-colors duration-(--m3-spring-effects-fast-duration) ease-(--m3-spring-effects-fast)\",\n    \"focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-m3-secondary focus-visible:no-underline\",\n    // `aria-disabled` rather than `:disabled`, because the anchor case has no\n    // disabled attribute to hang it on and the two must look identical.\n    \"aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:text-m3-on-surface/38 aria-disabled:no-underline\",\n  ].join(\" \"),\n  {\n    variants: {\n      /**\n       * `inherit` is the default and the one that matters: a link in a\n       * paragraph should be the paragraph's font, size, weight, and colour,\n       * distinguished by the underline alone. The named roles are for the\n       * cases that genuinely need to stand apart — a standalone call to\n       * action, a link inside an error message.\n       */\n      color: {\n        inherit: \"text-inherit hover:text-m3-primary\",\n        primary: \"text-m3-primary hover:text-m3-primary/80\",\n        error: \"text-m3-error hover:text-m3-error/80\",\n      },\n    },\n    defaultVariants: {\n      color: \"inherit\",\n    },\n  }\n)\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "navigation"
  ],
  "type": "registry:ui"
}