first commit

This commit is contained in:
2025-08-02 16:30:27 +02:00
commit 23646bfcee
14851 changed files with 1750626 additions and 0 deletions

View 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>
);
});