boltdocs 2.6.2 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/boltdocs.js +0 -1
- package/dist/cache-CQKlT4fI.mjs +6 -0
- package/dist/cache-DorPMFgW.cjs +6 -0
- package/dist/cards-BLoSiRuL.d.ts +30 -0
- package/dist/cards-CQn9mXZS.d.cts +30 -0
- package/dist/chunk-Ds5LZdWN.cjs +6 -0
- package/dist/client/index.cjs +1 -1
- package/dist/client/index.d.cts +167 -1338
- package/dist/client/index.d.ts +166 -1337
- package/dist/client/index.js +1 -1
- package/dist/{package-CFP44vfn.cjs → client/mdx.cjs} +1 -1
- package/dist/client/mdx.d.cts +128 -0
- package/dist/client/mdx.d.ts +129 -0
- package/dist/client/mdx.js +6 -0
- package/dist/client/primitives.cjs +6 -0
- package/dist/client/primitives.d.cts +818 -0
- package/dist/client/primitives.d.ts +818 -0
- package/dist/client/primitives.js +6 -0
- package/dist/client/theme/neutral.css +74 -361
- package/dist/client/theme/reset.css +189 -0
- package/dist/docs-layout-BlDhcQRv.cjs +6 -0
- package/dist/docs-layout-BvAOWEJw.js +6 -0
- package/dist/doctor-BQiQhCTl.cjs +6 -0
- package/dist/doctor-COpf35L2.cjs +20 -0
- package/dist/doctor-Dh1XP7Pz.mjs +20 -0
- package/dist/generator-DGW6pkCC.cjs +22 -0
- package/dist/generator-Dv3wEmhZ.mjs +22 -0
- package/dist/icons-dev-CrQLjoQp.js +6 -0
- package/dist/icons-dev-rzdz6Lf3.cjs +6 -0
- package/dist/image-BkIfa9oo.js +6 -0
- package/dist/image-DIGjCPe6.cjs +6 -0
- package/dist/mdx-K0WYBAJ3.js +7 -0
- package/dist/mdx-hpErbRUe.cjs +7 -0
- package/dist/meta-loader-0gJ4PtBC.cjs +6 -0
- package/dist/meta-loader-9IpAHWDS.mjs +6 -0
- package/dist/node/cli-entry.cjs +1 -2
- package/dist/node/cli-entry.mjs +1 -2
- package/dist/node/index.cjs +1 -1
- package/dist/node/index.d.cts +55 -11
- package/dist/node/index.d.mts +55 -12
- package/dist/node/index.mjs +1 -1
- package/dist/node/routes/worker.cjs +6 -0
- package/dist/node/routes/worker.d.cts +2 -0
- package/dist/node/routes/worker.d.mts +2 -0
- package/dist/node/routes/worker.mjs +6 -0
- package/dist/node-C2nWXElP.mjs +112 -0
- package/dist/node-CinkUtxV.cjs +112 -0
- package/dist/package-BMYLDBBP.cjs +6 -0
- package/dist/{package-Bqbn1AYK.mjs → package-HegMOTL_.mjs} +1 -1
- package/dist/parser-Bh11BsdA.cjs +6 -0
- package/dist/parser-D8eQvE7N.mjs +6 -0
- package/dist/parser-DYRzXWmA.cjs +6 -0
- package/dist/routes-CHf76Ye4.cjs +6 -0
- package/dist/routes-CMUZGI6T.mjs +6 -0
- package/dist/routes-Co1mRM58.cjs +6 -0
- package/dist/search-dialog-BACuzoVX.cjs +6 -0
- package/dist/search-dialog-BKagVT17.js +6 -0
- package/dist/search-dialog-C8w12eUx.js +6 -0
- package/dist/search-dialog-CGyrozZE.cjs +6 -0
- package/dist/search-dialog-D26rUnJ_.cjs +6 -0
- package/dist/sidebar-DKvg6KOc.d.cts +491 -0
- package/dist/sidebar-Dr1TiRIy.d.ts +491 -0
- package/dist/utils-BxNAXhZZ.mjs +7 -0
- package/dist/utils-Clzu7jvb.cjs +7 -0
- package/dist/worker-pool-Bd8Y9KDv.mjs +6 -0
- package/dist/worker-pool-BwU8ckrg.cjs +6 -0
- package/package.json +27 -8
- package/src/client/app/doc-page.tsx +9 -5
- package/src/client/app/docs-layout.tsx +17 -3
- package/src/client/app/head.tsx +122 -0
- package/src/client/app/helmet-compat.tsx +36 -0
- package/src/client/app/mdx-component.tsx +5 -52
- package/src/client/app/mdx-components-context.tsx +32 -8
- package/src/client/app/routes-context.tsx +2 -2
- package/src/client/app/scroll-handler.tsx +1 -1
- package/src/client/app/theme-context.tsx +5 -5
- package/src/client/app/ui-context.tsx +42 -0
- package/src/client/components/docs-layout-default.tsx +85 -0
- package/src/client/components/icons-dev.tsx +38 -15
- package/src/client/components/mdx/callout.tsx +97 -0
- package/src/client/components/mdx/card.tsx +73 -98
- package/src/client/components/mdx/cards.tsx +27 -0
- package/src/client/components/mdx/code-block.tsx +37 -17
- package/src/client/components/mdx/field.tsx +24 -56
- package/src/client/components/mdx/image.tsx +36 -15
- package/src/client/components/mdx/index.ts +19 -53
- package/src/client/components/mdx/table.tsx +46 -148
- package/src/client/components/mdx/typographics.tsx +120 -0
- package/src/client/components/mdx/{hooks/use-code-block.ts → use-code-block.ts} +5 -7
- package/src/client/components/primitives/breadcrumbs.tsx +5 -24
- package/src/client/components/primitives/button.tsx +3 -142
- package/src/client/components/primitives/code-block.tsx +104 -97
- package/src/client/components/{docs-layout.tsx → primitives/docs-layout.tsx} +15 -24
- package/src/client/components/primitives/error-boundary.tsx +107 -0
- package/src/client/components/primitives/heading.tsx +128 -0
- package/src/client/components/primitives/helpers/observer.ts +62 -32
- package/src/client/components/primitives/image.tsx +26 -0
- package/src/client/components/primitives/link.tsx +50 -52
- package/src/client/components/primitives/menu.tsx +25 -49
- package/src/client/components/primitives/navbar.tsx +234 -59
- package/src/client/components/primitives/on-this-page.tsx +169 -40
- package/src/client/components/primitives/page-nav.tsx +11 -39
- package/src/client/components/primitives/popover.tsx +12 -30
- package/src/client/components/primitives/search-dialog.tsx +77 -71
- package/src/client/components/primitives/sidebar.tsx +312 -119
- package/src/client/components/primitives/skeleton.tsx +1 -1
- package/src/client/components/primitives/tabs.tsx +5 -16
- package/src/client/components/primitives/tooltip.tsx +1 -1
- package/src/client/components/ui-base/banner.tsx +66 -0
- package/src/client/components/ui-base/breadcrumbs.tsx +26 -20
- package/src/client/components/ui-base/copy-markdown.tsx +43 -35
- package/src/client/components/ui-base/error-boundary.tsx +9 -46
- package/src/client/components/ui-base/github-stars.tsx +5 -3
- package/src/client/components/ui-base/index.ts +3 -3
- package/src/client/components/ui-base/last-updated.tsx +27 -0
- package/src/client/components/ui-base/navbar.tsx +183 -89
- package/src/client/components/ui-base/not-found.tsx +11 -9
- package/src/client/components/ui-base/on-this-page.tsx +8 -104
- package/src/client/components/ui-base/page-nav.tsx +23 -9
- package/src/client/components/ui-base/search-dialog.tsx +111 -36
- package/src/client/components/ui-base/search-highlight.tsx +10 -0
- package/src/client/components/ui-base/sidebar.tsx +77 -154
- package/src/client/components/ui-base/tabs.tsx +20 -7
- package/src/client/components/ui-base/theme-toggle.tsx +88 -10
- package/src/client/components/ui-base/version-i18n.tsx +80 -0
- package/src/client/hooks/index.ts +2 -1
- package/src/client/hooks/use-analytics.ts +272 -0
- package/src/client/hooks/use-i18n.ts +116 -50
- package/src/client/hooks/use-localized-to.ts +70 -27
- package/src/client/hooks/use-navbar.ts +69 -39
- package/src/client/hooks/use-page-nav.ts +28 -25
- package/src/client/hooks/use-routes.ts +63 -80
- package/src/client/hooks/use-search-highlight.ts +185 -0
- package/src/client/hooks/use-search.ts +12 -3
- package/src/client/hooks/use-sidebar.ts +183 -80
- package/src/client/hooks/use-tabs.ts +3 -4
- package/src/client/hooks/use-version.ts +44 -29
- package/src/client/index.ts +13 -87
- package/src/client/mdx.ts +2 -0
- package/src/client/primitives.ts +19 -0
- package/src/client/ssg/boltdocs-shell.tsx +68 -79
- package/src/client/ssg/create-routes.tsx +268 -72
- package/src/client/ssg/mdx-page.tsx +2 -1
- package/src/client/store/boltdocs-context.tsx +72 -20
- package/src/client/theme/neutral.css +74 -361
- package/src/client/theme/reset.css +189 -0
- package/src/client/types.ts +10 -2
- package/src/client/utils/path.ts +9 -0
- package/src/client/utils/react-to-text.ts +24 -24
- package/src/client/virtual.d.ts +1 -1
- package/src/shared/types.ts +82 -22
- package/dist/node-Bogvkxao.mjs +0 -101
- package/dist/node-CXaog6St.cjs +0 -101
- package/dist/search-dialog-CV3eJzMm.cjs +0 -6
- package/dist/search-dialog-DNTomKgu.js +0 -6
- package/dist/use-search-CS3gH19M.js +0 -6
- package/dist/use-search-DBpJZQuw.cjs +0 -6
- package/src/client/components/mdx/admonition.tsx +0 -91
- package/src/client/components/mdx/badge.tsx +0 -41
- package/src/client/components/mdx/button.tsx +0 -35
- package/src/client/components/mdx/component-preview.tsx +0 -37
- package/src/client/components/mdx/component-props.tsx +0 -83
- package/src/client/components/mdx/file-tree.tsx +0 -325
- package/src/client/components/mdx/hooks/use-component-preview.ts +0 -16
- package/src/client/components/mdx/hooks/useTable.ts +0 -74
- package/src/client/components/mdx/hooks/useTabs.ts +0 -68
- package/src/client/components/mdx/link.tsx +0 -38
- package/src/client/components/mdx/list.tsx +0 -192
- package/src/client/components/mdx/tabs.tsx +0 -135
- package/src/client/components/mdx/video.tsx +0 -68
- package/src/client/components/primitives/index.ts +0 -19
- package/src/client/components/primitives/navigation-menu.tsx +0 -114
- package/src/client/components/ui-base/head.tsx +0 -83
- package/src/client/components/ui-base/loading.tsx +0 -57
- package/src/client/components/ui-base/powered-by.tsx +0 -25
- package/src/client/hooks/use-onthispage.ts +0 -23
- package/src/client/utils/use-on-change.ts +0 -15
|
@@ -1,54 +1,52 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type ReactNode,
|
|
3
|
+
useRef,
|
|
4
|
+
useLayoutEffect,
|
|
5
|
+
useEffect,
|
|
6
|
+
useState,
|
|
7
|
+
} from 'react'
|
|
2
8
|
import * as RAC from 'react-aria-components'
|
|
3
|
-
import { ChevronRight } from 'lucide-react'
|
|
4
9
|
import { cn } from '../../utils/cn'
|
|
10
|
+
import { useUI } from '../../app/ui-context'
|
|
11
|
+
import { Link } from './link'
|
|
12
|
+
import { ChevronRight } from 'lucide-react'
|
|
5
13
|
import type { ComponentBase } from './types'
|
|
6
14
|
import type { ComponentRoute } from '../../types'
|
|
15
|
+
import { useSidebar } from '../../hooks/use-sidebar'
|
|
16
|
+
import { useLocalizedTo } from '../../hooks/use-localized-to'
|
|
17
|
+
import * as LucideIcons from 'lucide-react'
|
|
18
|
+
import virtualIcons from 'virtual:boltdocs-icons'
|
|
7
19
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
icon?: React.ElementType
|
|
11
|
-
}
|
|
20
|
+
// Persistent scroll position across navigation (SPA)
|
|
21
|
+
let sidebarScrollPos = 0
|
|
12
22
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
href: string
|
|
22
|
-
active?: boolean
|
|
23
|
-
icon?: React.ElementType
|
|
24
|
-
badge?: ComponentRoute['badge']
|
|
23
|
+
function getIcon(iconName?: string): React.ElementType | undefined {
|
|
24
|
+
if (!iconName) return undefined
|
|
25
|
+
const icons = { ...LucideIcons, ...virtualIcons } as unknown as Record<
|
|
26
|
+
string,
|
|
27
|
+
React.ElementType
|
|
28
|
+
>
|
|
29
|
+
const IconComponent = icons[iconName] || icons[iconName + 'Icon']
|
|
30
|
+
return IconComponent || undefined
|
|
25
31
|
}
|
|
26
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Internal Badge component for links
|
|
35
|
+
*/
|
|
27
36
|
const Badge = ({ badge }: { badge: ComponentRoute['badge'] }) => {
|
|
28
37
|
const colors = {
|
|
29
|
-
new: 'bg-primary-500/
|
|
30
|
-
updated: 'bg-
|
|
31
|
-
deprecated: 'bg-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Expire Badge
|
|
35
|
-
if (typeof badge === 'object' && badge?.expires) {
|
|
36
|
-
const expireDate = new Date(badge.expires)
|
|
37
|
-
const today = new Date()
|
|
38
|
-
const diffTime = expireDate.getTime() - today.getTime()
|
|
39
|
-
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
|
40
|
-
|
|
41
|
-
if (diffDays === 0) {
|
|
42
|
-
return null
|
|
43
|
-
}
|
|
38
|
+
new: 'bg-primary-500/10 text-primary-500 border border-primary-500/20',
|
|
39
|
+
updated: 'bg-emerald-500/10 text-emerald-500 border border-emerald-500/20',
|
|
40
|
+
deprecated: 'bg-danger-500/10 text-danger-500 border border-danger-500/20',
|
|
44
41
|
}
|
|
45
42
|
|
|
46
43
|
const text = typeof badge === 'string' ? badge : badge?.text
|
|
44
|
+
if (!text) return null
|
|
47
45
|
|
|
48
46
|
return (
|
|
49
47
|
<span
|
|
50
48
|
className={cn(
|
|
51
|
-
'ml-auto flex h-
|
|
49
|
+
'ml-auto flex h-5 items-center rounded-md text-[10px] font-bold px-1.5 py-0.5 uppercase tracking-wider',
|
|
52
50
|
colors[text as keyof typeof colors] || colors.new,
|
|
53
51
|
)}
|
|
54
52
|
>
|
|
@@ -57,53 +55,140 @@ const Badge = ({ badge }: { badge: ComponentRoute['badge'] }) => {
|
|
|
57
55
|
)
|
|
58
56
|
}
|
|
59
57
|
|
|
60
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Desktop Sidebar Container
|
|
60
|
+
*/
|
|
61
|
+
export function SidebarRoot({ children, className }: ComponentBase) {
|
|
61
62
|
return (
|
|
62
63
|
<aside
|
|
63
64
|
className={cn(
|
|
64
|
-
'
|
|
65
|
-
'w-sidebar h-full',
|
|
66
|
-
'overflow-y-auto border-r border-border-subtle bg-bg-main',
|
|
67
|
-
'py-6 px-4',
|
|
65
|
+
'hidden lg:flex flex-col w-sidebar sticky top-navbar h-[calc(100vh-var(--spacing-navbar))] border-r border-subtle bg-main',
|
|
68
66
|
className,
|
|
69
67
|
)}
|
|
70
68
|
>
|
|
71
|
-
|
|
69
|
+
{children}
|
|
72
70
|
</aside>
|
|
73
71
|
)
|
|
74
72
|
}
|
|
75
73
|
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Mobile Sidebar Modal
|
|
76
|
+
*/
|
|
77
|
+
export function SidebarMobile({ children, className }: ComponentBase) {
|
|
78
|
+
const { isSidebarOpen, closeSidebar } = useUI()
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<RAC.ModalOverlay
|
|
82
|
+
isOpen={isSidebarOpen}
|
|
83
|
+
onOpenChange={(open) => !open && closeSidebar()}
|
|
84
|
+
isDismissable={true}
|
|
85
|
+
className={cn(
|
|
86
|
+
'fixed inset-0 z-50 bg-black/20 backdrop-blur-sm lg:hidden',
|
|
87
|
+
'entering:animate-in entering:fade-in exiting:animate-out exiting:fade-out duration-300',
|
|
88
|
+
)}
|
|
89
|
+
>
|
|
90
|
+
<RAC.Modal
|
|
91
|
+
className={cn(
|
|
92
|
+
'fixed top-0 left-0 bottom-0 w-80 bg-main border-r border-subtle shadow-2xl outline-none',
|
|
93
|
+
'entering:animate-in entering:slide-in-from-left exiting:animate-out exiting:slide-out-to-left duration-300',
|
|
94
|
+
className,
|
|
95
|
+
)}
|
|
96
|
+
>
|
|
97
|
+
<RAC.Dialog className="h-full focus:outline-none outline-none flex flex-col">
|
|
98
|
+
{children}
|
|
99
|
+
</RAC.Dialog>
|
|
100
|
+
</RAC.Modal>
|
|
101
|
+
</RAC.ModalOverlay>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Shared Header for Sidebar
|
|
107
|
+
*/
|
|
108
|
+
export function SidebarHeader({ children, className }: ComponentBase) {
|
|
109
|
+
return (
|
|
110
|
+
<div
|
|
111
|
+
className={cn(
|
|
112
|
+
'flex items-center justify-between p-4 border-b border-subtle',
|
|
113
|
+
className,
|
|
114
|
+
)}
|
|
115
|
+
>
|
|
116
|
+
{children}
|
|
117
|
+
</div>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Scrollable Content Wrapper
|
|
123
|
+
*/
|
|
124
|
+
export function SidebarContent({ children, className }: ComponentBase) {
|
|
125
|
+
const scrollRef = useRef<HTMLDivElement>(null)
|
|
126
|
+
|
|
127
|
+
// Restore scroll position
|
|
128
|
+
useLayoutEffect(() => {
|
|
129
|
+
if (scrollRef.current) {
|
|
130
|
+
scrollRef.current.scrollTop = sidebarScrollPos
|
|
131
|
+
}
|
|
132
|
+
}, [])
|
|
133
|
+
|
|
134
|
+
// Save scroll position
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
const el = scrollRef.current
|
|
137
|
+
if (!el) return
|
|
138
|
+
const handleScroll = () => {
|
|
139
|
+
sidebarScrollPos = el.scrollTop
|
|
140
|
+
}
|
|
141
|
+
el.addEventListener('scroll', handleScroll, { passive: true })
|
|
142
|
+
return () => el.removeEventListener('scroll', handleScroll)
|
|
143
|
+
}, [])
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div
|
|
147
|
+
ref={scrollRef}
|
|
148
|
+
className={cn(
|
|
149
|
+
'flex-1 overflow-y-auto p-4 pb-16 custom-scrollbar',
|
|
150
|
+
className,
|
|
151
|
+
)}
|
|
152
|
+
>
|
|
153
|
+
<nav className="flex flex-col gap-6">{children}</nav>
|
|
154
|
+
</div>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Navigation Group
|
|
160
|
+
*/
|
|
161
|
+
export const SidebarGroup = ({
|
|
78
162
|
title,
|
|
79
163
|
icon: Icon,
|
|
164
|
+
children,
|
|
80
165
|
className,
|
|
81
|
-
}:
|
|
166
|
+
}: { title?: string; icon?: React.ElementType } & ComponentBase) => {
|
|
82
167
|
return (
|
|
83
|
-
<div className={
|
|
168
|
+
<div className={className}>
|
|
84
169
|
{title && (
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
)}
|
|
90
|
-
>
|
|
91
|
-
<div className="flex items-center gap-2">
|
|
92
|
-
{Icon && <Icon size={14} />}
|
|
93
|
-
{title}
|
|
94
|
-
</div>
|
|
95
|
-
</div>
|
|
170
|
+
<h4 className="px-2 mb-2 flex items-center gap-2 text-[11px] font-bold uppercase tracking-widest text-muted/50">
|
|
171
|
+
{Icon && <Icon size={12} />}
|
|
172
|
+
{title}
|
|
173
|
+
</h4>
|
|
96
174
|
)}
|
|
97
|
-
|
|
175
|
+
<div className="flex flex-col gap-0.5">{children}</div>
|
|
98
176
|
</div>
|
|
99
177
|
)
|
|
100
178
|
}
|
|
101
179
|
|
|
102
|
-
|
|
103
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Sidebar Link
|
|
182
|
+
*/
|
|
183
|
+
export interface SidebarLinkProps extends ComponentBase {
|
|
184
|
+
label: string
|
|
185
|
+
href: string
|
|
186
|
+
active?: boolean
|
|
187
|
+
icon?: React.ElementType
|
|
188
|
+
badge?: ComponentRoute['badge']
|
|
104
189
|
}
|
|
105
190
|
|
|
106
|
-
const SidebarLink = ({
|
|
191
|
+
export const SidebarLink = ({
|
|
107
192
|
label,
|
|
108
193
|
href,
|
|
109
194
|
active,
|
|
@@ -115,13 +200,10 @@ const SidebarLink = ({
|
|
|
115
200
|
<Link
|
|
116
201
|
href={href}
|
|
117
202
|
className={cn(
|
|
118
|
-
'group flex items-center gap-2.5 rounded-lg px-2.5 py-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
'bg-primary-500/10 text-primary-500 font-medium': active,
|
|
123
|
-
'text-text-muted hover:bg-bg-muted hover:text-text-main': !active,
|
|
124
|
-
},
|
|
203
|
+
'group flex items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-sm transition-all outline-none',
|
|
204
|
+
active
|
|
205
|
+
? 'bg-primary-500/10 text-primary-500 font-medium shadow-sm'
|
|
206
|
+
: 'text-muted hover:bg-surface hover:text-body',
|
|
125
207
|
className,
|
|
126
208
|
)}
|
|
127
209
|
>
|
|
@@ -129,9 +211,7 @@ const SidebarLink = ({
|
|
|
129
211
|
<Icon
|
|
130
212
|
size={16}
|
|
131
213
|
className={cn(
|
|
132
|
-
active
|
|
133
|
-
? 'text-primary-500'
|
|
134
|
-
: 'text-text-muted group-hover:text-text-main',
|
|
214
|
+
active ? 'text-primary-500' : 'text-muted group-hover:text-body',
|
|
135
215
|
)}
|
|
136
216
|
/>
|
|
137
217
|
)}
|
|
@@ -141,62 +221,54 @@ const SidebarLink = ({
|
|
|
141
221
|
)
|
|
142
222
|
}
|
|
143
223
|
|
|
144
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Nested SubGroup
|
|
226
|
+
*/
|
|
227
|
+
export const SidebarSubGroup = ({
|
|
145
228
|
label,
|
|
146
229
|
href,
|
|
147
230
|
active,
|
|
148
231
|
icon: Icon,
|
|
149
232
|
badge,
|
|
150
|
-
|
|
151
|
-
isOpen = false,
|
|
233
|
+
isOpen,
|
|
152
234
|
onToggle,
|
|
153
235
|
children,
|
|
154
|
-
|
|
236
|
+
className,
|
|
237
|
+
}: SidebarLinkProps & {
|
|
238
|
+
isOpen: boolean
|
|
239
|
+
onToggle: () => void
|
|
240
|
+
children: ReactNode
|
|
241
|
+
}) => {
|
|
155
242
|
return (
|
|
156
|
-
<div className="
|
|
157
|
-
<div className="flex items-center
|
|
158
|
-
<
|
|
243
|
+
<div className="flex flex-col gap-0.5">
|
|
244
|
+
<div className="group relative flex items-center">
|
|
245
|
+
<SidebarLink
|
|
246
|
+
label={label}
|
|
159
247
|
href={href}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
248
|
+
active={active}
|
|
249
|
+
icon={Icon}
|
|
250
|
+
badge={badge}
|
|
251
|
+
className={cn('flex-1 pr-8', className)}
|
|
252
|
+
/>
|
|
253
|
+
<button
|
|
254
|
+
onClick={(e) => {
|
|
255
|
+
e.preventDefault()
|
|
256
|
+
e.stopPropagation()
|
|
257
|
+
onToggle()
|
|
258
|
+
}}
|
|
259
|
+
className="absolute right-1 p-1.5 text-muted hover:text-body transition-colors outline-none cursor-pointer"
|
|
169
260
|
>
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
/>
|
|
179
|
-
)}
|
|
180
|
-
<span className="truncate">{label}</span>
|
|
181
|
-
{badge && <Badge badge={badge} />}
|
|
182
|
-
</Link>
|
|
183
|
-
{children && (
|
|
184
|
-
<RAC.Button
|
|
185
|
-
onPress={onToggle}
|
|
186
|
-
className="flex items-center justify-center p-1.5 ml-1 rounded-md text-text-muted hover:bg-bg-surface hover:text-text-main transition-colors outline-none focus-visible:ring-2 focus-visible:ring-primary-500/30 cursor-pointer"
|
|
187
|
-
>
|
|
188
|
-
<ChevronRight
|
|
189
|
-
size={16}
|
|
190
|
-
className={cn(
|
|
191
|
-
'transition-transform duration-200',
|
|
192
|
-
isOpen && 'rotate-90',
|
|
193
|
-
)}
|
|
194
|
-
/>
|
|
195
|
-
</RAC.Button>
|
|
196
|
-
)}
|
|
261
|
+
<ChevronRight
|
|
262
|
+
size={14}
|
|
263
|
+
className={cn(
|
|
264
|
+
'transition-transform duration-200',
|
|
265
|
+
isOpen && 'rotate-90',
|
|
266
|
+
)}
|
|
267
|
+
/>
|
|
268
|
+
</button>
|
|
197
269
|
</div>
|
|
198
|
-
{isOpen &&
|
|
199
|
-
<div className="
|
|
270
|
+
{isOpen && (
|
|
271
|
+
<div className="ml-4 pl-3 border-l border-subtle/50 mt-0.5 flex flex-col gap-0.5">
|
|
200
272
|
{children}
|
|
201
273
|
</div>
|
|
202
274
|
)}
|
|
@@ -204,8 +276,129 @@ const SidebarSubGroup = ({
|
|
|
204
276
|
)
|
|
205
277
|
}
|
|
206
278
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
279
|
+
/**
|
|
280
|
+
* Automated single-route rendering primitive
|
|
281
|
+
*/
|
|
282
|
+
export interface SidebarItemProps extends ComponentBase {
|
|
283
|
+
route: ComponentRoute
|
|
284
|
+
activePath: string
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function SidebarItem({
|
|
288
|
+
route,
|
|
289
|
+
activePath,
|
|
290
|
+
className,
|
|
291
|
+
}: SidebarItemProps) {
|
|
292
|
+
const localizedHref = useLocalizedTo(route.path)
|
|
293
|
+
const isCurrent =
|
|
294
|
+
activePath ===
|
|
295
|
+
(localizedHref.endsWith('/') ? localizedHref.slice(0, -1) : localizedHref)
|
|
296
|
+
const hasChildren = !!route.routes?.length || !!route.subRoutes?.length
|
|
297
|
+
const children = route.routes || route.subRoutes
|
|
298
|
+
|
|
299
|
+
const [isOpen, setIsOpen] = useState(() =>
|
|
300
|
+
activePath.startsWith(localizedHref),
|
|
301
|
+
)
|
|
302
|
+
const [prevActivePath, setPrevActivePath] = useState(activePath)
|
|
303
|
+
|
|
304
|
+
if (activePath !== prevActivePath) {
|
|
305
|
+
setPrevActivePath(activePath)
|
|
306
|
+
if (activePath.startsWith(localizedHref)) {
|
|
307
|
+
setIsOpen(true)
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (hasChildren) {
|
|
312
|
+
return (
|
|
313
|
+
<SidebarSubGroup
|
|
314
|
+
label={route.title}
|
|
315
|
+
href={route.path}
|
|
316
|
+
active={isCurrent}
|
|
317
|
+
icon={getIcon(route.icon)}
|
|
318
|
+
badge={route.badge}
|
|
319
|
+
isOpen={isOpen}
|
|
320
|
+
onToggle={() => setIsOpen(!isOpen)}
|
|
321
|
+
className={className}
|
|
322
|
+
>
|
|
323
|
+
{children?.map((subRoute) => (
|
|
324
|
+
<SidebarItem
|
|
325
|
+
key={subRoute.path}
|
|
326
|
+
route={subRoute}
|
|
327
|
+
activePath={activePath}
|
|
328
|
+
/>
|
|
329
|
+
))}
|
|
330
|
+
</SidebarSubGroup>
|
|
331
|
+
)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return (
|
|
335
|
+
<SidebarLink
|
|
336
|
+
label={route.title}
|
|
337
|
+
href={route.path}
|
|
338
|
+
active={isCurrent}
|
|
339
|
+
icon={getIcon(route.icon)}
|
|
340
|
+
badge={route.badge}
|
|
341
|
+
className={className}
|
|
342
|
+
/>
|
|
343
|
+
)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* High-level automated routes data rendering primitive
|
|
348
|
+
*/
|
|
349
|
+
export interface SidebarItemsProps extends ComponentBase {
|
|
350
|
+
routes: ComponentRoute[]
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function SidebarItems({ routes, className }: SidebarItemsProps) {
|
|
354
|
+
const { groups, ungrouped, activePath } = useSidebar(routes)
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<div className={cn('flex flex-col gap-6', className)}>
|
|
358
|
+
{ungrouped.length > 0 && (
|
|
359
|
+
<SidebarGroup>
|
|
360
|
+
{ungrouped.map((route) => (
|
|
361
|
+
<SidebarItem
|
|
362
|
+
key={route.path}
|
|
363
|
+
route={route}
|
|
364
|
+
activePath={activePath}
|
|
365
|
+
/>
|
|
366
|
+
))}
|
|
367
|
+
</SidebarGroup>
|
|
368
|
+
)}
|
|
369
|
+
|
|
370
|
+
{groups.map((group) => (
|
|
371
|
+
<SidebarGroup
|
|
372
|
+
key={group.title}
|
|
373
|
+
title={group.title}
|
|
374
|
+
icon={getIcon(group.icon)}
|
|
375
|
+
>
|
|
376
|
+
{group.routes.map((route) => (
|
|
377
|
+
<SidebarItem
|
|
378
|
+
key={route.path}
|
|
379
|
+
route={route}
|
|
380
|
+
activePath={activePath}
|
|
381
|
+
/>
|
|
382
|
+
))}
|
|
383
|
+
</SidebarGroup>
|
|
384
|
+
))}
|
|
385
|
+
</div>
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Main Sidebar Export
|
|
391
|
+
*/
|
|
392
|
+
export const Sidebar = Object.assign(SidebarRoot, {
|
|
393
|
+
Root: SidebarRoot,
|
|
394
|
+
Mobile: SidebarMobile,
|
|
395
|
+
Header: SidebarHeader,
|
|
396
|
+
Content: SidebarContent,
|
|
397
|
+
Group: SidebarGroup,
|
|
398
|
+
Link: SidebarLink,
|
|
399
|
+
SubGroup: SidebarSubGroup,
|
|
400
|
+
Item: SidebarItem,
|
|
401
|
+
Items: SidebarItems,
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
export default Sidebar
|
|
@@ -25,10 +25,7 @@ const TabsList = ({ children, className = '' }: ComponentBase) => {
|
|
|
25
25
|
return (
|
|
26
26
|
<div
|
|
27
27
|
role="tablist"
|
|
28
|
-
className={cn(
|
|
29
|
-
'relative flex flex-row items-center border-b border-border-subtle',
|
|
30
|
-
className,
|
|
31
|
-
)}
|
|
28
|
+
className={cn('relative flex flex-row items-center', className)}
|
|
32
29
|
>
|
|
33
30
|
{children}
|
|
34
31
|
</div>
|
|
@@ -46,9 +43,9 @@ const TabsItem = ({
|
|
|
46
43
|
<button
|
|
47
44
|
role="tab"
|
|
48
45
|
aria-selected={selected}
|
|
46
|
+
data-selected={selected}
|
|
49
47
|
className={cn(
|
|
50
|
-
'
|
|
51
|
-
selected ? 'text-primary-500' : 'text-text-muted hover:text-text-main',
|
|
48
|
+
'outline-none cursor-pointer bg-transparent border-none',
|
|
52
49
|
className,
|
|
53
50
|
)}
|
|
54
51
|
{...props}
|
|
@@ -59,19 +56,11 @@ const TabsItem = ({
|
|
|
59
56
|
}
|
|
60
57
|
|
|
61
58
|
const TabsContent = ({ children, className = '' }: ComponentBase) => {
|
|
62
|
-
return <div className={cn('
|
|
59
|
+
return <div className={cn('outline-none', className)}>{children}</div>
|
|
63
60
|
}
|
|
64
61
|
|
|
65
62
|
const TabsIndicator = ({ className = '', style }: TabsIndicatorProps) => {
|
|
66
|
-
return (
|
|
67
|
-
<div
|
|
68
|
-
className={cn(
|
|
69
|
-
'absolute bottom-0 h-0.5 bg-primary-500 transition-all duration-300',
|
|
70
|
-
className,
|
|
71
|
-
)}
|
|
72
|
-
style={style}
|
|
73
|
-
/>
|
|
74
|
-
)
|
|
63
|
+
return <div className={cn('absolute bottom-0', className)} style={style} />
|
|
75
64
|
}
|
|
76
65
|
|
|
77
66
|
Tabs.Root = Tabs
|
|
@@ -31,7 +31,7 @@ const TooltipContent = ({
|
|
|
31
31
|
offset={8}
|
|
32
32
|
className={(values) =>
|
|
33
33
|
cn(
|
|
34
|
-
'group z-50 overflow-visible rounded-md bg-
|
|
34
|
+
'group z-50 overflow-visible rounded-md bg-surface px-2.5 py-1.5 text-xs font-medium text-body ring-1 ring-subtle outline-hidden select-none',
|
|
35
35
|
'data-entering:animate-in data-entering:fade-in data-entering:zoom-in-95 data-entering:duration-100',
|
|
36
36
|
'data-exiting:animate-out data-exiting:fade-out data-exiting:zoom-out-95 data-exiting:duration-75',
|
|
37
37
|
'data-[placement=top]:slide-in-from-bottom-1',
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
|
+
import { X } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
export interface BannerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
/**
|
|
6
|
+
* If true, shows a close button to dismiss the banner.
|
|
7
|
+
*/
|
|
8
|
+
dismissible?: boolean
|
|
9
|
+
/**
|
|
10
|
+
* Unique identifier for the banner. If provided and dismissible is true,
|
|
11
|
+
* the dismissed state will be saved in localStorage so it doesn't reappear
|
|
12
|
+
* on subsequent visits.
|
|
13
|
+
*/
|
|
14
|
+
id?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function Banner({
|
|
18
|
+
children,
|
|
19
|
+
className = '',
|
|
20
|
+
dismissible = false,
|
|
21
|
+
id = 'boltdocs-banner',
|
|
22
|
+
...props
|
|
23
|
+
}: BannerProps) {
|
|
24
|
+
const [isVisible, setIsVisible] = useState(true)
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (dismissible && id) {
|
|
28
|
+
const isDismissed = localStorage.getItem(
|
|
29
|
+
`boltdocs-banner-dismissed-${id}`,
|
|
30
|
+
)
|
|
31
|
+
if (isDismissed === 'true') {
|
|
32
|
+
setIsVisible(false)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}, [dismissible, id])
|
|
36
|
+
|
|
37
|
+
const handleDismiss = () => {
|
|
38
|
+
setIsVisible(false)
|
|
39
|
+
if (dismissible && id) {
|
|
40
|
+
localStorage.setItem(`boltdocs-banner-dismissed-${id}`, 'true')
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!isVisible) return null
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div
|
|
48
|
+
className={`relative flex items-center justify-center px-4 py-2.5 text-xs font-semibold tracking-wide bg-primary-500/10 dark:bg-primary-500/15 text-primary-700 dark:text-primary-300 border-b border-primary-500/20 select-none animate-in fade-in duration-300 ${className}`}
|
|
49
|
+
{...props}
|
|
50
|
+
>
|
|
51
|
+
<div className="flex-1 text-center flex items-center justify-center gap-2">
|
|
52
|
+
{children}
|
|
53
|
+
</div>
|
|
54
|
+
{dismissible && (
|
|
55
|
+
<button
|
|
56
|
+
onClick={handleDismiss}
|
|
57
|
+
className="absolute right-3 top-1/2 -translate-y-1/2 p-1.5 opacity-70 hover:opacity-100 transition-all duration-300 rounded-xl hover:bg-primary-500/10 cursor-pointer border-none bg-transparent flex items-center justify-center outline-none"
|
|
58
|
+
aria-label="Dismiss banner"
|
|
59
|
+
>
|
|
60
|
+
<X className="w-3.5 h-3.5" />
|
|
61
|
+
</button>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
export default Banner
|