.ts
TypeScript
(application/typescript)
// 3rd-party
import React, { FC, useState } from "react";
import styled, { css } from "styled-components";
// app
import type { LayoutProps, WithThemeSchemeProp } from "../types";
import { Const } from "../const";
import { NamedColors } from "../utils/style";
import { removeCommentsAndSpacing } from "../utils/shared";
// app islands
import InstantRouterIndicator from "../islands/InstantRouterIndicator";
// app components
import { PageHeader } from "./PageHeader";
import { DrawerPrimary } from "./DrawerPrimary";
import { DrawerSettings } from "./DrawerSettings";

const BRANDLINE_HEIGHT = 4;
const HEADER_HEIGHT = 64;

// Default Layout implementation.
// (dataloading happens through HOC at export level lower in file)
const LayoutComponent: FC<LayoutProps & WithThemeSchemeProp> = (props) => {
  const {
    appVersion,
    children,
    gitStamp,
    themeScheme,
    showDrawerSettings = false,
    username,
    showDrawerPrimary = false,
    orgSlug = "",
    repoSlug = "",
    currentRef = Const.DEFAULT_HEAD_REF,
    path = "",
    layoutCounters,
  } = props;

  const sharedProps = {
    themeScheme,
  };

  const [drawerPrimaryOpen, setDrawerPrimaryOpen] =
    useState<boolean>(showDrawerPrimary);
  const [drawerSettingsOpen, setDrawerSettingsOpen] =
    useState<boolean>(showDrawerSettings);

  return (
    <>
      <style
        dangerouslySetInnerHTML={{
          __html: removeCommentsAndSpacing(`
            html,
            body {
              margin: 0;
              padding: 0;
              font-family: sans-serif;
              font-size: 16px;
              background-color: ${NamedColors.BACKGROUND[themeScheme]};
            }
            html {
              /* so anchor are not hidden behind sticky header */
              scroll-padding-top: ${HEADER_HEIGHT + 64}px;
            }
            body {
              overflow-x: hidden;
              overflow-y: scroll;
            }
            * {
              box-sizing: border-box;
            }
            a {
              color: ${NamedColors.TEXT_LINK[themeScheme]};
              font-weight: bold;
              text-decoration: none;
              transition: opacity 220ms linear 0s;
            }
            a:hover {
              text-decoration: underline;
            }
            a:active {
              opacity: 0.87;
            }
            a > * {
              pointer-events: none;
            }
            form {
              width: 100%;
            }
          `),
        }}
      />
      <StyledLayoutWrapper {...sharedProps}>
        <div data-islandid={`${InstantRouterIndicator.name}$$0`}>
          <InstantRouterIndicator />
        </div>
        <StyledPageWrapper>
          <DrawerPrimary
            commonProps={props as any}
            themeScheme={themeScheme}
            visible={drawerPrimaryOpen}
            counters={layoutCounters}
            orgSlug={orgSlug}
            repoSlug={repoSlug}
            currentRef={currentRef}
            path={path}
          />
          <DrawerSettings
            commonProps={props as any}
            themeScheme={themeScheme}
            visible={drawerSettingsOpen}
            counters={layoutCounters}
            username={username!}
          />
          <StyledChildrenWrapper
            {...sharedProps}
            showDrawerPrimary={drawerPrimaryOpen}
          >
            <StyledPageHeaderWrapper {...sharedProps}>
              <PageHeader
                commonProps={props as any}
                themeScheme={themeScheme}
                forceShowLogo={
                  showDrawerPrimary !== true && showDrawerSettings !== true
                }
                setDrawerPrimaryOpen={setDrawerPrimaryOpen}
                setDrawerSettingsOpen={setDrawerSettingsOpen}
              />
            </StyledPageHeaderWrapper>
            {children}
            <StyledFooterWrapper>
              <p>
                <a href={"https://gitfoss.dev/ethicdevs/gitfoss"}>
                  {Const.APP_NAME} &bull; v{appVersion} (#
                  {gitStamp.slice(0, 7)}) &bull; MIT License
                </a>
              </p>
            </StyledFooterWrapper>
          </StyledChildrenWrapper>
        </StyledPageWrapper>
      </StyledLayoutWrapper>
    </>
  );
};

// wrap and export the Layout as named export
// const _LayoutWithCounters = withLayoutCounters(
//   LayoutComponent as NonNullable<() => JSX.Element>,
// );
// withLayoutCounters; // as NonNullable<() => JSX.Element>;
// withLayoutCounters;
export const Layout = LayoutComponent;

/* Styled components (unchanged from previous layout structure) */
const StyledLayoutWrapper = styled.div<WithThemeSchemeProp>`
  display: flex;
  flex-flow: column nowrap;
  justify-content: flex-start;
  align-items: flex-start;

  min-height: 100vh;
  min-width: 100%;
  max-width: 100%;

  ${({ themeScheme }) => css`
    background-color: ${NamedColors.BACKGROUND[themeScheme]};
    color: ${NamedColors.TEXT_DEFAULT[themeScheme]};
  `};
`;

const StyledPageHeaderWrapper = styled.div<WithThemeSchemeProp>`
  display: flex;
  flex-flow: row nowrap;
  justify-content: flex-start;
  align-items: center;

  width: 100%;
  min-height: ${HEADER_HEIGHT}px;
  height: ${HEADER_HEIGHT}px;
  max-height: ${HEADER_HEIGHT}px;

  position: sticky;
  top: 0;
  z-index: 11000;

  gap: 8px;
  padding: 0 16px;

  backdrop-filter: blur(8px);

  ${({ themeScheme }) => css`
    background-color: ${NamedColors.HEADER[themeScheme]};
    border-bottom: 1px solid ${NamedColors.BORDER_DEFAULT[themeScheme]};

    & > a > h1 {
      color: ${NamedColors.TEXT_DEFAULT[themeScheme]};
      margin: 0;
      margin-left: 16px;
    }
  `};

  & > a {
    text-decoration: unset;

    display: flex;
    flex-flow: row nowrap;
    justify-content: flex-start;
    align-items: center;
  }
`;

const StyledPageWrapper = styled.div`
  display: flex;
  flex-flow: row nowrap;
  justify-content: flex-start;
  align-items: stretch;

  position: relative;

  flex: 1;
  width: 100%;
  min-height: calc(100% - ${BRANDLINE_HEIGHT + HEADER_HEIGHT}px);
`;

const StyledChildrenWrapper = styled.div<{ showDrawerPrimary?: boolean }>`
  display: flex;
  flex-flow: column nowrap;
  justify-content: flex-start;
  align-items: flex-start;

  flex: 1;
  width: 100%;

  ${({ showDrawerPrimary = false }) =>
    showDrawerPrimary &&
    css`
      max-width: calc(100% - 300px);
    `};

  @media only screen and (max-width: 768px) {
    max-width: 100%;
  }
`;

const StyledFooterWrapper = styled.div`
  display: flex;
  flex-flow: row wrap;
  justify-content: center;
  align-items: center;

  width: 100%;
  min-height: 44px;
  height: 44px;

  & > p {
    margin: 0 0 24px 0;
    opacity: 0.67;
    font-size: 12px;
  }
`;