rofrischmann/react-look

View on GitHub
packages/react-look/modules/plugins/friendlyClassName.js

Summary

Maintainability
B
4 hrs
Test Coverage
import { Utils } from 'react-look-core'
const { getChildType } = Utils

const classNameTemplate = (className, Component, element) => {
  return Component.constructor.displayName + '-' + getChildType(element) + '--' + className
}

const classMapping = new Map()

export default function friendlyClassNames({ StyleContainer, Component, element, newProps, styles, config }) {
  // Only transform if not already transformed and a className exists
  if (!newProps._hasFriendlyClassNames && newProps.className) {
    newProps.className = newProps.className.split(' ').reduce((className, cls) => {
      cls = cls.trim()
      // If the className has already been resolved
      // just use the former new className
      if (classMapping.has(cls)) {
        className += className + className !== '' ? ' ' : '' + classMapping.get(cls)
      } else {
        let newClass
        if (config && config.friendlyClassNameTemplate) {
          newClass = config.friendlyClassNameTemplate(cls, Component, element)
        } else {
          newClass = classNameTemplate(cls, Component, element)
        }
        let isLookClass = false

        // immutable selectors to iterate without changes
        const selectors = [ ...StyleContainer.selectors ]
        const mediaQueries = [ ...StyleContainer.mediaQueries ]

        // Replace basic selector classNames
        selectors.forEach(([ selector, styles ]) => {
          // only if the selectors starts with the className
          if (selector.indexOf('.' + cls) === 0) {
            const pseudo = selector.replace(new RegExp('.' + cls, 'g'), '')
            // Cache resolved classNames for later use
            classMapping.set(cls, newClass)
            // Push the new className and remove the old one
            StyleContainer.add('.' + newClass + pseudo, styles)
            StyleContainer.selectors.delete(selector)
            isLookClass = true
          }
        })

        // Iterate media queries and replace selectors
        mediaQueries.forEach(([ media, selectors ]) => {
          const mediaSelectors = [ ...selectors ]
          mediaSelectors.forEach(([ selector, styles ]) => {
            if (selector.indexOf('.' + cls) === 0) {
              isLookClass = true
              const pseudo = selector.replace(new RegExp('.' + cls, 'g'), '')
              classMapping.set(cls, newClass)
              // Also overwrite media query selectors
              StyleContainer.add('.' + newClass + pseudo, styles, media)
              StyleContainer.mediaQueries.get(media).delete('.' + cls + pseudo)
            }
          })
        })

        // Concats the new className or uses the old className
        // if it is not provided by Look
        className += (className !== '' ? ' ' : '') + (isLookClass ? newClass : cls)
      }
      return className; // eslint-disable-line
    }, '')
  }

  // Forces Look to clone the element
  newProps._lookShouldUpdate = true
  // Sets a flag to be able to skip resolving next time
  newProps._hasFriendlyClassNames = true

  return styles
}