@lexibase/rdx-ui 0.0.0-stage → 1.0.0-beta.1
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/LICENSE +8 -0
- package/README.md +21 -2
- package/package.json +65 -3
- package/postcss.config.mjs +12 -0
- package/src/components/button.tsx +70 -0
- package/src/components/docs-sidebar.tsx +102 -0
- package/src/components/dropdown-menu.tsx +257 -0
- package/src/components/home.tsx +48 -0
- package/src/components/loading-button.tsx +19 -0
- package/src/components/logo.tsx +12 -0
- package/src/components/mdx-components.tsx +51 -0
- package/src/components/mode-toggle.tsx +26 -0
- package/src/components/navbar.tsx +92 -0
- package/src/components/separator.tsx +28 -0
- package/src/components/version-dropdown.tsx +127 -0
- package/src/contexts/.gitkeep +0 -0
- package/src/hooks/.gitkeep +0 -0
- package/src/lib/utils.ts +6 -0
- package/src/providers/theme-provider.tsx +11 -0
- package/src/styles/globals.css +125 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
Copyright © 2025 Lexibase
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
5
|
+
|
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
-
#
|
|
1
|
+
# RDX UI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The shared theme used by the RDX development website and generated documentation
|
|
4
|
+
sites. The source of truth is `packages/rdx-ui/src`: Radix dropdowns, buttons,
|
|
5
|
+
logo, theme provider, navbar, sidebar, landing page and MDX components.
|
|
6
|
+
|
|
7
|
+
Published as `@lexibase/rdx-ui`. React/Next.js source is intentionally shipped
|
|
8
|
+
without bundling so Next.js preserves server/client component boundaries. Add
|
|
9
|
+
the package to `transpilePackages` when using it outside the RDX runtime.
|
|
10
|
+
The RDX runtime already configures this, PostCSS and Tailwind source scanning.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { Home } from '@lexibase/rdx-ui/components/home'
|
|
14
|
+
|
|
15
|
+
export default function MyHome() {
|
|
16
|
+
return <Home title="My project" />
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Configure project-owned components via `homepage.component` and `navbar.logo`.
|
|
21
|
+
Do not edit generated `.rdx/` files. Installing the versioning plugin does not
|
|
22
|
+
change this theme: it contributes data to the shared version dropdown.
|
package/package.json
CHANGED
|
@@ -1,6 +1,68 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lexibase/rdx-ui",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "The shared RDX documentation theme and UI components.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"files": [
|
|
8
|
+
"src",
|
|
9
|
+
"postcss.config.mjs",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/lexibase/rdx.git",
|
|
18
|
+
"directory": "packages/rdx-ui"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@tailwindcss/postcss": "^4.1.13",
|
|
22
|
+
"postcss": "^8.5.6",
|
|
23
|
+
"tailwindcss": "^4.1.13",
|
|
24
|
+
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
25
|
+
"@radix-ui/react-separator": "^1.1.7",
|
|
26
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
27
|
+
"class-variance-authority": "^0.7.1",
|
|
28
|
+
"clsx": "^2.1.1",
|
|
29
|
+
"lucide-react": "^0.542.0",
|
|
30
|
+
"next-themes": "^0.4.6",
|
|
31
|
+
"tailwind-merge": "^3.3.1",
|
|
32
|
+
"tw-animate-css": "^1.3.8"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"next": "^15.5.0",
|
|
36
|
+
"react": "^19.1.0",
|
|
37
|
+
"react-dom": "^19.1.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@tailwindcss/postcss": "^4.1.13",
|
|
41
|
+
"@types/node": "^22.15.3",
|
|
42
|
+
"@types/react": "19.1.0",
|
|
43
|
+
"@types/react-dom": "19.1.1",
|
|
44
|
+
"eslint": "^9.34.0",
|
|
45
|
+
"postcss": "^8.5.6",
|
|
46
|
+
"next": "^15.5.0",
|
|
47
|
+
"react": "19.1.0",
|
|
48
|
+
"react-dom": "19.1.0",
|
|
49
|
+
"tailwindcss": "^4.1.13",
|
|
50
|
+
"typescript": "5.9.2",
|
|
51
|
+
"@rdx/typescript-config": "1.0.0",
|
|
52
|
+
"@rdx/eslint-config": "1.0.0"
|
|
53
|
+
},
|
|
54
|
+
"exports": {
|
|
55
|
+
"./globals.css": "./src/styles/globals.css",
|
|
56
|
+
"./postcss.config": "./postcss.config.mjs",
|
|
57
|
+
"./lib/*": "./src/lib/*.ts",
|
|
58
|
+
"./components/*": "./src/components/*.tsx",
|
|
59
|
+
"./hooks/*": "./src/hooks/*.ts",
|
|
60
|
+
"./contenxts/*": "./src/contexts/*",
|
|
61
|
+
"./providers/*": "./src/providers/*.tsx"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"lint": "eslint . --max-warnings 0",
|
|
65
|
+
"generate:component": "turbo gen react-component",
|
|
66
|
+
"check-types": "tsc --noEmit"
|
|
67
|
+
}
|
|
6
68
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
|
|
3
|
+
const require = createRequire(import.meta.url)
|
|
4
|
+
|
|
5
|
+
/** @type {import('postcss-load-config').Config} */
|
|
6
|
+
const config = {
|
|
7
|
+
plugins: {
|
|
8
|
+
[require.resolve('@tailwindcss/postcss')]: {},
|
|
9
|
+
},
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default config
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { Slot } from '@radix-ui/react-slot'
|
|
5
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
6
|
+
|
|
7
|
+
import { cn } from '@lexibase/rdx-ui/lib/utils'
|
|
8
|
+
|
|
9
|
+
const buttonVariants = cva(
|
|
10
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
11
|
+
{
|
|
12
|
+
variants: {
|
|
13
|
+
variant: {
|
|
14
|
+
default:
|
|
15
|
+
'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
|
|
16
|
+
destructive:
|
|
17
|
+
'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 dark:hover:bg-destructive/40',
|
|
18
|
+
outline:
|
|
19
|
+
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
|
20
|
+
primary:
|
|
21
|
+
'bg-primary text-primary-foreground shadow-xs hover:bg-primary/80',
|
|
22
|
+
secondary:
|
|
23
|
+
'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
|
|
24
|
+
ghost:
|
|
25
|
+
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
|
26
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
27
|
+
},
|
|
28
|
+
size: {
|
|
29
|
+
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
|
|
30
|
+
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
|
|
31
|
+
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
|
32
|
+
icon: 'size-9',
|
|
33
|
+
link: 'inline-block',
|
|
34
|
+
},
|
|
35
|
+
active: {
|
|
36
|
+
true: 'bg-accent text-accent-foreground dark:bg-accent/50',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
defaultVariants: {
|
|
40
|
+
variant: 'default',
|
|
41
|
+
size: 'default',
|
|
42
|
+
active: false,
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
function Button({
|
|
48
|
+
className,
|
|
49
|
+
variant,
|
|
50
|
+
size,
|
|
51
|
+
active,
|
|
52
|
+
asChild = false,
|
|
53
|
+
...props
|
|
54
|
+
}: React.ComponentProps<'button'> &
|
|
55
|
+
VariantProps<typeof buttonVariants> & {
|
|
56
|
+
asChild?: boolean
|
|
57
|
+
active?: boolean
|
|
58
|
+
}) {
|
|
59
|
+
const Comp = asChild ? Slot : 'button'
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Comp
|
|
63
|
+
data-slot="button"
|
|
64
|
+
className={cn(buttonVariants({ variant, size, active, className }))}
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { Button, buttonVariants }
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from 'react'
|
|
4
|
+
import { SidebarClose, SidebarOpen } from 'lucide-react'
|
|
5
|
+
import Link from 'next/link'
|
|
6
|
+
|
|
7
|
+
import { Button } from './button'
|
|
8
|
+
import { Separator } from './separator'
|
|
9
|
+
|
|
10
|
+
export type SidebarCategory = {
|
|
11
|
+
title: string
|
|
12
|
+
links: { href: string; label: string }[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function DocsSidebar({
|
|
16
|
+
categories,
|
|
17
|
+
pathname,
|
|
18
|
+
}: {
|
|
19
|
+
categories: SidebarCategory[]
|
|
20
|
+
pathname: string
|
|
21
|
+
}) {
|
|
22
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
23
|
+
const dialog = useRef<HTMLDialogElement>(null)
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (isOpen && !dialog.current?.open) dialog.current?.showModal()
|
|
26
|
+
if (!isOpen && dialog.current?.open) dialog.current?.close()
|
|
27
|
+
}, [isOpen])
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
setIsOpen(false)
|
|
30
|
+
}, [pathname])
|
|
31
|
+
const content = (
|
|
32
|
+
<nav aria-label="Documentation pages">
|
|
33
|
+
{categories.map((category, index) => (
|
|
34
|
+
<div
|
|
35
|
+
key={`${index}:${category.title}`}
|
|
36
|
+
className="flex flex-col gap-0.5 mb-3 first:mt-4"
|
|
37
|
+
>
|
|
38
|
+
<p className="text-xs text-ring font-semibold uppercase mb-1">
|
|
39
|
+
{category.title}
|
|
40
|
+
</p>
|
|
41
|
+
{category.links.map((link) => (
|
|
42
|
+
<Button
|
|
43
|
+
key={link.href}
|
|
44
|
+
variant="ghost"
|
|
45
|
+
size="sm"
|
|
46
|
+
className="justify-start"
|
|
47
|
+
active={pathname === link.href}
|
|
48
|
+
asChild
|
|
49
|
+
aria-label={link.label}
|
|
50
|
+
>
|
|
51
|
+
<Link
|
|
52
|
+
className="text-sm text-primary"
|
|
53
|
+
href={link.href}
|
|
54
|
+
onClick={() => setIsOpen(false)}
|
|
55
|
+
aria-current={pathname === link.href ? 'page' : undefined}
|
|
56
|
+
>
|
|
57
|
+
{link.label}
|
|
58
|
+
</Link>
|
|
59
|
+
</Button>
|
|
60
|
+
))}
|
|
61
|
+
{index < categories.length - 1 && <Separator className="my-4" />}
|
|
62
|
+
</div>
|
|
63
|
+
))}
|
|
64
|
+
</nav>
|
|
65
|
+
)
|
|
66
|
+
return (
|
|
67
|
+
<>
|
|
68
|
+
<div className="md:hidden absolute top-2 left-4 z-30">
|
|
69
|
+
<Button
|
|
70
|
+
variant="ghost"
|
|
71
|
+
onClick={() => setIsOpen(true)}
|
|
72
|
+
aria-label="Open sidebar"
|
|
73
|
+
>
|
|
74
|
+
<SidebarOpen className="size-4" />
|
|
75
|
+
</Button>
|
|
76
|
+
</div>
|
|
77
|
+
<aside
|
|
78
|
+
data-rdx-sidebar
|
|
79
|
+
className="hidden md:block h-full w-40 lg:w-56 shrink-0 border-r border-border overflow-y-auto p-4"
|
|
80
|
+
>
|
|
81
|
+
{content}
|
|
82
|
+
</aside>
|
|
83
|
+
<dialog
|
|
84
|
+
ref={dialog}
|
|
85
|
+
aria-label="Documentation navigation"
|
|
86
|
+
onClose={() => setIsOpen(false)}
|
|
87
|
+
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"
|
|
88
|
+
>
|
|
89
|
+
<div className="flex justify-end mb-4">
|
|
90
|
+
<Button
|
|
91
|
+
variant="ghost"
|
|
92
|
+
onClick={() => setIsOpen(false)}
|
|
93
|
+
aria-label="Close sidebar"
|
|
94
|
+
>
|
|
95
|
+
<SidebarClose className="size-4" />
|
|
96
|
+
</Button>
|
|
97
|
+
</div>
|
|
98
|
+
{isOpen && content}
|
|
99
|
+
</dialog>
|
|
100
|
+
</>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
|
|
5
|
+
import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react'
|
|
6
|
+
|
|
7
|
+
import { cn } from '@lexibase/rdx-ui/lib/utils'
|
|
8
|
+
|
|
9
|
+
function DropdownMenu({
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
|
12
|
+
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function DropdownMenuPortal({
|
|
16
|
+
...props
|
|
17
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
|
18
|
+
return (
|
|
19
|
+
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function DropdownMenuTrigger({
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
|
26
|
+
return (
|
|
27
|
+
<DropdownMenuPrimitive.Trigger
|
|
28
|
+
data-slot="dropdown-menu-trigger"
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function DropdownMenuContent({
|
|
35
|
+
className,
|
|
36
|
+
sideOffset = 4,
|
|
37
|
+
...props
|
|
38
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
|
39
|
+
return (
|
|
40
|
+
<DropdownMenuPrimitive.Portal>
|
|
41
|
+
<DropdownMenuPrimitive.Content
|
|
42
|
+
data-slot="dropdown-menu-content"
|
|
43
|
+
sideOffset={sideOffset}
|
|
44
|
+
className={cn(
|
|
45
|
+
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
</DropdownMenuPrimitive.Portal>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function DropdownMenuGroup({
|
|
55
|
+
...props
|
|
56
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
|
57
|
+
return (
|
|
58
|
+
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function DropdownMenuItem({
|
|
63
|
+
className,
|
|
64
|
+
inset,
|
|
65
|
+
variant = 'default',
|
|
66
|
+
...props
|
|
67
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
|
68
|
+
inset?: boolean
|
|
69
|
+
variant?: 'default' | 'destructive'
|
|
70
|
+
}) {
|
|
71
|
+
return (
|
|
72
|
+
<DropdownMenuPrimitive.Item
|
|
73
|
+
data-slot="dropdown-menu-item"
|
|
74
|
+
data-inset={inset}
|
|
75
|
+
data-variant={variant}
|
|
76
|
+
className={cn(
|
|
77
|
+
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
78
|
+
className
|
|
79
|
+
)}
|
|
80
|
+
{...props}
|
|
81
|
+
/>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function DropdownMenuCheckboxItem({
|
|
86
|
+
className,
|
|
87
|
+
children,
|
|
88
|
+
checked,
|
|
89
|
+
...props
|
|
90
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
|
91
|
+
return (
|
|
92
|
+
<DropdownMenuPrimitive.CheckboxItem
|
|
93
|
+
data-slot="dropdown-menu-checkbox-item"
|
|
94
|
+
className={cn(
|
|
95
|
+
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
96
|
+
className
|
|
97
|
+
)}
|
|
98
|
+
checked={checked}
|
|
99
|
+
{...props}
|
|
100
|
+
>
|
|
101
|
+
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
|
102
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
103
|
+
<CheckIcon className="size-4" />
|
|
104
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
105
|
+
</span>
|
|
106
|
+
{children}
|
|
107
|
+
</DropdownMenuPrimitive.CheckboxItem>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function DropdownMenuRadioGroup({
|
|
112
|
+
...props
|
|
113
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
|
114
|
+
return (
|
|
115
|
+
<DropdownMenuPrimitive.RadioGroup
|
|
116
|
+
data-slot="dropdown-menu-radio-group"
|
|
117
|
+
{...props}
|
|
118
|
+
/>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function DropdownMenuRadioItem({
|
|
123
|
+
className,
|
|
124
|
+
children,
|
|
125
|
+
...props
|
|
126
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
|
127
|
+
return (
|
|
128
|
+
<DropdownMenuPrimitive.RadioItem
|
|
129
|
+
data-slot="dropdown-menu-radio-item"
|
|
130
|
+
className={cn(
|
|
131
|
+
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
132
|
+
className
|
|
133
|
+
)}
|
|
134
|
+
{...props}
|
|
135
|
+
>
|
|
136
|
+
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
|
137
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
138
|
+
<CircleIcon className="size-2 fill-current" />
|
|
139
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
140
|
+
</span>
|
|
141
|
+
{children}
|
|
142
|
+
</DropdownMenuPrimitive.RadioItem>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function DropdownMenuLabel({
|
|
147
|
+
className,
|
|
148
|
+
inset,
|
|
149
|
+
...props
|
|
150
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
|
151
|
+
inset?: boolean
|
|
152
|
+
}) {
|
|
153
|
+
return (
|
|
154
|
+
<DropdownMenuPrimitive.Label
|
|
155
|
+
data-slot="dropdown-menu-label"
|
|
156
|
+
data-inset={inset}
|
|
157
|
+
className={cn(
|
|
158
|
+
'px-2 py-1.5 text-sm font-medium data-[inset]:pl-8',
|
|
159
|
+
className
|
|
160
|
+
)}
|
|
161
|
+
{...props}
|
|
162
|
+
/>
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function DropdownMenuSeparator({
|
|
167
|
+
className,
|
|
168
|
+
...props
|
|
169
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
|
170
|
+
return (
|
|
171
|
+
<DropdownMenuPrimitive.Separator
|
|
172
|
+
data-slot="dropdown-menu-separator"
|
|
173
|
+
className={cn('bg-border -mx-1 my-1 h-px', className)}
|
|
174
|
+
{...props}
|
|
175
|
+
/>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function DropdownMenuShortcut({
|
|
180
|
+
className,
|
|
181
|
+
...props
|
|
182
|
+
}: React.ComponentProps<'span'>) {
|
|
183
|
+
return (
|
|
184
|
+
<span
|
|
185
|
+
data-slot="dropdown-menu-shortcut"
|
|
186
|
+
className={cn(
|
|
187
|
+
'text-muted-foreground ml-auto text-xs tracking-widest',
|
|
188
|
+
className
|
|
189
|
+
)}
|
|
190
|
+
{...props}
|
|
191
|
+
/>
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function DropdownMenuSub({
|
|
196
|
+
...props
|
|
197
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
|
198
|
+
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function DropdownMenuSubTrigger({
|
|
202
|
+
className,
|
|
203
|
+
inset,
|
|
204
|
+
children,
|
|
205
|
+
...props
|
|
206
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
207
|
+
inset?: boolean
|
|
208
|
+
}) {
|
|
209
|
+
return (
|
|
210
|
+
<DropdownMenuPrimitive.SubTrigger
|
|
211
|
+
data-slot="dropdown-menu-sub-trigger"
|
|
212
|
+
data-inset={inset}
|
|
213
|
+
className={cn(
|
|
214
|
+
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8',
|
|
215
|
+
className
|
|
216
|
+
)}
|
|
217
|
+
{...props}
|
|
218
|
+
>
|
|
219
|
+
{children}
|
|
220
|
+
<ChevronRightIcon className="ml-auto size-4" />
|
|
221
|
+
</DropdownMenuPrimitive.SubTrigger>
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function DropdownMenuSubContent({
|
|
226
|
+
className,
|
|
227
|
+
...props
|
|
228
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
|
229
|
+
return (
|
|
230
|
+
<DropdownMenuPrimitive.SubContent
|
|
231
|
+
data-slot="dropdown-menu-sub-content"
|
|
232
|
+
className={cn(
|
|
233
|
+
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
|
|
234
|
+
className
|
|
235
|
+
)}
|
|
236
|
+
{...props}
|
|
237
|
+
/>
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export {
|
|
242
|
+
DropdownMenu,
|
|
243
|
+
DropdownMenuPortal,
|
|
244
|
+
DropdownMenuTrigger,
|
|
245
|
+
DropdownMenuContent,
|
|
246
|
+
DropdownMenuGroup,
|
|
247
|
+
DropdownMenuLabel,
|
|
248
|
+
DropdownMenuItem,
|
|
249
|
+
DropdownMenuCheckboxItem,
|
|
250
|
+
DropdownMenuRadioGroup,
|
|
251
|
+
DropdownMenuRadioItem,
|
|
252
|
+
DropdownMenuSeparator,
|
|
253
|
+
DropdownMenuShortcut,
|
|
254
|
+
DropdownMenuSub,
|
|
255
|
+
DropdownMenuSubTrigger,
|
|
256
|
+
DropdownMenuSubContent,
|
|
257
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import Link from 'next/link'
|
|
3
|
+
|
|
4
|
+
import { Button } from './button'
|
|
5
|
+
import { Logo } from './logo'
|
|
6
|
+
|
|
7
|
+
/** The default landing page from apps/web, not a separate marketing theme. */
|
|
8
|
+
export function Home({
|
|
9
|
+
title = 'RDX',
|
|
10
|
+
logo,
|
|
11
|
+
description,
|
|
12
|
+
buttons = [],
|
|
13
|
+
}: {
|
|
14
|
+
title?: string
|
|
15
|
+
logo?: ReactNode
|
|
16
|
+
description?: string
|
|
17
|
+
buttons?: { label: string; href: string }[]
|
|
18
|
+
}) {
|
|
19
|
+
return (
|
|
20
|
+
<section
|
|
21
|
+
data-rdx-home
|
|
22
|
+
className="w-full h-full flex flex-col items-center justify-center"
|
|
23
|
+
>
|
|
24
|
+
{logo ?? (
|
|
25
|
+
<Logo className="size-52 hover:scale-105 hover:rotate-1 transition-[scale,rotation] duration-1000" />
|
|
26
|
+
)}
|
|
27
|
+
<span className="text-3xl font-mono uppercase">{title}</span>
|
|
28
|
+
{description && (
|
|
29
|
+
<p className="text-sm text-muted-foreground text-center max-w-xl mt-4 px-6">
|
|
30
|
+
{description}
|
|
31
|
+
</p>
|
|
32
|
+
)}
|
|
33
|
+
{buttons.length > 0 && (
|
|
34
|
+
<div className="flex flex-wrap justify-center gap-3 mt-6 px-6">
|
|
35
|
+
{buttons.map((button, index) => (
|
|
36
|
+
<Button
|
|
37
|
+
key={`${index}:${button.href}`}
|
|
38
|
+
variant={index === 0 ? 'default' : 'outline'}
|
|
39
|
+
asChild
|
|
40
|
+
>
|
|
41
|
+
<Link href={button.href}>{button.label}</Link>
|
|
42
|
+
</Button>
|
|
43
|
+
))}
|
|
44
|
+
</div>
|
|
45
|
+
)}
|
|
46
|
+
</section>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Spline } from 'lucide-react'
|
|
2
|
+
import { Button } from '@lexibase/rdx-ui/components/button'
|
|
3
|
+
|
|
4
|
+
export function LoadingButton() {
|
|
5
|
+
return (
|
|
6
|
+
<Button
|
|
7
|
+
variant="outline"
|
|
8
|
+
size="sm"
|
|
9
|
+
className="w-[134px] px-9 overflow-hidden"
|
|
10
|
+
disabled
|
|
11
|
+
aria-busy="true"
|
|
12
|
+
aria-label="Loading versions"
|
|
13
|
+
>
|
|
14
|
+
<div className="animate-spin flex items-center justify-center">
|
|
15
|
+
<Spline className="size-4" />
|
|
16
|
+
</div>
|
|
17
|
+
</Button>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const Logo = ({ className }: { className?: string }) => (
|
|
2
|
+
<svg
|
|
3
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
4
|
+
fill="currentColor"
|
|
5
|
+
viewBox="0 0 681 606"
|
|
6
|
+
className={className}
|
|
7
|
+
>
|
|
8
|
+
<path d="m94.3 40.4-2.8 2.4-.3 35.7c-.2 26 0 36.1.9 37.1.7.9 3.2 1.4 7 1.4 4.7 0 6-.4 6.9-1.9.6-1.2 1-12.6 1-30 0-24.8.2-28.1 1.6-28.1 1.4 0 29.9 6.5 67.9 15.5 8.3 1.9 21.8 5.1 30 7 8.3 1.9 21.8 5 30 7 8.3 1.9 24.9 5.8 37 8.6 36.6 8.3 45.5 10.8 55 15.4 14.3 7 24.2 17.2 30.6 31.5l2.4 5.5.3 191.5.2 191.4-4.7-4.6c-10.1-9.7-19.2-15.2-33.5-20.3-5.7-2-7.8-3.2-8.2-4.8-.3-1.2-.6-49.2-.6-106.7l.1-104.5-2.6-2.3c-1.4-1.2-3.5-2.2-4.6-2.2-2.8 0-7.5 2.6-8.3 4.5-.3.9-.6 50.5-.6 110.3-.1 79 .2 109.6 1.1 112 1.3 3.9 1.7 4.1 13.9 8.1 23.7 7.9 39.6 23.5 46 45.1 1.3 4.1 2.7 8.1 3.3 8.7 1.4 1.9 9.1 1.6 11.6-.5 1.2-1 2.7-4.2 3.6-7.7 5-19.7 19.5-35.7 39.4-43.2 3.6-1.4 55.8-14.8 115.9-29.9 60.2-15.1 109.8-27.7 110.4-28 3.9-2.4 3.8 1 3.8-212.9V45.8l-2.9-2.9c-1.8-1.8-4-2.9-5.6-2.9-2.6 0-12.9 2.3-43.5 9.6-8 1.9-23.3 5.5-34 8-10.7 2.4-23.1 5.3-27.5 6.4-13.7 3.3-27.6 6.6-69.5 16.4-22.3 5.3-43.8 10.5-47.8 11.6-15.5 4.3-29.7 13.7-39.5 25.9-3.3 4.2-6.2 7.7-6.6 7.9-.4.1-1.4-1.1-2.4-2.7-6.1-10.3-16.9-20.1-29.9-27-10.2-5.5-19.8-8.3-57.3-16.5-6-1.4-17.7-4.1-26-6.1-8.2-2-25.8-6.1-39-9.1-27.3-6.2-37.3-8.6-96.2-22.4-9.5-2.2-18.6-4-20.2-4-1.7 0-4.2 1-5.8 2.4zm537.5 214.1c-.2 150.9-.6 195.7-1.5 196.3-1.1.7-39 10.2-161.6 40.7-63.3 15.7-69.3 18.1-84.4 32.8l-6.3 6.1v-184c0-202.4-.2-194.9 6-207.8 5.5-11.4 14.6-20.5 26.5-26.4 6.8-3.4 12.5-5 45-12.6 22.7-5.3 82.4-19.4 110.5-26.1 8-1.8 21.5-5 30-7 8.5-1.9 18.9-4.4 23-5.4 4.1-1.1 8.8-2 10.3-2l2.8-.1-.3 195.5z" />
|
|
9
|
+
<path d="M81.1 126.2c-23.8 3.5-44.2 21.1-52.5 45.5-6.9 20.2-5.5 39.8 4.1 59.2 8.1 16.5 25 31.3 42.3 37.2 8.2 2.8 26.2 3.8 35.3 1.9 8-1.6 19.6-7 26.4-12.1l5.3-4v105.5c0 58-.3 105.7-.7 105.9-.5.3-8.4-1.4-17.8-3.7l-17-4.2v-84.6c0-81.8-.1-84.7-1.9-86.7-2.5-2.7-8.5-2.8-11.7-.2l-2.4 1.9-.3 89.6-.2 89.7 2.4 2.4c1.3 1.3 4.8 3 7.7 3.9C113 476.9 149 485 151.8 485c6.6 0 6.2 9.5 6.2-136.2 0-114 .2-131.9 1.5-133.1 1.2-1.3 12.1-1.4 78.2-1.2 80.5.2 81.1.2 82.7-4 1.3-3.6.6-8.2-1.7-10.3l-2.3-2.2H156l-2.9-6.5c-5.3-11.6-17.6-22.3-30-25.9-7.6-2.2-19.4-2.1-27.3.4-16.1 5-26.8 18.8-26.8 34.9 0 12.8 6 23.2 16.5 28.6 5 2.6 7 3 13.4 3 6.3 0 8.6-.5 13.4-2.8 6.9-3.4 9.2-6.4 8.3-10.9-1.3-6.6-5.6-8.4-11.1-4.8-2.1 1.4-4.7 2-8.8 2-5.3 0-6.1-.3-10-3.9-4.2-3.9-4.2-4-4.2-11 0-6.1.4-7.5 3-11.3 4-5.8 11.9-9.8 19.1-9.8 19 0 35.1 18.8 33.1 38.6-1.2 11.5-10.9 25.5-21.3 30.6-14.5 7.3-32.6 7-47.2-.7-12.7-6.6-24.5-20.6-29.2-34.6-6.3-18.6-1.2-42.8 12.1-57.3 5.8-6.3 16.1-12 24.7-13.6 3.3-.6 49.4-1 120.5-1 113.8 0 115.2 0 117.2-2 2.9-2.9 2.7-9.6-.4-12.1-2.1-1.7-4.1-1.9-19.5-1.9-9.4 0-61-.1-114.7-.3-53.7-.1-99.9.1-102.8.5z" />
|
|
10
|
+
<path d="M174.3 244.2c-1.1 1.3-2.3 3.1-2.6 4.1-1 2.7 1.8 8.4 4.4 9.2 1.2.4 33.5.8 71.6.8l69.4.1 2.1-2.2c3.2-3.1 2.8-8.9-.7-11.9l-2.6-2.3H176.3l-2 2.2zM195.9 286.3c-3.9 3-3.9 2.3-3.9 111 0 99 .1 105.6 1.8 107.6.9 1.2 3.4 2.4 5.4 2.7 3 .5 4.1.2 6.2-1.9l2.6-2.6.2-95.3c0-52.4.2-100 .3-105.8.1-10.9-.9-15.2-4-16.4-2.6-1-6.9-.6-8.6.7zM248.4 287.4l-2.8 2.3-.1 119.8v119.7l2.5 2c1.9 1.5 3.4 1.9 6.4 1.5 7.7-1.1 7.1 10.1 7.4-123.3l.2-119.5-2.4-2.4c-3.2-3.2-7.6-3.2-11.2-.1z" />
|
|
11
|
+
</svg>
|
|
12
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react'
|
|
2
|
+
|
|
3
|
+
import { Separator } from '@lexibase/rdx-ui/components/separator'
|
|
4
|
+
|
|
5
|
+
export const mdxComponents = {
|
|
6
|
+
h1: (props: ComponentProps<'h1'>) => (
|
|
7
|
+
<h1 className="text-4xl font-bold my-6" {...props} />
|
|
8
|
+
),
|
|
9
|
+
h2: (props: ComponentProps<'h2'>) => (
|
|
10
|
+
<h2 className="text-2xl font-bold my-6" {...props} />
|
|
11
|
+
),
|
|
12
|
+
h3: (props: ComponentProps<'h3'>) => (
|
|
13
|
+
<h3 className="text-xl font-bold my-6" {...props} />
|
|
14
|
+
),
|
|
15
|
+
p: (props: ComponentProps<'p'>) => <p className="my-4" {...props} />,
|
|
16
|
+
ul: (props: ComponentProps<'ul'>) => (
|
|
17
|
+
<ul className="list-disc my-4 ml-6" {...props} />
|
|
18
|
+
),
|
|
19
|
+
ol: (props: ComponentProps<'ol'>) => (
|
|
20
|
+
<ol className="list-decimal my-4 ml-6" {...props} />
|
|
21
|
+
),
|
|
22
|
+
li: (props: ComponentProps<'li'>) => (
|
|
23
|
+
<li className="my-2 before:mr-2" {...props} />
|
|
24
|
+
),
|
|
25
|
+
a: (props: ComponentProps<'a'>) => (
|
|
26
|
+
<a className="underline text-chart-1" {...props} />
|
|
27
|
+
),
|
|
28
|
+
blockquote: (props: ComponentProps<'blockquote'>) => (
|
|
29
|
+
<blockquote
|
|
30
|
+
className="bg-secondary text-muted-foreground border-l-2 pl-4 py-px my-2"
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
),
|
|
34
|
+
pre: (props: ComponentProps<'pre'>) => (
|
|
35
|
+
<pre
|
|
36
|
+
className="bg-secondary text-secondary-foreground rounded-md p-4 my-4 overflow-x-auto"
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
),
|
|
40
|
+
code: (props: ComponentProps<'code'>) => (
|
|
41
|
+
<code
|
|
42
|
+
className="bg-secondary text-secondary-foreground rounded-sm px-1 py-0.5"
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
),
|
|
46
|
+
|
|
47
|
+
// Custom
|
|
48
|
+
Separator: (props: ComponentProps<typeof Separator>) => (
|
|
49
|
+
<Separator className="my-8" {...props} />
|
|
50
|
+
),
|
|
51
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { SunMoon } from 'lucide-react'
|
|
4
|
+
import { useTheme } from 'next-themes'
|
|
5
|
+
import { Button } from '@lexibase/rdx-ui/components/button'
|
|
6
|
+
|
|
7
|
+
export function ModeToggle() {
|
|
8
|
+
const { theme, setTheme } = useTheme()
|
|
9
|
+
|
|
10
|
+
const toggleTheme = () => {
|
|
11
|
+
const nextTheme = theme === 'dark' ? 'light' : 'dark'
|
|
12
|
+
setTheme(nextTheme)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Button
|
|
17
|
+
size="icon"
|
|
18
|
+
variant="ghost"
|
|
19
|
+
onClick={toggleTheme}
|
|
20
|
+
aria-label="Mode Toggle"
|
|
21
|
+
>
|
|
22
|
+
<SunMoon className="h-[1.2rem] w-[1.2rem]" />
|
|
23
|
+
<span className="sr-only">colorTheme</span>
|
|
24
|
+
</Button>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import { Github } from 'lucide-react'
|
|
3
|
+
import Link from 'next/link'
|
|
4
|
+
|
|
5
|
+
import { Button } from './button'
|
|
6
|
+
import { Logo } from './logo'
|
|
7
|
+
import { Separator } from './separator'
|
|
8
|
+
import { ModeToggle } from './mode-toggle'
|
|
9
|
+
|
|
10
|
+
/** Shared composition extracted from apps/web/components/layout/Navbar. */
|
|
11
|
+
export function Navbar({
|
|
12
|
+
title = '',
|
|
13
|
+
siteTitle = 'RDX',
|
|
14
|
+
logo = <Logo className="size-5" />,
|
|
15
|
+
homeHref = '/',
|
|
16
|
+
docsHref = '/docs',
|
|
17
|
+
docsLink,
|
|
18
|
+
versionMenu,
|
|
19
|
+
github,
|
|
20
|
+
showToggleMode = false,
|
|
21
|
+
}: {
|
|
22
|
+
title?: string
|
|
23
|
+
siteTitle?: string
|
|
24
|
+
logo?: ReactNode
|
|
25
|
+
homeHref?: string
|
|
26
|
+
docsHref?: string
|
|
27
|
+
docsLink?: ReactNode
|
|
28
|
+
versionMenu?: ReactNode
|
|
29
|
+
github?: { visible: boolean; url: string }
|
|
30
|
+
showToggleMode?: boolean
|
|
31
|
+
}) {
|
|
32
|
+
return (
|
|
33
|
+
<nav
|
|
34
|
+
data-rdx-navbar
|
|
35
|
+
aria-label="Main navigation"
|
|
36
|
+
className="w-full flex flex-wrap shrink-0 items-center justify-between px-6 py-2 border-b border-border gap-y-2"
|
|
37
|
+
>
|
|
38
|
+
<div className="flex items-center gap-1">
|
|
39
|
+
<Button
|
|
40
|
+
variant="ghost"
|
|
41
|
+
size={title === '' ? 'icon' : 'default'}
|
|
42
|
+
asChild
|
|
43
|
+
aria-label={siteTitle}
|
|
44
|
+
>
|
|
45
|
+
<Link
|
|
46
|
+
href={homeHref}
|
|
47
|
+
className="cursor-default flex items-center gap-2"
|
|
48
|
+
>
|
|
49
|
+
{logo}
|
|
50
|
+
{title !== '' && <span>{title}</span>}
|
|
51
|
+
</Link>
|
|
52
|
+
</Button>
|
|
53
|
+
</div>
|
|
54
|
+
<div className="flex items-center md:gap-x-2 h-4">
|
|
55
|
+
{versionMenu}
|
|
56
|
+
{docsLink ?? (
|
|
57
|
+
<Button variant="link" size="sm" asChild>
|
|
58
|
+
<Link href={docsHref}>Docs</Link>
|
|
59
|
+
</Button>
|
|
60
|
+
)}
|
|
61
|
+
{github?.visible && github.url && (
|
|
62
|
+
<>
|
|
63
|
+
<Separator orientation="vertical" />
|
|
64
|
+
<Button
|
|
65
|
+
variant="ghost"
|
|
66
|
+
size="icon"
|
|
67
|
+
title="GitHub"
|
|
68
|
+
asChild
|
|
69
|
+
aria-label="GitHub"
|
|
70
|
+
>
|
|
71
|
+
<Link
|
|
72
|
+
className="cursor-default"
|
|
73
|
+
href={github.url}
|
|
74
|
+
target={github.url === '#' ? undefined : '_blank'}
|
|
75
|
+
rel={github.url === '#' ? undefined : 'noopener noreferrer'}
|
|
76
|
+
>
|
|
77
|
+
<Github className="h-[1.2rem] w-[1.2rem]" />
|
|
78
|
+
<span className="sr-only">GitHub</span>
|
|
79
|
+
</Link>
|
|
80
|
+
</Button>
|
|
81
|
+
</>
|
|
82
|
+
)}
|
|
83
|
+
{showToggleMode && (
|
|
84
|
+
<>
|
|
85
|
+
<Separator orientation="vertical" />
|
|
86
|
+
<ModeToggle />
|
|
87
|
+
</>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
</nav>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import * as SeparatorPrimitive from '@radix-ui/react-separator'
|
|
5
|
+
|
|
6
|
+
import { cn } from '@lexibase/rdx-ui/lib/utils'
|
|
7
|
+
|
|
8
|
+
function Separator({
|
|
9
|
+
className,
|
|
10
|
+
orientation = 'horizontal',
|
|
11
|
+
decorative = true,
|
|
12
|
+
...props
|
|
13
|
+
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
|
14
|
+
return (
|
|
15
|
+
<SeparatorPrimitive.Root
|
|
16
|
+
data-slot="separator"
|
|
17
|
+
decorative={decorative}
|
|
18
|
+
orientation={orientation}
|
|
19
|
+
className={cn(
|
|
20
|
+
'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { Separator }
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Archive, Bird, PackageOpen } from 'lucide-react'
|
|
4
|
+
import Link from 'next/link'
|
|
5
|
+
import type { ReactNode } from 'react'
|
|
6
|
+
|
|
7
|
+
import { Button } from './button'
|
|
8
|
+
import {
|
|
9
|
+
DropdownMenu,
|
|
10
|
+
DropdownMenuContent,
|
|
11
|
+
DropdownMenuGroup,
|
|
12
|
+
DropdownMenuItem,
|
|
13
|
+
DropdownMenuLabel,
|
|
14
|
+
DropdownMenuSeparator,
|
|
15
|
+
DropdownMenuShortcut,
|
|
16
|
+
DropdownMenuTrigger,
|
|
17
|
+
} from './dropdown-menu'
|
|
18
|
+
|
|
19
|
+
export type VersionOption = {
|
|
20
|
+
id: string
|
|
21
|
+
label: string
|
|
22
|
+
href: string
|
|
23
|
+
kind: 'live' | 'version' | 'archived'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function VersionIcon({ kind }: { kind: VersionOption['kind'] }) {
|
|
27
|
+
const Icon =
|
|
28
|
+
kind === 'live' ? Bird : kind === 'archived' ? Archive : PackageOpen
|
|
29
|
+
return <Icon className="size-4" />
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Uses the same Radix primitives, sizes, icons and states as the showcase. */
|
|
33
|
+
export function VersionDropdown({
|
|
34
|
+
options,
|
|
35
|
+
currentId,
|
|
36
|
+
onSelect,
|
|
37
|
+
footer,
|
|
38
|
+
}: {
|
|
39
|
+
options: VersionOption[]
|
|
40
|
+
currentId: string
|
|
41
|
+
onSelect?: (id: string) => void
|
|
42
|
+
footer?: ReactNode
|
|
43
|
+
}) {
|
|
44
|
+
const current =
|
|
45
|
+
options.find((option) => option.id === currentId) ?? options[0]
|
|
46
|
+
if (!current) return null
|
|
47
|
+
const live = options.filter((option) => option.kind === 'live')
|
|
48
|
+
const active = options.filter((option) => option.kind === 'version')
|
|
49
|
+
const archived = options.filter((option) => option.kind === 'archived')
|
|
50
|
+
const item = (option: VersionOption) => {
|
|
51
|
+
const className = `capitalize ${option.id === current.id ? 'bg-accent/30 border-1 border-border' : ''} ${option.kind === 'archived' ? 'text-muted-foreground line-through' : ''}`
|
|
52
|
+
const content = (
|
|
53
|
+
<>
|
|
54
|
+
{option.label}
|
|
55
|
+
<DropdownMenuShortcut>
|
|
56
|
+
<VersionIcon kind={option.kind} />
|
|
57
|
+
</DropdownMenuShortcut>
|
|
58
|
+
</>
|
|
59
|
+
)
|
|
60
|
+
return onSelect ? (
|
|
61
|
+
<DropdownMenuItem
|
|
62
|
+
key={option.id}
|
|
63
|
+
className={className}
|
|
64
|
+
onSelect={() => onSelect(option.id)}
|
|
65
|
+
>
|
|
66
|
+
{content}
|
|
67
|
+
</DropdownMenuItem>
|
|
68
|
+
) : (
|
|
69
|
+
<DropdownMenuItem key={option.id} className={className} asChild>
|
|
70
|
+
<Link
|
|
71
|
+
href={option.href}
|
|
72
|
+
aria-current={option.id === current.id ? 'true' : undefined}
|
|
73
|
+
>
|
|
74
|
+
{content}
|
|
75
|
+
</Link>
|
|
76
|
+
</DropdownMenuItem>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
return (
|
|
80
|
+
<DropdownMenu>
|
|
81
|
+
<DropdownMenuTrigger asChild>
|
|
82
|
+
<Button
|
|
83
|
+
data-rdx-version-trigger
|
|
84
|
+
variant="outline"
|
|
85
|
+
size="sm"
|
|
86
|
+
className="min-w-[134px] capitalize"
|
|
87
|
+
aria-label={`Documentation version: ${current.label}`}
|
|
88
|
+
>
|
|
89
|
+
{current.label}
|
|
90
|
+
<DropdownMenuShortcut
|
|
91
|
+
className={
|
|
92
|
+
current.kind === 'live'
|
|
93
|
+
? 'text-blue-500'
|
|
94
|
+
: current.kind === 'archived'
|
|
95
|
+
? 'text-red-500'
|
|
96
|
+
: 'text-primary'
|
|
97
|
+
}
|
|
98
|
+
>
|
|
99
|
+
<VersionIcon kind={current.kind} />
|
|
100
|
+
</DropdownMenuShortcut>
|
|
101
|
+
</Button>
|
|
102
|
+
</DropdownMenuTrigger>
|
|
103
|
+
<DropdownMenuContent className="w-56" align="start" collisionPadding={8}>
|
|
104
|
+
{live.length > 0 && (
|
|
105
|
+
<>
|
|
106
|
+
<DropdownMenuGroup>{live.map(item)}</DropdownMenuGroup>
|
|
107
|
+
<DropdownMenuSeparator />
|
|
108
|
+
</>
|
|
109
|
+
)}
|
|
110
|
+
{active.map(item)}
|
|
111
|
+
{archived.length > 0 && (
|
|
112
|
+
<>
|
|
113
|
+
<DropdownMenuSeparator />
|
|
114
|
+
<DropdownMenuGroup>
|
|
115
|
+
<DropdownMenuLabel className="text-center py-px cursor-default">
|
|
116
|
+
Archived
|
|
117
|
+
</DropdownMenuLabel>
|
|
118
|
+
<DropdownMenuSeparator />
|
|
119
|
+
{archived.map(item)}
|
|
120
|
+
</DropdownMenuGroup>
|
|
121
|
+
</>
|
|
122
|
+
)}
|
|
123
|
+
{footer}
|
|
124
|
+
</DropdownMenuContent>
|
|
125
|
+
</DropdownMenu>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
File without changes
|
|
File without changes
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
|
4
|
+
import * as React from 'react'
|
|
5
|
+
|
|
6
|
+
export function ThemeProvider({
|
|
7
|
+
children,
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<typeof NextThemesProvider>) {
|
|
10
|
+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
|
11
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
@import 'tailwindcss';
|
|
2
|
+
@source "../**/*.{ts,tsx}";
|
|
3
|
+
|
|
4
|
+
@import 'tw-animate-css';
|
|
5
|
+
|
|
6
|
+
@custom-variant dark (&:is(.dark *));
|
|
7
|
+
|
|
8
|
+
:root {
|
|
9
|
+
--background: oklch(1 0 0);
|
|
10
|
+
--foreground: oklch(0.145 0 0);
|
|
11
|
+
--card: oklch(1 0 0);
|
|
12
|
+
--card-foreground: oklch(0.145 0 0);
|
|
13
|
+
--popover: oklch(1 0 0);
|
|
14
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
15
|
+
--primary: oklch(0.205 0 0);
|
|
16
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
17
|
+
--secondary: oklch(0.97 0 0);
|
|
18
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
19
|
+
--muted: oklch(0.97 0 0);
|
|
20
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
21
|
+
--accent: oklch(0.97 0 0);
|
|
22
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
23
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
24
|
+
--destructive-foreground: oklch(0.577 0.245 27.325);
|
|
25
|
+
--border: oklch(0.922 0 0);
|
|
26
|
+
--input: oklch(0.922 0 0);
|
|
27
|
+
--ring: oklch(0.708 0 0);
|
|
28
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
29
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
30
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
31
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
32
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
33
|
+
--radius: 0.625rem;
|
|
34
|
+
--sidebar: oklch(0.985 0 0);
|
|
35
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
36
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
37
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
38
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
39
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
40
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
41
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.dark {
|
|
45
|
+
--background: oklch(0.145 0 0);
|
|
46
|
+
--foreground: oklch(0.985 0 0);
|
|
47
|
+
--card: oklch(0.145 0 0);
|
|
48
|
+
--card-foreground: oklch(0.985 0 0);
|
|
49
|
+
--popover: oklch(0.145 0 0);
|
|
50
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
51
|
+
--primary: oklch(0.985 0 0);
|
|
52
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
53
|
+
--secondary: oklch(0.269 0 0);
|
|
54
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
55
|
+
--muted: oklch(0.269 0 0);
|
|
56
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
57
|
+
--accent: oklch(0.269 0 0);
|
|
58
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
59
|
+
--destructive: oklch(0.396 0.141 25.723);
|
|
60
|
+
--destructive-foreground: oklch(0.637 0.237 25.331);
|
|
61
|
+
--border: oklch(0.269 0 0);
|
|
62
|
+
--input: oklch(0.269 0 0);
|
|
63
|
+
--ring: oklch(0.556 0 0);
|
|
64
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
65
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
66
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
67
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
68
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
69
|
+
--sidebar: oklch(0.205 0 0);
|
|
70
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
71
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
72
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
73
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
74
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
75
|
+
--sidebar-border: oklch(0.269 0 0);
|
|
76
|
+
--sidebar-ring: oklch(0.439 0 0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@theme inline {
|
|
80
|
+
--color-background: var(--background);
|
|
81
|
+
--color-foreground: var(--foreground);
|
|
82
|
+
--color-card: var(--card);
|
|
83
|
+
--color-card-foreground: var(--card-foreground);
|
|
84
|
+
--color-popover: var(--popover);
|
|
85
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
86
|
+
--color-primary: var(--primary);
|
|
87
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
88
|
+
--color-secondary: var(--secondary);
|
|
89
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
90
|
+
--color-muted: var(--muted);
|
|
91
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
92
|
+
--color-accent: var(--accent);
|
|
93
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
94
|
+
--color-destructive: var(--destructive);
|
|
95
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
96
|
+
--color-border: var(--border);
|
|
97
|
+
--color-input: var(--input);
|
|
98
|
+
--color-ring: var(--ring);
|
|
99
|
+
--color-chart-1: var(--chart-1);
|
|
100
|
+
--color-chart-2: var(--chart-2);
|
|
101
|
+
--color-chart-3: var(--chart-3);
|
|
102
|
+
--color-chart-4: var(--chart-4);
|
|
103
|
+
--color-chart-5: var(--chart-5);
|
|
104
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
105
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
106
|
+
--radius-lg: var(--radius);
|
|
107
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
108
|
+
--color-sidebar: var(--sidebar);
|
|
109
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
110
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
111
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
112
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
113
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
114
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
115
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@layer base {
|
|
119
|
+
* {
|
|
120
|
+
@apply border-border outline-ring/50;
|
|
121
|
+
}
|
|
122
|
+
body {
|
|
123
|
+
@apply bg-background text-foreground;
|
|
124
|
+
}
|
|
125
|
+
}
|