Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ All 4 phases complete across 5 designers (Page, View, DataModel, Process, Report
- [x] Implement `emptyState` spec property (custom no-data UI — critical for UX)
- [x] Implement `hiddenFields` and `fieldOrder` spec properties (view customization)
- [x] Implement `quickFilters` spec property (predefined filter buttons)
- [x] Implement `userFilters` spec property — Airtable Interfaces-style user filters (dropdown / tabs / toggle modes)

### P1. Spec Compliance — UI-Facing 📐

Expand Down
21 changes: 19 additions & 2 deletions packages/plugin-list/src/ListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { SortItem } from '@object-ui/components';
import { Search, SlidersHorizontal, ArrowUpDown, X, EyeOff, Group, Paintbrush, Ruler, Inbox, Download, AlignJustify, Share2, icons, type LucideIcon } from 'lucide-react';
import type { FilterGroup } from '@object-ui/components';
import { ViewSwitcher, ViewType } from './ViewSwitcher';
import { UserFilters } from './UserFilters';
import { SchemaRenderer, useNavigationOverlay } from '@object-ui/react';
import { useDensityMode } from '@object-ui/react';
import type { ListViewSchema } from '@object-ui/types';
Expand Down Expand Up @@ -233,6 +234,9 @@ export const ListView: React.FC<ListViewProps> = ({
return defaults;
});

// User Filters State (Airtable Interfaces-style)
const [userFilterConditions, setUserFilterConditions] = React.useState<any[]>([]);

// Hidden Fields State (initialized from schema)
const [hiddenFields, setHiddenFields] = React.useState<Set<string>>(
() => new Set(schema.hiddenFields || [])
Expand Down Expand Up @@ -314,11 +318,12 @@ export const ListView: React.FC<ListViewProps> = ({
});
}

// Merge base filters, user filters, and quick filters
// Merge base filters, user filters, quick filters, and user filter bar conditions
const allFilters = [
...(baseFilter.length > 0 ? [baseFilter] : []),
...(userFilter.length > 0 ? [userFilter] : []),
...quickFilterConditions,
...userFilterConditions,
];
Comment on lines +321 to 327
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment on line 321 could be more descriptive about what each filter source represents:

  • baseFilter (schema.filters): Static filters defined in the schema
  • userFilter (currentFilters): Filters from the FilterBuilder UI (advanced filter panel)
  • quickFilterConditions: Filters from toggle-style quick filter buttons
  • userFilterConditions: NEW - Filters from Airtable-style user filter bar (dropdown/tabs/toggle)

Consider updating the comment to clarify these distinctions, especially since userFilters is a new addition. For example:

// Merge all filter sources:
// 1. Base filters (schema.filters) - static schema-level filters
// 2. FilterBuilder (currentFilters) - advanced filter UI
// 3. Quick filters (schema.quickFilters) - toggle buttons
// 4. User filters (schema.userFilters) - Airtable-style dropdown/tabs/toggle

This helps future developers understand the complete filter pipeline.

Copilot uses AI. Check for mistakes.

if (allFilters.length > 1) {
Expand Down Expand Up @@ -365,7 +370,7 @@ export const ListView: React.FC<ListViewProps> = ({
fetchData();

return () => { isMounted = false; };
}, [schema.objectName, dataSource, schema.filters, currentSort, currentFilters, activeQuickFilters, refreshKey]); // Re-fetch on filter/sort change
}, [schema.objectName, dataSource, schema.filters, currentSort, currentFilters, activeQuickFilters, userFilterConditions, refreshKey]); // Re-fetch on filter/sort change

// Available view types based on schema configuration
const availableViews = React.useMemo(() => {
Expand Down Expand Up @@ -964,6 +969,18 @@ export const ListView: React.FC<ListViewProps> = ({
</div>
)}

{/* User Filters Row (Airtable Interfaces-style) */}
{schema.userFilters && (
<div className="border-b px-2 sm:px-4 py-1 bg-background" data-testid="user-filters">
<UserFilters
config={schema.userFilters}
objectDef={objectDef}
data={data}
onFilterChange={setUserFilterConditions}
/>
</div>
)}
Comment on lines +972 to +982
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The userFilters feature lacks integration tests with ListView. While UserFilters.test.tsx comprehensively tests the component in isolation, there are no tests verifying:

  1. The filters are properly rendered in the ListView toolbar
  2. Filter changes trigger data refetching with correct filter conditions
  3. userFilters work alongside existing quickFilters and FilterBuilder
  4. The data dependency (passing filtered data for counts) works correctly

Consider adding integration tests in ListView.test.tsx similar to the existing quickFilters tests (line 265-282), such as:

it('should render user filters when configured', () => {
  const schema: ListViewSchema = {
    type: 'list-view',
    objectName: 'contacts',
    userFilters: {
      element: 'dropdown',
      fields: [{ field: 'status', label: 'Status' }]
    }
  };
  renderWithProvider(<ListView schema={schema} />);
  expect(screen.getByTestId('user-filters')).toBeInTheDocument();
});

Copilot uses AI. Check for mistakes.

{/* View Content */}
<div key={currentView} className="flex-1 min-h-0 bg-background relative overflow-hidden animate-in fade-in-0 duration-200">
{!loading && data.length === 0 ? (
Expand Down
Loading