DEV Community

Abhi Raj
Abhi Raj

Posted on • Updated on

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

Top comments (1)

Collapse
 
nirajkvinit profile image
Niraj Kumar

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