@fuf-stack/megapixels 0.0.1 → 0.2.0

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,709 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+
21
+ // src/Filter/Filter.tsx
22
+ import { tv, variantsToClassNames } from "@fuf-stack/pixel-utils";
23
+ import Form from "@fuf-stack/uniform/Form";
24
+
25
+ // src/Filter/hooks/useFilterValidation.ts
26
+ import { useMemo } from "react";
27
+ import { object, string, stringToJSON, veto } from "@fuf-stack/veto";
28
+ var useFilterValidation = (filters2, withSearch) => {
29
+ return useMemo(() => {
30
+ let validationObject = {};
31
+ let filterValidation = {};
32
+ filters2.forEach((f) => {
33
+ filterValidation = __spreadProps(__spreadValues({}, filterValidation), {
34
+ [f.name]: f.validation(f.config)
35
+ });
36
+ });
37
+ validationObject = __spreadValues({
38
+ filter: stringToJSON().pipe(object(filterValidation)).or(object(filterValidation)).optional()
39
+ }, withSearch ? { search: string({ min: 0 }).nullable().optional() } : {});
40
+ return veto(validationObject);
41
+ }, [filters2, withSearch]);
42
+ };
43
+
44
+ // src/Filter/Subcomponents/ActiveFilters.tsx
45
+ import Label from "@fuf-stack/pixels/Label";
46
+
47
+ // src/Filter/Subcomponents/FiltersContext.tsx
48
+ import {
49
+ createContext,
50
+ useCallback,
51
+ useContext,
52
+ useEffect,
53
+ useMemo as useMemo2,
54
+ useRef,
55
+ useState
56
+ } from "react";
57
+ import { useFormContext } from "@fuf-stack/uniform/hooks";
58
+ import { jsx } from "react/jsx-runtime";
59
+ var FiltersContext = createContext(
60
+ void 0
61
+ );
62
+ var FiltersContextProvider = ({
63
+ children,
64
+ config: config3
65
+ }) => {
66
+ const {
67
+ formState,
68
+ getFieldState,
69
+ setValue,
70
+ triggerSubmit,
71
+ unregister,
72
+ watch
73
+ } = useFormContext();
74
+ const [currentModalFilter, setCurrentModalFilter] = useState(null);
75
+ const filterValue = watch("filter", {});
76
+ const getFilterFormFieldName = useCallback((name) => {
77
+ return `filter.${name}`;
78
+ }, []);
79
+ const getFilterValueByName = useCallback(
80
+ (name) => {
81
+ return filterValue[name];
82
+ },
83
+ [filterValue]
84
+ );
85
+ const showFilterModal = useCallback(
86
+ (name) => {
87
+ const prev = getFilterValueByName(name);
88
+ setCurrentModalFilter({
89
+ name,
90
+ hadValue: typeof prev !== "undefined",
91
+ previousValue: prev
92
+ });
93
+ },
94
+ [getFilterValueByName]
95
+ );
96
+ const closeFilterModal = useCallback(() => {
97
+ if (currentModalFilter == null ? void 0 : currentModalFilter.name) {
98
+ const fieldName = getFilterFormFieldName(currentModalFilter.name);
99
+ if (currentModalFilter.hadValue) {
100
+ setValue(fieldName, currentModalFilter.previousValue);
101
+ } else {
102
+ unregister(fieldName);
103
+ }
104
+ }
105
+ setCurrentModalFilter(null);
106
+ }, [getFilterFormFieldName, currentModalFilter, setValue, unregister]);
107
+ const lastSubmitCountRef = useRef(0);
108
+ useEffect(() => {
109
+ if (formState.submitCount !== lastSubmitCountRef.current && formState.isSubmitSuccessful) {
110
+ setCurrentModalFilter(null);
111
+ }
112
+ lastSubmitCountRef.current = formState.submitCount;
113
+ }, [
114
+ formState.submitCount,
115
+ formState.isSubmitSuccessful,
116
+ setCurrentModalFilter
117
+ ]);
118
+ const activeFilters = useMemo2(() => {
119
+ return config3.filter((f) => {
120
+ return Object.hasOwn(filterValue != null ? filterValue : {}, f.name);
121
+ }).map((f) => {
122
+ return f.name;
123
+ });
124
+ }, [config3, filterValue]);
125
+ const unusedFilters = useMemo2(() => {
126
+ return config3.filter((f) => {
127
+ return !Object.hasOwn(filterValue != null ? filterValue : {}, f.name);
128
+ }).map((f) => {
129
+ return f.name;
130
+ });
131
+ }, [config3, filterValue]);
132
+ const getFilterInstanceByName = useCallback(
133
+ (name) => {
134
+ return config3.find((f) => {
135
+ return f.name === name;
136
+ });
137
+ },
138
+ [config3]
139
+ );
140
+ const addFilter = useCallback(
141
+ (name) => {
142
+ const inst = getFilterInstanceByName(name);
143
+ showFilterModal(name);
144
+ setValue(getFilterFormFieldName(name), inst.defaultValue);
145
+ },
146
+ [
147
+ getFilterFormFieldName,
148
+ getFilterInstanceByName,
149
+ setValue,
150
+ showFilterModal
151
+ ]
152
+ );
153
+ const removeFilter = useCallback(
154
+ (name) => {
155
+ unregister(getFilterFormFieldName(name));
156
+ if ((currentModalFilter == null ? void 0 : currentModalFilter.name) === name) {
157
+ setCurrentModalFilter(null);
158
+ }
159
+ triggerSubmit();
160
+ },
161
+ [
162
+ getFilterFormFieldName,
163
+ currentModalFilter,
164
+ setCurrentModalFilter,
165
+ triggerSubmit,
166
+ unregister
167
+ ]
168
+ );
169
+ const hasError = useCallback(
170
+ (name) => {
171
+ return getFieldState(getFilterFormFieldName(name)).invalid;
172
+ },
173
+ [getFieldState, getFilterFormFieldName]
174
+ );
175
+ const contextValue = useMemo2(() => {
176
+ return {
177
+ activeFilters,
178
+ addFilter,
179
+ closeFilterModal,
180
+ getFilterFormFieldName,
181
+ getFilterValueByName,
182
+ getFilterInstanceByName,
183
+ hasError,
184
+ modalFilterName: currentModalFilter == null ? void 0 : currentModalFilter.name,
185
+ removeFilter,
186
+ showFilterModal,
187
+ unusedFilters
188
+ };
189
+ }, [
190
+ activeFilters,
191
+ addFilter,
192
+ closeFilterModal,
193
+ getFilterFormFieldName,
194
+ getFilterValueByName,
195
+ getFilterInstanceByName,
196
+ hasError,
197
+ currentModalFilter,
198
+ removeFilter,
199
+ showFilterModal,
200
+ unusedFilters
201
+ ]);
202
+ return /* @__PURE__ */ jsx(FiltersContext.Provider, { value: contextValue, children });
203
+ };
204
+ var useFilters = () => {
205
+ const ctx = useContext(FiltersContext);
206
+ if (!ctx) {
207
+ throw new Error("useFilters must be used within FiltersContextProvider");
208
+ }
209
+ return ctx;
210
+ };
211
+
212
+ // src/Filter/Subcomponents/ActiveFilters.tsx
213
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
214
+ var ActiveFilters = ({ className = void 0 }) => {
215
+ const {
216
+ activeFilters,
217
+ getFilterValueByName,
218
+ getFilterInstanceByName,
219
+ hasError,
220
+ removeFilter,
221
+ showFilterModal
222
+ } = useFilters();
223
+ return /* @__PURE__ */ jsx2(Fragment, { children: activeFilters.map((name) => {
224
+ const instance = getFilterInstanceByName(name);
225
+ const value = getFilterValueByName(name);
226
+ const DisplayComponent = instance.components.Display;
227
+ return /* @__PURE__ */ jsx2(
228
+ "button",
229
+ {
230
+ "aria-label": `Open ${name} filter`,
231
+ type: "button",
232
+ onClick: () => {
233
+ showFilterModal(name);
234
+ },
235
+ children: /* @__PURE__ */ jsxs(
236
+ Label,
237
+ {
238
+ className,
239
+ color: hasError(name) ? "danger" : "primary",
240
+ variant: "flat",
241
+ onClose: () => {
242
+ removeFilter(name);
243
+ },
244
+ children: [
245
+ instance.icon,
246
+ /* @__PURE__ */ jsx2(DisplayComponent, { config: instance.config, value })
247
+ ]
248
+ }
249
+ )
250
+ },
251
+ name
252
+ );
253
+ }) });
254
+ };
255
+ var ActiveFilters_default = ActiveFilters;
256
+
257
+ // src/Filter/Subcomponents/AddFilterMenu.tsx
258
+ import { FaSliders } from "react-icons/fa6";
259
+ import Menu from "@fuf-stack/pixels/Menu";
260
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
261
+ var AddFilterMenu = ({ classNames = {} }) => {
262
+ const { unusedFilters, addFilter, getFilterInstanceByName } = useFilters();
263
+ const menuItems = unusedFilters.map((name) => {
264
+ var _a;
265
+ const instance = getFilterInstanceByName(name);
266
+ const config3 = instance.config;
267
+ const label = (_a = config3 == null ? void 0 : config3.text) != null ? _a : name;
268
+ return {
269
+ key: name,
270
+ icon: instance.icon,
271
+ label,
272
+ onClick: () => {
273
+ addFilter(name);
274
+ }
275
+ };
276
+ });
277
+ return /* @__PURE__ */ jsxs2(
278
+ Menu,
279
+ {
280
+ isDisabled: !menuItems.length,
281
+ items: menuItems,
282
+ placement: "bottom-start",
283
+ className: {
284
+ item: classNames.addFilterMenuItem,
285
+ trigger: classNames.addFilterMenuButton
286
+ },
287
+ triggerButtonProps: {
288
+ "aria-label": "Add Filter",
289
+ disableRipple: true,
290
+ size: "sm",
291
+ variant: "bordered"
292
+ },
293
+ children: [
294
+ /* @__PURE__ */ jsx3(FaSliders, {}),
295
+ "Filter"
296
+ ]
297
+ }
298
+ );
299
+ };
300
+ var AddFilterMenu_default = AddFilterMenu;
301
+
302
+ // src/Filter/Subcomponents/FilterModal.tsx
303
+ import { Suspense } from "react";
304
+ import { PiSlidersHorizontalBold } from "react-icons/pi";
305
+ import Button from "@fuf-stack/pixels/Button";
306
+ import Modal from "@fuf-stack/pixels/Modal";
307
+ import SubmitButton from "@fuf-stack/uniform/SubmitButton";
308
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
309
+ var FilterModal = ({ classNames = {} }) => {
310
+ var _a, _b;
311
+ const {
312
+ closeFilterModal,
313
+ getFilterFormFieldName,
314
+ getFilterInstanceByName,
315
+ modalFilterName,
316
+ removeFilter
317
+ } = useFilters();
318
+ if (!modalFilterName) {
319
+ return null;
320
+ }
321
+ const instance = getFilterInstanceByName(modalFilterName);
322
+ const config3 = instance.config;
323
+ const FormComponent = instance.components.Form;
324
+ return /* @__PURE__ */ jsx4(
325
+ Modal,
326
+ {
327
+ isOpen: true,
328
+ onClose: closeFilterModal,
329
+ className: {
330
+ body: classNames.body,
331
+ footer: classNames.footer,
332
+ header: classNames.header
333
+ },
334
+ footer: /* @__PURE__ */ jsxs3(Fragment2, { children: [
335
+ /* @__PURE__ */ jsx4(
336
+ Button,
337
+ {
338
+ color: "danger",
339
+ variant: "flat",
340
+ onClick: () => {
341
+ removeFilter(modalFilterName);
342
+ },
343
+ children: "Remove"
344
+ }
345
+ ),
346
+ /* @__PURE__ */ jsx4(SubmitButton, { children: "Apply Filter" })
347
+ ] }),
348
+ header: /* @__PURE__ */ jsxs3(Fragment2, { children: [
349
+ (_a = instance.icon) != null ? _a : /* @__PURE__ */ jsx4(PiSlidersHorizontalBold, {}),
350
+ /* @__PURE__ */ jsx4("div", { children: `${(_b = config3 == null ? void 0 : config3.text) != null ? _b : modalFilterName} Filter` })
351
+ ] }),
352
+ children: /* @__PURE__ */ jsx4(Suspense, { children: /* @__PURE__ */ jsx4(
353
+ FormComponent,
354
+ {
355
+ config: config3,
356
+ fieldName: getFilterFormFieldName(modalFilterName)
357
+ }
358
+ ) })
359
+ }
360
+ );
361
+ };
362
+ var FilterModal_default = FilterModal;
363
+
364
+ // src/Filter/Subcomponents/SearchInput.tsx
365
+ import { useState as useState2 } from "react";
366
+ import { FaSearch } from "react-icons/fa";
367
+ import { motion } from "@fuf-stack/pixel-motion";
368
+ import Button2 from "@fuf-stack/pixels/Button";
369
+ import { useFormContext as useFormContext2 } from "@fuf-stack/uniform/hooks";
370
+ import Input from "@fuf-stack/uniform/Input";
371
+ import SubmitButton2 from "@fuf-stack/uniform/SubmitButton";
372
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
373
+ var SearchInput = ({ classNames = {}, config: config3 }) => {
374
+ var _a;
375
+ const { formState, setFocus, triggerSubmit } = useFormContext2();
376
+ const isInitiallyVisible = !!((_a = formState == null ? void 0 : formState.defaultValues) == null ? void 0 : _a.search);
377
+ const [isVisible, setIsVisible] = useState2(isInitiallyVisible);
378
+ const placeholder = typeof config3 === "object" ? config3.placeholder : void 0;
379
+ return /* @__PURE__ */ jsxs4("div", { className: classNames.searchWrapper, children: [
380
+ !isVisible && /* @__PURE__ */ jsx5(
381
+ Button2,
382
+ {
383
+ ariaLabel: "Show search input",
384
+ className: classNames.searchShowButton,
385
+ icon: /* @__PURE__ */ jsx5(FaSearch, {}),
386
+ size: "sm",
387
+ variant: "bordered",
388
+ onClick: () => {
389
+ setIsVisible(true);
390
+ }
391
+ }
392
+ ),
393
+ isVisible ? /* @__PURE__ */ jsxs4(
394
+ motion.div,
395
+ {
396
+ animate: { opacity: 1 },
397
+ className: classNames.searchMotionDiv,
398
+ initial: { opacity: 0.5 },
399
+ onAnimationComplete: () => {
400
+ if (!isInitiallyVisible) {
401
+ setFocus("search");
402
+ }
403
+ },
404
+ transition: {
405
+ // if the input was not initially visible, animate in
406
+ duration: isInitiallyVisible ? 0 : 0.3,
407
+ ease: "circOut"
408
+ },
409
+ children: [
410
+ /* @__PURE__ */ jsx5(
411
+ Input,
412
+ {
413
+ clearable: true,
414
+ debounceDelay: 0,
415
+ name: "search",
416
+ placeholder,
417
+ size: "sm",
418
+ className: {
419
+ input: classNames.searchInput,
420
+ inputWrapper: classNames.searchInputWrapper
421
+ },
422
+ onClear: () => {
423
+ triggerSubmit();
424
+ }
425
+ }
426
+ ),
427
+ /* @__PURE__ */ jsx5(
428
+ SubmitButton2,
429
+ {
430
+ ariaLabel: "Trigger search",
431
+ children: null,
432
+ className: classNames.searchSubmitButton,
433
+ color: "primary",
434
+ icon: /* @__PURE__ */ jsx5(FaSearch, {}),
435
+ size: "sm"
436
+ }
437
+ )
438
+ ]
439
+ },
440
+ "search-input"
441
+ ) : null
442
+ ] });
443
+ };
444
+ var SearchInput_default = SearchInput;
445
+
446
+ // src/Filter/Filter.tsx
447
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
448
+ var filterVariants = tv({
449
+ slots: {
450
+ // outer wrapper
451
+ base: "",
452
+ // add filter menu trigger button
453
+ addFilterMenuButton: "",
454
+ // add filter menu item
455
+ addFilterMenuItem: "",
456
+ // active filter label
457
+ activeFilterLabel: "dark:text-foreground h-8 cursor-pointer rounded-md",
458
+ // filter modal body
459
+ filterModalBody: "",
460
+ // filter modal header
461
+ filterModalHeader: "text-default-700 flex items-center gap-3",
462
+ // filter modal footer
463
+ filterModalFooter: "justify-between",
464
+ // form element
465
+ form: "mb-3 flex flex-wrap gap-3",
466
+ // search input field
467
+ searchInput: "",
468
+ // search input wrapper (inner control)
469
+ searchInputWrapper: "",
470
+ // search motion container
471
+ searchMotionDiv: "flex w-72 gap-2",
472
+ // search show button
473
+ searchShowButton: "",
474
+ // search submit button
475
+ searchSubmitButton: "",
476
+ // search wrapper
477
+ searchWrapper: "flex items-center"
478
+ }
479
+ });
480
+ var Filter = ({
481
+ children = void 0,
482
+ className = void 0,
483
+ config: config3,
484
+ formName = "filterComponentForm",
485
+ onChange,
486
+ values
487
+ }) => {
488
+ const handleSubmit = (nextValues) => {
489
+ onChange(nextValues);
490
+ };
491
+ const validation = useFilterValidation(
492
+ config3.filters,
493
+ Boolean(config3.search)
494
+ );
495
+ const { data: valuesValidated } = validation.validate(values);
496
+ const variants = filterVariants();
497
+ const classNames = variantsToClassNames(variants, className, "base");
498
+ return /* @__PURE__ */ jsxs5("div", { className: classNames.base, children: [
499
+ /* @__PURE__ */ jsxs5(
500
+ Form,
501
+ {
502
+ className: classNames.form,
503
+ debug: { disable: true },
504
+ initialValues: valuesValidated != null ? valuesValidated : {},
505
+ name: formName,
506
+ onSubmit: handleSubmit,
507
+ validation,
508
+ children: [
509
+ config3.search ? /* @__PURE__ */ jsx6(
510
+ SearchInput_default,
511
+ {
512
+ config: config3.search,
513
+ classNames: {
514
+ searchInput: classNames.searchInput,
515
+ searchInputWrapper: classNames.searchInputWrapper,
516
+ searchMotionDiv: classNames.searchMotionDiv,
517
+ searchShowButton: classNames.searchShowButton,
518
+ searchSubmitButton: classNames.searchSubmitButton,
519
+ searchWrapper: classNames.searchWrapper
520
+ }
521
+ }
522
+ ) : null,
523
+ /* @__PURE__ */ jsxs5(FiltersContextProvider, { config: config3.filters, children: [
524
+ /* @__PURE__ */ jsx6(ActiveFilters_default, { className: classNames.activeFilterLabel }),
525
+ /* @__PURE__ */ jsx6(
526
+ AddFilterMenu_default,
527
+ {
528
+ classNames: {
529
+ addFilterMenuButton: classNames.addFilterMenuButton,
530
+ addFilterMenuItem: classNames.addFilterMenuItem
531
+ }
532
+ }
533
+ ),
534
+ /* @__PURE__ */ jsx6(
535
+ FilterModal_default,
536
+ {
537
+ classNames: {
538
+ body: classNames.filterModalBody,
539
+ footer: classNames.filterModalFooter,
540
+ header: classNames.filterModalHeader
541
+ }
542
+ }
543
+ )
544
+ ] })
545
+ ]
546
+ }
547
+ ),
548
+ children == null ? void 0 : children(valuesValidated != null ? valuesValidated : {})
549
+ ] });
550
+ };
551
+ var Filter_default = Filter;
552
+
553
+ // src/Filter/filters/createFilter.ts
554
+ var createFilter = (definition) => {
555
+ return ({ name, icon, config: config3 }) => {
556
+ return {
557
+ components: definition.components,
558
+ config: __spreadValues(__spreadValues({}, definition.defaults.config), config3 != null ? config3 : {}),
559
+ defaultValue: definition.defaults.value,
560
+ icon,
561
+ name,
562
+ validation: definition.validation
563
+ };
564
+ };
565
+ };
566
+ var createFilter_default = createFilter;
567
+
568
+ // src/Filter/filters/boolean/Display.tsx
569
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
570
+ var Display = ({
571
+ value,
572
+ config: { text, textPrefix, textNoWord }
573
+ }) => {
574
+ if (typeof value === "boolean") {
575
+ return /* @__PURE__ */ jsxs6(Fragment3, { children: [
576
+ value ? textPrefix : `${textPrefix} ${textNoWord != null ? textNoWord : "no"}`,
577
+ " ",
578
+ text
579
+ ] });
580
+ }
581
+ return /* @__PURE__ */ jsx7(Fragment3, { children: `${text}...` });
582
+ };
583
+ var Display_default = Display;
584
+
585
+ // src/Filter/filters/boolean/Form.tsx
586
+ import Switch from "@fuf-stack/uniform/Switch";
587
+ import { jsx as jsx8 } from "react/jsx-runtime";
588
+ var Form2 = ({
589
+ fieldName,
590
+ config: { text, textPrefix }
591
+ }) => {
592
+ return /* @__PURE__ */ jsx8(Switch, { label: `${textPrefix} ${text}`, name: fieldName });
593
+ };
594
+ var Form_default = Form2;
595
+
596
+ // src/Filter/filters/boolean/schema.ts
597
+ import { boolean, object as object2, string as string2 } from "@fuf-stack/veto";
598
+ var config = object2({
599
+ /**
600
+ * Human‑readable label used in the UI (e.g. in the chip and modal header).
601
+ * Examples: "Magical", "Haunted"
602
+ */
603
+ text: string2(),
604
+ /**
605
+ * Optional word shown before the label when building sentence‑like chips.
606
+ * Examples: "is" → "is Magical"
607
+ */
608
+ textPrefix: string2().optional(),
609
+ /**
610
+ * Optional negation word used when a boolean value is false.
611
+ * Examples: "not" → "is not Magical"
612
+ */
613
+ textNoWord: string2().optional()
614
+ });
615
+ var validate = (_config) => {
616
+ return boolean().optional();
617
+ };
618
+
619
+ // src/Filter/filters/boolean/boolean.ts
620
+ var boolean2 = createFilter_default({
621
+ components: { Display: Display_default, Form: Form_default },
622
+ defaults: {
623
+ value: true,
624
+ config: { text: "Active", textPrefix: "is", textNoWord: "no" }
625
+ },
626
+ validation: validate
627
+ });
628
+
629
+ // src/Filter/filters/checkboxgroup/Display.tsx
630
+ var Display2 = ({
631
+ value,
632
+ config: { text, options }
633
+ }) => {
634
+ if (value && value.length > 0) {
635
+ const labels = value.map((val) => {
636
+ var _a, _b;
637
+ return (_b = (_a = options.find((op) => {
638
+ return op.value === val;
639
+ })) == null ? void 0 : _a.label) != null ? _b : val;
640
+ }).join(" ");
641
+ return `${text} is ${labels}`;
642
+ }
643
+ return `${text} is ...`;
644
+ };
645
+ var Display_default2 = Display2;
646
+
647
+ // src/Filter/filters/checkboxgroup/Form.tsx
648
+ import CheckboxGroup from "@fuf-stack/uniform/CheckboxGroup";
649
+ import { jsx as jsx9 } from "react/jsx-runtime";
650
+ var Form3 = ({ fieldName, config: config3 }) => {
651
+ return /* @__PURE__ */ jsx9(CheckboxGroup, { name: fieldName, options: config3.options });
652
+ };
653
+ var Form_default2 = Form3;
654
+
655
+ // src/Filter/filters/checkboxgroup/schema.ts
656
+ import { array, object as object3, refineArray, string as string3 } from "@fuf-stack/veto";
657
+ var config2 = object3({
658
+ /**
659
+ * Human‑readable label used in the UI (e.g. label and modal header).
660
+ * Example: "Snacks", "Mood"
661
+ */
662
+ text: string3(),
663
+ /**
664
+ * Options rendered as multiple checkboxes. Each option needs a `label`
665
+ * (what the user sees) and a `value` (what is written into the form state).
666
+ */
667
+ options: array(object3({ label: string3(), value: string3() }))
668
+ });
669
+ var validate2 = (cfg) => {
670
+ return refineArray(array(string3()).optional())({
671
+ unique: true,
672
+ custom: (values, ctx) => {
673
+ if (!cfg) {
674
+ return;
675
+ }
676
+ values.forEach((value) => {
677
+ if (!cfg.options.find((option) => {
678
+ return (option == null ? void 0 : option.value) === value;
679
+ })) {
680
+ ctx.addIssue({
681
+ code: "custom",
682
+ message: `Invalid value: ${value}`
683
+ });
684
+ }
685
+ });
686
+ }
687
+ });
688
+ };
689
+
690
+ // src/Filter/filters/checkboxgroup/checkboxgroup.ts
691
+ var checkboxgroup = createFilter_default({
692
+ components: { Display: Display_default2, Form: Form_default2 },
693
+ defaults: { value: [], config: { text: "Options", options: [] } },
694
+ validation: validate2
695
+ });
696
+
697
+ // src/Filter/index.ts
698
+ var filters = {
699
+ boolean: boolean2,
700
+ checkboxgroup
701
+ };
702
+ var Filter_default2 = Filter_default;
703
+
704
+ export {
705
+ createFilter_default,
706
+ filters,
707
+ Filter_default2 as Filter_default
708
+ };
709
+ //# sourceMappingURL=chunk-3ZL7ZLSU.js.map