UNPKG

react-command-flow

Version:

A beautiful interactive CLI-style command interface for React apps

352 lines (301 loc) 9.41 kB
# 🖥️ React Command Flow A beautifully interactive CLI-style command interface for React apps. Type commands, flow through steps, and build dynamic, conversational UI experiences. --- ## ✨ Features - 🖋 Terminal-style input & output - 📋 Step-based command flows - Built-in validation (sync & async) - 🔀 Conditional prompts, branching logic - Dynamic suggestions & tab completion - 💅 Theme support - 🔍 Help & clear commands out of the box - 🔌 Easy to extend and customize --- ## 📦 Installation ```bash npm install react-command-flow ``` --- ## ⚡ Usage ```jsx import React from "react"; import { CommandLine } from "react-command-flow"; import { commands } from "./commands"; export default function App() { return ( <div style={{ padding: "2rem" }}> <h1>React Command Flow Demo</h1> <CommandLine commands={commands} /> </div> ); } ``` --- ## 📚 Defining Commands ```js const colors = ["Red", "Green", "Blue", "Yellow", "Purple"]; const shapes = ["Circle", "Square", "Triangle", "Rectangle", "Hexagon"]; const commands = { "color-shape-quiz": { description: "Start your color and shape quiz 🌈🔺", steps: [ { prompt: async () => { await new Promise((res) => setTimeout(res, 1000)); // async wait return `What is your favorite color?\n${colors .map((c, i) => `${i + 1}. ${c}`) .join("\n")}\nEnter color index:`; }, field: "colorIndex", validate: async (val) => { return ["1", "2", "3", "4", "5"].includes(val) ? true : "❌ Please select a number between 1 and 5."; }, redirect: (val) => { const color = colors[parseInt(val) - 1]; if (color === "Red") return "color-red-flow"; if (color === "Green") return "color-green-flow"; if (color === "Blue") return "color-blue-flow"; if (color === "Yellow") return "color-yellow-flow"; if (color === "Purple") return "color-purple-flow"; return "color-other-flow"; }, }, ], onComplete: () => "Redirecting...", }, "color-red-flow": { description: "Red color path 🔴", hidden: true, steps: [ { prompt: "What shape do you associate with Red?", field: "redShape", validate: (val) => { return shapes.includes(val) ? true : "❌ Please enter a valid shape."; }, }, ], onComplete: async () => { await new Promise((res) => setTimeout(res, 1000)); // fake async post return "✅ Red is a powerful color!"; }, }, "color-green-flow": { description: "Green color path 🟢", hidden: true, steps: [ { prompt: "What shape do you associate with Green?", field: "greenShape", validate: (val) => { return shapes.includes(val) ? true : "❌ Please enter a valid shape."; }, }, ], onComplete: async () => { await new Promise((res) => setTimeout(res, 1000)); // fake async post return "✅ Green is a calming color!"; }, }, "color-blue-flow": { description: "Blue color path 🔵", hidden: true, steps: [ { prompt: "What shape do you associate with Blue?", field: "blueShape", validate: (val) => { return shapes.includes(val) ? true : "❌ Please enter a valid shape."; }, }, ], onComplete: async () => { await new Promise((res) => setTimeout(res, 1000)); // fake async post return "✅ Blue is a soothing color!"; }, }, "color-yellow-flow": { description: "Yellow color path 🟡", hidden: true, steps: [ { prompt: "What shape do you associate with Yellow?", field: "yellowShape", validate: (val) => { return shapes.includes(val) ? true : "❌ Please enter a valid shape."; }, }, { prompt: "What emotion do you associate with Yellow?", field: "yellowEmotion", validate: (val) => { const emotions = ["happiness", "energy", "warmth", "caution"]; return emotions.includes(val.toLowerCase()) ? true : "❌ Please enter a valid emotion (happiness, energy, warmth, caution)."; }, }, { prompt: "What object do you associate with Yellow?", field: "yellowObject", validate: (val) => { return val.length > 0 ? true : "❌ Please enter a valid object."; }, }, ], onComplete: async () => { await new Promise((res) => setTimeout(res, 1000)); // fake async post return "✅ Yellow is a bright and cheerful color!"; }, }, "color-purple-flow": { description: "Purple color path 🟣", hidden: true, steps: [ { prompt: "What shape do you associate with Purple?", field: "purpleShape", validate: (val) => { return shapes.includes(val) ? true : "❌ Please enter a valid shape."; }, }, ], onComplete: async () => { await new Promise((res) => setTimeout(res, 1000)); // fake async post return "✅ Purple is a royal color!"; }, }, "color-other-flow": { description: "Other color path 🌈", hidden: true, steps: [], onComplete: () => "💛 All colors are beautiful!", }, help: { description: "List available commands", steps: [], onComplete: (_, __, all) => { return Object.entries(all) .filter(([_, c]) => !c.hidden) .map(([name, c]) => `- ${name}: ${c.description}`) .join("\n"); }, }, clear: { description: "Clear the screen", steps: [], onComplete: () => "__CLEAR__", }, }; // Alias added outside commands["?"] = commands.help; export default commands; ``` --- ## 🧠 Advanced Examples - 🔁 Async prompt resolution - Custom validation per step - 🔀 `onStep` logic to transform values - 🧭 Conditional steps (based on previous answers) - 📦 Redirects to subcommands (optional) - 🔄 Repeat functionality with custom repeat questions ### 🎯 Repeat Configuration Commands can use a comprehensive `repeat` object for full control over the repeat behavior: ```js const commands = { "job-application": { description: "Job application flow", steps: [ /* ... */ ], onComplete: (data) => `Application submitted for ${data.name}`, repeat: { mode: "ask", question: "💼 Submit another application ({{yes}},{{no}})?", yes: "sure", no: "nope", wrongInput: "Please select either {{yes}} or {{no}}:", yesReply: "Starting new application...", noReply: "Application process completed.", targetCommand: "other-command", // Optional: redirect to different command }, }, survey: { description: "User survey", steps: [ /* ... */ ], onComplete: (data) => "Survey completed!", repeat: { mode: "ask", // Function-based custom repeat question question: ({ yesOption, noOption, commandName }) => `🔄 Take the survey again? Type '${yesOption}' to continue or '${noOption}' to finish.`, yes: "y", no: "n", wrongInput: "Please select either {{yes}} or {{no}}:", yesReply: "Let's do another survey!", noReply: "Thanks for participating!", }, }, "async-demo": { description: "Async repeat question demo", steps: [ /* ... */ ], onComplete: (data) => "Task completed!", repeat: { mode: "ask", // Async function-based custom repeat question question: async ({ yesOption, noOption, commandName }) => { // Simulate async operation (e.g., checking user preferences) await new Promise((resolve) => setTimeout(resolve, 500)); return `🤔 Continue with another task? Type '${yesOption}' or '${noOption}'.`; }, yes: "y", no: "n", wrongInput: "Please select either {{yes}} or {{no}}:", yesReply: "Great! Let's continue...", noReply: "Task session ended.", }, }, }; ``` **Repeat Object Properties:** - `mode` - "yes", "no", or "ask" - `question` - Custom repeat question (string or function) - `yes` - Custom "yes" option text - `no` - Custom "no" option text - `wrongInput` - Custom error message for invalid input - `yesReply` - Custom message when user selects "yes" - `noReply` - Custom message when user selects "no" - `targetCommand` - Optional: different command to run on repeat **Available placeholders for string-based questions and messages:** - `{{yes}}` - The "yes" option value - `{{no}}` - The "no" option value - `{command}` - The command name (legacy format) **Function parameters for dynamic questions:** - `yesOption` - The "yes" option string - `noOption` - The "no" option string - `commandName` - The name of the command --- ## 🖌️ Theming Supports custom themes with colors and fonts. You can pass a `theme` prop to `<CommandLine />`. --- ## 💡 Example Commands You Can Try - `hello` - `profile-setup` - `select-server` - `help` - `clear` --- ## 📜 License MIT free to use, modify, contribute. Made with 💛 by Stam & React Command Flow Contributors. --- ## ⭐ Star this repo if you like it! Let's build the coolest CLI UIs in the React world 🌍