docs-i18n 0.6.3 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{src/admin/ui → admin/app}/components/JobDialog.tsx +21 -2
- package/{src/admin/ui → admin/app}/components/JobPanel.tsx +1 -1
- package/{src/admin/ui → admin/app}/components/Preview.tsx +2 -5
- package/{src/admin/ui → admin/app}/lib/api.ts +18 -39
- package/admin/app/routeTree.gen.ts +68 -0
- package/admin/app/router.tsx +23 -0
- package/admin/app/routes/__root.tsx +55 -0
- package/admin/app/routes/index.tsx +416 -0
- package/{src/admin/ui → admin/app}/styles.css +36 -3
- package/admin/package.json +26 -0
- package/admin/server/functions/jobs.ts +53 -0
- package/admin/server/functions/misc.ts +84 -0
- package/{src/admin/server/routes → admin/server/functions}/models.ts +16 -29
- package/admin/server/functions/status.ts +61 -0
- package/admin/server/index.ts +35 -0
- package/admin/server/init.ts +46 -0
- package/{src/admin → admin}/server/services/job-manager.ts +39 -10
- package/{src/admin → admin}/server/services/status.ts +6 -6
- package/admin/tsconfig.json +19 -0
- package/{src/admin → admin}/vite.config.ts +8 -2
- package/dist/{assemble-7H4QCW35.js → assemble-CP2BRYQJ.js} +6 -4
- package/dist/{chunk-A3YQNPKZ.js → chunk-CLYUAWZE.js} +1 -1
- package/dist/{chunk-YN4VJHCQ.js → chunk-JHBSHTXC.js} +1 -1
- package/dist/chunk-L64GJ4OB.js +32 -0
- package/dist/{chunk-SKKZIV3L.js → chunk-PNKVD2UK.js} +1 -29
- package/dist/{chunk-XEOYZUHS.js → chunk-QKIR7RKQ.js} +4 -31
- package/dist/chunk-TRURQFP4.js +31 -0
- package/dist/cli.js +108 -7
- package/dist/index.d.ts +41 -1
- package/dist/index.js +92 -3
- package/dist/{rescan-O5D3CYC2.js → rescan-HXMWFAOC.js} +5 -3
- package/dist/{status-F4MYIAAY.js → status-AGZDXOTZ.js} +4 -2
- package/dist/{translate-ZIVKNAC4.js → translate-A5X6MX4Y.js} +14 -7
- package/dist/upload-XL6KG6S2.js +132 -0
- package/package.json +17 -15
- package/template/app/components/BlogArticle.tsx +159 -0
- package/template/app/components/BlogList.tsx +88 -0
- package/template/app/components/Breadcrumbs.tsx +81 -0
- package/template/app/components/Card.tsx +31 -0
- package/template/app/components/Doc.tsx +191 -0
- package/template/app/components/DocBreadcrumb.tsx +60 -0
- package/template/app/components/DocContainer.tsx +13 -0
- package/template/app/components/DocTitle.tsx +11 -0
- package/template/app/components/DocsLayout.tsx +715 -0
- package/template/app/components/Dropdown.tsx +116 -0
- package/template/app/components/FallbackBanner.tsx +36 -0
- package/template/app/components/Footer.tsx +29 -0
- package/template/app/components/FrameworkSelect.tsx +150 -0
- package/template/app/components/LibraryCard.tsx +178 -0
- package/template/app/components/LocaleSwitcher.tsx +43 -0
- package/template/app/components/Navbar.tsx +430 -0
- package/template/app/components/PostNotFound.tsx +20 -0
- package/template/app/components/SearchButton.tsx +32 -0
- package/template/app/components/Select.tsx +103 -0
- package/template/app/components/Spinner.tsx +18 -0
- package/template/app/components/ThemeProvider.tsx +141 -0
- package/template/app/components/ThemeToggle.tsx +31 -0
- package/template/app/components/Toc.tsx +86 -0
- package/template/app/components/VersionSelect.tsx +118 -0
- package/template/app/components/icons/BSkyIcon.tsx +27 -0
- package/template/app/components/icons/BaseballCapIcon.tsx +25 -0
- package/template/app/components/icons/BrandXIcon.tsx +28 -0
- package/template/app/components/icons/CheckCircleIcon.tsx +28 -0
- package/template/app/components/icons/CogsIcon.tsx +25 -0
- package/template/app/components/icons/DiscordIcon.tsx +24 -0
- package/template/app/components/icons/GithubIcon.tsx +24 -0
- package/template/app/components/icons/GoogleIcon.tsx +24 -0
- package/template/app/components/icons/InstagramIcon.tsx +24 -0
- package/template/app/components/icons/NpmIcon.tsx +26 -0
- package/template/app/components/icons/YinYangIcon.tsx +26 -0
- package/template/app/components/icons/YouTubeIcon.tsx +24 -0
- package/template/app/components/markdown/CodeBlock.tsx +254 -0
- package/template/app/components/markdown/FileTabs.tsx +58 -0
- package/template/app/components/markdown/FrameworkContent.tsx +76 -0
- package/template/app/components/markdown/Markdown.tsx +216 -0
- package/template/app/components/markdown/MarkdownContent.tsx +89 -0
- package/template/app/components/markdown/MarkdownFrameworkHandler.tsx +66 -0
- package/template/app/components/markdown/MarkdownHeadingContext.tsx +35 -0
- package/template/app/components/markdown/MarkdownLink.tsx +46 -0
- package/template/app/components/markdown/MarkdownTabsHandler.tsx +109 -0
- package/template/app/components/markdown/PackageManagerTabs.tsx +95 -0
- package/template/app/components/markdown/Tabs.tsx +139 -0
- package/template/app/components/markdown/index.ts +15 -0
- package/template/app/components/ui/Button.tsx +141 -0
- package/template/app/components/ui/InlineCode.tsx +16 -0
- package/template/app/components/ui/MarkdownImg.tsx +21 -0
- package/template/app/config/frameworks.ts +93 -0
- package/template/app/contexts/SearchContext.tsx +36 -0
- package/template/app/db/index.ts +17 -0
- package/template/app/db/schema.ts +74 -0
- package/template/app/hooks/useClickOutside.ts +106 -0
- package/template/app/routeTree.gen.ts +584 -0
- package/template/app/router.tsx +29 -0
- package/template/app/routes/$lang.$project.$version.docs.$.tsx +128 -0
- package/template/app/routes/$lang.$project.$version.docs.framework.$framework.$.tsx +106 -0
- package/template/app/routes/$lang.$project.$version.docs.framework.$framework.index.tsx +27 -0
- package/template/app/routes/$lang.$project.$version.docs.framework.index.tsx +44 -0
- package/template/app/routes/$lang.$project.$version.docs.index.tsx +27 -0
- package/template/app/routes/$lang.$project.$version.docs.tsx +70 -0
- package/template/app/routes/$lang.$project.$version.tsx +69 -0
- package/template/app/routes/$lang.$project.docs.$.tsx +104 -0
- package/template/app/routes/$lang.$project.docs.index.tsx +20 -0
- package/template/app/routes/$lang.$project.docs.tsx +79 -0
- package/template/app/routes/$lang.$project.tsx +89 -0
- package/template/app/routes/$lang.blog.$.tsx +82 -0
- package/template/app/routes/$lang.blog.index.tsx +56 -0
- package/template/app/routes/$lang.blog.tsx +26 -0
- package/template/app/routes/$lang.docs.$.tsx +100 -0
- package/template/app/routes/$lang.docs.framework.$framework.$.tsx +104 -0
- package/template/app/routes/$lang.docs.framework.$framework.index.tsx +32 -0
- package/template/app/routes/$lang.docs.framework.index.tsx +47 -0
- package/template/app/routes/$lang.docs.index.tsx +20 -0
- package/template/app/routes/$lang.docs.tsx +90 -0
- package/template/app/routes/$lang.tsx +16 -0
- package/template/app/routes/__root.tsx +180 -0
- package/template/app/routes/index.tsx +89 -0
- package/template/app/site.config.ts +182 -0
- package/template/app/styles/app.css +1029 -0
- package/template/app/types/index.ts +77 -0
- package/template/app/utils/blog.server.ts +193 -0
- package/template/app/utils/blog.ts +42 -0
- package/template/app/utils/config.ts +120 -0
- package/template/app/utils/content-loader.ts +400 -0
- package/template/app/utils/dates.ts +29 -0
- package/template/app/utils/docs.server.ts +150 -0
- package/template/app/utils/markdown/filterFrameworkContent.ts +233 -0
- package/template/app/utils/markdown/index.ts +2 -0
- package/template/app/utils/markdown/installCommand.ts +143 -0
- package/template/app/utils/markdown/plugins/collectHeadings.ts +104 -0
- package/template/app/utils/markdown/plugins/extractCodeMeta.ts +57 -0
- package/template/app/utils/markdown/plugins/helpers.ts +33 -0
- package/template/app/utils/markdown/plugins/index.ts +8 -0
- package/template/app/utils/markdown/plugins/parseCommentComponents.ts +103 -0
- package/template/app/utils/markdown/plugins/transformCommentComponents.ts +23 -0
- package/template/app/utils/markdown/plugins/transformFrameworkComponent.ts +217 -0
- package/template/app/utils/markdown/plugins/transformTabsComponent.ts +359 -0
- package/template/app/utils/markdown/processor.ts +75 -0
- package/template/app/utils/site-config.tsx +11 -0
- package/template/app/utils/upload.ts +232 -0
- package/template/app/utils/useLocalStorage.ts +65 -0
- package/template/app/utils/utils.ts +23 -0
- package/template/package.json +53 -0
- package/template/public/favicon.svg +1 -0
- package/template/public/fonts/Inter-latin-ext.woff2 +0 -0
- package/template/public/fonts/Inter-latin.woff2 +0 -0
- package/template/public/images/frameworks/angular-logo.svg +1 -0
- package/template/public/images/frameworks/js-logo.svg +1 -0
- package/template/public/images/frameworks/lit-logo.svg +1 -0
- package/template/public/images/frameworks/preact-logo.svg +6 -0
- package/template/public/images/frameworks/qwik-logo.svg +1 -0
- package/template/public/images/frameworks/react-logo.svg +1 -0
- package/template/public/images/frameworks/solid-logo.svg +1 -0
- package/template/public/images/frameworks/svelte-logo.svg +1 -0
- package/template/public/images/frameworks/vue-logo.svg +4 -0
- package/template/tsconfig.json +24 -0
- package/template/vite.config.ts +43 -0
- package/template/wrangler.jsonc +16 -0
- package/README.md +0 -161
- package/dist/server-73AVSOL5.js +0 -598
- package/src/admin/index.html +0 -13
- package/src/admin/server/index.ts +0 -138
- package/src/admin/server/routes/jobs.ts +0 -113
- package/src/admin/server/routes/status.ts +0 -57
- package/src/admin/ui/App.tsx +0 -332
- package/src/admin/ui/main.tsx +0 -19
- /package/{src/admin/ui → admin/app}/components/FileList.tsx +0 -0
- /package/{src/admin/ui → admin/app}/components/LangGrid.tsx +0 -0
- /package/{src/admin/ui → admin/app}/components/ProgressBar.tsx +0 -0
- /package/{src/admin/ui → admin/app}/lib/flags.ts +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blog post list component.
|
|
3
|
+
*
|
|
4
|
+
* Displays a responsive grid of blog post cards matching tanstack.com styling:
|
|
5
|
+
* - Header image (if present)
|
|
6
|
+
* - Title, author, date
|
|
7
|
+
* - Excerpt with line clamping
|
|
8
|
+
* - "Read More" link
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Link } from '@tanstack/react-router'
|
|
12
|
+
import { Card } from '~/components/Card'
|
|
13
|
+
import { format } from '~/utils/dates'
|
|
14
|
+
import { formatAuthors } from '~/utils/blog'
|
|
15
|
+
import type { BlogPostMeta } from '~/utils/blog'
|
|
16
|
+
|
|
17
|
+
type BlogListProps = {
|
|
18
|
+
posts: BlogPostMeta[]
|
|
19
|
+
lang: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function BlogList({ posts, lang }: BlogListProps) {
|
|
23
|
+
if (posts.length === 0) {
|
|
24
|
+
return (
|
|
25
|
+
<div className="py-20 text-center">
|
|
26
|
+
<p className="text-lg text-gray-500 dark:text-gray-400">
|
|
27
|
+
No blog posts yet. Check back soon!
|
|
28
|
+
</p>
|
|
29
|
+
</div>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<section className="grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-3 gap-4">
|
|
35
|
+
{posts.map(
|
|
36
|
+
({ slug, title, published, excerpt, headerImage, authors }) => {
|
|
37
|
+
return (
|
|
38
|
+
<Card
|
|
39
|
+
key={slug}
|
|
40
|
+
as={Link}
|
|
41
|
+
to={`/${lang}/blog/${slug}`}
|
|
42
|
+
className="relative flex flex-col justify-between overflow-hidden transition-all hover:shadow-sm hover:border-blue-500"
|
|
43
|
+
>
|
|
44
|
+
{headerImage ? (
|
|
45
|
+
<div className="aspect-video overflow-hidden bg-gray-100 dark:bg-gray-800">
|
|
46
|
+
<img
|
|
47
|
+
src={headerImage}
|
|
48
|
+
alt=""
|
|
49
|
+
loading="lazy"
|
|
50
|
+
decoding="async"
|
|
51
|
+
className="w-full h-full object-cover"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
) : null}
|
|
55
|
+
<div className="p-4 md:p-8 flex flex-col gap-4 flex-1 justify-between">
|
|
56
|
+
<div>
|
|
57
|
+
<div className="text-lg font-extrabold">{title}</div>
|
|
58
|
+
<div className="text-xs italic font-light mt-1">
|
|
59
|
+
by {formatAuthors(authors)}
|
|
60
|
+
{published ? (
|
|
61
|
+
<time
|
|
62
|
+
dateTime={published}
|
|
63
|
+
title={format(new Date(published), 'MMM d, yyyy')}
|
|
64
|
+
>
|
|
65
|
+
{' '}
|
|
66
|
+
on {format(new Date(published), 'MMM d, yyyy')}
|
|
67
|
+
</time>
|
|
68
|
+
) : null}
|
|
69
|
+
</div>
|
|
70
|
+
{excerpt ? (
|
|
71
|
+
<p className="text-sm mt-4 text-gray-600 dark:text-gray-400 leading-7 line-clamp-4">
|
|
72
|
+
{excerpt}
|
|
73
|
+
</p>
|
|
74
|
+
) : null}
|
|
75
|
+
</div>
|
|
76
|
+
<div>
|
|
77
|
+
<div className="text-blue-500 uppercase font-black text-sm">
|
|
78
|
+
Read More
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</Card>
|
|
83
|
+
)
|
|
84
|
+
},
|
|
85
|
+
)}
|
|
86
|
+
</section>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Link } from '@tanstack/react-router'
|
|
2
|
+
import { ChevronDown } from 'lucide-react'
|
|
3
|
+
import { twMerge } from 'tailwind-merge'
|
|
4
|
+
import type { MarkdownHeading } from '~/types'
|
|
5
|
+
import {
|
|
6
|
+
Dropdown,
|
|
7
|
+
DropdownTrigger,
|
|
8
|
+
DropdownContent,
|
|
9
|
+
DropdownItem,
|
|
10
|
+
} from './Dropdown'
|
|
11
|
+
|
|
12
|
+
type BreadcrumbsProps = {
|
|
13
|
+
/** Section label (e.g., "Getting Started", "Blog") */
|
|
14
|
+
section: string
|
|
15
|
+
/** Optional link for the section */
|
|
16
|
+
sectionTo?: string
|
|
17
|
+
headings?: MarkdownHeading[]
|
|
18
|
+
/** Breakpoint at which the TOC toggle is hidden (default: 'lg') */
|
|
19
|
+
tocHiddenBreakpoint?: 'md' | 'lg'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function Breadcrumbs({
|
|
23
|
+
section,
|
|
24
|
+
sectionTo,
|
|
25
|
+
headings,
|
|
26
|
+
tocHiddenBreakpoint = 'lg',
|
|
27
|
+
}: BreadcrumbsProps) {
|
|
28
|
+
const showTocToggle = headings && headings.length > 1
|
|
29
|
+
const hiddenClass = tocHiddenBreakpoint === 'md' ? 'md:hidden' : 'lg:hidden'
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className="flex flex-col">
|
|
33
|
+
<div className="flex items-center justify-between gap-4 text-sm text-gray-500 dark:text-gray-400">
|
|
34
|
+
{sectionTo ? (
|
|
35
|
+
<Link
|
|
36
|
+
to={sectionTo}
|
|
37
|
+
className="whitespace-nowrap hover:text-gray-700 dark:hover:text-gray-200 transition-colors"
|
|
38
|
+
>
|
|
39
|
+
{section}
|
|
40
|
+
</Link>
|
|
41
|
+
) : (
|
|
42
|
+
<span className="whitespace-nowrap">{section}</span>
|
|
43
|
+
)}
|
|
44
|
+
{showTocToggle && (
|
|
45
|
+
<Dropdown>
|
|
46
|
+
<DropdownTrigger>
|
|
47
|
+
<button
|
|
48
|
+
className={twMerge(
|
|
49
|
+
hiddenClass,
|
|
50
|
+
'whitespace-nowrap inline-flex items-center gap-1 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors cursor-pointer',
|
|
51
|
+
)}
|
|
52
|
+
>
|
|
53
|
+
<span>On this page</span>
|
|
54
|
+
<ChevronDown className="w-3.5 h-3.5" />
|
|
55
|
+
</button>
|
|
56
|
+
</DropdownTrigger>
|
|
57
|
+
<DropdownContent align="end" sideOffset={8} className={hiddenClass}>
|
|
58
|
+
{headings.map((heading) => (
|
|
59
|
+
<DropdownItem key={`breadcrumb-toc-${heading.id}`} asChild>
|
|
60
|
+
<Link
|
|
61
|
+
to="."
|
|
62
|
+
hash={heading.id}
|
|
63
|
+
style={{
|
|
64
|
+
paddingLeft: `${(heading.level - 2) * 0.5 + 0.5}rem`,
|
|
65
|
+
}}
|
|
66
|
+
resetScroll={false}
|
|
67
|
+
hashScrollIntoView={{
|
|
68
|
+
behavior: 'smooth',
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<span dangerouslySetInnerHTML={{ __html: heading.text }} />
|
|
72
|
+
</Link>
|
|
73
|
+
</DropdownItem>
|
|
74
|
+
))}
|
|
75
|
+
</DropdownContent>
|
|
76
|
+
</Dropdown>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { twMerge } from 'tailwind-merge'
|
|
3
|
+
|
|
4
|
+
type CardOwnProps<TElement extends React.ElementType = 'div'> = {
|
|
5
|
+
as?: TElement
|
|
6
|
+
children: React.ReactNode
|
|
7
|
+
className?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type CardProps<TElement extends React.ElementType = 'div'> =
|
|
11
|
+
CardOwnProps<TElement> &
|
|
12
|
+
Omit<React.ComponentPropsWithoutRef<TElement>, keyof CardOwnProps<TElement>>
|
|
13
|
+
|
|
14
|
+
type CardComponent = <TElement extends React.ElementType = 'div'>(
|
|
15
|
+
props: CardProps<TElement>,
|
|
16
|
+
) => React.ReactNode
|
|
17
|
+
|
|
18
|
+
export const Card: CardComponent = ({ as, children, className, ...props }) => {
|
|
19
|
+
const Component = as || 'div'
|
|
20
|
+
return React.createElement(
|
|
21
|
+
Component,
|
|
22
|
+
{
|
|
23
|
+
className: twMerge(
|
|
24
|
+
'bg-white dark:bg-gray-900 rounded-lg shadow-md border border-gray-200 dark:border-gray-800',
|
|
25
|
+
className,
|
|
26
|
+
),
|
|
27
|
+
...props,
|
|
28
|
+
},
|
|
29
|
+
children,
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { FoldHorizontal, UnfoldHorizontal } from 'lucide-react'
|
|
3
|
+
import { twMerge } from 'tailwind-merge'
|
|
4
|
+
import { useWidthToggle, DocNavigation } from '~/components/DocsLayout'
|
|
5
|
+
|
|
6
|
+
import { Toc } from './Toc'
|
|
7
|
+
import { renderMarkdown } from '~/utils/markdown'
|
|
8
|
+
import { DocBreadcrumb } from './DocBreadcrumb'
|
|
9
|
+
import { FallbackBanner } from './FallbackBanner'
|
|
10
|
+
import { MarkdownContent } from '~/components/markdown'
|
|
11
|
+
import type { DocsConfig, MarkdownHeading } from '~/types'
|
|
12
|
+
import { useLocalCurrentFramework } from './FrameworkSelect'
|
|
13
|
+
import { useParams } from '@tanstack/react-router'
|
|
14
|
+
|
|
15
|
+
type DocProps = {
|
|
16
|
+
title: string
|
|
17
|
+
content: string
|
|
18
|
+
repo: string
|
|
19
|
+
branch: string
|
|
20
|
+
filePath: string
|
|
21
|
+
shouldRenderToc?: boolean
|
|
22
|
+
colorFrom?: string
|
|
23
|
+
colorTo?: string
|
|
24
|
+
textColor?: string
|
|
25
|
+
// Breadcrumb props (optional)
|
|
26
|
+
config?: DocsConfig
|
|
27
|
+
// Footer content rendered after markdown
|
|
28
|
+
footer?: React.ReactNode
|
|
29
|
+
// Optional framework to use (overrides URL and local storage)
|
|
30
|
+
framework?: string
|
|
31
|
+
// Whether the content is a fallback (untranslated)
|
|
32
|
+
isFallback?: boolean
|
|
33
|
+
// Locale display name for fallback banner
|
|
34
|
+
locale?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function Doc({
|
|
38
|
+
title,
|
|
39
|
+
content,
|
|
40
|
+
repo,
|
|
41
|
+
branch,
|
|
42
|
+
filePath,
|
|
43
|
+
shouldRenderToc = true,
|
|
44
|
+
colorFrom,
|
|
45
|
+
colorTo,
|
|
46
|
+
textColor,
|
|
47
|
+
config,
|
|
48
|
+
footer,
|
|
49
|
+
framework: frameworkProp,
|
|
50
|
+
isFallback = false,
|
|
51
|
+
locale,
|
|
52
|
+
}: DocProps) {
|
|
53
|
+
// Extract headings synchronously during render to avoid hydration mismatch
|
|
54
|
+
const { headings, markup } = React.useMemo(
|
|
55
|
+
() => renderMarkdown(content),
|
|
56
|
+
[content],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
// Get current framework from prop, URL params, or local storage
|
|
60
|
+
const { framework: paramsFramework } = useParams({ strict: false })
|
|
61
|
+
const localCurrentFramework = useLocalCurrentFramework()
|
|
62
|
+
const currentFramework = React.useMemo(() => {
|
|
63
|
+
const fw =
|
|
64
|
+
frameworkProp ||
|
|
65
|
+
paramsFramework ||
|
|
66
|
+
localCurrentFramework.currentFramework ||
|
|
67
|
+
'react'
|
|
68
|
+
return typeof fw === 'string' ? fw.toLowerCase() : fw
|
|
69
|
+
}, [frameworkProp, paramsFramework, localCurrentFramework.currentFramework])
|
|
70
|
+
|
|
71
|
+
const isTocVisible = shouldRenderToc && headings.length > 1
|
|
72
|
+
|
|
73
|
+
const markdownContainerRef = React.useRef<HTMLDivElement>(null)
|
|
74
|
+
const [activeHeadings, setActiveHeadings] = React.useState<Array<string>>([])
|
|
75
|
+
|
|
76
|
+
const headingElementRefs = React.useRef<
|
|
77
|
+
Record<string, IntersectionObserverEntry>
|
|
78
|
+
>({})
|
|
79
|
+
|
|
80
|
+
// Try to get the width toggle context from DocsLayout
|
|
81
|
+
let isFullWidth = false
|
|
82
|
+
let setIsFullWidth: ((isFullWidth: boolean) => void) | undefined
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const context = useWidthToggle()
|
|
86
|
+
isFullWidth = context.isFullWidth
|
|
87
|
+
setIsFullWidth = context.setIsFullWidth
|
|
88
|
+
} catch {
|
|
89
|
+
// Context not available, that's okay
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
const callback = (headingsList: Array<IntersectionObserverEntry>) => {
|
|
94
|
+
headingElementRefs.current = headingsList.reduce(
|
|
95
|
+
(map, headingElement) => {
|
|
96
|
+
map[headingElement.target.id] = headingElement
|
|
97
|
+
return map
|
|
98
|
+
},
|
|
99
|
+
headingElementRefs.current,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
const visibleHeadings: Array<IntersectionObserverEntry> = []
|
|
103
|
+
Object.keys(headingElementRefs.current).forEach((key) => {
|
|
104
|
+
const headingElement = headingElementRefs.current[key]
|
|
105
|
+
if (headingElement.isIntersecting) {
|
|
106
|
+
visibleHeadings.push(headingElement)
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
if (visibleHeadings.length >= 1) {
|
|
111
|
+
setActiveHeadings(visibleHeadings.map((h) => h.target.id))
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const observer = new IntersectionObserver(callback, {
|
|
116
|
+
rootMargin: '0px',
|
|
117
|
+
threshold: 0.2,
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
const headingElements = Array.from(
|
|
121
|
+
markdownContainerRef.current?.querySelectorAll(
|
|
122
|
+
'h2[id], h3[id], h4[id], h5[id], h6[id]',
|
|
123
|
+
) ?? [],
|
|
124
|
+
)
|
|
125
|
+
headingElements.forEach((el) => observer.observe(el))
|
|
126
|
+
|
|
127
|
+
return () => observer.disconnect()
|
|
128
|
+
}, [headings])
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<div className="flex-1 min-h-0 flex flex-col pt-4 lg:pt-6 xl:pt-8">
|
|
132
|
+
{isFallback && locale && <FallbackBanner locale={locale} />}
|
|
133
|
+
|
|
134
|
+
<div
|
|
135
|
+
className={twMerge(
|
|
136
|
+
'w-full flex mx-auto max-w-[768px]',
|
|
137
|
+
(isTocVisible || isFullWidth) && 'max-w-full',
|
|
138
|
+
)}
|
|
139
|
+
>
|
|
140
|
+
<div className="flex flex-col w-full min-w-0">
|
|
141
|
+
{config && (
|
|
142
|
+
<div className="mb-3">
|
|
143
|
+
<DocBreadcrumb
|
|
144
|
+
config={config}
|
|
145
|
+
headings={isTocVisible ? headings : undefined}
|
|
146
|
+
/>
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
<MarkdownContent
|
|
150
|
+
title={title}
|
|
151
|
+
repo={repo}
|
|
152
|
+
branch={branch}
|
|
153
|
+
filePath={filePath}
|
|
154
|
+
htmlMarkup={markup}
|
|
155
|
+
containerRef={markdownContainerRef}
|
|
156
|
+
currentFramework={currentFramework}
|
|
157
|
+
titleBarActions={
|
|
158
|
+
setIsFullWidth ? (
|
|
159
|
+
<button
|
|
160
|
+
onClick={() => setIsFullWidth(!isFullWidth)}
|
|
161
|
+
className="p-2 mr-4 text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors shrink-0 hidden [@media(min-width:1535px)]:inline-flex"
|
|
162
|
+
title={isFullWidth ? 'Constrain width' : 'Expand width'}
|
|
163
|
+
>
|
|
164
|
+
{isFullWidth ? (
|
|
165
|
+
<FoldHorizontal className="w-4 h-4" />
|
|
166
|
+
) : (
|
|
167
|
+
<UnfoldHorizontal className="w-4 h-4" />
|
|
168
|
+
)}
|
|
169
|
+
</button>
|
|
170
|
+
) : null
|
|
171
|
+
}
|
|
172
|
+
/>
|
|
173
|
+
{footer ?? <DocNavigation />}
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
{isTocVisible && (
|
|
177
|
+
<div className="pl-4 w-32 lg:w-36 xl:w-44 2xl:w-56 3xl:w-64 shrink-0 hidden lg:block transition-all">
|
|
178
|
+
<Toc
|
|
179
|
+
headings={headings}
|
|
180
|
+
activeHeadings={activeHeadings}
|
|
181
|
+
colorFrom={colorFrom}
|
|
182
|
+
colorTo={colorTo}
|
|
183
|
+
textColor={textColor}
|
|
184
|
+
currentFramework={currentFramework}
|
|
185
|
+
/>
|
|
186
|
+
</div>
|
|
187
|
+
)}
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useParams } from '@tanstack/react-router'
|
|
2
|
+
import { Breadcrumbs } from './Breadcrumbs'
|
|
3
|
+
import type { DocsConfig, MarkdownHeading } from '~/types'
|
|
4
|
+
|
|
5
|
+
function findSectionForDoc(
|
|
6
|
+
config: DocsConfig,
|
|
7
|
+
docPath: string,
|
|
8
|
+
): string | null {
|
|
9
|
+
for (const section of config.sections) {
|
|
10
|
+
// Check core docs
|
|
11
|
+
for (const child of section.children) {
|
|
12
|
+
if (child.to === docPath) {
|
|
13
|
+
return section.label
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Check framework-specific docs in all frameworks
|
|
18
|
+
if (section.frameworks) {
|
|
19
|
+
for (const frameworkSection of section.frameworks) {
|
|
20
|
+
for (const child of frameworkSection.children) {
|
|
21
|
+
if (child.to === docPath) {
|
|
22
|
+
return section.label
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function DocBreadcrumb({
|
|
33
|
+
config,
|
|
34
|
+
headings,
|
|
35
|
+
}: {
|
|
36
|
+
config: DocsConfig
|
|
37
|
+
headings?: MarkdownHeading[]
|
|
38
|
+
}) {
|
|
39
|
+
const { _splat, framework } = useParams({ strict: false })
|
|
40
|
+
|
|
41
|
+
// Build the full doc path as it appears in config
|
|
42
|
+
// Config paths are like "overview" or "framework/react/overview"
|
|
43
|
+
const fullDocPath = framework ? `framework/${framework}/${_splat}` : _splat
|
|
44
|
+
|
|
45
|
+
// Find the section for this doc
|
|
46
|
+
const section = fullDocPath ? findSectionForDoc(config, fullDocPath) : null
|
|
47
|
+
|
|
48
|
+
// Only show if we found a section
|
|
49
|
+
if (!section) {
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Breadcrumbs
|
|
55
|
+
section={section}
|
|
56
|
+
headings={headings}
|
|
57
|
+
tocHiddenBreakpoint="lg"
|
|
58
|
+
/>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { HTMLProps } from 'react'
|
|
2
|
+
import { twMerge } from 'tailwind-merge'
|
|
3
|
+
|
|
4
|
+
export function DocContainer({
|
|
5
|
+
children,
|
|
6
|
+
...props
|
|
7
|
+
}: { children: React.ReactNode } & HTMLProps<HTMLDivElement>) {
|
|
8
|
+
return (
|
|
9
|
+
<div {...props} className={twMerge('w-full max-w-full', props.className)}>
|
|
10
|
+
{children}
|
|
11
|
+
</div>
|
|
12
|
+
)
|
|
13
|
+
}
|