boltdocs 1.10.2 → 1.11.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 (225) hide show
  1. package/package.json +29 -7
  2. package/src/client/app/config-context.tsx +18 -0
  3. package/src/client/app/docs-layout.tsx +14 -0
  4. package/src/client/app/index.tsx +132 -260
  5. package/src/client/app/mdx-component.tsx +52 -0
  6. package/src/client/app/mdx-components-context.tsx +23 -0
  7. package/src/client/app/mdx-page.tsx +20 -0
  8. package/src/client/app/preload.tsx +38 -30
  9. package/src/client/app/router.tsx +30 -0
  10. package/src/client/app/scroll-handler.tsx +40 -0
  11. package/src/client/app/theme-context.tsx +75 -0
  12. package/src/client/components/default-layout.tsx +80 -0
  13. package/src/client/components/docs-layout.tsx +105 -0
  14. package/src/client/components/icons-dev.tsx +74 -0
  15. package/src/client/components/mdx/admonition.tsx +107 -0
  16. package/src/client/components/mdx/badge.tsx +41 -0
  17. package/src/client/components/mdx/button.tsx +35 -0
  18. package/src/client/components/mdx/card.tsx +124 -0
  19. package/src/client/components/mdx/code-block.tsx +119 -0
  20. package/src/client/components/mdx/component-preview.tsx +47 -0
  21. package/src/client/components/mdx/component-props.tsx +83 -0
  22. package/src/client/components/mdx/field.tsx +66 -0
  23. package/src/client/components/mdx/file-tree.tsx +287 -0
  24. package/src/client/components/mdx/hooks/use-code-block.ts +56 -0
  25. package/src/client/components/mdx/hooks/use-component-preview.ts +16 -0
  26. package/src/client/components/mdx/hooks/useTable.ts +74 -0
  27. package/src/client/components/mdx/hooks/useTabs.ts +68 -0
  28. package/src/client/components/mdx/image.tsx +23 -0
  29. package/src/client/components/mdx/index.ts +53 -0
  30. package/src/client/components/mdx/link.tsx +38 -0
  31. package/src/client/components/mdx/list.tsx +192 -0
  32. package/src/client/components/mdx/table.tsx +156 -0
  33. package/src/client/components/mdx/tabs.tsx +135 -0
  34. package/src/client/components/mdx/video.tsx +68 -0
  35. package/src/client/components/primitives/breadcrumbs.tsx +79 -0
  36. package/src/client/components/primitives/button-group.tsx +54 -0
  37. package/src/client/components/primitives/button.tsx +145 -0
  38. package/src/client/components/primitives/helpers/observer.ts +120 -0
  39. package/src/client/components/primitives/index.ts +17 -0
  40. package/src/client/components/primitives/link.tsx +122 -0
  41. package/src/client/components/primitives/menu.tsx +159 -0
  42. package/src/client/components/primitives/navbar.tsx +359 -0
  43. package/src/client/components/primitives/navigation-menu.tsx +116 -0
  44. package/src/client/components/primitives/on-this-page.tsx +461 -0
  45. package/src/client/components/primitives/page-nav.tsx +87 -0
  46. package/src/client/components/primitives/popover.tsx +47 -0
  47. package/src/client/components/primitives/search-dialog.tsx +183 -0
  48. package/src/client/components/primitives/sidebar.tsx +154 -0
  49. package/src/client/components/primitives/tabs.tsx +90 -0
  50. package/src/client/components/primitives/tooltip.tsx +83 -0
  51. package/src/client/components/primitives/types.ts +11 -0
  52. package/src/client/components/ui-base/breadcrumbs.tsx +42 -0
  53. package/src/client/components/ui-base/copy-markdown.tsx +112 -0
  54. package/src/client/components/ui-base/error-boundary.tsx +52 -0
  55. package/src/client/components/ui-base/github-stars.tsx +27 -0
  56. package/src/client/components/ui-base/head.tsx +69 -0
  57. package/src/client/components/ui-base/loading.tsx +87 -0
  58. package/src/client/components/ui-base/navbar.tsx +138 -0
  59. package/src/client/components/ui-base/not-found.tsx +24 -0
  60. package/src/client/components/ui-base/on-this-page.tsx +152 -0
  61. package/src/client/components/ui-base/page-nav.tsx +39 -0
  62. package/src/client/components/ui-base/powered-by.tsx +19 -0
  63. package/src/client/components/ui-base/progress-bar.tsx +67 -0
  64. package/src/client/components/ui-base/search-dialog.tsx +82 -0
  65. package/src/client/components/ui-base/sidebar.tsx +104 -0
  66. package/src/client/components/ui-base/tabs.tsx +65 -0
  67. package/src/client/components/ui-base/theme-toggle.tsx +32 -0
  68. package/src/client/hooks/index.ts +12 -0
  69. package/src/client/hooks/use-breadcrumbs.ts +22 -0
  70. package/src/client/hooks/use-i18n.ts +84 -0
  71. package/src/client/hooks/use-localized-to.ts +95 -0
  72. package/src/client/hooks/use-location.ts +5 -0
  73. package/src/client/hooks/use-navbar.ts +60 -0
  74. package/src/client/hooks/use-onthispage.ts +23 -0
  75. package/src/client/hooks/use-page-nav.ts +22 -0
  76. package/src/client/hooks/use-routes.ts +72 -0
  77. package/src/client/hooks/use-search.ts +71 -0
  78. package/src/client/hooks/use-sidebar.ts +49 -0
  79. package/src/client/hooks/use-tabs.ts +43 -0
  80. package/src/client/hooks/use-version.ts +78 -0
  81. package/src/client/index.ts +55 -17
  82. package/src/client/integrations/codesandbox.ts +179 -0
  83. package/src/client/ssr.tsx +27 -16
  84. package/src/client/theme/neutral.css +360 -0
  85. package/src/client/types.ts +131 -27
  86. package/src/client/utils/cn.ts +6 -0
  87. package/src/client/utils/copy-clipboard.ts +22 -0
  88. package/src/client/utils/get-base-file-path.ts +21 -0
  89. package/src/client/utils/github.ts +121 -0
  90. package/src/client/utils/use-on-change.ts +15 -0
  91. package/src/client/virtual.d.ts +24 -0
  92. package/src/node/cache.ts +156 -156
  93. package/src/node/config.ts +159 -103
  94. package/src/node/index.ts +13 -13
  95. package/src/node/mdx.ts +213 -61
  96. package/src/node/plugin/entry.ts +29 -18
  97. package/src/node/plugin/html.ts +11 -11
  98. package/src/node/plugin/index.ts +161 -83
  99. package/src/node/plugin/types.ts +2 -4
  100. package/src/node/routes/cache.ts +6 -6
  101. package/src/node/routes/index.ts +206 -113
  102. package/src/node/routes/parser.ts +106 -81
  103. package/src/node/routes/sorter.ts +15 -15
  104. package/src/node/routes/types.ts +24 -24
  105. package/src/node/ssg/index.ts +46 -46
  106. package/src/node/ssg/meta.ts +4 -4
  107. package/src/node/ssg/options.ts +5 -5
  108. package/src/node/ssg/sitemap.ts +14 -14
  109. package/src/node/utils.ts +31 -31
  110. package/tsconfig.json +25 -20
  111. package/tsup.config.ts +23 -14
  112. package/dist/PackageManagerTabs-NVT7G625.mjs +0 -99
  113. package/dist/SearchDialog-AGVF6JBO.mjs +0 -194
  114. package/dist/SearchDialog-YPDOM7Q6.css +0 -2847
  115. package/dist/Video-KNTY5BNO.mjs +0 -6
  116. package/dist/cache-KNL5B4EE.mjs +0 -12
  117. package/dist/chunk-7SFUJWTB.mjs +0 -211
  118. package/dist/chunk-FFBNU6IJ.mjs +0 -386
  119. package/dist/chunk-FMTOYQLO.mjs +0 -37
  120. package/dist/chunk-TKLQWU7H.mjs +0 -1920
  121. package/dist/chunk-Z7JHYNAS.mjs +0 -57
  122. package/dist/client/index.css +0 -2847
  123. package/dist/client/index.d.mts +0 -372
  124. package/dist/client/index.d.ts +0 -372
  125. package/dist/client/index.js +0 -3630
  126. package/dist/client/index.mjs +0 -697
  127. package/dist/client/ssr.css +0 -2847
  128. package/dist/client/ssr.d.mts +0 -27
  129. package/dist/client/ssr.d.ts +0 -27
  130. package/dist/client/ssr.js +0 -2928
  131. package/dist/client/ssr.mjs +0 -33
  132. package/dist/config-BsFQ-ErD.d.mts +0 -159
  133. package/dist/config-BsFQ-ErD.d.ts +0 -159
  134. package/dist/node/index.d.mts +0 -91
  135. package/dist/node/index.d.ts +0 -91
  136. package/dist/node/index.js +0 -1187
  137. package/dist/node/index.mjs +0 -762
  138. package/dist/types-Dj-bfnC3.d.mts +0 -74
  139. package/dist/types-Dj-bfnC3.d.ts +0 -74
  140. package/src/client/theme/components/CodeBlock/CodeBlock.tsx +0 -61
  141. package/src/client/theme/components/CodeBlock/index.ts +0 -1
  142. package/src/client/theme/components/PackageManagerTabs/PackageManagerTabs.tsx +0 -131
  143. package/src/client/theme/components/PackageManagerTabs/index.ts +0 -1
  144. package/src/client/theme/components/PackageManagerTabs/pkg-tabs.css +0 -64
  145. package/src/client/theme/components/Playground/Playground.tsx +0 -180
  146. package/src/client/theme/components/Playground/index.ts +0 -1
  147. package/src/client/theme/components/Playground/playground.css +0 -238
  148. package/src/client/theme/components/Video/Video.tsx +0 -84
  149. package/src/client/theme/components/Video/index.ts +0 -1
  150. package/src/client/theme/components/Video/video.css +0 -41
  151. package/src/client/theme/components/mdx/Admonition.tsx +0 -80
  152. package/src/client/theme/components/mdx/Badge.tsx +0 -31
  153. package/src/client/theme/components/mdx/Button.tsx +0 -50
  154. package/src/client/theme/components/mdx/Card.tsx +0 -80
  155. package/src/client/theme/components/mdx/Field.tsx +0 -60
  156. package/src/client/theme/components/mdx/FileTree.tsx +0 -229
  157. package/src/client/theme/components/mdx/List.tsx +0 -57
  158. package/src/client/theme/components/mdx/Table.tsx +0 -151
  159. package/src/client/theme/components/mdx/Tabs.tsx +0 -123
  160. package/src/client/theme/components/mdx/index.ts +0 -27
  161. package/src/client/theme/components/mdx/mdx-components.css +0 -764
  162. package/src/client/theme/icons/bun.tsx +0 -62
  163. package/src/client/theme/icons/deno.tsx +0 -20
  164. package/src/client/theme/icons/discord.tsx +0 -12
  165. package/src/client/theme/icons/github.tsx +0 -15
  166. package/src/client/theme/icons/npm.tsx +0 -13
  167. package/src/client/theme/icons/pnpm.tsx +0 -72
  168. package/src/client/theme/icons/twitter.tsx +0 -12
  169. package/src/client/theme/styles/markdown.css +0 -394
  170. package/src/client/theme/styles/variables.css +0 -175
  171. package/src/client/theme/styles.css +0 -39
  172. package/src/client/theme/ui/Breadcrumbs/Breadcrumbs.tsx +0 -68
  173. package/src/client/theme/ui/Breadcrumbs/index.ts +0 -1
  174. package/src/client/theme/ui/CopyMarkdown/CopyMarkdown.tsx +0 -82
  175. package/src/client/theme/ui/CopyMarkdown/copy-markdown.css +0 -112
  176. package/src/client/theme/ui/CopyMarkdown/index.ts +0 -1
  177. package/src/client/theme/ui/ErrorBoundary/ErrorBoundary.tsx +0 -50
  178. package/src/client/theme/ui/ErrorBoundary/error-boundary.css +0 -55
  179. package/src/client/theme/ui/ErrorBoundary/index.ts +0 -1
  180. package/src/client/theme/ui/Footer/footer.css +0 -32
  181. package/src/client/theme/ui/Head/Head.tsx +0 -69
  182. package/src/client/theme/ui/Head/index.ts +0 -1
  183. package/src/client/theme/ui/LanguageSwitcher/LanguageSwitcher.tsx +0 -125
  184. package/src/client/theme/ui/LanguageSwitcher/index.ts +0 -1
  185. package/src/client/theme/ui/LanguageSwitcher/language-switcher.css +0 -98
  186. package/src/client/theme/ui/Layout/Layout.tsx +0 -203
  187. package/src/client/theme/ui/Layout/base.css +0 -106
  188. package/src/client/theme/ui/Layout/index.ts +0 -2
  189. package/src/client/theme/ui/Layout/pagination.css +0 -72
  190. package/src/client/theme/ui/Layout/responsive.css +0 -47
  191. package/src/client/theme/ui/Link/Link.tsx +0 -392
  192. package/src/client/theme/ui/Link/LinkPreview.tsx +0 -59
  193. package/src/client/theme/ui/Link/index.ts +0 -2
  194. package/src/client/theme/ui/Link/link-preview.css +0 -48
  195. package/src/client/theme/ui/Loading/Loading.tsx +0 -10
  196. package/src/client/theme/ui/Loading/index.ts +0 -1
  197. package/src/client/theme/ui/Loading/loading.css +0 -30
  198. package/src/client/theme/ui/Navbar/GithubStars.tsx +0 -27
  199. package/src/client/theme/ui/Navbar/Navbar.tsx +0 -193
  200. package/src/client/theme/ui/Navbar/Tabs.tsx +0 -99
  201. package/src/client/theme/ui/Navbar/index.ts +0 -2
  202. package/src/client/theme/ui/Navbar/navbar.css +0 -347
  203. package/src/client/theme/ui/NotFound/NotFound.tsx +0 -19
  204. package/src/client/theme/ui/NotFound/index.ts +0 -1
  205. package/src/client/theme/ui/NotFound/not-found.css +0 -64
  206. package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +0 -244
  207. package/src/client/theme/ui/OnThisPage/index.ts +0 -1
  208. package/src/client/theme/ui/OnThisPage/toc.css +0 -152
  209. package/src/client/theme/ui/PoweredBy/PoweredBy.tsx +0 -18
  210. package/src/client/theme/ui/PoweredBy/index.ts +0 -1
  211. package/src/client/theme/ui/PoweredBy/powered-by.css +0 -76
  212. package/src/client/theme/ui/ProgressBar/ProgressBar.css +0 -17
  213. package/src/client/theme/ui/ProgressBar/ProgressBar.tsx +0 -51
  214. package/src/client/theme/ui/ProgressBar/index.ts +0 -1
  215. package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +0 -209
  216. package/src/client/theme/ui/SearchDialog/index.ts +0 -1
  217. package/src/client/theme/ui/SearchDialog/search.css +0 -152
  218. package/src/client/theme/ui/Sidebar/Sidebar.tsx +0 -244
  219. package/src/client/theme/ui/Sidebar/index.ts +0 -1
  220. package/src/client/theme/ui/Sidebar/sidebar.css +0 -230
  221. package/src/client/theme/ui/ThemeToggle/ThemeToggle.tsx +0 -69
  222. package/src/client/theme/ui/ThemeToggle/index.ts +0 -1
  223. package/src/client/theme/ui/VersionSwitcher/VersionSwitcher.tsx +0 -136
  224. package/src/client/theme/ui/VersionSwitcher/index.ts +0 -1
  225. package/src/client/utils.ts +0 -49
@@ -0,0 +1,183 @@
1
+ import * as RAC from 'react-aria-components'
2
+ import { Search, Hash, FileText, CornerDownLeft } from 'lucide-react'
3
+ import { cn } from '@client/utils/cn'
4
+ import type { ComponentBase } from './types'
5
+
6
+ export interface SearchDialogProps extends ComponentBase {
7
+ isOpen?: boolean
8
+ onOpenChange?: (isOpen: boolean) => void
9
+ }
10
+
11
+ export interface SearchDialogItemProps
12
+ extends Omit<RAC.ListBoxItemProps, 'children'> {
13
+ className?: string
14
+ children: React.ReactNode
15
+ }
16
+
17
+ export interface SearchDialogItemIconProps {
18
+ isHeading?: boolean
19
+ className?: string
20
+ }
21
+
22
+ export const SearchDialogRoot = ({
23
+ children,
24
+ isOpen,
25
+ onOpenChange,
26
+ className,
27
+ }: SearchDialogProps) => {
28
+ return (
29
+ <RAC.ModalOverlay
30
+ isOpen={isOpen}
31
+ onOpenChange={onOpenChange}
32
+ isDismissable
33
+ className={cn(
34
+ 'fixed inset-0 z-100 bg-black/40 backdrop-blur-sm px-4 py-4 sm:py-20',
35
+ 'entering:animate-in entering:fade-in exiting:animate-out exiting:fade-out',
36
+ )}
37
+ >
38
+ <RAC.Modal
39
+ className={cn(
40
+ 'mx-auto w-full max-w-2xl overflow-hidden rounded-xl border border-border-subtle bg-bg-surface shadow-2xl ring-1 ring-black/5 outline-none',
41
+ 'entering:animate-in entering:fade-in entering:zoom-in-95 exiting:animate-out exiting:fade-out exiting:zoom-out-95',
42
+ className,
43
+ )}
44
+ >
45
+ <RAC.Dialog className="flex flex-col max-h-[70vh] focus:outline-none">
46
+ {children as any}
47
+ </RAC.Dialog>
48
+ </RAC.Modal>
49
+ </RAC.ModalOverlay>
50
+ )
51
+ }
52
+
53
+ export const SearchDialogAutocomplete = ({
54
+ children,
55
+ className,
56
+ ...props
57
+ }: RAC.AutocompleteProps<object> & { className?: string }) => {
58
+ return (
59
+ <div className={className}>
60
+ <RAC.Autocomplete {...props} className="flex flex-col min-h-0">
61
+ {children}
62
+ </RAC.Autocomplete>
63
+ </div>
64
+ )
65
+ }
66
+
67
+ export const SearchDialogInput = ({
68
+ className,
69
+ ...props
70
+ }: RAC.InputProps & { className?: string }) => {
71
+ return (
72
+ <RAC.SearchField
73
+ className="flex items-center gap-3 border-b border-border-subtle px-4 py-4"
74
+ autoFocus
75
+ >
76
+ <Search className="h-5 w-5 text-text-muted" />
77
+ <RAC.Input
78
+ {...props}
79
+ className={cn(
80
+ 'w-full bg-transparent text-lg text-text-main placeholder-text-muted outline-none',
81
+ className,
82
+ )}
83
+ placeholder="Search documentation..."
84
+ />
85
+ <div className="flex items-center gap-1.5 rounded-md border border-border-subtle bg-bg-main px-1.5 py-1 text-[10px] font-medium text-text-muted">
86
+ <kbd className="font-sans">ESC</kbd>
87
+ </div>
88
+ </RAC.SearchField>
89
+ )
90
+ }
91
+
92
+ export const SearchDialogList = ({
93
+ children,
94
+ className,
95
+ ...props
96
+ }: RAC.ListBoxProps<object> & { className?: string }) => {
97
+ return (
98
+ <RAC.ListBox
99
+ {...props}
100
+ className={cn('flex-1 overflow-y-auto p-2 outline-none', className)}
101
+ >
102
+ {children as any}
103
+ </RAC.ListBox>
104
+ )
105
+ }
106
+
107
+ export const SearchDialogItemRoot = ({
108
+ children,
109
+ className,
110
+ ...props
111
+ }: SearchDialogItemProps) => {
112
+ return (
113
+ <RAC.ListBoxItem
114
+ {...props}
115
+ className={cn(
116
+ 'group flex items-center gap-3 rounded-lg p-3 text-left outline-none cursor-pointer transition-colors',
117
+ 'text-text-muted hover:bg-bg-main hover:text-text-main focus:bg-primary-500 focus:text-white selected:bg-primary-500 selected:text-white',
118
+ className,
119
+ )}
120
+ >
121
+ {(itemProps) => (
122
+ <>
123
+ {children}
124
+ {(itemProps.isFocused || itemProps.isSelected) && (
125
+ <div className="ml-auto opacity-50 flex items-center gap-1">
126
+ <span className="text-[10px]">Select</span>
127
+ <CornerDownLeft size={10} />
128
+ </div>
129
+ )}
130
+ </>
131
+ )}
132
+ </RAC.ListBoxItem>
133
+ )
134
+ }
135
+
136
+ export const SearchDialogItemIcon = ({
137
+ isHeading,
138
+ className,
139
+ }: SearchDialogItemIconProps) => {
140
+ return (
141
+ <div className={cn('shrink-0', className)}>
142
+ {isHeading ? <Hash size={18} /> : <FileText size={18} />}
143
+ </div>
144
+ )
145
+ }
146
+
147
+ export const SearchDialogItemTitle = ({
148
+ children,
149
+ className,
150
+ }: ComponentBase) => {
151
+ return (
152
+ <span
153
+ className={cn('block font-medium truncate flex-1 text-sm', className)}
154
+ >
155
+ {children}
156
+ </span>
157
+ )
158
+ }
159
+
160
+ export const SearchDialogItemBio = ({ children, className }: ComponentBase) => {
161
+ return (
162
+ <span
163
+ className={cn(
164
+ 'ml-2 text-xs opacity-70 truncate hidden sm:inline group-focus:opacity-100',
165
+ className,
166
+ )}
167
+ >
168
+ {children}
169
+ </span>
170
+ )
171
+ }
172
+
173
+ export default {
174
+ Root: SearchDialogRoot,
175
+ Autocomplete: SearchDialogAutocomplete,
176
+ Input: SearchDialogInput,
177
+ List: SearchDialogList,
178
+ Item: Object.assign(SearchDialogItemRoot, {
179
+ Icon: SearchDialogItemIcon,
180
+ Title: SearchDialogItemTitle,
181
+ Bio: SearchDialogItemBio,
182
+ }),
183
+ }
@@ -0,0 +1,154 @@
1
+ import { Link } from './link'
2
+ import * as RAC from 'react-aria-components'
3
+ import { ChevronRight } from 'lucide-react'
4
+ import { cn } from '@client/utils/cn'
5
+ import type { ComponentBase } from './types'
6
+ import type { ComponentRoute } from '@client/types'
7
+
8
+ export interface SidebarGroupProps extends ComponentBase {
9
+ title?: string
10
+ icon?: React.ElementType
11
+ isOpen?: boolean
12
+ onToggle?: () => void
13
+ }
14
+
15
+ export interface SidebarLinkProps extends ComponentBase {
16
+ label: string
17
+ href: string
18
+ active?: boolean
19
+ icon?: React.ElementType
20
+ badge?: ComponentRoute['badge']
21
+ }
22
+
23
+ const Badge = ({ badge }: { badge: ComponentRoute['badge'] }) => {
24
+ const colors = {
25
+ new: 'bg-primary-500/20 text-primary-500',
26
+ updated: 'bg-gray-500/20 text-gray-500',
27
+ deprecated: 'bg-red-500/20 text-red-500',
28
+ }
29
+
30
+ // Expire Badge
31
+ if (typeof badge === 'object' && badge?.expires) {
32
+ const expireDate = new Date(badge.expires)
33
+ const today = new Date()
34
+ const diffTime = expireDate.getTime() - today.getTime()
35
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
36
+
37
+ if (diffDays === 0) {
38
+ return null
39
+ }
40
+ }
41
+
42
+ const text = typeof badge === 'string' ? badge : badge?.text
43
+
44
+ return (
45
+ <span
46
+ className={cn(
47
+ 'ml-auto flex h-4.5 items-center rounded-full text-[9px] font-medium px-1.5 py-0.5 text-center whitespace-nowrap',
48
+ colors[text as keyof typeof colors] || colors.new,
49
+ )}
50
+ >
51
+ {text}
52
+ </span>
53
+ )
54
+ }
55
+
56
+ export const SidebarRoot = ({ children, className }: ComponentBase) => {
57
+ return (
58
+ <aside
59
+ className={cn(
60
+ 'boltdocs-sidebar sticky top-navbar hidden lg:flex flex-col shrink-0',
61
+ 'w-sidebar h-full',
62
+ 'overflow-y-auto border-r border-border-subtle bg-bg-main',
63
+ 'py-6 px-4',
64
+ className,
65
+ )}
66
+ >
67
+ <nav className="flex-1 space-y-6">{children}</nav>
68
+ </aside>
69
+ )
70
+ }
71
+
72
+ export const SidebarGroup = ({
73
+ children,
74
+ title,
75
+ icon: Icon,
76
+ isOpen = true,
77
+ onToggle,
78
+ className,
79
+ }: SidebarGroupProps) => {
80
+ return (
81
+ <div className={cn('space-y-1', className)}>
82
+ {title && (
83
+ <RAC.Button
84
+ onPress={onToggle}
85
+ className={cn(
86
+ 'flex w-full items-center justify-between px-2 py-1.5 text-xs font-bold uppercase tracking-wider outline-none cursor-pointer',
87
+ 'text-text-muted hover:text-text-main transition-colors',
88
+ 'focus-visible:ring-2 focus-visible:ring-primary-500/30 rounded-md',
89
+ )}
90
+ >
91
+ <div className="flex items-center gap-2">
92
+ {Icon && <Icon size={14} />}
93
+ {title}
94
+ </div>
95
+ <ChevronRight
96
+ size={14}
97
+ className={cn(
98
+ 'transition-transform duration-200',
99
+ isOpen && 'rotate-90',
100
+ )}
101
+ />
102
+ </RAC.Button>
103
+ )}
104
+ {isOpen && <div className="space-y-0.5">{children}</div>}
105
+ </div>
106
+ )
107
+ }
108
+
109
+ export const SidebarGroupItem = ({ children, className }: ComponentBase) => {
110
+ return <div className={cn(className)}>{children}</div>
111
+ }
112
+
113
+ export const SidebarLink = ({
114
+ label,
115
+ href,
116
+ active,
117
+ icon: Icon,
118
+ badge,
119
+ className,
120
+ }: SidebarLinkProps) => {
121
+ return (
122
+ <Link
123
+ href={href}
124
+ className={cn(
125
+ 'group flex items-center gap-2.5 rounded-lg px-2.5 py-2 text-sm outline-none transition-colors',
126
+ 'focus-visible:ring-2 focus-visible:ring-primary-500/30',
127
+ active
128
+ ? 'bg-primary-500/10 text-primary-500 font-medium'
129
+ : 'text-text-muted hover:bg-bg-surface hover:text-text-main',
130
+ className,
131
+ )}
132
+ >
133
+ {Icon && (
134
+ <Icon
135
+ size={16}
136
+ className={cn(
137
+ active
138
+ ? 'text-primary-500'
139
+ : 'text-text-muted group-hover:text-text-main',
140
+ )}
141
+ />
142
+ )}
143
+ <span className="truncate">{label}</span>
144
+ {badge && <Badge badge={badge} />}
145
+ </Link>
146
+ )
147
+ }
148
+
149
+ export default {
150
+ SidebarRoot,
151
+ SidebarGroup,
152
+ SidebarGroupItem,
153
+ SidebarLink,
154
+ }
@@ -0,0 +1,90 @@
1
+ import { cn } from '@client/utils/cn'
2
+ import type { ComponentBase } from './types'
3
+
4
+ export interface TabsItemProps extends ComponentBase {
5
+ id: string
6
+ selected?: boolean
7
+ onClick?: () => void
8
+ onKeyDown?: (event: React.KeyboardEvent) => void
9
+ disabled?: boolean
10
+ }
11
+
12
+ export interface TabsIndicatorProps extends ComponentBase {
13
+ style?: React.CSSProperties
14
+ }
15
+
16
+ export const TabsRoot = ({
17
+ children,
18
+ className = '',
19
+ ...props
20
+ }: ComponentBase) => {
21
+ return (
22
+ <div className={cn('w-full', className)} {...props}>
23
+ {children}
24
+ </div>
25
+ )
26
+ }
27
+
28
+ export const TabsList = ({ children, className = '' }: ComponentBase) => {
29
+ return (
30
+ <div
31
+ role="tablist"
32
+ className={cn(
33
+ 'relative flex flex-row items-center border-b border-border-subtle',
34
+ className,
35
+ )}
36
+ >
37
+ {children}
38
+ </div>
39
+ )
40
+ }
41
+
42
+ export const TabsItem = ({
43
+ children,
44
+ id,
45
+ selected,
46
+ className = '',
47
+ ...props
48
+ }: TabsItemProps) => {
49
+ return (
50
+ <button
51
+ role="tab"
52
+ aria-selected={selected}
53
+ className={cn(
54
+ 'flex items-center gap-2 px-4 py-2 text-sm font-medium transition-colors outline-none cursor-pointer bg-transparent border-none',
55
+ selected ? 'text-primary-500' : 'text-text-muted hover:text-text-main',
56
+ className,
57
+ )}
58
+ {...props}
59
+ >
60
+ {children}
61
+ </button>
62
+ )
63
+ }
64
+
65
+ export const TabsContent = ({ children, className = '' }: ComponentBase) => {
66
+ return <div className={cn('p-4 outline-none', className)}>{children}</div>
67
+ }
68
+
69
+ export const TabsIndicator = ({
70
+ className = '',
71
+ style,
72
+ }: TabsIndicatorProps) => {
73
+ return (
74
+ <div
75
+ className={cn(
76
+ 'absolute bottom-0 h-0.5 bg-primary-500 transition-all duration-300',
77
+ className,
78
+ )}
79
+ style={style}
80
+ />
81
+ )
82
+ }
83
+
84
+ export default {
85
+ TabsRoot,
86
+ TabsList,
87
+ TabsItem,
88
+ TabsContent,
89
+ TabsIndicator,
90
+ }
@@ -0,0 +1,83 @@
1
+ import type { ReactNode } from 'react'
2
+ import * as RAC from 'react-aria-components'
3
+ import { cn } from '@client/utils/cn'
4
+
5
+ export interface TooltipProps extends Omit<RAC.TooltipProps, 'children'> {
6
+ /** The content to show inside the tooltip */
7
+ content: ReactNode
8
+ /** The trigger element (usually a button or link) */
9
+ children: React.ReactElement
10
+ /** Delay in milliseconds before showing the tooltip */
11
+ delay?: number
12
+ /** Delay in milliseconds before hiding the tooltip */
13
+ closeDelay?: number
14
+ }
15
+
16
+ // Fixed type for TooltipContentProps to match RAC's internal expectations
17
+ export interface TooltipContentProps extends RAC.TooltipProps {}
18
+
19
+ /**
20
+ * Modern, accessible Tooltip component built with React Aria Components.
21
+ * Featuring glassmorphism, animations, and smart positioning.
22
+ */
23
+ const TooltipContent = ({
24
+ className,
25
+ children,
26
+ ...props
27
+ }: TooltipContentProps) => {
28
+ return (
29
+ <RAC.Tooltip
30
+ {...props}
31
+ offset={8}
32
+ className={(values) =>
33
+ cn(
34
+ 'group z-50 overflow-visible rounded-md bg-bg-surface/90 px-2.5 py-1.5 text-xs font-medium text-text-main shadow-lg backdrop-blur-md ring-1 ring-border-subtle outline-hidden select-none',
35
+ 'data-entering:animate-in data-entering:fade-in data-entering:zoom-in-95 data-entering:duration-100',
36
+ 'data-exiting:animate-out data-exiting:fade-out data-exiting:zoom-out-95 data-exiting:duration-75',
37
+ 'data-[placement=top]:slide-in-from-bottom-1',
38
+ 'data-[placement=bottom]:slide-in-from-top-1',
39
+ 'data-[placement=left]:slide-in-from-right-1',
40
+ 'data-[placement=right]:slide-in-from-left-1',
41
+ typeof className === 'function' ? className(values) : className,
42
+ )
43
+ }
44
+ >
45
+ {(values) => (
46
+ <>
47
+ <RAC.OverlayArrow>
48
+ <svg
49
+ width={8}
50
+ height={8}
51
+ viewBox="0 0 8 8"
52
+ className="fill-bg-surface/90 stroke-border-subtle group-data-[placement=bottom]:rotate-180 group-data-[placement=left]:-rotate-90 group-data-[placement=right]:rotate-90"
53
+ >
54
+ <title>Arrow</title>
55
+ <path d="M0 0 L4 4 L8 0" />
56
+ </svg>
57
+ </RAC.OverlayArrow>
58
+ {typeof children === 'function' ? children(values) : children}
59
+ </>
60
+ )}
61
+ </RAC.Tooltip>
62
+ )
63
+ }
64
+
65
+ export const Tooltip = ({
66
+ content,
67
+ children,
68
+ delay = 500,
69
+ closeDelay = 0,
70
+ ...props
71
+ }: TooltipProps) => {
72
+ return (
73
+ <RAC.TooltipTrigger delay={delay} closeDelay={closeDelay}>
74
+ {children}
75
+ <TooltipContent {...props}>{content}</TooltipContent>
76
+ </RAC.TooltipTrigger>
77
+ )
78
+ }
79
+
80
+ export default {
81
+ Tooltip,
82
+ TooltipContent,
83
+ }
@@ -0,0 +1,11 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ export type ComponentBase = {
4
+ className?: string
5
+ children?: ReactNode
6
+ }
7
+
8
+ /**
9
+ * Helper to type compound components (e.g. Navbar with Navbar.Link)
10
+ */
11
+ export type CompoundComponent<P, S> = React.FC<P> & S
@@ -0,0 +1,42 @@
1
+ import { useBreadcrumbs } from '@hooks/use-breadcrumbs'
2
+ import { Home } from 'lucide-react'
3
+ import {
4
+ BreadcrumbsItem,
5
+ BreadcrumbsLink,
6
+ BreadcrumbsRoot,
7
+ BreadcrumbsSeparator,
8
+ } from '@components/primitives/breadcrumbs'
9
+ import { cn } from '@client/utils/cn'
10
+ import { useConfig } from '@client/app/config-context'
11
+
12
+ export function Breadcrumbs() {
13
+ const { crumbs, activeRoute } = useBreadcrumbs()
14
+ const config = useConfig()
15
+
16
+ if (crumbs.length === 0) return null
17
+
18
+ if (!config.themeConfig?.breadcrumbs) return null
19
+
20
+ return (
21
+ <BreadcrumbsRoot>
22
+ <BreadcrumbsItem>
23
+ <BreadcrumbsLink href="/">
24
+ <Home size={14} />
25
+ </BreadcrumbsLink>
26
+ </BreadcrumbsItem>
27
+ {crumbs.map((crumb, i) => (
28
+ <BreadcrumbsItem key={`crumb-${crumb.href}-${crumb.label}-${i}`}>
29
+ <BreadcrumbsSeparator />
30
+ <BreadcrumbsLink
31
+ href={crumb.href}
32
+ className={cn({
33
+ 'font-medium text-text-main': crumb.href === activeRoute?.path,
34
+ })}
35
+ >
36
+ {crumb.label}
37
+ </BreadcrumbsLink>
38
+ </BreadcrumbsItem>
39
+ ))}
40
+ </BreadcrumbsRoot>
41
+ )
42
+ }
@@ -0,0 +1,112 @@
1
+ import { useState } from 'react'
2
+ import { Copy, Check, ExternalLink, ChevronDown } from 'lucide-react'
3
+ import {
4
+ Button,
5
+ ButtonGroup,
6
+ Menu,
7
+ MenuItem,
8
+ MenuTrigger,
9
+ cn,
10
+ } from '@client/components/primitives'
11
+
12
+ import type { ComponentRoute } from '@client/types'
13
+
14
+ export interface CopyMarkdownProps {
15
+ content?: string
16
+ mdxRaw?: string
17
+ route?: ComponentRoute
18
+ config?: boolean | { text?: string; icon?: string }
19
+ }
20
+
21
+ const useCopyMarkdown = (content: string) => {
22
+ const [copied, setCopied] = useState(false)
23
+
24
+ const handleCopy = () => {
25
+ navigator.clipboard.writeText(content)
26
+ setCopied(true)
27
+ setTimeout(() => setCopied(false), 2000)
28
+ }
29
+
30
+ const handleOpenRaw = () => {
31
+ const blob = new Blob([content], { type: 'text/plain;charset=utf-8' })
32
+ const url = URL.createObjectURL(blob)
33
+ window.open(url, '_blank')
34
+ }
35
+
36
+ return {
37
+ copied,
38
+ handleCopy,
39
+ handleOpenRaw,
40
+ }
41
+ }
42
+
43
+ export function CopyMarkdown({ content, mdxRaw, config }: CopyMarkdownProps) {
44
+ const displayContent = mdxRaw || content || ''
45
+ const { copied, handleCopy, handleOpenRaw } = useCopyMarkdown(displayContent)
46
+
47
+ const isEnabled = config !== false
48
+ const buttonText =
49
+ typeof config === 'object'
50
+ ? config.text || 'Copy Markdown'
51
+ : 'Copy Markdown'
52
+
53
+ if (!isEnabled || !displayContent) return null
54
+
55
+ return (
56
+ <div className="relative inline-flex z-100 flex-shrink-0 w-max translate-y-0 active:translate-y-px transition-transform duration-200">
57
+ <ButtonGroup className="rounded-xl border border-border-subtle bg-bg-surface/40 backdrop-blur-md transition-all duration-300 hover:border-primary-500/50 hover:shadow-lg hover:shadow-primary-500/5 group overflow-hidden">
58
+ <Button
59
+ variant="ghost"
60
+ onPress={handleCopy}
61
+ icon={copied ? <Check size={16} /> : <Copy size={16} />}
62
+ iconPosition="left"
63
+ className={cn(
64
+ 'px-5 py-2 bg-transparent text-[0.8125rem] font-semibold h-9 border-none shrink-0',
65
+ 'text-text-main transition-all duration-300 hover:bg-primary-500/5',
66
+ copied && 'text-emerald-500 hover:bg-emerald-500/5',
67
+ )}
68
+ >
69
+ {copied ? 'Copied!' : buttonText}
70
+ </Button>
71
+
72
+ <MenuTrigger placement="bottom end">
73
+ <Button
74
+ variant="ghost"
75
+ isIconOnly
76
+ icon={<ChevronDown size={14} />}
77
+ className={cn(
78
+ 'px-3.5 h-9 border-l border-border-subtle/50 text-text-muted rounded-none bg-transparent shrink-0',
79
+ 'transition-all duration-300 hover:bg-primary-500/5 hover:text-primary-500',
80
+ )}
81
+ />
82
+ <Menu className="w-52">
83
+ <MenuItem
84
+ onAction={handleCopy}
85
+ className="flex flex-row items-start gap-2.5 group"
86
+ >
87
+ <Copy
88
+ size={16}
89
+ className="w-4 h-4 shrink-0 mt-0.5 transition-transform duration-200 group-hover:-translate-y-0.5 text-text-muted group-hover:text-primary-500"
90
+ />
91
+ <span className="font-medium text-[0.8125rem]">
92
+ Copy Markdown
93
+ </span>
94
+ </MenuItem>
95
+ <MenuItem
96
+ onAction={handleOpenRaw}
97
+ className="flex flex-row items-start gap-2.5 group"
98
+ >
99
+ <ExternalLink
100
+ size={16}
101
+ className="w-4 h-4 shrink-0 mt-0.5 transition-transform duration-200 group-hover:-translate-y-0.5 text-text-muted group-hover:text-primary-500"
102
+ />
103
+ <span className="font-medium text-[0.8125rem]">
104
+ View as Markdown
105
+ </span>
106
+ </MenuItem>
107
+ </Menu>
108
+ </MenuTrigger>
109
+ </ButtonGroup>
110
+ </div>
111
+ )
112
+ }