1+ import { Pin , PinOff } from 'lucide-preact'
2+ import { Button } from '@/components/ui/button'
13import { cn } from '@/lib/utils'
24import type { TocDataItem } from './toc-item'
35
46interface Props {
5- show : boolean
7+ open : boolean
8+ pinned : boolean
69 toc : TocDataItem [ ]
710 rootDepth : number
811 currentScrollRange : [ number , number ]
@@ -12,57 +15,71 @@ interface Props {
1215 anchorId : string ,
1316 ) => void
1417 onMouseLeave : ( ) => void
18+ onPinToggle : ( ) => void
1519}
1620
1721export function HoverCard ( {
18- show,
22+ open,
23+ pinned,
1924 toc,
2025 rootDepth,
2126 currentScrollRange,
2227 onScrollToButtonClick,
2328 onMouseLeave,
29+ onPinToggle,
2430} : Props ) {
2531 return (
26- < ul
32+ < div
2733 className = { cn (
28- 'fixed top-24 right-0 z-1000 -mt-1 rounded-2xl border' ,
29- 'px-4 py-2 text-xs' ,
30- 'bg-background shadow-context-menu backdrop-blur-background' ,
31- 'scrollbar-none max-h-[calc(100svh-4rem)] max-w-xs overflow-auto' ,
32- 'transition-all duration-200 ease-in-out' ,
33- show
34+ 'fixed top-24 right-0 z-1000 -mt-1 flex max-h-[calc(100svh-4rem)] max-w-72 flex-col text-xs' ,
35+ 'bg-background' ,
36+ 'scrollbar-none overflow-hidden' ,
37+ 'transition-transform duration-200 ease-in-out' ,
38+ open
3439 ? 'pointer-events-auto translate-x-0 opacity-100'
3540 : 'pointer-events-none translate-x-2 opacity-0' ,
3641 ) }
3742 onMouseLeave = { onMouseLeave }
3843 >
39- { toc . map ( ( heading , index ) => (
40- < li
41- key = { heading . anchorId }
42- className = 'flex w-full items-center'
43- style = { {
44- paddingLeft : `${ ( heading . depth - rootDepth ) * 12 } px` ,
45- } }
44+ < div className = 'flex justify-end p-1' >
45+ < Button
46+ variant = 'ghost'
47+ size = 'icon-sm'
48+ onClick = { onPinToggle }
49+ aria-label = { pinned ? '取消钉住' : '钉住' }
4650 >
47- < button
48- className = { cn (
49- 'group flex w-full justify-between py-1 text-muted-foreground' ,
50- index === currentScrollRange [ 0 ] ? 'text-foreground' : '' ,
51- ) }
52- type = 'button'
53- onClick = { ( ) => {
54- onScrollToButtonClick ( index , heading . $heading , heading . anchorId )
51+ { pinned ? < PinOff /> : < Pin /> }
52+ </ Button >
53+ </ div >
54+ < ul className = 'scrollbar-none overflow-auto rounded-2xl border px-4 py-2' >
55+ { toc . map ( ( heading , index ) => (
56+ < li
57+ key = { heading . anchorId }
58+ className = 'flex w-full items-center'
59+ style = { {
60+ paddingLeft : `${ ( heading . depth - rootDepth ) * 12 } px` ,
5561 } }
5662 >
57- < span className = 'max-w-prose select-none truncate duration-200 group-hover:text-foreground/80' >
58- { heading . title }
59- </ span >
60- < span className = 'ml-4 select-none text-[8px] opacity-50' >
61- H{ heading . depth }
62- </ span >
63- </ button >
64- </ li >
65- ) ) }
66- </ ul >
63+ < button
64+ className = { cn (
65+ 'group flex w-full justify-between py-1 text-muted-foreground' ,
66+ index === currentScrollRange [ 0 ] ? 'text-foreground' : '' ,
67+ ) }
68+ type = 'button'
69+ onClick = { ( ) => {
70+ onScrollToButtonClick ( index , heading . $heading , heading . anchorId )
71+ } }
72+ >
73+ < span className = 'max-w-prose select-none truncate duration-200 group-hover:text-foreground/80' >
74+ { heading . title }
75+ </ span >
76+ < span className = 'ml-4 select-none text-[8px] opacity-50' >
77+ H{ heading . depth }
78+ </ span >
79+ </ button >
80+ </ li >
81+ ) ) }
82+ </ ul >
83+ </ div >
6784 )
6885}
0 commit comments