feat: image filter
This commit is contained in:
parent
7242a0afab
commit
67fe4ba060
10 changed files with 38350 additions and 1079 deletions
|
|
@ -2,37 +2,67 @@ import { cva, type VariantProps } from "class-variance-authority"
|
|||
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
const button = cva(
|
||||
[
|
||||
|
||||
"inline-flex",
|
||||
"items-center",
|
||||
"rounded-xl",
|
||||
"text-center",
|
||||
"transition-colors",
|
||||
"delay-50",
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
intent: {
|
||||
primary: ["bg-blue-400", "justify-center", "text-white", "hover:enabled:bg-blue-700"],
|
||||
secondary: ["bg-transparent", "justify-center", "text-white", "hover:bg-slate-100", "rounded-3xl", "hover:text-vizoleG2", "border-solid", "border-2", "border-slate-100"],
|
||||
hero1: ["text-black", "bg-white", "justify-center","font-medium", "rounded-3xl", "text-sm", "text-center", "me-2", "mb-2", "border-solid", "border-2", "border-slate-100"],
|
||||
aboutUs: ["text-left","bg-transparent", "text-white", "font-semibold", "hover:text-white", "py-2", "px-4", "border", "border-gray-400","transition-all", "hover:border-white", "rounded", "flex", , "my-1","w-[50%]"]
|
||||
},
|
||||
size: {
|
||||
sm: ["min-w-20", "h-full", "min-h-10", "text-sm", "py-1.5", "px-4"],
|
||||
lg: ["min-w-32", "h-full", "min-h-12", "text-lg", "py-2.5", "px-6"],
|
||||
hero1: ["min-w-15", "h-full", "min-h-5", "text-sm", "px-5", "py-2"]
|
||||
},
|
||||
underline: { true: ["underline"], false: [] },
|
||||
const button = cva(["inline-flex", "items-center", "rounded-xl", "text-center", "transition-colors", "delay-50"], {
|
||||
variants: {
|
||||
intent: {
|
||||
primary: ["bg-blue-400", "justify-center", "text-white", "hover:enabled:bg-blue-700"],
|
||||
secondary: [
|
||||
"bg-transparent",
|
||||
"justify-center",
|
||||
"text-white",
|
||||
"hover:bg-slate-100",
|
||||
"rounded-3xl",
|
||||
"hover:text-vizoleG2",
|
||||
"border-solid",
|
||||
"border-2",
|
||||
"border-slate-100",
|
||||
],
|
||||
hero1: [
|
||||
"text-black",
|
||||
"bg-white",
|
||||
"justify-center",
|
||||
"font-medium",
|
||||
"rounded-3xl",
|
||||
"text-sm",
|
||||
"text-center",
|
||||
"me-2",
|
||||
"mb-2",
|
||||
"border-solid",
|
||||
"border-2",
|
||||
"border-slate-100",
|
||||
],
|
||||
aboutUs: [
|
||||
"text-left",
|
||||
"bg-transparent",
|
||||
"text-white",
|
||||
"font-semibold",
|
||||
"hover:text-white",
|
||||
"py-2",
|
||||
"px-4",
|
||||
"border",
|
||||
"border-gray-400",
|
||||
"transition-all",
|
||||
"hover:border-white",
|
||||
"rounded",
|
||||
"flex",
|
||||
,
|
||||
"my-1",
|
||||
"w-[50%]",
|
||||
"max-md:w-full",
|
||||
],
|
||||
},
|
||||
defaultVariants: {
|
||||
intent: "primary",
|
||||
size: "lg",
|
||||
size: {
|
||||
sm: ["min-w-20", "h-full", "min-h-10", "text-sm", "py-1.5", "px-4"],
|
||||
lg: ["min-w-32", "h-full", "min-h-12", "text-lg", "py-2.5", "px-6"],
|
||||
hero1: ["min-w-15", "h-full", "min-h-5", "text-sm", "px-5", "py-2"],
|
||||
},
|
||||
}
|
||||
)
|
||||
underline: { true: ["underline"], false: [] },
|
||||
},
|
||||
defaultVariants: {
|
||||
intent: "primary",
|
||||
size: "lg",
|
||||
},
|
||||
})
|
||||
|
||||
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLAnchorElement>, VariantProps<typeof button> {
|
||||
underline?: boolean
|
||||
|
|
|
|||
26
components/ImageFilter/FilterableData.tsx
Normal file
26
components/ImageFilter/FilterableData.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
export const FilterableData = [
|
||||
{
|
||||
name: "Web",
|
||||
src: "/images/site01.png",
|
||||
title: "Mountains",
|
||||
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
},
|
||||
{
|
||||
name: "UI/UX",
|
||||
src: "/images/site02.png",
|
||||
title: "BMW",
|
||||
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
},
|
||||
{
|
||||
name: "Web",
|
||||
src: "/images/site03.png",
|
||||
title: "Modeller",
|
||||
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
},
|
||||
{
|
||||
name: "Web",
|
||||
src: "/images/site04.png",
|
||||
title: "Billing App",
|
||||
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
},
|
||||
]
|
||||
59
components/ImageFilter/ImageFilter.tsx
Normal file
59
components/ImageFilter/ImageFilter.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"use client"
|
||||
|
||||
import { Button } from "components/Button/Button"
|
||||
import { useState } from "react"
|
||||
import { Image } from "./image"
|
||||
import { FilterableData } from "./FilterableData"
|
||||
import { motion } from "framer-motion" // Import motion
|
||||
|
||||
const ImageFilter = () => {
|
||||
const [activeFilter, setActiveFilter] = useState<string>("all")
|
||||
|
||||
const buttonCaptions = ["all", "Web", "Mobile App", "UI/UX"]
|
||||
|
||||
const handleFilterClick = (filter: string) => {
|
||||
setActiveFilter(filter)
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="mb-3 flex w-full flex-col gap-12 px-5 py-16 md:px-10 lg:px-16">
|
||||
<div className="flex w-full flex-wrap items-start gap-3 md:justify-center md:gap-6">
|
||||
{buttonCaptions.map((filter) => (
|
||||
<button
|
||||
key={filter}
|
||||
onClick={() => handleFilterClick(filter)}
|
||||
type="button"
|
||||
className={`mb-2 rounded-lg border-2 border-[#F0B37D] px-5 py-2.5 text-sm font-medium capitalize text-white hover:bg-[#F0B37D] focus:outline-none ${
|
||||
activeFilter === filter ? "bg-[#F0B37D]" : " "
|
||||
}`}
|
||||
>
|
||||
{filter === "all" ? "Show all" : filter}
|
||||
</button>
|
||||
))}
|
||||
{/* filtered cards display */}
|
||||
</div>
|
||||
<main className="grid w-full gap-x-5 gap-y-8 md:mt-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
{FilterableData.map((item, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
layout
|
||||
className={`w-full cursor-pointer rounded-lg border border-gray-600 bg-gray-800 shadow ${
|
||||
activeFilter === "all" || activeFilter === item.name
|
||||
? "" /* Show card */
|
||||
: "hidden" /* Hide if not matched */
|
||||
}`}
|
||||
>
|
||||
<Image
|
||||
className="h-[400px] w-full overflow-hidden rounded-lg"
|
||||
image={item.src}
|
||||
alt={item.name}
|
||||
objectCover="object-cover"
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</main>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default ImageFilter
|
||||
25
components/ImageFilter/image.tsx
Normal file
25
components/ImageFilter/image.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
type ImageOwnProps<T extends React.ElementType> = {
|
||||
className: string
|
||||
image: string
|
||||
alt: string
|
||||
objectCover?: string
|
||||
as?: T
|
||||
}
|
||||
|
||||
type ImageProps<T extends React.ElementType> = ImageOwnProps<T> & Omit<React.ComponentProps<T>, keyof ImageOwnProps<T>>
|
||||
|
||||
export const Image = <T extends React.ElementType = "div">({
|
||||
className,
|
||||
image,
|
||||
alt,
|
||||
objectCover,
|
||||
as,
|
||||
...rest
|
||||
}: ImageProps<T>) => {
|
||||
const Component = as || "div"
|
||||
return (
|
||||
<Component className={className} {...rest}>
|
||||
<img src={image} alt={alt} className={`h-full w-full ${objectCover}`} />
|
||||
</Component>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue