@trackunit/react-form-components 0.0.22 → 0.0.24

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 CHANGED
@@ -9,11 +9,11 @@ var tailwindStyledComponents = require('@trackunit/tailwind-styled-components');
9
9
  var React = require('react');
10
10
  var uuid = require('uuid');
11
11
  var dateFns = require('date-fns');
12
+ var libphonenumberJs = require('libphonenumber-js');
12
13
  var ReactSelect = require('react-select');
13
14
  var ReactAsyncCreatableSelect = require('react-select/async-creatable');
14
15
  var ReactCreatableSelect = require('react-select/creatable');
15
16
  var ReactAsyncSelect = require('react-select/async');
16
- var parsePhoneNumberFromString = require('libphonenumber-js');
17
17
 
18
18
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
19
19
 
@@ -41,7 +41,6 @@ var ReactSelect__default = /*#__PURE__*/_interopDefaultLegacy(ReactSelect);
41
41
  var ReactAsyncCreatableSelect__default = /*#__PURE__*/_interopDefaultLegacy(ReactAsyncCreatableSelect);
42
42
  var ReactCreatableSelect__default = /*#__PURE__*/_interopDefaultLegacy(ReactCreatableSelect);
43
43
  var ReactAsyncSelect__default = /*#__PURE__*/_interopDefaultLegacy(ReactAsyncSelect);
44
- var parsePhoneNumberFromString__default = /*#__PURE__*/_interopDefaultLegacy(parsePhoneNumberFromString);
45
44
 
46
45
  var defaultTranslations = {
47
46
 
@@ -494,6 +493,80 @@ const Content = tailwindStyledComponents.tw.div `
494
493
  items-center
495
494
  `;
496
495
 
496
+ /**
497
+ * @param phoneNumber - a phone number as a string
498
+ * @returns {boolean} true if the phone number starts with a plus sign
499
+ * @example checkIfPhoneNumberHasPlus("123456789") // false
500
+ * checkIfPhoneNumberHasPlus("+123456789") // true
501
+ */
502
+ const checkIfPhoneNumberHasPlus = (phoneNumber) => {
503
+ return phoneNumber.startsWith("+");
504
+ };
505
+ /**
506
+ * @param phoneNumber - a phone number as a string
507
+ * @returns {string|number} the phone number with a plus sign in front of it
508
+ * @example getPhoneNumberWithPlus("123456789") // "+123456789"
509
+ */
510
+ const getPhoneNumberWithPlus = (phoneNumber) => {
511
+ const stringPhoneNumber = phoneNumber.toString();
512
+ return checkIfPhoneNumberHasPlus(stringPhoneNumber) ? stringPhoneNumber : `+${stringPhoneNumber}`;
513
+ };
514
+
515
+ /**
516
+ * A custom hook for managing phone number input state and validation.
517
+ *
518
+ * @returns {object} - An object containing state values and event handler functions.
519
+ * @property {string} countryCode - The current country code value.
520
+ * @property {string} phoneNumber - The current phone number value.
521
+ * @property {boolean} isCountryCodeInvalid - Whether the country code value is invalid.
522
+ * @property {boolean} isPhoneNumberInvalid - Whether the phone number value is invalid.
523
+ * @property {Function} handleCountryCodeChange - A function for handling changes to the country code input.
524
+ * @property {Function} handlePhoneNumberChange - A function for handling changes to the phone number input.
525
+ * @property {string} combinedValue - The combined country code and phone number value.
526
+ */
527
+ const usePhoneInput = (value) => {
528
+ const [countryCode, setCountryCode] = React.useState("");
529
+ const [phoneNumber, setPhoneNumber] = React.useState("");
530
+ const [isCountryCodeInvalid, setIsCountryCodeInvalid] = React.useState(false);
531
+ const [isPhoneNumberInvalid, setIsPhoneNumberInvalid] = React.useState(false);
532
+ const [isCombinedValueInvalid, setIsCombinedValueInvalid] = React.useState(false);
533
+ const [combinedValue, setCombinedValue] = React.useState(value !== null && value !== void 0 ? value : "");
534
+ React.useEffect(() => {
535
+ var _a, _b;
536
+ if (value) {
537
+ const safePhoneNumber = getPhoneNumberWithPlus(value);
538
+ const number = libphonenumberJs.parsePhoneNumberFromString(safePhoneNumber);
539
+ setCountryCode((_a = number === null || number === void 0 ? void 0 : number.countryCallingCode.toString()) !== null && _a !== void 0 ? _a : "");
540
+ setPhoneNumber((_b = number === null || number === void 0 ? void 0 : number.nationalNumber.toString()) !== null && _b !== void 0 ? _b : "");
541
+ setCombinedValue(`${countryCode}${phoneNumber}`);
542
+ combinedValue && setIsCombinedValueInvalid(libphonenumberJs.isPossiblePhoneNumber(combinedValue.toString()));
543
+ }
544
+ }, [combinedValue, countryCode, phoneNumber, value]);
545
+ const handleCountryCodeChange = (option) => {
546
+ var _a;
547
+ setCountryCode((_a = option === null || option === void 0 ? void 0 : option.value) !== null && _a !== void 0 ? _a : "");
548
+ setIsCountryCodeInvalid(!Boolean(option === null || option === void 0 ? void 0 : option.value));
549
+ setCombinedValue(`${option === null || option === void 0 ? void 0 : option.value}${phoneNumber}`);
550
+ combinedValue && setIsCombinedValueInvalid(libphonenumberJs.isPossiblePhoneNumber(combinedValue.toString()));
551
+ };
552
+ const handlePhoneNumberChange = (event) => {
553
+ setPhoneNumber(event.target.value);
554
+ setIsPhoneNumberInvalid(!event.target.value || !Boolean(event.target.value.trim()));
555
+ setCombinedValue(`${countryCode}${event.target.value}`);
556
+ combinedValue && setIsCombinedValueInvalid(libphonenumberJs.isPossiblePhoneNumber(combinedValue.toString()));
557
+ };
558
+ return {
559
+ countryCode,
560
+ phoneNumber,
561
+ isCountryCodeInvalid,
562
+ isPhoneNumberInvalid,
563
+ handleCountryCodeChange,
564
+ handlePhoneNumberChange,
565
+ combinedValue,
566
+ isCombinedValueInvalid,
567
+ };
568
+ };
569
+
497
570
  /**
498
571
  * The SelectContainer component.
499
572
  *
@@ -975,7 +1048,7 @@ const ADDITIONAL_COUNTRY_CODES_SUPPORTED_BY_BACKEND = [
975
1048
  "883",
976
1049
  ];
977
1050
  const countryCodesSet = [
978
- ...new Set(parsePhoneNumberFromString.getCountries().map(code => parsePhoneNumberFromString.getCountryCallingCode(code))),
1051
+ ...new Set(libphonenumberJs.getCountries().map(code => libphonenumberJs.getCountryCallingCode(code))),
979
1052
  ...ADDITIONAL_COUNTRY_CODES_SUPPORTED_BY_BACKEND,
980
1053
  ]
981
1054
  .filter(code => !COUNTRY_CODES_NOT_SUPPORTED_BY_BACKEND.includes(code))
@@ -999,69 +1072,6 @@ const CountryCodeSelect = ({ excludedCountries, countryCode, isInvalid, onChange
999
1072
  return (jsxRuntime.jsx(Select, { dataTestId: dataTestId, className: "w-28", "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 }));
1000
1073
  };
1001
1074
 
1002
- /**
1003
- * @param phoneNumber - a phone number as a string
1004
- * @returns {boolean} true if the phone number starts with a plus sign
1005
- * @example checkIfPhoneNumberHasPlus("123456789") // false
1006
- * checkIfPhoneNumberHasPlus("+123456789") // true
1007
- */
1008
- const checkIfPhoneNumberHasPlus = (phoneNumber) => {
1009
- return phoneNumber.startsWith("+");
1010
- };
1011
- /**
1012
- * @param phoneNumber - a phone number as a string
1013
- * @returns {string|number} the phone number with a plus sign in front of it
1014
- * @example getPhoneNumberWithPlus("123456789") // "+123456789"
1015
- */
1016
- const getPhoneNumberWithPlus = (phoneNumber) => {
1017
- const stringPhoneNumber = phoneNumber.toString();
1018
- return checkIfPhoneNumberHasPlus(stringPhoneNumber) ? stringPhoneNumber : `+${stringPhoneNumber}`;
1019
- };
1020
-
1021
- /**
1022
- * A custom hook for managing phone number input state and validation.
1023
- *
1024
- * @returns {object} - An object containing state values and event handler functions.
1025
- * @property {string} countryCode - The current country code value.
1026
- * @property {string} phoneNumber - The current phone number value.
1027
- * @property {boolean} isCountryCodeInvalid - Whether the country code value is invalid.
1028
- * @property {boolean} isPhoneNumberInvalid - Whether the phone number value is invalid.
1029
- * @property {Function} handleCountryCodeChange - A function for handling changes to the country code input.
1030
- * @property {Function} handlePhoneNumberChange - A function for handling changes to the phone number input.
1031
- */
1032
- const usePhoneInput = (initialValue) => {
1033
- const [countryCode, setCountryCode] = React.useState("");
1034
- const [phoneNumber, setPhoneNumber] = React.useState("");
1035
- const [isCountryCodeInvalid, setIsCountryCodeInvalid] = React.useState(false);
1036
- const [isPhoneNumberInvalid, setIsPhoneNumberInvalid] = React.useState(false);
1037
- React.useEffect(() => {
1038
- var _a, _b;
1039
- if (initialValue) {
1040
- const safePhoneNumber = getPhoneNumberWithPlus(initialValue);
1041
- const number = parsePhoneNumberFromString__default["default"](safePhoneNumber);
1042
- setCountryCode((_a = number === null || number === void 0 ? void 0 : number.countryCallingCode.toString()) !== null && _a !== void 0 ? _a : "");
1043
- setPhoneNumber((_b = number === null || number === void 0 ? void 0 : number.nationalNumber.toString()) !== null && _b !== void 0 ? _b : "");
1044
- }
1045
- }, [initialValue]);
1046
- const handleCountryCodeChange = (option) => {
1047
- var _a;
1048
- setCountryCode((_a = option === null || option === void 0 ? void 0 : option.value) !== null && _a !== void 0 ? _a : "");
1049
- setIsCountryCodeInvalid(!Boolean(option === null || option === void 0 ? void 0 : option.value));
1050
- };
1051
- const handlePhoneNumberChange = (event) => {
1052
- setPhoneNumber(event.target.value);
1053
- setIsPhoneNumberInvalid(!event.target.value || !Boolean(event.target.value.trim()));
1054
- };
1055
- return {
1056
- countryCode,
1057
- phoneNumber,
1058
- isCountryCodeInvalid,
1059
- isPhoneNumberInvalid,
1060
- handleCountryCodeChange,
1061
- handlePhoneNumberChange,
1062
- };
1063
- };
1064
-
1065
1075
  /**
1066
1076
  * A component for inputting phone numbers with an optional action button for initiating a phone call.
1067
1077
  *
@@ -1073,9 +1083,21 @@ const usePhoneInput = (initialValue) => {
1073
1083
  * @returns {JSX.Element} - The PhoneInput component.
1074
1084
  */
1075
1085
  const PhoneInput = React.forwardRef((_a, ref) => {
1076
- var { dataTestId, isInvalid, disabled = false, value, fieldSize = "medium", disableAction = false, onChange, defaultValue } = _a, rest = __rest(_a, ["dataTestId", "isInvalid", "disabled", "value", "fieldSize", "disableAction", "onChange", "defaultValue"]);
1077
- const { countryCode, phoneNumber, isCountryCodeInvalid, isPhoneNumberInvalid, handleCountryCodeChange, handlePhoneNumberChange, } = usePhoneInput(value !== null && value !== void 0 ? value : defaultValue);
1078
- return (jsxRuntime.jsxs(Container, Object.assign({ "data-testid": `${dataTestId}-container` }, { children: [jsxRuntime.jsx("input", { hidden: true, id: "phone-input", "data-testid": dataTestId && `${dataTestId}`, value: value, ref: ref, onChange: onChange }), jsxRuntime.jsx(CountryCodeSelect, { dataTestId: dataTestId && `${dataTestId}-countryCodeSelect`, countryCode: countryCode, onChange: handleCountryCodeChange, disabled: disabled, isInvalid: isCountryCodeInvalid || isInvalid, placeholder: "+45" }), jsxRuntime.jsx(BaseInput, Object.assign({ id: "phoneInput-number", dataTestId: dataTestId && `${dataTestId}-phoneNumberInput`, fieldSize: fieldSize, type: "tel", value: phoneNumber, disabled: disabled, onChange: handlePhoneNumberChange, isInvalid: isPhoneNumberInvalid || isInvalid, actions: !disableAction && (jsxRuntime.jsx(ActionButton, { disabled: disabled || isInvalid || isCountryCodeInvalid || isPhoneNumberInvalid, value: `${countryCode}${phoneNumber}`, type: "PHONE_NUMBER", dataTestId: dataTestId && `${dataTestId}-phoneIcon`, iconSize: fieldSize })) }, rest))] })));
1086
+ var { dataTestId, isInvalid, disabled = false, value, defaultValue, fieldSize = "medium", disableAction = false, onChange, onChangeGetSeparateValues } = _a, rest = __rest(_a, ["dataTestId", "isInvalid", "disabled", "value", "defaultValue", "fieldSize", "disableAction", "onChange", "onChangeGetSeparateValues"]);
1087
+ const hiddenInputRef = React.useRef(null);
1088
+ const { countryCode, phoneNumber, isCountryCodeInvalid, isPhoneNumberInvalid, handleCountryCodeChange, handlePhoneNumberChange, combinedValue, isCombinedValueInvalid, } = usePhoneInput(value !== null && value !== void 0 ? value : defaultValue);
1089
+ React.useEffect(() => {
1090
+ const element = hiddenInputRef.current;
1091
+ const event = new Event("change");
1092
+ element === null || element === void 0 ? void 0 : element.dispatchEvent(event);
1093
+ // The event as unknown as ChangeEvent<HTMLInputElement> assertion is needed because
1094
+ // the event's type is inferred as Event, which doesn't have a target property, whereas ChangeEvent does.
1095
+ onChange && onChange(event);
1096
+ }, [combinedValue, onChange]);
1097
+ React.useEffect(() => {
1098
+ onChangeGetSeparateValues && onChangeGetSeparateValues({ countryCode, phoneNumber });
1099
+ }, [countryCode, onChangeGetSeparateValues, phoneNumber]);
1100
+ return (jsxRuntime.jsxs(Container, Object.assign({ "data-testid": dataTestId && `${dataTestId}-container` }, { children: [jsxRuntime.jsx("input", { hidden: true, id: "phone-input", "data-testid": dataTestId && dataTestId, value: combinedValue, ref: hiddenInputRef, onChange: onChange, "aria-invalid": isCombinedValueInvalid || isInvalid }), jsxRuntime.jsx(CountryCodeSelect, { dataTestId: dataTestId && `${dataTestId}-countryCodeSelect`, countryCode: countryCode, onChange: handleCountryCodeChange, disabled: disabled, isInvalid: isCountryCodeInvalid || isInvalid, placeholder: "+45" }), jsxRuntime.jsx(BaseInput, Object.assign({ id: "phoneInput-number", dataTestId: dataTestId && `${dataTestId}-phoneNumberInput`, fieldSize: fieldSize, type: "tel", value: phoneNumber, disabled: disabled, onChange: handlePhoneNumberChange, isInvalid: isPhoneNumberInvalid || isInvalid, actions: !disableAction && (jsxRuntime.jsx(ActionButton, { disabled: disabled || isInvalid || isCountryCodeInvalid || isPhoneNumberInvalid, value: `${countryCode}${phoneNumber}`, type: "PHONE_NUMBER", dataTestId: dataTestId && `${dataTestId}-phoneIcon`, iconSize: fieldSize })) }, rest))] })));
1079
1101
  });
1080
1102
  const Container = tailwindStyledComponents.tw.div `
1081
1103
  grid
@@ -1101,9 +1123,10 @@ const Container = tailwindStyledComponents.tw.div `
1101
1123
  */
1102
1124
  const PhoneField = React.forwardRef((_a, ref) => {
1103
1125
  var { label, id, tip, helpText, isInvalid, errorMessage, value, helpAddon, className, defaultValue, dataTestId } = _a, rest = __rest(_a, ["label", "id", "tip", "helpText", "isInvalid", "errorMessage", "value", "helpAddon", "className", "defaultValue", "dataTestId"]);
1104
- const renderAsInvalid = isInvalid || Boolean(errorMessage);
1126
+ const { isCombinedValueInvalid, isPhoneNumberInvalid, isCountryCodeInvalid, combinedValue } = usePhoneInput(value);
1127
+ const renderAsInvalid = isCombinedValueInvalid || isPhoneNumberInvalid || isCountryCodeInvalid || isInvalid || Boolean(errorMessage);
1105
1128
  const htmlForId = id ? id : "phoneField-" + uuid.v4();
1106
- return (jsxRuntime.jsx(FormGroup, Object.assign({ 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 !== null && value !== void 0 ? value : defaultValue, isInvalid: renderAsInvalid }, rest)) })));
1129
+ return (jsxRuntime.jsx(FormGroup, Object.assign({ 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: combinedValue, defaultValue: defaultValue, ref: ref }, rest)) })));
1107
1130
  });
1108
1131
 
1109
1132
  const RadioGroupContext = React__namespace.createContext(null);
@@ -1327,7 +1350,7 @@ const UploadField = React.forwardRef((_a, ref) => {
1327
1350
 
1328
1351
  /*
1329
1352
  * ----------------------------
1330
- * | SETUP TRANSLATIONS START |
1353
+ * | SETUP TRANSLATIONS START |
1331
1354
  * ----------------------------
1332
1355
  * This import and function call is needed to register translations for this library.
1333
1356
  * Do not remove this if this library has translations.
package/index.js CHANGED
@@ -3,15 +3,15 @@ import { registerTranslations } from '@trackunit/i18n-library-translation';
3
3
  import { IconButton, Icon, Tip, Heading, Text, MenuItem, Tag, Spinner } from '@trackunit/react-components';
4
4
  import { tw, twx, tws } from '@trackunit/tailwind-styled-components';
5
5
  import * as React from 'react';
6
- import React__default, { useMemo, forwardRef, useState, cloneElement, useEffect } from 'react';
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, isPossiblePhoneNumber, getCountries, getCountryCallingCode } from 'libphonenumber-js';
9
10
  import ReactSelect, { components } from 'react-select';
10
11
  export { default as ValueType } from 'react-select';
11
12
  import ReactAsyncCreatableSelect from 'react-select/async-creatable';
12
13
  import ReactCreatableSelect from 'react-select/creatable';
13
14
  import ReactAsyncSelect from 'react-select/async';
14
- import parsePhoneNumberFromString, { getCountries, getCountryCallingCode } from 'libphonenumber-js';
15
15
 
16
16
  var defaultTranslations = {
17
17
 
@@ -464,6 +464,80 @@ const Content = tw.div `
464
464
  items-center
465
465
  `;
466
466
 
467
+ /**
468
+ * @param phoneNumber - a phone number as a string
469
+ * @returns {boolean} true if the phone number starts with a plus sign
470
+ * @example checkIfPhoneNumberHasPlus("123456789") // false
471
+ * checkIfPhoneNumberHasPlus("+123456789") // true
472
+ */
473
+ const checkIfPhoneNumberHasPlus = (phoneNumber) => {
474
+ return phoneNumber.startsWith("+");
475
+ };
476
+ /**
477
+ * @param phoneNumber - a phone number as a string
478
+ * @returns {string|number} the phone number with a plus sign in front of it
479
+ * @example getPhoneNumberWithPlus("123456789") // "+123456789"
480
+ */
481
+ const getPhoneNumberWithPlus = (phoneNumber) => {
482
+ const stringPhoneNumber = phoneNumber.toString();
483
+ return checkIfPhoneNumberHasPlus(stringPhoneNumber) ? stringPhoneNumber : `+${stringPhoneNumber}`;
484
+ };
485
+
486
+ /**
487
+ * A custom hook for managing phone number input state and validation.
488
+ *
489
+ * @returns {object} - An object containing state values and event handler functions.
490
+ * @property {string} countryCode - The current country code value.
491
+ * @property {string} phoneNumber - The current phone number value.
492
+ * @property {boolean} isCountryCodeInvalid - Whether the country code value is invalid.
493
+ * @property {boolean} isPhoneNumberInvalid - Whether the phone number value is invalid.
494
+ * @property {Function} handleCountryCodeChange - A function for handling changes to the country code input.
495
+ * @property {Function} handlePhoneNumberChange - A function for handling changes to the phone number input.
496
+ * @property {string} combinedValue - The combined country code and phone number value.
497
+ */
498
+ const usePhoneInput = (value) => {
499
+ const [countryCode, setCountryCode] = useState("");
500
+ const [phoneNumber, setPhoneNumber] = useState("");
501
+ const [isCountryCodeInvalid, setIsCountryCodeInvalid] = useState(false);
502
+ const [isPhoneNumberInvalid, setIsPhoneNumberInvalid] = useState(false);
503
+ const [isCombinedValueInvalid, setIsCombinedValueInvalid] = useState(false);
504
+ const [combinedValue, setCombinedValue] = useState(value !== null && value !== void 0 ? value : "");
505
+ useEffect(() => {
506
+ var _a, _b;
507
+ if (value) {
508
+ const safePhoneNumber = getPhoneNumberWithPlus(value);
509
+ const number = parsePhoneNumberFromString(safePhoneNumber);
510
+ setCountryCode((_a = number === null || number === void 0 ? void 0 : number.countryCallingCode.toString()) !== null && _a !== void 0 ? _a : "");
511
+ setPhoneNumber((_b = number === null || number === void 0 ? void 0 : number.nationalNumber.toString()) !== null && _b !== void 0 ? _b : "");
512
+ setCombinedValue(`${countryCode}${phoneNumber}`);
513
+ combinedValue && setIsCombinedValueInvalid(isPossiblePhoneNumber(combinedValue.toString()));
514
+ }
515
+ }, [combinedValue, countryCode, phoneNumber, value]);
516
+ const handleCountryCodeChange = (option) => {
517
+ var _a;
518
+ setCountryCode((_a = option === null || option === void 0 ? void 0 : option.value) !== null && _a !== void 0 ? _a : "");
519
+ setIsCountryCodeInvalid(!Boolean(option === null || option === void 0 ? void 0 : option.value));
520
+ setCombinedValue(`${option === null || option === void 0 ? void 0 : option.value}${phoneNumber}`);
521
+ combinedValue && setIsCombinedValueInvalid(isPossiblePhoneNumber(combinedValue.toString()));
522
+ };
523
+ const handlePhoneNumberChange = (event) => {
524
+ setPhoneNumber(event.target.value);
525
+ setIsPhoneNumberInvalid(!event.target.value || !Boolean(event.target.value.trim()));
526
+ setCombinedValue(`${countryCode}${event.target.value}`);
527
+ combinedValue && setIsCombinedValueInvalid(isPossiblePhoneNumber(combinedValue.toString()));
528
+ };
529
+ return {
530
+ countryCode,
531
+ phoneNumber,
532
+ isCountryCodeInvalid,
533
+ isPhoneNumberInvalid,
534
+ handleCountryCodeChange,
535
+ handlePhoneNumberChange,
536
+ combinedValue,
537
+ isCombinedValueInvalid,
538
+ };
539
+ };
540
+
467
541
  /**
468
542
  * The SelectContainer component.
469
543
  *
@@ -969,69 +1043,6 @@ const CountryCodeSelect = ({ excludedCountries, countryCode, isInvalid, onChange
969
1043
  return (jsx(Select, { dataTestId: dataTestId, className: "w-28", "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 }));
970
1044
  };
971
1045
 
972
- /**
973
- * @param phoneNumber - a phone number as a string
974
- * @returns {boolean} true if the phone number starts with a plus sign
975
- * @example checkIfPhoneNumberHasPlus("123456789") // false
976
- * checkIfPhoneNumberHasPlus("+123456789") // true
977
- */
978
- const checkIfPhoneNumberHasPlus = (phoneNumber) => {
979
- return phoneNumber.startsWith("+");
980
- };
981
- /**
982
- * @param phoneNumber - a phone number as a string
983
- * @returns {string|number} the phone number with a plus sign in front of it
984
- * @example getPhoneNumberWithPlus("123456789") // "+123456789"
985
- */
986
- const getPhoneNumberWithPlus = (phoneNumber) => {
987
- const stringPhoneNumber = phoneNumber.toString();
988
- return checkIfPhoneNumberHasPlus(stringPhoneNumber) ? stringPhoneNumber : `+${stringPhoneNumber}`;
989
- };
990
-
991
- /**
992
- * A custom hook for managing phone number input state and validation.
993
- *
994
- * @returns {object} - An object containing state values and event handler functions.
995
- * @property {string} countryCode - The current country code value.
996
- * @property {string} phoneNumber - The current phone number value.
997
- * @property {boolean} isCountryCodeInvalid - Whether the country code value is invalid.
998
- * @property {boolean} isPhoneNumberInvalid - Whether the phone number value is invalid.
999
- * @property {Function} handleCountryCodeChange - A function for handling changes to the country code input.
1000
- * @property {Function} handlePhoneNumberChange - A function for handling changes to the phone number input.
1001
- */
1002
- const usePhoneInput = (initialValue) => {
1003
- const [countryCode, setCountryCode] = useState("");
1004
- const [phoneNumber, setPhoneNumber] = useState("");
1005
- const [isCountryCodeInvalid, setIsCountryCodeInvalid] = useState(false);
1006
- const [isPhoneNumberInvalid, setIsPhoneNumberInvalid] = useState(false);
1007
- useEffect(() => {
1008
- var _a, _b;
1009
- if (initialValue) {
1010
- const safePhoneNumber = getPhoneNumberWithPlus(initialValue);
1011
- const number = parsePhoneNumberFromString(safePhoneNumber);
1012
- setCountryCode((_a = number === null || number === void 0 ? void 0 : number.countryCallingCode.toString()) !== null && _a !== void 0 ? _a : "");
1013
- setPhoneNumber((_b = number === null || number === void 0 ? void 0 : number.nationalNumber.toString()) !== null && _b !== void 0 ? _b : "");
1014
- }
1015
- }, [initialValue]);
1016
- const handleCountryCodeChange = (option) => {
1017
- var _a;
1018
- setCountryCode((_a = option === null || option === void 0 ? void 0 : option.value) !== null && _a !== void 0 ? _a : "");
1019
- setIsCountryCodeInvalid(!Boolean(option === null || option === void 0 ? void 0 : option.value));
1020
- };
1021
- const handlePhoneNumberChange = (event) => {
1022
- setPhoneNumber(event.target.value);
1023
- setIsPhoneNumberInvalid(!event.target.value || !Boolean(event.target.value.trim()));
1024
- };
1025
- return {
1026
- countryCode,
1027
- phoneNumber,
1028
- isCountryCodeInvalid,
1029
- isPhoneNumberInvalid,
1030
- handleCountryCodeChange,
1031
- handlePhoneNumberChange,
1032
- };
1033
- };
1034
-
1035
1046
  /**
1036
1047
  * A component for inputting phone numbers with an optional action button for initiating a phone call.
1037
1048
  *
@@ -1043,9 +1054,21 @@ const usePhoneInput = (initialValue) => {
1043
1054
  * @returns {JSX.Element} - The PhoneInput component.
1044
1055
  */
1045
1056
  const PhoneInput = forwardRef((_a, ref) => {
1046
- var { dataTestId, isInvalid, disabled = false, value, fieldSize = "medium", disableAction = false, onChange, defaultValue } = _a, rest = __rest(_a, ["dataTestId", "isInvalid", "disabled", "value", "fieldSize", "disableAction", "onChange", "defaultValue"]);
1047
- const { countryCode, phoneNumber, isCountryCodeInvalid, isPhoneNumberInvalid, handleCountryCodeChange, handlePhoneNumberChange, } = usePhoneInput(value !== null && value !== void 0 ? value : defaultValue);
1048
- return (jsxs(Container, Object.assign({ "data-testid": `${dataTestId}-container` }, { children: [jsx("input", { hidden: true, id: "phone-input", "data-testid": dataTestId && `${dataTestId}`, value: value, ref: ref, onChange: onChange }), jsx(CountryCodeSelect, { dataTestId: dataTestId && `${dataTestId}-countryCodeSelect`, countryCode: countryCode, onChange: handleCountryCodeChange, disabled: disabled, isInvalid: isCountryCodeInvalid || isInvalid, placeholder: "+45" }), jsx(BaseInput, Object.assign({ id: "phoneInput-number", dataTestId: dataTestId && `${dataTestId}-phoneNumberInput`, fieldSize: fieldSize, type: "tel", value: phoneNumber, disabled: disabled, onChange: handlePhoneNumberChange, isInvalid: isPhoneNumberInvalid || isInvalid, actions: !disableAction && (jsx(ActionButton, { disabled: disabled || isInvalid || isCountryCodeInvalid || isPhoneNumberInvalid, value: `${countryCode}${phoneNumber}`, type: "PHONE_NUMBER", dataTestId: dataTestId && `${dataTestId}-phoneIcon`, iconSize: fieldSize })) }, rest))] })));
1057
+ var { dataTestId, isInvalid, disabled = false, value, defaultValue, fieldSize = "medium", disableAction = false, onChange, onChangeGetSeparateValues } = _a, rest = __rest(_a, ["dataTestId", "isInvalid", "disabled", "value", "defaultValue", "fieldSize", "disableAction", "onChange", "onChangeGetSeparateValues"]);
1058
+ const hiddenInputRef = useRef(null);
1059
+ const { countryCode, phoneNumber, isCountryCodeInvalid, isPhoneNumberInvalid, handleCountryCodeChange, handlePhoneNumberChange, combinedValue, isCombinedValueInvalid, } = usePhoneInput(value !== null && value !== void 0 ? value : defaultValue);
1060
+ useEffect(() => {
1061
+ const element = hiddenInputRef.current;
1062
+ const event = new Event("change");
1063
+ element === null || element === void 0 ? void 0 : element.dispatchEvent(event);
1064
+ // The event as unknown as ChangeEvent<HTMLInputElement> assertion is needed because
1065
+ // the event's type is inferred as Event, which doesn't have a target property, whereas ChangeEvent does.
1066
+ onChange && onChange(event);
1067
+ }, [combinedValue, onChange]);
1068
+ useEffect(() => {
1069
+ onChangeGetSeparateValues && onChangeGetSeparateValues({ countryCode, phoneNumber });
1070
+ }, [countryCode, onChangeGetSeparateValues, phoneNumber]);
1071
+ return (jsxs(Container, Object.assign({ "data-testid": dataTestId && `${dataTestId}-container` }, { children: [jsx("input", { hidden: true, id: "phone-input", "data-testid": dataTestId && dataTestId, value: combinedValue, ref: hiddenInputRef, onChange: onChange, "aria-invalid": isCombinedValueInvalid || isInvalid }), jsx(CountryCodeSelect, { dataTestId: dataTestId && `${dataTestId}-countryCodeSelect`, countryCode: countryCode, onChange: handleCountryCodeChange, disabled: disabled, isInvalid: isCountryCodeInvalid || isInvalid, placeholder: "+45" }), jsx(BaseInput, Object.assign({ id: "phoneInput-number", dataTestId: dataTestId && `${dataTestId}-phoneNumberInput`, fieldSize: fieldSize, type: "tel", value: phoneNumber, disabled: disabled, onChange: handlePhoneNumberChange, isInvalid: isPhoneNumberInvalid || isInvalid, actions: !disableAction && (jsx(ActionButton, { disabled: disabled || isInvalid || isCountryCodeInvalid || isPhoneNumberInvalid, value: `${countryCode}${phoneNumber}`, type: "PHONE_NUMBER", dataTestId: dataTestId && `${dataTestId}-phoneIcon`, iconSize: fieldSize })) }, rest))] })));
1049
1072
  });
1050
1073
  const Container = tw.div `
1051
1074
  grid
@@ -1071,9 +1094,10 @@ const Container = tw.div `
1071
1094
  */
1072
1095
  const PhoneField = forwardRef((_a, ref) => {
1073
1096
  var { label, id, tip, helpText, isInvalid, errorMessage, value, helpAddon, className, defaultValue, dataTestId } = _a, rest = __rest(_a, ["label", "id", "tip", "helpText", "isInvalid", "errorMessage", "value", "helpAddon", "className", "defaultValue", "dataTestId"]);
1074
- const renderAsInvalid = isInvalid || Boolean(errorMessage);
1097
+ const { isCombinedValueInvalid, isPhoneNumberInvalid, isCountryCodeInvalid, combinedValue } = usePhoneInput(value);
1098
+ const renderAsInvalid = isCombinedValueInvalid || isPhoneNumberInvalid || isCountryCodeInvalid || isInvalid || Boolean(errorMessage);
1075
1099
  const htmlForId = id ? id : "phoneField-" + v4();
1076
- return (jsx(FormGroup, Object.assign({ 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 !== null && value !== void 0 ? value : defaultValue, isInvalid: renderAsInvalid }, rest)) })));
1100
+ return (jsx(FormGroup, Object.assign({ 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: combinedValue, defaultValue: defaultValue, ref: ref }, rest)) })));
1077
1101
  });
1078
1102
 
1079
1103
  const RadioGroupContext = React.createContext(null);
@@ -1297,7 +1321,7 @@ const UploadField = forwardRef((_a, ref) => {
1297
1321
 
1298
1322
  /*
1299
1323
  * ----------------------------
1300
- * | SETUP TRANSLATIONS START |
1324
+ * | SETUP TRANSLATIONS START |
1301
1325
  * ----------------------------
1302
1326
  * This import and function call is needed to register translations for this library.
1303
1327
  * Do not remove this if this library has translations.
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@trackunit/react-form-components",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "dependencies": {
7
- "@trackunit/i18n-library-translation": "0.0.45",
8
- "@trackunit/react-components": "0.1.84",
7
+ "@trackunit/i18n-library-translation": "0.0.46",
8
+ "@trackunit/react-components": "0.1.86",
9
9
  "@trackunit/tailwind-styled-components": "0.0.56",
10
10
  "date-fns": "2.29.3",
11
- "libphonenumber-js": "1.10.26",
11
+ "libphonenumber-js": "1.10.30",
12
12
  "react": "18.2.0",
13
13
  "react-select": "5.7.3",
14
14
  "uuid": "8.3.2"
@@ -1,6 +1,10 @@
1
1
  /// <reference types="react" />
2
2
  import { BaseInputProps } from "../BaseInput";
3
3
  type BaseInputExposedProps = Omit<BaseInputProps, "actions">;
4
+ interface PhoneNumberProps {
5
+ countryCode: string;
6
+ phoneNumber: string;
7
+ }
4
8
  export interface PhoneInputProps extends BaseInputExposedProps {
5
9
  /**
6
10
  * To disable the action button.
@@ -11,6 +15,10 @@ export interface PhoneInputProps extends BaseInputExposedProps {
11
15
  * <PhoneInput disableAction />
12
16
  */
13
17
  disableAction?: boolean;
18
+ /**
19
+ * A callback function that gives you the country code and phone number as separate values.
20
+ */
21
+ onChangeGetSeparateValues?: (params: PhoneNumberProps) => void;
14
22
  }
15
23
  /**
16
24
  * A component for inputting phone numbers with an optional action button for initiating a phone call.
@@ -16,6 +16,12 @@ export interface usePhoneInputProps {
16
16
  * Whether the phone number value is invalid.
17
17
  */
18
18
  isPhoneNumberInvalid: boolean;
19
+ /**
20
+ * Whether the full phone number value is valid.
21
+ *
22
+ * @returns {boolean} Whether the full phone number value is valid.
23
+ */
24
+ isCombinedValueInvalid: boolean;
19
25
  /**
20
26
  * A function for handling changes to the country code input.
21
27
  */
@@ -27,6 +33,13 @@ export interface usePhoneInputProps {
27
33
  * A function for handling changes to the phone number input.
28
34
  */
29
35
  handlePhoneNumberChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
36
+ /**
37
+ * The combined country code and phone number value.
38
+ *
39
+ * @example "+123456789"
40
+ * @returns {string} Combined phone number
41
+ */
42
+ combinedValue: string | number | readonly string[] | undefined;
30
43
  }
31
44
  /**
32
45
  * A custom hook for managing phone number input state and validation.
@@ -38,5 +51,6 @@ export interface usePhoneInputProps {
38
51
  * @property {boolean} isPhoneNumberInvalid - Whether the phone number value is invalid.
39
52
  * @property {Function} handleCountryCodeChange - A function for handling changes to the country code input.
40
53
  * @property {Function} handlePhoneNumberChange - A function for handling changes to the phone number input.
54
+ * @property {string} combinedValue - The combined country code and phone number value.
41
55
  */
42
- export declare const usePhoneInput: (initialValue: string | number | readonly string[] | undefined) => usePhoneInputProps;
56
+ export declare const usePhoneInput: (value: string | number | readonly string[] | undefined) => usePhoneInputProps;