{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "password-input-experimental",
  "description": "An input that can toggle visibility of the password text and check the strength of the password. (Includes experimental support for all Shadcn icon libraries)",
  "dependencies": [
    "@zxcvbn-ts/core",
    "@zxcvbn-ts/language-common",
    "@zxcvbn-ts/language-en"
  ],
  "registryDependencies": [
    "input",
    "button",
    "tooltip",
    "input-group"
  ],
  "files": [
    {
      "path": "src/registry/experimental/items/password-input/components/password-input.tsx",
      "content": "\"use client\"\n\nimport { Input } from \"@/components/ui/input\"\nimport { cn } from \"@/lib/utils\"\nimport {\n  useState,\n  createContext,\n  useContext,\n  type ComponentProps,\n  type ReactNode,\n  type ChangeEvent,\n  useEffect,\n  useDeferredValue,\n  useMemo,\n} from \"react\"\nimport { zxcvbn, zxcvbnOptions } from \"@zxcvbn-ts/core\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\"\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupInput,\n} from \"@/components/ui/input-group\"\nimport { IconPlaceholder } from \"@/components/registry-helpers/icon-placeholder\"\n\nconst PasswordInputContext = createContext<{ password: string } | null>(null)\n\nexport function PasswordInput({\n  children,\n  onChange,\n  value,\n  defaultValue,\n  ...props\n}: Omit<ComponentProps<typeof Input>, \"type\"> & {\n  children?: ReactNode\n}) {\n  const [showPassword, setShowPassword] = useState(false)\n  const [password, setPassword] = useState(defaultValue ?? \"\")\n\n  const currentValue = value ?? password\n\n  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n    setPassword(e.target.value)\n    onChange?.(e)\n  }\n\n  return (\n    <PasswordInputContext value={{ password: currentValue.toString() }}>\n      <div className=\"space-y-3\">\n        <InputGroup>\n          <InputGroupInput\n            {...props}\n            value={value}\n            defaultValue={defaultValue}\n            type={showPassword ? \"text\" : \"password\"}\n            onChange={handleChange}\n          />\n          <InputGroupAddon align=\"inline-end\">\n            <InputGroupButton\n              size=\"icon-xs\"\n              onClick={() => setShowPassword(p => !p)}\n            >\n              {showPassword ? (\n                <IconPlaceholder\n                  lucide=\"EyeOffIcon\"\n                  tabler=\"IconEyeOff\"\n                  hugeicons=\"ViewOffSlashIcon\"\n                  phosphor=\"EyeSlashIcon\"\n                  remixicon=\"RiEyeOffLine\"\n                  className=\"size-4.5\"\n                />\n              ) : (\n                <IconPlaceholder\n                  lucide=\"EyeIcon\"\n                  tabler=\"IconEye\"\n                  hugeicons=\"ViewIcon\"\n                  phosphor=\"EyeIcon\"\n                  remixicon=\"RiEyeLine\"\n                  className=\"size-4.5\"\n                />\n              )}\n              <span className=\"sr-only\">\n                {showPassword ? \"Hide password\" : \"Show password\"}\n              </span>\n            </InputGroupButton>\n          </InputGroupAddon>\n        </InputGroup>\n        {children}\n      </div>\n    </PasswordInputContext>\n  )\n}\n\nexport function PasswordInputStrengthChecker() {\n  const [optionsLoaded, setOptionsLoaded] = useState(false)\n  const [errorLoadingOptions, setErrorLoadingOptions] = useState(false)\n\n  const { password } = usePasswordInput()\n  const deferredPassword = useDeferredValue(password)\n  const strengthResult = useMemo(() => {\n    if (!optionsLoaded || deferredPassword.length === 0) {\n      return { score: 0, feedback: { warning: undefined } } as const\n    }\n\n    return zxcvbn(deferredPassword)\n  }, [optionsLoaded, deferredPassword])\n\n  useEffect(() => {\n    Promise.all([\n      import(\"@zxcvbn-ts/language-common\"),\n      import(\"@zxcvbn-ts/language-en\"),\n    ])\n      .then(([common, english]) => {\n        zxcvbnOptions.setOptions({\n          translations: english.translations,\n          graphs: common.adjacencyGraphs,\n          maxLength: 50,\n          dictionary: {\n            ...common.dictionary,\n            ...english.dictionary,\n          },\n        })\n        setOptionsLoaded(true)\n      })\n      .catch(() => setErrorLoadingOptions(true))\n  }, [])\n\n  function getLabel() {\n    if (deferredPassword.length === 0) return \"Password strength\"\n    if (!optionsLoaded) return \"Loading strength checker\"\n\n    const score = strengthResult.score\n    switch (score) {\n      case 0:\n      case 1:\n        return \"Very weak\"\n      case 2:\n        return \"Weak\"\n      case 3:\n        return \"Strong\"\n      case 4:\n        return \"Very strong\"\n      default:\n        throw new Error(`Invalid score: ${score satisfies never}`)\n    }\n  }\n\n  const label = getLabel()\n\n  if (errorLoadingOptions) return null\n\n  return (\n    <div className=\"space-y-0.5\">\n      <div\n        role=\"progressbar\"\n        aria-label=\"Password Strength\"\n        aria-valuenow={strengthResult.score}\n        aria-valuemin={0}\n        aria-valuemax={4}\n        aria-valuetext={label}\n        className=\"flex gap-1\"\n      >\n        {Array.from({ length: 4 }).map((_, i) => {\n          const color =\n            strengthResult.score >= 3 ? \"bg-primary\" : \"bg-destructive\"\n\n          return (\n            <div\n              key={i}\n              className={cn(\n                \"h-1 flex-1 rounded-full\",\n                strengthResult.score > i ? color : \"bg-secondary\",\n              )}\n            />\n          )\n        })}\n      </div>\n      <div className=\"flex justify-end text-sm text-muted-foreground\">\n        {strengthResult.feedback.warning == null ? (\n          label\n        ) : (\n          <Tooltip>\n            <TooltipTrigger className=\"underline underline-offset-1\">\n              {label}\n            </TooltipTrigger>\n            <TooltipContent side=\"bottom\" sideOffset={4} className=\"text-base\">\n              {strengthResult.feedback.warning}\n            </TooltipContent>\n          </Tooltip>\n        )}\n      </div>\n    </div>\n  )\n}\n\nconst usePasswordInput = () => {\n  const context = useContext(PasswordInputContext)\n  if (context == null) {\n    throw new Error(\n      \"usePasswordInput must be used within a PasswordInputContext\",\n    )\n  }\n  return context\n}\n",
      "type": "registry:ui",
      "target": "components/ui/password-input.tsx"
    }
  ],
  "type": "registry:component"
}