codevise/pageflow

View on GitHub
entry_types/scrolled/package/src/frontend/FitViewport.js

Summary

Maintainability
A
35 mins
Test Coverage
import React, {useContext} from 'react';
import classNames from 'classnames';

import styles from "./FitViewport.module.css";
import Fullscreen, {useFullscreenDimensions} from "./Fullscreen";

const AspectRatioContext = React.createContext();

/**
 * Render a div with the given aspect ratio which does not
 * exceed the heigth of the viewport by setting an appropriate
 * `max-width` on the container.
 *
 * Wrap content in `FitViewport.Content` element:
 *
 *    <FitViewport aspectRatio={0.5625}>
 *      <FitViewport.Content>
 *         <div style={{height: '100%'}}>
 *            This div will have the specified aspec ratio
 *            while not exceeding the height of the viewport
 *         </div>
 *      </FitViewport.Content>
 *      <div>
 *        This div will have the same width as the content.
 *      </div>
 *    </FitViewport>
 *
 * @param {Object} props
 * @param {number} [props.aspectRatio] - Aspect ratio of div.
 * @param {Object} [props.file] - Use width/height of file to calculate aspect ratio.
 * @param {number} [props.scale] - Only take up fraction of the viewport height supplied as value between 0 and 1.
 * @param {Object} [props.opaque] - Render black background behind content.
 * @param {string} [props.fill] - Ignore aspect ration and fill viewport vertically.
 */
export function FitViewport({file, aspectRatio, opaque, children, fill, scale = 1}) {
  const {height} = useFullscreenDimensions();

  if (!file && !aspectRatio) return children;

  aspectRatio = fill ? 'fill' : aspectRatio || (file.height / file.width);

  let maxWidthCSS;

  if (fill) {
    maxWidthCSS = null;
  }
  else if (height) {
    // thumbnail view/fixed size: calculate absolute width in px
    maxWidthCSS = (height / aspectRatio * scale) + 'px';
  } else {
    // published view: set max width to specific aspect ratio depending on viewport height
    maxWidthCSS = (100 / aspectRatio * scale) + 'vh';
  }

  return (
    <div className={classNames(styles.container, {[styles.opaque]: opaque})}
         style={{maxWidth: maxWidthCSS}}>
      <AspectRatioContext.Provider value={aspectRatio}>
        {children}
      </AspectRatioContext.Provider>
    </div>
  );
}

FitViewport.Content =
  function FitViewportContent({children}) {
    let arPaddingTop = useContext(AspectRatioContext);

    if (arPaddingTop === 'fill') {
      return (
        <Fullscreen children={children} />
      )
    }

    arPaddingTop = arPaddingTop * 100

    if (!arPaddingTop) {
      return children;
    }

    return (
      <div className={styles.content}>
        <div style={{paddingTop: arPaddingTop + '%'}}>
        </div>
        <div className={styles.inner}>
          {children}
        </div>
      </div>
    );
  };