boltdocs 2.6.1 → 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 +173 -1328
  9. package/dist/client/index.d.ts +172 -1327
  10. package/dist/client/index.js +1 -1
  11. package/dist/{package-c99Cs7mD.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 +66 -13
  40. package/dist/node/index.d.mts +66 -14
  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-DukYeKmD.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 +120 -53
  129. package/src/client/hooks/use-localized-to.ts +70 -30
  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 +64 -81
  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 -77
  136. package/src/client/hooks/use-tabs.ts +3 -4
  137. package/src/client/hooks/use-version.ts +46 -18
  138. package/src/client/index.ts +13 -86
  139. package/src/client/mdx.ts +2 -0
  140. package/src/client/primitives.ts +19 -0
  141. package/src/client/ssg/boltdocs-shell.tsx +78 -57
  142. package/src/client/ssg/create-routes.tsx +290 -50
  143. package/src/client/ssg/mdx-page.tsx +2 -1
  144. package/src/client/store/boltdocs-context.tsx +83 -12
  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 +97 -21
  152. package/dist/node-CWN8U_p8.mjs +0 -88
  153. package/dist/node-D5iosYXv.cjs +0 -88
  154. package/dist/search-dialog-3lvKsbVG.js +0 -6
  155. package/dist/search-dialog-DMK5OpgH.cjs +0 -6
  156. package/dist/use-search-C9bxCqfF.js +0 -6
  157. package/dist/use-search-DcfZSunO.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 -76
  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
@@ -1,48 +1,56 @@
1
- import {
2
- Link as RACLink,
3
- type LinkProps as RACLinkProps,
4
- } from 'react-aria-components'
5
- import { useLocation } from 'react-router-dom'
1
+ import { useNavigate, useLocation } from 'react-router-dom'
6
2
  import { useLocalizedTo } from '../../hooks/use-localized-to'
7
3
  import { cn } from '../../utils/cn'
8
- import { forwardRef } from 'react'
9
-
10
- export interface LinkProps extends RACLinkProps {
4
+ export interface LinkProps
5
+ extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
11
6
  /** Should prefetch the page on hover? Default 'hover' */
12
7
  prefetch?: 'hover' | 'none'
13
8
  }
14
9
 
15
10
  /**
16
- * A primitive Link component that wraps React Aria Components' Link
11
+ * A primitive Link component that wraps a standard anchor tag
17
12
  * and adds framework-specific logic for path localization and preloading.
18
- *
19
- * It uses the global navigation configuration from BoltdocsRouterProvider
20
- * to handle seamless client-side transitions.
21
13
  */
22
- export const Link = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
23
- const { href, prefetch = 'hover', onMouseEnter, onFocus, ...rest } = props
14
+ export const Link = (props: LinkProps) => {
15
+ const { href, onMouseEnter, onFocus, onClick, ...rest } = props
24
16
 
17
+ const navigate = useNavigate()
25
18
  const localizedHref = useLocalizedTo(href ?? '')
26
19
 
20
+ const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
21
+ onClick?.(e)
22
+ if (e.defaultPrevented) return
23
+
24
+ const isExternal =
25
+ localizedHref &&
26
+ (localizedHref.startsWith('http://') ||
27
+ localizedHref.startsWith('https://') ||
28
+ localizedHref.startsWith('//'))
29
+
30
+ if (!isExternal) {
31
+ e.preventDefault()
32
+ navigate(localizedHref)
33
+ }
34
+ }
35
+
27
36
  const handleMouseEnter = (e: React.MouseEvent<HTMLAnchorElement>) => {
28
37
  onMouseEnter?.(e)
29
38
  }
30
39
 
31
- const handleFocus = (e: React.FocusEvent) => {
32
- onFocus?.(e as any)
40
+ const handleFocus = (e: React.FocusEvent<HTMLAnchorElement>) => {
41
+ onFocus?.(e)
33
42
  }
34
43
 
35
44
  return (
36
- <RACLink
45
+ <a
37
46
  {...rest}
38
- ref={ref}
39
- href={localizedHref as string}
47
+ href={localizedHref}
48
+ onClick={handleClick}
40
49
  onMouseEnter={handleMouseEnter}
41
- onFocus={handleFocus as any}
50
+ onFocus={handleFocus}
42
51
  />
43
52
  )
44
- })
45
- Link.displayName = 'Link'
53
+ }
46
54
 
47
55
  /**
48
56
  * Props for the NavLink component, extending standard Link props.
@@ -68,37 +76,27 @@ export interface NavLinkProps
68
76
 
69
77
  /**
70
78
  * A primitive NavLink component that provides active state detection.
71
- *
72
- * It combines the Link primitive with path matching logic to determine
73
- * if the link is currently active based on the browser's location.
74
79
  */
75
- export const NavLink = forwardRef<HTMLAnchorElement, NavLinkProps>(
76
- (props, ref) => {
77
- const { href, end = false, className, children, ...rest } = props
78
- const location = useLocation()
79
- const localizedHref = useLocalizedTo(href ?? '')
80
+ export const NavLink = (props: NavLinkProps) => {
81
+ const { href, end = false, className, children, ...rest } = props
82
+ const location = useLocation()
80
83
 
81
- const isActive = end
82
- ? location.pathname === localizedHref
83
- : location.pathname.startsWith(localizedHref as string)
84
+ const localizedHref = useLocalizedTo(href ?? '')
85
+
86
+ const isActive = end
87
+ ? location.pathname === localizedHref
88
+ : location.pathname.startsWith(localizedHref)
84
89
 
85
- const resolvedClassName =
86
- typeof className === 'function'
87
- ? className({ isActive })
88
- : cn(className, isActive && 'active')
89
- const resolvedChildren =
90
- typeof children === 'function' ? children({ isActive }) : children
90
+ const resolvedClassName =
91
+ typeof className === 'function'
92
+ ? className({ isActive })
93
+ : cn(className, isActive && 'active')
94
+ const resolvedChildren =
95
+ typeof children === 'function' ? children({ isActive }) : children
91
96
 
92
- return (
93
- <Link
94
- {...rest}
95
- ref={ref}
96
- href={href}
97
- className={resolvedClassName as any}
98
- >
99
- {resolvedChildren as any}
100
- </Link>
101
- )
102
- },
103
- )
104
- NavLink.displayName = 'NavLink'
97
+ return (
98
+ <Link {...rest} href={href} className={resolvedClassName}>
99
+ {resolvedChildren}
100
+ </Link>
101
+ )
102
+ }
@@ -1,25 +1,25 @@
1
- 'use client'
2
-
3
- import { Check, ChevronRight, Dot } from 'lucide-react'
1
+ import { Check, ChevronRight } from 'lucide-react'
4
2
  import * as RAC from 'react-aria-components'
5
3
  import { Children } from 'react'
6
4
  import { Popover, type PopoverProps } from './popover'
7
5
  import { cn } from '../../utils/cn'
6
+
8
7
  /**
9
8
  * MenuTrigger wraps a trigger (usually a Button) and a Menu.
10
9
  */
11
10
  export interface MenuTriggerProps extends RAC.MenuTriggerProps {
12
11
  placement?: PopoverProps['placement']
12
+ className?: string
13
13
  }
14
14
 
15
- function MenuTrigger(props: MenuTriggerProps) {
15
+ function MenuTrigger({ placement, className, ...props }: MenuTriggerProps) {
16
16
  const [trigger, menu] = (
17
17
  Children.toArray(props.children) as React.ReactElement[]
18
18
  ).slice(0, 2)
19
19
  return (
20
20
  <RAC.MenuTrigger {...props}>
21
21
  {trigger as any}
22
- <Popover placement={props.placement} className="min-w-35">
22
+ <Popover placement={placement} className={className}>
23
23
  {menu as any}
24
24
  </Popover>
25
25
  </RAC.MenuTrigger>
@@ -29,14 +29,18 @@ function MenuTrigger(props: MenuTriggerProps) {
29
29
  /**
30
30
  * SubmenuTrigger for nested menus.
31
31
  */
32
- function SubmenuTrigger(props: RAC.SubmenuTriggerProps) {
32
+ export interface SubmenuTriggerProps extends RAC.SubmenuTriggerProps {
33
+ className?: string
34
+ }
35
+
36
+ function SubmenuTrigger({ className, ...props }: SubmenuTriggerProps) {
33
37
  const [trigger, menu] = (
34
38
  Children.toArray(props.children) as React.ReactElement[]
35
39
  ).slice(0, 2)
36
40
  return (
37
41
  <RAC.SubmenuTrigger {...props}>
38
42
  {trigger as any}
39
- <Popover offset={-4} crossOffset={-4}>
43
+ <Popover offset={-4} crossOffset={-4} className={className}>
40
44
  {menu as any}
41
45
  </Popover>
42
46
  </RAC.SubmenuTrigger>
@@ -51,10 +55,7 @@ export function Menu<T extends object>(props: RAC.MenuProps<T>) {
51
55
  <RAC.Menu
52
56
  {...props}
53
57
  className={RAC.composeRenderProps(props.className, (className) =>
54
- cn(
55
- 'p-1.5 outline-none max-h-[inherit] overflow-auto max-w-75',
56
- className,
57
- ),
58
+ cn('outline-none overflow-auto', className),
58
59
  )}
59
60
  />
60
61
  )
@@ -72,42 +73,24 @@ function MenuItem(props: RAC.MenuItemProps) {
72
73
  <RAC.MenuItem
73
74
  {...props}
74
75
  textValue={textValue}
75
- className={RAC.composeRenderProps(
76
- props.className,
77
- (className, { isFocused, isPressed, isDisabled }) =>
78
- cn(
79
- 'group relative flex flex-row items-center gap-2 px-2 py-1 rounded-lg outline-none cursor-default hover:cursor-pointer transition-none',
80
- 'text-text-main text-[12px]',
81
- {
82
- 'bg-bg-surface-elevated text-primary-600 ring-1 ring-border-strong/5':
83
- isFocused,
84
- 'bg-bg-surface-elevanted': isPressed,
85
- 'opacity-40 grayscale pointer-events-none': isDisabled,
86
- },
87
- className,
88
- ),
76
+ className={RAC.composeRenderProps(props.className, (className) =>
77
+ cn(
78
+ 'group relative flex flex-row items-center cursor-default outline-none',
79
+ className,
80
+ ),
89
81
  )}
90
82
  >
91
83
  {RAC.composeRenderProps(
92
84
  props.children,
93
85
  (children, { selectionMode, isSelected, hasSubmenu }) => (
94
86
  <>
95
- {selectionMode !== 'none' && (
96
- <span className="flex items-center size-4 shrink-0 justify-center">
97
- {isSelected && selectionMode === 'multiple' && (
98
- <Check className="size-3.5 stroke-[2.5px] text-primary-500 animate-in zoom-in-50 duration-200" />
99
- )}
100
- {isSelected && selectionMode === 'single' && (
101
- <Dot className="size-5 text-primary-500 animate-in zoom-in-50 duration-200" />
102
- )}
87
+ {selectionMode === 'multiple' && (
88
+ <span className="flex items-center shrink-0 justify-center">
89
+ {isSelected && <Check className="size-3.5" />}
103
90
  </span>
104
91
  )}
105
- <div className="flex flex-row w-full transition-colors items-center gap-2 py-1 px-1">
106
- {children}
107
- </div>
108
- {hasSubmenu && (
109
- <ChevronRight className="size-4 ml-auto text-text-muted group-focused:text-primary-500/70 transition-colors" />
110
- )}
92
+ <div className="flex flex-row w-full items-center">{children}</div>
93
+ {hasSubmenu && <ChevronRight className="size-4 ml-auto" />}
111
94
  </>
112
95
  ),
113
96
  )}
@@ -129,13 +112,9 @@ function MenuSection<T extends object>({
129
112
  return (
130
113
  <RAC.MenuSection
131
114
  {...props}
132
- className={cn('flex flex-col gap-0.5', props.className)}
115
+ className={cn('flex flex-col', props.className)}
133
116
  >
134
- {title && (
135
- <RAC.Header className="px-3 py-2 text-[10px] font-bold uppercase tracking-[0.075em] text-text-muted/50 select-none">
136
- {title}
137
- </RAC.Header>
138
- )}
117
+ {title && <RAC.Header className="select-none">{title}</RAC.Header>}
139
118
  <RAC.Collection items={props.items}>{props.children}</RAC.Collection>
140
119
  </RAC.MenuSection>
141
120
  )
@@ -146,10 +125,7 @@ function MenuSection<T extends object>({
146
125
  */
147
126
  function MenuSeparator(props: RAC.SeparatorProps) {
148
127
  return (
149
- <RAC.Separator
150
- {...props}
151
- className="mx-2 my-1.5 border-t border-border-subtle/50"
152
- />
128
+ <RAC.Separator {...props} className={cn('border-t', props.className)} />
153
129
  )
154
130
  }
155
131
 
@@ -1,7 +1,17 @@
1
1
  import { type ReactNode, useState, useEffect } from 'react'
2
- import { Separator, ToggleButton, Link, cn } from './index'
3
- import { Button as ButtonRAC } from 'react-aria-components'
4
- import { Search, Sun, Moon, ExternalLink } from 'lucide-react'
2
+ import {
3
+ Button as ButtonRAC,
4
+ ModalOverlay,
5
+ Modal,
6
+ Dialog,
7
+ Separator,
8
+ ToggleButton,
9
+ } from 'react-aria-components'
10
+ import { Link } from './link'
11
+ import { Menu } from './menu'
12
+ import { Popover } from './popover'
13
+ import { cn } from '../../utils/cn'
14
+ import { Sun, Moon, ExternalLink, MoreVertical, X } from 'lucide-react'
5
15
  import * as IconsSocials from '../icons-dev'
6
16
  import type { ComponentBase } from './types'
7
17
  import type { BoltdocsSocialLink } from '../../../shared/types'
@@ -9,7 +19,6 @@ import type { BoltdocsSocialLink } from '../../../shared/types'
9
19
  export interface NavbarLinkProps extends Omit<ComponentBase, 'children'> {
10
20
  label: ReactNode
11
21
  href: string
12
- active?: boolean
13
22
  to?: 'internal' | 'external'
14
23
  }
15
24
 
@@ -18,6 +27,7 @@ export interface NavbarLogoProps extends Omit<ComponentBase, 'children'> {
18
27
  alt: string
19
28
  width?: number
20
29
  height?: number
30
+ href?: string
21
31
  }
22
32
 
23
33
  export interface NavbarSearchTriggerProps extends ComponentBase {
@@ -38,10 +48,7 @@ export interface NavbarSocialsProps extends ComponentBase {
38
48
  export const Navbar = ({ children, className, ...props }: ComponentBase) => {
39
49
  return (
40
50
  <header
41
- className={cn(
42
- 'boltdocs-navbar sticky top-0 z-50 w-full border-b border-border-subtle bg-bg-main/80 backdrop-blur-md',
43
- className,
44
- )}
51
+ className={cn('boltdocs-navbar sticky top-0 z-50 w-full', className)}
45
52
  {...props}
46
53
  >
47
54
  {children}
@@ -107,10 +114,11 @@ const NavbarLogo = ({
107
114
  width = 24,
108
115
  height = 24,
109
116
  className,
117
+ href = '/',
110
118
  }: NavbarLogoProps) => {
111
119
  return (
112
120
  <Link
113
- href="/"
121
+ href={href}
114
122
  className={cn('flex items-center gap-2 shrink-0 outline-none', className)}
115
123
  >
116
124
  {src ? (
@@ -126,9 +134,13 @@ const NavbarLogo = ({
126
134
  )
127
135
  }
128
136
 
129
- const NavbarTitle = ({ children, className }: ComponentBase) => {
137
+ const NavbarTitle = ({
138
+ children,
139
+ className,
140
+ href = '/',
141
+ }: { href?: string } & ComponentBase) => {
130
142
  return (
131
- <Link href="/">
143
+ <Link href={href}>
132
144
  <span
133
145
  className={cn(
134
146
  'text-lg font-bold tracking-tight hidden sm:inline-block',
@@ -154,25 +166,12 @@ const NavbarLinks = ({ children, className }: ComponentBase) => {
154
166
  )
155
167
  }
156
168
 
157
- const NavbarLink = ({
158
- label,
159
- href,
160
- active,
161
- to,
162
- className,
163
- }: NavbarLinkProps) => {
169
+ const NavbarLink = ({ label, href, to, className }: NavbarLinkProps) => {
164
170
  return (
165
171
  <Link
166
172
  href={href}
167
173
  target={to === 'external' ? '_blank' : undefined}
168
- className={cn(
169
- 'transition-colors outline-none font-medium focus-visible:ring-2 focus-visible:ring-primary-500/30 rounded-sm',
170
- {
171
- 'text-primary-500': active,
172
- 'text-text-muted hover:text-text-main': !active,
173
- },
174
- className,
175
- )}
174
+ className={cn('transition-all outline-none', className)}
176
175
  >
177
176
  {label as any}
178
177
  {to === 'external' && (
@@ -184,10 +183,115 @@ const NavbarLink = ({
184
183
  )
185
184
  }
186
185
 
187
- const NavbarSearchTrigger = ({
186
+ const NavbarDropdown = ({
187
+ label,
188
+ className,
189
+ children,
190
+ }: {
191
+ label: React.ReactNode
192
+ className?: string
193
+ children: React.ReactNode
194
+ }) => {
195
+ const [isOpen, setIsOpen] = useState(false)
196
+
197
+ return (
198
+ <div
199
+ className={cn('relative', className)}
200
+ onMouseEnter={() => {
201
+ setIsOpen(true)
202
+ }}
203
+ onMouseLeave={() => {
204
+ setIsOpen(false)
205
+ }}
206
+ >
207
+ <div
208
+ className={cn(
209
+ 'flex items-center gap-1 outline-none cursor-pointer select-none font-medium text-muted hover:text-body transition-colors',
210
+ )}
211
+ >
212
+ {label}
213
+ <svg
214
+ className={cn('w-4 h-4 transition-transform', isOpen && 'rotate-180')}
215
+ fill="none"
216
+ viewBox="0 0 24 24"
217
+ stroke="currentColor"
218
+ >
219
+ <path
220
+ strokeLinecap="round"
221
+ strokeLinejoin="round"
222
+ strokeWidth={2}
223
+ d="M19 9l-7 7-7-7"
224
+ />
225
+ </svg>
226
+ </div>
227
+ {isOpen && (
228
+ <div className="absolute top-full left-0 pt-1 z-[9999]">
229
+ <div className="min-w-[180px] p-1 bg-surface border border-subtle rounded-md shadow-lg">
230
+ {children}
231
+ </div>
232
+ </div>
233
+ )}
234
+ </div>
235
+ )
236
+ }
237
+
238
+ const NavbarDropdownItem = ({
239
+ href,
240
+ label,
241
+ className,
242
+ }: {
243
+ href: string
244
+ label: string
245
+ className?: string
246
+ }) => {
247
+ return (
248
+ <Link
249
+ href={href}
250
+ className={cn('block px-2 py-1.5 rounded hover:bg-surface', className)}
251
+ >
252
+ {label}
253
+ </Link>
254
+ )
255
+ }
256
+
257
+ const NavbarSearchTriggerDesktop = ({
258
+ className,
259
+ onPress,
260
+ children,
261
+ }: NavbarSearchTriggerProps) => {
262
+ return (
263
+ <ButtonRAC
264
+ onPress={onPress}
265
+ className={cn(
266
+ 'hidden lg:flex items-center justify-between gap-2 px-3 py-2 text-sm outline-none cursor-pointer w-full max-w-[720px]',
267
+ className,
268
+ )}
269
+ >
270
+ {children}
271
+ </ButtonRAC>
272
+ )
273
+ }
274
+
275
+ const NavbarSearchTriggerMobile = ({
188
276
  className,
189
277
  onPress,
278
+ children,
190
279
  }: NavbarSearchTriggerProps) => {
280
+ return (
281
+ <ButtonRAC
282
+ onPress={onPress}
283
+ className={cn(
284
+ 'lg:hidden flex h-10 w-10 items-center justify-center outline-none cursor-pointer',
285
+ className,
286
+ )}
287
+ aria-label="Search"
288
+ >
289
+ {children}
290
+ </ButtonRAC>
291
+ )
292
+ }
293
+
294
+ const NavbarSearchTriggerKbd = ({ className }: ComponentBase) => {
191
295
  const [mounted, setMounted] = useState(false)
192
296
  const isMac = mounted && /Mac|iPod|iPhone|iPad/.test(navigator.platform)
193
297
 
@@ -196,43 +300,34 @@ const NavbarSearchTrigger = ({
196
300
  }, [])
197
301
 
198
302
  return (
199
- <ButtonRAC
200
- onPress={onPress}
303
+ <div
201
304
  className={cn(
202
- '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',
203
- 'transition-all duration-200 hover:border-border-strong hover:text-text-main hover:bg-bg-muted hover:shadow-sm active:scale-[0.98]',
204
- 'focus-visible:ring-2 focus-visible:ring-primary-500/30',
205
- 'w-full max-w-[720px] justify-between',
305
+ 'hidden sm:flex items-center gap-1 pointer-events-none select-none',
206
306
  className,
207
307
  )}
208
308
  >
209
- <div className="flex items-center gap-2">
210
- <Search size={16} />
211
- <span className="hidden sm:inline-block">Search docs...</span>
212
- </div>
213
- <div className="hidden sm:flex items-center gap-1 pointer-events-none select-none">
214
- <kbd className="flex h-5 items-center justify-center rounded border border-border-subtle bg-bg-main px-1.5 font-mono text-[10px] font-medium">
215
- {isMac ? '⌘' : 'Ctrl'}
216
- </kbd>
217
- <kbd className="flex h-5 w-5 items-center justify-center rounded border border-border-subtle bg-bg-main font-mono text-[10px] font-medium">
218
- K
219
- </kbd>
220
- </div>
221
- </ButtonRAC>
309
+ <kbd className="flex items-center justify-center font-mono text-[10px]">
310
+ {isMac ? '⌘' : 'Ctrl'}
311
+ </kbd>
312
+ <kbd className="flex items-center justify-center font-mono text-[10px]">
313
+ K
314
+ </kbd>
315
+ </div>
222
316
  )
223
317
  }
224
318
 
319
+ const NavbarSearchTrigger = {
320
+ Desktop: NavbarSearchTriggerDesktop,
321
+ Mobile: NavbarSearchTriggerMobile,
322
+ Kbd: NavbarSearchTriggerKbd,
323
+ }
324
+
225
325
  const NavbarTheme = ({ className, theme, onThemeChange }: NavbarThemeProps) => {
226
326
  return (
227
327
  <ToggleButton
228
328
  isSelected={theme === 'dark'}
229
329
  onChange={onThemeChange}
230
- className={cn(
231
- 'rounded-md p-2 text-text-muted outline-none cursor-pointer',
232
- 'transition-all duration-300 hover:bg-bg-surface hover:text-text-main hover:rotate-12 active:scale-90',
233
- 'focus-visible:ring-2 focus-visible:ring-primary-500/30',
234
- className,
235
- )}
330
+ className={cn('outline-none cursor-pointer', className)}
236
331
  aria-label="Toggle theme"
237
332
  >
238
333
  {theme === 'dark' ? <Sun size={20} /> : <Moon size={20} />}
@@ -253,12 +348,7 @@ const NavbarSocials = ({ icon, link, className }: NavbarSocialsProps) => {
253
348
  href={link}
254
349
  target="_blank"
255
350
  rel="noopener noreferrer"
256
- className={cn(
257
- 'rounded-md p-2 text-text-muted outline-none transition-colors',
258
- 'hover:bg-bg-surface hover:text-text-main',
259
- 'focus-visible:ring-2 focus-visible:ring-primary-500/30',
260
- className,
261
- )}
351
+ className={cn('outline-none', className)}
262
352
  >
263
353
  <Icon name={icon} />
264
354
  </Link>
@@ -269,11 +359,91 @@ const NavbarSplit = ({ className }: ComponentBase) => {
269
359
  return (
270
360
  <Separator
271
361
  orientation="vertical"
272
- className={cn('h-6 w-px bg-border-subtle mx-1', className)}
362
+ className={cn('h-full w-px', className)}
273
363
  />
274
364
  )
275
365
  }
276
366
 
367
+ export interface NavbarMoreProps extends ComponentBase {
368
+ onPress?: () => void
369
+ }
370
+
371
+ const NavbarMore = ({ onPress, className }: NavbarMoreProps) => {
372
+ return (
373
+ <ButtonRAC
374
+ onPress={onPress}
375
+ className={cn(
376
+ 'md:hidden flex items-center justify-center outline-none cursor-pointer',
377
+ className,
378
+ )}
379
+ aria-label="More navigation"
380
+ >
381
+ <MoreVertical size={20} />
382
+ </ButtonRAC>
383
+ )
384
+ }
385
+
386
+ export interface NavbarMobileMenuProps extends ComponentBase {
387
+ isOpen: boolean
388
+ onClose: () => void
389
+ }
390
+
391
+ const NavbarMobileMenu = ({
392
+ isOpen,
393
+ onClose,
394
+ children,
395
+ className,
396
+ }: NavbarMobileMenuProps) => {
397
+ return (
398
+ <ModalOverlay
399
+ isOpen={isOpen}
400
+ onOpenChange={(open) => !open && onClose()}
401
+ isDismissable={true}
402
+ className={cn(
403
+ 'fixed inset-0 z-60 md:hidden transition-all duration-100',
404
+ className,
405
+ )}
406
+ >
407
+ <Modal className="fixed inset-0 outline-none">
408
+ <Dialog className="relative h-full outline-none flex flex-col p-6 pt-[calc(1.5rem+env(safe-area-inset-top,0px))] pb-[calc(1.5rem+env(safe-area-inset-bottom,0px))] px-[calc(1.5rem+env(safe-area-inset-left,0px))]">
409
+ <div className="flex items-center justify-between mb-6">
410
+ <span></span>
411
+ <ButtonRAC
412
+ onPress={onClose}
413
+ className="flex items-center justify-center outline-none cursor-pointer text-muted hover:text-body transition-colors"
414
+ aria-label="Close menu"
415
+ >
416
+ <X size={24} />
417
+ </ButtonRAC>
418
+ </div>
419
+ <nav className="flex-1 overflow-y-auto flex flex-col gap-4">
420
+ {children}
421
+ </nav>
422
+ </Dialog>
423
+ </Modal>
424
+ </ModalOverlay>
425
+ )
426
+ }
427
+
428
+ const NavbarMobileLink = ({
429
+ label,
430
+ href,
431
+ to,
432
+ onPress,
433
+ className,
434
+ }: NavbarLinkProps & { onPress?: () => void }) => {
435
+ return (
436
+ <Link
437
+ href={href}
438
+ target={to === 'external' ? '_blank' : undefined}
439
+ onClick={onPress}
440
+ className={cn('group flex items-center outline-none', className)}
441
+ >
442
+ {label as any}
443
+ </Link>
444
+ )
445
+ }
446
+
277
447
  Navbar.Root = Navbar
278
448
  Navbar.Left = NavbarLeft
279
449
  Navbar.Right = NavbarRight
@@ -282,10 +452,15 @@ Navbar.Logo = NavbarLogo
282
452
  Navbar.Title = NavbarTitle
283
453
  Navbar.Links = NavbarLinks
284
454
  Navbar.Link = NavbarLink
455
+ Navbar.Dropdown = NavbarDropdown
456
+ Navbar.DropdownItem = NavbarDropdownItem
285
457
  Navbar.SearchTrigger = NavbarSearchTrigger
286
458
  Navbar.Theme = NavbarTheme
287
459
  Navbar.Socials = NavbarSocials
288
460
  Navbar.Split = NavbarSplit
289
461
  Navbar.Content = NavbarContent
462
+ Navbar.More = NavbarMore
463
+ Navbar.MobileMenu = NavbarMobileMenu
464
+ Navbar.MobileLink = NavbarMobileLink
290
465
 
291
466
  export default Navbar