Welcome to the Settings project tutorial! If you are new to building interactive terminal applications, you are in the right place.
We are starting with the Settings Container. Think of this as the "Control Panel" or the "Settings App" on your smartphone. Before we look at individual settings like Wi-Fi or Bluetooth, we need the app itselfβthe window that holds everything together, handles the tabs, and knows when to close.
Imagine you have three different tools: a status checker, a configuration editor, and a usage monitor. Without a container, they are just loose pieces.
The Problem: You need a way to:
Esc.
The Solution:
The Settings component acting as a Container. It manages the state of the application (which tab is active) and the global layout.
To build this container, we need to understand three main concepts.
The container needs a memory. It needs to remember: "Is the user looking at the Status tab or the Config tab?" It uses React state to keep track of this.
Terminal windows can be tiny or huge. The container asks the system, "How many rows do I have?" and adjusts the height of the settings box so it doesn't get cut off.
The container listens for global keys. If you press Esc, the container decides if it should close the settings or if a sub-component (like a search bar) needs that key press first.
Using the Settings Container is simple. It is the entry point for your settings interface. You simply render the <Settings /> component.
Here is a simplified example of how it is used in the application:
import { Settings } from './Settings';
// When the user runs the "settings" command:
<Settings
onClose={() => console.log("User closed settings")}
defaultTab="Status"
context={commandContext}
/>
What happens here?
Settings component launches.onClose function runs to clean up.Let's look under the hood. When the Settings component loads, a specific sequence of events occurs to set up the screen.
Now, let's break down the actual code implementation into small, digestible blocks.
First, we need to know which tab is currently open. We use a standard React Hook for this.
// Inside Settings.tsx function
export function Settings({ defaultTab, ...props }) {
// 1. Setup state for the active tab
const [selectedTab, setSelectedTab] = useState(defaultTab);
// 2. Setup state to hide tabs (useful for sub-menus)
const [tabsHidden, setTabsHidden] = useState(false);
selectedTab keeps track of whether we are viewing "Status", "Config", or "Usage".A common issue in CLI apps is drawing content that is too tall for the screen. We use a custom hook to calculate safe boundaries.
// Get the size of the modal or terminal
const { rows } = useModalOrTerminalSize(useTerminalSize());
// Calculate safe height (leave room for borders)
const contentHeight = insideModal
? rows + 1
: Math.max(15, Math.min(Math.floor(rows * 0.8), 30));
rows (height). We then do some math to ensure the settings box takes up about 80% of the screen but never gets too small or too huge. This touches on concepts we will explore in Terminal UI Composition (Ink).
This is a critical part of the User Experience. We need to close the settings when Esc is pressed, but not if the user is currently typing in a search box inside the Config tab.
// Logic: Only handle Escape if a sub-component doesn't own it
const isActive = !tabsHidden &&
!(selectedTab === "Config" && configOwnsEsc);
useKeybinding("confirm:no", () => {
onClose("Status dialog dismissed");
}, { isActive });
configOwnsEsc is true. If it is, the Container ignores the Esc key so the Config tab can use it to clear a search. If not, the Container closes the app. For more on keys, see Keybinding & Interaction System.Finally, we build the list of tabs that will be displayed inside our container.
const tabs = [
<Tab key="status" title="Status">
<Status context={context} />
</Tab>,
<Tab key="config" title="Config">
<Config onClose={onClose} />
</Tab>,
// ... Usage tab and others
];
Tab components. Notice how we pass Status and Config as children. We will dive into these in System Status & Diagnostics.
We wrap everything in a Pane (the outer box color) and the Tabs system (the navigation bar).
return (
<Pane color="permission">
<Tabs
selectedTab={selectedTab}
onTabChange={setSelectedTab}
contentHeight={contentHeight}
>
{tabs}
</Tabs>
</Pane>
);
}
Pane gives it a border color, and Tabs renders the navigation header and the content of the selectedTab.In this chapter, we built the Settings Container.
Now that we have our container, let's fill in the first tab.
Next Chapter: System Status & Diagnostics
Generated by Code IQ