You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Doc.tsx 4.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. 'use client'
  2. import { useEffect, useMemo, useState } from 'react'
  3. import { useContext } from 'use-context-selector'
  4. import { useTranslation } from 'react-i18next'
  5. import { RiListUnordered } from '@remixicon/react'
  6. import TemplateEn from './template/template.en.mdx'
  7. import TemplateZh from './template/template.zh.mdx'
  8. import TemplateJa from './template/template.ja.mdx'
  9. import I18n from '@/context/i18n'
  10. import { LanguagesSupported } from '@/i18n/language'
  11. import useTheme from '@/hooks/use-theme'
  12. import { Theme } from '@/types/app'
  13. import cn from '@/utils/classnames'
  14. type DocProps = {
  15. apiBaseUrl: string
  16. }
  17. const Doc = ({ apiBaseUrl }: DocProps) => {
  18. const { locale } = useContext(I18n)
  19. const { t } = useTranslation()
  20. const [toc, setToc] = useState<Array<{ href: string; text: string }>>([])
  21. const [isTocExpanded, setIsTocExpanded] = useState(false)
  22. const { theme } = useTheme()
  23. // Set initial TOC expanded state based on screen width
  24. useEffect(() => {
  25. const mediaQuery = window.matchMedia('(min-width: 1280px)')
  26. setIsTocExpanded(mediaQuery.matches)
  27. }, [])
  28. // Extract TOC from article content
  29. useEffect(() => {
  30. const extractTOC = () => {
  31. const article = document.querySelector('article')
  32. if (article) {
  33. const headings = article.querySelectorAll('h2')
  34. const tocItems = Array.from(headings).map((heading) => {
  35. const anchor = heading.querySelector('a')
  36. if (anchor) {
  37. return {
  38. href: anchor.getAttribute('href') || '',
  39. text: anchor.textContent || '',
  40. }
  41. }
  42. return null
  43. }).filter((item): item is { href: string; text: string } => item !== null)
  44. setToc(tocItems)
  45. }
  46. }
  47. setTimeout(extractTOC, 0)
  48. }, [locale])
  49. // Handle TOC item click
  50. const handleTocClick = (e: React.MouseEvent<HTMLAnchorElement>, item: { href: string; text: string }) => {
  51. e.preventDefault()
  52. const targetId = item.href.replace('#', '')
  53. const element = document.getElementById(targetId)
  54. if (element) {
  55. const scrollContainer = document.querySelector('.scroll-container')
  56. if (scrollContainer) {
  57. const headerOffset = -40
  58. const elementTop = element.offsetTop - headerOffset
  59. scrollContainer.scrollTo({
  60. top: elementTop,
  61. behavior: 'smooth',
  62. })
  63. }
  64. }
  65. }
  66. const Template = useMemo(() => {
  67. switch (locale) {
  68. case LanguagesSupported[1]:
  69. return <TemplateZh apiBaseUrl={apiBaseUrl} />
  70. case LanguagesSupported[7]:
  71. return <TemplateJa apiBaseUrl={apiBaseUrl} />
  72. default:
  73. return <TemplateEn apiBaseUrl={apiBaseUrl} />
  74. }
  75. }, [apiBaseUrl, locale])
  76. return (
  77. <div className="flex">
  78. <div className={`fixed right-20 top-32 z-10 transition-all ${isTocExpanded ? 'w-64' : 'w-10'}`}>
  79. {isTocExpanded
  80. ? (
  81. <nav className="toc max-h-[calc(100vh-150px)] w-full overflow-y-auto rounded-lg bg-components-panel-bg p-4 shadow-md">
  82. <div className="mb-4 flex items-center justify-between">
  83. <h3 className="text-lg font-semibold text-text-primary">{t('appApi.develop.toc')}</h3>
  84. <button
  85. onClick={() => setIsTocExpanded(false)}
  86. className="text-text-tertiary hover:text-text-secondary"
  87. >
  88. </button>
  89. </div>
  90. <ul className="space-y-2">
  91. {toc.map((item, index) => (
  92. <li key={index}>
  93. <a
  94. href={item.href}
  95. className="text-text-secondary transition-colors duration-200 hover:text-text-primary hover:underline"
  96. onClick={e => handleTocClick(e, item)}
  97. >
  98. {item.text}
  99. </a>
  100. </li>
  101. ))}
  102. </ul>
  103. </nav>
  104. )
  105. : (
  106. <button
  107. onClick={() => setIsTocExpanded(true)}
  108. className="flex h-10 w-10 items-center justify-center rounded-full bg-components-button-secondary-bg shadow-md transition-colors duration-200 hover:bg-components-button-secondary-bg-hover"
  109. >
  110. <RiListUnordered className="h-6 w-6 text-components-button-secondary-text" />
  111. </button>
  112. )}
  113. </div>
  114. <article className={cn('prose-xl prose mx-1 rounded-t-xl bg-background-default px-4 pt-16 sm:mx-12', theme === Theme.dark && 'prose-invert')}>
  115. {Template}
  116. </article>
  117. </div>
  118. )
  119. }
  120. export default Doc