@nd-fir/react-terminal
v1.0.2
Published
Terminale React in stile Unix controllabile via event bus, con input multiline auto-size e supporto async.
Maintainers
Readme
@nd-fir/react-terminal
Terminale React stile Unix, controllabile da fuori via event bus.
Input multiline auto-size, wrapping adattivo, supporto a funzioni asincrone, loading interno/esterno.
Install
npm i @nd-fir/react-terminal
# oppure
pnpm add @nd-fir/react-terminalDipendenze
npm i reactImport base
import { Terminal, TerminalService } from "@nd-fir/react-terminal";
import "@nd-fir/react-terminal/styles.css"; // stile di defaultSe preferisci import opt-in del CSS, mantieni la riga sopra. Se vuoi gestire tu lo stile, omettila e usa classi/
options(vedi sotto).
Quick start
<Terminal
prompt="$"
run={async (input) => {
const [cmd, ...args] = input.trim().split(/\s+/);
switch (cmd) {
case "echo": return args.join(" ");
case "help": return "Comandi: help, echo";
default: return \`cmd non trovato: \${cmd}\`;
}
}}
/>E da qualunque punto della tua app:
TerminalService.print("help", "Comandi: help, echo"); // (comando, risposta)
TerminalService.run("help"); // (comando)
TerminalService.cast("Recupero dati..."); // (messaggio_loading)
TerminalService.clear(); // pulisce la historyComportamento tastiera
- Enter → invia comando
- Shift+Enter → nuova riga nell’input
- ↑/↓ → naviga la history dei comandi
API
<Terminal /> Props
type TerminalProps = {
prompt: string; // es. "$" o "nebula:~$"
run: (input: string) => Promise<string | void | null | undefined>;
isLoading?: boolean; // loading esterno (facoltativo)
onContextMenu?: (e: React.MouseEvent) => void; // per delegare a ContextMenu
options?: {
container?: HTMLAttributes<HTMLDivElement>;
prompt?: HTMLAttributes<HTMLSpanElement>;
command?: HTMLAttributes<HTMLSpanElement>; // input cmd
history?: HTMLAttributes<HTMLSpanElement>; // contenitore cmd/response in history
response?: HTMLAttributes<HTMLDivElement>; // output
loading?: HTMLAttributes<HTMLDivElement>; // area loading
};
};- L’input è una textarea auto-size.
- Il wrapping è già configurato:
white-space: pre-wrap,overflow-wrap: anywhere,word-break: break-word. - Se
isLoadingètrue, l’input è disabilitato e compare lo spinner anche senza run interno.
TerminalService (event bus)
TerminalService.run(cmd: string) // chiede al terminale di eseguire un comando
TerminalService.print(cmd: string, resp: string) // append di una riga risposta (con cmd associato o vuoto)
TerminalService.cast(text: string) // messaggi di stato/progresso (mostrati con spinner)
TerminalService.clear() // pulisce la historyLa consegna degli eventi è sincrona e in-order. Non fare lavoro pesante nel listener del componente; delega a funzioni async.
Loading interno vs esterno
- Interno: quando il terminale chiama
run(input)setta da solo il loading e mostra i messaggi passati daTerminalService.cast(...)durante l’esecuzione. - Esterno: passando la prop
isLoading={true}, l’input viene disabilitato e compare uno spinner generico anche senzaruninterno. Utile quando vuoi bloccare UI durante operazioni di pagina o per eseguire operazioni semplici dal context menu.
Styling / theming
- CSS di default:
@nd-fir/react-terminal/styles.css. - Personalizza via
options:
<Terminal
prompt="nebula:~$"
run={run}
options={{
container: { className: "my-term", style: { maxHeight: "28rem" } },
prompt: { className: "text-emerald-400" },
response: { className: "text-amber-300" },
}}
/>Ricette
1) ContextMenu con PrimeReact
import { ContextMenu } from "primereact/contextmenu";
import type { MenuItem } from "primereact/menuitem";
const cmRef = useRef<ContextMenu | null>(null);
const items: MenuItem[] = [
{ label: "Help", icon: "pi pi-question-circle", command: () => TerminalService.run("help") },
{ label: "Clear", icon: "pi pi-trash", command: () => TerminalService.clear() },
];
<>
<ContextMenu ref={cmRef} model={items} />
<Terminal
prompt="$"
run={run}
onContextMenu={(e) => {
e.preventDefault();
cmRef.current?.show(e);
}}
/>
</>;2) Pipeline async a step con messaggi e spinner
const delay = (ms: number) => new Promise((r) => setTimeout(r, ms));
async function fakeStep(label: string, ms: number) {
TerminalService.cast(label);
await delay(ms);
}
const run = async (input: string) => {
await fakeStep("Recupero dati…", 700);
await fakeStep("Validazione…", 800);
await fakeStep("Elaborazione…", 900);
TerminalService.cast("Aggregazione…");
await delay(600);
return "Pipeline completata";
};3) Comandi programmati all’avvio
useEffect(() => {
TerminalService.print("", "Benvenuto");
TerminalService.run("help");
}, []);4) Blocco input con loading esterno
const [busy, setBusy] = useState(false);
<Terminal prompt="$" run={run} isLoading={busy} />;Next.js / SSR
- Il componente è client-only: in file e/o modulo c’è
"use client". - Importa il CSS nel client (layout o pagina) o applica le tue classi via
options.
Accessibilità
role="region",aria-label="Terminale".- Area loading con
role="status"earia-live="polite". - L’input riceve focus automatico; clic sul container rifocalizza l’input.
Troubleshooting
- Invalid hook call / doppia React → assicurati che
reactsiano installati nel progetto che usa la libreria e che non esistano copie duplicate nella workspace. - Niente stile → importa
@nd-fir/react-terminal/styles.csso fornisci classi viaoptions. - Context menu di sistema → ricorda
e.preventDefault()inonContextMenu.
License
MIT
