mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-23 07:38:07 -05:00
Explore and Search refactor
This commit is contained in:
parent
4d6b5bd2e9
commit
7ea0815428
5 changed files with 164 additions and 95 deletions
|
@ -20,6 +20,7 @@ const SubNavigation = props => (
|
|||
'sub-nav-align-right': props.align === 'right'
|
||||
}
|
||||
)}
|
||||
role={props.role}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
|
@ -27,6 +28,7 @@ const SubNavigation = props => (
|
|||
|
||||
SubNavigation.propTypes = {
|
||||
align: PropTypes.string,
|
||||
role: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
|
|
@ -1,56 +1,92 @@
|
|||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const { useRef } = require('react');
|
||||
const {useRef} = require('react');
|
||||
const React = require('react');
|
||||
|
||||
const SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
|
||||
|
||||
require('./tabs.scss');
|
||||
|
||||
const TabItem = ({ children, ...props }) => {
|
||||
const tabItemRef = useRef()
|
||||
|
||||
return <li ref={tabItemRef} {...props}>{children}</li>
|
||||
}
|
||||
|
||||
/*
|
||||
* Container for a custom, horizontal list of navigation elements
|
||||
* that can be displayed within a view or component.
|
||||
*/
|
||||
const Tabs = ({ items, activeTabName }) => {
|
||||
let activeIndex
|
||||
const itemsRendered = items.map(({ name, onTrigger, getContent }, index) => {
|
||||
const isActive = name === activeTabName
|
||||
activeIndex = index
|
||||
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 (
|
||||
<li
|
||||
<button
|
||||
role="tab"
|
||||
aria-selected={`${isActive ? 'true' : 'false'}`}
|
||||
className={`${isActive ? 'active' : ''}`}
|
||||
onClick={onTrigger}
|
||||
tabIndex={isActive ? 0 : -1}
|
||||
key={name}
|
||||
ref={tabRef}
|
||||
>
|
||||
{getContent(isActive)}
|
||||
</li>
|
||||
)
|
||||
})
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
const handleKeyDown = event => {
|
||||
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End', 'Enter', ' '].includes(event.key)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
event.preventDefault()
|
||||
console.log(
|
||||
'hi'
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="tab-background" onKeyDown={handleKeyDown}>
|
||||
<SubNavigation className="tabs">
|
||||
<div
|
||||
className="tab-background"
|
||||
onKeyDown={handleKeyDown}// eslint-disable-line
|
||||
>
|
||||
<SubNavigation
|
||||
role="tablist"
|
||||
className="tabs"
|
||||
>
|
||||
{itemsRendered}
|
||||
</SubNavigation>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
Tabs.propTypes = {
|
||||
|
@ -61,7 +97,7 @@ Tabs.propTypes = {
|
|||
getContent: PropTypes.func.isRequired
|
||||
})
|
||||
).isRequired,
|
||||
activeTabName: PropTypes.string.isRequired,
|
||||
activeTabName: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
module.exports = Tabs;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -26,7 +26,7 @@ class Explore extends React.Component {
|
|||
'getExploreState',
|
||||
'handleGetExploreMore',
|
||||
'handleChangeSortMode',
|
||||
'getBubble',
|
||||
'getBubble'
|
||||
]);
|
||||
|
||||
this.state = this.getExploreState();
|
||||
|
@ -128,22 +128,25 @@ class Explore extends React.Component {
|
|||
items={[
|
||||
{
|
||||
name: 'projects',
|
||||
onTrigger: () => {
|
||||
window.location = `${window.location.origin}/explore/projects/${this.state.category}/${this.state.mode}`;
|
||||
onTrigger: () => {
|
||||
window.location = `${window.location.origin}/explore/projects/` +
|
||||
`${this.state.category}/${this.state.mode}`;
|
||||
},
|
||||
getContent: (isActive) => (
|
||||
getContent: isActive => (
|
||||
<div>
|
||||
{isActive ? (
|
||||
<img
|
||||
className="tab-icon projects"
|
||||
src="/svgs/tabs/projects-active.svg"
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
className="tab-icon projects"
|
||||
src="/svgs/tabs/projects-inactive.svg"
|
||||
/>
|
||||
)
|
||||
<img
|
||||
className="tab-icon projects"
|
||||
src="/svgs/tabs/projects-active.svg"
|
||||
alt=""
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
className="tab-icon projects"
|
||||
src="/svgs/tabs/projects-inactive.svg"
|
||||
alt=""
|
||||
/>
|
||||
)
|
||||
}
|
||||
<FormattedMessage id="general.projects" />
|
||||
</div>
|
||||
|
@ -151,29 +154,32 @@ class Explore extends React.Component {
|
|||
},
|
||||
{
|
||||
name: 'studios',
|
||||
onTrigger: () => {
|
||||
window.location = `${window.location.origin}/explore/studios/${this.state.category}/${this.state.mode}`;
|
||||
onTrigger: () => {
|
||||
window.location = `${window.location.origin}/explore/studios/` +
|
||||
`${this.state.category}/${this.state.mode}`;
|
||||
},
|
||||
getContent: (isActive) => (
|
||||
getContent: isActive => (
|
||||
<div>
|
||||
{isActive ? (
|
||||
<img
|
||||
className="tab-icon studios"
|
||||
src="/svgs/tabs/studios-active.svg"
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
className="tab-icon studios"
|
||||
src="/svgs/tabs/studios-inactive.svg"
|
||||
/>
|
||||
)
|
||||
<img
|
||||
className="tab-icon studios"
|
||||
src="/svgs/tabs/studios-active.svg"
|
||||
alt=""
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
className="tab-icon studios"
|
||||
src="/svgs/tabs/studios-inactive.svg"
|
||||
alt=""
|
||||
/>
|
||||
)
|
||||
}
|
||||
<FormattedMessage id="general.studios" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
]}
|
||||
activeTabName={ this.state.itemType }
|
||||
activeTabName={this.state.itemType}
|
||||
/>
|
||||
<div className="sort-controls">
|
||||
<SubNavigation className="categories">
|
||||
|
|
|
@ -30,8 +30,7 @@ class Search extends React.Component {
|
|||
bindAll(this, [
|
||||
'getSearchState',
|
||||
'handleChangeSortMode',
|
||||
'handleGetSearchMore',
|
||||
'getTab'
|
||||
'handleGetSearchMore'
|
||||
]);
|
||||
this.state = this.getSearchState();
|
||||
this.state.loaded = [];
|
||||
|
@ -150,38 +149,6 @@ class Search extends React.Component {
|
|||
});
|
||||
});
|
||||
}
|
||||
getTab (type) {
|
||||
const termText = this.encodeSearchTerm();
|
||||
let targetUrl = `/search/${type}`;
|
||||
if (termText) {
|
||||
targetUrl += `?q=${termText}`;
|
||||
}
|
||||
let allTab = (
|
||||
<a href={targetUrl}>
|
||||
<li>
|
||||
<img
|
||||
className={`tab-icon ${type}`}
|
||||
src={`/svgs/tabs/${type}-inactive.svg`}
|
||||
/>
|
||||
<FormattedMessage id={`general.${type}`} />
|
||||
</li>
|
||||
</a>
|
||||
);
|
||||
if (this.state.tab === type) {
|
||||
allTab = (
|
||||
<a href={targetUrl}>
|
||||
<li className="active">
|
||||
<img
|
||||
className={`tab-icon ${type}`}
|
||||
src={`/svgs/tabs/${type}-active.svg`}
|
||||
/>
|
||||
<FormattedMessage id={`general.${type}`} />
|
||||
</li>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return allTab;
|
||||
}
|
||||
getProjectBox () {
|
||||
const results = (
|
||||
<Grid
|
||||
|
@ -227,10 +194,67 @@ class Search extends React.Component {
|
|||
</h1>
|
||||
</div>
|
||||
</TitleBanner>
|
||||
<Tabs>
|
||||
{this.getTab('projects')}
|
||||
{this.getTab('studios')}
|
||||
</Tabs>
|
||||
<Tabs
|
||||
items={[
|
||||
{
|
||||
name: 'projects',
|
||||
onTrigger: () => {
|
||||
const termText = this.encodeSearchTerm();
|
||||
let targetUrl = `/search/projects`;
|
||||
if (termText) targetUrl += `?q=${termText}`;
|
||||
window.location = targetUrl;
|
||||
},
|
||||
getContent: isActive => (
|
||||
<div>
|
||||
{isActive ? (
|
||||
<img
|
||||
className="tab-icon projects"
|
||||
src="/svgs/tabs/projects-active.svg"
|
||||
alt=""
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
className="tab-icon projects"
|
||||
src="/svgs/tabs/projects-inactive.svg"
|
||||
alt=""
|
||||
/>
|
||||
)
|
||||
}
|
||||
<FormattedMessage id="general.projects" />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'studios',
|
||||
onTrigger: () => {
|
||||
const termText = this.encodeSearchTerm();
|
||||
let targetUrl = `/search/studios`;
|
||||
if (termText) targetUrl += `?q=${termText}`;
|
||||
window.location = targetUrl;
|
||||
},
|
||||
getContent: isActive => (
|
||||
<div>
|
||||
{isActive ? (
|
||||
<img
|
||||
className="tab-icon studios"
|
||||
src="/svgs/tabs/studios-active.svg"
|
||||
alt=""
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
className="tab-icon studios"
|
||||
src="/svgs/tabs/studios-inactive.svg"
|
||||
alt=""
|
||||
/>
|
||||
)
|
||||
}
|
||||
<FormattedMessage id="general.studios" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
]}
|
||||
activeTabName={this.state.tab}
|
||||
/>
|
||||
<div className="sort-controls">
|
||||
<Form className="sort-mode">
|
||||
<Select
|
||||
|
|
Loading…
Reference in a new issue