@carpenjk/prop-x
TypeScript icon, indicating that this package has built-in type declarations

3.0.1 • Public • Published

prop-x

A library for for passing for creating, manipulating, and using responsive props in react.

Introduction

prop-x is a library to facilitate passing multiple values for each react property. The library contains utilities for obtaining the correct value based on a provided index such as a breakpoint. The library contains can be separated into three main categories:

  • Core Functions for manipulating and retrieving key pair value ojects for usage as 2d props (i.e {prop1: [val1, val2, val3], prop2...})
  • CSS Functions for usage with styled-components that facilitate responsive styles within the styled components css. This is especially helpful for static site generation and server side rendering as now responsive properties can generate responsive css with no reliance on javascript's window object which is unavailable on the server.
  • Hooks 2 hooks are available that can be useful with client side only code. These hooks are useBreakpoints and useWindowSize.

api

function path description
parseSizeUnits @carpenjk/prop-x Utility for calculations on size props
parseAndCalc @carpenjk/prop-x Utility for calculations on size props
getPropIndex @carpenjk/prop-x Gets usable index for a given prop
getIndexedPropValue @carpenjk/prop-x Gets prop value for a given index
inverseProp @carpenjk/prop-x returns array of inversed boolean values
unwindProps @carpenjk/prop-x transforms prop arrays into an array of object key pair values
windProps @carpenjk/prop-x transforms array of object key pair values into an object of prop arrays
getProp @carpenjk/prop-x/css retrieves a prop value for appropriate breakpoint in css in js solutions
breakpoint @carpenjk/prop-x/css creates media query and tells getProp which breakpoint to use
condition @carpenjk/prop-x/css conditional css for css in js solutions
useBreakpoints @carpenjk/useBreakpoints hook provides breakpoint and window size state

License

Apache License 2.0

Get Started

Install

    npm i @carpenjk/prop-x

Usage

Pass 2d Properties

          <GridContainer
            rowHeight={['auto', '250px']}
            gridWidth={['100%', '80vw']}
            maxGridWidth={['none','1300px']}
          />

Use Properties within a styled-component

The most common use case is for creating responsive css by way of the getProp and breakpoint functions within a styled component tag function.

Theme

A theme object containing a set of breakpoints must be made available via the styled-components ThemeProvider

  import { ThemeProvider } from 'styled-components';
  const theme = {
    breakpoints: {
      0: 0,
      1: 880,
      2: 1050,
      3: 1200,
      4: 1400,
    }
  }

<ThemeProvider theme={theme}>
  <GridContainer
    rowHeight={['auto', '250px']}
    gridWidth={['100%', '80vw']}
    maxGridWidth={['none','1300px']}
  />
</ThemeProvider>
use properties

Properties indexed by breakpoint can be consumed in a styled component definition with the breakpoint and getProp functions provided in prop-x/css. In this example css is being generated with different height, width, and max-width values for different breakpoints.

import styled from 'styled-components';
import { breakpoint, getProp } from 'prop-x/css';

const StyledGrid = styled.div`
  position: relative;
  height: ${getProp('gridHeight')};
  width: ${getProp('gridWidth')};
  max-width: ${getProp('maxGridWidth')};

  ${breakpoint(1)`
    height: ${getProp('gridHeight')};
    width: ${getProp('gridWidth')};
    max-width: ${getProp('maxGridWidth')};

`;

const GridContainer = ({ width, height, maxGridWidth }) => {
  return (
    <StyledGrid width={width} height={height} maxGridWidth={maxGridWidth}}>
      {children}
    </StyledGrid>
  );
};

Full Example with Property manipulation

import styled from 'styled-components';
import { breakpoint, getProp } from 'prop-x/css';
import { unwindProps, parseSizeUnits, windProps } from 'prop-x';

const StyledGrid = styled.div`
  position: relative;
  display: grid;
  grid-auto-flow: row dense;
  grid-template-rows: ${getProp('rowHeight')};
  grid-auto-rows: ${getProp('rowHeight')};
  grid-template-columns: ${getProp('gridTemplateColumns')};
  justify-items: stretch;
  align-items: stretch;
  height: ${getProp('gridHeight')};
  width: ${getProp('gridWidth')};
  max-width: ${getProp('maxGridWidth')};
  overflow: hidden;

  > *:last-child {
    grid-row: unset;
    grid-column: unset;
    max-height: unset;
  }

  & button {
    width: 100%;
    height: 100%;
  }
  & button > img {
    flex: none;
    object-fit: ${getProp('imageFit')};
    height: 100%;
    width: 100%;
    cursor: pointer;
  }
  ${breakpoint(1)`
    display: grid;
    grid-auto-flow: row dense;
    grid-template-rows: ${getProp('rowHeight')};
    grid-auto-rows: ${getProp('rowHeight')};
    grid-template-columns: ${getProp('gridTemplateColumns')};
    justify-items: stretch;
    align-items: stretch;
    height: ${getProp('gridHeight')};
    width: ${getProp('gridWidth')};
    max-width: ${getProp('maxGridWidth')};
    & button > img {
      object-fit: ${getProp('imageFit')};
    }
    > *:last-child {
      grid-row: unset;
      grid-column: unset;
      max-height: unset;
    }
`}
`;

StyledGrid.defaultProps = {
  gridWidth: '100%',
};

function calcProps(props) {
  const {
    columns,
    columnWidth,
    gridHeight,
    gridWidth,
    images,
    maxGridWidth,
    rows,
    rowHeight,
    minColWidth,
    maxColWidth,
    rowWidth,
  } = props;

  function getRowHeight() {
    if (gridHeight) {
      const _gridHeight = parseSizeUnits(gridHeight);
      return `${_gridHeight.value / rows}${_gridHeight.unit}`;
    }
    return rowHeight || '1fr';
  }
  function getColumnWidth() {
    const width = columnWidth || '1fr';
    if (minColWidth && maxColWidth) {
      return `minmax(${minColWidth}, ${maxColWidth})`;
    }
    if (minColWidth) {
      // do something
      return `minmax(${minColWidth}, ${width})`;
    }
    if (maxColWidth) {
      return `minmax(${width}, ?${maxColWidth})`;
    }
    return width;
  }
  const imgCount = images && images.length ? images.length : 0;
  const _rowHeight = getRowHeight();
  const gridTemplateColumns = `repeat(${columns}, ${getColumnWidth()})`;
  return {
    ...props,
    columns,
    columnWidth,
    gridHeight,
    gridWidth,
    gridTemplateColumns,
    images,
    imgCount,
    maxGridWidth,
    rowHeight: _rowHeight,
    rows,
    minColWidth,
    rowWidth,
  };
}

const GridContainer = ({ images, children, ...props }) => {
  const calculatedProps = unwindProps({ ...props, images }).map((propsAry) =>
    calcProps(propsAry)
  );
  return (
    <StyledGrid {...windProps(calculatedProps)}>
      {children}
    </StyledGrid>
  );
};

core

getPropIndex

  const value = getIndexedPropValue(<propValues>, <index>);

This function returns the applicable index to use for the provided property and index The rules for determining which index is applicable are:

  • The lesser of provided index and last index in the prop array
  • Undefined if prop is undefined or not an array
  const index1 = getPropIndex(["a","b","c","d"], 2 ) // 2
  const index2 = getPropIndex(["a","b","c","d"]) 5) // 3
  const index3 = getPropIndex("a", 2) // undefined

getIndexedPropValue

  const i = getIndexedPropValue(<propValues>, <index>);

This function returns applicable prop value for the provided property values and index The rules for determining which value is applicable are:

  • The value at the lesser of the provided index and the last index in the prop Array
  • The prop value if it is not an array
  const index1 = getIndexedPropValue(["a","b","c","d"], 2 ) // "c"
  const index2 = getIndexedPropValue(["a","b","c","d"]) 5) // "d"
  const index3 = getIndexedPropValue("a", 2) // "a"

Array property values must be provided in 2d form (i.e. [[val1, val2,...]]) even if only one array is needed.

unwindProps

  const unwoundProps = unwindProps (<props>, <configObj>);

This function transforms and object containing prop arrays into array of single key value pairs. This is useful in combination with Array.prototype.map to apply logic to each set of values corresponding to an index such as a breakpoint. The transformation follows the following rules:

  • By default, the last value for a given property is used to fill the array such that every property has a value at every index unless specifically left undefined (i.e. [1,,,4])
  • defaultValues (optional) provided in the config object are used to fill missing values in the array. getIndexedPropValue is used to find the applicable default value for each missing property value.
  • useNoValue option provided in the config object overwrites default functionality and fills the array with undefined instead of carrying forward the last key value. Where a default value has been specified, the default value will still be used.
  • noValue option provided in the config object overwrites the useNoValue value. This can be used to provide an empty string or any other value for instead of undefined.
const unwoundProps = unwindProps({prop1: ["val1", "val2", "val3"], prop2: ["val1", "val2", "val3"]});
// unwoundProps = 
// [
//   {prop1: "val1", prop2: "val"},
//   {prop1: "val2", prop2: "val2"},
//   {prop1: "val3", prop2: "val3"}
// ]

//missing prop values are filled in to such that all props have values at each index
const unwoundProps = unwindProps({prop1: ["val1", "val2", "val3"], prop2: ["val1", "val2"]});
// unwoundProps = 
// [
//   {prop1: "val1", prop2: "val1"},
//   {prop1: "val2", prop2: "val2"},
//   {prop1: "val3", prop2: "val2"}
// ]

// single value prop
const unwoundProps = unwindProps({prop1: ["val1", "val2", "val3"], prop2: val1})
// unwoundProps =
// [
//   {prop1: "val1", prop2: "val1"},
//   {prop1: "val2", prop2: "val1"},
//   {prop1: "val3", prop2: "val1"}
// ]

//default values used to fill missing prop2 value at index 2
const unwoundProps = unwindProps(
  {prop1: ["val1", "val2", "val3"], prop2: ["val1", "val2"]},
  {defaultValues: {prop1: ["defVal1", "defVal2", "defVal3"], prop2: ["defVal1", "defVal2", "defVal3"}});
// unwoundProps = 
// [
//   {prop1: "val1", prop2: "val1"},
//   {prop1: "val2", prop2: "val2"},
//   {prop1: "val3", prop2: "defVal3"}
// ]

//useNoValue option
const unwoundProps = unwindProps(
  {prop1: ["val1", "val2", "val3"], prop2: ["val1", "val2"]},
  {
    options: {useNoValue: true}
  }
)
// unwoundProps = 
// [
//   {prop1: "val1", prop2: "val1"},
//   {prop1: "val2", prop2: "val2"},
//   {prop1: "val3", prop2: undefined}
// ]

//useNoValue option with default values
const unwoundProps = unwindProps(
  {prop1: ["val1", "val2", "val3"], prop2: ["val1", "val2"], prop3: "val1"},
  {
    defaultValues: { prop2: ["defVal1", "defVal2", "defVal3"},
    options: {
      useNoValue: true
    }
  }
);
// unwoundProps = 
// [
//   {prop1: "val1", prop2: "val1", prop3: "val1"},
//   {prop1: "val2", prop2: "val2", prop3: undefined},
//   {prop1: "val3", prop2: "defVal3", prop3: undefined}
// ]

//useNoValue and noValue
const unwoundProps = unwindProps(
  {prop1: ["val1", "val2", "val3"], prop2: ["val1", "val2"]},
  {
    options: {
      useNoValue: true,
      noValue: ""
    }
  }
);
// unwoundProps = 
// [
//   {prop1: "val1", prop2: "val1"},
//   {prop1: undefined, prop2: "val2"},
//   {prop1: undefined, prop2: ""}
// ]

windProps

  const windProps = unwindProps (<props>, <configObj>);

This function transforms an array of single value key pair objects into a single object with array values. It is the opposite of unwindProps and is helpful for transforming property arrays that have gone through unwindProps transformation or for when properties are provided in an unwound format. The transformation follows the following rules:

  • Missing values are transformed into the default noValue of ""
  • defaultValues (optional) provided in the config object are used to fill missing values in the array. getIndexedPropValue is used to find the applicable default value for each missing property value.
  • noValue option provided in the config object used to overwrite default noValue
   const windProps = windProps([
       {
         top: '100px',
         left: '50%',
         transform: 'translateX(-50%)',
       },
       {
         top: '50%',
         transform: 'translateY(-50%)'
       }
     ]);

   // woundProps = {
   //   top: ['100px', '50%'],
   //   left: ['50%',''],
   //   transform:  ['translateX(-50%)', 'translateY(-50%)']
   // }

   //use no value set to false can be helpful when it's ok to carryforward the property value for all indexes
   const windProps = windProps([
     {
       width: '100%',
       height: '50vh',
     },
     {
       height: '80vh',
     }
   ],{options: {useNoValue: false}});

   // woundProps = {
   //   width: ['100%'],
   //   height: ['50vh', '80vh'],
   // }

   //default values used to fill any missing values
   const windProps = windProps([
     {
       width: '100px',
       height: '50%',
     },
     {
       height: '50%',
     },
     {
       height:'75%',
     },
     {
       height: '80%',
     }
   ],{defaultValues: {width: ['85px', '95px', '100%']}});

   // woundProps = {
   //   width: ['100px', '95%', '100%', '100%'],
   //   height: ['50%','50%', '75%', '80%'],
   // }

   //noValue  used to change value used in instance of no value
   const windProps = windProps([
     {
       top: '100px',
       left: '50%',
       transform: 'translateX(-50%)',
     },
     {
       top: '50%',
       transform: 'translateY(-50%)'
     }
   ],{options: {noValue: undefined}});

   // woundProps = {
   //   top: ['100px', '50%'],
   //   left: ['50%',undefined],
   //   transform:  ['translateX(-50%)', 'translateY(-50%)']
   // }

inverseProp

  const inversed = inverseProp([true, false, true]);
  //inversed = [false, true, false]

This function toggles boolean values in an array.

parseSizeUnits

  const parsed = parseSizeUnits(vars);

This function parses 1 or many string size/unit value (i.e. '100px') into objects with whole, value, unit keys.

  const parsed = parseSizeUnits('100px');
  // parsed = {whole: '100px', value: 100, unit: 'px'}

  const parsed = parseSizeUnits('auto');
  // parsed = {whole: 'auto', value: undefined, unit: undefined}

  const parsed = parseSizeUnits('100');
  // parsed = {whole: '100', value: 100, unit: undefined}

  const parsed = parseSizeUnits(["100px", "auto", "1", "200%"]);
  // parsed = [
  //   {whole: '100', value: 100, unit: "px"},
  //   {whole: 'auto', value: undefined, unit: undefined},
  //   {whole: '1', value: 1, unit: undefined},
  //   {whole: '200%', value: 200, unit: "%"},
  // ]

parseAndCalc

  const calced = parseAndCalc(vars, ([var1, var2, var3])=> var1+ var2 + var3);

This function performs parses string/unit variables and passes the values into a provided callback function before returning the results as a value/unit string. It is useful for performing calculations on css size/unit values. The unit applied to the result is derived from the first variable containing a unit. This means some care must be used on the units and order of variables. These are some of the considerations when using this function:

  • Variables usually must contain the same unit with % units being an exception
  • Percentage units should always follow the variable with the required result unit
  const calced = parseAndCalc(['100px', '100px', '200px' ], ([var1, var2, var3])=> var1+ var2 + var3);
  // calced = "400px"

  const calced = parseAndCalc(['100px', '5', '200px' ], ([var1, var2, var3])=> var1 * ar2 - var3);
  // calced = "300px"

  // percentages can be used with other units if always known
  const calced = parseAndCalc(['100px', '50%', '200px' ], ([var1, var2, var3])=> var1 * (ar2/100) + var3);
  // calced = "250px"

  // percentages can be used with other units if always known
  const calced = parseAndCalc(['100px', '50%', '200px' ], ([var1, var2, var3])=> var1 * (ar2/100) + var3);
  // calced = "250px"

  // 
  const calced = parseAndCalc(['100px', 'auto', '200px' ], ([var1, var2, var3])=> var2 === 'auto' ? var1 + var2 + 100  : var1  );
  // calced = "400px"

css

breakpoint

${breakpoint(breakpointValue)`
    width: ${getProp('width)};
    ...
  `}

The breakpoint function allows for setting multiple breakpoints within the component style. The breakpoint function calls a tag function in which the styles are inserted. A breakpoint value or array containing starting and ending breakpoint keys must be passed to the function as a index of the breakpoints defined in the theme property. The breakpoint value is used by all of other style functions to find the correct style value from the theme definition.

const theme = {
    breakpoints: {
      0: 0,
      1: 880,
      2: 1050,
      3: 1200,
      4: 1400,
    }
  }

const StyledContainer = styled.div`
  width: 100px;
  ...
  ${breakpoint(1)`
    width: ${getProp('width')};
    ...
  `}
`
const Container = (){
  return (<StyledContainer width={['100px', '200px']} theme={theme}/>)
}

// produces the following css within the styled-component definition
//
// width: 100px;
// ...
// @media (min-width: 880px) {
//    width: 200px;
//    ...
// }

Providing Breakpoints

Breakpoints used by styled-components are passed inside the theme property given to the ThemeContext provider and subsequently passed as a property of the component. Their are 3 supported formats for providing breakpoints:

Breakpoints are supported for 3 formats:

  import { ThemeProvider } from 'styled-components';

  // Object literal with numerical pixel values
  const theme = {
    breakpoints: {
      0: 0,
      1: 880,
      2: 1050,
      3: 1200,
      4: 1400,
    }
  }

// Object literal with strings !!including pixel units
// !!note keys may not be castable to a number or all keys must be castable in order to ensure size rankings
  const theme = {
    breakpoints: {
      zero: "0px",
      sm: "880px",
      md: "1050px",
      lg: "1200px",
      xl: "1400px",
    }
  }

  
// Array with numerical pixel values
const theme = {
  breakpoints: []
    0,
    880,
    1050,
    1200,
    1400,
  ]
}

// Array with numerical pixel !!including pixel units
  const theme = {
    breakpoints: [
      "0px",
      "880px",
      "1050px",
      "1200px",
      "1400px",
    ]
  }

Setting Multiple Breakpoints Simultaneously

Multiple breakpoints can be set simultaneously if they share the same dynamic css properties. This reduces the amount of getProp and breakpoint code.

The following code set's

const theme = {
    breakpoints: {
      0: 0,
      1: 880,
      2: 1050,
      3: 1200,
      4: 1400,
    }
  }

const StyledContainer = styled.div`
  width: 100px;
  ...
  ${breakpoint([1,4])`
    width: ${getProp('width')};
    height: ${getProp('height')}
    ...
  `}
`

//width: 

const Container = (){
  return (<StyledContainer width={['100px', '200px', '300px', '400px', '500px'], height={['10px', '20px', '30px', '40px', '50px']}} theme={theme}/>)
}


// produces the following css within the styled-component definition
//
// width: 100px;
// ...
// @media (min-width: 880px) {
//    width: 200px;
//    height: 20px;
//    ...
// }
// @media (min-width: 1050px) {
//    width: 300px;
//    height: 30px;
//    ...
// }
// @media (min-width: 1200px) {
//    width: 400px;
//    height: 40px;
//    ...
// }
// @media (min-width: 1400px) {
//    width: 500px;
//    height: 50px;
//    ...
// }

condition

${condition('hide')`
    display: none;
    ...
  `}

The condition function allows for conditional css in a styled-components definition based on a truthy/falsy property value.

const StyledContainer = styled.div`
  display: flex;
  ...
  ${condition('hide')`
    display: none;
    ...
  `}
`
const Container = (){
  return (<StyledContainer hide={true} />)
}

// produces the following css within the styled-component definition
//
// display: flex;
// ...
// display: none;

condition can also be used in combination with the breakpoint function.

const theme = {
    breakpoints: {
      0: 0,
      1: 880,
      2: 1050,
      3: 1200,
      4: 1400,
    }
  }

const StyledContainer = styled.div`
  display: flex;
  ${condition('hide')`
    display: 'none';
    ...
  `}
  ...
  ${breakpoint(1)`
    ${condition('hide')`
      display: none;
      ...
    `}
  `}
  
`

const Container = (){
  return (<StyledContainer hide={[false, true]} theme={theme}/>)
}

// produces the following css within the styled-component definition
//
// display: flex;
// ...
// @media (min-width: 880px) {
//    display: none;
//    ...
// }

hooks

useBreakpoints

UseBreakpoints is a separate package so that prop-x doesn't need to have React as a peer dependency

Installation

  npm i @carpenjk/use-breakpoints

import

  import { useBreakpoints } from '@carpenjk/use-breakpoints

Usage

The useBreakpoints hooks provides access to the breakpoints and related functions and calculated values. It is useful for client side only code that needs to evaluate properties indexed by breakpoints.

const breakpoints: {
    0: 0,
    1: 880,
    2: 1050,
    3: 1200,
    4: 1400,
  }

const br = useBreakpoints(breakpoints);

// assume current window.innerWidth = 1000
//br = 
// {
//   br: [0, 880 , 1050, 1200, 1400],
//   upper: 1050,
//   lower: 880,
//   indexofLower: 1,
//   ratio: 0.838, //(1- (upper - lower) / upper)
//   current: 1000,  // current window width
// }

const indexedPropVal = getIndexedPropValue([100, 200, 300], br.indexOfLower);
// indexedPropVal = 200;

Package Sidebar

Install

npm i @carpenjk/prop-x

Weekly Downloads

2

Version

3.0.1

License

Apache-2.0

Unpacked Size

50.4 kB

Total Files

16

Last publish

Collaborators

  • carpenjk