ToolJet/frontend/src/Editor/Components/Table/CustomSelect.jsx
Kiran Ashok ae8cf517f9
fix : Muliselect table UI (#9474)
* 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>
2024-04-24 12:42:21 +05:30

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>
);
};