feat(UI): visualize input clear button (#48702)

* feat: visualize search bar inputs clear button

* chore: remove redundant empty lines

* fix: make search bar input horizontally responsive

* fix: make search bar input width follow suggestion items

* fix: moving clear button to right of the searchbar

* feat: add search bar clear button in LP

* fix: add a bigger spacing between the icons and the text

* fix: make the clear button show visible focus indicator

* chore: refactor the css code to avoid redundancy

* fix: make the buttons in search bar accessible for screen reader

* fix: make the buttons and input in search bar more accessible
This commit is contained in:
Manabu Matsumoto
2022-12-23 17:00:38 -06:00
committed by GitHub
parent 5b229e4f52
commit 95b724a3a2
9 changed files with 88 additions and 23 deletions
@@ -478,10 +478,11 @@
"hint": "Hint",
"heart": "Heart",
"initial": "Initial",
"inputReset": "Clear search terms",
"info": "Intro Information",
"spacer": "Spacer",
"toggle": "Toggle Checkmark",
"magnifier": "magnifier"
"magnifier": "Submit search terms"
},
"aria": {
"fcc-curriculum": "freeCodeCamp Curriculum",
+1 -1
View File
@@ -6,7 +6,7 @@ const Magnifier = (): JSX.Element => {
return (
<>
<span className='sr-only'>{t('icons.Magnifier')}</span>
<span className='sr-only'>{t('icons.magnifier')}</span>
<svg
className='ais-SearchBox-submitIcon'
height='10'
+24
View File
@@ -0,0 +1,24 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
const InputReset = (): JSX.Element => {
const { t } = useTranslation();
return (
<>
<span className='sr-only'>{t('icons.inputReset')}</span>
<svg
className='ais-SearchBox-resetIcon'
height='10'
viewBox='0 0 20 20'
width='10'
xmlns='http://www.w3.org/2000/svg'
>
<path d='M8.114 10L.944 2.83 0 1.885 1.886 0l.943.943L10 8.113l7.17-7.17.944-.943L20 1.886l-.943.943-7.17 7.17 7.17 7.17.943.944L18.114 20l-.943-.943-7.17-7.17-7.17 7.17-.944.943L0 18.114l.943-.943L8.113 10z'></path>
</svg>
</>
);
};
InputReset.displayName = 'InputReset';
export default InputReset;
@@ -477,6 +477,10 @@ button.nav-link[aria-disabled='true'] {
width: calc(100vw - 17rem);
}
.universal-nav-right .ais-SearchBox-reset {
right: 291px;
}
.universal-nav-right .fcc_searchBar .ais-Hits {
width: calc(100vw - 17rem);
}
@@ -485,7 +489,8 @@ button.nav-link[aria-disabled='true'] {
outline: 3px solid var(--blue-mid);
}
.ais-SearchBox-submit:focus {
.ais-SearchBox-submit:focus,
.ais-SearchBox-reset:focus {
outline: 3px solid var(--blue-mid);
}
@@ -560,6 +565,10 @@ button.nav-link[aria-disabled='true'] {
width: calc(100vw - 30px);
}
.universal-nav-right .ais-SearchBox-reset {
right: 15px;
}
.universal-nav-right .fcc_searchBar .ais-Hits {
width: calc(100% - 30px);
}
+2
View File
@@ -46,6 +46,8 @@ export class Header extends React.Component<
// the search bar should not toggle the menu
this.searchBarRef.current &&
!this.searchBarRef.current.contains(eventTarget) &&
// don't count clicks on searcn bar inputs reset button
!eventTarget.closest('.ais-SearchBox-reset') &&
// don't count clicks on language button/menu
!eventTarget.closest('.nav-lang') &&
// don't count clicks on disabled elements
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import React, { useState, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import Magnifier from '../../../assets/icons/Magnifier';
import InputReset from '../../../assets/icons/inputReset';
import { searchPageUrl } from '../../../utils/algolia-locale-setup';
type Props = {
@@ -12,6 +13,7 @@ const SearchBarOptimized = ({ innerRef }: Props): JSX.Element => {
const placeholder = t('search.placeholder');
const searchUrl = searchPageUrl;
const [value, setValue] = useState('');
const inputElementRef = useRef<HTMLInputElement>(null);
const onChange = (event: React.ChangeEvent<HTMLInputElement>) =>
setValue(event.target.value);
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
@@ -20,6 +22,10 @@ const SearchBarOptimized = ({ innerRef }: Props): JSX.Element => {
window.open(`${searchUrl}?query=${encodeURIComponent(value)}`, '_blank');
}
};
const onClick = () => {
setValue('');
inputElementRef.current?.focus();
};
return (
<div className='fcc_searchBar' data-testid='fcc_searchBar' ref={innerRef}>
@@ -32,11 +38,14 @@ const SearchBarOptimized = ({ innerRef }: Props): JSX.Element => {
onSubmit={onSubmit}
role='search'
>
<label className='sr-only' htmlFor='ais-SearchBox-input'>
{t ? t('search.label') : ''}
</label>
<input
aria-label='Search'
autoCapitalize='off'
autoComplete='off'
autoCorrect='off'
id='ais-SearchBox-input'
className='ais-SearchBox-input'
maxLength={512}
onChange={onChange}
@@ -44,14 +53,20 @@ const SearchBarOptimized = ({ innerRef }: Props): JSX.Element => {
spellCheck='false'
type='search'
value={value}
ref={inputElementRef}
/>
<button
className='ais-SearchBox-submit'
title='Submit your search query.'
type='submit'
>
<button className='ais-SearchBox-submit' type='submit'>
<Magnifier />
</button>
{value && (
<button
className='ais-SearchBox-reset'
onClick={onClick}
type='button'
>
<InputReset />
</button>
)}
</form>
</div>
</div>
@@ -187,6 +187,8 @@ export class SearchBar extends Component<SearchBarProps, SearchBarState> {
render(): JSX.Element {
const { isDropdownEnabled, isSearchFocused, innerRef, t } = this.props;
const { index } = this.state;
const submitTitle = t ? t('icons.magnifier') : '';
const resetTitle = t ? t('icons.inputReset') : '';
const placeholder = t ? t('search.placeholder') : '';
return (
@@ -210,7 +212,11 @@ export class SearchBar extends Component<SearchBarProps, SearchBarState> {
this.handleSearch(e);
}}
showLoadingIndicator={false}
translations={{ placeholder }}
translations={{
submitTitle: submitTitle,
resetTitle: resetTitle,
placeholder: placeholder
}}
/>
</div>
</ObserveKeys>
@@ -24,7 +24,8 @@
.ais-Menu-showMore,
.ais-RangeInput-submit,
.ais-RefinementList-showMore,
.ais-SearchBox-submit {
.ais-SearchBox-submit,
.ais-SearchBox-reset {
padding: 0;
overflow: visible;
font: inherit;
@@ -47,7 +48,8 @@
.ais-Menu-showMore::-moz-focus-inner,
.ais-RangeInput-submit::-moz-focus-inner,
.ais-RefinementList-showMore::-moz-focus-inner,
.ais-SearchBox-submit::-moz-focus-inner {
.ais-SearchBox-submit::-moz-focus-inner,
.ais-SearchBox-reset::-moz-focus-inner {
padding: 0;
border: 0;
}
@@ -60,7 +62,8 @@
.ais-Menu-showMore[disabled],
.ais-RangeInput-submit[disabled],
.ais-RefinementList-showMore[disabled],
.ais-SearchBox-submit[disabled] {
.ais-SearchBox-submit[disabled],
.ais-SearchBox-reset[disabled] {
cursor: default;
}
.ais-Breadcrumb-list,
@@ -609,7 +612,9 @@ a[class^='ais-'] {
.ais-SearchBox-input:-moz-placeholder {
color: var(--gray-15);
}
.ais-SearchBox-submit {
.ais-SearchBox-submit,
.ais-SearchBox-reset {
position: absolute;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
@@ -617,8 +622,8 @@ a[class^='ais-'] {
width: 20px;
height: 20px;
}
.ais-SearchBox-submit {
position: absolute;
.ais-SearchBox-reset {
right: 0;
}
.ais-SearchBox-resetIcon,
.ais-SearchBox-loadingIcon {
@@ -9,7 +9,6 @@
color: var(--gray-00);
}
.ais-SearchBox-reset,
.ais-SearchBox-loadingIndicator {
display: none;
}
@@ -22,11 +21,15 @@
height: 26px;
}
.ais-SearchBox-submit {
.ais-SearchBox-submit,
.ais-SearchBox-reset {
margin-top: 6px;
height: 26px;
width: 26px;
background-color: var(--gray-75);
}
.ais-SearchBox-submit {
padding-inline: 3px;
}
@@ -38,7 +41,7 @@
}
.fcc_searchBar .ais-SearchBox-input {
padding-inline-start: 1.5em;
padding-inline: 2em;
}
.fcc_searchBar .ais-Hits {
@@ -136,16 +139,16 @@ and arrow keys */
@media (min-width: 981px) {
.ais-SearchBox-input {
width: 100%;
max-width: 500px;
max-width: 100%;
}
.fcc_searchBar {
position: relative;
max-width: 520px;
max-width: 500px;
}
.fcc_searchBar .ais-Hits {
top: auto;
width: 100%;
max-width: 500px;
max-width: 100%;
left: 0px;
}
.fcc_searchBar .ais-SearchBox-form {