{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "password-strength",
  "type": "registry:ui",
  "title": "Password strength",
  "description": "A React password field with a four step strength meter and a live rule checklist. Announces the score politely and accepts your own scorer such as zxcvbn.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "https://ui.minidev.pro/r/password-input.json",
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/password-strength.tsx",
      "type": "registry:ui",
      "target": "components/ui/password-strength.tsx",
      "content": "\"use client\"\nimport * as React from \"react\"\nimport { CheckIcon, XIcon } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\nimport { PasswordInput } from \"@/components/ui/password-input\"\n\n/**\n * A password field with a four segment strength meter and a live checklist\n * of rules. The score is announced politely as it changes, rules are plain\n * text for screen readers, and nothing blocks typing. Swap in your own\n * scorer (for example zxcvbn) with the `score` prop.\n */\ntype Rule = { label: string; test: (value: string) => boolean }\n\ntype PasswordStrengthProps = {\n  value?: string\n  defaultValue?: string\n  onChange?: (value: string) => void\n  /** Rules shown in the checklist. */\n  rules?: Rule[]\n  /** Override the built in 0 to 4 score. */\n  score?: (value: string) => number\n  label?: string\n  id?: string\n  className?: string\n}\n\nconst DEFAULT_RULES: Rule[] = [\n  { label: \"At least 12 characters\", test: (v) => v.length >= 12 },\n  { label: \"Upper and lower case letters\", test: (v) => /[a-z]/.test(v) && /[A-Z]/.test(v) },\n  { label: \"A number\", test: (v) => /\\d/.test(v) },\n  { label: \"A symbol\", test: (v) => /[^A-Za-z0-9]/.test(v) },\n]\n\nconst LEVELS = [\n  { label: \"Too weak\", tone: \"bg-danger\", text: \"text-danger\" },\n  { label: \"Weak\", tone: \"bg-danger\", text: \"text-danger\" },\n  { label: \"Fair\", tone: \"bg-warning\", text: \"text-[color-mix(in_oklch,var(--warning)_70%,var(--fg))]\" },\n  { label: \"Good\", tone: \"bg-success\", text: \"text-success\" },\n  { label: \"Strong\", tone: \"bg-success\", text: \"text-success\" },\n]\n\nfunction defaultScore(v: string, rules: Rule[]) {\n  if (!v) return 0\n  const passed = rules.filter((r) => r.test(v)).length\n  const long = v.length >= 16 ? 1 : 0\n  return Math.max(1, Math.min(4, passed - (v.length < 8 ? 1 : 0) + long))\n}\n\nfunction PasswordStrength({ value, defaultValue = \"\", onChange, rules = DEFAULT_RULES, score, label = \"Password\", id, className }: PasswordStrengthProps) {\n  const [inner, setInner] = React.useState(defaultValue)\n  const v = value ?? inner\n  const s = score ? Math.max(0, Math.min(4, Math.round(score(v)))) : defaultScore(v, rules)\n  const level = LEVELS[s]\n  const auto = React.useId()\n  const fieldId = id ?? auto\n  return (\n    <div data-slot=\"password-strength\" className={cn(\"w-full max-w-sm space-y-2.5\", className)}>\n      <label htmlFor={fieldId} className=\"text-sm font-medium text-fg\">\n        {label}\n      </label>\n      <PasswordInput\n        id={fieldId}\n        value={v}\n        autoComplete=\"new-password\"\n        aria-describedby={`${fieldId}-strength ${fieldId}-rules`}\n        onChange={(e) => {\n          if (value === undefined) setInner(e.target.value)\n          onChange?.(e.target.value)\n        }}\n      />\n      <div className=\"flex items-center gap-3\">\n        <div className=\"flex flex-1 gap-1\" aria-hidden>\n          {[1, 2, 3, 4].map((n) => (\n            <span key={n} className={cn(\"h-1.5 flex-1 rounded-full transition-colors duration-300\", v && n <= s ? level.tone : \"bg-border\")} />\n          ))}\n        </div>\n        <span id={`${fieldId}-strength`} aria-live=\"polite\" className={cn(\"min-w-14 text-right text-xs font-medium\", v ? level.text : \"text-fg-subtle\")}>\n          {v ? level.label : \"Empty\"}\n        </span>\n      </div>\n      <ul id={`${fieldId}-rules`} className=\"space-y-1\">\n        {rules.map((r) => {\n          const ok = r.test(v)\n          return (\n            <li key={r.label} className={cn(\"flex items-center gap-2 text-xs transition-colors\", ok ? \"text-fg\" : \"text-fg-muted\")}>\n              <span className={cn(\"grid size-4 place-items-center rounded-full\", ok ? \"bg-success/15 text-success\" : \"bg-sunken text-fg-subtle\")}>\n                {ok ? <CheckIcon className=\"size-3\" strokeWidth={3} /> : <XIcon className=\"size-2.5\" strokeWidth={3} />}\n              </span>\n              {r.label}\n              <span className=\"sr-only\">{ok ? \"(met)\" : \"(not met)\"}</span>\n            </li>\n          )\n        })}\n      </ul>\n    </div>\n  )\n}\n\nexport { PasswordStrength }\nexport type { PasswordStrengthProps }\n"
    }
  ]
}
