Streams UI
이제 비디오를 스트리밍할 페이지를 만들어 보도록 하겠습니다.
pages/streams/index.tsx
import type { NextPage } from "next";
const Live: NextPage = () => {
return (
<div className="space-y-4 divide-y-2 py-10">
{[...Array(5)].map((_i, i) => (
<div key={i} className="px-4 pt-4">
<div className="aspect-video w-full rounded-md bg-slate-300" />
<h3 className="mt-2 text-lg font-medium text-gray-700">
Let's try potatos
</h3>
</div>
))}
<button className="fixed bottom-24 right-5 cursor-pointer rounded-full border-transparent bg-orange-400 p-4 text-white shadow-md transition-colors duration-300 hover:bg-orange-500">
<svg
className="h-6 w-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
></path>
</svg>
</button>
</div>
);
};
export default Live;
여기서도 마찬가지로 Home screen에서 사용한 부유 버튼을 사용하였습니다. 이를 FAB라고 합니다. 여기서 Heroicons.dev에 들어가서 svg icon만 바꾸었습니다. 다음으로는 비디오를 클릭했을 떄 비디오를 보며 채팅할 수 있는 페이지를 만들어 보겠습니다.
Streams Detail UI
pages/streams/[id].tsx
import type { NextPage } from "next";
const StreamDetail: NextPage = () => {
return (
<div className="space-y-4 px-4 py-10 scrollbar-hide">
<div className="aspect-video w-full rounded-md bg-slate-300" />
<h3 className="mt-2 text-2xl font-semibold text-gray-800">
Let's try potatos
</h3>
<div className=" h-[50vh] space-y-4 overflow-y-scroll py-10 px-4 pb-16">
<div className="flex items-start space-x-2">
<div className="h-8 w-8 rounded-full bg-slate-400" />
<div className="w-1/2 rounded-md border border-gray-300 p-2 text-sm text-gray-700">
<p>Hi how much are you selling them for?</p>
</div>
</div>
<div className="flex flex-row-reverse items-start space-x-2 space-x-reverse">
<div className="h-8 w-8 rounded-full bg-slate-400" />
<div className="w-1/2 rounded-md border border-gray-300 p-2 text-sm text-gray-700">
<p>I want ₩20,000</p>
</div>
</div>
<div className="flex items-start space-x-2">
<div className="h-8 w-8 rounded-full bg-slate-400" />
<div className="w-1/2 rounded-md border border-gray-300 p-2 text-sm text-gray-700">
<p>Hi how much are you selling them for?</p>
</div>
</div>
<div className="flex flex-row-reverse items-start space-x-2 space-x-reverse">
<div className="h-8 w-8 rounded-full bg-slate-400" />
<div className="w-1/2 rounded-md border border-gray-300 p-2 text-sm text-gray-700">
<p>I want ₩20,000</p>
</div>
</div>
<div className="flex items-start space-x-2">
<div className="h-8 w-8 rounded-full bg-slate-400" />
<div className="w-1/2 rounded-md border border-gray-300 p-2 text-sm text-gray-700">
<p>Hi how much are you selling them for?</p>
</div>
</div>
<div className="flex flex-row-reverse items-start space-x-2 space-x-reverse">
<div className="h-8 w-8 rounded-full bg-slate-400" />
<div className="w-1/2 rounded-md border border-gray-300 p-2 text-sm text-gray-700">
<p>I want ₩20,000</p>
</div>
</div>
<div className="flex items-start space-x-2">
<div className="h-8 w-8 rounded-full bg-slate-400" />
<div className="w-1/2 rounded-md border border-gray-300 p-2 text-sm text-gray-700">
<p>Hi how much are you selling them for?</p>
</div>
</div>
<div className="flex flex-row-reverse items-start space-x-2 space-x-reverse">
<div className="h-8 w-8 rounded-full bg-slate-400" />
<div className="w-1/2 rounded-md border border-gray-300 p-2 text-sm text-gray-700">
<p>I want ₩20,000</p>
</div>
</div>
<div className="flex items-start space-x-2">
<div className="h-8 w-8 rounded-full bg-slate-400" />
<div className="w-1/2 rounded-md border border-gray-300 p-2 text-sm text-gray-700">
<p>Hi how much are you selling them for?</p>
</div>
</div>
<div className="flex flex-row-reverse items-start space-x-2 space-x-reverse">
<div className="h-8 w-8 rounded-full bg-slate-400" />
<div className="w-1/2 rounded-md border border-gray-300 p-2 text-sm text-gray-700">
<p>I want ₩20,000</p>
</div>
</div>
<div className="flex items-start space-x-2">
<div className="h-8 w-8 rounded-full bg-slate-400" />
<div className="w-1/2 rounded-md border border-gray-300 p-2 text-sm text-gray-700">
<p>Hi how much are you selling them for?</p>
</div>
</div>
<div className="flex flex-row-reverse items-start space-x-2 space-x-reverse">
<div className="h-8 w-8 rounded-full bg-slate-400" />
<div className="w-1/2 rounded-md border border-gray-300 p-2 text-sm text-gray-700">
<p>I want ₩20,000</p>
</div>
</div>
</div>
<div className="fixed inset-x-0 bottom-3 m-0 mx-auto w-full max-w-md">
<div className="relative flex items-center">
<input
type="text"
className="w-full rounded-full border-gray-300 shadow-sm
focus:border-orange-500 focus:outline-none focus:ring-orange-500
"
/>
<div className="absolute inset-y-0 right-0 py-1.5 pr-1.5">
<button className="flex h-full items-center rounded-full bg-orange-500 px-3 text-sm text-white hover:bg-orange-600 focus:ring-2 focus:ring-orange-500 focus:ring-offset-2">
→
</button>
</div>
</div>
</div>
</div>
);
};
export default StreamDetail;
여기서는 비디오를 보면서 채팅칠 수 있게끔 채팅창을 h-[50vh]을 주어 view port에 따라 크기를 조절해 주었습니다. 또한 overflow-y-scroll을 주어 넘어갔을 떄 스크롤로 내려볼 수 있게 스타일링 하였습니다. 그 외에도 비디오의 속성중 aspect-video를 주어 width에 따라적절한 height를 (16 : 9)를 주게끔 반응형으로 설정해 주었습니다. 마지막으로 채팅을 입력할 수 있는 input은 chats에서 복사 해 왔습니다.
또한 여기서 scrollbar가 너무 못생겨서 없애기 위해 global.style에 다음과 같은 내용을 추가해 주었습니다.
styles/global.css
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
-ms-overflow-style: none;
}
::-webkit-scrollbar {
display: none;
}
이 외에도 Tailwind css에서 제공하는 scrollbar-hide plugin을 사용해도 됩니다.
Add Stream UI
pages/streams/create.tsx
import type { NextPage } from "next";
const Create: NextPage = () => {
return (
<div className=" space-y-5 py-10 px-4">
<div>
<label
className="mb-1 block text-sm font-medium text-gray-700"
htmlFor="name"
>
Name
</label>
<div className="relative flex items-center rounded-md shadow-sm">
<input
id="name"
type="email"
className="w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-orange-500 focus:outline-none focus:ring-orange-500"
required
/>
</div>
</div>
<div>
<label
className="mb-1 block text-sm font-medium text-gray-700"
htmlFor="price"
>
Price
</label>
<div className="relative flex items-center rounded-md shadow-sm">
<div className="pointer-events-none absolute left-0 flex items-center justify-center pl-3">
<span className="text-sm text-gray-500">$</span>
</div>
<input
id="price"
className="w-full appearance-none rounded-md border border-gray-300 px-3 py-2 pl-7 placeholder-gray-400 shadow-sm focus:border-orange-500 focus:outline-none focus:ring-orange-500"
type="text"
placeholder="0.00"
/>
<div className="pointer-events-none absolute right-0 flex items-center pr-3">
<span className="text-gray-500">USD</span>
</div>
</div>
</div>
<div>
<label
htmlFor="description"
className="mb-1 block text-sm font-medium text-gray-700"
>
Description
</label>
<textarea
id="description"
className="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500 "
rows={4}
/>
</div>
<button className=" w-full rounded-md border border-transparent bg-orange-500 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-orange-600 focus:outline-none focus:ring-2 focus:ring-orange-500 focus:ring-offset-2 ">
Go live
</button>
</div>
);
};
export default Create;
이는 이전에 item을 등록할 때 만든 페이지와 매우 비슷하기 때문에 거의 다 복붙 해왔습니다.
Layout part - 1
여기서 반응형을 하기에는 너무 버겁고 귀찮아서 그냥 아래와 같이 진행했습니다.
pages/_app.tsx
import "../styles/globals.css";
import type { AppProps } from "next/app";
function MyApp({ Component, pageProps }: AppProps) {
return (
<div className="mx-auto w-full max-w-xl">
<Component {...pageProps} />
</div>
);
}
export default MyApp;
이렇게 청사진이라고 할 수 있는 _app.tsx로 들어가서 max-w-xl로 max width를 지정해 줍니다. 그리고 mx-auto, w-full로 중앙정렬을 해 주면 모든 페이지가 저 div에 감싸져 페이지가 작아지게 됩니다.
Layout part - 2
다음으로는 이제 navBar를 만들어 보도록 하겠습니다. 이제 components폴더를 만들어서 layout을 관리하도록 하겠습니다. 여기서의 핵심은 각각의 폴더의 index.tsx파일은 페이지의 아래에 존재하는 TabBar로 들어가게 만들고, 각각의 sub Page들은 back버튼만 만들어서 이전의 페이지로 돌아가게 만드는 것입니다.
components/layout.tsx
import React, { useCallback } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { cls } from "../libs/utils";
interface LayoutProps {
title?: string;
canGoBack?: boolean;
hasTabBar?: boolean;
children: React.ReactNode;
}
const Layout = ({ title, canGoBack, hasTabBar, children }: LayoutProps) => {
const router = useRouter();
const onClick = useCallback(() => router.back(), [router]);
return (
<div>
<div
className={cls(
!canGoBack ? "justify-center" : "",
"fixed top-0 left-0 flex w-full items-center border-b bg-white px-10 py-3 text-lg font-medium text-gray-700"
)}
>
{canGoBack ? (
<button onClick={onClick}>
<svg
className="h-6 w-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M15 19l-7-7 7-7"
></path>
</svg>
</button>
) : null}
{title ? <span>{title}</span> : null}
</div>
<div className={cls("pt-16", hasTabBar ? "pb-[6rem]" : "")}>{children}</div>
{hasTabBar ? (
<nav className="fixed bottom-0 left-0 flex w-full items-center justify-around border-2 border-t bg-white pb-4 pt-4 text-xs text-gray-800">
<Link href="/">
<a className="flex flex-col items-center space-y-2">
<svg
className="h-6 w-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
></path>
</svg>
<span>홈</span>
</a>
</Link>
<Link href="/community">
<a className="flex flex-col items-center space-y-2">
<svg
className="h-6 w-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"
></path>
</svg>
<span>동네생활</span>
</a>
</Link>
<Link href="/chats">
<a className="flex flex-col items-center space-y-2">
<svg
className="h-6 w-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
></path>
</svg>
<span>채팅</span>
</a>
</Link>
<Link href="/live">
<a className="flex flex-col items-center space-y-2">
<svg
className="h-6 w-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"
></path>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"
></path>
</svg>
<span>라이브</span>
</a>
</Link>
<Link href="/profile">
<a className="flex flex-col items-center space-y-2">
<svg
className="h-6 w-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
></path>
</svg>
<span>나의 계정</span>
</a>
</Link>
</nav>
) : null}
</div>
);
};
export default Layout;
대부분의 페이지가 다음과 같은 형태를 띄게 됩니다. 이외의 페이지에도 동일하게 적용했습니다.
'Web > CloneCoding' 카테고리의 다른 글
[Carrot Market] #5 TAILWIND ReFactoring UI - 2 (0) | 2022.05.02 |
---|---|
[Carrot Market] #5 TAILWIND ReFactoring UI - 1 (0) | 2022.05.02 |
[Carrot Market] #5 TAILWIND CLONING UI - 2 (0) | 2022.05.01 |
[Carrot Market] #5 TAILWIND CLONING UI - 1 (0) | 2022.05.01 |
[Carrot-Market] #4 TAILWIND - 2 (0) | 2022.04.30 |