feat: implement carousel components for news section and update layout styles
This commit is contained in:
parent
3f9bfded80
commit
2b551aad28
24
app/app.css
24
app/app.css
@ -28,3 +28,27 @@ table.dataTable tbody > tr > td {
|
|||||||
border-right: none !important;
|
border-right: none !important;
|
||||||
border-bottom: 1px solid #ebebeb;
|
border-bottom: 1px solid #ebebeb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* .embla.hero {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.embla__container.hero {
|
||||||
|
display: flex;
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
.embla__slide.hero {
|
||||||
|
flex: 0 0 100%;
|
||||||
|
min-width: 0;
|
||||||
|
} */
|
||||||
|
|
||||||
|
.embla__slide {
|
||||||
|
margin-right: 10px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.embla__slide {
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
99
app/components/ui/carousel-hero.tsx
Normal file
99
app/components/ui/carousel-hero.tsx
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import useEmblaCarousel from 'embla-carousel-react'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import { Link } from 'react-router'
|
||||||
|
|
||||||
|
import { CarouselNextIcon } from '~/components/icons/carousel-next'
|
||||||
|
import { CarouselPreviousIcon } from '~/components/icons/carousel-previous'
|
||||||
|
import { Button } from '~/components/ui/button'
|
||||||
|
import { useNewsContext } from '~/contexts/news'
|
||||||
|
import type { TNews } from '~/types/news'
|
||||||
|
|
||||||
|
export const CarouselHero = (properties: TNews) => {
|
||||||
|
const { setIsSuccessOpen } = useNewsContext()
|
||||||
|
const { title, description, items } = properties
|
||||||
|
const [emblaReference, emblaApi] = useEmblaCarousel({ loop: true })
|
||||||
|
|
||||||
|
const previousSlide = useCallback(() => {
|
||||||
|
if (emblaApi) emblaApi.scrollPrev()
|
||||||
|
}, [emblaApi])
|
||||||
|
|
||||||
|
const nextSlide = useCallback(() => {
|
||||||
|
if (emblaApi) emblaApi.scrollNext()
|
||||||
|
}, [emblaApi])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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}
|
||||||
|
onClick={previousSlide}
|
||||||
|
/>
|
||||||
|
<CarouselNextIcon
|
||||||
|
color="#2E2F7C"
|
||||||
|
className="cursor-pointer"
|
||||||
|
width={45}
|
||||||
|
height={45}
|
||||||
|
onClick={nextSlide}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="embla hero overflow-hidden"
|
||||||
|
ref={emblaReference}
|
||||||
|
>
|
||||||
|
<div className="embla__container hero flex sm:gap-x-8">
|
||||||
|
{items.map(({ featured, title, content, slug, isPremium }, index) => (
|
||||||
|
<div
|
||||||
|
className="embla__slide hero w-full min-w-0 flex-none"
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
<div className="max-sm:mt-2 sm:flex">
|
||||||
|
<img
|
||||||
|
className="col-span-2 aspect-[174/100] object-cover"
|
||||||
|
src={featured}
|
||||||
|
alt={title}
|
||||||
|
/>
|
||||||
|
<div className="flex h-full flex-col justify-between gap-7 sm:px-5">
|
||||||
|
<div>
|
||||||
|
<h3 className="mt-2 w-full text-2xl font-bold sm:mt-0 sm:text-4xl">
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-md mt-5 text-[#777777] sm:text-xl">
|
||||||
|
{content}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
size="block"
|
||||||
|
{...(isPremium
|
||||||
|
? {
|
||||||
|
onClick: () => {
|
||||||
|
setIsSuccessOpen('warning')
|
||||||
|
},
|
||||||
|
to: '',
|
||||||
|
}
|
||||||
|
: { as: Link, to: `/detail/${slug}` })}
|
||||||
|
>
|
||||||
|
View More
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
118
app/components/ui/carousel-section.tsx
Normal file
118
app/components/ui/carousel-section.tsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import useEmblaCarousel from 'embla-carousel-react'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import { Link } from 'react-router'
|
||||||
|
|
||||||
|
import { CarouselNextIcon } from '~/components/icons/carousel-next'
|
||||||
|
import { CarouselPreviousIcon } from '~/components/icons/carousel-previous'
|
||||||
|
import { Button } from '~/components/ui/button'
|
||||||
|
import { useNewsContext } from '~/contexts/news'
|
||||||
|
import type { TNews } from '~/types/news'
|
||||||
|
|
||||||
|
export const CarouselSection = (properties: TNews) => {
|
||||||
|
const { setIsSuccessOpen } = useNewsContext()
|
||||||
|
const { title, description, items } = properties
|
||||||
|
const [emblaReference, emblaApi] = useEmblaCarousel({ loop: false })
|
||||||
|
|
||||||
|
const previousSlide = useCallback(() => {
|
||||||
|
if (emblaApi) emblaApi.scrollPrev()
|
||||||
|
}, [emblaApi])
|
||||||
|
|
||||||
|
const nextSlide = useCallback(() => {
|
||||||
|
if (emblaApi) emblaApi.scrollNext()
|
||||||
|
}, [emblaApi])
|
||||||
|
return (
|
||||||
|
<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}
|
||||||
|
onClick={previousSlide}
|
||||||
|
/>
|
||||||
|
<CarouselNextIcon
|
||||||
|
color="#2E2F7C"
|
||||||
|
className="cursor-pointer"
|
||||||
|
width={45}
|
||||||
|
height={45}
|
||||||
|
onClick={nextSlide}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="embla overflow-hidden"
|
||||||
|
ref={emblaReference}
|
||||||
|
>
|
||||||
|
<div className="embla__container col-span-3 flex max-h-[586px]">
|
||||||
|
{items.map(
|
||||||
|
({ featured, title, content, tags, slug, isPremium }, index) => (
|
||||||
|
<div
|
||||||
|
className="embla__slide w-full min-w-0 flex-none sm:w-1/3"
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col justify-between max-sm:mt-2">
|
||||||
|
<img
|
||||||
|
className="aspect-[5/4] max-h-[300px] w-full rounded-md object-cover"
|
||||||
|
src={featured}
|
||||||
|
alt={title}
|
||||||
|
/>
|
||||||
|
<div className={'flex flex-col justify-between gap-4'}>
|
||||||
|
<div className={'flex uppercase'}>
|
||||||
|
{tags?.map((item) => (
|
||||||
|
<span
|
||||||
|
key={index}
|
||||||
|
className="my-3 mr-2 inline-block rounded bg-[#F4F4F4] px-3 py-1 font-bold text-[#777777]"
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
{isPremium && (
|
||||||
|
<span className="my-3 mr-2 inline-block rounded bg-[#D1C675] px-3 py-1 font-bold text-[#9D761D]">
|
||||||
|
Premium Content
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="mt-2 w-full text-xl font-bold sm:text-2xl lg:mt-0">
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-md mt-5 text-[#777777] sm:text-xl">
|
||||||
|
{content}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
size="block"
|
||||||
|
{...(isPremium
|
||||||
|
? {
|
||||||
|
onClick: () => {
|
||||||
|
setIsSuccessOpen('warning')
|
||||||
|
},
|
||||||
|
to: '',
|
||||||
|
}
|
||||||
|
: { as: Link, to: `/news/detail/${slug}` })}
|
||||||
|
className="mb-5"
|
||||||
|
>
|
||||||
|
View More
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
132
app/data/contents.ts
Normal file
132
app/data/contents.ts
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import type { TNews } from '~/types/news'
|
||||||
|
|
||||||
|
export const SPOTLIGHT: TNews = {
|
||||||
|
title: 'SPOTLIGHT',
|
||||||
|
description: 'Berita Terhangat hari ini',
|
||||||
|
type: 'hero',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: '01 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',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '02 Travelling as a way of self-discovery and progress',
|
||||||
|
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: 'https://placehold.co/600x400.png',
|
||||||
|
slug: 'hotman-paris-membuka-perpustakaan-di-tengah-diskotik',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '03 Travelling as a way of self-discovery and progress',
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BERITA: TNews = {
|
||||||
|
title: 'BERITA',
|
||||||
|
description: 'Berita Terhangat hari ini',
|
||||||
|
type: 'grid',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: '01 Travelling as a way of self-discovery and progress ',
|
||||||
|
content:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
|
||||||
|
featured: '/images/news-2.jpg',
|
||||||
|
tags: ['Hukum Property'],
|
||||||
|
slug: 'travelling-as-a-way-of-self-discovery-and-progress',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '02 How does writing influence your personal brand?',
|
||||||
|
content:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
|
||||||
|
featured: '/images/news-3.jpg',
|
||||||
|
tags: ['Hukum'],
|
||||||
|
slug: 'how-does-writing-influence-your-personal-brand',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '03 Helping a local business reinvent itself',
|
||||||
|
content:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
|
||||||
|
featured: '/images/news-4.jpg',
|
||||||
|
tags: ['Hukum Property'],
|
||||||
|
isPremium: true,
|
||||||
|
slug: 'helping-a-local-business-reinvent-itself',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Travelling as a way of self-discovery and progress',
|
||||||
|
content:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
|
||||||
|
featured: 'https://placehold.co/600x400.png',
|
||||||
|
tags: ['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.',
|
||||||
|
featured: '/images/news-3.jpg',
|
||||||
|
tags: ['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.',
|
||||||
|
featured: '/images/news-4.jpg',
|
||||||
|
tags: ['Hukum Property'],
|
||||||
|
isPremium: true,
|
||||||
|
slug: 'helping-a-local-business-reinvent-itself',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export const KAJIAN: TNews = {
|
||||||
|
title: 'KAJIAN',
|
||||||
|
description: 'Berita Terhangat hari ini',
|
||||||
|
type: 'grid',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: 'Travelling as a way of self-discovery and progress',
|
||||||
|
content:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
|
||||||
|
featured: '/images/news-2.jpg',
|
||||||
|
tags: ['Hukum Property'],
|
||||||
|
isPremium: true,
|
||||||
|
slug: 'travelling-as-a-way-of-self-discovery-and-progress',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Travelling as a way of self-discovery and progress',
|
||||||
|
content:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
|
||||||
|
featured: 'https://placehold.co/600x400.png',
|
||||||
|
tags: ['Hukum Property'],
|
||||||
|
isPremium: true,
|
||||||
|
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.',
|
||||||
|
featured: '/images/news-3.jpg',
|
||||||
|
tags: ['Hukum Property'],
|
||||||
|
isPremium: true,
|
||||||
|
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.',
|
||||||
|
featured: '/images/news-4.jpg',
|
||||||
|
tags: ['Hukum Property'],
|
||||||
|
isPremium: true,
|
||||||
|
slug: 'helping-a-local-business-reinvent-itself',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
@ -34,7 +34,7 @@ export const NewsDefaultLayout = (properties: PropsWithChildren) => {
|
|||||||
<HeaderTop />
|
<HeaderTop />
|
||||||
<HeaderMenu />
|
<HeaderMenu />
|
||||||
</header>
|
</header>
|
||||||
<div className="grid sm:mx-[50px] sm:my-[25px] sm:gap-y-[25px]">
|
<div className="sm:mx-[50px] sm:my-[25px] sm:grid sm:gap-y-[25px]">
|
||||||
<Banner />
|
<Banner />
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export const SPOTLIGHT: TNews = {
|
|||||||
title: '02 Travelling as a way of self-discovery and progress',
|
title: '02 Travelling as a way of self-discovery and progress',
|
||||||
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..',
|
||||||
featured: 'https://placehold.co/600x400.png',
|
featured: 'https://placehold.co/800x500.png',
|
||||||
slug: 'hotman-paris-membuka-perpustakaan-di-tengah-diskotik',
|
slug: 'hotman-paris-membuka-perpustakaan-di-tengah-diskotik',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -63,7 +63,7 @@ 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.',
|
||||||
featured: 'https://placehold.co/600x400.png',
|
featured: 'https://placehold.co/800x500.png',
|
||||||
tags: ['Hukum Property'],
|
tags: ['Hukum Property'],
|
||||||
slug: 'travelling-as-a-way-of-self-discovery-and-progress',
|
slug: 'travelling-as-a-way-of-self-discovery-and-progress',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,23 +1,21 @@
|
|||||||
import { Card } from '~/components/ui/card'
|
import { Card } from '~/components/ui/card'
|
||||||
import { Carousel } from '~/components/ui/carousel'
|
import { CarouselHero } from '~/components/ui/carousel-hero'
|
||||||
|
import { CarouselSection } from '~/components/ui/carousel-section'
|
||||||
import { Newsletter } from '~/components/ui/newsletter'
|
import { Newsletter } from '~/components/ui/newsletter'
|
||||||
|
|
||||||
import { BERITA, KAJIAN, SPOTLIGHT } from './data'
|
import { SPOTLIGHT, BERITA } from './data'
|
||||||
|
|
||||||
export const NewsPage = () => {
|
export const NewsPage = () => {
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Card>
|
<Card>
|
||||||
<Carousel {...SPOTLIGHT} />
|
<CarouselHero {...SPOTLIGHT} />
|
||||||
</Card>
|
</Card>
|
||||||
<div className="min-h-[400px] sm:min-h-[300px]">
|
<div className="min-h-[400px] sm:min-h-[300px]">
|
||||||
<Newsletter className="mr-0 sm:-ml-14" />
|
<Newsletter className="mr-0 sm:-ml-14" />
|
||||||
</div>
|
</div>
|
||||||
<Card>
|
<Card>
|
||||||
<Carousel {...BERITA} />
|
<CarouselSection {...BERITA} />
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<Carousel {...KAJIAN} />
|
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user