*{
box-sizing: border-box;
}
body{
width: 100vw;
height: 100vh;
margin: 0;
background: rgb(189, 102, 102);
overflow: scroll;
}
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>
);
}
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{
width: 90%;
height: 100%;
margin: 30px auto;
}
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'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_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로 적용한다.
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>;
}
.content_card{
width: 100%;
height: auto;
min-height: 100%;
padding-top: 100px;
padding-bottom: 100px; /* footer height 만큼*/
}
import classes from "./Footer.module.css";
export default function Footer() {
return (
<footer className={classes.footer_container}>
<h1>Footer 섹션</h1>
</footer>
);
}
.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 컴포넌트와 겹치는 문제가 발생했다.
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_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 컴포넌트를 넣어주어서 해결했다. ( 분리해야함 )
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>
</>
);
}
.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"
})
})