@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
package/dist/index.js
ADDED
|
@@ -0,0 +1,1150 @@
|
|
|
1
|
+
// src/filters/RangeSliderFilter.tsx
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import {
|
|
4
|
+
Slider,
|
|
5
|
+
Label,
|
|
6
|
+
Input,
|
|
7
|
+
Popover,
|
|
8
|
+
PopoverTrigger,
|
|
9
|
+
PopoverContent,
|
|
10
|
+
buttonVariants,
|
|
11
|
+
cn
|
|
12
|
+
} from "@turtleclub/ui";
|
|
13
|
+
import { useQueryState, parseAsInteger } from "nuqs";
|
|
14
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
var defaultFormatter = (value) => {
|
|
16
|
+
return value.toLocaleString();
|
|
17
|
+
};
|
|
18
|
+
function RangeSliderFilter({
|
|
19
|
+
minQueryKey = "min",
|
|
20
|
+
maxQueryKey = "max",
|
|
21
|
+
min = 0,
|
|
22
|
+
max = 100,
|
|
23
|
+
step = 1,
|
|
24
|
+
disabled = false,
|
|
25
|
+
label = "Range",
|
|
26
|
+
showValues = true,
|
|
27
|
+
formatValue = defaultFormatter,
|
|
28
|
+
className = "",
|
|
29
|
+
usePopover = false
|
|
30
|
+
}) {
|
|
31
|
+
const [minValue, setMinValue] = useQueryState(minQueryKey, parseAsInteger.withDefault(min));
|
|
32
|
+
const [maxValue, setMaxValue] = useQueryState(maxQueryKey, parseAsInteger.withDefault(max));
|
|
33
|
+
const [localRange, setLocalRange] = useState([minValue, maxValue]);
|
|
34
|
+
const [minInputValue, setMinInputValue] = useState(minValue.toString());
|
|
35
|
+
const [maxInputValue, setMaxInputValue] = useState(maxValue.toString());
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
setLocalRange([minValue, maxValue]);
|
|
38
|
+
setMinInputValue(minValue.toString());
|
|
39
|
+
setMaxInputValue(maxValue.toString());
|
|
40
|
+
}, [minValue, maxValue]);
|
|
41
|
+
const handleRangeChange = (values) => {
|
|
42
|
+
setLocalRange(values);
|
|
43
|
+
};
|
|
44
|
+
const handleRangeCommit = (values) => {
|
|
45
|
+
const [newMin, newMax] = values;
|
|
46
|
+
if (newMin !== minValue) {
|
|
47
|
+
setMinValue(newMin === min ? null : newMin);
|
|
48
|
+
}
|
|
49
|
+
if (newMax !== maxValue) {
|
|
50
|
+
setMaxValue(newMax === max ? null : newMax);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const handleMinInputChange = (e) => {
|
|
54
|
+
setMinInputValue(e.target.value);
|
|
55
|
+
};
|
|
56
|
+
const handleMaxInputChange = (e) => {
|
|
57
|
+
setMaxInputValue(e.target.value);
|
|
58
|
+
};
|
|
59
|
+
const handleMinInputBlur = () => {
|
|
60
|
+
const numValue = parseInt(minInputValue, 10);
|
|
61
|
+
if (!isNaN(numValue)) {
|
|
62
|
+
const clampedValue = Math.max(min, Math.min(numValue, localRange[1]));
|
|
63
|
+
setLocalRange([clampedValue, localRange[1]]);
|
|
64
|
+
handleRangeCommit([clampedValue, localRange[1]]);
|
|
65
|
+
setMinInputValue(clampedValue.toString());
|
|
66
|
+
} else {
|
|
67
|
+
setMinInputValue(localRange[0].toString());
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const handleMaxInputBlur = () => {
|
|
71
|
+
const numValue = parseInt(maxInputValue, 10);
|
|
72
|
+
if (!isNaN(numValue)) {
|
|
73
|
+
const clampedValue = Math.min(max, Math.max(numValue, localRange[0]));
|
|
74
|
+
setLocalRange([localRange[0], clampedValue]);
|
|
75
|
+
handleRangeCommit([localRange[0], clampedValue]);
|
|
76
|
+
setMaxInputValue(clampedValue.toString());
|
|
77
|
+
} else {
|
|
78
|
+
setMaxInputValue(localRange[1].toString());
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const handleMinInputKeyDown = (e) => {
|
|
82
|
+
if (e.key === "Enter") {
|
|
83
|
+
handleMinInputBlur();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const handleMaxInputKeyDown = (e) => {
|
|
87
|
+
if (e.key === "Enter") {
|
|
88
|
+
handleMaxInputBlur();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const isDefaultRange = localRange[0] === min && localRange[1] === max;
|
|
92
|
+
const filterContent = /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
93
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
94
|
+
/* @__PURE__ */ jsx(Label, { className: "text-sm font-medium", children: label }),
|
|
95
|
+
showValues && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
96
|
+
/* @__PURE__ */ jsx("span", { children: formatValue(localRange[0]) }),
|
|
97
|
+
/* @__PURE__ */ jsx("span", { children: "-" }),
|
|
98
|
+
/* @__PURE__ */ jsx("span", { children: formatValue(localRange[1]) })
|
|
99
|
+
] })
|
|
100
|
+
] }),
|
|
101
|
+
/* @__PURE__ */ jsx("div", { className: "px-2", children: /* @__PURE__ */ jsx(
|
|
102
|
+
Slider,
|
|
103
|
+
{
|
|
104
|
+
value: localRange,
|
|
105
|
+
onValueChange: handleRangeChange,
|
|
106
|
+
onValueCommit: handleRangeCommit,
|
|
107
|
+
min,
|
|
108
|
+
max,
|
|
109
|
+
step,
|
|
110
|
+
disabled,
|
|
111
|
+
className: "w-full"
|
|
112
|
+
}
|
|
113
|
+
) }),
|
|
114
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between gap-2", children: [
|
|
115
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 flex-1", children: [
|
|
116
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "min-input", className: "text-xs text-muted-foreground", children: "Min" }),
|
|
117
|
+
/* @__PURE__ */ jsx(
|
|
118
|
+
Input,
|
|
119
|
+
{
|
|
120
|
+
id: "min-input",
|
|
121
|
+
type: "number",
|
|
122
|
+
value: minInputValue,
|
|
123
|
+
onChange: handleMinInputChange,
|
|
124
|
+
onBlur: handleMinInputBlur,
|
|
125
|
+
onKeyDown: handleMinInputKeyDown,
|
|
126
|
+
min,
|
|
127
|
+
max: localRange[1],
|
|
128
|
+
step,
|
|
129
|
+
disabled,
|
|
130
|
+
className: "h-7 text-xs [appearance:textfield] [&::-webkit-outer-spin-button]:m-0 [&::-webkit-outer-spin-button]:[-webkit-appearance:none] [&::-webkit-inner-spin-button]:m-0 [&::-webkit-inner-spin-button]:[-webkit-appearance:none]"
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
] }),
|
|
134
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 flex-1", children: [
|
|
135
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "max-input", className: "text-xs text-muted-foreground text-right", children: "Max" }),
|
|
136
|
+
/* @__PURE__ */ jsx(
|
|
137
|
+
Input,
|
|
138
|
+
{
|
|
139
|
+
id: "max-input",
|
|
140
|
+
type: "number",
|
|
141
|
+
value: maxInputValue,
|
|
142
|
+
onChange: handleMaxInputChange,
|
|
143
|
+
onBlur: handleMaxInputBlur,
|
|
144
|
+
onKeyDown: handleMaxInputKeyDown,
|
|
145
|
+
min: localRange[0],
|
|
146
|
+
max,
|
|
147
|
+
step,
|
|
148
|
+
disabled,
|
|
149
|
+
className: "h-7 text-xs [appearance:textfield] [&::-webkit-outer-spin-button]:m-0 [&::-webkit-outer-spin-button]:[-webkit-appearance:none] [&::-webkit-inner-spin-button]:m-0 [&::-webkit-inner-spin-button]:[-webkit-appearance:none]"
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
] })
|
|
153
|
+
] }),
|
|
154
|
+
!isDefaultRange && /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
|
|
155
|
+
"button",
|
|
156
|
+
{
|
|
157
|
+
onClick: () => {
|
|
158
|
+
setLocalRange([min, max]);
|
|
159
|
+
setMinInputValue(min.toString());
|
|
160
|
+
setMaxInputValue(max.toString());
|
|
161
|
+
handleRangeCommit([min, max]);
|
|
162
|
+
},
|
|
163
|
+
className: "text-xs text-muted-foreground hover:text-foreground transition-colors",
|
|
164
|
+
disabled,
|
|
165
|
+
children: "Reset"
|
|
166
|
+
}
|
|
167
|
+
) })
|
|
168
|
+
] });
|
|
169
|
+
if (usePopover) {
|
|
170
|
+
return /* @__PURE__ */ jsxs(Popover, { children: [
|
|
171
|
+
/* @__PURE__ */ jsxs(
|
|
172
|
+
PopoverTrigger,
|
|
173
|
+
{
|
|
174
|
+
className: cn(
|
|
175
|
+
buttonVariants({
|
|
176
|
+
variant: "default",
|
|
177
|
+
size: "default",
|
|
178
|
+
border: "bordered"
|
|
179
|
+
}),
|
|
180
|
+
"!bg-neutral-alpha-2",
|
|
181
|
+
className
|
|
182
|
+
),
|
|
183
|
+
disabled,
|
|
184
|
+
children: [
|
|
185
|
+
/* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
186
|
+
label,
|
|
187
|
+
":"
|
|
188
|
+
] }),
|
|
189
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: showValues && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
190
|
+
"[",
|
|
191
|
+
formatValue(localRange[0]),
|
|
192
|
+
" - ",
|
|
193
|
+
formatValue(localRange[1]),
|
|
194
|
+
"]"
|
|
195
|
+
] }) })
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
),
|
|
199
|
+
/* @__PURE__ */ jsx(PopoverContent, { className: "min-w-96", children: filterContent })
|
|
200
|
+
] });
|
|
201
|
+
}
|
|
202
|
+
return /* @__PURE__ */ jsx("div", { className, children: filterContent });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/filters/BooleanFilter.tsx
|
|
206
|
+
import { Switch, Label as Label2 } from "@turtleclub/ui";
|
|
207
|
+
import { useQueryState as useQueryState2, parseAsBoolean } from "nuqs";
|
|
208
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
209
|
+
function BooleanFilter({
|
|
210
|
+
queryKey = "enabled",
|
|
211
|
+
defaultValue = false,
|
|
212
|
+
disabled = false,
|
|
213
|
+
label,
|
|
214
|
+
description,
|
|
215
|
+
className = ""
|
|
216
|
+
}) {
|
|
217
|
+
const [value, setValue] = useQueryState2(queryKey, parseAsBoolean.withDefault(defaultValue));
|
|
218
|
+
const handleToggle = (checked) => {
|
|
219
|
+
setValue(checked === defaultValue ? null : checked);
|
|
220
|
+
};
|
|
221
|
+
return /* @__PURE__ */ jsxs2("div", { className: `flex items-center justify-between space-x-2 ${className}`, children: [
|
|
222
|
+
/* @__PURE__ */ jsxs2("div", { className: "space-y-0.5", children: [
|
|
223
|
+
/* @__PURE__ */ jsx2(Label2, { className: "text-sm font-medium", children: label }),
|
|
224
|
+
description && /* @__PURE__ */ jsx2("p", { className: "text-xs text-muted-foreground", children: description })
|
|
225
|
+
] }),
|
|
226
|
+
/* @__PURE__ */ jsx2(Switch, { checked: value, onCheckedChange: handleToggle, disabled })
|
|
227
|
+
] });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/filters/Filter.tsx
|
|
231
|
+
import { useQueryState as useQueryState3, parseAsString } from "nuqs";
|
|
232
|
+
import { Fragment as Fragment2, jsx as jsx3 } from "react/jsx-runtime";
|
|
233
|
+
function Filter({ queryKey, children, onValueChange }) {
|
|
234
|
+
const [value, setValue] = useQueryState3(queryKey, parseAsString.withDefault(""));
|
|
235
|
+
const handleValueChange = (newValue) => {
|
|
236
|
+
setValue(newValue || null);
|
|
237
|
+
onValueChange?.(newValue);
|
|
238
|
+
};
|
|
239
|
+
return /* @__PURE__ */ jsx3(Fragment2, { children: children({ value, onValueChange: handleValueChange }) });
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/filters/MultiSelectFilter.tsx
|
|
243
|
+
import { useQueryState as useQueryState4, parseAsArrayOf, parseAsString as parseAsString2 } from "nuqs";
|
|
244
|
+
import { Fragment as Fragment3, jsx as jsx4 } from "react/jsx-runtime";
|
|
245
|
+
function MultiSelectFilter({ queryKey, children, onValueChange }) {
|
|
246
|
+
const [value, setValue] = useQueryState4(queryKey, parseAsArrayOf(parseAsString2).withDefault([]));
|
|
247
|
+
const handleValueChange = (newValue) => {
|
|
248
|
+
setValue(newValue.length > 0 ? newValue : null);
|
|
249
|
+
onValueChange?.(newValue);
|
|
250
|
+
};
|
|
251
|
+
return /* @__PURE__ */ jsx4(Fragment3, { children: children({ value, onValueChange: handleValueChange }) });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/wrappers/FiltersGrid.tsx
|
|
255
|
+
import { cn as cn2 } from "@turtleclub/ui";
|
|
256
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
257
|
+
function FiltersGrid({
|
|
258
|
+
filters,
|
|
259
|
+
columns = 3,
|
|
260
|
+
className,
|
|
261
|
+
sectionClassName
|
|
262
|
+
}) {
|
|
263
|
+
const enabledFilters = filters.filter((filter) => filter.enabled);
|
|
264
|
+
if (enabledFilters.length === 0) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
const sections = enabledFilters.reduce(
|
|
268
|
+
(acc, filter) => {
|
|
269
|
+
const sectionName = filter.section || "default";
|
|
270
|
+
if (!acc[sectionName]) {
|
|
271
|
+
acc[sectionName] = [];
|
|
272
|
+
}
|
|
273
|
+
acc[sectionName].push(filter);
|
|
274
|
+
return acc;
|
|
275
|
+
},
|
|
276
|
+
{}
|
|
277
|
+
);
|
|
278
|
+
const sectionNames = Object.keys(sections);
|
|
279
|
+
return /* @__PURE__ */ jsx5("div", { className: cn2("space-y-4", className), children: sectionNames.map((sectionName, sectionIndex) => /* @__PURE__ */ jsxs3("div", { children: [
|
|
280
|
+
sectionIndex > 0 && /* @__PURE__ */ jsx5("div", { className: "border-t border-border mb-4" }),
|
|
281
|
+
/* @__PURE__ */ jsx5(
|
|
282
|
+
"div",
|
|
283
|
+
{
|
|
284
|
+
className: cn2("grid gap-2", sectionClassName),
|
|
285
|
+
style: {
|
|
286
|
+
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`
|
|
287
|
+
},
|
|
288
|
+
children: sections[sectionName].map((filter) => /* @__PURE__ */ jsx5("div", { className: "flex flex-col gap-2", children: filter.component }, filter.key))
|
|
289
|
+
}
|
|
290
|
+
)
|
|
291
|
+
] }, sectionName)) });
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// src/wrappers/FiltersPopover.tsx
|
|
295
|
+
import {
|
|
296
|
+
Popover as Popover2,
|
|
297
|
+
PopoverContent as PopoverContent2,
|
|
298
|
+
PopoverTrigger as PopoverTrigger2,
|
|
299
|
+
buttonVariants as buttonVariants2,
|
|
300
|
+
cn as cn3
|
|
301
|
+
} from "@turtleclub/ui";
|
|
302
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
303
|
+
function FiltersPopover({
|
|
304
|
+
filters,
|
|
305
|
+
triggerLabel = "Filters",
|
|
306
|
+
columns = 2,
|
|
307
|
+
className,
|
|
308
|
+
popoverClassName,
|
|
309
|
+
sectionClassName,
|
|
310
|
+
align = "start",
|
|
311
|
+
onClearAll
|
|
312
|
+
}) {
|
|
313
|
+
const enabledFilters = filters.filter((filter) => filter.enabled);
|
|
314
|
+
if (enabledFilters.length === 0) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
const sections = enabledFilters.reduce(
|
|
318
|
+
(acc, filter) => {
|
|
319
|
+
const sectionName = filter.section || "default";
|
|
320
|
+
if (!acc[sectionName]) {
|
|
321
|
+
acc[sectionName] = [];
|
|
322
|
+
}
|
|
323
|
+
acc[sectionName].push(filter);
|
|
324
|
+
return acc;
|
|
325
|
+
},
|
|
326
|
+
{}
|
|
327
|
+
);
|
|
328
|
+
const sectionNames = Object.keys(sections);
|
|
329
|
+
return /* @__PURE__ */ jsxs4(Popover2, { children: [
|
|
330
|
+
/* @__PURE__ */ jsx6(
|
|
331
|
+
PopoverTrigger2,
|
|
332
|
+
{
|
|
333
|
+
className: cn3(
|
|
334
|
+
buttonVariants2({
|
|
335
|
+
variant: "default",
|
|
336
|
+
size: "default",
|
|
337
|
+
border: "bordered"
|
|
338
|
+
}),
|
|
339
|
+
"!bg-neutral-alpha-2",
|
|
340
|
+
className
|
|
341
|
+
),
|
|
342
|
+
children: triggerLabel
|
|
343
|
+
}
|
|
344
|
+
),
|
|
345
|
+
/* @__PURE__ */ jsx6(
|
|
346
|
+
PopoverContent2,
|
|
347
|
+
{
|
|
348
|
+
className: cn3("w-auto min-w-[400px] max-w-[600px]", popoverClassName),
|
|
349
|
+
align,
|
|
350
|
+
children: /* @__PURE__ */ jsxs4("div", { className: "space-y-4", children: [
|
|
351
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between", children: [
|
|
352
|
+
/* @__PURE__ */ jsx6("h4", { className: "font-medium text-sm", children: "Filters" }),
|
|
353
|
+
onClearAll && /* @__PURE__ */ jsx6(
|
|
354
|
+
"button",
|
|
355
|
+
{
|
|
356
|
+
onClick: onClearAll,
|
|
357
|
+
className: "text-xs text-muted-foreground hover:text-foreground transition-colors",
|
|
358
|
+
children: "Reset all"
|
|
359
|
+
}
|
|
360
|
+
)
|
|
361
|
+
] }),
|
|
362
|
+
/* @__PURE__ */ jsx6("div", { className: "space-y-4", children: sectionNames.map((sectionName, sectionIndex) => /* @__PURE__ */ jsxs4("div", { children: [
|
|
363
|
+
sectionIndex > 0 && /* @__PURE__ */ jsx6("div", { className: "border-t border-border mb-4" }),
|
|
364
|
+
/* @__PURE__ */ jsx6(
|
|
365
|
+
"div",
|
|
366
|
+
{
|
|
367
|
+
className: cn3("grid gap-2", sectionClassName),
|
|
368
|
+
style: {
|
|
369
|
+
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`
|
|
370
|
+
},
|
|
371
|
+
children: sections[sectionName].map((filter) => /* @__PURE__ */ jsx6("div", { className: "flex flex-col gap-2", children: filter.component }, filter.key))
|
|
372
|
+
}
|
|
373
|
+
)
|
|
374
|
+
] }, sectionName)) })
|
|
375
|
+
] })
|
|
376
|
+
}
|
|
377
|
+
)
|
|
378
|
+
] });
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/wrappers/ActiveFilterBadges.tsx
|
|
382
|
+
import { useMemo } from "react";
|
|
383
|
+
import { Badge as Badge2, Button, cn as cn4 } from "@turtleclub/ui";
|
|
384
|
+
import { X } from "lucide-react";
|
|
385
|
+
import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
386
|
+
function ActiveFilterBadges({
|
|
387
|
+
filters,
|
|
388
|
+
className,
|
|
389
|
+
clearAllLabel = "Clear All",
|
|
390
|
+
showClearAll = true,
|
|
391
|
+
renderBadge,
|
|
392
|
+
onClearFilter,
|
|
393
|
+
onClearAll
|
|
394
|
+
}) {
|
|
395
|
+
const activeFilters = useMemo(() => {
|
|
396
|
+
return filters.filter((filter) => filter.isActive && filter.value);
|
|
397
|
+
}, [filters]);
|
|
398
|
+
const clearAll = () => {
|
|
399
|
+
const allQueryKeys = activeFilters.map((filter) => filter.queryKeys);
|
|
400
|
+
onClearAll(allQueryKeys);
|
|
401
|
+
};
|
|
402
|
+
if (activeFilters.length === 0) {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
return /* @__PURE__ */ jsxs5("div", { className: cn4("flex flex-wrap items-center gap-2", className), children: [
|
|
406
|
+
activeFilters.map((filter) => {
|
|
407
|
+
return /* @__PURE__ */ jsx7(
|
|
408
|
+
FilterBadge,
|
|
409
|
+
{
|
|
410
|
+
filter,
|
|
411
|
+
renderBadge,
|
|
412
|
+
onClearFilter
|
|
413
|
+
},
|
|
414
|
+
filter.key
|
|
415
|
+
);
|
|
416
|
+
}),
|
|
417
|
+
showClearAll && activeFilters.length > 1 && /* @__PURE__ */ jsx7(Button, { variant: "ghost", size: "sm", onClick: clearAll, className: "h-7 px-2 text-xs", children: clearAllLabel })
|
|
418
|
+
] });
|
|
419
|
+
}
|
|
420
|
+
function FilterBadge({ filter, renderBadge, onClearFilter }) {
|
|
421
|
+
const clearFilter = () => {
|
|
422
|
+
onClearFilter(filter.queryKeys);
|
|
423
|
+
};
|
|
424
|
+
if (renderBadge) {
|
|
425
|
+
return /* @__PURE__ */ jsx7(Fragment4, { children: renderBadge(filter, clearFilter) });
|
|
426
|
+
}
|
|
427
|
+
return /* @__PURE__ */ jsxs5(Badge2, { className: "flex items-center gap-1 pr-1 border-border border", children: [
|
|
428
|
+
/* @__PURE__ */ jsxs5("span", { className: "text-xs", children: [
|
|
429
|
+
filter.label,
|
|
430
|
+
": ",
|
|
431
|
+
filter.value
|
|
432
|
+
] }),
|
|
433
|
+
/* @__PURE__ */ jsx7(
|
|
434
|
+
"button",
|
|
435
|
+
{
|
|
436
|
+
onClick: clearFilter,
|
|
437
|
+
className: "ml-1 rounded-full p-0.5 hover:bg-black/10 focus:bg-black/10 focus:outline-none",
|
|
438
|
+
"aria-label": `Clear ${filter.label} filter`,
|
|
439
|
+
children: /* @__PURE__ */ jsx7(X, { className: "h-3 w-3" })
|
|
440
|
+
}
|
|
441
|
+
)
|
|
442
|
+
] });
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// src/wrappers/FiltersWrapper.tsx
|
|
446
|
+
import { cn as cn5 } from "@turtleclub/ui";
|
|
447
|
+
import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
448
|
+
function FiltersWrapper({
|
|
449
|
+
filters,
|
|
450
|
+
activeFilters,
|
|
451
|
+
layout,
|
|
452
|
+
className,
|
|
453
|
+
badgesClassName,
|
|
454
|
+
slotClassName,
|
|
455
|
+
leftSlot,
|
|
456
|
+
rightSlot,
|
|
457
|
+
columns = 3,
|
|
458
|
+
triggerLabel = "Filters",
|
|
459
|
+
popoverColumns = 2,
|
|
460
|
+
popoverClassName,
|
|
461
|
+
popoverContentClassName,
|
|
462
|
+
align = "start",
|
|
463
|
+
clearAllLabel = "Clear All",
|
|
464
|
+
showClearAll = true,
|
|
465
|
+
renderBadge,
|
|
466
|
+
showActiveBadges = true,
|
|
467
|
+
onClearFilter,
|
|
468
|
+
onClearAll
|
|
469
|
+
}) {
|
|
470
|
+
const hasActiveFilters = activeFilters.some((filter) => filter.isActive && filter.value);
|
|
471
|
+
return /* @__PURE__ */ jsxs6("div", { className: cn5("space-y-3", className), children: [
|
|
472
|
+
/* @__PURE__ */ jsxs6("div", { className: cn5("flex items-center gap-2", slotClassName), children: [
|
|
473
|
+
leftSlot,
|
|
474
|
+
/* @__PURE__ */ jsx8(Fragment5, { children: layout === "grid" ? /* @__PURE__ */ jsx8(FiltersGrid, { filters, columns, className: popoverClassName }) : /* @__PURE__ */ jsx8(
|
|
475
|
+
FiltersPopover,
|
|
476
|
+
{
|
|
477
|
+
filters,
|
|
478
|
+
triggerLabel,
|
|
479
|
+
columns: popoverColumns,
|
|
480
|
+
className: popoverClassName,
|
|
481
|
+
popoverClassName: popoverContentClassName,
|
|
482
|
+
align,
|
|
483
|
+
onClearAll: () => {
|
|
484
|
+
const allQueryKeys = activeFilters.map((filter) => filter.queryKeys).filter((keys) => keys !== void 0);
|
|
485
|
+
onClearAll(allQueryKeys);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
) }),
|
|
489
|
+
rightSlot
|
|
490
|
+
] }),
|
|
491
|
+
showActiveBadges && hasActiveFilters && /* @__PURE__ */ jsx8(
|
|
492
|
+
ActiveFilterBadges,
|
|
493
|
+
{
|
|
494
|
+
filters: activeFilters,
|
|
495
|
+
className: badgesClassName,
|
|
496
|
+
clearAllLabel,
|
|
497
|
+
showClearAll,
|
|
498
|
+
renderBadge,
|
|
499
|
+
onClearFilter,
|
|
500
|
+
onClearAll
|
|
501
|
+
}
|
|
502
|
+
)
|
|
503
|
+
] });
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// src/helpers/usePagination.ts
|
|
507
|
+
import { useQueryState as useQueryState5, parseAsInteger as parseAsInteger2 } from "nuqs";
|
|
508
|
+
import { useMemo as useMemo2, useCallback } from "react";
|
|
509
|
+
function usePagination(options = {}) {
|
|
510
|
+
const {
|
|
511
|
+
defaultPageSize = 10,
|
|
512
|
+
defaultPageIndex = 0,
|
|
513
|
+
pageIndexKey = "page",
|
|
514
|
+
pageSizeKey = "pageSize"
|
|
515
|
+
} = options;
|
|
516
|
+
const [rawPageIndex, setRawPageIndex] = useQueryState5(
|
|
517
|
+
pageIndexKey,
|
|
518
|
+
parseAsInteger2.withDefault(defaultPageIndex)
|
|
519
|
+
);
|
|
520
|
+
const [rawPageSize, setRawPageSize] = useQueryState5(
|
|
521
|
+
pageSizeKey,
|
|
522
|
+
parseAsInteger2.withDefault(defaultPageSize)
|
|
523
|
+
);
|
|
524
|
+
const pageIndex = useMemo2(() => Math.max(0, rawPageIndex), [rawPageIndex]);
|
|
525
|
+
const pageSize = rawPageSize;
|
|
526
|
+
const pagination = useMemo2(
|
|
527
|
+
() => ({
|
|
528
|
+
pageIndex,
|
|
529
|
+
pageSize
|
|
530
|
+
}),
|
|
531
|
+
[pageIndex, pageSize]
|
|
532
|
+
);
|
|
533
|
+
const setPagination = useCallback(
|
|
534
|
+
(updater) => {
|
|
535
|
+
const newPagination = typeof updater === "function" ? updater(pagination) : updater;
|
|
536
|
+
if (newPagination.pageIndex !== pageIndex) {
|
|
537
|
+
setRawPageIndex(Math.max(0, newPagination.pageIndex));
|
|
538
|
+
}
|
|
539
|
+
if (newPagination.pageSize !== pageSize) {
|
|
540
|
+
setRawPageSize(newPagination.pageSize);
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
[pagination, pageIndex, pageSize, setRawPageIndex, setRawPageSize]
|
|
544
|
+
);
|
|
545
|
+
const setPageIndex = useCallback(
|
|
546
|
+
(newPageIndex) => {
|
|
547
|
+
setRawPageIndex(Math.max(0, newPageIndex));
|
|
548
|
+
},
|
|
549
|
+
[setRawPageIndex]
|
|
550
|
+
);
|
|
551
|
+
const setPageSize = useCallback(
|
|
552
|
+
(newPageSize) => {
|
|
553
|
+
setRawPageSize(newPageSize);
|
|
554
|
+
setRawPageIndex(0);
|
|
555
|
+
},
|
|
556
|
+
[setRawPageSize, setRawPageIndex]
|
|
557
|
+
);
|
|
558
|
+
const nextPage = useCallback(
|
|
559
|
+
(totalPages) => {
|
|
560
|
+
const nextPageIndex = pageIndex + 1;
|
|
561
|
+
if (!totalPages || nextPageIndex < totalPages) {
|
|
562
|
+
setPageIndex(nextPageIndex);
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
[pageIndex, setPageIndex]
|
|
566
|
+
);
|
|
567
|
+
const previousPage = useCallback(() => {
|
|
568
|
+
if (pageIndex > 0) {
|
|
569
|
+
setPageIndex(pageIndex - 1);
|
|
570
|
+
}
|
|
571
|
+
}, [pageIndex, setPageIndex]);
|
|
572
|
+
const firstPage = useCallback(() => {
|
|
573
|
+
setPageIndex(0);
|
|
574
|
+
}, [setPageIndex]);
|
|
575
|
+
const lastPage = useCallback(
|
|
576
|
+
(totalPages) => {
|
|
577
|
+
setPageIndex(Math.max(0, totalPages - 1));
|
|
578
|
+
},
|
|
579
|
+
[setPageIndex]
|
|
580
|
+
);
|
|
581
|
+
const reset = useCallback(() => {
|
|
582
|
+
setRawPageIndex(defaultPageIndex);
|
|
583
|
+
setRawPageSize(defaultPageSize);
|
|
584
|
+
}, [setRawPageIndex, setRawPageSize, defaultPageIndex, defaultPageSize]);
|
|
585
|
+
const canPreviousPage = pageIndex > 0;
|
|
586
|
+
const canNextPage = useCallback(
|
|
587
|
+
(totalPages) => {
|
|
588
|
+
if (!totalPages) return true;
|
|
589
|
+
return pageIndex < totalPages - 1;
|
|
590
|
+
},
|
|
591
|
+
[pageIndex]
|
|
592
|
+
);
|
|
593
|
+
return {
|
|
594
|
+
pagination,
|
|
595
|
+
setPagination,
|
|
596
|
+
pageIndex,
|
|
597
|
+
pageSize,
|
|
598
|
+
setPageIndex,
|
|
599
|
+
setPageSize,
|
|
600
|
+
nextPage,
|
|
601
|
+
previousPage,
|
|
602
|
+
firstPage,
|
|
603
|
+
lastPage,
|
|
604
|
+
reset,
|
|
605
|
+
canNextPage,
|
|
606
|
+
canPreviousPage
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// src/helpers/useSorting.ts
|
|
611
|
+
import { useQueryState as useQueryState6, parseAsString as parseAsString3 } from "nuqs";
|
|
612
|
+
import { useMemo as useMemo3, useCallback as useCallback2 } from "react";
|
|
613
|
+
function useSorting(options = {}) {
|
|
614
|
+
const {
|
|
615
|
+
defaultSortBy,
|
|
616
|
+
defaultSortOrder = "asc",
|
|
617
|
+
sortByKey = "sortBy",
|
|
618
|
+
sortOrderKey = "sortOrder"
|
|
619
|
+
} = options;
|
|
620
|
+
const [sortBy, setSortBy] = useQueryState6(
|
|
621
|
+
sortByKey,
|
|
622
|
+
parseAsString3.withDefault(defaultSortBy || "")
|
|
623
|
+
);
|
|
624
|
+
const [sortOrder, setSortOrder] = useQueryState6(
|
|
625
|
+
sortOrderKey,
|
|
626
|
+
parseAsString3.withDefault(defaultSortOrder)
|
|
627
|
+
);
|
|
628
|
+
const validSortOrder = useMemo3(() => {
|
|
629
|
+
return sortOrder === "desc" ? "desc" : "asc";
|
|
630
|
+
}, [sortOrder]);
|
|
631
|
+
const sorting = useMemo3(() => {
|
|
632
|
+
if (!sortBy || sortBy.trim() === "") {
|
|
633
|
+
return [];
|
|
634
|
+
}
|
|
635
|
+
return [
|
|
636
|
+
{
|
|
637
|
+
id: sortBy,
|
|
638
|
+
desc: validSortOrder === "desc"
|
|
639
|
+
}
|
|
640
|
+
];
|
|
641
|
+
}, [sortBy, validSortOrder]);
|
|
642
|
+
const setSorting = useCallback2(
|
|
643
|
+
(updater) => {
|
|
644
|
+
const newSorting = typeof updater === "function" ? updater(sorting) : updater;
|
|
645
|
+
if (newSorting.length === 0) {
|
|
646
|
+
setSortBy("");
|
|
647
|
+
} else {
|
|
648
|
+
const firstSort = newSorting[0];
|
|
649
|
+
setSortBy(firstSort.id);
|
|
650
|
+
setSortOrder(firstSort.desc ? "desc" : "asc");
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
[sorting, setSortBy, setSortOrder]
|
|
654
|
+
);
|
|
655
|
+
const setSort = useCallback2(
|
|
656
|
+
(column, order = "asc") => {
|
|
657
|
+
setSortBy(column);
|
|
658
|
+
setSortOrder(order);
|
|
659
|
+
},
|
|
660
|
+
[setSortBy, setSortOrder]
|
|
661
|
+
);
|
|
662
|
+
const clearSort = useCallback2(() => {
|
|
663
|
+
setSortBy(null);
|
|
664
|
+
}, [setSortBy]);
|
|
665
|
+
const toggleSort = useCallback2(
|
|
666
|
+
(column) => {
|
|
667
|
+
if (sortBy === column) {
|
|
668
|
+
setSortOrder(validSortOrder === "asc" ? "desc" : "asc");
|
|
669
|
+
} else {
|
|
670
|
+
setSortBy(column);
|
|
671
|
+
setSortOrder("asc");
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
[sortBy, validSortOrder, setSortBy, setSortOrder]
|
|
675
|
+
);
|
|
676
|
+
const isSorted = useCallback2(
|
|
677
|
+
(column) => {
|
|
678
|
+
return sortBy === column;
|
|
679
|
+
},
|
|
680
|
+
[sortBy]
|
|
681
|
+
);
|
|
682
|
+
const getSortOrder = useCallback2(
|
|
683
|
+
(column) => {
|
|
684
|
+
return sortBy === column ? validSortOrder : void 0;
|
|
685
|
+
},
|
|
686
|
+
[sortBy, validSortOrder]
|
|
687
|
+
);
|
|
688
|
+
return {
|
|
689
|
+
sorting,
|
|
690
|
+
setSorting,
|
|
691
|
+
sortBy: sortBy || void 0,
|
|
692
|
+
sortOrder: validSortOrder,
|
|
693
|
+
setSort,
|
|
694
|
+
clearSort,
|
|
695
|
+
toggleSort,
|
|
696
|
+
isSorted,
|
|
697
|
+
getSortOrder
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// src/selectors/ChainsSelector.tsx
|
|
702
|
+
import { useMemo as useMemo4 } from "react";
|
|
703
|
+
import { MultiSelect } from "@turtleclub/ui";
|
|
704
|
+
import { useSupportedChains } from "@turtleclub/hooks";
|
|
705
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
706
|
+
function ChainsSelector({
|
|
707
|
+
value,
|
|
708
|
+
onValueChange,
|
|
709
|
+
placeholder = "Select chains",
|
|
710
|
+
disabled = false,
|
|
711
|
+
className,
|
|
712
|
+
maxCount = 1,
|
|
713
|
+
closeOnSelect = true,
|
|
714
|
+
searchable = true
|
|
715
|
+
}) {
|
|
716
|
+
const { chains, isLoading } = useSupportedChains({
|
|
717
|
+
page: 1,
|
|
718
|
+
limit: 100
|
|
719
|
+
// High limit to fetch all chains
|
|
720
|
+
});
|
|
721
|
+
const chainOptions = useMemo4(() => {
|
|
722
|
+
if (!chains || chains.length === 0) return [];
|
|
723
|
+
const filteredChains = chains.filter((chain) => chain.status === "active");
|
|
724
|
+
return filteredChains.map((chain) => ({
|
|
725
|
+
label: chain.name,
|
|
726
|
+
value: chain?.id ?? "",
|
|
727
|
+
icon: chain.logoUrl ? () => /* @__PURE__ */ jsx9(
|
|
728
|
+
"img",
|
|
729
|
+
{
|
|
730
|
+
src: chain.logoUrl,
|
|
731
|
+
alt: chain.name,
|
|
732
|
+
width: 16,
|
|
733
|
+
height: 16,
|
|
734
|
+
className: "size-4 rounded-full"
|
|
735
|
+
}
|
|
736
|
+
) : void 0
|
|
737
|
+
}));
|
|
738
|
+
}, [chains]);
|
|
739
|
+
return /* @__PURE__ */ jsx9(
|
|
740
|
+
MultiSelect,
|
|
741
|
+
{
|
|
742
|
+
searchable,
|
|
743
|
+
options: chainOptions,
|
|
744
|
+
value,
|
|
745
|
+
onValueChange,
|
|
746
|
+
disabled: disabled || isLoading,
|
|
747
|
+
placeholder: isLoading ? "Loading chains..." : placeholder,
|
|
748
|
+
closeOnSelect,
|
|
749
|
+
maxCount,
|
|
750
|
+
className
|
|
751
|
+
}
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// src/selectors/TokensSelector.tsx
|
|
756
|
+
import { useEffect as useEffect2, useMemo as useMemo5 } from "react";
|
|
757
|
+
import { MultiSelect as MultiSelect2 } from "@turtleclub/ui";
|
|
758
|
+
import { useSupportedTokens } from "@turtleclub/hooks";
|
|
759
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
760
|
+
function TokensSelector({
|
|
761
|
+
value,
|
|
762
|
+
onValueChange,
|
|
763
|
+
chainId,
|
|
764
|
+
placeholder = "Select tokens",
|
|
765
|
+
disabled = false,
|
|
766
|
+
className,
|
|
767
|
+
maxCount = 1,
|
|
768
|
+
closeOnSelect = true,
|
|
769
|
+
searchable = true
|
|
770
|
+
}) {
|
|
771
|
+
const isChainSelected = !!chainId && chainId.trim() !== "";
|
|
772
|
+
const { tokens, isLoading } = useSupportedTokens({
|
|
773
|
+
chainId: isChainSelected ? chainId : "",
|
|
774
|
+
limit: 9e3,
|
|
775
|
+
enabled: isChainSelected
|
|
776
|
+
});
|
|
777
|
+
const tokenOptions = useMemo5(() => {
|
|
778
|
+
if (!tokens) return [];
|
|
779
|
+
return tokens.map((token) => ({
|
|
780
|
+
label: `${token.symbol} - ${token.name}`,
|
|
781
|
+
value: token?.id || "",
|
|
782
|
+
icon: token.logoUrl ? () => /* @__PURE__ */ jsx10(
|
|
783
|
+
"img",
|
|
784
|
+
{
|
|
785
|
+
src: token.logoUrl,
|
|
786
|
+
alt: token.name,
|
|
787
|
+
width: 16,
|
|
788
|
+
height: 16,
|
|
789
|
+
className: "size-4 rounded-full"
|
|
790
|
+
}
|
|
791
|
+
) : void 0
|
|
792
|
+
}));
|
|
793
|
+
}, [tokens]);
|
|
794
|
+
useEffect2(() => {
|
|
795
|
+
if (!isChainSelected && value.length > 0) {
|
|
796
|
+
onValueChange([]);
|
|
797
|
+
}
|
|
798
|
+
}, [isChainSelected, value, onValueChange]);
|
|
799
|
+
return /* @__PURE__ */ jsx10(
|
|
800
|
+
MultiSelect2,
|
|
801
|
+
{
|
|
802
|
+
searchable,
|
|
803
|
+
options: tokenOptions,
|
|
804
|
+
value,
|
|
805
|
+
onValueChange,
|
|
806
|
+
disabled: disabled || isLoading || !isChainSelected,
|
|
807
|
+
placeholder: !isChainSelected ? "Select a chain first" : isLoading ? "Loading tokens..." : placeholder,
|
|
808
|
+
closeOnSelect,
|
|
809
|
+
maxCount,
|
|
810
|
+
className
|
|
811
|
+
}
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// src/selectors/ProductsSelector.tsx
|
|
816
|
+
import { useMemo as useMemo6 } from "react";
|
|
817
|
+
import { MultiSelect as MultiSelect3 } from "@turtleclub/ui";
|
|
818
|
+
import { useProducts } from "@turtleclub/hooks";
|
|
819
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
820
|
+
function ProductsSelector({
|
|
821
|
+
value,
|
|
822
|
+
onValueChange,
|
|
823
|
+
placeholder = "Select products",
|
|
824
|
+
disabled = false,
|
|
825
|
+
className,
|
|
826
|
+
maxCount = 1,
|
|
827
|
+
closeOnSelect = true,
|
|
828
|
+
searchable = true
|
|
829
|
+
}) {
|
|
830
|
+
const { data: productsData, isLoading } = useProducts({});
|
|
831
|
+
const products = useMemo6(() => productsData?.products ?? [], [productsData?.products]);
|
|
832
|
+
const productOptions = useMemo6(() => {
|
|
833
|
+
if (!products || products.length === 0) return [];
|
|
834
|
+
return products.map((product) => ({
|
|
835
|
+
label: product.name,
|
|
836
|
+
value: product.id,
|
|
837
|
+
icon: product.logoUrl ? () => /* @__PURE__ */ jsx11(
|
|
838
|
+
"img",
|
|
839
|
+
{
|
|
840
|
+
src: product.logoUrl,
|
|
841
|
+
alt: product.name,
|
|
842
|
+
width: 16,
|
|
843
|
+
height: 16,
|
|
844
|
+
className: "size-4 rounded-full"
|
|
845
|
+
}
|
|
846
|
+
) : void 0
|
|
847
|
+
}));
|
|
848
|
+
}, [products]);
|
|
849
|
+
return /* @__PURE__ */ jsx11(
|
|
850
|
+
MultiSelect3,
|
|
851
|
+
{
|
|
852
|
+
searchable,
|
|
853
|
+
options: productOptions,
|
|
854
|
+
value,
|
|
855
|
+
onValueChange,
|
|
856
|
+
disabled: disabled || isLoading,
|
|
857
|
+
placeholder: isLoading ? "Loading products..." : placeholder,
|
|
858
|
+
closeOnSelect,
|
|
859
|
+
maxCount,
|
|
860
|
+
className
|
|
861
|
+
}
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// src/selectors/OpportunitiesSelector.tsx
|
|
866
|
+
import { useMemo as useMemo7 } from "react";
|
|
867
|
+
import { MultiSelect as MultiSelect4 } from "@turtleclub/ui";
|
|
868
|
+
import { useOpportunities } from "@turtleclub/hooks";
|
|
869
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
870
|
+
function OpportunitiesSelector({
|
|
871
|
+
value,
|
|
872
|
+
onValueChange,
|
|
873
|
+
placeholder = "Select opportunities",
|
|
874
|
+
disabled = false,
|
|
875
|
+
className,
|
|
876
|
+
maxCount = 1,
|
|
877
|
+
closeOnSelect = true,
|
|
878
|
+
searchable = true
|
|
879
|
+
}) {
|
|
880
|
+
const { data: opportunitiesData, isLoading } = useOpportunities();
|
|
881
|
+
const opportunities = useMemo7(
|
|
882
|
+
() => opportunitiesData?.opportunities ?? [],
|
|
883
|
+
[opportunitiesData?.opportunities]
|
|
884
|
+
);
|
|
885
|
+
const opportunityOptions = useMemo7(() => {
|
|
886
|
+
if (!opportunities || opportunities.length === 0) return [];
|
|
887
|
+
const filteredOpportunities = opportunities.filter((opp) => opp.status === "active");
|
|
888
|
+
return filteredOpportunities.map((opportunity) => ({
|
|
889
|
+
label: opportunity.name || opportunity.shortName,
|
|
890
|
+
value: opportunity.id,
|
|
891
|
+
description: opportunity.shortName !== opportunity.name ? opportunity.shortName : void 0,
|
|
892
|
+
icon: opportunity.depositTokens?.[0]?.logoUrl ? () => /* @__PURE__ */ jsx12(
|
|
893
|
+
"img",
|
|
894
|
+
{
|
|
895
|
+
src: opportunity.depositTokens[0].logoUrl,
|
|
896
|
+
alt: opportunity.name,
|
|
897
|
+
width: 16,
|
|
898
|
+
height: 16,
|
|
899
|
+
className: "size-4 rounded-full"
|
|
900
|
+
}
|
|
901
|
+
) : void 0
|
|
902
|
+
}));
|
|
903
|
+
}, [opportunities]);
|
|
904
|
+
return /* @__PURE__ */ jsx12(
|
|
905
|
+
MultiSelect4,
|
|
906
|
+
{
|
|
907
|
+
searchable,
|
|
908
|
+
options: opportunityOptions,
|
|
909
|
+
value,
|
|
910
|
+
onValueChange,
|
|
911
|
+
disabled: disabled || isLoading,
|
|
912
|
+
placeholder: isLoading ? "Loading opportunities..." : placeholder,
|
|
913
|
+
closeOnSelect,
|
|
914
|
+
maxCount,
|
|
915
|
+
className
|
|
916
|
+
}
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// src/selectors/ChainSelector.tsx
|
|
921
|
+
import { useMemo as useMemo8 } from "react";
|
|
922
|
+
import { Combobox } from "@turtleclub/ui";
|
|
923
|
+
import { useSupportedChains as useSupportedChains2 } from "@turtleclub/hooks";
|
|
924
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
925
|
+
function ChainSelector({
|
|
926
|
+
value,
|
|
927
|
+
onValueChange,
|
|
928
|
+
placeholder = "Select chain",
|
|
929
|
+
disabled = false,
|
|
930
|
+
className,
|
|
931
|
+
closeOnSelect = true,
|
|
932
|
+
searchable = true
|
|
933
|
+
}) {
|
|
934
|
+
const { chains, isLoading } = useSupportedChains2({
|
|
935
|
+
page: 1,
|
|
936
|
+
limit: 100
|
|
937
|
+
// High limit to fetch all chains
|
|
938
|
+
});
|
|
939
|
+
const chainOptions = useMemo8(() => {
|
|
940
|
+
if (!chains || chains.length === 0) return [];
|
|
941
|
+
const filteredChains = chains.filter((chain) => chain.status === "active");
|
|
942
|
+
return filteredChains.map((chain) => ({
|
|
943
|
+
label: chain.name,
|
|
944
|
+
value: chain?.id ?? "",
|
|
945
|
+
icon: chain.logoUrl ? () => /* @__PURE__ */ jsx13(
|
|
946
|
+
"img",
|
|
947
|
+
{
|
|
948
|
+
src: chain.logoUrl,
|
|
949
|
+
alt: chain.name,
|
|
950
|
+
width: 16,
|
|
951
|
+
height: 16,
|
|
952
|
+
className: "size-4 rounded-full"
|
|
953
|
+
}
|
|
954
|
+
) : void 0
|
|
955
|
+
}));
|
|
956
|
+
}, [chains]);
|
|
957
|
+
return /* @__PURE__ */ jsx13(
|
|
958
|
+
Combobox,
|
|
959
|
+
{
|
|
960
|
+
searchable,
|
|
961
|
+
options: chainOptions,
|
|
962
|
+
value,
|
|
963
|
+
onValueChange,
|
|
964
|
+
disabled: disabled || isLoading,
|
|
965
|
+
placeholder: isLoading ? "Loading chains..." : placeholder,
|
|
966
|
+
closeOnSelect,
|
|
967
|
+
className
|
|
968
|
+
}
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// src/selectors/TokenSelector.tsx
|
|
973
|
+
import { useEffect as useEffect3, useMemo as useMemo9 } from "react";
|
|
974
|
+
import { Combobox as Combobox2 } from "@turtleclub/ui";
|
|
975
|
+
import { useSupportedTokens as useSupportedTokens2 } from "@turtleclub/hooks";
|
|
976
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
977
|
+
function TokenSelector({
|
|
978
|
+
value,
|
|
979
|
+
onValueChange,
|
|
980
|
+
chainId,
|
|
981
|
+
placeholder = "Select token",
|
|
982
|
+
disabled = false,
|
|
983
|
+
className,
|
|
984
|
+
closeOnSelect = true,
|
|
985
|
+
searchable = true
|
|
986
|
+
}) {
|
|
987
|
+
const isChainSelected = !!chainId && chainId.trim() !== "";
|
|
988
|
+
const { tokens, isLoading } = useSupportedTokens2({
|
|
989
|
+
chainId: isChainSelected ? chainId : "",
|
|
990
|
+
limit: 9e3,
|
|
991
|
+
enabled: isChainSelected
|
|
992
|
+
});
|
|
993
|
+
const tokenOptions = useMemo9(() => {
|
|
994
|
+
if (!tokens) return [];
|
|
995
|
+
return tokens.map((token) => ({
|
|
996
|
+
label: `${token.symbol} - ${token.name}`,
|
|
997
|
+
value: token?.id || "",
|
|
998
|
+
icon: token.logoUrl ? () => /* @__PURE__ */ jsx14(
|
|
999
|
+
"img",
|
|
1000
|
+
{
|
|
1001
|
+
src: token.logoUrl,
|
|
1002
|
+
alt: token.name,
|
|
1003
|
+
width: 16,
|
|
1004
|
+
height: 16,
|
|
1005
|
+
className: "size-4 rounded-full"
|
|
1006
|
+
}
|
|
1007
|
+
) : void 0
|
|
1008
|
+
}));
|
|
1009
|
+
}, [tokens]);
|
|
1010
|
+
useEffect3(() => {
|
|
1011
|
+
if (!isChainSelected && value) {
|
|
1012
|
+
onValueChange("");
|
|
1013
|
+
}
|
|
1014
|
+
}, [isChainSelected, value, onValueChange]);
|
|
1015
|
+
return /* @__PURE__ */ jsx14(
|
|
1016
|
+
Combobox2,
|
|
1017
|
+
{
|
|
1018
|
+
searchable,
|
|
1019
|
+
options: tokenOptions,
|
|
1020
|
+
value,
|
|
1021
|
+
onValueChange,
|
|
1022
|
+
disabled: disabled || isLoading || !isChainSelected,
|
|
1023
|
+
placeholder: !isChainSelected ? "Select a chain first" : isLoading ? "Loading tokens..." : placeholder,
|
|
1024
|
+
closeOnSelect,
|
|
1025
|
+
className
|
|
1026
|
+
}
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// src/selectors/ProductSelector.tsx
|
|
1031
|
+
import { useMemo as useMemo10 } from "react";
|
|
1032
|
+
import { Combobox as Combobox3 } from "@turtleclub/ui";
|
|
1033
|
+
import { useProducts as useProducts2 } from "@turtleclub/hooks";
|
|
1034
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
1035
|
+
function ProductSelector({
|
|
1036
|
+
value,
|
|
1037
|
+
onValueChange,
|
|
1038
|
+
placeholder = "Select product",
|
|
1039
|
+
disabled = false,
|
|
1040
|
+
className,
|
|
1041
|
+
closeOnSelect = true,
|
|
1042
|
+
searchable = true
|
|
1043
|
+
}) {
|
|
1044
|
+
const { data: productsData, isLoading } = useProducts2({});
|
|
1045
|
+
const products = useMemo10(() => productsData?.products ?? [], [productsData?.products]);
|
|
1046
|
+
const productOptions = useMemo10(() => {
|
|
1047
|
+
if (!products || products.length === 0) return [];
|
|
1048
|
+
return products.map((product) => ({
|
|
1049
|
+
label: product.name,
|
|
1050
|
+
value: product.id,
|
|
1051
|
+
icon: product.logoUrl ? () => /* @__PURE__ */ jsx15(
|
|
1052
|
+
"img",
|
|
1053
|
+
{
|
|
1054
|
+
src: product.logoUrl,
|
|
1055
|
+
alt: product.name,
|
|
1056
|
+
width: 16,
|
|
1057
|
+
height: 16,
|
|
1058
|
+
className: "size-4 rounded-full"
|
|
1059
|
+
}
|
|
1060
|
+
) : void 0
|
|
1061
|
+
}));
|
|
1062
|
+
}, [products]);
|
|
1063
|
+
return /* @__PURE__ */ jsx15(
|
|
1064
|
+
Combobox3,
|
|
1065
|
+
{
|
|
1066
|
+
searchable,
|
|
1067
|
+
options: productOptions,
|
|
1068
|
+
value,
|
|
1069
|
+
onValueChange,
|
|
1070
|
+
disabled: disabled || isLoading,
|
|
1071
|
+
placeholder: isLoading ? "Loading products..." : placeholder,
|
|
1072
|
+
closeOnSelect,
|
|
1073
|
+
className
|
|
1074
|
+
}
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// src/selectors/OpportunitySelector.tsx
|
|
1079
|
+
import { useMemo as useMemo11 } from "react";
|
|
1080
|
+
import { Combobox as Combobox4 } from "@turtleclub/ui";
|
|
1081
|
+
import { useOpportunities as useOpportunities2 } from "@turtleclub/hooks";
|
|
1082
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
1083
|
+
function OpportunitySelector({
|
|
1084
|
+
value,
|
|
1085
|
+
onValueChange,
|
|
1086
|
+
placeholder = "Select opportunity",
|
|
1087
|
+
disabled = false,
|
|
1088
|
+
className,
|
|
1089
|
+
closeOnSelect = true,
|
|
1090
|
+
searchable = true
|
|
1091
|
+
}) {
|
|
1092
|
+
const { data: opportunitiesData, isLoading } = useOpportunities2();
|
|
1093
|
+
const opportunities = useMemo11(
|
|
1094
|
+
() => opportunitiesData?.opportunities ?? [],
|
|
1095
|
+
[opportunitiesData?.opportunities]
|
|
1096
|
+
);
|
|
1097
|
+
const opportunityOptions = useMemo11(() => {
|
|
1098
|
+
if (!opportunities || opportunities.length === 0) return [];
|
|
1099
|
+
const filteredOpportunities = opportunities.filter((opp) => opp.status === "active");
|
|
1100
|
+
return filteredOpportunities.map((opportunity) => ({
|
|
1101
|
+
label: opportunity.name || opportunity.shortName,
|
|
1102
|
+
value: opportunity.id,
|
|
1103
|
+
description: opportunity.shortName !== opportunity.name ? opportunity.shortName : void 0,
|
|
1104
|
+
icon: opportunity.depositTokens?.[0]?.logoUrl ? () => /* @__PURE__ */ jsx16(
|
|
1105
|
+
"img",
|
|
1106
|
+
{
|
|
1107
|
+
src: opportunity.depositTokens[0].logoUrl,
|
|
1108
|
+
alt: opportunity.name,
|
|
1109
|
+
width: 16,
|
|
1110
|
+
height: 16,
|
|
1111
|
+
className: "size-4 rounded-full"
|
|
1112
|
+
}
|
|
1113
|
+
) : void 0
|
|
1114
|
+
}));
|
|
1115
|
+
}, [opportunities]);
|
|
1116
|
+
return /* @__PURE__ */ jsx16(
|
|
1117
|
+
Combobox4,
|
|
1118
|
+
{
|
|
1119
|
+
searchable,
|
|
1120
|
+
options: opportunityOptions,
|
|
1121
|
+
value,
|
|
1122
|
+
onValueChange,
|
|
1123
|
+
disabled: disabled || isLoading,
|
|
1124
|
+
placeholder: isLoading ? "Loading opportunities..." : placeholder,
|
|
1125
|
+
closeOnSelect,
|
|
1126
|
+
className
|
|
1127
|
+
}
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
export {
|
|
1131
|
+
ActiveFilterBadges,
|
|
1132
|
+
BooleanFilter,
|
|
1133
|
+
ChainSelector,
|
|
1134
|
+
ChainsSelector,
|
|
1135
|
+
Filter,
|
|
1136
|
+
FiltersGrid,
|
|
1137
|
+
FiltersPopover,
|
|
1138
|
+
FiltersWrapper,
|
|
1139
|
+
MultiSelectFilter,
|
|
1140
|
+
OpportunitiesSelector,
|
|
1141
|
+
OpportunitySelector,
|
|
1142
|
+
ProductSelector,
|
|
1143
|
+
ProductsSelector,
|
|
1144
|
+
RangeSliderFilter,
|
|
1145
|
+
TokenSelector,
|
|
1146
|
+
TokensSelector,
|
|
1147
|
+
usePagination,
|
|
1148
|
+
useSorting
|
|
1149
|
+
};
|
|
1150
|
+
//# sourceMappingURL=index.js.map
|