All files / islands / StatusDropdown.tsx

100.00% Branches 0/0
0.68% Lines 1/148
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
x2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 








































































































































































































































import { useEffect, useRef, useState } from "preact/hooks";

interface StatusDropdownProps {
  active?: boolean;
}

export default function StatusDropdown(
  { active = false }: StatusDropdownProps,
) {
  const [isOpen, setIsOpen] = useState(false);
  const [isClosing, setIsClosing] = useState(false);
  const [currentPath, setCurrentPath] = useState("");
  const dropdownRef = useRef<HTMLDivElement>(null);

  const statusItems = [
    { href: "/status", label: "状态码总览", icon: "📋" },
    { href: "/status/401", label: "401 未授权", icon: "🔒" },
    { href: "/status/403", label: "403 禁止访问", icon: "🚫" },
    { href: "/nonexistent-page", label: "404 页面未找到", icon: "🔍" },
    { href: "/status/500", label: "500 服务器错误", icon: "❌" },
    { href: "/status/502", label: "502 网关错误", icon: "🌐" },
    { href: "/status/503", label: "503 服务不可用", icon: "🔧" },
  ];

  // 获取当前路径
  useEffect(() => {
    setCurrentPath(globalThis.location.pathname);
  }, []);

  // 检查菜单项是否为激活状态
  const isItemActive = (href: string) => {
    if (href === "/nonexistent-page") {
      // 404页面的特殊处理
      return currentPath !== "/" && currentPath !== "/status" &&
        currentPath !== "/status/401" && currentPath !== "/status/403" &&
        currentPath !== "/status/500" && currentPath !== "/status/502" &&
        currentPath !== "/status/503" && currentPath !== "/components" &&
        currentPath !== "/hooks" && currentPath !== "/state" &&
        currentPath !== "/about";
    }
    return currentPath === href;
  };

  // 关闭下拉菜单的函数
  const closeDropdown = () => {
    setIsClosing(true);
    setTimeout(() => {
      setIsOpen(false);
      setIsClosing(false);
    }, 150); // 给动画时间完成
  };

  // 点击外部关闭下拉菜单
  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (
        dropdownRef.current &&
        !dropdownRef.current.contains(event.target as Node)
      ) {
        closeDropdown();
      }
    };

    if (isOpen) {
      document.addEventListener("mousedown", handleClickOutside);
    }

    return () => {
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, [isOpen]);

  // 处理菜单项点击
  const handleItemClick = (href: string) => {
    // 延迟导航,让关闭动画完成
    closeDropdown();
    setTimeout(() => {
      globalThis.location.href = href;
    }, 100);
  };

  return (
    <div className="relative" ref={dropdownRef}>
      <button
        type="button"
        onClick={() => {
          if (isOpen) {
            closeDropdown();
          } else {
            setIsOpen(true);
          }
        }}
        className={`
          relative px-4 py-2 rounded-xl text-sm font-medium 
          transition-all duration-300 ease-out
          group overflow-hidden
          ${
          isOpen || active
            ? "bg-gradient-to-r from-blue-500 to-purple-600 text-white shadow-lg shadow-blue-500/25 transform scale-105"
            : "text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gradient-to-r hover:from-blue-50 hover:to-purple-50 dark:hover:from-blue-900/30 dark:hover:to-purple-900/30 hover:shadow-md hover:scale-105"
        } flex items-center gap-2
        `}
      >
        {/* 活跃状态的发光效果 */}
        {(isOpen || active) && (
          <div className="absolute inset-0 bg-gradient-to-r from-blue-600 to-purple-700 opacity-50 blur-sm">
          </div>
        )}

        {/* 悬停时的动画背景 */}
        <div
          className={`
          absolute inset-0 bg-gradient-to-r from-blue-100 to-purple-100 dark:from-blue-800/30 dark:to-purple-800/30
          transform origin-left scale-x-0 group-hover:scale-x-100
          transition-transform duration-300 ease-out rounded-xl
          ${(isOpen || active) ? "opacity-0" : "opacity-100"}
        `}
        >
        </div>

        {/* 文本内容 */}
        <span className="relative z-10 tracking-wide">状态码</span>
        <svg
          className={`relative z-10 w-4 h-4 transition-all duration-300 ${
            isOpen ? "rotate-180 scale-110" : "group-hover:scale-110"
          }`}
          fill="none"
          stroke="currentColor"
          viewBox="0 0 24 24"
        >
          <path
            strokeLinecap="round"
            strokeLinejoin="round"
            strokeWidth={2}
            d="M19 9l-7 7-7-7"
          />
        </svg>

        {/* 底部装饰线 */}
        {!(isOpen || active) && (
          <div className="absolute bottom-0 left-1/2 w-0 h-0.5 bg-gradient-to-r from-blue-500 to-purple-600 group-hover:w-full group-hover:left-0 transition-all duration-300 rounded-full">
          </div>
        )}
      </button>

      {isOpen && (
        <div
          className={`
            absolute top-full left-0 mt-2 w-56 
            bg-white/95 dark:bg-gray-900/95 backdrop-blur-xl 
            rounded-2xl shadow-2xl border border-gray-200/50 dark:border-gray-700/50 
            z-[9999] overflow-hidden
            transition-all duration-200 ease-out
            ${
            isClosing
              ? "opacity-0 transform scale-95 translate-y-2"
              : "opacity-100 transform scale-100 translate-y-0"
          }
          `}
        >
          {/* 装饰性渐变背景 */}
          <div className="absolute inset-0 bg-gradient-to-br from-blue-600/5 via-transparent to-purple-600/5 dark:from-blue-400/10 dark:to-purple-400/10">
          </div>

          <div className="relative py-2">
            {statusItems.map((item, index) => {
              const itemActive = isItemActive(item.href);
              return (
                <button
                  key={item.href}
                  type="button"
                  onClick={() => handleItemClick(item.href)}
                  className={`
                    w-full flex items-center gap-3 px-4 py-3 text-sm text-left
                    transition-all duration-200 group relative overflow-hidden
                    ${isClosing ? "opacity-50" : "opacity-100"}
                    ${
                    itemActive
                      ? "bg-gradient-to-r from-blue-500/20 to-purple-500/20 text-blue-600 dark:text-blue-400 font-medium"
                      : "text-gray-700 dark:text-gray-300 hover:bg-gradient-to-r hover:from-blue-50 hover:to-purple-50 dark:hover:from-blue-900/30 dark:hover:to-purple-900/30 hover:text-blue-600 dark:hover:text-blue-400"
                  }
                  `}
                  style={{
                    transitionDelay: isClosing ? "0ms" : `${index * 30}ms`,
                  }}
                >
                  {/* 激活状态的左侧装饰条 */}
                  {itemActive && (
                    <div className="absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-blue-500 to-purple-600 rounded-r-full">
                    </div>
                  )}

                  {/* 悬停背景动画 */}
                  {!itemActive && (
                    <div className="absolute inset-0 bg-gradient-to-r from-blue-100 to-purple-100 dark:from-blue-800/30 dark:to-purple-800/30 transform origin-left scale-x-0 group-hover:scale-x-100 transition-transform duration-300 ease-out">
                    </div>
                  )}

                  <span
                    className={`relative z-10 text-lg ${
                      itemActive ? "scale-110" : ""
                    } transition-transform duration-200`}
                  >
                    {item.icon}
                  </span>
                  <span className="relative z-10 tracking-wide">
                    {item.label}
                  </span>

                  {/* 激活状态的右侧图标 */}
                  {itemActive && (
                    <div className="ml-auto relative z-10">
                      <svg
                        className="w-4 h-4 text-blue-500"
                        fill="currentColor"
                        viewBox="0 0 20 20"
                      >
                        <path
                          fillRule="evenodd"
                          d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
                          clipRule="evenodd"
                        />
                      </svg>
                    </div>
                  )}
                </button>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
}