mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-08-04 14:11:11 -07:00
fix: reduce height of scrollable list
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Text, Box } from 'ink';
|
||||
import { theme } from '../../semantic-colors.js';
|
||||
import {
|
||||
@@ -82,35 +82,39 @@ export function BaseSelectionList<
|
||||
priority,
|
||||
});
|
||||
|
||||
const [scrollOffset, setScrollOffset] = useState(0);
|
||||
const [scrollOffsetState, setScrollOffsetState] = useState(0);
|
||||
|
||||
// Handle scrolling for long lists
|
||||
useEffect(() => {
|
||||
const newScrollOffset = Math.max(
|
||||
// Compute effective scroll offset synchronously during render to avoid flicker
|
||||
let scrollOffset = scrollOffsetState;
|
||||
|
||||
if (activeIndex < scrollOffset) {
|
||||
scrollOffset = activeIndex;
|
||||
} else if (activeIndex >= scrollOffset + maxItemsToShow) {
|
||||
scrollOffset = Math.max(
|
||||
0,
|
||||
Math.min(activeIndex - maxItemsToShow + 1, items.length - maxItemsToShow),
|
||||
);
|
||||
if (activeIndex < scrollOffset) {
|
||||
setScrollOffset(activeIndex);
|
||||
} else if (activeIndex >= scrollOffset + maxItemsToShow) {
|
||||
setScrollOffset(newScrollOffset);
|
||||
}
|
||||
}, [activeIndex, items.length, scrollOffset, maxItemsToShow]);
|
||||
}
|
||||
|
||||
const maxScroll = Math.max(0, items.length - maxItemsToShow);
|
||||
if (scrollOffset > maxScroll) {
|
||||
scrollOffset = maxScroll;
|
||||
}
|
||||
|
||||
// Update state to match derived value if it changed
|
||||
if (scrollOffsetState !== scrollOffset) {
|
||||
setScrollOffsetState(scrollOffset);
|
||||
}
|
||||
|
||||
const visibleItems = items.slice(scrollOffset, scrollOffset + maxItemsToShow);
|
||||
const numberColumnWidth = String(items.length).length;
|
||||
|
||||
const canScrollUp = scrollOffset > 0;
|
||||
const canScrollDown = scrollOffset + maxItemsToShow < items.length;
|
||||
const hasScrollArrows = showScrollArrows && items.length > maxItemsToShow;
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
{/* Use conditional coloring instead of conditional rendering */}
|
||||
{showScrollArrows && items.length > maxItemsToShow && (
|
||||
<Text
|
||||
color={scrollOffset > 0 ? theme.text.primary : theme.text.secondary}
|
||||
>
|
||||
▲
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{visibleItems.map((item, index) => {
|
||||
const itemIndex = scrollOffset + index;
|
||||
const isSelected = activeIndex === itemIndex;
|
||||
@@ -139,19 +143,35 @@ export function BaseSelectionList<
|
||||
numberColumnWidth,
|
||||
)}.`;
|
||||
|
||||
// Determine the indicator character for the radio column
|
||||
let indicator = ' ';
|
||||
let indicatorColor = theme.text.primary;
|
||||
|
||||
if (isSelected) {
|
||||
indicator = selectedIndicator;
|
||||
indicatorColor = theme.ui.focus;
|
||||
} else if (hasScrollArrows && index === 0 && canScrollUp) {
|
||||
indicator = '▲';
|
||||
indicatorColor = theme.text.secondary;
|
||||
} else if (
|
||||
hasScrollArrows &&
|
||||
index === visibleItems.length - 1 &&
|
||||
canScrollDown
|
||||
) {
|
||||
indicator = '▼';
|
||||
indicatorColor = theme.text.secondary;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={item.key}
|
||||
alignItems="flex-start"
|
||||
backgroundColor={isSelected ? theme.background.focus : undefined}
|
||||
>
|
||||
{/* Radio button indicator */}
|
||||
{/* Radio button indicator (also shows scroll arrows inline) */}
|
||||
<Box minWidth={2} flexShrink={0}>
|
||||
<Text
|
||||
color={isSelected ? theme.ui.focus : theme.text.primary}
|
||||
aria-hidden
|
||||
>
|
||||
{isSelected ? selectedIndicator : ' '}
|
||||
<Text color={indicatorColor} aria-hidden>
|
||||
{indicator}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
@@ -178,18 +198,6 @@ export function BaseSelectionList<
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
|
||||
{showScrollArrows && items.length > maxItemsToShow && (
|
||||
<Text
|
||||
color={
|
||||
scrollOffset + maxItemsToShow < items.length
|
||||
? theme.text.primary
|
||||
: theme.text.secondary
|
||||
}
|
||||
>
|
||||
▼
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -172,11 +172,12 @@ export function SearchableList<T extends GenericListItem>({
|
||||
item: T,
|
||||
isActive: boolean,
|
||||
labelWidth: number,
|
||||
arrowChar?: string,
|
||||
) => (
|
||||
<Box flexDirection="row" alignItems="flex-start">
|
||||
<Box minWidth={2} flexShrink={0}>
|
||||
<Text color={isActive ? theme.status.success : theme.text.secondary}>
|
||||
{isActive ? '●' : ''}
|
||||
{isActive ? '●' : arrowChar || ' '}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box flexDirection="column" flexGrow={1} minWidth={0}>
|
||||
@@ -226,26 +227,44 @@ export function SearchableList<T extends GenericListItem>({
|
||||
</Box>
|
||||
) : (
|
||||
<>
|
||||
{filteredItems.length > maxItemsToShow && (
|
||||
<Box marginX={1}>
|
||||
<Text color={theme.text.secondary}>▲</Text>
|
||||
</Box>
|
||||
)}
|
||||
{visibleItems.map((item, index) => {
|
||||
const isSelected = activeIndex === scrollOffset + index;
|
||||
const hasScrollArrows = filteredItems.length > maxItemsToShow;
|
||||
const canScrollUp = scrollOffset > 0;
|
||||
const canScrollDown =
|
||||
scrollOffset + maxItemsToShow < filteredItems.length;
|
||||
|
||||
// Determine inline scroll arrow for this item position
|
||||
let arrowChar = '';
|
||||
if (
|
||||
hasScrollArrows &&
|
||||
index === 0 &&
|
||||
canScrollUp &&
|
||||
!isSelected
|
||||
) {
|
||||
arrowChar = '▲';
|
||||
} else if (
|
||||
hasScrollArrows &&
|
||||
index === visibleItems.length - 1 &&
|
||||
canScrollDown &&
|
||||
!isSelected
|
||||
) {
|
||||
arrowChar = '▼';
|
||||
}
|
||||
|
||||
return (
|
||||
<Box key={item.key} marginBottom={1} marginX={1}>
|
||||
{renderItem
|
||||
? renderItem(item, isSelected, maxLabelWidth)
|
||||
: defaultRenderItem(item, isSelected, maxLabelWidth)}
|
||||
: defaultRenderItem(
|
||||
item,
|
||||
isSelected,
|
||||
maxLabelWidth,
|
||||
arrowChar,
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
{filteredItems.length > maxItemsToShow && (
|
||||
<Box marginX={1}>
|
||||
<Text color={theme.text.secondary}>▼</Text>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
+4
-10
@@ -8,28 +8,22 @@ exports[`BaseSelectionList > Scroll Arrows (showScrollArrows) > should not show
|
||||
`;
|
||||
|
||||
exports[`BaseSelectionList > Scroll Arrows (showScrollArrows) > should show arrows and correct items when scrolled to the end 1`] = `
|
||||
"▲
|
||||
8. Item 8
|
||||
"▲ 8. Item 8
|
||||
9. Item 9
|
||||
● 10. Item 10
|
||||
▼
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`BaseSelectionList > Scroll Arrows (showScrollArrows) > should show arrows and correct items when scrolled to the middle 1`] = `
|
||||
"▲
|
||||
4. Item 4
|
||||
"▲ 4. Item 4
|
||||
5. Item 5
|
||||
● 6. Item 6
|
||||
▼
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`BaseSelectionList > Scroll Arrows (showScrollArrows) > should show arrows with correct colors when enabled (at the top) 1`] = `
|
||||
"▲
|
||||
● 1. Item 1
|
||||
"● 1. Item 1
|
||||
2. Item 2
|
||||
3. Item 3
|
||||
▼
|
||||
▼ 3. Item 3
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -7,10 +7,10 @@ exports[`SearchableList > should match snapshot 1`] = `
|
||||
│ Search... │
|
||||
╰────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
● Item One
|
||||
● Item One
|
||||
Description for item one
|
||||
|
||||
Item Two
|
||||
Item Two
|
||||
Description for item two
|
||||
|
||||
Item Three
|
||||
@@ -25,10 +25,10 @@ exports[`SearchableList > should reset selection to top when items change if res
|
||||
│ Search... │
|
||||
╰────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
Item One
|
||||
Item One
|
||||
Description for item one
|
||||
|
||||
● Item Two
|
||||
● Item Two
|
||||
Description for item two
|
||||
|
||||
Item Three
|
||||
@@ -43,7 +43,7 @@ exports[`SearchableList > should reset selection to top when items change if res
|
||||
│ One │
|
||||
╰────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
● Item One
|
||||
● Item One
|
||||
Description for item one
|
||||
"
|
||||
`;
|
||||
@@ -55,10 +55,10 @@ exports[`SearchableList > should reset selection to top when items change if res
|
||||
│ Search... │
|
||||
╰────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
● Item One
|
||||
● Item One
|
||||
Description for item one
|
||||
|
||||
Item Two
|
||||
Item Two
|
||||
Description for item two
|
||||
|
||||
Item Three
|
||||
|
||||
Reference in New Issue
Block a user