feat/hero #9
@ -2,22 +2,158 @@ import React from "react";
|
|||||||
import { Swiper, SwiperSlide } from "swiper/react";
|
import { Swiper, SwiperSlide } from "swiper/react";
|
||||||
import { Autoplay, Pagination, Navigation } from "swiper/modules";
|
import { Autoplay, Pagination, Navigation } from "swiper/modules";
|
||||||
import "swiper/css";
|
import "swiper/css";
|
||||||
|
import { Button } from "@/shared/libs/shadcn/ui/button";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { Badge } from "@/shared/libs/shadcn/ui/badge";
|
||||||
|
|
||||||
|
// Dummy data for slides, This only for testing, replace with actual data later
|
||||||
|
const dummyData = [
|
||||||
|
{
|
||||||
|
id: "jo21j189289",
|
||||||
|
isClickable: true,
|
||||||
|
title: "Yosuga no Sora",
|
||||||
|
tags: ["Romance", "Drama", "Ecchi"],
|
||||||
|
description:
|
||||||
|
"A story about two siblings who fall in love with each other. This is a controversial anime that has been criticized for its incestuous themes. Viewer discretion is advised. Also, this anime is not suitable for children. Please watch it at your own risk.",
|
||||||
|
buttonContent: "Watch Now",
|
||||||
|
buttonLink: "https://www.example.com/yosuga-no-sora",
|
||||||
|
imageUrl:
|
||||||
|
"https://asset.tribunnews.com/rDUJvGRU7_qDX-G4IFc5tUn08vw=/1200x675/filters:upscale():quality(30):format(webp):focal(0.5x0.5:0.5x0.5)/style/foto/bank/originals/sora-kasugano-karakter-dalam-anime-dewasa-yosuga-no-sora.jpg",
|
||||||
|
startDate: "2024-01-01",
|
||||||
|
endDate: "2024-12-31",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "jo21j189290",
|
||||||
|
isClickable: true,
|
||||||
|
title: "Redo of Healer",
|
||||||
|
tags: ["Action", "Fantasy", "Ecchi"],
|
||||||
|
description:
|
||||||
|
"A story about a healer who seeks revenge on those who wronged him. This is a controversial anime that has been criticized for its graphic violence and sexual content. Viewer discretion is advised. Also, this anime is not suitable for children. Please watch it at your own risk.",
|
||||||
|
buttonContent: "Watch Now",
|
||||||
|
buttonLink: "https://www.example.com/redo-of-healer",
|
||||||
|
imageUrl: "https://miro.medium.com/v2/1*O5bv-7EXnRC-VWmqimVEow.jpeg",
|
||||||
|
startDate: "2024-01-01",
|
||||||
|
endDate: "2024-12-31",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "jo21j189291",
|
||||||
|
isClickable: false,
|
||||||
|
title: "Warning System",
|
||||||
|
tags: [],
|
||||||
|
description:
|
||||||
|
"This is a warning system for anime that contain controversial content. This system is designed to inform viewers about the potential risks of watching certain anime. Viewer discretion is advised. Please watch these anime at your own risk.",
|
||||||
|
buttonContent: "Watch Now",
|
||||||
|
buttonLink: "https://www.example.com/boku-no-pico",
|
||||||
|
imageUrl: "",
|
||||||
|
startDate: "2024-01-01",
|
||||||
|
endDate: "2024-12-31",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
// End of dummy data
|
||||||
|
|
||||||
const Hero = () => {
|
const Hero = () => {
|
||||||
|
const router = useRouter();
|
||||||
return (
|
return (
|
||||||
<div className="h-140 rounded-md overflow-hidden">
|
<div className="h-120 rounded-md overflow-hidden">
|
||||||
<Swiper
|
<Swiper
|
||||||
spaceBetween={0}
|
spaceBetween={0}
|
||||||
slidesPerView={1}
|
slidesPerView={1}
|
||||||
onSlideChange={() => console.log("slide change")}
|
onSlideChange={() => console.log("slide change")}
|
||||||
onSwiper={(swiper) => console.log(swiper)}
|
onSwiper={(swiper) => console.log(swiper)}
|
||||||
className="h-full"
|
className="h-full"
|
||||||
autoplay={{ delay: 5000, disableOnInteraction: false }}
|
// autoplay={{ delay: 5000, disableOnInteraction: false }}
|
||||||
modules={[Autoplay, Pagination, Navigation]}
|
// modules={[Autoplay, Pagination, Navigation]}
|
||||||
>
|
>
|
||||||
<SwiperSlide className="bg-red-500">Slide 1</SwiperSlide>
|
{dummyData.map((slide) =>
|
||||||
<SwiperSlide className="bg-blue-500">Slide 2</SwiperSlide>
|
slide.imageUrl ? (
|
||||||
<SwiperSlide className="bg-green-500">Slide 3</SwiperSlide>
|
// Slide with image background
|
||||||
|
<SwiperSlide key={slide.id} className="relative overflow-hidden">
|
||||||
|
<img
|
||||||
|
src={slide.imageUrl}
|
||||||
|
alt={slide.title}
|
||||||
|
className="absolute top-0 left-0 z-0 object-cover w-full h-full opacity-80"
|
||||||
|
/>
|
||||||
|
<div className="absolute top-0 left-0 z-10 h-full w-full py-14 px-20">
|
||||||
|
<h1 className="text-6xl font-semibold tracking-tight">
|
||||||
|
{slide.title}
|
||||||
|
</h1>
|
||||||
|
<div className="mt-4 flex gap-1.5">
|
||||||
|
{slide.tags.map((tag) => (
|
||||||
|
<Badge
|
||||||
|
className="bg-neutral-200 text-neutral-800"
|
||||||
|
key={tag}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p className="mt-4 font-medium text-base max-w-[40vw]">
|
||||||
|
Twelve years ago, a colossal demon fox terrorized the world.
|
||||||
|
During the monster's attack on the Hidden Leaf Village, the
|
||||||
|
Hokage—the village's leader and most powerful ninja—sacrifices
|
||||||
|
himself to seal the beast inside a newborn, relieving
|
||||||
|
civilization from destruction while dooming the baby to a
|
||||||
|
lonely life. Now, after years of being shunned and bullied,
|
||||||
|
Naruto Uzumaki pesters the village with elaborate pranks and
|
||||||
|
vandalism. Despite these antics, he works hard to achieve his
|
||||||
|
dream: to become the Hokage and earn the acknowledgement of
|
||||||
|
those who have mistreated him for his entire life. Naruto
|
||||||
|
joins Team 7, a ninja squad made up of two of his
|
||||||
|
peers—prodigy Sasuke Uchiha and clever Sakura Haruno.
|
||||||
|
</p>
|
||||||
|
{slide.isClickable && (
|
||||||
|
<Button
|
||||||
|
size="lg"
|
||||||
|
onClick={() => router.push(slide.buttonLink)}
|
||||||
|
className="mt-6"
|
||||||
|
>
|
||||||
|
{slide.buttonContent}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</SwiperSlide>
|
||||||
|
) : (
|
||||||
|
// Fallback for slides without image
|
||||||
|
<SwiperSlide
|
||||||
|
key={slide.id}
|
||||||
|
className="relative overflow-hidden bg-neutral-800 flex flex-col items-center text-center pt-18"
|
||||||
|
>
|
||||||
|
<h1 className="text-6xl font-semibold tracking-tight">
|
||||||
|
{slide.title}
|
||||||
|
</h1>
|
||||||
|
<div className="mt-4 flex justify-center h gap-1.5">
|
||||||
|
{slide.tags.map((tag) => (
|
||||||
|
<Badge className="bg-neutral-200 text-neutral-800" key={tag}>
|
||||||
|
{tag}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p className="mt-4 font-medium text-base max-w-[40vw] mx-auto">
|
||||||
|
Twelve years ago, a colossal demon fox terrorized the world.
|
||||||
|
During the monster's attack on the Hidden Leaf Village, the
|
||||||
|
Hokage—the village's leader and most powerful ninja—sacrifices
|
||||||
|
himself to seal the beast inside a newborn, relieving
|
||||||
|
civilization from destruction while dooming the baby to a lonely
|
||||||
|
life. Now, after years of being shunned and bullied, Naruto
|
||||||
|
Uzumaki pesters the village with elaborate pranks and vandalism.
|
||||||
|
Despite these antics, he works hard to achieve his dream: to
|
||||||
|
become the Hokage and earn the acknowledgement of those who have
|
||||||
|
mistreated him for his entire life. Naruto joins Team 7, a ninja
|
||||||
|
squad made up of two of his peers—prodigy Sasuke Uchiha and
|
||||||
|
clever Sakura Haruno.
|
||||||
|
</p>
|
||||||
|
{slide.isClickable && (
|
||||||
|
<Button
|
||||||
|
size="lg"
|
||||||
|
onClick={() => router.push(slide.buttonLink)}
|
||||||
|
className="mt-6"
|
||||||
|
>
|
||||||
|
{slide.buttonContent}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</SwiperSlide>
|
||||||
|
),
|
||||||
|
)}
|
||||||
</Swiper>
|
</Swiper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
49
shared/libs/shadcn/ui/badge.tsx
Normal file
49
shared/libs/shadcn/ui/badge.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
import { Slot } from "radix-ui"
|
||||||
|
|
||||||
|
import { cn } from "@/shared/libs/shadcn/lib/utils"
|
||||||
|
|
||||||
|
const badgeVariants = cva(
|
||||||
|
"h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive overflow-hidden group/badge",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
||||||
|
secondary:
|
||||||
|
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
|
||||||
|
outline:
|
||||||
|
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
|
||||||
|
ghost:
|
||||||
|
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function Badge({
|
||||||
|
className,
|
||||||
|
variant = "default",
|
||||||
|
asChild = false,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"span"> &
|
||||||
|
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||||
|
const Comp = asChild ? Slot.Root : "span"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
data-slot="badge"
|
||||||
|
data-variant={variant}
|
||||||
|
className={cn(badgeVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Badge, badgeVariants }
|
||||||
@ -1,29 +1,36 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
import { Slot } from "radix-ui"
|
import { Slot } from "radix-ui";
|
||||||
|
|
||||||
import { cn } from "@/shared/libs/shadcn/lib/utils"
|
import { cn } from "@/shared/libs/shadcn/lib/utils";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
|
"focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 aria-invalid:ring-3 cursor-pointer [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
||||||
outline: "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground shadow-xs",
|
outline:
|
||||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
"border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground shadow-xs",
|
||||||
ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
|
secondary:
|
||||||
destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
||||||
|
ghost:
|
||||||
|
"hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
default:
|
||||||
|
"h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
||||||
xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
||||||
sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
|
sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
|
||||||
lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
|
lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
|
||||||
icon: "size-9",
|
icon: "size-9",
|
||||||
"icon-xs": "size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3",
|
"icon-xs":
|
||||||
"icon-sm": "size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md",
|
"size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3",
|
||||||
|
"icon-sm":
|
||||||
|
"size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md",
|
||||||
"icon-lg": "size-10",
|
"icon-lg": "size-10",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -31,8 +38,8 @@ const buttonVariants = cva(
|
|||||||
variant: "default",
|
variant: "default",
|
||||||
size: "default",
|
size: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
function Button({
|
function Button({
|
||||||
className,
|
className,
|
||||||
@ -42,9 +49,9 @@ function Button({
|
|||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"button"> &
|
}: React.ComponentProps<"button"> &
|
||||||
VariantProps<typeof buttonVariants> & {
|
VariantProps<typeof buttonVariants> & {
|
||||||
asChild?: boolean
|
asChild?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const Comp = asChild ? Slot.Root : "button"
|
const Comp = asChild ? Slot.Root : "button";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
@ -54,7 +61,7 @@ function Button({
|
|||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Button, buttonVariants }
|
export { Button, buttonVariants };
|
||||||
|
|||||||
Reference in New Issue
Block a user