Skip to content
This repository was archived by the owner on May 15, 2025. It is now read-only.
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 105 additions & 14 deletions src/components/extenders/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import type { ReactNode } from "react";
import { useMemo } from "react";
import { useMemo, useState } from "react";
import { Fragment } from "react";
import { useTheme } from "../../context/Theme.context";
import { mergeClass } from "../../utils/css";
import type { Component, GetSet } from "../../utils/types";
import Box, { type BoxProps } from "../primitives/Box";
import Collapsible from "../primitives/Collapsible";
import Divider from "../primitives/Divider";
import EventBlocker from "../primitives/EventBlocker";
import Icon from "../primitives/Icon";
import Space from "../primitives/Space";
import Text from "../primitives/Text";
import Group from "./Group";

export interface MenuOptions {
Expand All @@ -29,14 +32,53 @@ const divide = (element: JSX.Element, index: number, arr: JSX.Element[]) => (
</Fragment>
);

export function SubMenuResponsiv({ children, options, size, look, className }: Component<MenuProps & BoxProps>) {
const [isOpen, setIsOpen] = useState(false);

return (
<DropdownMenu.Sub>
<Group className="flex-col !gap-0">
<Group onClick={() => setIsOpen(prev => !prev)}>
{
<Group className="w-full justify-between" size="xl">
{children}
<Icon remix="RiArrowDownSLine" />
</Group>
}
</Group>
<Collapsible state={[isOpen, setIsOpen]}>
<Space size="md" />
<Box>
{Object.entries(options).map(([key, { label, options: subOptions }]) => {
if (subOptions && Object.keys(subOptions).length > 0)
return (
<SubMenuResponsiv {...{ size, look, className }} options={subOptions}>
{label}
</SubMenuResponsiv>
);
return (
<DropdownMenu.Item key={key} className="w-full">
<Text size="lg" className="text-main-12">
{label}
</Text>
</DropdownMenu.Item>
);
})}
</Box>
</Collapsible>
</Group>
</DropdownMenu.Sub>
);
}

export function SubMenu({ children, options, size, look, className }: Component<MenuProps & BoxProps>) {
const { vars } = useTheme();

return (
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>
<Group size="xl" className="w-full justify-between">
{children} <Icon remix="RiArrowRightSLine" />
{children} <Icon remix="RiArrowDownSLine" />
</Group>
</DropdownMenu.SubTrigger>
<DropdownMenu.Portal>
Expand Down Expand Up @@ -74,7 +116,7 @@ export default function Menu({
}: Component<MenuProps & BoxProps>) {
const { vars } = useTheme();

const menu = useMemo(() => {
const desktopMenuItems = useMemo(() => {
return Object.entries(options)
.map(([key, { label, options: subOptions }]) => {
if (subOptions && Object.keys(subOptions).length > 0)
Expand All @@ -88,22 +130,71 @@ export default function Menu({
.map(divide);
}, [options, size, look, className]);

const responsiveMenuItems = useMemo(() => {
return Object.entries(options)
.map(([key, { label, options: subOptions }]) => {
if (subOptions && Object.keys(subOptions).length > 0)
return (
<SubMenuResponsiv {...{ size, look, className }} options={subOptions}>
{label}
</SubMenuResponsiv>
);
return <DropdownMenu.Item key={key}>{label}</DropdownMenu.Item>;
})
.map(divide);
}, [options, size, look, className]);

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
const desktopMenu = useMemo(() => {
return (
<DropdownMenu.Content
className="hidden !pointer-events-auto md:block md:animate-drop md:p-md"
asChild
style={vars}>
<EventBlocker>
<Box
size={size || "lg"}
look={look || "soft"}
className={mergeClass("animate-drop text-main-12 bg-main-2 min-w-[24ch] m-lg", className)}
{...props}>
{desktopMenuItems}
</Box>
</EventBlocker>
</DropdownMenu.Content>
);
}, [options, size, look, className, vars, desktopMenuItems]);

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
const responsivMenu = useMemo(() => {
return (
<DropdownMenu.Content
className="block !pointer-events-auto py-[20px] md:!hidden md:opacity-0"
asChild
style={vars}>
<EventBlocker>
<Box
size={size || "lg"}
look={look || "soft"}
className={mergeClass("animate-drop text-main-12 bg-main-2 min-w-[100vw] min-h-[92vh] !rounded-0")}
{...props}>
{responsiveMenuItems}
</Box>
</EventBlocker>
</DropdownMenu.Content>
);
}, [options, size, look, className, vars, desktopMenuItems]);

return (
<DropdownMenu.Root open={state?.[0]}>
<DropdownMenu.Trigger>{children}</DropdownMenu.Trigger>

<DropdownMenu.Portal>
<DropdownMenu.Content className="!pointer-events-auto animate-drop p-md" asChild style={vars}>
<EventBlocker>
<Box
size={size || "lg"}
look={look || "soft"}
className={mergeClass("animate-drop text-main-12 bg-main-2 min-w-[24ch] m-lg", className)}
{...props}>
{menu}
</Box>
</EventBlocker>
</DropdownMenu.Content>
{
<>
{desktopMenu}
{responsivMenu}
</>
}
</DropdownMenu.Portal>
</DropdownMenu.Root>
);
Expand Down