ui: refactor local autocomplete input
This commit is contained in:
parent
1385ba40a1
commit
e13804009a
4 changed files with 745 additions and 649 deletions
|
|
@ -13,6 +13,7 @@ export function AutocompleteModal({
|
|||
options,
|
||||
isLoading,
|
||||
placeholder,
|
||||
commaQuery,
|
||||
}: {
|
||||
values: string[];
|
||||
onClose: () => void;
|
||||
|
|
@ -22,10 +23,11 @@ export function AutocompleteModal({
|
|||
options: { value: string; label: string }[];
|
||||
placeholder?: string;
|
||||
isLoading?: boolean;
|
||||
commaQuery?: boolean;
|
||||
}) {
|
||||
const [query, setQuery] = React.useState('');
|
||||
const [selectedValues, setSelectedValues] = React.useState<string[]>(
|
||||
values.filter((i) => i.length > 0)
|
||||
values.filter((i) => i && i.length > 0)
|
||||
);
|
||||
|
||||
const handleInputChange = (value: string) => {
|
||||
|
|
@ -48,6 +50,11 @@ export function AutocompleteModal({
|
|||
onApply(selectedValues);
|
||||
};
|
||||
|
||||
const applyQuery = () => {
|
||||
const vals = commaQuery ? query.split(',').map(i => i.trim()) : [query];
|
||||
onApply(vals);
|
||||
}
|
||||
|
||||
const sortedOptions = React.useMemo(() => {
|
||||
if (values[0] && values[0].length) {
|
||||
const sorted = options.sort((a, b) => {
|
||||
|
|
@ -56,13 +63,30 @@ export function AutocompleteModal({
|
|||
return sorted;
|
||||
}
|
||||
return options;
|
||||
}, [options.length, values]);
|
||||
}, [options, values]);
|
||||
|
||||
const queryBlocks = commaQuery ? query.split(',') : [query];
|
||||
const blocksAmount = queryBlocks.length;
|
||||
// x,y and z
|
||||
const queryStr = React.useMemo(() => {
|
||||
let str = ''
|
||||
queryBlocks.forEach((block, index) => {
|
||||
if (index === blocksAmount - 1 && blocksAmount > 1) {
|
||||
str += ' and '
|
||||
}
|
||||
str += `"${block.trim()}"`
|
||||
if (index < blocksAmount - 2) {
|
||||
str += ', '
|
||||
}
|
||||
})
|
||||
return str;
|
||||
}, [query])
|
||||
return (
|
||||
<OutsideClickDetectingDiv
|
||||
className={cn(
|
||||
'absolute left-0 mt-2 p-4 bg-white rounded-xl shadow border-gray-light z-10'
|
||||
)}
|
||||
style={{ minWidth: 320, minHeight: 100, top: '100%' }}
|
||||
style={{ width: 360, minHeight: 100, top: '100%' }}
|
||||
onClickOutside={() => {
|
||||
onClose();
|
||||
}}
|
||||
|
|
@ -73,12 +97,14 @@ export function AutocompleteModal({
|
|||
loading={isLoading}
|
||||
onChange={(e) => handleInputChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
className='rounded-lg'
|
||||
className="rounded-lg"
|
||||
/>
|
||||
<Loader loading={isLoading}>
|
||||
<>
|
||||
<div
|
||||
className={'flex flex-col gap-2 overflow-y-auto py-2'}
|
||||
className={
|
||||
'flex flex-col gap-2 overflow-y-auto py-2 overflow-x-hidden text-ellipsis'
|
||||
}
|
||||
style={{ maxHeight: 200 }}
|
||||
>
|
||||
{sortedOptions.map((item) => (
|
||||
|
|
@ -97,11 +123,11 @@ export function AutocompleteModal({
|
|||
<div className={'border-y border-y-gray-light py-2'}>
|
||||
<div
|
||||
className={
|
||||
'rounded cursor-pointer text-blue hover:bg-active-blue px-2 py-1'
|
||||
'whitespace-normal rounded cursor-pointer text-blue hover:bg-active-blue px-2 py-1'
|
||||
}
|
||||
onClick={() => onApply([query])}
|
||||
onClick={applyQuery}
|
||||
>
|
||||
Apply "{query}"
|
||||
Apply {queryStr}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
|
@ -130,7 +156,7 @@ interface Props {
|
|||
export function AutoCompleteContainer(props: Props) {
|
||||
const filterValueContainer = useRef<HTMLDivElement>(null);
|
||||
const [showValueModal, setShowValueModal] = useState(false);
|
||||
const isEmpty = props.value.length === 0 || !props.value[0].length;
|
||||
const isEmpty = props.value.length === 0 || !props.value[0];
|
||||
const onClose = () => setShowValueModal(false);
|
||||
const onApply = (values: string[]) => {
|
||||
props.onApplyValues(values);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||
import { Icon } from 'UI';
|
||||
import stl from './FilterAutoCompleteLocal.module.css';
|
||||
import { Input } from 'antd';
|
||||
import { AutocompleteModal, AutoCompleteContainer } from 'Shared/Filters/FilterAutoComplete/AutocompleteModal';
|
||||
|
||||
interface Props {
|
||||
showOrButton?: boolean;
|
||||
|
|
@ -15,89 +16,46 @@ interface Props {
|
|||
type?: string;
|
||||
isMultiple?: boolean;
|
||||
allowDecimals?: boolean;
|
||||
modalProps?: Record<string, any>;
|
||||
onApplyValues: (values: string[]) => void;
|
||||
}
|
||||
|
||||
function FilterAutoCompleteLocal(props: Props & { index: number }) {
|
||||
function FilterAutoCompleteLocal(props: { params: any, values: string[], onClose: () => void, onApply: (values: string[]) => void, placeholder?: string }) {
|
||||
const {
|
||||
showCloseButton = false,
|
||||
params = {},
|
||||
onClose,
|
||||
onApply,
|
||||
placeholder = 'Enter',
|
||||
showOrButton = false,
|
||||
onRemoveValue = () => null,
|
||||
onAddValue = () => null,
|
||||
value = '',
|
||||
type = 'text',
|
||||
isMultiple = true,
|
||||
allowDecimals = true,
|
||||
index,
|
||||
values,
|
||||
} = props;
|
||||
const [query, setQuery] = useState(value);
|
||||
|
||||
const onInputChange = (e) => {
|
||||
if (allowDecimals) {
|
||||
const value = e.target.value;
|
||||
setQuery(value);
|
||||
props.onSelect(null, value, index);
|
||||
} else {
|
||||
const value = e.target.value.replace(/[^\d]/, '');
|
||||
if (+value !== 0) {
|
||||
setQuery(value);
|
||||
props.onSelect(null, value, index);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setQuery(value);
|
||||
}, [value]);
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
props.onSelect(e, { value: query }, index);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center">
|
||||
<div className={stl.wrapper}>
|
||||
<Input
|
||||
name="query"
|
||||
onInput={onInputChange}
|
||||
value={query}
|
||||
autoFocus={true}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
<div className={stl.right}>
|
||||
{showCloseButton && (
|
||||
<div onClick={() => onRemoveValue(index)}>
|
||||
<Icon name="close" size="12" />
|
||||
</div>
|
||||
)}
|
||||
{showOrButton && isMultiple ? (
|
||||
<div onClick={() => onAddValue(index)} className="color-teal">
|
||||
<span className="px-1">or</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!showOrButton && isMultiple ? <div className="ml-2">or</div> : null}
|
||||
</div>
|
||||
const [options, setOptions] = useState<{ value: string; label: string }[]>(
|
||||
values.map((value) => ({ value, label: value }))
|
||||
);
|
||||
|
||||
const onApplyValues = (values: string[]) => {
|
||||
setOptions(values.map((value) => ({ value, label: value })));
|
||||
onApply(values);
|
||||
}
|
||||
|
||||
const splitValues = (value: string) => {
|
||||
const values = value.split(',').filter(v => v.length)
|
||||
setOptions(values.map((value) => ({ value, label: value })));
|
||||
}
|
||||
|
||||
return <AutocompleteModal
|
||||
values={values}
|
||||
onClose={onClose}
|
||||
onApply={onApplyValues}
|
||||
loadOptions={splitValues}
|
||||
options={options}
|
||||
isLoading={false}
|
||||
placeholder={placeholder}
|
||||
commaQuery
|
||||
/>
|
||||
}
|
||||
|
||||
function FilterLocalController(props: Props) {
|
||||
return props.value.map((value, index) => (
|
||||
<FilterAutoCompleteLocal
|
||||
{...props}
|
||||
key={index}
|
||||
index={index}
|
||||
showOrButton={index === props.value.length - 1}
|
||||
showCloseButton={props.value.length > 1}
|
||||
value={value}
|
||||
/>
|
||||
));
|
||||
return <AutoCompleteContainer {...props} modalRenderer={FilterAutoCompleteLocal} />
|
||||
}
|
||||
|
||||
export default FilterLocalController;
|
||||
|
|
|
|||
|
|
@ -95,10 +95,12 @@ function FilterValue(props: Props) {
|
|||
<FilterAutoCompleteLocal
|
||||
value={value}
|
||||
showCloseButton={showCloseButton}
|
||||
onAddValue={onAddValue}
|
||||
onApplyValues={onApplyValues}
|
||||
onRemoveValue={(index) => onRemoveValue(index)}
|
||||
onSelect={(e, item, index) => debounceOnSelect(e, item, index)}
|
||||
icon={filter.icon}
|
||||
placeholder={filter.placeholder}
|
||||
modalProps={{ placeholder: '' }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
1240
frontend/yarn.lock
1240
frontend/yarn.lock
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue