summaryrefslogtreecommitdiff
path: root/web/source/settings/lib/navigation/menu.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'web/source/settings/lib/navigation/menu.tsx')
-rw-r--r--web/source/settings/lib/navigation/menu.tsx175
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>
+ );
+}