@turtleclub/core 0.1.0-beta.100

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.
@@ -0,0 +1,159 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { cn } from "@turtleclub/ui";
5
+ import { FiltersGrid } from "./FiltersGrid";
6
+ import { FiltersPopover } from "./FiltersPopover";
7
+ import { ActiveFilterBadges, ActiveFilterConfig } from "./ActiveFilterBadges";
8
+ import { FilterConfig } from "./FiltersGrid";
9
+
10
+ export interface FiltersWrapperProps {
11
+ /** Array of filter configurations */
12
+ filters: FilterConfig[];
13
+ /** Array of active filter configurations for badges */
14
+ activeFilters: ActiveFilterConfig[];
15
+ /** Layout type: 'grid' or 'popover' */
16
+ layout: "grid" | "popover";
17
+ /** Custom className for the wrapper */
18
+ className?: string;
19
+ /** Custom className for the active badges section */
20
+ badgesClassName?: string;
21
+ /** Custom className for the slot container */
22
+ slotClassName?: string;
23
+ /** Optional content to render on the left side of filters */
24
+ leftSlot?: React.ReactNode;
25
+ /** Optional content to render on the right side of filters */
26
+ rightSlot?: React.ReactNode;
27
+
28
+ // Grid-specific props
29
+ /** Number of columns for grid layout (default: 3) */
30
+ columns?: number;
31
+
32
+ // Popover-specific props
33
+ /** Button label for popover trigger (default: "Filters") */
34
+ triggerLabel?: string;
35
+ /** Number of columns in popover (default: 2) */
36
+ popoverColumns?: number;
37
+ /** Custom className for popover trigger button */
38
+ popoverClassName?: string;
39
+ /** Custom className for popover content */
40
+ popoverContentClassName?: string;
41
+ /** Alignment for popover (default: "start") */
42
+ align?: "start" | "center" | "end";
43
+
44
+ // Active badges props
45
+ /** Label for clear all button (default: "Clear All") */
46
+ clearAllLabel?: string;
47
+ /** Whether to show clear all button (default: true) */
48
+ showClearAll?: boolean;
49
+ /** Custom render function for individual badges */
50
+ renderBadge?: (filter: ActiveFilterConfig, clearFilter: () => void) => React.ReactNode;
51
+ /** Whether to show active filter badges (default: true) */
52
+ showActiveBadges?: boolean;
53
+ /** Callback function to clear individual filter */
54
+ onClearFilter: (queryKeys: string | string[]) => void;
55
+ /** Callback function to clear all filters */
56
+ onClearAll: (allQueryKeys: (string | string[])[]) => void;
57
+ }
58
+
59
+ /**
60
+ * Unified wrapper component that combines filter controls with active filter badges.
61
+ *
62
+ * Supports both grid and popover layouts, automatically displays active filter badges
63
+ * below the filter controls with individual and bulk clear functionality.
64
+ *
65
+ * @example
66
+ * ```tsx
67
+ * const filters = [
68
+ * {
69
+ * key: 'chain',
70
+ * label: 'Chain',
71
+ * component: <ChainFilter />,
72
+ * enabled: true,
73
+ * },
74
+ * ];
75
+ *
76
+ * const activeFilters = [
77
+ * {
78
+ * key: 'chain',
79
+ * label: 'Chain',
80
+ * value: 'Ethereum',
81
+ * queryKeys: 'chainId',
82
+ * isActive: true,
83
+ * },
84
+ * ];
85
+ *
86
+ * <FiltersWrapper
87
+ * layout="grid"
88
+ * filters={filters}
89
+ * activeFilters={activeFilters}
90
+ * />
91
+ * ```
92
+ */
93
+ export function FiltersWrapper({
94
+ filters,
95
+ activeFilters,
96
+ layout,
97
+ className,
98
+ badgesClassName,
99
+ slotClassName,
100
+ leftSlot,
101
+ rightSlot,
102
+ columns = 3,
103
+ triggerLabel = "Filters",
104
+ popoverColumns = 2,
105
+ popoverClassName,
106
+ popoverContentClassName,
107
+ align = "start",
108
+ clearAllLabel = "Clear All",
109
+ showClearAll = true,
110
+ renderBadge,
111
+ showActiveBadges = true,
112
+ onClearFilter,
113
+ onClearAll,
114
+ }: FiltersWrapperProps) {
115
+ const hasActiveFilters = activeFilters.some((filter) => filter.isActive && filter.value);
116
+
117
+ return (
118
+ <div className={cn("space-y-3", className)}>
119
+ {/* Filter Controls */}
120
+ <div className={cn("flex items-center gap-2", slotClassName)}>
121
+ {leftSlot}
122
+ <>
123
+ {layout === "grid" ? (
124
+ <FiltersGrid filters={filters} columns={columns} className={popoverClassName} />
125
+ ) : (
126
+ <FiltersPopover
127
+ filters={filters}
128
+ triggerLabel={triggerLabel}
129
+ columns={popoverColumns}
130
+ className={popoverClassName}
131
+ popoverClassName={popoverContentClassName}
132
+ align={align}
133
+ onClearAll={() => {
134
+ const allQueryKeys = activeFilters
135
+ .map((filter) => filter.queryKeys)
136
+ .filter((keys): keys is string | string[] => keys !== undefined);
137
+ onClearAll(allQueryKeys);
138
+ }}
139
+ />
140
+ )}
141
+ </>
142
+ {rightSlot}
143
+ </div>
144
+
145
+ {/* Active Filter Badges */}
146
+ {showActiveBadges && hasActiveFilters && (
147
+ <ActiveFilterBadges
148
+ filters={activeFilters}
149
+ className={badgesClassName}
150
+ clearAllLabel={clearAllLabel}
151
+ showClearAll={showClearAll}
152
+ renderBadge={renderBadge}
153
+ onClearFilter={onClearFilter}
154
+ onClearAll={onClearAll}
155
+ />
156
+ )}
157
+ </div>
158
+ );
159
+ }
@@ -0,0 +1,272 @@
1
+ # Filter Wrappers
2
+
3
+ Two wrapper components for managing filters with a consistent interface. Each filter can be individually toggled on/off using the `enabled` property.
4
+
5
+ ## Components
6
+
7
+ ### `FiltersGrid`
8
+
9
+ A simple grid layout wrapper for displaying filters in a responsive grid.
10
+
11
+ **Props:**
12
+ - `filters`: Array of `FilterConfig` objects
13
+ - `columns`: Number of columns (default: 3)
14
+ - `gap`: Gap between items (default: 4)
15
+ - `className`: Additional CSS classes
16
+
17
+ ### `FiltersPopover`
18
+
19
+ A popover-based wrapper that displays filters inside a dropdown/popover component.
20
+
21
+ **Props:**
22
+ - `filters`: Array of `FilterConfig` objects
23
+ - `triggerLabel`: Button label (default: "Filters")
24
+ - `columns`: Number of columns in popover (default: 2)
25
+ - `gap`: Gap between items (default: 4)
26
+ - `className`: Additional CSS classes for trigger button
27
+ - `popoverClassName`: Additional CSS classes for popover content
28
+
29
+ ## FilterConfig Interface
30
+
31
+ ```typescript
32
+ interface FilterConfig {
33
+ key: string; // Unique identifier for the filter
34
+ label: string; // Display label for the filter
35
+ component: React.ReactNode; // The filter component to render
36
+ enabled: boolean; // Whether to show this filter (if false, filter is omitted)
37
+ }
38
+ ```
39
+
40
+ ## Usage Examples
41
+
42
+ ### Basic Grid Layout
43
+
44
+ ```tsx
45
+ import { FiltersGrid } from '@turtleclub/core/wrappers';
46
+ import { ChainFilter, TokenFilter, ProductFilter } from '@turtleclub/core/filters';
47
+
48
+ function MyComponent() {
49
+ const filters = [
50
+ {
51
+ key: 'chain',
52
+ label: 'Chain',
53
+ component: <ChainFilter />,
54
+ enabled: true,
55
+ },
56
+ {
57
+ key: 'token',
58
+ label: 'Token',
59
+ component: <TokenFilter />,
60
+ enabled: true,
61
+ },
62
+ {
63
+ key: 'product',
64
+ label: 'Product',
65
+ component: <ProductFilter onValueChange={(val) => console.log(val)} />,
66
+ enabled: false, // This filter will not be rendered
67
+ },
68
+ ];
69
+
70
+ return (
71
+ <FiltersGrid
72
+ filters={filters}
73
+ columns={3}
74
+ gap={4}
75
+ />
76
+ );
77
+ }
78
+ ```
79
+
80
+ ### Popover Layout
81
+
82
+ ```tsx
83
+ import { FiltersPopover } from '@turtleclub/core/wrappers';
84
+ import { ChainFilter, TokenFilter, TvlRangeFilter } from '@turtleclub/core/filters';
85
+
86
+ function MyComponent() {
87
+ const filters = [
88
+ {
89
+ key: 'chain',
90
+ label: 'Chain',
91
+ component: <ChainFilter />,
92
+ enabled: true,
93
+ },
94
+ {
95
+ key: 'token',
96
+ label: 'Token',
97
+ component: <TokenFilter />,
98
+ enabled: true,
99
+ },
100
+ {
101
+ key: 'tvl',
102
+ label: 'TVL Range',
103
+ component: <TvlRangeFilter />,
104
+ enabled: true,
105
+ },
106
+ ];
107
+
108
+ return (
109
+ <FiltersPopover
110
+ filters={filters}
111
+ triggerLabel="Apply Filters"
112
+ columns={2}
113
+ />
114
+ );
115
+ }
116
+ ```
117
+
118
+ ### Dynamic Filter Toggling
119
+
120
+ ```tsx
121
+ import { useState } from 'react';
122
+ import { FiltersGrid } from '@turtleclub/core/wrappers';
123
+ import { ChainFilter, TokenFilter, ProductFilter, TagFilter } from '@turtleclub/core/filters';
124
+
125
+ function MyComponent() {
126
+ const [showChain, setShowChain] = useState(true);
127
+ const [showToken, setShowToken] = useState(true);
128
+ const [showProduct, setShowProduct] = useState(false);
129
+ const [showTags, setShowTags] = useState(false);
130
+
131
+ const filters = [
132
+ {
133
+ key: 'chain',
134
+ label: 'Chain',
135
+ component: <ChainFilter />,
136
+ enabled: showChain,
137
+ },
138
+ {
139
+ key: 'token',
140
+ label: 'Token',
141
+ component: <TokenFilter />,
142
+ enabled: showToken,
143
+ },
144
+ {
145
+ key: 'product',
146
+ label: 'Product',
147
+ component: <ProductFilter onValueChange={(val) => console.log(val)} />,
148
+ enabled: showProduct,
149
+ },
150
+ {
151
+ key: 'tags',
152
+ label: 'Tags',
153
+ component: <TagFilter />,
154
+ enabled: showTags,
155
+ },
156
+ ];
157
+
158
+ return (
159
+ <div>
160
+ <div className="mb-4 flex gap-2">
161
+ <button onClick={() => setShowChain(!showChain)}>
162
+ Toggle Chain Filter
163
+ </button>
164
+ <button onClick={() => setShowToken(!showToken)}>
165
+ Toggle Token Filter
166
+ </button>
167
+ <button onClick={() => setShowProduct(!showProduct)}>
168
+ Toggle Product Filter
169
+ </button>
170
+ <button onClick={() => setShowTags(!showTags)}>
171
+ Toggle Tags Filter
172
+ </button>
173
+ </div>
174
+
175
+ <FiltersGrid filters={filters} />
176
+ </div>
177
+ );
178
+ }
179
+ ```
180
+
181
+ ### Responsive Grid
182
+
183
+ ```tsx
184
+ import { FiltersGrid } from '@turtleclub/core/wrappers';
185
+ import { ChainFilter, TokenFilter, ProductFilter } from '@turtleclub/core/filters';
186
+
187
+ function MyComponent() {
188
+ const filters = [
189
+ {
190
+ key: 'chain',
191
+ label: 'Chain',
192
+ component: <ChainFilter />,
193
+ enabled: true,
194
+ },
195
+ {
196
+ key: 'token',
197
+ label: 'Token',
198
+ component: <TokenFilter />,
199
+ enabled: true,
200
+ },
201
+ {
202
+ key: 'product',
203
+ label: 'Product',
204
+ component: <ProductFilter onValueChange={(val) => console.log(val)} />,
205
+ enabled: true,
206
+ },
207
+ ];
208
+
209
+ return (
210
+ <FiltersGrid
211
+ filters={filters}
212
+ columns={3}
213
+ className="md:grid-cols-2 lg:grid-cols-3"
214
+ />
215
+ );
216
+ }
217
+ ```
218
+
219
+ ### Combined Approach
220
+
221
+ You can use both wrappers together for different screen sizes:
222
+
223
+ ```tsx
224
+ import { useState } from 'react';
225
+ import { FiltersGrid, FiltersPopover } from '@turtleclub/core/wrappers';
226
+ import { ChainFilter, TokenFilter, ProductFilter } from '@turtleclub/core/filters';
227
+
228
+ function MyComponent() {
229
+ const filters = [
230
+ {
231
+ key: 'chain',
232
+ label: 'Chain',
233
+ component: <ChainFilter />,
234
+ enabled: true,
235
+ },
236
+ {
237
+ key: 'token',
238
+ label: 'Token',
239
+ component: <TokenFilter />,
240
+ enabled: true,
241
+ },
242
+ {
243
+ key: 'product',
244
+ label: 'Product',
245
+ component: <ProductFilter onValueChange={(val) => console.log(val)} />,
246
+ enabled: true,
247
+ },
248
+ ];
249
+
250
+ return (
251
+ <>
252
+ {/* Show popover on mobile */}
253
+ <div className="block md:hidden">
254
+ <FiltersPopover filters={filters} />
255
+ </div>
256
+
257
+ {/* Show grid on desktop */}
258
+ <div className="hidden md:block">
259
+ <FiltersGrid filters={filters} columns={3} />
260
+ </div>
261
+ </>
262
+ );
263
+ }
264
+ ```
265
+
266
+ ## Benefits
267
+
268
+ - **Consistent Interface**: Both wrappers use the same `FilterConfig` interface
269
+ - **Easy Toggle**: Simply set `enabled: false` to hide a filter
270
+ - **Flexible Layout**: Choose between grid or popover based on your needs
271
+ - **Type-Safe**: Full TypeScript support
272
+ - **Composable**: Works with any React component as the filter
@@ -0,0 +1,8 @@
1
+ export { FiltersGrid } from "./FiltersGrid";
2
+ export { FiltersPopover } from "./FiltersPopover";
3
+ export { ActiveFilterBadges } from "./ActiveFilterBadges";
4
+ export { FiltersWrapper } from "./FiltersWrapper";
5
+ export type { FilterConfig, FiltersGridProps } from "./FiltersGrid";
6
+ export type { FiltersPopoverProps } from "./FiltersPopover";
7
+ export type { ActiveFilterConfig, ActiveFilterBadgesProps } from "./ActiveFilterBadges";
8
+ export type { FiltersWrapperProps } from "./FiltersWrapper";