-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathdropdown-menu.tsx
More file actions
178 lines (155 loc) · 4.62 KB
/
dropdown-menu.tsx
File metadata and controls
178 lines (155 loc) · 4.62 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
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
import React, { useCallback } from "react"
import cx from "classnames"
import * as MenuPrimitive from "@radix-ui/react-dropdown-menu"
import { openBrowser } from "src/utils"
type Props = {
children: React.ReactNode
asChild?: boolean
trigger: React.ReactNode
} & Pick<
MenuPrimitive.DropdownMenuContentProps,
"side" | "sideOffset" | "align" | "alignOffset" | "onCloseAutoFocus"
> &
Pick<MenuPrimitive.DropdownMenuProps, "open" | "onOpenChange">
/**
* DropdownMenu is a floating menu with actions. It should be used to provide
* additional actions for users that don't warrant a top-level button.
*/
export default function DropdownMenu(props: Props) {
const {
children,
asChild,
trigger,
side,
sideOffset,
align,
alignOffset,
open,
onOpenChange,
onCloseAutoFocus,
} = props
return (
<MenuPrimitive.Root open={open} onOpenChange={onOpenChange}>
<MenuPrimitive.Trigger asChild={asChild}>{trigger}</MenuPrimitive.Trigger>
<MenuPrimitive.Content
className="dropdown shadow-popover overflow-hidden text-sm bg-white dark:bg-[#333e46] rounded-md py-1 z-50"
side={side}
sideOffset={sideOffset}
align={align}
alignOffset={alignOffset}
onCloseAutoFocus={onCloseAutoFocus}
>
{children}
</MenuPrimitive.Content>
</MenuPrimitive.Root>
)
}
DropdownMenu.defaultProps = {
sideOffset: 10,
}
DropdownMenu.Group = DropdownMenuGroup
DropdownMenu.Item = DropdownMenuItem
DropdownMenu.Link = DropdownMenuLink
DropdownMenu.RadioGroup = MenuPrimitive.RadioGroup
DropdownMenu.RadioItem = MenuPrimitive.RadioItem
/**
* DropdownMenu.Separator should be used to divide items into sections within a
* DropdownMenu.
*/
DropdownMenu.Separator = DropdownSeparator
const menuItemClasses = "block px-4 py-2"
const menuItemInteractiveClasses =
"cursor-pointer focus:outline-none hover:enabled:bg-gray-100 focus:bg-gray-100 dark:hover:enabled:bg-[#5E6971] dark:focus:bg-[#5E6971]"
type CommonMenuItemProps = {
className?: string
disabled?: boolean
/**
* hidden determines whether or not the menu item should appear. It's exposed as
* a convenience for menus with many nested conditionals.
*/
hidden?: boolean
}
type DropdownMenuGroupProps = CommonMenuItemProps & MenuPrimitive.DropdownMenuGroupProps
function DropdownMenuGroup(props: DropdownMenuGroupProps) {
const { className, ...rest } = props
return (
<MenuPrimitive.Group className={cx(className, menuItemClasses)} {...rest} />
)
}
type DropdownMenuItemProps = {
intent?: "danger"
} & CommonMenuItemProps &
Omit<MenuPrimitive.DropdownMenuItemProps, "onClick">
function DropdownMenuItem(props: DropdownMenuItemProps) {
const { className, disabled, intent, hidden, ...rest } = props
if (hidden === true) {
return null
}
return (
<MenuPrimitive.Item
className={cx(className, menuItemClasses, menuItemInteractiveClasses, {
"text-red-400": intent === "danger",
"text-gray-400 cursor-default": disabled,
})}
disabled={disabled}
{...rest}
/>
)
}
type DropdownMenuLinkProps = {
className?: string
href: string
} & Omit<MenuPrimitive.DropdownMenuItemProps, "onClick" | "onSelect" | "asChild">
function DropdownMenuLink(props: DropdownMenuLinkProps) {
const { className, children, disabled, href, ...rest } = props
// handleSelect handles when users use the keyboard to select a link.
const handleSelect = useCallback(() => {
openBrowser(href)
}, [href])
// handleClick handles when users click on the link.
const handleClick = useCallback(
(e: React.MouseEvent) => {
e.preventDefault()
handleSelect()
},
[handleSelect],
)
return (
<MenuPrimitive.Item
className={cx(
"relative",
className,
menuItemClasses,
menuItemInteractiveClasses,
{
"text-gray-400 bg-white cursor-default": disabled,
},
)}
disabled={disabled}
onSelect={handleSelect}
{...rest}
>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a className="stretched-link" href="#" onClick={handleClick}>
{children}
</a>
</MenuPrimitive.Item>
)
}
type DropdownSeparatorProps = Omit<CommonMenuItemProps, "disabled"> &
MenuPrimitive.DropdownMenuSeparatorProps
function DropdownSeparator(props: DropdownSeparatorProps) {
const { className, hidden, ...rest } = props
if (hidden === true) {
return null
}
return (
<MenuPrimitive.Separator
className={cx(
"my-1 border-b border-gray-200 dark:border-white dark:opacity-10",
className,
)}
{...rest}
/>
)
}