@jepepa/like-button 0.8.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,841 @@
1
+ // src/LikeButton/DefaultHeartIcon.tsx
2
+ import { jsx } from "react/jsx-runtime";
3
+ function DefaultHeartIcon({ size, className }) {
4
+ return /* @__PURE__ */ jsx(
5
+ "svg",
6
+ {
7
+ className,
8
+ style: {
9
+ width: size,
10
+ height: size,
11
+ stroke: "#111827",
12
+ strokeWidth: 2,
13
+ fill: "transparent"
14
+ },
15
+ viewBox: "0 0 24 24",
16
+ "aria-hidden": "true",
17
+ children: /* @__PURE__ */ jsx(
18
+ "path",
19
+ {
20
+ strokeLinecap: "round",
21
+ strokeLinejoin: "round",
22
+ d: "M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
23
+ }
24
+ )
25
+ }
26
+ );
27
+ }
28
+
29
+ // src/LikeButton/LikeButton.tsx
30
+ import { useId, useMemo } from "react";
31
+
32
+ // src/Particle/shapes/CircleShape.tsx
33
+ import { jsx as jsx2 } from "react/jsx-runtime";
34
+ function CircleShape({ size, color, className = "" }) {
35
+ return /* @__PURE__ */ jsx2(
36
+ "svg",
37
+ {
38
+ width: size,
39
+ height: size,
40
+ viewBox: "0 0 24 24",
41
+ className: `fill-current ${className}`,
42
+ style: { color },
43
+ "aria-hidden": "true",
44
+ children: /* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "10" })
45
+ }
46
+ );
47
+ }
48
+
49
+ // src/Particle/shapes/HeartShape.tsx
50
+ import { jsx as jsx3 } from "react/jsx-runtime";
51
+ function HeartShape({ size, color, className = "" }) {
52
+ return /* @__PURE__ */ jsx3(
53
+ "svg",
54
+ {
55
+ width: size,
56
+ height: size,
57
+ viewBox: "0 0 24 24",
58
+ className: `fill-current ${className}`,
59
+ style: { color },
60
+ "aria-hidden": "true",
61
+ children: /* @__PURE__ */ jsx3("path", { d: "M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" })
62
+ }
63
+ );
64
+ }
65
+
66
+ // src/Particle/shapes/SparkleShape.tsx
67
+ import { jsx as jsx4 } from "react/jsx-runtime";
68
+ function SparkleShape({ size, color, className = "" }) {
69
+ return /* @__PURE__ */ jsx4(
70
+ "svg",
71
+ {
72
+ width: size,
73
+ height: size,
74
+ viewBox: "0 0 24 24",
75
+ className: `fill-current ${className}`,
76
+ style: { color },
77
+ "aria-hidden": "true",
78
+ children: /* @__PURE__ */ jsx4("path", { d: "M12 2l2.5 7.5L22 12l-7.5 2.5L12 22l-2.5-7.5L2 12l7.5-2.5L12 2z" })
79
+ }
80
+ );
81
+ }
82
+
83
+ // src/Particle/shapes/SquareShape.tsx
84
+ import { jsx as jsx5 } from "react/jsx-runtime";
85
+ function SquareShape({ size, color, className = "" }) {
86
+ return /* @__PURE__ */ jsx5(
87
+ "svg",
88
+ {
89
+ width: size,
90
+ height: size,
91
+ viewBox: "0 0 24 24",
92
+ className: `fill-current ${className}`,
93
+ style: { color },
94
+ "aria-hidden": "true",
95
+ children: /* @__PURE__ */ jsx5("rect", { x: "3", y: "3", width: "18", height: "18", rx: "3" })
96
+ }
97
+ );
98
+ }
99
+
100
+ // src/Particle/shapes/StarShape.tsx
101
+ import { jsx as jsx6 } from "react/jsx-runtime";
102
+ function StarShape({ size, color, className = "" }) {
103
+ return /* @__PURE__ */ jsx6(
104
+ "svg",
105
+ {
106
+ width: size,
107
+ height: size,
108
+ viewBox: "0 0 24 24",
109
+ className: `fill-current ${className}`,
110
+ style: { color },
111
+ "aria-hidden": "true",
112
+ children: /* @__PURE__ */ jsx6("path", { d: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" })
113
+ }
114
+ );
115
+ }
116
+
117
+ // src/Particle/shapes/utils.ts
118
+ function getParticleShape(shape) {
119
+ if (typeof shape === "object" && "render" in shape) {
120
+ return ({ size, color, className }) => shape.render({ size, color, className });
121
+ }
122
+ switch (shape) {
123
+ case "heart":
124
+ return HeartShape;
125
+ case "star":
126
+ return StarShape;
127
+ case "circle":
128
+ return CircleShape;
129
+ case "square":
130
+ return SquareShape;
131
+ case "sparkle":
132
+ return SparkleShape;
133
+ default:
134
+ return HeartShape;
135
+ }
136
+ }
137
+
138
+ // src/Particle/useParticle.ts
139
+ import { useEffect, useState } from "react";
140
+ function useParticle({
141
+ angle,
142
+ distance,
143
+ scale,
144
+ speed,
145
+ easing,
146
+ fadeOut
147
+ }) {
148
+ const [isAnimating, setIsAnimating] = useState(false);
149
+ useEffect(() => {
150
+ const timer = requestAnimationFrame(() => setIsAnimating(true));
151
+ return () => cancelAnimationFrame(timer);
152
+ }, []);
153
+ const x = Math.cos(angle * Math.PI / 180) * distance;
154
+ const y = Math.sin(angle * Math.PI / 180) * distance;
155
+ return {
156
+ isAnimating,
157
+ x,
158
+ y,
159
+ transform: isAnimating ? `translate(${x}px, ${y}px) scale(${scale})` : "translate(0, 0) scale(0)",
160
+ opacity: isAnimating ? 0 : 1,
161
+ speed,
162
+ easing,
163
+ fadeOut
164
+ };
165
+ }
166
+
167
+ // src/Particle/Particle.tsx
168
+ import { jsx as jsx7 } from "react/jsx-runtime";
169
+ function Particle({
170
+ angle,
171
+ distance,
172
+ scale,
173
+ color,
174
+ shape,
175
+ speed,
176
+ easing,
177
+ fadeOut
178
+ }) {
179
+ const { transform, opacity } = useParticle({
180
+ angle,
181
+ distance,
182
+ scale,
183
+ speed,
184
+ easing,
185
+ fadeOut
186
+ });
187
+ const ShapeComponent = getParticleShape(shape);
188
+ return /* @__PURE__ */ jsx7(
189
+ "div",
190
+ {
191
+ className: "absolute w-10 h-10 transition-all",
192
+ style: {
193
+ color,
194
+ transform,
195
+ opacity: fadeOut ? opacity : 1,
196
+ transitionDuration: `${speed}ms`,
197
+ transitionTimingFunction: easing
198
+ },
199
+ children: /* @__PURE__ */ jsx7(ShapeComponent, { size: 40, color, className: "w-full h-full" })
200
+ }
201
+ );
202
+ }
203
+
204
+ // src/LikeButton/useLikeButton.ts
205
+ import { useCallback, useState as useState2 } from "react";
206
+
207
+ // src/Particle/presets.ts
208
+ var DEFAULT_PARTICLE_CONFIG = {
209
+ shape: "heart",
210
+ speed: 500,
211
+ distance: { min: 60, max: 100 },
212
+ spread: 360,
213
+ spreadOffset: 0,
214
+ size: { min: 1, max: 1.5 },
215
+ colors: ["#EF4444", "#B9FF14", "#3B82F6"],
216
+ count: 8,
217
+ easing: "cubic-bezier(0.22, 1, 0.36, 1)",
218
+ fadeOut: true
219
+ };
220
+ var PARTICLE_PRESETS = {
221
+ /**
222
+ * Burst - Fast, enthusiastic explosion effect
223
+ * - Wide 360° spread
224
+ * - Fast animation (400ms)
225
+ * - Multiple colors
226
+ * - 12 particles
227
+ * - Perfect for: Likes, favorites, celebrations
228
+ */
229
+ burst: {
230
+ shape: "heart",
231
+ speed: 400,
232
+ distance: { min: 80, max: 120 },
233
+ spread: 360,
234
+ spreadOffset: 0,
235
+ size: { min: 1, max: 1.5 },
236
+ colors: ["#EF4444", "#F59E0B", "#3B82F6"],
237
+ count: 12,
238
+ easing: "cubic-bezier(0.22, 1, 0.36, 1)",
239
+ fadeOut: true
240
+ },
241
+ /**
242
+ * Fountain - Upward spray effect
243
+ * - 120° upward spread
244
+ * - Medium animation (600ms)
245
+ * - Cool colors (blue, purple, pink)
246
+ * - 10 particles
247
+ * - Perfect for: Achievements, upgrades, success
248
+ */
249
+ fountain: {
250
+ shape: "circle",
251
+ speed: 600,
252
+ distance: { min: 60, max: 100 },
253
+ spread: 120,
254
+ spreadOffset: -90,
255
+ // Upward
256
+ size: { min: 0.8, max: 1.2 },
257
+ colors: ["#3B82F6", "#8B5CF6", "#EC4899"],
258
+ count: 10,
259
+ easing: "cubic-bezier(0.22, 1, 0.36, 1)",
260
+ fadeOut: true
261
+ },
262
+ /**
263
+ * Confetti - Colorful celebration effect
264
+ * - Full 360° spread
265
+ * - Slow animation (800ms)
266
+ * - Rainbow colors
267
+ * - 15 particles
268
+ * - Perfect for: Milestones, victories, special events
269
+ */
270
+ confetti: {
271
+ shape: "square",
272
+ speed: 800,
273
+ distance: { min: 70, max: 110 },
274
+ spread: 360,
275
+ spreadOffset: 0,
276
+ size: { min: 0.6, max: 1.4 },
277
+ colors: ["#EF4444", "#F59E0B", "#10B981", "#3B82F6", "#8B5CF6", "#EC4899"],
278
+ count: 15,
279
+ easing: "ease-out",
280
+ fadeOut: true
281
+ },
282
+ /**
283
+ * Gentle - Subtle, calm effect
284
+ * - 180° upward spread
285
+ * - Slow animation (700ms)
286
+ * - Soft red tones
287
+ * - 6 particles
288
+ * - Perfect for: Subtle interactions, quiet appreciation
289
+ */
290
+ gentle: {
291
+ shape: "heart",
292
+ speed: 700,
293
+ distance: { min: 40, max: 60 },
294
+ spread: 180,
295
+ spreadOffset: -90,
296
+ size: { min: 0.8, max: 1 },
297
+ colors: ["#EF4444", "#F87171"],
298
+ count: 6,
299
+ easing: "ease-in-out",
300
+ fadeOut: true
301
+ },
302
+ /**
303
+ * Fireworks - Explosive sparkle effect
304
+ * - Full 360° spread
305
+ * - Medium-fast animation (500ms)
306
+ * - Warm sparkle colors
307
+ * - 16 particles
308
+ * - Large size range
309
+ * - Perfect for: Big celebrations, major achievements
310
+ */
311
+ fireworks: {
312
+ shape: "sparkle",
313
+ speed: 500,
314
+ distance: { min: 100, max: 150 },
315
+ spread: 360,
316
+ spreadOffset: 0,
317
+ size: { min: 1.2, max: 2 },
318
+ colors: ["#FBBF24", "#F59E0B", "#EF4444", "#EC4899"],
319
+ count: 16,
320
+ easing: "cubic-bezier(0.22, 1, 0.36, 1)",
321
+ fadeOut: true
322
+ }
323
+ };
324
+ function getParticlePreset(preset) {
325
+ return PARTICLE_PRESETS[preset];
326
+ }
327
+
328
+ // src/Particle/utils.ts
329
+ function normalizeRange(value) {
330
+ return typeof value === "number" ? { min: value, max: value } : value;
331
+ }
332
+ function randomInRange(range) {
333
+ return range.min + Math.random() * (range.max - range.min);
334
+ }
335
+ function normalizeAngle(angle) {
336
+ const normalized = angle % 360;
337
+ const result = normalized < 0 ? normalized + 360 : normalized;
338
+ return result === 0 ? 0 : result;
339
+ }
340
+ function randomAngle(spread, offset) {
341
+ const angle = offset + Math.random() * spread;
342
+ return normalizeAngle(angle);
343
+ }
344
+ function mergeParticleConfig(base, override) {
345
+ if (!override) {
346
+ return base;
347
+ }
348
+ return {
349
+ ...base,
350
+ ...override
351
+ };
352
+ }
353
+ function resolveParticleConfig(preset, config) {
354
+ let resolved = { ...DEFAULT_PARTICLE_CONFIG };
355
+ if (preset) {
356
+ resolved = mergeParticleConfig(resolved, getParticlePreset(preset));
357
+ }
358
+ if (config) {
359
+ resolved = mergeParticleConfig(resolved, config);
360
+ }
361
+ return resolved;
362
+ }
363
+
364
+ // src/LikeButton/useLikeButton.ts
365
+ var LIKE_BUTTON_DEFAULTS = {
366
+ maxClicks: 14,
367
+ size: 96,
368
+ fillColor: "#EF4444",
369
+ waveColor: "#B91C1C"
370
+ };
371
+ function useLikeButton(options = {}) {
372
+ const {
373
+ localClicks: externalLocalClicks,
374
+ maxClicks = LIKE_BUTTON_DEFAULTS.maxClicks,
375
+ onClick,
376
+ onRightClick,
377
+ disabled: externalDisabled,
378
+ showParticles = true,
379
+ particlePreset,
380
+ particleConfig,
381
+ ariaLabel: customAriaLabel
382
+ } = options;
383
+ const [internalLocalClicks, setInternalLocalClicks] = useState2(0);
384
+ const [particles, setParticles] = useState2([]);
385
+ const localClicks = externalLocalClicks ?? internalLocalClicks;
386
+ const isMaxed = localClicks >= maxClicks;
387
+ const disabled = externalDisabled ?? isMaxed;
388
+ const spawnParticles = useCallback(() => {
389
+ if (!showParticles) return;
390
+ const config = resolveParticleConfig(particlePreset, particleConfig);
391
+ const distanceRange = normalizeRange(config.distance);
392
+ const sizeRange = normalizeRange(config.size);
393
+ const id = Date.now();
394
+ const newParticles = Array.from({ length: config.count }).map((_, i) => ({
395
+ id: `${id}-${i}`,
396
+ angle: randomAngle(config.spread, config.spreadOffset),
397
+ distance: randomInRange(distanceRange),
398
+ scale: randomInRange(sizeRange),
399
+ color: config.colors[Math.floor(Math.random() * config.colors.length)],
400
+ shape: config.shape,
401
+ speed: config.speed,
402
+ easing: config.easing,
403
+ fadeOut: config.fadeOut
404
+ }));
405
+ setParticles((prev) => [...prev, ...newParticles]);
406
+ const cleanupDelay = config.speed + 100;
407
+ setTimeout(() => {
408
+ setParticles((prev) => prev.filter((p) => !newParticles.find((np) => np.id === p.id)));
409
+ }, cleanupDelay);
410
+ }, [showParticles, particlePreset, particleConfig]);
411
+ const handleClick = useCallback(() => {
412
+ if (disabled) return;
413
+ const newLocalClicks = localClicks + 1;
414
+ if (externalLocalClicks === void 0) {
415
+ setInternalLocalClicks(newLocalClicks);
416
+ }
417
+ spawnParticles();
418
+ onClick?.(newLocalClicks);
419
+ }, [disabled, localClicks, externalLocalClicks, spawnParticles, onClick]);
420
+ const handleRightClick = useCallback(
421
+ (e) => {
422
+ e.preventDefault();
423
+ if (disabled) return;
424
+ onRightClick?.(localClicks);
425
+ },
426
+ [disabled, localClicks, onRightClick]
427
+ );
428
+ const fillPercentage = localClicks / maxClicks * 100;
429
+ const defaultAriaLabel = isMaxed ? "Thank you for your likes!" : `Like this content. ${maxClicks - localClicks} clicks remaining`;
430
+ return {
431
+ localClicks,
432
+ isMaxed,
433
+ disabled,
434
+ fillPercentage,
435
+ particles,
436
+ handleClick,
437
+ handleRightClick,
438
+ ariaLabel: customAriaLabel ?? defaultAriaLabel,
439
+ isPressed: localClicks > 0
440
+ };
441
+ }
442
+
443
+ // src/LikeButton/utils.ts
444
+ var DEFAULT_STYLES = {
445
+ borderWidth: 4,
446
+ borderColor: "#111827",
447
+ shadowOffset: 8,
448
+ shadowColor: "#111827",
449
+ backgroundColor: "white"
450
+ };
451
+ function getShapeStyles(shape = "circle") {
452
+ if (typeof shape === "string") {
453
+ switch (shape) {
454
+ case "circle":
455
+ return { borderRadius: "9999px" };
456
+ case "rounded":
457
+ return { borderRadius: "1rem" };
458
+ case "square":
459
+ return { borderRadius: "0" };
460
+ default:
461
+ return { borderRadius: "9999px" };
462
+ }
463
+ }
464
+ const styles = {};
465
+ if (shape.clipPath) {
466
+ styles.clipPath = shape.clipPath;
467
+ styles.borderRadius = shape.borderRadius ?? "0";
468
+ } else if (shape.borderRadius) {
469
+ styles.borderRadius = shape.borderRadius;
470
+ }
471
+ return styles;
472
+ }
473
+ function computeHoverOffset(shadowOffset) {
474
+ return Math.max(shadowOffset / 2, 2);
475
+ }
476
+ function computeButtonStyles(size, mergedStyles, shapeStyles) {
477
+ return {
478
+ width: size,
479
+ height: size,
480
+ borderWidth: mergedStyles.borderWidth,
481
+ borderColor: mergedStyles.borderColor,
482
+ backgroundColor: mergedStyles.backgroundColor,
483
+ boxShadow: `${mergedStyles.shadowOffset}px ${mergedStyles.shadowOffset}px 0px ${mergedStyles.shadowColor}`,
484
+ ...shapeStyles
485
+ };
486
+ }
487
+ function generateDynamicStyles(buttonSelector, hoverShadowOffset, mergedStyles) {
488
+ const translateOnHover = mergedStyles.shadowOffset - hoverShadowOffset;
489
+ return `
490
+ ${buttonSelector}:hover:not(:disabled) {
491
+ box-shadow: ${hoverShadowOffset}px ${hoverShadowOffset}px 0px ${mergedStyles.shadowColor};
492
+ transform: translate(${translateOnHover}px, ${translateOnHover}px);
493
+ }
494
+ ${buttonSelector}:active:not(:disabled) {
495
+ box-shadow: none;
496
+ transform: translate(${mergedStyles.shadowOffset}px, ${mergedStyles.shadowOffset}px) scale(0.9);
497
+ }
498
+ `;
499
+ }
500
+ var CURSOR_SVGS = {
501
+ heart: `data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 24 24' fill='%23EF4444' stroke='%23111827' stroke-width='1.5'><path d='M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z'/></svg>`,
502
+ star: `data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 24 24' fill='%23FBBF24' stroke='%23111827' stroke-width='1.5'><path d='M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z'/></svg>`,
503
+ "thumbs-up": `data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 24 24' fill='%233B82F6' stroke='%23111827' stroke-width='1.5'><path d='M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3'/></svg>`
504
+ };
505
+ var DEFAULT_HOTSPOT = 16;
506
+ function getCursorStyle(cursor = "heart") {
507
+ if (typeof cursor === "string") {
508
+ switch (cursor) {
509
+ case "pointer":
510
+ return "pointer";
511
+ case "none":
512
+ return "none";
513
+ case "heart":
514
+ case "star":
515
+ case "thumbs-up":
516
+ return `url("${CURSOR_SVGS[cursor]}") ${DEFAULT_HOTSPOT} ${DEFAULT_HOTSPOT}, pointer`;
517
+ default:
518
+ return "pointer";
519
+ }
520
+ }
521
+ const {
522
+ url,
523
+ hotspotX = DEFAULT_HOTSPOT,
524
+ hotspotY = DEFAULT_HOTSPOT,
525
+ fallback = "pointer"
526
+ } = cursor;
527
+ return `url("${url}") ${hotspotX} ${hotspotY}, ${fallback}`;
528
+ }
529
+
530
+ // src/LikeButton/LikeButton.tsx
531
+ import { jsx as jsx8, jsxs } from "react/jsx-runtime";
532
+ var MAX_FILL_HEIGHT = 85;
533
+ function LikeButton({
534
+ size = LIKE_BUTTON_DEFAULTS.size,
535
+ fillColor = LIKE_BUTTON_DEFAULTS.fillColor,
536
+ waveColor = LIKE_BUTTON_DEFAULTS.waveColor,
537
+ className = "",
538
+ showParticles = true,
539
+ renderIcon,
540
+ shape = "circle",
541
+ styles = {},
542
+ cursor = "heart",
543
+ minFillPercent = 0,
544
+ ...hookOptions
545
+ }) {
546
+ const clampedMinFill = Math.max(0, Math.min(MAX_FILL_HEIGHT, minFillPercent));
547
+ const reactId = useId();
548
+ const buttonId = `like-button${reactId.replace(/:/g, "-")}`;
549
+ const {
550
+ handleClick,
551
+ handleRightClick,
552
+ disabled,
553
+ ariaLabel,
554
+ isPressed,
555
+ isMaxed,
556
+ fillPercentage,
557
+ particles
558
+ } = useLikeButton({ showParticles, ...hookOptions });
559
+ const mergedStyles = useMemo(() => ({ ...DEFAULT_STYLES, ...styles }), [styles]);
560
+ const shapeStyles = useMemo(() => getShapeStyles(shape), [shape]);
561
+ const buttonStyle = useMemo(
562
+ () => computeButtonStyles(size, mergedStyles, shapeStyles),
563
+ [size, mergedStyles, shapeStyles]
564
+ );
565
+ const hoverShadowOffset = useMemo(
566
+ () => computeHoverOffset(mergedStyles.shadowOffset),
567
+ [mergedStyles.shadowOffset]
568
+ );
569
+ const dynamicStyles = useMemo(
570
+ () => `
571
+ @keyframes wave-scroll-left {
572
+ from { transform: translateX(0); }
573
+ to { transform: translateX(-50%); }
574
+ }
575
+ @keyframes wave-scroll-right {
576
+ from { transform: translateX(-50%); }
577
+ to { transform: translateX(0); }
578
+ }
579
+ ${generateDynamicStyles(`#${buttonId}`, hoverShadowOffset, mergedStyles)}
580
+ `,
581
+ [buttonId, hoverShadowOffset, mergedStyles]
582
+ );
583
+ const cursorStyle = useMemo(
584
+ () => disabled ? "not-allowed" : getCursorStyle(cursor),
585
+ [cursor, disabled]
586
+ );
587
+ const iconSize = size * 0.5;
588
+ const iconRenderProps = {
589
+ size: iconSize,
590
+ className: "relative z-20 transition-colors duration-300 pointer-events-none",
591
+ isMaxed,
592
+ fillPercentage
593
+ };
594
+ const renderedIcon = renderIcon === null ? null : renderIcon === void 0 ? /* @__PURE__ */ jsx8(DefaultHeartIcon, { ...iconRenderProps }) : renderIcon(iconRenderProps);
595
+ return /* @__PURE__ */ jsxs("div", { className: "relative inline-block", children: [
596
+ /* @__PURE__ */ jsx8("style", { children: dynamicStyles }),
597
+ /* @__PURE__ */ jsxs(
598
+ "button",
599
+ {
600
+ id: buttonId,
601
+ type: "button",
602
+ onClick: handleClick,
603
+ onContextMenu: handleRightClick,
604
+ disabled,
605
+ "aria-label": ariaLabel,
606
+ "aria-pressed": isPressed,
607
+ "aria-disabled": disabled,
608
+ style: { ...buttonStyle, cursor: cursorStyle },
609
+ className: `relative overflow-hidden z-10 border-solid flex items-center justify-center group transition-all focus:outline-none focus-visible:ring-4 focus-visible:ring-primary-dark focus-visible:ring-offset-2 ${className}`,
610
+ children: [
611
+ /* @__PURE__ */ jsxs(
612
+ "div",
613
+ {
614
+ className: "absolute bottom-0 left-0 right-0 z-0 transition-[height] duration-500 ease-out",
615
+ style: {
616
+ backgroundColor: fillColor,
617
+ height: isMaxed ? "100%" : `${clampedMinFill + fillPercentage / 100 * (MAX_FILL_HEIGHT - clampedMinFill)}%`
618
+ },
619
+ children: [
620
+ /* @__PURE__ */ jsx8(
621
+ "div",
622
+ {
623
+ className: "absolute bottom-full left-0 w-[200%] h-4 flex",
624
+ style: { animation: "wave-scroll-left 3s linear infinite" },
625
+ children: [0, 1].map((i) => /* @__PURE__ */ jsx8(
626
+ "svg",
627
+ {
628
+ className: "w-1/2 h-full fill-current",
629
+ style: { color: waveColor },
630
+ viewBox: "0 0 100 20",
631
+ preserveAspectRatio: "none",
632
+ "aria-hidden": "true",
633
+ children: /* @__PURE__ */ jsx8("path", { d: "M0,10 Q25,0 50,10 T100,10 V20 H0 Z" })
634
+ },
635
+ i
636
+ ))
637
+ }
638
+ ),
639
+ /* @__PURE__ */ jsx8(
640
+ "div",
641
+ {
642
+ className: "absolute bottom-full left-0 w-[200%] h-4 flex",
643
+ style: { animation: "wave-scroll-right 1.5s linear infinite" },
644
+ children: [0, 1].map((i) => /* @__PURE__ */ jsx8(
645
+ "svg",
646
+ {
647
+ className: "w-1/2 h-full fill-current",
648
+ style: { color: fillColor },
649
+ viewBox: "0 0 100 20",
650
+ preserveAspectRatio: "none",
651
+ "aria-hidden": "true",
652
+ children: /* @__PURE__ */ jsx8("path", { d: "M0,10 Q25,5 50,10 T100,10 V20 H0 Z" })
653
+ },
654
+ i
655
+ ))
656
+ }
657
+ )
658
+ ]
659
+ }
660
+ ),
661
+ renderedIcon
662
+ ]
663
+ }
664
+ ),
665
+ showParticles && /* @__PURE__ */ jsx8(
666
+ "div",
667
+ {
668
+ className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none",
669
+ "aria-hidden": "true",
670
+ children: particles.map((p) => /* @__PURE__ */ jsx8(Particle, { ...p }, p.id))
671
+ }
672
+ )
673
+ ] });
674
+ }
675
+
676
+ // src/LikeButton/LikeButton.vanilla.tsx
677
+ import { useId as useId2, useMemo as useMemo2 } from "react";
678
+
679
+ // src/Particle/Particle.vanilla.tsx
680
+ import { jsx as jsx9 } from "react/jsx-runtime";
681
+ function ParticleVanilla({
682
+ angle,
683
+ distance,
684
+ scale,
685
+ color,
686
+ shape,
687
+ speed,
688
+ easing,
689
+ fadeOut
690
+ }) {
691
+ const { transform, opacity } = useParticle({
692
+ angle,
693
+ distance,
694
+ scale,
695
+ speed,
696
+ easing,
697
+ fadeOut
698
+ });
699
+ const ShapeComponent = getParticleShape(shape);
700
+ return /* @__PURE__ */ jsx9(
701
+ "div",
702
+ {
703
+ className: "particle",
704
+ style: {
705
+ color,
706
+ transform,
707
+ opacity: fadeOut ? opacity : 1,
708
+ transitionDuration: `${speed}ms`,
709
+ transitionTimingFunction: easing
710
+ },
711
+ children: /* @__PURE__ */ jsx9(ShapeComponent, { size: 40, color, className: "particle__icon" })
712
+ }
713
+ );
714
+ }
715
+
716
+ // src/LikeButton/LikeButton.vanilla.tsx
717
+ import { jsx as jsx10, jsxs as jsxs2 } from "react/jsx-runtime";
718
+ var MAX_FILL_HEIGHT2 = 85;
719
+ function LikeButtonVanilla({
720
+ size = LIKE_BUTTON_DEFAULTS.size,
721
+ fillColor = LIKE_BUTTON_DEFAULTS.fillColor,
722
+ waveColor = LIKE_BUTTON_DEFAULTS.waveColor,
723
+ className = "",
724
+ showParticles = true,
725
+ renderIcon,
726
+ shape = "circle",
727
+ styles = {},
728
+ cursor = "heart",
729
+ minFillPercent = 0,
730
+ ...hookOptions
731
+ }) {
732
+ const clampedMinFill = Math.max(0, Math.min(MAX_FILL_HEIGHT2, minFillPercent));
733
+ const reactId = useId2();
734
+ const buttonId = `like-button${reactId.replace(/:/g, "-")}`;
735
+ const {
736
+ handleClick,
737
+ handleRightClick,
738
+ disabled,
739
+ ariaLabel,
740
+ isPressed,
741
+ isMaxed,
742
+ fillPercentage,
743
+ particles
744
+ } = useLikeButton({ showParticles, ...hookOptions });
745
+ const mergedStyles = useMemo2(() => ({ ...DEFAULT_STYLES, ...styles }), [styles]);
746
+ const shapeStyles = useMemo2(() => getShapeStyles(shape), [shape]);
747
+ const buttonStyle = useMemo2(
748
+ () => computeButtonStyles(size, mergedStyles, shapeStyles),
749
+ [size, mergedStyles, shapeStyles]
750
+ );
751
+ const hoverShadowOffset = useMemo2(
752
+ () => computeHoverOffset(mergedStyles.shadowOffset),
753
+ [mergedStyles.shadowOffset]
754
+ );
755
+ const dynamicStyles = useMemo2(
756
+ () => generateDynamicStyles(`#${buttonId}`, hoverShadowOffset, mergedStyles),
757
+ [buttonId, hoverShadowOffset, mergedStyles]
758
+ );
759
+ const cursorStyle = useMemo2(
760
+ () => disabled ? "not-allowed" : getCursorStyle(cursor),
761
+ [cursor, disabled]
762
+ );
763
+ const iconSize = size * 0.5;
764
+ const iconRenderProps = {
765
+ size: iconSize,
766
+ className: "like-button__icon",
767
+ isMaxed,
768
+ fillPercentage
769
+ };
770
+ const renderedIcon = renderIcon === null ? null : renderIcon === void 0 ? /* @__PURE__ */ jsx10(DefaultHeartIcon, { ...iconRenderProps }) : renderIcon(iconRenderProps);
771
+ return /* @__PURE__ */ jsxs2("div", { className: "like-button-container", children: [
772
+ /* @__PURE__ */ jsx10("style", { children: dynamicStyles }),
773
+ /* @__PURE__ */ jsxs2(
774
+ "button",
775
+ {
776
+ id: buttonId,
777
+ type: "button",
778
+ onClick: handleClick,
779
+ onContextMenu: handleRightClick,
780
+ disabled,
781
+ "aria-label": ariaLabel,
782
+ "aria-pressed": isPressed,
783
+ "aria-disabled": disabled,
784
+ style: { ...buttonStyle, cursor: cursorStyle },
785
+ className: `like-button ${className}`.trim(),
786
+ children: [
787
+ /* @__PURE__ */ jsxs2(
788
+ "div",
789
+ {
790
+ className: "like-button__fill",
791
+ style: {
792
+ backgroundColor: fillColor,
793
+ height: isMaxed ? "100%" : `${clampedMinFill + fillPercentage / 100 * (MAX_FILL_HEIGHT2 - clampedMinFill)}%`
794
+ },
795
+ children: [
796
+ /* @__PURE__ */ jsx10("div", { className: "like-button__wave like-button__wave--back", children: [0, 1].map((i) => /* @__PURE__ */ jsx10(
797
+ "svg",
798
+ {
799
+ className: "like-button__wave-svg",
800
+ style: { color: waveColor },
801
+ viewBox: "0 0 100 20",
802
+ preserveAspectRatio: "none",
803
+ "aria-hidden": "true",
804
+ children: /* @__PURE__ */ jsx10("path", { d: "M0,10 Q25,0 50,10 T100,10 V20 H0 Z" })
805
+ },
806
+ i
807
+ )) }),
808
+ /* @__PURE__ */ jsx10("div", { className: "like-button__wave like-button__wave--front", children: [0, 1].map((i) => /* @__PURE__ */ jsx10(
809
+ "svg",
810
+ {
811
+ className: "like-button__wave-svg",
812
+ style: { color: fillColor },
813
+ viewBox: "0 0 100 20",
814
+ preserveAspectRatio: "none",
815
+ "aria-hidden": "true",
816
+ children: /* @__PURE__ */ jsx10("path", { d: "M0,10 Q25,5 50,10 T100,10 V20 H0 Z" })
817
+ },
818
+ i
819
+ )) })
820
+ ]
821
+ }
822
+ ),
823
+ renderedIcon
824
+ ]
825
+ }
826
+ ),
827
+ showParticles && /* @__PURE__ */ jsx10("div", { className: "like-button__particles", "aria-hidden": "true", children: particles.map((p) => /* @__PURE__ */ jsx10(ParticleVanilla, { ...p }, p.id)) })
828
+ ] });
829
+ }
830
+
831
+ export {
832
+ DefaultHeartIcon,
833
+ useParticle,
834
+ Particle,
835
+ LIKE_BUTTON_DEFAULTS,
836
+ useLikeButton,
837
+ LikeButton,
838
+ ParticleVanilla,
839
+ LikeButtonVanilla
840
+ };
841
+ //# sourceMappingURL=chunk-WTGBDIZW.js.map