Skip to content

Commit

Permalink
Scope Search scss (github#20964)
Browse files Browse the repository at this point in the history
* scope Search scss, reduce custom styling

* use translation for loading and no results found

* remove unnescessary test
  • Loading branch information
mikesurowiec authored Aug 23, 2021
1 parent 146f435 commit 2a25efc
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 237 deletions.
67 changes: 67 additions & 0 deletions components/Search.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
.resultsContainer mark {
background: none;
color: inherit;
}

.searchResultTitle mark {
color: var(--color-auto-blue-5);
}

.searchResultIntro mark {
border-bottom: 1px solid var(--color-auto-blue-5);
}

.searchResultContent mark {
text-decoration: none;
border-bottom: 1px dotted var(--color-auto-gray-5);
}

.searchResultTitle em {
font-style: normal;
text-decoration: underline;
}

.resultsContainerOverlay {
display: none;
position: absolute;
top: 0;
right: 0;
width: 0;
border-radius: 3px;
background: var(--color-bg-primary);
box-shadow: 0 1px 15px rgba(0, 0, 0, 0.15);
transition: width 0.3s ease-in-out;
padding: 64px 24px 24px;
}

.resultsContainerOpen {
display: block;
}
.resultsContainerOpen.resultsContainerOverlay {
width: 60vw;
max-width: 760px;
}

.searchInput {
transition: width 0.3s ease-in-out;
}
.searchInputOverlay {
max-width: 690px;
width: 256px;
}
.searchInputExpanded {
width: 55vw;
position: absolute;
right: 0;
top: 0;
}

.searchOverlayOpen {
background: var(--color-bg-backdrop);
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: block;
}
174 changes: 108 additions & 66 deletions components/Search.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { useState, useEffect, useRef, ReactNode } from 'react'
import { useRouter } from 'next/router'
import debounce from 'lodash/debounce'
import cx from 'classnames'

import { useTranslation } from 'components/hooks/useTranslation'
import { sendEvent, EventType } from 'components/lib/events'
import { useMainContext } from './context/MainContext'
import { useVersion } from 'components/hooks/useVersion'
import cx from 'classnames'
import { useLanguages } from './context/LanguagesContext'

import styles from './Search.module.scss'

type SearchResult = {
url: string
breadcrumbs: string
Expand All @@ -17,16 +20,23 @@ type SearchResult = {
}

type Props = {
isStandalone?: boolean
isOverlay?: boolean
variant?: 'compact' | 'expanded'
autoFocus?: boolean
updateSearchParams?: boolean
children?: (props: { SearchInput: ReactNode; SearchResults: ReactNode }) => ReactNode
}
// Homepage and 404 should be `isStandalone`, all others not
// `updateSearchParams` should be false on the GraphQL explorer page
export function Search({ isStandalone = false, updateSearchParams = true, children }: Props) {
export function Search({
autoFocus = false,
isOverlay = false,
updateSearchParams = true,
variant = 'compact',
children,
}: Props) {
const router = useRouter()
const [query, setQuery] = useState(router.query.query || '')
const [results, setResults] = useState<Array<SearchResult>>([])
const [isLoading, setIsLoading] = useState(false)
const [activeHit, setActiveHit] = useState(0)
const inputRef = useRef<HTMLInputElement>(null)
const { t } = useTranslation('search')
Expand Down Expand Up @@ -104,23 +114,28 @@ export function Search({ isStandalone = false, updateSearchParams = true, childr
// If there's a query, call the endpoint
// Otherwise, there's no results by default
async function fetchSearchResults(xquery: string) {
if (xquery) {
const endpointUrl = new URL(location.origin)
endpointUrl.pathname = '/search'
const endpointParams: Record<string, string> = {
language,
version,
query: xquery,
}
endpointUrl.search = new URLSearchParams(endpointParams).toString()
setIsLoading(true)
try {
if (xquery) {
const endpointUrl = new URL(location.origin)
endpointUrl.pathname = '/search'
const endpointParams: Record<string, string> = {
language,
version,
query: xquery,
}
endpointUrl.search = new URLSearchParams(endpointParams).toString()

const response = await fetch(endpointUrl.toString(), {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
})
setResults(response.ok ? await response.json() : [])
} else {
setResults([])
const response = await fetch(endpointUrl.toString(), {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
})
setResults(response.ok ? await response.json() : [])
} else {
setResults([])
}
} finally {
setIsLoading(false)
}

// Analytics tracking
Expand All @@ -146,60 +161,92 @@ export function Search({ isStandalone = false, updateSearchParams = true, childr

const SearchResults = (
<>
<div id="search-results-container" className={results.length ? 'js-open' : ''}>
{Boolean(results.length) && (
<div className="ais-Hits d-block">
<ol className="ais-Hits-list">
{results.map(({ url, breadcrumbs, heading, title, content }, index) => (
<li
key={url}
className={'ais-Hits-item' + (index + 1 === activeHit ? ' active' : '')}
>
<div className="search-result border-top color-border-secondary py-3 px-2">
<a className="no-underline" href={url}>
{/* Breadcrumbs in search records don't include the page title. These fields may contain <mark> elements that we need to render */}
<div
className="search-result-breadcrumbs d-block color-text-primary opacity-60 text-small pb-1"
dangerouslySetInnerHTML={{ __html: breadcrumbs }}
/>
<div
className="search-result-title d-block h4-mktg color-text-primary"
dangerouslySetInnerHTML={{
__html: heading ? `${title}: ${heading}` : title,
}}
/>
<div
className="search-result-content d-block color-text-secondary"
dangerouslySetInnerHTML={{ __html: content }}
/>
</a>
</div>
</li>
))}
</ol>
</div>
<div
id="search-results-container"
className={cx(
'z-1',
styles.resultsContainer,
isOverlay && styles.resultsContainerOverlay,
query && styles.resultsContainerOpen
)}
>
{results.length > 0 ? (
<ol data-testid="search-results" className="d-block">
{results.map(({ url, breadcrumbs, heading, title, content }, index) => (
<li
key={url}
data-testid="search-result"
className={cx(
'list-style-none overflow-hidden hover:color-bg-info',
index + 1 === activeHit && 'color-bg-info'
)}
>
<div className="border-top color-border-secondary py-3 px-2">
<a className="no-underline" href={url}>
{/* Breadcrumbs in search records don't include the page title. These fields may contain <mark> elements that we need to render */}
<div
className="d-block color-text-primary opacity-60 text-small pb-1"
dangerouslySetInnerHTML={{ __html: breadcrumbs }}
/>
<div
className={cx(styles.searchResultTitle, 'd-block h4-mktg color-text-primary')}
dangerouslySetInnerHTML={{
__html: heading ? `${title}: ${heading}` : title,
}}
/>
<div
className={cx(
styles.searchResultContent,
'd-block color-text-secondary overflow-hidden'
)}
style={{ maxHeight: '4rem' }}
dangerouslySetInnerHTML={{ __html: content }}
/>
</a>
</div>
</li>
))}
</ol>
) : (
isOverlay && (
<div className="mt-2">
{isLoading ? <span>{t('loading')}...</span> : <span>{t('no_results')}.</span>}
</div>
)
)}
</div>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
<div
className={cx('search-overlay-desktop', !isStandalone && query ? 'js-open' : '')}
className={cx('-z-1', isOverlay && query ? styles.searchOverlayOpen : 'd-none')}
onClick={closeSearch}
></div>
/>
</>
)

const SearchInput = (
<div id="search-input-container" aria-hidden="true">
<div className="ais-SearchBox">
<form role="search" className="ais-SearchBox-form" noValidate onSubmit={preventRefresh}>
<div data-testid="search" aria-hidden="true">
<div className="position-relative z-2">
<form role="search" className="width-full d-flex" noValidate onSubmit={preventRefresh}>
<input
data-testid="site-search-input"
ref={inputRef}
className={cx('ais-SearchBox-input', isStandalone || query ? 'js-open' : '')}
className={cx(
styles.searchInput,
'form-control px-5 f4',
variant === 'compact' && 'py-2',
variant === 'expanded' && 'py-3',
isOverlay && styles.searchInputOverlay,
!isOverlay && 'width-full',
isOverlay && query && styles.searchInputExpanded
)}
style={{
background:
'var(--color-bg-primary) url("/assets/images/octicons/search.svg") no-repeat 6px',
}}
type="search"
placeholder={t`placeholder`}
/* eslint-disable-next-line jsx-a11y/no-autofocus */
autoFocus={isStandalone}
autoFocus={autoFocus}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
Expand All @@ -208,12 +255,7 @@ export function Search({ isStandalone = false, updateSearchParams = true, childr
onChange={debounce(onSearch, 200)}
defaultValue={query}
/>
<button
className="ais-SearchBox-submit"
type="submit"
title="Submit the search query."
hidden
/>
<button className="d-none" type="submit" title="Submit the search query." hidden />
</form>
</div>
</div>
Expand Down
6 changes: 4 additions & 2 deletions components/page-header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export const Header = () => {
const { t } = useTranslation(['header', 'homepage'])
const [isMenuOpen, setIsMenuOpen] = useState(false)

// the graphiql explorer utilizes `?query=` in the url and we don't want our search bar to mess that up
const updateSearchParams = router.asPath !== 'graphql/overview/explorer'
const showVersionPicker =
relativePath === 'index.md' ||
currentLayoutName === 'product-landing' ||
Expand Down Expand Up @@ -48,7 +50,7 @@ export const Header = () => {
{/* <!-- GitHub.com homepage and 404 page has a stylized search; Enterprise homepages do not --> */}
{relativePath !== 'index.md' && error !== '404' && (
<div className="d-inline-block ml-4">
{router.asPath !== '/graphql/overview/explorer' && <Search />}
<Search updateSearchParams={updateSearchParams} isOverlay={true} />
</div>
)}
</div>
Expand Down Expand Up @@ -112,7 +114,7 @@ export const Header = () => {
{/* <!-- GitHub.com homepage and 404 page has a stylized search; Enterprise homepages do not --> */}
{relativePath !== 'index.md' && error !== '404' && (
<div className="pt-3 border-top">
{router.asPath !== '/graphql/overview/explorer' && <Search />}
<Search updateSearchParams={updateSearchParams} />
</div>
)}
</div>
Expand Down
2 changes: 2 additions & 0 deletions data/ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ release_notes:
search:
need_help: Need help?
placeholder: Search topics, products...
loading: Loading
no_results: No results found
homepage:
explore_by_product: Explore by product
version_picker: Version
Expand Down
3 changes: 2 additions & 1 deletion pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ function LandingPage(props: LandingPageProps) {
<div>
{/* <!-- Hero --> */}
<section id="landing" className="color-bg-tertiary">
<Search isStandalone={true}>
{/* eslint-disable-next-line jsx-a11y/no-autofocus */}
<Search autoFocus={true} variant="expanded" isOverlay={false}>
{({ SearchInput, SearchResults }) => {
return (
<div className="container-xl px-3 px-md-6 pb-6 pb-lg-9">
Expand Down
1 change: 0 additions & 1 deletion stylesheets/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ $marketing-font-path: "/assets/fonts/inter/";
@import "headings.scss";
@import "images.scss";
@import "lists.scss";
@import "search.scss";
@import "shadows.scss";
@import "summary.scss";
@import "syntax-highlighting.scss";
Expand Down
Loading

0 comments on commit 2a25efc

Please sign in to comment.