Skip to content

Commit

Permalink
Refactoring based on review
Browse files Browse the repository at this point in the history
  • Loading branch information
scissorsneedfoodtoo committed Sep 28, 2019
1 parent 42814ac commit 9c3b842
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 160 deletions.
4 changes: 2 additions & 2 deletions client/src/components/layouts/global.css
Expand Up @@ -173,8 +173,8 @@ a:focus {
.btn-primary:active.focus,
.btn-primary.active.focus,
.open > .dropdown-toggle.btn-primary.focus {
background-color: var(--secondary-color) !important;
color: var(--secondary-background) !important;
background-color: var(--secondary-color);
color: var(--secondary-background);
}

.btn.disabled,
Expand Down
112 changes: 38 additions & 74 deletions client/src/components/search/searchBar/SearchBar.js
@@ -1,5 +1,4 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
Expand All @@ -19,6 +18,9 @@ import SearchHits from './SearchHits';
import './searchbar-base.css';
import './searchbar.css';

// Configure react-hotkeys to work with the searchbar
configure({ ignoreTags: ['select', 'textarea'] });

const propTypes = {
isDropdownEnabled: PropTypes.bool,
isSearchFocused: PropTypes.bool,
Expand Down Expand Up @@ -51,10 +53,10 @@ class SearchBar extends Component {
this.searchBarRef = React.createRef();
this.handleChange = this.handleChange.bind(this);
this.handleSearch = this.handleSearch.bind(this);
this.handleHits = this.handleHits.bind(this);
this.handleMouseEnter = this.handleMouseEnter.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.handleFocus = this.handleFocus.bind(this);
// this.handleResize = this.handleResize.bind(this);
this.state = {
hitsLength: 0,
index: -1,
Expand All @@ -65,21 +67,9 @@ class SearchBar extends Component {
componentDidMount() {
const searchInput = document.querySelector('.ais-SearchBox-input');
searchInput.id = 'fcc_instantsearch';

document.addEventListener('click', this.handleFocus);
}

componentDidUpdate() {
const { hitsNode, index } = this.state;
if (hitsNode) {
hitsNode.forEach(hit => hit.classList.add('unHighlighted'));
if (index >= 0) {
hitsNode[index].classList.remove('unHighlighted');
hitsNode[index].classList.add('highlighted');
}
}
}

componentWillUnmount() {
document.removeEventListener('click', this.handleFocus);
}
Expand All @@ -89,8 +79,17 @@ class SearchBar extends Component {
if (!isSearchFocused) {
toggleSearchFocused(true);
}
// Reset if user updates query
this.setState({ index: -1 });

this.setState(() => {
const hitsList = document.querySelector('.ais-Hits-list');
const hitsListItems = hitsList ? hitsList.childNodes : [];

return {
index: -1,
hitsLength: hitsListItems.length,
hitsNode: hitsListItems
};
});
}

handleFocus(e) {
Expand All @@ -111,9 +110,10 @@ class SearchBar extends Component {
// Disable the search dropdown
toggleSearchDropdown(false);

const selectedHit = hitsNode[index];
const hitLinks = Array.from(hitsNode).map(node => node.children[0]);
const selectedHit = hitLinks[index];
if (selectedHit) {
// Redirect to hit selected by arrow keys
// Redirect to hit / footer selected by arrow keys
return window.location.assign(selectedHit.href);
} else if (!query) {
// Set query to value in search bar if enter is pressed
Expand All @@ -125,39 +125,23 @@ class SearchBar extends Component {
// return navigate('/search');

// Temporary redirect to News search results page
// when search input submitted
return window.location.assign(
`https://freecodecamp.org/news/search/?query=${query}`
`https://freecodecamp.org/news/search/?query=${encodeURIComponent(query)}`
);
}

handleHits() {
const { isDropdownEnabled, isSearchFocused } = this.props;
if (isDropdownEnabled && isSearchFocused) {
const searchBar = ReactDOM.findDOMNode(this);
const currentHitsNode = searchBar.querySelectorAll(
'.fcc_suggestion_item'
);

// Reset index if the length of hits
// suddenly changes because the dropdown is open
// while the window height moves above / below 768 px
this.setState(prevState => ({
index:
prevState.hitsLength !== currentHitsNode.length
? -1
: prevState.index,
hitsLength: currentHitsNode.length,
hitsNode: currentHitsNode
}));
}
}

handleMouseEnter(e) {
const { hitsNode } = this.state;
const hoveredIndex = Array.from(hitsNode).indexOf(e.currentTarget);
e.persist();
const currentTargetText = e.currentTarget.innerText;

this.setState({
index: hoveredIndex
this.setState(({ hitsNode }) => {
const hitText = Array.from(hitsNode).map(
node => node.children[0].innerText
);
const hoveredIndex = hitText.indexOf(currentTargetText);

return { index: hoveredIndex };
});
}

Expand All @@ -172,49 +156,30 @@ class SearchBar extends Component {
INDEX_DOWN: ['down']
};

handlers = {
keyHandlers = {
INDEX_UP: e => {
const { index, hitsLength } = this.state;
e.preventDefault();

if (index === -1) {
this.setState({
index: hitsLength - 1
});
} else {
this.setState(prevState => ({
index: prevState.index - 1
}));
}
this.setState(({ index, hitsLength }) => ({
index: index === -1 ? hitsLength - 1 : index - 1
}));
},
INDEX_DOWN: e => {
const { index, hitsLength } = this.state;
e.preventDefault();

if (index === hitsLength - 1) {
this.setState({
index: -1
});
} else {
this.setState(prevState => ({
index: prevState.index + 1
}));
}
this.setState(({ index, hitsLength }) => ({
index: index === hitsLength - 1 ? -1 : index + 1
}));
}
};

render() {
const { isDropdownEnabled, isSearchFocused } = this.props;
// Allow react-hotkeys to work on the searchbar
configure({ ignoreTags: ['select', 'textarea'] });

return (
<div
className='fcc_searchBar'
data-testid='fcc_searchBar'
ref={this.searchBarRef}
>
<HotKeys handlers={this.handlers} keyMap={this.keyMap}>
<HotKeys handlers={this.keyHandlers} keyMap={this.keyMap}>
<div className='fcc_search_wrapper'>
<label className='fcc_sr_only' htmlFor='fcc_instantsearch'>
Search
Expand All @@ -229,10 +194,9 @@ class SearchBar extends Component {
/>
{isDropdownEnabled && isSearchFocused && (
<SearchHits
handleHits={this.handleHits}
handleMouseEnter={this.handleMouseEnter}
handleMouseLeave={this.handleMouseLeave}
handleSubmit={this.handleSearch}
selectedIndex={this.state.index}
/>
)}
</div>
Expand Down
36 changes: 14 additions & 22 deletions client/src/components/search/searchBar/SearchHits.js
Expand Up @@ -6,44 +6,43 @@ import Suggestion from './SearchSuggestion';
const CustomHits = connectHits(
({
hits,
currentRefinement,
handleSubmit,
handleHits,
searchQuery,
handleMouseEnter,
handleMouseLeave
handleMouseLeave,
selectedIndex
}) => {
const footer = [
{
objectID: `default-hit-${currentRefinement}`,
query: currentRefinement,
objectID: `default-hit-${searchQuery}`,
query: searchQuery,
_highlightResult: {
query: {
value: `
See all results for
<ais-highlight-0000000000>
${currentRefinement}
${searchQuery}
</ais-highlight-0000000000>
`
}
}
}
];
const allHits = hits.filter((_, i) => i < 8).concat(footer);
handleHits();
const allHits = hits.slice(0, 8).concat(footer);

return (
<div className='ais-Hits'>
<ul className='ais-Hits-list'>
{allHits.map(hit => (
{allHits.map((hit, i) => (
<li
className='ais-Hits-item'
className={
i === selectedIndex ? 'ais-Hits-item selected' : 'ais-Hits-item'
}
data-fccobjectid={hit.objectID}
key={hit.objectID}
>
<Suggestion
handleMouseEnter={handleMouseEnter}
handleMouseLeave={handleMouseLeave}
handleSubmit={handleSubmit}
hit={hit}
/>
</li>
Expand All @@ -55,20 +54,13 @@ const CustomHits = connectHits(
);

const SearchHits = connectStateResults(
({
handleSubmit,
handleHits,
searchState,
handleMouseEnter,
handleMouseLeave
}) => {
({ searchState, handleMouseEnter, handleMouseLeave, selectedIndex }) => {
return isEmpty(searchState) || !searchState.query ? null : (
<CustomHits
currentRefinement={searchState.query}
handleHits={handleHits}
handleMouseEnter={handleMouseEnter}
handleMouseLeave={handleMouseLeave}
handleSubmit={handleSubmit}
searchQuery={searchState.query}
selectedIndex={selectedIndex}
/>
);
}
Expand Down
13 changes: 4 additions & 9 deletions client/src/components/search/searchBar/SearchSuggestion.js
Expand Up @@ -3,12 +3,7 @@ import PropTypes from 'prop-types';
import { Highlight } from 'react-instantsearch-dom';
import { isEmpty } from 'lodash';

const Suggestion = ({
handleSubmit,
hit,
handleMouseEnter,
handleMouseLeave
}) => {
const Suggestion = ({ hit, handleMouseEnter, handleMouseLeave }) => {
const dropdownFooter = hit.objectID.includes('default-hit-');
return isEmpty(hit) || isEmpty(hit.objectID) ? null : (
<a
Expand All @@ -19,10 +14,11 @@ const Suggestion = ({
}
href={
dropdownFooter
? `https://freecodecamp.org/news/search/?query=${hit.query}`
? `https://freecodecamp.org/news/search/?query=${encodeURIComponent(
hit.query
)}`
: hit.url
}
onClick={e => (dropdownFooter ? handleSubmit(e, hit.query) : '')}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
Expand All @@ -40,7 +36,6 @@ const Suggestion = ({
Suggestion.propTypes = {
handleMouseEnter: PropTypes.func.isRequired,
handleMouseLeave: PropTypes.func.isRequired,
handleSubmit: PropTypes.func.isRequired,
hit: PropTypes.object
};

Expand Down
3 changes: 2 additions & 1 deletion client/src/components/search/searchBar/searchbar-base.css
Expand Up @@ -618,7 +618,7 @@ a[class^='ais-'] {
-moz-appearance: none;
appearance: none;
position: absolute;
z-index: 1;
z-index: 100;
width: 20px;
height: 20px;
top: 50%;
Expand All @@ -628,6 +628,7 @@ a[class^='ais-'] {
}
.ais-SearchBox-submit {
left: 0.3rem;
top: 57%;
}
.ais-SearchBox-reset {
right: 0.3rem;
Expand Down

0 comments on commit 9c3b842

Please sign in to comment.