digitalfabrik/integreat-app

View on GitHub
web/src/components/Layout.tsx

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
import React, { ReactNode, useLayoutEffect, useState } from 'react'
import styled, { css } from 'styled-components'

import dimensions from '../constants/dimensions'
import useWindowDimensions from '../hooks/useWindowDimensions'
import { MobileBanner } from './MobileBanner'

const additionalToolbarTopSpacing = 32

export const RichLayout = styled.div`
  position: relative;
  display: flex;
  min-height: 100vh;
  flex-direction: column;
  justify-content: space-between;
  color: ${props => props.theme.colors.textColor};
  font-family: ${props => props.theme.fonts.web.decorativeFont};
  font-size-adjust: ${props => props.theme.fonts.fontSizeAdjust};
  background-color: ${props => props.theme.colors.backgroundColor};
  line-height: ${props => props.theme.fonts.decorativeLineHeight};

  & a,
  button {
    &:focus-visible {
      outline: 2px solid ${props => props.theme.colors.textSecondaryColor};
    }

    cursor: pointer;
  }

  input {
    &:focus-visible {
      outline: 2px solid ${props => props.theme.colors.textSecondaryColor};
    }
  }

  textarea {
    &:focus-visible {
      outline: 2px solid ${props => props.theme.colors.textSecondaryColor};
    }
  }
`

const Body = styled.div<{ $fullWidth: boolean; $disableScrollingSafari: boolean }>`
  width: 100%;
  box-sizing: border-box;
  margin: 0 auto;
  flex-grow: 1;
  background-color: ${props => props.theme.colors.backgroundColor};
  word-wrap: break-word;
  min-height: 100%;

  /* Fix jumping iOS Safari Toolbar by prevent scrolling on body */

  ${props =>
    props.$disableScrollingSafari &&
    css`
      @supports (-webkit-touch-callout: none) {
        /* CSS specific to iOS safari devices */
        position: fixed;
        overflow: hidden;
      }
    `};
  /* https://aykevl.nl/2014/09/fix-jumping-scrollbar */
  ${props =>
    !props.$fullWidth &&
    css`
      @media screen and ${dimensions.minMaxWidth} {
        padding-inline: calc((100vw - ${dimensions.maxWidth}px) / 2) calc((200% - 100vw - ${dimensions.maxWidth}px) / 2);
      }
    `};
`

const Main = styled.main<{ $fullWidth: boolean }>`
  display: inline-block;
  width: ${props => (props.$fullWidth ? '100%' : dimensions.maxWidth - 2 * dimensions.toolbarWidth)}px;
  max-width: calc(100% - ${dimensions.toolbarWidth}px);
  box-sizing: border-box;
  margin: 0 auto;
  padding: ${props => (props.$fullWidth ? '0' : `0 ${dimensions.mainContainerHorizontalPadding}px 30px`)};
  text-align: start;
  word-wrap: break-word;

  & p {
    margin: ${props => props.theme.fonts.standardParagraphMargin} 0;
  }

  @media screen and ${dimensions.smallViewport} {
    position: static;
    width: 100%;
    max-width: initial;
    margin-top: 0;
  }
`

const Aside = styled.aside<{ $languageSelectorHeight: number }>`
  top: ${props => props.$languageSelectorHeight + dimensions.headerHeightLarge + additionalToolbarTopSpacing}px;
  margin-top: ${props => props.$languageSelectorHeight - dimensions.navigationMenuHeight}px;
  display: inline-block;
  position: sticky;
  width: ${dimensions.toolbarWidth}px;
  vertical-align: top;
  z-index: 10;

  &:empty {
    display: none;
  }

  &:empty + * {
    display: block;
    max-width: 100%;
  }
`

export const LAYOUT_ELEMENT_ID = 'layout'

type LayoutProps = {
  footer?: ReactNode
  header?: ReactNode
  toolbar?: ReactNode
  chat?: ReactNode
  children?: ReactNode
  fullWidth?: boolean
  disableScrollingSafari?: boolean
}

const Layout = ({
  footer,
  header,
  toolbar,
  chat,
  children,
  fullWidth = false,
  disableScrollingSafari = false,
}: LayoutProps): JSX.Element => {
  const { width, viewportSmall } = useWindowDimensions()
  const [languageSelectorHeight, setLanguageSelectorHeight] = useState<number>(0)

  useLayoutEffect(() => {
    const panelHeight = document.getElementById('languageSelector')?.clientHeight
    setLanguageSelectorHeight(panelHeight ?? 0)
  }, [width])

  return (
    <RichLayout id={LAYOUT_ELEMENT_ID}>
      <MobileBanner />
      {header}
      <Body $fullWidth={fullWidth} $disableScrollingSafari={disableScrollingSafari}>
        {!viewportSmall && <Aside $languageSelectorHeight={languageSelectorHeight}>{toolbar}</Aside>}
        <Main $fullWidth={fullWidth}>{children}</Main>
      </Body>
      {viewportSmall && toolbar}
      {chat}
      {footer}
    </RichLayout>
  )
}

export default Layout