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

feat(Menu): Update to v1 API #382

Merged
merged 10 commits into from
Sep 7, 2016
Merged

feat(Menu): Update to v1 API #382

merged 10 commits into from
Sep 7, 2016

Conversation

layershifter
Copy link
Member

WIP

Update Menu to v1 API

@levithomason
Copy link
Member

Linking #142 for fixing in this PR as well.

@levithomason
Copy link
Member

Linking previous update PR #319.

@codecov-io
Copy link

codecov-io commented Aug 14, 2016

Current coverage is 98.61% (diff: 100%)

Merging #382 into master will increase coverage by <.01%

@@             master       #382   diff @@
==========================================
  Files            99        101     +2   
  Lines          1438       1444     +6   
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
+ Hits           1418       1424     +6   
  Misses           20         20          
  Partials          0          0          

Powered by Codecov. Last update 8eee04c...f376f44


if (isItem) {
const onClick = (e) => {
this.handleItemClick(e, i)
if (isLink) this.handleItemClick(e, i)
if (child.props.onClick) child.props.onClick(e, i)
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll take this part from Accordion component.


Can you review this fragment? It looks not good to me.

Copy link
Member

@levithomason levithomason Aug 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should add the click handler if the user is using subcomponents. We did this initially elsewhere, but there is just no reliable way to determine which children we should add it to.

React.Children can only loop through the top level children. If the user uses their own menu item component, then we have no reliable type or displayName to identify which components are menu items. They may also use a higher order component or decorator on the MenuItem, in which case the menu item is no longer a top level child but a nested child wrapped in the decorator.

Because of this, I think the only reliable way to pass props to children is when using the <Menu items={} /> prop. In this case, we know we can add props to every object in the array. It is the best way I know of to manage the parent-child coupling.

More here: https://facebook.github.io/react/docs/context.html#parent-child-coupling

EDIT

If the user uses subcomponents, they will just have to wire their own click handlers. The same is true for our <Dropdown options={} /> vs <Dropdown><Dropdown.Item /></Dropdown> markup.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a look on basic example of Menu. After click on item it switches active class to this item. I've implemented it like it is in Accordion, but as you say this solution has problem.

I'm correctly understand that you suggest to use Content? But, as you answered earlier:

Context is an advanced and experimental feature. The API is likely to change in future releases.

Copy link
Member

@levithomason levithomason Aug 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm proposing two separate usage patterns.

items={} prop

My suggestion is that this usage will automatically manage the active item state:

<Menu items={[{}, {}, ...]} />

With this usage we can add the onClick handlers our selves and manage the active prop on the MenuItems internally. We can do this easily since we must map over all the items to create the MenuItems.

Subcomponents

This usage would not self manage the active item. The user needs to implement their own onClick handlers and set the active prop:

class MenuExample extends Component {
  handleItemClick = (name) => this.setState({ activeItem: name })

  render() {
    const { activeItem } = this.state
    return (
      <Menu>
        <Menu.Item 
          active={activeItem === 'home'} 
          onClick={this.handleItemClick.bind('home')} 
        />
        <Menu.Item 
          active={activeItem === 'search'} 
          onClick={this.handleItemClick.bind('search')} 
        />
        <Menu.Item 
          active={activeItem === 'settings'} 
          onClick={this.handleItemClick.bind('settings')} 
        />
      </Menu>
    )
  }
}

These ideas can be improved on, but I think we should only auto manage the active state if you use the items prop.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's reasonable, but it produce warning of ESLint 😞

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, our eslint settings don't like bind in an event handler. This can be solved easily like so:

onClick={() => this.handleItemClick('home')}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image
image

It seems that ESLint is more intelligent that you think 😺

@layershifter
Copy link
Member Author

offtop: How I can clean up these commits from PR?

image

@levithomason
Copy link
Member

levithomason commented Aug 17, 2016

Looks like a rebase was done from an out of date master. Update your local master, and the rebase from there. On the fix/menu branch I'd do:

$ git fetch origin master
$ git rebase origin/master

EDIT
If you're remote name is not origin, then use your remote name for the main Stardust repo.

@layershifter
Copy link
Member Author

layershifter commented Aug 17, 2016

Seems, I'll left it for you on merge 😰

image

@levithomason
Copy link
Member

no problem, one moment while i merge and force push...

@levithomason
Copy link
Member

Give it a look and see if it is correct now. I have a copy of the original branch just in case I hosed this one.

@layershifter
Copy link
Member Author

Looks okay, thanks 👍 Waiting for your reply there.

@levithomason levithomason changed the title Update Menu to v1 API Menu: Update to v1 API Aug 18, 2016
@layershifter
Copy link
Member Author

layershifter commented Aug 21, 2016

Menu widely uses Popup in docs. Are there any chance for its implementaion in near future?

@layershifter
Copy link
Member Author

Fixed examples using <iframe>, how I can correctly deal with this?

For finishing Content and States part of docs I need #414.

@levithomason
Copy link
Member

I paused the Form work to finish the augmentation. I hope to ship that today. I can do the popup next, or if you'd like to get a start on it we can tackle that one together.

@levithomason
Copy link
Member

levithomason commented Aug 24, 2016

I've started the Tab component, #430. It is made of a Menu and Segments. I think if MenuItem's accept a name prop, it could help the Tab component. It would also help our onClick binding as we can callback with the Menu.Item name:

export default class Menus extends Component {
  handleItemClick = (e, name) => this.setState({ activeItem: name })

  render() {
    const { activeItem } = this.state

    return (
      <Menu>
        <Menu.Item
          name='browse'
          active={activeItem === 'browse'}
          onClick={this.handleItemClick}
        >
          Browse
        </Menu.Item>

        <Menu.Item
          name='submit'
          active={activeItem === 'submit'}
          onClick={this.handleItemClick}
        >
          Submit
        </Menu.Item>

        <Menu.Menu position='right'>
          <Menu.Item
            name='signup'
            active={activeItem === 'signup'}
            onClick={this.handleItemClick}
          >
            Sign Up
          </Menu.Item>

          <Menu.Item
            name='help'
            active={activeItem === 'help'}
            onClick={this.handleItemClick}
          >
            Help
          </Menu.Item>
        </Menu.Menu>
      </Menu>
    )
  }
}

It can be used as shorthand props to generate the menu item text. Same example as above but with shorthand MenuItem child text:

export default class Menus extends Component {
  handleItemClick = (e, name) => this.setState({ activeItem: name })

  render() {
    const { activeItem } = this.state

    return (
      <Menu>
        <Menu.Item
          name='browse'
          active={activeItem === 'browse'}
          onClick={this.handleItemClick}
        />
        <Menu.Item
          name='submit'
          active={activeItem === 'submit'}
          onClick={this.handleItemClick}
        />
        <Menu.Menu position='right'>
          <Menu.Item
            name='signup'
            active={activeItem === 'signup'}
            onClick={this.handleItemClick}
          />
          <Menu.Item
            name='help'
            active={activeItem === 'help'}
            onClick={this.handleItemClick}
          /></Menu.Menu>
      </Menu>
    )
  }
}

Lastly, it works well with a shorthand items array where the objects are spread on each item. It could also be used to propagate the onClick to the Menu when using an array of items.

This would be an AutoControlledComponent now. The active item can be self managed. Optionally, a user could override the active item with an activeItemName prop:

const items = [
  { name: 'browse' },
  { name: 'submit' },
  { name: 'signup' },
  { name: 'help' },
]

export default class Menus extends Component {
  handleClick = (e, name) => {
    // do something with the clicked item
  }

  render() {
    return (
      <Menu onClick={this.handleClick} items={items} />
    )
  }
}

@layershifter
Copy link
Member Author

Looks pretty, take it work.

@layershifter
Copy link
Member Author

Added. I'm having problems with docs build on Windows, on master branch, too. And idea?

image

@levithomason
Copy link
Member

levithomason commented Aug 24, 2016

Gah, yep! There are hardcoded /s in the paths. Windows uses \s :/ I don't have a quick solve for this off the top of my head.

The docgenInfo.json is generated in node during the build. The ComponentDoc components then import and parse the json in browser. So, the browser code needs to know to use / instead of \.

One idea might be setting a global platform dependent __PATH_SEP__ during the build.

// config.js
compiler_globals: {
  __PATH_SEP__: JSON.stringify(path.sep),
}

https://nodejs.org/api/path.html#path_path_sep

Then we could use __PATH_SEP__ in the doc build when building paths. If you like, please open another issue and we'll work on this there.

@levithomason
Copy link
Member

Merged #435, give that a shot!

@levithomason
Copy link
Member

In addition to calling back with the menu item name onClick, let's also call back with the item index:

handleClick(e, { name, index}) {
  //... 
} 

<Menu.Item onClick={this.handleClick} />

This will help other components like Tab and Accordion use the Menu to control their activeIndex.

@layershifter
Copy link
Member Author

How I can get index when user passes children directly?

<Menu>
  <Menu.Item>A</Menu.Item>
  <Menu.Item>B</Menu.Item>
</Menu>

@levithomason
Copy link
Member

levithomason commented Sep 2, 2016

I've updated this to the latest master. I also fixed the existing tests and added missing sub component tests. It should be all set for continuing with the docs.

Since I squashed and force pushed to clean it up a bit, you'll probably want to blow away your local branch in favor of this one.

I did not make any major updates to the functionality. So, the Menu itself is still in progress.

@layershifter
Copy link
Member Author

Thanks much, I'll think I'll finish docs on this weekend.

return <div>{menus}</div>
}

export default ColoredInvertedMenus
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@levithomason what you think about this?

Copy link
Member

@levithomason levithomason Sep 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop is perfectly acceptable, but let's not show use of the private _meta prop. I don't want users knowing/using that. I think putting it in the docs will encourage use of it. Instead, let's just list the colors in an array explicitly.

@layershifter
Copy link
Member Author

Docs

Unbelievable, but it is finished ✌️ We need review for them.

Tests

Tests will be finished tomorrow. And final review will there soon 👍

Shorthands

Discuss?

Menu.Menu

I think, we don't need provide items shorthand there because it's sub menu componen while shorthands cover only simplest cases.

Menu

  • Autocontroll of index prop is pretty idea, but we need provide item handler. What you think about this?
const items [
  {name: 'Open'},
  {name: 'Save'},
]

<Menu items={items} onItemClick={handler} />
const items [
  {name: 'Open', onClick: onOpenClick},
  {active: true, name: 'Save', onClick: onSaveClick},
]

<Menu items={items} />
  • Skip Menu.Header and Menu.Menu for less complexity.

@levithomason
Copy link
Member

Docs - Wow, great work! Will get a review soon as I can.
Tests - LMK if/when these are done and I'll review as well.
Shorthands - I agree completely on Menu.Menu and the Menu items, onItemClick handler, and skipping header/sub menu. Anything more complicated can use sub components.

@layershifter
Copy link
Member Author

Tests

There are here 🚢

Shorthands

I'll implement and add docs with tests soon 😪

@levithomason
Copy link
Member

Will review this today, thanks for the massive amount of work here!

@levithomason
Copy link
Member

Heads up, doing a final review and some minor tweaks. Hope to merge this today.

@levithomason
Copy link
Member

Latest updates:

Start Case Name prop

Updated <Menu.Item name='fooBar' /> to Start Case the name (i.e. "Foo Bar") when used as content. Added a test for this and updated the examples as well.

Dropdown Item Updated

Completed TODO for Dropdown header usage. Also, reversed the augmentation from <Menu.Item as={Dropdown} /> to <Dropdown as={Menu.Item} /> as it seemed the Dropdown sub components made more sense this way. Finally, used icon prop shorthand for Dropdown.Items.

Colored Menus

Mapped over an array of colors to make examples a little more concise. More importantly, to not expose the _meta prop to users in the docs. I am sure it will lead to wider spread use of the _meta prop which is private.

Misc

I made some trivial updates to the docs in terms of verbiage and/or organization.

@levithomason levithomason merged commit 378afd6 into master Sep 7, 2016
@levithomason levithomason deleted the fix/menu branch September 7, 2016 02:35
@levithomason
Copy link
Member

🎉 This is great, thanks a million. We can do shorthand separately since this PR was so massive.

@levithomason
Copy link
Member

Released in stardust@0.42.0

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

Successfully merging this pull request may close these issues.

3 participants