@trackunit/react-form-components 1.8.64 → 1.8.65
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 +1394 -1326
- package/index.esm.js +1395 -1328
- package/package.json +1 -1
- package/src/components/{Select/Select.d.ts → BaseSelect/BaseSelect.d.ts} +3 -2
- package/src/components/{Select → BaseSelect}/index.d.ts +2 -2
- package/src/components/MultiSelectField/FormFieldSelectAdapterMulti.d.ts +59 -0
- package/src/components/MultiSelectField/MultiSelectField.d.ts +10 -0
- package/src/components/MultiSelectField/index.d.ts +1 -0
- package/src/components/SelectField/CreatableSelectField.d.ts +1 -1
- package/src/components/SelectField/FormFieldSelectAdapter.d.ts +1 -1
- package/src/index.d.ts +2 -1
- /package/src/components/{Select/Select.variants.d.ts → BaseSelect/BaseSelect.variants.d.ts} +0 -0
- /package/src/components/{Select → BaseSelect}/CreatableSelect.d.ts +0 -0
- /package/src/components/{Select → BaseSelect}/SelectMenuItem/SelectMenuItem.d.ts +0 -0
- /package/src/components/{Select → BaseSelect}/TagWithWidth.d.ts +0 -0
- /package/src/components/{Select → BaseSelect}/TagsContainer.d.ts +0 -0
- /package/src/components/{Select → BaseSelect}/useCustomComponents.d.ts +0 -0
- /package/src/components/{Select → BaseSelect}/useCustomStyles.d.ts +0 -0
- /package/src/components/{Select → BaseSelect}/useSelect.d.ts +0 -0
package/index.esm.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import { useNamespaceTranslation, registerTranslations, NamespaceTrans } from '@trackunit/i18n-library-translation';
|
|
3
3
|
import { Temporal } from '@js-temporal/polyfill';
|
|
4
|
-
import { IconButton, Icon, Tooltip, useIsTextTruncated,
|
|
4
|
+
import { IconButton, Icon, Tooltip, useIsTextTruncated, MenuItem, Tag, useResize, useDebounce, Spinner, Text, Heading, useIsFirstRender } from '@trackunit/react-components';
|
|
5
5
|
import { themeSpacing } from '@trackunit/ui-design-tokens';
|
|
6
|
-
import { forwardRef, useRef, useEffect, useImperativeHandle, useCallback, useState,
|
|
6
|
+
import { forwardRef, useRef, useEffect, useImperativeHandle, useCallback, useState, isValidElement, cloneElement, useLayoutEffect, useMemo, createContext, useContext } from 'react';
|
|
7
7
|
import { cvaMerge } from '@trackunit/css-class-variance-utilities';
|
|
8
8
|
import { titleCase } from 'string-ts';
|
|
9
9
|
import { useCopyToClipboard } from 'usehooks-ts';
|
|
10
10
|
import parsePhoneNumberFromString, { isSupportedCountry, getCountries, getCountryCallingCode, AsYouType, parseIncompletePhoneNumber, isValidPhoneNumber } from 'libphonenumber-js';
|
|
11
|
-
import { uuidv4, nonNullable } from '@trackunit/shared-utils';
|
|
12
|
-
import { Controller } from 'react-hook-form';
|
|
13
11
|
import ReactSelect, { components } from 'react-select';
|
|
14
12
|
export { default as ValueType } from 'react-select';
|
|
13
|
+
import ReactAsyncSelect from 'react-select/async';
|
|
15
14
|
import ReactAsyncCreatableSelect from 'react-select/async-creatable';
|
|
16
15
|
import ReactCreatableSelect from 'react-select/creatable';
|
|
17
|
-
import
|
|
16
|
+
import { uuidv4, nonNullable } from '@trackunit/shared-utils';
|
|
17
|
+
import { Controller } from 'react-hook-form';
|
|
18
18
|
import { twMerge } from 'tailwind-merge';
|
|
19
19
|
import { z } from 'zod';
|
|
20
20
|
|
|
@@ -697,6 +697,103 @@ const TextAreaBaseInput = ({ id, name, value, rows, disabled, placeholder, readO
|
|
|
697
697
|
*/
|
|
698
698
|
const TextBaseInput = ({ ref, ...rest }) => jsx(BaseInput, { ref: ref, type: "text", ...rest });
|
|
699
699
|
|
|
700
|
+
const cvaSelect = cvaMerge([
|
|
701
|
+
"relative",
|
|
702
|
+
"flex",
|
|
703
|
+
"shadow-sm",
|
|
704
|
+
"rounded-lg",
|
|
705
|
+
"border-neutral-300",
|
|
706
|
+
"hover:border-neutral-400",
|
|
707
|
+
"hover:bg-neutral-50",
|
|
708
|
+
"bg-white",
|
|
709
|
+
"transition",
|
|
710
|
+
"text-sm",
|
|
711
|
+
"min-h-0",
|
|
712
|
+
], {
|
|
713
|
+
variants: {
|
|
714
|
+
fieldSize: {
|
|
715
|
+
small: ["h-7", "text-xs"],
|
|
716
|
+
medium: ["h-8"],
|
|
717
|
+
large: ["h-10"],
|
|
718
|
+
},
|
|
719
|
+
invalid: {
|
|
720
|
+
true: "border border-red-600 text-red-600 hover:border-red-600",
|
|
721
|
+
false: "",
|
|
722
|
+
},
|
|
723
|
+
disabled: {
|
|
724
|
+
true: "!bg-neutral-100 hover:border-neutral-300",
|
|
725
|
+
false: "",
|
|
726
|
+
},
|
|
727
|
+
},
|
|
728
|
+
defaultVariants: {
|
|
729
|
+
invalid: false,
|
|
730
|
+
disabled: false,
|
|
731
|
+
},
|
|
732
|
+
});
|
|
733
|
+
const cvaSelectControl = cvaMerge([], {
|
|
734
|
+
variants: {
|
|
735
|
+
isDisabled: {
|
|
736
|
+
true: "!bg-neutral-100",
|
|
737
|
+
false: "",
|
|
738
|
+
},
|
|
739
|
+
prefix: {
|
|
740
|
+
true: ["ps-7"],
|
|
741
|
+
false: "",
|
|
742
|
+
},
|
|
743
|
+
invalid: {
|
|
744
|
+
true: "!border-0",
|
|
745
|
+
false: "",
|
|
746
|
+
},
|
|
747
|
+
},
|
|
748
|
+
defaultVariants: {
|
|
749
|
+
isDisabled: false,
|
|
750
|
+
prefix: false,
|
|
751
|
+
invalid: false,
|
|
752
|
+
},
|
|
753
|
+
});
|
|
754
|
+
const cvaSelectIcon = cvaMerge([
|
|
755
|
+
"mr-2",
|
|
756
|
+
"flex",
|
|
757
|
+
"cursor-pointer",
|
|
758
|
+
"items-center",
|
|
759
|
+
"justify-center",
|
|
760
|
+
"text-neutral-400",
|
|
761
|
+
"hover:text-neutral-500",
|
|
762
|
+
]);
|
|
763
|
+
const cvaSelectPrefixSuffix = cvaMerge(["flex", "justify-center", "items-center", "text-neutral-400", "absolute", "inset-y-0"], {
|
|
764
|
+
variants: {
|
|
765
|
+
kind: {
|
|
766
|
+
prefix: ["pl-3", "left-0"],
|
|
767
|
+
suffix: ["pr-3", "right-0"],
|
|
768
|
+
},
|
|
769
|
+
},
|
|
770
|
+
});
|
|
771
|
+
const cvaSelectXIcon = cvaMerge([
|
|
772
|
+
"mr-2",
|
|
773
|
+
"flex",
|
|
774
|
+
"cursor-pointer",
|
|
775
|
+
"items-center",
|
|
776
|
+
"justify-center",
|
|
777
|
+
"text-neutral-400",
|
|
778
|
+
"hover:text-neutral-500",
|
|
779
|
+
"ml-1",
|
|
780
|
+
]);
|
|
781
|
+
const cvaSelectMenuList = cvaMerge([], {
|
|
782
|
+
variants: {
|
|
783
|
+
menuIsOpen: {
|
|
784
|
+
true: "animate-fade-in-fast",
|
|
785
|
+
false: "animate-fade-out-fast",
|
|
786
|
+
},
|
|
787
|
+
},
|
|
788
|
+
});
|
|
789
|
+
const cvaSelectDynamicTagContainer = cvaMerge(["h-full", "flex", "gap-1", "items-center"], {
|
|
790
|
+
variants: {
|
|
791
|
+
visible: { true: "visible", false: "invisible" },
|
|
792
|
+
},
|
|
793
|
+
});
|
|
794
|
+
const cvaSelectCounter = cvaMerge(["overflow-hidden", "whitespace-nowrap"]);
|
|
795
|
+
const cvaSelectMenu = cvaMerge(["relative", "p-1", "grid", "gap-1"]);
|
|
796
|
+
|
|
700
797
|
/**
|
|
701
798
|
* Shared CVA for binary control items: Checkbox, RadioItem, ToggleSwitchOption
|
|
702
799
|
*/
|
|
@@ -923,137 +1020,709 @@ const Checkbox = ({ className, dataTestId = "checkbox", onChange, checked = fals
|
|
|
923
1020
|
Checkbox.displayName = "Checkbox";
|
|
924
1021
|
|
|
925
1022
|
/**
|
|
926
|
-
*
|
|
927
|
-
* This component is **not used directly**, but is part of the FormGroup and Field components.
|
|
928
|
-
*
|
|
929
|
-
* @param {LabelProps} props - The props for the Label component
|
|
930
|
-
* @returns {ReactElement} Label component
|
|
931
|
-
*/
|
|
932
|
-
const Label = ({ id, htmlFor, children, className, dataTestId, disabled, isInvalid, }) => {
|
|
933
|
-
return (jsx("label", { className: cvaLabel({ invalid: isInvalid, disabled, className }), "data-testid": dataTestId, htmlFor: htmlFor || "", id: id || "", children: children }));
|
|
934
|
-
};
|
|
935
|
-
|
|
936
|
-
const cvaFormGroup = cvaMerge(["component-formGroup-gap", "group", "form-group"]);
|
|
937
|
-
const cvaFormGroupContainerBefore = cvaMerge(["flex", "mb-1", "items-center"]);
|
|
938
|
-
const cvaFormGroupContainerAfter = cvaMerge(["flex", "justify-between", "mt-1", "text-xs", "text-neutral-500"], {
|
|
939
|
-
variants: {
|
|
940
|
-
invalid: {
|
|
941
|
-
true: "text-danger-500",
|
|
942
|
-
false: "",
|
|
943
|
-
},
|
|
944
|
-
isWarning: {
|
|
945
|
-
true: "text-default-500 ",
|
|
946
|
-
false: "",
|
|
947
|
-
},
|
|
948
|
-
},
|
|
949
|
-
compoundVariants: [
|
|
950
|
-
{
|
|
951
|
-
invalid: true,
|
|
952
|
-
isWarning: true,
|
|
953
|
-
className: "text-danger-500 ", // Ensures that 'invalid' takes precedence
|
|
954
|
-
},
|
|
955
|
-
],
|
|
956
|
-
});
|
|
957
|
-
const cvaHelpAddon = cvaMerge(["ml-auto"]);
|
|
958
|
-
|
|
959
|
-
/**
|
|
960
|
-
* The FormGroup component should be used to wrap any Input element that needs a label.
|
|
961
|
-
* Besides a label the component supplies an optional Tooltip, HelpText and HelpAddon support.
|
|
1023
|
+
* A single select menu item is a basic wrapper around Menu item designed to be used as a single value render in Select list
|
|
962
1024
|
*
|
|
963
|
-
* @param {
|
|
964
|
-
* @returns {ReactElement}
|
|
1025
|
+
* @param {SelectMenuItemProps} props - The props for the SingleSelectMenuItem
|
|
1026
|
+
* @returns {ReactElement} SingleSelectMenuItem
|
|
965
1027
|
*/
|
|
966
|
-
const
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1028
|
+
const SingleSelectMenuItem = ({ label, icon, onClick, selected, focused, dataTestId, disabled, optionLabelDescription, optionPrefix, fieldSize, }) => {
|
|
1029
|
+
return (jsx(MenuItem, { dataTestId: dataTestId, disabled: disabled, fieldSize: fieldSize, focused: focused, label: label, onClick: onClick, optionLabelDescription: optionLabelDescription, optionPrefix: isValidElement(optionPrefix)
|
|
1030
|
+
? cloneElement(optionPrefix, {
|
|
1031
|
+
className: "mr-1 flex items-center",
|
|
1032
|
+
size: "medium",
|
|
1033
|
+
})
|
|
1034
|
+
: optionPrefix, prefix: icon, selected: selected, suffix: selected ? jsx(Icon, { className: "block text-blue-600", name: "Check", size: "medium" }) : undefined }));
|
|
973
1035
|
};
|
|
974
|
-
|
|
975
1036
|
/**
|
|
976
|
-
*
|
|
1037
|
+
* A multi select menu item is a basic wrapper around Menu item designed to be used as a multi value render in Select list
|
|
977
1038
|
*
|
|
978
|
-
*
|
|
1039
|
+
* @param {SelectMenuItemProps} props - The props for the MultiSelectMenuItem
|
|
1040
|
+
* @returns {ReactElement} multi select menu item
|
|
979
1041
|
*/
|
|
980
|
-
const
|
|
981
|
-
|
|
982
|
-
|
|
1042
|
+
const MultiSelectMenuItem = ({ label, onClick, selected, focused, dataTestId, disabled, optionLabelDescription, optionPrefix, fieldSize, }) => {
|
|
1043
|
+
return (jsx(MenuItem, { dataTestId: dataTestId, disabled: disabled, fieldSize: fieldSize, focused: focused, label: label, onClick: e => {
|
|
1044
|
+
e.stopPropagation();
|
|
1045
|
+
onClick && onClick(e);
|
|
1046
|
+
}, optionLabelDescription: optionLabelDescription, optionPrefix: isValidElement(optionPrefix)
|
|
1047
|
+
? cloneElement(optionPrefix, {
|
|
1048
|
+
className: "mr-1 flex items-center",
|
|
1049
|
+
size: "medium",
|
|
1050
|
+
})
|
|
1051
|
+
: optionPrefix, prefix: jsx(Checkbox, { checked: selected, className: "gap-x-0", disabled: disabled, onChange: () => null, onClick: e => {
|
|
1052
|
+
e.stopPropagation();
|
|
1053
|
+
}, readOnly: false }), selected: selected }));
|
|
983
1054
|
};
|
|
984
|
-
CheckboxField.displayName = "CheckboxField";
|
|
985
1055
|
|
|
986
1056
|
/**
|
|
1057
|
+
* Extended Tag component with information about its own width.
|
|
1058
|
+
* Used in the select component.
|
|
987
1059
|
*
|
|
988
|
-
* @param
|
|
989
|
-
* @returns {
|
|
990
|
-
*/
|
|
991
|
-
const isString = (inputValue) => {
|
|
992
|
-
return typeof inputValue === "string";
|
|
993
|
-
};
|
|
994
|
-
/**
|
|
995
|
-
*
|
|
996
|
-
* @param inputValue - value to check if it is a number
|
|
997
|
-
* @returns {boolean} - true if value is a number
|
|
998
|
-
*/
|
|
999
|
-
const isNumber = (inputValue) => {
|
|
1000
|
-
return typeof inputValue === "number";
|
|
1001
|
-
};
|
|
1002
|
-
|
|
1003
|
-
/**
|
|
1004
|
-
* Validates a url
|
|
1060
|
+
* @param {TagProps} props - The props for the tag component
|
|
1061
|
+
* @returns {ReactElement} TagWithWidth component
|
|
1005
1062
|
*/
|
|
1006
|
-
const
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
}
|
|
1013
|
-
if (colorCode && isString(colorCode) && isValidHEXColor(colorCode)) {
|
|
1014
|
-
return undefined;
|
|
1015
|
-
}
|
|
1016
|
-
return "INVALID_HEX_CODE";
|
|
1063
|
+
const TagWithWidth = ({ onWidthKnown, children, ...rest }) => {
|
|
1064
|
+
const ref = useRef(null);
|
|
1065
|
+
useLayoutEffect(() => {
|
|
1066
|
+
onWidthKnown && onWidthKnown({ width: ref.current?.offsetWidth || 0 });
|
|
1067
|
+
}, [ref, onWidthKnown]);
|
|
1068
|
+
return (jsx(Tag, { ref: ref, ...rest, icon: isValidElement(rest.icon) ? cloneElement(rest.icon, { size: "small" }) : rest.icon, children: children }));
|
|
1017
1069
|
};
|
|
1018
1070
|
|
|
1019
|
-
const cvaInputColorField = cvaMerge([
|
|
1020
|
-
"ml-3",
|
|
1021
|
-
"h-4",
|
|
1022
|
-
"w-4",
|
|
1023
|
-
"self-center",
|
|
1024
|
-
"bg-inherit",
|
|
1025
|
-
"disabled:opacity-50",
|
|
1026
|
-
"disabled:pointer-events-none",
|
|
1027
|
-
"rounded-[4px]",
|
|
1028
|
-
], {
|
|
1029
|
-
variants: {
|
|
1030
|
-
readOnly: {
|
|
1031
|
-
true: "pointer-events-none",
|
|
1032
|
-
false: "",
|
|
1033
|
-
},
|
|
1034
|
-
},
|
|
1035
|
-
compoundVariants: [
|
|
1036
|
-
{
|
|
1037
|
-
readOnly: true,
|
|
1038
|
-
},
|
|
1039
|
-
],
|
|
1040
|
-
defaultVariants: {
|
|
1041
|
-
readOnly: false,
|
|
1042
|
-
},
|
|
1043
|
-
});
|
|
1044
|
-
|
|
1045
1071
|
/**
|
|
1046
|
-
*
|
|
1072
|
+
* TagsContainer component to display tags in limited space when children can't fit space it displays counter
|
|
1047
1073
|
*
|
|
1048
|
-
* @param
|
|
1049
|
-
* @returns {
|
|
1074
|
+
* @param {TagsContainerProps} props - The props for the TagContainer
|
|
1075
|
+
* @returns {ReactElement} TagsContainer
|
|
1050
1076
|
*/
|
|
1051
|
-
const
|
|
1052
|
-
const
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1077
|
+
const TagsContainer = ({ items, width = "100%", itemsGap = 6, postFix, preFix, disabled, }) => {
|
|
1078
|
+
const containerRef = useRef(null);
|
|
1079
|
+
const [isReady, setIsReady] = useState(false);
|
|
1080
|
+
const [counterWidth, setCounterWidth] = useState(0);
|
|
1081
|
+
const [availableSpaceWidth, setAvailableSpaceWidth] = useState(0);
|
|
1082
|
+
const [childrenWidths, setChildrenWidths] = useState([]);
|
|
1083
|
+
const itemsCount = items.length;
|
|
1084
|
+
const dimensions = useResize();
|
|
1085
|
+
const { width: windowWidth } = useDebounce(dimensions, 100);
|
|
1086
|
+
useEffect(() => {
|
|
1087
|
+
const containerWidth = containerRef.current?.offsetWidth || 0;
|
|
1088
|
+
setAvailableSpaceWidth(containerWidth);
|
|
1089
|
+
}, [windowWidth]);
|
|
1090
|
+
const onWidthKnownHandler = useCallback(({ width: reportedWidth }) => {
|
|
1091
|
+
setChildrenWidths(prev => {
|
|
1092
|
+
const next = [...prev, { width: reportedWidth + itemsGap }];
|
|
1093
|
+
if (next.length === itemsCount) {
|
|
1094
|
+
setIsReady(true);
|
|
1095
|
+
}
|
|
1096
|
+
return next;
|
|
1097
|
+
});
|
|
1098
|
+
}, [itemsCount, itemsGap]);
|
|
1099
|
+
const renderedElements = useMemo(() => {
|
|
1100
|
+
const requiredSpace = childrenWidths.reduce((previous, current) => {
|
|
1101
|
+
return previous + current.width;
|
|
1102
|
+
}, 0);
|
|
1103
|
+
const availableSpace = availableSpaceWidth - counterWidth;
|
|
1104
|
+
const { elements } = items
|
|
1105
|
+
.concat({ text: "", onClick: () => null, disabled: false })
|
|
1106
|
+
.reduce((acc, item, index) => {
|
|
1107
|
+
const spaceNeeded = childrenWidths.slice(0, index + 1).reduce((previous, current) => {
|
|
1108
|
+
return previous + current.width;
|
|
1109
|
+
}, 0);
|
|
1110
|
+
const isLast = index === items.length;
|
|
1111
|
+
const counterRequired = requiredSpace > availableSpace && acc.counter !== 0;
|
|
1112
|
+
if (isLast && counterRequired) {
|
|
1113
|
+
return {
|
|
1114
|
+
...acc,
|
|
1115
|
+
elements: [
|
|
1116
|
+
...acc.elements,
|
|
1117
|
+
jsx(TagWithWidth, { color: "white", disabled: disabled, icon: item.Icon, onWidthKnown: ({ width: reportedWidth }) => setCounterWidth(reportedWidth), children: jsxs("div", { className: cvaSelectCounter(), "data-testid": "select-counter", children: ["+", acc.counter] }) }, item.text + index),
|
|
1118
|
+
],
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
if (isLast) {
|
|
1122
|
+
return acc;
|
|
1123
|
+
}
|
|
1124
|
+
const itemCanFit = spaceNeeded <= availableSpace;
|
|
1125
|
+
if (itemCanFit) {
|
|
1126
|
+
return {
|
|
1127
|
+
...acc,
|
|
1128
|
+
elements: [
|
|
1129
|
+
...acc.elements,
|
|
1130
|
+
jsx(TagWithWidth, { className: "inline-flex shrink-0", color: item.disabled ? "neutral" : "white", dataTestId: `${item.text}-tag`, disabled: disabled, icon: item.Icon, onClose: e => {
|
|
1131
|
+
e.stopPropagation();
|
|
1132
|
+
item.onClick();
|
|
1133
|
+
}, onWidthKnown: onWidthKnownHandler, children: item.text }, item.text + index),
|
|
1134
|
+
],
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
return {
|
|
1138
|
+
elements: acc.elements,
|
|
1139
|
+
counter: item.text !== "" ? acc.counter + 1 : acc.counter,
|
|
1140
|
+
};
|
|
1141
|
+
}, { elements: [], counter: 0 });
|
|
1142
|
+
return elements;
|
|
1143
|
+
}, [items, availableSpaceWidth, counterWidth, disabled, onWidthKnownHandler, childrenWidths]);
|
|
1144
|
+
return (jsxs("div", { className: cvaSelectDynamicTagContainer({ visible: isReady || !!preFix }), ref: containerRef, style: {
|
|
1145
|
+
width: `${width}`,
|
|
1146
|
+
}, children: [preFix, renderedElements, postFix] }));
|
|
1147
|
+
};
|
|
1148
|
+
|
|
1149
|
+
/**
|
|
1150
|
+
* A hook to retrieve components override object.
|
|
1151
|
+
* This complex object includes all the compositional components that are used in react-select. If you wish to overwrite a component, pass in an object with the appropriate namespace.
|
|
1152
|
+
*
|
|
1153
|
+
* @template IsMulti
|
|
1154
|
+
* @template Group
|
|
1155
|
+
* @param {Partial<SelectComponents<Option, IsMulti, Group>> | undefined} componentsProps a custom component prop that you can to override defaults
|
|
1156
|
+
* @param {boolean} disabled decide to override disabled variant
|
|
1157
|
+
* @param {boolean} menuIsOpen menu is open state
|
|
1158
|
+
* @param {string} dataTestId a test id
|
|
1159
|
+
* @param {number} maxSelectedDisplayCount a number of max display count
|
|
1160
|
+
* @param {boolean} hasError decide to override hasError variant
|
|
1161
|
+
* @param {ReactNode} prefix a prefix element
|
|
1162
|
+
* @returns {Partial<SelectComponents<Option, boolean, GroupBase<Option>>> | undefined} components object to override react-select default components
|
|
1163
|
+
*/
|
|
1164
|
+
const useCustomComponents = ({ componentsProps, disabled, readOnly, setMenuIsEnabled, dataTestId, maxSelectedDisplayCount, prefix, hasError, fieldSize, getOptionLabelDescription, getOptionPrefix, }) => {
|
|
1165
|
+
const [t] = useTranslation();
|
|
1166
|
+
// perhaps it should not be wrap in memo (causing some issues with opening and closing on mobiles)
|
|
1167
|
+
const customComponents = useMemo(() => {
|
|
1168
|
+
return {
|
|
1169
|
+
ValueContainer: props => {
|
|
1170
|
+
if (props.isMulti && Array.isArray(props.children) && props.children.length > 0) {
|
|
1171
|
+
const PLACEHOLDER_KEY = "placeholder";
|
|
1172
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1173
|
+
const key = props && props.children && props.children[0] ? props.children[0]?.key : "";
|
|
1174
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1175
|
+
const values = props && props.children ? props.children[0] : [];
|
|
1176
|
+
const tags = key === PLACEHOLDER_KEY ? [] : values;
|
|
1177
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1178
|
+
const searchInput = props && props.children && props.children[1];
|
|
1179
|
+
const placeholderElement = Array.isArray(props.children)
|
|
1180
|
+
? props.children.find(child => child && child.key === PLACEHOLDER_KEY)
|
|
1181
|
+
: null;
|
|
1182
|
+
return (jsx(components.ValueContainer, { ...props, isDisabled: props.selectProps.isDisabled, children: maxSelectedDisplayCount === undefined ? (jsx(TagsContainer, { disabled: disabled, items: tags
|
|
1183
|
+
? tags.map(({ props: tagProps }) => {
|
|
1184
|
+
const optionPrefix = tagProps.data && getOptionPrefix ? getOptionPrefix(tagProps.data) : null;
|
|
1185
|
+
return {
|
|
1186
|
+
text: tagProps.children,
|
|
1187
|
+
onClick: disabled
|
|
1188
|
+
? undefined
|
|
1189
|
+
: (e) => {
|
|
1190
|
+
setMenuIsEnabled(false);
|
|
1191
|
+
tagProps.removeProps.onClick && tagProps.removeProps.onClick(e);
|
|
1192
|
+
},
|
|
1193
|
+
disabled: disabled,
|
|
1194
|
+
Icon: optionPrefix,
|
|
1195
|
+
};
|
|
1196
|
+
})
|
|
1197
|
+
: [], postFix: searchInput, preFix: placeholderElement ? jsx("span", { className: "absolute", children: placeholderElement }) : null, width: "100%" })) : (jsxs(Fragment, { children: [tags
|
|
1198
|
+
? tags.slice(0, maxSelectedDisplayCount).map(({ props: tagProps }) => {
|
|
1199
|
+
return (jsx(Tag, { className: "inline-flex shrink-0", color: disabled ? "unknown" : "primary", dataTestId: tagProps.children ? `${tagProps.children.toString()}-tag` : undefined, onClose: e => {
|
|
1200
|
+
e.stopPropagation();
|
|
1201
|
+
setMenuIsEnabled(false);
|
|
1202
|
+
tagProps.removeProps.onClick && tagProps.removeProps.onClick(e);
|
|
1203
|
+
}, children: tagProps.children }, tagProps.children?.toString()));
|
|
1204
|
+
})
|
|
1205
|
+
: null, tags && tags.length > maxSelectedDisplayCount ? (jsxs(Tag, { color: "neutral", dataTestId: "counter-tag", children: ["+", tags.length - maxSelectedDisplayCount] })) : null, searchInput, placeholderElement] })) }));
|
|
1206
|
+
}
|
|
1207
|
+
return (jsx(Fragment, { children: jsx(components.ValueContainer, { ...props, isDisabled: props.selectProps.isDisabled, children: props.children }) }));
|
|
1208
|
+
},
|
|
1209
|
+
LoadingIndicator: () => {
|
|
1210
|
+
return jsx(Spinner, { centering: "vertically", className: "mr-2", size: "small" });
|
|
1211
|
+
},
|
|
1212
|
+
DropdownIndicator: props => {
|
|
1213
|
+
const icon = props.selectProps.menuIsOpen ? (jsx(Icon, { name: "ChevronUp", size: "medium" })) : (jsx(Icon, { name: "ChevronDown", size: "medium" }));
|
|
1214
|
+
return props.selectProps.isLoading || props.selectProps.isDisabled || readOnly ? null : (jsx(components.DropdownIndicator, { ...props, children: jsx("div", { className: cvaSelectIcon(), children: icon }) }));
|
|
1215
|
+
},
|
|
1216
|
+
IndicatorSeparator: () => null,
|
|
1217
|
+
ClearIndicator: props => {
|
|
1218
|
+
if (disabled) {
|
|
1219
|
+
return null;
|
|
1220
|
+
}
|
|
1221
|
+
return (jsx(components.ClearIndicator, { ...props, innerProps: {
|
|
1222
|
+
...props.innerProps,
|
|
1223
|
+
onMouseDown: e => {
|
|
1224
|
+
e.preventDefault();
|
|
1225
|
+
},
|
|
1226
|
+
}, children: jsx("div", { className: cvaSelectXIcon(), "data-testid": dataTestId ? `${dataTestId}-XMarkIcon` : null, onClick: props.clearValue, children: jsx(Icon, { ariaLabel: t("clearIndicator.icon.tooltip.clearAll"), name: "XCircle", size: "medium" }) }) }));
|
|
1227
|
+
},
|
|
1228
|
+
Control: props => {
|
|
1229
|
+
return (jsx(components.Control, { ...props, className: cvaSelectControl({
|
|
1230
|
+
isDisabled: props.isDisabled,
|
|
1231
|
+
prefix: prefix ? true : false,
|
|
1232
|
+
invalid: hasError,
|
|
1233
|
+
}) }));
|
|
1234
|
+
},
|
|
1235
|
+
SingleValue: props => {
|
|
1236
|
+
const optionPrefix = getOptionPrefix ? getOptionPrefix(props.data) : null;
|
|
1237
|
+
return (jsx(components.SingleValue, { ...props, className: props.isDisabled ? "text-neutral-700" : "", children: jsxs("div", { className: "flex items-center gap-1", "data-testid": dataTestId + "-singleValue", children: [optionPrefix !== null ? optionPrefix : null, props.children, getOptionLabelDescription && getOptionLabelDescription(props.data) ? (jsxs("span", { className: "ml-1 text-neutral-400", children: ["(", getOptionLabelDescription(props.data), ")"] })) : null] }) }));
|
|
1238
|
+
},
|
|
1239
|
+
Menu: props => {
|
|
1240
|
+
return (jsx(components.Menu, { ...props, className: cvaSelectMenuList({ menuIsOpen: props.selectProps.menuIsOpen }) }));
|
|
1241
|
+
},
|
|
1242
|
+
Placeholder: props => {
|
|
1243
|
+
return (jsx(components.Placeholder, { ...props, className: "!text-neutral-400", children: props.children }));
|
|
1244
|
+
},
|
|
1245
|
+
MenuList: props => {
|
|
1246
|
+
return (jsx(components.MenuList, { ...props, innerProps: {
|
|
1247
|
+
...props.innerProps,
|
|
1248
|
+
onScroll: e => {
|
|
1249
|
+
const listEl = e.currentTarget;
|
|
1250
|
+
if (listEl.scrollTop + listEl.clientHeight >= listEl.scrollHeight &&
|
|
1251
|
+
props.selectProps.onMenuScrollToBottom) {
|
|
1252
|
+
/Firefox/.test(navigator.userAgent)
|
|
1253
|
+
? props.selectProps.onMenuScrollToBottom(new WheelEvent("scroll"))
|
|
1254
|
+
: props.selectProps.onMenuScrollToBottom(new TouchEvent(""));
|
|
1255
|
+
}
|
|
1256
|
+
},
|
|
1257
|
+
}, children: props.children }));
|
|
1258
|
+
},
|
|
1259
|
+
Option: props => {
|
|
1260
|
+
const componentProps = {
|
|
1261
|
+
label: props.label,
|
|
1262
|
+
focused: props.isFocused,
|
|
1263
|
+
selected: props.isSelected,
|
|
1264
|
+
onClick: props.innerProps.onClick,
|
|
1265
|
+
};
|
|
1266
|
+
return (jsx(components.Option, { ...props, innerProps: {
|
|
1267
|
+
...props.innerProps,
|
|
1268
|
+
role: "option",
|
|
1269
|
+
onClick: () => { },
|
|
1270
|
+
}, children: props.isMulti ? (jsx(MultiSelectMenuItem, { ...componentProps, dataTestId: typeof props.label === "string" ? props.label : undefined, disabled: disabled, fieldSize: fieldSize, optionLabelDescription: getOptionLabelDescription?.(props.data), optionPrefix: getOptionPrefix?.(props.data) })) : (jsx(SingleSelectMenuItem, { ...componentProps, dataTestId: typeof props.label === "string" ? props.label : undefined, disabled: disabled || props.isDisabled, fieldSize: fieldSize, optionLabelDescription: getOptionLabelDescription?.(props.data), optionPrefix: getOptionPrefix?.(props.data) })) }));
|
|
1271
|
+
},
|
|
1272
|
+
...componentsProps,
|
|
1273
|
+
};
|
|
1274
|
+
}, [
|
|
1275
|
+
componentsProps,
|
|
1276
|
+
maxSelectedDisplayCount,
|
|
1277
|
+
disabled,
|
|
1278
|
+
setMenuIsEnabled,
|
|
1279
|
+
readOnly,
|
|
1280
|
+
dataTestId,
|
|
1281
|
+
t,
|
|
1282
|
+
prefix,
|
|
1283
|
+
hasError,
|
|
1284
|
+
getOptionLabelDescription,
|
|
1285
|
+
fieldSize,
|
|
1286
|
+
getOptionPrefix,
|
|
1287
|
+
]);
|
|
1288
|
+
return customComponents;
|
|
1289
|
+
};
|
|
1290
|
+
|
|
1291
|
+
/**
|
|
1292
|
+
* @template IsMulti
|
|
1293
|
+
* @template Group
|
|
1294
|
+
* @param {RefObject<HTMLDivElement | null>} refContainer react ref to container element
|
|
1295
|
+
* @param {number | undefined} maxSelectedDisplayCount a number of max display count
|
|
1296
|
+
* @param {StylesConfig<Option, IsMulti, Group> | undefined} styles a optional object to override styles of react-select
|
|
1297
|
+
* @returns {StylesConfig<Option, boolean>} styles to override in select
|
|
1298
|
+
*/
|
|
1299
|
+
const useCustomStyles = ({ refContainer, maxSelectedDisplayCount, styles, disabled, fieldSize, }) => {
|
|
1300
|
+
const customStyles = useMemo(() => {
|
|
1301
|
+
return {
|
|
1302
|
+
control: base => {
|
|
1303
|
+
return {
|
|
1304
|
+
...base,
|
|
1305
|
+
minHeight: fieldSize === "small" ? "28px" : fieldSize === "large" ? "40px" : "32px",
|
|
1306
|
+
borderRadius: "var(--border-radius-lg)",
|
|
1307
|
+
backgroundColor: "inherit",
|
|
1308
|
+
};
|
|
1309
|
+
},
|
|
1310
|
+
singleValue: base => ({
|
|
1311
|
+
...base,
|
|
1312
|
+
}),
|
|
1313
|
+
multiValue: base => ({
|
|
1314
|
+
...base,
|
|
1315
|
+
}),
|
|
1316
|
+
multiValueLabel: base => ({
|
|
1317
|
+
...base,
|
|
1318
|
+
}),
|
|
1319
|
+
indicatorsContainer: base => ({
|
|
1320
|
+
...base,
|
|
1321
|
+
...(disabled && { display: "none" }),
|
|
1322
|
+
}),
|
|
1323
|
+
indicatorSeparator: () => ({
|
|
1324
|
+
width: "0px",
|
|
1325
|
+
}),
|
|
1326
|
+
menu: base => {
|
|
1327
|
+
return {
|
|
1328
|
+
...base,
|
|
1329
|
+
width: "100%",
|
|
1330
|
+
marginTop: "4px",
|
|
1331
|
+
marginBottom: "18px",
|
|
1332
|
+
transition: "all 1s ease-in-out",
|
|
1333
|
+
};
|
|
1334
|
+
},
|
|
1335
|
+
input: base => ({
|
|
1336
|
+
...base,
|
|
1337
|
+
marginLeft: "0px",
|
|
1338
|
+
}),
|
|
1339
|
+
placeholder: base => ({
|
|
1340
|
+
...base,
|
|
1341
|
+
}),
|
|
1342
|
+
option: () => ({}),
|
|
1343
|
+
menuPortal: base => ({
|
|
1344
|
+
...base,
|
|
1345
|
+
width: refContainer.current ? `${refContainer.current.clientWidth}px` : base.width,
|
|
1346
|
+
backgroundColor: "#ffffff",
|
|
1347
|
+
borderRadius: "var(--border-radius-lg)",
|
|
1348
|
+
zIndex: "var(--z-overlay)",
|
|
1349
|
+
borderColor: "rgb(var(--color-neutral-300))",
|
|
1350
|
+
boxShadow: "var(--tw-ring-inset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)",
|
|
1351
|
+
}),
|
|
1352
|
+
menuList: base => {
|
|
1353
|
+
return {
|
|
1354
|
+
...base,
|
|
1355
|
+
position: "relative",
|
|
1356
|
+
padding: "var(--spacing-1)",
|
|
1357
|
+
display: "grid",
|
|
1358
|
+
gap: "var(--spacing-1)",
|
|
1359
|
+
width: "100%",
|
|
1360
|
+
borderRadius: "0px",
|
|
1361
|
+
boxShadow: "none",
|
|
1362
|
+
paddingTop: "0px",
|
|
1363
|
+
};
|
|
1364
|
+
},
|
|
1365
|
+
valueContainer: base => {
|
|
1366
|
+
return {
|
|
1367
|
+
...base,
|
|
1368
|
+
paddingBlock: 0,
|
|
1369
|
+
flexWrap: maxSelectedDisplayCount !== undefined ? "wrap" : "nowrap",
|
|
1370
|
+
gap: "0.25rem",
|
|
1371
|
+
};
|
|
1372
|
+
},
|
|
1373
|
+
container: base => ({
|
|
1374
|
+
...base,
|
|
1375
|
+
width: "100%",
|
|
1376
|
+
}),
|
|
1377
|
+
dropdownIndicator: base => ({
|
|
1378
|
+
...base,
|
|
1379
|
+
padding: "0px",
|
|
1380
|
+
}),
|
|
1381
|
+
clearIndicator: base => {
|
|
1382
|
+
return {
|
|
1383
|
+
...base,
|
|
1384
|
+
padding: "0px",
|
|
1385
|
+
};
|
|
1386
|
+
},
|
|
1387
|
+
...styles,
|
|
1388
|
+
};
|
|
1389
|
+
}, [refContainer, disabled, fieldSize, maxSelectedDisplayCount, styles]);
|
|
1390
|
+
return { customStyles };
|
|
1391
|
+
};
|
|
1392
|
+
|
|
1393
|
+
/**
|
|
1394
|
+
* A hook used by selects to share the common code
|
|
1395
|
+
*
|
|
1396
|
+
* @param {SelectProps} props - The props for the Select component
|
|
1397
|
+
* @returns {UseSelectProps} Select component
|
|
1398
|
+
*/
|
|
1399
|
+
const useSelect = ({ id, className, dataTestId = "select", prefix, async, maxMenuHeight = 200, label, hasError, disabled, isMulti, components, value, options, onChange, isLoading, classNamePrefix = "", onMenuOpen, onMenuClose, maxSelectedDisplayCount = undefined, isClearable = false, isSearchable = true, onMenuScrollToBottom, styles, filterOption, onInputChange, getOptionLabelDescription, getOptionPrefix, fieldSize = "medium", ...props }) => {
|
|
1400
|
+
const refContainer = useRef(document.createElement("div"));
|
|
1401
|
+
const { customStyles } = useCustomStyles({
|
|
1402
|
+
refContainer,
|
|
1403
|
+
maxSelectedDisplayCount,
|
|
1404
|
+
styles,
|
|
1405
|
+
disabled: Boolean(disabled),
|
|
1406
|
+
fieldSize,
|
|
1407
|
+
});
|
|
1408
|
+
const [menuIsOpen, setMenuIsOpen] = useState(props.menuIsOpen ?? false);
|
|
1409
|
+
const [menuIsEnabled, setMenuIsEnabled] = useState(true);
|
|
1410
|
+
const customComponents = useCustomComponents({
|
|
1411
|
+
componentsProps: components,
|
|
1412
|
+
disabled: Boolean(disabled),
|
|
1413
|
+
readOnly: Boolean(props.readOnly),
|
|
1414
|
+
setMenuIsEnabled,
|
|
1415
|
+
dataTestId,
|
|
1416
|
+
maxSelectedDisplayCount,
|
|
1417
|
+
prefix,
|
|
1418
|
+
hasError,
|
|
1419
|
+
fieldSize,
|
|
1420
|
+
getOptionLabelDescription,
|
|
1421
|
+
getOptionPrefix,
|
|
1422
|
+
});
|
|
1423
|
+
const menuPlacement = "auto";
|
|
1424
|
+
const openMenuHandler = async () => {
|
|
1425
|
+
onMenuOpen?.();
|
|
1426
|
+
if (menuIsEnabled) {
|
|
1427
|
+
setMenuIsOpen(true);
|
|
1428
|
+
}
|
|
1429
|
+
else {
|
|
1430
|
+
setMenuIsEnabled(true);
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
const closeMenuHandler = () => {
|
|
1434
|
+
setMenuIsOpen(false);
|
|
1435
|
+
onMenuClose && onMenuClose();
|
|
1436
|
+
};
|
|
1437
|
+
return {
|
|
1438
|
+
refContainer,
|
|
1439
|
+
customStyles,
|
|
1440
|
+
menuIsOpen,
|
|
1441
|
+
customComponents,
|
|
1442
|
+
menuPlacement,
|
|
1443
|
+
openMenuHandler,
|
|
1444
|
+
closeMenuHandler,
|
|
1445
|
+
};
|
|
1446
|
+
};
|
|
1447
|
+
|
|
1448
|
+
// This is here to ensure the bundled react-components can expose the react-select for jest in external iris apps.
|
|
1449
|
+
const ReactSyncSelect = ReactSelect.default || ReactSelect;
|
|
1450
|
+
/**
|
|
1451
|
+
* Selects are input components used to choose a value from a set.
|
|
1452
|
+
*
|
|
1453
|
+
* @param {SelectProps} props - The props for the Select component
|
|
1454
|
+
* @returns {ReactElement} Select component
|
|
1455
|
+
*/
|
|
1456
|
+
const BaseSelect = (props) => {
|
|
1457
|
+
const { id, dataTestId = "select", prefix, async, maxMenuHeight = 200, label, hasError, disabled, isMulti, menuPosition = "absolute", value, options, onChange, isLoading, classNamePrefix = "select", onMenuScrollToBottom, onInputChange, isSearchable, isClearable = false, readOnly, fieldSize = "medium", openMenuOnClick = !disabled, openMenuOnFocus = false, hideSelectedOptions = false, } = props;
|
|
1458
|
+
const { refContainer, customStyles, menuIsOpen, customComponents, menuPlacement, openMenuHandler, closeMenuHandler } = useSelect(props);
|
|
1459
|
+
const reactSelectProps = useMemo(() => ({
|
|
1460
|
+
value,
|
|
1461
|
+
menuPlacement,
|
|
1462
|
+
maxMenuHeight,
|
|
1463
|
+
onChange,
|
|
1464
|
+
"aria-label": label,
|
|
1465
|
+
"data-testid": dataTestId,
|
|
1466
|
+
components: customComponents,
|
|
1467
|
+
styles: customStyles,
|
|
1468
|
+
tabSelectsValue: false,
|
|
1469
|
+
blurInputOnSelect: false,
|
|
1470
|
+
// This configuration allows for more flexible positioning control of the dropdown.
|
|
1471
|
+
// Setting menuPortalTarget to 'null' specifies that the dropdown should be rendered within
|
|
1472
|
+
// the parent element instead of 'document.body'.
|
|
1473
|
+
menuPortalTarget: props.menuPortalTarget !== undefined ? props.menuPortalTarget : document.body,
|
|
1474
|
+
isSearchable: disabled || readOnly ? false : isSearchable,
|
|
1475
|
+
menuShouldBlockScroll: true,
|
|
1476
|
+
menuShouldScrollIntoView: true,
|
|
1477
|
+
openMenuOnFocus,
|
|
1478
|
+
menuIsOpen: !readOnly ? menuIsOpen : false,
|
|
1479
|
+
openMenuOnClick,
|
|
1480
|
+
closeMenuOnSelect: !isMulti,
|
|
1481
|
+
isMulti,
|
|
1482
|
+
classNamePrefix,
|
|
1483
|
+
isLoading,
|
|
1484
|
+
isClearable,
|
|
1485
|
+
id,
|
|
1486
|
+
onMenuScrollToBottom,
|
|
1487
|
+
onInputChange,
|
|
1488
|
+
hideSelectedOptions,
|
|
1489
|
+
isDisabled: Boolean(disabled),
|
|
1490
|
+
}), [
|
|
1491
|
+
classNamePrefix,
|
|
1492
|
+
customComponents,
|
|
1493
|
+
customStyles,
|
|
1494
|
+
dataTestId,
|
|
1495
|
+
disabled,
|
|
1496
|
+
hideSelectedOptions,
|
|
1497
|
+
id,
|
|
1498
|
+
isClearable,
|
|
1499
|
+
isLoading,
|
|
1500
|
+
isMulti,
|
|
1501
|
+
isSearchable,
|
|
1502
|
+
label,
|
|
1503
|
+
maxMenuHeight,
|
|
1504
|
+
menuIsOpen,
|
|
1505
|
+
menuPlacement,
|
|
1506
|
+
onChange,
|
|
1507
|
+
onInputChange,
|
|
1508
|
+
onMenuScrollToBottom,
|
|
1509
|
+
openMenuOnClick,
|
|
1510
|
+
openMenuOnFocus,
|
|
1511
|
+
props.menuPortalTarget,
|
|
1512
|
+
readOnly,
|
|
1513
|
+
value,
|
|
1514
|
+
]);
|
|
1515
|
+
const renderAsDisabled = Boolean(props.disabled) || props.readOnly;
|
|
1516
|
+
return (jsxs("div", { className: cvaSelect({
|
|
1517
|
+
invalid: hasError,
|
|
1518
|
+
fieldSize: fieldSize,
|
|
1519
|
+
disabled: renderAsDisabled,
|
|
1520
|
+
className: props.className,
|
|
1521
|
+
}), "data-testid": dataTestId, ref: refContainer, children: [prefix !== undefined ? (jsx("div", { className: cvaSelectPrefixSuffix({ kind: "prefix" }), "data-testid": dataTestId ? `${dataTestId}-prefix` : null, children: prefix })) : null, async ? (jsx(ReactAsyncSelect, { ...props, ...reactSelectProps, ...async, menuPosition: menuPosition, onMenuClose: closeMenuHandler, onMenuOpen: openMenuHandler, placeholder: renderAsDisabled ? null : props.placeholder })) : (jsx(ReactSyncSelect, { ...props, ...reactSelectProps, isMulti: isMulti, menuPosition: menuPosition, onMenuClose: closeMenuHandler, onMenuOpen: openMenuHandler, options: options, placeholder: renderAsDisabled ? null : props.placeholder })), typeof props.disabled === "object" ? (jsx("div", { className: cvaSelectPrefixSuffix({ kind: "suffix" }), "data-testid": dataTestId ? `${dataTestId}-locked` : null, children: jsx(InputLockReasonTooltip, { ...props.disabled }) })) : null] }));
|
|
1522
|
+
};
|
|
1523
|
+
BaseSelect.displayName = "BaseSelect";
|
|
1524
|
+
|
|
1525
|
+
/**
|
|
1526
|
+
* CreatableSelects are input components used to choose a value from a set.
|
|
1527
|
+
*
|
|
1528
|
+
* @param {CreatableSelectProps} props - The props for the CreatableSelect component
|
|
1529
|
+
* @returns {ReactElement} CreatableSelect component
|
|
1530
|
+
*/
|
|
1531
|
+
const CreatableSelect = (props) => {
|
|
1532
|
+
const { id, dataTestId = "creatableSelect", prefix, async, maxMenuHeight = 200, label, hasError, disabled, isMulti, value, options, onChange, isLoading, classNamePrefix = "creatableSelect", onMenuScrollToBottom, onInputChange, isSearchable, isClearable = false, readOnly, openMenuOnClick = !disabled, openMenuOnFocus = !disabled, allowCreateWhileLoading, onCreateOption, } = props;
|
|
1533
|
+
const { refContainer, customStyles, menuIsOpen, customComponents, menuPlacement, openMenuHandler, closeMenuHandler } = useSelect(props);
|
|
1534
|
+
const reactCreatableSelectProps = useMemo(() => ({
|
|
1535
|
+
value,
|
|
1536
|
+
menuPlacement,
|
|
1537
|
+
maxMenuHeight,
|
|
1538
|
+
onChange,
|
|
1539
|
+
"aria-label": label,
|
|
1540
|
+
"data-testid": dataTestId,
|
|
1541
|
+
components: customComponents,
|
|
1542
|
+
styles: customStyles,
|
|
1543
|
+
tabSelectsValue: false,
|
|
1544
|
+
blurInputOnSelect: !isMulti,
|
|
1545
|
+
menuPortalTarget: props.menuPortalTarget || document.body,
|
|
1546
|
+
isSearchable: disabled || readOnly ? false : isSearchable,
|
|
1547
|
+
menuShouldBlockScroll: true,
|
|
1548
|
+
menuShouldScrollIntoView: true,
|
|
1549
|
+
openMenuOnFocus,
|
|
1550
|
+
menuIsOpen: !readOnly ? menuIsOpen : false,
|
|
1551
|
+
openMenuOnClick,
|
|
1552
|
+
closeMenuOnSelect: false,
|
|
1553
|
+
isMulti,
|
|
1554
|
+
classNamePrefix,
|
|
1555
|
+
isLoading,
|
|
1556
|
+
isClearable,
|
|
1557
|
+
id,
|
|
1558
|
+
onMenuScrollToBottom,
|
|
1559
|
+
onInputChange,
|
|
1560
|
+
allowCreateWhileLoading,
|
|
1561
|
+
onCreateOption,
|
|
1562
|
+
isDisabled: Boolean(disabled),
|
|
1563
|
+
}), [
|
|
1564
|
+
allowCreateWhileLoading,
|
|
1565
|
+
classNamePrefix,
|
|
1566
|
+
customComponents,
|
|
1567
|
+
customStyles,
|
|
1568
|
+
dataTestId,
|
|
1569
|
+
disabled,
|
|
1570
|
+
id,
|
|
1571
|
+
isClearable,
|
|
1572
|
+
isLoading,
|
|
1573
|
+
isMulti,
|
|
1574
|
+
isSearchable,
|
|
1575
|
+
label,
|
|
1576
|
+
maxMenuHeight,
|
|
1577
|
+
menuIsOpen,
|
|
1578
|
+
menuPlacement,
|
|
1579
|
+
onChange,
|
|
1580
|
+
onCreateOption,
|
|
1581
|
+
onInputChange,
|
|
1582
|
+
onMenuScrollToBottom,
|
|
1583
|
+
openMenuOnClick,
|
|
1584
|
+
openMenuOnFocus,
|
|
1585
|
+
props.menuPortalTarget,
|
|
1586
|
+
readOnly,
|
|
1587
|
+
value,
|
|
1588
|
+
]);
|
|
1589
|
+
const renderAsDisabled = Boolean(props.disabled) || props.readOnly;
|
|
1590
|
+
return (jsxs("div", { className: cvaSelect({ invalid: hasError, disabled: renderAsDisabled, className: props.className }), "data-testid": dataTestId, ref: refContainer, children: [prefix !== undefined ? (jsx("div", { className: cvaSelectPrefixSuffix({ kind: "prefix" }), "data-testid": dataTestId ? `${dataTestId}-prefix` : null, children: prefix })) : null, async ? (jsx(ReactAsyncCreatableSelect, { ...props, ...reactCreatableSelectProps, ...async, onMenuClose: closeMenuHandler, onMenuOpen: openMenuHandler, placeholder: renderAsDisabled ? null : props.placeholder })) : (jsx(ReactCreatableSelect, { ...props, ...reactCreatableSelectProps, hideSelectedOptions: false, isMulti: isMulti, onMenuClose: closeMenuHandler, onMenuOpen: openMenuHandler, options: options, placeholder: renderAsDisabled ? null : props.placeholder })), typeof props.disabled === "object" ? (jsx("div", { className: cvaSelectPrefixSuffix({ kind: "suffix" }), "data-testid": dataTestId ? `${dataTestId}-locked` : null, children: jsx(InputLockReasonTooltip, { ...props.disabled }) })) : null] }));
|
|
1591
|
+
};
|
|
1592
|
+
CreatableSelect.displayName = "CreatableSelect";
|
|
1593
|
+
|
|
1594
|
+
/**
|
|
1595
|
+
* The Label component is used for labels for input fields.
|
|
1596
|
+
* This component is **not used directly**, but is part of the FormGroup and Field components.
|
|
1597
|
+
*
|
|
1598
|
+
* @param {LabelProps} props - The props for the Label component
|
|
1599
|
+
* @returns {ReactElement} Label component
|
|
1600
|
+
*/
|
|
1601
|
+
const Label = ({ id, htmlFor, children, className, dataTestId, disabled, isInvalid, }) => {
|
|
1602
|
+
return (jsx("label", { className: cvaLabel({ invalid: isInvalid, disabled, className }), "data-testid": dataTestId, htmlFor: htmlFor || "", id: id || "", children: children }));
|
|
1603
|
+
};
|
|
1604
|
+
|
|
1605
|
+
const cvaFormGroup = cvaMerge(["component-formGroup-gap", "group", "form-group"]);
|
|
1606
|
+
const cvaFormGroupContainerBefore = cvaMerge(["flex", "mb-1", "items-center"]);
|
|
1607
|
+
const cvaFormGroupContainerAfter = cvaMerge(["flex", "justify-between", "mt-1", "text-xs", "text-neutral-500"], {
|
|
1608
|
+
variants: {
|
|
1609
|
+
invalid: {
|
|
1610
|
+
true: "text-danger-500",
|
|
1611
|
+
false: "",
|
|
1612
|
+
},
|
|
1613
|
+
isWarning: {
|
|
1614
|
+
true: "text-default-500 ",
|
|
1615
|
+
false: "",
|
|
1616
|
+
},
|
|
1617
|
+
},
|
|
1618
|
+
compoundVariants: [
|
|
1619
|
+
{
|
|
1620
|
+
invalid: true,
|
|
1621
|
+
isWarning: true,
|
|
1622
|
+
className: "text-danger-500 ", // Ensures that 'invalid' takes precedence
|
|
1623
|
+
},
|
|
1624
|
+
],
|
|
1625
|
+
});
|
|
1626
|
+
const cvaHelpAddon = cvaMerge(["ml-auto"]);
|
|
1627
|
+
|
|
1628
|
+
/**
|
|
1629
|
+
* The FormGroup component should be used to wrap any Input element that needs a label.
|
|
1630
|
+
* Besides a label the component supplies an optional Tooltip, HelpText and HelpAddon support.
|
|
1631
|
+
*
|
|
1632
|
+
* @param {FormGroupProps} props - The props for the FormGroup component
|
|
1633
|
+
* @returns {ReactElement} FormGroup component
|
|
1634
|
+
*/
|
|
1635
|
+
const FormGroup = ({ isInvalid, isWarning, helpText, helpAddon, tip, className, dataTestId, label, htmlFor, children, required = false, }) => {
|
|
1636
|
+
const [t] = useTranslation();
|
|
1637
|
+
const validationStateIcon = useMemo(() => {
|
|
1638
|
+
const color = isInvalid ? "danger" : isWarning ? "warning" : null;
|
|
1639
|
+
return color ? jsx(Icon, { color: color, name: "ExclamationTriangle", size: "small" }) : null;
|
|
1640
|
+
}, [isInvalid, isWarning]);
|
|
1641
|
+
return (jsxs("div", { className: cvaFormGroup({ className }), "data-testid": dataTestId, children: [label ? (jsxs("div", { className: cvaFormGroupContainerBefore(), children: [jsxs(Fragment, { children: [jsx(Label, { className: "component-formGroup-font", dataTestId: dataTestId ? `${dataTestId}-label` : undefined, htmlFor: htmlFor, id: htmlFor + "-label", children: label }), required ? (jsx(Tooltip, { "data-testid": "required-asterisk", label: t("field.required.asterisk.tooltip"), children: "*" })) : null] }), tip ? (jsx(Tooltip, { className: "ml-1", dataTestId: dataTestId ? `${dataTestId}-tooltip` : undefined, label: tip, placement: "bottom" })) : null] })) : null, children, helpText || helpAddon ? (jsxs("div", { className: cvaFormGroupContainerAfter({ invalid: isInvalid, isWarning: isWarning }), children: [helpText ? (jsxs("div", { className: "flex gap-1", children: [validationStateIcon, jsx("span", { "data-testid": dataTestId ? `${dataTestId}-helpText` : undefined, children: helpText })] })) : undefined, helpAddon ? (jsx("span", { className: cvaHelpAddon(), "data-testid": dataTestId ? `${dataTestId}-helpAddon` : null, children: helpAddon })) : null] })) : null] }));
|
|
1642
|
+
};
|
|
1643
|
+
|
|
1644
|
+
/**
|
|
1645
|
+
* The checkbox field component is used for entering boolean values.
|
|
1646
|
+
*
|
|
1647
|
+
* _**Do use**_ the CheckboxField for boolean input.
|
|
1648
|
+
*/
|
|
1649
|
+
const CheckboxField = ({ label, id, tip, helpText, helpAddon, isInvalid, className, checked, dataTestId, checkboxLabel, onChange, ref, ...rest }) => {
|
|
1650
|
+
const htmlForId = id ? id : "checkboxField-" + uuidv4();
|
|
1651
|
+
return (jsx(FormGroup, { className: "flex flex-col gap-1", dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: helpText, htmlFor: htmlForId, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(Checkbox, { checked: checked, className: className, dataTestId: dataTestId, id: htmlForId, label: checkboxLabel, onChange: onChange, ref: ref, ...rest }) }));
|
|
1652
|
+
};
|
|
1653
|
+
CheckboxField.displayName = "CheckboxField";
|
|
1654
|
+
|
|
1655
|
+
/**
|
|
1656
|
+
*
|
|
1657
|
+
* @param inputValue - value to check if it is a string
|
|
1658
|
+
* @returns {boolean} - true if value is a string
|
|
1659
|
+
*/
|
|
1660
|
+
const isString = (inputValue) => {
|
|
1661
|
+
return typeof inputValue === "string";
|
|
1662
|
+
};
|
|
1663
|
+
/**
|
|
1664
|
+
*
|
|
1665
|
+
* @param inputValue - value to check if it is a number
|
|
1666
|
+
* @returns {boolean} - true if value is a number
|
|
1667
|
+
*/
|
|
1668
|
+
const isNumber = (inputValue) => {
|
|
1669
|
+
return typeof inputValue === "number";
|
|
1670
|
+
};
|
|
1671
|
+
|
|
1672
|
+
/**
|
|
1673
|
+
* Validates a url
|
|
1674
|
+
*/
|
|
1675
|
+
const validateColorCode = (colorCode, required) => {
|
|
1676
|
+
if (!colorCode && !required) {
|
|
1677
|
+
return undefined;
|
|
1678
|
+
}
|
|
1679
|
+
if (!colorCode && required) {
|
|
1680
|
+
return "REQUIRED";
|
|
1681
|
+
}
|
|
1682
|
+
if (colorCode && isString(colorCode) && isValidHEXColor(colorCode)) {
|
|
1683
|
+
return undefined;
|
|
1684
|
+
}
|
|
1685
|
+
return "INVALID_HEX_CODE";
|
|
1686
|
+
};
|
|
1687
|
+
|
|
1688
|
+
const cvaInputColorField = cvaMerge([
|
|
1689
|
+
"ml-3",
|
|
1690
|
+
"h-4",
|
|
1691
|
+
"w-4",
|
|
1692
|
+
"self-center",
|
|
1693
|
+
"bg-inherit",
|
|
1694
|
+
"disabled:opacity-50",
|
|
1695
|
+
"disabled:pointer-events-none",
|
|
1696
|
+
"rounded-[4px]",
|
|
1697
|
+
], {
|
|
1698
|
+
variants: {
|
|
1699
|
+
readOnly: {
|
|
1700
|
+
true: "pointer-events-none",
|
|
1701
|
+
false: "",
|
|
1702
|
+
},
|
|
1703
|
+
},
|
|
1704
|
+
compoundVariants: [
|
|
1705
|
+
{
|
|
1706
|
+
readOnly: true,
|
|
1707
|
+
},
|
|
1708
|
+
],
|
|
1709
|
+
defaultVariants: {
|
|
1710
|
+
readOnly: false,
|
|
1711
|
+
},
|
|
1712
|
+
});
|
|
1713
|
+
|
|
1714
|
+
/**
|
|
1715
|
+
* Validates if the given value is a valid hex color.
|
|
1716
|
+
*
|
|
1717
|
+
* @param value - The string value to be validated.
|
|
1718
|
+
* @returns {boolean} True if the value is a valid hex color, otherwise false.
|
|
1719
|
+
*/
|
|
1720
|
+
const isValidHEXColor = (value) => {
|
|
1721
|
+
const hexRegex = /^#([0-9A-F]{6})$/i;
|
|
1722
|
+
return hexRegex.test(value);
|
|
1723
|
+
};
|
|
1724
|
+
/**
|
|
1725
|
+
* The ColorField component is used to enter color.
|
|
1057
1726
|
* ColorField validates that user enters a valid color address.
|
|
1058
1727
|
*
|
|
1059
1728
|
*/
|
|
@@ -1310,6 +1979,73 @@ const EmailField = ({ label, id, tip, helpText, errorMessage, helpAddon, classNa
|
|
|
1310
1979
|
};
|
|
1311
1980
|
EmailField.displayName = "EmailField";
|
|
1312
1981
|
|
|
1982
|
+
/** Type guard for function refs vs. object refs without deprecated types or assertions */
|
|
1983
|
+
function isWritableRef(r) {
|
|
1984
|
+
return typeof r === "object" && r !== null && "current" in r;
|
|
1985
|
+
}
|
|
1986
|
+
/**
|
|
1987
|
+
* Multi adapter:
|
|
1988
|
+
* - keeps Option[] semantics (via `MultiValue<Option>`)
|
|
1989
|
+
* - renders FormGroup chrome (label, help, error)
|
|
1990
|
+
* - exposes a hidden <select> for a stable ref target
|
|
1991
|
+
* - optionally renders one hidden <input> per selected option IF `getOptionValue` is provided
|
|
1992
|
+
* - passes through all remaining BaseSelect props with isMulti=true
|
|
1993
|
+
*/
|
|
1994
|
+
const FormFieldSelectAdapterMulti = (props) => {
|
|
1995
|
+
const { className, dataTestId, helpText, helpAddon, tip, label, isInvalid, errorMessage, name, onBlur, options, value, defaultValue, id, onChange, children, ref, ...selectProps } = props;
|
|
1996
|
+
// Hidden select for a stable DOM ref target (API parity with single adapter)
|
|
1997
|
+
const innerRef = useRef(null);
|
|
1998
|
+
// Bridge external ref (supports both callback and object refs)
|
|
1999
|
+
useEffect(() => {
|
|
2000
|
+
if (typeof ref === "function") {
|
|
2001
|
+
ref(innerRef.current);
|
|
2002
|
+
}
|
|
2003
|
+
else if (isWritableRef(ref)) {
|
|
2004
|
+
ref.current = innerRef.current;
|
|
2005
|
+
}
|
|
2006
|
+
}, [ref]);
|
|
2007
|
+
// Determine invalid state
|
|
2008
|
+
const renderAsInvalid = useMemo(() => (isInvalid === undefined ? Boolean(errorMessage) : isInvalid), [errorMessage, isInvalid]);
|
|
2009
|
+
// id to connect label and control
|
|
2010
|
+
const controlId = useMemo(() => (id ? id : "multiSelectField-" + uuidv4()), [id]);
|
|
2011
|
+
// If consumers provided getOptionValue (from BaseSelect props),
|
|
2012
|
+
// we can render hidden inputs for native form submit / RHF.
|
|
2013
|
+
const selectPropsWithAccessors = selectProps;
|
|
2014
|
+
const getOptionValue = typeof selectPropsWithAccessors.getOptionValue === "function" ? selectPropsWithAccessors.getOptionValue : undefined;
|
|
2015
|
+
// Compute selected options snapshot for hidden inputs (prefer controlled `value`)
|
|
2016
|
+
const selectedOptions = useMemo(() => value ?? defaultValue ?? [], [value, defaultValue]);
|
|
2017
|
+
// Build the exact prop bag for BaseSelect (multi=true).
|
|
2018
|
+
const childProps = {
|
|
2019
|
+
...selectProps,
|
|
2020
|
+
id: controlId,
|
|
2021
|
+
onBlur,
|
|
2022
|
+
options,
|
|
2023
|
+
isMulti: true,
|
|
2024
|
+
value: value ?? null,
|
|
2025
|
+
defaultValue,
|
|
2026
|
+
onChange: next => onChange?.(next),
|
|
2027
|
+
};
|
|
2028
|
+
return (jsxs(FormGroup, { className: className, dataTestId: dataTestId, helpAddon: helpAddon, helpText: (renderAsInvalid && errorMessage) || helpText, htmlFor: controlId, isInvalid: renderAsInvalid, label: label, required: "required" in selectProps && selectProps.required
|
|
2029
|
+
? !(("disabled" in selectProps && Boolean(selectProps.disabled)) ||
|
|
2030
|
+
("readOnly" in selectProps && Boolean(selectProps.readOnly)))
|
|
2031
|
+
: false, tip: tip, children: [jsx("select", { "aria-hidden": "true", defaultValue: "", hidden: true, name: name, ref: innerRef }), typeof getOptionValue === "function" &&
|
|
2032
|
+
selectedOptions.map((opt, idx) => {
|
|
2033
|
+
const primitiveValue = getOptionValue(opt);
|
|
2034
|
+
return typeof primitiveValue === "string" ? (jsx("input", { name: name, type: "hidden", value: primitiveValue }, `${primitiveValue}-${idx}`)) : null;
|
|
2035
|
+
}), children(childProps)] }));
|
|
2036
|
+
};
|
|
2037
|
+
FormFieldSelectAdapterMulti.displayName = "FormFieldSelectAdapterMulti";
|
|
2038
|
+
|
|
2039
|
+
/**
|
|
2040
|
+
* MultiSelectField — validated multi-select field.
|
|
2041
|
+
* Types mirror BaseSelect: options: Option[], value/defaultValue: Option[], onChange: (Option[] | null) => void
|
|
2042
|
+
* Implemented as a generic const component (no forwardRef, no assertions).
|
|
2043
|
+
*/
|
|
2044
|
+
const MultiSelectField = ({ ref, ...props }) => {
|
|
2045
|
+
return (jsx(FormFieldSelectAdapterMulti, { ...props, ref: ref, children: convertedProps => jsx(BaseSelect, { ...convertedProps }) }));
|
|
2046
|
+
};
|
|
2047
|
+
MultiSelectField.displayName = "MultiSelectField";
|
|
2048
|
+
|
|
1313
2049
|
const isNumberValid = (number) => {
|
|
1314
2050
|
if (!isNaN(+number) === false) {
|
|
1315
2051
|
return false;
|
|
@@ -1351,1333 +2087,664 @@ const validateNumber = (number, required = false, min, max) => {
|
|
|
1351
2087
|
}
|
|
1352
2088
|
// if the value is greater than max
|
|
1353
2089
|
if (isNumberValid(parsedNumber) && maxValue !== undefined && parsedNumber > maxValue) {
|
|
1354
|
-
return "LESS_THAN";
|
|
1355
|
-
}
|
|
1356
|
-
// if the value is a number and is valid
|
|
1357
|
-
if (isNumber(parsedNumber) && isNumberValid(parsedNumber)) {
|
|
1358
|
-
return undefined;
|
|
1359
|
-
}
|
|
1360
|
-
return "INVALID_NUMBER";
|
|
1361
|
-
};
|
|
1362
|
-
|
|
1363
|
-
/**
|
|
1364
|
-
* The number field component is used for entering numeric values and includes controls for incrementally increasing or decreasing the value.
|
|
1365
|
-
*
|
|
1366
|
-
* _**Do use**_ the NumberField when the controls to incrementally increase or decrease makes the task easier for the user.
|
|
1367
|
-
*
|
|
1368
|
-
* _**Do not use**_ this fields for non-serialized numbers. Use TextField instead.
|
|
1369
|
-
*/
|
|
1370
|
-
const NumberField = ({ label, id, tip, helpText, errorMessage, helpAddon, isInvalid, maxLength, className, value, dataTestId, defaultValue, onBlur, onChange, ref, ...rest }) => {
|
|
1371
|
-
const htmlForId = id ? id : "numberField-" + uuidv4();
|
|
1372
|
-
const [t] = useTranslation();
|
|
1373
|
-
const [innerValue, setInnerValue] = useState(() => {
|
|
1374
|
-
return Number(value?.toString()) || Number(defaultValue?.toString());
|
|
1375
|
-
});
|
|
1376
|
-
const [renderAsInvalid, setRenderAsInvalid] = useState((isInvalid === undefined ? Boolean(errorMessage) : isInvalid) ||
|
|
1377
|
-
!!validateNumber(value?.toString(), rest.required, rest.min, rest.max));
|
|
1378
|
-
const errorType = useMemo(() => validateNumber(innerValue, rest.required, rest.min, rest.max), [innerValue, rest.max, rest.min, rest.required]);
|
|
1379
|
-
const error = useMemo(() => {
|
|
1380
|
-
// for the case when a custom error message is provided
|
|
1381
|
-
if (errorMessage) {
|
|
1382
|
-
return errorMessage;
|
|
1383
|
-
}
|
|
1384
|
-
else if (errorType) {
|
|
1385
|
-
return t(`numberField.error.${errorType}`, { min: rest.min, max: rest.max });
|
|
1386
|
-
}
|
|
1387
|
-
return errorMessage;
|
|
1388
|
-
}, [errorMessage, errorType, rest.max, rest.min, t]);
|
|
1389
|
-
useEffect(() => {
|
|
1390
|
-
if (errorMessage) {
|
|
1391
|
-
setRenderAsInvalid(Boolean(errorMessage));
|
|
1392
|
-
}
|
|
1393
|
-
}, [errorMessage]);
|
|
1394
|
-
const handleBlur = useCallback(event => {
|
|
1395
|
-
const newValue = event.target.value;
|
|
1396
|
-
setInnerValue(newValue.toString());
|
|
1397
|
-
// for the case when a custom error message is provided
|
|
1398
|
-
if (errorMessage && !validateNumber(newValue, rest.required, rest.min, rest.max)) {
|
|
1399
|
-
setRenderAsInvalid(Boolean(errorMessage));
|
|
1400
|
-
}
|
|
1401
|
-
else {
|
|
1402
|
-
setRenderAsInvalid(!!validateNumber(newValue, rest.required, rest.min, rest.max));
|
|
1403
|
-
}
|
|
1404
|
-
onBlur?.(event);
|
|
1405
|
-
}, [errorMessage, onBlur, rest.max, rest.min, rest.required]);
|
|
1406
|
-
const handleChange = useCallback((event) => {
|
|
1407
|
-
setInnerValue(event.target.value);
|
|
1408
|
-
if (onChange) {
|
|
1409
|
-
onChange(event);
|
|
1410
|
-
}
|
|
1411
|
-
}, [onChange]);
|
|
1412
|
-
return (jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && error) || helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(NumberBaseInput, { "aria-labelledby": htmlForId + "-label", id: htmlForId, isInvalid: renderAsInvalid, maxLength: maxLength, onBlur: handleBlur, onChange: handleChange, ref: ref, value: value, ...rest, className: className, dataTestId: dataTestId }) }));
|
|
1413
|
-
};
|
|
1414
|
-
NumberField.displayName = "NumberField";
|
|
1415
|
-
|
|
1416
|
-
const cvaOptionCardLabel = cvaMerge([
|
|
1417
|
-
"group",
|
|
1418
|
-
"transition",
|
|
1419
|
-
"bg-white",
|
|
1420
|
-
"outline",
|
|
1421
|
-
"outline-1",
|
|
1422
|
-
"outline-neutral-300",
|
|
1423
|
-
"hover:bg-neutral-100",
|
|
1424
|
-
"focus:bg-neutral-200",
|
|
1425
|
-
"active:bg-neutral-200",
|
|
1426
|
-
"peer-checked:bg-primary-50",
|
|
1427
|
-
"peer-checked:outline-primary-600",
|
|
1428
|
-
"peer-checked:outline-2",
|
|
1429
|
-
"flex",
|
|
1430
|
-
"gap-2",
|
|
1431
|
-
"justify-center",
|
|
1432
|
-
"items-center",
|
|
1433
|
-
"text-center",
|
|
1434
|
-
"rounded-md",
|
|
1435
|
-
"relative",
|
|
1436
|
-
], {
|
|
1437
|
-
variants: {
|
|
1438
|
-
disabled: {
|
|
1439
|
-
true: ["cursor-not-allowed", "bg-neutral-100"],
|
|
1440
|
-
false: ["cursor-pointer"],
|
|
1441
|
-
},
|
|
1442
|
-
layout: {
|
|
1443
|
-
default: ["flex-col", "p-responsive-space", "w-full", "aspect-square"],
|
|
1444
|
-
compact: ["px-3", "py-1.5", "h-8", "min-h-[calc(var(--line-height-sm)+var(--spacing-3))]", "flex-row", "w-fit"],
|
|
1445
|
-
},
|
|
1446
|
-
},
|
|
1447
|
-
});
|
|
1448
|
-
const cvaOptionCardContent = cvaMerge(["flex", "flex-col", "items-center"]);
|
|
1449
|
-
const cvaOptionCardContainer = cvaMerge(["contents"]);
|
|
1450
|
-
const cvaOptionCardTitle = cvaMerge(["text-neutral-600"], {
|
|
1451
|
-
variants: {
|
|
1452
|
-
layout: {
|
|
1453
|
-
default: ["text-lg", "line-clamp-2"],
|
|
1454
|
-
compact: ["text-sm", "line-clamp-1"],
|
|
1455
|
-
},
|
|
1456
|
-
disabled: {
|
|
1457
|
-
true: ["text-neutral-400"],
|
|
1458
|
-
false: ["focus:text-neutral-800", "active:text-neutral-800"],
|
|
1459
|
-
},
|
|
1460
|
-
},
|
|
1461
|
-
});
|
|
1462
|
-
const cvaOptionCardText = cvaMerge(["text-neutral-600", "text-sm"], {
|
|
1463
|
-
variants: {
|
|
1464
|
-
type: {
|
|
1465
|
-
subheading: ["font-medium"],
|
|
1466
|
-
description: ["font-normal"],
|
|
1467
|
-
},
|
|
1468
|
-
disabled: {
|
|
1469
|
-
true: ["text-neutral-400"],
|
|
1470
|
-
false: ["focus:text-neutral-800", "active:text-neutral-800"],
|
|
1471
|
-
},
|
|
1472
|
-
},
|
|
1473
|
-
});
|
|
1474
|
-
const cvaInput = cvaMerge(["peer", "absolute", "h-0", "w-0", "opacity-0"]);
|
|
1475
|
-
const cvaCustomImage = cvaMerge(["text-neutral-400"], {
|
|
1476
|
-
variants: {
|
|
1477
|
-
disabled: {
|
|
1478
|
-
true: ["!text-neutral-400"],
|
|
1479
|
-
false: [""],
|
|
1480
|
-
},
|
|
1481
|
-
},
|
|
1482
|
-
});
|
|
1483
|
-
const cvaTag = cvaMerge([], {
|
|
1484
|
-
variants: {
|
|
1485
|
-
layout: {
|
|
1486
|
-
default: ["absolute", "top-2", "right-2"],
|
|
1487
|
-
compact: [],
|
|
1488
|
-
},
|
|
1489
|
-
},
|
|
1490
|
-
});
|
|
1491
|
-
|
|
1492
|
-
/**
|
|
1493
|
-
* A card version of a radio button that includes an icon, headings and a description.
|
|
1494
|
-
*/
|
|
1495
|
-
const OptionCard = ({ icon, heading, subheading, description, disabled, id, value, className, contentClassName, dataTestId, customImage, layout = "default", ref, tagProps, ...rest }) => {
|
|
1496
|
-
const htmlForId = id ?? "option-card-" + uuidv4();
|
|
1497
|
-
const subContent = useMemo(() => (jsxs("div", { className: cvaOptionCardContent({ className: contentClassName }), children: [subheading ? (jsx(Text, { align: "center", className: cvaOptionCardText({ type: "subheading", disabled }), type: "span", children: subheading })) : null, description ? (jsx(Text, { align: "center", className: cvaOptionCardText({ type: "description", disabled }), type: "span", children: description })) : null] })), [subheading, description, contentClassName, disabled]);
|
|
1498
|
-
return (jsx(Tooltip, { className: "w-fit", disabled: layout !== "compact" || (!subheading && !description), label: subContent, mode: "light", placement: "top", children: jsxs("div", { className: cvaOptionCardContainer(), "data-testid": dataTestId, children: [jsx("input", { className: cvaInput(), "data-testid": `${dataTestId}-option-card`, disabled: disabled, id: htmlForId, ref: ref, type: "radio", value: value, ...rest }), jsxs("label", { className: cvaOptionCardLabel({ className, disabled, layout }), "data-testid": `${dataTestId}-option-card-label`, htmlFor: htmlForId, children: [disabled && icon && !customImage
|
|
1499
|
-
? cloneElement(icon, { className: cvaCustomImage({ disabled, className: icon.props.className }) })
|
|
1500
|
-
: null, disabled && customImage ? jsx("img", { alt: "logo", className: customImage.className, src: customImage.src }) : null, !disabled && !customImage && icon, !disabled && customImage ? jsx("img", { alt: "logo", className: customImage.className, src: customImage.src }) : null, heading ? (layout === "default" ? (jsx(Heading, { className: cvaOptionCardTitle({ disabled, layout }), subtle: disabled, variant: "secondary", children: heading })) : (jsx(Text, { align: "center", className: cvaOptionCardTitle({ disabled, layout }), subtle: disabled, type: "span", weight: "thick", children: heading }))) : null, layout === "default" && (subheading || description) ? subContent : null, tagProps ? jsx(Tag, { className: cvaTag({ className: tagProps.className, layout }), ...tagProps }) : null] })] }) }));
|
|
1501
|
-
};
|
|
1502
|
-
OptionCard.displayName = "OptionCard";
|
|
1503
|
-
|
|
1504
|
-
/**
|
|
1505
|
-
* A thin wrapper around the `BaseInput` component for password input fields.
|
|
1506
|
-
*
|
|
1507
|
-
* NOTE: If shown with a label, please use the `PasswordField` component instead.
|
|
1508
|
-
*/
|
|
1509
|
-
const PasswordBaseInput = ({ ref, fieldSize, ...rest }) => {
|
|
1510
|
-
const [showPassword, setShowPassword] = useState(false);
|
|
1511
|
-
return (jsx(BaseInput, { ref: ref, ...rest, actions: jsx("div", { className: cvaActionContainer({ size: fieldSize }), children: jsx(IconButton, { className: cvaActionButton({ size: fieldSize }), icon: jsx(Icon, { name: showPassword ? "EyeSlash" : "Eye", size: "small" }), onClick: () => setShowPassword(prevState => !prevState), size: "small", variant: "secondary" }) }), type: showPassword ? "text" : "password" }));
|
|
1512
|
-
};
|
|
1513
|
-
|
|
1514
|
-
/**
|
|
1515
|
-
* Password fields enter a password or other confidential information. Characters are masked as they are typed.
|
|
1516
|
-
*
|
|
1517
|
-
* _**Do use** when the user has to input a password or something that needs to be obfuscated_
|
|
1518
|
-
*
|
|
1519
|
-
* _**Do not use** to confirm user actions, such as deleting. Use a checkbox for such flows._
|
|
1520
|
-
*/
|
|
1521
|
-
const PasswordField = ({ id, label, tip, helpText, helpAddon, errorMessage, isInvalid, maxLength, onChange, className, value, dataTestId, ref, ...rest }) => {
|
|
1522
|
-
const renderAsInvalid = isInvalid === undefined ? Boolean(errorMessage) : isInvalid;
|
|
1523
|
-
const htmlFor = id ? id : "passwordField-" + uuidv4();
|
|
1524
|
-
const handleChange = useCallback((event) => {
|
|
1525
|
-
onChange?.(event);
|
|
1526
|
-
}, [onChange]);
|
|
1527
|
-
return (jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && errorMessage) || helpText, htmlFor: htmlFor, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(PasswordBaseInput, { ...rest, "aria-labelledby": htmlFor + "-label", className: className, dataTestId: dataTestId, disabled: rest.readOnly, id: htmlFor, isInvalid: renderAsInvalid, maxLength: maxLength, onChange: handleChange, ref: ref, value: value }) }));
|
|
1528
|
-
};
|
|
1529
|
-
PasswordField.displayName = "PasswordField";
|
|
1530
|
-
|
|
1531
|
-
/**
|
|
1532
|
-
* Validates a phone number
|
|
1533
|
-
*/
|
|
1534
|
-
const validatePhoneNumber = (phoneNumber) => {
|
|
1535
|
-
if (!phoneNumber) {
|
|
1536
|
-
return "REQUIRED";
|
|
1537
|
-
}
|
|
1538
|
-
const asYouType = new AsYouType();
|
|
1539
|
-
asYouType.input(phoneNumber);
|
|
1540
|
-
const countryCode = asYouType.getCallingCode();
|
|
1541
|
-
const national = asYouType.getNationalNumber();
|
|
1542
|
-
const safePhoneNumber = getPhoneNumberWithPlus(phoneNumber.trim());
|
|
1543
|
-
const number = parsePhoneNumberFromString(safePhoneNumber);
|
|
1544
|
-
if (phoneNumber && isValidPhoneNumber(phoneNumber)) {
|
|
1545
|
-
return undefined;
|
|
1546
|
-
}
|
|
1547
|
-
if (!countryCode && national) {
|
|
1548
|
-
return "REQUIRED_COUNTRY";
|
|
1549
|
-
}
|
|
1550
|
-
if (phoneNumber &&
|
|
1551
|
-
(checkIfPhoneNumberHasPlus(phoneNumber)
|
|
1552
|
-
? isNaN(+phoneNumber.slice(1, phoneNumber.length))
|
|
1553
|
-
: isNaN(+phoneNumber) || !number)) {
|
|
1554
|
-
return "NOT_A_NUMBER";
|
|
1555
|
-
}
|
|
1556
|
-
if (safePhoneNumber.length <= 5) {
|
|
1557
|
-
//needs to be handled manually, parsePhoneNumberFromString can't parse it
|
|
1558
|
-
return "TOO_SHORT";
|
|
2090
|
+
return "LESS_THAN";
|
|
1559
2091
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
/**
|
|
1563
|
-
* Checks if the country code is valid and required
|
|
1564
|
-
*/
|
|
1565
|
-
const isInvalidCountryCode = (error, required) => (!!required && error === "REQUIRED") || error === "REQUIRED_COUNTRY";
|
|
1566
|
-
/**
|
|
1567
|
-
* Checks if the phone number is valid and required
|
|
1568
|
-
*/
|
|
1569
|
-
const isInvalidPhoneNumber = (error, required) => error !== "REQUIRED_COUNTRY" && ((!!error && error !== "REQUIRED") || (!!required && error === "REQUIRED"));
|
|
1570
|
-
/**
|
|
1571
|
-
* Checks if the phone number is valid and returns corresponding error message
|
|
1572
|
-
*/
|
|
1573
|
-
const phoneErrorMessage = (phoneNumber, required) => {
|
|
1574
|
-
if ((validatePhoneNumber(phoneNumber) === "REQUIRED" && !required) ||
|
|
1575
|
-
(validatePhoneNumber(phoneNumber) === "REQUIRED" && required && phoneNumber === undefined)) {
|
|
2092
|
+
// if the value is a number and is valid
|
|
2093
|
+
if (isNumber(parsedNumber) && isNumberValid(parsedNumber)) {
|
|
1576
2094
|
return undefined;
|
|
1577
2095
|
}
|
|
1578
|
-
return
|
|
2096
|
+
return "INVALID_NUMBER";
|
|
1579
2097
|
};
|
|
1580
2098
|
|
|
1581
2099
|
/**
|
|
1582
|
-
* The
|
|
1583
|
-
* It is a wrapper around the PhoneInput component and the FormGroup component.
|
|
1584
|
-
* It is used to render a phone number field with a label, a tip, a help text, a help addon and an error message.
|
|
2100
|
+
* The number field component is used for entering numeric values and includes controls for incrementally increasing or decreasing the value.
|
|
1585
2101
|
*
|
|
1586
|
-
*
|
|
1587
|
-
*
|
|
1588
|
-
*
|
|
1589
|
-
* @param {string} [helpAddon] - The help addon for the component.
|
|
1590
|
-
* @param {string} [errorMessage] - The error message for the component.
|
|
1591
|
-
* @param {string} [defaultValue] - The default value for the component.
|
|
1592
|
-
* @param {boolean} [disabled=false] - Whether the component is disabled or not.
|
|
1593
|
-
* @param {string} [fieldSize="medium"] - The size of the input field.
|
|
1594
|
-
* @param {boolean} [disableAction=false] - Whether the action button is disabled or not.
|
|
2102
|
+
* _**Do use**_ the NumberField when the controls to incrementally increase or decrease makes the task easier for the user.
|
|
2103
|
+
*
|
|
2104
|
+
* _**Do not use**_ this fields for non-serialized numbers. Use TextField instead.
|
|
1595
2105
|
*/
|
|
1596
|
-
const
|
|
1597
|
-
const htmlForId = id ? id : "
|
|
2106
|
+
const NumberField = ({ label, id, tip, helpText, errorMessage, helpAddon, isInvalid, maxLength, className, value, dataTestId, defaultValue, onBlur, onChange, ref, ...rest }) => {
|
|
2107
|
+
const htmlForId = id ? id : "numberField-" + uuidv4();
|
|
1598
2108
|
const [t] = useTranslation();
|
|
1599
2109
|
const [innerValue, setInnerValue] = useState(() => {
|
|
1600
|
-
return (value?.toString() || defaultValue?.toString())
|
|
2110
|
+
return Number(value?.toString()) || Number(defaultValue?.toString());
|
|
1601
2111
|
});
|
|
1602
2112
|
const [renderAsInvalid, setRenderAsInvalid] = useState((isInvalid === undefined ? Boolean(errorMessage) : isInvalid) ||
|
|
1603
|
-
!!
|
|
1604
|
-
const errorType = useMemo(() =>
|
|
2113
|
+
!!validateNumber(value?.toString(), rest.required, rest.min, rest.max));
|
|
2114
|
+
const errorType = useMemo(() => validateNumber(innerValue, rest.required, rest.min, rest.max), [innerValue, rest.max, rest.min, rest.required]);
|
|
1605
2115
|
const error = useMemo(() => {
|
|
1606
2116
|
// for the case when a custom error message is provided
|
|
1607
2117
|
if (errorMessage) {
|
|
1608
2118
|
return errorMessage;
|
|
1609
2119
|
}
|
|
1610
2120
|
else if (errorType) {
|
|
1611
|
-
return t(`
|
|
2121
|
+
return t(`numberField.error.${errorType}`, { min: rest.min, max: rest.max });
|
|
1612
2122
|
}
|
|
1613
2123
|
return errorMessage;
|
|
1614
|
-
}, [errorMessage, errorType, t]);
|
|
2124
|
+
}, [errorMessage, errorType, rest.max, rest.min, t]);
|
|
1615
2125
|
useEffect(() => {
|
|
1616
|
-
|
|
2126
|
+
if (errorMessage) {
|
|
2127
|
+
setRenderAsInvalid(Boolean(errorMessage));
|
|
2128
|
+
}
|
|
1617
2129
|
}, [errorMessage]);
|
|
1618
2130
|
const handleBlur = useCallback(event => {
|
|
1619
2131
|
const newValue = event.target.value;
|
|
1620
|
-
setInnerValue(newValue);
|
|
2132
|
+
setInnerValue(newValue.toString());
|
|
1621
2133
|
// for the case when a custom error message is provided
|
|
1622
|
-
if (errorMessage && !
|
|
2134
|
+
if (errorMessage && !validateNumber(newValue, rest.required, rest.min, rest.max)) {
|
|
1623
2135
|
setRenderAsInvalid(Boolean(errorMessage));
|
|
1624
2136
|
}
|
|
1625
2137
|
else {
|
|
1626
|
-
setRenderAsInvalid(!!
|
|
2138
|
+
setRenderAsInvalid(!!validateNumber(newValue, rest.required, rest.min, rest.max));
|
|
1627
2139
|
}
|
|
1628
2140
|
onBlur?.(event);
|
|
1629
|
-
}, [errorMessage, onBlur, rest.required]);
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
*/
|
|
1638
|
-
const PhoneFieldWithController = ({ control, controllerProps, name, value, ref, ...rest }) => {
|
|
1639
|
-
return (jsx(Controller, { control: control, defaultValue: value, name: name, ...controllerProps, render: ({ field }) => jsx(PhoneField, { ...rest, ...field, ref: ref }) }));
|
|
2141
|
+
}, [errorMessage, onBlur, rest.max, rest.min, rest.required]);
|
|
2142
|
+
const handleChange = useCallback((event) => {
|
|
2143
|
+
setInnerValue(event.target.value);
|
|
2144
|
+
if (onChange) {
|
|
2145
|
+
onChange(event);
|
|
2146
|
+
}
|
|
2147
|
+
}, [onChange]);
|
|
2148
|
+
return (jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && error) || helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(NumberBaseInput, { "aria-labelledby": htmlForId + "-label", id: htmlForId, isInvalid: renderAsInvalid, maxLength: maxLength, onBlur: handleBlur, onChange: handleChange, ref: ref, value: value, ...rest, className: className, dataTestId: dataTestId }) }));
|
|
1640
2149
|
};
|
|
1641
|
-
|
|
2150
|
+
NumberField.displayName = "NumberField";
|
|
1642
2151
|
|
|
1643
|
-
const
|
|
2152
|
+
const cvaOptionCardLabel = cvaMerge([
|
|
2153
|
+
"group",
|
|
2154
|
+
"transition",
|
|
2155
|
+
"bg-white",
|
|
2156
|
+
"outline",
|
|
2157
|
+
"outline-1",
|
|
2158
|
+
"outline-neutral-300",
|
|
2159
|
+
"hover:bg-neutral-100",
|
|
2160
|
+
"focus:bg-neutral-200",
|
|
2161
|
+
"active:bg-neutral-200",
|
|
2162
|
+
"peer-checked:bg-primary-50",
|
|
2163
|
+
"peer-checked:outline-primary-600",
|
|
2164
|
+
"peer-checked:outline-2",
|
|
2165
|
+
"flex",
|
|
2166
|
+
"gap-2",
|
|
2167
|
+
"justify-center",
|
|
2168
|
+
"items-center",
|
|
2169
|
+
"text-center",
|
|
2170
|
+
"rounded-md",
|
|
2171
|
+
"relative",
|
|
2172
|
+
], {
|
|
1644
2173
|
variants: {
|
|
2174
|
+
disabled: {
|
|
2175
|
+
true: ["cursor-not-allowed", "bg-neutral-100"],
|
|
2176
|
+
false: ["cursor-pointer"],
|
|
2177
|
+
},
|
|
1645
2178
|
layout: {
|
|
1646
|
-
|
|
2179
|
+
default: ["flex-col", "p-responsive-space", "w-full", "aspect-square"],
|
|
2180
|
+
compact: ["px-3", "py-1.5", "h-8", "min-h-[calc(var(--line-height-sm)+var(--spacing-3))]", "flex-row", "w-fit"],
|
|
1647
2181
|
},
|
|
1648
2182
|
},
|
|
1649
2183
|
});
|
|
1650
|
-
const
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
"h-4",
|
|
1654
|
-
"appearance-none",
|
|
1655
|
-
"rounded-3xl",
|
|
1656
|
-
"bg-white",
|
|
1657
|
-
"border-solid",
|
|
1658
|
-
"border",
|
|
1659
|
-
"border-neutral-300",
|
|
1660
|
-
"shadow-sm",
|
|
1661
|
-
"shrink-0",
|
|
1662
|
-
"transition",
|
|
1663
|
-
"box-border",
|
|
1664
|
-
"hover:cursor-pointer",
|
|
1665
|
-
"hover:bg-neutral-100",
|
|
1666
|
-
"focus-visible:outline-primary-700",
|
|
1667
|
-
], {
|
|
2184
|
+
const cvaOptionCardContent = cvaMerge(["flex", "flex-col", "items-center"]);
|
|
2185
|
+
const cvaOptionCardContainer = cvaMerge(["contents"]);
|
|
2186
|
+
const cvaOptionCardTitle = cvaMerge(["text-neutral-600"], {
|
|
1668
2187
|
variants: {
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
"border-4",
|
|
1673
|
-
"border-primary-600",
|
|
1674
|
-
"bg-white",
|
|
1675
|
-
"hover:bg-neutral-100",
|
|
1676
|
-
"hover:cursor-pointer",
|
|
1677
|
-
"outline-0",
|
|
1678
|
-
"active:bg-neutral-200",
|
|
1679
|
-
"active:ring-2",
|
|
1680
|
-
"active:ring-inset",
|
|
1681
|
-
"active:ring-primary-700",
|
|
1682
|
-
"group-active:ring-2",
|
|
1683
|
-
"group-active:ring-inset",
|
|
1684
|
-
"group-active:ring-primary-700",
|
|
1685
|
-
],
|
|
1686
|
-
false: "",
|
|
2188
|
+
layout: {
|
|
2189
|
+
default: ["text-lg", "line-clamp-2"],
|
|
2190
|
+
compact: ["text-sm", "line-clamp-1"],
|
|
1687
2191
|
},
|
|
1688
|
-
|
|
1689
|
-
true: ["
|
|
1690
|
-
false: "",
|
|
2192
|
+
disabled: {
|
|
2193
|
+
true: ["text-neutral-400"],
|
|
2194
|
+
false: ["focus:text-neutral-800", "active:text-neutral-800"],
|
|
2195
|
+
},
|
|
2196
|
+
},
|
|
2197
|
+
});
|
|
2198
|
+
const cvaOptionCardText = cvaMerge(["text-neutral-600", "text-sm"], {
|
|
2199
|
+
variants: {
|
|
2200
|
+
type: {
|
|
2201
|
+
subheading: ["font-medium"],
|
|
2202
|
+
description: ["font-normal"],
|
|
1691
2203
|
},
|
|
1692
2204
|
disabled: {
|
|
1693
|
-
true: [
|
|
1694
|
-
|
|
1695
|
-
"border-neutral-300",
|
|
1696
|
-
"cursor-not-allowed",
|
|
1697
|
-
"hover:bg-neutral-400",
|
|
1698
|
-
"active:bg-neutral-400",
|
|
1699
|
-
"group-active:ring-0",
|
|
1700
|
-
"group-active:ring-inset",
|
|
1701
|
-
],
|
|
1702
|
-
false: "",
|
|
2205
|
+
true: ["text-neutral-400"],
|
|
2206
|
+
false: ["focus:text-neutral-800", "active:text-neutral-800"],
|
|
1703
2207
|
},
|
|
1704
2208
|
},
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
2209
|
+
});
|
|
2210
|
+
const cvaInput = cvaMerge(["peer", "absolute", "h-0", "w-0", "opacity-0"]);
|
|
2211
|
+
const cvaCustomImage = cvaMerge(["text-neutral-400"], {
|
|
2212
|
+
variants: {
|
|
2213
|
+
disabled: {
|
|
2214
|
+
true: ["!text-neutral-400"],
|
|
2215
|
+
false: [""],
|
|
1710
2216
|
},
|
|
1711
|
-
|
|
2217
|
+
},
|
|
2218
|
+
});
|
|
2219
|
+
const cvaTag = cvaMerge([], {
|
|
2220
|
+
variants: {
|
|
2221
|
+
layout: {
|
|
2222
|
+
default: ["absolute", "top-2", "right-2"],
|
|
2223
|
+
compact: [],
|
|
2224
|
+
},
|
|
2225
|
+
},
|
|
1712
2226
|
});
|
|
1713
|
-
|
|
1714
|
-
const RadioGroupContext = createContext(null);
|
|
1715
|
-
|
|
1716
|
-
/**
|
|
1717
|
-
* Use radio buttons when you have a group of mutually exclusive choices and only one selection from the group is allowed.
|
|
1718
|
-
*
|
|
1719
|
-
* Radio buttons are used for mutually exclusive choices, not for multiple choices. Only one radio button can be selected at a time. When a user chooses a new item, the previous choice is automatically deselected.
|
|
1720
|
-
*
|
|
1721
|
-
* _**Do use** Radio buttons in forms, settings, or selections in a list._
|
|
1722
|
-
*
|
|
1723
|
-
* _**Do not use** Radio buttons if a user can select many option from a list, use checkboxes instead of radio buttons._
|
|
1724
|
-
*
|
|
1725
|
-
* @param {RadioGroupProps} props - The props for the RadioGroup component
|
|
1726
|
-
* @returns {ReactElement} RadioGroup component
|
|
1727
|
-
*/
|
|
1728
|
-
const RadioGroup = ({ children, id, name, value, disabled, onChange, label, inline, className, dataTestId, isInvalid, }) => {
|
|
1729
|
-
return (jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, label: label, children: jsx("div", { className: cvaRadioGroup({ layout: inline ? "inline" : null, className }), "data-testid": dataTestId, children: jsx(RadioGroupContext.Provider, { value: {
|
|
1730
|
-
id,
|
|
1731
|
-
value,
|
|
1732
|
-
name: name || id,
|
|
1733
|
-
onChange,
|
|
1734
|
-
disabled,
|
|
1735
|
-
isInvalid,
|
|
1736
|
-
}, children: children }) }) }));
|
|
1737
|
-
};
|
|
1738
|
-
RadioGroup.displayName = "RadioGroup";
|
|
1739
2227
|
|
|
1740
2228
|
/**
|
|
1741
|
-
*
|
|
1742
|
-
*
|
|
1743
|
-
* @param {RadioItemProps} props - The props for the RadioItem component
|
|
1744
|
-
* @returns {ReactElement} RadioItem component
|
|
2229
|
+
* A card version of a radio button that includes an icon, headings and a description.
|
|
1745
2230
|
*/
|
|
1746
|
-
const
|
|
1747
|
-
const
|
|
1748
|
-
const
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
const inputId = `${groupCtx?.id}-${value}`;
|
|
1753
|
-
const hasLabel = label !== undefined && label !== null && label !== "";
|
|
1754
|
-
return (jsxs("label", { className: hasLabel
|
|
1755
|
-
? cvaBinaryControlWrapper({ className })
|
|
1756
|
-
: `inline-flex w-fit items-center gap-2 ${className || ""}`.trim(), "data-testid": dataTestId ? `${dataTestId}-Wrapper` : undefined, htmlFor: inputId, children: [jsx("input", { "aria-describedby": descriptionId, checked: isChecked, className: cvaRadioItem({
|
|
1757
|
-
checked: isChecked,
|
|
1758
|
-
disabled: groupCtx?.disabled,
|
|
1759
|
-
invalid: groupCtx?.isInvalid,
|
|
1760
|
-
}), "data-testid": dataTestId, id: inputId, onChange: groupCtx?.onChange, type: "radio", value: value, ...rest }), hasLabel ? (jsx(Tooltip, { className: cvaBinaryControlLabelTooltip(), dataTestId: dataTestId ? `${dataTestId}-Label-Tooltip` : undefined, disabled: !isLabelTruncated, label: label, placement: "top", children: jsx("span", { className: cvaLabel({
|
|
1761
|
-
invalid: groupCtx?.isInvalid,
|
|
1762
|
-
disabled: groupCtx?.disabled,
|
|
1763
|
-
}), "data-testid": dataTestId ? `${dataTestId}-Label` : undefined, ref: labelRef, children: label }) }, "tooltip-" + rest.name)) : null, suffix ? (jsx("div", { className: cvaBinaryControlSuffixContainer(), "data-testid": dataTestId ? `${dataTestId}-suffix-container` : undefined, children: suffix })) : null, description ? (jsx(Tooltip, { className: cvaBinaryControlDescriptionTooltip(), dataTestId: dataTestId ? `${dataTestId}-Description-Tooltip` : undefined, disabled: !isDescriptionTruncated, label: description, placement: "top", children: jsx("span", { className: cvaBinaryControlDescription({ disabled: groupCtx?.disabled }), "data-testid": dataTestId ? `${dataTestId}-Description` : undefined, id: descriptionId, ref: descriptionRef, children: description }) }, "description-tooltip-" + rest.name)) : null] }));
|
|
2231
|
+
const OptionCard = ({ icon, heading, subheading, description, disabled, id, value, className, contentClassName, dataTestId, customImage, layout = "default", ref, tagProps, ...rest }) => {
|
|
2232
|
+
const htmlForId = id ?? "option-card-" + uuidv4();
|
|
2233
|
+
const subContent = useMemo(() => (jsxs("div", { className: cvaOptionCardContent({ className: contentClassName }), children: [subheading ? (jsx(Text, { align: "center", className: cvaOptionCardText({ type: "subheading", disabled }), type: "span", children: subheading })) : null, description ? (jsx(Text, { align: "center", className: cvaOptionCardText({ type: "description", disabled }), type: "span", children: description })) : null] })), [subheading, description, contentClassName, disabled]);
|
|
2234
|
+
return (jsx(Tooltip, { className: "w-fit", disabled: layout !== "compact" || (!subheading && !description), label: subContent, mode: "light", placement: "top", children: jsxs("div", { className: cvaOptionCardContainer(), "data-testid": dataTestId, children: [jsx("input", { className: cvaInput(), "data-testid": `${dataTestId}-option-card`, disabled: disabled, id: htmlForId, ref: ref, type: "radio", value: value, ...rest }), jsxs("label", { className: cvaOptionCardLabel({ className, disabled, layout }), "data-testid": `${dataTestId}-option-card-label`, htmlFor: htmlForId, children: [disabled && icon && !customImage
|
|
2235
|
+
? cloneElement(icon, { className: cvaCustomImage({ disabled, className: icon.props.className }) })
|
|
2236
|
+
: null, disabled && customImage ? jsx("img", { alt: "logo", className: customImage.className, src: customImage.src }) : null, !disabled && !customImage && icon, !disabled && customImage ? jsx("img", { alt: "logo", className: customImage.className, src: customImage.src }) : null, heading ? (layout === "default" ? (jsx(Heading, { className: cvaOptionCardTitle({ disabled, layout }), subtle: disabled, variant: "secondary", children: heading })) : (jsx(Text, { align: "center", className: cvaOptionCardTitle({ disabled, layout }), subtle: disabled, type: "span", weight: "thick", children: heading }))) : null, layout === "default" && (subheading || description) ? subContent : null, tagProps ? jsx(Tag, { className: cvaTag({ className: tagProps.className, layout }), ...tagProps }) : null] })] }) }));
|
|
1764
2237
|
};
|
|
1765
|
-
|
|
1766
|
-
const cvaTimeRange = cvaMerge([
|
|
1767
|
-
"flex",
|
|
1768
|
-
"flex-1",
|
|
1769
|
-
"items-center",
|
|
1770
|
-
"gap-4",
|
|
1771
|
-
"max-sm:gap-2",
|
|
1772
|
-
"border-transparent",
|
|
1773
|
-
"rounded-md",
|
|
1774
|
-
]);
|
|
2238
|
+
OptionCard.displayName = "OptionCard";
|
|
1775
2239
|
|
|
1776
2240
|
/**
|
|
1777
|
-
*
|
|
2241
|
+
* A thin wrapper around the `BaseInput` component for password input fields.
|
|
1778
2242
|
*
|
|
1779
|
-
*
|
|
1780
|
-
* @returns {ReactElement} TimeRange component
|
|
2243
|
+
* NOTE: If shown with a label, please use the `PasswordField` component instead.
|
|
1781
2244
|
*/
|
|
1782
|
-
const
|
|
1783
|
-
const [
|
|
1784
|
-
|
|
1785
|
-
timeTo: DEFAULT_TIME,
|
|
1786
|
-
});
|
|
1787
|
-
const onChangeFrom = (timeFrom) => {
|
|
1788
|
-
setTimeRange(prev => ({ ...prev, timeFrom }));
|
|
1789
|
-
};
|
|
1790
|
-
const onChangeTo = (timeTo) => {
|
|
1791
|
-
setTimeRange(prev => ({ ...prev, timeTo }));
|
|
1792
|
-
};
|
|
1793
|
-
const onRangeChange = () => onChange(timeRange);
|
|
1794
|
-
return (jsxs("div", { className: cvaTimeRange({ className }), "data-testid": dataTestId, id: id, children: [jsx(BaseInput, { dataTestId: `${dataTestId}-from`, disabled: disabled, isInvalid: isInvalid, onBlur: onRangeChange, onChange: (time) => onChangeFrom(time.currentTarget.value), type: "time", value: timeRange.timeFrom === "" ? DEFAULT_TIME : timeRange.timeFrom }), children ?? jsx("div", { "data-testid": `${dataTestId}-separator`, children: "-" }), jsx(BaseInput, { dataTestId: `${dataTestId}-to`, disabled: disabled, isInvalid: isInvalid, onBlur: onRangeChange, onChange: (time) => onChangeTo(time.currentTarget.value), type: "time", value: timeRange.timeTo === "" ? DEFAULT_TIME : timeRange.timeTo })] }));
|
|
2245
|
+
const PasswordBaseInput = ({ ref, fieldSize, ...rest }) => {
|
|
2246
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
2247
|
+
return (jsx(BaseInput, { ref: ref, ...rest, actions: jsx("div", { className: cvaActionContainer({ size: fieldSize }), children: jsx(IconButton, { className: cvaActionButton({ size: fieldSize }), icon: jsx(Icon, { name: showPassword ? "EyeSlash" : "Eye", size: "small" }), onClick: () => setShowPassword(prevState => !prevState), size: "small", variant: "secondary" }) }), type: showPassword ? "text" : "password" }));
|
|
1795
2248
|
};
|
|
1796
|
-
const DEFAULT_TIME = "12:00";
|
|
1797
|
-
|
|
1798
|
-
const cvaScheduleItem = cvaMerge([
|
|
1799
|
-
"grid",
|
|
1800
|
-
"pb-4",
|
|
1801
|
-
"gap-2",
|
|
1802
|
-
"grid-cols-[60px,200px,60px,2fr]",
|
|
1803
|
-
"max-sm:grid-cols-1",
|
|
1804
|
-
]);
|
|
1805
|
-
const cvaScheduleItemText = cvaMerge(["flex", "font-bold", "self-center"]);
|
|
1806
2249
|
|
|
1807
2250
|
/**
|
|
1808
|
-
*
|
|
2251
|
+
* Password fields enter a password or other confidential information. Characters are masked as they are typed.
|
|
1809
2252
|
*
|
|
1810
|
-
*
|
|
1811
|
-
*
|
|
2253
|
+
* _**Do use** when the user has to input a password or something that needs to be obfuscated_
|
|
2254
|
+
*
|
|
2255
|
+
* _**Do not use** to confirm user actions, such as deleting. Use a checkbox for such flows._
|
|
1812
2256
|
*/
|
|
1813
|
-
const
|
|
1814
|
-
const
|
|
1815
|
-
const
|
|
1816
|
-
|
|
1817
|
-
onChange(
|
|
1818
|
-
};
|
|
1819
|
-
|
|
1820
|
-
const newSchedule = schedule.map((day, dayIndex) => index === dayIndex
|
|
1821
|
-
? {
|
|
1822
|
-
...day,
|
|
1823
|
-
range: {
|
|
1824
|
-
timeFrom: day.range.timeFrom ? day.range.timeFrom : DEFAULT_TIME,
|
|
1825
|
-
timeTo: day.range.timeTo ? day.range.timeTo : DEFAULT_TIME,
|
|
1826
|
-
},
|
|
1827
|
-
isActive,
|
|
1828
|
-
}
|
|
1829
|
-
: day);
|
|
1830
|
-
onChange(newSchedule);
|
|
1831
|
-
};
|
|
1832
|
-
const onAllDayChange = (isAllDayChecked, index) => {
|
|
1833
|
-
const newSchedule = schedule.map((day, dayIndex) => index === dayIndex
|
|
1834
|
-
? {
|
|
1835
|
-
...day,
|
|
1836
|
-
range: {
|
|
1837
|
-
timeFrom: day.range.timeFrom ? day.range.timeFrom : DEFAULT_TIME,
|
|
1838
|
-
timeTo: day.range.timeTo ? day.range.timeTo : DEFAULT_TIME,
|
|
1839
|
-
},
|
|
1840
|
-
isAllDay: isAllDayChecked,
|
|
1841
|
-
}
|
|
1842
|
-
: day);
|
|
1843
|
-
onChange(newSchedule);
|
|
1844
|
-
};
|
|
1845
|
-
return (jsx("div", { className: className, "data-testid": dataTestId, children: schedule.map(({ label, range, isActive, key, checkboxLabel, isAllDay }, index) => {
|
|
1846
|
-
return (jsxs("div", { className: cvaScheduleItem(), children: [jsxs("div", { className: "grid grid-cols-2 gap-4 sm:hidden", children: [jsx(Text, { className: "font-medium text-neutral-500", children: t("schedule.label.day") }), jsx(Text, { className: cvaScheduleItemText(), size: "medium", subtle: !isActive, children: label }), jsx(Text, { className: "font-medium text-neutral-500", children: t("schedule.label.active") }), jsx(Checkbox, { checked: isActive, label: checkboxLabel, onChange: (event) => onActiveChange(Boolean(event.currentTarget.checked), index) }), jsx(Text, { className: "font-medium text-neutral-500", children: t("schedule.label.allDay") }), jsx(Checkbox, { checked: isAllDay ? isActive : undefined, disabled: !isActive, onChange: (event) => onAllDayChange(Boolean(event.currentTarget.checked), index) }), jsx(TimeRange, { disabled: !isActive || isAllDay, isInvalid: !!invalidKeys.find((invalidKey) => invalidKey === key), onChange: (newRange) => onRangeChange(newRange, index), range: range })] }), jsxs("div", { className: "max-sm:hidden sm:grid sm:grid-cols-[100px,200px,60px,250px,250px] sm:gap-2", children: [jsx(Checkbox, { checked: isActive, dataTestId: `${dataTestId}-${key}-checkbox`, label: checkboxLabel, onChange: (event) => onActiveChange(Boolean(event.currentTarget.checked), index) }), jsx(Text, { className: cvaScheduleItemText(), size: "medium", subtle: !isActive, children: label }), jsx(Checkbox, { checked: isAllDay ? isActive : undefined, dataTestId: `${dataTestId}-${key}-allday-checkbox`, disabled: !isActive, onChange: (event) => onAllDayChange(Boolean(event.currentTarget.checked), index) }), jsx(TimeRange, { dataTestId: `${dataTestId}-${key}-range`, disabled: !isActive || isAllDay, isInvalid: !!invalidKeys.find((invalidKey) => invalidKey === key), onChange: (newRange) => onRangeChange(newRange, index), range: isAllDay ? undefined : range })] })] }, key + label));
|
|
1847
|
-
}) }));
|
|
2257
|
+
const PasswordField = ({ id, label, tip, helpText, helpAddon, errorMessage, isInvalid, maxLength, onChange, className, value, dataTestId, ref, ...rest }) => {
|
|
2258
|
+
const renderAsInvalid = isInvalid === undefined ? Boolean(errorMessage) : isInvalid;
|
|
2259
|
+
const htmlFor = id ? id : "passwordField-" + uuidv4();
|
|
2260
|
+
const handleChange = useCallback((event) => {
|
|
2261
|
+
onChange?.(event);
|
|
2262
|
+
}, [onChange]);
|
|
2263
|
+
return (jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && errorMessage) || helpText, htmlFor: htmlFor, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(PasswordBaseInput, { ...rest, "aria-labelledby": htmlFor + "-label", className: className, dataTestId: dataTestId, disabled: rest.readOnly, id: htmlFor, isInvalid: renderAsInvalid, maxLength: maxLength, onChange: handleChange, ref: ref, value: value }) }));
|
|
1848
2264
|
};
|
|
2265
|
+
PasswordField.displayName = "PasswordField";
|
|
1849
2266
|
|
|
1850
|
-
const weekDay = {
|
|
1851
|
-
Monday: "monday",
|
|
1852
|
-
Tuesday: "tuesday",
|
|
1853
|
-
Wednesday: "wednesday",
|
|
1854
|
-
Thursday: "thursday",
|
|
1855
|
-
Friday: "friday",
|
|
1856
|
-
Saturday: "saturday",
|
|
1857
|
-
Sunday: "sunday",
|
|
1858
|
-
};
|
|
1859
|
-
var ScheduleVariant;
|
|
1860
|
-
(function (ScheduleVariant) {
|
|
1861
|
-
ScheduleVariant["ALL_DAYS"] = "all";
|
|
1862
|
-
ScheduleVariant["WEEKDAYS"] = "week";
|
|
1863
|
-
ScheduleVariant["CUSTOM"] = "custom";
|
|
1864
|
-
})(ScheduleVariant || (ScheduleVariant = {}));
|
|
1865
2267
|
/**
|
|
1866
|
-
*
|
|
1867
|
-
*
|
|
1868
|
-
* @param {string} scheduleString String of week schedule
|
|
1869
|
-
* @returns {WeekSchedule} Week schedule range
|
|
2268
|
+
* Validates a phone number
|
|
1870
2269
|
*/
|
|
1871
|
-
const
|
|
1872
|
-
if (!
|
|
1873
|
-
return
|
|
1874
|
-
variant: ScheduleVariant.CUSTOM,
|
|
1875
|
-
schedule: [1, 2, 3, 4, 5, 6, 7].map(day => ({
|
|
1876
|
-
day,
|
|
1877
|
-
range: { timeFrom: "", timeTo: "" },
|
|
1878
|
-
isAllDay: false,
|
|
1879
|
-
isActive: false,
|
|
1880
|
-
})),
|
|
1881
|
-
};
|
|
2270
|
+
const validatePhoneNumber = (phoneNumber) => {
|
|
2271
|
+
if (!phoneNumber) {
|
|
2272
|
+
return "REQUIRED";
|
|
1882
2273
|
}
|
|
1883
|
-
const
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
: {
|
|
1892
|
-
timeFrom: timeFrom,
|
|
1893
|
-
timeTo: timeTo,
|
|
1894
|
-
},
|
|
1895
|
-
isAllDay,
|
|
1896
|
-
isActive: Boolean(timeFrom) && Boolean(timeTo),
|
|
1897
|
-
};
|
|
1898
|
-
});
|
|
1899
|
-
const filteredSchedule = schedule
|
|
1900
|
-
.filter(daySchedule => daySchedule.range)
|
|
1901
|
-
.map(daySchedule => ({
|
|
1902
|
-
day: daySchedule.day,
|
|
1903
|
-
range: daySchedule.range,
|
|
1904
|
-
isAllDay: daySchedule.isAllDay,
|
|
1905
|
-
isActive: daySchedule.isActive,
|
|
1906
|
-
}));
|
|
1907
|
-
let variant;
|
|
1908
|
-
switch (schedule.length) {
|
|
1909
|
-
case 7:
|
|
1910
|
-
variant = isUniform(schedule) ? ScheduleVariant.ALL_DAYS : ScheduleVariant.CUSTOM;
|
|
1911
|
-
break;
|
|
1912
|
-
case 5:
|
|
1913
|
-
variant = hasConsecutiveDays(schedule) ? ScheduleVariant.WEEKDAYS : ScheduleVariant.CUSTOM;
|
|
1914
|
-
break;
|
|
1915
|
-
default:
|
|
1916
|
-
return {
|
|
1917
|
-
variant: ScheduleVariant.CUSTOM,
|
|
1918
|
-
schedule: filteredSchedule,
|
|
1919
|
-
};
|
|
2274
|
+
const asYouType = new AsYouType();
|
|
2275
|
+
asYouType.input(phoneNumber);
|
|
2276
|
+
const countryCode = asYouType.getCallingCode();
|
|
2277
|
+
const national = asYouType.getNationalNumber();
|
|
2278
|
+
const safePhoneNumber = getPhoneNumberWithPlus(phoneNumber.trim());
|
|
2279
|
+
const number = parsePhoneNumberFromString(safePhoneNumber);
|
|
2280
|
+
if (phoneNumber && isValidPhoneNumber(phoneNumber)) {
|
|
2281
|
+
return undefined;
|
|
1920
2282
|
}
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
2283
|
+
if (!countryCode && national) {
|
|
2284
|
+
return "REQUIRED_COUNTRY";
|
|
2285
|
+
}
|
|
2286
|
+
if (phoneNumber &&
|
|
2287
|
+
(checkIfPhoneNumberHasPlus(phoneNumber)
|
|
2288
|
+
? isNaN(+phoneNumber.slice(1, phoneNumber.length))
|
|
2289
|
+
: isNaN(+phoneNumber) || !number)) {
|
|
2290
|
+
return "NOT_A_NUMBER";
|
|
2291
|
+
}
|
|
2292
|
+
if (safePhoneNumber.length <= 5) {
|
|
2293
|
+
//needs to be handled manually, parsePhoneNumberFromString can't parse it
|
|
2294
|
+
return "TOO_SHORT";
|
|
2295
|
+
}
|
|
2296
|
+
return "INVALID_NUMBER";
|
|
1925
2297
|
};
|
|
1926
2298
|
/**
|
|
1927
|
-
*
|
|
2299
|
+
* Checks if the country code is valid and required
|
|
2300
|
+
*/
|
|
2301
|
+
const isInvalidCountryCode = (error, required) => (!!required && error === "REQUIRED") || error === "REQUIRED_COUNTRY";
|
|
2302
|
+
/**
|
|
2303
|
+
* Checks if the phone number is valid and required
|
|
2304
|
+
*/
|
|
2305
|
+
const isInvalidPhoneNumber = (error, required) => error !== "REQUIRED_COUNTRY" && ((!!error && error !== "REQUIRED") || (!!required && error === "REQUIRED"));
|
|
2306
|
+
/**
|
|
2307
|
+
* Checks if the phone number is valid and returns corresponding error message
|
|
2308
|
+
*/
|
|
2309
|
+
const phoneErrorMessage = (phoneNumber, required) => {
|
|
2310
|
+
if ((validatePhoneNumber(phoneNumber) === "REQUIRED" && !required) ||
|
|
2311
|
+
(validatePhoneNumber(phoneNumber) === "REQUIRED" && required && phoneNumber === undefined)) {
|
|
2312
|
+
return undefined;
|
|
2313
|
+
}
|
|
2314
|
+
return validatePhoneNumber(phoneNumber);
|
|
2315
|
+
};
|
|
2316
|
+
|
|
2317
|
+
/**
|
|
2318
|
+
* The PhoneField component is used to enter phone number.
|
|
2319
|
+
* It is a wrapper around the PhoneInput component and the FormGroup component.
|
|
2320
|
+
* It is used to render a phone number field with a label, a tip, a help text, a help addon and an error message.
|
|
1928
2321
|
*
|
|
1929
|
-
* @param {
|
|
1930
|
-
* @
|
|
2322
|
+
* @param {string} [label] - The label for the component.
|
|
2323
|
+
* @param {string} [tip] - The tip for the component.
|
|
2324
|
+
* @param {string} [helpText] - The help text for the component.
|
|
2325
|
+
* @param {string} [helpAddon] - The help addon for the component.
|
|
2326
|
+
* @param {string} [errorMessage] - The error message for the component.
|
|
2327
|
+
* @param {string} [defaultValue] - The default value for the component.
|
|
2328
|
+
* @param {boolean} [disabled=false] - Whether the component is disabled or not.
|
|
2329
|
+
* @param {string} [fieldSize="medium"] - The size of the input field.
|
|
2330
|
+
* @param {boolean} [disableAction=false] - Whether the action button is disabled or not.
|
|
1931
2331
|
*/
|
|
1932
|
-
const
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
2332
|
+
const PhoneField = ({ label, id, tip, helpText, isInvalid, errorMessage, value, helpAddon, className, defaultValue, dataTestId, name, onBlur, ref, ...rest }) => {
|
|
2333
|
+
const htmlForId = id ? id : "phoneField-" + uuidv4();
|
|
2334
|
+
const [t] = useTranslation();
|
|
2335
|
+
const [innerValue, setInnerValue] = useState(() => {
|
|
2336
|
+
return (value?.toString() || defaultValue?.toString()) ?? undefined;
|
|
2337
|
+
});
|
|
2338
|
+
const [renderAsInvalid, setRenderAsInvalid] = useState((isInvalid === undefined ? Boolean(errorMessage) : isInvalid) ||
|
|
2339
|
+
!!phoneErrorMessage(value?.toString(), rest.required));
|
|
2340
|
+
const errorType = useMemo(() => phoneErrorMessage(innerValue, rest.required), [innerValue, rest.required]);
|
|
2341
|
+
const error = useMemo(() => {
|
|
2342
|
+
// for the case when a custom error message is provided
|
|
2343
|
+
if (errorMessage) {
|
|
2344
|
+
return errorMessage;
|
|
2345
|
+
}
|
|
2346
|
+
else if (errorType) {
|
|
2347
|
+
return t(`phoneField.error.${errorType}`);
|
|
2348
|
+
}
|
|
2349
|
+
return errorMessage;
|
|
2350
|
+
}, [errorMessage, errorType, t]);
|
|
2351
|
+
useEffect(() => {
|
|
2352
|
+
setRenderAsInvalid(Boolean(errorMessage));
|
|
2353
|
+
}, [errorMessage]);
|
|
2354
|
+
const handleBlur = useCallback(event => {
|
|
2355
|
+
const newValue = event.target.value;
|
|
2356
|
+
setInnerValue(newValue);
|
|
2357
|
+
// for the case when a custom error message is provided
|
|
2358
|
+
if (errorMessage && !phoneErrorMessage(newValue.toString(), rest.required)) {
|
|
2359
|
+
setRenderAsInvalid(Boolean(errorMessage));
|
|
1944
2360
|
}
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
if (isAllDay) {
|
|
1948
|
-
return `${day}#00:00-24:00`;
|
|
2361
|
+
else {
|
|
2362
|
+
setRenderAsInvalid(!!phoneErrorMessage(newValue.toString(), rest.required));
|
|
1949
2363
|
}
|
|
1950
|
-
|
|
1951
|
-
})
|
|
1952
|
-
|
|
1953
|
-
};
|
|
1954
|
-
/**
|
|
1955
|
-
* Checks if a list of schedule objects have the same ranges
|
|
1956
|
-
*
|
|
1957
|
-
* @param {RawSchedule[]} schedule List of schedule objects
|
|
1958
|
-
* @returns {boolean} Whether the schedule is uniform
|
|
1959
|
-
*/
|
|
1960
|
-
const isUniform = (schedule) => {
|
|
1961
|
-
return schedule.every((day, _, collection) => collection[0]?.range?.timeFrom === day.range?.timeFrom && collection[0]?.range?.timeTo === day.range?.timeTo);
|
|
2364
|
+
onBlur?.(event);
|
|
2365
|
+
}, [errorMessage, onBlur, rest.required]);
|
|
2366
|
+
return (jsx(FormGroup, { className: className, dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && error) || helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(PhoneBaseInput, { "aria-labelledby": htmlForId + "-label", dataTestId: dataTestId, defaultValue: defaultValue, id: htmlForId, isInvalid: renderAsInvalid, name: name, onBlur: handleBlur, ref: ref, value: value, ...rest }) }));
|
|
1962
2367
|
};
|
|
2368
|
+
PhoneField.displayName = "PhoneField";
|
|
2369
|
+
|
|
1963
2370
|
/**
|
|
1964
|
-
*
|
|
2371
|
+
* The PhoneFieldWithController component is a wrapper for the PhoneField component to connect it to react-hook-form.
|
|
1965
2372
|
*
|
|
1966
|
-
* @param {RawSchedule[]} schedule List of schedule objects
|
|
1967
|
-
* @returns {boolean} Whether the schedule has consecutive days
|
|
1968
2373
|
*/
|
|
1969
|
-
const
|
|
1970
|
-
|
|
1971
|
-
return schedule.every(({ day }, index) => day === days[index]);
|
|
2374
|
+
const PhoneFieldWithController = ({ control, controllerProps, name, value, ref, ...rest }) => {
|
|
2375
|
+
return (jsx(Controller, { control: control, defaultValue: value, name: name, ...controllerProps, render: ({ field }) => jsx(PhoneField, { ...rest, ...field, ref: ref }) }));
|
|
1972
2376
|
};
|
|
2377
|
+
PhoneFieldWithController.displayName = "PhoneFieldWithController";
|
|
1973
2378
|
|
|
1974
|
-
const
|
|
1975
|
-
"shadow-none",
|
|
1976
|
-
"component-search-border",
|
|
1977
|
-
"component-search-background",
|
|
1978
|
-
"hover:component-search-background",
|
|
1979
|
-
"hover:component-search-focus-hover",
|
|
1980
|
-
"transition-all",
|
|
1981
|
-
"duration-300",
|
|
1982
|
-
], {
|
|
2379
|
+
const cvaRadioGroup = cvaMerge(["flex", "gap-2", "flex-col", "items-start"], {
|
|
1983
2380
|
variants: {
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
true: [
|
|
1987
|
-
"component-search-width",
|
|
1988
|
-
"component-search-widen",
|
|
1989
|
-
"hover:component-search-widen",
|
|
1990
|
-
"focus-within:w-full",
|
|
1991
|
-
"max-w-sm",
|
|
1992
|
-
],
|
|
1993
|
-
false: "w-full",
|
|
2381
|
+
layout: {
|
|
2382
|
+
inline: ["flex", "gap-3", "flex-row", "items-center"],
|
|
1994
2383
|
},
|
|
1995
2384
|
},
|
|
1996
2385
|
});
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
const Search = ({ className, placeholder, value, widenInputOnFocus, hideBorderWhenNotInFocus = false, disabled = false, onKeyUp, onChange, onFocus, onBlur, name, onClear, dataTestId, autoComplete = "on", loading = false, inputClassName, iconName = "MagnifyingGlass", style, xMarkRef, ref, ...rest }) => {
|
|
2004
|
-
const { t } = useTranslation();
|
|
2005
|
-
return (jsx(TextBaseInput, { ...rest, autoComplete: autoComplete, className: cvaSearch({ className, border: hideBorderWhenNotInFocus, widenOnFocus: widenInputOnFocus }), dataTestId: dataTestId, disabled: disabled, inputClassName: inputClassName, name: name, onBlur: onBlur, onChange: onChange, onFocus: onFocus, onKeyUp: onKeyUp, placeholder: placeholder ?? t("search.placeholder"), prefix: loading ? (jsx(Spinner, { centering: "centered", size: rest.fieldSize ?? undefined })) : (jsx(Icon, { name: iconName, size: rest.fieldSize ?? undefined })), ref: ref, suffix:
|
|
2006
|
-
//only show the clear button if there is a value and the onClear function is provided
|
|
2007
|
-
onClear && value ? (jsx("button", { className: "flex", "data-testid": dataTestId ? `${dataTestId}_suffix_component` : null, onClick: () => {
|
|
2008
|
-
onClear();
|
|
2009
|
-
}, ref: xMarkRef, type: "button", children: jsx(Icon, { name: "XMark", size: "small" }) })) : undefined, value: value }));
|
|
2010
|
-
};
|
|
2011
|
-
Search.displayName = "Search";
|
|
2012
|
-
|
|
2013
|
-
const cvaSelect = cvaMerge([
|
|
2014
|
-
"relative",
|
|
2015
|
-
"flex",
|
|
2016
|
-
"shadow-sm",
|
|
2017
|
-
"rounded-lg",
|
|
2018
|
-
"border-neutral-300",
|
|
2019
|
-
"hover:border-neutral-400",
|
|
2020
|
-
"hover:bg-neutral-50",
|
|
2386
|
+
const cvaRadioItem = cvaMerge([
|
|
2387
|
+
"self-center",
|
|
2388
|
+
"w-4",
|
|
2389
|
+
"h-4",
|
|
2390
|
+
"appearance-none",
|
|
2391
|
+
"rounded-3xl",
|
|
2021
2392
|
"bg-white",
|
|
2393
|
+
"border-solid",
|
|
2394
|
+
"border",
|
|
2395
|
+
"border-neutral-300",
|
|
2396
|
+
"shadow-sm",
|
|
2397
|
+
"shrink-0",
|
|
2022
2398
|
"transition",
|
|
2023
|
-
"
|
|
2024
|
-
"
|
|
2399
|
+
"box-border",
|
|
2400
|
+
"hover:cursor-pointer",
|
|
2401
|
+
"hover:bg-neutral-100",
|
|
2402
|
+
"focus-visible:outline-primary-700",
|
|
2025
2403
|
], {
|
|
2026
2404
|
variants: {
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2405
|
+
checked: {
|
|
2406
|
+
true: [
|
|
2407
|
+
"border-solid",
|
|
2408
|
+
"border-4",
|
|
2409
|
+
"border-primary-600",
|
|
2410
|
+
"bg-white",
|
|
2411
|
+
"hover:bg-neutral-100",
|
|
2412
|
+
"hover:cursor-pointer",
|
|
2413
|
+
"outline-0",
|
|
2414
|
+
"active:bg-neutral-200",
|
|
2415
|
+
"active:ring-2",
|
|
2416
|
+
"active:ring-inset",
|
|
2417
|
+
"active:ring-primary-700",
|
|
2418
|
+
"group-active:ring-2",
|
|
2419
|
+
"group-active:ring-inset",
|
|
2420
|
+
"group-active:ring-primary-700",
|
|
2421
|
+
],
|
|
2422
|
+
false: "",
|
|
2031
2423
|
},
|
|
2032
2424
|
invalid: {
|
|
2033
|
-
true: "border
|
|
2425
|
+
true: ["border-red-600", "active:ring-red-700"],
|
|
2034
2426
|
false: "",
|
|
2035
2427
|
},
|
|
2036
2428
|
disabled: {
|
|
2037
|
-
true:
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
const cvaSelectControl = cvaMerge([], {
|
|
2047
|
-
variants: {
|
|
2048
|
-
isDisabled: {
|
|
2049
|
-
true: "!bg-neutral-100",
|
|
2050
|
-
false: "",
|
|
2051
|
-
},
|
|
2052
|
-
prefix: {
|
|
2053
|
-
true: ["ps-7"],
|
|
2054
|
-
false: "",
|
|
2055
|
-
},
|
|
2056
|
-
invalid: {
|
|
2057
|
-
true: "!border-0",
|
|
2429
|
+
true: [
|
|
2430
|
+
"bg-neutral-400",
|
|
2431
|
+
"border-neutral-300",
|
|
2432
|
+
"cursor-not-allowed",
|
|
2433
|
+
"hover:bg-neutral-400",
|
|
2434
|
+
"active:bg-neutral-400",
|
|
2435
|
+
"group-active:ring-0",
|
|
2436
|
+
"group-active:ring-inset",
|
|
2437
|
+
],
|
|
2058
2438
|
false: "",
|
|
2059
|
-
},
|
|
2060
|
-
},
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
}
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
"items-center",
|
|
2072
|
-
"justify-center",
|
|
2073
|
-
"text-neutral-400",
|
|
2074
|
-
"hover:text-neutral-500",
|
|
2075
|
-
]);
|
|
2076
|
-
const cvaSelectPrefixSuffix = cvaMerge(["flex", "justify-center", "items-center", "text-neutral-400", "absolute", "inset-y-0"], {
|
|
2077
|
-
variants: {
|
|
2078
|
-
kind: {
|
|
2079
|
-
prefix: ["pl-3", "left-0"],
|
|
2080
|
-
suffix: ["pr-3", "right-0"],
|
|
2081
|
-
},
|
|
2082
|
-
},
|
|
2083
|
-
});
|
|
2084
|
-
const cvaSelectXIcon = cvaMerge([
|
|
2085
|
-
"mr-2",
|
|
2086
|
-
"flex",
|
|
2087
|
-
"cursor-pointer",
|
|
2088
|
-
"items-center",
|
|
2089
|
-
"justify-center",
|
|
2090
|
-
"text-neutral-400",
|
|
2091
|
-
"hover:text-neutral-500",
|
|
2092
|
-
"ml-1",
|
|
2093
|
-
]);
|
|
2094
|
-
const cvaSelectMenuList = cvaMerge([], {
|
|
2095
|
-
variants: {
|
|
2096
|
-
menuIsOpen: {
|
|
2097
|
-
true: "animate-fade-in-fast",
|
|
2098
|
-
false: "animate-fade-out-fast",
|
|
2099
|
-
},
|
|
2100
|
-
},
|
|
2101
|
-
});
|
|
2102
|
-
const cvaSelectDynamicTagContainer = cvaMerge(["h-full", "flex", "gap-1", "items-center"], {
|
|
2103
|
-
variants: {
|
|
2104
|
-
visible: { true: "visible", false: "invisible" },
|
|
2105
|
-
},
|
|
2106
|
-
});
|
|
2107
|
-
const cvaSelectCounter = cvaMerge(["overflow-hidden", "whitespace-nowrap"]);
|
|
2108
|
-
const cvaSelectMenu = cvaMerge(["relative", "p-1", "grid", "gap-1"]);
|
|
2109
|
-
|
|
2110
|
-
/**
|
|
2111
|
-
* A single select menu item is a basic wrapper around Menu item designed to be used as a single value render in Select list
|
|
2112
|
-
*
|
|
2113
|
-
* @param {SelectMenuItemProps} props - The props for the SingleSelectMenuItem
|
|
2114
|
-
* @returns {ReactElement} SingleSelectMenuItem
|
|
2115
|
-
*/
|
|
2116
|
-
const SingleSelectMenuItem = ({ label, icon, onClick, selected, focused, dataTestId, disabled, optionLabelDescription, optionPrefix, fieldSize, }) => {
|
|
2117
|
-
return (jsx(MenuItem, { dataTestId: dataTestId, disabled: disabled, fieldSize: fieldSize, focused: focused, label: label, onClick: onClick, optionLabelDescription: optionLabelDescription, optionPrefix: isValidElement(optionPrefix)
|
|
2118
|
-
? cloneElement(optionPrefix, {
|
|
2119
|
-
className: "mr-1 flex items-center",
|
|
2120
|
-
size: "medium",
|
|
2121
|
-
})
|
|
2122
|
-
: optionPrefix, prefix: icon, selected: selected, suffix: selected ? jsx(Icon, { className: "block text-blue-600", name: "Check", size: "medium" }) : undefined }));
|
|
2123
|
-
};
|
|
2124
|
-
/**
|
|
2125
|
-
* A multi select menu item is a basic wrapper around Menu item designed to be used as a multi value render in Select list
|
|
2126
|
-
*
|
|
2127
|
-
* @param {SelectMenuItemProps} props - The props for the MultiSelectMenuItem
|
|
2128
|
-
* @returns {ReactElement} multi select menu item
|
|
2129
|
-
*/
|
|
2130
|
-
const MultiSelectMenuItem = ({ label, onClick, selected, focused, dataTestId, disabled, optionLabelDescription, optionPrefix, fieldSize, }) => {
|
|
2131
|
-
return (jsx(MenuItem, { dataTestId: dataTestId, disabled: disabled, fieldSize: fieldSize, focused: focused, label: label, onClick: e => {
|
|
2132
|
-
e.stopPropagation();
|
|
2133
|
-
onClick && onClick(e);
|
|
2134
|
-
}, optionLabelDescription: optionLabelDescription, optionPrefix: isValidElement(optionPrefix)
|
|
2135
|
-
? cloneElement(optionPrefix, {
|
|
2136
|
-
className: "mr-1 flex items-center",
|
|
2137
|
-
size: "medium",
|
|
2138
|
-
})
|
|
2139
|
-
: optionPrefix, prefix: jsx(Checkbox, { checked: selected, className: "gap-x-0", disabled: disabled, onChange: () => null, onClick: e => {
|
|
2140
|
-
e.stopPropagation();
|
|
2141
|
-
}, readOnly: false }), selected: selected }));
|
|
2142
|
-
};
|
|
2143
|
-
|
|
2144
|
-
/**
|
|
2145
|
-
* Extended Tag component with information about its own width.
|
|
2146
|
-
* Used in the select component.
|
|
2147
|
-
*
|
|
2148
|
-
* @param {TagProps} props - The props for the tag component
|
|
2149
|
-
* @returns {ReactElement} TagWithWidth component
|
|
2150
|
-
*/
|
|
2151
|
-
const TagWithWidth = ({ onWidthKnown, children, ...rest }) => {
|
|
2152
|
-
const ref = useRef(null);
|
|
2153
|
-
useLayoutEffect(() => {
|
|
2154
|
-
onWidthKnown && onWidthKnown({ width: ref.current?.offsetWidth || 0 });
|
|
2155
|
-
}, [ref, onWidthKnown]);
|
|
2156
|
-
return (jsx(Tag, { ref: ref, ...rest, icon: isValidElement(rest.icon) ? cloneElement(rest.icon, { size: "small" }) : rest.icon, children: children }));
|
|
2157
|
-
};
|
|
2439
|
+
},
|
|
2440
|
+
},
|
|
2441
|
+
compoundVariants: [
|
|
2442
|
+
{
|
|
2443
|
+
checked: true,
|
|
2444
|
+
disabled: true,
|
|
2445
|
+
className: ["bg-white"],
|
|
2446
|
+
},
|
|
2447
|
+
],
|
|
2448
|
+
});
|
|
2449
|
+
|
|
2450
|
+
const RadioGroupContext = createContext(null);
|
|
2158
2451
|
|
|
2159
2452
|
/**
|
|
2160
|
-
*
|
|
2453
|
+
* Use radio buttons when you have a group of mutually exclusive choices and only one selection from the group is allowed.
|
|
2161
2454
|
*
|
|
2162
|
-
*
|
|
2163
|
-
*
|
|
2455
|
+
* Radio buttons are used for mutually exclusive choices, not for multiple choices. Only one radio button can be selected at a time. When a user chooses a new item, the previous choice is automatically deselected.
|
|
2456
|
+
*
|
|
2457
|
+
* _**Do use** Radio buttons in forms, settings, or selections in a list._
|
|
2458
|
+
*
|
|
2459
|
+
* _**Do not use** Radio buttons if a user can select many option from a list, use checkboxes instead of radio buttons._
|
|
2460
|
+
*
|
|
2461
|
+
* @param {RadioGroupProps} props - The props for the RadioGroup component
|
|
2462
|
+
* @returns {ReactElement} RadioGroup component
|
|
2164
2463
|
*/
|
|
2165
|
-
const
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
useEffect(() => {
|
|
2175
|
-
const containerWidth = containerRef.current?.offsetWidth || 0;
|
|
2176
|
-
setAvailableSpaceWidth(containerWidth);
|
|
2177
|
-
}, [windowWidth]);
|
|
2178
|
-
const onWidthKnownHandler = useCallback(({ width: reportedWidth }) => {
|
|
2179
|
-
setChildrenWidths(prev => {
|
|
2180
|
-
const next = [...prev, { width: reportedWidth + itemsGap }];
|
|
2181
|
-
if (next.length === itemsCount) {
|
|
2182
|
-
setIsReady(true);
|
|
2183
|
-
}
|
|
2184
|
-
return next;
|
|
2185
|
-
});
|
|
2186
|
-
}, [itemsCount, itemsGap]);
|
|
2187
|
-
const renderedElements = useMemo(() => {
|
|
2188
|
-
const requiredSpace = childrenWidths.reduce((previous, current) => {
|
|
2189
|
-
return previous + current.width;
|
|
2190
|
-
}, 0);
|
|
2191
|
-
const availableSpace = availableSpaceWidth - counterWidth;
|
|
2192
|
-
const { elements } = items
|
|
2193
|
-
.concat({ text: "", onClick: () => null, disabled: false })
|
|
2194
|
-
.reduce((acc, item, index) => {
|
|
2195
|
-
const spaceNeeded = childrenWidths.slice(0, index + 1).reduce((previous, current) => {
|
|
2196
|
-
return previous + current.width;
|
|
2197
|
-
}, 0);
|
|
2198
|
-
const isLast = index === items.length;
|
|
2199
|
-
const counterRequired = requiredSpace > availableSpace && acc.counter !== 0;
|
|
2200
|
-
if (isLast && counterRequired) {
|
|
2201
|
-
return {
|
|
2202
|
-
...acc,
|
|
2203
|
-
elements: [
|
|
2204
|
-
...acc.elements,
|
|
2205
|
-
jsx(TagWithWidth, { color: "white", disabled: disabled, icon: item.Icon, onWidthKnown: ({ width: reportedWidth }) => setCounterWidth(reportedWidth), children: jsxs("div", { className: cvaSelectCounter(), "data-testid": "select-counter", children: ["+", acc.counter] }) }, item.text + index),
|
|
2206
|
-
],
|
|
2207
|
-
};
|
|
2208
|
-
}
|
|
2209
|
-
if (isLast) {
|
|
2210
|
-
return acc;
|
|
2211
|
-
}
|
|
2212
|
-
const itemCanFit = spaceNeeded <= availableSpace;
|
|
2213
|
-
if (itemCanFit) {
|
|
2214
|
-
return {
|
|
2215
|
-
...acc,
|
|
2216
|
-
elements: [
|
|
2217
|
-
...acc.elements,
|
|
2218
|
-
jsx(TagWithWidth, { className: "inline-flex shrink-0", color: item.disabled ? "neutral" : "white", dataTestId: `${item.text}-tag`, disabled: disabled, icon: item.Icon, onClose: e => {
|
|
2219
|
-
e.stopPropagation();
|
|
2220
|
-
item.onClick();
|
|
2221
|
-
}, onWidthKnown: onWidthKnownHandler, children: item.text }, item.text + index),
|
|
2222
|
-
],
|
|
2223
|
-
};
|
|
2224
|
-
}
|
|
2225
|
-
return {
|
|
2226
|
-
elements: acc.elements,
|
|
2227
|
-
counter: item.text !== "" ? acc.counter + 1 : acc.counter,
|
|
2228
|
-
};
|
|
2229
|
-
}, { elements: [], counter: 0 });
|
|
2230
|
-
return elements;
|
|
2231
|
-
}, [items, availableSpaceWidth, counterWidth, disabled, onWidthKnownHandler, childrenWidths]);
|
|
2232
|
-
return (jsxs("div", { className: cvaSelectDynamicTagContainer({ visible: isReady || !!preFix }), ref: containerRef, style: {
|
|
2233
|
-
width: `${width}`,
|
|
2234
|
-
}, children: [preFix, renderedElements, postFix] }));
|
|
2464
|
+
const RadioGroup = ({ children, id, name, value, disabled, onChange, label, inline, className, dataTestId, isInvalid, }) => {
|
|
2465
|
+
return (jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, label: label, children: jsx("div", { className: cvaRadioGroup({ layout: inline ? "inline" : null, className }), "data-testid": dataTestId, children: jsx(RadioGroupContext.Provider, { value: {
|
|
2466
|
+
id,
|
|
2467
|
+
value,
|
|
2468
|
+
name: name || id,
|
|
2469
|
+
onChange,
|
|
2470
|
+
disabled,
|
|
2471
|
+
isInvalid,
|
|
2472
|
+
}, children: children }) }) }));
|
|
2235
2473
|
};
|
|
2474
|
+
RadioGroup.displayName = "RadioGroup";
|
|
2236
2475
|
|
|
2237
2476
|
/**
|
|
2238
|
-
*
|
|
2239
|
-
* This complex object includes all the compositional components that are used in react-select. If you wish to overwrite a component, pass in an object with the appropriate namespace.
|
|
2477
|
+
* The RadioItem component.
|
|
2240
2478
|
*
|
|
2241
|
-
* @
|
|
2242
|
-
* @
|
|
2243
|
-
* @param {Partial<SelectComponents<Option, IsMulti, Group>> | undefined} componentsProps a custom component prop that you can to override defaults
|
|
2244
|
-
* @param {boolean} disabled decide to override disabled variant
|
|
2245
|
-
* @param {boolean} menuIsOpen menu is open state
|
|
2246
|
-
* @param {string} dataTestId a test id
|
|
2247
|
-
* @param {number} maxSelectedDisplayCount a number of max display count
|
|
2248
|
-
* @param {boolean} hasError decide to override hasError variant
|
|
2249
|
-
* @param {ReactNode} prefix a prefix element
|
|
2250
|
-
* @returns {Partial<SelectComponents<Option, boolean, GroupBase<Option>>> | undefined} components object to override react-select default components
|
|
2479
|
+
* @param {RadioItemProps} props - The props for the RadioItem component
|
|
2480
|
+
* @returns {ReactElement} RadioItem component
|
|
2251
2481
|
*/
|
|
2252
|
-
const
|
|
2253
|
-
const
|
|
2254
|
-
|
|
2255
|
-
const
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
return (jsx(components.ValueContainer, { ...props, isDisabled: props.selectProps.isDisabled, children: maxSelectedDisplayCount === undefined ? (jsx(TagsContainer, { disabled: disabled, items: tags
|
|
2271
|
-
? tags.map(({ props: tagProps }) => {
|
|
2272
|
-
const optionPrefix = tagProps.data && getOptionPrefix ? getOptionPrefix(tagProps.data) : null;
|
|
2273
|
-
return {
|
|
2274
|
-
text: tagProps.children,
|
|
2275
|
-
onClick: disabled
|
|
2276
|
-
? undefined
|
|
2277
|
-
: (e) => {
|
|
2278
|
-
setMenuIsEnabled(false);
|
|
2279
|
-
tagProps.removeProps.onClick && tagProps.removeProps.onClick(e);
|
|
2280
|
-
},
|
|
2281
|
-
disabled: disabled,
|
|
2282
|
-
Icon: optionPrefix,
|
|
2283
|
-
};
|
|
2284
|
-
})
|
|
2285
|
-
: [], postFix: searchInput, preFix: placeholderElement ? jsx("span", { className: "absolute", children: placeholderElement }) : null, width: "100%" })) : (jsxs(Fragment, { children: [tags
|
|
2286
|
-
? tags.slice(0, maxSelectedDisplayCount).map(({ props: tagProps }) => {
|
|
2287
|
-
return (jsx(Tag, { className: "inline-flex shrink-0", color: disabled ? "unknown" : "primary", dataTestId: tagProps.children ? `${tagProps.children.toString()}-tag` : undefined, onClose: e => {
|
|
2288
|
-
e.stopPropagation();
|
|
2289
|
-
setMenuIsEnabled(false);
|
|
2290
|
-
tagProps.removeProps.onClick && tagProps.removeProps.onClick(e);
|
|
2291
|
-
}, children: tagProps.children }, tagProps.children?.toString()));
|
|
2292
|
-
})
|
|
2293
|
-
: null, tags && tags.length > maxSelectedDisplayCount ? (jsxs(Tag, { color: "neutral", dataTestId: "counter-tag", children: ["+", tags.length - maxSelectedDisplayCount] })) : null, searchInput, placeholderElement] })) }));
|
|
2294
|
-
}
|
|
2295
|
-
return (jsx(Fragment, { children: jsx(components.ValueContainer, { ...props, isDisabled: props.selectProps.isDisabled, children: props.children }) }));
|
|
2296
|
-
},
|
|
2297
|
-
LoadingIndicator: () => {
|
|
2298
|
-
return jsx(Spinner, { centering: "vertically", className: "mr-2", size: "small" });
|
|
2299
|
-
},
|
|
2300
|
-
DropdownIndicator: props => {
|
|
2301
|
-
const icon = props.selectProps.menuIsOpen ? (jsx(Icon, { name: "ChevronUp", size: "medium" })) : (jsx(Icon, { name: "ChevronDown", size: "medium" }));
|
|
2302
|
-
return props.selectProps.isLoading || props.selectProps.isDisabled || readOnly ? null : (jsx(components.DropdownIndicator, { ...props, children: jsx("div", { className: cvaSelectIcon(), children: icon }) }));
|
|
2303
|
-
},
|
|
2304
|
-
IndicatorSeparator: () => null,
|
|
2305
|
-
ClearIndicator: props => {
|
|
2306
|
-
if (disabled) {
|
|
2307
|
-
return null;
|
|
2308
|
-
}
|
|
2309
|
-
return (jsx(components.ClearIndicator, { ...props, innerProps: {
|
|
2310
|
-
...props.innerProps,
|
|
2311
|
-
onMouseDown: e => {
|
|
2312
|
-
e.preventDefault();
|
|
2313
|
-
},
|
|
2314
|
-
}, children: jsx("div", { className: cvaSelectXIcon(), "data-testid": dataTestId ? `${dataTestId}-XMarkIcon` : null, onClick: props.clearValue, children: jsx(Icon, { ariaLabel: t("clearIndicator.icon.tooltip.clearAll"), name: "XCircle", size: "medium" }) }) }));
|
|
2315
|
-
},
|
|
2316
|
-
Control: props => {
|
|
2317
|
-
return (jsx(components.Control, { ...props, className: cvaSelectControl({
|
|
2318
|
-
isDisabled: props.isDisabled,
|
|
2319
|
-
prefix: prefix ? true : false,
|
|
2320
|
-
invalid: hasError,
|
|
2321
|
-
}) }));
|
|
2322
|
-
},
|
|
2323
|
-
SingleValue: props => {
|
|
2324
|
-
const optionPrefix = getOptionPrefix ? getOptionPrefix(props.data) : null;
|
|
2325
|
-
return (jsx(components.SingleValue, { ...props, className: props.isDisabled ? "text-neutral-700" : "", children: jsxs("div", { className: "flex items-center gap-1", "data-testid": dataTestId + "-singleValue", children: [optionPrefix !== null ? optionPrefix : null, props.children, getOptionLabelDescription && getOptionLabelDescription(props.data) ? (jsxs("span", { className: "ml-1 text-neutral-400", children: ["(", getOptionLabelDescription(props.data), ")"] })) : null] }) }));
|
|
2326
|
-
},
|
|
2327
|
-
Menu: props => {
|
|
2328
|
-
return (jsx(components.Menu, { ...props, className: cvaSelectMenuList({ menuIsOpen: props.selectProps.menuIsOpen }) }));
|
|
2329
|
-
},
|
|
2330
|
-
Placeholder: props => {
|
|
2331
|
-
return (jsx(components.Placeholder, { ...props, className: "!text-neutral-400", children: props.children }));
|
|
2332
|
-
},
|
|
2333
|
-
MenuList: props => {
|
|
2334
|
-
return (jsx(components.MenuList, { ...props, innerProps: {
|
|
2335
|
-
...props.innerProps,
|
|
2336
|
-
onScroll: e => {
|
|
2337
|
-
const listEl = e.currentTarget;
|
|
2338
|
-
if (listEl.scrollTop + listEl.clientHeight >= listEl.scrollHeight &&
|
|
2339
|
-
props.selectProps.onMenuScrollToBottom) {
|
|
2340
|
-
/Firefox/.test(navigator.userAgent)
|
|
2341
|
-
? props.selectProps.onMenuScrollToBottom(new WheelEvent("scroll"))
|
|
2342
|
-
: props.selectProps.onMenuScrollToBottom(new TouchEvent(""));
|
|
2343
|
-
}
|
|
2344
|
-
},
|
|
2345
|
-
}, children: props.children }));
|
|
2346
|
-
},
|
|
2347
|
-
Option: props => {
|
|
2348
|
-
const componentProps = {
|
|
2349
|
-
label: props.label,
|
|
2350
|
-
focused: props.isFocused,
|
|
2351
|
-
selected: props.isSelected,
|
|
2352
|
-
onClick: props.innerProps.onClick,
|
|
2353
|
-
};
|
|
2354
|
-
return (jsx(components.Option, { ...props, innerProps: {
|
|
2355
|
-
...props.innerProps,
|
|
2356
|
-
role: "option",
|
|
2357
|
-
onClick: () => { },
|
|
2358
|
-
}, children: props.isMulti ? (jsx(MultiSelectMenuItem, { ...componentProps, dataTestId: typeof props.label === "string" ? props.label : undefined, disabled: disabled, fieldSize: fieldSize, optionLabelDescription: getOptionLabelDescription?.(props.data), optionPrefix: getOptionPrefix?.(props.data) })) : (jsx(SingleSelectMenuItem, { ...componentProps, dataTestId: typeof props.label === "string" ? props.label : undefined, disabled: disabled || props.isDisabled, fieldSize: fieldSize, optionLabelDescription: getOptionLabelDescription?.(props.data), optionPrefix: getOptionPrefix?.(props.data) })) }));
|
|
2359
|
-
},
|
|
2360
|
-
...componentsProps,
|
|
2361
|
-
};
|
|
2362
|
-
}, [
|
|
2363
|
-
componentsProps,
|
|
2364
|
-
maxSelectedDisplayCount,
|
|
2365
|
-
disabled,
|
|
2366
|
-
setMenuIsEnabled,
|
|
2367
|
-
readOnly,
|
|
2368
|
-
dataTestId,
|
|
2369
|
-
t,
|
|
2370
|
-
prefix,
|
|
2371
|
-
hasError,
|
|
2372
|
-
getOptionLabelDescription,
|
|
2373
|
-
fieldSize,
|
|
2374
|
-
getOptionPrefix,
|
|
2375
|
-
]);
|
|
2376
|
-
return customComponents;
|
|
2482
|
+
const RadioItem = ({ label, value, dataTestId, className, description, suffix, ...rest }) => {
|
|
2483
|
+
const groupCtx = useContext(RadioGroupContext);
|
|
2484
|
+
const isChecked = groupCtx?.value === value;
|
|
2485
|
+
const { ref: labelRef, isTextTruncated: isLabelTruncated } = useIsTextTruncated();
|
|
2486
|
+
const { ref: descriptionRef, isTextTruncated: isDescriptionTruncated } = useIsTextTruncated();
|
|
2487
|
+
const descriptionId = description ? `${groupCtx?.id}-${value}-description` : undefined;
|
|
2488
|
+
const inputId = `${groupCtx?.id}-${value}`;
|
|
2489
|
+
const hasLabel = label !== undefined && label !== null && label !== "";
|
|
2490
|
+
return (jsxs("label", { className: hasLabel
|
|
2491
|
+
? cvaBinaryControlWrapper({ className })
|
|
2492
|
+
: `inline-flex w-fit items-center gap-2 ${className || ""}`.trim(), "data-testid": dataTestId ? `${dataTestId}-Wrapper` : undefined, htmlFor: inputId, children: [jsx("input", { "aria-describedby": descriptionId, checked: isChecked, className: cvaRadioItem({
|
|
2493
|
+
checked: isChecked,
|
|
2494
|
+
disabled: groupCtx?.disabled,
|
|
2495
|
+
invalid: groupCtx?.isInvalid,
|
|
2496
|
+
}), "data-testid": dataTestId, id: inputId, onChange: groupCtx?.onChange, type: "radio", value: value, ...rest }), hasLabel ? (jsx(Tooltip, { className: cvaBinaryControlLabelTooltip(), dataTestId: dataTestId ? `${dataTestId}-Label-Tooltip` : undefined, disabled: !isLabelTruncated, label: label, placement: "top", children: jsx("span", { className: cvaLabel({
|
|
2497
|
+
invalid: groupCtx?.isInvalid,
|
|
2498
|
+
disabled: groupCtx?.disabled,
|
|
2499
|
+
}), "data-testid": dataTestId ? `${dataTestId}-Label` : undefined, ref: labelRef, children: label }) }, "tooltip-" + rest.name)) : null, suffix ? (jsx("div", { className: cvaBinaryControlSuffixContainer(), "data-testid": dataTestId ? `${dataTestId}-suffix-container` : undefined, children: suffix })) : null, description ? (jsx(Tooltip, { className: cvaBinaryControlDescriptionTooltip(), dataTestId: dataTestId ? `${dataTestId}-Description-Tooltip` : undefined, disabled: !isDescriptionTruncated, label: description, placement: "top", children: jsx("span", { className: cvaBinaryControlDescription({ disabled: groupCtx?.disabled }), "data-testid": dataTestId ? `${dataTestId}-Description` : undefined, id: descriptionId, ref: descriptionRef, children: description }) }, "description-tooltip-" + rest.name)) : null] }));
|
|
2377
2500
|
};
|
|
2378
2501
|
|
|
2502
|
+
const cvaTimeRange = cvaMerge([
|
|
2503
|
+
"flex",
|
|
2504
|
+
"flex-1",
|
|
2505
|
+
"items-center",
|
|
2506
|
+
"gap-4",
|
|
2507
|
+
"max-sm:gap-2",
|
|
2508
|
+
"border-transparent",
|
|
2509
|
+
"rounded-md",
|
|
2510
|
+
]);
|
|
2511
|
+
|
|
2379
2512
|
/**
|
|
2380
|
-
*
|
|
2381
|
-
*
|
|
2382
|
-
* @param {
|
|
2383
|
-
* @
|
|
2384
|
-
* @param {StylesConfig<Option, IsMulti, Group> | undefined} styles a optional object to override styles of react-select
|
|
2385
|
-
* @returns {StylesConfig<Option, boolean>} styles to override in select
|
|
2513
|
+
* TimeRange is used to create a time range entry.
|
|
2514
|
+
*
|
|
2515
|
+
* @param {TimeRangeProps} props - The props for the TimeRange component
|
|
2516
|
+
* @returns {ReactElement} TimeRange component
|
|
2386
2517
|
*/
|
|
2387
|
-
const
|
|
2388
|
-
const
|
|
2518
|
+
const TimeRange = ({ id, className, dataTestId, children, range, onChange, disabled, isInvalid, }) => {
|
|
2519
|
+
const [timeRange, setTimeRange] = useState(range ?? {
|
|
2520
|
+
timeFrom: DEFAULT_TIME,
|
|
2521
|
+
timeTo: DEFAULT_TIME,
|
|
2522
|
+
});
|
|
2523
|
+
const onChangeFrom = (timeFrom) => {
|
|
2524
|
+
setTimeRange(prev => ({ ...prev, timeFrom }));
|
|
2525
|
+
};
|
|
2526
|
+
const onChangeTo = (timeTo) => {
|
|
2527
|
+
setTimeRange(prev => ({ ...prev, timeTo }));
|
|
2528
|
+
};
|
|
2529
|
+
const onRangeChange = () => onChange(timeRange);
|
|
2530
|
+
return (jsxs("div", { className: cvaTimeRange({ className }), "data-testid": dataTestId, id: id, children: [jsx(BaseInput, { dataTestId: `${dataTestId}-from`, disabled: disabled, isInvalid: isInvalid, onBlur: onRangeChange, onChange: (time) => onChangeFrom(time.currentTarget.value), type: "time", value: timeRange.timeFrom === "" ? DEFAULT_TIME : timeRange.timeFrom }), children ?? jsx("div", { "data-testid": `${dataTestId}-separator`, children: "-" }), jsx(BaseInput, { dataTestId: `${dataTestId}-to`, disabled: disabled, isInvalid: isInvalid, onBlur: onRangeChange, onChange: (time) => onChangeTo(time.currentTarget.value), type: "time", value: timeRange.timeTo === "" ? DEFAULT_TIME : timeRange.timeTo })] }));
|
|
2531
|
+
};
|
|
2532
|
+
const DEFAULT_TIME = "12:00";
|
|
2533
|
+
|
|
2534
|
+
const cvaScheduleItem = cvaMerge([
|
|
2535
|
+
"grid",
|
|
2536
|
+
"pb-4",
|
|
2537
|
+
"gap-2",
|
|
2538
|
+
"grid-cols-[60px,200px,60px,2fr]",
|
|
2539
|
+
"max-sm:grid-cols-1",
|
|
2540
|
+
]);
|
|
2541
|
+
const cvaScheduleItemText = cvaMerge(["flex", "font-bold", "self-center"]);
|
|
2542
|
+
|
|
2543
|
+
/**
|
|
2544
|
+
* Schedule is used to create a time range entries.
|
|
2545
|
+
*
|
|
2546
|
+
* @param {ScheduleProps} props - The props for the Schedule component
|
|
2547
|
+
* @returns {ReactElement} Schedule component
|
|
2548
|
+
*/
|
|
2549
|
+
const Schedule = ({ className, dataTestId, schedule, onChange, invalidKeys = [] }) => {
|
|
2550
|
+
const [t] = useTranslation();
|
|
2551
|
+
const onRangeChange = (range, index) => {
|
|
2552
|
+
const newSchedule = schedule.map((day, dayIndex) => (index === dayIndex ? { ...day, range: { ...range } } : day));
|
|
2553
|
+
onChange(newSchedule);
|
|
2554
|
+
};
|
|
2555
|
+
const onActiveChange = (isActive, index) => {
|
|
2556
|
+
const newSchedule = schedule.map((day, dayIndex) => index === dayIndex
|
|
2557
|
+
? {
|
|
2558
|
+
...day,
|
|
2559
|
+
range: {
|
|
2560
|
+
timeFrom: day.range.timeFrom ? day.range.timeFrom : DEFAULT_TIME,
|
|
2561
|
+
timeTo: day.range.timeTo ? day.range.timeTo : DEFAULT_TIME,
|
|
2562
|
+
},
|
|
2563
|
+
isActive,
|
|
2564
|
+
}
|
|
2565
|
+
: day);
|
|
2566
|
+
onChange(newSchedule);
|
|
2567
|
+
};
|
|
2568
|
+
const onAllDayChange = (isAllDayChecked, index) => {
|
|
2569
|
+
const newSchedule = schedule.map((day, dayIndex) => index === dayIndex
|
|
2570
|
+
? {
|
|
2571
|
+
...day,
|
|
2572
|
+
range: {
|
|
2573
|
+
timeFrom: day.range.timeFrom ? day.range.timeFrom : DEFAULT_TIME,
|
|
2574
|
+
timeTo: day.range.timeTo ? day.range.timeTo : DEFAULT_TIME,
|
|
2575
|
+
},
|
|
2576
|
+
isAllDay: isAllDayChecked,
|
|
2577
|
+
}
|
|
2578
|
+
: day);
|
|
2579
|
+
onChange(newSchedule);
|
|
2580
|
+
};
|
|
2581
|
+
return (jsx("div", { className: className, "data-testid": dataTestId, children: schedule.map(({ label, range, isActive, key, checkboxLabel, isAllDay }, index) => {
|
|
2582
|
+
return (jsxs("div", { className: cvaScheduleItem(), children: [jsxs("div", { className: "grid grid-cols-2 gap-4 sm:hidden", children: [jsx(Text, { className: "font-medium text-neutral-500", children: t("schedule.label.day") }), jsx(Text, { className: cvaScheduleItemText(), size: "medium", subtle: !isActive, children: label }), jsx(Text, { className: "font-medium text-neutral-500", children: t("schedule.label.active") }), jsx(Checkbox, { checked: isActive, label: checkboxLabel, onChange: (event) => onActiveChange(Boolean(event.currentTarget.checked), index) }), jsx(Text, { className: "font-medium text-neutral-500", children: t("schedule.label.allDay") }), jsx(Checkbox, { checked: isAllDay ? isActive : undefined, disabled: !isActive, onChange: (event) => onAllDayChange(Boolean(event.currentTarget.checked), index) }), jsx(TimeRange, { disabled: !isActive || isAllDay, isInvalid: !!invalidKeys.find((invalidKey) => invalidKey === key), onChange: (newRange) => onRangeChange(newRange, index), range: range })] }), jsxs("div", { className: "max-sm:hidden sm:grid sm:grid-cols-[100px,200px,60px,250px,250px] sm:gap-2", children: [jsx(Checkbox, { checked: isActive, dataTestId: `${dataTestId}-${key}-checkbox`, label: checkboxLabel, onChange: (event) => onActiveChange(Boolean(event.currentTarget.checked), index) }), jsx(Text, { className: cvaScheduleItemText(), size: "medium", subtle: !isActive, children: label }), jsx(Checkbox, { checked: isAllDay ? isActive : undefined, dataTestId: `${dataTestId}-${key}-allday-checkbox`, disabled: !isActive, onChange: (event) => onAllDayChange(Boolean(event.currentTarget.checked), index) }), jsx(TimeRange, { dataTestId: `${dataTestId}-${key}-range`, disabled: !isActive || isAllDay, isInvalid: !!invalidKeys.find((invalidKey) => invalidKey === key), onChange: (newRange) => onRangeChange(newRange, index), range: isAllDay ? undefined : range })] })] }, key + label));
|
|
2583
|
+
}) }));
|
|
2584
|
+
};
|
|
2585
|
+
|
|
2586
|
+
const weekDay = {
|
|
2587
|
+
Monday: "monday",
|
|
2588
|
+
Tuesday: "tuesday",
|
|
2589
|
+
Wednesday: "wednesday",
|
|
2590
|
+
Thursday: "thursday",
|
|
2591
|
+
Friday: "friday",
|
|
2592
|
+
Saturday: "saturday",
|
|
2593
|
+
Sunday: "sunday",
|
|
2594
|
+
};
|
|
2595
|
+
var ScheduleVariant;
|
|
2596
|
+
(function (ScheduleVariant) {
|
|
2597
|
+
ScheduleVariant["ALL_DAYS"] = "all";
|
|
2598
|
+
ScheduleVariant["WEEKDAYS"] = "week";
|
|
2599
|
+
ScheduleVariant["CUSTOM"] = "custom";
|
|
2600
|
+
})(ScheduleVariant || (ScheduleVariant = {}));
|
|
2601
|
+
/**
|
|
2602
|
+
* Parse a string of week range schedule string to human readable schedule range.
|
|
2603
|
+
*
|
|
2604
|
+
* @param {string} scheduleString String of week schedule
|
|
2605
|
+
* @returns {WeekSchedule} Week schedule range
|
|
2606
|
+
*/
|
|
2607
|
+
const parseSchedule = (scheduleString) => {
|
|
2608
|
+
if (!scheduleString) {
|
|
2389
2609
|
return {
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
},
|
|
2398
|
-
singleValue: base => ({
|
|
2399
|
-
...base,
|
|
2400
|
-
}),
|
|
2401
|
-
multiValue: base => ({
|
|
2402
|
-
...base,
|
|
2403
|
-
}),
|
|
2404
|
-
multiValueLabel: base => ({
|
|
2405
|
-
...base,
|
|
2406
|
-
}),
|
|
2407
|
-
indicatorsContainer: base => ({
|
|
2408
|
-
...base,
|
|
2409
|
-
...(disabled && { display: "none" }),
|
|
2410
|
-
}),
|
|
2411
|
-
indicatorSeparator: () => ({
|
|
2412
|
-
width: "0px",
|
|
2413
|
-
}),
|
|
2414
|
-
menu: base => {
|
|
2415
|
-
return {
|
|
2416
|
-
...base,
|
|
2417
|
-
width: "100%",
|
|
2418
|
-
marginTop: "4px",
|
|
2419
|
-
marginBottom: "18px",
|
|
2420
|
-
transition: "all 1s ease-in-out",
|
|
2421
|
-
};
|
|
2422
|
-
},
|
|
2423
|
-
input: base => ({
|
|
2424
|
-
...base,
|
|
2425
|
-
marginLeft: "0px",
|
|
2426
|
-
}),
|
|
2427
|
-
placeholder: base => ({
|
|
2428
|
-
...base,
|
|
2429
|
-
}),
|
|
2430
|
-
option: () => ({}),
|
|
2431
|
-
menuPortal: base => ({
|
|
2432
|
-
...base,
|
|
2433
|
-
width: refContainer.current ? `${refContainer.current.clientWidth}px` : base.width,
|
|
2434
|
-
backgroundColor: "#ffffff",
|
|
2435
|
-
borderRadius: "var(--border-radius-lg)",
|
|
2436
|
-
zIndex: "var(--z-overlay)",
|
|
2437
|
-
borderColor: "rgb(var(--color-neutral-300))",
|
|
2438
|
-
boxShadow: "var(--tw-ring-inset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)",
|
|
2439
|
-
}),
|
|
2440
|
-
menuList: base => {
|
|
2441
|
-
return {
|
|
2442
|
-
...base,
|
|
2443
|
-
position: "relative",
|
|
2444
|
-
padding: "var(--spacing-1)",
|
|
2445
|
-
display: "grid",
|
|
2446
|
-
gap: "var(--spacing-1)",
|
|
2447
|
-
width: "100%",
|
|
2448
|
-
borderRadius: "0px",
|
|
2449
|
-
boxShadow: "none",
|
|
2450
|
-
paddingTop: "0px",
|
|
2451
|
-
};
|
|
2452
|
-
},
|
|
2453
|
-
valueContainer: base => {
|
|
2454
|
-
return {
|
|
2455
|
-
...base,
|
|
2456
|
-
paddingBlock: 0,
|
|
2457
|
-
flexWrap: maxSelectedDisplayCount !== undefined ? "wrap" : "nowrap",
|
|
2458
|
-
gap: "0.25rem",
|
|
2459
|
-
};
|
|
2460
|
-
},
|
|
2461
|
-
container: base => ({
|
|
2462
|
-
...base,
|
|
2463
|
-
width: "100%",
|
|
2464
|
-
}),
|
|
2465
|
-
dropdownIndicator: base => ({
|
|
2466
|
-
...base,
|
|
2467
|
-
padding: "0px",
|
|
2468
|
-
}),
|
|
2469
|
-
clearIndicator: base => {
|
|
2470
|
-
return {
|
|
2471
|
-
...base,
|
|
2472
|
-
padding: "0px",
|
|
2473
|
-
};
|
|
2474
|
-
},
|
|
2475
|
-
...styles,
|
|
2610
|
+
variant: ScheduleVariant.CUSTOM,
|
|
2611
|
+
schedule: [1, 2, 3, 4, 5, 6, 7].map(day => ({
|
|
2612
|
+
day,
|
|
2613
|
+
range: { timeFrom: "", timeTo: "" },
|
|
2614
|
+
isAllDay: false,
|
|
2615
|
+
isActive: false,
|
|
2616
|
+
})),
|
|
2476
2617
|
};
|
|
2477
|
-
}
|
|
2478
|
-
|
|
2618
|
+
}
|
|
2619
|
+
const schedule = scheduleString.split(",").map(daySchedule => {
|
|
2620
|
+
const [day, timeRange] = daySchedule.split("#");
|
|
2621
|
+
const [timeFrom, timeTo] = (timeRange ?? "").split("-");
|
|
2622
|
+
const isAllDay = timeFrom === "00:00" && timeTo === "24:00";
|
|
2623
|
+
return {
|
|
2624
|
+
day: Number(day),
|
|
2625
|
+
range: timeFrom === undefined || timeTo === undefined
|
|
2626
|
+
? undefined
|
|
2627
|
+
: {
|
|
2628
|
+
timeFrom: timeFrom,
|
|
2629
|
+
timeTo: timeTo,
|
|
2630
|
+
},
|
|
2631
|
+
isAllDay,
|
|
2632
|
+
isActive: Boolean(timeFrom) && Boolean(timeTo),
|
|
2633
|
+
};
|
|
2634
|
+
});
|
|
2635
|
+
const filteredSchedule = schedule
|
|
2636
|
+
.filter(daySchedule => daySchedule.range)
|
|
2637
|
+
.map(daySchedule => ({
|
|
2638
|
+
day: daySchedule.day,
|
|
2639
|
+
range: daySchedule.range,
|
|
2640
|
+
isAllDay: daySchedule.isAllDay,
|
|
2641
|
+
isActive: daySchedule.isActive,
|
|
2642
|
+
}));
|
|
2643
|
+
let variant;
|
|
2644
|
+
switch (schedule.length) {
|
|
2645
|
+
case 7:
|
|
2646
|
+
variant = isUniform(schedule) ? ScheduleVariant.ALL_DAYS : ScheduleVariant.CUSTOM;
|
|
2647
|
+
break;
|
|
2648
|
+
case 5:
|
|
2649
|
+
variant = hasConsecutiveDays(schedule) ? ScheduleVariant.WEEKDAYS : ScheduleVariant.CUSTOM;
|
|
2650
|
+
break;
|
|
2651
|
+
default:
|
|
2652
|
+
return {
|
|
2653
|
+
variant: ScheduleVariant.CUSTOM,
|
|
2654
|
+
schedule: filteredSchedule,
|
|
2655
|
+
};
|
|
2656
|
+
}
|
|
2657
|
+
return {
|
|
2658
|
+
variant,
|
|
2659
|
+
schedule: filteredSchedule,
|
|
2660
|
+
};
|
|
2479
2661
|
};
|
|
2480
|
-
|
|
2481
2662
|
/**
|
|
2482
|
-
*
|
|
2663
|
+
* Serialize week schedule to string schedule
|
|
2483
2664
|
*
|
|
2484
|
-
* @param {
|
|
2485
|
-
* @returns {
|
|
2665
|
+
* @param {WeekSchedule} weekSchedule Week schedule range
|
|
2666
|
+
* @returns {string} Schedule string
|
|
2486
2667
|
*/
|
|
2487
|
-
const
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
componentsProps: components,
|
|
2500
|
-
disabled: Boolean(disabled),
|
|
2501
|
-
readOnly: Boolean(props.readOnly),
|
|
2502
|
-
setMenuIsEnabled,
|
|
2503
|
-
dataTestId,
|
|
2504
|
-
maxSelectedDisplayCount,
|
|
2505
|
-
prefix,
|
|
2506
|
-
hasError,
|
|
2507
|
-
fieldSize,
|
|
2508
|
-
getOptionLabelDescription,
|
|
2509
|
-
getOptionPrefix,
|
|
2510
|
-
});
|
|
2511
|
-
const menuPlacement = "auto";
|
|
2512
|
-
const openMenuHandler = async () => {
|
|
2513
|
-
onMenuOpen?.();
|
|
2514
|
-
if (menuIsEnabled) {
|
|
2515
|
-
setMenuIsOpen(true);
|
|
2668
|
+
const serializeSchedule = (weekSchedule) => {
|
|
2669
|
+
return weekSchedule.schedule
|
|
2670
|
+
.filter(({ range, day, isAllDay }) => {
|
|
2671
|
+
const hasRange = range.timeFrom && range.timeTo;
|
|
2672
|
+
switch (weekSchedule.variant) {
|
|
2673
|
+
case ScheduleVariant.WEEKDAYS:
|
|
2674
|
+
return day <= 5 && hasRange;
|
|
2675
|
+
case ScheduleVariant.ALL_DAYS:
|
|
2676
|
+
return day <= 7 && hasRange;
|
|
2677
|
+
case ScheduleVariant.CUSTOM:
|
|
2678
|
+
default:
|
|
2679
|
+
return hasRange || isAllDay;
|
|
2516
2680
|
}
|
|
2517
|
-
|
|
2518
|
-
|
|
2681
|
+
})
|
|
2682
|
+
.map(({ day, range, isAllDay }) => {
|
|
2683
|
+
if (isAllDay) {
|
|
2684
|
+
return `${day}#00:00-24:00`;
|
|
2519
2685
|
}
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
onMenuClose && onMenuClose();
|
|
2524
|
-
};
|
|
2525
|
-
return {
|
|
2526
|
-
refContainer,
|
|
2527
|
-
customStyles,
|
|
2528
|
-
menuIsOpen,
|
|
2529
|
-
customComponents,
|
|
2530
|
-
menuPlacement,
|
|
2531
|
-
openMenuHandler,
|
|
2532
|
-
closeMenuHandler,
|
|
2533
|
-
};
|
|
2686
|
+
return `${day}#${range.timeFrom}-${range.timeTo}`;
|
|
2687
|
+
})
|
|
2688
|
+
.join(",");
|
|
2534
2689
|
};
|
|
2535
|
-
|
|
2536
2690
|
/**
|
|
2537
|
-
*
|
|
2691
|
+
* Checks if a list of schedule objects have the same ranges
|
|
2538
2692
|
*
|
|
2539
|
-
* @param {
|
|
2540
|
-
* @returns {
|
|
2693
|
+
* @param {RawSchedule[]} schedule List of schedule objects
|
|
2694
|
+
* @returns {boolean} Whether the schedule is uniform
|
|
2541
2695
|
*/
|
|
2542
|
-
const
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
tabSelectsValue: false,
|
|
2555
|
-
blurInputOnSelect: !isMulti,
|
|
2556
|
-
menuPortalTarget: props.menuPortalTarget || document.body,
|
|
2557
|
-
isSearchable: disabled || readOnly ? false : isSearchable,
|
|
2558
|
-
menuShouldBlockScroll: true,
|
|
2559
|
-
menuShouldScrollIntoView: true,
|
|
2560
|
-
openMenuOnFocus,
|
|
2561
|
-
menuIsOpen: !readOnly ? menuIsOpen : false,
|
|
2562
|
-
openMenuOnClick,
|
|
2563
|
-
closeMenuOnSelect: false,
|
|
2564
|
-
isMulti,
|
|
2565
|
-
classNamePrefix,
|
|
2566
|
-
isLoading,
|
|
2567
|
-
isClearable,
|
|
2568
|
-
id,
|
|
2569
|
-
onMenuScrollToBottom,
|
|
2570
|
-
onInputChange,
|
|
2571
|
-
allowCreateWhileLoading,
|
|
2572
|
-
onCreateOption,
|
|
2573
|
-
isDisabled: Boolean(disabled),
|
|
2574
|
-
}), [
|
|
2575
|
-
allowCreateWhileLoading,
|
|
2576
|
-
classNamePrefix,
|
|
2577
|
-
customComponents,
|
|
2578
|
-
customStyles,
|
|
2579
|
-
dataTestId,
|
|
2580
|
-
disabled,
|
|
2581
|
-
id,
|
|
2582
|
-
isClearable,
|
|
2583
|
-
isLoading,
|
|
2584
|
-
isMulti,
|
|
2585
|
-
isSearchable,
|
|
2586
|
-
label,
|
|
2587
|
-
maxMenuHeight,
|
|
2588
|
-
menuIsOpen,
|
|
2589
|
-
menuPlacement,
|
|
2590
|
-
onChange,
|
|
2591
|
-
onCreateOption,
|
|
2592
|
-
onInputChange,
|
|
2593
|
-
onMenuScrollToBottom,
|
|
2594
|
-
openMenuOnClick,
|
|
2595
|
-
openMenuOnFocus,
|
|
2596
|
-
props.menuPortalTarget,
|
|
2597
|
-
readOnly,
|
|
2598
|
-
value,
|
|
2599
|
-
]);
|
|
2600
|
-
const renderAsDisabled = Boolean(props.disabled) || props.readOnly;
|
|
2601
|
-
return (jsxs("div", { className: cvaSelect({ invalid: hasError, disabled: renderAsDisabled, className: props.className }), "data-testid": dataTestId, ref: refContainer, children: [prefix !== undefined ? (jsx("div", { className: cvaSelectPrefixSuffix({ kind: "prefix" }), "data-testid": dataTestId ? `${dataTestId}-prefix` : null, children: prefix })) : null, async ? (jsx(ReactAsyncCreatableSelect, { ...props, ...reactCreatableSelectProps, ...async, onMenuClose: closeMenuHandler, onMenuOpen: openMenuHandler, placeholder: renderAsDisabled ? null : props.placeholder })) : (jsx(ReactCreatableSelect, { ...props, ...reactCreatableSelectProps, hideSelectedOptions: false, isMulti: isMulti, onMenuClose: closeMenuHandler, onMenuOpen: openMenuHandler, options: options, placeholder: renderAsDisabled ? null : props.placeholder })), typeof props.disabled === "object" ? (jsx("div", { className: cvaSelectPrefixSuffix({ kind: "suffix" }), "data-testid": dataTestId ? `${dataTestId}-locked` : null, children: jsx(InputLockReasonTooltip, { ...props.disabled }) })) : null] }));
|
|
2696
|
+
const isUniform = (schedule) => {
|
|
2697
|
+
return schedule.every((day, _, collection) => collection[0]?.range?.timeFrom === day.range?.timeFrom && collection[0]?.range?.timeTo === day.range?.timeTo);
|
|
2698
|
+
};
|
|
2699
|
+
/**
|
|
2700
|
+
* Checks if a list of schedule objects are consecutive days
|
|
2701
|
+
*
|
|
2702
|
+
* @param {RawSchedule[]} schedule List of schedule objects
|
|
2703
|
+
* @returns {boolean} Whether the schedule has consecutive days
|
|
2704
|
+
*/
|
|
2705
|
+
const hasConsecutiveDays = (schedule) => {
|
|
2706
|
+
const days = [1, 2, 3, 4, 5];
|
|
2707
|
+
return schedule.every(({ day }, index) => day === days[index]);
|
|
2602
2708
|
};
|
|
2603
|
-
CreatableSelect.displayName = "CreatableSelect";
|
|
2604
2709
|
|
|
2605
|
-
|
|
2606
|
-
|
|
2710
|
+
const cvaSearch = cvaMerge([
|
|
2711
|
+
"shadow-none",
|
|
2712
|
+
"component-search-border",
|
|
2713
|
+
"component-search-background",
|
|
2714
|
+
"hover:component-search-background",
|
|
2715
|
+
"hover:component-search-focus-hover",
|
|
2716
|
+
"transition-all",
|
|
2717
|
+
"duration-300",
|
|
2718
|
+
], {
|
|
2719
|
+
variants: {
|
|
2720
|
+
border: { true: ["!component-search-borderless"], false: "" },
|
|
2721
|
+
widenOnFocus: {
|
|
2722
|
+
true: [
|
|
2723
|
+
"component-search-width",
|
|
2724
|
+
"component-search-widen",
|
|
2725
|
+
"hover:component-search-widen",
|
|
2726
|
+
"focus-within:w-full",
|
|
2727
|
+
"max-w-sm",
|
|
2728
|
+
],
|
|
2729
|
+
false: "w-full",
|
|
2730
|
+
},
|
|
2731
|
+
},
|
|
2732
|
+
});
|
|
2733
|
+
|
|
2607
2734
|
/**
|
|
2608
|
-
*
|
|
2735
|
+
* The Search component is used to render a search input field.
|
|
2609
2736
|
*
|
|
2610
|
-
* @param {
|
|
2611
|
-
* @returns {ReactElement} Select component
|
|
2737
|
+
* @param {SearchProps} props - The props for the Search component
|
|
2612
2738
|
*/
|
|
2613
|
-
const
|
|
2614
|
-
const {
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
value,
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
onChange,
|
|
2621
|
-
"aria-label": label,
|
|
2622
|
-
"data-testid": dataTestId,
|
|
2623
|
-
components: customComponents,
|
|
2624
|
-
styles: customStyles,
|
|
2625
|
-
tabSelectsValue: false,
|
|
2626
|
-
blurInputOnSelect: false,
|
|
2627
|
-
// This configuration allows for more flexible positioning control of the dropdown.
|
|
2628
|
-
// Setting menuPortalTarget to 'null' specifies that the dropdown should be rendered within
|
|
2629
|
-
// the parent element instead of 'document.body'.
|
|
2630
|
-
menuPortalTarget: props.menuPortalTarget !== undefined ? props.menuPortalTarget : document.body,
|
|
2631
|
-
isSearchable: disabled || readOnly ? false : isSearchable,
|
|
2632
|
-
menuShouldBlockScroll: true,
|
|
2633
|
-
menuShouldScrollIntoView: true,
|
|
2634
|
-
openMenuOnFocus,
|
|
2635
|
-
menuIsOpen: !readOnly ? menuIsOpen : false,
|
|
2636
|
-
openMenuOnClick,
|
|
2637
|
-
closeMenuOnSelect: !isMulti,
|
|
2638
|
-
isMulti,
|
|
2639
|
-
classNamePrefix,
|
|
2640
|
-
isLoading,
|
|
2641
|
-
isClearable,
|
|
2642
|
-
id,
|
|
2643
|
-
onMenuScrollToBottom,
|
|
2644
|
-
onInputChange,
|
|
2645
|
-
hideSelectedOptions,
|
|
2646
|
-
isDisabled: Boolean(disabled),
|
|
2647
|
-
}), [
|
|
2648
|
-
classNamePrefix,
|
|
2649
|
-
customComponents,
|
|
2650
|
-
customStyles,
|
|
2651
|
-
dataTestId,
|
|
2652
|
-
disabled,
|
|
2653
|
-
hideSelectedOptions,
|
|
2654
|
-
id,
|
|
2655
|
-
isClearable,
|
|
2656
|
-
isLoading,
|
|
2657
|
-
isMulti,
|
|
2658
|
-
isSearchable,
|
|
2659
|
-
label,
|
|
2660
|
-
maxMenuHeight,
|
|
2661
|
-
menuIsOpen,
|
|
2662
|
-
menuPlacement,
|
|
2663
|
-
onChange,
|
|
2664
|
-
onInputChange,
|
|
2665
|
-
onMenuScrollToBottom,
|
|
2666
|
-
openMenuOnClick,
|
|
2667
|
-
openMenuOnFocus,
|
|
2668
|
-
props.menuPortalTarget,
|
|
2669
|
-
readOnly,
|
|
2670
|
-
value,
|
|
2671
|
-
]);
|
|
2672
|
-
const renderAsDisabled = Boolean(props.disabled) || props.readOnly;
|
|
2673
|
-
return (jsxs("div", { className: cvaSelect({
|
|
2674
|
-
invalid: hasError,
|
|
2675
|
-
fieldSize: fieldSize,
|
|
2676
|
-
disabled: renderAsDisabled,
|
|
2677
|
-
className: props.className,
|
|
2678
|
-
}), "data-testid": dataTestId, ref: refContainer, children: [prefix !== undefined ? (jsx("div", { className: cvaSelectPrefixSuffix({ kind: "prefix" }), "data-testid": dataTestId ? `${dataTestId}-prefix` : null, children: prefix })) : null, async ? (jsx(ReactAsyncSelect, { ...props, ...reactSelectProps, ...async, menuPosition: menuPosition, onMenuClose: closeMenuHandler, onMenuOpen: openMenuHandler, placeholder: renderAsDisabled ? null : props.placeholder })) : (jsx(ReactSyncSelect, { ...props, ...reactSelectProps, isMulti: isMulti, menuPosition: menuPosition, onMenuClose: closeMenuHandler, onMenuOpen: openMenuHandler, options: options, placeholder: renderAsDisabled ? null : props.placeholder })), typeof props.disabled === "object" ? (jsx("div", { className: cvaSelectPrefixSuffix({ kind: "suffix" }), "data-testid": dataTestId ? `${dataTestId}-locked` : null, children: jsx(InputLockReasonTooltip, { ...props.disabled }) })) : null] }));
|
|
2739
|
+
const Search = ({ className, placeholder, value, widenInputOnFocus, hideBorderWhenNotInFocus = false, disabled = false, onKeyUp, onChange, onFocus, onBlur, name, onClear, dataTestId, autoComplete = "on", loading = false, inputClassName, iconName = "MagnifyingGlass", style, xMarkRef, ref, ...rest }) => {
|
|
2740
|
+
const { t } = useTranslation();
|
|
2741
|
+
return (jsx(TextBaseInput, { ...rest, autoComplete: autoComplete, className: cvaSearch({ className, border: hideBorderWhenNotInFocus, widenOnFocus: widenInputOnFocus }), dataTestId: dataTestId, disabled: disabled, inputClassName: inputClassName, name: name, onBlur: onBlur, onChange: onChange, onFocus: onFocus, onKeyUp: onKeyUp, placeholder: placeholder ?? t("search.placeholder"), prefix: loading ? (jsx(Spinner, { centering: "centered", size: rest.fieldSize ?? undefined })) : (jsx(Icon, { name: iconName, size: rest.fieldSize ?? undefined })), ref: ref, suffix:
|
|
2742
|
+
//only show the clear button if there is a value and the onClear function is provided
|
|
2743
|
+
onClear && value ? (jsx("button", { className: "flex", "data-testid": dataTestId ? `${dataTestId}_suffix_component` : null, onClick: () => {
|
|
2744
|
+
onClear();
|
|
2745
|
+
}, ref: xMarkRef, type: "button", children: jsx(Icon, { name: "XMark", size: "small" }) })) : undefined, value: value }));
|
|
2679
2746
|
};
|
|
2680
|
-
|
|
2747
|
+
Search.displayName = "Search";
|
|
2681
2748
|
|
|
2682
2749
|
/**
|
|
2683
2750
|
*
|
|
@@ -2764,7 +2831,7 @@ CreatableSelectField.displayName = "CreatableSelectField";
|
|
|
2764
2831
|
* @param {SelectFieldProps} props - The props for the SelectField component
|
|
2765
2832
|
*/
|
|
2766
2833
|
const SelectField = ({ ref, ...props }) => {
|
|
2767
|
-
return (jsx(FormFieldSelectAdapter, { ...props, ref: ref, children: convertedProps => jsx(
|
|
2834
|
+
return (jsx(FormFieldSelectAdapter, { ...props, ref: ref, children: convertedProps => jsx(BaseSelect, { ...convertedProps }) }));
|
|
2768
2835
|
};
|
|
2769
2836
|
SelectField.displayName = "SelectField";
|
|
2770
2837
|
|
|
@@ -3177,4 +3244,4 @@ const useZodValidators = () => {
|
|
|
3177
3244
|
*/
|
|
3178
3245
|
setupLibraryTranslations();
|
|
3179
3246
|
|
|
3180
|
-
export { ActionButton, BaseInput, Checkbox, CheckboxField, ColorField, CreatableSelect, CreatableSelectField, DEFAULT_TIME, DateBaseInput, DateField, DropZone, DropZoneDefaultLabel, EMAIL_REGEX, EmailField, FormFieldSelectAdapter, FormGroup, Label, MultiSelectMenuItem, NumberBaseInput, NumberField, OptionCard, PasswordBaseInput, PasswordField, PhoneBaseInput, PhoneField, PhoneFieldWithController, RadioGroup, RadioItem, Schedule, ScheduleVariant, Search,
|
|
3247
|
+
export { ActionButton, BaseInput, BaseSelect, Checkbox, CheckboxField, ColorField, CreatableSelect, CreatableSelectField, DEFAULT_TIME, DateBaseInput, DateField, DropZone, DropZoneDefaultLabel, EMAIL_REGEX, EmailField, FormFieldSelectAdapter, FormGroup, Label, MultiSelectField, MultiSelectMenuItem, NumberBaseInput, NumberField, OptionCard, PasswordBaseInput, PasswordField, PhoneBaseInput, PhoneField, PhoneFieldWithController, RadioGroup, RadioItem, Schedule, ScheduleVariant, Search, SelectField, SingleSelectMenuItem, TextAreaBaseInput, TextAreaField, TextBaseInput, TextField, TimeRange, TimeRangeField, ToggleSwitch, ToggleSwitchOption, UploadField, UploadInput, UrlField, checkIfPhoneNumberHasPlus, countryCodeToFlagEmoji, cvaAccessoriesContainer, cvaActionButton, cvaActionContainer, cvaInput$1 as cvaInput, cvaInputAddon, cvaInputBase, cvaInputBaseDisabled, cvaInputBaseInvalid, cvaInputField, cvaInputItemPlacementManager, cvaInputPrefix, cvaInputSuffix, cvaLabel, cvaSelect, cvaSelectControl, cvaSelectCounter, cvaSelectDynamicTagContainer, cvaSelectIcon, cvaSelectMenu, cvaSelectMenuList, cvaSelectPrefixSuffix, cvaSelectXIcon, getCountryAbbreviation, getPhoneNumberWithPlus, isInvalidCountryCode, isInvalidPhoneNumber, isValidHEXColor, parseSchedule, phoneErrorMessage, serializeSchedule, useCustomComponents, useGetPhoneValidationRules, usePhoneInput, useZodValidators, validateEmailAddress, validatePhoneNumber, weekDay };
|