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