11/**
2- * Dropdown Menu Component
2+ * Dropdown Menu Component (React 19)
33 *
44 * A dropdown menu component built on Radix UI DropdownMenu primitive.
55 * - Accessible menus with keyboard navigation and focus management
66 * - Supports menu items, checkboxes, radio groups, separators, and submenus
7+ * - All components accept `ref` as a standard prop (no forwardRef needed in React 19+)
78 */
89
910'use client'
@@ -26,154 +27,203 @@ const DropdownMenuSub = DropdownMenuPrimitive.Sub
2627
2728const DropdownMenuRadioGroup = DropdownMenuPrimitive . RadioGroup
2829
29- const DropdownMenuSubTrigger = React . forwardRef <
30- React . ElementRef < typeof DropdownMenuPrimitive . SubTrigger > ,
31- React . ComponentPropsWithoutRef < typeof DropdownMenuPrimitive . SubTrigger > & {
32- inset ?: boolean
33- }
34- > ( ( { className, inset, children, ...props } , ref ) => (
35- < DropdownMenuPrimitive . SubTrigger
36- ref = { ref }
37- className = { cn (
38- 'flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0' ,
39- inset && 'pl-8' ,
40- className ,
41- ) }
42- { ...props }
43- >
44- { children }
45- < ChevronRight className = "ml-auto" />
46- </ DropdownMenuPrimitive . SubTrigger >
47- ) )
30+ type DropdownMenuSubTriggerProps = React . ComponentPropsWithoutRef <
31+ typeof DropdownMenuPrimitive . SubTrigger
32+ > & {
33+ inset ?: boolean
34+ ref ?: React . Ref < React . ElementRef < typeof DropdownMenuPrimitive . SubTrigger > >
35+ }
36+
37+ const DropdownMenuSubTrigger = React . memo (
38+ ( {
39+ className,
40+ inset,
41+ children,
42+ ref,
43+ ...props
44+ } : DropdownMenuSubTriggerProps ) => (
45+ < DropdownMenuPrimitive . SubTrigger
46+ ref = { ref }
47+ className = { cn (
48+ 'flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0' ,
49+ inset && 'pl-8' ,
50+ className ,
51+ ) }
52+ { ...props }
53+ >
54+ { children }
55+ < ChevronRight className = "ml-auto" />
56+ </ DropdownMenuPrimitive . SubTrigger >
57+ ) ,
58+ )
4859DropdownMenuSubTrigger . displayName =
4960 DropdownMenuPrimitive . SubTrigger . displayName
5061
51- const DropdownMenuSubContent = React . forwardRef <
52- React . ElementRef < typeof DropdownMenuPrimitive . SubContent > ,
53- React . ComponentPropsWithoutRef < typeof DropdownMenuPrimitive . SubContent >
54- > ( ( { className, ...props } , ref ) => (
55- < DropdownMenuPrimitive . SubContent
56- ref = { ref }
57- className = { cn (
58- 'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground 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 origin-[--radix-dropdown-menu-content-transform-origin]' ,
59- className ,
60- ) }
61- { ...props }
62- />
63- ) )
64- DropdownMenuSubContent . displayName =
65- DropdownMenuPrimitive . SubContent . displayName
62+ type DropdownMenuSubContentProps = React . ComponentPropsWithoutRef <
63+ typeof DropdownMenuPrimitive . SubContent
64+ > & {
65+ ref ?: React . Ref < React . ElementRef < typeof DropdownMenuPrimitive . SubContent > >
66+ }
6667
67- const DropdownMenuContent = React . forwardRef <
68- React . ElementRef < typeof DropdownMenuPrimitive . Content > ,
69- React . ComponentPropsWithoutRef < typeof DropdownMenuPrimitive . Content >
70- > ( ( { className, sideOffset = 4 , ...props } , ref ) => (
71- < DropdownMenuPrimitive . Portal >
72- < DropdownMenuPrimitive . Content
68+ const DropdownMenuSubContent = React . memo (
69+ ( { className, ref, ...props } : DropdownMenuSubContentProps ) => (
70+ < DropdownMenuPrimitive . SubContent
7371 ref = { ref }
74- sideOffset = { sideOffset }
7572 className = { cn (
76- 'z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x- hidden rounded-md border bg-popover p-1 text-popover-foreground 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 origin-[--radix-dropdown-menu-content-transform-origin]' ,
73+ 'z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground 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 origin-[--radix-dropdown-menu-content-transform-origin]' ,
7774 className ,
7875 ) }
7976 { ...props }
8077 />
81- </ DropdownMenuPrimitive . Portal >
82- ) )
78+ ) ,
79+ )
80+ DropdownMenuSubContent . displayName =
81+ DropdownMenuPrimitive . SubContent . displayName
82+
83+ type DropdownMenuContentProps = React . ComponentPropsWithoutRef <
84+ typeof DropdownMenuPrimitive . Content
85+ > & {
86+ ref ?: React . Ref < React . ElementRef < typeof DropdownMenuPrimitive . Content > >
87+ }
88+
89+ const DropdownMenuContent = React . memo (
90+ ( { className, sideOffset = 4 , ref, ...props } : DropdownMenuContentProps ) => (
91+ < DropdownMenuPrimitive . Portal >
92+ < DropdownMenuPrimitive . Content
93+ ref = { ref }
94+ sideOffset = { sideOffset }
95+ className = { cn (
96+ 'z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-32 overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground 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 origin-[--radix-dropdown-menu-content-transform-origin]' ,
97+ className ,
98+ ) }
99+ { ...props }
100+ />
101+ </ DropdownMenuPrimitive . Portal >
102+ ) ,
103+ )
83104DropdownMenuContent . displayName = DropdownMenuPrimitive . Content . displayName
84105
85- const DropdownMenuItem = React . forwardRef <
86- React . ElementRef < typeof DropdownMenuPrimitive . Item > ,
87- React . ComponentPropsWithoutRef < typeof DropdownMenuPrimitive . Item > & {
88- inset ?: boolean
89- }
90- > ( ( { className, inset, ...props } , ref ) => (
91- < DropdownMenuPrimitive . Item
92- ref = { ref }
93- className = { cn (
94- 'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0' ,
95- inset && 'pl-8' ,
96- className ,
97- ) }
98- { ...props }
99- />
100- ) )
106+ type DropdownMenuItemProps = React . ComponentPropsWithoutRef <
107+ typeof DropdownMenuPrimitive . Item
108+ > & {
109+ inset ?: boolean
110+ ref ?: React . Ref < React . ElementRef < typeof DropdownMenuPrimitive . Item > >
111+ }
112+
113+ const DropdownMenuItem = React . memo (
114+ ( { className, inset, ref, ...props } : DropdownMenuItemProps ) => (
115+ < DropdownMenuPrimitive . Item
116+ ref = { ref }
117+ className = { cn (
118+ 'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0' ,
119+ inset && 'pl-8' ,
120+ className ,
121+ ) }
122+ { ...props }
123+ />
124+ ) ,
125+ )
101126DropdownMenuItem . displayName = DropdownMenuPrimitive . Item . displayName
102127
103- const DropdownMenuCheckboxItem = React . forwardRef <
104- React . ElementRef < typeof DropdownMenuPrimitive . CheckboxItem > ,
105- React . ComponentPropsWithoutRef < typeof DropdownMenuPrimitive . CheckboxItem >
106- > ( ( { className, children, checked, ...props } , ref ) => (
107- < DropdownMenuPrimitive . CheckboxItem
108- ref = { ref }
109- className = { cn (
110- '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-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50' ,
111- className ,
112- ) }
113- checked = { checked }
114- { ...props }
115- >
116- < span className = "absolute left-2 flex h-3.5 w-3.5 items-center justify-center" >
117- < DropdownMenuPrimitive . ItemIndicator >
118- < Check className = "h-4 w-4" />
119- </ DropdownMenuPrimitive . ItemIndicator >
120- </ span >
121- { children }
122- </ DropdownMenuPrimitive . CheckboxItem >
123- ) )
128+ type DropdownMenuCheckboxItemProps = React . ComponentPropsWithoutRef <
129+ typeof DropdownMenuPrimitive . CheckboxItem
130+ > & {
131+ ref ?: React . Ref < React . ElementRef < typeof DropdownMenuPrimitive . CheckboxItem > >
132+ }
133+
134+ const DropdownMenuCheckboxItem = React . memo (
135+ ( {
136+ className,
137+ children,
138+ checked,
139+ ref,
140+ ...props
141+ } : DropdownMenuCheckboxItemProps ) => (
142+ < DropdownMenuPrimitive . CheckboxItem
143+ ref = { ref }
144+ className = { cn (
145+ '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-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50' ,
146+ className ,
147+ ) }
148+ checked = { checked }
149+ { ...props }
150+ >
151+ < span className = "absolute left-2 flex h-3.5 w-3.5 items-center justify-center" >
152+ < DropdownMenuPrimitive . ItemIndicator >
153+ < Check className = "h-4 w-4" />
154+ </ DropdownMenuPrimitive . ItemIndicator >
155+ </ span >
156+ { children }
157+ </ DropdownMenuPrimitive . CheckboxItem >
158+ ) ,
159+ )
124160DropdownMenuCheckboxItem . displayName =
125161 DropdownMenuPrimitive . CheckboxItem . displayName
126162
127- const DropdownMenuRadioItem = React . forwardRef <
128- React . ElementRef < typeof DropdownMenuPrimitive . RadioItem > ,
129- React . ComponentPropsWithoutRef < typeof DropdownMenuPrimitive . RadioItem >
130- > ( ( { className, children, ...props } , ref ) => (
131- < DropdownMenuPrimitive . RadioItem
132- ref = { ref }
133- className = { cn (
134- '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-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50' ,
135- className ,
136- ) }
137- { ...props }
138- >
139- < span className = "absolute left-2 flex h-3.5 w-3.5 items-center justify-center" >
140- < DropdownMenuPrimitive . ItemIndicator >
141- < Circle className = "h-2 w-2 fill-current" />
142- </ DropdownMenuPrimitive . ItemIndicator >
143- </ span >
144- { children }
145- </ DropdownMenuPrimitive . RadioItem >
146- ) )
163+ type DropdownMenuRadioItemProps = React . ComponentPropsWithoutRef <
164+ typeof DropdownMenuPrimitive . RadioItem
165+ > & {
166+ ref ?: React . Ref < React . ElementRef < typeof DropdownMenuPrimitive . RadioItem > >
167+ }
168+
169+ const DropdownMenuRadioItem = React . memo (
170+ ( { className, children, ref, ...props } : DropdownMenuRadioItemProps ) => (
171+ < DropdownMenuPrimitive . RadioItem
172+ ref = { ref }
173+ className = { cn (
174+ '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-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50' ,
175+ className ,
176+ ) }
177+ { ...props }
178+ >
179+ < span className = "absolute left-2 flex h-3.5 w-3.5 items-center justify-center" >
180+ < DropdownMenuPrimitive . ItemIndicator >
181+ < Circle className = "h-2 w-2 fill-current" />
182+ </ DropdownMenuPrimitive . ItemIndicator >
183+ </ span >
184+ { children }
185+ </ DropdownMenuPrimitive . RadioItem >
186+ ) ,
187+ )
147188DropdownMenuRadioItem . displayName = DropdownMenuPrimitive . RadioItem . displayName
148189
149- const DropdownMenuLabel = React . forwardRef <
150- React . ElementRef < typeof DropdownMenuPrimitive . Label > ,
151- React . ComponentPropsWithoutRef < typeof DropdownMenuPrimitive . Label > & {
152- inset ?: boolean
153- }
154- > ( ( { className, inset, ...props } , ref ) => (
155- < DropdownMenuPrimitive . Label
156- ref = { ref }
157- className = { cn (
158- 'px-2 py-1.5 text-sm font-semibold' ,
159- inset && 'pl-8' ,
160- className ,
161- ) }
162- { ...props }
163- />
164- ) )
190+ type DropdownMenuLabelProps = React . ComponentPropsWithoutRef <
191+ typeof DropdownMenuPrimitive . Label
192+ > & {
193+ inset ?: boolean
194+ ref ?: React . Ref < React . ElementRef < typeof DropdownMenuPrimitive . Label > >
195+ }
196+
197+ const DropdownMenuLabel = React . memo (
198+ ( { className, inset, ref, ...props } : DropdownMenuLabelProps ) => (
199+ < DropdownMenuPrimitive . Label
200+ ref = { ref }
201+ className = { cn (
202+ 'px-2 py-1.5 text-sm font-semibold' ,
203+ inset && 'pl-8' ,
204+ className ,
205+ ) }
206+ { ...props }
207+ />
208+ ) ,
209+ )
165210DropdownMenuLabel . displayName = DropdownMenuPrimitive . Label . displayName
166211
167- const DropdownMenuSeparator = React . forwardRef <
168- React . ElementRef < typeof DropdownMenuPrimitive . Separator > ,
169- React . ComponentPropsWithoutRef < typeof DropdownMenuPrimitive . Separator >
170- > ( ( { className, ...props } , ref ) => (
171- < DropdownMenuPrimitive . Separator
172- ref = { ref }
173- className = { cn ( '-mx-1 my-1 h-px bg-muted' , className ) }
174- { ...props }
175- />
176- ) )
212+ type DropdownMenuSeparatorProps = React . ComponentPropsWithoutRef <
213+ typeof DropdownMenuPrimitive . Separator
214+ > & {
215+ ref ?: React . Ref < React . ElementRef < typeof DropdownMenuPrimitive . Separator > >
216+ }
217+
218+ const DropdownMenuSeparator = React . memo (
219+ ( { className, ref, ...props } : DropdownMenuSeparatorProps ) => (
220+ < DropdownMenuPrimitive . Separator
221+ ref = { ref }
222+ className = { cn ( '-mx-1 my-1 h-px bg-muted' , className ) }
223+ { ...props }
224+ />
225+ ) ,
226+ )
177227DropdownMenuSeparator . displayName = DropdownMenuPrimitive . Separator . displayName
178228
179229const DropdownMenuShortcut = React . memo ( function DropdownMenuShortcut ( {
0 commit comments