πŸ“² μ˜¨λ³΄λ”© νŽ˜μ΄μ§€ κ΅¬ν˜„ μŠ€ν† λ¦¬β€¦..

μ •ν˜œμΈΒ·2024λ…„ 12μ›” 3일
0

저희 ν”„λ‘œμ νŠΈ νŠΉμ„±μƒ 처음 μ ‘μ†ν•˜λŠ” μ‚¬μš©μžκ°€ μ‚¬μš©λ²•μ„ μ΅νžˆκΈ°μ— 어렀움이 μžˆμ„ 것이라 νŒλ‹¨ν–ˆκ³ , μ˜¨λ³΄λ”© νŽ˜μ΄μ§€κ°€ ν•„μˆ˜μ μœΌλ‘œ λ“€μ–΄κ°€μ•Όκ² λ‹€κ³  μƒκ°ν•˜κ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€....... ν•˜μ§€λ§Œ μ‹œκ°„μ΄ λ„ˆλ¬΄ μ—†μ—ˆκΈ° λ•Œλ¬Έμ—..... κ°€μž₯ κ°„λ‹¨ν•˜κ²Œ (ν•˜μ§€λ§Œ 라이브러리λ₯Ό μ‚¬μš©ν•˜μ§„ μ•Šκ³ .....) κ΅¬ν˜„ν•˜κΈ° μœ„ν•΄ κ³ λ―Όν–ˆκ³ , λΉ λ₯΄κ²Œ κ΅¬ν˜„ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

이번 ν”„λ‘œμ νŠΈμ—μ„œλŠ” 둜컬 μŠ€ν† λ¦¬μ§€(Local Storage)λ₯Ό ν™œμš©ν•΄ ν•œ 번 μ˜¨λ³΄λ”©μ„ μ™„λ£Œν•œ μ‚¬μš©μžμ—κ²ŒλŠ” λ‹€μ‹œ μ˜¨λ³΄λ”©μ„ 보여주지 μ•ŠλŠ” κΈ°λŠ₯κ³Ό, κ°€μž₯ κ°„λ‹¨ν•˜κ³  λΉ λ₯΄κ²Œ κ΅¬ν˜„ν•˜λ©΄μ„œλ„ λ°˜μ‘ν˜•μ„ κ³ λ €ν•œ UI둜 κ΅¬ν˜„ν•˜λŠ” 것을 μ€‘μš”ν•˜κ²Œ μƒκ°ν–ˆμŠ΅λ‹ˆλ‹€.

κ·Έλž˜μ„œ 이번 ν¬μŠ€νŒ…μ—μ„œλŠ” μ œκ°€ μ˜¨λ³΄λ”© νŽ˜μ΄μ§€λ₯Ό κ΅¬μ„±ν•˜λ©΄μ„œ κ²½ν—˜ν–ˆλ˜ λ‚΄μš©μ„ μž‘μ„±ν•΄λ³΄λ €κ³  ν•©λ‹ˆλ‹€!!


🎯 μ£Όμš” κ³ λ―Ό (?)

μ£Όμš”ν•˜κ²Œ κ΅¬ν˜„ν•΄μ•Ό ν–ˆλ˜, 생각해야 ν–ˆλ˜ μš”μ†ŒλŠ” μ•„λž˜ 4가지 μ •λ„μ˜€μŠ΅λ‹ˆλ‹€.

  1. μ˜¨λ³΄λ”© μŠ¬λΌμ΄λ“œ κ΅¬ν˜„
    • μ‚¬μš©μžκ°€ 이전/λ‹€μŒ λ²„νŠΌμ„ λ„˜κΈ°λ©΄ νŠœν† λ¦¬μ–Όμ΄ μ§„ν–‰λ˜λ„λ‘ 섀계
  2. ν•œ 번만 μ‹€ν–‰λ˜λ„λ‘ μ œμ–΄
    • 둜컬 μŠ€ν† λ¦¬μ§€λ₯Ό ν™œμš©ν•΄ 이미 μ˜¨λ³΄λ”©μ„ μ™„λ£Œν•œ μ‚¬μš©μžλŠ” λ‹€μ‹œ 보지 μ•Šλ„λ‘ μ„€μ •
  3. λ°˜μ‘ν˜• UI
    • λͺ¨λ“  λͺ¨λ°”일 ν™˜κ²½μ—μ„œ μžμ—°μŠ€λŸ½κ²Œ λ™μž‘ν•˜λ„λ‘ λ°˜μ‘ν˜• λ””μžμΈ κ΅¬ν˜„
  4. κ°„λ‹¨ν•˜κ³  λΉ λ₯Έ κ΅¬ν˜„
    • κ°€μž₯ κ°„λ‹¨ν•œ λ°©λ²•μœΌλ‘œ λΉ λ₯΄κ²Œ κ΅¬ν˜„

πŸ“‚ μ£Όμš” 파일 ꡬ쑰

저희 ν”„λ‘œμ νŠΈμ—λŠ” μ˜¨λ³΄λ”© νŽ˜μ΄μ§€λ³΄λ‹€ 훨씬 μ€‘μš”ν•œ κΈ°λŠ₯ κ°œμ„ μ΄λ‚˜ UI κ°œμ„ μ΄ λ§Žμ•˜κΈ° λ•Œλ¬Έμ—, μ˜¨λ³΄λ”© νŽ˜μ΄μ§€λŠ” μ΅œλŒ€ν•œ κ°„λ‹¨ν•˜κ³  λΉ λ₯΄κ²Œ κ΅¬ν˜„ν•΄μ•Ό ν–ˆμŠ΅λ‹ˆλ‹€β€¦β€¦..

κ·Έλž˜μ„œ μ΅œλŒ€ν•œ 이미 저희가 κ΅¬ν˜„ν•΄λ‘” μ‚¬μ΄νŠΈμ˜ 캑처 이미지λ₯Ό μ‚¬μš©ν•˜λ €κ³  ν–ˆκ³ , κ·Έλž˜μ„œ 이런 μ‹μœΌλ‘œ 데이터λ₯Ό λ”°λ‘œ μ €μž₯ν•  수 μžˆλ„λ‘ 파일 ꡬ쑰λ₯Ό μ„€κ³„ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

src/
β”œβ”€β”€ components/
β”‚   └── Onboarding.tsx     // μ˜¨λ³΄λ”© UIλ₯Ό 담은 μ»΄ν¬λ„ŒνŠΈ
β”œβ”€β”€ lib/
β”‚   └── data/
β”‚       └── onboardingData.ts // μ˜¨λ³΄λ”© μŠ¬λΌμ΄λ“œ 데이터
└── App.tsx                // μ˜¨λ³΄λ”© μ»΄ν¬λ„ŒνŠΈλ₯Ό ν˜ΈμΆœν•˜λŠ” 루트 μ»΄ν¬λ„ŒνŠΈ

πŸ“œ κ΅¬ν˜„ λ‚΄μš©

1. Onboarding.tsx μ»΄ν¬λ„ŒνŠΈ ꡬ성

πŸ” μŠ¬λΌμ΄λ“œ 관리 및 μƒνƒœ

λ¨Όμ € useStateλ₯Ό μ‚¬μš©ν•΄ ν˜„μž¬ μŠ¬λΌμ΄λ“œ(currentSlide)λ₯Ό κ΄€λ¦¬ν•˜κ³ , 'λ‹€μŒ' λ²„νŠΌμ„ λˆ„λ₯΄λ©΄ μŠ¬λΌμ΄λ“œκ°€ μ΄λ™ν•˜λ„λ‘ κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

그리고 λ§ˆμ§€λ§‰ μŠ¬λΌμ΄λ“œμ—μ„œλŠ” μžλ™μœΌλ‘œ μ˜¨λ³΄λ”©μ΄ μ’…λ£Œλ©λ‹ˆλ‹€.

const [currentSlide, setCurrentSlide] = useState(0);

const handleNext = () => {
  if (currentSlide < onboardingData.length - 1) {
    setCurrentSlide(currentSlide + 1);
  } else {
    onComplete(); // μ˜¨λ³΄λ”© μ’…λ£Œ
  }
};

πŸ” μ’…λ£Œ λ²„νŠΌ 및 둜컬 μŠ€ν† λ¦¬μ§€

onComplete ν•¨μˆ˜λŠ” μ˜¨λ³΄λ”©μ„ μ’…λ£Œν•˜κ³  둜컬 μŠ€ν† λ¦¬μ§€μ— onboardingCompleted ν”Œλž˜κ·Έλ₯Ό μ €μž₯ν•©λ‹ˆλ‹€.

이λ₯Ό 톡해 μ‚¬μš©μžκ°€ λ‹€μ‹œ λ°©λ¬Έν–ˆμ„ λ•Œ μ˜¨λ³΄λ”©μ„ μƒλž΅ν•˜λ„λ‘ ν–ˆμŠ΅λ‹ˆλ‹€.

(사싀 μ‹€μ œ μ½”λ“œμ—μ„œλŠ” localStorage에 μ €μž₯ν•˜λŠ” util ν•¨μˆ˜λ₯Ό λ§Œλ“€μ–΄λ‘μ—ˆκΈ° λ•Œλ¬Έμ— 쑰금 λ‹€λ₯΄μ§€λ§Œ, 이런 μ‹μœΌλ‘œ localStorage에 μ €μž₯ν–ˆλ‹€ μ •λ„λ§Œ λ΄μ£Όμ‹œλ©΄ 될 것 κ°™μŠ΅λ‹ˆλ‹€!!)

const onComplete = () => {
  localStorage.setItem('onboardingCompleted', 'true');
  // 이후 메인 ν™”λ©΄μœΌλ‘œ μ΄λ™ν•˜κ±°λ‚˜ μ˜¨λ³΄λ”© μ’…λ£Œ 처리
};

πŸ” Tailwind CSS둜 λ°˜μ‘ν˜• UI κ΅¬ν˜„

μ˜¨λ³΄λ”© νŽ˜μ΄μ§€λŠ” 이미지와 μ„€λͺ… ν…μŠ€νŠΈλ‘œ κ΅¬μ„±λ˜μ–΄ 있고, 이λ₯Ό Tailwind CSS둜 λ°°μΉ˜ν–ˆμŠ΅λ‹ˆλ‹€.

(μœ„μ—μ„œ μ–ΈκΈ‰ν–ˆλ“― μ‹œκ°„μ΄ μ—†μ—ˆκΈ°μ— μ΅œλŒ€ν•œ κ°„λ‹¨ν•˜κ²Œ κ΅¬ν˜„ν•˜κ³ μž ν–ˆκ³ , 이미지λ₯Ό ν™œμš©ν•˜λŠ” 방식이 κ°€μž₯ κ°„λ‹¨ν•˜λ‹€κ³  νŒλ‹¨ν–ˆμŠ΅λ‹ˆλ‹€.)

그리고 μ΄λ―Έμ§€μ—λŠ” absolute와 object-contain을 μ‚¬μš©ν•΄ 크기와 μœ„μΉ˜λ₯Ό λ™μ μœΌλ‘œ μ‘°μ •ν•΄μ£Όμ—ˆμŠ΅λ‹ˆλ‹€.

(이건 μ•„λ§ˆ μ•„λž˜μ— μ™„μ„±λœ κ²°κ³Ό 화면을 λ³΄μ‹œλ©΄ μ΄ν•΄λ˜μ‹€ 것 κ°™μŠ΅λ‹ˆλ‹€!)

<img
  src={`/assets/images/onboarding/slide${onboardingData[currentSlide].id}.png`}
  alt={`Slide ${currentSlide + 1}`}
  className="absolute top-10 mx-auto h-[75vh] object-contain"
/>

2. App.tsxμ—μ„œ μ˜¨λ³΄λ”© 호좜

πŸ” 둜컬 μŠ€ν† λ¦¬μ§€ 확인

App.tsxμ—μ„œλŠ” 둜컬 μŠ€ν† λ¦¬μ§€μ˜ onboardingCompleted 값을 ν™•μΈν•˜μ—¬ μ˜¨λ³΄λ”© μ—¬λΆ€λ₯Ό κ²°μ •ν–ˆμŠ΅λ‹ˆλ‹€.

import React, { useState, useEffect } from 'react';
import { Onboarding } from './components/Onboarding';

const App = () => {
  const [showOnboarding, setShowOnboarding] = useState(false);

  useEffect(() => {
    const completed = localStorage.getItem('onboardingCompleted');
    if (!completed) {
      setShowOnboarding(true);
    }
  }, []);

  return (
    <div>
      {showOnboarding ? (
        <Onboarding onComplete={() => setShowOnboarding(false)} />
      ) : (
        <div>메인 μ½˜ν…μΈ  ν‘œμ‹œ</div>
      )}
    </div>
  );
};

export default App;

3. 데이터 관리: onboardingData.ts

μ˜¨λ³΄λ”© μŠ¬λΌμ΄λ“œμ— ν‘œμ‹œν•  λ‚΄μš©μ„ λ³„λ„μ˜ 데이터 파일둜 κ΄€λ¦¬ν–ˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό 톡해 μ»΄ν¬λ„ŒνŠΈμ™€ 데이터λ₯Ό λΆ„λ¦¬ν•˜κ³  μœ μ§€λ³΄μˆ˜μ„±μ„ λ†’μ˜€μŠ΅λ‹ˆλ‹€.

export const onboardingData = [
  {
    id: 1,
    content: '저희 β€˜μ„ λ”°λΌ 길따라’ μ„œλΉ„μŠ€κ°€ μ²˜μŒμ΄μ‹œλΌκ³ μš”? \nμ œκ°€ μ‚¬μš©λ²•μ„ μ•Œλ €λ“œλ¦΄κ²Œμš”!',
  },
  {
    id: 2,
    content: '채널을 μƒμ„±ν•˜κ³  μ‹ΆμœΌμ‹œλ‹€λ©΄, \nνšŒμ›κ°€μž… ν›„ λ‘œκ·ΈμΈμ„ ν•΄μ£Όμ„Έμš”!',
  },
  {
    id: 3,
    content:
      '둜그인 ν›„ 본인이 생성해둔 채널듀을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€! \nκ³΅μœ ν•˜κΈ°λ₯Ό 눌러 게슀트 λ³„λ‘œ 링크λ₯Ό 확인할 μˆ˜λ„ μžˆμ–΄μš”!',
  },
  ...
];

🎨 UI 및 μ‚¬μš©μž κ²½ν—˜

  • μŠ¬λΌμ΄λ“œ λ„€λΉ„κ²Œμ΄μ…˜
    ν•˜λ‹¨μ— 점(dot) ν˜•νƒœμ˜ μŠ¬λΌμ΄λ“œ λ„€λΉ„κ²Œμ΄μ…˜μ„ μΆ”κ°€ν•΄ ν˜„μž¬ μŠ¬λΌμ΄λ“œ μœ„μΉ˜λ₯Ό μ‹œκ°μ μœΌλ‘œ ν‘œμ‹œν–ˆμŠ΅λ‹ˆλ‹€.
<div className="flex space-x-2">
  {onboardingData.map((slide, index) => (
    <div
      key={slide.id}
      className={`h-1 w-1 rounded-full ${
        index === currentSlide ? 'bg-blueGray-200' : 'bg-gray-300'
      }`}
    />
  ))}
</div>
  • μ’…λ£Œ λ²„νŠΌ
    우츑 상단에 'νŠœν† λ¦¬μ–Ό 끝내기' λ²„νŠΌμ„ λ°°μΉ˜ν•˜μ—¬ μ‚¬μš©μžκ°€ μ–Έμ œλ“  μ˜¨λ³΄λ”©μ„ λΉ λ₯΄κ²Œ μ’…λ£Œν•  수 μžˆλ„λ‘ ν–ˆμŠ΅λ‹ˆλ‹€.

πŸ”§ λͺ¨λ°”일 슀크둀 문제 ν•΄κ²°: height μ„€μ •

그런데 배포 ν›„ μ˜¨λ³΄λ”© νŽ˜μ΄μ§€λ₯Ό λͺ¨λ°”일 ν™˜κ²½μ—μ„œ μ‹€ν–‰ν•˜λ©΄μ„œ μ˜ˆμƒμΉ˜ λͺ»ν•œ λ¬Έμ œκ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.

νŠΉμ • κΈ°κΈ°μ—μ„œ height 값이 window.innerHeight와 μΌμΉ˜ν•˜μ§€ μ•Šμ•„ λΆˆν•„μš”ν•œ 슀크둀이 λ°œμƒν•˜λŠ” ν˜„μƒμ΄μ—ˆμŠ΅λ‹ˆλ‹€.

μ΄λŠ” λͺ¨λ°”일 λΈŒλΌμš°μ €κ°€ 뷰포트λ₯Ό κ³„μ‚°ν•˜λŠ” 방식이 window.innerHeight와 μΌμΉ˜ν•˜μ§€ μ•Šμ„ λ•Œ λ°œμƒν•˜λŠ” 문제둜, 이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ CSS와 JavaScriptλ₯Ό κ²°ν•©ν•œ 방법을 μ μš©ν–ˆμŠ΅λ‹ˆλ‹€.

πŸ“Œ λ°œμƒν•œ 문제

λͺ¨λ°”일 λΈŒλΌμš°μ €λŠ” μ£Όμ†Œ ν‘œμ‹œμ€„κ³Ό 같은 UI μš”μ†Œλ‘œ 인해 뷰포트의 높이가 λ™μ μœΌλ‘œ 변경될 수 μžˆμŠ΅λ‹ˆλ‹€.

특히, vh λ‹¨μœ„λŠ” 뷰포트 λ†’μ΄μ˜ 1%λ₯Ό μ˜λ―Έν•˜μ§€λ§Œ, λͺ¨λ°”μΌμ—μ„œ λΈŒλΌμš°μ € UI에 μ˜ν•΄ 잘λͺ» 계산될 κ°€λŠ₯성이 μžˆμŠ΅λ‹ˆλ‹€.

κ·Έ κ²°κ³Ό μ˜λ„ν•˜μ§€ μ•Šμ€ 슀크둀이 λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.

πŸ“Œ ν•΄κ²° 방법

useEffectλ₯Ό μ‚¬μš©ν•΄ λ™μ μœΌλ‘œ 높이λ₯Ό κ³„μ‚°ν•˜κ³  이λ₯Ό CSS λ³€μˆ˜λ‘œ μ„€μ •ν•˜μ—¬ λͺ¨λ“  ν™”λ©΄μ—μ„œ λ™μΌν•œ λ™μž‘μ„ 보μž₯ν•˜λ„λ‘ κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

πŸ’» μ½”λ“œ 적용

useEffect(() => {
  const setVh = () => {
    // μ‹€μ œ 높이λ₯Ό 1vh λ‹¨μœ„λ‘œ λ³€ν™˜
    const vh = window.innerHeight * 0.01;
    document.documentElement.style.setProperty('--vh', `${vh}px`);
  };
  setVh();

  // μ°½ 크기가 변경될 λ•Œλ§ˆλ‹€ 높이λ₯Ό λ‹€μ‹œ 계산
  window.addEventListener('resize', setVh);
  return () => {
    window.removeEventListener('resize', setVh);
  };
}, []);

이 μ½”λ“œλ₯Ό 톡해 --vhλΌλŠ” CSS λ³€μˆ˜λ₯Ό μƒμ„±ν•˜κ³ , 이λ₯Ό μŠ€νƒ€μΌμ—μ„œ μ‚¬μš©ν•˜μ—¬ μ •ν™•ν•œ 높이λ₯Ό 보μž₯ν–ˆμŠ΅λ‹ˆλ‹€.

πŸ’‘ 적용된 CSS

html, body {
  height: calc(var(--vh, 1vh) * 100); /* --vhλ₯Ό κΈ°μ€€μœΌλ‘œ 전체 높이 계산 */
  overflow: hidden; /* 슀크둀 제거 */
}

πŸ’» μ»΄ν¬λ„ŒνŠΈμ—μ„œ height μ„€μ •

Onboarding.tsx μ»΄ν¬λ„ŒνŠΈμ˜ μ΅œμƒλ‹¨ μ»¨ν…Œμ΄λ„ˆμ— style 속성을 μ‚¬μš©ν•˜μ—¬ 높이λ₯Ό μ„€μ •ν–ˆμŠ΅λ‹ˆλ‹€.

<div
  className="relative flex w-full flex-col items-center justify-center overflow-hidden bg-gray-100"
  style={{ height: `calc(var(--vh, 1vh) * 100)` }}
>
  {/* μ˜¨λ³΄λ”© μ½˜ν…μΈ  */}
</div>

πŸ›  μ΅œμ’… κ²°κ³Ό

이 방법을 ν†΅ν•΄μ„œ μ•„λž˜μ™€ 같이 λͺ¨λ°”일 ν™˜κ²½μ—μ„œλ„ 슀크둀 없이 μ˜¨λ³΄λ”© νŽ˜μ΄μ§€κ°€ 슀크둀 없이 ν‘œμ‹œλ  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. (λ°°ν„°λ¦¬λŠ”β€¦β€¦λ¬΄μ‹œν•΄μ£Όμ„Έμš”β€¦β€¦β€¦β€¦β€¦..ㅋㅋㅋㅋㅋㅋㅋ큐ㅠㅠㅠㅠㅠ)

이번 κ΅¬ν˜„μ—μ„œ λ°˜μ‘ν˜• λ””μžμΈ, 둜컬 μŠ€ν† λ¦¬μ§€ ν™œμš©, 데이터와 UI의 뢄리λ₯Ό 톡해 μœ μ§€λ³΄μˆ˜μ„±κ³Ό μ‚¬μš©μž κ²½ν—˜μ„ λͺ¨λ‘ κ³ λ €ν•œ 섀계λ₯Ό ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.


[Onboarding.tsx 파일 전체 μ½”λ“œ]

import { useEffect, useState } from 'react';
import { onboardingData } from '@/lib/data/onboardingData.ts';
import { MdClear } from 'react-icons/md';

interface IOnboardingProps {
  onComplete: () => void;
}

export const Onboarding = ({ onComplete }: IOnboardingProps) => {
  const [currentSlide, setCurrentSlide] = useState(0);

  useEffect(() => {
    const setVh = () => {
      const vh = window.innerHeight * 0.01;
      document.documentElement.style.setProperty('--vh', `${vh}px`);
    };
    setVh();

    window.addEventListener('resize', setVh);
    return () => {
      window.removeEventListener('resize', setVh);
    };
  }, []);

  const handleNext = () => {
    if (currentSlide < onboardingData.length - 1) {
      setCurrentSlide(currentSlide + 1);
    } else {
      onComplete();
    }
  };

  const handlePrev = () => {
    if (currentSlide > 0) {
      setCurrentSlide(currentSlide - 1);
    }
  };

  return (
    <div
      className="relative flex w-full flex-col items-center justify-center overflow-hidden bg-gray-100"
      style={{ height: window.innerHeight }}
    >
      <div className="absolute right-3 top-3 z-[6000] flex items-center gap-2">
        <div className="text-sm text-gray-200">νŠœν† λ¦¬μ–Ό 끝내기</div>
        <button
          onClick={onComplete}
          className="flex h-[30px] w-[30px] items-center justify-center rounded-full bg-gray-200"
        >
          <MdClear size={18} color="grayscale-850" />
        </button>
      </div>

      <div className="relative flex h-screen w-full items-center justify-center">
        <img
          src={`/assets/images/onboarding/slide${onboardingData[currentSlide].id}.png`}
          alt={`Slide ${currentSlide + 1}`}
          className="absolute top-10 mx-auto h-[75vh] object-contain"
        />
        <div className="absolute inset-0 bg-black bg-opacity-30" />
      </div>

      <div className="absolute bottom-2 flex w-[95%] flex-col">
        <div className="flex w-[100%] items-center justify-center text-white">
          <img
            src="/assets/images/onboarding/character.png"
            alt="캐릭터"
            className="max-h-16 w-[15%] object-contain"
          />
          <div
            className="bg-blueGray-200 m-2 flex h-20 w-[85%] items-center justify-center whitespace-pre rounded-lg bg-opacity-[0.5] text-center text-sm leading-relaxed"
            style={{ padding: '1rem 1rem' }}
          >
            {onboardingData[currentSlide].content}
          </div>
        </div>

        <div className="flex items-center justify-between p-4">
          <button
            onClick={handlePrev}
            disabled={currentSlide === 0}
            className="rounded bg-gray-300 px-4 py-2 text-gray-700 disabled:opacity-50"
          >
            이전
          </button>
          <div className="flex space-x-2">
            {onboardingData.map((slide, index) => (
              <div
                key={slide.id}
                className={`h-1 w-1 rounded-full ${
                  index === currentSlide ? 'bg-blueGray-200' : 'bg-gray-300'
                }`}
              />
            ))}
          </div>
          <button onClick={handleNext} className="bg-blueGray-200 rounded px-4 py-2 text-white">
            {currentSlide === onboardingData.length - 1 ? 'μ‹œμž‘' : 'λ‹€μŒ'}
          </button>
        </div>
      </div>
    </div>
  );
};

0개의 λŒ“κΈ€