/* GoToSocial Copyright (C) GoToSocial Authors admin@gotosocial.org SPDX-License-Identifier: AGPL-3.0-or-later This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import React, { PropsWithChildren } from "react"; import { Link, useRoute } from "wouter"; import { BaseUrlContext, MenuLevelContext, useBaseUrl, useHasPermission, useMenuLevel, } from "./util"; import UserLogoutCard from "../../components/user-logout-card"; import { nanoid } from "nanoid"; export interface MenuItemProps { /** * Name / title of this menu item. */ name?: string; /** * Url path component for this menu item. */ itemUrl: string; /** * If this menu item is a category containing * children, which child should be selected by * default when category title is clicked. * * Optional, use for categories only. */ defaultChild?: string; /** * Permissions required to access this * menu item (none, "moderator", "admin"). */ permissions?: string[]; /** * Fork-awesome string to render * icon for this menu item. */ icon?: string; } export function MenuItem(props: PropsWithChildren) { const { name, itemUrl, defaultChild, permissions, icon, children, } = props; // Derive where this item is // in terms of URL routing. const baseUrl = useBaseUrl(); const thisUrl = [ baseUrl, itemUrl ].join('/'); // Derive where this item is in // terms of nesting within the menu. const thisLevel = useMenuLevel(); const nextLevel = thisLevel+1; const topLevel = thisLevel === 0; // Check whether this item is currently active // (ie., user has selected it in the menu). // // This uses a wildcard to mark both parent // and relevant child as active. // // See: // https://github.com/molefrog/wouter?tab=readme-ov-file#useroute-route-matching-and-parameters const [isActive] = useRoute([ thisUrl, "*?" ].join("/")); // Don't render item if logged-in user // doesn't have permissions to use it. if (!useHasPermission(permissions)) { return null; } // Check whether this item has children. const hasChildren = children !== undefined; const childrenArray = hasChildren && Array.isArray(children); // Class name of the item varies depending // on where it is in the menu, and whether // it has children beneath it or not. const classNames: string[] = []; if (topLevel) { classNames.push("category", "top-level"); } else { switch (true) { case thisLevel === 1 && hasChildren: classNames.push("category", "expanding"); break; case thisLevel === 1 && !hasChildren: classNames.push("view", "expanding"); break; case thisLevel >= 2 && hasChildren: classNames.push("nested", "category"); break; case thisLevel >= 2 && !hasChildren: classNames.push("nested", "view"); break; } } if (isActive) { classNames.push("active"); } let content: React.JSX.Element | null; if ((isActive || topLevel) && childrenArray) { // Render children as a nested list. content = ; } else if (isActive && hasChildren) { // Render child as solo element. content = <>{children}; } else { // Not active: hide children. content = null; } // If a default child is defined, this item should point to that. const href = defaultChild ? [ thisUrl, defaultChild ].join("/") : thisUrl; return (
  • {icon &&