In the previous chapter, Chapter 1: Command Definition, we added our "item to the menu." We told the CLI that a command named mobile exists.
However, if you try to run it now, nothing happens. It's like ordering a dish at a restaurant, but the plate arrives empty.
In this chapter, we will build the Local JSX UI Handler. This is the "View" layer. We will learn how to build a graphical interface inside the terminal using React.
Most command-line tools just print text and exit:
$ echo "Hello"
Hello
$ _
But we want something richer. We want a "mini-application" that stays open, displays a QR code, and lets the user switch between iOS and Android versions.
To do this, we use React. Yes, the same React used for websites! But instead of HTML elements like <div> or <span>, we use special terminal components like <Box> and <Text>.
We are working in the file mobile.tsx. Let's break down how we construct this interface.
Just like a web app, our CLI feature is a React functional component. It receives a special prop called onDone.
import * as React from 'react';
import { Box, Text } from '../../ink.js';
type Props = {
onDone: () => void;
};
function MobileQRCode({ onDone }: Props) {
// Logic goes here...
return <Text>Hello World</Text>;
}
Explanation:
MobileQRCode: This is our main component.onDone: This is a function passed down by the framework. When our command is finished (e.g., the user presses 'q'), we call this function to tell the CLI to close our window and return to the normal terminal prompt.We need our UI to be interactive. We want to toggle between "iOS" and "Android". We use standard React hooks for this.
import { useState } from 'react';
// Inside MobileQRCode function:
const [platform, setPlatform] = useState<'ios' | 'android'>('ios');
// We also store the generated QR strings here
const [qrCodes, setQrCodes] = useState({ ios: '', android: '' });
Explanation:
platform: Tracks which tab is active. It starts as 'ios'.useState: This works exactly like it does in web development. When platform changes, the terminal redraws the screen to update the UI.
In the terminal, we can't use CSS files. Instead, we use a component called Box (powered by Yoga Layout, similar to Flexbox in CSS).
// Inside the return statement:
return (
<Pane>
<Box flexDirection="column" gap={1}>
<Text>Scan the QR Code below:</Text>
{/* QR Code Text Lines will go here */}
</Box>
</Pane>
);
Explanation:
Pane: A wrapper that handles basic spacing around our "window".Box: Acts like a <div> with display: flex.flexDirection="column": Stacks items vertically.gap={1}: Adds a space (1 character height) between the items.
We can make text bold, underlined, or colored using props on the Text component.
<Box flexDirection="row" gap={2}>
<Text bold={platform === 'ios'} underline={platform === 'ios'}>
iOS
</Text>
<Text dimColor> / </Text>
<Text bold={platform === 'android'} underline={platform === 'android'}>
Android
</Text>
</Box>
Explanation:
bold={platform === 'ios'}: If we are currently on iOS, make that text bold. This gives the user visual feedback on which tab is selected.Now that we have a Component, how does the CLI know to run it?
At the bottom of mobile.tsx, we export a specific function named call. This is the "bridge" between the CLI framework and our React code.
import type { LocalJSXCommandOnDone } from '../../types/command.js';
export async function call(onDone: LocalJSXCommandOnDone) {
return <MobileQRCode onDone={onDone} />;
}
Explanation:
type: 'local-jsx' from Chapter 1? Because we used that type, the framework looks for this call function.onDone capability to us.<MobileQRCode />), and the framework handles the rendering.It might seem like magic that React can render to a terminal window. Here is the flow of data:
console.log, but much faster and smarter.We now have a visual interface!
However, if you look at the full code, there are two major things missing from our explanation:
To make this interactive, we need to handle user input events and generate data asynchronously.
In the next chapter, we will learn how to make our interface respond to keyboard presses.
Next Chapter: Event-Driven Input Handling
Generated by Code IQ