@vllnt/ui 0.1.3 → 0.1.6

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 (217) hide show
  1. package/dist/components/accordion/accordion.js +172 -0
  2. package/dist/components/accordion/index.js +12 -0
  3. package/dist/components/alert/alert.js +53 -0
  4. package/dist/components/alert/index.js +7 -0
  5. package/dist/components/alert-dialog/alert-dialog.js +117 -0
  6. package/dist/components/alert-dialog/index.js +26 -0
  7. package/dist/components/aspect-ratio/aspect-ratio.js +6 -0
  8. package/dist/components/aspect-ratio/index.js +4 -0
  9. package/dist/components/avatar/avatar.js +43 -0
  10. package/dist/components/avatar/index.js +6 -0
  11. package/dist/components/badge/badge.js +26 -0
  12. package/dist/components/badge/index.js +5 -0
  13. package/dist/components/blog-card/blog-card.js +50 -0
  14. package/dist/components/blog-card/index.js +5 -0
  15. package/dist/components/breadcrumb/breadcrumb.js +65 -0
  16. package/dist/components/breadcrumb/index.js +4 -0
  17. package/dist/components/button/button.js +48 -0
  18. package/dist/components/button/index.js +5 -0
  19. package/dist/components/calendar/calendar.js +71 -0
  20. package/dist/components/calendar/index.js +4 -0
  21. package/dist/components/callout/callout.js +59 -0
  22. package/dist/components/callout/index.js +4 -0
  23. package/dist/components/card/card.js +64 -0
  24. package/dist/components/card/index.js +16 -0
  25. package/dist/components/carousel/carousel.js +235 -0
  26. package/dist/components/carousel/index.js +14 -0
  27. package/dist/components/category-filter/category-filter.js +34 -0
  28. package/dist/components/category-filter/index.js +4 -0
  29. package/dist/components/chart/area-chart.js +99 -0
  30. package/dist/components/chart/bar-chart.js +80 -0
  31. package/dist/components/chart/index.js +3 -0
  32. package/dist/components/chart/line-chart.js +97 -0
  33. package/dist/components/checkbox/checkbox.js +28 -0
  34. package/dist/components/checkbox/index.js +4 -0
  35. package/dist/components/checklist/checklist.js +181 -0
  36. package/dist/components/checklist/index.js +6 -0
  37. package/dist/components/code-block/code-block.js +123 -0
  38. package/dist/components/code-block/index.js +4 -0
  39. package/dist/components/code-playground/code-playground.js +86 -0
  40. package/dist/components/code-playground/index.js +8 -0
  41. package/dist/components/collapsible/collapsible.js +10 -0
  42. package/dist/components/collapsible/index.js +10 -0
  43. package/dist/components/command/command.js +123 -0
  44. package/dist/components/command/index.js +22 -0
  45. package/dist/components/comparison/comparison.js +109 -0
  46. package/dist/components/comparison/index.js +8 -0
  47. package/dist/components/completion-dialog/completion-dialog.js +173 -0
  48. package/dist/components/completion-dialog/index.js +6 -0
  49. package/dist/components/content-intro/content-intro.js +144 -0
  50. package/dist/components/content-intro/index.js +6 -0
  51. package/dist/components/context-menu/context-menu.js +154 -0
  52. package/dist/components/context-menu/index.js +34 -0
  53. package/dist/components/cookie-consent/cookie-consent.js +171 -0
  54. package/dist/components/cookie-consent/index.js +8 -0
  55. package/dist/components/dialog/dialog.js +105 -0
  56. package/dist/components/dialog/index.js +24 -0
  57. package/dist/components/drawer/drawer.js +102 -0
  58. package/dist/components/drawer/index.js +24 -0
  59. package/dist/components/dropdown-menu/dropdown-menu.js +151 -0
  60. package/dist/components/dropdown-menu/index.js +34 -0
  61. package/dist/components/exercise/exercise.js +112 -0
  62. package/dist/components/exercise/index.js +4 -0
  63. package/dist/components/faq/faq.js +56 -0
  64. package/dist/components/faq/index.js +5 -0
  65. package/dist/components/filter-bar/filter-bar.js +244 -0
  66. package/dist/components/filter-bar/index.js +6 -0
  67. package/dist/components/floating-action-button/floating-action-button.js +35 -0
  68. package/dist/components/floating-action-button/index.js +6 -0
  69. package/dist/components/flow-diagram/flow-canvas.js +109 -0
  70. package/dist/components/flow-diagram/flow-controls.js +140 -0
  71. package/dist/components/flow-diagram/flow-diagram.js +114 -0
  72. package/dist/components/flow-diagram/flow-error-boundary.js +63 -0
  73. package/dist/components/flow-diagram/flow-fullscreen.js +58 -0
  74. package/dist/components/flow-diagram/index.js +12 -0
  75. package/dist/components/flow-diagram/types.js +0 -0
  76. package/dist/components/flow-diagram/use-flow-diagram.js +141 -0
  77. package/dist/components/hover-card/hover-card.js +26 -0
  78. package/dist/components/hover-card/index.js +6 -0
  79. package/dist/components/index.js +633 -0
  80. package/dist/components/inline-input/index.js +4 -0
  81. package/dist/components/inline-input/inline-input.js +42 -0
  82. package/dist/components/input/index.js +4 -0
  83. package/dist/components/input/input.js +23 -0
  84. package/dist/components/input-otp/index.js +12 -0
  85. package/dist/components/input-otp/input-otp.js +54 -0
  86. package/dist/components/key-concept/index.js +8 -0
  87. package/dist/components/key-concept/key-concept.js +79 -0
  88. package/dist/components/keyboard-shortcuts-help/index.js +6 -0
  89. package/dist/components/keyboard-shortcuts-help/keyboard-shortcuts-help.js +121 -0
  90. package/dist/components/label/index.js +4 -0
  91. package/dist/components/label/label.js +21 -0
  92. package/dist/components/lang-provider/index.js +4 -0
  93. package/dist/components/lang-provider/lang-provider.js +18 -0
  94. package/dist/components/learning-objectives/index.js +10 -0
  95. package/dist/components/learning-objectives/learning-objectives.js +76 -0
  96. package/dist/components/mdx-content/index.js +4 -0
  97. package/dist/components/mdx-content/mdx-content.js +140 -0
  98. package/dist/components/menubar/index.js +36 -0
  99. package/dist/components/menubar/menubar.js +183 -0
  100. package/dist/components/model-selector/index.js +6 -0
  101. package/dist/components/model-selector/model-selector.js +374 -0
  102. package/dist/components/navbar-saas/index.js +6 -0
  103. package/dist/components/navbar-saas/navbar-saas.js +68 -0
  104. package/dist/components/navbar-saas/use-mobile.js +19 -0
  105. package/dist/components/navigation-menu/index.js +22 -0
  106. package/dist/components/navigation-menu/navigation-menu.js +108 -0
  107. package/dist/components/pagination/index.js +4 -0
  108. package/dist/components/pagination/pagination.js +44 -0
  109. package/dist/components/popover/index.js +12 -0
  110. package/dist/components/popover/popover.js +28 -0
  111. package/dist/components/pro-tip/index.js +8 -0
  112. package/dist/components/pro-tip/pro-tip.js +139 -0
  113. package/dist/components/profile-section/index.js +4 -0
  114. package/dist/components/profile-section/profile-section.js +45 -0
  115. package/dist/components/progress-bar/index.js +4 -0
  116. package/dist/components/progress-bar/progress-bar.js +56 -0
  117. package/dist/components/progress-card/index.js +6 -0
  118. package/dist/components/progress-card/progress-card.js +68 -0
  119. package/dist/components/quiz/index.js +4 -0
  120. package/dist/components/quiz/quiz.js +210 -0
  121. package/dist/components/radio-group/index.js +5 -0
  122. package/dist/components/radio-group/radio-group.js +36 -0
  123. package/dist/components/resizable/index.js +10 -0
  124. package/dist/components/resizable/resizable.js +39 -0
  125. package/dist/components/scroll-area/index.js +5 -0
  126. package/dist/components/scroll-area/scroll-area.js +39 -0
  127. package/dist/components/search-bar/index.js +4 -0
  128. package/dist/components/search-bar/search-bar.js +120 -0
  129. package/dist/components/search-dialog/index.js +4 -0
  130. package/dist/components/search-dialog/search-dialog.js +102 -0
  131. package/dist/components/select/index.js +24 -0
  132. package/dist/components/select/select.js +125 -0
  133. package/dist/components/separator/index.js +4 -0
  134. package/dist/components/separator/separator.js +25 -0
  135. package/dist/components/share-dialog/index.js +6 -0
  136. package/dist/components/share-dialog/share-dialog.js +121 -0
  137. package/dist/components/share-section/index.js +6 -0
  138. package/dist/components/share-section/share-section.js +57 -0
  139. package/dist/components/sheet/index.js +24 -0
  140. package/dist/components/sheet/sheet.js +116 -0
  141. package/dist/components/sidebar/index.js +4 -0
  142. package/dist/components/sidebar/sidebar.js +188 -0
  143. package/dist/components/sidebar-provider/index.js +5 -0
  144. package/dist/components/sidebar-provider/sidebar-provider.js +25 -0
  145. package/dist/components/sidebar-toggle/index.js +4 -0
  146. package/dist/components/sidebar-toggle/sidebar-toggle.js +36 -0
  147. package/dist/components/skeleton/index.js +4 -0
  148. package/dist/components/skeleton/skeleton.js +17 -0
  149. package/dist/components/slider/index.js +4 -0
  150. package/dist/components/slider/slider.js +24 -0
  151. package/dist/components/slideshow/index.js +6 -0
  152. package/dist/components/slideshow/slideshow.js +442 -0
  153. package/dist/components/social-fab/index.js +6 -0
  154. package/dist/components/social-fab/social-fab.js +211 -0
  155. package/dist/components/social-fab/use-social-fab.js +111 -0
  156. package/dist/components/spinner/index.js +4 -0
  157. package/dist/components/spinner/spinner.js +23 -0
  158. package/dist/components/step-by-step/index.js +8 -0
  159. package/dist/components/step-by-step/step-by-step.js +194 -0
  160. package/dist/components/step-navigation/index.js +4 -0
  161. package/dist/components/step-navigation/step-navigation.js +119 -0
  162. package/dist/components/switch/index.js +4 -0
  163. package/dist/components/switch/switch.js +28 -0
  164. package/dist/components/table/index.js +20 -0
  165. package/dist/components/table/table.js +87 -0
  166. package/dist/components/table-of-contents/index.js +4 -0
  167. package/dist/components/table-of-contents/table-of-contents.js +71 -0
  168. package/dist/components/table-of-contents-panel/index.js +6 -0
  169. package/dist/components/table-of-contents-panel/table-of-contents-panel.js +202 -0
  170. package/dist/components/tabs/index.js +12 -0
  171. package/dist/components/tabs/tabs.js +84 -0
  172. package/dist/components/terminal/index.js +8 -0
  173. package/dist/components/terminal/terminal.js +119 -0
  174. package/dist/components/textarea/index.js +4 -0
  175. package/dist/components/textarea/textarea.js +22 -0
  176. package/dist/components/theme-provider/index.js +4 -0
  177. package/dist/components/theme-provider/theme-provider.js +11 -0
  178. package/dist/components/theme-toggle/index.js +4 -0
  179. package/dist/components/theme-toggle/theme-toggle.js +172 -0
  180. package/dist/components/thinking-block/index.js +4 -0
  181. package/dist/components/thinking-block/thinking-block.js +52 -0
  182. package/dist/components/tldr-section/index.js +4 -0
  183. package/dist/components/tldr-section/tldr-section.js +92 -0
  184. package/dist/components/toast/index.js +18 -0
  185. package/dist/components/toast/toast.js +84 -0
  186. package/dist/components/toast/toaster.js +38 -0
  187. package/dist/components/toggle/index.js +5 -0
  188. package/dist/components/toggle/toggle.js +39 -0
  189. package/dist/components/toggle-group/index.js +5 -0
  190. package/dist/components/toggle-group/toggle-group.js +46 -0
  191. package/dist/components/tooltip/index.js +12 -0
  192. package/dist/components/tooltip/tooltip.js +27 -0
  193. package/dist/components/truncated-text/index.js +4 -0
  194. package/dist/components/truncated-text/truncated-text.js +25 -0
  195. package/dist/components/tutorial-card/index.js +6 -0
  196. package/dist/components/tutorial-card/tutorial-card.js +75 -0
  197. package/dist/components/tutorial-complete/index.js +6 -0
  198. package/dist/components/tutorial-complete/tutorial-complete.js +134 -0
  199. package/dist/components/tutorial-filters/index.js +6 -0
  200. package/dist/components/tutorial-filters/tutorial-filters.js +205 -0
  201. package/dist/components/tutorial-intro-content/index.js +6 -0
  202. package/dist/components/tutorial-intro-content/tutorial-intro-content.js +141 -0
  203. package/dist/components/tutorial-mdx/index.js +8 -0
  204. package/dist/components/tutorial-mdx/tutorial-mdx.js +219 -0
  205. package/dist/components/video-embed/index.js +4 -0
  206. package/dist/components/video-embed/video-embed.js +77 -0
  207. package/dist/index.js +12 -8557
  208. package/dist/lib/types.js +11 -0
  209. package/dist/lib/use-debounce.js +17 -0
  210. package/dist/lib/utils.js +8 -0
  211. package/dist/tailwind-preset.d.ts +5 -0
  212. package/dist/tailwind-preset.js +9 -10
  213. package/dist/types/content.js +0 -0
  214. package/dist/types/index.js +0 -0
  215. package/package.json +1 -1
  216. package/dist/chunk-XRV5RSYH.js +0 -569
  217. package/dist/flow-diagram-N3EHM6VB.js +0 -2
@@ -0,0 +1,109 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { memo } from "react";
4
+ import {
5
+ Background,
6
+ BackgroundVariant,
7
+ ReactFlow
8
+ } from "@xyflow/react";
9
+ import { useTheme } from "next-themes";
10
+ import { cn } from "../../lib/utils";
11
+ import { FlowControls } from "./flow-controls";
12
+ const REACT_FLOW_CLASS = [
13
+ // Node container styling — no !important on bg/color so inline styles take precedence
14
+ "[&_.react-flow__node]:rounded-md",
15
+ "[&_.react-flow__node]:border",
16
+ "[&_.react-flow__node]:border-border",
17
+ "[&_.react-flow__node]:bg-card",
18
+ "[&_.react-flow__node]:px-4",
19
+ "[&_.react-flow__node]:py-2",
20
+ "[&_.react-flow__node]:shadow-sm",
21
+ // Node text styling — defaults, overridable by inline style.color
22
+ "[&_.react-flow__node]:text-sm",
23
+ "[&_.react-flow__node]:text-card-foreground",
24
+ "[&_.react-flow__node]:font-medium",
25
+ // Edge styling
26
+ "[&_.react-flow__edge-path]:!stroke-muted-foreground",
27
+ "[&_.react-flow__edge-path]:!stroke-2",
28
+ // Arrow marker styling
29
+ "[&_.react-flow__arrowhead_polyline]:!fill-muted-foreground",
30
+ "[&_.react-flow__arrowhead_polyline]:!stroke-muted-foreground"
31
+ ].join(" ");
32
+ const FlowCanvas = memo(function FlowCanvas2({
33
+ allowCopy,
34
+ allowFullscreen,
35
+ className,
36
+ copyStatus,
37
+ edges,
38
+ fitView,
39
+ fitViewOptions,
40
+ height,
41
+ isFullscreen,
42
+ nodes,
43
+ onCopy,
44
+ onFitView,
45
+ onFullscreen,
46
+ onNodeClick,
47
+ onZoomIn,
48
+ onZoomOut,
49
+ showControls,
50
+ title
51
+ }) {
52
+ const { resolvedTheme } = useTheme();
53
+ const colorMode = resolvedTheme === "dark" ? "dark" : "light";
54
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative w-full", className), children: [
55
+ title ? /* @__PURE__ */ jsx("div", { className: "mb-2 text-sm font-medium text-foreground", children: title }) : null,
56
+ /* @__PURE__ */ jsx(
57
+ "div",
58
+ {
59
+ className: "relative w-full rounded-lg border border-border bg-background overflow-hidden",
60
+ style: { height: isFullscreen ? "100%" : height },
61
+ children: /* @__PURE__ */ jsxs(
62
+ ReactFlow,
63
+ {
64
+ className: REACT_FLOW_CLASS,
65
+ colorMode,
66
+ edges,
67
+ elementsSelectable: false,
68
+ fitView,
69
+ fitViewOptions: fitViewOptions ?? { padding: 0.2 },
70
+ nodes,
71
+ nodesConnectable: false,
72
+ nodesDraggable: false,
73
+ onNodeClick,
74
+ panOnScroll: true,
75
+ proOptions: { hideAttribution: true },
76
+ zoomOnScroll: true,
77
+ children: [
78
+ /* @__PURE__ */ jsx(
79
+ Background,
80
+ {
81
+ className: "!bg-background [&>pattern>circle]:fill-muted-foreground/20",
82
+ gap: 16,
83
+ size: 1,
84
+ variant: BackgroundVariant.Dots
85
+ }
86
+ ),
87
+ showControls ? /* @__PURE__ */ jsx(
88
+ FlowControls,
89
+ {
90
+ copyStatus,
91
+ onCopy,
92
+ onFitView,
93
+ onFullscreen,
94
+ onZoomIn,
95
+ onZoomOut,
96
+ showCopy: allowCopy,
97
+ showFullscreen: allowFullscreen
98
+ }
99
+ ) : null
100
+ ]
101
+ }
102
+ )
103
+ }
104
+ )
105
+ ] });
106
+ });
107
+ export {
108
+ FlowCanvas
109
+ };
@@ -0,0 +1,140 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { memo } from "react";
4
+ import {
5
+ Check,
6
+ Copy,
7
+ Loader2,
8
+ Maximize2,
9
+ Minus,
10
+ Move,
11
+ Plus,
12
+ X
13
+ } from "lucide-react";
14
+ import { cn } from "../../lib/utils";
15
+ const BUTTON_CLASS = "flex h-8 w-8 items-center justify-center rounded hover:bg-muted transition-colors disabled:opacity-50 disabled:cursor-not-allowed";
16
+ function ControlButton({
17
+ disabled,
18
+ icon,
19
+ label,
20
+ onClick,
21
+ title
22
+ }) {
23
+ return /* @__PURE__ */ jsx(
24
+ "button",
25
+ {
26
+ "aria-label": label,
27
+ className: BUTTON_CLASS,
28
+ disabled,
29
+ onClick,
30
+ title,
31
+ type: "button",
32
+ children: icon
33
+ }
34
+ );
35
+ }
36
+ function getCopyIcon(status) {
37
+ switch (status) {
38
+ case "copying":
39
+ return /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" });
40
+ case "success":
41
+ return /* @__PURE__ */ jsx(Check, { className: "h-4 w-4 text-green-500" });
42
+ case "error":
43
+ return /* @__PURE__ */ jsx(X, { className: "h-4 w-4 text-destructive" });
44
+ case "idle":
45
+ case void 0:
46
+ return /* @__PURE__ */ jsx(Copy, { className: "h-4 w-4" });
47
+ }
48
+ }
49
+ function getCopyTitle(status) {
50
+ switch (status) {
51
+ case "copying":
52
+ return "Copying...";
53
+ case "success":
54
+ return "Copied!";
55
+ case "error":
56
+ return "Copy failed";
57
+ case "idle":
58
+ case void 0:
59
+ return "Copy as image";
60
+ }
61
+ }
62
+ const FlowControls = memo(function FlowControls2({
63
+ className,
64
+ copyStatus,
65
+ onCopy,
66
+ onFitView,
67
+ onFullscreen,
68
+ onZoomIn,
69
+ onZoomOut,
70
+ showCopy = false,
71
+ showFullscreen = false
72
+ }) {
73
+ return /* @__PURE__ */ jsxs(
74
+ "div",
75
+ {
76
+ className: cn(
77
+ "absolute bottom-4 left-4 z-10 flex flex-col gap-1 rounded-md border border-border bg-background/90 p-1 backdrop-blur-sm",
78
+ className
79
+ ),
80
+ children: [
81
+ /* @__PURE__ */ jsx(
82
+ ControlButton,
83
+ {
84
+ icon: /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
85
+ label: "Zoom in",
86
+ onClick: onZoomIn,
87
+ title: "Zoom in"
88
+ }
89
+ ),
90
+ /* @__PURE__ */ jsx(
91
+ ControlButton,
92
+ {
93
+ icon: /* @__PURE__ */ jsx(Minus, { className: "h-4 w-4" }),
94
+ label: "Zoom out",
95
+ onClick: onZoomOut,
96
+ title: "Zoom out"
97
+ }
98
+ ),
99
+ /* @__PURE__ */ jsx("div", { className: "h-px bg-border" }),
100
+ /* @__PURE__ */ jsx(
101
+ ControlButton,
102
+ {
103
+ icon: /* @__PURE__ */ jsx(Move, { className: "h-4 w-4" }),
104
+ label: "Fit view",
105
+ onClick: onFitView,
106
+ title: "Fit view"
107
+ }
108
+ ),
109
+ showCopy && onCopy ? /* @__PURE__ */ jsxs(Fragment, { children: [
110
+ /* @__PURE__ */ jsx("div", { className: "h-px bg-border" }),
111
+ /* @__PURE__ */ jsx(
112
+ ControlButton,
113
+ {
114
+ disabled: copyStatus === "copying",
115
+ icon: getCopyIcon(copyStatus),
116
+ label: getCopyTitle(copyStatus),
117
+ onClick: onCopy,
118
+ title: getCopyTitle(copyStatus)
119
+ }
120
+ )
121
+ ] }) : null,
122
+ showFullscreen && onFullscreen ? /* @__PURE__ */ jsxs(Fragment, { children: [
123
+ /* @__PURE__ */ jsx("div", { className: "h-px bg-border" }),
124
+ /* @__PURE__ */ jsx(
125
+ ControlButton,
126
+ {
127
+ icon: /* @__PURE__ */ jsx(Maximize2, { className: "h-4 w-4" }),
128
+ label: "Fullscreen",
129
+ onClick: onFullscreen,
130
+ title: "Toggle fullscreen"
131
+ }
132
+ )
133
+ ] }) : null
134
+ ]
135
+ }
136
+ );
137
+ });
138
+ export {
139
+ FlowControls
140
+ };
@@ -0,0 +1,114 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import "@xyflow/react/dist/style.css";
4
+ import { memo, useCallback, useEffect } from "react";
5
+ import {
6
+ ReactFlowProvider
7
+ } from "@xyflow/react";
8
+ import { cn } from "../../lib/utils";
9
+ import { FlowCanvas } from "./flow-canvas";
10
+ import { FlowErrorBoundary } from "./flow-error-boundary";
11
+ import { FlowFullscreen } from "./flow-fullscreen";
12
+ import { useFlowDiagram } from "./use-flow-diagram";
13
+ function validateFlowData(nodes, edges) {
14
+ if (nodes.length === 0 && edges.length > 0) {
15
+ console.warn(
16
+ "[FlowDiagram] Edges provided without nodes - edges will not render"
17
+ );
18
+ }
19
+ const nodeIds = new Set(nodes.map((n) => n.id));
20
+ const invalidEdges = edges.filter(
21
+ (e) => !nodeIds.has(e.source) || !nodeIds.has(e.target)
22
+ );
23
+ if (invalidEdges.length > 0) {
24
+ console.warn(
25
+ `[FlowDiagram] ${invalidEdges.length} edge(s) reference non-existent nodes:`,
26
+ invalidEdges.map((e) => `${e.id}: ${e.source} -> ${e.target}`)
27
+ );
28
+ }
29
+ const nodesWithoutPosition = nodes.filter((n) => n.position === void 0);
30
+ if (nodesWithoutPosition.length > 0) {
31
+ console.warn(
32
+ `[FlowDiagram] ${nodesWithoutPosition.length} node(s) missing position:`,
33
+ nodesWithoutPosition.map((n) => n.id)
34
+ );
35
+ }
36
+ }
37
+ const FlowDiagramInner = memo(function FlowDiagramInner2({
38
+ allowCopy = false,
39
+ allowFullscreen = true,
40
+ className,
41
+ edges,
42
+ fitView = true,
43
+ fitViewOptions,
44
+ height = 400,
45
+ nodes,
46
+ onNodeClick,
47
+ showControls = true,
48
+ title
49
+ }) {
50
+ useEffect(() => {
51
+ validateFlowData(nodes, edges);
52
+ }, [nodes, edges]);
53
+ const {
54
+ closeFullscreen,
55
+ copyStatus,
56
+ copyToClipboard,
57
+ fitView: handleFitView,
58
+ isFullscreen,
59
+ toggleFullscreen,
60
+ zoomIn,
61
+ zoomOut
62
+ } = useFlowDiagram({ allowCopy, allowFullscreen });
63
+ const handleNodeClick = useCallback(
64
+ (_event, node) => {
65
+ onNodeClick?.(node);
66
+ },
67
+ [onNodeClick]
68
+ );
69
+ const handleCopy = useCallback(() => {
70
+ void copyToClipboard();
71
+ }, [copyToClipboard]);
72
+ const canvasProps = {
73
+ allowCopy,
74
+ allowFullscreen,
75
+ className,
76
+ copyStatus,
77
+ edges,
78
+ fitView,
79
+ fitViewOptions,
80
+ height,
81
+ isFullscreen,
82
+ nodes,
83
+ onCopy: allowCopy ? handleCopy : void 0,
84
+ onFitView: handleFitView,
85
+ onFullscreen: allowFullscreen ? toggleFullscreen : void 0,
86
+ onNodeClick: onNodeClick ? handleNodeClick : void 0,
87
+ onZoomIn: zoomIn,
88
+ onZoomOut: zoomOut,
89
+ showControls,
90
+ title
91
+ };
92
+ if (isFullscreen) {
93
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
94
+ /* @__PURE__ */ jsx(
95
+ "div",
96
+ {
97
+ className: cn(
98
+ "rounded-lg border border-border bg-muted/50",
99
+ className
100
+ ),
101
+ style: { height }
102
+ }
103
+ ),
104
+ /* @__PURE__ */ jsx(FlowFullscreen, { isOpen: isFullscreen, onClose: closeFullscreen, children: /* @__PURE__ */ jsx(FlowCanvas, { ...canvasProps }) })
105
+ ] });
106
+ }
107
+ return /* @__PURE__ */ jsx(FlowCanvas, { ...canvasProps });
108
+ });
109
+ const FlowDiagram = memo(function FlowDiagram2(props) {
110
+ return /* @__PURE__ */ jsx(FlowErrorBoundary, { height: props.height, children: /* @__PURE__ */ jsx(ReactFlowProvider, { children: /* @__PURE__ */ jsx(FlowDiagramInner, { ...props }) }) });
111
+ });
112
+ export {
113
+ FlowDiagram
114
+ };
@@ -0,0 +1,63 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Component } from "react";
4
+ import { AlertTriangle, RefreshCw } from "lucide-react";
5
+ import { cn } from "../../lib/utils";
6
+ class FlowErrorBoundary extends Component {
7
+ constructor(props) {
8
+ super(props);
9
+ this.handleRetry = () => {
10
+ this.setState({ error: null, hasError: false });
11
+ };
12
+ this.state = { error: null, hasError: false };
13
+ }
14
+ static getDerivedStateFromError(error) {
15
+ return { error, hasError: true };
16
+ }
17
+ componentDidCatch(error, errorInfo) {
18
+ console.error("[FlowDiagram] Error caught by boundary:", error, errorInfo);
19
+ this.props.onError?.(error, errorInfo);
20
+ }
21
+ render() {
22
+ const { children, className, fallback, height = 400 } = this.props;
23
+ const { error, hasError } = this.state;
24
+ if (hasError) {
25
+ if (fallback) {
26
+ return fallback;
27
+ }
28
+ return /* @__PURE__ */ jsxs(
29
+ "div",
30
+ {
31
+ className: cn(
32
+ "flex flex-col items-center justify-center gap-4 rounded-lg border border-border bg-muted/50 p-8 text-center",
33
+ className
34
+ ),
35
+ style: { height },
36
+ children: [
37
+ /* @__PURE__ */ jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-destructive/10", children: /* @__PURE__ */ jsx(AlertTriangle, { className: "h-6 w-6 text-destructive" }) }),
38
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
39
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-foreground", children: "Failed to render diagram" }),
40
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: error?.message ?? "An unexpected error occurred while rendering the flow diagram." })
41
+ ] }),
42
+ /* @__PURE__ */ jsxs(
43
+ "button",
44
+ {
45
+ className: "inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90",
46
+ onClick: this.handleRetry,
47
+ type: "button",
48
+ children: [
49
+ /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4" }),
50
+ "Try again"
51
+ ]
52
+ }
53
+ )
54
+ ]
55
+ }
56
+ );
57
+ }
58
+ return children;
59
+ }
60
+ }
61
+ export {
62
+ FlowErrorBoundary
63
+ };
@@ -0,0 +1,58 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { memo, useEffect } from "react";
4
+ import { X } from "lucide-react";
5
+ import { createPortal } from "react-dom";
6
+ import { cn } from "../../lib/utils";
7
+ const FlowFullscreen = memo(function FlowFullscreen2({
8
+ children,
9
+ isOpen,
10
+ onClose
11
+ }) {
12
+ useEffect(() => {
13
+ if (!isOpen) return;
14
+ const handleKeyDown = (e) => {
15
+ if (e.key === "Escape") {
16
+ onClose();
17
+ }
18
+ };
19
+ document.addEventListener("keydown", handleKeyDown);
20
+ return () => {
21
+ document.removeEventListener("keydown", handleKeyDown);
22
+ };
23
+ }, [isOpen, onClose]);
24
+ if (!isOpen) return null;
25
+ if (typeof document === "undefined") return null;
26
+ return createPortal(
27
+ /* @__PURE__ */ jsxs(
28
+ "div",
29
+ {
30
+ "aria-label": "Flow diagram fullscreen view",
31
+ "aria-modal": "true",
32
+ className: cn(
33
+ "fixed inset-0 z-[9999] flex flex-col bg-background",
34
+ "animate-in fade-in duration-200"
35
+ ),
36
+ role: "dialog",
37
+ children: [
38
+ /* @__PURE__ */ jsx("div", { className: "flex h-12 items-center justify-end border-b border-border px-4", children: /* @__PURE__ */ jsx(
39
+ "button",
40
+ {
41
+ "aria-label": "Close fullscreen",
42
+ className: "flex h-8 w-8 items-center justify-center rounded hover:bg-muted transition-colors",
43
+ onClick: onClose,
44
+ title: "Close fullscreen (Esc)",
45
+ type: "button",
46
+ children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" })
47
+ }
48
+ ) }),
49
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-hidden", children })
50
+ ]
51
+ }
52
+ ),
53
+ document.body
54
+ );
55
+ });
56
+ export {
57
+ FlowFullscreen
58
+ };
@@ -0,0 +1,12 @@
1
+ import { FlowDiagram } from "./flow-diagram";
2
+ import { FlowControls } from "./flow-controls";
3
+ import { FlowErrorBoundary } from "./flow-error-boundary";
4
+ import { FlowFullscreen } from "./flow-fullscreen";
5
+ import { useFlowDiagram } from "./use-flow-diagram";
6
+ export {
7
+ FlowControls,
8
+ FlowDiagram,
9
+ FlowErrorBoundary,
10
+ FlowFullscreen,
11
+ useFlowDiagram
12
+ };
File without changes
@@ -0,0 +1,141 @@
1
+ "use client";
2
+ import { useCallback, useEffect, useState } from "react";
3
+ import {
4
+ getNodesBounds,
5
+ getViewportForBounds,
6
+ useReactFlow
7
+ } from "@xyflow/react";
8
+ import { toPng } from "html-to-image";
9
+ const MIN_ZOOM = 0.1;
10
+ const MAX_ZOOM = 2;
11
+ const ZOOM_STEP = 0.2;
12
+ function useZoomControls(reactFlow) {
13
+ const [zoom, setZoom] = useState(1);
14
+ const zoomIn = useCallback(() => {
15
+ const newZoom = Math.min(zoom + ZOOM_STEP, MAX_ZOOM);
16
+ setZoom(newZoom);
17
+ void reactFlow.zoomTo(newZoom, { duration: 200 });
18
+ }, [zoom, reactFlow]);
19
+ const zoomOut = useCallback(() => {
20
+ const newZoom = Math.max(zoom - ZOOM_STEP, MIN_ZOOM);
21
+ setZoom(newZoom);
22
+ void reactFlow.zoomTo(newZoom, { duration: 200 });
23
+ }, [zoom, reactFlow]);
24
+ const fitView = useCallback(() => {
25
+ void reactFlow.fitView({ duration: 200, padding: 0.2 });
26
+ }, [reactFlow]);
27
+ useEffect(() => {
28
+ const viewport = reactFlow.getViewport();
29
+ setZoom(viewport.zoom);
30
+ }, [reactFlow]);
31
+ return { fitView, zoom, zoomIn, zoomOut };
32
+ }
33
+ function useFullscreenState(allowFullscreen) {
34
+ const [isFullscreen, setIsFullscreen] = useState(false);
35
+ const toggleFullscreen = useCallback(() => {
36
+ if (!allowFullscreen) return;
37
+ setIsFullscreen((previous) => !previous);
38
+ }, [allowFullscreen]);
39
+ const closeFullscreen = useCallback(() => {
40
+ setIsFullscreen(false);
41
+ }, []);
42
+ useEffect(() => {
43
+ if (!isFullscreen) return;
44
+ const handleKeyDown = (e) => {
45
+ if (e.key === "Escape") {
46
+ closeFullscreen();
47
+ }
48
+ };
49
+ document.addEventListener("keydown", handleKeyDown);
50
+ return () => {
51
+ document.removeEventListener("keydown", handleKeyDown);
52
+ };
53
+ }, [isFullscreen, closeFullscreen]);
54
+ useEffect(() => {
55
+ document.body.style.overflow = isFullscreen ? "hidden" : "";
56
+ return () => {
57
+ document.body.style.overflow = "";
58
+ };
59
+ }, [isFullscreen]);
60
+ return { closeFullscreen, isFullscreen, toggleFullscreen };
61
+ }
62
+ const IMAGE_WIDTH = 1024;
63
+ const IMAGE_HEIGHT = 768;
64
+ const COPY_SUCCESS_DURATION = 2e3;
65
+ async function captureFlowImage(reactFlow) {
66
+ const nodes = reactFlow.getNodes();
67
+ if (nodes.length === 0) {
68
+ throw new Error("Cannot copy: no nodes in diagram");
69
+ }
70
+ const nodesBounds = getNodesBounds(nodes);
71
+ const viewport = getViewportForBounds(
72
+ nodesBounds,
73
+ IMAGE_WIDTH,
74
+ IMAGE_HEIGHT,
75
+ 0.5,
76
+ 2,
77
+ 0.2
78
+ );
79
+ const flowElement = document.querySelector(".react-flow__viewport");
80
+ if (!flowElement || !(flowElement instanceof HTMLElement)) {
81
+ throw new Error("Cannot copy: flow viewport element not found");
82
+ }
83
+ const dataUrl = await toPng(flowElement, {
84
+ backgroundColor: "white",
85
+ height: IMAGE_HEIGHT,
86
+ style: {
87
+ height: String(IMAGE_HEIGHT),
88
+ transform: `translate(${viewport.x}px, ${viewport.y}px) scale(${viewport.zoom})`,
89
+ width: String(IMAGE_WIDTH)
90
+ },
91
+ width: IMAGE_WIDTH
92
+ });
93
+ const response = await fetch(dataUrl);
94
+ if (!response.ok) {
95
+ throw new Error("Failed to fetch image data");
96
+ }
97
+ return response.blob();
98
+ }
99
+ function useCopyToClipboard(reactFlow) {
100
+ const [copyStatus, setCopyStatus] = useState("idle");
101
+ const copyToClipboard = useCallback(async () => {
102
+ setCopyStatus("copying");
103
+ try {
104
+ const blob = await captureFlowImage(reactFlow);
105
+ const clipboardItem = new ClipboardItem({ ["image/png"]: blob });
106
+ await navigator.clipboard.write([clipboardItem]);
107
+ setCopyStatus("success");
108
+ setTimeout(() => {
109
+ setCopyStatus("idle");
110
+ }, COPY_SUCCESS_DURATION);
111
+ } catch (error) {
112
+ console.error("[FlowDiagram] Copy failed:", error);
113
+ setCopyStatus("error");
114
+ setTimeout(() => {
115
+ setCopyStatus("idle");
116
+ }, COPY_SUCCESS_DURATION);
117
+ }
118
+ }, [reactFlow]);
119
+ return { copyStatus, copyToClipboard };
120
+ }
121
+ function useFlowDiagram(options = {}) {
122
+ const { allowFullscreen = true } = options;
123
+ const reactFlow = useReactFlow();
124
+ const { fitView, zoom, zoomIn, zoomOut } = useZoomControls(reactFlow);
125
+ const { closeFullscreen, isFullscreen, toggleFullscreen } = useFullscreenState(allowFullscreen);
126
+ const { copyStatus, copyToClipboard } = useCopyToClipboard(reactFlow);
127
+ return {
128
+ closeFullscreen,
129
+ copyStatus,
130
+ copyToClipboard,
131
+ fitView,
132
+ isFullscreen,
133
+ toggleFullscreen,
134
+ zoom,
135
+ zoomIn,
136
+ zoomOut
137
+ };
138
+ }
139
+ export {
140
+ useFlowDiagram
141
+ };
@@ -0,0 +1,26 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { forwardRef } from "react";
4
+ import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
5
+ import { cn } from "../../lib/utils";
6
+ const HoverCard = HoverCardPrimitive.Root;
7
+ const HoverCardTrigger = HoverCardPrimitive.Trigger;
8
+ const HoverCardContent = forwardRef(({ align = "center", className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx(
9
+ HoverCardPrimitive.Content,
10
+ {
11
+ align,
12
+ className: cn(
13
+ "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none 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",
14
+ className
15
+ ),
16
+ ref,
17
+ sideOffset,
18
+ ...props
19
+ }
20
+ ));
21
+ HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
22
+ export {
23
+ HoverCard,
24
+ HoverCardContent,
25
+ HoverCardTrigger
26
+ };
@@ -0,0 +1,6 @@
1
+ import { HoverCard, HoverCardContent, HoverCardTrigger } from "./hover-card";
2
+ export {
3
+ HoverCard,
4
+ HoverCardContent,
5
+ HoverCardTrigger
6
+ };