|
1 | 1 | /** |
2 | 2 | * TabContainer — Self-contained tabbed interface. |
| 3 | + * Absorbs BottomTabBar + StatusPanel functionality. |
3 | 4 | * |
4 | | - * Tab content renders inline (ScreenContainer wraps it with the hints bar). |
5 | | - * Navigation chrome (status bar + tab bar) is pushed to ScreenContainer |
6 | | - * via NavChromeContext so it renders below the hints bar. |
| 5 | + * Key bindings are declared via useKeyBindings, which auto-registers |
| 6 | + * hints in the KeyboardHintsBar (rendered by ScreenContainer). |
7 | 7 | */ |
8 | 8 |
|
9 | 9 | import { Box, Text } from 'ink'; |
10 | | -import { useState, useMemo, useEffect, type ReactNode } from 'react'; |
| 10 | +import { useState, useMemo, type ReactNode } from 'react'; |
11 | 11 | import { Colors, Icons } from '../styles.js'; |
12 | 12 | import { |
13 | 13 | useKeyBindings, |
14 | 14 | KeyMatch, |
15 | 15 | type KeyBinding, |
16 | 16 | } from '../hooks/useKeyBindings.js'; |
17 | | -import { useNavChrome } from './ScreenContainer.js'; |
18 | 17 | import type { WizardStore } from '../store.js'; |
19 | 18 |
|
20 | 19 | export interface TabDefinition { |
@@ -42,10 +41,8 @@ export const TabContainer = ({ |
42 | 41 | store, |
43 | 42 | }: TabContainerProps) => { |
44 | 43 | const [activeTab, setActiveTab] = useState(0); |
| 44 | + // Fallback to local state when no store is provided |
45 | 45 | const [localExpanded, setLocalExpanded] = useState(false); |
46 | | - const navChrome = useNavChrome(); |
47 | | - const setNavChrome = (node: ReactNode) => navChrome.setNavChrome(node); |
48 | | - const clearNavChrome = () => navChrome.clearNavChrome(); |
49 | 46 |
|
50 | 47 | const statusExpanded = store ? store.statusExpanded : localExpanded; |
51 | 48 |
|
@@ -96,57 +93,51 @@ export const TabContainer = ({ |
96 | 93 | expandableStatus && statusExpanded ? EXPANDED_COUNT : COLLAPSED_COUNT; |
97 | 94 | const visibleMessages = allMessages.slice(-visibleCount); |
98 | 95 |
|
99 | | - // Push nav chrome to ScreenContainer |
100 | | - useEffect(() => { |
101 | | - setNavChrome( |
102 | | - <Box flexDirection="column"> |
103 | | - {/* Status bar */} |
104 | | - {visibleMessages.length > 0 && ( |
105 | | - <Box |
106 | | - flexDirection="column" |
107 | | - borderStyle="single" |
108 | | - borderTop |
109 | | - borderBottom={false} |
110 | | - borderLeft={false} |
111 | | - borderRight={false} |
112 | | - borderColor={Colors.muted} |
113 | | - paddingX={1} |
114 | | - overflow="hidden" |
115 | | - > |
116 | | - {visibleMessages.map((msg, i, arr) => { |
117 | | - const isCurrent = i === arr.length - 1; |
118 | | - return ( |
119 | | - <Text key={i} color={Colors.muted} dimColor={!isCurrent}> |
120 | | - {isCurrent ? Icons.diamond : '\u250A'} {msg} |
121 | | - </Text> |
122 | | - ); |
123 | | - })} |
124 | | - </Box> |
125 | | - )} |
| 96 | + return ( |
| 97 | + <Box flexDirection="column" flexGrow={1}> |
| 98 | + {/* Active tab content — overflow hidden so expanded status eats into this area */} |
| 99 | + <Box flexDirection="column" flexGrow={1} flexShrink={1} overflow="hidden"> |
| 100 | + {current?.component} |
| 101 | + </Box> |
126 | 102 |
|
127 | | - {/* Tab bar */} |
128 | | - <Box height={1} /> |
129 | | - <Box gap={1} paddingX={1}> |
130 | | - {tabs.map((tab, i) => ( |
131 | | - <Text |
132 | | - key={tab.id} |
133 | | - inverse={i === activeTab} |
134 | | - color={i === activeTab ? Colors.accent : Colors.muted} |
135 | | - bold={i === activeTab} |
136 | | - > |
137 | | - {` ${tab.label} `} |
138 | | - </Text> |
139 | | - ))} |
| 103 | + {/* Status bar */} |
| 104 | + {visibleMessages.length > 0 && ( |
| 105 | + <Box |
| 106 | + flexDirection="column" |
| 107 | + borderStyle="single" |
| 108 | + borderTop |
| 109 | + borderBottom={false} |
| 110 | + borderLeft={false} |
| 111 | + borderRight={false} |
| 112 | + borderColor={Colors.muted} |
| 113 | + paddingX={1} |
| 114 | + overflow="hidden" |
| 115 | + > |
| 116 | + {visibleMessages.map((msg, i, arr) => { |
| 117 | + const isCurrent = i === arr.length - 1; |
| 118 | + return ( |
| 119 | + <Text key={i} color={Colors.muted} dimColor={!isCurrent}> |
| 120 | + {isCurrent ? Icons.diamond : '\u250A'} {msg} |
| 121 | + </Text> |
| 122 | + ); |
| 123 | + })} |
140 | 124 | </Box> |
141 | | - </Box>, |
142 | | - ); |
143 | | - return clearNavChrome; |
144 | | - }, [activeTab, visibleMessages, tabs, setNavChrome, clearNavChrome]); |
| 125 | + )} |
145 | 126 |
|
146 | | - // Just render tab content — ScreenContainer handles the rest |
147 | | - return ( |
148 | | - <Box flexDirection="column" flexGrow={1}> |
149 | | - {current?.component} |
| 127 | + {/* Tab bar */} |
| 128 | + <Box height={1} /> |
| 129 | + <Box gap={1} paddingX={1}> |
| 130 | + {tabs.map((tab, i) => ( |
| 131 | + <Text |
| 132 | + key={tab.id} |
| 133 | + inverse={i === activeTab} |
| 134 | + color={i === activeTab ? Colors.accent : Colors.muted} |
| 135 | + bold={i === activeTab} |
| 136 | + > |
| 137 | + {` ${tab.label} `} |
| 138 | + </Text> |
| 139 | + ))} |
| 140 | + </Box> |
150 | 141 | </Box> |
151 | 142 | ); |
152 | 143 | }; |
0 commit comments