boltdocs 2.1.1 → 2.2.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.
- package/CHANGELOG.md +9 -0
- package/dist/base-ui/index.d.mts +25 -0
- package/dist/base-ui/index.d.ts +25 -0
- package/dist/base-ui/index.js +1 -0
- package/dist/base-ui/index.mjs +1 -0
- package/dist/{cache-Q4T6VAUL.mjs → cache-CRAZ55X7.mjs} +1 -1
- package/dist/chunk-5D6XPYQ3.mjs +74 -0
- package/dist/chunk-6QXCKZAT.mjs +1 -0
- package/dist/chunk-H4M6P3DM.mjs +1 -0
- package/dist/chunk-JD3RSDE4.mjs +1 -0
- package/dist/chunk-JXHNX2WN.mjs +1 -0
- package/dist/chunk-JZXLCA2E.mjs +1 -0
- package/dist/chunk-MZBG4N4W.mjs +1 -0
- package/dist/chunk-NBCYHLAA.mjs +1 -0
- package/dist/chunk-Q3MLYTIQ.mjs +1 -0
- package/dist/chunk-RSII2UPE.mjs +1 -0
- package/dist/chunk-T3W44KWY.mjs +1 -0
- package/dist/chunk-ZK2266IZ.mjs +1 -0
- package/dist/chunk-ZRJ55GGF.mjs +1 -0
- package/dist/client/index.d.mts +13 -115
- package/dist/client/index.d.ts +13 -115
- package/dist/client/index.js +1 -1
- package/dist/client/index.mjs +1 -1
- package/dist/client/ssr.js +1 -1
- package/dist/client/ssr.mjs +1 -1
- package/dist/client/types.d.mts +3 -0
- package/dist/client/types.d.ts +3 -0
- package/dist/client/types.js +1 -0
- package/dist/client/types.mjs +0 -0
- package/dist/copy-markdown-C-90ixSe.d.ts +15 -0
- package/dist/copy-markdown-CbS8X-qe.d.mts +15 -0
- package/dist/{client/hooks → hooks}/index.d.mts +25 -12
- package/dist/{client/hooks → hooks}/index.d.ts +25 -12
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/index.mjs +1 -0
- package/dist/integrations/index.d.mts +48 -0
- package/dist/integrations/index.d.ts +48 -0
- package/dist/integrations/index.js +1 -0
- package/dist/integrations/index.mjs +1 -0
- package/dist/link-DfBwCeZc.d.mts +68 -0
- package/dist/link-DfBwCeZc.d.ts +68 -0
- package/dist/loading-BqGrFWO5.d.mts +68 -0
- package/dist/loading-chS3pm9W.d.ts +68 -0
- package/dist/{client/components/mdx → mdx}/index.d.mts +6 -38
- package/dist/{client/components/mdx → mdx}/index.d.ts +6 -38
- package/dist/mdx/index.js +1 -0
- package/dist/mdx/index.mjs +1 -0
- package/dist/node/cli-entry.js +27 -27
- package/dist/node/cli-entry.mjs +1 -1
- package/dist/node/index.d.mts +44 -14
- package/dist/node/index.d.ts +44 -14
- package/dist/node/index.js +23 -23
- package/dist/node/index.mjs +1 -1
- package/dist/primitives/index.d.mts +301 -0
- package/dist/primitives/index.d.ts +301 -0
- package/dist/primitives/index.js +1 -0
- package/dist/primitives/index.mjs +1 -0
- package/dist/search-dialog-MA5AISC7.mjs +1 -0
- package/dist/{types-Cp21DHI6.d.mts → types-j7jvWsJj.d.mts} +63 -17
- package/dist/{types-Cp21DHI6.d.ts → types-j7jvWsJj.d.ts} +63 -17
- package/dist/{use-routes-xLhumjbV.d.ts → use-routes-Cd806kGw.d.ts} +1 -1
- package/dist/{use-routes-8Iei6jTp.d.mts → use-routes-DDL0_jkQ.d.mts} +1 -1
- package/package.json +34 -8
- package/src/client/app/index.tsx +155 -35
- package/src/client/app/mdx-component.tsx +7 -3
- package/src/client/app/theme-context.tsx +40 -23
- package/src/client/components/default-layout.tsx +12 -6
- package/src/client/components/primitives/breadcrumbs.tsx +1 -1
- package/src/client/components/primitives/navbar.tsx +5 -2
- package/src/client/components/primitives/search-dialog.tsx +12 -3
- package/src/client/components/ui-base/breadcrumbs.tsx +1 -1
- package/src/client/components/ui-base/index.ts +17 -0
- package/src/client/components/ui-base/navbar.tsx +66 -33
- package/src/client/components/ui-base/powered-by.tsx +8 -5
- package/src/client/components/ui-base/sidebar.tsx +31 -22
- package/src/client/components/ui-base/tabs.tsx +4 -1
- package/src/client/components/ui-base/theme-toggle.tsx +35 -15
- package/src/client/hooks/use-i18n.ts +37 -7
- package/src/client/hooks/use-localized-to.ts +45 -68
- package/src/client/hooks/use-navbar.ts +10 -3
- package/src/client/hooks/use-routes.ts +61 -17
- package/src/client/hooks/use-search.ts +21 -5
- package/src/client/hooks/use-sidebar.ts +5 -2
- package/src/client/hooks/use-version.ts +5 -0
- package/src/client/integrations/index.ts +1 -0
- package/src/client/store/use-boltdocs-store.ts +43 -0
- package/src/client/types.ts +4 -2
- package/src/client/utils/i18n.ts +23 -0
- package/src/node/config.ts +54 -17
- package/src/node/index.ts +1 -1
- package/src/node/mdx/cache.ts +12 -0
- package/src/node/mdx/highlighter.ts +47 -0
- package/src/node/mdx/index.ts +114 -0
- package/src/node/mdx/rehype-shiki.ts +53 -0
- package/src/node/mdx/remark-shiki.ts +61 -0
- package/src/node/plugin/html.ts +8 -4
- package/src/node/plugin/index.ts +117 -68
- package/src/node/routes/index.ts +34 -13
- package/src/node/routes/parser.ts +12 -4
- package/src/node/ssg/index.ts +3 -3
- package/src/node/utils.ts +32 -2
- package/tsup.config.ts +7 -2
- package/dist/chunk-52MVMZWS.mjs +0 -1
- package/dist/chunk-BVWWKXJH.mjs +0 -1
- package/dist/chunk-DVY3RDXD.mjs +0 -1
- package/dist/chunk-FUVYCYWC.mjs +0 -1
- package/dist/chunk-GBLMDJ2B.mjs +0 -1
- package/dist/chunk-ISPX45DF.mjs +0 -1
- package/dist/chunk-PNXZMUCO.mjs +0 -1
- package/dist/chunk-V2ZHKQSP.mjs +0 -74
- package/dist/client/components/mdx/index.js +0 -1
- package/dist/client/components/mdx/index.mjs +0 -1
- package/dist/client/hooks/index.js +0 -1
- package/dist/client/hooks/index.mjs +0 -1
- package/dist/search-dialog-TWGYKF2D.mjs +0 -1
- package/src/node/mdx.ts +0 -279
|
@@ -13,6 +13,7 @@ import { useConfig } from '@client/app/config-context'
|
|
|
13
13
|
import { useMdxComponents } from '@client/app/mdx-components-context'
|
|
14
14
|
|
|
15
15
|
import { useLocation } from 'react-router-dom'
|
|
16
|
+
import { getTranslated } from '@client/utils/i18n'
|
|
16
17
|
|
|
17
18
|
export interface LayoutProps {
|
|
18
19
|
children: React.ReactNode
|
|
@@ -24,7 +25,12 @@ export interface LayoutProps {
|
|
|
24
25
|
* and rearrange, wrap, or replace any section.
|
|
25
26
|
*/
|
|
26
27
|
export function DefaultLayout({ children }: LayoutProps) {
|
|
27
|
-
const {
|
|
28
|
+
const {
|
|
29
|
+
routes: filteredRoutes,
|
|
30
|
+
allRoutes,
|
|
31
|
+
currentRoute,
|
|
32
|
+
currentLocale,
|
|
33
|
+
} = useRoutes()
|
|
28
34
|
const { pathname } = useLocation()
|
|
29
35
|
const config = useConfig()
|
|
30
36
|
const mdxComponents = useMdxComponents()
|
|
@@ -36,8 +42,8 @@ export function DefaultLayout({ children }: LayoutProps) {
|
|
|
36
42
|
<DocsLayout>
|
|
37
43
|
<ProgressBar />
|
|
38
44
|
<Head
|
|
39
|
-
siteTitle={config.
|
|
40
|
-
siteDescription={config.
|
|
45
|
+
siteTitle={getTranslated(config.theme?.title, currentLocale) || 'Boltdocs'}
|
|
46
|
+
siteDescription={getTranslated(config.theme?.description, currentLocale) || ''}
|
|
41
47
|
routes={allRoutes}
|
|
42
48
|
/>
|
|
43
49
|
<Navbar />
|
|
@@ -52,7 +58,7 @@ export function DefaultLayout({ children }: LayoutProps) {
|
|
|
52
58
|
<CopyMarkdownComp
|
|
53
59
|
mdxRaw={currentRoute?._rawContent}
|
|
54
60
|
route={currentRoute}
|
|
55
|
-
config={config.
|
|
61
|
+
config={config.theme?.copyMarkdown}
|
|
56
62
|
/>
|
|
57
63
|
</DocsLayout.ContentHeader>
|
|
58
64
|
)}
|
|
@@ -69,8 +75,8 @@ export function DefaultLayout({ children }: LayoutProps) {
|
|
|
69
75
|
{!isHome && (
|
|
70
76
|
<OnThisPage
|
|
71
77
|
headings={currentRoute?.headings}
|
|
72
|
-
editLink={config.
|
|
73
|
-
communityHelp={config.
|
|
78
|
+
editLink={config.theme?.editLink}
|
|
79
|
+
communityHelp={config.theme?.communityHelp}
|
|
74
80
|
filePath={currentRoute?.filePath}
|
|
75
81
|
/>
|
|
76
82
|
)}
|
|
@@ -180,8 +180,11 @@ export const NavbarLink = ({
|
|
|
180
180
|
href={href}
|
|
181
181
|
target={to === 'external' ? '_blank' : undefined}
|
|
182
182
|
className={cn(
|
|
183
|
-
'transition-colors outline-none
|
|
184
|
-
|
|
183
|
+
'transition-colors outline-none focus-visible:ring-2 focus-visible:ring-primary-500/30 rounded-sm',
|
|
184
|
+
{
|
|
185
|
+
'text-primary-500 font-bold': active,
|
|
186
|
+
'text-text-muted hover:text-text-main font-medium': !active,
|
|
187
|
+
},
|
|
185
188
|
className,
|
|
186
189
|
)}
|
|
187
190
|
>
|
|
@@ -53,13 +53,22 @@ export const SearchDialogRoot = ({
|
|
|
53
53
|
export const SearchDialogAutocomplete = ({
|
|
54
54
|
children,
|
|
55
55
|
className,
|
|
56
|
+
onSelectionChange,
|
|
56
57
|
...props
|
|
57
|
-
}: RAC.AutocompleteProps<object> & {
|
|
58
|
+
}: RAC.AutocompleteProps<object> & {
|
|
59
|
+
className?: string
|
|
60
|
+
onSelectionChange?: (key: RAC.Key) => void
|
|
61
|
+
}) => {
|
|
62
|
+
const Autocomplete = RAC.Autocomplete as any
|
|
58
63
|
return (
|
|
59
64
|
<div className={className}>
|
|
60
|
-
<
|
|
65
|
+
<Autocomplete
|
|
66
|
+
{...props}
|
|
67
|
+
onSelectionChange={onSelectionChange}
|
|
68
|
+
className="flex flex-col min-h-0"
|
|
69
|
+
>
|
|
61
70
|
{children}
|
|
62
|
-
</
|
|
71
|
+
</Autocomplete>
|
|
63
72
|
</div>
|
|
64
73
|
)
|
|
65
74
|
}
|
|
@@ -12,7 +12,7 @@ import { useConfig } from '@client/app/config-context'
|
|
|
12
12
|
export function Breadcrumbs() {
|
|
13
13
|
const { crumbs, activeRoute } = useBreadcrumbs()
|
|
14
14
|
const config = useConfig()
|
|
15
|
-
const themeConfig = config.theme ||
|
|
15
|
+
const themeConfig = config.theme || {}
|
|
16
16
|
|
|
17
17
|
if (crumbs.length === 0) return null
|
|
18
18
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { Breadcrumbs } from './breadcrumbs'
|
|
2
|
+
export { CopyMarkdown } from './copy-markdown'
|
|
3
|
+
export type { CopyMarkdownProps } from './copy-markdown'
|
|
4
|
+
export { ErrorBoundary } from './error-boundary'
|
|
5
|
+
export { GithubStars } from './github-stars'
|
|
6
|
+
export { Head } from './head'
|
|
7
|
+
export { Loading } from './loading'
|
|
8
|
+
export { Navbar } from './navbar'
|
|
9
|
+
export { NotFound } from './not-found'
|
|
10
|
+
export { OnThisPage } from './on-this-page'
|
|
11
|
+
export { PageNav } from './page-nav'
|
|
12
|
+
export { PoweredBy } from './powered-by'
|
|
13
|
+
export { ProgressBar } from './progress-bar'
|
|
14
|
+
export { SearchDialog } from './search-dialog'
|
|
15
|
+
export { Sidebar } from './sidebar'
|
|
16
|
+
export { Tabs } from './tabs'
|
|
17
|
+
export { ThemeToggle } from './theme-toggle'
|
|
@@ -11,8 +11,9 @@ import { useLocation } from 'react-router-dom'
|
|
|
11
11
|
import type { BoltdocsSocialLink } from '@node/config'
|
|
12
12
|
import Menu from '@components/primitives/menu'
|
|
13
13
|
import { Button } from '@components/primitives/button'
|
|
14
|
-
import { ChevronDown } from 'lucide-react'
|
|
15
|
-
import {
|
|
14
|
+
import { ChevronDown, Languages } from 'lucide-react'
|
|
15
|
+
import { useLocalizedTo } from '@hooks/use-localized-to'
|
|
16
|
+
import type { NavbarLink as NavbarLinkType } from '@client/types'
|
|
16
17
|
|
|
17
18
|
const SearchDialog = lazy(() =>
|
|
18
19
|
import('./search-dialog').then((m) => ({
|
|
@@ -24,7 +25,7 @@ export function Navbar() {
|
|
|
24
25
|
const { links, title, logo, logoProps, github, social, config } = useNavbar()
|
|
25
26
|
const { routes, allRoutes, currentVersion, currentLocale } = useRoutes()
|
|
26
27
|
const { pathname } = useLocation()
|
|
27
|
-
const themeConfig = config.theme ||
|
|
28
|
+
const themeConfig = config.theme || {}
|
|
28
29
|
|
|
29
30
|
const hasTabs = themeConfig?.tabs && themeConfig.tabs.length > 0
|
|
30
31
|
|
|
@@ -44,7 +45,7 @@ export function Navbar() {
|
|
|
44
45
|
|
|
45
46
|
<NavbarPrimitive.Links>
|
|
46
47
|
{links.map((link) => (
|
|
47
|
-
<
|
|
48
|
+
<NavbarLinkItem key={link.href} link={link} />
|
|
48
49
|
))}
|
|
49
50
|
</NavbarPrimitive.Links>
|
|
50
51
|
</NavbarPrimitive.NavbarLeft>
|
|
@@ -78,16 +79,18 @@ export function Navbar() {
|
|
|
78
79
|
|
|
79
80
|
{pathname !== '/' && hasTabs && themeConfig?.tabs && (
|
|
80
81
|
<div className="w-full border-b border-border-subtle bg-bg-main">
|
|
81
|
-
<Tabs
|
|
82
|
-
tabs={themeConfig.tabs}
|
|
83
|
-
routes={allRoutes || routes || []}
|
|
84
|
-
/>
|
|
82
|
+
<Tabs tabs={themeConfig.tabs} routes={allRoutes || routes || []} />
|
|
85
83
|
</div>
|
|
86
84
|
)}
|
|
87
85
|
</NavbarPrimitive.NavbarRoot>
|
|
88
86
|
)
|
|
89
87
|
}
|
|
90
88
|
|
|
89
|
+
function NavbarLinkItem({ link }: { link: NavbarLinkType }) {
|
|
90
|
+
const localizedHref = useLocalizedTo(link.href)
|
|
91
|
+
return <NavbarPrimitive.Link {...(link as any)} href={localizedHref} />
|
|
92
|
+
}
|
|
93
|
+
|
|
91
94
|
function NavbarVersion() {
|
|
92
95
|
const { currentVersionLabel, availableVersions, handleVersionChange } =
|
|
93
96
|
useVersion()
|
|
@@ -96,43 +99,73 @@ function NavbarVersion() {
|
|
|
96
99
|
|
|
97
100
|
return (
|
|
98
101
|
<Menu.Trigger>
|
|
99
|
-
<Button
|
|
100
|
-
{
|
|
102
|
+
<Button
|
|
103
|
+
variant={'outline'}
|
|
104
|
+
size="sm"
|
|
105
|
+
rounded="lg"
|
|
106
|
+
iconPosition="right"
|
|
107
|
+
icon={<ChevronDown className="w-3.5 h-3.5 text-text-muted/60" />}
|
|
108
|
+
className="h-8 border-border-subtle/60 bg-bg-surface/30 backdrop-blur-sm transition-all duration-200 hover:border-primary-500/50 hover:bg-primary-500/5"
|
|
109
|
+
>
|
|
110
|
+
<span className="font-semibold text-[0.8125rem]">
|
|
111
|
+
{currentVersionLabel}
|
|
112
|
+
</span>
|
|
101
113
|
</Button>
|
|
102
|
-
<Menu.
|
|
103
|
-
{
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
<Menu.Root>
|
|
115
|
+
<Menu.Section items={availableVersions}>
|
|
116
|
+
{(version) => (
|
|
117
|
+
<Menu.Item
|
|
118
|
+
key={`${version.value ?? ''}`}
|
|
119
|
+
onPress={() => handleVersionChange(version.value)}
|
|
120
|
+
>
|
|
121
|
+
{version.label as string}
|
|
122
|
+
</Menu.Item>
|
|
123
|
+
)}
|
|
124
|
+
</Menu.Section>
|
|
125
|
+
</Menu.Root>
|
|
112
126
|
</Menu.Trigger>
|
|
113
127
|
)
|
|
114
128
|
}
|
|
115
129
|
|
|
116
130
|
function NavbarI18n() {
|
|
117
|
-
const {
|
|
131
|
+
const { currentLocale, availableLocales, handleLocaleChange } = useI18n()
|
|
118
132
|
|
|
119
133
|
if (availableLocales.length === 0) return null
|
|
120
134
|
|
|
121
135
|
return (
|
|
122
136
|
<Menu.Trigger>
|
|
123
|
-
<Button
|
|
124
|
-
{
|
|
137
|
+
<Button
|
|
138
|
+
variant={'outline'}
|
|
139
|
+
size="sm"
|
|
140
|
+
rounded="lg"
|
|
141
|
+
iconPosition="right"
|
|
142
|
+
icon={<ChevronDown className="w-3.5 h-3.5 text-text-muted/60" />}
|
|
143
|
+
className="h-8 border-border-subtle/60 bg-bg-surface/30 backdrop-blur-sm transition-all duration-200 hover:border-primary-500/50 hover:bg-primary-500/5 px-2.5"
|
|
144
|
+
>
|
|
145
|
+
<div className="flex items-center gap-1.5">
|
|
146
|
+
<Languages className="w-3.5 h-3.5 text-primary-500" />
|
|
147
|
+
<span className="font-bold text-[0.75rem] tracking-wider uppercase opacity-90">
|
|
148
|
+
{currentLocale || 'en'}
|
|
149
|
+
</span>
|
|
150
|
+
</div>
|
|
125
151
|
</Button>
|
|
126
|
-
<Menu.
|
|
127
|
-
{
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
152
|
+
<Menu.Root>
|
|
153
|
+
<Menu.Section items={availableLocales}>
|
|
154
|
+
{(locale) => (
|
|
155
|
+
<Menu.Item
|
|
156
|
+
key={`${locale.value ?? ''}`}
|
|
157
|
+
onPress={() => handleLocaleChange(locale.value)}
|
|
158
|
+
>
|
|
159
|
+
<div className="flex items-center justify-between w-full gap-4">
|
|
160
|
+
<span>{locale.label as string}</span>
|
|
161
|
+
<span className="text-[10px] font-bold opacity-40 uppercase tracking-tighter">
|
|
162
|
+
{locale.value}
|
|
163
|
+
</span>
|
|
164
|
+
</div>
|
|
165
|
+
</Menu.Item>
|
|
166
|
+
)}
|
|
167
|
+
</Menu.Section>
|
|
168
|
+
</Menu.Root>
|
|
136
169
|
</Menu.Trigger>
|
|
137
170
|
)
|
|
138
171
|
}
|
|
@@ -2,16 +2,19 @@ import { Zap } from 'lucide-react'
|
|
|
2
2
|
|
|
3
3
|
export function PoweredBy() {
|
|
4
4
|
return (
|
|
5
|
-
<div className="
|
|
5
|
+
<div className="flex items-center justify-center mt-10 mb-4 px-4 w-full">
|
|
6
6
|
<a
|
|
7
7
|
href="https://github.com/jesusalcaladev/boltdocs"
|
|
8
8
|
target="_blank"
|
|
9
9
|
rel="noopener noreferrer"
|
|
10
|
-
className="flex items-center gap-
|
|
10
|
+
className="group relative flex items-center gap-2 px-4 py-2 rounded-full border border-border-subtle bg-bg-surface/50 backdrop-blur-md transition-all duration-300 hover:border-primary-500/50 hover:bg-bg-surface hover:shadow-xl hover:shadow-primary-500/5 select-none"
|
|
11
11
|
>
|
|
12
|
-
<Zap
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
<Zap
|
|
13
|
+
className="w-3.5 h-3.5 text-text-muted group-hover:text-primary-500 transition-colors duration-300"
|
|
14
|
+
fill="currentColor"
|
|
15
|
+
/>
|
|
16
|
+
<span className="text-[11px] font-medium text-text-muted group-hover:text-text-main transition-colors duration-300 tracking-wide">
|
|
17
|
+
Powered by <strong className="font-bold text-text-main/80 group-hover:text-text-main">Boltdocs</strong>
|
|
15
18
|
</span>
|
|
16
19
|
</a>
|
|
17
20
|
</div>
|
|
@@ -8,7 +8,8 @@ import type { BoltdocsConfig } from '@node/config'
|
|
|
8
8
|
|
|
9
9
|
function getIcon(iconName?: string): React.ElementType | undefined {
|
|
10
10
|
if (!iconName) return undefined
|
|
11
|
-
const
|
|
11
|
+
const icons = LucideIcons as unknown as Record<string, React.ElementType>
|
|
12
|
+
const IconComponent = icons[iconName]
|
|
12
13
|
return IconComponent || undefined
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -45,16 +46,20 @@ function CollapsibleSidebarGroup({
|
|
|
45
46
|
isOpen={isOpen}
|
|
46
47
|
onToggle={() => setIsOpen(!isOpen)}
|
|
47
48
|
>
|
|
48
|
-
{group.routes.map((route: ComponentRoute) =>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
{group.routes.map((route: ComponentRoute) => {
|
|
50
|
+
const isCurrent =
|
|
51
|
+
activePath === (route.path.endsWith('/') ? route.path.slice(0, -1) : route.path)
|
|
52
|
+
return (
|
|
53
|
+
<SidebarPrimitive.SidebarLink
|
|
54
|
+
key={route.path}
|
|
55
|
+
label={route.title}
|
|
56
|
+
href={route.path}
|
|
57
|
+
active={isCurrent}
|
|
58
|
+
icon={getIcon(route.icon)}
|
|
59
|
+
badge={route.badge}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
})}
|
|
58
63
|
</SidebarPrimitive.SidebarGroup>
|
|
59
64
|
)
|
|
60
65
|
}
|
|
@@ -67,22 +72,26 @@ export function Sidebar({
|
|
|
67
72
|
config: BoltdocsConfig
|
|
68
73
|
}) {
|
|
69
74
|
const { groups, ungrouped, activePath } = useSidebar(routes)
|
|
70
|
-
const themeConfig = config.theme ||
|
|
75
|
+
const themeConfig = config.theme || {}
|
|
71
76
|
|
|
72
77
|
return (
|
|
73
78
|
<SidebarPrimitive.SidebarRoot>
|
|
74
79
|
{ungrouped.length > 0 && (
|
|
75
80
|
<SidebarPrimitive.SidebarGroup className="mb-6">
|
|
76
|
-
{ungrouped.map((route) =>
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
{ungrouped.map((route) => {
|
|
82
|
+
const isCurrent =
|
|
83
|
+
activePath === (route.path.endsWith('/') ? route.path.slice(0, -1) : route.path)
|
|
84
|
+
return (
|
|
85
|
+
<SidebarPrimitive.SidebarLink
|
|
86
|
+
key={route.path}
|
|
87
|
+
label={route.title}
|
|
88
|
+
href={route.path}
|
|
89
|
+
active={isCurrent}
|
|
90
|
+
icon={getIcon(route.icon)}
|
|
91
|
+
badge={route.badge}
|
|
92
|
+
/>
|
|
93
|
+
)
|
|
94
|
+
})}
|
|
86
95
|
</SidebarPrimitive.SidebarGroup>
|
|
87
96
|
)}
|
|
88
97
|
|
|
@@ -3,6 +3,8 @@ import T from '@components/primitives/tabs'
|
|
|
3
3
|
import { Link } from '@components/primitives/link'
|
|
4
4
|
import type { BoltdocsTab, ComponentRoute } from '@client/types'
|
|
5
5
|
import * as Icons from 'lucide-react'
|
|
6
|
+
import { getTranslated } from '@client/utils/i18n'
|
|
7
|
+
import { useRoutes } from '@hooks/use-routes'
|
|
6
8
|
|
|
7
9
|
export function Tabs({
|
|
8
10
|
tabs,
|
|
@@ -11,6 +13,7 @@ export function Tabs({
|
|
|
11
13
|
tabs: BoltdocsTab[]
|
|
12
14
|
routes: ComponentRoute[]
|
|
13
15
|
}) {
|
|
16
|
+
const { currentLocale } = useRoutes()
|
|
14
17
|
const { indicatorStyle, tabRefs, activeIndex } = useTabsHook(tabs, routes)
|
|
15
18
|
|
|
16
19
|
const renderTabIcon = (iconName?: string) => {
|
|
@@ -54,7 +57,7 @@ export function Tabs({
|
|
|
54
57
|
}`}
|
|
55
58
|
>
|
|
56
59
|
{renderTabIcon(tab.icon)}
|
|
57
|
-
<span>{tab.text}</span>
|
|
60
|
+
<span>{getTranslated(tab.text, currentLocale)}</span>
|
|
58
61
|
</Link>
|
|
59
62
|
)
|
|
60
63
|
})}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react'
|
|
2
|
-
import { Sun, Moon } from 'lucide-react'
|
|
2
|
+
import { Sun, Moon, Monitor } from 'lucide-react'
|
|
3
3
|
import { useTheme } from '@client/app/theme-context'
|
|
4
|
-
import {
|
|
4
|
+
import { Button } from 'react-aria-components'
|
|
5
|
+
import { Menu, MenuItem, MenuTrigger } from '@components/primitives/menu'
|
|
5
6
|
|
|
6
7
|
export function ThemeToggle() {
|
|
7
|
-
const { theme,
|
|
8
|
+
const { theme, setTheme } = useTheme()
|
|
8
9
|
const [mounted, setMounted] = useState(false)
|
|
9
10
|
|
|
10
11
|
useEffect(() => {
|
|
@@ -15,18 +16,37 @@ export function ThemeToggle() {
|
|
|
15
16
|
return <div className="h-9 w-9" />
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
const Icon = theme === 'system' ? Monitor : theme === 'dark' ? Moon : Sun
|
|
20
|
+
|
|
18
21
|
return (
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
<MenuTrigger placement="bottom right">
|
|
23
|
+
<Button
|
|
24
|
+
className="flex h-9 w-9 items-center justify-center rounded-md text-text-muted transition-colors hover:bg-bg-surface hover:text-text-main outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
|
|
25
|
+
aria-label="Selection theme"
|
|
26
|
+
>
|
|
27
|
+
<Icon size={20} className="animate-in fade-in zoom-in duration-300" />
|
|
28
|
+
</Button>
|
|
29
|
+
<Menu
|
|
30
|
+
selectionMode="single"
|
|
31
|
+
selectedKeys={[theme]}
|
|
32
|
+
onSelectionChange={(keys) => {
|
|
33
|
+
const newTheme = Array.from(keys)[0] as 'light' | 'dark' | 'system'
|
|
34
|
+
setTheme(newTheme)
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
<MenuItem id="light">
|
|
38
|
+
<Sun size={16} />
|
|
39
|
+
<span>Light</span>
|
|
40
|
+
</MenuItem>
|
|
41
|
+
<MenuItem id="dark">
|
|
42
|
+
<Moon size={16} />
|
|
43
|
+
<span>Dark</span>
|
|
44
|
+
</MenuItem>
|
|
45
|
+
<MenuItem id="system">
|
|
46
|
+
<Monitor size={16} />
|
|
47
|
+
<span>System</span>
|
|
48
|
+
</MenuItem>
|
|
49
|
+
</Menu>
|
|
50
|
+
</MenuTrigger>
|
|
31
51
|
)
|
|
32
52
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useNavigate } from 'react-router-dom'
|
|
2
2
|
import { getBaseFilePath } from '@client/utils/get-base-file-path'
|
|
3
3
|
import { useRoutes } from './use-routes'
|
|
4
|
+
import { useBoltdocsStore } from '../store/use-boltdocs-store'
|
|
4
5
|
|
|
5
6
|
export interface LocaleOption {
|
|
6
7
|
key: string
|
|
@@ -24,9 +25,13 @@ export function useI18n(): UseI18nReturn {
|
|
|
24
25
|
const routeContext = useRoutes()
|
|
25
26
|
const { allRoutes, currentRoute, currentLocale, config } = routeContext
|
|
26
27
|
const i18n = config.i18n
|
|
28
|
+
const setLocale = useBoltdocsStore((s) => s.setLocale)
|
|
27
29
|
|
|
28
30
|
const handleLocaleChange = (locale: string) => {
|
|
29
31
|
if (!i18n || locale === currentLocale) return
|
|
32
|
+
|
|
33
|
+
// Update store
|
|
34
|
+
setLocale(locale)
|
|
30
35
|
|
|
31
36
|
let targetPath = '/'
|
|
32
37
|
|
|
@@ -63,21 +68,46 @@ export function useI18n(): UseI18nReturn {
|
|
|
63
68
|
: `/${locale}`
|
|
64
69
|
}
|
|
65
70
|
} else {
|
|
66
|
-
|
|
71
|
+
// Fallback for when we don't have a current route (e.g. 404 page)
|
|
72
|
+
// Try to find the root documentation page for the target locale
|
|
73
|
+
const targetRoute = allRoutes.find(
|
|
74
|
+
(r) =>
|
|
75
|
+
(r.filePath === 'index.mdx' || r.filePath === 'index.md') &&
|
|
76
|
+
(r.locale || i18n.defaultLocale) === locale &&
|
|
77
|
+
!r.version, // Prefer non-versioned root
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if (targetRoute) {
|
|
81
|
+
targetPath = targetRoute.path
|
|
82
|
+
} else {
|
|
83
|
+
targetPath = locale === i18n.defaultLocale ? '/' : `/${locale}`
|
|
84
|
+
}
|
|
67
85
|
}
|
|
68
86
|
|
|
69
87
|
navigate(targetPath)
|
|
70
88
|
}
|
|
71
89
|
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
label
|
|
75
|
-
|
|
76
|
-
|
|
90
|
+
const currentLocaleConfig = config.i18n?.localeConfigs?.[currentLocale as string]
|
|
91
|
+
const currentLocaleLabel =
|
|
92
|
+
currentLocaleConfig?.label ||
|
|
93
|
+
config.i18n?.locales[currentLocale as string] ||
|
|
94
|
+
currentLocale
|
|
95
|
+
|
|
96
|
+
const availableLocales = config.i18n
|
|
97
|
+
? Object.entries(config.i18n.locales).map(([key, defaultLabel]) => {
|
|
98
|
+
const localeConfig = config.i18n?.localeConfigs?.[key]
|
|
99
|
+
return {
|
|
100
|
+
key,
|
|
101
|
+
label: localeConfig?.label || defaultLabel,
|
|
102
|
+
value: key,
|
|
103
|
+
isCurrent: key === currentLocale,
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
: []
|
|
77
107
|
|
|
78
108
|
return {
|
|
79
109
|
currentLocale,
|
|
80
|
-
currentLocaleLabel
|
|
110
|
+
currentLocaleLabel,
|
|
81
111
|
availableLocales,
|
|
82
112
|
handleLocaleChange,
|
|
83
113
|
}
|