ui: refactor local autocomplete input

This commit is contained in:
nick-delirium 2024-12-11 17:17:00 +01:00
parent 1385ba40a1
commit e13804009a
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
4 changed files with 745 additions and 649 deletions

View file

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

View file

@ -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;

View file

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

File diff suppressed because it is too large Load diff