Skip to content

Commit 29e59a1

Browse files
committed
migrate ButtonDropdown + install dropdownmenu from shadcn
1 parent 219c230 commit 29e59a1

File tree

4 files changed

+291
-68
lines changed

4 files changed

+291
-68
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"@radix-ui/react-checkbox": "^1.1.1",
4040
"@radix-ui/react-compose-refs": "^1.1.0",
4141
"@radix-ui/react-dialog": "^1.1.1",
42+
"@radix-ui/react-dropdown-menu": "^2.1.1",
4243
"@radix-ui/react-navigation-menu": "^1.2.0",
4344
"@radix-ui/react-popover": "^1.1.1",
4445
"@radix-ui/react-portal": "^1.1.1",

src/components/ButtonDropdown.tsx

Lines changed: 49 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
import React, { MouseEvent, useState } from "react"
2-
import { useTranslation } from "next-i18next"
32
import { MdMenu } from "react-icons/md"
4-
import {
5-
Button,
6-
ButtonProps,
7-
Menu,
8-
MenuButton,
9-
MenuItem,
10-
MenuList,
11-
} from "@chakra-ui/react"
123

13-
// Utils
4+
import { cn } from "@/lib/utils/cn"
145
import { trackCustomEvent } from "@/lib/utils/matomo"
156

16-
// Components
17-
import { BaseLink } from "./Link"
7+
import { Button } from "./ui/buttons/Button"
8+
import {
9+
DropdownMenu,
10+
DropdownMenuContent,
11+
DropdownMenuItem,
12+
DropdownMenuTrigger,
13+
} from "./ui/dropdown-menu"
14+
import { BaseLink } from "./ui/Link"
1815

1916
export interface ListItem {
2017
text: string
@@ -33,12 +30,12 @@ export interface List {
3330
items: Array<ListItem>
3431
}
3532

36-
export type ButtonDropdownProps = ButtonProps & {
33+
export type ButtonDropdownProps = {
3734
list: List
35+
className?: string
3836
}
3937

40-
const ButtonDropdown = ({ list, ...rest }: ButtonDropdownProps) => {
41-
const { t } = useTranslation("common")
38+
const ButtonDropdown = ({ list, className }: ButtonDropdownProps) => {
4239
const [selectedItem, setSelectedItem] = useState(list.text)
4340
const handleClick = (
4441
e: MouseEvent<HTMLElement>,
@@ -59,65 +56,50 @@ const ButtonDropdown = ({ list, ...rest }: ButtonDropdownProps) => {
5956
}
6057

6158
return (
62-
<Menu matchWidth>
63-
<MenuButton
64-
as={Button}
65-
leftIcon={<MdMenu />}
66-
variant="outline"
67-
_active={{ bg: "transparent" }}
68-
{...rest}
69-
>
70-
{t(selectedItem)}
71-
</MenuButton>
72-
<MenuList
73-
py={2}
74-
borderRadius="base"
75-
border="1px"
76-
borderColor="text"
77-
bg="dropdownBackground"
78-
zIndex="popover"
79-
>
59+
<DropdownMenu>
60+
<DropdownMenuTrigger asChild>
61+
<Button
62+
variant="outline"
63+
className={cn("flex justify-between", className)}
64+
>
65+
<MdMenu />
66+
<span className="flex-1 text-center">{selectedItem}</span>
67+
</Button>
68+
</DropdownMenuTrigger>
69+
<DropdownMenuContent className="w-full" sideOffset={10}>
8070
{list.items.map((item, idx) => {
8171
const { text, href } = item
8272

83-
return href ? (
84-
<BaseLink
85-
key={idx}
86-
href={href!}
87-
isPartiallyActive={false}
88-
textDecor="none"
89-
color="text"
90-
fontWeight="normal"
91-
_hover={{ textDecor: "none", color: "primary.base" }}
92-
_focus={{ textDecor: "none", color: "primary.base" }}
93-
>
94-
<MenuItem
95-
as="span"
73+
if (href) {
74+
return (
75+
<DropdownMenuItem
76+
key={item.text}
77+
className="justify-center"
9678
onClick={(e) => handleClick(e, item, idx)}
97-
p={2}
98-
textAlign="center"
99-
justifyContent="center"
100-
bg="dropdownBackground"
101-
_hover={{
102-
color: "primary.base",
103-
bg: "dropdownBackgroundHover",
104-
}}
105-
_focus={{
106-
color: "primary.base",
107-
bg: "dropdownBackgroundHover",
108-
}}
79+
asChild
10980
>
110-
{t(text)}
111-
</MenuItem>
112-
</BaseLink>
113-
) : (
114-
<MenuItem key={idx} onClick={(e) => handleClick(e, item, idx)}>
115-
{t(text)}
116-
</MenuItem>
81+
<BaseLink
82+
href={item.href!}
83+
className="text-body no-underline focus-visible:outline-0"
84+
>
85+
<span>{text}</span>
86+
</BaseLink>
87+
</DropdownMenuItem>
88+
)
89+
}
90+
91+
return (
92+
<DropdownMenuItem
93+
key={item.text}
94+
className="justify-center"
95+
onClick={(e) => handleClick(e, item, idx)}
96+
>
97+
<span>{text}</span>
98+
</DropdownMenuItem>
11799
)
118100
})}
119-
</MenuList>
120-
</Menu>
101+
</DropdownMenuContent>
102+
</DropdownMenu>
121103
)
122104
}
123105

src/components/ui/dropdown-menu.tsx

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import * as React from "react"
2+
import { MdCheck, MdChevronRight, MdCircle } from "react-icons/md"
3+
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
4+
5+
import { cn } from "@/lib/utils/cn"
6+
7+
const DropdownMenu = DropdownMenuPrimitive.Root
8+
9+
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
10+
11+
const DropdownMenuGroup = DropdownMenuPrimitive.Group
12+
13+
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
14+
15+
const DropdownMenuSub = DropdownMenuPrimitive.Sub
16+
17+
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
18+
19+
const DropdownMenuSubTrigger = React.forwardRef<
20+
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
21+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
22+
inset?: boolean
23+
}
24+
>(({ className, inset, children, ...props }, ref) => (
25+
<DropdownMenuPrimitive.SubTrigger
26+
ref={ref}
27+
className={cn(
28+
"focus:bg-accent data-[state=open]:bg-accent flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none",
29+
inset && "pl-8",
30+
className
31+
)}
32+
{...props}
33+
>
34+
{children}
35+
<MdChevronRight className="ml-auto h-4 w-4" />
36+
</DropdownMenuPrimitive.SubTrigger>
37+
))
38+
DropdownMenuSubTrigger.displayName =
39+
DropdownMenuPrimitive.SubTrigger.displayName
40+
41+
const DropdownMenuSubContent = React.forwardRef<
42+
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
43+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
44+
>(({ className, ...props }, ref) => (
45+
<DropdownMenuPrimitive.SubContent
46+
ref={ref}
47+
className={cn(
48+
"z-popover min-w-[8rem] overflow-hidden rounded-md border bg-background p-1 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
49+
className
50+
)}
51+
{...props}
52+
/>
53+
))
54+
DropdownMenuSubContent.displayName =
55+
DropdownMenuPrimitive.SubContent.displayName
56+
57+
const DropdownMenuContent = React.forwardRef<
58+
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
59+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
60+
>(({ className, sideOffset = 4, ...props }, ref) => (
61+
<DropdownMenuPrimitive.Portal>
62+
<DropdownMenuPrimitive.Content
63+
ref={ref}
64+
sideOffset={sideOffset}
65+
className={cn(
66+
"z-popover min-w-[8rem] overflow-hidden rounded border border-body bg-background py-2 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
67+
className
68+
)}
69+
{...props}
70+
/>
71+
</DropdownMenuPrimitive.Portal>
72+
))
73+
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
74+
75+
const DropdownMenuItem = React.forwardRef<
76+
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
77+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
78+
inset?: boolean
79+
}
80+
>(({ className, inset, ...props }, ref) => (
81+
<DropdownMenuPrimitive.Item
82+
ref={ref}
83+
className={cn(
84+
"relative flex cursor-default select-none items-center p-2 outline-none transition-colors focus:bg-background-highlight focus:text-primary-hover data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
85+
inset && "pl-8",
86+
className
87+
)}
88+
{...props}
89+
/>
90+
))
91+
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
92+
93+
const DropdownMenuCheckboxItem = React.forwardRef<
94+
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
95+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
96+
>(({ className, children, checked, ...props }, ref) => (
97+
<DropdownMenuPrimitive.CheckboxItem
98+
ref={ref}
99+
className={cn(
100+
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-background-highlight focus:text-primary-hover data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
101+
className
102+
)}
103+
checked={checked}
104+
{...props}
105+
>
106+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
107+
<DropdownMenuPrimitive.ItemIndicator>
108+
<MdCheck className="h-4 w-4" />
109+
</DropdownMenuPrimitive.ItemIndicator>
110+
</span>
111+
{children}
112+
</DropdownMenuPrimitive.CheckboxItem>
113+
))
114+
DropdownMenuCheckboxItem.displayName =
115+
DropdownMenuPrimitive.CheckboxItem.displayName
116+
117+
const DropdownMenuRadioItem = React.forwardRef<
118+
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
119+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
120+
>(({ className, children, ...props }, ref) => (
121+
<DropdownMenuPrimitive.RadioItem
122+
ref={ref}
123+
className={cn(
124+
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
125+
className
126+
)}
127+
{...props}
128+
>
129+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
130+
<DropdownMenuPrimitive.ItemIndicator>
131+
<MdCircle className="h-2 w-2 fill-current" />
132+
</DropdownMenuPrimitive.ItemIndicator>
133+
</span>
134+
{children}
135+
</DropdownMenuPrimitive.RadioItem>
136+
))
137+
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
138+
139+
const DropdownMenuLabel = React.forwardRef<
140+
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
141+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
142+
inset?: boolean
143+
}
144+
>(({ className, inset, ...props }, ref) => (
145+
<DropdownMenuPrimitive.Label
146+
ref={ref}
147+
className={cn(
148+
"px-2 py-1.5 text-sm font-semibold",
149+
inset && "pl-8",
150+
className
151+
)}
152+
{...props}
153+
/>
154+
))
155+
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
156+
157+
const DropdownMenuSeparator = React.forwardRef<
158+
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
159+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
160+
>(({ className, ...props }, ref) => (
161+
<DropdownMenuPrimitive.Separator
162+
ref={ref}
163+
className={cn("bg-muted -mx-1 my-1 h-px", className)}
164+
{...props}
165+
/>
166+
))
167+
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
168+
169+
const DropdownMenuShortcut = ({
170+
className,
171+
...props
172+
}: React.HTMLAttributes<HTMLSpanElement>) => {
173+
return (
174+
<span
175+
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
176+
{...props}
177+
/>
178+
)
179+
}
180+
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
181+
182+
export {
183+
DropdownMenu,
184+
DropdownMenuCheckboxItem,
185+
DropdownMenuContent,
186+
DropdownMenuGroup,
187+
DropdownMenuItem,
188+
DropdownMenuLabel,
189+
DropdownMenuPortal,
190+
DropdownMenuRadioGroup,
191+
DropdownMenuRadioItem,
192+
DropdownMenuSeparator,
193+
DropdownMenuShortcut,
194+
DropdownMenuSub,
195+
DropdownMenuSubContent,
196+
DropdownMenuSubTrigger,
197+
DropdownMenuTrigger,
198+
}

0 commit comments

Comments
 (0)