boltdocs 2.1.1 → 2.3.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 (133) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/bin/boltdocs.js +2 -2
  3. package/dist/base-ui/index.d.mts +25 -0
  4. package/dist/base-ui/index.d.ts +25 -0
  5. package/dist/base-ui/index.js +1 -0
  6. package/dist/base-ui/index.mjs +1 -0
  7. package/dist/{cache-Q4T6VAUL.mjs → cache-P6WK424C.mjs} +1 -1
  8. package/dist/chunk-22NXDNP4.mjs +74 -0
  9. package/dist/chunk-2HUVMMJU.mjs +1 -0
  10. package/dist/chunk-2Z5T6EAU.mjs +1 -0
  11. package/dist/chunk-CRZGOE32.mjs +1 -0
  12. package/dist/chunk-HA6543SL.mjs +1 -0
  13. package/dist/chunk-JD3RSDE4.mjs +1 -0
  14. package/dist/chunk-JZXLCA2E.mjs +1 -0
  15. package/dist/chunk-NBCYHLAA.mjs +1 -0
  16. package/dist/chunk-RPUERTVC.mjs +1 -0
  17. package/dist/chunk-T3W44KWY.mjs +1 -0
  18. package/dist/chunk-URTD6E6S.mjs +1 -0
  19. package/dist/chunk-W2NB4T6V.mjs +1 -0
  20. package/dist/chunk-Y4RRHPXC.mjs +1 -0
  21. package/dist/client/index.d.mts +13 -115
  22. package/dist/client/index.d.ts +13 -115
  23. package/dist/client/index.js +1 -1
  24. package/dist/client/index.mjs +1 -1
  25. package/dist/client/ssr.js +1 -1
  26. package/dist/client/ssr.mjs +1 -1
  27. package/dist/client/types.d.mts +3 -0
  28. package/dist/client/types.d.ts +3 -0
  29. package/dist/client/types.js +1 -0
  30. package/dist/client/types.mjs +0 -0
  31. package/dist/copy-markdown-C-90ixSe.d.ts +15 -0
  32. package/dist/copy-markdown-CbS8X-qe.d.mts +15 -0
  33. package/dist/{client/hooks → hooks}/index.d.mts +16 -11
  34. package/dist/{client/hooks → hooks}/index.d.ts +16 -11
  35. package/dist/hooks/index.js +1 -0
  36. package/dist/hooks/index.mjs +1 -0
  37. package/dist/integrations/index.d.mts +48 -0
  38. package/dist/integrations/index.d.ts +48 -0
  39. package/dist/integrations/index.js +1 -0
  40. package/dist/integrations/index.mjs +1 -0
  41. package/dist/link-DfBwCeZc.d.mts +68 -0
  42. package/dist/link-DfBwCeZc.d.ts +68 -0
  43. package/dist/loading-B7X5Wchs.d.ts +66 -0
  44. package/dist/loading-WuaQbsKb.d.mts +66 -0
  45. package/dist/{client/components/mdx → mdx}/index.d.mts +6 -38
  46. package/dist/{client/components/mdx → mdx}/index.d.ts +6 -38
  47. package/dist/mdx/index.js +1 -0
  48. package/dist/mdx/index.mjs +1 -0
  49. package/dist/node/cli-entry.js +31 -27
  50. package/dist/node/cli-entry.mjs +5 -1
  51. package/dist/node/index.d.mts +44 -14
  52. package/dist/node/index.d.ts +44 -14
  53. package/dist/node/index.js +24 -24
  54. package/dist/node/index.mjs +1 -1
  55. package/dist/primitives/index.d.mts +301 -0
  56. package/dist/primitives/index.d.ts +301 -0
  57. package/dist/primitives/index.js +1 -0
  58. package/dist/primitives/index.mjs +1 -0
  59. package/dist/search-dialog-ZRXBAQJ5.mjs +1 -0
  60. package/dist/{types-Cp21DHI6.d.mts → types-j7jvWsJj.d.mts} +63 -17
  61. package/dist/{types-Cp21DHI6.d.ts → types-j7jvWsJj.d.ts} +63 -17
  62. package/dist/{use-routes-xLhumjbV.d.ts → use-routes-Cd806kGw.d.ts} +1 -1
  63. package/dist/{use-routes-8Iei6jTp.d.mts → use-routes-DDL0_jkQ.d.mts} +1 -1
  64. package/package.json +35 -8
  65. package/src/client/app/index.tsx +155 -35
  66. package/src/client/app/mdx-component.tsx +7 -3
  67. package/src/client/app/theme-context.tsx +47 -23
  68. package/src/client/components/default-layout.tsx +16 -6
  69. package/src/client/components/primitives/breadcrumbs.tsx +1 -1
  70. package/src/client/components/primitives/navbar.tsx +8 -5
  71. package/src/client/components/primitives/search-dialog.tsx +15 -6
  72. package/src/client/components/primitives/sidebar.tsx +3 -2
  73. package/src/client/components/primitives/skeleton.tsx +26 -0
  74. package/src/client/components/ui-base/breadcrumbs.tsx +1 -1
  75. package/src/client/components/ui-base/index.ts +17 -0
  76. package/src/client/components/ui-base/loading.tsx +43 -73
  77. package/src/client/components/ui-base/navbar.tsx +74 -39
  78. package/src/client/components/ui-base/page-nav.tsx +2 -1
  79. package/src/client/components/ui-base/powered-by.tsx +11 -5
  80. package/src/client/components/ui-base/search-dialog.tsx +16 -5
  81. package/src/client/components/ui-base/sidebar.tsx +33 -22
  82. package/src/client/components/ui-base/tabs.tsx +4 -1
  83. package/src/client/components/ui-base/theme-toggle.tsx +35 -15
  84. package/src/client/hooks/use-i18n.ts +38 -7
  85. package/src/client/hooks/use-localized-to.ts +51 -73
  86. package/src/client/hooks/use-navbar.ts +10 -3
  87. package/src/client/hooks/use-page-nav.ts +27 -6
  88. package/src/client/hooks/use-routes.ts +62 -17
  89. package/src/client/hooks/use-search.ts +84 -46
  90. package/src/client/hooks/use-sidebar.ts +6 -2
  91. package/src/client/hooks/use-version.ts +5 -0
  92. package/src/client/integrations/index.ts +1 -0
  93. package/src/client/store/use-boltdocs-store.ts +44 -0
  94. package/src/client/theme/neutral.css +29 -0
  95. package/src/client/types.ts +4 -2
  96. package/src/client/utils/i18n.ts +23 -0
  97. package/src/node/{cli.ts → cli/build.ts} +17 -23
  98. package/src/node/cli/dev.ts +22 -0
  99. package/src/node/cli/doctor.ts +243 -0
  100. package/src/node/cli/index.ts +9 -0
  101. package/src/node/cli/ui.ts +54 -0
  102. package/src/node/cli-entry.ts +16 -16
  103. package/src/node/config.ts +54 -17
  104. package/src/node/index.ts +1 -1
  105. package/src/node/mdx/cache.ts +12 -0
  106. package/src/node/mdx/highlighter.ts +47 -0
  107. package/src/node/mdx/index.ts +114 -0
  108. package/src/node/mdx/rehype-shiki.ts +53 -0
  109. package/src/node/mdx/remark-shiki.ts +61 -0
  110. package/src/node/plugin/entry.ts +1 -1
  111. package/src/node/plugin/html.ts +8 -4
  112. package/src/node/plugin/index.ts +135 -72
  113. package/src/node/routes/index.ts +34 -13
  114. package/src/node/routes/parser.ts +13 -5
  115. package/src/node/search/index.ts +55 -0
  116. package/src/node/ssg/index.ts +15 -7
  117. package/src/node/ssg/robots.ts +7 -4
  118. package/src/node/utils.ts +32 -2
  119. package/tsup.config.ts +7 -2
  120. package/dist/chunk-52MVMZWS.mjs +0 -1
  121. package/dist/chunk-BVWWKXJH.mjs +0 -1
  122. package/dist/chunk-DVY3RDXD.mjs +0 -1
  123. package/dist/chunk-FUVYCYWC.mjs +0 -1
  124. package/dist/chunk-GBLMDJ2B.mjs +0 -1
  125. package/dist/chunk-ISPX45DF.mjs +0 -1
  126. package/dist/chunk-PNXZMUCO.mjs +0 -1
  127. package/dist/chunk-V2ZHKQSP.mjs +0 -74
  128. package/dist/client/components/mdx/index.js +0 -1
  129. package/dist/client/components/mdx/index.mjs +0 -1
  130. package/dist/client/hooks/index.js +0 -1
  131. package/dist/client/hooks/index.mjs +0 -1
  132. package/dist/search-dialog-TWGYKF2D.mjs +0 -1
  133. 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 { routes: filteredRoutes, allRoutes, currentRoute } = useRoutes()
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,12 @@ export function DefaultLayout({ children }: LayoutProps) {
36
42
  <DocsLayout>
37
43
  <ProgressBar />
38
44
  <Head
39
- siteTitle={config.themeConfig?.title || 'Boltdocs'}
40
- siteDescription={config.themeConfig?.description || ''}
45
+ siteTitle={
46
+ getTranslated(config.theme?.title, currentLocale) || 'Boltdocs'
47
+ }
48
+ siteDescription={
49
+ getTranslated(config.theme?.description, currentLocale) || ''
50
+ }
41
51
  routes={allRoutes}
42
52
  />
43
53
  <Navbar />
@@ -52,7 +62,7 @@ export function DefaultLayout({ children }: LayoutProps) {
52
62
  <CopyMarkdownComp
53
63
  mdxRaw={currentRoute?._rawContent}
54
64
  route={currentRoute}
55
- config={config.themeConfig?.copyMarkdown}
65
+ config={config.theme?.copyMarkdown}
56
66
  />
57
67
  </DocsLayout.ContentHeader>
58
68
  )}
@@ -69,8 +79,8 @@ export function DefaultLayout({ children }: LayoutProps) {
69
79
  {!isHome && (
70
80
  <OnThisPage
71
81
  headings={currentRoute?.headings}
72
- editLink={config.themeConfig?.editLink}
73
- communityHelp={config.themeConfig?.communityHelp}
82
+ editLink={config.theme?.editLink}
83
+ communityHelp={config.theme?.communityHelp}
74
84
  filePath={currentRoute?.filePath}
75
85
  />
76
86
  )}
@@ -16,7 +16,7 @@ export const BreadcrumbsRoot = ({
16
16
  return (
17
17
  <BreadcrumbsRAC
18
18
  className={cn(
19
- 'flex items-center gap-1.5 mb-0 text-sm text-text-muted',
19
+ 'flex items-center gap-1.5 pl-0! mb-0 text-sm text-text-muted',
20
20
  className,
21
21
  )}
22
22
  {...props}
@@ -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 hover:text-text-main focus-visible:ring-2 focus-visible:ring-primary-500/30 rounded-sm',
184
- active ? 'text-primary-500' : 'text-text-muted',
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
  >
@@ -211,7 +214,7 @@ export const NavbarSearchTrigger = ({
211
214
  onPress={onPress}
212
215
  className={cn(
213
216
  'flex items-center gap-2 rounded-full border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-muted outline-none cursor-pointer',
214
- 'transition-colors hover:border-border-strong hover:text-text-main',
217
+ 'transition-all duration-200 hover:border-border-strong hover:text-text-main hover:bg-bg-muted hover:shadow-sm active:scale-[0.98]',
215
218
  'focus-visible:ring-2 focus-visible:ring-primary-500/30',
216
219
  'w-full max-w-[320px] justify-between',
217
220
  className,
@@ -243,8 +246,8 @@ export const NavbarTheme = ({
243
246
  isSelected={theme === 'dark'}
244
247
  onChange={onThemeChange}
245
248
  className={cn(
246
- 'rounded-md p-2 text-text-muted outline-none cursor-pointer transition-colors',
247
- 'hover:bg-bg-surface hover:text-text-main',
249
+ 'rounded-md p-2 text-text-muted outline-none cursor-pointer',
250
+ 'transition-all duration-300 hover:bg-bg-surface hover:text-text-main hover:rotate-12 active:scale-90',
248
251
  'focus-visible:ring-2 focus-visible:ring-primary-500/30',
249
252
  className,
250
253
  )}
@@ -50,16 +50,25 @@ export const SearchDialogRoot = ({
50
50
  )
51
51
  }
52
52
 
53
- export const SearchDialogAutocomplete = ({
53
+ export const SearchDialogAutocomplete = <T extends object>({
54
54
  children,
55
55
  className,
56
+ onSelectionChange,
56
57
  ...props
57
- }: RAC.AutocompleteProps<object> & { className?: string }) => {
58
+ }: RAC.AutocompleteProps<T> & {
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
- <RAC.Autocomplete {...props} className="flex flex-col min-h-0">
65
+ <Autocomplete
66
+ {...props}
67
+ onSelectionChange={onSelectionChange}
68
+ className="flex flex-col min-h-0"
69
+ >
61
70
  {children}
62
- </RAC.Autocomplete>
71
+ </Autocomplete>
63
72
  </div>
64
73
  )
65
74
  }
@@ -89,11 +98,11 @@ export const SearchDialogInput = ({
89
98
  )
90
99
  }
91
100
 
92
- export const SearchDialogList = ({
101
+ export const SearchDialogList = <T extends object>({
93
102
  children,
94
103
  className,
95
104
  ...props
96
- }: RAC.ListBoxProps<object> & { className?: string }) => {
105
+ }: RAC.ListBoxProps<T> & { className?: string }) => {
97
106
  return (
98
107
  <RAC.ListBox
99
108
  {...props}
@@ -122,11 +122,12 @@ export const SidebarLink = ({
122
122
  <Link
123
123
  href={href}
124
124
  className={cn(
125
- 'group flex items-center gap-2.5 rounded-lg px-2.5 py-2 text-sm outline-none transition-colors',
125
+ 'group flex items-center gap-2.5 rounded-lg px-2.5 py-2 text-sm outline-none',
126
+ 'transition-all duration-200 ease-in-out',
126
127
  'focus-visible:ring-2 focus-visible:ring-primary-500/30',
127
128
  active
128
129
  ? 'bg-primary-500/10 text-primary-500 font-medium'
129
- : 'text-text-muted hover:bg-bg-surface hover:text-text-main',
130
+ : 'text-text-muted hover:bg-bg-surface hover:text-text-main hover:translate-x-1',
130
131
  className,
131
132
  )}
132
133
  >
@@ -0,0 +1,26 @@
1
+ import { cn } from '@client/utils/cn'
2
+
3
+ interface SkeletonProps extends React.HTMLAttributes<HTMLDivElement> {
4
+ variant?: 'rect' | 'circle'
5
+ }
6
+
7
+ /**
8
+ * A flexible skeleton component that mimics the shape of content
9
+ * while it is loading. Features a smooth pulse animation.
10
+ */
11
+ export function Skeleton({
12
+ className,
13
+ variant = 'rect',
14
+ ...props
15
+ }: SkeletonProps) {
16
+ return (
17
+ <div
18
+ className={cn(
19
+ 'animate-pulse bg-bg-muted',
20
+ variant === 'circle' ? 'rounded-full' : 'rounded-md',
21
+ className,
22
+ )}
23
+ {...props}
24
+ />
25
+ )
26
+ }
@@ -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 || config.themeConfig || {}
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'
@@ -1,85 +1,55 @@
1
- import { useEffect, useState } from 'react'
1
+ import { cn } from '@client/utils/cn'
2
+ import { Skeleton } from '@primitives/skeleton'
2
3
 
3
4
  /**
4
- * A premium loading component that includes an SVG fill animation
5
- * and a synchronized progress indicator.
6
- *
7
- * It features a glassmorphism container and a Bolt-style SVG logo
8
- * with a dynamic fill effect.
5
+ * A premium loading component that only skeletons the markdown content area.
6
+ * Designed to be used as a Suspense fallback within a persistent layout.
9
7
  */
10
8
  export function Loading() {
11
- const [progress, setProgress] = useState(0)
12
-
13
- useEffect(() => {
14
- let currentProgress = 0
15
- let up = true
9
+ return (
10
+ <div
11
+ className={cn(
12
+ 'w-full h-full relative overflow-y-auto transition-opacity duration-300 animate-fade-in',
13
+ )}
14
+ >
15
+ <div className="mx-auto max-w-(--spacing-content-max) px-4 py-8 space-y-10">
16
+ {/* Breadcrumbs */}
17
+ <div className="flex gap-2">
18
+ <Skeleton className="h-3 w-16" />
19
+ <Skeleton className="h-3 w-24" />
20
+ </div>
16
21
 
17
- const interval = setInterval(() => {
18
- if (up) {
19
- currentProgress += 1
20
- if (currentProgress >= 100) {
21
- currentProgress = 100
22
- up = false
23
- }
24
- } else {
25
- currentProgress -= 1
26
- if (currentProgress <= 0) {
27
- currentProgress = 0
28
- up = true
29
- }
30
- }
31
- setProgress(currentProgress)
32
- }, 20)
22
+ {/* Page Title */}
23
+ <Skeleton className="h-10 w-[60%] sm:h-12" />
33
24
 
34
- return () => clearInterval(interval)
35
- }, [])
25
+ {/* Intro Paragraph */}
26
+ <div className="space-y-3">
27
+ <Skeleton className="h-4 w-full" />
28
+ <Skeleton className="h-4 w-[95%]" />
29
+ <Skeleton className="h-4 w-[40%]" />
30
+ </div>
36
31
 
37
- const clipPathValue = `inset(${100 - progress}% 0 0 0)`
32
+ {/* Section 1 */}
33
+ <div className="space-y-6 pt-4">
34
+ <Skeleton className="h-7 w-32" />
35
+ <div className="space-y-3">
36
+ <Skeleton className="h-4 w-full" />
37
+ <Skeleton className="h-4 w-[98%]" />
38
+ <Skeleton className="h-4 w-[92%]" />
39
+ <Skeleton className="h-4 w-[60%]" />
40
+ </div>
41
+ </div>
38
42
 
39
- return (
40
- <div className="flex flex-col items-center justify-center min-h-[60vh] p-4 text-center">
41
- <div className="relative group">
42
- <div className="relative inline-block">
43
- {/* SVG Background (Dimmed Base) */}
44
- <svg
45
- className="w-24 h-auto opacity-10 filter grayscale brightness-50"
46
- viewBox="0 0 60 51"
47
- fill="none"
48
- xmlns="http://www.w3.org/2000/svg"
49
- role="img"
50
- aria-hidden="true"
51
- >
52
- <title>Loading indicator background</title>
53
- <path
54
- d="M29.4449 0H19.4449V16.5L29.4449 6.5V0Z"
55
- fill="currentColor"
56
- />
57
- <path
58
- d="M26.9449 22.7265C26.9449 22.5077 21.2201 27.0658 16.9449 28.5C13.7491 29.5721 12.3156 29.5038 8.94486 29.5C5.59532 29.4963 0 28.5 0 28.5C0 28.5 5.57953 28.5146 8.94486 27.5C12.5409 26.4158 14.8203 25.5843 17.9449 23.5C23.3445 19.898 29.4449 11.5 29.4449 11.5L29.9449 18C29.9449 18 33.5825 15.8308 36.4449 15C39.4452 14.1291 44.4449 14 44.4449 14C44.4449 14 36.9449 19 34.4449 21.5C31.5322 24.4126 29.8582 26.9017 29.4449 31C29.1217 34.2041 29.4771 36.4508 31.4449 39C33.5792 41.765 35.952 43.0183 39.4449 43C42.677 42.9831 45.3003 42.4182 47.4449 40C49.7406 37.4113 50.2495 34.4466 49.9449 31C49.6603 27.7804 48.4876 25.4953 45.9449 23.5C43.2931 21.4191 36.4449 24 36.4449 24L47.9449 15C47.9449 15 51.5761 16.771 53.4449 18.5C55.711 20.5967 56.7467 22.1546 57.9449 25C59.1784 27.9295 59.4832 29.8216 59.4449 33C59.4089 35.9867 59.179 37.78 57.9449 40.5C56.8475 42.9185 55.8511 44.6507 53.9449 46.5C51.9236 48.4609 50.5803 49.0076 47.9449 50C45.5414 50.9051 44.0131 51 41.4449 51C38.8766 51 37.3235 50.9685 34.9449 50C32.4851 48.9985 29.4449 46 29.4449 46V51H19.4449V37.9904L22.9449 31.4226L26.9449 22.7265Z"
59
- fill="currentColor"
60
- />
61
- </svg>
43
+ {/* Code Block Placeholder */}
44
+ <Skeleton className="h-32 w-full rounded-lg bg-bg-muted/50" />
62
45
 
63
- {/* SVG Animated (Vibrant Fill Overlay) */}
64
- <svg
65
- className="absolute top-0 left-0 w-24 h-auto text-primary-500 drop-shadow-[0_0_20px_rgba(var(--primary-rgb),0.5)] transition-[clip-path] duration-100 ease-linear"
66
- style={{ clipPath: clipPathValue }}
67
- viewBox="0 0 60 51"
68
- fill="none"
69
- xmlns="http://www.w3.org/2000/svg"
70
- role="img"
71
- aria-hidden="true"
72
- >
73
- <title>Loading indicator animated fill</title>
74
- <path
75
- d="M29.4449 0H19.4449V16.5L29.4449 6.5V0Z"
76
- fill="currentColor"
77
- />
78
- <path
79
- d="M26.9449 22.7265C26.9449 22.5077 21.2201 27.0658 16.9449 28.5C13.7491 29.5721 12.3156 29.5038 8.94486 29.5C5.59532 29.4963 0 28.5 0 28.5C0 28.5 5.57953 28.5146 8.94486 27.5C12.5409 26.4158 14.8203 25.5843 17.9449 23.5C23.3445 19.898 29.4449 11.5 29.4449 11.5L29.9449 18C29.9449 18 33.5825 15.8308 36.4449 15C39.4452 14.1291 44.4449 14 44.4449 14C44.4449 14 36.9449 19 34.4449 21.5C31.5322 24.4126 29.8582 26.9017 29.4449 31C29.1217 34.2041 29.4771 36.4508 31.4449 39C33.5792 41.765 35.952 43.0183 39.4449 43C42.677 42.9831 45.3003 42.4182 47.4449 40C49.7406 37.4113 50.2495 34.4466 49.9449 31C49.6603 27.7804 48.4876 25.4953 45.9449 23.5C43.2931 21.4191 36.4449 24 36.4449 24L47.9449 15C47.9449 15 51.5761 16.771 53.4449 18.5C55.711 20.5967 56.7467 22.1546 57.9449 25C59.1784 27.9295 59.4832 29.8216 59.4449 33C59.4089 35.9867 59.179 37.78 57.9449 40.5C56.8475 42.9185 55.8511 44.6507 53.9449 46.5C51.9236 48.4609 50.5803 49.0076 47.9449 50C45.5414 50.9051 44.0131 51 41.4449 51C38.8766 51 37.3235 50.9685 34.9449 50C32.4851 48.9985 29.4449 46 29.4449 46V51H19.4449V37.9904L22.9449 31.4226L26.9449 22.7265Z"
80
- fill="currentColor"
81
- />
82
- </svg>
46
+ {/* Section 2 */}
47
+ <div className="space-y-6 pt-4">
48
+ <Skeleton className="h-7 w-48" />
49
+ <div className="space-y-3">
50
+ <Skeleton className="h-4 w-full" />
51
+ <Skeleton className="h-4 w-[85%]" />
52
+ </div>
83
53
  </div>
84
54
  </div>
85
55
  </div>
@@ -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 { useConfig } from '@client/app/config-context'
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 || config.themeConfig || {}
28
+ const themeConfig = config.theme || {}
28
29
 
29
30
  const hasTabs = themeConfig?.tabs && themeConfig.tabs.length > 0
30
31
 
@@ -32,19 +33,21 @@ export function Navbar() {
32
33
  <NavbarPrimitive.NavbarRoot className={hasTabs ? 'border-b-0' : ''}>
33
34
  <NavbarPrimitive.Content>
34
35
  <NavbarPrimitive.NavbarLeft>
35
- <NavbarPrimitive.NavbarLogo
36
- src={logo ?? ''}
37
- alt={logoProps?.alt || title}
38
- width={logoProps?.width ?? 24}
39
- height={logoProps?.height ?? 24}
40
- />
36
+ {logo && (
37
+ <NavbarPrimitive.NavbarLogo
38
+ src={logo}
39
+ alt={logoProps?.alt || title}
40
+ width={logoProps?.width ?? 24}
41
+ height={logoProps?.height ?? 24}
42
+ />
43
+ )}
41
44
  <NavbarPrimitive.Title>{title}</NavbarPrimitive.Title>
42
45
 
43
46
  {config.versions && currentVersion && <NavbarVersion />}
44
47
 
45
48
  <NavbarPrimitive.Links>
46
49
  {links.map((link) => (
47
- <NavbarPrimitive.Link key={link.href} {...(link as any)} />
50
+ <NavbarLinkItem key={link.href} link={link} />
48
51
  ))}
49
52
  </NavbarPrimitive.Links>
50
53
  </NavbarPrimitive.NavbarLeft>
@@ -78,16 +81,18 @@ export function Navbar() {
78
81
 
79
82
  {pathname !== '/' && hasTabs && themeConfig?.tabs && (
80
83
  <div className="w-full border-b border-border-subtle bg-bg-main">
81
- <Tabs
82
- tabs={themeConfig.tabs}
83
- routes={allRoutes || routes || []}
84
- />
84
+ <Tabs tabs={themeConfig.tabs} routes={allRoutes || routes || []} />
85
85
  </div>
86
86
  )}
87
87
  </NavbarPrimitive.NavbarRoot>
88
88
  )
89
89
  }
90
90
 
91
+ function NavbarLinkItem({ link }: { link: NavbarLinkType }) {
92
+ const localizedHref = useLocalizedTo(link.href)
93
+ return <NavbarPrimitive.Link {...(link as any)} href={localizedHref} />
94
+ }
95
+
91
96
  function NavbarVersion() {
92
97
  const { currentVersionLabel, availableVersions, handleVersionChange } =
93
98
  useVersion()
@@ -96,43 +101,73 @@ function NavbarVersion() {
96
101
 
97
102
  return (
98
103
  <Menu.Trigger>
99
- <Button variant={'outline'} iconPosition="right" icon={<ChevronDown />}>
100
- {currentVersionLabel}
104
+ <Button
105
+ variant={'outline'}
106
+ size="sm"
107
+ rounded="lg"
108
+ iconPosition="right"
109
+ icon={<ChevronDown className="w-3.5 h-3.5 text-text-muted/60" />}
110
+ 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"
111
+ >
112
+ <span className="font-semibold text-[0.8125rem]">
113
+ {currentVersionLabel}
114
+ </span>
101
115
  </Button>
102
- <Menu.Section items={availableVersions}>
103
- {(version) => (
104
- <Menu.Item
105
- key={`${version.value ?? ''}`}
106
- onPress={() => handleVersionChange(version.value)}
107
- >
108
- {version.label as string}
109
- </Menu.Item>
110
- )}
111
- </Menu.Section>
116
+ <Menu.Root>
117
+ <Menu.Section items={availableVersions}>
118
+ {(version) => (
119
+ <Menu.Item
120
+ key={`${version.value ?? ''}`}
121
+ onPress={() => handleVersionChange(version.value)}
122
+ >
123
+ {version.label as string}
124
+ </Menu.Item>
125
+ )}
126
+ </Menu.Section>
127
+ </Menu.Root>
112
128
  </Menu.Trigger>
113
129
  )
114
130
  }
115
131
 
116
132
  function NavbarI18n() {
117
- const { currentLocaleLabel, availableLocales, handleLocaleChange } = useI18n()
133
+ const { currentLocale, availableLocales, handleLocaleChange } = useI18n()
118
134
 
119
135
  if (availableLocales.length === 0) return null
120
136
 
121
137
  return (
122
138
  <Menu.Trigger>
123
- <Button variant={'outline'} iconPosition="right" icon={<ChevronDown />}>
124
- {currentLocaleLabel}
139
+ <Button
140
+ variant={'outline'}
141
+ size="sm"
142
+ rounded="lg"
143
+ iconPosition="right"
144
+ icon={<ChevronDown className="w-3.5 h-3.5 text-text-muted/60" />}
145
+ 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"
146
+ >
147
+ <div className="flex items-center gap-1.5">
148
+ <Languages className="w-3.5 h-3.5 text-primary-500" />
149
+ <span className="font-bold text-[0.75rem] tracking-wider uppercase opacity-90">
150
+ {currentLocale || 'en'}
151
+ </span>
152
+ </div>
125
153
  </Button>
126
- <Menu.Section items={availableLocales}>
127
- {(locale) => (
128
- <Menu.Item
129
- key={`${locale.value ?? ''}`}
130
- onPress={() => handleLocaleChange(locale.value)}
131
- >
132
- {locale.label as string}
133
- </Menu.Item>
134
- )}
135
- </Menu.Section>
154
+ <Menu.Root>
155
+ <Menu.Section items={availableLocales}>
156
+ {(locale) => (
157
+ <Menu.Item
158
+ key={`${locale.value ?? ''}`}
159
+ onPress={() => handleLocaleChange(locale.value)}
160
+ >
161
+ <div className="flex items-center justify-between w-full gap-4">
162
+ <span>{locale.label as string}</span>
163
+ <span className="text-[10px] font-bold opacity-40 uppercase tracking-tighter">
164
+ {locale.value}
165
+ </span>
166
+ </div>
167
+ </Menu.Item>
168
+ )}
169
+ </Menu.Section>
170
+ </Menu.Root>
136
171
  </Menu.Trigger>
137
172
  )
138
173
  }
@@ -3,6 +3,7 @@ import PageNavPrimitive from '@components/primitives/page-nav'
3
3
 
4
4
  /**
5
5
  * Component to display the previous and next page navigation buttons.
6
+ * Enhanced with subtle entrance animations and a modern card layout.
6
7
  */
7
8
  export function PageNav() {
8
9
  const { prevPage, nextPage } = usePageNav()
@@ -10,7 +11,7 @@ export function PageNav() {
10
11
  if (!prevPage && !nextPage) return null
11
12
 
12
13
  return (
13
- <PageNavPrimitive.PageNavRoot>
14
+ <PageNavPrimitive.PageNavRoot className="animate-in fade-in slide-in-from-bottom-4 duration-700">
14
15
  {prevPage ? (
15
16
  <PageNavPrimitive.PageNavLink to={prevPage.path} direction="prev">
16
17
  <PageNavPrimitive.PageNavLink.Title>
@@ -2,16 +2,22 @@ import { Zap } from 'lucide-react'
2
2
 
3
3
  export function PoweredBy() {
4
4
  return (
5
- <div className="rounded-full px-4 py-2 bg-gray-100 text-xs text-gray-500 flex items-center gap-1 mt-6 justify-center">
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-1"
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 className="powered-by-icon" size={12} fill="currentColor" />
13
- <span>
14
- Powered by <strong>Boltdocs</strong>
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{' '}
18
+ <strong className="font-bold text-text-main/80 group-hover:text-text-main">
19
+ Boltdocs
20
+ </strong>
15
21
  </span>
16
22
  </a>
17
23
  </div>
@@ -12,8 +12,17 @@ import {
12
12
  } from '@components/primitives/search-dialog'
13
13
  import Navbar from '@components/primitives/navbar'
14
14
  import { useNavigate } from 'react-router-dom'
15
+ import type { ComponentRoute } from '@client/types'
16
+ interface SearchResult {
17
+ id: string
18
+ title: string
19
+ path: string
20
+ bio: string
21
+ groupTitle?: string
22
+ isHeading?: boolean
23
+ }
15
24
 
16
- export function SearchDialog({ routes }: { routes: any[] }) {
25
+ export function SearchDialog({ routes }: { routes: ComponentRoute[] }) {
17
26
  const { isOpen, setIsOpen, query, setQuery, list } = useSearch(routes)
18
27
  const navigate = useNavigate()
19
28
 
@@ -58,10 +67,12 @@ export function SearchDialog({ routes }: { routes: any[] }) {
58
67
  <SearchDialogAutocomplete onSelectionChange={handleSelect}>
59
68
  <SearchDialogInput
60
69
  value={query}
61
- onChange={(e: any) => setQuery(e.target.value)}
70
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
71
+ setQuery(e.target.value)
72
+ }
62
73
  />
63
- <SearchDialogList items={list}>
64
- {(item: any) => (
74
+ <SearchDialogList items={list as SearchResult[]}>
75
+ {(item: SearchResult) => (
65
76
  <SearchDialogItemRoot
66
77
  key={item.id}
67
78
  onPress={() => handleSelect(item.id)}
@@ -70,7 +81,7 @@ export function SearchDialog({ routes }: { routes: any[] }) {
70
81
  <SearchDialogItemIcon isHeading={item.isHeading} />
71
82
  <div className="flex flex-col justify-center gap-0.5">
72
83
  <SearchDialogItemTitle>{item.title}</SearchDialogItemTitle>
73
- <SearchDialogItemBio>{item.groupTitle}</SearchDialogItemBio>
84
+ <SearchDialogItemBio>{item.bio}</SearchDialogItemBio>
74
85
  </div>
75
86
  </SearchDialogItemRoot>
76
87
  )}