combobox-phone-number

PreviousNext
Docs
reuicomponent

Preview

Loading previewโ€ฆ
registry/default/components/combobox/phone-number.tsx
'use client';

import * as React from 'react';
import { Button, ButtonArrow } from '@/registry/default/ui/button';
import {
  Command,
  CommandCheck,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from '@/registry/default/ui/command';
import { Input } from '@/registry/default/ui/input';
import { Popover, PopoverContent, PopoverTrigger } from '@/registry/default/ui/popover';
import { ScrollArea } from '@/registry/default/ui/scroll-area';

const countries = [
  { code: 'US', name: 'United States', dialCode: '+1', flag: '๐Ÿ‡บ๐Ÿ‡ธ' },
  { code: 'GB', name: 'United Kingdom', dialCode: '+44', flag: '๐Ÿ‡ฌ๐Ÿ‡ง' },
  { code: 'CA', name: 'Canada', dialCode: '+1', flag: '๐Ÿ‡จ๐Ÿ‡ฆ' },
  { code: 'AU', name: 'Australia', dialCode: '+61', flag: '๐Ÿ‡ฆ๐Ÿ‡บ' },
  { code: 'DE', name: 'Germany', dialCode: '+49', flag: '๐Ÿ‡ฉ๐Ÿ‡ช' },
  { code: 'FR', name: 'France', dialCode: '+33', flag: '๐Ÿ‡ซ๐Ÿ‡ท' },
  { code: 'IT', name: 'Italy', dialCode: '+39', flag: '๐Ÿ‡ฎ๐Ÿ‡น' },
  { code: 'ES', name: 'Spain', dialCode: '+34', flag: '๐Ÿ‡ช๐Ÿ‡ธ' },
  { code: 'NL', name: 'Netherlands', dialCode: '+31', flag: '๐Ÿ‡ณ๐Ÿ‡ฑ' },
  { code: 'BE', name: 'Belgium', dialCode: '+32', flag: '๐Ÿ‡ง๐Ÿ‡ช' },
  { code: 'CH', name: 'Switzerland', dialCode: '+41', flag: '๐Ÿ‡จ๐Ÿ‡ญ' },
  { code: 'AT', name: 'Austria', dialCode: '+43', flag: '๐Ÿ‡ฆ๐Ÿ‡น' },
  { code: 'SE', name: 'Sweden', dialCode: '+46', flag: '๐Ÿ‡ธ๐Ÿ‡ช' },
  { code: 'NO', name: 'Norway', dialCode: '+47', flag: '๐Ÿ‡ณ๐Ÿ‡ด' },
  { code: 'DK', name: 'Denmark', dialCode: '+45', flag: '๐Ÿ‡ฉ๐Ÿ‡ฐ' },
  { code: 'FI', name: 'Finland', dialCode: '+358', flag: '๐Ÿ‡ซ๐Ÿ‡ฎ' },
  { code: 'PL', name: 'Poland', dialCode: '+48', flag: '๐Ÿ‡ต๐Ÿ‡ฑ' },
  { code: 'CZ', name: 'Czech Republic', dialCode: '+420', flag: '๐Ÿ‡จ๐Ÿ‡ฟ' },
  { code: 'HU', name: 'Hungary', dialCode: '+36', flag: '๐Ÿ‡ญ๐Ÿ‡บ' },
  { code: 'PT', name: 'Portugal', dialCode: '+351', flag: '๐Ÿ‡ต๐Ÿ‡น' },
  { code: 'GR', name: 'Greece', dialCode: '+30', flag: '๐Ÿ‡ฌ๐Ÿ‡ท' },
  { code: 'TR', name: 'Turkey', dialCode: '+90', flag: '๐Ÿ‡น๐Ÿ‡ท' },
  { code: 'RU', name: 'Russia', dialCode: '+7', flag: '๐Ÿ‡ท๐Ÿ‡บ' },
  { code: 'JP', name: 'Japan', dialCode: '+81', flag: '๐Ÿ‡ฏ๐Ÿ‡ต' },
  { code: 'KR', name: 'South Korea', dialCode: '+82', flag: '๐Ÿ‡ฐ๐Ÿ‡ท' },
  { code: 'CN', name: 'China', dialCode: '+86', flag: '๐Ÿ‡จ๐Ÿ‡ณ' },
  { code: 'IN', name: 'India', dialCode: '+91', flag: '๐Ÿ‡ฎ๐Ÿ‡ณ' },
  { code: 'SG', name: 'Singapore', dialCode: '+65', flag: '๐Ÿ‡ธ๐Ÿ‡ฌ' },
  { code: 'HK', name: 'Hong Kong', dialCode: '+852', flag: '๐Ÿ‡ญ๐Ÿ‡ฐ' },
  { code: 'TW', name: 'Taiwan', dialCode: '+886', flag: '๐Ÿ‡น๐Ÿ‡ผ' },
  { code: 'MY', name: 'Malaysia', dialCode: '+60', flag: '๐Ÿ‡ฒ๐Ÿ‡พ' },
  { code: 'TH', name: 'Thailand', dialCode: '+66', flag: '๐Ÿ‡น๐Ÿ‡ญ' },
  { code: 'PH', name: 'Philippines', dialCode: '+63', flag: '๐Ÿ‡ต๐Ÿ‡ญ' },
  { code: 'ID', name: 'Indonesia', dialCode: '+62', flag: '๐Ÿ‡ฎ๐Ÿ‡ฉ' },
  { code: 'VN', name: 'Vietnam', dialCode: '+84', flag: '๐Ÿ‡ป๐Ÿ‡ณ' },
  { code: 'BR', name: 'Brazil', dialCode: '+55', flag: '๐Ÿ‡ง๐Ÿ‡ท' },
  { code: 'MX', name: 'Mexico', dialCode: '+52', flag: '๐Ÿ‡ฒ๐Ÿ‡ฝ' },
  { code: 'AR', name: 'Argentina', dialCode: '+54', flag: '๐Ÿ‡ฆ๐Ÿ‡ท' },
  { code: 'CL', name: 'Chile', dialCode: '+56', flag: '๐Ÿ‡จ๐Ÿ‡ฑ' },
  { code: 'CO', name: 'Colombia', dialCode: '+57', flag: '๐Ÿ‡จ๐Ÿ‡ด' },
  { code: 'PE', name: 'Peru', dialCode: '+51', flag: '๐Ÿ‡ต๐Ÿ‡ช' },
  { code: 'ZA', name: 'South Africa', dialCode: '+27', flag: '๐Ÿ‡ฟ๐Ÿ‡ฆ' },
  { code: 'EG', name: 'Egypt', dialCode: '+20', flag: '๐Ÿ‡ช๐Ÿ‡ฌ' },
  { code: 'NG', name: 'Nigeria', dialCode: '+234', flag: '๐Ÿ‡ณ๐Ÿ‡ฌ' },
  { code: 'KE', name: 'Kenya', dialCode: '+254', flag: '๐Ÿ‡ฐ๐Ÿ‡ช' },
  { code: 'IL', name: 'Israel', dialCode: '+972', flag: '๐Ÿ‡ฎ๐Ÿ‡ฑ' },
  { code: 'AE', name: 'United Arab Emirates', dialCode: '+971', flag: '๐Ÿ‡ฆ๐Ÿ‡ช' },
  { code: 'SA', name: 'Saudi Arabia', dialCode: '+966', flag: '๐Ÿ‡ธ๐Ÿ‡ฆ' },
];

export default function PhoneNumberInputDemo() {
  const [open, setOpen] = React.useState(false);
  const [selectedCountry, setSelectedCountry] = React.useState(countries[0]);
  const [phoneNumber, setPhoneNumber] = React.useState('');

  // Handle phone number input change
  const handlePhoneChange = (inputValue: string) => {
    // Remove any non-digit characters except + at the start
    const cleanValue = inputValue.replace(/[^\d+]/g, '');

    // Check if user is typing a country code
    if (cleanValue.startsWith('+')) {
      const matchedCountry = countries.find((country) => cleanValue.startsWith(country.dialCode));

      if (matchedCountry && matchedCountry.code !== selectedCountry.code) {
        setSelectedCountry(matchedCountry);
        setPhoneNumber(cleanValue.slice(matchedCountry.dialCode.length));
        return;
      }
    }

    setPhoneNumber(cleanValue.startsWith('+') ? cleanValue : cleanValue);
  };

  // Handle country selection
  const handleCountrySelect = (countryCode: string) => {
    const country = countries.find((c) => c.code === countryCode);
    if (country) {
      setSelectedCountry(country);
      setOpen(false);
    }
  };

  return (
    <div className="flex items-center w-[300px]">
      {/* Country Selector */}
      <Popover open={open} onOpenChange={setOpen}>
        <PopoverTrigger asChild>
          <Button
            variant="outline"
            role="combobox"
            aria-expanded={open}
            className="w-24 items-center justify-between rounded-e-none border-e-0 bg-transparent"
          >
            <span className="flex items-center gap-1.5">
              <span className="text-sm leading-none">{selectedCountry.flag}</span>
              <span className="text-xs leading-none">{selectedCountry.dialCode}</span>
            </span>
            <ButtonArrow />
          </Button>
        </PopoverTrigger>
        <PopoverContent className="w-[300px] p-0" align="start">
          <Command>
            <CommandInput placeholder="Search country..." />
            <CommandList>
              <ScrollArea className="h-[300px]">
                <CommandEmpty>No country found.</CommandEmpty>
                <CommandGroup>
                  {countries.map((country) => (
                    <CommandItem
                      key={country.code}
                      value={`${country.name} ${country.dialCode}`}
                      onSelect={() => handleCountrySelect(country.code)}
                    >
                      <span className="flex items-center gap-1.5 leading-none">
                        <span className="text-sm">{country.flag}</span>
                        <span className="text-sm text-foreground truncate">{country.name}</span>
                        <span className="text-sm text-muted-foreground">{country.dialCode}</span>
                      </span>
                      {selectedCountry.code === country.code && <CommandCheck />}
                    </CommandItem>
                  ))}
                </CommandGroup>
              </ScrollArea>
            </CommandList>
          </Command>
        </PopoverContent>
      </Popover>

      {/* Phone Number Input */}
      <Input
        type="tel"
        placeholder="Enter phone number"
        value={phoneNumber.startsWith('+') ? phoneNumber : phoneNumber}
        onChange={(e) => handlePhoneChange(e.target.value)}
        className="rounded-l-none flex-1"
      />
    </div>
  );
}

Installation

npx shadcn@latest add @reui/combobox-phone-number

Usage

import { ComboboxPhoneNumber } from "@/components/combobox-phone-number"
<ComboboxPhoneNumber />