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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +50 -1
  2. package/dist/components/Button/Button.d.ts.map +1 -1
  3. package/dist/components/Input/Input.d.ts +4 -0
  4. package/dist/components/Input/Input.d.ts.map +1 -1
  5. package/dist/components/Logo/Logo.d.ts +29 -0
  6. package/dist/components/Logo/Logo.d.ts.map +1 -0
  7. package/dist/components/Logo/index.d.ts +3 -0
  8. package/dist/components/Logo/index.d.ts.map +1 -0
  9. package/dist/components/Modal/Modal.d.ts +72 -0
  10. package/dist/components/Modal/Modal.d.ts.map +1 -0
  11. package/dist/components/Modal/index.d.ts +3 -0
  12. package/dist/components/Modal/index.d.ts.map +1 -0
  13. package/dist/components/MultiSearchAutocomplete/MultiSearchAutocomplete.d.ts +25 -0
  14. package/dist/components/MultiSearchAutocomplete/MultiSearchAutocomplete.d.ts.map +1 -0
  15. package/dist/components/MultiSearchAutocomplete/index.d.ts +2 -0
  16. package/dist/components/MultiSearchAutocomplete/index.d.ts.map +1 -0
  17. package/dist/components/SearchAutocomplete/SearchAutocomplete.d.ts +22 -0
  18. package/dist/components/SearchAutocomplete/SearchAutocomplete.d.ts.map +1 -0
  19. package/dist/components/SearchAutocomplete/index.d.ts +3 -0
  20. package/dist/components/SearchAutocomplete/index.d.ts.map +1 -0
  21. package/dist/hooks/index.d.ts +2 -0
  22. package/dist/hooks/index.d.ts.map +1 -0
  23. package/dist/hooks/useDebounce.d.ts +15 -0
  24. package/dist/hooks/useDebounce.d.ts.map +1 -0
  25. package/dist/icons/Icon.d.ts +5 -4
  26. package/dist/icons/Icon.d.ts.map +1 -1
  27. package/dist/icons/index.d.ts +1 -3
  28. package/dist/icons/index.d.ts.map +1 -1
  29. package/dist/index.css +3 -1
  30. package/dist/index.d.ts +185 -12
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.esm.css +3 -1
  33. package/dist/index.esm.js +763 -29
  34. package/dist/index.esm.js.map +1 -1
  35. package/dist/index.js +768 -27
  36. package/dist/index.js.map +1 -1
  37. package/dist/molecules/SpecialtySearchAutocomplete/SpecialtySearchAutocomplete.d.ts +18 -0
  38. package/dist/molecules/SpecialtySearchAutocomplete/SpecialtySearchAutocomplete.d.ts.map +1 -0
  39. package/dist/molecules/SpecialtySearchAutocomplete/index.d.ts +3 -0
  40. package/dist/molecules/SpecialtySearchAutocomplete/index.d.ts.map +1 -0
  41. package/dist/molecules/index.d.ts +3 -0
  42. package/dist/molecules/index.d.ts.map +1 -0
  43. package/dist/utils/index.d.ts +5 -1
  44. package/dist/utils/index.d.ts.map +1 -1
  45. package/package.json +17 -2
  46. package/src/assets/favicon.svg +6 -0
  47. package/src/assets/logo.svg +17 -0
  48. package/src/components/Button/Button.tsx +22 -8
  49. package/src/components/Input/Input.tsx +42 -16
  50. package/src/components/Logo/Logo.tsx +100 -0
  51. package/src/components/Logo/index.ts +2 -0
  52. package/src/components/Modal/Modal.tsx +257 -0
  53. package/src/components/Modal/index.ts +2 -0
  54. package/src/components/MultiSearchAutocomplete/MultiSearchAutocomplete.tsx +319 -0
  55. package/src/components/MultiSearchAutocomplete/index.ts +1 -0
  56. package/src/components/SearchAutocomplete/SearchAutocomplete.tsx +315 -0
  57. package/src/components/SearchAutocomplete/index.ts +2 -0
  58. package/src/hooks/index.ts +1 -0
  59. package/src/hooks/useDebounce.ts +64 -0
  60. package/src/icons/Icon.tsx +15 -16
  61. package/src/icons/index.ts +39 -3
  62. package/src/index.ts +19 -0
  63. package/src/molecules/SpecialtySearchAutocomplete/SpecialtySearchAutocomplete.tsx +203 -0
  64. package/src/molecules/SpecialtySearchAutocomplete/index.ts +5 -0
  65. package/src/molecules/index.ts +5 -0
  66. package/src/styles/index.css +8 -5
  67. package/src/styles/tailwind.css +3 -0
  68. package/src/utils/index.ts +7 -2
@@ -1,34 +1,33 @@
1
+ import { LucideProps } from "lucide-react";
1
2
  import React from "react";
2
3
 
3
- export interface IconProps extends React.SVGProps<SVGSVGElement> {
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
- * Icon color
10
+ * The Lucide icon component to render
10
11
  */
11
- color?: string;
12
+ icon: React.ComponentType<LucideProps>;
12
13
  }
13
14
 
14
15
  const Icon = React.forwardRef<SVGSVGElement, IconProps>(
15
- ({ size = "md", color = "currentColor", className, ...props }, ref) => {
16
+ ({ size = "md", icon: IconComponent, className, ...props }, ref) => {
16
17
  const sizes = {
17
- sm: "h-4 w-4",
18
- md: "h-5 w-5",
19
- lg: "h-6 w-6",
20
- xl: "h-8 w-8",
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
- <svg
27
+ <IconComponent
25
28
  ref={ref}
26
- className={`${sizes[size]} ${className || ""}`}
27
- fill="none"
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
  );
@@ -1,5 +1,41 @@
1
- export { ArrowRight } from "./ArrowRight";
2
- export { Check } from "./Check";
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
+ };
@@ -0,0 +1,5 @@
1
+ export { SpecialtySearchAutocomplete } from "./SpecialtySearchAutocomplete";
2
+ export type {
3
+ Specialty,
4
+ SpecialtySearchAutocompleteProps,
5
+ } from "./SpecialtySearchAutocomplete";
@@ -0,0 +1,5 @@
1
+ export { SpecialtySearchAutocomplete } from "./SpecialtySearchAutocomplete";
2
+ export type {
3
+ Specialty,
4
+ SpecialtySearchAutocompleteProps,
5
+ } from "./SpecialtySearchAutocomplete";
@@ -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: #2563eb;
6
- --ews-primary-hover: #1d4ed8;
7
- --ews-primary-light: #dbeafe;
8
- --ews-secondary: #64748b;
9
- --ews-secondary-hover: #475569;
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;
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
@@ -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: 'USD')
20
+ * @param currency - Currency code (default: CURRENCY constant)
16
21
  * @returns Formatted currency string
17
22
  */
18
- export function formatCurrency(amount: number, currency = "USD"): string {
23
+ export function formatCurrency(amount: number, currency = CURRENCY): string {
19
24
  return new Intl.NumberFormat("en-US", {
20
25
  style: "currency",
21
26
  currency,