{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "password-strength",
  "type": "registry:ui",
  "title": "Password Strength",
  "description": "PasswordInput with a four-segment strength meter and a live, overridable requirements checklist.",
  "dependencies": [
    "@mantine/core@^9",
    "@mantine/hooks@^9",
    "@tabler/icons-react@^3"
  ],
  "docs": "Adapted from Mantine UI PasswordStrength at ffbf61c559f374a7ea28fcf00355e84dcbe9a908; requirements/minLength/label/placeholder became props, controlled and uncontrolled usage is now supported via useUncontrolled, and per-segment aria-labels plus live-region summaries were added for accessibility.",
  "registryDependencies": [
    "@house/mantine-ui-license"
  ],
  "files": [
    {
      "path": "registry/mantine-ui/password-strength/password-strength.tsx",
      "type": "registry:ui",
      "content": "/**\n * Adapted from Mantine UI's PasswordStrength at\n * ffbf61c559f374a7ea28fcf00355e84dcbe9a908. MIT licensed; see\n * LICENSES/MANTINE-UI.txt after installation.\n *\n * Controlled/uncontrolled contract (settled across floating-label-input,\n * password-strength, autocomplete-loading — the tranche's three form\n * controls): whether the public `value`/`onChange` pair is event-based or\n * value-based is decided by the props surface, not by preference.\n *   - Drop-in wrappers — `Props extends Omit<XProps, ...>` with X's own ref\n *     type, rendering nothing but `<X {...} />` — keep X's exact contract,\n *     because a consumer swapping `<X>` for the wrapper shouldn't have to\n *     rewrite their handler. TextInput/PasswordInput/Textarea are\n *     event-based (`ChangeEventHandler<HTMLInputElement>`);\n *     Autocomplete/Select and the rest of the combobox family are\n *     value-based (`(value: string) => void`).\n *   - Composite components that own their own props surface (their own\n *     BoxProps/ElementProps, their own ref) aren't a drop-in for any single\n *     base, so they expose the simple value-based contract instead.\n * Both controlled and uncontrolled work everywhere. Event-based drop-ins\n * hand-roll `useState` + `isControlled = value !== undefined`, because\n * `useUncontrolled`'s onChange payload type is tied to the tracked value\n * type and can't emit a raw DOM event. Everything else uses\n * `@mantine/hooks`' `useUncontrolled`.\n *\n * This component is a composite, not a drop-in for PasswordInput: its props\n * extend `BoxProps` + `ElementProps<\"div\", \"onChange\">` (its own surface,\n * not `Omit<PasswordInputProps, ...>`) and its ref is `HTMLDivElement`, not\n * `HTMLInputElement` — a consumer swapping `<PasswordInput>` for this\n * component already loses `error`, size/description forwarding to the\n * input, and the input ref, so there's no drop-in property left to preserve\n * by matching PasswordInput's event-based onChange. Value-based onChange,\n * `@mantine/hooks`' `useUncontrolled`.\n */\n\nimport {\n  Box,\n  type BoxProps,\n  Center,\n  type ElementProps,\n  type Factory,\n  factory,\n  type GetStylesApi,\n  Group,\n  PasswordInput,\n  Progress,\n  type StylesApiProps,\n  Text,\n  useProps,\n  useStyles,\n} from \"@mantine/core\";\nimport { useUncontrolled } from \"@mantine/hooks\";\nimport { IconCheck, IconX } from \"@tabler/icons-react\";\nimport type { ReactNode } from \"react\";\n\nimport classes from \"./password-strength.module.css\";\n\nexport interface PasswordStrengthRequirement {\n  /** Tested against the current value; a match counts the requirement as met. */\n  re: RegExp;\n  /** Requirement text shown in the checklist. */\n  label: string;\n}\n\n/** Upstream's requirement list, exported so consumers can extend rather than replace it. */\nexport const defaultPasswordStrengthRequirements: PasswordStrengthRequirement[] = [\n  { re: /[0-9]/, label: \"Includes number\" },\n  { re: /[a-z]/, label: \"Includes lowercase letter\" },\n  { re: /[A-Z]/, label: \"Includes uppercase letter\" },\n  { re: /[$&+,:;=?@#|'<>.^*()%!-]/, label: \"Includes special symbol\" },\n];\n\nfunction getPasswordStrength(\n  password: string,\n  requirements: readonly PasswordStrengthRequirement[],\n  minLength: number,\n) {\n  let multiplier = password.length >= minLength ? 0 : 1;\n\n  for (const requirement of requirements) {\n    if (!requirement.re.test(password)) {\n      multiplier += 1;\n    }\n  }\n\n  return Math.max(100 - (100 / (requirements.length + 1)) * multiplier, 0);\n}\n\nexport type PasswordStrengthStylesNames =\n  | \"root\"\n  | \"input\"\n  | \"meter\"\n  | \"bar\"\n  | \"requirement\"\n  | \"requirementLabel\";\n\nexport interface PasswordStrengthProps\n  extends BoxProps,\n    StylesApiProps<PasswordStrengthFactory>,\n    ElementProps<\"div\", \"onChange\"> {\n  /** Controlled value. Omit to let the component manage its own state. */\n  value?: string;\n  /** Initial value when uncontrolled. @default '' */\n  defaultValue?: string;\n  /** Called with the next value on every keystroke, controlled or not. */\n  onChange?: (value: string) => void;\n  /** @default 'Password' */\n  label?: ReactNode;\n  /** @default 'Your password' */\n  placeholder?: string;\n  /** @default true */\n  required?: boolean;\n  /** Minimum character count the length requirement checks for. @default 6 */\n  minLength?: number;\n  /** Regex + label pairs checked against the value. @default defaultPasswordStrengthRequirements */\n  requirements?: readonly PasswordStrengthRequirement[];\n}\n\nexport type PasswordStrengthFactory = Factory<{\n  props: PasswordStrengthProps;\n  ref: HTMLDivElement;\n  stylesNames: PasswordStrengthStylesNames;\n}>;\n\ninterface RequirementRowProps {\n  meets: boolean;\n  label: ReactNode;\n  unstyled: boolean | undefined;\n  getStyles: GetStylesApi<PasswordStrengthFactory>;\n}\n\nfunction RequirementRow({ meets, label, unstyled, getStyles }: RequirementRowProps) {\n  return (\n    <Text\n      component=\"div\"\n      unstyled={unstyled}\n      c={meets ? \"teal\" : \"red\"}\n      size=\"sm\"\n      {...getStyles(\"requirement\")}\n    >\n      <Center inline unstyled={unstyled}>\n        {meets ? (\n          <IconCheck size={14} stroke={1.5} aria-hidden=\"true\" />\n        ) : (\n          <IconX size={14} stroke={1.5} aria-hidden=\"true\" />\n        )}\n        <Box component=\"span\" {...getStyles(\"requirementLabel\")}>\n          {label}\n        </Box>\n      </Center>\n    </Text>\n  );\n}\n\nexport const PasswordStrength = factory<PasswordStrengthFactory>((_props) => {\n  const props = useProps(\"PasswordStrength\", null, _props);\n  const {\n    classNames,\n    className,\n    style,\n    styles,\n    unstyled,\n    vars,\n    attributes,\n    ref,\n    value,\n    defaultValue,\n    onChange,\n    label = \"Password\",\n    placeholder = \"Your password\",\n    required = true,\n    minLength = 6,\n    requirements = defaultPasswordStrengthRequirements,\n    ...others\n  } = props;\n\n  const [passwordValue, handleChange] = useUncontrolled({\n    value,\n    defaultValue,\n    finalValue: \"\",\n    onChange,\n  });\n\n  const getStyles = useStyles<PasswordStrengthFactory>({\n    name: \"PasswordStrength\",\n    classes,\n    props,\n    className,\n    style,\n    classNames,\n    styles,\n    unstyled,\n    attributes,\n    vars,\n  });\n\n  const strength = getPasswordStrength(passwordValue, requirements, minLength);\n  const barColor = strength > 80 ? \"teal\" : strength > 50 ? \"yellow\" : \"red\";\n  const strengthLabel = strength > 80 ? \"Strong\" : strength > 50 ? \"Fair\" : \"Weak\";\n  const meetsMinLength = passwordValue.length >= minLength;\n  const minLengthLabel = `Has at least ${minLength} character${minLength === 1 ? \"\" : \"s\"}`;\n\n  const bars = [1, 2, 3, 4].map((segment) => (\n    <Progress\n      key={`segment-${segment}`}\n      unstyled={unstyled}\n      value={\n        passwordValue.length > 0 && segment === 1 ? 100 : strength >= (segment / 4) * 100 ? 100 : 0\n      }\n      color={barColor}\n      size={4}\n      transitionDuration={0}\n      aria-label={`Password strength segment ${segment} of 4`}\n      {...getStyles(\"bar\")}\n    />\n  ));\n\n  const checks = requirements.map((requirement) => (\n    <RequirementRow\n      key={requirement.label}\n      label={requirement.label}\n      meets={requirement.re.test(passwordValue)}\n      unstyled={unstyled}\n      getStyles={getStyles}\n    />\n  ));\n\n  return (\n    <Box ref={ref} {...getStyles(\"root\")} {...others}>\n      <PasswordInput\n        unstyled={unstyled}\n        value={passwordValue}\n        onChange={(event) => handleChange(event.currentTarget.value)}\n        label={label}\n        placeholder={placeholder}\n        required={required}\n        {...getStyles(\"input\")}\n      />\n\n      <Group gap={5} grow role=\"group\" aria-label=\"Password strength\" {...getStyles(\"meter\")}>\n        {bars}\n      </Group>\n      <span className={classes.visuallyHidden} aria-live=\"polite\">\n        Password strength: {strengthLabel}\n      </span>\n\n      <div aria-live=\"polite\">\n        <RequirementRow\n          label={minLengthLabel}\n          meets={meetsMinLength}\n          unstyled={unstyled}\n          getStyles={getStyles}\n        />\n        {checks}\n      </div>\n    </Box>\n  );\n});\n\nPasswordStrength.classes = classes;\nPasswordStrength.displayName = \"PasswordStrength\";\n\nexport namespace PasswordStrength {\n  export type Props = PasswordStrengthProps;\n  export type StylesNames = PasswordStrengthStylesNames;\n  export type Factory = PasswordStrengthFactory;\n  export type Requirement = PasswordStrengthRequirement;\n}\n"
    },
    {
      "path": "registry/mantine-ui/password-strength/password-strength.module.css",
      "type": "registry:file",
      "target": "@ui/password-strength.module.css",
      "content": "/*\n * Adapted from Mantine UI's PasswordStrength at\n * ffbf61c559f374a7ea28fcf00355e84dcbe9a908. MIT licensed; see\n * LICENSES/MANTINE-UI.txt after installation.\n */\n.meter {\n  margin-top: var(--mantine-spacing-xs);\n  margin-bottom: var(--mantine-spacing-md);\n}\n\n.requirement {\n  margin-top: 5px;\n}\n\n.requirementLabel {\n  margin-left: 7px;\n}\n\n/* Screen-reader-only: announces the overall strength level without duplicating\n * the visible, color-coded meter for sighted users. */\n.visuallyHidden {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  overflow: hidden;\n  clip: rect(0, 0, 0, 0);\n  white-space: nowrap;\n  border: 0;\n}\n"
    }
  ],
  "meta": {
    "mantine": {
      "requires": ">=9",
      "provider": "MantineProvider",
      "stylesApi": {
        "PasswordStrength": [
          "root",
          "input",
          "meter",
          "bar",
          "requirement",
          "requirementLabel"
        ]
      },
      "props": {
        "PasswordStrength": [
          {
            "name": "value",
            "type": "string",
            "required": false,
            "description": "Controlled value. Omit to let the component manage its own state."
          },
          {
            "name": "defaultValue",
            "type": "string",
            "required": false,
            "default": "\"\"",
            "description": "Initial value when uncontrolled."
          },
          {
            "name": "onChange",
            "type": "(value: string) => void",
            "required": false,
            "description": "Called with the next value on every keystroke, controlled or uncontrolled alike."
          },
          {
            "name": "label",
            "type": "ReactNode",
            "required": false,
            "default": "\"Password\"",
            "description": "PasswordInput label."
          },
          {
            "name": "placeholder",
            "type": "string",
            "required": false,
            "default": "\"Your password\"",
            "description": "PasswordInput placeholder."
          },
          {
            "name": "required",
            "type": "boolean",
            "required": false,
            "default": "true",
            "description": "PasswordInput required flag."
          },
          {
            "name": "minLength",
            "type": "number",
            "required": false,
            "default": "6",
            "description": "Minimum character count the length requirement checks for; replaces upstream's hardcoded > 5."
          },
          {
            "name": "requirements",
            "type": "readonly PasswordStrengthRequirement[]",
            "required": false,
            "default": "defaultPasswordStrengthRequirements",
            "description": "{ re: RegExp; label: string } pairs checked against the value. The exported default mirrors upstream's list; consumers can extend rather than fork it."
          },
          {
            "name": "...others",
            "type": "BoxProps & ElementProps<\"div\", \"onChange\">",
            "description": "Forwarded to the root wrapper; native onChange is excluded so the custom string-valued onChange can't collide with it. Mantine Styles API props (classNames, styles, unstyled) are also accepted."
          }
        ]
      },
      "usage": {
        "path": "registry/mantine-ui/password-strength/password-strength.usage.tsx",
        "content": "import { PasswordStrength } from \"@/components/ui/password-strength\";\n\nexport function SignupPasswordField() {\n  return (\n    <PasswordStrength\n      label=\"Choose a password\"\n      placeholder=\"At least 6 characters\"\n      minLength={8}\n      onChange={(value) => console.log(\"password value\", value)}\n    />\n  );\n}\n"
      }
    }
  }
}
