@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
@@ -1,4 +1,5 @@
1
- import React from "react";
1
+ import React, { useState } from "react";
2
+ import { Eye, EyeOff } from "../../icons";
2
3
  import { cn } from "../../utils";
3
4
 
4
5
  export interface InputProps
@@ -35,6 +36,10 @@ export interface InputProps
35
36
  * Whether the input should take full width
36
37
  */
37
38
  fullWidth?: boolean;
39
+ /**
40
+ * Whether to show password toggle for password inputs
41
+ */
42
+ showPasswordToggle?: boolean;
38
43
  }
39
44
 
40
45
  const Input = React.forwardRef<HTMLInputElement, InputProps>(
@@ -49,7 +54,9 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
49
54
  leftIcon,
50
55
  rightIcon,
51
56
  fullWidth = false,
57
+ showPasswordToggle = false,
52
58
  id,
59
+ type = "text",
53
60
  ...props
54
61
  },
55
62
  ref
@@ -58,16 +65,21 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
58
65
  const hasError = Boolean(error);
59
66
  const actualVariant = hasError ? "error" : variant;
60
67
 
68
+ // Password visibility state
69
+ const [showPassword, setShowPassword] = useState(false);
70
+ const isPasswordInput = type === "password";
71
+ const shouldShowPasswordToggle = showPasswordToggle && isPasswordInput;
72
+ const actualType = isPasswordInput && showPassword ? "text" : type;
73
+
61
74
  const baseStyles =
62
- "block w-full rounded-md border transition-colors focus:outline-none focus:ring-2 focus:ring-offset-0";
75
+ "block w-full rounded-md border transition-colors focus:outline-none focus:ring-2 focus:ring-offset-0 hover:border-ews-primary";
63
76
 
64
77
  const variants = {
65
78
  default:
66
- "border-[var(--ews-gray-300)] focus:border-[var(--ews-primary)] focus:ring-[var(--ews-primary)]",
67
- error:
68
- "border-[var(--ews-error)] focus:border-[var(--ews-error)] focus:ring-[var(--ews-error)]",
79
+ "border-ews-gray-300 focus:border-ews-primary focus:ring-ews-primary",
80
+ error: "border-ews-error focus:border-ews-error focus:ring-ews-error",
69
81
  success:
70
- "border-[var(--ews-success)] focus:border-[var(--ews-success)] focus:ring-[var(--ews-success)]",
82
+ "border-ews-success focus:border-ews-success focus:ring-ews-success",
71
83
  };
72
84
 
73
85
  const sizes = {
@@ -87,7 +99,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
87
99
  {label && (
88
100
  <label
89
101
  htmlFor={inputId}
90
- className="block text-sm font-medium text-[var(--ews-gray-700)]"
102
+ className="block text-sm font-medium text-ews-gray-700"
91
103
  >
92
104
  {label}
93
105
  </label>
@@ -95,41 +107,55 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
95
107
  <div className="relative">
96
108
  {leftIcon && (
97
109
  <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
98
- <span
99
- className={cn("text-[var(--ews-gray-400)]", iconSizes[size])}
100
- >
110
+ <span className={cn("text-ews-gray-400", iconSizes[size])}>
101
111
  {leftIcon}
102
112
  </span>
103
113
  </div>
104
114
  )}
105
115
  <input
106
116
  id={inputId}
117
+ type={actualType}
107
118
  className={cn(
108
119
  baseStyles,
109
120
  variants[actualVariant],
110
121
  sizes[size],
111
122
  leftIcon && "pl-10",
112
- rightIcon && "pr-10",
123
+ (rightIcon || shouldShowPasswordToggle) && "pr-10",
113
124
  className
114
125
  )}
115
126
  ref={ref}
116
127
  {...props}
117
128
  />
118
- {rightIcon && (
129
+ {rightIcon && !shouldShowPasswordToggle && (
119
130
  <div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
120
- <span
121
- className={cn("text-[var(--ews-gray-400)]", iconSizes[size])}
122
- >
131
+ <span className={cn("text-ews-gray-400", iconSizes[size])}>
123
132
  {rightIcon}
124
133
  </span>
125
134
  </div>
126
135
  )}
136
+ {shouldShowPasswordToggle && (
137
+ <button
138
+ type="button"
139
+ className="absolute inset-y-0 right-0 pr-3 flex items-center"
140
+ onClick={() => setShowPassword(!showPassword)}
141
+ tabIndex={-1}
142
+ >
143
+ <span
144
+ className={cn(
145
+ "text-ews-gray-400 hover:text-ews-gray-600 transition-colors",
146
+ iconSizes[size]
147
+ )}
148
+ >
149
+ {showPassword ? <EyeOff size={16} /> : <Eye size={16} />}
150
+ </span>
151
+ </button>
152
+ )}
127
153
  </div>
128
154
  {(error || helperText) && (
129
155
  <p
130
156
  className={cn(
131
157
  "text-sm",
132
- error ? "text-[var(--ews-error)]" : "text-[var(--ews-gray-500)]"
158
+ error ? "text-ews-error" : "text-ews-gray-500"
133
159
  )}
134
160
  >
135
161
  {error || helperText}
@@ -0,0 +1,100 @@
1
+ import { cn } from "../../utils";
2
+
3
+ export interface LogoProps {
4
+ /**
5
+ * Logo size
6
+ */
7
+ size?: "sm" | "md" | "lg" | "xl";
8
+ /**
9
+ * Whether to show the tagline
10
+ */
11
+ showTagline?: boolean;
12
+ /**
13
+ * Whether to show only the icon (favicon version)
14
+ */
15
+ iconOnly?: boolean;
16
+ /**
17
+ * Logo variant - normal, white, or favicon
18
+ */
19
+ variant?: "normal" | "white" | "favicon";
20
+ /**
21
+ * Custom className
22
+ */
23
+ className?: string;
24
+ /**
25
+ * Click handler
26
+ */
27
+ onClick?: () => void;
28
+ }
29
+
30
+ const Logo = ({
31
+ size = "md",
32
+ showTagline = true,
33
+ iconOnly = false,
34
+ variant = "normal",
35
+ className,
36
+ onClick,
37
+ }: LogoProps) => {
38
+ const sizes = {
39
+ sm: "h-8",
40
+ md: "h-12",
41
+ lg: "h-16",
42
+ xl: "h-24",
43
+ };
44
+
45
+ const iconSizes = {
46
+ sm: "h-6 w-6",
47
+ md: "h-8 w-8",
48
+ lg: "h-12 w-12",
49
+ xl: "h-16 w-16",
50
+ };
51
+
52
+ // Get the appropriate logo image based on variant
53
+ // For iconOnly, always use favicon.ico
54
+ const logoSrc = iconOnly
55
+ ? "/favicon.ico"
56
+ : variant === "white"
57
+ ? "/image/logoWhite.png"
58
+ : variant === "favicon"
59
+ ? "/favicon.ico"
60
+ : "/image/logo.png";
61
+
62
+ if (iconOnly) {
63
+ return (
64
+ <div
65
+ className={cn(
66
+ "flex items-center justify-center",
67
+ iconSizes[size],
68
+ className
69
+ )}
70
+ onClick={onClick}
71
+ role={onClick ? "button" : undefined}
72
+ tabIndex={onClick ? 0 : undefined}
73
+ >
74
+ <img
75
+ src={logoSrc}
76
+ alt="MEDECINE 360 Logo"
77
+ className="w-full h-full object-contain"
78
+ />
79
+ </div>
80
+ );
81
+ }
82
+
83
+ return (
84
+ <div
85
+ className={cn("flex items-center", sizes[size], className)}
86
+ onClick={onClick}
87
+ role={onClick ? "button" : undefined}
88
+ tabIndex={onClick ? 0 : undefined}
89
+ >
90
+ {/* Logo Image */}
91
+ <img
92
+ src={logoSrc}
93
+ alt="MEDECINE 360 Logo"
94
+ className="h-full w-auto object-contain"
95
+ />
96
+ </div>
97
+ );
98
+ };
99
+
100
+ export { Logo };
@@ -0,0 +1,2 @@
1
+ export { Logo } from "./Logo";
2
+ export type { LogoProps } from "./Logo";
@@ -0,0 +1,257 @@
1
+ import { AlertCircle, AlertTriangle, CheckCircle, X } from "lucide-react";
2
+ import React, { useEffect } from "react";
3
+ import { cn } from "../../utils";
4
+ import { Button } from "../Button/Button";
5
+
6
+ export interface ErrorField {
7
+ name: string;
8
+ message: string;
9
+ path: string;
10
+ }
11
+
12
+ export interface ErrorObject {
13
+ code: string;
14
+ message: string;
15
+ fields: ErrorField[];
16
+ }
17
+
18
+ export interface ModalProps {
19
+ /**
20
+ * Whether the modal is open
21
+ */
22
+ isOpen: boolean;
23
+ /**
24
+ * Function to call when modal should be closed
25
+ */
26
+ onClose: () => void;
27
+ /**
28
+ * Modal title
29
+ */
30
+ title: string;
31
+ /**
32
+ * Modal content/description
33
+ */
34
+ children: React.ReactNode;
35
+ /**
36
+ * Modal variant
37
+ */
38
+ variant?: "error" | "warning" | "confirmation" | "info";
39
+ /**
40
+ * Primary action button text
41
+ */
42
+ primaryAction?: string;
43
+ /**
44
+ * Secondary action button text
45
+ */
46
+ secondaryAction?: string;
47
+ /**
48
+ * Function to call when primary action is clicked
49
+ */
50
+ onPrimaryAction?: () => void;
51
+ /**
52
+ * Function to call when secondary action is clicked
53
+ */
54
+ onSecondaryAction?: () => void;
55
+ /**
56
+ * Whether the primary action button is loading
57
+ */
58
+ isLoading?: boolean;
59
+ /**
60
+ * Whether the modal can be closed by clicking outside or pressing escape
61
+ */
62
+ closeOnOverlayClick?: boolean;
63
+ /**
64
+ * Custom className for the modal
65
+ */
66
+ className?: string;
67
+ /**
68
+ * Custom className for the content
69
+ */
70
+ contentClassName?: string;
71
+ /**
72
+ * Error object for error modals
73
+ */
74
+ error?: ErrorObject;
75
+ }
76
+
77
+ const Modal = ({
78
+ isOpen,
79
+ onClose,
80
+ title,
81
+ children,
82
+ variant = "info",
83
+ primaryAction,
84
+ secondaryAction,
85
+ onPrimaryAction,
86
+ onSecondaryAction,
87
+ isLoading = false,
88
+ closeOnOverlayClick = true,
89
+ className,
90
+ contentClassName,
91
+ error,
92
+ }: ModalProps) => {
93
+ // Handle escape key press
94
+ useEffect(() => {
95
+ const handleEscape = (event: KeyboardEvent) => {
96
+ if (event.key === "Escape" && isOpen) {
97
+ onClose();
98
+ }
99
+ };
100
+
101
+ if (isOpen) {
102
+ document.addEventListener("keydown", handleEscape);
103
+ // Prevent body scroll when modal is open
104
+ document.body.style.overflow = "hidden";
105
+ }
106
+
107
+ return () => {
108
+ document.removeEventListener("keydown", handleEscape);
109
+ document.body.style.overflow = "unset";
110
+ };
111
+ }, [isOpen, onClose]);
112
+
113
+ if (!isOpen) return null;
114
+
115
+ const getVariantStyles = () => {
116
+ switch (variant) {
117
+ case "error":
118
+ return {
119
+ icon: <AlertCircle className="w-6 h-6 text-ews-error" />,
120
+ iconBg: "bg-ews-error/10",
121
+ titleColor: "text-ews-error",
122
+ borderColor: "border-ews-error/20",
123
+ };
124
+ case "warning":
125
+ return {
126
+ icon: <AlertTriangle className="w-6 h-6 text-ews-warning" />,
127
+ iconBg: "bg-ews-warning/10",
128
+ titleColor: "text-ews-warning",
129
+ borderColor: "border-ews-warning/20",
130
+ };
131
+ case "confirmation":
132
+ return {
133
+ icon: <CheckCircle className="w-6 h-6 text-ews-success" />,
134
+ iconBg: "bg-ews-success/10",
135
+ titleColor: "text-ews-success",
136
+ borderColor: "border-ews-success/20",
137
+ };
138
+ default:
139
+ return {
140
+ icon: <AlertCircle className="w-6 h-6 text-ews-primary" />,
141
+ iconBg: "bg-ews-primary/10",
142
+ titleColor: "text-ews-primary",
143
+ borderColor: "border-ews-primary/20",
144
+ };
145
+ }
146
+ };
147
+
148
+ const variantStyles = getVariantStyles();
149
+
150
+ const handleOverlayClick = (e: React.MouseEvent) => {
151
+ if (e.target === e.currentTarget && closeOnOverlayClick) {
152
+ onClose();
153
+ }
154
+ };
155
+
156
+ return (
157
+ <div className="fixed inset-0 z-50 flex items-center justify-center">
158
+ {/* Backdrop */}
159
+ <div
160
+ className="absolute inset-0 bg-black/50 backdrop-blur-sm"
161
+ onClick={handleOverlayClick}
162
+ />
163
+
164
+ {/* Modal */}
165
+ <div
166
+ className={cn(
167
+ "relative w-full max-w-md mx-4 bg-white rounded-lg shadow-xl transform transition-all",
168
+ "animate-in fade-in-0 zoom-in-95 duration-200",
169
+ className
170
+ )}
171
+ role="dialog"
172
+ aria-modal="true"
173
+ aria-labelledby="modal-title"
174
+ >
175
+ {/* Header */}
176
+ <div
177
+ className={cn(
178
+ "flex items-center justify-between p-6 border-b",
179
+ variantStyles.borderColor
180
+ )}
181
+ >
182
+ <div className="flex items-center space-x-3">
183
+ <div className={cn("p-2 rounded-full", variantStyles.iconBg)}>
184
+ {variantStyles.icon}
185
+ </div>
186
+ <h2
187
+ id="modal-title"
188
+ className={cn("text-lg font-semibold", variantStyles.titleColor)}
189
+ >
190
+ {title}
191
+ </h2>
192
+ </div>
193
+ <button
194
+ onClick={onClose}
195
+ className="p-1 text-gray-400 hover:text-gray-600 transition-colors"
196
+ aria-label="Close modal"
197
+ >
198
+ <X className="w-5 h-5" />
199
+ </button>
200
+ </div>
201
+
202
+ {/* Content */}
203
+ <div className={cn("p-6", contentClassName)}>
204
+ <div className="text-gray-700 leading-relaxed">
205
+ {error && variant === "error" ? (
206
+ <div className="space-y-3">
207
+ <p>{error.message}</p>
208
+ {error.fields && error.fields.length > 0 && (
209
+ <div>
210
+ <p className="font-semibold text-gray-900">
211
+ Erreurs de champ:
212
+ </p>
213
+ <ul className="mt-2 space-y-1">
214
+ {error.fields.map((field, index) => (
215
+ <li key={index} className="text-ews-error">
216
+ • {field.path}: {field.message}
217
+ </li>
218
+ ))}
219
+ </ul>
220
+ </div>
221
+ )}
222
+ </div>
223
+ ) : (
224
+ children
225
+ )}
226
+ </div>
227
+ </div>
228
+
229
+ {/* Actions */}
230
+ {(primaryAction || secondaryAction) && (
231
+ <div className="flex items-center justify-end space-x-3 p-6 pt-0">
232
+ {secondaryAction && (
233
+ <Button
234
+ variant="ghost"
235
+ onClick={onSecondaryAction || onClose}
236
+ disabled={isLoading}
237
+ >
238
+ {secondaryAction}
239
+ </Button>
240
+ )}
241
+ {primaryAction && (
242
+ <Button
243
+ variant={variant === "error" ? "error" : "primary"}
244
+ onClick={onPrimaryAction}
245
+ loading={isLoading}
246
+ >
247
+ {primaryAction}
248
+ </Button>
249
+ )}
250
+ </div>
251
+ )}
252
+ </div>
253
+ </div>
254
+ );
255
+ };
256
+
257
+ export { Modal };
@@ -0,0 +1,2 @@
1
+ export { Modal } from "./Modal";
2
+ export type { ErrorField, ErrorObject, ModalProps } from "./Modal";