@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.
- package/dist/index.cjs +1179 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +574 -0
- package/dist/index.d.ts +574 -0
- package/dist/index.js +1150 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
- package/src/filters/BooleanFilter.tsx +46 -0
- package/src/filters/Filter.tsx +44 -0
- package/src/filters/MultiSelectFilter.tsx +46 -0
- package/src/filters/RangeSliderFilter.tsx +250 -0
- package/src/filters/index.ts +4 -0
- package/src/helpers/index.ts +5 -0
- package/src/helpers/usePagination.ts +210 -0
- package/src/helpers/useSorting.ts +185 -0
- package/src/index.ts +4 -0
- package/src/selectors/ChainSelector.tsx +72 -0
- package/src/selectors/ChainsSelector.tsx +76 -0
- package/src/selectors/OpportunitiesSelector.tsx +79 -0
- package/src/selectors/OpportunitySelector.tsx +75 -0
- package/src/selectors/ProductSelector.tsx +69 -0
- package/src/selectors/ProductsSelector.tsx +73 -0
- package/src/selectors/TokenSelector.tsx +85 -0
- package/src/selectors/TokensSelector.tsx +89 -0
- package/src/selectors/index.ts +11 -0
- package/src/wrappers/ActiveFilterBadges.tsx +139 -0
- package/src/wrappers/FiltersGrid.tsx +69 -0
- package/src/wrappers/FiltersPopover.tsx +113 -0
- package/src/wrappers/FiltersWrapper.tsx +159 -0
- package/src/wrappers/README.md +272 -0
- package/src/wrappers/index.ts +8 -0
|
@@ -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";
|