boltdocs 2.6.2 → 2.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 (177) hide show
  1. package/bin/boltdocs.js +0 -1
  2. package/dist/cache-CQKlT4fI.mjs +6 -0
  3. package/dist/cache-DorPMFgW.cjs +6 -0
  4. package/dist/cards-BLoSiRuL.d.ts +30 -0
  5. package/dist/cards-CQn9mXZS.d.cts +30 -0
  6. package/dist/chunk-Ds5LZdWN.cjs +6 -0
  7. package/dist/client/index.cjs +1 -1
  8. package/dist/client/index.d.cts +167 -1338
  9. package/dist/client/index.d.ts +166 -1337
  10. package/dist/client/index.js +1 -1
  11. package/dist/{package-CFP44vfn.cjs → client/mdx.cjs} +1 -1
  12. package/dist/client/mdx.d.cts +128 -0
  13. package/dist/client/mdx.d.ts +129 -0
  14. package/dist/client/mdx.js +6 -0
  15. package/dist/client/primitives.cjs +6 -0
  16. package/dist/client/primitives.d.cts +818 -0
  17. package/dist/client/primitives.d.ts +818 -0
  18. package/dist/client/primitives.js +6 -0
  19. package/dist/client/theme/neutral.css +74 -361
  20. package/dist/client/theme/reset.css +189 -0
  21. package/dist/docs-layout-BlDhcQRv.cjs +6 -0
  22. package/dist/docs-layout-BvAOWEJw.js +6 -0
  23. package/dist/doctor-BQiQhCTl.cjs +6 -0
  24. package/dist/doctor-COpf35L2.cjs +20 -0
  25. package/dist/doctor-Dh1XP7Pz.mjs +20 -0
  26. package/dist/generator-DGW6pkCC.cjs +22 -0
  27. package/dist/generator-Dv3wEmhZ.mjs +22 -0
  28. package/dist/icons-dev-CrQLjoQp.js +6 -0
  29. package/dist/icons-dev-rzdz6Lf3.cjs +6 -0
  30. package/dist/image-BkIfa9oo.js +6 -0
  31. package/dist/image-DIGjCPe6.cjs +6 -0
  32. package/dist/mdx-K0WYBAJ3.js +7 -0
  33. package/dist/mdx-hpErbRUe.cjs +7 -0
  34. package/dist/meta-loader-0gJ4PtBC.cjs +6 -0
  35. package/dist/meta-loader-9IpAHWDS.mjs +6 -0
  36. package/dist/node/cli-entry.cjs +1 -2
  37. package/dist/node/cli-entry.mjs +1 -2
  38. package/dist/node/index.cjs +1 -1
  39. package/dist/node/index.d.cts +55 -11
  40. package/dist/node/index.d.mts +55 -12
  41. package/dist/node/index.mjs +1 -1
  42. package/dist/node/routes/worker.cjs +6 -0
  43. package/dist/node/routes/worker.d.cts +2 -0
  44. package/dist/node/routes/worker.d.mts +2 -0
  45. package/dist/node/routes/worker.mjs +6 -0
  46. package/dist/node-C2nWXElP.mjs +112 -0
  47. package/dist/node-CinkUtxV.cjs +112 -0
  48. package/dist/package-BMYLDBBP.cjs +6 -0
  49. package/dist/{package-Bqbn1AYK.mjs → package-HegMOTL_.mjs} +1 -1
  50. package/dist/parser-Bh11BsdA.cjs +6 -0
  51. package/dist/parser-D8eQvE7N.mjs +6 -0
  52. package/dist/parser-DYRzXWmA.cjs +6 -0
  53. package/dist/routes-CHf76Ye4.cjs +6 -0
  54. package/dist/routes-CMUZGI6T.mjs +6 -0
  55. package/dist/routes-Co1mRM58.cjs +6 -0
  56. package/dist/search-dialog-BACuzoVX.cjs +6 -0
  57. package/dist/search-dialog-BKagVT17.js +6 -0
  58. package/dist/search-dialog-C8w12eUx.js +6 -0
  59. package/dist/search-dialog-CGyrozZE.cjs +6 -0
  60. package/dist/search-dialog-D26rUnJ_.cjs +6 -0
  61. package/dist/sidebar-DKvg6KOc.d.cts +491 -0
  62. package/dist/sidebar-Dr1TiRIy.d.ts +491 -0
  63. package/dist/utils-BxNAXhZZ.mjs +7 -0
  64. package/dist/utils-Clzu7jvb.cjs +7 -0
  65. package/dist/worker-pool-Bd8Y9KDv.mjs +6 -0
  66. package/dist/worker-pool-BwU8ckrg.cjs +6 -0
  67. package/package.json +27 -8
  68. package/src/client/app/doc-page.tsx +9 -5
  69. package/src/client/app/docs-layout.tsx +17 -3
  70. package/src/client/app/head.tsx +122 -0
  71. package/src/client/app/helmet-compat.tsx +36 -0
  72. package/src/client/app/mdx-component.tsx +5 -52
  73. package/src/client/app/mdx-components-context.tsx +32 -8
  74. package/src/client/app/routes-context.tsx +2 -2
  75. package/src/client/app/scroll-handler.tsx +1 -1
  76. package/src/client/app/theme-context.tsx +5 -5
  77. package/src/client/app/ui-context.tsx +42 -0
  78. package/src/client/components/docs-layout-default.tsx +85 -0
  79. package/src/client/components/icons-dev.tsx +38 -15
  80. package/src/client/components/mdx/callout.tsx +97 -0
  81. package/src/client/components/mdx/card.tsx +73 -98
  82. package/src/client/components/mdx/cards.tsx +27 -0
  83. package/src/client/components/mdx/code-block.tsx +37 -17
  84. package/src/client/components/mdx/field.tsx +24 -56
  85. package/src/client/components/mdx/image.tsx +36 -15
  86. package/src/client/components/mdx/index.ts +19 -53
  87. package/src/client/components/mdx/table.tsx +46 -148
  88. package/src/client/components/mdx/typographics.tsx +120 -0
  89. package/src/client/components/mdx/{hooks/use-code-block.ts → use-code-block.ts} +5 -7
  90. package/src/client/components/primitives/breadcrumbs.tsx +5 -24
  91. package/src/client/components/primitives/button.tsx +3 -142
  92. package/src/client/components/primitives/code-block.tsx +104 -97
  93. package/src/client/components/{docs-layout.tsx → primitives/docs-layout.tsx} +15 -24
  94. package/src/client/components/primitives/error-boundary.tsx +107 -0
  95. package/src/client/components/primitives/heading.tsx +128 -0
  96. package/src/client/components/primitives/helpers/observer.ts +62 -32
  97. package/src/client/components/primitives/image.tsx +26 -0
  98. package/src/client/components/primitives/link.tsx +50 -52
  99. package/src/client/components/primitives/menu.tsx +25 -49
  100. package/src/client/components/primitives/navbar.tsx +234 -59
  101. package/src/client/components/primitives/on-this-page.tsx +169 -40
  102. package/src/client/components/primitives/page-nav.tsx +11 -39
  103. package/src/client/components/primitives/popover.tsx +12 -30
  104. package/src/client/components/primitives/search-dialog.tsx +77 -71
  105. package/src/client/components/primitives/sidebar.tsx +312 -119
  106. package/src/client/components/primitives/skeleton.tsx +1 -1
  107. package/src/client/components/primitives/tabs.tsx +5 -16
  108. package/src/client/components/primitives/tooltip.tsx +1 -1
  109. package/src/client/components/ui-base/banner.tsx +66 -0
  110. package/src/client/components/ui-base/breadcrumbs.tsx +26 -20
  111. package/src/client/components/ui-base/copy-markdown.tsx +43 -35
  112. package/src/client/components/ui-base/error-boundary.tsx +9 -46
  113. package/src/client/components/ui-base/github-stars.tsx +5 -3
  114. package/src/client/components/ui-base/index.ts +3 -3
  115. package/src/client/components/ui-base/last-updated.tsx +27 -0
  116. package/src/client/components/ui-base/navbar.tsx +183 -89
  117. package/src/client/components/ui-base/not-found.tsx +11 -9
  118. package/src/client/components/ui-base/on-this-page.tsx +8 -104
  119. package/src/client/components/ui-base/page-nav.tsx +23 -9
  120. package/src/client/components/ui-base/search-dialog.tsx +111 -36
  121. package/src/client/components/ui-base/search-highlight.tsx +10 -0
  122. package/src/client/components/ui-base/sidebar.tsx +77 -154
  123. package/src/client/components/ui-base/tabs.tsx +20 -7
  124. package/src/client/components/ui-base/theme-toggle.tsx +88 -10
  125. package/src/client/components/ui-base/version-i18n.tsx +80 -0
  126. package/src/client/hooks/index.ts +2 -1
  127. package/src/client/hooks/use-analytics.ts +272 -0
  128. package/src/client/hooks/use-i18n.ts +116 -50
  129. package/src/client/hooks/use-localized-to.ts +70 -27
  130. package/src/client/hooks/use-navbar.ts +69 -39
  131. package/src/client/hooks/use-page-nav.ts +28 -25
  132. package/src/client/hooks/use-routes.ts +63 -80
  133. package/src/client/hooks/use-search-highlight.ts +185 -0
  134. package/src/client/hooks/use-search.ts +12 -3
  135. package/src/client/hooks/use-sidebar.ts +183 -80
  136. package/src/client/hooks/use-tabs.ts +3 -4
  137. package/src/client/hooks/use-version.ts +44 -29
  138. package/src/client/index.ts +13 -87
  139. package/src/client/mdx.ts +2 -0
  140. package/src/client/primitives.ts +19 -0
  141. package/src/client/ssg/boltdocs-shell.tsx +68 -79
  142. package/src/client/ssg/create-routes.tsx +268 -72
  143. package/src/client/ssg/mdx-page.tsx +2 -1
  144. package/src/client/store/boltdocs-context.tsx +72 -20
  145. package/src/client/theme/neutral.css +74 -361
  146. package/src/client/theme/reset.css +189 -0
  147. package/src/client/types.ts +10 -2
  148. package/src/client/utils/path.ts +9 -0
  149. package/src/client/utils/react-to-text.ts +24 -24
  150. package/src/client/virtual.d.ts +1 -1
  151. package/src/shared/types.ts +82 -22
  152. package/dist/node-Bogvkxao.mjs +0 -101
  153. package/dist/node-CXaog6St.cjs +0 -101
  154. package/dist/search-dialog-CV3eJzMm.cjs +0 -6
  155. package/dist/search-dialog-DNTomKgu.js +0 -6
  156. package/dist/use-search-CS3gH19M.js +0 -6
  157. package/dist/use-search-DBpJZQuw.cjs +0 -6
  158. package/src/client/components/mdx/admonition.tsx +0 -91
  159. package/src/client/components/mdx/badge.tsx +0 -41
  160. package/src/client/components/mdx/button.tsx +0 -35
  161. package/src/client/components/mdx/component-preview.tsx +0 -37
  162. package/src/client/components/mdx/component-props.tsx +0 -83
  163. package/src/client/components/mdx/file-tree.tsx +0 -325
  164. package/src/client/components/mdx/hooks/use-component-preview.ts +0 -16
  165. package/src/client/components/mdx/hooks/useTable.ts +0 -74
  166. package/src/client/components/mdx/hooks/useTabs.ts +0 -68
  167. package/src/client/components/mdx/link.tsx +0 -38
  168. package/src/client/components/mdx/list.tsx +0 -192
  169. package/src/client/components/mdx/tabs.tsx +0 -135
  170. package/src/client/components/mdx/video.tsx +0 -68
  171. package/src/client/components/primitives/index.ts +0 -19
  172. package/src/client/components/primitives/navigation-menu.tsx +0 -114
  173. package/src/client/components/ui-base/head.tsx +0 -83
  174. package/src/client/components/ui-base/loading.tsx +0 -57
  175. package/src/client/components/ui-base/powered-by.tsx +0 -25
  176. package/src/client/hooks/use-onthispage.ts +0 -23
  177. package/src/client/utils/use-on-change.ts +0 -15
@@ -3,20 +3,22 @@ import { Link } from '../primitives/link'
3
3
 
4
4
  export function NotFound() {
5
5
  return (
6
- <div className="flex items-center justify-center min-h-[60vh] text-center">
7
- <div className="space-y-4">
8
- <span className="text-8xl font-black tracking-tighter text-primary-500/20">
6
+ <div className="flex items-center justify-center min-h-[65vh] text-center px-4">
7
+ <div className="space-y-6 max-w-md mx-auto p-8 border border-subtle bg-surface rounded-2xl shadow-xs">
8
+ <span className="block text-7xl font-extrabold tracking-tight text-primary-500">
9
9
  404
10
10
  </span>
11
- <h1 className="text-2xl font-bold text-text-main">Page Not Found</h1>
12
- <p className="text-sm text-text-muted max-w-sm mx-auto">
13
- The page you're looking for doesn't exist or has been moved.
14
- </p>
11
+ <div className="space-y-2">
12
+ <h1 className="text-xl font-bold text-body">Page Not Found</h1>
13
+ <p className="text-sm text-muted leading-relaxed">
14
+ The page you're looking for doesn't exist or has been moved.
15
+ </p>
16
+ </div>
15
17
  <Link
16
18
  href="/"
17
- className="inline-flex items-center gap-2 rounded-lg bg-primary-500 px-5 py-2.5 text-sm font-semibold text-white outline-none transition-all hover:brightness-110 hover:shadow-lg focus-visible:ring-2 focus-visible:ring-primary-500/30"
19
+ className="inline-flex items-center gap-2 rounded-xl border border-subtle bg-main px-6 py-2.5 text-xs font-semibold text-body hover:bg-primary-50/50 hover:border-primary-500/50 transition-all duration-300 outline-none select-none"
18
20
  >
19
- <ArrowLeft size={16} /> Go to Home
21
+ <ArrowLeft size={14} /> Go to Home
20
22
  </Link>
21
23
  </div>
22
24
  </div>
@@ -1,123 +1,27 @@
1
- import {
2
- OnThisPage as OTP,
3
- AnchorProvider,
4
- ScrollProvider,
5
- useActiveAnchor,
6
- } from '../primitives/on-this-page'
7
- import { useRef, useEffect, useState, useCallback, useMemo } from 'react'
8
- import { useOnThisPage } from '../../hooks/use-onthispage'
1
+ import { OnThisPage as OTP } from '../primitives/on-this-page'
9
2
  import type { OnThisPageProps } from '../../types'
10
3
  import { Pencil, CircleHelp, TextAlignStart } from 'lucide-react'
11
4
 
12
5
  export function OnThisPage({
13
- headings: rawHeadings = [],
6
+ headings = [],
14
7
  editLink,
15
8
  communityHelp,
16
9
  filePath,
17
10
  }: OnThisPageProps) {
18
- const { headings } = useOnThisPage(rawHeadings)
19
-
20
- const toc = useMemo(
21
- () =>
22
- headings.map((h) => ({ title: h.text, url: `#${h.id}`, depth: h.level })),
23
- [headings],
24
- )
25
-
26
11
  if (headings.length === 0) return null
27
12
 
28
- return (
29
- <AnchorProvider toc={toc}>
30
- <OnThisPageInner
31
- headings={headings}
32
- editLink={editLink}
33
- communityHelp={communityHelp}
34
- filePath={filePath}
35
- />
36
- </AnchorProvider>
37
- )
38
- }
39
-
40
- function OnThisPageInner({
41
- headings,
42
- editLink,
43
- communityHelp,
44
- filePath,
45
- }: OnThisPageProps & {
46
- headings: { level: number; text: string; id: string }[]
47
- }) {
48
- const activeId = useActiveAnchor()
49
- const [indicatorStyle, setIndicatorStyle] = useState<React.CSSProperties>({
50
- opacity: 0,
51
- })
52
- const listRef = useRef<HTMLUListElement>(null)
53
- const scrollContainerRef = useRef<HTMLDivElement>(null)
54
-
55
- useEffect(() => {
56
- if (!activeId || !listRef.current) return
57
-
58
- const activeLink = listRef.current.querySelector(
59
- `a[href="#${activeId}"]`,
60
- ) as HTMLElement
61
-
62
- if (activeLink) {
63
- setIndicatorStyle({
64
- transform: `translateY(${activeLink.offsetTop}px)`,
65
- height: `${activeLink.offsetHeight}px`,
66
- opacity: 1,
67
- })
68
- }
69
- }, [activeId])
70
-
71
- const handleClick = useCallback(
72
- (e: React.MouseEvent<HTMLAnchorElement>, id: string) => {
73
- e.preventDefault()
74
- const el = document.getElementById(id)
75
-
76
- if (el) {
77
- el.scrollIntoView({ behavior: 'smooth' })
78
- window.history.pushState(null, '', `#${id}`)
79
- }
80
- },
81
- [],
82
- )
83
-
84
13
  return (
85
14
  <OTP.Root>
86
15
  <OTP.Header className="flex flex-row gap-x-2">
87
16
  <TextAlignStart size={16} />
88
17
  On this page
89
18
  </OTP.Header>
90
- <ScrollProvider containerRef={scrollContainerRef}>
91
- <OTP.Content
92
- className="max-h-[450px] boltdocs-otp-scroll-area"
93
- ref={scrollContainerRef}
94
- >
95
- <OTP.Indicator style={indicatorStyle} />
96
- <ul
97
- className="relative space-y-2 border-l border-border-subtle"
98
- ref={listRef}
99
- >
100
- {headings.map((h) => (
101
- <OTP.Item key={h.id} level={h.level}>
102
- <OTP.Link
103
- href={`#${h.id}`}
104
- active={activeId === h.id}
105
- onClick={(e) => handleClick(e, h.id)}
106
- className="pl-4"
107
- >
108
- {h.text}
109
- </OTP.Link>
110
- </OTP.Item>
111
- ))}
112
- </ul>
113
- </OTP.Content>
114
- </ScrollProvider>
19
+
20
+ <OTP.Tree headings={headings} />
115
21
 
116
22
  {(editLink || communityHelp) && (
117
- <div className="mt-8 pt-8 border-t border-border-subtle space-y-4">
118
- <p className="text-xs font-bold uppercase text-text-main">
119
- Need help?
120
- </p>
23
+ <div className="mt-8 pt-8 border-t border-subtle space-y-4">
24
+ <p className="text-xs font-bold text-body">Need help?</p>
121
25
  <ul className="space-y-3">
122
26
  {editLink && filePath && (
123
27
  <li>
@@ -125,7 +29,7 @@ function OnThisPageInner({
125
29
  href={editLink.replace(':path', filePath)}
126
30
  target="_blank"
127
31
  rel="noopener noreferrer"
128
- className="flex items-center gap-2 text-sm text-text-muted hover:text-text-main transition-colors"
32
+ className="flex items-center gap-2 text-sm text-muted hover:text-body transition-colors"
129
33
  >
130
34
  <Pencil size={16} />
131
35
  Edit this page
@@ -138,7 +42,7 @@ function OnThisPageInner({
138
42
  href={communityHelp}
139
43
  target="_blank"
140
44
  rel="noopener noreferrer"
141
- className="flex items-center gap-2 text-sm text-text-muted hover:text-text-main transition-colors"
45
+ className="flex items-center gap-2 text-sm text-muted hover:text-body transition-colors"
142
46
  >
143
47
  <CircleHelp size={16} />
144
48
  Community help
@@ -3,7 +3,7 @@ import { PageNav as PageNavPrimitive } from '../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
+ * Enhanced with subtle entrance animations, modern card layout, and hover highlights.
7
7
  */
8
8
  export function PageNav() {
9
9
  const { prevPage, nextPage } = usePageNav()
@@ -11,11 +11,17 @@ export function PageNav() {
11
11
  if (!prevPage && !nextPage) return null
12
12
 
13
13
  return (
14
- <PageNavPrimitive.Root className="animate-in fade-in slide-in-from-bottom-4 duration-700">
14
+ <PageNavPrimitive.Root className="pt-8 border-t border-subtle grid sm:grid-cols-2 gap-4 animate-in fade-in slide-in-from-bottom-4 duration-700 select-none">
15
15
  {prevPage ? (
16
- <PageNavPrimitive.Link to={prevPage.path} direction="prev">
17
- <PageNavPrimitive.Title>Previous</PageNavPrimitive.Title>
18
- <PageNavPrimitive.Description>
16
+ <PageNavPrimitive.Link
17
+ to={prevPage.path}
18
+ direction="prev"
19
+ className="group border border-subtle bg-surface p-5 rounded-2xl transition-all duration-300 hover:border-primary-500/50 hover:bg-primary-50/20"
20
+ >
21
+ <PageNavPrimitive.Title className="text-xs font-bold uppercase tracking-wider text-muted/60 mb-1">
22
+ Previous
23
+ </PageNavPrimitive.Title>
24
+ <PageNavPrimitive.Description className="text-sm sm:text-base font-bold text-body group-hover:text-primary-500 transition-colors">
19
25
  {prevPage.title}
20
26
  </PageNavPrimitive.Description>
21
27
  </PageNavPrimitive.Link>
@@ -23,13 +29,21 @@ export function PageNav() {
23
29
  <div />
24
30
  )}
25
31
 
26
- {nextPage && (
27
- <PageNavPrimitive.Link to={nextPage.path} direction="next">
28
- <PageNavPrimitive.Title>Next</PageNavPrimitive.Title>
29
- <PageNavPrimitive.Description>
32
+ {nextPage ? (
33
+ <PageNavPrimitive.Link
34
+ to={nextPage.path}
35
+ direction="next"
36
+ className="group border border-subtle bg-surface p-5 rounded-2xl transition-all duration-300 hover:border-primary-500/50 hover:bg-primary-50/20"
37
+ >
38
+ <PageNavPrimitive.Title className="text-xs font-bold uppercase tracking-wider text-muted/60 mb-1">
39
+ Next
40
+ </PageNavPrimitive.Title>
41
+ <PageNavPrimitive.Description className="text-sm sm:text-base font-bold text-body group-hover:text-primary-500 transition-colors">
30
42
  {nextPage.title}
31
43
  </PageNavPrimitive.Description>
32
44
  </PageNavPrimitive.Link>
45
+ ) : (
46
+ <div />
33
47
  )}
34
48
  </PageNavPrimitive.Root>
35
49
  )
@@ -1,9 +1,11 @@
1
1
  import { useEffect, useCallback } from 'react'
2
+ import { Search } from 'lucide-react'
2
3
  import { useSearch } from '../../hooks/use-search'
3
4
  import { SearchDialog as SearchDialogPrimitive } from '../primitives/search-dialog'
4
5
  import Navbar from '../primitives/navbar'
5
6
  import { useNavigate } from 'react-router-dom'
6
7
  import type { ComponentRoute } from '../../types'
8
+
7
9
  interface SearchResult {
8
10
  id: string
9
11
  title: string
@@ -13,6 +15,29 @@ interface SearchResult {
13
15
  isHeading?: boolean
14
16
  }
15
17
 
18
+ function Highlight({ text, query }: { text: string; query: string }) {
19
+ if (!query || !text) return <>{text}</>
20
+ const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
21
+ const regex = new RegExp(`(${escapedQuery})`, 'gi')
22
+ const parts = text.split(regex)
23
+ return (
24
+ <>
25
+ {parts.map((part, i) =>
26
+ regex.test(part) ? (
27
+ <mark
28
+ key={i}
29
+ className="bg-primary-500/20 text-primary-600 dark:text-primary-400 font-bold px-0.5 rounded-sm"
30
+ >
31
+ {part}
32
+ </mark>
33
+ ) : (
34
+ part
35
+ ),
36
+ )}
37
+ </>
38
+ )
39
+ }
40
+
16
41
  export function SearchDialog({ routes }: { routes: ComponentRoute[] }) {
17
42
  const { isOpen, setIsOpen, query, setQuery, list } = useSearch(routes)
18
43
  const navigate = useNavigate()
@@ -36,53 +61,103 @@ export function SearchDialog({ routes }: { routes: ComponentRoute[] }) {
36
61
  const path = String(key)
37
62
  setIsOpen(false)
38
63
 
39
- if (path.includes('#')) {
40
- const [p, id] = path.split('#')
41
- navigate(p)
64
+ const [baseUrl, hash] = path.split('#')
65
+ const search = query ? `?hl=${encodeURIComponent(query)}` : ''
66
+ const finalPath = `${baseUrl}${search}${hash ? `#${hash}` : ''}`
67
+
68
+ navigate(finalPath)
69
+
70
+ if (hash) {
42
71
  setTimeout(() => {
43
- const el = document.getElementById(id)
72
+ const el = document.getElementById(hash)
44
73
  if (el) el.scrollIntoView({ behavior: 'smooth' })
45
74
  }, 100)
46
- } else {
47
- navigate(path)
48
75
  }
49
76
  },
50
- [navigate, setIsOpen],
77
+ [navigate, setIsOpen, query],
51
78
  )
52
79
 
53
80
  return (
54
81
  <>
55
- <Navbar.SearchTrigger onPress={() => setIsOpen(true)} />
82
+ <Navbar.SearchTrigger.Desktop
83
+ onPress={() => setIsOpen(true)}
84
+ className="rounded-xl border border-subtle bg-surface text-muted transition-all duration-200 hover:border-primary-500/50 hover:text-body hover:bg-soft/50 hover:shadow-sm active:scale-[0.98] focus-visible:ring-2 focus-visible:ring-primary-500/30"
85
+ >
86
+ <div className="flex items-center gap-2">
87
+ <Search size={16} />
88
+ <span className="hidden sm:inline-block">Search docs...</span>
89
+ </div>
90
+ <Navbar.SearchTrigger.Kbd className="[&_kbd]:bg-main [&_kbd]:border [&_kbd]:border-subtle [&_kbd]:rounded [&_kbd]:px-1.5 [&_kbd]:h-5 [&_kbd]:w-5" />
91
+ </Navbar.SearchTrigger.Desktop>
56
92
 
57
- <SearchDialogPrimitive.Root isOpen={isOpen} onOpenChange={setIsOpen}>
58
- <SearchDialogPrimitive.Autocomplete onSelectionChange={handleSelect}>
59
- <SearchDialogPrimitive.Input
60
- value={query}
61
- onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
62
- setQuery(e.target.value)
63
- }
64
- />
65
- <SearchDialogPrimitive.List items={list as SearchResult[]}>
66
- {(item: SearchResult) => (
67
- <SearchDialogPrimitive.Item
68
- key={item.id}
69
- onPress={() => handleSelect(item.id)}
70
- textValue={item.title}
93
+ <Navbar.SearchTrigger.Mobile
94
+ onPress={() => setIsOpen(true)}
95
+ className="rounded-xl text-muted transition-all duration-200 hover:text-body active:scale-95 focus-visible:ring-2 focus-visible:ring-primary-500/30"
96
+ >
97
+ <Search size={20} />
98
+ </Navbar.SearchTrigger.Mobile>
99
+
100
+ <SearchDialogPrimitive.Overlay
101
+ isOpen={isOpen}
102
+ isDismissable
103
+ onOpenChange={() => setIsOpen(false)}
104
+ className="fixed inset-0 z-100 flex items-center justify-center p-4 bg-black/40 backdrop-blur-xs animate-fade-in"
105
+ >
106
+ <SearchDialogPrimitive.Content className="w-full max-w-lg bg-main border border-subtle shadow-md rounded-2xl overflow-hidden p-6">
107
+ <SearchDialogPrimitive.Dialog
108
+ aria-label="Search documentation"
109
+ className="flex flex-col min-h-0 h-[450px]"
110
+ >
111
+ <SearchDialogPrimitive.Autocomplete
112
+ onSelectionChange={handleSelect}
113
+ className="flex flex-col min-h-0"
114
+ >
115
+ <SearchDialogPrimitive.Input
116
+ value={query}
117
+ onChange={setQuery}
118
+ className="flex items-center gap-2 border border-subtle bg-surface px-4 py-2.5 rounded-xl focus-within:border-primary-500 mb-4"
71
119
  >
72
- <SearchDialogPrimitive.Item.Icon isHeading={item.isHeading} />
73
- <div className="flex flex-col justify-center gap-0.5">
74
- <SearchDialogPrimitive.Item.Title>
75
- {item.title}
76
- </SearchDialogPrimitive.Item.Title>
77
- <SearchDialogPrimitive.Item.Bio>
78
- {item.bio}
79
- </SearchDialogPrimitive.Item.Bio>
80
- </div>
81
- </SearchDialogPrimitive.Item>
82
- )}
83
- </SearchDialogPrimitive.List>
84
- </SearchDialogPrimitive.Autocomplete>
85
- </SearchDialogPrimitive.Root>
120
+ <SearchDialogPrimitive.Input.SearchInput
121
+ placeholder="Search documentation..."
122
+ className="w-full bg-transparent outline-none text-body text-sm"
123
+ />
124
+ {query && (
125
+ <SearchDialogPrimitive.Input.Button
126
+ onPress={() => setQuery('')}
127
+ className="text-muted hover:text-body text-xs cursor-pointer select-none"
128
+ >
129
+
130
+ </SearchDialogPrimitive.Input.Button>
131
+ )}
132
+ </SearchDialogPrimitive.Input>
133
+
134
+ <SearchDialogPrimitive.List items={list as SearchResult[]}>
135
+ {(item: SearchResult) => (
136
+ <SearchDialogPrimitive.Item
137
+ key={item.id}
138
+ onPress={() => handleSelect(item.id)}
139
+ textValue={item.title}
140
+ className="flex items-center gap-3 px-4 py-2 rounded-xl group dark:hover:bg-primary-300/40 hover:bg-primary-200/50 transition-colors duration-100"
141
+ >
142
+ <SearchDialogPrimitive.Item.Icon
143
+ isHeading={item.isHeading}
144
+ className="text-muted group-hover:text-primary-500 group-focus:text-primary-500"
145
+ />
146
+ <div className="flex flex-col justify-center min-w-0">
147
+ <SearchDialogPrimitive.Item.Title className="text-sm font-medium text-body truncate dark:group-hover:text-primary-100">
148
+ <Highlight text={item.title} query={query} />
149
+ </SearchDialogPrimitive.Item.Title>
150
+ <SearchDialogPrimitive.Item.Bio className="text-xs text-muted truncate">
151
+ <Highlight text={item.bio} query={query} />
152
+ </SearchDialogPrimitive.Item.Bio>
153
+ </div>
154
+ </SearchDialogPrimitive.Item>
155
+ )}
156
+ </SearchDialogPrimitive.List>
157
+ </SearchDialogPrimitive.Autocomplete>
158
+ </SearchDialogPrimitive.Dialog>
159
+ </SearchDialogPrimitive.Content>
160
+ </SearchDialogPrimitive.Overlay>
86
161
  </>
87
162
  )
88
163
  }
@@ -0,0 +1,10 @@
1
+ import { useSearchHighlight } from '../../hooks/use-search-highlight'
2
+
3
+ /**
4
+ * Component that enables search term highlighting on the page.
5
+ * It doesn't render anything visible.
6
+ */
7
+ export function SearchHighlight() {
8
+ useSearchHighlight('.boltdocs-page')
9
+ return null
10
+ }