feat: initial commit
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { Box, Spacer, Text, useInput } from "ink";
|
||||
import {
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
type ComponentType,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
|
||||
export function ListDisplay<T>({
|
||||
items,
|
||||
Component,
|
||||
height,
|
||||
isFocused,
|
||||
status,
|
||||
onSelect,
|
||||
}: {
|
||||
items: T[];
|
||||
Component: ComponentType<{ item: T; selected: boolean }>;
|
||||
height: number;
|
||||
isFocused?: boolean;
|
||||
status?: ReactNode;
|
||||
onSelect?: (item: T) => void;
|
||||
}) {
|
||||
const [start, setStart] = useState(0);
|
||||
const slice = Math.min(start + height - 1, items.length);
|
||||
const [selected, setSelected] = useState(0);
|
||||
|
||||
useInput(
|
||||
(input, key) => {
|
||||
if (key.upArrow) {
|
||||
setSelected((prev) => (prev > 0 ? prev - 1 : items.length - 1));
|
||||
} else if (key.downArrow) {
|
||||
setSelected((prev) => (prev + 1) % items.length);
|
||||
} else if (input === " " || key.return) {
|
||||
onSelect?.(items[selected]);
|
||||
}
|
||||
},
|
||||
{ isActive: isFocused }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (selected < start) setStart(selected);
|
||||
else if (selected >= slice)
|
||||
setStart(
|
||||
Math.max(Math.min(selected - slice + start + 1, items.length - 1), 0)
|
||||
);
|
||||
}, [selected, start, slice, items.length]);
|
||||
useEffect(() => {
|
||||
if (selected < 0) setSelected(Math.max(items.length - 1, 0));
|
||||
if (selected >= items.length) setSelected(0);
|
||||
}, [selected, items.length]);
|
||||
useEffect(() => {
|
||||
setSelected(0);
|
||||
}, [items]);
|
||||
|
||||
const indexed = useMemo(
|
||||
() => items.map((item, index) => ({ item, index })),
|
||||
[items]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Box flexDirection="column">
|
||||
{indexed.slice(start, slice).map(({ item, index }) => (
|
||||
<Component
|
||||
key={index}
|
||||
item={item}
|
||||
selected={selected === index && isFocused !== false}
|
||||
/>
|
||||
))}
|
||||
{items.length < height - 1 && (
|
||||
<Box height={height - 1 - items.length} />
|
||||
)}
|
||||
</Box>
|
||||
<Box justifyContent="space-between">
|
||||
{status}
|
||||
<Spacer />
|
||||
<Text color="magenta">
|
||||
{items.length ? (
|
||||
<>
|
||||
{start + 1}-{slice}/{items.length}
|
||||
</>
|
||||
) : (
|
||||
<>(empty)</>
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user