Skip to content

Commit 1d56861

Browse files
authored
Add "filter", "className", "style" to grid props (#151)
* add baseFilter to extracted and item grid * to filters * class name config * add changeset
1 parent 4256314 commit 1d56861

File tree

4 files changed

+78
-10
lines changed

4 files changed

+78
-10
lines changed

.changeset/silent-maps-kick.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@llamaindex/ui": patch
3+
---
4+
5+
Add filter/styles to grid

packages/ui/src/item-grid/extracted-data-item-grid.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {
22
TypedAgentData,
33
ExtractedData,
4+
FilterOperation,
45
} from "llama-cloud-services/beta/agent";
56
import { ItemGrid } from "./item-grid";
67
import {
@@ -19,13 +20,21 @@ export interface ExtractedDataItemGridProps<T> {
1920
onRowClick?: (item: TypedAgentData<ExtractedData<T>>) => void;
2021
// Other configurations
2122
defaultPageSize?: number;
23+
// Optional base filter to be passed to search API
24+
filter?: Record<string, FilterOperation>;
25+
// Styling (forwarded to ItemGrid)
26+
className?: string;
27+
style?: React.CSSProperties;
2228
}
2329

2430
export function ExtractedDataItemGrid<T>({
2531
customColumns = [],
2632
builtInColumns = {},
2733
onRowClick,
2834
defaultPageSize = 20,
35+
filter,
36+
className,
37+
style,
2938
}: ExtractedDataItemGridProps<T>) {
3039
const confidenceThreshold = useUIConfigStore(
3140
(state) => state.confidenceThreshold
@@ -58,6 +67,9 @@ export function ExtractedDataItemGrid<T>({
5867
customColumns={columns}
5968
onRowClick={onRowClick}
6069
defaultPageSize={defaultPageSize}
70+
filter={filter}
71+
className={className}
72+
style={style}
6173
/>
6274
);
6375
}

packages/ui/src/item-grid/item-grid.tsx

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useMemo } from "react";
1+
import { useState, useMemo, type CSSProperties } from "react";
22
import { cn } from "@/lib/utils";
33
import {
44
Table,
@@ -35,13 +35,21 @@ export interface ItemGridProp<T = unknown> {
3535
onRowClick?: (item: TypedAgentData<T>) => void;
3636
// Other configurations
3737
defaultPageSize?: number;
38+
// Optional root filter passed through directly to the search API
39+
filter?: Record<string, FilterOperation>;
40+
// Styling (outermost container only)
41+
className?: string;
42+
style?: CSSProperties;
3843
}
3944

4045
// Main Business Component
4146
export function ItemGrid<T = unknown>({
4247
customColumns = [],
4348
onRowClick,
4449
defaultPageSize = 20,
50+
filter,
51+
className,
52+
style,
4553
}: ItemGridProp<T>) {
4654
const [paginationState, setPaginationState] = useState<PaginationState>({
4755
page: 0,
@@ -54,7 +62,7 @@ export function ItemGrid<T = unknown>({
5462
direction: "desc",
5563
});
5664

57-
const [filters, setFilters] = useState<Record<string, string[]>>({});
65+
const [uiFilters, setUiFilters] = useState<Record<string, string[]>>({});
5866

5967
// Generate final columns array
6068
const columns = useMemo(() => {
@@ -67,17 +75,19 @@ export function ItemGrid<T = unknown>({
6775
}, [customColumns]);
6876

6977
// Convert frontend filter state to API format
70-
const apiFilters = useMemo(() => {
71-
const result: Record<string, FilterOperation> = {};
78+
const searchFilter = useMemo(() => {
79+
const result: Record<string, FilterOperation> = {
80+
...(filter || {}),
81+
};
7282

73-
Object.entries(filters).forEach(([columnKey, filterValues]) => {
83+
Object.entries(uiFilters).forEach(([columnKey, filterValues]) => {
7484
if (filterValues.length > 0) {
7585
result[columnKey] = { includes: filterValues };
7686
}
7787
});
7888

7989
return result;
80-
}, [filters]);
90+
}, [uiFilters, filter]);
8191

8292
// Convert frontend sort state to API format
8393
const apiSort = useMemo(() => {
@@ -91,7 +101,7 @@ export function ItemGrid<T = unknown>({
91101
}, [sortState]);
92102

93103
const { data, loading, error, totalSize, deleteItem, fetchData } =
94-
useItemGridData<T>(paginationState, apiFilters, apiSort);
104+
useItemGridData<T>(paginationState, searchFilter, apiSort);
95105

96106
// Create hooks object for passing to renderCell
97107
const hooks = useMemo(
@@ -122,7 +132,7 @@ export function ItemGrid<T = unknown>({
122132

123133
// Handle filtering
124134
const handleFilterChange = (columnKey: string, values: string[]) => {
125-
setFilters((prev) => ({
135+
setUiFilters((prev) => ({
126136
...prev,
127137
[columnKey]: values,
128138
}));
@@ -168,7 +178,7 @@ export function ItemGrid<T = unknown>({
168178
}
169179

170180
return (
171-
<div className="w-full space-y-4">
181+
<div className={cn("w-full space-y-4", className)} style={style}>
172182
<div className="rounded-md border">
173183
<Table className="table-fixed">
174184
<TableHeader>
@@ -186,7 +196,7 @@ export function ItemGrid<T = unknown>({
186196
sortState={sortState}
187197
onSort={handleSort}
188198
filterOptions={getFilterOptions(column.key)}
189-
selectedFilters={filters[column.key]}
199+
selectedFilters={uiFilters[column.key]}
190200
onFilterChange={handleFilterChange}
191201
/>
192202
</TableHead>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { describe, it, expect, vi } from "vitest";
2+
import { render } from "@testing-library/react";
3+
import { ApiProvider, createMockClients } from "../../src/lib";
4+
import { ExtractedDataItemGrid } from "../../src/item-grid/extracted-data-item-grid";
5+
import type { FilterOperation } from "llama-cloud-services/beta/agent";
6+
import type { ReactNode } from "react";
7+
8+
function renderWithProvider(ui: ReactNode, clients = createMockClients()) {
9+
return render(<ApiProvider clients={clients}>{ui}</ApiProvider>);
10+
}
11+
12+
describe("ExtractedDataItemGrid baseFilter", () => {
13+
it("passes filter through to search API", async () => {
14+
const clients = createMockClients();
15+
if (!clients.agentDataClient) {
16+
throw new Error("AgentDataClient not found");
17+
}
18+
const spy = vi.spyOn(clients.agentDataClient, "search");
19+
20+
const filter: Record<string, FilterOperation> = {
21+
status: { includes: ["approved"] },
22+
};
23+
24+
renderWithProvider(
25+
<ExtractedDataItemGrid
26+
customColumns={[]}
27+
builtInColumns={{}}
28+
defaultPageSize={5}
29+
filter={filter}
30+
/>,
31+
clients
32+
);
33+
34+
// Wait a microtask tick for effects to run
35+
await Promise.resolve();
36+
37+
expect(spy).toHaveBeenCalled();
38+
const call = spy.mock.calls.at(-1) as any[];
39+
expect(call?.[0]?.filter).toMatchObject(filter);
40+
});
41+
});

0 commit comments

Comments
 (0)