Skip to content

Instantly share code, notes, and snippets.

@michojekunle
Last active July 31, 2024 06:35
Show Gist options
  • Select an option

  • Save michojekunle/c0df1d7d05b0017bed52319bd987e834 to your computer and use it in GitHub Desktop.

Select an option

Save michojekunle/c0df1d7d05b0017bed52319bd987e834 to your computer and use it in GitHub Desktop.
PHONE INPUT
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/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import { Input, InputProps } from "@/components/ui/input";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover-dialog";
import { cn } from "@/lib/utils";
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, InputProps>(
({ className, ...props }, ref) => (
<Input
className={cn("rounded-s-none rounded-e-lg", 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 [searchTerm, setSearchTerm] = React.useState("");
const filteredOptions = React.useMemo(() => {
return options.filter((option) =>
option.label.toLowerCase().startsWith(searchTerm.toLowerCase()),
);
}, [options, searchTerm]);
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 rounded-s-lg px-3")}
disabled={disabled}
>
<FlagComponent country={value} countryName={value} />
<ChevronsUpDown
className={cn(
"h-4 w-4 opacity-50 -mr-2",
disabled ? "hidden" : "opacity-100",
)}
/>
</Button>
</PopoverTrigger>
<PopoverContent className="p-0 w-[300px]">
<Command>
<CommandList>
<CommandInput
placeholder="Search country..."
value={searchTerm}
onValueChange={setSearchTerm}
/>
<CommandEmpty>No country found.</CommandEmpty>
<CommandGroup>
{filteredOptions
.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="text-sm flex-1">{option.label}</span>
{option.value && (
<span className="text-sm text-foreground/50">
{`+${RPNInput.getCountryCallingCode(option.value)}`}
</span>
)}
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
option.value === value ? "opacity-100" : "opacity-0",
)}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
};
// 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 rounded-s-lg px-3")}
// disabled={disabled}
// >
// <FlagComponent country={value} countryName={value} />
// <ChevronsUpDown className={cn("h-4 w-4 opacity-50 -mr-2", disabled ? "hidden" : "opacity-100")} />
// </Button>
// </PopoverTrigger>
// <PopoverContent className="p-0 w-[300px]">
// <Command>
// <CommandList>
// <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="text-sm flex-1">{option.label}</span>
// {option.value && (
// <span className="text-sm text-foreground/50">
// {`+${RPNInput.getCountryCallingCode(option.value)}`}
// </span>
// )}
// <CheckIcon
// className={cn("ml-auto h-4 w-4", option.value === value ? "opacity-100" : "opacity-0")}
// />
// </CommandItem>
// ))}
// </CommandGroup>
// </CommandList>
// </Command>
// </PopoverContent>
// </Popover>
// );
// };
const FlagComponent = ({ country, countryName }: RPNInput.FlagProps) => {
const Flag = flags[country];
return (
<span className="flex h-4 w-6 overflow-hidden rounded-sm bg-foreground/20">
{Flag && <Flag title={countryName} />}
</span>
);
};
FlagComponent.displayName = "FlagComponent";
export { PhoneInput };
"use client";
import * as React from "react";
import * as PopoverPrimitive from "@radix-ui/react-popover";
import { cn } from "@/lib/utils";
const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
/>
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment