Less janky mobile search animations

Luke Karrys committed Oct 21, 2023 at 23:57 UTC 6afcd7afb514ff3f9eb1b640cc9d5851757faf85
1 file changed +126 -84
src/components/mobile-search.js
+126 -84
@@ -9,114 +9,156 @@ import useSiteMetadata from '../hooks/use-site-metadata'
9 import {HEADER_BAR, HEADER_HEIGHT} from '../constants'
10 import {LightTheme} from '../theme'
11
12 -function MobileSearch({onDismiss, ...props}) {
12 +function MobileSearch({open, setOpen, ...props}) {
13 const siteMetadata = useSiteMetadata()
14 - const {reset, results, isOpen, getInputProps, getItemProps, getMenuProps, highlightedIndex} = props
14 + const {reset, results, isOpen: resultsOpen, getInputProps, getItemProps, getMenuProps, highlightedIndex} = props
15
16 const handleDismiss = () => {
17 reset()
18 - onDismiss()
18 + setOpen(false)
19 }
20
21 return (
22 - <FocusOn returnFocus={true} onEscapeKey={handleDismiss}>
23 - <Box
24 - sx={{
25 - position: 'fixed',
26 - top: `${HEADER_BAR}px`,
27 - left: 0,
28 - right: 0,
29 - bottom: 0,
30 - zIndex: 1,
31 - }}
32 - >
33 - <Box
34 - sx={{
35 - position: 'absolute',
36 - top: 0,
37 - left: 0,
38 - right: 0,
39 - bottom: 0,
40 - bg: 'canvas.backdrop',
41 - zIndex: -1,
42 - }}
43 - as={motion.div}
44 - initial={{opacity: 0}}
45 - animate={{opacity: 1}}
46 - exit={{opacity: 0}}
47 - onClick={handleDismiss}
48 - />
49 - <Box sx={{display: 'flex', flexDirection: 'column', height: isOpen ? '100%' : 'auto'}}>
22 + <AnimatePresence>
23 + {open ? (
24 + <FocusOn returnFocus={true} onEscapeKey={handleDismiss}>
25 <Box
26 sx={{
52 - display: 'flex',
53 - bg: 'canvas.default',
54 - color: 'fg.default',
55 - height: `${HEADER_HEIGHT}px`,
56 - flex: '0 0 auto',
57 - px: 3,
58 - alignItems: 'center',
59 - border: '1px solid',
60 - borderTopWidth: 0,
61 - borderLeftWidth: 0,
62 - borderRightWidth: 0,
63 - borderColor: 'border.muted',
27 + position: 'fixed',
28 + top: `${HEADER_BAR}px`,
29 + left: 0,
30 + right: 0,
31 + bottom: 0,
32 + zIndex: 1,
33 }}
34 >
66 - <motion.div
67 - initial={{scaleX: 0.1}}
68 - animate={{scaleX: 1}}
69 - exit={{scaleX: 0.1, transition: {duration: 0.1}}}
70 - transition={{type: 'tween', duration: 0.2}}
71 - style={{width: '100%', originX: '100%'}}
72 - >
73 - <TextInput
74 - leadingVisual={SearchIcon}
75 - placeholder={`Search ${siteMetadata.title}`}
76 - aria-label={`Search ${siteMetadata.title}`}
77 - sx={{width: '100%'}}
78 - {...getInputProps()}
79 - />
80 - </motion.div>
81 - <Button sx={{ml: 3}} aria-label="Cancel" onClick={handleDismiss}>
82 - <XIcon />
83 - </Button>
84 - </Box>
85 - <LightTheme>
35 <Box
36 sx={{
88 - display: 'flex',
89 - bg: 'canvas.default',
90 - py: isOpen ? 1 : 0,
91 - flexDirection: 'column',
92 - flex: '1 1 auto',
93 - overflow: 'auto',
94 - }}
95 - style={{
96 - WebkitOverflowScrolling: 'touch',
37 + position: 'absolute',
38 + top: 0,
39 + left: 0,
40 + right: 0,
41 + bottom: 0,
42 + bg: 'overlay.backdrop',
43 + zIndex: -1,
44 }}
98 - {...getMenuProps()}
99 - >
100 - {isOpen ? <SearchResults {...{results, getItemProps, highlightedIndex}} /> : null}
45 + key="search-backdrop"
46 + as={motion.div}
47 + initial={{opacity: 0}}
48 + animate={{opacity: 1}}
49 + exit={{opacity: 0}}
50 + transition={{type: 'tween'}}
51 + onClick={handleDismiss}
52 + />
53 + <Box sx={{display: 'flex', flexDirection: 'column', height: resultsOpen ? '100%' : 'auto'}}>
54 + <Box
55 + sx={{
56 + display: 'flex',
57 + color: 'fg.default',
58 + height: `${HEADER_HEIGHT}px`,
59 + flex: '0 0 auto',
60 + px: 3,
61 + alignItems: 'center',
62 + border: '1px solid',
63 + borderTopWidth: 0,
64 + borderLeftWidth: 0,
65 + borderRightWidth: 0,
66 + borderColor: 'border.muted',
67 + position: 'relative',
68 + }}
69 + >
70 + <motion.div
71 + key="search-box"
72 + initial={{scaleX: 0}}
73 + animate={{scaleX: 1}}
74 + exit={{scaleX: 0}}
75 + transition={{type: 'tween', ease: 'easeOut', duration: 0.2}}
76 + style={{width: '100%', originX: '100%'}}
77 + >
78 + <Box
79 + sx={{
80 + position: 'absolute',
81 + top: 0,
82 + bottom: 0,
83 + left: 0,
84 + width: '70px',
85 + bg: 'canvas.default',
86 + zIndex: '-1',
87 + }}
88 + />
89 + <TextInput
90 + leadingVisual={SearchIcon}
91 + placeholder={`Search ${siteMetadata.title}`}
92 + aria-label={`Search ${siteMetadata.title}`}
93 + sx={{width: '100%'}}
94 + {...getInputProps()}
95 + />
96 + </motion.div>
97 + <Box
98 + key="button-blocker"
99 + as={motion.div}
100 + initial={{opacity: 0}}
101 + animate={{opacity: 1}}
102 + exit={{opacity: 0}}
103 + transition={{type: 'tween', ease: 'easeOut', duration: 0.2}}
104 + sx={{
105 + position: 'absolute',
106 + top: 0,
107 + bottom: 0,
108 + right: 0,
109 + width: '70px',
110 + bg: 'canvas.default',
111 + zIndex: '-1',
112 + }}
113 + />
114 + <Box
115 + key="button"
116 + as={motion.div}
117 + initial={{opacity: 0}}
118 + animate={{opacity: 1}}
119 + exit={{opacity: 0}}
120 + transition={{type: 'tween', ease: 'easeOut', duration: 0.2}}
121 + >
122 + <Button sx={{ml: 3}} aria-label="Cancel" onClick={handleDismiss}>
123 + <XIcon />
124 + </Button>
125 + </Box>
126 + </Box>
127 + <LightTheme>
128 + <Box
129 + sx={{
130 + display: 'flex',
131 + bg: 'canvas.default',
132 + py: resultsOpen ? 1 : 0,
133 + flexDirection: 'column',
134 + flex: '1 1 auto',
135 + overflow: 'auto',
136 + }}
137 + style={{
138 + WebkitOverflowScrolling: 'touch',
139 + }}
140 + {...getMenuProps()}
141 + >
142 + {resultsOpen ? <SearchResults {...{results, getItemProps, highlightedIndex}} /> : null}
143 + </Box>
144 + </LightTheme>
145 </Box>
102 - </LightTheme>
103 - </Box>
104 - </Box>
105 - </FocusOn>
146 + </Box>
147 + </FocusOn>
148 + ) : null}
149 + </AnimatePresence>
150 )
151 }
152
153 function MobileSearchWrapper(props) {
110 - const [isOpen, setIsOpen] = React.useState(false)
154 + const [open, setOpen] = React.useState(false)
155
156 return (
157 <>
114 - <Button aria-label="Search" aria-expanded={isOpen} onClick={() => setIsOpen(true)}>
158 + <Button aria-label="Search" aria-expanded={open} onClick={() => setOpen(true)}>
159 <SearchIcon />
160 </Button>
117 - <AnimatePresence>
118 - {isOpen ? <MobileSearch onDismiss={() => setIsOpen(false)} {...props} /> : null}
119 - </AnimatePresence>
161 + <MobileSearch open={open} setOpen={setOpen} {...props} />
162 </>
163 )
164 }