114 lines
3.5 KiB
TypeScript
114 lines
3.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { IMaskInput } from "react-imask";
|
|
import {
|
|
InputBase,
|
|
type InputBaseProps,
|
|
type PolymorphicComponentProps,
|
|
} from "@mantine/core";
|
|
import { useUncontrolled } from "@mantine/hooks";
|
|
import CountrySelect from "@/components/PhoneInput/components/CountrySelect";
|
|
import getFormat from "@/components/PhoneInput/utils/getFormat";
|
|
import getInitialDataFromValue from "@/components/PhoneInput/utils/getInitialDataFromValue";
|
|
|
|
export type Props = {
|
|
initialCountryCode?: string;
|
|
defaultValue?: string;
|
|
} & Omit<
|
|
PolymorphicComponentProps<typeof IMaskInput, InputBaseProps>,
|
|
"onChange" | "defaultValue"
|
|
> & { onChange: (value: string | null) => void };
|
|
|
|
const PhoneInput = ({
|
|
initialCountryCode = "RU",
|
|
value: _value,
|
|
onChange: _onChange,
|
|
defaultValue,
|
|
...props
|
|
}: Props) => {
|
|
const [value, onChange] = useUncontrolled({
|
|
value: _value,
|
|
defaultValue,
|
|
onChange: _onChange,
|
|
});
|
|
const initialData = useRef(
|
|
getInitialDataFromValue(value, initialCountryCode)
|
|
);
|
|
const [country, setCountry] = useState(initialData.current.country);
|
|
const [format, setFormat] = useState(initialData.current.format);
|
|
const [localValue, setLocalValue] = useState(
|
|
initialData.current.localValue
|
|
);
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
|
|
const lastNotifiedValue = useRef<string | null>(value ?? "");
|
|
|
|
useEffect(() => {
|
|
const value = localValue.trim();
|
|
if (value !== lastNotifiedValue.current) {
|
|
lastNotifiedValue.current = value;
|
|
onChange(value);
|
|
}
|
|
}, [country.code, localValue]);
|
|
|
|
useEffect(() => {
|
|
if (
|
|
typeof value !== "undefined" &&
|
|
value !== lastNotifiedValue.current
|
|
) {
|
|
const initialData = getInitialDataFromValue(
|
|
value,
|
|
initialCountryCode
|
|
);
|
|
lastNotifiedValue.current = value;
|
|
setCountry(initialData.country);
|
|
setFormat(initialData.format);
|
|
setLocalValue(initialData.localValue);
|
|
}
|
|
}, [value]);
|
|
|
|
const { readOnly, disabled } = props;
|
|
const leftSectionWidth = 90;
|
|
|
|
return (
|
|
<InputBase
|
|
{...props}
|
|
component={IMaskInput}
|
|
inputRef={inputRef}
|
|
leftSection={
|
|
<CountrySelect
|
|
disabled={disabled || readOnly}
|
|
country={country}
|
|
setCountry={country => {
|
|
setCountry(country);
|
|
setFormat(getFormat(country.code));
|
|
setLocalValue("");
|
|
if (inputRef.current) {
|
|
inputRef.current.focus();
|
|
}
|
|
}}
|
|
leftSectionWidth={leftSectionWidth}
|
|
/>
|
|
}
|
|
leftSectionWidth={leftSectionWidth}
|
|
styles={{
|
|
input: {
|
|
fontSize: 17,
|
|
paddingLeft: `calc(${leftSectionWidth}px + var(--mantine-spacing-sm))`,
|
|
},
|
|
section: {
|
|
borderRight:
|
|
"1px solid var(--mantine-color-default-border)",
|
|
},
|
|
}}
|
|
inputMode={"numeric"}
|
|
mask={format.mask}
|
|
value={localValue}
|
|
onAccept={value => setLocalValue(value)}
|
|
/>
|
|
);
|
|
};
|
|
|
|
export default PhoneInput;
|