menubar.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. 'use client';
  2. import * as React from 'react';
  3. import { Menu as MenuPrimitive } from '@base-ui/react/menu';
  4. import { Menubar as MenubarPrimitive } from '@base-ui/react/menubar';
  5. import { cn } from '@/lib/utils';
  6. import {
  7. DropdownMenu,
  8. DropdownMenuContent,
  9. DropdownMenuGroup,
  10. DropdownMenuItem,
  11. DropdownMenuLabel,
  12. DropdownMenuPortal,
  13. DropdownMenuRadioGroup,
  14. DropdownMenuSeparator,
  15. DropdownMenuShortcut,
  16. DropdownMenuSub,
  17. DropdownMenuSubContent,
  18. DropdownMenuSubTrigger,
  19. DropdownMenuTrigger,
  20. } from '@/components/ui/dropdown-menu';
  21. import { CheckIcon } from 'lucide-react';
  22. function Menubar({ className, ...props }: MenubarPrimitive.Props) {
  23. return (
  24. <MenubarPrimitive
  25. data-slot="menubar"
  26. className={cn(
  27. 'h-8 gap-0.5 rounded-lg border p-[3px] flex items-center',
  28. className,
  29. )}
  30. {...props}
  31. />
  32. );
  33. }
  34. function MenubarMenu({ ...props }: React.ComponentProps<typeof DropdownMenu>) {
  35. return <DropdownMenu data-slot="menubar-menu" {...props} />;
  36. }
  37. function MenubarGroup({
  38. ...props
  39. }: React.ComponentProps<typeof DropdownMenuGroup>) {
  40. return <DropdownMenuGroup data-slot="menubar-group" {...props} />;
  41. }
  42. function MenubarPortal({
  43. ...props
  44. }: React.ComponentProps<typeof DropdownMenuPortal>) {
  45. return <DropdownMenuPortal data-slot="menubar-portal" {...props} />;
  46. }
  47. function MenubarTrigger({
  48. className,
  49. ...props
  50. }: React.ComponentProps<typeof DropdownMenuTrigger>) {
  51. return (
  52. <DropdownMenuTrigger
  53. data-slot="menubar-trigger"
  54. className={cn(
  55. 'hover:bg-muted aria-expanded:bg-muted rounded-sm px-1.5 py-[2px] text-sm font-medium flex items-center outline-hidden select-none',
  56. className,
  57. )}
  58. {...props}
  59. />
  60. );
  61. }
  62. function MenubarContent({
  63. className,
  64. align = 'start',
  65. alignOffset = -4,
  66. sideOffset = 8,
  67. ...props
  68. }: React.ComponentProps<typeof DropdownMenuContent>) {
  69. return (
  70. <DropdownMenuContent
  71. data-slot="menubar-content"
  72. align={align}
  73. alignOffset={alignOffset}
  74. sideOffset={sideOffset}
  75. className={cn(
  76. 'bg-popover text-popover-foreground data-open:animate-in data-open:fade-in-0 data-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 ring-foreground/10 min-w-36 rounded-lg p-1 shadow-md ring-1 duration-100 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 cn-menu-target cn-menu-translucent',
  77. className,
  78. )}
  79. {...props}
  80. />
  81. );
  82. }
  83. function MenubarItem({
  84. className,
  85. inset,
  86. variant = 'default',
  87. ...props
  88. }: React.ComponentProps<typeof DropdownMenuItem>) {
  89. return (
  90. <DropdownMenuItem
  91. data-slot="menubar-item"
  92. data-inset={inset}
  93. data-variant={variant}
  94. className={cn(
  95. "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive! not-data-[variant=destructive]:focus:**:text-accent-foreground gap-1.5 rounded-md px-1.5 py-1 text-sm data-disabled:opacity-50 data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 group/menubar-item",
  96. className,
  97. )}
  98. {...props}
  99. />
  100. );
  101. }
  102. function MenubarCheckboxItem({
  103. className,
  104. children,
  105. checked,
  106. inset,
  107. ...props
  108. }: MenuPrimitive.CheckboxItem.Props & {
  109. inset?: boolean;
  110. }) {
  111. return (
  112. <MenuPrimitive.CheckboxItem
  113. data-slot="menubar-checkbox-item"
  114. data-inset={inset}
  115. className={cn(
  116. 'focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-1.5 rounded-md py-1 pr-1.5 pl-7 text-sm data-inset:pl-7 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
  117. className,
  118. )}
  119. checked={checked}
  120. {...props}
  121. >
  122. <span className="left-1.5 size-4 [&_svg:not([class*='size-'])]:size-4 pointer-events-none absolute flex items-center justify-center">
  123. <MenuPrimitive.CheckboxItemIndicator>
  124. <CheckIcon />
  125. </MenuPrimitive.CheckboxItemIndicator>
  126. </span>
  127. {children}
  128. </MenuPrimitive.CheckboxItem>
  129. );
  130. }
  131. function MenubarRadioGroup({
  132. ...props
  133. }: React.ComponentProps<typeof DropdownMenuRadioGroup>) {
  134. return <DropdownMenuRadioGroup data-slot="menubar-radio-group" {...props} />;
  135. }
  136. function MenubarRadioItem({
  137. className,
  138. children,
  139. inset,
  140. ...props
  141. }: MenuPrimitive.RadioItem.Props & {
  142. inset?: boolean;
  143. }) {
  144. return (
  145. <MenuPrimitive.RadioItem
  146. data-slot="menubar-radio-item"
  147. data-inset={inset}
  148. className={cn(
  149. "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-1.5 rounded-md py-1 pr-1.5 pl-7 text-sm data-disabled:opacity-50 data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0",
  150. className,
  151. )}
  152. {...props}
  153. >
  154. <span className="left-1.5 size-4 [&_svg:not([class*='size-'])]:size-4 pointer-events-none absolute flex items-center justify-center">
  155. <MenuPrimitive.RadioItemIndicator>
  156. <CheckIcon />
  157. </MenuPrimitive.RadioItemIndicator>
  158. </span>
  159. {children}
  160. </MenuPrimitive.RadioItem>
  161. );
  162. }
  163. function MenubarLabel({
  164. className,
  165. inset,
  166. ...props
  167. }: React.ComponentProps<typeof DropdownMenuLabel> & {
  168. inset?: boolean;
  169. }) {
  170. return (
  171. <DropdownMenuLabel
  172. data-slot="menubar-label"
  173. data-inset={inset}
  174. className={cn(
  175. 'px-1.5 py-1 text-sm font-medium data-inset:pl-7',
  176. className,
  177. )}
  178. {...props}
  179. />
  180. );
  181. }
  182. function MenubarSeparator({
  183. className,
  184. ...props
  185. }: React.ComponentProps<typeof DropdownMenuSeparator>) {
  186. return (
  187. <DropdownMenuSeparator
  188. data-slot="menubar-separator"
  189. className={cn('bg-border -mx-1 my-1 h-px', className)}
  190. {...props}
  191. />
  192. );
  193. }
  194. function MenubarShortcut({
  195. className,
  196. ...props
  197. }: React.ComponentProps<typeof DropdownMenuShortcut>) {
  198. return (
  199. <DropdownMenuShortcut
  200. data-slot="menubar-shortcut"
  201. className={cn(
  202. 'text-muted-foreground group-focus/menubar-item:text-accent-foreground text-xs tracking-widest ml-auto',
  203. className,
  204. )}
  205. {...props}
  206. />
  207. );
  208. }
  209. function MenubarSub({
  210. ...props
  211. }: React.ComponentProps<typeof DropdownMenuSub>) {
  212. return <DropdownMenuSub data-slot="menubar-sub" {...props} />;
  213. }
  214. function MenubarSubTrigger({
  215. className,
  216. inset,
  217. ...props
  218. }: React.ComponentProps<typeof DropdownMenuSubTrigger> & {
  219. inset?: boolean;
  220. }) {
  221. return (
  222. <DropdownMenuSubTrigger
  223. data-slot="menubar-sub-trigger"
  224. data-inset={inset}
  225. className={cn(
  226. "focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground gap-1.5 rounded-md px-1.5 py-1 text-sm data-inset:pl-7 [&_svg:not([class*='size-'])]:size-4",
  227. className,
  228. )}
  229. {...props}
  230. />
  231. );
  232. }
  233. function MenubarSubContent({
  234. className,
  235. ...props
  236. }: React.ComponentProps<typeof DropdownMenuSubContent>) {
  237. return (
  238. <DropdownMenuSubContent
  239. data-slot="menubar-sub-content"
  240. className={cn(
  241. 'bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 min-w-32 rounded-lg p-1 shadow-lg ring-1 duration-100 cn-menu-target cn-menu-translucent',
  242. className,
  243. )}
  244. {...props}
  245. />
  246. );
  247. }
  248. export {
  249. Menubar,
  250. MenubarPortal,
  251. MenubarMenu,
  252. MenubarTrigger,
  253. MenubarContent,
  254. MenubarGroup,
  255. MenubarSeparator,
  256. MenubarLabel,
  257. MenubarItem,
  258. MenubarShortcut,
  259. MenubarCheckboxItem,
  260. MenubarRadioGroup,
  261. MenubarRadioItem,
  262. MenubarSub,
  263. MenubarSubTrigger,
  264. MenubarSubContent,
  265. };