1import { useEffect, useRef } from 'react'
2import Link from 'next/link'
3import { useInView } from 'framer-motion'
4
5import { useSectionStore } from '@/components/SectionProvider'
6import { Tag } from '@/components/Tag'
7import { remToPx } from '@/lib/remToPx'
8
9function AnchorIcon(props) {
10 return (
11 <svg
12 viewBox="0 0 20 20"
13 fill="none"
14 strokeLinecap="round"
15 aria-hidden="true"
16 {...props}
17 >
18 <path d="m6.5 11.5-.964-.964a3.535 3.535 0 1 1 5-5l.964.964m2 2 .964.964a3.536 3.536 0 0 1-5 5L8.5 13.5m0-5 3 3" />
19 </svg>
20 )
21}
22
23function Eyebrow({ tag, label }) {
24 if (!tag && !label) {
25 return null
26 }
27
28 return (
29 <div className="flex items-center gap-x-3">
30 {tag && <Tag>{tag}</Tag>}
31 {tag && label && (
32 <span className="h-0.5 w-0.5 rounded-full bg-zinc-300 dark:bg-zinc-600" />
33 )}
34 {label && (
35 <span className="font-mono text-xs text-zinc-400">{label}</span>
36 )}
37 </div>
38 )
39}
40
41function Anchor({ id, inView, children }) {
42 return (
43 <Link
44 href={`#${id}`}
45 className="group text-inherit no-underline hover:text-inherit"
46 >
47 {inView && (
48 <div className="absolute ml-[calc(-1*var(--width))] mt-1 hidden w-[var(--width)] opacity-0 transition [--width:calc(2.625rem+0.5px+50%-min(50%,calc(theme(maxWidth.lg)+theme(spacing.8))))] group-hover:opacity-100 group-focus:opacity-100 md:block lg:z-50 2xl:[--width:theme(spacing.10)]">
49 <div className="group/anchor block h-5 w-5 rounded-lg bg-zinc-50 ring-1 ring-inset ring-zinc-300 transition hover:ring-zinc-500 dark:bg-zinc-800 dark:ring-zinc-700 dark:hover:bg-zinc-700 dark:hover:ring-zinc-600">
50 <AnchorIcon className="h-5 w-5 stroke-zinc-500 transition dark:stroke-zinc-400 dark:group-hover/anchor:stroke-white" />
51 </div>
52 </div>
53 )}
54 {children}
55 </Link>
56 )
57}
58
59export function Heading({
60 level = 2,
61 children,
62 id,
63 tag,
64 label,
65 anchor = true,
66 ...props
67}) {
68 let Component = `h${level}`
69 let ref = useRef()
70 let registerHeading = useSectionStore((s) => s.registerHeading)
71
72 let inView = useInView(ref, {
73 margin: `${remToPx(-3.5)}px 0px 0px 0px`,
74 amount: 'all',
75 })
76
77 useEffect(() => {
78 if (level === 2) {
79 registerHeading({ id, ref, offsetRem: tag || label ? 8 : 6 })
80 }
81 })
82
83 return (
84 <>
85 <Eyebrow tag={tag} label={label} />
86 <Component
87 ref={ref}
88 id={anchor ? id : undefined}
89 className={tag || label ? 'mt-2 scroll-mt-32' : 'scroll-mt-24'}
90 {...props}
91 >
92 {anchor ? (
93 <Anchor id={id} inView={inView}>
94 {children}
95 </Anchor>
96 ) : (
97 children
98 )}
99 </Component>
100 </>
101 )
102}