@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/components/PhoneInput/PhoneInput.d.ts +19 -0
- package/dist/components/PhoneInput/PhoneInput.d.ts.map +1 -0
- package/dist/components/PhoneInput/index.d.ts +3 -0
- package/dist/components/PhoneInput/index.d.ts.map +1 -0
- package/dist/index.d.ts +71 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +156 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +160 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/phone.d.ts +48 -0
- package/dist/utils/phone.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/PhoneInput/PhoneInput.tsx +127 -0
- package/src/components/PhoneInput/index.ts +2 -0
- package/src/index.ts +14 -1
- package/src/utils/index.ts +7 -0
- package/src/utils/phone.ts +109 -0
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;
|