코딩/React

NextJS + Typescript + TailwindCSS 적용 2편

드리프트 2021. 12. 26. 15:08
728x170

 

안녕하세요?

 

지난 블로그에서 NextJS와 Typescript 그리고 Tailwind CSS까지 적용된 템플릿을 만들었는데요.

 

이번 시간에는 거기에 추가해서 Tailwind CSS를 이용한 모바일 적용되는 반응형 템플릿을 만들어 보겠습니다.

 

이렇게 하는 이유는 한번 만들어 놓으면 나중에 재사용이 쉬워서 그렇습니다.

 

디자인은 원하시는 방향으로 만들어도 상관없습니다.

 

전체적인 Responsive(반응형) 디자인이 목적이니까요?

 

일단 components 폴더를 만듭시다.

 

그리고 Layout.tsx파일도 생성시킵시다.

 

mkdir components
cd components
touch Layout.tsx

 

Layout.tsx파일을 만들기 전에 _app.tsx에 Layout을 적용시켜 볼까요?

 

 

/pages/_app.tsx

import '../styles/globals.css'

import Layout from "../components/Layout";

function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  );
}

export default MyApp;

 

_app.tsx파일은 간단합니다.

 

앞으로 만들 모든 페이지는 Layout을 가진다는 뜻입니다.

 

이제 Layout.tsx파일을 볼까요?

 

 

/components/Layout.tsx

import React from "react";
import NavBar from "./Navbar";

type Props = {
  children: React.ReactNode;
};

export default function Layout(props: Props) {
  return (
    <div className="w-full p-0">
      <NavBar />
      {props.children}
    </div>
  );
}

위 코드를 보시면 전체적으로 우리의 Layout은 w-full p-0으로 지정했습니다.

 

width: 100%; padding : 0px; 이란 뜻이죠.

 

그리고 Navbar 컴포넌트가 보이네요.

 

상단 Navbar인데요.

 

이제 Navbar.tsx 파일을 components 폴더에 만듭시다.

 

/components/Navbar.tsx

import React, { useState } from "react";

import { MenuIcon, XIcon } from "@heroicons/react/outline";

const Navbar = () => {
  const [menuToggle, setMenuToggle] = useState(false);

  return (
    //   navbar goes here
    <nav className="bg-gray-100">
      <div className="max-w-6xl mx-auto px-4">
        <div className="flex justify-between">
          <div className="flex space-x-4">
            {/* logo */}
            <div>
              <a href="/" className="flex items-center py-5 px-2 text-gray-700">
                <svg
                  xmlns="http://www.w3.org/2000/svg"
                  className="h-5 w-5 mr-2 text-blue-400"
                  viewBox="0 0 20 20"
                  fill="currentColor"
                >
                  <path
                    fillRule="evenodd"
                    d="M9.504 1.132a1 1 0 01.992 0l1.75 1a1 1 0 11-.992 1.736L10 3.152l-1.254.716a1 1 0 11-.992-1.736l1.75-1zM5.618 4.504a1 1 0 01-.372 1.364L5.016 6l.23.132a1 1 0 11-.992 1.736L4 7.723V8a1 1 0 01-2 0V6a.996.996 0 01.52-.878l1.734-.99a1 1 0 011.364.372zm8.764 0a1 1 0 011.364-.372l1.733.99A1.002 1.002 0 0118 6v2a1 1 0 11-2 0v-.277l-.254.145a1 1 0 11-.992-1.736l.23-.132-.23-.132a1 1 0 01-.372-1.364zm-7 4a1 1 0 011.364-.372L10 8.848l1.254-.716a1 1 0 11.992 1.736L11 10.58V12a1 1 0 11-2 0v-1.42l-1.246-.712a1 1 0 01-.372-1.364zM3 11a1 1 0 011 1v1.42l1.246.712a1 1 0 11-.992 1.736l-1.75-1A1 1 0 012 14v-2a1 1 0 011-1zm14 0a1 1 0 011 1v2a1 1 0 01-.504.868l-1.75 1a1 1 0 11-.992-1.736L16 13.42V12a1 1 0 011-1zm-9.618 5.504a1 1 0 011.364-.372l.254.145V16a1 1 0 112 0v.277l.254-.145a1 1 0 11.992 1.736l-1.735.992a.995.995 0 01-1.022 0l-1.735-.992a1 1 0 01-.372-1.364z"
                    clipRule="evenodd"
                  />
                </svg>
                <span className="font-bold">Home</span>
              </a>
            </div>

            {/* primary nav */}
            <div className="hidden md:flex items-center space-x-1">
              <a
                href="/features"
                className="py-5 px-3 text-gray-700 hover:text-gray-900"
              >
                Features
              </a>
              <a
                href="#"
                className="py-5 px-3 text-gray-700 hover:text-gray-900"
              >
                Pricing
              </a>
            </div>
          </div>

          {/* secondary nav */}
          <div className="hidden md:flex items-center space-x-1">
            <a href="#" className="py-5 px-3">
              Login
            </a>
            <a
              href="#"
              className="py-2 px-3 bg-yellow-400 hover:bg-yellow-300 text-yellow-900 hover:text-yellow-800 rounded transition duration-300"
            >
              Signup
            </a>
          </div>

          {/* mobile menu */}
          <div className="md:hidden flex items-center">
            <button onClick={() => setMenuToggle(!menuToggle)}>
              {menuToggle ? (
                <XIcon className="w-6 h-6" />
              ) : (
                <MenuIcon className="w-6 h-6" />
              )}
            </button>
          </div>
        </div>
      </div>

      {/* mobile menu items */}
      <div className={`${!menuToggle ? "hidden" : ""} md:hidden`}>
        <a
          href="/features"
          className="block py-2 px-4 text-sm hover:bg-gray-200"
        >
          Features
        </a>
        <a href="#" className="block py-2 px-4 text-sm hover:bg-gray-200">
          Pricing
        </a>

        <a href="#" className="block py-2 px-4 text-sm hover:bg-gray-200">
          Login
        </a>
        <a href="#" className="block py-2 px-4 text-sm hover:bg-gray-200">
          Signup
        </a>
      </div>
    </nav>
  );
};

export default Navbar;

 

 

Navbar 컴포넌트를 보면 아이콘 관련하여 @heroicons/react/outline 모듈이 보이는데요.

 

우리가 쓸 메뉴 관련 아이콘 모듈입니다.

 

이걸 다음과 같이 설치해 주십시오.

 

 

npm i @heroicons/react

or 

yarn add @heroicons/react

 

이제 전체적인 모양새가 완성됐으니까 실행시켜 볼까요?

 

npm run dev

or

yarn dev

 

 

브라우저 크기를 줄이니까 모바일 방식으로 나타나네요.

 

이렇게 전체적으로 반응형 방식의 NextJS + Typescript + Tailwind CSS 템플릿 초안이 완성되었습니다.

 

여기서 추가로 모바일 메뉴에 있는 Features 파일을 만들어 볼까요?

 

/pages/features.tsx 파일을 다음과 같이 만듭시다.

 

 

/pages/features.tsx

import Head from "next/head";
import Card from "../components/Card";

export default function Features() {
  return (
    <div>
      <Head>
        <title>Tailwind CSS Tutorial - Features</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <div className="container px-5 py-10 mx-auto">
        {/* Cards Page - Title */}
        <div className="text-center mb-12">
          <h1 className="text-4xl md:text-6xl text-gray-700 font-semibold">
            Tailwind Css Responsive Cards
          </h1>
        </div>

        <div className="flex flex-wrap -m-4">
          {/* First Card */}
          <Card
            imageSrc="https://picsum.photos/id/188/720/400/"
            date="Oct 15, 2021"
            title="Cities are crowded"
            desc="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aperiam
              modi, expedita quos doloremque autem ipsum itaque incidunt ipsam
              reprehenderit fuga! Dolores quisquam eius cum accusamus?"
            viewed="1.2K"
            reply={6}
          />

          {/* Second Card */}
          <Card
            imageSrc="https://picsum.photos/id/1016/720/400"
            date="Oct 16, 2021"
            title="Mountains are alone"
            desc="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aperiam
              modi, expedita quos doloremque autem ipsum itaque incidunt ipsam
              reprehenderit fuga! Dolores quisquam eius cum accusamus?"
            viewed="1.1K"
            reply={5}
          />

          {/* Third Card */}
          <Card
            imageSrc="https://picsum.photos/id/1011/720/400"
            date="Oct 17, 2021"
            title="Lakes are silent"
            desc="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aperiam
              modi, expedita quos doloremque autem ipsum itaque incidunt ipsam
              reprehenderit fuga! Dolores quisquam eius cum accusamus?"
            viewed="1.0K"
            reply={3}
          />
        </div>
      </div>
    </div>
  );
}

 

코드를 보니까 components 폴더에서 Card 컴포넌트를 불러오는데요.

 

Card 컴포넌트는 지난번에 만들었던 카드형 디자인입니다.

 

/components/Card.tsx 파일을 다음과 같이 만들어 볼까요?

 

/components/Card.tsx

import { ArrowRightIcon, EyeIcon, ChatIcon } from "@heroicons/react/outline";

export interface CardProps {
  imageSrc: string;
  date: string;
  title: string;
  desc: string;
  viewed: string;
  reply: number;
}

const Card = (props: CardProps) => {
  return (
    <div className="p-4 sm:w-1/2 lg:w-1/3">
      <div className="h-full border-2 border-gray-200 border-opacity-60 rounded-lg overflow-hidden">
        <img
          className="lg:h-72 md:h-48 w-full object-cover object-center"
          src={props.imageSrc}
          alt="blog"
        />
        <div className="p-6 hover:bg-indigo-600 hover:text-white transition duration-300 ease-in">
          <h2 className="text-base font-medium text-indigo-300 mb-1">
            {props.date}
          </h2>
          <h1 className="text-2xl font-semibold mb-3">{props.title}</h1>
          <p className="leading-relaxed mb-3">{props.desc}</p>
          <div className="flex items-center flex-wrap ">
            <a className="text-indigo-300 inline-flex items-center md:mb-2 lg:mb-0">
              Read More
              <ArrowRightIcon className="w-4 h-4 ml-2" />
            </a>
            <span className="text-gray-400 mr-3 inline-flex items-center lg:ml-auto md:ml-0 ml-auto leading-none text-sm pr-3 py-1 border-r-2 border-gray-200">
              <EyeIcon className="w-4 h-4 mr-1" />
              {props.viewed}
            </span>
            <span className="text-gray-400 inline-flex items-center leading-none text-sm">
              <ChatIcon className="w-4 h-4 mr-1" />
              {props.reply}
            </span>
          </div>
        </div>
      </div>
    </div>
  );
};

export default Card;

 

이제 실행결과를 볼까요?

 

 

정말 멋진 카드형 UI가 완성되었네요.

 

그럼.

그리드형