59 lines
1.8 KiB
JavaScript
59 lines
1.8 KiB
JavaScript
//
|
|
// Inspired by https://github.com/karaggeorge/ink-scrollbar
|
|
//
|
|
import { Text, Box } from 'ink';
|
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
|
|
export default function Scrollbar({ current, thumbCharacter, highlight, padding, show, children }) {
|
|
const length = children.length;
|
|
const limit = Math.min(show, length);
|
|
const maxLength = Math.max(...children.map(option => option.length));
|
|
|
|
const [viewpoint, setViewpoint] = useState(
|
|
Math.min(Math.max(0, current - Math.floor(limit / 2)), length - limit)
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (current < viewpoint) {
|
|
setViewpoint(current);
|
|
} else if (current >= viewpoint + limit) {
|
|
setViewpoint(Math.min(current - limit + 1, length - limit));
|
|
}
|
|
}, [current, viewpoint, limit, length]);
|
|
|
|
const getScrollbar = useCallback(() => {
|
|
const height = Math.max(Math.round((limit / length) * limit), 1);
|
|
if (height === limit) return [];
|
|
|
|
const pos = Math.min(Math.round((viewpoint / length) * limit), limit - 1);
|
|
return Array.from({ length: height }, (_, i) => pos + i);
|
|
}, [limit, length, viewpoint]);
|
|
|
|
const getOption = useCallback((option, index) => {
|
|
if (!highlight || current !== index + viewpoint) return option;
|
|
const props = highlight === true ? { green: true } : highlight;
|
|
return <Box {...props}>{option}</Box>;
|
|
}, [current, viewpoint, highlight]);
|
|
|
|
const getSpacing = useCallback((option) => {
|
|
return '\u00A0'.repeat(maxLength - option.length + padding);
|
|
}, [maxLength, padding]);
|
|
|
|
const scrollbar = getScrollbar();
|
|
|
|
return (
|
|
children.filter((_, i) => i >= viewpoint && i < viewpoint + limit)
|
|
.map((option, i) => (
|
|
<Box key={i}>
|
|
<Text>{scrollbar.includes(i) ? `\u00A0${thumbCharacter}` : '\u00A0'.repeat(thumbCharacter.length + 1)}</Text>
|
|
{getOption(option, i)}
|
|
{getSpacing(option)}
|
|
</Box>
|
|
))
|
|
);
|
|
};
|
|
|
|
|
|
|
|
|