Heading.jsx

  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}