» React创建在线聊天软件 Web Chat App 前端 » 2. 开发 » 2.2 UI 排版布局

UI Layout

Let’s make a simple layout for the chat app first.

Open App.js, delete everything from the template, and update it to this:

import "./App.css";

function App() {
  return (
    <div className="app">
      <h1 className="app-name">Literank Web Chat</h1>
      <div className="segments">
        <span className="segment left-seg picked">Chat</span>
        <span className="segment right-seg">Groups</span>
      </div>
      <div className="card">
        <div className="contacts"></div>
        <div className="main">
          <div className="messages"></div>
          <div className="edit">
            <textarea className="edit-box" placeholder="Type here" />
            <div className="buttons">
              <button className="send-btn">Send</button>
              <span className="tip">Ctrl+Enter to send</span>
            </div>
          </div>
        </div>
      </div>
      <div className="status">
        <span className="dot connected"></span>
        <span>Connected</span>
      </div>
    </div>
  );
}

export default App;

Open App.css, delete everything from the template, and update it to this:

:root {
  --bg-color: #008170;
  --text-color: #ffffff;
  --theme-color: #005b41;
  --secondary-color: #232d3f;
}

body {
  background-color: var(--bg-color);
  font-family: Geneva, Verdana, Tahoma, sans-serif;
}

.app {
  margin: 5vh auto;
  width: 60rem;
  text-align: center;
  color: var(--text-color);
}

.app-name {
  text-align: left;
}

.segments {
  margin-top: 1rem;
}

.segment {
  background-color: var(--secondary-color);
  padding: 5px 12px;
  cursor: pointer;
}

.left-seg {
  border-top-left-radius: 0.5rem;
  border-bottom-left-radius: 0.5rem;
}

.right-seg {
  border-top-right-radius: 0.5rem;
  border-bottom-right-radius: 0.5rem;
}

.status {
  display: flex;
  justify-content: flex-end;
  align-items: center;
}

.picked {
  background-color: var(--theme-color);
}

.card {
  box-sizing: border-box;
  background-color: rgba(255, 255, 255, 0.2);
  margin: 1rem 0;
  border-radius: 0.5rem;
  display: flex;
}

.contacts {
  flex: 1;
}

.main {
  flex: 3;
  height: 60vh;
  background-color: whitesmoke;
  border-top-right-radius: 0.5rem;
  border-bottom-right-radius: 0.5rem;
  display: flex;
  flex-direction: column;
}

.main .messages {
  flex: 6;
}

.main .edit {
  border-top: solid 1px rgba(255, 255, 255, 0.5);
  flex: 1;
  display: flex;
}

.edit .edit-box {
  font-size: large;
  padding: 5px;
  border: none;
  flex: 5;
  resize: none;
}

.edit-box:focus {
  outline: solid 1px var(--theme-color);
}

.edit .buttons {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-evenly;
  flex-direction: column;
}

.tip {
  font-size: 10px;
  color: gray;
}

.send-btn {
  padding: 6px 20px;
  font-size: 20px;
  background-color: white;
  border: none;
  border-bottom: solid 5px var(--theme-color);
  border-radius: 0.5rem;
  cursor: pointer;
  color: var(--theme-color);
}

.dot {
  width: 10px;
  height: 10px;
  border-radius: 5px;
  background-color: white;
  margin-right: 5px;
}

.connected {
  background-color: lime;
}

The dev server will automatically refresh the page, and you should be able to see something like this.

Chat App UI

Message Bubbles

Add a component src/components/message.jsx:

import React from "react";
import "./Message.css";
import clsx from "clsx";

const Message = (props) => {
  return (
    <div className={clsx("wrapper", { "self-side": props.isSelf })}>
      <div className="message">
        <div className="meta">{props.username}</div>
        <div className="bubble">{props.message}</div>
      </div>
    </div>
  );
};

export default Message;

clsx is a tiny utility for constructing className strings conditionally.

Use npm to install it.

npm i clsx

Add its style file src/components/Message.css:

@import url(../vars.css);

.wrapper {
  display: flex;
  justify-content: flex-start;
}

.message {
  width: max-content;
}

.self-side {
  justify-content: flex-end;

  .bubble {
    background-color: var(--theme-color);
  }

  .meta {
    text-align: right;
    padding-right: 0.4em;
  }
}

.meta {
  color: darkgray;
  text-align: left;
  padding-left: 0.4em;
}

.bubble {
  background-color: var(--bg-color);
  padding: 0.6em;
  border-radius: 0.8em;
}

To share css variables among multiple files, create src/vars.css:

:root {
  --bg-color: #008170;
  --text-color: #ffffff;
  --theme-color: #005b41;
  --secondary-color: #232d3f;
}

Update src/App.css:

@@ -1,9 +1,4 @@
-:root {
-  --bg-color: #008170;
-  --text-color: #ffffff;
-  --theme-color: #005b41;
-  --secondary-color: #232d3f;
-}
+@import url(vars.css);
 
 body {
   background-color: var(--bg-color);
@@ -75,6 +70,7 @@ body {
 
 .main .messages {
   flex: 6;
+  padding: 10px;
 }
 
 .main .edit {

Put in test messages in src/App.js:

@@ -1,6 +1,25 @@
 import "./App.css";
+import Message from "./components/message";
 
 function App() {
+  const messages = [
+    {
+      username: "Alice",
+      message: "Hello there",
+    },
+    {
+      username: "Bob",
+      message: "How's everything?",
+    },
+    {
+      username: "Alice",
+      message: "Great! Wanna have a drink?",
+    },
+    {
+      username: "Bob",
+      message: "Sure",
+    },
+  ];
   return (
     <div className="app">
       <h1 className="app-name">Literank Web Chat</h1>
@@ -11,7 +30,15 @@ function App() {
       <div className="card">
         <div className="contacts"></div>
         <div className="main">
-          <div className="messages"></div>
+          <div className="messages">
+            {messages.map((e) => (
+              <Message
+                username={e.username}
+                message={e.message}
+                isSelf={e.username === "Bob"}
+              />
+            ))}
+          </div>
           <div className="edit">
             <textarea className="edit-box" placeholder="Type here" />
             <div className="buttons">

Your test messages should look like this:

Message Bubbles

Contact List

Add src/components/contact.jsx:

import React from "react";
import "./Contact.css";
import clsx from "clsx";

const Contact = (props) => {
  return (
    <div className={clsx("contact", { offline: props.isOffline })}>
      <div className="name truncate">{props.username}</div>
      <div className="last-message truncate">{props.message}</div>
    </div>
  );
};

export default Contact;

Add src/components/Contact.css:

@import url(../vars.css);

.contact {
  padding: 6px 10px;
  border-bottom: solid 1px var(--theme-color);
  text-align: left;
}

.name {
  font-weight: bold;
  font-size: larger;
  max-width: 7em;
}

.last-message {
  color: lightblue;
  font-size: x-small;
  max-width: 14em;
}

.offline {
  color: var(--secondary-color);
}

.truncate {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

Tune src/App.js:

@@ -1,4 +1,5 @@
 import "./App.css";
+import Contact from "./components/contact";
 import Message from "./components/message";
 
 function App() {
@@ -20,6 +21,24 @@ function App() {
       message: "Sure",
     },
   ];
+  const contacts = [
+    {
+      username: "🦁 Alice",
+      message: "Sure",
+    },
+    {
+      username: "♣ Cindy",
+      message: "Where r u",
+    },
+    {
+      username: "🐯 Doug Smith",
+      message: "Hi",
+    },
+    {
+      username: "🐴 Emily",
+      message: "How's your assignment? I didn't do much yesterday",
+    },
+  ];
   return (
     <div className="app">
       <h1 className="app-name">Literank Web Chat</h1>
@@ -28,7 +47,15 @@ function App() {
         <span className="segment right-seg">Groups</span>
       </div>
       <div className="card">
-        <div className="contacts"></div>
+        <div className="contacts">
+          {contacts.map((e) => (
+            <Contact
+              username={e.username}
+              message={e.message}
+              isOffline={e.username.includes("Emily")}
+            />
+          ))}
+        </div>
         <div className="main">
           <div className="messages">
             {messages.map((e) => (
@@ -49,8 +76,11 @@ function App() {
         </div>
       </div>
       <div className="status">
-        <span className="dot connected"></span>
-        <span>Connected</span>
+        <span>🐱 Bob</span>
+        <div className="connection-status">
+          <span className="dot connected"></span>
+          <span>Connected</span>
+        </div>
       </div>
     </div>
   );

Tune src/App.css:

@@ -7,7 +7,7 @@ body {
 
 .app {
   margin: 5vh auto;
-  width: 60rem;
+  width: 52rem;
   text-align: center;
   color: var(--text-color);
 }
@@ -38,7 +38,7 @@ body {
 
 .status {
   display: flex;
-  justify-content: flex-end;
+  justify-content: space-between;
   align-items: center;
 }
 
@@ -59,7 +59,7 @@ body {
 }
 
 .main {
-  flex: 3;
+  flex: 4;
   height: 60vh;
   background-color: whitesmoke;
   border-top-right-radius: 0.5rem;
@@ -115,6 +115,11 @@ body {
   color: var(--theme-color);
 }
 
+.connection-status {
+  display: flex;
+  align-items: center;
+}
+
 .dot {
   width: 10px;
   height: 10px;

Your contact list should be something like this:

Contact List

Title Bar

Add src/components/title-bar.jsx:

import React from "react";
import "./TitleBar.css";

const TitleBar = (props) => {
  return <div className="title">{props.username}</div>;
};

export default TitleBar;

Add src/components/TitleBar.css:

.title {
  font-size: larger;
  color: black;
  padding: 10px;
  border-bottom: solid 1px lightgray;
}

Tune src/App.js:

@@ -1,6 +1,7 @@
 import "./App.css";
 import Contact from "./components/contact";
 import Message from "./components/message";
+import TitleBar from "./components/ttile-bar";
 
 function App() {
   const messages = [
@@ -57,6 +58,7 @@ function App() {
           ))}
         </div>
         <div className="main">
+          <TitleBar username={contacts[0].username} />
           <div className="messages">
             {messages.map((e) => (
               <Message

Tune message bubble background color in src/components/Message.css:

@@ -29,7 +29,7 @@
 }
 
 .bubble {
-  background-color: var(--bg-color);
+  background-color: var(--secondary-color);
   padding: 0.6em;
   border-radius: 0.8em;
 }

It looks like this:

Title Bar