{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "knock-codes",
  "title": "Knock Codes",
  "description": "The main gate: renders children when unlocked, otherwise a PIN prompt. Local SHA-256 verification by default; pass a `verify` function for server-mode protection.",
  "registryDependencies": [
    "https://knock.codes/r/react/knock-codes-hook.json",
    "https://knock.codes/r/react/gate-wrapper.json",
    "https://knock.codes/r/react/pin-input.json"
  ],
  "files": [
    {
      "path": "packages/react/KnockCodes.tsx",
      "content": "\"use client\";\r\n\r\nimport { useState, type ReactNode } from \"react\";\r\nimport { useKnockCodes } from \"./useKnockCodes.ts\";\r\nimport { PinInput } from \"./PinInput.tsx\";\r\nimport { GateWrapper, type GateWrapperVariant } from \"./GateWrapper.tsx\";\r\nimport { DEFAULT_LABELS, type KnockCodesConfig, type KnockCodesLabels } from \"./types.ts\";\r\nimport { cx } from \"./cx.ts\";\r\n\r\nexport interface KnockCodesProps extends KnockCodesConfig {\r\n  children: ReactNode;\r\n  labels?: KnockCodesLabels;\r\n  /** Visual shell around the PIN prompt. @default \"page\" */\r\n  variant?: GateWrapperVariant;\r\n  autoFocus?: boolean;\r\n  className?: string;\r\n}\r\n\r\n/**\r\n * Wrapper component. Renders `children` only when a valid session exists;\r\n * otherwise renders the PIN entry UI. There is no separate \"mount loading\"\r\n * state — the PIN entry UI covers it.\r\n */\r\nexport function KnockCodes({ children, labels, variant = \"page\", autoFocus = true, className, ...config }: KnockCodesProps) {\r\n  const { state, error, submit } = useKnockCodes(config);\r\n  const [code, setCode] = useState(\"\");\r\n\r\n  if (state === \"unlocked\") return <>{children}</>;\r\n\r\n  const merged = { ...DEFAULT_LABELS, ...labels };\r\n  const modeLabel = config.verify ? \"SERVER VERIFY\" : \"LOCAL HASH\";\r\n\r\n  const handleSubmit = async () => {\r\n    await submit(code);\r\n    setCode(\"\"); // clearing after every attempt is an implementation detail, not a contract\r\n  };\r\n\r\n  return (\r\n    <GateWrapper variant={variant} className={className}>\r\n      <div\r\n        style={{ fontFamily: \"var(--ag-font, inherit)\" }}\r\n        className=\"w-full max-w-sm rounded-[var(--ag-radius,0.75rem)] border border-[var(--ag-border,#e5e7eb)] bg-[var(--ag-card,#ffffff)] p-7 shadow-sm dark:border-[var(--ag-border-dark,#1f2937)] dark:bg-[var(--ag-card-dark,#030712)]\"\r\n      >\r\n        <div className=\"mb-5 flex items-center justify-between gap-2\">\r\n          <span className=\"inline-flex items-center gap-1.5 text-[10px] font-medium tracking-wider text-red-600 uppercase dark:text-red-400\">\r\n            <span aria-hidden=\"true\" className=\"h-1.5 w-1.5 shrink-0 rounded-full bg-red-600 dark:bg-red-400\" />\r\n            Locked\r\n          </span>\r\n          <span className=\"rounded-full border border-gray-200 px-2 py-0.5 font-mono text-[10px] font-medium tracking-wider text-gray-500 uppercase dark:border-gray-800 dark:text-gray-400\">\r\n            {modeLabel}\r\n          </span>\r\n        </div>\r\n\r\n        <div className=\"mb-5 space-y-1\">\r\n          <h1 className=\"text-lg font-semibold text-gray-900 dark:text-gray-50\">{merged.heading}</h1>\r\n          {merged.subcopy && <p className=\"text-sm text-gray-500 dark:text-gray-400\">{merged.subcopy}</p>}\r\n        </div>\r\n\r\n        <PinInput\r\n          value={code}\r\n          onChange={setCode}\r\n          onSubmit={handleSubmit}\r\n          submitting={state === \"submitting\"}\r\n          error={error}\r\n          labels={labels}\r\n          autoFocus={autoFocus}\r\n        />\r\n      </div>\r\n    </GateWrapper>\r\n  );\r\n}\r\n",
      "type": "registry:file",
      "target": "components/knock-codes/react/KnockCodes.tsx"
    }
  ],
  "type": "registry:block"
}