diff --git a/package.json b/package.json index a2abdcc404..96fb062db7 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "format": "biome format --write .", "format:check": "biome format src integrations plugins scripts", "check": "astro check && eslint . && biome check .", - "check:links": "linkinator dist/ --recurse --retry-errors --retry-errors-count 3 --timeout 30000 --concurrency 25 --verbosity error --skip 'github.com/.*/edit/' --skip 'https://docs.mergify.com' --skip 'https://slack.mergify.com'" + "check:links": "linkinator dist/ --recurse --concurrency 25 --verbosity error --skip 'https?://'" }, "devDependencies": { "@actions/core": "^3.0.1", diff --git a/src/components/Modal/Modal.css b/src/components/Modal/Modal.css index 06de3e0e1e..b59e3e3963 100644 --- a/src/components/Modal/Modal.css +++ b/src/components/Modal/Modal.css @@ -1,5 +1,4 @@ .modal { - display: none; position: fixed; z-index: 100; left: 0; @@ -7,8 +6,19 @@ width: 100%; height: 100%; overflow: hidden; - background-color: rgb(0, 0, 0); - background-color: rgba(0, 0, 0, 0.4); + background-color: rgba(0, 0, 0, 0); + backdrop-filter: blur(0); + -webkit-backdrop-filter: blur(0); + transition: + background-color 180ms ease, + backdrop-filter 180ms ease, + -webkit-backdrop-filter 180ms ease; +} + +.modal.modal-open { + background-color: rgba(0, 0, 0, 0.35); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); } .modal-content { @@ -23,7 +33,23 @@ top: 50%; left: 50%; overflow: hidden; - transform: translate(-50%, -50%); + opacity: 0; + transform: translate(-50%, calc(-50% + 8px)) scale(0.98); + transition: + opacity 180ms ease, + transform 220ms cubic-bezier(0.22, 1, 0.36, 1); +} + +.modal.modal-open .modal-content { + opacity: 1; + transform: translate(-50%, -50%) scale(1); +} + +@media (prefers-reduced-motion: reduce) { + .modal, + .modal-content { + transition: none; + } } .close { diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx index 27ef1d6759..6a24e615ac 100644 --- a/src/components/Modal/Modal.tsx +++ b/src/components/Modal/Modal.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useEffect, useRef } from 'react'; +import { ReactNode, useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import './Modal.css'; @@ -8,8 +8,25 @@ interface Props { children: ReactNode; } +const TRANSITION_MS = 180; + export default function Modal({ open, onClose, children }: Props) { const modal = useRef(null); + // Decouple "in DOM" from "visually open" so we can transition out before + // unmounting. mounted controls render; visible drives the .modal-open class. + const [mounted, setMounted] = useState(open); + const [visible, setVisible] = useState(false); + + useEffect(() => { + if (open) { + setMounted(true); + const raf = requestAnimationFrame(() => setVisible(true)); + return () => cancelAnimationFrame(raf); + } + setVisible(false); + const timeout = setTimeout(() => setMounted(false), TRANSITION_MS); + return () => clearTimeout(timeout); + }, [open]); useEffect(() => { const close = (e: MouseEvent) => { @@ -21,10 +38,12 @@ export default function Modal({ open, onClose, children }: Props) { window.addEventListener('click', close); return () => window.removeEventListener('click', close); - }, []); + }, [onClose]); + + if (!mounted) return null; return createPortal( -
+
{children}
, document.body diff --git a/src/components/Search/PageDetails.tsx b/src/components/Search/PageDetails.tsx index 7b094ef1d7..7daf5fb973 100644 --- a/src/components/Search/PageDetails.tsx +++ b/src/components/Search/PageDetails.tsx @@ -113,7 +113,7 @@ export default function Preview({ entry }: PreviewProps) {
{ const entry = results[index]; @@ -126,6 +127,7 @@ export default function Results({ results, query, onNavigate }: ResultsProps) { useEffect(() => { setFocusedIndex(0); + setScrolled(false); // Prefetch preview HTML for the top results so the preview pane is instant. for (const entry of results.slice(0, 5)) { prefetchSectionHtml(entry); @@ -149,8 +151,11 @@ export default function Results({ results, query, onNavigate }: ResultsProps) { } return ( -
-
+
+
setScrolled(e.currentTarget.scrollTop > 4)} + > {results.map((entry, i) => ( Search Mergify Docs - + +