@windrun-huaiin/third-ui 29.2.0 → 30.0.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 (102) hide show
  1. package/dist/fuma/mdx/cheet-table.d.ts +13 -0
  2. package/dist/fuma/mdx/cheet-table.js +295 -0
  3. package/dist/fuma/mdx/cheet-table.mjs +293 -0
  4. package/dist/fuma/mdx/index.d.ts +1 -0
  5. package/dist/fuma/mdx/index.js +2 -0
  6. package/dist/fuma/mdx/index.mjs +1 -0
  7. package/dist/fuma/server/features/widgets.js +2 -0
  8. package/dist/fuma/server/features/widgets.mjs +2 -0
  9. package/dist/lib/fuma-schema-check-util.d.ts +1 -1
  10. package/dist/main/alert-dialog/confirm-dialog.d.ts +2 -1
  11. package/dist/main/alert-dialog/confirm-dialog.js +3 -3
  12. package/dist/main/alert-dialog/confirm-dialog.mjs +4 -4
  13. package/dist/main/alert-dialog/dialog-loading-action.d.ts +2 -1
  14. package/dist/main/alert-dialog/dialog-loading-action.js +6 -3
  15. package/dist/main/alert-dialog/dialog-loading-action.mjs +6 -3
  16. package/dist/main/alert-dialog/dialog-styles.d.ts +4 -2
  17. package/dist/main/alert-dialog/dialog-styles.js +8 -4
  18. package/dist/main/alert-dialog/dialog-styles.mjs +7 -5
  19. package/dist/main/alert-dialog/high-priority-confirm-dialog.d.ts +2 -1
  20. package/dist/main/alert-dialog/high-priority-confirm-dialog.js +7 -7
  21. package/dist/main/alert-dialog/high-priority-confirm-dialog.mjs +8 -8
  22. package/dist/main/alert-dialog/info-dialog.d.ts +2 -1
  23. package/dist/main/alert-dialog/info-dialog.js +3 -3
  24. package/dist/main/alert-dialog/info-dialog.mjs +4 -4
  25. package/dist/main/alert-dialog/undoable-confirm-dialog.d.ts +2 -1
  26. package/dist/main/alert-dialog/undoable-confirm-dialog.js +4 -4
  27. package/dist/main/alert-dialog/undoable-confirm-dialog.mjs +5 -5
  28. package/dist/main/anime/anime-beam-frame.d.ts +3 -0
  29. package/dist/main/anime/anime-beam-frame.js +63 -0
  30. package/dist/main/anime/anime-beam-frame.mjs +61 -0
  31. package/dist/main/anime/anime-spiral-loading.d.ts +10 -0
  32. package/dist/main/anime/anime-spiral-loading.js +77 -0
  33. package/dist/main/anime/anime-spiral-loading.mjs +75 -0
  34. package/dist/main/anime/index.d.ts +2 -0
  35. package/dist/main/anime/index.js +10 -0
  36. package/dist/main/anime/index.mjs +3 -0
  37. package/dist/main/beam-frame/animate.d.ts +3 -0
  38. package/dist/main/beam-frame/animate.js +63 -0
  39. package/dist/main/beam-frame/animate.mjs +61 -0
  40. package/dist/main/beam-frame/beam-frame.d.ts +4 -0
  41. package/dist/main/beam-frame/beam-frame.js +262 -0
  42. package/dist/main/beam-frame/beam-frame.mjs +258 -0
  43. package/dist/main/beam-frame/index.d.ts +4 -0
  44. package/dist/main/beam-frame/index.js +11 -0
  45. package/dist/main/beam-frame/index.mjs +3 -0
  46. package/dist/main/beam-frame/motion.d.ts +3 -0
  47. package/dist/main/beam-frame/motion.js +61 -0
  48. package/dist/main/beam-frame/motion.mjs +59 -0
  49. package/dist/main/beam-frame/share-config.d.ts +54 -0
  50. package/dist/main/beam-frame/share-config.js +161 -0
  51. package/dist/main/beam-frame/share-config.mjs +152 -0
  52. package/dist/main/beam-frame-config.d.ts +54 -0
  53. package/dist/main/beam-frame-config.js +161 -0
  54. package/dist/main/beam-frame-config.mjs +152 -0
  55. package/dist/main/calendar/random-date-range-dialog.d.ts +5 -2
  56. package/dist/main/calendar/random-date-range-dialog.js +239 -109
  57. package/dist/main/calendar/random-date-range-dialog.mjs +242 -112
  58. package/dist/main/cta.js +17 -1
  59. package/dist/main/cta.mjs +18 -2
  60. package/dist/main/delayed-img.d.ts +1 -1
  61. package/dist/main/delayed-img.js +8 -5
  62. package/dist/main/delayed-img.mjs +8 -5
  63. package/dist/main/info-tooltip.js +70 -9
  64. package/dist/main/info-tooltip.mjs +70 -9
  65. package/dist/main/loading-frame/index.d.ts +1 -0
  66. package/dist/main/loading.d.ts +2 -1
  67. package/dist/main/loading.js +64 -26
  68. package/dist/main/loading.mjs +64 -26
  69. package/dist/main/motion/index.d.ts +1 -0
  70. package/dist/main/motion/index.js +9 -0
  71. package/dist/main/motion/index.mjs +2 -0
  72. package/dist/main/motion/motion-beam-frame.d.ts +3 -0
  73. package/dist/main/motion/motion-beam-frame.js +61 -0
  74. package/dist/main/motion/motion-beam-frame.mjs +59 -0
  75. package/dist/main/snake-loading-frame.d.ts +7 -3
  76. package/dist/main/snake-loading-frame.js +44 -252
  77. package/dist/main/snake-loading-frame.mjs +46 -254
  78. package/package.json +16 -5
  79. package/src/fuma/mdx/cheet-table.tsx +650 -0
  80. package/src/fuma/mdx/index.ts +1 -0
  81. package/src/fuma/server/features/widgets.tsx +2 -0
  82. package/src/main/alert-dialog/confirm-dialog.tsx +5 -2
  83. package/src/main/alert-dialog/dialog-loading-action.tsx +22 -5
  84. package/src/main/alert-dialog/dialog-styles.ts +13 -3
  85. package/src/main/alert-dialog/high-priority-confirm-dialog.tsx +29 -24
  86. package/src/main/alert-dialog/info-dialog.tsx +5 -2
  87. package/src/main/alert-dialog/undoable-confirm-dialog.tsx +21 -18
  88. package/src/main/anime/anime-beam-frame.tsx +128 -0
  89. package/src/main/anime/anime-spiral-loading.tsx +123 -0
  90. package/src/main/anime/index.ts +9 -0
  91. package/src/main/beam-frame-config.tsx +341 -0
  92. package/src/main/calendar/random-date-range-dialog.tsx +242 -74
  93. package/src/main/cta.tsx +50 -21
  94. package/src/main/delayed-img.tsx +9 -4
  95. package/src/main/info-tooltip.tsx +116 -20
  96. package/src/main/loading-frame/index.ts +4 -0
  97. package/src/main/loading.tsx +75 -24
  98. package/src/main/motion/index.ts +8 -0
  99. package/src/main/motion/motion-beam-frame.tsx +137 -0
  100. package/src/main/snake-loading-frame.tsx +95 -496
  101. package/src/styles/cta.css +21 -4
  102. package/src/styles/third-ui.css +0 -20
@@ -3,5 +3,5 @@ interface DelayedImgProps extends ImageProps {
3
3
  wrapperClassName?: string;
4
4
  placeholderClassName?: string;
5
5
  }
6
- export declare function DelayedImg({ alt, wrapperClassName, placeholderClassName, className, onLoad, ...imageProps }: DelayedImgProps): import("react/jsx-runtime").JSX.Element;
6
+ export declare function DelayedImg({ alt, wrapperClassName, placeholderClassName, className, onError, onLoad, ...imageProps }: DelayedImgProps): import("react/jsx-runtime").JSX.Element;
7
7
  export {};
@@ -17,10 +17,10 @@ const ENV_DELAY_MS = Number.isFinite(parsedDelaySeconds) && parsedDelaySeconds >
17
17
  ? parsedDelaySeconds * 1000
18
18
  : 0;
19
19
  function DelayedImg(_a) {
20
- var { alt, wrapperClassName, placeholderClassName, className, onLoad } = _a, imageProps = tslib.__rest(_a, ["alt", "wrapperClassName", "placeholderClassName", "className", "onLoad"]);
20
+ var { alt, wrapperClassName, placeholderClassName, className, onError, onLoad } = _a, imageProps = tslib.__rest(_a, ["alt", "wrapperClassName", "placeholderClassName", "className", "onError", "onLoad"]);
21
21
  const shouldDelay = ENV_DELAY_ENABLED && ENV_DELAY_MS > 0;
22
22
  const [isMounted, setIsMounted] = React.useState(!shouldDelay);
23
- const [isLoaded, setIsLoaded] = React.useState(false);
23
+ const [isSettled, setIsSettled] = React.useState(false);
24
24
  React.useEffect(() => {
25
25
  if (!shouldDelay || isMounted) {
26
26
  return;
@@ -30,10 +30,13 @@ function DelayedImg(_a) {
30
30
  }, ENV_DELAY_MS);
31
31
  return () => window.clearTimeout(timer);
32
32
  }, [isMounted, shouldDelay]);
33
- return (jsxRuntime.jsxs("div", { className: utils.cn("relative", wrapperClassName), children: [(!isMounted || !isLoaded) && (jsxRuntime.jsx(snakeLoadingFrame.SnakeLoadingFrame, { shape: "rounded-rect", loading: true, themeColor: lib.themeSvgIconColor, className: utils.cn("absolute inset-0 rounded-[inherit] border shadow-sm bg-white/70 dark:bg-white/5", lib.themeBgColor, placeholderClassName), contentClassName: "h-full w-full", children: jsxRuntime.jsx("div", { "aria-hidden": "true", className: "absolute inset-0 rounded-[inherit] bg-white/20 dark:bg-white/0" }) })), isMounted && (jsxRuntime.jsx(Image, Object.assign({}, imageProps, { alt: alt, onLoad: (event) => {
34
- setIsLoaded(true);
33
+ return (jsxRuntime.jsxs("div", { className: utils.cn("relative", wrapperClassName), children: [(!isMounted || !isSettled) && (jsxRuntime.jsx(snakeLoadingFrame.SnakeLoadingFrame, { shape: "rounded-rect", loading: true, themeColor: lib.themeSvgIconColor, className: utils.cn("absolute inset-0 rounded-[inherit] border shadow-sm bg-white/70 dark:bg-white/5", lib.themeBgColor, placeholderClassName), contentClassName: "h-full w-full", children: jsxRuntime.jsx("div", { "aria-hidden": "true", className: "absolute inset-0 rounded-[inherit] bg-white/20 dark:bg-white/0" }) })), isMounted && (jsxRuntime.jsx(Image, Object.assign({}, imageProps, { alt: alt, onError: (event) => {
34
+ setIsSettled(true);
35
+ onError === null || onError === void 0 ? void 0 : onError(event);
36
+ }, onLoad: (event) => {
37
+ setIsSettled(true);
35
38
  onLoad === null || onLoad === void 0 ? void 0 : onLoad(event);
36
- }, className: utils.cn("transition duration-300", isLoaded ? "opacity-100" : "opacity-0", className) })))] }));
39
+ }, className: utils.cn("transition duration-300", isSettled ? "opacity-100" : "opacity-0", className) })))] }));
37
40
  }
38
41
 
39
42
  exports.DelayedImg = DelayedImg;
@@ -15,10 +15,10 @@ const ENV_DELAY_MS = Number.isFinite(parsedDelaySeconds) && parsedDelaySeconds >
15
15
  ? parsedDelaySeconds * 1000
16
16
  : 0;
17
17
  function DelayedImg(_a) {
18
- var { alt, wrapperClassName, placeholderClassName, className, onLoad } = _a, imageProps = __rest(_a, ["alt", "wrapperClassName", "placeholderClassName", "className", "onLoad"]);
18
+ var { alt, wrapperClassName, placeholderClassName, className, onError, onLoad } = _a, imageProps = __rest(_a, ["alt", "wrapperClassName", "placeholderClassName", "className", "onError", "onLoad"]);
19
19
  const shouldDelay = ENV_DELAY_ENABLED && ENV_DELAY_MS > 0;
20
20
  const [isMounted, setIsMounted] = useState(!shouldDelay);
21
- const [isLoaded, setIsLoaded] = useState(false);
21
+ const [isSettled, setIsSettled] = useState(false);
22
22
  useEffect(() => {
23
23
  if (!shouldDelay || isMounted) {
24
24
  return;
@@ -28,10 +28,13 @@ function DelayedImg(_a) {
28
28
  }, ENV_DELAY_MS);
29
29
  return () => window.clearTimeout(timer);
30
30
  }, [isMounted, shouldDelay]);
31
- return (jsxs("div", { className: cn("relative", wrapperClassName), children: [(!isMounted || !isLoaded) && (jsx(SnakeLoadingFrame, { shape: "rounded-rect", loading: true, themeColor: themeSvgIconColor, className: cn("absolute inset-0 rounded-[inherit] border shadow-sm bg-white/70 dark:bg-white/5", themeBgColor, placeholderClassName), contentClassName: "h-full w-full", children: jsx("div", { "aria-hidden": "true", className: "absolute inset-0 rounded-[inherit] bg-white/20 dark:bg-white/0" }) })), isMounted && (jsx(Image, Object.assign({}, imageProps, { alt: alt, onLoad: (event) => {
32
- setIsLoaded(true);
31
+ return (jsxs("div", { className: cn("relative", wrapperClassName), children: [(!isMounted || !isSettled) && (jsx(SnakeLoadingFrame, { shape: "rounded-rect", loading: true, themeColor: themeSvgIconColor, className: cn("absolute inset-0 rounded-[inherit] border shadow-sm bg-white/70 dark:bg-white/5", themeBgColor, placeholderClassName), contentClassName: "h-full w-full", children: jsx("div", { "aria-hidden": "true", className: "absolute inset-0 rounded-[inherit] bg-white/20 dark:bg-white/0" }) })), isMounted && (jsx(Image, Object.assign({}, imageProps, { alt: alt, onError: (event) => {
32
+ setIsSettled(true);
33
+ onError === null || onError === void 0 ? void 0 : onError(event);
34
+ }, onLoad: (event) => {
35
+ setIsSettled(true);
33
36
  onLoad === null || onLoad === void 0 ? void 0 : onLoad(event);
34
- }, className: cn("transition duration-300", isLoaded ? "opacity-100" : "opacity-0", className) })))] }));
37
+ }, className: cn("transition duration-300", isSettled ? "opacity-100" : "opacity-0", className) })))] }));
35
38
  }
36
39
 
37
40
  export { DelayedImg };
@@ -3,21 +3,69 @@
3
3
 
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
5
  var React = require('react');
6
+ var reactDom = require('react-dom');
6
7
  var icons = require('@windrun-huaiin/base-ui/icons');
7
8
  var lib = require('@windrun-huaiin/base-ui/lib');
8
9
  var utils = require('@windrun-huaiin/lib/utils');
9
10
 
11
+ const TOOLTIP_MARGIN = 12;
12
+ const TOOLTIP_GAP = 8;
13
+ const TOOLTIP_MAX_WIDTH = 288;
14
+ function clamp(value, min, max) {
15
+ return Math.min(Math.max(value, min), max);
16
+ }
17
+ function getTooltipPosition(target, align, desktopSide) {
18
+ const rect = target.getBoundingClientRect();
19
+ const viewportWidth = window.innerWidth;
20
+ const viewportHeight = window.innerHeight;
21
+ const maxWidth = Math.min(TOOLTIP_MAX_WIDTH, Math.max(160, viewportWidth - TOOLTIP_MARGIN * 2));
22
+ const useInlineSide = desktopSide === 'right' && viewportWidth >= 768;
23
+ if (useInlineSide) {
24
+ const rightSpace = viewportWidth - rect.right - TOOLTIP_GAP - TOOLTIP_MARGIN;
25
+ const leftSpace = rect.left - TOOLTIP_GAP - TOOLTIP_MARGIN;
26
+ const placeRight = rightSpace >= Math.min(220, maxWidth) || rightSpace >= leftSpace;
27
+ const preferredLeft = placeRight
28
+ ? rect.right + TOOLTIP_GAP
29
+ : rect.left - maxWidth - TOOLTIP_GAP;
30
+ return {
31
+ left: clamp(preferredLeft, TOOLTIP_MARGIN, viewportWidth - maxWidth - TOOLTIP_MARGIN),
32
+ top: clamp(rect.top + rect.height / 2, TOOLTIP_MARGIN + 40, viewportHeight - TOOLTIP_MARGIN - 40),
33
+ maxWidth,
34
+ side: 'inline',
35
+ };
36
+ }
37
+ const preferredLeft = align === 'start'
38
+ ? rect.left
39
+ : rect.right - maxWidth;
40
+ return {
41
+ left: clamp(preferredLeft, TOOLTIP_MARGIN, viewportWidth - maxWidth - TOOLTIP_MARGIN),
42
+ top: Math.min(rect.bottom + TOOLTIP_GAP, viewportHeight - TOOLTIP_MARGIN),
43
+ maxWidth,
44
+ side: 'bottom',
45
+ };
46
+ }
10
47
  function InfoTooltip({ content, className, align = 'end', desktopSide = 'right', }) {
11
48
  const normalizedContent = content.trim();
12
49
  const containerRef = React.useRef(null);
50
+ const tooltipRef = React.useRef(null);
13
51
  const [open, setOpen] = React.useState(false);
52
+ const [position, setPosition] = React.useState(null);
53
+ const updatePosition = () => {
54
+ if (!containerRef.current) {
55
+ return;
56
+ }
57
+ setPosition(getTooltipPosition(containerRef.current, align, desktopSide));
58
+ };
14
59
  React.useEffect(() => {
15
60
  function handlePointerDown(event) {
61
+ var _a;
16
62
  if (!containerRef.current) {
17
63
  return;
18
64
  }
19
65
  const target = event.target;
20
- if (target instanceof Node && !containerRef.current.contains(target)) {
66
+ if (target instanceof Node &&
67
+ !containerRef.current.contains(target) &&
68
+ !((_a = tooltipRef.current) === null || _a === void 0 ? void 0 : _a.contains(target))) {
21
69
  setOpen(false);
22
70
  }
23
71
  }
@@ -28,21 +76,34 @@ function InfoTooltip({ content, className, align = 'end', desktopSide = 'right',
28
76
  document.removeEventListener('touchstart', handlePointerDown);
29
77
  };
30
78
  }, []);
79
+ React.useEffect(() => {
80
+ if (!open) {
81
+ setPosition(null);
82
+ return;
83
+ }
84
+ updatePosition();
85
+ window.addEventListener('resize', updatePosition);
86
+ window.addEventListener('scroll', updatePosition, true);
87
+ return () => {
88
+ window.removeEventListener('resize', updatePosition);
89
+ window.removeEventListener('scroll', updatePosition, true);
90
+ };
91
+ }, [align, desktopSide, open]);
31
92
  if (!normalizedContent) {
32
93
  return null;
33
94
  }
34
- return (jsxRuntime.jsxs("span", { ref: containerRef, className: utils.cn('relative inline-flex h-5 w-5 shrink-0 align-middle', className), onMouseLeave: () => setOpen(false), children: [jsxRuntime.jsx("button", { type: "button", onMouseEnter: () => setOpen(true), onPointerDown: (event) => {
95
+ return (jsxRuntime.jsxs("span", { ref: containerRef, className: utils.cn('inline-flex h-5 w-5 shrink-0 align-middle', className), onMouseLeave: () => setOpen(false), children: [jsxRuntime.jsx("button", { type: "button", onMouseEnter: () => setOpen(true), onPointerDown: (event) => {
35
96
  event.stopPropagation();
36
97
  }, onClick: (event) => {
37
98
  event.stopPropagation();
38
99
  setOpen((value) => !value);
39
- }, className: utils.cn('inline-flex h-5 w-5 items-center justify-center rounded-full text-slate-400 transition', 'hover:bg-black/5 hover:dark:bg-white/5', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 dark:focus-visible:ring-offset-slate-950', 'hover:text-slate-700 dark:hover:text-white focus-visible:text-slate-700 dark:focus-visible:text-white', lib.themeIconColor, lib.themeRingColor), "aria-label": normalizedContent, "aria-expanded": open, children: jsxRuntime.jsx(icons.CircleQuestionMarkIcon, { className: "h-4 w-4" }) }), jsxRuntime.jsx("span", { className: utils.cn('pointer-events-none absolute top-full z-50 mt-2 w-[min(18rem,calc(100vw-2rem))] rounded-2xl border bg-white/95 px-3 py-2 text-xs leading-5 text-slate-600 shadow-xl backdrop-blur-sm dark:bg-slate-950/95 dark:text-slate-300', align === 'start' ? 'left-0 right-auto' : 'right-0 left-auto', desktopSide === 'right'
40
- ? align === 'start'
41
- ? 'sm:left-0 sm:right-auto md:left-full md:right-auto md:top-1/2 md:mt-0 md:ml-2 md:-translate-y-1/2'
42
- : 'sm:right-0 sm:left-auto md:left-full md:right-auto md:top-1/2 md:mt-0 md:ml-2 md:-translate-y-1/2'
43
- : align === 'start'
44
- ? 'md:left-0 md:right-auto md:top-full md:mt-2 md:ml-0 md:translate-y-0'
45
- : 'md:right-0 md:left-auto md:top-full md:mt-2 md:ml-0 md:translate-y-0', open ? 'block' : 'hidden', lib.themeBorderColor), role: "tooltip", children: normalizedContent })] }));
100
+ }, className: utils.cn('inline-flex h-5 w-5 items-center justify-center rounded-full text-slate-400 transition', 'hover:bg-black/5 hover:dark:bg-white/5', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 dark:focus-visible:ring-offset-slate-950', 'hover:text-slate-700 dark:hover:text-white focus-visible:text-slate-700 dark:focus-visible:text-white', lib.themeIconColor, lib.themeRingColor), "aria-label": normalizedContent, "aria-expanded": open, children: jsxRuntime.jsx(icons.CircleQuestionMarkIcon, { className: "h-4 w-4" }) }), open && position
101
+ ? reactDom.createPortal(jsxRuntime.jsx("span", { ref: tooltipRef, className: utils.cn('pointer-events-none fixed z-50 max-h-[calc(100vh-1.5rem)] overflow-y-auto rounded-2xl border bg-white/95 px-3 py-2 text-xs leading-5 text-slate-600 shadow-xl backdrop-blur-sm dark:bg-slate-950/95 dark:text-slate-300', position.side === 'inline' && '-translate-y-1/2', lib.themeBorderColor), style: {
102
+ left: position.left,
103
+ top: position.top,
104
+ width: position.maxWidth,
105
+ }, role: "tooltip", children: normalizedContent }), document.body)
106
+ : null] }));
46
107
  }
47
108
 
48
109
  exports.InfoTooltip = InfoTooltip;
@@ -1,21 +1,69 @@
1
1
  "use client";
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
3
  import { useRef, useState, useEffect } from 'react';
4
+ import { createPortal } from 'react-dom';
4
5
  import { CircleQuestionMarkIcon } from '@windrun-huaiin/base-ui/icons';
5
6
  import { themeIconColor, themeRingColor, themeBorderColor } from '@windrun-huaiin/base-ui/lib';
6
7
  import { cn } from '@windrun-huaiin/lib/utils';
7
8
 
9
+ const TOOLTIP_MARGIN = 12;
10
+ const TOOLTIP_GAP = 8;
11
+ const TOOLTIP_MAX_WIDTH = 288;
12
+ function clamp(value, min, max) {
13
+ return Math.min(Math.max(value, min), max);
14
+ }
15
+ function getTooltipPosition(target, align, desktopSide) {
16
+ const rect = target.getBoundingClientRect();
17
+ const viewportWidth = window.innerWidth;
18
+ const viewportHeight = window.innerHeight;
19
+ const maxWidth = Math.min(TOOLTIP_MAX_WIDTH, Math.max(160, viewportWidth - TOOLTIP_MARGIN * 2));
20
+ const useInlineSide = desktopSide === 'right' && viewportWidth >= 768;
21
+ if (useInlineSide) {
22
+ const rightSpace = viewportWidth - rect.right - TOOLTIP_GAP - TOOLTIP_MARGIN;
23
+ const leftSpace = rect.left - TOOLTIP_GAP - TOOLTIP_MARGIN;
24
+ const placeRight = rightSpace >= Math.min(220, maxWidth) || rightSpace >= leftSpace;
25
+ const preferredLeft = placeRight
26
+ ? rect.right + TOOLTIP_GAP
27
+ : rect.left - maxWidth - TOOLTIP_GAP;
28
+ return {
29
+ left: clamp(preferredLeft, TOOLTIP_MARGIN, viewportWidth - maxWidth - TOOLTIP_MARGIN),
30
+ top: clamp(rect.top + rect.height / 2, TOOLTIP_MARGIN + 40, viewportHeight - TOOLTIP_MARGIN - 40),
31
+ maxWidth,
32
+ side: 'inline',
33
+ };
34
+ }
35
+ const preferredLeft = align === 'start'
36
+ ? rect.left
37
+ : rect.right - maxWidth;
38
+ return {
39
+ left: clamp(preferredLeft, TOOLTIP_MARGIN, viewportWidth - maxWidth - TOOLTIP_MARGIN),
40
+ top: Math.min(rect.bottom + TOOLTIP_GAP, viewportHeight - TOOLTIP_MARGIN),
41
+ maxWidth,
42
+ side: 'bottom',
43
+ };
44
+ }
8
45
  function InfoTooltip({ content, className, align = 'end', desktopSide = 'right', }) {
9
46
  const normalizedContent = content.trim();
10
47
  const containerRef = useRef(null);
48
+ const tooltipRef = useRef(null);
11
49
  const [open, setOpen] = useState(false);
50
+ const [position, setPosition] = useState(null);
51
+ const updatePosition = () => {
52
+ if (!containerRef.current) {
53
+ return;
54
+ }
55
+ setPosition(getTooltipPosition(containerRef.current, align, desktopSide));
56
+ };
12
57
  useEffect(() => {
13
58
  function handlePointerDown(event) {
59
+ var _a;
14
60
  if (!containerRef.current) {
15
61
  return;
16
62
  }
17
63
  const target = event.target;
18
- if (target instanceof Node && !containerRef.current.contains(target)) {
64
+ if (target instanceof Node &&
65
+ !containerRef.current.contains(target) &&
66
+ !((_a = tooltipRef.current) === null || _a === void 0 ? void 0 : _a.contains(target))) {
19
67
  setOpen(false);
20
68
  }
21
69
  }
@@ -26,21 +74,34 @@ function InfoTooltip({ content, className, align = 'end', desktopSide = 'right',
26
74
  document.removeEventListener('touchstart', handlePointerDown);
27
75
  };
28
76
  }, []);
77
+ useEffect(() => {
78
+ if (!open) {
79
+ setPosition(null);
80
+ return;
81
+ }
82
+ updatePosition();
83
+ window.addEventListener('resize', updatePosition);
84
+ window.addEventListener('scroll', updatePosition, true);
85
+ return () => {
86
+ window.removeEventListener('resize', updatePosition);
87
+ window.removeEventListener('scroll', updatePosition, true);
88
+ };
89
+ }, [align, desktopSide, open]);
29
90
  if (!normalizedContent) {
30
91
  return null;
31
92
  }
32
- return (jsxs("span", { ref: containerRef, className: cn('relative inline-flex h-5 w-5 shrink-0 align-middle', className), onMouseLeave: () => setOpen(false), children: [jsx("button", { type: "button", onMouseEnter: () => setOpen(true), onPointerDown: (event) => {
93
+ return (jsxs("span", { ref: containerRef, className: cn('inline-flex h-5 w-5 shrink-0 align-middle', className), onMouseLeave: () => setOpen(false), children: [jsx("button", { type: "button", onMouseEnter: () => setOpen(true), onPointerDown: (event) => {
33
94
  event.stopPropagation();
34
95
  }, onClick: (event) => {
35
96
  event.stopPropagation();
36
97
  setOpen((value) => !value);
37
- }, className: cn('inline-flex h-5 w-5 items-center justify-center rounded-full text-slate-400 transition', 'hover:bg-black/5 hover:dark:bg-white/5', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 dark:focus-visible:ring-offset-slate-950', 'hover:text-slate-700 dark:hover:text-white focus-visible:text-slate-700 dark:focus-visible:text-white', themeIconColor, themeRingColor), "aria-label": normalizedContent, "aria-expanded": open, children: jsx(CircleQuestionMarkIcon, { className: "h-4 w-4" }) }), jsx("span", { className: cn('pointer-events-none absolute top-full z-50 mt-2 w-[min(18rem,calc(100vw-2rem))] rounded-2xl border bg-white/95 px-3 py-2 text-xs leading-5 text-slate-600 shadow-xl backdrop-blur-sm dark:bg-slate-950/95 dark:text-slate-300', align === 'start' ? 'left-0 right-auto' : 'right-0 left-auto', desktopSide === 'right'
38
- ? align === 'start'
39
- ? 'sm:left-0 sm:right-auto md:left-full md:right-auto md:top-1/2 md:mt-0 md:ml-2 md:-translate-y-1/2'
40
- : 'sm:right-0 sm:left-auto md:left-full md:right-auto md:top-1/2 md:mt-0 md:ml-2 md:-translate-y-1/2'
41
- : align === 'start'
42
- ? 'md:left-0 md:right-auto md:top-full md:mt-2 md:ml-0 md:translate-y-0'
43
- : 'md:right-0 md:left-auto md:top-full md:mt-2 md:ml-0 md:translate-y-0', open ? 'block' : 'hidden', themeBorderColor), role: "tooltip", children: normalizedContent })] }));
98
+ }, className: cn('inline-flex h-5 w-5 items-center justify-center rounded-full text-slate-400 transition', 'hover:bg-black/5 hover:dark:bg-white/5', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 dark:focus-visible:ring-offset-slate-950', 'hover:text-slate-700 dark:hover:text-white focus-visible:text-slate-700 dark:focus-visible:text-white', themeIconColor, themeRingColor), "aria-label": normalizedContent, "aria-expanded": open, children: jsx(CircleQuestionMarkIcon, { className: "h-4 w-4" }) }), open && position
99
+ ? createPortal(jsx("span", { ref: tooltipRef, className: cn('pointer-events-none fixed z-50 max-h-[calc(100vh-1.5rem)] overflow-y-auto rounded-2xl border bg-white/95 px-3 py-2 text-xs leading-5 text-slate-600 shadow-xl backdrop-blur-sm dark:bg-slate-950/95 dark:text-slate-300', position.side === 'inline' && '-translate-y-1/2', themeBorderColor), style: {
100
+ left: position.left,
101
+ top: position.top,
102
+ width: position.maxWidth,
103
+ }, role: "tooltip", children: normalizedContent }), document.body)
104
+ : null] }));
44
105
  }
45
106
 
46
107
  export { InfoTooltip };
@@ -1 +1,2 @@
1
1
  export { SnakeLoadingFrame, SnakeLoadingPreview } from '../snake-loading-frame';
2
+ export type { SnakeLoadingFrameProps, SnakeLoadingPreviewProps, } from '../snake-loading-frame';
@@ -5,6 +5,7 @@ interface LoadingProps {
5
5
  className?: string;
6
6
  label?: string;
7
7
  labelClassName?: string;
8
+ paused?: boolean;
8
9
  }
9
- export declare function Loading({ themeColor, compact, className, label, labelClassName, }?: LoadingProps): import("react/jsx-runtime").JSX.Element;
10
+ export declare function Loading({ themeColor, compact, className, label, labelClassName, paused, }?: LoadingProps): import("react/jsx-runtime").JSX.Element;
10
11
  export {};
@@ -2,8 +2,10 @@
2
2
  'use strict';
3
3
 
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
+ var React = require('react');
5
6
  var utils = require('@windrun-huaiin/lib/utils');
6
7
  var lib = require('@windrun-huaiin/base-ui/lib');
8
+ var animejs = require('animejs');
7
9
 
8
10
  const NUM_ROWS = 15;
9
11
  const NUM_COLS = 15;
@@ -53,36 +55,77 @@ function createLoadingPalette(baseHex) {
53
55
  shiftColor(baseHex, { r: -24, g: -14, b: 6 }),
54
56
  ];
55
57
  }
56
- function Loading({ themeColor = lib.themeSvgIconColor, compact = false, className, label = 'Loading...', labelClassName, } = {}) {
57
- const colors = createLoadingPalette(themeColor);
58
- const dots = [];
58
+ function Loading({ themeColor = lib.themeSvgIconColor, compact = false, className, label = 'Loading...', labelClassName, paused = false, } = {}) {
59
+ const gridRef = React.useRef(null);
60
+ const animationRef = React.useRef(null);
61
+ const pausedRef = React.useRef(paused);
62
+ const colors = React.useMemo(() => createLoadingPalette(themeColor), [themeColor]);
59
63
  const centerX = (NUM_COLS - 1) / 2;
60
64
  const centerY = (NUM_ROWS - 1) / 2;
61
- for (let i = 0; i < NUM_ROWS; i++) {
62
- for (let j = 0; j < NUM_COLS; j++) {
63
- // Calculate distance from the center of the grid
64
- const distance = Math.sqrt(Math.pow(i - centerY, 2) + Math.pow(j - centerX, 2));
65
- dots.push({
66
- id: `${i}-${j}`,
67
- row: i,
68
- col: j,
69
- // Animation delay based on distance, creating a ripple effect
70
- delay: distance * STAGGER_DELAY_FACTOR,
71
- // Color selection based on distance rings
72
- color: colors[Math.floor(distance) % colors.length],
73
- });
65
+ const dots = React.useMemo(() => {
66
+ const nextDots = [];
67
+ for (let i = 0; i < NUM_ROWS; i++) {
68
+ for (let j = 0; j < NUM_COLS; j++) {
69
+ const distance = Math.sqrt(Math.pow(i - centerY, 2) + Math.pow(j - centerX, 2));
70
+ nextDots.push({
71
+ id: `${i}-${j}`,
72
+ row: i,
73
+ col: j,
74
+ delay: distance * STAGGER_DELAY_FACTOR,
75
+ color: colors[Math.floor(distance) % colors.length],
76
+ });
77
+ }
74
78
  }
75
- }
79
+ return nextDots;
80
+ }, [centerX, centerY, colors]);
76
81
  // Calculate the total width and height of the dot container
77
82
  const containerWidth = (NUM_COLS - 1) * SPACING + DOT_SIZE;
78
83
  const containerHeight = (NUM_ROWS - 1) * SPACING + DOT_SIZE;
79
- return (jsxRuntime.jsx("div", { className: utils.cn('flex flex-col items-center justify-center bg-neutral-100 dark:bg-neutral-900', compact ? 'min-h-[250px] rounded-[28px] px-4 py-2' : 'min-h-screen', className), children: jsxRuntime.jsxs("div", { style: {
84
+ pausedRef.current = paused;
85
+ React.useEffect(() => {
86
+ var _a;
87
+ const grid = gridRef.current;
88
+ if (!grid) {
89
+ return undefined;
90
+ }
91
+ const dotNodes = Array.from(grid.querySelectorAll('[data-loading-dot]'));
92
+ (_a = animationRef.current) === null || _a === void 0 ? void 0 : _a.revert();
93
+ animationRef.current = animejs.animate(dotNodes, {
94
+ opacity: [0, 1, 0.7, 0],
95
+ scale: [0.2, 1.2, 0.8, 0.2],
96
+ duration: ANIMATION_DURATION * 1000,
97
+ delay: (target) => { var _a; return Number((_a = target === null || target === void 0 ? void 0 : target.dataset.loadingDelay) !== null && _a !== void 0 ? _a : 0) * 1000; },
98
+ ease: 'inOutSine',
99
+ loop: true,
100
+ });
101
+ if (pausedRef.current) {
102
+ animationRef.current.pause();
103
+ }
104
+ return () => {
105
+ var _a;
106
+ (_a = animationRef.current) === null || _a === void 0 ? void 0 : _a.revert();
107
+ animationRef.current = null;
108
+ };
109
+ }, [dots]);
110
+ React.useEffect(() => {
111
+ const animation = animationRef.current;
112
+ if (!animation) {
113
+ return;
114
+ }
115
+ if (paused) {
116
+ animation.pause();
117
+ }
118
+ else {
119
+ animation.play();
120
+ }
121
+ }, [paused]);
122
+ return (jsxRuntime.jsx("div", { className: utils.cn('flex flex-col items-center justify-center bg-neutral-100 dark:bg-neutral-900', compact ? 'min-h-[250px] rounded-[28px] px-4 py-2' : 'min-h-screen', className), children: jsxRuntime.jsxs("div", { ref: gridRef, style: {
80
123
  width: containerWidth,
81
124
  height: containerHeight,
82
125
  position: 'relative',
83
126
  borderRadius: '50%', // Make the container circular
84
127
  overflow: 'hidden', // Clip dots outside the circle
85
- }, children: [dots.map(dot => (jsxRuntime.jsx("div", { style: {
128
+ }, children: [dots.map(dot => (jsxRuntime.jsx("div", { "data-loading-dot": "", "data-loading-delay": dot.delay, style: {
86
129
  position: 'absolute',
87
130
  left: dot.col * SPACING,
88
131
  top: dot.row * SPACING,
@@ -90,13 +133,8 @@ function Loading({ themeColor = lib.themeSvgIconColor, compact = false, classNam
90
133
  height: DOT_SIZE,
91
134
  backgroundColor: dot.color,
92
135
  borderRadius: '50%',
93
- animationName: 'loading-dot-pulse',
94
- animationDuration: `${ANIMATION_DURATION}s`,
95
- animationTimingFunction: 'cubic-bezier(0.45, 0, 0.55, 1)',
96
- animationIterationCount: 'infinite',
97
- animationDelay: `${dot.delay}s`,
98
- opacity: 0,
99
- transform: 'scale(0)',
136
+ opacity: 0.35,
137
+ transform: 'scale(0.2)',
100
138
  } }, dot.id))), jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center", style: { pointerEvents: 'none' }, children: jsxRuntime.jsx("p", { className: utils.cn('text-xl font-semibold text-white', labelClassName), children: label }) })] }) }));
101
139
  }
102
140
 
@@ -1,7 +1,9 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { useRef, useMemo, useEffect } from 'react';
3
4
  import { cn } from '@windrun-huaiin/lib/utils';
4
5
  import { themeSvgIconColor } from '@windrun-huaiin/base-ui/lib';
6
+ import { animate } from 'animejs';
5
7
 
6
8
  const NUM_ROWS = 15;
7
9
  const NUM_COLS = 15;
@@ -51,36 +53,77 @@ function createLoadingPalette(baseHex) {
51
53
  shiftColor(baseHex, { r: -24, g: -14, b: 6 }),
52
54
  ];
53
55
  }
54
- function Loading({ themeColor = themeSvgIconColor, compact = false, className, label = 'Loading...', labelClassName, } = {}) {
55
- const colors = createLoadingPalette(themeColor);
56
- const dots = [];
56
+ function Loading({ themeColor = themeSvgIconColor, compact = false, className, label = 'Loading...', labelClassName, paused = false, } = {}) {
57
+ const gridRef = useRef(null);
58
+ const animationRef = useRef(null);
59
+ const pausedRef = useRef(paused);
60
+ const colors = useMemo(() => createLoadingPalette(themeColor), [themeColor]);
57
61
  const centerX = (NUM_COLS - 1) / 2;
58
62
  const centerY = (NUM_ROWS - 1) / 2;
59
- for (let i = 0; i < NUM_ROWS; i++) {
60
- for (let j = 0; j < NUM_COLS; j++) {
61
- // Calculate distance from the center of the grid
62
- const distance = Math.sqrt(Math.pow(i - centerY, 2) + Math.pow(j - centerX, 2));
63
- dots.push({
64
- id: `${i}-${j}`,
65
- row: i,
66
- col: j,
67
- // Animation delay based on distance, creating a ripple effect
68
- delay: distance * STAGGER_DELAY_FACTOR,
69
- // Color selection based on distance rings
70
- color: colors[Math.floor(distance) % colors.length],
71
- });
63
+ const dots = useMemo(() => {
64
+ const nextDots = [];
65
+ for (let i = 0; i < NUM_ROWS; i++) {
66
+ for (let j = 0; j < NUM_COLS; j++) {
67
+ const distance = Math.sqrt(Math.pow(i - centerY, 2) + Math.pow(j - centerX, 2));
68
+ nextDots.push({
69
+ id: `${i}-${j}`,
70
+ row: i,
71
+ col: j,
72
+ delay: distance * STAGGER_DELAY_FACTOR,
73
+ color: colors[Math.floor(distance) % colors.length],
74
+ });
75
+ }
72
76
  }
73
- }
77
+ return nextDots;
78
+ }, [centerX, centerY, colors]);
74
79
  // Calculate the total width and height of the dot container
75
80
  const containerWidth = (NUM_COLS - 1) * SPACING + DOT_SIZE;
76
81
  const containerHeight = (NUM_ROWS - 1) * SPACING + DOT_SIZE;
77
- return (jsx("div", { className: cn('flex flex-col items-center justify-center bg-neutral-100 dark:bg-neutral-900', compact ? 'min-h-[250px] rounded-[28px] px-4 py-2' : 'min-h-screen', className), children: jsxs("div", { style: {
82
+ pausedRef.current = paused;
83
+ useEffect(() => {
84
+ var _a;
85
+ const grid = gridRef.current;
86
+ if (!grid) {
87
+ return undefined;
88
+ }
89
+ const dotNodes = Array.from(grid.querySelectorAll('[data-loading-dot]'));
90
+ (_a = animationRef.current) === null || _a === void 0 ? void 0 : _a.revert();
91
+ animationRef.current = animate(dotNodes, {
92
+ opacity: [0, 1, 0.7, 0],
93
+ scale: [0.2, 1.2, 0.8, 0.2],
94
+ duration: ANIMATION_DURATION * 1000,
95
+ delay: (target) => { var _a; return Number((_a = target === null || target === void 0 ? void 0 : target.dataset.loadingDelay) !== null && _a !== void 0 ? _a : 0) * 1000; },
96
+ ease: 'inOutSine',
97
+ loop: true,
98
+ });
99
+ if (pausedRef.current) {
100
+ animationRef.current.pause();
101
+ }
102
+ return () => {
103
+ var _a;
104
+ (_a = animationRef.current) === null || _a === void 0 ? void 0 : _a.revert();
105
+ animationRef.current = null;
106
+ };
107
+ }, [dots]);
108
+ useEffect(() => {
109
+ const animation = animationRef.current;
110
+ if (!animation) {
111
+ return;
112
+ }
113
+ if (paused) {
114
+ animation.pause();
115
+ }
116
+ else {
117
+ animation.play();
118
+ }
119
+ }, [paused]);
120
+ return (jsx("div", { className: cn('flex flex-col items-center justify-center bg-neutral-100 dark:bg-neutral-900', compact ? 'min-h-[250px] rounded-[28px] px-4 py-2' : 'min-h-screen', className), children: jsxs("div", { ref: gridRef, style: {
78
121
  width: containerWidth,
79
122
  height: containerHeight,
80
123
  position: 'relative',
81
124
  borderRadius: '50%', // Make the container circular
82
125
  overflow: 'hidden', // Clip dots outside the circle
83
- }, children: [dots.map(dot => (jsx("div", { style: {
126
+ }, children: [dots.map(dot => (jsx("div", { "data-loading-dot": "", "data-loading-delay": dot.delay, style: {
84
127
  position: 'absolute',
85
128
  left: dot.col * SPACING,
86
129
  top: dot.row * SPACING,
@@ -88,13 +131,8 @@ function Loading({ themeColor = themeSvgIconColor, compact = false, className, l
88
131
  height: DOT_SIZE,
89
132
  backgroundColor: dot.color,
90
133
  borderRadius: '50%',
91
- animationName: 'loading-dot-pulse',
92
- animationDuration: `${ANIMATION_DURATION}s`,
93
- animationTimingFunction: 'cubic-bezier(0.45, 0, 0.55, 1)',
94
- animationIterationCount: 'infinite',
95
- animationDelay: `${dot.delay}s`,
96
- opacity: 0,
97
- transform: 'scale(0)',
134
+ opacity: 0.35,
135
+ transform: 'scale(0.2)',
98
136
  } }, dot.id))), jsx("div", { className: "absolute inset-0 flex items-center justify-center", style: { pointerEvents: 'none' }, children: jsx("p", { className: cn('text-xl font-semibold text-white', labelClassName), children: label }) })] }) }));
99
137
  }
100
138
 
@@ -0,0 +1 @@
1
+ export { MotionBeamFrame, MotionBeamFrame as BeamFrame, type BeamFrameProps, type BeamFrameTone, } from './motion-beam-frame';
@@ -0,0 +1,9 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var motionBeamFrame = require('./motion-beam-frame.js');
5
+
6
+
7
+
8
+ exports.BeamFrame = motionBeamFrame.MotionBeamFrame;
9
+ exports.MotionBeamFrame = motionBeamFrame.MotionBeamFrame;
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ export { MotionBeamFrame as BeamFrame, MotionBeamFrame } from './motion-beam-frame.mjs';
@@ -0,0 +1,3 @@
1
+ import { type BeamFrameProps } from '../beam-frame-config';
2
+ export type { BeamFrameProps, BeamFrameTone } from '../beam-frame-config';
3
+ export declare function MotionBeamFrame(props: BeamFrameProps): import("react/jsx-runtime").JSX.Element;