boltdocs 2.3.0 → 2.4.2

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 (93) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/base-ui/index.d.mts +3 -3
  3. package/dist/base-ui/index.d.ts +3 -3
  4. package/dist/base-ui/index.js +1 -1
  5. package/dist/base-ui/index.mjs +1 -1
  6. package/dist/chunk-2DI3OGHV.mjs +1 -0
  7. package/dist/chunk-64AJ5QLT.mjs +1 -0
  8. package/dist/chunk-DDX52BX4.mjs +1 -0
  9. package/dist/chunk-HRZDSFR5.mjs +1 -0
  10. package/dist/chunk-PPVDMDEL.mjs +1 -0
  11. package/dist/{chunk-HA6543SL.mjs → chunk-UBE4CKOA.mjs} +1 -1
  12. package/dist/chunk-UWT4AJTH.mjs +73 -0
  13. package/dist/chunk-WWJ7WKDI.mjs +1 -0
  14. package/dist/client/index.d.mts +15 -21
  15. package/dist/client/index.d.ts +15 -21
  16. package/dist/client/index.js +1 -1
  17. package/dist/client/index.mjs +1 -1
  18. package/dist/client/ssr.js +1 -1
  19. package/dist/client/ssr.mjs +1 -1
  20. package/dist/client/types.d.mts +1 -1
  21. package/dist/client/types.d.ts +1 -1
  22. package/dist/client/types.js +1 -1
  23. package/dist/{copy-markdown-CbS8X-qe.d.mts → copy-markdown--9yjpbyy.d.mts} +1 -1
  24. package/dist/{copy-markdown-C-90ixSe.d.ts → copy-markdown-l2MYkcG7.d.ts} +1 -1
  25. package/dist/hooks/index.d.mts +3 -3
  26. package/dist/hooks/index.d.ts +3 -3
  27. package/dist/hooks/index.js +1 -1
  28. package/dist/hooks/index.mjs +1 -1
  29. package/dist/integrations/index.d.mts +1 -1
  30. package/dist/integrations/index.d.ts +1 -1
  31. package/dist/{loading-B7X5Wchs.d.ts → loading-BwUos0wZ.d.mts} +2 -11
  32. package/dist/{loading-WuaQbsKb.d.mts → loading-nlnUD01v.d.ts} +2 -11
  33. package/dist/mdx/index.d.mts +4 -2
  34. package/dist/mdx/index.d.ts +4 -2
  35. package/dist/mdx/index.js +1 -1
  36. package/dist/mdx/index.mjs +1 -1
  37. package/dist/node/cli-entry.js +16 -17
  38. package/dist/node/cli-entry.mjs +1 -1
  39. package/dist/node/index.d.mts +0 -9
  40. package/dist/node/index.d.ts +0 -9
  41. package/dist/node/index.js +13 -14
  42. package/dist/node/index.mjs +1 -1
  43. package/dist/primitives/index.d.mts +11 -20
  44. package/dist/primitives/index.d.ts +11 -20
  45. package/dist/primitives/index.js +1 -1
  46. package/dist/primitives/index.mjs +1 -1
  47. package/dist/search-dialog-OONKKC5H.mjs +1 -0
  48. package/dist/{types-j7jvWsJj.d.ts → types-opDA2E9-.d.mts} +4 -11
  49. package/dist/{types-j7jvWsJj.d.mts → types-opDA2E9-.d.ts} +4 -11
  50. package/dist/{use-routes-Cd806kGw.d.ts → use-routes-DNwgTRpU.d.ts} +1 -1
  51. package/dist/{use-routes-DDL0_jkQ.d.mts → use-routes-DrT80Eom.d.mts} +1 -1
  52. package/package.json +1 -1
  53. package/src/client/app/index.tsx +20 -9
  54. package/src/client/app/mdx-components-context.tsx +2 -2
  55. package/src/client/app/mdx-page.tsx +0 -1
  56. package/src/client/app/scroll-handler.tsx +21 -10
  57. package/src/client/components/default-layout.tsx +0 -2
  58. package/src/client/components/docs-layout.tsx +34 -4
  59. package/src/client/components/icons-dev.tsx +154 -0
  60. package/src/client/components/mdx/code-block.tsx +57 -5
  61. package/src/client/components/mdx/component-preview.tsx +1 -0
  62. package/src/client/components/mdx/file-tree.tsx +35 -0
  63. package/src/client/components/primitives/helpers/observer.ts +30 -39
  64. package/src/client/components/primitives/index.ts +1 -0
  65. package/src/client/components/primitives/menu.tsx +18 -12
  66. package/src/client/components/primitives/navbar.tsx +31 -90
  67. package/src/client/components/primitives/on-this-page.tsx +7 -161
  68. package/src/client/components/primitives/popover.tsx +1 -2
  69. package/src/client/components/ui-base/copy-markdown.tsx +4 -10
  70. package/src/client/components/ui-base/index.ts +0 -1
  71. package/src/client/components/ui-base/navbar.tsx +10 -9
  72. package/src/client/hooks/use-navbar.ts +37 -6
  73. package/src/client/index.ts +0 -1
  74. package/src/client/theme/neutral.css +2 -3
  75. package/src/client/types.ts +2 -2
  76. package/src/node/config.ts +0 -14
  77. package/src/node/mdx/cache.ts +1 -1
  78. package/src/node/mdx/index.ts +2 -0
  79. package/src/node/mdx/rehype-shiki.ts +9 -0
  80. package/src/node/mdx/remark-code-meta.ts +35 -0
  81. package/src/node/mdx/remark-shiki.ts +1 -1
  82. package/src/node/plugin/entry.ts +21 -14
  83. package/src/node/plugin/index.ts +22 -4
  84. package/src/node/routes/parser.ts +3 -0
  85. package/src/node/ssg/index.ts +76 -16
  86. package/dist/chunk-22NXDNP4.mjs +0 -74
  87. package/dist/chunk-2HUVMMJU.mjs +0 -1
  88. package/dist/chunk-CRZGOE32.mjs +0 -1
  89. package/dist/chunk-RPUERTVC.mjs +0 -1
  90. package/dist/chunk-URTD6E6S.mjs +0 -1
  91. package/dist/chunk-W2NB4T6V.mjs +0 -1
  92. package/dist/search-dialog-ZRXBAQJ5.mjs +0 -1
  93. package/src/client/components/ui-base/progress-bar.tsx +0 -67
@@ -72,3 +72,157 @@ export const Bluesky = (props: WrapperProps) => (
72
72
  <path d="M5.202 2.857C7.954 4.922 10.913 9.11 12 11.358c1.087-2.247 4.046-6.436 6.798-8.501C20.783 1.366 24 .213 24 3.883c0 .732-.42 6.156-.667 7.037-.856 3.061-3.978 3.842-6.755 3.37 4.854.826 6.089 3.562 3.422 6.299-5.065 5.196-7.28-1.304-7.847-2.97-.104-.305-.152-.448-.153-.327 0-.121-.05.022-.153.327-.568 1.666-2.782 8.166-7.847 2.97-2.667-2.737-1.432-5.473 3.422-6.3-2.777.473-5.899-.308-6.755-3.369C.42 10.04 0 4.615 0 3.883c0-3.67 3.217-2.517 5.202-1.026" />
73
73
  </svg>
74
74
  )
75
+
76
+ // Icons file
77
+
78
+ export const TypeScript = (props: WrapperProps) => (
79
+ <svg
80
+ xmlns="http://www.w3.org/2000/svg"
81
+ fill="none"
82
+ viewBox="0 0 24 24"
83
+ {...wrapperProps(props)}
84
+ >
85
+ <title>{'TypeScript'}</title>
86
+ <path
87
+ fill="#2563EB"
88
+ d="M3.234 9.093V7.318h8.363v1.775H8.479V17.5H6.352V9.093H3.234zm15.263 1.153c-.04-.4-.21-.712-.512-.934-.301-.222-.71-.333-1.228-.333-.351 0-.648.05-.89.149-.242.096-.427.23-.557.403a.969.969 0 0 0-.189.586.838.838 0 0 0 .115.477c.086.136.204.254.353.353.149.097.321.181.517.254.195.07.404.13.626.179l.915.219c.444.1.852.232 1.223.397.371.166.693.37.965.612.271.242.482.527.631.855.152.328.23.704.234 1.129-.004.623-.163 1.163-.478 1.62-.311.454-.762.807-1.352 1.06-.587.248-1.294.372-2.123.372-.822 0-1.538-.126-2.147-.378-.607-.252-1.081-.624-1.422-1.118-.338-.497-.516-1.112-.532-1.845h2.083c.023.342.12.627.293.855.176.226.41.397.701.513a2.8 2.8 0 0 0 1 .168c.364 0 .68-.053.949-.159a1.45 1.45 0 0 0 .631-.442c.15-.189.224-.406.224-.651a.846.846 0 0 0-.204-.577c-.132-.156-.328-.288-.586-.398a5.964 5.964 0 0 0-.94-.298l-1.109-.278c-.858-.21-1.536-.536-2.033-.98-.497-.444-.744-1.042-.74-1.795-.004-.616.16-1.155.491-1.615.335-.461.794-.82 1.377-1.08.584-.258 1.247-.387 1.99-.387.755 0 1.414.13 1.978.388.567.258 1.007.618 1.322 1.079.315.46.477.994.488 1.6h-2.064z"
89
+ />
90
+ </svg>
91
+ )
92
+
93
+ export const JavaScript = (props: WrapperProps) => (
94
+ <svg
95
+ xmlns="http://www.w3.org/2000/svg"
96
+ fill="none"
97
+ viewBox="0 0 24 24"
98
+ {...wrapperProps(props)}
99
+ >
100
+ <title>{'JavaScript'}</title>
101
+ <path
102
+ fill="#F59E0B"
103
+ d="M8.383 7.318h2.127v7.1c0 .656-.147 1.226-.442 1.71a2.924 2.924 0 01-1.218 1.118c-.52.262-1.125.393-1.815.393-.613 0-1.17-.107-1.67-.323a2.67 2.67 0 01-1.183-.994c-.292-.448-.436-1.01-.433-1.686h2.143c.006.269.061.5.164.691.106.19.25.335.432.438.186.1.405.15.657.15.265 0 .488-.057.67-.17.186-.116.327-.285.423-.507.096-.222.145-.496.145-.82v-7.1zm9.43 2.928c-.04-.4-.21-.712-.511-.934-.302-.222-.711-.333-1.228-.333-.352 0-.648.05-.89.149-.242.096-.428.23-.557.403a.969.969 0 00-.19.586.838.838 0 00.115.477c.087.136.204.254.353.353.15.097.322.181.517.254.196.07.405.13.627.179l.915.219c.444.1.851.232 1.223.397.37.166.692.37.964.612s.482.527.631.855a2.7 2.7 0 01.234 1.129c-.003.623-.162 1.163-.477 1.62-.312.454-.763.807-1.353 1.06-.586.248-1.294.372-2.122.372-.822 0-1.538-.126-2.148-.378-.607-.252-1.08-.624-1.422-1.118-.338-.497-.515-1.112-.532-1.845h2.083c.023.342.121.627.293.855.176.226.41.397.702.513.295.112.628.168.999.168.364 0 .68-.053.95-.159.271-.106.482-.253.63-.442.15-.189.224-.406.224-.651a.846.846 0 00-.203-.577c-.133-.156-.329-.288-.587-.398a5.964 5.964 0 00-.94-.298l-1.108-.278c-.859-.21-1.537-.536-2.034-.98-.497-.444-.744-1.042-.74-1.795-.004-.616.16-1.155.492-1.615.334-.461.793-.82 1.377-1.08.583-.258 1.246-.387 1.989-.387.755 0 1.415.13 1.978.388.567.258 1.008.618 1.323 1.079.314.46.477.994.487 1.6h-2.063z"
104
+ ></path>
105
+ </svg>
106
+ )
107
+
108
+ export const Json = (props: WrapperProps) => (
109
+ <svg
110
+ xmlns="http://www.w3.org/2000/svg"
111
+ fill="none"
112
+ viewBox="0 0 24 24"
113
+ {...wrapperProps(props)}
114
+ >
115
+ <title>{'JSON'}</title>
116
+ <path
117
+ fill="#F59E0B"
118
+ d="M4.778 6.667A2.667 2.667 0 017.444 4a.889.889 0 010 1.778.889.889 0 00-.888.889v3.5c0 .701-.273 1.35-.73 1.833.457.483.73 1.132.73 1.832v3.501c0 .491.398.89.888.89a.889.889 0 010 1.777 2.667 2.667 0 01-2.666-2.667v-3.5a.889.889 0 00-.674-.863l-.43-.108a.889.889 0 010-1.724l.43-.108a.889.889 0 00.674-.862V6.667zm14.222 0A2.667 2.667 0 0016.333 4a.889.889 0 000 1.778c.491 0 .89.398.89.889v3.5c0 .701.272 1.35.729 1.833a2.664 2.664 0 00-.73 1.832v3.501a.889.889 0 01-.889.89.889.889 0 000 1.777A2.667 2.667 0 0019 17.333v-3.5c0-.408.278-.764.673-.863l.431-.108a.889.889 0 000-1.724l-.43-.108a.889.889 0 01-.674-.862V6.667z"
119
+ ></path>
120
+ </svg>
121
+ )
122
+
123
+ export const Css = (props: WrapperProps) => (
124
+ <svg
125
+ xmlns="http://www.w3.org/2000/svg"
126
+ fill="none"
127
+ viewBox="0 0 24 24"
128
+ {...wrapperProps(props)}
129
+ >
130
+ <title>{'CSS'}</title>
131
+ <path
132
+ fill="#0EA5E9"
133
+ d="M4.778 6.667A2.667 2.667 0 017.444 4a.889.889 0 010 1.778.889.889 0 00-.888.889v3.5c0 .701-.273 1.35-.73 1.833.457.483.73 1.132.73 1.832v3.501c0 .491.398.89.888.89a.889.889 0 010 1.777 2.667 2.667 0 01-2.666-2.667v-3.5a.889.889 0 00-.674-.863l-.43-.108a.889.889 0 010-1.724l.43-.108a.889.889 0 00.674-.862V6.667zm14.222 0A2.667 2.667 0 0016.333 4a.889.889 0 000 1.778c.491 0 .89.398.89.889v3.5c0 .701.272 1.35.729 1.833a2.664 2.664 0 00-.73 1.832v3.501a.889.889 0 01-.889.89.889.889 0 000 1.777A2.667 2.667 0 0019 17.333v-3.5c0-.408.278-.764.673-.863l.431-.108a.889.889 0 000-1.724l-.43-.108a.889.889 0 01-.674-.862V6.667z"
134
+ ></path>
135
+ </svg>
136
+ )
137
+
138
+ export const BracketsOrange = (props: WrapperProps) => (
139
+ <svg
140
+ xmlns="http://www.w3.org/2000/svg"
141
+ fill="none"
142
+ viewBox="0 0 24 24"
143
+ {...wrapperProps(props)}
144
+ >
145
+ <title>{'HTML'}</title>
146
+ <path
147
+ fill="#EA580C"
148
+ d="M4.778 6.667A2.667 2.667 0 017.444 4a.889.889 0 010 1.778.889.889 0 00-.888.889v3.5c0 .701-.273 1.35-.73 1.833.457.483.73 1.132.73 1.832v3.501c0 .491.398.89.888.89a.889.889 0 010 1.777 2.667 2.667 0 01-2.666-2.667v-3.5a.889.889 0 00-.674-.863l-.43-.108a.889.889 0 010-1.724l.43-.108a.889.889 0 00.674-.862V6.667zm14.222 0A2.667 2.667 0 0016.333 4a.889.889 0 000 1.778c.491 0 .89.398.89.889v3.5c0 .701.272 1.35.729 1.833a2.664 2.664 0 00-.73 1.832v3.501a.889.889 0 01-.889.89.889.889 0 000 1.777A2.667 2.667 0 0019 17.333v-3.5c0-.408.278-.764.673-.863l.431-.108a.889.889 0 000-1.724l-.43-.108a.889.889 0 01-.674-.862V6.667z"
149
+ ></path>
150
+ </svg>
151
+ )
152
+
153
+ export default BracketsOrange
154
+
155
+ export const React = (props: WrapperProps) => (
156
+ <svg
157
+ xmlns="http://www.w3.org/2000/svg"
158
+ fill="none"
159
+ viewBox="0 0 24 24"
160
+ {...wrapperProps(props)}
161
+ >
162
+ <title>{'React'}</title>
163
+ <path
164
+ fill="#0E8ADC"
165
+ d="M12 13.677a1.677 1.677 0 100-3.354 1.677 1.677 0 000 3.354z"
166
+ ></path>
167
+ <path
168
+ stroke="#0E8ADC"
169
+ d="M12 15.436c4.97 0 9-1.538 9-3.436s-4.03-3.436-9-3.436S3 10.102 3 12s4.03 3.436 9 3.436z"
170
+ ></path>
171
+ <path
172
+ stroke="#0E8ADC"
173
+ d="M9.024 13.718c2.485 4.305 5.832 7.025 7.476 6.076 1.644-.949.961-5.208-1.524-9.512C12.491 5.977 9.144 3.257 7.5 4.206c-1.644.949-.961 5.208 1.524 9.512z"
174
+ ></path>
175
+ <path
176
+ stroke="#0E8ADC"
177
+ d="M9.024 10.282c-2.485 4.304-3.168 8.563-1.524 9.512 1.644.95 4.99-1.771 7.476-6.076 2.485-4.304 3.168-8.563 1.524-9.512-1.644-.95-4.99 1.771-7.476 6.076z"
178
+ ></path>
179
+ </svg>
180
+ )
181
+
182
+ export const Markdown = (props: WrapperProps) => (
183
+ <svg
184
+ xmlns="http://www.w3.org/2000/svg"
185
+ fill="none"
186
+ viewBox="0 0 24 24"
187
+ {...wrapperProps(props)}
188
+ >
189
+ <title>{'Markdown'}</title>
190
+ <path
191
+ fill="#60A5FA"
192
+ d="M3 15.714V8h2.323l2.322 2.836L9.968 8h2.322v7.714H9.968V11.29l-2.323 2.836-2.322-2.836v4.424H3zm14.516 0l-3.484-3.743h2.323V8h2.322v3.97H21l-3.484 3.744z"
193
+ ></path>
194
+ </svg>
195
+ )
196
+
197
+ export const Shell = (props: WrapperProps) => (
198
+ <svg
199
+ xmlns="http://www.w3.org/2000/svg"
200
+ fill="none"
201
+ viewBox="0 0 25 24"
202
+ {...wrapperProps(props)}
203
+ >
204
+ <title>{'Shell'}</title>
205
+ <path
206
+ stroke="#14B8A6"
207
+ strokeLinecap="round"
208
+ strokeLinejoin="round"
209
+ strokeWidth="2"
210
+ d="M4.336 17l6-6-6-6M12.336 19h8"
211
+ ></path>
212
+ </svg>
213
+ )
214
+
215
+ export const Yaml = (props: WrapperProps) => (
216
+ <svg
217
+ xmlns="http://www.w3.org/2000/svg"
218
+ fill="none"
219
+ viewBox="0 0 24 24"
220
+ {...wrapperProps(props)}
221
+ >
222
+ <title>{'YAML'}</title>
223
+ <path
224
+ fill="#A78BFA"
225
+ d="M6.533 5.864h2.755l2.654 5.011h.113l2.654-5.011h2.756l-4.245 7.522V17.5h-2.443v-4.114L6.533 5.864z"
226
+ ></path>
227
+ </svg>
228
+ )
@@ -1,11 +1,38 @@
1
1
  import * as RAC from 'react-aria-components'
2
- import { Copy, Check } from 'lucide-react'
2
+ import { Copy, Check, File } from 'lucide-react'
3
3
  import { cn } from '@client/utils/cn'
4
4
  import { useCodeBlock } from './hooks/use-code-block'
5
5
  import { useConfig } from '@client/app/config-context'
6
- import { CodeSandbox } from '@components/icons-dev'
6
+ import {
7
+ CodeSandbox,
8
+ TypeScript,
9
+ JavaScript,
10
+ React as ReactIcon,
11
+ Json,
12
+ Css,
13
+ BracketsOrange,
14
+ Markdown,
15
+ Shell,
16
+ Yaml,
17
+ } from '@components/icons-dev'
7
18
  import { Tooltip } from '@components/primitives/tooltip'
8
19
 
20
+ const langIconMap: Record<string, React.ComponentType<{ size?: number }>> = {
21
+ ts: TypeScript,
22
+ tsx: ReactIcon,
23
+ js: JavaScript,
24
+ jsx: ReactIcon,
25
+ json: Json,
26
+ css: Css,
27
+ html: BracketsOrange,
28
+ md: Markdown,
29
+ mdx: Markdown,
30
+ bash: Shell,
31
+ sh: Shell,
32
+ yaml: Yaml,
33
+ yml: Yaml,
34
+ }
35
+
9
36
  export interface CodeBlockProps {
10
37
  children?: React.ReactNode
11
38
  className?: string
@@ -15,6 +42,8 @@ export interface CodeBlockProps {
15
42
  title?: string
16
43
  lang?: string
17
44
  highlightedHtml?: string
45
+ 'data-lang'?: string
46
+ plain?: boolean
18
47
  [key: string]: any
19
48
  }
20
49
 
@@ -25,11 +54,15 @@ export function CodeBlock(props: CodeBlockProps) {
25
54
  hideSandbox = true,
26
55
  hideCopy = false,
27
56
  highlightedHtml,
57
+ title,
58
+ 'data-lang': dataLang,
59
+ plain = false,
28
60
  ...rest
29
61
  } = props
30
62
  const config = useConfig()
31
63
  const globalSandbox = config?.integrations?.sandbox
32
64
  const isSandboxEnabled = !!globalSandbox?.enable && !hideSandbox
65
+ const lang = props.lang || dataLang || ''
33
66
  const {
34
67
  copied,
35
68
  isExpanded,
@@ -41,13 +74,32 @@ export function CodeBlock(props: CodeBlockProps) {
41
74
  shouldTruncate,
42
75
  } = useCodeBlock(props)
43
76
 
77
+ const LangIcon = langIconMap[lang]
78
+
44
79
  return (
45
80
  <div
46
81
  className={cn(
47
- 'group relative my-6 overflow-hidden rounded-lg border border-border-subtle bg-(--color-code-bg)',
48
- shouldTruncate && '[&>pre]:max-h-[250px] [&>pre]:overflow-hidden',
82
+ 'group relative overflow-hidden bg-(--color-code-bg)',
83
+ 'contain-layout contain-paint', // Optimization: isolate code block rendering
84
+ {
85
+ 'my-6 rounded-lg border border-border-subtle': !plain,
86
+ '[&>pre]:max-h-62.5 [&>pre]:overflow-hidden': shouldTruncate,
87
+ },
88
+ props.className,
49
89
  )}
50
90
  >
91
+ {/* Title Header */}
92
+ {title && (
93
+ <div className="flex items-center gap-2 border-b border-border-subtle bg-bg-surface/50 px-4 py-2 text-[13px] font-medium text-text-muted">
94
+ {LangIcon ? (
95
+ <LangIcon size={14} />
96
+ ) : (
97
+ <File size={14} className="opacity-60" />
98
+ )}
99
+ <span>{title}</span>
100
+ </div>
101
+ )}
102
+
51
103
  {/* Toolbar */}
52
104
  <div className="absolute top-3 right-4 z-50 flex items-center gap-2 transition-all duration-300 opacity-0 group-hover:opacity-100">
53
105
  {isSandboxEnabled && (
@@ -66,7 +118,7 @@ export function CodeBlock(props: CodeBlockProps) {
66
118
  <RAC.Button
67
119
  onPress={handleCopy}
68
120
  className={cn(
69
- 'grid place-items-center w-8 h-8 bg-transparent outline-none cursor-pointer transition-all duration-200 hover:scale-115 active:scale-95 [&>svg]:w-5 [&>svg]:h-5 [&>svg]:stroke-2',
121
+ 'grid place-items-center size-8 bg-transparent outline-none cursor-pointer transition-all duration-200 hover:scale-110 active:scale-95 [&>svg]:size-4 [&>svg]:stroke-2',
70
122
  copied
71
123
  ? 'text-emerald-400'
72
124
  : 'text-text-muted hover:text-text-main',
@@ -37,6 +37,7 @@ export function ComponentPreview(props: ComponentPreviewProps) {
37
37
  title={sandboxOptions.title}
38
38
  lang="tsx"
39
39
  highlightedHtml={highlightedHtml}
40
+ plain={true}
40
41
  >
41
42
  {initialCode}
42
43
  </CodeBlock>
@@ -10,11 +10,39 @@ import {
10
10
  } from 'lucide-react'
11
11
  import { cn } from '@client/utils/cn'
12
12
 
13
+ import {
14
+ TypeScript,
15
+ JavaScript,
16
+ React as ReactIcon,
17
+ Json,
18
+ Css,
19
+ BracketsOrange,
20
+ Markdown,
21
+ Shell,
22
+ Yaml,
23
+ } from '@components/icons-dev'
24
+
13
25
  // --- Constants & Types ---
14
26
 
15
27
  const ICON_SIZE = 16
16
28
  const STROKE_WIDTH = 2
17
29
 
30
+ const FILE_EXTENSION_MAP: Record<string, React.ComponentType<{ size?: number }>> = {
31
+ ts: TypeScript,
32
+ tsx: ReactIcon,
33
+ js: JavaScript,
34
+ jsx: ReactIcon,
35
+ json: Json,
36
+ css: Css,
37
+ html: BracketsOrange,
38
+ md: Markdown,
39
+ mdx: Markdown,
40
+ bash: Shell,
41
+ sh: Shell,
42
+ yaml: Yaml,
43
+ yml: Yaml,
44
+ }
45
+
18
46
  const FILE_REGEXES = {
19
47
  CODE: /\.(ts|tsx|js|jsx|json|mjs|cjs|astro|vue|svelte)$/i,
20
48
  TEXT: /\.(md|mdx|txt)$/i,
@@ -68,6 +96,13 @@ function getFileIcon(filename: string, isFolder: boolean) {
68
96
  )
69
97
  }
70
98
 
99
+ // Check for specialized language icons
100
+ const extension = name.split('.').pop() || ''
101
+ const LangIcon = FILE_EXTENSION_MAP[extension]
102
+ if (LangIcon) {
103
+ return <LangIcon size={ICON_SIZE} />
104
+ }
105
+
71
106
  const fileIconClass = cn(
72
107
  iconClass,
73
108
  'text-text-dim group-hover:text-text-main',
@@ -14,51 +14,42 @@ export class Observer {
14
14
  private callback(entries: IntersectionObserverEntry[]) {
15
15
  if (entries.length === 0) return
16
16
 
17
- let hasActive = false
18
- this.items = this.items.map((item) => {
19
- const entry = entries.find((entry) => entry.target.id === item.id)
20
- let active = entry ? entry.isIntersecting : item.active && !item.fallback
21
- if (this.single && hasActive) active = false
22
-
23
- if (item.active !== active) {
24
- item = {
25
- ...item,
26
- t: Date.now(),
27
- active,
28
- fallback: false,
29
- }
17
+ // 1. Update internal state based on current intersection and position
18
+ for (const entry of entries) {
19
+ const item = this.items.find((i) => i.id === entry.target.id)
20
+ if (item) {
21
+ // item.active will track if the heading is currently "on or below" the trigger line
22
+ item.active = entry.isIntersecting
23
+
24
+ // item.fallback will track if the heading has scrolled "above" the trigger line
25
+ // RootMargin top is -100px, so trigger line is at 100px.
26
+ const activationLine = 100
27
+ item.fallback =
28
+ !entry.isIntersecting && entry.boundingClientRect.top < activationLine
30
29
  }
30
+ }
31
31
 
32
- if (active) hasActive = true
33
- return item
34
- })
35
-
36
- if (!hasActive && entries[0].rootBounds) {
37
- const viewTop = entries[0].rootBounds.top
38
- let min = Number.MAX_VALUE
39
- let fallbackIdx = -1
40
-
41
- for (let i = 0; i < this.items.length; i++) {
42
- const element = document.getElementById(this.items[i].id)
43
- if (!element) continue
44
-
45
- const d = Math.abs(viewTop - element.getBoundingClientRect().top)
46
- if (d < min) {
47
- fallbackIdx = i
48
- min = d
49
- }
32
+ // 2. The active heading is the LAST one in document order that has scrolled past the line.
33
+ let highlightIdx = -1
34
+ for (let i = this.items.length - 1; i >= 0; i--) {
35
+ if (this.items[i].fallback) {
36
+ highlightIdx = i
37
+ break
50
38
  }
39
+ }
51
40
 
52
- if (fallbackIdx !== -1) {
53
- this.items[fallbackIdx] = {
54
- ...this.items[fallbackIdx],
55
- active: true,
56
- fallback: true,
57
- t: Date.now(),
58
- }
59
- }
41
+ // 3. Initial state: If no headings have passed the line yet, default to the first heading.
42
+ if (highlightIdx === -1 && this.items.length > 0) {
43
+ highlightIdx = 0
60
44
  }
61
45
 
46
+ // 4. Map back to UI state
47
+ this.items = this.items.map((item, idx) => ({
48
+ ...item,
49
+ active: idx === highlightIdx,
50
+ t: idx === highlightIdx ? Date.now() : item.t,
51
+ }))
52
+
62
53
  this.onChange?.()
63
54
  }
64
55
 
@@ -12,6 +12,7 @@ export * from './menu'
12
12
  export * from './popover'
13
13
  export * from './tooltip'
14
14
  export * from './link'
15
+ export * from './skeleton'
15
16
  export { Separator, ToggleButton } from 'react-aria-components'
16
17
 
17
18
  export { cn } from '../../utils/cn'
@@ -20,7 +20,7 @@ export function MenuTrigger(props: MenuTriggerProps) {
20
20
  return (
21
21
  <RAC.MenuTrigger {...props}>
22
22
  {trigger as any}
23
- <Popover placement={props.placement} className="min-w-[200px]">
23
+ <Popover placement={props.placement} className="min-w-35">
24
24
  {menu as any}
25
25
  </Popover>
26
26
  </RAC.MenuTrigger>
@@ -52,7 +52,10 @@ export function Menu<T extends object>(props: RAC.MenuProps<T>) {
52
52
  <RAC.Menu
53
53
  {...props}
54
54
  className={RAC.composeRenderProps(props.className, (className) =>
55
- cn('p-1.5 outline-none max-h-[inherit] overflow-auto', className),
55
+ cn(
56
+ 'p-1.5 outline-none max-h-[inherit] overflow-auto max-w-75',
57
+ className,
58
+ ),
56
59
  )}
57
60
  />
58
61
  )
@@ -74,11 +77,14 @@ export function MenuItem(props: RAC.MenuItemProps) {
74
77
  props.className,
75
78
  (className, { isFocused, isPressed, isDisabled }) =>
76
79
  cn(
77
- 'group relative flex flex-row items-center gap-2.5 px-3 py-1.5 rounded-lg outline-none cursor-default transition-all duration-200',
78
- 'text-text-main text-[0.8125rem]',
79
- isFocused && 'bg-primary-500/10 text-primary-600 shadow-sm',
80
- isPressed && 'scale-[0.98] bg-primary-500/15',
81
- isDisabled && 'opacity-40 grayscale pointer-events-none',
80
+ 'group relative flex flex-row items-center gap-2 px-2 py-1 rounded-lg outline-none cursor-default hover:cursor-pointer transition-none',
81
+ 'text-text-main text-[12px]',
82
+ {
83
+ 'bg-bg-surface-elevated text-primary-600 ring-1 ring-border-strong/5':
84
+ isFocused,
85
+ 'bg-bg-surface-elevanted': isPressed,
86
+ 'opacity-40 grayscale pointer-events-none': isDisabled,
87
+ },
82
88
  className,
83
89
  ),
84
90
  )}
@@ -88,20 +94,20 @@ export function MenuItem(props: RAC.MenuItemProps) {
88
94
  (children, { selectionMode, isSelected, hasSubmenu }) => (
89
95
  <>
90
96
  {selectionMode !== 'none' && (
91
- <span className="flex items-center w-4 h-4 shrink-0 justify-center">
97
+ <span className="flex items-center size-4 shrink-0 justify-center">
92
98
  {isSelected && selectionMode === 'multiple' && (
93
- <Check className="w-3.5 h-3.5 stroke-[2.5px] text-primary-500 animate-in zoom-in-50 duration-200" />
99
+ <Check className="size-3.5 stroke-[2.5px] text-primary-500 animate-in zoom-in-50 duration-200" />
94
100
  )}
95
101
  {isSelected && selectionMode === 'single' && (
96
- <Dot className="w-6 h-6 text-primary-500 animate-in zoom-in-50 duration-200" />
102
+ <Dot className="size-5 text-primary-500 animate-in zoom-in-50 duration-200" />
97
103
  )}
98
104
  </span>
99
105
  )}
100
- <div className="flex-1 flex flex-row items-center gap-2.5 truncate font-medium">
106
+ <div className="flex flex-row w-full transition-colors items-center gap-2 py-1 px-1">
101
107
  {children}
102
108
  </div>
103
109
  {hasSubmenu && (
104
- <ChevronRight className="w-4 h-4 ml-auto text-text-muted group-focused:text-primary-500/70 transition-colors" />
110
+ <ChevronRight className="size-4 ml-auto text-text-muted group-focused:text-primary-500/70 transition-colors" />
105
111
  )}
106
112
  </>
107
113
  ),
@@ -1,16 +1,7 @@
1
1
  import { type ReactNode, useState, useEffect } from 'react'
2
- import {
3
- Button,
4
- Separator,
5
- ToggleButton,
6
- Link,
7
- Menu,
8
- MenuItem,
9
- MenuTrigger,
10
- cn,
11
- } from './index'
2
+ import { Separator, ToggleButton, Link, cn } from './index'
12
3
  import { Button as ButtonRAC } from 'react-aria-components'
13
- import { Search, Sun, Moon, ExternalLink, ChevronDown } from 'lucide-react'
4
+ import { Search, Sun, Moon, ExternalLink } from 'lucide-react'
14
5
  import * as IconsSocials from '@components/icons-dev'
15
6
  import type { ComponentBase } from './types'
16
7
  import type { BoltdocsSocialLink } from '@node/config'
@@ -39,21 +30,6 @@ export interface NavbarThemeProps {
39
30
  onThemeChange: (isSelected: boolean) => void
40
31
  }
41
32
 
42
- export interface NavbarMenuProps extends ComponentBase {
43
- label: ReactNode
44
- icon?: ReactNode
45
- }
46
-
47
- export interface NavbarVersionProps extends ComponentBase {
48
- current: string
49
- }
50
-
51
- export interface NavbarItemProps extends Omit<ComponentBase, 'children'> {
52
- label: string
53
- onPress?: () => void
54
- isCurrent?: boolean
55
- }
56
-
57
33
  export interface NavbarSocialsProps extends ComponentBase {
58
34
  icon: string
59
35
  link: string
@@ -92,13 +68,25 @@ export const NavbarContent = ({ children, className }: ComponentBase) => {
92
68
 
93
69
  export const NavbarLeft = ({ children, className }: ComponentBase) => {
94
70
  return (
95
- <div className={cn('flex items-center gap-4', className)}>{children}</div>
71
+ <div
72
+ className={cn(
73
+ 'flex flex-1 items-center justify-start gap-4 min-w-0',
74
+ className,
75
+ )}
76
+ >
77
+ {children}
78
+ </div>
96
79
  )
97
80
  }
98
81
 
99
82
  export const NavbarRight = ({ children, className }: ComponentBase) => {
100
83
  return (
101
- <div className={cn('flex items-center gap-2 md:gap-4', className)}>
84
+ <div
85
+ className={cn(
86
+ 'flex flex-1 items-center justify-end gap-2 md:gap-4 min-w-0',
87
+ className,
88
+ )}
89
+ >
102
90
  {children}
103
91
  </div>
104
92
  )
@@ -108,7 +96,7 @@ export const NavbarCenter = ({ children, className }: ComponentBase) => {
108
96
  return (
109
97
  <div
110
98
  className={cn(
111
- 'hidden lg:flex flex-1 justify-center items-center gap-4 px-4',
99
+ 'hidden lg:flex flex-1 justify-center items-center gap-4 px-4 min-w-0 w-full',
112
100
  className,
113
101
  )}
114
102
  >
@@ -144,14 +132,16 @@ export const NavbarLogo = ({
144
132
 
145
133
  export const NavbarTitle = ({ children, className }: ComponentBase) => {
146
134
  return (
147
- <span
148
- className={cn(
149
- 'text-lg font-bold tracking-tight hidden sm:inline-block',
150
- className,
151
- )}
152
- >
153
- {children}
154
- </span>
135
+ <Link href="/">
136
+ <span
137
+ className={cn(
138
+ 'text-lg font-bold tracking-tight hidden sm:inline-block',
139
+ className,
140
+ )}
141
+ >
142
+ {children}
143
+ </span>
144
+ </Link>
155
145
  )
156
146
  }
157
147
 
@@ -180,10 +170,10 @@ export const NavbarLink = ({
180
170
  href={href}
181
171
  target={to === 'external' ? '_blank' : undefined}
182
172
  className={cn(
183
- 'transition-colors outline-none focus-visible:ring-2 focus-visible:ring-primary-500/30 rounded-sm',
173
+ 'transition-colors outline-none font-medium focus-visible:ring-2 focus-visible:ring-primary-500/30 rounded-sm',
184
174
  {
185
- 'text-primary-500 font-bold': active,
186
- 'text-text-muted hover:text-text-main font-medium': !active,
175
+ 'text-primary-500': active,
176
+ 'text-text-muted hover:text-text-main': !active,
187
177
  },
188
178
  className,
189
179
  )}
@@ -216,7 +206,7 @@ export const NavbarSearchTrigger = ({
216
206
  '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',
217
207
  'transition-all duration-200 hover:border-border-strong hover:text-text-main hover:bg-bg-muted hover:shadow-sm active:scale-[0.98]',
218
208
  'focus-visible:ring-2 focus-visible:ring-primary-500/30',
219
- 'w-full max-w-[320px] justify-between',
209
+ 'w-full max-w-[720px] justify-between',
220
210
  className,
221
211
  )}
222
212
  >
@@ -258,54 +248,6 @@ export const NavbarTheme = ({
258
248
  )
259
249
  }
260
250
 
261
- export const NavbarMenu = ({
262
- label,
263
- children,
264
- className,
265
- icon,
266
- }: NavbarMenuProps) => {
267
- return (
268
- <MenuTrigger placement="bottom end">
269
- <Button
270
- variant="ghost"
271
- className={cn(
272
- 'flex items-center gap-1.5 rounded-md px-3 py-1.5 text-text-muted outline-none cursor-pointer transition-colors',
273
- 'hover:bg-bg-surface hover:text-text-main',
274
- 'focus-visible:ring-2 focus-visible:ring-primary-500/30',
275
- className,
276
- )}
277
- >
278
- {icon && <span className="flex items-center shrink-0">{icon}</span>}
279
- <span className="text-[13px] font-bold uppercase tracking-wide">
280
- {label}
281
- </span>
282
- <ChevronDown size={14} className="ml-0.5 opacity-50" />
283
- </Button>
284
- <Menu className="min-w-[180px]">{children as any}</Menu>
285
- </MenuTrigger>
286
- )
287
- }
288
-
289
- export const NavbarItem = ({
290
- label,
291
- className,
292
- onPress,
293
- isCurrent,
294
- }: NavbarItemProps) => {
295
- return (
296
- <MenuItem
297
- onAction={onPress}
298
- className={cn(
299
- isCurrent &&
300
- 'bg-primary-500 text-white font-medium hover:bg-primary-600 focus:bg-primary-600 focus:text-white',
301
- className,
302
- )}
303
- >
304
- {label}
305
- </MenuItem>
306
- )
307
- }
308
-
309
251
  export const Icon = ({ name }: { name: BoltdocsSocialLink['icon'] }) => {
310
252
  if (name === 'github') return <IconsSocials.Github />
311
253
  if (name === 'discord') return <IconsSocials.Discord />
@@ -355,7 +297,6 @@ export default {
355
297
  Link: NavbarLink,
356
298
  SearchTrigger: NavbarSearchTrigger,
357
299
  Theme: NavbarTheme,
358
- Item: NavbarItem,
359
300
  Socials: NavbarSocials,
360
301
  Split: NavbarSplit,
361
302
  Content: NavbarContent,