From 518b779e2a772d58cac80593170206ab3155be2d Mon Sep 17 00:00:00 2001 From: Nicolas Borges Date: Tue, 29 Sep 2026 20:14:36 -0400 Subject: [PATCH 1/5] refactor(TUI): improve command menu scroll and resize mechanics --- src/components/BrandBanner.tsx | 3 + src/components/Layout.tsx | 33 ++++- src/components/RouterScreen.test.tsx | 18 +++ src/components/RouterScreen.tsx | 198 ++++++++++++++++----------- src/tui/index.tsx | 10 +- src/tui/resize.ts | 132 +++++++++++++++--- src/tui/tui.test.tsx | 29 ++-- 7 files changed, 309 insertions(+), 114 deletions(-) diff --git a/src/components/BrandBanner.tsx b/src/components/BrandBanner.tsx index 7fc38bf339..fb6a81904a 100644 --- a/src/components/BrandBanner.tsx +++ b/src/components/BrandBanner.tsx @@ -10,6 +10,9 @@ const LOGO: string[] = [ "▀ ▀ ▀▀▀ ▀▀▀ ▀ ▀ ▀ ▀▀▀ ▀▀▀ ▀ ▀ ▀▀▀ ▀▀▀ ▀▀▀ ▀▀▀", ]; +export const MIN_BANNER_COLUMNS = 80; +export const MIN_BANNER_ROWS = 30; + export interface BrandBannerProps { terminalProgram?: string; theme?: InkUITheme; diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 8bf012f798..376ba98b93 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -8,6 +8,13 @@ import type { KeyHintProps } from "./ui/key-hint"; export interface LayoutProps { // banner is content rendered above the standard breadcrumb header. banner?: React.ReactNode; + // bannerHeight is the number of terminal rows occupied by the banner. + bannerHeight?: number; + // hideBanner suppresses the banner regardless of terminal dimensions. + hideBanner?: boolean; + // bannerMinColumns/bannerMinRows suppress the banner below these dimensions. + bannerMinColumns?: number; + bannerMinRows?: number; // breadcrumb is passed through to the Header. breadcrumb: HeaderProps["breadcrumb"]; // description is passed through to the Header, shown dimmed after the breadcrumb. @@ -15,27 +22,47 @@ export interface LayoutProps { // keyHints is passed through to the Footer (KeyHint) row. keyHints: KeyHintProps["keys"]; // children fill the content area between the header and footer. - children: React.ReactNode; + children: + | React.ReactNode + | ((dimensions: { + columns: number; + rows: number; + contentRows: number; + bannerVisible: boolean; + }) => React.ReactNode); } +const FRAME_ROWS = 4; + // Layout is the standard full-screen frame: a breadcrumb Header at the top, a // KeyHint Footer at the bottom, and a flexible content area in between that grows // to fill the remaining terminal height. export const Layout: React.FC = ({ banner, + bannerHeight = 0, + hideBanner = false, + bannerMinColumns = 0, + bannerMinRows = 0, breadcrumb, description, keyHints, children, }) => { const { columns, rows } = useWindowSize(); + const bannerVisible = + Boolean(banner) && !hideBanner && columns >= bannerMinColumns && rows >= bannerMinRows; + const contentRows = Math.max(0, rows - FRAME_ROWS - (bannerVisible ? bannerHeight : 0)); + const content = + typeof children === "function" + ? children({ columns, rows, contentRows, bannerVisible }) + : children; return ( - {banner} + {bannerVisible ? banner : null}
- {children} + {content}