بیایید ورودی شماره تلفن بین المللی را با React ، Tailwind CSS و UI بدون سر ایجاد کنیم

در روز ، کاربران مجبور بودند برای اتصال به یک شماره تلفن یا آدرس به یک کارمند شرکت تلفن فریاد بزنند. اگرچه امروزه ، این رویکرد دوباره در بین سیستم های مدیریت تماس خودکار محبوبیت پیدا کرد ، اما ورودی های تلفنی پیشرفت بزرگی را کسب کردند و از معاملات پلاستیکی دور با 10 رقم تا رابط های مدرن روی صفحه نمایش حاصل شد.
امروز می خواهیم یکی از آنها را بسازیم. این ورودی قادر خواهد بود:
-
ادغام کد بین المللی کشوربشر ما یک انتخاب کننده کد کشور واضح و به راحتی در دسترس را ارائه خواهیم داد ، که برای پایگاه های جهانی کاربر ضروری است. این ویژگی قالب بندی دقیق شماره را تضمین می کند.
-
پوشش ورودیبشر استفاده از ماسک های ورودی با هدایت کاربران از طریق فرمت شماره صحیح ، روند ورود را ساده می کند. این باعث کاهش خطاها و بهبود خوانایی می شود.
-
ناوبری لیست کشور در دسترسبشر ما قصد داریم تا از چندین کشور انتخابی را به کاربران ارائه دهیم. بنابراین آنها باید بتوانند بدون زحمت در لیست حرکت کنند و فیلتر کنند.
یک مجموعه فیلد تلفن ایجاد کنید
در اینجا تجزیه و تحلیل کل مؤلفه شماره تلفن بین المللی FieldSet ارائه شده است.
CountryCodeInput
برای انتخاب کدهای کشور ، کشویی جعبه لیست را فراهم می کند. PhoneNumberInput
با اعتبار سنجی فرمت از ورود شماره تلفن مراقبت می کند. Field
از UI بدون سر ساختار درستی را فراهم می کند ، اما ما نمی توانیم از کتابخانه استفاده کنیم Label
مؤلفه ، بنابراین ما باید اساسی را پیاده سازی کنیم HTMLLabelElement
بشر
import { Field } from '@headlessui/react';
import { countryList } from './countryList.ts';
import { CountryCodeInput } from './CountryCodeInput';
import { PhoneNumberInput } from './PhoneNumberInput';
const App = () => {
// ...
return (
<Field className="flex flex-col gap-2">
<label className="cursor-pointer text-sm/6 font-medium" htmlFor={id}>
Phone number:
label>
<div className="flex gap-3">
<CountryCodeInput
countryList={countryList}
value={countryCode}
onChange={handleCodeChange}
/>
<PhoneNumberInput
id={id}
onChange={handlePhoneChange}
value={phoneNumber}
/>
div>
Field>
);
};
export default App;
مؤلفه اصلی (src/App.tsx
) دو متغیر حالت را حفظ می کند:phoneNumber
که شماره تلفن وارد شده کاربر را ذخیره می کندcountryCode
که کد انتخاب شده کشور را ردیابی می کند (پیش فرض به "+1"
).
import { useCallback, useState } from 'react';
import { countryList, CountryConfig } from './countryList.ts';
// phone number logic
const [phoneNumber, setPhoneNumber] = useState('');
const handlePhoneChange = useCallback((value: string) => {
setPhoneNumber(value);
}, []);
// country code logic
const [countryCode, setCountryCode] = useState<CountryConfig['code']>('+1');
const handleCodeChange = useCallback((nextCode?: CountryConfig['code']) => {
if (nextCode) {
setCountryCode(nextCode);
}
}, []);
پیکربندی کدهای کشور
درون src/countryList.ts
ما داده های لازم را برای انتخاب انتخاب بین کدهای تلفن چند کشور و فرمت ها تعریف خواهیم کرد.
CountryConfig
نوع ساختار برای هر کشور را با: تعریف می کند:
-
flag
: نمایندگی ایموجی پرچم یونیکد (به عنوان مثال ، “🇫🇷” برای فرانسه). -
code
: کد شماره گیری کشور (به عنوان مثال ، “+33” برای فرانسه). یک الگوی تحت اللفظی که تضمین می کند همه کدهای کشور با یک نماد + شروع می شوند و به دنبال آن اعداد یا شخصیت های دیگر. -
name
: نام کامل کشور. -
mask
: الگوی قالب بندی اختیاری برای شماره تلفن. اطلاعات بیشتر در مورد این بعداً
type Code = `+${number | string}`;
export type CountryConfig = {
flag: string;
code: Code;
name: string;
mask?: string;
};
ورودی شماره تلفن
ورودی اصلی قسمت تلفن در src/PhoneNumberInput/PhoneNumberInput.tsx
بشر مؤلفه چهار غرفه را می پذیرد: id
: یک شناسه منحصر به فرد برای عنصر ورودی ؛ mask
: یک رشته اختیاری که ماسک ورودی را تعریف می کند. onChange
: یک تابع پاسخ به تماس برای رسیدگی به تغییرات در مقدار ورودی. value
مقدار فعلی ورودی است.
ما از کلاسهای زیر Tailwind CSS استفاده خواهیم کرد: tabular-nums
: عرض شخصیت شماره سازگار ، focus:outline-none data-[focus]:outline-2 ...
: تنظیم مجدد طرح پیش فرض و اضافه کردن یک سفارشی.
import { ChangeEvent, FC, useCallback } from 'react';
import classNames from 'classnames';
import { Input } from '@headlessui/react';
import { useMask } from './useMask.ts';
export type Props = {
id: string;
mask?: string;
onChange: (value: string) => void;
value: string;
};
export const PhoneNumberInput: FC<Props> = ({
id,
mask = '______________',
onChange,
value: valueProp,
}) => {
const { options, hasEmptyMask, inputRef } = useMask({ mask });
return (
<Input
placeholder={hasEmptyMask ? 'Phone number' : mask}
ref={inputRef}
id={id}
className={classNames(
'w-36 rounded-md bg-stone-100 px-2 py-1.5 text-sm/6 tabular-nums text-black',
'focus:outline-none data-[focus]:outline-2 data-[focus]:-outline-offset-2 data-[focus]:outline-black/25',
)}
type="tel"
onChange={handleChange}
value={value}
/>
);
};
ماسک ورودی تلفن
ماسک ورودی کاربر را راهنمایی می کند تا شماره تلفن خود را با فرمت سازگار و صحیح وارد کند. به عنوان نوع کاربر ، قسمت ورودی به طور خودکار کاراکترهایی مانند پرانتز ، هافن و فضاها را در موقعیت های مناسب وارد می کند.
درون PhoneNumberInput
ما از format
ابزار از کتابخانه @React-Input/Mask برای تمیز کردن مقدار خروجی از نمادهای ماسک.
import { format } from '@react-input/mask';
// ...
const value = format(valueProp, options);
const handleChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
onChange(format(event.target.value, options));
},
[onChange, options],
);
در src/PhoneNumberInput/useMask.ts
از قلاب سفارشی برای اجرای منطق ماسک ورودی استفاده می شود. برمی گردد options
برای ماسک ، یک بولی hasEmptyMask
برای بررسی اینکه آیا ماسک خالی است ، و inputRef
برای مراجعه به عنصر ورودی.
// src/PhoneNumberInput/PhoneNumberInput.tsx
const { options, hasEmptyMask, inputRef } = useMask({ mask });
قلاب یک پروانه واحد را می پذیرد (mask
) ، که یک رشته است که الگوی ماسک ورودی مورد نظر را تعریف می کند. این الگوی مشخص می کند که چگونه ورودی باید فرمت شود ، با تأکیدات ()_
) نمایندگان متقاضیان رقمی.
درون قلاب ، useMemo
برای ایجاد گزینه های ماسک استفاده می شود. این تضمین می کند که آنها فقط در صورت محاسبه مجدد محاسبه می شوند mask
تغییر عملکرد ، بهینه سازی عملکرد. شیء جایگزینی در گزینه ها مشخص می کند که باید زیر نظر را با رقم جایگزین کرد /\d/
بشر
در useMaskVanilla
عملکرد از @react-input/mask
کتابخانه با گزینه های ماسک فراخوانی می شود تا مرجع عنصر ورودی ایجاد شود.
قلاب همچنین شامل یک چک برای تعیین اینکه آیا ماسک ارائه شده خالی است یا خیر. این کار با تقسیم رشته ماسک در کاراکترهای جداگانه و بررسی اینکه آیا همه شخصیت ها تأکید می کنند ، انجام می شود. نتیجه در hasEmptyMask
متغیر
import { useMemo } from 'react';
import { useMask as useMaskVanilla, } from '@react-input/mask';
export type Props = {
mask: string;
};
export const useMask = ({ mask }: Props) => {
const options = useMemo(
() => ({
mask,
replacement: { _: /\d/ },
}),
[mask],
);
const inputRef = useMaskVanilla(options);
const hasEmptyMask = mask.split('').every((char) => {
return char === '_';
});
return { options, inputRef, hasEmptyMask };
};
ورودی انتخاب کد کشور
یک کد کشور تلفن ، که به عنوان کد شماره گیری مشترک بین المللی (ISD) نیز شناخته می شود ، پیشوند عددی است که پیش از یک شماره تلفن است. عملکرد اصلی آن مسیریابی تماس ها و پیام ها به کشور صحیح یا منطقه جغرافیایی است.
src/CountryCodeInput/CountryCodeInput.tsx
سه پیشنهاد را می پذیرد: countryList
با value
وت onChange
بشر در countryList
مجموعه ای از تنظیمات کشور است ، value
کد کشور منتخب است (+351
)) ، و onChange
یک تابع پاسخ به تماس برای رسیدگی به تغییرات در کد انتخاب شده کشور است.
export type Props = {
countryList: CountryConfig[];
value: CountryConfig['code'];
onChange: (value?: CountryConfig['code']) => void;
};
CountryCodeInput
یک وضعیت داخلی را برای لیست کشور حفظ می کند ، که با countryList
هر زمان که دومی تغییر کند ، خاصیت و به روز می شود.
const [countryList, setCountryList] = useState(countryListProp);
useEffect(() => {
setCountryList(countryListProp);
}, [countryListProp]);
ما ورودی کد کشور را به عنوان ترکیبی از Listbox و مؤلفه های ورودی از UI بدون سر پیاده سازی خواهیم کرد.
در Listbox
از مؤلفه برای ایجاد منوی کشویی استفاده می شود. در ListboxButton
کد و پرچم کشور منتخب را نشان می دهد ، و ListboxOptions
شامل لیست گزینه های کشور است. این همان چیزی است که کاربر در ابتدا می بیند.
در Input
در صورت وجود بیش از پنج کشور ، از مؤلفه برای فیلتر کردن لیست کشور استفاده می شود.
ما کلاس گروه Tailwind را به ListboxOption
برای نمایش یک علامت گذاری برای نشان دادن انتخاب فعلی.
<div className="w-24">
<Listbox value={selected} onChange={handleSelect}>
<ListboxButton
className={classNames(
'relative w-full rounded-md bg-stone-100 px-2 py-1.5 text-sm/6 text-black',
'flex items-center gap-1',
'focus:outline-none data-[focus]:outline-2 data-[focus]:-outline-offset-2 data-[focus]:outline-black/25',
)}
>
<span>{selectedFlag}span>
<span className="ml-auto grow-0">{selected}span>
<Icon className="group pointer-events-none size-3.5 shrink-0 text-black/60" name="caret-down" />
ListboxButton>
<ListboxOptions
anchor={ANCHOR_PROP}
transition
className={classNames(
'w-56 rounded-md border border-stone-300 bg-stone-100 focus-visible:outline-none',
'flex flex-col',
'origin-top transition duration-200 ease-in data-[closed]:scale-95 data-[closed]:opacity-0',
'shadow-md shadow-black/20',
)}
>
{countryListProp.length > 5 && (
<div className=" border-b-2 border-b-black/10 p-2">
<Input
value={filter}
onChange={setFilter}
placeholder="Country name"
className={classNames(
'max-w-full rounded-full bg-stone-200 px-4 py-1 text-sm/6 text-black ',
'focus:bg-white focus:outline-none data-[focus]:outline-2 data-[focus]:-outline-offset-2 data-[focus]:outline-black/25',
)}
/>
div>
)}
<div className="max-h-48 grow overflow-auto">
{countryList.map(({ code, flag, name }) => (
<ListboxOption
key={code}
value={code}
className="group flex cursor-pointer select-none items-center gap-1 p-1.5 data-[focus]:bg-black/10"
>
<div className="size-4">
<Icon className="invisible size-4 text-black group-data-[selected]:visible" name="check" />
div>
{flag}
<div className="w-9 shrink-0 text-right text-sm/6 tabular-nums text-black">
{code}
div>
<div className="truncate text-sm/6 text-black/65 group-data-[hover]:text-black">
{name}
div>
ListboxOption>
))}
div>
ListboxOptions>
Listbox>
div>
انتخاب کشور
src/CountryCodeInput/useCountrySelect.ts
قلاب سفارشی منطق انتخاب لیست کشور را مدیریت می کند. این فراهم می کند selectedFlag
با handleSelect
وت selected
مقادیر رسیدگی به انتخاب و نمایش کشور منتخب.
useCountrySelect
همان والدین را می پذیرد CountryCodeInput
بشر NB! ما استفاده می کنیم countryList
ارزش ملک ، نه وضعیت داخلی به همین نام. این مورد برای سازگاری با عملکرد فیلتر لازم است.
// src/CountryCodeInput/CountryCodeInput.tsx
const { selectedFlag, handleSelect, selected } = useCountrySelect({
onChange,
countryListProp,
value,
});
useCountrySelect
یک حالت داخلی را انتخاب می کند که برای پیگیری کد کشور انتخاب شده در حال حاضر انتخاب شده است. این حالت با مقدار ارزش اولیه تنظیم می شود و هر زمان که مقدار ارزش تغییر می کند به روز می شود.
handleSelect
عملکرد حالت انتخاب شده را به روز می کند و تماس می گیرد onChange
تماس با کد کشور تازه انتخاب شده.
selectedFlag
متغیر با استفاده از useMemo
قلاب این پرچم کشور منتخب را با جستجوی countryListProp
آرایه برای کشور با کد تطبیق.
سرانجام ، قلاب این مقادیر را برای استفاده از مؤلفه والدین برای مدیریت انتخاب و نمایش کشور انتخاب شده باز می گرداند.
import {
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { CountryConfig } from '../countryList.ts';
export type Props = {
countryListProp: CountryConfig[];
value: CountryConfig['code'];
onChange: (value?: CountryConfig['code']) => void;
};
export const useCountrySelect = ({
value,
onChange,
countryListProp,
}: Props) => {
const [selected, setSelected] = useState(value);
useEffect(() => {
setSelected(value);
}, [value]);
const handleSelect = useCallback(
(selectedCode: CountryConfig['code']) => {
onChange(selectedCode);
setSelected(selectedCode);
},
[onChange],
);
const selectedFlag = useMemo(
() => countryListProp.find(({ code }) => code === selected)?.flag,
[countryListProp, selected],
);
return { selected, handleSelect, selectedFlag };
};
لیست کشور فیلتر
src/CountryCodeInput/useCountryFilter.ts
قلاب سفارشی برای فیلتر کردن لیست کشور بر اساس ورودی کاربر استفاده می شود. آن را فراهم می کند filter
حالت و الف setFilter
عملکرد برای به روزرسانی مقدار فیلتر.
useCountryFilter
دو غرفه را می پذیرد: countryListProp
(همان فوق) و setCountryList
: تابعی برای به روزرسانی لیست کشور فیلتر شده.
// src/CountryCodeInput/CountryCodeInput.tsx
const { filter, setFilter } = useCountryFilter({
setCountryList,
countryListProp,
});
useCountryFilter
حالت داخلی را حفظ می کند filter
برای پیگیری مقدار فیلتر فعلی. این حالت به عنوان یک رشته خالی آغاز می شود.
setFilter
عملکرد به روز می کند filter
دولت و فیلتر countryListProp
بر اساس ورودی کاربر. اگر ورودی خالی نباشد ، این لیست را فیلتر می کند تا فقط کشورهایی را شامل شود که نام آنها حاوی مقدار ورودی (حساس به مورد) است. اگر ورودی خالی باشد ، لیست را به نسخه اصلی تنظیم می کند countryListProp
بشر
import { ChangeEvent, useCallback, useState, Dispatch, SetStateAction } from 'react';
import { CountryConfig } from '../countryList.ts';
export type Props = {
countryListProp: CountryConfig[];
setCountryList: Dispatch<SetStateAction<CountryConfig[]>>;
}
export const useCountryFilter = ({setCountryList, countryListProp}: Props) => {
const [filter, setFilterState] = useState('');
const setFilter = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
setFilterState(event.target.value);
const nextCountries = countryListProp.filter(({ name }) =>
name.toLowerCase().includes(event.target.value.toLowerCase()),
);
if (event.target.value !== '') {
setCountryList(nextCountries);
} else {
setCountryList(countryListProp);
}
},
[countryListProp, setCountryList],
);
return {filter, setFilter}
}
سرانجام ، ما این بلوک را به src/CountryCodeInput/CountryCodeInput.tsx
برای رسیدگی به نتایج فیلتر خالی.
{countryList.length === 0 && (
<div className="py-1.5 text-center text-sm/6 text-black/65">
No results.
div>
)}
نسخه آزمایشی
در اینجا نمایشی کامل از مؤلفه ورودی تلفن بین المللی ارائه شده است.
برنامه نویسی مبارک!