Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions components/home-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@ import { Button } from "@/components/ui/button"
import { triggerHaptic } from "@/lib/haptics"
import { themeAwareHeaderPrimaryCtaClasses } from "@/lib/header-primary-cta-classes"
import { getPersonById, splitPersonDisplayName } from "@/lib/people"
import { useMobileDownloadPrompt } from "@/components/mobile-download-prompt"

interface HomeContentProps {
highlightReadTimes: Record<string, string>
}

export function HomeContent({ highlightReadTimes }: HomeContentProps) {
const { openMobileDownloadPrompt, mobileDownloadPrompt } = useMobileDownloadPrompt()
const sectionHeadingClass = "text-2xl font-semibold tracking-tight text-foreground sm:text-3xl"
const alphaPillClass =
"inline-flex items-center rounded-full border border-black/20 px-1.5 py-0.5 text-[0.62rem] tracking-[0.08em] text-black/60 dark:border-white/25 dark:text-[#B9B9B9] sm:text-[0.66rem]"
Expand Down Expand Up @@ -139,10 +137,7 @@ export function HomeContent({ highlightReadTimes }: HomeContentProps) {
>
<Link
href="/download"
onClick={(event) => {
if (openMobileDownloadPrompt(event)) {
return
}
onClick={() => {
triggerHaptic()
}}
className="group flex items-center gap-2"
Expand Down Expand Up @@ -564,8 +559,6 @@ export function HomeContent({ highlightReadTimes }: HomeContentProps) {

</div>
</main>

{mobileDownloadPrompt}
<SiteFooter />
</div>
)
Expand Down
79 changes: 49 additions & 30 deletions components/mobile-download-prompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { CalendarPlus, Check, Link2, Monitor } from "lucide-react"
import { useCallback, useEffect, useState } from "react"
import { createPortal } from "react-dom"
import { triggerHaptic } from "@/lib/haptics"
import { FaArrowUpFromBracket } from "react-icons/fa6"

Expand Down Expand Up @@ -98,6 +99,7 @@ interface MobileDownloadPromptOverlayProps {

function MobileDownloadPromptOverlay({ isOpen, onClose, targetUrl }: MobileDownloadPromptOverlayProps) {
const [isCopyConfirmed, setIsCopyConfirmed] = useState(false)
const [viewportBox, setViewportBox] = useState({ top: 0, height: 0 })
const closePrompt = useCallback(() => {
triggerHaptic()
onClose()
Expand All @@ -124,6 +126,28 @@ function MobileDownloadPromptOverlay({ isOpen, onClose, targetUrl }: MobileDownl
}
}, [isOpen, onClose])

useEffect(() => {
if (!isOpen) {
return
}

const updateViewportBox = () => {
setViewportBox({
top: window.scrollY || window.pageYOffset || 0,
height: window.innerHeight || 0,
})
}

updateViewportBox()
window.addEventListener("resize", updateViewportBox)
window.addEventListener("orientationchange", updateViewportBox)

return () => {
window.removeEventListener("resize", updateViewportBox)
window.removeEventListener("orientationchange", updateViewportBox)
}
}, [isOpen])

const onShare = useCallback(async () => {
triggerHaptic()
if (navigator.share) {
Expand Down Expand Up @@ -174,13 +198,21 @@ function MobileDownloadPromptOverlay({ isOpen, onClose, targetUrl }: MobileDownl
return null
}

return (
if (typeof document === "undefined") {
return null
}

return createPortal(
<div
className="fixed inset-0 z-[120] flex items-end bg-background/55 text-foreground backdrop-blur-[2px] dark:bg-background/70"
className="absolute inset-x-0 z-[120] flex items-end bg-background/55 text-foreground backdrop-blur-[2px] dark:bg-background/70"
role="dialog"
aria-modal="true"
aria-labelledby="mobile-download-prompt-title"
onClick={closePrompt}
style={{
top: `${viewportBox.top}px`,
height: `${Math.max(viewportBox.height, 1)}px`,
}}
>
<div className="w-full px-2 pb-[max(0.5rem,env(safe-area-inset-bottom,0px))]">
<div
Expand Down Expand Up @@ -234,33 +266,26 @@ function MobileDownloadPromptOverlay({ isOpen, onClose, targetUrl }: MobileDownl
</button>
</div>
</div>
</div>
</div>,
document.body,
)
}

export function useMobileDownloadPrompt() {
const [isOpen, setIsOpen] = useState(false)
const [isMobile, setIsMobile] = useState(false)

useEffect(() => {
setIsMobile(isLikelyMobileDevice())
}, [])

const targetUrl = "https://www.tiles.run"

const openMobileDownloadPrompt = useCallback(
(event?: DownloadIntentEvent) => {
if (!isMobile) {
return false
}
const openMobileDownloadPrompt = useCallback((event?: DownloadIntentEvent) => {
if (!isLikelyMobileDevice()) {
return false
}

event?.preventDefault()
triggerHaptic()
setIsOpen(true)
return true
},
[isMobile],
)
event?.preventDefault()
triggerHaptic()
setIsOpen(true)
return true
}, [])

return {
openMobileDownloadPrompt,
Expand All @@ -271,19 +296,13 @@ export function useMobileDownloadPrompt() {
}

export function GlobalMobileDownloadPrompt() {
const [isMobile, setIsMobile] = useState(false)
const [isOpen, setIsOpen] = useState(false)

useEffect(() => {
setIsMobile(isLikelyMobileDevice())
}, [])

useEffect(() => {
if (!isMobile) {
return
}

const onDocumentClick = (event: MouseEvent) => {
if (!isLikelyMobileDevice()) {
return
}
const target = event.target
if (!(target instanceof Element)) {
return
Expand Down Expand Up @@ -329,7 +348,7 @@ export function GlobalMobileDownloadPrompt() {
return () => {
document.removeEventListener("click", onDocumentClick, true)
}
}, [isMobile])
}, [])

return (
<MobileDownloadPromptOverlay
Expand Down
11 changes: 7 additions & 4 deletions components/mobile-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import Link from "next/link"
import Image from "next/image"
import { Button } from "@/components/ui/button"
import { useEffect, useState } from "react"
import { Download } from "lucide-react"
import { useEffect } from "react"
import { Download, X } from "lucide-react"
import { FaXTwitter, FaBluesky, FaInstagram, FaDiscord, FaGithub, FaRss } from "react-icons/fa6"
import { SiHuggingface } from "react-icons/si"
import { triggerHaptic } from "@/lib/haptics"
Expand Down Expand Up @@ -185,8 +185,11 @@ export function MobileMenu({ isOpen, onClose, themeAware = false, hasBanner = fa
aria-label="Close navigation menu"
type="button"
>
<span className={`block h-px w-5 rounded-full ${themeAware ? 'bg-foreground' : 'bg-black'} absolute opacity-95 transition-all duration-300 rotate-45`} />
<span className={`block h-px w-5 rounded-full ${themeAware ? 'bg-foreground' : 'bg-black'} absolute opacity-95 transition-all duration-300 -rotate-45`} />
<X
className={`h-5 w-5 ${themeAware ? 'text-foreground' : 'text-black'}`}
strokeWidth={1.9}
aria-hidden
/>
</button>
</div>
</div>
Expand Down
17 changes: 4 additions & 13 deletions components/site-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { Download } from "lucide-react"
import { MobileMenu } from "./mobile-menu"
import { triggerHaptic } from "@/lib/haptics"
import { themeAwareHeaderPrimaryCtaClasses } from "@/lib/header-primary-cta-classes"
import { useMobileDownloadPrompt } from "@/components/mobile-download-prompt"
import { usePathname } from "next/navigation"

/** Set to true to show the top banner (e.g. for announcements). */
Expand All @@ -30,7 +29,7 @@ const SiteHeaderChrome = memo(function SiteHeaderChrome({
isBannerVisible: boolean
onDismissBanner: () => void
onOpenMenu: () => void
onDownloadClick: (event: { preventDefault: () => void }) => void
onDownloadClick: () => void
onHomeClick: (event: { preventDefault: () => void }) => void
}) {
const headerChrome = themeAware
Expand Down Expand Up @@ -190,7 +189,6 @@ const SiteHeaderChrome = memo(function SiteHeaderChrome({
function SiteHeaderContent({ themeAware = true }: SiteHeaderProps) {
const [isMenuOpen, setIsMenuOpen] = useState(false)
const [isBannerVisible, setIsBannerVisible] = useState(false)
const { openMobileDownloadPrompt, mobileDownloadPrompt } = useMobileDownloadPrompt()
const pathname = usePathname()
useEffect(() => {
if (typeof window === "undefined" || !BANNER_ENABLED) return
Expand All @@ -207,15 +205,9 @@ function SiteHeaderContent({ themeAware = true }: SiteHeaderProps) {

const openMenu = useCallback(() => setIsMenuOpen(true), [])
const closeMenu = useCallback(() => setIsMenuOpen(false), [])
const handleDownloadClick = useCallback(
(event: { preventDefault: () => void }) => {
if (openMobileDownloadPrompt(event)) {
return
}
triggerHaptic()
},
[openMobileDownloadPrompt],
)
const handleDownloadClick = useCallback(() => {
triggerHaptic()
}, [])
const handleHomeClick = useCallback(
(event: { preventDefault: () => void }) => {
triggerHaptic()
Expand Down Expand Up @@ -247,7 +239,6 @@ function SiteHeaderContent({ themeAware = true }: SiteHeaderProps) {
themeAware={themeAware}
hasBanner={isBannerVisible}
/>
{mobileDownloadPrompt}
</>
)
}
Expand Down