Client: DropDown: Draft navigation variant

This commit is contained in:
Aleksandr Lushkin 2024-08-30 16:30:35 +02:00
parent eac40356c7
commit 797f2c83d6
3 changed files with 68 additions and 27 deletions

View File

@ -433,6 +433,7 @@ const DropDown = ({
getItemSize={getItemSize} getItemSize={getItemSize}
isOpen={open || false} isOpen={open || false}
enableKeyboardEvents={enableKeyboardEvents || false} enableKeyboardEvents={enableKeyboardEvents || false}
isDropdownReady={state.isDropdownReady}
> >
{children} {children}
</VirtualList> </VirtualList>

View File

@ -101,6 +101,7 @@ export interface VirtualListProps {
>; >;
enableKeyboardEvents: boolean; enableKeyboardEvents: boolean;
getItemSize: (index: number) => number; getItemSize: (index: number) => number;
isDropdownReady: boolean;
} }
export interface RowProps { export interface RowProps {

View File

@ -50,14 +50,16 @@ function VirtualList({
isNoFixedHeightOptions, isNoFixedHeightOptions,
getItemSize, getItemSize,
enableKeyboardEvents, enableKeyboardEvents,
isDropdownReady,
}: VirtualListProps) { }: VirtualListProps) {
const ref = useRef<VariableSizeList>(null); const ref = useRef<VariableSizeList>(null);
const focusTrapRef = useRef<HTMLDivElement>(null);
const activeIndex = useMemo(() => { const activeIndex = useMemo(() => {
let foundIndex = -1; let foundIndex = -1;
React.Children.forEach(cleanChildren, (child, index) => { React.Children.forEach(cleanChildren, (child, index) => {
const props = child && React.isValidElement(child) && child.props; const props = child && React.isValidElement(child) && child.props;
if (props?.disabled) foundIndex = index; if (props?.isSelected) foundIndex = index;
}); });
return foundIndex; return foundIndex;
}, [cleanChildren]); }, [cleanChildren]);
@ -67,9 +69,10 @@ function VirtualList({
const onKeyDown = useCallback( const onKeyDown = useCallback(
(event: KeyboardEvent) => { (event: KeyboardEvent) => {
if (!ref.current || !isOpen) return; if (!focusTrapRef.current || !isOpen) return;
event.preventDefault(); event.preventDefault();
event.stopPropagation();
let index = currentIndexRef.current; let index = currentIndexRef.current;
@ -96,26 +99,27 @@ function VirtualList({
setCurrentIndex(index); setCurrentIndex(index);
currentIndexRef.current = index; currentIndexRef.current = index;
ref.current.scrollToItem(index, "smart"); // ref.current.scrollToItem(index, "smart");
}, },
[isOpen, children], [isOpen, children],
); );
useEffect(() => { useEffect(() => {
if (isOpen && maxHeight && enableKeyboardEvents) { if (isOpen && isDropdownReady && enableKeyboardEvents) {
window.addEventListener("keydown", onKeyDown); focusTrapRef.current?.focus();
focusTrapRef.current?.addEventListener("keydown", onKeyDown);
} }
const refVar = ref.current; const refVar = ref.current;
return () => { return () => {
window.removeEventListener("keydown", onKeyDown); focusTrapRef.current?.removeEventListener("keydown", onKeyDown);
if (itemCount > 0 && refVar) { if (itemCount > 0 && refVar) {
setCurrentIndex(activeIndex); setCurrentIndex(activeIndex);
currentIndexRef.current = activeIndex; currentIndexRef.current = activeIndex;
refVar.scrollToItem(activeIndex, "smart"); // refVar.scrollToItem(activeIndex, "smart");
} }
}; };
}, [ }, [
@ -126,6 +130,7 @@ function VirtualList({
children, children,
onKeyDown, onKeyDown,
itemCount, itemCount,
isDropdownReady,
]); ]);
const handleMouseMove = useCallback((index: number) => { const handleMouseMove = useCallback((index: number) => {
@ -135,28 +140,62 @@ function VirtualList({
currentIndexRef.current = index; currentIndexRef.current = index;
}, []); }, []);
if (!maxHeight) return cleanChildren || children; const items = cleanChildren || children;
return isNoFixedHeightOptions ? ( if (!maxHeight)
<Scrollbar style={{ height: maxHeight }}>{cleanChildren}</Scrollbar> return (
) : ( <div
<VariableSizeList className="focus-trap"
ref={ref} style={{ outline: "none" }}
width={width} ref={focusTrapRef}
itemCount={itemCount} tabIndex={0}
itemSize={getItemSize} >
height={calculatedHeight} {items?.map((item, index) => (
itemData={{ <Row
children: cleanChildren, key={index}
theme, data={{
activeIndex, children: cleanChildren,
activedescendant: currentIndex, theme,
handleMouseMove, activeIndex,
}} activedescendant: currentIndex,
outerElementType={Scrollbar} handleMouseMove,
}}
index={index}
style={{}}
/>
))}
</div>
);
return (
<div
className="focus-trap"
style={{ outline: "none" }}
ref={focusTrapRef}
tabIndex={0}
> >
{Row} {isNoFixedHeightOptions ? (
</VariableSizeList> <Scrollbar style={{ height: maxHeight }}>{cleanChildren}</Scrollbar>
) : (
<VariableSizeList
ref={ref}
width={width}
itemCount={itemCount}
itemSize={getItemSize}
height={calculatedHeight}
itemData={{
children: cleanChildren,
theme,
activeIndex,
activedescendant: currentIndex,
handleMouseMove,
}}
outerElementType={Scrollbar}
>
{Row}
</VariableSizeList>
)}
</div>
); );
} }