|
1 | 1 | import React, { useState, useRef, useEffect } from "react"; |
2 | 2 | import { createPortal } from "react-dom"; |
3 | | -import styled from "@emotion/styled"; |
4 | 3 | import { TooltipWrapper, Tooltip } from "./Tooltip"; |
5 | | - |
6 | | -const KebabButton = styled.button<{ active?: boolean }>` |
7 | | - background: ${(props) => (props.active ? "rgba(255, 255, 255, 0.1)" : "none")}; |
8 | | - border: 1px solid rgba(255, 255, 255, 0.2); |
9 | | - color: #cccccc; |
10 | | - font-size: 10px; |
11 | | - padding: 2px 8px; |
12 | | - border-radius: 3px; |
13 | | - cursor: pointer; |
14 | | - transition: all 0.2s ease; |
15 | | - font-family: var(--font-primary); |
16 | | - display: flex; |
17 | | - align-items: center; |
18 | | - justify-content: center; |
19 | | - white-space: nowrap; |
20 | | -
|
21 | | - &:hover { |
22 | | - background: rgba(255, 255, 255, 0.1); |
23 | | - border-color: rgba(255, 255, 255, 0.3); |
24 | | - } |
25 | | -
|
26 | | - &:disabled { |
27 | | - opacity: 0.5; |
28 | | - cursor: not-allowed; |
29 | | - } |
30 | | -`; |
31 | | - |
32 | | -const DropdownMenu = styled.div` |
33 | | - position: fixed; |
34 | | - background: #1e1e1e; |
35 | | - border: 1px solid #3e3e42; |
36 | | - border-radius: 3px; |
37 | | - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.8); |
38 | | - z-index: 10000; |
39 | | - min-width: 160px; |
40 | | - overflow: hidden; |
41 | | -`; |
42 | | - |
43 | | -const MenuItem = styled.button<{ active?: boolean; disabled?: boolean }>` |
44 | | - width: 100%; |
45 | | - background: ${(props) => (props.active ? "rgba(255, 255, 255, 0.15)" : "#1e1e1e")}; |
46 | | - border: none; |
47 | | - border-bottom: 1px solid #2d2d30; |
48 | | - color: ${(props) => (props.disabled ? "#808080" : "#cccccc")}; |
49 | | - font-size: 11px; |
50 | | - padding: 8px 12px; |
51 | | - text-align: left; |
52 | | - cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")}; |
53 | | - transition: all 0.15s ease; |
54 | | - font-family: var(--font-primary); |
55 | | - display: flex; |
56 | | - align-items: center; |
57 | | - gap: 8px; |
58 | | - opacity: ${(props) => (props.disabled ? 0.5 : 1)}; |
59 | | -
|
60 | | - &:last-child { |
61 | | - border-bottom: none; |
62 | | - } |
63 | | -
|
64 | | - &:hover { |
65 | | - background: ${(props) => (props.disabled ? "#1e1e1e" : "rgba(255, 255, 255, 0.15)")}; |
66 | | - color: ${(props) => (props.disabled ? "#808080" : "#ffffff")}; |
67 | | - } |
68 | | -`; |
69 | | - |
70 | | -const MenuItemEmoji = styled.span` |
71 | | - font-size: 13px; |
72 | | - width: 16px; |
73 | | - text-align: center; |
74 | | - flex-shrink: 0; |
75 | | -`; |
76 | | - |
77 | | -const MenuItemLabel = styled.span` |
78 | | - flex: 1; |
79 | | -`; |
80 | | - |
81 | | -const MenuContainer = styled.div` |
82 | | - position: relative; |
83 | | -`; |
| 4 | +import { cn } from "@/lib/utils"; |
84 | 5 |
|
85 | 6 | export interface KebabMenuItem { |
86 | 7 | label: string; |
@@ -149,47 +70,60 @@ export const KebabMenu: React.FC<KebabMenuProps> = ({ items, className }) => { |
149 | 70 | }; |
150 | 71 |
|
151 | 72 | const button = ( |
152 | | - <KebabButton |
| 73 | + <button |
153 | 74 | ref={buttonRef} |
154 | | - active={isOpen} |
155 | 75 | onClick={() => setIsOpen(!isOpen)} |
156 | | - className={className} |
| 76 | + className={cn( |
| 77 | + "border border-white/20 text-[#cccccc] text-[10px] py-0.5 px-2 rounded-[3px] cursor-pointer transition-all duration-200 font-primary flex items-center justify-center whitespace-nowrap", |
| 78 | + isOpen ? "bg-white/10" : "bg-none", |
| 79 | + "hover:bg-white/10 hover:border-white/30", |
| 80 | + "disabled:opacity-50 disabled:cursor-not-allowed", |
| 81 | + className |
| 82 | + )} |
157 | 83 | > |
158 | 84 | ⋮ |
159 | | - </KebabButton> |
| 85 | + </button> |
160 | 86 | ); |
161 | 87 |
|
162 | 88 | return ( |
163 | 89 | <> |
164 | | - <MenuContainer> |
| 90 | + <div className="relative"> |
165 | 91 | <TooltipWrapper inline> |
166 | 92 | {button} |
167 | 93 | <Tooltip align="center">More actions</Tooltip> |
168 | 94 | </TooltipWrapper> |
169 | | - </MenuContainer> |
| 95 | + </div> |
170 | 96 |
|
171 | 97 | {isOpen && |
172 | 98 | createPortal( |
173 | | - <DropdownMenu |
| 99 | + <div |
174 | 100 | ref={menuRef} |
| 101 | + className="fixed bg-[#1e1e1e] border border-[#3e3e42] rounded-[3px] shadow-[0_4px_16px_rgba(0,0,0,0.8)] z-[10000] min-w-[160px] overflow-hidden" |
175 | 102 | style={{ |
176 | 103 | top: `${dropdownPosition.top}px`, |
177 | 104 | left: `${dropdownPosition.left}px`, |
178 | 105 | }} |
179 | 106 | > |
180 | 107 | {items.map((item, index) => ( |
181 | | - <MenuItem |
| 108 | + <button |
182 | 109 | key={index} |
183 | | - active={item.active} |
184 | | - disabled={item.disabled} |
185 | 110 | onClick={() => handleItemClick(item)} |
186 | 111 | title={item.tooltip} |
| 112 | + className={cn( |
| 113 | + "w-full border-none border-b border-[#2d2d30] text-[11px] py-2 px-3 text-left transition-all duration-150 font-primary flex items-center gap-2", |
| 114 | + "last:border-b-0", |
| 115 | + item.disabled |
| 116 | + ? "bg-[#1e1e1e] text-[#808080] cursor-not-allowed opacity-50 hover:bg-[#1e1e1e] hover:text-[#808080]" |
| 117 | + : item.active |
| 118 | + ? "bg-white/15 text-[#cccccc] cursor-pointer hover:bg-white/15 hover:text-white" |
| 119 | + : "bg-[#1e1e1e] text-[#cccccc] cursor-pointer hover:bg-white/15 hover:text-white" |
| 120 | + )} |
187 | 121 | > |
188 | | - {item.emoji && <MenuItemEmoji>{item.emoji}</MenuItemEmoji>} |
189 | | - <MenuItemLabel>{item.label}</MenuItemLabel> |
190 | | - </MenuItem> |
| 122 | + {item.emoji && <span className="text-[13px] w-4 text-center flex-shrink-0">{item.emoji}</span>} |
| 123 | + <span className="flex-1">{item.label}</span> |
| 124 | + </button> |
191 | 125 | ))} |
192 | | - </DropdownMenu>, |
| 126 | + </div>, |
193 | 127 | document.body |
194 | 128 | )} |
195 | 129 | </> |
|
0 commit comments