diff --git a/projects/packages/premium-analytics/changelog/update-remove-analytics-page-descriptions b/projects/packages/premium-analytics/changelog/update-remove-analytics-page-descriptions new file mode 100644 index 000000000000..11387ada5015 --- /dev/null +++ b/projects/packages/premium-analytics/changelog/update-remove-analytics-page-descriptions @@ -0,0 +1,4 @@ +Significance: patch +Type: removed + +Remove the page descriptions from the dashboard and report pages. diff --git a/projects/packages/premium-analytics/changelog/update-remove-section-header-date-subtitle b/projects/packages/premium-analytics/changelog/update-remove-section-header-date-subtitle new file mode 100644 index 000000000000..36d47360843f --- /dev/null +++ b/projects/packages/premium-analytics/changelog/update-remove-section-header-date-subtitle @@ -0,0 +1,4 @@ +Significance: patch +Type: removed + +Remove the date configuration subtitle from the section header. diff --git a/projects/packages/premium-analytics/packages/formatters/README.md b/projects/packages/premium-analytics/packages/formatters/README.md index 7dd18db6a828..a48022d9a891 100644 --- a/projects/packages/premium-analytics/packages/formatters/README.md +++ b/projects/packages/premium-analytics/packages/formatters/README.md @@ -193,8 +193,8 @@ one width and not another. Two things to know if you add a form: ## `formatDateRangeLong( range?, options? )` -Format a date range in explicit, readable form, for prominent surfaces such as -the section header subtitle. Returns `''` when range or dates are missing. +Format a date range in explicit, readable form, for prominent surfaces. +Returns `''` when range or dates are missing. The shape follows the range's own length, along two independent axes. A window of a week or less leads each end with its weekday, which is what makes a diff --git a/projects/packages/premium-analytics/packages/formatters/src/date/format-date-range-long.ts b/projects/packages/premium-analytics/packages/formatters/src/date/format-date-range-long.ts index 6666bd4da842..fbcf11e8ad8d 100644 --- a/projects/packages/premium-analytics/packages/formatters/src/date/format-date-range-long.ts +++ b/projects/packages/premium-analytics/packages/formatters/src/date/format-date-range-long.ts @@ -99,7 +99,7 @@ function getSiteYear( date: Date | number ): number { } /** - * Format a date range in the explicit form the section header subtitle uses. + * Format a date range in explicit, readable form, for prominent surfaces. * * The shape follows the range's own length: a week or less leads each end with * its weekday, the year is carried only outside the reference year, and a day diff --git a/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/date-comparison-dropdown.tsx b/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/date-comparison-dropdown.tsx index 1325c7c32561..0d61de7a26d8 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/date-comparison-dropdown.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/date-comparison-dropdown.tsx @@ -86,7 +86,7 @@ export function DateComparisonDropdown( { /* * Additive: `Compare +` until a preset is picked, then a trigger naming it — * spelled out rather than a bare `+`, since a glyph alone read as decoration. - * Names the preset, not the period: the section header's subtitle covers that. + * Names the preset, not the period. */ return ( { - beforeEach( () => { - setSettings( { - ...DEFAULT_SETTINGS, - formats: { ...DEFAULT_SETTINGS.formats, date: 'F j, Y' }, - timezone: { offset: 0, offsetFormatted: '0', string: TEST_TIMEZONE, abbr: TEST_TIMEZONE }, - } ); - } ); - - it( 'returns undefined when the range is incomplete', () => { - expect( getSectionSubtitle( {} ) ).toBeUndefined(); - expect( getSectionSubtitle( { range: {} } ) ).toBeUndefined(); - expect( getSectionSubtitle( { range: { from: at( 2026, 7, 21 ) } } ) ).toBeUndefined(); - } ); - - it( 'appends how long the range is', () => { - expect( getSectionSubtitle( { range: currentYearRange( 7 ) } ) ).toContain( '(7 days)' ); - } ); - - it( 'pluralizes a single-day range', () => { - expect( getSectionSubtitle( { range: currentYearRange( 1 ) } ) ).toContain( '(1 day)' ); - } ); - - it( 'describes a month-scale range in months', () => { - expect( - getSectionSubtitle( { - range: { from: at( 2025, 7, 1 ), to: endOf( 2026, 6, 30 ) }, - } ) - ).toBe( 'July 1, 2025 – June 30, 2026 (12 months)' ); - } ); - - it( 'describes a rolling sub-day window in hours', () => { - expect( - getSectionSubtitle( { - range: { - from: new TZDate( 2026, 6, 28, 14, 30, 0, 0, TEST_TIMEZONE ), - to: new TZDate( 2026, 6, 29, 14, 30, 0, 0, TEST_TIMEZONE ), - }, - } ) - ).toContain( '(24 hours)' ); - } ); - - it( 'names the days the compared period covers, in place of the preset', () => { - expect( - getSectionSubtitle( { - range: { from: at( 2026, 6, 1 ), to: endOf( 2026, 6, 7 ) }, - comparisonPresetId: 'previous-period', - comparisonRange: { from: at( 2026, 5, 25 ), to: endOf( 2026, 5, 31 ) }, - } ) - ).toMatch( /\(7 days\) vs\. May 25\u2009–\u200931, 2026$/ ); - } ); - - it( 'names the days for a calendar-shifted preset too', () => { - expect( - getSectionSubtitle( { - range: { from: at( 2026, 6, 1 ), to: endOf( 2026, 6, 7 ) }, - comparisonPresetId: 'previous-year', - comparisonRange: { from: at( 2025, 6, 1 ), to: endOf( 2025, 6, 7 ) }, - } ) - ).toMatch( /\(7 days\) vs\. June 1\u2009–\u20097, 2025$/ ); - } ); - - it( 'names both sides of a rolling 24-hour comparison by the day they end on', () => { - expect( - getSectionSubtitle( { - range: { - from: new TZDate( 2026, 7, 19, 15, 0, 0, 0, TEST_TIMEZONE ), - to: new TZDate( 2026, 7, 20, 15, 0, 0, 0, TEST_TIMEZONE ), - }, - comparisonPresetId: 'previous-period', - comparisonRange: { - from: new TZDate( 2026, 7, 18, 15, 0, 0, 0, TEST_TIMEZONE ), - to: new TZDate( 2026, 7, 19, 15, 0, 0, 0, TEST_TIMEZONE ), - }, - interval: 'hour', - } ) - ).toBe( 'Thursday, August 20 (24 hours, hourly) vs. August 19, 2026' ); - } ); - - it( 'falls back to naming the preset when the comparison window is missing', () => { - expect( - getSectionSubtitle( { - range: currentYearRange( 7 ), - comparisonPresetId: 'previous-period', - } ) - ).toMatch( /\(7 days\) vs\. Previous period$/ ); - - expect( - getSectionSubtitle( { - range: currentYearRange( 7 ), - comparisonPresetId: 'previous-period', - comparisonRange: { from: at( 2026, 5, 25 ) }, - } ) - ).toMatch( /\(7 days\) vs\. Previous period$/ ); - } ); - - it( 'ignores a comparison window carried without a preset', () => { - expect( - getSectionSubtitle( { - range: currentYearRange( 7 ), - comparisonRange: { from: at( 2026, 5, 25 ), to: endOf( 2026, 5, 31 ) }, - } ) - ).not.toContain( 'May' ); - } ); - - it( 'omits the comparison when none is applied', () => { - expect( getSectionSubtitle( { range: currentYearRange( 7 ) } ) ).not.toContain( 'vs.' ); - } ); - - describe( 'the chart interval', () => { - it( 'joins the length inside the parenthetical', () => { - expect( getSectionSubtitle( { range: currentYearRange( 7 ), interval: 'day' } ) ).toContain( - '(7 days, daily)' - ); - } ); - - it( 'stands alone where the range carries no length', () => { - expect( - getSectionSubtitle( { - range: { from: at( 2021, 1, 1 ), to: endOf( 2026, 7, 30 ) }, - presetId: 'all-time', - interval: 'year', - } ) - ).toBe( 'January 1, 2021 – July 30, 2026 (yearly)' ); - } ); - - it( 'stays out of the copy on a surface carrying no interval control', () => { - const subtitle = getSectionSubtitle( { range: currentYearRange( 7 ) } ); - - expect( subtitle ).toContain( '(7 days)' ); - expect( subtitle ).not.toMatch( /hourly|daily|weekly|monthly|yearly/ ); - } ); - - it( 'stays inside the parenthetical, ahead of the comparison', () => { - expect( - getSectionSubtitle( { - range: { from: at( 2026, 6, 1 ), to: endOf( 2026, 6, 7 ) }, - interval: 'day', - comparisonPresetId: 'previous-period', - comparisonRange: { from: at( 2026, 5, 25 ), to: endOf( 2026, 5, 31 ) }, - } ) - ).toMatch( /\(7 days, daily\) vs\. May 25\u2009–\u200931, 2026$/ ); - } ); - } ); - - describe( 'the year surface', () => { - /* - * `all-time` and the running year start on a calendar boundary and end at - * the end of today, so the day they're read on decides the unit — each is - * one selection, read on three days: mid-month, month-end, year-end. - */ - const READ_ON = [ - [ 'mid-month', endOf( 2026, 7, 30 ) ], - [ 'on the last day of a month', endOf( 2026, 7, 31 ) ], - [ 'on the last day of a year', endOf( 2026, 12, 31 ) ], - ] as const; - - const ALL_TIME_FROM = at( 2021, 1, 1 ); - const RUNNING_YEAR_FROM = at( 2026, 1, 1 ); - - it.each( READ_ON )( 'all-time carries no length when read %s', ( _label, to ) => { - const subtitle = getSectionSubtitle( { - range: { from: ALL_TIME_FROM, to }, - presetId: 'all-time', - } ); - - expect( subtitle ).toContain( 'January 1, 2021' ); - expect( subtitle ).not.toMatch( /\(\d/ ); - } ); - - it.each( READ_ON )( 'the running year carries no length when read %s', ( _label, to ) => { - const subtitle = getSectionSubtitle( { - range: { from: RUNNING_YEAR_FROM, to }, - presetId: 'year-2026', - } ); - - // Both halves hold still: no length, and the calendar shape rather - // than the weekday-led one a day-scale measurement would pick. - expect( subtitle ).toContain( 'January 1, 2026' ); - expect( subtitle ).not.toMatch( /\(\d/ ); - expect( subtitle ).not.toMatch( /day/i ); - } ); - - it( 'a past year carries no length either, since its label names it', () => { - expect( - getSectionSubtitle( { - range: { from: at( 2025, 1, 1 ), to: endOf( 2025, 12, 31 ) }, - presetId: 'year-2025', - } ) - ).not.toMatch( /\(\d/ ); - } ); - - it( 'still measures the same range under any other preset', () => { - expect( - getSectionSubtitle( { - range: { from: ALL_TIME_FROM, to: endOf( 2026, 7, 30 ) }, - presetId: 'custom', - } ) - ).toContain( '(2037 days)' ); - } ); - - it( 'measures the running year under a non-year preset, unit and all', () => { - // The regression this guards: read mid-month the same dates measure in - // days, and on a month boundary in months, which is the length the - // year surface refuses to state. - expect( - getSectionSubtitle( { - range: { from: RUNNING_YEAR_FROM, to: endOf( 2026, 7, 30 ) }, - presetId: 'custom', - } ) - ).toBe( 'January 1 – July 30 (211 days)' ); - - expect( - getSectionSubtitle( { - range: { from: RUNNING_YEAR_FROM, to: endOf( 2026, 7, 31 ) }, - presetId: 'custom', - } ) - ).toBe( 'January 1 – July 31 (7 months)' ); - } ); - } ); -} ); diff --git a/projects/packages/premium-analytics/packages/ui/src/section-header/__tests__/section-header.test.tsx b/projects/packages/premium-analytics/packages/ui/src/section-header/__tests__/section-header.test.tsx index 3c0c63e7005f..a28b55f25073 100644 --- a/projects/packages/premium-analytics/packages/ui/src/section-header/__tests__/section-header.test.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/section-header/__tests__/section-header.test.tsx @@ -1,8 +1,6 @@ import { render, screen } from '@testing-library/react'; import { SectionHeader } from '../section-header'; -const SUBTITLE = 'Jul 21 – Jul 27 (7 days)'; - describe( 'SectionHeader', () => { it( 'renders the title as a level-2 heading', () => { render( ); @@ -19,20 +17,6 @@ describe( 'SectionHeader', () => { ); } ); - it( 'renders the subtitle when one is given', () => { - render( ); - - expect( screen.getByText( SUBTITLE ) ).toBeInTheDocument(); - } ); - - it( 'drops the subtitle once there is nothing to describe', () => { - const { rerender } = render( ); - - rerender( ); - - expect( screen.queryByText( SUBTITLE ) ).not.toBeInTheDocument(); - } ); - it( 'renders the date controls passed to the slot', () => { render( diff --git a/projects/packages/premium-analytics/packages/ui/src/section-header/get-section-subtitle.ts b/projects/packages/premium-analytics/packages/ui/src/section-header/get-section-subtitle.ts deleted file mode 100644 index 2eb33d025d74..000000000000 --- a/projects/packages/premium-analytics/packages/ui/src/section-header/get-section-subtitle.ts +++ /dev/null @@ -1,182 +0,0 @@ -/** - * External dependencies - */ -import { - getComparisonPresetLabel, - getDateRangeSpan, - isYearSurfacePresetId, - type ComparisonPresetId, - type DateRange, - type DateRangeSpan, - type IntervalType, - type PrimaryPresetId, -} from '@jetpack-premium-analytics/datetime'; -import { formatDateRange, formatDateRangeLong } from '@jetpack-premium-analytics/formatters'; -import { __, _n, sprintf } from '@wordpress/i18n'; - -type SectionSubtitleArgs = { - /** - * The applied date range, not the picker's staged draft. - */ - range?: DateRange; - - /** - * The applied comparison preset, when a comparison is active. - */ - comparisonPresetId?: ComparisonPresetId; - - /** - * The window that preset resolved to. Named in the preset's place: the - * preset stays readable on the comparison control beside the subtitle, - * while only the dates say which days the deltas are measured against. - */ - comparisonRange?: DateRange; - - /** - * The applied primary preset. Only read to recognise the year surface, - * whose length is not a property of the selection. - */ - presetId?: PrimaryPresetId; - - /** - * The applied chart interval. Omit it on a surface carrying no interval - * control, so the subtitle never states a bucket the reader cannot change. - */ - interval?: IntervalType; -}; - -/** - * Spell out a range's length, e.g. "7 days". - */ -function getSpanLabel( span: DateRangeSpan ): string { - switch ( span.unit ) { - case 'hour': - return sprintf( - // translators: %d is a number of hours. - _n( '%d hour', '%d hours', span.value, 'jetpack-premium-analytics-pkg' ), - span.value - ); - case 'month': - return sprintf( - // translators: %d is a number of months. - _n( '%d month', '%d months', span.value, 'jetpack-premium-analytics-pkg' ), - span.value - ); - case 'year': - return sprintf( - // translators: %d is a number of years. - _n( '%d year', '%d years', span.value, 'jetpack-premium-analytics-pkg' ), - span.value - ); - default: - return sprintf( - // translators: %d is a number of days. - _n( '%d day', '%d days', span.value, 'jetpack-premium-analytics-pkg' ), - span.value - ); - } -} - -/** - * Name the interval as a cadence, e.g. "daily". - * - * A cadence rather than the menu's "By days": the window length sits beside it - * in the same units, where "(24 hours, by hours)" reads as a stutter. - * - * @param interval - The applied bucket. - * @return The localized cadence. - */ -function getIntervalCadenceLabel( interval: IntervalType ): string { - switch ( interval ) { - case 'hour': - return __( 'hourly', 'jetpack-premium-analytics-pkg' ); - case 'day': - return __( 'daily', 'jetpack-premium-analytics-pkg' ); - case 'week': - return __( 'weekly', 'jetpack-premium-analytics-pkg' ); - case 'month': - return __( 'monthly', 'jetpack-premium-analytics-pkg' ); - case 'year': - return __( 'yearly', 'jetpack-premium-analytics-pkg' ); - } -} - -/** - * Describe the applied date configuration for a section header subtitle. - * Reads the applied range, not the preset — except the year surface, whose - * length reflects today's date, not the selection, so it states none. - * - * @return The subtitle, or undefined when the range is incomplete. - */ -export function getSectionSubtitle( { - range, - comparisonPresetId, - comparisonRange, - presetId, - interval, -}: SectionSubtitleArgs ): string | undefined { - /* - * The year surface is described by its selection, not by measuring it: both - * the length and the range's own shape follow the measured unit, and for a - * still-running year that unit changes by the day. - */ - const isYearSurface = isYearSurfacePresetId( presetId ); - const rangeLabel = formatDateRangeLong( range, { calendarScale: isYearSurface } ); - - if ( ! rangeLabel ) { - return undefined; - } - - const span = isYearSurface ? null : getDateRangeSpan( range ); - - /* - * The parenthetical holds how long the window is and how the charts bucket - * it. Either can be absent, so the shapes are spelled out as whole format - * strings rather than joined, leaving the separator to translators. - */ - const details = [ - span ? getSpanLabel( span ) : null, - interval ? getIntervalCadenceLabel( interval ) : null, - ].filter( ( detail ): detail is string => detail !== null ); - - let dateConfiguration = rangeLabel; - - if ( details.length === 2 ) { - dateConfiguration = sprintf( - // translators: %1$s is a date range, %2$s is how long it is, e.g. "7 days", %3$s is the chart interval, e.g. "daily". - __( '%1$s (%2$s, %3$s)', 'jetpack-premium-analytics-pkg' ), - rangeLabel, - details[ 0 ], - details[ 1 ] - ); - } else if ( details.length === 1 ) { - dateConfiguration = sprintf( - // translators: %1$s is a date range, %2$s is either how long it is, e.g. "7 days", or the chart interval, e.g. "daily". - __( '%1$s (%2$s)', 'jetpack-premium-analytics-pkg' ), - rangeLabel, - details[ 0 ] - ); - } - - const comparisonLabel = comparisonPresetId - ? getComparisonPresetLabel( comparisonPresetId ) - : null; - - if ( ! comparisonLabel ) { - return dateConfiguration; - } - - /* - * The medium form: the primary already leads with weekdays, and a second - * weekday-led range after "vs." reads as a second selection rather than as - * the baseline for the first. - */ - const comparisonRangeLabel = formatDateRange( comparisonRange, { collapseSingleDay: true } ); - - return sprintf( - // translators: %1$s is a date range with its length, %2$s is the compared period — either its dates, e.g. "July 14 – 20, 2026", or a preset name, e.g. "Previous period". - __( '%1$s vs. %2$s', 'jetpack-premium-analytics-pkg' ), - dateConfiguration, - comparisonRangeLabel || comparisonLabel - ); -} diff --git a/projects/packages/premium-analytics/packages/ui/src/section-header/section-header.module.scss b/projects/packages/premium-analytics/packages/ui/src/section-header/section-header.module.scss index f929787f73ca..cf234063a545 100644 --- a/projects/packages/premium-analytics/packages/ui/src/section-header/section-header.module.scss +++ b/projects/packages/premium-analytics/packages/ui/src/section-header/section-header.module.scss @@ -20,21 +20,18 @@ $section-header-title-min-inline-size: 320px; display: grid; /* - * Row one is the title and the controls; the subtitle spans a row of its own - * below. The controls track is capped at its max-content, so the controls - * render at their natural width and a long title truncates instead of - * costing them labels; the title track takes what is left, floored so the - * controls cannot squeeze it into nothing. Only once the title sits at that - * floor does the controls track compress, and the panel adapts internally. - * The subtitle crosses the flexible track, so its length cannot inflate the - * controls track either. + * The title and the controls share one row. The controls track is capped at + * its max-content, so the controls render at their natural width and a long + * title truncates instead of costing them labels; the title track takes what + * is left, floored so the controls cannot squeeze it into nothing. Only once + * the title sits at that floor does the controls track compress, and the + * panel adapts internally. */ grid-template-columns: minmax($section-header-title-min-inline-size, 1fr) minmax(0, max-content); align-items: center; - // Column gap only: the rows carry their spacing as their own padding, so - // the condensing subtitle can take it along. A row gap belongs to the - // track and would stay behind as a band under the title. + // Column gap only: in the stacked layout the controls row carries its + // spacing as its own padding. column-gap: var(--wpds-dimension-gap-sm); row-gap: 0; @@ -43,26 +40,17 @@ $section-header-title-min-inline-size: 320px; text-overflow: ellipsis; white-space: nowrap; } - - // A row wrapper rather than the text itself: condensing animates the grid - // row, and the padding is the row spacing it gives back on the way out. - .subtitleRow { - grid-column: 1 / -1; - padding-block-start: var(--wpds-dimension-gap-sm); - } } /* - * Condenses the header into a compact bar as it pins: the subtitle fades and - * gives its row back, and the title drops a step on the type scale. + * Condenses the header into a compact bar as it pins: the title drops a step + * on the type scale. * * The surface, not this component, knows where the header pins, so it opts in * by publishing a `--section-header-pin` view timeline whose `exit` phase * opens at the pin point (see the dashboard's pin marker in * `routes/dashboard/stage.module.scss`). Progress-driven, so it follows the - * scroll in both directions; the short range bounds the per-frame layout cost - * of animating the grid row, and `fr` interpolation collapses the row without - * measuring the subtitle text. + * scroll in both directions. * * The @supports gate is load-bearing: without scroll-driven animation * support, the `animation` shorthand would run as a 0s time animation and its @@ -74,19 +62,6 @@ $section-header-title-min-inline-size: 320px; .condensing { - .subtitleRow { - display: grid; - grid-template-rows: 1fr; - animation: condense-subtitle linear both; - animation-timeline: --section-header-pin; - animation-range: exit; - - > .subtitle { - min-block-size: 0; - overflow: hidden; - } - } - // Buys no height: the row is floored by the date controls beside it. // What changes is what the pinned band reads as. .title { @@ -99,22 +74,12 @@ $section-header-title-min-inline-size: 320px; // Scroll-driven, but still chrome moving under the reader. @media (prefers-reduced-motion: reduce) { - .condensing .subtitleRow, .condensing .title { animation: none; } } } -@keyframes condense-subtitle { - - to { - grid-template-rows: 0fr; - opacity: 0; - padding-block-start: 0; - } -} - @keyframes condense-title { to { @@ -162,16 +127,9 @@ $section-header-title-min-inline-size: 320px; } } - // One column, so source order would leave the controls between the title and - // the subtitle describing it. - .subtitleRow { - order: 1; - } - - // Last row, reading from the same edge as the title. A row of its own now, - // so it carries its row spacing as padding the way the subtitle does. + // A row of its own now, reading from the same edge as the title, so it + // carries its row spacing as padding. .controls { - order: 2; justify-content: flex-start; padding-block-start: var(--wpds-dimension-gap-sm); diff --git a/projects/packages/premium-analytics/packages/ui/src/section-header/section-header.tsx b/projects/packages/premium-analytics/packages/ui/src/section-header/section-header.tsx index d737ad997107..104a4f952dbe 100644 --- a/projects/packages/premium-analytics/packages/ui/src/section-header/section-header.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/section-header/section-header.tsx @@ -7,15 +7,9 @@ type SectionHeaderProps = { title: string; /** - * Subtitle describing the active date configuration. Omit it while the - * consumer has nothing to describe. - */ - subtitle?: string; - - /** - * Condenses into a compact bar once it pins: subtitle fades out, title - * drops a type-scale step. Requires the surface to publish a - * `--section-header-pin` view timeline; falls back to resting layout without it. + * Condenses into a compact bar once it pins: the title drops a type-scale + * step. Requires the surface to publish a `--section-header-pin` view + * timeline; falls back to resting layout without it. */ condenseOnScroll?: boolean; @@ -27,19 +21,14 @@ type SectionHeaderProps = { }; /** - * Section header for an analytics surface: title and date controls share the - * first row (title truncates, controls keep natural width); the subtitle sits - * below. Below a container-query width everything stacks and the title wraps. + * Section header for an analytics surface: title and date controls share one + * row (title truncates, controls keep natural width). Below a container-query + * width the two stack and the title wraps. * * @param {SectionHeaderProps} props - The props for the SectionHeader component. * @return The section header element. */ -export function SectionHeader( { - title, - subtitle, - condenseOnScroll = false, - children, -}: SectionHeaderProps ) { +export function SectionHeader( { title, condenseOnScroll = false, children }: SectionHeaderProps ) { return (
@@ -54,14 +43,6 @@ export function SectionHeader( { { children } ) : null } - - { subtitle ? ( -
- - { subtitle } - -
- ) : null }
); diff --git a/projects/packages/premium-analytics/packages/ui/src/section-header/stories/section-header.stories.tsx b/projects/packages/premium-analytics/packages/ui/src/section-header/stories/section-header.stories.tsx index fdb32ccf9f10..64df4bf5cd84 100644 --- a/projects/packages/premium-analytics/packages/ui/src/section-header/stories/section-header.stories.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/section-header/stories/section-header.stories.tsx @@ -1,9 +1,7 @@ import { computePrimaryRange, - getComparisonRangeFromPreset, PRESET_ALL_TIME, type ComparisonPresetId, - type DateRange, type IntervalType, type PrimaryPresetId, type YearSurfacePresetId, @@ -18,7 +16,6 @@ import { resolveStoryInterval, } from '../../date-interval-dropdown/stories/story-interval-options'; import { DateYearFilter } from '../../date-year-filter'; -import { getSectionSubtitle } from '../get-section-subtitle'; import { SectionHeader } from '../section-header'; import type { DateRange as PanelDateRange } from '../../date-filters-panel/date-filters-panel'; import type { Meta, StoryObj } from '@storybook/react'; @@ -33,12 +30,10 @@ const meta: Meta< typeof SectionHeader > = { docs: { description: { component: - 'Header for an analytics surface. A **title** names the section and a ' + - '**subtitle** describes the active date configuration, on a row of its ' + - 'own so its length never costs the controls width.\n\n' + - 'The controls are a slot: the consumer passes them as `children`, keeps ' + - 'the date state, and derives the subtitle from the *applied* range, so ' + - 'the header stays purely presentational.', + 'Header for an analytics surface: a **title** names the section, and the ' + + 'date controls sit beside it on the same row.\n\n' + + 'The controls are a slot: the consumer passes them as `children` and keeps ' + + 'the date state, so the header stays purely presentational.', }, }, }, @@ -64,17 +59,6 @@ type PrimaryFilterState = { presetId: PrimaryPresetId; }; -/** - * The applied date configuration the subtitle describes. - */ -type AppliedDateState = { - range: PanelDateRange; - comparisonPresetId?: ComparisonPresetId; - comparisonRange?: DateRange; - presetId?: PrimaryPresetId; - interval?: IntervalType; -}; - /** * Committed/staged seed mirroring the dashboard default: Last 30 days. */ @@ -88,10 +72,7 @@ function buildInitialPrimaryState(): PrimaryFilterState { } /** - * Whether the primary picker is holding an un-applied edit. - * - * One predicate for two rules, the way `useReportDateFilters` has it: it gates - * Apply, and it decides whether a comparison change commits on its own. + * Whether the primary picker is holding an un-applied edit, which gates Apply. */ function hasPrimaryDraft( staged: PrimaryFilterState, committed: PrimaryFilterState ): boolean { return ( @@ -101,24 +82,12 @@ function hasPrimaryDraft( staged: PrimaryFilterState, committed: PrimaryFilterSt ); } -/** - * Derived on every commit rather than stored, because `buildRangePatch` - * re-derives the comparison whenever the primary range moves. - */ -function comparisonRangeFor( range: PanelDateRange, presetId: ComparisonPresetId | undefined ) { - return presetId ? getComparisonRangeFromPreset( range, presetId ) : undefined; -} - /** * Rolling date controls for the slot, wired like the dashboard: staged primary - * edits committed on Apply. A trimmed copy of the DateFiltersPanel story - * harness. + * edits committed on Apply, while a comparison or interval pick commits on its + * own. A trimmed copy of the DateFiltersPanel story harness. */ -function RollingDateControls( { - onAppliedChange, -}: { - onAppliedChange: ( applied: AppliedDateState ) => void; -} ) { +function RollingDateControls() { const initial = buildInitialPrimaryState(); const [ committed, setCommitted ] = useState( initial ); @@ -132,16 +101,6 @@ function RollingDateControls( { const [ pickedInterval, setPickedInterval ] = useState< IntervalType | undefined >( undefined ); - /** - * What the interval resolves to under a given preset, so a pick the next - * range no longer allows coerces on Apply rather than being carried. - */ - const intervalFor = useCallback( - ( presetId: PrimaryPresetId ) => - resolveStoryInterval( pickedInterval, getStoryIntervalOptions( presetId ) ), - [ pickedInterval ] - ); - const handleChange = useCallback( ( nextRange?: PanelDateRange, nextPresetId?: PrimaryPresetId ) => { const next: PrimaryFilterState = { @@ -157,62 +116,22 @@ function RollingDateControls( { const handleApply = useCallback( () => { setCommitted( stagedRef.current ); - onAppliedChange( { - range: stagedRef.current.range, - presetId: stagedRef.current.presetId, - comparisonPresetId, - comparisonRange: comparisonRangeFor( stagedRef.current.range, comparisonPresetId ), - interval: intervalFor( stagedRef.current.presetId ), - } ); - }, [ onAppliedChange, comparisonPresetId, intervalFor ] ); + }, [] ); const handleCancel = useCallback( () => { stagedRef.current = committed; setStaged( committed ); }, [ committed ] ); - /* - * Mirrors `useReportDateFilters`: a comparison change commits on its own and - * moves the subtitle right away, unless a primary edit is staged — then it - * rides along and both land on Apply. - */ const handleComparisonChange = useCallback( ( _range: PanelDateRange | undefined, nextPresetId?: ComparisonPresetId ) => { setComparisonPresetId( nextPresetId ); - - if ( ! hasPrimaryDraft( stagedRef.current, committed ) ) { - onAppliedChange( { - range: committed.range, - presetId: committed.presetId, - comparisonPresetId: nextPresetId, - comparisonRange: comparisonRangeFor( committed.range, nextPresetId ), - interval: intervalFor( committed.presetId ), - } ); - } }, - [ onAppliedChange, committed, intervalFor ] - ); - - // Applies on click unless a primary edit is staged, in which case it lands - // with it — the same rule a comparison change follows. - const handleIntervalChange = useCallback( - ( nextInterval: IntervalType ) => { - setPickedInterval( nextInterval ); - - if ( ! hasPrimaryDraft( stagedRef.current, committed ) ) { - onAppliedChange( { - range: committed.range, - presetId: committed.presetId, - comparisonPresetId, - comparisonRange: comparisonRangeFor( committed.range, comparisonPresetId ), - interval: nextInterval, - } ); - } - }, - [ onAppliedChange, committed, comparisonPresetId ] + [] ); - const canApply = hasPrimaryDraft( staged, committed ); + // A pick the committed range no longer allows coerces to the finest bucket + // it does, the way the report params have it. const intervalOptions = getStoryIntervalOptions( staged.presetId ); return ( @@ -227,64 +146,32 @@ function RollingDateControls( { intervalOptions={ intervalOptions } onChange={ handleChange } onComparisonChange={ handleComparisonChange } - onIntervalChange={ handleIntervalChange } + onIntervalChange={ setPickedInterval } onApply={ handleApply } onCancel={ handleCancel } - canApply={ canApply } + canApply={ hasPrimaryDraft( staged, committed ) } timeZone={ STORYBOOK_TIMEZONE } /> ); } -/** - * The year surface's own seed: all time, bucketed by the coarsest-but-one - * interval its span allows. - */ -function buildInitialYearState(): AppliedDateState { - const range = computePrimaryRange( PRESET_ALL_TIME, STORYBOOK_TIMEZONE ); - - return { - range: { from: range?.from, to: range?.to }, - presetId: PRESET_ALL_TIME, - interval: getStoryIntervalOptions( PRESET_ALL_TIME )[ 0 ], - }; -} - /** * Year-surface controls for the slot: all time plus calendar years and the * interval control, but no comparison, as the Insights instance specifies. */ -function YearDateControls( { - containerElement, - onSelectionChange, -}: { - containerElement: HTMLElement | null; - onSelectionChange: ( selection: AppliedDateState ) => void; -} ) { - const [ selection, setSelection ] = useState< AppliedDateState >( buildInitialYearState ); +function YearDateControls( { containerElement }: { containerElement: HTMLElement | null } ) { + const [ presetId, setPresetId ] = useState< YearSurfacePresetId >( PRESET_ALL_TIME ); const [ pickedInterval, setPickedInterval ] = useState< IntervalType | undefined >( undefined ); - const intervalOptions = getStoryIntervalOptions( selection.presetId ); + // A calendar year allows finer buckets than all time, so the interval is + // re-resolved against the selection rather than carried. + const intervalOptions = getStoryIntervalOptions( presetId ); return ( { - // A calendar year allows finer buckets than all time, so the - // selection is re-resolved rather than carried. - const next: AppliedDateState = { - range, - presetId: nextPresetId, - interval: resolveStoryInterval( - pickedInterval, - getStoryIntervalOptions( nextPresetId ) - ), - }; - - setSelection( next ); - onSelectionChange( next ); - } } + value={ presetId } + onSelect={ ( _range, nextPresetId ) => setPresetId( nextPresetId ) } timeZone={ STORYBOOK_TIMEZONE } containerElement={ containerElement } /> @@ -292,10 +179,7 @@ function YearDateControls( { { - setPickedInterval( nextInterval ); - onSelectionChange( { ...selection, interval: nextInterval } ); - } } + onChange={ setPickedInterval } /> ); @@ -303,40 +187,24 @@ function YearDateControls( { type SectionHeaderStoryProps = { title: string; - subtitle?: string; condenseOnScroll?: boolean; }; function RollingSectionHeaderStory( { title, condenseOnScroll }: SectionHeaderStoryProps ) { - const [ applied, setApplied ] = useState< AppliedDateState >( () => { - const initial = buildInitialPrimaryState(); - - return { - range: initial.range, - presetId: initial.presetId, - interval: getStoryIntervalOptions( initial.presetId )[ 0 ], - }; - } ); - return ( - - + + ); } function YearSectionHeaderStory( { title }: SectionHeaderStoryProps ) { const [ container, setContainer ] = useState< HTMLDivElement | null >( null ); - const [ applied, setApplied ] = useState< AppliedDateState >( buildInitialYearState ); return (
- - + +
); @@ -346,13 +214,10 @@ function YearSectionHeaderStory( { title }: SectionHeaderStoryProps ) { * The **Traffic-like** instance: rolling presets, custom range, chart interval, * and comparison in the slot. * - * The subtitle derives from the *applied* configuration, so it holds still - * while a range edit is staged and only moves on Apply. Picking a comparison or - * an interval moves it right away, matching how those commit on their own. - * - * The interval control is a glyph, so the subtitle is the only place its choice - * is readable, and where the coercion shows: switching to a preset that - * disallows the active bucket falls back to the finest one it allows. + * Range edits are staged and land on Apply; a comparison or interval pick + * commits on its own, the way `useReportDateFilters` has it. Switching to a + * preset that disallows the active bucket falls back to the finest one it + * allows. */ export const Default: Story = { args: { @@ -378,9 +243,7 @@ export const LongTitle: Story = { * years) and the chart interval in the slot. * * Per the design's instances table, this surface carries *no comparison - * control* but does carry the interval one. Its ranges are the longest the - * dashboard offers, so its subtitle carries no length — the years are already - * in the range itself — and names only the bucket. + * control* but does carry the interval one. */ export const YearSurface: Story = { args: { @@ -426,11 +289,11 @@ const PIN_MARKER_STYLE = { /** * `condenseOnScroll` on a pinned header: scroll the box below, and once the - * header clears the strip above it and pins, the subtitle fades and gives its - * row back over the next 40px. Nothing condenses while the header is still on - * its way up, and the effect reverses as you scroll back up. Browsers without + * header clears the strip above it and pins, the title drops a type-scale step + * over the next 40px. Nothing condenses while the header is still on its way + * up, and the effect reverses as you scroll back up. Browsers without * scroll-driven animations, surfaces that publish no pin marker, and readers - * who asked for reduced motion all keep the subtitle in place. + * who asked for reduced motion all keep the resting title. */ export const CondensingOnScroll: Story = { args: { @@ -462,7 +325,6 @@ export const CondensingOnScroll: Story = { export const WithoutControls: Story = { args: { title: 'Site traffic', - subtitle: 'Last 30 days', }, - render: ( { title, subtitle } ) => , + render: ( { title } ) => , }; diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/README.md b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/README.md index d52a6b2fd901..b9366a189a67 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/README.md +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/README.md @@ -11,7 +11,6 @@ const label = __( 'All pages' ); } breadcrumbs={ } - subTitle={ __( 'All your posts and archive pages.' ) } actions={ downloadButton } > @@ -34,7 +33,7 @@ const label = __( 'All pages' ); ``` - **`ReportPageShell`** — the outer `Page` shell: the shared Jetpack visual, - Stats breadcrumbs, subtitle, and page-level actions. + Stats breadcrumbs and page-level actions. - **`ReportPageLayout`** — the report content scaffold: optional internal tabs, the section header, and stacked sections. `ReportPageSection` is the bordered card each section renders in. @@ -63,11 +62,11 @@ const label = __( 'All pages' ); `ReportPageLayout` renders `SectionHeader`, the component the dashboard's sections use. Pass `title` and the `useReportDateFilters` controller; the layout -composes `DateFiltersPanel` and derives the subtitle with `getSectionSubtitle`. +composes `DateFiltersPanel` in the header's controls slot. -The picker offers the range alone — no interval control, no comparison — and the -subtitle names neither. Both stay on the URL untouched, so the dashboard keeps -them. Declaring this per report is WOOA7S-1952. +The picker offers the range alone — no interval control, no comparison. Both +stay on the URL untouched, so the dashboard keeps them. Declaring this per +report is WOOA7S-1952. A report page carries three names: 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 b58fc95b38f2..bba0fab005eb 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 @@ -3,7 +3,6 @@ */ import { DateFiltersPanel } from '@jetpack-premium-analytics/ui'; import { render, screen } from '@testing-library/react'; -import { getSettings, setSettings } from '@wordpress/date'; /** * Internal dependencies */ @@ -18,16 +17,12 @@ jest.mock( '@jetpack-premium-analytics/ui', () => ( { const dateFiltersPanelMock = jest.mocked( DateFiltersPanel ); -// Captured before any test installs settings, so repeats do not compound. -const DEFAULT_SETTINGS = getSettings(); - const APPLIED_RANGE = { from: new Date( Date.UTC( 2024, 0, 8 ) ), to: new Date( Date.UTC( 2024, 0, 14, 23, 59, 59, 999 ) ), }; -// Another year, so a subtitle reading the draft instead of the applied range -// is unmistakable. +// A draft over the applied window, so the controller reaches the panel mid-edit. const STAGED_RANGE = { from: new Date( Date.UTC( 2019, 0, 7 ) ), to: new Date( Date.UTC( 2019, 0, 13, 23, 59, 59, 999 ) ), @@ -61,11 +56,6 @@ function buildDateFilters(): ReportDateFilters { describe( 'ReportPageLayout', () => { beforeEach( () => { jest.clearAllMocks(); - setSettings( { - ...DEFAULT_SETTINGS, - formats: { ...DEFAULT_SETTINGS.formats, date: 'F j, Y' }, - timezone: { offset: 0, offsetFormatted: '0', string: 'UTC', abbr: 'UTC' }, - } ); } ); it( 'renders the report title as the section heading', () => { @@ -89,31 +79,6 @@ describe( 'ReportPageLayout', () => { ); } ); - // Holds still while an edit is open; moves only on Apply. - it( 'describes the applied window rather than the staged draft', () => { - render( - - table - - ); - - expect( screen.getByText( /2024/ ) ).not.toHaveTextContent( '2019' ); - } ); - - // The header offers a control for neither, so it describes neither. - it( 'names neither the chart interval nor the comparison the controller carries', () => { - render( - - table - - ); - - const subtitle = screen.getByText( /2024/ ); - - expect( subtitle ).not.toHaveTextContent( /hourly|daily|weekly|monthly|yearly/ ); - expect( subtitle ).not.toHaveTextContent( /vs\.|Previous period|Previous month/ ); - } ); - // Whether the panel draws the comparison control is the report route's scope; the // layout only has to leave the comparison state alone on its way through. it( 'passes the comparison state through without disturbing it', () => { @@ -136,10 +101,9 @@ describe( 'ReportPageLayout', () => { expect( dateFilters.onIntervalChange ).not.toHaveBeenCalled(); } ); - it( 'leaves the header the title alone on a report with no date window', () => { - const { container } = render( table ); + it( 'mounts no date picker on a report with no date window', () => { + render( table ); expect( screen.queryByTestId( 'date-filters-panel' ) ).not.toBeInTheDocument(); - expect( container ).not.toHaveTextContent( /\(\d/ ); } ); } ); diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/report-page-layout.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/report-page-layout.tsx index cd744e9e319a..2d3179c54550 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/report-page-layout.tsx +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/report-page/report-page-layout.tsx @@ -1,9 +1,8 @@ /** * External dependencies */ -import { DateFiltersPanel, SectionHeader, getSectionSubtitle } from '@jetpack-premium-analytics/ui'; +import { DateFiltersPanel, SectionHeader } from '@jetpack-premium-analytics/ui'; import clsx from 'clsx'; -import { useMemo } from 'react'; /** * Internal dependencies */ @@ -31,20 +30,10 @@ export interface ReportPageLayoutProps { * @return The report page scaffold. */ export function ReportPageLayout( { title, dateFilters, tabs, children }: ReportPageLayoutProps ) { - const appliedRange = dateFilters?.appliedRange; - const appliedPresetId = dateFilters?.appliedPresetId; - - // The applied range, not the picker's staged draft. No interval or - // comparison: the header must not describe what it offers no control for. - const subtitle = useMemo( - () => getSectionSubtitle( { range: appliedRange, presetId: appliedPresetId } ), - [ appliedRange, appliedPresetId ] - ); - return (
{ tabs } - + { dateFilters ? : null }
{ children }
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 bd28abbc3037..988231a6929d 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 @@ -196,7 +196,6 @@ function ComposedReportPage( { withComparison, isLoading }: ReportPageStoryContr return ( } - subTitle="All your posts and archive pages." actions={ } > @@ -255,7 +254,7 @@ const meta = { docs: { description: { component: - 'The shared report-page framework: `ReportPageShell` (the page header — breadcrumbs, description, actions), `ReportPageLayout` (optional tabs, the `SectionHeader` carrying the report title and its date controls, and the stacked sections), `ReportPerformanceChart` (multi-metric visits chart with metric show/hide and interval control), and `ReportRecordsTable` (Core DataViews table with client-side search/sort/pagination). Module report pages compose these with their own data hook and field config.', + 'The shared report-page framework: `ReportPageShell` (the page header — breadcrumbs and actions), `ReportPageLayout` (optional tabs, the `SectionHeader` carrying the report title and its date controls, and the stacked sections), `ReportPerformanceChart` (multi-metric visits chart with metric show/hide and interval control), and `ReportRecordsTable` (Core DataViews table with client-side search/sort/pagination). Module report pages compose these with their own data hook and field config.', }, }, }, diff --git a/projects/packages/premium-analytics/routes/dashboard/config/sections.test.ts b/projects/packages/premium-analytics/routes/dashboard/config/sections.test.ts index 58bc1ddc7c25..319eb75075e9 100644 --- a/projects/packages/premium-analytics/routes/dashboard/config/sections.test.ts +++ b/projects/packages/premium-analytics/routes/dashboard/config/sections.test.ts @@ -11,7 +11,6 @@ const SECTIONS: DashboardSection[] = [ slug: 'traffic', label: 'Traffic', title: 'Site traffic', - description: 'Views, visitors, and where they came from.', order: 10, date_filter: 'range', default_layout: [], @@ -21,7 +20,6 @@ const SECTIONS: DashboardSection[] = [ slug: 'insights', label: 'Insights', title: 'Activity insights', - description: 'Longer-term patterns in your content and audience.', order: 20, date_filter: 'year', default_layout: [], @@ -33,7 +31,6 @@ const SECTIONS: DashboardSection[] = [ slug: 'subscribers', label: 'Subscribers', title: 'Subscribers stats', - description: 'How your subscriber list is growing, and how your emails land.', order: 30, default_layout: [], }, @@ -45,7 +42,6 @@ const STORE: DashboardSection = { slug: 'store', label: 'Store', title: null, - description: 'Sales, orders, and what your customers are buying.', order: 40, date_filter: 'range', default_layout: [], diff --git a/projects/packages/premium-analytics/routes/dashboard/config/sections.ts b/projects/packages/premium-analytics/routes/dashboard/config/sections.ts index d45e24519bb4..5f0862d85411 100644 --- a/projects/packages/premium-analytics/routes/dashboard/config/sections.ts +++ b/projects/packages/premium-analytics/routes/dashboard/config/sections.ts @@ -36,13 +36,6 @@ export type DashboardSection = { */ title?: string | null; - /** - * Translated section description, rendered as the page subtitle while this - * section is active. Missing or `null` renders no subtitle — there is no - * default copy behind it. - */ - description?: string | null; - /** * Sort order (ascending). */ diff --git a/projects/packages/premium-analytics/routes/dashboard/stage.module.scss b/projects/packages/premium-analytics/routes/dashboard/stage.module.scss index 79ec63c553a5..ef92b8e5f6bd 100644 --- a/projects/packages/premium-analytics/routes/dashboard/stage.module.scss +++ b/projects/packages/premium-analytics/routes/dashboard/stage.module.scss @@ -145,8 +145,8 @@ @supports (animation-timeline: --section-header-pin) { .sectionHeader { - // Tightens the padding on the subtitle's own timeline: once pinned - // there are no tabs left to clear, and the band should cost the + // Tightens the padding on the timeline the header condenses on: once + // pinned there are no tabs left to clear, and the band should cost the // widgets as little height as it can. animation: condense-section-header linear both; animation-timeline: --section-header-pin; diff --git a/projects/packages/premium-analytics/routes/dashboard/stage.test.tsx b/projects/packages/premium-analytics/routes/dashboard/stage.test.tsx index 8d2d17b633ff..73de3d63a45e 100644 --- a/projects/packages/premium-analytics/routes/dashboard/stage.test.tsx +++ b/projects/packages/premium-analytics/routes/dashboard/stage.test.tsx @@ -56,17 +56,11 @@ jest.mock( '@jetpack-premium-analytics/ui', () => ( { DateFiltersPanel: () => , DateIntervalDropdown: () => null, DateYearFilter: () => null, - SectionHeader: ( { subtitle, children }: { subtitle?: string; children: ReactNode } ) => ( -
- { subtitle ? { `subtitle: ${ subtitle }` } : null } - { children } -
- ), + SectionHeader: ( { children }: { children: ReactNode } ) =>
{ children }
, SectionTabPanel: ( { value, children }: { value: string; children: ReactNode } ) => value === mockActiveSectionSlug ?
{ children }
: null, StatsBreadcrumbs: () => null, StatsPageIcon: () => null, - getSectionSubtitle: () => 'Jan 1 - Jan 30', } ) ); jest.mock( '@wordpress/admin-ui', () => ( { @@ -380,13 +374,12 @@ describe( 'Dashboard header date control', () => { useSectionDateFilterMock.mockReturnValue( DATE_FILTER_RANGE ); } ); - it( 'renders the control and announces the range by default', () => { + it( 'renders the control by default', () => { mockSection( { date_filter: DATE_FILTER_RANGE } ); render( ); expect( screen.getByText( 'header offers comparison' ) ).toBeInTheDocument(); - expect( screen.getByText( 'subtitle: Jan 1 - Jan 30' ) ).toBeInTheDocument(); } ); it( 'renders no control for a section that hands it to its widgets', () => { @@ -401,18 +394,6 @@ describe( 'Dashboard header date control', () => { expect( screen.queryByText( /^header offers/ ) ).not.toBeInTheDocument(); } ); - // The reader has no header control to change the range with. - it( 'announces no range for a section that hands the control to its widgets', () => { - mockSection( { - date_filter: DATE_FILTER_RANGE, - date_filter_options: { with_date_comparison: false, with_header_date_control: false }, - } ); - - render( ); - - expect( screen.queryByText( /^subtitle:/ ) ).not.toBeInTheDocument(); - } ); - // Moving the control must not strip the params the widgets fetch with. it( 'keeps the comparison scope when only the placement moves', () => { mockSection( { @@ -436,6 +417,5 @@ describe( 'Dashboard header date control', () => { render( ); expect( screen.getByText( 'header offers comparison' ) ).toBeInTheDocument(); - expect( screen.getByText( 'subtitle: Jan 1 - Jan 30' ) ).toBeInTheDocument(); } ); } ); diff --git a/projects/packages/premium-analytics/routes/dashboard/stage.tsx b/projects/packages/premium-analytics/routes/dashboard/stage.tsx index aff68e37f965..fd94e520315b 100644 --- a/projects/packages/premium-analytics/routes/dashboard/stage.tsx +++ b/projects/packages/premium-analytics/routes/dashboard/stage.tsx @@ -14,13 +14,12 @@ import { SectionTabPanel, StatsBreadcrumbs, StatsPageIcon, - getSectionSubtitle, } from '@jetpack-premium-analytics/ui'; import { Page } from '@wordpress/admin-ui'; import { Spinner } from '@wordpress/components'; import { store as coreStore } from '@wordpress/core-data'; import { useSelect } from '@wordpress/data'; -import { useCallback, useEffect, useMemo, useState } from '@wordpress/element'; +import { useCallback, useEffect, useState } from '@wordpress/element'; import { WidgetDashboard } from '@wordpress/widget-dashboard'; import { type WidgetModuleRecord } from '@wordpress/widget-primitives'; import { isPremiumAnalyticsInitialSyncFinished } from '../site-readiness'; @@ -135,35 +134,6 @@ function Dashboard(): JSX.Element { const showHeaderDateControl = activeSectionRecord?.date_filter_options?.with_header_date_control ?? true; - /* - * The subtitle follows the applied range, not the picker's staged draft, since it - * states what the widgets show; a header missing a control must not announce it. - */ - const comparisonPresetId = showComparison ? dateFilters.appliedComparisonPresetId : undefined; - const comparisonRange = showComparison ? dateFilters.appliedComparisonRange : undefined; - const sectionSubtitle = useMemo( () => { - if ( ! showHeaderDateControl ) { - return undefined; - } - - return getSectionSubtitle( { - range: dateFilters.appliedRange, - presetId: dateFilters.appliedPresetId, - comparisonPresetId, - comparisonRange, - // The interval control renders as a glyph, so the subtitle is where - // the active bucket is readable. - interval: dateFilters.appliedInterval, - } ); - }, [ - showHeaderDateControl, - dateFilters.appliedRange, - dateFilters.appliedPresetId, - dateFilters.appliedInterval, - comparisonPresetId, - comparisonRange, - ] ); - /* * The year surface applies on click — no Apply step of its own — so stage and * commit together, the way the quick presets do inside `DateRangeFilter`. @@ -250,7 +220,6 @@ function Dashboard(): JSX.Element { } breadcrumbs={ } - subTitle={ activeSectionRecord?.description } actions={ } className={ styles.dashboard } > @@ -265,16 +234,12 @@ function Dashboard(): JSX.Element { value={ section.slug } className={ styles.content } > - { /* Marks where the header below comes to rest, so its subtitle - starts condensing there. Measured, never seen. */ } + { /* Marks where the header below comes to rest, so it starts + condensing there. Measured, never seen. */ }