@trackunit/react-form-components 1.8.64 → 1.8.66

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