{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "author": "Lopifi (https://lopifi.com)",
  "categories": [
    "primitives",
    "finance",
    "accounting"
  ],
  "dependencies": [],
  "description": "Masked value with reveal.",
  "files": [
    {
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/ledger/cn\";\nimport { EyeIcon, EyeSlashIcon } from \"@/components/ledger/icons\";\n\nexport interface RedactedProps extends React.ComponentPropsWithoutRef<\"span\"> {\n  children: React.ReactNode;\n  defaultRevealed?: boolean;\n  label?: string;\n  /** Blur the real value instead of masking it. Layout-identical either way. */\n  mode?: \"mask\" | \"blur\";\n}\n\n/**\n * Redaction is a presentation state, not missing data.\n *\n * The masked form reserves the revealed width so nothing reflows, and the\n * blur variant keeps `user-select: none` so the value can't be copied out\n * of a \"hidden\" state.\n */\nexport const Redacted = React.forwardRef<HTMLSpanElement, RedactedProps>(\n  ({ className, children, mode = \"blur\", label = \"Balance\", defaultRevealed = false, ...props }, ref) => {\n    const [revealed, setRevealed] = React.useState(defaultRevealed);\n\n    return (\n      <span className={cn(\"inline-flex items-center gap-2\", className)} ref={ref} {...props}>\n        <span\n          aria-hidden={!revealed}\n          className={cn(\n            \"transition-[filter,opacity] duration-2 ease-out\",\n            !revealed && mode === \"blur\" && \"select-none blur-[6px]\",\n            !revealed && mode === \"mask\" && \"invisible\",\n          )}\n        >\n          {children}\n        </span>\n        {!revealed && mode === \"mask\" ? (\n          <span aria-hidden=\"true\" className=\"absolute text-text-tertiary\">\n            ••••\n          </span>\n        ) : null}\n        <button\n          aria-label={revealed ? `${label} shown, activate to hide` : `${label} hidden, activate to reveal`}\n          aria-pressed={revealed}\n          className={cn(\n            \"grid size-8 shrink-0 place-items-center rounded-sm text-text-tertiary\",\n            \"transition-colors duration-1 ease-standard\",\n            \"@media(hover:hover)and(pointer:fine){hover:bg-surface-hover}\",\n            \"hover:bg-surface-hover hover:text-text-secondary\",\n          )}\n          onClick={() => setRevealed((v) => !v)}\n          type=\"button\"\n        >\n          {revealed ? (\n            <EyeSlashIcon aria-hidden=\"true\" className=\"size-4\" />\n          ) : (\n            <EyeIcon aria-hidden=\"true\" className=\"size-4\" />\n          )}\n        </button>\n      </span>\n    );\n  },\n);\nRedacted.displayName = \"Redacted\";\n",
      "path": "components/ledger/redacted.tsx",
      "target": "components/ledger/redacted.tsx",
      "type": "registry:ui"
    }
  ],
  "name": "redacted",
  "registryDependencies": [
    "@lopifi/theme",
    "@lopifi/cn",
    "@lopifi/icons"
  ],
  "title": "Redacted",
  "type": "registry:ui"
}
