DEV Community

0 seconds of 0 secondsVolume 90%
Press shift question mark to access a list of keyboard shortcuts
00:00
00:00
00:00
 
D2D
D2D

Posted on

The Power of Thinking..."I CAN"

First off, I need to admit that despite my years of experience working on so many projects, there is one hurdle that I never quite got over - creating my own Chat UI. I've tried doing it several times in the past (mostly in my private tinkering). I sometimes thought, "I think this is this, this weekend, I'm going to crack this and get it looking right." But no matter what, this is one of those UI features I could never get right.

After years of attempting it, I eventually began to think to myself that maybe I'd never be able to crack this thing. So, I began to abandon that attempt...until today.


Adding a Group Discussion Feature to AceIt's Assignment Details page

The first thing I really want to conquer was getting those chat bubbles right. So I started with the chat bubbles from the other senders in the Group Discussion:

{/* Outermost layout will have a left align by default */}
<div className="flex-grow flex-col p-4 overflow-y-auto">
  {/* Chat bubble from other senders */}
  <div className="flex mb-4">
    <div className="flex items-start mr-4"> 
      {/* The contents of the chat bubble */} 
    </div>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

The best way to look at this layout is that the outermost container is an entire row stretched across the group chat area.

Like with most chatroom UIs, incoming messages will always start from the left of the UI. And when it comes to the contents of our chat bubbles, we want to ensure that everything is aligned to the top, so we use items-start here.

Adding Contents to our Chat Bubble

{/* Outermost layout will have a left align by default */}
<div className="flex-grow flex-col p-4 overflow-y-auto">
  {/* Chat bubble from other senders */}
  <div className="flex mb-4">
    <div className="flex items-start mr-4"> 
      {/* Next, we add our profile image. */}
      <div className="w-8 h-8 rounded-full overflow-hidden mr-2">
        <Image 
          src="/female-student-chinese.jpg" 
          alt="Profile Picture" 
          width={50} 
          height={50} 
          className="object-cover" 
        />
      </div> 
    </div>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

The next thing we want is to include the user's profile image. This one here is quite straightforward. We want a fully rounded profile image, and it will be positioned to the start/left of the chat bubble.

Next, we will add our message contents.

{/* Outermost layout will have a left align by default */}
<div className="flex-grow flex-col p-4 overflow-y-auto">
  {/* Chat bubble from other senders */}
  <div className="flex mb-4">
    <div className="flex items-start mr-4"> 
      {/* Profile Image here. */}

      <div className="bg-gray-200 rounded-lg p-3 max-w-xs">
        <p className="text-xs font-medium text-gray-800">Agnes Yeo</p> 
        <p className="text-sm">Hey everyone, just wanted to check in and see how the project's coming along!</p>
        <span className="text-xs text-gray-500">Tue 10:15 am</span>
      </div>
    </div>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

What About the User's Message Bubble?

That is quite simple; we just need to make a few changes. Like most familial chat apps, as the sender/user, we don't need to display a profile image. So it will be just the message content itself.

<div className="flex justify-end mb-4">
  <div className="bg-blue-500 text-white rounded-lg p-3 max-w-xs ml-4"> 
    <p className="text-sm">Hey Agnes! I'm making good progress on the research. How's the mood board coming along?</p>
    <span className="text-xs text-gray-100">Tue 10:45 am</span> 
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Our Textarea for Typing, Sending and Attaching Stuff

The most essential feature of any Chat UI is having the ability to type a message, hit Send, right? So we can't do without that functionality here.

{/* Message Input Area */}
<div className="border-t border-gray-300 p-4 bg-white"> 
  {/* Use flex-col for vertical layout */}
  <div className="flex flex-col justify-start">
    {/* Container for textarea and send button */}
    <div className="flex"> 
      <textarea 
        className="flex-grow resize-none border border-gray-400 rounded-md p-2 mr-2 focus:outline-none focus:ring focus:ring-blue-300 bg-gray-100" 
        placeholder="Type your message..."
      ></textarea>
      <button className="flex bg-blue-500 text-white px-4 py-2 rounded-md self-start items-center justify-center ml-2"> 
        Send <SendHorizonal width={20} height={20} className="ml-1" />
      </button>
    </div>
    {/* Attachment button below */}
    <button className="bg-gray-300 rounded-md py-2 px-4 mt-2 flex items-center justify-center w-max"> 
      <Paperclip width={16} height={16} className="mr-1"/> Add Attachments
    </button>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

What is important here is that I've chosen to place the attachment in a separate flex row (layout-wise). The initial idea of placing it inside the textarea didn't work out very well. Plus I wanted to keep things simple and not overcomplicate things. So, this seems like the easiest solution.

Once I get to the logic implementation stages, I'll add in a context menu for the Button. So it will present the student with a few options like attaching Images, Videos, Files and Links.

At this point, I'm already feeling pretty confident and honestly quite pleased with myself. But I also felt something's still missing.

Image Attachments and Modal Overlays...so, Let's Add Some!!

<div className="flex justify-end mb-4">
  <div className="bg-blue-500 text-white rounded-lg p-3 max-w-xs ml-4"> 
    {/* Our message content remains here. */}

    {/* Container for attachment previews */}
    <div className="flex flex-row gap-x-2 mt-2"> 
      <div className="w-1/2 relative"> 
        {/* Add "relative" for image overlay */}
        <div className="relative aspect-square overflow-hidden rounded-md">
          <Image 
            src="/sample-wireframes-01.jpg"
            fill={true}
            quality={90}
            alt="Mobile friendly wireframes" 
            className="object-cover w-1/2 aspect-square" 
          />
        </div>
        <div className="absolute inset-0 flex items-center justify-center opacity-0 hover:opacity-50 bg-gray-900 rounded-md transition duration-300">
          {/* Expand icon */}
          <Expand className="text-white" size={32} /> 
        </div>
      </div>
      <div className="w-1/2 relative">
        <div className="relative aspect-square overflow-hidden rounded-md"> 
          {/* Image 2 */}
          <Image 
            src="/sample-wireframes-02.jpg"
            alt="Mobile friendly wireframes"
            quality={90}
            fill={true}
            className="object-cover w-1/2 aspect-square" 
          />
        </div>
        <div className="absolute inset-0 flex items-center justify-center opacity-0 hover:opacity-50 bg-gray-900 rounded-md transition duration-300">
          {/* Expand icon */}
          <Expand className="text-white" size={32} /> 
        </div>
      </div>
    </div>
    <span className="text-xs text-gray-100">Thu 2:15 pm</span> 
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

I've removed the onClick event handler from the code snippet for now. The most important aspect is how I've laid out the attached images to the bottom of the message. I've ensured they each share half the width while filling out a square shape. Think of it like thumbnails.

I've also included the <Expand /> Lucide icons. They worked perfectly with the layout. The overlay will show nicely when a user mouseovers the images on a desktop browser.

Setting up our Modal Overlay

At first, I tried to place this overlay layout at the bottom of the section, but that somehow didn't quite work properly. So I decided to move it all the way to the top of the right panel. And now, it works perfectly.

{/* Image Modal Overlay */}
{isShowImageModal && (
  <div className="absolute left-0 top-0 w-full h-full z-40 items-center justify-center"
    style={{
      backgroundColor: "#000000AA"
    }}
  >
    {/* Container for image and controls */}
    <div className="relative">  
      {/* Close button */}
      <button className="absolute top-4 right-4 z-50 text-white"> 
        <X size={32} onClick={() => setIsShowImageModal(false)} /> 
      </button>
    </div>

    <div className="w-[90%] h-[90%] opacity-100">
      {/* Left Chevron button */}
      {currModalImage.includes("02") && (
        <button 
          className="absolute top-[50%] left-5 z-50 text-white"
          onClick={() => {
            if (currModalImage.includes("02")) {
              setCurrModalImage("/sample-wireframes-01.jpg");
            }
          }}
        > 
          <ChevronLeft 
            size={48} 
          /> 
        </button>
      )}

      <Image src={currModalImage} alt="attachment image" fill style={{ objectFit: "contain" }} className="aspect-auto" />

      {/* Right Chevron button */}      
      {currModalImage.includes("01") && (
        <button 
          className="absolute top-[50%] right-5 z-50 text-white"
          onClick={() => {
            if (currModalImage.includes("01")) {
              setCurrModalImage("/sample-wireframes-02.jpg");
            }
          }}
        > 
          <ChevronRight size={48} />
        </button>
      )}
    </div>
  </div>
)}
Enter fullscreen mode Exit fullscreen mode

With the overlay done, the next thing I needed to do was to add the Image to the overlay:

<Image 
  src={currModalImage} 
  alt="attachment image" 
  fill 
  style={{ objectFit: "contain" }} 
  className="aspect-auto" 
/>
Enter fullscreen mode Exit fullscreen mode

The props fill, style={{ objectFit: "contain" }} and className="aspect-auto" helped to ensure that regardless of whether the photo/image is in a portrait or landscape mode, it would still fit nicely within the overlay's area.

Next, we need to add our Chevrons (Left/Right) so that the user can easily navigate between the available images. For our UI prototype, we will go straight ahead and hard-code the values. But ofcourse, when it comes time to work on the logic, we will need a more dynamic way to handle this.

{/* Left Chevron button */}
{currModalImage.includes("02") && (
  <button 
    className="absolute top-[50%] left-5 z-50 text-white"
    onClick={() => {
      if (currModalImage.includes("02")) {
        setCurrModalImage("/sample-wireframes-01.jpg");
      }
    }}
  > 
    <ChevronLeft 
      size={48} 
    /> 
  </button>
)}

<Image src={currModalImage} alt="attachment image" fill style={{ objectFit: "contain" }} className="aspect-auto" />

{/* Right Chevron button */}
{currModalImage.includes("01") && (
  <button 
    className="absolute top-[50%] right-5 z-50 text-white"
    onClick={() => {
      if (currModalImage.includes("01")) {
        setCurrModalImage("/sample-wireframes-02.jpg");
      }
    }}
  > 
    <ChevronRight size={48} />
  </button>
)}
Enter fullscreen mode Exit fullscreen mode

The position of the Chevrons is worth highlighting/noting. Now, this is what I can think of using tailwindcss. Perhaps there might be a more effective way to do it. Gemini kind of broke down around this time (actually before I implemented them). I actually did this whole portion by myself without any help from Gemini, so I'm feeling pretty good about myself.

Since I've added the conditional checks before rendering the Chevron buttons, I can simplify the internal logic by self-checking my own UI logic. So the final UI Chevron button should look something like this:

{/* Right Chevron button */}
{currModalImage.includes("01") && (
  <button 
    className="absolute top-[50%] right-5 z-50 text-white"
    onClick={() => setCurrModalImage("/sample-wireframes-02.jpg")}
  > 
    <ChevronRight size={48} />
  </button>
)}
Enter fullscreen mode Exit fullscreen mode

Final Thoughts & Ideas

So, guys, that's it for my Group Discussion section UI prototype. Frankly, I am pretty happy with how it all turned out. I am sure we can make more tweaks to enhance the UI further. Still, before I overcomplicate everything, the key objective at this stage of the project is to assess and identify any position UI issues before making the UI slightly more dynamic.

A couple of ideas/next-steps on my mind:

πŸ‘¨β€πŸ’» I could eventually refactor the message bubbles and turn them into reusable components.

πŸ‘¨β€πŸ’» Once I get to the application logic, I will add the attachments selection.

πŸ‘¨β€πŸ’» Auto-scroll down to the latest message.

Dev Diairies image

User Feedback & The Pivot That Saved The Project β†ͺ️

We’re following the journey of a dev team building on the Stellar Network as they go from hackathon idea to funded startup, testing their product in the real world and adapting as they go.

Watch full video πŸŽ₯

Top comments (0)

Scale globally with MongoDB Atlas. Try free.

Scale globally with MongoDB Atlas. Try free.

MongoDB Atlas is the global, multi-cloud database for modern apps trusted by developers and enterprises to build, scale, and run cutting-edge applications, with automated scaling, built-in security, and 125+ cloud regions.

Learn More