11import React from "react" ;
2- import { Box , Text } from "ink" ;
2+ import { Box , Text , useStdout } from "ink" ;
33import { colors } from "../utils/theme.js" ;
44import { UpdateNotification } from "./UpdateNotification.js" ;
55
@@ -8,17 +8,89 @@ export interface BreadcrumbItem {
88 active ?: boolean ;
99}
1010
11+ /** Display mode: full (all items, border), compact (shorter labels), or minimal (active/last only) */
12+ export type BreadcrumbCompactMode = "full" | "compact" | "minimal" ;
13+
1114interface BreadcrumbProps {
1215 items : BreadcrumbItem [ ] ;
1316 showVersionCheck ?: boolean ;
17+ /** When set, overrides width-based mode (from vertical layout allocator) */
18+ compactMode ?: BreadcrumbCompactMode ;
19+ }
20+
21+ const BREADCRUMB_FULL_MIN_WIDTH = 70 ;
22+ const BREADCRUMB_COMPACT_MIN_WIDTH = 45 ;
23+ const FULL_MAX_LABEL_LENGTH = 40 ;
24+ const COMPACT_MAX_LABEL_LENGTH = 12 ;
25+
26+ function getModeFromWidth (
27+ terminalWidth : number ,
28+ override ?: BreadcrumbCompactMode ,
29+ ) : BreadcrumbCompactMode {
30+ if ( override !== undefined ) return override ;
31+ if ( terminalWidth >= BREADCRUMB_FULL_MIN_WIDTH ) return "full" ;
32+ if ( terminalWidth >= BREADCRUMB_COMPACT_MIN_WIDTH ) return "compact" ;
33+ return "minimal" ;
1434}
1535
1636export const Breadcrumb = ( {
1737 items,
1838 showVersionCheck = false ,
39+ compactMode : compactModeProp ,
1940} : BreadcrumbProps ) => {
2041 const env = process . env . RUNLOOP_ENV ?. toLowerCase ( ) ;
2142 const isDevEnvironment = env === "dev" ;
43+ const { stdout } = useStdout ( ) ;
44+
45+ const [ terminalWidth , setTerminalWidth ] = React . useState ( ( ) =>
46+ stdout ?. columns && stdout . columns > 0 ? stdout . columns : 80 ,
47+ ) ;
48+
49+ React . useEffect ( ( ) => {
50+ if ( ! stdout ) return ;
51+ const handleResize = ( ) => {
52+ const w = stdout . columns && stdout . columns > 0 ? stdout . columns : 80 ;
53+ setTerminalWidth ( w ) ;
54+ } ;
55+ stdout . on ( "resize" , handleResize ) ;
56+ handleResize ( ) ;
57+ return ( ) => {
58+ stdout . off ( "resize" , handleResize ) ;
59+ } ;
60+ } , [ stdout ] ) ;
61+
62+ const mode = getModeFromWidth ( terminalWidth , compactModeProp ) ;
63+ const maxLabelLen =
64+ mode === "full"
65+ ? FULL_MAX_LABEL_LENGTH
66+ : mode === "compact"
67+ ? COMPACT_MAX_LABEL_LENGTH
68+ : Math . max ( 5 , terminalWidth - 10 ) ;
69+ const showBorder = mode === "full" ;
70+ const paddingX = mode === "full" ? 2 : mode === "compact" ? 1 : 0 ;
71+
72+ // Items to show: minimal = active or last only; compact/full = all with truncation
73+ const displayItems = React . useMemo ( ( ) => {
74+ if ( mode === "minimal" && items . length > 0 ) {
75+ const active = items . find ( ( i ) => i . active ) ?? items [ items . length - 1 ] ;
76+ return [
77+ {
78+ ...active ,
79+ label :
80+ active . label . length > maxLabelLen
81+ ? active . label . substring ( 0 , maxLabelLen - 3 ) + "..."
82+ : active . label ,
83+ } ,
84+ ] ;
85+ }
86+ return items . map ( ( item ) => ( {
87+ ...item ,
88+ label :
89+ item . label . length > maxLabelLen
90+ ? item . label . substring ( 0 , maxLabelLen - 3 ) + "..."
91+ : item . label ,
92+ } ) ) ;
93+ } , [ items , mode , maxLabelLen ] ) ;
2294
2395 return (
2496 < Box
@@ -29,40 +101,31 @@ export const Breadcrumb = ({
29101 >
30102 < Box flexShrink = { 0 } >
31103 < Box
32- borderStyle = "round"
33- borderColor = { colors . primary }
34- paddingX = { 2 }
104+ borderStyle = { showBorder ? "round" : undefined }
105+ borderColor = { showBorder ? colors . primary : undefined }
106+ paddingX = { paddingX }
35107 paddingY = { 0 }
36108 >
37109 < Text color = { colors . primary } bold >
38110 rl
39111 </ Text >
40- { isDevEnvironment && (
112+ { isDevEnvironment && mode !== "minimal" && (
41113 < Text color = { colors . error } bold >
42114 { " " }
43115 (dev)
44116 </ Text >
45117 ) }
46- < Text color = { colors . textDim } > › </ Text >
47- { items . map ( ( item , index ) => {
48- // Limit label length to prevent Yoga layout engine errors
49- const MAX_LABEL_LENGTH = 80 ;
50- const truncatedLabel =
51- item . label . length > MAX_LABEL_LENGTH
52- ? item . label . substring ( 0 , MAX_LABEL_LENGTH ) + "..."
53- : item . label ;
54-
55- return (
56- < React . Fragment key = { index } >
57- < Text color = { item . active ? colors . primary : colors . textDim } >
58- { truncatedLabel }
59- </ Text >
60- { index < items . length - 1 && (
61- < Text color = { colors . textDim } > › </ Text >
62- ) }
63- </ React . Fragment >
64- ) ;
65- } ) }
118+ { displayItems . length > 0 && < Text color = { colors . textDim } > › </ Text > }
119+ { displayItems . map ( ( item , index ) => (
120+ < React . Fragment key = { index } >
121+ < Text color = { item . active ? colors . primary : colors . textDim } >
122+ { item . label }
123+ </ Text >
124+ { index < displayItems . length - 1 && (
125+ < Text color = { colors . textDim } > › </ Text >
126+ ) }
127+ </ React . Fragment >
128+ ) ) }
66129 </ Box >
67130 </ Box >
68131 { showVersionCheck && < UpdateNotification /> }
0 commit comments