Chats UI
이제 어떠한 상대방과 채팅을 할떄의 UI를 구축해 보도록 하겠습니다.
pages/chats/index.tsx
import type { NextPage } from "next";
const Write: NextPage = () => {
return (
<div className="divide-y-[1px] py-10">
{[...Array(5)].map((_, i) => (
<div
key={i}
className="space-x-300 mb-3 flex items-center space-x-2 py-2 px-3 "
>
<div className="h-12 w-12 rounded-full bg-slate-300" />
<div>
<p className="text-gray-700">Steve Jebs</p>
<p className="text-sm text-gray-500">
See you dtomorrow in the corner at 2pm
</p>
</div>
</div>
))}
</div>
);
};
export default Write;
여기서 중요한 점은 divide modifier입니다. 만약 border-b-1을 하게되면 맨 마지막 component에도 border가 생기는데 우리는 이를 없애주고 싶다면 상위 컴포넌트에 last:border-b-0을 해주면 됩니다. 하지만 Tailwind에서는 이를 위해서 utility modifier를 제공합니다. 바로 divide-y-[number], divide-x-[number]입니다. 이는 자동으로 다음 sibiling이 없으면 border를 그어주지 않습니다. 이는 기본 css를 조합해서 만든 것입니다.
즉 Divide Width Utility는 엘리먼트 사이의 border width를 제어하기 위해 사용하면 좋습니다.
Chats Detail UI
구체적인 채팅 상세페이지를 작성하도록 하겠습니다.
pages/chats/[id].tsx
import type { NextPage } from "next";
const ChatDetail: NextPage = () => {
return (
<div className="space-y-4 py-10 px-4">
<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 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="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 ChatDetail;
이 Chat Detail UI에서의 핵심은 position을 잘 사용하는 것과 flex-row-reverse를 하면 flex box가 뒤집어 지는데, 이 떄 space-x-[number]를 하고 싶으면 space-x-reverse로 css의 변수를 미리 바꾸어 주어야 정상 작동한다.
또한 채팅을 보내는 input을 만들 떄, max-w-md,w-full, mx-auto inset-x-0으로 정확히 중앙에 최대 넓이를 정해서 배치하는 것도 좋은 방법이였다고 생각합니다.
Profile UI
이제 사용자의 Profile페이지를 만들어 보도록 하겠습니다.
pages/profile/index.tsx
import type { NextPage } from "next";
const Profile: NextPage = () => {
return (
<div className="py-10 px-4">
<div className="flex items-center space-x-3">
<div className="h-16 w-16 rounded-full bg-slate-500" />
<div className="flex flex-col">
<span className="font-medium text-gray-800">Steve Jebs</span>
<span className="font-sm text-gray-700">Edit profile →</span>
</div>
</div>
<div className="mt-10 flex justify-around">
<div className="flex flex-col items-center">
<div className="aligned-center flex h-14 w-14 items-center justify-center rounded-full bg-orange-500 text-white">
<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 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"
></path>
</svg>
</div>
<span className="mt-2 text-sm font-medium text-gray-700">판매내역</span>
</div>
<div>
<div className="aligned-center flex h-14 w-14 items-center justify-center rounded-full bg-orange-500 text-white">
<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 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"
></path>
</svg>
</div>
<span className="mt-2 text-sm font-medium text-gray-700">구매내역</span>
</div>
<div>
<div className="aligned-center flex h-14 w-14 items-center justify-center rounded-full bg-orange-500 text-white">
<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="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
></path>
</svg>
</div>
<span className="mt-2 text-sm font-medium text-gray-700">관심목록</span>
</div>
</div>
<div className="mt-12">
<div className="flex items-center space-x-4">
<div className="h-12 w-12 rounded-full bg-slate-400" />
<div>
<h4 className="text-sm font-bold text-gray-800">니꼬</h4>
<div className="flex items-center">
<svg
className="h-5 w-5 text-yellow-400"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</svg>
<svg
className="h-5 w-5 text-yellow-400"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</svg>
<svg
className="h-5 w-5 text-yellow-400"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</svg>
<svg
className="h-5 w-5 text-yellow-400"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</svg>
<svg
className="h-5 w-5 text-gray-400"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</svg>
</div>
</div>
</div>
<div className="mt-4 text-sm text-gray-600">
<p>
Normally, both your asses would be dead as fucking fried chicken, but you
happen to pull this shit while I'm in a transitional period so I
don't wanna kill you, I wanna help you. But I can't give you
this case, it don't belong to me. Besides, I've already been
through too much shit this morning over this case to hand it over to your
dumb ass.
</p>
</div>
</div>
</div>
);
};
export default Profile;
여기서 이제 생각해야 할 점은 판매내역, 구매내열, 관심목록을 보여줄 페이지 UI를 구현해야 한다는 점입니다. 여기서는 Home Screen UI를 재사용해 보도록 하겠습니다. 바로 아래에서 구현합니다.
Bought, Loved and Sold UI
앞서 말한대로 Home screen UI를 재사용 하도록 하겠습니다.
pages/profile/bought.tsx
import type { NextPage } from "next";
const Bought: NextPage = () => (
<div className="p flex flex-col space-y-5 py-10">
{[...Array(10)].map((_, i) => (
<div
key={i}
className="flex cursor-pointer justify-between border-b px-4 pb-4"
>
<div className="flex space-x-4">
<div className="h-20 w-20 rounded-md bg-gray-400" />
<div className="flex flex-col pt-2">
<h3 className="text-sm font-medium text-gray-900">New iPhone 14</h3>
<span className="text-xs text-gray-500">Black</span>
<span className="font-md mt-1 text-gray-900">$95</span>
</div>
</div>
<div className="flex items-end justify-end space-x-2">
<div className="flex items-center space-x-0.5 text-sm text-gray-600">
<svg
className="h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
></path>
</svg>
<span>1</span>
</div>
<div className="flex items-center space-x-0.5 text-sm text-gray-600">
<svg
className="h-4 w-4"
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>1</span>
</div>
</div>
</div>
))}
</div>
);
export default Bought;
pages/profile/sold.tsx
import type { NextPage } from "next";
const Sold: NextPage = () => (
<div className="p flex flex-col space-y-5 py-10">
{[...Array(10)].map((_, i) => (
<div
key={i}
className="flex cursor-pointer justify-between border-b px-4 pb-4"
>
<div className="flex space-x-4">
<div className="h-20 w-20 rounded-md bg-gray-400" />
<div className="flex flex-col pt-2">
<h3 className="text-sm font-medium text-gray-900">New iPhone 14</h3>
<span className="text-xs text-gray-500">Black</span>
<span className="font-md mt-1 text-gray-900">$95</span>
</div>
</div>
<div className="flex items-end justify-end space-x-2">
<div className="flex items-center space-x-0.5 text-sm text-gray-600">
<svg
className="h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
></path>
</svg>
<span>1</span>
</div>
<div className="flex items-center space-x-0.5 text-sm text-gray-600">
<svg
className="h-4 w-4"
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>1</span>
</div>
</div>
</div>
))}
</div>
);
export default Sold;
pages/profile/loved.tsx
import type { NextPage } from "next";
const Loved: NextPage = () => (
<div className="p flex flex-col space-y-5 py-10">
{[...Array(10)].map((_, i) => (
<div
key={i}
className="flex cursor-pointer justify-between border-b px-4 pb-4"
>
<div className="flex space-x-4">
<div className="h-20 w-20 rounded-md bg-gray-400" />
<div className="flex flex-col pt-2">
<h3 className="text-sm font-medium text-gray-900">New iPhone 14</h3>
<span className="text-xs text-gray-500">Black</span>
<span className="font-md mt-1 text-gray-900">$95</span>
</div>
</div>
<div className="flex items-end justify-end space-x-2">
<div className="flex items-center space-x-0.5 text-sm text-gray-600">
<svg
className="h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
></path>
</svg>
<span>1</span>
</div>
<div className="flex items-center space-x-0.5 text-sm text-gray-600">
<svg
className="h-4 w-4"
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>1</span>
</div>
</div>
</div>
))}
</div>
);
export default Loved;
Edit Profile UI
pages/profile/edit.tsx
import type { NextPage } from "next";
const EditProfile: NextPage = () => {
return (
<div className="space-y-4 py-10 px-4">
<div className="flex items-center space-x-3">
<div className="h-14 w-14 rounded-full bg-slate-400" />
<label
htmlFor="picture"
className="cursor-pointer rounded-md border border-gray-300 py-2 px-3 text-sm font-medium text-gray-700 shadow-sm focus:ring-2 focus:ring-orange-500 focus:ring-offset-2"
>
Change photo
<input
id="picture"
type="file"
className="hidden"
accept="image/*"
></input>
</label>
</div>
<div className="space-y-1">
<label htmlFor="email" className="text-sm font-medium text-gray-700">
Email Adress
</label>
<input
id="email"
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 className="space-y-1">
<label htmlFor="phone" className="text-sm font-medium text-gray-700">
Phone number
</label>
<div className="flex rounded-md shadow-sm ">
<span className="flex select-none items-center justify-center rounded-l-md border border-r-0 border-gray-300 bg-gray-50 px-3 text-sm text-gray-500">
+82
</span>
<input
id="input"
type="number"
className="w-full appearance-none rounded-r-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>
<button
className="mt-6 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"
>
Update profile
</button>
</div>
);
};
export default EditProfile;
여기서는 사진, 이메일, 전화번호를 변경할 수 있는 페이지를 만들어 주었습니다. input type="file"은 기존의 items upload페이지와 비슷하게 디자인 했습니다.
'Web > CloneCoding' 카테고리의 다른 글
[Carrot Market] #5 TAILWIND ReFactoring UI - 1 (0) | 2022.05.02 |
---|---|
[Carrot Market] #5 TAILWIND CLONING UI - 3 (0) | 2022.05.01 |
[Carrot Market] #5 TAILWIND CLONING UI - 1 (0) | 2022.05.01 |
[Carrot-Market] #4 TAILWIND - 2 (0) | 2022.04.30 |
[Carrot-Market] #4 TAILWIND - 1 (0) | 2022.04.30 |