- ํ๋ก์ ํธ ๋ง๋ค๊ธฐ
ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด ๋ด ์๋ค. ๋จผ์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค ํด๋๋ก ์ด๋ ํ ์๋ ๋ช ๋ น์ ์ ๋ ฅํฉ๋๋ค.
ํ๋ก์ ํธ๋ช ์ insta-clone์ผ๋ก ํ๊ฒ ์ต๋๋ค. (์ด๋ฆ์ ์ํ๋ ํ๋ก์ ํธ ์ด๋ฆ์ผ๋ก ์ฌ์ฉํด๋ ๋ฉ๋๋ค.)
next.js ํ๋ก์ ํธ๋ฅผ ๋ง๋ค๊ธฐ ์ํด์๋ npx create-react-app ์ ์ด์ฉํฉ๋๋ค.
> npx create-react-app insta-clone
1~2๋ถํ์ ํ๋ก์ ํธ๊ฐ ์์ฑ์ด ๋ฉ๋๋ค. ์ด์ ๋น์ฃผ์ผ ์คํ๋์ค ์ฝ๋๋ก ํ๋ก์ ํธ๋ฅผ ์ด์ด ๋ด ์๋ค.
> ๋น์ฃผ์ผ์คํ๋์ค ์ฝ๋์์ ์์ฑํ insta-clone ํด๋๋ฅผ ์ ํํฉ๋๋ค.
์๋ ์์ฑ๋ ์ฝ๋๋ฅผ ์ผ๋จ ์คํํด ๋ด ์๋ค.
๋น์ฃผ์ผ์คํ๋์ค ์ฝ๋(VSC) ๋ฉ๋ด์์ Terminal - New Terminal ์ ์ ํ ํ ํ npm run dev๋ฅผ ์คํํฉ๋๋ค.
ํฌ๋กฌ์ ์ด์ด์ http://localhost:3000/ ์ ๋ ฅํ๋ฉด Welcome to Next.js! ๊ฐ ๋จ๋๊ฑธ ํ์ธ ํ ์ ์์ต๋๋ค.
[๋๋ ํ ๋ฆฌ ์ญํ ]
pages ํด๋ : ํ์ด์ง ์์ฑ, ๋ผ์ฐํ ์ฒ๋ฆฌ๊ฐ ๋จ
public ํด๋ : ์ด๋ฏธ์ง,๋น๋์ค,์ค๋์ค ๋ฆฌ์์ค ํด๋
pages/api ํด๋ : ์๋ฒ์์ ๋์ํ๋ api ๊ตฌํ ํด๋
tailwind๋ฅผ ์ค์นํฉ๋๋ค.
cmd> npm install -D tailwindcss postcss autoprefixer
ํ์ฌ ํ๋ก์ ํธ์ tailwindcss ์ค์ ํฉ๋๋ค.
cmd> npx tailwindcss init -p
tailwind.config.js ์ฝ๋๋ฅผ ์๋์ ๊ฐ์ด ์์ ํฉ๋๋ค. pages, components ๋๋ ํ ๋ฆฌ์์ ์ฌ์ฉํ ์ ์๋ ํ์ฅ์ js,jsx,ts,tsx๋ฅผ ์ ์ํ ๋ด์ฉ์ ๋๋ค.
module.exports = {
content: [
"./pages/**/*.{js,jsx,ts,tsx}",
"./components/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {},
},
plugins: [],
};
styles/globals.css ์์
@tailwind base;
@tailwind components;
@tailwind utilities;
pages/index ์๋์ผ๋ก ์์ฑ๋ ์ฝ๋๋ฅผ ์ง์ฐ๊ณ ์ธ์คํํด๋ก Start!!! ์ฝ๋๋ก ์์ ํด ๋ด ์๋ค.
2์ฅ์์ ๋ฐฐ์ด Tailwind CSS (m-3, flex, justify-between, w-100 h-8 shadow rounded …) ๋ฅผ ์ฌ์ฉํ์ฌ ์ฝ๋๋ฅผ ๋ณ๊ฒฝํด ๋ด ์๋ค.
export default function Home() {
return (
<div className="m-3">
<div className="flex justify-between m-3">
<div className="w-100 h-8 shadow rounded
bg-[#d51c7c] text-white">์ธ์คํํด๋ก Start!!!</div>
</div>
</div>
)
}
์๋ฒ๊ฐ ์คํ๋์ด ์๋ค๋ฉด Ctrl+C ๋ก ์๋ฒ๋ฅผ ์ค์ง ํ npm run dev๋ฅผ ์คํ์์ผ ์ฃผ์ธ์.
(Tailwind CSS ์ค์ ์ ์ํด์ tailwind.config.js ๋ณ๊ฒฝํ์์ผ๋ฏ๋ก ์ฌ์คํ์ด ํ์ํฉ๋๋ค.)
Tailwind CSS๊ฐ ์ ์ฉ๋ ํ๋ฉด์ผ๋ก ์ถ๋ ฅ๋์์ต๋๋ค.
Tailwind CSS๊ฐ ์ ์ฉ๋๋๊ฒ๋ ํ์ธํ๊ณ , ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ์์ ์ ์งํํด ๋ณด๊ฒ ์ต๋๋ค.
component ๋ผ๋ ๋ง๋ค๊ธฐ
ํ๋ฉด์ ํฌ๊ฒ Header, Feed, Modal ๋ก ๋๋๊ฒ ์ต๋๋ค. ๋๋์ด์ง Header, Feed, Modal์ components ์ ์ ์ํ๊ฒ ์ต๋๋ค.
component๋ฅผ ์ฌ์ฉํ๋ ์ด์ ๋ ํ๋์ ํ์ด์ง์ html์ ๋ง๋ค์ด ์ฌ์ฉํ๋ฉด ์ฝ๋๊ฐ ๊ธธ์ด์ง๊ณ ๊น๋ํ์ง ์๊ธฐ ๋๋ฌธ์ ํด๋น ๊ธฐ๋ฅ๋ณ๋ก ์ปดํฌ๋ํธํ ์ํค๋๊ฑธ ๊ถ์ฅํฉ๋๋ค.
ํ๋์ฉ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
const Home: NextPage = () => {
return (
<div className=”bg-gray-50 h-screen overflow-y-scroll scrollbar-hide”>
{/* Header */}
{/* Feed */}
{/* Modal */}
</div>
)
}
bg-gray-50 background ์นผ๋ผ ํ์ 50
h-screen height 100vh (viewport ๋์ด์ 100%)
overflow-y-scroll ํฌ๊ธฐ๊ฐ overflow ๋์์๋ y-scroll ํ์
- Header Component
๋จผ์ , components ํด๋๋ฅผ ์์ฑํ๊ณ ๊ทธ ๋ฐ์ Header.tsx๋ฅผ ์์ฑํฉ๋๋ค.
rfce ์ ๋ ฅ ํ ํญ์ ์ ํํ๋ฉด ์ฝ๋๊ฐ ์๋์์ฑ๋ฉ๋๋ค.
( ๋ง์ฝ, ์๋์์ฑ์ด ๋์ง ์์ผ๋ฉด VS Code ์ต์คํ ์ ์์ ES7 React/Redux/GraphQL/React-Native snippets ์ค์นํด ์ฃผ์ธ์. ๋ฐ๋ณต๋๋ ์์ ์ ์ค์ฌ์ฃผ๊ณ ๊ฐ๋ฐ์ ์์ฐ์ฑ์ ๋์์ค๋๋ค.)
import React from 'react'
function Header() {
return (
<div>
Header
{/* Left */}
{/* Middle */}
{/* Right */}
</div>
)
}
export default Header
Header๋ ํฌ๊ฒ Left, Middle, Right๋ก ๋๋์ด์ง๋๋ค.
Left์๋ ์ธ์คํ๊ทธ๋จ๋ก๊ณ ๋ฅผ ํ์ํ๊ณ ๊ฐ์ด๋ฐ๋ ์์น Input Form , ๊ทธ๋ฆฌ๊ณ ์ค๋ฅธ์ชฝ์๋ ๋ฉ๋ด์์ด์ฝ์ ํ์ํ๊ฒ ์ต๋๋ค.
Left (์ด๋ฏธ์ง) Middle (Search) Right (icons)
Left์ ์ด๋ฏธ์ง๋ฅผ ์ถ๊ฐํด ๋ณด๊ฒ ์ต๋๋ค.
<Image
src='https://links.papareact.com/ocw'
layout='fill'
objectFit='contain'
/>
Image ํด๋์ค์ object-fit์ <img>, <video>์ ์ฌ์ด์ฆ๊ฐ ์ปจํ ์ด๋ ๋ด๋ถ์์ ์ด๋ป๊ฒ ์กฐ์ ๋๋์ง๋ฅผ ๋ํ๋ ๋๋ค. object-fit์ด ‘contain’์ด๋ฉด ๋ด๋ถ ์ฝํ ์ธ ์ ๋น์จ์ ์ ์งํ๋ฉด์ ์ปจํ ์ด๋์ fit ๋๋๋ก ๋ด๋ถ ์ฝํ ์ธ ์ ์ฌ์ด์ฆ๊ฐ ์กฐ์ ๋ฉ๋๋ค.
Image ํด๋์ค์ src, layout, objectFit๋ฅผ ์ ๋ ฅํ ํ ์คํํด ๋ณด์์ต๋๋ค.
์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค์.
Next/image์์๋ src์ ๋ค์ด๊ฐ๋ ๋๋ฉ์ธ์ ๋ฏธ๋ฆฌ next.config.js์ ์ ์๋ฅผ ํด ์ฃผ์ด์ผ ๋ฉ๋๋ค. ์ ์ ํ์ผ์ ์ ๊ทผ์ ์๊ด์์ง๋ง ์ธ๋ถ ์๋ฒ ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ ธ์์ผ ๋ ๊ฒฝ์ฐ๋ ๋๋ฉ์ธ ์ค์ ์ด ํ์ํฉ๋๋ค.
next.config.js์ ๋๋ฉ์ธ์ ์ถ๊ฐํด ์ฃผ์ธ์.
์๋ฒ๋ฅผ ์ค์งํ๊ณ ๋ค์ npm run dev๋ก ์คํํด ์ฃผ์ธ์. ํ๊ฒฝํ์ผ ๋ณ๊ฒฝ์์๋ ๋ฐ๋์ ์๋ฒ๋ฅผ ์ค์งํ๊ณ ์คํํ์ ์ผ ๋ฉ๋๋ค.
์ธ์คํ๊ทธ๋จ ๋ก๊ณ ์ด๋ฏธ์ง๊ฐ ์ถ๋ ฅ๋์๋ค์. ํ ๊ฐ์ด๋ฐ์ ํฌ๊ฒ ํ์๋์๋ค์. ์์น๋ ํฌ๊ธฐ๋ Tailwind๋ก ์กฐ์ ํด ๋ณผ๊ฒ์
relative h-24 2-24๋ก ์ฌ์ด์ฆ๋ฅผ ๋ณ๊ฒฝํ์๊ณ , ์์ div์ flex justify-between ๋ฅผ ์ถ๊ฐํ์์ต๋๋ค.
์ด๋ฏธ์ง๋ ๋ชจ๋ฐ์ผ๋์ PCํ๋ฉด์ผ๋ 2๊ฐ์ง๋ก ํ์ํ๋ ค๊ณ ํด์. ํ์ฌ ์ด๋ฏธ์ง๋ PC๋ฒ์ ์ผ๋๋ง ํ์ํฉ๋๋ค. ๊ทธ๋์ hidden lg:inline-grid ๋ฅผ ๋ฃ์ด ์ค๋๋ค.
์ด์ ๋ชจ๋ฐ์ผ๋ ์ด๋ฏธ์ง๋ก ์ถ๊ฐํด ๋ด ์๋ค.
์ด๋ฏธ์ง๋ฅผ ์ถ๊ฐํ className์ relative w-10 h-10 lg:hidden ์ ๋ฃ์ด ์ฃผ์์ต๋๋ค.
์์ด์ฝ์ heroicons ๋ฌด๋ฃ ์์ด์ฝ์ ์ฌ์ฉํ๊ฒ ์ต๋๋ค. ์ฌ์ฉํ๋ ค๋ฉด ๋จผ์ , ์ธ์คํจ์ด ํ์ํฉ๋๋ค.
heroicons ์ธ์คํจํ๊ธฐ
npm install @heroicons/react@v1
tailwindcss๊ฐ ์ง์ํ๋ forms ๋ ๊ฐ์ด ์ธ์คํจ ํ๊ฒ ์ต๋๋ค. ์ด ๋ถ๋ถ์ ์ธ์คํจ ํ ํ๊ฒฝ์ค์ ๋ ๋ณ๊ฒฝ์ด ํ์ํฉ๋๋ค.
npm install @tailwindcss/forms
์คํฌ๋กค๋ฐ๋ ํ์ํด์ ๋ฏธ๋ ์ธ์คํจ ํ๊ฒ ์ต๋๋ค.
npm install –save-dev tailwind-scrollbar
tailwind.config.js forms, scrollbar ์ถ๊ฐํด ์ฃผ์ธ์.
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
'./app/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [require("@tailwindcss/forms")],
require("tailwind-scrollbar"),
require("tailwind-scrollbar-hide"),
],
}
Search ํ๋ฉด์ถ๊ฐ
{/* Middle */}
<div className="max-w-xs">
<div className="relative mt-1 p-3 rounded-md">
<div className="absolute inset-y-0 pl-3 flex items-center
pointer-events-none">
<SearchIcon className="h-5 w-5" />
</div>
<input
className="bg-gray-50 block w-full pl-10
sm:text-sm border-gray-300 rounded-md
focus:ring-black focus:border-black"
type="text" placeholder="Search" />
</div>
</div>
SearchIcon์ ํ์ํฉ๋๋ค. ๊ทธ๋ฐ๋ฐ SearchIcon์ ํฌ์ปค์ค๊ฐ ํ์์๊ธฐ ๋๋ฌธ์ pointer-events-none์ ์์ div ํด๋์ค๋ก ์ ์ํฉ๋๋ค.
Menu ํ๋ฉด
{/* Right */}
<div className="flex items-center justify-end space-x-4">
<HomeIcon className="navBtn " />
<MenuIcon className="h-6 md:hidden cursor-pointer" />
<PaperAirplaneIcon className="navBtn rotate-45" />
<PlusCircleIcon className="navBtn" />
<UserGroupIcon className="navBtn" />
<HeartIcon className="navBtn" />
</div>
MenuIcon์ ํ๋ฉด์ด ์ปค์ง๋ฉด (md) ํ๋ ์ํต๋๋ค. navBtn์ ๋ฒํผ๋ค์ ์์ฑ์ด ๊ฐ๊ธฐ ๋๋ฌธ์ globals.css ์ ์ ์ํ์ฌ ์ฌ์ฉํฉ๋๋ค.
globals.css navBtn ํด๋์ค๋ฅผ ์ ์ํด ๋ณด๊ฒ ์ต๋๋ค.
@layer components {
.navBtn {
@apply hidden h-6 md:inline-flex
cursor-pointer hover:scale-125
transition-all duration-150 ease-out;
}
}
ํค๋๋ฅผ ์์ฑํ์ต๋๋ค.
Feed๋ฅผ ๊ตฌ์ฑํด ๋ด ์๋ค.
Feed Component
Feed Component๋ฅผ ์์ฑํด ๋ณด๊ฒ ์ต๋๋ค.
components/Feed.tsx ๋ง๋ค๊ธฐ
Feed๋ 2๊ฐ์ ์ธ์ ์ผ๋ก ๊ตฌ๋ถ๋ฉ๋๋ค. ์ฒซ๋ฒ์งธ ์ธ์ ์๋ Stories, Posts ๋๋ฒ์งธ ์ธ์ ์๋ Mini profile, Suggestions ์ผ๋ก ๊ตฌ๋ถ๋ฉ๋๋ค.
Stories ์ปดํฌ๋ํธ ์์ฑ
Stories์ปดํฌ๋ํธ๋ ์ฌ๋ฌ๊ฐ์ Story์ปดํฌ๋ํธ๋ก ์ด๋ฃจ์ด์ ธ ์์ต๋๋ค.
import React from 'react'
function Stories() {
return (
<div>
{/* Story */}
{/* Story */}
{/* Story */}
{/* Story */}
{/* Story */}
</div>
)
}
export default Stories
์ธ์คํ์์ ๋งจ์์ ์์นํด ์๊ณ , ์ธ์น๋ค์ด ์คํ ๋ฆฌ๋ฅผ ์ฌ๋ฆฌ๋ฉด ์์ด์ฝ์ด ํ์ด๋ผ์ดํธ ์ฒ๋ฆฌ๋์ด ํ์๋ฉ๋๋ค. ํ์ฌ ์ธ์น๊ด๋ จ๋ถ๋ถ์ ์ด๋ฒ ๋ฒ์๊ฐ ์๋๋ฏ๋ก, ์คํ ๋ฆฌ์ ์ธ์น ํ์๋ randomuser.me ์ฌ์ดํธ์ API๋ฅผ ์ด์ฉํ๊ฒ ์ต๋๋ค.
๋จผ์ , next.js์์ ์ง์ํ๋ getServerSideProps์ ๋ํด์ ์ค๋ช ํ๊ฒ ์ต๋๋ค.
getServerSideProps๋ Next์์ ํ์ด์ง์ ๋ฐ์ดํฐ๋ฅผ ์๋ฒ๋ก๋ถํฐ ์ ๊ณต๋ฐ๋ ๊ธฐ๋ณธ API์ ๋๋ค. ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ํจ์นํ์ฌ ์ด๊ธฐ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋๋ก ๊ตฌ์ฑ์ด ๋์ด ์์ต๋๋ค.
randomUsers ๋ก ์คํ ๋ฆฌ์ ์ ์ ๋ค์ ์๋์์ฑํฉ๋๋ค.
export async function getServerSideProps() {
console.log("getServerSideProps step1 ");
let randomUsersResults = [];
try {
const res = await fetch(
"https://randomuser.me/api/?results=30&inc=name,login,picture"
);
randomUsersResults = await res.json();
} catch (e) {
randomUsersResults = [];
}
return {
props: {
randomUsersResults,
},
};
}
getServerSideProps๋ ์ด๋ฆ ๊ทธ๋๋ก ์๋ฒ ์ธก์์ props๋ฅผ ๋ฐ์์ค๋ ๊ธฐ๋ฅ์ ํ๊ฒ ๋ฉ๋๋ค. ํ์ด์ง๋ฅผ ์์ฒญ ์๋ง๋ค ์คํ์ด ๋๋ฉฐ getServerSideProps์์ ํ์ด์ง๋ก ์ ๋ฌํด์ค ๋ฐ์ดํฐ๋ฅผ ์๋ฒ์์ ๋ ๋๋ง์ ํ๊ฒ ๋ฉ๋๋ค. ์๋ฒ์์ ์คํ๋๊ธฐ ๋๋ฌธ์, ์์ ์ฝ๋๋ฅผ ์๋ก๊ณ ์นจ์ ํ์ฌ ์คํํด ๋ณธ๋ค๋ฉด ๋ธ๋ผ์ฐ์ ์ ์ฝ์ ์ถ๋ ฅ์ด ์๋ ํฐ๋ฏธ๋์์ ์ถ๋ ฅ ๋๋ ๊ฒ์ ํ์ธํ์ค ์ ์์ต๋๋ค.
์คํ ๋ฆฌ์ ์คํฌ๋กค๋ฐ๋ ํ์ํฉ๋๋ค. ์ฐ๋ฆฌ๋ ์์์ ๋ฏธ๋ฆฌ tailwind-scrollbar๋ฅผ ์ธ์คํจ ํ์ต๋๋ค.
Feed ์์ค
function Feed({ randomUsers }) {
return (
<main className="grid grid-cols-1 md:grid-cols-2
md:max-w-3xl xl:grid-cols-3 xl:max-w-6xl max-auto">
{/* Section */}
<section className="col-span-2">
<Stories randomUsers={randomUsers} />
{/* Posts */}
</section>
{/* Section */}
<section>
{/* Mini profile */}
{/* Suggestions */}
</section>
</main>
)
}
Stories ์์ค
function Stories({ randomUsers }) {
return (
<div className="flex space-x-2 p-6 bg-white mt-8
border-gray-200 rounded-sm
overflow-x-scroll scrollbar-thin scrollbar-thumb-black">
{randomUsers?.results?.map(profile => (
<Story key={profile.login.username}
img={profile.picture.thumbnail}
username={profile.login.username} />
))}
</div>
)
}
username, thumbnail์ ํ์ํฉ๋๋ค. ์คํฌ๋กค ๊ด๋ จ overflow-x-scroll scrollbar-thin scrollbar-thumb-black ๋ ํด๋์ค์ ์ถ๊ฐํ์ต๋๋ค.
Story ์์ค
function Story({key, img, username}) {
return (
<div>
<img
src={img}
alt="profile pic"
className="h-14 w-14 rounded-full p-[1.5px] border-red-500 border-2
object-contail cursor-pointer hover:scale-110
transform duration-200 ease-out"
/>
<p className="text-xs w-14 truncate text-center">{username}</p>
</div>
)
}
hover:scale-110 ์คํ ๋ฆฌ์ ๋ง์ฐ์ค ์ค๋ฒ์์ ์ด๋ฏธ์ง๋ฅผ ํฌ๊ธฐ๋ฅผ 110%๋ก ํ๋ํฉ๋๋ค.
Posts
function Posts() {
return (
<div>
{/* Post */}
{/* Post */}
{/* Post */}
{/* Post */}
</div>
)
}
Posts ์ปดํฌ๋ํธ๋ ์ฌ๋ฌ๊ฐ์ Post๋ก ์ด๋ฃจ์ด์ ธ ์์ต๋๋ค.
ํ์ด์ด๋ฒ ์ด์ค์ ์ฐ๊ฒฐํ๋ ๋ถ๋ถ์ 5์ฅ์์ ๋ค๋ฃจ๊ณ ์์ต๋๋ค. ์ฌ๊ธฐ์์๋ ๋๋ฏธ๋ฐ์ดํฐ๋ก ํ๋ฉด์ ๊ตฌ์ฑํ๊ฒ ์ต๋๋ค.
const posts = [ // ๋๋ฏธ ๋ฐ์ดํฐ
{
id: "123",
username: "blockmeta",
userImg: "https://i.pinimg.com/564x/dc/36/eb/
dc36eb1e90a9513ef82b343ef2d852ac.jpg",
img: "https://i.pinimg.com/236x/63/b7/41/
63b7413ad91d74ae34986de68283bf9d.jpg",
caption: "Next.js ์ธ์คํํด๋ก ์ฝ๋ฉ ์ ๋ณต",
}
];
function Posts() {
return (
<div>
{
posts.map((post) => (
<Post key={post.id} id={post.id}
username={post.username}
userImg={post.userImg}
img={post.img}
caption={post.caption}
/>
))
}
</div>
)
}
id, username, userImg, img, caption ์ props (ํ๋กฌ์ค)๋ก Post ์ปดํฌ๋ํธ์ ๊ฐ์ ์ ๋ฌํฉ๋๋ค.
๋๋น ๋ฐ์ดํฐ posts๋ ๋ฆฌ์คํธ๋ก ๋์ด ์์ด์ map()์ ์ด์ฉํฉ๋๋ค.
Post์์๋ ์ ๋ฌ ๋ฐ์ props๋ ์๋์ ๊ฐ์ด ํ์ํฉ๋๋ค.
function Post( { id, username, userImg, img, caption })
์ ๋ฌํ ๋๋ ๋ฐ์๋ ๊ฐ์ ๋ณ์์ด๋ฆ์ผ๋ก ์ฌ์ฉํ๊ฒ์.
Post์๋ Header, img, Buttons, caption, comments, input box ํ์ํ ์ปดํผ๋ํธ๊ฐ ๋ง์ด ์์ด์. ์ฐ๋ฆฌ๊ฐ ๊ตฌํํ ํต์ฌ ๊ธฐ๋ฅ์ด ์์ต๋๋ค.
ํ๋์ฉ ์ดํด ๋ณด๊ฒ ์ต๋๋ค.
Header
{/* Header */}
<div className="bg-white my-7 border rounded-sm">
<div className="flex items-center p-5">
<img className="rounded-full h-12 w-12 object-contain
border p-1 mr-3" />
<p className="flex-1 font-bold">{username}</p>
<DotsHorizontalIcon className="h-5" />
</div>
</div>
Header ๋ถ๋ถ์ด ์คํ๋ ํ๋ฉด์ ๋๋ค.
img
<img src={img} className=”object-cover w-full” />
Button
{/* Buttons */}
<HeartIcon className='btn' />
<ChatIcon className='btn' />
<PaperAirplaneIcon className='btn' />
๊ณตํต์ผ๋ก ์ฌ์ฉํ๋ btn ํด๋์ค๋ globals.css์ @apply๋ก ์ ์ํด ์ค๋๋ค.
globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.navBtn {
@apply hidden h-6 md:inline-flex
cursor-pointer hover:scale-125
transition-all duration-150 ease-out;
}
.btn {
@apply h-7 hover:scale-125 cursor-pointer
transition-all duration-150 ease-out
}
}
BookmarkIcon ๋ ์ถ๊ฐ ํ์ต๋๋ค.
{/* Buttons */}
<div className="flex justify-between px-4 pt-4">
<div className="flex space-x-4">
<HeartIcon className='btn' />
<ChatIcon className='btn' />
<PaperAirplaneIcon className='btn' />
</div>
<BookmarkIcon className="btn" />
</div>
HeartIcon, ChatIcon, PaperAirplaneIcon ์ด ํ์๋ ํ๋ฉด์ ๋๋ค.
caption
username, caption ์ด ํ์๋ ํ๋ฉด์ ๋๋ค.
truncate ๊ธ์๊ฐ ํ๋ฉด์ ๋์น๋ฉด ๋์น๋ ๋ถ๋ถ์ ์๋ฅด๊ณ … ์ค์๋ง๋ก ํ์
input box
input box๋ border๋ฅผ
์์ ๊ณ ๊น๋ํ๊ฒ ์ฒ๋ฆฌํ์์ต๋๋ค.
border-none focus:ring-0 outline-none
MinProfile
Suggestion
'2. Back-end > >> ๐ Next.js' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Next.js - React, Express.js ๊ทธ๋ฆฌ๊ณ SSR์ ํ๋ฐฉ์ (0) | 2022.12.20 |
---|---|
Next.js 13์ผ๋ก ๋ง๋๋ News App (TypeScript, StepZen, Tailwind, Dark Mode, GraphQL) (0) | 2022.12.13 |
Next.js 13 ์ ๋ฆฌ (0) | 2022.12.07 |
๋๊ธ