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:
committed by
mrugesh
parent
07b38d02b3
commit
a03602a1d4
@ -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',
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
@ -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() {
|
||||||
|
@ -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}
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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>
|
||||||
|
</li>
|
||||||
<Highlight attribute='title' hit={result} />
|
</a>
|
||||||
</p>
|
))}
|
||||||
</li>
|
|
||||||
</a>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
@ -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>
|
||||||
|
Reference in New Issue
Block a user