Files
Danny White 476d4a5851 refactor(ui): drop redundant Button variant="default" props (#50161)
## What kind of change does this PR introduce?

Mechanical cleanup on top of the Button default-variant change (#50160).

## What is the current behavior?

Many callsites still pass `variant="default"` even though that is now
the component default.

## What is the new behavior?

Removes redundant static `variant="default"` from legacy `Button` and
`ButtonTooltip` callsites. Keeps explicit defaults where they document
the API:

- `button-default.tsx` and `button-sizes.tsx` demos
- `DocsButton`, which pins neutral styling at the wrapper boundary

## To test

Studio:

- [Auth → Rate
Limits](https://studio-staging-2s957kwc4-supabase.vercel.app/dashboard/project/_/auth/rate-limits):
dirty the form so Cancel appears; Cancel stays neutral, Save stays green
- [Project Settings → API
Keys](https://studio-staging-2s957kwc4-supabase.vercel.app/dashboard/project/_/settings/api-keys):
`DocsButton` in the header actions stays neutral

Design system:

- [Design system →
Button](https://design-system-git-dnywh-dc924ac1-supabase.vercel.app/design-system/docs/components/button):
`button-default` / `button-sizes` still show explicit default styling;
Primary (green) is restricted to the Primary section (and `asChild`)

WWW:

- [www → Brand
assets](https://zone-www-dot-com-git-dnywh-dc924ac1-supabase.vercel.app/brand-assets):
Download logo kit / Download button kit stay neutral
2026-09-11 17:05:26 +10:00

329 lines
9.7 KiB
TypeScript

import { format } from 'date-fns'
import dayjs from 'dayjs'
import { ArrowRight, Calendar } from 'lucide-react'
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
import {
Button,
ButtonProps,
Calendar as CalendarPicker,
Popover,
PopoverContent,
PopoverSeparator,
PopoverTrigger,
} from 'ui'
import { TimeSplitInput } from './TimeSplitInput'
import type { DatePickerToFrom } from '@/components/interfaces/Settings/Logs/Logs.types'
interface DatePickerProps {
onChange?: (args: DatePickerToFrom) => void
to?: string // ISO string
from?: string // ISO string
triggerButtonVariant?: ButtonProps['variant']
triggerButtonClassName?: string
triggerButtonTitle?: string
triggerButtonSize?: 'tiny' | 'small'
contentSide?: 'bottom' | 'top'
minDate?: Date
maxDate?: Date
hideTime?: boolean
hideClear?: boolean
selectsRange?: boolean
renderFooter?: (args: DatePickerToFrom) => ReactNode | void
children?: ReactNode | ReactNode[] | null
}
const START_DATE_DEFAULT = new Date()
const END_DATE_DEFAULT = new Date()
const START_TIME_DEFAULT = { HH: '00', mm: '00', ss: '00' }
const END_TIME_DEFAULT = { HH: '23', mm: '59', ss: '59' }
const calculateDisabledDays = (minDate?: Date, maxDate?: Date) => {
const rules: Array<{ before: Date } | { after: Date }> = []
if (minDate) {
rules.push({ before: dayjs(minDate).startOf('day').toDate() })
}
if (maxDate) {
rules.push({ after: dayjs(maxDate).endOf('day').toDate() })
}
if (rules.length === 0) return undefined
if (rules.length === 1) return rules[0]
return rules
}
export function DatePicker({
to,
from,
onChange,
triggerButtonVariant = 'default',
triggerButtonClassName = '',
triggerButtonTitle,
triggerButtonSize,
contentSide = 'bottom',
minDate,
maxDate,
hideTime = false,
hideClear = false,
selectsRange = true,
renderFooter = () => null,
children,
}: DatePickerProps) {
const [open, setOpen] = useState<boolean>(false)
const [appliedStartDate, setAppliedStartDate] = useState<null | Date>(null)
const [appliedEndDate, setAppliedEndDate] = useState<null | Date>(null)
const [startDate, setStartDate] = useState<Date | null>(START_DATE_DEFAULT)
const [endDate, setEndDate] = useState<Date | null>(END_DATE_DEFAULT)
const [startTime, setStartTime] = useState<any>(START_TIME_DEFAULT)
const [endTime, setEndTime] = useState<any>(END_TIME_DEFAULT)
const disabledDays = useMemo(() => calculateDisabledDays(minDate, maxDate), [minDate, maxDate])
const startMonth = minDate ? dayjs(minDate).startOf('month').toDate() : undefined
const endMonth = maxDate ? dayjs(maxDate).endOf('month').toDate() : undefined
const clampDateToRange = useCallback(
(date: Date | null) => {
if (!date) return date
let nextDate = date
if (minDate && dayjs(nextDate).isBefore(minDate, 'day')) {
nextDate = dayjs(minDate).toDate()
}
if (maxDate && dayjs(nextDate).isAfter(maxDate, 'day')) {
nextDate = dayjs(maxDate).toDate()
}
return nextDate
},
[minDate, maxDate]
)
useEffect(() => {
if (!minDate && !maxDate) return
setStartDate((prev) => {
if (!prev) return prev
const clamped = clampDateToRange(prev)
return dayjs(clamped).isSame(prev) ? prev : clamped
})
setEndDate((prev) => {
if (!prev) return prev
const clamped = clampDateToRange(prev)
return dayjs(clamped).isSame(prev) ? prev : clamped
})
setAppliedStartDate((prev) => {
if (!prev) return prev
const clamped = clampDateToRange(prev)
return dayjs(clamped).isSame(prev) ? prev : clamped
})
setAppliedEndDate((prev) => {
if (!prev) return prev
const clamped = clampDateToRange(prev)
return dayjs(clamped).isSame(prev) ? prev : clamped
})
}, [minDate, maxDate, clampDateToRange])
useEffect(() => {
if (!from) {
setAppliedStartDate(null)
} else if (from !== appliedStartDate?.toISOString()) {
const start = dayjs(from)
const startDate = start.toDate()
setAppliedStartDate(startDate)
setStartDate(startDate)
setStartTime({
HH: start.format('HH'),
mm: start.format('mm'),
ss: start.format('ss'),
})
}
if (!to) {
setAppliedEndDate(null)
} else if (to !== appliedEndDate?.toISOString()) {
const end = dayjs(to)
const endDate = end.toDate()
setAppliedEndDate(endDate)
setEndDate(endDate)
setEndTime({
HH: end.format('HH'),
mm: end.format('mm'),
ss: end.format('ss'),
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [to, from])
function handleDatePickerChange(dates: Date | [from: Date | null, to: Date | null] | null) {
if (!dates) {
setStartDate(null)
setEndDate(null)
} else if (dates instanceof Date) {
setStartDate(dates)
setEndDate(dates)
} else {
const [from, to] = dates
setStartDate(from)
setEndDate(to)
}
}
function handleSubmit() {
setOpen(false)
setAppliedStartDate(startDate)
setAppliedEndDate(endDate)
const payload = {
from: dayjs(startDate)
.second(Number(startTime.ss))
.minute(Number(startTime.mm))
.hour(Number(startTime.HH))
.toISOString(),
to: dayjs(endDate || startDate)
.second(Number(endTime.ss))
.minute(Number(endTime.mm))
.hour(Number(endTime.HH))
.toISOString(),
}
if (onChange) onChange(payload)
}
function handleClear() {
setOpen(false)
setStartDate(START_DATE_DEFAULT)
setEndDate(END_DATE_DEFAULT)
setStartTime(START_TIME_DEFAULT)
setEndTime(END_TIME_DEFAULT)
setAppliedStartDate(null)
setAppliedEndDate(null)
if (onChange) onChange({ from: null, to: null })
}
return (
<Popover modal open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
title={triggerButtonTitle}
variant={triggerButtonVariant}
icon={<Calendar />}
size={triggerButtonSize}
className={triggerButtonClassName}
>
{children !== undefined ? (
children
) : (
<>
{/* Custom */}
{selectsRange &&
appliedStartDate &&
appliedEndDate &&
appliedStartDate !== appliedEndDate ? (
<>
{format(new Date(appliedStartDate), 'dd MMM')} -{' '}
{format(new Date(appliedEndDate), 'dd MMM')}
</>
) : appliedStartDate || appliedEndDate ? (
format(new Date((appliedStartDate || appliedEndDate)!), 'dd MMM')
) : (
'Custom'
)}
</>
)}
</Button>
</PopoverTrigger>
<PopoverContent align="center" side={contentSide} className="p-0">
<>
{hideTime ? null : (
<>
<div className="flex items-stretch justify-between p-2">
{!selectsRange ? null : (
<>
<div className="flex grow flex-col gap-1">
<TimeSplitInput
type="start"
startTime={startTime}
endTime={endTime}
time={startTime}
setTime={setStartTime}
setStartTime={setStartTime}
setEndTime={setEndTime}
startDate={startDate}
endDate={endDate}
/>
</div>
<div className="flex items-center justify-center w-12 text-foreground-lighter">
<ArrowRight strokeWidth={1.5} size={14} />
</div>
</>
)}
<div className="flex grow flex-col gap-1">
<TimeSplitInput
type="end"
startTime={startTime}
endTime={endTime}
time={endTime}
setTime={setEndTime}
setStartTime={setStartTime}
setEndTime={setEndTime}
startDate={startDate}
endDate={endDate}
/>
</div>
</div>
</>
)}
<div className="p-2">
{selectsRange ? (
<CalendarPicker
mode="range"
startMonth={startMonth}
endMonth={endMonth}
disabled={disabledDays}
selected={{ from: startDate ?? undefined, to: endDate ?? undefined }}
onSelect={(range) => {
handleDatePickerChange([range?.from ?? null, range?.to ?? null])
}}
/>
) : (
<CalendarPicker
mode="single"
startMonth={startMonth}
endMonth={endMonth}
disabled={disabledDays}
selected={endDate ?? undefined}
onSelect={(date) => {
handleDatePickerChange(date ?? null)
}}
/>
)}
</div>
{renderFooter({
from: startDate?.toISOString() || null,
to: endDate?.toISOString() || null,
})}
<PopoverSeparator />
<div className="flex items-center justify-end gap-2 py-2 px-3 pb-4">
{!hideClear && <Button onClick={() => handleClear()}>Clear</Button>}
<Button variant="primary" onClick={() => handleSubmit()}>
Apply
</Button>
</div>
</>
</PopoverContent>
</Popover>
)
}