first commit
This commit is contained in:
122
admin/static/sections-autocomplete/SectionsWidget.tsx
Normal file
122
admin/static/sections-autocomplete/SectionsWidget.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import './styles.scss';
|
||||
import { memo, useEffect, useState, type KeyboardEvent } from 'react';
|
||||
import { useCache } from './cache';
|
||||
import { SectionRow } from './SectionRow';
|
||||
import { AutocompleteSections } from './Autocomplete';
|
||||
import type { WidgetProps } from './index';
|
||||
import { WidgetPropsProvider } from './utils';
|
||||
|
||||
function handleKeyboardNav(ev: KeyboardEvent<HTMLDivElement>) {
|
||||
const { currentTarget } = ev;
|
||||
|
||||
switch (ev.key) {
|
||||
case 'Escape':
|
||||
(currentTarget.querySelector('.form-control') as HTMLInputElement).focus();
|
||||
break;
|
||||
|
||||
case 'ArrowDown':
|
||||
if (!document.activeElement?.classList.contains('section-row')) {
|
||||
const firstFocusTarget = currentTarget.querySelector('.section-row') as HTMLDivElement | undefined;
|
||||
if (!firstFocusTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
firstFocusTarget.focus();
|
||||
firstFocusTarget.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
return;
|
||||
}
|
||||
|
||||
const nextSibling = document.activeElement.nextElementSibling as HTMLDivElement | undefined;
|
||||
if (nextSibling && nextSibling.classList.contains('section-row')) {
|
||||
nextSibling.focus();
|
||||
nextSibling.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ArrowUp':
|
||||
const prevSibling = document.activeElement?.previousElementSibling as HTMLDivElement | undefined;
|
||||
if (prevSibling && prevSibling.classList.contains('section-row')) {
|
||||
prevSibling.focus();
|
||||
prevSibling.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export function SectionsWidget(props: WidgetProps) {
|
||||
const [shouldShow, setShouldShow] = useState(false);
|
||||
const [value, setValue] = useState('');
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('');
|
||||
const [cache] = useCache();
|
||||
|
||||
useEffect(() => {
|
||||
function handleClick() {
|
||||
setShouldShow(false);
|
||||
}
|
||||
|
||||
document.addEventListener('click', handleClick);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClick);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => setDebouncedSearchTerm(value), 300);
|
||||
|
||||
return () => clearTimeout(timeout);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div
|
||||
onKeyUp={handleKeyboardNav}
|
||||
onKeyDown={(ev) => {
|
||||
if (ev.key === 'ArrowUp' || ev.key === 'ArrowDown') {
|
||||
ev.preventDefault();
|
||||
}
|
||||
}}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
setShouldShow(true);
|
||||
}}
|
||||
>
|
||||
<input
|
||||
onClick={() => {
|
||||
cache._fetchTopLevelSections();
|
||||
setShouldShow(true);
|
||||
}}
|
||||
type="text"
|
||||
className="form-control"
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
value={value}
|
||||
placeholder="Vyhledejte sekci"
|
||||
onFocus={() => {
|
||||
setShouldShow(true);
|
||||
setValue('');
|
||||
}}
|
||||
/>
|
||||
<WidgetPropsProvider value={{...props, hide: () => setShouldShow(false), show: () => setShouldShow(true) }}>
|
||||
{shouldShow && <WidgetPopover searchTerm={debouncedSearchTerm} />}
|
||||
</WidgetPropsProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const WidgetPopover = memo(({ searchTerm }: { searchTerm: string }) => {
|
||||
const topLevel = useCache('topLevelSections');
|
||||
|
||||
return (
|
||||
<div className="autocomplete-window">
|
||||
{!searchTerm && topLevel.map((section, i) => (
|
||||
<SectionRow
|
||||
section={section}
|
||||
key={`t${section.id}-${i}`}
|
||||
hasSubsections={!!section.has_subsections}
|
||||
/>
|
||||
))}
|
||||
{!!searchTerm && <AutocompleteSections searchTerm={searchTerm} />}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user