jpg619/version-bump-tar-2
@reggi/path-to-regexp
dependabot/npm_and_yarn/main/copy-to-clipboard-4.0.2
dependabot/npm_and_yarn/main/eslint-10.4.0
dependabot/npm_and_yarn/main/npmcli/eslint-config-7.0.0
dependabot/npm_and_yarn/main/proc-log-7.0.0
dependabot/npm_and_yarn/npm_and_yarn-826852524d
dependabot/npm_and_yarn/npm_and_yarn-ab9a7f4bc2
deprecate-totp-2fa
dhei/classic-tokens
gat-bypass-2fa-docs
jpg619/fix-accessibility-content-flow
jpg619/version-bump-tar-2
kartykp/gat-bypass-2fa-docs
kartykp/upgrade-path-to-regex
main
maitxn/version-bump-tar
patch-1
reggi/cache-based-on-version
reggi/dev-engines
reggi/fix-transform-prettier
reggi/overrides
update-search-sensitivity
| 1 | import React from 'react' |
| 2 | import {Button, ActionList, Text} from '@primer/react' |
| 3 | import {XIcon, SearchIcon} from '@primer/octicons-react' |
| 4 | import {AnimatePresence, motion} from 'framer-motion' |
| 5 | import {FocusOn} from 'react-focus-on' |
| 6 | import TextInput from './text-input' |
| 7 | import useSiteMetadata from '../hooks/use-site-metadata' |
| 8 | import {HEADER_BAR, HEADER_HEIGHT, Z_INDEX} from '../constants' |
| 9 | import {LightTheme} from '../theme' |
| 10 | import {LinkNoUnderline} from './link' |
| 11 | import * as getNav from '../util/get-nav' |
| 12 | import omit from '../util/omit' |
| 13 | import {announce} from '../util/aria-live' |
| 14 | |
| 15 | import * as styles from './search.module.css' |
| 16 | |
| 17 | const SearchResults = ({results, getItemProps, highlightedIndex}) => { |
| 18 | const siteMetadata = useSiteMetadata() |
| 19 | if (!results || results.length === 0) { |
| 20 | announce('No results') |
| 21 | return ( |
| 22 | <div aria-live="polite" className={styles.Box}> |
| 23 | No results |
| 24 | </div> |
| 25 | ) |
| 26 | } |
| 27 | |
| 28 | return ( |
| 29 | <ActionList> |
| 30 | {results.map((item, index) => { |
| 31 | // keep the variant in the breadcrumb if we have one and its not the |
| 32 | // same as the last breadcrumb. this makes sure that variant index pages |
| 33 | // don't all appear the same in the search results |
| 34 | const variant = getNav.getVariant(getNav.getVariantRoot(item.path), item.path) |
| 35 | const hierarchy = getNav.getItemBreadcrumbs(item.path) |
| 36 | if (!variant || variant !== hierarchy[hierarchy.length - 1]?.shortName) { |
| 37 | hierarchy.pop() |
| 38 | } |
| 39 | |
| 40 | return ( |
| 41 | <ActionList.Item |
| 42 | key={item.path} |
| 43 | // fixes a bug between downshift and react/primer where the search result item is always disabled |
| 44 | // this is safe to remove because we know we never have any disabled search results |
| 45 | {...omit(getItemProps({item, index}), 'aria-disabled')} |
| 46 | as={LinkNoUnderline} |
| 47 | to={item.path} |
| 48 | active={highlightedIndex === index} |
| 49 | > |
| 50 | <div |
| 51 | aria-label={`${item.title}${hierarchy.length ? ` in ${hierarchy.map(s => s.shortName || s.title).join(' / ')}` : ` in ${siteMetadata.shortName}`}, ${index + 1} of ${results.length}`} |
| 52 | className={styles.Box_1} |
| 53 | > |
| 54 | <Text className={styles.Text}> |
| 55 | {hierarchy.length ? hierarchy.map(s => s.shortName || s.title).join(' / ') : siteMetadata.shortName} |
| 56 | </Text> |
| 57 | <Text>{item.title}</Text> |
| 58 | </div> |
| 59 | </ActionList.Item> |
| 60 | ) |
| 61 | })} |
| 62 | </ActionList> |
| 63 | ) |
| 64 | } |
| 65 | |
| 66 | export const Desktop = props => { |
| 67 | const siteMetadata = useSiteMetadata() |
| 68 | const {getInputProps, getMenuProps, resultsOpen, ...rest} = props |
| 69 | |
| 70 | return ( |
| 71 | <div className={styles.Box_2}> |
| 72 | <TextInput |
| 73 | placeholder={`Search ${siteMetadata.title}`} |
| 74 | aria-label={`Search ${siteMetadata.title}`} |
| 75 | className={styles.TextInput} |
| 76 | {...getInputProps()} |
| 77 | /> |
| 78 | <div className={styles.Box_3} {...getMenuProps()}> |
| 79 | {resultsOpen ? ( |
| 80 | <LightTheme className={styles.LightTheme}> |
| 81 | <SearchResults {...rest} /> |
| 82 | </LightTheme> |
| 83 | ) : null} |
| 84 | </div> |
| 85 | </div> |
| 86 | ) |
| 87 | } |
| 88 | |
| 89 | export const Mobile = ({ |
| 90 | resultsOpen, |
| 91 | getInputProps, |
| 92 | getMenuProps, |
| 93 | isMobileSearchOpen, |
| 94 | setMobileSearchOpen, |
| 95 | isForceClose, |
| 96 | resetAndClose, |
| 97 | ...rest |
| 98 | }) => { |
| 99 | const siteMetadata = useSiteMetadata() |
| 100 | const getCloseAnimation = exit => (isForceClose ? undefined : {exit}) |
| 101 | |
| 102 | const handleSearchToggle = React.useCallback(() => { |
| 103 | setMobileSearchOpen(true) |
| 104 | }, [setMobileSearchOpen]) |
| 105 | |
| 106 | return ( |
| 107 | <> |
| 108 | {!isMobileSearchOpen && ( |
| 109 | <Button |
| 110 | aria-label="Search" |
| 111 | aria-expanded={isMobileSearchOpen} |
| 112 | onClick={handleSearchToggle} |
| 113 | className={styles.Button} |
| 114 | > |
| 115 | <SearchIcon /> |
| 116 | </Button> |
| 117 | )} |
| 118 | <AnimatePresence> |
| 119 | {isMobileSearchOpen ? ( |
| 120 | <FocusOn returnFocus={true} onEscapeKey={() => resetAndClose(true)}> |
| 121 | <div |
| 122 | style={{ |
| 123 | top: `${HEADER_BAR}px`, |
| 124 | zIndex: Z_INDEX.SEARCH_OVERLAY, |
| 125 | }} |
| 126 | className={styles.Box_4} |
| 127 | > |
| 128 | <motion.div |
| 129 | key="search-backdrop" |
| 130 | initial={{opacity: 0}} |
| 131 | animate={{opacity: 1}} |
| 132 | transition={{type: 'tween'}} |
| 133 | onClick={() => resetAndClose(true)} |
| 134 | className={styles.Box_5} |
| 135 | {...getCloseAnimation({opacity: 0})} |
| 136 | /> |
| 137 | <div |
| 138 | style={{ |
| 139 | height: resultsOpen ? '100%' : 'auto', |
| 140 | }} |
| 141 | className={styles.Box_6} |
| 142 | > |
| 143 | <div |
| 144 | style={{ |
| 145 | height: `${HEADER_HEIGHT}px`, |
| 146 | zIndex: Z_INDEX.SEARCH_OVERLAY + 1, |
| 147 | }} |
| 148 | className={styles.Box_7} |
| 149 | > |
| 150 | <motion.div |
| 151 | key="search-box" |
| 152 | initial={{scaleX: 0}} |
| 153 | animate={{scaleX: 1}} |
| 154 | {...getCloseAnimation({scaleX: 0})} |
| 155 | transition={{type: 'tween', ease: 'easeOut', duration: 0.2}} |
| 156 | style={{width: '100%', originX: '100%'}} |
| 157 | > |
| 158 | <div className={styles.Box_8} /> |
| 159 | <TextInput |
| 160 | leadingVisual={SearchIcon} |
| 161 | placeholder={`Search ${siteMetadata.title}`} |
| 162 | aria-label={`Search ${siteMetadata.title}`} |
| 163 | className={styles.TextInput_1} |
| 164 | {...getInputProps()} |
| 165 | /> |
| 166 | </motion.div> |
| 167 | <motion.div |
| 168 | key="button-blocker" |
| 169 | initial={{opacity: 0}} |
| 170 | animate={{opacity: 1}} |
| 171 | {...getCloseAnimation({scaleX: 0})} |
| 172 | transition={{type: 'tween', ease: 'easeOut', duration: 0.2}} |
| 173 | className={styles.Box_9} |
| 174 | /> |
| 175 | <motion.div |
| 176 | key="button" |
| 177 | initial={{opacity: 0}} |
| 178 | animate={{opacity: 1}} |
| 179 | {...getCloseAnimation({opacity: 0})} |
| 180 | transition={{type: 'tween', ease: 'easeOut', duration: 0.2}} |
| 181 | > |
| 182 | <Button aria-label="Cancel" onClick={() => resetAndClose(false)} className={styles.Button_1}> |
| 183 | <XIcon /> |
| 184 | </Button> |
| 185 | </motion.div> |
| 186 | </div> |
| 187 | <LightTheme |
| 188 | style={{ |
| 189 | WebkitOverflowScrolling: 'touch', |
| 190 | }} |
| 191 | className={styles.LightTheme_1} |
| 192 | {...getMenuProps()} |
| 193 | > |
| 194 | {resultsOpen ? <SearchResults {...rest} /> : null} |
| 195 | </LightTheme> |
| 196 | </div> |
| 197 | </div> |
| 198 | </FocusOn> |
| 199 | ) : null} |
| 200 | </AnimatePresence> |
| 201 | </> |
| 202 | ) |
| 203 | } |