personal-page/_src/components/Inputs/SelectInput.tsx

142 lines
3.3 KiB
TypeScript

import React, { useState, useRef, useEffect } from 'react';
import styles from './SelectInput.module.css';
interface Option {
label: string;
value: any;
}
interface Props {
onChange: (value: string | string[] | null) => void;
options: Option[];
isMultiple?: boolean;
value?: string | string[];
}
export default function SelectInput({
options,
isMultiple = false,
onChange,
value = [],
}: Props): JSX.Element {
const [selected, setSelected] = useState<string[]>([]);
const [filteredOptions, setFilteredOptions] = useState<Props['options']>([]);
const [isOptionsOpen, setIsOptionsOpen] = useState<boolean>(false);
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
setFilteredOptions(options);
}, [options]);
useEffect(() => {
if (selected.length === 0) {
onChange(null);
return;
}
onChange(isMultiple ? selected : selected[0]);
}, [selected]);
const handleFocusInput = (): void => {
setIsOptionsOpen(true);
inputRef.current?.focus();
};
const handleAddElement = (item: any): void => {
setSelected((prev) => {
if (isMultiple) {
return [...prev, item];
}
return [item];
});
setIsOptionsOpen(false);
if (inputRef.current === null) return;
inputRef.current.value = '';
};
const handleRemoveElement = (idx: number): void => {
setIsOptionsOpen(false);
setSelected((prev) => {
prev.splice(idx, 1);
return [...prev];
});
};
const handleLooseFocus = (e: React.FocusEvent<HTMLDivElement>): void => {
if (!e.currentTarget.contains(e.relatedTarget)) {
// Not triggered when swapping focus between children
setIsOptionsOpen(false);
}
};
const handleFilterOptions = ({
target,
}: React.ChangeEvent<HTMLInputElement>): void => {
if (target.value === '') {
setFilteredOptions(options);
return;
}
const newOptions = options.filter(
(item) =>
item.label.toLowerCase().search(target.value.toLowerCase()) !== -1,
);
setFilteredOptions(newOptions);
};
return (
<div className={styles.wrapper} onBlur={handleLooseFocus}>
<div
className={styles.input}
onClick={() => {
handleFocusInput();
}}
>
{selected.map((item, idx) => (
<div className={styles.selectedItem + ' hstack'} key={idx}>
<div>{item}</div>
<div
className={styles.deleteItem}
onClick={() => {
handleRemoveElement(idx);
}}
>
{'X'}
</div>
</div>
))}
<input
ref={inputRef}
className={styles.realInput}
type="text"
onChange={handleFilterOptions}
/>
</div>
<button
onClick={() => {
onChange(null);
setSelected([]);
}}
>
X
</button>
<div className={styles.optionList} hidden={!isOptionsOpen}>
{filteredOptions.map((item, idx) => (
<button
className={styles.optionItem}
key={idx}
disabled={selected.includes(item.value)}
onClick={() => {
handleAddElement(item.value);
}}
>
{item.label}
</button>
))}
</div>
</div>
);
}