mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
UI: Improve TOC rendering for steps
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'fumadocs-ui': patch
|
||||
'@fumadocs/base-ui': patch
|
||||
---
|
||||
|
||||
Improve TOC rendering for steps
|
||||
@@ -1,15 +1,23 @@
|
||||
'use client';
|
||||
import * as Primitive from 'fumadocs-core/toc';
|
||||
import { type ComponentProps, useEffect, useEffectEvent, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
type ComponentProps,
|
||||
type ReactNode,
|
||||
useEffect,
|
||||
useEffectEvent,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { TocThumb, useTOCItems } from '.';
|
||||
import { mergeRefs } from '@/utils/merge-refs';
|
||||
import { useI18n } from '@/contexts/i18n';
|
||||
|
||||
interface ComputedSVG {
|
||||
d: string;
|
||||
width: number;
|
||||
height: number;
|
||||
content: ReactNode;
|
||||
}
|
||||
|
||||
export function TOCItems({ ref, className, ...props }: ComponentProps<'div'>) {
|
||||
@@ -23,34 +31,63 @@ export function TOCItems({ ref, className, ...props }: ComponentProps<'div'>) {
|
||||
let w = 0;
|
||||
let h = 0;
|
||||
let upperBottom = 0;
|
||||
let upperX = 0;
|
||||
let d = '';
|
||||
const output: ReactNode[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
const element: HTMLElement | null = container.querySelector(
|
||||
`a[href="#${items[i].url.slice(1)}"]`,
|
||||
`a[href="#${item.url.slice(1)}"]`,
|
||||
);
|
||||
if (!element) continue;
|
||||
|
||||
const styles = getComputedStyle(element);
|
||||
const x = getLineOffset(items[i].depth) + 0.5,
|
||||
const x = getLineOffset(item.depth) + 0.5,
|
||||
top = element.offsetTop + parseFloat(styles.paddingTop),
|
||||
bottom = element.offsetTop + element.clientHeight - parseFloat(styles.paddingBottom);
|
||||
|
||||
w = Math.max(x + 0.5, w);
|
||||
w = Math.max(x + 8, w);
|
||||
h = Math.max(h, bottom);
|
||||
|
||||
if (i === 0) {
|
||||
d += ` M${x} ${top} L${x} ${bottom}`;
|
||||
} else {
|
||||
const upperX = getLineOffset(items[i - 1].depth) + 0.5;
|
||||
d += ` C ${upperX} ${top - 4} ${x} ${upperBottom + 4} ${x} ${top} L${x} ${bottom}`;
|
||||
}
|
||||
|
||||
if (item._step !== undefined) {
|
||||
output.push(
|
||||
<circle
|
||||
key={`${i}-circle`}
|
||||
cx={x}
|
||||
cy={(top + bottom) / 2}
|
||||
r="8"
|
||||
className="fill-fd-primary"
|
||||
/>,
|
||||
<text
|
||||
key={`${i}-text`}
|
||||
x={x}
|
||||
y={(top + bottom) / 2}
|
||||
textAnchor="middle"
|
||||
alignmentBaseline="central"
|
||||
dominantBaseline="middle"
|
||||
className="fill-fd-primary-foreground font-medium text-xs leading-none font-mono"
|
||||
>
|
||||
{item._step}
|
||||
</text>,
|
||||
);
|
||||
}
|
||||
|
||||
upperX = x;
|
||||
upperBottom = bottom;
|
||||
}
|
||||
|
||||
output.unshift(
|
||||
<path key="path" d={d} className="stroke-fd-primary" strokeWidth="1" fill="none" />,
|
||||
);
|
||||
setSvg({
|
||||
d,
|
||||
content: output,
|
||||
width: w,
|
||||
height: h,
|
||||
});
|
||||
@@ -88,7 +125,7 @@ export function TOCItems({ ref, className, ...props }: ComponentProps<'div'>) {
|
||||
clipPath: `polygon(0 var(--fd-top), 100% var(--fd-top), 100% calc(var(--fd-top) + var(--fd-height)), 0 calc(var(--fd-top) + var(--fd-height)))`,
|
||||
}}
|
||||
>
|
||||
<path d={svg.d} className="stroke-fd-primary" strokeWidth="1" fill="none" />
|
||||
{svg.content}
|
||||
</svg>
|
||||
<ThumbBox />
|
||||
</TocThumb>
|
||||
@@ -120,44 +157,48 @@ interface ThumbBoxInfo {
|
||||
|
||||
function ThumbBox() {
|
||||
const items = Primitive.useItems();
|
||||
const previousRef = useRef<ThumbBoxInfo>({
|
||||
startIdx: -1,
|
||||
endIdx: -1,
|
||||
isUp: false,
|
||||
});
|
||||
const previousRef = useRef<ThumbBoxInfo>(null);
|
||||
const startIdx = items.findIndex((item) => item.active);
|
||||
const endIdx = items.findLastIndex((item) => item.active);
|
||||
if (startIdx === -1) return;
|
||||
|
||||
const prev = previousRef.current;
|
||||
const isUp =
|
||||
prev.startIdx > startIdx ||
|
||||
prev.endIdx > endIdx ||
|
||||
(prev.startIdx === startIdx && prev.endIdx === endIdx && prev.isUp);
|
||||
let isUp = false;
|
||||
if (previousRef.current) {
|
||||
const prev = previousRef.current;
|
||||
isUp =
|
||||
prev.startIdx > startIdx ||
|
||||
prev.endIdx > endIdx ||
|
||||
(prev.startIdx === startIdx && prev.endIdx === endIdx && prev.isUp);
|
||||
}
|
||||
|
||||
previousRef.current = { startIdx, endIdx, isUp };
|
||||
const original = items[isUp ? startIdx : endIdx].original;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="absolute size-1 bg-fd-primary rounded-full transition-transform"
|
||||
style={{
|
||||
translate: `calc(${getLineOffset(items[isUp ? startIdx : endIdx].original.depth)}px - 1.5px) calc(${
|
||||
translate: `${getLineOffset(original.depth) - 1.5}px calc(${
|
||||
isUp ? 'var(--fd-top)' : 'var(--fd-top) + var(--fd-height)'
|
||||
} - 1.5px)`,
|
||||
scale: original._step !== undefined ? '0' : '1',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const a = 8;
|
||||
|
||||
function getItemOffset(depth: number): number {
|
||||
if (depth <= 2) return 14;
|
||||
if (depth === 3) return 26;
|
||||
return 36;
|
||||
if (depth <= 2) return 12 + a;
|
||||
if (depth === 3) return 24 + a;
|
||||
return 36 + a;
|
||||
}
|
||||
|
||||
function getLineOffset(depth: number): number {
|
||||
if (depth <= 2) return 2;
|
||||
if (depth === 3) return 10;
|
||||
return 20;
|
||||
if (depth <= 2) return a;
|
||||
if (depth === 3) return 8 + a;
|
||||
return 16 + a;
|
||||
}
|
||||
|
||||
export function TOCItem({
|
||||
@@ -180,7 +221,7 @@ export function TOCItem({
|
||||
href={item.url}
|
||||
{...props}
|
||||
className={cn(
|
||||
'prose relative py-1.5 text-sm scroll-m-4 text-fd-muted-foreground hover:text-fd-accent-foreground transition-colors wrap-anywhere first:pt-0 last:pb-0 data-[active=true]:text-fd-primary',
|
||||
'group prose relative py-1.5 text-sm scroll-m-4 text-fd-muted-foreground hover:text-fd-accent-foreground transition-colors wrap-anywhere first:pt-0 last:pb-0 data-[active=true]:text-fd-primary',
|
||||
props.className,
|
||||
)}
|
||||
style={{
|
||||
@@ -192,7 +233,7 @@ export function TOCItem({
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox={`${Math.min(offset, upperOffset)} 0 ${Math.abs(upperOffset - offset)} 12`}
|
||||
className="absolute -top-1.5"
|
||||
className="absolute -top-1.5 -z-1"
|
||||
style={{
|
||||
width: Math.abs(upperOffset - offset) + 1,
|
||||
height: 12,
|
||||
@@ -210,7 +251,7 @@ export function TOCItem({
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
'absolute inset-y-0 w-px bg-fd-foreground/10',
|
||||
'absolute inset-y-0 w-px bg-fd-foreground/10 -z-1',
|
||||
offset !== upperOffset && 'top-1.5',
|
||||
offset !== lowerOffset && 'bottom-1.5',
|
||||
)}
|
||||
@@ -218,6 +259,16 @@ export function TOCItem({
|
||||
insetInlineStart: offset,
|
||||
}}
|
||||
/>
|
||||
{item._step !== undefined && (
|
||||
<div
|
||||
className="absolute flex items-center justify-center -translate-1/2 -z-1 top-[calc(50%-var(--t,0px)+var(--b,0px))] size-4 font-mono font-medium text-xs bg-fd-muted text-fd-muted-foreground rounded-full leading-none group-first:[--t:--spacing(0.75)] group-last:[--b:--spacing(0.75)]"
|
||||
style={{
|
||||
insetInlineStart: offset,
|
||||
}}
|
||||
>
|
||||
{item._step}
|
||||
</div>
|
||||
)}
|
||||
{item.title}
|
||||
</Primitive.TOCItem>
|
||||
);
|
||||
|
||||
@@ -57,10 +57,9 @@ interface TocThumbInfo {
|
||||
|
||||
interface TocThumbProps extends ComponentProps<'div'> {
|
||||
containerRef: RefObject<HTMLElement | null>;
|
||||
align?: 'center' | 'end';
|
||||
}
|
||||
|
||||
export function TocThumb({ containerRef, align = 'end', ...props }: TocThumbProps) {
|
||||
export function TocThumb({ containerRef, ...props }: TocThumbProps) {
|
||||
const thumbRef = useRef<HTMLDivElement>(null);
|
||||
const active = useActiveAnchors();
|
||||
|
||||
@@ -85,19 +84,13 @@ export function TocThumb({ containerRef, align = 'end', ...props }: TocThumbProp
|
||||
const element = container.querySelector<HTMLElement>(`a[href="#${item}"]`);
|
||||
if (!element) continue;
|
||||
|
||||
if (align === 'center') {
|
||||
const y = element.offsetTop + element.clientHeight / 2;
|
||||
upper = Math.min(upper, y);
|
||||
lower = Math.max(lower, y);
|
||||
} else {
|
||||
const styles = getComputedStyle(element);
|
||||
const styles = getComputedStyle(element);
|
||||
|
||||
upper = Math.min(upper, element.offsetTop + parseFloat(styles.paddingTop));
|
||||
lower = Math.max(
|
||||
lower,
|
||||
element.offsetTop + element.clientHeight - parseFloat(styles.paddingBottom),
|
||||
);
|
||||
}
|
||||
upper = Math.min(upper, element.offsetTop + parseFloat(styles.paddingTop));
|
||||
lower = Math.max(
|
||||
lower,
|
||||
element.offsetTop + element.clientHeight - parseFloat(styles.paddingBottom),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -63,9 +63,6 @@ export function remarkSteps({
|
||||
value: steps,
|
||||
},
|
||||
],
|
||||
data: {
|
||||
_fd_step: true,
|
||||
} as object,
|
||||
children,
|
||||
};
|
||||
}
|
||||
@@ -94,8 +91,7 @@ export function remarkSteps({
|
||||
|
||||
return (tree) => {
|
||||
visit(tree, (parent) => {
|
||||
if (!('children' in parent) || parent.type === 'heading') return;
|
||||
if (parent.data && '_fd_step' in parent.data) return 'skip';
|
||||
if (!('children' in parent) || parent.type === 'heading') return 'skip';
|
||||
|
||||
let startIdx = -1;
|
||||
let i = 0;
|
||||
@@ -114,12 +110,15 @@ export function remarkSteps({
|
||||
for (; i < parent.children.length; i++) {
|
||||
const node = parent.children[i];
|
||||
|
||||
if (node.type !== 'heading') continue;
|
||||
if (node.type !== 'heading' || node.data?.hProperties?.['data-fd-step'] !== undefined)
|
||||
continue;
|
||||
if (startIdx !== -1) {
|
||||
const startDepth = (parent.children[startIdx] as Heading).depth;
|
||||
|
||||
if (node.depth > startDepth) continue;
|
||||
else if (node.depth < startDepth) onEnd();
|
||||
if (node.depth !== startDepth) {
|
||||
if (node.depth < startDepth) onEnd();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!handleHeadingStep(node)) {
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
# Hello
|
||||
# Hello [step]
|
||||
|
||||
## 1. First
|
||||
|
||||
@@ -20,10 +20,10 @@ content
|
||||
|
||||
content
|
||||
|
||||
# 1. Big: First
|
||||
# World [step]
|
||||
|
||||
content
|
||||
|
||||
# Big: Second [step]
|
||||
# Bye [step]
|
||||
|
||||
content
|
||||
|
||||
+27
-25
@@ -1,42 +1,44 @@
|
||||
# Hello
|
||||
|
||||
<div className="fd-steps">
|
||||
<div className="fd-step">
|
||||
## First
|
||||
# Hello
|
||||
|
||||
content
|
||||
<div className="fd-steps">
|
||||
<div className="fd-step">
|
||||
## First
|
||||
|
||||
### Little Tip
|
||||
content
|
||||
|
||||
### Little Tip
|
||||
|
||||
content
|
||||
</div>
|
||||
|
||||
<div className="fd-step">
|
||||
## Second
|
||||
|
||||
content
|
||||
</div>
|
||||
|
||||
<div className="fd-step">
|
||||
## Third
|
||||
|
||||
content
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Ended
|
||||
|
||||
content
|
||||
</div>
|
||||
|
||||
<div className="fd-step">
|
||||
## Second
|
||||
# World
|
||||
|
||||
content
|
||||
</div>
|
||||
|
||||
<div className="fd-step">
|
||||
## Third
|
||||
|
||||
content
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Ended
|
||||
|
||||
content
|
||||
|
||||
<div className="fd-steps">
|
||||
<div className="fd-step">
|
||||
# Big: First
|
||||
|
||||
content
|
||||
</div>
|
||||
|
||||
<div className="fd-step">
|
||||
# Big: Second
|
||||
# Bye
|
||||
|
||||
content
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
'use client';
|
||||
import * as Primitive from 'fumadocs-core/toc';
|
||||
import { type ComponentProps, useEffect, useEffectEvent, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
type ComponentProps,
|
||||
type ReactNode,
|
||||
useEffect,
|
||||
useEffectEvent,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { TocThumb, useTOCItems } from '.';
|
||||
import { mergeRefs } from '@/utils/merge-refs';
|
||||
import { useI18n } from '@/contexts/i18n';
|
||||
|
||||
interface ComputedSVG {
|
||||
d: string;
|
||||
width: number;
|
||||
height: number;
|
||||
content: ReactNode;
|
||||
}
|
||||
|
||||
export function TOCItems({ ref, className, ...props }: ComponentProps<'div'>) {
|
||||
@@ -23,34 +31,63 @@ export function TOCItems({ ref, className, ...props }: ComponentProps<'div'>) {
|
||||
let w = 0;
|
||||
let h = 0;
|
||||
let upperBottom = 0;
|
||||
let upperX = 0;
|
||||
let d = '';
|
||||
const output: ReactNode[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
const element: HTMLElement | null = container.querySelector(
|
||||
`a[href="#${items[i].url.slice(1)}"]`,
|
||||
`a[href="#${item.url.slice(1)}"]`,
|
||||
);
|
||||
if (!element) continue;
|
||||
|
||||
const styles = getComputedStyle(element);
|
||||
const x = getLineOffset(items[i].depth) + 0.5,
|
||||
const x = getLineOffset(item.depth) + 0.5,
|
||||
top = element.offsetTop + parseFloat(styles.paddingTop),
|
||||
bottom = element.offsetTop + element.clientHeight - parseFloat(styles.paddingBottom);
|
||||
|
||||
w = Math.max(x + 0.5, w);
|
||||
w = Math.max(x + 8, w);
|
||||
h = Math.max(h, bottom);
|
||||
|
||||
if (i === 0) {
|
||||
d += ` M${x} ${top} L${x} ${bottom}`;
|
||||
} else {
|
||||
const upperX = getLineOffset(items[i - 1].depth) + 0.5;
|
||||
d += ` C ${upperX} ${top - 4} ${x} ${upperBottom + 4} ${x} ${top} L${x} ${bottom}`;
|
||||
}
|
||||
|
||||
if (item._step !== undefined) {
|
||||
output.push(
|
||||
<circle
|
||||
key={`${i}-circle`}
|
||||
cx={x}
|
||||
cy={(top + bottom) / 2}
|
||||
r="8"
|
||||
className="fill-fd-primary"
|
||||
/>,
|
||||
<text
|
||||
key={`${i}-text`}
|
||||
x={x}
|
||||
y={(top + bottom) / 2}
|
||||
textAnchor="middle"
|
||||
alignmentBaseline="central"
|
||||
dominantBaseline="middle"
|
||||
className="fill-fd-primary-foreground font-medium text-xs leading-none font-mono"
|
||||
>
|
||||
{item._step}
|
||||
</text>,
|
||||
);
|
||||
}
|
||||
|
||||
upperX = x;
|
||||
upperBottom = bottom;
|
||||
}
|
||||
|
||||
output.unshift(
|
||||
<path key="path" d={d} className="stroke-fd-primary" strokeWidth="1" fill="none" />,
|
||||
);
|
||||
setSvg({
|
||||
d,
|
||||
content: output,
|
||||
width: w,
|
||||
height: h,
|
||||
});
|
||||
@@ -88,7 +125,7 @@ export function TOCItems({ ref, className, ...props }: ComponentProps<'div'>) {
|
||||
clipPath: `polygon(0 var(--fd-top), 100% var(--fd-top), 100% calc(var(--fd-top) + var(--fd-height)), 0 calc(var(--fd-top) + var(--fd-height)))`,
|
||||
}}
|
||||
>
|
||||
<path d={svg.d} className="stroke-fd-primary" strokeWidth="1" fill="none" />
|
||||
{svg.content}
|
||||
</svg>
|
||||
<ThumbBox />
|
||||
</TocThumb>
|
||||
@@ -120,44 +157,48 @@ interface ThumbBoxInfo {
|
||||
|
||||
function ThumbBox() {
|
||||
const items = Primitive.useItems();
|
||||
const previousRef = useRef<ThumbBoxInfo>({
|
||||
startIdx: -1,
|
||||
endIdx: -1,
|
||||
isUp: false,
|
||||
});
|
||||
const previousRef = useRef<ThumbBoxInfo>(null);
|
||||
const startIdx = items.findIndex((item) => item.active);
|
||||
const endIdx = items.findLastIndex((item) => item.active);
|
||||
if (startIdx === -1) return;
|
||||
|
||||
const prev = previousRef.current;
|
||||
const isUp =
|
||||
prev.startIdx > startIdx ||
|
||||
prev.endIdx > endIdx ||
|
||||
(prev.startIdx === startIdx && prev.endIdx === endIdx && prev.isUp);
|
||||
let isUp = false;
|
||||
if (previousRef.current) {
|
||||
const prev = previousRef.current;
|
||||
isUp =
|
||||
prev.startIdx > startIdx ||
|
||||
prev.endIdx > endIdx ||
|
||||
(prev.startIdx === startIdx && prev.endIdx === endIdx && prev.isUp);
|
||||
}
|
||||
|
||||
previousRef.current = { startIdx, endIdx, isUp };
|
||||
const original = items[isUp ? startIdx : endIdx].original;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="absolute size-1 bg-fd-primary rounded-full transition-transform"
|
||||
style={{
|
||||
translate: `calc(${getLineOffset(items[isUp ? startIdx : endIdx].original.depth)}px - 1.5px) calc(${
|
||||
translate: `${getLineOffset(original.depth) - 1.5}px calc(${
|
||||
isUp ? 'var(--fd-top)' : 'var(--fd-top) + var(--fd-height)'
|
||||
} - 1.5px)`,
|
||||
scale: original._step !== undefined ? '0' : '1',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const a = 8;
|
||||
|
||||
function getItemOffset(depth: number): number {
|
||||
if (depth <= 2) return 14;
|
||||
if (depth === 3) return 26;
|
||||
return 36;
|
||||
if (depth <= 2) return 12 + a;
|
||||
if (depth === 3) return 24 + a;
|
||||
return 36 + a;
|
||||
}
|
||||
|
||||
function getLineOffset(depth: number): number {
|
||||
if (depth <= 2) return 2;
|
||||
if (depth === 3) return 10;
|
||||
return 20;
|
||||
if (depth <= 2) return a;
|
||||
if (depth === 3) return 8 + a;
|
||||
return 16 + a;
|
||||
}
|
||||
|
||||
export function TOCItem({
|
||||
@@ -180,7 +221,7 @@ export function TOCItem({
|
||||
href={item.url}
|
||||
{...props}
|
||||
className={cn(
|
||||
'prose relative py-1.5 text-sm scroll-m-4 text-fd-muted-foreground hover:text-fd-accent-foreground transition-colors wrap-anywhere first:pt-0 last:pb-0 data-[active=true]:text-fd-primary',
|
||||
'group prose relative py-1.5 text-sm scroll-m-4 text-fd-muted-foreground hover:text-fd-accent-foreground transition-colors wrap-anywhere first:pt-0 last:pb-0 data-[active=true]:text-fd-primary',
|
||||
props.className,
|
||||
)}
|
||||
style={{
|
||||
@@ -192,7 +233,7 @@ export function TOCItem({
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox={`${Math.min(offset, upperOffset)} 0 ${Math.abs(upperOffset - offset)} 12`}
|
||||
className="absolute -top-1.5"
|
||||
className="absolute -top-1.5 -z-1"
|
||||
style={{
|
||||
width: Math.abs(upperOffset - offset) + 1,
|
||||
height: 12,
|
||||
@@ -210,7 +251,7 @@ export function TOCItem({
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
'absolute inset-y-0 w-px bg-fd-foreground/10',
|
||||
'absolute inset-y-0 w-px bg-fd-foreground/10 -z-1',
|
||||
offset !== upperOffset && 'top-1.5',
|
||||
offset !== lowerOffset && 'bottom-1.5',
|
||||
)}
|
||||
@@ -218,6 +259,16 @@ export function TOCItem({
|
||||
insetInlineStart: offset,
|
||||
}}
|
||||
/>
|
||||
{item._step !== undefined && (
|
||||
<div
|
||||
className="absolute flex items-center justify-center -translate-1/2 -z-1 top-[calc(50%-var(--t,0px)+var(--b,0px))] size-4 font-mono font-medium text-xs bg-fd-muted text-fd-muted-foreground rounded-full leading-none group-first:[--t:--spacing(0.75)] group-last:[--b:--spacing(0.75)]"
|
||||
style={{
|
||||
insetInlineStart: offset,
|
||||
}}
|
||||
>
|
||||
{item._step}
|
||||
</div>
|
||||
)}
|
||||
{item.title}
|
||||
</Primitive.TOCItem>
|
||||
);
|
||||
|
||||
@@ -57,10 +57,9 @@ interface TocThumbInfo {
|
||||
|
||||
interface TocThumbProps extends ComponentProps<'div'> {
|
||||
containerRef: RefObject<HTMLElement | null>;
|
||||
align?: 'center' | 'end';
|
||||
}
|
||||
|
||||
export function TocThumb({ containerRef, align = 'end', ...props }: TocThumbProps) {
|
||||
export function TocThumb({ containerRef, ...props }: TocThumbProps) {
|
||||
const thumbRef = useRef<HTMLDivElement>(null);
|
||||
const active = useActiveAnchors();
|
||||
|
||||
@@ -85,19 +84,13 @@ export function TocThumb({ containerRef, align = 'end', ...props }: TocThumbProp
|
||||
const element = container.querySelector<HTMLElement>(`a[href="#${item}"]`);
|
||||
if (!element) continue;
|
||||
|
||||
if (align === 'center') {
|
||||
const y = element.offsetTop + element.clientHeight / 2;
|
||||
upper = Math.min(upper, y);
|
||||
lower = Math.max(lower, y);
|
||||
} else {
|
||||
const styles = getComputedStyle(element);
|
||||
const styles = getComputedStyle(element);
|
||||
|
||||
upper = Math.min(upper, element.offsetTop + parseFloat(styles.paddingTop));
|
||||
lower = Math.max(
|
||||
lower,
|
||||
element.offsetTop + element.clientHeight - parseFloat(styles.paddingBottom),
|
||||
);
|
||||
}
|
||||
upper = Math.min(upper, element.offsetTop + parseFloat(styles.paddingTop));
|
||||
lower = Math.max(
|
||||
lower,
|
||||
element.offsetTop + element.clientHeight - parseFloat(styles.paddingBottom),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user