njmyers-component-library

0.1.24 • Public • Published

Component Library

This is a collection of useful and maybe useless React components. In general components will follow a few rules in order to increase usability. These rules in paricular are applied in how the components will use inline styles

Global Usage notes

Inline Styling

  1. All components accept className prop.
  2. For a shallow merge of styles, use the style prop on a component.
  3. To escape all inline styles, pass an empty object to replaceStyle prop.
  4. For a custom reusable component, redefine the defaultProps with a new replaceStyle object. Don't forget to merge all of the other defaultProps!
<Component className="class" />
// behaves as expected - inline styles take precendence over rules applied to className
<Component style={{ color: 'red' }} />
// shallowly merges style with the default inline styles of the component
<Component replaceStyle={{}} />
// all inline styles are removed

Animation Props

Status Switch

Wrap your React Components in a StatusSwitch to conditionally render based on status props.

import React from 'react';

class Stateful extends React.Component {
  state = {
    status: 'initial',
  };

  toLoading = () => this.setState({ status: 'loading' });

  toError = () => this.setState({ status: 'error' });

  render() {
    return (
      <StatusSwitch status={this.state.status}>
        <div>
          <p>Some content</p>
        </div>
      </StatusSwitch>
    );
  }
}

Also you can pass in your own components as render props loading or error so you can customize the error and loading renders

class Stateful extends React.Component {
  render() {
    return (
      <StatusSwitch
        status={this.state.status}
        error={(props) => <p>error</p>}
        loading={(props) => <p>loading</p>}
      >
        <div>
          <p>Some content</p>
        </div>
      </StatusSwitch>
    );
  }
}

Form Components

A selection of React form components. Meant to assist in adding labels and making accessibility automatic. Also helps in generating css classes and modifiers.

Input

import React from 'react';

class Form extends Component {
  state = {
    age: 0
  }

  this.onChange(event) => {
    this.setState({
      [event.target.name]: event.target.value
    })
  }

  render() {
    <form>
      <Input
        value={this.state.value}
        onChange={this.onChange}
        className="formInput"
        name="age"
        label="Your Age"
      />
    </form>;
  }
}

SemiSticky

SemiSticky is a position aware component that animates in and out on scroll positions. It is inspired by the CSS property position: sticky but allows for usage in a much wider variety of situations. SemiSticky uses the AnimationProps for determining it's style. Please see AnimationProps for more information about usage.

SemiSticky uses a single prop to determine the scroll position of it's on state. That prop is called top and it signifies the amount of pixels from the top of the page that the component should apply it's onState styles. Anything greater then top will apply the onState styles and anything less then top will apply the offState styles.

Usage

A common usage pattern is for fixed position headers and footers that will show themselves based on a user's scroll position.

import React from 'react';

class Main extends React.Component {
  render() {
    return (
      <SemiSticky top={400}>
        <Header />
      </SemiSticky>
    );
  }
}

In the above example, the Header component will hide from view when the user scrolls down 400 pixels. Sounds simple enough but to implement yourself takes many lines of code! Feel free to add your own onState and offState styles and SemiSticky will automatically generate CSS transitions.

If you would like to shallowly merge styles, use the style prop. If you want to replace the default inline styles, use the replaceStyle prop. You can also apply className prop but keep in mind that all of the inline styles will take precedence.

Props

Prop Name Type Required Default Value Description
children ReactNode false - react children (your component)
className string false - className applied to the container element
offState {} false {
transform: 'translateY(-100px)',
}
css inline styles applied to the off state
onState {} false {
transform: 'translateY(0)',
}
css inline styles applied to the on state
replaceStyle {} false {
position: 'fixed',
background: 'rgba(0, 0, 0, 0.8)',
width: '100%',
top: 0,
left: 0,
}
completely replace all styles
sizes {
isSemiStickyActive: boolean,
}
true - inherited sizing info
style {} false - shallowly merge styles
top number false 200 distance from the document top to engage the on state styles
transitionSpeed number false 0.25 the speed of the transition
transitionTiming | 'ease'
| 'linear'
| 'ease-in'
| 'ease-out'
| 'ease-in-out'
| 'step-start'
| 'step-end'
false 'ease' the transition timing function

LazyImage

A component for lazily loading images

Usage

<LazyImage src={src} placeholder={placeholder} />

Props

Prop Name Type Required Default Value Description
alt string false - pass-thru alt tag for image
baseStyles {} false {
margin: 0,
padding: 0,
width: '100%',
height: 'auto',
backfaceVisibility: 'inherit',
}
base styles applied to all elements
className string true - class name applied to the components in BEM style
containerStyle {} false {
position: 'relative',
overflow: 'hidden',
// fix for image element whitespace
lineHeight: 0,
}
shallow merge of styles applied to the container
imageStyle {} false {} shallow merge of styles applied to the highres
name string false - name attribute for onclick events
onClick Function false - pass-thru onclick function
placeholder string true - placeholder image src
placeholderStyle {} false {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
objectFit: 'contain',
transition: '0.25s opacity',
}
shallow merge of styles applied to the placeholder
src string true - high-res image src
title string false - pass-thru title tag for image

Modal

Modal creates ... a modal. It uses ReactDOM.createPortal so it renders your element outside of the HTML tree. However it is still controlled by whichever react parent component it is used in. By default Modal renders to the id 'modal-root'. Please be sure to add 'modal-root' to your HTML file or else nothing will be rendered by this component.

The default export from Modal is the styled modal. It contains background, transitions and all sorts of fun inline css. If you would like to use only the portal creation you can find it as a named export UnstyledModal.

import { Modal, UnstyledModal } from 'njmyers-component-library';

Usage

Simply add your children and use the parent component to control the status as 'on' or 'off'.

import React from 'react';
import { Modal } from 'njmyers-component-library';

class ModalParent extends React.Component {
  state = {
    status: 'on'
  }

  turnOffModal = () => {
    this.setState({
      status: 'off'
    })
  }

  componentDidMount() {
    setTimeout(this.turnOffModal, 4000)
  }

  render() {
    return (
      <Modal status={this.state.status} />
        <p>Annoying advertisement</p>
      </Modal>
    )
  }
}

In the above example, the 'annoying advertisement' will render itself in aside under the 'modal-root' id for 4 seconds and then will animate to its offState styles. When ModalParent unmounts the modal component will be removed from 'modal-root'.

Props

Prop Name Type Required Default Value Description
children ReactNode false - react children (your component)
className string false - className applied to the container element
modalRoot string false 'modal-root' div id where to render the react portal
offState {} false {
opacity: 0,
}
css inline styles applied to the off state
onState {} false {
opacity: 1,
}
css inline styles applied to the on state
replaceStyle {} false {
background: 'rgba(255, 255, 255, 0.8)',
position: 'fixed',
width: '100%',
height: '100%',
top: 0,
left: 0,
bottom: 0,
right: 0,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}
completely replace all styles
status 'on' | 'off' false 'off' display status of the styled modal
style {} false {} shallowly merge styles
transitionSpeed number false 0.25 the speed of the transition
transitionTiming | 'ease'
| 'linear'
| 'ease-in'
| 'ease-out'
| 'ease-in-out'
| 'step-start'
| 'step-end'
false 'ease' the transition timing function
zIndexOff number false -20 z-index of the modal when off
zIndexOn number false 1 z-index of the modal when on

BEM

Background (Frustration)

I've read many articles about using BEM syntax. As I understand it the main points are as follows.

  1. BEM syntax reduces css specificity (GOOD)
  2. BEM syntax can be implemented easily with preprocessors (GOOD)
  3. BEM syntax makes your HTML or Components look ugly (BAD!)

If you stick to BEM syntax then you end up with a React Component like this

<p className="block-element block_element-modifier block_element-otherModifier">
  Text
</p>

And sass or scss that looks like this

.block
  &_element
    &-modifier
    &-otherModifier

The sass looks like great IMHO but the markup looks terrible. How you want to write your sass or scss is a topic for another conversation. All of the aforementioned articles then proceed to explain how they have made adjustments on the BEM syntax so that it is easier to read. Usually this means creating shorter classNames and using tricks.

TODO ADD EXAMPLE

Now of course this is a good thing as we want our source code to be cleaner and easier to read. However it comes at the cost of possible reducing the specificity of your compilied css code. Why do we have to make a sacrifice?

Solution

Now we have a brand new component for writing BEM classNames to your components. We hopefully get the best of both worlds by being able to using the full power of BEM in terms of specificity while using a much cleaner syntax in React. Behold the power of <BEM>

class Menu extends React.Component {
  static defaultProps = {
    menuItems: [
      {
        text: 'Link1',
        id: 1,
      },
      {
        text: 'Link2',
        id: 2,
      },
      {
        text: 'Link3',
        id: 3,
      },
    ],
  };

  state = {
    active: 1,
  };

  render() {
    return (
      <BEM block="menu">
        <nav>
          {this.props.menuItems.map((item) => (
            <div element="buttonContainer">
              <button
                element="button"
                modifiers={this.state.active === item.id ? 'active' : ''}
              >
                {item.text}
              </button>
            </div>
          ))}
        </nav>
      </BEM>
    );
  }
}

Now what will this render? Something like this of course depending on which data you pass in

<nav class="menu">
  <div element="buttonContainer" class="menu_buttonContainer">
    <button element="button" modifiers="active" class="menu_button menu_button-active">Link1</button>
  </div
  ><div element="buttonContainer" class="menu_buttonContainer">
    <button element="button" modifiers="" class="menu_button">Link2</button>
  </div>
  <div element="buttonContainer" class="menu_buttonContainer">
    <button element="button" modifiers="" class="menu_button">Link3</button>
  </div>
</nav>

Great... not only do we have the wordy classes all properly applied we also have searchable property strings associated with each element. Yay success

How would it look manually applying classNames to this component?

class Menu extends React.Component {
  // ... data here
  render() {
    return (
      <nav className="menu">
        {this.props.menuItems.map((item) => (
          <div className="menu_buttonContainer">
            <button
              className={`menu_button ${
                this.state.active === item.id ? 'menu_Button-active' : ''
              }`}
            >
              {item.text}
            </button>
          </div>
        ))}
      </nav>
    );
  }
}

Not too bad. But what if we want to apply modifiers then it gets messy really quickly

class Menu extends React.Component {
  // ... data here
  render() {
    return (
      <nav className="menu menu-right">
        {this.props.menuItems.map((item) => (
          <div className="menu_buttonContainer menu_buttonContainer-square">
            <button
              className={`menu_button ${
                this.state.active === item.id ? 'menu_button-active' : ''
              } ${this.state.animate === item.id ? 'menu_button-animate' : ''}`}
            >
              {item.text}
            </button>
          </div>
        ))}
      </nav>
    );
  }
}

Ughhh gross. You might try to clean it up with a helper function.

class Menu extends React.Component {
  getButtonClass = (id) => {
    return `menu_button ${
      this.state.active === id ? 'menu_button-active' : ''
    } ${this.state.animate === id ? 'menu_button-animate' : ''}`;
  };
  // ... data here
  render() {
    return (
      <nav className="menu menu-right">
        {this.props.menuItems.map((item) => (
          <div className="menu_buttonContainer menu_buttonContainer-square">
            <button className={this.getButtonClass(item.id)}>
              {item.text}
            </button>
          </div>
        ))}
      </nav>
    );
  }
}

Well why not just use <BEM> ?

class Menu extends React.Component {
  // same data from before

  state = {
    active: 1,
    animate: 1,
  };

  getModifiers = (id) => {
    return Object.entries(this.state)
      .filter((entry) => entry[1] === id)
      .map((entry) => entry[0]);
  };

  render() {
    return (
      <BEM block="menu">
        <nav modifier="right">
          {this.props.menuItems.map((item) => (
            <div element="buttonContainer" modifiers="square">
              <button element="button" modifiers={this.getModifiers(item.id)}>
                {item.text}
              </button>
            </div>
          ))}
        </nav>
      </BEM>
    );
  }
}

And that will produce the following html

<nav modifier="right" class="menu">
    <div element="buttonContainer" modifiers="square" class="menu_buttonContainer menu_buttonContainer-square">
        <button element="button" modifiers="active,animate" class="menu_button menu_button-active menu_button-animate">Link1</button>
    </div>
    <div element="buttonContainer" modifiers="square" class="menu_buttonContainer menu_buttonContainer-square">
        <button element="button" modifiers="" class="menu_button">Link2</button>
    </div>
    <div element="buttonContainer" modifiers="square" class="menu_buttonContainer menu_buttonContainer-square">
        <button element="button" modifiers="" class="menu_button">Link3</button>
    </div>
</nav>

And now you can write your sass the same way

.menu
  &_buttonContainer
    &-square
  &_button
    &-animate
    &-active

Anyways you may think differently but this syntax makes alot of sense for me!

Notes

BEM will only add classes to dom elements and not to Components. In the below example the p entities will receive classNames "block_element" and "block_otherElement" repectively. No classNames will be added to the <SpecialComponent />. This is a design feature not an error. We would assume that you will use another instance of BEM inside your special component as it is special enough to warrant it's own component! This frees you up for using the create rich environment of composing components together.

render() {
  return (
    <BEM block="block">
      <p element="element">text</p>
      <p element="otherElement">text</p>
      <SpecialComponent />
    </BEM>
  )
}

All classNames are added to previously existing classNames. This means you can still use things like font awesome and existing classNames as BEM builds on top of already existing features. The i element below would have the className of "fa block_element"

render() {
  return (
    <BEM block="block">
      <i className="fa" element="element">text</i>
    </BEM>
  )
}

Readme

Keywords

none

Package Sidebar

Install

npm i njmyers-component-library

Weekly Downloads

25

Version

0.1.24

License

none

Unpacked Size

595 kB

Total Files

135

Last publish

Collaborators

  • njmyers