Skip to content
Merged
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
15 changes: 2 additions & 13 deletions src/components/landing/ChartsCatalogGallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ChartsCatalogChart,
type ChartsCatalogModuleReference,
} from '~/components/charts/ChartsCatalogChart'
import { useInView } from '~/hooks/useInView'
import type { ChartsCatalogCase } from '~/utils/charts-catalog'

type CatalogCase = Pick<
Expand Down Expand Up @@ -49,7 +50,7 @@ export function CatalogChartsHero({
)
const [focused, setFocused] = React.useState(false)
const [hovered, setHovered] = React.useState(false)
const [inView, setInView] = React.useState(true)
const inView = useInView(rootRef, { threshold: 0.2 })
const [pageVisible, setPageVisible] = React.useState(true)
const [paused, setPaused] = React.useState(false)
const reducedMotion = useReducedMotion()
Expand Down Expand Up @@ -77,18 +78,6 @@ export function CatalogChartsHero({
return () => intervals.forEach((interval) => window.clearInterval(interval))
}, [orderedCases.length, running, visibleTileCount])

React.useEffect(() => {
const root = rootRef.current
if (!root || !('IntersectionObserver' in window)) return

const observer = new IntersectionObserver(
([entry]) => setInView(Boolean(entry?.isIntersecting)),
{ threshold: 0.2 },
)
observer.observe(root)
return () => observer.disconnect()
}, [])

React.useEffect(() => {
const updateVisibility = () =>
setPageVisible(document.visibilityState === 'visible')
Expand Down
17 changes: 2 additions & 15 deletions src/components/landing/ChartsLandingGraphics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react'
import { Pause, Play, Shuffle } from '@phosphor-icons/react'

import { useIsDark } from '~/hooks/useIsDark'
import { useInView } from '~/hooks/useInView'

import kineticBarChartSource from '../../../scripts/charts-landing/kinetic-bar-chart.ts?raw'
import kineticDonutChartSource from '../../../scripts/charts-landing/kinetic-donut-chart.ts?raw'
Expand Down Expand Up @@ -377,7 +378,7 @@ export function KineticChartsHero() {
const [outgoingIndex, setOutgoingIndex] = React.useState<number | null>(null)
const [focusWithin, setFocusWithin] = React.useState(false)
const [hovered, setHovered] = React.useState(false)
const [inView, setInView] = React.useState(true)
const inView = useInView(rootRef, { threshold: 0.18 })
const [autoAdvanceOverride, setAutoAdvanceOverride] = React.useState(false)
const [pageVisible, setPageVisible] = React.useState(true)
const [paused, setPaused] = React.useState(false)
Expand Down Expand Up @@ -438,20 +439,6 @@ export function KineticChartsHero() {
return () => window.clearInterval(interval)
}, [autoPlay, interacting, showNewVariation])

React.useEffect(() => {
const root = rootRef.current
if (!root || !('IntersectionObserver' in window)) {
return
}

const observer = new IntersectionObserver(
([entry]) => setInView(Boolean(entry?.isIntersecting)),
{ threshold: 0.18 },
)
observer.observe(root)
return () => observer.disconnect()
}, [])

React.useEffect(() => {
const updateVisibility = () =>
setPageVisible(document.visibilityState === 'visible')
Expand Down
31 changes: 31 additions & 0 deletions src/hooks/useInView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as React from 'react'

export function useInView<TElement extends Element>(
targetRef: React.RefObject<TElement | null>,
{
root = null,
rootMargin = '0px',
threshold = 0,
}: IntersectionObserverInit = {},
) {
const [inView, setInView] = React.useState(false)

React.useEffect(() => {
const target = targetRef.current
if (!target) return

if (!('IntersectionObserver' in window)) {
setInView(true)
return
}

const observer = new IntersectionObserver(
([entry]) => setInView(Boolean(entry?.isIntersecting)),
{ root, rootMargin, threshold },
)
observer.observe(target)
return () => observer.disconnect()
}, [root, rootMargin, targetRef, threshold])

return inView
}
16 changes: 11 additions & 5 deletions src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import { HomeSocialProofSection } from '~/components/home/HomeSocialProofSection
import { HomeStatsSection } from '~/components/home/HomeStatsSection'
import { useQuery } from '@tanstack/react-query'
import { Button, Eyebrow } from '~/components/ds/ui'
import { useInView } from '~/hooks/useInView'
import { useNpmDownloadCounter } from '~/hooks/useNpmDownloadCounter'
import { homepageNpmStatsSummaryQuery, ossStatsQuery } from '~/queries/stats'
import { useLibrariesOverlay } from '~/contexts/LibrariesOverlayContext'
import { fetchRecentPosts } from '~/utils/blog.functions'
import { usePrefersReducedMotion } from '~/utils/usePrefersReducedMotion'
import { seo } from '~/utils/seo'

export const Route = createFileRoute('/')({
Expand Down Expand Up @@ -565,21 +567,24 @@ function FrameworkAdapterGraph({
}) {
const [activeAdapterIndex, setActiveAdapterIndex] = React.useState(0)
const [flowProgress, setFlowProgress] = React.useState(0)
const rootRef = React.useRef<HTMLDivElement>(null)
const isVisible = useInView(rootRef)
const prefersReducedMotion = usePrefersReducedMotion()

React.useEffect(() => {
if (!isVisible || prefersReducedMotion !== false) return

const intervalId = window.setInterval(() => {
setActiveAdapterIndex(
(currentIndex) => (currentIndex + 1) % frameworkAdapterNodes.length,
)
}, 1150)

return () => window.clearInterval(intervalId)
}, [])
}, [isVisible, prefersReducedMotion])

React.useEffect(() => {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
return
}
if (!isVisible || prefersReducedMotion !== false) return

let frameId = 0
let lastUpdate = 0
Expand All @@ -597,13 +602,14 @@ function FrameworkAdapterGraph({
frameId = window.requestAnimationFrame(update)

return () => window.cancelAnimationFrame(frameId)
}, [])
}, [isVisible, prefersReducedMotion])

return (
// The node positions below are hard-coded against a 320×128 grid
// (adapterGraphWidth/Height), so the whole graph is scaled as a unit to
// fill the 460×233 slot rather than re-deriving every coordinate.
<div
ref={rootRef}
aria-hidden="true"
className="relative h-32 w-[320px] shrink-0 scale-[1.35] font-mono text-[10px] font-bold"
>
Expand Down
Loading