@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.
- package/README.md +50 -1
- package/dist/components/Button/Button.d.ts.map +1 -1
- package/dist/components/Input/Input.d.ts +4 -0
- package/dist/components/Input/Input.d.ts.map +1 -1
- package/dist/components/Logo/Logo.d.ts +29 -0
- package/dist/components/Logo/Logo.d.ts.map +1 -0
- package/dist/components/Logo/index.d.ts +3 -0
- package/dist/components/Logo/index.d.ts.map +1 -0
- package/dist/components/Modal/Modal.d.ts +72 -0
- package/dist/components/Modal/Modal.d.ts.map +1 -0
- package/dist/components/Modal/index.d.ts +3 -0
- package/dist/components/Modal/index.d.ts.map +1 -0
- package/dist/components/MultiSearchAutocomplete/MultiSearchAutocomplete.d.ts +25 -0
- package/dist/components/MultiSearchAutocomplete/MultiSearchAutocomplete.d.ts.map +1 -0
- package/dist/components/MultiSearchAutocomplete/index.d.ts +2 -0
- package/dist/components/MultiSearchAutocomplete/index.d.ts.map +1 -0
- package/dist/components/SearchAutocomplete/SearchAutocomplete.d.ts +22 -0
- package/dist/components/SearchAutocomplete/SearchAutocomplete.d.ts.map +1 -0
- package/dist/components/SearchAutocomplete/index.d.ts +3 -0
- package/dist/components/SearchAutocomplete/index.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/useDebounce.d.ts +15 -0
- package/dist/hooks/useDebounce.d.ts.map +1 -0
- package/dist/icons/Icon.d.ts +5 -4
- package/dist/icons/Icon.d.ts.map +1 -1
- package/dist/icons/index.d.ts +1 -3
- package/dist/icons/index.d.ts.map +1 -1
- package/dist/index.css +3 -1
- package/dist/index.d.ts +185 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.css +3 -1
- package/dist/index.esm.js +763 -29
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +768 -27
- package/dist/index.js.map +1 -1
- package/dist/molecules/SpecialtySearchAutocomplete/SpecialtySearchAutocomplete.d.ts +18 -0
- package/dist/molecules/SpecialtySearchAutocomplete/SpecialtySearchAutocomplete.d.ts.map +1 -0
- package/dist/molecules/SpecialtySearchAutocomplete/index.d.ts +3 -0
- package/dist/molecules/SpecialtySearchAutocomplete/index.d.ts.map +1 -0
- package/dist/molecules/index.d.ts +3 -0
- package/dist/molecules/index.d.ts.map +1 -0
- package/dist/utils/index.d.ts +5 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/package.json +17 -2
- package/src/assets/favicon.svg +6 -0
- package/src/assets/logo.svg +17 -0
- package/src/components/Button/Button.tsx +22 -8
- package/src/components/Input/Input.tsx +42 -16
- package/src/components/Logo/Logo.tsx +100 -0
- package/src/components/Logo/index.ts +2 -0
- package/src/components/Modal/Modal.tsx +257 -0
- package/src/components/Modal/index.ts +2 -0
- package/src/components/MultiSearchAutocomplete/MultiSearchAutocomplete.tsx +319 -0
- package/src/components/MultiSearchAutocomplete/index.ts +1 -0
- package/src/components/SearchAutocomplete/SearchAutocomplete.tsx +315 -0
- package/src/components/SearchAutocomplete/index.ts +2 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useDebounce.ts +64 -0
- package/src/icons/Icon.tsx +15 -16
- package/src/icons/index.ts +39 -3
- package/src/index.ts +19 -0
- package/src/molecules/SpecialtySearchAutocomplete/SpecialtySearchAutocomplete.tsx +203 -0
- package/src/molecules/SpecialtySearchAutocomplete/index.ts +5 -0
- package/src/molecules/index.ts +5 -0
- package/src/styles/index.css +8 -5
- package/src/styles/tailwind.css +3 -0
- 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-
|
|
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-
|
|
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-
|
|
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-
|
|
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,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 };
|