diff --git a/package-lock.json b/package-lock.json
index 62d9b904..00317149 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@abelflopes/react-ck",
- "version": "4.19.0",
+ "version": "4.20.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@abelflopes/react-ck",
- "version": "4.19.0",
+ "version": "4.20.0",
"workspaces": [
"packages/*/*"
],
diff --git a/packages/components/base/src/boolean-input/BooleaInput.tsx b/packages/components/base/src/boolean-input/BooleaInput.tsx
index 6f537efa..df1228cd 100644
--- a/packages/components/base/src/boolean-input/BooleaInput.tsx
+++ b/packages/components/base/src/boolean-input/BooleaInput.tsx
@@ -3,7 +3,7 @@ import React, { useEffect, useMemo, useRef } from "react";
import classNames from "classnames";
import { type FormFieldContextProps, useFormFieldContext } from "../form-field";
import { type BooleanInputIconComponent } from "./types";
-import { megeRefs } from "@react-ck/react-utils";
+import { mergeRefs } from "@react-ck/react-utils";
/**
* Props interface for the BooleanInput component.
@@ -58,7 +58,7 @@ export const BooleaInput = ({
(disabled || formFieldContext?.disabled) && styles.disabled,
)}>
{
- megeRefs(ref, containerRef)(el);
+ mergeRefs(ref, containerRef)(el);
setFocusWrapperElement(el || undefined);
}}
tabIndex={0}
diff --git a/packages/components/base/src/file-uploader/FileUploader.tsx b/packages/components/base/src/file-uploader/FileUploader.tsx
index 40715c71..83e5cbce 100644
--- a/packages/components/base/src/file-uploader/FileUploader.tsx
+++ b/packages/components/base/src/file-uploader/FileUploader.tsx
@@ -4,7 +4,7 @@ import classNames from "classnames";
import { Text } from "../text";
import { Button, type ButtonProps } from "../button";
import { readFileList } from "./utils/read-file";
-import { megeRefs } from "@react-ck/react-utils";
+import { mergeRefs } from "@react-ck/react-utils";
// TODO: check https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file
// TODO: add size limitation: https://stackoverflow.com/questions/5697605/limit-the-size-of-a-file-upload-html-input-element
@@ -106,7 +106,7 @@ export const FileUploader = ({
)}>
+ *
+ * Apple
+ * Banana
+ *
+ *
+ * Carrot
+ * Broccoli
+ *
+ *
+ * ```
+ *
+ * @param props - Component props {@link SelectGroupProps}
+ * @returns React element
+ */
+const SelectGroup = ({
+ name,
+ children,
+}: Readonly
>): React.ReactElement => {
+ const { generateUniqueId } = useManagerContext();
+
+ const uniqueId = generateUniqueId();
+
+ return (
+
+
+
+ {name}
+
+
+ {children}
+
+ );
+};
+
+SelectGroup[DISPLAY_NAME_ATTRIBUTE] = "SelectGroup";
+
+export { SelectGroup };
diff --git a/packages/components/base/src/select/index.tsx b/packages/components/base/src/select/index.tsx
index be8422b2..a14b2d5f 100644
--- a/packages/components/base/src/select/index.tsx
+++ b/packages/components/base/src/select/index.tsx
@@ -1,16 +1,20 @@
import styles from "./styles/index.module.scss";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { SelectOption } from "./SelectOption";
+import { SelectGroup } from "./SelectGroup";
import { Menu } from "../menu";
import { Dropdown } from "../dropdown";
import { Input } from "../input";
import classNames from "classnames";
-import { megeRefs, raf } from "@react-ck/react-utils";
+import { mergeRefs, raf } from "@react-ck/react-utils";
import { EmptyState } from "../empty-state";
import { getChildrenData, simplifyString, valueAsArray } from "./utils";
import { type SelectProps, type ChangeHandler } from "./types";
import { SelectContext, type SelectContextProps } from "./context";
import { useFormFieldContext } from "../form-field";
+import { Spinner } from "../spinner";
+import { Icon } from "@react-ck/icon";
+import { IconChevronDown } from "@react-ck/icon/icons/IconChevronDown";
/** Default positions to exclude from auto-positioning */
const defaultExclude: SelectProps["excludeAutoPosition"] = [
@@ -75,6 +79,7 @@ const Select = ({
allowDeselect = true,
required,
disabled,
+ loading,
displayValueDivider = ",",
fullWidth,
position,
@@ -105,17 +110,47 @@ const Select = ({
const childrenData = useMemo(() => getChildrenData(children), [children]);
/** Options list children filtered by user search */
- const filteredOptions = useMemo(
- () =>
- childrenData
- .filter(
- (i) =>
- !search.length ||
- (i.textContent && simplifyString(i.textContent).includes(simplifyString(search))),
- )
- .map((i) => i.element),
- [childrenData, search],
- );
+ const filteredOptions = useMemo(() => {
+ const filtered = childrenData.filter(
+ (i) =>
+ !search.length ||
+ (i.textContent && simplifyString(i.textContent).includes(simplifyString(search))),
+ );
+
+ // Group options by groupName
+ const groupedOptions: { [key: string]: React.ReactNode[] } = {};
+ const ungroupedOptions: React.ReactNode[] = [];
+
+ filtered.forEach((item) => {
+ if (item.isSelectOption) {
+ if (item.groupName) {
+ if (!(item.groupName in groupedOptions)) {
+ groupedOptions[item.groupName] = [];
+ }
+ groupedOptions[item.groupName]?.push(item.element);
+ } else {
+ ungroupedOptions.push(item.element);
+ }
+ }
+ });
+
+ // Render grouped options
+ const result: React.ReactNode[] = [];
+
+ // Add ungrouped options first
+ ungroupedOptions.forEach((option) => result.push(option));
+
+ // Add grouped options using SelectGroup component
+ Object.entries(groupedOptions).forEach(([groupName, options]) => {
+ result.push(
+
+ {options}
+ ,
+ );
+ });
+
+ return result;
+ }, [childrenData, search]);
/** Returns the internal value always as an array to facilitate operations */
const selectedValuesList = useMemo(
@@ -256,7 +291,9 @@ const Select = ({
const resizeObserver = new ResizeObserver(() => {
if (!valueSlotRefCurrent || !sizeSetterRef.current) return;
- valueSlotRefCurrent.style.width = `${sizeSetterRef.current.clientWidth + 10}px`;
+ if (valueSlotRefCurrent.clientWidth < sizeSetterRef.current.clientWidth) {
+ valueSlotRefCurrent.style.width = `${sizeSetterRef.current.clientWidth + 10}px`;
+ }
});
resizeObserver.observe(sizeSetterRef.current);
@@ -278,7 +315,7 @@ const Select = ({
styles[`skin_${computedSkin}`],
formFieldContext === undefined && styles.standalone,
(disabled || formFieldContext?.disabled) && styles.disabled,
-
+ loading && styles.loading,
(fullWidth ?? formFieldContext?.fullWidth) && styles.full_width,
className,
)}
@@ -291,11 +328,17 @@ const Select = ({
onBlur?.(e);
}}>
- {displayValue ||
{placeholder}}
+
+ {displayValue || {placeholder}}
+
+ {loading &&
}
+
+
+