Skip to content

Commit 6bba745

Browse files
authored
Merge pull request #14 from objectql/copilot/add-airtable-filter-component
2 parents c4637f4 + 750b2cb commit 6bba745

8 files changed

Lines changed: 1060 additions & 1 deletion

File tree

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
import { SchemaRenderer } from '@object-ui/react';
2+
import '@object-ui/components';
3+
4+
const filterBuilderSchema = {
5+
type: 'div',
6+
className: 'min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 p-8',
7+
body: [
8+
{
9+
type: 'div',
10+
className: 'max-w-5xl mx-auto space-y-8',
11+
body: [
12+
// Header
13+
{
14+
type: 'div',
15+
className: 'space-y-2',
16+
body: [
17+
{
18+
type: 'div',
19+
className: 'text-3xl font-bold tracking-tight',
20+
body: { type: 'text', content: 'Filter Builder Demo' }
21+
},
22+
{
23+
type: 'div',
24+
className: 'text-muted-foreground',
25+
body: {
26+
type: 'text',
27+
content: 'Airtable-like filter component with advanced field types and operators'
28+
}
29+
}
30+
]
31+
},
32+
33+
// Example 1: User Data Filtering with Date and Select
34+
{
35+
type: 'card',
36+
className: 'shadow-lg',
37+
body: [
38+
{
39+
type: 'div',
40+
className: 'p-6 border-b',
41+
body: [
42+
{
43+
type: 'div',
44+
className: 'text-xl font-semibold',
45+
body: { type: 'text', content: 'User Data Filters' }
46+
},
47+
{
48+
type: 'div',
49+
className: 'text-sm text-muted-foreground mt-1',
50+
body: {
51+
type: 'text',
52+
content: 'Advanced filtering with date, select, and boolean fields'
53+
}
54+
}
55+
]
56+
},
57+
{
58+
type: 'div',
59+
className: 'p-6',
60+
body: {
61+
type: 'filter-builder',
62+
name: 'userFilters',
63+
label: 'User Filters',
64+
fields: [
65+
{ value: 'name', label: 'Name', type: 'text' },
66+
{ value: 'email', label: 'Email', type: 'text' },
67+
{ value: 'age', label: 'Age', type: 'number' },
68+
{
69+
value: 'status',
70+
label: 'Status',
71+
type: 'select',
72+
options: [
73+
{ value: 'active', label: 'Active' },
74+
{ value: 'inactive', label: 'Inactive' },
75+
{ value: 'pending', label: 'Pending' }
76+
]
77+
},
78+
{
79+
value: 'department',
80+
label: 'Department',
81+
type: 'select',
82+
options: [
83+
{ value: 'engineering', label: 'Engineering' },
84+
{ value: 'sales', label: 'Sales' },
85+
{ value: 'marketing', label: 'Marketing' },
86+
{ value: 'support', label: 'Support' }
87+
]
88+
},
89+
{ value: 'joinDate', label: 'Join Date', type: 'date' },
90+
{ value: 'isVerified', label: 'Is Verified', type: 'boolean' }
91+
],
92+
value: {
93+
id: 'root',
94+
logic: 'and',
95+
conditions: [
96+
{
97+
id: 'cond-1',
98+
field: 'status',
99+
operator: 'equals',
100+
value: 'active'
101+
}
102+
]
103+
}
104+
}
105+
}
106+
]
107+
},
108+
{
109+
type: 'card',
110+
className: 'shadow-lg',
111+
body: [
112+
{
113+
type: 'div',
114+
className: 'p-6 border-b',
115+
body: [
116+
{
117+
type: 'div',
118+
className: 'text-xl font-semibold',
119+
body: { type: 'text', content: 'Product Filters' }
120+
},
121+
{
122+
type: 'div',
123+
className: 'text-sm text-muted-foreground mt-1',
124+
body: {
125+
type: 'text',
126+
content: 'Filter products by name, price, category, and stock'
127+
}
128+
}
129+
]
130+
},
131+
{
132+
type: 'div',
133+
className: 'p-6',
134+
body: {
135+
type: 'filter-builder',
136+
name: 'productFilters',
137+
label: 'Product Filters',
138+
fields: [
139+
{ value: 'name', label: 'Product Name', type: 'text' },
140+
{ value: 'price', label: 'Price', type: 'number' },
141+
{ value: 'category', label: 'Category', type: 'text' },
142+
{ value: 'stock', label: 'Stock Quantity', type: 'number' },
143+
{ value: 'brand', label: 'Brand', type: 'text' },
144+
{ value: 'rating', label: 'Rating', type: 'number' }
145+
],
146+
value: {
147+
id: 'root',
148+
logic: 'or',
149+
conditions: [
150+
{
151+
id: 'cond-1',
152+
field: 'price',
153+
operator: 'lessThan',
154+
value: '100'
155+
},
156+
{
157+
id: 'cond-2',
158+
field: 'category',
159+
operator: 'equals',
160+
value: 'Electronics'
161+
}
162+
]
163+
}
164+
}
165+
}
166+
]
167+
},
168+
169+
// Example 3: Empty Filter Builder
170+
{
171+
type: 'card',
172+
className: 'shadow-lg',
173+
body: [
174+
{
175+
type: 'div',
176+
className: 'p-6 border-b',
177+
body: [
178+
{
179+
type: 'div',
180+
className: 'text-xl font-semibold',
181+
body: { type: 'text', content: 'Order Filters' }
182+
},
183+
{
184+
type: 'div',
185+
className: 'text-sm text-muted-foreground mt-1',
186+
body: {
187+
type: 'text',
188+
content: 'Start with an empty filter - try adding conditions!'
189+
}
190+
}
191+
]
192+
},
193+
{
194+
type: 'div',
195+
className: 'p-6',
196+
body: {
197+
type: 'filter-builder',
198+
name: 'orderFilters',
199+
label: 'Order Filters',
200+
fields: [
201+
{ value: 'orderId', label: 'Order ID', type: 'text' },
202+
{ value: 'customer', label: 'Customer Name', type: 'text' },
203+
{ value: 'total', label: 'Total Amount', type: 'number' },
204+
{ value: 'status', label: 'Order Status', type: 'text' },
205+
{ value: 'date', label: 'Order Date', type: 'text' },
206+
{ value: 'shipped', label: 'Shipped', type: 'boolean' }
207+
],
208+
value: {
209+
id: 'root',
210+
logic: 'and',
211+
conditions: []
212+
}
213+
}
214+
}
215+
]
216+
},
217+
218+
// Features section
219+
{
220+
type: 'card',
221+
className: 'shadow-lg bg-primary/5 border-primary/20',
222+
body: {
223+
type: 'div',
224+
className: 'p-6',
225+
body: [
226+
{
227+
type: 'div',
228+
className: 'text-lg font-semibold mb-4',
229+
body: { type: 'text', content: '✨ Features' }
230+
},
231+
{
232+
type: 'div',
233+
className: 'grid md:grid-cols-2 gap-4 text-sm',
234+
body: [
235+
{
236+
type: 'div',
237+
className: 'flex items-start gap-2',
238+
body: [
239+
{ type: 'div', body: { type: 'text', content: '✓' }, className: 'text-primary font-bold' },
240+
{ type: 'div', body: { type: 'text', content: 'Dynamic add/remove filter conditions' } }
241+
]
242+
},
243+
{
244+
type: 'div',
245+
className: 'flex items-start gap-2',
246+
body: [
247+
{ type: 'div', body: { type: 'text', content: '✓' }, className: 'text-primary font-bold' },
248+
{ type: 'div', body: { type: 'text', content: 'Field-type aware operators' } }
249+
]
250+
},
251+
{
252+
type: 'div',
253+
className: 'flex items-start gap-2',
254+
body: [
255+
{ type: 'div', body: { type: 'text', content: '✓' }, className: 'text-primary font-bold' },
256+
{ type: 'div', body: { type: 'text', content: 'AND/OR logic toggling' } }
257+
]
258+
},
259+
{
260+
type: 'div',
261+
className: 'flex items-start gap-2',
262+
body: [
263+
{ type: 'div', body: { type: 'text', content: '✓' }, className: 'text-primary font-bold' },
264+
{ type: 'div', body: { type: 'text', content: 'Date & Select field support' } }
265+
]
266+
},
267+
{
268+
type: 'div',
269+
className: 'flex items-start gap-2',
270+
body: [
271+
{ type: 'div', body: { type: 'text', content: '✓' }, className: 'text-primary font-bold' },
272+
{ type: 'div', body: { type: 'text', content: 'Clear all filters button' } }
273+
]
274+
},
275+
{
276+
type: 'div',
277+
className: 'flex items-start gap-2',
278+
body: [
279+
{ type: 'div', body: { type: 'text', content: '✓' }, className: 'text-primary font-bold' },
280+
{ type: 'div', body: { type: 'text', content: 'Tailwind CSS styled' } }
281+
]
282+
},
283+
{
284+
type: 'div',
285+
className: 'flex items-start gap-2',
286+
body: [
287+
{ type: 'div', body: { type: 'text', content: '✓' }, className: 'text-primary font-bold' },
288+
{ type: 'div', body: { type: 'text', content: 'Shadcn UI components' } }
289+
]
290+
},
291+
{
292+
type: 'div',
293+
className: 'flex items-start gap-2',
294+
body: [
295+
{ type: 'div', body: { type: 'text', content: '✓' }, className: 'text-primary font-bold' },
296+
{ type: 'div', body: { type: 'text', content: 'Schema-driven configuration' } }
297+
]
298+
}
299+
]
300+
}
301+
]
302+
}
303+
}
304+
]
305+
}
306+
]
307+
};
308+
309+
function FilterBuilderDemo() {
310+
return (
311+
<SchemaRenderer
312+
schema={filterBuilderSchema}
313+
/>
314+
);
315+
}
316+
317+
export default FilterBuilderDemo;

examples/prototype/src/main.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,16 @@ import { StrictMode } from 'react'
22
import { createRoot } from 'react-dom/client'
33
import './index.css'
44
import App from './App.tsx'
5+
import FilterBuilderDemo from './FilterBuilderDemo.tsx'
6+
7+
// Check if URL parameter specifies which demo to show
8+
const urlParams = new URLSearchParams(window.location.search);
9+
const demo = urlParams.get('demo');
10+
11+
const DemoApp = demo === 'filter-builder' ? FilterBuilderDemo : App;
512

613
createRoot(document.getElementById('root')!).render(
714
<StrictMode>
8-
<App />
15+
<DemoApp />
916
</StrictMode>,
1017
)

0 commit comments

Comments
 (0)