- FIX KRITIS: apps/dimentorin/src/index.css @theme nyimpang dari Figma (67 token beda: primary-500 #1a8ce6 vs #23a1eb, semua warna success/info/warning/danger + skala typography). Disamakan dgn libs/ui (source of truth Figma). - Hapus hardcoded warna non-token di apps: blue-400, gray-5/6/700, amber/emerald/red-100, green-200/500 -> token primary/neutral/warning/success/danger - libs/ui: gray-* -> neutral-*, blue-* -> primary-*, #1a8ce6 -> #23a1eb, bg-[#F0F8FF] -> primary-50, SVG fill hex -> fill-* token classes - Typography: text-[27/26/22/11px] menyimpang dari skala Figma -> text-[29/23/19/12px] - Radius: rounded-4xl (32px, tidak ada di Figma) -> rounded-3xl (24px)
141 lines
4.9 KiB
TypeScript
141 lines
4.9 KiB
TypeScript
import { ReactElement, useRef, useState } from "react";
|
|
import { ArticleCard } from "./_components/card/article";
|
|
import { cn, For, Show } from "@imphnen-frontend-service/utils";
|
|
import { Button } from "@imphnen-frontend-service/ui/atoms";
|
|
import { motion, useInView, Variants } from "framer-motion";
|
|
import { useGetArticleCategories, useGetArticles } from "@imphnen-frontend-service/service";
|
|
|
|
export default function Components(): ReactElement {
|
|
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)
|
|
|
|
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>
|
|
|
|
<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>
|
|
|
|
<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>
|
|
</div>
|
|
</div>
|
|
</Show>
|
|
</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}>
|
|
<For data={tabs}>
|
|
{(category) => (
|
|
<Button
|
|
key={category}
|
|
type="button"
|
|
variant="text"
|
|
onClick={() => setActiveTab(category)}
|
|
className={cn(
|
|
"relative text-[10px] text-neutral-400 font-medium px-3 py-2 rounded-3xl md:text-xs md:font-semibold xl:text-[15px]",
|
|
"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>
|
|
|
|
<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>
|
|
</div>
|
|
</motion.section>
|
|
</main>
|
|
)
|
|
}
|