boltdocs 1.10.2 → 2.0.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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/dist/cache-7G6D532T.mjs +1 -0
- package/dist/chunk-A4HQPEPU.mjs +1 -0
- package/dist/chunk-BA5NH5HU.mjs +1 -0
- package/dist/chunk-BQCD3DWG.mjs +1 -0
- package/dist/chunk-H63UMKYF.mjs +1 -0
- package/dist/chunk-IWHRQHS7.mjs +1 -0
- package/dist/chunk-JZXLCA2E.mjs +1 -0
- package/dist/chunk-MFU7Q6WF.mjs +1 -0
- package/dist/chunk-QYPNX5UN.mjs +1 -0
- package/dist/chunk-XEAPSFMB.mjs +1 -0
- package/dist/client/components/mdx/index.d.mts +209 -0
- package/dist/client/components/mdx/index.d.ts +209 -0
- package/dist/client/components/mdx/index.js +1 -0
- package/dist/client/components/mdx/index.mjs +1 -0
- package/dist/client/hooks/index.d.mts +133 -0
- package/dist/client/hooks/index.d.ts +133 -0
- package/dist/client/hooks/index.js +1 -0
- package/dist/client/hooks/index.mjs +1 -0
- package/dist/client/index.d.mts +138 -298
- package/dist/client/index.d.ts +138 -298
- package/dist/client/index.js +1 -3630
- package/dist/client/index.mjs +1 -697
- package/dist/client/ssr.d.mts +7 -3
- package/dist/client/ssr.d.ts +7 -3
- package/dist/client/ssr.js +1 -2928
- package/dist/client/ssr.mjs +1 -33
- package/dist/{config-BsFQ-ErD.d.ts → config-CX4l-ZNp.d.mts} +42 -35
- package/dist/{config-BsFQ-ErD.d.mts → config-CX4l-ZNp.d.ts} +42 -35
- package/dist/node/index.d.mts +2 -4
- package/dist/node/index.d.ts +2 -4
- package/dist/node/index.js +31 -1161
- package/dist/node/index.mjs +31 -736
- package/dist/search-dialog-EB3N4TYM.mjs +1 -0
- package/dist/types-BuZWFT7r.d.ts +159 -0
- package/dist/types-CvT-SGbK.d.mts +159 -0
- package/dist/use-routes-5bAtAAYX.d.mts +30 -0
- package/dist/use-routes-BefRXY3v.d.ts +30 -0
- package/package.json +34 -12
- package/src/client/app/config-context.tsx +18 -0
- package/src/client/app/docs-layout.tsx +14 -0
- package/src/client/app/index.tsx +137 -262
- 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 -84
- 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 +102 -82
- package/src/node/routes/sorter.ts +15 -15
- package/src/node/routes/types.ts +24 -24
- package/src/node/ssg/index.ts +73 -47
- 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 +54 -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/ssr.css +0 -2847
- 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,116 @@
|
|
|
1
|
+
import * as RAC from 'react-aria-components'
|
|
2
|
+
import { ChevronDown } from 'lucide-react'
|
|
3
|
+
import { cn } from '@client/utils/cn'
|
|
4
|
+
import type { ComponentBase, CompoundComponent } from './types'
|
|
5
|
+
|
|
6
|
+
export interface NavigationMenuItemProps extends ComponentBase {
|
|
7
|
+
label: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface NavigationMenuLinkProps
|
|
11
|
+
extends Omit<ComponentBase, 'children'> {
|
|
12
|
+
href: string
|
|
13
|
+
label: string
|
|
14
|
+
description?: string
|
|
15
|
+
children?:
|
|
16
|
+
| React.ReactNode
|
|
17
|
+
| ((opts: RAC.MenuItemRenderProps) => React.ReactNode)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type NavigationMenuComponent = CompoundComponent<
|
|
21
|
+
ComponentBase,
|
|
22
|
+
{
|
|
23
|
+
List: React.FC<ComponentBase>
|
|
24
|
+
Item: React.FC<NavigationMenuItemProps>
|
|
25
|
+
Link: React.FC<NavigationMenuLinkProps>
|
|
26
|
+
}
|
|
27
|
+
>
|
|
28
|
+
|
|
29
|
+
const NavigationMenuRoot = ({
|
|
30
|
+
children,
|
|
31
|
+
className,
|
|
32
|
+
...props
|
|
33
|
+
}: ComponentBase) => {
|
|
34
|
+
return (
|
|
35
|
+
<nav className={cn('relative flex items-center', className)} {...props}>
|
|
36
|
+
{children}
|
|
37
|
+
</nav>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const NavigationMenuList = ({ children, className }: ComponentBase) => {
|
|
42
|
+
return (
|
|
43
|
+
<div className={cn('flex list-none items-center gap-1', className)}>
|
|
44
|
+
{children}
|
|
45
|
+
</div>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const NavigationMenuItem = ({
|
|
50
|
+
children,
|
|
51
|
+
label,
|
|
52
|
+
className,
|
|
53
|
+
}: NavigationMenuItemProps) => {
|
|
54
|
+
return (
|
|
55
|
+
<RAC.MenuTrigger>
|
|
56
|
+
<RAC.Button
|
|
57
|
+
className={cn(
|
|
58
|
+
'flex items-center gap-1 rounded-md px-3 py-1.5 text-sm font-medium outline-none transition-colors cursor-pointer',
|
|
59
|
+
'text-text-muted hover:bg-bg-surface hover:text-text-main',
|
|
60
|
+
'focus-visible:ring-2 focus-visible:ring-primary-500/30',
|
|
61
|
+
className,
|
|
62
|
+
)}
|
|
63
|
+
>
|
|
64
|
+
{label}
|
|
65
|
+
<ChevronDown size={14} className="transition-transform" />
|
|
66
|
+
</RAC.Button>
|
|
67
|
+
<RAC.Popover
|
|
68
|
+
placement="bottom start"
|
|
69
|
+
className="entering:animate-in entering:fade-in entering:zoom-in-95 exiting:animate-out exiting:fade-out exiting:zoom-out-95 fill-mode-forwards"
|
|
70
|
+
>
|
|
71
|
+
<RAC.Menu className="w-56 outline-none rounded-xl border border-border-subtle bg-bg-surface p-2 shadow-xl ring-1 ring-border-strong/5">
|
|
72
|
+
{children as any}
|
|
73
|
+
</RAC.Menu>
|
|
74
|
+
</RAC.Popover>
|
|
75
|
+
</RAC.MenuTrigger>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const NavigationMenuLink = ({
|
|
80
|
+
label,
|
|
81
|
+
href,
|
|
82
|
+
description,
|
|
83
|
+
className,
|
|
84
|
+
children,
|
|
85
|
+
...props
|
|
86
|
+
}: NavigationMenuLinkProps) => {
|
|
87
|
+
return (
|
|
88
|
+
<RAC.MenuItem
|
|
89
|
+
href={href}
|
|
90
|
+
className={cn(
|
|
91
|
+
'block rounded-lg px-3 py-2 text-sm outline-none cursor-pointer transition-colors',
|
|
92
|
+
'hover:bg-bg-muted focus:bg-bg-muted',
|
|
93
|
+
className,
|
|
94
|
+
)}
|
|
95
|
+
{...props}
|
|
96
|
+
>
|
|
97
|
+
{children || (
|
|
98
|
+
<>
|
|
99
|
+
<div className="font-semibold text-text-main">{label}</div>
|
|
100
|
+
{description && (
|
|
101
|
+
<div className="text-xs text-text-muted line-clamp-1 mt-0.5">
|
|
102
|
+
{description}
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
105
|
+
</>
|
|
106
|
+
)}
|
|
107
|
+
</RAC.MenuItem>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export default {
|
|
112
|
+
NavigationMenuRoot,
|
|
113
|
+
NavigationMenuList,
|
|
114
|
+
NavigationMenuItem,
|
|
115
|
+
NavigationMenuLink,
|
|
116
|
+
}
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
createContext,
|
|
3
|
+
use,
|
|
4
|
+
useEffect,
|
|
5
|
+
useImperativeHandle,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
type RefObject,
|
|
11
|
+
} from 'react'
|
|
12
|
+
import scrollIntoView from 'scroll-into-view-if-needed'
|
|
13
|
+
import { cn } from '../../utils/cn'
|
|
14
|
+
import { useOnChange } from '../../utils/use-on-change'
|
|
15
|
+
import type { ComponentBase, CompoundComponent } from './types'
|
|
16
|
+
import { getItemId } from './helpers/observer'
|
|
17
|
+
|
|
18
|
+
export interface TOCItemType {
|
|
19
|
+
title: ReactNode
|
|
20
|
+
url: string
|
|
21
|
+
depth: number
|
|
22
|
+
_step?: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type TableOfContents = TOCItemType[]
|
|
26
|
+
|
|
27
|
+
export interface TOCItemInfo {
|
|
28
|
+
id: string
|
|
29
|
+
active: boolean
|
|
30
|
+
/** last time the item is updated */
|
|
31
|
+
t: number
|
|
32
|
+
/** currently active but not intersecting in viewport */
|
|
33
|
+
fallback: boolean
|
|
34
|
+
original?: TOCItemType
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface AnchorProviderProps {
|
|
38
|
+
toc: TOCItemType[]
|
|
39
|
+
/**
|
|
40
|
+
* Only accept one active item at most
|
|
41
|
+
* @defaultValue false
|
|
42
|
+
*/
|
|
43
|
+
single?: boolean
|
|
44
|
+
children?: ReactNode
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ScrollProviderProps {
|
|
48
|
+
/**
|
|
49
|
+
* Scroll into the view of container when active
|
|
50
|
+
*/
|
|
51
|
+
containerRef: RefObject<HTMLElement | null>
|
|
52
|
+
children?: ReactNode
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface OnThisPageContentProps extends ComponentBase {
|
|
56
|
+
ref?: React.Ref<HTMLDivElement>
|
|
57
|
+
scrollRef?: RefObject<HTMLElement | null>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface OnThisPageItemProps extends ComponentBase {
|
|
61
|
+
level?: number
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface OnThisPageLinkProps extends ComponentBase {
|
|
65
|
+
href?: string
|
|
66
|
+
active?: boolean
|
|
67
|
+
onClick?: (event: React.MouseEvent<HTMLAnchorElement>) => void
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface OnThisPageIndicatorProps extends ComponentBase {
|
|
71
|
+
style?: React.CSSProperties
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const ItemsContext = createContext<TOCItemInfo[] | null>(null)
|
|
75
|
+
const ScrollContext = createContext<RefObject<HTMLElement | null> | null>(null)
|
|
76
|
+
|
|
77
|
+
class Observer {
|
|
78
|
+
items: TOCItemInfo[] = []
|
|
79
|
+
single = false
|
|
80
|
+
private observer: IntersectionObserver | null = null
|
|
81
|
+
onChange?: () => void
|
|
82
|
+
|
|
83
|
+
private callback(entries: IntersectionObserverEntry[]) {
|
|
84
|
+
if (entries.length === 0) return
|
|
85
|
+
|
|
86
|
+
let hasActive = false
|
|
87
|
+
this.items = this.items.map((item) => {
|
|
88
|
+
const entry = entries.find((entry) => entry.target.id === item.id)
|
|
89
|
+
let active = entry ? entry.isIntersecting : item.active && !item.fallback
|
|
90
|
+
if (this.single && hasActive) active = false
|
|
91
|
+
|
|
92
|
+
if (item.active !== active) {
|
|
93
|
+
item = {
|
|
94
|
+
...item,
|
|
95
|
+
t: Date.now(),
|
|
96
|
+
active,
|
|
97
|
+
fallback: false,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (active) hasActive = true
|
|
102
|
+
return item
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
if (!hasActive && entries[0].rootBounds) {
|
|
106
|
+
const viewTop = entries[0].rootBounds.top
|
|
107
|
+
let min = Number.MAX_VALUE
|
|
108
|
+
let fallbackIdx = -1
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < this.items.length; i++) {
|
|
111
|
+
const element = document.getElementById(this.items[i].id)
|
|
112
|
+
if (!element) continue
|
|
113
|
+
|
|
114
|
+
const d = Math.abs(viewTop - element.getBoundingClientRect().top)
|
|
115
|
+
if (d < min) {
|
|
116
|
+
fallbackIdx = i
|
|
117
|
+
min = d
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (fallbackIdx !== -1) {
|
|
122
|
+
this.items[fallbackIdx] = {
|
|
123
|
+
...this.items[fallbackIdx],
|
|
124
|
+
active: true,
|
|
125
|
+
fallback: true,
|
|
126
|
+
t: Date.now(),
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.onChange?.()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
setItems(newItems: TOCItemType[]) {
|
|
135
|
+
const observer = this.observer
|
|
136
|
+
if (observer) {
|
|
137
|
+
for (const item of this.items) {
|
|
138
|
+
const element = document.getElementById(item.id)
|
|
139
|
+
if (!element) continue
|
|
140
|
+
observer.unobserve(element)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.items = []
|
|
145
|
+
for (const item of newItems) {
|
|
146
|
+
const id = getItemId(item.url)
|
|
147
|
+
if (!id) continue
|
|
148
|
+
|
|
149
|
+
this.items.push({
|
|
150
|
+
id,
|
|
151
|
+
active: false,
|
|
152
|
+
fallback: false,
|
|
153
|
+
t: 0,
|
|
154
|
+
original: item,
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
this.watchItems()
|
|
158
|
+
|
|
159
|
+
// In an SPA, the TOC might update before the MDX content is in the DOM.
|
|
160
|
+
// We perform a few delayed scans to ensure we catch those elements.
|
|
161
|
+
if (typeof window !== 'undefined') {
|
|
162
|
+
setTimeout(() => this.watchItems(), 100)
|
|
163
|
+
setTimeout(() => this.watchItems(), 500)
|
|
164
|
+
setTimeout(() => this.watchItems(), 1000)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this.onChange?.()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
watch(options?: IntersectionObserverInit) {
|
|
171
|
+
if (this.observer) return
|
|
172
|
+
this.observer = new IntersectionObserver(this.callback.bind(this), options)
|
|
173
|
+
this.watchItems()
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private watchItems() {
|
|
177
|
+
if (!this.observer) return
|
|
178
|
+
for (const item of this.items) {
|
|
179
|
+
const element = document.getElementById(item.id)
|
|
180
|
+
if (!element) continue
|
|
181
|
+
this.observer.observe(element)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
unwatch() {
|
|
186
|
+
this.observer?.disconnect()
|
|
187
|
+
this.observer = null
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function useItems() {
|
|
192
|
+
const ctx = use(ItemsContext)
|
|
193
|
+
if (!ctx)
|
|
194
|
+
throw new Error(
|
|
195
|
+
`Component must be used under the <AnchorProvider /> component.`,
|
|
196
|
+
)
|
|
197
|
+
return ctx
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function useScrollStatus(ref: RefObject<HTMLElement | null>) {
|
|
201
|
+
const [status, setStatus] = useState({
|
|
202
|
+
isOverflowing: false,
|
|
203
|
+
isAtBottom: false,
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
const el = ref.current
|
|
208
|
+
if (!el) return
|
|
209
|
+
|
|
210
|
+
const checkStatus = () => {
|
|
211
|
+
const isOverflowing = el.scrollHeight > el.clientHeight
|
|
212
|
+
// We use a 2px threshold for floating point math issues
|
|
213
|
+
const isAtBottom = el.scrollHeight - el.scrollTop <= el.clientHeight + 2
|
|
214
|
+
setStatus({ isOverflowing, isAtBottom })
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
checkStatus()
|
|
218
|
+
el.addEventListener('scroll', checkStatus, { passive: true })
|
|
219
|
+
window.addEventListener('resize', checkStatus)
|
|
220
|
+
|
|
221
|
+
const mutationObserver = new MutationObserver(checkStatus)
|
|
222
|
+
mutationObserver.observe(el, { childList: true, subtree: true })
|
|
223
|
+
|
|
224
|
+
return () => {
|
|
225
|
+
el.removeEventListener('scroll', checkStatus)
|
|
226
|
+
window.removeEventListener('resize', checkStatus)
|
|
227
|
+
mutationObserver.disconnect()
|
|
228
|
+
}
|
|
229
|
+
}, [ref])
|
|
230
|
+
|
|
231
|
+
return status
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function useActiveAnchor(): string | undefined {
|
|
235
|
+
const items = useItems()
|
|
236
|
+
return useMemo(() => {
|
|
237
|
+
let out: TOCItemInfo | undefined
|
|
238
|
+
for (const item of items) {
|
|
239
|
+
if (!item.active) continue
|
|
240
|
+
if (!out || item.t > out.t) {
|
|
241
|
+
out = item
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return out?.id
|
|
245
|
+
}, [items])
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function useActiveAnchors(): string[] {
|
|
249
|
+
const items = useItems()
|
|
250
|
+
return useMemo(() => {
|
|
251
|
+
const out: string[] = []
|
|
252
|
+
for (const item of items) {
|
|
253
|
+
if (item.active) out.push(item.id)
|
|
254
|
+
}
|
|
255
|
+
return out
|
|
256
|
+
}, [items])
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Optional: add auto-scroll to TOC items. */
|
|
260
|
+
export function ScrollProvider({
|
|
261
|
+
containerRef,
|
|
262
|
+
children,
|
|
263
|
+
}: ScrollProviderProps) {
|
|
264
|
+
return (
|
|
265
|
+
<ScrollContext.Provider value={containerRef}>
|
|
266
|
+
{children}
|
|
267
|
+
</ScrollContext.Provider>
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function AnchorProvider({
|
|
272
|
+
toc,
|
|
273
|
+
single = false,
|
|
274
|
+
children,
|
|
275
|
+
}: AnchorProviderProps) {
|
|
276
|
+
const observer = useMemo(() => new Observer(), [])
|
|
277
|
+
const [items, setItems] = useState<TOCItemInfo[]>(observer.items)
|
|
278
|
+
|
|
279
|
+
observer.single = single
|
|
280
|
+
useEffect(() => {
|
|
281
|
+
observer.setItems(toc)
|
|
282
|
+
}, [observer, toc])
|
|
283
|
+
|
|
284
|
+
useEffect(() => {
|
|
285
|
+
observer.watch({
|
|
286
|
+
rootMargin: '0px',
|
|
287
|
+
threshold: 0.98,
|
|
288
|
+
})
|
|
289
|
+
observer.onChange = () => setItems([...observer.items])
|
|
290
|
+
|
|
291
|
+
return () => {
|
|
292
|
+
observer.unwatch()
|
|
293
|
+
}
|
|
294
|
+
}, [observer])
|
|
295
|
+
|
|
296
|
+
return <ItemsContext.Provider value={items}>{children}</ItemsContext.Provider>
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export const OnThisPageRoot = ({ children, className }: ComponentBase) => {
|
|
300
|
+
return (
|
|
301
|
+
<nav
|
|
302
|
+
className={cn(
|
|
303
|
+
'sticky top-navbar hidden xl:flex flex-col shrink-0',
|
|
304
|
+
'w-toc',
|
|
305
|
+
'py-8 pl-6 pr-4',
|
|
306
|
+
className,
|
|
307
|
+
)}
|
|
308
|
+
>
|
|
309
|
+
{children}
|
|
310
|
+
</nav>
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export const OnThisPageHeader = ({
|
|
315
|
+
children,
|
|
316
|
+
className,
|
|
317
|
+
...props
|
|
318
|
+
}: ComponentBase) => {
|
|
319
|
+
return (
|
|
320
|
+
<div
|
|
321
|
+
className={cn(
|
|
322
|
+
'mb-4 text-xs font-bold uppercase tracking-wider text-text-main',
|
|
323
|
+
className,
|
|
324
|
+
)}
|
|
325
|
+
{...props}
|
|
326
|
+
>
|
|
327
|
+
{children}
|
|
328
|
+
</div>
|
|
329
|
+
)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export const OnThisPageContent = ({
|
|
333
|
+
children,
|
|
334
|
+
className,
|
|
335
|
+
ref,
|
|
336
|
+
...props
|
|
337
|
+
}: OnThisPageContentProps) => {
|
|
338
|
+
const internalRef = useRef<HTMLDivElement>(null)
|
|
339
|
+
|
|
340
|
+
useImperativeHandle(ref, () => internalRef.current!)
|
|
341
|
+
|
|
342
|
+
const { isOverflowing, isAtBottom } = useScrollStatus(internalRef)
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<div
|
|
346
|
+
ref={internalRef}
|
|
347
|
+
className={cn(
|
|
348
|
+
'relative overflow-y-auto boltdocs-otp-content',
|
|
349
|
+
isOverflowing &&
|
|
350
|
+
!isAtBottom &&
|
|
351
|
+
'mask-[linear-gradient(to_bottom,black_85%,transparent_100%)]',
|
|
352
|
+
className,
|
|
353
|
+
)}
|
|
354
|
+
{...props}
|
|
355
|
+
>
|
|
356
|
+
{children}
|
|
357
|
+
</div>
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
OnThisPageContent.displayName = 'OnThisPageContent'
|
|
362
|
+
|
|
363
|
+
export const OnThisPageList = ({ children, className }: ComponentBase) => {
|
|
364
|
+
return (
|
|
365
|
+
<ul
|
|
366
|
+
className={cn(
|
|
367
|
+
'relative space-y-1 text-sm border-l border-border-subtle',
|
|
368
|
+
className,
|
|
369
|
+
)}
|
|
370
|
+
>
|
|
371
|
+
{children}
|
|
372
|
+
</ul>
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export const OnThisPageItem = ({
|
|
377
|
+
level,
|
|
378
|
+
children,
|
|
379
|
+
className,
|
|
380
|
+
}: OnThisPageItemProps) => {
|
|
381
|
+
return <li className={cn(level === 3 && 'pl-3', className)}>{children}</li>
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export const OnThisPageLink = ({
|
|
385
|
+
children,
|
|
386
|
+
href,
|
|
387
|
+
active,
|
|
388
|
+
onClick,
|
|
389
|
+
className,
|
|
390
|
+
}: OnThisPageLinkProps) => {
|
|
391
|
+
const items = use(ItemsContext)
|
|
392
|
+
const containerRef = use(ScrollContext)
|
|
393
|
+
const id = href ? getItemId(href) : null
|
|
394
|
+
const anchorRef = useRef<HTMLAnchorElement>(null)
|
|
395
|
+
const [internalActive, setInternalActive] = useState(active)
|
|
396
|
+
|
|
397
|
+
useOnChange(
|
|
398
|
+
id && items ? items.find((i) => i.id === id)?.active : null,
|
|
399
|
+
(newActive) => {
|
|
400
|
+
if (newActive !== null && newActive !== internalActive) {
|
|
401
|
+
setInternalActive(!!newActive)
|
|
402
|
+
|
|
403
|
+
if (newActive && anchorRef.current && containerRef?.current) {
|
|
404
|
+
scrollIntoView(anchorRef.current, {
|
|
405
|
+
behavior: 'smooth',
|
|
406
|
+
block: 'center',
|
|
407
|
+
inline: 'center',
|
|
408
|
+
scrollMode: 'if-needed',
|
|
409
|
+
boundary: containerRef.current,
|
|
410
|
+
})
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
// Also sync with external active prop if provided
|
|
417
|
+
useEffect(() => {
|
|
418
|
+
if (active !== undefined) setInternalActive(active)
|
|
419
|
+
}, [active])
|
|
420
|
+
|
|
421
|
+
return (
|
|
422
|
+
<a
|
|
423
|
+
ref={anchorRef}
|
|
424
|
+
href={href}
|
|
425
|
+
onClick={onClick}
|
|
426
|
+
data-active={internalActive}
|
|
427
|
+
className={cn(
|
|
428
|
+
'block py-1 pl-4 text-[13px] outline-none transition-colors hover:text-text-main',
|
|
429
|
+
internalActive ? 'text-primary-500 font-medium' : 'text-text-muted',
|
|
430
|
+
className,
|
|
431
|
+
)}
|
|
432
|
+
>
|
|
433
|
+
{children}
|
|
434
|
+
</a>
|
|
435
|
+
)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export const OnThisPageIndicator = ({
|
|
439
|
+
style,
|
|
440
|
+
className,
|
|
441
|
+
}: OnThisPageIndicatorProps) => {
|
|
442
|
+
return (
|
|
443
|
+
<div
|
|
444
|
+
className={cn(
|
|
445
|
+
'absolute -left-px w-0.5 rounded-full bg-primary-500 transition-all duration-300',
|
|
446
|
+
className,
|
|
447
|
+
)}
|
|
448
|
+
style={style}
|
|
449
|
+
/>
|
|
450
|
+
)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export default {
|
|
454
|
+
OnThisPageRoot,
|
|
455
|
+
OnThisPageHeader,
|
|
456
|
+
OnThisPageContent,
|
|
457
|
+
OnThisPageList,
|
|
458
|
+
OnThisPageItem,
|
|
459
|
+
OnThisPageLink,
|
|
460
|
+
OnThisPageIndicator,
|
|
461
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as RAC from 'react-aria-components'
|
|
2
|
+
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
|
3
|
+
import { cn } from '@client/utils/cn'
|
|
4
|
+
import type { ComponentBase } from './types'
|
|
5
|
+
|
|
6
|
+
export interface PageNavProps extends ComponentBase {
|
|
7
|
+
to: string
|
|
8
|
+
direction: 'prev' | 'next'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const PageNavRoot = ({ children, className }: ComponentBase) => {
|
|
12
|
+
return (
|
|
13
|
+
<nav
|
|
14
|
+
className={cn(
|
|
15
|
+
'grid grid-cols-1 sm:grid-cols-2 gap-4 mt-12 pt-8 border-t border-border-subtle',
|
|
16
|
+
className,
|
|
17
|
+
)}
|
|
18
|
+
>
|
|
19
|
+
{children}
|
|
20
|
+
</nav>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const PageNavLink = ({
|
|
25
|
+
children,
|
|
26
|
+
to,
|
|
27
|
+
direction,
|
|
28
|
+
className,
|
|
29
|
+
}: PageNavProps) => {
|
|
30
|
+
const isNext = direction === 'next'
|
|
31
|
+
return (
|
|
32
|
+
<RAC.Link
|
|
33
|
+
href={to}
|
|
34
|
+
className={cn(
|
|
35
|
+
'flex group items-center p-4 rounded-xl border border-border-subtle bg-bg-surface outline-none',
|
|
36
|
+
'transition-all hover:bg-bg-main hover:border-primary-500 hover:shadow-lg',
|
|
37
|
+
'focus-visible:ring-2 focus-visible:ring-primary-500/30',
|
|
38
|
+
isNext ? 'text-right justify-end' : 'text-left justify-start',
|
|
39
|
+
className,
|
|
40
|
+
)}
|
|
41
|
+
>
|
|
42
|
+
{!isNext && (
|
|
43
|
+
<ChevronLeft className="mr-3 h-5 w-5 text-text-muted group-hover:text-primary-500 transition-transform group-hover:-translate-x-1" />
|
|
44
|
+
)}
|
|
45
|
+
<div className="flex flex-col gap-1 flex-1">{children}</div>
|
|
46
|
+
{isNext && (
|
|
47
|
+
<ChevronRight className="ml-3 h-5 w-5 text-text-muted group-hover:text-primary-500 transition-transform group-hover:translate-x-1" />
|
|
48
|
+
)}
|
|
49
|
+
</RAC.Link>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const PageNavTitle = ({ children, className }: ComponentBase) => {
|
|
54
|
+
return (
|
|
55
|
+
<span
|
|
56
|
+
className={cn(
|
|
57
|
+
'text-xs font-medium uppercase tracking-wider text-text-muted',
|
|
58
|
+
className,
|
|
59
|
+
)}
|
|
60
|
+
>
|
|
61
|
+
{children}
|
|
62
|
+
</span>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const PageNavDescription = ({ children, className }: ComponentBase) => {
|
|
67
|
+
return (
|
|
68
|
+
<span
|
|
69
|
+
className={cn('text-base font-bold text-text-main truncate', className)}
|
|
70
|
+
>
|
|
71
|
+
{children}
|
|
72
|
+
</span>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const PageNavIcon = ({ children }: ComponentBase) => {
|
|
77
|
+
return <>{children}</>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export default {
|
|
81
|
+
PageNavRoot,
|
|
82
|
+
PageNavLink: Object.assign(PageNavLink, {
|
|
83
|
+
Title: PageNavTitle,
|
|
84
|
+
Description: PageNavDescription,
|
|
85
|
+
Icon: PageNavIcon,
|
|
86
|
+
}),
|
|
87
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as RAC from 'react-aria-components'
|
|
4
|
+
import { cn } from '@client/utils/cn'
|
|
5
|
+
|
|
6
|
+
export interface PopoverProps extends Omit<RAC.PopoverProps, 'children'> {
|
|
7
|
+
children: React.ReactNode
|
|
8
|
+
className?: string
|
|
9
|
+
showArrow?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A reusable Popover primitive with premium glassmorphism styling and smooth animations.
|
|
14
|
+
*/
|
|
15
|
+
export const Popover = ({
|
|
16
|
+
children,
|
|
17
|
+
className,
|
|
18
|
+
showArrow,
|
|
19
|
+
...props
|
|
20
|
+
}: PopoverProps) => {
|
|
21
|
+
return (
|
|
22
|
+
<RAC.Popover
|
|
23
|
+
offset={8}
|
|
24
|
+
{...props}
|
|
25
|
+
className={RAC.composeRenderProps(className, (className) =>
|
|
26
|
+
cn(
|
|
27
|
+
'z-50 overflow-auto rounded-xl border border-border-subtle bg-bg-surface/80 shadow-xl backdrop-blur-md outline-none',
|
|
28
|
+
'entering:animate-in entering:fade-in entering:zoom-in-95 exiting:animate-out exiting:fade-out exiting:zoom-out-95 fill-mode-forwards',
|
|
29
|
+
className,
|
|
30
|
+
),
|
|
31
|
+
)}
|
|
32
|
+
>
|
|
33
|
+
{showArrow && (
|
|
34
|
+
<RAC.OverlayArrow className="group">
|
|
35
|
+
<svg
|
|
36
|
+
viewBox="0 0 12 12"
|
|
37
|
+
className="block h-3 w-3 fill-bg-surface/80 stroke-border-subtle group-placement-bottom:rotate-180 group-placement-left:-rotate-90 group-placement-right:rotate-90"
|
|
38
|
+
aria-hidden="true"
|
|
39
|
+
>
|
|
40
|
+
<path d="M0 0 L6 6 L12 0" />
|
|
41
|
+
</svg>
|
|
42
|
+
</RAC.OverlayArrow>
|
|
43
|
+
)}
|
|
44
|
+
{children as any}
|
|
45
|
+
</RAC.Popover>
|
|
46
|
+
)
|
|
47
|
+
}
|