-
Notifications
You must be signed in to change notification settings - Fork 377
Tabs updates #4146
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
Tabs updates #4146
Conversation
|
PF4 preview: https://patternfly-react-pr-4146.surge.sh |
| insetOnMd: undefined, | ||
| insetOnLg: undefined, | ||
| insetOnXl: undefined, | ||
| insetOn2Xl: undefined |
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.
Is there a particular reason these props are defined as undefined?
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.
Do you recommend setting it to null? We only want to apply the modifier if one is supplied?
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.
Nit: no reason to set defaults to undefined. Just don't specify a default and they will be undefined.
| // Update scroll button state and which button to highlight | ||
| setTimeout(() => { | ||
| this.handleScrollButtons(); | ||
| }, 1); |
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 really shouldn't be using setTimeout -- it doesn't always work as expected in production Vs local.
Is there something we can do via componentDidUpdate instead, when state has changed?
Better yet, perhaps use the callback provided by setState?
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.
Why is this needed?
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 was preexisting. Not sure why it is here. I am looking into it.
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.
@jschuler this is what I was asking about. I do not understand why we need this.
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.
good question, i did that a year ago :D let's remove it
| } | ||
| }; | ||
|
|
||
| insetString = (s: string) => (s === '2xl' ? '_2xl' : capitalizeFirstLetter(s)); |
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's not clear what format is expected here. Some comments (i.e., examples) would help clarify how capitalizeFirstLetter is used to build modifiers.
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 an internal function used only by this component would a comment be enough or should I add examples to?
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.
Yes, just looking for code comments here
| href?: string; | ||
| /** Tab title */ | ||
| /** Content rendered in the tab title */ | ||
| title: React.ReactNode; |
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 appears that TabTitleText is required for proper styling. Should the type reflect that?
Or, at least note that in the comments?
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 also add that to your PR description? Looks like the components to use are not mentioned
mcarrano
left a comment
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.
@tlabaj I'm comparing this against the Core examples here http://patternfly-v4.surge.sh/documentation/core/components/tabs and I'm not seeing examples for Box tabs. Is this PR intended to reflect all recent tab updates?
|
@mcarrano to consolidate the examples, I added a checkbox at the bottom, "isBox" to switch modifier on and off. If it is too confusing, I can separate the examples (that is how I i had it initially). |
|
I like this approach @tlabaj . It reduces the number of examples which is a good thing :) My only concern is it could be easily missed. Would it be possible to add some text at the top of the examples section to explain this? I can give you some text if you want. |
|
@mcarrano yes, please provide some text that you think would work. |
|
@tlabaj how about, "Most tab variations are available as open (default) or box style tabs. Select the 'isBox' checkbox to preview an example with box styled tabs." |
redallen
left a comment
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 scrolling behavior seems fine, but maybe we could make a better inset[On(Size)] API more akin to how we do breakpoint mods? Other than that just some nits.
|
|
||
| export const TabTitleIcon: React.FunctionComponent<TabTitleIconProps> = ({ | ||
| children, | ||
| className = '', |
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.
Nit: we don't have to set these to a default of empty string anymore on v4.
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 followed the API we have in the drawer component for width.
| insetOnMd: undefined, | ||
| insetOnLg: undefined, | ||
| insetOnXl: undefined, | ||
| insetOn2Xl: undefined |
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.
Nit: no reason to set defaults to undefined. Just don't specify a default and they will be undefined.
| // Update scroll button state and which button to highlight | ||
| setTimeout(() => { | ||
| this.handleScrollButtons(); | ||
| }, 1); |
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.
Why is this needed?
| /** Unmounts tab children (removes them from the DOM) when they are no longer visible */ | ||
| unmountOnExit?: boolean; | ||
| /* Modifies the tabs component padding/inset to visually match padding of other adjacent components.*/ | ||
| inset?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; |
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.
Maybe something like our breakpoint mods helpers would be a better API here? The structure is looking similar.
| * | ||
| * @param {string} s string to make first letter a capital | ||
| */ | ||
| export const capitalizeFirstLetter = (s: string) => s.charAt(0).toUpperCase() + s.slice(1); |
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.
There's a capitalize helper on line 9 already that does the exact same thing.
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 see that now. I just read the description and thought it capitalized the whole string. Thanks! I will update description of original function.
|
|
||
| export const TabTitleText: React.FunctionComponent<TabTitleTextProps> = ({ | ||
| children, | ||
| className = '', |
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.
Nit: we don't have to set these to a default of empty string anymore on v4.
|
|
||
| export interface TabTitleIconProps extends React.HTMLProps<HTMLSpanElement> { | ||
| /** Icon to be rendered inside the tab button title. */ | ||
| children?: React.ReactNode; |
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 make this required
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 do not generally make children for similar components required. Is it because the title is required?
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.
without children this component does nothing right? so that's why i think it's not optional. when else should something be required? :)
|
|
||
| export interface TabTitleTextProps extends React.HTMLProps<HTMLSpanElement> { | ||
| /** Text to be rendered inside the tab button title. */ | ||
| children?: React.ReactNode; |
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 should be required
| } | ||
|
|
||
| componentWillUnmount() { | ||
| document.removeEventListener('resize', this.handleScrollButtons, false); |
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.
in componentDidMount you added the listener to window. They both should be the same
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.
Interesting this is how it was before. I did not update here. Should we have it on window on both?
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.
Both should be on window.
| formatInsetString = (s: string) => (s === '2xl' ? '_2xl' : capitalize(s)); | ||
|
|
||
| componentDidMount() { | ||
| window.addEventListener('resize', this.handleScrollButtons, false); |
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 we only listen to resize if !isVertical? Looks like it needs that condition to add the modifier
| <AngleLeftIcon /> | ||
| </button> | ||
| <ul className={css(styles.tabsList)} ref={this.tabList}> | ||
| <ul className={css(styles.tabsList)} ref={this.tabList} onScroll={this.handleScrollButtons}> |
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.
similar question here, should we only call the function if ! isVertical?
|
Nav link example scrolls top on click |
Codecov Report
@@ Coverage Diff @@
## v4 #4146 +/- ##
==========================================
- Coverage 57.94% 57.82% -0.13%
==========================================
Files 391 393 +2
Lines 5982 6010 +28
Branches 2354 2364 +10
==========================================
+ Hits 3466 3475 +9
- Misses 2066 2091 +25
+ Partials 450 444 -6
Continue to review full report at Codecov.
|
jessiehuff
left a comment
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.
LGTM from an accessibility standpoint! It's keyboard accessible and sounds good in VO. I noticed that axe shows a violation for the aria-controls of two examples which is odd because the axe script didn't flag it, and I can't determine why it was flagged by axe in the browser. It looks unrelated to your PR though so I'll investigate that separately. 🙂
mattnolting
left a comment
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.
LGTM! 👍
redallen
left a comment
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 like to see us use the breakpoint modifier API for insets rather than 5 props. Other than that this LGTM and has great test coverage, thanks @tlabaj !
| /** Unmounts tab children (removes them from the DOM) when they are no longer visible */ | ||
| unmountOnExit?: boolean; | ||
| /* Modifies the tabs component padding/inset to visually match padding of other adjacent components.*/ | ||
| inset?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; |
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 experimented with our breakpoint modifier API and I think it'd be a good fit and extendable to future modifiers:
export interface TabBreakpointMod {
/** The attribute to modify */
modifier:
| 'insetNone'
| 'insetSm'
| 'insetMd'
| 'insetLg'
| 'insetXl'
| 'inset_2xl';
/** The breakpoint at which to apply the modifier */
breakpoint?: 'sm' | 'md' | 'lg' | 'xl' | '2xl';
}...
/** Array of objects representing the various modifiers to apply to tabs at various breakpoints */
breakpointMods?: TabBreakpointMod[];...
formatBreakpointMods(breakpointMods, styles),
...
<Tabs
activeKey={activeTabKey}
onSelect={this.handleTabClick}
breakpointMods={[
{ breakpoint: 'md', modifier: 'insetSm' },
{ breakpoint: 'lg', modifier: 'insetLg' },
{ breakpoint: 'xl', modifier: 'inset_2xl' }
]}
isBox={isBox}>Let me know what you think!
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.
Fair enough. I see how this allows for a more flexible interface. But, I do we think if we make this change we should probably go back and update Drawer for consistency.
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. I think Drawer should use the same API and we should document it in the breaking change release notes.
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.
Drawer is still beta. Do you mind opening the drawer issue. I will update the Tabs API.
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 opened #4238
| rightScrollAriaLabel?: string; | ||
| /** determines what tag is used around the Tabs. Use "nav" to define the Tabs inside a navigation region */ | ||
| /** Determines what tag is used around the tabs. Use "nav" to define the tabs inside a navigation region */ | ||
| variant?: 'div' | 'nav'; |
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.
do we normally use component where you specify the type of element to be used?
redallen
left a comment
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.
Need this in for RC1, would be good to change to using the breakpointMods API later.

What: Closes #3951
Breaking changes
Tabs:
variantfor consistency. You will need to update all instances ofvariantprop tocomponentTabVariantenum Name toTabComponentfor consistent naming. You will now need to update all instances oftabVarianttoTabComponent.Tab:
<TabTitleText>for proper styling. If you would like to place an Icon in the Tab, it should be wrapped with<TabTitleIcon>for proper styling.