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
}