60 lines
1.6 KiB
TypeScript
60 lines
1.6 KiB
TypeScript
import React, { useCallback, useRef } from "react";
|
|
|
|
export interface SelectableListProps<T> {
|
|
items: T[];
|
|
selectedValue?: T | null;
|
|
renderLabel: (item: T) => React.ReactNode;
|
|
onSelect: (item: T) => void;
|
|
}
|
|
|
|
export const SelectableList = <T,>(
|
|
props: SelectableListProps<T>,
|
|
): JSX.Element => {
|
|
const { items, selectedValue, renderLabel, onSelect } = props;
|
|
|
|
const listRef = useRef<HTMLUListElement | null>(null);
|
|
|
|
const handleKeyDown = useCallback(
|
|
(e: React.KeyboardEvent<HTMLUListElement>) => {
|
|
if (!items.length) return;
|
|
|
|
const currentIndex = selectedValue ? items.indexOf(selectedValue) : -1;
|
|
|
|
if (e.key === "ArrowDown") {
|
|
e.preventDefault();
|
|
const nextIndex =
|
|
currentIndex < items.length - 1 ? currentIndex + 1 : 0;
|
|
onSelect(items[nextIndex]);
|
|
}
|
|
|
|
if (e.key === "ArrowUp") {
|
|
e.preventDefault();
|
|
const prevIndex =
|
|
currentIndex > 0 ? currentIndex - 1 : items.length - 1;
|
|
onSelect(items[prevIndex]);
|
|
}
|
|
},
|
|
[items, selectedValue, onSelect],
|
|
);
|
|
|
|
return (
|
|
<ul
|
|
ref={listRef}
|
|
className="selectable-list"
|
|
tabIndex={0}
|
|
onKeyDown={handleKeyDown}
|
|
>
|
|
{items.map((item, index) => {
|
|
const isSelected = selectedValue === item;
|
|
const className = isSelected ? "selected" : "";
|
|
|
|
return (
|
|
<li key={index} onClick={() => onSelect(item)} className={className}>
|
|
{renderLabel(item)}
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
);
|
|
};
|