kartykp/gat-bypass-2fa-docs
@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, Box, 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 | const SearchResults = ({results, getItemProps, highlightedIndex}) => { |
| 16 | const siteMetadata = useSiteMetadata() |
| 17 | if (!results || results.length === 0) { |
| 18 | announce('No results') |
| 19 | return ( |
| 20 | <Box sx={{fontSize: 2, px: 3, py: 3}} aria-live="polite"> |
| 21 | No results |
| 22 | </Box> |
| 23 | ) |
| 24 | } |
| 25 | |
| 26 | return ( |
| 27 | <ActionList> |
| 28 | {results.map((item, index) => { |
| 29 | // keep the variant in the breadcrumb if we have one and its not the |
| 30 | // same as the last breadcrumb. this makes sure that variant index pages |
| 31 | // don't all appear the same in the search results |
| 32 | const variant = getNav.getVariant(getNav.getVariantRoot(item.path), item.path) |
| 33 | const hierarchy = getNav.getItemBreadcrumbs(item.path) |
| 34 | if (!variant || variant !== hierarchy[hierarchy.length - 1]?.shortName) { |
| 35 | hierarchy.pop() |
| 36 | } |
| 37 | |
| 38 | return ( |
| 39 | <ActionList.Item |
| 40 | key={item.path} |
| 41 | // fixes a bug between downshift and react/primer where the search result item is always disabled |
| 42 | // this is safe to remove because we know we never have any disabled search results |
| 43 | {...omit(getItemProps({item, index}), 'aria-disabled')} |
| 44 | as={LinkNoUnderline} |
| 45 | to={item.path} |
| 46 | active={highlightedIndex === index} |
| 47 | > |
| 48 | <Box |
| 49 | sx={{display: 'flex', flexDirection: 'column', flex: '0 0 auto'}} |
| 50 | aria-label={`${item.title}${hierarchy.length ? ` in ${hierarchy.map(s => s.shortName || s.title).join(' / ')}` : ` in ${siteMetadata.shortName}`}, ${index + 1} of ${results.length}`} |
| 51 | > |
| 52 | <Text sx={{fontSize: 0}}> |
| 53 | {hierarchy.length ? hierarchy.map(s => s.shortName || s.title).join(' / ') : siteMetadata.shortName} |
| 54 | </Text> |
| 55 | <Text>{item.title}</Text> |
| 56 | </Box> |
| 57 | </ActionList.Item> |
| 58 | ) |
| 59 | })} |
| 60 | </ActionList> |
| 61 | ) |
| 62 | } |
| 63 | |
| 64 | export const Desktop = props => { |
| 65 | const siteMetadata = useSiteMetadata() |
| 66 | const {getInputProps, getMenuProps, resultsOpen, ...rest} = props |
| 67 | |
| 68 | return ( |
| 69 | <Box sx={{position: 'relative'}}> |
| 70 | <TextInput |
| 71 | sx={{width: '240px'}} |
| 72 | placeholder={`Search ${siteMetadata.title}`} |
| 73 | aria-label={`Search ${siteMetadata.title}`} |
| 74 | {...getInputProps()} |
| 75 | /> |
| 76 | <Box sx={{position: 'absolute', left: 0, right: 0, pt: 1}} {...getMenuProps()}> |
| 77 | {resultsOpen ? ( |
| 78 | <LightTheme |
| 79 | sx={{ |
| 80 | overflow: 'auto', |
| 81 | minWidth: 300, |
| 82 | maxHeight: '70vh', |
| 83 | boxShadow: 'shadow.large', |
| 84 | borderColor: 'border.muted', |
| 85 | bg: 'canvas.overlay', |
| 86 | borderRadius: 2, |
| 87 | borderWidth: 1, |
| 88 | borderStyle: 'solid', |
| 89 | }} |
| 90 | > |
| 91 | <SearchResults {...rest} /> |
| 92 | </LightTheme> |
| 93 | ) : null} |
| 94 | </Box> |
| 95 | </Box> |
| 96 | ) |
| 97 | } |
| 98 | |
| 99 | export const Mobile = ({ |
| 100 | resultsOpen, |
| 101 | getInputProps, |
| 102 | getMenuProps, |
| 103 | isMobileSearchOpen, |
| 104 | setMobileSearchOpen, |
| 105 | isForceClose, |
| 106 | resetAndClose, |
| 107 | ...rest |
| 108 | }) => { |
| 109 | const siteMetadata = useSiteMetadata() |
| 110 | const getCloseAnimation = exit => (isForceClose ? undefined : {exit}) |
| 111 | |
| 112 | const handleSearchToggle = React.useCallback(() => { |
| 113 | setMobileSearchOpen(true) |
| 114 | }, [setMobileSearchOpen]) |
| 115 | |
| 116 | return ( |
| 117 | <> |
| 118 | {!isMobileSearchOpen && ( |
| 119 | <Button |
| 120 | aria-label="Search" |
| 121 | aria-expanded={isMobileSearchOpen} |
| 122 | onClick={handleSearchToggle} |
| 123 | sx={{ |
| 124 | '&:focus-visible': { |
| 125 | outline: '2px solid', |
| 126 | outlineColor: '-webkit-focus-ring-color', |
| 127 | outlineOffset: '1px', |
| 128 | }, |
| 129 | }} |
| 130 | > |
| 131 | <SearchIcon /> |
| 132 | </Button> |
| 133 | )} |
| 134 | |
| 135 | <AnimatePresence> |
| 136 | {isMobileSearchOpen ? ( |
| 137 | <FocusOn returnFocus={true} onEscapeKey={() => resetAndClose(true)}> |
| 138 | <Box |
| 139 | sx={{ |
| 140 | position: 'fixed', |
| 141 | top: `${HEADER_BAR}px`, |
| 142 | left: 0, |
| 143 | right: 0, |
| 144 | bottom: 0, |
| 145 | zIndex: Z_INDEX.SEARCH_OVERLAY, |
| 146 | }} |
| 147 | > |
| 148 | <Box |
| 149 | sx={{ |
| 150 | position: 'absolute', |
| 151 | top: 0, |
| 152 | left: 0, |
| 153 | right: 0, |
| 154 | bottom: 0, |
| 155 | bg: 'overlay.backdrop', |
| 156 | zIndex: -1, |
| 157 | }} |
| 158 | key="search-backdrop" |
| 159 | as={motion.div} |
| 160 | initial={{opacity: 0}} |
| 161 | animate={{opacity: 1}} |
| 162 | transition={{type: 'tween'}} |
| 163 | onClick={() => resetAndClose(true)} |
| 164 | {...getCloseAnimation({opacity: 0})} |
| 165 | /> |
| 166 | <Box sx={{display: 'flex', flexDirection: 'column', height: resultsOpen ? '100%' : 'auto'}}> |
| 167 | <Box |
| 168 | sx={{ |
| 169 | display: 'flex', |
| 170 | color: 'fg.default', |
| 171 | height: `${HEADER_HEIGHT}px`, |
| 172 | flex: '0 0 auto', |
| 173 | px: 3, |
| 174 | alignItems: 'center', |
| 175 | border: '1px solid', |
| 176 | borderTopWidth: 0, |
| 177 | borderLeftWidth: 0, |
| 178 | borderRightWidth: 0, |
| 179 | borderColor: 'border.muted', |
| 180 | position: 'relative', |
| 181 | bg: 'canvas.default', |
| 182 | zIndex: Z_INDEX.SEARCH_OVERLAY + 1, |
| 183 | }} |
| 184 | > |
| 185 | <motion.div |
| 186 | key="search-box" |
| 187 | initial={{scaleX: 0}} |
| 188 | animate={{scaleX: 1}} |
| 189 | {...getCloseAnimation({scaleX: 0})} |
| 190 | transition={{type: 'tween', ease: 'easeOut', duration: 0.2}} |
| 191 | style={{width: '100%', originX: '100%'}} |
| 192 | > |
| 193 | <Box |
| 194 | sx={{ |
| 195 | position: 'absolute', |
| 196 | top: 0, |
| 197 | bottom: 0, |
| 198 | left: 0, |
| 199 | width: '70px', |
| 200 | bg: 'canvas.default', |
| 201 | zIndex: '-1', |
| 202 | }} |
| 203 | /> |
| 204 | <TextInput |
| 205 | leadingVisual={SearchIcon} |
| 206 | placeholder={`Search ${siteMetadata.title}`} |
| 207 | aria-label={`Search ${siteMetadata.title}`} |
| 208 | sx={{width: '100%'}} |
| 209 | {...getInputProps()} |
| 210 | /> |
| 211 | </motion.div> |
| 212 | <Box |
| 213 | key="button-blocker" |
| 214 | as={motion.div} |
| 215 | initial={{opacity: 0}} |
| 216 | animate={{opacity: 1}} |
| 217 | {...getCloseAnimation({scaleX: 0})} |
| 218 | transition={{type: 'tween', ease: 'easeOut', duration: 0.2}} |
| 219 | sx={{ |
| 220 | position: 'absolute', |
| 221 | top: 0, |
| 222 | bottom: 0, |
| 223 | right: 0, |
| 224 | width: '70px', |
| 225 | bg: 'canvas.default', |
| 226 | zIndex: '-1', |
| 227 | }} |
| 228 | /> |
| 229 | <Box |
| 230 | key="button" |
| 231 | as={motion.div} |
| 232 | initial={{opacity: 0}} |
| 233 | animate={{opacity: 1}} |
| 234 | {...getCloseAnimation({opacity: 0})} |
| 235 | transition={{type: 'tween', ease: 'easeOut', duration: 0.2}} |
| 236 | > |
| 237 | <Button sx={{ml: 3}} aria-label="Cancel" onClick={() => resetAndClose(false)}> |
| 238 | <XIcon /> |
| 239 | </Button> |
| 240 | </Box> |
| 241 | </Box> |
| 242 | <LightTheme |
| 243 | sx={{ |
| 244 | display: 'flex', |
| 245 | bg: 'canvas.default', |
| 246 | flexDirection: 'column', |
| 247 | flex: '1 1 auto', |
| 248 | overflow: 'auto', |
| 249 | }} |
| 250 | style={{ |
| 251 | WebkitOverflowScrolling: 'touch', |
| 252 | }} |
| 253 | {...getMenuProps()} |
| 254 | > |
| 255 | {resultsOpen ? <SearchResults {...rest} /> : null} |
| 256 | </LightTheme> |
| 257 | </Box> |
| 258 | </Box> |
| 259 | </FocusOn> |
| 260 | ) : null} |
| 261 | </AnimatePresence> |
| 262 | </> |
| 263 | ) |
| 264 | } |