@turtleclub/core 0.1.0-beta.12 → 0.1.0-beta.121

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 CHANGED
@@ -59,13 +59,18 @@ function RangeSliderFilter({
59
59
  label = "Range",
60
60
  showValues = true,
61
61
  formatValue = defaultFormatter,
62
- className = ""
62
+ className = "",
63
+ usePopover = false
63
64
  }) {
64
65
  const [minValue, setMinValue] = (0, import_nuqs.useQueryState)(minQueryKey, import_nuqs.parseAsInteger.withDefault(min));
65
66
  const [maxValue, setMaxValue] = (0, import_nuqs.useQueryState)(maxQueryKey, import_nuqs.parseAsInteger.withDefault(max));
66
67
  const [localRange, setLocalRange] = (0, import_react.useState)([minValue, maxValue]);
68
+ const [minInputValue, setMinInputValue] = (0, import_react.useState)(minValue.toString());
69
+ const [maxInputValue, setMaxInputValue] = (0, import_react.useState)(maxValue.toString());
67
70
  (0, import_react.useEffect)(() => {
68
71
  setLocalRange([minValue, maxValue]);
72
+ setMinInputValue(minValue.toString());
73
+ setMaxInputValue(maxValue.toString());
69
74
  }, [minValue, maxValue]);
70
75
  const handleRangeChange = (values) => {
71
76
  setLocalRange(values);
@@ -79,8 +84,46 @@ function RangeSliderFilter({
79
84
  setMaxValue(newMax === max ? null : newMax);
80
85
  }
81
86
  };
87
+ const handleMinInputChange = (e) => {
88
+ setMinInputValue(e.target.value);
89
+ };
90
+ const handleMaxInputChange = (e) => {
91
+ setMaxInputValue(e.target.value);
92
+ };
93
+ const handleMinInputBlur = () => {
94
+ const numValue = parseInt(minInputValue, 10);
95
+ if (!isNaN(numValue)) {
96
+ const clampedValue = Math.max(min, Math.min(numValue, localRange[1]));
97
+ setLocalRange([clampedValue, localRange[1]]);
98
+ handleRangeCommit([clampedValue, localRange[1]]);
99
+ setMinInputValue(clampedValue.toString());
100
+ } else {
101
+ setMinInputValue(localRange[0].toString());
102
+ }
103
+ };
104
+ const handleMaxInputBlur = () => {
105
+ const numValue = parseInt(maxInputValue, 10);
106
+ if (!isNaN(numValue)) {
107
+ const clampedValue = Math.min(max, Math.max(numValue, localRange[0]));
108
+ setLocalRange([localRange[0], clampedValue]);
109
+ handleRangeCommit([localRange[0], clampedValue]);
110
+ setMaxInputValue(clampedValue.toString());
111
+ } else {
112
+ setMaxInputValue(localRange[1].toString());
113
+ }
114
+ };
115
+ const handleMinInputKeyDown = (e) => {
116
+ if (e.key === "Enter") {
117
+ handleMinInputBlur();
118
+ }
119
+ };
120
+ const handleMaxInputKeyDown = (e) => {
121
+ if (e.key === "Enter") {
122
+ handleMaxInputBlur();
123
+ }
124
+ };
82
125
  const isDefaultRange = localRange[0] === min && localRange[1] === max;
83
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `space-y-3 ${className}`, children: [
126
+ const filterContent = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-3", children: [
84
127
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between", children: [
85
128
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ui.Label, { className: "text-sm font-medium", children: label }),
86
129
  showValues && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
@@ -102,15 +145,53 @@ function RangeSliderFilter({
102
145
  className: "w-full"
103
146
  }
104
147
  ) }),
105
- showValues && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex justify-between text-xs text-muted-foreground", children: [
106
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatValue(min) }),
107
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatValue(max) })
148
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex justify-between gap-2", children: [
149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1 flex-1", children: [
150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ui.Label, { htmlFor: "min-input", className: "text-xs text-muted-foreground", children: "Min" }),
151
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
152
+ import_ui.Input,
153
+ {
154
+ id: "min-input",
155
+ type: "number",
156
+ value: minInputValue,
157
+ onChange: handleMinInputChange,
158
+ onBlur: handleMinInputBlur,
159
+ onKeyDown: handleMinInputKeyDown,
160
+ min,
161
+ max: localRange[1],
162
+ step,
163
+ disabled,
164
+ 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]"
165
+ }
166
+ )
167
+ ] }),
168
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1 flex-1", children: [
169
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ui.Label, { htmlFor: "max-input", className: "text-xs text-muted-foreground text-right", children: "Max" }),
170
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
171
+ import_ui.Input,
172
+ {
173
+ id: "max-input",
174
+ type: "number",
175
+ value: maxInputValue,
176
+ onChange: handleMaxInputChange,
177
+ onBlur: handleMaxInputBlur,
178
+ onKeyDown: handleMaxInputKeyDown,
179
+ min: localRange[0],
180
+ max,
181
+ step,
182
+ disabled,
183
+ 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]"
184
+ }
185
+ )
186
+ ] })
108
187
  ] }),
109
188
  !isDefaultRange && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
110
189
  "button",
111
190
  {
112
191
  onClick: () => {
113
192
  setLocalRange([min, max]);
193
+ setMinInputValue(min.toString());
194
+ setMaxInputValue(max.toString());
114
195
  handleRangeCommit([min, max]);
115
196
  },
116
197
  className: "text-xs text-muted-foreground hover:text-foreground transition-colors",
@@ -119,6 +200,40 @@ function RangeSliderFilter({
119
200
  }
120
201
  ) })
121
202
  ] });
203
+ if (usePopover) {
204
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ui.Popover, { children: [
205
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
206
+ import_ui.PopoverTrigger,
207
+ {
208
+ className: (0, import_ui.cn)(
209
+ (0, import_ui.buttonVariants)({
210
+ variant: "default",
211
+ size: "default",
212
+ border: "bordered"
213
+ }),
214
+ "!bg-neutral-alpha-2",
215
+ className
216
+ ),
217
+ disabled,
218
+ children: [
219
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "font-medium", children: [
220
+ label,
221
+ ":"
222
+ ] }),
223
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-muted-foreground", children: showValues && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
224
+ "[",
225
+ formatValue(localRange[0]),
226
+ " - ",
227
+ formatValue(localRange[1]),
228
+ "]"
229
+ ] }) })
230
+ ]
231
+ }
232
+ ),
233
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ui.PopoverContent, { className: "min-w-96", children: filterContent })
234
+ ] });
235
+ }
236
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, children: filterContent });
122
237
  }
123
238
 
124
239
  // src/filters/BooleanFilter.tsx
@@ -212,7 +327,6 @@ function FiltersGrid({
212
327
 
213
328
  // src/wrappers/FiltersPopover.tsx
214
329
  var import_ui4 = require("@turtleclub/ui");
215
- var import_lucide_react = require("lucide-react");
216
330
  var import_jsx_runtime6 = require("react/jsx-runtime");
217
331
  function FiltersPopover({
218
332
  filters,
@@ -220,7 +334,9 @@ function FiltersPopover({
220
334
  columns = 2,
221
335
  className,
222
336
  popoverClassName,
223
- sectionClassName
337
+ sectionClassName,
338
+ align = "start",
339
+ onClearAll
224
340
  }) {
225
341
  const enabledFilters = filters.filter((filter) => filter.enabled);
226
342
  if (enabledFilters.length === 0) {
@@ -239,17 +355,38 @@ function FiltersPopover({
239
355
  );
240
356
  const sectionNames = Object.keys(sections);
241
357
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ui4.Popover, { children: [
242
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ui4.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ui4.Button, { variant: "outline", className: (0, import_ui4.cn)(className), children: [
243
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react.SlidersHorizontal, { className: "mr-2 h-4 w-4" }),
244
- triggerLabel
245
- ] }) }),
358
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
359
+ import_ui4.PopoverTrigger,
360
+ {
361
+ className: (0, import_ui4.cn)(
362
+ (0, import_ui4.buttonVariants)({
363
+ variant: "default",
364
+ size: "default",
365
+ border: "bordered"
366
+ }),
367
+ "!bg-neutral-alpha-2",
368
+ className
369
+ ),
370
+ children: triggerLabel
371
+ }
372
+ ),
246
373
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
247
374
  import_ui4.PopoverContent,
248
375
  {
249
376
  className: (0, import_ui4.cn)("w-auto min-w-[400px] max-w-[600px]", popoverClassName),
250
- align: "start",
377
+ align,
251
378
  children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "space-y-4", children: [
252
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h4", { className: "font-medium text-sm", children: "Filters" }) }),
379
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center justify-between", children: [
380
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h4", { className: "font-medium text-sm", children: "Filters" }),
381
+ onClearAll && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
382
+ "button",
383
+ {
384
+ onClick: onClearAll,
385
+ className: "text-xs text-muted-foreground hover:text-foreground transition-colors",
386
+ children: "Reset all"
387
+ }
388
+ )
389
+ ] }),
253
390
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "space-y-4", children: sectionNames.map((sectionName, sectionIndex) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
254
391
  sectionIndex > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "border-t border-border mb-4" }),
255
392
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
@@ -272,7 +409,7 @@ function FiltersPopover({
272
409
  // src/wrappers/ActiveFilterBadges.tsx
273
410
  var import_react2 = require("react");
274
411
  var import_ui5 = require("@turtleclub/ui");
275
- var import_lucide_react2 = require("lucide-react");
412
+ var import_lucide_react = require("lucide-react");
276
413
  var import_jsx_runtime7 = require("react/jsx-runtime");
277
414
  function ActiveFilterBadges({
278
415
  filters,
@@ -327,7 +464,7 @@ function FilterBadge({ filter, renderBadge, onClearFilter }) {
327
464
  onClick: clearFilter,
328
465
  className: "ml-1 rounded-full p-0.5 hover:bg-black/10 focus:bg-black/10 focus:outline-none",
329
466
  "aria-label": `Clear ${filter.label} filter`,
330
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react2.X, { className: "h-3 w-3" })
467
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react.X, { className: "h-3 w-3" })
331
468
  }
332
469
  )
333
470
  ] });
@@ -342,11 +479,15 @@ function FiltersWrapper({
342
479
  layout,
343
480
  className,
344
481
  badgesClassName,
482
+ slotClassName,
483
+ leftSlot,
484
+ rightSlot,
345
485
  columns = 3,
346
486
  triggerLabel = "Filters",
347
487
  popoverColumns = 2,
348
488
  popoverClassName,
349
489
  popoverContentClassName,
490
+ align = "start",
350
491
  clearAllLabel = "Clear All",
351
492
  showClearAll = true,
352
493
  renderBadge,
@@ -356,16 +497,25 @@ function FiltersWrapper({
356
497
  }) {
357
498
  const hasActiveFilters = activeFilters.some((filter) => filter.isActive && filter.value);
358
499
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: (0, import_ui6.cn)("space-y-3", className), children: [
359
- layout === "grid" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(FiltersGrid, { filters, columns, className: popoverClassName }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
360
- FiltersPopover,
361
- {
362
- filters,
363
- triggerLabel,
364
- columns: popoverColumns,
365
- className: popoverClassName,
366
- popoverClassName: popoverContentClassName
367
- }
368
- ),
500
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: (0, import_ui6.cn)("flex items-center gap-2", slotClassName), children: [
501
+ leftSlot,
502
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_jsx_runtime8.Fragment, { children: layout === "grid" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(FiltersGrid, { filters, columns, className: popoverClassName }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
503
+ FiltersPopover,
504
+ {
505
+ filters,
506
+ triggerLabel,
507
+ columns: popoverColumns,
508
+ className: popoverClassName,
509
+ popoverClassName: popoverContentClassName,
510
+ align,
511
+ onClearAll: () => {
512
+ const allQueryKeys = activeFilters.map((filter) => filter.queryKeys).filter((keys) => keys !== void 0);
513
+ onClearAll(allQueryKeys);
514
+ }
515
+ }
516
+ ) }),
517
+ rightSlot
518
+ ] }),
369
519
  showActiveBadges && hasActiveFilters && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
370
520
  ActiveFilterBadges,
371
521
  {
@@ -591,7 +741,11 @@ function ChainsSelector({
591
741
  closeOnSelect = true,
592
742
  searchable = true
593
743
  }) {
594
- const { chains, isLoading } = (0, import_hooks.useSupportedChains)();
744
+ const { chains, isLoading } = (0, import_hooks.useSupportedChains)({
745
+ page: 1,
746
+ limit: 100
747
+ // High limit to fetch all chains
748
+ });
595
749
  const chainOptions = (0, import_react5.useMemo)(() => {
596
750
  if (!chains || chains.length === 0) return [];
597
751
  const filteredChains = chains.filter((chain) => chain.status === "active");
@@ -644,8 +798,8 @@ function TokensSelector({
644
798
  }) {
645
799
  const isChainSelected = !!chainId && chainId.trim() !== "";
646
800
  const { tokens, isLoading } = (0, import_hooks2.useSupportedTokens)({
647
- // chainId: isChainSelected ? chainId : undefined,
648
- limit: 100,
801
+ chainId: isChainSelected ? chainId : "",
802
+ limit: 9e3,
649
803
  enabled: isChainSelected
650
804
  });
651
805
  const tokenOptions = (0, import_react6.useMemo)(() => {
@@ -744,7 +898,6 @@ var import_jsx_runtime12 = require("react/jsx-runtime");
744
898
  function OpportunitiesSelector({
745
899
  value,
746
900
  onValueChange,
747
- productId,
748
901
  placeholder = "Select opportunities",
749
902
  disabled = false,
750
903
  className,
@@ -752,13 +905,7 @@ function OpportunitiesSelector({
752
905
  closeOnSelect = true,
753
906
  searchable = true
754
907
  }) {
755
- const isProductSelected = !!productId && productId.trim() !== "";
756
- const { data: opportunitiesData, isLoading } = (0, import_hooks4.useOpportunities)({
757
- filters: {
758
- productId
759
- },
760
- enabled: isProductSelected
761
- });
908
+ const { data: opportunitiesData, isLoading } = (0, import_hooks4.useOpportunities)();
762
909
  const opportunities = (0, import_react8.useMemo)(
763
910
  () => opportunitiesData?.opportunities ?? [],
764
911
  [opportunitiesData?.opportunities]
@@ -782,11 +929,6 @@ function OpportunitiesSelector({
782
929
  ) : void 0
783
930
  }));
784
931
  }, [opportunities]);
785
- (0, import_react8.useEffect)(() => {
786
- if (!isProductSelected && value.length > 0) {
787
- onValueChange([]);
788
- }
789
- }, [isProductSelected, value, onValueChange]);
790
932
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
791
933
  import_ui10.MultiSelect,
792
934
  {
@@ -794,8 +936,8 @@ function OpportunitiesSelector({
794
936
  options: opportunityOptions,
795
937
  value,
796
938
  onValueChange,
797
- disabled: disabled || isLoading || !isProductSelected,
798
- placeholder: !isProductSelected ? "Select a product first" : isLoading ? "Loading opportunities..." : placeholder,
939
+ disabled: disabled || isLoading,
940
+ placeholder: isLoading ? "Loading opportunities..." : placeholder,
799
941
  closeOnSelect,
800
942
  maxCount,
801
943
  className
@@ -817,7 +959,11 @@ function ChainSelector({
817
959
  closeOnSelect = true,
818
960
  searchable = true
819
961
  }) {
820
- const { chains, isLoading } = (0, import_hooks5.useSupportedChains)();
962
+ const { chains, isLoading } = (0, import_hooks5.useSupportedChains)({
963
+ page: 1,
964
+ limit: 100
965
+ // High limit to fetch all chains
966
+ });
821
967
  const chainOptions = (0, import_react9.useMemo)(() => {
822
968
  if (!chains || chains.length === 0) return [];
823
969
  const filteredChains = chains.filter((chain) => chain.status === "active");
@@ -868,8 +1014,8 @@ function TokenSelector({
868
1014
  }) {
869
1015
  const isChainSelected = !!chainId && chainId.trim() !== "";
870
1016
  const { tokens, isLoading } = (0, import_hooks6.useSupportedTokens)({
871
- // chainId: isChainSelected ? chainId : undefined,
872
- limit: 100,
1017
+ chainId: isChainSelected ? chainId : "",
1018
+ limit: 9e3,
873
1019
  enabled: isChainSelected
874
1020
  });
875
1021
  const tokenOptions = (0, import_react10.useMemo)(() => {
@@ -965,20 +1111,13 @@ var import_jsx_runtime16 = require("react/jsx-runtime");
965
1111
  function OpportunitySelector({
966
1112
  value,
967
1113
  onValueChange,
968
- productId,
969
1114
  placeholder = "Select opportunity",
970
1115
  disabled = false,
971
1116
  className,
972
1117
  closeOnSelect = true,
973
1118
  searchable = true
974
1119
  }) {
975
- const isProductSelected = !!productId && productId.trim() !== "";
976
- const { data: opportunitiesData, isLoading } = (0, import_hooks8.useOpportunities)({
977
- filters: {
978
- productId
979
- },
980
- enabled: isProductSelected
981
- });
1120
+ const { data: opportunitiesData, isLoading } = (0, import_hooks8.useOpportunities)();
982
1121
  const opportunities = (0, import_react12.useMemo)(
983
1122
  () => opportunitiesData?.opportunities ?? [],
984
1123
  [opportunitiesData?.opportunities]
@@ -1002,11 +1141,6 @@ function OpportunitySelector({
1002
1141
  ) : void 0
1003
1142
  }));
1004
1143
  }, [opportunities]);
1005
- (0, import_react12.useEffect)(() => {
1006
- if (!isProductSelected && value) {
1007
- onValueChange("");
1008
- }
1009
- }, [isProductSelected, value, onValueChange]);
1010
1144
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1011
1145
  import_ui14.Combobox,
1012
1146
  {
@@ -1014,8 +1148,8 @@ function OpportunitySelector({
1014
1148
  options: opportunityOptions,
1015
1149
  value,
1016
1150
  onValueChange,
1017
- disabled: disabled || isLoading || !isProductSelected,
1018
- placeholder: !isProductSelected ? "Select a product first" : isLoading ? "Loading opportunities..." : placeholder,
1151
+ disabled: disabled || isLoading,
1152
+ placeholder: isLoading ? "Loading opportunities..." : placeholder,
1019
1153
  closeOnSelect,
1020
1154
  className
1021
1155
  }