{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "mobile-number-input",
  "title": "Mobile Number Input",
  "description": "An Iranian mobile number input that normalizes mixed paste formats and Persian digits, with a redundant validity indicator.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "@persianlabsui/input-group",
    "@persianlabsui/iranian-mobile"
  ],
  "files": [
    {
      "path": "registry/base/ui/mobile-number-input.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { CheckIcon, CircleAlertIcon } from \"lucide-react\"\n\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupInput,\n} from \"@/components/ui/input-group\"\nimport { useControllableState } from \"@/hooks/use-controllable-state\"\nimport { isValidIranPhone, normalizeIranPhone } from \"@/lib/iranian-mobile\"\nimport { cn } from \"@/lib/utils\"\n\nexport interface MobileNumberInputProps extends Omit<\n  React.ComponentProps<typeof InputGroupInput>,\n  \"value\" | \"defaultValue\" | \"onChange\" | \"type\" | \"dir\"\n> {\n  value?: string\n  defaultValue?: string\n  onValueChange?: (value: string) => void\n}\n\nfunction MobileNumberInput({\n  value,\n  defaultValue = \"\",\n  onValueChange,\n  className,\n  ...props\n}: MobileNumberInputProps) {\n  const [number, setNumber] = useControllableState({\n    prop: value,\n    defaultProp: defaultValue,\n    onChange: onValueChange,\n    caller: \"MobileNumberInput\",\n  })\n  const normalized = normalizeIranPhone(number)\n  const complete = normalized.length === 11\n  const valid = complete && isValidIranPhone(normalized)\n\n  return (\n    // Like CardNumberInput/ShabaInput in bank-input.tsx, a phone number is\n    // display-formatted, non-RTL-meaningful text, so the field always\n    // renders LTR regardless of the surrounding document direction.\n    <InputGroup className={cn(\"h-10\", className)} dir=\"ltr\">\n      <InputGroupInput\n        {...props}\n        dir=\"ltr\"\n        inputMode=\"numeric\"\n        autoComplete=\"tel\"\n        spellCheck={false}\n        translate=\"no\"\n        aria-invalid={complete && !valid}\n        value={normalized}\n        onChange={(event) =>\n          // Runs for both typing and pasting — a paste event updates the\n          // native input's value first, which fires this same handler, so\n          // pasted Persian digits or +98/0098/98-prefixed numbers are\n          // normalized immediately without needing a separate paste handler\n          // (and without ever blocking the paste itself).\n          setNumber(normalizeIranPhone(event.target.value).slice(0, 11))\n        }\n        placeholder=\"0912 123 4567\"\n        className=\"font-mono text-base tracking-wide\"\n      />\n      {complete && (\n        <InputGroupAddon align=\"inline-end\">\n          {valid ? (\n            <CheckIcon\n              role=\"img\"\n              aria-label=\"شماره موبایل معتبر است\"\n              className=\"size-4 text-success\"\n            />\n          ) : (\n            <CircleAlertIcon\n              role=\"img\"\n              aria-label=\"شماره موبایل معتبر نیست\"\n              className=\"size-4 text-destructive\"\n            />\n          )}\n        </InputGroupAddon>\n      )}\n    </InputGroup>\n  )\n}\n\nexport { MobileNumberInput }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/base/hooks/use-controllable-state.ts",
      "content": "// Thanks @radix-ui\n\nimport * as React from \"react\"\n\n// use-layout-effect.tsx\n// https://github.com/radix-ui/primitives/blob/main/packages/react/use-layout-effect/src/use-layout-effect.tsx\n\n/**\n * On the server, React emits a warning when calling `useLayoutEffect`.\n * This is because neither `useLayoutEffect` nor `useEffect` run on the server.\n * We use this safe version which suppresses the warning by replacing it with a noop on the server.\n *\n * See: https://reactjs.org/docs/hooks-reference.html#uselayouteffect\n */\nconst useLayoutEffect = globalThis?.document ? React.useLayoutEffect : () => {}\n\n// use-controllable-state.tsx\n// https://github.com/radix-ui/primitives/blob/main/packages/react/use-controllable-state/src/use-controllable-state.tsx\n\n// Prevent bundlers from trying to optimize the import\nconst useInsertionEffect: typeof useLayoutEffect =\n  (React as never)[\" useInsertionEffect \".trim().toString()] || useLayoutEffect\n\ntype ChangeHandler<T> = (state: T) => void\ntype SetStateFn<T> = React.Dispatch<React.SetStateAction<T>>\n\ninterface UseControllableStateParams<T> {\n  prop?: T | undefined\n  defaultProp: T\n  onChange?: ChangeHandler<T>\n  caller?: string\n}\n\nexport function useControllableState<T>({\n  prop,\n  defaultProp,\n  onChange = () => {},\n  caller,\n}: UseControllableStateParams<T>): [T, SetStateFn<T>] {\n  const [uncontrolledProp, setUncontrolledProp, onChangeRef] =\n    useUncontrolledState({\n      defaultProp,\n      onChange,\n    })\n  const isControlled = prop !== undefined\n  const value = isControlled ? prop : uncontrolledProp\n\n  // OK to disable conditionally calling hooks here because they will always run\n  // consistently in the same environment. Bundlers should be able to remove the\n  // code block entirely in production.\n  /* eslint-disable react-hooks/rules-of-hooks */\n  if (process.env.NODE_ENV !== \"production\") {\n    const isControlledRef = React.useRef(prop !== undefined)\n    React.useEffect(() => {\n      const wasControlled = isControlledRef.current\n      if (wasControlled !== isControlled) {\n        const from = wasControlled ? \"controlled\" : \"uncontrolled\"\n        const to = isControlled ? \"controlled\" : \"uncontrolled\"\n        console.warn(\n          `${caller} is changing from ${from} to ${to}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`\n        )\n      }\n      isControlledRef.current = isControlled\n    }, [isControlled, caller])\n  }\n  /* eslint-enable react-hooks/rules-of-hooks */\n\n  const setValue = React.useCallback<SetStateFn<T>>(\n    (nextValue) => {\n      if (isControlled) {\n        const value = isFunction(nextValue) ? nextValue(prop) : nextValue\n        if (value !== prop) {\n          onChangeRef.current?.(value)\n        }\n      } else {\n        setUncontrolledProp(nextValue)\n      }\n    },\n    [isControlled, prop, setUncontrolledProp, onChangeRef]\n  )\n\n  return [value, setValue]\n}\n\nfunction useUncontrolledState<T>({\n  defaultProp,\n  onChange,\n}: Omit<UseControllableStateParams<T>, \"prop\">): [\n  Value: T,\n  setValue: React.Dispatch<React.SetStateAction<T>>,\n  OnChangeRef: React.RefObject<ChangeHandler<T> | undefined>,\n] {\n  const [value, setValue] = React.useState(defaultProp)\n  const prevValueRef = React.useRef(value)\n\n  const onChangeRef = React.useRef(onChange)\n  useInsertionEffect(() => {\n    onChangeRef.current = onChange\n  }, [onChange])\n\n  React.useEffect(() => {\n    if (prevValueRef.current !== value) {\n      onChangeRef.current?.(value)\n      prevValueRef.current = value\n    }\n  }, [value, prevValueRef])\n\n  return [value, setValue, onChangeRef]\n}\n\nfunction isFunction(value: unknown): value is (...args: never[]) => unknown {\n  return typeof value === \"function\"\n}\n",
      "type": "registry:hook"
    }
  ],
  "type": "registry:ui"
}