From d17e81031ef239b733a4e56a30365626ba1cb286 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 3 Jun 2026 14:52:42 +0800 Subject: [PATCH 1/8] feat(premium-analytics): add ui package from next-woocommerce-analytics components --- .../packages/ui/package.json | 25 ++ .../date-comparison-dropdown.scss | 34 ++ .../date-comparison-dropdown.tsx | 170 ++++++++ .../ui/src/date-comparison-dropdown/index.ts | 1 + .../date-filters-panel/date-filters-panel.tsx | 246 +++++++++++ .../ui/src/date-filters-panel/index.ts | 1 + .../date-range-input/date-range-input.scss | 36 ++ .../src/date-range-input/date-range-input.tsx | 119 +++++ .../packages/ui/src/date-range-input/index.ts | 1 + .../date-range-popover/date-range-filter.scss | 105 +++++ .../date-range-popover/date-range-filter.tsx | 411 ++++++++++++++++++ .../ui/src/date-range-popover/index.ts | 2 + .../date-range-presets.scss | 27 ++ .../date-range-presets/date-range-presets.tsx | 148 +++++++ .../ui/src/date-range-presets/index.ts | 29 ++ .../packages/ui/src/index.ts | 1 + .../src/use-comparison-date-presets/index.ts | 2 + .../use-comparison-date-presets.ts | 54 +++ .../packages/ui/tsconfig.json | 9 + 19 files changed, 1421 insertions(+) create mode 100644 projects/packages/premium-analytics/packages/ui/package.json create mode 100644 projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/date-comparison-dropdown.scss create mode 100644 projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/date-comparison-dropdown.tsx create mode 100644 projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/index.ts create mode 100644 projects/packages/premium-analytics/packages/ui/src/date-filters-panel/date-filters-panel.tsx create mode 100644 projects/packages/premium-analytics/packages/ui/src/date-filters-panel/index.ts create mode 100644 projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.scss create mode 100644 projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.tsx create mode 100644 projects/packages/premium-analytics/packages/ui/src/date-range-input/index.ts create mode 100644 projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.scss create mode 100644 projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx create mode 100644 projects/packages/premium-analytics/packages/ui/src/date-range-popover/index.ts create mode 100644 projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.scss create mode 100644 projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx create mode 100644 projects/packages/premium-analytics/packages/ui/src/date-range-presets/index.ts create mode 100644 projects/packages/premium-analytics/packages/ui/src/index.ts create mode 100644 projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/index.ts create mode 100644 projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/use-comparison-date-presets.ts create mode 100644 projects/packages/premium-analytics/packages/ui/tsconfig.json diff --git a/projects/packages/premium-analytics/packages/ui/package.json b/projects/packages/premium-analytics/packages/ui/package.json new file mode 100644 index 000000000000..746a1f67760b --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/package.json @@ -0,0 +1,25 @@ +{ + "name": "@next-woo-analytics/components", + "description": "WooCommerce Analytics components", + "version": "1.0.0", + "type": "module", + "wpModule": true, + "main": "src/index.ts", + "exports": { + ".": "./build/src/index.js" + }, + "dependencies": { + "@automattic/ui": "*", + "date-fns": "*", + "@next-woo-analytics/data": "workspace:*", + "@next-woo-analytics/datetime": "workspace:*", + "@wc-analytics/formatters": "workspace:*", + "@wordpress/components": "*", + "@automattic/admin-toolkit": "*", + "@automattic/design-system": "*", + "@wordpress/icons": "*", + "@wordpress/compose": "*", + "@wordpress/ui": "*", + "clsx": "*" + } +} diff --git a/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/date-comparison-dropdown.scss b/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/date-comparison-dropdown.scss new file mode 100644 index 000000000000..93626fe01f0b --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/date-comparison-dropdown.scss @@ -0,0 +1,34 @@ +.date-comparison-dropdown { + &__button { + background-color: var( --wpds-color-bg-surface-neutral-strong ); + } +} + +.date-filters-panel-button { + background-color: var( --wpds-color-bg-surface-neutral-strong ); +} + +.date-comparison-dropdown__popover { + width: 235px; +} + +/* disable animation for the date range popover */ +/* stylelint-disable property-no-unknown, selector-pseudo-element-no-unknown */ +@media not (prefers-reduced-motion: reduce) { + .date-comparison-dropdown__popover { + view-transition-name: next-admin--date-comparison-dropdown; + transition: none !important; + } +} + +/* ensure it's above the canvas/stage during the transition */ +::view-transition-group(next-admin--date-comparison-dropdown) { + z-index: 3000; +} + +/* no animation for the snapshot (avoid "flashing") */ +::view-transition-new(next-admin--date-comparison-dropdown), +::view-transition-old(next-admin--date-comparison-dropdown) { + animation: none; +} +/* stylelint-enable property-no-unknown, selector-pseudo-element-no-unknown */ 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 new file mode 100644 index 000000000000..5334eabbca17 --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/date-comparison-dropdown.tsx @@ -0,0 +1,170 @@ +/** + * External dependencies + */ +import { privateApis as componentsPrivateApis } from '@wordpress/components'; +import { unlock } from '@automattic/admin-toolkit'; +import { Button } from '@wordpress/ui'; +import { formatDateRange } from '@wc-analytics/formatters'; +import { sprintf, __ } from '@wordpress/i18n'; +import { useMemo } from 'react'; +import type { ComparisonPresetId } from '@next-woo-analytics/datetime'; + +const { Menu } = unlock( componentsPrivateApis ); + +/** + * Internal dependencies + */ +import { DateRangePresets } from '../date-range-presets'; +import type { ComparisonDateRangePreset } from '../use-comparison-date-presets'; +import './date-comparison-dropdown.scss'; + +type DateComparisonDropdownProps = { + /** + * Available comparison presets (e.g., previous-period, previous-month) + */ + presets: ComparisonDateRangePreset[]; + /** + * Whether comparison is enabled + */ + enabled: boolean; + /** + * Currently selected comparison preset ID + */ + presetId?: ComparisonPresetId; + /** + * Whether to remove "Compare to:" prefix from button label + */ + removeCompareToPrefix?: boolean; + /** + * Callback when comparison is enabled + */ + onEnable: () => void; + /** + * Callback when a comparison preset is selected + */ + onPresetChange: ( id: ComparisonPresetId ) => void; + /** + * Callback when comparison is cleared + */ + onClear: () => void; +}; + +export function DateComparisonDropdown( { + presets, + enabled, + presetId, + removeCompareToPrefix = false, + onEnable, + onPresetChange, + onClear, +}: DateComparisonDropdownProps ) { + const selectedPreset = useMemo( + () => + presetId ? presets.find( ( p ) => p.id === presetId ) : undefined, + [ presets, presetId ] + ); + + const comparisonRange = selectedPreset?.range; + const hasValidPreset = !! comparisonRange; + const hasPresets = presets.length > 0; + + if ( ! enabled ) { + return ( + + + { __( 'No comparison', 'woocommerce-analytics' ) } + + } + /> + + + + + { __( + 'No comparison', + 'woocommerce-analytics' + ) } + + + + + + { __( + 'Comparison to past', + 'woocommerce-analytics' + ) } + + + + + + ); + } + + let label: string = __( 'Select comparison', 'woocommerce-analytics' ); + if ( hasValidPreset ) { + if ( removeCompareToPrefix ) { + label = formatDateRange( comparisonRange ); + } else { + label = sprintf( + // translators: %s is the comparison range label + __( 'Compare to: %s', 'woocommerce-analytics' ), + formatDateRange( comparisonRange ) + ); + } + } + + return ( + + + { label } + + } + /> + + { hasPresets && ( + { + /* + * Type assertion is safe here because: + * 1. presets is ComparisonDateRangePreset[] (strongly typed) + * 2. DateRangePresets picks id from our presets array + * 3. Therefore id must be ComparisonPresetId + */ + onPresetChange( id as ComparisonPresetId ); + } } + onClear={ onClear } + /> + ) } + + + ); +} diff --git a/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/index.ts b/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/index.ts new file mode 100644 index 000000000000..89ca07f36f91 --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/index.ts @@ -0,0 +1 @@ +export { DateComparisonDropdown } from './date-comparison-dropdown'; 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 new file mode 100644 index 000000000000..ff4b7498adfd --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/date-filters-panel.tsx @@ -0,0 +1,246 @@ +/** + * External dependencies + */ +import { Stack } from '@wordpress/ui'; +import { BaseControl } from '@wordpress/components'; +import { useMemo, useCallback } from 'react'; +import { + isComparisonPresetId, + isPrimaryPreset, + type ComparisonPresetId, + type PrimaryPresetId, +} from '@next-woo-analytics/datetime'; + +/** + * Internal dependencies + */ +import { DateRangePopover } from '../date-range-popover'; +import { DateComparisonDropdown } from '../date-comparison-dropdown'; +import { useComparisonDatePresets } from '../use-comparison-date-presets'; + +type DateRangePopoverProps = Parameters< typeof DateRangePopover >[ 0 ]; + +export type DateRange = DateRangePopoverProps[ 'range' ]; + +export type DateFiltersPanelProps = { + /** + * The current date range preset ID (e.g., 'last-7-days', 'last-30-days'). + */ + presetId?: PrimaryPresetId; + + /** + * The current primary date range. + */ + range: DateRange; + + /** + * The current comparison preset ID (e.g., 'previous-period', 'previous-month'). + */ + comparisonPresetId?: ComparisonPresetId; + + /** + * Callback when the primary date range changes. + */ + onChange: DateRangePopoverProps[ 'onChange' ]; + + /** + * Callback when the comparison date range changes. + * Receives the calculated comparison range and the preset ID used. + */ + onComparisonChange: ( + range: DateRange | undefined, + presetId?: ComparisonPresetId + ) => void; + + /** + * Props for the date range popover. + */ + rangeControlProps?: Omit< + Parameters< typeof BaseControl >[ 0 ], + 'children' + >; + + /** + * Props for the date comparison dropdown. + */ + comparisonControlProps?: Omit< + Parameters< typeof BaseControl >[ 0 ], + 'children' + >; + + /** + * Callback when the primary date range is applied. + */ + onApply: DateRangePopoverProps[ 'onApply' ]; + + /** + * Callback when the primary date range is canceled. + */ + onCancel: DateRangePopoverProps[ 'onCancel' ]; + + /** + * Whether the primary date range can be applied. + */ + canApply?: boolean; + + /** + * IANA timezone string (e.g., 'America/New_York', 'Europe/London'). + * Required for proper date/time handling. + */ + timeZone: string; + + /** + * Optional external container element for responsive calculations. + * When provided, the DateRangePopover will measure this container's width + * instead of its own wrapper to determine mobile/wide layouts. + */ + containerElement?: HTMLElement | null; +}; + +/** + * DateFiltersPanel - Manages date range selection and comparison controls + * + * This component serves as the container for date filtering functionality, + * managing both the primary date range selection and the comparison date range. + * It owns the comparison state and delegates to child components for UI. + */ +export function DateFiltersPanel( { + presetId, + range, + comparisonPresetId, + onChange, + onComparisonChange, + rangeControlProps = { + label: null, + help: null, + }, + comparisonControlProps = { + label: null, + help: null, + }, + onApply, + onCancel, + canApply = true, + timeZone, + containerElement, +}: DateFiltersPanelProps ) { + /** + * Validate and normalize the primary preset ID. + * Only accepts built-in preset IDs (including 'custom'). + * Invalid/unknown values are treated as undefined, which allows + * DateRangePopover to handle them gracefully (falls back to custom). + */ + const validatedPresetId = useMemo( () => { + if ( ! presetId ) { + return undefined; + } + // Only accept known built-in presets + // Unknown/garbage values from URL are rejected to prevent UI inconsistency + return isPrimaryPreset( presetId ) ? presetId : undefined; + }, [ presetId ] ); + + // Validate and normalize the comparison preset ID + const validatedComparisonPresetId = useMemo( () => { + return isComparisonPresetId( comparisonPresetId ) + ? comparisonPresetId + : undefined; + }, [ comparisonPresetId ] ); + + // Derive comparison enabled state directly from validated prop + const comparisonEnabled = !! validatedComparisonPresetId; + + // Get available presets for the current range + const presets = useComparisonDatePresets( range ); + + /** + * Determines the default preset ID to use when comparison is enabled. + * Priority order: + * 1. 'previous-period' + * 2. 'previous-month' + * 3. First available preset + */ + const defaultPresetId = useMemo( () => { + return ( + presets.find( ( p ) => p.id === 'previous-period' )?.id ?? + presets.find( ( p ) => p.id === 'previous-month' )?.id ?? + presets[ 0 ]?.id + ); + }, [ presets ] ); + + /** + * Currently selected comparison preset, + * based on the validated stored preset ID, or the default preset. + * Returns undefined if no preset is selected + * or if the ID doesn't match any available preset. + */ + const preset = useMemo( () => { + const id = validatedComparisonPresetId ?? defaultPresetId; + return id ? presets.find( ( p ) => p.id === id ) : undefined; + }, [ presets, validatedComparisonPresetId, defaultPresetId ] ); + + const presetChange = useCallback( + ( id: ComparisonPresetId ) => { + const nextPreset = presets.find( ( p ) => p.id === id ); + onComparisonChange( nextPreset?.range, id ); + }, + [ onComparisonChange, presets ] + ); + + /** + * Handles clearing the comparison completely. + * Clears the selected preset and notifies parent. + */ + const clearComparison = useCallback( () => { + onComparisonChange( undefined, undefined ); + }, [ onComparisonChange ] ); + + const handleEnable = useCallback( () => { + // Use validated ID with fallback to default + const presetIdToUse = validatedComparisonPresetId ?? defaultPresetId; + if ( preset?.range && presetIdToUse ) { + onComparisonChange( preset.range, presetIdToUse ); + } + }, [ + onComparisonChange, + preset, + validatedComparisonPresetId, + defaultPresetId, + ] ); + + return ( + + + + + + + + + + ); +} diff --git a/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/index.ts b/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/index.ts new file mode 100644 index 000000000000..9a1d3322329e --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/src/date-filters-panel/index.ts @@ -0,0 +1 @@ +export { DateFiltersPanel } from './date-filters-panel'; diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.scss b/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.scss new file mode 100644 index 000000000000..754e7105acaf --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.scss @@ -0,0 +1,36 @@ +.input-date-control { + flex: 1; + font-size: var( --wpds-font-size-sm ); + + @supports selector(&::-webkit-calendar-picker-indicator) { + input[type="date"] { + // Removes extra spaces for the calendar icon. + width: fit-content; + padding-right: 0; + + appearance: none; + -webkit-appearance: none; + background: none; + + font-size: var( --wpds-font-size-md ); + + // Removes the calendar icon. + &::-webkit-calendar-picker-indicator { + display: none; + } + } + } + + @supports not selector(&::-webkit-calendar-picker-indicator) { + input[type="date"] { + padding: 0; // We'll control input's inner spacing manually + + min-width: fit-content; // Prevent extra space on smaller screens + + // Use flex to center the input's content horizontally + display: flex; + width: calc(100% - 20px); // Take almost all the space + margin-inline: auto; // Keep things centered + } + } +} diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.tsx b/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.tsx new file mode 100644 index 000000000000..740951cfd3a4 --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.tsx @@ -0,0 +1,119 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Field, Input, Stack } from '@wordpress/ui'; +import { useCallback, useEffect, useState } from 'react'; +import { createTZDateFromParts } from '@next-woo-analytics/datetime'; +import { formatDate } from '@wc-analytics/formatters'; + +/** + * Internal dependencies + */ +import { DateRangePopover } from '../date-range-popover/date-range-filter'; +import './date-range-input.scss'; + +type DateRangeInputProps = Pick< + Parameters< typeof DateRangePopover >[ 0 ], + 'range' | 'onChange' +> & { + timeZone: string; +}; + +type DateInputProps = Pick< DateRangeInputProps, 'timeZone' > & { + label: string; + date?: Date; + onChange: ( date?: Date ) => void; +}; + +const formatToString = ( date?: Date ) => + date ? formatDate( date, 'iso' ) : ''; + +function parseFromString( dateString: string, timeZone: string ) { + const [ year, month, day ] = dateString + .split( '-' ) + .map( ( x ) => Number( x ) ); + + const parsedDate = createTZDateFromParts( + [ year, month - 1, day ], + timeZone + ); + + return ! isNaN( parsedDate.getTime() ) ? parsedDate : undefined; +} + +function DateInput( { label, date, onChange, timeZone }: DateInputProps ) { + const [ value, setValue ] = useState( formatToString( date ) ); + + useEffect( () => { + setValue( formatToString( date ) ); + }, [ date ] ); + + const onInputChange = useCallback( + ( event: React.ChangeEvent< HTMLInputElement > ) => { + const newValue = event.target.value; + setValue( newValue ); + + const newDate = parseFromString( newValue, timeZone ); + + // Call onChange only when the date is complete and reasonable, to avoid unwanted updates. + // Also avoids parseFromString auto-filling partial input (e.g. "20" → "1920"). + if ( newDate && newDate.getFullYear() > 2000 ) { + onChange( newDate ); + } + }, + [ onChange, timeZone ] + ); + + const onClick = useCallback( ( e: React.MouseEvent ) => { + // Prevents the date input from opening the browser date picker, + // as we want to use a custom date picker elsewhere. + e.preventDefault(); + }, [] ); + + return ( + + { label } + + + ); +} + +export function DateRangeInput( { + range, + onChange, + timeZone, +}: DateRangeInputProps ) { + const { from, to } = range; + + return ( + + { + if ( nextFrom && to && nextFrom <= to ) { + onChange( { from: nextFrom, to } ); + } + } } + /> + + { + if ( nextTo && from && from <= nextTo ) { + onChange( { from, to: nextTo } ); + } + } } + /> + + ); +} diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-input/index.ts b/projects/packages/premium-analytics/packages/ui/src/date-range-input/index.ts new file mode 100644 index 000000000000..d42bdcd634fe --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-input/index.ts @@ -0,0 +1 @@ +export { DateRangeInput } from './date-range-input'; diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.scss b/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.scss new file mode 100644 index 000000000000..b0bc48158ac7 --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.scss @@ -0,0 +1,105 @@ +@use "@wordpress/base-styles/variables" as vars; +@use "@wordpress/base-styles/colors" as colors; + +.date-range-popover-content { + --wca-popover-padding: var( --wpds-dimension-padding-lg ); + --wca-popover-border-color: #{colors.$gray-300}; + --wca-popover-border-width: #{vars.$border-width}; + + display: grid; + grid-template-columns: auto 1fr; + grid-template-rows: 1fr auto; + + // Grid lines: background = line color, gap = line width + background-color: var( --wca-popover-border-color ); + column-gap: var( --wca-popover-border-width ); + row-gap: var( --wca-popover-border-width ); + + .date-range-calendar { + display: flex; + justify-content: center; + align-items: center; + } + + // Mobile layout: override grid to use flex column + &--mobile { + display: flex; + flex-direction: column; + gap: var( --wpds-dimension-gap-lg ); + padding: var( --wca-popover-padding ); + background-color: #fff; + + .date-range-calendar { + --a8c-calendar-button-width: 32px; + --a8c-calendar-button-height: 32px; + } + + .date-range-popover-actions { + padding: 0; + padding-block-start: var( --wpds-dimension-gap-sm ); + } + } +} + +.date-range-presets-wrapper { + grid-row: 1; + display: grid; + grid-template-columns: minmax(0, max-content) 1fr; + background-color: #fff; + padding-bottom: var( --wpds-dimension-gap-sm ); + width: calc( 60 * var( --wpds-dimension-base ) ); + padding-right: var( --wpds-dimension-padding-sm ); +} + +.date-filters-panel-button { + background-color: #fff; // ToDo: handle this upstream. +} + +.date-range-calendar-wrapper { + --wca-calendar-button-width: 32px; + --wca-calendar-button-height: 32px; + --wca-calendar-button-gap: 1rem; // consistent with automattic/ui style + --wca-calendar-padding: var( --wca-popover-padding ); + --wca-calendar-width: calc( var( --wca-calendar-button-width ) * 7 + var( --wca-calendar-padding ) * 2 ); + --wca-calendar-width-wide: calc( var( --wca-calendar-button-width ) * 14 + var( --wca-calendar-padding ) * 2 + var( --wca-calendar-button-gap ) ); + + grid-row: 1; + padding: var( --wca-calendar-padding ); + width: var( --wca-calendar-width ); + background-color: #fff; + + .date-range-calendar { + --a8c-calendar-button-width: var( --wca-calendar-button-width ); + --a8c-calendar-button-height: var( --wca-calendar-button-height ); + } + + &__wide { + width: var( --wca-calendar-width-wide ); + } +} + +.date-range-popover-actions { + grid-column: 1 / -1; + padding: calc( var( --wca-popover-padding ) / 2 ) var( --wca-popover-padding ); + background-color: #fff; +} + +/* disable animation for the date range popover */ +/* stylelint-disable property-no-unknown, selector-pseudo-element-no-unknown */ +@media not (prefers-reduced-motion: reduce) { + .date-filters-panel__popover { + view-transition-name: next-admin--date-range-popover; + } +} + +/* ensure it's above the canvas/stage during the transition */ +::view-transition-group(next-admin--date-range-popover) { + z-index: 3000; +} + +/* no animation for the snapshot (avoid "flashing") */ +::view-transition-new(next-admin--date-range-popover), +::view-transition-old(next-admin--date-range-popover) { + animation: none; +} +/* stylelint-enable property-no-unknown, selector-pseudo-element-no-unknown */ diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx b/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx new file mode 100644 index 000000000000..d09f8b634c51 --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx @@ -0,0 +1,411 @@ +/** + * External dependencies + */ +import { DateRangeCalendar } from '@automattic/ui'; +import { + Dropdown, + SelectControl, + privateApis as componentsPrivateApis, +} from '@wordpress/components'; +import { unlock } from '@automattic/admin-toolkit'; +import { calendar } from '@wordpress/icons'; +import { Badge, Button, Stack } from '@wordpress/ui'; +import { useState, useCallback, useMemo, useEffect } from 'react'; +import { useResizeObserver } from '@wordpress/compose'; +import { formatDateRange } from '@wc-analytics/formatters'; +import { __ } from '@wordpress/i18n'; +import clsx from 'clsx'; +import '@automattic/ui/style.css'; +import { + getPresetLabel, + getDefaultDateRangePresets, + PRESET_CUSTOM, + type PrimaryPresetId, + type DateRangePreset, +} from '@next-woo-analytics/datetime'; + +/** + * Internal dependencies + */ +import { DateRangePresets } from '../date-range-presets'; +import { DateRangeInput } from '../date-range-input'; +import './date-range-filter.scss'; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment +const { Menu } = unlock( componentsPrivateApis ); + +/** + * Threshold width (in pixels) below which we consider the layout "mobile". + * This is based on the container width, not the viewport. + */ +const MOBILE_CONTAINER_WIDTH_THRESHOLD = 480; + +/** + * Date range type from @automattic/ui. + * Represents a range with `from` and `to` Date objects. + */ +export type DateRange = NonNullable< + Parameters< typeof DateRangeCalendar >[ 0 ][ 'selected' ] +>; + +/** + * Props for DateRangePopoverContent component. + */ +type DateRangePopoverContentProps = { + /** + * Currently selected preset identifier + */ + presetId?: PrimaryPresetId; + + /** + * The selected date range + */ + range: DateRange; + + /** + * Callback when range or preset changes + */ + onChange: ( range?: DateRange, preset?: PrimaryPresetId ) => void; + + /** + * Callback when user applies the selection + */ + onApply: () => void; + + /** + * Callback when user cancels the selection + */ + onCancel: () => void; + + /** + * Whether the Apply button should be enabled + */ + canApply: boolean; + + /** + * Whether to show wide screen layout (2 months) + */ + isWideScreen?: boolean; + + /** + * Whether to show mobile layout (dropdown presets instead of sidebar) + */ + isMobile?: boolean; + + /** + * IANA timezone string (e.g., 'America/New_York', 'Europe/London'). + * Required for proper date/time handling. + */ + timeZone: string; +}; + +/** + * Props for DateRangePresetsDropdown component. + */ +type DateRangePresetsDropdownProps = { + value: PrimaryPresetId | null; + onRangeChange: ( range: DateRange, id: PrimaryPresetId ) => void; + presets?: DateRangePreset[]; + timeZone: string; +}; + +function getDisplayedMonth( range: DateRange ): Date { + return range?.from ?? new Date(); +} + +/** + * Action buttons for the date range popover (Cancel/Apply). + */ +function DateRangePopoverActions( { + onCancel, + onApply, + canApply, +}: Pick< DateRangePopoverContentProps, 'onCancel' | 'onApply' | 'canApply' > ) { + return ( + + + + + ); +} + +/** + * Dropdown version of DateRangePresets for mobile layout. + * Displays presets as a SelectControl instead of a menu list. + */ +function DateRangePresetsDropdown( { + value, + onRangeChange, + presets: presetsProp, + timeZone, +}: DateRangePresetsDropdownProps ) { + const defaultPresets = useMemo( + () => ( presetsProp ? [] : getDefaultDateRangePresets( timeZone ) ), + [ presetsProp, timeZone ] + ); + const presets = presetsProp || defaultPresets; + + const options = useMemo( + () => [ + ...presets.map( ( { id, label } ) => ( { + value: id, + label, + } ) ), + { + value: PRESET_CUSTOM, + label: __( 'Custom range', 'woocommerce-analytics' ), + }, + ], + [ presets ] + ); + + const handleChange = useCallback( + ( selectedValue: string ) => { + const preset = presets.find( ( p ) => p.id === selectedValue ); + if ( preset ) { + onRangeChange( preset.range, preset.id ); + } + }, + [ presets, onRangeChange ] + ); + + return ( + + ); +} + +/** + * Content of the DateRangePopover, extracted for Storybook visualization. + * This component is exported for internal use only (stories, testing). + */ +export function DateRangePopoverContent( { + presetId, + range, + onChange, + onApply, + onCancel, + canApply, + isWideScreen = false, + isMobile = false, + timeZone, +}: DateRangePopoverContentProps ) { + const [ displayedMonth, setDisplayedMonth ] = useState( + getDisplayedMonth( range ) + ); + + const handleChange = ( + nextRange?: DateRange, + nextPrimaryPresetId?: PrimaryPresetId + ) => { + if ( nextRange ) { + setDisplayedMonth( getDisplayedMonth( nextRange ) ); + } + + // If nextPrimaryPresetId is undefined, the user manually changed the dates + // (via calendar or input fields), so we switch to PRESET_CUSTOM + const effectivePrimaryPresetId = nextPrimaryPresetId ?? PRESET_CUSTOM; + + onChange( nextRange, effectivePrimaryPresetId ); + }; + + // Mobile layout: single column with dropdown presets + if ( isMobile ) { + return ( +
+ + + + + handleChange( nextRange ) } + numberOfMonths={ 1 } + month={ displayedMonth } + onMonthChange={ setDisplayedMonth } + timeZone={ timeZone } + /> + + +
+ ); + } + + // Desktop layout: grid with sidebar presets + return ( +
+
+ + + +
+ + + + + handleChange( nextRange ) } + numberOfMonths={ isWideScreen ? 2 : 1 } + month={ displayedMonth } + onMonthChange={ setDisplayedMonth } + timeZone={ timeZone } + /> + + + +
+ ); +} + +type DateRangePopoverProps = Omit< + DateRangePopoverContentProps, + 'isWideScreen' | 'isMobile' +> & { + /** + * Optional external container element for responsive calculations. + * When provided, the component will measure this container's width + * instead of its own wrapper to determine mobile/wide layouts. + */ + containerElement?: HTMLElement | null; +}; + +/** + * Threshold width (in pixels) for showing 2 months in calendar. + * Based on CSS: --wca-calendar-width-wide (~500px for 2 months + presets sidebar) + */ +const WIDE_CONTAINER_THRESHOLD = 780; + +export function DateRangePopover( { + presetId, + range, + onChange, + onApply, + onCancel, + canApply, + timeZone, + containerElement, +}: DateRangePopoverProps ) { + const [ containerWidth, setContainerWidth ] = useState< number | null >( + null + ); + + // Callback to update container width + const handleResize = useCallback( ( entries: ResizeObserverEntry[] ) => { + const entry = entries[ 0 ]; + if ( entry ) { + setContainerWidth( entry.contentRect.width ); + } + }, [] ); + + // ResizeObserver for the reference container + const setObserverRef = useResizeObserver< HTMLElement >( handleResize ); + + // Attach observer to containerElement if provided, otherwise use document.body + useEffect( () => { + const element = containerElement ?? document.body; + setObserverRef( element ); + }, [ containerElement, setObserverRef ] ); + + // Determine layout based on container width + const isMobile = + containerWidth !== null && + containerWidth < MOBILE_CONTAINER_WIDTH_THRESHOLD; + + const isWideScreen = + containerWidth !== null && containerWidth >= WIDE_CONTAINER_THRESHOLD; + + const presetLabel = getPresetLabel( presetId ); + + return ( + ( + + ) } + renderContent={ ( { onClose } ) => ( + { + onApply(); + onClose(); + } } + onCancel={ () => { + onCancel(); + onClose(); + } } + canApply={ canApply } + isWideScreen={ isWideScreen } + isMobile={ isMobile } + timeZone={ timeZone } + /> + ) } + /> + ); +} diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-popover/index.ts b/projects/packages/premium-analytics/packages/ui/src/date-range-popover/index.ts new file mode 100644 index 000000000000..9d4c1f786baf --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-popover/index.ts @@ -0,0 +1,2 @@ +export { DateRangePopover, DateRangePopoverContent } from './date-range-filter'; +export type { DateRange } from './date-range-filter'; diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.scss b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.scss new file mode 100644 index 000000000000..1c1f1306fc48 --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.scss @@ -0,0 +1,27 @@ +@use "@wordpress/base-styles/variables" as vars; +@use "@wordpress/base-styles/colors" as colors; + +.date-range-presets{ + max-width: 240px; +} + +.date-range-presets, +.date-range-presets__custom-group { + .date-range-presets__item { + min-height: var( --wpds-font-line-height-2xl ); + } +} + +.date-range-presets__custom-group { + // Custom button acts as a label, not an interactive element. + // Override disabled styles to show selection state visually. + .date-range-presets__custom { + &[aria-disabled="true"] { + color: var( --wpds-color-fg-content-neutral-weak ); + } + + &[aria-checked="true"] { + color: var( --wpds-color-fg-content-neutral ); + } + } +} diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx new file mode 100644 index 000000000000..e60fd29057b5 --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx @@ -0,0 +1,148 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { privateApis as componentsPrivateApis } from '@wordpress/components'; +import { unlock } from '@automattic/admin-toolkit'; +import { useMemo } from 'react'; +import { + PRESET_CUSTOM, + getDefaultDateRangePresets, + type PrimaryPresetId, + type DateRangePreset, +} from '@next-woo-analytics/datetime'; + +/** + * Internal dependencies + */ +import { DateRangePopover } from '../date-range-popover/date-range-filter'; +import './date-range-presets.scss'; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment +const { Menu } = unlock( componentsPrivateApis ); + +type DateRange = Parameters< typeof DateRangePopover >[ 0 ][ 'range' ]; + +/** + * Props for the DateRangePresets component. + */ +type DateRangePresetsProps = { + /** + * Callback fired when a preset is selected + */ + onRangeChange: ( range: DateRange, id: PrimaryPresetId ) => void; + + /** + * Currently selected preset ID, or null if none + */ + value: PrimaryPresetId | null; + + /** + * IANA timezone string (e.g., 'America/New_York'). + * Required when using default presets. Optional if explicit presets are provided. + */ + timeZone?: string; + + /** + * Custom presets to display instead of defaults + */ + presets?: DateRangePreset[]; + + /** + * Whether to show the custom date option + */ + supportCustom?: boolean; + + /** + * Optional callback to clear/remove comparison. + * When provided, shows a "No comparison" option. + */ + onClear?: () => void; + + /** + * Whether clicking a preset item should close the parent popover. + * Defaults to undefined (Ariakit default: checkbox items stay open). + */ + hideOnClick?: boolean; +}; + +export function DateRangePresets( { + onRangeChange, + value, + timeZone, + presets: presetsProp, + onClear, + hideOnClick, +}: DateRangePresetsProps ) { + const defaultPresets = useMemo( () => { + if ( presetsProp ) { + return []; + } + + if ( ! timeZone ) { + throw new Error( + 'DateRangePresets: `timeZone` is required when `presets` are not provided.' + ); + } + + return getDefaultDateRangePresets( timeZone ); + }, [ presetsProp, timeZone ] ); + + const presets = useMemo( + () => presetsProp || defaultPresets, + [ presetsProp, defaultPresets ] + ); + + return ( + <> + + { presets.map( ( { id, label, range: presetRange } ) => ( + onRangeChange( presetRange, id ) } + hideOnClick={ hideOnClick } + > + { label } + + ) ) } + + + + + + + + { __( 'Custom', 'woocommerce-analytics' ) } + + + + { onClear && ( + + + { __( 'No comparison', 'woocommerce-analytics' ) } + + + ) } + + + ); +} diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-presets/index.ts b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/index.ts new file mode 100644 index 000000000000..2d34bfc5454d --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/index.ts @@ -0,0 +1,29 @@ +export { DateRangePresets } from './date-range-presets'; + +/** + * Re-export types, constants, and guards from datetime + * so existing consumers of this barrel continue to work. + */ +export { + getDefaultDateRangePresets, + getPresetLabel, + isSelectablePreset, + isPrimaryPreset, + // Preset constants + PRESET_TODAY, + PRESET_YESTERDAY, + PRESET_LAST_7_DAYS, + PRESET_LAST_30_DAYS, + PRESET_LAST_90_DAYS, + PRESET_LAST_365_DAYS, + PRESET_LAST_MONTH, + PRESET_LAST_12_MONTHS, + PRESET_LAST_YEAR, + PRESET_CUSTOM, +} from '@next-woo-analytics/datetime'; + +export type { + PrimaryPresetId, + SelectablePresetId, + DateRangePreset, +} from '@next-woo-analytics/datetime'; diff --git a/projects/packages/premium-analytics/packages/ui/src/index.ts b/projects/packages/premium-analytics/packages/ui/src/index.ts new file mode 100644 index 000000000000..9a1d3322329e --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/src/index.ts @@ -0,0 +1 @@ +export { DateFiltersPanel } from './date-filters-panel'; diff --git a/projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/index.ts b/projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/index.ts new file mode 100644 index 000000000000..945c2ce54278 --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/index.ts @@ -0,0 +1,2 @@ +export { useComparisonDatePresets } from './use-comparison-date-presets'; +export type { ComparisonDateRangePreset } from './use-comparison-date-presets'; diff --git a/projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/use-comparison-date-presets.ts b/projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/use-comparison-date-presets.ts new file mode 100644 index 000000000000..a7f9f48e731f --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/use-comparison-date-presets.ts @@ -0,0 +1,54 @@ +/** + * External dependencies + */ +import { useMemo } from 'react'; +import { + getComparisonRangeFromPreset, + getComparisonPresetConfigs, + type ComparisonPresetId, +} from '@next-woo-analytics/datetime'; + +/** + * Internal dependencies + */ +import type { DateRange } from '../date-range-popover/date-range-filter'; + +/** + * A comparison-specific date range preset. + * Similar to DateRangePreset but with a strongly-typed ComparisonPresetId. + */ +export type ComparisonDateRangePreset = { + id: ComparisonPresetId; + label: string; + range: DateRange; +}; + +/** + * Custom hook that generates comparison date presets + * based on a reference date range. + * + * @param referenceRange - The primary date range to compare against + * @return Array of comparison presets with strongly-typed IDs + */ +export function useComparisonDatePresets( + referenceRange: DateRange +): ComparisonDateRangePreset[] { + return useMemo( () => { + if ( ! referenceRange.from || ! referenceRange.to ) { + return []; + } + + return getComparisonPresetConfigs() + .map( ( { id, label } ) => { + const range = getComparisonRangeFromPreset( + referenceRange, + id + ); + return range ? { id, label, range } : null; + } ) + .filter( + ( preset ): preset is ComparisonDateRangePreset => + preset !== null + ); + }, [ referenceRange ] ); +} diff --git a/projects/packages/premium-analytics/packages/ui/tsconfig.json b/projects/packages/premium-analytics/packages/ui/tsconfig.json new file mode 100644 index 000000000000..18b7c843caa5 --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "build", + "skipLibCheck": true + }, + "include": [ "src/**/*" ], + "exclude": [ "build", "node_modules" ] +} \ No newline at end of file From 41d3f1bc5faa62e575cf5fcedc273c6ff08a4a2e Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 3 Jun 2026 14:53:43 +0800 Subject: [PATCH 2/8] refactor(premium-analytics): adapt ui package imports and manifest for monorepo --- .../packages/ui/package.json | 37 +++++++++---------- .../date-comparison-dropdown.tsx | 14 +++---- .../date-filters-panel/date-filters-panel.tsx | 2 +- .../src/date-range-input/date-range-input.tsx | 8 ++-- .../date-range-popover/date-range-filter.tsx | 10 ++--- .../date-range-presets/date-range-presets.tsx | 6 +-- .../ui/src/date-range-presets/index.ts | 4 +- .../use-comparison-date-presets.ts | 2 +- .../packages/ui/tsconfig.json | 9 ----- 9 files changed, 41 insertions(+), 51 deletions(-) delete mode 100644 projects/packages/premium-analytics/packages/ui/tsconfig.json diff --git a/projects/packages/premium-analytics/packages/ui/package.json b/projects/packages/premium-analytics/packages/ui/package.json index 746a1f67760b..1aae2e2cda00 100644 --- a/projects/packages/premium-analytics/packages/ui/package.json +++ b/projects/packages/premium-analytics/packages/ui/package.json @@ -1,25 +1,24 @@ { - "name": "@next-woo-analytics/components", - "description": "WooCommerce Analytics components", - "version": "1.0.0", + "name": "@automattic/jetpack-premium-analytics-ui", + "version": "0.1.0", + "private": true, "type": "module", - "wpModule": true, "main": "src/index.ts", - "exports": { - ".": "./build/src/index.js" - }, + "types": "src/index.ts", + "sideEffects": [ + "*.scss" + ], "dependencies": { - "@automattic/ui": "*", - "date-fns": "*", - "@next-woo-analytics/data": "workspace:*", - "@next-woo-analytics/datetime": "workspace:*", - "@wc-analytics/formatters": "workspace:*", - "@wordpress/components": "*", - "@automattic/admin-toolkit": "*", - "@automattic/design-system": "*", - "@wordpress/icons": "*", - "@wordpress/compose": "*", - "@wordpress/ui": "*", - "clsx": "*" + "@automattic/ui": "1.0.2", + "@jetpack-premium-analytics/datetime": "workspace:*", + "@jetpack-premium-analytics/formatters": "workspace:*", + "@wordpress/components": "33.1.0", + "@wordpress/compose": "7.46.0", + "@wordpress/i18n": "^6.9.0", + "@wordpress/icons": "^13.0.0", + "@wordpress/private-apis": "1.46.0", + "@wordpress/ui": "0.13.0", + "clsx": "2.1.1", + "react": "18.3.1" } } 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 5334eabbca17..3088dd941c6c 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 @@ -4,10 +4,10 @@ import { privateApis as componentsPrivateApis } from '@wordpress/components'; import { unlock } from '@automattic/admin-toolkit'; import { Button } from '@wordpress/ui'; -import { formatDateRange } from '@wc-analytics/formatters'; +import { formatDateRange } from '@jetpack-premium-analytics/formatters'; import { sprintf, __ } from '@wordpress/i18n'; import { useMemo } from 'react'; -import type { ComparisonPresetId } from '@next-woo-analytics/datetime'; +import type { ComparisonPresetId } from '@jetpack-premium-analytics/datetime'; const { Menu } = unlock( componentsPrivateApis ); @@ -80,7 +80,7 @@ export function DateComparisonDropdown( { size="compact" id="date-comparison-dropdown-button" > - { __( 'No comparison', 'woocommerce-analytics' ) } + { __( 'No comparison', 'jetpack-premium-analytics' ) } } /> @@ -94,7 +94,7 @@ export function DateComparisonDropdown( { { __( 'No comparison', - 'woocommerce-analytics' + 'jetpack-premium-analytics' ) } @@ -109,7 +109,7 @@ export function DateComparisonDropdown( { { __( 'Comparison to past', - 'woocommerce-analytics' + 'jetpack-premium-analytics' ) } @@ -119,14 +119,14 @@ export function DateComparisonDropdown( { ); } - let label: string = __( 'Select comparison', 'woocommerce-analytics' ); + let label: string = __( 'Select comparison', 'jetpack-premium-analytics' ); if ( hasValidPreset ) { if ( removeCompareToPrefix ) { label = formatDateRange( comparisonRange ); } else { label = sprintf( // translators: %s is the comparison range label - __( 'Compare to: %s', 'woocommerce-analytics' ), + __( 'Compare to: %s', 'jetpack-premium-analytics' ), formatDateRange( comparisonRange ) ); } 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 ff4b7498adfd..d2a3202800ad 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 @@ -9,7 +9,7 @@ import { isPrimaryPreset, type ComparisonPresetId, type PrimaryPresetId, -} from '@next-woo-analytics/datetime'; +} from '@jetpack-premium-analytics/datetime'; /** * Internal dependencies diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.tsx b/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.tsx index 740951cfd3a4..18e5d3d3e955 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.tsx @@ -4,8 +4,8 @@ import { __ } from '@wordpress/i18n'; import { Field, Input, Stack } from '@wordpress/ui'; import { useCallback, useEffect, useState } from 'react'; -import { createTZDateFromParts } from '@next-woo-analytics/datetime'; -import { formatDate } from '@wc-analytics/formatters'; +import { createTZDateFromParts } from '@jetpack-premium-analytics/datetime'; +import { formatDate } from '@jetpack-premium-analytics/formatters'; /** * Internal dependencies @@ -94,7 +94,7 @@ export function DateRangeInput( { return ( { @@ -105,7 +105,7 @@ export function DateRangeInput( { /> { diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx b/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx index d09f8b634c51..33caecc2f0ec 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx @@ -12,7 +12,7 @@ import { calendar } from '@wordpress/icons'; import { Badge, Button, Stack } from '@wordpress/ui'; import { useState, useCallback, useMemo, useEffect } from 'react'; import { useResizeObserver } from '@wordpress/compose'; -import { formatDateRange } from '@wc-analytics/formatters'; +import { formatDateRange } from '@jetpack-premium-analytics/formatters'; import { __ } from '@wordpress/i18n'; import clsx from 'clsx'; import '@automattic/ui/style.css'; @@ -22,7 +22,7 @@ import { PRESET_CUSTOM, type PrimaryPresetId, type DateRangePreset, -} from '@next-woo-analytics/datetime'; +} from '@jetpack-premium-analytics/datetime'; /** * Internal dependencies @@ -130,7 +130,7 @@ function DateRangePopoverActions( { className="date-range-popover-actions" > ); @@ -168,7 +168,7 @@ function DateRangePresetsDropdown( { } ) ), { value: PRESET_CUSTOM, - label: __( 'Custom range', 'woocommerce-analytics' ), + label: __( 'Custom range', 'jetpack-premium-analytics' ), }, ], [ presets ] diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx index e60fd29057b5..7048c34be05f 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx @@ -10,7 +10,7 @@ import { getDefaultDateRangePresets, type PrimaryPresetId, type DateRangePreset, -} from '@next-woo-analytics/datetime'; +} from '@jetpack-premium-analytics/datetime'; /** * Internal dependencies @@ -123,7 +123,7 @@ export function DateRangePresets( { disabled > - { __( 'Custom', 'woocommerce-analytics' ) } + { __( 'Custom', 'jetpack-premium-analytics' ) } @@ -138,7 +138,7 @@ export function DateRangePresets( { hideOnClick > - { __( 'No comparison', 'woocommerce-analytics' ) } + { __( 'No comparison', 'jetpack-premium-analytics' ) } ) } diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-presets/index.ts b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/index.ts index 2d34bfc5454d..ed2d27e2e31b 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-range-presets/index.ts +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/index.ts @@ -20,10 +20,10 @@ export { PRESET_LAST_12_MONTHS, PRESET_LAST_YEAR, PRESET_CUSTOM, -} from '@next-woo-analytics/datetime'; +} from '@jetpack-premium-analytics/datetime'; export type { PrimaryPresetId, SelectablePresetId, DateRangePreset, -} from '@next-woo-analytics/datetime'; +} from '@jetpack-premium-analytics/datetime'; diff --git a/projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/use-comparison-date-presets.ts b/projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/use-comparison-date-presets.ts index a7f9f48e731f..021b5702696e 100644 --- a/projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/use-comparison-date-presets.ts +++ b/projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/use-comparison-date-presets.ts @@ -6,7 +6,7 @@ import { getComparisonRangeFromPreset, getComparisonPresetConfigs, type ComparisonPresetId, -} from '@next-woo-analytics/datetime'; +} from '@jetpack-premium-analytics/datetime'; /** * Internal dependencies diff --git a/projects/packages/premium-analytics/packages/ui/tsconfig.json b/projects/packages/premium-analytics/packages/ui/tsconfig.json deleted file mode 100644 index 18b7c843caa5..000000000000 --- a/projects/packages/premium-analytics/packages/ui/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "build", - "skipLibCheck": true - }, - "include": [ "src/**/*" ], - "exclude": [ "build", "node_modules" ] -} \ No newline at end of file From 196765f5624c8501feecaf22d152016be1482c2c Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 3 Jun 2026 14:54:02 +0800 Subject: [PATCH 3/8] refactor(premium-analytics): decouple ui package from admin-toolkit unlock --- .../date-comparison-dropdown.tsx | 2 +- .../date-range-popover/date-range-filter.tsx | 2 +- .../date-range-presets/date-range-presets.tsx | 2 +- .../packages/ui/src/lock/unlock.ts | 22 +++++++++++++++++++ 4 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 projects/packages/premium-analytics/packages/ui/src/lock/unlock.ts 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 3088dd941c6c..19cfe7f37f4e 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 @@ -2,7 +2,7 @@ * External dependencies */ import { privateApis as componentsPrivateApis } from '@wordpress/components'; -import { unlock } from '@automattic/admin-toolkit'; +import { unlock } from '../lock/unlock'; import { Button } from '@wordpress/ui'; import { formatDateRange } from '@jetpack-premium-analytics/formatters'; import { sprintf, __ } from '@wordpress/i18n'; diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx b/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx index 33caecc2f0ec..51d015ff72b2 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx @@ -7,7 +7,7 @@ import { SelectControl, privateApis as componentsPrivateApis, } from '@wordpress/components'; -import { unlock } from '@automattic/admin-toolkit'; +import { unlock } from '../lock/unlock'; import { calendar } from '@wordpress/icons'; import { Badge, Button, Stack } from '@wordpress/ui'; import { useState, useCallback, useMemo, useEffect } from 'react'; diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx index 7048c34be05f..e401c5accf64 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx @@ -3,7 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { privateApis as componentsPrivateApis } from '@wordpress/components'; -import { unlock } from '@automattic/admin-toolkit'; +import { unlock } from '../lock/unlock'; import { useMemo } from 'react'; import { PRESET_CUSTOM, diff --git a/projects/packages/premium-analytics/packages/ui/src/lock/unlock.ts b/projects/packages/premium-analytics/packages/ui/src/lock/unlock.ts new file mode 100644 index 000000000000..ca799e25b34c --- /dev/null +++ b/projects/packages/premium-analytics/packages/ui/src/lock/unlock.ts @@ -0,0 +1,22 @@ +/** + * Local unlock helper for reaching the private `Menu` component that + * `@wordpress/components` exposes through `@wordpress/private-apis`. + * + * Upstream reached `Menu` via `@automattic/admin-toolkit`'s `unlock`, which is + * not available in this monorepo. This mirrors existing Jetpack precedent that + * opts in to the private APIs directly: + * + * - `projects/packages/jetpack-mu-wpcom/src/common/utils.ts` (`getUnlock()`) + * - `projects/js-packages/charts/src/stories/unlock.ts` + * + * The opt-in module name only needs to be an allow-listed core module; the + * returned `unlock` reads private data bound to any object, so it resolves the + * private APIs locked onto `@wordpress/components`' `privateApis`. + */ + +import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis'; + +export const { unlock } = __dangerousOptInToUnstableAPIsOnlyForCoreModules( + 'I acknowledge private features are not for use in themes or plugins and doing so will break in the next version of WordPress.', + '@wordpress/components' +); From 77b521557795e4b9484cbe81da24e8862cde9b75 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 3 Jun 2026 14:54:13 +0800 Subject: [PATCH 4/8] style(premium-analytics): align ported ui package with jetpack prettier --- .../date-comparison-dropdown.scss | 10 +-- .../date-comparison-dropdown.tsx | 19 ++---- .../date-filters-panel/date-filters-panel.tsx | 34 +++------- .../date-range-input/date-range-input.scss | 10 +-- .../src/date-range-input/date-range-input.tsx | 29 ++------ .../date-range-popover/date-range-filter.scss | 23 ++++--- .../date-range-popover/date-range-filter.tsx | 66 +++++-------------- .../date-range-presets.scss | 10 +-- .../date-range-presets/date-range-presets.tsx | 13 +--- .../use-comparison-date-presets.ts | 14 +--- 10 files changed, 70 insertions(+), 158 deletions(-) diff --git a/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/date-comparison-dropdown.scss b/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/date-comparison-dropdown.scss index 93626fe01f0b..a6f6f1909e14 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/date-comparison-dropdown.scss +++ b/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/date-comparison-dropdown.scss @@ -1,6 +1,6 @@ .date-comparison-dropdown { &__button { - background-color: var( --wpds-color-bg-surface-neutral-strong ); + background-color: var( --wpds-color-bg-surface-neutral-strong ); } } @@ -14,7 +14,7 @@ /* disable animation for the date range popover */ /* stylelint-disable property-no-unknown, selector-pseudo-element-no-unknown */ -@media not (prefers-reduced-motion: reduce) { +@media not ( prefers-reduced-motion: reduce ) { .date-comparison-dropdown__popover { view-transition-name: next-admin--date-comparison-dropdown; transition: none !important; @@ -22,13 +22,13 @@ } /* ensure it's above the canvas/stage during the transition */ -::view-transition-group(next-admin--date-comparison-dropdown) { +::view-transition-group( next-admin--date-comparison-dropdown ) { z-index: 3000; } /* no animation for the snapshot (avoid "flashing") */ -::view-transition-new(next-admin--date-comparison-dropdown), -::view-transition-old(next-admin--date-comparison-dropdown) { +::view-transition-new( next-admin--date-comparison-dropdown ), +::view-transition-old( next-admin--date-comparison-dropdown ) { animation: none; } /* stylelint-enable property-no-unknown, selector-pseudo-element-no-unknown */ 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 19cfe7f37f4e..58379d52d21c 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 @@ -59,8 +59,7 @@ export function DateComparisonDropdown( { onClear, }: DateComparisonDropdownProps ) { const selectedPreset = useMemo( - () => - presetId ? presets.find( ( p ) => p.id === presetId ) : undefined, + () => ( presetId ? presets.find( p => p.id === presetId ) : undefined ), [ presets, presetId ] ); @@ -86,16 +85,9 @@ export function DateComparisonDropdown( { /> - + - { __( - 'No comparison', - 'jetpack-premium-analytics' - ) } + { __( 'No comparison', 'jetpack-premium-analytics' ) } @@ -107,10 +99,7 @@ export function DateComparisonDropdown( { hideOnClick > - { __( - 'Comparison to past', - 'jetpack-premium-analytics' - ) } + { __( 'Comparison to past', 'jetpack-premium-analytics' ) } 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 d2a3202800ad..fc06b24ef7c3 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 @@ -47,26 +47,17 @@ export type DateFiltersPanelProps = { * Callback when the comparison date range changes. * Receives the calculated comparison range and the preset ID used. */ - onComparisonChange: ( - range: DateRange | undefined, - presetId?: ComparisonPresetId - ) => void; + onComparisonChange: ( range: DateRange | undefined, presetId?: ComparisonPresetId ) => void; /** * Props for the date range popover. */ - rangeControlProps?: Omit< - Parameters< typeof BaseControl >[ 0 ], - 'children' - >; + rangeControlProps?: Omit< Parameters< typeof BaseControl >[ 0 ], 'children' >; /** * Props for the date comparison dropdown. */ - comparisonControlProps?: Omit< - Parameters< typeof BaseControl >[ 0 ], - 'children' - >; + comparisonControlProps?: Omit< Parameters< typeof BaseControl >[ 0 ], 'children' >; /** * Callback when the primary date range is applied. @@ -141,9 +132,7 @@ export function DateFiltersPanel( { // Validate and normalize the comparison preset ID const validatedComparisonPresetId = useMemo( () => { - return isComparisonPresetId( comparisonPresetId ) - ? comparisonPresetId - : undefined; + return isComparisonPresetId( comparisonPresetId ) ? comparisonPresetId : undefined; }, [ comparisonPresetId ] ); // Derive comparison enabled state directly from validated prop @@ -161,8 +150,8 @@ export function DateFiltersPanel( { */ const defaultPresetId = useMemo( () => { return ( - presets.find( ( p ) => p.id === 'previous-period' )?.id ?? - presets.find( ( p ) => p.id === 'previous-month' )?.id ?? + presets.find( p => p.id === 'previous-period' )?.id ?? + presets.find( p => p.id === 'previous-month' )?.id ?? presets[ 0 ]?.id ); }, [ presets ] ); @@ -175,12 +164,12 @@ export function DateFiltersPanel( { */ const preset = useMemo( () => { const id = validatedComparisonPresetId ?? defaultPresetId; - return id ? presets.find( ( p ) => p.id === id ) : undefined; + return id ? presets.find( p => p.id === id ) : undefined; }, [ presets, validatedComparisonPresetId, defaultPresetId ] ); const presetChange = useCallback( ( id: ComparisonPresetId ) => { - const nextPreset = presets.find( ( p ) => p.id === id ); + const nextPreset = presets.find( p => p.id === id ); onComparisonChange( nextPreset?.range, id ); }, [ onComparisonChange, presets ] @@ -200,12 +189,7 @@ export function DateFiltersPanel( { if ( preset?.range && presetIdToUse ) { onComparisonChange( preset.range, presetIdToUse ); } - }, [ - onComparisonChange, - preset, - validatedComparisonPresetId, - defaultPresetId, - ] ); + }, [ onComparisonChange, preset, validatedComparisonPresetId, defaultPresetId ] ); return ( diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.scss b/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.scss index 754e7105acaf..8d74ebc150f4 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.scss +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.scss @@ -2,8 +2,8 @@ flex: 1; font-size: var( --wpds-font-size-sm ); - @supports selector(&::-webkit-calendar-picker-indicator) { - input[type="date"] { + @supports selector( &::-webkit-calendar-picker-indicator ) { + input[type='date'] { // Removes extra spaces for the calendar icon. width: fit-content; padding-right: 0; @@ -21,15 +21,15 @@ } } - @supports not selector(&::-webkit-calendar-picker-indicator) { - input[type="date"] { + @supports not selector( &::-webkit-calendar-picker-indicator ) { + input[type='date'] { padding: 0; // We'll control input's inner spacing manually min-width: fit-content; // Prevent extra space on smaller screens // Use flex to center the input's content horizontally display: flex; - width: calc(100% - 20px); // Take almost all the space + width: calc( 100% - 20px ); // Take almost all the space margin-inline: auto; // Keep things centered } } diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.tsx b/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.tsx index 18e5d3d3e955..64e3419a0ace 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.tsx @@ -26,18 +26,12 @@ type DateInputProps = Pick< DateRangeInputProps, 'timeZone' > & { onChange: ( date?: Date ) => void; }; -const formatToString = ( date?: Date ) => - date ? formatDate( date, 'iso' ) : ''; +const formatToString = ( date?: Date ) => ( date ? formatDate( date, 'iso' ) : '' ); function parseFromString( dateString: string, timeZone: string ) { - const [ year, month, day ] = dateString - .split( '-' ) - .map( ( x ) => Number( x ) ); + const [ year, month, day ] = dateString.split( '-' ).map( x => Number( x ) ); - const parsedDate = createTZDateFromParts( - [ year, month - 1, day ], - timeZone - ); + const parsedDate = createTZDateFromParts( [ year, month - 1, day ], timeZone ); return ! isNaN( parsedDate.getTime() ) ? parsedDate : undefined; } @@ -74,21 +68,12 @@ function DateInput( { label, date, onChange, timeZone }: DateInputProps ) { return ( { label } - + ); } -export function DateRangeInput( { - range, - onChange, - timeZone, -}: DateRangeInputProps ) { +export function DateRangeInput( { range, onChange, timeZone }: DateRangeInputProps ) { const { from, to } = range; return ( @@ -97,7 +82,7 @@ export function DateRangeInput( { label={ __( 'From', 'jetpack-premium-analytics' ) } date={ from } timeZone={ timeZone } - onChange={ ( nextFrom ) => { + onChange={ nextFrom => { if ( nextFrom && to && nextFrom <= to ) { onChange( { from: nextFrom, to } ); } @@ -108,7 +93,7 @@ export function DateRangeInput( { label={ __( 'To', 'jetpack-premium-analytics' ) } date={ to } timeZone={ timeZone } - onChange={ ( nextTo ) => { + onChange={ nextTo => { if ( nextTo && from && from <= nextTo ) { onChange( { from, to: nextTo } ); } diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.scss b/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.scss index b0bc48158ac7..9eca700420e4 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.scss +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.scss @@ -1,5 +1,5 @@ -@use "@wordpress/base-styles/variables" as vars; -@use "@wordpress/base-styles/colors" as colors; +@use '@wordpress/base-styles/variables' as vars; +@use '@wordpress/base-styles/colors' as colors; .date-range-popover-content { --wca-popover-padding: var( --wpds-dimension-padding-lg ); @@ -44,7 +44,7 @@ .date-range-presets-wrapper { grid-row: 1; display: grid; - grid-template-columns: minmax(0, max-content) 1fr; + grid-template-columns: minmax( 0, max-content ) 1fr; background-color: #fff; padding-bottom: var( --wpds-dimension-gap-sm ); width: calc( 60 * var( --wpds-dimension-base ) ); @@ -60,8 +60,13 @@ --wca-calendar-button-height: 32px; --wca-calendar-button-gap: 1rem; // consistent with automattic/ui style --wca-calendar-padding: var( --wca-popover-padding ); - --wca-calendar-width: calc( var( --wca-calendar-button-width ) * 7 + var( --wca-calendar-padding ) * 2 ); - --wca-calendar-width-wide: calc( var( --wca-calendar-button-width ) * 14 + var( --wca-calendar-padding ) * 2 + var( --wca-calendar-button-gap ) ); + --wca-calendar-width: calc( + var( --wca-calendar-button-width ) * 7 + var( --wca-calendar-padding ) * 2 + ); + --wca-calendar-width-wide: calc( + var( --wca-calendar-button-width ) * 14 + var( --wca-calendar-padding ) * 2 + + var( --wca-calendar-button-gap ) + ); grid-row: 1; padding: var( --wca-calendar-padding ); @@ -86,20 +91,20 @@ /* disable animation for the date range popover */ /* stylelint-disable property-no-unknown, selector-pseudo-element-no-unknown */ -@media not (prefers-reduced-motion: reduce) { +@media not ( prefers-reduced-motion: reduce ) { .date-filters-panel__popover { view-transition-name: next-admin--date-range-popover; } } /* ensure it's above the canvas/stage during the transition */ -::view-transition-group(next-admin--date-range-popover) { +::view-transition-group( next-admin--date-range-popover ) { z-index: 3000; } /* no animation for the snapshot (avoid "flashing") */ -::view-transition-new(next-admin--date-range-popover), -::view-transition-old(next-admin--date-range-popover) { +::view-transition-new( next-admin--date-range-popover ), +::view-transition-old( next-admin--date-range-popover ) { animation: none; } /* stylelint-enable property-no-unknown, selector-pseudo-element-no-unknown */ diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx b/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx index 51d015ff72b2..6d35760113bf 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx @@ -44,9 +44,7 @@ const MOBILE_CONTAINER_WIDTH_THRESHOLD = 480; * Date range type from @automattic/ui. * Represents a range with `from` and `to` Date objects. */ -export type DateRange = NonNullable< - Parameters< typeof DateRangeCalendar >[ 0 ][ 'selected' ] ->; +export type DateRange = NonNullable< Parameters< typeof DateRangeCalendar >[ 0 ][ 'selected' ] >; /** * Props for DateRangePopoverContent component. @@ -132,12 +130,7 @@ function DateRangePopoverActions( { - @@ -176,7 +169,7 @@ function DateRangePresetsDropdown( { const handleChange = useCallback( ( selectedValue: string ) => { - const preset = presets.find( ( p ) => p.id === selectedValue ); + const preset = presets.find( p => p.id === selectedValue ); if ( preset ) { onRangeChange( preset.range, preset.id ); } @@ -209,14 +202,9 @@ export function DateRangePopoverContent( { isMobile = false, timeZone, }: DateRangePopoverContentProps ) { - const [ displayedMonth, setDisplayedMonth ] = useState( - getDisplayedMonth( range ) - ); + const [ displayedMonth, setDisplayedMonth ] = useState( getDisplayedMonth( range ) ); - const handleChange = ( - nextRange?: DateRange, - nextPrimaryPresetId?: PrimaryPresetId - ) => { + const handleChange = ( nextRange?: DateRange, nextPrimaryPresetId?: PrimaryPresetId ) => { if ( nextRange ) { setDisplayedMonth( getDisplayedMonth( nextRange ) ); } @@ -238,27 +226,19 @@ export function DateRangePopoverContent( { timeZone={ timeZone } /> - + handleChange( nextRange ) } + onSelect={ nextRange => handleChange( nextRange ) } numberOfMonths={ 1 } month={ displayedMonth } onMonthChange={ setDisplayedMonth } timeZone={ timeZone } /> - + ); } @@ -283,16 +263,12 @@ export function DateRangePopoverContent( { gap="lg" direction="column" > - + handleChange( nextRange ) } + onSelect={ nextRange => handleChange( nextRange ) } numberOfMonths={ isWideScreen ? 2 : 1 } month={ displayedMonth } onMonthChange={ setDisplayedMonth } @@ -300,19 +276,12 @@ export function DateRangePopoverContent( { /> - + ); } -type DateRangePopoverProps = Omit< - DateRangePopoverContentProps, - 'isWideScreen' | 'isMobile' -> & { +type DateRangePopoverProps = Omit< DateRangePopoverContentProps, 'isWideScreen' | 'isMobile' > & { /** * Optional external container element for responsive calculations. * When provided, the component will measure this container's width @@ -337,9 +306,7 @@ export function DateRangePopover( { timeZone, containerElement, }: DateRangePopoverProps ) { - const [ containerWidth, setContainerWidth ] = useState< number | null >( - null - ); + const [ containerWidth, setContainerWidth ] = useState< number | null >( null ); // Callback to update container width const handleResize = useCallback( ( entries: ResizeObserverEntry[] ) => { @@ -359,12 +326,9 @@ export function DateRangePopover( { }, [ containerElement, setObserverRef ] ); // Determine layout based on container width - const isMobile = - containerWidth !== null && - containerWidth < MOBILE_CONTAINER_WIDTH_THRESHOLD; + const isMobile = containerWidth !== null && containerWidth < MOBILE_CONTAINER_WIDTH_THRESHOLD; - const isWideScreen = - containerWidth !== null && containerWidth >= WIDE_CONTAINER_THRESHOLD; + const isWideScreen = containerWidth !== null && containerWidth >= WIDE_CONTAINER_THRESHOLD; const presetLabel = getPresetLabel( presetId ); diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.scss b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.scss index 1c1f1306fc48..729a1d328ccd 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.scss +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.scss @@ -1,7 +1,7 @@ -@use "@wordpress/base-styles/variables" as vars; -@use "@wordpress/base-styles/colors" as colors; +@use '@wordpress/base-styles/variables' as vars; +@use '@wordpress/base-styles/colors' as colors; -.date-range-presets{ +.date-range-presets { max-width: 240px; } @@ -16,11 +16,11 @@ // Custom button acts as a label, not an interactive element. // Override disabled styles to show selection state visually. .date-range-presets__custom { - &[aria-disabled="true"] { + &[aria-disabled='true'] { color: var( --wpds-color-fg-content-neutral-weak ); } - &[aria-checked="true"] { + &[aria-checked='true'] { color: var( --wpds-color-fg-content-neutral ); } } diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx index e401c5accf64..4037bbc96cc4 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx @@ -88,10 +88,7 @@ export function DateRangePresets( { return getDefaultDateRangePresets( timeZone ); }, [ presetsProp, timeZone ] ); - const presets = useMemo( - () => presetsProp || defaultPresets, - [ presetsProp, defaultPresets ] - ); + const presets = useMemo( () => presetsProp || defaultPresets, [ presetsProp, defaultPresets ] ); return ( <> @@ -122,9 +119,7 @@ export function DateRangePresets( { checked={ value === PRESET_CUSTOM } disabled > - - { __( 'Custom', 'jetpack-premium-analytics' ) } - + { __( 'Custom', 'jetpack-premium-analytics' ) } { onClear && ( @@ -137,9 +132,7 @@ export function DateRangePresets( { onChange={ onClear } hideOnClick > - - { __( 'No comparison', 'jetpack-premium-analytics' ) } - + { __( 'No comparison', 'jetpack-premium-analytics' ) } ) } diff --git a/projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/use-comparison-date-presets.ts b/projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/use-comparison-date-presets.ts index 021b5702696e..74acf7ac072d 100644 --- a/projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/use-comparison-date-presets.ts +++ b/projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/use-comparison-date-presets.ts @@ -30,9 +30,7 @@ export type ComparisonDateRangePreset = { * @param referenceRange - The primary date range to compare against * @return Array of comparison presets with strongly-typed IDs */ -export function useComparisonDatePresets( - referenceRange: DateRange -): ComparisonDateRangePreset[] { +export function useComparisonDatePresets( referenceRange: DateRange ): ComparisonDateRangePreset[] { return useMemo( () => { if ( ! referenceRange.from || ! referenceRange.to ) { return []; @@ -40,15 +38,9 @@ export function useComparisonDatePresets( return getComparisonPresetConfigs() .map( ( { id, label } ) => { - const range = getComparisonRangeFromPreset( - referenceRange, - id - ); + const range = getComparisonRangeFromPreset( referenceRange, id ); return range ? { id, label, range } : null; } ) - .filter( - ( preset ): preset is ComparisonDateRangePreset => - preset !== null - ); + .filter( ( preset ): preset is ComparisonDateRangePreset => preset !== null ); }, [ referenceRange ] ); } From 806998be42ab06efdce262c9500080410872db22 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 3 Jun 2026 14:54:31 +0800 Subject: [PATCH 5/8] fix(premium-analytics): satisfy ui package typecheck and import lint --- .../date-comparison-dropdown.tsx | 27 ++++++++++++------ .../date-filters-panel/date-filters-panel.tsx | 9 +++--- .../src/date-range-input/date-range-input.tsx | 5 ++-- .../date-range-popover/date-range-filter.tsx | 28 +++++++++---------- .../date-range-presets/date-range-presets.tsx | 10 +++---- .../use-comparison-date-presets.ts | 3 +- 6 files changed, 42 insertions(+), 40 deletions(-) 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 58379d52d21c..34f39700b781 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 @@ -1,23 +1,26 @@ /** * External dependencies */ -import { privateApis as componentsPrivateApis } from '@wordpress/components'; -import { unlock } from '../lock/unlock'; -import { Button } from '@wordpress/ui'; import { formatDateRange } from '@jetpack-premium-analytics/formatters'; +import { privateApis as componentsPrivateApis } from '@wordpress/components'; import { sprintf, __ } from '@wordpress/i18n'; +import { Button } from '@wordpress/ui'; import { useMemo } from 'react'; -import type { ComparisonPresetId } from '@jetpack-premium-analytics/datetime'; - -const { Menu } = unlock( componentsPrivateApis ); - /** * Internal dependencies */ import { DateRangePresets } from '../date-range-presets'; +import { unlock } from '../lock/unlock'; import type { ComparisonDateRangePreset } from '../use-comparison-date-presets'; +import type { + ComparisonPresetId, + DateRangePreset, + PrimaryPresetId, +} from '@jetpack-premium-analytics/datetime'; import './date-comparison-dropdown.scss'; +const { Menu } = unlock( componentsPrivateApis ); + type DateComparisonDropdownProps = { /** * Available comparison presets (e.g., previous-period, previous-month) @@ -138,8 +141,14 @@ export function DateComparisonDropdown( { { hasPresets && ( { /* 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 fc06b24ef7c3..dd9f6a3fc189 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 @@ -1,21 +1,20 @@ /** * External dependencies */ -import { Stack } from '@wordpress/ui'; -import { BaseControl } from '@wordpress/components'; -import { useMemo, useCallback } from 'react'; import { isComparisonPresetId, isPrimaryPreset, type ComparisonPresetId, type PrimaryPresetId, } from '@jetpack-premium-analytics/datetime'; - +import { BaseControl } from '@wordpress/components'; +import { Stack } from '@wordpress/ui'; +import { useMemo, useCallback } from 'react'; /** * Internal dependencies */ -import { DateRangePopover } from '../date-range-popover'; import { DateComparisonDropdown } from '../date-comparison-dropdown'; +import { DateRangePopover } from '../date-range-popover'; import { useComparisonDatePresets } from '../use-comparison-date-presets'; type DateRangePopoverProps = Parameters< typeof DateRangePopover >[ 0 ]; diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.tsx b/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.tsx index 64e3419a0ace..562fe3f07f98 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.tsx @@ -1,12 +1,11 @@ /** * External dependencies */ +import { createTZDateFromParts } from '@jetpack-premium-analytics/datetime'; +import { formatDate } from '@jetpack-premium-analytics/formatters'; import { __ } from '@wordpress/i18n'; import { Field, Input, Stack } from '@wordpress/ui'; import { useCallback, useEffect, useState } from 'react'; -import { createTZDateFromParts } from '@jetpack-premium-analytics/datetime'; -import { formatDate } from '@jetpack-premium-analytics/formatters'; - /** * Internal dependencies */ diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx b/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx index 6d35760113bf..926074092897 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.tsx @@ -2,36 +2,34 @@ * External dependencies */ import { DateRangeCalendar } from '@automattic/ui'; +import { + getPresetLabel, + getDefaultDateRangePresets, + PRESET_CUSTOM, + type PrimaryPresetId, + type DateRangePreset, +} from '@jetpack-premium-analytics/datetime'; +import { formatDateRange } from '@jetpack-premium-analytics/formatters'; import { Dropdown, SelectControl, privateApis as componentsPrivateApis, } from '@wordpress/components'; -import { unlock } from '../lock/unlock'; -import { calendar } from '@wordpress/icons'; -import { Badge, Button, Stack } from '@wordpress/ui'; -import { useState, useCallback, useMemo, useEffect } from 'react'; import { useResizeObserver } from '@wordpress/compose'; -import { formatDateRange } from '@jetpack-premium-analytics/formatters'; import { __ } from '@wordpress/i18n'; +import { calendar } from '@wordpress/icons'; +import { Badge, Button, Stack } from '@wordpress/ui'; import clsx from 'clsx'; +import { useState, useCallback, useMemo, useEffect } from 'react'; import '@automattic/ui/style.css'; -import { - getPresetLabel, - getDefaultDateRangePresets, - PRESET_CUSTOM, - type PrimaryPresetId, - type DateRangePreset, -} from '@jetpack-premium-analytics/datetime'; - /** * Internal dependencies */ -import { DateRangePresets } from '../date-range-presets'; import { DateRangeInput } from '../date-range-input'; +import { DateRangePresets } from '../date-range-presets'; +import { unlock } from '../lock/unlock'; import './date-range-filter.scss'; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { Menu } = unlock( componentsPrivateApis ); /** diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx index 4037bbc96cc4..f27ac51d264c 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.tsx @@ -1,24 +1,22 @@ /** * External dependencies */ -import { __ } from '@wordpress/i18n'; -import { privateApis as componentsPrivateApis } from '@wordpress/components'; -import { unlock } from '../lock/unlock'; -import { useMemo } from 'react'; import { PRESET_CUSTOM, getDefaultDateRangePresets, type PrimaryPresetId, type DateRangePreset, } from '@jetpack-premium-analytics/datetime'; - +import { privateApis as componentsPrivateApis } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useMemo } from 'react'; /** * Internal dependencies */ import { DateRangePopover } from '../date-range-popover/date-range-filter'; +import { unlock } from '../lock/unlock'; import './date-range-presets.scss'; -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { Menu } = unlock( componentsPrivateApis ); type DateRange = Parameters< typeof DateRangePopover >[ 0 ][ 'range' ]; diff --git a/projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/use-comparison-date-presets.ts b/projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/use-comparison-date-presets.ts index 74acf7ac072d..1a018083db1c 100644 --- a/projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/use-comparison-date-presets.ts +++ b/projects/packages/premium-analytics/packages/ui/src/use-comparison-date-presets/use-comparison-date-presets.ts @@ -1,13 +1,12 @@ /** * External dependencies */ -import { useMemo } from 'react'; import { getComparisonRangeFromPreset, getComparisonPresetConfigs, type ComparisonPresetId, } from '@jetpack-premium-analytics/datetime'; - +import { useMemo } from 'react'; /** * Internal dependencies */ From f5dc81dd4488f632aeebb300986c8d126c9ce26f Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 3 Jun 2026 14:54:45 +0800 Subject: [PATCH 6/8] chore(premium-analytics): wire ui package deps and relax lint for the port --- pnpm-lock.yaml | 26 +++++++++++++++++-- .../premium-analytics/eslint.config.mjs | 16 ++++++++++++ .../packages/premium-analytics/package.json | 7 +++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39be62eeb6a7..8f2900069e6e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3829,12 +3829,21 @@ importers: '@automattic/number-formatters': specifier: workspace:* version: link:../../js-packages/number-formatters + '@automattic/ui': + specifier: 1.0.2 + version: 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@date-fns/tz': specifier: 1.4.1 version: 1.4.1 '@wordpress/boot': specifier: 0.13.0 version: 0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@wordpress/components': + specifier: 33.1.0 + version: 33.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@wordpress/compose': + specifier: 7.46.0 + version: 7.46.0(react@18.3.1) '@wordpress/data': specifier: 10.46.0 version: 10.46.0(react@18.3.1) @@ -3844,9 +3853,18 @@ importers: '@wordpress/icons': specifier: ^13.0.0 version: 13.1.0(react@18.3.1) + '@wordpress/private-apis': + specifier: 1.46.0 + version: 1.46.0 '@wordpress/route': specifier: 0.12.0 version: 0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@wordpress/ui': + specifier: 0.13.0 + version: 0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + clsx: + specifier: 2.1.1 + version: 2.1.1 date-fns: specifier: 4.1.0 version: 4.1.0 @@ -3866,9 +3884,12 @@ importers: '@typescript/native-preview': specifier: 7.0.0-dev.20260225.1 version: 7.0.0-dev.20260225.1 + '@wordpress/base-styles': + specifier: 8.0.0 + version: 8.0.0 '@wordpress/build': specifier: 0.14.0 - version: 0.14.0(@babel/core@7.29.0)(@wordpress/boot@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2) + version: 0.14.0(@babel/core@7.29.0)(@wordpress/boot@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/private-apis@1.46.0)(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2) browserslist: specifier: 4.28.2 version: 4.28.2 @@ -23985,7 +24006,7 @@ snapshots: - browserslist - supports-color - '@wordpress/build@0.14.0(@babel/core@7.29.0)(@wordpress/boot@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)': + '@wordpress/build@0.14.0(@babel/core@7.29.0)(@wordpress/boot@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/private-apis@1.46.0)(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)': dependencies: '@emotion/babel-plugin': 11.13.5 '@wordpress/style-runtime': 0.2.0 @@ -24005,6 +24026,7 @@ snapshots: sass-embedded: 1.97.3 optionalDependencies: '@wordpress/boot': 0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@wordpress/private-apis': 1.46.0 '@wordpress/route': 0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) transitivePeerDependencies: - '@babel/core' diff --git a/projects/packages/premium-analytics/eslint.config.mjs b/projects/packages/premium-analytics/eslint.config.mjs index 6b5bdb16906e..cf5c8fdf69ba 100644 --- a/projects/packages/premium-analytics/eslint.config.mjs +++ b/projects/packages/premium-analytics/eslint.config.mjs @@ -27,5 +27,21 @@ export default defineConfig( 'jsdoc/require-returns': 'off', 'jsdoc/check-indentation': 'off', }, + }, + { + // First UI package in the port: also soften JSDoc rules for the ui + // package and allow the upstream inline-handler JSX style. Temporary — + // tighten these up in a follow-up alongside datetime/formatters. + files: [ 'packages/ui/**' ], + rules: { + 'jsdoc/require-jsdoc': 'off', + 'jsdoc/require-description': 'off', + 'jsdoc/require-param': 'off', + 'jsdoc/require-param-description': 'off', + 'jsdoc/require-returns': 'off', + 'jsdoc/check-indentation': 'off', + 'jsdoc/escape-inline-tags': 'off', + 'react/jsx-no-bind': 'off', + }, } ); diff --git a/projects/packages/premium-analytics/package.json b/projects/packages/premium-analytics/package.json index e50feb5cb962..4e1ba3ad66c7 100644 --- a/projects/packages/premium-analytics/package.json +++ b/projects/packages/premium-analytics/package.json @@ -30,12 +30,18 @@ }, "dependencies": { "@automattic/number-formatters": "workspace:*", + "@automattic/ui": "1.0.2", "@date-fns/tz": "1.4.1", "@wordpress/boot": "0.13.0", + "@wordpress/components": "33.1.0", + "@wordpress/compose": "7.46.0", "@wordpress/data": "10.46.0", "@wordpress/i18n": "^6.9.0", "@wordpress/icons": "^13.0.0", + "@wordpress/private-apis": "1.46.0", "@wordpress/route": "0.12.0", + "@wordpress/ui": "0.13.0", + "clsx": "2.1.1", "date-fns": "4.1.0", "react": "18.3.1", "react-dom": "18.3.1" @@ -44,6 +50,7 @@ "@babel/core": "7.29.0", "@types/jest": "30.0.0", "@typescript/native-preview": "7.0.0-dev.20260225.1", + "@wordpress/base-styles": "8.0.0", "@wordpress/build": "0.14.0", "browserslist": "4.28.2" } From 224ba596249d550f2be6db6f7bb4c20d9e4a4431 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 3 Jun 2026 14:54:53 +0800 Subject: [PATCH 7/8] changelog: add entry for premium-analytics ui package port --- .../wooa7s-1318-integrate-components-package-into-analytics | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/packages/premium-analytics/changelog/wooa7s-1318-integrate-components-package-into-analytics diff --git a/projects/packages/premium-analytics/changelog/wooa7s-1318-integrate-components-package-into-analytics b/projects/packages/premium-analytics/changelog/wooa7s-1318-integrate-components-package-into-analytics new file mode 100644 index 000000000000..777a39c9cea4 --- /dev/null +++ b/projects/packages/premium-analytics/changelog/wooa7s-1318-integrate-components-package-into-analytics @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Port the components package (date range/comparison filter UI components and SCSS) from next-woocommerce-analytics as the internal `ui` package. From 1d4b764eaf687bb35a55d8b76234cdd50e77d1d5 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 3 Jun 2026 15:31:16 +0800 Subject: [PATCH 8/8] fix(premium-analytics): resolve stylelint errors in ui package scss --- .../date-comparison-dropdown.scss | 12 ++-- .../date-range-input/date-range-input.scss | 12 ++-- .../date-range-popover/date-range-filter.scss | 56 +++++++++---------- .../date-range-presets.scss | 16 +++--- 4 files changed, 49 insertions(+), 47 deletions(-) diff --git a/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/date-comparison-dropdown.scss b/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/date-comparison-dropdown.scss index a6f6f1909e14..29fea4bf18a3 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/date-comparison-dropdown.scss +++ b/projects/packages/premium-analytics/packages/ui/src/date-comparison-dropdown/date-comparison-dropdown.scss @@ -1,11 +1,12 @@ .date-comparison-dropdown { + &__button { - background-color: var( --wpds-color-bg-surface-neutral-strong ); + background-color: var(--wpds-color-bg-surface-neutral-strong); } } .date-filters-panel-button { - background-color: var( --wpds-color-bg-surface-neutral-strong ); + background-color: var(--wpds-color-bg-surface-neutral-strong); } .date-comparison-dropdown__popover { @@ -15,6 +16,7 @@ /* disable animation for the date range popover */ /* stylelint-disable property-no-unknown, selector-pseudo-element-no-unknown */ @media not ( prefers-reduced-motion: reduce ) { + .date-comparison-dropdown__popover { view-transition-name: next-admin--date-comparison-dropdown; transition: none !important; @@ -22,13 +24,13 @@ } /* ensure it's above the canvas/stage during the transition */ -::view-transition-group( next-admin--date-comparison-dropdown ) { +::view-transition-group(next-admin--date-comparison-dropdown) { z-index: 3000; } /* no animation for the snapshot (avoid "flashing") */ -::view-transition-new( next-admin--date-comparison-dropdown ), -::view-transition-old( next-admin--date-comparison-dropdown ) { +::view-transition-new(next-admin--date-comparison-dropdown), +::view-transition-old(next-admin--date-comparison-dropdown) { animation: none; } /* stylelint-enable property-no-unknown, selector-pseudo-element-no-unknown */ diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.scss b/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.scss index 8d74ebc150f4..f0ebd4c84d75 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.scss +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-input/date-range-input.scss @@ -1,9 +1,10 @@ .input-date-control { flex: 1; - font-size: var( --wpds-font-size-sm ); + font-size: var(--wpds-typography-font-size-sm); @supports selector( &::-webkit-calendar-picker-indicator ) { - input[type='date'] { + + input[type="date"] { // Removes extra spaces for the calendar icon. width: fit-content; padding-right: 0; @@ -12,7 +13,7 @@ -webkit-appearance: none; background: none; - font-size: var( --wpds-font-size-md ); + font-size: var(--wpds-typography-font-size-md); // Removes the calendar icon. &::-webkit-calendar-picker-indicator { @@ -22,14 +23,15 @@ } @supports not selector( &::-webkit-calendar-picker-indicator ) { - input[type='date'] { + + input[type="date"] { padding: 0; // We'll control input's inner spacing manually min-width: fit-content; // Prevent extra space on smaller screens // Use flex to center the input's content horizontally display: flex; - width: calc( 100% - 20px ); // Take almost all the space + width: calc(100% - 20px); // Take almost all the space margin-inline: auto; // Keep things centered } } diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.scss b/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.scss index 9eca700420e4..aa17958cb048 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.scss +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-popover/date-range-filter.scss @@ -1,8 +1,8 @@ -@use '@wordpress/base-styles/variables' as vars; -@use '@wordpress/base-styles/colors' as colors; +@use "@wordpress/base-styles/variables" as vars; +@use "@wordpress/base-styles/colors" as colors; .date-range-popover-content { - --wca-popover-padding: var( --wpds-dimension-padding-lg ); + --wca-popover-padding: var(--wpds-dimension-padding-lg); --wca-popover-border-color: #{colors.$gray-300}; --wca-popover-border-width: #{vars.$border-width}; @@ -11,9 +11,9 @@ grid-template-rows: 1fr auto; // Grid lines: background = line color, gap = line width - background-color: var( --wca-popover-border-color ); - column-gap: var( --wca-popover-border-width ); - row-gap: var( --wca-popover-border-width ); + background-color: var(--wca-popover-border-color); + column-gap: var(--wca-popover-border-width); + row-gap: var(--wca-popover-border-width); .date-range-calendar { display: flex; @@ -25,8 +25,8 @@ &--mobile { display: flex; flex-direction: column; - gap: var( --wpds-dimension-gap-lg ); - padding: var( --wca-popover-padding ); + gap: var(--wpds-dimension-gap-lg); + padding: var(--wca-popover-padding); background-color: #fff; .date-range-calendar { @@ -36,7 +36,7 @@ .date-range-popover-actions { padding: 0; - padding-block-start: var( --wpds-dimension-gap-sm ); + padding-block-start: var(--wpds-dimension-gap-sm); } } } @@ -44,11 +44,11 @@ .date-range-presets-wrapper { grid-row: 1; display: grid; - grid-template-columns: minmax( 0, max-content ) 1fr; + grid-template-columns: minmax(0, max-content) 1fr; background-color: #fff; - padding-bottom: var( --wpds-dimension-gap-sm ); - width: calc( 60 * var( --wpds-dimension-base ) ); - padding-right: var( --wpds-dimension-padding-sm ); + padding-bottom: var(--wpds-dimension-gap-sm); + width: calc(60 * var(--wpds-dimension-base)); + padding-right: var(--wpds-dimension-padding-sm); } .date-filters-panel-button { @@ -59,52 +59,48 @@ --wca-calendar-button-width: 32px; --wca-calendar-button-height: 32px; --wca-calendar-button-gap: 1rem; // consistent with automattic/ui style - --wca-calendar-padding: var( --wca-popover-padding ); - --wca-calendar-width: calc( - var( --wca-calendar-button-width ) * 7 + var( --wca-calendar-padding ) * 2 - ); - --wca-calendar-width-wide: calc( - var( --wca-calendar-button-width ) * 14 + var( --wca-calendar-padding ) * 2 + - var( --wca-calendar-button-gap ) - ); + --wca-calendar-padding: var(--wca-popover-padding); + --wca-calendar-width: calc(var(--wca-calendar-button-width) * 7 + var(--wca-calendar-padding) * 2); + --wca-calendar-width-wide: calc(var(--wca-calendar-button-width) * 14 + var(--wca-calendar-padding) * 2 + var(--wca-calendar-button-gap)); grid-row: 1; - padding: var( --wca-calendar-padding ); - width: var( --wca-calendar-width ); + padding: var(--wca-calendar-padding); + width: var(--wca-calendar-width); background-color: #fff; .date-range-calendar { - --a8c-calendar-button-width: var( --wca-calendar-button-width ); - --a8c-calendar-button-height: var( --wca-calendar-button-height ); + --a8c-calendar-button-width: var(--wca-calendar-button-width); + --a8c-calendar-button-height: var(--wca-calendar-button-height); } &__wide { - width: var( --wca-calendar-width-wide ); + width: var(--wca-calendar-width-wide); } } .date-range-popover-actions { grid-column: 1 / -1; - padding: calc( var( --wca-popover-padding ) / 2 ) var( --wca-popover-padding ); + padding: calc(var(--wca-popover-padding) / 2) var(--wca-popover-padding); background-color: #fff; } /* disable animation for the date range popover */ /* stylelint-disable property-no-unknown, selector-pseudo-element-no-unknown */ @media not ( prefers-reduced-motion: reduce ) { + .date-filters-panel__popover { view-transition-name: next-admin--date-range-popover; } } /* ensure it's above the canvas/stage during the transition */ -::view-transition-group( next-admin--date-range-popover ) { +::view-transition-group(next-admin--date-range-popover) { z-index: 3000; } /* no animation for the snapshot (avoid "flashing") */ -::view-transition-new( next-admin--date-range-popover ), -::view-transition-old( next-admin--date-range-popover ) { +::view-transition-new(next-admin--date-range-popover), +::view-transition-old(next-admin--date-range-popover) { animation: none; } /* stylelint-enable property-no-unknown, selector-pseudo-element-no-unknown */ diff --git a/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.scss b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.scss index 729a1d328ccd..446ea4a41439 100644 --- a/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.scss +++ b/projects/packages/premium-analytics/packages/ui/src/date-range-presets/date-range-presets.scss @@ -1,5 +1,5 @@ -@use '@wordpress/base-styles/variables' as vars; -@use '@wordpress/base-styles/colors' as colors; +@use "@wordpress/base-styles/variables" as vars; +@use "@wordpress/base-styles/colors" as colors; .date-range-presets { max-width: 240px; @@ -7,8 +7,9 @@ .date-range-presets, .date-range-presets__custom-group { + .date-range-presets__item { - min-height: var( --wpds-font-line-height-2xl ); + min-height: var(--wpds-typography-line-height-2xl); } } @@ -16,12 +17,13 @@ // Custom button acts as a label, not an interactive element. // Override disabled styles to show selection state visually. .date-range-presets__custom { - &[aria-disabled='true'] { - color: var( --wpds-color-fg-content-neutral-weak ); + + &[aria-disabled="true"] { + color: var(--wpds-color-fg-content-neutral-weak); } - &[aria-checked='true'] { - color: var( --wpds-color-fg-content-neutral ); + &[aria-checked="true"] { + color: var(--wpds-color-fg-content-neutral); } } }