Combobox 7

PreviousNext

Timezone selector combobox with real timezone data from Intl API

Docs
shadcn-studiocomponent

Preview

Loading preview…
registry/new-york/components/combobox/combobox-07.tsx
'use client'

import { useId, useMemo, useState } from 'react'

import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react'

import { Button } from '@/registry/new-york/ui/button'
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList
} from '@/registry/new-york/ui/command'
import { Label } from '@/registry/new-york/ui/label'
import { Popover, PopoverContent, PopoverTrigger } from '@/registry/new-york/ui/popover'

import { cn } from '@/registry/new-york/lib/utils'

const ComboboxTimezoneDemo = () => {
  const id = useId()
  const [open, setOpen] = useState(false)
  const [value, setValue] = useState<string>('Indian/Cocos')

  // Fetch supported timezones
  const timezones = Intl.supportedValuesOf('timeZone')

  const formattedTimezones = useMemo(() => {
    return timezones
      .map(timezone => {
        const formatter = new Intl.DateTimeFormat('en', {
          timeZone: timezone,
          timeZoneName: 'shortOffset'
        })

        const parts = formatter.formatToParts(new Date())
        const offset = parts.find(part => part.type === 'timeZoneName')?.value || ''
        const formattedOffset = offset === 'GMT' ? 'GMT+0' : offset

        return {
          value: timezone,
          label: `(${formattedOffset}) ${timezone.replace(/_/g, ' ')}`,
          numericOffset: parseInt(formattedOffset.replace('GMT', '').replace('+', '') || '0')
        }
      })
      .sort((a, b) => a.numericOffset - b.numericOffset) // Sort by numeric offset
  }, [timezones])

  return (
    <div className='w-full max-w-xs space-y-2'>
      <Label htmlFor={id}>Timezone combobox</Label>
      <Popover open={open} onOpenChange={setOpen}>
        <PopoverTrigger asChild>
          <Button id={id} variant='outline' role='combobox' aria-expanded={open} className='w-full justify-between'>
            <span className={cn('truncate')}>
              {value ? (
                formattedTimezones.find(timezone => timezone.value === value)?.label
              ) : (
                <span className='text-muted-foreground'>Select timezone</span>
              )}
            </span>
            <ChevronsUpDownIcon className='text-muted-foreground/80 shrink-0' aria-hidden='true' />
          </Button>
        </PopoverTrigger>
        <PopoverContent className='w-(--radix-popper-anchor-width) p-0'>
          <Command>
            <CommandInput placeholder='Search timezone' />
            <CommandList>
              <CommandEmpty>No timezone found.</CommandEmpty>
              <CommandGroup>
                {formattedTimezones.map(({ value: itemValue, label }) => (
                  <CommandItem
                    key={itemValue}
                    value={itemValue}
                    onSelect={currentValue => {
                      setValue(currentValue === value ? '' : currentValue)
                      setOpen(false)
                    }}
                  >
                    <span className='truncate'>{label}</span>
                    {value === itemValue && <CheckIcon size={16} className='ml-auto' />}
                  </CommandItem>
                ))}
              </CommandGroup>
            </CommandList>
          </Command>
        </PopoverContent>
      </Popover>
    </div>
  )
}

export default ComboboxTimezoneDemo

Installation

npx shadcn@latest add @shadcn-studio/combobox-07

Usage

import { Combobox07 } from "@/components/combobox-07"
<Combobox07 />