-
Notifications
You must be signed in to change notification settings - Fork 351
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
PF4: feat(Tabs) Add Tabs for PF4 #1144
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
4330d35
PF4: feat(Tabs) Add Tabs for PF4
tlabaj 2a80a6c
update based on reviews
tlabaj e5e9cee
update examples to match core
tlabaj b0b306b
update test snapshot
tlabaj 6f8bc54
add missing index.d.ts file
tlabaj 70f1a55
Remove secondary tabs
tlabaj 23b12ad
Revert "Remove secondary tabs"
tlabaj ca69701
remove scroll buttons from secondary tabs
tlabaj 826bca5
updates based on review comments
tlabaj a1cdf9c
Add tabs to integration App
tlabaj File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
10 changes: 10 additions & 0 deletions
10
packages/patternfly-4/react-core/src/components/Tabs/Tab.d.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { SFC, HTMLProps } from 'react'; | ||
|
||
export interface TabProps extends HTMLProps<HTMLDivElement> { | ||
title: string; | ||
eventKey: number; | ||
} | ||
|
||
declare const Tab: SFC<TabProps>; | ||
|
||
export default Tab; |
25 changes: 25 additions & 0 deletions
25
packages/patternfly-4/react-core/src/components/Tabs/Tab.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
const propTypes = { | ||
/** content rendered inside the Tab content area. */ | ||
children: PropTypes.node, | ||
/** additional classes added to the Modal */ | ||
className: PropTypes.string, | ||
/** Tab title */ | ||
title: PropTypes.string.isRequired, | ||
/** uniquely identifies the tab */ | ||
eventKey: PropTypes.number.isRequired | ||
}; | ||
|
||
const defaultProps = { | ||
children: null, | ||
className: '' | ||
}; | ||
|
||
const Tab = ({ className, children, title, eventKey, ...props }) => <React.Fragment>{children}</React.Fragment>; | ||
|
||
Tab.propTypes = propTypes; | ||
Tab.defaultProps = defaultProps; | ||
|
||
export default Tab; |
21 changes: 21 additions & 0 deletions
21
packages/patternfly-4/react-core/src/components/Tabs/Tab.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import React from 'react'; | ||
import { shallow } from 'enzyme'; | ||
import Tab from './Tab'; | ||
|
||
test('should render tab', () => { | ||
const view = shallow( | ||
<Tab id="tab1" eventKey={0} title="Tab item 1"> | ||
Tab 1 section | ||
</Tab> | ||
); | ||
expect(view).toMatchSnapshot(); | ||
}); | ||
|
||
test('should render active tab', () => { | ||
const view = shallow( | ||
<Tab id="tab1" eventKey={0} title="Tab item 1" isActive> | ||
Tab 1 section | ||
</Tab> | ||
); | ||
expect(view).toMatchSnapshot(); | ||
}); |
16 changes: 16 additions & 0 deletions
16
packages/patternfly-4/react-core/src/components/Tabs/Tabs.d.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { SFC, HTMLProps, FormEvent } from 'react'; | ||
import { Omit } from '../../typeUtils'; | ||
|
||
export interface TabsProps extends Omit<HTMLProps<HTMLDivElement>, 'onSelect'> { | ||
children: any; | ||
activeKey?: number; | ||
onSelect?(event: FormEvent<HTMLInputElement>, eventKey: number): void; | ||
isFilled?: boolean; | ||
isSecondary?: boolean; | ||
leftScrollAriaLabel?: string; | ||
rightScrollAriaLabel?: string; | ||
} | ||
|
||
declare const Tabs: SFC<TabsProps>; | ||
|
||
export default Tabs; |
20 changes: 20 additions & 0 deletions
20
packages/patternfly-4/react-core/src/components/Tabs/Tabs.docs.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { Tabs, Tab } from '@patternfly/react-core'; | ||
import SimpleTabs from './examples/SimpleTabs'; | ||
import ScrollButtonsTabs from './examples/ScrollButtonsTabs'; | ||
import FilledTabs from './examples/FilledTabs'; | ||
import SecondaryTabs from './examples/SecondaryTabs'; | ||
|
||
export default { | ||
title: 'Tabs', | ||
live: false, | ||
components: { | ||
Tabs, | ||
Tab | ||
}, | ||
examples: [ | ||
{ component: SimpleTabs, title: 'Primary tabs with sections' }, | ||
{ component: ScrollButtonsTabs, title: 'Scroll buttons' }, | ||
{ component: SecondaryTabs, title: 'Secondary buttons' }, | ||
{ component: FilledTabs, title: 'Filled buttons' } | ||
] | ||
}; |
224 changes: 224 additions & 0 deletions
224
packages/patternfly-4/react-core/src/components/Tabs/Tabs.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
import React from 'react'; | ||
import styles from '@patternfly/patternfly-next/components/Tabs/tabs.css'; | ||
import { css } from '@patternfly/react-styles'; | ||
import PropTypes from 'prop-types'; | ||
import { AngleLeftIcon, AngleRightIcon } from '@patternfly/react-icons'; | ||
import { getUniqueId, isElementInView, sideElementIsOutOfView } from '../../internal/util'; | ||
import { SIDE } from '../../internal/constants'; | ||
|
||
const propTypes = { | ||
/** content rendered inside the Tabs Component. */ | ||
children: PropTypes.node.isRequired, | ||
/** additional classes added to the Tabs */ | ||
className: PropTypes.string, | ||
/** the index of the active tab */ | ||
activeKey: PropTypes.number, | ||
/** handel tab selection */ | ||
onSelect: PropTypes.func, | ||
/** Enables the filled tab list layout */ | ||
isFilled: PropTypes.bool, | ||
/** Enables Secondary Tab styling */ | ||
isSecondary: PropTypes.bool, | ||
/** Aria Label for the left Scroll Button */ | ||
leftScrollAriaLabel: PropTypes.string, | ||
/** Aria Label for the right Scroll Button */ | ||
rightScrollAriaLabel: PropTypes.string | ||
}; | ||
|
||
const defaultProps = { | ||
className: '', | ||
activeKey: 0, | ||
onSelect: () => undefined, | ||
isFilled: false, | ||
isSecondary: false, | ||
leftScrollAriaLabel: 'Scroll left', | ||
rightScrollAriaLabel: 'Scroll Right' | ||
}; | ||
|
||
class Tabs extends React.Component { | ||
static propTypes = propTypes; | ||
static defaultProps = defaultProps; | ||
|
||
state = { | ||
showLeftScrollButton: false, | ||
showRightScrollButton: false, | ||
highlightLeftScrollButton: false, | ||
highlightRightScrollButton: false | ||
}; | ||
|
||
id = getUniqueId(); | ||
tabList = React.createRef(); | ||
|
||
handleTabClick(event, eventKey) { | ||
this.props.onSelect(event, eventKey); | ||
} | ||
|
||
handleScrollButtons = () => { | ||
if (this.tabList.current) { | ||
const container = this.tabList.current; | ||
// get first element and check if it is in view | ||
const showLeftScrollButton = !isElementInView(container, container.firstChild, false); | ||
|
||
// get lase element and check if it is in view | ||
const showRightScrollButton = !isElementInView(container, container.lastChild, false); | ||
|
||
// determine if selected tab is out of view and apply styles | ||
let selectedTab; | ||
const childrenArr = Array.from(container.children); | ||
childrenArr.forEach(child => { | ||
const { className } = child; | ||
if (className.search('pf-m-current') > 0) { | ||
selectedTab = child; | ||
} | ||
}); | ||
|
||
const sideOutOfView = sideElementIsOutOfView(container, selectedTab); | ||
|
||
this.setState({ | ||
showLeftScrollButton, | ||
showRightScrollButton, | ||
highlightLeftScrollButton: (sideOutOfView === SIDE.LEFT || sideOutOfView === SIDE.BOTH) && showLeftScrollButton, | ||
highlightRightScrollButton: | ||
(sideOutOfView === SIDE.RIGHT || sideOutOfView === SIDE.BOTH) && showRightScrollButton | ||
}); | ||
} | ||
}; | ||
|
||
scrollLeft = () => { | ||
// find first Element that is fully in view on the left, then scroll to the element before it | ||
if (this.tabList.current) { | ||
const container = this.tabList.current; | ||
const childrenArr = Array.from(container.children); | ||
let firstElementInView; | ||
let lastElementOutOfView; | ||
let i; | ||
for (i = 0; i < childrenArr.length && !firstElementInView; i++) { | ||
if (isElementInView(container, childrenArr[i], false)) { | ||
firstElementInView = childrenArr[i]; | ||
lastElementOutOfView = childrenArr[i - 1]; | ||
} | ||
} | ||
lastElementOutOfView && (container.scrollLeft -= lastElementOutOfView.scrollWidth); | ||
} | ||
}; | ||
|
||
scrollRight = () => { | ||
// find last Element that is fully in view on the right, then scroll to the element after it | ||
if (this.tabList.current) { | ||
const container = this.tabList.current; | ||
const childrenArr = Array.from(container.children); | ||
let lastElementInView; | ||
let firstElementOutOfView; | ||
let i; | ||
for (i = childrenArr.length - 1; i >= 0 && !lastElementInView; i--) { | ||
if (isElementInView(container, childrenArr[i], false)) { | ||
lastElementInView = childrenArr[i]; | ||
firstElementOutOfView = childrenArr[i + 1]; | ||
} | ||
} | ||
firstElementOutOfView && (container.scrollLeft += firstElementOutOfView.scrollWidth); | ||
} | ||
}; | ||
|
||
componentDidMount() { | ||
window.addEventListener('resize', this.handleScrollButtons, false); | ||
|
||
// call the handle resize function to check if scroll buttons should be shown | ||
this.handleScrollButtons(); | ||
} | ||
|
||
componentWillUnmount() { | ||
document.removeEventListener('resize', this.handleScrollButtons, false); | ||
} | ||
|
||
render() { | ||
const { | ||
className, | ||
children, | ||
activeKey, | ||
isFilled, | ||
isSecondary, | ||
leftScrollAriaLabel, | ||
rightScrollAriaLabel, | ||
...props | ||
} = this.props; | ||
const { | ||
showLeftScrollButton, | ||
showRightScrollButton, | ||
highlightLeftScrollButton, | ||
highlightRightScrollButton | ||
} = this.state; | ||
return ( | ||
<React.Fragment> | ||
<div | ||
{...props} | ||
className={css( | ||
styles.tabs, | ||
isFilled && styles.modifiers.fill, | ||
isSecondary && styles.modifiers.tabsSecondary, | ||
showLeftScrollButton && styles.modifiers.start, | ||
showRightScrollButton && styles.modifiers.end, | ||
highlightLeftScrollButton && styles.modifiers.startCurrent, | ||
highlightRightScrollButton && styles.modifiers.endCurrent, | ||
className | ||
)} | ||
> | ||
{!isSecondary && ( | ||
<button | ||
className={css(styles.tabsScrollButton)} | ||
variant="plain" | ||
aria-label={leftScrollAriaLabel} | ||
onClick={this.scrollLeft} | ||
> | ||
<AngleLeftIcon /> | ||
</button> | ||
)} | ||
<ul className={css(styles.tabsList)} ref={this.tabList} onScroll={this.handleScrollButtons}> | ||
{children.map((child, index) => ( | ||
<li | ||
key={index} | ||
className={css( | ||
styles.tabsItem, | ||
child.props.eventKey === activeKey && styles.modifiers.current, | ||
className | ||
)} | ||
> | ||
<button | ||
className={css(styles.tabsButton)} | ||
onClick={event => this.handleTabClick(event, child.props.eventKey)} | ||
id={`pf-tab-${child.props.eventKey}-${child.props.id || this.id}`} | ||
aria-controls={`pf-tab-section-${child.props.eventKey}-${child.props.id || this.id}`} | ||
> | ||
{child.props.title} | ||
</button> | ||
</li> | ||
))} | ||
</ul> | ||
{!isSecondary && ( | ||
<button | ||
className={css(styles.tabsScrollButton)} | ||
variant="plain" | ||
aria-label={rightScrollAriaLabel} | ||
onClick={this.scrollRight} | ||
> | ||
<AngleRightIcon /> | ||
</button> | ||
)} | ||
</div> | ||
{children.map((child, index) => ( | ||
<section | ||
key={index} | ||
hidden={child.props.eventKey !== activeKey} | ||
className={css(child.props.className)} | ||
id={`pf-tab-section-${child.props.eventKey}-${child.props.id || this.id}`} | ||
aria-labelledby={`pf-tab-${child.props.eventKey}-${child.props.id || this.id}`} | ||
> | ||
{child.props.children} | ||
</section> | ||
))} | ||
</React.Fragment> | ||
); | ||
} | ||
} | ||
|
||
export default Tabs; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
children included in html props
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.
don't I need to override . it here since it 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.
You should import this and add this to the typescript integration app since it's more complicated component.