feat: redesign header to be compact with ASCII icon (#18713)

Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
Keith Guerin
2026-03-02 13:12:17 -08:00
committed by GitHub
parent b7a8f0d1f9
commit 31ca57ec94
15 changed files with 382 additions and 631 deletions
+74 -19
View File
@@ -1,58 +1,113 @@
/**
* @license
* Copyright 2025 Google LLC
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { Box } from 'ink';
import { Header } from './Header.js';
import { Tips } from './Tips.js';
import { Box, Text } from 'ink';
import { UserIdentity } from './UserIdentity.js';
import { Tips } from './Tips.js';
import { useSettings } from '../contexts/SettingsContext.js';
import { useConfig } from '../contexts/ConfigContext.js';
import { useUIState } from '../contexts/UIStateContext.js';
import { Banner } from './Banner.js';
import { useBanner } from '../hooks/useBanner.js';
import { useTips } from '../hooks/useTips.js';
import { theme } from '../semantic-colors.js';
import { ThemedGradient } from './ThemedGradient.js';
import { CliSpinner } from './CliSpinner.js';
interface AppHeaderProps {
version: string;
showDetails?: boolean;
}
const ICON = `▝▜▄
▝▜▄
▗▟▀
▝▀ `;
export const AppHeader = ({ version, showDetails = true }: AppHeaderProps) => {
const settings = useSettings();
const config = useConfig();
const { nightly, terminalWidth, bannerData, bannerVisible } = useUIState();
const { terminalWidth, bannerData, bannerVisible, updateInfo } = useUIState();
const { bannerText } = useBanner(bannerData);
const { showTips } = useTips();
const showHeader = !(
settings.merged.ui.hideBanner || config.getScreenReader()
);
if (!showDetails) {
return (
<Box flexDirection="column">
<Header version={version} nightly={false} />
{showHeader && (
<Box
flexDirection="row"
marginTop={1}
marginBottom={1}
paddingLeft={2}
>
<Box flexShrink={0}>
<ThemedGradient>{ICON}</ThemedGradient>
</Box>
<Box marginLeft={2} flexDirection="column">
<Box>
<Text bold color={theme.text.primary}>
Gemini CLI
</Text>
<Text color={theme.text.secondary}> v{version}</Text>
</Box>
</Box>
</Box>
)}
</Box>
);
}
return (
<Box flexDirection="column">
{!(settings.merged.ui.hideBanner || config.getScreenReader()) && (
<>
<Header version={version} nightly={nightly} />
{bannerVisible && bannerText && (
<Banner
width={terminalWidth}
bannerText={bannerText}
isWarning={bannerData.warningText !== ''}
/>
)}
</>
{showHeader && (
<Box flexDirection="row" marginTop={1} marginBottom={1} paddingLeft={2}>
<Box flexShrink={0}>
<ThemedGradient>{ICON}</ThemedGradient>
</Box>
<Box marginLeft={2} flexDirection="column">
{/* Line 1: Gemini CLI vVersion [Updating] */}
<Box>
<Text bold color={theme.text.primary}>
Gemini CLI
</Text>
<Text color={theme.text.secondary}> v{version}</Text>
{updateInfo && (
<Box marginLeft={2}>
<Text color={theme.text.secondary}>
<CliSpinner /> Updating
</Text>
</Box>
)}
</Box>
{/* Line 2: Blank */}
<Box height={1} />
{/* Lines 3 & 4: User Identity info (Email /auth and Plan /upgrade) */}
{settings.merged.ui.showUserIdentity !== false && (
<UserIdentity config={config} />
)}
</Box>
</Box>
)}
{settings.merged.ui.showUserIdentity !== false && (
<UserIdentity config={config} />
{bannerVisible && bannerText && (
<Banner
width={terminalWidth}
bannerText={bannerText}
isWarning={bannerData.warningText !== ''}
/>
)}
{!(settings.merged.ui.hideTips || config.getScreenReader()) &&
showTips && <Tips config={config} />}
</Box>