Menubar

Описание

Визуально постоянное меню, распространенное в настольных приложениях и обеспечивающее быстрый доступ к последовательному набору команд.

Компонент используется только для настольных приложений, поэтому он использует уменьшенные размеры, нежели в компоненте Menu.

Примеры

Компонент

Компонент можно сохранить в src/shared/ui/Menubar.tsx. Обратите внимание на необходимые зависимости: Label.

Так же данный компонент использует @radix-ui/react-menubar компонент.

yarn add @radix-ui/react-menubar
"use client"
import React from "react"
import * as MenubarPrimitive from "@radix-ui/react-menubar"
import { Check, NavArrowRight, Circle } from "iconoir-react"
import Label, { labelVariants } from "../typography/Label"
import { cn } from "@/lib/cn"
const MenubarMenu = MenubarPrimitive.Menu
const MenubarGroup = MenubarPrimitive.Group
const MenubarPortal = MenubarPrimitive.Portal
const MenubarSub = MenubarPrimitive.Sub
const MenubarRadioGroup = MenubarPrimitive.RadioGroup
const Menubar = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Root
ref={ref}
className={cn(
"flex h-10 items-center space-x-1 rounded-medium border border-outlineVariant p-1",
className
)}
{...props}
/>
))
Menubar.displayName = MenubarPrimitive.Root.displayName
const MenubarTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<MenubarPrimitive.Trigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-small px-3 py-1.5 outline-none hover:bg-surfaceContainer focus:bg-surfaceContainerHighest focus:text-onSurface data-[state=open]:bg-surfaceContainerHighest data-[state=open]:text-onSurface transition-colors",
className
)}
{...props}
>
<Label size="large">{children}</Label>
</MenubarPrimitive.Trigger>
))
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
const MenubarSubTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<MenubarPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center px-2 py-1.5 outline-none text-onSurface aria-disabled:text-opacity-[0.38] data-[highlighted]:bg-onSurface/[0.12]",
inset && "pl-8",
className
)}
{...props}
>
<Label size="large">{children}</Label>
<NavArrowRight className="ml-auto h-4 w-4" />
</MenubarPrimitive.SubTrigger>
))
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
const MenubarSubContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] flex flex-col rounded bg-surfaceContainer shadow-elevation2 py-1",
className
)}
{...props}
/>
))
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
const MenubarContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
>(({ className, align = "start", ...props }, ref) => (
<MenubarPrimitive.Portal>
<MenubarPrimitive.Content
ref={ref}
align={align}
className={cn(
"relative z-50 min-w-[12rem] text-onSurfaceContainer flex flex-col rounded bg-surfaceContainer shadow-elevation2 py-1",
className
)}
{...props}
/>
</MenubarPrimitive.Portal>
))
MenubarContent.displayName = MenubarPrimitive.Content.displayName
const MenubarItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Item
ref={ref}
className={cn(
labelVariants({ size: "large" }),
"relative flex cursor-default select-none items-center px-2 py-1.5 outline-none text-onSurface aria-disabled:text-opacity-[0.38] data-[highlighted]:bg-onSurface/[0.12]",
inset && "pl-8",
className
)}
{...props}
/>
))
MenubarItem.displayName = MenubarPrimitive.Item.displayName
const MenubarCheckboxItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<MenubarPrimitive.CheckboxItem
ref={ref}
className={cn(
labelVariants({ size: "large" }),
"relative flex cursor-default select-none items-center py-1.5 pl-8 pr-2 outline-none text-onSurface aria-disabled:text-opacity-[0.38] data-[highlighted]:bg-onSurface/[0.12]",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.CheckboxItem>
))
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
const MenubarRadioItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<MenubarPrimitive.RadioItem
ref={ref}
className={cn(
labelVariants({ size: "large" }),
"relative flex cursor-default select-none items-center py-1.5 pl-8 pr-2 outline-none text-onSurface aria-disabled:text-opacity-[0.38] data-[highlighted]:bg-onSurface/[0.12]",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.RadioItem>
))
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
const MenubarLabel = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-12",
className
)}
{...props}
/>
))
MenubarLabel.displayName = MenubarPrimitive.Label.displayName
const MenubarSeparator = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Separator
ref={ref}
className={cn("w-full my-1 h-px bg-outlineVariant", className)}
{...props}
/>
))
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
const MenubarShortcut = ({
className,
children,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<Label
size="large"
className={cn("ml-auto text-onSurfaceVariant", className)}
{...props}
>
{children}
</Label>
)
}
MenubarShortcut.displayname = "MenubarShortcut"
export {
Menubar,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarItem,
MenubarSeparator,
MenubarLabel,
MenubarCheckboxItem,
MenubarRadioGroup,
MenubarRadioItem,
MenubarPortal,
MenubarSubContent,
MenubarSubTrigger,
MenubarGroup,
MenubarSub,
MenubarShortcut,
}

Использование

Данный файл экспортирует 16 компонентов

Menubar - root компонент, содержащий стейт о том, какой MenubarMenu сейчас открыт

MenubarMenu - компонент, содержащий все части MenubarContent

MenubarTrigger - кнопка, открывающая одно из MenubarMenu

MenubarPortal - когда используется, вставляет MenubarContent в тег body. В ином случае MenubarContent будет вставляться в MenubarMenu

MenubarContent - контейнер, содержащий все элементы Menu

MenubarItem - кнопка в Menu; может быть текстом в конце (MenubarShortcut)

MenubarCheckboxItem - кнопка в Menu с функционалом чекбокса; может быть текстом в конце (MenubarShortcut)

MenubarRadioGroup - обёртка для хранения состояния радио-кнопок MenubarRadioItem

MenubarRadioItem - кнопка в Menu с функционалом радио-кнопки; может быть текстом в конце (MenubarShortcut)

MenubarSeparator - разделитель Menu на логические блоки

MenubarLabel - текст, который несёт только информативную часть (не является кнопкой)

MenubarSub - аналог MenubarMenu, но для вторичного Menu

MenubarSubContent - аналог MenubarContent, но для вторичного Menu

MenubarSubTrigger - кнопка, активирующая вторичное Menu

MenubarShortcut - текст в конце кнопок MenubarItem, MenubarCheckboxItem и MenubarRadioItem

MenubarGroup - используется для группировки нескольких MenubarItem, MenubarCheckboxItem и MenubarRadioItem

Примеры

Ниже - код для примера в начале страницы.

"use client"
import React from "react"
import {
Menubar,
MenubarCheckboxItem,
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarTrigger,
} from "@/shared/ui/Menubar"
export function MenubarExample() {
const [profile, setProfile] = React.useState("benoit")
return (
<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarItem>
New Tab <MenubarShortcut>⌘+T</MenubarShortcut>
</MenubarItem>
<MenubarItem>
New Window <MenubarShortcut>⌘+N</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled>New Incognito Window</MenubarItem>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>Share</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem>Email link</MenubarItem>
<MenubarItem>Messages</MenubarItem>
<MenubarItem>Notes</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarItem>
Print... <MenubarShortcut>⌘+P</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Edit</MenubarTrigger>
<MenubarContent>
<MenubarItem>
Undo <MenubarShortcut>⌘+Z</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Redo <MenubarShortcut>⇧⌘+Z</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>Find</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem>Search the web</MenubarItem>
<MenubarSeparator />
<MenubarItem>Find...</MenubarItem>
<MenubarItem>Find Next</MenubarItem>
<MenubarItem>Find Previous</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarItem>Cut</MenubarItem>
<MenubarItem>Copy</MenubarItem>
<MenubarItem>Paste</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>View</MenubarTrigger>
<MenubarContent>
<MenubarCheckboxItem>Always Show Bookmarks Bar</MenubarCheckboxItem>
<MenubarCheckboxItem checked>
Always Show Full URLs
</MenubarCheckboxItem>
<MenubarSeparator />
<MenubarItem inset>
Reload <MenubarShortcut>⌘+R</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled inset>
Force Reload <MenubarShortcut>⇧⌘+R</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem inset>Toggle Fullscreen</MenubarItem>
<MenubarSeparator />
<MenubarItem inset>Hide Sidebar</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Profiles</MenubarTrigger>
<MenubarContent>
<MenubarRadioGroup value={profile} onValueChange={setProfile}>
<MenubarRadioItem value="andy">Andy</MenubarRadioItem>
<MenubarRadioItem value="benoit">Benoit</MenubarRadioItem>
<MenubarRadioItem value="Luis">Luis</MenubarRadioItem>
</MenubarRadioGroup>
<MenubarSeparator />
<MenubarItem inset>Edit...</MenubarItem>
<MenubarSeparator />
<MenubarItem inset>Add Profile...</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
)
}