@ews-admin/global-design-system 1.0.0 → 1.1.1

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 (68) hide show
  1. package/README.md +50 -1
  2. package/dist/components/Button/Button.d.ts.map +1 -1
  3. package/dist/components/Input/Input.d.ts +4 -0
  4. package/dist/components/Input/Input.d.ts.map +1 -1
  5. package/dist/components/Logo/Logo.d.ts +29 -0
  6. package/dist/components/Logo/Logo.d.ts.map +1 -0
  7. package/dist/components/Logo/index.d.ts +3 -0
  8. package/dist/components/Logo/index.d.ts.map +1 -0
  9. package/dist/components/Modal/Modal.d.ts +72 -0
  10. package/dist/components/Modal/Modal.d.ts.map +1 -0
  11. package/dist/components/Modal/index.d.ts +3 -0
  12. package/dist/components/Modal/index.d.ts.map +1 -0
  13. package/dist/components/MultiSearchAutocomplete/MultiSearchAutocomplete.d.ts +25 -0
  14. package/dist/components/MultiSearchAutocomplete/MultiSearchAutocomplete.d.ts.map +1 -0
  15. package/dist/components/MultiSearchAutocomplete/index.d.ts +2 -0
  16. package/dist/components/MultiSearchAutocomplete/index.d.ts.map +1 -0
  17. package/dist/components/SearchAutocomplete/SearchAutocomplete.d.ts +22 -0
  18. package/dist/components/SearchAutocomplete/SearchAutocomplete.d.ts.map +1 -0
  19. package/dist/components/SearchAutocomplete/index.d.ts +3 -0
  20. package/dist/components/SearchAutocomplete/index.d.ts.map +1 -0
  21. package/dist/hooks/index.d.ts +2 -0
  22. package/dist/hooks/index.d.ts.map +1 -0
  23. package/dist/hooks/useDebounce.d.ts +15 -0
  24. package/dist/hooks/useDebounce.d.ts.map +1 -0
  25. package/dist/icons/Icon.d.ts +5 -4
  26. package/dist/icons/Icon.d.ts.map +1 -1
  27. package/dist/icons/index.d.ts +1 -3
  28. package/dist/icons/index.d.ts.map +1 -1
  29. package/dist/index.css +3 -1
  30. package/dist/index.d.ts +185 -12
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.esm.css +3 -1
  33. package/dist/index.esm.js +763 -29
  34. package/dist/index.esm.js.map +1 -1
  35. package/dist/index.js +768 -27
  36. package/dist/index.js.map +1 -1
  37. package/dist/molecules/SpecialtySearchAutocomplete/SpecialtySearchAutocomplete.d.ts +18 -0
  38. package/dist/molecules/SpecialtySearchAutocomplete/SpecialtySearchAutocomplete.d.ts.map +1 -0
  39. package/dist/molecules/SpecialtySearchAutocomplete/index.d.ts +3 -0
  40. package/dist/molecules/SpecialtySearchAutocomplete/index.d.ts.map +1 -0
  41. package/dist/molecules/index.d.ts +3 -0
  42. package/dist/molecules/index.d.ts.map +1 -0
  43. package/dist/utils/index.d.ts +5 -1
  44. package/dist/utils/index.d.ts.map +1 -1
  45. package/package.json +17 -2
  46. package/src/assets/favicon.svg +6 -0
  47. package/src/assets/logo.svg +17 -0
  48. package/src/components/Button/Button.tsx +22 -8
  49. package/src/components/Input/Input.tsx +42 -16
  50. package/src/components/Logo/Logo.tsx +100 -0
  51. package/src/components/Logo/index.ts +2 -0
  52. package/src/components/Modal/Modal.tsx +257 -0
  53. package/src/components/Modal/index.ts +2 -0
  54. package/src/components/MultiSearchAutocomplete/MultiSearchAutocomplete.tsx +319 -0
  55. package/src/components/MultiSearchAutocomplete/index.ts +1 -0
  56. package/src/components/SearchAutocomplete/SearchAutocomplete.tsx +315 -0
  57. package/src/components/SearchAutocomplete/index.ts +2 -0
  58. package/src/hooks/index.ts +1 -0
  59. package/src/hooks/useDebounce.ts +64 -0
  60. package/src/icons/Icon.tsx +15 -16
  61. package/src/icons/index.ts +39 -3
  62. package/src/index.ts +19 -0
  63. package/src/molecules/SpecialtySearchAutocomplete/SpecialtySearchAutocomplete.tsx +203 -0
  64. package/src/molecules/SpecialtySearchAutocomplete/index.ts +5 -0
  65. package/src/molecules/index.ts +5 -0
  66. package/src/styles/index.css +8 -5
  67. package/src/styles/tailwind.css +3 -0
  68. package/src/utils/index.ts +7 -2
package/dist/index.js CHANGED
@@ -5,6 +5,10 @@ var React = require('react');
5
5
 
6
6
  function r(e){var t,f,n="";if("string"==typeof e||"number"==typeof e)n+=e;else if("object"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=" "),n+=f);}else for(f in e)e[f]&&(n&&(n+=" "),n+=f);return n}function clsx(){for(var e,t,f=0,n="",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=" "),n+=t);return n}
7
7
 
8
+ /**
9
+ * Default currency for price formatting
10
+ */
11
+ const CURRENCY = "XOF";
8
12
  /**
9
13
  * Utility function to merge class names
10
14
  * @param inputs - Class values to merge
@@ -16,10 +20,10 @@ function cn(...inputs) {
16
20
  /**
17
21
  * Utility function to format currency
18
22
  * @param amount - Amount to format
19
- * @param currency - Currency code (default: 'USD')
23
+ * @param currency - Currency code (default: CURRENCY constant)
20
24
  * @returns Formatted currency string
21
25
  */
22
- function formatCurrency(amount, currency = "USD") {
26
+ function formatCurrency(amount, currency = CURRENCY) {
23
27
  return new Intl.NumberFormat("en-US", {
24
28
  style: "currency",
25
29
  currency,
@@ -65,31 +69,330 @@ function generateId(prefix = "ews") {
65
69
  const Button = React.forwardRef(({ className, variant = "primary", size = "md", loading = false, fullWidth = false, leftIcon, rightIcon, children, disabled, ...props }, ref) => {
66
70
  const baseStyles = "inline-flex items-center justify-center font-medium rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none";
67
71
  const variants = {
68
- primary: "bg-[var(--ews-primary)] text-white hover:bg-[var(--ews-primary-hover)] focus:ring-[var(--ews-primary)]",
69
- secondary: "bg-[var(--ews-gray-200)] text-[var(--ews-gray-900)] hover:bg-[var(--ews-gray-300)] focus:ring-[var(--ews-gray-500)]",
70
- success: "bg-[var(--ews-success)] text-white hover:bg-[var(--ews-success-hover)] focus:ring-[var(--ews-success)]",
71
- warning: "bg-[var(--ews-warning)] text-white hover:bg-[var(--ews-warning-hover)] focus:ring-[var(--ews-warning)]",
72
- error: "bg-[var(--ews-error)] text-white hover:bg-[var(--ews-error-hover)] focus:ring-[var(--ews-error)]",
73
- ghost: "bg-transparent text-[var(--ews-gray-700)] hover:bg-[var(--ews-gray-100)] focus:ring-[var(--ews-gray-500)]",
72
+ primary: "bg-ews-primary text-white hover:bg-ews-primary-hover focus:ring-ews-primary",
73
+ secondary: "bg-ews-secondary text-white hover:bg-ews-secondary-hover focus:ring-ews-secondary",
74
+ success: "bg-ews-success text-white hover:bg-ews-success-hover focus:ring-ews-success",
75
+ warning: "bg-ews-warning text-white hover:bg-ews-warning-hover focus:ring-ews-warning",
76
+ error: "bg-ews-error text-white hover:bg-ews-error-hover focus:ring-ews-error",
77
+ ghost: "bg-transparent text-ews-gray-700 hover:bg-ews-gray-100 focus:ring-ews-gray-500",
74
78
  };
75
79
  const sizes = {
76
80
  sm: "px-3 py-1.5 text-sm",
77
81
  md: "px-4 py-2 text-base",
78
82
  lg: "px-6 py-3 text-lg",
79
83
  };
80
- return (jsxRuntime.jsxs("button", { className: cn(baseStyles, variants[variant], sizes[size], fullWidth && "w-full", className), ref: ref, disabled: disabled || loading, ...props, children: [loading && (jsxRuntime.jsxs("svg", { className: "animate-spin -ml-1 mr-2 h-4 w-4", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [jsxRuntime.jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), jsxRuntime.jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] })), !loading && leftIcon && jsxRuntime.jsx("span", { className: "mr-2", children: leftIcon }), children, !loading && rightIcon && jsxRuntime.jsx("span", { className: "ml-2", children: rightIcon })] }));
84
+ const iconSizes = {
85
+ sm: "h-4 w-4",
86
+ md: "h-5 w-5",
87
+ lg: "h-6 w-6",
88
+ };
89
+ return (jsxRuntime.jsxs("button", { className: cn(baseStyles, variants[variant], sizes[size], fullWidth && "w-full", className), ref: ref, disabled: disabled || loading, ...props, children: [loading && (jsxRuntime.jsxs("svg", { className: "animate-spin -ml-1 mr-2 h-4 w-4", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [jsxRuntime.jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), jsxRuntime.jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] })), !loading && leftIcon && (jsxRuntime.jsx("span", { className: cn("mr-2 flex items-center", iconSizes[size]), children: leftIcon })), children, !loading && rightIcon && (jsxRuntime.jsx("span", { className: cn("ml-2 flex items-center", iconSizes[size]), children: rightIcon }))] }));
81
90
  });
82
91
  Button.displayName = "Button";
83
92
 
84
- const Input = React.forwardRef(({ className, variant = "default", size = "md", label, helperText, error, leftIcon, rightIcon, fullWidth = false, id, ...props }, ref) => {
93
+ /**
94
+ * @license lucide-react v0.544.0 - ISC
95
+ *
96
+ * This source code is licensed under the ISC license.
97
+ * See the LICENSE file in the root directory of this source tree.
98
+ */
99
+
100
+ const toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
101
+ const toCamelCase = (string) => string.replace(
102
+ /^([A-Z])|[\s-_]+(\w)/g,
103
+ (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase()
104
+ );
105
+ const toPascalCase = (string) => {
106
+ const camelCase = toCamelCase(string);
107
+ return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
108
+ };
109
+ const mergeClasses = (...classes) => classes.filter((className, index, array) => {
110
+ return Boolean(className) && className.trim() !== "" && array.indexOf(className) === index;
111
+ }).join(" ").trim();
112
+ const hasA11yProp = (props) => {
113
+ for (const prop in props) {
114
+ if (prop.startsWith("aria-") || prop === "role" || prop === "title") {
115
+ return true;
116
+ }
117
+ }
118
+ };
119
+
120
+ /**
121
+ * @license lucide-react v0.544.0 - ISC
122
+ *
123
+ * This source code is licensed under the ISC license.
124
+ * See the LICENSE file in the root directory of this source tree.
125
+ */
126
+
127
+ var defaultAttributes = {
128
+ xmlns: "http://www.w3.org/2000/svg",
129
+ width: 24,
130
+ height: 24,
131
+ viewBox: "0 0 24 24",
132
+ fill: "none",
133
+ stroke: "currentColor",
134
+ strokeWidth: 2,
135
+ strokeLinecap: "round",
136
+ strokeLinejoin: "round"
137
+ };
138
+
139
+ /**
140
+ * @license lucide-react v0.544.0 - ISC
141
+ *
142
+ * This source code is licensed under the ISC license.
143
+ * See the LICENSE file in the root directory of this source tree.
144
+ */
145
+
146
+
147
+ const Icon$1 = React.forwardRef(
148
+ ({
149
+ color = "currentColor",
150
+ size = 24,
151
+ strokeWidth = 2,
152
+ absoluteStrokeWidth,
153
+ className = "",
154
+ children,
155
+ iconNode,
156
+ ...rest
157
+ }, ref) => React.createElement(
158
+ "svg",
159
+ {
160
+ ref,
161
+ ...defaultAttributes,
162
+ width: size,
163
+ height: size,
164
+ stroke: color,
165
+ strokeWidth: absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth,
166
+ className: mergeClasses("lucide", className),
167
+ ...!children && !hasA11yProp(rest) && { "aria-hidden": "true" },
168
+ ...rest
169
+ },
170
+ [
171
+ ...iconNode.map(([tag, attrs]) => React.createElement(tag, attrs)),
172
+ ...Array.isArray(children) ? children : [children]
173
+ ]
174
+ )
175
+ );
176
+
177
+ /**
178
+ * @license lucide-react v0.544.0 - ISC
179
+ *
180
+ * This source code is licensed under the ISC license.
181
+ * See the LICENSE file in the root directory of this source tree.
182
+ */
183
+
184
+
185
+ const createLucideIcon = (iconName, iconNode) => {
186
+ const Component = React.forwardRef(
187
+ ({ className, ...props }, ref) => React.createElement(Icon$1, {
188
+ ref,
189
+ iconNode,
190
+ className: mergeClasses(
191
+ `lucide-${toKebabCase(toPascalCase(iconName))}`,
192
+ `lucide-${iconName}`,
193
+ className
194
+ ),
195
+ ...props
196
+ })
197
+ );
198
+ Component.displayName = toPascalCase(iconName);
199
+ return Component;
200
+ };
201
+
202
+ /**
203
+ * @license lucide-react v0.544.0 - ISC
204
+ *
205
+ * This source code is licensed under the ISC license.
206
+ * See the LICENSE file in the root directory of this source tree.
207
+ */
208
+
209
+
210
+ const __iconNode$9 = [
211
+ ["path", { d: "M5 12h14", key: "1ays0h" }],
212
+ ["path", { d: "m12 5 7 7-7 7", key: "xquz4c" }]
213
+ ];
214
+ const ArrowRight = createLucideIcon("arrow-right", __iconNode$9);
215
+
216
+ /**
217
+ * @license lucide-react v0.544.0 - ISC
218
+ *
219
+ * This source code is licensed under the ISC license.
220
+ * See the LICENSE file in the root directory of this source tree.
221
+ */
222
+
223
+
224
+ const __iconNode$8 = [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]];
225
+ const Check = createLucideIcon("check", __iconNode$8);
226
+
227
+ /**
228
+ * @license lucide-react v0.544.0 - ISC
229
+ *
230
+ * This source code is licensed under the ISC license.
231
+ * See the LICENSE file in the root directory of this source tree.
232
+ */
233
+
234
+
235
+ const __iconNode$7 = [
236
+ ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
237
+ ["line", { x1: "12", x2: "12", y1: "8", y2: "12", key: "1pkeuh" }],
238
+ ["line", { x1: "12", x2: "12.01", y1: "16", y2: "16", key: "4dfq90" }]
239
+ ];
240
+ const CircleAlert = createLucideIcon("circle-alert", __iconNode$7);
241
+
242
+ /**
243
+ * @license lucide-react v0.544.0 - ISC
244
+ *
245
+ * This source code is licensed under the ISC license.
246
+ * See the LICENSE file in the root directory of this source tree.
247
+ */
248
+
249
+
250
+ const __iconNode$6 = [
251
+ ["path", { d: "M21.801 10A10 10 0 1 1 17 3.335", key: "yps3ct" }],
252
+ ["path", { d: "m9 11 3 3L22 4", key: "1pflzl" }]
253
+ ];
254
+ const CircleCheckBig = createLucideIcon("circle-check-big", __iconNode$6);
255
+
256
+ /**
257
+ * @license lucide-react v0.544.0 - ISC
258
+ *
259
+ * This source code is licensed under the ISC license.
260
+ * See the LICENSE file in the root directory of this source tree.
261
+ */
262
+
263
+
264
+ const __iconNode$5 = [
265
+ [
266
+ "path",
267
+ {
268
+ d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49",
269
+ key: "ct8e1f"
270
+ }
271
+ ],
272
+ ["path", { d: "M14.084 14.158a3 3 0 0 1-4.242-4.242", key: "151rxh" }],
273
+ [
274
+ "path",
275
+ {
276
+ d: "M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143",
277
+ key: "13bj9a"
278
+ }
279
+ ],
280
+ ["path", { d: "m2 2 20 20", key: "1ooewy" }]
281
+ ];
282
+ const EyeOff = createLucideIcon("eye-off", __iconNode$5);
283
+
284
+ /**
285
+ * @license lucide-react v0.544.0 - ISC
286
+ *
287
+ * This source code is licensed under the ISC license.
288
+ * See the LICENSE file in the root directory of this source tree.
289
+ */
290
+
291
+
292
+ const __iconNode$4 = [
293
+ [
294
+ "path",
295
+ {
296
+ d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0",
297
+ key: "1nclc0"
298
+ }
299
+ ],
300
+ ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]
301
+ ];
302
+ const Eye = createLucideIcon("eye", __iconNode$4);
303
+
304
+ /**
305
+ * @license lucide-react v0.544.0 - ISC
306
+ *
307
+ * This source code is licensed under the ISC license.
308
+ * See the LICENSE file in the root directory of this source tree.
309
+ */
310
+
311
+
312
+ const __iconNode$3 = [
313
+ ["path", { d: "m21 21-4.34-4.34", key: "14j7rj" }],
314
+ ["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }]
315
+ ];
316
+ const Search = createLucideIcon("search", __iconNode$3);
317
+
318
+ /**
319
+ * @license lucide-react v0.544.0 - ISC
320
+ *
321
+ * This source code is licensed under the ISC license.
322
+ * See the LICENSE file in the root directory of this source tree.
323
+ */
324
+
325
+
326
+ const __iconNode$2 = [
327
+ ["path", { d: "M11 2v2", key: "1539x4" }],
328
+ ["path", { d: "M5 2v2", key: "1yf1q8" }],
329
+ ["path", { d: "M5 3H4a2 2 0 0 0-2 2v4a6 6 0 0 0 12 0V5a2 2 0 0 0-2-2h-1", key: "rb5t3r" }],
330
+ ["path", { d: "M8 15a6 6 0 0 0 12 0v-3", key: "x18d4x" }],
331
+ ["circle", { cx: "20", cy: "10", r: "2", key: "ts1r5v" }]
332
+ ];
333
+ const Stethoscope = createLucideIcon("stethoscope", __iconNode$2);
334
+
335
+ /**
336
+ * @license lucide-react v0.544.0 - ISC
337
+ *
338
+ * This source code is licensed under the ISC license.
339
+ * See the LICENSE file in the root directory of this source tree.
340
+ */
341
+
342
+
343
+ const __iconNode$1 = [
344
+ [
345
+ "path",
346
+ {
347
+ d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",
348
+ key: "wmoenq"
349
+ }
350
+ ],
351
+ ["path", { d: "M12 9v4", key: "juzpu7" }],
352
+ ["path", { d: "M12 17h.01", key: "p32p05" }]
353
+ ];
354
+ const TriangleAlert = createLucideIcon("triangle-alert", __iconNode$1);
355
+
356
+ /**
357
+ * @license lucide-react v0.544.0 - ISC
358
+ *
359
+ * This source code is licensed under the ISC license.
360
+ * See the LICENSE file in the root directory of this source tree.
361
+ */
362
+
363
+
364
+ const __iconNode = [
365
+ ["path", { d: "M18 6 6 18", key: "1bl5f8" }],
366
+ ["path", { d: "m6 6 12 12", key: "d8bk6v" }]
367
+ ];
368
+ const X = createLucideIcon("x", __iconNode);
369
+
370
+ const Icon = React.forwardRef(({ size = "md", icon: IconComponent, className, ...props }, ref) => {
371
+ const sizes = {
372
+ sm: 16,
373
+ md: 20,
374
+ lg: 24,
375
+ xl: 32,
376
+ };
377
+ const iconSize = typeof size === "number" ? size : sizes[size];
378
+ return (jsxRuntime.jsx(IconComponent, { ref: ref, size: iconSize, className: className, ...props }));
379
+ });
380
+ Icon.displayName = "Icon";
381
+
382
+ const Input = React.forwardRef(({ className, variant = "default", size = "md", label, helperText, error, leftIcon, rightIcon, fullWidth = false, showPasswordToggle = false, id, type = "text", ...props }, ref) => {
85
383
  const inputId = id || `input-${Math.random().toString(36).substr(2, 9)}`;
86
384
  const hasError = Boolean(error);
87
385
  const actualVariant = hasError ? "error" : variant;
88
- const baseStyles = "block w-full rounded-md border transition-colors focus:outline-none focus:ring-2 focus:ring-offset-0";
386
+ // Password visibility state
387
+ const [showPassword, setShowPassword] = React.useState(false);
388
+ const isPasswordInput = type === "password";
389
+ const shouldShowPasswordToggle = showPasswordToggle && isPasswordInput;
390
+ const actualType = isPasswordInput && showPassword ? "text" : type;
391
+ const baseStyles = "block w-full rounded-md border transition-colors focus:outline-none focus:ring-2 focus:ring-offset-0 hover:border-ews-primary";
89
392
  const variants = {
90
- default: "border-[var(--ews-gray-300)] focus:border-[var(--ews-primary)] focus:ring-[var(--ews-primary)]",
91
- error: "border-[var(--ews-error)] focus:border-[var(--ews-error)] focus:ring-[var(--ews-error)]",
92
- success: "border-[var(--ews-success)] focus:border-[var(--ews-success)] focus:ring-[var(--ews-success)]",
393
+ default: "border-ews-gray-300 focus:border-ews-primary focus:ring-ews-primary",
394
+ error: "border-ews-error focus:border-ews-error focus:ring-ews-error",
395
+ success: "border-ews-success focus:border-ews-success focus:ring-ews-success",
93
396
  };
94
397
  const sizes = {
95
398
  sm: "px-3 py-1.5 text-sm",
@@ -101,36 +404,474 @@ const Input = React.forwardRef(({ className, variant = "default", size = "md", l
101
404
  md: "h-5 w-5",
102
405
  lg: "h-6 w-6",
103
406
  };
104
- return (jsxRuntime.jsxs("div", { className: cn("space-y-1", fullWidth ? "w-full" : "w-auto"), children: [label && (jsxRuntime.jsx("label", { htmlFor: inputId, className: "block text-sm font-medium text-[var(--ews-gray-700)]", children: label })), jsxRuntime.jsxs("div", { className: "relative", children: [leftIcon && (jsxRuntime.jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none", children: jsxRuntime.jsx("span", { className: cn("text-[var(--ews-gray-400)]", iconSizes[size]), children: leftIcon }) })), jsxRuntime.jsx("input", { id: inputId, className: cn(baseStyles, variants[actualVariant], sizes[size], leftIcon && "pl-10", rightIcon && "pr-10", className), ref: ref, ...props }), rightIcon && (jsxRuntime.jsx("div", { className: "absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none", children: jsxRuntime.jsx("span", { className: cn("text-[var(--ews-gray-400)]", iconSizes[size]), children: rightIcon }) }))] }), (error || helperText) && (jsxRuntime.jsx("p", { className: cn("text-sm", error ? "text-[var(--ews-error)]" : "text-[var(--ews-gray-500)]"), children: error || helperText }))] }));
407
+ return (jsxRuntime.jsxs("div", { className: cn("space-y-1", fullWidth ? "w-full" : "w-auto"), children: [label && (jsxRuntime.jsx("label", { htmlFor: inputId, className: "block text-sm font-medium text-ews-gray-700", children: label })), jsxRuntime.jsxs("div", { className: "relative", children: [leftIcon && (jsxRuntime.jsx("div", { className: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none", children: jsxRuntime.jsx("span", { className: cn("text-ews-gray-400", iconSizes[size]), children: leftIcon }) })), jsxRuntime.jsx("input", { id: inputId, type: actualType, className: cn(baseStyles, variants[actualVariant], sizes[size], leftIcon && "pl-10", (rightIcon || shouldShowPasswordToggle) && "pr-10", className), ref: ref, ...props }), rightIcon && !shouldShowPasswordToggle && (jsxRuntime.jsx("div", { className: "absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none", children: jsxRuntime.jsx("span", { className: cn("text-ews-gray-400", iconSizes[size]), children: rightIcon }) })), shouldShowPasswordToggle && (jsxRuntime.jsx("button", { type: "button", className: "absolute inset-y-0 right-0 pr-3 flex items-center", onClick: () => setShowPassword(!showPassword), tabIndex: -1, children: jsxRuntime.jsx("span", { className: cn("text-ews-gray-400 hover:text-ews-gray-600 transition-colors", iconSizes[size]), children: showPassword ? jsxRuntime.jsx(EyeOff, { size: 16 }) : jsxRuntime.jsx(Eye, { size: 16 }) }) }))] }), (error || helperText) && (jsxRuntime.jsx("p", { className: cn("text-sm", error ? "text-ews-error" : "text-ews-gray-500"), children: error || helperText }))] }));
105
408
  });
106
409
  Input.displayName = "Input";
107
410
 
108
- const Icon = React.forwardRef(({ size = "md", color = "currentColor", className, ...props }, ref) => {
109
- const sizes = {
110
- sm: "h-4 w-4",
111
- md: "h-5 w-5",
112
- lg: "h-6 w-6",
113
- xl: "h-8 w-8",
411
+ const DEFAULT_DELAY = 500;
412
+ /**
413
+ * A custom hook that debounces a value
414
+ * @param value - The value to debounce
415
+ * @param delay - The delay in milliseconds (optional, defaults to 300ms)
416
+ * @returns The debounced value
417
+ */
418
+ function useDebounce(value, delay) {
419
+ const [debouncedValue, setDebouncedValue] = React.useState(value);
420
+ const actualDelay = delay ?? DEFAULT_DELAY;
421
+ React.useEffect(() => {
422
+ const handler = setTimeout(() => {
423
+ setDebouncedValue(value);
424
+ }, actualDelay);
425
+ return () => {
426
+ clearTimeout(handler);
427
+ };
428
+ }, [value, actualDelay]);
429
+ return debouncedValue;
430
+ }
431
+ /**
432
+ * A custom hook that provides a debounced callback function
433
+ * @param callback - The function to debounce
434
+ * @param delay - The delay in milliseconds (optional, defaults to 300ms)
435
+ * @returns A debounced version of the callback
436
+ */
437
+ function useDebouncedCallback(callback, delay) {
438
+ const [debounceTimer, setDebounceTimer] = React.useState(null);
439
+ const debouncedCallback = ((...args) => {
440
+ if (debounceTimer) {
441
+ clearTimeout(debounceTimer);
442
+ }
443
+ const actualDelay = delay ?? DEFAULT_DELAY;
444
+ const newTimer = setTimeout(() => {
445
+ callback(...args);
446
+ }, actualDelay);
447
+ setDebounceTimer(newTimer);
448
+ });
449
+ React.useEffect(() => {
450
+ return () => {
451
+ if (debounceTimer) {
452
+ clearTimeout(debounceTimer);
453
+ }
454
+ };
455
+ }, [debounceTimer]);
456
+ return debouncedCallback;
457
+ }
458
+
459
+ function SearchAutocomplete({ onSelect, selectedId, searchFunction, getEntityById, getDisplayValue, getSecondaryText, placeholder, icon = Search, disabled = false, minSearchLength = 2, debounceTime = 300, error, }) {
460
+ const [searchTerm, setSearchTerm] = React.useState("");
461
+ const [entities, setEntities] = React.useState([]);
462
+ const [isLoading, setIsLoading] = React.useState(false);
463
+ const [showDropdown, setShowDropdown] = React.useState(false);
464
+ const [selectedEntity, setSelectedEntity] = React.useState(null);
465
+ const [initialLoadDone, setInitialLoadDone] = React.useState(false);
466
+ const [searchError, setSearchError] = React.useState(null);
467
+ const dropdownRef = React.useRef(null);
468
+ const debouncedSearchTerm = useDebounce(searchTerm, debounceTime);
469
+ // Track if this is an initial load or a user-initiated search
470
+ const isUserSearch = React.useRef(false);
471
+ // Only fetch the selected entity by ID once on initial load
472
+ React.useEffect(() => {
473
+ const fetchSelectedEntity = async () => {
474
+ // Only fetch if we have a selectedId, getEntityById function, and haven't loaded yet
475
+ if (!selectedId || !getEntityById || initialLoadDone)
476
+ return;
477
+ setIsLoading(true);
478
+ try {
479
+ const entity = await getEntityById(selectedId);
480
+ if (entity) {
481
+ setSelectedEntity(entity);
482
+ setSearchTerm(getDisplayValue(entity));
483
+ }
484
+ }
485
+ catch (error) {
486
+ console.error("Error fetching selected entity:", error);
487
+ }
488
+ finally {
489
+ setIsLoading(false);
490
+ setInitialLoadDone(true);
491
+ }
492
+ };
493
+ fetchSelectedEntity();
494
+ }, [selectedId, getEntityById, getDisplayValue, initialLoadDone]);
495
+ // Handle search term changes - only for user-initiated searches
496
+ React.useEffect(() => {
497
+ // Skip if this is just setting the initial value
498
+ if (!isUserSearch.current)
499
+ return;
500
+ // Clear entities if search term is too short
501
+ if (searchTerm.trim().length < minSearchLength) {
502
+ setEntities([]);
503
+ setSearchError(null);
504
+ }
505
+ }, [searchTerm, minSearchLength]);
506
+ // Fetch entities when debounced search term changes - only for user searches
507
+ React.useEffect(() => {
508
+ const fetchEntities = async () => {
509
+ if (!debouncedSearchTerm ||
510
+ debouncedSearchTerm.length < minSearchLength) {
511
+ return;
512
+ }
513
+ setIsLoading(true);
514
+ setSearchError(null);
515
+ try {
516
+ const results = await searchFunction(debouncedSearchTerm);
517
+ setEntities(results);
518
+ }
519
+ catch (error) {
520
+ console.error("Error fetching search results:", error);
521
+ setSearchError(error instanceof Error ? error.message : "Search failed");
522
+ }
523
+ finally {
524
+ setIsLoading(false);
525
+ }
526
+ };
527
+ // Only fetch if this is a user-initiated search
528
+ if (isUserSearch.current) {
529
+ fetchEntities();
530
+ }
531
+ }, [debouncedSearchTerm, searchFunction, minSearchLength]);
532
+ // Handle click outside to close dropdown
533
+ React.useEffect(() => {
534
+ const handleClickOutside = (event) => {
535
+ if (dropdownRef.current &&
536
+ !dropdownRef.current.contains(event.target)) {
537
+ setShowDropdown(false);
538
+ }
539
+ };
540
+ document.addEventListener("mousedown", handleClickOutside);
541
+ return () => {
542
+ document.removeEventListener("mousedown", handleClickOutside);
543
+ };
544
+ }, []);
545
+ const handleSearchChange = (e) => {
546
+ const value = e.target.value;
547
+ // Mark this as a user-initiated search
548
+ isUserSearch.current = true;
549
+ setSearchTerm(value);
550
+ if (selectedEntity) {
551
+ setSelectedEntity(null);
552
+ onSelect("");
553
+ }
554
+ setShowDropdown(value.trim().length >= minSearchLength);
114
555
  };
115
- return (jsxRuntime.jsx("svg", { ref: ref, className: `${sizes[size]} ${className || ""}`, fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props }));
116
- });
117
- Icon.displayName = "Icon";
556
+ const handleEntitySelect = (entity) => {
557
+ // This is a user selection, not an API fetch
558
+ isUserSearch.current = false;
559
+ console.log(`Selected entity with ID: ${entity.id}`);
560
+ setSelectedEntity(entity);
561
+ setSearchTerm(getDisplayValue(entity));
562
+ onSelect(entity.id);
563
+ setShowDropdown(false);
564
+ };
565
+ const handleClearSelection = () => {
566
+ // This is a user action, but not a search
567
+ isUserSearch.current = false;
568
+ setSelectedEntity(null);
569
+ setSearchTerm("");
570
+ onSelect("");
571
+ };
572
+ return (jsxRuntime.jsxs("div", { className: "relative", ref: dropdownRef, children: [jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx(Input, { type: "text", placeholder: `${placeholder} (min. ${minSearchLength} characters)`, value: selectedEntity ? "" : searchTerm, onChange: handleSearchChange, onFocus: () => searchTerm.length >= minSearchLength &&
573
+ isUserSearch.current &&
574
+ setShowDropdown(true), disabled: disabled, autoComplete: "off", name: "search-autocomplete", leftIcon: React.createElement(icon, { size: 16 }) }), selectedEntity && (jsxRuntime.jsx("button", { type: "button", className: "flex absolute inset-y-0 right-0 items-center pr-3", onClick: handleClearSelection, disabled: disabled, children: jsxRuntime.jsx(X, { className: "w-5 h-5 text-gray-400 hover:text-gray-500" }) }))] }), selectedEntity && (jsxRuntime.jsx("div", { className: "p-4 mt-3 rounded-lg border bg-neutral-50 border-neutral-200", children: jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [jsxRuntime.jsxs("div", { className: "flex items-center", children: [selectedEntity.photoUrl ? (jsxRuntime.jsx("img", { src: selectedEntity.photoUrl, alt: getDisplayValue(selectedEntity), className: "object-cover mr-3 w-10 h-10 rounded-full" })) : (React.createElement(icon, {
575
+ className: "w-10 h-10 text-neutral-400 mr-3",
576
+ })), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "font-medium text-neutral-900", children: getDisplayValue(selectedEntity) }), getSecondaryText && getSecondaryText(selectedEntity) && (jsxRuntime.jsx("div", { className: "text-sm text-neutral-500", children: getSecondaryText(selectedEntity) }))] })] }), jsxRuntime.jsx("button", { type: "button", onClick: handleClearSelection, className: "transition-colors text-neutral-400 hover:text-neutral-600", disabled: disabled, children: jsxRuntime.jsx(X, { className: "w-4 h-4" }) })] }) })), showDropdown && entities?.length > 0 && (jsxRuntime.jsx("div", { className: "overflow-auto absolute z-10 mt-1 w-full max-h-60 bg-white rounded-md shadow-lg", children: jsxRuntime.jsx("ul", { className: "py-1", children: entities.map((entity) => (jsxRuntime.jsx("li", { className: "px-4 py-2 cursor-pointer hover:bg-gray-100", onClick: () => handleEntitySelect(entity), children: jsxRuntime.jsxs("div", { className: "flex items-center", children: [React.createElement(icon, {
577
+ className: "w-4 h-4 text-gray-400",
578
+ }), jsxRuntime.jsxs("div", { className: "ml-2", children: [jsxRuntime.jsx("div", { className: "font-medium", children: getDisplayValue(entity) }), getSecondaryText && getSecondaryText(entity) && (jsxRuntime.jsx("div", { className: "text-xs text-gray-500", children: getSecondaryText(entity) }))] })] }) }, entity.id))) }) })), showDropdown && searchError && (jsxRuntime.jsx("div", { className: "absolute z-10 p-4 mt-1 w-full text-center text-red-600 bg-white rounded-md shadow-lg border border-red-200", children: jsxRuntime.jsxs("div", { className: "flex items-center justify-center", children: [jsxRuntime.jsx(X, { className: "w-4 h-4 mr-2" }), "Error: ", searchError] }) })), showDropdown && error && !searchError && (jsxRuntime.jsx("div", { className: "absolute z-10 p-4 mt-1 w-full text-center text-red-600 bg-white rounded-md shadow-lg border border-red-200", children: jsxRuntime.jsxs("div", { className: "flex items-center justify-center", children: [jsxRuntime.jsx(X, { className: "w-4 h-4 mr-2" }), error] }) })), showDropdown &&
579
+ debouncedSearchTerm &&
580
+ entities?.length === 0 &&
581
+ !isLoading &&
582
+ !searchError &&
583
+ !error && (jsxRuntime.jsx("div", { className: "absolute z-10 p-4 mt-1 w-full text-center text-gray-500 bg-white rounded-md shadow-lg", children: debouncedSearchTerm.length < minSearchLength
584
+ ? `Type at least ${minSearchLength} characters to search`
585
+ : "No results found. Try a different search term." })), isLoading && showDropdown && (jsxRuntime.jsx("div", { className: "absolute z-10 p-4 mt-1 w-full text-center text-gray-500 bg-white rounded-md shadow-lg", children: jsxRuntime.jsxs("div", { className: "flex items-center justify-center", children: [jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-4 w-4 border-b-2 border-ews-primary mr-2" }), "Loading..."] }) }))] }));
586
+ }
118
587
 
119
- const ArrowRight = (props) => (jsxRuntime.jsxs(Icon, { ...props, children: [jsxRuntime.jsx("path", { d: "M5 12h14" }), jsxRuntime.jsx("path", { d: "m12 5 7 7-7 7" })] }));
588
+ function MultiSearchAutocomplete({ items, selectedItems, onSelectionChange, onSearch, getEntityById, getPrimaryText, getSecondaryText, placeholder, disabled = false, loading = false, multiple = true, className, renderSelectedItem, renderListItem, keepOpenOnSelect = true, error, minSearchLength = 2, debounceTime = 300, }) {
589
+ const [searchTerm, setSearchTerm] = React.useState("");
590
+ const [isOpen, setIsOpen] = React.useState(false);
591
+ const [filteredItems, setFilteredItems] = React.useState(items);
592
+ const [isSearching, setIsSearching] = React.useState(false);
593
+ const [searchError, setSearchError] = React.useState(null);
594
+ const dropdownRef = React.useRef(null);
595
+ const inputRef = React.useRef(null);
596
+ const debouncedSearchTerm = useDebounce(searchTerm, debounceTime);
597
+ // Handle search term changes
598
+ React.useEffect(() => {
599
+ // If search term is empty, show all items
600
+ if (!searchTerm.trim()) {
601
+ setFilteredItems(items);
602
+ setSearchError(null);
603
+ return;
604
+ }
605
+ // If search term is too short, don't search
606
+ if (searchTerm.length < minSearchLength) {
607
+ setFilteredItems([]);
608
+ setSearchError(null);
609
+ return;
610
+ }
611
+ }, [searchTerm, items, minSearchLength]);
612
+ // Debounced search with API calls
613
+ React.useEffect(() => {
614
+ // If debounced search term is empty or too short, don't search
615
+ if (!debouncedSearchTerm.trim() ||
616
+ debouncedSearchTerm.length < minSearchLength) {
617
+ return;
618
+ }
619
+ const performSearch = async () => {
620
+ try {
621
+ setIsSearching(true);
622
+ setSearchError(null);
623
+ await onSearch(debouncedSearchTerm);
624
+ }
625
+ catch (err) {
626
+ setSearchError(err instanceof Error ? err.message : "Search failed");
627
+ }
628
+ finally {
629
+ setIsSearching(false);
630
+ }
631
+ };
632
+ performSearch();
633
+ }, [debouncedSearchTerm, onSearch, minSearchLength]);
634
+ // Update filtered items when items prop changes
635
+ React.useEffect(() => {
636
+ setFilteredItems(items);
637
+ }, [items]);
638
+ const handleItemToggle = React.useCallback((item) => {
639
+ if (multiple) {
640
+ const isSelected = selectedItems.some((selected) => selected.id === item.id);
641
+ if (isSelected) {
642
+ onSelectionChange(selectedItems.filter((selected) => selected.id !== item.id));
643
+ }
644
+ else {
645
+ onSelectionChange([...selectedItems, item]);
646
+ }
647
+ // Keep dropdown open for multiple selection
648
+ if (keepOpenOnSelect) {
649
+ setIsOpen(true);
650
+ }
651
+ }
652
+ else {
653
+ onSelectionChange([item]);
654
+ setIsOpen(false);
655
+ }
656
+ }, [selectedItems, onSelectionChange, multiple, keepOpenOnSelect]);
657
+ const handleRemoveItem = React.useCallback((itemToRemove) => {
658
+ onSelectionChange(selectedItems.filter((item) => item.id !== itemToRemove.id));
659
+ }, [selectedItems, onSelectionChange]);
660
+ const handleInputFocus = () => {
661
+ if (!disabled) {
662
+ setIsOpen(true);
663
+ }
664
+ };
665
+ const handleInputBlur = () => {
666
+ // Don't close immediately to allow for clicks on dropdown items
667
+ // Only close if keepOpenOnSelect is false
668
+ if (!keepOpenOnSelect) {
669
+ setTimeout(() => setIsOpen(false), 150);
670
+ }
671
+ };
672
+ // Close dropdown when clicking outside
673
+ React.useEffect(() => {
674
+ const handleClickOutside = (event) => {
675
+ if (dropdownRef.current &&
676
+ !dropdownRef.current.contains(event.target)) {
677
+ setIsOpen(false);
678
+ }
679
+ };
680
+ const handleEscapeKey = (event) => {
681
+ if (event.key === "Escape") {
682
+ setIsOpen(false);
683
+ }
684
+ };
685
+ if (isOpen) {
686
+ document.addEventListener("mousedown", handleClickOutside);
687
+ document.addEventListener("keydown", handleEscapeKey);
688
+ }
689
+ return () => {
690
+ document.removeEventListener("mousedown", handleClickOutside);
691
+ document.removeEventListener("keydown", handleEscapeKey);
692
+ };
693
+ }, [isOpen]);
694
+ // Default render functions
695
+ const defaultRenderSelectedItem = (entity) => (jsxRuntime.jsxs("span", { className: "inline-flex items-center px-3 py-1 text-sm font-medium rounded-full bg-ews-primary/10 text-ews-primary border border-ews-primary/20", children: [getPrimaryText(entity), jsxRuntime.jsx("button", { type: "button", onClick: () => handleRemoveItem(entity), className: "ml-2 text-ews-primary/60 hover:text-ews-primary", disabled: disabled, title: "Remove item", children: jsxRuntime.jsx(X, { className: "w-3 h-3" }) })] }));
696
+ const defaultRenderListItem = (entity, isSelected) => (jsxRuntime.jsxs("div", { className: "flex items-center space-x-3", children: [jsxRuntime.jsx("div", { className: cn("w-5 h-5 border-2 rounded flex items-center justify-center", isSelected ? "bg-ews-primary border-ews-primary" : "border-gray-300"), children: isSelected && jsxRuntime.jsx(Check, { className: "w-3 h-3 text-white" }) }), jsxRuntime.jsxs("div", { className: "flex flex-col", children: [jsxRuntime.jsx("span", { className: cn("font-medium", isSelected ? "text-ews-primary" : "text-gray-900"), children: getPrimaryText(entity) }), getSecondaryText && (jsxRuntime.jsx("span", { className: cn("text-sm", isSelected ? "text-ews-primary/70" : "text-gray-500"), children: getSecondaryText(entity) }))] })] }));
697
+ return (jsxRuntime.jsxs("div", { className: cn("relative", className), ref: dropdownRef, children: [selectedItems.length > 0 && (jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2 mb-3", children: selectedItems.map((item) => (jsxRuntime.jsx("div", { children: renderSelectedItem
698
+ ? renderSelectedItem(item)
699
+ : defaultRenderSelectedItem(item) }, item.id))) })), jsxRuntime.jsx(Input, { ref: inputRef, type: "text", value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), onFocus: handleInputFocus, onBlur: handleInputBlur, placeholder: placeholder, disabled: disabled, leftIcon: jsxRuntime.jsx(Search, { className: "w-4 h-4" }), className: "w-full" }), isOpen && !disabled && (jsxRuntime.jsx("div", { className: "absolute z-10 mt-1 w-full max-h-60 bg-white rounded-lg border border-gray-200 shadow-lg overflow-auto", children: loading || isSearching ? (jsxRuntime.jsxs("div", { className: "px-4 py-3 text-sm text-gray-500 flex items-center", children: [jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-4 w-4 border-b-2 border-ews-primary mr-2" }), isSearching ? "Searching..." : "Loading..."] })) : searchError ? (jsxRuntime.jsx("div", { className: "px-4 py-3 text-sm text-red-600", children: jsxRuntime.jsxs("div", { className: "flex items-center", children: [jsxRuntime.jsx(X, { className: "w-4 h-4 mr-2" }), "Error: ", searchError] }) })) : error ? (jsxRuntime.jsx("div", { className: "px-4 py-3 text-sm text-red-600", children: jsxRuntime.jsxs("div", { className: "flex items-center", children: [jsxRuntime.jsx(X, { className: "w-4 h-4 mr-2" }), error] }) })) : filteredItems.length === 0 ? (jsxRuntime.jsx("div", { className: "px-4 py-3 text-sm text-gray-500", children: searchTerm.length < minSearchLength
700
+ ? `Type at least ${minSearchLength} characters to search`
701
+ : "No items found" })) : (jsxRuntime.jsx("div", { className: "py-1", children: filteredItems.map((item) => {
702
+ const isSelected = selectedItems.some((selected) => selected.id === item.id);
703
+ return (jsxRuntime.jsx("button", { type: "button", onClick: () => handleItemToggle(item), className: cn("w-full px-4 py-3 text-left flex items-center transition-colors", isSelected
704
+ ? "bg-ews-primary/10 text-ews-primary border-l-4 border-ews-primary"
705
+ : "hover:bg-ews-primary/5 text-gray-900"), children: renderListItem
706
+ ? renderListItem(item, isSelected)
707
+ : defaultRenderListItem(item, isSelected) }, item.id));
708
+ }) })) }))] }));
709
+ }
120
710
 
121
- const Check = (props) => (jsxRuntime.jsx(Icon, { ...props, children: jsxRuntime.jsx("path", { d: "M20 6 9 17l-5-5" }) }));
711
+ const Modal = ({ isOpen, onClose, title, children, variant = "info", primaryAction, secondaryAction, onPrimaryAction, onSecondaryAction, isLoading = false, closeOnOverlayClick = true, className, contentClassName, error, }) => {
712
+ // Handle escape key press
713
+ React.useEffect(() => {
714
+ const handleEscape = (event) => {
715
+ if (event.key === "Escape" && isOpen) {
716
+ onClose();
717
+ }
718
+ };
719
+ if (isOpen) {
720
+ document.addEventListener("keydown", handleEscape);
721
+ // Prevent body scroll when modal is open
722
+ document.body.style.overflow = "hidden";
723
+ }
724
+ return () => {
725
+ document.removeEventListener("keydown", handleEscape);
726
+ document.body.style.overflow = "unset";
727
+ };
728
+ }, [isOpen, onClose]);
729
+ if (!isOpen)
730
+ return null;
731
+ const getVariantStyles = () => {
732
+ switch (variant) {
733
+ case "error":
734
+ return {
735
+ icon: jsxRuntime.jsx(CircleAlert, { className: "w-6 h-6 text-ews-error" }),
736
+ iconBg: "bg-ews-error/10",
737
+ titleColor: "text-ews-error",
738
+ borderColor: "border-ews-error/20",
739
+ };
740
+ case "warning":
741
+ return {
742
+ icon: jsxRuntime.jsx(TriangleAlert, { className: "w-6 h-6 text-ews-warning" }),
743
+ iconBg: "bg-ews-warning/10",
744
+ titleColor: "text-ews-warning",
745
+ borderColor: "border-ews-warning/20",
746
+ };
747
+ case "confirmation":
748
+ return {
749
+ icon: jsxRuntime.jsx(CircleCheckBig, { className: "w-6 h-6 text-ews-success" }),
750
+ iconBg: "bg-ews-success/10",
751
+ titleColor: "text-ews-success",
752
+ borderColor: "border-ews-success/20",
753
+ };
754
+ default:
755
+ return {
756
+ icon: jsxRuntime.jsx(CircleAlert, { className: "w-6 h-6 text-ews-primary" }),
757
+ iconBg: "bg-ews-primary/10",
758
+ titleColor: "text-ews-primary",
759
+ borderColor: "border-ews-primary/20",
760
+ };
761
+ }
762
+ };
763
+ const variantStyles = getVariantStyles();
764
+ const handleOverlayClick = (e) => {
765
+ if (e.target === e.currentTarget && closeOnOverlayClick) {
766
+ onClose();
767
+ }
768
+ };
769
+ return (jsxRuntime.jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm", onClick: handleOverlayClick }), jsxRuntime.jsxs("div", { className: cn("relative w-full max-w-md mx-4 bg-white rounded-lg shadow-xl transform transition-all", "animate-in fade-in-0 zoom-in-95 duration-200", className), role: "dialog", "aria-modal": "true", "aria-labelledby": "modal-title", children: [jsxRuntime.jsxs("div", { className: cn("flex items-center justify-between p-6 border-b", variantStyles.borderColor), children: [jsxRuntime.jsxs("div", { className: "flex items-center space-x-3", children: [jsxRuntime.jsx("div", { className: cn("p-2 rounded-full", variantStyles.iconBg), children: variantStyles.icon }), jsxRuntime.jsx("h2", { id: "modal-title", className: cn("text-lg font-semibold", variantStyles.titleColor), children: title })] }), jsxRuntime.jsx("button", { onClick: onClose, className: "p-1 text-gray-400 hover:text-gray-600 transition-colors", "aria-label": "Close modal", children: jsxRuntime.jsx(X, { className: "w-5 h-5" }) })] }), jsxRuntime.jsx("div", { className: cn("p-6", contentClassName), children: jsxRuntime.jsx("div", { className: "text-gray-700 leading-relaxed", children: error && variant === "error" ? (jsxRuntime.jsxs("div", { className: "space-y-3", children: [jsxRuntime.jsx("p", { children: error.message }), error.fields && error.fields.length > 0 && (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("p", { className: "font-semibold text-gray-900", children: "Erreurs de champ:" }), jsxRuntime.jsx("ul", { className: "mt-2 space-y-1", children: error.fields.map((field, index) => (jsxRuntime.jsxs("li", { className: "text-ews-error", children: ["\u2022 ", field.path, ": ", field.message] }, index))) })] }))] })) : (children) }) }), (primaryAction || secondaryAction) && (jsxRuntime.jsxs("div", { className: "flex items-center justify-end space-x-3 p-6 pt-0", children: [secondaryAction && (jsxRuntime.jsx(Button, { variant: "ghost", onClick: onSecondaryAction || onClose, disabled: isLoading, children: secondaryAction })), primaryAction && (jsxRuntime.jsx(Button, { variant: variant === "error" ? "error" : "primary", onClick: onPrimaryAction, loading: isLoading, children: primaryAction }))] }))] })] }));
770
+ };
771
+
772
+ const Logo = ({ size = "md", showTagline = true, iconOnly = false, variant = "normal", className, onClick, }) => {
773
+ const sizes = {
774
+ sm: "h-8",
775
+ md: "h-12",
776
+ lg: "h-16",
777
+ xl: "h-24",
778
+ };
779
+ const iconSizes = {
780
+ sm: "h-6 w-6",
781
+ md: "h-8 w-8",
782
+ lg: "h-12 w-12",
783
+ xl: "h-16 w-16",
784
+ };
785
+ // Get the appropriate logo image based on variant
786
+ // For iconOnly, always use favicon.ico
787
+ const logoSrc = iconOnly
788
+ ? "/favicon.ico"
789
+ : variant === "white"
790
+ ? "/image/logoWhite.png"
791
+ : variant === "favicon"
792
+ ? "/favicon.ico"
793
+ : "/image/logo.png";
794
+ if (iconOnly) {
795
+ return (jsxRuntime.jsx("div", { className: cn("flex items-center justify-center", iconSizes[size], className), onClick: onClick, role: onClick ? "button" : undefined, tabIndex: onClick ? 0 : undefined, children: jsxRuntime.jsx("img", { src: logoSrc, alt: "MEDECINE 360 Logo", className: "w-full h-full object-contain" }) }));
796
+ }
797
+ return (jsxRuntime.jsx("div", { className: cn("flex items-center", sizes[size], className), onClick: onClick, role: onClick ? "button" : undefined, tabIndex: onClick ? 0 : undefined, children: jsxRuntime.jsx("img", { src: logoSrc, alt: "MEDECINE 360 Logo", className: "h-full w-auto object-contain" }) }));
798
+ };
122
799
 
123
- const Search = (props) => (jsxRuntime.jsxs(Icon, { ...props, children: [jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "8" }), jsxRuntime.jsx("path", { d: "m21 21-4.35-4.35" })] }));
800
+ const SpecialtySearchAutocomplete = ({ selectedSpecialties = [], onSpecialtiesChange, placeholder = "Search and select medical specialties...", className = "", disabled = false, maxSelections, showSelectedCount = true, }) => {
801
+ const [specialties, setSpecialties] = React.useState([]);
802
+ const [isLoading, setIsLoading] = React.useState(false);
803
+ // Mock API call - replace with actual API
804
+ const fetchSpecialties = React.useCallback(async (searchTerm) => {
805
+ setIsLoading(true);
806
+ try {
807
+ // Simulate API delay
808
+ await new Promise((resolve) => setTimeout(resolve, 300));
809
+ // Mock data - replace with actual API call
810
+ const mockSpecialties = [
811
+ { id: "1", code: "CARD", label: "Cardiology" },
812
+ { id: "2", code: "DERM", label: "Dermatology" },
813
+ { id: "3", code: "ENDO", label: "Endocrinology" },
814
+ { id: "4", code: "GAST", label: "Gastroenterology" },
815
+ { id: "5", code: "HEMA", label: "Hematology" },
816
+ { id: "6", code: "NEUR", label: "Neurology" },
817
+ { id: "7", code: "ONCO", label: "Oncology" },
818
+ { id: "8", code: "ORTH", label: "Orthopedics" },
819
+ { id: "9", code: "PED", label: "Pediatrics" },
820
+ { id: "10", code: "PSYC", label: "Psychiatry" },
821
+ { id: "11", code: "RAD", label: "Radiology" },
822
+ { id: "12", code: "SURG", label: "Surgery" },
823
+ { id: "13", code: "UROL", label: "Urology" },
824
+ { id: "14", code: "GYN", label: "Gynecology" },
825
+ { id: "15", code: "OPHT", label: "Ophthalmology" },
826
+ ];
827
+ // Filter based on search term
828
+ const filtered = mockSpecialties.filter((specialty) => specialty.label.toLowerCase().includes(searchTerm.toLowerCase()) ||
829
+ specialty.code.toLowerCase().includes(searchTerm.toLowerCase()));
830
+ setSpecialties(filtered);
831
+ }
832
+ catch (error) {
833
+ console.error("Error fetching specialties:", error);
834
+ setSpecialties([]);
835
+ }
836
+ finally {
837
+ setIsLoading(false);
838
+ }
839
+ }, []);
840
+ // Initial load
841
+ React.useEffect(() => {
842
+ fetchSpecialties("");
843
+ }, [fetchSpecialties]);
844
+ const handleSelectionChange = React.useCallback((items) => {
845
+ // Check max selections limit
846
+ if (maxSelections && items.length > maxSelections) {
847
+ return; // Don't update if exceeding max selections
848
+ }
849
+ onSpecialtiesChange(items);
850
+ }, [onSpecialtiesChange, maxSelections]);
851
+ const getEntityById = React.useCallback(async (id) => {
852
+ return specialties.find((specialty) => specialty.id === id);
853
+ }, [specialties]);
854
+ return (jsxRuntime.jsxs("div", { className: cn("space-y-3", className), children: [jsxRuntime.jsxs("div", { className: "flex items-center space-x-2", children: [jsxRuntime.jsx("div", { className: "flex justify-center items-center w-8 h-8 rounded-lg bg-secondary-100", children: jsxRuntime.jsx(Stethoscope, { className: "w-4 h-4 text-secondary-600" }) }), jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900", children: "Medical Specialties" })] }), jsxRuntime.jsx("div", { children: jsxRuntime.jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Select Specialties" }) }), jsxRuntime.jsx(MultiSearchAutocomplete, { items: specialties, selectedItems: selectedSpecialties, onSelectionChange: handleSelectionChange, onSearch: fetchSpecialties, getEntityById: getEntityById, getPrimaryText: (specialty) => specialty.label, getSecondaryText: (specialty) => specialty.code, placeholder: placeholder, disabled: disabled, loading: isLoading, multiple: true, keepOpenOnSelect: true, className: "w-full", renderSelectedItem: (specialty) => (jsxRuntime.jsx("span", { className: "inline-flex items-center px-3 py-1 text-sm font-medium rounded-full bg-ews-primary/10 text-ews-primary border border-ews-primary/20", children: specialty.label })), renderListItem: (specialty, isSelected) => (jsxRuntime.jsxs("div", { className: "flex items-center space-x-3", children: [jsxRuntime.jsx("div", { className: cn("w-5 h-5 border-2 rounded flex items-center justify-center", isSelected
855
+ ? "bg-ews-primary border-ews-primary"
856
+ : "border-gray-300"), children: isSelected && (jsxRuntime.jsx("svg", { className: "w-3 h-3 text-white", fill: "currentColor", viewBox: "0 0 20 20", children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) })) }), jsxRuntime.jsxs("div", { className: "flex flex-col", children: [jsxRuntime.jsx("span", { className: cn("font-medium", isSelected ? "text-ews-primary" : "text-gray-900"), children: specialty.label }), jsxRuntime.jsx("span", { className: cn("text-sm", isSelected ? "text-ews-primary/70" : "text-gray-500"), children: specialty.code })] })] })) }), showSelectedCount && selectedSpecialties.length > 0 && (jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-sm text-gray-600", children: [jsxRuntime.jsxs("span", { children: [selectedSpecialties.length, " specialty", selectedSpecialties.length !== 1 ? "ies" : "", " selected"] }), maxSelections && (jsxRuntime.jsxs("span", { className: "text-gray-400", children: [selectedSpecialties.length, "/", maxSelections] }))] }))] }));
857
+ };
124
858
 
125
859
  exports.ArrowRight = ArrowRight;
126
860
  exports.Button = Button;
127
861
  exports.Check = Check;
128
862
  exports.Icon = Icon;
129
863
  exports.Input = Input;
864
+ exports.Logo = Logo;
865
+ exports.Modal = Modal;
866
+ exports.MultiSearchAutocomplete = MultiSearchAutocomplete;
130
867
  exports.Search = Search;
868
+ exports.SearchAutocomplete = SearchAutocomplete;
869
+ exports.SpecialtySearchAutocomplete = SpecialtySearchAutocomplete;
131
870
  exports.cn = cn;
132
871
  exports.debounce = debounce;
133
872
  exports.formatCurrency = formatCurrency;
134
873
  exports.formatDate = formatDate;
135
874
  exports.generateId = generateId;
875
+ exports.useDebounce = useDebounce;
876
+ exports.useDebouncedCallback = useDebouncedCallback;
136
877
  //# sourceMappingURL=index.js.map