From edd4ef4d93783bc573efe90b74f24b03654847a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:23:47 +0000 Subject: [PATCH 1/3] Initial plan From 987e0a6957a0e8da21f367cc9d0de1bc7b0d3dde Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:35:21 +0000 Subject: [PATCH 2/3] feat: merge ListView toolbar rows and add UserFilters maxVisible collapse - Add maxVisible prop to UserFilters for badge overflow into "More" popover - Merge UserFilters row into toolbar (left: badges, right: tool buttons) - Change Search from inline input to icon button + Popover - Remove separate UserFilters row from ListView - Add 6 new UserFilters collapse behavior tests - Add 3 new merged toolbar layout tests - Update 7 existing search-related tests for new icon+popover pattern - All 148 tests pass (33 UserFilters + 115 ListView) Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- packages/plugin-list/src/ListView.tsx | 102 ++++++---- packages/plugin-list/src/UserFilters.tsx | 175 +++++++++++------- .../src/__tests__/ListView.test.tsx | 95 ++++++++-- .../src/__tests__/UserFilters.test.tsx | 93 ++++++++++ 4 files changed, 341 insertions(+), 124 deletions(-) diff --git a/packages/plugin-list/src/ListView.tsx b/packages/plugin-list/src/ListView.tsx index 287cab708..98a82fd97 100644 --- a/packages/plugin-list/src/ListView.tsx +++ b/packages/plugin-list/src/ListView.tsx @@ -287,6 +287,7 @@ export const ListView: React.FC = ({ (schema.viewType as ViewType) ); const [searchTerm, setSearchTerm] = React.useState(''); + const [showSearchPopover, setShowSearchPopover] = React.useState(false); // Sort State const [showSort, setShowSort] = React.useState(false); @@ -1016,35 +1017,23 @@ export const ListView: React.FC = ({ )} - {/* Airtable-style Toolbar — Row 2: Tool buttons */} + {/* Airtable-style Toolbar — Merged: UserFilter badges (left) + Tool buttons (right) */}
- {/* Search (left end — Airtable-style) */} - {toolbarFlags.showSearch && ( -
- - handleSearchChange(e.target.value)} - className="pl-7 h-7 text-xs bg-muted/50 border-transparent hover:bg-muted focus:bg-background focus:border-input transition-colors" - /> - {searchTerm && ( - - )} -
- )} - - {/* --- Separator: Search | Fields --- */} - {toolbarFlags.showSearch && toolbarFlags.showHideFields && ( -
+ {/* User Filters — inline in toolbar (Airtable Interfaces-style) */} + {resolvedUserFilters && ( + <> +
+ +
+
+ )} {/* Hide Fields */} @@ -1387,6 +1376,53 @@ export const ListView: React.FC = ({ Print )} + + {/* --- Separator: Print | Search --- */} + {toolbarFlags.showSearch && (schema.allowPrinting || (schema.sharing?.enabled || schema.sharing?.type) || (resolvedExportOptions && schema.allowExport !== false)) && ( +
+ )} + + {/* Search (icon button + popover) */} + {toolbarFlags.showSearch && ( + + + + + +
+ + handleSearchChange(e.target.value)} + className="pl-7 h-8 text-xs" + autoFocus + /> + {searchTerm && ( + + )} +
+
+
+ )}
{/* Right: Add Record */} @@ -1436,18 +1472,6 @@ export const ListView: React.FC = ({
)} - {/* User Filters Row (Airtable Interfaces-style) */} - {resolvedUserFilters && ( -
- -
- )} - {/* View Content */}
{!loading && data.length === 0 ? ( diff --git a/packages/plugin-list/src/UserFilters.tsx b/packages/plugin-list/src/UserFilters.tsx index 1f069c460..79599f838 100644 --- a/packages/plugin-list/src/UserFilters.tsx +++ b/packages/plugin-list/src/UserFilters.tsx @@ -37,6 +37,8 @@ export interface UserFiltersProps { data?: any[]; /** Callback when filter state changes */ onFilterChange: (filters: any[]) => void; + /** Maximum visible filter badges before collapsing into "More" dropdown (dropdown mode only) */ + maxVisible?: number; className?: string; } @@ -53,6 +55,7 @@ export function UserFilters({ objectDef, data = [], onFilterChange, + maxVisible, className, }: UserFiltersProps) { switch (config.element) { @@ -63,6 +66,7 @@ export function UserFilters({ objectDef={objectDef} data={data} onFilterChange={onFilterChange} + maxVisible={maxVisible} className={className} /> ); @@ -138,10 +142,11 @@ interface DropdownFiltersProps { objectDef?: any; data: any[]; onFilterChange: (filters: any[]) => void; + maxVisible?: number; className?: string; } -function DropdownFilters({ fields, objectDef, data, onFilterChange, className }: DropdownFiltersProps) { +function DropdownFilters({ fields, objectDef, data, onFilterChange, maxVisible, className }: DropdownFiltersProps) { const [selectedValues, setSelectedValues] = React.useState< Record >(() => { @@ -182,6 +187,92 @@ function DropdownFilters({ fields, objectDef, data, onFilterChange, className }: // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // Split fields into visible and overflow based on maxVisible + const visibleFields = maxVisible !== undefined && maxVisible < resolvedFields.length + ? resolvedFields.slice(0, maxVisible) + : resolvedFields; + const overflowFields = maxVisible !== undefined && maxVisible < resolvedFields.length + ? resolvedFields.slice(maxVisible) + : []; + + const renderBadge = (f: ResolvedField) => { + const selected = selectedValues[f.field] || []; + const hasSelection = selected.length > 0; + + return ( + + + + + +
+ {f.options.map(opt => { + const selected = selectedValues[f.field] || []; + return ( + + ); + })} +
+
+
+ ); + }; + return (
@@ -190,80 +281,30 @@ function DropdownFilters({ fields, objectDef, data, onFilterChange, className }: No filter fields ) : ( - resolvedFields.map(f => { - const selected = selectedValues[f.field] || []; - const hasSelection = selected.length > 0; - - return ( - + <> + {visibleFields.map(renderBadge)} + {overflowFields.length > 0 && ( + - -
- {f.options.map(opt => ( - - ))} + +
+ {overflowFields.map(renderBadge)}
- ); - }) + )} + )} )} - {/* --- Separator: Print | Search --- */} - {toolbarFlags.showSearch && (schema.allowPrinting || (schema.sharing?.enabled || schema.sharing?.type) || (resolvedExportOptions && schema.allowExport !== false)) && ( -
- )} + {/* --- Separator: Print/Share/Export | Search --- */} + {(() => { + const hasLeftSideItems = schema.allowPrinting || (schema.sharing?.enabled || schema.sharing?.type) || (resolvedExportOptions && schema.allowExport !== false); + return toolbarFlags.showSearch && hasLeftSideItems ? ( +
+ ) : null; + })()} {/* Search (icon button + popover) */} {toolbarFlags.showSearch && ( diff --git a/packages/plugin-list/src/UserFilters.tsx b/packages/plugin-list/src/UserFilters.tsx index 79599f838..819bfb280 100644 --- a/packages/plugin-list/src/UserFilters.tsx +++ b/packages/plugin-list/src/UserFilters.tsx @@ -233,9 +233,7 @@ function DropdownFilters({ fields, objectDef, data, onFilterChange, maxVisible,
- {f.options.map(opt => { - const selected = selectedValues[f.field] || []; - return ( + {f.options.map(opt => ( - ); - })} + ))}