"use client" /** * Input masking — aligned with [Shadcn Studio input-mask](https://shadcnstudio.com/docs/components/input-mask): * `use-mask-input` for phone / date / time / custom / zip; use `PaymentCardFields` for card flows. */ import * as React from "react" import { useMaskInput, withMask, useHookFormMask, withHookFormMask, useTanStackFormMask, withTanStackFormMask, } from "use-mask-input" import type { Mask, Options } from "use-mask-input" import { composeRefs } from "../../lib/compose-refs" export { composeRefs } from "../../lib/compose-refs" import { cn } from "../../lib/utils" import { Input } from "./input" /** Default mask UX per Shadcn Studio demos (underscore placeholder, no hover mask). */ export const exxatInputMaskDefaults: Options = { placeholder: "_", showMaskOnHover: false, } /** Common patterns (Inputmask `9` = digit, `a` = letter, `*` = alnum). */ export const exxatMaskPatterns = { /** US NANP 10-digit display */ phoneUS: "(999) 999-9999", /** US ZIP or ZIP+4 */ zipUS: "99999[-9999]", /** Calendar-style date (validate separately). */ dateMDY: "99/99/9999", /** Studio input-mask-01 style — UK-style plate-ish token; adjust per product. */ customReference: "AA99 AAA", } as const satisfies Record export function exxatTimeMaskOptions(): Options & { inputFormat: string outputFormat: string } { return { ...exxatInputMaskDefaults, inputFormat: "HH:MM:ss", outputFormat: "HH:MM:ss", } } export function useExxatPhoneMask(options?: Options) { return useMaskInput({ mask: exxatMaskPatterns.phoneUS, options: { ...exxatInputMaskDefaults, ...options }, }) } export function useExxatZipMask(options?: Options) { return useMaskInput({ mask: exxatMaskPatterns.zipUS, options: { ...exxatInputMaskDefaults, ...options }, }) } export function useExxatDateMDYMask(options?: Options) { return useMaskInput({ mask: exxatMaskPatterns.dateMDY, options: { ...exxatInputMaskDefaults, ...options }, }) } export function useExxatTimeMask(options?: Options) { return useMaskInput({ mask: "datetime", options: { ...exxatTimeMaskOptions(), ...options }, }) } export function useExxatCustomMask(mask: Mask, options?: Options) { return useMaskInput({ mask, options: { ...exxatInputMaskDefaults, ...options }, }) } /** Merge React Hook Form Controller `field.ref` with a mask ref. */ export function useExxatMaskedFieldRef( fieldRef: React.Ref, mask: Mask, options?: Options ): React.RefCallback { const maskRef = useMaskInput({ mask, options: { ...exxatInputMaskDefaults, ...options }, }) return React.useMemo(() => composeRefs(fieldRef, maskRef), [fieldRef, maskRef]) } export type MaskedInputProps = React.ComponentProps & { mask: Mask maskOptions?: Options } /** Standalone masked `Input` (forwardRef + merged mask ref). */ const MaskedInput = React.forwardRef(function MaskedInput( { mask, maskOptions, className, type = "text", ...props }, ref ) { const maskRef = useMaskInput({ mask, options: { ...exxatInputMaskDefaults, ...maskOptions }, }) return ( ) }) export { MaskedInput, useMaskInput, withMask, useHookFormMask, withHookFormMask, useTanStackFormMask, withTanStackFormMask, } export type { Mask, Options }