<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: iamkiya</title>
    <description>The latest articles on Forem by iamkiya (@iamkiya).</description>
    <link>https://forem.com/iamkiya</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1006779%2Fcdba9525-a2ca-4400-af4f-6c6f338f86a5.jpg</url>
      <title>Forem: iamkiya</title>
      <link>https://forem.com/iamkiya</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/iamkiya"/>
    <language>en</language>
    <item>
      <title>Level Up Your Portfolio: Building a Drag-and-Drop Terminal in React!</title>
      <dc:creator>iamkiya</dc:creator>
      <pubDate>Wed, 26 Mar 2025 05:32:08 +0000</pubDate>
      <link>https://forem.com/iamkiya/level-up-your-portfolio-building-a-drag-and-drop-terminal-in-react-81j</link>
      <guid>https://forem.com/iamkiya/level-up-your-portfolio-building-a-drag-and-drop-terminal-in-react-81j</guid>
      <description>&lt;p&gt;I'm thrilled to share the latest milestone in my journey to create the ultimate desktop environment portfolio! I've just finished implementing a fully functional terminal with draggable and resizable windows, all powered by custom code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fivqgn4ohmu0zzl7wh1rd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fivqgn4ohmu0zzl7wh1rd.png" alt="An image showing a terminal interface with text commands and output." width="643" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/somebodyda/my-terminal-app" rel="noopener noreferrer"&gt;github code&lt;/a&gt; &lt;br&gt;
&lt;a href="https://my-terminal-app-d9j1-git-main-somebodydas-projects.vercel.app/" rel="noopener noreferrer"&gt;live preview&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This feature allows users to interact with the simulated operating system in a familiar and intuitive way. I've even included some dummy commands to give you a taste of what's possible.&lt;/p&gt;

&lt;p&gt;Building a realistic desktop environment for a portfolio website is no small feat, and adding a functional terminal takes it to a whole new level. This terminal isn't just a static element; it's a dynamic, interactive component that allows users to explore and engage with the simulated operating system.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Started: Setting Up Your Next.js Project
&lt;/h2&gt;

&lt;p&gt;Before diving into the terminal code, let's walk through the initial setup. We'll be using Next.js for its robust features and excellent developer experience.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a New Next.js App:&lt;/p&gt;

&lt;p&gt;Open your terminal and run the following command to create a new Next.js project:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-next-app my-terminal-app
cd my-terminal-app

&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flas00pvcg1q60n0b5sjz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flas00pvcg1q60n0b5sjz.png" alt="An image of a terminal during the Next.js application setup, displaying the command 'npx create-next-app my-terminal-app' and the subsequent configuration questions, with all affirmative responses except for the src directory prompt." width="800" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This will set up a basic Next.js project structure for you.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install Required Packages:&lt;/p&gt;

&lt;p&gt;We'll need a package to handle drag-and-drop, and type definitions. Install them using npm or yarn:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install react-rnd
npm install -D @types/node @types/react @types/react-dom typescript

&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;react-rnd: This package provides powerful and flexible drag-and-resize functionality. It's perfect for creating resizable and movable windows, which is essential for our terminal.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;@types/*: These packages provide TypeScript type definitions for Node.js, React, and React DOM. Using TypeScript improves code quality and maintainability by adding static typing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;typescript: needed for typescript support.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Important Note Regarding &lt;code&gt;next/dynamic&lt;/code&gt;:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;next/dynamic&lt;/code&gt; import is a built-in feature of Next.js, not a separate package. It allows us to dynamically import components, which is crucial for client-side rendering. Since our terminal component relies on browser-specific APIs, we need to ensure it's not rendered on the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diving into the Code: React-Powered Terminal Magic
&lt;/h2&gt;

&lt;p&gt;Let's take a peek at the React JS code that powers this terminal. We're leveraging &lt;code&gt;next.js&lt;/code&gt; for our application, &lt;code&gt;react-rnd&lt;/code&gt; for the drag-and-resize functionality, and a custom &lt;code&gt;TerminalComponent&lt;/code&gt; to handle the terminal logic.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pages.tsx&lt;/code&gt; (Main Page):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"use client";
import dynamic from "next/dynamic";
import { Rnd } from "react-rnd";

const TerminalComponent = dynamic(() =&amp;gt; import("./components/Terminal"), {
  ssr: false, // Important: Disable server-side rendering
});

export default function Home() {
  return (
    &amp;lt;div className="w-full h-screen"&amp;gt;
      &amp;lt;Rnd
        default={{
          x: 20,
          y: 20,
          width: 600,
          height: 400,
        }}
        minWidth={200}
        minHeight={150}
        style={{
          border: "1px solid #ddd",
          background: "#f0f0f0",
          display: "flex",
          flexDirection: "column",
        }}
      &amp;gt;
        &amp;lt;TerminalComponent /&amp;gt;
      &amp;lt;/Rnd&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we use &lt;code&gt;react-rnd&lt;/code&gt; to make the terminal window draggable and resizable. The &lt;code&gt;dynamic&lt;/code&gt; import ensures that the &lt;code&gt;TerminalComponent&lt;/code&gt; is only loaded on the client-side, which is crucial since it relies on browser-specific APIs.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;index.tsx&lt;/code&gt; (Terminal Component):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// components/Terminal.tsx
"use client";
import React, { useEffect, useRef, useState } from "react";
import type { AvailableCommands, NestedCommands } from "../data/command";

const BashTerminal: React.FC = () =&amp;gt; {
  // 1. Importing Dependencies and Defining Types:
  // (Import statements already present)

  // 2. Initializing State Variables:
  const [cmd, setCmd] = useState&amp;lt;string&amp;gt;("");
  const [output, setOutput] = useState&amp;lt;string&amp;gt;("");
  const [history, setHistory] = useState&amp;lt;string[]&amp;gt;([]);
  const terminalRef = useRef&amp;lt;HTMLDivElement&amp;gt;(null);
  const [nestedMode, setNestedMode] = useState&amp;lt;keyof NestedCommands | null&amp;gt;(null);

  const hostname = "terminal";
  const username = "terminal";
  const [directory, setDirectory] = useState&amp;lt;string&amp;gt;("~");

  // 3. Utility Functions:
  const print = (text: string, currentOutput: string): string =&amp;gt; {
    return currentOutput + text;
  };

  const command = (outputText: string, currentOutput: string): string =&amp;gt; {
    return print(`${outputText}\n${username}@${hostname} ${directory} $ `, currentOutput);
  };

  const empty = (currentOutput = ""): string =&amp;gt; {
    return print(`${username}@${hostname} ${directory} $ `, currentOutput);
  };

  const setup = (): string =&amp;gt; {
    return empty();
  };

  const cd = (dir: string, param: string | undefined): string =&amp;gt; {
    if (param === undefined) {
      return "~";
    }
    if (param.charAt(0) === "/") {
      return param;
    }
    return `${dir}/${param}`;
  };

  // 4. Command Definitions:
  const availableCommands: AvailableCommands = {
    pwd: () =&amp;gt; directory,
    cd: (tokens) =&amp;gt; {
      setDirectory(cd(directory, tokens[1]));
      return null;
    },
    echo: (tokens) =&amp;gt; tokens.slice(1).join(" "),
    clear: () =&amp;gt; ({ clear: true }),
    history: () =&amp;gt; history.join("\n"),
    help: () =&amp;gt; "Available commands: clear, echo, cd, pwd, history, help, mycommand",
    mycommand: () =&amp;gt; {
      setNestedMode("mycommand");
      return "Entered mycommand mode. Type 'list', 'info', or 'exit'.";
    },
  };

  const nestedCommands: NestedCommands = {
    mycommand: {
      list: () =&amp;gt; "Item 1, Item 2, Item 3",
      info: () =&amp;gt; "This is info within mycommand.",
      exit: () =&amp;gt; {
        setNestedMode(null);
        return `\n${username}@${hostname} ${directory} $ `;
      },
    },
  };

  // 5. Command Execution:
  const run = async (cmd: string): Promise&amp;lt;string | { clear: boolean } | null&amp;gt; =&amp;gt; {
    const tokens = cmd.split(" ");
    const commandName = tokens[0];

    if (nestedMode) {
      if (nestedCommands[nestedMode] &amp;amp;&amp;amp; commandName in nestedCommands[nestedMode]) {
        const nestedModeObject = nestedCommands[nestedMode];
        if (typeof nestedModeObject === "object" &amp;amp;&amp;amp; nestedModeObject !== null &amp;amp;&amp;amp; commandName in nestedModeObject) {
          return nestedModeObject[commandName as keyof typeof nestedModeObject]();
        }
      }
      return `Command not found in ${nestedMode}: ${commandName}`;
    }

    if (commandName in availableCommands) {
      const result = availableCommands[commandName as keyof typeof availableCommands](tokens);
      if (result instanceof Promise) {
        return await result;
      }
      return result;
    }

    return commandName ? `Command not found: ${commandName}` : "";
  };

  // 6. Effect Hooks:
  useEffect(() =&amp;gt; {
    setOutput(setup());
    terminalRef.current?.focus();
  }, []);

  useEffect(() =&amp;gt; {
    if (terminalRef.current) {
      terminalRef.current.scrollTo({
        top: terminalRef.current.scrollHeight,
        behavior: "smooth",
      });
    }
  }, [output]);

  // 7. Event Handling:
  const handleKeyDown = async (e: React.KeyboardEvent&amp;lt;HTMLDivElement&amp;gt;) =&amp;gt; {
    if (e.ctrlKey &amp;amp;&amp;amp; e.shiftKey &amp;amp;&amp;amp; e.key === "V") {
      e.preventDefault();
      navigator.clipboard.readText().then(text =&amp;gt; setCmd(prev =&amp;gt; prev + text)).catch(err =&amp;gt; {
        console.error("Clipboard access failed:", err);
        alert("Clipboard access denied. Please check your browser permissions.");
      });
      return;
    }

    if (e.key === "Backspace") {
      e.preventDefault();
      setCmd(prev =&amp;gt; prev.slice(0, -1));
    } else if (e.key === "Enter") {
      e.preventDefault();
      const cmdToRun = cmd.trim();
      if (cmdToRun) {
        setHistory(prev =&amp;gt; [...prev, cmdToRun]);
        const result = await run(cmdToRun.toLowerCase());
        setOutput(prev =&amp;gt; {
          const commandLine = `${username}@${hostname} ${directory} $ ${cmdToRun}`;
          let resultOutput: string | { clear: boolean } | null = "";
          if (result === null) resultOutput = `${username}@${hostname} ${directory} $ `;
          else if (typeof result === "object" &amp;amp;&amp;amp; result.clear) return empty();
          else resultOutput = typeof result === "string" &amp;amp;&amp;amp; result.includes("\n") ? result : `\n${command(typeof result === "string" ? result : "", "")}`;
          const lastPromptIndex = prev.lastIndexOf(`${username}@${hostname} ${directory} $ `);
          const cleanedPrev = lastPromptIndex !== -1 ? prev.substring(0, lastPromptIndex) : prev;
          return cleanedPrev + commandLine + (typeof resultOutput === "string" ? resultOutput : "");
        });
      } else setOutput(prev =&amp;gt; empty(prev));
      setCmd("");
    } else if (e.key.length === 1 &amp;amp;&amp;amp; !e.ctrlKey &amp;amp;&amp;amp; !e.metaKey) setCmd(prev =&amp;gt; prev + e.key);
  };

  // 8. Rendering the Terminal:
  return (
    &amp;lt;div className="flex flex-col w-full h-full p-4 bg-gray-900 text-green-400 font-mono border border-gray-700 shadow-lg overflow-hidden" tabIndex={0} onKeyDown={handleKeyDown} ref={terminalRef}&amp;gt;
      &amp;lt;div className="flex items-center bg-gray-800 px-4 py-2 border-b border-gray-700"&amp;gt;
        &amp;lt;div className="flex space-x-2 mr-4"&amp;gt;
          &amp;lt;div className="w-3 h-3 rounded-full bg-red-500" /&amp;gt;
          &amp;lt;div className="w-3 h-3 rounded-full bg-yellow-500" /&amp;gt;
          &amp;lt;div className="w-3 h-3 rounded-full bg-green-500" /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div className="text-sm text-gray-400"&amp;gt;bash&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;pre className="flex-1 p-4 overflow-y-auto text-sm leading-relaxed whitespace-pre-wrap break-words"&amp;gt;
        {output}
        &amp;lt;span className="inline-flex items-center"&amp;gt;
          {cmd}
          &amp;lt;span className="ml-1 w-2 h-5 bg-green-400 animate-pulse"&amp;gt;|&amp;lt;/span&amp;gt;
        &amp;lt;/span&amp;gt;
      &amp;lt;/pre&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default BashTerminal;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;commands.tsx&lt;/code&gt; (Command Types):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface AvailableCommands {
  pwd: () =&amp;gt; string;
  cd: (tokens: string[]) =&amp;gt; string | null;
  echo: (tokens: string[]) =&amp;gt; string;
  clear: () =&amp;gt; { clear: boolean };
  history: () =&amp;gt; string;
  help: () =&amp;gt; string;
  mycommand: () =&amp;gt; string;
}

interface NestedCommands {
  mycommand: {
    list: () =&amp;gt; string;
    info: () =&amp;gt; string;
    exit: () =&amp;gt; string;
  };
}

export type { AvailableCommands, NestedCommands };

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file defines the types for our commands, ensuring type safety and code clarity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technical Highlights:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  Client-Side Rendering with &lt;code&gt;next/dynamic&lt;/code&gt;: As mentioned earlier, &lt;code&gt;next/dynamic&lt;/code&gt; with &lt;code&gt;ssr: false&lt;/code&gt; is crucial for our terminal component. Browser-specific APIs like DOM manipulation are only available on the client side.&lt;/li&gt;
&lt;li&gt;  Drag-and-Resize with &lt;code&gt;react-rnd&lt;/code&gt;: &lt;code&gt;react-rnd&lt;/code&gt; simplifies the process of creating draggable and resizable elements. Its intuitive API and extensive customization options make it a great choice for our terminal windows.&lt;/li&gt;
&lt;li&gt;  State Management with &lt;code&gt;useState&lt;/code&gt;: We use &lt;code&gt;useState&lt;/code&gt; to manage the terminal's state, including command input, output, and history. React's state management ensures that the terminal updates correctly in response to user interactions.&lt;/li&gt;
&lt;li&gt;  DOM Manipulation with &lt;code&gt;useRef&lt;/code&gt;: &lt;code&gt;useRef&lt;/code&gt; allows us to access the terminal's DOM element for scrolling and focus management. This is essential for providing a smooth and responsive user experience.&lt;/li&gt;
&lt;li&gt;  Event Handling with &lt;code&gt;onKeyDown&lt;/code&gt;: We use &lt;code&gt;onKeyDown&lt;/code&gt; to capture keyboard input, enabling users to type commands and interact with the terminal.&lt;/li&gt;
&lt;li&gt;  TypeScript for Type Safety: By using TypeScript and type definitions, we ensure that our code is type-safe, reducing the risk of runtime errors and improving code maintainability.&lt;/li&gt;
&lt;li&gt;  Clipboard integration: added ctrl+shift+v for paste from clipboard.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why These Packages?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;react-rnd&lt;/code&gt;: I chose &lt;code&gt;react-rnd&lt;/code&gt; because of its ease of use and flexibility. It provides a simple and efficient way to add drag-and-resize functionality to React components, which is essential for creating a realistic desktop environment.&lt;/li&gt;
&lt;li&gt;  TypeScript: I opted for TypeScript to improve code quality and maintainability. Its static typing helps catch errors early and makes the codebase easier to understand and refactor.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Looking Ahead: What's Next?
&lt;/h4&gt;

&lt;p&gt;Stay tuned for more updates as I continue to build the "GOAT" portfolio website! What features are you most excited to see next? I'm planning to add more interactive elements, enhance the simulated operating system, and refine the overall user experience.&lt;/p&gt;

&lt;p&gt;Some potential features I'm considering include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Implementing a file system and file explorer.&lt;/li&gt;
&lt;li&gt;  Adding more complex commands and utilities.&lt;/li&gt;
&lt;li&gt;  Creating a more visually appealing desktop environment.&lt;/li&gt;
&lt;li&gt;  Adding more nested commands.&lt;/li&gt;
&lt;li&gt;  Adding more commands to the available commands.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Looking Ahead: What's Next?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Stay tuned for more updates as I continue to build the 'GOAT' portfolio website! What features are you most excited to see next?&lt;/p&gt;

</description>
      <category>react</category>
      <category>nextjs</category>
      <category>portfolio</category>
      <category>terminal</category>
    </item>
  </channel>
</rss>
