-
Notifications
You must be signed in to change notification settings - Fork 843
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
[EuiCollapsibleNavBeta] Build out component state and responsive behavior #7027
Conversation
- separate from `EuiCollapsibleNav` itself
+ update `EuiHeader` to export its padding as var for this component to use
- on desktop, collapsing retains the flyout but in an icon only button state that toggles popovers for accordions - on small screens, the flyout reverts to an overlay and fully disappears when collapsed - on smallest possible screens, the flyout becomes 100% width (requires an `!important` to override EuiFlyout's default CSS)
- exporting/importing `_EuiFlyoutSide` directly lets us skip the potential `undefined` type of the flyout prop
Adding @elastic/eui-team for info, but assigning primary QA/review to @breehall. Let me know if that works for you Bree! |
// TODO: Context for sharing state to all children | ||
<> |
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.
This will be addressed in the next PR (EuiCollapsibleNavItem
will need context and cannot be passed props directly). If you want a sneak preview, you can take a look at my WIP branch.
// Wait for any fixed headers to be queried before rendering (prevents position jumping) | ||
const flyout = fixedHeadersCount !== false && ( | ||
<EuiFlyout |
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.
This seems to be new as of React 18, which seems to be a lot more aggressive about mounting/unmounting 😬 I probably need to dig deeper into React 18's changes to figure out why / see if there's a workaround.
|
||
const classes = classNames( | ||
'euiCollapsibleNav', | ||
'euiCollapsibleNavBeta', |
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 planning on removing this className
once this component is out of beta and replaces euiCollapsibleNav
completely.
|
||
const onWindowResize = throttle(getBreakpoints, 50); | ||
window.addEventListener('resize', onWindowResize); | ||
return () => window.removeEventListener('resize', onWindowResize); |
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 love adding an extra window listener just for this component, but I decided the "custom" responsive breakpoints (based on the width of the nav) felt better than just blindly using EUI's s
/m
sized breakpoints. I think it fits the "container query" vs "media query" paradigm better as well.
/** | ||
* Collapsed state | ||
*/ | ||
const [isCollapsed, setIsCollapsed] = useState(initialIsCollapsed); | ||
const toggleCollapsed = useCallback( | ||
() => setIsCollapsed((isCollapsed) => !isCollapsed), | ||
[] | ||
); | ||
const onClose = useCallback(() => setIsCollapsed(true), []); |
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.
Just want to note that unlike the current EuiCollapsibleNav
, the new component's collapsed/expanded state is entirely self-contained (as opposed to being set/passed by consumers).
The button
prop that previously exists on the old component is also now entirely handled by EuiCollapsibleNavBeta
. The component is also primarily meant to be used within EuiHeader
, and looks a bit odd if it's not in one (this can be overridden via custom styles if absolutely needed).
TBH, I figure if consumers really want their own custom nav/flyout, they can simply use EuiFlyout
. As such, I decided to get way more opinionated about EuiCollapsibleNav
going forward, since I expect primarily just Kibana to be using it. It's also a much more higher-level component compared to others, and IMO the higher level a component is the less flexible it needs to be (as opposed to lower level building blocks/atoms).
If we get a consumer request for more customization of EuiCollapsibleNav
we can re-evaluate in the future, but for now I'm leaning towards expedience.
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.
TBH, I figure if consumers really want their own custom nav/flyout, they can simply use EuiFlyout
This is fair!
Preview documentation changes for this PR: https://eui.elastic.co/pr_7027_buildkite/ |
Preview documentation changes for this PR: https://eui.elastic.co/pr_7027/ |
💚 Build Succeeded
|
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.
Great work Cee! I just have a question or two surrounding the sizing of the menu!
<EuiFlyout | ||
{...rest} // EuiCollapsibleNav is significantly less permissive than EuiFlyout | ||
id={flyoutID} | ||
css={cssStyles} | ||
className={classes} | ||
style={style} | ||
size={248} // TODO: Responsive behavior | ||
side={side} | ||
focusTrapProps={focusTrapProps} | ||
as="nav" | ||
type="push" // TODO: Responsive behavior | ||
paddingSize="none" | ||
pushMinBreakpoint="s" | ||
onClose={() => {}} // TODO: Collapsed state | ||
hideCloseButton={true} | ||
> | ||
{/* TODO: collapsible button */} | ||
{children} | ||
</EuiFlyout> |
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 think utilizing EuiFlyout
instead of EuiCollapsibleNav
is a good decision here. This component will be quite an improvement from EuiCollapsibleNav
and if we decide to move away from that component in the future, we won't have to worry about "detangling" this version as much.
/** | ||
* Collapsed state | ||
*/ | ||
const [isCollapsed, setIsCollapsed] = useState(initialIsCollapsed); | ||
const toggleCollapsed = useCallback( | ||
() => setIsCollapsed((isCollapsed) => !isCollapsed), | ||
[] | ||
); | ||
const onClose = useCallback(() => setIsCollapsed(true), []); |
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.
TBH, I figure if consumers really want their own custom nav/flyout, they can simply use EuiFlyout
This is fair!
if (isSmallestScreen) return '100%'; | ||
if (isSmallScreen) return _width; | ||
if (isCollapsed) return headerHeight; |
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.
Were the three different menu sizes a requirement for this newer version of a collapsible nav? I'm thinking of current navigation and of the current offerings, and I don't think they take up the entire screen when displayed on a super small device.
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.
Resize your window appropriately to confirm that the nav's breakpoints are dynamic based on the width passed - it's considered "desktop" at 3x the width of the nav, and "smallest" at under 1.5x the width of the nav
Can you tell me a little about why we chose to go with dynamic breakpoints?
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.
Were the three different menu sizes a requirement for this newer version of a collapsible nav?
Yep, specific requirements from the designers. See the original issue (#6759 - Ctrl+F for "docked option") and the Figma link. The "Navigation At Smallest Viewport Sizes" pink callout in particular is very new, no other flyouts go to 100% screen width.
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 tell me a little about why we chose to go with dynamic breakpoints?
This is somewhat answered in my above comment, but let me expound on it a bit more:
[...] I decided the "custom" responsive breakpoints (based on the width of the nav) felt better than just blindly using EUI's
s
/m
sized breakpoints. I think it fits the "container query" vs "media query" paradigm better as well.
Let's say a consumer wants, for whatever godforsaken reason, to set their nav width to 1000px wide, and we're using the static s
sized EUI breakpoint (575px) for determining when to switch to an overlay flyout. This combination is a layout nightmare for consumers on smaller screens - the navigation will push content off-screen for consumers at (e.g.) 800px wide screens. Our options at that point are:
- To hope consumers don't do something that dumb or if they do, that they update/override EUI's breakpoints accordingly (wishful thinking)
- To place strict guardrails around the
width
prop and try to do things like set a maximum width on it (can be frustrating for both parties and can inevitably lead to issues being opened when our components don't support a super custom or edge case scenario) - Or 3, and IMO the most flexible option, to base the responsive breakpoints of the component on its width, which we're already obtaining from consumers as a prop.
To be clear, sometimes 2 is the option we go with, but in this the benefits of 3 were well worth the dev effort, and, as mentioned, more closely follows the paradigm of container queries vs media queries.
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.
Oh, I maybe missed some nuance in your question - if you're asking about where I came up with the 3x/1.5x ratios, those didn't come from the designers, I kind of eyeballed them based on what felt good/natural based on visual balance and so forth.
My reasoning for 3x is - you don't want a nav taking up more than 1/3rd the total available screen width - 1/2 definitely feels like it's massively cramping the page content. For 1.5x, anything larger made a sorta weird width jump when going from a non-full-width flyout to a full-width flyout.
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.
Oh, I maybe missed some nuance in your question
No you got it! I wasn't sure if this was a pattern that came from designers or if we as developers were creating a new pattern, so thank you for pointing me to the info.
This definitely feels like a "training wheels" situation where we're just guiding and hoping they'll make good decisions.
Edit: Nevermind, I'm taking that thought back!
Have we thought about adding a minimum width as a safe guard? They shouldn't want to do that, but accidents happen
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.
This is very dependent on the scenario, but in general I'm not a super huge fan of setting safeguards around props (the exception to that being accessibility requirements).
You end up with requests like this one where the consumer has a specific usage for a component that doesn't match the one we designed for, but makes total sense for their layout or their primary audience.
In this case we've designed a system that works very well for Kibana's intended nav width (248px) and will work reasonably well for most sensible nav sizes within the 100-600px ballpark (could go even higher if a consumer is using those 4000px thunder monitors; no shade). Could a consumer pass in something ridiculous like -200 and break the component? Yeah, sure, but that's not realistic and there's no sense spending time writing extra guards or tests for it - generally, trying to handle/predict reasonable use cases and ignoring unreasonable ones is going to be the sanest path forward. And sometimes we get issues opened where we determine the request to be reasonable after all and just not one we'd thought of, and that's totally okay/part of the iterative process of things.
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.
but that's not realistic and there's no sense sitting spending time writing extra guards or tests for scenarios that it
Absolutely! And that's why I took the thought back after thinking on it a little more. Hope I didn't waste too much of your time explaining. Thank you so much for the insight!
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.
No worries at all, I always like having these convos out in the open - there's always a chance future-us will want to go back and figure out why we did something a certain way, or even that someone totally random out there on the internet will learn something new!
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.
This is good to go! Thank you for answering my questions in the comments!
Thanks a ton for the review and the engaging conversations Bree! |
Preview documentation changes for this PR: https://eui.elastic.co/pr_7027/ |
Summary
EuiCollapsibleNavItem
needs to conditionally render a icon that toggles a popover, but only for larger desktop screens when in the "collapsed" state.EuiCollapsibleNavItem
component #6904As always, I recommend following along by commit.
Screenshots
Desktop - expanded
Desktop - collapsed
As noted above, this is expected to be broken and will be implemented in the next PR. However, the flyout width should be correct.
Mobile - expanded
Smallest screens should have the flyout overlay to 100% width

Mobile - collapsed
QA
gh pr checkout 7027
yarn storybook
side
prop in the Storybook controls panel correctly toggles the flyout position and the icon directionswidth
prop to a custom width, e.g.400
General checklist
- [ ] Checked for accessibility including keyboard-only and screenreader modesBelow items are all N/A due to the component's beta status
- [ ] A changelog entry exists and is marked appropriately- [ ] Checked for breaking changes and labeled appropriately- [ ] Props have proper autodocs (using@default
if default values are missing) and playground toggles- [ ] Added documentation- [ ] Checked Code Sandbox works for any docs examples- [ ] Updated the Figma library counterpart