Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
cb79c17
add translated date bar stories
retrofox Jul 29, 2026
cef1721
add abbreviated labels for the surface presets
retrofox Jul 29, 2026
9f0d087
derive the preset label mode from a measured probe
retrofox Jul 29, 2026
c101e9a
truncate the custom trigger label instead of wrapping it
retrofox Jul 29, 2026
08a63e0
add a resizable rung to the translated stories
retrofox Jul 29, 2026
833e1a1
let the date panel own its responsive measurement
retrofox Jul 29, 2026
9f39951
cover the label mode decision with tests
retrofox Jul 29, 2026
b8f79cb
merge trunk
retrofox Jul 29, 2026
3b755b4
match the spec's Last 24H abbreviation
retrofox Jul 29, 2026
71a691d
Merge remote-tracking branch 'origin/trunk' into update/date-controls…
retrofox Jul 29, 2026
1e0f9b6
merge trunk
retrofox Jul 31, 2026
7b6fb37
rename the compact story to what it now shows
retrofox Jul 31, 2026
dcb769d
cut the panel's work on every resize frame
retrofox Jul 31, 2026
5bc9054
let the section header size the date controls
retrofox Jul 31, 2026
0675e77
label the custom trigger "Custom" while idle
retrofox Aug 3, 2026
e2eb661
restore the remembered range on the custom trigger
retrofox Aug 3, 2026
52337a9
own the remembered custom range in the panel
retrofox Aug 3, 2026
0cffd7b
Merge remote-tracking branch 'origin/trunk' into update/date-controls…
retrofox Aug 3, 2026
749cc20
update locations report filters instance
retrofox Aug 3, 2026
ae55622
Merge remote-tracking branch 'origin/trunk' into update/date-controls…
retrofox Aug 3, 2026
f9babe1
merge trunk
retrofox Aug 3, 2026
2c618e8
key the trigger font size to the collapsed row
retrofox Aug 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: changed

Date range presets now shorten their labels before collapsing into a select, and the switch is measured from the active language's own labels instead of a fixed width. The panel also measures itself, so callers no longer pass a container element.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: added

Add translated DateFiltersPanel stories (Russian, Dutch, German) that render the date bar at several container widths, so the layout is reviewed against real language packs rather than English only.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Internal dependencies
*/
import { getDefaultDateRangePresets, getQuickSurfacePresets } from '../presets';
import { QUICK_SURFACE_PRESETS } from '../presets/types';

const TIME_ZONE = 'America/New_York';

describe( 'surface preset short labels', () => {
it( 'gives every quick surface preset a short label', () => {
const presets = getQuickSurfacePresets( TIME_ZONE );

expect( presets ).toHaveLength( QUICK_SURFACE_PRESETS.length );

for ( const preset of presets ) {
expect( preset.shortLabel ).toBeTruthy();
}
} );

it( 'keeps the short label shorter than the full one', () => {
for ( const { label, shortLabel } of getQuickSurfacePresets( TIME_ZONE ) ) {
expect( shortLabel!.length ).toBeLessThan( label.length );
}
} );

/*
* The abbreviations exist for the pill row, and only the quick surface
* presets render there. A short label on any other preset would never be
* shown, so it would be dead weight the translators still have to carry.
*/
it( 'leaves presets outside the surface without one', () => {
const surface = new Set< string >( QUICK_SURFACE_PRESETS );

const offSurface = getDefaultDateRangePresets( TIME_ZONE ).filter(
preset => ! surface.has( preset.id )
);

expect( offSurface.length ).toBeGreaterThan( 0 );

for ( const preset of offSurface ) {
expect( preset.shortLabel ).toBeUndefined();
}
} );
} );
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { __, _x } from '@wordpress/i18n';
import {
startOfDay,
endOfDay,
Expand Down Expand Up @@ -67,6 +67,12 @@ export const DEFAULT_YEAR_SURFACE_COUNT = 6;
type PresetDefinition = {
id: SelectablePresetId;
getLabel: () => string;
/**
* Short label for the surface pills. Only the quick surface presets carry
* one: they are the only presets rendered in a fixed-width row. Translated
* separately rather than truncated, since the English forms are initials.
*/
getShortLabel?: () => string;
getRange: ( ctx: DateContext ) => Required< DateRange >;
};

Expand Down Expand Up @@ -94,6 +100,9 @@ export const PRESET_DEFINITIONS: ReadonlyArray< PresetDefinition > = [
{
id: PRESET_LAST_24_HOURS,
getLabel: () => __( 'Last 24 hours', 'jetpack-premium-analytics-pkg' ),
getShortLabel: () =>
/* translators: abbreviation for "Last 24 hours". Shown in a segmented control too narrow for the full label, so keep it as short as the language allows. */
_x( 'Last 24H', 'short date range preset', 'jetpack-premium-analytics-pkg' ),
getRange: ( { now } ) => ( {
from: subHours( now, 24 ),
to: now,
Expand All @@ -102,6 +111,9 @@ export const PRESET_DEFINITIONS: ReadonlyArray< PresetDefinition > = [
{
id: PRESET_LAST_7_DAYS,
getLabel: () => __( 'Last 7 days', 'jetpack-premium-analytics-pkg' ),
getShortLabel: () =>
/* translators: abbreviation for "Last 7 days". Shown in a segmented control too narrow for the full label, so keep it as short as the language allows. */
_x( '7D', 'short date range preset', 'jetpack-premium-analytics-pkg' ),
getRange: ( { initOfToday, endOfYesterday } ) => ( {
from: subDays( initOfToday, 7 ),
to: endOfYesterday,
Expand All @@ -110,6 +122,9 @@ export const PRESET_DEFINITIONS: ReadonlyArray< PresetDefinition > = [
{
id: PRESET_LAST_30_DAYS,
getLabel: () => __( 'Last 30 days', 'jetpack-premium-analytics-pkg' ),
getShortLabel: () =>
/* translators: abbreviation for "Last 30 days". Shown in a segmented control too narrow for the full label, so keep it as short as the language allows. */
_x( '30D', 'short date range preset', 'jetpack-premium-analytics-pkg' ),
getRange: ( { initOfToday, endOfYesterday } ) => ( {
from: subDays( initOfToday, 30 ),
to: endOfYesterday,
Expand Down Expand Up @@ -142,6 +157,9 @@ export const PRESET_DEFINITIONS: ReadonlyArray< PresetDefinition > = [
{
id: PRESET_LAST_12_MONTHS,
getLabel: () => __( 'Last 12 months', 'jetpack-premium-analytics-pkg' ),
getShortLabel: () =>
/* translators: abbreviation for "Last 12 months". Shown in a segmented control too narrow for the full label, so keep it as short as the language allows. */
_x( '12M', 'short date range preset', 'jetpack-premium-analytics-pkg' ),
getRange: ( { initOfToday, endOfYesterday } ) => ( {
from: subMonths( initOfToday, 12 ),
to: endOfYesterday,
Expand Down Expand Up @@ -221,6 +239,11 @@ function buildDateContext( timeZone: string ): DateContext {
export type DateRangePreset< TId extends ComputablePresetId = SelectablePresetId > = {
id: TId;
label: string;
/**
* Abbreviated label, present only on presets that render as surface pills.
* Consumers that never run out of room can ignore it.
*/
shortLabel?: string;
range: Required< DateRange >;
};

Expand Down Expand Up @@ -282,9 +305,10 @@ function computeAllTimeRange( startYear: number, ctx: DateContext ): Required< D
export function getDefaultDateRangePresets( timeZone: string ): DateRangePreset[] {
const ctx = buildDateContext( timeZone );

return PRESET_DEFINITIONS.map( ( { id, getLabel, getRange } ) => ( {
return PRESET_DEFINITIONS.map( ( { id, getLabel, getShortLabel, getRange } ) => ( {
id,
label: getLabel(),
shortLabel: getShortLabel?.(),
range: getRange( ctx ),
} ) );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { deriveComparisonRange, encodeDateToSearchParam } from '@jetpack-premium
import { DateFiltersPanel } from '@jetpack-premium-analytics/ui';
import { Stack } from '@wordpress/ui';
import { endOfDay } from 'date-fns';
import { useCallback, useMemo, useState, useEffect } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { getStoreInfo } from '../helpers/store-info';
import type { DataFormControlProps } from '@wordpress/dataviews';

Expand Down Expand Up @@ -127,18 +127,6 @@ export function ReportParamsField( {
setStagedReportParams( attributes?.reportParams );
}, [ setStagedReportParams, attributes ] );

/*
* Get the dashboard layout surface for responsive calculations.
* This is a temporary workaround until @automattic/dashboard exposes
* a Context provider. See WOOA7S-1008 for the upstream solution.
*/
const [ containerElement, setContainerElement ] = useState< HTMLElement | null >( null );

useEffect( () => {
const node = document.querySelector< HTMLElement >( '.next-admin-layout__surface' );
setContainerElement( node );
}, [] );

return (
<Stack direction="column" gap="sm">
<DateFiltersPanel
Expand All @@ -151,7 +139,6 @@ export function ReportParamsField( {
canApply={ isDateRangeDirty }
onCancel={ clear }
timeZone={ getSiteTimezone() }
containerElement={ containerElement }
/>
</Stack>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ import type {
} from '@jetpack-premium-analytics/datetime';

/**
* The values and callbacks that drive `DateFiltersPanel`, minus the
* `containerElement` ref which the consuming page owns.
* The values and callbacks that drive `DateFiltersPanel`.
*/
type PickerRange = { from: Date | undefined; to: Date | undefined };

Expand Down Expand Up @@ -70,8 +69,7 @@ function toPickerRange( from?: string, to?: string ) {
*
* Edits are staged locally and committed atomically on Apply (or immediately
* for comparison changes), so widgets re-fetch only on commit. The hook returns
* everything `DateFiltersPanel` needs except the responsive-measurement
* `containerElement`, which the page owns. Shared by every analytics page that
* everything `DateFiltersPanel` needs. Shared by every analytics page that
* mounts the panel so the staged-search behavior stays identical across them.
*
* @param from - The route path the search params are bound to (e.g. `/`).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Internal dependencies
*/
import { resolvePresetLabelMode } from '../date-range-layout';

// Roughly the Russian measurements from WOOA7S-1817: the locale with the widest
// gap between the two forms, so the middle tier is a real range and not a sliver.
const WIDTHS = { full: 694, abbreviated: 316 };

describe( 'resolvePresetLabelMode', () => {
it( 'holds full labels until both measurements arrive', () => {
expect( resolvePresetLabelMode( null, WIDTHS ) ).toBe( 'full' );
expect( resolvePresetLabelMode( 200, null ) ).toBe( 'full' );
expect( resolvePresetLabelMode( null, null ) ).toBe( 'full' );
} );

it( 'keeps full labels while they fit', () => {
expect( resolvePresetLabelMode( 1200, WIDTHS ) ).toBe( 'full' );
expect( resolvePresetLabelMode( WIDTHS.full, WIDTHS ) ).toBe( 'full' );
} );

it( 'abbreviates as soon as the full row no longer fits', () => {
expect( resolvePresetLabelMode( WIDTHS.full - 1, WIDTHS ) ).toBe( 'abbreviated' );
expect( resolvePresetLabelMode( 500, WIDTHS ) ).toBe( 'abbreviated' );
expect( resolvePresetLabelMode( WIDTHS.abbreviated, WIDTHS ) ).toBe( 'abbreviated' );
} );

it( 'collapses to the select only once the abbreviated row overflows too', () => {
expect( resolvePresetLabelMode( WIDTHS.abbreviated - 1, WIDTHS ) ).toBe( 'select' );
expect( resolvePresetLabelMode( 0, WIDTHS ) ).toBe( 'select' );
} );

/*
* Each tier has to be reachable at some width. A rule that skipped the middle
* one would still satisfy the boundary assertions above while dropping
* straight from full labels to a select, which is the behavior this replaced.
*/
it( 'reaches every mode across the width range', () => {
const seen = new Set(
Array.from( { length: 1200 }, ( _, width ) => resolvePresetLabelMode( width, WIDTHS ) )
);

expect( seen ).toEqual( new Set( [ 'full', 'abbreviated', 'select' ] ) );
} );

/*
* Widths come from measuring live DOM, so they are not guaranteed ordered.
* The rule must stay monotonic regardless: never widen the labels as the
* container narrows.
*/
it( 'never upgrades the labels as the container shrinks', () => {
const rank = { select: 0, abbreviated: 1, full: 2 } as const;
const degenerate = { full: 300, abbreviated: 500 };

for ( const widths of [ WIDTHS, degenerate ] ) {
let previous = rank[ resolvePresetLabelMode( 1200, widths ) ];

for ( let width = 1200; width >= 0; width -= 1 ) {
const current = rank[ resolvePresetLabelMode( width, widths ) ];
expect( current ).toBeLessThanOrEqual( previous );
previous = current;
}
}
} );
} );
Loading
Loading