@trackunit/react-form-components 1.8.122 → 1.8.123
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 +873 -576
- package/index.esm.js +863 -572
- package/package.json +9 -8
- package/src/components/BaseInput/BaseInput.d.ts +1 -5
- package/src/components/BaseSelect/BaseSelect.d.ts +2 -5
- package/src/components/BaseSelect/BaseSelect.variants.d.ts +26 -16
- package/src/components/BaseSelect/CreatableSelect.d.ts +15 -10
- package/src/components/BaseSelect/custom-components/CounterTag.d.ts +15 -0
- package/src/components/BaseSelect/custom-components/MultiValue.d.ts +18 -0
- package/src/components/BaseSelect/{SelectMenuItem → custom-components/SelectMenuItem}/SelectMenuItem.d.ts +3 -3
- package/src/components/BaseSelect/index.d.ts +2 -1
- package/src/components/BaseSelect/useCreatableSelect.d.ts +10 -0
- package/src/components/BaseSelect/useCustomComponents.d.ts +26 -23
- package/src/components/BaseSelect/useMultiMeasure.d.ts +27 -0
- package/src/components/BaseSelect/useMultiValueOverflow.d.ts +21 -0
- package/src/components/BaseSelect/useSelect.d.ts +22 -39
- package/src/components/DropZone/DropZone.d.ts +1 -1
- package/src/components/FormGroup/FormGroup.d.ts +2 -2
- package/src/components/MultiSelectField/FormFieldSelectAdapterMulti.d.ts +21 -18
- package/src/components/MultiSelectField/MultiSelectField.d.ts +9 -4
- package/src/components/PhoneField/PhoneBaseInput/PhoneBaseInput.d.ts +2 -2
- package/src/components/SelectField/CreatableSelectField.d.ts +5 -3
- package/src/components/SelectField/FormFieldSelectAdapter.d.ts +14 -10
- package/src/components/SelectField/SelectField.d.ts +6 -9
- package/src/components/UrlField/UrlBaseInput/UrlBaseInput.d.ts +2 -1
- package/src/components/storybook-utils/sharedArgTypes.d.ts +0 -54
- package/src/translation.d.ts +2 -2
- package/src/types.d.ts +1 -1
- package/src/components/BaseSelect/TagWithWidth.d.ts +0 -16
- package/src/components/BaseSelect/TagsContainer.d.ts +0 -51
- package/src/components/BaseSelect/useCustomStyles.d.ts +0 -20
package/index.esm.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import { useNamespaceTranslation, registerTranslations, NamespaceTrans } from '@trackunit/i18n-library-translation';
|
|
3
3
|
import { Temporal } from '@js-temporal/polyfill';
|
|
4
|
-
import { IconButton, Icon, Tooltip,
|
|
4
|
+
import { IconButton, Icon, Tooltip, cvaMenu, cvaMenuList, Tag, useIsTextTruncated, MenuItem, useMeasure, useDebounce, Spinner, useMergeRefs, useScrollBlock, Text, Heading, useIsFirstRender } from '@trackunit/react-components';
|
|
5
5
|
import { themeSpacing } from '@trackunit/ui-design-tokens';
|
|
6
|
-
import { forwardRef, useRef, useEffect, useImperativeHandle, useCallback, useState, isValidElement, cloneElement, useLayoutEffect, useMemo, createContext, useContext } from 'react';
|
|
6
|
+
import { forwardRef, useRef, useEffect, useImperativeHandle, useCallback, useState, isValidElement, cloneElement, useLayoutEffect, useReducer, useMemo, createContext, useContext } from 'react';
|
|
7
7
|
import { cvaMerge } from '@trackunit/css-class-variance-utilities';
|
|
8
8
|
import { titleCase } from 'string-ts';
|
|
9
9
|
import { useCopyToClipboard } from 'usehooks-ts';
|
|
@@ -11,10 +11,11 @@ import parsePhoneNumberFromString, { isSupportedCountry, getCountries, getCountr
|
|
|
11
11
|
import ReactSelect, { components } from 'react-select';
|
|
12
12
|
export { default as ValueType } from 'react-select';
|
|
13
13
|
import ReactAsyncSelect from 'react-select/async';
|
|
14
|
+
import { isEqual, omit } from 'es-toolkit';
|
|
15
|
+
import { twMerge } from 'tailwind-merge';
|
|
14
16
|
import ReactAsyncCreatableSelect from 'react-select/async-creatable';
|
|
15
17
|
import ReactCreatableSelect from 'react-select/creatable';
|
|
16
18
|
import { uuidv4, nonNullable } from '@trackunit/shared-utils';
|
|
17
|
-
import { twMerge } from 'tailwind-merge';
|
|
18
19
|
import { Controller } from 'react-hook-form';
|
|
19
20
|
import { z } from 'zod';
|
|
20
21
|
|
|
@@ -48,6 +49,8 @@ var defaultTranslations = {
|
|
|
48
49
|
"schedule.label.allDay": "All Day",
|
|
49
50
|
"schedule.label.day": "Day",
|
|
50
51
|
"search.placeholder": "Search",
|
|
52
|
+
"select.loadingMessage": "Loading...",
|
|
53
|
+
"select.noOptionsMessage": "No options found",
|
|
51
54
|
"urlField.error.INVALID_URL": "Please enter a valid URL",
|
|
52
55
|
"urlField.error.REQUIRED": "The URL is required"
|
|
53
56
|
};
|
|
@@ -433,10 +436,8 @@ const SuffixRenderer = ({ suffix, isInvalid, isWarning, "data-testid": dataTestI
|
|
|
433
436
|
* For specific input types make sure to use the corresponding input component.
|
|
434
437
|
* This is a base used by our other input components such as TextBaseInput, NumberBaseInput, PasswordBaseInput, etc.
|
|
435
438
|
*/
|
|
436
|
-
const BaseInput = ({ className, isInvalid = false, "data-testid": dataTestId, prefix, suffix, addonBefore, addonAfter, actions, fieldSize = "medium",
|
|
439
|
+
const BaseInput = ({ className, isInvalid = false, "data-testid": dataTestId, prefix, suffix, addonBefore, addonAfter, actions, fieldSize = "medium", inputClassName, placeholder, isWarning = false, type, genericAction, style, ref, required = false, readOnly = false, disabled = false, ...rest }) => {
|
|
437
440
|
// Derive final flags
|
|
438
|
-
const renderAsDisabled = Boolean(rest.disabled);
|
|
439
|
-
const renderAsReadonly = Boolean(rest.readOnly);
|
|
440
441
|
const beforeContainerRef = useRef(null);
|
|
441
442
|
const afterContainerRef = useRef(null);
|
|
442
443
|
useEffect(() => {
|
|
@@ -466,19 +467,18 @@ const BaseInput = ({ className, isInvalid = false, "data-testid": dataTestId, pr
|
|
|
466
467
|
const innerRef = useRef(null);
|
|
467
468
|
useImperativeHandle(ref, () => innerRef.current, []);
|
|
468
469
|
return (jsxs("div", { className: cvaInput$1({
|
|
469
|
-
disabled:
|
|
470
|
-
readOnly: renderAsReadonly,
|
|
470
|
+
disabled: Boolean(disabled),
|
|
471
471
|
invalid: isInvalid,
|
|
472
472
|
isWarning,
|
|
473
473
|
size: fieldSize,
|
|
474
474
|
className,
|
|
475
|
-
}), "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, "data-testid": dataTestId, fieldSize: fieldSize, position: "before" }), jsx(PrefixRenderer, { "data-testid": dataTestId, disabled:
|
|
475
|
+
}), "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, "data-testid": dataTestId, fieldSize: fieldSize, position: "before" }), jsx(PrefixRenderer, { "data-testid": dataTestId, disabled: Boolean(disabled), prefix: prefix, type: type })] }), jsx("input", { "aria-required": required, className: cvaInputElement({
|
|
476
476
|
size: fieldSize,
|
|
477
477
|
className: cvaInputItemPlacementManager({ position: "span", className: inputClassName }),
|
|
478
|
-
}), "data-testid": dataTestId, placeholder:
|
|
478
|
+
}), "data-testid": dataTestId, placeholder: Boolean(disabled) ? undefined : placeholder, ref: innerRef, required: required, style: {
|
|
479
479
|
paddingLeft: `calc(var(--before-width, 0px) + ${themeSpacing[2]})`,
|
|
480
480
|
paddingRight: `calc(var(--after-width, 0px) + ${themeSpacing[2]})`,
|
|
481
|
-
}, type: type, ...rest, disabled:
|
|
481
|
+
}, type: type, ...rest, disabled: Boolean(disabled), readOnly: Boolean(readOnly) }), jsxs("div", { className: cvaAccessoriesContainer({ className: cvaInputItemPlacementManager({ position: "after" }) }), "data-testid": dataTestId ? `${dataTestId}-after-container` : undefined, ref: afterContainerRef, children: [jsx(LockReasonRenderer, { "data-testid": dataTestId + "-disabled", lockReason: disabled }), jsx(LockReasonRenderer, { "data-testid": dataTestId + "-readonly", lockReason: Boolean(readOnly) && !Boolean(disabled) ? readOnly : undefined }), jsx(GenericActionsRenderer, { fieldSize: fieldSize, genericAction: genericAction, innerRef: innerRef }), jsx(SuffixRenderer, { "data-testid": dataTestId, disabled: Boolean(disabled), isInvalid: isInvalid, isWarning: isWarning, suffix: suffix }), actions, jsx(AddonRenderer, { addon: addonAfter, "data-testid": dataTestId, fieldSize: fieldSize, position: "after" })] })] }));
|
|
482
482
|
};
|
|
483
483
|
BaseInput.displayName = "BaseInput";
|
|
484
484
|
|
|
@@ -651,7 +651,7 @@ const PhoneBaseInput = ({ "data-testid": dataTestId, isInvalid, disabled = false
|
|
|
651
651
|
onFocus?.(event);
|
|
652
652
|
fieldIsFocused.current = true;
|
|
653
653
|
}, [onFocus]);
|
|
654
|
-
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, { "data-testid": dataTestId ? `${dataTestId}-phoneIcon` : undefined, disabled: isInvalid, size: fieldSize
|
|
654
|
+
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, { "data-testid": dataTestId ? `${dataTestId}-phoneIcon` : undefined, disabled: isInvalid, size: fieldSize, type: "PHONE_NUMBER", value: value?.toString() || "" })) : null, "data-testid": 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 }) }));
|
|
655
655
|
};
|
|
656
656
|
|
|
657
657
|
const cvaTextAreaBaseInput = cvaMerge([
|
|
@@ -701,62 +701,54 @@ const TextAreaBaseInput = ({ id, name, value, rows, disabled, placeholder, readO
|
|
|
701
701
|
*/
|
|
702
702
|
const TextBaseInput = ({ ref, ...rest }) => jsx(BaseInput, { ref: ref, type: "text", ...rest });
|
|
703
703
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
"border-neutral-300",
|
|
710
|
-
"hover:border-neutral-400",
|
|
711
|
-
"hover:bg-neutral-50",
|
|
712
|
-
"bg-white",
|
|
713
|
-
"transition",
|
|
714
|
-
"text-sm",
|
|
715
|
-
"min-h-0",
|
|
716
|
-
], {
|
|
704
|
+
/**
|
|
705
|
+
* The container for the select component — with state styling
|
|
706
|
+
* !This is _the_ place in select styles to manage the aperance of the text and background
|
|
707
|
+
*/
|
|
708
|
+
const cvaSelectContainer = cvaMerge([cvaInputBase(), "relative", "transition", "min-h-0"], {
|
|
717
709
|
variants: {
|
|
718
710
|
fieldSize: {
|
|
719
|
-
small:
|
|
720
|
-
medium:
|
|
721
|
-
large:
|
|
722
|
-
},
|
|
723
|
-
invalid: {
|
|
724
|
-
true: "border border-red-600 text-red-600 hover:border-red-600",
|
|
725
|
-
false: "",
|
|
711
|
+
small: cvaInputBaseSize({ size: "small" }),
|
|
712
|
+
medium: cvaInputBaseSize({ size: "medium" }),
|
|
713
|
+
large: cvaInputBaseSize({ size: "large" }),
|
|
726
714
|
},
|
|
727
715
|
disabled: {
|
|
728
|
-
true:
|
|
716
|
+
true: cvaInputBaseDisabled(),
|
|
729
717
|
false: "",
|
|
730
718
|
},
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
invalid: false,
|
|
734
|
-
disabled: false,
|
|
735
|
-
},
|
|
736
|
-
});
|
|
737
|
-
const cvaSelectControl = cvaMerge([], {
|
|
738
|
-
variants: {
|
|
739
|
-
isDisabled: {
|
|
740
|
-
true: "!bg-neutral-100",
|
|
719
|
+
invalid: {
|
|
720
|
+
true: cvaInputBaseInvalid(),
|
|
741
721
|
false: "",
|
|
742
722
|
},
|
|
743
|
-
|
|
744
|
-
true:
|
|
723
|
+
focused: {
|
|
724
|
+
true: "outline-native",
|
|
745
725
|
false: "",
|
|
746
726
|
},
|
|
747
|
-
|
|
748
|
-
true:
|
|
727
|
+
readOnly: {
|
|
728
|
+
true: cvaInputBaseReadOnly(),
|
|
749
729
|
false: "",
|
|
750
730
|
},
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
731
|
+
defaultVariants: {
|
|
732
|
+
invalid: false,
|
|
733
|
+
disabled: false,
|
|
734
|
+
fieldSize: "medium",
|
|
735
|
+
focused: false,
|
|
736
|
+
readOnly: false,
|
|
737
|
+
},
|
|
756
738
|
},
|
|
757
739
|
});
|
|
758
|
-
const
|
|
759
|
-
|
|
740
|
+
const cvaSelectControl = cvaMerge(["px-3", "gap-x-1", "flex", "h-full"]);
|
|
741
|
+
const cvaSelectLoadingMessage = cvaMerge(["text-neutral-400", "text-center"]);
|
|
742
|
+
const cvaSelectNoOptionsMessage = cvaMerge(["text-neutral-400", "text-center"]);
|
|
743
|
+
const cvaSelectDropdownIconContainer = cvaMerge([
|
|
744
|
+
"flex",
|
|
745
|
+
"cursor-pointer",
|
|
746
|
+
"place-items-center",
|
|
747
|
+
"text-neutral-400",
|
|
748
|
+
"hover:text-neutral-500",
|
|
749
|
+
]);
|
|
750
|
+
const cvaSelectPrefixSuffix = cvaMerge(["flex", "items-center", "text-neutral-400"]);
|
|
751
|
+
const cvaSelectClearIndicator = cvaMerge([
|
|
760
752
|
"flex",
|
|
761
753
|
"cursor-pointer",
|
|
762
754
|
"items-center",
|
|
@@ -764,39 +756,88 @@ const cvaSelectIcon = cvaMerge([
|
|
|
764
756
|
"text-neutral-400",
|
|
765
757
|
"hover:text-neutral-500",
|
|
766
758
|
]);
|
|
767
|
-
const
|
|
759
|
+
const cvaSelectDropdownIndicator = cvaMerge(["transition-transform", "duration-200"], {
|
|
768
760
|
variants: {
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
761
|
+
menuIsOpen: {
|
|
762
|
+
true: "rotate-180",
|
|
763
|
+
false: "rotate-0",
|
|
772
764
|
},
|
|
773
765
|
},
|
|
774
766
|
});
|
|
775
|
-
const
|
|
776
|
-
"mr-2",
|
|
767
|
+
const cvaSelectValueContainer = cvaMerge([
|
|
777
768
|
"flex",
|
|
778
|
-
"
|
|
769
|
+
"flex-nowrap",
|
|
779
770
|
"items-center",
|
|
780
|
-
"
|
|
781
|
-
"
|
|
782
|
-
"
|
|
783
|
-
"
|
|
771
|
+
"grow",
|
|
772
|
+
"overflow-hidden",
|
|
773
|
+
"relative",
|
|
774
|
+
"gap-x-1",
|
|
784
775
|
]);
|
|
785
|
-
|
|
776
|
+
// for the placement of items inside the value container
|
|
777
|
+
const insideValueContainerClasses = cvaMerge(["col-start-1", "col-end-3", "row-start-1", "row-end-2"]);
|
|
778
|
+
const cvaSelectSingleValue = cvaMerge([
|
|
779
|
+
insideValueContainerClasses(),
|
|
780
|
+
"max-w-full",
|
|
781
|
+
"overflow-hidden",
|
|
782
|
+
"text-ellipsis",
|
|
783
|
+
"whitespace-nowrap",
|
|
784
|
+
]);
|
|
785
|
+
const cvaSelectMenu = cvaMerge([cvaMenu({ limitWidth: false }), "absolute", "w-full"], {
|
|
786
786
|
variants: {
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
787
|
+
placement: {
|
|
788
|
+
bottom: "top-[calc(100%+var(--spacing-1))]",
|
|
789
|
+
top: "bottom-[calc(-100%+var(--spacing-1))]",
|
|
790
790
|
},
|
|
791
791
|
},
|
|
792
|
+
defaultVariants: {
|
|
793
|
+
placement: "bottom",
|
|
794
|
+
},
|
|
792
795
|
});
|
|
793
|
-
const
|
|
796
|
+
const cvaSelectMenuList = cvaMerge([cvaMenuList(), "relative", "w-full"]);
|
|
797
|
+
const cvaSelectPlaceholder = cvaMerge(["absolute", "text-neutral-400"]);
|
|
798
|
+
const cvaSelectIndicatorsContainer = cvaMerge(["flex", "items-center", "gap-x-1"]);
|
|
799
|
+
const cvaSelectMultiValue = cvaMerge([], {
|
|
794
800
|
variants: {
|
|
795
|
-
|
|
801
|
+
hidden: {
|
|
802
|
+
true: "!hidden",
|
|
803
|
+
false: "",
|
|
804
|
+
},
|
|
805
|
+
invisible: {
|
|
806
|
+
true: "invisible",
|
|
807
|
+
false: "",
|
|
808
|
+
},
|
|
809
|
+
},
|
|
810
|
+
defaultVariants: {
|
|
811
|
+
hidden: false,
|
|
796
812
|
},
|
|
797
813
|
});
|
|
798
|
-
|
|
799
|
-
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Internal component for displaying a counter badge showing hidden multi-select values.
|
|
817
|
+
* Used for measurement and display when tags overflow the container.
|
|
818
|
+
*/
|
|
819
|
+
const CounterTag = ({ fieldSize, hiddenCount, totalCount, ref, className, "data-testid": dataTestId, }) => {
|
|
820
|
+
if (hiddenCount === 0) {
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
return (jsx(Tag, { className: twMerge("inline-flex shrink-0", className), color: "neutral", "data-testid": dataTestId ?? "select-counter", ref: ref, size: fieldSize === "small" ? "small" : "medium", children: totalCount > hiddenCount ? `+${hiddenCount}` : hiddenCount }));
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Internal component for rendering multi-select values with measurement support.
|
|
828
|
+
* Uses the measurement state to determine if the value should be displayed or hidden based on available width.
|
|
829
|
+
*/
|
|
830
|
+
const MultiValue = ({ data, children, onClose, className, disabled, fieldSize, getOptionPrefix, ref, "data-testid": dataTestId, }) => {
|
|
831
|
+
const optionPrefix = getOptionPrefix ? getOptionPrefix(data) : null;
|
|
832
|
+
const handleOnClose = (e) => {
|
|
833
|
+
if (disabled) {
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
e.stopPropagation();
|
|
837
|
+
onClose?.(e);
|
|
838
|
+
};
|
|
839
|
+
return (jsx(Tag, { className: twMerge(className, "shrink-0", "inline-flex"), color: disabled ? "neutral" : "white", "data-testid": dataTestId, icon: optionPrefix, onClose: disabled ? undefined : handleOnClose, ref: ref, size: fieldSize === "small" ? "small" : "medium", children: jsx("span", { className: "flex items-center gap-1", children: children }) }));
|
|
840
|
+
};
|
|
800
841
|
|
|
801
842
|
/**
|
|
802
843
|
* Shared CVA for binary control items: Checkbox, RadioItem, ToggleSwitchOption
|
|
@@ -1029,13 +1070,13 @@ Checkbox.displayName = "Checkbox";
|
|
|
1029
1070
|
* @param {SelectMenuItemProps} props - The props for the SingleSelectMenuItem
|
|
1030
1071
|
* @returns {ReactElement} SingleSelectMenuItem
|
|
1031
1072
|
*/
|
|
1032
|
-
const SingleSelectMenuItem = ({ label, icon, onClick, selected, focused, "data-testid": dataTestId,
|
|
1073
|
+
const SingleSelectMenuItem = ({ label, icon, onClick, selected = false, focused = false, disabled = false, "data-testid": dataTestId, optionLabelDescription, optionPrefix, fieldSize = "medium", }) => {
|
|
1033
1074
|
return (jsx(MenuItem, { "data-testid": dataTestId, disabled: disabled, fieldSize: fieldSize, focused: focused, label: label, onClick: onClick, optionLabelDescription: optionLabelDescription, optionPrefix: isValidElement(optionPrefix)
|
|
1034
1075
|
? cloneElement(optionPrefix, {
|
|
1035
1076
|
className: "mr-1 flex items-center",
|
|
1036
1077
|
size: "medium",
|
|
1037
1078
|
})
|
|
1038
|
-
: optionPrefix, prefix: icon, selected: selected, suffix: selected ? jsx(Icon, {
|
|
1079
|
+
: optionPrefix, prefix: icon, selected: selected, suffix: selected ? jsx(Icon, { color: "primary", name: "Check", size: fieldSize === "large" ? "medium" : "small" }) : undefined }));
|
|
1039
1080
|
};
|
|
1040
1081
|
/**
|
|
1041
1082
|
* A multi select menu item is a basic wrapper around Menu item designed to be used as a multi value render in Select list
|
|
@@ -1046,7 +1087,7 @@ const SingleSelectMenuItem = ({ label, icon, onClick, selected, focused, "data-t
|
|
|
1046
1087
|
const MultiSelectMenuItem = ({ label, onClick, selected, focused, "data-testid": dataTestId, disabled, optionLabelDescription, optionPrefix, fieldSize, }) => {
|
|
1047
1088
|
return (jsx(MenuItem, { "data-testid": dataTestId, disabled: disabled, fieldSize: fieldSize, focused: focused, label: label, onClick: e => {
|
|
1048
1089
|
e.stopPropagation();
|
|
1049
|
-
onClick
|
|
1090
|
+
onClick?.(e);
|
|
1050
1091
|
}, optionLabelDescription: optionLabelDescription, optionPrefix: isValidElement(optionPrefix)
|
|
1051
1092
|
? cloneElement(optionPrefix, {
|
|
1052
1093
|
className: "mr-1 flex items-center",
|
|
@@ -1058,542 +1099,778 @@ const MultiSelectMenuItem = ({ label, onClick, selected, focused, "data-testid":
|
|
|
1058
1099
|
};
|
|
1059
1100
|
|
|
1060
1101
|
/**
|
|
1061
|
-
*
|
|
1062
|
-
*
|
|
1102
|
+
* Custom hook to measure the geometry of multiple elements indexed by number.
|
|
1103
|
+
* Similar to useMeasure but handles multiple elements efficiently with a single ResizeObserver.
|
|
1063
1104
|
*
|
|
1064
|
-
* @param {
|
|
1065
|
-
* @returns {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
const
|
|
1082
|
-
const
|
|
1083
|
-
const
|
|
1084
|
-
const [counterWidth, setCounterWidth] = useState(0);
|
|
1085
|
-
const [availableSpaceWidth, setAvailableSpaceWidth] = useState(0);
|
|
1086
|
-
const [childrenWidths, setChildrenWidths] = useState([]);
|
|
1087
|
-
const itemsCount = items.length;
|
|
1088
|
-
const dimensions = useResize();
|
|
1089
|
-
const { width: windowWidth } = useDebounce(dimensions, { delay: 100 });
|
|
1105
|
+
* @param {UseMultiMeasureOptions} options - Configuration options
|
|
1106
|
+
* @returns {UseMultiMeasureResult} An object containing `geometries` Map and `getRef` function to create refs
|
|
1107
|
+
* @example
|
|
1108
|
+
* ```tsx
|
|
1109
|
+
* const { geometries, getRef } = useMultiMeasure({
|
|
1110
|
+
* onChange: (geometries) => console.log('Geometries changed', geometries)
|
|
1111
|
+
* });
|
|
1112
|
+
* return items.map((item, index) => (
|
|
1113
|
+
* <div key={index} ref={getRef(index)}>Item {index}</div>
|
|
1114
|
+
* ));
|
|
1115
|
+
* ```
|
|
1116
|
+
*/
|
|
1117
|
+
const useMultiMeasure = ({ skip = false, onChange } = {}) => {
|
|
1118
|
+
const [geometries, setGeometries] = useState(new Map());
|
|
1119
|
+
const [elementCount, setElementCount] = useState(0); // Track element count to trigger useLayoutEffect
|
|
1120
|
+
const elementsRef = useRef(new Map());
|
|
1121
|
+
const observerRef = useRef(null);
|
|
1122
|
+
const onChangeRef = useRef(onChange);
|
|
1123
|
+
const refCallbacksRef = useRef(new Map());
|
|
1124
|
+
const updateScheduledRef = useRef(false);
|
|
1090
1125
|
useEffect(() => {
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1126
|
+
onChangeRef.current = onChange;
|
|
1127
|
+
}, [onChange]);
|
|
1128
|
+
// Update geometries for all currently tracked elements
|
|
1129
|
+
const updateAllGeometries = useCallback(() => {
|
|
1130
|
+
// Batch multiple rapid calls (like during unmount/remount) into a single update
|
|
1131
|
+
if (updateScheduledRef.current) {
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
updateScheduledRef.current = true;
|
|
1135
|
+
queueMicrotask(() => {
|
|
1136
|
+
updateScheduledRef.current = false;
|
|
1137
|
+
const newGeometries = new Map();
|
|
1138
|
+
elementsRef.current.forEach((element, index) => {
|
|
1139
|
+
const rect = element.getBoundingClientRect();
|
|
1140
|
+
newGeometries.set(index, {
|
|
1141
|
+
width: rect.width,
|
|
1142
|
+
height: rect.height,
|
|
1143
|
+
top: rect.top,
|
|
1144
|
+
bottom: rect.bottom,
|
|
1145
|
+
left: rect.left,
|
|
1146
|
+
right: rect.right,
|
|
1147
|
+
x: rect.x,
|
|
1148
|
+
y: rect.y,
|
|
1149
|
+
});
|
|
1150
|
+
});
|
|
1151
|
+
setGeometries(prevGeometries => {
|
|
1152
|
+
if (isEqual(prevGeometries, newGeometries)) {
|
|
1153
|
+
return prevGeometries;
|
|
1154
|
+
}
|
|
1155
|
+
onChangeRef.current?.(newGeometries);
|
|
1156
|
+
return newGeometries;
|
|
1157
|
+
});
|
|
1158
|
+
});
|
|
1159
|
+
}, []);
|
|
1160
|
+
// Measure elements after they're mounted (useLayoutEffect runs synchronously after DOM updates)
|
|
1161
|
+
useLayoutEffect(() => {
|
|
1162
|
+
if (skip || elementsRef.current.size === 0) {
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
// Initial synchronous measurement - this runs in the same render cycle, so tests don't need waitFor
|
|
1166
|
+
const newGeometries = new Map();
|
|
1167
|
+
elementsRef.current.forEach((element, index) => {
|
|
1168
|
+
const rect = element.getBoundingClientRect();
|
|
1169
|
+
newGeometries.set(index, {
|
|
1170
|
+
width: rect.width,
|
|
1171
|
+
height: rect.height,
|
|
1172
|
+
top: rect.top,
|
|
1173
|
+
bottom: rect.bottom,
|
|
1174
|
+
left: rect.left,
|
|
1175
|
+
right: rect.right,
|
|
1176
|
+
x: rect.x,
|
|
1177
|
+
y: rect.y,
|
|
1178
|
+
});
|
|
1179
|
+
});
|
|
1180
|
+
setGeometries(prevGeometries => {
|
|
1181
|
+
if (isEqual(prevGeometries, newGeometries)) {
|
|
1182
|
+
return prevGeometries;
|
|
1099
1183
|
}
|
|
1100
|
-
|
|
1184
|
+
onChangeRef.current?.(newGeometries);
|
|
1185
|
+
return newGeometries;
|
|
1101
1186
|
});
|
|
1102
|
-
}, [
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
.
|
|
1110
|
-
.
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1187
|
+
}, [skip, elementCount]);
|
|
1188
|
+
// Set up ResizeObserver for subsequent changes
|
|
1189
|
+
useEffect(() => {
|
|
1190
|
+
if (skip) {
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
if (observerRef.current !== null) {
|
|
1194
|
+
observerRef.current.disconnect();
|
|
1195
|
+
observerRef.current = null;
|
|
1196
|
+
}
|
|
1197
|
+
const observer = new ResizeObserver(() => {
|
|
1198
|
+
updateAllGeometries();
|
|
1199
|
+
});
|
|
1200
|
+
// Observe all current elements
|
|
1201
|
+
elementsRef.current.forEach(element => {
|
|
1202
|
+
observer.observe(element);
|
|
1203
|
+
});
|
|
1204
|
+
observerRef.current = observer;
|
|
1205
|
+
return () => {
|
|
1206
|
+
if (observerRef.current !== null) {
|
|
1207
|
+
observerRef.current.disconnect();
|
|
1208
|
+
observerRef.current = null;
|
|
1124
1209
|
}
|
|
1125
|
-
|
|
1126
|
-
|
|
1210
|
+
};
|
|
1211
|
+
}, [skip, updateAllGeometries]);
|
|
1212
|
+
// Create stable ref callbacks per index
|
|
1213
|
+
const getRef = useCallback((index) => {
|
|
1214
|
+
const existing = refCallbacksRef.current.get(index);
|
|
1215
|
+
if (existing) {
|
|
1216
|
+
return existing;
|
|
1217
|
+
}
|
|
1218
|
+
const callback = (el) => {
|
|
1219
|
+
if (el) {
|
|
1220
|
+
elementsRef.current.set(index, el);
|
|
1221
|
+
// Observe this new element
|
|
1222
|
+
if (!skip && observerRef.current) {
|
|
1223
|
+
observerRef.current.observe(el);
|
|
1224
|
+
}
|
|
1225
|
+
// Trigger re-render to measure new element
|
|
1226
|
+
setElementCount(elementsRef.current.size);
|
|
1127
1227
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1228
|
+
else {
|
|
1229
|
+
elementsRef.current.delete(index);
|
|
1230
|
+
// Trigger re-render to update measurements
|
|
1231
|
+
setElementCount(elementsRef.current.size);
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
refCallbacksRef.current.set(index, callback);
|
|
1235
|
+
return callback;
|
|
1236
|
+
}, [skip]);
|
|
1237
|
+
return { geometries, getRef };
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
const DEFAULT_STATE = {
|
|
1241
|
+
phase: "uninitialized",
|
|
1242
|
+
visibleCount: 0,
|
|
1243
|
+
totalCount: 0,
|
|
1244
|
+
withCounter: false,
|
|
1245
|
+
multiValueGeometries: new Map(),
|
|
1246
|
+
availableWidthForTags: 0,
|
|
1247
|
+
};
|
|
1248
|
+
const RESERVED_TEXT_INPUT_WIDTH = 40;
|
|
1249
|
+
const GAP_BEFORE_COUNTER = 4;
|
|
1250
|
+
const GAP_BEFORE_TEXT_INPUT = 4;
|
|
1251
|
+
const measurementReducer = (state, action) => {
|
|
1252
|
+
switch (action.type) {
|
|
1253
|
+
case "UPDATE_GEOMETRIES": {
|
|
1254
|
+
// Only reset phase if the count meaningfully changed from the last completed measurement
|
|
1255
|
+
// Don't reset for hide/show cycles (0 size) - only when actual values added/removed
|
|
1256
|
+
const meaningfulCountChange = action.geometries.size > 0 && action.geometries.size !== state.totalCount;
|
|
1257
|
+
return {
|
|
1258
|
+
...state,
|
|
1259
|
+
multiValueGeometries: action.geometries,
|
|
1260
|
+
phase: meaningfulCountChange ? "measuring" : state.phase,
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
case "SET_AVAILABLE_WIDTH_FOR_TAGS": {
|
|
1264
|
+
// Only update if we have a valid width - prevents temporary 0-width states from breaking measurement
|
|
1265
|
+
const newWidth = action.availableWidthForTags > 0 ? action.availableWidthForTags : state.availableWidthForTags;
|
|
1266
|
+
// If width didn't actually change, don't update state
|
|
1267
|
+
if (newWidth === state.availableWidthForTags) {
|
|
1268
|
+
return state;
|
|
1140
1269
|
}
|
|
1141
1270
|
return {
|
|
1142
|
-
|
|
1143
|
-
|
|
1271
|
+
...state,
|
|
1272
|
+
availableWidthForTags: newWidth,
|
|
1273
|
+
phase: "measuring",
|
|
1144
1274
|
};
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1275
|
+
}
|
|
1276
|
+
case "SET_MEASUREMENTS": {
|
|
1277
|
+
return {
|
|
1278
|
+
...state,
|
|
1279
|
+
phase: action.phase,
|
|
1280
|
+
visibleCount: action.visibleCount,
|
|
1281
|
+
totalCount: action.totalCount,
|
|
1282
|
+
withCounter: action.withCounter,
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
case "RESET": {
|
|
1286
|
+
return DEFAULT_STATE;
|
|
1287
|
+
}
|
|
1288
|
+
case "CHANGE_PHASE": {
|
|
1289
|
+
return {
|
|
1290
|
+
...state,
|
|
1291
|
+
phase: action.phase,
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
default: {
|
|
1295
|
+
throw new Error(`${action} is not known`);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
/**
|
|
1300
|
+
* Hook to manage multi-value overflow detection and rendering.
|
|
1301
|
+
* Measures which values fit in the container and determines when to show a counter.
|
|
1302
|
+
*/
|
|
1303
|
+
const useMultiValueOverflow = ({ skip = false }) => {
|
|
1304
|
+
const [measurementState, dispatch] = useReducer(measurementReducer, DEFAULT_STATE);
|
|
1305
|
+
const { ref: setValueContainerRef, geometry: containerGeometry } = useMeasure({
|
|
1306
|
+
skip: skip || measurementState.multiValueGeometries.size === 0,
|
|
1307
|
+
});
|
|
1308
|
+
const { ref: setCounterRef } = useMeasure({
|
|
1309
|
+
skip: skip || measurementState.multiValueGeometries.size === 0,
|
|
1310
|
+
});
|
|
1311
|
+
const { ref: setFakeCounterRef, geometry: fakeCounterGeometry } = useMeasure({
|
|
1312
|
+
skip: skip || measurementState.multiValueGeometries.size === 0,
|
|
1313
|
+
});
|
|
1314
|
+
const [menuElement, setMenuElement] = useState(null);
|
|
1315
|
+
const setMenuRef = useCallback((element) => {
|
|
1316
|
+
setMenuElement(element);
|
|
1317
|
+
}, []);
|
|
1318
|
+
const menuIsOpen = menuElement !== null;
|
|
1319
|
+
const { getRef: setGeometryRef } = useMultiMeasure({
|
|
1320
|
+
skip,
|
|
1321
|
+
onChange: geometries => {
|
|
1322
|
+
dispatch({ type: "UPDATE_GEOMETRIES", geometries });
|
|
1323
|
+
},
|
|
1324
|
+
});
|
|
1325
|
+
const availableWidthForTags = useMemo(() => {
|
|
1326
|
+
if (!containerGeometry || skip) {
|
|
1327
|
+
return 0;
|
|
1328
|
+
}
|
|
1329
|
+
const reservedInputWidth = menuIsOpen ? RESERVED_TEXT_INPUT_WIDTH : 0;
|
|
1330
|
+
let availableWidth = containerGeometry.width - reservedInputWidth - GAP_BEFORE_TEXT_INPUT;
|
|
1331
|
+
if (measurementState.phase === "measuring-with-counter") {
|
|
1332
|
+
availableWidth -= (fakeCounterGeometry?.width ?? 0) + GAP_BEFORE_COUNTER;
|
|
1333
|
+
}
|
|
1334
|
+
return Math.max(0, availableWidth);
|
|
1335
|
+
}, [containerGeometry, fakeCounterGeometry?.width, measurementState.phase, menuIsOpen, skip]);
|
|
1336
|
+
useDebounce(availableWidthForTags, {
|
|
1337
|
+
onBounce: debouncedAvailableWidthForTags => {
|
|
1338
|
+
dispatch({ type: "SET_AVAILABLE_WIDTH_FOR_TAGS", availableWidthForTags: debouncedAvailableWidthForTags });
|
|
1339
|
+
},
|
|
1340
|
+
delay: 60,
|
|
1341
|
+
});
|
|
1342
|
+
// Measurement logic
|
|
1343
|
+
useLayoutEffect(() => {
|
|
1344
|
+
if (skip) {
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
const hasInvalidMultiItemGeometry = Array.from(measurementState.multiValueGeometries.values()).some(
|
|
1348
|
+
// Don't measure if any geometry has invalid dimensions (not yet laid out)
|
|
1349
|
+
geo => geo.width === 0 || geo.height === 0);
|
|
1350
|
+
if (!containerGeometry ||
|
|
1351
|
+
containerGeometry.width === 0 ||
|
|
1352
|
+
measurementState.multiValueGeometries.size === 0 ||
|
|
1353
|
+
measurementState.availableWidthForTags <= 0 ||
|
|
1354
|
+
hasInvalidMultiItemGeometry) {
|
|
1355
|
+
// Early return
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
const totalCount = measurementState.multiValueGeometries.size;
|
|
1359
|
+
switch (measurementState.phase) {
|
|
1360
|
+
case "uninitialized": {
|
|
1361
|
+
dispatch({ type: "CHANGE_PHASE", phase: "measuring" });
|
|
1362
|
+
break;
|
|
1363
|
+
}
|
|
1364
|
+
case "measuring": {
|
|
1365
|
+
const visibleCount = getVisibleCountFromGeometries({
|
|
1366
|
+
geometries: measurementState.multiValueGeometries,
|
|
1367
|
+
availableWidth: measurementState.availableWidthForTags,
|
|
1368
|
+
containerX: containerGeometry.x,
|
|
1369
|
+
totalCount,
|
|
1370
|
+
});
|
|
1371
|
+
if (visibleCount === totalCount) {
|
|
1372
|
+
dispatch({ type: "SET_MEASUREMENTS", totalCount, visibleCount, withCounter: false, phase: "complete" });
|
|
1373
|
+
}
|
|
1374
|
+
else {
|
|
1375
|
+
dispatch({
|
|
1376
|
+
type: "SET_MEASUREMENTS",
|
|
1377
|
+
totalCount,
|
|
1378
|
+
visibleCount,
|
|
1379
|
+
withCounter: true,
|
|
1380
|
+
phase: "measuring-with-counter",
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
break;
|
|
1384
|
+
}
|
|
1385
|
+
case "measuring-with-counter": {
|
|
1386
|
+
if (!fakeCounterGeometry || fakeCounterGeometry.width === 0) {
|
|
1387
|
+
// Wait another render cycle for the fake counter to be measured
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
const visibleCount = getVisibleCountFromGeometries({
|
|
1391
|
+
geometries: measurementState.multiValueGeometries,
|
|
1392
|
+
availableWidth: measurementState.availableWidthForTags,
|
|
1393
|
+
containerX: containerGeometry.x,
|
|
1394
|
+
totalCount,
|
|
1395
|
+
});
|
|
1396
|
+
dispatch({ type: "SET_MEASUREMENTS", totalCount, visibleCount, withCounter: true, phase: "complete" });
|
|
1397
|
+
break;
|
|
1398
|
+
}
|
|
1399
|
+
case "complete": {
|
|
1400
|
+
break;
|
|
1401
|
+
}
|
|
1402
|
+
default: {
|
|
1403
|
+
throw new Error(`${measurementState.phase} is not known`);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
}, [
|
|
1407
|
+
containerGeometry,
|
|
1408
|
+
containerGeometry?.width,
|
|
1409
|
+
fakeCounterGeometry,
|
|
1410
|
+
fakeCounterGeometry?.width,
|
|
1411
|
+
measurementState,
|
|
1412
|
+
measurementState.phase,
|
|
1413
|
+
measurementState.totalCount,
|
|
1414
|
+
measurementState.visibleCount,
|
|
1415
|
+
skip,
|
|
1416
|
+
]);
|
|
1417
|
+
// Store current values in a ref so getter functions can stay stable
|
|
1418
|
+
const valuesRef = useRef({
|
|
1419
|
+
visibleCount: measurementState.visibleCount,
|
|
1420
|
+
totalCount: measurementState.totalCount,
|
|
1421
|
+
withCounter: measurementState.withCounter,
|
|
1422
|
+
isComplete: measurementState.phase === "complete",
|
|
1423
|
+
});
|
|
1424
|
+
// Use state setter to trigger rerenders when values change
|
|
1425
|
+
// The setter itself is stable, but calling it triggers a rerender
|
|
1426
|
+
const [, setVersion] = useState(0);
|
|
1427
|
+
// Update ref and trigger rerender when state changes
|
|
1428
|
+
useEffect(() => {
|
|
1429
|
+
valuesRef.current = {
|
|
1430
|
+
visibleCount: measurementState.visibleCount,
|
|
1431
|
+
totalCount: measurementState.totalCount,
|
|
1432
|
+
withCounter: measurementState.withCounter,
|
|
1433
|
+
isComplete: measurementState.phase === "complete",
|
|
1434
|
+
};
|
|
1435
|
+
// Trigger rerender so components can read new values from getters
|
|
1436
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
1437
|
+
setVersion(prev => prev + 1);
|
|
1438
|
+
}, [
|
|
1439
|
+
measurementState.visibleCount,
|
|
1440
|
+
measurementState.totalCount,
|
|
1441
|
+
measurementState.withCounter,
|
|
1442
|
+
measurementState.phase,
|
|
1443
|
+
]);
|
|
1444
|
+
// Create stable getter functions that read from the ref
|
|
1445
|
+
// These functions stay stable (same reference) but always return the latest values
|
|
1446
|
+
const getVisibleCount = useCallback(() => valuesRef.current.visibleCount, []);
|
|
1447
|
+
const getTotalCount = useCallback(() => valuesRef.current.totalCount, []);
|
|
1448
|
+
const getCounterWidth = useCallback(() => valuesRef.current.withCounter, []);
|
|
1449
|
+
const getIsComplete = useCallback(() => valuesRef.current.isComplete, []);
|
|
1450
|
+
return useMemo(() => ({
|
|
1451
|
+
setValueContainerRef,
|
|
1452
|
+
setCounterRef,
|
|
1453
|
+
setFakeCounterRef,
|
|
1454
|
+
setGeometryRef,
|
|
1455
|
+
setMenuRef,
|
|
1456
|
+
getVisibleCount,
|
|
1457
|
+
getTotalCount,
|
|
1458
|
+
getCounterWidth,
|
|
1459
|
+
getIsComplete,
|
|
1460
|
+
}), [
|
|
1461
|
+
setValueContainerRef,
|
|
1462
|
+
setCounterRef,
|
|
1463
|
+
setFakeCounterRef,
|
|
1464
|
+
setGeometryRef,
|
|
1465
|
+
setMenuRef,
|
|
1466
|
+
getVisibleCount,
|
|
1467
|
+
getTotalCount,
|
|
1468
|
+
getCounterWidth,
|
|
1469
|
+
getIsComplete,
|
|
1470
|
+
]);
|
|
1471
|
+
};
|
|
1472
|
+
const getVisibleCountFromGeometries = ({ geometries, availableWidth, containerX, totalCount, }) => {
|
|
1473
|
+
const firstGeometry = geometries.get(0);
|
|
1474
|
+
if (firstGeometry === undefined) {
|
|
1475
|
+
throw new Error("First multiValue geometry is not available, this should never happen");
|
|
1476
|
+
}
|
|
1477
|
+
let visibleCount = 0;
|
|
1478
|
+
for (let i = 0; i < totalCount; i++) {
|
|
1479
|
+
const geometry = geometries.get(i);
|
|
1480
|
+
if (geometry === undefined) {
|
|
1481
|
+
throw new Error(`MultiValue geometry at index ${i} is not available, this should never happen`);
|
|
1482
|
+
}
|
|
1483
|
+
const distanceFromFirst = geometry.x + geometry.width - containerX;
|
|
1484
|
+
const fits = distanceFromFirst <= availableWidth;
|
|
1485
|
+
if (fits) {
|
|
1486
|
+
visibleCount = i + 1; //+1 because we want to count, not get the index
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
return visibleCount;
|
|
1151
1490
|
};
|
|
1152
1491
|
|
|
1153
1492
|
/**
|
|
1154
1493
|
* A hook to retrieve components override object.
|
|
1155
1494
|
* 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.
|
|
1156
1495
|
*
|
|
1157
|
-
* @template
|
|
1158
|
-
* @template
|
|
1159
|
-
* @
|
|
1160
|
-
* @param {
|
|
1161
|
-
* @
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
* @returns {Partial<SelectComponents<Option, boolean, GroupBase<Option>>> | undefined} components object to override react-select default components
|
|
1167
|
-
*/
|
|
1168
|
-
const useCustomComponents = ({ componentsProps, disabled, readOnly, setMenuIsEnabled, "data-testid": dataTestId, maxSelectedDisplayCount, prefix, hasError, fieldSize, getOptionLabelDescription, getOptionPrefix, }) => {
|
|
1496
|
+
* @template TOption
|
|
1497
|
+
* @template TIsMulti
|
|
1498
|
+
* @template TGroup
|
|
1499
|
+
* @param {CustomComponentsProps<TOption>} props - The custom components props
|
|
1500
|
+
* @returns {Partial<SelectComponents<TOption, TIsMulti, TGroup>>} components object to override react-select default components
|
|
1501
|
+
*/
|
|
1502
|
+
const useCustomComponents = ({ disabled, readOnly, "data-testid": dataTestId, prefix, hasError, fieldSize = "medium", getOptionLabelDescription, getOptionPrefix, className, isMulti, //prefer using the component prop (ala. selectValueContainer.isMulti) inside of customComponents instead of this one.
|
|
1503
|
+
autoComplete, // see https://github.com/JedWatson/react-select/issues/758
|
|
1504
|
+
}) => {
|
|
1169
1505
|
const [t] = useTranslation();
|
|
1506
|
+
const { setValueContainerRef, setCounterRef, setFakeCounterRef, setGeometryRef, setMenuRef, getVisibleCount, getTotalCount, getCounterWidth, getIsComplete, } = useMultiValueOverflow({ skip: !isMulti });
|
|
1507
|
+
const interactable = useMemo(() => !Boolean(disabled) && !Boolean(readOnly), [disabled, readOnly]);
|
|
1170
1508
|
// perhaps it should not be wrap in memo (causing some issues with opening and closing on mobiles)
|
|
1509
|
+
// Component functions stay stable (reading from refs), so react-select won't lose focus
|
|
1171
1510
|
const customComponents = useMemo(() => {
|
|
1172
1511
|
return {
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
},
|
|
1197
|
-
disabled: disabled,
|
|
1198
|
-
Icon: optionPrefix,
|
|
1199
|
-
};
|
|
1200
|
-
})
|
|
1201
|
-
: [], postFix: searchInput, preFix: placeholderElement ? jsx("span", { className: "absolute", children: placeholderElement }) : null, width: "100%" })) : (jsxs(Fragment, { children: [tags
|
|
1202
|
-
? tags.slice(0, maxSelectedDisplayCount).map(({ props: tagProps }) => {
|
|
1203
|
-
return (jsx(Tag, { className: "inline-flex shrink-0", color: disabled ? "unknown" : "primary", "data-testid": tagProps.children ? `${tagProps.children.toString()}-tag` : undefined, onClose: e => {
|
|
1204
|
-
e.stopPropagation();
|
|
1205
|
-
setMenuIsEnabled(false);
|
|
1206
|
-
tagProps.removeProps.onClick && tagProps.removeProps.onClick(e);
|
|
1207
|
-
}, children: tagProps.children }, tagProps.children?.toString()));
|
|
1208
|
-
})
|
|
1209
|
-
: null, tags && tags.length > maxSelectedDisplayCount ? (jsxs(Tag, { color: "neutral", "data-testid": "counter-tag", children: ["+", tags.length - maxSelectedDisplayCount] })) : null, searchInput, placeholderElement] })) }));
|
|
1210
|
-
}
|
|
1211
|
-
return (jsx(Fragment, { children: jsx(components.ValueContainer, { ...props, isDisabled: props.selectProps.isDisabled, children: props.children }) }));
|
|
1512
|
+
SelectContainer: selectContainer => {
|
|
1513
|
+
return (jsx(components.SelectContainer, { ...selectContainer, className: cvaSelectContainer({
|
|
1514
|
+
invalid: hasError,
|
|
1515
|
+
fieldSize,
|
|
1516
|
+
disabled: Boolean(disabled),
|
|
1517
|
+
className: [className, selectContainer.className],
|
|
1518
|
+
readOnly: Boolean(readOnly),
|
|
1519
|
+
focused: selectContainer.isFocused,
|
|
1520
|
+
}), getStyles: getNoStyles, innerProps: {
|
|
1521
|
+
...selectContainer.innerProps,
|
|
1522
|
+
...(dataTestId ? { "data-testid": dataTestId } : {}),
|
|
1523
|
+
}, children: selectContainer.children }));
|
|
1524
|
+
},
|
|
1525
|
+
Control: selectControl => {
|
|
1526
|
+
return (jsxs(components.Control, { ...selectControl, className: cvaSelectControl({
|
|
1527
|
+
className: selectControl.className,
|
|
1528
|
+
}), getStyles: getNoStyles, innerProps: Boolean(readOnly)
|
|
1529
|
+
? // We omit the onMouseDown and onTouchEnd events to allow text selection
|
|
1530
|
+
omit(selectControl.innerProps, ["onMouseDown", "onTouchEnd"])
|
|
1531
|
+
: selectControl.innerProps, children: [prefix !== undefined ? (jsx("div", { className: cvaSelectPrefixSuffix(), "data-testid": dataTestId ? `${dataTestId}-prefix` : null, children: prefix })) : null, selectControl.children, typeof disabled === "object" ? (jsx("div", { className: cvaSelectPrefixSuffix(), "data-testid": dataTestId ? `${dataTestId}-disabled-locked` : null, children: jsx(InputLockReasonTooltip, { ...disabled }) })) : null, typeof readOnly === "object" && !Boolean(disabled) ? (jsx("div", { className: cvaSelectPrefixSuffix(), "data-testid": dataTestId ? `${dataTestId}-readonly-locked` : null, children: jsx(InputLockReasonTooltip, { ...readOnly }) })) : null] }));
|
|
1532
|
+
},
|
|
1533
|
+
Placeholder: selectPlaceholder => {
|
|
1534
|
+
return (jsx(components.Placeholder, { ...selectPlaceholder, className: cvaSelectPlaceholder({ className: selectPlaceholder.className }), getStyles: getNoStyles, children: selectPlaceholder.children }));
|
|
1212
1535
|
},
|
|
1213
|
-
LoadingIndicator:
|
|
1214
|
-
return jsx(Spinner, { centering: "vertically",
|
|
1536
|
+
LoadingIndicator: selectLoadingIndicator => {
|
|
1537
|
+
return jsx(Spinner, { ...selectLoadingIndicator.innerProps, centering: "vertically", size: "small" });
|
|
1215
1538
|
},
|
|
1216
|
-
DropdownIndicator:
|
|
1217
|
-
|
|
1218
|
-
|
|
1539
|
+
DropdownIndicator: selectDropdownIndicator => {
|
|
1540
|
+
if (!interactable || selectDropdownIndicator.selectProps.isLoading) {
|
|
1541
|
+
return null;
|
|
1542
|
+
}
|
|
1543
|
+
return (jsx(components.DropdownIndicator, { ...selectDropdownIndicator, className: cvaSelectDropdownIconContainer({ className: selectDropdownIndicator.className }), getStyles: getNoStyles, children: jsx(Icon, { className: cvaSelectDropdownIndicator({ menuIsOpen: selectDropdownIndicator.selectProps.menuIsOpen }), name: "ChevronDown", size: "medium" }) }));
|
|
1544
|
+
},
|
|
1545
|
+
// --------------------------------
|
|
1546
|
+
// Inside Popover👇
|
|
1547
|
+
// --------------------------------
|
|
1548
|
+
ValueContainer: selectValueContainer => {
|
|
1549
|
+
// Read latest overflow values using getters (functions stay stable)
|
|
1550
|
+
const currentVisibleCount = getVisibleCount();
|
|
1551
|
+
const currentTotalCount = getTotalCount();
|
|
1552
|
+
const currentWithCounter = getCounterWidth();
|
|
1553
|
+
const currentIsComplete = getIsComplete();
|
|
1554
|
+
return (jsx(components.ValueContainer, { ...selectValueContainer, className: cvaSelectValueContainer({ className: selectValueContainer.className }), getStyles: getNoStyles, innerProps: {
|
|
1555
|
+
...selectValueContainer.innerProps,
|
|
1556
|
+
ref: useMergeRefs([setValueContainerRef, selectValueContainer.innerProps?.ref]),
|
|
1557
|
+
}, children: selectValueContainer.isMulti ? (jsxs(Fragment, { children: [getPlaceholderElement(selectValueContainer.children), currentWithCounter && !currentIsComplete ? (
|
|
1558
|
+
// Render the test-counter-tag in the beginning to make sure it's included in the calculation
|
|
1559
|
+
// will be removed when the calculation is complete
|
|
1560
|
+
jsx(CounterTag, { className: "invisible" // Is invisible because we're only putting it here to measure the width
|
|
1561
|
+
, "data-testid": "fake-multiselect-counter", fieldSize: fieldSize, hiddenCount: currentTotalCount - currentVisibleCount, ref: setFakeCounterRef, totalCount: currentTotalCount })) : null, getMultiValueComponents(selectValueContainer.children), currentWithCounter && currentIsComplete ? (
|
|
1562
|
+
// This is the actual tag that will be visible in the UI
|
|
1563
|
+
jsx(CounterTag, { "data-testid": dataTestId ? `${dataTestId}-multiselect-counter` : "multiselect-counter", fieldSize: fieldSize, hiddenCount: currentTotalCount - currentVisibleCount, ref: setCounterRef, totalCount: currentTotalCount })) : null, getInputComponent(selectValueContainer.children)] })) : (selectValueContainer.children) }));
|
|
1219
1564
|
},
|
|
1220
1565
|
IndicatorSeparator: () => null,
|
|
1221
|
-
|
|
1222
|
-
|
|
1566
|
+
Input: selectInput => {
|
|
1567
|
+
return jsx(components.Input, { ...selectInput, autoComplete: autoComplete });
|
|
1568
|
+
},
|
|
1569
|
+
IndicatorsContainer: selectIndicatorsContainer => {
|
|
1570
|
+
return (jsx(components.IndicatorsContainer, { ...selectIndicatorsContainer, className: cvaSelectIndicatorsContainer({ className: selectIndicatorsContainer.className }), getStyles: getNoStyles, children: selectIndicatorsContainer.children }));
|
|
1571
|
+
},
|
|
1572
|
+
ClearIndicator: selectClearIndicator => {
|
|
1573
|
+
if (Boolean(disabled)) {
|
|
1223
1574
|
return null;
|
|
1224
1575
|
}
|
|
1225
|
-
return (jsx(components.ClearIndicator, { ...
|
|
1226
|
-
...props.innerProps,
|
|
1227
|
-
onMouseDown: e => {
|
|
1228
|
-
e.preventDefault();
|
|
1229
|
-
},
|
|
1230
|
-
}, children: jsx("div", { className: cvaSelectXIcon(), "data-testid": dataTestId ? `${dataTestId}-XMarkIcon` : null, onClick: props.clearValue, children: jsx(Icon, { ariaLabel: t("clearIndicator.icon.tooltip.clearAll"), name: "XCircle", size: "medium" }) }) }));
|
|
1576
|
+
return (jsx(components.ClearIndicator, { ...selectClearIndicator, className: cvaSelectClearIndicator({ className: selectClearIndicator.className }), getStyles: getNoStyles, children: jsx(Icon, { ariaLabel: t("clearIndicator.icon.tooltip.clearAll"), "data-testid": `${dataTestId}-XMarkIcon`, name: "XCircle", size: "medium" }) }));
|
|
1231
1577
|
},
|
|
1232
|
-
|
|
1233
|
-
return (jsx(components.
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1578
|
+
LoadingMessage: selectLoadingMessage => {
|
|
1579
|
+
return (jsx(components.LoadingMessage, { ...selectLoadingMessage, className: cvaSelectLoadingMessage({ className: selectLoadingMessage.className }), getStyles: getNoStyles, children: t("select.loadingMessage") }));
|
|
1580
|
+
},
|
|
1581
|
+
NoOptionsMessage: selectNoOptionsMessage => {
|
|
1582
|
+
return (jsx(components.NoOptionsMessage, { ...selectNoOptionsMessage, className: cvaSelectNoOptionsMessage({ className: selectNoOptionsMessage.className }), getStyles: getNoStyles, children: t("select.noOptionsMessage") }));
|
|
1583
|
+
},
|
|
1584
|
+
SingleValue: selectSingleValue => {
|
|
1585
|
+
const optionPrefix = getOptionPrefix ? getOptionPrefix(selectSingleValue.data) : null;
|
|
1586
|
+
return (jsx(components.SingleValue, { ...selectSingleValue, className: cvaSelectSingleValue({
|
|
1587
|
+
className: selectSingleValue.className,
|
|
1588
|
+
}), getStyles: getNoStyles, children: jsxs("div", { className: "flex items-center gap-1", "data-testid": dataTestId + "-singleValue", children: [optionPrefix !== null ? optionPrefix : null, selectSingleValue.children, getOptionLabelDescription && getOptionLabelDescription(selectSingleValue.data) ? (jsxs("span", { className: "ml-1 text-neutral-400", children: ["(", getOptionLabelDescription(selectSingleValue.data), ")"] })) : null] }) }));
|
|
1238
1589
|
},
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1590
|
+
MultiValueContainer: ({ children }) => children, // Just pass on the children
|
|
1591
|
+
MultiValueRemove: () => null, // is built-in to the MultiValue (tag) component
|
|
1592
|
+
MultiValueLabel: ({ children }) => children, // Just pass on the children
|
|
1593
|
+
MultiValue: selectMultiValue => {
|
|
1594
|
+
// Read latest overflow values using getters (functions stay stable)
|
|
1595
|
+
const currentVisibleCount = getVisibleCount();
|
|
1596
|
+
const currentWithCounter = getCounterWidth();
|
|
1597
|
+
const currentIsComplete = getIsComplete();
|
|
1598
|
+
const index = selectMultiValue.index;
|
|
1599
|
+
return (jsx(components.MultiValue, { ...selectMultiValue, getStyles: getNoStyles, children: jsx(MultiValue, { className: cvaSelectMultiValue({
|
|
1600
|
+
hidden: currentIsComplete && index + 1 > currentVisibleCount, // Hide if doesn't fit
|
|
1601
|
+
invisible: currentWithCounter && !currentIsComplete, // Make invisible if measuring with counter. When there's a counter, it would otherwise briefly change layout to make room for the "fake" counter
|
|
1602
|
+
}), data: selectMultiValue.data, "data-testid": dataTestId ? `${dataTestId}-multiValue-${index}` : undefined, disabled: Boolean(disabled), fieldSize: fieldSize, getOptionPrefix: getOptionPrefix, onClose: selectMultiValue.removeProps.onClick, ref: setGeometryRef(index), children: selectMultiValue.children }) }));
|
|
1242
1603
|
},
|
|
1243
|
-
Menu:
|
|
1244
|
-
return (jsx(components.Menu, { ...
|
|
1604
|
+
Menu: selectMenu => {
|
|
1605
|
+
return (jsx(components.Menu, { ...selectMenu, className: cvaSelectMenu({ className: selectMenu.className, placement: selectMenu.placement }), getStyles: getNoStyles, innerProps: {
|
|
1606
|
+
...selectMenu.innerProps,
|
|
1607
|
+
ref: useMergeRefs([setMenuRef, selectMenu.innerProps.ref]),
|
|
1608
|
+
} }));
|
|
1245
1609
|
},
|
|
1246
|
-
|
|
1247
|
-
return (jsx(components.
|
|
1610
|
+
MenuPortal: selectMenuPortal => {
|
|
1611
|
+
return (jsx(components.MenuPortal, { ...selectMenuPortal, className: "!z-overlay", children: selectMenuPortal.children }));
|
|
1248
1612
|
},
|
|
1249
|
-
MenuList:
|
|
1250
|
-
return (jsx(components.MenuList, {
|
|
1251
|
-
|
|
1613
|
+
MenuList: selectMenuList => {
|
|
1614
|
+
return (jsx(components.MenuList, { className: cvaSelectMenuList({
|
|
1615
|
+
className: selectMenuList.className,
|
|
1616
|
+
}), ...selectMenuList, getStyles: getNoStyles, innerProps: {
|
|
1617
|
+
...selectMenuList.innerProps,
|
|
1252
1618
|
onScroll: e => {
|
|
1253
1619
|
const listEl = e.currentTarget;
|
|
1254
1620
|
if (listEl.scrollTop + listEl.clientHeight >= listEl.scrollHeight &&
|
|
1255
|
-
|
|
1256
|
-
/Firefox/.test(navigator.userAgent)
|
|
1257
|
-
|
|
1258
|
-
|
|
1621
|
+
selectMenuList.selectProps.onMenuScrollToBottom) {
|
|
1622
|
+
if (/Firefox/.test(navigator.userAgent)) {
|
|
1623
|
+
selectMenuList.selectProps.onMenuScrollToBottom(new WheelEvent("scroll"));
|
|
1624
|
+
}
|
|
1625
|
+
else {
|
|
1626
|
+
selectMenuList.selectProps.onMenuScrollToBottom(new TouchEvent(""));
|
|
1627
|
+
}
|
|
1259
1628
|
}
|
|
1260
1629
|
},
|
|
1261
|
-
}, children:
|
|
1630
|
+
}, children: selectMenuList.children }));
|
|
1262
1631
|
},
|
|
1263
|
-
Option:
|
|
1632
|
+
Option: selectOption => {
|
|
1264
1633
|
const componentProps = {
|
|
1265
|
-
label:
|
|
1266
|
-
focused:
|
|
1267
|
-
selected:
|
|
1268
|
-
onClick:
|
|
1634
|
+
label: selectOption.label,
|
|
1635
|
+
focused: selectOption.isFocused,
|
|
1636
|
+
selected: selectOption.isSelected,
|
|
1637
|
+
onClick: selectOption.innerProps.onClick,
|
|
1269
1638
|
};
|
|
1270
|
-
return (jsx(components.Option, { ...
|
|
1271
|
-
...
|
|
1639
|
+
return (jsx(components.Option, { ...selectOption, innerProps: {
|
|
1640
|
+
...selectOption.innerProps,
|
|
1272
1641
|
role: "option",
|
|
1273
|
-
|
|
1274
|
-
}, children: props.isMulti ? (jsx(MultiSelectMenuItem, { ...componentProps, "data-testid": typeof props.label === "string" ? props.label : undefined, disabled: disabled, fieldSize: fieldSize, optionLabelDescription: getOptionLabelDescription?.(props.data), optionPrefix: getOptionPrefix?.(props.data) })) : (jsx(SingleSelectMenuItem, { ...componentProps, "data-testid": typeof props.label === "string" ? props.label : undefined, disabled: disabled || props.isDisabled, fieldSize: fieldSize, optionLabelDescription: getOptionLabelDescription?.(props.data), optionPrefix: getOptionPrefix?.(props.data) })) }));
|
|
1642
|
+
}, children: selectOption.isMulti ? (jsx(MultiSelectMenuItem, { ...componentProps, "data-testid": typeof selectOption.label === "string" ? selectOption.label : undefined, disabled: Boolean(disabled), fieldSize: fieldSize, optionLabelDescription: getOptionLabelDescription?.(selectOption.data), optionPrefix: getOptionPrefix?.(selectOption.data) })) : (jsx(SingleSelectMenuItem, { ...componentProps, "data-testid": typeof selectOption.label === "string" ? selectOption.label : undefined, disabled: Boolean(disabled) || selectOption.isDisabled, fieldSize: fieldSize, optionLabelDescription: getOptionLabelDescription?.(selectOption.data), optionPrefix: getOptionPrefix?.(selectOption.data) })) }));
|
|
1275
1643
|
},
|
|
1276
|
-
...componentsProps,
|
|
1277
1644
|
};
|
|
1278
1645
|
}, [
|
|
1279
|
-
|
|
1280
|
-
|
|
1646
|
+
hasError,
|
|
1647
|
+
fieldSize,
|
|
1281
1648
|
disabled,
|
|
1282
|
-
|
|
1649
|
+
className,
|
|
1283
1650
|
readOnly,
|
|
1284
1651
|
dataTestId,
|
|
1285
|
-
t,
|
|
1286
1652
|
prefix,
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1653
|
+
interactable,
|
|
1654
|
+
getVisibleCount,
|
|
1655
|
+
getTotalCount,
|
|
1656
|
+
getCounterWidth,
|
|
1657
|
+
getIsComplete,
|
|
1658
|
+
setValueContainerRef,
|
|
1659
|
+
setFakeCounterRef,
|
|
1660
|
+
setCounterRef,
|
|
1661
|
+
autoComplete,
|
|
1662
|
+
t,
|
|
1290
1663
|
getOptionPrefix,
|
|
1664
|
+
getOptionLabelDescription,
|
|
1665
|
+
setGeometryRef,
|
|
1666
|
+
setMenuRef,
|
|
1291
1667
|
]);
|
|
1292
1668
|
return customComponents;
|
|
1293
1669
|
};
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
const
|
|
1304
|
-
|
|
1305
|
-
return
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
singleValue: base => ({
|
|
1315
|
-
...base,
|
|
1316
|
-
}),
|
|
1317
|
-
multiValue: base => ({
|
|
1318
|
-
...base,
|
|
1319
|
-
}),
|
|
1320
|
-
multiValueLabel: base => ({
|
|
1321
|
-
...base,
|
|
1322
|
-
}),
|
|
1323
|
-
indicatorsContainer: base => ({
|
|
1324
|
-
...base,
|
|
1325
|
-
...(disabled && { display: "none" }),
|
|
1326
|
-
}),
|
|
1327
|
-
indicatorSeparator: () => ({
|
|
1328
|
-
width: "0px",
|
|
1329
|
-
}),
|
|
1330
|
-
menu: base => {
|
|
1331
|
-
return {
|
|
1332
|
-
...base,
|
|
1333
|
-
width: "100%",
|
|
1334
|
-
marginTop: "4px",
|
|
1335
|
-
marginBottom: "18px",
|
|
1336
|
-
transition: "all 1s ease-in-out",
|
|
1337
|
-
};
|
|
1338
|
-
},
|
|
1339
|
-
input: base => ({
|
|
1340
|
-
...base,
|
|
1341
|
-
marginLeft: "0px",
|
|
1342
|
-
}),
|
|
1343
|
-
placeholder: base => ({
|
|
1344
|
-
...base,
|
|
1345
|
-
}),
|
|
1346
|
-
option: () => ({}),
|
|
1347
|
-
menuPortal: base => ({
|
|
1348
|
-
...base,
|
|
1349
|
-
width: refContainer.current ? `${refContainer.current.clientWidth}px` : base.width,
|
|
1350
|
-
backgroundColor: "#ffffff",
|
|
1351
|
-
borderRadius: "var(--border-radius-lg)",
|
|
1352
|
-
zIndex: "var(--z-overlay)",
|
|
1353
|
-
borderColor: "rgb(var(--color-neutral-300))",
|
|
1354
|
-
boxShadow: "var(--tw-ring-inset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)",
|
|
1355
|
-
}),
|
|
1356
|
-
menuList: base => {
|
|
1357
|
-
return {
|
|
1358
|
-
...base,
|
|
1359
|
-
position: "relative",
|
|
1360
|
-
padding: "var(--spacing-1)",
|
|
1361
|
-
display: "grid",
|
|
1362
|
-
gap: "var(--spacing-1)",
|
|
1363
|
-
width: "100%",
|
|
1364
|
-
borderRadius: "0px",
|
|
1365
|
-
boxShadow: "none",
|
|
1366
|
-
paddingTop: "0px",
|
|
1367
|
-
};
|
|
1368
|
-
},
|
|
1369
|
-
valueContainer: base => {
|
|
1370
|
-
return {
|
|
1371
|
-
...base,
|
|
1372
|
-
paddingBlock: 0,
|
|
1373
|
-
flexWrap: maxSelectedDisplayCount !== undefined ? "wrap" : "nowrap",
|
|
1374
|
-
gap: "0.25rem",
|
|
1375
|
-
};
|
|
1376
|
-
},
|
|
1377
|
-
container: base => ({
|
|
1378
|
-
...base,
|
|
1379
|
-
width: "100%",
|
|
1380
|
-
}),
|
|
1381
|
-
dropdownIndicator: base => ({
|
|
1382
|
-
...base,
|
|
1383
|
-
padding: "0px",
|
|
1384
|
-
}),
|
|
1385
|
-
clearIndicator: base => {
|
|
1386
|
-
return {
|
|
1387
|
-
...base,
|
|
1388
|
-
padding: "0px",
|
|
1389
|
-
};
|
|
1390
|
-
},
|
|
1391
|
-
...styles,
|
|
1392
|
-
};
|
|
1393
|
-
}, [refContainer, disabled, fieldSize, maxSelectedDisplayCount, styles]);
|
|
1394
|
-
return { customStyles };
|
|
1670
|
+
const getNoStyles = () => {
|
|
1671
|
+
// To prevent the default styles from being applied from react-select.
|
|
1672
|
+
return {};
|
|
1673
|
+
};
|
|
1674
|
+
const getInputComponent = (children) => {
|
|
1675
|
+
return Array.isArray(children)
|
|
1676
|
+
? children.find(child => child !== null && child !== undefined && /react-select-\d+-input/.test(child?.props?.id))
|
|
1677
|
+
: null;
|
|
1678
|
+
};
|
|
1679
|
+
const getMultiValueComponents = (children) => {
|
|
1680
|
+
if (!Array.isArray(children)) {
|
|
1681
|
+
return null;
|
|
1682
|
+
}
|
|
1683
|
+
// Only return if children[0] is an array (the multiValue array)
|
|
1684
|
+
return Array.isArray(children[0]) ? children[0] : null;
|
|
1685
|
+
};
|
|
1686
|
+
const getPlaceholderElement = (children) => {
|
|
1687
|
+
return Array.isArray(children)
|
|
1688
|
+
? children.find(child => child !== null && child !== undefined && child.key === "placeholder")
|
|
1689
|
+
: null;
|
|
1395
1690
|
};
|
|
1396
1691
|
|
|
1397
1692
|
/**
|
|
1398
1693
|
* A hook used by selects to share the common code
|
|
1399
1694
|
*
|
|
1400
1695
|
* @param {SelectProps} props - The props for the Select component
|
|
1401
|
-
* @returns {
|
|
1402
|
-
*/
|
|
1403
|
-
const useSelect = ({
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
fieldSize,
|
|
1411
|
-
});
|
|
1412
|
-
const [menuIsOpen, setMenuIsOpen] = useState(props.menuIsOpen ?? false);
|
|
1413
|
-
const [menuIsEnabled, setMenuIsEnabled] = useState(true);
|
|
1696
|
+
* @returns {ReactSelectProps} Props for react-select component
|
|
1697
|
+
*/
|
|
1698
|
+
const useSelect = ({ disabled = false, //renaming to isDisabled, so not directly passed to react-select
|
|
1699
|
+
"data-testid": dataTestId,
|
|
1700
|
+
// Extract critical props that must trigger recalculation when they change
|
|
1701
|
+
onMenuOpen, onMenuClose, options, value, onChange, defaultValue,
|
|
1702
|
+
// restProps are pur in a ref to keep dependencies stable while always having the latest values
|
|
1703
|
+
// See explanation on restPropsRef below for more details 👇
|
|
1704
|
+
...restProps }) => {
|
|
1414
1705
|
const customComponents = useCustomComponents({
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1706
|
+
disabled, // intentionally not evaluated as boolean, since it can be object too!
|
|
1707
|
+
readOnly: restProps.readOnly ?? false, // intentionally not evaluated as boolean, since it can be object too!
|
|
1708
|
+
"data-testid": dataTestId ?? "select",
|
|
1709
|
+
prefix: restProps.prefix,
|
|
1710
|
+
hasError: restProps.hasError,
|
|
1711
|
+
fieldSize: restProps.fieldSize,
|
|
1712
|
+
getOptionLabelDescription: restProps.getOptionLabelDescription,
|
|
1713
|
+
getOptionPrefix: restProps.getOptionPrefix,
|
|
1714
|
+
isMulti: restProps.isMulti ?? false,
|
|
1715
|
+
className: restProps.className,
|
|
1716
|
+
autoComplete: restProps.autoComplete,
|
|
1426
1717
|
});
|
|
1427
|
-
const
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1718
|
+
const interactable = useMemo(() => !Boolean(disabled) && !Boolean(restProps.readOnly), [disabled, restProps.readOnly]);
|
|
1719
|
+
// Determine the portal target for the menu
|
|
1720
|
+
const portalTarget = useMemo(() => (restProps.menuPortalTarget !== undefined ? restProps.menuPortalTarget : document.body), [restProps.menuPortalTarget]);
|
|
1721
|
+
// Use custom scroll blocking hook to prevent layout shifts
|
|
1722
|
+
// Pass the portal target so we only block scroll when menu is portaled to document.body
|
|
1723
|
+
const { blockScroll, restoreScroll } = useScrollBlock(portalTarget);
|
|
1724
|
+
// Store callbacks in refs to keep wrapper callbacks stable
|
|
1725
|
+
// This prevents unnecessary re-renders when parent components don't memoize these callbacks
|
|
1726
|
+
const onMenuOpenRef = useRef(onMenuOpen);
|
|
1727
|
+
const onMenuCloseRef = useRef(onMenuClose);
|
|
1728
|
+
useEffect(() => {
|
|
1729
|
+
onMenuOpenRef.current = onMenuOpen;
|
|
1730
|
+
}, [onMenuOpen]);
|
|
1731
|
+
useEffect(() => {
|
|
1732
|
+
onMenuCloseRef.current = onMenuClose;
|
|
1733
|
+
}, [onMenuClose]);
|
|
1734
|
+
// Wrap user's onMenuOpen callback to block scrolling
|
|
1735
|
+
// We apply scroll blocking directly instead of using react-select's menuShouldBlockScroll
|
|
1736
|
+
// because it doesn't properly account for existing body padding, causing layout shifts
|
|
1737
|
+
// See commeont next to menuShouldBlockScroll below for more
|
|
1738
|
+
const handleMenuOpen = useCallback(() => {
|
|
1739
|
+
blockScroll();
|
|
1740
|
+
onMenuOpenRef.current?.();
|
|
1741
|
+
}, [blockScroll]);
|
|
1742
|
+
// Wrap user's onMenuClose callback to restore scrolling
|
|
1743
|
+
const handleMenuClose = useCallback(() => {
|
|
1744
|
+
restoreScroll();
|
|
1745
|
+
onMenuCloseRef.current?.();
|
|
1746
|
+
}, [restoreScroll]);
|
|
1747
|
+
// Store restProps in a ref to keep dependencies stable while always having the latest values
|
|
1748
|
+
// This allows us to access restProps in useMemo without including them in the dependency array
|
|
1749
|
+
// Criteria for props in restProps:
|
|
1750
|
+
// - Props used in computations are already tracked via derived values (interactable, portalTarget, etc.)
|
|
1751
|
+
// - Other props are accessed via ref - they'll always be latest but won't trigger recalculation
|
|
1752
|
+
// This is a trade-off: we prioritize stability over perfect reactivity for less critical props
|
|
1753
|
+
const restPropsRef = useRef(restProps);
|
|
1754
|
+
useEffect(() => {
|
|
1755
|
+
restPropsRef.current = restProps;
|
|
1756
|
+
}, [restProps]);
|
|
1757
|
+
// eslint-disable-next-line react-hooks/refs
|
|
1758
|
+
return useMemo(() => {
|
|
1759
|
+
const currentRestProps = restPropsRef.current;
|
|
1760
|
+
return {
|
|
1761
|
+
...currentRestProps,
|
|
1762
|
+
options,
|
|
1763
|
+
value,
|
|
1764
|
+
onChange,
|
|
1765
|
+
defaultValue,
|
|
1766
|
+
components: customComponents,
|
|
1767
|
+
unstyled: true,
|
|
1768
|
+
"aria-label": currentRestProps.label,
|
|
1769
|
+
"data-testid": dataTestId ?? "select",
|
|
1770
|
+
tabSelectsValue: false,
|
|
1771
|
+
blurInputOnSelect: false,
|
|
1772
|
+
// This configuration allows for more flexible positioning control of the dropdown.
|
|
1773
|
+
// Setting menuPortalTarget to 'null' specifies that the dropdown should be rendered within
|
|
1774
|
+
// the parent element instead of 'document.body'.
|
|
1775
|
+
menuPortalTarget: portalTarget,
|
|
1776
|
+
isSearchable: interactable ? (currentRestProps.isSearchable ?? true) : false,
|
|
1777
|
+
// Disable react-select's built-in scroll blocking as we handle it ourselves in onMenuOpen/onMenuClose
|
|
1778
|
+
// to prevent layout shifts caused by not accounting for existing body padding in the react-select implementation.
|
|
1779
|
+
// See: https://github.com/JedWatson/react-select/issues/5342 AND https://github.com/JedWatson/react-select/issues/5020
|
|
1780
|
+
menuShouldBlockScroll: false,
|
|
1781
|
+
menuShouldScrollIntoView: true,
|
|
1782
|
+
openMenuOnClick: interactable ? (currentRestProps.openMenuOnClick ?? true) : false,
|
|
1783
|
+
openMenuOnFocus: Boolean(currentRestProps.openMenuOnFocus),
|
|
1784
|
+
closeMenuOnSelect: !Boolean(currentRestProps.isMulti),
|
|
1785
|
+
isDisabled: Boolean(disabled),
|
|
1786
|
+
isClearable: Boolean(currentRestProps.isClearable),
|
|
1787
|
+
menuPlacement: currentRestProps.menuPlacement ?? "auto",
|
|
1788
|
+
placeholder: interactable ? currentRestProps.placeholder : undefined,
|
|
1789
|
+
hideSelectedOptions: Boolean(currentRestProps.hideSelectedOptions),
|
|
1790
|
+
menuIsOpen: interactable ? undefined : false, // close it if not interactable, otherwise leave state to react-select
|
|
1791
|
+
// Wire up our custom menu open/close handlers
|
|
1792
|
+
onMenuOpen: handleMenuOpen,
|
|
1793
|
+
onMenuClose: handleMenuClose,
|
|
1794
|
+
// 👇 putting these here to avoid them _accidentally_ being overwritten in the future👇
|
|
1795
|
+
maxMenuHeight: undefined, // controlled custom components styling
|
|
1796
|
+
minMenuHeight: undefined, // controlled custom components styling
|
|
1797
|
+
theme: undefined,
|
|
1798
|
+
classNames: undefined,
|
|
1799
|
+
styles: undefined,
|
|
1800
|
+
};
|
|
1801
|
+
}, [
|
|
1445
1802
|
customComponents,
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1803
|
+
disabled,
|
|
1804
|
+
interactable,
|
|
1805
|
+
handleMenuOpen,
|
|
1806
|
+
handleMenuClose,
|
|
1807
|
+
dataTestId,
|
|
1808
|
+
portalTarget,
|
|
1809
|
+
// Critical props that must trigger recalculation when they change
|
|
1810
|
+
options,
|
|
1811
|
+
value,
|
|
1812
|
+
onChange,
|
|
1813
|
+
defaultValue,
|
|
1814
|
+
// restPropsRef is intentionally not included - we access restPropsRef.current inside useMemo
|
|
1815
|
+
// to always get the latest restProps without causing recalculation when the object reference changes
|
|
1816
|
+
]);
|
|
1450
1817
|
};
|
|
1451
1818
|
|
|
1452
1819
|
// This is here to ensure the bundled react-components can expose the react-select for jest in external iris apps.
|
|
1453
1820
|
const ReactSyncSelect = ReactSelect.default || ReactSelect;
|
|
1454
1821
|
/**
|
|
1455
|
-
*
|
|
1822
|
+
* BaseSelect are input components used to choose a value from a set.
|
|
1456
1823
|
*
|
|
1457
1824
|
* @param {SelectProps} props - The props for the Select component
|
|
1458
1825
|
* @returns {ReactElement} Select component
|
|
1459
1826
|
*/
|
|
1460
1827
|
const BaseSelect = (props) => {
|
|
1461
|
-
const
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
isClearable,
|
|
1489
|
-
id,
|
|
1490
|
-
onMenuScrollToBottom,
|
|
1491
|
-
onInputChange,
|
|
1492
|
-
hideSelectedOptions,
|
|
1493
|
-
isDisabled: Boolean(disabled),
|
|
1494
|
-
}), [
|
|
1495
|
-
classNamePrefix,
|
|
1496
|
-
customComponents,
|
|
1497
|
-
customStyles,
|
|
1498
|
-
dataTestId,
|
|
1499
|
-
disabled,
|
|
1500
|
-
hideSelectedOptions,
|
|
1501
|
-
id,
|
|
1502
|
-
isClearable,
|
|
1503
|
-
isLoading,
|
|
1504
|
-
isMulti,
|
|
1505
|
-
isSearchable,
|
|
1506
|
-
label,
|
|
1507
|
-
maxMenuHeight,
|
|
1508
|
-
menuIsOpen,
|
|
1509
|
-
menuPlacement,
|
|
1510
|
-
onChange,
|
|
1511
|
-
onInputChange,
|
|
1512
|
-
onMenuScrollToBottom,
|
|
1513
|
-
openMenuOnClick,
|
|
1514
|
-
openMenuOnFocus,
|
|
1515
|
-
props.menuPortalTarget,
|
|
1516
|
-
readOnly,
|
|
1517
|
-
value,
|
|
1518
|
-
]);
|
|
1519
|
-
const renderAsDisabled = Boolean(props.disabled) || props.readOnly;
|
|
1520
|
-
return (jsxs("div", { className: cvaSelect({
|
|
1521
|
-
invalid: hasError,
|
|
1522
|
-
fieldSize: fieldSize,
|
|
1523
|
-
disabled: renderAsDisabled,
|
|
1524
|
-
className: props.className,
|
|
1525
|
-
}), "data-testid": dataTestId, ref: refContainer, children: [prefix !== undefined ? (jsx("div", { className: cvaSelectPrefixSuffix({ kind: "prefix" }), "data-testid": dataTestId ? `${dataTestId}-prefix` : null, children: prefix })) : null, async ? (jsx(ReactAsyncSelect, { ...props, ...reactSelectProps, ...async, menuPosition: menuPosition, onMenuClose: closeMenuHandler, onMenuOpen: openMenuHandler, placeholder: renderAsDisabled ? null : props.placeholder })) : (jsx(ReactSyncSelect, { ...props, ...reactSelectProps, isMulti: isMulti, menuPosition: menuPosition, onMenuClose: closeMenuHandler, onMenuOpen: openMenuHandler, options: options, placeholder: renderAsDisabled ? null : props.placeholder })), typeof props.disabled === "object" ? (jsx("div", { className: cvaSelectPrefixSuffix({ kind: "suffix" }), "data-testid": dataTestId ? `${dataTestId}-locked` : null, children: jsx(InputLockReasonTooltip, { ...props.disabled }) })) : null] }));
|
|
1828
|
+
const select = useSelect(props);
|
|
1829
|
+
return props.async ? jsx(ReactAsyncSelect, { ...select }) : jsx(ReactSyncSelect, { ...select });
|
|
1830
|
+
};
|
|
1831
|
+
|
|
1832
|
+
/**
|
|
1833
|
+
* A hook used by creatable selects that extends useSelect with creatable-specific functionality
|
|
1834
|
+
*
|
|
1835
|
+
* @param props - The props for the CreatableSelect component
|
|
1836
|
+
* @returns {ReactCreatableProps} Props for react-select creatable component
|
|
1837
|
+
*/
|
|
1838
|
+
const useCreatableSelect = (props) => {
|
|
1839
|
+
const { onCreateOption, allowCreateWhileLoading, isMulti } = props;
|
|
1840
|
+
const baseSelectProps = useSelect(props);
|
|
1841
|
+
// Store onCreateOption in a ref to keep wrapper stable
|
|
1842
|
+
// This prevents unnecessary re-renders when parent components don't memoize this callback
|
|
1843
|
+
const onCreateOptionRef = useRef(onCreateOption);
|
|
1844
|
+
useEffect(() => {
|
|
1845
|
+
onCreateOptionRef.current = onCreateOption;
|
|
1846
|
+
}, [onCreateOption]);
|
|
1847
|
+
return useMemo(() => ({
|
|
1848
|
+
...baseSelectProps,
|
|
1849
|
+
allowCreateWhileLoading,
|
|
1850
|
+
onCreateOption: onCreateOptionRef.current,
|
|
1851
|
+
// Override some defaults specific to creatable selects
|
|
1852
|
+
closeMenuOnSelect: false, // Keep menu open for multi-creation
|
|
1853
|
+
blurInputOnSelect: !Boolean(isMulti), // Only blur if not multi
|
|
1854
|
+
}), [baseSelectProps, allowCreateWhileLoading, isMulti]);
|
|
1526
1855
|
};
|
|
1527
|
-
BaseSelect.displayName = "BaseSelect";
|
|
1528
1856
|
|
|
1529
1857
|
/**
|
|
1530
1858
|
* CreatableSelects are input components used to choose a value from a set.
|
|
1531
1859
|
*
|
|
1532
|
-
|
|
1533
|
-
*
|
|
1860
|
+
/**
|
|
1861
|
+
* CreatableSelect is a component that allows users to select from existing options or create new ones.
|
|
1862
|
+
*
|
|
1863
|
+
* @template TOption - The option type.
|
|
1864
|
+
* @template TIsAsync - Indicates whether the component is asynchronous.
|
|
1865
|
+
* @template TIsMulti - Indicates whether multiple selections are allowed.
|
|
1866
|
+
* @template TGroup - The group base type for options.
|
|
1867
|
+
* @param {CreatableSelectProps} props - The props to configure the CreatableSelect component.
|
|
1868
|
+
* @returns {ReactElement} A ReactElement rendering the CreatableSelect.
|
|
1534
1869
|
*/
|
|
1535
1870
|
const CreatableSelect = (props) => {
|
|
1536
|
-
const
|
|
1537
|
-
|
|
1538
|
-
const reactCreatableSelectProps = useMemo(() => ({
|
|
1539
|
-
value,
|
|
1540
|
-
menuPlacement,
|
|
1541
|
-
maxMenuHeight,
|
|
1542
|
-
onChange,
|
|
1543
|
-
"aria-label": label,
|
|
1544
|
-
"data-testid": dataTestId,
|
|
1545
|
-
components: customComponents,
|
|
1546
|
-
styles: customStyles,
|
|
1547
|
-
tabSelectsValue: false,
|
|
1548
|
-
blurInputOnSelect: !isMulti,
|
|
1549
|
-
menuPortalTarget: props.menuPortalTarget || document.body,
|
|
1550
|
-
isSearchable: disabled || readOnly ? false : isSearchable,
|
|
1551
|
-
menuShouldBlockScroll: true,
|
|
1552
|
-
menuShouldScrollIntoView: true,
|
|
1553
|
-
openMenuOnFocus,
|
|
1554
|
-
menuIsOpen: !readOnly ? menuIsOpen : false,
|
|
1555
|
-
openMenuOnClick,
|
|
1556
|
-
closeMenuOnSelect: false,
|
|
1557
|
-
isMulti,
|
|
1558
|
-
classNamePrefix,
|
|
1559
|
-
isLoading,
|
|
1560
|
-
isClearable,
|
|
1561
|
-
id,
|
|
1562
|
-
onMenuScrollToBottom,
|
|
1563
|
-
onInputChange,
|
|
1564
|
-
allowCreateWhileLoading,
|
|
1565
|
-
onCreateOption,
|
|
1566
|
-
isDisabled: Boolean(disabled),
|
|
1567
|
-
}), [
|
|
1568
|
-
allowCreateWhileLoading,
|
|
1569
|
-
classNamePrefix,
|
|
1570
|
-
customComponents,
|
|
1571
|
-
customStyles,
|
|
1572
|
-
dataTestId,
|
|
1573
|
-
disabled,
|
|
1574
|
-
id,
|
|
1575
|
-
isClearable,
|
|
1576
|
-
isLoading,
|
|
1577
|
-
isMulti,
|
|
1578
|
-
isSearchable,
|
|
1579
|
-
label,
|
|
1580
|
-
maxMenuHeight,
|
|
1581
|
-
menuIsOpen,
|
|
1582
|
-
menuPlacement,
|
|
1583
|
-
onChange,
|
|
1584
|
-
onCreateOption,
|
|
1585
|
-
onInputChange,
|
|
1586
|
-
onMenuScrollToBottom,
|
|
1587
|
-
openMenuOnClick,
|
|
1588
|
-
openMenuOnFocus,
|
|
1589
|
-
props.menuPortalTarget,
|
|
1590
|
-
readOnly,
|
|
1591
|
-
value,
|
|
1592
|
-
]);
|
|
1593
|
-
const renderAsDisabled = Boolean(props.disabled) || props.readOnly;
|
|
1594
|
-
return (jsxs("div", { className: cvaSelect({ invalid: hasError, disabled: renderAsDisabled, className: props.className }), "data-testid": dataTestId, ref: refContainer, children: [prefix !== undefined ? (jsx("div", { className: cvaSelectPrefixSuffix({ kind: "prefix" }), "data-testid": dataTestId ? `${dataTestId}-prefix` : null, children: prefix })) : null, async ? (jsx(ReactAsyncCreatableSelect, { ...props, ...reactCreatableSelectProps, ...async, onMenuClose: closeMenuHandler, onMenuOpen: openMenuHandler, placeholder: renderAsDisabled ? null : props.placeholder })) : (jsx(ReactCreatableSelect, { ...props, ...reactCreatableSelectProps, hideSelectedOptions: false, isMulti: isMulti, onMenuClose: closeMenuHandler, onMenuOpen: openMenuHandler, options: options, placeholder: renderAsDisabled ? null : props.placeholder })), typeof props.disabled === "object" ? (jsx("div", { className: cvaSelectPrefixSuffix({ kind: "suffix" }), "data-testid": dataTestId ? `${dataTestId}-locked` : null, children: jsx(InputLockReasonTooltip, { ...props.disabled }) })) : null] }));
|
|
1871
|
+
const creatableSelect = useCreatableSelect(props);
|
|
1872
|
+
return props.async ? (jsx(ReactAsyncCreatableSelect, { ...creatableSelect })) : (jsx(ReactCreatableSelect, { ...creatableSelect }));
|
|
1595
1873
|
};
|
|
1596
|
-
CreatableSelect.displayName = "CreatableSelect";
|
|
1597
1874
|
|
|
1598
1875
|
/**
|
|
1599
1876
|
* The Label component is used for labels for input fields.
|
|
@@ -1636,7 +1913,7 @@ const cvaHelpAddon = cvaMerge(["ml-auto"]);
|
|
|
1636
1913
|
* @param {FormGroupProps} props - The props for the FormGroup component
|
|
1637
1914
|
* @returns {ReactElement} FormGroup component
|
|
1638
1915
|
*/
|
|
1639
|
-
const FormGroup = ({ isInvalid, isWarning, helpText, helpAddon, tip, className, "data-testid": dataTestId, label, htmlFor, children, required = false, }) => {
|
|
1916
|
+
const FormGroup = ({ isInvalid = false, isWarning = false, helpText, helpAddon, tip, className, "data-testid": dataTestId, label, htmlFor, children, required = false, }) => {
|
|
1640
1917
|
const [t] = useTranslation();
|
|
1641
1918
|
const validationStateIcon = useMemo(() => {
|
|
1642
1919
|
const color = isInvalid ? "danger" : isWarning ? "warning" : null;
|
|
@@ -1730,7 +2007,7 @@ const isValidHEXColor = (value) => {
|
|
|
1730
2007
|
* ColorField validates that user enters a valid color address.
|
|
1731
2008
|
*
|
|
1732
2009
|
*/
|
|
1733
|
-
const ColorField = forwardRef(({ label, id, tip, helpText, errorMessage, helpAddon, className, defaultValue, "data-testid": dataTestId, value: propValue, onChange, isInvalid, onBlur, fieldSize = "medium", style, disabled, readOnly,
|
|
2010
|
+
const ColorField = forwardRef(({ label, id, tip, helpText, errorMessage, helpAddon, className, defaultValue, "data-testid": dataTestId, value: propValue, onChange, isInvalid, onBlur, fieldSize = "medium", style, disabled, readOnly, isWarning, inputClassName, ...inputProps }, ref) => {
|
|
1734
2011
|
const renderAsDisabled = Boolean(disabled);
|
|
1735
2012
|
const renderAsReadonly = Boolean(readOnly);
|
|
1736
2013
|
const htmlForId = useMemo(() => (id ? id : "colorField-" + uuidv4()), [id]);
|
|
@@ -1782,7 +2059,7 @@ const ColorField = forwardRef(({ label, id, tip, helpText, errorMessage, helpAdd
|
|
|
1782
2059
|
className,
|
|
1783
2060
|
}), "data-testid": dataTestId ? `${dataTestId}-container` : undefined, style: style, children: [jsx("input", { "aria-labelledby": htmlForId + "-label", className: cvaInputColorField({ readOnly: renderAsReadonly }), "data-testid": dataTestId, defaultValue: defaultValue, disabled: renderAsDisabled, id: htmlForId, onBlur: handleBlur, onChange: handleInputChange, readOnly: renderAsReadonly, ref: innerRef, type: "color", value: innerValue }), jsx("input", { "aria-labelledby": htmlForId + "-label-text", className: cvaInputElement({
|
|
1784
2061
|
className: twMerge("px-1 focus-visible:outline-none", inputClassName),
|
|
1785
|
-
}), "data-testid": dataTestId ? `${dataTestId}-textField` : undefined, disabled: renderAsDisabled, onBlur: handleBlur, onChange: handleInputChange, readOnly: renderAsReadonly
|
|
2062
|
+
}), "data-testid": dataTestId ? `${dataTestId}-textField` : undefined, disabled: renderAsDisabled, onBlur: handleBlur, onChange: handleInputChange, readOnly: renderAsReadonly, type: "text", value: innerValue, ...inputProps }), jsx(GenericActionsRenderer, { disabled: renderAsDisabled || renderAsReadonly, fieldSize: fieldSize, genericAction: "edit", innerRef: innerRef, tooltipLabel: t("colorField.tooltip") })] }) }));
|
|
1786
2063
|
});
|
|
1787
2064
|
ColorField.displayName = "ColorField";
|
|
1788
2065
|
|
|
@@ -1968,7 +2245,7 @@ const EmailBaseInput = ({ fieldSize = "medium", disabled = false, "data-testid":
|
|
|
1968
2245
|
setEmail(newValue);
|
|
1969
2246
|
}, [onChange]);
|
|
1970
2247
|
const renderAsInvalid = (email && !validateEmailAddress(email)) || isInvalid;
|
|
1971
|
-
return (jsx(BaseInput, { actions: email && email.length > 0 ? (jsx(ActionButton, { "data-testid": dataTestId ? `${dataTestId}-emailIcon` : undefined, disabled: disableAction || isInvalid, onClick: sendEmail, size: fieldSize
|
|
2248
|
+
return (jsx(BaseInput, { actions: email && email.length > 0 ? (jsx(ActionButton, { "data-testid": dataTestId ? `${dataTestId}-emailIcon` : undefined, disabled: disableAction || isInvalid, onClick: sendEmail, size: fieldSize, type: "EMAIL", value: email })) : null, "data-testid": dataTestId, disabled: disabled, fieldSize: fieldSize, isInvalid: renderAsInvalid, onChange: handleChange, placeholder: rest.placeholder || "mail@example.com", ref: ref, type: "email", ...rest }));
|
|
1972
2249
|
};
|
|
1973
2250
|
|
|
1974
2251
|
/**
|
|
@@ -2007,14 +2284,17 @@ function isWritableRef(r) {
|
|
|
2007
2284
|
}
|
|
2008
2285
|
/**
|
|
2009
2286
|
* Multi adapter:
|
|
2010
|
-
* - keeps
|
|
2287
|
+
* - keeps TOption[] semantics (via `MultiValue<TOption>`)
|
|
2011
2288
|
* - renders FormGroup chrome (label, help, error)
|
|
2012
2289
|
* - exposes a hidden <select> for a stable ref target
|
|
2013
2290
|
* - optionally renders one hidden <input> per selected option IF `getOptionValue` is provided
|
|
2014
2291
|
* - passes through all remaining BaseSelect props with isMulti=true
|
|
2292
|
+
*
|
|
2293
|
+
* @param {FormFieldSelectAdapterMultiProps} props - The props for the FormFieldSelectAdapterMulti component
|
|
2294
|
+
* @returns {ReactElement} FormFieldSelectAdapterMulti component
|
|
2015
2295
|
*/
|
|
2016
2296
|
const FormFieldSelectAdapterMulti = (props) => {
|
|
2017
|
-
const { className, "data-testid": dataTestId, helpText, helpAddon, tip, label, isInvalid, errorMessage, name, onBlur, options, value, defaultValue, id, onChange, children, ref, ...selectProps } = props;
|
|
2297
|
+
const { className, "data-testid": dataTestId, helpText, helpAddon, tip, label, isInvalid, errorMessage, name, onBlur, options, value, defaultValue, id, htmlFor: htmlForProp, onChange, children, ref, ...selectProps } = props;
|
|
2018
2298
|
// Hidden select for a stable DOM ref target (API parity with single adapter)
|
|
2019
2299
|
const innerRef = useRef(null);
|
|
2020
2300
|
// Bridge external ref (supports both callback and object refs)
|
|
@@ -2029,25 +2309,28 @@ const FormFieldSelectAdapterMulti = (props) => {
|
|
|
2029
2309
|
// Determine invalid state
|
|
2030
2310
|
const renderAsInvalid = useMemo(() => (isInvalid === undefined ? Boolean(errorMessage) : isInvalid), [errorMessage, isInvalid]);
|
|
2031
2311
|
// id to connect label and control
|
|
2032
|
-
const controlId = useMemo(() =>
|
|
2312
|
+
const controlId = useMemo(() => htmlForProp ?? id ?? "multiSelectField-" + uuidv4(), [htmlForProp, id]);
|
|
2033
2313
|
// If consumers provided getOptionValue (from BaseSelect props),
|
|
2034
2314
|
// we can render hidden inputs for native form submit / RHF.
|
|
2035
2315
|
const selectPropsWithAccessors = selectProps;
|
|
2036
|
-
const getOptionValue = typeof selectPropsWithAccessors.getOptionValue === "function"
|
|
2316
|
+
const getOptionValue = useMemo(() => typeof selectPropsWithAccessors.getOptionValue === "function"
|
|
2317
|
+
? selectPropsWithAccessors.getOptionValue
|
|
2318
|
+
: undefined, [selectPropsWithAccessors]);
|
|
2037
2319
|
// Compute selected options snapshot for hidden inputs (prefer controlled `value`)
|
|
2038
2320
|
const selectedOptions = useMemo(() => value ?? defaultValue ?? [], [value, defaultValue]);
|
|
2039
2321
|
// Build the exact prop bag for BaseSelect (multi=true).
|
|
2040
|
-
const childProps = {
|
|
2322
|
+
const childProps = useMemo(() => ({
|
|
2041
2323
|
...selectProps,
|
|
2324
|
+
"data-testid": dataTestId,
|
|
2042
2325
|
id: controlId,
|
|
2043
2326
|
onBlur,
|
|
2044
2327
|
options,
|
|
2045
2328
|
isMulti: true,
|
|
2046
2329
|
value: value ?? null,
|
|
2047
2330
|
defaultValue,
|
|
2048
|
-
onChange
|
|
2049
|
-
};
|
|
2050
|
-
return (jsxs(FormGroup, { className: className, "data-testid": dataTestId, helpAddon: helpAddon, helpText: (renderAsInvalid && errorMessage) || helpText, htmlFor: controlId, isInvalid: renderAsInvalid, label: label, required: "required" in selectProps && selectProps.required
|
|
2331
|
+
onChange,
|
|
2332
|
+
}), [selectProps, dataTestId, controlId, onBlur, options, value, defaultValue, onChange]);
|
|
2333
|
+
return (jsxs(FormGroup, { className: className, "data-testid": dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && errorMessage) || helpText, htmlFor: controlId, isInvalid: renderAsInvalid, label: label, required: "required" in selectProps && selectProps.required
|
|
2051
2334
|
? !(("disabled" in selectProps && Boolean(selectProps.disabled)) ||
|
|
2052
2335
|
("readOnly" in selectProps && Boolean(selectProps.readOnly)))
|
|
2053
2336
|
: false, tip: tip, children: [jsx("select", { "aria-hidden": "true", defaultValue: "", hidden: true, name: name, ref: innerRef }), typeof getOptionValue === "function" &&
|
|
@@ -2056,12 +2339,13 @@ const FormFieldSelectAdapterMulti = (props) => {
|
|
|
2056
2339
|
return typeof primitiveValue === "string" ? (jsx("input", { name: name, type: "hidden", value: primitiveValue }, `${primitiveValue}-${idx}`)) : null;
|
|
2057
2340
|
}), children(childProps)] }));
|
|
2058
2341
|
};
|
|
2059
|
-
FormFieldSelectAdapterMulti.displayName = "FormFieldSelectAdapterMulti";
|
|
2060
2342
|
|
|
2061
2343
|
/**
|
|
2062
|
-
* MultiSelectField
|
|
2063
|
-
*
|
|
2064
|
-
*
|
|
2344
|
+
* MultiSelectField is a custom Select component wrapped in the FormGroup component
|
|
2345
|
+
* that allows you to select multiple options from a list.
|
|
2346
|
+
*
|
|
2347
|
+
* @param {MultiSelectFieldProps} props - The props for the MultiSelectField component
|
|
2348
|
+
* @returns {ReactElement} MultiSelectField component
|
|
2065
2349
|
*/
|
|
2066
2350
|
const MultiSelectField = ({ ref, ...props }) => {
|
|
2067
2351
|
return (jsx(FormFieldSelectAdapterMulti, { ...props, ref: ref, children: convertedProps => jsx(BaseSelect, { ...convertedProps }) }));
|
|
@@ -2771,15 +3055,16 @@ Search.displayName = "Search";
|
|
|
2771
3055
|
/**
|
|
2772
3056
|
*
|
|
2773
3057
|
*/
|
|
2774
|
-
const FormFieldSelectAdapter = ({ className, "data-testid": dataTestId, helpText, helpAddon, tip, label, isInvalid, errorMessage, name, onBlur, options, value, defaultValue, id, onChange, children, ref, ...rest }) => {
|
|
3058
|
+
const FormFieldSelectAdapter = ({ className, "data-testid": dataTestId, helpText, helpAddon, tip, label, isInvalid = false, errorMessage, name, onBlur, options, value, defaultValue, id, htmlFor: htmlForProp, onChange, children, ref, required = false, ...rest }) => {
|
|
2775
3059
|
const isFirstRender = useIsFirstRender();
|
|
2776
|
-
const [innerValue, setInnerValue] = useState(value
|
|
3060
|
+
const [innerValue, setInnerValue] = useState(value ?? defaultValue);
|
|
2777
3061
|
useEffect(() => {
|
|
2778
3062
|
setInnerValue(defaultValue);
|
|
2779
3063
|
}, [defaultValue]);
|
|
2780
|
-
const renderAsInvalid = isInvalid
|
|
2781
|
-
const htmlFor = useMemo(() =>
|
|
3064
|
+
const renderAsInvalid = isInvalid || Boolean(errorMessage);
|
|
3065
|
+
const htmlFor = useMemo(() => htmlForProp ?? id ?? "selectField-" + uuidv4(), [htmlForProp, id]);
|
|
2782
3066
|
const innerRef = useRef(null);
|
|
3067
|
+
// eslint-disable-next-line local-rules/no-typescript-assertion, @typescript-eslint/no-non-null-assertion
|
|
2783
3068
|
useImperativeHandle(ref, () => innerRef.current, []);
|
|
2784
3069
|
useEffect(() => {
|
|
2785
3070
|
if (innerValue === undefined) {
|
|
@@ -2795,8 +3080,13 @@ const FormFieldSelectAdapter = ({ className, "data-testid": dataTestId, helpText
|
|
|
2795
3080
|
const optionsWithCurrentSelectionBackupOption = [
|
|
2796
3081
|
// Add the current selection in case there's no options loaded yet (in CreatableSelect)
|
|
2797
3082
|
// Also _don't_ add it if it's a duplicate
|
|
2798
|
-
|
|
2799
|
-
|
|
3083
|
+
// Only add backup option when innerValue is defined (not undefined) and not an empty string
|
|
3084
|
+
// Note: Empty string is used as a sentinel value for "no selection" (see onChange handler below)
|
|
3085
|
+
// and should not be added as a backup option. Zero (0) is a valid option value, so we allow it.
|
|
3086
|
+
innerValue !== undefined && innerValue !== "" && !options.find(option => option.value === innerValue)
|
|
3087
|
+
? // It is safe enough to assert this because the only properties that are used are value and label, and those are present in the TOption type.
|
|
3088
|
+
// eslint-disable-next-line local-rules/no-typescript-assertion
|
|
3089
|
+
{ value: innerValue, label: String(innerValue) }
|
|
2800
3090
|
: null,
|
|
2801
3091
|
...options,
|
|
2802
3092
|
].filter(nonNullable);
|
|
@@ -2804,15 +3094,16 @@ const FormFieldSelectAdapter = ({ className, "data-testid": dataTestId, helpText
|
|
|
2804
3094
|
return (jsxs(FormGroup, { isInvalid: renderAsInvalid,
|
|
2805
3095
|
htmlFor,
|
|
2806
3096
|
className,
|
|
2807
|
-
"data-testid": dataTestId,
|
|
3097
|
+
"data-testid": dataTestId ? `${dataTestId}-FormGroup` : undefined,
|
|
2808
3098
|
helpText: (renderAsInvalid && errorMessage) || helpText,
|
|
2809
3099
|
helpAddon,
|
|
2810
3100
|
tip,
|
|
2811
3101
|
label,
|
|
2812
|
-
required:
|
|
3102
|
+
required: required ? !Boolean(rest.disabled ?? rest.readOnly) : false, children: [jsx("select", { onChange, ref: innerRef, name, value: innerValue, hidden: true, children: optionsWithCurrentSelectionBackupOption.map(option => {
|
|
2813
3103
|
return (jsx("option", { value: option.value, children: option.label }, option.value));
|
|
2814
3104
|
}) }), children({
|
|
2815
3105
|
...rest,
|
|
3106
|
+
required,
|
|
2816
3107
|
id,
|
|
2817
3108
|
onBlur,
|
|
2818
3109
|
options: optionsWithCurrentSelectionBackupOption,
|
|
@@ -2822,6 +3113,7 @@ const FormFieldSelectAdapter = ({ className, "data-testid": dataTestId, helpText
|
|
|
2822
3113
|
// So even if react-select sends a null value, we need to convert it to an empty string
|
|
2823
3114
|
setInnerValue(!e ? "" : e.value);
|
|
2824
3115
|
},
|
|
3116
|
+
"data-testid": dataTestId,
|
|
2825
3117
|
value: selectedOption,
|
|
2826
3118
|
defaultValue: selectedOption,
|
|
2827
3119
|
})] }));
|
|
@@ -2837,24 +3129,23 @@ FormFieldSelectAdapter.displayName = "FormFieldSelectAdapter";
|
|
|
2837
3129
|
*
|
|
2838
3130
|
* @param {SelectFieldProps & CreatableSelectProps} props - The props for the CreatableSelectField component
|
|
2839
3131
|
*/
|
|
2840
|
-
const CreatableSelectField = ({ allowCreateWhileLoading, onCreateOption, ref, ...props }) => {
|
|
2841
|
-
const creatableSelectOnlyProps = {
|
|
3132
|
+
const CreatableSelectField = ({ allowCreateWhileLoading = false, onCreateOption, ref, ...props }) => {
|
|
3133
|
+
const creatableSelectOnlyProps = {
|
|
3134
|
+
allowCreateWhileLoading,
|
|
3135
|
+
onCreateOption,
|
|
3136
|
+
};
|
|
2842
3137
|
return (jsx(FormFieldSelectAdapter, { ...props, ref: ref, children: convertedProps => jsx(CreatableSelect, { ...convertedProps, ...creatableSelectOnlyProps }) }));
|
|
2843
3138
|
};
|
|
2844
3139
|
CreatableSelectField.displayName = "CreatableSelectField";
|
|
2845
3140
|
|
|
2846
3141
|
/**
|
|
2847
|
-
*
|
|
2848
|
-
*
|
|
2849
|
-
* This means that it can easily be added to any form alongside other Field components.
|
|
2850
|
-
*
|
|
2851
|
-
* This is done to make the field compatible with the React-hook-form library.
|
|
3142
|
+
* MultiSelect is a custom Select component wrapped in the FormGroup component
|
|
2852
3143
|
*
|
|
2853
3144
|
* @param {SelectFieldProps} props - The props for the SelectField component
|
|
2854
3145
|
*/
|
|
2855
|
-
|
|
3146
|
+
function SelectField({ ref, ...props }) {
|
|
2856
3147
|
return (jsx(FormFieldSelectAdapter, { ...props, ref: ref, children: convertedProps => jsx(BaseSelect, { ...convertedProps }) }));
|
|
2857
|
-
}
|
|
3148
|
+
}
|
|
2858
3149
|
SelectField.displayName = "SelectField";
|
|
2859
3150
|
|
|
2860
3151
|
/**
|
|
@@ -3077,7 +3368,7 @@ const cvaUploadInputField = cvaMerge([
|
|
|
3077
3368
|
*
|
|
3078
3369
|
* NOTE: If shown with a label, please use the `UploadField` component instead.
|
|
3079
3370
|
*/
|
|
3080
|
-
const UploadInput = ({ disabled, acceptedTypes, nonInteractive, uploadLabel, multipleFiles, ref, ...rest }) => (jsx("label", { className: "tu-upload-input", children: jsx(BaseInput, { accept: acceptedTypes, addonBefore: uploadLabel, disabled: disabled, inputClassName: cvaUploadInputField(), multiple: multipleFiles,
|
|
3371
|
+
const UploadInput = ({ disabled, acceptedTypes, nonInteractive, uploadLabel, multipleFiles, ref, ...rest }) => (jsx("label", { className: "tu-upload-input", children: jsx(BaseInput, { accept: acceptedTypes, addonBefore: uploadLabel, disabled: disabled, inputClassName: cvaUploadInputField(), multiple: multipleFiles, onClick: event => {
|
|
3081
3372
|
// onClick used to work with nonInteractive option
|
|
3082
3373
|
if (nonInteractive) {
|
|
3083
3374
|
event.preventDefault();
|
|
@@ -3132,10 +3423,10 @@ const validateUrl = (url, required) => {
|
|
|
3132
3423
|
*
|
|
3133
3424
|
* NOTE: If shown with a label, please use the `UrlField` component instead.
|
|
3134
3425
|
*/
|
|
3135
|
-
const UrlBaseInput = ({ "data-testid": dataTestId,
|
|
3426
|
+
const UrlBaseInput = ({ isInvalid = false, "data-testid": dataTestId, disabled = false, fieldSize = "medium", disableAction = false, value, defaultValue, ref, ...rest }) => {
|
|
3136
3427
|
const [url, setUrl] = useState(value?.toString() || defaultValue?.toString());
|
|
3137
3428
|
const renderAsInvalid = (url && typeof url === "string" && !validateUrlAddress(url)) || isInvalid;
|
|
3138
|
-
return (jsx(BaseInput, { "data-testid": 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, { "data-testid": (dataTestId && `${dataTestId}-url-input-Icon`) || "url-input-action-icon", disabled: renderAsInvalid || Boolean(disabled) || disableAction, size: fieldSize
|
|
3429
|
+
return (jsx(BaseInput, { "data-testid": 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, { "data-testid": (dataTestId && `${dataTestId}-url-input-Icon`) || "url-input-action-icon", disabled: renderAsInvalid || Boolean(disabled) || disableAction, size: fieldSize, type: "WEB_ADDRESS", value: url })) }));
|
|
3139
3430
|
};
|
|
3140
3431
|
|
|
3141
3432
|
/**
|
|
@@ -3274,4 +3565,4 @@ const useZodValidators = () => {
|
|
|
3274
3565
|
*/
|
|
3275
3566
|
setupLibraryTranslations();
|
|
3276
3567
|
|
|
3277
|
-
export { ActionButton, BaseInput, BaseSelect, Checkbox, CheckboxField, ColorField, CreatableSelect, CreatableSelectField, DEFAULT_TIME, DateBaseInput, DateField, DropZone, DropZoneDefaultLabel, EMAIL_REGEX, EmailField, FormFieldSelectAdapter, FormGroup, Label, MultiSelectField,
|
|
3568
|
+
export { ActionButton, BaseInput, BaseSelect, Checkbox, CheckboxField, ColorField, CreatableSelect, CreatableSelectField, DEFAULT_TIME, DateBaseInput, DateField, DropZone, DropZoneDefaultLabel, EMAIL_REGEX, EmailField, FormFieldSelectAdapter, FormGroup, Label, MultiSelectField, NumberBaseInput, NumberField, OptionCard, PasswordBaseInput, PasswordField, PhoneBaseInput, PhoneField, PhoneFieldWithController, RadioGroup, RadioItem, Schedule, ScheduleVariant, Search, SelectField, TextAreaBaseInput, TextAreaField, TextBaseInput, TextField, TimeRange, TimeRangeField, ToggleSwitch, ToggleSwitchOption, UploadField, UploadInput, UrlField, checkIfPhoneNumberHasPlus, countryCodeToFlagEmoji, cvaAccessoriesContainer, cvaActionButton, cvaActionContainer, cvaInput$1 as cvaInput, cvaInputAddon, cvaInputBase, cvaInputBaseDisabled, cvaInputBaseInvalid, cvaInputBaseReadOnly, cvaInputBaseSize, cvaInputElement, cvaInputItemPlacementManager, cvaInputPrefix, cvaInputSuffix, cvaLabel, cvaSelectClearIndicator, cvaSelectContainer, cvaSelectControl, cvaSelectDropdownIconContainer, cvaSelectDropdownIndicator, cvaSelectIndicatorsContainer, cvaSelectLoadingMessage, cvaSelectMenu, cvaSelectMenuList, cvaSelectMultiValue, cvaSelectNoOptionsMessage, cvaSelectPlaceholder, cvaSelectPrefixSuffix, cvaSelectSingleValue, cvaSelectValueContainer, getCountryAbbreviation, getPhoneNumberWithPlus, isInvalidCountryCode, isInvalidPhoneNumber, isValidHEXColor, parseSchedule, phoneErrorMessage, serializeSchedule, useCreatableSelect, useCustomComponents, useGetPhoneValidationRules, usePhoneInput, useSelect, useZodValidators, validateEmailAddress, validatePhoneNumber, weekDay };
|