Skip to content

Commit 8faf503

Browse files
author
raineer
committed
fix codevs filter dropdown
1 parent a5291ae commit 8faf503

1 file changed

Lines changed: 151 additions & 89 deletions

File tree

Lines changed: 151 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

33
import { useEffect, useState } from "react";
4+
import { createPortal } from "react-dom";
45
import { Box } from "@/components/shared/dashboard";
56
import { Button } from "@/components/ui/button";
67
import { Position, Project, SkillCategory } from "@/types/home/codev";
@@ -37,8 +38,11 @@ export default function FilterCodevs({
3738
const [showFilter, setShowFilter] = useState(false);
3839
const [positions, setPositions] = useState<Position[]>([]);
3940
const [projects, setProjects] = useState<Project[]>([]);
40-
4141
const [supabase, setSupabase] = useState<any>(null);
42+
43+
// Track button position for dropdown placement
44+
const [buttonRef, setButtonRef] = useState<HTMLDivElement | null>(null);
45+
const [dropdownPosition, setDropdownPosition] = useState({ top: 0, right: 0 });
4246

4347
useEffect(() => {
4448
const supabaseClient = createClientClientComponent();
@@ -69,6 +73,17 @@ export default function FilterCodevs({
6973
fetchData();
7074
}, [supabase]);
7175

76+
// Calculate dropdown position when button ref changes or filter opens
77+
useEffect(() => {
78+
if (buttonRef && showFilter) {
79+
const rect = buttonRef.getBoundingClientRect();
80+
setDropdownPosition({
81+
top: rect.bottom + 8, // 8px gap below button
82+
right: window.innerWidth - rect.right, // Align right edge
83+
});
84+
}
85+
}, [buttonRef, showFilter]);
86+
7287
const toggleFilter = () => setShowFilter((prev) => !prev);
7388

7489
const clearFilter = () =>
@@ -88,106 +103,153 @@ export default function FilterCodevs({
88103
});
89104
};
90105

106+
// Render dropdown using portal to escape stacking context
107+
const renderDropdown = () => {
108+
if (typeof window === "undefined" || !showFilter) return null;
109+
110+
return createPortal(
111+
<>
112+
{/* Backdrop overlay - close on click outside */}
113+
<div
114+
className="fixed inset-0 z-40"
115+
onClick={() => setShowFilter(false)}
116+
aria-hidden="true"
117+
/>
118+
119+
{/* Filter dropdown panel */}
120+
<div
121+
className="fixed z-50 max-h-[calc(100vh-6rem)] min-h-[300px] w-[90vw] overflow-auto
122+
sm:w-96 md:w-80 rounded-2xl bg-white/95 backdrop-blur-md p-6 shadow-2xl
123+
dark:bg-gray-900/95 border border-gray-200 dark:border-gray-700"
124+
style={{
125+
top: `${dropdownPosition.top}px`,
126+
right: `${dropdownPosition.right}px`,
127+
}}
128+
>
129+
{/* Header with clear button */}
130+
<div className="mb-4 flex items-center justify-between">
131+
<h3 className="font-semibold text-gray-900 dark:text-white drop-shadow-sm">
132+
Filters
133+
</h3>
134+
<button
135+
onClick={clearFilter}
136+
className="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400
137+
dark:hover:text-blue-300 transition-colors duration-200 px-2 py-1
138+
rounded-md hover:bg-gray-100 dark:hover:bg-gray-800"
139+
>
140+
Clear all
141+
</button>
142+
</div>
143+
144+
{/* Filter tabs */}
145+
<Tabs defaultValue="position" className="w-full">
146+
<TabsList className="grid w-full grid-cols-3 bg-gray-100 dark:bg-gray-800
147+
border border-gray-200 dark:border-gray-700">
148+
<TabsTrigger value="position">Position</TabsTrigger>
149+
<TabsTrigger value="status">Status</TabsTrigger>
150+
<TabsTrigger value="projects">Projects</TabsTrigger>
151+
</TabsList>
152+
153+
{/* Position filter tab */}
154+
<TabsContent value="position" className="mt-4 space-y-2">
155+
{positions.map((pos) => (
156+
<div
157+
key={pos.id}
158+
className="flex items-center space-x-2 rounded-lg p-2 transition-colors
159+
hover:bg-gray-100 dark:hover:bg-gray-800"
160+
>
161+
<Checkbox
162+
id={`pos-${pos.id}`}
163+
checked={filters.positions.includes(pos.name || "")}
164+
onCheckedChange={() =>
165+
handleCheckedChange("positions", pos.name || "")
166+
}
167+
/>
168+
<label
169+
htmlFor={`pos-${pos.id}`}
170+
className="text-sm cursor-pointer text-gray-900 dark:text-gray-100"
171+
>
172+
{pos.name}
173+
</label>
174+
</div>
175+
))}
176+
</TabsContent>
177+
178+
{/* Status filter tab */}
179+
<TabsContent value="status" className="mt-4 space-y-2">
180+
{AVAILABILITY_STATUS.map((status) => (
181+
<div
182+
key={status}
183+
className="flex items-center space-x-2 rounded-lg p-2 transition-colors
184+
hover:bg-gray-100 dark:hover:bg-gray-800"
185+
>
186+
<Checkbox
187+
id={`status-${status}`}
188+
checked={filters.availability.includes(status)}
189+
onCheckedChange={() =>
190+
handleCheckedChange("availability", status)
191+
}
192+
/>
193+
<label
194+
htmlFor={`status-${status}`}
195+
className="text-sm cursor-pointer text-gray-900 dark:text-gray-100"
196+
>
197+
{status}
198+
</label>
199+
</div>
200+
))}
201+
</TabsContent>
202+
203+
{/* Projects filter tab */}
204+
<TabsContent value="projects" className="mt-4 space-y-2">
205+
{projects.map((project) => (
206+
<div
207+
key={project.id}
208+
className="flex items-center space-x-2 rounded-lg p-2 transition-colors
209+
hover:bg-gray-100 dark:hover:bg-gray-800"
210+
>
211+
<Checkbox
212+
id={project.id}
213+
checked={filters.projects.includes(project.id)}
214+
onCheckedChange={() =>
215+
handleCheckedChange("projects", project.id)
216+
}
217+
/>
218+
<label
219+
htmlFor={project.name}
220+
className="text-sm cursor-pointer text-gray-900 dark:text-gray-100"
221+
>
222+
{project.name}
223+
</label>
224+
</div>
225+
))}
226+
</TabsContent>
227+
</Tabs>
228+
</div>
229+
</>,
230+
document.body
231+
);
232+
};
233+
91234
return (
92-
<div className="relative flex justify-end">
235+
<div className="relative flex justify-end" ref={setButtonRef}>
236+
{/* Filter toggle button */}
93237
<Button
94238
variant="default"
95239
size="sm"
96240
onClick={toggleFilter}
97241
className={cn(
98242
"text-white transition-all duration-300 backdrop-blur-sm border shadow-lg",
99243
showFilter
100-
? "bg-gradient-to-r from-pink-500/80 to-rose-600/80 hover:from-pink-600 hover:to-rose-700 border-pink-400/50"
101-
: "bg-gradient-to-r from-customBlue-500/80 to-indigo-500/80 hover:from-customBlue-600 hover:to-indigo-600 border-customBlue-400/50"
244+
? "bg-gradient-to-r from-pink-500/80 to-rose-600/80 hover:from-pink-600 hover:to-rose-700 border-pink-400/50"
245+
: "bg-gradient-to-r from-customBlue-500/80 to-indigo-500/80 hover:from-customBlue-600 hover:to-indigo-600 border-customBlue-400/50"
102246
)}
103247
>
104248
{showFilter ? "Close filter" : "Filter"}
105249
</Button>
106250

107-
<div
108-
className={`absolute right-0 top-12 z-[1] max-h-[calc(100vh-20rem)] min-h-[300px] w-[300%] overflow-auto
109-
sm:-right-3/4 sm:w-96 md:right-0 md:w-80 rounded-2xl bg-white/10 backdrop-blur-md p-6 shadow-2xl dark:bg-white/5 border border-white/20 dark:border-white/10
110-
${!showFilter && "hidden"}
111-
`}
112-
>
113-
<div className="mb-4 flex items-center justify-between">
114-
<h3 className="font-semibold text-white drop-shadow-sm">Filters</h3>
115-
<button
116-
onClick={clearFilter}
117-
className="text-sm text-customBlue-300 hover:text-customBlue-100 dark:text-customBlue-400 dark:hover:text-customBlue-200 transition-colors duration-200 px-2 py-1 rounded-md hover:bg-white/20 dark:hover:bg-white/10"
118-
>
119-
Clear all
120-
</button>
121-
</div>
122-
123-
<Tabs defaultValue="position" className="w-full">
124-
<TabsList className="grid w-full grid-cols-3 bg-white/10 backdrop-blur-sm border border-white/20 dark:border-white/10">
125-
<TabsTrigger value="position">Position</TabsTrigger>
126-
<TabsTrigger value="status">Status</TabsTrigger>
127-
<TabsTrigger value="projects">Projects</TabsTrigger>
128-
</TabsList>
129-
130-
<TabsContent value="position" className="mt-4 space-y-2">
131-
{positions.map((pos) => (
132-
<div
133-
key={pos.id}
134-
className="flex items-center space-x-2 rounded-lg p-2 transition-colors hover:bg-white/20 dark:hover:bg-white/10"
135-
>
136-
<Checkbox
137-
id={`pos-${pos.id}`}
138-
checked={filters.positions.includes(pos.name || "")}
139-
onCheckedChange={() =>
140-
handleCheckedChange("positions", pos.name || "")
141-
}
142-
/>
143-
<label htmlFor={`pos-${pos.id}`} className="text-sm cursor-pointer text-white dark:text-gray-200">
144-
{pos.name}
145-
</label>
146-
</div>
147-
))}
148-
</TabsContent>
149-
150-
<TabsContent value="status" className="mt-4 space-y-2">
151-
{AVAILABILITY_STATUS.map((status) => (
152-
<div
153-
key={status}
154-
className="flex items-center space-x-2 rounded-lg p-2 transition-colors hover:bg-white/20 dark:hover:bg-white/10"
155-
>
156-
<Checkbox
157-
id={`status-${status}`}
158-
checked={filters.availability.includes(status)}
159-
onCheckedChange={() =>
160-
handleCheckedChange("availability", status)
161-
}
162-
/>
163-
<label htmlFor={`status-${status}`} className="text-sm cursor-pointer text-white dark:text-gray-200">
164-
{status}
165-
</label>
166-
</div>
167-
))}
168-
</TabsContent>
169-
170-
<TabsContent value="projects" className="mt-4 space-y-2">
171-
{projects.map((project) => (
172-
<div
173-
key={project.id}
174-
className="flex items-center space-x-2 rounded-lg p-2 transition-colors hover:bg-white/20 dark:hover:bg-white/10"
175-
>
176-
<Checkbox
177-
id={project.id}
178-
checked={filters.projects.includes(project.id)}
179-
onCheckedChange={() =>
180-
handleCheckedChange("projects", project.id)
181-
}
182-
/>
183-
<label htmlFor={project.name} className="text-sm cursor-pointer text-white dark:text-gray-200">
184-
{project.name}
185-
</label>
186-
</div>
187-
))}
188-
</TabsContent>
189-
</Tabs>
190-
</div>
251+
{/* Render dropdown in portal (z-50 above everything) */}
252+
{renderDropdown()}
191253
</div>
192254
);
193-
}
255+
}

0 commit comments

Comments
 (0)