{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "locked-code",
  "title": "Locked code",
  "description": "A locked Pro file: a blurred stub, an unlock link with the price, the facts, and the CLI and MCP commands to copy once you have a key.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "@manniche/badge",
    "@manniche/copy-button"
  ],
  "files": [
    {
      "path": "registry/manniche/locked-code/locked-code.tsx",
      "content": "// LockedCode: the code view of a paid component before it is unlocked. A file header with a lock and the tier badge,\n// a stub of code blurred behind a lock panel (heading, a line of text, the unlock link, a price note and a few facts),\n// and under it a row for people who already have a key: the CLI and MCP commands, each with a copy button.\n//\n// The stub is decoration. It is hidden from screen readers and blurred with `filter`, but it still ships in the page's\n// HTML, so pass placeholder lines that look like code, never the real source.\n//\n// The key slot above the heading lights up while the unlock link is hovered or has keyboard focus, so the panel\n// answers before the click. Under reduced motion it lights at once instead of fading.\n//\n// Every part carries a `data-slot` (`locked-code`, `locked-code-unlock`, `locked-code-light`, …), the shadcn way to\n// style a part from outside without new props.\nimport { Lock } from 'lucide-react'\nimport { Fragment, useId, type MouseEvent, type ReactNode } from 'react'\nimport { cn } from '@/lib/utils'\nimport { Badge } from '@/registry/manniche/badge/badge'\nimport { CopyButton } from '@/registry/manniche/copy-button/copy-button'\n\nexport type LockedCodeCommand = {\n  /** A short name shown before the command, such as \"CLI\" or \"MCP\". */\n  label: string\n  /** The whole command, exactly as it lands on the clipboard. */\n  value: string\n  /** The start of `value` to draw fainter, such as `npx shadcn@latest add`. It must be how `value` begins. */\n  prefix?: string\n}\n\nexport type LockedCodeLabels = {\n  /** The tier badge in the header. Default \"Pro\". */\n  pro?: string\n  /** The panel's heading. Default \"The code is locked\". */\n  heading?: string\n  /** The line under the heading; `{name}` becomes `name`. Default \"{name} is part of Pro. A key opens its source code.\" */\n  body?: string\n  /** The unlock link, before the price. Default \"Unlock\". */\n  unlock?: string\n  /** The lead of the key row, set in bold. Default \"Have a key?\". */\n  haveKey?: string\n  /** The rest of that line. Default \"Get the code with the CLI or MCP.\". */\n  haveKeyText?: string\n  /** Where the key is kept; `{env}` becomes `envVar` set as code. Default \"The key lives in {env}, never in the code or the URL.\" */\n  keyNote?: string\n  /** The link to `setupHref`. Default \"How to set it up\". */\n  setup?: string\n  /** Names each command for screen readers; `{label}` becomes its label. Default \"{label} command\". */\n  command?: string\n  /** The copy buttons. Defaults \"Copy\", \"Copied\" and \"Not copied\". */\n  copy?: string\n  copied?: string\n  copyFailed?: string\n}\n\nexport type LockedCodeProps = {\n  /** The component's name, such as \"flux-image\". Used in the text and, with \".tsx\", as the file name. */\n  name: string\n  /** The file name in the header. Default `${name}.tsx`. */\n  filename?: string\n  /**\n   * Placeholder lines drawn blurred behind the lock. They ship in the page's HTML, so never pass the real source:\n   * a few generic lines (imports, a props type, an empty function) are enough to read as code.\n   */\n  stub: string\n  /** Where the unlock link goes, such as the pricing section. */\n  unlockHref: string\n  /** Called when the unlock link is clicked, for opening a checkout in place. Call `preventDefault()` to stay. */\n  onUnlock?: (event: MouseEvent<HTMLAnchorElement>) => void\n  /** The price after the unlock label, such as \"€49\". Leave it out for a plain \"Unlock\". */\n  price?: string\n  /** One line under the link about the price, such as an introductory offer and when it ends. */\n  priceNote?: ReactNode\n  /** Short facts in a row under the price, such as \"One-time payment\". */\n  facts?: string[]\n  /** The commands that fetch the code with a key, each with a copy button. Leave it empty to drop the key row. */\n  commands?: LockedCodeCommand[]\n  /** The environment variable the key lives in, shown in the key row. */\n  envVar?: string\n  /** A page that explains how to set the key up, linked from the key row. */\n  setupHref?: string\n  /** Words for other languages. */\n  labels?: LockedCodeLabels\n  /** The heading level of \"The code is locked\", to fit the page outline. Default 3. */\n  headingLevel?: 2 | 3 | 4 | 5 | 6\n  className?: string\n}\n\n// Replaces {key} in a label with a value, keeping the rest of the text as it is.\nfunction fill(template: string, values: Record<string, ReactNode>): ReactNode {\n  return template.split(/(\\{\\w+\\})/).map((part, i) => {\n    const key = /^\\{(\\w+)\\}$/.exec(part)?.[1]\n    return <Fragment key={i}>{key && key in values ? values[key] : part}</Fragment>\n  })\n}\nconst fillText = (template: string, values: Record<string, string>) =>\n  template.replace(/\\{(\\w+)\\}/g, (m, key: string) => values[key] ?? m)\n\n// A long command breaks between words first, then after a slash. Each piece is kept whole (so flux-image or\n// --header never splits at its hyphen) unless it is wider than the line on its own.\nconst breakable = (s: string) =>\n  s.split(/( +)/).map((word, w) =>\n    word.trim() ? (\n      <Fragment key={w}>\n        {word.split(/(?<=\\/)(?!\\/)/).map((part, i) => (\n          <span key={i} className=\"inline-block max-w-full\">\n            {part}\n          </span>\n        ))}\n      </Fragment>\n    ) : (\n      word\n    ),\n  )\n\n// A recess in the surface: darker than the card, lit from above.\nconst WELL =\n  'bg-background shadow-[inset_0_1px_2px_rgb(0_0_0/0.08),inset_0_0_0_1px_color-mix(in_oklab,var(--foreground)_7%,transparent)] dark:shadow-[inset_0_1px_2px_rgb(0_0_0/0.55),inset_0_0_0_1px_color-mix(in_oklab,var(--foreground)_6%,transparent)]'\nconst PIT =\n  'bg-background shadow-[inset_0_2px_4px_rgb(0_0_0/0.14),inset_0_0_0_1px_color-mix(in_oklab,var(--foreground)_9%,transparent)] dark:shadow-[inset_0_2px_4px_rgb(0_0_0/0.7),inset_0_0_0_1px_color-mix(in_oklab,var(--foreground)_8%,transparent)]'\n\n/** The locked code view for a paid component: a blurred stub behind a lock panel, and the commands for key holders. */\nexport function LockedCode({\n  name,\n  filename,\n  stub,\n  unlockHref,\n  onUnlock,\n  price,\n  priceNote,\n  facts = [],\n  commands = [],\n  envVar,\n  setupHref,\n  labels = {},\n  headingLevel = 3,\n  className,\n}: LockedCodeProps) {\n  const {\n    pro = 'Pro',\n    heading = 'The code is locked',\n    body = '{name} is part of Pro. A key opens its source code.',\n    unlock = 'Unlock',\n    haveKey = 'Have a key?',\n    haveKeyText = 'Get the code with the CLI or MCP.',\n    keyNote = 'The key lives in {env}, never in the code or the URL.',\n    setup = 'How to set it up',\n    command = '{label} command',\n    copy = 'Copy',\n    copied = 'Copied',\n    copyFailed = 'Not copied',\n  } = labels\n  const headingId = useId()\n  const fileId = useId()\n  const Heading = `h${headingLevel}` as const\n  const lines = stub.replace(/\\n+$/, '').split('\\n')\n\n  return (\n    <section\n      data-slot=\"locked-code\"\n      aria-labelledby={`${fileId} ${headingId}`}\n      className={cn(\n        '@container min-w-0 overflow-hidden rounded-[calc(var(--radius)*2+2px)] bg-card text-card-foreground shadow-[0_0_0_1px_color-mix(in_oklab,var(--foreground)_9%,transparent),0_1px_2px_rgba(0,0,0,0.03)]',\n        className,\n      )}\n    >\n      <div\n        data-slot=\"locked-code-header\"\n        className=\"flex min-h-12 items-center gap-2.5 border-b border-border py-1 pr-2 pl-4 font-mono text-[12.5px] leading-none font-medium text-muted-foreground\"\n      >\n        <Lock className=\"size-3.5 flex-none\" strokeWidth={1.75} aria-hidden />\n        <span id={fileId} className=\"min-w-0 truncate text-foreground\">\n          {filename ?? `${name}.tsx`}\n        </span>\n        <Badge variant=\"pro\" className=\"font-sans\">\n          {pro}\n        </Badge>\n      </div>\n\n      <div className=\"relative\">\n        <pre\n          data-slot=\"locked-code-stub\"\n          aria-hidden\n          className=\"pointer-events-none absolute inset-0 m-0 overflow-hidden py-4 font-mono text-[13px] leading-6 text-foreground opacity-40 blur-[5px] select-none [mask-image:linear-gradient(#000_45%,transparent)]\"\n        >\n          {lines.map((line, i) => (\n            <span key={i} className=\"block min-h-6 pr-5 whitespace-pre\">\n              <span className=\"inline-block w-[52px] pr-[18px] text-right text-muted-foreground\">{i + 1}</span>\n              {line}\n            </span>\n          ))}\n        </pre>\n\n        <div\n          data-slot=\"locked-code-panel\"\n          className=\"group/lock relative px-4 py-[22px] text-center @min-[560px]:px-6 @min-[560px]:pt-[26px] @min-[560px]:pb-6\"\n        >\n          {/* The key slot: a groove that lights while the unlock link is pointed at or focused. */}\n          <div aria-hidden className={cn('relative mx-auto mb-4 h-5 w-[132px] rounded-[10px]', PIT)}>\n            <i className=\"absolute top-[9px] right-[18px] left-[18px] h-0.5 rounded-full bg-foreground/20 dark:bg-black\" />\n            <i\n              data-slot=\"locked-code-light\"\n              className=\"absolute top-[9px] right-[18px] left-[18px] h-0.5 rounded-full bg-primary opacity-0 transition-opacity duration-150 ease-out-quint group-has-[a:focus-visible]/lock:opacity-100 motion-reduce:transition-none [@media(hover:hover)]:group-has-[a:hover]/lock:opacity-100\"\n            />\n            <i className={cn('absolute top-[3px] -right-[26px] size-3.5 rounded-full', PIT)} />\n          </div>\n\n          <Heading\n            id={headingId}\n            className=\"font-serif text-[30px] leading-[1.1] font-normal tracking-[-0.02em] text-balance\"\n          >\n            {heading}\n          </Heading>\n          <p className=\"mx-auto mt-2 max-w-[52ch] text-base leading-[1.55] text-pretty text-muted-foreground\">\n            {fill(body, { name })}\n          </p>\n\n          <a\n            data-slot=\"locked-code-unlock\"\n            href={unlockHref}\n            onClick={onUnlock}\n            className=\"mt-[18px] inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-full bg-foreground px-[18px] text-[14.5px] leading-none font-medium whitespace-nowrap text-background shadow-[inset_0_1px_0_color-mix(in_oklab,var(--background)_35%,transparent),inset_0_-2px_0_rgb(0_0_0/0.2)] transition-transform duration-150 ease-out-quint outline-none select-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card active:scale-[0.97] motion-reduce:transition-none @min-[560px]:w-auto @min-[560px]:min-w-[220px]\"\n          >\n            {unlock}\n            {price && (\n              <>\n                {' '}\n                <span aria-hidden>·</span> {price}\n              </>\n            )}\n          </a>\n          {priceNote && <p className=\"mt-2.5 text-sm leading-normal text-pretty text-muted-foreground\">{priceNote}</p>}\n          {facts.length > 0 && (\n            <ul className=\"mx-auto mt-3.5 flex flex-col items-center gap-1.5 text-sm leading-normal text-muted-foreground @min-[560px]:flex-row @min-[560px]:flex-wrap @min-[560px]:justify-center\">\n              {facts.map((fact) => (\n                <li\n                  key={fact}\n                  className=\"@min-[560px]:border-l @min-[560px]:border-foreground/15 @min-[560px]:px-3 @min-[560px]:first:border-l-0\"\n                >\n                  {fact}\n                </li>\n              ))}\n            </ul>\n          )}\n        </div>\n      </div>\n\n      {commands.length > 0 && (\n        <div\n          data-slot=\"locked-code-keys\"\n          className=\"grid gap-x-8 gap-y-3 border-t border-border bg-muted/40 px-4 py-3.5 @min-[680px]:grid-cols-[minmax(220px,300px)_minmax(0,1fr)] @min-[680px]:items-center @min-[680px]:px-5\"\n        >\n          <div>\n            <p className=\"text-[15px] leading-[1.45] text-pretty text-muted-foreground\">\n              <strong className=\"font-medium text-foreground\">{haveKey}</strong> {haveKeyText}\n            </p>\n            {(envVar || setupHref) && (\n              <p className=\"mt-1.5 text-[13px] leading-normal text-pretty text-muted-foreground\">\n                {envVar &&\n                  fill(keyNote, {\n                    env: <code className=\"font-mono text-[12px] text-foreground/80\">{envVar}</code>,\n                  })}\n                {envVar && setupHref && ' '}\n                {setupHref && (\n                  <a\n                    href={setupHref}\n                    className=\"rounded-[4px] underline decoration-foreground/30 underline-offset-[3px] outline-none hover:text-foreground hover:decoration-foreground/60 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card\"\n                  >\n                    {setup}\n                  </a>\n                )}\n              </p>\n            )}\n          </div>\n          <div className=\"grid min-w-0 gap-2\">\n            {commands.map((c) => {\n              const lead = c.prefix && c.value.startsWith(c.prefix) ? c.prefix : ''\n              return (\n                <div\n                  key={c.label + c.value}\n                  role=\"group\"\n                  aria-label={fillText(command, { label: c.label })}\n                  data-slot=\"locked-code-command\"\n                  className={cn('flex min-w-0 items-center gap-1.5 rounded-[14px] py-1 pr-1 pl-3.5', WELL)}\n                >\n                  <span\n                    aria-hidden\n                    className=\"min-w-7 flex-none font-mono text-[11px] leading-none font-medium text-muted-foreground\"\n                  >\n                    {c.label}\n                  </span>\n                  <code className=\"min-w-0 flex-1 py-[5px] font-mono text-[12.5px] leading-[1.45] [overflow-wrap:anywhere] text-foreground\">\n                    {lead && <span className=\"text-muted-foreground\">{lead}</span>}\n                    {breakable(c.value.slice(lead.length))}\n                  </code>\n                  {/* The copy button's resting fill follows --primary; here it rests muted like a key cap, and\n                      keeps its green \"copied\" and red \"not copied\" states. */}\n                  <span className=\"inline-flex flex-none [--primary:var(--muted)] [--primary-foreground:var(--foreground)] hover:[--primary:color-mix(in_oklab,var(--foreground)_10%,var(--muted))]\">\n                    <CopyButton\n                      value={c.value}\n                      labels={{ copy, copied, failed: copyFailed }}\n                      className=\"rounded-[10px] px-3 text-[13px] shadow-[inset_0_1px_0_color-mix(in_oklab,var(--foreground)_8%,transparent),inset_0_-2px_0_rgb(0_0_0/0.12),0_0_0_1px_color-mix(in_oklab,var(--foreground)_9%,transparent)]\"\n                    />\n                  </span>\n                </div>\n              )\n            })}\n          </div>\n        </div>\n      )}\n    </section>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "meta": {
    "tier": "free",
    "added": "2026-10-07",
    "usedIn": [],
    "props": [
      {
        "name": "name",
        "type": "string",
        "default": null,
        "required": true,
        "description": "The component's name, such as \"flux-image\". Used in the text and, with \".tsx\", as the file name."
      },
      {
        "name": "filename",
        "type": "string",
        "default": null,
        "required": false,
        "description": "The file name in the header. Default `${name}.tsx`."
      },
      {
        "name": "stub",
        "type": "string",
        "default": null,
        "required": true,
        "description": "Placeholder lines drawn blurred behind the lock. They ship in the page's HTML, so never pass the real source: a few generic lines (imports, a props type, an empty function) are enough to read as code."
      },
      {
        "name": "unlockHref",
        "type": "string",
        "default": null,
        "required": true,
        "description": "Where the unlock link goes, such as the pricing section."
      },
      {
        "name": "onUnlock",
        "type": "(event: MouseEvent<HTMLAnchorElement>) => void",
        "default": null,
        "required": false,
        "description": "Called when the unlock link is clicked, for opening a checkout in place. Call `preventDefault()` to stay."
      },
      {
        "name": "price",
        "type": "string",
        "default": null,
        "required": false,
        "description": "The price after the unlock label, such as \"€49\". Leave it out for a plain \"Unlock\"."
      },
      {
        "name": "priceNote",
        "type": "ReactNode",
        "default": null,
        "required": false,
        "description": "One line under the link about the price, such as an introductory offer and when it ends."
      },
      {
        "name": "facts",
        "type": "string[]",
        "default": "[]",
        "required": false,
        "description": "Short facts in a row under the price, such as \"One-time payment\"."
      },
      {
        "name": "commands",
        "type": "LockedCodeCommand[]",
        "default": "[]",
        "required": false,
        "description": "The commands that fetch the code with a key, each with a copy button. Leave it empty to drop the key row."
      },
      {
        "name": "envVar",
        "type": "string",
        "default": null,
        "required": false,
        "description": "The environment variable the key lives in, shown in the key row."
      },
      {
        "name": "setupHref",
        "type": "string",
        "default": null,
        "required": false,
        "description": "A page that explains how to set the key up, linked from the key row."
      },
      {
        "name": "labels",
        "type": "LockedCodeLabels",
        "default": "{}",
        "required": false,
        "description": "Words for other languages."
      },
      {
        "name": "headingLevel",
        "type": "2 | 3 | 4 | 5 | 6",
        "default": "3",
        "required": false,
        "description": "The heading level of \"The code is locked\", to fit the page outline. Default 3."
      },
      {
        "name": "className",
        "type": "string",
        "default": null,
        "required": false,
        "description": null
      }
    ],
    "a11y": [
      {
        "topic": "keyboard",
        "text": "Tab order: Unlock, How to set it up, Copy CLI, Copy MCP. The key slot lights on hover and on keyboard focus.",
        "keys": [
          "Tab",
          "Enter"
        ]
      },
      {
        "topic": "screen-reader",
        "text": "A region named by the file name and heading. The stub is aria-hidden: pass placeholder lines, never real source, since it ships in the HTML. Each command row is a group named after its command, so each Copy says what it copies."
      },
      {
        "topic": "motion",
        "text": "No transition under reduced motion."
      }
    ]
  },
  "categories": [
    "surfaces"
  ],
  "type": "registry:component"
}