Files
Gildas GarciaandIvan Vasilov 96d43099bb chore: refactor Button API so that it can be used a standard button (#46880)
## Problem

Our `<Button>` component breaks the default `button` contract by
redefining the `type` prop to set its variant (`primary`, `default`,
etc) instead of the button type (`submit`, `button`, etc).
This is confusing and forces to write more code when using it with
shadcn components that expect/inject the standard button props.

## Solution

- rename the `type` prop to `variant`
- rename the `htmlType` prop to `type`
- propagate the changes where necessary
- format code

## How to test

As this is just prop renaming, if it builds it's ok

---------

Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
2026-06-16 23:59:58 +02:00

164 lines
6.1 KiB
TypeScript

import { useFlag } from 'common'
import { CheckIcon, ChevronsUpDown, Globe } from 'lucide-react'
import { useId, useMemo, useState } from 'react'
import {
Button,
Card,
CardContent,
cn,
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
Popover,
PopoverContent,
PopoverTrigger,
ScrollArea,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import {
PageSection,
PageSectionContent,
PageSectionDescription,
PageSectionMeta,
PageSectionSummary,
PageSectionTitle,
} from 'ui-patterns/PageSection'
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 TimezoneSettings = () => {
const timezonePickerEnabled = useFlag('timezonePicker')
const { timezone, storedTimezone, setTimezone, isAutoDetected } = useTimezone()
const track = useTrack()
const [open, setOpen] = useState(false)
const listboxId = useId()
// Browser timezone is captured once and stays stable even when the user has
// overridden the dashboard timezone — that's the value the "Auto detect"
// option will revert to.
const browserTimezone = useMemo(() => guessLocalTimezone(), [])
const triggerLabel = useMemo(() => findTimezoneByIana(timezone)?.text ?? timezone, [timezone])
if (!timezonePickerEnabled) return null
const handleSelect = (nextStored: string) => {
setTimezone(nextStored)
const resolvedNext = nextStored || guessLocalTimezone()
track('timezone_picker_clicked', {
previousTimezone: timezone,
nextTimezone: resolvedNext,
isAutoDetected: nextStored === '',
source: 'account_preferences',
})
setOpen(false)
}
return (
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>Timezone</PageSectionTitle>
<PageSectionDescription>
Choose how dates and times in logs and other dashboard surfaces are displayed.
</PageSectionDescription>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
<Card>
<CardContent>
<FormItemLayout
isReactForm={false}
label="Display timezone"
layout="flex-row-reverse"
description={
isAutoDetected
? `Auto detected from your browser (${browserTimezone}).`
: 'Pick "Auto detect" to follow your browser timezone again.'
}
>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
role="combobox"
aria-expanded={open}
aria-controls={listboxId}
className="w-full justify-between"
variant="default"
size="small"
icon={<Globe />}
iconRight={<ChevronsUpDown size={14} strokeWidth={1.5} />}
>
<span className="truncate text-left">
{isAutoDetected ? `Auto detect (${timezone})` : triggerLabel}
</span>
</Button>
</PopoverTrigger>
<PopoverContent id={listboxId} className="w-[--radix-popover-trigger-width] p-0">
<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>
{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>
</PopoverContent>
</Popover>
</FormItemLayout>
</CardContent>
</Card>
</PageSectionContent>
</PageSection>
)
}