-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Feature: Add closeOnSelect & onSelect to Navbar API with tests & docs #2280
Conversation
I don't think this is the right approach. I think you should hook into |
Hooking into |
… lazyAutoToggle hook
Current coverage is 93.34% (diff: 100%)@@ master #2280 diff @@
==========================================
Files 94 94
Lines 1683 1684 +1
Methods 0 0
Messages 0 0
Branches 0 0
==========================================
+ Hits 1571 1572 +1
Misses 112 112
Partials 0 0
|
You were totally right @taion, the hook on I also updated the docs & test for this approach in the latest commit. |
@@ -93,6 +93,8 @@ const defaultProps = { | |||
const contextTypes = { | |||
$bs_navbar: React.PropTypes.shape({ | |||
bsClass: React.PropTypes.string, | |||
lazyAutoToggle: React.PropTypes.bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'd just pass down an onSelect
here
* Does nothing if no child <Nav> is present. lazyAutoToggle should not be used | ||
* for complex nav menus. | ||
*/ | ||
lazyAutoToggle: React.PropTypes.bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd call this toggleOnSelect
(or maybe autoToggle
)
|
@kevinzwhuang I mean add one. @jquense wdyt? |
@taion ah I see what you mean. 👍 In that case we might not even need a Though I think maybe it should be named |
Personally I think I feel like this "toggle on select" behavior you're describing is common enough that it'd make sense to have as an option. It may not account for all edge cases, but it's probably what most users want. |
Sounds good @taion. I'll work on updating this PR with that approach sometime this weekend. |
Updated to the latest changes as discussed with Docs & tests have been updated to reflect this new API. Also I've created an updated Plunker with the latest build of this PR. Works well with In this latest demo, I've added a particular example using the |
@@ -319,6 +322,7 @@ class Nav extends React.Component { | |||
const childOnSelect = createChainedFunction( | |||
child.props.onSelect, | |||
onSelect, | |||
navbar && (navbar.onSelect || navbar.toggleOnSelect && navbar.onToggle) || null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you put all this logic in e.g. a handleSelect
on <Navbar>
?
Folded the Tests did not need to be modified since it's the same behaviour, updated plunker with latest build continues to work, which you can run here: https://plnkr.co/edit/PZhOUx |
@@ -100,6 +126,7 @@ class Navbar extends React.Component { | |||
bsClass, | |||
expanded, | |||
onToggle: this.handleToggle, | |||
onSelect: this.handleSelect, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry, one last thing – i think it might be better to make this just
createChainedFunction(onSelect, toggleOnSelect && this.handleToggle)
I think it's pretty unlikely that users will specify both toggleOnSelect
and onSelect
, and I think it'd be a bit cleaner this way.
Otherwise, this is looking really good. Thanks!
Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
navbar-btn Buttons aren't going to work correctly with this. it doesn't fail, but it may be unexpected that they don't work? do we care?
|
||
return { | ||
$bs_navbar: { | ||
bsClass, | ||
expanded, | ||
onToggle: this.handleToggle, | ||
onSelect: createChainedFunction(onSelect, toggleOnSelect && this.handleToggle || null), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this actually toggle or only close the navbar? Theoretically you can't select something if its collapsed, but double clicks may get through, or other weird races.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should toggle, only after the NavBar's onSelect
if it exists and the Nav/NavItem's onSelect
(the sequence is on Nav.js
line 320). The race with double clicks is inevitable with the current API, since it happens with Navbar.Toggle already currently.
}); | ||
|
||
it('Should fire onSelect with eventKey for nav children', () => { | ||
const selectSpy = sinon.spy(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this work correctly with a NavDropdown?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NavDropdown's are ignored by onSelect & toggleOnSelect and their subcomponent MenuItems fire those callbacks correctly. I think this is the correct behavior out of the box - you can see it demonstrated here:
https://plnkr.co/edit/4sn165?p=preview (when you click the NavDropdown, no onSelect or onToggle callbacks are fired for either configuration).
I added a condition for Updated plunker with this build version: https://plnkr.co/edit/4sn165?p=preview Update: Changed the condition to a function that evaluates Update on this: Checking |
…w.innerWidth on runtime
const { bsClass, expanded } = this.props; | ||
const { bsClass, expanded, onSelect, toggleOnSelect } = this.props; | ||
const mobileToggle = () => { | ||
if (typeof(window) !== 'undefined' && window.innerWidth < 768) { |
There was a problem hiding this comment.
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 need this check. it's too brittle anyway as a user can change the navbar collapse breakpoint to whatever they want so hard coding a pixel width here isn't ideal.
I do think that it illustrates a more general problem with this feature tho, you don't really want to be toggling expanded state while the expansion is not relevant, however there isn't really any way to know that from JS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it's brittle hard-coded as is.
One approach I can think of dealing with is is exposing a prop toggleOnSelectMaxWidth
. It's of React.PropTypes.number
and defaults to 768. So then when toggleOnSelect == true
the window.innerWidth check would compare with toggleOnSelectMaxWidth
.
Or toggleOnSelect
could be the numeric value itself, and default to 0, so it's falsey when not set. When set, it can serve as the maxWidth itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does anything bad happen if we unnecessarily toggle collapsed state? The styling is a no-op on desktop views, plus the user has to opt-in to this behavior anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The whole navbar looks like it sort of re-renders when it tries to collapse while in a non-mobile viewport.
Here's an older plunker that demonstrates this: https://plnkr.co/edit/PZhOUx?p=preview
(make sure to resize the iframe to greater than 768px)
If we can figure out a way to fix this behavior in the lifecycle of the navbar, then we could get away with not having to deal with a arbitrary innerWidth
breakpoint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm really against users needing to input there grid breakpoint in order for this to work well. IMO it defeats the value of having this "add on" functionality if is comes with config, or a gotcha.
The only way i can think of tho to handle this is some sort of computedStyle sniffing to detect if the nav is in the right media query...that's not much less brittle tho :/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree - users shouldn't have to set a breakpoint.
I think I found a solution to this problem. It goes back to the point you mentioned about toggling vs closing the navbar! We were tackling it from the wrong angle with toggleOnSelect
, which is causing the navbar to toggle between expanded
states even when the screen size is not appropriate. In fact, screen size should not matter - only the value of expanded
should really matter.
The right functionality should be something more like a closeOnSelect
or collapseOnSelect
, which will set expanded
to false, only if it was true. If expanded
is false, then it does nothing. I'll update this PR with a commit to demonstrate this new approach shortly.
@jquense I don't care about Navbar buttons. How do those even show up on mobile/collapsed anyway? IMO it's a non-problem. |
That's a great idea. |
Latest commit adds |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can deal with the comment nits later
@@ -57,7 +58,36 @@ const propTypes = { | |||
* @controllable navExpanded | |||
*/ | |||
onToggle: React.PropTypes.func, | |||
|
|||
/** | |||
* A callback fired when a `<NavItem>` grandchild is selected inside a child |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"descendant" rather than "grandchild" (since it's actually Navbar > Navbar.Collapse > Nav > NavItem, and there might even be a LinkContainer or so in there)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about "...when a descendant of a child <Nav>
is selected..."? This might refer to NavItem, LinkContainer, etc., basically any descendants that can be selected without referring to any specific ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds great.
/** | ||
* Sets `expanded` to false after the onSelect event of a child of `<Nav>` | ||
* (such as `<NavItem>` or `<MenuItem>`). Does nothing if no `<Nav>` or | ||
* `<NavItem>`/`<MenuItem>` children exist. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<MenuItem>
only goes under dropdowns actually
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Working off of what I brainstormed in the comment above, how's this?
"
Sets expanded
to false
after the onSelect event of a descendant of a child <Nav>
. Does nothing if no <Nav>
or <Nav>
descendants exist.
"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
looks good to me. Thanks for the great work. Let me know @kevinzwhuang if you've like to be part of the react-bootstrap organization*. Its really low commitment, contribute as you have time. (baring unlikely disagreement from other @react-bootstrap/collaborators) |
Does not seem to work on mobile device? |
@GerardoMachadoGandaria could you provide some more details? I use this in my own apps and just tested this again myself on Android and iPhone and there's no problem. Are you using |
Sorry my fault. Works fine. |
Glad it worked out 😄 |
Update 10/28:
closeOnSelect
has been renamed tocollapseOnSelect
to fit better semantically, given that it changes on theexpanded
prop and notopen
.Update 10/27:
Several improvements have been made on this API since the last update on 10/17.
The demo of the latest build can be run here: https://plnkr.co/edit/SNvGZyLkiBmWOnyT86yG
A summary of what it looks like now is as follows:
Update 10/17:
This PR has been improved from its original implementation. To see a running demo of the current iteration, you can run this Plunker here: https://plnkr.co/edit/43i4XA
The proposal has been simplified and improved with these two new props to the
<Navbar>
API:/Update
Description:
Auto toggling as a behavior in react-bootstrap has been discussed at length in #1301 & #1692. @jquense and @taion have made good points against implementing it into the API due to complexities of different cases. There have also been valid arguments made for a simple version of this behavior to have available for users that does not need to catch every edge case. I think there is value to having a simple implementation of this expected behavior (normal click menus) for many users.
After reading over the discussion, I'd like to propose a simple addition to the Navbar API -
lazyAutoToggle
. I thought of naming itlazy
to make it very apparent that it goes about autotoggling in a nondiscriminant & noncomplex way on<Navbar.Collapse>
and its subcomponents. It does not attempt to be an omni-solution for all toggling needs.Demo:
I've written a demo of this functionality in the plnkr. You can run the example at the link below:
https://plnkr.co/edit/vDejOB
There you can compare the functionality of a
<Navbar>
with & withoutlazyAutoToggle
on.It uses the built version of
react-bootstrap
from this PR.How to use:
Simply add
lazyAutoToggle
to a<Navbar>
with a child<Navbar.Collapse>
.Changes Proposed:
lazyAutoToggle
prop to NavbarlazyAutoToggle
tochildContext
of NavbarlazyAutoToggle
andonToggle
fromthis.context.$bs_navbar
in<Navbar.Collapse>
to fireonToggle
when both existThis solution is fairly straightforward. It involves adding the prop value of
lazyAutoToggle
to thechildContext
of<Navbar>
and its children - particularly for use by<Navbar.Collapse>
.<Navbar.Collapse>
will then fireonToggle
from its parent<Navbar>
if and only iflazyAutoToggle
&&onToggle
exist inthis.context.$bs_navbar
.To support this new prop, I've also added:
lazyAutoToggle
onNavbar.Collapse
clickslazyAutoToggle
onNavbar.Collapse
subcomponent click + onClick eventslazyAutoToggle
inNavbar
propTypes.Not included:
Since this is a simple solution, this will not attempt to cover any complex cases where individual items might need to prevent toggling. I can foresee a roadmap towards providing an array prop of components that should be ignored by lazyAutoToggle, but that is not a part of this PR.
As a single prop solution,
lazyAutoToggle
works extremely well with regular links, buttons, etc. (normal click-related menu items).cc: @jquense and @taion