diff options
Diffstat (limited to 'web/source/settings/lib/navigation/menu.tsx')
-rw-r--r-- | web/source/settings/lib/navigation/menu.tsx | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/web/source/settings/lib/navigation/menu.tsx b/web/source/settings/lib/navigation/menu.tsx new file mode 100644 index 000000000..514e3ea2f --- /dev/null +++ b/web/source/settings/lib/navigation/menu.tsx @@ -0,0 +1,175 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ + +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<MenuItemProps>) { + 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 { + if (thisLevel === 1 && hasChildren) { + classNames.push("category", "expanding"); + } else if (thisLevel === 1 && !hasChildren) { + classNames.push("view", "expanding"); + } else if (thisLevel === 2) { + classNames.push("view", "nested"); + } + } + + if (isActive) { + classNames.push("active"); + } + + let content: React.JSX.Element | null; + if ((isActive || topLevel) && childrenArray) { + // Render children as a nested list. + content = <ul>{children}</ul>; + } 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 ( + <li key={nanoid()} className={classNames.join(" ")}> + <Link href={href} className="title"> + <span> + {icon && <i className={`icon fa fa-fw ${icon}`} aria-hidden="true" />} + {name} + </span> + </Link> + { content && + <BaseUrlContext.Provider value={thisUrl}> + <MenuLevelContext.Provider value={nextLevel}> + {content} + </MenuLevelContext.Provider> + </BaseUrlContext.Provider> + } + </li> + ); +} + +export interface SidebarMenuProps{} + +export function SidebarMenu({ children }: PropsWithChildren<SidebarMenuProps>) { + return ( + <div className="sidebar"> + <UserLogoutCard /> + <nav className="menu-tree"> + <MenuLevelContext.Provider value={0}> + <ul className="top-level"> + {children} + </ul> + </MenuLevelContext.Provider> + </nav> + </div> + ); +} |