.ts
TypeScript
(application/typescript)
// 3rd-party
import type { ReactIsland } from "@ethicdevs/react-monolith";
import React, { useCallback, useState } from "react";
import styled, { css } from "styled-components";
// app
import type {
  CommonProps,
  SectionsWithPages,
  WithThemeSchemeProp,
} from "../types";
import { MenuDivider } from "../components/MenuDivider";
import { MenuItem } from "../components/MenuItem";
import { NamedColors } from "../utils/style";

interface SideMenuProps extends CommonProps {
  foo?: boolean;
  currentPageSlug?: string;
  currentSectionSlug?: string;
  menuDefinition: SectionsWithPages;
}

const SideMenu: ReactIsland<SideMenuProps & WithThemeSchemeProp> = ({
  menuDefinition,
  themeScheme,
  currentPageSlug = undefined,
  currentSectionSlug = undefined,
}) => {
  const [activeSectionSlug, setActiveSectionSlug] = useState<string | null>(
    currentSectionSlug || null
  );
  const [activePageSlug, setActivePageSlug] = useState<string | null>(
    currentPageSlug || null
  );

  const onMenuItemSectionClick: React.MouseEventHandler<HTMLAnchorElement> =
    useCallback(
      (ev) => {
        ev.persist();
        ev.preventDefault();

        const windowPathname = new URL(window.location.href).pathname;
        const windowSectionSlug = windowPathname.split("/")[2];
        const linkPathname = new URL(ev.currentTarget.href).pathname;
        const linkSectionSlug = linkPathname.split("/")[2];
        const linkPageSlug = linkPathname.split("/")[3];

        const changePage = () => {
          let timeoutId = setTimeout(() => {
            clearTimeout(timeoutId);
            window.location.replace(linkPathname);
          }, 10);
        };

        if (windowSectionSlug !== linkSectionSlug) {
          setActiveSectionSlug(linkSectionSlug);
          if (activePageSlug !== linkPageSlug) {
            setActivePageSlug(linkPageSlug);
          }
          changePage();
        } else if (
          windowSectionSlug === activeSectionSlug &&
          activeSectionSlug !== linkSectionSlug
        ) {
          setActiveSectionSlug(linkSectionSlug);
          if (activePageSlug !== linkPageSlug) {
            setActivePageSlug(linkPageSlug);
          }
          changePage();
        } else {
          // toggle state
          setActiveSectionSlug((prev) =>
            prev == null ? linkSectionSlug : null
          );
        }
      },
      [
        activeSectionSlug,
        activePageSlug,
        setActiveSectionSlug,
        setActivePageSlug,
      ]
    );

  return (
    <div style={{ width: "100%" }}>
      {menuDefinition != null &&
        Object.entries(menuDefinition).map(
          ([sectionSlug, section], idx, arr) => (
            <React.Fragment key={sectionSlug}>
              <StyledMenuSectionContainer
                themeScheme={themeScheme}
                active={sectionSlug === activeSectionSlug}
              >
                <>
                  <MenuItem
                    data-section
                    slug={sectionSlug}
                    section
                    active={sectionSlug === activeSectionSlug}
                    href={`/docs/${sectionSlug}/${
                      Object.keys(section.pagesBySlug)[0]
                    }`}
                    onClick={onMenuItemSectionClick}
                    themeScheme={themeScheme}
                    title={section.title}
                  >
                    {section.title}
                  </MenuItem>
                  {activeSectionSlug === sectionSlug &&
                    section.pagesBySlug != null &&
                    Object.entries(section.pagesBySlug).map(
                      ([pageSlug, page]) => (
                        <MenuItem
                          key={pageSlug}
                          slug={pageSlug}
                          active={
                            sectionSlug === activeSectionSlug &&
                            pageSlug === activePageSlug
                          }
                          href={`/docs/${sectionSlug}/${pageSlug}`}
                          themeScheme={themeScheme}
                          title={page.metas.title}
                        >
                          {page.metas.title}
                        </MenuItem>
                      )
                    )}
                </>
              </StyledMenuSectionContainer>
              {idx < arr.length - 1 && (
                <MenuDivider themeScheme={themeScheme} />
              )}
            </React.Fragment>
          )
        )}
    </div>
  );
};

const hoverOrActiveCss = css<{ active?: boolean } & WithThemeSchemeProp>`
  ${({ themeScheme }) => css`
    background-color: ${NamedColors.SIDE_MENU_SECTION_HOVER[themeScheme]};
  `};

  & > [data-section] {
    ${({ themeScheme, active = false }) => css`
      border-bottom: 1px solid
        ${active
          ? NamedColors.BORDER_DEFAULT[themeScheme]
          : NamedColors.SIDE_MENU_ITEM_HOVER[themeScheme]};
      background-color: ${NamedColors.SIDE_MENU_ITEM_HOVER[
        themeScheme
      ]} !important;
    `};
  }
`;

const StyledMenuSectionContainer = styled.div<
  { active?: boolean } & WithThemeSchemeProp
>`
  width: 100%;

  transition: background-color 140ms ease-in-out 0s, color 140ms ease-in-out 0s;

  ${({ active = false }) => active && hoverOrActiveCss};

  &:hover {
    ${hoverOrActiveCss};
  }
`;

SideMenu.displayName = "SideMenu";
export default SideMenu;