mirror of
https://github.com/supabase/supabase.git
synced 2026-06-17 21:23:59 +08:00
## Problem UTC was buried in the middle of the timezone list. Users managing servers (which run in UTC) had to scroll or search to find it. ## Fix Hardcode a UTC entry immediately after "Auto detect" so the two most common choices are always at the top. The entry uses the standard `UTC` IANA name and shows the checkmark when selected, consistent with all other entries. ## How to test - Open any page in Studio and click your user avatar. - Open the Timezone submenu. - Confirm the order is: Auto detect, (UTC) Coordinated Universal Time, then the rest of the list. - Select UTC and confirm the checkmark appears and the trigger label updates. - Search "UTC" in the search box and confirm it still matches. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added explicit "UTC (Coordinated Universal Time)" option at the top of the timezone selector dropdown for easier access. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
127 lines
4.6 KiB
TypeScript
127 lines
4.6 KiB
TypeScript
import { CheckIcon } from 'lucide-react'
|
|
import { useMemo, useState } from 'react'
|
|
import {
|
|
cn,
|
|
Command,
|
|
CommandEmpty,
|
|
CommandGroup,
|
|
CommandInput,
|
|
CommandItem,
|
|
CommandList,
|
|
DropdownMenuPortal,
|
|
DropdownMenuSub,
|
|
DropdownMenuSubContent,
|
|
DropdownMenuSubTrigger,
|
|
ScrollArea,
|
|
} from 'ui'
|
|
|
|
import { findTimezoneByIana, TIMEZONES_BY_IANA } from '@/lib/constants/timezones'
|
|
import { useTimezone } from '@/lib/datetime'
|
|
import { guessLocalTimezone } from '@/lib/dayjs'
|
|
import { useTrack } from '@/lib/telemetry/track'
|
|
|
|
const AUTO_OPTION_VALUE = '__auto__'
|
|
|
|
export const TimezoneDropdown = () => {
|
|
const { timezone, storedTimezone, setTimezone, isAutoDetected } = useTimezone()
|
|
const track = useTrack()
|
|
const [open, setOpen] = useState(false)
|
|
|
|
// The "Auto detect" row always advertises the browser's own timezone, even
|
|
// when the user is currently overriding it with a manual pick.
|
|
const browserTimezone = useMemo(() => guessLocalTimezone(), [])
|
|
|
|
const triggerLabel = useMemo(() => {
|
|
return findTimezoneByIana(timezone)?.text ?? timezone
|
|
}, [timezone])
|
|
|
|
const handleSelect = (nextStored: string) => {
|
|
setTimezone(nextStored)
|
|
const resolvedNext = nextStored || guessLocalTimezone()
|
|
track('timezone_picker_clicked', {
|
|
previousTimezone: timezone,
|
|
nextTimezone: resolvedNext,
|
|
isAutoDetected: nextStored === '',
|
|
source: 'user_dropdown',
|
|
})
|
|
setOpen(false)
|
|
}
|
|
|
|
return (
|
|
<DropdownMenuSub open={open} onOpenChange={setOpen}>
|
|
<DropdownMenuSubTrigger className="flex gap-2 cursor-pointer">
|
|
<div className="flex flex-col min-w-0">
|
|
<span>Timezone</span>
|
|
<span className="text-xs text-foreground-lighter truncate" title={triggerLabel}>
|
|
{isAutoDetected ? `Auto (${timezone})` : triggerLabel}
|
|
</span>
|
|
</div>
|
|
</DropdownMenuSubTrigger>
|
|
<DropdownMenuPortal>
|
|
<DropdownMenuSubContent className="p-0 w-[320px]" sideOffset={4}>
|
|
<Command>
|
|
<CommandInput placeholder="Search timezone..." className="h-9" />
|
|
<CommandList>
|
|
<CommandEmpty>No timezones found</CommandEmpty>
|
|
<CommandGroup>
|
|
<ScrollArea className="h-72">
|
|
<CommandItem
|
|
key={AUTO_OPTION_VALUE}
|
|
value={`Auto detect ${browserTimezone}`}
|
|
onSelect={() => handleSelect('')}
|
|
>
|
|
<div className="flex flex-col">
|
|
<span>Auto detect</span>
|
|
<span className="text-xs text-foreground-lighter">{browserTimezone}</span>
|
|
</div>
|
|
<CheckIcon
|
|
className={cn(
|
|
'ml-auto h-4 w-4',
|
|
isAutoDetected ? 'opacity-100' : 'opacity-0'
|
|
)}
|
|
/>
|
|
</CommandItem>
|
|
<CommandItem
|
|
key="UTC"
|
|
value="UTC Coordinated Universal Time"
|
|
onSelect={() => handleSelect('UTC')}
|
|
>
|
|
{'(UTC) Coordinated Universal Time'}
|
|
<CheckIcon
|
|
className={cn(
|
|
'ml-auto h-4 w-4',
|
|
!isAutoDetected && storedTimezone === 'UTC' ? 'opacity-100' : 'opacity-0'
|
|
)}
|
|
/>
|
|
</CommandItem>
|
|
{TIMEZONES_BY_IANA.map((entry) => {
|
|
const ianaName = entry.utc[0]
|
|
const isSelected = !isAutoDetected && storedTimezone === ianaName
|
|
return (
|
|
<CommandItem
|
|
key={ianaName}
|
|
// CommandItem matches against the `value` prop for the input filter — include
|
|
// both the human label and the IANA name so search works for either.
|
|
value={`${entry.text} ${ianaName}`}
|
|
onSelect={() => handleSelect(ianaName)}
|
|
>
|
|
{entry.text}
|
|
<CheckIcon
|
|
className={cn(
|
|
'ml-auto h-4 w-4',
|
|
isSelected ? 'opacity-100' : 'opacity-0'
|
|
)}
|
|
/>
|
|
</CommandItem>
|
|
)
|
|
})}
|
|
</ScrollArea>
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</DropdownMenuSubContent>
|
|
</DropdownMenuPortal>
|
|
</DropdownMenuSub>
|
|
)
|
|
}
|