-
-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathColorThemeDropdown.tsx
More file actions
146 lines (132 loc) · 5.07 KB
/
ColorThemeDropdown.tsx
File metadata and controls
146 lines (132 loc) · 5.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import { useEffect, useState, useRef } from "react";
import { themes, changeTheme, subscribeToThemeChanges, initializeThemeWatcher } from "../lib/themeUtils";
export default function ColorThemeDropdown() {
const [currentTheme, setCurrentTheme] = useState<string>("default");
const [isOpen, setIsOpen] = useState(false);
const [isMounted, setIsMounted] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setIsMounted(true);
// Initialize theme watcher (only once globally, but safe to call multiple times)
const watcherCleanup = initializeThemeWatcher();
// Subscribe to theme changes
const unsubscribe = subscribeToThemeChanges((themeId) => {
setCurrentTheme(themeId);
});
return () => {
unsubscribe();
watcherCleanup();
};
}, []);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setIsOpen(false);
}
};
if (isOpen) {
document.addEventListener("mousedown", handleClickOutside);
}
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [isOpen]);
const handleThemeChange = (themeId: string) => {
changeTheme(themeId);
setIsOpen(false);
};
if (!isMounted) return null;
const currentThemeData = themes.find((t) => t.id === currentTheme) || themes[0];
return (
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setIsOpen(!isOpen)}
className="w-10 h-10 rounded-lg bg-gray-100 dark:bg-ipython-slate text-theme-primary dark:text-theme-secondary hover:bg-gray-200 dark:hover:bg-ipython-slate/70 transition-all flex items-center justify-center overflow-hidden"
aria-label="Select color theme"
title={`Color theme: ${currentThemeData.name}`}
>
{currentThemeData.id === 'random' ? (
<div className="w-8 h-8 rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center">
<svg
className="w-5 h-5 text-gray-700 dark:text-gray-300"
fill="none"
stroke="currentColor"
strokeWidth="2"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"
/>
</svg>
</div>
) : (
<div
className="w-8 h-8 rounded-full"
style={{ backgroundImage: currentThemeData.dotGradient }}
/>
)}
</button>
{isOpen && (
<div className="absolute right-0 mt-2 w-56 rounded-lg bg-white dark:bg-ipython-slate border border-gray-200 dark:border-gray-700 shadow-lg z-50 overflow-hidden">
<div className="p-2 max-h-96 overflow-y-auto">
{themes.map((theme) => (
<button
key={theme.id}
onClick={() => handleThemeChange(theme.id)}
className={`w-full flex items-center gap-3 px-3 py-2 rounded-md text-left transition-colors ${
currentTheme === theme.id
? "bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100"
: "text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800"
}`}
>
{theme.id === 'random' ? (
<div className="w-5 h-5 rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center flex-shrink-0">
<svg
className="w-3.5 h-3.5 text-gray-700 dark:text-gray-300"
fill="none"
stroke="currentColor"
strokeWidth="2"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"
/>
</svg>
</div>
) : (
<div
className="w-5 h-5 rounded-full flex-shrink-0"
style={{ backgroundImage: theme.dotGradient }}
/>
)}
<span className="text-sm font-medium">{theme.name}</span>
{currentTheme === theme.id && (
<svg
className="w-4 h-4 ml-auto text-theme-primary dark:text-theme-secondary"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
)}
</button>
))}
</div>
</div>
)}
</div>
);
}