@classytic/fluid 0.2.4 → 0.3.2

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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -62
  3. package/dist/api-pagination-CJ0vR_w6.d.mts +34 -0
  4. package/dist/api-pagination-DBTE0yk4.mjs +190 -0
  5. package/dist/chunk-DQk6qfdC.mjs +18 -0
  6. package/dist/client/calendar.d.mts +105 -0
  7. package/dist/client/calendar.mjs +202 -0
  8. package/dist/client/core.d.mts +1614 -0
  9. package/dist/client/core.mjs +2779 -0
  10. package/dist/client/error.d.mts +125 -0
  11. package/dist/client/error.mjs +166 -0
  12. package/dist/client/hooks.d.mts +162 -0
  13. package/dist/client/hooks.mjs +447 -0
  14. package/dist/client/table.d.mts +84 -0
  15. package/dist/client/table.mjs +373 -0
  16. package/dist/client/theme.d.mts +6 -0
  17. package/dist/client/theme.mjs +65 -0
  18. package/dist/command.d.mts +134 -0
  19. package/dist/command.mjs +132 -0
  20. package/dist/compact.d.mts +359 -0
  21. package/dist/compact.mjs +892 -0
  22. package/dist/dashboard.d.mts +778 -0
  23. package/dist/dashboard.mjs +1617 -0
  24. package/dist/filter-utils-DqMmy_v-.mjs +72 -0
  25. package/dist/filter-utils-IZ0GtuPo.d.mts +40 -0
  26. package/dist/forms.d.mts +1549 -0
  27. package/dist/forms.mjs +3740 -0
  28. package/dist/index.d.mts +296 -0
  29. package/dist/index.mjs +432 -0
  30. package/dist/layouts.d.mts +215 -0
  31. package/dist/layouts.mjs +460 -0
  32. package/dist/search-context-DR7DBs7S.mjs +19 -0
  33. package/dist/search.d.mts +254 -0
  34. package/dist/search.mjs +523 -0
  35. package/dist/sheet-wrapper-CWNCvYMD.mjs +211 -0
  36. package/dist/use-base-search-BGgWnWaF.d.mts +35 -0
  37. package/dist/use-debounce-xmZucz5e.mjs +53 -0
  38. package/dist/use-keyboard-shortcut-Bl6YM5Q7.mjs +82 -0
  39. package/dist/use-keyboard-shortcut-_mRCh3QO.d.mts +24 -0
  40. package/dist/use-media-query-BnVNIKT4.mjs +17 -0
  41. package/dist/use-mobile-BX3SQVo2.mjs +20 -0
  42. package/dist/use-scroll-detection-CsgsQYvy.mjs +43 -0
  43. package/dist/utils-CDue7cEt.d.mts +6 -0
  44. package/dist/utils-DQ5SCVoW.mjs +10 -0
  45. package/package.json +85 -45
  46. package/styles.css +2 -2
  47. package/dist/chunk-GUHK2DTW.js +0 -15
  48. package/dist/chunk-GUHK2DTW.js.map +0 -1
  49. package/dist/chunk-H3NFL3GJ.js +0 -57
  50. package/dist/chunk-H3NFL3GJ.js.map +0 -1
  51. package/dist/chunk-J2YRTQE4.js +0 -293
  52. package/dist/chunk-J2YRTQE4.js.map +0 -1
  53. package/dist/compact.d.ts +0 -217
  54. package/dist/compact.js +0 -986
  55. package/dist/compact.js.map +0 -1
  56. package/dist/dashboard.d.ts +0 -387
  57. package/dist/dashboard.js +0 -1032
  58. package/dist/dashboard.js.map +0 -1
  59. package/dist/index.d.ts +0 -2140
  60. package/dist/index.js +0 -6422
  61. package/dist/index.js.map +0 -1
  62. package/dist/layout.d.ts +0 -25
  63. package/dist/layout.js +0 -4
  64. package/dist/layout.js.map +0 -1
  65. package/dist/search.d.ts +0 -172
  66. package/dist/search.js +0 -341
  67. package/dist/search.js.map +0 -1
  68. package/dist/use-base-search-AS5Z3SAy.d.ts +0 -64
  69. package/dist/utils-Cbsgs0XP.d.ts +0 -5
@@ -0,0 +1,523 @@
1
+ "use client";
2
+
3
+ import { t as __exportAll } from "./chunk-DQk6qfdC.mjs";
4
+ import { t as cn } from "./utils-DQ5SCVoW.mjs";
5
+ import { t as useIsMobile } from "./use-mobile-BX3SQVo2.mjs";
6
+ import { r as SheetWrapper } from "./sheet-wrapper-CWNCvYMD.mjs";
7
+ import { n as useSearch, t as SearchProvider } from "./search-context-DR7DBs7S.mjs";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ import { useEffect, useRef, useState } from "react";
10
+ import { Filter, FilterX, RotateCcw, Search, X } from "lucide-react";
11
+ import { Button } from "@/components/ui/button";
12
+ import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@/components/ui/input-group";
13
+ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
14
+ import { Badge } from "@/components/ui/badge";
15
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
16
+
17
+ //#region src/components/search/search-root.tsx
18
+ /**
19
+ * Root search component that provides context to all child components
20
+ *
21
+ * @example
22
+ * // Manual mode (default)
23
+ * <Search.Root hook={useMySearch()}>
24
+ * <Search.Input />
25
+ * <Search.Actions />
26
+ * </Search.Root>
27
+ *
28
+ * @example
29
+ * // Auto-search mode — no Search button needed
30
+ * <Search.Root hook={useMySearch()} autoSearch>
31
+ * <Search.Input />
32
+ * <Search.ActiveFilters />
33
+ * </Search.Root>
34
+ */
35
+ function SearchRoot({ children, hook, className, autoSearch = false, autoSearchDelay = 300 }) {
36
+ const handleSearchRef = useRef(hook.handleSearch);
37
+ handleSearchRef.current = hook.handleSearch;
38
+ const timerRef = useRef(void 0);
39
+ const isInitialMount = useRef(true);
40
+ useEffect(() => {
41
+ if (!autoSearch) return;
42
+ if (isInitialMount.current) {
43
+ isInitialMount.current = false;
44
+ return;
45
+ }
46
+ if (timerRef.current) clearTimeout(timerRef.current);
47
+ timerRef.current = setTimeout(() => {
48
+ handleSearchRef.current();
49
+ }, autoSearchDelay);
50
+ return () => {
51
+ if (timerRef.current) clearTimeout(timerRef.current);
52
+ };
53
+ }, [
54
+ autoSearch,
55
+ autoSearchDelay,
56
+ hook.searchValue,
57
+ hook.searchType,
58
+ hook.filters
59
+ ]);
60
+ return /* @__PURE__ */ jsx(SearchProvider, {
61
+ value: hook,
62
+ children: /* @__PURE__ */ jsx("div", {
63
+ className: cn("w-full", className),
64
+ children
65
+ })
66
+ });
67
+ }
68
+
69
+ //#endregion
70
+ //#region src/components/search/search-input.tsx
71
+ /**
72
+ * Search input component using modern shadcn InputGroup pattern
73
+ *
74
+ * @example
75
+ * <Search.Input placeholder="Search..." />
76
+ */
77
+ function SearchInput({ placeholder = "Search...", className, disabled, showIcon = true, showClearButton = false, onKeyDown, ...props }) {
78
+ const { searchValue, setSearchValue, handleSearch } = useSearch();
79
+ const handleKeyDown = (event) => {
80
+ onKeyDown?.(event);
81
+ if (!event.defaultPrevented && event.key === "Enter" && !disabled) handleSearch?.();
82
+ };
83
+ return /* @__PURE__ */ jsxs(InputGroup, {
84
+ className: cn("h-10 flex-1 shadow-sm", className),
85
+ "data-disabled": disabled || void 0,
86
+ children: [
87
+ showIcon && /* @__PURE__ */ jsx(InputGroupAddon, {
88
+ align: "inline-start",
89
+ children: /* @__PURE__ */ jsx(Search, { className: "size-4" })
90
+ }),
91
+ /* @__PURE__ */ jsx(InputGroupInput, {
92
+ ...props,
93
+ type: "text",
94
+ placeholder,
95
+ value: searchValue || "",
96
+ onChange: (e) => setSearchValue?.(e.target.value),
97
+ onKeyDown: handleKeyDown,
98
+ disabled,
99
+ className: "text-base"
100
+ }),
101
+ showClearButton && searchValue && /* @__PURE__ */ jsx(InputGroupAddon, {
102
+ align: "inline-end",
103
+ children: /* @__PURE__ */ jsx(InputGroupButton, {
104
+ variant: "ghost",
105
+ size: "icon-sm",
106
+ onClick: () => setSearchValue?.(""),
107
+ disabled,
108
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
109
+ })
110
+ })
111
+ ]
112
+ });
113
+ }
114
+
115
+ //#endregion
116
+ //#region src/components/search/search-type-input.tsx
117
+ /**
118
+ * Search input component with type selector using InputGroup pattern
119
+ *
120
+ * @example
121
+ * <Search.TypeInput
122
+ * placeholder="Search..."
123
+ * searchTypeOptions={[
124
+ * { value: "_id", label: "ID" },
125
+ * { value: "customerPhone", label: "Phone" },
126
+ * { value: "customerEmail", label: "Email" },
127
+ * ]}
128
+ * />
129
+ */
130
+ function SearchTypeInput({ placeholder = "Search...", className, disabled, showIcon = true, showClearButton = false, searchTypeOptions = [], onKeyDown, ...props }) {
131
+ const { searchValue, setSearchValue, searchType, setSearchType, handleSearch } = useSearch();
132
+ const handleKeyDown = (event) => {
133
+ onKeyDown?.(event);
134
+ if (!event.defaultPrevented && event.key === "Enter" && !disabled) handleSearch?.();
135
+ };
136
+ const selectedOption = searchTypeOptions.find((opt) => opt.value === searchType);
137
+ return /* @__PURE__ */ jsxs(InputGroup, {
138
+ className: cn("h-10 flex-1 shadow-sm", className),
139
+ "data-disabled": disabled || void 0,
140
+ children: [
141
+ showIcon && /* @__PURE__ */ jsx(InputGroupAddon, {
142
+ align: "inline-start",
143
+ children: /* @__PURE__ */ jsx(Search, { className: "size-4" })
144
+ }),
145
+ searchTypeOptions.length > 0 && /* @__PURE__ */ jsxs(InputGroupAddon, {
146
+ align: "inline-start",
147
+ className: "pr-0",
148
+ children: [/* @__PURE__ */ jsxs(Select, {
149
+ value: searchType,
150
+ onValueChange: setSearchType,
151
+ disabled,
152
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
153
+ className: "h-7 w-auto border-0 bg-transparent px-2 text-sm font-medium shadow-none focus:ring-0 focus-visible:ring-0 focus-visible:border-0",
154
+ children: /* @__PURE__ */ jsx(SelectValue, { children: selectedOption?.label || searchTypeOptions[0]?.label })
155
+ }), /* @__PURE__ */ jsx(SelectContent, { children: searchTypeOptions.map((option) => /* @__PURE__ */ jsx(SelectItem, {
156
+ value: option.value,
157
+ children: option.label
158
+ }, option.value)) })]
159
+ }), /* @__PURE__ */ jsx("div", { className: "h-6 w-px bg-border ml-1" })]
160
+ }),
161
+ /* @__PURE__ */ jsx(InputGroupInput, {
162
+ ...props,
163
+ type: "text",
164
+ placeholder,
165
+ value: searchValue || "",
166
+ onChange: (e) => setSearchValue?.(e.target.value),
167
+ onKeyDown: handleKeyDown,
168
+ disabled,
169
+ className: "text-base"
170
+ }),
171
+ showClearButton && searchValue && /* @__PURE__ */ jsx(InputGroupAddon, {
172
+ align: "inline-end",
173
+ children: /* @__PURE__ */ jsx(InputGroupButton, {
174
+ variant: "ghost",
175
+ size: "icon-sm",
176
+ onClick: () => setSearchValue?.(""),
177
+ disabled,
178
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
179
+ })
180
+ })
181
+ ]
182
+ });
183
+ }
184
+
185
+ //#endregion
186
+ //#region src/components/search/search-filter-actions.tsx
187
+ /**
188
+ * Filter actions component (Reset/Apply buttons)
189
+ * Used inside filter popovers/sheets
190
+ */
191
+ function SearchFilterActions({ onClose, disabled }) {
192
+ const { handleSearch, clearFilters, hasActiveFilters } = useSearch();
193
+ const handleApply = () => {
194
+ handleSearch?.();
195
+ onClose?.();
196
+ };
197
+ return /* @__PURE__ */ jsxs("div", {
198
+ className: "flex gap-2 border-t bg-muted/30 p-4 pt-3 -mx-4 -mb-4",
199
+ children: [/* @__PURE__ */ jsx(Button, {
200
+ variant: "outline",
201
+ size: "sm",
202
+ onClick: clearFilters,
203
+ className: "flex-1",
204
+ disabled: disabled || !hasActiveFilters,
205
+ children: "Reset"
206
+ }), /* @__PURE__ */ jsxs(Button, {
207
+ size: "sm",
208
+ onClick: handleApply,
209
+ className: "flex-1",
210
+ disabled,
211
+ children: [/* @__PURE__ */ jsx(Search, {
212
+ size: 16,
213
+ className: "mr-2"
214
+ }), "Apply"]
215
+ })]
216
+ });
217
+ }
218
+
219
+ //#endregion
220
+ //#region src/components/search/search-filters.tsx
221
+ /**
222
+ * Search filters component with mobile sheet support
223
+ *
224
+ * @example
225
+ * <Search.Filters>
226
+ * <TagChoiceInput label="Category" ... />
227
+ * <SelectInput label="Status" ... />
228
+ * </Search.Filters>
229
+ */
230
+ function SearchFilters({ children, title = "Filters", description = "Refine your search results", disabled, className }) {
231
+ const { hasActiveFilters, filters = {} } = useSearch();
232
+ const isMobile = useIsMobile();
233
+ const [isOpen, setIsOpen] = useState(false);
234
+ const activeFilterCount = Object.values(filters).filter((value) => {
235
+ if (Array.isArray(value)) return value.filter(Boolean).length > 0;
236
+ if (typeof value === "string") return value.trim().length > 0 && value !== "all";
237
+ if (typeof value === "number") return value !== void 0 && value !== null && !Number.isNaN(value);
238
+ if (typeof value === "boolean") return value;
239
+ return Boolean(value);
240
+ }).length;
241
+ const FilterContent = () => /* @__PURE__ */ jsxs("div", {
242
+ className: "space-y-4",
243
+ children: [/* @__PURE__ */ jsx("div", {
244
+ className: "space-y-4",
245
+ children
246
+ }), /* @__PURE__ */ jsx(SearchFilterActions, { onClose: () => setIsOpen(false) })]
247
+ });
248
+ const triggerButton = /* @__PURE__ */ jsxs(Button, {
249
+ variant: "outline",
250
+ size: "default",
251
+ className: cn("relative h-10 w-10 shrink-0 px-0 sm:w-auto sm:px-4", hasActiveFilters && "bg-primary/10 border-primary hover:bg-primary/15"),
252
+ disabled,
253
+ "aria-label": "Open filters",
254
+ children: [
255
+ /* @__PURE__ */ jsx(Filter, { className: "size-4" }),
256
+ /* @__PURE__ */ jsx("span", {
257
+ className: "hidden sm:inline ml-2",
258
+ children: "Filters"
259
+ }),
260
+ activeFilterCount > 0 && /* @__PURE__ */ jsx(Badge, {
261
+ variant: "secondary",
262
+ className: "absolute -right-1 -top-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1 text-xs font-medium text-primary-foreground",
263
+ children: activeFilterCount
264
+ })
265
+ ]
266
+ });
267
+ if (isMobile) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Button, {
268
+ variant: "outline",
269
+ size: "default",
270
+ className: cn("relative h-10 w-10 shrink-0 px-0", hasActiveFilters && "bg-primary/10 border-primary hover:bg-primary/15"),
271
+ disabled,
272
+ "aria-label": "Open filters",
273
+ onClick: () => setIsOpen(true),
274
+ children: [/* @__PURE__ */ jsx(Filter, { className: "size-4" }), activeFilterCount > 0 && /* @__PURE__ */ jsx(Badge, {
275
+ variant: "secondary",
276
+ className: "absolute -right-1 -top-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1 text-xs font-medium text-primary-foreground",
277
+ children: activeFilterCount
278
+ })]
279
+ }), /* @__PURE__ */ jsx(SheetWrapper, {
280
+ open: isOpen,
281
+ onOpenChange: setIsOpen,
282
+ title,
283
+ description,
284
+ side: "bottom",
285
+ size: "default",
286
+ children: /* @__PURE__ */ jsx(FilterContent, {})
287
+ })] });
288
+ return /* @__PURE__ */ jsxs(Popover, {
289
+ open: isOpen,
290
+ onOpenChange: setIsOpen,
291
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, { render: triggerButton }), /* @__PURE__ */ jsx(PopoverContent, {
292
+ className: "w-80 p-4 sm:w-96",
293
+ align: "end",
294
+ sideOffset: 8,
295
+ children: /* @__PURE__ */ jsx(FilterContent, {})
296
+ })]
297
+ });
298
+ }
299
+
300
+ //#endregion
301
+ //#region src/components/search/search-actions.tsx
302
+ /**
303
+ * Search action buttons
304
+ *
305
+ * @example
306
+ * // Combined clear (default)
307
+ * <Search.Actions />
308
+ *
309
+ * @example
310
+ * // Split clear — separate buttons for search text vs filters
311
+ * <Search.Actions clearMode="split" />
312
+ */
313
+ function SearchActions({ showSearchButton = true, showClearButton = true, searchButtonText, clearButtonText = "Clear", clearMode = "combined", disabled, className }) {
314
+ const { handleSearch, clearSearch, clearSearchValue, clearFilters, searchValue, hasActiveFilters, hasActiveSearch } = useSearch();
315
+ const isMobile = useIsMobile();
316
+ const canSearch = Boolean(searchValue?.trim() || hasActiveFilters);
317
+ const canClear = Boolean(searchValue?.trim() || hasActiveFilters || hasActiveSearch);
318
+ const canClearSearch = Boolean(searchValue?.trim() || hasActiveSearch);
319
+ return /* @__PURE__ */ jsxs("div", {
320
+ className: cn("flex items-center gap-2", className),
321
+ children: [
322
+ showClearButton && clearMode === "combined" && canClear && (isMobile ? /* @__PURE__ */ jsx(Button, {
323
+ variant: "outline",
324
+ size: "icon",
325
+ onClick: clearSearch,
326
+ "aria-label": clearButtonText,
327
+ title: clearButtonText,
328
+ className: "shrink-0",
329
+ children: /* @__PURE__ */ jsx(RotateCcw, {})
330
+ }) : /* @__PURE__ */ jsx(Button, {
331
+ variant: "outline",
332
+ size: "default",
333
+ onClick: clearSearch,
334
+ className: "h-10 shrink-0",
335
+ children: clearButtonText
336
+ })),
337
+ showClearButton && clearMode === "split" && /* @__PURE__ */ jsxs(Fragment, { children: [canClearSearch && /* @__PURE__ */ jsx(Button, {
338
+ variant: "outline",
339
+ size: isMobile ? "icon" : "default",
340
+ onClick: clearSearchValue,
341
+ "aria-label": "Clear search",
342
+ title: "Clear search",
343
+ className: cn("shrink-0", !isMobile && "h-10"),
344
+ children: isMobile ? /* @__PURE__ */ jsx(RotateCcw, {}) : "Clear search"
345
+ }), hasActiveFilters && /* @__PURE__ */ jsx(Button, {
346
+ variant: "outline",
347
+ size: isMobile ? "icon" : "default",
348
+ onClick: clearFilters,
349
+ "aria-label": "Clear filters",
350
+ title: "Clear filters",
351
+ className: cn("shrink-0", !isMobile && "h-10"),
352
+ children: isMobile ? /* @__PURE__ */ jsx(FilterX, {}) : "Clear filters"
353
+ })] }),
354
+ showSearchButton && /* @__PURE__ */ jsxs(Button, {
355
+ size: "default",
356
+ onClick: handleSearch,
357
+ disabled: disabled || !canSearch,
358
+ className: "h-10 shrink-0 px-3 sm:px-4",
359
+ children: [/* @__PURE__ */ jsx(Search, {
360
+ size: 16,
361
+ className: cn(searchButtonText ? "mr-2" : "sm:mr-2")
362
+ }), /* @__PURE__ */ jsx("span", {
363
+ className: "hidden sm:inline",
364
+ children: searchButtonText || "Search"
365
+ })]
366
+ })
367
+ ]
368
+ });
369
+ }
370
+
371
+ //#endregion
372
+ //#region src/components/search/search-container.tsx
373
+ /**
374
+ * Container for search input and action buttons
375
+ * Provides responsive layout
376
+ *
377
+ * @example
378
+ * <Search.Container>
379
+ * <Search.Input />
380
+ * <Search.Filters />
381
+ * <Search.Actions />
382
+ * </Search.Container>
383
+ */
384
+ function SearchContainer({ children, className }) {
385
+ return /* @__PURE__ */ jsx("div", {
386
+ className: cn("flex items-center gap-2 flex-1", className),
387
+ children
388
+ });
389
+ }
390
+
391
+ //#endregion
392
+ //#region src/components/search/search-active-filters.tsx
393
+ function capitalize(s) {
394
+ return s.charAt(0).toUpperCase() + s.replace(/([A-Z])/g, " $1").slice(1);
395
+ }
396
+ function isFilterActive(value, defaultValue) {
397
+ if (value === defaultValue) return false;
398
+ if (Array.isArray(value)) return value.filter(Boolean).length > 0;
399
+ if (typeof value === "string") return value.trim().length > 0 && value !== "all";
400
+ if (typeof value === "boolean") return value;
401
+ return value != null;
402
+ }
403
+ function formatFilterValue(value, valueLabelMap) {
404
+ if (Array.isArray(value)) {
405
+ const mapped = value.map((v) => valueLabelMap?.[String(v)] ?? String(v));
406
+ return mapped.length <= 3 ? mapped.join(", ") : `${mapped.length} selected`;
407
+ }
408
+ if (typeof value === "boolean") return value ? "Yes" : "No";
409
+ const str = String(value);
410
+ return valueLabelMap?.[str] ?? str;
411
+ }
412
+ /**
413
+ * Renders active filters as removable pills below the search bar.
414
+ *
415
+ * @example
416
+ * ```tsx
417
+ * <Search.Root hook={searchHook}>
418
+ * <Search.Container>
419
+ * <Search.Input />
420
+ * <Search.Filters>{...}</Search.Filters>
421
+ * <Search.Actions />
422
+ * </Search.Container>
423
+ * <Search.ActiveFilters
424
+ * labels={{ status: "Status", role: "Role" }}
425
+ * valueLabels={{ status: { active: "Active", inactive: "Inactive" } }}
426
+ * />
427
+ * </Search.Root>
428
+ * ```
429
+ */
430
+ function SearchActiveFilters({ labels, valueLabels, formatValue: customFormat, showClearAll = true, className }) {
431
+ const { filters, filterFields, removeFilter, clearFilters, hasActiveFilters } = useSearch();
432
+ if (!hasActiveFilters) return null;
433
+ const activePills = [];
434
+ for (const [key, value] of Object.entries(filters)) {
435
+ const config = filterFields[key];
436
+ if (!config) continue;
437
+ if (!isFilterActive(value, config.defaultValue ?? (config.type === "array" ? [] : ""))) continue;
438
+ const label = labels?.[key] ?? config.label ?? capitalize(key);
439
+ let display;
440
+ if (customFormat) {
441
+ display = customFormat(key, value);
442
+ if (display === null) continue;
443
+ } else display = formatFilterValue(value, valueLabels?.[key]);
444
+ activePills.push({
445
+ key,
446
+ label,
447
+ display
448
+ });
449
+ }
450
+ if (activePills.length === 0) return null;
451
+ return /* @__PURE__ */ jsxs("div", {
452
+ className: cn("flex flex-wrap items-center gap-1.5", className),
453
+ children: [activePills.map(({ key, label, display }) => /* @__PURE__ */ jsxs(Badge, {
454
+ variant: "secondary",
455
+ className: "gap-1 pl-2 pr-1 py-0.5 text-xs font-normal",
456
+ children: [
457
+ /* @__PURE__ */ jsxs("span", {
458
+ className: "font-medium",
459
+ children: [label, ":"]
460
+ }),
461
+ /* @__PURE__ */ jsx("span", {
462
+ className: "max-w-[120px] truncate",
463
+ children: display
464
+ }),
465
+ /* @__PURE__ */ jsx("button", {
466
+ type: "button",
467
+ onClick: () => removeFilter(key),
468
+ className: "ml-0.5 rounded-sm p-0.5 hover:bg-muted-foreground/20",
469
+ "aria-label": `Remove ${label} filter`,
470
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
471
+ })
472
+ ]
473
+ }, key)), showClearAll && activePills.length > 1 && /* @__PURE__ */ jsx(Button, {
474
+ variant: "ghost",
475
+ size: "sm",
476
+ onClick: clearFilters,
477
+ className: "h-6 px-2 text-xs text-muted-foreground",
478
+ children: "Clear all"
479
+ })]
480
+ });
481
+ }
482
+
483
+ //#endregion
484
+ //#region src/components/search/index.ts
485
+ var search_exports = /* @__PURE__ */ __exportAll({
486
+ Actions: () => SearchActions,
487
+ ActiveFilters: () => SearchActiveFilters,
488
+ Container: () => SearchContainer,
489
+ FilterActions: () => SearchFilterActions,
490
+ Filters: () => SearchFilters,
491
+ Input: () => SearchInput,
492
+ Root: () => SearchRoot,
493
+ SearchProvider: () => SearchProvider,
494
+ TypeInput: () => SearchTypeInput,
495
+ useSearch: () => useSearch
496
+ });
497
+
498
+ //#endregion
499
+ //#region src/search.ts
500
+ /**
501
+ * @classytic/fluid/search
502
+ * Composable search component system with filters and actions
503
+ *
504
+ * @example
505
+ * ```tsx
506
+ * import { Search, SearchProvider, useSearch } from "@classytic/fluid/search";
507
+ *
508
+ * function MySearch() {
509
+ * return (
510
+ * <Search.Root hook={searchHook}>
511
+ * <Search.Container>
512
+ * <Search.Input placeholder="Search..." />
513
+ * <Search.Filters>{...}</Search.Filters>
514
+ * <Search.Actions />
515
+ * </Search.Container>
516
+ * </Search.Root>
517
+ * );
518
+ * }
519
+ * ```
520
+ */
521
+
522
+ //#endregion
523
+ export { search_exports as Search, SearchProvider, useSearch };