A day range that also shows the same dates a year earlier, moving 29 February to 28 February in non-leap years and flagging the day count mismatch. Use this when figures are read against the same dates last year and leap years must not skew the comparison.
registry/patterns/year-over-year-range/year-over-year-range.tsx
"use client"
import * as React from "react"
import { DateRangeField } from "@/components/ui/date-range-field"
import {
formatDateRange,
rangeLength,
resolveLocale,
samePeriodLastYear,
type DateRange,
} from "@/components/ui/date-time-core"
export interface YearOverYearRangeProps {
value?: DateRange | null
defaultValue?: DateRange | null
onValueChange?: (range: DateRange | null, lastYear: DateRange | null) => void
max?: string
locale?: string
}
export function yearOverYear(range: DateRange) {
const lastYear = samePeriodLastYear(range)
return { lastYear, dayDifference: rangeLength(range) - rangeLength(lastYear) }
}
export function YearOverYearRange({
value,
defaultValue = null,
onValueChange,
max,
locale,
}: YearOverYearRangeProps) {
const loc = resolveLocale(locale)
const [inner, setInner] = React.useState<DateRange | null>(defaultValue)
const range = value === undefined ? inner : value
const result = range ? yearOverYear(range) : null
return (
<div className="flex flex-col gap-3">
<DateRangeField
locale={loc}
max={max}
value={range}
onValueChange={(next) => {
if (value === undefined) setInner(next)
onValueChange?.(next, next ? yearOverYear(next).lastYear : null)
}}
/>
<div role="status" className="text-sm">
{range && result ? (
<>
<p>
<strong>This year</strong>{" "}
{formatDateRange(range.from, range.to, loc)}
</p>
<p>
<strong>Last year</strong>{" "}
{formatDateRange(result.lastYear.from, result.lastYear.to, loc)}
</p>
{result.dayDifference !== 0 ? (
<p className="text-muted-foreground">
This year has {Math.abs(result.dayDifference)}{" "}
{result.dayDifference > 0 ? "more" : "fewer"} day
{Math.abs(result.dayDifference) === 1 ? "" : "s"} because of the
leap day.
</p>
) : null}
</>
) : (
"Enter a date range."
)}
</div>
</div>
)
}