Add TagsV2 support in Table component

This commit is contained in:
Nakul Nagargade 2026-04-20 14:01:24 +05:30
parent 8186f3d061
commit dbd35e942e
16 changed files with 645 additions and 8 deletions

View file

@ -105,6 +105,7 @@ export const PropertiesTabElements = ({
{ label: 'Date Picker', value: 'datepicker' },
{ label: 'Select', value: 'select' },
{ label: 'MultiSelect', value: 'newMultiSelect' },
{ label: 'Tags', value: 'tagsV2' },
{ label: 'Boolean', value: 'boolean' },
{ label: 'Image', value: 'image' },
{ label: 'Link', value: 'link' },
@ -449,7 +450,7 @@ export const PropertiesTabElements = ({
component={component}
/>
)}
{['select', 'newMultiSelect'].includes(column.columnType) && (
{['select', 'newMultiSelect', 'tagsV2'].includes(column.columnType) && (
<OptionsList
column={column}
props={props}

View file

@ -140,6 +140,7 @@ export const StylesTabElements = ({
'select',
'text',
'newMultiSelect',
'tagsV2',
'datepicker',
].includes(column.columnType) && (
<>

View file

@ -97,6 +97,7 @@ export const ValidationProperties = ({
case 'dropdown':
case 'select':
case 'newMultiSelect':
case 'tagsV2':
return [
{
property: 'customRule',

View file

@ -10,6 +10,8 @@ import { ProgramaticallyHandleProperties } from '../ProgramaticallyHandlePropert
import { resolveReferences } from '@/_helpers/utils';
import { Button as ButtonComponent } from '@/components/ui/Button/Button';
import { unset } from 'lodash';
import ToggleGroup from '@/ToolJetUI/SwitchGroup/ToggleGroup';
import ToggleGroupItem from '@/ToolJetUI/SwitchGroup/ToggleGroupItem';
export const OptionsList = ({
column,
@ -263,9 +265,40 @@ export const OptionsList = ({
};
items.push({
title: 'Options',
title: column.columnType === 'tagsV2' ? 'Tags' : 'Options',
children: (
<div className="d-flex custom-gap-7 flex-column">
{column.columnType === 'tagsV2' && (
<>
<div className="field d-flex custom-gap-12 align-items-center align-self-stretch justify-content-between px-3">
<label className="form-label">Sort tags</label>
<ToggleGroup
onValueChange={(value) => onColumnItemChange(index, 'sortTags', value)}
defaultValue={column?.sortTags || 'none'}
style={{ width: '58%' }}
>
<ToggleGroupItem value="none">None</ToggleGroupItem>
<ToggleGroupItem value="a-z">a-z</ToggleGroupItem>
<ToggleGroupItem value="z-a">z-a</ToggleGroupItem>
</ToggleGroup>
</div>
<ProgramaticallyHandleProperties
label="Allow multiple selection"
currentState={currentState}
index={index}
darkMode={darkMode}
callbackFunction={onColumnItemChange}
property="allowMultipleSelection"
props={column}
component={component}
paramMeta={{
type: 'toggle',
displayName: 'Allow multiple selection',
}}
paramType="properties"
/>
</>
)}
<ProgramaticallyHandleProperties
label="Auto assign colors"
currentState={currentState}
@ -400,7 +433,7 @@ export const OptionsList = ({
className="tw-w-full mt-2"
width="100%"
>
Add new option
{column.columnType === 'tagsV2' ? 'Add new tag' : 'Add new option'}
</ButtonComponent>
</div>
</div>

View file

@ -44,6 +44,7 @@ const getColumnTypeDisplayText = (columnType) => {
boolean: 'Boolean',
select: 'Select',
newMultiSelect: 'Multiselect',
tagsV2: 'Tags',
json: 'JSON',
markdown: 'Markdown',
html: 'HTML',

View file

@ -0,0 +1,29 @@
import React from 'react';
const TagsV2TypeIcon = ({ fill = '#ACB2B9', width = '16', className = '', viewBox = '0 0 16 16', style, height }) => (
<svg
className={className}
width={width}
height={height}
viewBox={viewBox}
fill="none"
xmlns="http://www.w3.org/2000/svg"
style={style}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M1.33398 11.0618V13.1027C1.33398 13.9482 2.01937 14.6336 2.86483 14.6336H10.0769C10.5001 14.6336 10.9043 14.4584 11.1938 14.1497L14.3913 10.739C14.7593 10.3465 14.7593 9.7356 14.3913 9.34303L13.8315 8.746L11.9383 10.7655C11.456 11.28 10.7822 11.5719 10.0769 11.5719H2.86483C2.29046 11.5719 1.76041 11.382 1.33398 11.0618Z"
fill={fill}
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2.86483 1.36621C2.01937 1.36621 1.33398 2.05159 1.33398 2.89706V9.02045C1.33398 9.86592 2.01937 10.5513 2.86483 10.5513H10.0769C10.5001 10.5513 10.9043 10.3762 11.1938 10.0675L14.3913 6.65676C14.7593 6.26419 14.7593 5.65332 14.3913 5.26075L11.1938 1.85005C10.9043 1.54135 10.5001 1.36621 10.0769 1.36621H2.86483Z"
fill={fill}
/>
<circle cx="5" cy="5.9" r="1.2" fill="white" />
</svg>
);
export default TagsV2TypeIcon;

View file

@ -12,6 +12,7 @@ export { default as MarkdownTypeIcon } from './MarkdownTypeIcon';
export { default as HTMLTypeIcon } from './HTMLTypeIcon';
export { default as BadgeTypeIcon } from './BadgeTypeIcon';
export { default as TagsTypeIcon } from './TagsTypeIcon';
export { default as TagsV2TypeIcon } from './TagsV2TypeIcon';
export { default as RadioTypeIcon } from './RadioTypeIcon';
export { default as RatingTypeIcon } from './RadioTypeIcon';
export { default as ButtonTypeIcon } from './ButtonTypeIcon';

View file

@ -22,7 +22,7 @@ export const useColumnManager = ({ component, paramUpdated, currentState }) => {
let modifiedColumn = { ...column };
// Handle select/multiselect default options
if (property === 'columnType' && (value === 'select' || value === 'newMultiSelect')) {
if (property === 'columnType' && (value === 'select' || value === 'newMultiSelect' || value === 'tagsV2')) {
if (modifiedColumn.options?.length > 0) {
modifiedColumn.options = modifiedColumn.options.map((opt) => {
const { makeDefaultOption, ...rest } = opt;

View file

@ -13,6 +13,7 @@ import {
StringTypeIcon,
BadgeTypeIcon,
TagsTypeIcon,
TagsV2TypeIcon,
RadioTypeIcon,
RatingTypeIcon,
ButtonTypeIcon,
@ -56,6 +57,8 @@ export const getColumnIcon = (columnType) => {
return BadgeTypeIcon;
case 'tags':
return TagsTypeIcon;
case 'tagsV2':
return TagsV2TypeIcon;
case 'rating':
return RatingTypeIcon;
case 'button':

View file

@ -0,0 +1,425 @@
import React, { useCallback, useMemo, useRef, useEffect, useState } from 'react';
import Select from '@/_ui/Select';
import SolidIcon from '@/_ui/Icon/SolidIcons';
import { Checkbox } from '@/_ui/CheckBox/CheckBox';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import { isArray } from 'lodash';
import TagsInputMenuList from '@/AppBuilder/Widgets/TagsInput/TagsInputMenuList';
import defaultStyles from '@/_ui/Select/styles';
const COLORS = [
'#40474D33',
'#CE276133',
'#6745E233',
'#2576CE33',
'#1A9C6D33',
'#69AF2033',
'#F3571733',
'#EB2E3933',
'#A438C033',
'#405DE633',
'#1E8FA333',
'#34A94733',
'#F1911933',
];
const sortOptions = (opts, sortTags) => {
if (!sortTags || sortTags === 'none') return opts;
return [...opts].sort((a, b) => {
const cmp = (a.label || '').localeCompare(b.label || '');
return sortTags === 'a-z' ? cmp : -cmp;
});
};
const CustomOption = ({ innerRef, innerProps, children, isSelected, ...props }) => {
const { label, value, data } = props;
const { optionColors } = props.selectProps;
return (
<div
ref={innerRef}
{...innerProps}
className="option-wrapper d-flex"
style={{ backgroundColor: 'var(--cc-surface1-surface)' }}
>
{props.selectProps.isMulti ? (
<Checkbox label="" isChecked={isSelected} onChange={(e) => e.stopPropagation()} key="" value={children} />
) : (
<div style={{ visibility: isSelected ? 'visible' : 'hidden' }}>
<Checkbox label="" isChecked={isSelected} onChange={(e) => e.stopPropagation()} key="" value={children} />
</div>
)}
<div
className="table-select-menu-pill"
style={{
background: optionColors?.[value] || 'var(--surfaces-surface-03)',
color: data?.labelColor || 'var(--text-primary)',
padding: '2px 6px',
borderRadius: '6px',
fontSize: '12px',
}}
>
{label}
</div>
</div>
);
};
const MultiValueRemove = ({ innerProps }) => <div {...innerProps} />;
const CustomMultiValueContainer = ({ children }) => (
<div
style={{
display: 'flex',
flexWrap: 'wrap',
alignItems: 'center',
}}
>
{children}
</div>
);
const DropdownIndicator = ({ selectProps }) => {
return (
<div
className="cell-icon-display"
style={{ alignSelf: 'center' }}
onMouseDown={(e) => {
const isOpen = selectProps.menuIsOpen;
selectProps.onMenuOpen(!isOpen);
const tdElement = e.currentTarget.closest('td');
if (tdElement) {
const clickEvent = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window,
});
tdElement.dispatchEvent(clickEvent);
}
}}
>
<SolidIcon
name={selectProps.menuIsOpen ? 'arrowUpTriangle' : 'arrowDownTriangle'}
width="16"
height="16"
fill="#6A727C"
/>
</div>
);
};
const getOverlay = (value, darkMode) => {
if (!isArray(value)) return <div />;
return (
<div
style={{ maxWidth: '266px' }}
className={`overlay-cell-table overlay-multiselect-table ${darkMode ? 'dark-theme' : ''}`}
>
{value.map((option) => (
<span
key={option.label || option}
style={{
padding: '2px 6px',
background: 'var(--surfaces-surface-03)',
borderRadius: '6px',
color: 'var(--text-primary)',
fontSize: '12px',
}}
>
{option.label || option}
</span>
))}
</div>
);
};
/**
* TagsRenderer creatable tags dropdown for the Table TagsV2 column.
*
* Distinct from SelectRenderer: users can create new tag options at runtime
* via TagsInputMenuList, tags are optionally auto-colored, and the option
* list can be sorted. Borrows only control + valueContainer styles from
* SelectRenderer via _sharedStyles; all other styling is local.
*
* Validation is owned by the adapter; this component is pure UI.
*/
export const TagsRenderer = ({
options,
value,
onChange,
defaultOptionsList = [],
isMulti,
autoAssignColors = true,
sortTags = 'none',
placeholder,
disabled,
className,
darkMode,
textColor = '',
horizontalAlignment = 'left',
optionsLoadingState = false,
isEditable,
isNewRow,
isFocused,
setIsFocused,
menuIsOpen,
isValid = true,
validationError,
}) => {
const [runtimeTags, setRuntimeTags] = useState([]);
const allOptions = useMemo(() => [...options, ...runtimeTags], [options, runtimeTags]);
const sortedOptions = useMemo(() => sortOptions(allOptions, sortTags), [allOptions, sortTags]);
const optionColors = useMemo(
() =>
allOptions.reduce((acc, option, index) => {
acc[option.value] =
option.optionColor || (autoAssignColors ? COLORS[index % COLORS.length] : 'var(--surfaces-surface-03)');
return acc;
}, {}),
[allOptions, autoAssignColors]
);
const handleCreate = useCallback(
(newLabel) => {
const trimmed = (newLabel || '').trim();
if (!trimmed) return;
const newTag = { label: trimmed, value: trimmed };
setRuntimeTags((prev) => (prev.some((t) => t.value === trimmed) ? prev : [...prev, newTag]));
if (isMulti) {
const currentValues = isArray(value) ? value.map((v) => (v && typeof v === 'object' ? v.value : v)) : [];
onChange([...currentValues, trimmed]);
} else {
onChange(trimmed);
}
},
[isMulti, value, onChange]
);
const isValidNewOption = useCallback(
(input) => {
if (!input || !input.trim()) return false;
const trimmed = input.trim().toLowerCase();
return !allOptions.some((o) => o.value?.toLowerCase() === trimmed);
},
[allOptions]
);
const containerRef = useRef(null);
useEffect(() => {
if (!isMulti && !isNewRow) return;
const handleDocumentClick = (event) => {
if (!containerRef.current?.contains(event.target)) {
setIsFocused?.(false);
}
};
document.addEventListener('mousedown', handleDocumentClick);
return () => document.removeEventListener('mousedown', handleDocumentClick);
}, [isMulti, isNewRow, setIsFocused]);
const customComponents = {
MenuList: (props) => (
<TagsInputMenuList
{...props}
allowNewTags
inputValue={props.selectProps?.inputValue}
optionsLoadingState={optionsLoadingState}
darkMode={darkMode}
allOptions={allOptions}
onCreateTag={handleCreate}
autoPickChipColor={autoAssignColors}
/>
),
Option: CustomOption,
DropdownIndicator: isEditable ? DropdownIndicator : null,
...(isMulti && {
MultiValueRemove,
MultiValueContainer: CustomMultiValueContainer,
}),
};
const customStyles = useMemo(
() => ({
...defaultStyles(darkMode, '100%'),
valueContainer: (provided) => ({
...provided,
...(isMulti && {
marginBottom: '0',
display: 'flex',
flexWrap: 'no-wrap',
overflow: 'hidden',
flexDirection: 'row',
}),
justifyContent: horizontalAlignment,
}),
...(isMulti && {
multiValue: (provided) => ({
...provided,
display: 'inline-block',
marginRight: '4px',
}),
multiValueLabel: (provided, state) => {
const option = state.data;
return {
...provided,
padding: '2px 6px',
background: optionColors?.[option.value] || 'var(--surfaces-surface-03)',
borderRadius: '6px',
color: option?.labelColor || textColor || 'var(--text-primary)',
fontSize: '12px',
};
},
}),
singleValue: (provided, state) => {
const option = state.data;
return {
...provided,
padding: '2px 6px',
background: optionColors?.[option.value] || 'var(--surfaces-surface-03)',
borderRadius: '6px',
color: option?.labelColor || textColor || 'var(--text-primary)',
fontSize: '12px',
};
},
menuList: (provided) => ({
...provided,
display: 'flex',
flexDirection: 'column',
overflowY: 'auto',
backgroundColor: 'var(--surfaces-surface-01)',
padding: '4px',
}),
menu: (provided) => ({
...provided,
padding: '0',
marginTop: '5px',
borderRadius: '8px',
boxShadow: 'var(--elevation-300-box-shadow)',
border: '1px solid var(--border-weak)',
}),
}),
[darkMode, isMulti, horizontalAlignment, textColor, optionColors]
);
const defaultValue = useMemo(
() =>
defaultOptionsList.length >= 1
? isMulti
? defaultOptionsList
: defaultOptionsList.slice(-1)[0]
: isMulti
? []
: {},
[isMulti, defaultOptionsList]
);
const selectedValue = useMemo(() => {
if (!value) return null;
if (isMulti && value?.length) {
if (!isArray(value)) return [];
const resolved = value.map((val) => {
const v = val && typeof val === 'object' ? val.value : val;
const match = allOptions?.find((option) => option.value === v);
if (match) return match;
return { label: String(v), value: String(v) };
});
return sortOptions(resolved.filter(Boolean), sortTags);
}
const v = isArray(value) ? value[0] : value;
if (!v) return null;
const match = allOptions?.find((option) => option.value === (v && typeof v === 'object' ? v.value : v));
if (match) return match;
return {
label: String(v && typeof v === 'object' ? v.value : v),
value: String(v && typeof v === 'object' ? v.value : v),
};
}, [allOptions, value, isMulti, sortTags]);
const handleChange = useCallback(
(newValue) => {
setIsFocused(false);
if (!isMulti && newValue === selectedValue?.value) {
onChange('');
} else {
onChange(newValue);
}
},
[isMulti, selectedValue, onChange, setIsFocused]
);
const isOverflowing = useCallback(() => {
if (!containerRef.current) return false;
const valueContainer = containerRef.current.querySelector('.react-select__value-container');
return valueContainer?.clientHeight > containerRef.current?.clientHeight;
}, []);
return (
<OverlayTrigger
placement="bottom"
overlay={
isMulti && (selectedValue?.length || defaultValue?.length) && !isFocused ? (
getOverlay(selectedValue || defaultValue, darkMode)
) : (
<div />
)
}
trigger={isMulti && !isFocused && isOverflowing() && ['hover', 'focus']}
rootClose={true}
>
<>
<div
className="w-100 d-flex align-items-center"
ref={containerRef}
onClick={() => {
if (isNewRow && isEditable) {
setIsFocused((prev) => !prev);
}
}}
>
<Select
options={sortedOptions}
hasSearch
isDisabled={disabled}
className={className}
components={customComponents}
value={selectedValue}
onMenuInputFocus={() => setIsFocused(true)}
onChange={handleChange}
useCustomStyles={true}
styles={customStyles}
defaultValue={defaultValue}
placeholder={placeholder}
isMulti={isMulti}
hideSelectedOptions={false}
isClearable={false}
clearIndicator={false}
darkMode={darkMode}
menuIsOpen={menuIsOpen}
isFocused={isFocused}
optionColors={optionColors}
creatable
onCreateOption={handleCreate}
formatCreateLabel={() => null}
isValidNewOption={isValidNewOption}
/>
</div>
{isEditable && !isValid && (
<div
onClick={() => {
if (!isValid) setIsFocused(true);
}}
className="invalid-feedback d-block"
>
{validationError}
</div>
)}
</>
</OverlayTrigger>
);
};
export default TagsRenderer;

View file

@ -0,0 +1,84 @@
import React, { useCallback, useState } from 'react';
import { TagsRenderer } from '@/AppBuilder/Shared/DataTypes/renderers/TagsRenderer';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
import { isArray } from 'lodash';
import useTextColor from '../_hooks/useTextColor';
export const TagsV2Column = ({
options,
value,
onChange,
placeholder,
disabled,
className,
darkMode,
defaultOptionsList = [],
textColor = '',
allowMultipleSelection = true,
sortTags = 'none',
optionsLoadingState = false,
horizontalAlignment = 'left',
isEditable,
column,
isNewRow,
autoAssignColors = true,
id,
}) => {
const [isFocused, setIsFocused] = useState(false);
const validateWidget = useStore((state) => state.validateWidget, shallow);
const cellTextColor = useTextColor(id, textColor);
const validationData = validateWidget({
validationObject: {
customRule: { value: column?.customRule },
},
widgetValue: value,
customResolveObjects: { value },
});
const { isValid, validationError } = validationData;
const handleChange = useCallback(
(newValue) => {
if (allowMultipleSelection) {
const arr = isArray(newValue) ? newValue : [];
onChange(arr.map((v) => (v && typeof v === 'object' ? v.value : v)));
} else {
if (newValue == null || newValue === '') {
onChange([]);
return;
}
const v = typeof newValue === 'object' ? newValue.value : newValue;
onChange([v]);
}
},
[allowMultipleSelection, onChange]
);
return (
<TagsRenderer
options={options}
value={value}
onChange={handleChange}
placeholder={placeholder}
disabled={disabled}
className={className}
darkMode={darkMode}
defaultOptionsList={defaultOptionsList}
textColor={cellTextColor}
isMulti={allowMultipleSelection}
optionsLoadingState={optionsLoadingState}
horizontalAlignment={horizontalAlignment}
isEditable={isEditable}
isNewRow={isNewRow}
autoAssignColors={autoAssignColors}
isFocused={isFocused}
setIsFocused={setIsFocused}
isValid={isValid}
validationError={validationError}
menuIsOpen={isFocused || undefined}
sortTags={sortTags}
/>
);
};

View file

@ -7,6 +7,7 @@ export { LinkColumn } from './adapters/LinkColumnAdapter';
export { ImageColumn } from './adapters/ImageColumnAdapter';
export { DatepickerColumn } from './adapters/Datepicker';
export { CustomSelectColumn } from './adapters/SelectColumnAdapter'; // Select & MultiSelect
export { TagsV2Column } from './adapters/TagsV2ColumnAdapter'; // TagsV2
export { JsonColumn } from './adapters/JsonColumnAdapter';
export { MarkdownColumn } from './adapters/MarkdownColumnAdapter';
export { HTMLColumn } from './adapters/HtmlColumnAdapter';

View file

@ -177,7 +177,7 @@ export function AddNewRow({ id, hideAddNewRowPopup, darkMode, allColumns, fireEv
'align-items-center flex-column': cell.column.columnDef.meta?.columnType === 'selector',
'selector-column':
cell.column.columnDef.meta?.columnType === 'selector' && cell.column.id === 'selection',
'has-select': ['select', 'newMultiSelect'].includes(cell.column.columnDef.meta?.columnType),
'has-select': ['select', 'newMultiSelect', 'tagsV2'].includes(cell.column.columnDef.meta?.columnType),
isEditable: true,
})}
style={{ width: cell.column.getSize() }}

View file

@ -107,7 +107,7 @@ export const TableRow = ({
['text', 'string', undefined, 'number'].includes(cell.column.columnDef?.meta?.columnType) &&
!contentWrap,
'selector-column': cell.column.columnDef?.meta?.columnType === 'selector',
'has-select': ['select', 'newMultiSelect'].includes(cell.column.columnDef?.meta?.columnType),
'has-select': ['select', 'newMultiSelect', 'tagsV2'].includes(cell.column.columnDef?.meta?.columnType),
'has-tags': cell.column.columnDef?.meta?.columnType === 'tags',
'has-link': cell.column.columnDef?.meta?.columnType === 'link',
'has-radio': cell.column.columnDef?.meta?.columnType === 'radio',

View file

@ -11,6 +11,7 @@ import {
LinkColumn,
ImageColumn,
CustomSelectColumn,
TagsV2Column,
TextColumn,
JsonColumn,
MarkdownColumn,
@ -295,6 +296,52 @@ export default function generateColumnsData({
);
}
case 'tagsV2': {
let useDynamicOptions = getResolvedValue(column?.useDynamicOptions);
if (useDynamicOptions) {
const dynamicOptions = getResolvedValue(column?.dynamicOptions || [], { cellValue, rowData });
columnOptions.selectOptions = Array.isArray(dynamicOptions) ? dynamicOptions : [];
} else {
const options = column?.options ?? [];
columnOptions.selectOptions =
options?.map((option) => ({
label: option.label,
value: option.value,
optionColor: option.optionColor,
labelColor: option.labelColor,
})) ?? [];
}
const tagsAutoAssignColors = getResolvedValue(column.autoAssignColors) ?? true;
const sortTags = getResolvedValue(column.sortTags) ?? 'none';
const allowMultipleSelection = getResolvedValue(column.allowMultipleSelection) ?? true;
return (
<TagsV2Column
options={columnOptions.selectOptions}
value={cellValue}
onChange={(value) => handleCellValueChange(row.index, column.key || column.name, value, row.original)}
disabled={!isEditable}
darkMode={darkMode}
containerWidth={columnSize}
defaultOptionsList={column?.defaultOptionsList || []}
optionsLoadingState={
useDynamicOptions && getResolvedValue(column?.optionsLoadingState) ? true : false
}
autoAssignColors={tagsAutoAssignColors}
isEditable={isEditable}
allowMultipleSelection={allowMultipleSelection}
sortTags={sortTags}
className="select-search table-select-search"
column={column}
isNewRow={columnForAddNewRow}
horizontalAlignment={column?.horizontalAlignment}
textColor={getResolvedValue(column.textColor, { cellValue, rowData })}
id={id}
/>
);
}
case 'badge':
case 'badges':
return (

View file

@ -1,13 +1,23 @@
import React from 'react';
import _ from 'lodash';
import Select, { components } from 'react-select';
import CreatableSelect from 'react-select/creatable';
import defaultStyles from './styles';
const CustomInput = (props) => {
return <components.Input {...props} data-cy={`${props.selectProps.dataCy || ''}-select-dropdown-input`} />;
};
export const SelectComponent = ({ options = [], value, onChange, closeMenuOnSelect, darkMode, ...restProps }) => {
export const SelectComponent = ({
options = [],
value,
onChange,
closeMenuOnSelect,
darkMode,
creatable = false,
...restProps
}) => {
const selectRef = React.useRef(null);
const isDarkMode = darkMode ?? localStorage.getItem('darkMode') === 'true';
const SelectElement = creatable ? CreatableSelect : Select;
const {
isMulti = false,
styles = {},
@ -54,7 +64,7 @@ export const SelectComponent = ({ options = [], value, onChange, closeMenuOnSele
return option.label;
};
return (
<Select
<SelectElement
{...restProps}
ref={selectRef}
selectRef={selectRef} // Exposed ref for custom components if needed