codevise/pageflow

View on GitHub
entry_types/scrolled/package/src/contentElements/heading/Heading.js

Summary

Maintainability
A
1 hr
Test Coverage
import React, {useEffect, useState, useRef} from 'react';
import classNames from 'classnames';
import {
  withShadowClassName,
  paletteColor,
  Text,
  EditableInlineText,
  useContentElementConfigurationUpdate,
  useContentElementEditorState,
  useContentElementLifecycle,
  useDarkBackground,
  useIsStaticPreview,
  useI18n,
  contentElementWidths,
  utils
} from 'pageflow-scrolled/frontend';

import styles from './Heading.module.css';

export function Heading({configuration, sectionProps, contentElementWidth}) {
  const level = configuration.level || sectionProps.sectionIndex;
  const firstSectionInEntry = level === 0;
  const updateConfiguration = useContentElementConfigurationUpdate();
  const {t} = useI18n({locale: 'ui'});
  const darkBackground = useDarkBackground();
  const {isSelected, isEditable} = useContentElementEditorState();

  const legacyValue = configuration.children;
  const Tag = firstSectionInEntry ? 'h1' : 'h2';

  const forcePaddingTop = firstSectionInEntry && !('marginTop' in configuration);

  const entranceAnimation = (!useIsStaticPreview() && configuration.entranceAnimation) || 'none';
  const [animating, setAnimating] = useState(false);

  useContentElementLifecycle({
    onActivate() {
      setAnimating(entranceAnimation !== 'none');
    },

    onInvisible() {
      if (isEditable) {
        setAnimating(false);
      }
    }
  });

  const previousAnimation = useRef(entranceAnimation);
  const previouslySelected = useRef(isSelected);

  useEffect(() => {
    if (isEditable && previousAnimation.current !== entranceAnimation) {
      previousAnimation.current = entranceAnimation;

      setAnimating(false)
      setTimeout(() => setAnimating(true), 100);
    }
  }, [entranceAnimation, isEditable]);

  useEffect(() => {
    if (!previouslySelected.current && isSelected) {
      setAnimating(true)
    }

    previouslySelected.current = isSelected;
  }, [isSelected]);

  function renderSubtitle(name) {
    const value = configuration[name];

    if (!isSelected && utils.isBlankEditableTextValue(value)) {
      return null;
    }

    return (
      <Text scaleCategory={getScaleCategory(configuration, firstSectionInEntry, name)}>
        <div className={styles[name]}
             role="doc-subtitle">
          <EditableInlineText value={value}
                              hyphens={configuration.hyphens}
                              placeholder={t(`pageflow_scrolled.inline_editing.type_${name}`)}
                              onChange={value => updateConfiguration({[name]: value})} />
        </div>
      </Text>
    );
  }

  return (
    <header className={classNames(styles.root,
                                  styles[`animation-${entranceAnimation}`],
                                  {[styles.animating]: animating},
                                  {[styles.hasTagline]: !utils.isBlankEditableTextValue(
                                    configuration.tagline
                                  ) || isSelected},
                                  {[styles.forcePaddingTop]: forcePaddingTop},
                                  {[styles[sectionProps.layout]]:
                                    contentElementWidth > contentElementWidths.md ||
                                   sectionProps.layout === 'centerRagged'},
                                  {[withShadowClassName]: !sectionProps.invert})}>
      {renderSubtitle('tagline')}
      <Tag className={classNames(styles.main,
                                 'scope-headings',
                                 configuration.typographyVariant &&
                                 `typography-heading-${configuration.typographyVariant}`,
                                 darkBackground ? styles.light : styles.dark)}
           style={{color: paletteColor(configuration.color)}}>
        <Text scaleCategory={getScaleCategory(configuration, firstSectionInEntry)}
              inline={true}>
          <EditableInlineText value={configuration.value}
                              defaultValue={legacyValue}
                              hyphens={configuration.hyphens}
                              placeholder={firstSectionInEntry ?
                                           t('pageflow_scrolled.inline_editing.type_title') :
                                           t('pageflow_scrolled.inline_editing.type_heading')}
                              onChange={value => updateConfiguration({value})} />
        </Text>
      </Tag>
      {renderSubtitle('subtitle')}
    </header>
  );
}

function getScaleCategory(configuration, firstSectionInEntry, suffix = '') {
  const base = `heading${capitalize(suffix)}`;

  switch (configuration.textSize) {
    case 'large':
      return `${base}-lg`;
    case 'medium':
      return `${base}-md`;
    case 'small':
      return `${base}-sm`;
    default:
      return firstSectionInEntry ? `${base}-lg` : `${base}-sm`;
  }
}

function capitalize(string) {
  return string.charAt(0).toUpperCase() + string.slice(1);
}