warqadui 0.0.8 → 0.0.10

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 (166) hide show
  1. package/package.json +62 -10
  2. package/.vscode/settings.json +0 -3
  3. package/apps/dev-app/.env +0 -1
  4. package/apps/dev-app/errors.log +0 -0
  5. package/apps/dev-app/index.html +0 -12
  6. package/apps/dev-app/node_modules/.vite/deps/@tanstack_react-table.js +0 -3254
  7. package/apps/dev-app/node_modules/.vite/deps/@tanstack_react-table.js.map +0 -7
  8. package/apps/dev-app/node_modules/.vite/deps/_metadata.json +0 -166
  9. package/apps/dev-app/node_modules/.vite/deps/antd.js +0 -108982
  10. package/apps/dev-app/node_modules/.vite/deps/antd.js.map +0 -7
  11. package/apps/dev-app/node_modules/.vite/deps/axios.js +0 -2751
  12. package/apps/dev-app/node_modules/.vite/deps/axios.js.map +0 -7
  13. package/apps/dev-app/node_modules/.vite/deps/chunk-5OG7DCD7.js +0 -41
  14. package/apps/dev-app/node_modules/.vite/deps/chunk-5OG7DCD7.js.map +0 -7
  15. package/apps/dev-app/node_modules/.vite/deps/chunk-DC5AMYBS.js +0 -39
  16. package/apps/dev-app/node_modules/.vite/deps/chunk-DC5AMYBS.js.map +0 -7
  17. package/apps/dev-app/node_modules/.vite/deps/chunk-DKXRQMOD.js +0 -135
  18. package/apps/dev-app/node_modules/.vite/deps/chunk-DKXRQMOD.js.map +0 -7
  19. package/apps/dev-app/node_modules/.vite/deps/chunk-EL47BWQR.js +0 -37
  20. package/apps/dev-app/node_modules/.vite/deps/chunk-EL47BWQR.js.map +0 -7
  21. package/apps/dev-app/node_modules/.vite/deps/chunk-HHL3MHGV.js +0 -288
  22. package/apps/dev-app/node_modules/.vite/deps/chunk-HHL3MHGV.js.map +0 -7
  23. package/apps/dev-app/node_modules/.vite/deps/chunk-IGGUWUPT.js +0 -60
  24. package/apps/dev-app/node_modules/.vite/deps/chunk-IGGUWUPT.js.map +0 -7
  25. package/apps/dev-app/node_modules/.vite/deps/chunk-IGXZPJXT.js +0 -928
  26. package/apps/dev-app/node_modules/.vite/deps/chunk-IGXZPJXT.js.map +0 -7
  27. package/apps/dev-app/node_modules/.vite/deps/chunk-L2GCM37S.js +0 -21628
  28. package/apps/dev-app/node_modules/.vite/deps/chunk-L2GCM37S.js.map +0 -7
  29. package/apps/dev-app/node_modules/.vite/deps/chunk-M7DZDBHW.js +0 -14
  30. package/apps/dev-app/node_modules/.vite/deps/chunk-M7DZDBHW.js.map +0 -7
  31. package/apps/dev-app/node_modules/.vite/deps/chunk-S54SBVCU.js +0 -1906
  32. package/apps/dev-app/node_modules/.vite/deps/chunk-S54SBVCU.js.map +0 -7
  33. package/apps/dev-app/node_modules/.vite/deps/chunk-WFNHCR67.js +0 -21
  34. package/apps/dev-app/node_modules/.vite/deps/chunk-WFNHCR67.js.map +0 -7
  35. package/apps/dev-app/node_modules/.vite/deps/clsx.js +0 -10
  36. package/apps/dev-app/node_modules/.vite/deps/clsx.js.map +0 -7
  37. package/apps/dev-app/node_modules/.vite/deps/dayjs.js +0 -6
  38. package/apps/dev-app/node_modules/.vite/deps/dayjs.js.map +0 -7
  39. package/apps/dev-app/node_modules/.vite/deps/dayjs_plugin_customParseFormat.js +0 -6
  40. package/apps/dev-app/node_modules/.vite/deps/dayjs_plugin_customParseFormat.js.map +0 -7
  41. package/apps/dev-app/node_modules/.vite/deps/framer-motion.js +0 -12388
  42. package/apps/dev-app/node_modules/.vite/deps/framer-motion.js.map +0 -7
  43. package/apps/dev-app/node_modules/.vite/deps/html2canvas-pro.js +0 -9713
  44. package/apps/dev-app/node_modules/.vite/deps/html2canvas-pro.js.map +0 -7
  45. package/apps/dev-app/node_modules/.vite/deps/html2canvas.esm-IYRWPPEI.js +0 -7808
  46. package/apps/dev-app/node_modules/.vite/deps/html2canvas.esm-IYRWPPEI.js.map +0 -7
  47. package/apps/dev-app/node_modules/.vite/deps/index.es-3WTXOFZ2.js +0 -10392
  48. package/apps/dev-app/node_modules/.vite/deps/index.es-3WTXOFZ2.js.map +0 -7
  49. package/apps/dev-app/node_modules/.vite/deps/jspdf.js +0 -14806
  50. package/apps/dev-app/node_modules/.vite/deps/jspdf.js.map +0 -7
  51. package/apps/dev-app/node_modules/.vite/deps/lucide-react.js +0 -31586
  52. package/apps/dev-app/node_modules/.vite/deps/lucide-react.js.map +0 -7
  53. package/apps/dev-app/node_modules/.vite/deps/package.json +0 -3
  54. package/apps/dev-app/node_modules/.vite/deps/purify.es-JNLDEIMX.js +0 -1029
  55. package/apps/dev-app/node_modules/.vite/deps/purify.es-JNLDEIMX.js.map +0 -7
  56. package/apps/dev-app/node_modules/.vite/deps/react-dom.js +0 -7
  57. package/apps/dev-app/node_modules/.vite/deps/react-dom.js.map +0 -7
  58. package/apps/dev-app/node_modules/.vite/deps/react-dom_client.js +0 -8
  59. package/apps/dev-app/node_modules/.vite/deps/react-dom_client.js.map +0 -7
  60. package/apps/dev-app/node_modules/.vite/deps/react-hook-form.js +0 -2233
  61. package/apps/dev-app/node_modules/.vite/deps/react-hook-form.js.map +0 -7
  62. package/apps/dev-app/node_modules/.vite/deps/react-phone-number-input.js +0 -9307
  63. package/apps/dev-app/node_modules/.vite/deps/react-phone-number-input.js.map +0 -7
  64. package/apps/dev-app/node_modules/.vite/deps/react-router-dom.js +0 -14234
  65. package/apps/dev-app/node_modules/.vite/deps/react-router-dom.js.map +0 -7
  66. package/apps/dev-app/node_modules/.vite/deps/react.js +0 -6
  67. package/apps/dev-app/node_modules/.vite/deps/react.js.map +0 -7
  68. package/apps/dev-app/node_modules/.vite/deps/react_jsx-dev-runtime.js +0 -913
  69. package/apps/dev-app/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +0 -7
  70. package/apps/dev-app/node_modules/.vite/deps/react_jsx-runtime.js +0 -7
  71. package/apps/dev-app/node_modules/.vite/deps/react_jsx-runtime.js.map +0 -7
  72. package/apps/dev-app/node_modules/.vite/deps/tailwind-merge.js +0 -2534
  73. package/apps/dev-app/node_modules/.vite/deps/tailwind-merge.js.map +0 -7
  74. package/apps/dev-app/node_modules/tailwindcss/LICENSE +0 -21
  75. package/apps/dev-app/node_modules/tailwindcss/README.md +0 -36
  76. package/apps/dev-app/node_modules/tailwindcss/dist/chunk-L5IEUH3R.mjs +0 -38
  77. package/apps/dev-app/node_modules/tailwindcss/dist/chunk-UWKE2Z6N.mjs +0 -1
  78. package/apps/dev-app/node_modules/tailwindcss/dist/chunk-X4GG3EDV.mjs +0 -1
  79. package/apps/dev-app/node_modules/tailwindcss/dist/colors-C__qRT83.d.ts +0 -347
  80. package/apps/dev-app/node_modules/tailwindcss/dist/colors.d.mts +0 -347
  81. package/apps/dev-app/node_modules/tailwindcss/dist/colors.d.ts +0 -5
  82. package/apps/dev-app/node_modules/tailwindcss/dist/colors.js +0 -1
  83. package/apps/dev-app/node_modules/tailwindcss/dist/colors.mjs +0 -1
  84. package/apps/dev-app/node_modules/tailwindcss/dist/default-theme.d.mts +0 -1199
  85. package/apps/dev-app/node_modules/tailwindcss/dist/default-theme.d.ts +0 -1199
  86. package/apps/dev-app/node_modules/tailwindcss/dist/default-theme.js +0 -1
  87. package/apps/dev-app/node_modules/tailwindcss/dist/default-theme.mjs +0 -1
  88. package/apps/dev-app/node_modules/tailwindcss/dist/flatten-color-palette.d.mts +0 -6
  89. package/apps/dev-app/node_modules/tailwindcss/dist/flatten-color-palette.d.ts +0 -6
  90. package/apps/dev-app/node_modules/tailwindcss/dist/flatten-color-palette.js +0 -3
  91. package/apps/dev-app/node_modules/tailwindcss/dist/flatten-color-palette.mjs +0 -1
  92. package/apps/dev-app/node_modules/tailwindcss/dist/lib.d.mts +0 -378
  93. package/apps/dev-app/node_modules/tailwindcss/dist/lib.d.ts +0 -3
  94. package/apps/dev-app/node_modules/tailwindcss/dist/lib.js +0 -38
  95. package/apps/dev-app/node_modules/tailwindcss/dist/lib.mjs +0 -1
  96. package/apps/dev-app/node_modules/tailwindcss/dist/plugin.d.mts +0 -11
  97. package/apps/dev-app/node_modules/tailwindcss/dist/plugin.d.ts +0 -134
  98. package/apps/dev-app/node_modules/tailwindcss/dist/plugin.js +0 -1
  99. package/apps/dev-app/node_modules/tailwindcss/dist/plugin.mjs +0 -1
  100. package/apps/dev-app/node_modules/tailwindcss/dist/resolve-config-B4yBzhca.d.ts +0 -29
  101. package/apps/dev-app/node_modules/tailwindcss/dist/resolve-config-QUZ9b-Gn.d.mts +0 -190
  102. package/apps/dev-app/node_modules/tailwindcss/dist/types-CJYAW1ql.d.mts +0 -128
  103. package/apps/dev-app/node_modules/tailwindcss/index.css +0 -944
  104. package/apps/dev-app/node_modules/tailwindcss/package.json +0 -89
  105. package/apps/dev-app/node_modules/tailwindcss/preflight.css +0 -393
  106. package/apps/dev-app/node_modules/tailwindcss/theme.css +0 -510
  107. package/apps/dev-app/node_modules/tailwindcss/utilities.css +0 -1
  108. package/apps/dev-app/package.json +0 -34
  109. package/apps/dev-app/src/App.tsx +0 -74
  110. package/apps/dev-app/src/index.css +0 -18
  111. package/apps/dev-app/src/main.tsx +0 -18
  112. package/apps/dev-app/src/pages/Buttons.tsx +0 -122
  113. package/apps/dev-app/src/pages/DataTable.tsx +0 -208
  114. package/apps/dev-app/src/pages/Fields.tsx +0 -342
  115. package/apps/dev-app/src/pages/Modals.tsx +0 -151
  116. package/apps/dev-app/src/pages/Spins.tsx +0 -161
  117. package/apps/dev-app/ts_errors.txt +0 -0
  118. package/apps/dev-app/tsconfig.json +0 -25
  119. package/apps/dev-app/tsconfig.node.json +0 -10
  120. package/apps/dev-app/vite.config.ts +0 -11
  121. package/packages/ui/log.txt +0 -0
  122. package/packages/ui/package.json +0 -70
  123. package/packages/ui/postcss.config.js +0 -6
  124. package/packages/ui/src/components/Button.tsx +0 -85
  125. package/packages/ui/src/components/Card.tsx +0 -97
  126. package/packages/ui/src/components/CodeBlock.tsx +0 -53
  127. package/packages/ui/src/components/DashboardLayout.tsx +0 -442
  128. package/packages/ui/src/components/Fields/Input.tsx +0 -191
  129. package/packages/ui/src/components/Fields/PhoneInput.tsx +0 -134
  130. package/packages/ui/src/components/Fields/date.tsx +0 -165
  131. package/packages/ui/src/components/Fields/index.tsx +0 -17
  132. package/packages/ui/src/components/Fields/searchApi.tsx +0 -479
  133. package/packages/ui/src/components/Fields/select.tsx +0 -131
  134. package/packages/ui/src/components/Fields/textArea.tsx +0 -121
  135. package/packages/ui/src/components/LoadingBox.tsx +0 -11
  136. package/packages/ui/src/components/PageHeader.tsx +0 -34
  137. package/packages/ui/src/components/ThemeToggle.tsx +0 -35
  138. package/packages/ui/src/components/modal/Modal.tsx +0 -81
  139. package/packages/ui/src/components/spins/ClassicSpin.tsx +0 -18
  140. package/packages/ui/src/components/spins/LoadingSpin.tsx +0 -45
  141. package/packages/ui/src/components/spins/OverlaySpin.tsx +0 -10
  142. package/packages/ui/src/components/spins/index.tsx +0 -13
  143. package/packages/ui/src/components/tables/DataTable.tsx +0 -261
  144. package/packages/ui/src/components/tables/index.ts +0 -1
  145. package/packages/ui/src/hooks/Fetches/useApis.tsx +0 -197
  146. package/packages/ui/src/hooks/ThemeContext.tsx +0 -56
  147. package/packages/ui/src/hooks/useModal.tsx +0 -38
  148. package/packages/ui/src/hooks/useTheme.ts +0 -34
  149. package/packages/ui/src/index.ts +0 -24
  150. package/packages/ui/src/providers/WarqadProvider.tsx +0 -69
  151. package/packages/ui/src/styles.css +0 -26
  152. package/packages/ui/src/utils/cn.ts +0 -6
  153. package/packages/ui/src/utils/pdf.ts +0 -171
  154. package/packages/ui/tailwind.config.js +0 -13
  155. package/packages/ui/tsconfig.json +0 -17
  156. package/packages/ui/warqad-ui-0.0.1.tgz +0 -0
  157. package/packages/ui/warqadui-0.0.3.tgz +0 -0
  158. package/warqad-ui-0.0.1.tgz +0 -0
  159. /package/{packages/ui/dist → dist}/index.d.mts +0 -0
  160. /package/{packages/ui/dist → dist}/index.d.ts +0 -0
  161. /package/{packages/ui/dist → dist}/index.js +0 -0
  162. /package/{packages/ui/dist → dist}/index.mjs +0 -0
  163. /package/{packages/ui/dist → dist}/styles.d.mts +0 -0
  164. /package/{packages/ui/dist → dist}/styles.d.ts +0 -0
  165. /package/{packages/ui/dist → dist}/styles.js +0 -0
  166. /package/{packages/ui/dist → dist}/styles.mjs +0 -0
@@ -1,479 +0,0 @@
1
- import React, { useState, useEffect, useRef, forwardRef } from "react";
2
- import { Search, Loader2, ChevronDown, Check, X } from "lucide-react";
3
- import useApi from "../../hooks/Fetches/useApis";
4
- import { useWarqadConfig } from "../../providers/WarqadProvider";
5
- import { Controller } from "react-hook-form";
6
-
7
- export interface SearchApiProps extends Omit<
8
- React.InputHTMLAttributes<HTMLInputElement>,
9
- "onChange" | "value"
10
- > {
11
- label?: string;
12
- apiUrl: string;
13
- placeholder?: string;
14
- value?: any;
15
- onChange?: (value: any) => void;
16
- queryKey?: string;
17
- labelKey?: string;
18
- valueKey?: string;
19
- error?: string;
20
- containerClassName?: string;
21
- name?: any;
22
- form?: any;
23
- obj?: any;
24
- filter?: any[];
25
- v?: number;
26
- }
27
-
28
- interface SearchApiContentProps extends SearchApiProps {
29
- currentValue: any;
30
- onChangeHandler: (value: any) => void;
31
- fieldInternalProps?: {
32
- ref?: React.Ref<any>;
33
- onBlur?: () => void;
34
- };
35
- }
36
-
37
- const SearchApiContent = ({
38
- label,
39
- apiUrl,
40
- placeholder = "Search...",
41
- currentValue,
42
- onChangeHandler,
43
- queryKey = "search",
44
- labelKey = "name",
45
- valueKey = "_id",
46
- error,
47
- containerClassName = "",
48
- className = "",
49
- disabled,
50
- name,
51
- obj,
52
- form,
53
- filter = [],
54
- required,
55
- v = 2,
56
- fieldInternalProps,
57
- ...props
58
- }: SearchApiContentProps) => {
59
- const [searchTerm, setSearchTerm] = useState("");
60
- const { theme } = useWarqadConfig();
61
- const primaryColor = theme?.primaryColor;
62
- const [options, setOptions] = useState<any[]>([]);
63
- const [isOpen, setIsOpen] = useState(false);
64
- const [isFocused, setIsFocused] = useState(false);
65
- const [activeIndex, setActiveIndex] = useState(-1);
66
- const [selectedObject, setSelectedObject] = useState<any>(undefined);
67
- const [hasAttemptedHydration, setHasAttemptedHydration] = useState(false);
68
- const containerRef = useRef<HTMLDivElement>(null);
69
- const inputRef = useRef<HTMLInputElement>(null);
70
- const prevApiUrlRef = useRef(apiUrl);
71
-
72
- const setInputRef = (element: HTMLInputElement | null) => {
73
- (inputRef as any).current = element;
74
- if (fieldInternalProps?.ref) {
75
- if (typeof fieldInternalProps.ref === "function") {
76
- fieldInternalProps.ref(element);
77
- } else {
78
- (fieldInternalProps.ref as any).current = element;
79
- }
80
- }
81
- };
82
-
83
- const { get, isLoading, error: errorMessage } = useApi();
84
-
85
- const fetchOptions = async (query: string) => {
86
- try {
87
- const params: Record<string, any> = {};
88
- if (query) params[queryKey] = query;
89
-
90
- const response = await get({
91
- url: apiUrl,
92
- params,
93
- v: v as any,
94
- });
95
-
96
- if (Array.isArray(response)) {
97
- setOptions(response);
98
- } else if (response?.data && Array.isArray(response.data)) {
99
- setOptions(response.data);
100
- } else if (response?.items && Array.isArray(response.items)) {
101
- setOptions(response.items);
102
- } else {
103
- setOptions([]);
104
- }
105
- } catch (err: any) {
106
- console.error("Failed to fetch options:", err);
107
- if (form && name) {
108
- form.setError(name, {
109
- type: "manual",
110
- message: err.message || "Failed to fetch options",
111
- });
112
- }
113
- setOptions([]);
114
- }
115
- };
116
-
117
- useEffect(() => {
118
- const timer = setTimeout(() => {
119
- if (isOpen) {
120
- fetchOptions(searchTerm);
121
- }
122
- }, 300);
123
- return () => clearTimeout(timer);
124
- }, [searchTerm, apiUrl, isOpen]);
125
-
126
- useEffect(() => {
127
- if (isOpen && options.length === 0) {
128
- fetchOptions("");
129
- }
130
- }, [isOpen]);
131
-
132
- useEffect(() => {
133
- if (currentValue && !selectedObject) {
134
- setHasAttemptedHydration(false);
135
- fetchOptions(currentValue).finally(() => {
136
- setHasAttemptedHydration(true);
137
- });
138
- } else if (!currentValue) {
139
- setHasAttemptedHydration(true);
140
- if (selectedObject) setSelectedObject(undefined);
141
- }
142
- }, [currentValue, selectedObject]);
143
-
144
- const getOptionValue = (option: any) => {
145
- if (
146
- valueKey === "_id" &&
147
- option._id === undefined &&
148
- option.id !== undefined
149
- ) {
150
- return option.id;
151
- }
152
- return option[valueKey];
153
- };
154
-
155
- useEffect(() => {
156
- if (currentValue && !selectedObject && options.length > 0) {
157
- const found = options.find((o) => getOptionValue(o) === currentValue);
158
- if (found) {
159
- setSelectedObject(found);
160
- if (form && obj) {
161
- form.setValue(obj, found);
162
- }
163
- }
164
- }
165
- }, [currentValue, options, selectedObject, form, obj]);
166
-
167
- useEffect(() => {
168
- if (prevApiUrlRef.current !== apiUrl) {
169
- prevApiUrlRef.current = apiUrl;
170
- setSearchTerm("");
171
- setOptions([]);
172
- setSelectedObject(undefined);
173
-
174
- if (form) {
175
- if (name) {
176
- form.setValue(name, undefined, {
177
- shouldValidate: true,
178
- shouldDirty: true,
179
- });
180
- }
181
- if (obj) {
182
- form.setValue(obj, undefined, {
183
- shouldValidate: true,
184
- shouldDirty: true,
185
- });
186
- }
187
- } else {
188
- onChangeHandler(undefined);
189
- }
190
- fetchOptions("");
191
- }
192
- }, [apiUrl, form, name, obj, onChangeHandler]);
193
-
194
- useEffect(() => {
195
- const handleClickOutside = (event: MouseEvent) => {
196
- if (
197
- containerRef.current &&
198
- !containerRef.current.contains(event.target as Node)
199
- ) {
200
- setIsOpen(false);
201
- setIsFocused(false);
202
- setSearchTerm("");
203
- fieldInternalProps?.onBlur?.();
204
- }
205
- };
206
- document.addEventListener("mousedown", handleClickOutside);
207
- return () => document.removeEventListener("mousedown", handleClickOutside);
208
- }, [fieldInternalProps]);
209
-
210
- const getDisplayValue = () => {
211
- if (selectedObject) return selectedObject[labelKey];
212
- if (options.length > 0 && currentValue) {
213
- const found = options.find((o) => getOptionValue(o) === currentValue);
214
- if (found) return found[labelKey];
215
- }
216
- if (isLoading || (currentValue && !hasAttemptedHydration))
217
- return "Loading...";
218
- return currentValue || "";
219
- };
220
-
221
- const handleSelect = (option: any) => {
222
- const value = getOptionValue(option);
223
- setSelectedObject(option);
224
- onChangeHandler(value);
225
- if (form) {
226
- if (obj) form.setValue(obj, option);
227
- if (name) form.clearErrors(name);
228
- }
229
- setIsOpen(false);
230
- setSearchTerm("");
231
- setActiveIndex(-1);
232
- fieldInternalProps?.onBlur?.();
233
- };
234
-
235
- const handleKeyDown = (e: React.KeyboardEvent) => {
236
- if (!isOpen) {
237
- if (e.key === "ArrowDown" || e.key === "Enter") setIsOpen(true);
238
- return;
239
- }
240
- switch (e.key) {
241
- case "ArrowDown":
242
- e.preventDefault();
243
- setActiveIndex((prev) => (prev < options.length - 1 ? prev + 1 : prev));
244
- break;
245
- case "ArrowUp":
246
- e.preventDefault();
247
- setActiveIndex((prev) => (prev > 0 ? prev - 1 : prev));
248
- break;
249
- case "Enter":
250
- e.preventDefault();
251
- if (activeIndex >= 0 && activeIndex < options.length)
252
- handleSelect(options[activeIndex]);
253
- break;
254
- case "Escape":
255
- setIsOpen(false);
256
- break;
257
- }
258
- };
259
-
260
- const clearSelection = (e: React.MouseEvent) => {
261
- e.stopPropagation();
262
- if (form) {
263
- if (name)
264
- form.setValue(name, undefined, {
265
- shouldValidate: true,
266
- shouldDirty: true,
267
- });
268
- if (obj)
269
- form.setValue(obj, undefined, {
270
- shouldValidate: true,
271
- shouldDirty: true,
272
- });
273
- } else {
274
- onChangeHandler(undefined);
275
- }
276
- setSearchTerm("");
277
- };
278
-
279
- const filteredOptions = options.filter(
280
- (option) => !filter.includes(getOptionValue(option)),
281
- );
282
-
283
- useEffect(() => {
284
- if (form && name && !form.watch(name)) {
285
- form.setValue(name, undefined);
286
- if (obj) form.setValue(obj, undefined);
287
- }
288
- }, [form, name, obj]);
289
-
290
- let message = error;
291
- if (form && name) {
292
- const {
293
- formState: { errors },
294
- } = form;
295
- const nameParts = name.split(".");
296
- let currentError = errors;
297
- for (const part of nameParts) {
298
- currentError = currentError?.[part];
299
- }
300
- message = currentError?.message || message;
301
- }
302
-
303
- return (
304
- <div ref={containerRef} className={`space-y-2 group ${containerClassName}`}>
305
- {label && (
306
- <label
307
- className={`block text-xs font-medium transition-colors duration-200`}
308
- style={{ color: isFocused || isOpen ? primaryColor : undefined }}
309
- >
310
- {label}
311
- {required && <span className="text-red-500 ml-1">*</span>}
312
- </label>
313
- )}
314
-
315
- <div className="relative">
316
- <div
317
- className={`flex items-center w-full h-8 rounded-lg border bg-gray-50 dark:bg-zinc-900/50 text-gray-900 dark:text-white transition-all duration-200 cursor-text ${
318
- isFocused || isOpen
319
- ? "ring-2 bg-white dark:bg-zinc-900"
320
- : "border-gray-200 dark:border-zinc-800"
321
- } ${className}`}
322
- style={{
323
- borderColor: isFocused || isOpen ? primaryColor : undefined,
324
- boxShadow:
325
- isFocused || isOpen
326
- ? `${primaryColor}33 0px 0px 0px 2px`
327
- : undefined,
328
- }}
329
- onClick={() => {
330
- if (disabled) return;
331
- inputRef.current?.focus();
332
- setIsOpen(true);
333
- }}
334
- >
335
- <div className="pl-3 text-gray-400">
336
- <Search className="h-4 w-4" />
337
- </div>
338
- <div className="flex-1 relative">
339
- {!isOpen && currentValue && (
340
- <div className="absolute inset-0 flex items-center px-3 truncate select-none">
341
- {getDisplayValue()}
342
- </div>
343
- )}
344
- <input
345
- ref={setInputRef}
346
- type="text"
347
- className={`block w-full bg-transparent border-none focus:ring-0 h-8 py-0 pr-8 placeholder-gray-400 focus:outline-none text-sm ${
348
- !isOpen && currentValue ? "opacity-0" : "opacity-100"
349
- }`}
350
- placeholder={!currentValue ? placeholder : ""}
351
- value={searchTerm}
352
- onChange={(e) => setSearchTerm(e.target.value)}
353
- onFocus={() => {
354
- setIsFocused(true);
355
- setIsOpen(true);
356
- }}
357
- onKeyDown={handleKeyDown}
358
- disabled={disabled}
359
- {...props}
360
- />
361
- </div>
362
- <div className="pr-3 flex items-center gap-1">
363
- {isLoading ? (
364
- <Loader2 className="h-4 w-4 animate-spin text-blue-500" />
365
- ) : (
366
- <>
367
- {currentValue && !disabled && (
368
- <button
369
- onClick={clearSelection}
370
- type="button"
371
- className="p-0.5 hover:bg-gray-200 dark:hover:bg-zinc-700 rounded-full text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
372
- >
373
- <X className="h-3 w-3" />
374
- </button>
375
- )}
376
- <ChevronDown
377
- className={`h-4 w-4 text-gray-400 transition-transform ${isOpen ? "rotate-180" : ""}`}
378
- />
379
- </>
380
- )}
381
- </div>
382
- </div>
383
-
384
- {isOpen && (
385
- <div className="absolute z-50 w-full mt-1 bg-white dark:bg-zinc-900 rounded-xl border border-gray-200 dark:border-zinc-800 shadow-lg max-h-60 overflow-y-auto overflow-x-hidden">
386
- {isLoading && options.length === 0 ? (
387
- <div className="p-4 text-center text-sm text-gray-500">
388
- <Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />
389
- Loading options...
390
- </div>
391
- ) : filteredOptions.length > 0 ? (
392
- <div className="py-1">
393
- {filteredOptions.map((option, index) => {
394
- const optValue = getOptionValue(option);
395
- const isSelected = currentValue === optValue;
396
- const isHighlighted = index === activeIndex;
397
- return (
398
- <button
399
- key={optValue || index}
400
- type="button"
401
- className={`w-full text-left px-3 py-2 text-sm flex items-center justify-between transition-colors ${
402
- isSelected
403
- ? "font-medium"
404
- : isHighlighted
405
- ? "bg-gray-100 dark:bg-zinc-800 text-gray-900 dark:text-gray-100"
406
- : "text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-zinc-800"
407
- }`}
408
- style={{
409
- backgroundColor: isSelected
410
- ? `${primaryColor}1A`
411
- : undefined,
412
- color: isSelected ? primaryColor : undefined,
413
- }}
414
- onClick={() => handleSelect(option)}
415
- onMouseEnter={() => setActiveIndex(index)}
416
- >
417
- <span className="truncate">{option[labelKey]}</span>
418
- {isSelected && (
419
- <Check
420
- className="h-4 w-4"
421
- style={{ color: primaryColor }}
422
- />
423
- )}
424
- </button>
425
- );
426
- })}
427
- </div>
428
- ) : (
429
- <div className="p-4 text-center text-sm text-gray-500">
430
- {errorMessage ||
431
- (searchTerm
432
- ? `No results for "${searchTerm}"`
433
- : "No options available")}
434
- </div>
435
- )}
436
- </div>
437
- )}
438
- </div>
439
- {message && (
440
- <p className="text-sm text-red-600 dark:text-red-400">{message}</p>
441
- )}
442
- </div>
443
- );
444
- };
445
-
446
- export const SearchApi = forwardRef<HTMLDivElement, SearchApiProps>(
447
- (props, _) => {
448
- const { form, name, onChange, value } = props;
449
-
450
- if (form && name) {
451
- return (
452
- <Controller
453
- name={name}
454
- control={form.control}
455
- render={({
456
- field: { onChange: formOnChange, value: formValue, ref, onBlur },
457
- }) => (
458
- <SearchApiContent
459
- {...props}
460
- currentValue={formValue}
461
- onChangeHandler={formOnChange}
462
- fieldInternalProps={{ ref, onBlur }}
463
- />
464
- )}
465
- />
466
- );
467
- }
468
-
469
- return (
470
- <SearchApiContent
471
- {...props}
472
- currentValue={value}
473
- onChangeHandler={onChange || (() => {})}
474
- />
475
- );
476
- },
477
- );
478
-
479
- SearchApi.displayName = "SearchApi";
@@ -1,131 +0,0 @@
1
- import { forwardRef, useState } from "react";
2
- import { ChevronDown } from "lucide-react";
3
- import { useWarqadConfig } from "../../providers/WarqadProvider";
4
-
5
- export interface Option {
6
- value: string | number | boolean;
7
- label: string;
8
- disabled?: boolean;
9
- }
10
-
11
- export interface SelectProps extends Omit<
12
- React.SelectHTMLAttributes<HTMLSelectElement>,
13
- "onChange"
14
- > {
15
- label: string;
16
- options: Option[];
17
- icon?: React.ReactNode;
18
- error?: string;
19
- containerClassName?: string;
20
- name?: any;
21
- form?: any;
22
- onChange?: (value: string | number | boolean) => void;
23
- }
24
-
25
- export const Select = forwardRef<HTMLSelectElement, SelectProps>(
26
- (
27
- {
28
- label,
29
- options,
30
- icon,
31
- error,
32
- containerClassName = "",
33
- name,
34
- form,
35
- onChange,
36
- onFocus,
37
- onBlur,
38
- className = "",
39
- ...props
40
- },
41
- ref,
42
- ) => {
43
- const [isFocused, setIsFocused] = useState(false);
44
- const { theme } = useWarqadConfig();
45
- const primaryColor = theme?.primaryColor;
46
-
47
- let message = error;
48
- if (form) {
49
- const {
50
- formState: { errors },
51
- } = form;
52
- const nameI = name.split(".");
53
- const names: string = nameI.length > 1 ? nameI[1] : nameI[0];
54
- message = errors?.[names ? names : name]?.message;
55
- }
56
-
57
- return (
58
- <div className={`space-y-2 group ${containerClassName}`}>
59
- <label
60
- htmlFor={props.id}
61
- className={`block text-xs font-medium transition-colors duration-200`}
62
- style={{ color: isFocused ? primaryColor : undefined }}
63
- >
64
- {label}
65
- {props.required && <span className="text-red-500 ml-1">*</span>}
66
- </label>
67
- <div className="relative">
68
- {icon && (
69
- <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
70
- <div
71
- className={`transition-colors duration-200`}
72
- style={{ color: isFocused ? primaryColor : "#9ca3af" }}
73
- >
74
- {icon}
75
- </div>
76
- </div>
77
- )}
78
- <select
79
- ref={ref}
80
- onFocus={(e) => {
81
- setIsFocused(true);
82
- onFocus?.(e);
83
- }}
84
- {...(form ? form.register(name as any) : {})}
85
- onBlur={(e) => {
86
- setIsFocused(false);
87
- onBlur?.(e);
88
- }}
89
- onChange={(e) => {
90
- onChange?.(e.target.value);
91
- if (form) {
92
- form.setValue(name, e.target.value);
93
- }
94
- }}
95
- className={`block w-full ${
96
- icon ? "pl-10" : "pl-3"
97
- } pr-10 h-8 py-0 rounded-lg border-gray-200 dark:border-zinc-800 bg-gray-50 dark:bg-zinc-900/50 text-gray-900 dark:text-white placeholder-gray-400 focus:bg-white dark:focus:bg-zinc-900 outline-none transition-all duration-200 border appearance-none text-sm ${className}`}
98
- style={{
99
- borderColor: isFocused ? primaryColor : undefined,
100
- boxShadow: isFocused
101
- ? `${primaryColor}33 0px 0px 0px 2px`
102
- : undefined,
103
- }}
104
- {...props}
105
- >
106
- <option value="" disabled>
107
- Select {label}
108
- </option>
109
- {options.map((option) => (
110
- <option
111
- key={String(option.value)}
112
- value={String(option.value)}
113
- disabled={option.disabled}
114
- >
115
- {option.label}
116
- </option>
117
- ))}
118
- </select>
119
- <div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
120
- <ChevronDown className="h-4 w-4 text-gray-400" />
121
- </div>
122
- </div>
123
- {message && (
124
- <p className="text-sm text-red-600 dark:text-red-400">{message}</p>
125
- )}
126
- </div>
127
- );
128
- },
129
- );
130
-
131
- Select.displayName = "Select";