這次我們會使用React 和 MUI 實作一個聊天視窗。
MUI 提供了一個簡單、可定制且可訪問的 React 組件庫。 遵循您自己的設計系統,或從 Material Design 開始。
MUI: The React component library you always wanted
Install Environment
npm install @mui/material @emotion/react @emotion/styled @mui/icons-material
Prepare Data
這邊我們準備一些聊天資料,其中data的結構就是之後聊天資料新增時所使用的結構。
### ./src/static/chat.json
{
"data": [
{"id":0, "user": "Theta", "content": "hello Tom!", "timestamp": "2022-10-18 09:56:00.000"},
{"id":1,"user": "Tom", "content": "hello Any!", "timestamp": "2022-10-18 09:57:00.000"},
{"id":2,"user": "Any", "content": "hello Theta!", "timestamp": "2022-10-18 09:58:00.000"}
]
}
Type
### ./src/type/chat.tsx
export type Chat = {
id: number
user: string
content: string
timestamp: string
}
components
這邊我們做一個讀取資料的方法,未來可轉化成與資料庫連接。
### ./src/components/chatService.tsx
import { Chat } from "../type/chat";
import chatJson from '../static/chat.json'
type ChatDataSource = {
data: Array<Chat>
}
export const ChatService = () => {
return {
getChatHistory: (): ChatDataSource => chatJson
}
}
再來就是主要的主要畫面,我們期待是一個可以往下滾動的聊天室窗。
### ./src/components/bubbleWindow.tsx
import * as React from 'react';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import Divider from '@mui/material/Divider';
import ListItemText from '@mui/material/ListItemText';
import ListItemAvatar from '@mui/material/ListItemAvatar';
import Avatar from '@mui/material/Avatar';
import Typography from '@mui/material/Typography';
import { ChatService } from "./chatService";
import { Chat } from '../type/chat';
import { useEffect, useState } from 'react';
import { Button, Input } from '@mui/material';
import SendIcon from '@mui/icons-material/Send';
interface ItemsChatListPorps {
chatData: Array<Chat>
}
interface SendMsgElementPorps {
onCallback: ((chats: Array<Chat>) => void)
data: Array<Chat>
}
const genChatRowUserInfo = (data: Chat): JSX.Element => {
return (
<React.Fragment>
<Typography
sx={{ display: 'inline' }}
component="span"
variant="body2"
color="text.primary"
>
{data.user}
</Typography>
<br/>
{data.timestamp}
</React.Fragment>
)
}
const genChatRow = (data: Chat): JSX.Element => {
return (
<div key={data.id}>
<ListItem alignItems="flex-start">
<ListItemAvatar>
<Avatar alt="Remy Sharp" src="/static/images/avatar/1.jpg" />
</ListItemAvatar>
<ListItemText
primary={data.content}
secondary={genChatRowUserInfo(data)}
/>
</ListItem>
<Divider variant="inset" component="li" hidden/>
</div>
)
}
const ItemsChatList = (props:ItemsChatListPorps) => {
const chatData = props.chatData.map(data => genChatRow(data))
return (
<List id="MainWin" sx={{ height: 500, width: '100%', maxWidth: 360, bgcolor: 'background.paper', overflow: "auto" }}>
{chatData}
</List>
);
}
const SendMsgElement = (props:SendMsgElementPorps) => {
const [newChat, setNewChat] = useState("");
return (
<div className="p-inputgroup">
<Input
autoFocus={true}
placeholder="say something"
value={newChat}
onChange={(e)=> {
setNewChat(e.target.value);
}
}/>
<Button
endIcon={<SendIcon />}
className="p-button-info"
onClick={() => {
const updatedChatData = props.data.concat([refactorChat(props.data.length+1, newChat)])
props.onCallback(updatedChatData)
setNewChat("")
}}
>Send</Button>
</div>
);
}
const refactorChat = (id:number, sent: string): Chat => ({
id: id,
content: sent,
timestamp: "time",
user: "someone"
})
const scrollToButtom = (idLabel: string) => {
const chatWindowElement = document.getElementById(idLabel);
if(chatWindowElement){
chatWindowElement.scrollTo(0, chatWindowElement.scrollHeight)
}
}
export default function AlignItemsList() {
const chatService = ChatService()
const [chatData, setChatData] = useState<Array<Chat>>(chatService.getChatHistory().data);
useEffect(() => { scrollToButtom("MainWin") })
return (
<div>
<ItemsChatList chatData={chatData}/>
<div className="col-12 md:col-4">
<SendMsgElement
onCallback= {setChatData}
data= {chatData}
/>
</div>
</div>
);
}
App.tsx
### ./src/App.tsx
import React from 'react';
import logo from './logo.svg';
import './App.css';
import AlignItemsList from './components/bubbleWindow';
const App = () => {
return (
<div className="App">
<AlignItemsList/>
</div>
);
}
export default App;