DEV Community

Abhi Raj
Abhi Raj

Posted on • Edited on

4

How to make custom mentions input box like google chat in react

Image description

Image description

Note: i also made a package which you can use
https://www.npmjs.com/package/react-mentions-input-latest

code:

./MentionsInput.jsx



import React, { useEffect, useRef, useState } from "react";
import { setCaretToEnd, createSpanDiv } from "./utils";
import "./MentionsInput.css";
function MentionsInput(props) {
  const { data: UsersList, trigger } = props;
  const [showSuggestion, setShowSuggestion] = useState(false);
  const main_text_area = document.getElementById("main-text-area");
  const main_text_areaRef = useRef(null);
  const handleSuggestionClick = (name, id) => {
    createSpanDiv(name, main_text_area);
    setCaretToEnd(main_text_areaRef?.current);
    const childNodes = Array.from(main_text_areaRef?.current?.childNodes);

    childNodes.forEach((node) => {
      if (node.nodeType !== Node.TEXT_NODE) return;
      const text = node.textContent;
      const replacedText = text.replace(
        new RegExp(`${trigger}(\\w+)`, "g"),
        ""
      );
      node.textContent = replacedText;
    });

    setShowSuggestion(false);
  };
  useEffect(() => {
    if (!main_text_areaRef?.current) return;
    const main_text_area = main_text_areaRef?.current;
    const inputVal = document.getElementById("ow808");

    main_text_area.setAttribute("tabindex", 1);
    main_text_area.focus();

    // Function to handle keyup event
    const handleKeyUp = (e) => {
      const inputText = e?.target?.innerText;

      const atIndex = inputText.lastIndexOf(trigger);
      const showSuggestion =
        atIndex !== -1 &&
        inputText.substring(atIndex + 1).split(" ")[0].length > 2;
      setShowSuggestion(showSuggestion);
    };

    // Add event listener
    inputVal.addEventListener("keyup", handleKeyUp);

    // Cleanup function to remove event listener
    return () => inputVal.removeEventListener("keyup", handleKeyUp);
  }, [main_text_areaRef?.current]);

  return (
    <div className="h-screen w-full flex justify-center items-center bg-pink-100">
      <div className="w-[400px] h-[20px]" id="ow808">
        {showSuggestion && (
          <section className="flex flex-col m-2 my-2 bg-white px-2 py-3 rounded-lg transition-all ease-in-out duration-500">
            <div className="max-h-36 overflow-y-scroll">
              {UsersList &&
                UsersList.length > 0 &&
                UsersList.map((user, index) => (
                  <div
                    className={`flex justify-start items-center p-2 space-x-1 cursor-pointer hover:bg-gray-200  ${
                      index === 4 || index === UsersList.length - 1
                        ? ``
                        : `border-b-[1.5px]`
                    } transition-all ease-in-out duration-500 focus:bg-[#c4c4c4] `}
                    key={index}
                    onClick={() => {
                      handleSuggestionClick(user.name, user.id);
                    }}
                  >
                    <img
                      className="h-6 w-6 rounded-full object-cover"
                      src={user?.userAvatar}
                      alt={user?.name}
                    />
                    <p className="overflow-x-hidden">{user.name}</p>
                  </div>
                ))}
            </div>
          </section>
        )}
        <div className="cnOaDb I9OJHe">
          <div
            className="border-2 border-gray-400 focus:outline-none px-3 py-2 rounded-lg"
            role="textbox"
            aria-multiline="true"
            spellCheck="true"
            id="main-text-area"
            ref={main_text_areaRef}
            contentEditable="true"
            dir="ltr"
            placeholder="Type Somehting here ...."
          ></div>
        </div>
      </div>
    </div>
  );
}

export default MentionsInput;




Enter fullscreen mode Exit fullscreen mode

./utils.js



export function setCaretToEnd(element) {
  element.focus();
  const range = document.createRange();
  const selection = window.getSelection();
  range.selectNodeContents(element);
  range.collapse(false);
  selection.removeAllRanges();
  selection.addRange(range);
}

export const UsersList = [
  {
    id: 235075,
    name: "Nitin P",
    userAvatar:
      "https://cdn.pixabay.com/photo/2018/03/27/21/43/startup-3267505_640.jpg",
  },
  {
    id: 473839,
    name: "Nitin P",
    userAvatar:
      "https://cdn.pixabay.com/photo/2018/03/27/21/43/startup-3267505_640.jpg",
  },
  {
    id: 2360589,
    name: "Nitin Pahwa",
    userAvatar:
      "https://cdn.pixabay.com/photo/2018/03/27/21/43/startup-3267505_640.jpg",
  },
  {
    id: 713607,
    name: "Nitin Pal",
    userAvatar:
      "https://cdn.pixabay.com/photo/2018/03/27/21/43/startup-3267505_640.jpg",
  },
  {
    id: 1496449,
    name: "Nitin Pandey",
    userAvatar:
      "https://cdn.pixabay.com/photo/2018/03/27/21/43/startup-3267505_640.jpg",
  },
];

export const createSpanDiv = (name, main_text_area) => {
  var spanElement = document.createElement("span");
  spanElement.textContent = name;
  spanElement.classList = "text-indigo-500 font-bold mentioned-user";
  spanElement.setAttribute("contenteditable", "false");
  main_text_area.appendChild(spanElement);
};




Enter fullscreen mode Exit fullscreen mode

./MentionsInput.css__




[contenteditable="true"]:empty:before {
  content: attr(placeholder);
  pointer-events: none;
  display: block; /* For Firefox */
  color: rgba(92, 86, 89, 0.497);
}

/* width */
::-webkit-scrollbar {
  width: 7px;
}

/* Track */
::-webkit-scrollbar-track {
  background: #888;
  border-radius: 10px;
}

/* Handle */
::-webkit-scrollbar-thumb {
  background: #5e5d5d;
  border-radius: 5px;
}

/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
  background: #555;
}



Enter fullscreen mode Exit fullscreen mode

use it like this

./App.js



import React, { useEffect, useState } from "react";
import {
  BrowserRouter as Router,
  Routes,
  Route,
  Navigate,
  useNavigate,
} from "react-router-dom";
import MentionsInput from "./Components/MentionsInput";
import { UsersList } from "./Components/utils";

function App() {
  return (
    <Router>
      <Routes>
        <Route
          path="/"
          element={
            <>
              <MentionsInput data={UsersList} trigger="#" />
            </>
          }
        />
      </Routes>
    </Router>
  );
}

export default App;




Enter fullscreen mode Exit fullscreen mode

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (1)

Collapse
 
nirajkvinit profile image
Niraj Kumar

Library Not working. Perhaps, you may want to publish a working demo

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay