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