@lexibase/rdx-ui 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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'> & { 'data-rdx-code-mode'?: 'folder' | 'diff' }) {
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
- mode === 'folder' ? messages.folderTree : (language ?? messages.code)
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 === 'diff'
66
- ? Children.map(codeProps?.children, (node) => {
67
- if (
68
- !isValidElement<{
69
- children?: ReactNode
70
- 'data-rdx-line-variant'?: string
71
- }>(node)
72
- )
73
- return node
74
- const variant = node.props['data-rdx-line-variant']
75
- if (!variant) return node
76
- return (
77
- <CodeBlockLine
78
- variant={
79
- variant === 'added' || variant === 'removed'
80
- ? variant
81
- : variant === 'hunk'
82
- ? 'highlighted'
83
- : 'default'
84
- }
85
- >
86
- {variant === 'added' || variant === 'removed'
87
- ? withoutMarker(node.props.children)
88
- : node.props.children}
89
- </CodeBlockLine>
90
- )
91
- })
92
- : (codeProps?.children ?? children)
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}`]
@@ -105,10 +105,7 @@ export const mdxComponents = {
105
105
  />
106
106
  ),
107
107
  a: ({ className, children, ...props }: ComponentProps<'a'>) => (
108
- <a
109
- className={cn('underline text-chart-1 text-blue-500', className)}
110
- {...props}
111
- >
108
+ <a className={cn('underline text-rdx-highlighting', className)} {...props}>
112
109
  {linkedImages(children)}
113
110
  </a>
114
111
  ),
@@ -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,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
+ }
@@ -0,0 +1,146 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { Dialog as SheetPrimitive } from '@base-ui/react/dialog'
5
+ import { cn, mergeStateClassName } from '../lib/utils'
6
+ import { useMessages } from '../providers/locale-provider'
7
+
8
+ import { Button } from './button'
9
+ import { XIcon } from 'lucide-react'
10
+
11
+ function Sheet({ ...props }: SheetPrimitive.Root.Props) {
12
+ return <SheetPrimitive.Root data-slot="sheet" {...props} />
13
+ }
14
+
15
+ function SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {
16
+ return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
17
+ }
18
+
19
+ function SheetClose({ ...props }: SheetPrimitive.Close.Props) {
20
+ return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
21
+ }
22
+
23
+ function SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {
24
+ return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
25
+ }
26
+
27
+ function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
28
+ return (
29
+ <SheetPrimitive.Backdrop
30
+ data-slot="sheet-overlay"
31
+ className={mergeStateClassName(
32
+ 'fixed inset-0 z-50 bg-black/10 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs motion-reduce:transition-none',
33
+ className
34
+ )}
35
+ {...props}
36
+ />
37
+ )
38
+ }
39
+
40
+ function SheetContent({
41
+ className,
42
+ children,
43
+ side = 'right',
44
+ showCloseButton = true,
45
+ closeLabel,
46
+ portalProps,
47
+ ...props
48
+ }: SheetPrimitive.Popup.Props & {
49
+ side?: 'top' | 'right' | 'bottom' | 'left'
50
+ showCloseButton?: boolean
51
+ closeLabel?: string
52
+ portalProps?: SheetPrimitive.Portal.Props
53
+ }) {
54
+ const messages = useMessages()
55
+ return (
56
+ <SheetPortal {...portalProps}>
57
+ <SheetOverlay />
58
+ <SheetPrimitive.Popup
59
+ data-slot="sheet-content"
60
+ data-side={side}
61
+ className={mergeStateClassName(
62
+ 'fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-lg transition duration-200 ease-in-out data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm motion-reduce:transition-none',
63
+ className
64
+ )}
65
+ {...props}
66
+ >
67
+ {children}
68
+ {showCloseButton && (
69
+ <SheetPrimitive.Close
70
+ data-slot="sheet-close"
71
+ render={
72
+ <Button
73
+ variant="ghost"
74
+ className="absolute top-3 right-3"
75
+ size="icon-sm"
76
+ />
77
+ }
78
+ >
79
+ <XIcon aria-hidden="true" />
80
+ <span className="sr-only">{closeLabel ?? messages.close}</span>
81
+ </SheetPrimitive.Close>
82
+ )}
83
+ </SheetPrimitive.Popup>
84
+ </SheetPortal>
85
+ )
86
+ }
87
+
88
+ function SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {
89
+ return (
90
+ <div
91
+ data-slot="sheet-header"
92
+ className={cn('flex flex-col gap-0.5 p-4', className)}
93
+ {...props}
94
+ />
95
+ )
96
+ }
97
+
98
+ function SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {
99
+ return (
100
+ <div
101
+ data-slot="sheet-footer"
102
+ className={cn('mt-auto flex flex-col gap-2 p-4', className)}
103
+ {...props}
104
+ />
105
+ )
106
+ }
107
+
108
+ function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
109
+ return (
110
+ <SheetPrimitive.Title
111
+ data-slot="sheet-title"
112
+ className={mergeStateClassName(
113
+ 'cn-font-heading text-base font-medium text-foreground',
114
+ className
115
+ )}
116
+ {...props}
117
+ />
118
+ )
119
+ }
120
+
121
+ function SheetDescription({
122
+ className,
123
+ ...props
124
+ }: SheetPrimitive.Description.Props) {
125
+ return (
126
+ <SheetPrimitive.Description
127
+ data-slot="sheet-description"
128
+ className={mergeStateClassName(
129
+ 'text-sm text-muted-foreground',
130
+ className
131
+ )}
132
+ {...props}
133
+ />
134
+ )
135
+ }
136
+
137
+ export {
138
+ Sheet,
139
+ SheetTrigger,
140
+ SheetClose,
141
+ SheetContent,
142
+ SheetHeader,
143
+ SheetFooter,
144
+ SheetTitle,
145
+ SheetDescription,
146
+ }
@@ -144,6 +144,7 @@ function Tabs({ className, ...props }: TabsPrimitive.Root.Props) {
144
144
  }
145
145
 
146
146
  function TabsList({ className, ...props }: TabsPrimitive.List.Props) {
147
+ const messages = useMessages()
147
148
  return (
148
149
  <div
149
150
  data-slot="tabs-header"
@@ -151,6 +152,9 @@ function TabsList({ className, ...props }: TabsPrimitive.List.Props) {
151
152
  >
152
153
  <TabsPrimitive.List
153
154
  data-slot="tabs-list"
155
+ aria-label={
156
+ props['aria-labelledby'] ? undefined : messages.documentationTabs
157
+ }
154
158
  className={mergeStateClassName(
155
159
  'flex min-w-0 flex-1 gap-0.5 overflow-x-auto p-1.5 data-[orientation=vertical]:flex-col',
156
160
  className
@@ -167,7 +171,7 @@ function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
167
171
  <TabsPrimitive.Tab
168
172
  data-slot="tabs-trigger"
169
173
  className={mergeStateClassName(
170
- 'relative shrink-0 rounded-md border border-transparent px-2.5 py-1 text-sm font-medium whitespace-nowrap text-muted-foreground transition-colors hover:bg-muted/60 hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring data-active:border-border data-active:bg-background data-active:text-foreground data-active:shadow-xs data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:hover:bg-transparent data-disabled:hover:text-muted-foreground motion-reduce:transition-none',
174
+ 'relative shrink-0 rounded-md border border-transparent px-2.5 py-1 text-sm font-medium whitespace-nowrap text-muted-foreground transition-colors hover:bg-muted/60 hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring data-active:border-border data-active:bg-background data-active:text-rdx-selected data-active:shadow-xs data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:hover:bg-transparent data-disabled:hover:text-muted-foreground motion-reduce:transition-none',
171
175
  className
172
176
  )}
173
177
  {...props}
package/src/lib/i18n.ts CHANGED
@@ -32,15 +32,25 @@ const en = {
32
32
  'Press Escape or use the close button to return to the document.',
33
33
  copyCode: 'Copy code',
34
34
  wrapCode: 'Wrap lines',
35
+ showMore: 'Show more',
36
+ showLess: 'Show less',
35
37
  codeCopied: 'Code copied to clipboard.',
36
38
  copyCodeError: 'Could not copy. Select the code and copy it manually.',
37
39
  copyRetry: 'Could not copy. Try again or copy manually.',
38
40
  copied: 'Copied!',
39
41
  copyTab: 'Copy active tab',
42
+ documentationTabs: 'Documentation tabs',
40
43
  tabCopied: 'Tab content copied to clipboard.',
41
44
  copyTabError: 'Could not copy. Select the tab content and copy it manually.',
42
45
  code: 'Code',
43
46
  folderTree: 'Folder',
47
+ diagram: 'Diagram',
48
+ showDiagram: 'Show diagram',
49
+ showDiagramSource: 'Show diagram source',
50
+ enlargeDiagram: 'Enlarge diagram',
51
+ diagramLoading: 'Rendering diagram…',
52
+ diagramError:
53
+ 'Could not render this diagram. Check the Mermaid source below.',
44
54
  table: 'Table',
45
55
  current: 'Current',
46
56
  version: 'Version {version}',
@@ -101,17 +111,27 @@ const ptBR = {
101
111
  'Pressione Escape ou use o botão de fechar para voltar ao documento.',
102
112
  copyCode: 'Copiar código',
103
113
  wrapCode: 'Quebrar linhas',
114
+ showMore: 'Mostrar mais',
115
+ showLess: 'Mostrar menos',
104
116
  codeCopied: 'Código copiado para a área de transferência.',
105
117
  copyCodeError:
106
118
  'Não foi possível copiar. Selecione o código e copie manualmente.',
107
119
  copyRetry: 'Não foi possível copiar. Tente novamente ou copie manualmente.',
108
120
  copied: 'Copiado!',
109
121
  copyTab: 'Copiar aba ativa',
122
+ documentationTabs: 'Abas da documentação',
110
123
  tabCopied: 'Conteúdo da aba copiado para a área de transferência.',
111
124
  copyTabError:
112
125
  'Não foi possível copiar. Selecione o conteúdo da aba e copie manualmente.',
113
126
  code: 'Código',
114
127
  folderTree: 'Pastas',
128
+ diagram: 'Diagrama',
129
+ showDiagram: 'Mostrar diagrama',
130
+ showDiagramSource: 'Mostrar código do diagrama',
131
+ enlargeDiagram: 'Ampliar diagrama',
132
+ diagramLoading: 'Renderizando diagrama…',
133
+ diagramError:
134
+ 'Não foi possível renderizar o diagrama. Confira o código Mermaid abaixo.',
115
135
  table: 'Tabela',
116
136
  current: 'Atual',
117
137
  version: 'Versão {version}',