@lexibase/rdx-ui 3.3.0 → 3.5.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.
Files changed (123) hide show
  1. package/README.md +6 -0
  2. package/THIRD_PARTY_NOTICES.md +7 -0
  3. package/dist/mermaid/LICENSE +21 -0
  4. package/dist/mermaid/chunks/mermaid.esm.min/abnfDiagram-HYZUXDRY.mjs +1 -0
  5. package/dist/mermaid/chunks/mermaid.esm.min/architecture-WOLXFQ4H-GHTOFUQ5.mjs +1 -0
  6. package/dist/mermaid/chunks/mermaid.esm.min/architectureDiagram-FWCGMENZ.mjs +36 -0
  7. package/dist/mermaid/chunks/mermaid.esm.min/blockDiagram-4CSTLEIA.mjs +140 -0
  8. package/dist/mermaid/chunks/mermaid.esm.min/c4Diagram-XZWFQOWE.mjs +38 -0
  9. package/dist/mermaid/chunks/mermaid.esm.min/chunk-3KU6CYTB.mjs +1 -0
  10. package/dist/mermaid/chunks/mermaid.esm.min/chunk-4GHFHL4L.mjs +1 -0
  11. package/dist/mermaid/chunks/mermaid.esm.min/chunk-5EYK72AM.mjs +1 -0
  12. package/dist/mermaid/chunks/mermaid.esm.min/chunk-5OZTHVBM.mjs +1 -0
  13. package/dist/mermaid/chunks/mermaid.esm.min/chunk-674HQBI4.mjs +1 -0
  14. package/dist/mermaid/chunks/mermaid.esm.min/chunk-7P5HNUOW.mjs +213 -0
  15. package/dist/mermaid/chunks/mermaid.esm.min/chunk-A2CVJI47.mjs +1 -0
  16. package/dist/mermaid/chunks/mermaid.esm.min/chunk-ADJWZCCI.mjs +2 -0
  17. package/dist/mermaid/chunks/mermaid.esm.min/chunk-AJJEVQBW.mjs +1 -0
  18. package/dist/mermaid/chunks/mermaid.esm.min/chunk-BOZ75642.mjs +15 -0
  19. package/dist/mermaid/chunks/mermaid.esm.min/chunk-C5JO3OR4.mjs +1 -0
  20. package/dist/mermaid/chunks/mermaid.esm.min/chunk-CJ6WPRXO.mjs +2 -0
  21. package/dist/mermaid/chunks/mermaid.esm.min/chunk-D36F7FYG.mjs +14 -0
  22. package/dist/mermaid/chunks/mermaid.esm.min/chunk-DFAUXGQ3.mjs +1 -0
  23. package/dist/mermaid/chunks/mermaid.esm.min/chunk-DXAG4NAJ.mjs +1 -0
  24. package/dist/mermaid/chunks/mermaid.esm.min/chunk-EW3V7WDR.mjs +1 -0
  25. package/dist/mermaid/chunks/mermaid.esm.min/chunk-F6PIGZHG.mjs +1 -0
  26. package/dist/mermaid/chunks/mermaid.esm.min/chunk-FNR2RGIF.mjs +1 -0
  27. package/dist/mermaid/chunks/mermaid.esm.min/chunk-FWWDAFD2.mjs +134 -0
  28. package/dist/mermaid/chunks/mermaid.esm.min/chunk-GDOATIN3.mjs +32 -0
  29. package/dist/mermaid/chunks/mermaid.esm.min/chunk-GDTRMLOK.mjs +1 -0
  30. package/dist/mermaid/chunks/mermaid.esm.min/chunk-GHI2JMQX.mjs +2 -0
  31. package/dist/mermaid/chunks/mermaid.esm.min/chunk-H2SWLY5Y.mjs +3 -0
  32. package/dist/mermaid/chunks/mermaid.esm.min/chunk-IER4RZHT.mjs +1 -0
  33. package/dist/mermaid/chunks/mermaid.esm.min/chunk-IZVUBGS4.mjs +88 -0
  34. package/dist/mermaid/chunks/mermaid.esm.min/chunk-JOOTFCOX.mjs +1 -0
  35. package/dist/mermaid/chunks/mermaid.esm.min/chunk-KJ5T2YZX.mjs +62 -0
  36. package/dist/mermaid/chunks/mermaid.esm.min/chunk-KTS5TP6K.mjs +1 -0
  37. package/dist/mermaid/chunks/mermaid.esm.min/chunk-L2B47AAZ.mjs +1 -0
  38. package/dist/mermaid/chunks/mermaid.esm.min/chunk-LFUUSPDU.mjs +2 -0
  39. package/dist/mermaid/chunks/mermaid.esm.min/chunk-LGKD3V67.mjs +1 -0
  40. package/dist/mermaid/chunks/mermaid.esm.min/chunk-MFYRHTYG.mjs +1 -0
  41. package/dist/mermaid/chunks/mermaid.esm.min/chunk-MIE3LDYP.mjs +1 -0
  42. package/dist/mermaid/chunks/mermaid.esm.min/chunk-MMC4O45A.mjs +1 -0
  43. package/dist/mermaid/chunks/mermaid.esm.min/chunk-PKJCTQEK.mjs +339 -0
  44. package/dist/mermaid/chunks/mermaid.esm.min/chunk-QVIEFLGF.mjs +1 -0
  45. package/dist/mermaid/chunks/mermaid.esm.min/chunk-SDIBIOXT.mjs +1 -0
  46. package/dist/mermaid/chunks/mermaid.esm.min/chunk-TRWJJ4LR.mjs +1 -0
  47. package/dist/mermaid/chunks/mermaid.esm.min/chunk-USJJLVIZ.mjs +1 -0
  48. package/dist/mermaid/chunks/mermaid.esm.min/chunk-V3KJR4XU.mjs +1 -0
  49. package/dist/mermaid/chunks/mermaid.esm.min/chunk-WLHZKB66.mjs +136 -0
  50. package/dist/mermaid/chunks/mermaid.esm.min/chunk-XGFVAD2T.mjs +70 -0
  51. package/dist/mermaid/chunks/mermaid.esm.min/chunk-XOUW2DEQ.mjs +1 -0
  52. package/dist/mermaid/chunks/mermaid.esm.min/chunk-XS6APLAC.mjs +1 -0
  53. package/dist/mermaid/chunks/mermaid.esm.min/chunk-XUG7IRDC.mjs +10 -0
  54. package/dist/mermaid/chunks/mermaid.esm.min/chunk-YX6CVSZ3.mjs +1 -0
  55. package/dist/mermaid/chunks/mermaid.esm.min/chunk-ZZ52EIBP.mjs +1 -0
  56. package/dist/mermaid/chunks/mermaid.esm.min/classDiagram-v2-Y5FV6ONJ.mjs +217 -0
  57. package/dist/mermaid/chunks/mermaid.esm.min/cose-bilkent-MSIMRU2B.mjs +1 -0
  58. package/dist/mermaid/chunks/mermaid.esm.min/cynefin-EF2NZ3EQ-R54WXN27.mjs +1 -0
  59. package/dist/mermaid/chunks/mermaid.esm.min/cynefinDiagram-YJSODOYC.mjs +62 -0
  60. package/dist/mermaid/chunks/mermaid.esm.min/dagre-JKPHJXCU.mjs +4 -0
  61. package/dist/mermaid/chunks/mermaid.esm.min/diagram-2HKZALZE.mjs +3 -0
  62. package/dist/mermaid/chunks/mermaid.esm.min/diagram-HHHQHE5F.mjs +24 -0
  63. package/dist/mermaid/chunks/mermaid.esm.min/diagram-HPYKNB4U.mjs +200 -0
  64. package/dist/mermaid/chunks/mermaid.esm.min/diagram-MZWFMCCE.mjs +30 -0
  65. package/dist/mermaid/chunks/mermaid.esm.min/diagram-PVSZJELT.mjs +24 -0
  66. package/dist/mermaid/chunks/mermaid.esm.min/diagram-X6UQTMWD.mjs +41 -0
  67. package/dist/mermaid/chunks/mermaid.esm.min/ebnfDiagram-6GPSJLNQ.mjs +1 -0
  68. package/dist/mermaid/chunks/mermaid.esm.min/elk-7HBNN7NY.mjs +27 -0
  69. package/dist/mermaid/chunks/mermaid.esm.min/erDiagram-Y4LBXKHG.mjs +99 -0
  70. package/dist/mermaid/chunks/mermaid.esm.min/eventmodeling-K75KTNOO-5X7W3UBU.mjs +1 -0
  71. package/dist/mermaid/chunks/mermaid.esm.min/flowDiagram-KMDKLPNL.mjs +1 -0
  72. package/dist/mermaid/chunks/mermaid.esm.min/ganttDiagram-76A4JV4W.mjs +292 -0
  73. package/dist/mermaid/chunks/mermaid.esm.min/gitGraph-VSP46ZUC-FQK3UU6A.mjs +1 -0
  74. package/dist/mermaid/chunks/mermaid.esm.min/gitGraphDiagram-EVPX2XFH.mjs +106 -0
  75. package/dist/mermaid/chunks/mermaid.esm.min/info-OHQRW6UA-QF2V7E2T.mjs +1 -0
  76. package/dist/mermaid/chunks/mermaid.esm.min/infoDiagram-G4MRDF25.mjs +2 -0
  77. package/dist/mermaid/chunks/mermaid.esm.min/ishikawaDiagram-UBNKZSLF.mjs +70 -0
  78. package/dist/mermaid/chunks/mermaid.esm.min/journeyDiagram-5CPKFXFQ.mjs +139 -0
  79. package/dist/mermaid/chunks/mermaid.esm.min/kanban-definition-WEZKRRYB.mjs +89 -0
  80. package/dist/mermaid/chunks/mermaid.esm.min/katex-AYS4IGT2.mjs +257 -0
  81. package/dist/mermaid/chunks/mermaid.esm.min/mindmap-definition-O75EZIMS.mjs +98 -0
  82. package/dist/mermaid/chunks/mermaid.esm.min/packet-JDAUHWVQ-IBKN7U47.mjs +1 -0
  83. package/dist/mermaid/chunks/mermaid.esm.min/pegDiagram-LKIDSOEG.mjs +1 -0
  84. package/dist/mermaid/chunks/mermaid.esm.min/pie-XZMESJXO-Q2BLAFAQ.mjs +1 -0
  85. package/dist/mermaid/chunks/mermaid.esm.min/pieDiagram-7BIPEFMD.mjs +39 -0
  86. package/dist/mermaid/chunks/mermaid.esm.min/quadrantDiagram-IHCSLFLC.mjs +7 -0
  87. package/dist/mermaid/chunks/mermaid.esm.min/radar-ABXABTNO-DQ2FIE3D.mjs +1 -0
  88. package/dist/mermaid/chunks/mermaid.esm.min/railroad-I3PHUGI6-IFWMPZK5.mjs +1 -0
  89. package/dist/mermaid/chunks/mermaid.esm.min/railroad-abnf-LNEFI6M7-5TBVSGVF.mjs +1 -0
  90. package/dist/mermaid/chunks/mermaid.esm.min/railroad-ebnf-DWYD2UWJ-IQAPAQTS.mjs +1 -0
  91. package/dist/mermaid/chunks/mermaid.esm.min/railroad-peg-JR7BVNR7-7XRGHPYN.mjs +1 -0
  92. package/dist/mermaid/chunks/mermaid.esm.min/railroadDiagram-T6ZQNPER.mjs +1 -0
  93. package/dist/mermaid/chunks/mermaid.esm.min/requirementDiagram-NFODNGNY.mjs +84 -0
  94. package/dist/mermaid/chunks/mermaid.esm.min/sankeyDiagram-LAUYGPPA.mjs +40 -0
  95. package/dist/mermaid/chunks/mermaid.esm.min/sequenceDiagram-OV7DSCPC.mjs +169 -0
  96. package/dist/mermaid/chunks/mermaid.esm.min/sizeCapture-ERV5D3W5.mjs +1 -0
  97. package/dist/mermaid/chunks/mermaid.esm.min/stateDiagram-v2-PVXPCO6V.mjs +291 -0
  98. package/dist/mermaid/chunks/mermaid.esm.min/swimlanes-2OY6MWR5.mjs +1 -0
  99. package/dist/mermaid/chunks/mermaid.esm.min/swimlanesDiagram-OROSK42J.mjs +8 -0
  100. package/dist/mermaid/chunks/mermaid.esm.min/timeline-definition-W2ENXKF5.mjs +120 -0
  101. package/dist/mermaid/chunks/mermaid.esm.min/treeView-D4JQ5SDB-G2RRDZEV.mjs +1 -0
  102. package/dist/mermaid/chunks/mermaid.esm.min/treemap-SAJKECNS-GRWGXJWJ.mjs +1 -0
  103. package/dist/mermaid/chunks/mermaid.esm.min/usecaseDiagram-WGGL4CJY.mjs +370 -0
  104. package/dist/mermaid/chunks/mermaid.esm.min/vennDiagram-REWWOBY3.mjs +34 -0
  105. package/dist/mermaid/chunks/mermaid.esm.min/wardley-7MLQ67FV-4I27TEHZ.mjs +1 -0
  106. package/dist/mermaid/chunks/mermaid.esm.min/wardleyDiagram-3VXUCKTG.mjs +78 -0
  107. package/dist/mermaid/chunks/mermaid.esm.min/xychartDiagram-BH4T3JT5.mjs +7 -0
  108. package/dist/mermaid/mermaid.esm.min.d.mts +5 -0
  109. package/dist/mermaid/mermaid.esm.min.mjs +14 -0
  110. package/package.json +8 -3
  111. package/src/components/brand-logo.tsx +28 -0
  112. package/src/components/button-group.tsx +87 -0
  113. package/src/components/card.tsx +102 -0
  114. package/src/components/copy-page.tsx +124 -0
  115. package/src/components/docs-sidebar.tsx +16 -2
  116. package/src/components/docs-toc.tsx +6 -2
  117. package/src/components/edit-page-link.tsx +22 -0
  118. package/src/components/heading-permalink.tsx +74 -0
  119. package/src/components/mdx-components.tsx +47 -18
  120. package/src/components/steps.tsx +141 -0
  121. package/src/lib/i18n.ts +24 -0
  122. package/src/lib/mermaid.ts +3 -2
  123. package/src/styles/globals.css +12 -0
@@ -0,0 +1,87 @@
1
+ import { mergeProps } from '@base-ui/react/merge-props'
2
+ import { useRender } from '@base-ui/react/use-render'
3
+ import { cva, type VariantProps } from 'class-variance-authority'
4
+ import { cn } from '@lexibase/rdx-ui/lib/utils'
5
+
6
+ import { Separator } from './separator'
7
+
8
+ const buttonGroupVariants = cva(
9
+ "flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-lg [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
10
+ {
11
+ variants: {
12
+ orientation: {
13
+ horizontal:
14
+ '*:data-slot:rounded-r-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-lg! [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]~[data-slot]]:border-l-0',
15
+ vertical:
16
+ 'flex-col *:data-slot:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-lg! [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0',
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ orientation: 'horizontal',
21
+ },
22
+ }
23
+ )
24
+
25
+ function ButtonGroup({
26
+ className,
27
+ orientation,
28
+ ...props
29
+ }: React.ComponentProps<'div'> & VariantProps<typeof buttonGroupVariants>) {
30
+ return (
31
+ <div
32
+ role="group"
33
+ data-slot="button-group"
34
+ data-orientation={orientation}
35
+ className={cn(buttonGroupVariants({ orientation }), className)}
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+
41
+ function ButtonGroupText({
42
+ className,
43
+ render,
44
+ ...props
45
+ }: useRender.ComponentProps<'div'>) {
46
+ return useRender({
47
+ defaultTagName: 'div',
48
+ props: mergeProps<'div'>(
49
+ {
50
+ className: cn(
51
+ "flex items-center gap-2 rounded-lg border bg-muted px-2.5 text-sm font-medium [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
52
+ className
53
+ ),
54
+ },
55
+ props
56
+ ),
57
+ render,
58
+ state: {
59
+ slot: 'button-group-text',
60
+ },
61
+ })
62
+ }
63
+
64
+ function ButtonGroupSeparator({
65
+ className,
66
+ orientation = 'vertical',
67
+ ...props
68
+ }: React.ComponentProps<typeof Separator>) {
69
+ return (
70
+ <Separator
71
+ data-slot="button-group-separator"
72
+ orientation={orientation}
73
+ className={cn(
74
+ 'relative self-stretch bg-input data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px data-vertical:h-auto',
75
+ className
76
+ )}
77
+ {...props}
78
+ />
79
+ )
80
+ }
81
+
82
+ export {
83
+ ButtonGroup,
84
+ ButtonGroupSeparator,
85
+ ButtonGroupText,
86
+ buttonGroupVariants,
87
+ }
@@ -0,0 +1,102 @@
1
+ import * as React from 'react'
2
+ import { cn } from '@lexibase/rdx-ui/lib/utils'
3
+
4
+ function Card({
5
+ className,
6
+ size = 'default',
7
+ ...props
8
+ }: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {
9
+ return (
10
+ <div
11
+ data-slot="card"
12
+ data-size={size}
13
+ className={cn(
14
+ 'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',
15
+ className
16
+ )}
17
+ {...props}
18
+ />
19
+ )
20
+ }
21
+
22
+ function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
23
+ return (
24
+ <div
25
+ data-slot="card-header"
26
+ className={cn(
27
+ 'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',
28
+ className
29
+ )}
30
+ {...props}
31
+ />
32
+ )
33
+ }
34
+
35
+ function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
36
+ return (
37
+ <div
38
+ data-slot="card-title"
39
+ className={cn(
40
+ 'cn-font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm',
41
+ className
42
+ )}
43
+ {...props}
44
+ />
45
+ )
46
+ }
47
+
48
+ function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
49
+ return (
50
+ <div
51
+ data-slot="card-description"
52
+ className={cn('text-sm text-muted-foreground', className)}
53
+ {...props}
54
+ />
55
+ )
56
+ }
57
+
58
+ function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
59
+ return (
60
+ <div
61
+ data-slot="card-action"
62
+ className={cn(
63
+ 'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
64
+ className
65
+ )}
66
+ {...props}
67
+ />
68
+ )
69
+ }
70
+
71
+ function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
72
+ return (
73
+ <div
74
+ data-slot="card-content"
75
+ className={cn('px-(--card-spacing)', className)}
76
+ {...props}
77
+ />
78
+ )
79
+ }
80
+
81
+ function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
82
+ return (
83
+ <div
84
+ data-slot="card-footer"
85
+ className={cn(
86
+ 'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',
87
+ className
88
+ )}
89
+ {...props}
90
+ />
91
+ )
92
+ }
93
+
94
+ export {
95
+ Card,
96
+ CardHeader,
97
+ CardFooter,
98
+ CardTitle,
99
+ CardAction,
100
+ CardDescription,
101
+ CardContent,
102
+ }
@@ -0,0 +1,124 @@
1
+ 'use client'
2
+
3
+ import { Check, ChevronDown, CircleAlert, Copy, FileText } from 'lucide-react'
4
+ import { useEffect, useRef, useState } from 'react'
5
+
6
+ import { useMessages } from '../providers/locale-provider'
7
+ import { Button } from './button'
8
+ import { ButtonGroup } from './button-group'
9
+ import {
10
+ DropdownMenu,
11
+ DropdownMenuContent,
12
+ DropdownMenuLinkItem,
13
+ DropdownMenuTrigger,
14
+ } from './dropdown-menu'
15
+
16
+ export function CopyPage({ href }: { href: string }) {
17
+ const messages = useMessages()
18
+ const [status, setStatus] = useState<'idle' | 'copying' | 'copied' | 'error'>(
19
+ 'idle'
20
+ )
21
+ const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
22
+ const request = useRef<AbortController | undefined>(undefined)
23
+
24
+ useEffect(
25
+ () => () => {
26
+ request.current?.abort()
27
+ clearTimeout(timer.current)
28
+ },
29
+ []
30
+ )
31
+
32
+ async function copyPage() {
33
+ if (request.current) return
34
+ const controller = new AbortController()
35
+ request.current = controller
36
+ clearTimeout(timer.current)
37
+ setStatus('copying')
38
+
39
+ try {
40
+ if (!navigator.clipboard) throw new Error('Clipboard unavailable')
41
+ const markdown = fetch(href, { signal: controller.signal }).then(
42
+ (response) => {
43
+ if (
44
+ !response.ok ||
45
+ !response.headers.get('content-type')?.startsWith('text/markdown')
46
+ )
47
+ throw new Error('Markdown unavailable')
48
+ return response.text()
49
+ }
50
+ )
51
+ if (navigator.clipboard.write && typeof ClipboardItem !== 'undefined') {
52
+ // Initiate the write inside the click's activation, even while fetching.
53
+ const blob = markdown.then(
54
+ (text) => new Blob([text], { type: 'text/plain' })
55
+ )
56
+ // ClipboardItem construction can fail before the browser consumes the blob.
57
+ void blob.catch(() => {})
58
+ await Promise.all([
59
+ blob,
60
+ navigator.clipboard.write([
61
+ new ClipboardItem({ 'text/plain': blob }),
62
+ ]),
63
+ ])
64
+ } else {
65
+ await navigator.clipboard.writeText(await markdown)
66
+ }
67
+ if (controller.signal.aborted) return
68
+ setStatus('copied')
69
+ timer.current = setTimeout(() => setStatus('idle'), 1800)
70
+ } catch {
71
+ if (!controller.signal.aborted) setStatus('error')
72
+ controller.abort()
73
+ } finally {
74
+ request.current = undefined
75
+ }
76
+ }
77
+
78
+ const Icon =
79
+ status === 'copied' ? Check : status === 'error' ? CircleAlert : Copy
80
+
81
+ return (
82
+ <ButtonGroup
83
+ aria-label={messages.copyPageOptions}
84
+ className="ms-auto shrink-0"
85
+ >
86
+ <Button
87
+ type="button"
88
+ variant="outline"
89
+ size="sm"
90
+ onClick={copyPage}
91
+ aria-busy={status === 'copying'}
92
+ title={status === 'error' ? messages.copyPageError : undefined}
93
+ >
94
+ <Icon aria-hidden="true" />
95
+ {messages.copyThisPage}
96
+ </Button>
97
+ <DropdownMenu>
98
+ <DropdownMenuTrigger
99
+ aria-label={messages.copyPageOptions}
100
+ render={<Button type="button" variant="outline" size="icon-sm" />}
101
+ >
102
+ <ChevronDown aria-hidden="true" />
103
+ </DropdownMenuTrigger>
104
+ <DropdownMenuContent align="end">
105
+ <DropdownMenuLinkItem
106
+ render={<a href={href} target="_blank" rel="noopener noreferrer" />}
107
+ closeOnClick
108
+ >
109
+ <FileText aria-hidden="true" />
110
+ {messages.viewMarkdown}
111
+ <span className="sr-only"> ({messages.opensInNewTab})</span>
112
+ </DropdownMenuLinkItem>
113
+ </DropdownMenuContent>
114
+ </DropdownMenu>
115
+ <span role="status" className="sr-only">
116
+ {status === 'copied'
117
+ ? messages.pageCopied
118
+ : status === 'error'
119
+ ? messages.copyPageError
120
+ : ''}
121
+ </span>
122
+ </ButtonGroup>
123
+ )
124
+ }
@@ -5,6 +5,7 @@ import { useMessages } from '../providers/locale-provider'
5
5
  import { useEffect, useRef, useState } from 'react'
6
6
  import { SidebarClose, SidebarOpen } from 'lucide-react'
7
7
  import { AnimatePresence, motion, useReducedMotion } from 'motion/react'
8
+ import type { RdxPageHighlight } from '@lexibase/rdx-types'
8
9
 
9
10
  import { Button } from './button'
10
11
  import { Separator } from './separator'
@@ -18,7 +19,7 @@ import {
18
19
 
19
20
  export type SidebarCategory = {
20
21
  title: string
21
- links: { href: string; label: string }[]
22
+ links: { href: string; label: string; highlighting?: RdxPageHighlight }[]
22
23
  }
23
24
 
24
25
  export function DocsSidebar({
@@ -73,7 +74,11 @@ export function DocsSidebar({
73
74
  variant="ghost"
74
75
  size="sm"
75
76
  active={pathname === link.href}
76
- aria-label={link.label}
77
+ aria-label={
78
+ link.highlighting
79
+ ? `${link.label} (${link.highlighting === 'important' ? messages.sidebarImportant : messages.sidebarNew})`
80
+ : link.label
81
+ }
77
82
  href={link.href}
78
83
  transitionTypes={
79
84
  currentIndex < 0 || link.href === pathname
@@ -89,6 +94,15 @@ export function DocsSidebar({
89
94
  className={`h-auto min-h-8 w-full min-w-0 justify-start whitespace-normal py-1.5 text-left text-sm [overflow-wrap:anywhere] ${pathname === link.href ? 'text-accent-foreground' : 'text-primary'}`}
90
95
  >
91
96
  {link.label}
97
+ {link.highlighting && (
98
+ <span
99
+ aria-hidden="true"
100
+ data-rdx-sidebar-highlight={link.highlighting}
101
+ className={`relative ms-auto size-2 shrink-0 rounded-full bg-current ${link.highlighting === 'important' ? 'text-amber-600 dark:text-amber-400' : 'text-blue-600 dark:text-blue-400'}`}
102
+ >
103
+ <span className="absolute inset-0 rounded-full bg-current opacity-0 motion-safe:animate-[rdx-sidebar-highlight_2.8s_ease-out_infinite]" />
104
+ </span>
105
+ )}
92
106
  </Button>
93
107
  ))}
94
108
  {index < categories.length - 1 && <Separator className="my-4" />}
@@ -138,13 +138,17 @@ function PageToc({ contentId }: { contentId: string }) {
138
138
  element.getClientRects().length > 0 &&
139
139
  !!element.id &&
140
140
  !!element.textContent?.trim() &&
141
+ !element.closest('[data-slot="step-panel"][inert]') &&
141
142
  getComputedStyle(element).visibility !== 'hidden'
142
143
  )
143
144
  setHeadings(
144
145
  elements
145
146
  .map((element) => ({
146
147
  id: element.id,
147
- title: element.textContent?.trim() ?? '',
148
+ title:
149
+ (
150
+ element.querySelector('[data-rdx-heading-text]') ?? element
151
+ ).textContent?.trim() ?? '',
148
152
  depth: Number(element.tagName.slice(1)),
149
153
  }))
150
154
  .filter((heading) => heading.title)
@@ -158,7 +162,7 @@ function PageToc({ contentId }: { contentId: string }) {
158
162
  subtree: true,
159
163
  characterData: true,
160
164
  attributes: true,
161
- attributeFilter: ['hidden', 'style', 'class', 'id'],
165
+ attributeFilter: ['hidden', 'style', 'class', 'id', 'inert'],
162
166
  })
163
167
  scrollTarget.addEventListener('scroll', schedule, { passive: true })
164
168
  window.addEventListener('resize', collect)
@@ -0,0 +1,22 @@
1
+ import { Pencil } from 'lucide-react'
2
+
3
+ import { getMessages } from '../lib/i18n'
4
+ import { Button } from './button'
5
+
6
+ export function EditPageLink({ href, lang }: { href: string; lang?: string }) {
7
+ const messages = getMessages(lang)
8
+ return (
9
+ <Button
10
+ variant="link"
11
+ size="link"
12
+ href={href}
13
+ target="_blank"
14
+ rel="noopener noreferrer"
15
+ className="mb-4 self-start text-sm text-muted-foreground hover:text-foreground"
16
+ >
17
+ <Pencil aria-hidden="true" className="me-1.5 inline size-3.5" />
18
+ {messages.editThisPage}
19
+ <span className="sr-only"> ({messages.opensInNewTab})</span>
20
+ </Button>
21
+ )
22
+ }
@@ -0,0 +1,74 @@
1
+ 'use client'
2
+
3
+ import { Check, Hash } from 'lucide-react'
4
+ import {
5
+ useEffect,
6
+ useRef,
7
+ useState,
8
+ type MouseEvent,
9
+ type ReactNode,
10
+ } from 'react'
11
+
12
+ import { useMessages } from '../providers/locale-provider'
13
+
14
+ export function HeadingPermalink({
15
+ id,
16
+ children,
17
+ }: {
18
+ id: string
19
+ children: ReactNode
20
+ }) {
21
+ const messages = useMessages()
22
+ const [copied, setCopied] = useState(false)
23
+ const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
24
+ useEffect(() => () => clearTimeout(timer.current), [])
25
+
26
+ const href = `#${encodeURIComponent(id)}`
27
+
28
+ async function copy(event: MouseEvent<HTMLAnchorElement>) {
29
+ if (
30
+ event.defaultPrevented ||
31
+ event.metaKey ||
32
+ event.ctrlKey ||
33
+ event.altKey ||
34
+ event.shiftKey ||
35
+ !navigator.clipboard?.writeText
36
+ )
37
+ return
38
+
39
+ try {
40
+ await navigator.clipboard.writeText(event.currentTarget.href)
41
+ } catch {
42
+ // The native link still takes the reader to the section.
43
+ return
44
+ }
45
+
46
+ setCopied(true)
47
+ clearTimeout(timer.current)
48
+ timer.current = setTimeout(() => setCopied(false), 1800)
49
+ }
50
+
51
+ const Icon = copied ? Check : Hash
52
+
53
+ return (
54
+ <>
55
+ <a
56
+ href={href}
57
+ onClick={copy}
58
+ title={messages.sectionPermalink}
59
+ className="rounded-sm text-inherit no-underline underline-offset-4 hover:underline focus-visible:underline focus-visible:outline-2 focus-visible:outline-ring"
60
+ >
61
+ {children}
62
+ </a>
63
+ <span
64
+ aria-hidden="true"
65
+ className="pointer-events-none ms-1 inline-flex size-6 items-center justify-center align-middle text-muted-foreground pointer-fine:opacity-0 pointer-fine:group-hover/mdx-heading:opacity-100 pointer-fine:group-focus-within/mdx-heading:opacity-100"
66
+ >
67
+ <Icon className="size-4" />
68
+ </span>
69
+ <span role="status" className="sr-only">
70
+ {copied ? messages.sectionLinkCopied : ''}
71
+ </span>
72
+ </>
73
+ )
74
+ }
@@ -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 { HeadingPermalink } from './heading-permalink'
32
33
  import { MdxKeyboardShortcut } from './mdx-keyboard-shortcut'
33
34
  import {
34
35
  Table,
@@ -41,6 +42,7 @@ import {
41
42
  TableRow,
42
43
  } from './table'
43
44
  import { Tabs, TabsContent, TabsList, TabsTrigger } from './tabs'
45
+ import { Steps, Step, StepTitle } from './steps'
44
46
  import { Separator } from '@lexibase/rdx-ui/components/separator'
45
47
 
46
48
  function linkedImages(children: ReactNode): ReactNode {
@@ -71,16 +73,46 @@ function MdxTable({ lang, className, ...props }: ComponentProps<'table'>) {
71
73
  )
72
74
  }
73
75
 
76
+ function containsLink(children: ReactNode): boolean {
77
+ return Children.toArray(children).some(
78
+ (child) =>
79
+ isValidElement<{ href?: string; children?: ReactNode }>(child) &&
80
+ (child.type === 'a' ||
81
+ child.props.href !== undefined ||
82
+ containsLink(child.props.children))
83
+ )
84
+ }
85
+
86
+ function heading(Tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6', styles: string) {
87
+ return function MdxHeading({
88
+ className,
89
+ children,
90
+ id,
91
+ ...props
92
+ }: ComponentProps<'h2'>) {
93
+ const title = <span data-rdx-heading-text>{children}</span>
94
+ return (
95
+ <Tag
96
+ id={id}
97
+ className={cn('group/mdx-heading', styles, className)}
98
+ {...props}
99
+ >
100
+ {id && !containsLink(children) ? (
101
+ <HeadingPermalink key={id} id={id}>
102
+ {title}
103
+ </HeadingPermalink>
104
+ ) : (
105
+ title
106
+ )}
107
+ </Tag>
108
+ )
109
+ }
110
+ }
111
+
74
112
  export const mdxComponents = {
75
- h1: ({ className, ...props }: ComponentProps<'h1'>) => (
76
- <h1 className={cn('text-4xl font-bold my-6', className)} {...props} />
77
- ),
78
- h2: ({ className, ...props }: ComponentProps<'h2'>) => (
79
- <h2 className={cn('text-2xl font-bold my-6', className)} {...props} />
80
- ),
81
- h3: ({ className, ...props }: ComponentProps<'h3'>) => (
82
- <h3 className={cn('text-xl font-bold my-6', className)} {...props} />
83
- ),
113
+ h1: heading('h1', 'text-4xl font-bold my-6'),
114
+ h2: heading('h2', 'text-2xl font-bold my-6'),
115
+ h3: heading('h3', 'text-xl font-bold my-6'),
84
116
  p: ({ className, ...props }: ComponentProps<'p'>) => (
85
117
  <p className={cn('my-4', className)} {...props} />
86
118
  ),
@@ -122,15 +154,9 @@ export const mdxComponents = {
122
154
  />
123
155
  ),
124
156
 
125
- h4: ({ className, ...props }: ComponentProps<'h4'>) => (
126
- <h4 className={cn('text-lg font-semibold my-4', className)} {...props} />
127
- ),
128
- h5: ({ className, ...props }: ComponentProps<'h5'>) => (
129
- <h5 className={cn('text-base font-semibold my-4', className)} {...props} />
130
- ),
131
- h6: ({ className, ...props }: ComponentProps<'h6'>) => (
132
- <h6 className={cn('text-sm font-semibold my-4', className)} {...props} />
133
- ),
157
+ h4: heading('h4', 'text-lg font-semibold my-4'),
158
+ h5: heading('h5', 'text-base font-semibold my-4'),
159
+ h6: heading('h6', 'text-sm font-semibold my-4'),
134
160
  hr: ({ className, ...props }: ComponentProps<'hr'>) => (
135
161
  <hr className={cn('my-8 border-border', className)} {...props} />
136
162
  ),
@@ -169,6 +195,9 @@ export const mdxComponents = {
169
195
  AccordionItem,
170
196
  AccordionTrigger,
171
197
  AccordionContent,
198
+ Steps,
199
+ Step,
200
+ StepTitle,
172
201
  Tabs,
173
202
  TabsList,
174
203
  TabsTrigger,