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'
export default function Banner() {
export const Banner = () => {
return (
<div className="min-h-[65px] bg-amber-400 sm:mx-10">
<div className="relative">

View File

@ -1,10 +1,5 @@
import { cva, type VariantProps } from 'class-variance-authority'
import type {
ButtonHTMLAttributes,
HTMLAttributes,
MouseEventHandler,
ReactNode,
} from 'react'
import type { ReactNode, ElementType, ComponentPropsWithoutRef } from 'react'
import { twMerge } from 'tailwind-merge'
const buttonVariants = cva(
@ -16,14 +11,6 @@ const buttonVariants = cva(
newsPrimaryOutline: 'border-[3px] border-white text-white text-lg',
newsSecondary: 'border-[3px] border-[#2E2F7C] text-[#2E2F7C] text-lg',
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: {
default: 'h-[50px] w-[150px]',
@ -40,34 +27,38 @@ const buttonVariants = cva(
},
)
type TProperties = {
type?: ButtonHTMLAttributes<HTMLButtonElement>['type']
onClick?: MouseEventHandler<HTMLButtonElement>
className?: HTMLAttributes<HTMLButtonElement>['className']
disabled?: boolean
type ButtonBaseProperties = {
children: ReactNode
variant?: VariantProps<typeof buttonVariants>['variant']
size?: VariantProps<typeof buttonVariants>['size']
className?: string
}
export const Button = (properties: TProperties) => {
const {
type = 'button',
onClick,
className,
disabled,
children,
variant,
size,
} = properties
type PolymorphicReference<C extends ElementType> =
ComponentPropsWithoutRef<C>['ref']
type ButtonProperties<C extends ElementType> = ButtonBaseProperties & {
as?: C
ref?: PolymorphicReference<C>
} & Omit<ComponentPropsWithoutRef<C>, keyof ButtonBaseProperties>
export const Button = <C extends ElementType = 'button'>({
as,
children,
variant,
size,
className,
...properties
}: ButtonProperties<C>) => {
const Component = as || 'button'
const classes = twMerge(buttonVariants({ variant, size, className }))
return (
<button
type={type}
onClick={onClick}
className={twMerge(buttonVariants({ variant, size, className }))}
disabled={disabled}
<Component
className={classes}
{...properties}
>
{children}
</button>
</Component>
)
}

View File

@ -1,3 +1,4 @@
import { Link } from 'react-router'
import { twMerge } from 'tailwind-merge'
import { CarouselNextIcon } from '~/components/icons/carousel-next'
@ -9,159 +10,96 @@ import { Button } from './button'
export const Carousel = (properties: TNews) => {
const { title, description, items, type } = properties
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 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="grid">
<h2 className="text-2xl font-extrabold text-[#2E2F7C] sm:text-4xl">
{title}
</h2>
<p className="text-xl font-light text-[#777777] italic sm:text-2xl">
{description}
</p>
</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="mt-3 mb-3 flex items-center justify-between border-b border-black pb-3 sm:mb-[30px] sm:pb-[30px]">
<div className="grid">
<h2 className="text-2xl font-extrabold text-[#2E2F7C] sm:text-4xl">
{title}
</h2>
<p className="text-xl font-light text-[#777777] italic sm:text-2xl">
{description}
</p>
</div>
<div className="flex gap-2.5">
<CarouselPreviousIcon
color="#DCDCDC"
className="cursor-pointer"
width={45}
height={45}
/>
<CarouselNextIcon
color="#2E2F7C"
className="cursor-pointer"
width={45}
height={45}
/>
</div>
</div>
<div
className={twMerge(
'grid sm:grid sm:gap-x-8',
type === 'hero' ? 'grid-cols-1' : 'sm:grid-cols-3',
)}
>
{items.map(({ image, title, content, tag }, index) => (
<div
key={index}
className={twMerge(
'grid sm:gap-x-8',
type === 'hero' ? 'grid-cols-1 sm:grid-cols-3' : '',
)}
>
<img
className={twMerge(
'w-full object-cover',
type === 'hero'
? 'col-span-2 aspect-[174/100]'
: 'aspect-[5/4] rounded-md',
)}
src={image}
alt={title}
/>
<div
className={twMerge(
'flex flex-col justify-between',
type === 'hero' ? 'gap-7' : 'gap-4',
)}
>
<div className={`${type === 'hero' ? 'hidden' : ''} `}>
{tag?.map((item) => (
<span className="my-3 mr-2 inline-block rounded bg-gray-200 px-3 py-1">
{item}
</span>
))}
</div>
<div>
<h3
className={twMerge(
'mt-2 w-full font-bold lg:mt-0',
type === 'hero'
? 'text-2xl sm:text-4xl'
: 'text-xl sm:text-2xl',
)}
>
{title}
</h3>
<p className="text-md mt-5 text-[#777777] sm:text-xl">
{content}
</p>
</div>
<Button size="block">View More</Button>
</div>
</div>
))}
<div className="flex gap-2.5">
<CarouselPreviousIcon
color="#DCDCDC"
className="cursor-pointer"
width={45}
height={45}
/>
<CarouselNextIcon
color="#2E2F7C"
className="cursor-pointer"
width={45}
height={45}
/>
</div>
</div>
</>
<div
className={twMerge(
'grid sm:grid sm:gap-x-8',
type === 'hero' ? 'grid-cols-1' : 'sm:grid-cols-3',
)}
>
{items.map(({ featured, title, content, tag, slug }, index) => (
<div
key={index}
className={twMerge(
'grid sm:gap-x-8',
type === 'hero' ? 'grid-cols-1 sm:grid-cols-3' : '',
)}
>
<img
className={twMerge(
'w-full object-cover',
type === 'hero'
? 'col-span-2 aspect-[174/100]'
: 'aspect-[5/4] rounded-md',
)}
src={featured}
alt={title}
/>
<div
className={twMerge(
'flex flex-col justify-between',
type === 'hero' ? 'gap-7' : 'gap-4',
)}
>
<div className={`${type === 'hero' ? 'hidden' : ''} `}>
{tag?.map((item) => (
<span className="my-3 mr-2 inline-block rounded bg-gray-200 px-3 py-1">
{item}
</span>
))}
</div>
<div>
<h3
className={twMerge(
'mt-2 w-full font-bold lg:mt-0',
type === 'hero'
? 'text-2xl sm:text-4xl'
: 'text-xl sm:text-2xl',
)}
>
{title}
</h3>
<p className="text-md mt-5 text-[#777777] sm:text-xl">
{content}
</p>
</div>
<Button
size="block"
as={Link}
to={slug}
>
View More
</Button>
</div>
</div>
))}
</div>
</div>
)
}

View File

@ -1,7 +1,7 @@
import { Button } from '~/components/ui/button'
import { APP } from '~/data/meta'
export const UiNewsLetter = () => {
export const Newsletter = () => {
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">

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',
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..',
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',
content:
'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'],
slug: 'travelling-as-a-way-of-self-discovery-and-progress',
},
{
title: 'How does writing influence your personal brand?',
content:
'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'],
slug: 'how-does-writing-influence-your-personal-brand',
},
{
title: 'Helping a local business reinvent itself',
content:
'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'],
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',
content:
'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'],
slug: 'travelling-as-a-way-of-self-discovery-and-progress',
},
{
title: 'How does writing influence your personal brand?',
content:
'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'],
slug: 'how-does-writing-influence-your-personal-brand',
},
{
title: 'Helping a local business reinvent itself',
content:
'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'],
slug: 'helping-a-local-business-reinvent-itself',
},
],
}

View File

@ -1,8 +1,8 @@
import { Card } from '~/components/ui/card'
import { Carousel } from '~/components/ui/carousel'
import { Newsletter } from '~/components/ui/newsletter'
import { BERITA, KAJIAN, SPOTLIGHT } from './data'
import { Newsletter } from './newsletter'
export const NewsPage = () => {
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 { Banner } from '~/components/ui/banner'
import { FooterLinks } from '~/layouts/footer-links'
import { FooterNewsletter } from '~/layouts/footer-newsletter'
import { HeaderMenu } from '~/layouts/header-menu'
@ -13,11 +14,7 @@ const NewsLayout = () => {
<HeaderMenu />
</header>
<div className="grid sm:mx-[50px] sm:my-[25px] sm:gap-y-[25px]">
<img
src="/images/banner.png"
alt="banner"
className="h-[50px] w-full object-fill sm:h-[100px] sm:object-contain"
/>
<Banner />
<Outlet />
</div>

View File

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