feat: accessible search bar (#36784)
* feat: Passing hits from SearchHits to parent SearchBar to keep track of with the keyboard. Moved all logic for number of hits to WithInstantSearch.js * Basic functionality working * Added up/down looping functionality to dropdown * Set 's' and '/' as shortcuts to focus the search bar * Moved some things around and added functionality for mouse hovering to change the selected hit. Reworked a bit of the global CSS so mouse hovers don't cause multiple highlights in the dropdown * Brought back magnifying glass icon * feat: Switched out onKeyDown and key codes for react-hotkeys * Refactoring based on review
This commit is contained in:
committed by
mrugesh
parent
8225ce7572
commit
07c552bffe
@ -92,7 +92,7 @@ th {
|
|||||||
color: var(--secondary-color);
|
color: var(--secondary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:not(.fcc_suggestion_item):hover {
|
||||||
color: var(--tertiary-color);
|
color: var(--tertiary-color);
|
||||||
background-color: var(--tertiary-background);
|
background-color: var(--tertiary-background);
|
||||||
}
|
}
|
||||||
@ -163,6 +163,7 @@ a:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn:active:hover,
|
.btn:active:hover,
|
||||||
|
.btn-primary:hover,
|
||||||
.btn-primary:active:hover,
|
.btn-primary:active:hover,
|
||||||
.btn-primary.active:hover,
|
.btn-primary.active:hover,
|
||||||
.open > .dropdown-toggle.btn-primary:hover,
|
.open > .dropdown-toggle.btn-primary:hover,
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component, Fragment } from 'react';
|
||||||
import { Location } from '@reach/router';
|
import { Location } from '@reach/router';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { InstantSearch, Configure } from 'react-instantsearch-dom';
|
import { InstantSearch, Configure } from 'react-instantsearch-dom';
|
||||||
import qs from 'query-string';
|
import qs from 'query-string';
|
||||||
import { navigate } from 'gatsby';
|
import { navigate } from 'gatsby';
|
||||||
|
import Media from 'react-responsive';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isSearchDropdownEnabledSelector,
|
isSearchDropdownEnabledSelector,
|
||||||
@ -116,6 +117,7 @@ class InstantSearchRoot extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { query } = this.props;
|
const { query } = this.props;
|
||||||
|
const MAX_MOBILE_HEIGHT = 768;
|
||||||
return (
|
return (
|
||||||
<InstantSearch
|
<InstantSearch
|
||||||
apiKey='4318af87aa3ce128708f1153556c6108'
|
apiKey='4318af87aa3ce128708f1153556c6108'
|
||||||
@ -124,7 +126,18 @@ class InstantSearchRoot extends Component {
|
|||||||
onSearchStateChange={this.onSearchStateChange}
|
onSearchStateChange={this.onSearchStateChange}
|
||||||
searchState={{ query }}
|
searchState={{ query }}
|
||||||
>
|
>
|
||||||
<Configure hitsPerPage={15} />
|
{this.isSearchPage() ? (
|
||||||
|
<Configure hitsPerPage={15} />
|
||||||
|
) : (
|
||||||
|
<Fragment>
|
||||||
|
<Media maxHeight={MAX_MOBILE_HEIGHT}>
|
||||||
|
<Configure hitsPerPage={5} />
|
||||||
|
</Media>
|
||||||
|
<Media minHeight={MAX_MOBILE_HEIGHT + 1}>
|
||||||
|
<Configure hitsPerPage={8} />
|
||||||
|
</Media>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</InstantSearch>
|
</InstantSearch>
|
||||||
);
|
);
|
||||||
|
@ -4,6 +4,8 @@ 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 { HotKeys, configure } from 'react-hotkeys';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isSearchDropdownEnabledSelector,
|
isSearchDropdownEnabledSelector,
|
||||||
@ -17,6 +19,9 @@ import SearchHits from './SearchHits';
|
|||||||
import './searchbar-base.css';
|
import './searchbar-base.css';
|
||||||
import './searchbar.css';
|
import './searchbar.css';
|
||||||
|
|
||||||
|
// Configure react-hotkeys to work with the searchbar
|
||||||
|
configure({ ignoreTags: ['select', 'textarea'] });
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
isDropdownEnabled: PropTypes.bool,
|
isDropdownEnabled: PropTypes.bool,
|
||||||
isSearchFocused: PropTypes.bool,
|
isSearchFocused: PropTypes.bool,
|
||||||
@ -48,19 +53,25 @@ class SearchBar extends Component {
|
|||||||
|
|
||||||
this.searchBarRef = React.createRef();
|
this.searchBarRef = React.createRef();
|
||||||
this.handleChange = this.handleChange.bind(this);
|
this.handleChange = this.handleChange.bind(this);
|
||||||
this.handlePageClick = this.handlePageClick.bind(this);
|
|
||||||
this.handleSearch = this.handleSearch.bind(this);
|
this.handleSearch = this.handleSearch.bind(this);
|
||||||
|
this.handleMouseEnter = this.handleMouseEnter.bind(this);
|
||||||
|
this.handleMouseLeave = this.handleMouseLeave.bind(this);
|
||||||
|
this.handleFocus = this.handleFocus.bind(this);
|
||||||
|
this.handleHits = this.handleHits.bind(this);
|
||||||
|
this.state = {
|
||||||
|
index: -1,
|
||||||
|
hits: []
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const searchInput = document.querySelector('.ais-SearchBox-input');
|
const searchInput = document.querySelector('.ais-SearchBox-input');
|
||||||
searchInput.id = 'fcc_instantsearch';
|
searchInput.id = 'fcc_instantsearch';
|
||||||
|
document.addEventListener('click', this.handleFocus);
|
||||||
document.addEventListener('click', this.handlePageClick);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
document.removeEventListener('click', this.handlePageClick);
|
document.removeEventListener('click', this.handleFocus);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange() {
|
handleChange() {
|
||||||
@ -68,53 +79,136 @@ class SearchBar extends Component {
|
|||||||
if (!isSearchFocused) {
|
if (!isSearchFocused) {
|
||||||
toggleSearchFocused(true);
|
toggleSearchFocused(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
index: -1
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePageClick(e) {
|
handleFocus(e) {
|
||||||
const { toggleSearchFocused } = this.props;
|
const { toggleSearchFocused } = this.props;
|
||||||
const isSearchFocusedClick = this.searchBarRef.current.contains(e.target);
|
const isSearchFocused = this.searchBarRef.current.contains(e.target);
|
||||||
return toggleSearchFocused(isSearchFocusedClick);
|
if (!isSearchFocused) {
|
||||||
|
// Reset if user clicks outside of
|
||||||
|
// search bar / closes dropdown
|
||||||
|
this.setState({ index: -1 });
|
||||||
|
}
|
||||||
|
return toggleSearchFocused(isSearchFocused);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearch(e, query) {
|
handleSearch(e, query) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const { toggleSearchDropdown, updateSearchQuery } = this.props;
|
const { toggleSearchDropdown, updateSearchQuery } = this.props;
|
||||||
// disable the search dropdown
|
const { index, hits } = this.state;
|
||||||
|
const selectedHit = hits[index];
|
||||||
|
|
||||||
|
// Disable the search dropdown
|
||||||
toggleSearchDropdown(false);
|
toggleSearchDropdown(false);
|
||||||
if (query) {
|
if (selectedHit) {
|
||||||
updateSearchQuery(query);
|
// Redirect to hit / footer selected by arrow keys
|
||||||
|
return window.location.assign(selectedHit.url);
|
||||||
|
} else if (!query) {
|
||||||
|
// Set query to value in search bar if enter is pressed
|
||||||
|
query = e.currentTarget.children[0].value;
|
||||||
}
|
}
|
||||||
|
updateSearchQuery(query);
|
||||||
|
|
||||||
// For Learn search results page
|
// For Learn search results page
|
||||||
// return navigate('/search');
|
// return navigate('/search');
|
||||||
|
|
||||||
// Temporary redirect to News search results page
|
// Temporary redirect to News search results page
|
||||||
return window.location.assign(
|
// when non-empty search input submitted
|
||||||
`https://freecodecamp.org/news/search/?query=${query}`
|
return query
|
||||||
);
|
? window.location.assign(
|
||||||
|
`https://freecodecamp.org/news/search/?query=${encodeURIComponent(
|
||||||
|
query
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleMouseEnter(e) {
|
||||||
|
e.persist();
|
||||||
|
const hoveredText = e.currentTarget.innerText;
|
||||||
|
|
||||||
|
this.setState(({ hits }) => {
|
||||||
|
const hitsTitles = hits.map(hit => hit.title);
|
||||||
|
const hoveredIndex = hitsTitles.indexOf(hoveredText);
|
||||||
|
|
||||||
|
return { index: hoveredIndex };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseLeave() {
|
||||||
|
this.setState({
|
||||||
|
index: -1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleHits(currHits) {
|
||||||
|
const { hits } = this.state;
|
||||||
|
|
||||||
|
if (!isEqual(hits, currHits)) {
|
||||||
|
this.setState({
|
||||||
|
index: -1,
|
||||||
|
hits: currHits
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyMap = {
|
||||||
|
INDEX_UP: ['up'],
|
||||||
|
INDEX_DOWN: ['down']
|
||||||
|
};
|
||||||
|
|
||||||
|
keyHandlers = {
|
||||||
|
INDEX_UP: e => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.setState(({ index, hits }) => ({
|
||||||
|
index: index === -1 ? hits.length - 1 : index - 1
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
INDEX_DOWN: e => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.setState(({ index, hits }) => ({
|
||||||
|
index: index === hits.length - 1 ? -1 : index + 1
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isDropdownEnabled, isSearchFocused } = this.props;
|
const { isDropdownEnabled, isSearchFocused } = this.props;
|
||||||
|
const { index } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='fcc_searchBar'
|
className='fcc_searchBar'
|
||||||
data-testid='fcc_searchBar'
|
data-testid='fcc_searchBar'
|
||||||
ref={this.searchBarRef}
|
ref={this.searchBarRef}
|
||||||
>
|
>
|
||||||
<div className='fcc_search_wrapper'>
|
<HotKeys handlers={this.keyHandlers} keyMap={this.keyMap}>
|
||||||
<label className='fcc_sr_only' htmlFor='fcc_instantsearch'>
|
<div className='fcc_search_wrapper'>
|
||||||
Search
|
<label className='fcc_sr_only' htmlFor='fcc_instantsearch'>
|
||||||
</label>
|
Search
|
||||||
<SearchBox
|
</label>
|
||||||
onChange={this.handleChange}
|
<SearchBox
|
||||||
onSubmit={this.handleSearch}
|
focusShortcuts={[83, 191]}
|
||||||
showLoadingIndicator={true}
|
onChange={this.handleChange}
|
||||||
translations={{ placeholder }}
|
onFocus={this.handleFocus}
|
||||||
/>
|
onSubmit={this.handleSearch}
|
||||||
{isDropdownEnabled && isSearchFocused && (
|
showLoadingIndicator={true}
|
||||||
<SearchHits handleSubmit={this.handleSearch} />
|
translations={{ placeholder }}
|
||||||
)}
|
/>
|
||||||
</div>
|
{isDropdownEnabled && isSearchFocused && (
|
||||||
|
<SearchHits
|
||||||
|
handleHits={this.handleHits}
|
||||||
|
handleMouseEnter={this.handleMouseEnter}
|
||||||
|
handleMouseLeave={this.handleMouseLeave}
|
||||||
|
selectedIndex={index}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</HotKeys>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,50 +1,89 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { connectStateResults, connectHits } from 'react-instantsearch-dom';
|
import { connectStateResults, connectHits } from 'react-instantsearch-dom';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import Suggestion from './SearchSuggestion';
|
import Suggestion from './SearchSuggestion';
|
||||||
|
|
||||||
const CustomHits = connectHits(({ hits, currentRefinement, handleSubmit }) => {
|
const CustomHits = connectHits(
|
||||||
const shortenedHits = hits.filter((hit, i) => i < 8);
|
({
|
||||||
const defaultHit = [
|
hits,
|
||||||
{
|
searchQuery,
|
||||||
objectID: `default-hit-${currentRefinement}`,
|
handleMouseEnter,
|
||||||
query: currentRefinement,
|
handleMouseLeave,
|
||||||
_highlightResult: {
|
selectedIndex,
|
||||||
query: {
|
handleHits
|
||||||
value: `
|
}) => {
|
||||||
|
const footer = [
|
||||||
|
{
|
||||||
|
objectID: `default-hit-${searchQuery}`,
|
||||||
|
query: searchQuery,
|
||||||
|
url: `https://freecodecamp.org/news/search/?query=${encodeURIComponent(
|
||||||
|
searchQuery
|
||||||
|
)}`,
|
||||||
|
title: `See all results for ${searchQuery}`,
|
||||||
|
_highlightResult: {
|
||||||
|
query: {
|
||||||
|
value: `
|
||||||
See all results for
|
See all results for
|
||||||
<ais-highlight-0000000000>
|
<ais-highlight-0000000000>
|
||||||
${currentRefinement}
|
${searchQuery}
|
||||||
</ais-highlight-0000000000>
|
</ais-highlight-0000000000>
|
||||||
`
|
`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
];
|
||||||
];
|
const allHits = hits.slice(0, 8).concat(footer);
|
||||||
return (
|
useEffect(() => {
|
||||||
<div className='ais-Hits'>
|
handleHits(allHits);
|
||||||
<ul className='ais-Hits-list'>
|
});
|
||||||
{shortenedHits.concat(defaultHit).map(hit => (
|
|
||||||
<li
|
|
||||||
className='ais-Hits-item'
|
|
||||||
data-fccobjectid={hit.objectID}
|
|
||||||
key={hit.objectID}
|
|
||||||
>
|
|
||||||
<Suggestion handleSubmit={handleSubmit} hit={hit} />
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const SearchHits = connectStateResults(({ handleSubmit, searchState }) => {
|
return (
|
||||||
return isEmpty(searchState) || !searchState.query ? null : (
|
<div className='ais-Hits'>
|
||||||
<CustomHits
|
<ul className='ais-Hits-list'>
|
||||||
currentRefinement={searchState.query}
|
{allHits.map((hit, i) => (
|
||||||
handleSubmit={handleSubmit}
|
<li
|
||||||
/>
|
className={
|
||||||
);
|
i === selectedIndex ? 'ais-Hits-item selected' : 'ais-Hits-item'
|
||||||
});
|
}
|
||||||
|
data-fccobjectid={hit.objectID}
|
||||||
|
key={hit.objectID}
|
||||||
|
>
|
||||||
|
<Suggestion
|
||||||
|
handleMouseEnter={handleMouseEnter}
|
||||||
|
handleMouseLeave={handleMouseLeave}
|
||||||
|
hit={hit}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const SearchHits = connectStateResults(
|
||||||
|
({
|
||||||
|
searchState,
|
||||||
|
handleMouseEnter,
|
||||||
|
handleMouseLeave,
|
||||||
|
selectedIndex,
|
||||||
|
handleHits
|
||||||
|
}) => {
|
||||||
|
return isEmpty(searchState) || !searchState.query ? null : (
|
||||||
|
<CustomHits
|
||||||
|
handleHits={handleHits}
|
||||||
|
handleMouseEnter={handleMouseEnter}
|
||||||
|
handleMouseLeave={handleMouseLeave}
|
||||||
|
searchQuery={searchState.query}
|
||||||
|
selectedIndex={selectedIndex}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
CustomHits.propTypes = {
|
||||||
|
handleHits: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
export default SearchHits;
|
export default SearchHits;
|
||||||
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { Highlight } from 'react-instantsearch-dom';
|
import { Highlight } from 'react-instantsearch-dom';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
|
|
||||||
const Suggestion = ({ handleSubmit, hit }) => {
|
const Suggestion = ({ hit, handleMouseEnter, handleMouseLeave }) => {
|
||||||
const dropdownFooter = hit.objectID.includes('default-hit-');
|
const dropdownFooter = hit.objectID.includes('default-hit-');
|
||||||
return isEmpty(hit) || isEmpty(hit.objectID) ? null : (
|
return isEmpty(hit) || isEmpty(hit.objectID) ? null : (
|
||||||
<a
|
<a
|
||||||
@ -12,8 +12,15 @@ const Suggestion = ({ handleSubmit, hit }) => {
|
|||||||
? 'fcc_suggestion_footer fcc_suggestion_item'
|
? 'fcc_suggestion_footer fcc_suggestion_item'
|
||||||
: 'fcc_suggestion_item'
|
: 'fcc_suggestion_item'
|
||||||
}
|
}
|
||||||
href={hit.url}
|
href={
|
||||||
onClick={e => (dropdownFooter ? handleSubmit(e, hit.query) : '')}
|
dropdownFooter
|
||||||
|
? `https://freecodecamp.org/news/search/?query=${encodeURIComponent(
|
||||||
|
hit.query
|
||||||
|
)}`
|
||||||
|
: hit.url
|
||||||
|
}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
>
|
>
|
||||||
<span className='hit-name'>
|
<span className='hit-name'>
|
||||||
{dropdownFooter ? (
|
{dropdownFooter ? (
|
||||||
@ -27,7 +34,8 @@ const Suggestion = ({ handleSubmit, hit }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Suggestion.propTypes = {
|
Suggestion.propTypes = {
|
||||||
handleSubmit: PropTypes.func.isRequired,
|
handleMouseEnter: PropTypes.func.isRequired,
|
||||||
|
handleMouseLeave: PropTypes.func.isRequired,
|
||||||
hit: PropTypes.object
|
hit: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -618,7 +618,7 @@ a[class^='ais-'] {
|
|||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 100;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
@ -628,6 +628,7 @@ a[class^='ais-'] {
|
|||||||
}
|
}
|
||||||
.ais-SearchBox-submit {
|
.ais-SearchBox-submit {
|
||||||
left: 0.3rem;
|
left: 0.3rem;
|
||||||
|
top: 57%;
|
||||||
}
|
}
|
||||||
.ais-SearchBox-reset {
|
.ais-SearchBox-reset {
|
||||||
right: 0.3rem;
|
right: 0.3rem;
|
||||||
|
@ -10,16 +10,16 @@
|
|||||||
color: var(--gray-00);
|
color: var(--gray-00);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ais-SearchBox-submit,
|
|
||||||
.ais-SearchBox-reset {
|
.ais-SearchBox-reset {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ais-SearchBox-input {
|
.ais-SearchBox-input {
|
||||||
padding: 1px 10px;
|
padding: 1px 10px 1px 30px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: calc(100vw - 10px);
|
width: calc(100vw - 10px);
|
||||||
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fcc_searchBar .ais-SearchBox-input,
|
.fcc_searchBar .ais-SearchBox-input,
|
||||||
@ -43,8 +43,64 @@
|
|||||||
left: 5px;
|
left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#fcc_instantsearch {
|
/* hits */
|
||||||
margin-top: 6px;
|
.fcc_searchBar .ais-Highlight-highlighted {
|
||||||
|
background-color: transparent;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ais-Highlight-nonHighlighted {
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fcc_hits_wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fcc_suggestion_item {
|
||||||
|
display: block;
|
||||||
|
padding: 8px;
|
||||||
|
color: var(--gray-00) !important;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fcc_suggestion_item [class^='ais-'] {
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fcc_suggestion_item:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fcc_sr_only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ais-Hits-item {
|
||||||
|
background-color: var(--gray-75);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hit selected with arrow keys or mouse */
|
||||||
|
.selected {
|
||||||
|
background-color: var(--blue-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 380px) {
|
@media (min-width: 380px) {
|
||||||
@ -66,8 +122,6 @@
|
|||||||
@media (min-width: 700px) {
|
@media (min-width: 700px) {
|
||||||
.ais-SearchBox-input {
|
.ais-SearchBox-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
|
||||||
#fcc_instantsearch {
|
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
@ -85,78 +139,12 @@
|
|||||||
top: auto;
|
top: auto;
|
||||||
right: 15px;
|
right: 15px;
|
||||||
}
|
}
|
||||||
|
.ais-SearchBox-submit {
|
||||||
|
left: 0.85rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media (min-width: 1100px) {
|
@media (min-width: 1100px) {
|
||||||
.fcc_searchBar .ais-Hits {
|
.fcc_searchBar .ais-Hits {
|
||||||
width: calc(100% - 20px);
|
width: calc(100% - 20px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* hits */
|
|
||||||
.fcc_searchBar .ais-Highlight-highlighted {
|
|
||||||
background-color: transparent;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ais-Highlight-nonHighlighted {
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fcc_hits_wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fcc_suggestion_item {
|
|
||||||
display: block;
|
|
||||||
padding: 8px;
|
|
||||||
color: var(--gray-00);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fcc_suggestion_item [class^='ais-'] {
|
|
||||||
font-size: 17px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fcc_suggestion_item:hover {
|
|
||||||
background-color: var(--blue-dark);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fcc_sr_only {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
padding: 0;
|
|
||||||
margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0, 0, 0, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user