@ews-admin/global-design-system 1.1.26 → 1.1.27

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/dist/index.js CHANGED
@@ -60,6 +60,76 @@ function createEnvConfig(env, overrides) {
60
60
  };
61
61
  }
62
62
 
63
+ /**
64
+ * Available country codes supported by the platform
65
+ */
66
+ const COUNTRY_CODES = [
67
+ { code: "+221", country: "SN" }, // Senegal
68
+ { code: "+235", country: "TD" }, // Chad
69
+ ];
70
+ /**
71
+ * Determines the default country code based on phone number prefix or country ISO code.
72
+ * Falls back to "+221" (Senegal) when no match is found.
73
+ */
74
+ function getDefaultCountryCode(phoneNumber, country) {
75
+ if (phoneNumber) {
76
+ const match = COUNTRY_CODES.find(({ code }) => phoneNumber.startsWith(code));
77
+ if (match)
78
+ return match.code;
79
+ }
80
+ if (country) {
81
+ const match = COUNTRY_CODES.find(({ country: c }) => c.toUpperCase() === country.toUpperCase());
82
+ if (match)
83
+ return match.code;
84
+ }
85
+ return "+221";
86
+ }
87
+ /**
88
+ * Extracts the country code prefix from a phone number and returns both
89
+ * the detected country code and the local number without the prefix.
90
+ *
91
+ * @example
92
+ * extractCountryCodeFromPhoneNumber("+22177123456") // { countryCode: "+221", cleanedPhoneNumber: "77123456" }
93
+ * extractCountryCodeFromPhoneNumber("77123456") // { countryCode: "+221", cleanedPhoneNumber: "77123456" }
94
+ */
95
+ function extractCountryCodeFromPhoneNumber(phoneNumber, country) {
96
+ const matched = COUNTRY_CODES.find(({ code }) => phoneNumber.startsWith(code));
97
+ if (matched) {
98
+ const cleaned = phoneNumber
99
+ .replace(new RegExp(`^\\${matched.code}\\s*`), "")
100
+ .trim();
101
+ return { countryCode: matched.code, cleanedPhoneNumber: cleaned };
102
+ }
103
+ return {
104
+ countryCode: getDefaultCountryCode(phoneNumber, country),
105
+ cleanedPhoneNumber: phoneNumber.trim(),
106
+ };
107
+ }
108
+ /**
109
+ * Formats a phone number by prepending the country code if not already present.
110
+ * If the number already starts with a known country code it is returned as-is.
111
+ * Leading zeros, spaces and dashes are stripped from the local part before formatting.
112
+ *
113
+ * @example
114
+ * formatPhoneNumberWithCountryCode("77123456", "+221") // "+22177123456"
115
+ * formatPhoneNumberWithCountryCode("+22177123456", "+221") // "+22177123456" (unchanged)
116
+ * formatPhoneNumberWithCountryCode("077123456", "+221") // "+22177123456" (leading 0 stripped)
117
+ */
118
+ function formatPhoneNumberWithCountryCode(phoneNumber, countryCode = "+221") {
119
+ const trimmed = phoneNumber?.trim() || "";
120
+ if (!trimmed)
121
+ return "";
122
+ // Already has a known country code — return as-is
123
+ if (COUNTRY_CODES.some(({ code }) => trimmed.startsWith(code))) {
124
+ return trimmed;
125
+ }
126
+ // Strip leading zeros, spaces, dashes and + signs from the local part
127
+ const cleaned = trimmed.replace(/^[\s+0-]*/, "").trim();
128
+ if (!cleaned)
129
+ return "";
130
+ return `${countryCode}${cleaned}`;
131
+ }
132
+
63
133
  /**
64
134
  * Default currency for price formatting
65
135
  */
@@ -33872,6 +33942,91 @@ const DropdownMultiSelect = ({ options, name, control, placeholder = "Select opt
33872
33942
  }), error && jsxRuntime.jsx("p", { className: "mt-1 text-sm text-ews-error", children: error })] }));
33873
33943
  };
33874
33944
 
33945
+ const DEFAULT_COUNTRY_CODE = "+221";
33946
+ /**
33947
+ * Phone number input with integrated country code selector.
33948
+ *
33949
+ * The component stores the **full** phone number (e.g. "+22177123456") as its
33950
+ * form value so that the country code prefix is always preserved on save.
33951
+ * The text field displays only the local part ("77123456") for a clean UX.
33952
+ *
33953
+ * Works with React Hook Form via `Controller` — no extra setup needed.
33954
+ */
33955
+ const PhoneInput = React.forwardRef(({ countryCode, onCountryCodeChange, onChange, value, ...props }, ref) => {
33956
+ // internalCode tracks user's explicit dropdown selection when no countryCode
33957
+ // prop is provided and value doesn't carry a recognised prefix.
33958
+ const [internalCode, setInternalCode] = React.useState(DEFAULT_COUNTRY_CODE);
33959
+ const nativeRef = React.useRef(null);
33960
+ // Derive the country code directly from value on every render — no state
33961
+ // sync needed. Falls back to internalCode when value is empty or unrecognised.
33962
+ const codeFromValue = value
33963
+ ? COUNTRY_CODES.find(({ code }) => value.startsWith(code))?.code ?? null
33964
+ : null;
33965
+ const activeCode = countryCode ?? codeFromValue ?? internalCode;
33966
+ const handleCodeChange = (newCode) => {
33967
+ // Update internal or external country code state.
33968
+ if (onCountryCodeChange) {
33969
+ onCountryCodeChange(newCode);
33970
+ }
33971
+ else {
33972
+ setInternalCode(newCode);
33973
+ }
33974
+ // Re-emit the current phone value with the new country code so that
33975
+ // RHF (Controller) and any other onChange listeners stay in sync even
33976
+ // when the user changes the dropdown without retyping the number.
33977
+ const el = nativeRef.current;
33978
+ if (el && onChange) {
33979
+ const numeric = formatNumeric(el.value);
33980
+ const fullValue = numeric ? `${newCode}${numeric}` : "";
33981
+ // Pass the value string directly — RHF's Controller field.onChange
33982
+ // accepts raw values, avoiding unreliable fake-event parsing.
33983
+ onChange(fullValue);
33984
+ }
33985
+ };
33986
+ const toLocalPart = (v) => extractCountryCodeFromPhoneNumber(v || "").cleanedPhoneNumber;
33987
+ // When value prop changes (Controller / direct value prop usage),
33988
+ // update the DOM input directly so the display stays in sync.
33989
+ React.useEffect(() => {
33990
+ const el = nativeRef.current;
33991
+ if (el && value !== undefined) {
33992
+ el.value = toLocalPart(value);
33993
+ }
33994
+ }, [value]);
33995
+ const handleChange = (e) => {
33996
+ // Enforce digits-only in the displayed field.
33997
+ const numeric = formatNumeric(e.target.value);
33998
+ if (e.target.value !== numeric) {
33999
+ e.target.value = numeric;
34000
+ }
34001
+ // Always emit the full phone number (country code + local part) to the form.
34002
+ const fullValue = numeric ? `${activeCode}${numeric}` : "";
34003
+ const syntheticEvent = {
34004
+ ...e,
34005
+ target: { ...e.target, value: fullValue, name: e.target.name },
34006
+ };
34007
+ onChange?.(syntheticEvent);
34008
+ };
34009
+ // Merge the forwarded ref (used by RHF register) with our internal ref.
34010
+ const composedRef = React.useCallback((node) => {
34011
+ nativeRef.current = node;
34012
+ if (typeof ref === "function")
34013
+ ref(node);
34014
+ else if (ref)
34015
+ ref.current = node;
34016
+ }, [ref]);
34017
+ // Initial display: strip country code so only the local part is shown.
34018
+ const initialDisplay = value !== undefined ? toLocalPart(value) : undefined;
34019
+ return (jsxRuntime.jsx(Input, { ref: composedRef, type: "tel",
34020
+ // Use defaultValue (uncontrolled) so React never resets what the user types.
34021
+ // Updates from the value prop are applied imperatively via the useEffect above.
34022
+ defaultValue: initialDisplay, countryCodeSelect: {
34023
+ options: COUNTRY_CODES,
34024
+ value: activeCode,
34025
+ onChange: handleCodeChange,
34026
+ }, onChange: handleChange, ...props }));
34027
+ });
34028
+ PhoneInput.displayName = "PhoneInput";
34029
+
33875
34030
  const Logo = ({ size = "md", showTagline: _showTagline = true, iconOnly = false, variant = "normal", className, onClick, customSrc, alt = "MEDECINE 360 Logo", clickable = false, }) => {
33876
34031
  const sizes = {
33877
34032
  sm: "h-8",
@@ -34661,6 +34816,7 @@ exports.BusFront = BusFront;
34661
34816
  exports.BusFrontIcon = BusFront;
34662
34817
  exports.BusIcon = Bus;
34663
34818
  exports.Button = Button;
34819
+ exports.COUNTRY_CODES = COUNTRY_CODES;
34664
34820
  exports.Cable = Cable;
34665
34821
  exports.CableCar = CableCar;
34666
34822
  exports.CableCarIcon = CableCar;
@@ -38486,6 +38642,7 @@ exports.PhoneForwardedIcon = PhoneForwarded;
38486
38642
  exports.PhoneIcon = Phone;
38487
38643
  exports.PhoneIncoming = PhoneIncoming;
38488
38644
  exports.PhoneIncomingIcon = PhoneIncoming;
38645
+ exports.PhoneInput = PhoneInput;
38489
38646
  exports.PhoneMissed = PhoneMissed;
38490
38647
  exports.PhoneMissedIcon = PhoneMissed;
38491
38648
  exports.PhoneOff = PhoneOff;
@@ -39679,10 +39836,13 @@ exports.cn = cn;
39679
39836
  exports.createEnvConfig = createEnvConfig;
39680
39837
  exports.createLucideIcon = createLucideIcon;
39681
39838
  exports.debounce = debounce;
39839
+ exports.extractCountryCodeFromPhoneNumber = extractCountryCodeFromPhoneNumber;
39682
39840
  exports.formatCurrency = formatCurrency;
39683
39841
  exports.formatDate = formatDate;
39684
39842
  exports.formatNumeric = formatNumeric;
39843
+ exports.formatPhoneNumberWithCountryCode = formatPhoneNumberWithCountryCode;
39685
39844
  exports.generateId = generateId;
39845
+ exports.getDefaultCountryCode = getDefaultCountryCode;
39686
39846
  exports.icons = index;
39687
39847
  exports.isValidPhoneNumber = isValidPhoneNumber;
39688
39848
  exports.useDebounce = useDebounce;