Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React example complexity #393

Closed
ianchanning opened this issue Mar 11, 2017 · 9 comments
Closed

React example complexity #393

ianchanning opened this issue Mar 11, 2017 · 9 comments

Comments

@ianchanning
Copy link

This is a feature request for the react examples I guess. I'm learning React and I've just started to try and implement MDC-Web.

I'm using MDC-Web v0.6.0 with React v15.4.1 (Ubuntu 16.04 Vagrant box running on Fedora 25 + Firefox)

I've installed and run the react example. It works perfectly straight out of the box, which is awesome. Plus it gives a fully working state handling model using PureComponents and Immutable as part of the example which is also great.

What concerns me is that Checkbox.js is ~200 lines long (excluding the licence comment).

If it is 200 lines to implement a checkbox, how much coding does it take to implement a form with error handling? Or beyond that an application.

Perhaps I'm just penalising the example because it gives a good level of detail of handling state, which most other examples don't do.

So my questions would be:

  1. Is the level of complexity an issue with MCD-Web or React or MCD-Web + React
  2. Is the level of complexity going to grow exponentially as you add components
@kminehart
Copy link

The problem you probably see is that the the somewhat complex components for Material Components Web implement a "Foundation" prototype, which essentially defines functions for binding events, adding and removing classes, updating styles, etc.

This has to be done to create components that can be used in any framework. The react example you see, the reason it's so extensive is because it implements the Foundation prototype to work with React.

For example, you see that it implements the "addClass" function?

addClass: className => this.setState(prevState => ({
  classes: prevState.classes.add(className),        
})),                                                

Managing the state is sort of a "react" thing. In other frameworks, you might be setting a variable. In vanilla JS, you'd just be appending the class to element.classes.

You'll often see that by making something "extensible", you run into a situation where you expose a lot of functionality, and it becomes overwhelming. I see it with a lot of javascript libraries.

To answer your second question, you really only implement these "mdc" components once. Afterwards, your components will start to look like normal react components. So no, it doesn't increase in complexity. It's more of an initial spike, and then a downward slope.

@ghost
Copy link

ghost commented Mar 15, 2017

I guess really, what this is crying out for is someone to take the initiative and translate these into react components and open-source them. 🙄

@ianchanning
Copy link
Author

@kminehart Thanks, that's an excellent explanation! I understand better now. These are the guts of the elements that would typically be wrapped up in an npm library.

@kminehart
Copy link

@burkhardr honestly it wouldn't take a whole lot of effort. In one of my projects for my employer I have most of the components finished in React.

When I've finished them all I'll clean them up and publish them. My only worry is that as material-components-web grows, I'll have to keep up with it.

@ghost
Copy link

ghost commented Mar 15, 2017

@kminehart Keep us in the loop if your employer lets you open-source. If you're worried about keeping up with the underlying library, I'm sure the core maintainers and the wider community will be happy to help.

@kminehart
Copy link

kminehart commented Apr 3, 2017

It doesn't look like it's going to happen as we're switching focus from MDC for the time being.

I'll provide some snippets though, it's honestly very similar for other mdc components. The hardest one for me was Menu.

I never got around to implementing the focus, nor do I quite have the time to do that either. :(

import React, {PropTypes} from 'react';
import {MDCSimpleMenu, MDCSimpleMenuFoundation} from '@material/menu';
import MenuItem from '../MenuItem';
import {Set as ImmutableSet, Map as ImmutableMap} from 'immutable';
import {getTransformPropertyName} from '@material/menu/util';

const OPENFROMS = {
  "top-left": "mdc-simple-menu--open-from-top-left",
  "top-right": "mdc-simple-menu--open-from-top-right",
  "bottom-left": "mdc-simple-menu--open-from-bottom-left",
  "bottom-right": "mdc-simple-menu--open-from-bottom-right",
}
class Menu extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      classes: new ImmutableSet(),
    };

    this.foundation = new MDCSimpleMenuFoundation({
      addClass: (className) => {
        this.setState(prevState => ({
          classes: prevState.classes.add(className),
        }));
      },
      removeClass: (className) => {
        this.setState(prevState => ({
          classes: prevState.classes.remove(className),
        }));
      },
      hasClass: (className) => {
        if(!this.refs.menu) return;
        return this.refs.menu.classList.contains(className);
      },
      hasNecessaryDom: () => {
        return !!this.refs.menu;
      },
      getInnerDimensions: () => {
        return {
          width: this.refs.menuItemsContainer.offsetWidth,
          height: this.refs.menuItemsContainer.offsetHeight,
        };
      },
      hasAnchor: () => {
        return this.refs.menu.parentElement && this.refs.menu.parentElement.classList.contains('mdc-menu-anchor');
      },
      getAnchorDimensions: () => {
        return this.refs.menu.parentElement.getBoundingClientRect()
      },
      getWindowDimensions: () => {
         return {width: window.innerWidth, height: window.innerHeight};
      },
      setScale: (x, y) => {
        if(!this.refs.menu) return;
        this.refs.menu.style[getTransformPropertyName(window)] = `scale(${x}, ${y})`;
      },
      setInnerScale: (x, y) => {
        if(!this.refs.menuItemsContainer) return;
        this.refs.menuItemsContainer.style[getTransformPropertyName(window)] = `scale(${x}, ${y})`;
      },
      getNumberOfItems: () => {
        if(!this.props.items) return;
        return this.props.items.length;
      },
      registerInteractionHandler: (type, handler) => {
        if(!this.refs.menu) return;
        this.refs.menu.addEventListener(type, handler);
      },
      deregisterInteractionHandler: (type, handler) => {
        if(!this.refs.menu) return;
        this.refs.menu.removeEventListener(type, handler);
      },
      registerDocumentClickHandler: (handler) => {
        document.addEventListener('click', handler);
      },
      deregisterDocumentClickHandler: (handler) => {
        document.removeEventListener('click', handler);
      },
      setTransitionDelayForItemAtIndex: (index, value) => {
        return;
      },
      getIndexForEventTarget: (target) => {
        if(!this.props.items) return;
        return this.props.items.indexOf(target);
      },
      notifySelected: (evtData) => {
        let evt = new CustomEvent('MDCSimpleMenu:selected', {
          detail: {
            index: evtData.index,
            item: this.items[evtData.index],
          },
        });
        this.refs.menu.dispatchEvent(evt);
      },
      notifyCancel: () => {
        let evt = new CustomEvent('MDCSimpleMenu:cancel');
        this.refs.menu.dispatchEvent(evt);
        this.props.onClose();
      },
      saveFocus: () => {
      },
      restoreFocus: () => {
      },
      isFocused: () => {
      },
      focus: () => {
      },
      getFocusedItemIndex: () => {
      },
      focusItemAtIndex: (index) => {
      },
      isRtl: () => {
      },
      setTransformOrigin: (origin) => {
        this.refs.menu.style[`${getTransformPropertyName(window)}-origin`] = origin;
      },
      setPosition: (position) => {
        if(!this.refs.menu) return;
        this.refs.menu.style.left = 'left' in position ? position.left : null;
        this.refs.menu.style.right = 'right' in position ? position.right : null;
        this.refs.menu.style.top = 'top' in position ? position.top : null;
        this.refs.menu.style.bottom = 'bottom' in position ? position.bottom : null;
      },
      getAccurateTime: () => {
        return window.performance.now();
      },
    });
  }

  componentDidUpdate() {
    if(this.props.open && !this.foundation.isOpen()) this.foundation.open();
  }
  componentDidMount() {
    this.foundation.init();
  }
  componentWillUnmount() {
    this.foundation.destroy();
  }
  render() {
    return(
      <div className={`mdc-simple-menu ${OPENFROMS[this.props.openFrom]} ${this.props.className || ''} ${this.state.classes.toJS().join(' ')}`}
           ref="menu">
        <ul
          className='mdc-simple-menu__items mdc-list'
          role='menu'
          ref='menuItemsContainer'>
            {this.props.items.map((item, key) =>
              <MenuItem key={key} label={item.label} icon={item.icon} onClick={item.onClick} />
            )}
        </ul>
      </div>
    )
  }
}

Menu.defaultProps = {
  openFrom: "top-right",
  items: [],
  onClose: () => {},
  open: false,
};

Menu.propTypes = {
  openFrom: PropTypes.oneOf([
    "top-right",
    "top-left",
    "bottom-right",
    "bottom-left",
  ]),
  items: PropTypes.array.isRequired,
  onClose: PropTypes.func,
  open: PropTypes.bool.isRequired,
};

export default Menu;

MenuItem

import React, {PropTypes} from 'react';
import {ripple} from 'material-components-web';

class MenuItem extends React.Component {
  constructor(props) {
    super(props);
  }

  componentDidMount() {
  }

  render() {
    return(
      <li className={`mdc-list-item ${this.props.className || ''}`}
        role='menuitem'
        onClick={this.props.onClick}>
        {
          this.props.icon
          ?
          <i className="mdc-list-item__start-detail material-icons" aria-hidden="true">{this.props.icon}</i>
          :
          null
        }
        {this.props.label}
      </li>
    )
  }
}

MenuItem.defaultProps = {
  onClick: () => {},
};

MenuItem.propTypes = {
  label: PropTypes.string.isRequired,
  icon: PropTypes.string,
};

export default MenuItem;

Obviously you're free to use, distribute, and modify code however you want; it comes without warranty.

@jeffreysnell
Copy link

Found this for anyone still looking for react wrappers around these components.
https://github.com/kradio3/react-mdc-web

@carlitux
Copy link

Hi, right now I am working in a wrapper https://github.com/carlitux/material-toolbox if you can take a look would be great. I hope soon I will publish to npm.

@garrett-thompson
Copy link

I suggest https://github.com/prateekbh/preact-material-components

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants