From 69e501a20a7f58cdf174dd20c92d58a249aff740 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 9 Sep 2026 10:47:00 +0800 Subject: [PATCH 1/3] Premium Analytics: remove the date range step arrows The arrows are gone from the date filters row, alongside the mechanism behind them: the surface that still rendered them was the dashboard, and the detail pages already opted out. UNI-751 --- .../changelog/remove-period-step-arrows | 4 + .../packages/datetime/README.md | 16 -- .../__tests__/get-comparison-range.test.ts | 5 +- .../src/__tests__/step-date-range.test.ts | 215 ------------------ .../src/__tests__/to-date-range.test.ts | 46 +--- .../datetime/src/get-comparison-range.ts | 4 +- .../packages/datetime/src/index.ts | 3 +- .../packages/datetime/src/step-date-range.ts | 122 ---------- .../packages/datetime/src/to-date-range.ts | 36 +-- .../__tests__/build-range-patch.test.ts | 30 --- .../use-report-date-filters.test.tsx | 82 ------- .../use-report-date-filters.tsx | 53 +---- .../__tests__/date-filters-panel.test.tsx | 35 +-- .../date-filters-panel.scss | 3 +- .../date-filters-panel/date-filters-panel.tsx | 34 --- .../stories/date-filters-panel.stories.tsx | 23 -- .../__tests__/date-period-navigation.test.tsx | 59 ----- .../date-period-navigation.scss | 21 -- .../date-period-navigation.tsx | 72 ------ .../ui/src/date-period-navigation/index.ts | 1 - .../date-period-navigation.stories.tsx | 87 ------- .../packages/ui/src/index.ts | 1 - .../__tests__/report-page-layout.test.tsx | 1 - .../stories/report-page.stories.tsx | 1 - .../routes/use-detail-date-controls.test.ts | 1 - .../routes/use-detail-date-controls.ts | 6 +- .../tests/groups/ui-no-mocks-part1.test.tsx | 1 - .../changelog/remove-period-step-arrows | 4 + .../changelog/remove-period-step-arrows | 4 + 29 files changed, 29 insertions(+), 941 deletions(-) create mode 100644 projects/packages/premium-analytics/changelog/remove-period-step-arrows delete mode 100644 projects/packages/premium-analytics/packages/datetime/src/__tests__/step-date-range.test.ts delete mode 100644 projects/packages/premium-analytics/packages/datetime/src/step-date-range.ts delete mode 100644 projects/packages/premium-analytics/packages/ui/src/date-period-navigation/__tests__/date-period-navigation.test.tsx delete mode 100644 projects/packages/premium-analytics/packages/ui/src/date-period-navigation/date-period-navigation.scss delete mode 100644 projects/packages/premium-analytics/packages/ui/src/date-period-navigation/date-period-navigation.tsx delete mode 100644 projects/packages/premium-analytics/packages/ui/src/date-period-navigation/index.ts delete mode 100644 projects/packages/premium-analytics/packages/ui/src/date-period-navigation/stories/date-period-navigation.stories.tsx create mode 100644 projects/plugins/jetpack/changelog/remove-period-step-arrows create mode 100644 projects/plugins/premium-analytics/changelog/remove-period-step-arrows diff --git a/projects/packages/premium-analytics/changelog/remove-period-step-arrows b/projects/packages/premium-analytics/changelog/remove-period-step-arrows new file mode 100644 index 000000000000..eabeef518231 --- /dev/null +++ b/projects/packages/premium-analytics/changelog/remove-period-step-arrows @@ -0,0 +1,4 @@ +Significance: minor +Type: removed + +Remove the arrows that stepped the date range back and forward a period. diff --git a/projects/packages/premium-analytics/packages/datetime/README.md b/projects/packages/premium-analytics/packages/datetime/README.md index fe4dc2a7e988..4f21073c2884 100644 --- a/projects/packages/premium-analytics/packages/datetime/README.md +++ b/projects/packages/premium-analytics/packages/datetime/README.md @@ -215,22 +215,6 @@ A whole-month range stays in days below two months and only collapses into years from two years up, so "Last 30 days" reads as 30 days and a twelve-month window as 12 months. -#### `stepDateRange( range, direction )` - -Shifts a range backward or forward (`'previous' | 'next'`) by its own length. -Steps move in calendar units, so a step across a DST boundary keeps the wall -clock; where a calendar step cannot be undone, it falls back to whole days. -Returns `undefined` when the range has no measurable span. - -```typescript -stepDateRange( { from, to }, 'previous' ); // Last 7 days -> the 7 days before -``` - -#### `canStepForward( range, now )` - -Whether the next window has already happened in full. Pass the site's `now`, -not the browser's. - ## Types ### `DateRange` diff --git a/projects/packages/premium-analytics/packages/datetime/src/__tests__/get-comparison-range.test.ts b/projects/packages/premium-analytics/packages/datetime/src/__tests__/get-comparison-range.test.ts index 5b668b729484..48c97c2f7637 100644 --- a/projects/packages/premium-analytics/packages/datetime/src/__tests__/get-comparison-range.test.ts +++ b/projects/packages/premium-analytics/packages/datetime/src/__tests__/get-comparison-range.test.ts @@ -7,7 +7,6 @@ import { differenceInDays } from 'date-fns'; */ import { getDateRangeSpan } from '../date-range-span'; import { COMPARISON_PRESETS, getComparisonRangeFromPreset } from '../get-comparison-range'; -import { stepDateRange } from '../step-date-range'; import { createTZDateFromParts } from '../tz'; describe( 'getComparisonRangeFromPreset', () => { @@ -389,8 +388,7 @@ describe( 'getComparisonRangeFromPreset', () => { it( 'falls back to the day count where a month step will not reverse', () => { // 31 January through 30 March measures as two months, but two months // back from 31 January clamps to 30 November: 62 days against the - // reference's 59. The step arrows count days there, and a comparison - // naming a different window than the arrow would is a defect. + // reference's 59, so the comparison counts days instead. const clamping = { from: new Date( 2026, 0, 31, 0, 0, 0, 0 ), to: new Date( 2026, 2, 30, 23, 59, 59, 999 ), @@ -401,7 +399,6 @@ describe( 'getComparisonRangeFromPreset', () => { }; expect( getComparisonRangeFromPreset( clamping, 'previous-period' ) ).toEqual( expected ); - expect( stepDateRange( clamping, 'previous' ) ).toEqual( expected ); } ); it( 'ends the previous whole months on a month end, whatever day the reference ends on', () => { diff --git a/projects/packages/premium-analytics/packages/datetime/src/__tests__/step-date-range.test.ts b/projects/packages/premium-analytics/packages/datetime/src/__tests__/step-date-range.test.ts deleted file mode 100644 index e08e6339436b..000000000000 --- a/projects/packages/premium-analytics/packages/datetime/src/__tests__/step-date-range.test.ts +++ /dev/null @@ -1,215 +0,0 @@ -/** - * External dependencies - */ -import { TZDate } from '@date-fns/tz'; -import { differenceInCalendarDays } from 'date-fns'; -/** - * Internal dependencies - */ -import { canStepForward, stepDateRange } from '../step-date-range'; -import type { DateRange } from '../get-comparison-range'; - -/** - * Build a local-time date, so day boundaries land in the machine's timezone the - * way the span helper reads them. - * - * @param year - Full year. - * @param month - 1-based month. - * @param day - Day of the month. - * @param hour - Hour of the day. - * @param minute - Minute of the hour. - * @return The date. - */ -function at( year: number, month: number, day: number, hour = 0, minute = 0 ): Date { - return new Date( year, month - 1, day, hour, minute ); -} - -/** - * A range covering whole days, which is what a preset produces. - * - * @param from - First day. - * @param to - Last day. - * @return The inclusive whole-day range. - */ -function wholeDays( from: Date, to: Date ) { - return { - from: new Date( from.getFullYear(), from.getMonth(), from.getDate(), 0, 0, 0, 0 ), - to: new Date( to.getFullYear(), to.getMonth(), to.getDate(), 23, 59, 59, 999 ), - }; -} - -describe( 'stepDateRange', () => { - it( 'moves a day-scale window by its own length', () => { - const range = wholeDays( at( 2026, 7, 21 ), at( 2026, 7, 27 ) ); - - const previous = stepDateRange( range, 'previous' ); - - expect( previous?.from ).toEqual( wholeDays( at( 2026, 7, 14 ), at( 2026, 7, 20 ) ).from ); - expect( previous?.to ).toEqual( wholeDays( at( 2026, 7, 14 ), at( 2026, 7, 20 ) ).to ); - } ); - - it( 'moves an hour-snapped window by its full length', () => { - // `last-24-hours`: hour boundaries, a millisecond short of 24 hours. - const range = { - from: at( 2026, 7, 9, 19 ), - to: new Date( 2026, 6, 10, 18, 59, 59, 999 ), - }; - - const previous = stepDateRange( range, 'previous' ); - - expect( previous?.from ).toEqual( at( 2026, 7, 8, 19 ) ); - expect( previous?.to ).toEqual( new Date( 2026, 6, 9, 18, 59, 59, 999 ) ); - } ); - - it( 'moves a month-scale window by calendar months, not by days', () => { - // A whole year: stepping back has to land on the same days of the month. - const range = wholeDays( at( 2026, 1, 1 ), at( 2026, 12, 31 ) ); - - const previous = stepDateRange( range, 'previous' ); - - expect( previous?.from.getFullYear() ).toBe( 2025 ); - expect( previous?.from.getMonth() ).toBe( 0 ); - expect( previous?.from.getDate() ).toBe( 1 ); - expect( previous?.to.getMonth() ).toBe( 11 ); - expect( previous?.to.getDate() ).toBe( 31 ); - } ); - - it( 'steps a sub-day window by hours', () => { - const range = { from: at( 2026, 7, 27, 9 ), to: at( 2026, 7, 28, 9 ) }; - - const previous = stepDateRange( range, 'previous' ); - - expect( previous?.from ).toEqual( at( 2026, 7, 26, 9 ) ); - expect( previous?.to ).toEqual( at( 2026, 7, 27, 9 ) ); - } ); - - /* - * A property rather than one example because of clamping: `addMonths` shortens - * a day the target month cannot hold, and the clamp does not undo. - */ - describe( 'is reversible', () => { - const cases = [ - [ 'days', wholeDays( at( 2026, 7, 21 ), at( 2026, 7, 27 ) ) ], - [ 'whole months', wholeDays( at( 2026, 1, 1 ), at( 2026, 12, 31 ) ) ], - [ 'months from a 31st', wholeDays( at( 2026, 8, 31 ), at( 2026, 10, 30 ) ) ], - [ 'a leap February', wholeDays( at( 2024, 1, 31 ), at( 2024, 3, 30 ) ) ], - [ 'hours', { from: at( 2026, 7, 27, 9 ), to: at( 2026, 7, 28, 9 ) } ], - ] as const; - - it.each( cases )( 'returns to the starting window across %s', ( _name, range ) => { - const back = stepDateRange( range, 'previous' ); - const forward = back && stepDateRange( back, 'next' ); - - expect( forward?.from ).toEqual( range.from ); - expect( forward?.to ).toEqual( range.to ); - } ); - - it.each( cases )( 'returns from several steps out across %s', ( _name, range ) => { - let moved: typeof range | undefined = range; - - for ( let i = 0; i < 3; i++ ) { - moved = moved && ( stepDateRange( moved, 'previous' ) as typeof range ); - } - for ( let i = 0; i < 3; i++ ) { - moved = moved && ( stepDateRange( moved, 'next' ) as typeof range ); - } - - expect( moved?.from ).toEqual( range.from ); - expect( moved?.to ).toEqual( range.to ); - } ); - } ); - - /* - * Counted in calendar days, not elapsed milliseconds: a window crossing a DST - * change is an hour longer, and the step preserves the days a reader sees. - */ - it( 'preserves the window length when a calendar step would clamp', () => { - const range = wholeDays( at( 2026, 8, 31 ), at( 2026, 10, 30 ) ); - - const previous = stepDateRange( range, 'previous' ); - const daysIn = ( r?: DateRange ) => - r?.from && r.to ? differenceInCalendarDays( r.to, r.from ) : null; - - expect( daysIn( previous ) ).toBe( daysIn( range ) ); - } ); - - it( 'returns undefined for a range it cannot measure', () => { - expect( - stepDateRange( { from: undefined, to: at( 2026, 7, 27 ) }, 'previous' ) - ).toBeUndefined(); - expect( stepDateRange( { from: at( 2026, 7, 21 ), to: undefined }, 'next' ) ).toBeUndefined(); - expect( stepDateRange( {}, 'next' ) ).toBeUndefined(); - } ); -} ); - -describe( 'canStepForward', () => { - const now = at( 2026, 7, 27, 12 ); - - /* - * Live presets end in the future (`Last 24 hours`, `today`) or before the - * present (`Last 7 days`), yet all are the latest window available. - */ - it( 'is false on a rolling window whose end has just gone stale', () => { - const to = at( 2026, 7, 27, 11, 59 ); - - expect( canStepForward( { from: at( 2026, 7, 26, 11, 59 ), to }, now ) ).toBe( false ); - } ); - - // The live window ends in the future on purpose; counting that bucket is what - // keeps it reachable after a step back. - it( 'is true on the window right behind a live hour-snapped one', () => { - const live = { - from: at( 2026, 7, 26, 13 ), - to: new Date( 2026, 6, 27, 12, 59, 59, 999 ), - }; - const back = stepDateRange( live, 'previous' ); - - expect( canStepForward( live, now ) ).toBe( false ); - expect( canStepForward( back!, now ) ).toBe( true ); - } ); - - it( 'is true on yesterday while today is still running', () => { - const today = wholeDays( at( 2026, 7, 27 ), at( 2026, 7, 27 ) ); - const yesterday = stepDateRange( today, 'previous' ); - - expect( canStepForward( today, now ) ).toBe( false ); - expect( canStepForward( yesterday!, now ) ).toBe( true ); - } ); - - it( 'closes the bucket on the window timezone, not the machine one', () => { - const site = '+00:00'; - const yesterday = { - from: new TZDate( 2026, 6, 26, 0, 0, 0, 0, site ), - to: new TZDate( 2026, 6, 26, 23, 59, 59, 999, site ), - }; - // 02:00 UTC on the next site day, whatever timezone runs the tests. - const nowInstant = new Date( Date.UTC( 2026, 6, 27, 2, 0 ) ); - - expect( canStepForward( yesterday, nowInstant ) ).toBe( true ); - } ); - - it( 'is false on a window ending at the end of yesterday', () => { - const range = wholeDays( at( 2026, 7, 20 ), at( 2026, 7, 26 ) ); - - expect( canStepForward( range, now ) ).toBe( false ); - } ); - - it( 'is true once the window has been stepped back', () => { - const range = wholeDays( at( 2026, 7, 13 ), at( 2026, 7, 19 ) ); - - expect( canStepForward( range, now ) ).toBe( true ); - } ); - - // Stepping forward from there lands on the latest window, which is where the - // control has to disappear again. - it( 'turns false again on the window a forward step lands on', () => { - const range = wholeDays( at( 2026, 7, 13 ), at( 2026, 7, 19 ) ); - const next = stepDateRange( range, 'next' ); - - expect( canStepForward( next!, now ) ).toBe( false ); - } ); - - it( 'is false without an end to step from', () => { - expect( canStepForward( { from: at( 2026, 7, 14 ), to: undefined }, now ) ).toBe( false ); - } ); -} ); diff --git a/projects/packages/premium-analytics/packages/datetime/src/__tests__/to-date-range.test.ts b/projects/packages/premium-analytics/packages/datetime/src/__tests__/to-date-range.test.ts index 50a920b477fb..e44f0a689e98 100644 --- a/projects/packages/premium-analytics/packages/datetime/src/__tests__/to-date-range.test.ts +++ b/projects/packages/premium-analytics/packages/datetime/src/__tests__/to-date-range.test.ts @@ -6,7 +6,7 @@ import { TZDate } from '@date-fns/tz'; * Internal dependencies */ import { getDateRangeSpan } from '../date-range-span'; -import { clampRangeEndToToday, completeToDateRange } from '../to-date-range'; +import { completeToDateRange } from '../to-date-range'; /** * A zone away from UTC, so a boundary computed on the wrong clock lands on a @@ -92,47 +92,3 @@ describe( 'completeToDateRange', () => { expect( completeToDateRange( open, 'last-12-months' ) ).toBe( open ); } ); } ); - -describe( 'clampRangeEndToToday', () => { - // Where a forward step out of "12 months" lands: the running month closed, - // which reaches eleven days past the day it was taken on. - const steppedForward = { from: at( 2025, 9, 1 ), to: endOf( 2026, 8, 31 ) }; - const noon = new TZDate( 2026, 7, 20, 12, 0, 0, 0, TIMEZONE ); - - it( 'pulls a window ending after today back to the end of today', () => { - expect( clampRangeEndToToday( steppedForward, noon ) ).toEqual( { - from: at( 2025, 9, 1 ), - to: endOf( 2026, 8, 20 ), - } ); - } ); - - it( 'closes the day on the window’s own clock', () => { - // 02:00 UTC is still the 26th in New York, so a clamp read on the - // browser's clock would leave the window a day long. - const clamped = clampRangeEndToToday( - steppedForward, - new Date( Date.UTC( 2026, 7, 27, 2, 0 ) ) - ); - - expect( clamped.to ).toEqual( endOf( 2026, 8, 26 ) ); - expect( clamped.to ).toBeInstanceOf( TZDate ); - } ); - - it( 'leaves a window that already ends today where it is', () => { - const toDate = { from: at( 2025, 9, 1 ), to: endOf( 2026, 8, 20 ) }; - - expect( clampRangeEndToToday( toDate, noon ) ).toBe( toDate ); - } ); - - it( 'leaves a window ending in the past where it is', () => { - const past = { from: at( 2024, 9, 1 ), to: endOf( 2025, 8, 31 ) }; - - expect( clampRangeEndToToday( past, noon ) ).toBe( past ); - } ); - - it( 'returns a range without an end untouched', () => { - const open = { from: at( 2025, 9, 1 ) }; - - expect( clampRangeEndToToday( open, noon ) ).toBe( open ); - } ); -} ); diff --git a/projects/packages/premium-analytics/packages/datetime/src/get-comparison-range.ts b/projects/packages/premium-analytics/packages/datetime/src/get-comparison-range.ts index b57b68a0e818..6a8d2b78999b 100644 --- a/projects/packages/premium-analytics/packages/datetime/src/get-comparison-range.ts +++ b/projects/packages/premium-analytics/packages/datetime/src/get-comparison-range.ts @@ -71,8 +71,8 @@ function getInclusiveDayCount( from: Date, to: Date ): number { * whole number of months. Detected by round trip against the day after the * range ends, and again from the start stepped back by that count: a start a * month step cannot undo (31 January two months back clamps to 30 November) - * measures in days, the way the step arrows measure it. Shared by the - * previous-period shift and its label, so both take the same branch; unlike + * measures in days instead. Shared by the previous-period shift and its + * label, so both take the same branch; unlike * `getDateRangeSpan`, a single month counts. * * @param from - Range start. diff --git a/projects/packages/premium-analytics/packages/datetime/src/index.ts b/projects/packages/premium-analytics/packages/datetime/src/index.ts index d29ae84a9ebf..b1fa8dc947d9 100644 --- a/projects/packages/premium-analytics/packages/datetime/src/index.ts +++ b/projects/packages/premium-analytics/packages/datetime/src/index.ts @@ -21,8 +21,7 @@ export { INTERVAL_TYPES, isIntervalType, type IntervalType } from './interval'; export { getDateRangeSpan, type DateRangeSpan, type DateRangeSpanUnit } from './date-range-span'; -export { stepDateRange, canStepForward, type StepDirection } from './step-date-range'; -export { completeToDateRange, clampRangeEndToToday } from './to-date-range'; +export { completeToDateRange } from './to-date-range'; export { drillDateRange } from './drill-date-range'; diff --git a/projects/packages/premium-analytics/packages/datetime/src/step-date-range.ts b/projects/packages/premium-analytics/packages/datetime/src/step-date-range.ts deleted file mode 100644 index 456037eb6c66..000000000000 --- a/projects/packages/premium-analytics/packages/datetime/src/step-date-range.ts +++ /dev/null @@ -1,122 +0,0 @@ -/** - * External dependencies - */ -import { TZDate } from '@date-fns/tz'; -import { - addDays, - addHours, - addMonths, - addYears, - differenceInCalendarDays, - endOfDay, - endOfHour, - endOfMonth, - endOfYear, -} from 'date-fns'; -/** - * Internal dependencies - */ -import { getDateRangeSpan, type DateRangeSpanUnit } from './date-range-span'; -import type { DateRange } from './get-comparison-range'; - -/** - * Which way a step moves the window. - */ -export type StepDirection = 'previous' | 'next'; - -/** - * Calendar adders rather than a millisecond offset: twelve months back has to - * land on the same days of the month, and a step across a DST boundary has to - * keep the wall clock. - */ -const ADD_BY_UNIT: Record< DateRangeSpanUnit, ( date: Date, amount: number ) => Date > = { - hour: addHours, - day: addDays, - month: addMonths, - year: addYears, -}; - -/** - * Move both ends of a range by the same calendar amount. - * - * @param from - Range start. - * @param to - Range end. - * @param unit - The unit to move in. - * @param amount - How far, signed. - * @return The shifted range. - */ -function shift( from: Date, to: Date, unit: DateRangeSpanUnit, amount: number ): DateRange { - const add = ADD_BY_UNIT[ unit ]; - - return { from: add( from, amount ), to: add( to, amount ) }; -} - -/** - * Shift a range backward or forward by its own length. - * - * Falls back to the day count where a calendar step will not undo — `addMonths` - * clamps a day the target month is too short for, and reversibility wins. - * - * @param range - The window to move. - * @param direction - Which way to move it. - * @return The shifted range, or `undefined` when the range has no measurable span. - */ -export function stepDateRange( range: DateRange, direction: StepDirection ): DateRange | undefined { - const { from, to } = range; - const span = getDateRangeSpan( range ); - - if ( ! from || ! to || ! span ) { - return undefined; - } - - const sign = direction === 'previous' ? -1 : 1; - const stepped = shift( from, to, span.unit, sign * span.value ); - const returned = shift( stepped.from!, stepped.to!, span.unit, -sign * span.value ); - - if ( returned.from!.getTime() === from.getTime() && returned.to!.getTime() === to.getTime() ) { - return stepped; - } - - const days = differenceInCalendarDays( to, from ) + 1; - - return shift( from, to, 'day', sign * days ); -} - -/** - * End of the bucket an instant sits in, at each granularity a window measures - * in. - */ -const END_OF_BUCKET: Record< DateRangeSpanUnit, ( date: Date ) => Date > = { - hour: endOfHour, - day: endOfDay, - month: endOfMonth, - year: endOfYear, -}; - -/** - * Whether there is a later window to step into. - * - * The next window qualifies once it ends within the bucket `now` sits in, so a - * live preset ending in the running hour or day stays reachable. - * - * @param range - The window to test. - * @param now - The instant to compare against. - * @return Whether a forward step lands on a window already worth showing. - */ -export function canStepForward( range: DateRange, now: Date ): boolean { - const span = getDateRangeSpan( range ); - const next = stepDateRange( range, 'next' ); - - if ( ! span || ! next?.to ) { - return false; - } - - // Anchored to the window's own timezone, so a site offset from the browser - // closes its buckets on its own clock. - const timeZone = range.to && 'timeZone' in range.to ? ( range.to as TZDate ).timeZone : undefined; - const horizon = END_OF_BUCKET[ span.unit ]( - timeZone ? new TZDate( now.getTime(), timeZone ) : now - ); - - return next.to.getTime() <= horizon.getTime(); -} diff --git a/projects/packages/premium-analytics/packages/datetime/src/to-date-range.ts b/projects/packages/premium-analytics/packages/datetime/src/to-date-range.ts index 37c05b688312..7c1a3491ba05 100644 --- a/projects/packages/premium-analytics/packages/datetime/src/to-date-range.ts +++ b/projects/packages/premium-analytics/packages/datetime/src/to-date-range.ts @@ -1,8 +1,7 @@ /** * External dependencies */ -import { TZDate } from '@date-fns/tz'; -import { endOfDay, endOfMonth } from 'date-fns'; +import { endOfMonth } from 'date-fns'; /** * Internal dependencies */ @@ -15,9 +14,9 @@ import type { DateRange } from './get-comparison-range'; * `last-12-months` runs from the first of a month to the end of today, so * measuring it reports the shape of today's date rather than of the * selection: 354 days mid-month, 12 months on the last day of one. Anything - * that describes or moves the window in whole units — its length, the step - * arrows, the previous period — measures this window instead. What the reader - * sees stays the range as selected. + * that describes the window in whole units — its length, the previous + * period — measures this window instead. What the reader sees stays the range + * as selected. * * Every other preset comes back untouched. A hand-picked range that happens * to start on the first has no running month to complete, so it is measured @@ -40,30 +39,3 @@ export function completeToDateRange< T extends DateRange >( return { ...range, to: endOfMonth( range.to ) }; } - -/** - * Pull a window's end back to the end of today when it runs past it. - * - * A window measured in whole months or years may step forward into the month - * or year in progress, which `canStepForward` counts as reachable so the - * window a reader stepped back from stays reachable. The step itself lands on - * the end of that unit, days the report has no data for and the chart would - * draw as empty buckets. Stepping forward out of "12 months" therefore returns - * the to-date window the reader started from. - * - * @param range - The window to clamp. - * @param now - The current instant. - * @return The clamped window, or `range` itself when it already ends today or earlier. - */ -export function clampRangeEndToToday< T extends DateRange >( range: T, now: Date ): T { - if ( ! range.to ) { - return range; - } - - // Anchored to the window's own timezone, so a site offset from the browser - // closes the day on its own clock. - const timeZone = 'timeZone' in range.to ? ( range.to as TZDate ).timeZone : undefined; - const endOfToday = endOfDay( timeZone ? new TZDate( now.getTime(), timeZone ) : now ); - - return range.to.getTime() > endOfToday.getTime() ? { ...range, to: endOfToday } : range; -} diff --git a/projects/packages/premium-analytics/packages/routing/src/hooks/use-report-date-filters/__tests__/build-range-patch.test.ts b/projects/packages/premium-analytics/packages/routing/src/hooks/use-report-date-filters/__tests__/build-range-patch.test.ts index 00918dfa1f68..d01433eecb6d 100644 --- a/projects/packages/premium-analytics/packages/routing/src/hooks/use-report-date-filters/__tests__/build-range-patch.test.ts +++ b/projects/packages/premium-analytics/packages/routing/src/hooks/use-report-date-filters/__tests__/build-range-patch.test.ts @@ -10,7 +10,6 @@ jest.mock( '@jetpack-premium-analytics/datetime', () => ( { /** * External dependencies */ -import { canStepForward, stepDateRange } from '@jetpack-premium-analytics/datetime'; /** * Internal dependencies */ @@ -151,35 +150,6 @@ describe( 'buildRangePatch', () => { expect( patch?.to ).toBe( '2026-07-10T14:30:00.000+00:00' ); } ); - /* - * Rounding a stepped `to` up to the end of its day stretches a rolling - * window on every step and pushes its next window into the future, hiding - * the forward arrow. - */ - it( 'steps a rolling window back and forward without changing its length', () => { - const previous = stepDateRange( { from, to }, 'previous' ); - const back = buildRangePatch( { - nextRange: previous, - nextPresetId: 'custom', - exactRange: true, - effective: { preset: 'last-24-hours', interval: 'hour' }, - } ); - - expect( back ).toMatchObject( { - from: '2026-07-08T14:30:00.000+00:00', - to: '2026-07-09T14:30:00.000+00:00', - interval: 'hour', - preset: 'custom', - } ); - - const backRange = { from: new Date( back?.from ?? '' ), to: new Date( back?.to ?? '' ) }; - expect( canStepForward( backRange, to ) ).toBe( true ); - - const returned = stepDateRange( backRange, 'next' ); - expect( returned?.from?.getTime() ).toBe( from.getTime() ); - expect( returned?.to?.getTime() ).toBe( to.getTime() ); - } ); - it( 're-derives the comparison range from the new primary range when enabled', () => { const patch = buildRangePatch( { nextRange: { from, to }, diff --git a/projects/packages/premium-analytics/packages/routing/src/hooks/use-report-date-filters/__tests__/use-report-date-filters.test.tsx b/projects/packages/premium-analytics/packages/routing/src/hooks/use-report-date-filters/__tests__/use-report-date-filters.test.tsx index 64e705fbd7f4..26dfb8f8f329 100644 --- a/projects/packages/premium-analytics/packages/routing/src/hooks/use-report-date-filters/__tests__/use-report-date-filters.test.tsx +++ b/projects/packages/premium-analytics/packages/routing/src/hooks/use-report-date-filters/__tests__/use-report-date-filters.test.tsx @@ -374,39 +374,6 @@ describe( 'useReportDateFilters', () => { } ); } ); - /* - * The step commits the exact stepped window: rounding its `to` up to the - * end of the day would stretch a rolling window on every step. - */ - it( 'steps the applied window by its own length and commits it as custom', () => { - const { result, rerender } = renderDateFilters( { - from: '2026-07-09T14:30:00.000+00:00', - to: '2026-07-10T14:30:00.000+00:00', - preset: 'last-24-hours', - interval: 'hour', - } ); - - act( () => result.current.onStep( 'previous' ) ); - rerender(); - - expect( mockNavigate ).toHaveBeenCalledTimes( 1 ); - expect( mockSearch ).toMatchObject( { - from: '2026-07-08T14:30:00.000+00:00', - to: '2026-07-09T14:30:00.000+00:00', - preset: 'custom', - interval: 'hour', - } ); - expect( result.current.appliedPresetId ).toBe( 'custom' ); - } ); - - it( 'ignores a step without a measurable window', () => { - const { result } = renderDateFilters(); - - act( () => result.current.onStep( 'previous' ) ); - - expect( mockNavigate ).not.toHaveBeenCalled(); - } ); - it( 'replaces the current entry when the page reconciles the range', () => { const { result, rerender } = renderDateFilters( { from: '2026-07-01T00:00:00.000+00:00', @@ -636,53 +603,4 @@ describe( 'useReportDateFilters', () => { expect( result.current.presetId ).toBe( 'custom' ); } ); } ); - - it( 'steps a to-date preset by whole months and compares it with the months before', () => { - // `last-12-months` as read on 20 August 2026. Stepped by its day count - // the window would start on 12 September and its comparison on the 24th. - const { result, rerender } = renderDateFilters( { - from: '2025-09-01T00:00:00.000+00:00', - to: '2026-08-20T23:59:59.999+00:00', - preset: 'last-12-months', - interval: 'month', - comp: '1', - compare_preset: 'previous-period', - } ); - - act( () => result.current.onStep( 'previous' ) ); - rerender(); - - expect( mockSearch ).toMatchObject( { - from: '2024-09-01T00:00:00.000+00:00', - to: '2025-08-31T23:59:59.999+00:00', - preset: 'custom', - interval: 'month', - compare_from: '2023-09-01T00:00:00.000+00:00', - compare_to: '2024-08-31T23:59:59.999+00:00', - } ); - } ); - - it( 'lands back on the to-date window when a step forward closes the running month', () => { - // The window a step back out of `last-12-months` leaves. Stepping - // forward again closes August, eleven days past the day it is read on: - // days the report has no data for, and the forward arrow would then - // disappear on a window nobody can leave. - jest.useFakeTimers().setSystemTime( Date.parse( '2026-08-20T12:00:00.000Z' ) ); - - const { result, rerender } = renderDateFilters( { - from: '2024-09-01T00:00:00.000+00:00', - to: '2025-08-31T23:59:59.999+00:00', - preset: 'custom', - interval: 'month', - } ); - - act( () => result.current.onStep( 'next' ) ); - rerender(); - - expect( mockSearch ).toMatchObject( { - from: '2025-09-01T00:00:00.000+00:00', - to: '2026-08-20T23:59:59.999+00:00', - preset: 'custom', - } ); - } ); } ); diff --git a/projects/packages/premium-analytics/packages/routing/src/hooks/use-report-date-filters/use-report-date-filters.tsx b/projects/packages/premium-analytics/packages/routing/src/hooks/use-report-date-filters/use-report-date-filters.tsx index 097f85148e3b..1346e8a89642 100644 --- a/projects/packages/premium-analytics/packages/routing/src/hooks/use-report-date-filters/use-report-date-filters.tsx +++ b/projects/packages/premium-analytics/packages/routing/src/hooks/use-report-date-filters/use-report-date-filters.tsx @@ -7,12 +7,9 @@ import { resolveIntervalForRange, } from '@jetpack-premium-analytics/data'; import { - clampRangeEndToToday, - completeToDateRange, drillDateRange, PRESET_CUSTOM, reportingTimeZone, - stepDateRange, toLocalTZ, } from '@jetpack-premium-analytics/datetime'; import { useCallback, useMemo } from 'react'; @@ -28,7 +25,6 @@ import type { DateRange, IntervalType, PrimaryPresetId, - StepDirection, } from '@jetpack-premium-analytics/datetime'; type PickerRange = { from: Date | undefined; to: Date | undefined }; @@ -70,11 +66,6 @@ export type ReportDateFilters = { onComparisonChange: ( range: DateRange | undefined, presetId?: ComparisonPresetId ) => void; onIntervalChange: ( interval: IntervalType ) => void; - /** - * Step the applied window backward or forward by its own length. - */ - onStep: ( direction: StepDirection ) => void; - /** * Open the chart bucket containing a date, narrowing to the next finer * interval. `interval` is the bucket size the chart drew; defaults to the @@ -259,46 +250,9 @@ export function useReportDateFilters< TFrom extends string >( from?: TFrom ): Re ); /* - * Commits and pushes a history entry so Back undoes the step. Steps the - * applied range, not the staged one — the arrows sit outside the picker, - * so stepping must not apply an open draft. - */ - const onStep = useCallback( - ( direction: StepDirection ) => { - // A to-date window steps as its completed window, so the arrows move - // "12 months" by whole months rather than by the days read so far. - const stepped = stepDateRange( - completeToDateRange( appliedRange, appliedPresetId ), - direction - ); - - if ( ! stepped ) { - return; - } - - const patch = buildRangePatch( { - // A forward step closes the running unit the window was measured - // against, which reaches past today. Stepping forward out of - // "12 months" returns the to-date window, not a month of empty - // buckets. - nextRange: clampRangeEndToToday( stepped, toLocalTZ( undefined, timeZone ) ), - nextPresetId: PRESET_CUSTOM, - exactRange: true, - effective, - } ); - - if ( patch ) { - stage( patch ); - commit(); - } - }, - [ appliedPresetId, appliedRange, commit, effective, stage, timeZone ] - ); - - /* - * Commits and pushes a history entry, like `onStep`, so Back exits a - * drill-down. Reads the applied range/interval, not the staged one: the - * chart draws what's applied, so the click belongs to that window. + * Commits and pushes a history entry, so Back exits a drill-down. Reads the + * applied range/interval, not the staged one: the chart draws what's + * applied, so the click belongs to that window. */ const drillDown = useCallback( ( date: Date, bucketInterval: IntervalType = appliedInterval ) => { @@ -375,7 +329,6 @@ export function useReportDateFilters< TFrom extends string >( from?: TFrom ): Re onChange, onComparisonChange, onIntervalChange, - onStep, drillDown, onApply, onCancel, diff --git a/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/__tests__/date-filters-panel.test.tsx b/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/__tests__/date-filters-panel.test.tsx index f0b933ef7c86..453035b08ba9 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/__tests__/date-filters-panel.test.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/__tests__/date-filters-panel.test.tsx @@ -43,32 +43,6 @@ describe( 'DateFiltersPanel', () => { expect( screen.queryByRole( 'button', { name: 'Compare' } ) ).not.toBeInTheDocument(); } ); - it( 'steps the applied window from the navigation arrows', async () => { - const onStep = jest.fn(); - const user = userEvent.setup(); - - // A window whose next one has fully happened, so both arrows render. - renderPanel( { - onStep, - appliedRange: { - from: new Date( '2020-07-01T00:00:00.000Z' ), - to: new Date( '2020-07-30T23:59:59.999Z' ), - }, - } ); - - await user.click( screen.getByRole( 'button', { name: 'Previous period' } ) ); - expect( onStep ).toHaveBeenCalledWith( 'previous' ); - - await user.click( screen.getByRole( 'button', { name: 'Next period' } ) ); - expect( onStep ).toHaveBeenCalledWith( 'next' ); - } ); - - it( 'renders no period navigation without onStep', () => { - renderPanel(); - - expect( screen.queryByRole( 'button', { name: 'Previous period' } ) ).not.toBeInTheDocument(); - } ); - // The comparison qualifies the range the presets just set; the interval only // buckets the charts. Reading order follows that, so it is worth pinning. it( 'places the comparison before the chart interval', () => { @@ -167,20 +141,15 @@ describe( 'DateFiltersPanel', () => { it( 'greys every control out while disabled', () => { renderPanel( { disabled: true, - onStep: jest.fn(), - appliedRange: { - from: new Date( '2020-07-01T00:00:00.000Z' ), - to: new Date( '2020-07-30T23:59:59.999Z' ), - }, withIntervalControl: true, intervalOptions: [ 'day', 'week' ], interval: 'day', onIntervalChange: jest.fn(), } ); - // Both arrows, the period, the comparison and the interval. + // The period, the comparison and the interval. const buttons = screen.getAllByRole( 'button' ); - expect( buttons ).toHaveLength( 5 ); + expect( buttons ).toHaveLength( 3 ); buttons.forEach( button => { expect( button ).toHaveAttribute( 'aria-disabled', 'true' ); } ); diff --git a/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/date-filters-panel.scss b/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/date-filters-panel.scss index ef53bf602833..5b6a4555151a 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/date-filters-panel.scss +++ b/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/date-filters-panel.scss @@ -27,8 +27,7 @@ } // A glyph in a square box: nothing to give back when the row runs short. - .date-interval-dropdown, - .date-period-navigation { + .date-interval-dropdown { flex: 0 0 auto; } } diff --git a/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/date-filters-panel.tsx b/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/date-filters-panel.tsx index e83a0bf8b82e..c6e15bc0bff6 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/date-filters-panel.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/date-filters-panel.tsx @@ -3,14 +3,12 @@ */ import { useReportScope } from '@jetpack-premium-analytics/data'; import { - canStepForward, isComparisonPresetId, isPrimaryPreset, type ComparisonPresetId, type IntervalType, type PrimaryPresetId, type QuickSurfacePresetId, - type StepDirection, } from '@jetpack-premium-analytics/datetime'; import { Stack } from '@jetpack-premium-analytics/externals'; import { BaseControl } from '@wordpress/components'; @@ -21,7 +19,6 @@ import { useMemo, useCallback, useState } from 'react'; import { DateComparisonDropdown } from '../date-comparison-dropdown'; import { DateIntervalDropdown } from '../date-interval-dropdown'; import { DatePeriodDropdown } from '../date-period-dropdown'; -import { DatePeriodNavigation } from '../date-period-navigation'; import { useComparisonDatePresets } from '../use-comparison-date-presets'; import './date-filters-panel.scss'; @@ -95,13 +92,6 @@ export type DateFiltersPanelProps = { onIntervalChange?: ( interval: IntervalType ) => void; - /** - * Steps the applied window backward or forward by its own length. Left out, - * the navigation controls are not rendered at all: a surface whose range is - * not a movable window has nowhere to step. - */ - onStep?: ( direction: StepDirection ) => void; - /** * Props for the date range popover. */ @@ -150,7 +140,6 @@ export function DateFiltersPanel( { onChange, onComparisonChange, onIntervalChange, - onStep, rangeControlProps = { label: null, help: null, @@ -245,27 +234,6 @@ export function DateFiltersPanel( { ] ); - /* - * Built once, rendered in the row and the probe, like the other controls. - * Reads the applied range, not staged: the arrows commit on click, so a - * drafted range must not decide whether the forward step is available. - */ - const navigationControl = useMemo( () => { - if ( ! onStep ) { - return null; - } - - const committedRange = appliedRange ?? range; - - return ( - - ); - }, [ appliedRange, disabled, onStep, range ] ); - // Same arrangement as the comparison control: built once, rendered in the // row and in the probe. const intervalControl = useMemo( @@ -284,8 +252,6 @@ export function DateFiltersPanel( { return (
- { navigationControl } - { - const stepped = stepDateRange( stagedPrimaryRef.current.range, direction ); - - if ( ! stepped?.from || ! stepped.to ) { - return; - } - - const nextPrimary: PrimaryFilterState = { - range: { from: stepped.from, to: stepped.to }, - presetId: PRESET_CUSTOM, - }; - - stagedPrimaryRef.current = nextPrimary; - setStagedPrimary( nextPrimary ); - setCommittedPrimary( nextPrimary ); - }, [] ); - /* * The interval follows the range being edited, so switching preset re-derives * the menu. A pick the new range still allows survives; one it does not falls @@ -214,7 +192,6 @@ function DateFiltersPanelStory( { onChange={ handlePrimaryChange } onComparisonChange={ handleComparisonChange } onIntervalChange={ setPickedInterval } - onStep={ handleStep } onApply={ handlePrimaryApply } onCancel={ handlePrimaryCancel } canApply={ canApplyPrimary } diff --git a/projects/packages/premium-analytics/packages/ui/src/date-period-navigation/__tests__/date-period-navigation.test.tsx b/projects/packages/premium-analytics/packages/ui/src/date-period-navigation/__tests__/date-period-navigation.test.tsx deleted file mode 100644 index a63e68de2bea..000000000000 --- a/projects/packages/premium-analytics/packages/ui/src/date-period-navigation/__tests__/date-period-navigation.test.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { DatePeriodNavigation } from '../date-period-navigation'; - -describe( 'DatePeriodNavigation', () => { - it( 'steps backward', async () => { - const onStep = jest.fn(); - const user = userEvent.setup(); - - render( ); - - await user.click( screen.getByRole( 'button', { name: 'Previous period' } ) ); - - expect( onStep ).toHaveBeenCalledWith( 'previous' ); - } ); - - it( 'steps forward', async () => { - const onStep = jest.fn(); - const user = userEvent.setup(); - - render( ); - - await user.click( screen.getByRole( 'button', { name: 'Next period' } ) ); - - expect( onStep ).toHaveBeenCalledWith( 'next' ); - } ); - - /* - * Absent rather than disabled. A disabled arrow states a rule the reader has - * to work out, and on a live preset that rule holds for as long as they stay - * on it. - */ - it( 'omits the forward control while the window reaches the present', () => { - render( ); - - expect( screen.getByRole( 'button', { name: 'Previous period' } ) ).toBeInTheDocument(); - expect( screen.queryByRole( 'button', { name: 'Next period' } ) ).not.toBeInTheDocument(); - } ); - - it( 'greys both arrows out while disabled, focusable still', async () => { - const onStep = jest.fn(); - const user = userEvent.setup(); - - render( ); - - const previous = screen.getByRole( 'button', { name: 'Previous period' } ); - expect( previous ).toHaveAttribute( 'aria-disabled', 'true' ); - expect( screen.getByRole( 'button', { name: 'Next period' } ) ).toHaveAttribute( - 'aria-disabled', - 'true' - ); - - previous.focus(); - expect( previous ).toHaveFocus(); - - await user.click( previous ); - expect( onStep ).not.toHaveBeenCalled(); - } ); -} ); diff --git a/projects/packages/premium-analytics/packages/ui/src/date-period-navigation/date-period-navigation.scss b/projects/packages/premium-analytics/packages/ui/src/date-period-navigation/date-period-navigation.scss deleted file mode 100644 index d3c61b02c0ba..000000000000 --- a/projects/packages/premium-analytics/packages/ui/src/date-period-navigation/date-period-navigation.scss +++ /dev/null @@ -1,21 +0,0 @@ -@use "../styles/preset-surface"; - -// Its own bordered group, to the left of the presets rather than inside them: -// the pills are a set to choose from and these move the choice, so sharing one -// frame would read as a fifth and sixth preset. -.date-period-navigation { - - @include preset-surface.group; -} - -.date-period-navigation__step { - - @include preset-surface.pill; - - // A glyph in a square box, sized like the pills beside it, which take this - // from `.date-filters-panel`. Set here too so the group holds its shape - // wherever else it is mounted. - --wp-ui-button-height: calc(var(--wpds-dimension-size-lg) - 2px); - - min-width: var(--wpds-dimension-size-lg); -} diff --git a/projects/packages/premium-analytics/packages/ui/src/date-period-navigation/date-period-navigation.tsx b/projects/packages/premium-analytics/packages/ui/src/date-period-navigation/date-period-navigation.tsx deleted file mode 100644 index fb922d828da7..000000000000 --- a/projects/packages/premium-analytics/packages/ui/src/date-period-navigation/date-period-navigation.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/** - * External dependencies - */ -import { Button } from '@jetpack-premium-analytics/externals'; -import { __ } from '@wordpress/i18n'; -import { chevronLeft, chevronRight } from '@wordpress/icons'; -import clsx from 'clsx'; - -import './date-period-navigation.scss'; - -type DatePeriodNavigationProps = { - /** - * Whether the window has a future to step into. Derived upstream from the - * range rather than from a layout rule, so the control follows where the - * window sits in time. - */ - canStepForward: boolean; - - /** Greys the arrows out but keeps them focusable: a passing state, not a missing control. */ - disabled?: boolean; - - /** - * Fired with the direction the reader asked for. - */ - onStep: ( direction: 'previous' | 'next' ) => void; -}; - -/** - * Steps the active window backward and forward by its own length. - * - * The forward control is absent rather than disabled on the latest window — - * a disabled arrow states a rule the reader has to work out for themselves. - * - * @param {DatePeriodNavigationProps} props - The props for the DatePeriodNavigation component. - * @return The navigation element. - */ -export function DatePeriodNavigation( { - canStepForward, - disabled = false, - onStep, -}: DatePeriodNavigationProps ) { - return ( -
- { /* The glyph carries no wording, so the name is the whole label. */ } - - - { canStepForward && ( - - ) } -
- ); -} diff --git a/projects/packages/premium-analytics/packages/ui/src/date-period-navigation/index.ts b/projects/packages/premium-analytics/packages/ui/src/date-period-navigation/index.ts deleted file mode 100644 index 819b14bb50ae..000000000000 --- a/projects/packages/premium-analytics/packages/ui/src/date-period-navigation/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { DatePeriodNavigation } from './date-period-navigation'; diff --git a/projects/packages/premium-analytics/packages/ui/src/date-period-navigation/stories/date-period-navigation.stories.tsx b/projects/packages/premium-analytics/packages/ui/src/date-period-navigation/stories/date-period-navigation.stories.tsx deleted file mode 100644 index 944e2dba3ade..000000000000 --- a/projects/packages/premium-analytics/packages/ui/src/date-period-navigation/stories/date-period-navigation.stories.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { canStepForward, stepDateRange } from '@jetpack-premium-analytics/datetime'; -import { useState } from 'react'; -import { DatePeriodNavigation } from '../date-period-navigation'; -import type { DateRange, StepDirection } from '@jetpack-premium-analytics/datetime'; -import type { Meta, StoryObj } from '@storybook/react'; - -const meta: Meta< typeof DatePeriodNavigation > = { - title: 'Packages/Premium Analytics/UI/DatePeriodNavigation', - component: DatePeriodNavigation, - tags: [ 'autodocs' ], - parameters: { - docs: { - description: { - component: - 'Steps the active window backward and forward by its own length.\n\n' + - 'The forward control is absent rather than disabled while the window is the ' + - 'latest one available. `canStepForward` is a prop, derived upstream from the ' + - 'range, so availability follows where the window sits in time rather than a ' + - 'layout rule.', - }, - }, - }, - argTypes: { - onStep: { control: false }, - }, -}; - -export default meta; - -type Story = StoryObj< typeof DatePeriodNavigation >; - -/** - * Seven whole days ending at the given day. - * - * @param endingDaysAgo - How many days back the window ends. - * @return The window. - */ -function weekEnding( endingDaysAgo: number ): DateRange { - const to = new Date(); - to.setDate( to.getDate() - endingDaysAgo ); - to.setHours( 23, 59, 59, 999 ); - - const from = new Date( to ); - from.setDate( from.getDate() - 6 ); - from.setHours( 0, 0, 0, 0 ); - - return { from, to }; -} - -/** - * The control wired to a real window, so stepping moves it and the forward - * arrow appears and disappears the way it does in the panel. - * - * @param props - Story props. - * @param props.initialEndsAgo - How many days back the starting window ends. - * @return The wired control. - */ -function DatePeriodNavigationWithState( { initialEndsAgo }: { initialEndsAgo: number } ) { - const [ range, setRange ] = useState( () => weekEnding( initialEndsAgo ) ); - - const step = ( direction: StepDirection ) => { - const next = stepDateRange( range, direction ); - - if ( next ) { - setRange( next ); - } - }; - - return ( - - ); -} - -/** - * The latest window: only the back arrow, since there is nothing after it. - */ -export const Default: Story = { - render: () => , -}; - -/** - * A window already stepped back. Both arrows show; stepping forward to the - * latest window drops the forward one again. - */ -export const SteppedBack: Story = { - render: () => , -}; diff --git a/projects/packages/premium-analytics/packages/ui/src/index.ts b/projects/packages/premium-analytics/packages/ui/src/index.ts index b86ab77244b9..9fe58ee6a001 100644 --- a/projects/packages/premium-analytics/packages/ui/src/index.ts +++ b/projects/packages/premium-analytics/packages/ui/src/index.ts @@ -9,7 +9,6 @@ export { export { DateFiltersPanel } from './date-filters-panel'; export { DateIntervalDropdown } from './date-interval-dropdown'; export { DatePeriodDropdown } from './date-period-dropdown'; -export { DatePeriodNavigation } from './date-period-navigation'; export { DateYearFilter, type DateYearFilterProps } from './date-year-filter'; export { OnboardingWelcomeModal, diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/__tests__/report-page-layout.test.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/__tests__/report-page-layout.test.tsx index bba0fab005eb..9bb4d25b2e2d 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/__tests__/report-page-layout.test.tsx +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/__tests__/report-page-layout.test.tsx @@ -43,7 +43,6 @@ function buildDateFilters(): ReportDateFilters { onChange: jest.fn(), onComparisonChange: jest.fn(), onIntervalChange: jest.fn(), - onStep: jest.fn(), onApply: jest.fn(), onCancel: jest.fn(), canApply: true, diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/stories/report-page.stories.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/stories/report-page.stories.tsx index 430de235d741..db2a0317538b 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/stories/report-page.stories.tsx +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/stories/report-page.stories.tsx @@ -183,7 +183,6 @@ const STORY_DATE_FILTERS: ReportDateFilters = { onChange: () => {}, onComparisonChange: () => {}, onIntervalChange: () => {}, - onStep: () => {}, onApply: () => {}, onCancel: () => {}, canApply: false, diff --git a/projects/packages/premium-analytics/routes/use-detail-date-controls.test.ts b/projects/packages/premium-analytics/routes/use-detail-date-controls.test.ts index a57a4493c135..0cb75b16bc48 100644 --- a/projects/packages/premium-analytics/routes/use-detail-date-controls.test.ts +++ b/projects/packages/premium-analytics/routes/use-detail-date-controls.test.ts @@ -51,7 +51,6 @@ describe( 'useDetailDateControls', () => { presetIds: DETAIL_SURFACE_PRESETS, withCustomRange: false, withIntervalControl: false, - onStep: undefined, } ); // Half past midnight in Taipei, not in the runner's zone. expect( result.current.allTimeStart?.toISOString() ).toBe( '2026-07-07T16:29:35.000Z' ); diff --git a/projects/packages/premium-analytics/routes/use-detail-date-controls.ts b/projects/packages/premium-analytics/routes/use-detail-date-controls.ts index 0869e867e828..4d4da11b7259 100644 --- a/projects/packages/premium-analytics/routes/use-detail-date-controls.ts +++ b/projects/packages/premium-analytics/routes/use-detail-date-controls.ts @@ -16,7 +16,6 @@ type DetailDateControls = { allTimeStart: Date | undefined; withCustomRange: false; withIntervalControl: false; - onStep: undefined; }; /** @@ -36,8 +35,8 @@ type DetailDateFilters = { * render before the summary loads, so an all-time range applied against an * unknown or stale start is re-anchored in place once it resolves. * - * Spread after the date-filter controller's props — `onStep` and the interval - * props it hands out are what this unsets. + * Spread after the date-filter controller's props: the interval props it + * hands out are what this unsets. * * @param publishedDate - The resource's publish date, as the summary carries it: * a site-local wall time, or an offset-bearing instant. @@ -80,7 +79,6 @@ export function useDetailDateControls( allTimeStart, withCustomRange: false, withIntervalControl: false, - onStep: undefined, } ), [ allTimeStart ] ); diff --git a/projects/packages/premium-analytics/tests/groups/ui-no-mocks-part1.test.tsx b/projects/packages/premium-analytics/tests/groups/ui-no-mocks-part1.test.tsx index cd383c7da195..ff08ebcfed1f 100644 --- a/projects/packages/premium-analytics/tests/groups/ui-no-mocks-part1.test.tsx +++ b/projects/packages/premium-analytics/tests/groups/ui-no-mocks-part1.test.tsx @@ -5,5 +5,4 @@ import '../../packages/ui/src/dataviews-drilldown-native/__tests__/dataviews-dri import '../../packages/ui/src/dataviews-drilldown-native/__tests__/process-hierarchy-levels.test'; import '../../packages/ui/src/date-comparison-dropdown/__tests__/date-comparison-dropdown.test'; import '../../packages/ui/src/date-interval-dropdown/__tests__/date-interval-dropdown.test'; -import '../../packages/ui/src/date-period-navigation/__tests__/date-period-navigation.test'; import '../../packages/ui/src/onboarding-welcome-modal/__tests__/onboarding-welcome-modal.test'; diff --git a/projects/plugins/jetpack/changelog/remove-period-step-arrows b/projects/plugins/jetpack/changelog/remove-period-step-arrows new file mode 100644 index 000000000000..70e923b0300c --- /dev/null +++ b/projects/plugins/jetpack/changelog/remove-period-step-arrows @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +Premium Analytics: remove the arrows that stepped the date range back and forward a period. diff --git a/projects/plugins/premium-analytics/changelog/remove-period-step-arrows b/projects/plugins/premium-analytics/changelog/remove-period-step-arrows new file mode 100644 index 000000000000..eabeef518231 --- /dev/null +++ b/projects/plugins/premium-analytics/changelog/remove-period-step-arrows @@ -0,0 +1,4 @@ +Significance: minor +Type: removed + +Remove the arrows that stepped the date range back and forward a period. From e2c8034281966edf44e2e92c65cf25bb3902a053 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 9 Sep 2026 11:00:23 +0800 Subject: [PATCH 2/3] fix: drop the deleted step-date-range suite from its test group Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01SwEiwvYnpNdaqsK71QznVw --- .../tests/groups/datetime-no-mocks-part2.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/packages/premium-analytics/tests/groups/datetime-no-mocks-part2.test.tsx b/projects/packages/premium-analytics/tests/groups/datetime-no-mocks-part2.test.tsx index fb7dfef7881b..f52eee4a2470 100644 --- a/projects/packages/premium-analytics/tests/groups/datetime-no-mocks-part2.test.tsx +++ b/projects/packages/premium-analytics/tests/groups/datetime-no-mocks-part2.test.tsx @@ -2,6 +2,5 @@ import '../../packages/datetime/src/__tests__/site-time-zone.test'; import '../../packages/datetime/src/__tests__/site-timestamp.test'; -import '../../packages/datetime/src/__tests__/step-date-range.test'; import '../../packages/datetime/src/__tests__/tz.test'; import '../../packages/datetime/src/__tests__/year-presets.test'; From 4442a253f531b5acb5f4e64fa5b10251cf1ea71f Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 9 Sep 2026 11:28:18 +0800 Subject: [PATCH 3/3] chore: tidy up the leftovers from removing the step arrows Drops `completeToDateRange` from the datetime barrel, whose only caller outside the package was the step callback, and clears the docs and comments the removal left pointing at things that no longer exist. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01THgGUBmikHUTBQQ4B2ktSW --- .../packages/premium-analytics/packages/datetime/README.md | 2 +- .../premium-analytics/packages/datetime/src/index.ts | 2 -- .../__tests__/build-range-patch.test.ts | 3 --- .../ui/src/date-filters-panel/date-filters-panel.tsx | 7 ------- .../stories/date-filters-panel.stories.tsx | 2 +- .../plugins/jetpack/changelog/remove-period-step-arrows | 2 +- 6 files changed, 3 insertions(+), 15 deletions(-) diff --git a/projects/packages/premium-analytics/packages/datetime/README.md b/projects/packages/premium-analytics/packages/datetime/README.md index 4f21073c2884..0df9f7b24a91 100644 --- a/projects/packages/premium-analytics/packages/datetime/README.md +++ b/projects/packages/premium-analytics/packages/datetime/README.md @@ -197,7 +197,7 @@ same window as an earlier one is dropped. Each option carries the resolved **Returns:** `ComparisonOption[]` - Empty when the range is incomplete or inverted -### Range Measurement and Stepping +### Range Measurement #### `getDateRangeSpan( range? )` diff --git a/projects/packages/premium-analytics/packages/datetime/src/index.ts b/projects/packages/premium-analytics/packages/datetime/src/index.ts index b1fa8dc947d9..fba3832bddee 100644 --- a/projects/packages/premium-analytics/packages/datetime/src/index.ts +++ b/projects/packages/premium-analytics/packages/datetime/src/index.ts @@ -21,8 +21,6 @@ export { INTERVAL_TYPES, isIntervalType, type IntervalType } from './interval'; export { getDateRangeSpan, type DateRangeSpan, type DateRangeSpanUnit } from './date-range-span'; -export { completeToDateRange } from './to-date-range'; - export { drillDateRange } from './drill-date-range'; export { toBucketStamp, resolveBucketStamp } from './bucket-stamp'; diff --git a/projects/packages/premium-analytics/packages/routing/src/hooks/use-report-date-filters/__tests__/build-range-patch.test.ts b/projects/packages/premium-analytics/packages/routing/src/hooks/use-report-date-filters/__tests__/build-range-patch.test.ts index d01433eecb6d..2e7e28e967f1 100644 --- a/projects/packages/premium-analytics/packages/routing/src/hooks/use-report-date-filters/__tests__/build-range-patch.test.ts +++ b/projects/packages/premium-analytics/packages/routing/src/hooks/use-report-date-filters/__tests__/build-range-patch.test.ts @@ -7,9 +7,6 @@ jest.mock( '@jetpack-premium-analytics/datetime', () => ( { ...jest.requireActual( '@jetpack-premium-analytics/datetime' ), reportingTimeZone: () => '+00:00', } ) ); -/** - * External dependencies - */ /** * Internal dependencies */ diff --git a/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/date-filters-panel.tsx b/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/date-filters-panel.tsx index c6e15bc0bff6..17bed79f5cad 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/date-filters-panel.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/date-filters-panel.tsx @@ -206,11 +206,6 @@ export function DateFiltersPanel( { const comparisonLabel = typeof comparisonControlProps.label === 'string' ? comparisonControlProps.label : undefined; - /* - * Built once and rendered twice: the row the user sees, and the probe that - * measures it. The same element in both places means the measurement cannot - * drift from what it measures. - */ const comparisonControl = useMemo( () => ( withIntervalControl && intervalOptions && onIntervalChange ? ( diff --git a/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/stories/date-filters-panel.stories.tsx b/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/stories/date-filters-panel.stories.tsx index 15f3fe9ac06f..09f8c027deed 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/stories/date-filters-panel.stories.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/stories/date-filters-panel.stories.tsx @@ -370,7 +370,7 @@ const LADDER_WIDTHS = [ 960, 782, 600, 360, 280 ]; /** * One locale's bar at each reference width, so where it stops fitting is * visible rather than asserted. Annotated against the four preset pills - * alone — a floor, since the trigger/comparison/interval/navigation share the line. + * alone — a floor, since the trigger/comparison/interval share the line. */ function WidthLadder( { fixture }: { fixture: LocaleFixture } ) { return ( diff --git a/projects/plugins/jetpack/changelog/remove-period-step-arrows b/projects/plugins/jetpack/changelog/remove-period-step-arrows index 70e923b0300c..be526ff1933c 100644 --- a/projects/plugins/jetpack/changelog/remove-period-step-arrows +++ b/projects/plugins/jetpack/changelog/remove-period-step-arrows @@ -1,4 +1,4 @@ Significance: minor Type: enhancement -Premium Analytics: remove the arrows that stepped the date range back and forward a period. +Premium Analytics: Remove the arrows that stepped the date range back and forward a period.