responsive base v2

This commit is contained in:
hamid zarghami
2025-09-02 14:53:58 +03:30
parent 2fa9ab1bbb
commit 138f26e293
4 changed files with 138 additions and 32 deletions
+28 -2
View File
@@ -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 />
+31 -3
View File
@@ -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>
)
}
+77 -25
View File
@@ -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'>
پیشنهاد خریداران
</div>
{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>
+2 -2
View File
@@ -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>