@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.
- 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
package/src/icons/Icon.tsx
CHANGED
|
@@ -1,34 +1,33 @@
|
|
|
1
|
+
import { LucideProps } from "lucide-react";
|
|
1
2
|
import React from "react";
|
|
2
3
|
|
|
3
|
-
export interface IconProps extends
|
|
4
|
+
export interface IconProps extends Omit<LucideProps, "size"> {
|
|
4
5
|
/**
|
|
5
6
|
* Icon size
|
|
6
7
|
*/
|
|
7
|
-
size?: "sm" | "md" | "lg" | "xl";
|
|
8
|
+
size?: "sm" | "md" | "lg" | "xl" | number;
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
+
* The Lucide icon component to render
|
|
10
11
|
*/
|
|
11
|
-
|
|
12
|
+
icon: React.ComponentType<LucideProps>;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
const Icon = React.forwardRef<SVGSVGElement, IconProps>(
|
|
15
|
-
({ size = "md",
|
|
16
|
+
({ size = "md", icon: IconComponent, className, ...props }, ref) => {
|
|
16
17
|
const sizes = {
|
|
17
|
-
sm:
|
|
18
|
-
md:
|
|
19
|
-
lg:
|
|
20
|
-
xl:
|
|
18
|
+
sm: 16,
|
|
19
|
+
md: 20,
|
|
20
|
+
lg: 24,
|
|
21
|
+
xl: 32,
|
|
21
22
|
};
|
|
22
23
|
|
|
24
|
+
const iconSize = typeof size === "number" ? size : sizes[size];
|
|
25
|
+
|
|
23
26
|
return (
|
|
24
|
-
<
|
|
27
|
+
<IconComponent
|
|
25
28
|
ref={ref}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
stroke={color}
|
|
29
|
-
strokeWidth="2"
|
|
30
|
-
strokeLinecap="round"
|
|
31
|
-
strokeLinejoin="round"
|
|
29
|
+
size={iconSize}
|
|
30
|
+
className={className}
|
|
32
31
|
{...props}
|
|
33
32
|
/>
|
|
34
33
|
);
|
package/src/icons/index.ts
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
|
-
export
|
|
2
|
-
export {
|
|
1
|
+
// Re-export commonly used Lucide icons
|
|
2
|
+
export {
|
|
3
|
+
ArrowRight,
|
|
4
|
+
Bell,
|
|
5
|
+
Building,
|
|
6
|
+
Calendar,
|
|
7
|
+
Check,
|
|
8
|
+
ChevronDown,
|
|
9
|
+
ChevronLeft,
|
|
10
|
+
ChevronRight,
|
|
11
|
+
ChevronUp,
|
|
12
|
+
Clock,
|
|
13
|
+
Download,
|
|
14
|
+
Edit,
|
|
15
|
+
Eye,
|
|
16
|
+
EyeOff,
|
|
17
|
+
Filter,
|
|
18
|
+
Heart,
|
|
19
|
+
Home,
|
|
20
|
+
Lock,
|
|
21
|
+
Mail,
|
|
22
|
+
MapPin,
|
|
23
|
+
Menu,
|
|
24
|
+
Minus,
|
|
25
|
+
Phone,
|
|
26
|
+
Plus,
|
|
27
|
+
Search,
|
|
28
|
+
Settings,
|
|
29
|
+
Share,
|
|
30
|
+
Star,
|
|
31
|
+
Trash2,
|
|
32
|
+
Unlock,
|
|
33
|
+
Upload,
|
|
34
|
+
User,
|
|
35
|
+
X,
|
|
36
|
+
type LucideProps,
|
|
37
|
+
} from "lucide-react";
|
|
38
|
+
|
|
39
|
+
// Keep our custom Icon component for backward compatibility
|
|
3
40
|
export { Icon } from "./Icon";
|
|
4
41
|
export type { IconProps } from "./Icon";
|
|
5
|
-
export { Search } from "./Search";
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,21 @@ export type { ButtonProps } from "./components/Button";
|
|
|
5
5
|
export { Input } from "./components/Input";
|
|
6
6
|
export type { InputProps } from "./components/Input";
|
|
7
7
|
|
|
8
|
+
export { SearchAutocomplete } from "./components/SearchAutocomplete";
|
|
9
|
+
export type { SearchableEntity } from "./components/SearchAutocomplete";
|
|
10
|
+
|
|
11
|
+
export { MultiSearchAutocomplete } from "./components/MultiSearchAutocomplete";
|
|
12
|
+
|
|
13
|
+
export { Modal } from "./components/Modal";
|
|
14
|
+
export type { ErrorField, ErrorObject, ModalProps } from "./components/Modal";
|
|
15
|
+
|
|
16
|
+
export { Logo } from "./components/Logo";
|
|
17
|
+
export type { LogoProps } from "./components/Logo";
|
|
18
|
+
|
|
19
|
+
// Molecules
|
|
20
|
+
export { SpecialtySearchAutocomplete } from "./molecules";
|
|
21
|
+
export type { Specialty, SpecialtySearchAutocompleteProps } from "./molecules";
|
|
22
|
+
|
|
8
23
|
// Icons
|
|
9
24
|
export { ArrowRight, Check, Icon, Search } from "./icons";
|
|
10
25
|
export type { IconProps } from "./icons";
|
|
@@ -12,5 +27,9 @@ export type { IconProps } from "./icons";
|
|
|
12
27
|
// Utils
|
|
13
28
|
export { cn, debounce, formatCurrency, formatDate, generateId } from "./utils";
|
|
14
29
|
|
|
30
|
+
// Hooks
|
|
31
|
+
export { useDebounce, useDebouncedCallback } from "./hooks";
|
|
32
|
+
|
|
15
33
|
// Styles
|
|
16
34
|
import "./styles/index.css";
|
|
35
|
+
import "./styles/tailwind.css";
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { Stethoscope } from "lucide-react";
|
|
2
|
+
import React, { useCallback, useEffect, useState } from "react";
|
|
3
|
+
import { MultiSearchAutocomplete } from "../../components/MultiSearchAutocomplete";
|
|
4
|
+
import { cn } from "../../utils";
|
|
5
|
+
|
|
6
|
+
export interface Specialty {
|
|
7
|
+
id: string;
|
|
8
|
+
code: string;
|
|
9
|
+
label: string;
|
|
10
|
+
[key: string]: string | number | boolean | undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SpecialtySearchAutocompleteProps {
|
|
14
|
+
selectedSpecialties: Specialty[];
|
|
15
|
+
onSpecialtiesChange: (specialties: Specialty[]) => void;
|
|
16
|
+
placeholder?: string;
|
|
17
|
+
className?: string;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
maxSelections?: number;
|
|
20
|
+
showSelectedCount?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const SpecialtySearchAutocomplete: React.FC<
|
|
24
|
+
SpecialtySearchAutocompleteProps
|
|
25
|
+
> = ({
|
|
26
|
+
selectedSpecialties = [],
|
|
27
|
+
onSpecialtiesChange,
|
|
28
|
+
placeholder = "Search and select medical specialties...",
|
|
29
|
+
className = "",
|
|
30
|
+
disabled = false,
|
|
31
|
+
maxSelections,
|
|
32
|
+
showSelectedCount = true,
|
|
33
|
+
}) => {
|
|
34
|
+
const [specialties, setSpecialties] = useState<Specialty[]>([]);
|
|
35
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
36
|
+
|
|
37
|
+
// Mock API call - replace with actual API
|
|
38
|
+
const fetchSpecialties = useCallback(async (searchTerm: string) => {
|
|
39
|
+
setIsLoading(true);
|
|
40
|
+
try {
|
|
41
|
+
// Simulate API delay
|
|
42
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
43
|
+
|
|
44
|
+
// Mock data - replace with actual API call
|
|
45
|
+
const mockSpecialties: Specialty[] = [
|
|
46
|
+
{ id: "1", code: "CARD", label: "Cardiology" },
|
|
47
|
+
{ id: "2", code: "DERM", label: "Dermatology" },
|
|
48
|
+
{ id: "3", code: "ENDO", label: "Endocrinology" },
|
|
49
|
+
{ id: "4", code: "GAST", label: "Gastroenterology" },
|
|
50
|
+
{ id: "5", code: "HEMA", label: "Hematology" },
|
|
51
|
+
{ id: "6", code: "NEUR", label: "Neurology" },
|
|
52
|
+
{ id: "7", code: "ONCO", label: "Oncology" },
|
|
53
|
+
{ id: "8", code: "ORTH", label: "Orthopedics" },
|
|
54
|
+
{ id: "9", code: "PED", label: "Pediatrics" },
|
|
55
|
+
{ id: "10", code: "PSYC", label: "Psychiatry" },
|
|
56
|
+
{ id: "11", code: "RAD", label: "Radiology" },
|
|
57
|
+
{ id: "12", code: "SURG", label: "Surgery" },
|
|
58
|
+
{ id: "13", code: "UROL", label: "Urology" },
|
|
59
|
+
{ id: "14", code: "GYN", label: "Gynecology" },
|
|
60
|
+
{ id: "15", code: "OPHT", label: "Ophthalmology" },
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
// Filter based on search term
|
|
64
|
+
const filtered = mockSpecialties.filter(
|
|
65
|
+
(specialty) =>
|
|
66
|
+
specialty.label.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
67
|
+
specialty.code.toLowerCase().includes(searchTerm.toLowerCase())
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
setSpecialties(filtered);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error("Error fetching specialties:", error);
|
|
73
|
+
setSpecialties([]);
|
|
74
|
+
} finally {
|
|
75
|
+
setIsLoading(false);
|
|
76
|
+
}
|
|
77
|
+
}, []);
|
|
78
|
+
|
|
79
|
+
// Initial load
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
fetchSpecialties("");
|
|
82
|
+
}, [fetchSpecialties]);
|
|
83
|
+
|
|
84
|
+
const handleSelectionChange = useCallback(
|
|
85
|
+
(items: Specialty[]) => {
|
|
86
|
+
// Check max selections limit
|
|
87
|
+
if (maxSelections && items.length > maxSelections) {
|
|
88
|
+
return; // Don't update if exceeding max selections
|
|
89
|
+
}
|
|
90
|
+
onSpecialtiesChange(items);
|
|
91
|
+
},
|
|
92
|
+
[onSpecialtiesChange, maxSelections]
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const getEntityById = useCallback(
|
|
96
|
+
async (id: string): Promise<Specialty | undefined> => {
|
|
97
|
+
return specialties.find((specialty) => specialty.id === id);
|
|
98
|
+
},
|
|
99
|
+
[specialties]
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div className={cn("space-y-3", className)}>
|
|
104
|
+
{/* Header with title and icon */}
|
|
105
|
+
<div className="flex items-center space-x-2">
|
|
106
|
+
<div className="flex justify-center items-center w-8 h-8 rounded-lg bg-secondary-100">
|
|
107
|
+
<Stethoscope className="w-4 h-4 text-secondary-600" />
|
|
108
|
+
</div>
|
|
109
|
+
<h3 className="text-lg font-semibold text-gray-900">
|
|
110
|
+
Medical Specialties
|
|
111
|
+
</h3>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
{/* Label */}
|
|
115
|
+
<div>
|
|
116
|
+
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
117
|
+
Select Specialties
|
|
118
|
+
</label>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
{/* MultiSearchAutocomplete component */}
|
|
122
|
+
<MultiSearchAutocomplete<Specialty>
|
|
123
|
+
items={specialties}
|
|
124
|
+
selectedItems={selectedSpecialties}
|
|
125
|
+
onSelectionChange={handleSelectionChange}
|
|
126
|
+
onSearch={fetchSpecialties}
|
|
127
|
+
getEntityById={getEntityById}
|
|
128
|
+
getPrimaryText={(specialty) => specialty.label}
|
|
129
|
+
getSecondaryText={(specialty) => specialty.code}
|
|
130
|
+
placeholder={placeholder}
|
|
131
|
+
disabled={disabled}
|
|
132
|
+
loading={isLoading}
|
|
133
|
+
multiple={true}
|
|
134
|
+
keepOpenOnSelect={true}
|
|
135
|
+
className="w-full"
|
|
136
|
+
renderSelectedItem={(specialty) => (
|
|
137
|
+
<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">
|
|
138
|
+
{specialty.label}
|
|
139
|
+
</span>
|
|
140
|
+
)}
|
|
141
|
+
renderListItem={(specialty, isSelected) => (
|
|
142
|
+
<div className="flex items-center space-x-3">
|
|
143
|
+
<div
|
|
144
|
+
className={cn(
|
|
145
|
+
"w-5 h-5 border-2 rounded flex items-center justify-center",
|
|
146
|
+
isSelected
|
|
147
|
+
? "bg-ews-primary border-ews-primary"
|
|
148
|
+
: "border-gray-300"
|
|
149
|
+
)}
|
|
150
|
+
>
|
|
151
|
+
{isSelected && (
|
|
152
|
+
<svg
|
|
153
|
+
className="w-3 h-3 text-white"
|
|
154
|
+
fill="currentColor"
|
|
155
|
+
viewBox="0 0 20 20"
|
|
156
|
+
>
|
|
157
|
+
<path
|
|
158
|
+
fillRule="evenodd"
|
|
159
|
+
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"
|
|
160
|
+
clipRule="evenodd"
|
|
161
|
+
/>
|
|
162
|
+
</svg>
|
|
163
|
+
)}
|
|
164
|
+
</div>
|
|
165
|
+
<div className="flex flex-col">
|
|
166
|
+
<span
|
|
167
|
+
className={cn(
|
|
168
|
+
"font-medium",
|
|
169
|
+
isSelected ? "text-ews-primary" : "text-gray-900"
|
|
170
|
+
)}
|
|
171
|
+
>
|
|
172
|
+
{specialty.label}
|
|
173
|
+
</span>
|
|
174
|
+
<span
|
|
175
|
+
className={cn(
|
|
176
|
+
"text-sm",
|
|
177
|
+
isSelected ? "text-ews-primary/70" : "text-gray-500"
|
|
178
|
+
)}
|
|
179
|
+
>
|
|
180
|
+
{specialty.code}
|
|
181
|
+
</span>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
)}
|
|
185
|
+
/>
|
|
186
|
+
|
|
187
|
+
{/* Selected count display */}
|
|
188
|
+
{showSelectedCount && selectedSpecialties.length > 0 && (
|
|
189
|
+
<div className="flex items-center justify-between text-sm text-gray-600">
|
|
190
|
+
<span>
|
|
191
|
+
{selectedSpecialties.length} specialty
|
|
192
|
+
{selectedSpecialties.length !== 1 ? "ies" : ""} selected
|
|
193
|
+
</span>
|
|
194
|
+
{maxSelections && (
|
|
195
|
+
<span className="text-gray-400">
|
|
196
|
+
{selectedSpecialties.length}/{maxSelections}
|
|
197
|
+
</span>
|
|
198
|
+
)}
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
};
|
package/src/styles/index.css
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/* EWS Global Design System - Base Styles */
|
|
2
|
+
@tailwind base;
|
|
3
|
+
@tailwind components;
|
|
4
|
+
@tailwind utilities;
|
|
2
5
|
|
|
3
6
|
:root {
|
|
4
7
|
/* Colors */
|
|
5
|
-
--ews-primary: #
|
|
6
|
-
--ews-primary-hover: #
|
|
7
|
-
--ews-primary-light: #
|
|
8
|
-
--ews-secondary: #
|
|
9
|
-
--ews-secondary-hover: #
|
|
8
|
+
--ews-primary: #21596c;
|
|
9
|
+
--ews-primary-hover: #1a4756;
|
|
10
|
+
--ews-primary-light: #c0d0d4;
|
|
11
|
+
--ews-secondary: #3ba1a1;
|
|
12
|
+
--ews-secondary-hover: #308181;
|
|
10
13
|
--ews-success: #059669;
|
|
11
14
|
--ews-success-hover: #047857;
|
|
12
15
|
--ews-warning: #d97706;
|
package/src/utils/index.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { clsx, type ClassValue } from "clsx";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Default currency for price formatting
|
|
5
|
+
*/
|
|
6
|
+
export const CURRENCY = "XOF";
|
|
7
|
+
|
|
3
8
|
/**
|
|
4
9
|
* Utility function to merge class names
|
|
5
10
|
* @param inputs - Class values to merge
|
|
@@ -12,10 +17,10 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
12
17
|
/**
|
|
13
18
|
* Utility function to format currency
|
|
14
19
|
* @param amount - Amount to format
|
|
15
|
-
* @param currency - Currency code (default:
|
|
20
|
+
* @param currency - Currency code (default: CURRENCY constant)
|
|
16
21
|
* @returns Formatted currency string
|
|
17
22
|
*/
|
|
18
|
-
export function formatCurrency(amount: number, currency =
|
|
23
|
+
export function formatCurrency(amount: number, currency = CURRENCY): string {
|
|
19
24
|
return new Intl.NumberFormat("en-US", {
|
|
20
25
|
style: "currency",
|
|
21
26
|
currency,
|