add category section

This commit is contained in:
2026-02-17 21:33:47 +03:30
parent f355fd9bb6
commit 53484a07f8
15 changed files with 220 additions and 37 deletions

View File

@@ -16,6 +16,10 @@
"main_title_line2": "سریع و مطمئن",
"contact_us": "تماس با ما"
},
"Categories" : {
"title" : "دسته بندی ها",
"description" : "از بین صدها مرکز زیبایی، دقیقا همونی که میخای رو پیدا کن."
},
"Application": {
"title": "دانلود نرم افزار تلفن همراه 141",
"description": "نسخه‌ی تلفن همراه اپلیکیشن مرکز مدیریت راه‌های کشور با قابلیت‌هایی نظیر نمایش تصاویر دوربین‌های، نظارتی در سراسر کشور، مسیریابی همراه با نمایش مسافت و زمان تقریبی و جزئیات مسیر، مشاهده اخبار متنی و شنیداری، کروکی ترافیک استان‌های کشور، دریافت اطلاعیه‌های مربوط به مسیرهای پرتردد و ... آماده دریافت از مارکت‌های مختلف می‌باشد.",

View File

@@ -0,0 +1,3 @@
<svg width="15" height="12" viewBox="0 0 15 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.06694 0.183058C6.31102 0.427136 6.31102 0.822864 6.06694 1.06694L2.13388 5H13.9583C14.3035 5 14.5833 5.27982 14.5833 5.625C14.5833 5.97018 14.3035 6.25 13.9583 6.25H2.13388L6.06694 10.1831C6.31102 10.4271 6.31102 10.8229 6.06694 11.0669C5.82286 11.311 5.42714 11.311 5.18306 11.0669L0.183058 6.06694C-0.0610194 5.82286 -0.0610194 5.42714 0.183058 5.18306L5.18306 0.183058C5.42714 -0.0610194 5.82286 -0.0610194 6.06694 0.183058Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 610 B

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2254 4.55806C11.4695 4.31398 11.8652 4.31398 12.1093 4.55806L17.1093 9.55806C17.3533 9.80214 17.3533 10.1979 17.1093 10.4419L12.1093 15.4419C11.8652 15.686 11.4695 15.686 11.2254 15.4419C10.9813 15.1979 10.9813 14.8021 11.2254 14.5581L15.1584 10.625H3.33398C2.98881 10.625 2.70898 10.3452 2.70898 10C2.70898 9.65482 2.98881 9.375 3.33398 9.375H15.1584L11.2254 5.44194C10.9813 5.19786 10.9813 4.80214 11.2254 4.55806Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@@ -17,3 +17,5 @@ export { default as CalenderIcon } from "&/icons/calender.svg";
export { default as LocationIcon } from "&/icons/location.svg";
export { default as MarkerIcon } from "&/icons/marker-icon.svg";
export { default as MarkerPreviewIcon } from "&/icons/marker-preview.svg";
export { default as ArrowRight } from "&/icons/arrow-right.svg";
export { default as ArrowLeft } from "&/icons/arrow-left.svg";

View File

@@ -1,34 +1,151 @@
"use client"
import { Card, CardContent } from "@/components/UI/card"
import {Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious} from "@/components/UI/Carousel";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/UI/Carousel"
import { categories } from "@/data/mockCategories"
import Image from "next/image"
import {easeOut, motion} from "framer-motion"
type Props = {
title: string
description: string
}
export function CarouselComponent() {
const containerVariants = {
hidden: {},
show: {
transition: {
staggerChildren: 0.08,
},
},
}
const itemVariants = {
hidden: {
opacity: 0,
y: 30,
scale: 0.95,
},
show: {
opacity: 1,
y: 0,
scale: 1,
transition: {
duration: 0.4,
ease: easeOut,
},
},
}
export function CarouselComponent({ title, description }: Props) {
return (
<Carousel
dir={"rtl"}
className="w-full"
opts={{
direction: "rtl",
}}
>
<CarouselContent>
{Array.from({ length: 150 }).map((_, index) => (
<CarouselItem key={index} className="basis-1/3">
<div className="p-1">
<Card dir={"rtl"}>
<CardContent className="flex aspect-square items-center justify-center p-6">
hello
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
<div className="relative w-full">
<Carousel dir="rtl" className="w-full" opts={{ direction: "rtl" }}>
{/* 🔥 Animated Header */}
<motion.div
initial={{ opacity: 0, x: 40 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6 }}
className="flex items-center justify-between w-full h-full my-5"
>
<span className="flex items-center justify-center gap-x-2">
<motion.div
initial={{ height: 0 }}
animate={{ height: 64 }}
transition={{ duration: 0.5 }}
className="bg-primary-100 w-4 rounded-2xl rounded-tr-none"
/>
<div>
<p className="text-lg font-semibold">{title}</p>
<p className="text-muted-foreground">{description}</p>
</div>
</span>
</motion.div>
{/* 🔥 Animated Carousel Items */}
<motion.div
variants={containerVariants}
initial="hidden"
animate="show"
>
<CarouselContent>
{categories.map((category) => (
<CarouselItem
key={category.id}
className="
md:basis-1/5
lg:basis-1/7
relative
after:content-['']
after:absolute
after:top-1/2
after:-translate-y-1/2
after:left-2
after:w-px
after:h-[50%]
after:bg-border
last:after:hidden
"
>
<motion.div variants={itemVariants} className="p-1">
<motion.div
whileHover={{
scale: 1.05,
y: -5,
}}
transition={{ type: "spring", stiffness: 300 }}
>
<Card dir="rtl" className="transition-shadow hover:shadow-xl cursor-pointer">
<CardContent className="flex flex-col gap-3 h-30 items-center justify-center p-6">
<motion.div
whileHover={{ rotate: 5 }}
transition={{ type: "spring", stiffness: 200 }}
>
<Image
src={category.image}
alt={category.title}
width={80}
height={80}
className="rounded-full"
/>
</motion.div>
<p className="font-semibold text-text-muted">
{category.title}
</p>
</CardContent>
</Card>
</motion.div>
</motion.div>
</CarouselItem>
))}
</CarouselContent>
</motion.div>
{/* 🔥 Animated Controls */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5 }}
className="absolute top-10 left-16 flex gap-2"
>
<motion.div whileTap={{ scale: 0.85 }}>
<CarouselPrevious />
</motion.div>
<motion.div whileTap={{ scale: 0.85 }}>
<CarouselNext />
</motion.div>
</motion.div>
</Carousel>
</div>
)
}

View File

@@ -1,10 +1,17 @@
import {CarouselComponent} from "./CarouselComponent";
import { CarouselComponent } from "./CarouselComponent"
import { useTranslations } from "next-intl"
const Categories = () => {
const t = useTranslations("Categories")
return (
<>
<CarouselComponent />
</>
<div className="container mx-auto my-16">
<CarouselComponent
title={t("title")}
description={t("description")}
/>
</div>
)
}
export default Categories;
export default Categories

View File

@@ -7,7 +7,7 @@ import useEmblaCarousel, {
import { cn } from "@/lib/utils"
import { Button } from "@/components/UI/button"
import {ChevronIcon} from "@/assets";
import {ArrowLeft, ArrowRight, ChevronIcon} from "@/assets";
type CarouselApi = UseEmblaCarouselType[1]
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
@@ -138,7 +138,7 @@ function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
ref={carouselRef}
className="overflow-hidden"
className="overflow-hidden border border-text-primary/10 p-1 rounded-2xl"
data-slot="carousel-content"
>
<div
@@ -185,7 +185,7 @@ function CarouselPrevious({
variant={variant}
size={size}
className={cn(
"rounded-full absolute touch-manipulation",
"rounded-xl p-5 absolute touch-manipulation bg-card border border-primary-300",
orientation === "horizontal"
? "top-1/2 -left-12 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
@@ -195,7 +195,7 @@ function CarouselPrevious({
onClick={scrollPrev}
{...props}
>
<ChevronIcon className="cn-rtl-flip" />
<ArrowLeft className="text-text-primary" />
<span className="sr-only">Previous slide</span>
</Button>
)
@@ -215,7 +215,7 @@ function CarouselNext({
variant={variant}
size={size}
className={cn(
"rounded-full absolute touch-manipulation",
"rounded-xl p-5 absolute touch-manipulation bg-card border-primary-300",
orientation === "horizontal"
? "top-1/2 -right-12 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
@@ -225,7 +225,7 @@ function CarouselNext({
onClick={scrollNext}
{...props}
>
<ChevronIcon className="cn-rtl-flip rotate-180" />
<ArrowRight className="text-text-primary size-5" />
<span className="sr-only">Next slide</span>
</Button>
)

View File

@@ -11,7 +11,7 @@ function Card({
<div
data-slot="card"
data-size={size}
className={cn("ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-xl py-4 text-sm ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col", className)}
className={cn("ring-foreground/10 text-card-foreground gap-4 overflow-hidden rounded-xl py-4 text-sm has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col", className)}
{...props}
/>
)

View File

@@ -0,0 +1,47 @@
export const categories = [
{
id: 1,
title: "خدمات مو",
image: "/images/categories/مو.jpg",
},
{
id: 2,
title: "خدمات میکاپ",
image: "/images/categories/میکاپ.jpg",
},
{
id: 3,
title: "خدمات ناخن",
image: "/images/categories/ناخن.jpg",
},
{
id: 4,
title: "خدمات فیشیال",
image: "/images/categories/فیشیال.jpg",
},
{
id: 5,
title: "خدمات مژه و ابرو",
image: "/images/categories/مژه.jpg",
},
{
id: 7,
title: "ژل و بوتاکس",
image: "/images/categories/ژل.jpg",
},
{
id: 8,
title: "ژل و بوتاکس",
image: "/images/categories/ژل.jpg",
},
{
id: 9,
title: "ژل و بوتاکس",
image: "/images/categories/ژل.jpg",
},
{
id: 10,
title: "ژل و بوتاکس",
image: "/images/categories/ژل.jpg",
},
]