diff --git a/app/category/components/CategoryFilters.tsx b/app/category/components/CategoryFilters.tsx new file mode 100644 index 0000000..df35f56 --- /dev/null +++ b/app/category/components/CategoryFilters.tsx @@ -0,0 +1,87 @@ +"use client"; + +import Input from "@/app/components/Input"; +import { cn } from "@/app/lib/cn"; +import { AddSquare, MinusSquare } from "iconsax-reactjs"; +import { useState, type FC } from "react"; +import { FILTER_SECTIONS, type FilterSection } from "../constants"; + +const CategoryFilters: FC = () => { + const [openSections, setOpenSections] = useState>(() => Object.fromEntries(FILTER_SECTIONS.map((section) => [section.id, Boolean(section.defaultOpen)]))); + const [selected, setSelected] = useState([]); + + const toggleSection = (id: string) => { + setOpenSections((prev) => ({ ...prev, [id]: !prev[id] })); + }; + + const toggleOption = (option: string) => { + setSelected((prev) => (prev.includes(option) ? prev.filter((item) => item !== option) : [...prev, option])); + }; + + return ( +
+ + + +
+ ); +}; + +type FilterAccordionProps = { + section: FilterSection; + isOpen: boolean; + selected: string[]; + onToggle: () => void; + onToggleOption: (option: string) => void; +}; + +const FilterAccordion: FC = ({ section, isOpen, selected, onToggle, onToggleOption }) => { + const Icon = isOpen ? MinusSquare : AddSquare; + + return ( +
+ + + {isOpen && section.options && ( +
    + {section.options.map((option) => { + const checked = selected.includes(option); + + return ( +
  • + +
  • + ); + })} +
+ )} +
+ ); +}; + +export default CategoryFilters; diff --git a/app/category/components/CategoryHero.tsx b/app/category/components/CategoryHero.tsx new file mode 100644 index 0000000..c3e1b0a --- /dev/null +++ b/app/category/components/CategoryHero.tsx @@ -0,0 +1,47 @@ +import heroImage from "@/assets/images/category/hero.jpg"; +import { ArrowLeft2 } from "iconsax-reactjs"; +import Image from "next/image"; +import Link from "next/link"; +import { type FC } from "react"; +import { CATEGORY_DESCRIPTION, CATEGORY_TITLE } from "../constants"; + +const CategoryHero: FC = () => { + return ( + <> +
+
+
+

{CATEGORY_TITLE}

+

{CATEGORY_DESCRIPTION}

+
+ +
+ {CATEGORY_TITLE} +
+
+
+ + + + ); +}; + +export default CategoryHero; diff --git a/app/category/components/CategoryProducts.tsx b/app/category/components/CategoryProducts.tsx new file mode 100644 index 0000000..7230bfb --- /dev/null +++ b/app/category/components/CategoryProducts.tsx @@ -0,0 +1,16 @@ +import GridWrapper from "@/app/components/GridWrapper"; +import ProductCard from "@/app/home/components/ProductCard"; +import { type FC } from "react"; +import { CATEGORY_PRODUCTS } from "../constants"; + +const CategoryProducts: FC = () => { + return ( + + {CATEGORY_PRODUCTS.map((title, index) => ( + + ))} + + ); +}; + +export default CategoryProducts; diff --git a/app/category/components/CategoryToolbar.tsx b/app/category/components/CategoryToolbar.tsx new file mode 100644 index 0000000..6543609 --- /dev/null +++ b/app/category/components/CategoryToolbar.tsx @@ -0,0 +1,22 @@ +import { Sort } from "iconsax-reactjs"; +import { type FC } from "react"; +import { SORT_OPTIONS } from "../constants"; + +const CategoryToolbar: FC = () => { + return ( +
+
+ + +
+
+ ); +}; + +export default CategoryToolbar; diff --git a/app/category/constants.ts b/app/category/constants.ts new file mode 100644 index 0000000..39031a2 --- /dev/null +++ b/app/category/constants.ts @@ -0,0 +1,31 @@ +export const CATEGORY_TITLE = "جعبه"; + +export const CATEGORY_DESCRIPTION = + "جعبه محصول فقط یک محافظ نیست؛ اولین تجربه مشتری از برند شماست. با چاپ اختصاصی، رنگ‌ها و طراحی منحصربه‌فرد، بسته‌بندی‌ای خلق کنید که داستان برندتان را پیش از باز شدن جعبه روایت کند. جزئیات دقیق، چاپ باکیفیت و طراحی چشم‌نواز، تجربه‌ای ماندگار برای مشتریان شما می‌سازد."; + +export const SORT_OPTIONS = [ + { label: "پرفروش‌ترین", value: "bestseller" }, + { label: "جدیدترین", value: "newest" }, + { label: "ارزان‌ترین", value: "cheapest" }, + { label: "گران‌ترین", value: "expensive" }, +] as const; + +export type FilterSection = { + id: string; + title: string; + options?: readonly string[]; + defaultOpen?: boolean; +}; + +export const FILTER_SECTIONS: readonly FilterSection[] = [ + { id: "categories", title: "دسته بندی ها" }, + { + id: "paper", + title: "جنس ورق", + defaultOpen: true, + options: ["سه لا E فلوت کرافت", "سه لا یک رو سفید", "سه لا E فلوت سفید"], + }, + { id: "price", title: "بازه قیمتی" }, +]; + +export const CATEGORY_PRODUCTS = ["جعبه گل", "جعبه هدیه", "جعبه کفش", "جعبه کیبوردی", "جعبه گل", "جعبه هدیه", "جعبه کفش", "جعبه کیبوردی"] as const; diff --git a/app/category/page.tsx b/app/category/page.tsx new file mode 100644 index 0000000..4f6c5d2 --- /dev/null +++ b/app/category/page.tsx @@ -0,0 +1,28 @@ +import ContactCtaSection from "@/app/home/components/ContactCtaSection"; +import CategoryFilters from "./components/CategoryFilters"; +import CategoryHero from "./components/CategoryHero"; +import CategoryProducts from "./components/CategoryProducts"; +import CategoryToolbar from "./components/CategoryToolbar"; + +const CategoryDetailPage = () => { + return ( +
+ + +
+
+ + +
+ + +
+
+
+ + +
+ ); +}; + +export default CategoryDetailPage; diff --git a/app/components/Input.tsx b/app/components/Input.tsx index 369601c..65e66c7 100644 --- a/app/components/Input.tsx +++ b/app/components/Input.tsx @@ -7,22 +7,28 @@ export type InputVariant = "primary" | "search"; type Props = { variant?: InputVariant; className?: string; + iconClassName?: string; } & React.InputHTMLAttributes; -const Input: FC = (props) => { - const { variant = "primary", className, ...rest } = props; - +const Input: FC = ({ variant = "primary", className, iconClassName, ...rest }) => { return (
- {variant === "search" &&
); diff --git a/app/components/Select.tsx b/app/components/Select.tsx index 6092473..955940c 100644 --- a/app/components/Select.tsx +++ b/app/components/Select.tsx @@ -18,7 +18,7 @@ const Select: FC = ({ options, className, placeholder, children, ...rest