sendou.ink/app/components/elements/Menu.tsx
Kalle 2b5b1b1948
Some checks are pending
E2E Tests / e2e (push) Waiting to run
Tests and checks on push / run-checks-and-tests (push) Waiting to run
Updates translation progress / update-translation-progress-issue (push) Waiting to run
New match page (#3032)
2026-05-04 18:15:10 +03:00

111 lines
2.2 KiB
TypeScript

import clsx from "clsx";
import {
Header,
Menu,
MenuItem,
type MenuItemProps,
MenuTrigger,
Popover,
type PopoverProps,
Section,
} from "react-aria-components";
import { Image } from "../Image";
import styles from "./Menu.module.css";
interface SendouMenuProps {
trigger: React.ReactNode;
scrolling?: boolean;
opensLeft?: boolean;
children: React.ReactNode;
popoverClassName?: string;
placement?: PopoverProps["placement"];
}
export function SendouMenu({
children,
trigger,
opensLeft,
scrolling,
placement,
}: SendouMenuProps) {
return (
<MenuTrigger>
{trigger}
<Popover
placement={placement}
className={clsx(styles.popover, "scrollbar", {
[styles.scrolling]: scrolling,
[styles.popoverOpensLeft]: !opensLeft,
})}
>
<Menu className={styles.itemsContainer}>{children}</Menu>
</Popover>
</MenuTrigger>
);
}
export interface SendouMenuItemProps extends MenuItemProps {
icon?: React.ReactNode;
imagePath?: string;
isActive?: boolean;
isDestructive?: boolean;
}
export function SendouMenuSection({
children,
headerText,
headerClassName,
}: {
children: React.ReactNode;
headerText?: React.ReactNode;
headerClassName?: string;
}) {
return (
<Section>
{headerText ? (
<Header className={clsx(styles.menuHeader, headerClassName)}>
{headerText}
</Header>
) : null}
{children}
</Section>
);
}
export function SendouMenuItem(props: SendouMenuItemProps) {
const textValue =
props.textValue ??
(typeof props.children === "string" ? props.children : undefined);
return (
<MenuItem
{...props}
textValue={textValue}
className={({ isSelected, isDisabled }) =>
clsx(styles.item, {
[styles.itemSelected]: isSelected,
[styles.itemDisabled]: isDisabled,
[styles.itemActive]: props.isActive,
[styles.itemDestructive]: props.isDestructive,
})
}
>
{/** biome-ignore lint/complexity/noUselessFragments: Biome v2 migration */}
<>
{props.icon ? (
<span className={styles.itemIcon}>{props.icon}</span>
) : null}
{props.imagePath ? (
<Image
path={props.imagePath}
alt=""
width={20}
height={20}
className={styles.itemImg}
/>
) : null}
{props.children}
</>
</MenuItem>
);
}