@lexibase/rdx-ui 1.1.0 → 3.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/README.md +4 -5
- package/THIRD_PARTY_NOTICES.md +34 -0
- package/package.json +10 -6
- package/src/components/button.tsx +22 -8
- package/src/components/code-block.tsx +274 -43
- package/src/components/copy-code-button.tsx +1 -76
- package/src/components/dialog.tsx +124 -34
- package/src/components/docs-search-dialog.tsx +29 -14
- package/src/components/docs-search.tsx +9 -5
- package/src/components/docs-sidebar.tsx +9 -7
- package/src/components/docs-toc.tsx +6 -3
- package/src/components/footer-credit.tsx +8 -4
- package/src/components/home.tsx +3 -3
- package/src/components/kbd.tsx +29 -0
- package/src/components/loading-button.tsx +5 -1
- package/src/components/mdx-code-block-view.tsx +77 -0
- package/src/components/mdx-code-block.tsx +108 -0
- package/src/components/mdx-components.tsx +105 -47
- package/src/components/mdx-image.tsx +73 -0
- package/src/components/mode-toggle.tsx +5 -2
- package/src/components/navbar.tsx +13 -9
- package/src/components/not-found.tsx +36 -0
- package/src/components/table.tsx +122 -0
- package/src/components/tabs.tsx +17 -9
- package/src/components/version-dropdown.tsx +9 -2
- package/src/components/version-notice.tsx +7 -5
- package/src/hooks/use-highlighted-code.ts +75 -0
- package/src/lib/highlighter.tsx +124 -0
- package/src/lib/i18n.ts +156 -0
- package/src/providers/locale-provider.tsx +29 -0
- package/src/styles/globals.css +1 -0
- package/src/styles/shadcn.css +650 -0
- package/src/components/button-link.tsx +0 -27
|
@@ -1,76 +1,166 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
// shadcn/ui Base Nova Dialog, using the shared RDX theme and buttons.
|
|
4
3
|
import * as React from 'react'
|
|
5
4
|
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog'
|
|
6
|
-
import {
|
|
7
|
-
|
|
5
|
+
import { cn, mergeStateClassName } from '../lib/utils'
|
|
6
|
+
|
|
8
7
|
import { Button } from './button'
|
|
8
|
+
import { XIcon } from 'lucide-react'
|
|
9
|
+
import { useMessages } from '../providers/locale-provider'
|
|
9
10
|
|
|
10
|
-
function Dialog(props:
|
|
11
|
-
return <DialogPrimitive.Root {...props} />
|
|
11
|
+
function Dialog({ ...props }: DialogPrimitive.Root.Props) {
|
|
12
|
+
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
) {
|
|
14
|
+
|
|
15
|
+
function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {
|
|
16
16
|
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
|
17
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
|
+
|
|
18
43
|
function DialogContent({
|
|
19
44
|
className,
|
|
20
45
|
children,
|
|
46
|
+
showCloseButton = true,
|
|
47
|
+
closeLabel,
|
|
48
|
+
overlayClassName,
|
|
21
49
|
...props
|
|
22
|
-
}:
|
|
50
|
+
}: DialogPrimitive.Popup.Props & {
|
|
51
|
+
showCloseButton?: boolean
|
|
52
|
+
closeLabel?: string
|
|
53
|
+
overlayClassName?: DialogPrimitive.Backdrop.Props['className']
|
|
54
|
+
}) {
|
|
55
|
+
const messages = useMessages()
|
|
23
56
|
return (
|
|
24
|
-
<
|
|
25
|
-
<
|
|
26
|
-
data-slot="dialog-overlay"
|
|
27
|
-
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"
|
|
28
|
-
/>
|
|
57
|
+
<DialogPortal>
|
|
58
|
+
<DialogOverlay className={overlayClassName} />
|
|
29
59
|
<DialogPrimitive.Popup
|
|
30
60
|
data-slot="dialog-content"
|
|
31
61
|
className={mergeStateClassName(
|
|
32
|
-
'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',
|
|
33
63
|
className
|
|
34
64
|
)}
|
|
35
65
|
{...props}
|
|
36
66
|
>
|
|
37
67
|
{children}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
+
)}
|
|
50
83
|
</DialogPrimitive.Popup>
|
|
51
|
-
</
|
|
84
|
+
</DialogPortal>
|
|
52
85
|
)
|
|
53
86
|
}
|
|
54
|
-
|
|
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
|
+
/>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function DialogFooter({
|
|
55
99
|
className,
|
|
100
|
+
showCloseButton = false,
|
|
101
|
+
children,
|
|
56
102
|
...props
|
|
57
|
-
}: 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) {
|
|
58
127
|
return (
|
|
59
128
|
<DialogPrimitive.Title
|
|
60
|
-
|
|
129
|
+
data-slot="dialog-title"
|
|
130
|
+
className={mergeStateClassName(
|
|
131
|
+
'cn-font-heading text-base leading-none font-medium',
|
|
132
|
+
className
|
|
133
|
+
)}
|
|
61
134
|
{...props}
|
|
62
135
|
/>
|
|
63
136
|
)
|
|
64
137
|
}
|
|
138
|
+
|
|
65
139
|
function DialogDescription({
|
|
66
140
|
className,
|
|
67
141
|
...props
|
|
68
|
-
}:
|
|
142
|
+
}: DialogPrimitive.Description.Props) {
|
|
69
143
|
return (
|
|
70
144
|
<DialogPrimitive.Description
|
|
71
|
-
|
|
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
|
+
)}
|
|
72
150
|
{...props}
|
|
73
151
|
/>
|
|
74
152
|
)
|
|
75
153
|
}
|
|
76
|
-
|
|
154
|
+
|
|
155
|
+
export {
|
|
156
|
+
Dialog,
|
|
157
|
+
DialogClose,
|
|
158
|
+
DialogContent,
|
|
159
|
+
DialogDescription,
|
|
160
|
+
DialogFooter,
|
|
161
|
+
DialogHeader,
|
|
162
|
+
DialogOverlay,
|
|
163
|
+
DialogPortal,
|
|
164
|
+
DialogTitle,
|
|
165
|
+
DialogTrigger,
|
|
166
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { useMessages } from '../providers/locale-provider'
|
|
4
|
+
|
|
3
5
|
import {
|
|
4
6
|
useDeferredValue,
|
|
5
7
|
useEffect,
|
|
@@ -23,6 +25,8 @@ import {
|
|
|
23
25
|
CommandItem,
|
|
24
26
|
CommandList,
|
|
25
27
|
} from './command'
|
|
28
|
+
import { formatMessage } from '../lib/i18n'
|
|
29
|
+
|
|
26
30
|
import { Button } from './button'
|
|
27
31
|
|
|
28
32
|
const cache = new Map<string, PreparedSearch>()
|
|
@@ -40,6 +44,7 @@ export function DocsSearchDialog({
|
|
|
40
44
|
onOpenChange: (value: boolean) => void
|
|
41
45
|
triggerRef: RefObject<HTMLButtonElement | null>
|
|
42
46
|
}) {
|
|
47
|
+
const messages = useMessages()
|
|
43
48
|
const router = useRouter()
|
|
44
49
|
const pathname = usePathname()
|
|
45
50
|
const [query, setQuery] = useState('')
|
|
@@ -94,16 +99,22 @@ export function DocsSearchDialog({
|
|
|
94
99
|
}
|
|
95
100
|
return (
|
|
96
101
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
97
|
-
<DialogContent
|
|
98
|
-
|
|
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
|
+
>
|
|
108
|
+
<DialogTitle className="sr-only">
|
|
109
|
+
{messages.searchDocumentation}
|
|
110
|
+
</DialogTitle>
|
|
99
111
|
<DialogDescription className="sr-only">
|
|
100
|
-
|
|
101
|
-
result and Enter to open it.
|
|
112
|
+
{formatMessage(messages.searchDescription, { version })}
|
|
102
113
|
</DialogDescription>
|
|
103
|
-
<Command shouldFilter={false} label=
|
|
114
|
+
<Command shouldFilter={false} label={messages.searchDocumentation}>
|
|
104
115
|
<CommandInput
|
|
105
|
-
aria-label=
|
|
106
|
-
placeholder=
|
|
116
|
+
aria-label={messages.searchDocumentation}
|
|
117
|
+
placeholder={messages.searchPlaceholder}
|
|
107
118
|
value={query}
|
|
108
119
|
onValueChange={setQuery}
|
|
109
120
|
maxLength={120}
|
|
@@ -111,21 +122,24 @@ export function DocsSearchDialog({
|
|
|
111
122
|
<div className="px-2 pt-2 pb-1 text-[11px] text-muted-foreground">
|
|
112
123
|
{version}
|
|
113
124
|
</div>
|
|
114
|
-
<CommandList
|
|
125
|
+
<CommandList
|
|
126
|
+
aria-busy={loading}
|
|
127
|
+
aria-label={messages.searchDocumentation}
|
|
128
|
+
>
|
|
115
129
|
{loading ? (
|
|
116
130
|
<div
|
|
117
131
|
role="status"
|
|
118
132
|
className="flex justify-center gap-2 p-6 text-sm text-muted-foreground"
|
|
119
133
|
>
|
|
120
134
|
<LoaderCircle className="size-4 animate-spin motion-reduce:animate-none" />
|
|
121
|
-
|
|
135
|
+
{messages.loadingSearch}
|
|
122
136
|
</div>
|
|
123
137
|
) : state.error ? (
|
|
124
138
|
<div
|
|
125
139
|
role="status"
|
|
126
140
|
className="p-4 text-center text-sm text-muted-foreground"
|
|
127
141
|
>
|
|
128
|
-
|
|
142
|
+
{messages.searchUnavailable}{' '}
|
|
129
143
|
<Button
|
|
130
144
|
variant="link"
|
|
131
145
|
size="sm"
|
|
@@ -134,16 +148,16 @@ export function DocsSearchDialog({
|
|
|
134
148
|
setRetry((value) => value + 1)
|
|
135
149
|
}}
|
|
136
150
|
>
|
|
137
|
-
|
|
151
|
+
{messages.tryAgain}
|
|
138
152
|
</Button>
|
|
139
153
|
</div>
|
|
140
154
|
) : !query.trim() ? (
|
|
141
155
|
<div className="px-4 py-6 text-center text-sm text-muted-foreground">
|
|
142
|
-
|
|
156
|
+
{messages.typeToSearch}
|
|
143
157
|
</div>
|
|
144
158
|
) : (
|
|
145
159
|
<>
|
|
146
|
-
<CommandEmpty>
|
|
160
|
+
<CommandEmpty>{messages.noResults}</CommandEmpty>
|
|
147
161
|
{results.map((entry) => (
|
|
148
162
|
<CommandItem
|
|
149
163
|
key={entry.href}
|
|
@@ -175,7 +189,8 @@ export function DocsSearchDialog({
|
|
|
175
189
|
)}
|
|
176
190
|
</CommandList>
|
|
177
191
|
<div className="border-t px-2 py-2 text-[10px] text-muted-foreground">
|
|
178
|
-
↑ ↓
|
|
192
|
+
↑ ↓ {messages.navigate}{' '}
|
|
193
|
+
<span className="mx-2">↵ {messages.open}</span> Esc {messages.close}
|
|
179
194
|
</div>
|
|
180
195
|
</Command>
|
|
181
196
|
</DialogContent>
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { useMessages } from '../providers/locale-provider'
|
|
4
|
+
|
|
3
5
|
import { useEffect, useRef, useState } from 'react'
|
|
4
6
|
import dynamic from 'next/dynamic'
|
|
5
7
|
import { Search } from 'lucide-react'
|
|
6
8
|
import { Button } from './button'
|
|
9
|
+
import { Kbd } from './kbd'
|
|
7
10
|
|
|
8
11
|
const SearchDialog = dynamic(
|
|
9
12
|
() =>
|
|
@@ -19,6 +22,7 @@ export function DocsSearch({
|
|
|
19
22
|
indexUrl: string
|
|
20
23
|
version: string
|
|
21
24
|
}) {
|
|
25
|
+
const messages = useMessages()
|
|
22
26
|
const [open, setOpen] = useState(false)
|
|
23
27
|
const [activated, setActivated] = useState(false)
|
|
24
28
|
const trigger = useRef<HTMLButtonElement | null>(null)
|
|
@@ -50,20 +54,20 @@ export function DocsSearch({
|
|
|
50
54
|
data-rdx-search-trigger
|
|
51
55
|
variant="outline"
|
|
52
56
|
size="sm"
|
|
53
|
-
aria-label=
|
|
57
|
+
aria-label={messages.searchDocumentation}
|
|
54
58
|
aria-haspopup="dialog"
|
|
55
59
|
aria-expanded={open}
|
|
56
60
|
onClick={show}
|
|
57
61
|
className="h-8 gap-2 px-2 text-muted-foreground sm:min-w-40 sm:justify-start"
|
|
58
62
|
>
|
|
59
63
|
<Search className="size-3.5" />
|
|
60
|
-
<span className="hidden sm:inline">
|
|
61
|
-
<
|
|
62
|
-
className="ml-auto hidden rounded border px-1 text-[10px] text-muted-foreground sm:inline"
|
|
64
|
+
<span className="hidden sm:inline">{messages.search}</span>
|
|
65
|
+
<Kbd
|
|
66
|
+
className="ml-auto hidden h-auto rounded border bg-transparent px-1 text-[10px] text-muted-foreground sm:inline"
|
|
63
67
|
aria-hidden="true"
|
|
64
68
|
>
|
|
65
69
|
⌘ / Ctrl K
|
|
66
|
-
</
|
|
70
|
+
</Kbd>
|
|
67
71
|
</Button>
|
|
68
72
|
{activated && (
|
|
69
73
|
<SearchDialog
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { useMessages } from '../providers/locale-provider'
|
|
4
|
+
|
|
3
5
|
import { useEffect, useRef, useState } from 'react'
|
|
4
6
|
import { SidebarClose, SidebarOpen } from 'lucide-react'
|
|
5
7
|
|
|
6
8
|
import { Button } from './button'
|
|
7
|
-
import { ButtonLink } from './button-link'
|
|
8
9
|
import { Separator } from './separator'
|
|
9
10
|
|
|
10
11
|
export type SidebarCategory = {
|
|
@@ -19,6 +20,7 @@ export function DocsSidebar({
|
|
|
19
20
|
categories: SidebarCategory[]
|
|
20
21
|
pathname: string
|
|
21
22
|
}) {
|
|
23
|
+
const messages = useMessages()
|
|
22
24
|
const [openPath, setOpenPath] = useState<string | null>(null)
|
|
23
25
|
const isOpen = openPath === pathname
|
|
24
26
|
const dialog = useRef<HTMLDialogElement>(null)
|
|
@@ -27,7 +29,7 @@ export function DocsSidebar({
|
|
|
27
29
|
if (!isOpen && dialog.current?.open) dialog.current?.close()
|
|
28
30
|
}, [isOpen])
|
|
29
31
|
const content = (
|
|
30
|
-
<nav aria-label=
|
|
32
|
+
<nav aria-label={messages.documentationPages}>
|
|
31
33
|
{categories.map((category, index) => (
|
|
32
34
|
<div
|
|
33
35
|
key={`${index}:${category.title}`}
|
|
@@ -37,7 +39,7 @@ export function DocsSidebar({
|
|
|
37
39
|
{category.title}
|
|
38
40
|
</p>
|
|
39
41
|
{category.links.map((link) => (
|
|
40
|
-
<
|
|
42
|
+
<Button
|
|
41
43
|
key={link.href}
|
|
42
44
|
variant="ghost"
|
|
43
45
|
size="sm"
|
|
@@ -49,7 +51,7 @@ export function DocsSidebar({
|
|
|
49
51
|
className="justify-start text-sm text-primary"
|
|
50
52
|
>
|
|
51
53
|
{link.label}
|
|
52
|
-
</
|
|
54
|
+
</Button>
|
|
53
55
|
))}
|
|
54
56
|
{index < categories.length - 1 && <Separator className="my-4" />}
|
|
55
57
|
</div>
|
|
@@ -62,7 +64,7 @@ export function DocsSidebar({
|
|
|
62
64
|
<Button
|
|
63
65
|
variant="ghost"
|
|
64
66
|
onClick={() => setOpenPath(pathname)}
|
|
65
|
-
aria-label=
|
|
67
|
+
aria-label={messages.openSidebar}
|
|
66
68
|
>
|
|
67
69
|
<SidebarOpen className="size-4" />
|
|
68
70
|
</Button>
|
|
@@ -75,7 +77,7 @@ export function DocsSidebar({
|
|
|
75
77
|
</aside>
|
|
76
78
|
<dialog
|
|
77
79
|
ref={dialog}
|
|
78
|
-
aria-label=
|
|
80
|
+
aria-label={messages.documentationNavigation}
|
|
79
81
|
onClose={() => setOpenPath(null)}
|
|
80
82
|
className="fixed inset-0 m-0 h-dvh max-h-none w-full max-w-none bg-background text-foreground border-0 p-4 backdrop:bg-black/40"
|
|
81
83
|
>
|
|
@@ -83,7 +85,7 @@ export function DocsSidebar({
|
|
|
83
85
|
<Button
|
|
84
86
|
variant="ghost"
|
|
85
87
|
onClick={() => setOpenPath(null)}
|
|
86
|
-
aria-label=
|
|
88
|
+
aria-label={messages.closeSidebar}
|
|
87
89
|
>
|
|
88
90
|
<SidebarClose className="size-4" />
|
|
89
91
|
</Button>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { useMessages } from '../providers/locale-provider'
|
|
4
|
+
|
|
3
5
|
import { useEffect, useRef, useState } from 'react'
|
|
4
6
|
import { usePathname } from 'next/navigation'
|
|
5
7
|
|
|
@@ -77,6 +79,7 @@ export function DocsToc({ contentId = 'rdx-main' }: { contentId?: string }) {
|
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
function PageToc({ contentId }: { contentId: string }) {
|
|
82
|
+
const messages = useMessages()
|
|
80
83
|
const [headings, setHeadings] = useState<Heading[]>([])
|
|
81
84
|
const [activeId, setActiveId] = useState('')
|
|
82
85
|
const actions = useRef<PopoverActions | null>(null)
|
|
@@ -184,7 +187,7 @@ function PageToc({ contentId }: { contentId: string }) {
|
|
|
184
187
|
<PopoverTrigger
|
|
185
188
|
openOnHover
|
|
186
189
|
delay={120}
|
|
187
|
-
aria-label=
|
|
190
|
+
aria-label={messages.onThisPage}
|
|
188
191
|
className="flex max-h-[40vh] w-12 flex-col items-end gap-2 overflow-hidden rounded-md p-3 text-muted-foreground/40 outline-none transition-colors hover:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring"
|
|
189
192
|
>
|
|
190
193
|
{headings.map((heading) => (
|
|
@@ -215,9 +218,9 @@ function PageToc({ contentId }: { contentId: string }) {
|
|
|
215
218
|
className="hidden max-h-[min(60vh,var(--available-height))] w-64 overflow-y-auto xl:flex"
|
|
216
219
|
>
|
|
217
220
|
<PopoverTitle className="px-2 pt-1 text-xs text-muted-foreground">
|
|
218
|
-
|
|
221
|
+
{messages.onThisPage}
|
|
219
222
|
</PopoverTitle>
|
|
220
|
-
<nav aria-label=
|
|
223
|
+
<nav aria-label={messages.onThisPage}>
|
|
221
224
|
<HeadingLinks
|
|
222
225
|
nodes={headingTree(headings)}
|
|
223
226
|
baseDepth={baseDepth}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
+
import { getMessages } from '../lib/i18n'
|
|
1
2
|
import type { ReactNode } from 'react'
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
+
import { Button } from './button'
|
|
4
5
|
|
|
5
6
|
/** Shared site identity and framework credit for documentation footers. */
|
|
6
7
|
export function FooterCredit({
|
|
8
|
+
lang,
|
|
7
9
|
name,
|
|
8
10
|
since,
|
|
9
11
|
children,
|
|
10
12
|
}: {
|
|
13
|
+
lang?: string
|
|
11
14
|
name: string
|
|
12
15
|
since?: number
|
|
13
16
|
children?: ReactNode
|
|
14
17
|
}) {
|
|
18
|
+
const messages = getMessages(lang)
|
|
15
19
|
const currentYear = new Date().getFullYear()
|
|
16
20
|
const years =
|
|
17
21
|
since !== undefined && since !== currentYear
|
|
@@ -24,8 +28,8 @@ export function FooterCredit({
|
|
|
24
28
|
© {years} <strong>{name}</strong>
|
|
25
29
|
{children}
|
|
26
30
|
<span className="inline-block">
|
|
27
|
-
{
|
|
28
|
-
<
|
|
31
|
+
{` · ${messages.poweredBy} `}
|
|
32
|
+
<Button
|
|
29
33
|
variant="link"
|
|
30
34
|
size="link"
|
|
31
35
|
href="https://github.com/lexibase/rdx"
|
|
@@ -33,7 +37,7 @@ export function FooterCredit({
|
|
|
33
37
|
rel="noopener noreferrer"
|
|
34
38
|
>
|
|
35
39
|
RDX
|
|
36
|
-
</
|
|
40
|
+
</Button>
|
|
37
41
|
</span>
|
|
38
42
|
</p>
|
|
39
43
|
</section>
|
package/src/components/home.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { Button } from './button'
|
|
4
4
|
import { Logo } from './logo'
|
|
5
5
|
|
|
6
6
|
/** The default landing page from apps/web, not a separate marketing theme. */
|
|
@@ -32,13 +32,13 @@ export function Home({
|
|
|
32
32
|
{buttons.length > 0 && (
|
|
33
33
|
<div className="flex flex-wrap justify-center gap-3 mt-6 px-6">
|
|
34
34
|
{buttons.map((button, index) => (
|
|
35
|
-
<
|
|
35
|
+
<Button
|
|
36
36
|
key={`${index}:${button.href}`}
|
|
37
37
|
variant={index === 0 ? 'default' : 'outline'}
|
|
38
38
|
href={button.href}
|
|
39
39
|
>
|
|
40
40
|
{button.label}
|
|
41
|
-
</
|
|
41
|
+
</Button>
|
|
42
42
|
))}
|
|
43
43
|
</div>
|
|
44
44
|
)}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react'
|
|
2
|
+
|
|
3
|
+
import { cn } from '../lib/utils'
|
|
4
|
+
|
|
5
|
+
// shadcn/ui base-nova Kbd; see THIRD_PARTY_NOTICES.md for the upstream MIT license.
|
|
6
|
+
function Kbd({ className, ...props }: ComponentProps<'kbd'>) {
|
|
7
|
+
return (
|
|
8
|
+
<kbd
|
|
9
|
+
data-slot="kbd"
|
|
10
|
+
className={cn(
|
|
11
|
+
"pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm bg-muted px-1 font-sans text-xs font-medium text-muted-foreground select-none in-data-[slot=tooltip-content]:bg-background/20 in-data-[slot=tooltip-content]:text-background dark:in-data-[slot=tooltip-content]:bg-background/10 [&_svg:not([class*='size-'])]:size-3",
|
|
12
|
+
className
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function KbdGroup({ className, ...props }: ComponentProps<'div'>) {
|
|
20
|
+
return (
|
|
21
|
+
<kbd
|
|
22
|
+
data-slot="kbd-group"
|
|
23
|
+
className={cn('inline-flex items-center gap-1', className)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { Kbd, KbdGroup }
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useMessages } from '../providers/locale-provider'
|
|
1
4
|
import { Spline } from 'lucide-react'
|
|
2
5
|
import { Button } from '@lexibase/rdx-ui/components/button'
|
|
3
6
|
|
|
4
7
|
export function LoadingButton() {
|
|
8
|
+
const messages = useMessages()
|
|
5
9
|
return (
|
|
6
10
|
<Button
|
|
7
11
|
variant="outline"
|
|
@@ -9,7 +13,7 @@ export function LoadingButton() {
|
|
|
9
13
|
className="w-[134px] px-9 overflow-hidden"
|
|
10
14
|
disabled
|
|
11
15
|
aria-busy="true"
|
|
12
|
-
aria-label=
|
|
16
|
+
aria-label={messages.loadingVersions}
|
|
13
17
|
>
|
|
14
18
|
<div className="animate-spin flex items-center justify-center">
|
|
15
19
|
<Spline className="size-4" />
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, type ComponentProps, type ReactNode } from 'react'
|
|
4
|
+
import { CodeXml, FolderTree, GitCompareArrows, WrapText } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
import { useMessages } from '../providers/locale-provider'
|
|
7
|
+
import { Button } from './button'
|
|
8
|
+
import {
|
|
9
|
+
CodeBlock,
|
|
10
|
+
CodeBlockActions,
|
|
11
|
+
CodeBlockContent,
|
|
12
|
+
CodeBlockCopyButton,
|
|
13
|
+
CodeBlockHeader,
|
|
14
|
+
CodeBlockTitle,
|
|
15
|
+
} from './code-block'
|
|
16
|
+
|
|
17
|
+
/** The wrap action follows shadcn's stateful Code Block composition. */
|
|
18
|
+
export function MdxCodeBlockView({
|
|
19
|
+
label,
|
|
20
|
+
mode,
|
|
21
|
+
value,
|
|
22
|
+
preProps,
|
|
23
|
+
children,
|
|
24
|
+
}: {
|
|
25
|
+
label: string
|
|
26
|
+
mode?: 'folder' | 'diff'
|
|
27
|
+
value: string
|
|
28
|
+
preProps: ComponentProps<'pre'>
|
|
29
|
+
children: ReactNode
|
|
30
|
+
}) {
|
|
31
|
+
const [wrap, setWrap] = useState(false)
|
|
32
|
+
const messages = useMessages()
|
|
33
|
+
const Icon =
|
|
34
|
+
mode === 'folder'
|
|
35
|
+
? FolderTree
|
|
36
|
+
: mode === 'diff'
|
|
37
|
+
? GitCompareArrows
|
|
38
|
+
: CodeXml
|
|
39
|
+
return (
|
|
40
|
+
<CodeBlock
|
|
41
|
+
size="sm"
|
|
42
|
+
data-rdx-code-block
|
|
43
|
+
data-rdx-code-mode={mode}
|
|
44
|
+
data-rdx-code-value={value}
|
|
45
|
+
className="my-4 max-w-full"
|
|
46
|
+
>
|
|
47
|
+
<CodeBlockHeader>
|
|
48
|
+
<CodeBlockTitle>
|
|
49
|
+
<Icon aria-hidden="true" /> {label}
|
|
50
|
+
</CodeBlockTitle>
|
|
51
|
+
<CodeBlockActions>
|
|
52
|
+
<Button
|
|
53
|
+
variant="ghost"
|
|
54
|
+
size="icon-xs"
|
|
55
|
+
className="md:hidden"
|
|
56
|
+
aria-label={messages.wrapCode}
|
|
57
|
+
title={messages.wrapCode}
|
|
58
|
+
aria-pressed={wrap}
|
|
59
|
+
onClick={() => setWrap(!wrap)}
|
|
60
|
+
>
|
|
61
|
+
<WrapText aria-hidden="true" />
|
|
62
|
+
</Button>
|
|
63
|
+
<CodeBlockCopyButton value={value} />
|
|
64
|
+
</CodeBlockActions>
|
|
65
|
+
</CodeBlockHeader>
|
|
66
|
+
<CodeBlockContent
|
|
67
|
+
{...preProps}
|
|
68
|
+
wrap={wrap}
|
|
69
|
+
className={[preProps.className, 'data-wrap:wrap-anywhere']
|
|
70
|
+
.filter(Boolean)
|
|
71
|
+
.join(' ')}
|
|
72
|
+
>
|
|
73
|
+
{children}
|
|
74
|
+
</CodeBlockContent>
|
|
75
|
+
</CodeBlock>
|
|
76
|
+
)
|
|
77
|
+
}
|