boltdocs 2.7.10 → 2.7.11

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 (137) hide show
  1. package/dist/client/index.cjs +1929 -1
  2. package/dist/client/index.js +1880 -1
  3. package/dist/client/mdx.cjs +7 -1
  4. package/dist/client/mdx.js +7 -1
  5. package/dist/client/primitives.cjs +60 -1
  6. package/dist/client/primitives.js +20 -1
  7. package/dist/docs-layout-BXHV0xw_.cjs +1431 -0
  8. package/dist/docs-layout-DwFndmj5.js +1231 -0
  9. package/dist/icons-dev-3cZMyt8r.cjs +1204 -0
  10. package/dist/icons-dev-Df8OQ481.js +839 -0
  11. package/dist/image-DtrI2cw3.cjs +268 -0
  12. package/dist/image-jxPb-2iV.js +214 -0
  13. package/dist/mdx-BdWkJTeB.cjs +523 -0
  14. package/dist/mdx-UTTLFWJq.js +494 -0
  15. package/dist/node/cli-entry.cjs +1 -1
  16. package/dist/node/cli-entry.mjs +1 -1
  17. package/dist/node/index.cjs +1 -1
  18. package/dist/node/index.mjs +1 -1
  19. package/dist/{node-DtEDyN1u.cjs → node-BSM4qcDK.cjs} +1 -1
  20. package/dist/{node-_1jhMGYx.mjs → node-BspZN3R2.mjs} +1 -1
  21. package/dist/{package-DrwtlXfk.cjs → package-DIIrjuWI.cjs} +1 -1
  22. package/dist/{package--0Yf0t1N.mjs → package-K0zsjGIz.mjs} +1 -1
  23. package/dist/{search-dialog-ByvGScjt.js → search-dialog-BHuIiUC6.js} +3 -1
  24. package/dist/search-dialog-BNF10tDl.js +375 -0
  25. package/dist/search-dialog-BwkDuI9R.cjs +220 -0
  26. package/dist/search-dialog-C7xuvyNk.cjs +386 -0
  27. package/dist/search-dialog-CIQg6k8c.cjs +8 -0
  28. package/dist/search-dialog-D-DDN7zJ.js +208 -0
  29. package/package.json +3 -4
  30. package/dist/docs-layout-KoWNZc8_.js +0 -6
  31. package/dist/docs-layout-x2yKt2cL.cjs +0 -6
  32. package/dist/icons-dev-B_RZIyxu.js +0 -6
  33. package/dist/icons-dev-BlV3wWFT.cjs +0 -6
  34. package/dist/image-BHhTvQzr.cjs +0 -6
  35. package/dist/image-CqKzYD8f.js +0 -6
  36. package/dist/mdx-DudBEac0.js +0 -7
  37. package/dist/mdx-r4cDQxWu.cjs +0 -7
  38. package/dist/search-dialog-B584t9ZF.js +0 -6
  39. package/dist/search-dialog-BvBopRsZ.cjs +0 -6
  40. package/dist/search-dialog-Cyko6TJm.cjs +0 -6
  41. package/dist/search-dialog-D6BNohIJ.js +0 -6
  42. package/dist/search-dialog-DuYTIefy.cjs +0 -6
  43. package/src/client/app/config-context.tsx +0 -51
  44. package/src/client/app/doc-page.tsx +0 -38
  45. package/src/client/app/docs-layout.tsx +0 -28
  46. package/src/client/app/head.tsx +0 -122
  47. package/src/client/app/helmet-compat.tsx +0 -36
  48. package/src/client/app/mdx-component.tsx +0 -8
  49. package/src/client/app/mdx-components-context.tsx +0 -72
  50. package/src/client/app/routes-context.tsx +0 -34
  51. package/src/client/app/scroll-handler.tsx +0 -74
  52. package/src/client/app/theme-context.tsx +0 -103
  53. package/src/client/app/ui-context.tsx +0 -42
  54. package/src/client/components/docs-layout-default.tsx +0 -85
  55. package/src/client/components/icons-dev.tsx +0 -282
  56. package/src/client/components/mdx/callout.tsx +0 -97
  57. package/src/client/components/mdx/card.tsx +0 -99
  58. package/src/client/components/mdx/cards.tsx +0 -27
  59. package/src/client/components/mdx/code-block.tsx +0 -184
  60. package/src/client/components/mdx/field.tsx +0 -33
  61. package/src/client/components/mdx/image.tsx +0 -44
  62. package/src/client/components/mdx/index.ts +0 -19
  63. package/src/client/components/mdx/table.tsx +0 -54
  64. package/src/client/components/mdx/typographics.tsx +0 -120
  65. package/src/client/components/mdx/use-code-block.ts +0 -34
  66. package/src/client/components/primitives/breadcrumbs.tsx +0 -54
  67. package/src/client/components/primitives/button-group.tsx +0 -54
  68. package/src/client/components/primitives/button.tsx +0 -6
  69. package/src/client/components/primitives/code-block.tsx +0 -120
  70. package/src/client/components/primitives/docs-layout.tsx +0 -125
  71. package/src/client/components/primitives/error-boundary.tsx +0 -107
  72. package/src/client/components/primitives/heading.tsx +0 -128
  73. package/src/client/components/primitives/helpers/observer.ts +0 -141
  74. package/src/client/components/primitives/image.tsx +0 -26
  75. package/src/client/components/primitives/link.tsx +0 -102
  76. package/src/client/components/primitives/menu.tsx +0 -137
  77. package/src/client/components/primitives/navbar.tsx +0 -466
  78. package/src/client/components/primitives/on-this-page.tsx +0 -430
  79. package/src/client/components/primitives/page-nav.tsx +0 -51
  80. package/src/client/components/primitives/popover.tsx +0 -28
  81. package/src/client/components/primitives/search-dialog.tsx +0 -193
  82. package/src/client/components/primitives/sidebar.tsx +0 -423
  83. package/src/client/components/primitives/skeleton.tsx +0 -26
  84. package/src/client/components/primitives/tabs.tsx +0 -70
  85. package/src/client/components/primitives/tooltip.tsx +0 -81
  86. package/src/client/components/primitives/types.ts +0 -11
  87. package/src/client/components/ui-base/banner.tsx +0 -66
  88. package/src/client/components/ui-base/breadcrumbs.tsx +0 -44
  89. package/src/client/components/ui-base/copy-markdown.tsx +0 -107
  90. package/src/client/components/ui-base/error-boundary.tsx +0 -15
  91. package/src/client/components/ui-base/github-stars.tsx +0 -29
  92. package/src/client/components/ui-base/icons.tsx +0 -240
  93. package/src/client/components/ui-base/index.ts +0 -16
  94. package/src/client/components/ui-base/last-updated.tsx +0 -27
  95. package/src/client/components/ui-base/navbar.tsx +0 -266
  96. package/src/client/components/ui-base/not-found.tsx +0 -26
  97. package/src/client/components/ui-base/on-this-page.tsx +0 -57
  98. package/src/client/components/ui-base/page-nav.tsx +0 -50
  99. package/src/client/components/ui-base/search-dialog.tsx +0 -163
  100. package/src/client/components/ui-base/search-highlight.tsx +0 -10
  101. package/src/client/components/ui-base/sidebar.tsx +0 -92
  102. package/src/client/components/ui-base/tabs.tsx +0 -83
  103. package/src/client/components/ui-base/theme-toggle.tsx +0 -130
  104. package/src/client/components/ui-base/version-i18n.tsx +0 -80
  105. package/src/client/hooks/index.ts +0 -13
  106. package/src/client/hooks/use-analytics.ts +0 -272
  107. package/src/client/hooks/use-breadcrumbs.ts +0 -22
  108. package/src/client/hooks/use-i18n.ts +0 -182
  109. package/src/client/hooks/use-localized-to.ts +0 -113
  110. package/src/client/hooks/use-location.ts +0 -5
  111. package/src/client/hooks/use-navbar.ts +0 -130
  112. package/src/client/hooks/use-page-nav.ts +0 -46
  113. package/src/client/hooks/use-routes.ts +0 -108
  114. package/src/client/hooks/use-search-highlight.ts +0 -185
  115. package/src/client/hooks/use-search.ts +0 -118
  116. package/src/client/hooks/use-sidebar.ts +0 -205
  117. package/src/client/hooks/use-tabs.ts +0 -46
  118. package/src/client/hooks/use-version.ts +0 -111
  119. package/src/client/index.ts +0 -31
  120. package/src/client/mdx.ts +0 -2
  121. package/src/client/primitives.ts +0 -19
  122. package/src/client/ssg/boltdocs-shell.tsx +0 -148
  123. package/src/client/ssg/create-routes.tsx +0 -473
  124. package/src/client/ssg/index.ts +0 -4
  125. package/src/client/ssg/mdx-page.tsx +0 -38
  126. package/src/client/store/boltdocs-context.tsx +0 -137
  127. package/src/client/theme/neutral.css +0 -141
  128. package/src/client/theme/reset.css +0 -189
  129. package/src/client/types.ts +0 -116
  130. package/src/client/utils/cn.ts +0 -6
  131. package/src/client/utils/copy-clipboard.ts +0 -22
  132. package/src/client/utils/get-base-file-path.ts +0 -21
  133. package/src/client/utils/github.ts +0 -121
  134. package/src/client/utils/i18n.ts +0 -23
  135. package/src/client/utils/path.ts +0 -9
  136. package/src/client/utils/react-to-text.ts +0 -34
  137. package/src/client/virtual.d.ts +0 -24
@@ -1,83 +0,0 @@
1
- import { useEffect } from 'react'
2
- import { useTabs as useTabsHook } from '../../hooks/use-tabs'
3
- import { Tabs as T } from '../primitives/tabs'
4
- import { Link } from '../primitives/link'
5
- import type { BoltdocsTab, ComponentRoute } from '../../types'
6
- import * as DefaultIcons from './icons'
7
- import virtualIcons from 'virtual:boltdocs-icons'
8
- import { getTranslated } from '../../utils/i18n'
9
- import { useRoutes } from '../../hooks/use-routes'
10
-
11
- export function Tabs({
12
- tabs,
13
- routes,
14
- }: {
15
- tabs: BoltdocsTab[]
16
- routes: ComponentRoute[]
17
- }) {
18
- const { currentLocale } = useRoutes()
19
- const { indicatorStyle, tabRefs, activeIndex } = useTabsHook(tabs, routes)
20
-
21
- useEffect(() => {
22
- const activeTab = tabRefs.current[activeIndex]
23
- if (activeTab) {
24
- activeTab.scrollIntoView({
25
- behavior: 'smooth',
26
- block: 'nearest',
27
- inline: 'center',
28
- })
29
- }
30
- }, [activeIndex])
31
-
32
- const renderTabIcon = (iconName?: string) => {
33
- if (!iconName) return null
34
- if (iconName.trim().startsWith('<svg')) {
35
- return (
36
- <span
37
- className="h-4 w-4"
38
- dangerouslySetInnerHTML={{ __html: iconName }}
39
- />
40
- )
41
- }
42
- const icons = { ...DefaultIcons, ...virtualIcons } as Record<string, any>
43
- const TabIcon = icons[iconName] || icons[iconName + 'Icon']
44
- if (TabIcon) {
45
- return <TabIcon size={16} />
46
- }
47
- return <img src={iconName} alt="" className="h-4 w-4 object-contain" />
48
- }
49
-
50
- return (
51
- <div className="mx-auto max-w-(--breakpoint-3xl) px-4 md:px-6 select-none">
52
- <T.List className="border-none py-0 scrollbar-hide relative flex flex-row items-center">
53
- {tabs.map((tab, index) => {
54
- const isActive = index === activeIndex
55
- const firstRoute = routes.find(
56
- (r) => r.tab && r.tab.toLowerCase() === tab.id.toLowerCase(),
57
- )
58
- const linkTo = firstRoute ? firstRoute.path : '#'
59
-
60
- return (
61
- <Link
62
- key={tab.id}
63
- href={linkTo}
64
- ref={(el: HTMLAnchorElement | null) => {
65
- tabRefs.current[index] = el
66
- }}
67
- className={`relative flex items-center gap-2 px-4 py-3.5 text-sm font-semibold transition-colors duration-300 outline-none whitespace-nowrap ${
68
- isActive ? 'text-primary-500' : 'text-muted hover:text-body'
69
- }`}
70
- >
71
- {renderTabIcon(tab.icon)}
72
- <span>{getTranslated(tab.text, currentLocale)}</span>
73
- </Link>
74
- )
75
- })}
76
- <T.Indicator
77
- style={indicatorStyle}
78
- className="h-0.5 bg-primary-500 rounded-full transition-all duration-300"
79
- />
80
- </T.List>
81
- </div>
82
- )
83
- }
@@ -1,130 +0,0 @@
1
- import { useEffect, useState } from 'react'
2
- import { Sun, Moon, Monitor } from './icons'
3
- import { useTheme } from '../../app/theme-context'
4
- import { Button } from 'react-aria-components'
5
- import { Menu } from '../primitives/menu'
6
- import { cn } from '../../utils/cn'
7
-
8
- export function ThemeToggle() {
9
- const { theme, setTheme } = useTheme()
10
- const [mounted, setMounted] = useState(false)
11
-
12
- useEffect(() => {
13
- setMounted(true)
14
- }, [])
15
-
16
- if (!mounted) {
17
- return <div className="h-9 w-9" />
18
- }
19
-
20
- const Icon = theme === 'system' ? Monitor : theme === 'dark' ? Moon : Sun
21
-
22
- return (
23
- <Menu.Trigger placement="bottom right">
24
- <Button
25
- className="flex h-9 w-9 items-center justify-center rounded-xl text-muted transition-colors hover:bg-surface hover:text-body outline-none border-none bg-transparent cursor-pointer"
26
- aria-label="Selection theme"
27
- >
28
- <Icon size={20} className="animate-in fade-in zoom-in duration-300" />
29
- </Button>
30
- <Menu.Root
31
- selectionMode="single"
32
- selectedKeys={[theme]}
33
- onSelectionChange={(keys) => {
34
- const newTheme = Array.from(keys)[0] as 'light' | 'dark' | 'system'
35
- setTheme(newTheme)
36
- }}
37
- className="w-36 bg-main border border-subtle rounded-xl p-1.5 shadow-md outline-none flex flex-col gap-0.5 animate-fade-in z-100"
38
- >
39
- <Menu.Item
40
- id="light"
41
- className="group flex items-center gap-2 px-3 py-2 rounded-lg text-xs font-medium text-body dark:hover:bg-primary-300/50 hover:bg-primary-200/50 transition-colors duration-100 cursor-pointer select-none outline-none group data-selected:text-primary-500 data-selected:bg-primary-500/5"
42
- >
43
- <Sun
44
- className="group-hover:text-primary-500 dark:group-hover:text-primary-200"
45
- size={16}
46
- />
47
- <span className="ml-2">Light</span>
48
- </Menu.Item>
49
- <Menu.Item
50
- id="dark"
51
- className="flex items-center gap-2 px-3 py-2 rounded-lg text-xs font-medium text-body dark:hover:bg-primary-300/50 hover:bg-primary-200/50 transition-colors duration-100 cursor-pointer select-none outline-none group data-selected:text-primary-500 data-selected:bg-primary-500/5"
52
- >
53
- <Moon
54
- className="group-hover:text-primary-500 dark:group-hover:text-primary-200"
55
- size={16}
56
- />
57
- <span className="ml-2">Dark</span>
58
- </Menu.Item>
59
- <Menu.Item
60
- id="system"
61
- className="flex items-center gap-2 px-3 py-2 rounded-lg text-xs font-medium text-body dark:hover:bg-primary-300/50 hover:bg-primary-200/50 transition-colors duration-100 cursor-pointer select-none outline-none group data-selected:text-primary-500 data-selected:bg-primary-500/5"
62
- >
63
- <Monitor
64
- className="group-hover:text-primary-500 dark:group-hover:text-primary-200"
65
- size={16}
66
- />
67
- <span className="ml-2">System</span>
68
- </Menu.Item>
69
- </Menu.Root>
70
- </Menu.Trigger>
71
- )
72
- }
73
-
74
- export function ThemeSwitcher({ className }: { className?: string }) {
75
- const { theme, setTheme } = useTheme()
76
- const [mounted, setMounted] = useState(false)
77
-
78
- useEffect(() => {
79
- setMounted(true)
80
- }, [])
81
-
82
- if (!mounted) {
83
- return (
84
- <div
85
- className={cn(
86
- 'h-10 w-full bg-surface rounded-xl animate-pulse',
87
- className,
88
- )}
89
- />
90
- )
91
- }
92
-
93
- const isDark = theme === 'dark'
94
-
95
- return (
96
- <div
97
- className={cn(
98
- 'flex p-1 bg-surface border border-subtle rounded-xl relative w-full h-11',
99
- className,
100
- )}
101
- >
102
- <div
103
- className={cn(
104
- 'absolute inset-y-1 w-[calc(50%-4px)] bg-main border border-subtle rounded-lg transition-all duration-300 ease-out shadow-xs',
105
- isDark ? 'translate-x-full' : 'translate-x-0',
106
- )}
107
- />
108
- <button
109
- onClick={() => setTheme('light')}
110
- className={cn(
111
- 'flex-1 flex items-center justify-center rounded-lg z-10 transition-colors outline-none cursor-pointer border-none bg-transparent',
112
- !isDark ? 'text-body font-semibold' : 'text-muted hover:text-body',
113
- )}
114
- aria-label="Light mode"
115
- >
116
- <Sun size={18} />
117
- </button>
118
- <button
119
- onClick={() => setTheme('dark')}
120
- className={cn(
121
- 'flex-1 flex items-center justify-center rounded-lg z-10 transition-colors outline-none cursor-pointer border-none bg-transparent',
122
- isDark ? 'text-body font-semibold' : 'text-muted hover:text-body',
123
- )}
124
- aria-label="Dark mode"
125
- >
126
- <Moon size={18} />
127
- </button>
128
- </div>
129
- )
130
- }
@@ -1,80 +0,0 @@
1
- import { useVersion } from '../../hooks/use-version'
2
- import { useI18n } from '../../hooks/use-i18n'
3
- import { Menu } from '../primitives/menu'
4
- import { Button } from '../primitives/button'
5
- import { ChevronDown, Languages } from './icons'
6
- import { cn } from '../../utils/cn'
7
-
8
- export function VersionSelector({ className }: { className?: string }) {
9
- const { currentVersionLabel, availableVersions, handleVersionChange } =
10
- useVersion()
11
-
12
- if (availableVersions.length === 0) return null
13
-
14
- return (
15
- <Menu.Trigger>
16
- <Button
17
- className={cn(
18
- 'flex h-9 items-center justify-between gap-2 border border-subtle bg-surface px-4 py-1.5 rounded-xl text-xs font-semibold text-body hover:bg-primary-50/20 hover:border-primary-500/50 transition-all duration-300 outline-none select-none cursor-pointer',
19
- className,
20
- )}
21
- >
22
- <span className="font-semibold text-[0.8125rem]">
23
- {currentVersionLabel}
24
- </span>
25
- <ChevronDown className="w-3.5 h-3.5 text-muted/60" />
26
- </Button>
27
- <Menu.Root className="w-40 bg-main border border-subtle rounded-xl p-1.5 shadow-md outline-none flex flex-col gap-0.5 animate-fade-in z-100">
28
- <Menu.Section items={availableVersions}>
29
- {(version) => (
30
- <Menu.Item
31
- key={`${version.value ?? ''}`}
32
- onPress={() => handleVersionChange(version.value)}
33
- className="flex items-center gap-2 px-3 py-2 rounded-lg text-xs font-medium text-body hover:bg-primary-50/50 cursor-pointer select-none outline-none group data-selected:text-primary-500 data-selected:bg-primary-500/5"
34
- >
35
- {version.label as string}
36
- </Menu.Item>
37
- )}
38
- </Menu.Section>
39
- </Menu.Root>
40
- </Menu.Trigger>
41
- )
42
- }
43
-
44
- export function I18nSelector({ className }: { className?: string }) {
45
- const { currentLocale, availableLocales, handleLocaleChange } = useI18n()
46
-
47
- if (availableLocales.length === 0) return null
48
-
49
- return (
50
- <Menu.Trigger>
51
- <Button
52
- className={cn(
53
- 'flex h-9 items-center justify-between gap-2 border border-subtle bg-surface px-4 py-1.5 rounded-xl text-xs font-semibold text-body hover:bg-primary-50/20 hover:border-primary-500/50 transition-all duration-300 outline-none select-none cursor-pointer',
54
- className,
55
- )}
56
- >
57
- <div className="flex items-center gap-1.5">
58
- <Languages className="w-3.5 h-3.5 text-primary-500" />
59
- <span className="font-bold text-[0.75rem] uppercase opacity-90">
60
- {currentLocale || 'en'}
61
- </span>
62
- </div>
63
- <ChevronDown className="w-3.5 h-3.5 text-muted/60" />
64
- </Button>
65
- <Menu.Root className="w-40 bg-main border border-subtle rounded-xl p-1.5 shadow-md outline-none flex flex-col gap-0.5 animate-fade-in z-100">
66
- <Menu.Section items={availableLocales}>
67
- {(locale) => (
68
- <Menu.Item
69
- key={`${locale.value ?? ''}`}
70
- onPress={() => handleLocaleChange(locale.value)}
71
- className="flex items-center gap-2 px-3 py-2 rounded-lg text-xs font-medium text-body dark:hover:bg-primary-300/50 hover:bg-primary-200/50 transition-colors duration-100 cursor-pointer select-none outline-none group data-selected:text-primary-500 data-selected:bg-primary-500/5"
72
- >
73
- <span>{locale.label as string}</span>
74
- </Menu.Item>
75
- )}
76
- </Menu.Section>
77
- </Menu.Root>
78
- </Menu.Trigger>
79
- )
80
- }
@@ -1,13 +0,0 @@
1
- export { useNavbar } from './use-navbar'
2
- export { useSidebar } from './use-sidebar'
3
- export { useSearch } from './use-search'
4
- export { useTabs } from './use-tabs'
5
- export { useVersion } from './use-version'
6
- export { useI18n } from './use-i18n'
7
- export { usePageNav } from './use-page-nav'
8
- export { useBreadcrumbs } from './use-breadcrumbs'
9
- export { useRoutes } from './use-routes'
10
- export { useLocalizedTo } from './use-localized-to'
11
- export { useLocation } from './use-location'
12
- export { useSearchHighlight } from './use-search-highlight'
13
- export { useAnalytics, useTrackPageView, useTrackEvent } from './use-analytics'
@@ -1,272 +0,0 @@
1
- import { useCallback, useEffect, useMemo, useRef } from 'react'
2
- import { useLocation } from './use-location'
3
- import type { BoltdocsIntegrationsConfig } from '../../shared/types'
4
-
5
- declare global {
6
- interface Window {
7
- gtag?: (...args: unknown[]) => void
8
- dataLayer?: unknown[]
9
- gtag_report_conversion?: (url?: string) => boolean
10
- }
11
- }
12
-
13
- export interface AnalyticsEvent {
14
- action: string
15
- category?: string
16
- label?: string
17
- value?: number
18
- params?: Record<string, unknown>
19
- }
20
-
21
- export interface AnalyticsInstance {
22
- trackPageView: (path: string, title?: string) => void
23
- trackEvent: (event: AnalyticsEvent) => void
24
- trackSearch: (query: string, resultsCount?: number) => void
25
- trackDownload: (file: string, type?: string) => void
26
- trackExternalLink: (url: string) => void
27
- isEnabled: boolean
28
- }
29
-
30
- function createAnalyticsInstance(
31
- config?: BoltdocsIntegrationsConfig,
32
- ): AnalyticsInstance {
33
- if (typeof window === 'undefined') {
34
- return createDisabledAnalytics()
35
- }
36
-
37
- const isGtagAvailable = typeof window.gtag === 'function'
38
-
39
- if (isGtagAvailable) {
40
- return createGtagAnalytics(config)
41
- }
42
-
43
- if (window.dataLayer) {
44
- return createDataLayerAnalytics(config)
45
- }
46
-
47
- return createDisabledAnalytics()
48
- }
49
-
50
- function createGtagAnalytics(
51
- config?: BoltdocsIntegrationsConfig,
52
- ): AnalyticsInstance {
53
- return {
54
- trackPageView: (path: string, title?: string) => {
55
- window.gtag?.('event', 'page_view', {
56
- page_path: path,
57
- page_title: title || document.title,
58
- send_to: config?.ga4?.measurementId,
59
- })
60
- },
61
- trackEvent: ({ action, category, label, value, params }) => {
62
- window.gtag?.('event', action, {
63
- event_category: category,
64
- event_label: label,
65
- value,
66
- send_to: config?.ga4?.measurementId,
67
- ...params,
68
- })
69
- },
70
- trackSearch: (query: string, resultsCount?: number) => {
71
- window.gtag?.('event', 'search', {
72
- search_term: query,
73
- results_count: resultsCount,
74
- send_to: config?.ga4?.measurementId,
75
- })
76
- },
77
- trackDownload: (file: string, type?: string) => {
78
- window.gtag?.('event', 'file_download', {
79
- file_name: file,
80
- file_type: type || file.split('.').pop(),
81
- send_to: config?.ga4?.measurementId,
82
- })
83
- },
84
- trackExternalLink: (url: string) => {
85
- window.gtag?.('event', 'external_link', {
86
- link_url: url,
87
- send_to: config?.ga4?.measurementId,
88
- })
89
- },
90
- isEnabled: true,
91
- }
92
- }
93
-
94
- function createDataLayerAnalytics(
95
- config?: BoltdocsIntegrationsConfig,
96
- ): AnalyticsInstance {
97
- return {
98
- trackPageView: (path: string, title?: string) => {
99
- window.dataLayer?.push({
100
- event: 'page_view',
101
- page_path: path,
102
- page_title: title || document.title,
103
- send_to: config?.gtm?.tagId,
104
- })
105
- },
106
- trackEvent: ({ action, category, label, value, params }) => {
107
- window.dataLayer?.push({
108
- event: action,
109
- event_category: category,
110
- event_label: label,
111
- value,
112
- send_to: config?.gtm?.tagId,
113
- ...params,
114
- })
115
- },
116
- trackSearch: (query: string, resultsCount?: number) => {
117
- window.dataLayer?.push({
118
- event: 'search',
119
- search_term: query,
120
- results_count: resultsCount,
121
- send_to: config?.gtm?.tagId,
122
- })
123
- },
124
- trackDownload: (file: string, type?: string) => {
125
- window.dataLayer?.push({
126
- event: 'file_download',
127
- file_name: file,
128
- file_type: type || file.split('.').pop(),
129
- send_to: config?.gtm?.tagId,
130
- })
131
- },
132
- trackExternalLink: (url: string) => {
133
- window.dataLayer?.push({
134
- event: 'external_link',
135
- link_url: url,
136
- send_to: config?.gtm?.tagId,
137
- })
138
- },
139
- isEnabled: true,
140
- }
141
- }
142
-
143
- function createDisabledAnalytics(): AnalyticsInstance {
144
- return {
145
- trackPageView: () => {},
146
- trackEvent: () => {},
147
- trackSearch: () => {},
148
- trackDownload: () => {},
149
- trackExternalLink: () => {},
150
- isEnabled: false,
151
- }
152
- }
153
-
154
- export interface UseAnalyticsOptions {
155
- config?: BoltdocsIntegrationsConfig
156
- autoTrackPageViews?: boolean
157
- autoTrackDownloads?: boolean
158
- autoTrackExternalLinks?: boolean
159
- excludePatterns?: RegExp[]
160
- }
161
-
162
- const CONFIG_INSTANCE_SYMBOL = Symbol.for('__BDOCS_CONFIG_INSTANCE__')
163
-
164
- export function useAnalytics(options: UseAnalyticsOptions = {}) {
165
- const {
166
- config: optionsConfig,
167
- autoTrackPageViews = true,
168
- autoTrackDownloads = true,
169
- autoTrackExternalLinks = true,
170
- excludePatterns = [],
171
- } = options
172
-
173
- const globalConfig =
174
- typeof globalThis !== 'undefined'
175
- ? ((globalThis as any)[CONFIG_INSTANCE_SYMBOL] as
176
- | { integrations?: BoltdocsIntegrationsConfig }
177
- | undefined)
178
- : undefined
179
- const config = optionsConfig ?? globalConfig?.integrations
180
-
181
- const analytics = useMemo(() => createAnalyticsInstance(config), [config])
182
-
183
- const previousPath = useRef<string>('')
184
- const location = useLocation()
185
-
186
- useEffect(() => {
187
- if (!autoTrackPageViews || !analytics.isEnabled) return
188
-
189
- const path = location.pathname + location.search
190
-
191
- if (path !== previousPath.current) {
192
- previousPath.current = path
193
- analytics.trackPageView(path, document.title)
194
- }
195
- }, [location.pathname, autoTrackPageViews, analytics])
196
-
197
- useEffect(() => {
198
- if (!autoTrackDownloads || !analytics.isEnabled) return
199
-
200
- const handleClick = (event: MouseEvent) => {
201
- const target = (event.target as Element)?.closest('a')
202
- if (!target) return
203
-
204
- const href = target.getAttribute('href')
205
- if (!href) return
206
-
207
- if (excludePatterns.some((pattern) => pattern.test(href))) return
208
-
209
- const isDownload =
210
- target.hasAttribute('download') ||
211
- /\.(pdf|doc|docx|xls|xlsx|ppt|pptx|zip|rar|7z|tar|gz|mp3|mp4|avi|mov|png|jpg|jpeg|gif|svg|webp)$/i.test(
212
- href,
213
- )
214
-
215
- if (isDownload) {
216
- const fileName = href.split('/').pop() || href
217
- analytics.trackDownload(fileName, fileName.split('.').pop())
218
- }
219
- }
220
-
221
- document.addEventListener('click', handleClick)
222
- return () => document.removeEventListener('click', handleClick)
223
- }, [autoTrackDownloads, autoTrackExternalLinks, analytics, excludePatterns])
224
-
225
- useEffect(() => {
226
- if (!autoTrackExternalLinks || !analytics.isEnabled) return
227
-
228
- const handleClick = (event: MouseEvent) => {
229
- const target = (event.target as Element)?.closest('a')
230
- if (!target) return
231
-
232
- const href = target.getAttribute('href')
233
- if (!href) return
234
-
235
- if (excludePatterns.some((pattern) => pattern.test(href))) return
236
-
237
- const isExternal =
238
- href.startsWith('http://') ||
239
- href.startsWith('https://') ||
240
- href.startsWith('//')
241
-
242
- if (isExternal && !href.includes(window.location.hostname)) {
243
- analytics.trackExternalLink(href)
244
- }
245
- }
246
-
247
- document.addEventListener('click', handleClick)
248
- return () => document.removeEventListener('click', handleClick)
249
- }, [autoTrackExternalLinks, analytics, excludePatterns])
250
-
251
- return analytics
252
- }
253
-
254
- export function useTrackPageView() {
255
- const analytics = useMemo(() => createAnalyticsInstance(), [])
256
- return useCallback(
257
- (path: string, title?: string) => {
258
- analytics.trackPageView(path, title)
259
- },
260
- [analytics],
261
- )
262
- }
263
-
264
- export function useTrackEvent() {
265
- const analytics = useMemo(() => createAnalyticsInstance(), [])
266
- return useCallback(
267
- (event: AnalyticsEvent) => {
268
- analytics.trackEvent(event)
269
- },
270
- [analytics],
271
- )
272
- }
@@ -1,22 +0,0 @@
1
- import { useRoutes } from './use-routes'
2
-
3
- /**
4
- * Hook to generate breadcrumbs based on the current active route.
5
- */
6
- export function useBreadcrumbs() {
7
- const { currentRoute: activeRoute } = useRoutes()
8
-
9
- const crumbs: Array<{ label: string; href?: string }> = []
10
-
11
- if (activeRoute) {
12
- if (activeRoute.groupTitle) {
13
- crumbs.push({ label: activeRoute.groupTitle })
14
- }
15
- crumbs.push({ label: activeRoute.title, href: activeRoute.path })
16
- }
17
-
18
- return {
19
- crumbs,
20
- activeRoute,
21
- }
22
- }