Skip to content

Commit e582388

Browse files
committed
🤖 refactor: Convert KebabMenu to Tailwind CSS
- Replace all styled-components with Tailwind utility classes - Use cn() utility for conditional button and menu item states - Maintain portal rendering and click-outside behavior - Preserve disabled/active states with proper hover logic - ~45% code reduction (198 lines)
1 parent c8c16ae commit e582388

File tree

1 file changed

+28
-94
lines changed

1 file changed

+28
-94
lines changed

src/components/KebabMenu.tsx

Lines changed: 28 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,7 @@
11
import React, { useState, useRef, useEffect } from "react";
22
import { createPortal } from "react-dom";
3-
import styled from "@emotion/styled";
43
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";
845

856
export interface KebabMenuItem {
867
label: string;
@@ -149,47 +70,60 @@ export const KebabMenu: React.FC<KebabMenuProps> = ({ items, className }) => {
14970
};
15071

15172
const button = (
152-
<KebabButton
73+
<button
15374
ref={buttonRef}
154-
active={isOpen}
15575
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+
)}
15783
>
15884
159-
</KebabButton>
85+
</button>
16086
);
16187

16288
return (
16389
<>
164-
<MenuContainer>
90+
<div className="relative">
16591
<TooltipWrapper inline>
16692
{button}
16793
<Tooltip align="center">More actions</Tooltip>
16894
</TooltipWrapper>
169-
</MenuContainer>
95+
</div>
17096

17197
{isOpen &&
17298
createPortal(
173-
<DropdownMenu
99+
<div
174100
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"
175102
style={{
176103
top: `${dropdownPosition.top}px`,
177104
left: `${dropdownPosition.left}px`,
178105
}}
179106
>
180107
{items.map((item, index) => (
181-
<MenuItem
108+
<button
182109
key={index}
183-
active={item.active}
184-
disabled={item.disabled}
185110
onClick={() => handleItemClick(item)}
186111
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+
)}
187121
>
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>
191125
))}
192-
</DropdownMenu>,
126+
</div>,
193127
document.body
194128
)}
195129
</>

0 commit comments

Comments
 (0)