mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-24 09:28:31 +00:00
* Fix max canvas width select broken * Added padding right instead of setting width to avoid overlapping of numbers on the arrow Added padding right to provide spacing to right side of number container * fix : focusing on any cell of number column type, results into first cell getting focused * fix : text alignment bug in string column type * fixed : text alignmnet of editable div in string column type * Fix : avoid clicking thrice on element to start editing the content * Fix : Overlay issue in deprecated columns * Fix : multiselect deprecated secondary text not appearing on column list * Fix table fixes * fix placeholder in multiselect dynamic options * fix: fix for scrollbar in viewer * fix: integration bugfixes for table (#9457) * change label for link color * remove alt text for cell image * fix : number arrow , labels * limit table popover max height * Bug fixes from appbuilder-1.6 (#9465) * Fix : Selecting same value again is not discarding the selected value in single select * If default options list is empty then return null * Fix : Deleting the default option will rresult into removing from default option list as well * showing error message at the bottom and cell value in the center for text and string column type * Making Pagination aligned in the center horizontally * Fix: Download popup * Fix add new row string text not changing its color in dark mode * Fix date picker on change in add new row not changing when value is empty * fix : table default ui (#9454) * fix : muliselect popover not showing (#9455) * update default number data from string to number (#9467) * Fix in datepicker * fix: add fixed height to error feedback * fix: fixed issue that caused multiselect option delete to fail (#9469) * fix: fixed issue that caused multiselect option delete to fail * refactor: minor code cleanup * fix : ui for table multislect * remove logs --------- Co-authored-by: Nakul Nagargade <nakul@tooljet.com> Co-authored-by: manishkushare <kushare.manish9@gmail.com> Co-authored-by: Johnson Cherian <johnsonc.dev@gmail.com> Co-authored-by: Manish Kushare <37823141+manishkushare@users.noreply.github.com>
326 lines
9.5 KiB
JavaScript
326 lines
9.5 KiB
JavaScript
import React, { useRef, useState, useEffect, useMemo } from 'react';
|
|
import Select from '@/_ui/Select';
|
|
import { components } from 'react-select';
|
|
import defaultStyles from '@/_ui/Select/styles';
|
|
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
|
import { Checkbox } from '@/_ui/CheckBox/CheckBox';
|
|
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
|
|
import { isArray, isString } from 'lodash';
|
|
const { MenuList } = components;
|
|
|
|
export const CustomSelect = ({
|
|
options,
|
|
value,
|
|
onChange,
|
|
fuzzySearch = false,
|
|
placeholder,
|
|
disabled,
|
|
className,
|
|
darkMode,
|
|
defaultOptionsList,
|
|
textColor = '',
|
|
isMulti,
|
|
containerWidth,
|
|
optionsLoadingState = false,
|
|
horizontalAlignment = 'left',
|
|
isEditable,
|
|
}) => {
|
|
const containerRef = useRef(null);
|
|
const inputRef = useRef(null); // Ref for the input search box
|
|
const [isFocused, setIsFocused] = useState(false);
|
|
useEffect(() => {
|
|
const handleDocumentClick = (event) => {
|
|
if (isMulti && !containerRef.current?.contains(event.target)) {
|
|
setIsFocused(false);
|
|
}
|
|
};
|
|
|
|
document.addEventListener('mousedown', handleDocumentClick);
|
|
|
|
return () => {
|
|
document.removeEventListener('mousedown', handleDocumentClick);
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
// Focus the input search box when the menu list is open and the component is focused
|
|
if (isFocused && inputRef.current) {
|
|
inputRef.current.focus();
|
|
}
|
|
}, [isFocused]);
|
|
|
|
const customStyles = {
|
|
...defaultStyles(darkMode, '100%'),
|
|
...(isMulti && {
|
|
multiValue: (provided) => ({
|
|
...provided,
|
|
display: 'inline-block', // Display selected options inline
|
|
marginRight: '4px', // Add some space between options
|
|
}),
|
|
}),
|
|
valueContainer: (provided) => ({
|
|
...provided,
|
|
...(isMulti && {
|
|
marginBottom: '0',
|
|
display: 'flex',
|
|
flexWrap: 'no-wrap',
|
|
overflow: 'hidden',
|
|
flexDirection: 'row',
|
|
}),
|
|
justifyContent: horizontalAlignment,
|
|
}),
|
|
menuList: (base) => ({
|
|
...base,
|
|
backgroundColor: 'var(--surfaces-surface-01) ',
|
|
color: 'var(--text-primary)',
|
|
cursor: 'pointer',
|
|
overflow: 'auto',
|
|
}),
|
|
multiValueLabel: (provided) => ({
|
|
...provided,
|
|
padding: '2px 6px',
|
|
background: 'var(--surfaces-surface-03)',
|
|
borderRadius: '6px',
|
|
color: textColor || 'var(--text-primary)',
|
|
fontSize: '12px',
|
|
}),
|
|
singleValue: (provided) => ({
|
|
...provided,
|
|
padding: '2px 6px',
|
|
background: 'var(--surfaces-surface-03)',
|
|
borderRadius: '6px',
|
|
color: textColor || 'var(--text-primary)',
|
|
fontSize: '12px',
|
|
}),
|
|
};
|
|
const customComponents = {
|
|
MenuList: (props) => <CustomMenuList {...props} optionsLoadingState={optionsLoadingState} inputRef={inputRef} />,
|
|
Option: CustomMultiSelectOption,
|
|
DropdownIndicator: isEditable ? DropdownIndicator : null,
|
|
...(isMulti && {
|
|
MultiValueRemove,
|
|
MultiValueContainer: CustomMultiValueContainer,
|
|
}),
|
|
};
|
|
const defaultValue = useMemo(() => {
|
|
if (defaultOptionsList.length >= 1) {
|
|
return !isMulti ? defaultOptionsList[defaultOptionsList.length - 1] : defaultOptionsList;
|
|
} else {
|
|
return isMulti ? [] : {};
|
|
}
|
|
}, [isMulti, defaultOptionsList]);
|
|
|
|
const _value = useMemo(() => {
|
|
// Return null to show default value
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
if (isMulti && value?.length) {
|
|
if (isArray(value)) {
|
|
return options?.filter((option) =>
|
|
value?.find((val) => {
|
|
if (val.hasOwnProperty('value')) {
|
|
return option.value === val.value;
|
|
} else return option.value === val;
|
|
})
|
|
);
|
|
} else {
|
|
return []; // Return empty array to not show default value in case of wrong value to be set
|
|
}
|
|
} else {
|
|
// Condition for single select
|
|
return options?.find((option) => option.value === value) || [];
|
|
}
|
|
}, [options, value, isMulti]);
|
|
|
|
const selectContainerRef = useRef(null); // Ref for the input search box
|
|
const containerHeight = selectContainerRef.current?.clientHeight;
|
|
const valueContainerHeight = selectContainerRef.current?.querySelector(
|
|
'.react-select__value-container'
|
|
)?.clientHeight;
|
|
|
|
return (
|
|
<OverlayTrigger
|
|
placement="bottom"
|
|
overlay={
|
|
isMulti && (_value?.length || defaultValue?.length) && !isFocused ? (
|
|
getOverlay(_value ? _value : defaultValue, containerWidth, darkMode)
|
|
) : (
|
|
<div></div>
|
|
)
|
|
}
|
|
trigger={isMulti && !isFocused && valueContainerHeight > containerHeight && ['hover', 'focus']} //container width -24 -16 gives that select container size
|
|
rootClose={true}
|
|
>
|
|
<div className="w-100 h-100 d-flex align-items-center" ref={selectContainerRef}>
|
|
<Select
|
|
options={options}
|
|
hasSearch={false}
|
|
fuzzySearch={fuzzySearch}
|
|
isDisabled={disabled}
|
|
className={className}
|
|
components={customComponents}
|
|
value={_value}
|
|
onMenuInputFocus={() => setIsFocused(true)}
|
|
onChange={(value) => {
|
|
setIsFocused(false);
|
|
if (!isMulti && value === _value?.value) {
|
|
// for single select column type if on change value is similar to current value , then discard the current value
|
|
onChange('');
|
|
} else {
|
|
onChange(value);
|
|
}
|
|
}}
|
|
useCustomStyles={true}
|
|
styles={customStyles}
|
|
defaultValue={defaultValue}
|
|
placeholder={placeholder}
|
|
isMulti={isMulti}
|
|
hideSelectedOptions={false}
|
|
isClearable={false}
|
|
clearIndicator={false}
|
|
darkMode={darkMode}
|
|
{...{
|
|
menuIsOpen: isFocused || undefined,
|
|
isFocused: isFocused || undefined,
|
|
}}
|
|
/>
|
|
</div>
|
|
</OverlayTrigger>
|
|
);
|
|
};
|
|
|
|
const CustomMenuList = ({ optionsLoadingState, children, selectProps, inputRef, ...props }) => {
|
|
const { onInputChange, inputValue, onMenuInputFocus } = selectProps;
|
|
|
|
return (
|
|
<div className="table-select-custom-menu-list" onClick={(e) => e.stopPropagation()}>
|
|
<div className="table-select-column-type-search-box-wrapper ">
|
|
{!inputValue && (
|
|
<span className="">
|
|
<SolidIcon name="search" width="14" />
|
|
</span>
|
|
)}
|
|
<input
|
|
autoCorrect="off"
|
|
autoComplete="off"
|
|
spellCheck="false"
|
|
type="text"
|
|
value={inputValue}
|
|
onChange={(e) =>
|
|
onInputChange(e.currentTarget.value, {
|
|
action: 'input-change',
|
|
})
|
|
}
|
|
onMouseDown={(e) => {
|
|
e.stopPropagation();
|
|
e.target.focus();
|
|
}}
|
|
onTouchEnd={(e) => {
|
|
e.stopPropagation();
|
|
e.target.focus();
|
|
}}
|
|
onFocus={onMenuInputFocus}
|
|
placeholder="Search..."
|
|
className="table-select-column-type-search-box"
|
|
ref={inputRef} // Assign the ref to the input search box
|
|
/>
|
|
</div>
|
|
<MenuList {...props} selectProps={selectProps}>
|
|
{optionsLoadingState ? (
|
|
<div class="text-center py-4">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="sr-only"></span>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
children
|
|
)}
|
|
</MenuList>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const CustomMultiSelectOption = ({ innerRef, innerProps, children, isSelected, ...props }) => {
|
|
return (
|
|
<div ref={innerRef} {...innerProps} className="option-wrapper d-flex">
|
|
{props.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>
|
|
)}
|
|
{children}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const MultiValueRemove = (props) => {
|
|
const { innerProps } = props;
|
|
return <div {...innerProps} />;
|
|
};
|
|
const CustomMultiValueContainer = (props) => {
|
|
return (
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
flexWrap: 'wrap',
|
|
alignItems: 'center',
|
|
}}
|
|
>
|
|
{props.children}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const getOverlay = (value, containerWidth, darkMode) => {
|
|
const getLabel = (option) => {
|
|
if (option?.hasOwnProperty('label')) {
|
|
return option.label;
|
|
} else if (isString(option)) {
|
|
return option;
|
|
} else return '';
|
|
};
|
|
|
|
return Array.isArray(value) ? (
|
|
<div
|
|
style={{
|
|
maxWidth: '266px',
|
|
}}
|
|
className={`overlay-cell-table overlay-multiselect-table ${darkMode && 'dark-theme'}`}
|
|
>
|
|
{value?.map((option) => {
|
|
return (
|
|
<span
|
|
style={{
|
|
padding: '2px 6px',
|
|
background: 'var(--surfaces-surface-03)',
|
|
borderRadius: '6px',
|
|
color: 'var(--text-primary)',
|
|
fontSize: '12px',
|
|
}}
|
|
key={getLabel(option)}
|
|
>
|
|
{getLabel(option)}
|
|
</span>
|
|
);
|
|
})}
|
|
</div>
|
|
) : (
|
|
<div></div>
|
|
);
|
|
};
|
|
|
|
const DropdownIndicator = (props) => {
|
|
return (
|
|
<div {...props} className="cell-icon-display">
|
|
{/* Your custom SVG */}
|
|
{props.selectProps.menuIsOpen ? (
|
|
<SolidIcon name="arrowUpTriangle" width="16" height="16" fill={'#6A727C'} />
|
|
) : (
|
|
<SolidIcon name="arrowDownTriangle" width="16" height="16" fill={'#6A727C'} />
|
|
)}
|
|
</div>
|
|
);
|
|
};
|