@classytic/fluid 0.4.1 → 0.5.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.
- package/README.md +21 -1
- package/dist/client/calendar.d.mts +1 -2
- package/dist/client/calendar.mjs +4 -4
- package/dist/client/color-picker.d.mts +94 -0
- package/dist/client/color-picker.mjs +392 -0
- package/dist/client/core.d.mts +243 -557
- package/dist/client/core.mjs +351 -1462
- package/dist/client/error.d.mts +41 -41
- package/dist/client/error.mjs +35 -35
- package/dist/client/gallery.d.mts +175 -0
- package/dist/client/gallery.mjs +546 -0
- package/dist/client/hooks.d.mts +57 -39
- package/dist/client/hooks.mjs +29 -7
- package/dist/client/spreadsheet.d.mts +30 -27
- package/dist/client/spreadsheet.mjs +80 -80
- package/dist/client/table.d.mts +66 -33
- package/dist/client/table.mjs +87 -54
- package/dist/client/theme.mjs +1 -1
- package/dist/command.d.mts +6 -4
- package/dist/command.mjs +3 -3
- package/dist/compact.d.mts +97 -95
- package/dist/compact.mjs +336 -322
- package/dist/dashboard.d.mts +614 -422
- package/dist/dashboard.mjs +1051 -762
- package/dist/{dropdown-wrapper-B86u9Fri.mjs → dropdown-wrapper-B9nRDUlz.mjs} +25 -35
- package/dist/forms.d.mts +1037 -972
- package/dist/forms.mjs +2849 -2721
- package/dist/index.d.mts +218 -152
- package/dist/index.mjs +357 -264
- package/dist/layouts.d.mts +94 -94
- package/dist/layouts.mjs +115 -110
- package/dist/phone-input-B9_XPNvv.mjs +429 -0
- package/dist/phone-input-CLH_UjQZ.d.mts +31 -0
- package/dist/{search-context-DR7DBs7S.mjs → search-context-1g3ZmOvx.mjs} +1 -1
- package/dist/search.d.mts +168 -164
- package/dist/search.mjs +305 -301
- package/dist/{sheet-wrapper-C13Y-Q6w.mjs → sheet-wrapper-B2uxookb.mjs} +1 -1
- package/dist/timeline-Bgu1mIe9.d.mts +373 -0
- package/dist/timeline-HJtWf4Op.mjs +804 -0
- package/dist/{use-base-search-BGgWnWaF.d.mts → use-base-search-DFC4QKYU.d.mts} +1 -1
- package/dist/{use-media-query-BnVNIKT4.mjs → use-media-query-ChLfFChU.mjs} +6 -7
- package/package.json +10 -2
- /package/dist/{api-pagination-CJ0vR_w6.d.mts → api-pagination-C30ser2L.d.mts} +0 -0
- /package/dist/{filter-utils-DqMmy_v-.mjs → filter-utils-BGIvtq1R.mjs} +0 -0
- /package/dist/{filter-utils-IZ0GtuPo.d.mts → filter-utils-DOFTBWm1.d.mts} +0 -0
- /package/dist/{use-debounce-xmZucz5e.mjs → use-debounce-BNoNiEon.mjs} +0 -0
- /package/dist/{use-keyboard-shortcut-Bl6YM5Q7.mjs → use-keyboard-shortcut-C_Vk-36P.mjs} +0 -0
- /package/dist/{use-keyboard-shortcut-_mRCh3QO.d.mts → use-keyboard-shortcut-Q4CSPzSI.d.mts} +0 -0
- /package/dist/{use-mobile-BX3SQVo2.mjs → use-mobile-CnEmFiQx.mjs} +0 -0
- /package/dist/{use-scroll-detection-CsgsQYvy.mjs → use-scroll-detection-BKfqkmEC.mjs} +0 -0
- /package/dist/{utils-CDue7cEt.d.mts → utils-rqvYP1by.d.mts} +0 -0
package/dist/search.mjs
CHANGED
|
@@ -2,183 +2,197 @@
|
|
|
2
2
|
|
|
3
3
|
import { t as __exportAll } from "./chunk-DQk6qfdC.mjs";
|
|
4
4
|
import { t as cn } from "./utils-DQ5SCVoW.mjs";
|
|
5
|
-
import { t as useIsMobile } from "./use-mobile-
|
|
6
|
-
import { r as SheetWrapper } from "./sheet-wrapper-
|
|
7
|
-
import { n as useSearch, t as SearchProvider } from "./search-context-
|
|
8
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { t as useIsMobile } from "./use-mobile-CnEmFiQx.mjs";
|
|
6
|
+
import { r as SheetWrapper } from "./sheet-wrapper-B2uxookb.mjs";
|
|
7
|
+
import { n as useSearch, t as SearchProvider } from "./search-context-1g3ZmOvx.mjs";
|
|
9
8
|
import { useEffect, useRef, useState } from "react";
|
|
9
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
10
10
|
import { Filter, FilterX, RotateCcw, Search, X } from "lucide-react";
|
|
11
11
|
import { Button } from "@/components/ui/button";
|
|
12
|
+
import { Badge } from "@/components/ui/badge";
|
|
12
13
|
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@/components/ui/input-group";
|
|
13
14
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
|
14
|
-
import { Badge } from "@/components/ui/badge";
|
|
15
15
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
16
16
|
|
|
17
|
-
//#region src/components/search/search-
|
|
17
|
+
//#region src/components/search/search-actions.tsx
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* Search action buttons
|
|
20
20
|
*
|
|
21
21
|
* @example
|
|
22
|
-
* //
|
|
23
|
-
* <Search.
|
|
24
|
-
* <Search.Input />
|
|
25
|
-
* <Search.Actions />
|
|
26
|
-
* </Search.Root>
|
|
22
|
+
* // Combined clear (default)
|
|
23
|
+
* <Search.Actions />
|
|
27
24
|
*
|
|
28
25
|
* @example
|
|
29
|
-
* //
|
|
30
|
-
* <Search.
|
|
31
|
-
* <Search.Input />
|
|
32
|
-
* <Search.ActiveFilters />
|
|
33
|
-
* </Search.Root>
|
|
26
|
+
* // Split clear — separate buttons for search text vs filters
|
|
27
|
+
* <Search.Actions clearMode="split" />
|
|
34
28
|
*/
|
|
35
|
-
function
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
29
|
+
function SearchActions({ showSearchButton = true, showClearButton = true, searchButtonText, clearButtonText = "Clear", clearMode = "combined", disabled, className }) {
|
|
30
|
+
const { handleSearch, clearSearch, clearSearchValue, clearFilters, searchValue, hasActiveFilters, hasActiveSearch } = useSearch();
|
|
31
|
+
const isMobile = useIsMobile();
|
|
32
|
+
const canSearch = Boolean(searchValue?.trim() || hasActiveFilters);
|
|
33
|
+
const canClear = Boolean(searchValue?.trim() || hasActiveFilters || hasActiveSearch);
|
|
34
|
+
const canClearSearch = Boolean(searchValue?.trim() || hasActiveSearch);
|
|
35
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
36
|
+
className: cn("flex items-center gap-2", className),
|
|
37
|
+
children: [
|
|
38
|
+
showClearButton && clearMode === "combined" && canClear && (isMobile ? /* @__PURE__ */ jsx(Button, {
|
|
39
|
+
variant: "outline",
|
|
40
|
+
size: "icon",
|
|
41
|
+
onClick: clearSearch,
|
|
42
|
+
"aria-label": clearButtonText,
|
|
43
|
+
title: clearButtonText,
|
|
44
|
+
className: "shrink-0",
|
|
45
|
+
children: /* @__PURE__ */ jsx(RotateCcw, {})
|
|
46
|
+
}) : /* @__PURE__ */ jsx(Button, {
|
|
47
|
+
variant: "outline",
|
|
48
|
+
size: "default",
|
|
49
|
+
onClick: clearSearch,
|
|
50
|
+
className: "h-10 shrink-0",
|
|
51
|
+
children: clearButtonText
|
|
52
|
+
})),
|
|
53
|
+
showClearButton && clearMode === "split" && /* @__PURE__ */ jsxs(Fragment$1, { children: [canClearSearch && /* @__PURE__ */ jsx(Button, {
|
|
54
|
+
variant: "outline",
|
|
55
|
+
size: isMobile ? "icon" : "default",
|
|
56
|
+
onClick: clearSearchValue,
|
|
57
|
+
"aria-label": "Clear search",
|
|
58
|
+
title: "Clear search",
|
|
59
|
+
className: cn("shrink-0", !isMobile && "h-10"),
|
|
60
|
+
children: isMobile ? /* @__PURE__ */ jsx(RotateCcw, {}) : "Clear search"
|
|
61
|
+
}), hasActiveFilters && /* @__PURE__ */ jsx(Button, {
|
|
62
|
+
variant: "outline",
|
|
63
|
+
size: isMobile ? "icon" : "default",
|
|
64
|
+
onClick: clearFilters,
|
|
65
|
+
"aria-label": "Clear filters",
|
|
66
|
+
title: "Clear filters",
|
|
67
|
+
className: cn("shrink-0", !isMobile && "h-10"),
|
|
68
|
+
children: isMobile ? /* @__PURE__ */ jsx(FilterX, {}) : "Clear filters"
|
|
69
|
+
})] }),
|
|
70
|
+
showSearchButton && /* @__PURE__ */ jsxs(Button, {
|
|
71
|
+
size: "default",
|
|
72
|
+
onClick: handleSearch,
|
|
73
|
+
disabled: disabled || !canSearch,
|
|
74
|
+
className: "h-10 shrink-0 px-3 sm:px-4",
|
|
75
|
+
children: [/* @__PURE__ */ jsx(Search, {
|
|
76
|
+
size: 16,
|
|
77
|
+
className: cn(searchButtonText ? "mr-2" : "sm:mr-2")
|
|
78
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
79
|
+
className: "hidden sm:inline",
|
|
80
|
+
children: searchButtonText || "Search"
|
|
81
|
+
})]
|
|
82
|
+
})
|
|
83
|
+
]
|
|
66
84
|
});
|
|
67
85
|
}
|
|
68
86
|
|
|
69
87
|
//#endregion
|
|
70
|
-
//#region src/components/search/search-
|
|
88
|
+
//#region src/components/search/search-active-filters.tsx
|
|
89
|
+
function capitalize(s) {
|
|
90
|
+
return s.charAt(0).toUpperCase() + s.replace(/([A-Z])/g, " $1").slice(1);
|
|
91
|
+
}
|
|
92
|
+
function isFilterActive(value, defaultValue) {
|
|
93
|
+
if (value === defaultValue) return false;
|
|
94
|
+
if (Array.isArray(value)) return value.filter(Boolean).length > 0;
|
|
95
|
+
if (typeof value === "string") return value.trim().length > 0 && value !== "all";
|
|
96
|
+
if (typeof value === "boolean") return value;
|
|
97
|
+
return value != null;
|
|
98
|
+
}
|
|
99
|
+
function formatFilterValue(value, valueLabelMap) {
|
|
100
|
+
if (Array.isArray(value)) {
|
|
101
|
+
const mapped = value.map((v) => valueLabelMap?.[String(v)] ?? String(v));
|
|
102
|
+
return mapped.length <= 3 ? mapped.join(", ") : `${mapped.length} selected`;
|
|
103
|
+
}
|
|
104
|
+
if (typeof value === "boolean") return value ? "Yes" : "No";
|
|
105
|
+
const str = String(value);
|
|
106
|
+
return valueLabelMap?.[str] ?? str;
|
|
107
|
+
}
|
|
71
108
|
/**
|
|
72
|
-
*
|
|
109
|
+
* Renders active filters as removable pills below the search bar.
|
|
73
110
|
*
|
|
74
111
|
* @example
|
|
75
|
-
*
|
|
112
|
+
* ```tsx
|
|
113
|
+
* <Search.Root hook={searchHook}>
|
|
114
|
+
* <Search.Container>
|
|
115
|
+
* <Search.Input />
|
|
116
|
+
* <Search.Filters>{...}</Search.Filters>
|
|
117
|
+
* <Search.Actions />
|
|
118
|
+
* </Search.Container>
|
|
119
|
+
* <Search.ActiveFilters
|
|
120
|
+
* labels={{ status: "Status", role: "Role" }}
|
|
121
|
+
* valueLabels={{ status: { active: "Active", inactive: "Inactive" } }}
|
|
122
|
+
* />
|
|
123
|
+
* </Search.Root>
|
|
124
|
+
* ```
|
|
76
125
|
*/
|
|
77
|
-
function
|
|
78
|
-
const {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
126
|
+
function SearchActiveFilters({ labels, valueLabels, formatValue: customFormat, showClearAll = true, className }) {
|
|
127
|
+
const { filters, filterFields, removeFilter, clearFilters, hasActiveFilters } = useSearch();
|
|
128
|
+
if (!hasActiveFilters) return null;
|
|
129
|
+
const activePills = [];
|
|
130
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
131
|
+
const config = filterFields[key];
|
|
132
|
+
if (!config) continue;
|
|
133
|
+
if (!isFilterActive(value, config.defaultValue ?? (config.type === "array" ? [] : ""))) continue;
|
|
134
|
+
const label = labels?.[key] ?? config.label ?? capitalize(key);
|
|
135
|
+
let display;
|
|
136
|
+
if (customFormat) {
|
|
137
|
+
display = customFormat(key, value);
|
|
138
|
+
if (display === null) continue;
|
|
139
|
+
} else display = formatFilterValue(value, valueLabels?.[key]);
|
|
140
|
+
activePills.push({
|
|
141
|
+
key,
|
|
142
|
+
label,
|
|
143
|
+
display
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (activePills.length === 0) return null;
|
|
147
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
148
|
+
className: cn("flex flex-wrap items-center gap-1.5", className),
|
|
149
|
+
children: [activePills.map(({ key, label, display }) => /* @__PURE__ */ jsxs(Badge, {
|
|
150
|
+
variant: "secondary",
|
|
151
|
+
className: "gap-1 pl-2 pr-1 py-0.5 text-xs font-normal",
|
|
152
|
+
children: [
|
|
153
|
+
/* @__PURE__ */ jsxs("span", {
|
|
154
|
+
className: "font-medium",
|
|
155
|
+
children: [label, ":"]
|
|
156
|
+
}),
|
|
157
|
+
/* @__PURE__ */ jsx("span", {
|
|
158
|
+
className: "max-w-[120px] truncate",
|
|
159
|
+
children: display
|
|
160
|
+
}),
|
|
161
|
+
/* @__PURE__ */ jsx("button", {
|
|
162
|
+
type: "button",
|
|
163
|
+
onClick: () => removeFilter(key),
|
|
164
|
+
className: "ml-0.5 rounded-sm p-0.5 hover:bg-muted-foreground/20",
|
|
165
|
+
"aria-label": `Remove ${label} filter`,
|
|
166
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
|
|
109
167
|
})
|
|
110
|
-
|
|
111
|
-
|
|
168
|
+
]
|
|
169
|
+
}, key)), showClearAll && activePills.length > 1 && /* @__PURE__ */ jsx(Button, {
|
|
170
|
+
variant: "ghost",
|
|
171
|
+
size: "sm",
|
|
172
|
+
onClick: clearFilters,
|
|
173
|
+
className: "h-6 px-2 text-xs text-muted-foreground",
|
|
174
|
+
children: "Clear all"
|
|
175
|
+
})]
|
|
112
176
|
});
|
|
113
177
|
}
|
|
114
178
|
|
|
115
179
|
//#endregion
|
|
116
|
-
//#region src/components/search/search-
|
|
180
|
+
//#region src/components/search/search-container.tsx
|
|
117
181
|
/**
|
|
118
|
-
*
|
|
182
|
+
* Container for search input and action buttons
|
|
183
|
+
* Provides responsive layout
|
|
119
184
|
*
|
|
120
185
|
* @example
|
|
121
|
-
* <Search.
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* { value: "customerEmail", label: "Email" },
|
|
127
|
-
* ]}
|
|
128
|
-
* />
|
|
186
|
+
* <Search.Container>
|
|
187
|
+
* <Search.Input />
|
|
188
|
+
* <Search.Filters />
|
|
189
|
+
* <Search.Actions />
|
|
190
|
+
* </Search.Container>
|
|
129
191
|
*/
|
|
130
|
-
function
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
]
|
|
192
|
+
function SearchContainer({ children, className }) {
|
|
193
|
+
return /* @__PURE__ */ jsx("div", {
|
|
194
|
+
className: cn("flex items-center gap-2 flex-1", className),
|
|
195
|
+
children
|
|
182
196
|
});
|
|
183
197
|
}
|
|
184
198
|
|
|
@@ -264,7 +278,7 @@ function SearchFilters({ children, title = "Filters", description = "Refine your
|
|
|
264
278
|
})
|
|
265
279
|
]
|
|
266
280
|
});
|
|
267
|
-
if (isMobile) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Button, {
|
|
281
|
+
if (isMobile) return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Button, {
|
|
268
282
|
variant: "outline",
|
|
269
283
|
size: "default",
|
|
270
284
|
className: cn("relative h-10 w-10 shrink-0 px-0", hasActiveFilters && "bg-primary/10 border-primary hover:bg-primary/15"),
|
|
@@ -298,185 +312,175 @@ function SearchFilters({ children, title = "Filters", description = "Refine your
|
|
|
298
312
|
}
|
|
299
313
|
|
|
300
314
|
//#endregion
|
|
301
|
-
//#region src/components/search/search-
|
|
315
|
+
//#region src/components/search/search-input.tsx
|
|
302
316
|
/**
|
|
303
|
-
* Search
|
|
304
|
-
*
|
|
305
|
-
* @example
|
|
306
|
-
* // Combined clear (default)
|
|
307
|
-
* <Search.Actions />
|
|
317
|
+
* Search input component using modern shadcn InputGroup pattern
|
|
308
318
|
*
|
|
309
319
|
* @example
|
|
310
|
-
*
|
|
311
|
-
* <Search.Actions clearMode="split" />
|
|
320
|
+
* <Search.Input placeholder="Search..." />
|
|
312
321
|
*/
|
|
313
|
-
function
|
|
314
|
-
const {
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
return /* @__PURE__ */ jsxs(
|
|
320
|
-
className: cn("flex
|
|
322
|
+
function SearchInput({ placeholder = "Search...", className, inputClassName, disabled, showIcon = true, showClearButton = false, onKeyDown, ...props }) {
|
|
323
|
+
const { searchValue, setSearchValue, handleSearch } = useSearch();
|
|
324
|
+
const handleKeyDown = (event) => {
|
|
325
|
+
onKeyDown?.(event);
|
|
326
|
+
if (!event.defaultPrevented && event.key === "Enter" && !disabled) handleSearch?.();
|
|
327
|
+
};
|
|
328
|
+
return /* @__PURE__ */ jsxs(InputGroup, {
|
|
329
|
+
className: cn("h-10 flex-1 shadow-sm", className),
|
|
330
|
+
"data-disabled": disabled || void 0,
|
|
321
331
|
children: [
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
className: "
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
})]
|
|
332
|
+
showIcon && /* @__PURE__ */ jsx(InputGroupAddon, {
|
|
333
|
+
align: "inline-start",
|
|
334
|
+
children: /* @__PURE__ */ jsx(Search, { className: "size-4" })
|
|
335
|
+
}),
|
|
336
|
+
/* @__PURE__ */ jsx(InputGroupInput, {
|
|
337
|
+
...props,
|
|
338
|
+
type: "text",
|
|
339
|
+
placeholder,
|
|
340
|
+
value: searchValue || "",
|
|
341
|
+
onChange: (e) => setSearchValue?.(e.target.value),
|
|
342
|
+
onKeyDown: handleKeyDown,
|
|
343
|
+
disabled,
|
|
344
|
+
className: cn("text-base", inputClassName)
|
|
345
|
+
}),
|
|
346
|
+
showClearButton && searchValue && /* @__PURE__ */ jsx(InputGroupAddon, {
|
|
347
|
+
align: "inline-end",
|
|
348
|
+
children: /* @__PURE__ */ jsx(InputGroupButton, {
|
|
349
|
+
variant: "ghost",
|
|
350
|
+
size: "icon-sm",
|
|
351
|
+
onClick: () => setSearchValue?.(""),
|
|
352
|
+
disabled,
|
|
353
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-4" })
|
|
354
|
+
})
|
|
366
355
|
})
|
|
367
356
|
]
|
|
368
357
|
});
|
|
369
358
|
}
|
|
370
359
|
|
|
371
360
|
//#endregion
|
|
372
|
-
//#region src/components/search/search-
|
|
361
|
+
//#region src/components/search/search-root.tsx
|
|
373
362
|
/**
|
|
374
|
-
*
|
|
375
|
-
* Provides responsive layout
|
|
363
|
+
* Root search component that provides context to all child components
|
|
376
364
|
*
|
|
377
365
|
* @example
|
|
378
|
-
*
|
|
366
|
+
* // Manual mode (default)
|
|
367
|
+
* <Search.Root hook={useMySearch()}>
|
|
379
368
|
* <Search.Input />
|
|
380
|
-
* <Search.Filters />
|
|
381
369
|
* <Search.Actions />
|
|
382
|
-
* </Search.
|
|
370
|
+
* </Search.Root>
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* // Auto-search mode — no Search button needed
|
|
374
|
+
* <Search.Root hook={useMySearch()} autoSearch>
|
|
375
|
+
* <Search.Input />
|
|
376
|
+
* <Search.ActiveFilters />
|
|
377
|
+
* </Search.Root>
|
|
383
378
|
*/
|
|
384
|
-
function
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
379
|
+
function SearchRoot({ children, hook, className, autoSearch = false, autoSearchDelay = 300 }) {
|
|
380
|
+
const handleSearchRef = useRef(hook.handleSearch);
|
|
381
|
+
handleSearchRef.current = hook.handleSearch;
|
|
382
|
+
const timerRef = useRef(void 0);
|
|
383
|
+
const isInitialMount = useRef(true);
|
|
384
|
+
useEffect(() => {
|
|
385
|
+
if (!autoSearch) return () => {
|
|
386
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
387
|
+
};
|
|
388
|
+
if (isInitialMount.current) {
|
|
389
|
+
isInitialMount.current = false;
|
|
390
|
+
return () => {
|
|
391
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
395
|
+
timerRef.current = setTimeout(() => {
|
|
396
|
+
handleSearchRef.current();
|
|
397
|
+
}, autoSearchDelay);
|
|
398
|
+
return () => {
|
|
399
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
400
|
+
};
|
|
401
|
+
}, [
|
|
402
|
+
autoSearch,
|
|
403
|
+
autoSearchDelay,
|
|
404
|
+
hook.searchValue,
|
|
405
|
+
hook.searchType,
|
|
406
|
+
hook.filters
|
|
407
|
+
]);
|
|
408
|
+
return /* @__PURE__ */ jsx(SearchProvider, {
|
|
409
|
+
value: hook,
|
|
410
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
411
|
+
className: cn("w-full", className),
|
|
412
|
+
children
|
|
413
|
+
})
|
|
388
414
|
});
|
|
389
415
|
}
|
|
390
416
|
|
|
391
417
|
//#endregion
|
|
392
|
-
//#region src/components/search/search-
|
|
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
|
-
}
|
|
418
|
+
//#region src/components/search/search-type-input.tsx
|
|
412
419
|
/**
|
|
413
|
-
*
|
|
420
|
+
* Search input component with type selector using InputGroup pattern
|
|
414
421
|
*
|
|
415
422
|
* @example
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
* labels={{ status: "Status", role: "Role" }}
|
|
425
|
-
* valueLabels={{ status: { active: "Active", inactive: "Inactive" } }}
|
|
426
|
-
* />
|
|
427
|
-
* </Search.Root>
|
|
428
|
-
* ```
|
|
423
|
+
* <Search.TypeInput
|
|
424
|
+
* placeholder="Search..."
|
|
425
|
+
* searchTypeOptions={[
|
|
426
|
+
* { value: "_id", label: "ID" },
|
|
427
|
+
* { value: "customerPhone", label: "Phone" },
|
|
428
|
+
* { value: "customerEmail", label: "Email" },
|
|
429
|
+
* ]}
|
|
430
|
+
* />
|
|
429
431
|
*/
|
|
430
|
-
function
|
|
431
|
-
const {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
432
|
+
function SearchTypeInput({ placeholder = "Search...", className, inputClassName, disabled, showIcon = true, showClearButton = false, searchTypeOptions = [], onKeyDown, ...props }) {
|
|
433
|
+
const { searchValue, setSearchValue, searchType, setSearchType, handleSearch } = useSearch();
|
|
434
|
+
const handleKeyDown = (event) => {
|
|
435
|
+
onKeyDown?.(event);
|
|
436
|
+
if (!event.defaultPrevented && event.key === "Enter" && !disabled) handleSearch?.();
|
|
437
|
+
};
|
|
438
|
+
const selectedOption = searchTypeOptions.find((opt) => opt.value === searchType);
|
|
439
|
+
return /* @__PURE__ */ jsxs(InputGroup, {
|
|
440
|
+
className: cn("h-10 flex-1 shadow-sm", className),
|
|
441
|
+
"data-disabled": disabled || void 0,
|
|
442
|
+
children: [
|
|
443
|
+
showIcon && /* @__PURE__ */ jsx(InputGroupAddon, {
|
|
444
|
+
align: "inline-start",
|
|
445
|
+
children: /* @__PURE__ */ jsx(Search, { className: "size-4" })
|
|
446
|
+
}),
|
|
447
|
+
searchTypeOptions.length > 0 && /* @__PURE__ */ jsxs(InputGroupAddon, {
|
|
448
|
+
align: "inline-start",
|
|
449
|
+
className: "pr-0",
|
|
450
|
+
children: [/* @__PURE__ */ jsxs(Select, {
|
|
451
|
+
value: searchType,
|
|
452
|
+
onValueChange: setSearchType,
|
|
453
|
+
disabled,
|
|
454
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
455
|
+
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 dark:bg-transparent dark:hover:bg-transparent",
|
|
456
|
+
children: /* @__PURE__ */ jsx(SelectValue, { children: selectedOption?.label || searchTypeOptions[0]?.label })
|
|
457
|
+
}), /* @__PURE__ */ jsx(SelectContent, { children: searchTypeOptions.map((option) => /* @__PURE__ */ jsx(SelectItem, {
|
|
458
|
+
value: option.value,
|
|
459
|
+
children: option.label
|
|
460
|
+
}, option.value)) })]
|
|
461
|
+
}), /* @__PURE__ */ jsx("div", { className: "h-6 w-px bg-border ml-1" })]
|
|
462
|
+
}),
|
|
463
|
+
/* @__PURE__ */ jsx(InputGroupInput, {
|
|
464
|
+
...props,
|
|
465
|
+
type: "text",
|
|
466
|
+
placeholder,
|
|
467
|
+
value: searchValue || "",
|
|
468
|
+
onChange: (e) => setSearchValue?.(e.target.value),
|
|
469
|
+
onKeyDown: handleKeyDown,
|
|
470
|
+
disabled,
|
|
471
|
+
className: cn("text-base", inputClassName)
|
|
472
|
+
}),
|
|
473
|
+
showClearButton && searchValue && /* @__PURE__ */ jsx(InputGroupAddon, {
|
|
474
|
+
align: "inline-end",
|
|
475
|
+
children: /* @__PURE__ */ jsx(InputGroupButton, {
|
|
476
|
+
variant: "ghost",
|
|
477
|
+
size: "icon-sm",
|
|
478
|
+
onClick: () => setSearchValue?.(""),
|
|
479
|
+
disabled,
|
|
480
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-4" })
|
|
471
481
|
})
|
|
472
|
-
|
|
473
|
-
|
|
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
|
-
})]
|
|
482
|
+
})
|
|
483
|
+
]
|
|
480
484
|
});
|
|
481
485
|
}
|
|
482
486
|
|