responsive base v2
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import Layout from '@/hoc/Layout'
|
||||
import { NextPage } from 'next'
|
||||
import {
|
||||
@@ -12,10 +14,14 @@ import Filters from '../components/Filters'
|
||||
import Sorts from '../components/Sorts'
|
||||
import GridWrapper from '@/components/GridWrapper'
|
||||
import ProductCard from '@/components/ProductCard'
|
||||
import { Setting5 } from 'iconsax-react'
|
||||
import { useState } from 'react'
|
||||
|
||||
const Products: NextPage = () => {
|
||||
const [showMobileFilters, setShowMobileFilters] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="mt-14 px-4 sm:px-8 md:px-20">
|
||||
<div className="mt-14 px-4 sm:px-6 md:px-8 lg:px-20">
|
||||
<Breadcrumb aria-label="breadcrumb">
|
||||
<BreadcrumbList className="text-sm text-muted-foreground">
|
||||
<BreadcrumbItem>
|
||||
@@ -32,8 +38,28 @@ const Products: NextPage = () => {
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
|
||||
<div className='mt-8 sm:mt-12 md:mt-14 flex flex-col lg:flex-row gap-4 sm:gap-6'>
|
||||
{/* Mobile Filter Button */}
|
||||
<div className='lg:hidden mt-4 flex justify-between items-center'>
|
||||
<button
|
||||
onClick={() => setShowMobileFilters(true)}
|
||||
className='flex items-center gap-2 px-3 py-2 border border-border rounded-lg bg-white shadow-sm'
|
||||
>
|
||||
<Setting5 size={18} color='#666666' />
|
||||
<span className='text-sm text-[#666666]'>فیلترها</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className='mt-6 sm:mt-8 md:mt-12 lg:mt-8 flex flex-col lg:flex-row gap-4 sm:gap-6'>
|
||||
{/* Desktop Filters */}
|
||||
<Filters />
|
||||
|
||||
{/* Mobile Filters Modal */}
|
||||
<Filters
|
||||
isMobile={true}
|
||||
isOpen={showMobileFilters}
|
||||
onClose={() => setShowMobileFilters(false)}
|
||||
/>
|
||||
|
||||
<div className='flex-1'>
|
||||
<Sorts />
|
||||
|
||||
|
||||
@@ -11,8 +11,15 @@ import { getCategories } from '@/share/service/ShareService'
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
import CategoryTree from './CategoryTree'
|
||||
import { Category } from '@/share/types/SharedTypes'
|
||||
import Modal from '@/components/Modal'
|
||||
|
||||
const Filters: FC = () => {
|
||||
type FiltersProps = {
|
||||
isMobile?: boolean
|
||||
isOpen?: boolean
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
const Filters: FC<FiltersProps> = ({ isMobile = false, isOpen = false, onClose }) => {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const searchParams = useSearchParams()
|
||||
@@ -64,6 +71,11 @@ const Filters: FC = () => {
|
||||
if (rating) params.set('rating', String(rating))
|
||||
else params.delete('rating')
|
||||
router.push(`${pathname}?${params.toString()}`)
|
||||
|
||||
// Close mobile modal after applying filters
|
||||
if (isMobile && onClose) {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
const clearFilters = () => {
|
||||
@@ -77,8 +89,8 @@ const Filters: FC = () => {
|
||||
router.push(`${pathname}?${params.toString()}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='w-[270px] border border-border rounded-[10px] p-5'>
|
||||
const FilterContent = () => (
|
||||
<>
|
||||
<div className='flex items-center gap-2.5'>
|
||||
<Setting5 size={24} color='#666666' />
|
||||
<div className='text-[#666666]'>فیلترها</div>
|
||||
@@ -169,6 +181,22 @@ const Filters: FC = () => {
|
||||
<Button variant='outline' onClick={clearFilters}>حذف</Button>
|
||||
<Button onClick={applyFilters}>اعمال</Button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Modal open={isOpen} close={onClose || (() => { })} title="فیلترها" isHeader={true}>
|
||||
<div className='max-h-[70vh] overflow-y-auto'>
|
||||
<FilterContent />
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='w-[270px] hidden lg:block border border-border rounded-[10px] p-5'>
|
||||
<FilterContent />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,39 +1,91 @@
|
||||
import { Sort } from 'iconsax-react'
|
||||
import { FC } from 'react'
|
||||
import { Sort, ArrowDown2 } from 'iconsax-react'
|
||||
import { FC, useState, useEffect, useRef } from 'react'
|
||||
|
||||
const sortOptions = [
|
||||
{ key: 'relevant', label: 'مرتبط ترین' },
|
||||
{ key: 'most_visited', label: 'پربازدیدترین' },
|
||||
{ key: 'newest', label: 'جدیدترین' },
|
||||
{ key: 'best_selling', label: 'پرفروش ترین' },
|
||||
{ key: 'cheapest', label: 'ارزانترین' },
|
||||
{ key: 'most_expensive', label: 'گرانترین' },
|
||||
{ key: 'customer_suggestion', label: 'پیشنهاد خریداران' },
|
||||
]
|
||||
|
||||
const Sorts: FC = () => {
|
||||
const [selectedSort, setSelectedSort] = useState('newest')
|
||||
const [showDropdown, setShowDropdown] = useState(false)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const selectedOption = sortOptions.find(option => option.key === selectedSort)
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setShowDropdown(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (showDropdown) {
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside)
|
||||
}
|
||||
}, [showDropdown])
|
||||
|
||||
return (
|
||||
<div className='flex justify-between items-center text-[#999999] text-sm border-b border-border pb-5'>
|
||||
<div className='flex items-center gap-4'>
|
||||
{/* Desktop Version */}
|
||||
<div className='hidden lg:flex items-center gap-4'>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<Sort size={24} color='#333333' />
|
||||
<div className='text-sm text-[#333333]'>مرتب سازی:</div>
|
||||
</div>
|
||||
|
||||
<div className='cursor-pointer'>
|
||||
مرتبط ترین
|
||||
</div>
|
||||
<div className='cursor-pointer'>
|
||||
پربازدیدترین
|
||||
</div>
|
||||
<div className='text-primary'>
|
||||
جدیدترین
|
||||
</div>
|
||||
<div className='cursor-pointer'>
|
||||
پرفروش ترین
|
||||
</div>
|
||||
<div className='cursor-pointer'>
|
||||
ارزانترین
|
||||
</div>
|
||||
<div className='cursor-pointer'>
|
||||
گرانترین
|
||||
</div>
|
||||
<div className='cursor-pointer'>
|
||||
پیشنهاد خریداران
|
||||
{sortOptions.map((option) => (
|
||||
<div
|
||||
key={option.key}
|
||||
className={`cursor-pointer ${selectedSort === option.key ? 'text-primary' : ''}`}
|
||||
onClick={() => setSelectedSort(option.key)}
|
||||
>
|
||||
{option.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{/* Mobile Version */}
|
||||
<div className='lg:hidden flex items-center gap-2 relative' ref={dropdownRef}>
|
||||
<Sort size={20} color='#333333' />
|
||||
<div className='text-sm text-[#333333]'>مرتب سازی:</div>
|
||||
<div
|
||||
className='flex items-center gap-1 cursor-pointer bg-gray-50 px-3 py-1.5 rounded-lg'
|
||||
onClick={() => setShowDropdown(!showDropdown)}
|
||||
>
|
||||
<span className='text-sm text-[#333333]'>{selectedOption?.label}</span>
|
||||
<ArrowDown2 size={16} color='#333333' />
|
||||
</div>
|
||||
|
||||
{showDropdown && (
|
||||
<div className='absolute top-full right-0 mt-2 bg-white border border-border rounded-lg shadow-lg z-10 min-w-[180px]'>
|
||||
{sortOptions.map((option) => (
|
||||
<div
|
||||
key={option.key}
|
||||
className={`px-4 py-3 cursor-pointer text-sm hover:bg-gray-50 ${selectedSort === option.key ? 'text-primary bg-blue-50' : 'text-[#333333]'
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSelectedSort(option.key)
|
||||
setShowDropdown(false)
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='text-sm'>
|
||||
100 کالا
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -33,7 +33,7 @@ const Input: FC<Props> = (props: Props) => {
|
||||
const [search, setSearch] = useState<string>('')
|
||||
|
||||
const inputClass = clx(
|
||||
'w-full h-10 sm:h-12 text-black block px-3 sm:px-4 text-xs sm:text-sm rounded-xl border border-[#D0D0D0]',
|
||||
'w-full h-10 text-black block px-4 text-xs rounded-xl border border-[#D0D0D0]',
|
||||
props.readOnly && 'bg-gray-100 border-0 text-description',
|
||||
props.variant === 'search' && 'bg-[#EEF0F7] border-0 pr-10',
|
||||
props.className
|
||||
@@ -80,7 +80,7 @@ const Input: FC<Props> = (props: Props) => {
|
||||
|
||||
return (
|
||||
<div className='w-full'>
|
||||
<label className='text-xs sm:text-sm text-black'>
|
||||
<label className='text-sm text-black'>
|
||||
{props.label}
|
||||
</label>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user