{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "biometric-gate",
  "title": "Face ID",
  "description": "Face ID gate, or the hook for a single action · real only in a dev build",
  "dependencies": [
    "expo-local-authentication@~57.0.3",
    "lucide-react-native@^1.21.0"
  ],
  "registryDependencies": [
    "@kit-native/button",
    "@kit-native/lib-haptics",
    "@kit-native/lib-native-context",
    "@kit-native/lib-utils",
    "@kit-native/spinner",
    "@kit-native/spot",
    "@kit-native/text"
  ],
  "files": [
    {
      "path": "components/kit/biometric-gate.tsx",
      "content": "import { Spinner } from '@/components/kit/spinner';\nimport { Spot } from '@/components/kit/spot';\nimport { Button } from '@/components/ui/button';\nimport { Text } from '@/components/ui/text';\nimport { haptic } from '@/lib/haptics';\nimport { useCapability } from '@/lib/native-context';\nimport { cn } from '@/lib/utils';\nimport * as LocalAuthentication from 'expo-local-authentication';\nimport { LockIcon, ScanFaceIcon } from 'lucide-react-native';\nimport * as React from 'react';\nimport { Pressable, View } from 'react-native';\n\n/** Long enough to read as real in a demo recording. */\nconst SIMULATED_DELAY = 900;\n\n/**\n * Imperative Face ID / Touch ID for a single action — confirming a payment, revealing a secret.\n *\n * Resolves true when the prototype should proceed, including on the simulated path.\n */\nexport function useBiometricAuth(simulate?: boolean) {\n  const { cap, request } = useCapability('biometrics', simulate);\n\n  const authenticate = React.useCallback(\n    async (prompt = 'Confirm it is you'): Promise<boolean> => {\n      if (cap.simulated) {\n        await new Promise((r) => setTimeout(r, SIMULATED_DELAY));\n        haptic('success');\n        return true;\n      }\n      const settled = await request();\n      if (settled.simulated) {\n        await new Promise((r) => setTimeout(r, SIMULATED_DELAY));\n        haptic('success');\n        return true;\n      }\n      try {\n        const result = await LocalAuthentication.authenticateAsync({ promptMessage: prompt });\n        haptic(result.success ? 'success' : 'error');\n        return result.success;\n      } catch {\n        return false;\n      }\n    },\n    [cap.simulated, request]\n  );\n\n  return { authenticate, simulated: cap.simulated };\n}\n\ntype Props = {\n  /** Revealed once unlocked. */\n  children: React.ReactNode;\n  title?: string;\n  subtitle?: string;\n  /** Reason string shown inside the OS dialog. */\n  prompt?: string;\n  /** Start locked. Default true. Re-lock by changing `key`. */\n  locked?: boolean;\n  onUnlock?: () => void;\n  /** Escape hatch. Always shown — a prototype must never trap the viewer. */\n  fallbackLabel?: string;\n  simulate?: boolean;\n  className?: string;\n};\n\n/**\n * Hides its children behind Face ID / Touch ID.\n *\n * Simulated anywhere the real thing cannot run — the web preview, Expo Go — with a\n * short pause so the unlock still reads as real.\n */\nexport function BiometricGate({\n  children,\n  title = 'Locked',\n  subtitle = 'Unlock to see this section.',\n  prompt = 'Unlock',\n  locked = true,\n  onUnlock,\n  fallbackLabel = 'Use a passcode instead',\n  simulate,\n  className,\n}: Props) {\n  const [unlocked, setUnlocked] = React.useState(!locked);\n  const [busy, setBusy] = React.useState(false);\n  const { authenticate, simulated } = useBiometricAuth(simulate);\n\n  if (unlocked) return <>{children}</>;\n\n  const unlock = async () => {\n    setBusy(true);\n    const ok = await authenticate(prompt);\n    setBusy(false);\n    if (!ok) return;\n    setUnlocked(true);\n    onUnlock?.();\n  };\n\n  const skip = () => {\n    haptic('selection');\n    setUnlocked(true);\n    onUnlock?.();\n  };\n\n  return (\n    <View className={cn('items-center justify-center gap-2 px-6 py-12', className)}>\n      <Spot icon={simulated ? LockIcon : ScanFaceIcon} size=\"lg\" className=\"mb-4\" />\n      <Text variant=\"h4\" className=\"text-center\">\n        {title}\n      </Text>\n      <Text className=\"text-muted-foreground max-w-xs text-center\">{subtitle}</Text>\n      <Button onPress={unlock} disabled={busy} className=\"mt-4 w-full max-w-xs\">\n        {busy ? <Spinner tone=\"primary-foreground\" /> : <Text>Unlock</Text>}\n      </Button>\n      <Pressable onPress={skip} accessibilityRole=\"button\" className=\"mt-1 min-h-11 justify-center\">\n        <Text className=\"text-muted-foreground text-sm font-semibold\">{fallbackLabel}</Text>\n      </Pressable>\n    </View>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/kit/biometric-gate.tsx"
    }
  ],
  "meta": {
    "exports": [
      "BiometricGate",
      "useBiometricAuth"
    ],
    "api": "<BiometricGate title? subtitle? prompt? locked? onUnlock? simulate?>…</BiometricGate> · useBiometricAuth()"
  },
  "docs": "Run `npx expo install --fix` afterwards so Expo SDK packages match your SDK. Needs the kit theme (`@kit-native/theme`) and `KitThemeProvider` from `@kit-native/lib-theme-context` at the root.",
  "categories": [
    "native"
  ],
  "type": "registry:component"
}