Next.js 13 기본 골격

버건디·2023년 3월 28일
0

Next.js

목록 보기
32/52
post-custom-banner

- globals.css

*{
  box-sizing: border-box;
}
body{
  width: 100vw;
  height: 100vh;
  margin: 0;
  background: rgb(189, 102, 102);
  overflow: scroll;
}

- layout.tsx

import "./globals.css";

import localFont from "next/font/local";
import Header from "@/components/Header/Header";
import Card from "@/components/Card/Card";
import ContentCard from "@/components/Card/ContentCard";
import Footer from "@/components/Footer/Footer";

const myFont = localFont({
  src: "./fonts/RobotoCondensed-Bold.ttf",
  display: "swap",
});

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" className={myFont.className}>
      <body>
        <Card>
          <Header />
          <ContentCard>{children}</ContentCard>
          <Footer />
        </Card>
      </body>
    </html>
  );
}

- Card.tsx 컴포넌트

import classes from "./Card.module.css";

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

export default function Card({ children }: Children) {
  return <div className={classes.Card}>{children}</div>;
}

- Card.module.css 코드

.Card{
    width: 90%;
    height: 100%;
    margin: 30px auto;
}

- Header.tsx

import classes from "./Header.module.css";
import Link from "next/link";

export default function Header() {
  return (
    <>
      <div className={classes.header_container}>
        <div className={classes.main_Logo}>
          <Link href={"/"} className={classes.link}>
            <h1 className={classes.main_name}>BRGNDY&apos;s BLOG</h1>
          </Link>
        </div>
        <div className={classes.header_category_container}>
          <div className={classes.category}>
            <Link href={"/"} className={classes.link}>
              <span>home</span>
            </Link>
          </div>
          <div className={classes.category}>
            <Link href={"/about"} className={classes.link}>
              <span>about</span>
            </Link>
          </div>
          <div className={classes.category}>
            <Link href={"/posts"} className={classes.link}>
              <span>posts</span>
            </Link>
          </div>
          <div className={classes.category}>
            <Link href={"/contact"} className={classes.link}>
              <span>contact</span>
            </Link>
          </div>
        </div>
      </div>
    </>
  );
}

- Header.module.css

.header_container{
    width: 90%;
    height : 100px;
    display: flex;
    justify-content: space-between;
    border-bottom: 1px solid black;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    
}

.main_name{
    margin: 0;
}



.header_category_container{
    display: flex;
}

.main_Logo{
    display: flex;
    justify-content : center;
    align-items: center;
    font-size: 28px;
}

.category{
    margin: 10px;
    display: flex;
    justify-content : center;
    align-items: center;
    font-size: 24px;
}

.link{
    color: black;
    text-decoration: none;
}


.link:visited{
    text-decoration: none;
}

position을 fixed로 하고 top 0 left 0 right 0 을 주니까 헤더 컴포넌트가 가운데로 위치하지 않았다 .

.header_container{
    width: 90%;
    height : 100px;
    display: flex;
    justify-content: space-between;
    border-bottom: 1px solid black;
    position: fixed;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    z-index: 10;
    background: rgb(189, 102, 102);
}

이런식으로 left를 50%를 주고, transform 속성을 주어서 양 옆 수평을 맞춰주면 헤더가 가운데로 잘 위치한다.

또한 스크롤이 됐을때 컨텐츠와 헤더가 겹치지 않기위해 z-index와 background 속성을 지정해준다.

이런식으로 fixed position을 활용하면 컨텐츠 속성들이 헤더와 겹쳐지게 보이는데, 이럴때 ContentCard라는 카드 컴포넌트를 하나 더 만들어주어서 골격을 만들어 줄 수 있다.

ContentCard 컴포넌트는 안에 컨텐츠가 많을수도, 적을수도 있어서 min-height 는 100%으로 주되, height 자체는 auto로 적용한다.

- ContentCard.tsx

import classes from "./ContentCard.module.css";

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

export default function ContentCard({ children }: childrenType) {
  return <div className={classes.content_card}>{children}</div>;
}

- ContentCard.module.css

.content_card{
    width: 100%;
    height: auto;
    min-height: 100%;
    padding-top: 100px;
    padding-bottom: 100px; /* footer height 만큼*/
}

- Footer.tsx

import classes from "./Footer.module.css";

export default function Footer() {
  return (
    <footer className={classes.footer_container}>
      <h1>Footer 섹션</h1>
    </footer>
  );
}

- Footer.module.css

.footer_container{
    width: 100%;
    height : 100px;
    border-top: 1px solid black;
    display: flex;
    justify-content: center;
    z-index: 10;
    background: rgb(189, 102, 102);
    margin-top : 10px;
    position : relative;
    transform : translateY(-100%);
}

골격이 잘잡혔다.


기본 골격이 위에 저렇게 되어있었는데, Post List 컴포넌트가 안에 들어오니까 footer 컴포넌트와 겹치는 문제가 발생했다.

- PostList.tsx

import classes from "./PostList.module.css";
import PostItem from "./PostItem";
import { postsType } from "../../../types/postsType";

type PostListProps = {
  posts: postsType[];
};

export default function PostList({ posts }: PostListProps) {
  return (
    <>
      <div className={classes.postList_container}>
        <div className={classes.featuredList_container}>
          {posts.map((post) => {
            return (
              <PostItem
                key={post.id}
                id={post.id}
                title={post.title}
                description={post.description}
                date={post.date}
                category={post.category}
                path={post.path}
                featured={post.featured}
                imgUrl={post.imgUrl}
              />
            );
          })}
        </div>
      </div>
    </>
  );
}

- PostList.module.css

.postList_container{
    display: flex;
}

.featuredList_container{
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    grid-template-rows: auto;
    gap: 20px;
    animation : fadeIn 0.3s forwards;
}

.text{
    margin-left : 20px;
}

layout.tsx 에서 ContentCard 안에 Footer 컴포넌트를 넣어주어서 해결했다. ( 분리해야함 )

-PostItem.tsx

import classes from "./PostItem.module.css";
import Image from "next/image";
import Link from "next/link";

type postType = {
  id: number;
  title: string;
  description: string;
  date: string;
  category: string;
  path: string;
  featured: boolean;
  imgUrl: string;
};

export default function PostItem({
  title,
  description,
  date,
  category,
  path,
  featured,
  imgUrl,
}: postType) {
  return (
    <>
      <div className={classes.postItem_li}>
        <Link href={`/posts/${path}`} className={classes.post_link}>
          <div className={classes.postItem_img_container}>
            <Image
              src={imgUrl}
              alt={title}
              width={300}
              height={250}
              className={classes.post_img}
            />
          </div>
          <div className={classes.postItem_info_container}>
            <div className={classes.postItem_info_date}>
              <p>{date}</p>
            </div>
            <div className={classes.postItem_info_title}>
              <p>{title}</p>
            </div>
            <div className={classes.postItem_info_description}>
              <p>{description}</p>
            </div>
            <div className={classes.postItem_info_category}>
              <p>{category}</p>
            </div>
          </div>
        </Link>
      </div>
    </>
  );
}

- PostItem.module.css

.post_link{
    
    text-decoration: none;
    color : black;
}

.postItem_li{
    list-style: none;
    display: flex;
    flex-direction: column;
    border-radius: 20px;
    box-shadow: 0px 20px 50px rgba(0, 0, 0, 0.3);
    margin: 30px;
}


.postItem_img_container{
    width: 100%;
}

.post_img{
    object-fit: cover;
    width: 100%;
    height: 100%;
    border-top-left-radius: 20px;
    border-top-right-radius: 20px;
}

.postItem_info_container{
display: flex;
flex-direction : column;
padding: 10px;
margin-top: 5px;

}

.postItem_info_container p{
    margin: 5px;
}

.postItem_info_date{
    display: flex;
    justify-content: flex-end;
}

.postItem_info_description{
    width: 100%;
    display: flex;
    justify-content: center;
    
}

.postItem_info_description p{
    overflow: hidden;
    text-overflow: ellipsis;
    -webkit-line-clamp : 1; /* 최대 1줄까지 표시 */
    display: -webkit-box; /* Webkit 계열 브라우저에서만 지원하는 속성입니다. */
    -webkit-box-orient: vertical;
   
}

.postItem_info_title{
    display: flex;
    justify-content: center;
    font-size: 20px;
    font-weight: bold;
}

.postItem_info_title p{
    overflow: hidden;
    text-overflow: ellipsis;
    -webkit-line-clamp : 1; /* 최대 1줄까지 표시 */
    display: -webkit-box; /* Webkit 계열 브라우저에서만 지원하는 속성입니다. */
    -webkit-box-orient: vertical;
   
}

.postItem_info_category{
    display: flex;
    justify-content: center;
    
}


.postItem_info_category p{
   background-color: rgb(178, 82, 82);
   padding : 10px;
    border-radius: 10px;
}
// 구조 보는법 
['div', 'span', 'ul', 'li', 'dd', 'dl', 'section', 'h1', 'a', 'img', 'form', 'button', 'header', 'footer', 'input', 'p'].forEach(e => {
    document.querySelectorAll(e).forEach(element => {
        element.style.outline = "1px solid dodgerblue"
    })
})

profile
https://brgndy.me/ 로 옮기는 중입니다 :)
post-custom-banner

0개의 댓글