@ews-admin/global-design-system 1.1.14 → 1.1.16

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 (44) hide show
  1. package/README.md +4 -0
  2. package/dist/components/Input/Input.d.ts +22 -0
  3. package/dist/components/Input/Input.d.ts.map +1 -1
  4. package/dist/components/Input/index.d.ts +1 -1
  5. package/dist/components/Input/index.d.ts.map +1 -1
  6. package/dist/components/Logo/Logo.d.ts +3 -27
  7. package/dist/components/Logo/Logo.d.ts.map +1 -1
  8. package/dist/components/Logo/Logo.types.d.ts +41 -0
  9. package/dist/components/Logo/Logo.types.d.ts.map +1 -0
  10. package/dist/components/Logo/index.d.ts +1 -1
  11. package/dist/components/Logo/index.d.ts.map +1 -1
  12. package/dist/components/Logo/logoAssets.d.ts +1 -0
  13. package/dist/components/Logo/logoAssets.d.ts.map +1 -0
  14. package/dist/components/SearchAutocomplete/SearchAutocomplete.d.ts +1 -1
  15. package/dist/components/SearchAutocomplete/SearchAutocomplete.d.ts.map +1 -1
  16. package/dist/components/Select/Select.d.ts +3 -3
  17. package/dist/components/Select/Select.d.ts.map +1 -1
  18. package/dist/hooks/useSelectField.d.ts +4 -4
  19. package/dist/hooks/useSelectField.d.ts.map +1 -1
  20. package/dist/icons/Icon.d.ts +1 -1
  21. package/dist/icons/Icon.d.ts.map +1 -1
  22. package/dist/index.css +2 -2
  23. package/dist/index.d.ts +52 -14
  24. package/dist/index.esm.css +2 -2
  25. package/dist/index.esm.js +77 -23
  26. package/dist/index.esm.js.map +1 -1
  27. package/dist/index.js +76 -22
  28. package/dist/index.js.map +1 -1
  29. package/dist/styles/theme-variables.css +62 -0
  30. package/dist/utils/index.d.ts +2 -2
  31. package/dist/utils/index.d.ts.map +1 -1
  32. package/package.json +1 -1
  33. package/src/components/Input/Input.tsx +112 -3
  34. package/src/components/Input/index.ts +1 -1
  35. package/src/components/Logo/Logo.tsx +65 -45
  36. package/src/components/Logo/Logo.types.ts +42 -0
  37. package/src/components/Logo/index.ts +1 -1
  38. package/src/components/SearchAutocomplete/SearchAutocomplete.tsx +1 -1
  39. package/src/components/Select/Select.tsx +21 -8
  40. package/src/hooks/useSelectField.ts +7 -2
  41. package/src/icons/Icon.tsx +1 -1
  42. package/src/styles/index.css +0 -32
  43. package/src/utils/index.ts +5 -3
  44. package/tailwind.preset.js +23 -23
package/dist/index.js CHANGED
@@ -75,13 +75,15 @@ const formatNumeric = (value) => {
75
75
  };
76
76
  /**
77
77
  * Utility function to validate phone numbers
78
- * Validates phone numbers with 1-15 digits, starting with a non-zero digit
78
+ * Validates phone numbers with 1-17 digits, optionally starting with + symbol
79
79
  * @param value - Phone number string to validate
80
80
  * @returns Boolean indicating if the phone number is valid
81
81
  */
82
82
  function isValidPhoneNumber(value) {
83
83
  const trimmedValue = value.trim();
84
- const phoneRegex = /^[0-9]\d{1,17}$/;
84
+ // Allow + at the beginning, followed by 1-17 digits
85
+ // Or just 1-17 digits without +
86
+ const phoneRegex = /^(\+\d{1,17}|\d{1,17})$/;
85
87
  return phoneRegex.test(trimmedValue);
86
88
  }
87
89
 
@@ -455,7 +457,7 @@ const UserIcon = ({ size = 24, color = "currentColor", className = "", ...props
455
457
  return jsxRuntime.jsx(User, { size: size, color: color, className: className, ...props });
456
458
  };
457
459
 
458
- const Input = React.forwardRef(({ className, variant = "default", size = "md", label, helperText, error, leftIcon, rightIcon, fullWidth = false, showPasswordToggle = false, required = false, id, type = "text", ...props }, ref) => {
460
+ const Input = React.forwardRef(({ className, variant = "default", size = "md", label, helperText, error, leftIcon, rightIcon, fullWidth = false, showPasswordToggle = false, required = false, countryCodeSelect, id, type = "text", ...props }, ref) => {
459
461
  const inputId = id || `input-${Math.random().toString(36).substr(2, 9)}`;
460
462
  const hasError = Boolean(error);
461
463
  const actualVariant = hasError ? "error" : variant;
@@ -464,6 +466,20 @@ const Input = React.forwardRef(({ className, variant = "default", size = "md", l
464
466
  const isPasswordInput = type === "password";
465
467
  const shouldShowPasswordToggle = showPasswordToggle && isPasswordInput;
466
468
  const actualType = isPasswordInput && showPassword ? "text" : type;
469
+ // Country code dropdown state
470
+ const [isDropdownOpen, setIsDropdownOpen] = React.useState(false);
471
+ const dropdownRef = React.useRef(null);
472
+ // Close dropdown when clicking outside
473
+ React.useEffect(() => {
474
+ const handleClickOutside = (event) => {
475
+ if (dropdownRef.current &&
476
+ !dropdownRef.current.contains(event.target)) {
477
+ setIsDropdownOpen(false);
478
+ }
479
+ };
480
+ document.addEventListener("mousedown", handleClickOutside);
481
+ return () => document.removeEventListener("mousedown", handleClickOutside);
482
+ }, []);
467
483
  const baseStyles = "block w-full rounded-md border transition-colors focus:outline-none focus:ring-2 focus:ring-offset-0 hover:border-ews-primary";
468
484
  const variants = {
469
485
  default: "border-ews-gray-300 focus:border-ews-primary focus:ring-ews-primary",
@@ -494,7 +510,13 @@ const Input = React.forwardRef(({ className, variant = "default", size = "md", l
494
510
  }
495
511
  }
496
512
  // Default rendering for non-checkbox inputs
497
- return (jsxRuntime.jsxs("div", { className: cn("space-y-1", fullWidth ? "w-full" : "w-auto"), children: [label && (jsxRuntime.jsxs("label", { htmlFor: inputId, className: "block text-sm font-medium text-ews-gray-700", children: [label, required && jsxRuntime.jsx("span", { className: "ml-1 text-ews-error", children: "*" })] })), jsxRuntime.jsxs("div", { className: "relative", children: [leftIcon && (jsxRuntime.jsx("div", { className: "flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none", children: jsxRuntime.jsx("span", { className: cn("text-ews-gray-400", iconSizes[size]), children: leftIcon }) })), jsxRuntime.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 && (jsxRuntime.jsx("div", { className: "flex absolute inset-y-0 right-0 items-center pr-3 pointer-events-none", children: jsxRuntime.jsx("span", { className: cn("text-ews-gray-400", iconSizes[size]), children: rightIcon }) })), shouldShowPasswordToggle && (jsxRuntime.jsx("button", { type: "button", className: "flex absolute inset-y-0 right-0 items-center pr-3", onClick: () => setShowPassword(!showPassword), tabIndex: -1, children: jsxRuntime.jsx("span", { className: cn("transition-colors text-ews-gray-400 hover:text-ews-gray-600", iconSizes[size]), children: showPassword ? jsxRuntime.jsx(EyeOff, { size: 16 }) : jsxRuntime.jsx(Eye, { size: 16 }) }) }))] }), (error || helperText) && (jsxRuntime.jsx("p", { className: cn("text-sm", error ? "text-ews-error" : "text-ews-gray-500"), children: error || helperText }))] }));
513
+ return (jsxRuntime.jsxs("div", { className: cn("space-y-1", fullWidth ? "w-full" : "w-auto"), children: [label && (jsxRuntime.jsxs("label", { htmlFor: inputId, className: "block text-sm font-medium text-ews-gray-700", children: [label, required && jsxRuntime.jsx("span", { className: "ml-1 text-ews-error", children: "*" })] })), jsxRuntime.jsxs("div", { className: "relative", children: [countryCodeSelect && (jsxRuntime.jsx("div", { className: "absolute inset-y-0 left-0 flex items-center pl-1 z-10", ref: dropdownRef, children: jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsxs("button", { type: "button", onClick: () => setIsDropdownOpen(!isDropdownOpen), className: cn("flex items-center gap-1 px-2 py-1 rounded text-ews-gray-700", "hover:bg-ews-gray-50 transition-colors cursor-pointer", "focus:outline-none focus:ring-2 focus:ring-ews-primary focus:ring-offset-0", "border-0 bg-transparent text-base"), children: [jsxRuntime.jsx("span", { className: "font-medium", children: countryCodeSelect.value }), jsxRuntime.jsx(ChevronDown, { className: cn("w-4 h-4 text-ews-gray-400 transition-transform", isDropdownOpen && "rotate-180") })] }), isDropdownOpen && (jsxRuntime.jsx("div", { className: cn("absolute top-full left-0 mt-1 bg-white rounded-md border shadow-lg", "border-ews-gray-300 min-w-[120px] z-50"), style: { maxHeight: "200px", overflowY: "auto" }, children: countryCodeSelect.options.map((item) => {
514
+ const isSelected = item.code === countryCodeSelect.value;
515
+ return (jsxRuntime.jsxs("div", { onClick: () => {
516
+ countryCodeSelect.onChange(item.code);
517
+ setIsDropdownOpen(false);
518
+ }, className: cn("px-3 py-2 text-sm cursor-pointer transition-colors", isSelected && "bg-ews-primary text-white", !isSelected && "hover:bg-ews-gray-50"), children: [jsxRuntime.jsx("span", { className: "font-medium", children: item.code }), item.country && (jsxRuntime.jsx("span", { className: cn("ml-2 text-xs", isSelected ? "text-white/80" : "text-ews-gray-500"), children: item.country }))] }, item.code));
519
+ }) }))] }) })), leftIcon && !countryCodeSelect && (jsxRuntime.jsx("div", { className: "flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none", children: jsxRuntime.jsx("span", { className: cn("text-ews-gray-400", iconSizes[size]), children: leftIcon }) })), jsxRuntime.jsx("input", { id: inputId, type: actualType, className: cn(baseStyles, variants[actualVariant], sizes[size], countryCodeSelect && "pl-24", leftIcon && !countryCodeSelect && "pl-10", (rightIcon || shouldShowPasswordToggle) && "pr-10", className), ref: ref, ...props }), rightIcon && !shouldShowPasswordToggle && (jsxRuntime.jsx("div", { className: "flex absolute inset-y-0 right-0 items-center pr-3 pointer-events-none", children: jsxRuntime.jsx("span", { className: cn("text-ews-gray-400", iconSizes[size]), children: rightIcon }) })), shouldShowPasswordToggle && (jsxRuntime.jsx("button", { type: "button", className: "flex absolute inset-y-0 right-0 items-center pr-3", onClick: () => setShowPassword(!showPassword), tabIndex: -1, children: jsxRuntime.jsx("span", { className: cn("transition-colors text-ews-gray-400 hover:text-ews-gray-600", iconSizes[size]), children: showPassword ? jsxRuntime.jsx(EyeOff, { size: 16 }) : jsxRuntime.jsx(Eye, { size: 16 }) }) }))] }), (error || helperText) && (jsxRuntime.jsx("p", { className: cn("text-sm", error ? "text-ews-error" : "text-ews-gray-500"), children: error || helperText }))] }));
498
520
  });
499
521
  Input.displayName = "Input";
500
522
 
@@ -517,7 +539,7 @@ const Select = React.forwardRef(({ options = [], value, onChange, placeholder =
517
539
  ? options.filter((option) => option.label.toLowerCase().includes(searchTerm.toLowerCase()))
518
540
  : options;
519
541
  // Calculate dropdown position based on available space
520
- const calculateDropdownPosition = () => {
542
+ const calculateDropdownPosition = React.useCallback(() => {
521
543
  if (!containerRef.current)
522
544
  return;
523
545
  const containerRect = containerRef.current.getBoundingClientRect();
@@ -548,7 +570,7 @@ const Select = React.forwardRef(({ options = [], value, onChange, placeholder =
548
570
  else {
549
571
  setDropdownPosition("bottom");
550
572
  }
551
- };
573
+ }, [filteredOptions.length, searchable, maxHeight]);
552
574
  // Alternative calculation using actual dropdown element when available
553
575
  const calculateDropdownPositionWithElement = () => {
554
576
  if (!containerRef.current || !dropdownRef.current)
@@ -598,7 +620,13 @@ const Select = React.forwardRef(({ options = [], value, onChange, placeholder =
598
620
  calculateDropdownPositionWithElement();
599
621
  });
600
622
  }
601
- }, [isOpen, filteredOptions.length, searchable, maxHeight]);
623
+ }, [
624
+ isOpen,
625
+ filteredOptions.length,
626
+ searchable,
627
+ maxHeight,
628
+ calculateDropdownPosition,
629
+ ]);
602
630
  // Recalculate position on window resize
603
631
  React.useEffect(() => {
604
632
  const handleResize = () => {
@@ -612,7 +640,7 @@ const Select = React.forwardRef(({ options = [], value, onChange, placeholder =
612
640
  window.removeEventListener("resize", handleResize);
613
641
  window.removeEventListener("scroll", handleResize);
614
642
  };
615
- }, [isOpen]);
643
+ }, [isOpen, calculateDropdownPosition]);
616
644
  // Handle keyboard navigation
617
645
  const handleKeyDown = (event) => {
618
646
  if (disabled)
@@ -1721,7 +1749,7 @@ const DropdownMultiSelect = ({ options, name, control, placeholder = "Select opt
1721
1749
  }), error && jsxRuntime.jsx("p", { className: "mt-1 text-sm text-ews-error", children: error })] }));
1722
1750
  };
1723
1751
 
1724
- const Logo = ({ size = "md", showTagline: _showTagline = true, iconOnly = false, variant = "normal", className, onClick, }) => {
1752
+ const Logo = ({ size = "md", showTagline: _showTagline = true, iconOnly = false, variant = "normal", className, onClick, customSrc, alt = "MEDECINE 360 Logo", clickable = false, }) => {
1725
1753
  const sizes = {
1726
1754
  sm: "h-8",
1727
1755
  md: "h-12",
@@ -1734,21 +1762,47 @@ const Logo = ({ size = "md", showTagline: _showTagline = true, iconOnly = false,
1734
1762
  lg: "h-12 w-12",
1735
1763
  xl: "h-16 w-16",
1736
1764
  };
1737
- // Get the appropriate logo image based on variant
1738
- // For iconOnly, always use favicon.ico
1739
- const logoSrc = iconOnly
1740
- ? "/favicon.ico"
1741
- : variant === "white"
1742
- ? "/image/logoWhite.png"
1743
- : variant === "fullWhite"
1744
- ? "/image/logoFullWhite.png"
1745
- : variant === "favicon"
1746
- ? "/favicon.ico"
1747
- : "/image/logo.png";
1765
+ // Get the appropriate logo image based on variant or custom source
1766
+ // For iconOnly, always use favicon.ico unless customSrc is provided
1767
+ const logoSrc = customSrc ||
1768
+ (iconOnly
1769
+ ? "/favicon.ico"
1770
+ : variant === "white"
1771
+ ? "/image/logoWhite.png"
1772
+ : variant === "fullWhite"
1773
+ ? "/image/logoFullWhite.png"
1774
+ : variant === "favicon"
1775
+ ? "/favicon.ico"
1776
+ : "/image/logo.png");
1777
+ const isClickable = clickable || !!onClick;
1748
1778
  if (iconOnly) {
1749
- return (jsxRuntime.jsx("div", { className: cn("flex items-center justify-center", iconSizes[size], className), onClick: onClick, role: onClick ? "button" : undefined, tabIndex: onClick ? 0 : undefined, children: jsxRuntime.jsx("img", { src: logoSrc, alt: "MEDECINE 360 Logo", className: "w-full h-full object-contain" }) }));
1779
+ return (jsxRuntime.jsx("div", { className: cn("flex items-center justify-center", iconSizes[size], isClickable && "cursor-pointer", className), onClick: onClick, role: isClickable ? "button" : undefined, tabIndex: isClickable ? 0 : undefined, onKeyDown: isClickable
1780
+ ? (e) => {
1781
+ if (e.key === "Enter" || e.key === " ") {
1782
+ e.preventDefault();
1783
+ onClick?.();
1784
+ }
1785
+ }
1786
+ : undefined, children: jsxRuntime.jsx("img", { src: logoSrc, alt: alt, className: "object-contain w-full h-full", onError: (e) => {
1787
+ // Fallback to favicon if image fails to load
1788
+ if (logoSrc !== "/favicon.ico") {
1789
+ e.target.src = "/favicon.ico";
1790
+ }
1791
+ } }) }));
1750
1792
  }
1751
- return (jsxRuntime.jsx("div", { className: cn("flex items-center", sizes[size], className), onClick: onClick, role: onClick ? "button" : undefined, tabIndex: onClick ? 0 : undefined, children: jsxRuntime.jsx("img", { src: logoSrc, alt: "MEDECINE 360 Logo", className: "h-full w-auto object-contain" }) }));
1793
+ return (jsxRuntime.jsx("div", { className: cn("flex items-center", sizes[size], isClickable && "cursor-pointer", className), onClick: onClick, role: isClickable ? "button" : undefined, tabIndex: isClickable ? 0 : undefined, onKeyDown: isClickable
1794
+ ? (e) => {
1795
+ if (e.key === "Enter" || e.key === " ") {
1796
+ e.preventDefault();
1797
+ onClick?.();
1798
+ }
1799
+ }
1800
+ : undefined, children: jsxRuntime.jsx("img", { src: logoSrc, alt: alt, className: "object-contain w-auto h-full", onError: (e) => {
1801
+ // Fallback to favicon if image fails to load
1802
+ if (logoSrc !== "/favicon.ico") {
1803
+ e.target.src = "/favicon.ico";
1804
+ }
1805
+ } }) }));
1752
1806
  };
1753
1807
 
1754
1808
  const PROMED_THEME = {