mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-24 09:28:31 +00:00
Merge pull request #12281 from ToolJet/feat/dropdown-option-sort
Feat: Added sorting to options in Dropdown and Multiselect
This commit is contained in:
commit
e3c02d48ab
10 changed files with 164 additions and 39 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import Accordion from '@/_ui/Accordion';
|
||||
import { EventManager } from '../EventManager';
|
||||
import { renderElement } from '../Utils';
|
||||
|
|
@ -14,6 +14,7 @@ import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
|||
import SortableList from '@/_components/SortableList';
|
||||
import Trash from '@/_ui/Icon/solidIcons/Trash';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { sortArray } from '@/Editor/Components/DropdownV2/utils';
|
||||
|
||||
export function Select({ componentMeta, darkMode, ...restProps }) {
|
||||
const {
|
||||
|
|
@ -27,10 +28,13 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
|
|||
allComponents,
|
||||
pages,
|
||||
} = restProps;
|
||||
|
||||
const isInitialRender = useRef(true);
|
||||
const getResolvedValue = useStore((state) => state.getResolvedValue, shallow);
|
||||
const isMultiSelect = component?.component?.component === 'MultiselectV2';
|
||||
|
||||
const isDynamicOptionsEnabled = getResolvedValue(component?.component?.definition?.properties?.advanced?.value);
|
||||
const isSortingEnabled = componentMeta?.properties['sort'] ?? false;
|
||||
const sort = component?.component?.definition?.properties?.sort?.value;
|
||||
|
||||
const constructOptions = () => {
|
||||
let optionsValue = component?.component?.definition?.properties?.options?.value;
|
||||
|
|
@ -89,6 +93,15 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
|
|||
paramUpdated({ name: 'options' }, 'value', options, 'properties', false, props);
|
||||
};
|
||||
|
||||
const updateSortParam = (value) => {
|
||||
paramUpdated({ name: 'sort' }, 'value', value, 'properties');
|
||||
};
|
||||
|
||||
const updateOptions = (options) => {
|
||||
setOptions(options);
|
||||
updateAllOptionsParams(options);
|
||||
};
|
||||
|
||||
const generateNewOptions = () => {
|
||||
let found = false;
|
||||
let label = '';
|
||||
|
|
@ -114,8 +127,8 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
|
|||
const handleAddOption = () => {
|
||||
let _option = generateNewOptions();
|
||||
const _items = [...options, _option];
|
||||
setOptions(_items);
|
||||
updateAllOptionsParams(_items);
|
||||
const sortedItems = sortArray(_items, sort);
|
||||
updateOptions(sortedItems);
|
||||
};
|
||||
|
||||
const handleDeleteOption = (index) => {
|
||||
|
|
@ -134,8 +147,7 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
|
|||
}
|
||||
return option;
|
||||
});
|
||||
setOptions(_options);
|
||||
updateAllOptionsParams(_options);
|
||||
updateOptions(_options);
|
||||
};
|
||||
|
||||
const handleValueChange = (value, index) => {
|
||||
|
|
@ -148,16 +160,17 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
|
|||
}
|
||||
return option;
|
||||
});
|
||||
setOptions(_options);
|
||||
updateAllOptionsParams(_options);
|
||||
updateOptions(_options);
|
||||
};
|
||||
|
||||
const reorderOptions = async (startIndex, endIndex) => {
|
||||
const result = [...options];
|
||||
const [removed] = result.splice(startIndex, 1);
|
||||
result.splice(endIndex, 0, removed);
|
||||
setOptions(result);
|
||||
updateAllOptionsParams(result);
|
||||
updateOptions(result);
|
||||
if (isSortingEnabled && sort !== 'none') {
|
||||
updateSortParam('none');
|
||||
}
|
||||
};
|
||||
|
||||
const onDragEnd = ({ source, destination }) => {
|
||||
|
|
@ -200,9 +213,8 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
|
|||
};
|
||||
}
|
||||
});
|
||||
setOptions(_options);
|
||||
updateOptions(_options);
|
||||
setMarkedAsDefault(_value);
|
||||
updateAllOptionsParams(_options);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -219,8 +231,7 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
|
|||
}
|
||||
return option;
|
||||
});
|
||||
setOptions(_options);
|
||||
updateAllOptionsParams(_options);
|
||||
updateOptions(_options);
|
||||
};
|
||||
|
||||
const handleDisableChange = (value, index) => {
|
||||
|
|
@ -236,8 +247,7 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
|
|||
}
|
||||
return option;
|
||||
});
|
||||
setOptions(_options);
|
||||
updateAllOptionsParams(_options);
|
||||
updateOptions(_options);
|
||||
};
|
||||
|
||||
const handleOnFxPress = (active, index, key) => {
|
||||
|
|
@ -253,12 +263,20 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
|
|||
}
|
||||
return option;
|
||||
});
|
||||
setOptions(_options);
|
||||
updateAllOptionsParams(_options);
|
||||
updateOptions(_options);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setOptions(constructOptions());
|
||||
if (!isInitialRender.current && isSortingEnabled) {
|
||||
const sortedOptions = sortArray([...options], sort);
|
||||
updateOptions(sortedOptions);
|
||||
}
|
||||
}, [sort]);
|
||||
|
||||
useEffect(() => {
|
||||
const sortedOptions = sortArray(constructOptions(), sort);
|
||||
updateOptions(sortedOptions);
|
||||
isInitialRender.current = false;
|
||||
}, [isMultiSelect, component?.id]);
|
||||
|
||||
const _renderOverlay = (item, index) => {
|
||||
|
|
@ -385,6 +403,12 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
|
|||
trigger="click"
|
||||
placement="left"
|
||||
rootClose
|
||||
onExited={() => {
|
||||
if (isSortingEnabled && sort !== 'none') {
|
||||
const sortedOptions = sortArray([...options], sort);
|
||||
updateOptions(sortedOptions);
|
||||
}
|
||||
}}
|
||||
overlay={_renderOverlay(item, index)}
|
||||
onToggle={(isOpen) => {
|
||||
if (!isOpen) {
|
||||
|
|
@ -515,6 +539,17 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
|
|||
currentState,
|
||||
allComponents
|
||||
)}
|
||||
{isSortingEnabled &&
|
||||
renderElement(
|
||||
component,
|
||||
componentMeta,
|
||||
paramUpdated,
|
||||
dataQueries,
|
||||
'sort',
|
||||
'properties',
|
||||
currentState,
|
||||
allComponents
|
||||
)}
|
||||
</>
|
||||
),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -63,6 +63,18 @@ export const dropdownV2Config = {
|
|||
},
|
||||
accordian: 'Options',
|
||||
},
|
||||
sort: {
|
||||
type: 'switch',
|
||||
displayName: 'Sort options',
|
||||
validation: { schema: { type: 'string' }, defaultValue: 'asc' },
|
||||
options: [
|
||||
{ displayName: 'None', value: 'none' },
|
||||
{ displayName: 'a-z', value: 'asc' },
|
||||
{ displayName: 'z-a', value: 'desc' },
|
||||
],
|
||||
accordian: 'Options',
|
||||
isFxNotRequired: true,
|
||||
},
|
||||
loadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Loading state',
|
||||
|
|
@ -300,6 +312,7 @@ export const dropdownV2Config = {
|
|||
},
|
||||
label: { value: 'Select' },
|
||||
optionsLoadingState: { value: '{{false}}' },
|
||||
sort: { value: 'asc' },
|
||||
placeholder: { value: 'Select an option' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -130,6 +130,18 @@ export const multiselectV2Config = {
|
|||
},
|
||||
accordian: 'Options',
|
||||
},
|
||||
sort: {
|
||||
type: 'switch',
|
||||
displayName: 'Sort options',
|
||||
validation: { schema: { type: 'string' }, defaultValue: 'asc' },
|
||||
options: [
|
||||
{ displayName: 'None', value: 'none' },
|
||||
{ displayName: 'a-z', value: 'asc' },
|
||||
{ displayName: 'z-a', value: 'desc' },
|
||||
],
|
||||
accordian: 'Options',
|
||||
isFxNotRequired: true,
|
||||
},
|
||||
loadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Loading state',
|
||||
|
|
@ -313,6 +325,7 @@ export const multiselectV2Config = {
|
|||
advanced: { value: `{{false}}` },
|
||||
showAllOption: { value: '{{false}}' },
|
||||
optionsLoadingState: { value: '{{false}}' },
|
||||
sort: { value: 'asc' },
|
||||
placeholder: { value: 'Select the options' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import CustomMenuList from './CustomMenuList';
|
|||
import CustomOption from './CustomOption';
|
||||
import Label from '@/_ui/Label';
|
||||
import cx from 'classnames';
|
||||
import { getInputBackgroundColor, getInputBorderColor, getInputFocusedColor } from './utils';
|
||||
import { getInputBackgroundColor, getInputBorderColor, getInputFocusedColor, sortArray } from './utils';
|
||||
import { isMobileDevice } from '@/_helpers/appUtils';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
|
||||
|
|
@ -68,6 +68,7 @@ export const DropdownV2 = ({
|
|||
loadingState: dropdownLoadingState,
|
||||
disabledState,
|
||||
optionsLoadingState,
|
||||
sort,
|
||||
} = properties;
|
||||
const {
|
||||
selectedTextColor,
|
||||
|
|
@ -116,6 +117,7 @@ export const DropdownV2 = ({
|
|||
const foundItem = _schema?.find((item) => item?.default === true);
|
||||
return !hasVisibleFalse(foundItem?.value) ? foundItem?.value : undefined;
|
||||
}
|
||||
|
||||
const selectOptions = useMemo(() => {
|
||||
let _options = advanced ? schema : options;
|
||||
if (Array.isArray(_options)) {
|
||||
|
|
@ -128,11 +130,11 @@ export const DropdownV2 = ({
|
|||
isDisabled: data?.disable ?? false,
|
||||
}));
|
||||
|
||||
return _selectOptions;
|
||||
return sortArray(_selectOptions, sort);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}, [advanced, schema, options]);
|
||||
}, [advanced, schema, options, sort]);
|
||||
|
||||
function selectOption(value) {
|
||||
const val = selectOptions.filter((option) => !option.isDisabled)?.find((option) => option.value === value);
|
||||
|
|
|
|||
|
|
@ -67,3 +67,12 @@ export const highlightText = (text = '', highlight) => {
|
|||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const sortArray = (arr, sort) => {
|
||||
if (sort === 'asc') {
|
||||
return arr.sort((a, b) => a.label?.localeCompare(b.label));
|
||||
} else if (sort === 'desc') {
|
||||
return arr.sort((a, b) => b.label?.localeCompare(a.label));
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import cx from 'classnames';
|
|||
import Label from '@/_ui/Label';
|
||||
const tinycolor = require('tinycolor2');
|
||||
import { CustomDropdownIndicator, CustomClearIndicator } from '../DropdownV2/DropdownV2';
|
||||
import { getInputBackgroundColor, getInputBorderColor, getInputFocusedColor } from '../DropdownV2/utils';
|
||||
import { getInputBackgroundColor, getInputBorderColor, getInputFocusedColor, sortArray } from '../DropdownV2/utils';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
|
||||
export const MultiselectV2 = ({
|
||||
|
|
@ -37,6 +37,7 @@ export const MultiselectV2 = ({
|
|||
placeholder,
|
||||
loadingState: multiSelectLoadingState,
|
||||
optionsLoadingState,
|
||||
sort,
|
||||
} = properties;
|
||||
const {
|
||||
selectedTextColor,
|
||||
|
|
@ -92,17 +93,17 @@ export const MultiselectV2 = ({
|
|||
const _options = advanced ? schema : options;
|
||||
let _selectOptions = Array.isArray(_options)
|
||||
? _options
|
||||
.filter((data) => data?.visible ?? true)
|
||||
.map((data) => ({
|
||||
...data,
|
||||
label: data?.label,
|
||||
value: data?.value,
|
||||
isDisabled: data?.disable ?? false,
|
||||
}))
|
||||
.filter((data) => data?.visible ?? true)
|
||||
.map((data) => ({
|
||||
...data,
|
||||
label: data?.label,
|
||||
value: data?.value,
|
||||
isDisabled: data?.disable ?? false,
|
||||
}))
|
||||
: [];
|
||||
return _selectOptions;
|
||||
return sortArray(_selectOptions, sort);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [advanced, JSON.stringify(schema), JSON.stringify(options)]);
|
||||
}, [advanced, JSON.stringify(schema), JSON.stringify(options), sort]);
|
||||
|
||||
function findDefaultItem(value, isAdvanced, isDefault) {
|
||||
if (isAdvanced) {
|
||||
|
|
@ -364,8 +365,8 @@ export const MultiselectV2 = ({
|
|||
selectedTextColor !== '#1B1F24'
|
||||
? selectedTextColor
|
||||
: isMultiSelectLoading || isMultiSelectDisabled
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
}),
|
||||
|
||||
input: (provided, _state) => ({
|
||||
|
|
@ -400,10 +401,10 @@ export const MultiselectV2 = ({
|
|||
color: _state.isDisabled
|
||||
? 'var(_--text-disbled)'
|
||||
: selectedTextColor !== '#1B1F24'
|
||||
? selectedTextColor
|
||||
: isMultiSelectDisabled || isMultiSelectLoading
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
? selectedTextColor
|
||||
: isMultiSelectDisabled || isMultiSelectLoading
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
borderRadius: _state.isFocused && '8px',
|
||||
padding: '8px 6px 8px 12px',
|
||||
'&:hover': {
|
||||
|
|
@ -435,7 +436,7 @@ export const MultiselectV2 = ({
|
|||
data-cy={`label-${String(componentName).toLowerCase()} `}
|
||||
className={cx('multiselect-widget', 'd-flex', {
|
||||
[alignment === 'top' &&
|
||||
((labelWidth != 0 && label?.length != 0) || (auto && labelWidth == 0 && label && label?.length != 0))
|
||||
((labelWidth != 0 && label?.length != 0) || (auto && labelWidth == 0 && label && label?.length != 0))
|
||||
? 'flex-column'
|
||||
: 'align-items-center']: true,
|
||||
'flex-row-reverse': direction === 'right' && alignment === 'side',
|
||||
|
|
|
|||
|
|
@ -63,6 +63,18 @@ export const dropdownV2Config = {
|
|||
},
|
||||
accordian: 'Options',
|
||||
},
|
||||
sort: {
|
||||
type: 'switch',
|
||||
displayName: 'Sort options',
|
||||
validation: { schema: { type: 'string' }, defaultValue: 'asc' },
|
||||
options: [
|
||||
{ displayName: 'None', value: 'none' },
|
||||
{ displayName: 'a-z', value: 'asc' },
|
||||
{ displayName: 'z-a', value: 'desc' },
|
||||
],
|
||||
accordian: 'Options',
|
||||
isFxNotRequired: true,
|
||||
},
|
||||
loadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Loading state',
|
||||
|
|
@ -300,6 +312,7 @@ export const dropdownV2Config = {
|
|||
},
|
||||
label: { value: 'Select' },
|
||||
optionsLoadingState: { value: '{{false}}' },
|
||||
sort: { value: 'asc' },
|
||||
placeholder: { value: 'Select an option' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -130,6 +130,18 @@ export const multiselectV2Config = {
|
|||
},
|
||||
accordian: 'Options',
|
||||
},
|
||||
sort: {
|
||||
type: 'switch',
|
||||
displayName: 'Sort options',
|
||||
validation: { schema: { type: 'string' }, defaultValue: 'asc' },
|
||||
options: [
|
||||
{ displayName: 'None', value: 'none' },
|
||||
{ displayName: 'a-z', value: 'asc' },
|
||||
{ displayName: 'z-a', value: 'desc' },
|
||||
],
|
||||
accordian: 'Options',
|
||||
isFxNotRequired: true,
|
||||
},
|
||||
loadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Loading state',
|
||||
|
|
@ -313,6 +325,7 @@ export const multiselectV2Config = {
|
|||
advanced: { value: `{{false}}` },
|
||||
showAllOption: { value: '{{false}}' },
|
||||
optionsLoadingState: { value: '{{false}}' },
|
||||
sort: { value: 'asc' },
|
||||
placeholder: { value: 'Select the options' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -63,6 +63,18 @@ export const dropdownV2Config = {
|
|||
},
|
||||
accordian: 'Options',
|
||||
},
|
||||
sort: {
|
||||
type: 'switch',
|
||||
displayName: 'Sort options',
|
||||
validation: { schema: { type: 'string' }, defaultValue: 'asc' },
|
||||
options: [
|
||||
{ displayName: 'None', value: 'none' },
|
||||
{ displayName: 'a-z', value: 'asc' },
|
||||
{ displayName: 'z-a', value: 'desc' },
|
||||
],
|
||||
accordian: 'Options',
|
||||
isFxNotRequired: true,
|
||||
},
|
||||
loadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Loading state',
|
||||
|
|
@ -300,6 +312,7 @@ export const dropdownV2Config = {
|
|||
},
|
||||
label: { value: 'Select' },
|
||||
optionsLoadingState: { value: '{{false}}' },
|
||||
sort: { value: 'asc' },
|
||||
placeholder: { value: 'Select an option' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -130,6 +130,18 @@ export const multiselectV2Config = {
|
|||
},
|
||||
accordian: 'Options',
|
||||
},
|
||||
sort: {
|
||||
type: 'switch',
|
||||
displayName: 'Sort options',
|
||||
validation: { schema: { type: 'string' }, defaultValue: 'asc' },
|
||||
options: [
|
||||
{ displayName: 'None', value: 'none' },
|
||||
{ displayName: 'a-z', value: 'asc' },
|
||||
{ displayName: 'z-a', value: 'desc' },
|
||||
],
|
||||
accordian: 'Options',
|
||||
isFxNotRequired: true,
|
||||
},
|
||||
loadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Loading state',
|
||||
|
|
@ -313,6 +325,7 @@ export const multiselectV2Config = {
|
|||
advanced: { value: `{{false}}` },
|
||||
showAllOption: { value: '{{false}}' },
|
||||
optionsLoadingState: { value: '{{false}}' },
|
||||
sort: { value: 'asc' },
|
||||
placeholder: { value: 'Select the options' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
|
|
|
|||
Loading…
Reference in a new issue