2025-07-28 20:45:29 +07:00
|
|
|
import { ReactElement, useRef, useState } from "react";
|
|
|
|
|
import { ArticleCard } from "./_components/card/article";
|
2026-08-04 19:31:29 +07:00
|
|
|
import { cn, For, Show } from "@imphnen-frontend-service/utils";
|
2025-07-28 20:45:29 +07:00
|
|
|
import { Button } from "@imphnen-frontend-service/ui/atoms";
|
|
|
|
|
import { motion, useInView, Variants } from "framer-motion";
|
2026-08-04 19:31:29 +07:00
|
|
|
import { useGetArticleCategories, useGetArticles } from "@imphnen-frontend-service/service";
|
2025-07-28 20:45:29 +07:00
|
|
|
|
|
|
|
|
export default function Components(): ReactElement {
|
2026-08-04 19:31:29 +07:00
|
|
|
const [activeTab, setActiveTab] = useState<string>("Semua")
|
|
|
|
|
const { data: articles = [], isLoading } = useGetArticles({ per_page: 20 })
|
|
|
|
|
const { data: categories = [] } = useGetArticleCategories()
|
|
|
|
|
|
|
|
|
|
const tabs = ["Semua", ...categories]
|
|
|
|
|
|
|
|
|
|
const featured = articles[0]
|
|
|
|
|
const recent = articles.slice(1, 4)
|
|
|
|
|
const filtered = activeTab === "Semua"
|
|
|
|
|
? articles
|
|
|
|
|
: articles.filter((a) => a.category === activeTab)
|
2025-07-28 20:45:29 +07:00
|
|
|
|
|
|
|
|
const ref = useRef(null)
|
|
|
|
|
const isInView = useInView(ref, { once: true, amount: 0.2 })
|
|
|
|
|
|
|
|
|
|
const containerVariants: Variants = {
|
|
|
|
|
hidden: { opacity: 0 },
|
|
|
|
|
visible: {
|
|
|
|
|
opacity: 1,
|
|
|
|
|
transition: {
|
|
|
|
|
staggerChildren: 0.1,
|
|
|
|
|
delayChildren: 0.2,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const childVariants: Variants = {
|
|
|
|
|
hidden: { opacity: 0, y: 20 },
|
|
|
|
|
visible: {
|
|
|
|
|
opacity: 1,
|
|
|
|
|
y: 0,
|
|
|
|
|
transition: {
|
|
|
|
|
duration: 0.4,
|
|
|
|
|
ease: [0.25, 0.46, 0.45, 0.94],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<main ref={ref}>
|
|
|
|
|
<motion.section
|
|
|
|
|
className="w-full p-8 md:py-14 md:px-[60px] lg:py-16 lg:px-20"
|
|
|
|
|
variants={containerVariants}
|
|
|
|
|
initial="hidden"
|
|
|
|
|
animate={isInView ? "visible" : "hidden"}
|
|
|
|
|
>
|
|
|
|
|
<div className="max-w-7xl mx-auto mb-8 md:mb-20">
|
|
|
|
|
<motion.h1
|
|
|
|
|
className="text-[23px] font-semibold text-neutral-800 mb-9 md:text-[29px] xl:text-[46px] xl:font-bold"
|
|
|
|
|
variants={childVariants}
|
|
|
|
|
>
|
|
|
|
|
Featuring Articles
|
|
|
|
|
</motion.h1>
|
|
|
|
|
|
2026-08-04 19:31:29 +07:00
|
|
|
<Show
|
|
|
|
|
condition={!isLoading}
|
|
|
|
|
fallback={<p className="text-neutral-500">Memuat artikel...</p>}
|
|
|
|
|
>
|
|
|
|
|
<div className="grid gap-12 xl:grid-cols-2">
|
|
|
|
|
<motion.div variants={childVariants}>
|
|
|
|
|
<ArticleCard variant="featured" article={featured} />
|
|
|
|
|
</motion.div>
|
2025-07-28 20:45:29 +07:00
|
|
|
|
2026-08-04 19:31:29 +07:00
|
|
|
<div>
|
|
|
|
|
<motion.h1
|
|
|
|
|
className="text-[19px] text-neutral-800 font-semibold mb-10 md:text-[29px] xl:text-[37px] xl:mb-12"
|
|
|
|
|
variants={childVariants}
|
|
|
|
|
>
|
|
|
|
|
Recent Articles
|
|
|
|
|
</motion.h1>
|
|
|
|
|
<div className="grid gap-10">
|
|
|
|
|
<For data={recent}>
|
|
|
|
|
{(article, index) => (
|
|
|
|
|
<motion.div key={article.id ?? index} variants={childVariants}>
|
|
|
|
|
<ArticleCard variant="recent" article={article} />
|
|
|
|
|
</motion.div>
|
|
|
|
|
)}
|
|
|
|
|
</For>
|
|
|
|
|
</div>
|
2025-07-28 20:45:29 +07:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-08-04 19:31:29 +07:00
|
|
|
</Show>
|
2025-07-28 20:45:29 +07:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="max-w-7xl mx-auto">
|
|
|
|
|
<motion.h1
|
|
|
|
|
className="text-[19px] text-neutral-800 font-semibold mb-4 md:text-[23px] xl:text-[29px]"
|
|
|
|
|
variants={childVariants}
|
|
|
|
|
>
|
|
|
|
|
Category
|
|
|
|
|
</motion.h1>
|
|
|
|
|
<div className="scrollbar-hide mb-8 w-full overflow-auto mx-auto">
|
|
|
|
|
<motion.div className="min-w-max w-auto flex gap-x-3" variants={childVariants}>
|
2026-08-04 19:31:29 +07:00
|
|
|
<For data={tabs}>
|
2025-07-28 20:45:29 +07:00
|
|
|
{(category) => (
|
|
|
|
|
<Button
|
|
|
|
|
key={category}
|
|
|
|
|
type="button"
|
|
|
|
|
variant="text"
|
|
|
|
|
onClick={() => setActiveTab(category)}
|
|
|
|
|
className={cn(
|
2026-08-05 19:28:02 +07:00
|
|
|
"relative text-[10px] text-neutral-400 font-medium px-3 py-2 rounded-3xl md:text-xs md:font-semibold xl:text-[15px]",
|
2025-07-28 20:45:29 +07:00
|
|
|
"before:w-0 before:absolute before:h-0.5 before:mx-auto before:inset-x-0 before:bg-primary-500 hover:before:w-full before:bottom-0 before:left-0 before:transition-all before:duration-300",
|
|
|
|
|
activeTab === category && "text-primary-500 before:w-full"
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{category}
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</For>
|
|
|
|
|
</motion.div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-08-04 19:31:29 +07:00
|
|
|
<Show
|
|
|
|
|
condition={filtered.length > 0}
|
|
|
|
|
fallback={<p className="text-neutral-500 text-center py-10">Belum ada artikel di kategori ini.</p>}
|
|
|
|
|
>
|
|
|
|
|
<div className="grid gap-8 xl:grid-cols-3">
|
|
|
|
|
<For data={filtered}>
|
|
|
|
|
{(article, index) => (
|
|
|
|
|
<motion.div key={article.id ?? index} variants={childVariants}>
|
|
|
|
|
<ArticleCard article={article} />
|
|
|
|
|
</motion.div>
|
|
|
|
|
)}
|
|
|
|
|
</For>
|
|
|
|
|
</div>
|
|
|
|
|
</Show>
|
2025-07-28 20:45:29 +07:00
|
|
|
</div>
|
|
|
|
|
</motion.section>
|
|
|
|
|
</main>
|
|
|
|
|
)
|
|
|
|
|
}
|