Skip to content

Conversation

@tlabaj
Copy link
Contributor

@tlabaj tlabaj commented Apr 28, 2020

What: Closes #3951

Breaking changes

Tabs:

  1. Changed prop variant for consistency. You will need to update all instances of variant prop to component
  2. Changed TabVariant enum Name to TabComponent for consistent naming. You will now need to update all instances of tabVariant to TabComponent.

Tab:

  1. The title should be wrapped with <TabTitleText> for proper styling. If you would like to place an Icon in the Tab, it should be wrapped with <TabTitleIcon> for proper styling.

@tlabaj tlabaj added ux review Breaking change 💥 this change requires a major release and has API changes. labels Apr 28, 2020
@tlabaj tlabaj added Breaking change 💥 this change requires a major release and has API changes. and removed Breaking change 💥 this change requires a major release and has API changes. labels Apr 28, 2020
@patternfly-build
Copy link
Collaborator

patternfly-build commented Apr 28, 2020

@tlabaj tlabaj changed the title Tab updates Tabs updates Apr 28, 2020
insetOnMd: undefined,
insetOnLg: undefined,
insetOnXl: undefined,
insetOn2Xl: undefined
Copy link
Member

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?

Copy link
Contributor Author

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?

Copy link
Contributor

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);
Copy link
Member

@dlabrecq dlabrecq Apr 28, 2020

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?

Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this needed?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Collaborator

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));
Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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;
Copy link
Member

@dlabrecq dlabrecq Apr 29, 2020

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?

Copy link
Collaborator

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

Copy link
Member

@mcarrano mcarrano left a 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?

@tlabaj
Copy link
Contributor Author

tlabaj commented Apr 29, 2020

@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).

@mcarrano
Copy link
Member

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.

@tlabaj
Copy link
Contributor Author

tlabaj commented May 4, 2020

@mcarrano yes, please provide some text that you think would work.

@mcarrano
Copy link
Member

mcarrano commented May 4, 2020

@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."

Copy link
Contributor

@redallen redallen left a 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 = '',
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

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);
Copy link
Contributor

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';
Copy link
Contributor

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);
Copy link
Contributor

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.

Copy link
Contributor Author

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 = '',
Copy link
Contributor

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;
Copy link
Collaborator

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

Copy link
Contributor Author

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?

Copy link
Collaborator

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;
Copy link
Collaborator

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);
Copy link
Collaborator

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

Copy link
Contributor Author

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?

Copy link
Contributor

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);
Copy link
Collaborator

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}>
Copy link
Collaborator

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?

@mattnolting
Copy link
Contributor

Screen Shot 2020-05-07 at 3 27 06 PM

Looks like .pf-c-tabs__link is being added twice

@mattnolting
Copy link
Contributor

Nav link example scrolls top on click .pf-c-tab__link click https://patternfly-react-pr-4146.surge.sh/documentation/react/components/tabs#using-the-nav-element

@codecov-io
Copy link

codecov-io commented May 8, 2020

Codecov Report

Merging #4146 into v4 will decrease coverage by 0.12%.
The diff coverage is 64.40%.

Impacted file tree graph

@@            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     
Flag Coverage Δ
#patternfly4 57.82% <64.40%> (-0.13%) ⬇️
Impacted Files Coverage Δ
...es/react-core/src/components/DataList/DataList.tsx 90.90% <ø> (ø)
...-core/src/components/Drawer/DrawerPanelContent.tsx 45.45% <ø> (ø)
...eact-core/src/components/FileUpload/FileUpload.tsx 25.00% <ø> (ø)
...core/src/components/FileUpload/FileUploadField.tsx 46.87% <ø> (ø)
...eact-core/src/components/FormSelect/FormSelect.tsx 75.00% <ø> (ø)
...es/react-core/src/components/Nav/NavExpandable.tsx 51.72% <ø> (ø)
...rc/components/Pagination/PaginationOptionsMenu.tsx 78.78% <ø> (ø)
packages/react-core/src/components/Tabs/Tab.tsx 100.00% <ø> (ø)
...kages/react-core/src/components/Tabs/TabButton.tsx 83.33% <ø> (ø)
.../react-core/src/components/TextInput/TextInput.tsx 84.61% <ø> (ø)
... and 12 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update df0b6ba...5c853b5. Read the comment docs.

Copy link
Contributor

@jessiehuff jessiehuff left a 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. 🙂

Copy link
Contributor

@mattnolting mattnolting left a comment

Choose a reason for hiding this comment

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

LGTM! 👍

Copy link
Contributor

@redallen redallen left a 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';
Copy link
Contributor

@redallen redallen May 11, 2020

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!

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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';
Copy link
Contributor

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?

Copy link
Contributor

@redallen redallen left a 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.

@redallen redallen dismissed stale reviews from dlabrecq and jschuler May 12, 2020 17:57

Comments addressed.

@redallen redallen merged commit 01afe1d into patternfly:v4 May 12, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Breaking change 💥 this change requires a major release and has API changes.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tabs - updates to support new design

10 participants