@keboola/design 1.0.0 → 1.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.
@@ -0,0 +1,996 @@
1
+ import { useId, useState, useRef, useEffect, useCallback } from 'react';
2
+ import { renderMermaidSVG } from 'beautiful-mermaid';
3
+ import colors from '@keboola/tailwind-config/colors';
4
+ import { clsx } from 'clsx';
5
+ import { extendTailwindMerge } from 'tailwind-merge';
6
+ import { Root as Root$1, Trigger, Portal, Content } from '@radix-ui/react-tooltip';
7
+ import { cva } from 'class-variance-authority';
8
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
+ import { Root, Slottable } from '@radix-ui/react-slot';
10
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
11
+ import { Tooltip as Tooltip$1, cn as cn$1 } from '@keboola/design';
12
+
13
+ // src/blocks/MermaidDiagram/MermaidDiagram.tsx
14
+ var twMerge = extendTailwindMerge({
15
+ prefix: "tw-"
16
+ });
17
+ var cn = (...classes) => twMerge(clsx(...classes));
18
+
19
+ // src/utils/copyToClipboard.ts
20
+ var copyToClipboard = (text) => {
21
+ return navigator.clipboard.writeText(text);
22
+ };
23
+
24
+ // src/constants/keyCodes.ts
25
+ var keyCodes = {
26
+ ENTER: "Enter",
27
+ SPACE: " "
28
+ };
29
+ var tooltipVariants = cva(
30
+ [
31
+ "tailwindcss-preflight",
32
+ "tw-inline-block",
33
+ "tw-rounded",
34
+ "tw-bg-neutral-800",
35
+ "tw-text-white",
36
+ "tw-break-anywhere",
37
+ "tw-max-h-96",
38
+ "tw-overflow-y-auto",
39
+ "tw-z-[1060]"
40
+ ],
41
+ {
42
+ defaultVariants: {
43
+ variant: "action"
44
+ },
45
+ variants: {
46
+ variant: {
47
+ action: [
48
+ "tw-max-w-fit",
49
+ "tw-px-3",
50
+ "tw-py-2",
51
+ "tw-text-center",
52
+ "tw-text-xs",
53
+ "tw-font-medium",
54
+ "tw-uppercase",
55
+ "tw-tracking-wider"
56
+ ],
57
+ explanatory: [
58
+ "tw-max-w-80",
59
+ "tw-px-4",
60
+ "tw-py-3",
61
+ "tw-text-left",
62
+ "tw-text-sm",
63
+ "tw-font-normal"
64
+ ]
65
+ }
66
+ }
67
+ }
68
+ );
69
+ var TooltipContent = ({
70
+ className,
71
+ variant,
72
+ keepOpenOnPointerDownOutside = false,
73
+ children,
74
+ onPointerDownOutside,
75
+ sideOffset = 8,
76
+ collisionPadding = 8,
77
+ hideWhenDetached = true,
78
+ placement = "right",
79
+ ...rest
80
+ }) => {
81
+ const handlePointerDownOutside = (event) => {
82
+ if (keepOpenOnPointerDownOutside) event.preventDefault();
83
+ onPointerDownOutside?.(event);
84
+ };
85
+ return /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
86
+ Content,
87
+ {
88
+ ...rest,
89
+ "data-tooltip-content": true,
90
+ sideOffset,
91
+ collisionPadding,
92
+ onPointerDownOutside: handlePointerDownOutside,
93
+ className: tooltipVariants({ className, variant }),
94
+ hideWhenDetached,
95
+ side: placement,
96
+ children
97
+ }
98
+ ) });
99
+ };
100
+ var DELAY_SHOW_TOOLTIP = 300;
101
+ var TooltipRoot = ({ children, delayDuration = DELAY_SHOW_TOOLTIP, ...rest }) => /* @__PURE__ */ jsx(Root$1, { ...rest, delayDuration, children });
102
+ var TooltipTrigger = ({
103
+ ref,
104
+ children,
105
+ className,
106
+ keepOpenOnClick = false,
107
+ onClick,
108
+ onPointerDown,
109
+ asChild,
110
+ ...rest
111
+ }) => {
112
+ const handleClick = (event) => {
113
+ if (keepOpenOnClick) event.preventDefault();
114
+ onClick?.(event);
115
+ };
116
+ const handePointerDown = (event) => {
117
+ if (keepOpenOnClick) event.preventDefault();
118
+ onPointerDown?.(event);
119
+ };
120
+ return /* @__PURE__ */ jsx(
121
+ Trigger,
122
+ {
123
+ ...rest,
124
+ asChild: true,
125
+ ref,
126
+ className: cn("tailwindcss-preflight", className),
127
+ onClick: handleClick,
128
+ onPointerDown: handePointerDown,
129
+ children: asChild ? children : /* @__PURE__ */ jsx("span", { children })
130
+ }
131
+ );
132
+ };
133
+ var ACTION_TOOLTIP_LIMIT = 30;
134
+ var Tooltip = ({
135
+ tooltip,
136
+ children,
137
+ className,
138
+ keepOpenOnTriggerClick = false,
139
+ variant,
140
+ placement = "right",
141
+ forceHide = false,
142
+ forceShow = false,
143
+ // trigger props
144
+ triggerAsChild = false
145
+ }) => {
146
+ const [open, setOpen] = useState(false);
147
+ const resolvedVariant = (() => {
148
+ if (!!variant) return variant;
149
+ if (typeof tooltip === "string" && tooltip.length > ACTION_TOOLTIP_LIMIT) return "explanatory";
150
+ return "action";
151
+ })();
152
+ const isActionVariant = resolvedVariant === "action";
153
+ const handleKeyUp = (event) => {
154
+ if (![keyCodes.ENTER, keyCodes.SPACE].includes(event.key)) return;
155
+ event.stopPropagation();
156
+ };
157
+ return /* @__PURE__ */ jsxs(
158
+ TooltipRoot,
159
+ {
160
+ open: !!tooltip && (open || forceShow) && !forceHide,
161
+ onOpenChange: setOpen,
162
+ disableHoverableContent: isActionVariant,
163
+ children: [
164
+ /* @__PURE__ */ jsx(
165
+ TooltipTrigger,
166
+ {
167
+ asChild: triggerAsChild,
168
+ keepOpenOnClick: keepOpenOnTriggerClick,
169
+ onKeyUp: handleKeyUp,
170
+ children
171
+ }
172
+ ),
173
+ /* @__PURE__ */ jsx(
174
+ TooltipContent,
175
+ {
176
+ className,
177
+ variant: resolvedVariant,
178
+ placement,
179
+ keepOpenOnPointerDownOutside: keepOpenOnTriggerClick,
180
+ children: tooltip
181
+ }
182
+ )
183
+ ]
184
+ }
185
+ );
186
+ };
187
+ Tooltip.Root = TooltipRoot;
188
+ Tooltip.Content = TooltipContent;
189
+ Tooltip.Trigger = TooltipTrigger;
190
+ var HEIGHT_CLASS_NAME = {
191
+ xxs: "tw-h-3 tw-rounded",
192
+ xs: "tw-h-4 tw-rounded",
193
+ sm: "tw-h-5 tw-rounded",
194
+ md: "tw-h-6 tw-rounded-md",
195
+ lg: "tw-h-8 tw-rounded-lg",
196
+ xl: "tw-h-10 tw-rounded-lg",
197
+ "2xl": "tw-h-12 tw-rounded-lg"
198
+ };
199
+ var ANIMATION_CLASS_NAME = cn(
200
+ // base styles
201
+ "tw-relative",
202
+ "tw-overflow-hidden",
203
+ "tw-pointer-events-none",
204
+ "tw-bg-neutral-150",
205
+ // before
206
+ 'before:tw-content-[""]',
207
+ "before:tw-opacity-100",
208
+ "before:tw-absolute",
209
+ "before:tw-inset-0",
210
+ "before:-tw-translate-x-full",
211
+ "before:tw-animate-[shimmer_2s_infinite]",
212
+ "before:tw-bg-gradient-to-r",
213
+ "before:tw-from-transparent",
214
+ "before:tw-to-transparent",
215
+ "before:tw-via-neutral-50"
216
+ );
217
+ var Skeleton = ({ height = "md", className, count = 1 }) => {
218
+ const prefixId = useId();
219
+ const keys = Array.from({ length: count }, (_, i) => `${prefixId}-${i}`);
220
+ return /* @__PURE__ */ jsx(Fragment, { children: keys.map((key) => /* @__PURE__ */ jsx("div", { className: cn(HEIGHT_CLASS_NAME[height], ANIMATION_CLASS_NAME, className) }, key)) });
221
+ };
222
+ var Icon = ({ ref, icon, ...rest }) => /* @__PURE__ */ jsx(FontAwesomeIcon, { ...rest, ref, icon });
223
+ var variants = cva(
224
+ [
225
+ "tailwindcss-preflight",
226
+ "tw-group/button",
227
+ "tw-border-box",
228
+ // layout
229
+ "tw-relative",
230
+ "tw-flex",
231
+ "tw-items-center",
232
+ "tw-justify-center",
233
+ "tw-gap-2",
234
+ // text
235
+ "tw-text-xs",
236
+ "tw-leading-5",
237
+ "tw-font-medium",
238
+ "tw-tracking-[1px]",
239
+ "tw-uppercase",
240
+ // border
241
+ "tw-rounded",
242
+ "tw-border",
243
+ "tw-border-transparent",
244
+ // styling
245
+ "tw-shadow-none",
246
+ "[a]:tw-cursor-pointer",
247
+ // explicitly set for anchor
248
+ "focus-visible:tw-outline",
249
+ "focus-visible:tw-outline-1",
250
+ "focus-visible:-tw-outline-offset-1",
251
+ "focus-visible:tw-outline-secondary-500",
252
+ "focus-visible:tw-ring-4",
253
+ "focus-visible:tw-ring-secondary-200",
254
+ // disabled
255
+ "disabled:tw-opacity-40",
256
+ "disabled:tw-cursor-not-allowed",
257
+ // anchor support
258
+ "!tw-no-underline",
259
+ "aria-disabled:tw-opacity-40",
260
+ "aria-disabled:tw-cursor-not-allowed",
261
+ "[&_svg]:tw-shrink-0"
262
+ ],
263
+ {
264
+ defaultVariants: {
265
+ size: "medium",
266
+ variant: "primary",
267
+ block: false
268
+ },
269
+ variants: {
270
+ block: {
271
+ true: ["tw-w-full"]
272
+ },
273
+ variant: {
274
+ primary: [
275
+ "tw-text-white",
276
+ "tw-bg-primary-500",
277
+ "focus:tw-text-white",
278
+ "hover:tw-text-white",
279
+ "hover:not-disabled:tw-bg-primary-600",
280
+ "active:not-disabled:tw-bg-primary-800",
281
+ // anchor disabled override
282
+ "aria-disabled:!tw-bg-primary-500"
283
+ ],
284
+ secondary: [
285
+ "tw-text-white",
286
+ "tw-bg-secondary-500",
287
+ "focus:tw-text-white",
288
+ "hover:tw-text-white",
289
+ "hover:not-disabled:tw-bg-secondary-700",
290
+ "active:not-disabled:tw-bg-secondary-800",
291
+ // anchor disabled override
292
+ "aria-disabled:!tw-bg-secondary-500"
293
+ ],
294
+ danger: [
295
+ "tw-text-white",
296
+ "tw-bg-error-500",
297
+ "focus:tw-text-white",
298
+ "hover:tw-text-white",
299
+ "hover:not-disabled:tw-bg-error-600",
300
+ "active:not-disabled:tw-bg-error-700",
301
+ // anchor disabled override
302
+ "aria-disabled:!tw-bg-error-500"
303
+ ],
304
+ invisible: [
305
+ "tw-text-neutral-800",
306
+ "tw-bg-transparent",
307
+ "focus:tw-text-neutral-800",
308
+ "hover:not-disabled:tw-bg-neutral-100",
309
+ "hover:tw-text-neutral-800",
310
+ "active:not-disabled:tw-bg-neutral-150",
311
+ // contrasting icon on hover/active
312
+ "[&_svg]:tw-text-neutral-400",
313
+ "[&_svg]:hover:not-disabled:tw-text-secondary-600",
314
+ "[&_svg]:active:not-disabled:tw-text-secondary-600",
315
+ // anchor disabled override
316
+ "aria-disabled:!tw-bg-transparent"
317
+ ],
318
+ outline: [
319
+ "tw-text-neutral-800",
320
+ "tw-bg-white",
321
+ // border
322
+ "!tw-border-neutral-200",
323
+ "hover:tw-text-neutral-800",
324
+ "focus:tw-text-neutral-800",
325
+ "hover:not-disabled:!tw-border-neutral-300",
326
+ "hover:not-disabled:tw-bg-neutral-100",
327
+ "active:not-disabled:tw-bg-neutral-200",
328
+ // contrasting icon hover, same as invisible
329
+ "[&_svg]:tw-text-neutral-400",
330
+ "[&_svg]:not-disabled:hover:tw-text-secondary-600",
331
+ "[&_svg]:active:not-disabled:tw-text-secondary-600",
332
+ // anchor disabled override
333
+ "aria-disabled:!tw-bg-white",
334
+ "aria-disabled:!tw-border-neutral-200"
335
+ ],
336
+ inline: [
337
+ "tw-text-neutral-800",
338
+ "tw-bg-transparent",
339
+ "tw-rounded-full",
340
+ "[&_svg]:tw-text-neutral-400",
341
+ "[&_svg]:not-disabled:hover:tw-text-secondary-600",
342
+ "[&_svg]:not-disabled:focus:tw-text-secondary-600"
343
+ ]
344
+ },
345
+ size: {
346
+ medium: ["tw-max-h-10", "tw-min-h-10", "tw-px-4", "tw-py-2.5", "[&_svg]:tw-size-4"],
347
+ small: ["tw-max-h-8", "tw-min-h-8", "tw-px-4", "tw-py-1.5", "[&_svg]:tw-size-4"],
348
+ large: ["tw-max-h-12", "tw-min-h-12", "tw-px-4", "tw-py-3.5", "[&_svg]:tw-size-4"],
349
+ "icon-lg": ["tw-size-12", "tw-p-0", "[&_svg]:tw-size-6"],
350
+ icon: ["tw-size-10", "tw-p-0", "[&_svg]:tw-size-4"],
351
+ "icon-sm": ["tw-size-8", "tw-p-0", "[&_svg]:tw-size-4"],
352
+ "icon-xs": ["tw-size-6", "tw-p-0", "[&_svg]:tw-size-4"],
353
+ "icon-2xs": ["!tw-inline-flex", "tw-size-5", "tw-max-h-5", "[&_svg]:tw-size-3"],
354
+ "icon-3xs": ["tw-h-fit", "tw-w-fit", "[&_svg]:tw-size-4"]
355
+ }
356
+ }
357
+ }
358
+ );
359
+ var argoVariants = cva(
360
+ [
361
+ "tailwindcss-preflight",
362
+ // layout
363
+ "tw-relative",
364
+ "tw-inline-flex",
365
+ "tw-items-center",
366
+ "tw-justify-center",
367
+ "tw-whitespace-nowrap",
368
+ "tw-gap-2",
369
+ // border
370
+ "tw-border",
371
+ "tw-border-transparent",
372
+ "tw-rounded-lg",
373
+ // text
374
+ "tw-font-medium",
375
+ "tw-select-none",
376
+ // focus visible
377
+ "!tw-outline-none",
378
+ "focus-visible:tw-ring-2",
379
+ // disabled
380
+ "disabled:tw-cursor-not-allowed",
381
+ "disabled:tw-opacity-50",
382
+ "active:tw-opacity-60",
383
+ "tw-transition-all",
384
+ "tw-bg-clip-padding",
385
+ // anchor support
386
+ "!tw-no-underline",
387
+ // icon
388
+ "tw-shrink-0",
389
+ "[&_svg]:tw-pointer-events-none",
390
+ "[&_svg]:tw-shrink-0",
391
+ "[&_svg:not([class*='tw-size-'])]:tw-size-4"
392
+ ],
393
+ {
394
+ defaultVariants: {
395
+ variant: "primary",
396
+ size: "medium"
397
+ },
398
+ variants: {
399
+ block: {
400
+ true: ["tw-w-full"]
401
+ },
402
+ variant: {
403
+ primary: [
404
+ "tw-bg-primary",
405
+ "tw-text-primary-foreground",
406
+ "[a]:hover:tw-bg-primary/80",
407
+ "hover:not-disabled:tw-bg-primary/90",
408
+ "hover:tw-text-primary-foreground",
409
+ "focus:tw-text-primary-foreground",
410
+ "focus-visible:tw-border-primary",
411
+ "focus-visible:tw-ring-primary/50"
412
+ ],
413
+ secondary: [
414
+ "tw-bg-secondary",
415
+ "tw-text-secondary-foreground",
416
+ "hover:tw-bg-secondary/80",
417
+ "hover:tw-text-secondary-foreground",
418
+ "focus:tw-text-secondary-foreground",
419
+ "focus-visible:tw-border-secondary",
420
+ "focus-visible:tw-ring-primary/50"
421
+ ],
422
+ // should be called "destructive" by Figma
423
+ danger: [
424
+ "tw-bg-destructive",
425
+ "tw-text-destructive-foreground",
426
+ "hover:tw-text-destructive-foreground",
427
+ "focus:tw-text-destructive-foreground",
428
+ "focus-visible:tw-border-destructive",
429
+ "focus-visible:tw-ring-destructive/20"
430
+ ],
431
+ outline: [
432
+ "!tw-border-input",
433
+ "tw-bg-background",
434
+ "hover:not-disabled:tw-bg-accent",
435
+ "hover:not-disabled:tw-text-accent-foreground",
436
+ "hover:active:tw-bg-background",
437
+ "focus-visible:!tw-border-primary",
438
+ "focus-visible:tw-ring-primary/50"
439
+ ],
440
+ // should be called "ghost" by Figma
441
+ invisible: [
442
+ "tw-bg-transparent",
443
+ "tw-text-foreground",
444
+ "hover:not-disabled:tw-bg-accent",
445
+ "hover:not-disabled:tw-text-accent-foreground",
446
+ "hover:not-disabled:tw-border-accent",
447
+ "focus-visible:tw-ring-primary/50"
448
+ ],
449
+ // should be called "link" by Figma
450
+ inline: [
451
+ "tw-bg-transparent",
452
+ "tw-text-primary",
453
+ "hover:tw-underline",
454
+ "hover:active:tw-bg-transparent",
455
+ "focus-visible:tw-ring-primary/50"
456
+ ]
457
+ },
458
+ size: {
459
+ // should be called "default" by Figma
460
+ medium: ["tw-max-h-9", "tw-py-2", "tw-px-4", "tw-text-sm"],
461
+ // should be called "small" by Figma
462
+ small: ["tw-max-h-8", "tw-py-2", "tw-px-3", "tw-text-xs"],
463
+ // should be called "large" by Figma
464
+ large: ["tw-max-h-10", "tw-py-2.5", "tw-px-6", "tw-text-sm"],
465
+ "icon-lg": ["tw-size-10"],
466
+ icon: ["tw-size-9"],
467
+ "icon-sm": ["tw-size-8"],
468
+ // unsupported in ARGO, remove when old design is gone
469
+ "icon-xs": [],
470
+ "icon-2xs": [],
471
+ "icon-3xs": []
472
+ }
473
+ }
474
+ }
475
+ );
476
+ var Button = ({
477
+ ref,
478
+ children,
479
+ disabled = false,
480
+ className,
481
+ type = "button",
482
+ icon: iconProp,
483
+ isLoading = false,
484
+ asChild = false,
485
+ onClick,
486
+ argo = false,
487
+ variant = "primary",
488
+ size = "medium",
489
+ block = false,
490
+ ...buttonProps
491
+ }) => {
492
+ const Comp = asChild ? Root : "button";
493
+ const icon = isLoading ? "spinner" : iconProp;
494
+ const classNames = argo ? argoVariants({ variant, size, block, className }) : variants({
495
+ size,
496
+ variant,
497
+ block,
498
+ className
499
+ });
500
+ const handleClick = (event) => {
501
+ const isAnchor = event.currentTarget.tagName === "A";
502
+ if (isAnchor && disabled) {
503
+ event.preventDefault();
504
+ return;
505
+ }
506
+ onClick?.(event);
507
+ };
508
+ return /* @__PURE__ */ jsxs(
509
+ Comp,
510
+ {
511
+ ...buttonProps,
512
+ ref,
513
+ disabled,
514
+ "aria-disabled": disabled,
515
+ "data-variant": variant,
516
+ "data-icon-only": !children,
517
+ type,
518
+ onClick: handleClick,
519
+ className: cn(classNames),
520
+ children: [
521
+ icon && /* @__PURE__ */ jsx(Icon, { spin: isLoading, icon }),
522
+ /* @__PURE__ */ jsx(Slottable, { children })
523
+ ]
524
+ }
525
+ );
526
+ };
527
+ var Clipboard = ({
528
+ ref,
529
+ text: textProp,
530
+ children,
531
+ tooltip = "Copy to Clipboard",
532
+ tooltipPlacement = "top",
533
+ tooltipVariant: tooltipVariantProp = "action",
534
+ asChild = false,
535
+ onClick,
536
+ className,
537
+ ...restButtonProps
538
+ }) => {
539
+ const [isTooltipOpen, setIsTooltipOpen] = useState(false);
540
+ const [state, setState] = useState("ready");
541
+ const text = typeof textProp === "function" ? textProp() : textProp;
542
+ const copy = async (text2) => {
543
+ try {
544
+ if (!document.hasFocus()) window.focus();
545
+ await copyToClipboard(text2);
546
+ setState("copied");
547
+ } catch {
548
+ setState("error");
549
+ }
550
+ };
551
+ const handleOnClick = (event) => {
552
+ if (text && state === "ready") void copy(text);
553
+ onClick?.(event);
554
+ };
555
+ const handleOnOpenChange = (open) => {
556
+ if (!open && state !== "ready") setState("ready");
557
+ setIsTooltipOpen(open);
558
+ };
559
+ const tooltipContent = (() => {
560
+ if (!text) return "Nothing to copy";
561
+ if (state === "error") return "Failed to copy";
562
+ if (state === "copied") return "Copied to Clipboard";
563
+ return tooltip;
564
+ })();
565
+ const tooltipVariant = state === "ready" && !!text ? tooltipVariantProp : "action";
566
+ return /* @__PURE__ */ jsxs(Tooltip$1.Root, { open: isTooltipOpen, onOpenChange: handleOnOpenChange, children: [
567
+ /* @__PURE__ */ jsx(
568
+ Tooltip$1.Trigger,
569
+ {
570
+ ...restButtonProps,
571
+ asChild,
572
+ keepOpenOnClick: true,
573
+ onClick: handleOnClick,
574
+ className: cn$1("tw-cursor-pointer", className),
575
+ ref,
576
+ children: children ?? text
577
+ }
578
+ ),
579
+ /* @__PURE__ */ jsx(
580
+ Tooltip$1.Content,
581
+ {
582
+ keepOpenOnPointerDownOutside: true,
583
+ placement: tooltipPlacement,
584
+ variant: tooltipVariant,
585
+ children: tooltipContent
586
+ }
587
+ )
588
+ ] });
589
+ };
590
+ var DEFAULT_ICON = "copy";
591
+ var ClipboardButton = ({
592
+ // clipboard props
593
+ text,
594
+ tooltip,
595
+ tooltipPlacement,
596
+ tooltipVariant,
597
+ // button props
598
+ icon = DEFAULT_ICON,
599
+ variant = "outline",
600
+ children,
601
+ ...rest
602
+ }) => /* @__PURE__ */ jsx(
603
+ Clipboard,
604
+ {
605
+ asChild: true,
606
+ tooltip,
607
+ tooltipPlacement,
608
+ tooltipVariant,
609
+ text,
610
+ children: /* @__PURE__ */ jsx(Button, { ...rest, icon, variant, disabled: !text, children })
611
+ }
612
+ );
613
+ var MIN_SCALE = 0.5;
614
+ var MAX_SCALE = 3;
615
+ var ZOOM_STEP = 0.1;
616
+ var ZoomableSvg = ({ html, copyText, className }) => {
617
+ const containerRef = useRef(null);
618
+ const [scale, setScale] = useState(1);
619
+ const [translate, setTranslate] = useState({ x: 0, y: 0 });
620
+ const [isFullscreen, setIsFullscreen] = useState(false);
621
+ useEffect(() => {
622
+ if (!isFullscreen) return;
623
+ const onKeyDown = (e) => {
624
+ if (e.key === "Escape") {
625
+ e.stopPropagation();
626
+ setIsFullscreen(false);
627
+ }
628
+ };
629
+ document.addEventListener("keydown", onKeyDown, { capture: true });
630
+ return () => document.removeEventListener("keydown", onKeyDown, { capture: true });
631
+ }, [isFullscreen]);
632
+ const dragRef = useRef(null);
633
+ useEffect(() => {
634
+ const el = containerRef.current;
635
+ if (!el) return;
636
+ const onWheel = (e) => {
637
+ if (!e.ctrlKey && !e.metaKey) return;
638
+ e.preventDefault();
639
+ const delta = e.deltaY > 0 ? -ZOOM_STEP : ZOOM_STEP;
640
+ setScale((s) => Math.min(MAX_SCALE, Math.max(MIN_SCALE, s + delta)));
641
+ };
642
+ el.addEventListener("wheel", onWheel, { passive: false });
643
+ return () => el.removeEventListener("wheel", onWheel);
644
+ }, []);
645
+ const didDragRef = useRef(false);
646
+ const handleMouseDown = useCallback(
647
+ (e) => {
648
+ if (e.button !== 0) return;
649
+ e.preventDefault();
650
+ didDragRef.current = false;
651
+ dragRef.current = {
652
+ startX: e.clientX,
653
+ startY: e.clientY,
654
+ startTx: translate.x,
655
+ startTy: translate.y
656
+ };
657
+ },
658
+ [translate]
659
+ );
660
+ const handleMouseMove = useCallback((e) => {
661
+ if (!dragRef.current) return;
662
+ didDragRef.current = true;
663
+ setTranslate({
664
+ x: dragRef.current.startTx + e.clientX - dragRef.current.startX,
665
+ y: dragRef.current.startTy + e.clientY - dragRef.current.startY
666
+ });
667
+ }, []);
668
+ const handleMouseUp = useCallback(() => {
669
+ dragRef.current = null;
670
+ }, []);
671
+ const handleReset = useCallback(() => {
672
+ setScale(1);
673
+ setTranslate({ x: 0, y: 0 });
674
+ }, []);
675
+ const isZoomed = Math.abs(scale - 1) > 1e-3 || translate.x !== 0 || translate.y !== 0;
676
+ const handleClick = useCallback((e) => {
677
+ if (didDragRef.current) {
678
+ e.stopPropagation();
679
+ }
680
+ }, []);
681
+ const stopPropagation = useCallback((e) => {
682
+ e.stopPropagation();
683
+ }, []);
684
+ const diagramContent = /* @__PURE__ */ jsxs(
685
+ "div",
686
+ {
687
+ role: "presentation",
688
+ className: cn("tw-group/zoom tw-relative", isFullscreen && "tw-h-full"),
689
+ onClick: handleClick,
690
+ children: [
691
+ /* @__PURE__ */ jsx(
692
+ "div",
693
+ {
694
+ role: "presentation",
695
+ ref: containerRef,
696
+ className: cn(
697
+ "tw-overflow-hidden tw-cursor-grab active:tw-cursor-grabbing tw-p-3",
698
+ isFullscreen && "tw-h-full"
699
+ ),
700
+ onMouseDown: handleMouseDown,
701
+ onMouseMove: handleMouseMove,
702
+ onMouseUp: handleMouseUp,
703
+ onMouseLeave: handleMouseUp,
704
+ children: /* @__PURE__ */ jsx(
705
+ "div",
706
+ {
707
+ className: cn(
708
+ "tw-flex tw-justify-center tw-origin-center",
709
+ isFullscreen && "tw-h-full [&>svg]:tw-h-full"
710
+ ),
711
+ style: { transform: `translate(${translate.x}px, ${translate.y}px) scale(${scale})` },
712
+ dangerouslySetInnerHTML: { __html: html }
713
+ }
714
+ )
715
+ }
716
+ ),
717
+ /* @__PURE__ */ jsxs(
718
+ "div",
719
+ {
720
+ role: "presentation",
721
+ className: "tw-absolute tw-right-1 tw-top-1 tw-flex tw-gap-1 tw-opacity-0 tw-transition-opacity group-hover/zoom:tw-opacity-100",
722
+ onClick: stopPropagation,
723
+ children: [
724
+ isZoomed && /* @__PURE__ */ jsx(Tooltip, { tooltip: "Reset zoom", placement: "top", triggerAsChild: true, children: /* @__PURE__ */ jsx(Button, { size: "small", variant: "outline", onClick: handleReset, argo: true, children: "Reset" }) }),
725
+ copyText && /* @__PURE__ */ jsx(
726
+ ClipboardButton,
727
+ {
728
+ text: copyText,
729
+ tooltip: "Copy code",
730
+ size: "icon-sm",
731
+ variant: "outline",
732
+ argo: true
733
+ }
734
+ ),
735
+ /* @__PURE__ */ jsx(
736
+ Tooltip,
737
+ {
738
+ tooltip: isFullscreen ? "Exit fullscreen" : "Fullscreen",
739
+ placement: "top",
740
+ triggerAsChild: true,
741
+ children: /* @__PURE__ */ jsx(
742
+ Button,
743
+ {
744
+ size: "icon-sm",
745
+ variant: "outline",
746
+ icon: isFullscreen ? "down-left-and-up-right-to-center" : "up-right-and-down-left-from-center",
747
+ "aria-label": isFullscreen ? "Exit fullscreen" : "Fullscreen",
748
+ onClick: () => setIsFullscreen((f) => !f),
749
+ argo: true
750
+ }
751
+ )
752
+ }
753
+ )
754
+ ]
755
+ }
756
+ )
757
+ ]
758
+ }
759
+ );
760
+ if (isFullscreen) {
761
+ return /* @__PURE__ */ jsx(
762
+ "div",
763
+ {
764
+ role: "presentation",
765
+ className: "tw-fixed tw-inset-0 tw-z-[1060] tw-bg-neutral-50 tw-p-4",
766
+ onClick: stopPropagation,
767
+ children: diagramContent
768
+ }
769
+ );
770
+ }
771
+ return /* @__PURE__ */ jsx(
772
+ "div",
773
+ {
774
+ className: cn(
775
+ "tw-overflow-hidden tw-rounded-md tw-border tw-border-neutral-150 tw-bg-neutral-50",
776
+ className
777
+ ),
778
+ children: diagramContent
779
+ }
780
+ );
781
+ };
782
+ var mermaidPromise = null;
783
+ var getMermaid = () => {
784
+ if (!mermaidPromise) {
785
+ mermaidPromise = import('mermaid').then((m) => {
786
+ m.default.initialize({
787
+ startOnLoad: false,
788
+ securityLevel: "strict",
789
+ theme: "base",
790
+ themeVariables: {
791
+ primaryColor: colors.secondary[100],
792
+ primaryTextColor: colors.neutral[800],
793
+ primaryBorderColor: colors.secondary[700],
794
+ secondaryColor: colors.white,
795
+ secondaryTextColor: colors.neutral[800],
796
+ secondaryBorderColor: colors.neutral[150],
797
+ tertiaryColor: colors.warning[100],
798
+ tertiaryTextColor: colors.neutral[800],
799
+ tertiaryBorderColor: colors.warning[500],
800
+ lineColor: colors.neutral[300],
801
+ textColor: colors.neutral[800],
802
+ mainBkg: colors.secondary[100],
803
+ nodeBorder: colors.secondary[700],
804
+ clusterBkg: colors.white,
805
+ clusterBorder: colors.neutral[150],
806
+ titleColor: colors.neutral[800],
807
+ edgeLabelBackground: colors.white,
808
+ nodeTextColor: colors.neutral[800],
809
+ actorBorder: colors.secondary[700],
810
+ actorBkg: colors.secondary[100],
811
+ actorTextColor: colors.neutral[800],
812
+ actorLineColor: colors.neutral[300],
813
+ signalColor: colors.neutral[800],
814
+ signalTextColor: colors.neutral[800],
815
+ labelBoxBkgColor: colors.secondary[100],
816
+ labelBoxBorderColor: colors.secondary[700],
817
+ labelTextColor: colors.neutral[800],
818
+ loopTextColor: colors.neutral[800],
819
+ noteBorderColor: colors.warning[500],
820
+ noteBkgColor: colors.warning[100],
821
+ noteTextColor: colors.neutral[800],
822
+ sectionBkgColor: colors.secondary[100],
823
+ altSectionBkgColor: colors.white,
824
+ sectionBkgColor2: colors.neutral[100],
825
+ taskBorderColor: colors.secondary[700],
826
+ taskBkgColor: colors.secondary[100],
827
+ taskTextColor: colors.neutral[800],
828
+ taskTextDarkColor: colors.neutral[800],
829
+ taskTextLightColor: colors.neutral[800],
830
+ activeTaskBorderColor: colors.primary.DEFAULT,
831
+ activeTaskBkgColor: colors.secondary[200],
832
+ doneTaskBorderColor: colors.primary[600],
833
+ doneTaskBkgColor: colors.primary[100],
834
+ critBorderColor: colors.error[500],
835
+ critBkgColor: colors.error[100],
836
+ fontFamily: "Inter, system-ui, sans-serif",
837
+ fontSize: "14px"
838
+ }
839
+ });
840
+ return m;
841
+ }).catch((err) => {
842
+ mermaidPromise = null;
843
+ throw err;
844
+ });
845
+ }
846
+ return mermaidPromise;
847
+ };
848
+ var stripFontImports = (svg) => svg.replace(/@import\s+url\([^)]*fonts\.googleapis\.com[^)]*\);?\s*/g, "");
849
+ var tryFastRender = (code) => {
850
+ try {
851
+ return stripFontImports(
852
+ renderMermaidSVG(code, {
853
+ bg: colors.white,
854
+ fg: colors.neutral[800],
855
+ transparent: true,
856
+ font: "Inter, system-ui, sans-serif"
857
+ })
858
+ );
859
+ } catch {
860
+ return null;
861
+ }
862
+ };
863
+ var makeResponsive = (svgString) => {
864
+ const viewBoxMatch = svgString.match(/viewBox="[^"]*\s([\d.]+)\s[\d.]+"/);
865
+ const vbWidth = viewBoxMatch ? Math.round(Number(viewBoxMatch[1])) : void 0;
866
+ let result = svgString.replace(/(<svg[^>]*?)\s+width="[^"]*"/i, '$1 width="100%"').replace(/(<svg[^>]*?)\s+height="[^"]*"/i, "$1");
867
+ if (!vbWidth) return result;
868
+ const hasStyle = /(<svg[^>]*?)\s+style="([^"]*)"/i.test(result);
869
+ if (hasStyle) {
870
+ result = result.replace(/(<svg[^>]*?)\s+style="([^"]*)"/i, (_m, pre, styles) => {
871
+ const cleaned = styles.replace(/max-width:\s*[^;"]*;?\s*/g, "").trim();
872
+ const combined = [cleaned, `max-width: ${vbWidth}px`].filter(Boolean).join("; ");
873
+ return `${pre} style="${combined}"`;
874
+ });
875
+ } else {
876
+ result = result.replace(/<svg/, `<svg style="max-width: ${vbWidth}px"`);
877
+ }
878
+ return result;
879
+ };
880
+ var RENDER_DEBOUNCE_MS = 300;
881
+ var svgCache = /* @__PURE__ */ new Map();
882
+ var MermaidDiagram = ({ code }) => {
883
+ const id = useId();
884
+ const [svg, setSvg] = useState(() => {
885
+ const cached = svgCache.get(code);
886
+ if (cached) return cached;
887
+ const fast = tryFastRender(code);
888
+ if (fast) {
889
+ svgCache.set(code, fast);
890
+ return fast;
891
+ }
892
+ return null;
893
+ });
894
+ const [renderFailed, setRenderFailed] = useState(false);
895
+ const debounceRef = useRef(null);
896
+ const renderIdRef = useRef(0);
897
+ const [prevCode, setPrevCode] = useState(code);
898
+ if (code !== prevCode) {
899
+ setPrevCode(code);
900
+ const cached = svgCache.get(code);
901
+ const fast = cached ?? tryFastRender(code);
902
+ if (fast && !cached) svgCache.set(code, fast);
903
+ setSvg(fast);
904
+ setRenderFailed(false);
905
+ }
906
+ useEffect(() => {
907
+ if (svgCache.has(code)) {
908
+ setSvg(svgCache.get(code));
909
+ setRenderFailed(false);
910
+ return;
911
+ }
912
+ const fastResult = tryFastRender(code);
913
+ if (fastResult) {
914
+ svgCache.set(code, fastResult);
915
+ setSvg(fastResult);
916
+ setRenderFailed(false);
917
+ return;
918
+ }
919
+ setSvg(null);
920
+ const currentRenderId = ++renderIdRef.current;
921
+ setRenderFailed(false);
922
+ if (debounceRef.current) {
923
+ clearTimeout(debounceRef.current);
924
+ }
925
+ debounceRef.current = setTimeout(() => {
926
+ getMermaid().then(async (mermaidModule) => {
927
+ if (renderIdRef.current !== currentRenderId) return;
928
+ const rendererId = `mermaid-${id.replaceAll(":", "")}`;
929
+ const container = document.createElement("div");
930
+ container.style.visibility = "hidden";
931
+ container.style.position = "absolute";
932
+ container.style.top = "-9999px";
933
+ container.style.width = "900px";
934
+ document.body.appendChild(container);
935
+ try {
936
+ const result = await mermaidModule.default.render(rendererId, code, container);
937
+ if (renderIdRef.current === currentRenderId && result) {
938
+ svgCache.set(code, result.svg);
939
+ setSvg(result.svg);
940
+ setRenderFailed(false);
941
+ }
942
+ } finally {
943
+ container.remove();
944
+ }
945
+ }).catch((err) => {
946
+ if (renderIdRef.current === currentRenderId) {
947
+ console.error("[MermaidDiagram] fallback failed:", err);
948
+ setSvg(null);
949
+ setRenderFailed(true);
950
+ }
951
+ });
952
+ }, RENDER_DEBOUNCE_MS);
953
+ const timer = debounceRef.current;
954
+ const renderId = renderIdRef;
955
+ return () => {
956
+ renderId.current++;
957
+ if (timer) {
958
+ clearTimeout(timer);
959
+ }
960
+ };
961
+ }, [code, id]);
962
+ const processedSvg = svg ? makeResponsive(svg) : null;
963
+ if (renderFailed) {
964
+ return /* @__PURE__ */ jsx(
965
+ "pre",
966
+ {
967
+ className: cn(
968
+ "tw-my-3 tw-overflow-auto tw-whitespace-pre-wrap tw-break-all",
969
+ "tw-py-3 tw-px-4",
970
+ "tw-bg-neutral-50",
971
+ "tw-rounded-md tw-text-neutral-400",
972
+ "tw-text-sm tw-font-mono"
973
+ ),
974
+ children: /* @__PURE__ */ jsx("code", { children: code })
975
+ }
976
+ );
977
+ }
978
+ if (processedSvg) {
979
+ return /* @__PURE__ */ jsx(
980
+ ZoomableSvg,
981
+ {
982
+ html: processedSvg,
983
+ copyText: `\`\`\`mermaid
984
+ ${code}
985
+ \`\`\``,
986
+ className: "tw-my-3"
987
+ },
988
+ code
989
+ );
990
+ }
991
+ return /* @__PURE__ */ jsx("div", { className: "tw-my-3 tw-overflow-hidden tw-rounded-md tw-border tw-border-neutral-150 tw-bg-neutral-50 tw-p-6", children: /* @__PURE__ */ jsx(Skeleton, { className: "tw-h-40" }) });
992
+ };
993
+
994
+ export { MermaidDiagram };
995
+ //# sourceMappingURL=mermaid.js.map
996
+ //# sourceMappingURL=mermaid.js.map