// // 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 {option}; }, [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) => ( {scrollbar.includes(i) ? `\u00A0${thumbCharacter}` : '\u00A0'.repeat(thumbCharacter.length + 1)} {getOption(option, i)} {getSpacing(option)} )) ); };