frigate/web/src/components/Select.jsx

212 lines
6.2 KiB
React
Raw Normal View History

2021-02-02 07:28:25 +03:00
import { h, Fragment } from 'preact';
import ArrowDropdown from '../icons/ArrowDropdown';
import ArrowDropup from '../icons/ArrowDropup';
import Menu, { MenuItem } from './Menu';
import TextField from './TextField';
2021-07-16 09:42:14 +03:00
import DatePicker from './DatePicker';
import Calender from './Calender';
2021-02-02 07:28:25 +03:00
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
2021-07-16 09:42:14 +03:00
export default function Select({
type,
label,
onChange,
paramName,
options: inputOptions = [],
selected: propSelected,
}) {
2021-02-02 07:28:25 +03:00
const options = useMemo(
() =>
2021-07-16 09:42:14 +03:00
typeof inputOptions[1] === 'string' ? inputOptions.map((opt) => ({ value: opt, label: opt })) : inputOptions,
2021-02-02 07:28:25 +03:00
[inputOptions]
);
2021-07-16 09:42:14 +03:00
2021-02-02 07:28:25 +03:00
const [showMenu, setShowMenu] = useState(false);
2021-07-16 09:42:14 +03:00
const [selected, setSelected] = useState();
2021-07-17 12:12:17 +03:00
const [datePickerValue, setDatePickerValue] = useState();
2021-02-02 07:28:25 +03:00
2021-07-16 09:42:14 +03:00
useEffect(() => {
2021-07-17 12:12:17 +03:00
if (type === 'datepicker' && 'after' && 'before' in propSelected) {
for (let i = 0; i < inputOptions.length; i++) {
if (
inputOptions[i].value &&
Object.entries(inputOptions[i].value).sort().toString() === Object.entries(propSelected).sort().toString()
) {
setDatePickerValue(inputOptions[i]?.label);
break;
} else {
setDatePickerValue(
`${new Date(propSelected.after * 1000).toLocaleDateString()} -> ${new Date(
propSelected.before * 1000 - 1
).toLocaleDateString()}`
);
}
}
}
if (type === 'dropdown') {
setSelected(
Math.max(
options.findIndex(({ value }) => Object.values(propSelected).includes(value)),
0
)
);
}
}, [inputOptions, propSelected, setSelected]);
2021-07-16 09:42:14 +03:00
const [focused, setFocused] = useState(null);
const [showDatePicker, setShowDatePicker] = useState(false);
const calenderRef = useRef(null);
2021-02-02 07:28:25 +03:00
const ref = useRef(null);
const handleSelect = useCallback(
(value, label) => {
setSelected(options.findIndex((opt) => opt.value === value));
2021-07-16 09:42:14 +03:00
setShowMenu(false);
if (!value) return setShowDatePicker(true);
2021-02-02 07:28:25 +03:00
onChange && onChange(value, label);
2021-07-16 09:42:14 +03:00
},
[onChange, options]
);
const handleDateRange = useCallback(
(range) => {
// setSelected(options.findIndex((opt) => opt.value === value));
onChange && onChange(range, 'range');
2021-02-02 07:28:25 +03:00
setShowMenu(false);
},
[onChange, options]
2021-02-02 07:28:25 +03:00
);
const handleClick = useCallback(() => {
setShowMenu(true);
}, [setShowMenu]);
const handleKeydown = useCallback(
(event) => {
switch (event.key) {
2021-02-11 18:16:35 +03:00
case 'Enter': {
if (!showMenu) {
setShowMenu(true);
setFocused(selected);
} else {
setSelected(focused);
onChange && onChange(options[focused].value, options[focused].label);
setShowMenu(false);
}
break;
2021-02-02 07:28:25 +03:00
}
2021-02-11 18:16:35 +03:00
case 'ArrowDown': {
const newIndex = focused + 1;
newIndex < options.length && setFocused(newIndex);
break;
}
2021-02-02 07:28:25 +03:00
2021-02-11 18:16:35 +03:00
case 'ArrowUp': {
const newIndex = focused - 1;
newIndex > -1 && setFocused(newIndex);
break;
}
// no default
2021-02-02 07:28:25 +03:00
}
},
[onChange, options, showMenu, setShowMenu, setFocused, focused, selected]
2021-02-02 07:28:25 +03:00
);
const handleDismiss = useCallback(() => {
setShowMenu(false);
}, [setShowMenu]);
// Reset the state if the prop value changes
useEffect(() => {
const selectedIndex = Math.max(
options.findIndex(({ value }) => value === propSelected),
0
);
if (propSelected && selectedIndex !== selected) {
setSelected(selectedIndex);
setFocused(selectedIndex);
}
// DO NOT include `selected`
}, [options, propSelected]); // eslint-disable-line react-hooks/exhaustive-deps
2021-02-02 07:28:25 +03:00
2021-07-16 09:42:14 +03:00
useEffect(() => {
window.addEventListener('click', addBackDrop);
// setDateToInput(state.selectedDay);
return function cleanup() {
window.removeEventListener('click', addBackDrop);
};
}, [showDatePicker]);
const findDOMNode = (component) => {
return (component && (component.base || (component.nodeType === 1 && component))) || null;
};
const addBackDrop = (e) => {
if (showDatePicker && !findDOMNode(calenderRef.current).contains(e.target)) {
setShowDatePicker(false);
}
};
switch (type) {
case 'datepicker':
return (
<Fragment>
<DatePicker
inputRef={ref}
label={label}
onchange={onChange}
onclick={handleClick}
onkeydown={handleKeydown}
trailingIcon={showMenu ? ArrowDropup : ArrowDropdown}
2021-07-17 12:12:17 +03:00
value={datePickerValue}
/>
2021-07-16 09:42:14 +03:00
{showDatePicker && (
<Menu className="rounded-t-none" onDismiss={handleDismiss} relativeTo={ref}>
<Calender onChange={handleDateRange} calenderRef={calenderRef} />
</Menu>
)}
{showMenu ? (
<Menu className="rounded-t-none" onDismiss={handleDismiss} relativeTo={ref} widthRelative>
{options.map(({ value, label }, i) => (
<MenuItem key={value} label={label} focus={focused === i} onSelect={handleSelect} value={value} />
))}
</Menu>
) : null}
</Fragment>
);
case 'dropdown':
return (
<Fragment>
<TextField
inputRef={ref}
label={label}
onchange={onChange}
onclick={handleClick}
onkeydown={handleKeydown}
readonly
trailingIcon={showMenu ? ArrowDropup : ArrowDropdown}
value={options[selected]?.label}
/>
{showMenu ? (
<Menu className="rounded-t-none" onDismiss={handleDismiss} relativeTo={ref} widthRelative>
{options.map(({ value, label }, i) => (
<MenuItem
key={value}
label={label}
focus={focused === i}
onSelect={handleSelect}
2021-07-17 12:12:17 +03:00
value={{ [paramName]: value }}
2021-07-16 09:42:14 +03:00
/>
))}
</Menu>
) : null}
</Fragment>
);
default:
return <div />;
}
2021-02-02 07:28:25 +03:00
}