feat: refactor news components and add news detail layout

This commit is contained in:
Ardeman 2025-02-20 07:01:36 +08:00
parent f17627bdf2
commit e73949295d
12 changed files with 179 additions and 209 deletions

View File

@ -2,7 +2,7 @@ import { Link } from 'react-router'
import { APP } from '~/data/meta' import { APP } from '~/data/meta'
export default function Banner() { export const Banner = () => {
return ( return (
<div className="min-h-[65px] bg-amber-400 sm:mx-10"> <div className="min-h-[65px] bg-amber-400 sm:mx-10">
<div className="relative"> <div className="relative">

View File

@ -1,10 +1,5 @@
import { cva, type VariantProps } from 'class-variance-authority' import { cva, type VariantProps } from 'class-variance-authority'
import type { import type { ReactNode, ElementType, ComponentPropsWithoutRef } from 'react'
ButtonHTMLAttributes,
HTMLAttributes,
MouseEventHandler,
ReactNode,
} from 'react'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
const buttonVariants = cva( const buttonVariants = cva(
@ -16,14 +11,6 @@ const buttonVariants = cva(
newsPrimaryOutline: 'border-[3px] border-white text-white text-lg', newsPrimaryOutline: 'border-[3px] border-white text-white text-lg',
newsSecondary: 'border-[3px] border-[#2E2F7C] text-[#2E2F7C] text-lg', newsSecondary: 'border-[3px] border-[#2E2F7C] text-[#2E2F7C] text-lg',
icon: '', icon: '',
// destructive:
// 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
// outline:
// 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
// secondary:
// 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
// ghost: 'hover:bg-accent hover:text-accent-foreground',
// link: 'text-primary underline-offset-4 hover:underline',
}, },
size: { size: {
default: 'h-[50px] w-[150px]', default: 'h-[50px] w-[150px]',
@ -40,34 +27,38 @@ const buttonVariants = cva(
}, },
) )
type TProperties = { type ButtonBaseProperties = {
type?: ButtonHTMLAttributes<HTMLButtonElement>['type']
onClick?: MouseEventHandler<HTMLButtonElement>
className?: HTMLAttributes<HTMLButtonElement>['className']
disabled?: boolean
children: ReactNode children: ReactNode
variant?: VariantProps<typeof buttonVariants>['variant'] variant?: VariantProps<typeof buttonVariants>['variant']
size?: VariantProps<typeof buttonVariants>['size'] size?: VariantProps<typeof buttonVariants>['size']
className?: string
} }
export const Button = (properties: TProperties) => { type PolymorphicReference<C extends ElementType> =
const { ComponentPropsWithoutRef<C>['ref']
type = 'button',
onClick, type ButtonProperties<C extends ElementType> = ButtonBaseProperties & {
className, as?: C
disabled, ref?: PolymorphicReference<C>
} & Omit<ComponentPropsWithoutRef<C>, keyof ButtonBaseProperties>
export const Button = <C extends ElementType = 'button'>({
as,
children, children,
variant, variant,
size, size,
} = properties className,
...properties
}: ButtonProperties<C>) => {
const Component = as || 'button'
const classes = twMerge(buttonVariants({ variant, size, className }))
return ( return (
<button <Component
type={type} className={classes}
onClick={onClick} {...properties}
className={twMerge(buttonVariants({ variant, size, className }))}
disabled={disabled}
> >
{children} {children}
</button> </Component>
) )
} }

View File

@ -1,3 +1,4 @@
import { Link } from 'react-router'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { CarouselNextIcon } from '~/components/icons/carousel-next' import { CarouselNextIcon } from '~/components/icons/carousel-next'
@ -9,74 +10,6 @@ import { Button } from './button'
export const Carousel = (properties: TNews) => { export const Carousel = (properties: TNews) => {
const { title, description, items, type } = properties const { title, description, items, type } = properties
return ( return (
<>
{/* <div >
<div className="mb-[30px] flex items-center justify-between border-b border-black pb-[30px]">
<div className="grid">
<h2 className="text-4xl font-extrabold text-[#2E2F7C]">{title}</h2>
<p className="text-2xl font-light text-[#777777] italic">
{description}
</p>
</div>
<div className="flex gap-2.5">
<CarouselPreviousIcon
color="#DCDCDC"
className="cursor-pointer"
/>
<CarouselNextIcon
color="#2E2F7C"
className="cursor-pointer"
/>
</div>
</div>
<div
className={twMerge(
'grid gap-x-8',
type === 'hero' ? 'grid-cols-1' : 'grid-cols-3',
)}
>
{items.map(({ image, title, content }, index) => (
<div
key={index}
className={twMerge(
'grid gap-x-8',
type === 'hero' ? 'grid-cols-3' : '',
)}
>
<img
className={twMerge(
'w-full object-cover',
type === 'hero'
? 'col-span-2 aspect-[174/100]'
: 'aspect-[5/4]',
)}
src={image}
alt={title}
/>
<div
className={twMerge(
'flex flex-col justify-between',
type === 'hero' ? 'gap-7' : 'gap-4',
)}
>
<div>
<h3
className={twMerge(
'font-bold',
type === 'hero' ? 'text-4xl' : 'text-2xl',
)}
>
{title}
</h3>
<p className="text-xl text-[#777777]">{content}</p>
</div>
<Button size="block">View More</Button>
</div>
</div>
))}
</div>
</div> */}
<div className=""> <div className="">
<div className="mt-3 mb-3 flex items-center justify-between border-b border-black pb-3 sm:mb-[30px] sm:pb-[30px]"> <div className="mt-3 mb-3 flex items-center justify-between border-b border-black pb-3 sm:mb-[30px] sm:pb-[30px]">
<div className="grid"> <div className="grid">
@ -109,7 +42,7 @@ export const Carousel = (properties: TNews) => {
type === 'hero' ? 'grid-cols-1' : 'sm:grid-cols-3', type === 'hero' ? 'grid-cols-1' : 'sm:grid-cols-3',
)} )}
> >
{items.map(({ image, title, content, tag }, index) => ( {items.map(({ featured, title, content, tag, slug }, index) => (
<div <div
key={index} key={index}
className={twMerge( className={twMerge(
@ -124,7 +57,7 @@ export const Carousel = (properties: TNews) => {
? 'col-span-2 aspect-[174/100]' ? 'col-span-2 aspect-[174/100]'
: 'aspect-[5/4] rounded-md', : 'aspect-[5/4] rounded-md',
)} )}
src={image} src={featured}
alt={title} alt={title}
/> />
<div <div
@ -156,12 +89,17 @@ export const Carousel = (properties: TNews) => {
{content} {content}
</p> </p>
</div> </div>
<Button size="block">View More</Button> <Button
size="block"
as={Link}
to={slug}
>
View More
</Button>
</div> </div>
</div> </div>
))} ))}
</div> </div>
</div> </div>
</>
) )
} }

View File

@ -1,7 +1,7 @@
import { Button } from '~/components/ui/button' import { Button } from '~/components/ui/button'
import { APP } from '~/data/meta' import { APP } from '~/data/meta'
export const UiNewsLetter = () => { export const Newsletter = () => {
return ( return (
<> <>
<div className="relative col-span-2 my-5 grid max-h-[400px] gap-y-6 bg-[#2E2F7C] p-5 px-10 text-white sm:grid-cols-2 sm:px-10"> <div className="relative col-span-2 my-5 grid max-h-[400px] gap-y-6 bg-[#2E2F7C] p-5 px-10 text-white sm:grid-cols-2 sm:px-10">

View File

@ -0,0 +1,11 @@
import type { TNewsDetail } from '~/types/news'
export const CONTENT: TNewsDetail = {
title: 'Hotman Paris Membuka Perpustakaan di tengah Diskotik',
content:
'Pengacara Kondang, Hotman Paris Hutapea, membuka sebuah perpustakaan baru di dalam diskotik nya yang berlokasi di daerah Jakarta Pusat, Hotman berkata Perpustakaan ini dibuka dengan harapan untuk meningkatkan gairah membaca masyarakat Indonesia, namun sayangnya..',
featured: '/images/news-1.jpg',
slug: 'hotman-paris-membuka-perpustakaan-di-tengah-diskotik',
author: 'John Doe',
date: new Date(),
}

View File

@ -0,0 +1,17 @@
import { Card } from '~/components/ui/card'
import { CONTENT } from './data'
export const NewsDetailPage = () => {
const { title } = CONTENT
return (
<div className="relative">
<Card>
<h2 className="text-2xl font-extrabold text-[#2E2F7C] sm:text-4xl">
{title}
</h2>
News Detail
</Card>
</div>
)
}

View File

@ -9,7 +9,8 @@ export const SPOTLIGHT: TNews = {
title: 'Hotman Paris Membuka Perpustakaan di tengah Diskotik', title: 'Hotman Paris Membuka Perpustakaan di tengah Diskotik',
content: content:
'Pengacara Kondang, Hotman Paris Hutapea, membuka sebuah perpustakaan baru di dalam diskotik nya yang berlokasi di daerah Jakarta Pusat, Hotman berkata Perpustakaan ini dibuka dengan harapan untuk meningkatkan gairah membaca masyarakat Indonesia, namun sayangnya..', 'Pengacara Kondang, Hotman Paris Hutapea, membuka sebuah perpustakaan baru di dalam diskotik nya yang berlokasi di daerah Jakarta Pusat, Hotman berkata Perpustakaan ini dibuka dengan harapan untuk meningkatkan gairah membaca masyarakat Indonesia, namun sayangnya..',
image: '/images/news-1.jpg', featured: '/images/news-1.jpg',
slug: 'hotman-paris-membuka-perpustakaan-di-tengah-diskotik',
}, },
], ],
} }
@ -23,22 +24,25 @@ export const BERITA: TNews = {
title: 'Travelling as a way of self-discovery and progress', title: 'Travelling as a way of self-discovery and progress',
content: content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
image: '/images/news-2.jpg', featured: '/images/news-2.jpg',
tag: ['Hukum Property'], tag: ['Hukum Property'],
slug: 'travelling-as-a-way-of-self-discovery-and-progress',
}, },
{ {
title: 'How does writing influence your personal brand?', title: 'How does writing influence your personal brand?',
content: content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
image: '/images/news-3.jpg', featured: '/images/news-3.jpg',
tag: ['Hukum'], tag: ['Hukum'],
slug: 'how-does-writing-influence-your-personal-brand',
}, },
{ {
title: 'Helping a local business reinvent itself', title: 'Helping a local business reinvent itself',
content: content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
image: '/images/news-4.jpg', featured: '/images/news-4.jpg',
tag: ['Hukum Property', 'PREMIUM CONTENT'], tag: ['Hukum Property', 'PREMIUM CONTENT'],
slug: 'helping-a-local-business-reinvent-itself',
}, },
], ],
} }
@ -52,22 +56,25 @@ export const KAJIAN: TNews = {
title: 'Travelling as a way of self-discovery and progress', title: 'Travelling as a way of self-discovery and progress',
content: content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
image: '/images/news-2.jpg', featured: '/images/news-2.jpg',
tag: ['Hukum Property', 'PREMIUM CONTENT'], tag: ['Hukum Property', 'PREMIUM CONTENT'],
slug: 'travelling-as-a-way-of-self-discovery-and-progress',
}, },
{ {
title: 'How does writing influence your personal brand?', title: 'How does writing influence your personal brand?',
content: content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
image: '/images/news-3.jpg', featured: '/images/news-3.jpg',
tag: ['Hukum Property', 'PREMIUM CONTENT'], tag: ['Hukum Property', 'PREMIUM CONTENT'],
slug: 'how-does-writing-influence-your-personal-brand',
}, },
{ {
title: 'Helping a local business reinvent itself', title: 'Helping a local business reinvent itself',
content: content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
image: '/images/news-4.jpg', featured: '/images/news-4.jpg',
tag: ['Hukum Property', 'PREMIUM CONTENT'], tag: ['Hukum Property', 'PREMIUM CONTENT'],
slug: 'helping-a-local-business-reinvent-itself',
}, },
], ],
} }

View File

@ -1,8 +1,8 @@
import { Card } from '~/components/ui/card' import { Card } from '~/components/ui/card'
import { Carousel } from '~/components/ui/carousel' import { Carousel } from '~/components/ui/carousel'
import { Newsletter } from '~/components/ui/newsletter'
import { BERITA, KAJIAN, SPOTLIGHT } from './data' import { BERITA, KAJIAN, SPOTLIGHT } from './data'
import { Newsletter } from './newsletter'
export const NewsPage = () => { export const NewsPage = () => {
return ( return (

View File

@ -1,3 +0,0 @@
export const Newsletter = () => {
return <div>Newsletter</div>
}

View File

@ -0,0 +1,7 @@
import { NewsDetailPage } from '~/pages/news-detail'
const NewsDetailLayout = () => {
return <NewsDetailPage />
}
export default NewsDetailLayout

View File

@ -1,5 +1,6 @@
import { Outlet } from 'react-router' import { Outlet } from 'react-router'
import { Banner } from '~/components/ui/banner'
import { FooterLinks } from '~/layouts/footer-links' import { FooterLinks } from '~/layouts/footer-links'
import { FooterNewsletter } from '~/layouts/footer-newsletter' import { FooterNewsletter } from '~/layouts/footer-newsletter'
import { HeaderMenu } from '~/layouts/header-menu' import { HeaderMenu } from '~/layouts/header-menu'
@ -13,11 +14,7 @@ const NewsLayout = () => {
<HeaderMenu /> <HeaderMenu />
</header> </header>
<div className="grid sm:mx-[50px] sm:my-[25px] sm:gap-y-[25px]"> <div className="grid sm:mx-[50px] sm:my-[25px] sm:gap-y-[25px]">
<img <Banner />
src="/images/banner.png"
alt="banner"
className="h-[50px] w-full object-fill sm:h-[100px] sm:object-contain"
/>
<Outlet /> <Outlet />
</div> </div>

View File

@ -2,10 +2,15 @@ export type TNews = {
title: string title: string
description: string description: string
type: 'hero' | 'grid' type: 'hero' | 'grid'
items: { items: Pick<TNewsDetail, 'title' | 'content' | 'featured' | 'slug' | 'tag'>[]
}
export type TNewsDetail = {
title: string title: string
content: string content: string
image: string featured: string
author: string
date: Date
slug: string
tag?: Array<string> tag?: Array<string>
}[]
} }