Explore and Search refactor

This commit is contained in:
Paul Clue 2022-12-13 19:13:03 -05:00
parent 4d6b5bd2e9
commit 7ea0815428
5 changed files with 164 additions and 95 deletions

View file

@ -20,6 +20,7 @@ const SubNavigation = props => (
'sub-nav-align-right': props.align === 'right' 'sub-nav-align-right': props.align === 'right'
} }
)} )}
role={props.role}
> >
{props.children} {props.children}
</div> </div>
@ -27,6 +28,7 @@ const SubNavigation = props => (
SubNavigation.propTypes = { SubNavigation.propTypes = {
align: PropTypes.string, align: PropTypes.string,
role: PropTypes.string,
children: PropTypes.node, children: PropTypes.node,
className: PropTypes.string className: PropTypes.string
}; };

View file

@ -1,56 +1,92 @@
const classNames = require('classnames');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const { useRef } = require('react'); const {useRef} = require('react');
const React = require('react'); const React = require('react');
const SubNavigation = require('../../components/subnavigation/subnavigation.jsx'); const SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
require('./tabs.scss'); 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 * Container for a custom, horizontal list of navigation elements
* that can be displayed within a view or component. * that can be displayed within a view or component.
*/ */
const Tabs = ({ items, activeTabName }) => { const Tabs = ({items, activeTabName}) => {
let activeIndex const tabElementRefs = useRef({});
const itemsRendered = items.map(({ name, onTrigger, getContent }, index) => {
const isActive = name === activeTabName const itemsRendered = items.map(({name, onTrigger, getContent}) => {
activeIndex = index const isActive = name === activeTabName;
let tabRef;
if (tabElementRefs.current[name]) {
tabRef = tabElementRefs.current[name];
} else {
tabRef = React.createRef();
tabElementRefs.current[name] = tabRef;
}
return ( return (
<li <button
role="tab"
aria-selected={`${isActive ? 'true' : 'false'}`}
className={`${isActive ? 'active' : ''}`} className={`${isActive ? 'active' : ''}`}
onClick={onTrigger} onClick={onTrigger}
tabIndex={isActive ? 0 : -1} tabIndex={isActive ? 0 : -1}
key={name} key={name}
ref={tabRef}
> >
{getContent(isActive)} {getContent(isActive)}
</li> </button>
) );
}) });
const handleKeyDown = event => { const handleKeyDown = event => {
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End', 'Enter', ' '].includes(event.key)) { if (!['ArrowLeft', 'ArrowRight', 'Home', 'End', 'Enter', ' '].includes(event.key)) {
return return;
} }
event.preventDefault() event.preventDefault();
console.log( const focusedIndex = Object.values(tabElementRefs.current)
'hi' .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 ( return (
<div className="tab-background" onKeyDown={handleKeyDown}> <div
<SubNavigation className="tabs"> className="tab-background"
onKeyDown={handleKeyDown}// eslint-disable-line
>
<SubNavigation
role="tablist"
className="tabs"
>
{itemsRendered} {itemsRendered}
</SubNavigation> </SubNavigation>
</div> </div>
) );
}; };
Tabs.propTypes = { Tabs.propTypes = {
@ -61,7 +97,7 @@ Tabs.propTypes = {
getContent: PropTypes.func.isRequired getContent: PropTypes.func.isRequired
}) })
).isRequired, ).isRequired,
activeTabName: PropTypes.string.isRequired, activeTabName: PropTypes.string.isRequired
}; };
module.exports = Tabs; module.exports = Tabs;

View file

@ -14,13 +14,14 @@
justify-content: center; justify-content: center;
} }
.tabs li { .tabs button {
margin: 0; margin: 0;
border: 0; border: 0;
border-radius: 0; border-radius: 0;
width: $cols2; width: $cols2;
text-align: center; text-align: center;
color: $header-gray; color: $header-gray;
background-color: transparent;
&.active { &.active {
border-bottom: 3px solid $ui-aqua; border-bottom: 3px solid $ui-aqua;

View file

@ -26,7 +26,7 @@ class Explore extends React.Component {
'getExploreState', 'getExploreState',
'handleGetExploreMore', 'handleGetExploreMore',
'handleChangeSortMode', 'handleChangeSortMode',
'getBubble', 'getBubble'
]); ]);
this.state = this.getExploreState(); this.state = this.getExploreState();
@ -129,21 +129,24 @@ class Explore extends React.Component {
{ {
name: 'projects', name: 'projects',
onTrigger: () => { onTrigger: () => {
window.location = `${window.location.origin}/explore/projects/${this.state.category}/${this.state.mode}`; window.location = `${window.location.origin}/explore/projects/` +
`${this.state.category}/${this.state.mode}`;
}, },
getContent: (isActive) => ( getContent: isActive => (
<div> <div>
{isActive ? ( {isActive ? (
<img <img
className="tab-icon projects" className="tab-icon projects"
src="/svgs/tabs/projects-active.svg" src="/svgs/tabs/projects-active.svg"
/> alt=""
) : ( />
<img ) : (
className="tab-icon projects" <img
src="/svgs/tabs/projects-inactive.svg" className="tab-icon projects"
/> src="/svgs/tabs/projects-inactive.svg"
) alt=""
/>
)
} }
<FormattedMessage id="general.projects" /> <FormattedMessage id="general.projects" />
</div> </div>
@ -152,28 +155,31 @@ class Explore extends React.Component {
{ {
name: 'studios', name: 'studios',
onTrigger: () => { onTrigger: () => {
window.location = `${window.location.origin}/explore/studios/${this.state.category}/${this.state.mode}`; window.location = `${window.location.origin}/explore/studios/` +
`${this.state.category}/${this.state.mode}`;
}, },
getContent: (isActive) => ( getContent: isActive => (
<div> <div>
{isActive ? ( {isActive ? (
<img <img
className="tab-icon studios" className="tab-icon studios"
src="/svgs/tabs/studios-active.svg" src="/svgs/tabs/studios-active.svg"
/> alt=""
) : ( />
<img ) : (
className="tab-icon studios" <img
src="/svgs/tabs/studios-inactive.svg" className="tab-icon studios"
/> src="/svgs/tabs/studios-inactive.svg"
) alt=""
/>
)
} }
<FormattedMessage id="general.studios" /> <FormattedMessage id="general.studios" />
</div> </div>
) )
} }
]} ]}
activeTabName={ this.state.itemType } activeTabName={this.state.itemType}
/> />
<div className="sort-controls"> <div className="sort-controls">
<SubNavigation className="categories"> <SubNavigation className="categories">

View file

@ -30,8 +30,7 @@ class Search extends React.Component {
bindAll(this, [ bindAll(this, [
'getSearchState', 'getSearchState',
'handleChangeSortMode', 'handleChangeSortMode',
'handleGetSearchMore', 'handleGetSearchMore'
'getTab'
]); ]);
this.state = this.getSearchState(); this.state = this.getSearchState();
this.state.loaded = []; 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 () { getProjectBox () {
const results = ( const results = (
<Grid <Grid
@ -227,10 +194,67 @@ class Search extends React.Component {
</h1> </h1>
</div> </div>
</TitleBanner> </TitleBanner>
<Tabs> <Tabs
{this.getTab('projects')} items={[
{this.getTab('studios')} {
</Tabs> 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"> <div className="sort-controls">
<Form className="sort-mode"> <Form className="sort-mode">
<Select <Select