TableOfContents.tsx 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import React from "react"
  2. import Link from "next/link"
  3. export function TableOfContents({ toc }) {
  4. const items = toc.filter((item) => item.id && (item.level === 2 || item.level === 3))
  5. if (items.length <= 1) {
  6. return null
  7. }
  8. return (
  9. <nav className="toc">
  10. <ul className="flex column">
  11. {items.map((item) => {
  12. const href = `#${item.id}`
  13. const active = typeof window !== "undefined" && window.location.hash === href
  14. return (
  15. <li
  16. key={item.title}
  17. className={[active ? "active" : undefined, item.level === 3 ? "padded" : undefined]
  18. .filter(Boolean)
  19. .join(" ")}>
  20. <Link href={href}>{item.title}</Link>
  21. </li>
  22. )
  23. })}
  24. </ul>
  25. <style jsx>
  26. {`
  27. nav {
  28. position: sticky;
  29. top: 0;
  30. max-height: calc(100vh - var(--top-nav-height) - 6rem);
  31. width: 100%;
  32. align-self: flex-start;
  33. margin-bottom: 1rem;
  34. padding: 0.5rem 0 0;
  35. border-left: 1px solid var(--border-color);
  36. transition: border-color 0.2s ease;
  37. overflow-y: auto;
  38. }
  39. ul {
  40. margin: 0;
  41. padding-left: 1rem;
  42. display: flex;
  43. flex-direction: column;
  44. }
  45. li {
  46. list-style-type: none;
  47. margin: 0 0 1rem;
  48. }
  49. li :global(a) {
  50. text-decoration: none;
  51. color: var(--text-secondary);
  52. }
  53. li :global(a:hover),
  54. li.active :global(a) {
  55. text-decoration: underline;
  56. }
  57. li.padded {
  58. padding-left: 1rem;
  59. }
  60. /* Hide on tablet and mobile */
  61. @media (max-width: 1024px) {
  62. nav {
  63. display: none;
  64. }
  65. }
  66. `}
  67. </style>
  68. </nav>
  69. )
  70. }