redesigned app for cleaner ux
This commit is contained in:
1 parent
fe468885b0
commit
2a3f542e98
33 files changed
+1379
-374
No files matched your search
@@ -0,0 +1,106 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
interface Country {
|
||||
code: string;
|
||||
iso: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
const COUNTRIES: Country[] = [
|
||||
{ code: "+351", iso: "pt", label: "Portugal" },
|
||||
{ code: "+34", iso: "es", label: "Spain" },
|
||||
{ code: "+33", iso: "fr", label: "France" },
|
||||
{ code: "+44", iso: "gb", label: "United Kingdom" },
|
||||
{ code: "+1", iso: "us", label: "United States" },
|
||||
{ code: "+55", iso: "br", label: "Brazil" },
|
||||
];
|
||||
|
||||
const flagUrl = (iso: string) => `https://flagcdn.com/${iso}.svg`;
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
onChange: (code: string) => void;
|
||||
}
|
||||
|
||||
const PhoneCodeSelect = ({ value, onChange }: Props) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const active = COUNTRIES.find((c) => c.code === value) ?? COUNTRIES[0];
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const handlePointerDown = (e: MouseEvent) => {
|
||||
if (
|
||||
containerRef.current &&
|
||||
!containerRef.current.contains(e.target as Node)
|
||||
) {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handlePointerDown);
|
||||
return () => document.removeEventListener("mousedown", handlePointerDown);
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<div className="relative" ref={containerRef}>
|
||||
<input type="hidden" name="phone_code" value={active.code} />
|
||||
<motion.button
|
||||
type="button"
|
||||
className="flex items-center gap-1.5 outline-none"
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
transition={{ type: "spring", stiffness: 500, damping: 30 }}
|
||||
aria-label="Country code"
|
||||
>
|
||||
<img
|
||||
src={flagUrl(active.iso)}
|
||||
alt={active.label}
|
||||
className="w-5 h-[14px] rounded-[2px] object-cover shrink-0"
|
||||
/>
|
||||
<span className="text-sm md:text-base text-white font-medium leading-none">
|
||||
{active.code}
|
||||
</span>
|
||||
</motion.button>
|
||||
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95, y: -6 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: -6 }}
|
||||
transition={{ type: "spring", damping: 24, stiffness: 300 }}
|
||||
style={{ transformOrigin: "top left" }}
|
||||
className="absolute left-0 top-full mt-2 w-48 rounded-xl glass-panel shadow-glass-lg overflow-hidden z-20"
|
||||
>
|
||||
{COUNTRIES.map((country) => (
|
||||
<button
|
||||
key={country.code}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onChange(country.code);
|
||||
setOpen(false);
|
||||
}}
|
||||
className={`flex w-full items-center gap-2 px-3 py-2 text-sm text-left transition-colors ${
|
||||
country.code === active.code
|
||||
? "text-gold-light bg-white/5"
|
||||
: "text-white/70 hover:bg-white/5 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
<img
|
||||
src={flagUrl(country.iso)}
|
||||
alt={country.label}
|
||||
className="w-5 h-[14px] rounded-[2px] object-cover shrink-0"
|
||||
/>
|
||||
<span className="flex-1">{country.label}</span>
|
||||
<span className="text-white/40">{country.code}</span>
|
||||
</button>
|
||||
))}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PhoneCodeSelect;
|
||||
Reference in new issue
Block a user