{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "password-input",
  "title": "Password Input",
  "description": "Password field with show/hide and a live strength meter.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.cuelark.com/r/theme.json",
    "https://ui.cuelark.com/r/motion.json"
  ],
  "files": [
    {
      "path": "src/registry/cuelark/ui/password-input.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { CheckIcon, EyeIcon, EyeOffIcon } from \"lucide-react\";\n\nimport { gsap, motionScale } from \"@/lib/motion\";\nimport { cn } from \"@/lib/utils\";\n\ntype Rule = { label: string; test: (v: string) => boolean };\n\nconst DEFAULT_RULES: Rule[] = [\n  { label: \"8+ characters\", test: (v) => v.length >= 8 },\n  { label: \"A number\", test: (v) => /\\d/.test(v) },\n  { label: \"Upper and lower case\", test: (v) => /[a-z]/.test(v) && /[A-Z]/.test(v) },\n  { label: \"A symbol\", test: (v) => /[^A-Za-z0-9]/.test(v) },\n];\nconst LEVELS = [\"Too weak\", \"Weak\", \"Fair\", \"Good\", \"Strong\"];\nconst TONE = [\"bg-destructive\", \"bg-destructive\", \"bg-warning\", \"bg-success\", \"bg-success\"];\n\ntype PasswordInputProps = Omit<React.ComponentProps<\"input\">, \"type\" | \"size\"> & {\n  /** Show the strength meter and checklist. */\n  strength?: boolean;\n  rules?: Rule[];\n};\n\n/**\n * Password field with a show/hide toggle and a live strength meter: four segments fill as rules pass, and each\n * rule ticks off with a small pop. Built for sign-up forms; the checklist is announced politely.\n */\nfunction PasswordInput({ strength = true, rules = DEFAULT_RULES, className, value, defaultValue, onChange, ...props }: PasswordInputProps) {\n  const [visible, setVisible] = React.useState(false);\n  const [internal, setInternal] = React.useState(String(defaultValue ?? \"\"));\n  const current = value !== undefined ? String(value) : internal;\n  const passed = rules.map((r) => r.test(current));\n  const score = current ? passed.filter(Boolean).length : 0;\n  const listRef = React.useRef<HTMLUListElement>(null);\n  const prev = React.useRef(passed);\n\n  React.useEffect(() => {\n    const list = listRef.current;\n    if (list && motionScale(list))\n      passed.forEach((p, i) => {\n        if (p && !prev.current[i]) gsap.fromTo(list.children[i]!, { scale: 1.12 }, { scale: 1, duration: 0.4, ease: \"cue-snap\" });\n      });\n    prev.current = passed;\n  });\n\n  return (\n    <div data-slot=\"password-input\" className={cn(\"grid gap-2.5\", className)}>\n      <div className=\"flex h-ctl-md items-center rounded-md border border-input bg-card shadow-well transition-[border-color,box-shadow] duration-150 has-[input:focus-visible]:border-ring has-[input:focus-visible]:ring-3 has-[input:focus-visible]:ring-ring/20\">\n        <input\n          type={visible ? \"text\" : \"password\"}\n          autoComplete=\"new-password\"\n          value={value}\n          defaultValue={defaultValue}\n          onChange={(e) => {\n            setInternal(e.target.value);\n            onChange?.(e);\n          }}\n          className=\"h-full w-full min-w-0 flex-1 bg-transparent px-3 text-sm outline-none placeholder:text-muted-foreground/80\"\n          {...props}\n        />\n        <button\n          type=\"button\"\n          onClick={() => setVisible((v) => !v)}\n          aria-label={visible ? \"Hide password\" : \"Show password\"}\n          aria-pressed={visible}\n          className=\"me-1 grid size-7 place-items-center rounded-md text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50\"\n        >\n          {visible ? <EyeOffIcon className=\"size-4\" /> : <EyeIcon className=\"size-4\" />}\n        </button>\n      </div>\n      {strength && (\n        <>\n          <div className=\"flex items-center gap-3\">\n            <div className=\"flex flex-1 gap-1\" aria-hidden=\"true\">\n              {rules.map((_, i) => (\n                <span key={i} className={cn(\"h-1 flex-1 rounded-full bg-muted transition-colors duration-300\", i < score && TONE[score])} />\n              ))}\n            </div>\n            <span className=\"w-16 text-right font-mono text-[0.6875rem] text-muted-foreground\" aria-live=\"polite\">\n              {current ? LEVELS[score] : \"\"}\n            </span>\n          </div>\n          <ul ref={listRef} className=\"grid grid-cols-2 gap-x-4 gap-y-1.5 text-xs\" aria-live=\"polite\">\n            {rules.map((r, i) => (\n              <li key={r.label} className={cn(\"flex origin-left items-center gap-1.5 transition-colors\", passed[i] ? \"text-foreground\" : \"text-muted-foreground\")}>\n                <span className={cn(\"grid size-3.5 place-items-center rounded-full border transition-colors\", passed[i] ? \"border-success bg-success text-success-foreground\" : \"border-border\")}>\n                  {passed[i] && <CheckIcon className=\"size-2.5\" strokeWidth={3} />}\n                </span>\n                {r.label}\n                <span className=\"sr-only\">{passed[i] ? \"met\" : \"not met\"}</span>\n              </li>\n            ))}\n          </ul>\n        </>\n      )}\n    </div>\n  );\n}\n\nexport { PasswordInput };\nexport type { PasswordInputProps };\n",
      "type": "registry:ui"
    }
  ],
  "docs": "Docs and examples: https://ui.cuelark.com/components/password-input",
  "categories": [
    "forms"
  ],
  "type": "registry:ui"
}