feat: add components
This commit is contained in:
parent
c7f97a3dc2
commit
627733ceb9
32
components/Feed.js
Normal file
32
components/Feed.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
const Feed = ({
|
||||||
|
media,
|
||||||
|
type,
|
||||||
|
setOverlayContent,
|
||||||
|
currentIndex,
|
||||||
|
limitPerPage,
|
||||||
|
}) => (
|
||||||
|
<main className="grid grid-cols-3 gap-1">
|
||||||
|
{media
|
||||||
|
.slice(currentIndex, limitPerPage)
|
||||||
|
.map(({ id, url, previewPath, embedPath = url }) => (
|
||||||
|
<a
|
||||||
|
key={id}
|
||||||
|
href={url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferer"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
setOverlayContent({
|
||||||
|
url: embedPath,
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img alt={url} src={previewPath} className={`aspect-${type}`} />
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Feed;
|
49
components/Footer.js
Normal file
49
components/Footer.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import { YEAR_TO_BUMP } from "../constants";
|
||||||
|
|
||||||
|
const Footer = ({ license, links, repo, commitID }) => (
|
||||||
|
<footer className="flex items-center justify-between flex-wrap py-4 text-center mt-10">
|
||||||
|
<div className="w-10/12 md:w-7/12 mx-auto font-semibold">
|
||||||
|
<div className="text-sm mb-10">
|
||||||
|
{links.map(({ url, label }) => (
|
||||||
|
<Link key={url + label} href={url} className="mx-4">
|
||||||
|
{label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
<Link href={repo} className="mx-4">
|
||||||
|
Source Code
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-sm font-normal text-neutral-400">
|
||||||
|
<p>
|
||||||
|
© {YEAR_TO_BUMP}{" "}
|
||||||
|
<Link
|
||||||
|
className="text-neutral-600"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferer"
|
||||||
|
href="https://github.com/faultables"
|
||||||
|
>
|
||||||
|
faultables
|
||||||
|
</Link>{" "}
|
||||||
|
• All media is licensed under{" "}
|
||||||
|
<Link className="underline" href={license.url}>
|
||||||
|
{license.name}
|
||||||
|
</Link>{" "}
|
||||||
|
unless stated otherwise •{" "}
|
||||||
|
<Link
|
||||||
|
className="text-neutral-600"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferer"
|
||||||
|
href={`${repo}/commit/${commitID}`}
|
||||||
|
>
|
||||||
|
{commitID}
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Footer;
|
21
components/Navbar.js
Normal file
21
components/Navbar.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
const Navbar = ({ name, logo }) => (
|
||||||
|
<nav className="flex py-2 border-b">
|
||||||
|
<div className="w-3/12 md:w-7/12 md:mx-auto">
|
||||||
|
<div className="md:w-2/12">
|
||||||
|
<Link href="/">
|
||||||
|
{logo ? (
|
||||||
|
<img alt={name} src={logo} className="w-full" />
|
||||||
|
) : (
|
||||||
|
<h1 className="font-bold leading-relaxed tracking-tight text-2xl text-neutral-800">
|
||||||
|
{name}
|
||||||
|
</h1>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Navbar;
|
57
components/Overlay.js
Normal file
57
components/Overlay.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { MEDIA_TYPE } from "../constants";
|
||||||
|
|
||||||
|
const OverlayContent = ({ overlayContent }) => {
|
||||||
|
if (overlayContent?.type === MEDIA_TYPE.PHOTOS) {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
alt={overlayContent?.url}
|
||||||
|
src={overlayContent?.url}
|
||||||
|
className="z-20 cursor-default"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (overlayContent?.type === MEDIA_TYPE.VIDEOS) {
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
width="50%"
|
||||||
|
height="50%"
|
||||||
|
allow="fullscreen"
|
||||||
|
sandbox="allow-same-origin allow-scripts allow-popups"
|
||||||
|
src={overlayContent?.url}
|
||||||
|
></iframe>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Overlay = ({ overlayContent, closeOverlay }) => {
|
||||||
|
const isOverlayOpen = overlayContent?.type !== undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={closeOverlay}
|
||||||
|
className={`${
|
||||||
|
isOverlayOpen
|
||||||
|
? "bg-neutral-800/95 fixed w-screen h-screen left-0 top-0 cursor-pointer"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isOverlayOpen ? (
|
||||||
|
<p
|
||||||
|
onClick={closeOverlay}
|
||||||
|
className="fixed right-0 bottom-0 m-5 text-white rounded text-sm"
|
||||||
|
>
|
||||||
|
Click anywhere or press "Escape" to close
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`flex justify-center items-center ${
|
||||||
|
isOverlayOpen ? "h-screen w-screen" : "h-0 w-0"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<OverlayContent overlayContent={overlayContent} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Overlay;
|
46
components/Profile.js
Normal file
46
components/Profile.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
const Profile = ({ profile, totalPosts }) => (
|
||||||
|
<div className="flex flex-warp">
|
||||||
|
<div className="lg:w-4/12 w-3/12 md:py-10 lg:px-20 md:px-10">
|
||||||
|
<img
|
||||||
|
alt={profile.display_name}
|
||||||
|
src={profile.avatar}
|
||||||
|
className="w-100 rounded-full mx-auto border border-gray-200 p-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="lg:w-9/12 md:w-10/12 px-10 mb-5 md:p-5">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<h2 className="text-2xl font-semibold">{profile.username}</h2>
|
||||||
|
<div className="ml-4">
|
||||||
|
<a
|
||||||
|
className="rounded-md bg-gray-100 px-5 font-semibold py-2 ml-2 text-sm leading-relaxed"
|
||||||
|
href={profile.follow_url}
|
||||||
|
>
|
||||||
|
Follow
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
className="rounded-md bg-gray-100 px-5 font-semibold py-2 ml-2 text-sm leading-relaxed"
|
||||||
|
href={profile.message_url}
|
||||||
|
>
|
||||||
|
Message
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="my-5 font-bold text-sm">
|
||||||
|
<p>{totalPosts} posts</p>
|
||||||
|
</div>
|
||||||
|
<p className="font-bold mb-1">{profile.display_name}</p>
|
||||||
|
<p>{profile.about}</p>
|
||||||
|
<p className="leading-loose font-semibold text-blue-900">
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferer"
|
||||||
|
href={`https://${profile.link}`}
|
||||||
|
>
|
||||||
|
{profile.link}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Profile;
|
27
components/Tab.js
Normal file
27
components/Tab.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
const isActiveTab = (currentTab, activeTab) =>
|
||||||
|
currentTab === activeTab ? "border-t" : "";
|
||||||
|
|
||||||
|
const Tab = ({ activeTab, setActiveTab }) => (
|
||||||
|
<div className="flex items-center justify-center text-center gap-5 border-t mb-4">
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab("photos")}
|
||||||
|
className={`uppercase tracking-tight font-semibold text-sm border-black pt-3 px-3 ${isActiveTab(
|
||||||
|
"photos",
|
||||||
|
activeTab
|
||||||
|
)}`}
|
||||||
|
>
|
||||||
|
Photos
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab("videos")}
|
||||||
|
className={`uppercase tracking-tight font-semibold text-sm pt-3 px-3 border-black ${isActiveTab(
|
||||||
|
"videos",
|
||||||
|
activeTab
|
||||||
|
)}`}
|
||||||
|
>
|
||||||
|
Videos
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Tab;
|
Loading…
Reference in New Issue
Block a user