DS-Volare / front-end

0 stars 0 forks source link

[Feat] 챗봇 컴포넌트 개발 #22

Open yellow-jam opened 1 month ago

yellow-jam commented 1 month ago

⚙️ 기능 설명

변환 페이지의 챗봇 컴포넌트를 만듭니다.

✅ To-do

디자인

📑 참고 자료

Framer Motion

챗봇 아이콘(토글 버튼), 챗봇 drawer 열고 닫는 모션을 구현했습니다.

otcroz commented 1 month ago

react-typing-animation은 최근 업데이트가 2년 전이고, react-type-animation은 지난 주에 업데이트 했네요.

  1. react-typing-animation 라이브러리와 현재 우리 프로젝트와의 버전 문제 때문에 오류가 발생하지 않는 이상 바꾸지 않아도 된다고 생각함.
  2. 장기적으로 보면 최신에 업데이트한 라이브러리를 사용하는 것이 이후의 버전 문제로 인한 유지보수 비용을 줄일 수 있다고 생각함.

제 생각은 이렇습니다.. 라이브러리를 변경하고 구현하는 데 많은 시간이 걸리지 않으면 변경해도 괜찮을 것 같아요.

yellow-jam commented 1 month ago

챗봇 컴포넌트 구현

조금 늦었지만 챗봇 기능을 이루는 주요 컴포넌트를 설명하겠습니다. 백엔드와 연결하지 않은 상태입니다.

채팅 구현

전체 동작: MessageForm 컴포넌트를 통해 메시지를 전송하면 ChatbotBox의 handleSendMessage에서 메시지 리스트(messages)를 업데이트합니다. (챗봇메시지의 id를 Date.now()로 생성하여 사용 중) 메시지 리스트가 MessageList 컴포넌트를 통해 렌더링되고, 각각의 Item은 ChatMessage 컴포넌트로 선언되어 있습니다.

스크롤바 조정

채팅을 보내고 나서 스크롤바를 최하단으로 내리는 동작을 추가하는 과정에서 MessageList 컴포넌트를 forwardRef를 사용하여 선언했습니다.

forwardRef

함수형 컴포넌트는 인스턴스가 없어서 ref 속성을 사용할 수 없다. 커스텀 컴포넌트에서 ref를 통한 직접 제어가 필요할 때, React.forwardRef를 사용하면 부모 컴포넌트로부터 하위 컴포넌트로 ref를 전달할 수 있다. React Component를 forwardRef() 함수로 감싸주면, 컴포넌트 함수는 2번째 매개변수를 갖게 되는데 이를 통해 ref prop을 넘길 수 있다. forwardRef 참고자료

타이핑 애니메이션

이것 때문에 코드가 복잡한 감이 있습니다... 커밋을 분리하지 않았더니 단계별 구현과정을 보기가 어렵네요. 반성합니다...

참고 자료

yellow-jam commented 1 month ago

챗봇 드로어 레이아웃 애니메이션

Framer Motion을 사용한 애니메이션 동작에 대해 설명합니다.

AnimatePresence

Framer Motion 공식 문서

조건부 렌더링을 적용하는 것만으로는 컴포넌트에 퇴장 애니메이션을 주기 어렵습니다. (unvisible 상태가 되면 바로 DOM에서 unmount되므로 퇴장 애니메이션이 보여지는 동안 delay될 필요가 있음) 이때 Framer Motion에서 지원하는 AnimatePresence를 통해 퇴장 애니메이션(exit animation)을 구현할 수 있습니다. element가 나타나거나 사라지는 상태를 감지(DOM을 비교)하여 애니메이션 효과를 발생시키는 wrapper 컴포넌트입니다.

사용법 (AnimatePresence의 규칙)

  1. visible 상태여야 한다.
  2. Animation 내부에 조건문이 있어야 한다.

기본적인 코드는 아래와 같습니다. 컴포넌트가 보여질 때 initialanimate 애니메이션이 실행되며, 퇴장할 때 animateexit 애니메이션이 실행됩니다.

import { motion, AnimatePresence } from "framer-motion";

<AnimatePresence>
  {isVisible && (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
    />
  )}
</AnimatePresence>

참고 자료

yellow-jam commented 1 month ago

플로팅 버튼 애니메이션

Framer Motion을 사용한 애니메이션 동작에 대해 설명합니다.

variants로 애니메이션 함수화, custom으로 인수 전달

framer motion 컴포넌트의 기본형은 다음과 같습니다. (태그 안에 속성값을 직접 지정하는 방법)

return ( <ChatbotButton initial="init" animate="end" variants={buttonVariants} custom={isDrawerOpen} transition={{ type: 'tween' }} whileTap={{ scale: 0.9 }} onClick={() => { setIsDrawerOpen(!isDrawerOpen); }} /> )

### 축약된 형태
토글처럼 단순한 동작을 하는 경우 이런 식으로 축약해서 쓸 수도 있군요.
```tsx
// 예제
import { motion } from "framer-motion"

const variants = {
  open: { opacity: 1, x: 0 },
  closed: { opacity: 0, x: "-100%" },
}

export const MyComponent = () => {
  const [isOpen, setIsOpen] = useState(false)

  return (
    <motion.nav
      animate={isOpen ? "open" : "closed"}
      variants={variants}
    >
      <Toggle onClick={() => setIsOpen(isOpen => !isOpen)} />
      <Items />
    </motion.nav>
  )
}

제 코드를 리팩토링했습니다.

// 컴포넌트 내부의 코드
const buttonVariants = {
  open: { opacity: 1, x: -250, zIndex: 1 },
  closed: { opacity: 1, x: 0, zIndex: 1 },
};
return (
    <ChatbotButton
      animate={isDrawerOpen ? 'open' : 'closed'}
      variants={buttonVariants}
      transition={{ type: 'tween' }}
     />
  )

참고 자료

yellow-jam commented 1 month ago

타이핑 애니메이션 라이브러리 교체

변경 전

import Typing from 'react-typing-animation';

...

 <Typing
    startDelay={30}
    speed={50}
    onFinishedTyping={() => id && onEndTyping(id)}
>

변경 후

import { TypeAnimation } from 'react-type-animation';

...

<TypeAnimation
    sequence={[
      text,
      () => {
        id && onEndTyping(id);
      },
    ]}
    wrapper="span"
    speed={50}
    repeat={1}
    cursor={false}
/>

참고 자료