'use client';
import { CheckIcon, ChevronsUpDown } from 'lucide-react';
import * as React from 'react';
import * as RPNInput from 'react-phone-number-input';
import flags from 'react-phone-number-input/flags';
import { Button } from '@/components/website/ui/button';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/website/ui/command';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/website/ui/popover';
import { cn } from '@/lib/utils';
import { ScrollArea } from '@/components/website/ui/scroll-area';
type PhoneInputProps = Omit<
React.InputHTMLAttributes<HTMLInputElement>,
'onChange' | 'value'
> &
Omit<RPNInput.Props<typeof RPNInput.default>, 'onChange'> & {
onChange?: (value: RPNInput.Value) => void;
};
const PhoneInput: React.ForwardRefExoticComponent<PhoneInputProps> =
React.forwardRef<React.ElementRef<typeof RPNInput.default>, PhoneInputProps>(
({ className, onChange, ...props }, ref) => {
return (
<RPNInput.default
ref={ref}
className={cn('flex', className)}
flagComponent={FlagComponent}
countrySelectComponent={CountrySelect}
inputComponent={InputComponent}
/**
* Handles the onChange event.
*
* react-phone-number-input might trigger the onChange event as undefined
* when a valid phone number is not entered. To prevent this,
* the value is coerced to an empty string.
*
* @param {E164Number | undefined} value - The entered value
*/
// @ts-ignore
onChange={(value) => onChange?.(value || '')}
{...props}
/>
);
}
);
PhoneInput.displayName = 'PhoneInput';
const InputComponent = React.forwardRef<HTMLInputElement, any>(
({ className, ...props }, ref) => (
<input
className={cn(
'rounded-e-lg rounded-s-none px-2 bg-primary-base outline-hidden w-full ',
className
)}
{...props}
ref={ref}
/>
)
);
InputComponent.displayName = 'InputComponent';
type CountrySelectOption = { label: string; value: RPNInput.Country };
type CountrySelectProps = {
disabled?: boolean;
value: RPNInput.Country;
onChange: (value: RPNInput.Country) => void;
options: CountrySelectOption[];
};
const CountrySelect = ({
disabled,
value,
onChange,
options,
}: CountrySelectProps) => {
const handleSelect = React.useCallback(
(country: RPNInput.Country) => {
onChange(country);
},
[onChange]
);
return (
<Popover>
<PopoverTrigger asChild>
<Button
type='button'
variant={'outline'}
className={cn('flex gap-1 rounded-e-none bg-primary-base rounded-s-lg px-3')}
disabled={disabled}
>
<FlagComponent country={value} countryName={value} />
<ChevronsUpDown
className={cn(
'-mr-2 h-4 w-4 opacity-50',
disabled ? 'hidden' : 'opacity-100'
)}
/>
</Button>
</PopoverTrigger>
<PopoverContent className='w-[300px] p-0'>
<Command className='dark:bg-neutral-950 border dark:border-neutral-800 border-neutral-200'>
<CommandList>
<ScrollArea className='h-72'>
<CommandInput placeholder='Search country...' />
<CommandEmpty>No country found.</CommandEmpty>
<CommandGroup>
{options
.filter((x) => x.value)
.map((option) => (
<CommandItem
className='gap-2'
key={option.value}
onSelect={() => handleSelect(option.value)}
>
<FlagComponent
country={option.value}
countryName={option.label}
/>
<span className='flex-1 text-sm'>{option.label}</span>
{option.value && (
<span className='text-foreground/50 text-sm'>
{`+${RPNInput.getCountryCallingCode(option.value)}`}
</span>
)}
<CheckIcon
className={cn(
'ml-auto h-4 w-4',
option.value === value ? 'opacity-100' : 'opacity-0'
)}
/>
</CommandItem>
))}
</CommandGroup>
</ScrollArea>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
};
const FlagComponent = ({ country, countryName }: RPNInput.FlagProps) => {
const Flag = flags[country];
return (
<span className='bg-foreground/20 flex h-4 w-6 overflow-hidden rounded-xs'>
{Flag && <Flag title={countryName} />}
</span>
);
};
FlagComponent.displayName = 'FlagComponent';
export { PhoneInput };