@trackunit/react-form-components 0.0.288 → 0.0.290

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.esm.js CHANGED
@@ -1,17 +1,17 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { useNamespaceTranslation, registerTranslations } from '@trackunit/i18n-library-translation';
3
- import { IconButton, Icon, Tooltip, Heading, Text, MenuItem, Tag, Spinner } from '@trackunit/react-components';
4
- import { cvaMerge } from '@trackunit/css-class-variance-utilities';
5
3
  import * as React from 'react';
6
- import React__default, { useMemo, forwardRef, useCallback, cloneElement, useState, useEffect, useRef } from 'react';
4
+ import React__default, { forwardRef, useState, useMemo, useRef, useImperativeHandle, useCallback, useEffect, cloneElement } from 'react';
7
5
  import { v4 } from 'uuid';
8
- import { format } from 'date-fns';
9
- import parsePhoneNumberFromString, { isSupportedCountry, getCountries, getCountryCallingCode, isValidPhoneNumber, parsePhoneNumberFromString as parsePhoneNumberFromString$1 } from 'libphonenumber-js';
6
+ import { Tooltip, Text, MenuItem, Icon, Tag, Spinner, IconButton, Heading } from '@trackunit/react-components';
7
+ import { cvaMerge } from '@trackunit/css-class-variance-utilities';
10
8
  import ReactSelect, { components } from 'react-select';
11
9
  export { default as ValueType } from 'react-select';
12
10
  import ReactAsyncCreatableSelect from 'react-select/async-creatable';
13
11
  import ReactCreatableSelect from 'react-select/creatable';
14
12
  import ReactAsyncSelect from 'react-select/async';
13
+ import { format } from 'date-fns';
14
+ import parsePhoneNumberFromString, { isSupportedCountry, getCountries, getCountryCallingCode, isValidPhoneNumber, parsePhoneNumberFromString as parsePhoneNumberFromString$1 } from 'libphonenumber-js';
15
15
 
16
16
  var defaultTranslations = {
17
17
  "phoneField.error.INVALID_COUNTRY": "The country code is not valid",
@@ -67,54 +67,6 @@ const setupLibraryTranslations = () => {
67
67
  registerTranslations(translations);
68
68
  };
69
69
 
70
- const cvaActionButton = cvaMerge(["drop-shadow-none", "rounded-md"], {
71
- variants: {
72
- size: {
73
- small: ["w-6", "h-6"],
74
- medium: ["w-8", "h-8"],
75
- },
76
- },
77
- defaultVariants: {
78
- size: "medium",
79
- },
80
- });
81
- const cvaActionContainer = cvaMerge(["flex", "items-center", "m-1"]);
82
-
83
- /**
84
- * The ActionButton component is a wrapper over IconButton to perform an action when the onClick event is triggered.
85
- *
86
- * @param {ActionButtonProps} props - The props for the ActionButton component
87
- * @returns {JSX.Element} ActionButton component
88
- */
89
- const ActionButton = ({ type, value, dataTestId, iconSize, disabled, className, }) => {
90
- const getIconName = () => {
91
- switch (type) {
92
- case "PHONE_NUMBER":
93
- return "PhoneArrowUpRight";
94
- case "WEB_ADDRESS":
95
- return "ArrowTopRightOnSquare";
96
- case "EMAIL":
97
- default:
98
- return "Envelope";
99
- }
100
- };
101
- const ButtonAction = () => {
102
- switch (type) {
103
- case "EMAIL":
104
- return window.open(`mailto:${value}`);
105
- case "WEB_ADDRESS":
106
- if (value) {
107
- return window.open(value, "_blank", "noopener,noreferrer");
108
- }
109
- return null;
110
- case "PHONE_NUMBER":
111
- default:
112
- return window.open(`tel:${value}`);
113
- }
114
- };
115
- return (jsx("div", { className: cvaActionContainer({ className }), children: jsx(IconButton, { disabled: disabled, dataTestId: dataTestId || "testIconButtonId", size: "small", color: "tertiary", icon: jsx(Icon, { size: "small", name: getIconName() }), onClick: ButtonAction, className: cvaActionButton({ size: iconSize }) }) }));
116
- };
117
-
118
70
  /******************************************************************************
119
71
  Copyright (c) Microsoft Corporation.
120
72
 
@@ -152,135 +104,95 @@ function __awaiter(thisArg, _arguments, P, generator) {
152
104
  });
153
105
  }
154
106
 
155
- const cvaInputBase = cvaMerge([
156
- "component-baseInput-shadow",
157
- "component-baseInput-border",
158
- "component-baseInput-background",
159
- "border-slate-300",
160
- "focus-within:ring-2",
161
- "focus-within:ring-inset",
162
- "focus-within:ring-primary-500",
163
- "focus-within:border-slate-400",
164
- "hover:border-slate-400",
165
- "hover:bg-slate-50",
166
- "transition",
167
- ]);
168
- const cvaInputBaseDisabled = cvaMerge(["bg-slate-100", "hover:bg-slate-100", "hover:border-slate-300"]);
169
- const cvaInputBaseInvalid = cvaMerge(["border-danger-600", "focus-within:ring-danger-600"]);
170
- const cvaInput = cvaMerge(["relative", "flex", "h-10", cvaInputBase()], {
171
- variants: {
172
- size: {
173
- small: ["h-8"],
174
- medium: ["h-10"],
175
- },
176
- disabled: {
177
- true: cvaInputBaseDisabled(),
178
- false: "",
179
- },
180
- invalid: {
181
- true: cvaInputBaseInvalid(),
182
- false: "",
183
- },
184
- },
185
- });
186
- const cvaInputField = cvaMerge([
187
- "w-full",
188
- "px-3",
189
- "border-0",
190
- "bg-transparent",
107
+ const cvaLabel = cvaMerge([
191
108
  "text-base",
192
- "text-slate-900",
193
- "placeholder-slate-400",
194
- "focus-visible:outline-none",
109
+ "text-slate-700",
110
+ "truncate",
111
+ "hover:text-slate-800",
112
+ "group-hover:text-slate-800",
113
+ "active:text-slate-800",
114
+ "group-active:text-slate-800",
195
115
  ], {
196
116
  variants: {
197
- size: {
198
- small: ["py-1"],
199
- medium: ["py-2"],
117
+ invalid: {
118
+ true: "text-red-600 hover:text-red-700 active:text-red-700 group-hover:text-red-700 group-active:text-red-700",
119
+ false: "",
200
120
  },
201
121
  disabled: {
202
- true: ["text-slate-700"],
122
+ true: "text-slate-400 hover:text-slate-400 group-hover:text-slate-400",
203
123
  false: "",
204
124
  },
205
125
  },
126
+ defaultVariants: {
127
+ invalid: false,
128
+ disabled: false,
129
+ },
206
130
  });
207
- const cvaInputAddon = cvaMerge([
208
- "flex",
209
- "justify-center",
210
- "items-center",
211
- "px-3",
212
- "border-slate-300",
213
- "text-slate-500",
214
- "bg-slate-50",
215
- ]);
216
- const cvaInputAddonBefore = cvaMerge([cvaInputAddon(), "border-r", "rounded-l-lg"]);
217
- const cvaInputAddonAfter = cvaMerge([cvaInputAddon(), "border-l", "rounded-r-lg"]);
218
- const cvaInputPrefix = cvaMerge([
219
- "flex",
220
- "justify-center",
221
- "items-center",
222
- "text-slate-400",
223
- "hover:component-search-prefix",
224
- "component-baseInput-prefix",
225
- "component-search-borderless",
226
- "px-3",
227
- ], {
131
+
132
+ /**
133
+ * The Label component is used for labels for input fields.
134
+ * This component is **not used directly**, but is part of the FormGroup and Field components.
135
+ *
136
+ * @param {LabelProps} props - The props for the Label component
137
+ * @returns {JSX.Element} Label component
138
+ */
139
+ const Label = ({ id, htmlFor, children, className, dataTestId, disabled, isInvalid, }) => {
140
+ return (jsx("label", { id: id || "", htmlFor: htmlFor || "", className: cvaLabel({ invalid: isInvalid, disabled, className }), "data-testid": dataTestId, children: children }));
141
+ };
142
+
143
+ const cvaFormGroup = cvaMerge(["component-formGroup-gap"], {
228
144
  variants: {
229
145
  disabled: {
230
- true: ["text-slate-500"],
146
+ true: "pointer-events-none",
231
147
  false: "",
232
148
  },
233
149
  },
234
150
  });
235
- const cvaInputSuffix = cvaMerge(["flex", "justify-center", "items-center", "text-slate-400", "px-3"], {
151
+ const cvaFormGroupContainerBefore = cvaMerge(["flex", "gap-1", "mb-1"]);
152
+ const cvaFormGroupContainerAfter = cvaMerge(["flex", "justify-between", "mt-1", "text-xs", "text-slate-500"], {
236
153
  variants: {
237
- disabled: {
238
- true: ["text-slate-500"],
154
+ invalid: {
155
+ true: "text-danger-500",
239
156
  false: "",
240
157
  },
241
158
  },
242
159
  });
160
+ const cvaHelpAddon = cvaMerge(["ml-auto"]);
243
161
 
244
162
  /**
245
- * A base input component that can be used for text inputs, password inputs, etc.
246
- * A reference to the input element is provided as the `ref` prop.
247
- * Extends props from [React.InputHTMLAttributes](https://reactjs.org/docs/dom-elements.html#input)
163
+ * The FormGroup component should be used to wrap any Input element that needs a label.
164
+ * Besides a label the component supplies an optional Tooltip, HelpText and HelpAddon support.
248
165
  *
249
- * For specific input types make sure to use the corresponding input component.
250
- * This is a base used by our other input components such as TextInput, NumberInput, PasswordInput, etc.
166
+ * @param {FormGroupProps} props - The props for the FormGroup component
167
+ * @returns {JSX.Element} FormGroup component
251
168
  */
252
- const BaseInput = React.forwardRef((_a, ref) => {
253
- var { className, isInvalid, dataTestId, prefix, suffix, addonBefore, addonAfter, actions, fieldSize = "medium", nonInteractive = false, type, inputClassName, addonBeforeClassName } = _a, rest = __rest(_a, ["className", "isInvalid", "dataTestId", "prefix", "suffix", "addonBefore", "addonAfter", "actions", "fieldSize", "nonInteractive", "type", "inputClassName", "addonBeforeClassName"]);
254
- const isReadOnlyWithPlaceholder = useMemo(() => !!rest.readOnly && !!rest.placeholder && !rest.value, [rest.readOnly, rest.placeholder, rest.value]);
255
- const renderAsDisabled = rest.disabled || rest.readOnly;
256
- return (jsxs("div", { className: cvaInput({
257
- disabled: renderAsDisabled,
258
- invalid: isInvalid,
259
- size: fieldSize,
260
- className,
261
- }), "data-testid": dataTestId && `${dataTestId}-container`, children: [addonBefore && addonBefore !== undefined ? (jsx("div", { className: cvaInputAddonBefore({ className: addonBeforeClassName }), "data-testid": dataTestId && `${dataTestId}-addonBefore`, children: addonBefore })) : null, prefix && addonBefore !== prefix ? (jsx("div", { className: cvaInputPrefix({
262
- disabled: renderAsDisabled,
263
- }), "data-testid": dataTestId && `${dataTestId}-prefix`, children: prefix })) : null, jsx("input", Object.assign({ ref: ref, "data-testid": dataTestId, className: cvaInputField({ size: fieldSize, disabled: renderAsDisabled, className: inputClassName }), type: isReadOnlyWithPlaceholder ? "text" : type }, rest, { readOnly: rest.readOnly || nonInteractive })), suffix && addonBefore !== suffix ? (jsx("div", { className: cvaInputSuffix({
264
- disabled: renderAsDisabled,
265
- }), "data-testid": dataTestId && `${dataTestId}-suffix`, children: suffix })) : null, actions && addonBefore !== actions ? actions : null, addonAfter && addonAfter !== undefined && addonBefore !== addonAfter ? (jsx("div", { className: cvaInputAddonAfter(), "data-testid": dataTestId && `${dataTestId}-addonAfter`, children: addonAfter })) : null] }));
266
- });
169
+ const FormGroup = ({ isInvalid, helpText, helpAddon, tip, className, dataTestId, label, htmlFor, children, tipIconProps, disabled = false, }) => {
170
+ return (jsxs("div", { className: cvaFormGroup({ disabled, className }), "data-testid": dataTestId, children: [jsxs("div", { className: cvaFormGroupContainerBefore(), children: [jsx(Label, { className: "component-formGroup-font", dataTestId: dataTestId && `${dataTestId}-label`, htmlFor: htmlFor, id: htmlFor + "-label", children: label }), tip && (jsx(Tooltip, { dataTestId: dataTestId && `${dataTestId}-tooltip`, iconProps: tipIconProps, label: tip, placement: "bottom" }))] }), children, jsxs("div", { className: cvaFormGroupContainerAfter({ invalid: isInvalid }), children: [helpText && jsx("span", { "data-testid": dataTestId && `${dataTestId}-helpText`, children: helpText }), helpAddon && (jsx("span", { className: cvaHelpAddon(), "data-testid": dataTestId && `${dataTestId}-helpAddon`, children: helpAddon }))] })] }));
171
+ };
267
172
 
268
- const cvaLabel = cvaMerge([
269
- "text-base",
270
- "text-slate-700",
271
- "truncate",
272
- "hover:text-slate-800",
273
- "group-hover:text-slate-800",
274
- "active:text-slate-800",
275
- "group-active:text-slate-800",
173
+ const cvaSelect = cvaMerge([
174
+ "relative",
175
+ "flex",
176
+ "shadow-sm",
177
+ "rounded-lg",
178
+ "border",
179
+ "border-slate-300",
180
+ "focus-within:ring-2",
181
+ "focus-within:ring-inset",
182
+ "focus-within:ring-primary-600",
183
+ "focus-within:border-slate-400",
184
+ "hover:border-slate-400",
185
+ "hover:bg-slate-50",
186
+ "bg-white",
187
+ "transition",
276
188
  ], {
277
189
  variants: {
278
190
  invalid: {
279
- true: "text-red-600 hover:text-red-700 active:text-red-700 group-hover:text-red-700 group-active:text-red-700",
191
+ true: "border-red-600 text-red-600 focus-within:ring-red-600 hover:border-red-600",
280
192
  false: "",
281
193
  },
282
194
  disabled: {
283
- true: "text-slate-400 hover:text-slate-400 group-hover:text-slate-400",
195
+ true: "!bg-slate-100",
284
196
  false: "",
285
197
  },
286
198
  },
@@ -289,32 +201,111 @@ const cvaLabel = cvaMerge([
289
201
  disabled: false,
290
202
  },
291
203
  });
204
+ const cvaSelectIcon = cvaMerge("mr-2 flex cursor-pointer items-center justify-center text-slate-400 hover:text-slate-500");
205
+ const cvaSelectPrefix = cvaMerge(["flex", "justify-center", "items-center", "text-slate-400", "pl-2"]);
206
+ const cvaSelectXIcon = cvaMerge([
207
+ "mr-2 flex cursor-pointer items-center justify-center text-slate-400 hover:text-slate-500",
208
+ "ml-1",
209
+ ]);
210
+ const cvaSelectMenuList = cvaMerge(["min-w-min", "shadow-md", "rounded-lg", "z-20", "bg-white", "p-1", "border", "border-slate-300", "gap-1", "grid"], {
211
+ variants: {
212
+ menuIsOpen: {
213
+ true: "animate-fade-in-fast",
214
+ false: "animate-fade-out-fast",
215
+ },
216
+ },
217
+ });
218
+ const cvaSelectDynamicTagContainer = cvaMerge(["h-full", "flex", "gap-1", "items-center"], {
219
+ variants: {
220
+ visible: { true: "visible", false: "invisible" },
221
+ },
222
+ });
223
+ const cvaSelectCounter = cvaMerge(["overflow-hidden", "whitespace-nowrap"]);
224
+ const cvaSelectMenu = cvaMerge(["relative", "p-1", "grid", "gap-1"]);
292
225
 
293
226
  /**
294
- * The CheckIcon component is used by the checkbox to display the checked state.
227
+ * The SelectContainer component.
295
228
  *
296
- * @param {CheckIconProps} props - The props for the CheckIcon component
297
- * @returns {JSX.Element} CheckIcon component
229
+ * @param {SelectContainerProps} props - The props for the SelectContainer component
230
+ * @returns {JSX.Element} SelectContainer component
298
231
  */
299
- const CheckIcon = ({ className, dataTestId }) => {
300
- return (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: className, "data-testid": dataTestId, fill: "none", viewBox: "0 0 10 10", stroke: "currentColor", strokeWidth: "2", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "m2 5.5 2 2L8.5 3" }) }));
232
+ const SelectContainer = ({ children, label, hideLabel, id, hasError, error, description, info, dataTestId, isDisabled, }) => {
233
+ return (jsxs("label", { className: cvaSelect({ invalid: hasError, disabled: isDisabled }), "data-testid": dataTestId, children: [label && !hideLabel && jsx(Label, { htmlFor: id, children: label }), children, (hasError || info) && jsx(Text, { size: "small", children: hasError ? error : info })] }));
301
234
  };
302
235
 
303
- const cvaCheckbox = cvaMerge([
304
- "w-4",
305
- "h-4",
306
- "border-solid",
307
- "border",
308
- "shrink-0",
309
- "border-slate-300",
310
- "flex",
311
- "rounded",
312
- "hover:bg-slate-100",
313
- "bg-white",
314
- "items-center",
315
- "shadow-sm",
316
- "justify-center",
317
- "box-border",
236
+ /**
237
+ * @param {MultiValue<Option> | SingleValue<Option>} arg option to check type
238
+ * @returns {arg is MultiValue<Option> } is Multivalue
239
+ */
240
+ function isMultiValue(arg) {
241
+ return Array.isArray(arg);
242
+ }
243
+ function isGroupBase(arg) {
244
+ return arg.options !== undefined;
245
+ }
246
+ const isSelectedOption = (option, selected) => {
247
+ if (isGroupBase(option)) {
248
+ return false;
249
+ }
250
+ return isMultiValue(selected)
251
+ ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
252
+ selected.some(v => v.value === option.value)
253
+ : // eslint-disable-next-line @typescript-eslint/no-explicit-any
254
+ option.value === (selected === null || selected === void 0 ? void 0 : selected.value);
255
+ };
256
+ const removeSelectedFromGroups = (group, selected) => {
257
+ if (isGroupBase(group)) {
258
+ return Object.assign(Object.assign({}, group), { options: group.options.filter(option => !isSelectedOption(option, selected)) });
259
+ }
260
+ return group;
261
+ };
262
+ /**
263
+ * @template IsMulti
264
+ * @template Group
265
+ * @param {OptionsOrGroups<Option, Group> | undefined} options An array of options to select from
266
+ * @param {PropsValue<Option> | undefined} value Selected values
267
+ * @returns {OptionsOrGroups<Option, Group>} An array of ordered options with selected on top
268
+ */
269
+ const getOrderedOptions = (options, value) => {
270
+ if (value && options) {
271
+ const orderedValues = isMultiValue(value)
272
+ ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
273
+ [...value].sort((a, b) => a.label.localeCompare(b.label))
274
+ : [value];
275
+ const selectableOptions = options
276
+ .filter(option => !isSelectedOption(option, value))
277
+ .map(option => removeSelectedFromGroups(option, value));
278
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
279
+ return orderedValues.concat(selectableOptions) || [];
280
+ }
281
+ return options || [];
282
+ };
283
+
284
+ /**
285
+ * The CheckIcon component is used by the checkbox to display the checked state.
286
+ *
287
+ * @param {CheckIconProps} props - The props for the CheckIcon component
288
+ * @returns {JSX.Element} CheckIcon component
289
+ */
290
+ const CheckIcon = ({ className, dataTestId }) => {
291
+ return (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: className, "data-testid": dataTestId, fill: "none", viewBox: "0 0 10 10", stroke: "currentColor", strokeWidth: "2", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "m2 5.5 2 2L8.5 3" }) }));
292
+ };
293
+
294
+ const cvaCheckbox = cvaMerge([
295
+ "w-4",
296
+ "h-4",
297
+ "border-solid",
298
+ "border",
299
+ "shrink-0",
300
+ "border-slate-300",
301
+ "flex",
302
+ "rounded",
303
+ "hover:bg-slate-100",
304
+ "bg-white",
305
+ "items-center",
306
+ "shadow-sm",
307
+ "justify-center",
308
+ "box-border",
318
309
  "transition",
319
310
  "focus:bg-slate-200",
320
311
  "outline-0",
@@ -478,838 +469,899 @@ const Checkbox = React.forwardRef((_a, ref) => {
478
469
  });
479
470
 
480
471
  /**
481
- * A wrapper around BaseInput with a pop-up day picker.
482
- *
483
- * NOTE: If shown with a label, please use the `DateField` component instead.
484
- */
485
- const DateInput = forwardRef((_a, ref) => {
486
- var { min, max, defaultValue, value } = _a, rest = __rest(_a, ["min", "max", "defaultValue", "value"]);
487
- const formatDateToInputString = (date) => date instanceof Date ? format(date, "yyyy-MM-dd") : date;
488
- // Chrome and Firefox need their default icon to have datepicker functionality.
489
- const showIcon = !/Chrome/.test(navigator.userAgent) && !/Firefox/.test(navigator.userAgent);
490
- return (jsx(BaseInput, Object.assign({ type: "date", value: formatDateToInputString(value), defaultValue: formatDateToInputString(defaultValue), min: formatDateToInputString(min), max: formatDateToInputString(max), ref: ref, suffix: showIcon && jsx(Icon, { dataTestId: "calendar", name: "Calendar", size: "medium", type: "solid" }) }, rest)));
491
- });
492
-
493
- /**
494
- * The Label component is used for labels for input fields.
495
- * This component is **not used directly**, but is part of the FormGroup and Field components.
472
+ * A single select menu item is a basic wrapper around Menu item designed to be used as a single value render in Select list
496
473
  *
497
- * @param {LabelProps} props - The props for the Label component
498
- * @returns {JSX.Element} Label component
474
+ * @param {SelectMenuItemProps} props - The props for the SingleSelectMenuItem
475
+ * @returns {JSX.Element} SingleSelectMenuItem
499
476
  */
500
- const Label = ({ id, htmlFor, children, className, dataTestId, disabled, isInvalid, }) => {
501
- return (jsx("label", { id: id || "", htmlFor: htmlFor || "", className: cvaLabel({ invalid: isInvalid, disabled, className }), "data-testid": dataTestId, children: children }));
477
+ const SingleSelectMenuItem = ({ label, icon, onClick, selected, dataTestId, focused, disabled, }) => {
478
+ return (jsx(MenuItem, { dataTestId: dataTestId, label: label, prefix: icon, onClick: onClick, selected: selected, disabled: disabled, focused: focused, suffix: selected ? jsx(Icon, { name: "Check", size: "small" }) : undefined }));
502
479
  };
503
-
504
- const cvaFormGroup = cvaMerge(["component-formGroup-gap"], {
505
- variants: {
506
- disabled: {
507
- true: "pointer-events-none",
508
- false: "",
509
- },
510
- },
511
- });
512
- const cvaFormGroupContainerBefore = cvaMerge(["flex", "gap-1", "mb-1"]);
513
- const cvaFormGroupContainerAfter = cvaMerge(["flex", "justify-between", "mt-1", "text-xs", "text-slate-500"], {
514
- variants: {
515
- invalid: {
516
- true: "text-danger-500",
517
- false: "",
518
- },
519
- },
520
- });
521
- const cvaHelpAddon = cvaMerge(["ml-auto"]);
522
-
523
480
  /**
524
- * The FormGroup component should be used to wrap any Input element that needs a label.
525
- * Besides a label the component supplies an optional Tooltip, HelpText and HelpAddon support.
481
+ * A multi select menu item is a basic wrapper around Menu item designed to be used as a multi value render in Select list
526
482
  *
527
- * @param {FormGroupProps} props - The props for the FormGroup component
528
- * @returns {JSX.Element} FormGroup component
483
+ * @param {SelectMenuItemProps} props - The props for the MultiSelectMenuItem
484
+ * @returns {JSX.Element} multi select menu item
529
485
  */
530
- const FormGroup = ({ isInvalid, helpText, helpAddon, tip, className, dataTestId, label, htmlFor, children, tipIconProps, disabled = false, }) => {
531
- return (jsxs("div", { className: cvaFormGroup({ disabled, className }), "data-testid": dataTestId, children: [jsxs("div", { className: cvaFormGroupContainerBefore(), children: [jsx(Label, { className: "component-formGroup-font", dataTestId: dataTestId && `${dataTestId}-label`, htmlFor: htmlFor, id: htmlFor + "-label", children: label }), tip && (jsx(Tooltip, { dataTestId: dataTestId && `${dataTestId}-tooltip`, iconProps: tipIconProps, label: tip, placement: "bottom" }))] }), children, jsxs("div", { className: cvaFormGroupContainerAfter({ invalid: isInvalid }), children: [helpText && jsx("span", { "data-testid": dataTestId && `${dataTestId}-helpText`, children: helpText }), helpAddon && (jsx("span", { className: cvaHelpAddon(), "data-testid": dataTestId && `${dataTestId}-helpAddon`, children: helpAddon }))] })] }));
486
+ const MultiSelectMenuItem = ({ label, onClick, selected, dataTestId, focused, disabled, }) => {
487
+ return (jsx(MenuItem, { dataTestId: dataTestId, label: label, disabled: disabled, prefix: jsx(Checkbox, { checked: selected, readOnly: false, onChange: () => null, disabled: disabled, onClick: e => {
488
+ e.stopPropagation();
489
+ } }), onClick: e => {
490
+ e.stopPropagation();
491
+ onClick && onClick(e);
492
+ }, selected: selected, focused: focused }));
532
493
  };
533
494
 
534
495
  /**
535
- * The date field component is used for entering date values.
536
- *
537
- * _**Do use**_ the DateField for date input.
496
+ * Extended Tag component with information about its own width.
497
+ * Used in the select component.
538
498
  *
539
- * _**Do not use**_ this fields for non-serialized dates. Use TextField instead.
499
+ * @param {TagProps} props - The props for the tag component
500
+ * @returns {JSX.Element} TagWithWidth component
540
501
  */
541
- const DateField = forwardRef((_a, ref) => {
542
- var { label, id, tip, helpText, errorMessage, helpAddon, isInvalid, className, defaultValue, dataTestId } = _a, rest = __rest(_a, ["label", "id", "tip", "helpText", "errorMessage", "helpAddon", "isInvalid", "className", "defaultValue", "dataTestId"]);
543
- const renderAsInvalid = isInvalid === undefined ? Boolean(errorMessage) : isInvalid;
544
- const htmlForId = id ? id : "dateField-" + v4();
545
- return (jsx(FormGroup, { htmlFor: htmlForId, label: label, tip: tip, isInvalid: renderAsInvalid, helpText: (renderAsInvalid && errorMessage) || helpText, helpAddon: helpAddon, disabled: rest.disabled, dataTestId: dataTestId && `${dataTestId}-FormGroup`, children: jsx(DateInput, Object.assign({ id: htmlForId, "aria-labelledby": htmlForId + "-label", ref: ref, defaultValue: defaultValue, isInvalid: renderAsInvalid }, rest, { className: className, dataTestId: dataTestId })) }));
546
- });
547
-
548
- const cvaDropZone = cvaMerge([
549
- "flex",
550
- "component-baseInput-background",
551
- "justify-center",
552
- "text-slate-500",
553
- "rounded-sm",
554
- "border-2",
555
- "border-gray-200",
556
- "border-dashed",
557
- "hover:bg-slate-100",
558
- "hover:border-solid",
559
- "hover:border-primary-500",
560
- ], {
561
- variants: {
562
- size: {
563
- small: ["p-2"],
564
- medium: ["p-4"],
565
- large: ["p-8"],
566
- },
567
- disabled: { true: ["bg-slate-100", "hover:bg-slate-100"], false: "" },
568
- dragActive: { true: ["border-gray-200", "bg-slate-100"], false: "" },
569
- dropComplete: {
570
- true: ["border-solid", "border-primary-500", "bg-slate-100", "hover:border-solid", "hover:border-gray-200"],
571
- false: "",
572
- },
573
- invalid: { true: ["border-danger-600", "text-danger-500"], false: "" },
574
- },
575
- });
576
- const cvaDropZoneLabel = cvaMerge([
577
- "h-full",
578
- "pt-1",
579
- "pb-1",
580
- "gap-2",
581
- "items-center",
582
- "flex-col",
583
- "flex",
584
- "justify-center",
585
- ]);
502
+ const TagWithWidth = (_a) => {
503
+ var { onWidthKnown, children } = _a, rest = __rest(_a, ["onWidthKnown", "children"]);
504
+ const ref = React__default.useRef(null);
505
+ React__default.useLayoutEffect(() => {
506
+ var _a;
507
+ onWidthKnown && onWidthKnown({ width: ((_a = ref.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || 0 });
508
+ }, [ref, onWidthKnown]);
509
+ return (jsx(Tag, Object.assign({ ref: ref }, rest, { children: children })));
510
+ };
586
511
 
587
512
  /**
588
- * The Drop Zone can be used to drag and drop files
513
+ * TagsContainer component to display tags in limited space when children can't fit space it displays counter
589
514
  *
590
- * @param {DropZoneProps} props - The props for the DropZone component
591
- * @returns {JSX.Element} DropZone component
515
+ * @param {TagsContainerProps} props - The props for the TagContainer
516
+ * @returns {JSX.Element} TagsContainer
592
517
  */
593
- const DropZone = (_a) => {
594
- var { className, dataTestId, filesSelected, label, size = "large", isInvalid = false, disabled = false } = _a, rest = __rest(_a, ["className", "dataTestId", "filesSelected", "label", "size", "isInvalid", "disabled"]);
595
- const [dragActive, setDragActive] = React__default.useState(false);
596
- const [fileDropped, setFileDropped] = React__default.useState(false);
597
- // function that handles drag enter, drag leave and drag over
598
- const handleDrag = (e) => {
599
- e.preventDefault();
600
- e.stopPropagation();
601
- if ((e.type === "dragenter" || e.type === "dragover") && !disabled) {
602
- setDragActive(true);
603
- }
604
- else if (e.type === "dragleave") {
605
- setDragActive(false);
518
+ const TagsContainer = ({ items, width = "100%", itemsGap = 5, postFix, disabled }) => {
519
+ const [isReady, setIsReady] = React__default.useState(false);
520
+ const [counterWidth, setCounterWidth] = React__default.useState(0);
521
+ const containerRef = React__default.useRef(null);
522
+ const availableWidth = React__default.useRef();
523
+ const childrenWidth = React__default.useRef([]);
524
+ const itemsCount = items.length;
525
+ React__default.useLayoutEffect(() => {
526
+ var _a;
527
+ availableWidth.current = ((_a = containerRef === null || containerRef === void 0 ? void 0 : containerRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || 0;
528
+ }, [containerRef]);
529
+ const onWidthKnownHandler = ({ width: reportedWidth }) => {
530
+ childrenWidth.current.push({ width: reportedWidth + itemsGap });
531
+ if (childrenWidth.current.length === itemsCount) {
532
+ setIsReady(true);
606
533
  }
607
534
  };
608
- //function to handle when user clicks on dropzone to upload
609
- const handleChange = (e) => {
610
- e.preventDefault();
611
- e.stopPropagation();
612
- if (e.target.files && !disabled) {
613
- filesSelected(e.target.files);
535
+ const requiredSpace = childrenWidth.current.reduce((previous, current) => {
536
+ return previous + current.width;
537
+ }, 0);
538
+ let counter = 0;
539
+ const availableSpace = ((availableWidth === null || availableWidth === void 0 ? void 0 : availableWidth.current) || 0) - counterWidth;
540
+ const renderedElements = items
541
+ .concat({ text: "", onClick: () => null, disabled: false }) // reserved element for a potential counter
542
+ .map((item, index) => {
543
+ const spaceNeeded = childrenWidth.current.slice(0, index + 1).reduce((previous, current) => {
544
+ return previous + current.width;
545
+ }, 0);
546
+ const isLast = index === items.length;
547
+ const counterRequired = requiredSpace > availableSpace && counter !== 0;
548
+ if (isLast && counterRequired) {
549
+ return (jsx(TagWithWidth, { onWidthKnown: ({ width: reportedWidth }) => setCounterWidth(reportedWidth), color: "white", disabled: disabled, children: jsxs("div", { "data-testid": "select-counter", className: cvaSelectCounter(), children: ["+", counter] }) }, item.text + index));
614
550
  }
615
- };
616
- //funtion to handle drop
617
- const handleDrop = (e) => {
618
- e.preventDefault();
619
- e.stopPropagation();
620
- setDragActive(false);
621
- if (e.dataTransfer.files && e.dataTransfer.files[0] && !disabled) {
622
- filesSelected(e.dataTransfer.files);
623
- setFileDropped(true);
551
+ if (isLast) {
552
+ return null;
624
553
  }
625
- };
626
- return (jsxs("div", Object.assign({ className: cvaDropZone({ size, dropComplete: fileDropped, dragActive, disabled, invalid: isInvalid, className }), "data-testid": dataTestId, onDragEnter: handleDrag, onDragLeave: handleDrag, onDragOver: handleDrag, onDrop: handleDrop, onClick: e => {
627
- if (disabled) {
628
- e.preventDefault();
629
- e.stopPropagation();
630
- }
631
- } }, rest, { children: [jsx("input", { title: "Hidden File Input", type: "file", id: "input-file-upload", onChange: handleChange, className: "hidden" }), jsxs("label", { className: cvaDropZoneLabel(), htmlFor: "input-file-upload", "data-testid": dataTestId && `${dataTestId}-label`, children: [jsx(Icon, { type: "solid", name: "ArrowUpCircle", color: isInvalid ? "danger" : "neutral", style: !isInvalid ? { color: "rgb(156 163 175)" } : {} }), jsx("span", { children: label })] })] })));
554
+ const itemCanFit = spaceNeeded <= availableSpace;
555
+ if (itemCanFit) {
556
+ return (jsx(TagWithWidth, { onWidthKnown: onWidthKnownHandler, className: "inline-flex shrink-0", onClose: e => {
557
+ e.stopPropagation();
558
+ item.onClick();
559
+ }, color: item.disabled ? "unknown" : "primary", dataTestId: `${item.text}-tag`, disabled: disabled, children: item.text }, item.text + index));
560
+ }
561
+ const fromTheItems = item.text !== "";
562
+ if (fromTheItems) {
563
+ counter = counter + 1;
564
+ }
565
+ return null;
566
+ })
567
+ .filter(element => element !== null);
568
+ return (jsxs("div", { style: {
569
+ width: `${width}`,
570
+ }, className: cvaSelectDynamicTagContainer({ visible: isReady }), ref: containerRef, children: [renderedElements, postFix] }));
632
571
  };
633
572
 
634
- // Doing the same check as we do on the backend
635
- // Using OWASP pattern from: https://owasp.org/www-community/OWASP_Validation_Regex_Repository
636
- const EMAIL_REGEX = /^[a-zA-Z0-9_+&*-]+(?:\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,7}$/;
637
573
  /**
638
- * @description Validate given email id.
639
- * @param email The address to validate.
640
- * @returns {boolean} Returns true if the email address is valid else false.
641
- * @example validateEmailAddress(test@gmail.com) // true
574
+ * A hook to retrieve components override object.
575
+ * 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.
576
+ *
577
+ * @template IsMulti
578
+ * @template Group
579
+ * @param {Partial<SelectComponents<Option, IsMulti, Group>> | undefined} componentsProps a custom component prop that you can to override defaults
580
+ * @param {boolean} disabled decide to override disabled variant
581
+ * @param {boolean} menuIsOpen menu is open state
582
+ * @param {React.MutableRefObject<boolean>} refMenuIsEnabled a flag to block menu from open
583
+ * @param {string} dataTestId a test id
584
+ * @param {number} maxSelectedDisplayCount a number of max display count
585
+ * @param {JSX.Element} dropdownIcon an custom dropdown icon
586
+ * @returns {Partial<SelectComponents<Option, boolean, GroupBase<Option>>> | undefined} components object to override react-select default components
642
587
  */
643
- const validateEmailAddress = (email) => {
644
- if (!email) {
645
- return false;
646
- }
647
- return EMAIL_REGEX.test(email);
588
+ const useCustomComponents = (componentsProps, disabled, menuIsOpen, refMenuIsEnabled, dataTestId, maxSelectedDisplayCount, dropdownIcon) => {
589
+ // perhaps it should not be wrap in memo (causing some issues with opening and closing on mobiles)
590
+ const customComponents = React.useMemo(() => {
591
+ return Object.assign({ ValueContainer: props => {
592
+ if (props.isMulti && Array.isArray(props.children) && props.children.length > 0) {
593
+ const PLACEHOLDER_KEY = "placeholder";
594
+ const key = props && props.children && props.children[0] ? props.children[0].key : "";
595
+ const values = props && props.children ? props.children[0] : [];
596
+ const tags = key === PLACEHOLDER_KEY ? [] : values;
597
+ const searchInput = props && props.children && props.children[1];
598
+ return (jsx(components.ValueContainer, Object.assign({}, props, { isDisabled: props.selectProps.isDisabled, children: maxSelectedDisplayCount === undefined ? (jsx(TagsContainer, { width: "100%", items: tags
599
+ ? tags.map(({ props: tagProps }) => {
600
+ return {
601
+ text: tagProps.children,
602
+ onClick: disabled
603
+ ? undefined
604
+ : (e) => {
605
+ var _a, _b;
606
+ refMenuIsEnabled.current = false;
607
+ ((_a = tagProps === null || tagProps === void 0 ? void 0 : tagProps.removeProps) === null || _a === void 0 ? void 0 : _a.onClick) && ((_b = tagProps === null || tagProps === void 0 ? void 0 : tagProps.removeProps) === null || _b === void 0 ? void 0 : _b.onClick(e));
608
+ },
609
+ disabled: disabled,
610
+ };
611
+ })
612
+ : [], postFix: searchInput, disabled: disabled })) : (jsxs(Fragment, { children: [tags &&
613
+ tags.slice(0, maxSelectedDisplayCount).map(({ props: tagProps }) => {
614
+ var _a, _b;
615
+ return (jsx(Tag, { className: "inline-flex shrink-0", onClose: e => {
616
+ var _a, _b;
617
+ e.stopPropagation();
618
+ refMenuIsEnabled.current = false;
619
+ ((_a = tagProps === null || tagProps === void 0 ? void 0 : tagProps.removeProps) === null || _a === void 0 ? void 0 : _a.onClick) && ((_b = tagProps === null || tagProps === void 0 ? void 0 : tagProps.removeProps) === null || _b === void 0 ? void 0 : _b.onClick(e));
620
+ }, color: disabled ? "unknown" : "primary", dataTestId: tagProps.children ? `${(_a = tagProps.children) === null || _a === void 0 ? void 0 : _a.toString()}-tag` : undefined, children: tagProps.children }, (_b = tagProps.children) === null || _b === void 0 ? void 0 : _b.toString()));
621
+ }), tags && tags.length > maxSelectedDisplayCount && (jsxs(Tag, { color: "neutral", dataTestId: "counter-tag", children: ["+", tags.length - maxSelectedDisplayCount] })), searchInput] })) })));
622
+ }
623
+ return (jsx(components.ValueContainer, Object.assign({}, props, { isDisabled: props.selectProps.isDisabled, children: props.children })));
624
+ }, LoadingIndicator: props => {
625
+ return jsx(Spinner, { size: "small", className: "mt-1.5 mr-1" });
626
+ }, DropdownIndicator: props => {
627
+ const icon = props.selectProps.menuIsOpen ? (jsx(Icon, { name: "ChevronUp", size: "medium" })) : (jsx(Icon, { name: "ChevronDown", size: "medium" }));
628
+ return props.selectProps.isLoading ? null : (jsx(components.DropdownIndicator, Object.assign({}, props, { children: jsx("div", { className: cvaSelectIcon(), children: dropdownIcon ? dropdownIcon : icon }) })));
629
+ }, IndicatorSeparator: () => null, ClearIndicator: props => {
630
+ if (disabled) {
631
+ return null;
632
+ }
633
+ return (jsx(components.ClearIndicator, Object.assign({}, props, { children: jsx("div", { className: cvaSelectXIcon(), onClick: props.clearValue, "data-testid": dataTestId && `${dataTestId}-XMarkIcon`, children: jsx(Icon, { name: "XCircle", size: "medium" }) }) })));
634
+ }, Control: props => {
635
+ return jsx(components.Control, Object.assign({}, props, { className: props.isDisabled ? "bg-slate-100" : "" }));
636
+ }, SingleValue: props => {
637
+ return (jsx(components.SingleValue, Object.assign({}, props, { className: props.isDisabled ? "text-slate-700" : "", children: jsx("div", { "data-testid": dataTestId + "-singleValue", children: props.children }) })));
638
+ }, Menu: props => {
639
+ return (jsx(components.Menu, Object.assign({}, props, { className: cvaSelectMenuList({ menuIsOpen: props.selectProps.menuIsOpen }) })));
640
+ }, MenuList: props => {
641
+ return (jsx(components.MenuList, Object.assign({}, props, { innerProps: Object.assign(Object.assign({}, props.innerProps), { onScroll: e => {
642
+ const listEl = e.currentTarget;
643
+ if (listEl.scrollTop + listEl.clientHeight >= listEl.scrollHeight) {
644
+ props.selectProps.onMenuScrollToBottom && props.selectProps.onMenuScrollToBottom(new TouchEvent(""));
645
+ }
646
+ } }), children: props.children })));
647
+ }, Option: props => {
648
+ const componentProps = {
649
+ label: props.label,
650
+ focused: props.isFocused,
651
+ selected: props.isSelected,
652
+ onClick: props.innerProps.onClick,
653
+ };
654
+ return (jsx(components.Option, Object.assign({}, props, { innerProps: Object.assign(Object.assign({}, props.innerProps), { role: "option", onClick: () => { } }), children: props.isMulti ? (jsx(MultiSelectMenuItem, Object.assign({}, componentProps, { disabled: disabled, dataTestId: typeof props.label === "string" ? props.label : undefined }))) : (jsx(SingleSelectMenuItem, Object.assign({}, componentProps, { disabled: disabled || props.isDisabled, dataTestId: typeof props.label === "string" ? props.label : undefined }))) })));
655
+ } }, componentsProps);
656
+ // eslint-disable-next-line react-hooks/exhaustive-deps
657
+ }, [componentsProps, disabled, maxSelectedDisplayCount]); // do not add dropdownIcon (it will cause issue with opening/closing list for selects with custom icon)
658
+ return customComponents;
648
659
  };
649
660
 
650
661
  /**
651
- * A Email Input component is used for input of the type Email.
652
- *
653
- * It has an ActionButton which sends an email to the specified address using "mailto:" link.
654
- *
655
- * Extends props of BaseInput.
656
- *
657
- * A reference to the input element is provided as the `ref` prop.
658
- * For specific input types make sure to use the corresponding input component.
662
+ * @template IsMulti
663
+ * @template Group
664
+ * @param {React.RefObject<HTMLDivElement>} refContainer react ref to container element
665
+ * @param {React.RefObject<HTMLDivElement>} refPrefix react ref to prefix element
666
+ * @param {number | undefined} maxSelectedDisplayCount a number of max display count
667
+ * @param {StylesConfig<Option, IsMulti, Group> | undefined} styles a optional object to override styles of react-select
668
+ * @returns {StylesConfig<Option, boolean>} styles to override in select
659
669
  */
660
- const EmailInput = React.forwardRef((_a, ref) => {
661
- var { fieldSize = "medium", disabled = false, dataTestId, isInvalid = false } = _a, rest = __rest(_a, ["fieldSize", "disabled", "dataTestId", "isInvalid"]);
662
- const [email, setEmail] = React.useState("");
663
- const sendEmail = () => {
664
- return window.open(`mailto:${email}`);
665
- };
666
- const renderAsInvalid = (email && !validateEmailAddress(email)) || isInvalid;
667
- return (jsx(BaseInput, Object.assign({ dataTestId: dataTestId, ref: ref, type: "email", placeholder: rest.placeholder || "mail@example.com", onChange: e => setEmail(e.target.value), isInvalid: renderAsInvalid, actions: jsx(ActionButton, { disabled: disabled, value: email, type: "EMAIL", onClick: sendEmail, dataTestId: dataTestId && `${dataTestId}-emailIcon`, iconSize: fieldSize }) }, rest)));
668
- });
670
+ const useCustomStyles = (refContainer, refPrefix, maxSelectedDisplayCount, styles) => {
671
+ const customStyles = React.useMemo(() => {
672
+ return Object.assign({ control: base => {
673
+ return Object.assign(Object.assign({}, base), { border: "0", boxShadow: "0", "&:hover": {
674
+ border: "0",
675
+ }, marginRight: "2px", backgroundColor: "" });
676
+ }, singleValue: base => (Object.assign({}, base)), multiValue: base => (Object.assign({}, base)), multiValueLabel: base => (Object.assign({}, base)), indicatorsContainer: base => (Object.assign({}, base)), indicatorSeparator: () => ({
677
+ width: "0px",
678
+ }), menu: base => {
679
+ return Object.assign(Object.assign({}, base), { width: "100%", marginTop: "4px", marginBottom: "18px", transition: "all 1s ease-in-out" });
680
+ }, input: base => (Object.assign(Object.assign({}, base), { marginLeft: "0px" })), placeholder: base => (Object.assign({}, base)), option: () => ({}), menuPortal: base => (Object.assign(Object.assign({}, base), { width: (refContainer === null || refContainer === void 0 ? void 0 : refContainer.current) ? `${refContainer.current.clientWidth}px` : base.width, transform: (refPrefix === null || refPrefix === void 0 ? void 0 : refPrefix.current) ? `translate(-${refPrefix.current.clientWidth + 2}px)` : "translate(-2px)", backgroundColor: "#ffffff", borderRadius: "var(--border-radius-lg)", zIndex: 20, borderColor: "rgb(var(--color-slate-300))", boxShadow: "var(--tw-ring-inset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)" })), menuList: base => {
681
+ return Object.assign(Object.assign({}, base), { position: "relative", padding: "var(--spacing-1)", display: "grid", gap: "var(--spacing-1)", width: "100%", borderRadius: "0px", boxShadow: "none", paddingTop: "0px" });
682
+ }, valueContainer: base => {
683
+ return Object.assign(Object.assign({}, base), { flexWrap: maxSelectedDisplayCount !== undefined ? "wrap" : "nowrap", gap: "0.25rem" });
684
+ }, container: base => (Object.assign(Object.assign({}, base), { border: "none", width: "calc(100% - 2px)", paddingLeft: "2px" })), dropdownIndicator: base => (Object.assign(Object.assign({}, base), { padding: "0px" })), clearIndicator: base => {
685
+ return Object.assign(Object.assign({}, base), { padding: "0px" });
686
+ } }, styles);
687
+ // eslint-disable-next-line react-hooks/exhaustive-deps
688
+ }, [refContainer, refPrefix]);
689
+ return { customStyles };
690
+ };
669
691
 
670
692
  /**
671
- * The EmailField component is used to enter email.
672
- * EmailField validates that user enters a valid email address.
693
+ * A hook used by selects to share the common code
673
694
  *
695
+ * @param {SelectProps} props - The props for the Select component
696
+ * @returns {IUseSelect} Select component
674
697
  */
675
- const EmailField = forwardRef((_a, ref) => {
676
- var { label, id, tip, helpText, errorMessage, helpAddon, className, defaultValue, dataTestId, value, onChange, isInvalid = false } = _a, rest = __rest(_a, ["label", "id", "tip", "helpText", "errorMessage", "helpAddon", "className", "defaultValue", "dataTestId", "value", "onChange", "isInvalid"]);
677
- const htmlForId = id ? id : "emailField-" + v4();
678
- // Type guard to check if value is a string
679
- function isString(inputValue) {
680
- return typeof inputValue === "string";
681
- }
682
- const renderAsInvalid = !!errorMessage || (value && isString(value) && !validateEmailAddress(value)) || isInvalid;
683
- const handleChange = useCallback((event) => {
684
- if (onChange) {
685
- onChange(event);
698
+ const useSelect = (_a) => {
699
+ var _b;
700
+ var { id, className, dataTestId = "select", prefix, async, dropdownIcon, maxMenuHeight = 200, label, hasError, disabled, isMulti, components, value, options, onChange, isLoading, classNamePrefix = "", onMenuOpen, onMenuClose, maxSelectedDisplayCount = undefined, isClearable = false, isSearchable = true, onMenuScrollToBottom, styles, filterOption, onInputChange } = _a, props = __rest(_a, ["id", "className", "dataTestId", "prefix", "async", "dropdownIcon", "maxMenuHeight", "label", "hasError", "disabled", "isMulti", "components", "value", "options", "onChange", "isLoading", "classNamePrefix", "onMenuOpen", "onMenuClose", "maxSelectedDisplayCount", "isClearable", "isSearchable", "onMenuScrollToBottom", "styles", "filterOption", "onInputChange"]);
701
+ const refContainer = React__default.useRef(null);
702
+ const refPrefix = React__default.useRef(null);
703
+ const { customStyles } = useCustomStyles(refContainer, refPrefix, maxSelectedDisplayCount, styles);
704
+ const [menuIsOpen, setMenuIsOpen] = React__default.useState((_b = props.menuIsOpen) !== null && _b !== void 0 ? _b : false);
705
+ const refMenuIsEnabled = React__default.useRef(true);
706
+ const customComponents = useCustomComponents(components, disabled || false, menuIsOpen, refMenuIsEnabled, dataTestId, maxSelectedDisplayCount, dropdownIcon);
707
+ const menuPlacement = "auto";
708
+ const openMenuHandler = () => __awaiter(void 0, void 0, void 0, function* () {
709
+ onMenuOpen && onMenuOpen();
710
+ if (refMenuIsEnabled.current) {
711
+ setMenuIsOpen(true);
686
712
  }
687
- }, [onChange]);
688
- return (jsx(FormGroup, { htmlFor: htmlForId, label: label, tip: tip, isInvalid: renderAsInvalid, helpText: (renderAsInvalid && errorMessage) || helpText, helpAddon: helpAddon, disabled: rest.disabled, dataTestId: dataTestId && `${dataTestId}-FormGroup`, children: jsx(EmailInput, Object.assign({ id: htmlForId, "aria-labelledby": htmlForId + "-label", ref: ref, defaultValue: defaultValue, value: value, onChange: handleChange, isInvalid: renderAsInvalid, disabled: renderAsInvalid }, rest, { className: className, dataTestId: dataTestId })) }));
689
- });
713
+ else {
714
+ refMenuIsEnabled.current = true;
715
+ }
716
+ });
717
+ const closeMenuHandler = () => {
718
+ setMenuIsOpen(false);
719
+ onMenuClose && onMenuClose();
720
+ };
721
+ const orderedOptions = React__default.useMemo(() => {
722
+ return disabled
723
+ ? getOrderedOptions(options, value).map(option => {
724
+ return Object.assign(Object.assign({}, option), { disabled: true });
725
+ })
726
+ : getOrderedOptions(options, value);
727
+ }, [options, value, disabled]);
728
+ return {
729
+ refContainer,
730
+ refPrefix,
731
+ customStyles,
732
+ menuIsOpen,
733
+ customComponents,
734
+ menuPlacement,
735
+ openMenuHandler,
736
+ closeMenuHandler,
737
+ orderedOptions,
738
+ };
739
+ };
690
740
 
691
741
  /**
692
- * A thin wrapper around the `BaseInput` component for number input fields.
742
+ * The CreatableSelect component.
693
743
  *
694
- * NOTE: If shown with a label, please use the `NumberField` component instead.
744
+ * @param {CreateableSelectProps} props - The props for the CreatableSelect component
745
+ * @returns {JSX.Element} CreatableSelect component
695
746
  */
696
- const NumberInput = forwardRef((props, ref) => {
697
- return jsx(BaseInput, Object.assign({ ref: ref, type: "number" }, props, { value: props.value }));
698
- });
747
+ const CreatableSelect = (props) => {
748
+ const { id, dataTestId = "select", async, maxMenuHeight = 200, label, error, disabled, isMulti, value, hideLabel, description, info, options, onChange, isLoading, classNamePrefix = "", filterOption, prefix, onInputChange, placeholder, isClearable, } = props;
749
+ const hasError = !!error;
750
+ const { refPrefix, customStyles, menuIsOpen, customComponents, menuPlacement, openMenuHandler, closeMenuHandler, orderedOptions, } = useSelect(props);
751
+ const selectProps = {
752
+ value,
753
+ menuPlacement,
754
+ maxMenuHeight,
755
+ onChange,
756
+ "aria-label": label,
757
+ "data-testid": dataTestId,
758
+ components: customComponents,
759
+ styles: customStyles,
760
+ tabSelectsValue: false,
761
+ blurInputOnSelect: !isMulti,
762
+ menuPortalTarget: document.body,
763
+ isSearchable: disabled ? false : true,
764
+ menuShouldBlockScroll: true,
765
+ menuShouldScrollIntoView: true,
766
+ openMenuOnFocus: disabled ? false : true,
767
+ menuIsOpen: disabled ? false : menuIsOpen,
768
+ openMenuOnClick: true,
769
+ closeMenuOnSelect: false,
770
+ isMulti: isMulti,
771
+ classNamePrefix,
772
+ isLoading,
773
+ onInputChange,
774
+ placeholder,
775
+ isClearable,
776
+ id,
777
+ };
778
+ return (jsx(SelectContainer, { label: label, hideLabel: hideLabel, id: id, hasError: hasError, error: error, description: description, info: info, dataTestId: dataTestId, isDisabled: disabled, children: jsxs(Fragment, { children: [prefix !== undefined && (jsx("div", { className: cvaSelectPrefix(), "data-testid": dataTestId && `${dataTestId}-prefix`, ref: refPrefix, children: prefix })), async ? (jsx(ReactAsyncCreatableSelect, Object.assign({ "aria-label": label, maxMenuHeight: maxMenuHeight, "data-testid": dataTestId, components: customComponents }, props, { onMenuOpen: () => {
779
+ !disabled && openMenuHandler();
780
+ }, onMenuClose: () => {
781
+ closeMenuHandler();
782
+ } }))) : (jsx(ReactCreatableSelect, Object.assign({}, selectProps, { options: filterOption ? orderedOptions : options, isMulti: isMulti, onMenuOpen: () => {
783
+ !disabled && openMenuHandler();
784
+ }, onMenuClose: () => {
785
+ closeMenuHandler();
786
+ } })))] }) }));
787
+ };
699
788
 
700
789
  /**
701
- * The number field component is used for entering numeric values and includes controls for incrementally increasing or decreasing the value.
702
- *
703
- * _**Do use**_ the NumberField when the controls to incrementally increase or decrease makes the task easier for the user.
790
+ * Selects are input components used to choose a value from a set.
704
791
  *
705
- * _**Do not use**_ this fields for non-serialized numbers. Use TextField instead.
706
- */
707
- const NumberField = forwardRef((_a, ref) => {
708
- var { label, id, tip, helpText, errorMessage, helpAddon, isInvalid, maxLength, className, value, dataTestId } = _a, rest = __rest(_a, ["label", "id", "tip", "helpText", "errorMessage", "helpAddon", "isInvalid", "maxLength", "className", "value", "dataTestId"]);
709
- const renderAsInvalid = isInvalid === undefined ? Boolean(errorMessage) : isInvalid;
710
- const htmlForId = id ? id : "numberField-" + v4();
711
- return (jsx(FormGroup, { htmlFor: htmlForId, label: label, tip: tip, isInvalid: renderAsInvalid, helpText: (renderAsInvalid && errorMessage) || helpText, helpAddon: helpAddon, dataTestId: dataTestId && `${dataTestId}-FormGroup`, children: jsx(NumberInput, Object.assign({ id: htmlForId, "aria-labelledby": htmlForId + "-label", ref: ref, maxLength: maxLength, value: value, isInvalid: renderAsInvalid }, rest, { className: className, dataTestId: dataTestId })) }));
712
- });
713
-
714
- const cvaOptionCardLabel = cvaMerge([
715
- cvaInputBase(),
716
- "text-center",
717
- "component-baseInput-background",
718
- "aspect-square",
719
- "flex",
720
- "flex-col",
721
- "gap-2",
722
- "justify-center",
723
- "items-center",
724
- "p-responsive-space",
725
- "w-full",
726
- "peer-checked:bg-primary-50",
727
- "peer-checked:border-primary-600",
728
- "peer-checked:border-2",
729
- ], {
730
- variants: {
731
- disabled: {
732
- true: [cvaInputBaseDisabled(), "cursor-not-allowed"],
733
- false: ["cursor-pointer"],
734
- },
735
- },
736
- });
737
- const cvaOptionCardContent = cvaMerge(["flex", "flex-col", "items-center"]);
738
- const cvaOptionCardContainer = cvaMerge(["contents"]);
739
-
740
- /**
741
- * A card version of a radio button that includes an icon, headings and a description.
792
+ * @param {SelectProps} props - The props for the Select component
793
+ * @returns {JSX.Element} Select component
742
794
  */
743
- const OptionCard = forwardRef((_a, ref) => {
744
- var { icon, heading, subheading, description, disabled, id, value, className, dataTestId } = _a, rest = __rest(_a, ["icon", "heading", "subheading", "description", "disabled", "id", "value", "className", "dataTestId"]);
745
- const htmlForId = id !== null && id !== void 0 ? id : "option-card-" + v4();
746
- return (jsxs("div", { "data-testid": dataTestId, className: cvaOptionCardContainer(), children: [jsx("input", Object.assign({ ref: ref, id: htmlForId, type: "radio", value: value, className: "peer hidden" }, rest)), jsxs("label", { htmlFor: htmlForId, className: cvaOptionCardLabel({ className, disabled }), children: [disabled && icon && cloneElement(icon, { className: `${icon.props.className} text-secondary-400` }), !disabled && icon, heading && (jsx(Heading, { variant: "secondary", subtle: disabled, children: heading })), (subheading || description) && (jsxs("div", { className: cvaOptionCardContent(), children: [subheading && (jsx(Text, { type: "span", weight: "thick", align: "center", subtle: disabled, children: subheading })), description && (jsx(Text, { type: "span", subtle: true, align: "center", children: description }))] }))] })] }));
747
- });
795
+ const Select = (props) => {
796
+ const { className } = props, propsNoClassName = __rest(props, ["className"]);
797
+ const { id, dataTestId = "select", prefix, async, maxMenuHeight = 200, label, hasError, disabled, isMulti, value, options, onChange, isLoading, classNamePrefix = dataTestId ? dataTestId : "select", onMenuScrollToBottom, filterOption, onInputChange, isSearchable, isClearable = false, readOnly, } = props;
798
+ const { refContainer, refPrefix, customStyles, menuIsOpen, customComponents, menuPlacement, openMenuHandler, closeMenuHandler, orderedOptions, } = useSelect(props);
799
+ const selectProps = {
800
+ value,
801
+ menuPlacement,
802
+ maxMenuHeight,
803
+ onChange,
804
+ "aria-label": label,
805
+ "data-testid": dataTestId,
806
+ components: customComponents,
807
+ styles: customStyles,
808
+ tabSelectsValue: false,
809
+ blurInputOnSelect: !isMulti,
810
+ menuPortalTarget: props.menuPortalTarget || document.body,
811
+ isSearchable: disabled || readOnly ? false : isSearchable,
812
+ menuShouldBlockScroll: true,
813
+ menuShouldScrollIntoView: true,
814
+ openMenuOnFocus: true,
815
+ menuIsOpen: !readOnly ? menuIsOpen : false,
816
+ openMenuOnClick: true,
817
+ closeMenuOnSelect: false,
818
+ isMulti,
819
+ classNamePrefix,
820
+ isLoading,
821
+ isClearable,
822
+ id,
823
+ onMenuScrollToBottom,
824
+ onInputChange,
825
+ };
826
+ return (jsxs("div", { ref: refContainer, className: cvaSelect({ invalid: hasError, disabled: disabled || readOnly, className }), "data-testid": dataTestId, children: [prefix !== undefined && (jsx("div", { className: cvaSelectPrefix(), "data-testid": dataTestId && `${dataTestId}-prefix`, ref: refPrefix, children: prefix })), async ? (jsx(ReactAsyncSelect, Object.assign({}, propsNoClassName, selectProps, async, { onMenuOpen: openMenuHandler, onMenuClose: closeMenuHandler }))) : (jsx(ReactSelect, Object.assign({}, propsNoClassName, selectProps, { options: filterOption ? orderedOptions : options, isMulti: isMulti, hideSelectedOptions: false, onMenuOpen: openMenuHandler, onMenuClose: closeMenuHandler })))] }));
827
+ };
828
+ Select.displayName = "Select";
748
829
 
749
830
  /**
750
- * A thin wrapper around the `BaseInput` component for password input fields.
831
+ * The SelectField component is a Select component wrapped in the FromGroup component.
751
832
  *
752
- * NOTE: If shown with a label, please use the `PasswordField` component instead.
753
- */
754
- const PasswordInput = forwardRef((props, ref) => (jsx(BaseInput, Object.assign({ ref: ref, type: props.obfuscate ? "text" : "password" }, props))));
755
-
756
- /**
757
- * Password fields enter a password or other confidential information. Characters are masked as they are typed.
833
+ * This means that it can easily be added to any form alongside other Field components.
758
834
  *
759
- * _**Do use** when the user has to input a password or something that needs to be obfuscated_
835
+ * This is done to make the field compatible with the React-hook-form library.
760
836
  *
761
- * _**Do not use** to confirm user actions, such as deleting. Use a checkbox for such flows._
837
+ * @param {SelectFieldProps} props - The props for the SelectField component
838
+ * @returns {JSX.Element} SelectField component
762
839
  */
763
- const PasswordField = forwardRef((_a, ref) => {
764
- var { id, label, tip, helpText, helpAddon, errorMessage, isInvalid, maxLength, onChange, className, value, dataTestId } = _a, rest = __rest(_a, ["id", "label", "tip", "helpText", "helpAddon", "errorMessage", "isInvalid", "maxLength", "onChange", "className", "value", "dataTestId"]);
840
+ const SelectField = forwardRef((_a, ref) => {
841
+ var { className, dataTestId, helpText, helpAddon, tip, label, disabled, isInvalid, errorMessage, name, onBlur, options, value, defaultValue, id, onChange } = _a; __rest(_a, ["className", "dataTestId", "helpText", "helpAddon", "tip", "label", "disabled", "isInvalid", "errorMessage", "name", "onBlur", "options", "value", "defaultValue", "id", "onChange"]);
842
+ const [innerValue, setInnerValue] = useState(value || defaultValue);
765
843
  const renderAsInvalid = isInvalid === undefined ? Boolean(errorMessage) : isInvalid;
766
- const htmlFor = id ? id : "passwordField-" + v4();
767
- const [showPassword, setShowPassword] = useState(false);
768
- const handleChange = useCallback((event) => {
844
+ const htmlFor = useMemo(() => (id ? id : "selectField-" + v4()), [id]);
845
+ const innerRef = useRef(null);
846
+ useImperativeHandle(ref, () => innerRef.current, []);
847
+ const handleChange = useCallback((newValue) => {
848
+ setInnerValue(newValue === null || newValue === void 0 ? void 0 : newValue.value);
849
+ }, []);
850
+ useEffect(() => {
851
+ var _a;
852
+ if (!innerValue) {
853
+ return;
854
+ }
855
+ const event = new Event("change");
856
+ (_a = innerRef.current) === null || _a === void 0 ? void 0 : _a.dispatchEvent(event);
857
+ // The event as unknown as ChangeEvent<HTMLInputElement> assertion is needed because
858
+ // the event's type is inferred as Event, which doesn't have a target property, whereas ChangeEvent does.
769
859
  onChange === null || onChange === void 0 ? void 0 : onChange(event);
770
- }, [onChange]);
771
- return (jsx(FormGroup, { htmlFor: htmlFor, label: label, tip: tip, isInvalid: renderAsInvalid, helpText: (renderAsInvalid && errorMessage) || helpText, helpAddon: helpAddon, dataTestId: dataTestId && `${dataTestId}-FormGroup`, children: jsx(PasswordInput, Object.assign({}, rest, { obfuscate: showPassword, disabled: rest.readOnly, id: htmlFor, "aria-labelledby": htmlFor + "-label", ref: ref, maxLength: maxLength, value: value, isInvalid: renderAsInvalid, className: className, onChange: handleChange, dataTestId: dataTestId, actions: jsx(Icon, { className: "absolute top-0 bottom-0 right-2 h-full content-center", name: showPassword ? "EyeSlash" : "Eye", size: "small", color: "neutral", onClick: () => setShowPassword(prevState => !prevState) }) })) }));
860
+ }, [onChange, innerValue]);
861
+ const selectedOption = useMemo(() => options.find(option => option.value === value), [options, value]);
862
+ const defaultSelectedOption = options.find(option => option.value === defaultValue);
863
+ return (jsxs(FormGroup, { isInvalid: renderAsInvalid,
864
+ htmlFor,
865
+ className,
866
+ dataTestId,
867
+ helpText,
868
+ helpAddon,
869
+ tip,
870
+ label,
871
+ disabled, children: [jsx("select", { onChange, ref: innerRef, name, value: innerValue, hidden: true, children: options.map(option => {
872
+ return (jsx("option", { value: option.value, children: option.label }, option.value));
873
+ }) }), jsx(Select, { id,
874
+ isDisabled: disabled,
875
+ onBlur,
876
+ options,
877
+ onChange: handleChange,
878
+ value: selectedOption,
879
+ defaultValue: defaultSelectedOption })] }));
772
880
  });
773
881
 
774
- /**
775
- * @param phoneNumber - a phone number as a string
776
- * @returns {boolean} true if the phone number starts with a plus sign
777
- * @example checkIfPhoneNumberHasPlus("123456789") // false
778
- * checkIfPhoneNumberHasPlus("+123456789") // true
779
- */
780
- const checkIfPhoneNumberHasPlus = (phoneNumber) => {
781
- return phoneNumber.startsWith("+");
782
- };
783
- /**
784
- * @param phoneNumber - a phone number as a string
785
- * @returns {string|number} the phone number with a plus sign in front of it
786
- * @example getPhoneNumberWithPlus("123456789") // "+123456789"
787
- */
788
- const getPhoneNumberWithPlus = (phoneNumber) => {
789
- const stringPhoneNumber = phoneNumber.toString();
790
- return checkIfPhoneNumberHasPlus(stringPhoneNumber) ? stringPhoneNumber : `+${stringPhoneNumber}`;
791
- };
792
- /**
793
- * Generates a flag emoji based on the given country code.
794
- *
795
- * @param {string} countryCode - The two-letter country code (ISO 3166-1 alpha-2).
796
- * @returns {string} - The corresponding flag emoji for the given country code.
797
- * @example getPhoneNumberWithPlus("DK") // "🇩🇰"
798
- */
799
- const countryCodeToFlagEmoji = (countryCode) => {
800
- let code = countryCode;
801
- if (countryCode.startsWith("+")) {
802
- code = getCountryAbbreviation(countryCode.substring(1));
803
- }
804
- else if (!isNaN(Number(countryCode))) {
805
- code = getCountryAbbreviation(countryCode);
806
- }
807
- return isSupportedCountry(code.toUpperCase())
808
- ? code.toUpperCase().replace(/./g, char => String.fromCodePoint(127397 + char.charCodeAt(0)))
809
- : "";
810
- };
811
- /**
812
- * Retrieves the ISO 3166-1 alpha-2 country code associated with a given international calling code.
813
- *
814
- * @param {string} callCode - The international calling code for a country.
815
- * @returns {string} The abbreviation for a country or an empty string if no matching country is found.
816
- * @example getCountryAbbreviation("45") // "DK"
817
- * @example getCountryAbbreviation("+45") // "DK"
818
- */
819
- const getCountryAbbreviation = (callCode) => {
820
- let code = callCode;
821
- if (callCode.startsWith("+")) {
822
- code = callCode.substring(1);
823
- }
824
- return getCountries().find(c => getCountryCallingCode(c) === code) || "";
825
- };
882
+ const cvaActionButton = cvaMerge(["drop-shadow-none", "rounded-md"], {
883
+ variants: {
884
+ size: {
885
+ small: ["w-6", "h-6"],
886
+ medium: ["w-8", "h-8"],
887
+ },
888
+ },
889
+ defaultVariants: {
890
+ size: "medium",
891
+ },
892
+ });
893
+ const cvaActionContainer = cvaMerge(["flex", "items-center", "m-1"]);
826
894
 
827
895
  /**
828
- * A custom hook for managing phone number input state and validation.
896
+ * The ActionButton component is a wrapper over IconButton to perform an action when the onClick event is triggered.
829
897
  *
830
- * @property {Function} getPhoneNumber - A function for get formatted phone number with country code and plus sign
898
+ * @param {ActionButtonProps} props - The props for the ActionButton component
899
+ * @returns {JSX.Element} ActionButton component
831
900
  */
832
- const usePhoneInput = () => {
833
- const getPhoneNumber = ({ country, phone }) => {
834
- if (country) {
835
- return getPhoneNumberWithPlus(`${country}${phone || ""}`);
901
+ const ActionButton = ({ type, value, dataTestId, iconSize, disabled, className, }) => {
902
+ const getIconName = () => {
903
+ switch (type) {
904
+ case "PHONE_NUMBER":
905
+ return "PhoneArrowUpRight";
906
+ case "WEB_ADDRESS":
907
+ return "ArrowTopRightOnSquare";
908
+ case "EMAIL":
909
+ default:
910
+ return "Envelope";
911
+ }
912
+ };
913
+ const ButtonAction = () => {
914
+ switch (type) {
915
+ case "EMAIL":
916
+ return window.open(`mailto:${value}`);
917
+ case "WEB_ADDRESS":
918
+ if (value) {
919
+ return window.open(value, "_blank", "noopener,noreferrer");
920
+ }
921
+ return null;
922
+ case "PHONE_NUMBER":
923
+ default:
924
+ return window.open(`tel:${value}`);
836
925
  }
837
- return phone || "";
838
- };
839
- return {
840
- getPhoneNumber,
841
926
  };
927
+ return (jsx("div", { className: cvaActionContainer({ className }), children: jsx(IconButton, { disabled: disabled, dataTestId: dataTestId || "testIconButtonId", size: "small", color: "tertiary", icon: jsx(Icon, { size: "small", name: getIconName() }), onClick: ButtonAction, className: cvaActionButton({ size: iconSize }) }) }));
842
928
  };
843
929
 
844
- const cvaSelect = cvaMerge([
845
- "relative",
846
- "flex",
847
- "shadow-sm",
848
- "rounded-lg",
849
- "border",
930
+ const cvaInputBase = cvaMerge([
931
+ "component-baseInput-shadow",
932
+ "component-baseInput-border",
933
+ "component-baseInput-background",
850
934
  "border-slate-300",
851
935
  "focus-within:ring-2",
852
936
  "focus-within:ring-inset",
853
- "focus-within:ring-primary-600",
937
+ "focus-within:ring-primary-500",
854
938
  "focus-within:border-slate-400",
855
939
  "hover:border-slate-400",
856
940
  "hover:bg-slate-50",
857
- "bg-white",
858
941
  "transition",
859
- ], {
942
+ ]);
943
+ const cvaInputBaseDisabled = cvaMerge(["bg-slate-100", "hover:bg-slate-100", "hover:border-slate-300"]);
944
+ const cvaInputBaseInvalid = cvaMerge(["border-danger-600", "focus-within:ring-danger-600"]);
945
+ const cvaInput = cvaMerge(["relative", "flex", "h-10", cvaInputBase()], {
860
946
  variants: {
947
+ size: {
948
+ small: ["h-8"],
949
+ medium: ["h-10"],
950
+ },
951
+ disabled: {
952
+ true: cvaInputBaseDisabled(),
953
+ false: "",
954
+ },
861
955
  invalid: {
862
- true: "border-red-600 text-red-600 focus-within:ring-red-600 hover:border-red-600",
956
+ true: cvaInputBaseInvalid(),
863
957
  false: "",
864
958
  },
959
+ },
960
+ });
961
+ const cvaInputField = cvaMerge([
962
+ "w-full",
963
+ "px-3",
964
+ "border-0",
965
+ "bg-transparent",
966
+ "text-base",
967
+ "text-slate-900",
968
+ "placeholder-slate-400",
969
+ "focus-visible:outline-none",
970
+ ], {
971
+ variants: {
972
+ size: {
973
+ small: ["py-1"],
974
+ medium: ["py-2"],
975
+ },
865
976
  disabled: {
866
- true: "!bg-slate-100",
977
+ true: ["text-slate-700"],
867
978
  false: "",
868
979
  },
869
980
  },
870
- defaultVariants: {
871
- invalid: false,
872
- disabled: false,
873
- },
874
981
  });
875
- const cvaSelectIcon = cvaMerge("mr-2 flex cursor-pointer items-center justify-center text-slate-400 hover:text-slate-500");
876
- const cvaSelectPrefix = cvaMerge(["flex", "justify-center", "items-center", "text-slate-400", "pl-2"]);
877
- const cvaSelectXIcon = cvaMerge([
878
- "mr-2 flex cursor-pointer items-center justify-center text-slate-400 hover:text-slate-500",
879
- "ml-1",
982
+ const cvaInputAddon = cvaMerge([
983
+ "flex",
984
+ "justify-center",
985
+ "items-center",
986
+ "px-3",
987
+ "border-slate-300",
988
+ "text-slate-500",
989
+ "bg-slate-50",
880
990
  ]);
881
- const cvaSelectMenuList = cvaMerge(["min-w-min", "shadow-md", "rounded-lg", "z-20", "bg-white", "p-1", "border", "border-slate-300", "gap-1", "grid"], {
991
+ const cvaInputAddonBefore = cvaMerge([cvaInputAddon(), "border-r", "rounded-l-lg"]);
992
+ const cvaInputAddonAfter = cvaMerge([cvaInputAddon(), "border-l", "rounded-r-lg"]);
993
+ const cvaInputPrefix = cvaMerge([
994
+ "flex",
995
+ "justify-center",
996
+ "items-center",
997
+ "text-slate-400",
998
+ "hover:component-search-prefix",
999
+ "component-baseInput-prefix",
1000
+ "component-search-borderless",
1001
+ "px-3",
1002
+ ], {
882
1003
  variants: {
883
- menuIsOpen: {
884
- true: "animate-fade-in-fast",
885
- false: "animate-fade-out-fast",
1004
+ disabled: {
1005
+ true: ["text-slate-500"],
1006
+ false: "",
886
1007
  },
887
1008
  },
888
1009
  });
889
- const cvaSelectDynamicTagContainer = cvaMerge(["h-full", "flex", "gap-1", "items-center"], {
1010
+ const cvaInputSuffix = cvaMerge(["flex", "justify-center", "items-center", "text-slate-400", "px-3"], {
890
1011
  variants: {
891
- visible: { true: "visible", false: "invisible" },
1012
+ disabled: {
1013
+ true: ["text-slate-500"],
1014
+ false: "",
1015
+ },
892
1016
  },
893
1017
  });
894
- const cvaSelectCounter = cvaMerge(["overflow-hidden", "whitespace-nowrap"]);
895
- const cvaSelectMenu = cvaMerge(["relative", "p-1", "grid", "gap-1"]);
896
1018
 
897
1019
  /**
898
- * The SelectContainer component.
1020
+ * A base input component that can be used for text inputs, password inputs, etc.
1021
+ * A reference to the input element is provided as the `ref` prop.
1022
+ * Extends props from [React.InputHTMLAttributes](https://reactjs.org/docs/dom-elements.html#input)
899
1023
  *
900
- * @param {SelectContainerProps} props - The props for the SelectContainer component
901
- * @returns {JSX.Element} SelectContainer component
1024
+ * For specific input types make sure to use the corresponding input component.
1025
+ * This is a base used by our other input components such as TextInput, NumberInput, PasswordInput, etc.
902
1026
  */
903
- const SelectContainer = ({ children, label, hideLabel, id, hasError, error, description, info, dataTestId, isDisabled, }) => {
904
- return (jsxs("label", { className: cvaSelect({ invalid: hasError, disabled: isDisabled }), "data-testid": dataTestId, children: [label && !hideLabel && jsx(Label, { htmlFor: id, children: label }), children, (hasError || info) && jsx(Text, { size: "small", children: hasError ? error : info })] }));
905
- };
1027
+ const BaseInput = React.forwardRef((_a, ref) => {
1028
+ var { className, isInvalid, dataTestId, prefix, suffix, addonBefore, addonAfter, actions, fieldSize = "medium", nonInteractive = false, type, inputClassName, addonBeforeClassName } = _a, rest = __rest(_a, ["className", "isInvalid", "dataTestId", "prefix", "suffix", "addonBefore", "addonAfter", "actions", "fieldSize", "nonInteractive", "type", "inputClassName", "addonBeforeClassName"]);
1029
+ const isReadOnlyWithPlaceholder = useMemo(() => !!rest.readOnly && !!rest.placeholder && !rest.value, [rest.readOnly, rest.placeholder, rest.value]);
1030
+ const renderAsDisabled = rest.disabled || rest.readOnly;
1031
+ return (jsxs("div", { className: cvaInput({
1032
+ disabled: renderAsDisabled,
1033
+ invalid: isInvalid,
1034
+ size: fieldSize,
1035
+ className,
1036
+ }), "data-testid": dataTestId && `${dataTestId}-container`, children: [addonBefore && addonBefore !== undefined ? (jsx("div", { className: cvaInputAddonBefore({ className: addonBeforeClassName }), "data-testid": dataTestId && `${dataTestId}-addonBefore`, children: addonBefore })) : null, prefix && addonBefore !== prefix ? (jsx("div", { className: cvaInputPrefix({
1037
+ disabled: renderAsDisabled,
1038
+ }), "data-testid": dataTestId && `${dataTestId}-prefix`, children: prefix })) : null, jsx("input", Object.assign({ ref: ref, "data-testid": dataTestId, className: cvaInputField({ size: fieldSize, disabled: renderAsDisabled, className: inputClassName }), type: isReadOnlyWithPlaceholder ? "text" : type }, rest, { readOnly: rest.readOnly || nonInteractive })), suffix && addonBefore !== suffix ? (jsx("div", { className: cvaInputSuffix({
1039
+ disabled: renderAsDisabled,
1040
+ }), "data-testid": dataTestId && `${dataTestId}-suffix`, children: suffix })) : null, actions && addonBefore !== actions ? actions : null, addonAfter && addonAfter !== undefined && addonBefore !== addonAfter ? (jsx("div", { className: cvaInputAddonAfter(), "data-testid": dataTestId && `${dataTestId}-addonAfter`, children: addonAfter })) : null] }));
1041
+ });
906
1042
 
907
1043
  /**
908
- * @param {MultiValue<Option> | SingleValue<Option>} arg option to check type
909
- * @returns {arg is MultiValue<Option> } is Multivalue
1044
+ * A wrapper around BaseInput with a pop-up day picker.
1045
+ *
1046
+ * NOTE: If shown with a label, please use the `DateField` component instead.
910
1047
  */
911
- function isMultiValue(arg) {
912
- return Array.isArray(arg);
913
- }
914
- function isGroupBase(arg) {
915
- return arg.options !== undefined;
916
- }
917
- const isSelectedOption = (option, selected) => {
918
- if (isGroupBase(option)) {
919
- return false;
920
- }
921
- return isMultiValue(selected)
922
- ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
923
- selected.some(v => v.value === option.value)
924
- : // eslint-disable-next-line @typescript-eslint/no-explicit-any
925
- option.value === (selected === null || selected === void 0 ? void 0 : selected.value);
926
- };
927
- const removeSelectedFromGroups = (group, selected) => {
928
- if (isGroupBase(group)) {
929
- return Object.assign(Object.assign({}, group), { options: group.options.filter(option => !isSelectedOption(option, selected)) });
930
- }
931
- return group;
1048
+ const DateInput = forwardRef((_a, ref) => {
1049
+ var { min, max, defaultValue, value } = _a, rest = __rest(_a, ["min", "max", "defaultValue", "value"]);
1050
+ const formatDateToInputString = (date) => date instanceof Date ? format(date, "yyyy-MM-dd") : date;
1051
+ // Chrome and Firefox need their default icon to have datepicker functionality.
1052
+ const showIcon = !/Chrome/.test(navigator.userAgent) && !/Firefox/.test(navigator.userAgent);
1053
+ return (jsx(BaseInput, Object.assign({ type: "date", value: formatDateToInputString(value), defaultValue: formatDateToInputString(defaultValue), min: formatDateToInputString(min), max: formatDateToInputString(max), ref: ref, suffix: showIcon && jsx(Icon, { dataTestId: "calendar", name: "Calendar", size: "medium", type: "solid" }) }, rest)));
1054
+ });
1055
+
1056
+ /**
1057
+ * The date field component is used for entering date values.
1058
+ *
1059
+ * _**Do use**_ the DateField for date input.
1060
+ *
1061
+ * _**Do not use**_ this fields for non-serialized dates. Use TextField instead.
1062
+ */
1063
+ const DateField = forwardRef((_a, ref) => {
1064
+ var { label, id, tip, helpText, errorMessage, helpAddon, isInvalid, className, defaultValue, dataTestId } = _a, rest = __rest(_a, ["label", "id", "tip", "helpText", "errorMessage", "helpAddon", "isInvalid", "className", "defaultValue", "dataTestId"]);
1065
+ const renderAsInvalid = isInvalid === undefined ? Boolean(errorMessage) : isInvalid;
1066
+ const htmlForId = id ? id : "dateField-" + v4();
1067
+ return (jsx(FormGroup, { htmlFor: htmlForId, label: label, tip: tip, isInvalid: renderAsInvalid, helpText: (renderAsInvalid && errorMessage) || helpText, helpAddon: helpAddon, disabled: rest.disabled, dataTestId: dataTestId && `${dataTestId}-FormGroup`, children: jsx(DateInput, Object.assign({ id: htmlForId, "aria-labelledby": htmlForId + "-label", ref: ref, defaultValue: defaultValue, isInvalid: renderAsInvalid }, rest, { className: className, dataTestId: dataTestId })) }));
1068
+ });
1069
+
1070
+ const cvaDropZone = cvaMerge([
1071
+ "flex",
1072
+ "component-baseInput-background",
1073
+ "justify-center",
1074
+ "text-slate-500",
1075
+ "rounded-sm",
1076
+ "border-2",
1077
+ "border-gray-200",
1078
+ "border-dashed",
1079
+ "hover:bg-slate-100",
1080
+ "hover:border-solid",
1081
+ "hover:border-primary-500",
1082
+ ], {
1083
+ variants: {
1084
+ size: {
1085
+ small: ["p-2"],
1086
+ medium: ["p-4"],
1087
+ large: ["p-8"],
1088
+ },
1089
+ disabled: { true: ["bg-slate-100", "hover:bg-slate-100"], false: "" },
1090
+ dragActive: { true: ["border-gray-200", "bg-slate-100"], false: "" },
1091
+ dropComplete: {
1092
+ true: ["border-solid", "border-primary-500", "bg-slate-100", "hover:border-solid", "hover:border-gray-200"],
1093
+ false: "",
1094
+ },
1095
+ invalid: { true: ["border-danger-600", "text-danger-500"], false: "" },
1096
+ },
1097
+ });
1098
+ const cvaDropZoneLabel = cvaMerge([
1099
+ "h-full",
1100
+ "pt-1",
1101
+ "pb-1",
1102
+ "gap-2",
1103
+ "items-center",
1104
+ "flex-col",
1105
+ "flex",
1106
+ "justify-center",
1107
+ ]);
1108
+
1109
+ /**
1110
+ * The Drop Zone can be used to drag and drop files
1111
+ *
1112
+ * @param {DropZoneProps} props - The props for the DropZone component
1113
+ * @returns {JSX.Element} DropZone component
1114
+ */
1115
+ const DropZone = (_a) => {
1116
+ var { className, dataTestId, filesSelected, label, size = "large", isInvalid = false, disabled = false } = _a, rest = __rest(_a, ["className", "dataTestId", "filesSelected", "label", "size", "isInvalid", "disabled"]);
1117
+ const [dragActive, setDragActive] = React__default.useState(false);
1118
+ const [fileDropped, setFileDropped] = React__default.useState(false);
1119
+ // function that handles drag enter, drag leave and drag over
1120
+ const handleDrag = (e) => {
1121
+ e.preventDefault();
1122
+ e.stopPropagation();
1123
+ if ((e.type === "dragenter" || e.type === "dragover") && !disabled) {
1124
+ setDragActive(true);
1125
+ }
1126
+ else if (e.type === "dragleave") {
1127
+ setDragActive(false);
1128
+ }
1129
+ };
1130
+ //function to handle when user clicks on dropzone to upload
1131
+ const handleChange = (e) => {
1132
+ e.preventDefault();
1133
+ e.stopPropagation();
1134
+ if (e.target.files && !disabled) {
1135
+ filesSelected(e.target.files);
1136
+ }
1137
+ };
1138
+ //funtion to handle drop
1139
+ const handleDrop = (e) => {
1140
+ e.preventDefault();
1141
+ e.stopPropagation();
1142
+ setDragActive(false);
1143
+ if (e.dataTransfer.files && e.dataTransfer.files[0] && !disabled) {
1144
+ filesSelected(e.dataTransfer.files);
1145
+ setFileDropped(true);
1146
+ }
1147
+ };
1148
+ return (jsxs("div", Object.assign({ className: cvaDropZone({ size, dropComplete: fileDropped, dragActive, disabled, invalid: isInvalid, className }), "data-testid": dataTestId, onDragEnter: handleDrag, onDragLeave: handleDrag, onDragOver: handleDrag, onDrop: handleDrop, onClick: e => {
1149
+ if (disabled) {
1150
+ e.preventDefault();
1151
+ e.stopPropagation();
1152
+ }
1153
+ } }, rest, { children: [jsx("input", { title: "Hidden File Input", type: "file", id: "input-file-upload", onChange: handleChange, className: "hidden" }), jsxs("label", { className: cvaDropZoneLabel(), htmlFor: "input-file-upload", "data-testid": dataTestId && `${dataTestId}-label`, children: [jsx(Icon, { type: "solid", name: "ArrowUpCircle", color: isInvalid ? "danger" : "neutral", style: !isInvalid ? { color: "rgb(156 163 175)" } : {} }), jsx("span", { children: label })] })] })));
932
1154
  };
1155
+
1156
+ // Doing the same check as we do on the backend
1157
+ // Using OWASP pattern from: https://owasp.org/www-community/OWASP_Validation_Regex_Repository
1158
+ const EMAIL_REGEX = /^[a-zA-Z0-9_+&*-]+(?:\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,7}$/;
933
1159
  /**
934
- * @template IsMulti
935
- * @template Group
936
- * @param {OptionsOrGroups<Option, Group> | undefined} options An array of options to select from
937
- * @param {PropsValue<Option> | undefined} value Selected values
938
- * @returns {OptionsOrGroups<Option, Group>} An array of ordered options with selected on top
1160
+ * @description Validate given email id.
1161
+ * @param email The address to validate.
1162
+ * @returns {boolean} Returns true if the email address is valid else false.
1163
+ * @example validateEmailAddress(test@gmail.com) // true
939
1164
  */
940
- const getOrderedOptions = (options, value) => {
941
- if (value && options) {
942
- const orderedValues = isMultiValue(value)
943
- ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
944
- [...value].sort((a, b) => a.label.localeCompare(b.label))
945
- : [value];
946
- const selectableOptions = options
947
- .filter(option => !isSelectedOption(option, value))
948
- .map(option => removeSelectedFromGroups(option, value));
949
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
950
- return orderedValues.concat(selectableOptions) || [];
1165
+ const validateEmailAddress = (email) => {
1166
+ if (!email) {
1167
+ return false;
951
1168
  }
952
- return options || [];
1169
+ return EMAIL_REGEX.test(email);
953
1170
  };
954
1171
 
955
1172
  /**
956
- * A single select menu item is a basic wrapper around Menu item designed to be used as a single value render in Select list
1173
+ * A Email Input component is used for input of the type Email.
957
1174
  *
958
- * @param {SelectMenuItemProps} props - The props for the SingleSelectMenuItem
959
- * @returns {JSX.Element} SingleSelectMenuItem
960
- */
961
- const SingleSelectMenuItem = ({ label, icon, onClick, selected, dataTestId, focused, disabled, }) => {
962
- return (jsx(MenuItem, { dataTestId: dataTestId, label: label, prefix: icon, onClick: onClick, selected: selected, disabled: disabled, focused: focused, suffix: selected ? jsx(Icon, { name: "Check", size: "small" }) : undefined }));
963
- };
964
- /**
965
- * A multi select menu item is a basic wrapper around Menu item designed to be used as a multi value render in Select list
1175
+ * It has an ActionButton which sends an email to the specified address using "mailto:" link.
966
1176
  *
967
- * @param {SelectMenuItemProps} props - The props for the MultiSelectMenuItem
968
- * @returns {JSX.Element} multi select menu item
1177
+ * Extends props of BaseInput.
1178
+ *
1179
+ * A reference to the input element is provided as the `ref` prop.
1180
+ * For specific input types make sure to use the corresponding input component.
969
1181
  */
970
- const MultiSelectMenuItem = ({ label, onClick, selected, dataTestId, focused, disabled, }) => {
971
- return (jsx(MenuItem, { dataTestId: dataTestId, label: label, disabled: disabled, prefix: jsx(Checkbox, { checked: selected, readOnly: false, onChange: () => null, disabled: disabled, onClick: e => {
972
- e.stopPropagation();
973
- } }), onClick: e => {
974
- e.stopPropagation();
975
- onClick && onClick(e);
976
- }, selected: selected, focused: focused }));
977
- };
1182
+ const EmailInput = React.forwardRef((_a, ref) => {
1183
+ var { fieldSize = "medium", disabled = false, dataTestId, isInvalid = false } = _a, rest = __rest(_a, ["fieldSize", "disabled", "dataTestId", "isInvalid"]);
1184
+ const [email, setEmail] = React.useState("");
1185
+ const sendEmail = () => {
1186
+ return window.open(`mailto:${email}`);
1187
+ };
1188
+ const renderAsInvalid = (email && !validateEmailAddress(email)) || isInvalid;
1189
+ return (jsx(BaseInput, Object.assign({ dataTestId: dataTestId, ref: ref, type: "email", placeholder: rest.placeholder || "mail@example.com", onChange: e => setEmail(e.target.value), isInvalid: renderAsInvalid, actions: jsx(ActionButton, { disabled: disabled, value: email, type: "EMAIL", onClick: sendEmail, dataTestId: dataTestId && `${dataTestId}-emailIcon`, iconSize: fieldSize }) }, rest)));
1190
+ });
978
1191
 
979
1192
  /**
980
- * Extended Tag component with information about its own width.
981
- * Used in the select component.
1193
+ * The EmailField component is used to enter email.
1194
+ * EmailField validates that user enters a valid email address.
982
1195
  *
983
- * @param {TagProps} props - The props for the tag component
984
- * @returns {JSX.Element} TagWithWidth component
985
1196
  */
986
- const TagWithWidth = (_a) => {
987
- var { onWidthKnown, children } = _a, rest = __rest(_a, ["onWidthKnown", "children"]);
988
- const ref = React__default.useRef(null);
989
- React__default.useLayoutEffect(() => {
990
- var _a;
991
- onWidthKnown && onWidthKnown({ width: ((_a = ref.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || 0 });
992
- }, [ref, onWidthKnown]);
993
- return (jsx(Tag, Object.assign({ ref: ref }, rest, { children: children })));
994
- };
1197
+ const EmailField = forwardRef((_a, ref) => {
1198
+ var { label, id, tip, helpText, errorMessage, helpAddon, className, defaultValue, dataTestId, value, onChange, isInvalid = false } = _a, rest = __rest(_a, ["label", "id", "tip", "helpText", "errorMessage", "helpAddon", "className", "defaultValue", "dataTestId", "value", "onChange", "isInvalid"]);
1199
+ const htmlForId = id ? id : "emailField-" + v4();
1200
+ // Type guard to check if value is a string
1201
+ function isString(inputValue) {
1202
+ return typeof inputValue === "string";
1203
+ }
1204
+ const renderAsInvalid = !!errorMessage || (value && isString(value) && !validateEmailAddress(value)) || isInvalid;
1205
+ const handleChange = useCallback((event) => {
1206
+ if (onChange) {
1207
+ onChange(event);
1208
+ }
1209
+ }, [onChange]);
1210
+ return (jsx(FormGroup, { htmlFor: htmlForId, label: label, tip: tip, isInvalid: renderAsInvalid, helpText: (renderAsInvalid && errorMessage) || helpText, helpAddon: helpAddon, disabled: rest.disabled, dataTestId: dataTestId && `${dataTestId}-FormGroup`, children: jsx(EmailInput, Object.assign({ id: htmlForId, "aria-labelledby": htmlForId + "-label", ref: ref, defaultValue: defaultValue, value: value, onChange: handleChange, isInvalid: renderAsInvalid, disabled: renderAsInvalid }, rest, { className: className, dataTestId: dataTestId })) }));
1211
+ });
995
1212
 
996
1213
  /**
997
- * TagsContainer component to display tags in limited space when children can't fit space it displays counter
1214
+ * A thin wrapper around the `BaseInput` component for number input fields.
998
1215
  *
999
- * @param {TagsContainerProps} props - The props for the TagContainer
1000
- * @returns {JSX.Element} TagsContainer
1216
+ * NOTE: If shown with a label, please use the `NumberField` component instead.
1001
1217
  */
1002
- const TagsContainer = ({ items, width = "100%", itemsGap = 5, postFix, disabled }) => {
1003
- const [isReady, setIsReady] = React__default.useState(false);
1004
- const [counterWidth, setCounterWidth] = React__default.useState(0);
1005
- const containerRef = React__default.useRef(null);
1006
- const availableWidth = React__default.useRef();
1007
- const childrenWidth = React__default.useRef([]);
1008
- const itemsCount = items.length;
1009
- React__default.useLayoutEffect(() => {
1010
- var _a;
1011
- availableWidth.current = ((_a = containerRef === null || containerRef === void 0 ? void 0 : containerRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || 0;
1012
- }, [containerRef]);
1013
- const onWidthKnownHandler = ({ width: reportedWidth }) => {
1014
- childrenWidth.current.push({ width: reportedWidth + itemsGap });
1015
- if (childrenWidth.current.length === itemsCount) {
1016
- setIsReady(true);
1017
- }
1018
- };
1019
- const requiredSpace = childrenWidth.current.reduce((previous, current) => {
1020
- return previous + current.width;
1021
- }, 0);
1022
- let counter = 0;
1023
- const availableSpace = ((availableWidth === null || availableWidth === void 0 ? void 0 : availableWidth.current) || 0) - counterWidth;
1024
- const renderedElements = items
1025
- .concat({ text: "", onClick: () => null, disabled: false }) // reserved element for a potential counter
1026
- .map((item, index) => {
1027
- const spaceNeeded = childrenWidth.current.slice(0, index + 1).reduce((previous, current) => {
1028
- return previous + current.width;
1029
- }, 0);
1030
- const isLast = index === items.length;
1031
- const counterRequired = requiredSpace > availableSpace && counter !== 0;
1032
- if (isLast && counterRequired) {
1033
- return (jsx(TagWithWidth, { onWidthKnown: ({ width: reportedWidth }) => setCounterWidth(reportedWidth), color: "white", disabled: disabled, children: jsxs("div", { "data-testid": "select-counter", className: cvaSelectCounter(), children: ["+", counter] }) }, item.text + index));
1034
- }
1035
- if (isLast) {
1036
- return null;
1037
- }
1038
- const itemCanFit = spaceNeeded <= availableSpace;
1039
- if (itemCanFit) {
1040
- return (jsx(TagWithWidth, { onWidthKnown: onWidthKnownHandler, className: "inline-flex shrink-0", onClose: e => {
1041
- e.stopPropagation();
1042
- item.onClick();
1043
- }, color: item.disabled ? "unknown" : "primary", dataTestId: `${item.text}-tag`, disabled: disabled, children: item.text }, item.text + index));
1044
- }
1045
- const fromTheItems = item.text !== "";
1046
- if (fromTheItems) {
1047
- counter = counter + 1;
1048
- }
1049
- return null;
1050
- })
1051
- .filter(element => element !== null);
1052
- return (jsxs("div", { style: {
1053
- width: `${width}`,
1054
- }, className: cvaSelectDynamicTagContainer({ visible: isReady }), ref: containerRef, children: [renderedElements, postFix] }));
1055
- };
1218
+ const NumberInput = forwardRef((props, ref) => {
1219
+ return jsx(BaseInput, Object.assign({ ref: ref, type: "number" }, props, { value: props.value }));
1220
+ });
1056
1221
 
1057
1222
  /**
1058
- * A hook to retrieve components override object.
1059
- * 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.
1223
+ * The number field component is used for entering numeric values and includes controls for incrementally increasing or decreasing the value.
1060
1224
  *
1061
- * @template IsMulti
1062
- * @template Group
1063
- * @param {Partial<SelectComponents<Option, IsMulti, Group>> | undefined} componentsProps a custom component prop that you can to override defaults
1064
- * @param {boolean} disabled decide to override disabled variant
1065
- * @param {boolean} menuIsOpen menu is open state
1066
- * @param {React.MutableRefObject<boolean>} refMenuIsEnabled a flag to block menu from open
1067
- * @param {string} dataTestId a test id
1068
- * @param {number} maxSelectedDisplayCount a number of max display count
1069
- * @param {JSX.Element} dropdownIcon an custom dropdown icon
1070
- * @returns {Partial<SelectComponents<Option, boolean, GroupBase<Option>>> | undefined} components object to override react-select default components
1225
+ * _**Do use**_ the NumberField when the controls to incrementally increase or decrease makes the task easier for the user.
1226
+ *
1227
+ * _**Do not use**_ this fields for non-serialized numbers. Use TextField instead.
1071
1228
  */
1072
- const useCustomComponents = (componentsProps, disabled, menuIsOpen, refMenuIsEnabled, dataTestId, maxSelectedDisplayCount, dropdownIcon) => {
1073
- // perhaps it should not be wrap in memo (causing some issues with opening and closing on mobiles)
1074
- const customComponents = React.useMemo(() => {
1075
- return Object.assign({ ValueContainer: props => {
1076
- if (props.isMulti && Array.isArray(props.children) && props.children.length > 0) {
1077
- const PLACEHOLDER_KEY = "placeholder";
1078
- const key = props && props.children && props.children[0] ? props.children[0].key : "";
1079
- const values = props && props.children ? props.children[0] : [];
1080
- const tags = key === PLACEHOLDER_KEY ? [] : values;
1081
- const searchInput = props && props.children && props.children[1];
1082
- return (jsx(components.ValueContainer, Object.assign({}, props, { isDisabled: props.selectProps.isDisabled, children: maxSelectedDisplayCount === undefined ? (jsx(TagsContainer, { width: "100%", items: tags
1083
- ? tags.map(({ props: tagProps }) => {
1084
- return {
1085
- text: tagProps.children,
1086
- onClick: disabled
1087
- ? undefined
1088
- : (e) => {
1089
- var _a, _b;
1090
- refMenuIsEnabled.current = false;
1091
- ((_a = tagProps === null || tagProps === void 0 ? void 0 : tagProps.removeProps) === null || _a === void 0 ? void 0 : _a.onClick) && ((_b = tagProps === null || tagProps === void 0 ? void 0 : tagProps.removeProps) === null || _b === void 0 ? void 0 : _b.onClick(e));
1092
- },
1093
- disabled: disabled,
1094
- };
1095
- })
1096
- : [], postFix: searchInput, disabled: disabled })) : (jsxs(Fragment, { children: [tags &&
1097
- tags.slice(0, maxSelectedDisplayCount).map(({ props: tagProps }) => {
1098
- var _a, _b;
1099
- return (jsx(Tag, { className: "inline-flex shrink-0", onClose: e => {
1100
- var _a, _b;
1101
- e.stopPropagation();
1102
- refMenuIsEnabled.current = false;
1103
- ((_a = tagProps === null || tagProps === void 0 ? void 0 : tagProps.removeProps) === null || _a === void 0 ? void 0 : _a.onClick) && ((_b = tagProps === null || tagProps === void 0 ? void 0 : tagProps.removeProps) === null || _b === void 0 ? void 0 : _b.onClick(e));
1104
- }, color: disabled ? "unknown" : "primary", dataTestId: tagProps.children ? `${(_a = tagProps.children) === null || _a === void 0 ? void 0 : _a.toString()}-tag` : undefined, children: tagProps.children }, (_b = tagProps.children) === null || _b === void 0 ? void 0 : _b.toString()));
1105
- }), tags && tags.length > maxSelectedDisplayCount && (jsxs(Tag, { color: "neutral", dataTestId: "counter-tag", children: ["+", tags.length - maxSelectedDisplayCount] })), searchInput] })) })));
1106
- }
1107
- return (jsx(components.ValueContainer, Object.assign({}, props, { isDisabled: props.selectProps.isDisabled, children: props.children })));
1108
- }, LoadingIndicator: props => {
1109
- return jsx(Spinner, { size: "small", className: "mt-1.5 mr-1" });
1110
- }, DropdownIndicator: props => {
1111
- const icon = props.selectProps.menuIsOpen ? (jsx(Icon, { name: "ChevronUp", size: "medium" })) : (jsx(Icon, { name: "ChevronDown", size: "medium" }));
1112
- return props.selectProps.isLoading ? null : (jsx(components.DropdownIndicator, Object.assign({}, props, { children: jsx("div", { className: cvaSelectIcon(), children: dropdownIcon ? dropdownIcon : icon }) })));
1113
- }, IndicatorSeparator: () => null, ClearIndicator: props => {
1114
- if (disabled) {
1115
- return null;
1116
- }
1117
- return (jsx(components.ClearIndicator, Object.assign({}, props, { children: jsx("div", { className: cvaSelectXIcon(), onClick: props.clearValue, "data-testid": dataTestId && `${dataTestId}-XMarkIcon`, children: jsx(Icon, { name: "XCircle", size: "medium" }) }) })));
1118
- }, Control: props => {
1119
- return jsx(components.Control, Object.assign({}, props, { className: props.isDisabled ? "bg-slate-100" : "" }));
1120
- }, SingleValue: props => {
1121
- return (jsx(components.SingleValue, Object.assign({}, props, { className: props.isDisabled ? "text-slate-700" : "", children: jsx("div", { "data-testid": dataTestId + "-singleValue", children: props.children }) })));
1122
- }, Menu: props => {
1123
- return (jsx(components.Menu, Object.assign({}, props, { className: cvaSelectMenuList({ menuIsOpen: props.selectProps.menuIsOpen }) })));
1124
- }, MenuList: props => {
1125
- return (jsx(components.MenuList, Object.assign({}, props, { innerProps: Object.assign(Object.assign({}, props.innerProps), { onScroll: e => {
1126
- const listEl = e.currentTarget;
1127
- if (listEl.scrollTop + listEl.clientHeight >= listEl.scrollHeight) {
1128
- props.selectProps.onMenuScrollToBottom && props.selectProps.onMenuScrollToBottom(new TouchEvent(""));
1129
- }
1130
- } }), children: props.children })));
1131
- }, Option: props => {
1132
- const componentProps = {
1133
- label: props.label,
1134
- focused: props.isFocused,
1135
- selected: props.isSelected,
1136
- onClick: props.innerProps.onClick,
1137
- };
1138
- return (jsx(components.Option, Object.assign({}, props, { innerProps: Object.assign(Object.assign({}, props.innerProps), { role: "option", onClick: () => { } }), children: props.isMulti ? (jsx(MultiSelectMenuItem, Object.assign({}, componentProps, { disabled: disabled, dataTestId: typeof props.label === "string" ? props.label : undefined }))) : (jsx(SingleSelectMenuItem, Object.assign({}, componentProps, { disabled: disabled || props.isDisabled, dataTestId: typeof props.label === "string" ? props.label : undefined }))) })));
1139
- } }, componentsProps);
1140
- // eslint-disable-next-line react-hooks/exhaustive-deps
1141
- }, [componentsProps, disabled, maxSelectedDisplayCount]); // do not add dropdownIcon (it will cause issue with opening/closing list for selects with custom icon)
1142
- return customComponents;
1143
- };
1229
+ const NumberField = forwardRef((_a, ref) => {
1230
+ var { label, id, tip, helpText, errorMessage, helpAddon, isInvalid, maxLength, className, value, dataTestId } = _a, rest = __rest(_a, ["label", "id", "tip", "helpText", "errorMessage", "helpAddon", "isInvalid", "maxLength", "className", "value", "dataTestId"]);
1231
+ const renderAsInvalid = isInvalid === undefined ? Boolean(errorMessage) : isInvalid;
1232
+ const htmlForId = id ? id : "numberField-" + v4();
1233
+ return (jsx(FormGroup, { htmlFor: htmlForId, label: label, tip: tip, isInvalid: renderAsInvalid, helpText: (renderAsInvalid && errorMessage) || helpText, helpAddon: helpAddon, dataTestId: dataTestId && `${dataTestId}-FormGroup`, children: jsx(NumberInput, Object.assign({ id: htmlForId, "aria-labelledby": htmlForId + "-label", ref: ref, maxLength: maxLength, value: value, isInvalid: renderAsInvalid }, rest, { className: className, dataTestId: dataTestId })) }));
1234
+ });
1235
+
1236
+ const cvaOptionCardLabel = cvaMerge([
1237
+ cvaInputBase(),
1238
+ "text-center",
1239
+ "component-baseInput-background",
1240
+ "aspect-square",
1241
+ "flex",
1242
+ "flex-col",
1243
+ "gap-2",
1244
+ "justify-center",
1245
+ "items-center",
1246
+ "p-responsive-space",
1247
+ "w-full",
1248
+ "peer-checked:bg-primary-50",
1249
+ "peer-checked:border-primary-600",
1250
+ "peer-checked:border-2",
1251
+ ], {
1252
+ variants: {
1253
+ disabled: {
1254
+ true: [cvaInputBaseDisabled(), "cursor-not-allowed"],
1255
+ false: ["cursor-pointer"],
1256
+ },
1257
+ },
1258
+ });
1259
+ const cvaOptionCardContent = cvaMerge(["flex", "flex-col", "items-center"]);
1260
+ const cvaOptionCardContainer = cvaMerge(["contents"]);
1144
1261
 
1145
1262
  /**
1146
- * @template IsMulti
1147
- * @template Group
1148
- * @param {React.RefObject<HTMLDivElement>} refContainer react ref to container element
1149
- * @param {React.RefObject<HTMLDivElement>} refPrefix react ref to prefix element
1150
- * @param {number | undefined} maxSelectedDisplayCount a number of max display count
1151
- * @param {StylesConfig<Option, IsMulti, Group> | undefined} styles a optional object to override styles of react-select
1152
- * @returns {StylesConfig<Option, boolean>} styles to override in select
1263
+ * A card version of a radio button that includes an icon, headings and a description.
1153
1264
  */
1154
- const useCustomStyles = (refContainer, refPrefix, maxSelectedDisplayCount, styles) => {
1155
- const customStyles = React.useMemo(() => {
1156
- return Object.assign({ control: base => {
1157
- return Object.assign(Object.assign({}, base), { border: "0", boxShadow: "0", "&:hover": {
1158
- border: "0",
1159
- }, marginRight: "2px", backgroundColor: "" });
1160
- }, singleValue: base => (Object.assign({}, base)), multiValue: base => (Object.assign({}, base)), multiValueLabel: base => (Object.assign({}, base)), indicatorsContainer: base => (Object.assign({}, base)), indicatorSeparator: () => ({
1161
- width: "0px",
1162
- }), menu: base => {
1163
- return Object.assign(Object.assign({}, base), { width: "100%", marginTop: "4px", marginBottom: "18px", transition: "all 1s ease-in-out" });
1164
- }, input: base => (Object.assign(Object.assign({}, base), { marginLeft: "0px" })), placeholder: base => (Object.assign({}, base)), option: () => ({}), menuPortal: base => (Object.assign(Object.assign({}, base), { width: (refContainer === null || refContainer === void 0 ? void 0 : refContainer.current) ? `${refContainer.current.clientWidth}px` : base.width, transform: (refPrefix === null || refPrefix === void 0 ? void 0 : refPrefix.current) ? `translate(-${refPrefix.current.clientWidth + 2}px)` : "translate(-2px)", backgroundColor: "#ffffff", borderRadius: "var(--border-radius-lg)", zIndex: 20, borderColor: "rgb(var(--color-slate-300))", boxShadow: "var(--tw-ring-inset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)" })), menuList: base => {
1165
- return Object.assign(Object.assign({}, base), { position: "relative", padding: "var(--spacing-1)", display: "grid", gap: "var(--spacing-1)", width: "100%", borderRadius: "0px", boxShadow: "none", paddingTop: "0px" });
1166
- }, valueContainer: base => {
1167
- return Object.assign(Object.assign({}, base), { flexWrap: maxSelectedDisplayCount !== undefined ? "wrap" : "nowrap", gap: "0.25rem" });
1168
- }, container: base => (Object.assign(Object.assign({}, base), { border: "none", width: "calc(100% - 2px)", paddingLeft: "2px" })), dropdownIndicator: base => (Object.assign(Object.assign({}, base), { padding: "0px" })), clearIndicator: base => {
1169
- return Object.assign(Object.assign({}, base), { padding: "0px" });
1170
- } }, styles);
1171
- // eslint-disable-next-line react-hooks/exhaustive-deps
1172
- }, [refContainer, refPrefix]);
1173
- return { customStyles };
1174
- };
1265
+ const OptionCard = forwardRef((_a, ref) => {
1266
+ var { icon, heading, subheading, description, disabled, id, value, className, dataTestId } = _a, rest = __rest(_a, ["icon", "heading", "subheading", "description", "disabled", "id", "value", "className", "dataTestId"]);
1267
+ const htmlForId = id !== null && id !== void 0 ? id : "option-card-" + v4();
1268
+ return (jsxs("div", { "data-testid": dataTestId, className: cvaOptionCardContainer(), children: [jsx("input", Object.assign({ ref: ref, id: htmlForId, type: "radio", value: value, className: "peer hidden" }, rest)), jsxs("label", { htmlFor: htmlForId, className: cvaOptionCardLabel({ className, disabled }), children: [disabled && icon && cloneElement(icon, { className: `${icon.props.className} text-secondary-400` }), !disabled && icon, heading && (jsx(Heading, { variant: "secondary", subtle: disabled, children: heading })), (subheading || description) && (jsxs("div", { className: cvaOptionCardContent(), children: [subheading && (jsx(Text, { type: "span", weight: "thick", align: "center", subtle: disabled, children: subheading })), description && (jsx(Text, { type: "span", subtle: true, align: "center", children: description }))] }))] })] }));
1269
+ });
1175
1270
 
1176
1271
  /**
1177
- * A hook used by selects to share the common code
1272
+ * A thin wrapper around the `BaseInput` component for password input fields.
1178
1273
  *
1179
- * @param {SelectProps} props - The props for the Select component
1180
- * @returns {IUseSelect} Select component
1274
+ * NOTE: If shown with a label, please use the `PasswordField` component instead.
1181
1275
  */
1182
- const useSelect = (_a) => {
1183
- var _b;
1184
- var { id, className, dataTestId = "select", prefix, async, dropdownIcon, maxMenuHeight = 200, label, hasError, disabled, isMulti, components, value, options, onChange, isLoading, classNamePrefix = "", onMenuOpen, onMenuClose, maxSelectedDisplayCount = undefined, isClearable = false, isSearchable = true, onMenuScrollToBottom, styles, filterOption, onInputChange } = _a, props = __rest(_a, ["id", "className", "dataTestId", "prefix", "async", "dropdownIcon", "maxMenuHeight", "label", "hasError", "disabled", "isMulti", "components", "value", "options", "onChange", "isLoading", "classNamePrefix", "onMenuOpen", "onMenuClose", "maxSelectedDisplayCount", "isClearable", "isSearchable", "onMenuScrollToBottom", "styles", "filterOption", "onInputChange"]);
1185
- const refContainer = React__default.useRef(null);
1186
- const refPrefix = React__default.useRef(null);
1187
- const { customStyles } = useCustomStyles(refContainer, refPrefix, maxSelectedDisplayCount, styles);
1188
- const [menuIsOpen, setMenuIsOpen] = React__default.useState((_b = props.menuIsOpen) !== null && _b !== void 0 ? _b : false);
1189
- const refMenuIsEnabled = React__default.useRef(true);
1190
- const customComponents = useCustomComponents(components, disabled || false, menuIsOpen, refMenuIsEnabled, dataTestId, maxSelectedDisplayCount, dropdownIcon);
1191
- const menuPlacement = "auto";
1192
- const openMenuHandler = () => __awaiter(void 0, void 0, void 0, function* () {
1193
- onMenuOpen && onMenuOpen();
1194
- if (refMenuIsEnabled.current) {
1195
- setMenuIsOpen(true);
1196
- }
1197
- else {
1198
- refMenuIsEnabled.current = true;
1199
- }
1200
- });
1201
- const closeMenuHandler = () => {
1202
- setMenuIsOpen(false);
1203
- onMenuClose && onMenuClose();
1204
- };
1205
- const orderedOptions = React__default.useMemo(() => {
1206
- return disabled
1207
- ? getOrderedOptions(options, value).map(option => {
1208
- return Object.assign(Object.assign({}, option), { disabled: true });
1209
- })
1210
- : getOrderedOptions(options, value);
1211
- }, [options, value, disabled]);
1212
- return {
1213
- refContainer,
1214
- refPrefix,
1215
- customStyles,
1216
- menuIsOpen,
1217
- customComponents,
1218
- menuPlacement,
1219
- openMenuHandler,
1220
- closeMenuHandler,
1221
- orderedOptions,
1222
- };
1223
- };
1276
+ const PasswordInput = forwardRef((props, ref) => (jsx(BaseInput, Object.assign({ ref: ref, type: props.obfuscate ? "text" : "password" }, props))));
1224
1277
 
1225
1278
  /**
1226
- * The CreatableSelect component.
1279
+ * Password fields enter a password or other confidential information. Characters are masked as they are typed.
1227
1280
  *
1228
- * @param {CreateableSelectProps} props - The props for the CreatableSelect component
1229
- * @returns {JSX.Element} CreatableSelect component
1281
+ * _**Do use** when the user has to input a password or something that needs to be obfuscated_
1282
+ *
1283
+ * _**Do not use** to confirm user actions, such as deleting. Use a checkbox for such flows._
1230
1284
  */
1231
- const CreatableSelect = (props) => {
1232
- const { id, dataTestId = "select", async, maxMenuHeight = 200, label, error, disabled, isMulti, value, hideLabel, description, info, options, onChange, isLoading, classNamePrefix = "", filterOption, prefix, onInputChange, placeholder, isClearable, } = props;
1233
- const hasError = !!error;
1234
- const { refPrefix, customStyles, menuIsOpen, customComponents, menuPlacement, openMenuHandler, closeMenuHandler, orderedOptions, } = useSelect(props);
1235
- const selectProps = {
1236
- value,
1237
- menuPlacement,
1238
- maxMenuHeight,
1239
- onChange,
1240
- "aria-label": label,
1241
- "data-testid": dataTestId,
1242
- components: customComponents,
1243
- styles: customStyles,
1244
- tabSelectsValue: false,
1245
- blurInputOnSelect: !isMulti,
1246
- menuPortalTarget: document.body,
1247
- isSearchable: disabled ? false : true,
1248
- menuShouldBlockScroll: true,
1249
- menuShouldScrollIntoView: true,
1250
- openMenuOnFocus: disabled ? false : true,
1251
- menuIsOpen: disabled ? false : menuIsOpen,
1252
- openMenuOnClick: true,
1253
- closeMenuOnSelect: false,
1254
- isMulti: isMulti,
1255
- classNamePrefix,
1256
- isLoading,
1257
- onInputChange,
1258
- placeholder,
1259
- isClearable,
1260
- id,
1261
- };
1262
- return (jsx(SelectContainer, { label: label, hideLabel: hideLabel, id: id, hasError: hasError, error: error, description: description, info: info, dataTestId: dataTestId, isDisabled: disabled, children: jsxs(Fragment, { children: [prefix !== undefined && (jsx("div", { className: cvaSelectPrefix(), "data-testid": dataTestId && `${dataTestId}-prefix`, ref: refPrefix, children: prefix })), async ? (jsx(ReactAsyncCreatableSelect, Object.assign({ "aria-label": label, maxMenuHeight: maxMenuHeight, "data-testid": dataTestId, components: customComponents }, props, { onMenuOpen: () => {
1263
- !disabled && openMenuHandler();
1264
- }, onMenuClose: () => {
1265
- closeMenuHandler();
1266
- } }))) : (jsx(ReactCreatableSelect, Object.assign({}, selectProps, { options: filterOption ? orderedOptions : options, isMulti: isMulti, onMenuOpen: () => {
1267
- !disabled && openMenuHandler();
1268
- }, onMenuClose: () => {
1269
- closeMenuHandler();
1270
- } })))] }) }));
1285
+ const PasswordField = forwardRef((_a, ref) => {
1286
+ var { id, label, tip, helpText, helpAddon, errorMessage, isInvalid, maxLength, onChange, className, value, dataTestId } = _a, rest = __rest(_a, ["id", "label", "tip", "helpText", "helpAddon", "errorMessage", "isInvalid", "maxLength", "onChange", "className", "value", "dataTestId"]);
1287
+ const renderAsInvalid = isInvalid === undefined ? Boolean(errorMessage) : isInvalid;
1288
+ const htmlFor = id ? id : "passwordField-" + v4();
1289
+ const [showPassword, setShowPassword] = useState(false);
1290
+ const handleChange = useCallback((event) => {
1291
+ onChange === null || onChange === void 0 ? void 0 : onChange(event);
1292
+ }, [onChange]);
1293
+ return (jsx(FormGroup, { htmlFor: htmlFor, label: label, tip: tip, isInvalid: renderAsInvalid, helpText: (renderAsInvalid && errorMessage) || helpText, helpAddon: helpAddon, dataTestId: dataTestId && `${dataTestId}-FormGroup`, children: jsx(PasswordInput, Object.assign({}, rest, { obfuscate: showPassword, disabled: rest.readOnly, id: htmlFor, "aria-labelledby": htmlFor + "-label", ref: ref, maxLength: maxLength, value: value, isInvalid: renderAsInvalid, className: className, onChange: handleChange, dataTestId: dataTestId, actions: jsx(Icon, { className: "absolute top-0 bottom-0 right-2 h-full content-center", name: showPassword ? "EyeSlash" : "Eye", size: "small", color: "neutral", onClick: () => setShowPassword(prevState => !prevState) }) })) }));
1294
+ });
1295
+
1296
+ /**
1297
+ * @param phoneNumber - a phone number as a string
1298
+ * @returns {boolean} true if the phone number starts with a plus sign
1299
+ * @example checkIfPhoneNumberHasPlus("123456789") // false
1300
+ * checkIfPhoneNumberHasPlus("+123456789") // true
1301
+ */
1302
+ const checkIfPhoneNumberHasPlus = (phoneNumber) => {
1303
+ return phoneNumber.startsWith("+");
1304
+ };
1305
+ /**
1306
+ * @param phoneNumber - a phone number as a string
1307
+ * @returns {string|number} the phone number with a plus sign in front of it
1308
+ * @example getPhoneNumberWithPlus("123456789") // "+123456789"
1309
+ */
1310
+ const getPhoneNumberWithPlus = (phoneNumber) => {
1311
+ const stringPhoneNumber = phoneNumber.toString();
1312
+ return checkIfPhoneNumberHasPlus(stringPhoneNumber) ? stringPhoneNumber : `+${stringPhoneNumber}`;
1313
+ };
1314
+ /**
1315
+ * Generates a flag emoji based on the given country code.
1316
+ *
1317
+ * @param {string} countryCode - The two-letter country code (ISO 3166-1 alpha-2).
1318
+ * @returns {string} - The corresponding flag emoji for the given country code.
1319
+ * @example getPhoneNumberWithPlus("DK") // "🇩🇰"
1320
+ */
1321
+ const countryCodeToFlagEmoji = (countryCode) => {
1322
+ let code = countryCode;
1323
+ if (countryCode.startsWith("+")) {
1324
+ code = getCountryAbbreviation(countryCode.substring(1));
1325
+ }
1326
+ else if (!isNaN(Number(countryCode))) {
1327
+ code = getCountryAbbreviation(countryCode);
1328
+ }
1329
+ return isSupportedCountry(code.toUpperCase())
1330
+ ? code.toUpperCase().replace(/./g, char => String.fromCodePoint(127397 + char.charCodeAt(0)))
1331
+ : "";
1332
+ };
1333
+ /**
1334
+ * Retrieves the ISO 3166-1 alpha-2 country code associated with a given international calling code.
1335
+ *
1336
+ * @param {string} callCode - The international calling code for a country.
1337
+ * @returns {string} The abbreviation for a country or an empty string if no matching country is found.
1338
+ * @example getCountryAbbreviation("45") // "DK"
1339
+ * @example getCountryAbbreviation("+45") // "DK"
1340
+ */
1341
+ const getCountryAbbreviation = (callCode) => {
1342
+ let code = callCode;
1343
+ if (callCode.startsWith("+")) {
1344
+ code = callCode.substring(1);
1345
+ }
1346
+ return getCountries().find(c => getCountryCallingCode(c) === code) || "";
1271
1347
  };
1272
1348
 
1273
1349
  /**
1274
- * Selects are input components used to choose a value from a set.
1350
+ * A custom hook for managing phone number input state and validation.
1275
1351
  *
1276
- * @param {SelectProps} props - The props for the Select component
1277
- * @returns {JSX.Element} Select component
1352
+ * @property {Function} getPhoneNumber - A function for get formatted phone number with country code and plus sign
1278
1353
  */
1279
- const Select = (props) => {
1280
- const { className } = props, propsNoClassName = __rest(props, ["className"]);
1281
- const { id, dataTestId = "select", prefix, async, maxMenuHeight = 200, label, hasError, disabled, isMulti, value, options, onChange, isLoading, classNamePrefix = dataTestId ? dataTestId : "select", onMenuScrollToBottom, filterOption, onInputChange, isSearchable, isClearable = false, readOnly, } = props;
1282
- const { refContainer, refPrefix, customStyles, menuIsOpen, customComponents, menuPlacement, openMenuHandler, closeMenuHandler, orderedOptions, } = useSelect(props);
1283
- const selectProps = {
1284
- value,
1285
- menuPlacement,
1286
- maxMenuHeight,
1287
- onChange,
1288
- "aria-label": label,
1289
- "data-testid": dataTestId,
1290
- components: customComponents,
1291
- styles: customStyles,
1292
- tabSelectsValue: false,
1293
- blurInputOnSelect: !isMulti,
1294
- menuPortalTarget: props.menuPortalTarget || document.body,
1295
- isSearchable: disabled || readOnly ? false : isSearchable,
1296
- menuShouldBlockScroll: true,
1297
- menuShouldScrollIntoView: true,
1298
- openMenuOnFocus: true,
1299
- menuIsOpen: !readOnly ? menuIsOpen : false,
1300
- openMenuOnClick: true,
1301
- closeMenuOnSelect: false,
1302
- isMulti,
1303
- classNamePrefix,
1304
- isLoading,
1305
- isClearable,
1306
- id,
1307
- onMenuScrollToBottom,
1308
- onInputChange,
1354
+ const usePhoneInput = () => {
1355
+ const getPhoneNumber = ({ country, phone }) => {
1356
+ if (country) {
1357
+ return getPhoneNumberWithPlus(`${country}${phone || ""}`);
1358
+ }
1359
+ return phone || "";
1360
+ };
1361
+ return {
1362
+ getPhoneNumber,
1309
1363
  };
1310
- return (jsxs("div", { ref: refContainer, className: cvaSelect({ invalid: hasError, disabled: disabled || readOnly, className }), "data-testid": dataTestId, children: [prefix !== undefined && (jsx("div", { className: cvaSelectPrefix(), "data-testid": dataTestId && `${dataTestId}-prefix`, ref: refPrefix, children: prefix })), async ? (jsx(ReactAsyncSelect, Object.assign({}, propsNoClassName, selectProps, async, { onMenuOpen: openMenuHandler, onMenuClose: closeMenuHandler }))) : (jsx(ReactSelect, Object.assign({}, propsNoClassName, selectProps, { options: filterOption ? orderedOptions : options, isMulti: isMulti, hideSelectedOptions: false, onMenuOpen: openMenuHandler, onMenuClose: closeMenuHandler })))] }));
1311
1364
  };
1312
- Select.displayName = "Select";
1313
1365
 
1314
1366
  const COUNTRY_CODES_NOT_SUPPORTED_BY_BACKEND = ["+53", "+98", "+211", "+247", "+249", "+383", "+850", "+963"];
1315
1367
  const ADDITIONAL_COUNTRY_CODES_SUPPORTED_BY_BACKEND = [
@@ -2204,4 +2256,4 @@ const UrlField = forwardRef((_a, ref) => {
2204
2256
  */
2205
2257
  setupLibraryTranslations();
2206
2258
 
2207
- export { ActionButton, BaseInput, Checkbox, CreatableSelect, DateField, DateInput, DropZone, EMAIL_REGEX, EmailField, EmailInput, FormGroup, Label, MultiSelectMenuItem, NumberField, NumberInput, OptionCard, PasswordField, PasswordInput, PhoneField, PhoneInput, RadioGroup, RadioItem, Schedule, ScheduleVariant, Search, Select, SingleSelectMenuItem, TextArea, TextAreaField, TextField, TextInput, TimeRange, TimeRangeField, Toggle, UploadField, UploadInput, UrlField, UrlInput, checkIfPhoneNumberHasPlus, countryCodeToFlagEmoji, cvaInput, cvaInputAddon, cvaInputAddonAfter, cvaInputAddonBefore, cvaInputBase, cvaInputBaseDisabled, cvaInputBaseInvalid, cvaInputField, cvaInputPrefix, cvaInputSuffix, cvaSelect, cvaSelectCounter, cvaSelectDynamicTagContainer, cvaSelectIcon, cvaSelectMenu, cvaSelectMenuList, cvaSelectPrefix, cvaSelectXIcon, getCountryAbbreviation, getOrderedOptions, getPhoneNumberWithPlus, isInvalidCountryCode, isInvalidPhoneNumber, isMultiValue, parseSchedule, serializeSchedule, useCustomComponents, usePhoneInput, validateEmailAddress, validatePhoneNumber, weekDay };
2259
+ export { ActionButton, BaseInput, Checkbox, CreatableSelect, DateField, DateInput, DropZone, EMAIL_REGEX, EmailField, EmailInput, FormGroup, Label, MultiSelectMenuItem, NumberField, NumberInput, OptionCard, PasswordField, PasswordInput, PhoneField, PhoneInput, RadioGroup, RadioItem, Schedule, ScheduleVariant, Search, Select, SelectField, SingleSelectMenuItem, TextArea, TextAreaField, TextField, TextInput, TimeRange, TimeRangeField, Toggle, UploadField, UploadInput, UrlField, UrlInput, checkIfPhoneNumberHasPlus, countryCodeToFlagEmoji, cvaInput, cvaInputAddon, cvaInputAddonAfter, cvaInputAddonBefore, cvaInputBase, cvaInputBaseDisabled, cvaInputBaseInvalid, cvaInputField, cvaInputPrefix, cvaInputSuffix, cvaSelect, cvaSelectCounter, cvaSelectDynamicTagContainer, cvaSelectIcon, cvaSelectMenu, cvaSelectMenuList, cvaSelectPrefix, cvaSelectXIcon, getCountryAbbreviation, getOrderedOptions, getPhoneNumberWithPlus, isInvalidCountryCode, isInvalidPhoneNumber, isMultiValue, parseSchedule, serializeSchedule, useCustomComponents, usePhoneInput, validateEmailAddress, validatePhoneNumber, weekDay };