@object-ui/plugin-list 3.1.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +12 -0
- package/dist/index.js +9958 -9934
- package/dist/index.umd.cjs +21 -21
- package/dist/plugin-list.css +1 -1
- package/dist/src/ListView.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/ListView.tsx +25 -11
- package/src/UserFilters.tsx +0 -8
- package/src/__tests__/ListView.test.tsx +64 -2
- package/src/__tests__/UserFilters.test.tsx +3 -11
package/src/ListView.tsx
CHANGED
|
@@ -557,7 +557,16 @@ export const ListView: React.FC<ListViewProps> = ({
|
|
|
557
557
|
if (schema.data && typeof schema.data === 'object' && !Array.isArray(schema.data)) {
|
|
558
558
|
const dataConfig = schema.data as any;
|
|
559
559
|
if (dataConfig.provider === 'value' && Array.isArray(dataConfig.items)) {
|
|
560
|
-
|
|
560
|
+
let items = dataConfig.items;
|
|
561
|
+
if (searchTerm) {
|
|
562
|
+
const q = searchTerm.toLowerCase();
|
|
563
|
+
items = items.filter((row: any) =>
|
|
564
|
+
Object.values(row).some(
|
|
565
|
+
(v) => v != null && String(v).toLowerCase().includes(q),
|
|
566
|
+
),
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
setData(items);
|
|
561
570
|
setLoading(false);
|
|
562
571
|
setDataLimitReached(false);
|
|
563
572
|
return;
|
|
@@ -565,7 +574,16 @@ export const ListView: React.FC<ListViewProps> = ({
|
|
|
565
574
|
}
|
|
566
575
|
// Also support schema.data as a plain array (shorthand for value provider)
|
|
567
576
|
if (Array.isArray(schema.data)) {
|
|
568
|
-
|
|
577
|
+
let items = schema.data as any[];
|
|
578
|
+
if (searchTerm) {
|
|
579
|
+
const q = searchTerm.toLowerCase();
|
|
580
|
+
items = items.filter((row: any) =>
|
|
581
|
+
Object.values(row).some(
|
|
582
|
+
(v) => v != null && String(v).toLowerCase().includes(q),
|
|
583
|
+
),
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
setData(items);
|
|
569
587
|
setLoading(false);
|
|
570
588
|
setDataLimitReached(false);
|
|
571
589
|
return;
|
|
@@ -1071,12 +1089,11 @@ export const ListView: React.FC<ListViewProps> = ({
|
|
|
1071
1089
|
</div>
|
|
1072
1090
|
)}
|
|
1073
1091
|
|
|
1074
|
-
{/* Airtable-style Toolbar —
|
|
1092
|
+
{/* Airtable-style Toolbar — UserFilter badges (left) + Tool buttons (right) */}
|
|
1075
1093
|
<div className="border-b px-2 sm:px-4 py-1 flex items-center justify-between gap-1 sm:gap-2 bg-background">
|
|
1076
|
-
<div className="flex items-center gap-0.5 overflow-x-auto
|
|
1094
|
+
<div className="flex items-center gap-0.5 overflow-x-auto min-w-0">
|
|
1077
1095
|
{/* User Filters — inline in toolbar (Airtable Interfaces-style) */}
|
|
1078
1096
|
{resolvedUserFilters && (
|
|
1079
|
-
<>
|
|
1080
1097
|
<div className="shrink-0 min-w-0" data-testid="user-filters">
|
|
1081
1098
|
<UserFilters
|
|
1082
1099
|
config={resolvedUserFilters}
|
|
@@ -1086,10 +1103,10 @@ export const ListView: React.FC<ListViewProps> = ({
|
|
|
1086
1103
|
maxVisible={3}
|
|
1087
1104
|
/>
|
|
1088
1105
|
</div>
|
|
1089
|
-
<div className="h-4 w-px bg-border/60 mx-0.5 shrink-0" />
|
|
1090
|
-
</>
|
|
1091
1106
|
)}
|
|
1107
|
+
</div>
|
|
1092
1108
|
|
|
1109
|
+
<div className="flex items-center gap-0.5 shrink-0">
|
|
1093
1110
|
{/* Hide Fields */}
|
|
1094
1111
|
{toolbarFlags.showHideFields && (
|
|
1095
1112
|
<Popover open={showHideFields} onOpenChange={setShowHideFields}>
|
|
@@ -1456,7 +1473,7 @@ export const ListView: React.FC<ListViewProps> = ({
|
|
|
1456
1473
|
<Search className="h-3.5 w-3.5" />
|
|
1457
1474
|
</Button>
|
|
1458
1475
|
</PopoverTrigger>
|
|
1459
|
-
<PopoverContent align="end" className="w-64 p-2" data-testid="search-popover">
|
|
1476
|
+
<PopoverContent align="end" className="w-[calc(100vw-2rem)] sm:w-64 p-2" data-testid="search-popover">
|
|
1460
1477
|
<div className="relative">
|
|
1461
1478
|
<Search className="absolute left-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
|
|
1462
1479
|
<Input
|
|
@@ -1480,10 +1497,7 @@ export const ListView: React.FC<ListViewProps> = ({
|
|
|
1480
1497
|
</PopoverContent>
|
|
1481
1498
|
</Popover>
|
|
1482
1499
|
)}
|
|
1483
|
-
</div>
|
|
1484
1500
|
|
|
1485
|
-
{/* Right: Add Record */}
|
|
1486
|
-
<div className="flex items-center gap-1">
|
|
1487
1501
|
{/* Add Record (top position) */}
|
|
1488
1502
|
{toolbarFlags.showAddRecord && toolbarFlags.addRecordPosition === 'top' && (
|
|
1489
1503
|
<Button
|
package/src/UserFilters.tsx
CHANGED
|
@@ -303,14 +303,6 @@ function DropdownFilters({ fields, objectDef, data, onFilterChange, maxVisible,
|
|
|
303
303
|
)}
|
|
304
304
|
</>
|
|
305
305
|
)}
|
|
306
|
-
<button
|
|
307
|
-
className="inline-flex items-center gap-1 h-7 px-2 text-xs text-muted-foreground hover:text-foreground hover:bg-muted rounded-md transition-colors shrink-0"
|
|
308
|
-
data-testid="user-filters-add"
|
|
309
|
-
title="Add filter"
|
|
310
|
-
>
|
|
311
|
-
<Plus className="h-3.5 w-3.5" />
|
|
312
|
-
<span className="hidden sm:inline">Add filter</span>
|
|
313
|
-
</button>
|
|
314
306
|
</div>
|
|
315
307
|
);
|
|
316
308
|
}
|
|
@@ -540,7 +540,7 @@ describe('ListView', () => {
|
|
|
540
540
|
expect(screen.getByTestId('filter-badge-is_active')).toBeInTheDocument();
|
|
541
541
|
});
|
|
542
542
|
|
|
543
|
-
it('should show Add filter button in userFilters', () => {
|
|
543
|
+
it('should not show Add filter button in userFilters (removed from UI)', () => {
|
|
544
544
|
const schema: ListViewSchema = {
|
|
545
545
|
type: 'list-view',
|
|
546
546
|
objectName: 'contacts',
|
|
@@ -555,7 +555,7 @@ describe('ListView', () => {
|
|
|
555
555
|
};
|
|
556
556
|
|
|
557
557
|
renderWithProvider(<ListView schema={schema} />);
|
|
558
|
-
expect(screen.
|
|
558
|
+
expect(screen.queryByTestId('user-filters-add')).not.toBeInTheDocument();
|
|
559
559
|
});
|
|
560
560
|
|
|
561
561
|
it('should not render userFilters when objectDef has no filterable fields', async () => {
|
|
@@ -1577,6 +1577,68 @@ describe('ListView', () => {
|
|
|
1577
1577
|
expect(mockDataSource.find).not.toHaveBeenCalled();
|
|
1578
1578
|
});
|
|
1579
1579
|
|
|
1580
|
+
it('should filter inline array data by searchTerm', async () => {
|
|
1581
|
+
const schema: ListViewSchema = {
|
|
1582
|
+
type: 'list-view',
|
|
1583
|
+
objectName: 'contacts',
|
|
1584
|
+
viewType: 'grid',
|
|
1585
|
+
fields: ['name', 'email'],
|
|
1586
|
+
data: [
|
|
1587
|
+
{ _id: '1', name: 'Alice', email: 'alice@test.com' },
|
|
1588
|
+
{ _id: '2', name: 'Bob', email: 'bob@test.com' },
|
|
1589
|
+
{ _id: '3', name: 'Charlie', email: 'charlie@test.com' },
|
|
1590
|
+
] as any,
|
|
1591
|
+
};
|
|
1592
|
+
|
|
1593
|
+
mockDataSource.find.mockClear();
|
|
1594
|
+
renderWithProvider(<ListView schema={schema} dataSource={mockDataSource} />);
|
|
1595
|
+
|
|
1596
|
+
await vi.waitFor(() => {
|
|
1597
|
+
expect(screen.getByText('3 records')).toBeInTheDocument();
|
|
1598
|
+
});
|
|
1599
|
+
|
|
1600
|
+
// Open search popover and type search query
|
|
1601
|
+
fireEvent.click(screen.getByTestId('search-icon-button'));
|
|
1602
|
+
fireEvent.change(screen.getByPlaceholderText(/search/i), { target: { value: 'alice' } });
|
|
1603
|
+
|
|
1604
|
+
await vi.waitFor(() => {
|
|
1605
|
+
expect(screen.getByText('1 record')).toBeInTheDocument();
|
|
1606
|
+
});
|
|
1607
|
+
expect(mockDataSource.find).not.toHaveBeenCalled();
|
|
1608
|
+
});
|
|
1609
|
+
|
|
1610
|
+
it('should filter value provider data by searchTerm', async () => {
|
|
1611
|
+
const schema: ListViewSchema = {
|
|
1612
|
+
type: 'list-view',
|
|
1613
|
+
objectName: 'contacts',
|
|
1614
|
+
viewType: 'grid',
|
|
1615
|
+
fields: ['name', 'email'],
|
|
1616
|
+
data: {
|
|
1617
|
+
provider: 'value',
|
|
1618
|
+
items: [
|
|
1619
|
+
{ _id: '1', name: 'Alice', email: 'alice@test.com' },
|
|
1620
|
+
{ _id: '2', name: 'Bob', email: 'bob@test.com' },
|
|
1621
|
+
],
|
|
1622
|
+
} as any,
|
|
1623
|
+
};
|
|
1624
|
+
|
|
1625
|
+
mockDataSource.find.mockClear();
|
|
1626
|
+
renderWithProvider(<ListView schema={schema} dataSource={mockDataSource} />);
|
|
1627
|
+
|
|
1628
|
+
await vi.waitFor(() => {
|
|
1629
|
+
expect(screen.getByText('2 records')).toBeInTheDocument();
|
|
1630
|
+
});
|
|
1631
|
+
|
|
1632
|
+
// Open search popover and type search query
|
|
1633
|
+
fireEvent.click(screen.getByTestId('search-icon-button'));
|
|
1634
|
+
fireEvent.change(screen.getByPlaceholderText(/search/i), { target: { value: 'bob' } });
|
|
1635
|
+
|
|
1636
|
+
await vi.waitFor(() => {
|
|
1637
|
+
expect(screen.getByText('1 record')).toBeInTheDocument();
|
|
1638
|
+
});
|
|
1639
|
+
expect(mockDataSource.find).not.toHaveBeenCalled();
|
|
1640
|
+
});
|
|
1641
|
+
|
|
1580
1642
|
it('should fall back to dataSource.find when schema.data is not set', async () => {
|
|
1581
1643
|
const mockItems = [
|
|
1582
1644
|
{ _id: '1', name: 'Alice', email: 'alice@test.com' },
|
|
@@ -369,10 +369,10 @@ describe('UserFilters', () => {
|
|
|
369
369
|
});
|
|
370
370
|
|
|
371
371
|
// ============================================
|
|
372
|
-
// Add Filter Entry Point
|
|
372
|
+
// Add Filter Entry Point (removed)
|
|
373
373
|
// ============================================
|
|
374
374
|
describe('Add filter entry', () => {
|
|
375
|
-
it('
|
|
375
|
+
it('does not render "Add filter" button (removed from UI)', () => {
|
|
376
376
|
const config = {
|
|
377
377
|
element: 'dropdown' as const,
|
|
378
378
|
fields: [
|
|
@@ -387,15 +387,7 @@ describe('UserFilters', () => {
|
|
|
387
387
|
};
|
|
388
388
|
const onChange = vi.fn();
|
|
389
389
|
render(<UserFilters config={config} onFilterChange={onChange} />);
|
|
390
|
-
expect(screen.
|
|
391
|
-
expect(screen.getByText('Add filter')).toBeInTheDocument();
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
it('renders "Add filter" button even when no fields are provided', () => {
|
|
395
|
-
const config = { element: 'dropdown' as const };
|
|
396
|
-
const onChange = vi.fn();
|
|
397
|
-
render(<UserFilters config={config} onFilterChange={onChange} />);
|
|
398
|
-
expect(screen.getByTestId('user-filters-add')).toBeInTheDocument();
|
|
390
|
+
expect(screen.queryByTestId('user-filters-add')).not.toBeInTheDocument();
|
|
399
391
|
});
|
|
400
392
|
});
|
|
401
393
|
|