Project/Airbnb Clone

[Airbnb 클론코딩] 환경설정 및 Navbar UI(With. Youtube)

hu6r1s 2023. 6. 11. 18:44

유튜브를 보던 중에 Airbnb 클론코딩을 하는 것을 보았다.

그래서 Next.js를 찍먹해보려고 한다.

환경설정

해당 영상에서는 Next.js 13, React, Tailwind, Prisma, MongoDB를 사용한다.

먼저 Next 프로젝트 폴더를 만들어준다.

npx create-next-app --typescript

이렇게 하면 기본 셋팅을 물어보는 것이 있는데 초기 설정 값으로 설정하면 된다.

프로젝트 폴더가 만들어지면 npm run dev를 통해 실행해주면서 localhost:3000으로 들어가면 된다.

Navbar

먼저 완성된 디렉터리 구조는 이러하다.

📦app
 ┣ 📂components
 ┃ ┣ 📂navbar
 ┃ ┃ ┣ 📜Logo.tsx
 ┃ ┃ ┣ 📜MenuItem.tsx
 ┃ ┃ ┣ 📜Navbar.tsx
 ┃ ┃ ┣ 📜Search.tsx
 ┃ ┃ ┗ 📜UserMenu.tsx
 ┃ ┣ 📜Avatar.tsx
 ┃ ┗ 📜Container.tsx
 ┣ 📜favicon.ico
 ┣ 📜globals.css
 ┣ 📜layout.tsx
 ┗ 📜page.tsx

하나씩 알아보도록 하자.

layout.tsx

import { Nunito } from 'next/font/google'
import Navbar from './components/navbar/Navbar'
import './globals.css'

export const metadata = {
  title: 'Airbnb',
  description: 'Airbnb clone',
}

const font = Nunito({
  subsets: ["latin"]
})

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={font.className}>
        <Navbar />
        {children}
      </body>
    </html>
  )
}

폰트를 적용하는 것은 다른 포스트에서 확인하면 된다.

Navbar라는 컴포넌트를 만들었고 이를 넣어줬다.

Navbar Component

import Container from '../Container';
import Logo from './Logo';
import Search from './Search';
import UserMenu from './UserMenu';

const Navbar = () => {
  return (
    <div className='fixed w-full bg-white z-10 shadow-sm'>
      <div className='py-4 border-b-[1px]'>
        <Container>
          <div className="
            flex
            flex-row
            items-center
            justify-between
            gap-3
            md:gap-0
          ">
            <Logo />
            <Search />
            <UserMenu />
          </div>
        </Container>
      </div>
    </div>
  );
};

export default Navbar;

Navbar 컴포넌트에는 로고, 검색, 유저관련 메뉴가 있다. 이도 다른 컴포넌트를 가져오는 것이다.

위의 컴포넌트들을 감싸주는 컨테이너 컴포넌트가 있다.

'use client';

interface ContainerProps {
  children: React.ReactNode;
}

const Container: React.FC<ContainerProps> = ({ children }) => {
  return (
    <div className="
      max-w-[2520px]
      mx-auto
      xl:px-20
      md:px-10
      sm:px-2
      px-4
    ">
      {children}
    </div>
  );
};

export default Container;

컨테이너를 이렇게 만들면 컨테이너 안에 있는 Logo, Searh, UserMenu가 children에 들어간다.

Logo

다음은 로고가 들어가야 하는데 이도 Logo Component를 만들어준다.

'use client';

import Image from 'next/image';
import { useRouter } from 'next/navigation';

const Logo = () => {
  const router = useRouter()

  return (
    <Image
      alt="Logo"
      className="hidden md:block cursor-pointer"
      height="100"
      width="100"
      src="/images/logo.png"
    />
  );
};

export default Logo;

Next.js에는 Image Component가 있는데 이를 통해 간편하게 이미지를 가져올 수 있다.

Image 컴포넌트를 import해주고 tailwind를 사용해서 CSS를 적용해주고 src 속성을 통해 이미지 path를 넣어주면 된다.

이미지들은 깃허브를 통해 다운받거나 airbnb사이트에서 가져오자.

Search

Search 컴포넌트를 만들기 전에 설치해야 하는 것이 있다.

npm install react-icons

react-icons를 설치하여 필요한 아이콘들을 가져올 것이다.

'use client';

import { BiSearch } from 'react-icons/bi';

const Search = () => {
  return (
    <div className="
      border-[1px]
      w-full
      md:w-auto
      py-2
      rounded-full
      shadow-sm
      hover:shadow-md
      transition
      cursor-pointer
    ">
      <div className="
        flex
        flex-row
        items-center
        justify-between
      ">
        <div className="
          text-sm
          font-semibold
          px-6
        ">
          Anywhere
        </div>
        <div className="
            hidden
            sm:block
            text-sm
            font-semibold
            px-6
            border-x-[1px]
            flex-1
            text-center
          ">
          Any Week
        </div>
        <div className="
            text-sm
            pl-6
            pr-2
            text-gray-600
            flex
            flex-row
            items-center
            gap-3
          ">
          <div className="hidden sm:block">Add Guests</div>
          <div className="
            p-2
            bg-rose-500
            rounded-full
            text-white
          ">
            <BiSearch size={18} />
          </div>
        </div>
      </div>
    </div>
  );
};

export default Search;

다른 CSS는 얘기하기 않고 아이콘에 대해서만 말하면 react-icons를 통해 필요한 아이콘 이름을 import해준다.

그리고 아이콘을 사용할 위치에 해당 아이콘을 넣으면 된다. 아이콘은 airbnb의 퍼스널 컬러로 배경색을 넣어준다.

UserMenu

마지막으로 UserMenu는 계정 프로필과 목록을 나타내는 컴포넌트이다.

'use client';

import { useCallback, useState } from 'react';
import { AiOutlineMenu } from 'react-icons/ai'
import Avatar from '../Avatar';
import MenuItem from './MenuItem';

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

  const toggleOpen = useCallback(() => {
    setIsOpen((value) => !value)
  }, [])
  return (
    <div className="relative">
      <div className="flex flex-row items-center gap-3">
        <div
          onClick={() => { }}
          className="
            hidden
            md:block
            text-sm
            font-semibold
            py-3
            px-4
            rounded-full
            hover:bg-neutral-100
            transition
            cursor-pointer
          "
        >
          Airbnb your home
        </div>
        <div
          onClick={toggleOpen}
          className="
            p-4
            md:py-1
            md:px-2
            border-[1px]
            border-neutral-200
            flex
            flex-row
            items-center
            gap-3
            rounded-full
            cursor-pointer
            hover:shadow-md
            transition
          "
        >
          <AiOutlineMenu />
          <div className="hidden md:block">
            <Avatar />
          </div>
        </div>
      </div>

      {isOpen && (
        <div
          className="
            absolute
            rounded-xl
            shadow-md
            w-[40vw]
            md:w-3/4
            bg-white
            overflow-hidden
            right-0
            top-12
            text-sm
          "
        >
          <div className="flex flex-col cursor-pointer">
            <>
              <MenuItem
                onClick={() => { }}
                label="Login"
              />
            </>
          </div>
        </div>
      )}
    </div>
  );
};

export default UserMenu;

목록에 대한 것은 나중에 얘기하는 것으로 하고 계정 프로필만 말하면 드롭다운을 만들어주기 위해 useState를 사용한다.

isOpensetIsOpen으로 값을 boolean으로 해준다.

useCallback을 이용해 반복되는 toggleOpen 함수를 만들어주자.

Avatar 컴포넌트는 아까 로고를 만들 때과 마찬가지로 이미지를 넣으면 된다.

해당 아바타 이미지를 클릭하면 드롭다운을 열리게 한다.

isOpen이 true이면 로그인이 나오게 된다. MenuItem 컴포넌트도 이전의 Container 컴포넌트와 마찬가지이므로 따로 코드를 첨부하지는 않겠다.

마무리

완성된 Navbar는 이렇게 되어 있다.

유튜브를 보면서 처음으로 클론코딩을 했는데 내가 프론트를 할 때, 컴포넌트 구조와 확연히 다른 것을 느끼게 되었다..

코드도 한결 보기가 좋은 것 같다.

다음에는 Auth UI를 만들어 볼 것이고 깃허브를 통해 정리해놔야겠다.