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.
- package/package.json +29 -7
- package/src/client/app/config-context.tsx +18 -0
- package/src/client/app/docs-layout.tsx +14 -0
- package/src/client/app/index.tsx +132 -260
- package/src/client/app/mdx-component.tsx +52 -0
- package/src/client/app/mdx-components-context.tsx +23 -0
- package/src/client/app/mdx-page.tsx +20 -0
- package/src/client/app/preload.tsx +38 -30
- package/src/client/app/router.tsx +30 -0
- package/src/client/app/scroll-handler.tsx +40 -0
- package/src/client/app/theme-context.tsx +75 -0
- package/src/client/components/default-layout.tsx +80 -0
- package/src/client/components/docs-layout.tsx +105 -0
- package/src/client/components/icons-dev.tsx +74 -0
- package/src/client/components/mdx/admonition.tsx +107 -0
- package/src/client/components/mdx/badge.tsx +41 -0
- package/src/client/components/mdx/button.tsx +35 -0
- package/src/client/components/mdx/card.tsx +124 -0
- package/src/client/components/mdx/code-block.tsx +119 -0
- package/src/client/components/mdx/component-preview.tsx +47 -0
- package/src/client/components/mdx/component-props.tsx +83 -0
- package/src/client/components/mdx/field.tsx +66 -0
- package/src/client/components/mdx/file-tree.tsx +287 -0
- package/src/client/components/mdx/hooks/use-code-block.ts +56 -0
- package/src/client/components/mdx/hooks/use-component-preview.ts +16 -0
- package/src/client/components/mdx/hooks/useTable.ts +74 -0
- package/src/client/components/mdx/hooks/useTabs.ts +68 -0
- package/src/client/components/mdx/image.tsx +23 -0
- package/src/client/components/mdx/index.ts +53 -0
- package/src/client/components/mdx/link.tsx +38 -0
- package/src/client/components/mdx/list.tsx +192 -0
- package/src/client/components/mdx/table.tsx +156 -0
- package/src/client/components/mdx/tabs.tsx +135 -0
- package/src/client/components/mdx/video.tsx +68 -0
- package/src/client/components/primitives/breadcrumbs.tsx +79 -0
- package/src/client/components/primitives/button-group.tsx +54 -0
- package/src/client/components/primitives/button.tsx +145 -0
- package/src/client/components/primitives/helpers/observer.ts +120 -0
- package/src/client/components/primitives/index.ts +17 -0
- package/src/client/components/primitives/link.tsx +122 -0
- package/src/client/components/primitives/menu.tsx +159 -0
- package/src/client/components/primitives/navbar.tsx +359 -0
- package/src/client/components/primitives/navigation-menu.tsx +116 -0
- package/src/client/components/primitives/on-this-page.tsx +461 -0
- package/src/client/components/primitives/page-nav.tsx +87 -0
- package/src/client/components/primitives/popover.tsx +47 -0
- package/src/client/components/primitives/search-dialog.tsx +183 -0
- package/src/client/components/primitives/sidebar.tsx +154 -0
- package/src/client/components/primitives/tabs.tsx +90 -0
- package/src/client/components/primitives/tooltip.tsx +83 -0
- package/src/client/components/primitives/types.ts +11 -0
- package/src/client/components/ui-base/breadcrumbs.tsx +42 -0
- package/src/client/components/ui-base/copy-markdown.tsx +112 -0
- package/src/client/components/ui-base/error-boundary.tsx +52 -0
- package/src/client/components/ui-base/github-stars.tsx +27 -0
- package/src/client/components/ui-base/head.tsx +69 -0
- package/src/client/components/ui-base/loading.tsx +87 -0
- package/src/client/components/ui-base/navbar.tsx +138 -0
- package/src/client/components/ui-base/not-found.tsx +24 -0
- package/src/client/components/ui-base/on-this-page.tsx +152 -0
- package/src/client/components/ui-base/page-nav.tsx +39 -0
- package/src/client/components/ui-base/powered-by.tsx +19 -0
- package/src/client/components/ui-base/progress-bar.tsx +67 -0
- package/src/client/components/ui-base/search-dialog.tsx +82 -0
- package/src/client/components/ui-base/sidebar.tsx +104 -0
- package/src/client/components/ui-base/tabs.tsx +65 -0
- package/src/client/components/ui-base/theme-toggle.tsx +32 -0
- package/src/client/hooks/index.ts +12 -0
- package/src/client/hooks/use-breadcrumbs.ts +22 -0
- package/src/client/hooks/use-i18n.ts +84 -0
- package/src/client/hooks/use-localized-to.ts +95 -0
- package/src/client/hooks/use-location.ts +5 -0
- package/src/client/hooks/use-navbar.ts +60 -0
- package/src/client/hooks/use-onthispage.ts +23 -0
- package/src/client/hooks/use-page-nav.ts +22 -0
- package/src/client/hooks/use-routes.ts +72 -0
- package/src/client/hooks/use-search.ts +71 -0
- package/src/client/hooks/use-sidebar.ts +49 -0
- package/src/client/hooks/use-tabs.ts +43 -0
- package/src/client/hooks/use-version.ts +78 -0
- package/src/client/index.ts +55 -17
- package/src/client/integrations/codesandbox.ts +179 -0
- package/src/client/ssr.tsx +27 -16
- package/src/client/theme/neutral.css +360 -0
- package/src/client/types.ts +131 -27
- package/src/client/utils/cn.ts +6 -0
- package/src/client/utils/copy-clipboard.ts +22 -0
- package/src/client/utils/get-base-file-path.ts +21 -0
- package/src/client/utils/github.ts +121 -0
- package/src/client/utils/use-on-change.ts +15 -0
- package/src/client/virtual.d.ts +24 -0
- package/src/node/cache.ts +156 -156
- package/src/node/config.ts +159 -103
- package/src/node/index.ts +13 -13
- package/src/node/mdx.ts +213 -61
- package/src/node/plugin/entry.ts +29 -18
- package/src/node/plugin/html.ts +11 -11
- package/src/node/plugin/index.ts +161 -83
- package/src/node/plugin/types.ts +2 -4
- package/src/node/routes/cache.ts +6 -6
- package/src/node/routes/index.ts +206 -113
- package/src/node/routes/parser.ts +106 -81
- package/src/node/routes/sorter.ts +15 -15
- package/src/node/routes/types.ts +24 -24
- package/src/node/ssg/index.ts +46 -46
- package/src/node/ssg/meta.ts +4 -4
- package/src/node/ssg/options.ts +5 -5
- package/src/node/ssg/sitemap.ts +14 -14
- package/src/node/utils.ts +31 -31
- package/tsconfig.json +25 -20
- package/tsup.config.ts +23 -14
- package/dist/PackageManagerTabs-NVT7G625.mjs +0 -99
- package/dist/SearchDialog-AGVF6JBO.mjs +0 -194
- package/dist/SearchDialog-YPDOM7Q6.css +0 -2847
- package/dist/Video-KNTY5BNO.mjs +0 -6
- package/dist/cache-KNL5B4EE.mjs +0 -12
- package/dist/chunk-7SFUJWTB.mjs +0 -211
- package/dist/chunk-FFBNU6IJ.mjs +0 -386
- package/dist/chunk-FMTOYQLO.mjs +0 -37
- package/dist/chunk-TKLQWU7H.mjs +0 -1920
- package/dist/chunk-Z7JHYNAS.mjs +0 -57
- package/dist/client/index.css +0 -2847
- package/dist/client/index.d.mts +0 -372
- package/dist/client/index.d.ts +0 -372
- package/dist/client/index.js +0 -3630
- package/dist/client/index.mjs +0 -697
- package/dist/client/ssr.css +0 -2847
- package/dist/client/ssr.d.mts +0 -27
- package/dist/client/ssr.d.ts +0 -27
- package/dist/client/ssr.js +0 -2928
- package/dist/client/ssr.mjs +0 -33
- package/dist/config-BsFQ-ErD.d.mts +0 -159
- package/dist/config-BsFQ-ErD.d.ts +0 -159
- package/dist/node/index.d.mts +0 -91
- package/dist/node/index.d.ts +0 -91
- package/dist/node/index.js +0 -1187
- package/dist/node/index.mjs +0 -762
- package/dist/types-Dj-bfnC3.d.mts +0 -74
- package/dist/types-Dj-bfnC3.d.ts +0 -74
- package/src/client/theme/components/CodeBlock/CodeBlock.tsx +0 -61
- package/src/client/theme/components/CodeBlock/index.ts +0 -1
- package/src/client/theme/components/PackageManagerTabs/PackageManagerTabs.tsx +0 -131
- package/src/client/theme/components/PackageManagerTabs/index.ts +0 -1
- package/src/client/theme/components/PackageManagerTabs/pkg-tabs.css +0 -64
- package/src/client/theme/components/Playground/Playground.tsx +0 -180
- package/src/client/theme/components/Playground/index.ts +0 -1
- package/src/client/theme/components/Playground/playground.css +0 -238
- package/src/client/theme/components/Video/Video.tsx +0 -84
- package/src/client/theme/components/Video/index.ts +0 -1
- package/src/client/theme/components/Video/video.css +0 -41
- package/src/client/theme/components/mdx/Admonition.tsx +0 -80
- package/src/client/theme/components/mdx/Badge.tsx +0 -31
- package/src/client/theme/components/mdx/Button.tsx +0 -50
- package/src/client/theme/components/mdx/Card.tsx +0 -80
- package/src/client/theme/components/mdx/Field.tsx +0 -60
- package/src/client/theme/components/mdx/FileTree.tsx +0 -229
- package/src/client/theme/components/mdx/List.tsx +0 -57
- package/src/client/theme/components/mdx/Table.tsx +0 -151
- package/src/client/theme/components/mdx/Tabs.tsx +0 -123
- package/src/client/theme/components/mdx/index.ts +0 -27
- package/src/client/theme/components/mdx/mdx-components.css +0 -764
- package/src/client/theme/icons/bun.tsx +0 -62
- package/src/client/theme/icons/deno.tsx +0 -20
- package/src/client/theme/icons/discord.tsx +0 -12
- package/src/client/theme/icons/github.tsx +0 -15
- package/src/client/theme/icons/npm.tsx +0 -13
- package/src/client/theme/icons/pnpm.tsx +0 -72
- package/src/client/theme/icons/twitter.tsx +0 -12
- package/src/client/theme/styles/markdown.css +0 -394
- package/src/client/theme/styles/variables.css +0 -175
- package/src/client/theme/styles.css +0 -39
- package/src/client/theme/ui/Breadcrumbs/Breadcrumbs.tsx +0 -68
- package/src/client/theme/ui/Breadcrumbs/index.ts +0 -1
- package/src/client/theme/ui/CopyMarkdown/CopyMarkdown.tsx +0 -82
- package/src/client/theme/ui/CopyMarkdown/copy-markdown.css +0 -112
- package/src/client/theme/ui/CopyMarkdown/index.ts +0 -1
- package/src/client/theme/ui/ErrorBoundary/ErrorBoundary.tsx +0 -50
- package/src/client/theme/ui/ErrorBoundary/error-boundary.css +0 -55
- package/src/client/theme/ui/ErrorBoundary/index.ts +0 -1
- package/src/client/theme/ui/Footer/footer.css +0 -32
- package/src/client/theme/ui/Head/Head.tsx +0 -69
- package/src/client/theme/ui/Head/index.ts +0 -1
- package/src/client/theme/ui/LanguageSwitcher/LanguageSwitcher.tsx +0 -125
- package/src/client/theme/ui/LanguageSwitcher/index.ts +0 -1
- package/src/client/theme/ui/LanguageSwitcher/language-switcher.css +0 -98
- package/src/client/theme/ui/Layout/Layout.tsx +0 -203
- package/src/client/theme/ui/Layout/base.css +0 -106
- package/src/client/theme/ui/Layout/index.ts +0 -2
- package/src/client/theme/ui/Layout/pagination.css +0 -72
- package/src/client/theme/ui/Layout/responsive.css +0 -47
- package/src/client/theme/ui/Link/Link.tsx +0 -392
- package/src/client/theme/ui/Link/LinkPreview.tsx +0 -59
- package/src/client/theme/ui/Link/index.ts +0 -2
- package/src/client/theme/ui/Link/link-preview.css +0 -48
- package/src/client/theme/ui/Loading/Loading.tsx +0 -10
- package/src/client/theme/ui/Loading/index.ts +0 -1
- package/src/client/theme/ui/Loading/loading.css +0 -30
- package/src/client/theme/ui/Navbar/GithubStars.tsx +0 -27
- package/src/client/theme/ui/Navbar/Navbar.tsx +0 -193
- package/src/client/theme/ui/Navbar/Tabs.tsx +0 -99
- package/src/client/theme/ui/Navbar/index.ts +0 -2
- package/src/client/theme/ui/Navbar/navbar.css +0 -347
- package/src/client/theme/ui/NotFound/NotFound.tsx +0 -19
- package/src/client/theme/ui/NotFound/index.ts +0 -1
- package/src/client/theme/ui/NotFound/not-found.css +0 -64
- package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +0 -244
- package/src/client/theme/ui/OnThisPage/index.ts +0 -1
- package/src/client/theme/ui/OnThisPage/toc.css +0 -152
- package/src/client/theme/ui/PoweredBy/PoweredBy.tsx +0 -18
- package/src/client/theme/ui/PoweredBy/index.ts +0 -1
- package/src/client/theme/ui/PoweredBy/powered-by.css +0 -76
- package/src/client/theme/ui/ProgressBar/ProgressBar.css +0 -17
- package/src/client/theme/ui/ProgressBar/ProgressBar.tsx +0 -51
- package/src/client/theme/ui/ProgressBar/index.ts +0 -1
- package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +0 -209
- package/src/client/theme/ui/SearchDialog/index.ts +0 -1
- package/src/client/theme/ui/SearchDialog/search.css +0 -152
- package/src/client/theme/ui/Sidebar/Sidebar.tsx +0 -244
- package/src/client/theme/ui/Sidebar/index.ts +0 -1
- package/src/client/theme/ui/Sidebar/sidebar.css +0 -230
- package/src/client/theme/ui/ThemeToggle/ThemeToggle.tsx +0 -69
- package/src/client/theme/ui/ThemeToggle/index.ts +0 -1
- package/src/client/theme/ui/VersionSwitcher/VersionSwitcher.tsx +0 -136
- package/src/client/theme/ui/VersionSwitcher/index.ts +0 -1
- package/src/client/utils.ts +0 -49
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import * as RAC from 'react-aria-components'
|
|
2
|
+
import { cn } from '@client/utils/cn'
|
|
3
|
+
import { cva } from 'class-variance-authority'
|
|
4
|
+
import type { VariantProps } from 'class-variance-authority'
|
|
5
|
+
|
|
6
|
+
export const buttonVariants = cva(
|
|
7
|
+
'flex flex-row items-center justify-center w-auto font-semibold tracking-tight no-underline whitespace-nowrap select-none outline-none transition-all duration-200 cursor-pointer pressed:scale-[0.97] hover:-translate-y-px leading-none',
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
primary:
|
|
12
|
+
'bg-primary-500 text-white shadow-md hover:brightness-110 hover:shadow-lg',
|
|
13
|
+
secondary:
|
|
14
|
+
'bg-bg-surface text-text-main border border-border-subtle hover:bg-bg-muted hover:border-border-strong',
|
|
15
|
+
outline:
|
|
16
|
+
'bg-transparent text-text-main border border-border-strong hover:bg-bg-surface hover:border-primary-500',
|
|
17
|
+
ghost:
|
|
18
|
+
'bg-transparent text-text-muted hover:bg-bg-surface hover:text-text-main',
|
|
19
|
+
danger:
|
|
20
|
+
'bg-[var(--color-danger-500)]/10 text-[var(--color-danger-500)] border border-[var(--color-danger-500)]/20 hover:bg-[var(--color-danger-500)]/15',
|
|
21
|
+
success:
|
|
22
|
+
'bg-[var(--color-success-500)]/10 text-[var(--color-success-500)] border border-[var(--color-success-500)]/20 hover:bg-[var(--color-success-500)]/15',
|
|
23
|
+
warning:
|
|
24
|
+
'bg-[var(--color-warning-500)]/10 text-[var(--color-warning-500)] border border-[var(--color-warning-500)]/20 hover:bg-[var(--color-warning-500)]/15',
|
|
25
|
+
info: 'bg-[var(--color-info-500)]/10 text-[var(--color-info-500)] border border-[var(--color-info-500)]/20 hover:bg-[var(--color-info-500)]/15',
|
|
26
|
+
subtle: 'bg-primary-500/10 text-primary-500 hover:bg-primary-500/20',
|
|
27
|
+
link: 'bg-transparent text-primary-500 !p-0 !min-h-0 hover:underline',
|
|
28
|
+
},
|
|
29
|
+
size: {
|
|
30
|
+
sm: 'min-h-8 px-3.5 text-[0.8125rem] gap-1.5',
|
|
31
|
+
md: 'min-h-10 px-5 text-[0.9375rem] gap-2',
|
|
32
|
+
lg: 'min-h-12 px-7 text-[1.05rem] gap-2.5',
|
|
33
|
+
},
|
|
34
|
+
rounded: {
|
|
35
|
+
none: 'rounded-none',
|
|
36
|
+
sm: 'rounded-sm',
|
|
37
|
+
md: 'rounded-md',
|
|
38
|
+
lg: 'rounded-lg',
|
|
39
|
+
full: 'rounded-full',
|
|
40
|
+
},
|
|
41
|
+
iconSize: {
|
|
42
|
+
sm: 'w-8 h-8 p-0',
|
|
43
|
+
md: 'w-10 h-10 p-0',
|
|
44
|
+
lg: 'w-12 h-12 p-0',
|
|
45
|
+
},
|
|
46
|
+
disabled: {
|
|
47
|
+
true: 'opacity-50 cursor-not-allowed pointer-events-none',
|
|
48
|
+
false: null,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
defaultVariants: {
|
|
52
|
+
variant: 'primary',
|
|
53
|
+
size: 'md',
|
|
54
|
+
rounded: 'md',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
)
|
|
58
|
+
type ButtonVariantType = VariantProps<typeof buttonVariants>
|
|
59
|
+
|
|
60
|
+
export interface ButtonProps
|
|
61
|
+
extends Omit<RAC.ButtonProps, 'children' | 'className'>,
|
|
62
|
+
ButtonVariantType {
|
|
63
|
+
icon?: React.ReactNode
|
|
64
|
+
iconPosition?: 'left' | 'right'
|
|
65
|
+
href?: string
|
|
66
|
+
children?: React.ReactNode
|
|
67
|
+
className?: string
|
|
68
|
+
isIconOnly?: boolean
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const Button = ({
|
|
72
|
+
href,
|
|
73
|
+
icon,
|
|
74
|
+
iconPosition = 'left',
|
|
75
|
+
isIconOnly,
|
|
76
|
+
children,
|
|
77
|
+
className,
|
|
78
|
+
variant,
|
|
79
|
+
size,
|
|
80
|
+
rounded,
|
|
81
|
+
iconSize,
|
|
82
|
+
disabled,
|
|
83
|
+
...props
|
|
84
|
+
}: ButtonProps) => {
|
|
85
|
+
const isOnlyIcon = isIconOnly || (!children && !!icon)
|
|
86
|
+
|
|
87
|
+
const content = isOnlyIcon ? (
|
|
88
|
+
<span className="inline-flex items-center justify-center [&>svg]:w-[1.2rem] [&>svg]:h-[1.2rem]">
|
|
89
|
+
{icon}
|
|
90
|
+
</span>
|
|
91
|
+
) : (
|
|
92
|
+
<>
|
|
93
|
+
{icon && iconPosition === 'left' && (
|
|
94
|
+
<span className="inline-flex items-center shrink-0 [&>svg]:w-[1.1rem] [&>svg]:h-[1.1rem]">
|
|
95
|
+
{icon}
|
|
96
|
+
</span>
|
|
97
|
+
)}
|
|
98
|
+
<span className="flex items-center">{children}</span>
|
|
99
|
+
{icon && iconPosition === 'right' && (
|
|
100
|
+
<span className="inline-flex items-center shrink-0 [&>svg]:w-[1.1rem] [&>svg]:h-[1.1rem]">
|
|
101
|
+
{icon}
|
|
102
|
+
</span>
|
|
103
|
+
)}
|
|
104
|
+
</>
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if (href) {
|
|
108
|
+
return (
|
|
109
|
+
<RAC.Link
|
|
110
|
+
href={href}
|
|
111
|
+
className={cn(
|
|
112
|
+
buttonVariants({
|
|
113
|
+
variant,
|
|
114
|
+
size,
|
|
115
|
+
rounded,
|
|
116
|
+
iconSize: isOnlyIcon ? iconSize : undefined,
|
|
117
|
+
disabled,
|
|
118
|
+
}),
|
|
119
|
+
className,
|
|
120
|
+
)}
|
|
121
|
+
{...(props as RAC.LinkProps)}
|
|
122
|
+
>
|
|
123
|
+
{content}
|
|
124
|
+
</RAC.Link>
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<RAC.Button
|
|
130
|
+
className={cn(
|
|
131
|
+
buttonVariants({
|
|
132
|
+
variant,
|
|
133
|
+
size,
|
|
134
|
+
rounded,
|
|
135
|
+
iconSize: isOnlyIcon ? iconSize : undefined,
|
|
136
|
+
disabled,
|
|
137
|
+
}),
|
|
138
|
+
className,
|
|
139
|
+
)}
|
|
140
|
+
{...(props as RAC.ButtonProps)}
|
|
141
|
+
>
|
|
142
|
+
{content}
|
|
143
|
+
</RAC.Button>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { TOCItemInfo, TOCItemType } from '../on-this-page'
|
|
2
|
+
|
|
3
|
+
export function getItemId(url: string) {
|
|
4
|
+
if (url.startsWith('#')) return url.slice(1)
|
|
5
|
+
return null
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class Observer {
|
|
9
|
+
items: TOCItemInfo[] = []
|
|
10
|
+
single = false
|
|
11
|
+
private observer: IntersectionObserver | null = null
|
|
12
|
+
onChange?: () => void
|
|
13
|
+
|
|
14
|
+
private callback(entries: IntersectionObserverEntry[]) {
|
|
15
|
+
if (entries.length === 0) return
|
|
16
|
+
|
|
17
|
+
let hasActive = false
|
|
18
|
+
this.items = this.items.map((item) => {
|
|
19
|
+
const entry = entries.find((entry) => entry.target.id === item.id)
|
|
20
|
+
let active = entry ? entry.isIntersecting : item.active && !item.fallback
|
|
21
|
+
if (this.single && hasActive) active = false
|
|
22
|
+
|
|
23
|
+
if (item.active !== active) {
|
|
24
|
+
item = {
|
|
25
|
+
...item,
|
|
26
|
+
t: Date.now(),
|
|
27
|
+
active,
|
|
28
|
+
fallback: false,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (active) hasActive = true
|
|
33
|
+
return item
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
if (!hasActive && entries[0].rootBounds) {
|
|
37
|
+
const viewTop = entries[0].rootBounds.top
|
|
38
|
+
let min = Number.MAX_VALUE
|
|
39
|
+
let fallbackIdx = -1
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < this.items.length; i++) {
|
|
42
|
+
const element = document.getElementById(this.items[i].id)
|
|
43
|
+
if (!element) continue
|
|
44
|
+
|
|
45
|
+
const d = Math.abs(viewTop - element.getBoundingClientRect().top)
|
|
46
|
+
if (d < min) {
|
|
47
|
+
fallbackIdx = i
|
|
48
|
+
min = d
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (fallbackIdx !== -1) {
|
|
53
|
+
this.items[fallbackIdx] = {
|
|
54
|
+
...this.items[fallbackIdx],
|
|
55
|
+
active: true,
|
|
56
|
+
fallback: true,
|
|
57
|
+
t: Date.now(),
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.onChange?.()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setItems(newItems: TOCItemType[]) {
|
|
66
|
+
const observer = this.observer
|
|
67
|
+
if (observer) {
|
|
68
|
+
for (const item of this.items) {
|
|
69
|
+
const element = document.getElementById(item.id)
|
|
70
|
+
if (!element) continue
|
|
71
|
+
observer.unobserve(element)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.items = []
|
|
76
|
+
for (const item of newItems) {
|
|
77
|
+
const id = getItemId(item.url)
|
|
78
|
+
if (!id) continue
|
|
79
|
+
|
|
80
|
+
this.items.push({
|
|
81
|
+
id,
|
|
82
|
+
active: false,
|
|
83
|
+
fallback: false,
|
|
84
|
+
t: 0,
|
|
85
|
+
original: item,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
this.watchItems()
|
|
89
|
+
|
|
90
|
+
// In an SPA, the TOC might update before the MDX content is in the DOM.
|
|
91
|
+
// We perform a few delayed scans to ensure we catch those elements.
|
|
92
|
+
if (typeof window !== 'undefined') {
|
|
93
|
+
setTimeout(() => this.watchItems(), 100)
|
|
94
|
+
setTimeout(() => this.watchItems(), 500)
|
|
95
|
+
setTimeout(() => this.watchItems(), 1000)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this.onChange?.()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
watch(options?: IntersectionObserverInit) {
|
|
102
|
+
if (this.observer) return
|
|
103
|
+
this.observer = new IntersectionObserver(this.callback.bind(this), options)
|
|
104
|
+
this.watchItems()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private watchItems() {
|
|
108
|
+
if (!this.observer) return
|
|
109
|
+
for (const item of this.items) {
|
|
110
|
+
const element = document.getElementById(item.id)
|
|
111
|
+
if (!element) continue
|
|
112
|
+
this.observer.observe(element)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
unwatch() {
|
|
117
|
+
this.observer?.disconnect()
|
|
118
|
+
this.observer = null
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export * from './navbar'
|
|
2
|
+
export * from './navigation-menu'
|
|
3
|
+
export * from './search-dialog'
|
|
4
|
+
export * from './on-this-page'
|
|
5
|
+
export * from './page-nav'
|
|
6
|
+
export * from './tabs'
|
|
7
|
+
export * from './sidebar'
|
|
8
|
+
export * from './breadcrumbs'
|
|
9
|
+
export * from './button'
|
|
10
|
+
export * from './button-group'
|
|
11
|
+
export * from './menu'
|
|
12
|
+
export * from './popover'
|
|
13
|
+
export * from './tooltip'
|
|
14
|
+
export * from './link'
|
|
15
|
+
export { Separator, ToggleButton } from 'react-aria-components'
|
|
16
|
+
|
|
17
|
+
export { cn } from '../../utils/cn'
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Link as RACLink,
|
|
4
|
+
type LinkProps as RACLinkProps,
|
|
5
|
+
} from 'react-aria-components'
|
|
6
|
+
import { useLocation } from 'react-router-dom'
|
|
7
|
+
import { useLocalizedTo } from '@hooks/use-localized-to'
|
|
8
|
+
import { usePreload } from '@client/app/preload'
|
|
9
|
+
import { cn } from '@client/utils/cn'
|
|
10
|
+
|
|
11
|
+
export interface LinkProps extends RACLinkProps {
|
|
12
|
+
/** Should prefetch the page on hover? Default 'hover' */
|
|
13
|
+
prefetch?: 'hover' | 'none'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A primitive Link component that wraps React Aria Components' Link
|
|
18
|
+
* and adds framework-specific logic for path localization and preloading.
|
|
19
|
+
*
|
|
20
|
+
* It uses the global navigation configuration from BoltdocsRouterProvider
|
|
21
|
+
* to handle seamless client-side transitions.
|
|
22
|
+
*/
|
|
23
|
+
export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
|
|
24
|
+
(props, ref) => {
|
|
25
|
+
const { href, prefetch = 'hover', onMouseEnter, onFocus, ...rest } = props
|
|
26
|
+
|
|
27
|
+
const localizedHref = useLocalizedTo(href ?? '')
|
|
28
|
+
const { preload } = usePreload()
|
|
29
|
+
|
|
30
|
+
const handleMouseEnter = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
31
|
+
onMouseEnter?.(e)
|
|
32
|
+
if (
|
|
33
|
+
prefetch === 'hover' &&
|
|
34
|
+
typeof localizedHref === 'string' &&
|
|
35
|
+
localizedHref.startsWith('/')
|
|
36
|
+
) {
|
|
37
|
+
preload(localizedHref)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const handleFocus = (e: React.FocusEvent) => {
|
|
42
|
+
onFocus?.(e as any)
|
|
43
|
+
if (
|
|
44
|
+
prefetch === 'hover' &&
|
|
45
|
+
typeof localizedHref === 'string' &&
|
|
46
|
+
localizedHref.startsWith('/')
|
|
47
|
+
) {
|
|
48
|
+
preload(localizedHref)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<RACLink
|
|
54
|
+
{...rest}
|
|
55
|
+
ref={ref}
|
|
56
|
+
href={localizedHref as string}
|
|
57
|
+
onMouseEnter={handleMouseEnter}
|
|
58
|
+
onFocus={handleFocus as any}
|
|
59
|
+
/>
|
|
60
|
+
)
|
|
61
|
+
},
|
|
62
|
+
)
|
|
63
|
+
Link.displayName = 'Link'
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Props for the NavLink component, extending standard Link props.
|
|
67
|
+
*/
|
|
68
|
+
export interface NavLinkProps
|
|
69
|
+
extends Omit<LinkProps, 'className' | 'children'> {
|
|
70
|
+
/**
|
|
71
|
+
* When true, the active state will only be applied if the paths match exactly.
|
|
72
|
+
* Default is false.
|
|
73
|
+
*/
|
|
74
|
+
end?: boolean
|
|
75
|
+
/**
|
|
76
|
+
* Provides access to the active state for conditional children rendering.
|
|
77
|
+
*/
|
|
78
|
+
children?:
|
|
79
|
+
| React.ReactNode
|
|
80
|
+
| ((props: { isActive: boolean }) => React.ReactNode)
|
|
81
|
+
/**
|
|
82
|
+
* Provides access to the active state for conditional styling.
|
|
83
|
+
*/
|
|
84
|
+
className?: string | ((props: { isActive: boolean }) => string)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A primitive NavLink component that provides active state detection.
|
|
89
|
+
*
|
|
90
|
+
* It combines the Link primitive with path matching logic to determine
|
|
91
|
+
* if the link is currently active based on the browser's location.
|
|
92
|
+
*/
|
|
93
|
+
export const NavLink = React.forwardRef<HTMLAnchorElement, NavLinkProps>(
|
|
94
|
+
(props, ref) => {
|
|
95
|
+
const { href, end = false, className, children, ...rest } = props
|
|
96
|
+
const location = useLocation()
|
|
97
|
+
const localizedHref = useLocalizedTo(href ?? '')
|
|
98
|
+
|
|
99
|
+
const isActive = end
|
|
100
|
+
? location.pathname === localizedHref
|
|
101
|
+
: location.pathname.startsWith(localizedHref as string)
|
|
102
|
+
|
|
103
|
+
const resolvedClassName =
|
|
104
|
+
typeof className === 'function'
|
|
105
|
+
? className({ isActive })
|
|
106
|
+
: cn(className, isActive && 'active')
|
|
107
|
+
const resolvedChildren =
|
|
108
|
+
typeof children === 'function' ? children({ isActive }) : children
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<Link
|
|
112
|
+
{...rest}
|
|
113
|
+
ref={ref}
|
|
114
|
+
href={href}
|
|
115
|
+
className={resolvedClassName as any}
|
|
116
|
+
>
|
|
117
|
+
{resolvedChildren as any}
|
|
118
|
+
</Link>
|
|
119
|
+
)
|
|
120
|
+
},
|
|
121
|
+
)
|
|
122
|
+
NavLink.displayName = 'NavLink'
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Check, ChevronRight, Dot } from 'lucide-react'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
import * as RAC from 'react-aria-components'
|
|
6
|
+
import { Popover, type PopoverProps } from './popover'
|
|
7
|
+
import { cn } from '@client/utils/cn'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* MenuTrigger wraps a trigger (usually a Button) and a Menu.
|
|
11
|
+
*/
|
|
12
|
+
export interface MenuTriggerProps extends RAC.MenuTriggerProps {
|
|
13
|
+
placement?: PopoverProps['placement']
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function MenuTrigger(props: MenuTriggerProps) {
|
|
17
|
+
const [trigger, menu] = (
|
|
18
|
+
React.Children.toArray(props.children) as React.ReactElement[]
|
|
19
|
+
).slice(0, 2)
|
|
20
|
+
return (
|
|
21
|
+
<RAC.MenuTrigger {...props}>
|
|
22
|
+
{trigger as any}
|
|
23
|
+
<Popover placement={props.placement} className="min-w-[200px]">
|
|
24
|
+
{menu as any}
|
|
25
|
+
</Popover>
|
|
26
|
+
</RAC.MenuTrigger>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* SubmenuTrigger for nested menus.
|
|
32
|
+
*/
|
|
33
|
+
export function SubmenuTrigger(props: RAC.SubmenuTriggerProps) {
|
|
34
|
+
const [trigger, menu] = (
|
|
35
|
+
React.Children.toArray(props.children) as React.ReactElement[]
|
|
36
|
+
).slice(0, 2)
|
|
37
|
+
return (
|
|
38
|
+
<RAC.SubmenuTrigger {...props}>
|
|
39
|
+
{trigger as any}
|
|
40
|
+
<Popover offset={-4} crossOffset={-4}>
|
|
41
|
+
{menu as any}
|
|
42
|
+
</Popover>
|
|
43
|
+
</RAC.SubmenuTrigger>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The Menu container.
|
|
49
|
+
*/
|
|
50
|
+
export function Menu<T extends object>(props: RAC.MenuProps<T>) {
|
|
51
|
+
return (
|
|
52
|
+
<RAC.Menu
|
|
53
|
+
{...props}
|
|
54
|
+
className={RAC.composeRenderProps(props.className, (className) =>
|
|
55
|
+
cn('p-1.5 outline-none max-h-[inherit] overflow-auto', className),
|
|
56
|
+
)}
|
|
57
|
+
/>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* MenuItem with support for selection states and submenus.
|
|
63
|
+
*/
|
|
64
|
+
export function MenuItem(props: RAC.MenuItemProps) {
|
|
65
|
+
const textValue =
|
|
66
|
+
props.textValue ||
|
|
67
|
+
(typeof props.children === 'string' ? props.children : undefined)
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<RAC.MenuItem
|
|
71
|
+
{...props}
|
|
72
|
+
textValue={textValue}
|
|
73
|
+
className={RAC.composeRenderProps(
|
|
74
|
+
props.className,
|
|
75
|
+
(className, { isFocused, isPressed, isDisabled }) =>
|
|
76
|
+
cn(
|
|
77
|
+
'group relative flex flex-row items-center gap-2.5 px-3 py-1.5 rounded-lg outline-none cursor-default transition-all duration-200',
|
|
78
|
+
'text-text-main text-[0.8125rem]',
|
|
79
|
+
isFocused && 'bg-primary-500/10 text-primary-600 shadow-sm',
|
|
80
|
+
isPressed && 'scale-[0.98] bg-primary-500/15',
|
|
81
|
+
isDisabled && 'opacity-40 grayscale pointer-events-none',
|
|
82
|
+
className,
|
|
83
|
+
),
|
|
84
|
+
)}
|
|
85
|
+
>
|
|
86
|
+
{RAC.composeRenderProps(
|
|
87
|
+
props.children,
|
|
88
|
+
(children, { selectionMode, isSelected, hasSubmenu }) => (
|
|
89
|
+
<>
|
|
90
|
+
{selectionMode !== 'none' && (
|
|
91
|
+
<span className="flex items-center w-4 h-4 shrink-0 justify-center">
|
|
92
|
+
{isSelected && selectionMode === 'multiple' && (
|
|
93
|
+
<Check className="w-3.5 h-3.5 stroke-[2.5px] text-primary-500 animate-in zoom-in-50 duration-200" />
|
|
94
|
+
)}
|
|
95
|
+
{isSelected && selectionMode === 'single' && (
|
|
96
|
+
<Dot className="w-6 h-6 text-primary-500 animate-in zoom-in-50 duration-200" />
|
|
97
|
+
)}
|
|
98
|
+
</span>
|
|
99
|
+
)}
|
|
100
|
+
<div className="flex-1 flex flex-row items-center gap-2.5 truncate font-medium">
|
|
101
|
+
{children}
|
|
102
|
+
</div>
|
|
103
|
+
{hasSubmenu && (
|
|
104
|
+
<ChevronRight className="w-4 h-4 ml-auto text-text-muted group-focused:text-primary-500/70 transition-colors" />
|
|
105
|
+
)}
|
|
106
|
+
</>
|
|
107
|
+
),
|
|
108
|
+
)}
|
|
109
|
+
</RAC.MenuItem>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* MenuSection for grouping items with an optional header.
|
|
115
|
+
*/
|
|
116
|
+
export interface MenuSectionProps<T> extends RAC.MenuSectionProps<T> {
|
|
117
|
+
title?: string
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function MenuSection<T extends object>({
|
|
121
|
+
title,
|
|
122
|
+
...props
|
|
123
|
+
}: MenuSectionProps<T>) {
|
|
124
|
+
return (
|
|
125
|
+
<RAC.MenuSection
|
|
126
|
+
{...props}
|
|
127
|
+
className={cn('flex flex-col gap-0.5', props.className)}
|
|
128
|
+
>
|
|
129
|
+
{title && (
|
|
130
|
+
<RAC.Header className="px-3 py-2 text-[10px] font-bold uppercase tracking-[0.075em] text-text-muted/50 select-none">
|
|
131
|
+
{title}
|
|
132
|
+
</RAC.Header>
|
|
133
|
+
)}
|
|
134
|
+
<RAC.Collection items={props.items}>{props.children}</RAC.Collection>
|
|
135
|
+
</RAC.MenuSection>
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* MenuSeparator for visual division.
|
|
141
|
+
*/
|
|
142
|
+
export function MenuSeparator(props: RAC.SeparatorProps) {
|
|
143
|
+
return (
|
|
144
|
+
<RAC.Separator
|
|
145
|
+
{...props}
|
|
146
|
+
className="mx-2 my-1.5 border-t border-border-subtle/50"
|
|
147
|
+
/>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Default export for convenience
|
|
152
|
+
export default {
|
|
153
|
+
Root: Menu,
|
|
154
|
+
Item: MenuItem,
|
|
155
|
+
Trigger: MenuTrigger,
|
|
156
|
+
SubTrigger: SubmenuTrigger,
|
|
157
|
+
Section: MenuSection,
|
|
158
|
+
Separator: MenuSeparator,
|
|
159
|
+
}
|