@trackunit/react-form-components 1.7.33 → 1.7.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs.js +352 -362
- package/index.esm.js +348 -356
- package/package.json +8 -8
- package/src/components/BaseInput/BaseInput.d.ts +1 -1
- package/src/components/{DateInput/DateInput.d.ts → DateField/DateBaseInput/DateBaseInput.d.ts} +3 -6
- package/src/components/DateField/DateField.d.ts +2 -2
- package/src/components/{EmailInput/EmailInput.d.ts → EmailField/EmailBaseInput/EmailBaseInput.d.ts} +5 -8
- package/src/components/FormGroup/FormGroup.d.ts +1 -1
- package/src/components/NumberField/NumberBaseInput/NumberBaseInput.d.ts +11 -0
- package/src/components/NumberField/NumberField.d.ts +2 -2
- package/src/components/PasswordField/PasswordBaseInput/PasswordBaseInput.d.ts +12 -0
- package/src/components/PasswordField/PasswordField.d.ts +2 -2
- package/src/components/{PhoneInput/PhoneInput.d.ts → PhoneField/PhoneBaseInput/PhoneBaseInput.d.ts} +4 -7
- package/src/components/PhoneField/PhoneField.d.ts +2 -2
- package/src/components/Search/Search.d.ts +2 -2
- package/src/components/{TextArea/TextArea.d.ts → TextAreaField/TextArea/TextAreaBaseInput.d.ts} +2 -5
- package/src/components/{TextArea/TextArea.variants.d.ts → TextAreaField/TextArea/TextAreaBaseInput.variants.d.ts} +1 -1
- package/src/components/TextAreaField/TextAreaField.d.ts +2 -2
- package/src/components/TextField/TextBaseInput/TextBaseInput.d.ts +11 -0
- package/src/components/TextField/TextField.d.ts +2 -2
- package/src/components/{UrlInput/UrlInput.d.ts → UrlField/UrlBaseInput/UrlBaseInput.d.ts} +4 -7
- package/src/components/UrlField/UrlField.d.ts +2 -2
- package/src/index.d.ts +10 -12
- package/src/components/EmailInput/index.d.ts +0 -1
- package/src/components/NumberInput/NumberInput.d.ts +0 -14
- package/src/components/PasswordField/index.d.ts +0 -1
- package/src/components/PasswordInput/PasswordInput.d.ts +0 -15
- package/src/components/PhoneInput/index.d.ts +0 -1
- package/src/components/TextArea/index.d.ts +0 -1
- package/src/components/TextAreaField/index.d.ts +0 -1
- package/src/components/TextInput/TextInput.d.ts +0 -14
- /package/src/components/{PhoneInput → PhoneField/PhoneBaseInput}/CountryCodeSelect.d.ts +0 -0
- /package/src/components/{PhoneInput → PhoneField/PhoneBaseInput}/CountryCodes.d.ts +0 -0
- /package/src/components/{PhoneInput → PhoneField/PhoneBaseInput}/PhoneInputValidationUtils.d.ts +0 -0
- /package/src/components/{PhoneInput → PhoneField/PhoneBaseInput}/PhoneNumberUtilities.d.ts +0 -0
package/index.esm.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import { useNamespaceTranslation, registerTranslations, NamespaceTrans } from '@trackunit/i18n-library-translation';
|
|
3
|
+
import { Temporal } from '@js-temporal/polyfill';
|
|
3
4
|
import { IconButton, Icon, Tooltip, useGeometry, useIsTextTruncated, Text, Heading, Tag, Spinner, MenuItem, useResize, useDebounce, useIsFirstRender } from '@trackunit/react-components';
|
|
4
|
-
import { useCopyToClipboard } from 'usehooks-ts';
|
|
5
|
-
import { cvaMerge } from '@trackunit/css-class-variance-utilities';
|
|
6
5
|
import { themeSpacing } from '@trackunit/ui-design-tokens';
|
|
7
|
-
import { forwardRef, useRef, useImperativeHandle,
|
|
6
|
+
import { forwardRef, useRef, useImperativeHandle, useCallback, useEffect, useState, useMemo, cloneElement, createContext, useContext, isValidElement, useLayoutEffect } from 'react';
|
|
7
|
+
import { cvaMerge } from '@trackunit/css-class-variance-utilities';
|
|
8
8
|
import { titleCase } from 'string-ts';
|
|
9
|
-
import {
|
|
10
|
-
import { Temporal } from '@js-temporal/polyfill';
|
|
9
|
+
import { useCopyToClipboard } from 'usehooks-ts';
|
|
11
10
|
import parsePhoneNumberFromString, { isSupportedCountry, getCountries, getCountryCallingCode, AsYouType, parseIncompletePhoneNumber, isValidPhoneNumber } from 'libphonenumber-js';
|
|
11
|
+
import { uuidv4, nonNullable } from '@trackunit/shared-utils';
|
|
12
12
|
import { Controller } from 'react-hook-form';
|
|
13
13
|
import ReactSelect, { components } from 'react-select';
|
|
14
14
|
export { default as ValueType } from 'react-select';
|
|
@@ -98,82 +98,6 @@ const setupLibraryTranslations = () => {
|
|
|
98
98
|
registerTranslations(translations);
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
-
const cvaActionButton = cvaMerge(["drop-shadow-none", "rounded-md"], {
|
|
102
|
-
variants: {
|
|
103
|
-
size: {
|
|
104
|
-
small: ["w-6", "h-6", "min-h-0"],
|
|
105
|
-
medium: ["w-6", "h-6", "min-h-0"],
|
|
106
|
-
large: ["w-8", "h-8"],
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
defaultVariants: {
|
|
110
|
-
size: "medium",
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
const cvaActionContainer = cvaMerge(["flex", "items-center"], {
|
|
114
|
-
variants: {
|
|
115
|
-
size: {
|
|
116
|
-
//I just measured manually the top/bottom spacing
|
|
117
|
-
//when using the action button inside an input
|
|
118
|
-
//might need tweaking in the future
|
|
119
|
-
small: ["m-[1px]"],
|
|
120
|
-
medium: ["m-[3px]"],
|
|
121
|
-
large: ["m-[7px]"],
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
defaultVariants: {
|
|
125
|
-
size: "medium",
|
|
126
|
-
},
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* The ActionButton component is a wrapper over IconButton to perform an action when the onClick event is triggered.
|
|
131
|
-
*
|
|
132
|
-
* @param {ActionButtonProps} props - The props for the ActionButton component
|
|
133
|
-
* @returns {ReactElement} ActionButton component
|
|
134
|
-
*/
|
|
135
|
-
const ActionButton = ({ type, value, dataTestId, size, disabled, className, onClick }) => {
|
|
136
|
-
const [, copyToClipboard] = useCopyToClipboard();
|
|
137
|
-
const getIconName = () => {
|
|
138
|
-
switch (type) {
|
|
139
|
-
case "PHONE_NUMBER":
|
|
140
|
-
return "PhoneArrowUpRight";
|
|
141
|
-
case "WEB_ADDRESS":
|
|
142
|
-
return "ArrowTopRightOnSquare";
|
|
143
|
-
case "EMAIL":
|
|
144
|
-
return "Envelope";
|
|
145
|
-
case "EDIT":
|
|
146
|
-
return "Pencil";
|
|
147
|
-
case "COPY":
|
|
148
|
-
default:
|
|
149
|
-
return "ClipboardDocument";
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
const buttonAction = () => {
|
|
153
|
-
switch (type) {
|
|
154
|
-
case "EMAIL":
|
|
155
|
-
return window.open(`mailto:${value}`);
|
|
156
|
-
case "WEB_ADDRESS":
|
|
157
|
-
if (value) {
|
|
158
|
-
return window.open(value, "_blank", "noopener,noreferrer");
|
|
159
|
-
}
|
|
160
|
-
return null;
|
|
161
|
-
case "PHONE_NUMBER":
|
|
162
|
-
return window.open(`tel:${value}`);
|
|
163
|
-
case "EDIT":
|
|
164
|
-
return value?.current?.click();
|
|
165
|
-
case "COPY":
|
|
166
|
-
// Typescript seems to be unable to detect RefObject
|
|
167
|
-
// as one of the members of the union RefObject | string | null which gives access to the `current` property
|
|
168
|
-
return copyToClipboard(value?.current?.value ?? "");
|
|
169
|
-
default:
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
const adjustedIconSize = size === "large" ? "medium" : size;
|
|
174
|
-
return (jsx("div", { className: cvaActionContainer({ className, size }), children: jsx(IconButton, { className: cvaActionButton({ size: adjustedIconSize }), dataTestId: dataTestId || "testIconButtonId", disabled: disabled, icon: jsx(Icon, { name: getIconName(), size: adjustedIconSize }), onClick: buttonAction, size: "small", variant: "secondary" }) }));
|
|
175
|
-
};
|
|
176
|
-
|
|
177
101
|
const cvaInputBase = cvaMerge([
|
|
178
102
|
"component-baseInput-shadow",
|
|
179
103
|
"component-baseInput-border",
|
|
@@ -343,6 +267,82 @@ const AddonRenderer = ({ addon, dataTestId, className, fieldSize, position }) =>
|
|
|
343
267
|
return (jsx("div", { className: cvaInputAddon({ size: fieldSize, position, className }), "data-testid": dataTestId ? `${dataTestId}-addon${titleCase(position)}` : null, children: addon }));
|
|
344
268
|
};
|
|
345
269
|
|
|
270
|
+
const cvaActionButton = cvaMerge(["drop-shadow-none", "rounded-md"], {
|
|
271
|
+
variants: {
|
|
272
|
+
size: {
|
|
273
|
+
small: ["w-6", "h-6", "min-h-0"],
|
|
274
|
+
medium: ["w-6", "h-6", "min-h-0"],
|
|
275
|
+
large: ["w-8", "h-8"],
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
defaultVariants: {
|
|
279
|
+
size: "medium",
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
const cvaActionContainer = cvaMerge(["flex", "items-center"], {
|
|
283
|
+
variants: {
|
|
284
|
+
size: {
|
|
285
|
+
//I just measured manually the top/bottom spacing
|
|
286
|
+
//when using the action button inside an input
|
|
287
|
+
//might need tweaking in the future
|
|
288
|
+
small: ["m-[1px]"],
|
|
289
|
+
medium: ["m-[3px]"],
|
|
290
|
+
large: ["m-[7px]"],
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
defaultVariants: {
|
|
294
|
+
size: "medium",
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* The ActionButton component is a wrapper over IconButton to perform an action when the onClick event is triggered.
|
|
300
|
+
*
|
|
301
|
+
* @param {ActionButtonProps} props - The props for the ActionButton component
|
|
302
|
+
* @returns {ReactElement} ActionButton component
|
|
303
|
+
*/
|
|
304
|
+
const ActionButton = ({ type, value, dataTestId, size, disabled, className, onClick }) => {
|
|
305
|
+
const [, copyToClipboard] = useCopyToClipboard();
|
|
306
|
+
const getIconName = () => {
|
|
307
|
+
switch (type) {
|
|
308
|
+
case "PHONE_NUMBER":
|
|
309
|
+
return "PhoneArrowUpRight";
|
|
310
|
+
case "WEB_ADDRESS":
|
|
311
|
+
return "ArrowTopRightOnSquare";
|
|
312
|
+
case "EMAIL":
|
|
313
|
+
return "Envelope";
|
|
314
|
+
case "EDIT":
|
|
315
|
+
return "Pencil";
|
|
316
|
+
case "COPY":
|
|
317
|
+
default:
|
|
318
|
+
return "ClipboardDocument";
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
const buttonAction = () => {
|
|
322
|
+
switch (type) {
|
|
323
|
+
case "EMAIL":
|
|
324
|
+
return window.open(`mailto:${value}`);
|
|
325
|
+
case "WEB_ADDRESS":
|
|
326
|
+
if (value) {
|
|
327
|
+
return window.open(value, "_blank", "noopener,noreferrer");
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
case "PHONE_NUMBER":
|
|
331
|
+
return window.open(`tel:${value}`);
|
|
332
|
+
case "EDIT":
|
|
333
|
+
return value?.current?.click();
|
|
334
|
+
case "COPY":
|
|
335
|
+
// Typescript seems to be unable to detect RefObject
|
|
336
|
+
// as one of the members of the union RefObject | string | null which gives access to the `current` property
|
|
337
|
+
return copyToClipboard(value?.current?.value ?? "");
|
|
338
|
+
default:
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
const adjustedIconSize = size === "large" ? "medium" : size;
|
|
343
|
+
return (jsx("div", { className: cvaActionContainer({ className, size }), children: jsx(IconButton, { className: cvaActionButton({ size: adjustedIconSize }), dataTestId: dataTestId || "testIconButtonId", disabled: disabled, icon: jsx(Icon, { name: getIconName(), size: adjustedIconSize }), onClick: buttonAction, size: "small", variant: "secondary" }) }));
|
|
344
|
+
};
|
|
345
|
+
|
|
346
346
|
const GenericActionsRenderer = ({ genericAction, disabled, fieldSize, innerRef, tooltipLabel, }) => {
|
|
347
347
|
const [t] = useTranslation();
|
|
348
348
|
if (!genericAction) {
|
|
@@ -412,41 +412,260 @@ const SuffixRenderer = ({ suffix, isInvalid, isWarning, dataTestId, disabled, })
|
|
|
412
412
|
};
|
|
413
413
|
|
|
414
414
|
/**
|
|
415
|
-
* A base input component that can be used for text inputs, password inputs, etc.
|
|
416
|
-
* A reference to the input element is provided as the `ref` prop.
|
|
417
|
-
* Extends props from [React.InputHTMLAttributes](https://reactjs.org/docs/dom-elements.html#input)
|
|
415
|
+
* A base input component that can be used for text inputs, password inputs, etc.
|
|
416
|
+
* A reference to the input element is provided as the `ref` prop.
|
|
417
|
+
* Extends props from [React.InputHTMLAttributes](https://reactjs.org/docs/dom-elements.html#input)
|
|
418
|
+
*
|
|
419
|
+
* For specific input types make sure to use the corresponding input component.
|
|
420
|
+
* This is a base used by our other input components such as TextBaseInput, NumberBaseInput, PasswordBaseInput, etc.
|
|
421
|
+
*/
|
|
422
|
+
const BaseInput = ({ className, isInvalid, dataTestId, prefix, suffix, addonBefore, addonAfter, actions, fieldSize = "medium", nonInteractive = false, inputClassName, placeholder, isWarning, type, genericAction, style, ref, ...rest }) => {
|
|
423
|
+
// Derive final flags
|
|
424
|
+
const renderAsDisabled = Boolean(rest.disabled);
|
|
425
|
+
const renderAsReadonly = Boolean(rest.readOnly);
|
|
426
|
+
const beforeContainerRef = useRef(null);
|
|
427
|
+
const { width: beforeContainerWidth } = useGeometry(beforeContainerRef);
|
|
428
|
+
const afterContainerRef = useRef(null);
|
|
429
|
+
const { width: afterContainerWidth } = useGeometry(afterContainerRef);
|
|
430
|
+
// Keep a reference to the input element
|
|
431
|
+
const innerRef = useRef(null);
|
|
432
|
+
useImperativeHandle(ref, () => innerRef.current, []);
|
|
433
|
+
return (jsxs("div", { className: cvaInput$1({
|
|
434
|
+
disabled: renderAsDisabled,
|
|
435
|
+
invalid: isInvalid,
|
|
436
|
+
isWarning,
|
|
437
|
+
size: fieldSize,
|
|
438
|
+
className,
|
|
439
|
+
}), "data-testid": dataTestId ? `${dataTestId}-container` : undefined, style: style, children: [jsxs("div", { className: cvaAccessoriesContainer({ className: cvaInputItemPlacementManager({ position: "before" }) }), "data-testid": dataTestId ? `${dataTestId}-before-container` : undefined, ref: beforeContainerRef, children: [jsx(AddonRenderer, { addon: addonBefore, dataTestId: dataTestId, fieldSize: fieldSize, position: "before" }), jsx(PrefixRenderer, { dataTestId: dataTestId, disabled: renderAsDisabled, prefix: prefix, type: type })] }), jsx("input", { "aria-required": rest.required, className: cvaInputField({
|
|
440
|
+
readOnly: renderAsReadonly,
|
|
441
|
+
size: fieldSize,
|
|
442
|
+
disabled: renderAsDisabled,
|
|
443
|
+
className: cvaInputItemPlacementManager({ position: "span", className: inputClassName }),
|
|
444
|
+
}), "data-testid": dataTestId, placeholder: renderAsDisabled ? undefined : placeholder, ref: innerRef, style: {
|
|
445
|
+
paddingLeft: beforeContainerWidth ? `calc(${beforeContainerWidth}px + ${themeSpacing[2]})` : undefined,
|
|
446
|
+
paddingRight: afterContainerWidth ? `calc(${afterContainerWidth}px + ${themeSpacing[2]})` : undefined,
|
|
447
|
+
}, type: type, ...rest, disabled: renderAsDisabled, readOnly: renderAsReadonly || nonInteractive }), jsxs("div", { className: cvaAccessoriesContainer({ className: cvaInputItemPlacementManager({ position: "after" }) }), "data-testid": dataTestId ? `${dataTestId}-after-container` : undefined, ref: afterContainerRef, children: [jsx(LockReasonRenderer, { dataTestId: dataTestId + "-disabled", lockReason: rest.disabled }), jsx(LockReasonRenderer, { dataTestId: dataTestId + "-readonly", lockReason: rest.readOnly && !rest.disabled ? rest.readOnly : undefined }), jsx(GenericActionsRenderer, { fieldSize: fieldSize, genericAction: genericAction, innerRef: innerRef }), jsx(SuffixRenderer, { dataTestId: dataTestId, disabled: renderAsDisabled, isInvalid: isInvalid, isWarning: isWarning, suffix: suffix }), actions, jsx(AddonRenderer, { addon: addonAfter, dataTestId: dataTestId, fieldSize: fieldSize, position: "after" })] })] }));
|
|
448
|
+
};
|
|
449
|
+
BaseInput.displayName = "BaseInput";
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* A wrapper around BaseInput with a pop-up day picker.
|
|
453
|
+
*
|
|
454
|
+
* The value is formatted to an ISO date string (YYYY-MM-DD)
|
|
455
|
+
*
|
|
456
|
+
* NOTE: If shown with a label, please use the `DateField` component instead.
|
|
457
|
+
*/
|
|
458
|
+
const DateBaseInput = ({ min, max, defaultValue, value, ref, ...rest }) => {
|
|
459
|
+
const formatDateToInputString = (date) => date instanceof Date
|
|
460
|
+
? Temporal.PlainDateTime.from({
|
|
461
|
+
year: date.getFullYear(),
|
|
462
|
+
month: date.getMonth() + 1,
|
|
463
|
+
day: date.getDate(),
|
|
464
|
+
})
|
|
465
|
+
.toPlainDate()
|
|
466
|
+
.toString()
|
|
467
|
+
: date;
|
|
468
|
+
// Chrome and Firefox need their default icon to have datepicker functionality.
|
|
469
|
+
const showIcon = !/Chrome/.test(navigator.userAgent) && !/Firefox/.test(navigator.userAgent);
|
|
470
|
+
return (jsx(BaseInput, { defaultValue: formatDateToInputString(defaultValue), max: formatDateToInputString(max), min: formatDateToInputString(min), ref: ref, suffix: showIcon ? jsx(Icon, { dataTestId: "calendar", name: "Calendar", size: "medium", type: "solid" }) : null, type: "date", value: formatDateToInputString(value), ...rest }));
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* A thin wrapper around the `BaseInput` component for number input fields.
|
|
475
|
+
*
|
|
476
|
+
* NOTE: If shown with a label, please use the `NumberField` component instead.
|
|
477
|
+
*/
|
|
478
|
+
const NumberBaseInput = ({ ref, ...rest }) => {
|
|
479
|
+
const inputElementRef = useRef(null);
|
|
480
|
+
const preventDefaultWheelEvent = useCallback((event) => {
|
|
481
|
+
const inputElement = inputElementRef.current;
|
|
482
|
+
const activeElement = document.activeElement;
|
|
483
|
+
if (inputElement && activeElement === inputElement) {
|
|
484
|
+
event.preventDefault();
|
|
485
|
+
}
|
|
486
|
+
}, []);
|
|
487
|
+
const forwardAndStoreInputRef = useCallback((node) => {
|
|
488
|
+
const previousNode = inputElementRef.current;
|
|
489
|
+
if (previousNode) {
|
|
490
|
+
previousNode.removeEventListener("wheel", preventDefaultWheelEvent);
|
|
491
|
+
}
|
|
492
|
+
inputElementRef.current = node;
|
|
493
|
+
if (node) {
|
|
494
|
+
// NOTE: Prevent the default browser behavior of changing the value via mouse wheel
|
|
495
|
+
node.addEventListener("wheel", preventDefaultWheelEvent, { passive: false });
|
|
496
|
+
}
|
|
497
|
+
if (typeof ref === "function") {
|
|
498
|
+
ref(node);
|
|
499
|
+
}
|
|
500
|
+
else if (ref && typeof ref === "object") {
|
|
501
|
+
ref.current = node;
|
|
502
|
+
}
|
|
503
|
+
}, [preventDefaultWheelEvent, ref]);
|
|
504
|
+
useEffect(() => {
|
|
505
|
+
return () => {
|
|
506
|
+
const element = inputElementRef.current;
|
|
507
|
+
if (element) {
|
|
508
|
+
element.removeEventListener("wheel", preventDefaultWheelEvent);
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
}, [preventDefaultWheelEvent]);
|
|
512
|
+
return jsx(BaseInput, { ref: forwardAndStoreInputRef, type: "number", ...rest, value: rest.value });
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* @param phoneNumber - a phone number as a string
|
|
517
|
+
* @returns {boolean} true if the phone number starts with a plus sign
|
|
518
|
+
* @example checkIfPhoneNumberHasPlus("123456789") // false
|
|
519
|
+
* checkIfPhoneNumberHasPlus("+123456789") // true
|
|
520
|
+
*/
|
|
521
|
+
const checkIfPhoneNumberHasPlus = (phoneNumber) => {
|
|
522
|
+
return phoneNumber.startsWith("+");
|
|
523
|
+
};
|
|
524
|
+
/**
|
|
525
|
+
* @param phoneNumber - a phone number as a string
|
|
526
|
+
* @returns {string|number} the phone number with a plus sign in front of it
|
|
527
|
+
* @example getPhoneNumberWithPlus("123456789") // "+123456789"
|
|
528
|
+
*/
|
|
529
|
+
const getPhoneNumberWithPlus = (phoneNumber) => {
|
|
530
|
+
const stringPhoneNumber = phoneNumber.toString();
|
|
531
|
+
return checkIfPhoneNumberHasPlus(stringPhoneNumber) ? stringPhoneNumber : `+${stringPhoneNumber}`;
|
|
532
|
+
};
|
|
533
|
+
/**
|
|
534
|
+
* Generates a flag emoji based on the given country code.
|
|
535
|
+
*
|
|
536
|
+
* @param {string} countryCode - The two-letter country code (ISO 3166-1 alpha-2).
|
|
537
|
+
* @returns {string} - The corresponding flag emoji for the given country code.
|
|
538
|
+
* @example getPhoneNumberWithPlus("DK") // "🇩🇰"
|
|
539
|
+
*/
|
|
540
|
+
const countryCodeToFlagEmoji = (countryCode) => {
|
|
541
|
+
let code = countryCode;
|
|
542
|
+
if (countryCode.startsWith("+")) {
|
|
543
|
+
code = getCountryAbbreviation(countryCode.substring(1));
|
|
544
|
+
}
|
|
545
|
+
else if (!isNaN(Number(countryCode))) {
|
|
546
|
+
code = getCountryAbbreviation(countryCode);
|
|
547
|
+
}
|
|
548
|
+
return isSupportedCountry(code.toUpperCase())
|
|
549
|
+
? code.toUpperCase().replace(/./g, char => String.fromCodePoint(127397 + char.charCodeAt(0)))
|
|
550
|
+
: "";
|
|
551
|
+
};
|
|
552
|
+
/**
|
|
553
|
+
* Retrieves the ISO 3166-1 alpha-2 country code associated with a given international calling code.
|
|
554
|
+
*
|
|
555
|
+
* @param {string} callCode - The international calling code for a country.
|
|
556
|
+
* @returns {string} The abbreviation for a country or an empty string if no matching country is found.
|
|
557
|
+
* @example getCountryAbbreviation("45") // "DK"
|
|
558
|
+
* @example getCountryAbbreviation("+45") // "DK"
|
|
559
|
+
*/
|
|
560
|
+
const getCountryAbbreviation = (callCode) => {
|
|
561
|
+
let code = callCode;
|
|
562
|
+
if (callCode.startsWith("+")) {
|
|
563
|
+
code = callCode.substring(1);
|
|
564
|
+
}
|
|
565
|
+
return getCountries().find(c => getCountryCallingCode(c) === code) || "";
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
const DEFAULT_COUNTRY_CODE = undefined;
|
|
569
|
+
/**
|
|
570
|
+
* A component for inputting phone numbers with an optional action button for initiating a phone call.
|
|
571
|
+
*
|
|
572
|
+
* @param {string} [dataTestId] - The data test ID for the component.
|
|
573
|
+
* @param {string|number} [value] - The value of the input field. The value should include the country code as well.
|
|
574
|
+
* @param {boolean} [disabled=false] - Whether the component is disabled or not.
|
|
575
|
+
* @param {string} [fieldSize="medium"] - The size of the input field.
|
|
576
|
+
* @param {boolean} [disableAction=false] - Whether the action button is disabled or not.
|
|
577
|
+
*/
|
|
578
|
+
const PhoneBaseInput = ({ dataTestId, isInvalid, disabled = false, value, defaultValue, fieldSize = "medium", disableAction = false, onChange, readOnly, onFocus, onBlur, name, ref, ...rest }) => {
|
|
579
|
+
const [innerValue, setInnerValue] = useState(() => {
|
|
580
|
+
return (value?.toString() || defaultValue?.toString()) ?? "";
|
|
581
|
+
});
|
|
582
|
+
const fieldIsFocused = useRef(false);
|
|
583
|
+
const [countryCode, setCountryCode] = useState(DEFAULT_COUNTRY_CODE);
|
|
584
|
+
const determineCountry = useCallback((newValue) => {
|
|
585
|
+
const asYouType = new AsYouType();
|
|
586
|
+
asYouType.input(newValue);
|
|
587
|
+
setCountryCode(asYouType.getCountry());
|
|
588
|
+
}, []);
|
|
589
|
+
const handleChange = useCallback(event => {
|
|
590
|
+
const newValue = event.target.value;
|
|
591
|
+
event.target.value = parseIncompletePhoneNumber(newValue);
|
|
592
|
+
onChange?.(event);
|
|
593
|
+
setInnerValue(newValue);
|
|
594
|
+
determineCountry(newValue);
|
|
595
|
+
}, [onChange, determineCountry]);
|
|
596
|
+
const makePretty = useCallback((newValue) => {
|
|
597
|
+
const asYouType = new AsYouType();
|
|
598
|
+
const pretty = asYouType.input(newValue);
|
|
599
|
+
setInnerValue(pretty);
|
|
600
|
+
setCountryCode(asYouType.getCountry());
|
|
601
|
+
}, []);
|
|
602
|
+
useEffect(() => {
|
|
603
|
+
if (!fieldIsFocused.current) {
|
|
604
|
+
makePretty(typeof value === "string" ? value : "");
|
|
605
|
+
}
|
|
606
|
+
}, [makePretty, value]);
|
|
607
|
+
const handleBlur = useCallback(event => {
|
|
608
|
+
const newValue = event.target.value;
|
|
609
|
+
makePretty(newValue);
|
|
610
|
+
onBlur?.(event);
|
|
611
|
+
fieldIsFocused.current = false;
|
|
612
|
+
}, [makePretty, onBlur]);
|
|
613
|
+
const handleFocus = useCallback(event => {
|
|
614
|
+
const newValue = event.target.value;
|
|
615
|
+
const noneFormattedValue = parseIncompletePhoneNumber(newValue);
|
|
616
|
+
setInnerValue(noneFormattedValue);
|
|
617
|
+
onFocus?.(event);
|
|
618
|
+
fieldIsFocused.current = true;
|
|
619
|
+
}, [onFocus]);
|
|
620
|
+
return (jsx("div", { className: "grid grid-cols-1 gap-2", "data-testid": dataTestId ? `${dataTestId}-container` : null, children: jsx(BaseInput, { actions: !disableAction && innerValue && innerValue.length > 0 ? (jsx(ActionButton, { dataTestId: dataTestId ? `${dataTestId}-phoneIcon` : undefined, disabled: isInvalid, size: fieldSize ?? undefined, type: "PHONE_NUMBER", value: value?.toString() || "" })) : null, dataTestId: dataTestId ? `${dataTestId}-phoneNumberInput` : undefined, disabled: disabled, fieldSize: fieldSize, id: "phoneInput-number", isInvalid: isInvalid, name: name, onBlur: handleBlur, onChange: handleChange, onFocus: handleFocus, prefix: (countryCode && countryCodeToFlagEmoji(countryCode)) || undefined, readOnly: readOnly, ref: ref, type: "tel", value: innerValue, ...rest }) }));
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
const cvaTextAreaBaseInput = cvaMerge([
|
|
624
|
+
cvaInputBase(),
|
|
625
|
+
"block",
|
|
626
|
+
"overflow-auto",
|
|
627
|
+
"appearance-none",
|
|
628
|
+
"px-3",
|
|
629
|
+
"py-2",
|
|
630
|
+
"text-base",
|
|
631
|
+
"text-slate-900",
|
|
632
|
+
"placeholder-slate-400",
|
|
633
|
+
"w-full",
|
|
634
|
+
"h-20",
|
|
635
|
+
"transition",
|
|
636
|
+
], {
|
|
637
|
+
variants: {
|
|
638
|
+
disabled: {
|
|
639
|
+
true: cvaInputBaseDisabled(),
|
|
640
|
+
false: "",
|
|
641
|
+
},
|
|
642
|
+
invalid: {
|
|
643
|
+
true: cvaInputBaseInvalid(),
|
|
644
|
+
false: "",
|
|
645
|
+
},
|
|
646
|
+
resize: {
|
|
647
|
+
both: "resize",
|
|
648
|
+
vertical: "resize-y",
|
|
649
|
+
horizontal: "resize-x",
|
|
650
|
+
none: "resize-none",
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* The TextArea is a base component, and should not be used very often.
|
|
657
|
+
* For most cases the TextAreaField is the correct component.
|
|
658
|
+
*/
|
|
659
|
+
const TextAreaBaseInput = ({ id, name, value, rows, disabled, placeholder, readOnly, tabIndex, onChange, onFocus, onBlur, maxLength, resize = "vertical", defaultValue, required, dataTestId, isInvalid, className, ref, ...rest }) => {
|
|
660
|
+
return (jsx("textarea", { className: cvaTextAreaBaseInput({ className, resize, invalid: isInvalid, disabled }), defaultValue: defaultValue, disabled: disabled, id: id, maxLength: maxLength, name: name, onBlur: onBlur, onFocus: onFocus, placeholder: placeholder, readOnly: readOnly, ref: ref, required: required, rows: rows, tabIndex: tabIndex, value: value, ...rest, "data-testid": dataTestId, onChange: onChange }));
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* A thin wrapper around the `BaseInput` component for text input fields.
|
|
418
665
|
*
|
|
419
|
-
*
|
|
420
|
-
* This is a base used by our other input components such as TextInput, NumberInput, PasswordInput, etc.
|
|
666
|
+
* NOTE: If shown with a label, please use the `TextField` component instead.
|
|
421
667
|
*/
|
|
422
|
-
const
|
|
423
|
-
// Derive final flags
|
|
424
|
-
const renderAsDisabled = Boolean(rest.disabled);
|
|
425
|
-
const renderAsReadonly = Boolean(rest.readOnly);
|
|
426
|
-
const beforeContainerRef = useRef(null);
|
|
427
|
-
const { width: beforeContainerWidth } = useGeometry(beforeContainerRef);
|
|
428
|
-
const afterContainerRef = useRef(null);
|
|
429
|
-
const { width: afterContainerWidth } = useGeometry(afterContainerRef);
|
|
430
|
-
// Keep a reference to the input element
|
|
431
|
-
const innerRef = useRef(null);
|
|
432
|
-
useImperativeHandle(ref, () => innerRef.current, []);
|
|
433
|
-
return (jsxs("div", { className: cvaInput$1({
|
|
434
|
-
disabled: renderAsDisabled,
|
|
435
|
-
invalid: isInvalid,
|
|
436
|
-
isWarning,
|
|
437
|
-
size: fieldSize,
|
|
438
|
-
className,
|
|
439
|
-
}), "data-testid": dataTestId ? `${dataTestId}-container` : undefined, style: style, children: [jsxs("div", { className: cvaAccessoriesContainer({ className: cvaInputItemPlacementManager({ position: "before" }) }), "data-testid": dataTestId ? `${dataTestId}-before-container` : undefined, ref: beforeContainerRef, children: [jsx(AddonRenderer, { addon: addonBefore, dataTestId: dataTestId, fieldSize: fieldSize, position: "before" }), jsx(PrefixRenderer, { dataTestId: dataTestId, disabled: renderAsDisabled, prefix: prefix, type: type })] }), jsx("input", { "aria-required": rest.required, className: cvaInputField({
|
|
440
|
-
readOnly: renderAsReadonly,
|
|
441
|
-
size: fieldSize,
|
|
442
|
-
disabled: renderAsDisabled,
|
|
443
|
-
className: cvaInputItemPlacementManager({ position: "span", className: inputClassName }),
|
|
444
|
-
}), "data-testid": dataTestId, placeholder: renderAsDisabled ? undefined : placeholder, ref: innerRef, style: {
|
|
445
|
-
paddingLeft: beforeContainerWidth ? `calc(${beforeContainerWidth}px + ${themeSpacing[2]})` : undefined,
|
|
446
|
-
paddingRight: afterContainerWidth ? `calc(${afterContainerWidth}px + ${themeSpacing[2]})` : undefined,
|
|
447
|
-
}, type: type, ...rest, disabled: renderAsDisabled, readOnly: renderAsReadonly || nonInteractive }), jsxs("div", { className: cvaAccessoriesContainer({ className: cvaInputItemPlacementManager({ position: "after" }) }), "data-testid": dataTestId ? `${dataTestId}-after-container` : undefined, ref: afterContainerRef, children: [jsx(LockReasonRenderer, { dataTestId: dataTestId + "-disabled", lockReason: rest.disabled }), jsx(LockReasonRenderer, { dataTestId: dataTestId + "-readonly", lockReason: rest.readOnly && !rest.disabled ? rest.readOnly : undefined }), jsx(GenericActionsRenderer, { fieldSize: fieldSize, genericAction: genericAction, innerRef: innerRef }), jsx(SuffixRenderer, { dataTestId: dataTestId, disabled: renderAsDisabled, isInvalid: isInvalid, isWarning: isWarning, suffix: suffix }), actions, jsx(AddonRenderer, { addon: addonAfter, dataTestId: dataTestId, fieldSize: fieldSize, position: "after" })] })] }));
|
|
448
|
-
};
|
|
449
|
-
BaseInput.displayName = "BaseInput";
|
|
668
|
+
const TextBaseInput = ({ ref, ...rest }) => jsx(BaseInput, { ref: ref, type: "text", ...rest });
|
|
450
669
|
|
|
451
670
|
/**
|
|
452
671
|
* Shared CVA for binary control items: Checkbox, RadioItem, ToggleSwitchOption
|
|
@@ -713,7 +932,7 @@ const FormGroup = ({ isInvalid, isWarning, helpText, helpAddon, tip, className,
|
|
|
713
932
|
const color = isInvalid ? "danger" : isWarning ? "warning" : null;
|
|
714
933
|
return color ? jsx(Icon, { color: color, name: "ExclamationTriangle", size: "small" }) : null;
|
|
715
934
|
}, [isInvalid, isWarning]);
|
|
716
|
-
return (jsxs("div", { className: cvaFormGroup({ className }), "data-testid": dataTestId, children: [jsxs("div", { className: cvaFormGroupContainerBefore(), children: [
|
|
935
|
+
return (jsxs("div", { className: cvaFormGroup({ className }), "data-testid": dataTestId, children: [label ? (jsxs("div", { className: cvaFormGroupContainerBefore(), children: [jsxs(Fragment, { children: [jsx(Label, { className: "component-formGroup-font", dataTestId: dataTestId ? `${dataTestId}-label` : undefined, htmlFor: htmlFor, id: htmlFor + "-label", children: label }), required ? (jsx(Tooltip, { "data-testid": "required-asterisk", label: t("field.required.asterisk.tooltip"), children: "*" })) : null] }), tip ? (jsx(Tooltip, { className: "ml-1", dataTestId: dataTestId ? `${dataTestId}-tooltip` : undefined, label: tip, placement: "bottom" })) : null] })) : null, children, helpText || helpAddon ? (jsxs("div", { className: cvaFormGroupContainerAfter({ invalid: isInvalid, isWarning: isWarning }), children: [helpText ? (jsxs("div", { className: "flex gap-1", children: [validationStateIcon, jsx("span", { "data-testid": dataTestId ? `${dataTestId}-helpText` : undefined, children: helpText })] })) : undefined, helpAddon ? (jsx("span", { className: cvaHelpAddon(), "data-testid": dataTestId ? `${dataTestId}-helpAddon` : null, children: helpAddon })) : null] })) : null] }));
|
|
717
936
|
};
|
|
718
937
|
|
|
719
938
|
/**
|
|
@@ -839,29 +1058,6 @@ const ColorField = forwardRef(({ label, id, tip, helpText, errorMessage, helpAdd
|
|
|
839
1058
|
});
|
|
840
1059
|
ColorField.displayName = "ColorField";
|
|
841
1060
|
|
|
842
|
-
/**
|
|
843
|
-
* A wrapper around BaseInput with a pop-up day picker.
|
|
844
|
-
*
|
|
845
|
-
* The value is formatted to an ISO date string (YYYY-MM-DD)
|
|
846
|
-
*
|
|
847
|
-
* NOTE: If shown with a label, please use the `DateField` component instead.
|
|
848
|
-
*/
|
|
849
|
-
const DateInput = ({ min, max, defaultValue, value, ref, ...rest }) => {
|
|
850
|
-
const formatDateToInputString = (date) => date instanceof Date
|
|
851
|
-
? Temporal.PlainDateTime.from({
|
|
852
|
-
year: date.getFullYear(),
|
|
853
|
-
month: date.getMonth() + 1,
|
|
854
|
-
day: date.getDate(),
|
|
855
|
-
})
|
|
856
|
-
.toPlainDate()
|
|
857
|
-
.toString()
|
|
858
|
-
: date;
|
|
859
|
-
// Chrome and Firefox need their default icon to have datepicker functionality.
|
|
860
|
-
const showIcon = !/Chrome/.test(navigator.userAgent) && !/Firefox/.test(navigator.userAgent);
|
|
861
|
-
return (jsx(BaseInput, { defaultValue: formatDateToInputString(defaultValue), max: formatDateToInputString(max), min: formatDateToInputString(min), ref: ref, suffix: showIcon ? jsx(Icon, { dataTestId: "calendar", name: "Calendar", size: "medium", type: "solid" }) : null, type: "date", value: formatDateToInputString(value), ...rest }));
|
|
862
|
-
};
|
|
863
|
-
DateInput.displayName = "DateInput";
|
|
864
|
-
|
|
865
1061
|
/**
|
|
866
1062
|
* The date field component is used for entering date values.
|
|
867
1063
|
*
|
|
@@ -872,7 +1068,7 @@ DateInput.displayName = "DateInput";
|
|
|
872
1068
|
const DateField = ({ label, id, tip, helpText, errorMessage, helpAddon, isInvalid, className, defaultValue, dataTestId, ref, ...rest }) => {
|
|
873
1069
|
const renderAsInvalid = isInvalid === undefined ? Boolean(errorMessage) : isInvalid;
|
|
874
1070
|
const htmlForId = id ? id : "dateField-" + uuidv4();
|
|
875
|
-
return (jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && errorMessage) || helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(
|
|
1071
|
+
return (jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && errorMessage) || helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(DateBaseInput, { "aria-labelledby": htmlForId + "-label", defaultValue: defaultValue, id: htmlForId, isInvalid: renderAsInvalid, ref: ref, ...rest, className: className, dataTestId: dataTestId }) }));
|
|
876
1072
|
};
|
|
877
1073
|
DateField.displayName = "DateField";
|
|
878
1074
|
|
|
@@ -1027,8 +1223,8 @@ const validateEmailId = (emailId, required) => {
|
|
|
1027
1223
|
* A reference to the input element is provided as the `ref` prop.
|
|
1028
1224
|
* For specific input types make sure to use the corresponding input component.
|
|
1029
1225
|
*/
|
|
1030
|
-
const
|
|
1031
|
-
const [email, setEmail] = useState(
|
|
1226
|
+
const EmailBaseInput = ({ fieldSize = "medium", disabled = false, dataTestId, isInvalid = false, onChange, disableAction = false, ref, ...rest }) => {
|
|
1227
|
+
const [email, setEmail] = useState(rest.value?.toString() || rest.defaultValue?.toString());
|
|
1032
1228
|
const sendEmail = () => {
|
|
1033
1229
|
return window.open(`mailto:${email}`);
|
|
1034
1230
|
};
|
|
@@ -1040,7 +1236,6 @@ const EmailInput = ({ fieldSize = "medium", disabled = false, dataTestId, isInva
|
|
|
1040
1236
|
const renderAsInvalid = (email && !validateEmailAddress(email)) || isInvalid;
|
|
1041
1237
|
return (jsx(BaseInput, { actions: email && email.length > 0 ? (jsx(ActionButton, { dataTestId: dataTestId ? `${dataTestId}-emailIcon` : undefined, disabled: disableAction || isInvalid, onClick: sendEmail, size: fieldSize ?? undefined, type: "EMAIL", value: email })) : null, dataTestId: dataTestId, disabled: disabled, isInvalid: renderAsInvalid, onChange: handleChange, placeholder: rest.placeholder || "mail@example.com", ref: ref, type: "email", ...rest }));
|
|
1042
1238
|
};
|
|
1043
|
-
EmailInput.displayName = "EmailInput";
|
|
1044
1239
|
|
|
1045
1240
|
/**
|
|
1046
1241
|
* The EmailField component is used to enter email.
|
|
@@ -1068,7 +1263,7 @@ const EmailField = ({ label, id, tip, helpText, errorMessage, helpAddon, classNa
|
|
|
1068
1263
|
onChange(event);
|
|
1069
1264
|
}
|
|
1070
1265
|
}, [onChange]);
|
|
1071
|
-
return (jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && error) || helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(
|
|
1266
|
+
return (jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && error) || helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(EmailBaseInput, { "aria-labelledby": htmlForId + "-label", defaultValue: defaultValue, id: htmlForId, isInvalid: renderAsInvalid, onBlur: handleBlur, onChange: handleChange, ref: ref, value: value, ...rest, className: className, dataTestId: dataTestId }) }));
|
|
1072
1267
|
};
|
|
1073
1268
|
EmailField.displayName = "EmailField";
|
|
1074
1269
|
|
|
@@ -1122,49 +1317,6 @@ const validateNumber = (number, required = false, min, max) => {
|
|
|
1122
1317
|
return "INVALID_NUMBER";
|
|
1123
1318
|
};
|
|
1124
1319
|
|
|
1125
|
-
/**
|
|
1126
|
-
* A thin wrapper around the `BaseInput` component for number input fields.
|
|
1127
|
-
*
|
|
1128
|
-
* NOTE: If shown with a label, please use the `NumberField` component instead.
|
|
1129
|
-
*/
|
|
1130
|
-
const NumberInput = ({ ref, ...rest }) => {
|
|
1131
|
-
const inputElementRef = useRef(null);
|
|
1132
|
-
const preventDefaultWheelEvent = useCallback((event) => {
|
|
1133
|
-
const inputElement = inputElementRef.current;
|
|
1134
|
-
const activeElement = document.activeElement;
|
|
1135
|
-
if (inputElement && activeElement === inputElement) {
|
|
1136
|
-
event.preventDefault();
|
|
1137
|
-
}
|
|
1138
|
-
}, []);
|
|
1139
|
-
const forwardAndStoreInputRef = useCallback((node) => {
|
|
1140
|
-
const previousNode = inputElementRef.current;
|
|
1141
|
-
if (previousNode) {
|
|
1142
|
-
previousNode.removeEventListener("wheel", preventDefaultWheelEvent);
|
|
1143
|
-
}
|
|
1144
|
-
inputElementRef.current = node;
|
|
1145
|
-
if (node) {
|
|
1146
|
-
// NOTE: Prevent the default browser behavior of changing the value via mouse wheel
|
|
1147
|
-
node.addEventListener("wheel", preventDefaultWheelEvent, { passive: false });
|
|
1148
|
-
}
|
|
1149
|
-
if (typeof ref === "function") {
|
|
1150
|
-
ref(node);
|
|
1151
|
-
}
|
|
1152
|
-
else if (ref && typeof ref === "object") {
|
|
1153
|
-
ref.current = node;
|
|
1154
|
-
}
|
|
1155
|
-
}, [preventDefaultWheelEvent, ref]);
|
|
1156
|
-
useEffect(() => {
|
|
1157
|
-
return () => {
|
|
1158
|
-
const element = inputElementRef.current;
|
|
1159
|
-
if (element) {
|
|
1160
|
-
element.removeEventListener("wheel", preventDefaultWheelEvent);
|
|
1161
|
-
}
|
|
1162
|
-
};
|
|
1163
|
-
}, [preventDefaultWheelEvent]);
|
|
1164
|
-
return jsx(BaseInput, { ref: forwardAndStoreInputRef, type: "number", ...rest, value: rest.value });
|
|
1165
|
-
};
|
|
1166
|
-
NumberInput.displayName = "NumberInput";
|
|
1167
|
-
|
|
1168
1320
|
/**
|
|
1169
1321
|
* The number field component is used for entering numeric values and includes controls for incrementally increasing or decreasing the value.
|
|
1170
1322
|
*
|
|
@@ -1214,7 +1366,7 @@ const NumberField = ({ label, id, tip, helpText, errorMessage, helpAddon, isInva
|
|
|
1214
1366
|
onChange(event);
|
|
1215
1367
|
}
|
|
1216
1368
|
}, [onChange]);
|
|
1217
|
-
return (jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && error) || helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(
|
|
1369
|
+
return (jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && error) || helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(NumberBaseInput, { "aria-labelledby": htmlForId + "-label", id: htmlForId, isInvalid: renderAsInvalid, maxLength: maxLength, onBlur: handleBlur, onChange: handleChange, ref: ref, value: value, ...rest, className: className, dataTestId: dataTestId }) }));
|
|
1218
1370
|
};
|
|
1219
1371
|
NumberField.displayName = "NumberField";
|
|
1220
1372
|
|
|
@@ -1328,11 +1480,10 @@ OptionCard.displayName = "OptionCard";
|
|
|
1328
1480
|
*
|
|
1329
1481
|
* NOTE: If shown with a label, please use the `PasswordField` component instead.
|
|
1330
1482
|
*/
|
|
1331
|
-
const
|
|
1483
|
+
const PasswordBaseInput = ({ ref, fieldSize, ...rest }) => {
|
|
1332
1484
|
const [showPassword, setShowPassword] = useState(false);
|
|
1333
1485
|
return (jsx(BaseInput, { ref: ref, ...rest, actions: jsx("div", { className: cvaActionContainer({ size: fieldSize }), children: jsx(IconButton, { className: cvaActionButton({ size: fieldSize }), icon: jsx(Icon, { name: showPassword ? "EyeSlash" : "Eye", size: "small" }), onClick: () => setShowPassword(prevState => !prevState), size: "small", variant: "secondary" }) }), type: showPassword ? "text" : "password" }));
|
|
1334
1486
|
};
|
|
1335
|
-
PasswordInput.displayName = "PasswordInput";
|
|
1336
1487
|
|
|
1337
1488
|
/**
|
|
1338
1489
|
* Password fields enter a password or other confidential information. Characters are masked as they are typed.
|
|
@@ -1347,119 +1498,10 @@ const PasswordField = ({ id, label, tip, helpText, helpAddon, errorMessage, isIn
|
|
|
1347
1498
|
const handleChange = useCallback((event) => {
|
|
1348
1499
|
onChange?.(event);
|
|
1349
1500
|
}, [onChange]);
|
|
1350
|
-
return (jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && errorMessage) || helpText, htmlFor: htmlFor, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(
|
|
1501
|
+
return (jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && errorMessage) || helpText, htmlFor: htmlFor, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(PasswordBaseInput, { ...rest, "aria-labelledby": htmlFor + "-label", className: className, dataTestId: dataTestId, disabled: rest.readOnly, id: htmlFor, isInvalid: renderAsInvalid, maxLength: maxLength, onChange: handleChange, ref: ref, value: value }) }));
|
|
1351
1502
|
};
|
|
1352
1503
|
PasswordField.displayName = "PasswordField";
|
|
1353
1504
|
|
|
1354
|
-
/**
|
|
1355
|
-
* @param phoneNumber - a phone number as a string
|
|
1356
|
-
* @returns {boolean} true if the phone number starts with a plus sign
|
|
1357
|
-
* @example checkIfPhoneNumberHasPlus("123456789") // false
|
|
1358
|
-
* checkIfPhoneNumberHasPlus("+123456789") // true
|
|
1359
|
-
*/
|
|
1360
|
-
const checkIfPhoneNumberHasPlus = (phoneNumber) => {
|
|
1361
|
-
return phoneNumber.startsWith("+");
|
|
1362
|
-
};
|
|
1363
|
-
/**
|
|
1364
|
-
* @param phoneNumber - a phone number as a string
|
|
1365
|
-
* @returns {string|number} the phone number with a plus sign in front of it
|
|
1366
|
-
* @example getPhoneNumberWithPlus("123456789") // "+123456789"
|
|
1367
|
-
*/
|
|
1368
|
-
const getPhoneNumberWithPlus = (phoneNumber) => {
|
|
1369
|
-
const stringPhoneNumber = phoneNumber.toString();
|
|
1370
|
-
return checkIfPhoneNumberHasPlus(stringPhoneNumber) ? stringPhoneNumber : `+${stringPhoneNumber}`;
|
|
1371
|
-
};
|
|
1372
|
-
/**
|
|
1373
|
-
* Generates a flag emoji based on the given country code.
|
|
1374
|
-
*
|
|
1375
|
-
* @param {string} countryCode - The two-letter country code (ISO 3166-1 alpha-2).
|
|
1376
|
-
* @returns {string} - The corresponding flag emoji for the given country code.
|
|
1377
|
-
* @example getPhoneNumberWithPlus("DK") // "🇩🇰"
|
|
1378
|
-
*/
|
|
1379
|
-
const countryCodeToFlagEmoji = (countryCode) => {
|
|
1380
|
-
let code = countryCode;
|
|
1381
|
-
if (countryCode.startsWith("+")) {
|
|
1382
|
-
code = getCountryAbbreviation(countryCode.substring(1));
|
|
1383
|
-
}
|
|
1384
|
-
else if (!isNaN(Number(countryCode))) {
|
|
1385
|
-
code = getCountryAbbreviation(countryCode);
|
|
1386
|
-
}
|
|
1387
|
-
return isSupportedCountry(code.toUpperCase())
|
|
1388
|
-
? code.toUpperCase().replace(/./g, char => String.fromCodePoint(127397 + char.charCodeAt(0)))
|
|
1389
|
-
: "";
|
|
1390
|
-
};
|
|
1391
|
-
/**
|
|
1392
|
-
* Retrieves the ISO 3166-1 alpha-2 country code associated with a given international calling code.
|
|
1393
|
-
*
|
|
1394
|
-
* @param {string} callCode - The international calling code for a country.
|
|
1395
|
-
* @returns {string} The abbreviation for a country or an empty string if no matching country is found.
|
|
1396
|
-
* @example getCountryAbbreviation("45") // "DK"
|
|
1397
|
-
* @example getCountryAbbreviation("+45") // "DK"
|
|
1398
|
-
*/
|
|
1399
|
-
const getCountryAbbreviation = (callCode) => {
|
|
1400
|
-
let code = callCode;
|
|
1401
|
-
if (callCode.startsWith("+")) {
|
|
1402
|
-
code = callCode.substring(1);
|
|
1403
|
-
}
|
|
1404
|
-
return getCountries().find(c => getCountryCallingCode(c) === code) || "";
|
|
1405
|
-
};
|
|
1406
|
-
|
|
1407
|
-
const DEFAULT_COUNTRY_CODE = undefined;
|
|
1408
|
-
/**
|
|
1409
|
-
* A component for inputting phone numbers with an optional action button for initiating a phone call.
|
|
1410
|
-
*
|
|
1411
|
-
* @param {string} [dataTestId] - The data test ID for the component.
|
|
1412
|
-
* @param {string|number} [value] - The value of the input field. The value should include the country code as well.
|
|
1413
|
-
* @param {boolean} [disabled=false] - Whether the component is disabled or not.
|
|
1414
|
-
* @param {string} [fieldSize="medium"] - The size of the input field.
|
|
1415
|
-
* @param {boolean} [disableAction=false] - Whether the action button is disabled or not.
|
|
1416
|
-
*/
|
|
1417
|
-
const PhoneInput = ({ dataTestId, isInvalid, disabled = false, value, defaultValue, fieldSize = "medium", disableAction = false, onChange, readOnly, onFocus, onBlur, name, ref, ...rest }) => {
|
|
1418
|
-
const [innerValue, setInnerValue] = useState(() => {
|
|
1419
|
-
return (value?.toString() || defaultValue?.toString()) ?? "";
|
|
1420
|
-
});
|
|
1421
|
-
const fieldIsFocused = useRef(false);
|
|
1422
|
-
const [countryCode, setCountryCode] = useState(DEFAULT_COUNTRY_CODE);
|
|
1423
|
-
const determineCountry = useCallback((newValue) => {
|
|
1424
|
-
const asYouType = new AsYouType();
|
|
1425
|
-
asYouType.input(newValue);
|
|
1426
|
-
setCountryCode(asYouType.getCountry());
|
|
1427
|
-
}, []);
|
|
1428
|
-
const handleChange = useCallback(event => {
|
|
1429
|
-
const newValue = event.target.value;
|
|
1430
|
-
event.target.value = parseIncompletePhoneNumber(newValue);
|
|
1431
|
-
onChange?.(event);
|
|
1432
|
-
setInnerValue(newValue);
|
|
1433
|
-
determineCountry(newValue);
|
|
1434
|
-
}, [onChange, determineCountry]);
|
|
1435
|
-
const makePretty = useCallback((newValue) => {
|
|
1436
|
-
const asYouType = new AsYouType();
|
|
1437
|
-
const pretty = asYouType.input(newValue);
|
|
1438
|
-
setInnerValue(pretty);
|
|
1439
|
-
setCountryCode(asYouType.getCountry());
|
|
1440
|
-
}, []);
|
|
1441
|
-
useEffect(() => {
|
|
1442
|
-
if (!fieldIsFocused.current) {
|
|
1443
|
-
makePretty(typeof value === "string" ? value : "");
|
|
1444
|
-
}
|
|
1445
|
-
}, [makePretty, value]);
|
|
1446
|
-
const handleBlur = useCallback(event => {
|
|
1447
|
-
const newValue = event.target.value;
|
|
1448
|
-
makePretty(newValue);
|
|
1449
|
-
onBlur?.(event);
|
|
1450
|
-
fieldIsFocused.current = false;
|
|
1451
|
-
}, [makePretty, onBlur]);
|
|
1452
|
-
const handleFocus = useCallback(event => {
|
|
1453
|
-
const newValue = event.target.value;
|
|
1454
|
-
const noneFormattedValue = parseIncompletePhoneNumber(newValue);
|
|
1455
|
-
setInnerValue(noneFormattedValue);
|
|
1456
|
-
onFocus?.(event);
|
|
1457
|
-
fieldIsFocused.current = true;
|
|
1458
|
-
}, [onFocus]);
|
|
1459
|
-
return (jsx("div", { className: "grid grid-cols-1 gap-2", "data-testid": dataTestId ? `${dataTestId}-container` : null, children: jsx(BaseInput, { actions: !disableAction && innerValue && innerValue.length > 0 ? (jsx(ActionButton, { dataTestId: dataTestId ? `${dataTestId}-phoneIcon` : undefined, disabled: isInvalid, size: fieldSize ?? undefined, type: "PHONE_NUMBER", value: value?.toString() || "" })) : null, dataTestId: dataTestId ? `${dataTestId}-phoneNumberInput` : undefined, disabled: disabled, fieldSize: fieldSize, id: "phoneInput-number", isInvalid: isInvalid, name: name, onBlur: handleBlur, onChange: handleChange, onFocus: handleFocus, prefix: (countryCode && countryCodeToFlagEmoji(countryCode)) || undefined, readOnly: readOnly, ref: ref, type: "tel", value: innerValue, ...rest }) }));
|
|
1460
|
-
};
|
|
1461
|
-
PhoneInput.displayName = "PhoneInput";
|
|
1462
|
-
|
|
1463
1505
|
/**
|
|
1464
1506
|
* Validates a phone number
|
|
1465
1507
|
*/
|
|
@@ -1559,7 +1601,7 @@ const PhoneField = ({ label, id, tip, helpText, isInvalid, errorMessage, value,
|
|
|
1559
1601
|
}
|
|
1560
1602
|
onBlur?.(event);
|
|
1561
1603
|
}, [errorMessage, onBlur, rest.required]);
|
|
1562
|
-
return (jsx(FormGroup, { className: className, dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && error) || helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(
|
|
1604
|
+
return (jsx(FormGroup, { className: className, dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && error) || helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(PhoneBaseInput, { "aria-labelledby": htmlForId + "-label", dataTestId: dataTestId, defaultValue: defaultValue, id: htmlForId, isInvalid: renderAsInvalid, name: name, onBlur: handleBlur, ref: ref, value: value, ...rest }) }));
|
|
1563
1605
|
};
|
|
1564
1606
|
PhoneField.displayName = "PhoneField";
|
|
1565
1607
|
|
|
@@ -1900,14 +1942,6 @@ const hasConsecutiveDays = (schedule) => {
|
|
|
1900
1942
|
return schedule.every(({ day }, index) => day === days[index]);
|
|
1901
1943
|
};
|
|
1902
1944
|
|
|
1903
|
-
/**
|
|
1904
|
-
* A thin wrapper around the `BaseInput` component for text input fields.
|
|
1905
|
-
*
|
|
1906
|
-
* NOTE: If shown with a label, please use the `TextField` component instead.
|
|
1907
|
-
*/
|
|
1908
|
-
const TextInput = ({ ref, ...rest }) => jsx(BaseInput, { ref: ref, type: "text", ...rest });
|
|
1909
|
-
TextInput.displayName = "TextInput";
|
|
1910
|
-
|
|
1911
1945
|
const cvaSearch = cvaMerge([
|
|
1912
1946
|
"shadow-none",
|
|
1913
1947
|
"component-search-border",
|
|
@@ -1939,7 +1973,7 @@ const cvaSearch = cvaMerge([
|
|
|
1939
1973
|
*/
|
|
1940
1974
|
const Search = ({ className, placeholder, value, widenInputOnFocus, hideBorderWhenNotInFocus = false, disabled, onKeyUp, onChange, onFocus, onBlur, name, onClear, dataTestId, autoComplete = "on", loading, inputClassName, iconName = "MagnifyingGlass", style, xMarkRef, ref, ...rest }) => {
|
|
1941
1975
|
const { t } = useTranslation();
|
|
1942
|
-
return (jsx(
|
|
1976
|
+
return (jsx(TextBaseInput, { ...rest, autoComplete: autoComplete, className: cvaSearch({ className, border: hideBorderWhenNotInFocus, widenOnFocus: widenInputOnFocus }), dataTestId: dataTestId, disabled: disabled, inputClassName: inputClassName, name: name, onBlur: onBlur, onChange: onChange, onFocus: onFocus, onKeyUp: onKeyUp, placeholder: placeholder ?? t("search.placeholder"), prefix: loading ? (jsx(Spinner, { centering: "centered", size: rest.fieldSize ?? undefined })) : (jsx(Icon, { name: iconName, size: rest.fieldSize ?? undefined })), ref: ref, suffix:
|
|
1943
1977
|
//only show the clear button if there is a value and the onClear function is provided
|
|
1944
1978
|
onClear && value ? (jsx("button", { className: "flex", "data-testid": dataTestId ? `${dataTestId}_suffix_component` : null, onClick: () => {
|
|
1945
1979
|
onClear();
|
|
@@ -2705,47 +2739,6 @@ const SelectField = ({ ref, ...props }) => {
|
|
|
2705
2739
|
};
|
|
2706
2740
|
SelectField.displayName = "SelectField";
|
|
2707
2741
|
|
|
2708
|
-
const cvaTextArea = cvaMerge([
|
|
2709
|
-
cvaInputBase(),
|
|
2710
|
-
"block",
|
|
2711
|
-
"overflow-auto",
|
|
2712
|
-
"appearance-none",
|
|
2713
|
-
"px-3",
|
|
2714
|
-
"py-2",
|
|
2715
|
-
"text-base",
|
|
2716
|
-
"text-slate-900",
|
|
2717
|
-
"placeholder-slate-400",
|
|
2718
|
-
"w-full",
|
|
2719
|
-
"h-20",
|
|
2720
|
-
"transition",
|
|
2721
|
-
], {
|
|
2722
|
-
variants: {
|
|
2723
|
-
disabled: {
|
|
2724
|
-
true: cvaInputBaseDisabled(),
|
|
2725
|
-
false: "",
|
|
2726
|
-
},
|
|
2727
|
-
invalid: {
|
|
2728
|
-
true: cvaInputBaseInvalid(),
|
|
2729
|
-
false: "",
|
|
2730
|
-
},
|
|
2731
|
-
resize: {
|
|
2732
|
-
both: "resize",
|
|
2733
|
-
vertical: "resize-y",
|
|
2734
|
-
horizontal: "resize-x",
|
|
2735
|
-
none: "resize-none",
|
|
2736
|
-
},
|
|
2737
|
-
},
|
|
2738
|
-
});
|
|
2739
|
-
|
|
2740
|
-
/**
|
|
2741
|
-
* The TextArea is a base component, and should not be used very often.
|
|
2742
|
-
* For most cases the TextAreaField is the correct component.
|
|
2743
|
-
*/
|
|
2744
|
-
const TextArea = ({ id, name, value, rows, disabled, placeholder, readOnly, tabIndex, onChange, onFocus, onBlur, maxLength, resize = "vertical", defaultValue, required, dataTestId, isInvalid, className, ref, ...rest }) => {
|
|
2745
|
-
return (jsx("textarea", { className: cvaTextArea({ className, resize, invalid: isInvalid, disabled }), defaultValue: defaultValue, disabled: disabled, id: id, maxLength: maxLength, name: name, onBlur: onBlur, onFocus: onFocus, placeholder: placeholder, readOnly: readOnly, ref: ref, required: required, rows: rows, tabIndex: tabIndex, value: value, ...rest, "data-testid": dataTestId, onChange: onChange }));
|
|
2746
|
-
};
|
|
2747
|
-
TextArea.displayName = "TextArea";
|
|
2748
|
-
|
|
2749
2742
|
/**
|
|
2750
2743
|
* The TextLengthIndicator component shows a `{length}/{maxLength}` label.
|
|
2751
2744
|
* Used for TextFields to communicate a maximum allowed input length.
|
|
@@ -2771,7 +2764,7 @@ const TextAreaField = ({ id, label, tip, helpText, helpAddon, errorMessage, isIn
|
|
|
2771
2764
|
}
|
|
2772
2765
|
}, [onChange]);
|
|
2773
2766
|
return (jsx(FormGroup, { className: twMerge(className, "grid", "grid-rows-[auto_1fr_auto]"), dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon ||
|
|
2774
|
-
(typeof maxLength === "number" && jsx(TextLengthIndicator, { length: valueLength, maxLength: maxLength })), helpText: errorMessage || helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(
|
|
2767
|
+
(typeof maxLength === "number" && jsx(TextLengthIndicator, { length: valueLength, maxLength: maxLength })), helpText: errorMessage || helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(TextAreaBaseInput, { "aria-labelledby": htmlForId + "-label", id: htmlForId, isInvalid: renderAsInvalid, maxLength: maxLength, ref: ref, value: value, ...rest, className: "h-auto", dataTestId: dataTestId, onChange: handleChange }) }));
|
|
2775
2768
|
};
|
|
2776
2769
|
TextAreaField.displayName = "TextAreaField";
|
|
2777
2770
|
|
|
@@ -2789,7 +2782,7 @@ const TextField = ({ id, label, tip, helpText, helpAddon, errorMessage, isInvali
|
|
|
2789
2782
|
}
|
|
2790
2783
|
}, [onChange]);
|
|
2791
2784
|
return (jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon ||
|
|
2792
|
-
(typeof maxLength === "number" && jsx(TextLengthIndicator, { length: valueLength, maxLength: maxLength })), helpText: (renderAsInvalid && errorMessage) || helpText, htmlFor: htmlFor, isInvalid: renderAsInvalid, isWarning: isWarning, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(
|
|
2785
|
+
(typeof maxLength === "number" && jsx(TextLengthIndicator, { length: valueLength, maxLength: maxLength })), helpText: (renderAsInvalid && errorMessage) || helpText, htmlFor: htmlFor, isInvalid: renderAsInvalid, isWarning: isWarning, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(TextBaseInput, { "aria-labelledby": htmlFor + "-label", id: htmlFor, isInvalid: renderAsInvalid, isWarning: isWarning, maxLength: maxLength, ref: ref, value: value, ...rest, className: className, dataTestId: dataTestId, onChange: handleChange }) }));
|
|
2793
2786
|
};
|
|
2794
2787
|
TextField.displayName = "TextField";
|
|
2795
2788
|
|
|
@@ -3013,12 +3006,11 @@ const validateUrl = (url, required) => {
|
|
|
3013
3006
|
*
|
|
3014
3007
|
* NOTE: If shown with a label, please use the `UrlField` component instead.
|
|
3015
3008
|
*/
|
|
3016
|
-
const
|
|
3009
|
+
const UrlBaseInput = ({ dataTestId, isInvalid, disabled = false, fieldSize = "medium", disableAction = false, value, defaultValue, ref, ...rest }) => {
|
|
3017
3010
|
const [url, setUrl] = useState(value?.toString() || defaultValue?.toString());
|
|
3018
3011
|
const renderAsInvalid = (url && typeof url === "string" && !validateUrlAddress(url)) || isInvalid;
|
|
3019
3012
|
return (jsx(BaseInput, { dataTestId: dataTestId ? `${dataTestId}-url-input` : undefined, disabled: disabled, id: "url-input", isInvalid: renderAsInvalid, onChange: e => setUrl(e.target.value), placeholder: rest.placeholder || "https://www.example.com", ref: ref, type: "url", value: url, ...rest, actions: !disableAction && (jsx(ActionButton, { dataTestId: (dataTestId && `${dataTestId}-url-input-Icon`) || "url-input-action-icon", disabled: renderAsInvalid || Boolean(disabled) || disableAction, size: fieldSize ?? undefined, type: "WEB_ADDRESS", value: url })) }));
|
|
3020
3013
|
};
|
|
3021
|
-
UrlInput.displayName = "UrlField";
|
|
3022
3014
|
|
|
3023
3015
|
/**
|
|
3024
3016
|
* The UrlField component is used to enter url.
|
|
@@ -3040,7 +3032,7 @@ const UrlField = ({ label, id, tip, helpText, errorMessage, helpAddon, className
|
|
|
3040
3032
|
setRenderAsInvalid(!!validateUrl(newValue, rest.required));
|
|
3041
3033
|
onBlur?.(event);
|
|
3042
3034
|
}, [onBlur, rest.required]);
|
|
3043
|
-
return (jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: renderAsInvalid ? error : helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(
|
|
3035
|
+
return (jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: renderAsInvalid ? error : helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(rest.disabled || rest.readOnly) : false, tip: tip, children: jsx(UrlBaseInput, { "aria-labelledby": htmlForId + "-label", id: htmlForId, isInvalid: renderAsInvalid, onBlur: handleBlur, ref: ref, value: value || defaultValue, ...rest, className: className, dataTestId: dataTestId }) }));
|
|
3044
3036
|
};
|
|
3045
3037
|
UrlField.displayName = "UrlField";
|
|
3046
3038
|
|
|
@@ -3156,4 +3148,4 @@ const useZodValidators = () => {
|
|
|
3156
3148
|
*/
|
|
3157
3149
|
setupLibraryTranslations();
|
|
3158
3150
|
|
|
3159
|
-
export { ActionButton, BaseInput, Checkbox, CheckboxField, ColorField, CreatableSelect, CreatableSelectField, DEFAULT_TIME,
|
|
3151
|
+
export { ActionButton, BaseInput, Checkbox, CheckboxField, ColorField, CreatableSelect, CreatableSelectField, DEFAULT_TIME, DateBaseInput, DateField, DropZone, DropZoneDefaultLabel, EMAIL_REGEX, EmailField, FormFieldSelectAdapter, FormGroup, Label, MultiSelectMenuItem, NumberBaseInput, NumberField, OptionCard, PasswordBaseInput, PasswordField, PhoneBaseInput, PhoneField, PhoneFieldWithController, RadioGroup, RadioItem, Schedule, ScheduleVariant, Search, Select, SelectField, SingleSelectMenuItem, TextAreaBaseInput, TextAreaField, TextBaseInput, TextField, TimeRange, TimeRangeField, ToggleSwitch, ToggleSwitchOption, UploadField, UploadInput, UrlField, checkIfPhoneNumberHasPlus, countryCodeToFlagEmoji, cvaAccessoriesContainer, cvaActionButton, cvaActionContainer, cvaInput$1 as cvaInput, cvaInputAddon, cvaInputBase, cvaInputBaseDisabled, cvaInputBaseInvalid, cvaInputField, cvaInputItemPlacementManager, cvaInputPrefix, cvaInputSuffix, cvaLabel, cvaSelect, cvaSelectControl, cvaSelectCounter, cvaSelectDynamicTagContainer, cvaSelectIcon, cvaSelectMenu, cvaSelectMenuList, cvaSelectPrefixSuffix, cvaSelectXIcon, getCountryAbbreviation, getPhoneNumberWithPlus, isInvalidCountryCode, isInvalidPhoneNumber, isValidHEXColor, parseSchedule, phoneErrorMessage, serializeSchedule, useCustomComponents, useGetPhoneValidationRules, usePhoneInput, useZodValidators, validateEmailAddress, validatePhoneNumber, weekDay };
|