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
11 changes: 3 additions & 8 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ const nextConfig: NextConfig = {
'@radix-ui/react-tabs',
'@radix-ui/react-switch',
'motion',
'react-markdown',
'react-syntax-highlighter'
]
},
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
"@remcostoeten/analytics": "^1.7.0",
"@remcostoeten/analytics-manager": "^0.1.4",
"@remcostoeten/auth-drawer": "^0.3.2",
"@remcostoeten/notifier": "^1.1.0",
"@remcostoeten/use-shortcut": "^2.3.0",
Expand All @@ -77,7 +78,6 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"drizzle-orm": "^0.45.1",
"geist": "1.2.2",
"glob": "^13.0.3",
"gray-matter": "^4.0.3",
"heic2any": "^0.0.4",
Expand All @@ -92,7 +92,6 @@
"react": "19",
"react-dom": "19",
"react-icons": "^5.5.0",
"react-markdown": "^10.1.0",
"react-syntax-highlighter": "^16.1.0",
"rehype-mdx-code-props": "^3.0.1",
"rehype-raw": "^7.0.0",
Expand Down
4 changes: 1 addition & 3 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { ReactNode } from 'react'
import type { Metadata } from 'next'
import Script from 'next/script'
import { GeistSans } from 'geist/font/sans'
import { GeistMono } from 'geist/font/mono'
import { cn } from '@/shared/lib/cn'
import {
WebsiteStructuredData,
Expand Down Expand Up @@ -99,7 +97,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
suppressHydrationWarning
className={cn(
'bg-white text-black antialiased dark:bg-black dark:text-white',
`${GeistSans.variable} ${GeistMono.variable} font-sans`
'font-sans'
)}
>
<head>
Expand Down
8 changes: 6 additions & 2 deletions src/components/contact/contact-popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ import { cn } from '@/shared/lib/cn'

const FORM_FIELDS = ['name', 'email', 'subject', 'message'] as const

export function ContactPopover() {
const [isOpen, setIsOpen] = useState(false)
type TContactPopoverProps = {
initialOpen?: boolean
}

export function ContactPopover({ initialOpen = false }: TContactPopoverProps) {
const [isOpen, setIsOpen] = useState(initialOpen)
const [status, setStatus] = useState<TSendStatus>('idle')
const containerRef = useRef<HTMLDivElement>(null)
const popoverRef = useRef<HTMLDivElement>(null)
Expand Down
62 changes: 62 additions & 0 deletions src/components/contact/lazy-contact-popover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use client'

import { lazy, Suspense, useState } from 'react'

const ContactPopover = lazy(() =>
import('./contact-popover').then(module => ({
default: module.ContactPopover
}))
)

function Trigger({
onLoad,
onOpen
}: {
onLoad: () => void
onOpen: () => void
}) {
return (
<div className="relative inline-block text-left">
<button
type="button"
aria-haspopup="dialog"
aria-expanded={false}
onPointerEnter={onLoad}
onFocus={onLoad}
Comment on lines +22 to +25

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The lazy contact trigger hard-codes aria-expanded={false}, and the resume trigger exposes no expanded state at all; while the lazy module is loading after activation, assistive technology is told that the control is closed even though the user has requested it to open.

Triggers: When a user activates the trigger before its lazy module has finished loading.

Suggested fix: Pass the open state into Trigger and expose it through aria-expanded (and an appropriate relationship to the pending dialog/drawer).

onClick={onOpen}
className="text-muted-foreground hover:text-foreground transition-colors text-sm font-medium"
>
Contact
</button>
</div>
)
}

/**
* Defers loading the popover (and its motion dependency) until the user
* hovers, focuses, or clicks the trigger, keeping it out of the initial
* bundle of every page that renders the footer.
*/
export function LazyContactPopover() {
const [load, setLoad] = useState(false)
const [open, setOpen] = useState(false)

function handleLoad() {
setLoad(true)
}

function handleOpen() {
setLoad(true)
setOpen(true)
}

if (!load) return <Trigger onLoad={handleLoad} onOpen={handleOpen} />

return (
<Suspense
fallback={<Trigger onLoad={handleLoad} onOpen={handleOpen} />}
>
<ContactPopover initialOpen={open} />
</Suspense>
)
}
32 changes: 16 additions & 16 deletions src/components/layout/breadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import type { Route } from 'next'
import Link from 'next/link'
import { usePathname, useSearchParams } from 'next/navigation'
import { Fragment, Suspense } from 'react'
import { usePathname } from 'next/navigation'
import { Fragment, useEffect, useState } from 'react'
import { Home } from 'lucide-react'

interface BreadcrumbItem {
Expand Down Expand Up @@ -57,21 +57,29 @@ function generateBreadcrumbs(pathname: string): BreadcrumbItem[] {
return breadcrumbs
}

function BreadcrumbsContent({ params }: BreadcrumbProps) {
function useLangParam() {
const [linkParams, setLinkParams] = useState('')

useEffect(() => {
const lang = new URLSearchParams(window.location.search).get('lang')
if (lang) setLinkParams(`?lang=${lang}`)
}, [])

return linkParams
}

export function Breadcrumbs({ params }: BreadcrumbProps) {
const pathname = usePathname()
const searchParams = useSearchParams()
const breadcrumbs = generateBreadcrumbs(pathname)

const langParam = searchParams.get('lang')
const linkParams = langParam ? `?lang=${langParam}` : ''
const linkParams = useLangParam()

if (pathname === '/' || breadcrumbs.length === 0) {
return null
}

return (
<nav aria-label="Breadcrumb">
<ol className="flex items-center gap-1 text-xs font-mono text-muted-foreground/50">
<ol className="flex items-center gap-1 text-xs font-mono text-muted-foreground">
<li>
<Link
href={buildHref('/', params) as Route}
Expand Down Expand Up @@ -108,11 +116,3 @@ function BreadcrumbsContent({ params }: BreadcrumbProps) {
</nav>
)
}

export function Breadcrumbs(props: BreadcrumbProps) {
return (
<Suspense fallback={null}>
<BreadcrumbsContent {...props} />
</Suspense>
)
}
8 changes: 4 additions & 4 deletions src/components/layout/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { toast } from 'sonner'
import Link from 'next/link'
import { useLatestCommit } from '@/hooks/use-github'
import { AnimatedNumber } from '../ui/effects/animated-number'
import { ContactPopover } from '@/components/contact/contact-popover'
import { ResumeDrawer } from '../resume-drawer'
import { LazyContactPopover } from '@/components/contact/lazy-contact-popover'
import { LazyResumeDrawer } from '../lazy-resume-drawer'
import { CurrentYear } from '@/components/ui/current-year'

export function Footer() {
Expand Down Expand Up @@ -113,11 +113,11 @@ export function Footer() {
</div>

<div className="flex items-center gap-4">
<ContactPopover />
<LazyContactPopover />

<span className="text-border">|</span>

<ResumeDrawer />
<LazyResumeDrawer />

<span className="text-border">|</span>

Expand Down
60 changes: 60 additions & 0 deletions src/components/lazy-resume-drawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use client'

import { lazy, Suspense, useState } from 'react'
import { FileUser } from 'lucide-react'

const ResumeDrawer = lazy(() =>
import('./resume-drawer').then(module => ({
default: module.ResumeDrawer
}))
)

function Trigger({
onLoad,
onOpen
}: {
onLoad: () => void
onOpen: () => void
}) {
return (
<button
type="button"
onPointerEnter={onLoad}
onFocus={onLoad}
onClick={onOpen}
className="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
>
<FileUser className="w-4 h-4" />
<span>Resume</span>
</button>
)
}

/**
* Defers loading the drawer (and its vaul dependency) until the user hovers,
* focuses, or clicks the trigger, keeping it out of the initial bundle of
* every page that renders the footer.
*/
export function LazyResumeDrawer() {
const [load, setLoad] = useState(false)
const [open, setOpen] = useState(false)

function handleLoad() {
setLoad(true)
}

function handleOpen() {
setLoad(true)
setOpen(true)
}

if (!load) return <Trigger onLoad={handleLoad} onOpen={handleOpen} />

return (
<Suspense
fallback={<Trigger onLoad={handleLoad} onOpen={handleOpen} />}
>
<ResumeDrawer initialOpen={open} />
</Suspense>
)
}
1 change: 0 additions & 1 deletion src/components/packages/package-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export function PackageCard({ pkg, version }: Props) {
href={`/packages/${pkg.slug}` as Route}
prefetch
className="block px-4 py-5 md:px-5 transition-colors duration-150 ease-out hover:bg-muted/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring active:bg-muted/50"
aria-label={`Read about ${pkg.name}`}
>
<div className="flex items-start gap-3">
<div className="mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md border border-border/70 bg-background shadow-sm">
Expand Down
8 changes: 6 additions & 2 deletions src/components/resume-drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import { Drawer } from 'vaul'
import { FileUser, Download, X, Maximize2, Minimize2 } from 'lucide-react'
import { useState } from 'react'

export function ResumeDrawer() {
type Props = {
initialOpen?: boolean
}

export function ResumeDrawer({ initialOpen = false }: Props) {
const [isFullscreen, setIsFullscreen] = useState(false)

return (
<Drawer.Root shouldScaleBackground>
<Drawer.Root shouldScaleBackground defaultOpen={initialOpen}>
<Drawer.Trigger asChild>
<button className="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors">
<FileUser className="w-4 h-4" />
Expand Down
5 changes: 3 additions & 2 deletions src/components/ui/effects/animated-number.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import React, {
useRef,
useState
} from 'react'
import { useInView, useReducedMotion } from 'motion/react'
import { useInViewOnce } from '@/hooks/use-in-view-once'
import { useReducedMotion } from '@/hooks/use-reduced-motion'
import { useStaggerLayer } from '../stagger-system'

// =============================================================================
Expand Down Expand Up @@ -281,7 +282,7 @@ export function AnimatedNumber({
useAnimatedNumberContext()

// Only enable view-based animations on client side
const isInView = useInView(elementRef, { once: true, margin: '100px' })
const isInView = useInViewOnce(elementRef, '100px')

useEffect(() => {
setIsClient(true)
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/skeletons/section-skeletons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export function ActivitySectionSkeleton() {
key={month}
className="relative overflow-visible z-20"
>
<span className="whitespace-nowrap absolute text-[10px] text-muted-foreground/30">
<span className="whitespace-nowrap absolute text-[10px] text-muted-foreground">
{month}
</span>
</div>
Expand Down
Loading
Loading