From df7f7b8b285dae9545fb20be8863740f07045d47 Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Tue, 2 Dec 2025 10:41:57 +0100 Subject: [PATCH 1/9] Analytics filter refinements ref https://linear.app/ghost/issue/NY-768/analytics-filter-ui-refinements --- apps/shade/src/components/ui/filters.tsx | 8 +++--- apps/shade/src/components/ui/pagemenu.tsx | 12 ++++----- .../views/Stats/components/stats-filter.tsx | 15 ++++++----- .../src/views/Stats/layout/stats-header.tsx | 26 ++++++++++++++----- 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/apps/shade/src/components/ui/filters.tsx b/apps/shade/src/components/ui/filters.tsx index a85749b5be3..b84437ed8a1 100644 --- a/apps/shade/src/components/ui/filters.tsx +++ b/apps/shade/src/components/ui/filters.tsx @@ -102,7 +102,7 @@ export interface FilterI18nConfig { // Default English i18n configuration export const DEFAULT_I18N: FilterI18nConfig = { // UI Labels - addFilter: 'Add filter', + addFilter: '', searchFields: 'Search fields...', noFieldsFound: 'No fields found.', noResultsFound: 'No results found.', @@ -119,7 +119,7 @@ export const DEFAULT_I18N: FilterI18nConfig = { percent: '%', defaultCurrency: '$', defaultColor: '#000000', - addFilterTitle: 'Add filter', + addFilterTitle: '', // Operators operators: { @@ -236,7 +236,7 @@ const filterInputVariants = cva( size: { lg: 'h-10 px-2.5 text-sm has-[[data-slot=filters-prefix]]:ps-0 has-[[data-slot=filters-suffix]]:pe-0', md: 'h-[34px] px-2 text-sm has-[[data-slot=filters-prefix]]:ps-0 has-[[data-slot=filters-suffix]]:pe-0', - sm: 'h-8 px-1.5 text-xs has-[[data-slot=filters-prefix]]:ps-0 has-[[data-slot=filters-suffix]]:pe-0' + sm: 'h-8 px-2 text-xs has-[[data-slot=filters-prefix]]:ps-0 has-[[data-slot=filters-suffix]]:pe-0' }, cursorPointer: { true: 'cursor-pointer', @@ -941,7 +941,7 @@ function FilterOperatorDropdown({field, operator, values, onChange} // If hideOperatorSelect is true, just render the operator as plain text if (field.hideOperatorSelect) { return ( -
+
{operatorLabel}
); diff --git a/apps/shade/src/components/ui/pagemenu.tsx b/apps/shade/src/components/ui/pagemenu.tsx index 842367c685a..ab41f7e218e 100644 --- a/apps/shade/src/components/ui/pagemenu.tsx +++ b/apps/shade/src/components/ui/pagemenu.tsx @@ -48,7 +48,7 @@ const PageMenu = React.forwardRef( const measureContainer = measureRef.current; const containerWidth = container.clientWidth; const dropdownWidth = 60; // Space needed for dropdown button - const gap = 6; // Gap between items (gap-1.5) + const gap = 8; // Gap between items (gap-2) // Get all child elements from the measure container const items = Array.from(measureContainer.children) as HTMLElement[]; @@ -142,9 +142,9 @@ const PageMenu = React.forwardRef( {/* Visible container */}
-
+
{visibleItems}
@@ -208,12 +208,12 @@ const PageMenuItem = React.forwardRef(
- + { navigate('/analytics/'); - }}>Overview + }}> + + Overview + {appSettings?.analytics.webAnalytics && { navigate('/analytics/web/'); - }}>Web traffic + }}> + + Web traffic + } {appSettings?.newslettersEnabled && { navigate('/analytics/newsletters/'); - }}>Newsletters + }}> + + Newsletters + } { navigate('/analytics/growth/'); - }}>Growth + }}> + + Growth + - {appSettings?.analytics.webAnalytics && ( + {/* {appSettings?.analytics.webAnalytics && ( { navigate('/analytics/locations/'); }}>Locations - )} + )} */} {children} From a833c8f44cee74df854b169b4e13d169d923bd2b Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Tue, 2 Dec 2025 11:33:27 +0100 Subject: [PATCH 2/9] Removed unrelated changes --- apps/shade/src/components/ui/pagemenu.tsx | 12 +++++----- .../src/views/Stats/layout/stats-header.tsx | 24 +++++-------------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/apps/shade/src/components/ui/pagemenu.tsx b/apps/shade/src/components/ui/pagemenu.tsx index ab41f7e218e..842367c685a 100644 --- a/apps/shade/src/components/ui/pagemenu.tsx +++ b/apps/shade/src/components/ui/pagemenu.tsx @@ -48,7 +48,7 @@ const PageMenu = React.forwardRef( const measureContainer = measureRef.current; const containerWidth = container.clientWidth; const dropdownWidth = 60; // Space needed for dropdown button - const gap = 8; // Gap between items (gap-2) + const gap = 6; // Gap between items (gap-1.5) // Get all child elements from the measure container const items = Array.from(measureContainer.children) as HTMLElement[]; @@ -142,9 +142,9 @@ const PageMenu = React.forwardRef( {/* Visible container */}
-
+
{visibleItems}
@@ -208,12 +208,12 @@ const PageMenuItem = React.forwardRef( )} - + {selectedFieldForOptions ? ( // Show original select/multiselect rendering without back button // SelectOptionsPopover renders its own Command component when inline={true} diff --git a/apps/stats/src/views/Stats/components/stats-filter.tsx b/apps/stats/src/views/Stats/components/stats-filter.tsx index 47a988bb3ff..ff97643a0cf 100644 --- a/apps/stats/src/views/Stats/components/stats-filter.tsx +++ b/apps/stats/src/views/Stats/components/stats-filter.tsx @@ -460,6 +460,8 @@ function StatsFilter({filters, utmTrackingEnabled = false, onChange, ...props}: onSearchChange: setSearchQuery, operators: supportedOperators, defaultOperator: 'is', + className: 'w-80', + popoverContentClassName: 'w-80', hideOperatorSelect: true }, { From cee1023d9062e40c56b55ff5cfb2363366ac040a Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Tue, 2 Dec 2025 12:07:27 +0100 Subject: [PATCH 4/9] Updated icon --- apps/stats/src/views/Stats/components/stats-filter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/stats/src/views/Stats/components/stats-filter.tsx b/apps/stats/src/views/Stats/components/stats-filter.tsx index ff97643a0cf..431ee13a2f5 100644 --- a/apps/stats/src/views/Stats/components/stats-filter.tsx +++ b/apps/stats/src/views/Stats/components/stats-filter.tsx @@ -452,7 +452,7 @@ function StatsFilter({filters, utmTrackingEnabled = false, onChange, ...props}: key: 'post', label: 'Post or page', type: 'select', - icon: , + icon: , options: postOptions, searchable: true, asyncSearch: true, From 4febc95902338c540f254be1aa6a560733c6c9f6 Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Tue, 2 Dec 2025 12:54:05 +0100 Subject: [PATCH 5/9] Updated badge style --- .../views/Stats/components/stats-filter.tsx | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/apps/stats/src/views/Stats/components/stats-filter.tsx b/apps/stats/src/views/Stats/components/stats-filter.tsx index 431ee13a2f5..eeaab6a35c2 100644 --- a/apps/stats/src/views/Stats/components/stats-filter.tsx +++ b/apps/stats/src/views/Stats/components/stats-filter.tsx @@ -90,7 +90,7 @@ const useUtmOptionsForField = (fieldKey: string, currentFilters: Filter[] = []) value: value, // Add a custom icon element that shows the count badge icon: ( - + {visits.toLocaleString()} ) @@ -156,7 +156,7 @@ const useSourceOptions = (currentFilters: Filter[] = []) => { value, // Add a custom icon element that shows the count badge icon: ( - + {visits.toLocaleString()} ) @@ -383,7 +383,8 @@ function StatsFilter({filters, utmTrackingEnabled = false, onChange, ...props}: defaultOperator: 'is', hideOperatorSelect: true, options: utmSourceOptions, - searchable: true + searchable: true, + selectedOptionsClassName: 'hidden' }, { key: 'utm_medium', @@ -395,7 +396,8 @@ function StatsFilter({filters, utmTrackingEnabled = false, onChange, ...props}: defaultOperator: 'is', hideOperatorSelect: true, options: utmMediumOptions, - searchable: true + searchable: true, + selectedOptionsClassName: 'hidden' }, { key: 'utm_campaign', @@ -407,7 +409,8 @@ function StatsFilter({filters, utmTrackingEnabled = false, onChange, ...props}: defaultOperator: 'is', hideOperatorSelect: true, options: utmCampaignOptions, - searchable: true + searchable: true, + selectedOptionsClassName: 'hidden' }, { key: 'utm_content', @@ -419,7 +422,8 @@ function StatsFilter({filters, utmTrackingEnabled = false, onChange, ...props}: defaultOperator: 'is', hideOperatorSelect: true, options: utmContentOptions, - searchable: true + searchable: true, + selectedOptionsClassName: 'hidden' }, { key: 'utm_term', @@ -431,7 +435,8 @@ function StatsFilter({filters, utmTrackingEnabled = false, onChange, ...props}: defaultOperator: 'is', hideOperatorSelect: true, options: utmTermOptions, - searchable: true + searchable: true, + selectedOptionsClassName: 'hidden' } ] : []; @@ -474,7 +479,8 @@ function StatsFilter({filters, utmTrackingEnabled = false, onChange, ...props}: defaultOperator: 'is', hideOperatorSelect: true, options: sourceOptions, - searchable: true + searchable: true, + selectedOptionsClassName: 'hidden' } ] }, From a9353a1283de138dd2f7a21584c1403dfd394355 Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Tue, 2 Dec 2025 13:18:37 +0100 Subject: [PATCH 6/9] Added auto close popup --- apps/shade/src/components/ui/filters.tsx | 12 ++++++++++++ .../src/views/Stats/components/stats-filter.tsx | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/shade/src/components/ui/filters.tsx b/apps/shade/src/components/ui/filters.tsx index 0c85392f0f0..8dd5279247b 100644 --- a/apps/shade/src/components/ui/filters.tsx +++ b/apps/shade/src/components/ui/filters.tsx @@ -747,6 +747,8 @@ export interface FilterFieldConfig { // Controlled values support for this field value?: T[]; onValueChange?: (values: T[]) => void; + // Auto-close dropdown after selection (even for multiselect types) + autoCloseOnSelect?: boolean; } // Helper functions to handle both flat and grouped field configurations @@ -1046,6 +1048,7 @@ function SelectOptionsPopover({ const handleClose = () => { setOpen(false); + setTimeout(() => setSearchInput(''), 200); onClose?.(); }; @@ -1128,6 +1131,10 @@ function SelectOptionsPopover({ } else { onChange(newValues); } + // Auto-close if configured + if (field.autoCloseOnSelect) { + onClose?.(); + } // For multiselect, don't close the popover to allow multiple selections } else { if (field.onValueChange) { @@ -1255,6 +1262,11 @@ function SelectOptionsPopover({ return; // Don't exceed max selections } onChange(newValues); + // Auto-close if configured + if (field.autoCloseOnSelect) { + setOpen(false); + handleClose(); + } } else { onChange([option.value] as T[]); setOpen(false); diff --git a/apps/stats/src/views/Stats/components/stats-filter.tsx b/apps/stats/src/views/Stats/components/stats-filter.tsx index eeaab6a35c2..aaa41cfdb10 100644 --- a/apps/stats/src/views/Stats/components/stats-filter.tsx +++ b/apps/stats/src/views/Stats/components/stats-filter.tsx @@ -451,7 +451,8 @@ function StatsFilter({filters, utmTrackingEnabled = false, onChange, ...props}: icon: , options: audienceOptions.map(({value, label, icon}) => ({value, label, icon})), defaultOperator: 'is any of', - hideOperatorSelect: true + hideOperatorSelect: true, + autoCloseOnSelect: true }, { key: 'post', From af1a59fc60f9556723eb9b1f03b0369c997fff56 Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Tue, 2 Dec 2025 13:41:12 +0100 Subject: [PATCH 7/9] Fixed bug --- apps/shade/src/components/ui/filters.tsx | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/shade/src/components/ui/filters.tsx b/apps/shade/src/components/ui/filters.tsx index 8dd5279247b..6b5071b324b 100644 --- a/apps/shade/src/components/ui/filters.tsx +++ b/apps/shade/src/components/ui/filters.tsx @@ -1198,7 +1198,13 @@ function SelectOptionsPopover({ )}
- + {field.searchable !== false && ( ({ )} - + {selectedFieldForOptions ? ( // Show original select/multiselect rendering without back button // SelectOptionsPopover renders its own Command component when inline={true} @@ -2085,7 +2097,11 @@ export function Filters({ const shouldClosePopover = selectedFieldForOptions.type === 'select'; addFilterWithOption(selectedFieldForOptions, values as unknown[], shouldClosePopover); }} - onClose={() => setAddFilterOpen(false)} + onClose={() => { + setAddFilterOpen(false); + setSelectedFieldKeyForOptions(null); + setTempSelectedValues([]); + }} /> ) : ( // Show field selection - needs Command wrapper for search/list From 97f93695900c0988776df6221654f59a5ea679ee Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Tue, 2 Dec 2025 13:46:51 +0100 Subject: [PATCH 8/9] Removed animation on popover close --- apps/shade/src/components/ui/filters.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/shade/src/components/ui/filters.tsx b/apps/shade/src/components/ui/filters.tsx index 6b5071b324b..15dd9c7addc 100644 --- a/apps/shade/src/components/ui/filters.tsx +++ b/apps/shade/src/components/ui/filters.tsx @@ -1201,7 +1201,7 @@ function SelectOptionsPopover({ @@ -1643,7 +1643,7 @@ function FilterValueSelector({field, values, onChange, operator}: F )}
- + {field.searchable !== false && ( ({ From 998a7a823d8ac98a24e0f115acd7d45d5625596d Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Tue, 2 Dec 2025 13:49:16 +0100 Subject: [PATCH 9/9] Rabbit fix --- apps/shade/src/components/ui/filters.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/shade/src/components/ui/filters.tsx b/apps/shade/src/components/ui/filters.tsx index 15dd9c7addc..65a60280f7e 100644 --- a/apps/shade/src/components/ui/filters.tsx +++ b/apps/shade/src/components/ui/filters.tsx @@ -1270,7 +1270,6 @@ function SelectOptionsPopover({ onChange(newValues); // Auto-close if configured if (field.autoCloseOnSelect) { - setOpen(false); handleClose(); } } else {