{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "link",
  "type": "registry:ui",
  "title": "Link",
  "description": "Fluent 2-styled link with shadcn APIs. Brand-colored text with a hover-only underline for standalone use or a persistent underline for links inline in prose, aria-disabled support, and asChild for wrapping framework router links.",
  "author": "graundtech <https://github.com/graundtech/fluent2-react-kit>",
  "dependencies": [
    "class-variance-authority",
    "@radix-ui/react-slot"
  ],
  "registryDependencies": [
    "https://fluent2-react-kit.graund.io/r/utils.json"
  ],
  "files": [
    {
      "path": "components/ui/link.tsx",
      "type": "registry:ui",
      "target": "components/ui/link.tsx",
      "content": "import { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Link — Fluent 2-styled, shadcn-API link.\n *\n * shadcn has no Link primitive; this is a Fluent-driven addition that follows\n * the same authoring pattern as `Button` (cva + `asChild` + `data-slot`) so it\n * reads as part of the same system. The color ramp matches Fluent's\n * `BrandForegroundLink` states exactly — one brand-ramp step darker than an\n * earlier (off-by-one-light) version: rest `brand-70` (`#115ea3`), hover\n * `brand-60` (`#0f548c`), pressed `brand-50` (`#0c3b5e`) in light; rest\n * `brand-100`, hover `brand-110`, pressed `brand-120` in dark. Weight is\n * `font-normal` (Fluent Link is Body 1 / Regular 400, not the medium 500 an\n * earlier version used). Button's own `link` variant carries the same color\n * ramp (kept in sync) but keeps the button's `font-semibold` weight.\n *\n * Two variants, mapping directly to the two Fluent 2 link usages\n * (https://storybooks.fluentui.dev/react/?path=/docs/components-link--docs):\n * - `default` — a standalone link (nav, button-like, card action). No\n *   underline at rest; underline appears on hover only, matching Button's\n *   `link` variant so the two read identically.\n * - `inline` — a link inside a sentence of body text. Fluent requires a\n *   persistent underline here so the link is distinguishable from surrounding\n *   text by shape, not color alone (this also keeps it usable for anyone who\n *   can't perceive the brand-color contrast against body text).\n *\n * `asChild` (Radix Slot) exists specifically so this component can wrap a\n * framework router's own link and still carry Fluent's visual treatment:\n *\n *   <Link asChild>\n *     <NextLink href=\"/dashboard\">Dashboard</NextLink>\n *   </Link>\n *\n * Disabled: anchors have no native `disabled` attribute, so this component\n * does not invent one (that would break the plain DOM-attribute contract\n * `ComponentProps<\"a\">` gives consumers). Instead it styles `aria-disabled`.\n * Two things consumers must still do themselves, because neither can be\n * enforced from inside the component:\n * - Omit `href` on a disabled link where possible — an anchor without `href`\n *   is not in the tab order and is not exposed as a link by assistive tech,\n *   which is the most robust \"disabled\" for a link.\n * - If `href` must stay (e.g. to preserve layout/routing), pair\n *   `aria-disabled=\"true\"` with these classes: `pointer-events-none` blocks\n *   pointer activation and `no-underline`/`opacity-50` give a visible cue.\n *   This does **not** remove the link from the tab order or stop Enter-key\n *   activation on its own — add `tabIndex={-1}` too if full keyboard removal\n *   is required.\n *\n * Server-safe: no `\"use client\"`, no hooks/handlers — the React import is\n * type-only, so the file can be dropped straight into an RSC tree.\n */\nconst linkVariants = cva(\n  [\n    // shape + type — Fluent Link is Body 1 / Regular 400\n    \"rounded-xs font-normal text-brand-70 underline-offset-4 dark:text-brand-100\",\n    // motion — token duration + Fluent easyEase curve; honors prefers-reduced-motion\n    // automatically (the duration tokens collapse to 0.01ms in tokens.css).\n    \"outline-none transition-colors duration-fast ease-ease\",\n    // interactive state ramp — Fluent BrandForegroundLink, one brand step\n    // darker than the color set above, kept in sync with Button's `link`\n    // variant. rest brand-70 → hover brand-60 → pressed brand-50 (light);\n    // rest brand-100 → hover brand-110 → pressed brand-120 (dark). The brand\n    // ramp is global, so the dark steps are declared explicitly (conventions §4).\n    \"hover:text-brand-60 hover:underline active:text-brand-50 dark:hover:text-brand-110 dark:active:text-brand-120\",\n    // focus — Fluent double-stroke approximation: 2px brand ring + 2px surface-colored gap\n    \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n    // disabled — anchors have no native `disabled`; styled off `aria-disabled` instead.\n    // Opacity-based to match the rest of the kit's disabled treatment (see conventions\n    // doc §4); `no-underline` also cancels the `inline` variant's persistent underline.\n    \"aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-disabled:no-underline\",\n  ],\n  {\n    variants: {\n      variant: {\n        // Standalone link — no underline at rest, only on hover.\n        default: \"\",\n        // Inline-in-prose link — Fluent requires an always-on underline so the\n        // link reads as a link independent of its brand-color contrast.\n        inline: \"underline\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n);\n\nfunction Link({\n  className,\n  variant = \"default\",\n  asChild = false,\n  ...props\n}: ComponentProps<\"a\"> &\n  VariantProps<typeof linkVariants> & {\n    asChild?: boolean;\n  }) {\n  const Comp = asChild ? Slot : \"a\";\n\n  return (\n    <Comp\n      data-slot=\"link\"\n      data-variant={variant}\n      className={cn(linkVariants({ variant, className }))}\n      {...props}\n    />\n  );\n}\n\nexport { Link, linkVariants };\n"
    }
  ]
}
