premium-react-loaders 4.0.0 → 4.1.1

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 (163) hide show
  1. package/README.md +21 -6
  2. package/dist/components/index.d.ts +1 -0
  3. package/dist/components/index.d.ts.map +1 -1
  4. package/dist/components/neon/NeonPulse.d.ts +12 -0
  5. package/dist/components/neon/NeonPulse.d.ts.map +1 -0
  6. package/dist/components/neon/NeonSpinner.d.ts +12 -0
  7. package/dist/components/neon/NeonSpinner.d.ts.map +1 -0
  8. package/dist/components/neon/NeonText.d.ts +12 -0
  9. package/dist/components/neon/NeonText.d.ts.map +1 -0
  10. package/dist/components/neon/index.d.ts +4 -0
  11. package/dist/components/neon/index.d.ts.map +1 -0
  12. package/dist/components/particle/ParticleField.d.ts +3 -0
  13. package/dist/components/particle/ParticleField.d.ts.map +1 -0
  14. package/dist/components/particle/ParticleTrail.d.ts +12 -0
  15. package/dist/components/particle/ParticleTrail.d.ts.map +1 -0
  16. package/dist/components/particle/index.d.ts +2 -0
  17. package/dist/components/particle/index.d.ts.map +1 -1
  18. package/dist/components/status/InfoIndicator.d.ts +29 -0
  19. package/dist/components/status/InfoIndicator.d.ts.map +1 -0
  20. package/dist/components/status/WarningIndicator.d.ts +29 -0
  21. package/dist/components/status/WarningIndicator.d.ts.map +1 -0
  22. package/dist/components/status/index.d.ts +2 -0
  23. package/dist/components/status/index.d.ts.map +1 -1
  24. package/dist/index.cjs +41 -27
  25. package/dist/index.js +41 -27
  26. package/dist/index10.cjs +1 -1
  27. package/dist/index10.js +1 -1
  28. package/dist/index11.cjs +1 -1
  29. package/dist/index11.js +1 -1
  30. package/dist/index12.cjs +1 -1
  31. package/dist/index12.js +1 -1
  32. package/dist/index13.cjs +1 -1
  33. package/dist/index13.js +1 -1
  34. package/dist/index14.cjs +1 -1
  35. package/dist/index14.js +1 -1
  36. package/dist/index15.cjs +1 -1
  37. package/dist/index15.js +1 -1
  38. package/dist/index17.cjs +1 -1
  39. package/dist/index17.js +1 -1
  40. package/dist/index18.cjs +1 -1
  41. package/dist/index18.js +1 -1
  42. package/dist/index19.cjs +1 -1
  43. package/dist/index19.js +1 -1
  44. package/dist/index20.cjs +1 -1
  45. package/dist/index20.js +1 -1
  46. package/dist/index21.cjs +1 -1
  47. package/dist/index21.js +1 -1
  48. package/dist/index22.cjs +1 -1
  49. package/dist/index22.js +1 -1
  50. package/dist/index23.cjs +1 -1
  51. package/dist/index23.js +1 -1
  52. package/dist/index24.cjs +1 -1
  53. package/dist/index24.js +1 -1
  54. package/dist/index25.cjs +1 -1
  55. package/dist/index25.js +1 -1
  56. package/dist/index26.cjs +1 -1
  57. package/dist/index26.js +1 -1
  58. package/dist/index27.cjs +1 -1
  59. package/dist/index27.js +1 -1
  60. package/dist/index28.cjs +1 -1
  61. package/dist/index28.js +1 -1
  62. package/dist/index29.cjs +1 -1
  63. package/dist/index29.js +1 -1
  64. package/dist/index30.cjs +1 -1
  65. package/dist/index30.js +1 -1
  66. package/dist/index31.cjs +1 -1
  67. package/dist/index31.js +1 -1
  68. package/dist/index32.cjs +1 -1
  69. package/dist/index32.js +1 -1
  70. package/dist/index33.cjs +1 -1
  71. package/dist/index33.js +1 -1
  72. package/dist/index34.cjs +1 -1
  73. package/dist/index34.js +1 -1
  74. package/dist/index35.cjs +1 -1
  75. package/dist/index35.js +1 -1
  76. package/dist/index36.cjs +1 -1
  77. package/dist/index36.js +1 -1
  78. package/dist/index37.cjs +59 -145
  79. package/dist/index37.js +61 -147
  80. package/dist/index38.cjs +32 -53
  81. package/dist/index38.js +33 -54
  82. package/dist/index39.cjs +138 -84
  83. package/dist/index39.js +140 -86
  84. package/dist/index40.cjs +33 -106
  85. package/dist/index40.js +34 -107
  86. package/dist/index41.cjs +66 -50
  87. package/dist/index41.js +66 -50
  88. package/dist/index42.cjs +123 -44
  89. package/dist/index42.js +124 -45
  90. package/dist/index43.cjs +43 -85
  91. package/dist/index43.js +44 -86
  92. package/dist/index44.cjs +35 -27
  93. package/dist/index44.js +35 -27
  94. package/dist/index45.cjs +87 -58
  95. package/dist/index45.js +87 -58
  96. package/dist/index46.cjs +35 -88
  97. package/dist/index46.js +36 -89
  98. package/dist/index47.cjs +62 -58
  99. package/dist/index47.js +63 -59
  100. package/dist/index48.cjs +87 -110
  101. package/dist/index48.js +88 -111
  102. package/dist/index49.cjs +48 -109
  103. package/dist/index49.js +49 -110
  104. package/dist/index50.cjs +110 -62
  105. package/dist/index50.js +111 -63
  106. package/dist/index51.cjs +104 -49
  107. package/dist/index51.js +104 -49
  108. package/dist/index52.cjs +43 -77
  109. package/dist/index52.js +44 -78
  110. package/dist/index53.cjs +56 -56
  111. package/dist/index53.js +56 -56
  112. package/dist/index54.cjs +104 -149
  113. package/dist/index54.js +106 -151
  114. package/dist/index55.cjs +97 -38
  115. package/dist/index55.js +99 -40
  116. package/dist/index56.cjs +161 -27
  117. package/dist/index56.js +162 -28
  118. package/dist/index57.cjs +36 -102
  119. package/dist/index57.js +38 -104
  120. package/dist/index58.cjs +19 -34
  121. package/dist/index58.js +20 -35
  122. package/dist/index59.cjs +87 -34
  123. package/dist/index59.js +89 -36
  124. package/dist/index60.cjs +25 -23
  125. package/dist/index60.js +25 -23
  126. package/dist/index61.cjs +32 -65
  127. package/dist/index61.js +34 -67
  128. package/dist/index62.cjs +29 -36
  129. package/dist/index62.js +30 -37
  130. package/dist/index63.cjs +66 -59
  131. package/dist/index63.js +69 -62
  132. package/dist/index64.cjs +71 -121
  133. package/dist/index64.js +72 -122
  134. package/dist/index65.cjs +92 -0
  135. package/dist/index65.js +92 -0
  136. package/dist/index66.cjs +76 -0
  137. package/dist/index66.js +76 -0
  138. package/dist/index67.cjs +76 -0
  139. package/dist/index67.js +76 -0
  140. package/dist/index68.cjs +64 -0
  141. package/dist/index68.js +64 -0
  142. package/dist/index69.cjs +60 -0
  143. package/dist/index69.js +60 -0
  144. package/dist/index7.cjs +1 -1
  145. package/dist/index7.js +1 -1
  146. package/dist/index70.cjs +57 -0
  147. package/dist/index70.js +57 -0
  148. package/dist/index71.cjs +125 -0
  149. package/dist/index71.js +125 -0
  150. package/dist/index8.cjs +1 -1
  151. package/dist/index8.js +1 -1
  152. package/dist/index9.cjs +1 -1
  153. package/dist/index9.js +1 -1
  154. package/dist/premium-react-loaders.css +85 -0
  155. package/dist/types/index.d.ts +1 -0
  156. package/dist/types/index.d.ts.map +1 -1
  157. package/dist/types/neon.d.ts +26 -0
  158. package/dist/types/neon.d.ts.map +1 -0
  159. package/dist/types/particle.d.ts +18 -0
  160. package/dist/types/particle.d.ts.map +1 -1
  161. package/dist/types/status.d.ts +30 -0
  162. package/dist/types/status.d.ts.map +1 -1
  163. package/package.json +1 -1
package/dist/index54.js CHANGED
@@ -1,30 +1,35 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef } from "react";
3
- import { Skeleton } from "./index7.js";
4
- import { useLoaderVisibility } from "./index64.js";
3
+ import { useReducedMotion, useLoaderVisibility, getEffectiveDuration } from "./index71.js";
5
4
  import { cn } from "./index3.js";
6
- import { normalizeSize } from "./index4.js";
7
- const FormFieldLoader = forwardRef(
5
+ import { parseSizeToNumber, normalizeSize } from "./index4.js";
6
+ const Helix = forwardRef(
8
7
  ({
9
8
  size = "md",
10
- fieldType = "text",
11
- showLabel = true,
12
- labelWidth = 100,
13
- fieldWidth = "100%",
14
- fieldHeight,
15
- showValidation = false,
16
- showRequired = false,
17
- showHelperText = false,
9
+ color = "#3b82f6",
10
+ secondaryColor = "#8b5cf6",
11
+ particleCount = 12,
12
+ radius = 20,
13
+ height,
14
+ turns = 1.5,
15
+ direction = "up",
16
+ particleSize = 6,
17
+ showLines = false,
18
+ speed = "normal",
19
+ reverse = false,
20
+ respectMotionPreference = true,
18
21
  delay = 0,
19
22
  minDuration = 0,
20
23
  transition,
21
24
  className,
22
25
  style,
23
- testId = "form-field-loader",
26
+ testId = "helix",
24
27
  visible = true,
25
- ariaLabel = "Loading form field...",
28
+ ariaLabel = "Loading...",
26
29
  ...rest
27
30
  }, ref) => {
31
+ const prefersReducedMotion = useReducedMotion();
32
+ const effectiveDuration = getEffectiveDuration(speed, respectMotionPreference, prefersReducedMotion);
28
33
  const { shouldRender, opacity, transitionStyle } = useLoaderVisibility(
29
34
  visible,
30
35
  delay,
@@ -32,158 +37,108 @@ const FormFieldLoader = forwardRef(
32
37
  transition
33
38
  );
34
39
  if (!shouldRender) return null;
35
- const getFieldHeight = () => {
36
- if (fieldHeight) return normalizeSize(fieldHeight);
37
- switch (fieldType) {
38
- case "textarea":
39
- return "80px";
40
- case "select":
41
- return "40px";
42
- case "checkbox":
43
- case "radio":
44
- return "20px";
45
- default:
46
- return "40px";
47
- }
48
- };
49
- return /* @__PURE__ */ jsxs(
40
+ const containerSize = normalizeSize(size);
41
+ const helixHeight = height ? parseSizeToNumber(normalizeSize(height), 80) : parseSizeToNumber(containerSize, 40) * 1.5;
42
+ const particles = Array.from({ length: particleCount }, (_, index) => {
43
+ const angle = index / particleCount * Math.PI * 2 * turns;
44
+ const y = index / particleCount * helixHeight;
45
+ const x = Math.cos(angle) * radius;
46
+ const z = Math.sin(angle) * radius;
47
+ const isSecondStrand = index % 2 === 1;
48
+ return {
49
+ x,
50
+ y,
51
+ z,
52
+ angle,
53
+ color: isSecondStrand ? secondaryColor : color
54
+ };
55
+ });
56
+ const animationClass = direction === "up" ? "animate-helix-rotate-up" : "animate-helix-rotate-down";
57
+ return /* @__PURE__ */ jsx(
50
58
  "div",
51
59
  {
52
60
  ref,
53
61
  "data-testid": testId,
54
- className: cn("w-full", className),
62
+ className: cn("inline-flex items-center justify-center", className),
55
63
  style: {
56
64
  ...style,
57
65
  opacity,
58
- transition: transitionStyle
66
+ transition: transitionStyle,
67
+ perspective: "600px"
59
68
  },
60
69
  role: "status",
61
70
  "aria-label": ariaLabel,
62
71
  "aria-busy": "true",
63
72
  ...rest,
64
- children: [
65
- showLabel && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 mb-2", children: [
66
- /* @__PURE__ */ jsx(
67
- Skeleton,
68
- {
69
- width: normalizeSize(labelWidth),
70
- height: 16,
71
- borderRadius: 4
72
- }
73
- ),
74
- showRequired && /* @__PURE__ */ jsx(
75
- Skeleton,
76
- {
77
- width: 8,
78
- height: 8,
79
- variant: "circular"
80
- }
81
- )
82
- ] }),
83
- /* @__PURE__ */ jsx("div", { className: "w-full", children: fieldType === "checkbox" || fieldType === "radio" ? (
84
- // Checkbox/Radio with label on the right
85
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
86
- /* @__PURE__ */ jsx(
87
- Skeleton,
88
- {
89
- width: 20,
90
- height: 20,
91
- variant: fieldType === "radio" ? "circular" : "rectangular",
92
- borderRadius: fieldType === "checkbox" ? 4 : void 0
93
- }
94
- ),
95
- /* @__PURE__ */ jsx(
96
- Skeleton,
97
- {
98
- width: 150,
99
- height: 16,
100
- borderRadius: 4
101
- }
102
- )
103
- ] })
104
- ) : fieldType === "select" ? (
105
- // Select dropdown
106
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
107
- /* @__PURE__ */ jsx(
108
- Skeleton,
109
- {
110
- width: fieldWidth,
111
- height: getFieldHeight(),
112
- borderRadius: 6
113
- }
114
- ),
115
- /* @__PURE__ */ jsx("div", { className: "absolute right-3 top-1/2 -translate-y-1/2", children: /* @__PURE__ */ jsx(
116
- Skeleton,
117
- {
118
- width: 12,
119
- height: 12,
120
- variant: "rectangular"
121
- }
122
- ) })
123
- ] })
124
- ) : fieldType === "file" ? (
125
- // File input
126
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
127
- /* @__PURE__ */ jsx(
128
- Skeleton,
129
- {
130
- width: 120,
131
- height: 40,
132
- borderRadius: 6
133
- }
134
- ),
135
- /* @__PURE__ */ jsx(
136
- Skeleton,
73
+ children: /* @__PURE__ */ jsxs(
74
+ "div",
75
+ {
76
+ className: cn("relative", animationClass),
77
+ style: {
78
+ width: `${radius * 2 + particleSize * 2}px`,
79
+ height: `${helixHeight}px`,
80
+ transformStyle: "preserve-3d",
81
+ animationDuration: effectiveDuration,
82
+ animationDirection: reverse ? "reverse" : "normal"
83
+ },
84
+ children: [
85
+ showLines && particles.map((particle, index) => {
86
+ if (index === 0) return null;
87
+ const prevParticle = particles[index - 1];
88
+ return /* @__PURE__ */ jsx(
89
+ "svg",
90
+ {
91
+ className: "absolute",
92
+ style: {
93
+ left: "50%",
94
+ top: "0",
95
+ transform: "translateX(-50%)",
96
+ width: `${radius * 2 + particleSize * 2}px`,
97
+ height: `${helixHeight}px`,
98
+ pointerEvents: "none"
99
+ },
100
+ children: /* @__PURE__ */ jsx(
101
+ "line",
102
+ {
103
+ x1: prevParticle.x + radius + particleSize,
104
+ y1: prevParticle.y,
105
+ x2: particle.x + radius + particleSize,
106
+ y2: particle.y,
107
+ stroke: particle.color,
108
+ strokeWidth: "1",
109
+ opacity: "0.3"
110
+ }
111
+ )
112
+ },
113
+ `line-${index}`
114
+ );
115
+ }),
116
+ particles.map((particle, index) => /* @__PURE__ */ jsx(
117
+ "div",
137
118
  {
138
- width: "calc(100% - 132px)",
139
- height: 40,
140
- borderRadius: 6
141
- }
142
- )
143
- ] })
144
- ) : (
145
- // Text, textarea, etc.
146
- /* @__PURE__ */ jsx(
147
- Skeleton,
148
- {
149
- width: fieldWidth,
150
- height: getFieldHeight(),
151
- borderRadius: 6
152
- }
153
- )
154
- ) }),
155
- showHelperText && /* @__PURE__ */ jsx("div", { className: "mt-1", children: /* @__PURE__ */ jsx(
156
- Skeleton,
157
- {
158
- width: 200,
159
- height: 14,
160
- borderRadius: 4
161
- }
162
- ) }),
163
- showValidation && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 mt-1", children: [
164
- /* @__PURE__ */ jsx(
165
- Skeleton,
166
- {
167
- width: 14,
168
- height: 14,
169
- variant: "circular"
170
- }
171
- ),
172
- /* @__PURE__ */ jsx(
173
- Skeleton,
174
- {
175
- width: 180,
176
- height: 14,
177
- borderRadius: 4
178
- }
179
- )
180
- ] })
181
- ]
119
+ className: "absolute animate-helix-particle-pulse",
120
+ style: {
121
+ left: "50%",
122
+ top: `${particle.y}px`,
123
+ width: `${particleSize}px`,
124
+ height: `${particleSize}px`,
125
+ backgroundColor: particle.color,
126
+ borderRadius: "50%",
127
+ transform: `translate3d(${particle.x}px, 0, ${particle.z}px)`,
128
+ animationDuration: "1s",
129
+ animationDelay: `${index * 0.08}s`
130
+ }
131
+ },
132
+ index
133
+ ))
134
+ ]
135
+ }
136
+ )
182
137
  }
183
138
  );
184
139
  }
185
140
  );
186
- FormFieldLoader.displayName = "FormFieldLoader";
141
+ Helix.displayName = "Helix";
187
142
  export {
188
- FormFieldLoader
143
+ Helix
189
144
  };
package/dist/index55.cjs CHANGED
@@ -2,52 +2,111 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const react = require("react");
5
+ const hooks = require("./index71.cjs");
5
6
  const classNames = require("./index3.cjs");
6
- const LiveRegion = react.forwardRef(
7
+ const colors = require("./index4.cjs");
8
+ const PerspectiveRing = react.forwardRef(
7
9
  ({
8
- message,
9
- politeness = "polite",
10
- clearAfter,
11
- atomic = true,
12
- relevant = "additions",
13
- className
10
+ size = "md",
11
+ color = "#3b82f6",
12
+ tilt = 45,
13
+ thickness = 4,
14
+ showShadow = true,
15
+ shadowBlur = 10,
16
+ segments = 8,
17
+ speed = "normal",
18
+ reverse = false,
19
+ respectMotionPreference = true,
20
+ delay = 0,
21
+ minDuration = 0,
22
+ transition,
23
+ className,
24
+ style,
25
+ testId = "perspective-ring",
26
+ visible = true,
27
+ ariaLabel = "Loading...",
28
+ ...rest
14
29
  }, ref) => {
15
- const [displayMessage, setDisplayMessage] = react.useState(message);
16
- const clearTimerRef = react.useRef(null);
17
- react.useEffect(() => {
18
- setDisplayMessage(message);
19
- if (clearTimerRef.current) {
20
- clearTimeout(clearTimerRef.current);
21
- clearTimerRef.current = null;
22
- }
23
- if (clearAfter && message) {
24
- clearTimerRef.current = setTimeout(() => {
25
- setDisplayMessage("");
26
- }, clearAfter);
27
- }
28
- return () => {
29
- if (clearTimerRef.current) {
30
- clearTimeout(clearTimerRef.current);
31
- }
32
- };
33
- }, [message, clearAfter]);
30
+ const prefersReducedMotion = hooks.useReducedMotion();
31
+ const effectiveDuration = hooks.getEffectiveDuration(speed, respectMotionPreference, prefersReducedMotion);
32
+ const { shouldRender, opacity, transitionStyle } = hooks.useLoaderVisibility(
33
+ visible,
34
+ delay,
35
+ minDuration,
36
+ transition
37
+ );
38
+ if (!shouldRender) return null;
39
+ const ringSize = colors.normalizeSize(size);
40
+ const tiltAngle = Math.min(Math.max(tilt, 0), 90);
41
+ const segmentCount = Math.min(Math.max(segments, 4), 24);
34
42
  return /* @__PURE__ */ jsxRuntime.jsx(
35
43
  "div",
36
44
  {
37
45
  ref,
38
- className: classNames.cn(
39
- // Visually hidden but accessible to screen readers
40
- "sr-only absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0",
41
- className
42
- ),
43
- role: politeness === "assertive" ? "alert" : "status",
44
- "aria-live": politeness,
45
- "aria-atomic": atomic,
46
- "aria-relevant": relevant,
47
- children: displayMessage
46
+ "data-testid": testId,
47
+ className: classNames.cn("inline-flex items-center justify-center", className),
48
+ style: {
49
+ ...style,
50
+ opacity,
51
+ transition: transitionStyle,
52
+ perspective: "800px"
53
+ },
54
+ role: "status",
55
+ "aria-label": ariaLabel,
56
+ "aria-busy": "true",
57
+ ...rest,
58
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", style: { width: ringSize, height: ringSize }, children: [
59
+ showShadow && /* @__PURE__ */ jsxRuntime.jsx(
60
+ "div",
61
+ {
62
+ className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2",
63
+ style: {
64
+ width: `calc(${ringSize} * 0.8)`,
65
+ height: `calc(${ringSize} * 0.2)`,
66
+ borderRadius: "50%",
67
+ backgroundColor: "rgba(0, 0, 0, 0.2)",
68
+ filter: `blur(${shadowBlur}px)`,
69
+ transform: `translate(-50%, calc(${ringSize} * 0.3))`
70
+ }
71
+ }
72
+ ),
73
+ /* @__PURE__ */ jsxRuntime.jsx(
74
+ "div",
75
+ {
76
+ className: "absolute inset-0 animate-spinner-rotate",
77
+ style: {
78
+ transformStyle: "preserve-3d",
79
+ transform: `rotateX(${tiltAngle}deg)`,
80
+ animationDuration: effectiveDuration,
81
+ animationDirection: reverse ? "reverse" : "normal"
82
+ },
83
+ children: Array.from({ length: segmentCount }).map((_, index) => {
84
+ const angle = index / segmentCount * 360;
85
+ const segmentOpacity = 0.3 + (Math.cos(angle * Math.PI / 180) + 1) / 2 * 0.7;
86
+ return /* @__PURE__ */ jsxRuntime.jsx(
87
+ "div",
88
+ {
89
+ className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2",
90
+ style: {
91
+ width: ringSize,
92
+ height: ringSize,
93
+ borderRadius: "50%",
94
+ border: `${thickness}px solid ${color}`,
95
+ borderColor: "transparent",
96
+ borderTopColor: color,
97
+ transform: `rotateZ(${angle}deg)`,
98
+ opacity: segmentOpacity
99
+ }
100
+ },
101
+ index
102
+ );
103
+ })
104
+ }
105
+ )
106
+ ] })
48
107
  }
49
108
  );
50
109
  }
51
110
  );
52
- LiveRegion.displayName = "LiveRegion";
53
- exports.LiveRegion = LiveRegion;
111
+ PerspectiveRing.displayName = "PerspectiveRing";
112
+ exports.PerspectiveRing = PerspectiveRing;
package/dist/index55.js CHANGED
@@ -1,53 +1,112 @@
1
- import { jsx } from "react/jsx-runtime";
2
- import { forwardRef, useState, useRef, useEffect } from "react";
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { useReducedMotion, useLoaderVisibility, getEffectiveDuration } from "./index71.js";
3
4
  import { cn } from "./index3.js";
4
- const LiveRegion = forwardRef(
5
+ import { normalizeSize } from "./index4.js";
6
+ const PerspectiveRing = forwardRef(
5
7
  ({
6
- message,
7
- politeness = "polite",
8
- clearAfter,
9
- atomic = true,
10
- relevant = "additions",
11
- className
8
+ size = "md",
9
+ color = "#3b82f6",
10
+ tilt = 45,
11
+ thickness = 4,
12
+ showShadow = true,
13
+ shadowBlur = 10,
14
+ segments = 8,
15
+ speed = "normal",
16
+ reverse = false,
17
+ respectMotionPreference = true,
18
+ delay = 0,
19
+ minDuration = 0,
20
+ transition,
21
+ className,
22
+ style,
23
+ testId = "perspective-ring",
24
+ visible = true,
25
+ ariaLabel = "Loading...",
26
+ ...rest
12
27
  }, ref) => {
13
- const [displayMessage, setDisplayMessage] = useState(message);
14
- const clearTimerRef = useRef(null);
15
- useEffect(() => {
16
- setDisplayMessage(message);
17
- if (clearTimerRef.current) {
18
- clearTimeout(clearTimerRef.current);
19
- clearTimerRef.current = null;
20
- }
21
- if (clearAfter && message) {
22
- clearTimerRef.current = setTimeout(() => {
23
- setDisplayMessage("");
24
- }, clearAfter);
25
- }
26
- return () => {
27
- if (clearTimerRef.current) {
28
- clearTimeout(clearTimerRef.current);
29
- }
30
- };
31
- }, [message, clearAfter]);
28
+ const prefersReducedMotion = useReducedMotion();
29
+ const effectiveDuration = getEffectiveDuration(speed, respectMotionPreference, prefersReducedMotion);
30
+ const { shouldRender, opacity, transitionStyle } = useLoaderVisibility(
31
+ visible,
32
+ delay,
33
+ minDuration,
34
+ transition
35
+ );
36
+ if (!shouldRender) return null;
37
+ const ringSize = normalizeSize(size);
38
+ const tiltAngle = Math.min(Math.max(tilt, 0), 90);
39
+ const segmentCount = Math.min(Math.max(segments, 4), 24);
32
40
  return /* @__PURE__ */ jsx(
33
41
  "div",
34
42
  {
35
43
  ref,
36
- className: cn(
37
- // Visually hidden but accessible to screen readers
38
- "sr-only absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0",
39
- className
40
- ),
41
- role: politeness === "assertive" ? "alert" : "status",
42
- "aria-live": politeness,
43
- "aria-atomic": atomic,
44
- "aria-relevant": relevant,
45
- children: displayMessage
44
+ "data-testid": testId,
45
+ className: cn("inline-flex items-center justify-center", className),
46
+ style: {
47
+ ...style,
48
+ opacity,
49
+ transition: transitionStyle,
50
+ perspective: "800px"
51
+ },
52
+ role: "status",
53
+ "aria-label": ariaLabel,
54
+ "aria-busy": "true",
55
+ ...rest,
56
+ children: /* @__PURE__ */ jsxs("div", { className: "relative", style: { width: ringSize, height: ringSize }, children: [
57
+ showShadow && /* @__PURE__ */ jsx(
58
+ "div",
59
+ {
60
+ className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2",
61
+ style: {
62
+ width: `calc(${ringSize} * 0.8)`,
63
+ height: `calc(${ringSize} * 0.2)`,
64
+ borderRadius: "50%",
65
+ backgroundColor: "rgba(0, 0, 0, 0.2)",
66
+ filter: `blur(${shadowBlur}px)`,
67
+ transform: `translate(-50%, calc(${ringSize} * 0.3))`
68
+ }
69
+ }
70
+ ),
71
+ /* @__PURE__ */ jsx(
72
+ "div",
73
+ {
74
+ className: "absolute inset-0 animate-spinner-rotate",
75
+ style: {
76
+ transformStyle: "preserve-3d",
77
+ transform: `rotateX(${tiltAngle}deg)`,
78
+ animationDuration: effectiveDuration,
79
+ animationDirection: reverse ? "reverse" : "normal"
80
+ },
81
+ children: Array.from({ length: segmentCount }).map((_, index) => {
82
+ const angle = index / segmentCount * 360;
83
+ const segmentOpacity = 0.3 + (Math.cos(angle * Math.PI / 180) + 1) / 2 * 0.7;
84
+ return /* @__PURE__ */ jsx(
85
+ "div",
86
+ {
87
+ className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2",
88
+ style: {
89
+ width: ringSize,
90
+ height: ringSize,
91
+ borderRadius: "50%",
92
+ border: `${thickness}px solid ${color}`,
93
+ borderColor: "transparent",
94
+ borderTopColor: color,
95
+ transform: `rotateZ(${angle}deg)`,
96
+ opacity: segmentOpacity
97
+ }
98
+ },
99
+ index
100
+ );
101
+ })
102
+ }
103
+ )
104
+ ] })
46
105
  }
47
106
  );
48
107
  }
49
108
  );
50
- LiveRegion.displayName = "LiveRegion";
109
+ PerspectiveRing.displayName = "PerspectiveRing";
51
110
  export {
52
- LiveRegion
111
+ PerspectiveRing
53
112
  };