Context
Lighthouse currently ships a 600+ line project datePicker component used at 30 callsites. The library Calendar is the natural target for migration, but Calendar today supports only mode='single' and mode='range'. To migrate cleanly we need feature parity on four axes that Calendar lacks:
- Compare-mode — dual-range selection where the user picks a primary range AND a comparison range (e.g. "Last 7 days vs previous 7 days"). Drives all analytics filter bars.
- Presets — a sidebar/dropdown of common ranges (Today, Yesterday, Last 7/30/90 days, This/Last month, Custom) with the active preset visually pinned. Some products also need comparison-presets ("Previous period", "Same period last year").
- Time picker — when the consumer wants the range to include hours (e.g. abandoned-checkout cohort selection), a HH:MM AM/PM input that binds to the same value.
- Descriptive labels — formatted output for the trigger button (
"Jun 1 – Jun 30, 2026", "Compared to: May 1 – May 30") the consumer can render in the closed-state UI.
Question for maintainer
I want to scope this against GUIDELINES.md before authoring. Two possible shapes — preference?
Shape A — extend Calendar itself with optional props:
presets?: Array<{ label: string, getRange(): { start: Date, end: Date } }> — consumer-supplied, library renders a sidebar
compareMode?: boolean + a second compareRangeStart/End bindable pair, OR a new mode='compare'
time?: { start: string, end: string } + ontimechange event when mode allows
triggerLabel?: Snippet<[{ start, end, compareStart?, compareEnd?, presetKey? }]> for the formatted label
Shape B — a new DatePicker primitive that composes Calendar + adds the popover/trigger/preset surface, leaving Calendar slim:
DatePicker owns the trigger button, popover positioning, preset list, time inputs
- Internally renders the existing
Calendar for the grid
- The trigger label snippet is part of
DatePicker's API, not Calendar's
I'd lean towards (B) — it keeps Calendar philosophically clean (it's a grid, not a popover) and matches the Button + Popover + Calendar composition pattern the library already validates with RangeSelect (recipe ships in #235). But (A) keeps the API surface smaller.
Which shape would you accept, and is there anything about the preset / time / compare-mode semantics you'd want shaped differently before I open the PR?
Why this matters
This is the single biggest blocker to Lighthouse fully migrating off project-owned UI primitives. 30 callsites × 4 missing features is the gap between "library extension PR" and "rewrite each callsite to compose smaller primitives", and the first is dramatically less risky if the library is willing to absorb the feature surface.
Context
Lighthouse currently ships a 600+ line project
datePickercomponent used at 30 callsites. The libraryCalendaris the natural target for migration, but Calendar today supports onlymode='single'andmode='range'. To migrate cleanly we need feature parity on four axes that Calendar lacks:"Jun 1 – Jun 30, 2026","Compared to: May 1 – May 30") the consumer can render in the closed-state UI.Question for maintainer
I want to scope this against GUIDELINES.md before authoring. Two possible shapes — preference?
Shape A — extend
Calendaritself with optional props:presets?: Array<{ label: string, getRange(): { start: Date, end: Date } }>— consumer-supplied, library renders a sidebarcompareMode?: boolean+ a secondcompareRangeStart/Endbindable pair, OR a newmode='compare'time?: { start: string, end: string }+ontimechangeevent whenmodeallowstriggerLabel?: Snippet<[{ start, end, compareStart?, compareEnd?, presetKey? }]>for the formatted labelShape B — a new
DatePickerprimitive that composesCalendar+ adds the popover/trigger/preset surface, leavingCalendarslim:DatePickerowns the trigger button, popover positioning, preset list, time inputsCalendarfor the gridDatePicker's API, notCalendar'sI'd lean towards (B) — it keeps
Calendarphilosophically clean (it's a grid, not a popover) and matches theButton + Popover + Calendarcomposition pattern the library already validates withRangeSelect(recipe ships in #235). But (A) keeps the API surface smaller.Which shape would you accept, and is there anything about the preset / time / compare-mode semantics you'd want shaped differently before I open the PR?
Why this matters
This is the single biggest blocker to Lighthouse fully migrating off project-owned UI primitives. 30 callsites × 4 missing features is the gap between "library extension PR" and "rewrite each callsite to compose smaller primitives", and the first is dramatically less risky if the library is willing to absorb the feature surface.