fix(search): update search bar index and styling (#36656)

* Adjust search bar to only show hits for News
* Dropdown links working. Working on basic pagination
* Links to articles and temporarily routing to the News search result page working
* Fix colors for search bar and drop down
* Added Lato 300 font weight. Search bar now looks and works like it does on News.
* feat:update styles
* Update client/src/components/search/searchBar/SearchBar.js

Co-Authored-By: Ahmad Abdolsaheb <ahmad.abdolsaheb@gmail.com>

* Added Lato 300 font weight. Search bar now looks and works like it does on News.


Co-authored-by: Ahmad Abdolsaheb <ahmad.abdolsaheb@gmail.com>
This commit is contained in:
Kristofer Koishigawa
2019-08-28 15:52:19 +09:00
committed by mrugesh
parent 07b38d02b3
commit a03602a1d4
10 changed files with 111 additions and 98 deletions

View File

@ -126,7 +126,7 @@ module.exports = {
{ {
resolve: 'gatsby-plugin-google-fonts', resolve: 'gatsby-plugin-google-fonts',
options: { options: {
fonts: ['Lato:400,400i,500', 'Roboto Mono:400,700'] fonts: ['Lato:300,400,400i,500,700', 'Roboto Mono:400,700']
} }
}, },
'gatsby-plugin-sitemap', 'gatsby-plugin-sitemap',

View File

@ -15,7 +15,7 @@ import {
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
const DEBOUNCE_TIME = 400; const DEBOUNCE_TIME = 100;
const propTypes = { const propTypes = {
children: PropTypes.any, children: PropTypes.any,
@ -120,11 +120,11 @@ class InstantSearchRoot extends Component {
<InstantSearch <InstantSearch
apiKey='4318af87aa3ce128708f1153556c6108' apiKey='4318af87aa3ce128708f1153556c6108'
appId='QMJYL5WYTI' appId='QMJYL5WYTI'
indexName='query_suggestions' indexName='news'
onSearchStateChange={this.onSearchStateChange} onSearchStateChange={this.onSearchStateChange}
searchState={{ query }} searchState={{ query }}
> >
<Configure hitsPerPage={8} /> <Configure hitsPerPage={15} />
{this.props.children} {this.props.children}
</InstantSearch> </InstantSearch>
); );

View File

@ -6,7 +6,7 @@ export const ns = 'search';
const initialState = { const initialState = {
query: '', query: '',
indexName: 'query_suggestions', indexName: 'news',
isSearchDropdownEnabled: true, isSearchDropdownEnabled: true,
isSearchBarFocused: false isSearchBarFocused: false
}; };

View File

@ -4,7 +4,6 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { SearchBox } from 'react-instantsearch-dom'; import { SearchBox } from 'react-instantsearch-dom';
import { navigate } from 'gatsby';
import { import {
isSearchDropdownEnabledSelector, isSearchDropdownEnabledSelector,
@ -41,7 +40,7 @@ const mapDispatchToProps = dispatch =>
dispatch dispatch
); );
const placeholder = 'Search 8,000+ lessons, articles, and videos'; const placeholder = 'Search 5,000+ tutorials';
class SearchBar extends Component { class SearchBar extends Component {
constructor(props) { constructor(props) {
@ -85,7 +84,13 @@ class SearchBar extends Component {
if (query) { if (query) {
updateSearchQuery(query); updateSearchQuery(query);
} }
return navigate('/search'); // For Learn search results page
// return navigate('/search');
// Temporary redirect to News search results page
return window.location.assign(
`https://freecodecamp.org/news/search/?query=${query}`
);
} }
render() { render() {

View File

@ -4,16 +4,19 @@ import isEmpty from 'lodash/isEmpty';
import Suggestion from './SearchSuggestion'; import Suggestion from './SearchSuggestion';
const CustomHits = connectHits(({ hits, currentRefinement, handleSubmit }) => { const CustomHits = connectHits(({ hits, currentRefinement, handleSubmit }) => {
const shortenedHits = hits.filter((hit, i) => i < 8);
const defaultHit = [ const defaultHit = [
{ {
objectID: `default-hit-${currentRefinement}`, objectID: `default-hit-${currentRefinement}`,
query: currentRefinement, query: currentRefinement,
_highlightResult: { _highlightResult: {
query: { query: {
value: value: `
'Search for "<ais-highlight-0000000000>' + See all results for
currentRefinement + <ais-highlight-0000000000>
'</ais-highlight-0000000000>"' ${currentRefinement}
</ais-highlight-0000000000>
`
} }
} }
} }
@ -21,7 +24,7 @@ const CustomHits = connectHits(({ hits, currentRefinement, handleSubmit }) => {
return ( return (
<div className='ais-Hits'> <div className='ais-Hits'>
<ul className='ais-Hits-list'> <ul className='ais-Hits-list'>
{defaultHit.concat(hits).map(hit => ( {shortenedHits.concat(defaultHit).map(hit => (
<li <li
className='ais-Hits-item' className='ais-Hits-item'
data-fccobjectid={hit.objectID} data-fccobjectid={hit.objectID}

View File

@ -4,17 +4,22 @@ import { Highlight } from 'react-instantsearch-dom';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
const Suggestion = ({ handleSubmit, hit }) => { const Suggestion = ({ handleSubmit, hit }) => {
const dropdownFooter = hit.objectID.includes('default-hit-');
return isEmpty(hit) || isEmpty(hit.objectID) ? null : ( return isEmpty(hit) || isEmpty(hit.objectID) ? null : (
<a <a
className='fcc_suggestion_item' className={
href='/search' dropdownFooter
onClick={e => handleSubmit(e, hit.query)} ? 'fcc_suggestion_footer fcc_suggestion_item'
: 'fcc_suggestion_item'
}
href={hit.url}
onClick={e => (dropdownFooter ? handleSubmit(e, hit.query) : '')}
> >
<span className='hit-name'> <span className='hit-name'>
{hit.objectID.includes('default-hit-') ? ( {dropdownFooter ? (
<Highlight attribute='query' hit={hit} tagName='strong' /> <Highlight attribute='query' hit={hit} tagName='strong' />
) : ( ) : (
<Highlight attribute='query' hit={hit} /> <Highlight attribute='title' hit={hit} />
)} )}
</span> </span>
</a> </a>

View File

@ -420,9 +420,6 @@ a[class^='ais-'] {
margin-left: 1rem; margin-left: 1rem;
padding: 1rem; padding: 1rem;
width: calc(25% - 1rem); width: calc(25% - 1rem);
border: 1px solid #c4c8d8;
-webkit-box-shadow: 0 2px 5px 0px #e3e5ec;
box-shadow: 0 2px 5px 0px #e3e5ec;
} }
.ais-Panel-body .ais-InfiniteHits-item, .ais-Panel-body .ais-InfiniteHits-item,
.ais-Panel-body .ais-InfiniteResults-item, .ais-Panel-body .ais-InfiniteResults-item,
@ -596,7 +593,6 @@ a[class^='ais-'] {
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none; -moz-appearance: none;
appearance: none; appearance: none;
padding: 0.3rem 1.7rem;
width: 100%; width: 100%;
position: relative; position: relative;
background-color: #fff; background-color: #fff;
@ -604,16 +600,16 @@ a[class^='ais-'] {
border-radius: 0px; border-radius: 0px;
} }
.ais-SearchBox-input::-webkit-input-placeholder { .ais-SearchBox-input::-webkit-input-placeholder {
color: #a5aed1; color: var(--gray-15);
} }
.ais-SearchBox-input::-moz-placeholder { .ais-SearchBox-input::-moz-placeholder {
color: #a5aed1; color: var(--gray-15);
} }
.ais-SearchBox-input:-ms-input-placeholder { .ais-SearchBox-input:-ms-input-placeholder {
color: #a5aed1; color: var(--gray-15);
} }
.ais-SearchBox-input:-moz-placeholder { .ais-SearchBox-input:-moz-placeholder {
color: #a5aed1; color: var(--gray-15);
} }
.ais-SearchBox-submit, .ais-SearchBox-submit,
.ais-SearchBox-reset, .ais-SearchBox-reset,
@ -646,8 +642,9 @@ a[class^='ais-'] {
transform: translateX(-50%) translateY(-50%); transform: translateX(-50%) translateY(-50%);
} }
.ais-SearchBox-submitIcon path, .ais-SearchBox-submitIcon path,
.ais-SearchBox-resetIcon path { .ais-SearchBox-resetIcon path,
fill: #495588; .ais-SearchBox-loadingIndicator {
fill: var(--gray-15);
} }
.ais-SearchBox-submitIcon { .ais-SearchBox-submitIcon {
width: 14px; width: 14px;
@ -661,13 +658,8 @@ a[class^='ais-'] {
width: 16px; width: 16px;
height: 16px; height: 16px;
} }
.ais-SearchBox-input {
padding-left: 25px;
font-size: 14px;
}
.ais-SearchBox-submitIcon > path { .ais-SearchBox-submitIcon > path {
fill: #006400; fill: var(--gray-15);
} }
.ais-Hits { .ais-Hits {
position: absolute; position: absolute;
@ -679,17 +671,12 @@ a[class^='ais-'] {
margin: 0; margin: 0;
padding: 0; padding: 0;
border: 0; border: 0;
-webkit-box-shadow: none;
box-shadow: none;
} }
.ais-Hits-list { .ais-Hits-list {
margin: 0; margin: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
border: 1px solid #c4c8d8;
-webkit-box-shadow: 0 2px 5px 0px #e3e5ec;
box-shadow: 0 2px 5px 0px #e3e5ec;
} }
strong.ais-Highlight-highlighted { strong.ais-Highlight-highlighted {
background-color: transparent; background-color: transparent;

View File

@ -2,11 +2,28 @@
flex-grow: 1; flex-grow: 1;
padding: 0 10px; padding: 0 10px;
max-height: 33px; max-height: 33px;
font-family: 'Lato', sans-serif;
}
.fcc_searchBar strong,
.fcc_searchBar a:hover {
color: var(--gray-00);
}
.ais-SearchBox-submit,
.ais-SearchBox-reset {
display: none;
}
.ais-SearchBox-input {
padding: 1px 10px;
font-size: 18px;
} }
.fcc_searchBar .ais-SearchBox-input, .fcc_searchBar .ais-SearchBox-input,
.fcc_searchBar .ais-Hits { .fcc_searchBar .ais-Hits {
background-color: var(--quaternary-background); background-color: var(--gray-75);
color: var(--gray-00);
} }
.fcc_searchBar .ais-SearchBox-form { .fcc_searchBar .ais-SearchBox-form {
@ -49,26 +66,31 @@
font-weight: bold; font-weight: bold;
} }
.fcc_searchBar .fcc_suggestion_item { .ais-Highlight-nonHighlighted {
padding: 10px; font-weight: 300;
} }
.fcc_hits_wrapper { .fcc_hits_wrapper {
display: flex; display: flex;
justify-content: center; justify-content: center;
} }
.fcc_suggestion_item { .fcc_suggestion_item {
display: block; display: block;
padding: 1rem; padding: 8px;
color: #333; color: var(--gray-00);
text-decoration: none;
} }
.fcc_suggestion_item [class^='ais-'] { .fcc_suggestion_item [class^='ais-'] {
font-size: 17px; font-size: 17px;
} }
.fcc_suggestion_item:hover { .fcc_suggestion_item:hover {
background-color: rgba(0, 100, 0, 0.4); background-color: var(--blue-dark);
color: white;
cursor: pointer; cursor: pointer;
} }
.fcc_sr_only { .fcc_sr_only {
position: absolute; position: absolute;
width: 1px; width: 1px;
@ -79,3 +101,29 @@
clip: rect(0, 0, 0, 0); clip: rect(0, 0, 0, 0);
border: 0; border: 0;
} }
/* Dropdown footer */
.fcc_suggestion_footer {
border-top: 1.5px solid var(--gray-00);
}
.fcc_suggestion_footer .hit-name .ais-Highlight .ais-Highlight-nonHighlighted {
font-weight: bold;
}
/* Show only the first 5 hits on mobile */
.ais-Hits-list .ais-Hits-item:nth-child(n + 6) {
display: none;
}
/* Ensure the dropdown footer is always visible */
.ais-Hits-list .ais-Hits-item:nth-child(9) {
display: block;
}
@media (min-width: 767px) and (min-height: 768px) {
/* Show hits 6-8 on desktop and some tablets */
.ais-Hits-list .ais-Hits-item:nth-child(n + 6) {
display: block;
}
}

View File

@ -9,58 +9,29 @@ import { isEmpty } from 'lodash';
import EmptySearch from './EmptySearch'; import EmptySearch from './EmptySearch';
import NoResults from './NoResults'; import NoResults from './NoResults';
import { homeLocation } from '../../../../config/env.json';
import './search-page-hits.css'; import './search-page-hits.css';
const indexMap = {
challenges: {
title: 'Lesson',
url: `${homeLocation}/learn`
},
guides: {
title: 'Guide',
url: `${homeLocation}/guide`
},
youtube: {
title: 'YouTube',
url: 'https://www.youtube.com/watch?v='
}
};
const buildUrl = (index, result) =>
`${indexMap[index].url}${'videoId' in result ? result.videoId : result.url}`;
const AllHits = connectAutoComplete(({ hits, currentRefinement }) => { const AllHits = connectAutoComplete(({ hits, currentRefinement }) => {
if (hits.some(hit => isEmpty(hit.index))) { const isHitsEmpty = !hits.length;
return null;
}
const nonQuerySuggestionHits = hits.filter(
({ index }) => index !== 'query_suggestions'
);
const isHitsEmpty = nonQuerySuggestionHits.every(({ hits }) => !hits.length);
return currentRefinement && !isHitsEmpty ? ( return currentRefinement && !isHitsEmpty ? (
<div className='ais-Hits search-page'> <div className='ais-Hits search-page'>
<ul className='ais-Hits-list'> <ul className='ais-Hits-list'>
{nonQuerySuggestionHits.map(({ hits: results, index }) => {hits.map(result => (
results.map(result => ( <a
<a href={result.url}
href={buildUrl(index, result)} key={result.objectID}
key={result.objectID} rel='noopener noreferrer'
rel='noopener noreferrer' target='_blank'
target='_blank' >
> <li className='ais-Hits-item dataset-node'>
<li className='ais-Hits-item dataset-node'> <p>
<p> <Highlight attribute='title' hit={result} />
<strong>{indexMap[index].title}:</strong> </p>
&nbsp; </li>
<Highlight attribute='title' hit={result} /> </a>
</p> ))}
</li>
</a>
))
)}
</ul> </ul>
</div> </div>
) : ( ) : (

View File

@ -2,12 +2,11 @@ import React, { Fragment, Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Helmet from 'react-helmet'; import Helmet from 'react-helmet';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Index, PoweredBy as PoweredByAlgolia } from 'react-instantsearch-dom'; import { Index } from 'react-instantsearch-dom';
import { Grid, Row, Col } from '@freecodecamp/react-bootstrap'; import { Grid, Row, Col } from '@freecodecamp/react-bootstrap';
import { updateSearchQuery } from '../components/search/redux'; import { updateSearchQuery } from '../components/search/redux';
import SearchPageHits from '../components/search/searchPage/SearchPageHits'; import SearchPageHits from '../components/search/searchPage/SearchPageHits';
import Spacer from '../components/helpers/Spacer';
import './search.css'; import './search.css';
@ -23,18 +22,13 @@ class SearchPage extends Component {
return ( return (
<Fragment> <Fragment>
<Helmet title='Search | freeCodeCamp.org' /> <Helmet title='Search | freeCodeCamp.org' />
<Index indexName='challenges' /> <Index indexName='news' />
<Index indexName='guides' />
<Index indexName='youtube' />
<Grid> <Grid>
<Row> <Row>
<Col xs={12}> <Col xs={12}>
<main> <main>
<SearchPageHits /> <SearchPageHits />
</main> </main>
<Spacer />
<PoweredByAlgolia className='powered-by-wrapper' />
<Spacer />
</Col> </Col>
</Row> </Row>
</Grid> </Grid>