"use client";
import { useState, useMemo, useEffect } from "react";
import { Globe, Search, Check, Locate } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";
type Timezone = {
id: string;
name: string;
city: string;
region: string;
offset: string;
offsetMinutes: number;
};
const timezones: Timezone[] = [
{ id: "Pacific/Honolulu", name: "Hawaii", city: "Honolulu", region: "Americas", offset: "-10:00", offsetMinutes: -600 },
{ id: "America/Anchorage", name: "Alaska", city: "Anchorage", region: "Americas", offset: "-09:00", offsetMinutes: -540 },
{ id: "America/Los_Angeles", name: "Pacific Time", city: "Los Angeles", region: "Americas", offset: "-08:00", offsetMinutes: -480 },
{ id: "America/Denver", name: "Mountain Time", city: "Denver", region: "Americas", offset: "-07:00", offsetMinutes: -420 },
{ id: "America/Chicago", name: "Central Time", city: "Chicago", region: "Americas", offset: "-06:00", offsetMinutes: -360 },
{ id: "America/New_York", name: "Eastern Time", city: "New York", region: "Americas", offset: "-05:00", offsetMinutes: -300 },
{ id: "America/Sao_Paulo", name: "Brasilia Time", city: "São Paulo", region: "Americas", offset: "-03:00", offsetMinutes: -180 },
{ id: "Europe/London", name: "Greenwich Mean Time", city: "London", region: "Europe", offset: "+00:00", offsetMinutes: 0 },
{ id: "Europe/Paris", name: "Central European", city: "Paris", region: "Europe", offset: "+01:00", offsetMinutes: 60 },
{ id: "Europe/Helsinki", name: "Eastern European", city: "Helsinki", region: "Europe", offset: "+02:00", offsetMinutes: 120 },
{ id: "Europe/Moscow", name: "Moscow Time", city: "Moscow", region: "Europe", offset: "+03:00", offsetMinutes: 180 },
{ id: "Asia/Dubai", name: "Gulf Standard", city: "Dubai", region: "Asia", offset: "+04:00", offsetMinutes: 240 },
{ id: "Asia/Kolkata", name: "India Standard", city: "Mumbai", region: "Asia", offset: "+05:30", offsetMinutes: 330 },
{ id: "Asia/Bangkok", name: "Indochina Time", city: "Bangkok", region: "Asia", offset: "+07:00", offsetMinutes: 420 },
{ id: "Asia/Shanghai", name: "China Standard", city: "Shanghai", region: "Asia", offset: "+08:00", offsetMinutes: 480 },
{ id: "Asia/Tokyo", name: "Japan Standard", city: "Tokyo", region: "Asia", offset: "+09:00", offsetMinutes: 540 },
{ id: "Australia/Sydney", name: "Australian Eastern", city: "Sydney", region: "Pacific", offset: "+11:00", offsetMinutes: 660 },
{ id: "Pacific/Auckland", name: "New Zealand", city: "Auckland", region: "Pacific", offset: "+13:00", offsetMinutes: 780 },
];
const regions = ["Americas", "Europe", "Asia", "Pacific"];
export const title = "React Dialog Block Timezone Picker";
export default function DialogTimezonePicker() {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
const [selected, setSelected] = useState<string>("America/New_York");
const [currentTime, setCurrentTime] = useState<Date>(new Date());
useEffect(() => {
const timer = setInterval(() => {
setCurrentTime(new Date());
}, 1000);
return () => clearInterval(timer);
}, []);
const filteredTimezones = useMemo(() => {
if (!search) return timezones;
const searchLower = search.toLowerCase();
return timezones.filter(
(tz) =>
tz.name.toLowerCase().includes(searchLower) ||
tz.city.toLowerCase().includes(searchLower) ||
tz.offset.includes(search) ||
tz.id.toLowerCase().includes(searchLower)
);
}, [search]);
const groupedTimezones = useMemo(() => {
const groups: Record<string, Timezone[]> = {};
regions.forEach((region) => {
const regionTimezones = filteredTimezones.filter((tz) => tz.region === region);
if (regionTimezones.length > 0) {
groups[region] = regionTimezones;
}
});
return groups;
}, [filteredTimezones]);
const getTimeInTimezone = (offsetMinutes: number) => {
const utc = currentTime.getTime() + currentTime.getTimezoneOffset() * 60000;
const tzTime = new Date(utc + offsetMinutes * 60000);
return tzTime.toLocaleTimeString("en-US", {
hour: "numeric",
minute: "2-digit",
hour12: true,
});
};
const handleAutoDetect = () => {
try {
const detected = Intl.DateTimeFormat().resolvedOptions().timeZone;
const found = timezones.find((tz) => tz.id === detected);
if (found) {
setSelected(found.id);
}
} catch {
console.log("Could not detect timezone");
}
};
const handleSelect = (timezoneId: string) => {
setSelected(timezoneId);
};
const handleClose = () => {
setOpen(false);
setTimeout(() => {
setSearch("");
}, 200);
};
const selectedTimezone = timezones.find((tz) => tz.id === selected);
return (
<Dialog open={open} onOpenChange={setOpen}>
<div className="flex min-h-[350px] items-center justify-center">
<DialogTrigger asChild>
<Button variant="outline">
<Globe className="mr-2 h-4 w-4" />
Select Timezone
</Button>
</DialogTrigger>
</div>
<DialogContent style={{ maxWidth: "384px" }}>
<DialogHeader>
<DialogTitle>Select Timezone</DialogTitle>
<DialogDescription>
Choose your timezone for scheduling.
</DialogDescription>
</DialogHeader>
<div className="space-y-3 py-4">
<div className="flex gap-2">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search timezone..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="pl-9"
/>
</div>
<Button variant="outline" size="icon" onClick={handleAutoDetect} title="Auto-detect">
<Locate className="h-4 w-4" />
</Button>
</div>
<ScrollArea className="h-[200px]">
{Object.entries(groupedTimezones).map(([region, tzList]) => (
<div key={region} className="mb-3">
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-1 px-2">
{region}
</p>
<div className="space-y-0.5">
{tzList.map((tz) => (
<button
key={tz.id}
onClick={() => handleSelect(tz.id)}
className={cn(
"w-full flex items-center justify-between px-2 py-1.5 rounded-md transition-colors text-left",
selected === tz.id
? "bg-primary/10 text-primary"
: "hover:bg-muted"
)}
>
<div className="flex items-center gap-2">
<Check className={cn("h-3 w-3", selected === tz.id ? "opacity-100" : "opacity-0")} />
<span className="text-sm">{tz.city}</span>
</div>
<span className="text-xs text-muted-foreground tabular-nums">
{getTimeInTimezone(tz.offsetMinutes)}
</span>
</button>
))}
</div>
</div>
))}
{filteredTimezones.length === 0 && (
<p className="text-sm text-muted-foreground text-center py-8">
No timezones found
</p>
)}
</ScrollArea>
</div>
<DialogFooter>
<Button variant="outline" onClick={handleClose}>
Cancel
</Button>
<Button onClick={handleClose}>
Save
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}