Files
supabase/packages/ui-patterns/Toc/toc-thumb.tsx
Francesco Sansalvadore d24df14aab Feat/toc component (#33964)
* test fuma toc

* make toc work

* ok toc

* ui-patterns toc

* fix types

* cleanup

* fix overflow issues

* cleanup

* fix loading w

* Update apps/docs/components/GuidesTableOfContents.tsx

* fix prettier

* format

* install deps

* target ES2018

* tweak regex

* update docs test snapshots

* fix useSubscribeTocRerender
2025-03-05 23:10:02 +01:00

74 lines
1.9 KiB
TypeScript

'use client'
import { type HTMLAttributes, type RefObject, useEffect, useRef } from 'react'
import * as Primitive from './toc-primitive'
import { useEffectEvent, useOnChange } from 'common'
export type TOCThumb = [top: number, height: number]
function calc(container: HTMLElement, active: string[]): TOCThumb {
if (active.length === 0 || container.clientHeight === 0) {
return [0, 0]
}
let upper = Number.MAX_VALUE,
lower = 0
for (const item of active) {
const element = container.querySelector<HTMLElement>(`a[href="#${item}"]`)
if (!element) continue
const styles = getComputedStyle(element)
upper = Math.min(upper, element.offsetTop + parseFloat(styles.paddingTop))
lower = Math.max(
lower,
element.offsetTop + element.clientHeight - parseFloat(styles.paddingBottom)
)
}
return [upper, lower - upper]
}
function update(element: HTMLElement, info: TOCThumb): void {
element.style.setProperty('--toc-top', `${info[0]}px`)
element.style.setProperty('--toc-height', `${info[1]}px`)
}
export function TocThumb({
containerRef,
...props
}: HTMLAttributes<HTMLDivElement> & {
containerRef: RefObject<HTMLElement | null>
}) {
const active = Primitive.useActiveAnchors()
const thumbRef = useRef<HTMLDivElement>(null)
const onResize = useEffectEvent(() => {
if (!containerRef.current || !thumbRef.current) return
update(thumbRef.current, calc(containerRef.current, active))
})
useEffect(() => {
if (!containerRef.current) return
const container = containerRef.current
onResize()
const observer = new ResizeObserver(onResize)
observer.observe(container)
return () => {
observer.disconnect()
}
}, [containerRef, onResize])
useOnChange(active, () => {
if (!containerRef.current || !thumbRef.current) return
update(thumbRef.current, calc(containerRef.current, active))
})
return <div ref={thumbRef} role="none" {...props} />
}