Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit responds to the first two points made by @zburke in the PR feedback: - Change CSS import to use local path - Split into separate components
- Loading branch information
Andrew Isherwood
committed
Feb 4, 2022
1 parent
f751e44
commit 5d9a980
Showing
7 changed files
with
185 additions
and
162 deletions.
There are no files selected for viewing
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,62 @@ | ||
import React, { useContext, useEffect, useRef } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
import { TabsContext } from './TabsContext'; | ||
|
||
import css from './Tabs.css'; | ||
|
||
const Tab = (props) => { | ||
const { | ||
children, | ||
index | ||
} = props; | ||
|
||
const thisTab = useRef(null); | ||
|
||
const { | ||
selectedTabIndex, | ||
setSelectedTabIndex | ||
} = useContext(TabsContext); | ||
|
||
// Ensure the correct tab has focus | ||
useEffect(() => { | ||
if (selectedTabIndex === index) { | ||
thisTab.current.focus(); | ||
} | ||
// Having index as a dep makes no sense, it's never | ||
// going to change | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [selectedTabIndex]); | ||
|
||
const activeStyle = selectedTabIndex === index ? css.primary : css.default; | ||
const finalStyles = [css.tab, activeStyle].join(' '); | ||
|
||
return ( | ||
// Keyboard based interactivity with the tabs is handled in TabList | ||
// so we don't need a onKey* handler here | ||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events | ||
<li | ||
ref={thisTab} | ||
tabIndex={selectedTabIndex === index ? 0 : -1} | ||
onClick={() => setSelectedTabIndex(index)} | ||
aria-selected={selectedTabIndex === index} | ||
aria-controls={`tabpanel-${index}`} | ||
className={finalStyles} | ||
id={`tab-${index}`} | ||
role="tab" | ||
> | ||
{children} | ||
</li> | ||
); | ||
}; | ||
|
||
Tab.propTypes = { | ||
children: PropTypes.oneOfType([ | ||
PropTypes.element, | ||
PropTypes.array, | ||
PropTypes.string | ||
]), | ||
index: PropTypes.number | ||
}; | ||
|
||
export default Tab; |
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,75 @@ | ||
import React, { useContext, cloneElement } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
import { TabsContext } from './TabsContext'; | ||
|
||
import css from './Tabs.css'; | ||
|
||
const TabList = (props) => { | ||
const { | ||
ariaLabel, | ||
children | ||
} = props; | ||
|
||
const { | ||
selectedTabIndex, | ||
setSelectedTabIndex | ||
} = useContext(TabsContext); | ||
|
||
// Add the index to each child, which will allow us to ensure the current | ||
// tab is styled correctly and has focus etc. | ||
const childrenArray = Array.isArray(children) ? children : [children]; | ||
const childrenWithIndex = childrenArray.map((child, index) => cloneElement(child, { index, key: index })); | ||
|
||
// Handle setting of the next index when navigating | ||
// by keyboard | ||
const calculateNextIndex = (action) => { | ||
if (action === 'increase') { | ||
const maxIndex = children.length - 1; | ||
return selectedTabIndex < maxIndex ? | ||
selectedTabIndex + 1 : | ||
selectedTabIndex; | ||
} | ||
if (action === 'decrease') { | ||
return selectedTabIndex > 0 ? | ||
selectedTabIndex - 1 : | ||
selectedTabIndex; | ||
} | ||
return selectedTabIndex; | ||
}; | ||
|
||
// Handle the right and left cursor keys for navigating | ||
// via keyboard. | ||
const handleKeyDown = (e) => { | ||
switch (e.keyCode) { | ||
case 39: // Right arrow | ||
setSelectedTabIndex(calculateNextIndex('increase')); | ||
break; | ||
case 37: // Left arrow | ||
setSelectedTabIndex(calculateNextIndex('decrease')); | ||
break; | ||
default: | ||
} | ||
}; | ||
|
||
return ( | ||
<ul | ||
onKeyDown={handleKeyDown} | ||
aria-label={ariaLabel} | ||
className={css.tabList} | ||
role="tablist" | ||
> | ||
{childrenWithIndex} | ||
</ul> | ||
); | ||
}; | ||
|
||
TabList.propTypes = { | ||
ariaLabel: PropTypes.string.isRequired, | ||
children: PropTypes.oneOfType([ | ||
PropTypes.element, | ||
PropTypes.array | ||
]) | ||
}; | ||
|
||
export default TabList; |
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,35 @@ | ||
import React, { useContext } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
import { TabsContext } from './TabsContext'; | ||
|
||
import css from './Tabs.css'; | ||
|
||
const TabPanel = (props) => { | ||
const { | ||
children, | ||
index | ||
} = props; | ||
|
||
const { selectedTabIndex } = useContext(TabsContext); | ||
|
||
return selectedTabIndex === index && ( | ||
<div | ||
tabIndex={selectedTabIndex === index ? 0 : -1} | ||
id={`tabpanel-${index}`} | ||
className={css.tabPanel} | ||
role="tabpanel" | ||
> | ||
{children} | ||
</div> | ||
); | ||
}; | ||
|
||
TabPanel.propTypes = { | ||
children: PropTypes.oneOfType([ | ||
PropTypes.element, | ||
PropTypes.string | ||
]), | ||
index: PropTypes.number | ||
}; | ||
export default TabPanel; |
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 |
---|---|---|
@@ -1,4 +1,4 @@ | ||
@import '@folio/stripes-components/lib/variables'; | ||
@import '../variables.css'; | ||
|
||
ul { | ||
margin: 0; | ||
|
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
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 |
---|---|---|
@@ -1 +1,4 @@ | ||
export { Tabs, TabList, Tab, TabPanel } from './Tabs'; | ||
export { default as Tabs } from './Tabs'; | ||
export { default as TabList } from './TabList'; | ||
export { default as Tab } from './Tab'; | ||
export { default as TabPanel } from './TabPanel'; |
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