@lexibase/rdx-ui 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD_PARTY_NOTICES.md +3 -2
- package/package.json +4 -2
- package/src/components/docs-page-transition.tsx +178 -0
- package/src/components/docs-sidebar.tsx +105 -32
- package/src/components/docs-toc.tsx +6 -6
- package/src/components/kbd.tsx +8 -1
- package/src/components/mdx-code-block-view.tsx +133 -7
- package/src/components/mdx-code-block.tsx +83 -30
- package/src/components/mdx-components.tsx +6 -4
- package/src/components/mdx-folder-tree.tsx +58 -0
- package/src/components/mdx-keyboard-shortcut.tsx +74 -0
- package/src/components/mdx-mermaid.tsx +252 -0
- package/src/components/sheet.tsx +146 -0
- package/src/components/skip-link.tsx +10 -0
- package/src/components/tabs.tsx +6 -1
- package/src/lib/i18n.ts +48 -0
- package/src/lib/mermaid.ts +114 -0
- package/src/styles/globals.css +112 -14
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
import { getMessages } from '../lib/i18n'
|
|
10
10
|
import { CodeBlockLine } from './code-block'
|
|
11
11
|
import { MdxCodeBlockView } from './mdx-code-block-view'
|
|
12
|
+
import { MdxFolderTree } from './mdx-folder-tree'
|
|
13
|
+
import { MdxMermaid } from './mdx-mermaid'
|
|
12
14
|
|
|
13
15
|
function textOf(children: ReactNode): string {
|
|
14
16
|
return Children.toArray(children)
|
|
@@ -47,8 +49,16 @@ export function MdxCodeBlock({
|
|
|
47
49
|
lang,
|
|
48
50
|
children,
|
|
49
51
|
'data-rdx-code-mode': mode,
|
|
52
|
+
'data-rdx-code-title': title,
|
|
53
|
+
'data-rdx-code-line-numbers': numbered,
|
|
54
|
+
'data-rdx-code-start-line': firstLine,
|
|
50
55
|
...props
|
|
51
|
-
}: ComponentProps<'pre'> & {
|
|
56
|
+
}: ComponentProps<'pre'> & {
|
|
57
|
+
'data-rdx-code-mode'?: 'folder' | 'diff' | 'mermaid'
|
|
58
|
+
'data-rdx-code-title'?: string
|
|
59
|
+
'data-rdx-code-line-numbers'?: string
|
|
60
|
+
'data-rdx-code-start-line'?: string
|
|
61
|
+
}) {
|
|
52
62
|
const messages = getMessages(lang)
|
|
53
63
|
const code = Children.toArray(children).find((child) => isValidElement(child))
|
|
54
64
|
const codeProps = isValidElement<{
|
|
@@ -59,42 +69,85 @@ export function MdxCodeBlock({
|
|
|
59
69
|
: undefined
|
|
60
70
|
const language = codeProps?.className?.match(/(?:^|\s)language-([^\s]+)/)?.[1]
|
|
61
71
|
const value = textOf(codeProps?.children ?? children)
|
|
72
|
+
if (mode === 'mermaid' || language?.toLowerCase() === 'mermaid') {
|
|
73
|
+
return (
|
|
74
|
+
<MdxMermaid
|
|
75
|
+
value={value}
|
|
76
|
+
title={title}
|
|
77
|
+
lineNumbers={numbered === 'true'}
|
|
78
|
+
startLine={firstLine === undefined ? undefined : Number(firstLine)}
|
|
79
|
+
preProps={props}
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
62
83
|
const label =
|
|
63
|
-
|
|
84
|
+
title ||
|
|
85
|
+
(mode === 'folder' ? messages.folderTree : (language ?? messages.code))
|
|
86
|
+
const lineNumbers = numbered === 'true'
|
|
87
|
+
const raw = codeProps?.children ?? children
|
|
88
|
+
const nodes = Children.toArray(raw)
|
|
89
|
+
type LineProps = {
|
|
90
|
+
children?: ReactNode
|
|
91
|
+
className?: string
|
|
92
|
+
'data-rdx-line-variant'?: string
|
|
93
|
+
}
|
|
94
|
+
const lines = nodes.filter(
|
|
95
|
+
(node) =>
|
|
96
|
+
isValidElement<LineProps>(node) &&
|
|
97
|
+
(node.props['data-rdx-line-variant'] ||
|
|
98
|
+
node.props.className?.split(/\s+/).includes('line'))
|
|
99
|
+
)
|
|
100
|
+
const lastLine = lines.at(-1)
|
|
64
101
|
const content =
|
|
65
|
-
mode === '
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
102
|
+
mode === 'folder' ? (
|
|
103
|
+
<MdxFolderTree value={value} />
|
|
104
|
+
) : (mode === 'diff' || lineNumbers) && typeof raw !== 'string' ? (
|
|
105
|
+
nodes.map((node) => {
|
|
106
|
+
if (lineNumbers && node === '\n') return null
|
|
107
|
+
if (!isValidElement<LineProps>(node)) return node
|
|
108
|
+
const variant = node.props['data-rdx-line-variant']
|
|
109
|
+
if (
|
|
110
|
+
!variant &&
|
|
111
|
+
!(lineNumbers && node.props.className?.split(/\s+/).includes('line'))
|
|
112
|
+
)
|
|
113
|
+
return node
|
|
114
|
+
// A final newline terminates the last source line; intentional blank
|
|
115
|
+
// lines before it still receive the official component's own number.
|
|
116
|
+
if (
|
|
117
|
+
lineNumbers &&
|
|
118
|
+
lines.length > 1 &&
|
|
119
|
+
node === lastLine &&
|
|
120
|
+
value.endsWith('\n') &&
|
|
121
|
+
textOf(node.props.children) === ''
|
|
122
|
+
)
|
|
123
|
+
return null
|
|
124
|
+
return (
|
|
125
|
+
<CodeBlockLine
|
|
126
|
+
key={node.key}
|
|
127
|
+
variant={
|
|
128
|
+
variant === 'added' || variant === 'removed'
|
|
129
|
+
? variant
|
|
130
|
+
: variant === 'hunk'
|
|
131
|
+
? 'highlighted'
|
|
132
|
+
: 'default'
|
|
133
|
+
}
|
|
134
|
+
>
|
|
135
|
+
{variant === 'added' || variant === 'removed'
|
|
136
|
+
? withoutMarker(node.props.children)
|
|
137
|
+
: node.props.children}
|
|
138
|
+
</CodeBlockLine>
|
|
139
|
+
)
|
|
140
|
+
})
|
|
141
|
+
) : (
|
|
142
|
+
raw
|
|
143
|
+
)
|
|
93
144
|
return (
|
|
94
145
|
<MdxCodeBlockView
|
|
95
146
|
label={label}
|
|
96
147
|
mode={mode}
|
|
97
148
|
value={value}
|
|
149
|
+
lineNumbers={lineNumbers}
|
|
150
|
+
startLine={firstLine === undefined ? undefined : Number(firstLine)}
|
|
98
151
|
preProps={{
|
|
99
152
|
...props,
|
|
100
153
|
className: [props.className, language && `language-${language}`]
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
} from './code-block'
|
|
30
30
|
import { MdxCodeBlock } from './mdx-code-block'
|
|
31
31
|
import { Kbd, KbdGroup } from './kbd'
|
|
32
|
+
import { MdxKeyboardShortcut } from './mdx-keyboard-shortcut'
|
|
32
33
|
import {
|
|
33
34
|
Table,
|
|
34
35
|
TableBody,
|
|
@@ -105,10 +106,7 @@ export const mdxComponents = {
|
|
|
105
106
|
/>
|
|
106
107
|
),
|
|
107
108
|
a: ({ className, children, ...props }: ComponentProps<'a'>) => (
|
|
108
|
-
<a
|
|
109
|
-
className={cn('underline text-chart-1 text-blue-500', className)}
|
|
110
|
-
{...props}
|
|
111
|
-
>
|
|
109
|
+
<a className={cn('underline text-rdx-highlighting', className)} {...props}>
|
|
112
110
|
{linkedImages(children)}
|
|
113
111
|
</a>
|
|
114
112
|
),
|
|
@@ -157,6 +155,7 @@ export const mdxComponents = {
|
|
|
157
155
|
CodeBlockTitle,
|
|
158
156
|
Kbd,
|
|
159
157
|
KbdGroup,
|
|
158
|
+
KeyboardShortcut: MdxKeyboardShortcut,
|
|
160
159
|
Table,
|
|
161
160
|
TableHeader,
|
|
162
161
|
TableBody,
|
|
@@ -190,6 +189,9 @@ export const mdxComponents = {
|
|
|
190
189
|
export function getMdxComponents(lang?: string) {
|
|
191
190
|
return {
|
|
192
191
|
...mdxComponents,
|
|
192
|
+
KeyboardShortcut: (props: { children?: ReactNode }) => (
|
|
193
|
+
<MdxKeyboardShortcut {...props} lang={lang} />
|
|
194
|
+
),
|
|
193
195
|
pre: (props: ComponentProps<'pre'>) => (
|
|
194
196
|
<MdxCodeBlock {...props} lang={lang} />
|
|
195
197
|
),
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {
|
|
2
|
+
File,
|
|
3
|
+
FileCode2,
|
|
4
|
+
FileImage,
|
|
5
|
+
FileJson,
|
|
6
|
+
FileText,
|
|
7
|
+
Folder,
|
|
8
|
+
} from 'lucide-react'
|
|
9
|
+
|
|
10
|
+
import { CodeBlockLine } from './code-block'
|
|
11
|
+
|
|
12
|
+
function entryIcon(name: string) {
|
|
13
|
+
if (name.endsWith('/')) return { Icon: Folder, kind: 'folder' }
|
|
14
|
+
const extension = name.includes('.')
|
|
15
|
+
? name.split('.').at(-1)?.toLowerCase()
|
|
16
|
+
: undefined
|
|
17
|
+
if (extension === 'json' || extension === 'jsonc')
|
|
18
|
+
return { Icon: FileJson, kind: 'json' }
|
|
19
|
+
if (extension && /^(?:png|jpe?g|gif|webp|svg|avif|ico|bmp)$/.test(extension))
|
|
20
|
+
return { Icon: FileImage, kind: 'image' }
|
|
21
|
+
if (extension && /^(?:mdx?|txt|log|rst)$/.test(extension))
|
|
22
|
+
return { Icon: FileText, kind: 'text' }
|
|
23
|
+
if (
|
|
24
|
+
extension &&
|
|
25
|
+
/^(?:[cm]?[jt]sx?|html|css|scss|vue|svelte|py|rb|go|rs|java|c|cpp|h|cs|php|sql|sh|bash|ya?ml|toml)$/.test(
|
|
26
|
+
extension
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
return { Icon: FileCode2, kind: 'code' }
|
|
30
|
+
return { Icon: File, kind: 'file' }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Decorate generated text with icons inside the official shadcn line component. */
|
|
34
|
+
export function MdxFolderTree({ value }: { value: string }) {
|
|
35
|
+
const trailingNewline = value.endsWith('\n')
|
|
36
|
+
const lines = value.replace(/\n$/, '').split('\n')
|
|
37
|
+
return lines.map((line, index) => {
|
|
38
|
+
const match = line.match(/^([ │\t]*(?:├──|└──)\s*)(.*)$/)
|
|
39
|
+
const prefix = match?.[1] ?? line.match(/^[ │\t]*/)?.[0] ?? ''
|
|
40
|
+
const name = match?.[2] ?? line.slice(prefix.length)
|
|
41
|
+
const { Icon, kind } = entryIcon(name)
|
|
42
|
+
return (
|
|
43
|
+
<CodeBlockLine key={index} data-rdx-folder-kind={name ? kind : undefined}>
|
|
44
|
+
{prefix && <span aria-hidden="true">{prefix}</span>}
|
|
45
|
+
{name && (
|
|
46
|
+
<>
|
|
47
|
+
<Icon
|
|
48
|
+
aria-hidden="true"
|
|
49
|
+
className="mr-1.5 inline-block size-3.5 align-[-2px] text-muted-foreground"
|
|
50
|
+
/>
|
|
51
|
+
{name}
|
|
52
|
+
</>
|
|
53
|
+
)}
|
|
54
|
+
{index < lines.length - 1 || trailingNewline ? '\n' : null}
|
|
55
|
+
</CodeBlockLine>
|
|
56
|
+
)
|
|
57
|
+
})
|
|
58
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
import { getMessages, type InterfaceMessages } from '../lib/i18n'
|
|
4
|
+
import { Kbd, KbdGroup } from './kbd'
|
|
5
|
+
|
|
6
|
+
const keyNames: Record<string, keyof InterfaceMessages> = {
|
|
7
|
+
ctrl: 'keyboardControl',
|
|
8
|
+
control: 'keyboardControl',
|
|
9
|
+
'⌃': 'keyboardControl',
|
|
10
|
+
shift: 'keyboardShift',
|
|
11
|
+
'⇧': 'keyboardShift',
|
|
12
|
+
alt: 'keyboardAlt',
|
|
13
|
+
cmd: 'keyboardCommand',
|
|
14
|
+
command: 'keyboardCommand',
|
|
15
|
+
'⌘': 'keyboardCommand',
|
|
16
|
+
option: 'keyboardOption',
|
|
17
|
+
'⌥': 'keyboardOption',
|
|
18
|
+
esc: 'keyboardEscape',
|
|
19
|
+
escape: 'keyboardEscape',
|
|
20
|
+
'⎋': 'keyboardEscape',
|
|
21
|
+
enter: 'keyboardEnter',
|
|
22
|
+
return: 'keyboardEnter',
|
|
23
|
+
'↵': 'keyboardEnter',
|
|
24
|
+
'⏎': 'keyboardEnter',
|
|
25
|
+
space: 'keyboardSpace',
|
|
26
|
+
spacebar: 'keyboardSpace',
|
|
27
|
+
'+': 'keyboardPlus',
|
|
28
|
+
arrowup: 'keyboardArrowUp',
|
|
29
|
+
'↑': 'keyboardArrowUp',
|
|
30
|
+
arrowdown: 'keyboardArrowDown',
|
|
31
|
+
'↓': 'keyboardArrowDown',
|
|
32
|
+
arrowleft: 'keyboardArrowLeft',
|
|
33
|
+
'←': 'keyboardArrowLeft',
|
|
34
|
+
arrowright: 'keyboardArrowRight',
|
|
35
|
+
'→': 'keyboardArrowRight',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Markdown adapter: preserve shadcn visuals, expose one readable text alternative. */
|
|
39
|
+
export function MdxKeyboardShortcut({
|
|
40
|
+
children,
|
|
41
|
+
lang,
|
|
42
|
+
}: {
|
|
43
|
+
children?: ReactNode
|
|
44
|
+
lang?: string
|
|
45
|
+
}) {
|
|
46
|
+
if (typeof children !== 'string') return <Kbd>{children}</Kbd>
|
|
47
|
+
const keys = children
|
|
48
|
+
.trim()
|
|
49
|
+
.split(/\s+\+\s+/)
|
|
50
|
+
.map((key) => key.trim())
|
|
51
|
+
if (keys.length < 2 || keys.some((key) => !key)) return <Kbd>{children}</Kbd>
|
|
52
|
+
const messages = getMessages(lang)
|
|
53
|
+
const description = keys
|
|
54
|
+
.map((key) => {
|
|
55
|
+
const name = Object.hasOwn(keyNames, key.toLowerCase())
|
|
56
|
+
? keyNames[key.toLowerCase()]
|
|
57
|
+
: undefined
|
|
58
|
+
return name ? messages[name] : key
|
|
59
|
+
})
|
|
60
|
+
.join(messages.keyboardJoiner)
|
|
61
|
+
return (
|
|
62
|
+
<span data-rdx-kbd-value={children} className="inline-flex align-baseline">
|
|
63
|
+
<span className="sr-only select-none">{description}</span>
|
|
64
|
+
<KbdGroup aria-hidden="true">
|
|
65
|
+
{keys.map((key, index) => (
|
|
66
|
+
<span key={index} className="inline-flex items-center gap-1">
|
|
67
|
+
{index > 0 && <span>+</span>}
|
|
68
|
+
<Kbd>{key}</Kbd>
|
|
69
|
+
</span>
|
|
70
|
+
))}
|
|
71
|
+
</KbdGroup>
|
|
72
|
+
</span>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
type ComponentProps,
|
|
9
|
+
} from 'react'
|
|
10
|
+
import { useTheme } from 'next-themes'
|
|
11
|
+
import {
|
|
12
|
+
CodeXml,
|
|
13
|
+
Eye,
|
|
14
|
+
Maximize2,
|
|
15
|
+
RotateCw,
|
|
16
|
+
Workflow,
|
|
17
|
+
WrapText,
|
|
18
|
+
} from 'lucide-react'
|
|
19
|
+
|
|
20
|
+
import { renderMermaid } from '../lib/mermaid'
|
|
21
|
+
import { useMessages } from '../providers/locale-provider'
|
|
22
|
+
import { Button } from './button'
|
|
23
|
+
import {
|
|
24
|
+
CodeBlock,
|
|
25
|
+
CodeBlockActions,
|
|
26
|
+
CodeBlockContent,
|
|
27
|
+
CodeBlockCopyButton,
|
|
28
|
+
CodeBlockHeader,
|
|
29
|
+
CodeBlockTitle,
|
|
30
|
+
} from './code-block'
|
|
31
|
+
import {
|
|
32
|
+
Dialog,
|
|
33
|
+
DialogContent,
|
|
34
|
+
DialogDescription,
|
|
35
|
+
DialogTitle,
|
|
36
|
+
DialogTrigger,
|
|
37
|
+
} from './dialog'
|
|
38
|
+
|
|
39
|
+
/** A Mermaid fence adapts the existing shadcn Code Block and Dialog. */
|
|
40
|
+
export function MdxMermaid({
|
|
41
|
+
value,
|
|
42
|
+
title,
|
|
43
|
+
lineNumbers,
|
|
44
|
+
startLine,
|
|
45
|
+
preProps,
|
|
46
|
+
}: {
|
|
47
|
+
value: string
|
|
48
|
+
title?: string
|
|
49
|
+
lineNumbers?: boolean
|
|
50
|
+
startLine?: number
|
|
51
|
+
preProps: ComponentProps<'pre'>
|
|
52
|
+
}) {
|
|
53
|
+
const messages = useMessages()
|
|
54
|
+
const { resolvedTheme } = useTheme()
|
|
55
|
+
const block = useRef<HTMLDivElement>(null)
|
|
56
|
+
const [visible, setVisible] = useState(false)
|
|
57
|
+
const [sourceView, setSourceView] = useState(false)
|
|
58
|
+
const [wrap, setWrap] = useState(false)
|
|
59
|
+
const [attempt, setAttempt] = useState(0)
|
|
60
|
+
const [result, setResult] = useState<{
|
|
61
|
+
source: string
|
|
62
|
+
theme: string
|
|
63
|
+
svg?: string
|
|
64
|
+
error?: boolean
|
|
65
|
+
}>()
|
|
66
|
+
const current =
|
|
67
|
+
result?.source === value && result.theme === resolvedTheme
|
|
68
|
+
? result
|
|
69
|
+
: undefined
|
|
70
|
+
const svg = current?.svg
|
|
71
|
+
const error = current?.error
|
|
72
|
+
const image = svg
|
|
73
|
+
? `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`
|
|
74
|
+
: undefined
|
|
75
|
+
const description = useMemo(() => {
|
|
76
|
+
if (!svg) return title || messages.diagram
|
|
77
|
+
const document = new DOMParser().parseFromString(svg, 'image/svg+xml')
|
|
78
|
+
return [
|
|
79
|
+
title || document.querySelector('title')?.textContent || messages.diagram,
|
|
80
|
+
document.querySelector('desc')?.textContent,
|
|
81
|
+
]
|
|
82
|
+
.filter(Boolean)
|
|
83
|
+
.join('. ')
|
|
84
|
+
}, [svg, title, messages.diagram])
|
|
85
|
+
const label = title || 'Mermaid'
|
|
86
|
+
const showSource = sourceView || !svg
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
const element = block.current
|
|
90
|
+
if (!element) return
|
|
91
|
+
const observer = new IntersectionObserver(
|
|
92
|
+
(entries) => {
|
|
93
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
94
|
+
setVisible(true)
|
|
95
|
+
observer.disconnect()
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{ rootMargin: '200px' }
|
|
99
|
+
)
|
|
100
|
+
observer.observe(element)
|
|
101
|
+
return () => observer.disconnect()
|
|
102
|
+
}, [])
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (!visible || !resolvedTheme) return
|
|
106
|
+
let active = true
|
|
107
|
+
const theme = resolvedTheme
|
|
108
|
+
void renderMermaid(value, theme === 'dark', () => active).then(
|
|
109
|
+
(svg) => {
|
|
110
|
+
if (active && svg) setResult({ source: value, theme, svg })
|
|
111
|
+
},
|
|
112
|
+
() => {
|
|
113
|
+
if (active) setResult({ source: value, theme, error: true })
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
return () => {
|
|
117
|
+
active = false
|
|
118
|
+
}
|
|
119
|
+
}, [value, resolvedTheme, visible, attempt])
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<CodeBlock
|
|
123
|
+
ref={block}
|
|
124
|
+
size="sm"
|
|
125
|
+
data-rdx-code-block
|
|
126
|
+
data-rdx-code-mode="mermaid"
|
|
127
|
+
data-rdx-code-value={value}
|
|
128
|
+
className="my-4 max-w-full"
|
|
129
|
+
>
|
|
130
|
+
<CodeBlockHeader>
|
|
131
|
+
<CodeBlockTitle title={label}>
|
|
132
|
+
<Workflow aria-hidden="true" /> {label}
|
|
133
|
+
</CodeBlockTitle>
|
|
134
|
+
<CodeBlockActions>
|
|
135
|
+
{svg && (
|
|
136
|
+
<Button
|
|
137
|
+
variant="ghost"
|
|
138
|
+
size="icon-xs"
|
|
139
|
+
aria-label={
|
|
140
|
+
sourceView ? messages.showDiagram : messages.showDiagramSource
|
|
141
|
+
}
|
|
142
|
+
title={
|
|
143
|
+
sourceView ? messages.showDiagram : messages.showDiagramSource
|
|
144
|
+
}
|
|
145
|
+
aria-pressed={sourceView}
|
|
146
|
+
onClick={() => setSourceView(!sourceView)}
|
|
147
|
+
>
|
|
148
|
+
{sourceView ? (
|
|
149
|
+
<Eye aria-hidden="true" />
|
|
150
|
+
) : (
|
|
151
|
+
<CodeXml aria-hidden="true" />
|
|
152
|
+
)}
|
|
153
|
+
</Button>
|
|
154
|
+
)}
|
|
155
|
+
{image && (
|
|
156
|
+
<Dialog>
|
|
157
|
+
<DialogTrigger
|
|
158
|
+
render={<Button variant="ghost" size="icon-xs" />}
|
|
159
|
+
aria-label={messages.enlargeDiagram}
|
|
160
|
+
title={messages.enlargeDiagram}
|
|
161
|
+
>
|
|
162
|
+
<Maximize2 aria-hidden="true" />
|
|
163
|
+
</DialogTrigger>
|
|
164
|
+
<DialogContent
|
|
165
|
+
data-rdx-mermaid-dialog
|
|
166
|
+
className="flex max-h-[calc(100dvh-2rem)] w-[calc(100vw-2rem)] max-w-6xl flex-col gap-2 sm:max-w-6xl"
|
|
167
|
+
>
|
|
168
|
+
<DialogTitle className="pr-8">
|
|
169
|
+
{title || messages.diagram}
|
|
170
|
+
</DialogTitle>
|
|
171
|
+
<DialogDescription className="sr-only">
|
|
172
|
+
{messages.imagePreviewDescription}
|
|
173
|
+
</DialogDescription>
|
|
174
|
+
<div
|
|
175
|
+
role="region"
|
|
176
|
+
aria-label={messages.diagram}
|
|
177
|
+
tabIndex={0}
|
|
178
|
+
className="min-h-0 overflow-auto rounded-md p-2 outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
179
|
+
>
|
|
180
|
+
<img
|
|
181
|
+
src={image}
|
|
182
|
+
alt={description}
|
|
183
|
+
className="mx-auto h-auto max-w-none"
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
</DialogContent>
|
|
187
|
+
</Dialog>
|
|
188
|
+
)}
|
|
189
|
+
{error && (
|
|
190
|
+
<Button
|
|
191
|
+
variant="ghost"
|
|
192
|
+
size="icon-xs"
|
|
193
|
+
aria-label={messages.tryAgain}
|
|
194
|
+
title={messages.tryAgain}
|
|
195
|
+
onClick={() => setAttempt(attempt + 1)}
|
|
196
|
+
>
|
|
197
|
+
<RotateCw aria-hidden="true" />
|
|
198
|
+
</Button>
|
|
199
|
+
)}
|
|
200
|
+
{showSource && (
|
|
201
|
+
<Button
|
|
202
|
+
variant="ghost"
|
|
203
|
+
size="icon-xs"
|
|
204
|
+
className="md:hidden"
|
|
205
|
+
aria-label={messages.wrapCode}
|
|
206
|
+
title={messages.wrapCode}
|
|
207
|
+
aria-pressed={wrap}
|
|
208
|
+
onClick={() => setWrap(!wrap)}
|
|
209
|
+
>
|
|
210
|
+
<WrapText aria-hidden="true" />
|
|
211
|
+
</Button>
|
|
212
|
+
)}
|
|
213
|
+
<CodeBlockCopyButton value={value} />
|
|
214
|
+
</CodeBlockActions>
|
|
215
|
+
</CodeBlockHeader>
|
|
216
|
+
{error && (
|
|
217
|
+
<p role="status" className="px-3 pt-2 text-xs text-muted-foreground">
|
|
218
|
+
{messages.diagramError}
|
|
219
|
+
</p>
|
|
220
|
+
)}
|
|
221
|
+
{!current && visible && (
|
|
222
|
+
<p role="status" className="px-3 pt-2 text-xs text-muted-foreground">
|
|
223
|
+
{messages.diagramLoading}
|
|
224
|
+
</p>
|
|
225
|
+
)}
|
|
226
|
+
{showSource ? (
|
|
227
|
+
<CodeBlockContent
|
|
228
|
+
{...preProps}
|
|
229
|
+
wrap={wrap}
|
|
230
|
+
lineNumbers={lineNumbers}
|
|
231
|
+
startLine={startLine}
|
|
232
|
+
>
|
|
233
|
+
{value}
|
|
234
|
+
</CodeBlockContent>
|
|
235
|
+
) : (
|
|
236
|
+
<div
|
|
237
|
+
role="region"
|
|
238
|
+
aria-label={title || messages.diagram}
|
|
239
|
+
tabIndex={0}
|
|
240
|
+
data-rdx-mermaid-preview
|
|
241
|
+
className="min-w-0 overflow-auto p-4 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
|
|
242
|
+
>
|
|
243
|
+
<img
|
|
244
|
+
src={image}
|
|
245
|
+
alt={description}
|
|
246
|
+
className="mx-auto h-auto max-w-full"
|
|
247
|
+
/>
|
|
248
|
+
</div>
|
|
249
|
+
)}
|
|
250
|
+
</CodeBlock>
|
|
251
|
+
)
|
|
252
|
+
}
|