@lexibase/rdx-ui 2.0.0 → 3.1.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/THIRD_PARTY_NOTICES.md +8 -2
- package/package.json +8 -3
- package/src/components/button.tsx +2 -0
- package/src/components/code-block.tsx +274 -46
- package/src/components/copy-code-button.tsx +1 -79
- package/src/components/dialog.tsx +123 -36
- package/src/components/docs-page-transition.tsx +178 -0
- package/src/components/docs-search-dialog.tsx +6 -1
- package/src/components/docs-sidebar.tsx +103 -30
- package/src/components/docs-toc.tsx +6 -6
- package/src/components/mdx-code-block-view.tsx +203 -0
- package/src/components/mdx-code-block.tsx +161 -0
- package/src/components/mdx-components.tsx +47 -16
- package/src/components/mdx-folder-tree.tsx +58 -0
- package/src/components/mdx-image.tsx +73 -0
- package/src/components/mdx-mermaid.tsx +252 -0
- package/src/components/sheet.tsx +146 -0
- package/src/components/tabs.tsx +12 -3
- package/src/hooks/use-highlighted-code.ts +75 -0
- package/src/lib/highlighter.tsx +124 -0
- package/src/lib/i18n.ts +32 -0
- package/src/lib/mermaid.ts +114 -0
- package/src/styles/globals.css +91 -14
- package/src/styles/shadcn.css +650 -0
|
@@ -1,79 +1,166 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useMessages } from '../providers/locale-provider'
|
|
4
|
-
|
|
5
|
-
// shadcn/ui Base Nova Dialog, using the shared RDX theme and buttons.
|
|
6
3
|
import * as React from 'react'
|
|
7
4
|
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog'
|
|
8
|
-
import {
|
|
9
|
-
|
|
5
|
+
import { cn, mergeStateClassName } from '../lib/utils'
|
|
6
|
+
|
|
10
7
|
import { Button } from './button'
|
|
8
|
+
import { XIcon } from 'lucide-react'
|
|
9
|
+
import { useMessages } from '../providers/locale-provider'
|
|
11
10
|
|
|
12
|
-
function Dialog(props:
|
|
13
|
-
return <DialogPrimitive.Root {...props} />
|
|
11
|
+
function Dialog({ ...props }: DialogPrimitive.Root.Props) {
|
|
12
|
+
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
|
14
13
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
) {
|
|
14
|
+
|
|
15
|
+
function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {
|
|
18
16
|
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
|
19
17
|
}
|
|
18
|
+
|
|
19
|
+
function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {
|
|
20
|
+
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function DialogClose({ ...props }: DialogPrimitive.Close.Props) {
|
|
24
|
+
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function DialogOverlay({
|
|
28
|
+
className,
|
|
29
|
+
...props
|
|
30
|
+
}: DialogPrimitive.Backdrop.Props) {
|
|
31
|
+
return (
|
|
32
|
+
<DialogPrimitive.Backdrop
|
|
33
|
+
data-slot="dialog-overlay"
|
|
34
|
+
className={mergeStateClassName(
|
|
35
|
+
'fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 motion-reduce:data-open:animate-none motion-reduce:data-closed:animate-none',
|
|
36
|
+
className
|
|
37
|
+
)}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
20
43
|
function DialogContent({
|
|
21
44
|
className,
|
|
22
45
|
children,
|
|
46
|
+
showCloseButton = true,
|
|
47
|
+
closeLabel,
|
|
48
|
+
overlayClassName,
|
|
23
49
|
...props
|
|
24
|
-
}:
|
|
50
|
+
}: DialogPrimitive.Popup.Props & {
|
|
51
|
+
showCloseButton?: boolean
|
|
52
|
+
closeLabel?: string
|
|
53
|
+
overlayClassName?: DialogPrimitive.Backdrop.Props['className']
|
|
54
|
+
}) {
|
|
25
55
|
const messages = useMessages()
|
|
26
56
|
return (
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
data-slot="dialog-overlay"
|
|
30
|
-
className="fixed inset-0 isolate z-50 bg-black/30 backdrop-blur-xs duration-100 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 motion-reduce:data-open:animate-none motion-reduce:data-closed:animate-none"
|
|
31
|
-
/>
|
|
57
|
+
<DialogPortal>
|
|
58
|
+
<DialogOverlay className={overlayClassName} />
|
|
32
59
|
<DialogPrimitive.Popup
|
|
33
60
|
data-slot="dialog-content"
|
|
34
61
|
className={mergeStateClassName(
|
|
35
|
-
'fixed top-
|
|
62
|
+
'fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 motion-reduce:data-open:animate-none motion-reduce:data-closed:animate-none',
|
|
36
63
|
className
|
|
37
64
|
)}
|
|
38
65
|
{...props}
|
|
39
66
|
>
|
|
40
67
|
{children}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
68
|
+
{showCloseButton && (
|
|
69
|
+
<DialogPrimitive.Close
|
|
70
|
+
data-slot="dialog-close"
|
|
71
|
+
render={
|
|
72
|
+
<Button
|
|
73
|
+
variant="ghost"
|
|
74
|
+
className="absolute top-2 right-2"
|
|
75
|
+
size="icon-sm"
|
|
76
|
+
/>
|
|
77
|
+
}
|
|
78
|
+
>
|
|
79
|
+
<XIcon />
|
|
80
|
+
<span className="sr-only">{closeLabel ?? messages.close}</span>
|
|
81
|
+
</DialogPrimitive.Close>
|
|
82
|
+
)}
|
|
53
83
|
</DialogPrimitive.Popup>
|
|
54
|
-
</
|
|
84
|
+
</DialogPortal>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
89
|
+
return (
|
|
90
|
+
<div
|
|
91
|
+
data-slot="dialog-header"
|
|
92
|
+
className={cn('flex flex-col gap-2', className)}
|
|
93
|
+
{...props}
|
|
94
|
+
/>
|
|
55
95
|
)
|
|
56
96
|
}
|
|
57
|
-
|
|
97
|
+
|
|
98
|
+
function DialogFooter({
|
|
58
99
|
className,
|
|
100
|
+
showCloseButton = false,
|
|
101
|
+
children,
|
|
59
102
|
...props
|
|
60
|
-
}: React.ComponentProps<
|
|
103
|
+
}: React.ComponentProps<'div'> & {
|
|
104
|
+
showCloseButton?: boolean
|
|
105
|
+
}) {
|
|
106
|
+
const messages = useMessages()
|
|
107
|
+
return (
|
|
108
|
+
<div
|
|
109
|
+
data-slot="dialog-footer"
|
|
110
|
+
className={cn(
|
|
111
|
+
'-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',
|
|
112
|
+
className
|
|
113
|
+
)}
|
|
114
|
+
{...props}
|
|
115
|
+
>
|
|
116
|
+
{children}
|
|
117
|
+
{showCloseButton && (
|
|
118
|
+
<DialogPrimitive.Close render={<Button variant="outline" />}>
|
|
119
|
+
{messages.close}
|
|
120
|
+
</DialogPrimitive.Close>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
|
|
61
127
|
return (
|
|
62
128
|
<DialogPrimitive.Title
|
|
63
|
-
|
|
129
|
+
data-slot="dialog-title"
|
|
130
|
+
className={mergeStateClassName(
|
|
131
|
+
'cn-font-heading text-base leading-none font-medium',
|
|
132
|
+
className
|
|
133
|
+
)}
|
|
64
134
|
{...props}
|
|
65
135
|
/>
|
|
66
136
|
)
|
|
67
137
|
}
|
|
138
|
+
|
|
68
139
|
function DialogDescription({
|
|
69
140
|
className,
|
|
70
141
|
...props
|
|
71
|
-
}:
|
|
142
|
+
}: DialogPrimitive.Description.Props) {
|
|
72
143
|
return (
|
|
73
144
|
<DialogPrimitive.Description
|
|
74
|
-
|
|
145
|
+
data-slot="dialog-description"
|
|
146
|
+
className={mergeStateClassName(
|
|
147
|
+
'text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',
|
|
148
|
+
className
|
|
149
|
+
)}
|
|
75
150
|
{...props}
|
|
76
151
|
/>
|
|
77
152
|
)
|
|
78
153
|
}
|
|
79
|
-
|
|
154
|
+
|
|
155
|
+
export {
|
|
156
|
+
Dialog,
|
|
157
|
+
DialogClose,
|
|
158
|
+
DialogContent,
|
|
159
|
+
DialogDescription,
|
|
160
|
+
DialogFooter,
|
|
161
|
+
DialogHeader,
|
|
162
|
+
DialogOverlay,
|
|
163
|
+
DialogPortal,
|
|
164
|
+
DialogTitle,
|
|
165
|
+
DialogTrigger,
|
|
166
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, ViewTransition, type ReactNode } from 'react'
|
|
4
|
+
import { usePathname, useRouter } from 'next/navigation'
|
|
5
|
+
|
|
6
|
+
const directions = {
|
|
7
|
+
'rdx-doc-forward': 'rdx-doc-forward',
|
|
8
|
+
'rdx-doc-back': 'rdx-doc-back',
|
|
9
|
+
default: 'none',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const interactive = [
|
|
13
|
+
'a',
|
|
14
|
+
'button',
|
|
15
|
+
'input',
|
|
16
|
+
'textarea',
|
|
17
|
+
'select',
|
|
18
|
+
'label',
|
|
19
|
+
'summary',
|
|
20
|
+
'dialog',
|
|
21
|
+
'[contenteditable]:not([contenteditable="false"])',
|
|
22
|
+
'[role="button"]',
|
|
23
|
+
'[role="slider"]',
|
|
24
|
+
'[role="textbox"]',
|
|
25
|
+
'[role="combobox"]',
|
|
26
|
+
'[draggable="true"]',
|
|
27
|
+
'pre',
|
|
28
|
+
'table',
|
|
29
|
+
'img',
|
|
30
|
+
'video',
|
|
31
|
+
'audio',
|
|
32
|
+
'canvas',
|
|
33
|
+
'iframe',
|
|
34
|
+
'[data-rdx-code-block]',
|
|
35
|
+
'[data-slot="table-container"]',
|
|
36
|
+
'[data-slot="tabs"]',
|
|
37
|
+
'[data-rdx-mermaid-preview]',
|
|
38
|
+
].join(',')
|
|
39
|
+
|
|
40
|
+
/** Let the router retain ownership of navigation, focus and history. */
|
|
41
|
+
export function DocsPageTransition({
|
|
42
|
+
children,
|
|
43
|
+
previousHref,
|
|
44
|
+
nextHref,
|
|
45
|
+
}: {
|
|
46
|
+
children: ReactNode
|
|
47
|
+
previousHref?: string
|
|
48
|
+
nextHref?: string
|
|
49
|
+
}) {
|
|
50
|
+
const router = useRouter()
|
|
51
|
+
const pathname = usePathname()
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const scroller = document.querySelector<HTMLElement>(
|
|
55
|
+
'[data-rdx-docs-scroll]'
|
|
56
|
+
)
|
|
57
|
+
if (!scroller) return
|
|
58
|
+
let gesture: {
|
|
59
|
+
id: number
|
|
60
|
+
x: number
|
|
61
|
+
y: number
|
|
62
|
+
top: number
|
|
63
|
+
time: number
|
|
64
|
+
} | null = null
|
|
65
|
+
let navigationAt = -Infinity
|
|
66
|
+
const selected = () => window.getSelection()?.isCollapsed === false
|
|
67
|
+
const cancel = () => {
|
|
68
|
+
gesture = null
|
|
69
|
+
}
|
|
70
|
+
const start = (event: TouchEvent) => {
|
|
71
|
+
cancel()
|
|
72
|
+
if (
|
|
73
|
+
event.defaultPrevented ||
|
|
74
|
+
window.innerWidth >= 768 ||
|
|
75
|
+
event.touches.length !== 1 ||
|
|
76
|
+
selected()
|
|
77
|
+
)
|
|
78
|
+
return
|
|
79
|
+
const target = event.target
|
|
80
|
+
if (!(target instanceof Element) || target.closest(interactive)) return
|
|
81
|
+
// Leave nested horizontal scrollers to their own gestures, including custom MDX.
|
|
82
|
+
for (
|
|
83
|
+
let element: Element | null = target;
|
|
84
|
+
element && element !== scroller;
|
|
85
|
+
element = element.parentElement
|
|
86
|
+
) {
|
|
87
|
+
if (
|
|
88
|
+
element.scrollWidth > element.clientWidth + 1 &&
|
|
89
|
+
/auto|scroll/.test(getComputedStyle(element).overflowX)
|
|
90
|
+
)
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
const touch = event.touches[0]!
|
|
94
|
+
// Keep browser/system edge gestures available.
|
|
95
|
+
if (touch.clientX < 32 || touch.clientX > window.innerWidth - 32) return
|
|
96
|
+
gesture = {
|
|
97
|
+
id: touch.identifier,
|
|
98
|
+
x: touch.clientX,
|
|
99
|
+
y: touch.clientY,
|
|
100
|
+
top: scroller.scrollTop,
|
|
101
|
+
time: performance.now(),
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const move = (event: TouchEvent) => {
|
|
105
|
+
if (event.defaultPrevented) return cancel()
|
|
106
|
+
if (!gesture) return
|
|
107
|
+
const touch = event.touches[0]
|
|
108
|
+
if (
|
|
109
|
+
event.touches.length !== 1 ||
|
|
110
|
+
!touch ||
|
|
111
|
+
touch.identifier !== gesture.id
|
|
112
|
+
)
|
|
113
|
+
return cancel()
|
|
114
|
+
const dx = Math.abs(touch.clientX - gesture.x)
|
|
115
|
+
const dy = Math.abs(touch.clientY - gesture.y)
|
|
116
|
+
if (dy > 12 && dy >= dx) cancel()
|
|
117
|
+
}
|
|
118
|
+
const end = (event: TouchEvent) => {
|
|
119
|
+
const current = gesture
|
|
120
|
+
cancel()
|
|
121
|
+
if (
|
|
122
|
+
!current ||
|
|
123
|
+
event.touches.length ||
|
|
124
|
+
selected() ||
|
|
125
|
+
window.innerWidth >= 768
|
|
126
|
+
)
|
|
127
|
+
return
|
|
128
|
+
const touch = Array.from(event.changedTouches).find(
|
|
129
|
+
(entry) => entry.identifier === current.id
|
|
130
|
+
)
|
|
131
|
+
if (!touch) return
|
|
132
|
+
const dx = touch.clientX - current.x
|
|
133
|
+
const dy = touch.clientY - current.y
|
|
134
|
+
const now = performance.now()
|
|
135
|
+
if (
|
|
136
|
+
now - current.time > 1000 ||
|
|
137
|
+
now - navigationAt < 450 ||
|
|
138
|
+
Math.abs(scroller.scrollTop - current.top) > 8
|
|
139
|
+
)
|
|
140
|
+
return
|
|
141
|
+
if (
|
|
142
|
+
Math.abs(dx) < Math.min(120, Math.max(64, window.innerWidth * 0.18)) ||
|
|
143
|
+
Math.abs(dx) < Math.abs(dy) * 1.5
|
|
144
|
+
)
|
|
145
|
+
return
|
|
146
|
+
const href = dx < 0 ? nextHref : previousHref
|
|
147
|
+
if (!href) return
|
|
148
|
+
navigationAt = now
|
|
149
|
+
router.push(href, {
|
|
150
|
+
transitionTypes: [dx < 0 ? 'rdx-doc-forward' : 'rdx-doc-back'],
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
// Passive listeners never take scrolling, zooming or selection away from the browser.
|
|
154
|
+
scroller.addEventListener('touchstart', start, { passive: true })
|
|
155
|
+
scroller.addEventListener('touchmove', move, { passive: true })
|
|
156
|
+
scroller.addEventListener('touchend', end, { passive: true })
|
|
157
|
+
scroller.addEventListener('touchcancel', cancel, { passive: true })
|
|
158
|
+
return () => {
|
|
159
|
+
scroller.removeEventListener('touchstart', start)
|
|
160
|
+
scroller.removeEventListener('touchmove', move)
|
|
161
|
+
scroller.removeEventListener('touchend', end)
|
|
162
|
+
scroller.removeEventListener('touchcancel', cancel)
|
|
163
|
+
}
|
|
164
|
+
}, [pathname, previousHref, nextHref, router])
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<ViewTransition
|
|
168
|
+
name="rdx-document"
|
|
169
|
+
default="none"
|
|
170
|
+
enter={directions}
|
|
171
|
+
exit={directions}
|
|
172
|
+
update={directions}
|
|
173
|
+
share={directions}
|
|
174
|
+
>
|
|
175
|
+
{children}
|
|
176
|
+
</ViewTransition>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
@@ -99,7 +99,12 @@ export function DocsSearchDialog({
|
|
|
99
99
|
}
|
|
100
100
|
return (
|
|
101
101
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
102
|
-
<DialogContent
|
|
102
|
+
<DialogContent
|
|
103
|
+
finalFocus={triggerRef}
|
|
104
|
+
closeLabel={messages.closeSearch}
|
|
105
|
+
overlayClassName="bg-black/30"
|
|
106
|
+
className="top-[20%] w-[calc(100%-2rem)] max-w-lg translate-y-0 p-1 shadow-lg sm:max-w-lg"
|
|
107
|
+
>
|
|
103
108
|
<DialogTitle className="sr-only">
|
|
104
109
|
{messages.searchDocumentation}
|
|
105
110
|
</DialogTitle>
|
|
@@ -4,9 +4,17 @@ import { useMessages } from '../providers/locale-provider'
|
|
|
4
4
|
|
|
5
5
|
import { useEffect, useRef, useState } from 'react'
|
|
6
6
|
import { SidebarClose, SidebarOpen } from 'lucide-react'
|
|
7
|
+
import { AnimatePresence, motion, useReducedMotion } from 'motion/react'
|
|
7
8
|
|
|
8
9
|
import { Button } from './button'
|
|
9
10
|
import { Separator } from './separator'
|
|
11
|
+
import {
|
|
12
|
+
Sheet,
|
|
13
|
+
SheetClose,
|
|
14
|
+
SheetContent,
|
|
15
|
+
SheetTitle,
|
|
16
|
+
SheetTrigger,
|
|
17
|
+
} from './sheet'
|
|
10
18
|
|
|
11
19
|
export type SidebarCategory = {
|
|
12
20
|
title: string
|
|
@@ -21,13 +29,34 @@ export function DocsSidebar({
|
|
|
21
29
|
pathname: string
|
|
22
30
|
}) {
|
|
23
31
|
const messages = useMessages()
|
|
24
|
-
const [
|
|
25
|
-
const
|
|
26
|
-
const
|
|
32
|
+
const [isOpen, setOpen] = useState(false)
|
|
33
|
+
const open = useRef(false)
|
|
34
|
+
const actions = useRef<{ close: () => void; unmount: () => void } | null>(
|
|
35
|
+
null
|
|
36
|
+
)
|
|
37
|
+
const previousPath = useRef(pathname)
|
|
38
|
+
const reducedMotion = useReducedMotion()
|
|
39
|
+
const pageOrder = new Map(
|
|
40
|
+
categories
|
|
41
|
+
.flatMap((category) => category.links)
|
|
42
|
+
.map((page, index) => [page.href, index])
|
|
43
|
+
)
|
|
44
|
+
const currentIndex = pageOrder.get(pathname) ?? -1
|
|
45
|
+
|
|
46
|
+
// Route changes (including browser history) must use the same closing phase
|
|
47
|
+
// as Escape and the close button, keeping the content mounted until exit ends.
|
|
27
48
|
useEffect(() => {
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
}, [
|
|
49
|
+
if (previousPath.current !== pathname) actions.current?.close()
|
|
50
|
+
previousPath.current = pathname
|
|
51
|
+
}, [pathname])
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
const desktop = window.matchMedia('(min-width: 768px)')
|
|
54
|
+
const closeOnDesktop = () => {
|
|
55
|
+
if (desktop.matches) actions.current?.close()
|
|
56
|
+
}
|
|
57
|
+
desktop.addEventListener('change', closeOnDesktop)
|
|
58
|
+
return () => desktop.removeEventListener('change', closeOnDesktop)
|
|
59
|
+
}, [])
|
|
31
60
|
const content = (
|
|
32
61
|
<nav aria-label={messages.documentationPages}>
|
|
33
62
|
{categories.map((category, index) => (
|
|
@@ -46,9 +75,18 @@ export function DocsSidebar({
|
|
|
46
75
|
active={pathname === link.href}
|
|
47
76
|
aria-label={link.label}
|
|
48
77
|
href={link.href}
|
|
49
|
-
|
|
78
|
+
transitionTypes={
|
|
79
|
+
currentIndex < 0 || link.href === pathname
|
|
80
|
+
? undefined
|
|
81
|
+
: [
|
|
82
|
+
pageOrder.get(link.href)! < currentIndex
|
|
83
|
+
? 'rdx-doc-back'
|
|
84
|
+
: 'rdx-doc-forward',
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
onClick={() => actions.current?.close()}
|
|
50
88
|
aria-current={pathname === link.href ? 'page' : undefined}
|
|
51
|
-
className=
|
|
89
|
+
className={`justify-start text-sm ${pathname === link.href ? 'text-accent-foreground' : 'text-primary'}`}
|
|
52
90
|
>
|
|
53
91
|
{link.label}
|
|
54
92
|
</Button>
|
|
@@ -59,15 +97,22 @@ export function DocsSidebar({
|
|
|
59
97
|
</nav>
|
|
60
98
|
)
|
|
61
99
|
return (
|
|
62
|
-
|
|
100
|
+
<Sheet
|
|
101
|
+
open={isOpen}
|
|
102
|
+
actionsRef={actions}
|
|
103
|
+
onOpenChange={(nextOpen, details) => {
|
|
104
|
+
if (!nextOpen) details.preventUnmountOnClose()
|
|
105
|
+
open.current = nextOpen
|
|
106
|
+
setOpen(nextOpen)
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
63
109
|
<div className="md:hidden absolute top-2 left-4 z-30">
|
|
64
|
-
<
|
|
65
|
-
variant="ghost"
|
|
66
|
-
onClick={() => setOpenPath(pathname)}
|
|
110
|
+
<SheetTrigger
|
|
111
|
+
render={<Button variant="ghost" />}
|
|
67
112
|
aria-label={messages.openSidebar}
|
|
68
113
|
>
|
|
69
114
|
<SidebarOpen className="size-4" />
|
|
70
|
-
</
|
|
115
|
+
</SheetTrigger>
|
|
71
116
|
</div>
|
|
72
117
|
<aside
|
|
73
118
|
data-rdx-sidebar
|
|
@@ -75,23 +120,51 @@ export function DocsSidebar({
|
|
|
75
120
|
>
|
|
76
121
|
{content}
|
|
77
122
|
</aside>
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
123
|
+
<AnimatePresence>
|
|
124
|
+
{isOpen && (
|
|
125
|
+
<SheetContent
|
|
126
|
+
key="mobile-sidebar"
|
|
127
|
+
side="left"
|
|
128
|
+
showCloseButton={false}
|
|
129
|
+
portalProps={{ keepMounted: true }}
|
|
130
|
+
data-rdx-mobile-sidebar
|
|
131
|
+
className="md:hidden w-full! max-w-none! h-dvh border-0 bg-background text-foreground gap-0 p-4 overflow-y-auto translate-none! opacity-100! transition-none!"
|
|
132
|
+
render={
|
|
133
|
+
<motion.div
|
|
134
|
+
initial="closed"
|
|
135
|
+
animate="open"
|
|
136
|
+
exit="closed"
|
|
137
|
+
variants={{
|
|
138
|
+
closed: { x: reducedMotion ? 0 : '-100%' },
|
|
139
|
+
open: { x: 0 },
|
|
140
|
+
}}
|
|
141
|
+
transition={
|
|
142
|
+
reducedMotion
|
|
143
|
+
? { duration: 0 }
|
|
144
|
+
: { type: 'spring', stiffness: 300, damping: 30 }
|
|
145
|
+
}
|
|
146
|
+
onAnimationComplete={(phase) => {
|
|
147
|
+
if (phase === 'closed' && !open.current)
|
|
148
|
+
actions.current?.unmount()
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
151
|
+
}
|
|
89
152
|
>
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
153
|
+
<SheetTitle className="sr-only">
|
|
154
|
+
{messages.documentationNavigation}
|
|
155
|
+
</SheetTitle>
|
|
156
|
+
<div className="flex justify-end mb-4">
|
|
157
|
+
<SheetClose
|
|
158
|
+
render={<Button variant="ghost" />}
|
|
159
|
+
aria-label={messages.closeSidebar}
|
|
160
|
+
>
|
|
161
|
+
<SidebarClose className="size-4" />
|
|
162
|
+
</SheetClose>
|
|
163
|
+
</div>
|
|
164
|
+
{content}
|
|
165
|
+
</SheetContent>
|
|
166
|
+
)}
|
|
167
|
+
</AnimatePresence>
|
|
168
|
+
</Sheet>
|
|
96
169
|
)
|
|
97
170
|
}
|
|
@@ -54,7 +54,7 @@ function HeadingLinks({
|
|
|
54
54
|
className={cn(
|
|
55
55
|
'block rounded-md py-1.5 pr-2 text-xs leading-5 text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
56
56
|
heading.id === activeId &&
|
|
57
|
-
'bg-accent/60 font-medium text-
|
|
57
|
+
'bg-accent/60 font-medium text-rdx-selected'
|
|
58
58
|
)}
|
|
59
59
|
>
|
|
60
60
|
{heading.title}
|
|
@@ -98,12 +98,12 @@ function PageToc({ contentId }: { contentId: string }) {
|
|
|
98
98
|
}
|
|
99
99
|
const scrollTarget = scrollRoot ?? window
|
|
100
100
|
// Keep native fragment links/history; smooth the actual document scroller.
|
|
101
|
-
//
|
|
101
|
+
// Match the TOC's xl visibility and leave reduced-motion navigation instant.
|
|
102
102
|
const scrollElement = scrollRoot ?? document.documentElement
|
|
103
103
|
const alreadySmooth = scrollElement.classList.contains(
|
|
104
|
-
'motion-safe:scroll-smooth'
|
|
104
|
+
'xl:motion-safe:scroll-smooth'
|
|
105
105
|
)
|
|
106
|
-
scrollElement.classList.add('motion-safe:scroll-smooth')
|
|
106
|
+
scrollElement.classList.add('xl:motion-safe:scroll-smooth')
|
|
107
107
|
function updateActive() {
|
|
108
108
|
frame = 0
|
|
109
109
|
const top = (scrollRoot?.getBoundingClientRect().top ?? 0) + 96
|
|
@@ -172,7 +172,7 @@ function PageToc({ contentId }: { contentId: string }) {
|
|
|
172
172
|
cancelAnimationFrame(frame)
|
|
173
173
|
cancelAnimationFrame(initialFrame)
|
|
174
174
|
if (!alreadySmooth)
|
|
175
|
-
scrollElement.classList.remove('motion-safe:scroll-smooth')
|
|
175
|
+
scrollElement.classList.remove('xl:motion-safe:scroll-smooth')
|
|
176
176
|
}
|
|
177
177
|
}, [contentId])
|
|
178
178
|
|
|
@@ -197,7 +197,7 @@ function PageToc({ contentId }: { contentId: string }) {
|
|
|
197
197
|
className={cn(
|
|
198
198
|
'h-0.5 shrink-0 rounded-full bg-current transition-colors',
|
|
199
199
|
heading.depth === baseDepth ? 'w-5' : 'w-3',
|
|
200
|
-
heading.id === activeId && 'text-
|
|
200
|
+
heading.id === activeId && 'text-rdx-selected'
|
|
201
201
|
)}
|
|
202
202
|
/>
|
|
203
203
|
))}
|