feat: refactor news components and add news detail layout
This commit is contained in:
parent
f17627bdf2
commit
e73949295d
@ -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">
|
||||
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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">
|
||||
|
||||
11
app/pages/news-detail/data.ts
Normal file
11
app/pages/news-detail/data.ts
Normal 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(),
|
||||
}
|
||||
17
app/pages/news-detail/index.tsx
Normal file
17
app/pages/news-detail/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@ -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',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
export const Newsletter = () => {
|
||||
return <div>Newsletter</div>
|
||||
}
|
||||
7
app/routes/_layout.news.$slug.tsx
Normal file
7
app/routes/_layout.news.$slug.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import { NewsDetailPage } from '~/pages/news-detail'
|
||||
|
||||
const NewsDetailLayout = () => {
|
||||
return <NewsDetailPage />
|
||||
}
|
||||
|
||||
export default NewsDetailLayout
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user