diff --git a/src/components/subnavigation/subnavigation.jsx b/src/components/subnavigation/subnavigation.jsx
index e9d964975..b04ae38cb 100644
--- a/src/components/subnavigation/subnavigation.jsx
+++ b/src/components/subnavigation/subnavigation.jsx
@@ -20,6 +20,7 @@ const SubNavigation = props => (
'sub-nav-align-right': props.align === 'right'
}
)}
+ role={props.role}
>
{props.children}
@@ -27,6 +28,7 @@ const SubNavigation = props => (
SubNavigation.propTypes = {
align: PropTypes.string,
+ role: PropTypes.string,
children: PropTypes.node,
className: PropTypes.string
};
diff --git a/src/components/tabs/tabs.jsx b/src/components/tabs/tabs.jsx
index cc637ff56..07553ef13 100644
--- a/src/components/tabs/tabs.jsx
+++ b/src/components/tabs/tabs.jsx
@@ -1,5 +1,5 @@
-const classNames = require('classnames');
const PropTypes = require('prop-types');
+const {useRef} = require('react');
const React = require('react');
const SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
@@ -10,17 +10,94 @@ require('./tabs.scss');
* Container for a custom, horizontal list of navigation elements
* that can be displayed within a view or component.
*/
-const Tabs = props => (
-
-
- {props.children}
-
-
-);
+const Tabs = ({items, activeTabName}) => {
+ const tabElementRefs = useRef({});
+
+ const itemsRendered = items.map(({name, onTrigger, getContent}) => {
+ const isActive = name === activeTabName;
+
+ let tabRef;
+ if (tabElementRefs.current[name]) {
+ tabRef = tabElementRefs.current[name];
+ } else {
+ tabRef = React.createRef();
+ tabElementRefs.current[name] = tabRef;
+ }
+
+ return (
+
+ );
+ });
+
+ const handleKeyDown = event => {
+ if (!['ArrowLeft', 'ArrowRight', 'Home', 'End', 'Enter', ' '].includes(event.key)) {
+ return;
+ }
+ event.preventDefault();
+ const focusedIndex = Object.values(tabElementRefs.current)
+ .findIndex(tabElementRef =>
+ document.activeElement === tabElementRef.current
+ );
+ if (event.key === 'ArrowLeft') {
+ let nextIndex;
+ if (focusedIndex === 0) {
+ nextIndex = Object.values(tabElementRefs.current).length - 1;
+ } else {
+ nextIndex = focusedIndex - 1;
+ }
+ Object.values(tabElementRefs.current)[nextIndex].current.focus();
+ } else if (event.key === 'ArrowRight') {
+ let nextIndex;
+ if (focusedIndex === Object.values(tabElementRefs.current).length - 1) {
+ nextIndex = 0;
+ } else {
+ nextIndex = focusedIndex + 1;
+ }
+ Object.values(tabElementRefs.current)[nextIndex].current.focus();
+ } else if (event.key === 'Home') {
+ Object.values(tabElementRefs.current)[0].current.focus();
+ } else if (event.key === 'End') {
+ const lastTab = Object.values(tabElementRefs.current).length - 1;
+ Object.values(tabElementRefs.current)[lastTab].current.focus();
+ } else if (event.key === 'Enter' || event.key === ' ') {
+ items[focusedIndex].onTrigger();
+ }
+ };
+
+ return (
+
+
+ {itemsRendered}
+
+
+ );
+};
Tabs.propTypes = {
- children: PropTypes.node,
- className: PropTypes.string
+ items: PropTypes.arrayOf(
+ PropTypes.shape({
+ name: PropTypes.string.isRequired,
+ onTrigger: PropTypes.func.isRequired,
+ getContent: PropTypes.func.isRequired
+ })
+ ).isRequired,
+ activeTabName: PropTypes.string.isRequired
};
module.exports = Tabs;
diff --git a/src/components/tabs/tabs.scss b/src/components/tabs/tabs.scss
index a612c3f76..791e9723f 100644
--- a/src/components/tabs/tabs.scss
+++ b/src/components/tabs/tabs.scss
@@ -14,13 +14,14 @@
justify-content: center;
}
-.tabs li {
+.tabs button {
margin: 0;
border: 0;
border-radius: 0;
width: $cols2;
text-align: center;
color: $header-gray;
+ background-color: transparent;
&.active {
border-bottom: 3px solid $ui-aqua;
diff --git a/src/l10n.json b/src/l10n.json
index 41ce896c0..82ebe2b4b 100644
--- a/src/l10n.json
+++ b/src/l10n.json
@@ -74,6 +74,8 @@
"general.download": "Download",
"general.password": "Password",
"general.press": "Press",
+ "general.projectsSelected": "Projects Tab Selected",
+ "general.projectsNotS": "Projects",
"general.privacyPolicy": "Privacy Policy",
"general.projects": "Projects",
"general.profile": "Profile",
@@ -91,6 +93,8 @@
"general.startOver": "Start over",
"general.statistics": "Statistics",
"general.studios": "Studios",
+ "general.studiosSelected": "Studios Tab Selected",
+ "general.studiosNotS": "Studios",
"general.support": "Resources",
"general.ideas": "Ideas",
"general.tipsWindow": "Tips Window",
@@ -111,13 +115,21 @@
"general.seeAllComments": "See all comments",
"general.all": "All",
+ "general.allSelected": "All Selected",
"general.animations": "Animations",
+ "general.animationsSelected": "Animations Selected",
"general.art": "Art",
+ "general.artSelected": "Art Selected",
"general.games": "Games",
+ "general.gamesSelected": "Games Selected",
"general.music": "Music",
+ "general.musicSelected": "Music Selected",
"general.results": "Results",
+ "general.resultsSelected": "Results Selected",
"general.stories": "Stories",
+ "general.storiesSelected": "Stories Selected",
"general.tutorials": "Tutorials",
+ "general.tutorialsSelected": "Tutorials Selected",
"general.teacherAccounts": "Teacher Accounts",
diff --git a/src/views/explore/explore.jsx b/src/views/explore/explore.jsx
index 4bd84f2f9..7d4e9dd42 100644
--- a/src/views/explore/explore.jsx
+++ b/src/views/explore/explore.jsx
@@ -26,10 +26,8 @@ class Explore extends React.Component {
bindAll(this, [
'getExploreState',
'handleGetExploreMore',
- 'changeItemType',
'handleChangeSortMode',
- 'getBubble',
- 'getTab'
+ 'getBubble'
]);
this.state = this.getExploreState();
@@ -96,16 +94,7 @@ class Explore extends React.Component {
}
});
}
- changeItemType () {
- let newType;
- for (const t of this.state.acceptableTypes) {
- if (this.state.itemType !== t) {
- newType = t;
- break;
- }
- }
- window.location = `${window.location.origin}/explore/${newType}/${this.state.tab}/${this.state.mode}`;
- }
+
handleChangeSortMode (name, value) {
if (this.state.acceptableModes.indexOf(value) !== -1) {
window.location =
@@ -124,31 +113,7 @@ class Explore extends React.Component {
);
}
- getTab (type) {
- const classes = classNames({
- active: (this.state.itemType === type)
- });
- return (
-
-
- {this.state.itemType === type ? [
-
- ] : [
-
- ]}
-
-
-
- );
- }
+
render () {
return (
@@ -160,10 +125,63 @@ class Explore extends React.Component {
-
- {this.getTab('projects')}
- {this.getTab('studios')}
-
+ {
+ window.location = `${window.location.origin}/explore/projects/` +
+ `${this.state.category}/${this.state.mode}`;
+ },
+ getContent: isActive => (
+
+ {isActive ? (
+
+ ) : (
+
+ )
+ }
+
+
+ )
+ },
+ {
+ name: 'studios',
+ onTrigger: () => {
+ window.location = `${window.location.origin}/explore/studios/` +
+ `${this.state.category}/${this.state.mode}`;
+ },
+ getContent: isActive => (
+
+ {isActive ? (
+
+ ) : (
+
+ )
+ }
+
+
+ )
+ }
+ ]}
+ activeTabName={this.state.itemType}
+ />
{this.getBubble('all')}
diff --git a/src/views/search/search.jsx b/src/views/search/search.jsx
index c5d1d4c27..74d18f99f 100644
--- a/src/views/search/search.jsx
+++ b/src/views/search/search.jsx
@@ -31,8 +31,7 @@ class Search extends React.Component {
bindAll(this, [
'getSearchState',
'handleChangeSortMode',
- 'handleGetSearchMore',
- 'getTab'
+ 'handleGetSearchMore'
]);
this.state = this.getSearchState();
this.state.loaded = [];
@@ -151,38 +150,6 @@ class Search extends React.Component {
});
});
}
- getTab (type) {
- const termText = this.encodeSearchTerm();
- let targetUrl = `/search/${type}`;
- if (termText) {
- targetUrl += `?q=${termText}`;
- }
- let allTab = (
-
-
-
-
-
-
- );
- if (this.state.tab === type) {
- allTab = (
-
-
-
-
-
-
- );
- }
- return allTab;
- }
getProjectBox () {
const results = (
-
- {this.getTab('projects')}
- {this.getTab('studios')}
-
+ {
+ const termText = this.encodeSearchTerm();
+ let targetUrl = `/search/projects`;
+ if (termText) targetUrl += `?q=${termText}`;
+ window.location = targetUrl;
+ },
+ getContent: isActive => (
+
+ {isActive ? (
+
+ ) : (
+
+ )
+ }
+
+
+ )
+ },
+ {
+ name: 'studios',
+ onTrigger: () => {
+ const termText = this.encodeSearchTerm();
+ let targetUrl = `/search/studios`;
+ if (termText) targetUrl += `?q=${termText}`;
+ window.location = targetUrl;
+ },
+ getContent: isActive => (
+
+ {isActive ? (
+
+ ) : (
+
+ )
+ }
+
+
+ )
+ }
+ ]}
+ activeTabName={this.state.tab}
+ />