@trackunit/react-form-components 0.0.226 → 0.0.228

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/index.cjs.js CHANGED
@@ -44,7 +44,7 @@ var ReactCreatableSelect__default = /*#__PURE__*/_interopDefaultLegacy(ReactCrea
44
44
  var ReactAsyncSelect__default = /*#__PURE__*/_interopDefaultLegacy(ReactAsyncSelect);
45
45
 
46
46
  var defaultTranslations = {
47
-
47
+ "phoneField.error": "Invalid phone number"
48
48
  };
49
49
 
50
50
  /** The translation namespace for this library */
@@ -78,6 +78,10 @@ const translations = {
78
78
  th: () => Promise.resolve().then(function () { return require('./translation.cjs17.js'); }),
79
79
  },
80
80
  };
81
+ /**
82
+ * Local useTranslation for this specific library
83
+ */
84
+ const useTranslation = () => i18nLibraryTranslation.useNamespaceTranslation(namespace);
81
85
  /**
82
86
  * Registers the translations for this library
83
87
  */
@@ -779,6 +783,38 @@ const getPhoneNumberWithPlus = (phoneNumber) => {
779
783
  const stringPhoneNumber = phoneNumber.toString();
780
784
  return checkIfPhoneNumberHasPlus(stringPhoneNumber) ? stringPhoneNumber : `+${stringPhoneNumber}`;
781
785
  };
786
+ /**
787
+ * Generates a flag emoji based on the given country code.
788
+ *
789
+ * @param {string} countryCode - The two-letter country code (ISO 3166-1 alpha-2).
790
+ * @returns {string} - The corresponding flag emoji for the given country code.
791
+ * @example getPhoneNumberWithPlus("DK") // "🇩🇰"
792
+ */
793
+ const countryCodeToFlagEmoji = (countryCode) => {
794
+ let code = countryCode;
795
+ if (countryCode.startsWith("+")) {
796
+ code = getCountryAbbreviation(countryCode.substring(1));
797
+ }
798
+ else if (!isNaN(Number(countryCode))) {
799
+ code = getCountryAbbreviation(countryCode);
800
+ }
801
+ return code.toUpperCase().replace(/./g, char => String.fromCodePoint(127397 + char.charCodeAt(0)));
802
+ };
803
+ /**
804
+ * Retrieves the ISO 3166-1 alpha-2 country code associated with a given international calling code.
805
+ *
806
+ * @param {string} callCode - The international calling code for a country.
807
+ * @returns {string} The abbreviation for a country or an empty string if no matching country is found.
808
+ * @example getCountryAbbreviation("45") // "DK"
809
+ * @example getCountryAbbreviation("+45") // "DK"
810
+ */
811
+ const getCountryAbbreviation = (callCode) => {
812
+ let code = callCode;
813
+ if (callCode.startsWith("+")) {
814
+ code = callCode.substring(1);
815
+ }
816
+ return parsePhoneNumberFromString.getCountries().find(c => parsePhoneNumberFromString.getCountryCallingCode(c) === code) || "";
817
+ };
782
818
 
783
819
  /**
784
820
  * A custom hook for managing phone number input state and validation.
@@ -1268,7 +1304,7 @@ const Select = (props) => {
1268
1304
  };
1269
1305
  Select.displayName = "Select";
1270
1306
 
1271
- const COUNTRY_CODES_NOT_SUPPORTED_BY_BACKEND = ["53", "98", "211", "247", "249", "383", "850", "963"];
1307
+ const COUNTRY_CODES_NOT_SUPPORTED_BY_BACKEND = ["+53", "+98", "+211", "+247", "+249", "+383", "+850", "+963"];
1272
1308
  const ADDITIONAL_COUNTRY_CODES_SUPPORTED_BY_BACKEND = [
1273
1309
  "1242",
1274
1310
  "1246",
@@ -1298,13 +1334,73 @@ const ADDITIONAL_COUNTRY_CODES_SUPPORTED_BY_BACKEND = [
1298
1334
  "882",
1299
1335
  "883",
1300
1336
  ];
1301
- const countryCodesSet = [
1302
- ...new Set(parsePhoneNumberFromString.getCountries().map(code => parsePhoneNumberFromString.getCountryCallingCode(code))),
1303
- ...ADDITIONAL_COUNTRY_CODES_SUPPORTED_BY_BACKEND,
1304
- ]
1305
- .filter(code => !COUNTRY_CODES_NOT_SUPPORTED_BY_BACKEND.includes(code))
1306
- .sort((a, b) => Number(a) - Number(b));
1307
- const countryCodes = countryCodesSet.map(code => ({ label: `+${code}`, value: `+${code}` })); // We add the + sign to the value so that the google libphonenumber-js can parse it correctly.
1337
+ const DUPLICATE_COUNTRY_CODES = [
1338
+ // +1
1339
+ "AG",
1340
+ "AI",
1341
+ "AS",
1342
+ "BB",
1343
+ "BM",
1344
+ "BS",
1345
+ "CA",
1346
+ "DM",
1347
+ "DO",
1348
+ "GD",
1349
+ "GU",
1350
+ "JM",
1351
+ "KN",
1352
+ "KY",
1353
+ "LC",
1354
+ "MP",
1355
+ "MS",
1356
+ "PR",
1357
+ "SX",
1358
+ "TC",
1359
+ "TT",
1360
+ "VC",
1361
+ "VG",
1362
+ "VI",
1363
+ // +7
1364
+ "KZ",
1365
+ // +39
1366
+ "VA",
1367
+ // +44
1368
+ "GG",
1369
+ "IM",
1370
+ "JE",
1371
+ // +47
1372
+ "SJ",
1373
+ // +61
1374
+ "CC",
1375
+ "CX",
1376
+ // 212
1377
+ "EH",
1378
+ // 262
1379
+ "RE",
1380
+ // 290
1381
+ "SH",
1382
+ // 590
1383
+ "BL",
1384
+ "MF",
1385
+ // 599
1386
+ "CW",
1387
+ ];
1388
+ const countries = parsePhoneNumberFromString.getCountries()
1389
+ .filter(code => !DUPLICATE_COUNTRY_CODES.includes(code))
1390
+ .map(code => {
1391
+ const c = parsePhoneNumberFromString.getCountryCallingCode(code);
1392
+ return {
1393
+ label: `+${c} ${countryCodeToFlagEmoji(code)}`,
1394
+ value: `+${c}`,
1395
+ };
1396
+ });
1397
+ const additionalCountries = ADDITIONAL_COUNTRY_CODES_SUPPORTED_BY_BACKEND.map(code => ({
1398
+ label: `+${code}`,
1399
+ value: `+${code}`,
1400
+ }));
1401
+ const countryCodes = [...countries, ...additionalCountries]
1402
+ .filter(code => !COUNTRY_CODES_NOT_SUPPORTED_BY_BACKEND.includes(code.value))
1403
+ .sort((a, b) => Number(a.value) - Number(b.value));
1308
1404
 
1309
1405
  /**
1310
1406
  * The CountryCodeSelect component is used to select a country code for a phone number.
@@ -1320,7 +1416,7 @@ const CountryCodeSelect = ({ excludedCountries, countryCode, isInvalid, onChange
1320
1416
  const filteredCodes = countryCodes.filter(code => {
1321
1417
  return !(excludedCountries === null || excludedCountries === void 0 ? void 0 : excludedCountries.some(excludedCode => excludedCode.value === code.value));
1322
1418
  });
1323
- return (jsxRuntime.jsx(Select, { dataTestId: dataTestId, className: "w-32", "aria-invalid": isCountryCodeMissing, options: filteredCodes.map(code => code), value: filteredCodes.find(({ value }) => value === `+${countryCode}`), placeholder: placeholder, hasError: isCountryCodeMissing || isInvalid, maxMenuHeight: 200, onChange: onChange, disabled: disabled, readOnly: readOnly, onBlur: onBlur, closeMenuOnSelect: true, menuIsOpen: readOnly ? false : undefined, isClearable: isClearable }));
1419
+ return (jsxRuntime.jsx(Select, { dataTestId: dataTestId, className: "w-36", "aria-invalid": isCountryCodeMissing, options: filteredCodes.map(code => code), value: filteredCodes.find(({ value }) => value === `+${countryCode}`), placeholder: placeholder, hasError: isCountryCodeMissing || isInvalid, maxMenuHeight: 200, onChange: onChange, disabled: disabled, readOnly: readOnly, onBlur: onBlur, closeMenuOnSelect: true, menuIsOpen: readOnly ? false : undefined, isClearable: isClearable }));
1324
1420
  };
1325
1421
 
1326
1422
  /**
@@ -1338,7 +1434,16 @@ const PhoneInput = React.forwardRef((_a, ref) => {
1338
1434
  var { dataTestId, isInvalid, disabled = false, value, defaultValue, fieldSize = "medium", disableAction = false, onChange, onBlurCountryCode, readOnly, countryCodePlaceholder, onChangeCountryCode, onBlur, onFieldsBlur, onChangeValue, isCountryCodeClearable } = _a, rest = __rest(_a, ["dataTestId", "isInvalid", "disabled", "value", "defaultValue", "fieldSize", "disableAction", "onChange", "onBlurCountryCode", "readOnly", "countryCodePlaceholder", "onChangeCountryCode", "onBlur", "onFieldsBlur", "onChangeValue", "isCountryCodeClearable"]);
1339
1435
  const DEFAULT_COUNTRY_CODE = "+45";
1340
1436
  const { getPhoneNumber } = usePhoneInput();
1341
- const countryCodePlaceholderValue = !readOnly ? countryCodePlaceholder || DEFAULT_COUNTRY_CODE : "";
1437
+ const [placeholder, setPlaceholder] = React.useState();
1438
+ React.useEffect(() => {
1439
+ if (readOnly) {
1440
+ setPlaceholder("");
1441
+ }
1442
+ else {
1443
+ const countryCode = countryCodePlaceholder || DEFAULT_COUNTRY_CODE;
1444
+ setPlaceholder(`${countryCode} ${countryCodeToFlagEmoji(countryCode)}`);
1445
+ }
1446
+ }, [countryCodePlaceholder, readOnly]);
1342
1447
  const safePhoneNumber = getPhoneNumberWithPlus(value || "");
1343
1448
  const number = parsePhoneNumberFromString__default["default"](safePhoneNumber, { defaultCountry: "DK" });
1344
1449
  const valueHasOnlyCountryCode = (number === null || number === void 0 ? void 0 : number.isPossible) === undefined && typeof value === "string" && value[0] === "+" && value.length < 5;
@@ -1387,7 +1492,7 @@ const PhoneInput = React.forwardRef((_a, ref) => {
1387
1492
  }, onChange: e => {
1388
1493
  var _a;
1389
1494
  onCodeChangeHandler((_a = e === null || e === void 0 ? void 0 : e.value) !== null && _a !== void 0 ? _a : "");
1390
- }, disabled: disabled, readOnly: readOnly, isInvalid: isInvalid, placeholder: countryCodePlaceholderValue, isClearable: isCountryCodeClearable }), jsxRuntime.jsx(BaseInput, Object.assign({ id: "phoneInput-number", dataTestId: dataTestId && `${dataTestId}-phoneNumberInput`, fieldSize: fieldSize, type: "tel", value: phone, disabled: disabled, readOnly: readOnly, isInvalid: isInvalid, onChange: e => {
1495
+ }, disabled: disabled, readOnly: readOnly, isInvalid: isInvalid, placeholder: placeholder, isClearable: isCountryCodeClearable }), jsxRuntime.jsx(BaseInput, Object.assign({ id: "phoneInput-number", dataTestId: dataTestId && `${dataTestId}-phoneNumberInput`, fieldSize: fieldSize, type: "tel", value: phone, disabled: disabled, readOnly: readOnly, isInvalid: isInvalid, onChange: e => {
1391
1496
  onPhoneChangeHandler(e.target.value);
1392
1497
  }, onBlur: () => {
1393
1498
  onBlurHandler();
@@ -1413,8 +1518,24 @@ const PhoneInput = React.forwardRef((_a, ref) => {
1413
1518
  const PhoneField = React.forwardRef((_a, ref) => {
1414
1519
  var { label, id, tip, helpText, isInvalid, errorMessage, value, helpAddon, className, defaultValue, dataTestId, onChangeValue, onFieldsBlur } = _a, rest = __rest(_a, ["label", "id", "tip", "helpText", "isInvalid", "errorMessage", "value", "helpAddon", "className", "defaultValue", "dataTestId", "onChangeValue", "onFieldsBlur"]);
1415
1520
  const htmlForId = id ? id : "phoneField-" + uuid.v4();
1416
- const renderAsInvalid = isInvalid || Boolean(errorMessage);
1417
- return (jsxRuntime.jsx(FormGroup, { htmlFor: htmlForId, label: label, tip: tip, isInvalid: renderAsInvalid, helpText: (renderAsInvalid && errorMessage) || helpText, helpAddon: helpAddon, disabled: rest.disabled, className: className, dataTestId: dataTestId && `${dataTestId}-FormGroup`, children: jsxRuntime.jsx(PhoneInput, Object.assign({ id: htmlForId, "aria-labelledby": htmlForId + "-label", value: value, defaultValue: defaultValue, ref: ref, isInvalid: renderAsInvalid, dataTestId: dataTestId, onChangeValue: onChangeValue, onFieldsBlur: onFieldsBlur }, rest)) }));
1521
+ const [t] = useTranslation();
1522
+ const [isValid, setIsValid] = React.useState(true);
1523
+ const renderAsInvalid = !isValid || isInvalid || Boolean(errorMessage);
1524
+ const onChangeValueHandler = ({ phone, national, countryCode }) => {
1525
+ if (!isValid && countryCode) {
1526
+ const countryAbbreviation = getCountryAbbreviation(countryCode);
1527
+ setIsValid(phone && national && countryAbbreviation ? parsePhoneNumberFromString.isValidPhoneNumber(phone, countryAbbreviation) : true);
1528
+ }
1529
+ onChangeValue && onChangeValue({ phone, national, countryCode, isValid });
1530
+ };
1531
+ const handleOnFieldBlur = ({ phone, national, countryCode }) => {
1532
+ if (countryCode) {
1533
+ const countryAbbreviation = getCountryAbbreviation(countryCode);
1534
+ setIsValid(phone && national && countryAbbreviation ? parsePhoneNumberFromString.isValidPhoneNumber(phone, countryAbbreviation) : true);
1535
+ }
1536
+ onFieldsBlur && onFieldsBlur({ phone, national, countryCode, isValid });
1537
+ };
1538
+ return (jsxRuntime.jsx(FormGroup, { htmlFor: htmlForId, label: label, tip: tip, isInvalid: renderAsInvalid, helpText: (renderAsInvalid && (errorMessage || t("phoneField.error"))) || helpText, helpAddon: helpAddon, disabled: rest.disabled, className: className, dataTestId: dataTestId && `${dataTestId}-FormGroup`, children: jsxRuntime.jsx(PhoneInput, Object.assign({ id: htmlForId, "aria-labelledby": htmlForId + "-label", value: value, defaultValue: defaultValue, ref: ref, isInvalid: renderAsInvalid, dataTestId: dataTestId, onChangeValue: onChangeValueHandler, onFieldsBlur: handleOnFieldBlur }, rest)) }));
1418
1539
  });
1419
1540
 
1420
1541
  /**
@@ -2069,6 +2190,7 @@ exports.Toggle = Toggle;
2069
2190
  exports.UploadField = UploadField;
2070
2191
  exports.UploadInput = UploadInput;
2071
2192
  exports.checkIfPhoneNumberHasPlus = checkIfPhoneNumberHasPlus;
2193
+ exports.countryCodeToFlagEmoji = countryCodeToFlagEmoji;
2072
2194
  exports.cvaInput = cvaInput;
2073
2195
  exports.cvaInputAddon = cvaInputAddon;
2074
2196
  exports.cvaInputAddonAfter = cvaInputAddonAfter;
@@ -2087,6 +2209,7 @@ exports.cvaSelectMenu = cvaSelectMenu;
2087
2209
  exports.cvaSelectMenuList = cvaSelectMenuList;
2088
2210
  exports.cvaSelectPrefix = cvaSelectPrefix;
2089
2211
  exports.cvaSelectXIcon = cvaSelectXIcon;
2212
+ exports.getCountryAbbreviation = getCountryAbbreviation;
2090
2213
  exports.getOrderedOptions = getOrderedOptions;
2091
2214
  exports.getPhoneNumberWithPlus = getPhoneNumberWithPlus;
2092
2215
  exports.isInvalidCountryCode = isInvalidCountryCode;
package/index.esm.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { registerTranslations } from '@trackunit/i18n-library-translation';
2
+ import { useNamespaceTranslation, registerTranslations } from '@trackunit/i18n-library-translation';
3
3
  import { IconButton, Icon, Tip, Heading, Text, MenuItem, Tag, Spinner } from '@trackunit/react-components';
4
4
  import { cvaMerge } from '@trackunit/css-class-variance-utilities';
5
5
  import * as React from 'react';
6
6
  import React__default, { useMemo, forwardRef, useState, cloneElement, useEffect, useRef } from 'react';
7
7
  import { v4 } from 'uuid';
8
8
  import { format } from 'date-fns';
9
- import parsePhoneNumberFromString, { getCountries, getCountryCallingCode, parsePhoneNumberFromString as parsePhoneNumberFromString$1, isValidPhoneNumber, validatePhoneNumberLength } from 'libphonenumber-js';
9
+ import parsePhoneNumberFromString, { getCountries, getCountryCallingCode, isValidPhoneNumber, parsePhoneNumberFromString as parsePhoneNumberFromString$1, validatePhoneNumberLength } from 'libphonenumber-js';
10
10
  import ReactSelect, { components } from 'react-select';
11
11
  export { default as ValueType } from 'react-select';
12
12
  import ReactAsyncCreatableSelect from 'react-select/async-creatable';
@@ -14,7 +14,7 @@ import ReactCreatableSelect from 'react-select/creatable';
14
14
  import ReactAsyncSelect from 'react-select/async';
15
15
 
16
16
  var defaultTranslations = {
17
-
17
+ "phoneField.error": "Invalid phone number"
18
18
  };
19
19
 
20
20
  /** The translation namespace for this library */
@@ -48,6 +48,10 @@ const translations = {
48
48
  th: () => import('./translation.esm17.js'),
49
49
  },
50
50
  };
51
+ /**
52
+ * Local useTranslation for this specific library
53
+ */
54
+ const useTranslation = () => useNamespaceTranslation(namespace);
51
55
  /**
52
56
  * Registers the translations for this library
53
57
  */
@@ -749,6 +753,38 @@ const getPhoneNumberWithPlus = (phoneNumber) => {
749
753
  const stringPhoneNumber = phoneNumber.toString();
750
754
  return checkIfPhoneNumberHasPlus(stringPhoneNumber) ? stringPhoneNumber : `+${stringPhoneNumber}`;
751
755
  };
756
+ /**
757
+ * Generates a flag emoji based on the given country code.
758
+ *
759
+ * @param {string} countryCode - The two-letter country code (ISO 3166-1 alpha-2).
760
+ * @returns {string} - The corresponding flag emoji for the given country code.
761
+ * @example getPhoneNumberWithPlus("DK") // "🇩🇰"
762
+ */
763
+ const countryCodeToFlagEmoji = (countryCode) => {
764
+ let code = countryCode;
765
+ if (countryCode.startsWith("+")) {
766
+ code = getCountryAbbreviation(countryCode.substring(1));
767
+ }
768
+ else if (!isNaN(Number(countryCode))) {
769
+ code = getCountryAbbreviation(countryCode);
770
+ }
771
+ return code.toUpperCase().replace(/./g, char => String.fromCodePoint(127397 + char.charCodeAt(0)));
772
+ };
773
+ /**
774
+ * Retrieves the ISO 3166-1 alpha-2 country code associated with a given international calling code.
775
+ *
776
+ * @param {string} callCode - The international calling code for a country.
777
+ * @returns {string} The abbreviation for a country or an empty string if no matching country is found.
778
+ * @example getCountryAbbreviation("45") // "DK"
779
+ * @example getCountryAbbreviation("+45") // "DK"
780
+ */
781
+ const getCountryAbbreviation = (callCode) => {
782
+ let code = callCode;
783
+ if (callCode.startsWith("+")) {
784
+ code = callCode.substring(1);
785
+ }
786
+ return getCountries().find(c => getCountryCallingCode(c) === code) || "";
787
+ };
752
788
 
753
789
  /**
754
790
  * A custom hook for managing phone number input state and validation.
@@ -1238,7 +1274,7 @@ const Select = (props) => {
1238
1274
  };
1239
1275
  Select.displayName = "Select";
1240
1276
 
1241
- const COUNTRY_CODES_NOT_SUPPORTED_BY_BACKEND = ["53", "98", "211", "247", "249", "383", "850", "963"];
1277
+ const COUNTRY_CODES_NOT_SUPPORTED_BY_BACKEND = ["+53", "+98", "+211", "+247", "+249", "+383", "+850", "+963"];
1242
1278
  const ADDITIONAL_COUNTRY_CODES_SUPPORTED_BY_BACKEND = [
1243
1279
  "1242",
1244
1280
  "1246",
@@ -1268,13 +1304,73 @@ const ADDITIONAL_COUNTRY_CODES_SUPPORTED_BY_BACKEND = [
1268
1304
  "882",
1269
1305
  "883",
1270
1306
  ];
1271
- const countryCodesSet = [
1272
- ...new Set(getCountries().map(code => getCountryCallingCode(code))),
1273
- ...ADDITIONAL_COUNTRY_CODES_SUPPORTED_BY_BACKEND,
1274
- ]
1275
- .filter(code => !COUNTRY_CODES_NOT_SUPPORTED_BY_BACKEND.includes(code))
1276
- .sort((a, b) => Number(a) - Number(b));
1277
- const countryCodes = countryCodesSet.map(code => ({ label: `+${code}`, value: `+${code}` })); // We add the + sign to the value so that the google libphonenumber-js can parse it correctly.
1307
+ const DUPLICATE_COUNTRY_CODES = [
1308
+ // +1
1309
+ "AG",
1310
+ "AI",
1311
+ "AS",
1312
+ "BB",
1313
+ "BM",
1314
+ "BS",
1315
+ "CA",
1316
+ "DM",
1317
+ "DO",
1318
+ "GD",
1319
+ "GU",
1320
+ "JM",
1321
+ "KN",
1322
+ "KY",
1323
+ "LC",
1324
+ "MP",
1325
+ "MS",
1326
+ "PR",
1327
+ "SX",
1328
+ "TC",
1329
+ "TT",
1330
+ "VC",
1331
+ "VG",
1332
+ "VI",
1333
+ // +7
1334
+ "KZ",
1335
+ // +39
1336
+ "VA",
1337
+ // +44
1338
+ "GG",
1339
+ "IM",
1340
+ "JE",
1341
+ // +47
1342
+ "SJ",
1343
+ // +61
1344
+ "CC",
1345
+ "CX",
1346
+ // 212
1347
+ "EH",
1348
+ // 262
1349
+ "RE",
1350
+ // 290
1351
+ "SH",
1352
+ // 590
1353
+ "BL",
1354
+ "MF",
1355
+ // 599
1356
+ "CW",
1357
+ ];
1358
+ const countries = getCountries()
1359
+ .filter(code => !DUPLICATE_COUNTRY_CODES.includes(code))
1360
+ .map(code => {
1361
+ const c = getCountryCallingCode(code);
1362
+ return {
1363
+ label: `+${c} ${countryCodeToFlagEmoji(code)}`,
1364
+ value: `+${c}`,
1365
+ };
1366
+ });
1367
+ const additionalCountries = ADDITIONAL_COUNTRY_CODES_SUPPORTED_BY_BACKEND.map(code => ({
1368
+ label: `+${code}`,
1369
+ value: `+${code}`,
1370
+ }));
1371
+ const countryCodes = [...countries, ...additionalCountries]
1372
+ .filter(code => !COUNTRY_CODES_NOT_SUPPORTED_BY_BACKEND.includes(code.value))
1373
+ .sort((a, b) => Number(a.value) - Number(b.value));
1278
1374
 
1279
1375
  /**
1280
1376
  * The CountryCodeSelect component is used to select a country code for a phone number.
@@ -1290,7 +1386,7 @@ const CountryCodeSelect = ({ excludedCountries, countryCode, isInvalid, onChange
1290
1386
  const filteredCodes = countryCodes.filter(code => {
1291
1387
  return !(excludedCountries === null || excludedCountries === void 0 ? void 0 : excludedCountries.some(excludedCode => excludedCode.value === code.value));
1292
1388
  });
1293
- return (jsx(Select, { dataTestId: dataTestId, className: "w-32", "aria-invalid": isCountryCodeMissing, options: filteredCodes.map(code => code), value: filteredCodes.find(({ value }) => value === `+${countryCode}`), placeholder: placeholder, hasError: isCountryCodeMissing || isInvalid, maxMenuHeight: 200, onChange: onChange, disabled: disabled, readOnly: readOnly, onBlur: onBlur, closeMenuOnSelect: true, menuIsOpen: readOnly ? false : undefined, isClearable: isClearable }));
1389
+ return (jsx(Select, { dataTestId: dataTestId, className: "w-36", "aria-invalid": isCountryCodeMissing, options: filteredCodes.map(code => code), value: filteredCodes.find(({ value }) => value === `+${countryCode}`), placeholder: placeholder, hasError: isCountryCodeMissing || isInvalid, maxMenuHeight: 200, onChange: onChange, disabled: disabled, readOnly: readOnly, onBlur: onBlur, closeMenuOnSelect: true, menuIsOpen: readOnly ? false : undefined, isClearable: isClearable }));
1294
1390
  };
1295
1391
 
1296
1392
  /**
@@ -1308,7 +1404,16 @@ const PhoneInput = forwardRef((_a, ref) => {
1308
1404
  var { dataTestId, isInvalid, disabled = false, value, defaultValue, fieldSize = "medium", disableAction = false, onChange, onBlurCountryCode, readOnly, countryCodePlaceholder, onChangeCountryCode, onBlur, onFieldsBlur, onChangeValue, isCountryCodeClearable } = _a, rest = __rest(_a, ["dataTestId", "isInvalid", "disabled", "value", "defaultValue", "fieldSize", "disableAction", "onChange", "onBlurCountryCode", "readOnly", "countryCodePlaceholder", "onChangeCountryCode", "onBlur", "onFieldsBlur", "onChangeValue", "isCountryCodeClearable"]);
1309
1405
  const DEFAULT_COUNTRY_CODE = "+45";
1310
1406
  const { getPhoneNumber } = usePhoneInput();
1311
- const countryCodePlaceholderValue = !readOnly ? countryCodePlaceholder || DEFAULT_COUNTRY_CODE : "";
1407
+ const [placeholder, setPlaceholder] = useState();
1408
+ useEffect(() => {
1409
+ if (readOnly) {
1410
+ setPlaceholder("");
1411
+ }
1412
+ else {
1413
+ const countryCode = countryCodePlaceholder || DEFAULT_COUNTRY_CODE;
1414
+ setPlaceholder(`${countryCode} ${countryCodeToFlagEmoji(countryCode)}`);
1415
+ }
1416
+ }, [countryCodePlaceholder, readOnly]);
1312
1417
  const safePhoneNumber = getPhoneNumberWithPlus(value || "");
1313
1418
  const number = parsePhoneNumberFromString(safePhoneNumber, { defaultCountry: "DK" });
1314
1419
  const valueHasOnlyCountryCode = (number === null || number === void 0 ? void 0 : number.isPossible) === undefined && typeof value === "string" && value[0] === "+" && value.length < 5;
@@ -1357,7 +1462,7 @@ const PhoneInput = forwardRef((_a, ref) => {
1357
1462
  }, onChange: e => {
1358
1463
  var _a;
1359
1464
  onCodeChangeHandler((_a = e === null || e === void 0 ? void 0 : e.value) !== null && _a !== void 0 ? _a : "");
1360
- }, disabled: disabled, readOnly: readOnly, isInvalid: isInvalid, placeholder: countryCodePlaceholderValue, isClearable: isCountryCodeClearable }), jsx(BaseInput, Object.assign({ id: "phoneInput-number", dataTestId: dataTestId && `${dataTestId}-phoneNumberInput`, fieldSize: fieldSize, type: "tel", value: phone, disabled: disabled, readOnly: readOnly, isInvalid: isInvalid, onChange: e => {
1465
+ }, disabled: disabled, readOnly: readOnly, isInvalid: isInvalid, placeholder: placeholder, isClearable: isCountryCodeClearable }), jsx(BaseInput, Object.assign({ id: "phoneInput-number", dataTestId: dataTestId && `${dataTestId}-phoneNumberInput`, fieldSize: fieldSize, type: "tel", value: phone, disabled: disabled, readOnly: readOnly, isInvalid: isInvalid, onChange: e => {
1361
1466
  onPhoneChangeHandler(e.target.value);
1362
1467
  }, onBlur: () => {
1363
1468
  onBlurHandler();
@@ -1383,8 +1488,24 @@ const PhoneInput = forwardRef((_a, ref) => {
1383
1488
  const PhoneField = forwardRef((_a, ref) => {
1384
1489
  var { label, id, tip, helpText, isInvalid, errorMessage, value, helpAddon, className, defaultValue, dataTestId, onChangeValue, onFieldsBlur } = _a, rest = __rest(_a, ["label", "id", "tip", "helpText", "isInvalid", "errorMessage", "value", "helpAddon", "className", "defaultValue", "dataTestId", "onChangeValue", "onFieldsBlur"]);
1385
1490
  const htmlForId = id ? id : "phoneField-" + v4();
1386
- const renderAsInvalid = isInvalid || Boolean(errorMessage);
1387
- return (jsx(FormGroup, { htmlFor: htmlForId, label: label, tip: tip, isInvalid: renderAsInvalid, helpText: (renderAsInvalid && errorMessage) || helpText, helpAddon: helpAddon, disabled: rest.disabled, className: className, dataTestId: dataTestId && `${dataTestId}-FormGroup`, children: jsx(PhoneInput, Object.assign({ id: htmlForId, "aria-labelledby": htmlForId + "-label", value: value, defaultValue: defaultValue, ref: ref, isInvalid: renderAsInvalid, dataTestId: dataTestId, onChangeValue: onChangeValue, onFieldsBlur: onFieldsBlur }, rest)) }));
1491
+ const [t] = useTranslation();
1492
+ const [isValid, setIsValid] = useState(true);
1493
+ const renderAsInvalid = !isValid || isInvalid || Boolean(errorMessage);
1494
+ const onChangeValueHandler = ({ phone, national, countryCode }) => {
1495
+ if (!isValid && countryCode) {
1496
+ const countryAbbreviation = getCountryAbbreviation(countryCode);
1497
+ setIsValid(phone && national && countryAbbreviation ? isValidPhoneNumber(phone, countryAbbreviation) : true);
1498
+ }
1499
+ onChangeValue && onChangeValue({ phone, national, countryCode, isValid });
1500
+ };
1501
+ const handleOnFieldBlur = ({ phone, national, countryCode }) => {
1502
+ if (countryCode) {
1503
+ const countryAbbreviation = getCountryAbbreviation(countryCode);
1504
+ setIsValid(phone && national && countryAbbreviation ? isValidPhoneNumber(phone, countryAbbreviation) : true);
1505
+ }
1506
+ onFieldsBlur && onFieldsBlur({ phone, national, countryCode, isValid });
1507
+ };
1508
+ return (jsx(FormGroup, { htmlFor: htmlForId, label: label, tip: tip, isInvalid: renderAsInvalid, helpText: (renderAsInvalid && (errorMessage || t("phoneField.error"))) || helpText, helpAddon: helpAddon, disabled: rest.disabled, className: className, dataTestId: dataTestId && `${dataTestId}-FormGroup`, children: jsx(PhoneInput, Object.assign({ id: htmlForId, "aria-labelledby": htmlForId + "-label", value: value, defaultValue: defaultValue, ref: ref, isInvalid: renderAsInvalid, dataTestId: dataTestId, onChangeValue: onChangeValueHandler, onFieldsBlur: handleOnFieldBlur }, rest)) }));
1388
1509
  });
1389
1510
 
1390
1511
  /**
@@ -2002,4 +2123,4 @@ const UploadField = forwardRef((_a, ref) => {
2002
2123
  */
2003
2124
  setupLibraryTranslations();
2004
2125
 
2005
- export { ActionButton, BaseInput, Checkbox, CreatableSelect, DateField, DateInput, DropZone, EmailField, EmailInput, FormGroup, Label, MultiSelectMenuItem, NumberField, NumberInput, OptionCard, PhoneField, PhoneInput, RadioGroup, RadioItem, Schedule, ScheduleVariant, Search, Select, SingleSelectMenuItem, TextArea, TextAreaField, TextField, TextInput, TimeRange, TimeRangeField, Toggle, UploadField, UploadInput, checkIfPhoneNumberHasPlus, cvaInput, cvaInputAddon, cvaInputAddonAfter, cvaInputAddonBefore, cvaInputBase, cvaInputBaseDisabled, cvaInputBaseInvalid, cvaInputField, cvaInputPrefix, cvaInputSuffix, cvaSelect, cvaSelectCounter, cvaSelectDynamicTagContainer, cvaSelectIcon, cvaSelectMenu, cvaSelectMenuList, cvaSelectPrefix, cvaSelectXIcon, getOrderedOptions, getPhoneNumberWithPlus, isInvalidCountryCode, isInvalidPhoneNumber, isMultiValue, parseSchedule, serializeSchedule, useCustomComponents, usePhoneInput, validateEmailAddress, validatePhoneNumber, weekDay };
2126
+ export { ActionButton, BaseInput, Checkbox, CreatableSelect, DateField, DateInput, DropZone, EmailField, EmailInput, FormGroup, Label, MultiSelectMenuItem, NumberField, NumberInput, OptionCard, PhoneField, PhoneInput, RadioGroup, RadioItem, Schedule, ScheduleVariant, Search, Select, SingleSelectMenuItem, TextArea, TextAreaField, TextField, TextInput, TimeRange, TimeRangeField, Toggle, UploadField, UploadInput, checkIfPhoneNumberHasPlus, countryCodeToFlagEmoji, cvaInput, cvaInputAddon, cvaInputAddonAfter, cvaInputAddonBefore, cvaInputBase, cvaInputBaseDisabled, cvaInputBaseInvalid, cvaInputField, cvaInputPrefix, cvaInputSuffix, cvaSelect, cvaSelectCounter, cvaSelectDynamicTagContainer, cvaSelectIcon, cvaSelectMenu, cvaSelectMenuList, cvaSelectPrefix, cvaSelectXIcon, getCountryAbbreviation, getOrderedOptions, getPhoneNumberWithPlus, isInvalidCountryCode, isInvalidPhoneNumber, isMultiValue, parseSchedule, serializeSchedule, useCustomComponents, usePhoneInput, validateEmailAddress, validatePhoneNumber, weekDay };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-form-components",
3
- "version": "0.0.226",
3
+ "version": "0.0.228",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -6,6 +6,7 @@ export interface PhoneNumberValue {
6
6
  phone?: string;
7
7
  national?: string;
8
8
  countryCode?: string;
9
+ isValid?: boolean;
9
10
  }
10
11
  export interface PhoneInputProps extends BaseInputExposedProps {
11
12
  /**
@@ -1,3 +1,4 @@
1
+ import { CountryCode } from "libphonenumber-js";
1
2
  /**
2
3
  * @param phoneNumber - a phone number as a string
3
4
  * @returns {boolean} true if the phone number starts with a plus sign
@@ -11,3 +12,20 @@ export declare const checkIfPhoneNumberHasPlus: (phoneNumber: string) => boolean
11
12
  * @example getPhoneNumberWithPlus("123456789") // "+123456789"
12
13
  */
13
14
  export declare const getPhoneNumberWithPlus: (phoneNumber: string | number | readonly string[]) => string;
15
+ /**
16
+ * Generates a flag emoji based on the given country code.
17
+ *
18
+ * @param {string} countryCode - The two-letter country code (ISO 3166-1 alpha-2).
19
+ * @returns {string} - The corresponding flag emoji for the given country code.
20
+ * @example getPhoneNumberWithPlus("DK") // "🇩🇰"
21
+ */
22
+ export declare const countryCodeToFlagEmoji: (countryCode: string) => string;
23
+ /**
24
+ * Retrieves the ISO 3166-1 alpha-2 country code associated with a given international calling code.
25
+ *
26
+ * @param {string} callCode - The international calling code for a country.
27
+ * @returns {string} The abbreviation for a country or an empty string if no matching country is found.
28
+ * @example getCountryAbbreviation("45") // "DK"
29
+ * @example getCountryAbbreviation("+45") // "DK"
30
+ */
31
+ export declare const getCountryAbbreviation: (callCode: string) => CountryCode | "";
@@ -15,8 +15,8 @@ export declare const translations: TranslationResource<TranslationKeys>;
15
15
  /**
16
16
  * Local useTranslation for this specific library
17
17
  */
18
- export declare const useTranslation: () => [TransForLibs<never>, import("i18next").i18n, boolean] & {
19
- t: TransForLibs<never>;
18
+ export declare const useTranslation: () => [TransForLibs<"phoneField.error">, import("i18next").i18n, boolean] & {
19
+ t: TransForLibs<"phoneField.error">;
20
20
  i18n: import("i18next").i18n;
21
21
  ready: boolean;
22
22
  };