elements
lifecycle
browser
- useAudio
- useBattery
- useBluetooth
- useBreakpoints
- useBroadcastChannel
- useBrowserLocation
- useClipboard
- useCopy
- useCssVar
- useDisplayMedia
- useDocumentEvent
- useDocumentTitle
- useDocumentVisibility
- useEventListener
- useEventSource
- useEyeDropper
- useFavicon
- useFileSystemAccess
- useFps
- useFullscreen
- useGamepad
- useGeolocation
- useMeasure
- useMediaControls
- useMediaQuery
- useMemory
- useNetwork
- useObjectUrl
- useOnline
- useOtpCredential
- usePermission
- usePictureInPicture
- usePointerLock
- usePostMessage
- useRaf
- useShare
- useSpeechRecognition
- useSpeechSynthesis
- useSticky
utilities
state
user
sensors
Checkout
Enter your details to complete the payment of $49.00.
import { useField, useMask } from '@siberiacancode/reactuse';
import { cn } from '@/utils/lib';
interface Country {
code: string;
flag: string;
mask: string;
name: string;
}
const COUNTRIES = [
{ code: '7', name: 'Russia', flag: 'π·πΊ', mask: '+9 (999) 999-99-99' },
{ code: '1', name: 'USA', flag: 'πΊπΈ', mask: '+9 (999) 999-9999' },
{ code: '44', name: 'UK', flag: 'π¬π§', mask: '+99 9999 999999' },
{ code: '77', name: 'Kazakhstan', flag: 'π°πΏ', mask: '+99 (999) 999-99-99' },
{ code: '380', name: 'Ukraine', flag: 'πΊπ¦', mask: '+999 (99) 999-99-99' },
{ code: '998', name: 'Uzbekistan', flag: 'πΊπΏ', mask: '+999 (99) 999-99-99' }
] as const;
const DEFAULT_MASK = '999999999999999';
const INITIAL_DATE_VALUE = '05062026';
const SORTED_COUNTRIES = COUNTRIES.toSorted((a, b) => b.code.length - a.code.length);
const detectCountry = (rawValue: string): Country | undefined =>
SORTED_COUNTRIES.find((country) => rawValue.startsWith(country.code));
const Demo = () => {
const name = useField('');
const cvv = useField('');
const phoneMask = useMask(DEFAULT_MASK, {
showMask: 'never',
modify: (rawValue) => ({ mask: detectCountry(rawValue)?.mask ?? DEFAULT_MASK }),
beforeMaskedChange: ({ nextState }) => ({
...nextState,
selection: { start: nextState.value.length, end: nextState.value.length }
})
});
const phone = phoneMask.watch();
const cardNumber = useMask('9999 9999 9999 9999', {
showMask: 'never'
});
const expiry = useMask('99/99', { showMask: 'never' });
const paymentDate = useMask('99/99/9999', {
showMask: 'never',
initialValue: INITIAL_DATE_VALUE
});
const country = detectCountry(phone.rawValue);
return (
<section className='flex w-full max-w-md flex-col gap-4 p-4'>
<div className='flex flex-col gap-1'>
<h2 className='text-foreground text-base font-semibold'>Checkout</h2>
<p className='text-muted-foreground text-xs'>
Enter your details to complete the payment of $49.00.
</p>
</div>
<div className='flex flex-col gap-1.5'>
<label className='text-foreground text-xs font-medium'>Full name</label>
<input
className='border-border bg-card text-foreground rounded-md border px-3 py-2 text-sm outline-none'
placeholder='John Carter'
{...name.register()}
/>
</div>
<div className='flex flex-col gap-1.5'>
<label className='text-foreground text-xs font-medium'>Phone number</label>
<div className='relative'>
{country && (
<span className='pointer-events-none absolute top-1/2 left-3 -translate-y-1/2 text-base'>
{country.flag}
</span>
)}
<input
className={cn(
'border-border bg-card text-foreground w-full rounded-md border py-2 pr-3 text-sm outline-none',
country ? 'pl-10!' : 'pl-3'
)}
inputMode='tel'
placeholder='Start typing with country code'
{...phoneMask.register()}
/>
</div>
{country && <span className='text-muted-foreground px-1 text-[10px]'>{country.name}</span>}
</div>
<div className='flex flex-col gap-3'>
<label className='text-foreground text-xs font-medium'>Card details</label>
<input
className='border-border bg-card text-foreground rounded-md border px-3 py-2 font-mono text-sm tracking-wider outline-none'
inputMode='numeric'
placeholder='1234 5678 9012 3456'
{...cardNumber.register()}
/>
<div className='flex gap-2'>
<input
className='border-border bg-card text-foreground w-full rounded-md border px-3 py-2 font-mono text-sm outline-none'
inputMode='numeric'
placeholder='MM/YY'
{...expiry.register()}
/>
<input
className='border-border bg-card text-foreground w-full rounded-md border px-3 py-2 font-mono text-sm outline-none'
inputMode='numeric'
placeholder='DD/MM/YYYY'
{...paymentDate.register()}
/>
<input
className='border-border bg-card text-foreground w-full rounded-md border px-3 py-2 font-mono text-sm outline-none'
inputMode='numeric'
maxLength={3}
placeholder='CVV'
type='password'
{...cvv.register()}
/>
</div>
<div className='flex items-center justify-end'>
<button data-size='sm' type='button'>
Pay $49.00
</button>
</div>
</div>
</section>
);
};
export default Demo;
Installation
pnpm add @siberiacancode/reactuseUsage
const phoneMask = useMask('+7 (999) 999-99-99');Type Declarations
import type {
ChangeEventHandler,
ClipboardEventHandler,
FocusEventHandler,
KeyboardEventHandler,
MouseEventHandler,
RefObject
} from 'react';
export type UseMaskPattern = string | Array<string | RegExp>;
export interface UseMaskOptions {
/** Clear value on blur when mask is incomplete */
autoClear?: boolean;
/** Initial raw value */
initialValue?: string;
/** Mask pattern string or array of string literals and RegExp objects */
mask: UseMaskPattern;
/** Defines when mask slots are displayed */
showMask?: UseMaskShow;
/** Character displayed in unfilled slot */
slot?: string;
/** Override or extend the default token map */
tokens?: Record<string, RegExp>;
/** Escape hatch for advanced cursor/value manipulation */
beforeMaskedChange?: (states: {
previousState: MaskState;
currentState: MaskState;
nextState: MaskState;
}) => MaskState;
/** Called before masking on each keystroke, can return overrides for mask options */
modify?: (
value: string
) => Partial<Pick<UseMaskOptions, 'mask' | 'showMask' | 'slot' | 'tokens'>>;
/** Called on every change with raw and masked values */
onChangeRaw?: (rawValue: string, maskedValue: string) => void;
/** Called when all required mask slots are filled */
onFilled?: (maskedValue: string, rawValue: string) => void;
/** Transform each character before validation and insertion */
transform?: (char: string) => string;
}
export interface MaskState {
selection: { start: number; end: number } | null;
value: string;
}
export interface UseMaskRegisterParams {
/** The blur event handler */
onBlur?: FocusEventHandler<HTMLInputElement>;
/** The change event handler */
onChange?: ChangeEventHandler<HTMLInputElement>;
/** The focus event handler */
onFocus?: FocusEventHandler<HTMLInputElement>;
/** The key down event handler */
onKeyDown?: KeyboardEventHandler<HTMLInputElement>;
/** The mouse down event handler */
onMouseDown?: MouseEventHandler<HTMLInputElement>;
/** The mouse up event handler */
onMouseUp?: MouseEventHandler<HTMLInputElement>;
/** The paste event handler */
onPaste?: ClipboardEventHandler<HTMLInputElement>;
}
export type UseMaskShow = 'always' | 'filled' | 'focus' | 'never';
export type UseMaskGetValueType = 'display' | 'masked' | 'raw';
export interface UseMaskGetValueMap {
display: string;
masked: string;
raw: string;
}
export interface UseMaskValue {
/** Current value displayed in the input */
displayValue: string;
/** Whether all required mask slots are filled */
filled: boolean;
/** Current masked value without placeholder slots */
maskedValue: string;
/** Current raw unmasked value */
rawValue: string;
/** Current value displayed in the input */
value: string;
}
export interface UseMaskReturn {
/** The input ref */
ref: RefObject<HTMLInputElement | null>;
/** Get current mask value */
getValue: <Type extends UseMaskGetValueType = 'raw'>(type?: Type) => UseMaskGetValueMap[Type];
/** Register the masked input */
register: (params?: UseMaskRegisterParams) => {
onBlur?: FocusEventHandler<HTMLInputElement>;
onChange?: ChangeEventHandler<HTMLInputElement>;
onFocus?: FocusEventHandler<HTMLInputElement>;
onKeyDown?: KeyboardEventHandler<HTMLInputElement>;
onMouseDown?: MouseEventHandler<HTMLInputElement>;
onMouseUp?: MouseEventHandler<HTMLInputElement>;
onPaste?: ClipboardEventHandler<HTMLInputElement>;
ref: (node: HTMLInputElement | null | undefined) => void;
};
/** Clear the input value and reset state */
reset: () => void;
/** Set the raw input value */
setValue: (value: string) => void;
/** The watch function */
watch: () => UseMaskValue;
}
export type UseMaskReturnValue = UseMaskReturn;
interface MaskLiteralSlot {
char: string;
optional?: boolean;
type: 'literal';
}
interface MaskTokenSlot {
char: string;
optional?: boolean;
pattern: RegExp;
type: 'token';
}
export type MaskSlot = MaskLiteralSlot | MaskTokenSlot;
interface UndoState {
rawValue: string;
selectionStart: number;
}API
Parameters
| Name | Type | Default | Note |
|---|---|---|---|
| mask | UseMaskPattern | - | Mask pattern string or array of literals and RegExp tokens |
| options | Omit<UseMaskOptions, 'mask'> | - | The hook options when mask is passed as the first argument |
| options.autoClear | boolean | false | Clear value on blur when mask is incomplete |
| options.initialValue | string | "" | Initial raw value |
| options.modify | (value: string) => Partial<Pick<UseMaskOptions, 'mask' | 'showMask' | 'slot' | 'tokens'>> | - | Called before masking and can return dynamic mask option overrides |
| options.showMask | UseMaskShow | "focus" | Defines when placeholder slots are displayed |
| options.slot | string | null | "" | Character displayed in unfilled slots |
| options.tokens | Record<string, RegExp> | - | Override or extend the default token map |
| options.beforeMaskedStateChange | (states: { previousState: MaskState; currentState: MaskState; nextState: MaskState }) => MaskState | - | Escape hatch for advanced cursor/value manipulation |
| options.onChangeRaw | (rawValue: string, maskedValue: string) => void | - | Called on every change with raw and display values |
| options.onFilled | (maskedValue: string, rawValue: string) => void | - | Called when all required mask slots are filled |
| options.transform | (char: string) => string | - | Transform each character before validation and insertion |
Returns
UseMaskReturnContributors
ddebabin
Last updated on