Skip to main content
Glama
terminal.stories.tsx13.3 kB
import type { Meta, StoryObj } from '@storybook/react'; import { expect, userEvent, within } from '@storybook/test'; import { useState } from 'react'; import { Terminal } from './Terminal'; /** * Terminal Component Stories * * The Terminal component displays command-line interface output with support for * ANSI color codes, interactive input, and customizable styling. * * ## Key Features * - **ANSI Color Support**: Parses and renders ANSI color codes for styled terminal output * - **Interactive Input**: Built-in command input field with Enter key submission * - **Dark/Light Mode**: Automatic color adaptation for different themes * - **Closeable Tabs**: Optional close button for tab-based terminal interfaces * - **Custom Titles**: Configurable tab title (defaults to 'bash') * - **Scrollable Content**: Handles long output with hidden scrollbars * * ## When to Use * - Displaying command-line output or logs * - Interactive terminal simulations * - Code examples with syntax highlighting * - Build and deployment logs * - CLI documentation and tutorials */ const meta = { title: 'Components/Terminal', component: Terminal, parameters: { docs: { description: { component: ` A terminal emulator component that renders command-line output with ANSI color code support. ### ANSI Color Support: The component supports a wide range of ANSI color codes including: - Basic colors (red, green, blue, yellow, cyan, magenta) - Extended colors (grey variants, orange, amber) - Text formatting (bold) - Color reset codes ### Interactive Features: - **Command Input**: Type commands and press Enter to submit - **Event Handling**: \`onSubmit\` callback receives the entered command - **Tab Management**: Optional close button with \`onClose\` callback ### Styling: - Automatic adaptation between light and dark modes - Hidden scrollbars for clean appearance - macOS-style window controls (red, yellow, green dots) - Monospace font for authentic terminal look ### Use Cases: - Interactive CLI demonstrations - Build process visualization - Log file viewers - Code execution output - Installation guides `, }, }, }, tags: ['autodocs'], argTypes: { children: { description: 'Terminal output text with optional ANSI color codes', control: 'text', }, title: { description: 'Tab title displayed in the terminal header', control: 'text', }, isDarkMode: { description: 'Enables dark mode color scheme for ANSI codes', control: 'boolean', }, onClose: { description: 'Callback when close button is clicked (shows close button if provided)', action: 'closed', }, onSubmit: { description: 'Callback when Enter key is pressed in input field', action: 'submitted', }, }, } satisfies Meta<typeof Terminal>; export default meta; type Story = StoryObj<typeof Terminal>; /** * ## Basic Examples * * These stories demonstrate the core functionality and appearance of the Terminal component. */ /** * ### Simple Output * * Basic terminal with plain text output, no colors or special formatting. */ export const SimpleOutput: Story = { args: { children: `$ ls -la total 48 drwxr-xr-x 12 user staff 384 Dec 4 10:30 . drwxr-xr-x 5 user staff 160 Dec 3 14:20 .. -rw-r--r-- 1 user staff 1234 Dec 4 09:15 README.md -rw-r--r-- 1 user staff 5678 Dec 4 10:30 package.json`, title: 'bash', }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); // Check terminal content is rendered await expect(canvas.getByText(/README.md/)).toBeInTheDocument(); await expect(canvas.getByText(/package.json/)).toBeInTheDocument(); // Check tab title await expect(canvas.getByText('bash')).toBeInTheDocument(); }, }; /** * ### With ANSI Colors * * Terminal output with ANSI color codes for syntax highlighting and visual emphasis. */ export const WithAnsiColors: Story = { args: { children: `\x1b[32m✓\x1b[0m Build completed successfully! \x1b[34mℹ\x1b[0m Starting development server... \x1b[33m⚠\x1b[0m Warning: Deprecated API usage detected \x1b[31m✗\x1b[0m Error: Module not found \x1b[90m[DEBUG]\x1b[0m Internal state: \x1b[1m{ ready: true }\x1b[0m`, title: 'build-output', isDarkMode: false, }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); // Check content is rendered await expect( canvas.getByText(/Build completed successfully/) ).toBeInTheDocument(); await expect( canvas.getByText(/Error: Module not found/) ).toBeInTheDocument(); // Check tab title await expect(canvas.getByText('build-output')).toBeInTheDocument(); }, }; /** * ### Dark Mode * * Terminal with dark mode color scheme applied to ANSI codes. */ export const DarkMode: Story = { args: { children: `\x1b[32m$ npm install intlayer\x1b[0m \x1b[90mInstalling dependencies...\x1b[0m \x1b[34m→\x1b[0m Fetching packages \x1b[32m✓\x1b[0m Successfully installed \x1b[1m245\x1b[0m packages \x1b[36mℹ\x1b[0m Total size: \x1b[1m42.3 MB\x1b[0m`, title: 'terminal', isDarkMode: true, }, parameters: { backgrounds: { default: 'dark' }, }, }; /** * ### Custom Title * * Terminal with a custom tab title for specific shell types or contexts. */ export const CustomTitle: Story = { args: { children: `zsh: command started at Thu Dec 4 10:30:45 PST 2025 > Running tests... > All tests passed!`, title: 'zsh', }, }; /** * ## Interactive Features * * Stories demonstrating interactive capabilities and event handling. */ /** * ### With Close Button * * Terminal with a close button in the tab, useful for multi-tab interfaces. */ export const WithCloseButton: Story = { args: { children: `$ echo "This terminal can be closed" This terminal can be closed $`, title: 'closeable-terminal', onClose: () => alert('Terminal closed!'), }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); // Find close button const closeButton = canvas.getByRole('button', { name: /close tab/i }); await expect(closeButton).toBeInTheDocument(); // Test close button is clickable await expect(closeButton).not.toBeDisabled(); }, }; /** * ### Interactive Terminal * * Fully interactive terminal that accepts commands and displays output. */ export const InteractiveTerminal: Story = { render: () => { const [output, setOutput] = useState(`Welcome to Interactive Terminal! Type 'help' for available commands. $ `); const handleSubmit = (command: string) => { let response = ''; switch (command.toLowerCase().trim()) { case 'help': response = `Available commands: help - Show this help message date - Display current date echo - Echo back your message clear - Clear terminal (not implemented)`; break; case 'date': response = new Date().toString(); break; case 'clear': setOutput('$ '); return; default: if (command.toLowerCase().startsWith('echo ')) { response = command.substring(5); } else { response = `\x1b[31mCommand not found: ${command}\x1b[0m\nType 'help' for available commands.`; } } setOutput((prev) => `${prev}${command}\n${response}\n$ `); }; return ( <Terminal title="interactive-bash" onSubmit={handleSubmit}> {output} </Terminal> ); }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); // Find input field const input = canvas.getByRole('textbox'); await expect(input).toBeInTheDocument(); // Type a command await userEvent.type(input, 'help'); await userEvent.keyboard('{Enter}'); // Check help message appears await expect(canvas.getByText(/Available commands/)).toBeInTheDocument(); // Type date command await userEvent.type(input, 'date'); await userEvent.keyboard('{Enter}'); }, }; /** * ## Advanced Use Cases * * Complex scenarios demonstrating advanced terminal usage. */ /** * ### Build Process Output * * Simulates a typical build process with colored status indicators. */ export const BuildProcessOutput: Story = { args: { children: `\x1b[1m> Building application...\x1b[0m \x1b[34m[1/5]\x1b[0m Cleaning output directory... \x1b[32m✓\x1b[0m \x1b[34m[2/5]\x1b[0m Compiling TypeScript... \x1b[32m✓\x1b[0m \x1b[34m[3/5]\x1b[0m Bundling with webpack... \x1b[32m✓\x1b[0m \x1b[34m[4/5]\x1b[0m Optimizing assets... \x1b[32m✓\x1b[0m \x1b[34m[5/5]\x1b[0m Generating source maps... \x1b[32m✓\x1b[0m \x1b[1m\x1b[32m✓ Build completed in 12.3s\x1b[0m \x1b[90mOutput: dist/ Size: 2.4 MB (gzipped: 856 KB)\x1b[0m`, title: 'build', }, }; /** * ### Git Command Output * * Shows Git command output with ANSI colors for status indicators. */ export const GitCommandOutput: Story = { args: { children: `$ git status On branch \x1b[32mmain\x1b[0m Your branch is up to date with '\x1b[31morigin/main\x1b[0m'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) \x1b[31mmodified: src/components/Terminal.tsx\x1b[0m \x1b[31mmodified: src/stories/terminal.stories.tsx\x1b[0m Untracked files: (use "git add <file>..." to include in what will be committed) \x1b[31msrc/components/Terminal/\x1b[0m no changes added to commit (use "git add" and/or "git commit -a")`, title: 'git-bash', }, }; /** * ### NPM Install Output * * Displays typical npm package installation output with progress indicators. */ export const NpmInstallOutput: Story = { args: { children: `$ npm install @intlayer/core react react-dom \x1b[90madded 248 packages, and audited 249 packages in 8s\x1b[0m \x1b[90m42 packages are looking for funding\x1b[0m \x1b[90m run \`npm fund\` for details\x1b[0m \x1b[32mfound 0 vulnerabilities\x1b[0m`, title: 'npm', }, }; /** * ### Test Runner Output * * Shows test execution results with pass/fail indicators. */ export const TestRunnerOutput: Story = { args: { children: `\x1b[1m\x1b[32m PASS\x1b[0m src/components/Terminal.test.tsx \x1b[32m ✓\x1b[0m renders without crashing (23ms) \x1b[32m ✓\x1b[0m displays provided text content (12ms) \x1b[32m ✓\x1b[0m handles ANSI color codes (18ms) \x1b[32m ✓\x1b[0m calls onSubmit when Enter is pressed (34ms) \x1b[32m ✓\x1b[0m calls onClose when close button clicked (15ms) \x1b[1m\x1b[31m FAIL\x1b[0m src/utils/parser.test.tsx \x1b[32m ✓\x1b[0m parses simple text (8ms) \x1b[31m ✗\x1b[0m handles nested ANSI codes (42ms) \x1b[90mExpected: "\x1b[34mblue\x1b[0m" Received: "blue"\x1b[0m \x1b[1mTest Suites:\x1b[0m \x1b[31m1 failed\x1b[0m, \x1b[32m1 passed\x1b[0m, 2 total \x1b[1mTests:\x1b[0m \x1b[31m1 failed\x1b[0m, \x1b[32m6 passed\x1b[0m, 7 total \x1b[1mTime:\x1b[0m 2.456s`, title: 'jest', }, }; /** * ### Long Output with Scrolling * * Terminal with extensive output to demonstrate scrolling behavior. */ export const LongOutputWithScrolling: Story = { args: { children: Array.from( { length: 50 }, (_, i) => `\x1b[90m[${String(i + 1).padStart(3, '0')}]\x1b[0m Processing item ${i + 1} ${i % 3 === 0 ? '\x1b[32m✓\x1b[0m' : '\x1b[34m→\x1b[0m'}` ).join('\n'), title: 'long-output', }, parameters: { docs: { description: { story: 'Terminal with many lines of output to demonstrate scrolling behavior. The scrollbar is hidden for a cleaner look.', }, }, }, }; /** * ### Empty Terminal * * Terminal with no output, just the input prompt. */ export const EmptyTerminal: Story = { args: { children: '$ ', title: 'empty-terminal', onSubmit: (cmd) => console.log('Command:', cmd), }, }; /** * ### Multiple Lines Input * * Terminal showing multi-line command execution. */ export const MultiLineInput: Story = { args: { children: `$ cat << EOF > config.json { "name": "intlayer", "version": "1.0.0", "type": "module" } EOF $ cat config.json { "name": "intlayer", "version": "1.0.0", "type": "module" }`, title: 'bash', }, }; /** * ## Accessibility * * Stories demonstrating accessibility features and keyboard navigation. */ /** * ### Keyboard Navigation * * Tests keyboard interaction with the terminal input field. */ export const KeyboardNavigation: Story = { args: { children: 'Type a command and press Enter to submit.\n$ ', title: 'keyboard-test', onSubmit: (cmd) => alert(`Command submitted: ${cmd}`), }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); // Find input field const input = canvas.getByRole('textbox'); await expect(input).toBeInTheDocument(); // Test typing await userEvent.type(input, 'ls -la'); await expect(input).toHaveValue('ls -la'); // Test Enter key (will trigger alert in this case) // await userEvent.keyboard('{Enter}'); // Note: Commented out to avoid alert in automated testing }, };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server