Button

Button components provides some interesting food for thought. They have very little component code -- no state, and no other dynamic lifecycle operations. But they do have a lot of options for constructing styles. Is it an outlined button? Disabled? Large or small? Is it being hovered? Focused?

In particular, Buttons illustrate how we might use the meta section of a component style object. In order to let the user override all of the visual states, we have to put all properties for all possible states into the component meta.

The following sample is intentionally incomplete. It shows only the static style object and a portion of the dynamicStyle() function. The function is essentially a long list of if-then statements, taking the props and creating a set of instance styles based on the relevant sub-objects in the meta.

The component code itself is trivial, just like the ValueLabel component above.

styles.js

import _ from 'lodash';

// static styles
export default {
  display: 'inline-block',
  padding: '4px 12px',
  border: '1px solid transparent',
  borderRadius: '2px',
  color:      '#fff',
  fontSize:   '14px',
  textAlign:  'center',

  ':focus': {outline: 'thin dotted'},

  // the button component has many props-dependent styles. each
  // scenario is captured in the meta here, which allows users
  // to override everything
  //
  meta: {
    // button sizes. the keys here ("small" etc) are used as props: <Button size="small">
    // the user can add more sizes in their theme
    size: {
      small: {
        fontSize: '12px',
        padding:  '3px 9px'
      },
      large: {
        fontSize: '16px',
        padding:  '6px 14px'
      }
    },
    // disabled state
    disabled: {
      color:           'disabled',  // color key from global meta
      backgroundColor: '#fff',
      borderColor:     'disabled',
      cursor:          'default',
      pointerEvents:   'none'
    },
    // outline button
    outline: {
      backgroundColor: '#fff',
      color: 'primary'   // another color key
    },
    hoverStates: {
      byType: {
        primary: {backgroundColor: 'yellow'},
        warning: {backgroundColor: 'orange'}
      },
      disabled: {backgroundColor: '#fff'},
      outline:  {backgroundColor: '#fff'}
    }
  }
};

// this function is incomplete, but shows how to work
// with a richly-specified meta
//
export function dynamicStyle({props, componentTheme, globalMeta}) {
  let buttonType     = props.type || 'primary',
      instanceStyles = {},
      {disabled, outline, hovering /* etc */} = props;

  if (props.disabled) {
    instanceStyles = _.merge({}, instanceStyles, componentTheme.meta.disabled);
  }
  else {
    // apply the correct styles for the hover state,
    // based on the button type
    instanceStyles[':hover'] = componentTheme.meta.hoverStates.byType[buttonType];
  }

  /* ... etc ... */

  return _.merge(
    {}, 
    componentTheme,

    // merge in the size-specific props
    componentMeta.size[props.size],

    // and this instance has its own styles, crafted above
    instanceStyles
  );
}

All of the significant logic for the button is in the styling function. The component itself is paper-thin.

button.js

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import staticStyle, {dynamicStyle} from './styles';
import {Styled} from 'styletron-themer';

export default class Button extends Component {
  static propTypes = {
    type:     PropTypes.oneOf(['primary', 'default', 'warning']),
    size:     PropTypes.string,
    disabled: PropTypes.bool,
    outline:  PropTypes.bool
  };

  render() {
    return (
      <Styled
        themeName = 'Button'
        staticStyle = {staticStyle}
        dynamicStyle = {dynamicStyle}
        {...this.props}
      >
        {(className, props) => {
          // strip out component props so we can pass the remaining props
          // down to the HTML element
          const {type, size, disabled, outline, ...otherProps} = props;
          return (
            <button {...otherProps} className={className}>
              {this.props.children}
            </button>
          );
        }}
      </Styled>
    );
  }
}

results matching ""

    No results matching ""