(isOpen: boolean)
| 178 | let [isTransitioning, setTransitioning] = useState(false); |
| 179 | let renderCallback = useRef<(() => void) | null>(null); |
| 180 | let onOpenChange = (isOpen: boolean) => { |
| 181 | if (!document.startViewTransition) { |
| 182 | setOpen(false); |
| 183 | if (isOpen) { |
| 184 | setWasOpen(true); |
| 185 | } |
| 186 | return; |
| 187 | } |
| 188 | |
| 189 | // Don't transition the entire page. |
| 190 | document.documentElement.style.viewTransitionName = 'none'; |
| 191 | |
| 192 | // Only transition label if it is visible (scrolled to the top of the page). |
| 193 | if (window.scrollY === 0 && labelRef.current && isOpen) { |
| 194 | labelRef.current.style.viewTransitionName = 'search-menu-label'; |
| 195 | } |
| 196 | |
| 197 | let viewTransition = document.startViewTransition(() => { |
| 198 | if (labelRef.current && window.scrollY === 0) { |
| 199 | labelRef.current.style.viewTransitionName = !isOpen ? 'search-menu-label' : ''; |
| 200 | } |
| 201 | |
| 202 | // Wait until next render. Using flushSync causes flickering. |
| 203 | return new Promise<void>(resolve => { |
| 204 | renderCallback.current = resolve; |
| 205 | setOpen(isOpen); |
| 206 | setTransitioning(true); |
| 207 | if (isOpen) { |
| 208 | setWasOpen(true); |
| 209 | } |
| 210 | }); |
| 211 | }); |
| 212 | |
| 213 | viewTransition.finished.then(() => { |
| 214 | document.documentElement.style.viewTransitionName = ''; |
| 215 | labelRef.current!.style.viewTransitionName = ''; |
| 216 | setTransitioning(false); |
| 217 | }); |
| 218 | }; |
| 219 | |
| 220 | useLayoutEffect(() => { |
| 221 | renderCallback.current?.(); |
no test coverage detected