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,116 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
|
3
|
+
import { twMerge } from 'tailwind-merge'
|
|
4
|
+
|
|
5
|
+
type DropdownProps = {
|
|
6
|
+
children: React.ReactNode
|
|
7
|
+
open?: boolean
|
|
8
|
+
onOpenChange?: (open: boolean) => void
|
|
9
|
+
modal?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type DropdownTriggerProps = {
|
|
13
|
+
children: React.ReactNode
|
|
14
|
+
className?: string
|
|
15
|
+
asChild?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type DropdownContentProps = {
|
|
19
|
+
children: React.ReactNode
|
|
20
|
+
className?: string
|
|
21
|
+
align?: 'start' | 'center' | 'end'
|
|
22
|
+
sideOffset?: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type DropdownItemProps = {
|
|
26
|
+
children: React.ReactNode
|
|
27
|
+
className?: string
|
|
28
|
+
onSelect?: () => void
|
|
29
|
+
asChild?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type DropdownSeparatorProps = {
|
|
33
|
+
className?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function Dropdown({
|
|
37
|
+
children,
|
|
38
|
+
open,
|
|
39
|
+
onOpenChange,
|
|
40
|
+
modal = false,
|
|
41
|
+
}: DropdownProps) {
|
|
42
|
+
return (
|
|
43
|
+
<DropdownMenu.Root open={open} onOpenChange={onOpenChange} modal={modal}>
|
|
44
|
+
{children}
|
|
45
|
+
</DropdownMenu.Root>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function DropdownTrigger({
|
|
50
|
+
children,
|
|
51
|
+
className,
|
|
52
|
+
asChild = true,
|
|
53
|
+
}: DropdownTriggerProps) {
|
|
54
|
+
return (
|
|
55
|
+
<DropdownMenu.Trigger asChild={asChild} className={className}>
|
|
56
|
+
{children}
|
|
57
|
+
</DropdownMenu.Trigger>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function DropdownContent({
|
|
62
|
+
children,
|
|
63
|
+
className,
|
|
64
|
+
align = 'end',
|
|
65
|
+
sideOffset = 6,
|
|
66
|
+
}: DropdownContentProps) {
|
|
67
|
+
return (
|
|
68
|
+
<DropdownMenu.Portal>
|
|
69
|
+
<DropdownMenu.Content
|
|
70
|
+
align={align}
|
|
71
|
+
sideOffset={sideOffset}
|
|
72
|
+
className={twMerge(
|
|
73
|
+
'dropdown-content z-[1000] min-w-48 rounded-lg p-1.5',
|
|
74
|
+
'border border-gray-200 dark:border-gray-700',
|
|
75
|
+
'bg-white dark:bg-gray-800',
|
|
76
|
+
'shadow-lg',
|
|
77
|
+
className,
|
|
78
|
+
)}
|
|
79
|
+
>
|
|
80
|
+
{children}
|
|
81
|
+
</DropdownMenu.Content>
|
|
82
|
+
</DropdownMenu.Portal>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function DropdownItem({
|
|
87
|
+
children,
|
|
88
|
+
className,
|
|
89
|
+
onSelect,
|
|
90
|
+
asChild,
|
|
91
|
+
}: DropdownItemProps) {
|
|
92
|
+
return (
|
|
93
|
+
<DropdownMenu.Item
|
|
94
|
+
asChild={asChild}
|
|
95
|
+
onSelect={onSelect}
|
|
96
|
+
className={twMerge(
|
|
97
|
+
'flex cursor-pointer select-none items-center gap-2 rounded-md px-2 py-1.5 outline-none',
|
|
98
|
+
'text-sm text-gray-700 dark:text-gray-300',
|
|
99
|
+
'hover:bg-gray-100 dark:hover:bg-gray-700/50',
|
|
100
|
+
'focus:bg-gray-100 dark:focus:bg-gray-700/50',
|
|
101
|
+
'transition-colors duration-150',
|
|
102
|
+
className,
|
|
103
|
+
)}
|
|
104
|
+
>
|
|
105
|
+
{children}
|
|
106
|
+
</DropdownMenu.Item>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function DropdownSeparator({ className }: DropdownSeparatorProps) {
|
|
111
|
+
return (
|
|
112
|
+
<DropdownMenu.Separator
|
|
113
|
+
className={twMerge('my-1 h-px bg-gray-200 dark:bg-gray-700', className)}
|
|
114
|
+
/>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
type FallbackBannerProps = {
|
|
2
|
+
/** The display name of the requested locale (e.g., "Japanese", "Spanish") */
|
|
3
|
+
locale: string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Warning banner displayed when a page has not been translated to the requested locale.
|
|
8
|
+
* Uses the same styling as GitHub-style markdown alerts for visual consistency.
|
|
9
|
+
*/
|
|
10
|
+
export function FallbackBanner({ locale }: FallbackBannerProps) {
|
|
11
|
+
return (
|
|
12
|
+
<div className="markdown-alert markdown-alert-warning mb-4">
|
|
13
|
+
<div className="markdown-alert-title">
|
|
14
|
+
<svg
|
|
15
|
+
className="octicon octicon-info mr-2"
|
|
16
|
+
viewBox="0 0 16 16"
|
|
17
|
+
width="16"
|
|
18
|
+
height="16"
|
|
19
|
+
aria-hidden="true"
|
|
20
|
+
>
|
|
21
|
+
<path
|
|
22
|
+
fillRule="evenodd"
|
|
23
|
+
d="M8.22 1.754a.25.25 0 00-.44 0L1.698 13.132a.25.25 0 00.22.368h12.164a.25.25 0 00.22-.368L8.22 1.754zm-1.763-.707c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0114.082 15H1.918a1.75 1.75 0 01-1.543-2.575L6.457 1.047zM9 11a1 1 0 11-2 0 1 1 0 012 0zm-.25-5.25a.75.75 0 00-1.5 0v2.5a.75.75 0 001.5 0v-2.5z"
|
|
24
|
+
></path>
|
|
25
|
+
</svg>
|
|
26
|
+
Translation Unavailable
|
|
27
|
+
</div>
|
|
28
|
+
<div className="markdown-alert-content">
|
|
29
|
+
<p>
|
|
30
|
+
This page has not been translated to {locale} yet. You are viewing the
|
|
31
|
+
English version.
|
|
32
|
+
</p>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Link } from '@tanstack/react-router'
|
|
2
|
+
import { Card } from './Card'
|
|
3
|
+
|
|
4
|
+
export function Footer() {
|
|
5
|
+
return (
|
|
6
|
+
<Card
|
|
7
|
+
className={`relative flex flex-col items-start justify-center gap-4 p-8
|
|
8
|
+
max-w-(--breakpoint-lg) mx-auto text-sm`}
|
|
9
|
+
>
|
|
10
|
+
<div className={`grid gap-1 sm:grid-cols-2 md:grid-cols-3`}>
|
|
11
|
+
<div>
|
|
12
|
+
<Link to="/">Home</Link>
|
|
13
|
+
</div>
|
|
14
|
+
<div>
|
|
15
|
+
<a
|
|
16
|
+
href="https://github.com/tanstack"
|
|
17
|
+
target="_blank"
|
|
18
|
+
rel="noreferrer"
|
|
19
|
+
>
|
|
20
|
+
GitHub
|
|
21
|
+
</a>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<div className={`text-center opacity-60`}>
|
|
25
|
+
© {new Date().getFullYear()} TanStack
|
|
26
|
+
</div>
|
|
27
|
+
</Card>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { create } from 'zustand'
|
|
3
|
+
import { useNavigate, useParams } from '@tanstack/react-router'
|
|
4
|
+
import { Select } from './Select'
|
|
5
|
+
import {
|
|
6
|
+
type Framework,
|
|
7
|
+
type FrameworkOption,
|
|
8
|
+
getFrameworkOptions,
|
|
9
|
+
} from '~/config/frameworks'
|
|
10
|
+
|
|
11
|
+
export function FrameworkSelect({
|
|
12
|
+
frameworks,
|
|
13
|
+
}: {
|
|
14
|
+
frameworks: Framework[]
|
|
15
|
+
}) {
|
|
16
|
+
const frameworkConfig = useFrameworkConfig({
|
|
17
|
+
frameworks,
|
|
18
|
+
})
|
|
19
|
+
const selectedFramework = frameworkConfig.available.find(
|
|
20
|
+
(f) => f.value === frameworkConfig.selected,
|
|
21
|
+
)
|
|
22
|
+
return (
|
|
23
|
+
<Select
|
|
24
|
+
className="w-full"
|
|
25
|
+
icon={
|
|
26
|
+
selectedFramework?.logo ? (
|
|
27
|
+
<img
|
|
28
|
+
src={selectedFramework.logo}
|
|
29
|
+
alt={selectedFramework.label}
|
|
30
|
+
className="w-4 h-4"
|
|
31
|
+
/>
|
|
32
|
+
) : undefined
|
|
33
|
+
}
|
|
34
|
+
selected={frameworkConfig.selected}
|
|
35
|
+
available={frameworkConfig.available}
|
|
36
|
+
onSelect={frameworkConfig.onSelect}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Let's use zustand to wrap the local storage logic. This way
|
|
42
|
+
// we'll get subscriptions for free and we can use it in other
|
|
43
|
+
// components if we need to.
|
|
44
|
+
export const useLocalCurrentFramework = create<{
|
|
45
|
+
currentFramework?: string
|
|
46
|
+
setCurrentFramework: (framework: string) => void
|
|
47
|
+
}>((set) => ({
|
|
48
|
+
currentFramework:
|
|
49
|
+
typeof document !== 'undefined'
|
|
50
|
+
? localStorage.getItem('framework') || undefined
|
|
51
|
+
: undefined,
|
|
52
|
+
setCurrentFramework: (framework: string) => {
|
|
53
|
+
localStorage.setItem('framework', framework)
|
|
54
|
+
set({ currentFramework: framework })
|
|
55
|
+
},
|
|
56
|
+
}))
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get the stored framework preference from localStorage.
|
|
60
|
+
* Safe to call during SSR (returns undefined).
|
|
61
|
+
*/
|
|
62
|
+
export function getStoredFrameworkPreference(): string | undefined {
|
|
63
|
+
if (typeof window === 'undefined') return undefined
|
|
64
|
+
return localStorage.getItem('framework') || undefined
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Hook to persist framework preference (localStorage only, no auth).
|
|
69
|
+
*/
|
|
70
|
+
export function usePersistFrameworkPreference() {
|
|
71
|
+
const localCurrentFramework = useLocalCurrentFramework()
|
|
72
|
+
|
|
73
|
+
return React.useCallback(
|
|
74
|
+
(framework: string) => {
|
|
75
|
+
localCurrentFramework.setCurrentFramework(framework)
|
|
76
|
+
},
|
|
77
|
+
[localCurrentFramework],
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function useFrameworkConfig({ frameworks }: { frameworks: Framework[] }) {
|
|
82
|
+
const currentFramework = useCurrentFramework(frameworks)
|
|
83
|
+
|
|
84
|
+
const frameworkConfig = React.useMemo(() => {
|
|
85
|
+
return {
|
|
86
|
+
label: 'Framework',
|
|
87
|
+
selected: frameworks.includes(currentFramework.framework as Framework)
|
|
88
|
+
? currentFramework.framework
|
|
89
|
+
: 'react',
|
|
90
|
+
available: getFrameworkOptions(frameworks),
|
|
91
|
+
onSelect: (option: { label: string; value: string }) => {
|
|
92
|
+
currentFramework.setFramework(option.value)
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
}, [frameworks, currentFramework])
|
|
96
|
+
|
|
97
|
+
return frameworkConfig
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Use framework in URL path
|
|
102
|
+
* Otherwise use framework in localStorage if it exists
|
|
103
|
+
* Otherwise fallback to react
|
|
104
|
+
*/
|
|
105
|
+
export function useCurrentFramework(frameworks: Framework[]) {
|
|
106
|
+
const navigate = useNavigate()
|
|
107
|
+
|
|
108
|
+
const { framework: paramsFramework } = useParams({
|
|
109
|
+
strict: false,
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const localCurrentFramework = useLocalCurrentFramework()
|
|
113
|
+
|
|
114
|
+
// Priority: URL params > localStorage > 'react'
|
|
115
|
+
let framework = (paramsFramework ||
|
|
116
|
+
localCurrentFramework.currentFramework ||
|
|
117
|
+
'react') as Framework
|
|
118
|
+
|
|
119
|
+
framework = frameworks.includes(framework) ? framework : 'react'
|
|
120
|
+
|
|
121
|
+
const setFramework = React.useCallback(
|
|
122
|
+
(framework: string) => {
|
|
123
|
+
navigate({
|
|
124
|
+
params: { framework } as any,
|
|
125
|
+
})
|
|
126
|
+
localCurrentFramework.setCurrentFramework(framework)
|
|
127
|
+
},
|
|
128
|
+
[localCurrentFramework, navigate],
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
React.useEffect(() => {
|
|
132
|
+
// Set the framework in localStorage if it doesn't exist
|
|
133
|
+
if (!localCurrentFramework.currentFramework) {
|
|
134
|
+
localCurrentFramework.setCurrentFramework(framework)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Set the framework in localStorage if it doesn't match the URL
|
|
138
|
+
if (
|
|
139
|
+
paramsFramework &&
|
|
140
|
+
paramsFramework !== localCurrentFramework.currentFramework
|
|
141
|
+
) {
|
|
142
|
+
localCurrentFramework.setCurrentFramework(paramsFramework)
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
framework,
|
|
148
|
+
setFramework,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { Link } from '@tanstack/react-router'
|
|
2
|
+
import { twMerge } from 'tailwind-merge'
|
|
3
|
+
import type { ProjectConfig } from '~/types'
|
|
4
|
+
import { siteConfig, isSingleProject } from '~/site.config'
|
|
5
|
+
|
|
6
|
+
export default function LibraryCard({
|
|
7
|
+
project,
|
|
8
|
+
index = 0,
|
|
9
|
+
}: {
|
|
10
|
+
project: ProjectConfig
|
|
11
|
+
index?: number
|
|
12
|
+
}) {
|
|
13
|
+
// Use simplified URL for single-project sites
|
|
14
|
+
const singleProject = isSingleProject()
|
|
15
|
+
const to = singleProject
|
|
16
|
+
? '/$lang/docs/$'
|
|
17
|
+
: siteConfig.hideLatestVersion
|
|
18
|
+
? '/$lang/$project/docs/$'
|
|
19
|
+
: '/$lang/$project/$version/docs/$'
|
|
20
|
+
|
|
21
|
+
const params = singleProject
|
|
22
|
+
? {
|
|
23
|
+
lang: siteConfig.defaultLocale,
|
|
24
|
+
_splat: project.defaultDocs || 'overview',
|
|
25
|
+
}
|
|
26
|
+
: siteConfig.hideLatestVersion
|
|
27
|
+
? {
|
|
28
|
+
lang: siteConfig.defaultLocale,
|
|
29
|
+
project: project.id,
|
|
30
|
+
_splat: project.defaultDocs || 'overview',
|
|
31
|
+
}
|
|
32
|
+
: {
|
|
33
|
+
lang: siteConfig.defaultLocale,
|
|
34
|
+
project: project.id,
|
|
35
|
+
version: project.latestVersion,
|
|
36
|
+
_splat: project.defaultDocs || 'overview',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Link
|
|
41
|
+
to={to}
|
|
42
|
+
params={params as any}
|
|
43
|
+
className={twMerge(
|
|
44
|
+
// General
|
|
45
|
+
'p-8 relative group z-0 min-h-[250px] xl:min-h-[220px] shadow-sm hover:shadow-none bg-white dark:bg-gray-900',
|
|
46
|
+
|
|
47
|
+
// Transition
|
|
48
|
+
'transition-all duration-300 ease-out',
|
|
49
|
+
|
|
50
|
+
// Border
|
|
51
|
+
'rounded-xl border border-gray-200 dark:border-gray-800 hover:border-current/50',
|
|
52
|
+
|
|
53
|
+
// Shadow / Glow (behind everything)
|
|
54
|
+
'before:bg-current',
|
|
55
|
+
'before:content-[""] before:absolute before:inset-0 before:blur-xl before:opacity-0 hover:before:opacity-20 before:transition-all before:duration-300 before:ease-out',
|
|
56
|
+
|
|
57
|
+
// Card Background (behind content, front of shadow)
|
|
58
|
+
'after:absolute after:inset-0 after:-z-10 after:bg-white dark:after:bg-gray-900 after:backdrop-blur-sm after:rounded-xl',
|
|
59
|
+
|
|
60
|
+
// Transform
|
|
61
|
+
'hover:-translate-y-1',
|
|
62
|
+
project.textColor,
|
|
63
|
+
)}
|
|
64
|
+
style={{
|
|
65
|
+
zIndex: index,
|
|
66
|
+
willChange: 'transform',
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
{/* Background content that will blur on hover */}
|
|
70
|
+
<div className="z-0 relative group-hover:blur-[0.5px] transition-[filter] duration-300 ease-out">
|
|
71
|
+
<div className="flex gap-2 justify-between items-center">
|
|
72
|
+
<div
|
|
73
|
+
className={twMerge(
|
|
74
|
+
`flex items-center gap-2 text-[1.2rem] font-extrabold uppercase [letter-spacing:-.04em]`,
|
|
75
|
+
)}
|
|
76
|
+
>
|
|
77
|
+
<span
|
|
78
|
+
className={twMerge(
|
|
79
|
+
'rounded-lg leading-none flex items-center',
|
|
80
|
+
'bg-current',
|
|
81
|
+
)}
|
|
82
|
+
>
|
|
83
|
+
<span className="text-white dark:text-black text-xs leading-none p-1.5 px-2 uppercase">
|
|
84
|
+
{siteConfig.name}
|
|
85
|
+
</span>
|
|
86
|
+
</span>
|
|
87
|
+
<span className="text-current">{project.name}</span>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
{project.tagline && (
|
|
91
|
+
<div
|
|
92
|
+
className={twMerge(
|
|
93
|
+
`text-sm italic font-medium mt-3`,
|
|
94
|
+
'text-current',
|
|
95
|
+
)}
|
|
96
|
+
>
|
|
97
|
+
{project.tagline}
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
|
|
101
|
+
{/* Description preview with ellipsis */}
|
|
102
|
+
{project.description && (
|
|
103
|
+
<div
|
|
104
|
+
className={`text-sm mt-3 text-gray-600 dark:text-gray-400 line-clamp-3 leading-relaxed`}
|
|
105
|
+
>
|
|
106
|
+
{project.description}
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{/* Foreground content that appears on hover */}
|
|
112
|
+
<div
|
|
113
|
+
className="absolute inset-0 z-30 bg-white/95 dark:bg-black/95 p-6 rounded-xl
|
|
114
|
+
backdrop-blur-sm flex flex-col justify-center opacity-0 group-hover:opacity-100
|
|
115
|
+
transition-opacity duration-300 ease-out pointer-events-none group-hover:pointer-events-auto"
|
|
116
|
+
>
|
|
117
|
+
<div
|
|
118
|
+
className={`text-sm text-gray-800 dark:text-gray-200 leading-relaxed`}
|
|
119
|
+
>
|
|
120
|
+
{project.description}
|
|
121
|
+
</div>
|
|
122
|
+
{project.frameworks && project.frameworks.length > 0 && (
|
|
123
|
+
<div className="flex flex-wrap gap-2 mt-4">
|
|
124
|
+
{project.frameworks.map((fw) => (
|
|
125
|
+
<span
|
|
126
|
+
key={fw}
|
|
127
|
+
className="text-xs px-2 py-1 bg-gray-100 dark:bg-gray-800 rounded-full text-gray-600 dark:text-gray-400"
|
|
128
|
+
>
|
|
129
|
+
{fw.charAt(0).toUpperCase() + fw.slice(1)}
|
|
130
|
+
</span>
|
|
131
|
+
))}
|
|
132
|
+
</div>
|
|
133
|
+
)}
|
|
134
|
+
<div className="mt-6 text-center">
|
|
135
|
+
<span
|
|
136
|
+
className="inline-flex items-center gap-2 px-4 py-2 bg-black/5 dark:bg-white/10
|
|
137
|
+
rounded-full text-sm font-medium text-gray-900 dark:text-white"
|
|
138
|
+
>
|
|
139
|
+
Click to learn more
|
|
140
|
+
<svg
|
|
141
|
+
className="w-4 h-4 transform transition-transform duration-200 group-hover:translate-x-0.5"
|
|
142
|
+
fill="none"
|
|
143
|
+
viewBox="0 0 24 24"
|
|
144
|
+
stroke="currentColor"
|
|
145
|
+
>
|
|
146
|
+
<path
|
|
147
|
+
strokeLinecap="round"
|
|
148
|
+
strokeLinejoin="round"
|
|
149
|
+
strokeWidth={2}
|
|
150
|
+
d="M9 5l7 7-7 7"
|
|
151
|
+
/>
|
|
152
|
+
</svg>
|
|
153
|
+
</span>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
{/* Badge */}
|
|
157
|
+
{project.badge ? (
|
|
158
|
+
<div
|
|
159
|
+
className={twMerge(
|
|
160
|
+
`absolute -top-2 -right-2 z-40 px-2 py-1 rounded-md`,
|
|
161
|
+
'bg-gradient-to-r',
|
|
162
|
+
project.colorFrom,
|
|
163
|
+
project.colorTo,
|
|
164
|
+
'uppercase font-black italic text-xs',
|
|
165
|
+
'text-white',
|
|
166
|
+
)}
|
|
167
|
+
style={{
|
|
168
|
+
animation: 'pulseScale 3s infinite',
|
|
169
|
+
animationTimingFunction: 'ease-in-out',
|
|
170
|
+
animationDelay: `${index * 0.5}s`,
|
|
171
|
+
}}
|
|
172
|
+
>
|
|
173
|
+
<span>{project.badge}</span>
|
|
174
|
+
</div>
|
|
175
|
+
) : null}
|
|
176
|
+
</Link>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { useNavigate, useParams } from '@tanstack/react-router'
|
|
3
|
+
import { Globe } from 'lucide-react'
|
|
4
|
+
import { siteConfig } from '~/site.config'
|
|
5
|
+
import { Select, type SelectOption } from './Select'
|
|
6
|
+
|
|
7
|
+
export function LocaleSwitcher() {
|
|
8
|
+
const params = useParams({ strict: false }) as {
|
|
9
|
+
lang?: string
|
|
10
|
+
_splat?: string
|
|
11
|
+
}
|
|
12
|
+
const navigate = useNavigate()
|
|
13
|
+
const currentLang = params.lang || siteConfig.defaultLocale
|
|
14
|
+
|
|
15
|
+
const localeOptions: SelectOption[] = React.useMemo(
|
|
16
|
+
() =>
|
|
17
|
+
Object.entries(siteConfig.supportedLocales).map(([code, name]) => ({
|
|
18
|
+
label: name,
|
|
19
|
+
value: code,
|
|
20
|
+
})),
|
|
21
|
+
[],
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
const handleSelect = React.useCallback(
|
|
25
|
+
(option: SelectOption) => {
|
|
26
|
+
const newLang = option.value
|
|
27
|
+
const pathname = window.location.pathname
|
|
28
|
+
const newPath = pathname.replace(/^\/[^/]+/, `/${newLang}`)
|
|
29
|
+
navigate({ to: newPath })
|
|
30
|
+
},
|
|
31
|
+
[navigate],
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Select
|
|
36
|
+
className="w-full"
|
|
37
|
+
icon={<Globe className="w-3.5 h-3.5 opacity-60" />}
|
|
38
|
+
selected={currentLang}
|
|
39
|
+
available={localeOptions}
|
|
40
|
+
onSelect={handleSelect}
|
|
41
|
+
/>
|
|
42
|
+
)
|
|
43
|
+
}
|