tableOfContents.tsx 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import {useEffect} from 'react';
  2. import styled from '@emotion/styled';
  3. import tocbot from 'tocbot';
  4. import space from 'sentry/styles/space';
  5. const Container = styled('div')`
  6. height: 100%;
  7. width: 16em;
  8. @media only screen and (max-width: ${p => p.theme.breakpoints.medium}) {
  9. display: none;
  10. }
  11. `;
  12. const Content = styled('div')`
  13. position: fixed;
  14. top: calc(${space(4)} * 3);
  15. & > .toc-wrapper > .toc-list {
  16. padding-left: 0;
  17. border-left: solid 2px ${p => p.theme.border};
  18. }
  19. & .toc-list-item {
  20. position: relative;
  21. list-style-type: none;
  22. margin-left: ${space(2)};
  23. }
  24. & .toc-list-item::before {
  25. content: '';
  26. position: absolute;
  27. height: 100%;
  28. top: 0;
  29. left: 0;
  30. transform: translateX(calc(-2px - ${space(2)}));
  31. border-left: solid 2px ${p => p.theme.textColor};
  32. opacity: 0;
  33. transition: opacity 0.2s;
  34. }
  35. & .toc-list-item.is-active-li::before {
  36. opacity: 1;
  37. }
  38. & .toc-list-item > a {
  39. color: ${p => p.theme.subText};
  40. }
  41. & .toc-list-item.is-active-li > a {
  42. font-weight: 600;
  43. color: ${p => p.theme.textColor};
  44. }
  45. `;
  46. const Heading = styled('p')`
  47. font-weight: 600;
  48. font-size: 0.875em;
  49. color: ${p => p.theme.textColor};
  50. text-transform: uppercase;
  51. margin-bottom: ${space(1)};
  52. `;
  53. type Props = {
  54. /**
  55. * When true, show empty container. We still want the container
  56. * (as opposed to showing nothing at all) because it affects the
  57. * page layout and we want to preserve the layout across pages.
  58. */
  59. hidden: boolean;
  60. };
  61. const TableOfContents = ({hidden}: Props) => {
  62. useEffect(() => {
  63. /**
  64. * Initialize Tocbot if it's not hidden
  65. * https://tscanlin.github.io/tocbot/
  66. */
  67. !hidden &&
  68. tocbot.init({
  69. tocSelector: '.toc-wrapper',
  70. contentSelector: '.sbdocs-content',
  71. headingSelector: 'h2',
  72. headingsOffset: 40,
  73. scrollSmoothOffset: -40,
  74. /**
  75. * Ignore headings that did not
  76. * come from the main markdown code.
  77. */
  78. ignoreSelector: ':not(.sbdocs), .hide-from-toc',
  79. orderedList: false,
  80. /**
  81. * Prevent default linking behavior,
  82. * leaving only the smooth scrolling.
  83. */
  84. onClick: () => false,
  85. });
  86. }, [hidden]);
  87. return (
  88. <Container>
  89. {!hidden && (
  90. <Content>
  91. <Heading>Contents</Heading>
  92. <div className="toc-wrapper" />
  93. </Content>
  94. )}
  95. </Container>
  96. );
  97. };
  98. export default TableOfContents;