docs-i18n 0.6.2 → 0.7.0

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.
Files changed (169) hide show
  1. package/{src/admin/ui → admin/app}/components/JobDialog.tsx +21 -2
  2. package/{src/admin/ui → admin/app}/components/JobPanel.tsx +1 -1
  3. package/{src/admin/ui → admin/app}/components/Preview.tsx +2 -5
  4. package/{src/admin/ui → admin/app}/lib/api.ts +18 -39
  5. package/admin/app/routeTree.gen.ts +68 -0
  6. package/admin/app/router.tsx +23 -0
  7. package/admin/app/routes/__root.tsx +55 -0
  8. package/admin/app/routes/index.tsx +416 -0
  9. package/{src/admin/ui → admin/app}/styles.css +36 -3
  10. package/admin/package.json +27 -0
  11. package/admin/server/functions/jobs.ts +53 -0
  12. package/admin/server/functions/misc.ts +84 -0
  13. package/{src/admin/server/routes → admin/server/functions}/models.ts +16 -29
  14. package/admin/server/functions/status.ts +61 -0
  15. package/admin/server/index.ts +35 -0
  16. package/admin/server/init.ts +46 -0
  17. package/{src/admin → admin}/server/services/job-manager.ts +39 -10
  18. package/{src/admin → admin}/server/services/status.ts +6 -6
  19. package/admin/tsconfig.json +19 -0
  20. package/{src/admin → admin}/vite.config.ts +8 -2
  21. package/dist/{assemble-7H4QCW35.js → assemble-CP2BRYQJ.js} +6 -4
  22. package/dist/{chunk-A3YQNPKZ.js → chunk-CLYUAWZE.js} +1 -1
  23. package/dist/{chunk-YN4VJHCQ.js → chunk-JHBSHTXC.js} +1 -1
  24. package/dist/chunk-L64GJ4OB.js +32 -0
  25. package/dist/{chunk-SKKZIV3L.js → chunk-PNKVD2UK.js} +1 -29
  26. package/dist/{chunk-XEOYZUHS.js → chunk-QKIR7RKQ.js} +4 -31
  27. package/dist/chunk-TRURQFP4.js +31 -0
  28. package/dist/cli.js +108 -23
  29. package/dist/index.d.ts +41 -1
  30. package/dist/index.js +92 -3
  31. package/dist/{rescan-O5D3CYC2.js → rescan-HXMWFAOC.js} +5 -3
  32. package/dist/{status-F4MYIAAY.js → status-AGZDXOTZ.js} +4 -2
  33. package/dist/{translate-ZIVKNAC4.js → translate-A5X6MX4Y.js} +14 -7
  34. package/dist/upload-XL6KG6S2.js +132 -0
  35. package/package.json +17 -15
  36. package/template/app/components/BlogArticle.tsx +159 -0
  37. package/template/app/components/BlogList.tsx +88 -0
  38. package/template/app/components/Breadcrumbs.tsx +81 -0
  39. package/template/app/components/Card.tsx +31 -0
  40. package/template/app/components/Doc.tsx +191 -0
  41. package/template/app/components/DocBreadcrumb.tsx +60 -0
  42. package/template/app/components/DocContainer.tsx +13 -0
  43. package/template/app/components/DocTitle.tsx +11 -0
  44. package/template/app/components/DocsLayout.tsx +715 -0
  45. package/template/app/components/Dropdown.tsx +116 -0
  46. package/template/app/components/FallbackBanner.tsx +36 -0
  47. package/template/app/components/Footer.tsx +29 -0
  48. package/template/app/components/FrameworkSelect.tsx +150 -0
  49. package/template/app/components/LibraryCard.tsx +178 -0
  50. package/template/app/components/LocaleSwitcher.tsx +43 -0
  51. package/template/app/components/Navbar.tsx +430 -0
  52. package/template/app/components/PostNotFound.tsx +20 -0
  53. package/template/app/components/SearchButton.tsx +32 -0
  54. package/template/app/components/Select.tsx +103 -0
  55. package/template/app/components/Spinner.tsx +18 -0
  56. package/template/app/components/ThemeProvider.tsx +141 -0
  57. package/template/app/components/ThemeToggle.tsx +31 -0
  58. package/template/app/components/Toc.tsx +86 -0
  59. package/template/app/components/VersionSelect.tsx +118 -0
  60. package/template/app/components/icons/BSkyIcon.tsx +27 -0
  61. package/template/app/components/icons/BaseballCapIcon.tsx +25 -0
  62. package/template/app/components/icons/BrandXIcon.tsx +28 -0
  63. package/template/app/components/icons/CheckCircleIcon.tsx +28 -0
  64. package/template/app/components/icons/CogsIcon.tsx +25 -0
  65. package/template/app/components/icons/DiscordIcon.tsx +24 -0
  66. package/template/app/components/icons/GithubIcon.tsx +24 -0
  67. package/template/app/components/icons/GoogleIcon.tsx +24 -0
  68. package/template/app/components/icons/InstagramIcon.tsx +24 -0
  69. package/template/app/components/icons/NpmIcon.tsx +26 -0
  70. package/template/app/components/icons/YinYangIcon.tsx +26 -0
  71. package/template/app/components/icons/YouTubeIcon.tsx +24 -0
  72. package/template/app/components/markdown/CodeBlock.tsx +254 -0
  73. package/template/app/components/markdown/FileTabs.tsx +58 -0
  74. package/template/app/components/markdown/FrameworkContent.tsx +76 -0
  75. package/template/app/components/markdown/Markdown.tsx +216 -0
  76. package/template/app/components/markdown/MarkdownContent.tsx +89 -0
  77. package/template/app/components/markdown/MarkdownFrameworkHandler.tsx +66 -0
  78. package/template/app/components/markdown/MarkdownHeadingContext.tsx +35 -0
  79. package/template/app/components/markdown/MarkdownLink.tsx +46 -0
  80. package/template/app/components/markdown/MarkdownTabsHandler.tsx +109 -0
  81. package/template/app/components/markdown/PackageManagerTabs.tsx +95 -0
  82. package/template/app/components/markdown/Tabs.tsx +139 -0
  83. package/template/app/components/markdown/index.ts +15 -0
  84. package/template/app/components/ui/Button.tsx +141 -0
  85. package/template/app/components/ui/InlineCode.tsx +16 -0
  86. package/template/app/components/ui/MarkdownImg.tsx +21 -0
  87. package/template/app/config/frameworks.ts +93 -0
  88. package/template/app/contexts/SearchContext.tsx +36 -0
  89. package/template/app/db/index.ts +17 -0
  90. package/template/app/db/schema.ts +74 -0
  91. package/template/app/hooks/useClickOutside.ts +106 -0
  92. package/template/app/routeTree.gen.ts +584 -0
  93. package/template/app/router.tsx +29 -0
  94. package/template/app/routes/$lang.$project.$version.docs.$.tsx +128 -0
  95. package/template/app/routes/$lang.$project.$version.docs.framework.$framework.$.tsx +106 -0
  96. package/template/app/routes/$lang.$project.$version.docs.framework.$framework.index.tsx +27 -0
  97. package/template/app/routes/$lang.$project.$version.docs.framework.index.tsx +44 -0
  98. package/template/app/routes/$lang.$project.$version.docs.index.tsx +27 -0
  99. package/template/app/routes/$lang.$project.$version.docs.tsx +70 -0
  100. package/template/app/routes/$lang.$project.$version.tsx +69 -0
  101. package/template/app/routes/$lang.$project.docs.$.tsx +104 -0
  102. package/template/app/routes/$lang.$project.docs.index.tsx +20 -0
  103. package/template/app/routes/$lang.$project.docs.tsx +79 -0
  104. package/template/app/routes/$lang.$project.tsx +89 -0
  105. package/template/app/routes/$lang.blog.$.tsx +82 -0
  106. package/template/app/routes/$lang.blog.index.tsx +56 -0
  107. package/template/app/routes/$lang.blog.tsx +26 -0
  108. package/template/app/routes/$lang.docs.$.tsx +100 -0
  109. package/template/app/routes/$lang.docs.framework.$framework.$.tsx +104 -0
  110. package/template/app/routes/$lang.docs.framework.$framework.index.tsx +32 -0
  111. package/template/app/routes/$lang.docs.framework.index.tsx +47 -0
  112. package/template/app/routes/$lang.docs.index.tsx +20 -0
  113. package/template/app/routes/$lang.docs.tsx +90 -0
  114. package/template/app/routes/$lang.tsx +16 -0
  115. package/template/app/routes/__root.tsx +180 -0
  116. package/template/app/routes/index.tsx +89 -0
  117. package/template/app/site.config.ts +182 -0
  118. package/template/app/styles/app.css +1029 -0
  119. package/template/app/types/index.ts +77 -0
  120. package/template/app/utils/blog.server.ts +193 -0
  121. package/template/app/utils/blog.ts +42 -0
  122. package/template/app/utils/config.ts +120 -0
  123. package/template/app/utils/content-loader.ts +400 -0
  124. package/template/app/utils/dates.ts +29 -0
  125. package/template/app/utils/docs.server.ts +150 -0
  126. package/template/app/utils/markdown/filterFrameworkContent.ts +233 -0
  127. package/template/app/utils/markdown/index.ts +2 -0
  128. package/template/app/utils/markdown/installCommand.ts +143 -0
  129. package/template/app/utils/markdown/plugins/collectHeadings.ts +104 -0
  130. package/template/app/utils/markdown/plugins/extractCodeMeta.ts +57 -0
  131. package/template/app/utils/markdown/plugins/helpers.ts +33 -0
  132. package/template/app/utils/markdown/plugins/index.ts +8 -0
  133. package/template/app/utils/markdown/plugins/parseCommentComponents.ts +103 -0
  134. package/template/app/utils/markdown/plugins/transformCommentComponents.ts +23 -0
  135. package/template/app/utils/markdown/plugins/transformFrameworkComponent.ts +217 -0
  136. package/template/app/utils/markdown/plugins/transformTabsComponent.ts +359 -0
  137. package/template/app/utils/markdown/processor.ts +75 -0
  138. package/template/app/utils/site-config.tsx +11 -0
  139. package/template/app/utils/upload.ts +232 -0
  140. package/template/app/utils/useLocalStorage.ts +65 -0
  141. package/template/app/utils/utils.ts +23 -0
  142. package/template/package.json +54 -0
  143. package/template/public/favicon.svg +1 -0
  144. package/template/public/fonts/Inter-latin-ext.woff2 +0 -0
  145. package/template/public/fonts/Inter-latin.woff2 +0 -0
  146. package/template/public/images/frameworks/angular-logo.svg +1 -0
  147. package/template/public/images/frameworks/js-logo.svg +1 -0
  148. package/template/public/images/frameworks/lit-logo.svg +1 -0
  149. package/template/public/images/frameworks/preact-logo.svg +6 -0
  150. package/template/public/images/frameworks/qwik-logo.svg +1 -0
  151. package/template/public/images/frameworks/react-logo.svg +1 -0
  152. package/template/public/images/frameworks/solid-logo.svg +1 -0
  153. package/template/public/images/frameworks/svelte-logo.svg +1 -0
  154. package/template/public/images/frameworks/vue-logo.svg +4 -0
  155. package/template/tsconfig.json +24 -0
  156. package/template/vite.config.ts +43 -0
  157. package/template/wrangler.jsonc +16 -0
  158. package/README.md +0 -161
  159. package/dist/server-73AVSOL5.js +0 -598
  160. package/src/admin/index.html +0 -13
  161. package/src/admin/server/index.ts +0 -138
  162. package/src/admin/server/routes/jobs.ts +0 -113
  163. package/src/admin/server/routes/status.ts +0 -57
  164. package/src/admin/ui/App.tsx +0 -332
  165. package/src/admin/ui/main.tsx +0 -19
  166. /package/{src/admin/ui → admin/app}/components/FileList.tsx +0 -0
  167. /package/{src/admin/ui → admin/app}/components/LangGrid.tsx +0 -0
  168. /package/{src/admin/ui → admin/app}/components/ProgressBar.tsx +0 -0
  169. /package/{src/admin/ui → admin/app}/lib/flags.ts +0 -0
@@ -0,0 +1,89 @@
1
+ import * as React from 'react'
2
+ import { SquarePen } from 'lucide-react'
3
+ import { twMerge } from 'tailwind-merge'
4
+ import { Markdown } from './Markdown'
5
+ import { Button } from '../ui/Button'
6
+
7
+ type MarkdownContentProps = {
8
+ title: string
9
+ repo: string
10
+ branch: string
11
+ filePath: string
12
+ /** Pre-rendered HTML markup (from renderMarkdown). If not provided, rawContent will be rendered. */
13
+ htmlMarkup?: string
14
+ /** Raw markdown content to render. Used if htmlMarkup is not provided. */
15
+ rawContent?: string
16
+ /** Optional render function for raw markdown to HTML */
17
+ renderMarkdown?: (raw: string) => { markup: string; headings: any[] }
18
+ /** Additional elements to render in the title bar (e.g., width toggle button) */
19
+ titleBarActions?: React.ReactNode
20
+ /** Additional class names for the prose container */
21
+ proseClassName?: string
22
+ /** Ref for the markdown container (used for heading intersection observer in docs) */
23
+ containerRef?: React.RefObject<HTMLDivElement | null>
24
+ /** Current framework for filtering markdown content */
25
+ currentFramework?: string
26
+ }
27
+
28
+ export function MarkdownContent({
29
+ title,
30
+ repo,
31
+ branch,
32
+ filePath,
33
+ htmlMarkup,
34
+ rawContent,
35
+ renderMarkdown,
36
+ titleBarActions,
37
+ proseClassName,
38
+ containerRef,
39
+ }: MarkdownContentProps) {
40
+ const markdownElement = htmlMarkup ? (
41
+ <Markdown htmlMarkup={htmlMarkup} />
42
+ ) : rawContent ? (
43
+ <Markdown rawContent={rawContent} renderMarkdown={renderMarkdown} />
44
+ ) : null
45
+
46
+ return (
47
+ <>
48
+ {title ? (
49
+ <div className="flex flex-wrap items-center justify-between gap-2">
50
+ <h1
51
+ className={`flex gap-4 items-center flex-wrap text-xl md:text-2xl font-black`}
52
+ >
53
+ {title}
54
+ </h1>
55
+ <div className="flex items-center gap-2 shrink-0">
56
+ {titleBarActions}
57
+ </div>
58
+ </div>
59
+ ) : null}
60
+ <div className="h-4" />
61
+ <div className="h-px bg-gray-500 opacity-20" />
62
+ <div className="h-4" />
63
+ <div
64
+ ref={containerRef}
65
+ className={twMerge(
66
+ 'prose prose-gray dark:prose-invert max-w-none',
67
+ '[font-size:16px]',
68
+ 'styled-markdown-content',
69
+ proseClassName,
70
+ )}
71
+ >
72
+ {markdownElement}
73
+ </div>
74
+ <div className="h-12" />
75
+ <div className="w-full h-px bg-gray-500 opacity-30" />
76
+ <div className="flex py-4">
77
+ <Button
78
+ variant="ghost"
79
+ size="xs"
80
+ as="a"
81
+ href={`https://github.com/${repo}/edit/${branch}/${filePath}`}
82
+ >
83
+ <SquarePen className="w-3.5 h-3.5" />
84
+ Edit on GitHub
85
+ </Button>
86
+ </div>
87
+ </>
88
+ )
89
+ }
@@ -0,0 +1,66 @@
1
+ import * as React from 'react'
2
+ import { domToReact, Element } from 'html-react-parser'
3
+ import type { HTMLReactParserOptions } from 'html-react-parser'
4
+
5
+ // Helper to resolve different module shapes (named export vs default)
6
+ function resolveModuleDefault(mod: any, key: string): React.ComponentType<any> {
7
+ if (!mod) return undefined as any
8
+ if (mod[key] && typeof mod[key] === 'function') return mod[key]
9
+ if (mod.default) {
10
+ if (mod.default[key] && typeof mod.default[key] === 'function')
11
+ return mod.default[key]
12
+ if (typeof mod.default === 'function') return mod.default
13
+ }
14
+ if (typeof mod === 'function') return mod
15
+ return (mod as any).default ?? (mod as any)
16
+ }
17
+
18
+ const FrameworkContent = React.lazy<React.ComponentType<any>>(() =>
19
+ import('./FrameworkContent').then((mod) => ({
20
+ default: resolveModuleDefault(mod, 'FrameworkContent'),
21
+ })),
22
+ )
23
+
24
+ export function handleFrameworkComponent(
25
+ domNode: Element,
26
+ attributes: Record<string, any>,
27
+ options: HTMLReactParserOptions,
28
+ ) {
29
+ const frameworkMeta = domNode.attribs['data-framework-meta']
30
+ if (!frameworkMeta) {
31
+ return null
32
+ }
33
+
34
+ try {
35
+ const { codeBlocksByFramework } = JSON.parse(frameworkMeta)
36
+ const availableFrameworks = JSON.parse(
37
+ domNode.attribs['data-available-frameworks'] || '[]',
38
+ )
39
+
40
+ const panelElements = domNode.children?.filter(
41
+ (child): child is Element =>
42
+ child instanceof Element && child.name === 'md-framework-panel',
43
+ )
44
+
45
+ // Build panelsByFramework map
46
+ const panelsByFramework: Record<string, React.ReactNode> = {}
47
+ panelElements?.forEach((panel) => {
48
+ const fw = panel.attribs['data-framework']
49
+ if (fw) {
50
+ panelsByFramework[fw] = domToReact(panel.children as any, options)
51
+ }
52
+ })
53
+
54
+ return (
55
+ <React.Suspense fallback={<div>Loading...</div>}>
56
+ <FrameworkContent
57
+ codeBlocksByFramework={codeBlocksByFramework}
58
+ availableFrameworks={availableFrameworks}
59
+ panelsByFramework={panelsByFramework}
60
+ />
61
+ </React.Suspense>
62
+ )
63
+ } catch {
64
+ return null
65
+ }
66
+ }
@@ -0,0 +1,35 @@
1
+ import * as React from 'react'
2
+
3
+ export type MarkdownHeading = {
4
+ depth: number
5
+ slug: string
6
+ text: string
7
+ }
8
+
9
+ const MarkdownHeadingContext = React.createContext<{
10
+ headings: MarkdownHeading[]
11
+ setHeadings(headings: MarkdownHeading[]): void
12
+ }>({
13
+ headings: [],
14
+ setHeadings: () => undefined,
15
+ })
16
+
17
+ export function MarkdownHeadingProvider({
18
+ children,
19
+ }: {
20
+ children: React.ReactNode
21
+ }) {
22
+ const [headings, setHeadings] = React.useState<MarkdownHeading[]>([])
23
+
24
+ const value = React.useMemo(() => ({ headings, setHeadings }), [headings])
25
+
26
+ return (
27
+ <MarkdownHeadingContext.Provider value={value}>
28
+ {children}
29
+ </MarkdownHeadingContext.Provider>
30
+ )
31
+ }
32
+
33
+ export function useMarkdownHeadings() {
34
+ return React.useContext(MarkdownHeadingContext)
35
+ }
@@ -0,0 +1,46 @@
1
+ import { Link } from '@tanstack/react-router'
2
+ import type { HTMLProps } from 'react'
3
+
4
+ function isRelativeLink(link: string) {
5
+ return (
6
+ !link.startsWith(`/`) &&
7
+ !link.startsWith('http://') &&
8
+ !link.startsWith('https://') &&
9
+ !link.startsWith('//') &&
10
+ !link.startsWith('#') &&
11
+ !link.startsWith('mailto:')
12
+ )
13
+ }
14
+
15
+ export function MarkdownLink({
16
+ href: hrefProp,
17
+ ...rest
18
+ }: HTMLProps<HTMLAnchorElement>) {
19
+ if (!isRelativeLink(hrefProp ?? '')) {
20
+ // eslint-disable-next-line jsx-a11y/anchor-has-content
21
+ return <a {...rest} href={hrefProp} />
22
+ }
23
+
24
+ const [hrefWithoutHash, hash] = hrefProp?.split('#') ?? []
25
+ let hrefWithoutMd = hrefWithoutHash.replace('.md', '')
26
+
27
+ // Force relative links to resolve one level higher
28
+ if (hrefWithoutMd.startsWith('../')) {
29
+ hrefWithoutMd = hrefWithoutMd.replace(/^\.\.\//gm, '../../')
30
+ } else if (hrefWithoutMd.startsWith('./')) {
31
+ hrefWithoutMd = hrefWithoutMd.replace(/^\.\//gm, '../')
32
+ } else {
33
+ hrefWithoutMd = `../${hrefWithoutMd}`
34
+ }
35
+
36
+ return (
37
+ <Link
38
+ {...rest}
39
+ unsafeRelative="path"
40
+ to={hrefWithoutMd}
41
+ hash={hash}
42
+ preload={undefined}
43
+ ref={undefined}
44
+ />
45
+ )
46
+ }
@@ -0,0 +1,109 @@
1
+ import * as React from 'react'
2
+ import { domToReact, Element } from 'html-react-parser'
3
+ import type { HTMLReactParserOptions } from 'html-react-parser'
4
+
5
+ // Helper to resolve different module shapes (named export vs default)
6
+ function resolveModuleDefault(mod: any, key: string): React.ComponentType<any> {
7
+ if (!mod) return undefined as any
8
+ if (mod[key] && typeof mod[key] === 'function') return mod[key]
9
+ if (mod.default) {
10
+ if (mod.default[key] && typeof mod.default[key] === 'function')
11
+ return mod.default[key]
12
+ if (typeof mod.default === 'function') return mod.default
13
+ }
14
+ if (typeof mod === 'function') return mod
15
+ return (mod as any).default ?? (mod as any)
16
+ }
17
+
18
+ const Tabs = React.lazy<React.ComponentType<any>>(() =>
19
+ import('./Tabs').then((mod) => ({
20
+ default: resolveModuleDefault(mod, 'Tabs'),
21
+ })),
22
+ )
23
+ const PackageManagerTabs = React.lazy<React.ComponentType<any>>(() =>
24
+ import('./PackageManagerTabs').then((mod) => ({
25
+ default: resolveModuleDefault(mod, 'PackageManagerTabs'),
26
+ })),
27
+ )
28
+ const FileTabs = React.lazy<React.ComponentType<any>>(() =>
29
+ import('./FileTabs').then((mod) => ({
30
+ default: resolveModuleDefault(mod, 'FileTabs'),
31
+ })),
32
+ )
33
+
34
+ export function handleTabsComponent(
35
+ domNode: Element,
36
+ attributes: Record<string, any>,
37
+ options: HTMLReactParserOptions,
38
+ ) {
39
+ const pmMeta = domNode.attribs['data-package-manager-meta']
40
+
41
+ // Handle package-manager variant
42
+ if (pmMeta) {
43
+ try {
44
+ const { packagesByFramework, mode } = JSON.parse(pmMeta)
45
+ const frameworks = Object.keys(packagesByFramework)
46
+
47
+ return (
48
+ <React.Suspense fallback={<div>Loading...</div>}>
49
+ <PackageManagerTabs
50
+ packagesByFramework={packagesByFramework}
51
+ mode={mode}
52
+ frameworks={frameworks}
53
+ />
54
+ </React.Suspense>
55
+ )
56
+ } catch {
57
+ // Fall through to default tabs if parsing fails
58
+ }
59
+ }
60
+
61
+ // Check if this is files variant
62
+ const filesMeta = domNode.attribs['data-files-meta']
63
+ if (filesMeta) {
64
+ try {
65
+ const tabs = attributes.tabs || []
66
+
67
+ const panelElements = domNode.children?.filter(
68
+ (child): child is Element =>
69
+ child instanceof Element && child.name === 'md-tab-panel',
70
+ )
71
+
72
+ const children = panelElements?.map((panel) =>
73
+ domToReact(panel.children as any, options),
74
+ )
75
+
76
+ return (
77
+ <React.Suspense fallback={<div>Loading...</div>}>
78
+ <FileTabs tabs={tabs} children={children as any} />
79
+ </React.Suspense>
80
+ )
81
+ } catch {
82
+ // Fall through to default tabs if parsing fails
83
+ }
84
+ }
85
+
86
+ // Handle default tabs variant
87
+ const tabs = attributes.tabs
88
+
89
+ if (!tabs || !Array.isArray(tabs)) {
90
+ return null
91
+ }
92
+
93
+ const panelElements = domNode.children?.filter(
94
+ (child): child is Element =>
95
+ child instanceof Element && child.name === 'md-tab-panel',
96
+ )
97
+
98
+ const children = panelElements?.map((panel) => {
99
+ const result = domToReact(panel.children as any, options)
100
+ // Wrap in fragment to ensure it's a single React node
101
+ return <>{result}</>
102
+ })
103
+
104
+ return (
105
+ <React.Suspense fallback={<div>Loading...</div>}>
106
+ <Tabs tabs={tabs} children={children as any} />
107
+ </React.Suspense>
108
+ )
109
+ }
@@ -0,0 +1,95 @@
1
+ import { useParams } from '@tanstack/react-router'
2
+ import { create } from 'zustand'
3
+ import { Tabs, type TabDefinition } from './Tabs'
4
+ import { CodeBlock } from './CodeBlock'
5
+ import {
6
+ getInstallCommand,
7
+ PACKAGE_MANAGERS,
8
+ type PackageManager,
9
+ type InstallMode,
10
+ } from '../../utils/markdown/installCommand'
11
+
12
+ // Use zustand for cross-component synchronization
13
+ // This ensures all PackageManagerTabs instances on the page stay in sync
14
+ const usePackageManagerStore = create<{
15
+ packageManager: PackageManager
16
+ setPackageManager: (pm: PackageManager) => void
17
+ }>((set) => ({
18
+ packageManager:
19
+ typeof document !== 'undefined'
20
+ ? (localStorage.getItem('packageManager') as PackageManager) || 'npm'
21
+ : 'npm',
22
+ setPackageManager: (pm: PackageManager) => {
23
+ localStorage.setItem('packageManager', pm)
24
+ set({ packageManager: pm })
25
+ },
26
+ }))
27
+
28
+ // Simple localStorage-based framework preference (replaces useCurrentUserQuery auth dependency)
29
+ function useLocalFrameworkPreference(): string | undefined {
30
+ if (typeof document === 'undefined') return undefined
31
+ return localStorage.getItem('framework') || undefined
32
+ }
33
+
34
+ type PackageManagerTabsProps = {
35
+ packagesByFramework: Record<string, string[][]>
36
+ mode: InstallMode
37
+ frameworks: string[]
38
+ }
39
+
40
+ export function PackageManagerTabs({
41
+ packagesByFramework,
42
+ mode,
43
+ }: PackageManagerTabsProps) {
44
+ const { packageManager: storedPackageManager, setPackageManager } =
45
+ usePackageManagerStore()
46
+
47
+ const { framework: paramsFramework } = useParams({ strict: false })
48
+ const localFramework = useLocalFrameworkPreference()
49
+
50
+ const actualFramework = (paramsFramework ||
51
+ localFramework ||
52
+ 'react') as string
53
+
54
+ const normalizedFramework = actualFramework.toLowerCase()
55
+ const packageGroups = packagesByFramework[normalizedFramework]
56
+
57
+ // Hide component if current framework not in package list
58
+ if (!packageGroups || packageGroups.length === 0) {
59
+ return null
60
+ }
61
+
62
+ // Use stored package manager if valid, otherwise default to first one
63
+ const selectedPackageManager = PACKAGE_MANAGERS.includes(storedPackageManager)
64
+ ? storedPackageManager
65
+ : PACKAGE_MANAGERS[0]
66
+
67
+ // Generate tabs for each package manager
68
+ const tabs: TabDefinition[] = PACKAGE_MANAGERS.map((pm) => ({
69
+ slug: pm,
70
+ name: pm,
71
+ headers: [],
72
+ }))
73
+
74
+ // Generate children (command blocks) for each package manager
75
+ const children = PACKAGE_MANAGERS.map((pm) => {
76
+ const commands = getInstallCommand(pm, packageGroups, mode)
77
+ const commandText = commands.join('\n')
78
+ return (
79
+ <CodeBlock key={pm}>
80
+ <code className="language-bash">{commandText}</code>
81
+ </CodeBlock>
82
+ )
83
+ })
84
+
85
+ return (
86
+ <div className="package-manager-tabs">
87
+ <Tabs
88
+ tabs={tabs}
89
+ children={children}
90
+ activeSlug={selectedPackageManager}
91
+ onTabChange={(slug) => setPackageManager(slug as PackageManager)}
92
+ />
93
+ </div>
94
+ )
95
+ }
@@ -0,0 +1,139 @@
1
+ import { useParams } from '@tanstack/react-router'
2
+ import * as React from 'react'
3
+
4
+ export type FrameworkOption = {
5
+ label: string
6
+ value: string
7
+ logo: string
8
+ color: string
9
+ fontColor: string
10
+ }
11
+
12
+ export type TabDefinition = {
13
+ slug: string
14
+ name: string
15
+ headers?: Array<string>
16
+ }
17
+
18
+ export type TabsProps = {
19
+ tabs?: Array<TabDefinition>
20
+ children?: Array<React.ReactNode> | React.ReactNode
21
+ activeSlug?: string
22
+ onTabChange?: (slug: string) => void
23
+ /** Optional framework options for showing logos in tab buttons */
24
+ frameworkOptions?: FrameworkOption[]
25
+ }
26
+
27
+ export function Tabs({
28
+ tabs: tabsProp = [],
29
+ children: childrenProp,
30
+ activeSlug: controlledActiveSlug,
31
+ onTabChange,
32
+ frameworkOptions = [],
33
+ }: TabsProps) {
34
+ const id = React.useId()
35
+ const childrenArray = React.Children.toArray(childrenProp)
36
+
37
+ const params = useParams({ strict: false })
38
+ const framework = params?.framework ?? undefined
39
+
40
+ const [internalActiveSlug, setInternalActiveSlug] = React.useState(() => {
41
+ const match = framework
42
+ ? tabsProp.find((tab) => tab.slug === framework)
43
+ : undefined
44
+ return match?.slug ?? tabsProp[0]?.slug ?? ''
45
+ })
46
+
47
+ const activeSlug = controlledActiveSlug ?? internalActiveSlug
48
+ const setActiveSlug = React.useCallback(
49
+ (slug: string) => {
50
+ if (onTabChange) {
51
+ onTabChange(slug)
52
+ } else {
53
+ setInternalActiveSlug(slug)
54
+ }
55
+ },
56
+ [onTabChange],
57
+ )
58
+
59
+ if (tabsProp.length === 0) return null
60
+
61
+ return (
62
+ <div className="my-4">
63
+ <div className="not-prose flex items-center justify-start gap-2 rounded-t-md border-1 border-b-none border-gray-200 dark:border-gray-800 overflow-x-auto overflow-y-hidden bg-white dark:bg-gray-950">
64
+ {tabsProp.map((tab) => {
65
+ return (
66
+ <Tab
67
+ key={`${id}-${tab.slug}`}
68
+ id={id}
69
+ tab={tab}
70
+ activeSlug={activeSlug}
71
+ setActiveSlug={setActiveSlug}
72
+ frameworkOptions={frameworkOptions}
73
+ />
74
+ )
75
+ })}
76
+ </div>
77
+ <div
78
+ className={`border border-gray-500/20 rounded-b-md bg-gray-100 dark:bg-gray-900`}
79
+ >
80
+ {childrenArray.map((child, index) => {
81
+ const tab = tabsProp[index]
82
+ if (!tab) return null
83
+ return (
84
+ <div
85
+ key={`${id}-${tab.slug}`}
86
+ data-tab={tab.slug}
87
+ hidden={tab.slug !== activeSlug}
88
+ className="max-w-none flex flex-col gap-2 text-base"
89
+ >
90
+ {child}
91
+ </div>
92
+ )
93
+ })}
94
+ </div>
95
+ </div>
96
+ )
97
+ }
98
+
99
+ const Tab = React.memo(
100
+ ({
101
+ tab,
102
+ activeSlug,
103
+ setActiveSlug,
104
+ frameworkOptions = [],
105
+ }: {
106
+ id?: string
107
+ tab: TabDefinition
108
+ activeSlug: string
109
+ setActiveSlug: (slug: string) => void
110
+ frameworkOptions?: FrameworkOption[]
111
+ }) => {
112
+ const option = React.useMemo(
113
+ () =>
114
+ frameworkOptions.find(
115
+ (o) =>
116
+ o.value === tab.slug.toLowerCase() ||
117
+ o.label.toLowerCase() === tab.name.toLowerCase(),
118
+ ),
119
+ [tab.slug, tab.name, frameworkOptions],
120
+ )
121
+
122
+ return (
123
+ <button
124
+ aria-label={tab.name}
125
+ title={tab.name}
126
+ type="button"
127
+ onClick={() => setActiveSlug(tab.slug)}
128
+ className={`inline-flex items-center justify-center gap-2 px-3 py-1.5 -mb-[1px] border-b-2 text-sm font-bold transition-colors hover:bg-gray-100 dark:hover:bg-gray-800 rounded-t-md overflow-y-none ${
129
+ activeSlug === tab.slug
130
+ ? 'border-current text-current bg-gray-100 dark:bg-gray-900'
131
+ : 'border-transparent text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200'
132
+ }`}
133
+ >
134
+ {option && <img src={option.logo} alt="" className="w-4 h-4 -ml-1" />}
135
+ <span>{tab.name}</span>
136
+ </button>
137
+ )
138
+ },
139
+ )
@@ -0,0 +1,15 @@
1
+ export { CodeBlock } from './CodeBlock'
2
+ export { Markdown } from './Markdown'
3
+ export { MarkdownContent } from './MarkdownContent'
4
+ export { MarkdownLink } from './MarkdownLink'
5
+ export { Tabs, type TabDefinition, type TabsProps, type FrameworkOption } from './Tabs'
6
+ export { FileTabs, type FileTabDefinition, type FileTabsProps } from './FileTabs'
7
+ export { PackageManagerTabs } from './PackageManagerTabs'
8
+ export { FrameworkContent } from './FrameworkContent'
9
+ export { handleTabsComponent } from './MarkdownTabsHandler'
10
+ export { handleFrameworkComponent } from './MarkdownFrameworkHandler'
11
+ export {
12
+ MarkdownHeadingProvider,
13
+ useMarkdownHeadings,
14
+ type MarkdownHeading,
15
+ } from './MarkdownHeadingContext'