11'use client' ;
22
3- import { useState } from 'react' ;
4- import { Dialog , DialogPanel , DialogTitle } from '@headlessui/react' ;
5- import { X } from 'lucide-react' ;
3+ import { useMemo , useState } from 'react' ;
4+ import { Dialog , DialogBackdrop , DialogPanel , DialogTitle } from '@headlessui/react' ;
5+ import { Minus , Plus , X } from 'lucide-react' ;
66import { YUGIOH_FILTER_SECTIONS } from '@/constants/yugiohFilters' ;
77
88const CardFilter = ( {
9- filters, // { rarity: [], condition: [] }
9+ filters = { } , // { rarity: [], condition: [] }
1010 updateFilters, // (filterType, updatedValues) => void
11+ clearFilters,
12+ sections = YUGIOH_FILTER_SECTIONS ,
1113 open, // boolean (optional controlled prop)
1214 setOpen, // (open: boolean) => void (optional controlled setter)
1315 isModalOpen, // legacy boolean prop
@@ -36,6 +38,13 @@ const CardFilter = ( {
3638 const openFilters = ( ) => resolvedSetOpen ( true ) ;
3739 const closeFilters = ( ) => resolvedSetOpen ( false ) ;
3840
41+ const activeCount = useMemo ( ( ) => {
42+ return Object . values ( filters || { } ) . reduce (
43+ ( total , entries ) => total + ( Array . isArray ( entries ) ? entries . length : 0 ) ,
44+ 0 ,
45+ ) ;
46+ } , [ filters ] ) ;
47+
3948 const handleCheckboxChange = async ( event , filterType ) => {
4049 const { value, checked } = event . target ;
4150 const prev = filters [ filterType ] || [ ] ;
@@ -61,68 +70,102 @@ const CardFilter = ( {
6170 return (
6271 < div >
6372 { triggerContent }
64- < Dialog open = { resolvedOpen } onClose = { resolvedSetOpen } className = "relative z-10 " >
65- < div className = "fixed inset-0" />
66-
73+ < Dialog open = { resolvedOpen } onClose = { resolvedSetOpen } className = "relative z-[120]" >
74+ < DialogBackdrop
75+ transition
76+ className = "fixed inset-0 bg-black/70 backdrop-blur-sm transition-opacity duration-300 data-closed:opacity-0"
77+ />
6778 < div className = "fixed inset-0 overflow-hidden" >
6879 < div className = "absolute inset-0 overflow-hidden" >
69- < div className = "pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-12 sm:pl-2 glass backdrop " >
80+ < div className = "pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10 sm:pl-0 " >
7081 < DialogPanel
7182 transition
72- className = "pointer-events-auto w-screen max-w-md transform transition duration-500 ease-in- out data-closed:translate-x-full sm:duration-700 "
83+ className = "pointer-events-auto w-screen max-w-md transform transition duration-300 ease-out data-closed:translate-x-full"
7384 >
74- < div className = "relative flex h-full flex-col overflow-y-auto py-6 shadow-xl dark:bg-gray-800 dark:after:absolute dark:after:inset-y-0 dark:after:left-0 dark:after:w-px dark:after:bg-white/10" >
75- < div className = "px-4 sm:px-6" >
76- < div className = "flex items-start justify-between" >
77- < DialogTitle className = "text-base font-semibold text-white" >
78- { title }
79- </ DialogTitle >
80- < div className = "ml-3 flex h-7 items-center" >
85+ < div className = "relative flex h-full flex-col overflow-y-auto border-l border-white/10 bg-slate-950/92 py-6 text-white shadow-2xl backdrop-blur-xl" >
86+ < div className = "px-5 sm:px-6" >
87+ < div className = "flex items-start justify-between gap-3" >
88+ < div >
89+ < p className = "text-xs uppercase tracking-wide text-white/50" > Filters</ p >
90+ < DialogTitle className = "text-lg font-semibold text-white" >
91+ { title }
92+ </ DialogTitle >
93+ { activeCount > 0 && (
94+ < p className = "text-xs text-indigo-100/80" > { activeCount } active</ p >
95+ ) }
96+ </ div >
97+ < div className = "flex items-center gap-2" >
98+ { typeof clearFilters === 'function' && activeCount > 0 && (
99+ < button
100+ type = "button"
101+ onClick = { clearFilters }
102+ className = "rounded-full border border-white/10 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-white/70 transition hover:border-white/40 hover:text-white"
103+ >
104+ Clear All
105+ </ button >
106+ ) }
81107 < button
82108 type = "button"
83109 title = { "Close" }
84110 onClick = { closeFilters }
85- className = "relative rounded-md text-white hover:text-gray-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:hover:text-white dark:focus-visible:outline-indigo-500 "
111+ className = "relative rounded-md p-1 text-white/70 transition hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
86112 >
87- < span className = "absolute -inset-2.5" />
88113 < span className = "sr-only" > Close filters</ span >
89- < X color = { `red` } aria-hidden = "true" className = "size-6 " />
114+ < X aria-hidden = "true" className = "size-5 " />
90115 </ button >
91116 </ div >
92117 </ div >
93118 </ div >
94- < div className = "relative mt-6 flex-1 px-4 " >
95- < div className = "space-y-6" >
96- { YUGIOH_FILTER_SECTIONS . map ( filter => (
97- < div key = { filter . id } className = "space-y-3" >
98- < p className = "text-sm font-semibold text-white" > { filter . label } </ p >
99- < div className = "space-y-2" >
100- { filter . options . map ( value => (
101- < label
102- key = { value }
103- htmlFor = { `${ filter . id } -${ value } ` }
104- className = "flex items-start gap-2 text-sm text-gray-200"
105- >
106- < input
107- type = "checkbox"
108- id = { `${ filter . id } -${ value } ` }
109- value = { value }
110- checked = { filters [ filter . id ] ?. includes ( value ) || false }
111- onChange = { e => handleCheckboxChange ( e , filter . id ) }
112- className = "mt-1 h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-700"
113- />
114- < span > { value } </ span >
115- </ label >
116- ) ) }
117- </ div >
118- </ div >
119- ) ) }
119+ < div className = "relative mt-6 flex-1 px-5 sm:px-6" >
120+ < div className = "space-y-4" >
121+ { sections . map ( filter => {
122+ const activeValues = new Set ( filters [ filter . id ] || [ ] ) ;
123+
124+ return (
125+ < details
126+ key = { filter . id }
127+ className = "group rounded-2xl border border-white/10 bg-white/5"
128+ open
129+ >
130+ < summary className = "flex cursor-pointer items-center justify-between px-4 py-3 text-sm font-semibold tracking-wide text-white/80" >
131+ < span > { filter . label } </ span >
132+ < span className = "text-white/60 group-open:hidden" >
133+ < Plus size = { 16 } />
134+ </ span >
135+ < span className = "hidden text-white/60 group-open:block" >
136+ < Minus size = { 16 } />
137+ </ span >
138+ </ summary >
139+ < div className = "border-t border-white/5 px-4 py-3 text-sm" >
140+ < div className = "max-h-64 space-y-2 overflow-y-auto pr-1" >
141+ { filter . options . map ( value => (
142+ < label
143+ key = { value }
144+ htmlFor = { `${ filter . id } -${ value } ` }
145+ className = "flex items-center gap-3 rounded-lg border border-white/5 bg-white/5 px-3 py-2 text-white/80 transition hover:border-white/30"
146+ >
147+ < input
148+ type = "checkbox"
149+ id = { `${ filter . id } -${ value } ` }
150+ value = { value }
151+ checked = { activeValues . has ( value ) }
152+ onChange = { e => handleCheckboxChange ( e , filter . id ) }
153+ className = "h-4 w-4 rounded border-gray-300 text-indigo-500 focus:ring-indigo-400"
154+ />
155+ < span className = "text-sm" > { value } </ span >
156+ </ label >
157+ ) ) }
158+ </ div >
159+ </ div >
160+ </ details >
161+ ) ;
162+ } ) }
120163 </ div >
121164
122- < div className = "mt-6 border-t border-gray-200 pt-4 dark:border-white/10 " >
165+ < div className = "mt-6 border-t border-white/10 pt-4" >
123166 < button
124167 type = "button"
125- className = "flex w-full justify-center rounded-md bg-indigo-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
168+ className = "flex w-full justify-center rounded-md bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
126169 onClick = { closeFilters }
127170 >
128171 Apply Filters
0 commit comments