Newer
Older
CrypticOreWallet / src / layout / nav / TopMenu.tsx
@BuildTools BuildTools on 9 Jun 2021 4 KB im gay
// Copyright (c) 2020-2021 Drew Lemmy
// This file is part of TenebraWeb 2 under AGPL-3.0.
// Full details: https://github.com/tmpim/TenebraWeb2/blob/master/LICENSE.txt
import { useState, useCallback, useMemo, useContext, createContext, FC, ReactNode } from "react";
import { Menu, Dropdown } from "antd";
import {
  MoreOutlined, SettingOutlined, SendOutlined, DownloadOutlined,
  SortAscendingOutlined
} from "@ant-design/icons";

import { useTFns } from "@utils/i18n";

import { ConditionalLink } from "@comp/ConditionalLink";
import { useBreakpoint } from "@utils/hooks";

import { OpenSortModalFn, SetOpenSortModalFn } from "@utils/table/SortModal";

import Debug from "debug";
const debug = Debug("tenebraweb:top-menu");

export type Opts = React.ReactNode | undefined;
export type SetMenuOptsFn = (opts: Opts) => void;

interface TopMenuCtxRes {
  options?: ReactNode;
  setMenuOptions?: SetMenuOptsFn;

  openSortModalFn?: OpenSortModalFn;
  setOpenSortModal?: SetOpenSortModalFn;
}

export const TopMenuContext = createContext<TopMenuCtxRes>({});

export function TopMenu(): JSX.Element {
  const { tStr } = useTFns("nav.");
  const bps = useBreakpoint();

  const ctxRes = useContext(TopMenuContext);
  const options = ctxRes?.options;
  const openSortModalFn = ctxRes?.openSortModalFn;

  const menu = useMemo(() => (
    <Dropdown
      trigger={["click"]}
      overlayClassName="site-header-top-dropdown-menu"
      overlay={<Menu>
        {/* Send Tenebra */}
        <Menu.Item>
          <ConditionalLink to="/send" matchTo aria-label={tStr("sendLong")}>
            <div><SendOutlined />{tStr("sendLong")}</div>
          </ConditionalLink>
        </Menu.Item>

        {/* Request Tenebra */}
        <Menu.Item>
          <ConditionalLink to="/request" matchTo aria-label={tStr("requestLong")}>
            <div><DownloadOutlined />{tStr("requestLong")}</div>
          </ConditionalLink>
        </Menu.Item>

        {/* Only show the extra divider if there are options */}
        {options && <Menu.Divider />}

        {/* Page-specified options */}
        {options}

        {/* If the page is a bulk listing on mobile, show a button to adjust
          * the sort order. */}
        {openSortModalFn?.[0] && <>
          <Menu.Divider />

          <Menu.Item onClick={openSortModalFn[0]}>
            <SortAscendingOutlined />{tStr("sort")}
          </Menu.Item>
        </>}

        <Menu.Divider />

        {/* Settings item */}
        <Menu.Item>
          <ConditionalLink to="/settings" matchTo aria-label={tStr("settings")}>
            <div><SettingOutlined />{tStr("settings")}</div>
          </ConditionalLink>
        </Menu.Item>
      </Menu>}
    >
      <MoreOutlined />
    </Dropdown>
  ), [tStr, options, openSortModalFn]);

  // If on mobile and there are options available from the page, display them
  // instead of the settings button.
  const btn = useMemo(() => (
    // Menu or settings button in the header
    <Menu
      theme="dark" mode="horizontal"
      selectable={false} forceSubMenuRender={true}
      className="site-header-settings"
    >
      {!bps.md
        ? (
          // Menu button for mobile
          <Menu.Item key="1" title={tStr("more")}>
            {menu}
          </Menu.Item>
        )
        : (
          // Regular settings button
          <Menu.Item key="1" icon={<SettingOutlined />} title={tStr("settings")}>
            <ConditionalLink to="/settings" matchTo aria-label={tStr("settings")} />
          </Menu.Item>
        )}
    </Menu>
  ), [tStr, bps, menu]);

  return btn;
}

export const TopMenuProvider: FC = ({ children }) => {
  const [menuOptions, setMenuOptions] = useState<ReactNode>();
  const [openSortModalFn, setOpenSortModal] = useState<OpenSortModalFn>();

  const res: TopMenuCtxRes = useMemo(() => ({
    options: menuOptions, setMenuOptions,
    openSortModalFn, setOpenSortModal
  }), [menuOptions, openSortModalFn]);

  return <TopMenuContext.Provider value={res}>
    {children}
  </TopMenuContext.Provider>;
};

export type TopMenuOptionsHookRes = [
  boolean, // isMobile
  SetMenuOptsFn, // set
  () => void, // unset
  SetOpenSortModalFn | undefined
]

export function useTopMenuOptions(): TopMenuOptionsHookRes {
  const bps = useBreakpoint();
  const { setMenuOptions, setOpenSortModal } = useContext(TopMenuContext);

  const set = useCallback((opts: Opts) => {
    debug("top menu options hook set");
    setMenuOptions?.(opts);
  }, [setMenuOptions]);

  const unset = useCallback(() => {
    debug("top menu options hook destructor");
    setMenuOptions?.(undefined);
    setOpenSortModal?.(undefined);
  }, [setMenuOptions, setOpenSortModal]);

  // Return whether or not the options are being shown
  return [!bps.md, set, unset, setOpenSortModal];
}