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

DropdownButton, SplitButton, DropdownMenu, MenuItem Rewrite #1096

Merged
merged 1 commit into from
Aug 21, 2015

Conversation

mtscout6
Copy link
Member

Major part of #526

Adds:

  • Keyboard Navigation
  • Aria properties for Assistive Technology
  • Option to use custom menu with any of the dropdown variations
  • NavDropdown component (Similar to DropdownButton but specifically for use within Nav components.
  • DropdownToggle Component which can be used as an alternative to the DropdownButton title prop. Can either be directly imported or used as a static property of the DropdownButton with DropdownButton.Toggle. (Useful should you want to use glyphicons or custom html within the toggle that's not a simple string.)
  • SplitToggle Similar to the DropdownToggle but targeted at the SplitButton's toggle.

Changed:

Removed:

  • DropdownStateMixin - Everything is using ES6 class syntax so no more mixin usage

Work Remaining: (If anyone would like to help here I'd greatly appreciate it)

Potential Problems that may still exist:

  • Nav component onSelect handler is passed to NavDropdown the signatures don't match so we will either need to switch over Nav, NavBar and NavItem to the new signature or add some kind of temporary adaptor till we can address them. I'm not sure which option will involve the least amount of work.

Minor inconveniences to figure out: (Should not prohibit this PR)

I'm hoping that that's everything needed to put a bow tie on this.

@jquense
Copy link
Member

jquense commented Jul 31, 2015

woohoo! gonna dig into this a bit tomorrow, I have a few cases at work where I can try it out.

@mtscout6
Copy link
Member Author

mtscout6 commented Aug 1, 2015

I'm not sure which option will involve the least amount of work.
-mtscout6 (myself earlier)

Digging deeper this may be complicated pending the outcome of #419 (comment)

@taion
Copy link
Member

taion commented Aug 2, 2015

👏

}

handleDocumentClick(event) {
let inTree = domUtils.isNodeInTree(event.target, React.findDOMNode(this));
Copy link
Member

Choose a reason for hiding this comment

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

I think this is probably now a duplicate of domUtils.contains which was added recently-ish

@jquense
Copy link
Member

jquense commented Aug 5, 2015

gonna try and help out with some docs tomorrow if I get some time :)

@jquense
Copy link
Member

jquense commented Aug 8, 2015

Do we have further consensus on whether we are pushing forward with the onSelect event, or going to try a different approach?

}
}

bindRootCloseHandler() {
Copy link
Member

Choose a reason for hiding this comment

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

It would be great to just use RootCloseWrapper here, but I don't think it'll work as it adds a wrapping div, @taion any thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

w/r/t RootCloseWrapper, I originally wrote it to use a wrapping div because I couldn't be sure that whatever custom popover I wrapped it with wouldn't do something dumb with onClick.

For something like this where we control what gets rendered (i.e. with the outer-most ul), I'd modify RootCloseWrapper to have a separate path that just injects the onClick prop instead of using the wrapping div.

Copy link
Member

Choose a reason for hiding this comment

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

Thats sort of the rub here as well, folks can now provide custom menu components, so we don't know for certain that the component will handle it...

Copy link
Member

Choose a reason for hiding this comment

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

#render() here always renders a ul as the outer-most DOM node, and we control it directly, no? Unless I'm misreading this code. i.e. we can just inject onClick into that ul from RootCloseWrapper painlessly.

Copy link
Member

Choose a reason for hiding this comment

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

bah sorry yes. I was conflating this question along with another one about whether it makes sense to push the root click to the DropdownBase instead of the menu, so all menus' benefit from it.

Copy link
Member

Choose a reason for hiding this comment

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

Oh sorry, I've barely gotten started reading through the rest of the PR.

How much does the rendering break if we add the wrapping div?

@mtscout6
Copy link
Member Author

@jquense I like your earlier proposal for the events, it dramatically simplifies things.

@taion taion added this to the v0.25 Release milestone Aug 10, 2015
const TOGGLE_REF = 'toggle-btn';

export default class DropdownBase extends React.Component {
constructor(props, Toggle=DropdownToggle) {
Copy link
Member

Choose a reason for hiding this comment

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

I believe the 2nd param to React.Component#constructor is supposed to be context.

Could we just use this.constructor.Toggle below? Or set Toggle on the prototype?

Copy link
Member

Choose a reason for hiding this comment

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

On second thought, this.constructor.Toggle will break everyone using IE <= 10 because static inheritance won't work there, so I'd suggest putting Toggle on the prototype, e.g. below do SplitButton.prototype.Toggle = SplitToggle.

@taion
Copy link
Member

taion commented Aug 11, 2015

This is super cool.

labelledBy: this.props.id
};

if (menu === undefined) {
Copy link
Member

Choose a reason for hiding this comment

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

cc @jquense

This is actually the opposite of what we decided to do for Modal, i.e. the support for custom modal dialogs is going into react-overlays, while the R-B Modal intentionally has fairly limited support (and a different API) for specifying a custom modal dialog class.

It might be nice to be consistent here if possible.

Copy link
Member

Choose a reason for hiding this comment

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

I was thinking about that myself and i am a bit torn about this one. If there is a more generic dropdown implementation that could be refactored out, its probably to early to know what that'd look like yet. On the other hand i think the level of customizability here is probably on par with what you could do with bootstrap proper. We, for instance gut the built in dropdown (in our non react projects), and essentially just use the styles. this api would let me do that in react, which I really appreciate.

Copy link
Member

Choose a reason for hiding this comment

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

Sure - the right answer might just be to make the R-B Modal's customDialogComponent API look a little more like this. I don't think it'd affect anybody deciding between using R-B Modal and react-overlays, but it'd make things line up better between R-B modals and dropdowns.

Copy link
Member

Choose a reason for hiding this comment

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

Ya definitely, as it is, the custom dialog prop is sort of a 'good enough' solution for the time anyway. I'm all for consistency

@mtscout6
Copy link
Member Author

@jquense With that PR complete did you see anything else that's needed before we bring this in? If it's complete I can do a squash and rebase

@taion
Copy link
Member

taion commented Aug 12, 2015

@mtscout6 I think we still need to fix the way we pass in Toggle to the constructors, and figure out if we're okay with being inconsistent between here and Modal for how we specify custom menus v custom modal dialogs.

@jquense
Copy link
Member

jquense commented Aug 12, 2015

So there is a good, point here: #526 (comment) the current PR will break users using a custom menuItem, since the extractors look for not known types and assume they are menu's.

We can always say that these cases need to have a menu wrapped around them, or perhaps create a more public api for telling the dropdown about types.

@mtscout6 mtscout6 mentioned this pull request Aug 12, 2015
30 tasks
@taion
Copy link
Member

taion commented Aug 12, 2015

@jquense An alternative might be to use something like dialogComponent (maybe menuComponent?) per Modal to specify the custom menu class.

@jquense
Copy link
Member

jquense commented Aug 12, 2015

@taion i would like to keep the current way if possible, it seems the more ergonomic of the two api (to me anyway). One issue with that approach tho is that you'd have to do something similar for the Button and Toggle components as well and that seems ugly...

Ultimately the issue is that the Dropdown really needs to know what things are menu items, or toggles, or menus. The most straight forward way to me is just to duck type it with a static property or something, not super elegant.

@taion
Copy link
Member

taion commented Aug 12, 2015

That makes sense. What do you think of doing the same for Modal, then, and moving toward deprecating dialogComponent there?

@jquense
Copy link
Member

jquense commented Aug 12, 2015

seems reasonable to me, tho that case is a little different, since the user is almost always going to specify a custom dialog, whereas here they may just specify a menu to add some classes or something.

The other thing might be that maybe we can jsut get rid of that feature on the modal completely, and point ppl to react-overlays if they need something more custom...I still need to see what the diff is between that and our implementation tho, before I can say its easy to do.

@taion
Copy link
Member

taion commented Aug 13, 2015

To make sure I'm not misunderstanding you, is your proposal that the children of a dropdown should be:

  1. Components that are explicitly labeled with a role (toggle, button, or menu); at most one of each
  2. Some set of components, none of which are explicitly labelled with a role

If the user does (1), then we use the explicitly labeled components. If the user does (2), then everything proceeds per the default.

The alternatively would be something like exposing toggleComponent (is this just the existing title prop?) and dropdownButton props and forcing the explicit use of those.

Thinking a little more, I'm not sure which semantically feels better. I like the elegance of specifying everything under children, but the difference between the custom and non-custom API looks a little magical; I don't really have a better answer, though.

@taion
Copy link
Member

taion commented Aug 13, 2015

To add to that, one difference between this and the API for Modal is that the children of the Modal have no special semantic meaning (except for the purposes of injection onHide into the header), and otherwise just behave as a children array.

This is a bit different in that the different children have different semantic meanings; e.g. it looks like Toggle and Menu would be invariant to things like re-orderings, and they won't get passed around as-is.

@jquense
Copy link
Member

jquense commented Aug 13, 2015

I think maybe some sort of type prop might be a good general API. If we could do something fairly foolproof we could not have to mess with things like linkProps or other awkward child pass throughs. not sure it would work, but it would be cool if it did. I don't like the magic, but elements as props always feels really awkward to me as well

@jquense
Copy link
Member

jquense commented Aug 13, 2015

examples of what I am thinking of...

<DropdownButton>
  <Button bsRole='toggle' >open</Button>

  <CustomMenu bsRole='menu'>
    <MenuItem>Item 2</MenuItem>
  </CustomMenu>
</DropdownButton>

or

<NavItem><Link bsRole='link'>client route</Link></NavItem>

I want to say that whatever the API is it is really an escape hatch. Most folks shouldn't need this sort of behavior, In general it will be other library authors (like react-router-bootstrap).

In this particular case, I think the "magic" is in assuming certain components are Menus. It seems like the perhaps we should be explicitly checking for menus and toggles, and just assume everything else is a MenuItem, which preserves the current behavior.

essentially a component has two "modes" for dealing with children, if a bsRole is defined on any child, then there is a strict whitelist of components allowed, only children with a role, and only whitelisted roles for that component. Otherwise it just does its default, such as wrapping children in a menu.

@jquense
Copy link
Member

jquense commented Aug 17, 2015

ok I checked a few things off I think, Are more pending items before we merge this? In liee of general onSelect documentation perhaps we should document controlled/uncontrolled props in getting started or something? If so does that block the PR here?

@jquense
Copy link
Member

jquense commented Aug 20, 2015

anything else here? LGTM otherwise (baring some commit squashing)...

@mtscout6
Copy link
Member Author

Looks good to me, though I don't have time to do the squashing for this before tomorrow.

@@ -21,6 +21,9 @@ if (options.debug) {
}

export default _.extend({}, baseConfig, {

devtool: options.debug ? 'source-map' : null,
Copy link
Member

Choose a reason for hiding this comment

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

Probably shouldn't change this here either.

@taion
Copy link
Member

taion commented Aug 20, 2015

This is really cool BTW. Thanks so much for all the work you've put into this - the new "custom" API is fantastic.

@jquense
Copy link
Member

jquense commented Aug 21, 2015

Ya it really is super sweet and going to save me a ton of work in the long run, thanks @mtscout6 !

@mtscout6
Copy link
Member Author

I'm going to give the rebase and squash a go right now.

@mtscout6
Copy link
Member Author

Well, that rebase went quicker than I was expecting.

@mtscout6
Copy link
Member Author

😢 Linting killed it, I'll get that fixed. Give me a few minutes.

…ely rewritten

Adds:
- Keyboard Navigation
- Aria properties for Assistive Technology
- Option to use custom menu with any of the dropdown variations
- `NavDropdown` component (Similar to `DropdownButton` but specifically
  for use within `Nav` components.
- `DropdownToggle` Component which can be used as an alternative to the
  `DropdownButton` `title` prop. Can either be directly imported or used
  as a static property of the `DropdownButton` with
  `DropdownButton.Toggle`. _(Useful should you want to use glyphicons or
  custom html within the toggle that's not a simple string.)_
- `SplitToggle` Similar to the `DropdownToggle` but targeted at the
  `SplitButton`'s toggle.
- Generic `Dropdown` component for easy dropdown customization.

Changed:
- Event handling of all these components to be in line with #419
- Written with ES6 class syntax

Removed:
- DropdownStateMixin - Everything is using ES6 class syntax so no more
  mixin usage
@mtscout6
Copy link
Member Author

Linting is all fixed and everything is now squashed.

@mtscout6 mtscout6 changed the title [WIP] DropdownButton, SplitButton, DropdownMenu, MenuItem Rewrite DropdownButton, SplitButton, DropdownMenu, MenuItem Rewrite Aug 21, 2015
@taion
Copy link
Member

taion commented Aug 21, 2015

Well what are we waiting for then? :shipit: :shipit: :shipit: :D

mtscout6 added a commit that referenced this pull request Aug 21, 2015
DropdownButton, SplitButton, DropdownMenu, MenuItem Rewrite
@mtscout6 mtscout6 merged commit 3a9eec9 into v0.25-rc Aug 21, 2015
@mtscout6 mtscout6 deleted the dropdown-revisited branch August 21, 2015 15:22
@AlexKVal
Copy link
Member

Yay 🎉 Finally 🚀
It was a long way.

@AndersDJohnson
Copy link

+1 for sub-components instead of title attributes on components like NavDropdown.

Though, this appears to be somewhat achievable already, e.g.:

var title = <span><i className="fa fa-gear"></i> Settings</span>;
var drop = <NavDropdown eventKey={3} title={title}> ... </NavDropdown>;

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

Successfully merging this pull request may close these issues.

None yet

6 participants