채팅에 쓰인 가사는 라이즈 - 원키스
만들고 보니까 채팅바를 왜 하단에 고정 안 시켜두었는지 모르겠지만. 추후 수정하는 걸로...
저 채팅 ui 는 contact 페이지다.
예전 팀 과제 할 때 팀원 한 분이 소켓통신으로 채팅 기능을 구현하셨는데 그때는 개발이라는 게 정말 정말 UI 좀 깔짝 거리는 게 다였어서 그냥 대단하다. 하고 박수만 쳤던 게 끝이었다. 지금도 관심은 가는 데 스트레스 받고 힘들 것 같아서 그것 까진 무리무리. 대신 언젠가는 채팅 형식의 UI를 구성해보는 것도 나쁘진 않을 것 같다는 생각을 했었다. 쓸 데도 없고 그땐 실력도 뭣도 없어서 그냥 속으로만 생각했지만. 지금도 지피티 도움을 받아서 고민 시간을 반의 반으로 줄이면서 하고는 있지만, 예전 꼬꼬마 시절보단 훨씬 쉽고 빠른 시간 내에 이해할 수 있다는 것에 의의를 두고 있다. 성장한 거지 뭐 별 거 있어...
하여튼 CURD 기능은 없고, 일단은 UI만 만들어 놨다. 그러니까 옷만 입혀놨다는 거다. 이 옷에 어울리는 액세서리와 화장품을 추가하려면 좀 걸릴 듯. 기획 설계할 때 아 이왕 하는 거 백단도 구성해보자 -> 그러면 8월 안에 완성 못 할 것 같음 -> 백단 버리고 firebase로 가자 -> 사소한 거 하나하나 디자인 하고 싶어 -> 그러면 8월 안에 못 끝냄 -> 더 덜어내자 < 지금 8월 6일 기준으로 여기까지 왔다. 기능 붙이는 걸 나중에 하고 다른 페이지 디자인부터 하는 게 좋을 것 같음. 모든 페이지에 다 firestore를 쓸 거기 때문에 일단은.... 이것도 언제 바뀔 지 모른다.
이 프로젝트에서 사용중인 폰트들은 나중에 따로 정리해서 올리겠음.
const ContactPage = () => {
return (
<>
<Header/>
<ChatMsg/>
</>
)
}
ContactPage.tsx
ChatMsg 다음과 같이 나뉘어져 있다.
- <ChatTitle> 채팅방 입장 초기 문구
- <SpeechBubble> 말풍선
-<ChatInput> 채팅창
원래 ChatInput을 따로 분리하지 않고 chatMsg에 놔뒀다가 오늘 글 쓰면서 급하게 분리. 설계 대충하면 이렇게 된다...
const ChatTitle = () => {
const date = new Date();
const formatter = new Intl.DateTimeFormat('ko-KR', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
const today = formatter.format(date);
return (
<div className="flex flex-col justify-center items-center mt-10">
<motion.div
initial={{y: 50, opacity: 0}} // 초기 상태: 화면 아래에서 위로 이동 및 투명
animate={{y: show ? 0 : 50, opacity: show ? 1 : 0}} // 애니메이션 상태: 위로 이동 및 보임
transition={{duration: 1, ease: "easeOut"}} // 애니메이션 지속 시간과 easing
exit={{y: 50, opacity: 0}} // exit 애니메이션: 다시 아래로 이동 및 투명
>
<div className="w-72 h-14 shadow-lg rounded-2xl bg-gray-50 flex items-center justify-center">
<span className=" text-base font-normal" style={{fontFamily: 'CoreDream'}}>📆 {today}</span>
</div>
</motion.div>
애니메이션 라이브러리로
import { motion} from "framer-motion";
framer-motion 사용하고 있는데, 라이브러리를 최대한 걷어내자 주의지만 애니메이션 css 를 잘 모르기도 하고 지금 하나하나 필요한 거 적용해보면서 커스텀 해보고 익히는 중이라 좀 더 효율적으로 하고자 사용 중이다. 꽤 기능도 많고 편한 것 같음. 한국어로 설명된 게 없어서 간단한 기능 예시로는 지티피로 뽑아내고 있다.
css 말풍선 간단하게 만들어주는 사이트가 있어서 기본적인 건 여기서 가지고 왔다. 세상 편함...
https://projects.verou.me/bubbly/
Bubbly — CSS speech bubbles made easy
projects.verou.me
const SpeechBubble = ({name, phone, email, content} : textInfo) => {
const [show, setShow] = useState(false);
useEffect(() => {
setShow(true);
}, []);
return (
<motion.div
initial={{y: 50, opacity: 0}} // 초기 상태: 화면 아래에서 위로 이동 및 투명
animate={{y: show ? 0 : 50, opacity: show ? 1 : 0}} // 애니메이션 상태: 위로 이동 및 보임
transition={{duration: 1, ease: "easeOut"}} // 애니메이션 지속 시간과 easing
exit={{y: 50, opacity: 0}} // exit 애니메이션: 다시 아래로 이동 및 투명
>
<div css={bubbleCss}>
<div className="chatBubble shadow-lg" style={{fontFamily: 'CoreDream'}}>{content}</div>
</div>
</motion.div>
)
}
SppechBubble.tsx
css는 따로 분리 하지 않고 여기 넣어주었다.
css 수정하면서 selection도 좀 바꾸었다. 원래는 저 말풍선 배경색이 selection 되는 건데, 저 말풍선에 그대로 사용하면 색이 겹치기 때문에 진한 노란색으로 바꿔보았다. 글귀 내용은 <몰락의 에티카> 신형철. 제일 좋아하는 평론가....책 또 언제 내주시나요. 그의 모든 글 중 역시 압도적으로 좋아하는 서문. 아마 나말고도 좋아하는 사람들 많은 걸로 알고 있다.
.chatBubble {
width: 400px;
min-height: 50px;
height: auto;
최소 높이를 설정하고 height를 auto로 해서 안에 있는 content 크기에 따라 높이가 자동적으로 조절 되게 했다. 패딩은 아래 정도로 줌.
padding: 20px;
const ChatInput = ({getBubbles}) => {
const [show, setShow] = useState(false);
const [content, setContent] = useState<string>('')
const addBubble = () => {
if(content.trim()) {
getBubbles( {name: "w뚜뚜", phone: "0101231234", email: "dududu@naver.com", content });
setContent('')
}
}
const onEnter = (e) => {
if(e.key === 'Enter') {
e.preventDefault();
addBubble();
}
}
const changeContent = (e) => {
setContent(e.target.value);
}
ChatInput.tsx
채팅창에서 enter를 누르거나 bubble 버튼을 클릭하면 ChatMsg에 있는 getBubbles 함수가 호출되면서 관련 내용을 가지고 가 출력한다.
원래 상위 -> 하위로 값을 넘기는 작업만 해봤는데 이번엔 거꾸로 작업하는 거라 초반에 좀 헤맸지만 .... 뭔가 더 효율적인 방법이 있는 것 같은데 나중에 강의 들으면서 보충해야겠다. 지금은 로직 자체가 복잡한 게 아니라서 이 정도면 될 듯 하다.
interface getChatData {
name : string;
phone : string;
email : string;
content : string;
}
const ChatMsg = () => {
const [bubbles, setBubbles] = useState<getChatData[]>([]);
const newBubbles = (newData: getChatData) => {
setBubbles([...bubbles, newData]);
}
return (
<>
<div className="w-full border-t-2 border-black">
<div css={msgCss}>
<ChatTitle/>
{bubbles.map((item, index) => (
<SpeechBubble key={index} name={item.name} phone={item.phone} email={item.email} content={item.content}/>
))}
</div>
<ChatInput getBubbles={newBubbles}/>
</div>
</>
)
}
ChatMsg.tsx
아무튼 컴포넌트를 쪼갠 덕분에 여기선 꽤나 간단해졌다. 지저분하게 나열됐던 객체 내용들을 interface로 하나로 묶어 넣어주고, setBubbles에 스프레드 연산자를 사용해서 기존 버블 내용을 복사한 뒤 받아온 객체를 추가해준다.
그러면 위와 같이 말풍선이 계속 생기면서 내가 추가한 버블 내용이 잘 나오는 걸 알 수 있음. 이름과 전화번호, 이메일까지 넣어서 만드려고 했는데 어떤 UI가 좋을지 안 떠오르기도 하고 내가 이걸 배포하게 되면 저 세 가지는 넘나 사적인 개인 정보인데 이걸 그대로 노출하는 건 아니지않나 싶기도 해서 앞으로 어떻게 활용할 지는 좀 생각해 봐야 할 것 같음.
'코딩 > 사이드 프로젝트' 카테고리의 다른 글
[블로그 만들기 #1] react 초기 로딩 화면 구현 (0) | 2024.08.04 |
---|