Menus

Описание

Меню отображают список вариантов на временной поверхности.

Меню появляется, когда пользователи взаимодействуют с кнопкой, действием или другим элементом управления.

Они могут быть открыты с помощью различных элементов, чаще всего это Icon Buttons, Buttons и Text Fields.

Подробнее об использовании элемента читайте на официальном сайте

Примеры

Компонент

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

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

yarn add @radix-ui/react-dropdown-menu
"use client"
import React from "react"
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"
import { NavArrowRight } from "iconoir-react"
import Label from "@/shared/typography/Label"
import { cn } from "@/lib/cn"
const MenuRoot = DropdownMenu.Root
const MenuTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenu.Trigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenu.Trigger>
>(({ children, className, ...props }, forwardedRef) => (
<DropdownMenu.Trigger
className={cn(className)}
{...props}
ref={forwardedRef}
asChild
>
{children}
</DropdownMenu.Trigger>
))
MenuTrigger.displayName = "MenuTrigger"
const MenuPortal = DropdownMenu.Portal
const MenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenu.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenu.Content>
>(({ className, children, ...props }, forwardedRef) => (
<DropdownMenu.Content
className={cn(
"flex flex-col rounded bg-surfaceContainer shadow-elevation2 py-2 w-[280px] will-change-[opacity,transform] data-[side=bottom]:animate-slide-down-and-fade-in origin-top-left data-[state=closed]:animate-slide-up-and-fade-out",
className
)}
{...props}
ref={forwardedRef}
align="start"
>
{children}
</DropdownMenu.Content>
))
MenuContent.displayName = "MenuContent"
type MenuItemElement = React.ElementRef<typeof DropdownMenu.Item>
type DropdownMenuProps = React.ComponentPropsWithoutRef<
typeof DropdownMenu.Item
>
interface MenuItemProps extends DropdownMenuProps {
leading?: React.ReactNode
trailing?: React.ReactNode | string
}
const MenuItem = React.forwardRef<MenuItemElement, MenuItemProps>(
({ className, children, leading, trailing, ...props }, forwardedRef) => (
<DropdownMenu.Item
className={cn(
"h-12 flex items-center gap-3 px-3 select-none outline-none text-onSurface aria-disabled:text-opacity-[0.38] data-[highlighted]:bg-onSurface/[0.12]",
className
)}
{...props}
ref={forwardedRef}
>
{leading && <span className="text-onSurfaceVariant">{leading}</span>}
<Label size="large">{children}</Label>
{typeof trailing === "string" ? (
<Label size="large" className="ml-auto text-onSurfaceVariant">
{trailing}
</Label>
) : (
<span className="ml-auto text-onSurfaceVariant">{trailing}</span>
)}
</DropdownMenu.Item>
)
)
MenuItem.displayName = "MenuItem"
const MenuSeparator = () => (
<DropdownMenu.Separator className="w-full my-2 h-px bg-outlineVariant" />
)
const MenuSub = DropdownMenu.Sub
const MenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenu.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenu.SubTrigger>
>(({ className, children, ...props }, forwardedRef) => (
<DropdownMenu.SubTrigger
className={cn(
"text-onSurface h-12 flex items-center justify-between gap-3 px-3 aria-disabled:text-opacity-[0.38] select-none outline-none hover:bg-onSurface bg-opacity-0 hover:bg-opacity-[0.08] active:bg-opacity-[0.12] aria-disabled:bg-opacity-0 data-[highlighted]:bg-onSurface data-[highlighted]:bg-opacity-[0.12]",
className
)}
ref={forwardedRef}
{...props}
>
<>
<Label size="large">{children}</Label>
<NavArrowRight />
</>
</DropdownMenu.SubTrigger>
))
MenuSubTrigger.displayName = "MenuSubTrigger"
const MenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenu.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenu.SubContent>
>(({ children, className, ...props }, forwardedRef) => (
<DropdownMenu.SubContent
className={cn(
"flex flex-col rounded bg-surfaceContainer shadow-elevation2 py-2 w-[280px] will-change-[opacity,transform] data-[side=right]:animate-slide-down-and-fade-in origin-top-left data-[state=closed]:animate-slide-up-and-fade-out",
className
)}
ref={forwardedRef}
{...props}
>
{children}
</DropdownMenu.SubContent>
))
MenuSubContent.displayName = "MenuSubContent"
export {
MenuRoot,
MenuTrigger,
MenuPortal,
MenuContent,
MenuItem,
MenuSeparator,
MenuSub,
MenuSubTrigger,
MenuSubContent,
}

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

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

MenuRoot - компонент, содержащий стейт о том, открыто сейчас Menu или нет

MenuTrigger - кнопка, открывающая Menu

MenuPortal - обёртка для MenuContent

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

MenuItem - кнопка в Menu; может быть с иконкой вначале (leading), иконкой или текстом в конце (trailing)

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

MenuSub - аналог MenuRoot, но для вторичного Menu

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

MenuSubContent - аналог MenuContent, но для вторичного Menu

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

<MenuRoot>
<MenuTrigger>
<Button appearance={"tonal"}>Trigger the menu</Button>
</MenuTrigger>
<MenuPortal>
<MenuContent>
<MenuItem leading={<Plus />}>With leading icon</MenuItem>
<MenuItem trailing="⌘+B" className="pl-12">
With trailing text
</MenuItem>
<MenuItem trailing={<Check />} className="pl-12">
With trailing icon
</MenuItem>
<MenuItem leading={<Plus />} trailing={<Check />}>
With both
</MenuItem>
<MenuItem leading={<Plus />} trailing="⌘+B">
With both
</MenuItem>
<MenuSeparator />
<MenuItem disabled>Disabled item</MenuItem>
</MenuContent>
</MenuPortal>
</MenuRoot>