Files
supabase/apps/docs/features/docs/Reference.ui.client.tsx
T
Anthony Lio 6d08a747f1 fix(docs): guide reference perf enhancements (#50239)
## What kind of change does this PR introduce?

follow-up to #50235 to reduce reference page payloads and cold rendering
overhead

## What is the current behavior?

reference pages ship a large rsc payload inside the html _ most of it is
duplication rather than content along with shiki that writes ~30
character css variable name for every syntax token making the page heavy
in some cases

## What is the new behavior?

- moves repeated styles into shared css and uses compact, namespaced
token classes
- renders details icons inside the client trigger
- follows shiki’s guidance to [reuse one
highlighter](https://shiki.style/guide/best-performance#cache-the-highlighter-instance)
and [load languages on
demand](https://shiki.style/guide/best-performance#use-shorthands)

`page size`
page | before | after | change
-- | -- | -- | --
javascript | 10.61 mb | 8.28 mb | -21.9%
dart | 4.59 mb | 4.13 mb | -10.1%
python | 4.38 mb | 3.73 mb | -14.9%
swift | 2.87 mb | 2.56 mb | -10.9%
server | 1.59 mb | 1.35 mb | -15.2%
kotlin | 3.42 mb | 3.17 mb | -7.2%

`cold initialization`
language | before | after | reduction
-- | -- | -- | --
bash | 2,180 ms | 23 ms | 98.95%
javascript | 2,245 ms | 38 ms | 98.29%

## Additional context

measured on a local production build which uses the checked in generated
content _ production has larger sdk data, so absolute sizes there will
be higher

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added reusable expand/collapse controls for API reference details,
with updated icons, labels, and styling.
* Improved code block rendering with class-based syntax highlighting,
wrapped-code support, responsive layouts, and lazy language loading.

* **Style**
* Added theme-aware syntax-token colors, line-number styling, and
configurable code-block shadows.
  * Consolidated expandable reference panel and item styling.

* **Tests**
* Added coverage for syntax highlighting, code block rendering, language
support, token stability, and reference details.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-15 23:55:49 +03:00

128 lines
3.8 KiB
TypeScript

'use client'
import { ReferenceContentInitiallyScrolledContext } from '~/features/docs/Reference.navigation.client'
import { safeHistoryReplaceState } from '~/lib/historyUtils'
import { XCircle } from 'lucide-react'
import type { HTMLAttributes, PropsWithChildren } from 'react'
import { useContext, useEffect, useRef, useState } from 'react'
import { useInView } from 'react-intersection-observer'
import {
cn,
CollapsibleTrigger,
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from 'ui'
import { type IApiEndPoint } from './Reference.api.utils'
import { API_REFERENCE_REQUEST_BODY_SCHEMA_DATA_ATTRIBUTES } from './Reference.ui.shared'
/**
* Wrap a reference section with client-side functionality:
*
* - Intersection observer to auto-update the URL when the user scrolls the page
* - An ID to scroll to programmatically. This is on the entire section rather
* than the heading to avoid problems with scroll-to position when the heading
* is sticky.
*/
export function ReferenceSectionWrapper({
id,
link,
children,
className,
}: PropsWithChildren<{ id: string; link: string; className?: string }> &
HTMLAttributes<HTMLElement>) {
const initialScrollHappened = useContext(ReferenceContentInitiallyScrolledContext)
const { ref } = useInView({
threshold: 0,
rootMargin: '-10% 0% -50% 0%',
onChange: (inView) => {
if (
inView &&
initialScrollHappened &&
window.scrollY > 0 /* Don't update on first navigation to introduction */
) {
safeHistoryReplaceState(link)
}
},
})
return (
<section
ref={ref}
id={id}
className={cn('scroll-mt-[calc(var(--header-height)+4rem)]', className)}
>
{children}
</section>
)
}
export function ApiOperationBodySchemeSelector({
requestBody,
className,
}: {
requestBody: IApiEndPoint['requestBody']
className?: string
}) {
const availableSchemes = Object.keys(requestBody?.content || {}) as Array<
'application/json' | 'application/x-www-form-urlencoded'
>
const [selectedScheme, setSelectedScheme] = useState(availableSchemes[0])
const containerRef = useRef<HTMLDivElement>(null)
const allSchemeDetails = useRef<HTMLUListElement[]>([])
useEffect(() => {
const elements = containerRef.current?.querySelectorAll(
`[${API_REFERENCE_REQUEST_BODY_SCHEMA_DATA_ATTRIBUTES.KEY}]`
)
allSchemeDetails.current = elements ? (Array.from(elements) as HTMLUListElement[]) : []
}, [])
useEffect(() => {
allSchemeDetails.current?.forEach((schemeDetails) => {
schemeDetails.hidden =
schemeDetails.getAttribute(API_REFERENCE_REQUEST_BODY_SCHEMA_DATA_ATTRIBUTES.KEY) !==
selectedScheme
})
}, [selectedScheme])
return (
<div ref={containerRef} className={cn('flex items-center justify-between gap-2', className)}>
<h3 className="text-base text-foreground">Body</h3>
<Select
value={selectedScheme}
onValueChange={(value) =>
setSelectedScheme(value as 'application/json' | 'application/x-www-form-urlencoded')
}
>
<SelectTrigger className="w-48 [&>span]:w-full [&>span]:truncate">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{availableSchemes.map((scheme) => (
<SelectItem key={scheme} value={scheme}>
{scheme}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
)
}
export function DetailsTrigger({ label, className }: { label: string; className?: string }) {
return (
<CollapsibleTrigger className={cn('group reference-details-trigger', className)}>
<XCircle size={14} aria-hidden="true" className="reference-details-trigger-icon" />
{label}
</CollapsibleTrigger>
)
}