UNPKG

@newrelic/gatsby-theme-newrelic

Version:

[![Community Project header](https://github.com/newrelic/opensource-website/raw/master/src/images/categories/Community_Project.png)](https://opensource.newrelic.com/oss-category/#community-project)

365 lines (343 loc) 9.47 kB
import React, { forwardRef, useState } from 'react'; import PropTypes from 'prop-types'; import { css } from '@emotion/react'; import { graphql, useStaticQuery } from 'gatsby'; import Icon from './Icon'; import Link from './Link'; import composeHandlers from '../utils/composeHandlers'; import useSyncedRef from '../hooks/useSyncedRef'; import useKeyPress from '../hooks/useKeyPress'; const SIZES = { SMALL: 'small', MEDIUM: 'medium', LARGE: 'large', }; const ICONS = { FILTER: 'fe-filter', SEARCH: 'fe-search', }; const ICON_ALIGNMENTS = { LEFT: 'left', RIGHT: 'right', }; const HORIZONTAL_SPACING = { [SIZES.SMALL]: '0.5rem', [SIZES.MEDIUM]: '1rem', [SIZES.LARGE]: '1rem', }; const SearchInput = forwardRef( ( { alignIcon = 'left', className, focusWithHotKey, iconName = 'fe-search', isIconClickable = false, onBlur, onClear, onFocus, onSubmit, onMove, setValue, size = 'medium', showShortcut = false, value, width, ...props }, ref ) => { const { site: { layout: { mobileBreakpoint }, }, } = useStaticQuery(graphql` query SearchInputQuery { site { layout { mobileBreakpoint } } } `); const inputRef = useSyncedRef(ref); const [showHotKey, setShowHotkey] = useState(Boolean(focusWithHotKey)); useKeyPress(focusWithHotKey, (e) => { e.preventDefault(); inputRef.current.focus(); }); return ( <div width={width} className={className} css={css` --horizontal-spacing: ${HORIZONTAL_SPACING[size]}; border: var(--search-input-border); border-radius: 4px; position: relative; width: ${width || '100%'}; ${size && styles.size[size].container} `} > {isIconClickable ? ( <Link to={`search-results/?query=${value}&page=1`} css={css` color: var(--brand-button-primary-accent); &:hover { color: var(--brand-button-primary-accent-hover); } `} > <Icon css={css` position: absolute; ${alignIcon === 'right' ? css` right: var(--horizontal-spacing); ` : css` left: var(--horizontal-spacing); `} top: 50%; transform: translateY(-50%); `} name={iconName} size={styles.size[size].icon} /> </Link> ) : ( <Icon css={css` position: absolute; ${alignIcon === 'right' ? css` right: var(--horizontal-spacing); ` : css` left: var(--horizontal-spacing); `} top: 50%; transform: translateY(-50%); `} name={iconName} size={styles.size[size].icon} /> )} <input ref={inputRef} value={value} {...props} type="text" onInput={(e) => setValue(e.target.value)} onFocus={composeHandlers(onFocus, () => setShowHotkey(false))} onBlur={composeHandlers(onBlur, () => setShowHotkey(Boolean(focusWithHotKey)) )} onKeyDown={(e) => { switch (e.key) { case 'ArrowUp': onMove('prev'); break; case 'ArrowDown': onMove('next'); break; case 'Escape': // without this, if the user is in fullscreen on Mac Firefox, // Esc will exit fullscreen as well. e.preventDefault(); onClear && onClear(); e.target.blur(); break; case 'Enter': return onSubmit?.(value); default: // do nothing } }} css={css` width: 100%; border: 1px solid var(--primary-text-color); border-radius: 4px; background: var(--secondary-background-color); transition: 0.15s ease-out; line-height: 1; color: var(--primary-text-color); ${alignIcon === 'left' ? css` padding-left: calc( var(--horizontal-spacing) + 0.5rem + var(--icon-size) ); padding-right: var(--horizontal-spacing); ` : css` padding-left: var(--horizontal-spacing); padding-right: calc( var(--horizontal-spacing) + 0.5rem + var(--icon-size) ); `} ${size && styles.size[size].input} &:focus { outline: none; border: 1px solid rgba(0, 126, 138, 0.6); box-shadow: 0 0 0 4px rgba(0, 126, 138, 0.1); } `} /> {showShortcut && ( <kbd css={css` border: 1px solid currentColor; border-radius: 4px; display: inline-grid; line-height: 1.1; margin-right: 0.25rem; padding: 2px 4px; place-items: center; position: absolute; right: 0.5rem; top: 50%; transform: translateY(-50%); @media (max-width: ${mobileBreakpoint}) { display: none; } `} > / </kbd> )} {onClear && ( <button onClick={(e) => { e.preventDefault(); onClear(); }} css={css` display: none; right: ${alignIcon === 'right' ? ' calc(var(--horizontal-spacing) + 0.5rem + var(--icon-size))' : 'var(--horizontal-spacing)'}; top: 50%; transform: translateY(-50%); &:hover { cursor: pointer; } color: var(--primary-text-color); border: none; background: transparent; position: absolute; margin: 0; padding: 0; outline: none; z-index: 123; @media (max-width: ${mobileBreakpoint}) { display: block; } `} type="button" > <Icon name="fe-x" css={css` display: block; `} size={styles.size[size].icon} /> </button> )} {showHotKey && ( <span className="search-hotkey" css={css` position: absolute; right: var(--horizontal-spacing); top: 50%; transform: translateY(-50%); border: 1px solid var(--border-color); line-height: 1rem; text-align: center; background: var(--secondary-background-color); ${styles.size[size].hotkey} `} > {focusWithHotKey} </span> )} </div> ); } ); SearchInput.propTypes = { alignIcon: PropTypes.oneOf(Object.values(ICON_ALIGNMENTS)), className: PropTypes.string, focusWithHotKey: PropTypes.string, iconName: PropTypes.oneOf(Object.values(ICONS)), isIconClickable: PropTypes.bool, onBlur: PropTypes.func, onClear: PropTypes.func, onFocus: PropTypes.func, onMove: PropTypes.func.isRequired, onSubmit: PropTypes.func, setValue: PropTypes.func.isRequired, size: PropTypes.oneOf(Object.values(SIZES)), showShortcut: PropTypes.bool, value: PropTypes.string, width: PropTypes.string, }; SearchInput.SIZE = SIZES; SearchInput.ICONS = ICONS; SearchInput.ICON_ALIGNMENT = ICON_ALIGNMENTS; export default SearchInput; const styles = { size: { [SIZES.SMALL]: { input: css` font-size: 0.75rem; padding-top: 0.25rem; padding-bottom: 0.25rem; `, container: css` --icon-size: 0.75rem; `, hotkey: css` border-radius: 0.125rem; font-size: 0.675rem; padding: 0.0625rem 0.3125rem; `, icon: '0.75rem', }, [SIZES.MEDIUM]: { input: css` font-size: 0.875rem; padding-top: 0.5rem; padding-bottom: 0.5rem; `, container: css` --icon-size: 1rem; `, hotkey: css` border-radius: 0.125rem; font-size: 0.875rem; padding: 0.125rem 0.375rem; `, icon: '0.875rem', }, [SIZES.LARGE]: { input: css` font-size: 1.25rem; font-weight: 500; padding-top: 0.75rem; padding-bottom: 0.75rem; `, container: css` --icon-size: 1.5rem; `, hotkey: css` border-radius: 0.125rem; font-size: 1.25rem; padding: 0.125rem 0.4375rem; `, icon: '1.25rem', }, }, };