@lexibase/rdx-ui 1.1.0 → 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/README.md +4 -5
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/package.json +3 -2
- package/src/components/button.tsx +20 -8
- package/src/components/code-block.tsx +4 -1
- package/src/components/copy-code-button.tsx +9 -6
- package/src/components/dialog.tsx +4 -1
- package/src/components/docs-search-dialog.tsx +23 -13
- 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-components.tsx +59 -35
- 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 +10 -7
- package/src/components/version-dropdown.tsx +9 -2
- package/src/components/version-notice.tsx +7 -5
- package/src/lib/i18n.ts +144 -0
- package/src/providers/locale-provider.tsx +29 -0
- package/src/components/button-link.tsx +0 -27
package/README.md
CHANGED
|
@@ -45,11 +45,10 @@ manual-copy guidance instead of a false success message.
|
|
|
45
45
|
|
|
46
46
|
The shared components use `@base-ui/react` and the RDX theme.
|
|
47
47
|
`components.json` selects shadcn/ui's `base-nova` style.
|
|
48
|
-
Use `render` for interactive composition and `
|
|
48
|
+
Use `render` for interactive composition and `Button` with `href` for navigation:
|
|
49
49
|
|
|
50
50
|
```tsx
|
|
51
51
|
import { Button } from '@lexibase/rdx-ui/components/button'
|
|
52
|
-
import { ButtonLink } from '@lexibase/rdx-ui/components/button-link'
|
|
53
52
|
import {
|
|
54
53
|
DropdownMenu, DropdownMenuTrigger, DropdownMenuContent,
|
|
55
54
|
DropdownMenuGroup, DropdownMenuLabel, DropdownMenuItem,
|
|
@@ -57,7 +56,7 @@ import {
|
|
|
57
56
|
} from '@lexibase/rdx-ui/components/dropdown-menu'
|
|
58
57
|
import Link from 'next/link'
|
|
59
58
|
|
|
60
|
-
<
|
|
59
|
+
<Button href="/docs" variant="link">Documentation</Button>
|
|
61
60
|
|
|
62
61
|
<DropdownMenu>
|
|
63
62
|
<DropdownMenuTrigger render={<Button variant="outline" />}>
|
|
@@ -75,8 +74,8 @@ import Link from 'next/link'
|
|
|
75
74
|
</DropdownMenu>
|
|
76
75
|
```
|
|
77
76
|
|
|
78
|
-
`
|
|
79
|
-
`Button` renders a native button; use `type="submit"` explicitly for form submission.
|
|
77
|
+
`Button` with `href` renders a native Next.js link; use `variant="link"` for text links.
|
|
78
|
+
Without `href`, `Button` renders a native button; use `type="submit"` explicitly for form submission.
|
|
80
79
|
Menu actions use `onClick`. Labels must be inside `DropdownMenuGroup`.
|
|
81
80
|
Checkbox values are boolean; checkbox/radio items remain open unless
|
|
82
81
|
`closeOnClick` is enabled.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
## shadcn/ui
|
|
4
|
+
|
|
5
|
+
Table and Kbd are adapted from the official base-nova registry components:
|
|
6
|
+
https://ui.shadcn.com/docs/components/base/table and
|
|
7
|
+
https://ui.shadcn.com/docs/components/base/kbd.
|
|
8
|
+
|
|
9
|
+
MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2023 shadcn
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lexibase/rdx-ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": "^22.18.0 || ^24.11.0"
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"description": "The shared RDX documentation theme and UI components.",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"files": [
|
|
11
|
+
"THIRD_PARTY_NOTICES.md",
|
|
11
12
|
"src",
|
|
12
13
|
"postcss.config.mjs",
|
|
13
14
|
"postcss-plugin.cjs",
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
"tailwind-merge": "^3.7.0",
|
|
34
35
|
"tailwindcss": "^4.3.3",
|
|
35
36
|
"tw-animate-css": "^1.4.0",
|
|
36
|
-
"@lexibase/rdx-types": "1.1.
|
|
37
|
+
"@lexibase/rdx-types": "1.1.1"
|
|
37
38
|
},
|
|
38
39
|
"peerDependencies": {
|
|
39
40
|
"next": "^16.4.2",
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { Button as ButtonPrimitive } from '@base-ui/react/button'
|
|
4
4
|
import { cva, type VariantProps } from 'class-variance-authority'
|
|
5
|
+
import Link from 'next/link'
|
|
6
|
+
import type { ComponentProps } from 'react'
|
|
5
7
|
|
|
6
8
|
import { cn } from '@lexibase/rdx-ui/lib/utils'
|
|
7
9
|
|
|
@@ -43,13 +45,23 @@ const buttonVariants = cva(
|
|
|
43
45
|
}
|
|
44
46
|
)
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
type ButtonProps =
|
|
49
|
+
| (ButtonPrimitive.Props &
|
|
50
|
+
VariantProps<typeof buttonVariants> & { href?: never })
|
|
51
|
+
| (ComponentProps<typeof Link> & VariantProps<typeof buttonVariants>)
|
|
52
|
+
|
|
53
|
+
function Button(props: ButtonProps) {
|
|
54
|
+
if (props.href !== undefined) {
|
|
55
|
+
const { className, variant = 'link', size, active, ...linkProps } = props
|
|
56
|
+
return (
|
|
57
|
+
<Link
|
|
58
|
+
data-slot="button"
|
|
59
|
+
className={cn(buttonVariants({ variant, size, active }), className)}
|
|
60
|
+
{...linkProps}
|
|
61
|
+
/>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
const { className, variant, size, active, ...buttonProps } = props
|
|
53
65
|
return (
|
|
54
66
|
<ButtonPrimitive
|
|
55
67
|
data-slot="button"
|
|
@@ -59,7 +71,7 @@ function Button({
|
|
|
59
71
|
typeof className === 'function' ? className(state) : className
|
|
60
72
|
)
|
|
61
73
|
}
|
|
62
|
-
{...
|
|
74
|
+
{...buttonProps}
|
|
63
75
|
/>
|
|
64
76
|
)
|
|
65
77
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getMessages } from '../lib/i18n'
|
|
1
2
|
import { Children, isValidElement, type ComponentProps } from 'react'
|
|
2
3
|
import { CodeXml } from 'lucide-react'
|
|
3
4
|
|
|
@@ -6,10 +7,12 @@ import { CopyCodeButton } from './copy-code-button'
|
|
|
6
7
|
|
|
7
8
|
/** Keep the code server-rendered; only clipboard interaction needs JavaScript. */
|
|
8
9
|
export function CodeBlock({
|
|
10
|
+
lang,
|
|
9
11
|
children,
|
|
10
12
|
className,
|
|
11
13
|
...props
|
|
12
14
|
}: ComponentProps<'pre'>) {
|
|
15
|
+
const messages = getMessages(lang)
|
|
13
16
|
const code = Children.toArray(children).find(
|
|
14
17
|
(child) =>
|
|
15
18
|
isValidElement<{ className?: string }>(child) &&
|
|
@@ -35,7 +38,7 @@ export function CodeBlock({
|
|
|
35
38
|
className="min-w-0 truncate font-mono text-xs font-medium text-muted-foreground"
|
|
36
39
|
title={language}
|
|
37
40
|
>
|
|
38
|
-
{language ??
|
|
41
|
+
{language ?? messages.code}
|
|
39
42
|
</span>
|
|
40
43
|
</div>
|
|
41
44
|
<CopyCodeButton />
|
|
@@ -1,11 +1,14 @@
|
|
|
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 { Check, Copy } from 'lucide-react'
|
|
5
7
|
|
|
6
8
|
import { Button } from './button'
|
|
7
9
|
|
|
8
10
|
export function CopyCodeButton() {
|
|
11
|
+
const messages = useMessages()
|
|
9
12
|
const [status, setStatus] = useState<'idle' | 'copied' | 'error'>('idle')
|
|
10
13
|
const pending = useRef(false)
|
|
11
14
|
const mounted = useRef(false)
|
|
@@ -50,9 +53,9 @@ export function CopyCodeButton() {
|
|
|
50
53
|
}
|
|
51
54
|
>
|
|
52
55
|
{status === 'copied'
|
|
53
|
-
?
|
|
56
|
+
? messages.codeCopied
|
|
54
57
|
: status === 'error'
|
|
55
|
-
?
|
|
58
|
+
? messages.copyCodeError
|
|
56
59
|
: ''}
|
|
57
60
|
</span>
|
|
58
61
|
<Button
|
|
@@ -61,13 +64,13 @@ export function CopyCodeButton() {
|
|
|
61
64
|
size="icon"
|
|
62
65
|
data-status={status}
|
|
63
66
|
className="size-7 shrink-0 border-border/70 bg-background/60 p-0 text-muted-foreground shadow-none hover:text-foreground data-[status=copied]:border-emerald-500/30 data-[status=copied]:bg-emerald-500/10 data-[status=copied]:text-emerald-700 dark:data-[status=copied]:text-emerald-400 data-[status=error]:border-amber-500/30 [&>svg]:size-3.5"
|
|
64
|
-
aria-label=
|
|
67
|
+
aria-label={messages.copyCode}
|
|
65
68
|
title={
|
|
66
69
|
status === 'copied'
|
|
67
|
-
?
|
|
70
|
+
? messages.copied
|
|
68
71
|
: status === 'error'
|
|
69
|
-
?
|
|
70
|
-
:
|
|
72
|
+
? messages.copyRetry
|
|
73
|
+
: messages.copyCode
|
|
71
74
|
}
|
|
72
75
|
onClick={(event) => copy(event.currentTarget)}
|
|
73
76
|
>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { useMessages } from '../providers/locale-provider'
|
|
4
|
+
|
|
3
5
|
// shadcn/ui Base Nova Dialog, using the shared RDX theme and buttons.
|
|
4
6
|
import * as React from 'react'
|
|
5
7
|
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog'
|
|
@@ -20,6 +22,7 @@ function DialogContent({
|
|
|
20
22
|
children,
|
|
21
23
|
...props
|
|
22
24
|
}: React.ComponentProps<typeof DialogPrimitive.Popup>) {
|
|
25
|
+
const messages = useMessages()
|
|
23
26
|
return (
|
|
24
27
|
<DialogPrimitive.Portal>
|
|
25
28
|
<DialogPrimitive.Backdrop
|
|
@@ -43,7 +46,7 @@ function DialogContent({
|
|
|
43
46
|
className="absolute top-2 right-2 size-7"
|
|
44
47
|
/>
|
|
45
48
|
}
|
|
46
|
-
aria-label=
|
|
49
|
+
aria-label={messages.closeSearch}
|
|
47
50
|
>
|
|
48
51
|
<X className="size-3.5" />
|
|
49
52
|
</DialogPrimitive.Close>
|
|
@@ -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('')
|
|
@@ -95,15 +100,16 @@ export function DocsSearchDialog({
|
|
|
95
100
|
return (
|
|
96
101
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
97
102
|
<DialogContent finalFocus={triggerRef}>
|
|
98
|
-
<DialogTitle className="sr-only">
|
|
103
|
+
<DialogTitle className="sr-only">
|
|
104
|
+
{messages.searchDocumentation}
|
|
105
|
+
</DialogTitle>
|
|
99
106
|
<DialogDescription className="sr-only">
|
|
100
|
-
|
|
101
|
-
result and Enter to open it.
|
|
107
|
+
{formatMessage(messages.searchDescription, { version })}
|
|
102
108
|
</DialogDescription>
|
|
103
|
-
<Command shouldFilter={false} label=
|
|
109
|
+
<Command shouldFilter={false} label={messages.searchDocumentation}>
|
|
104
110
|
<CommandInput
|
|
105
|
-
aria-label=
|
|
106
|
-
placeholder=
|
|
111
|
+
aria-label={messages.searchDocumentation}
|
|
112
|
+
placeholder={messages.searchPlaceholder}
|
|
107
113
|
value={query}
|
|
108
114
|
onValueChange={setQuery}
|
|
109
115
|
maxLength={120}
|
|
@@ -111,21 +117,24 @@ export function DocsSearchDialog({
|
|
|
111
117
|
<div className="px-2 pt-2 pb-1 text-[11px] text-muted-foreground">
|
|
112
118
|
{version}
|
|
113
119
|
</div>
|
|
114
|
-
<CommandList
|
|
120
|
+
<CommandList
|
|
121
|
+
aria-busy={loading}
|
|
122
|
+
aria-label={messages.searchDocumentation}
|
|
123
|
+
>
|
|
115
124
|
{loading ? (
|
|
116
125
|
<div
|
|
117
126
|
role="status"
|
|
118
127
|
className="flex justify-center gap-2 p-6 text-sm text-muted-foreground"
|
|
119
128
|
>
|
|
120
129
|
<LoaderCircle className="size-4 animate-spin motion-reduce:animate-none" />
|
|
121
|
-
|
|
130
|
+
{messages.loadingSearch}
|
|
122
131
|
</div>
|
|
123
132
|
) : state.error ? (
|
|
124
133
|
<div
|
|
125
134
|
role="status"
|
|
126
135
|
className="p-4 text-center text-sm text-muted-foreground"
|
|
127
136
|
>
|
|
128
|
-
|
|
137
|
+
{messages.searchUnavailable}{' '}
|
|
129
138
|
<Button
|
|
130
139
|
variant="link"
|
|
131
140
|
size="sm"
|
|
@@ -134,16 +143,16 @@ export function DocsSearchDialog({
|
|
|
134
143
|
setRetry((value) => value + 1)
|
|
135
144
|
}}
|
|
136
145
|
>
|
|
137
|
-
|
|
146
|
+
{messages.tryAgain}
|
|
138
147
|
</Button>
|
|
139
148
|
</div>
|
|
140
149
|
) : !query.trim() ? (
|
|
141
150
|
<div className="px-4 py-6 text-center text-sm text-muted-foreground">
|
|
142
|
-
|
|
151
|
+
{messages.typeToSearch}
|
|
143
152
|
</div>
|
|
144
153
|
) : (
|
|
145
154
|
<>
|
|
146
|
-
<CommandEmpty>
|
|
155
|
+
<CommandEmpty>{messages.noResults}</CommandEmpty>
|
|
147
156
|
{results.map((entry) => (
|
|
148
157
|
<CommandItem
|
|
149
158
|
key={entry.href}
|
|
@@ -175,7 +184,8 @@ export function DocsSearchDialog({
|
|
|
175
184
|
)}
|
|
176
185
|
</CommandList>
|
|
177
186
|
<div className="border-t px-2 py-2 text-[10px] text-muted-foreground">
|
|
178
|
-
↑ ↓
|
|
187
|
+
↑ ↓ {messages.navigate}{' '}
|
|
188
|
+
<span className="mx-2">↵ {messages.open}</span> Esc {messages.close}
|
|
179
189
|
</div>
|
|
180
190
|
</Command>
|
|
181
191
|
</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" />
|