feat(ui): add solid background color option for input prompt (#16563)

Co-authored-by: Alexander Farber <farber72@outlook.de>
This commit is contained in:
Jacob Richman
2026-01-26 15:23:54 -08:00
committed by GitHub
parent 7fbf470373
commit b5fe372b5b
40 changed files with 898 additions and 420 deletions

View File

@@ -0,0 +1,102 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type React from 'react';
import { useMemo } from 'react';
import { Box, Text } from 'ink';
import { useUIState } from '../../contexts/UIStateContext.js';
import {
interpolateColor,
resolveColor,
getSafeLowColorBackground,
} from '../../themes/color-utils.js';
import { isLowColorDepth } from '../../utils/terminalUtils.js';
export interface HalfLinePaddedBoxProps {
/**
* The base color to blend with the terminal background.
*/
backgroundBaseColor: string;
/**
* The opacity (0-1) for blending the backgroundBaseColor onto the terminal background.
*/
backgroundOpacity: number;
/**
* Whether to render the solid background color.
*/
useBackgroundColor?: boolean;
children: React.ReactNode;
}
/**
* A container component that renders a solid background with half-line padding
* at the top and bottom using block characters (▀/▄).
*/
export const HalfLinePaddedBox: React.FC<HalfLinePaddedBoxProps> = (props) => {
if (props.useBackgroundColor === false) {
return <>{props.children}</>;
}
return <HalfLinePaddedBoxInternal {...props} />;
};
const HalfLinePaddedBoxInternal: React.FC<HalfLinePaddedBoxProps> = ({
backgroundBaseColor,
backgroundOpacity,
children,
}) => {
const { terminalWidth, terminalBackgroundColor } = useUIState();
const terminalBg = terminalBackgroundColor || 'black';
const isLowColor = isLowColorDepth();
const backgroundColor = useMemo(() => {
// Interpolated background colors often look bad in 256-color terminals
if (isLowColor) {
return getSafeLowColorBackground(terminalBg);
}
const resolvedBase =
resolveColor(backgroundBaseColor) || backgroundBaseColor;
const resolvedTerminalBg = resolveColor(terminalBg) || terminalBg;
return interpolateColor(
resolvedTerminalBg,
resolvedBase,
backgroundOpacity,
);
}, [backgroundBaseColor, backgroundOpacity, terminalBg, isLowColor]);
if (!backgroundColor) {
return <>{children}</>;
}
return (
<Box
width={terminalWidth}
flexDirection="column"
alignItems="stretch"
minHeight={1}
flexShrink={0}
backgroundColor={backgroundColor}
>
<Box width={terminalWidth} flexDirection="row">
<Text backgroundColor={backgroundColor} color={terminalBg}>
{'▀'.repeat(terminalWidth)}
</Text>
</Box>
{children}
<Box width={terminalWidth} flexDirection="row">
<Text color={terminalBg} backgroundColor={backgroundColor}>
{'▄'.repeat(terminalWidth)}
</Text>
</Box>
</Box>
);
};

View File

@@ -374,4 +374,37 @@ describe('ScrollableList Demo Behavior', () => {
});
});
});
describe('Width Prop', () => {
it('should apply the width prop to the container', async () => {
const items = [{ id: '1', title: 'Item 1' }];
let lastFrame: () => string | undefined;
await act(async () => {
const result = render(
<MouseProvider mouseEventsEnabled={false}>
<KeypressProvider>
<ScrollProvider>
<Box width={100} height={20}>
<ScrollableList
data={items}
renderItem={({ item }) => <Text>{item.title}</Text>}
estimatedItemHeight={() => 1}
keyExtractor={(item) => item.id}
hasFocus={true}
width={50}
/>
</Box>
</ScrollProvider>
</KeypressProvider>
</MouseProvider>,
);
lastFrame = result.lastFrame;
});
await waitFor(() => {
expect(lastFrame()).toContain('Item 1');
});
});
});
});

View File

@@ -37,6 +37,7 @@ type VirtualizedListProps<T> = {
interface ScrollableListProps<T> extends VirtualizedListProps<T> {
hasFocus: boolean;
width?: string | number;
}
export type ScrollableListRef<T> = VirtualizedListRef<T>;
@@ -45,7 +46,7 @@ function ScrollableList<T>(
props: ScrollableListProps<T>,
ref: React.Ref<ScrollableListRef<T>>,
) {
const { hasFocus } = props;
const { hasFocus, width } = props;
const virtualizedListRef = useRef<VirtualizedListRef<T>>(null);
const containerRef = useRef<DOMElement>(null);
@@ -236,6 +237,7 @@ function ScrollableList<T>(
flexGrow={1}
flexDirection="column"
overflow="hidden"
width={width}
>
<VirtualizedList
ref={virtualizedListRef}