Skip to content

Commit 5455c4d

Browse files
feat: src/components/Dropdown.tsx
1 parent 160fcac commit 5455c4d

1 file changed

Lines changed: 99 additions & 0 deletions

File tree

src/components/Dropdown.tsx

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import React, { useState, useRef, useEffect } from "react";
2+
3+
export interface DropdownOption {
4+
value: string;
5+
label: string;
6+
icon?: string;
7+
}
8+
9+
export interface DropdownProps {
10+
options: DropdownOption[];
11+
value?: string;
12+
onChange: (value: string) => void;
13+
placeholder?: string;
14+
disabled?: boolean;
15+
}
16+
17+
export function Dropdown({ options, value, onChange, placeholder = "Select...", disabled }: DropdownProps) {
18+
const [open, setOpen] = useState(false);
19+
const ref = useRef<HTMLDivElement>(null);
20+
21+
const selected = options.find(o => o.value === value);
22+
23+
useEffect(() => {
24+
function handleClick(e: MouseEvent) {
25+
if (ref.current && !ref.current.contains(e.target as Node)) {
26+
setOpen(false);
27+
}
28+
}
29+
document.addEventListener("mousedown", handleClick);
30+
return () => document.removeEventListener("mousedown", handleClick);
31+
}, []);
32+
33+
return (
34+
<div ref={ref} style={{ position: "relative", display: "inline-block", minWidth: "160px" }}>
35+
<button
36+
onClick={() => !disabled && setOpen(!open)}
37+
disabled={disabled}
38+
style={{
39+
width: "100%",
40+
padding: "10px 14px",
41+
border: "1px solid #2a2a3a",
42+
borderRadius: "10px",
43+
background: "#14141e",
44+
color: selected ? "#e4e4e7" : "#71717a",
45+
fontSize: "14px",
46+
cursor: disabled ? "not-allowed" : "pointer",
47+
display: "flex",
48+
justifyContent: "space-between",
49+
alignItems: "center",
50+
fontFamily: "inherit",
51+
}}
52+
>
53+
<span>{selected ? (selected.icon ? selected.icon + " " : "") + selected.label : placeholder}</span>
54+
<span style={{ fontSize: "10px" }}>{open ? "\u25B2" : "\u25BC"}</span>
55+
</button>
56+
{open && (
57+
<div style={{
58+
position: "absolute",
59+
top: "calc(100% + 4px)",
60+
left: 0,
61+
right: 0,
62+
background: "#1a1a26",
63+
border: "1px solid #2a2a3a",
64+
borderRadius: "10px",
65+
overflow: "hidden",
66+
boxShadow: "0 8px 24px rgba(0,0,0,0.4)",
67+
zIndex: 100,
68+
animation: "dropIn 0.15s ease",
69+
}}>
70+
<style>{"@keyframes dropIn { from { opacity: 0; transform: translateY(-4px); } }"}</style>
71+
{options.map(opt => (
72+
<button
73+
key={opt.value}
74+
onClick={() => { onChange(opt.value); setOpen(false); }}
75+
style={{
76+
width: "100%",
77+
padding: "10px 14px",
78+
border: "none",
79+
background: opt.value === value ? "rgba(99,102,241,0.15)" : "transparent",
80+
color: opt.value === value ? "#6366f1" : "#e4e4e7",
81+
fontSize: "14px",
82+
cursor: "pointer",
83+
textAlign: "left",
84+
fontFamily: "inherit",
85+
display: "flex",
86+
gap: "8px",
87+
}}
88+
onMouseEnter={e => (e.currentTarget.style.background = "rgba(99,102,241,0.1)")}
89+
onMouseLeave={e => (e.currentTarget.style.background = opt.value === value ? "rgba(99,102,241,0.15)" : "transparent")}
90+
>
91+
{opt.icon && <span>{opt.icon}</span>}
92+
{opt.label}
93+
</button>
94+
))}
95+
</div>
96+
)}
97+
</div>
98+
);
99+
}

0 commit comments

Comments
 (0)