gymnastjs/gymnast

View on GitHub
src/utils/index.tsx

Summary

Maintainability
A
1 hr
Test Coverage
import cxs from '../cxs'
import defaults from '../defaults'
import {
Noop,
SpacingAliases,
SpacingProps,
SpacingValues,
ConfigDefaults,
} from '../types'
import log from '../log'
import { errors } from '../errors'
 
const hasDefinedValues = (keys: { [key: string]: any | void }) => (
key: string
) => typeof keys[key] !== 'undefined'
const isDefined = (val?: any) => typeof val !== 'undefined'
// regex case examples: https://regex101.com/r/bs73rZ/1
 
export const splitPattern = /(?:(?:\s+)?,(?:\s+)?|\s+)/
export const noop: Noop = () => null
export const times = (n: number): any[] =>
new Array(n).fill(undefined).map((val, index) => index)
export const kebabCase = (str: string) =>
str
.replace(/^[A-Z]/, match => match.toLowerCase())
.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`)
 
export function validateSpacingProps(
props: SpacingProps & {
marginArray?: Array<number> | void
paddingArray?: Array<number> | void
}
) {
if (process.env.NODE_ENV === 'production') {
return true
}
 
const margins = ['marginTop', 'marginRight', 'marginBottom', 'marginLeft']
const paddings = [
'paddingTop',
'paddingRight',
'paddingBottom',
'paddingLeft',
]
 
if (
(typeof props.marginArray !== 'undefined' &&
margins.some(hasDefinedValues(props))) ||
(typeof props.paddingArray !== 'undefined' &&
paddings.some(hasDefinedValues(props)))
) {
log.error(errors.MIXEDSPACING, `"${JSON.stringify(props)}" used`)
return false
}
return true
}
 
Function `getSpacing` has 27 lines of code (exceeds 25 allowed). Consider refactoring.
function getSpacing(
values: Array<number> | void = [],
type: 'margin' | 'padding'
): { [CSSProp: string]: number } {
if (!values || !values.length) {
return {}
}
 
let allValues = []
 
switch (values.length) {
case 1:
allValues = [values[0], values[0], values[0], values[0]]
break
case 2:
allValues = [values[0], values[1], values[0], values[1]]
break
case 3:
allValues = [values[0], values[1], values[2], values[1]]
break
case 4:
allValues = values
break
default:
log.error(errors.TOOMANYSPACEVAL, `"${JSON.stringify(values)}" used`)
allValues = values
}
 
return {
[`${type}Top`]: allValues[0],
[`${type}Right`]: allValues[1],
[`${type}Bottom`]: allValues[2],
[`${type}Left`]: allValues[3],
}
}
 
export function getCSS(
prop: string | void,
value: number | string | void,
base: number
): { [CSSProp: string]: number } {
const num = typeof value === 'number' ? value : parseFloat(value || '0')
 
if (!prop || typeof value === 'undefined') {
return {}
}
 
if (prop.includes('padding')) {
return { [prop]: num * base }
}
 
return {
[`${prop.replace('margin', 'border')}Width`]: num * base,
}
}
/**
* parseSpacing allows using different kinds of input for spacing parameters. Instead of allowing
* only number arrays, the following are also valid:
*
* - arrays of strings or numbers (converted to float):
* - `margin={["1", 0, "0.5"]}` becomes `[1, 0, 0.5]`
* - space-separated or comma-separated strings
* - `margin="0"` becomes `[0]`
* - `margin="1 0"` becomes `[1, 0]`
* - `margin="1,0"` becomes `[2, 0]`
* - numbers
* - `margin={1}` becomes `[1]`
*/
 
export function parseSpacing(
spacing: any,
spacingAliases: SpacingAliases | void
): number[] | void {
if (typeof spacing === 'undefined') {
return undefined
}
if (typeof spacing === 'number') {
return [spacing]
}
 
let spacingArray
if (spacing instanceof Array) {
spacingArray = spacing
} else if (typeof spacing === 'string') {
spacingArray = spacing.split(splitPattern)
}
if (spacingArray) {
return replaceSpacingAliases(spacingArray, spacingAliases).map(value =>
parseFloat(`${value}`)
)
}
 
log.error(errors.INVALIDSPACING, `"${typeof spacing}" used`)
return undefined
}
 
function replaceSpacingAlias(
value: SpacingValues,
spacingAliases: SpacingAliases | void
) {
if (spacingAliases && typeof value === 'string' && value in spacingAliases) {
return spacingAliases[value]
}
return value
}
 
export function replaceSpacingAliases(
spacingArray: Array<SpacingValues>,
spacingAliases: SpacingAliases | void
): Array<SpacingValues> {
return spacingArray.map(value => replaceSpacingAlias(value, spacingAliases))
}
 
function replaceSpacingAliasValues({
props,
spacingAliases,
}: {
props: { [direction: string]: any }
spacingAliases: SpacingAliases
}): { [direction: string]: SpacingValues } {
return Object.keys(props).reduce(
(acc, key) => ({
...acc,
[key]: replaceSpacingAlias(props[key], spacingAliases),
}),
{}
)
}
 
type CombineSpacingSettings = {
spacingProps: SpacingProps
base: number
spacingAliases?: SpacingAliases
gutter?: number
verticalGutter?: number
}
 
export function combineSpacing({
spacingProps,
base,
spacingAliases,
gutter = defaults.gutter,
verticalGutter = defaults.verticalGutter,
}: CombineSpacingSettings) {
const combinedSpacingAliases = {
...defaults.spacingAliases,
...spacingAliases,
gutter,
'gutter/2': gutter / 2,
verticalGutter,
'verticalGutter/2': verticalGutter / 2,
}
const { margin, padding, ...props } = spacingProps
const marginArray = parseSpacing(margin, combinedSpacingAliases)
const paddingArray = parseSpacing(padding, combinedSpacingAliases)
 
if (
!validateSpacingProps({
marginArray,
paddingArray,
...props,
})
) {
return {}
}
 
const flatProps = {
...replaceSpacingAliasValues({
props,
spacingAliases: combinedSpacingAliases,
}),
...getSpacing(marginArray, 'margin'),
...getSpacing(paddingArray, 'padding'),
}
 
return Object.keys(flatProps).reduce(
(acc, prop) => ({ ...acc, ...getCSS(prop, flatProps[prop], base) }),
{}
)
}
 
export function toCXS(raw: { [CSSProp: string]: string | number | {} }) {
const styles: { [CSSProp: string]: string } = {}
 
Object.keys(raw).forEach(style => {
styles[style] = cxs(raw[style])
})
 
return styles
}
 
export function getValue<A>(
context: { [key: string]: any } = {},
property: keyof ConfigDefaults,
override?: A
): A {
return [override, context[property], defaults[property]].find(isDefined)
}
 
export function getValues<C extends {}, D extends {}>(
context?: C,
overrides?: D
): ConfigDefaults {
return {
...defaults,
...(context || {}),
...(overrides || {}),
}
}
 
export function accumulateOver(props: Array<string>) {
return (acc: any, current: any) => {
props.forEach(prop => {
acc[prop] = Object.assign(acc[prop], current[prop])
})
return acc
}
}
 
export default {
accumulateOver,
combineSpacing,
getCSS,
getValue,
getValues,
noop,
parseSpacing,
replaceSpacingAliases,
splitPattern,
times,
toCXS,
validateSpacingProps,
}