@fmarlats/react-like-button 1.1.4
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.
- package/LICENSE +22 -0
- package/README.md +482 -0
- package/dist/chunk-VLFZGMEX.js +725 -0
- package/dist/chunk-VLFZGMEX.js.map +1 -0
- package/dist/index.cjs +926 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +68 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +209 -0
- package/dist/index.js.map +1 -0
- package/dist/like-button.css +68 -0
- package/dist/styles.css +181 -0
- package/dist/vanilla.cjs +742 -0
- package/dist/vanilla.cjs.map +1 -0
- package/dist/vanilla.d.cts +562 -0
- package/dist/vanilla.d.ts +562 -0
- package/dist/vanilla.js +16 -0
- package/dist/vanilla.js.map +1 -0
- package/package.json +109 -0
|
@@ -0,0 +1,725 @@
|
|
|
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/Particle/presets.ts
|
|
30
|
+
var DEFAULT_PARTICLE_CONFIG = {
|
|
31
|
+
shape: "heart",
|
|
32
|
+
speed: 500,
|
|
33
|
+
distance: { min: 60, max: 100 },
|
|
34
|
+
spread: 360,
|
|
35
|
+
spreadOffset: 0,
|
|
36
|
+
size: { min: 1, max: 1.5 },
|
|
37
|
+
colors: ["#EF4444", "#B9FF14", "#3B82F6"],
|
|
38
|
+
count: 8,
|
|
39
|
+
easing: "cubic-bezier(0.22, 1, 0.36, 1)",
|
|
40
|
+
fadeOut: true
|
|
41
|
+
};
|
|
42
|
+
var PARTICLE_PRESETS = {
|
|
43
|
+
/**
|
|
44
|
+
* Burst - Fast, enthusiastic explosion effect
|
|
45
|
+
* - Wide 360° spread
|
|
46
|
+
* - Fast animation (400ms)
|
|
47
|
+
* - Multiple colors
|
|
48
|
+
* - 12 particles
|
|
49
|
+
* - Perfect for: Likes, favorites, celebrations
|
|
50
|
+
*/
|
|
51
|
+
burst: {
|
|
52
|
+
shape: "heart",
|
|
53
|
+
speed: 400,
|
|
54
|
+
distance: { min: 80, max: 120 },
|
|
55
|
+
spread: 360,
|
|
56
|
+
spreadOffset: 0,
|
|
57
|
+
size: { min: 1, max: 1.5 },
|
|
58
|
+
colors: ["#EF4444", "#F59E0B", "#3B82F6"],
|
|
59
|
+
count: 12,
|
|
60
|
+
easing: "cubic-bezier(0.22, 1, 0.36, 1)",
|
|
61
|
+
fadeOut: true
|
|
62
|
+
},
|
|
63
|
+
/**
|
|
64
|
+
* Fountain - Upward spray effect
|
|
65
|
+
* - 120° upward spread
|
|
66
|
+
* - Medium animation (600ms)
|
|
67
|
+
* - Cool colors (blue, purple, pink)
|
|
68
|
+
* - 10 particles
|
|
69
|
+
* - Perfect for: Achievements, upgrades, success
|
|
70
|
+
*/
|
|
71
|
+
fountain: {
|
|
72
|
+
shape: "circle",
|
|
73
|
+
speed: 600,
|
|
74
|
+
distance: { min: 60, max: 100 },
|
|
75
|
+
spread: 120,
|
|
76
|
+
spreadOffset: -90,
|
|
77
|
+
// Upward
|
|
78
|
+
size: { min: 0.8, max: 1.2 },
|
|
79
|
+
colors: ["#3B82F6", "#8B5CF6", "#EC4899"],
|
|
80
|
+
count: 10,
|
|
81
|
+
easing: "cubic-bezier(0.22, 1, 0.36, 1)",
|
|
82
|
+
fadeOut: true
|
|
83
|
+
},
|
|
84
|
+
/**
|
|
85
|
+
* Confetti - Colorful celebration effect
|
|
86
|
+
* - Full 360° spread
|
|
87
|
+
* - Slow animation (800ms)
|
|
88
|
+
* - Rainbow colors
|
|
89
|
+
* - 15 particles
|
|
90
|
+
* - Perfect for: Milestones, victories, special events
|
|
91
|
+
*/
|
|
92
|
+
confetti: {
|
|
93
|
+
shape: "square",
|
|
94
|
+
speed: 800,
|
|
95
|
+
distance: { min: 70, max: 110 },
|
|
96
|
+
spread: 360,
|
|
97
|
+
spreadOffset: 0,
|
|
98
|
+
size: { min: 0.6, max: 1.4 },
|
|
99
|
+
colors: ["#EF4444", "#F59E0B", "#10B981", "#3B82F6", "#8B5CF6", "#EC4899"],
|
|
100
|
+
count: 15,
|
|
101
|
+
easing: "ease-out",
|
|
102
|
+
fadeOut: true
|
|
103
|
+
},
|
|
104
|
+
/**
|
|
105
|
+
* Gentle - Subtle, calm effect
|
|
106
|
+
* - 180° upward spread
|
|
107
|
+
* - Slow animation (700ms)
|
|
108
|
+
* - Soft red tones
|
|
109
|
+
* - 6 particles
|
|
110
|
+
* - Perfect for: Subtle interactions, quiet appreciation
|
|
111
|
+
*/
|
|
112
|
+
gentle: {
|
|
113
|
+
shape: "heart",
|
|
114
|
+
speed: 700,
|
|
115
|
+
distance: { min: 40, max: 60 },
|
|
116
|
+
spread: 180,
|
|
117
|
+
spreadOffset: -90,
|
|
118
|
+
size: { min: 0.8, max: 1 },
|
|
119
|
+
colors: ["#EF4444", "#F87171"],
|
|
120
|
+
count: 6,
|
|
121
|
+
easing: "ease-in-out",
|
|
122
|
+
fadeOut: true
|
|
123
|
+
},
|
|
124
|
+
/**
|
|
125
|
+
* Fireworks - Explosive sparkle effect
|
|
126
|
+
* - Full 360° spread
|
|
127
|
+
* - Medium-fast animation (500ms)
|
|
128
|
+
* - Warm sparkle colors
|
|
129
|
+
* - 16 particles
|
|
130
|
+
* - Large size range
|
|
131
|
+
* - Perfect for: Big celebrations, major achievements
|
|
132
|
+
*/
|
|
133
|
+
fireworks: {
|
|
134
|
+
shape: "sparkle",
|
|
135
|
+
speed: 500,
|
|
136
|
+
distance: { min: 100, max: 150 },
|
|
137
|
+
spread: 360,
|
|
138
|
+
spreadOffset: 0,
|
|
139
|
+
size: { min: 1.2, max: 2 },
|
|
140
|
+
colors: ["#FBBF24", "#F59E0B", "#EF4444", "#EC4899"],
|
|
141
|
+
count: 16,
|
|
142
|
+
easing: "cubic-bezier(0.22, 1, 0.36, 1)",
|
|
143
|
+
fadeOut: true
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
function getParticlePreset(preset) {
|
|
147
|
+
return PARTICLE_PRESETS[preset];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/LikeButton/useLikeButton.ts
|
|
151
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
152
|
+
|
|
153
|
+
// src/Particle/utils.ts
|
|
154
|
+
function normalizeRange(value) {
|
|
155
|
+
return typeof value === "number" ? { min: value, max: value } : value;
|
|
156
|
+
}
|
|
157
|
+
function randomInRange(range) {
|
|
158
|
+
return range.min + Math.random() * (range.max - range.min);
|
|
159
|
+
}
|
|
160
|
+
function normalizeAngle(angle) {
|
|
161
|
+
const normalized = angle % 360;
|
|
162
|
+
const result = normalized < 0 ? normalized + 360 : normalized;
|
|
163
|
+
return result === 0 ? 0 : result;
|
|
164
|
+
}
|
|
165
|
+
function randomAngle(spread, offset) {
|
|
166
|
+
const angle = offset + Math.random() * spread;
|
|
167
|
+
return normalizeAngle(angle);
|
|
168
|
+
}
|
|
169
|
+
function mergeParticleConfig(base, override) {
|
|
170
|
+
if (!override) {
|
|
171
|
+
return base;
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
...base,
|
|
175
|
+
...override
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function resolveParticleConfig(preset, config) {
|
|
179
|
+
let resolved = { ...DEFAULT_PARTICLE_CONFIG };
|
|
180
|
+
if (preset) {
|
|
181
|
+
resolved = mergeParticleConfig(resolved, getParticlePreset(preset));
|
|
182
|
+
}
|
|
183
|
+
if (config) {
|
|
184
|
+
resolved = mergeParticleConfig(resolved, config);
|
|
185
|
+
}
|
|
186
|
+
return resolved;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/LikeButton/utils.ts
|
|
190
|
+
var MAX_FILL_HEIGHT = 85;
|
|
191
|
+
var ICON_SIZE_RATIO = 0.5;
|
|
192
|
+
var MIN_HOVER_OFFSET = 2;
|
|
193
|
+
var PARTICLE_CLEANUP_BUFFER_MS = 100;
|
|
194
|
+
var WAVE_BACK_DURATION_S = 3;
|
|
195
|
+
var WAVE_FRONT_DURATION_S = 1.5;
|
|
196
|
+
var DEFAULT_STYLES = {
|
|
197
|
+
borderWidth: 4,
|
|
198
|
+
borderColor: "#111827",
|
|
199
|
+
shadowOffset: 8,
|
|
200
|
+
shadowColor: "#111827",
|
|
201
|
+
backgroundColor: "white"
|
|
202
|
+
};
|
|
203
|
+
function getShapeStyles(shape = "circle") {
|
|
204
|
+
if (typeof shape === "string") {
|
|
205
|
+
switch (shape) {
|
|
206
|
+
case "circle":
|
|
207
|
+
return { borderRadius: "9999px" };
|
|
208
|
+
case "rounded":
|
|
209
|
+
return { borderRadius: "1rem" };
|
|
210
|
+
case "square":
|
|
211
|
+
return { borderRadius: "0" };
|
|
212
|
+
default:
|
|
213
|
+
return { borderRadius: "9999px" };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const styles = {};
|
|
217
|
+
if (shape.clipPath) {
|
|
218
|
+
styles.clipPath = shape.clipPath;
|
|
219
|
+
styles.borderRadius = shape.borderRadius ?? "0";
|
|
220
|
+
} else if (shape.borderRadius) {
|
|
221
|
+
styles.borderRadius = shape.borderRadius;
|
|
222
|
+
}
|
|
223
|
+
return styles;
|
|
224
|
+
}
|
|
225
|
+
function computeHoverOffset(shadowOffset) {
|
|
226
|
+
return Math.max(shadowOffset / 2, MIN_HOVER_OFFSET);
|
|
227
|
+
}
|
|
228
|
+
function computeButtonStyles(size, mergedStyles, shapeStyles) {
|
|
229
|
+
return {
|
|
230
|
+
width: size,
|
|
231
|
+
height: size,
|
|
232
|
+
borderWidth: mergedStyles.borderWidth,
|
|
233
|
+
borderColor: mergedStyles.borderColor,
|
|
234
|
+
backgroundColor: mergedStyles.backgroundColor,
|
|
235
|
+
boxShadow: `${mergedStyles.shadowOffset}px ${mergedStyles.shadowOffset}px 0px ${mergedStyles.shadowColor}`,
|
|
236
|
+
...shapeStyles
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function computeHoverActiveVars(hoverShadowOffset, mergedStyles) {
|
|
240
|
+
const translateOnHover = mergedStyles.shadowOffset - hoverShadowOffset;
|
|
241
|
+
return {
|
|
242
|
+
"--shadow-offset": `${mergedStyles.shadowOffset}px`,
|
|
243
|
+
"--shadow-color": mergedStyles.shadowColor,
|
|
244
|
+
"--hover-shadow-offset": `${hoverShadowOffset}px`,
|
|
245
|
+
"--translate-hover": `${translateOnHover}px`
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
var CURSOR_SVGS = {
|
|
249
|
+
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>`,
|
|
250
|
+
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>`,
|
|
251
|
+
"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>`
|
|
252
|
+
};
|
|
253
|
+
var DEFAULT_HOTSPOT = 16;
|
|
254
|
+
function getCursorStyle(cursor = "heart") {
|
|
255
|
+
if (typeof cursor === "string") {
|
|
256
|
+
switch (cursor) {
|
|
257
|
+
case "pointer":
|
|
258
|
+
return "pointer";
|
|
259
|
+
case "none":
|
|
260
|
+
return "none";
|
|
261
|
+
case "heart":
|
|
262
|
+
case "star":
|
|
263
|
+
case "thumbs-up":
|
|
264
|
+
return `url("${CURSOR_SVGS[cursor]}") ${DEFAULT_HOTSPOT} ${DEFAULT_HOTSPOT}, pointer`;
|
|
265
|
+
default:
|
|
266
|
+
return "pointer";
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const {
|
|
270
|
+
url,
|
|
271
|
+
hotspotX = DEFAULT_HOTSPOT,
|
|
272
|
+
hotspotY = DEFAULT_HOTSPOT,
|
|
273
|
+
fallback = "pointer"
|
|
274
|
+
} = cursor;
|
|
275
|
+
return `url("${url}") ${hotspotX} ${hotspotY}, ${fallback}`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/LikeButton/useLikeButton.ts
|
|
279
|
+
var LIKE_BUTTON_DEFAULTS = {
|
|
280
|
+
maxClicks: 1,
|
|
281
|
+
size: 96,
|
|
282
|
+
fillColor: "#EF4444",
|
|
283
|
+
waveColor: "#B91C1C"
|
|
284
|
+
};
|
|
285
|
+
function useLikeButton(options = {}) {
|
|
286
|
+
const {
|
|
287
|
+
clicks: externalClicks,
|
|
288
|
+
defaultClicks = 0,
|
|
289
|
+
maxClicks = LIKE_BUTTON_DEFAULTS.maxClicks,
|
|
290
|
+
onClick,
|
|
291
|
+
onChange,
|
|
292
|
+
onRightClick,
|
|
293
|
+
disabled: externalDisabled,
|
|
294
|
+
showParticles = true,
|
|
295
|
+
particlePreset,
|
|
296
|
+
particleConfig,
|
|
297
|
+
ariaLabel: customAriaLabel
|
|
298
|
+
} = options;
|
|
299
|
+
if (externalClicks !== void 0 && options.defaultClicks !== void 0) {
|
|
300
|
+
console.warn(
|
|
301
|
+
"LikeButton: `defaultClicks` is ignored when `clicks` is provided (controlled mode)."
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
const [internalClicks, setInternalClicks] = useState(defaultClicks);
|
|
305
|
+
const [particles, setParticles] = useState([]);
|
|
306
|
+
const timeoutRefs = useRef(/* @__PURE__ */ new Set());
|
|
307
|
+
useEffect(() => {
|
|
308
|
+
return () => {
|
|
309
|
+
for (const timeoutId of timeoutRefs.current) {
|
|
310
|
+
clearTimeout(timeoutId);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
}, []);
|
|
314
|
+
const clicks = externalClicks ?? internalClicks;
|
|
315
|
+
const isMaxed = clicks >= maxClicks;
|
|
316
|
+
const disabled = externalDisabled ?? isMaxed;
|
|
317
|
+
const spawnParticles = useCallback(() => {
|
|
318
|
+
if (!showParticles) return;
|
|
319
|
+
const config = resolveParticleConfig(particlePreset, particleConfig);
|
|
320
|
+
const distanceRange = normalizeRange(config.distance);
|
|
321
|
+
const sizeRange = normalizeRange(config.size);
|
|
322
|
+
const id = Date.now();
|
|
323
|
+
const newParticles = Array.from({ length: config.count }).map((_, i) => ({
|
|
324
|
+
id: `${id}-${i}`,
|
|
325
|
+
angle: randomAngle(config.spread, config.spreadOffset),
|
|
326
|
+
distance: randomInRange(distanceRange),
|
|
327
|
+
scale: randomInRange(sizeRange),
|
|
328
|
+
color: config.colors[Math.floor(Math.random() * config.colors.length)],
|
|
329
|
+
shape: config.shape,
|
|
330
|
+
speed: config.speed,
|
|
331
|
+
easing: config.easing,
|
|
332
|
+
fadeOut: config.fadeOut
|
|
333
|
+
}));
|
|
334
|
+
setParticles((prev) => [...prev, ...newParticles]);
|
|
335
|
+
const cleanupDelay = config.speed + PARTICLE_CLEANUP_BUFFER_MS;
|
|
336
|
+
const idsToRemove = new Set(newParticles.map((p) => p.id));
|
|
337
|
+
const timeoutId = setTimeout(() => {
|
|
338
|
+
setParticles((prev) => prev.filter((p) => !idsToRemove.has(p.id)));
|
|
339
|
+
timeoutRefs.current.delete(timeoutId);
|
|
340
|
+
}, cleanupDelay);
|
|
341
|
+
timeoutRefs.current.add(timeoutId);
|
|
342
|
+
}, [showParticles, particlePreset, particleConfig]);
|
|
343
|
+
const handleClick = useCallback(
|
|
344
|
+
(e) => {
|
|
345
|
+
if (disabled) return;
|
|
346
|
+
const newClicks = clicks + 1;
|
|
347
|
+
if (externalClicks === void 0) {
|
|
348
|
+
setInternalClicks(newClicks);
|
|
349
|
+
}
|
|
350
|
+
spawnParticles();
|
|
351
|
+
onChange?.(newClicks);
|
|
352
|
+
onClick?.(newClicks, e);
|
|
353
|
+
},
|
|
354
|
+
[disabled, clicks, externalClicks, spawnParticles, onChange, onClick]
|
|
355
|
+
);
|
|
356
|
+
const handleRightClick = useCallback(
|
|
357
|
+
(e) => {
|
|
358
|
+
e.preventDefault();
|
|
359
|
+
if (disabled) return;
|
|
360
|
+
onRightClick?.(clicks, e);
|
|
361
|
+
},
|
|
362
|
+
[disabled, clicks, onRightClick]
|
|
363
|
+
);
|
|
364
|
+
const handleKeyDown = useCallback(
|
|
365
|
+
(e) => {
|
|
366
|
+
if (e.shiftKey && e.key === "Enter") {
|
|
367
|
+
e.preventDefault();
|
|
368
|
+
if (disabled) return;
|
|
369
|
+
onRightClick?.(clicks, e);
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
[disabled, clicks, onRightClick]
|
|
373
|
+
);
|
|
374
|
+
const fillPercentage = clicks / maxClicks * 100;
|
|
375
|
+
const defaultAriaLabel = isMaxed ? "Thank you for your likes!" : `Like this content. ${maxClicks - clicks} clicks remaining`;
|
|
376
|
+
const ariaLabelState = {
|
|
377
|
+
isMaxed,
|
|
378
|
+
remaining: maxClicks - clicks,
|
|
379
|
+
clicks,
|
|
380
|
+
maxClicks
|
|
381
|
+
};
|
|
382
|
+
const computedAriaLabel = customAriaLabel === void 0 ? defaultAriaLabel : typeof customAriaLabel === "function" ? customAriaLabel(ariaLabelState) : customAriaLabel;
|
|
383
|
+
return {
|
|
384
|
+
clicks,
|
|
385
|
+
isMaxed,
|
|
386
|
+
disabled,
|
|
387
|
+
fillPercentage,
|
|
388
|
+
particles,
|
|
389
|
+
handleClick,
|
|
390
|
+
handleRightClick,
|
|
391
|
+
handleKeyDown,
|
|
392
|
+
ariaLabel: computedAriaLabel,
|
|
393
|
+
isPressed: clicks > 0,
|
|
394
|
+
hasRightClickAction: onRightClick !== void 0
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// src/LikeButton/LikeButton.vanilla.tsx
|
|
399
|
+
import { forwardRef, useId, useMemo } from "react";
|
|
400
|
+
|
|
401
|
+
// src/Particle/shapes/CircleShape.tsx
|
|
402
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
403
|
+
function CircleShape({ size, color, className = "" }) {
|
|
404
|
+
return /* @__PURE__ */ jsx2(
|
|
405
|
+
"svg",
|
|
406
|
+
{
|
|
407
|
+
width: size,
|
|
408
|
+
height: size,
|
|
409
|
+
viewBox: "0 0 24 24",
|
|
410
|
+
className: `fill-current ${className}`,
|
|
411
|
+
style: { color },
|
|
412
|
+
"aria-hidden": "true",
|
|
413
|
+
children: /* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "10" })
|
|
414
|
+
}
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// src/Particle/shapes/HeartShape.tsx
|
|
419
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
420
|
+
function HeartShape({ size, color, className = "" }) {
|
|
421
|
+
return /* @__PURE__ */ jsx3(
|
|
422
|
+
"svg",
|
|
423
|
+
{
|
|
424
|
+
width: size,
|
|
425
|
+
height: size,
|
|
426
|
+
viewBox: "0 0 24 24",
|
|
427
|
+
className: `fill-current ${className}`,
|
|
428
|
+
style: { color },
|
|
429
|
+
"aria-hidden": "true",
|
|
430
|
+
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" })
|
|
431
|
+
}
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// src/Particle/shapes/SparkleShape.tsx
|
|
436
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
437
|
+
function SparkleShape({ size, color, className = "" }) {
|
|
438
|
+
return /* @__PURE__ */ jsx4(
|
|
439
|
+
"svg",
|
|
440
|
+
{
|
|
441
|
+
width: size,
|
|
442
|
+
height: size,
|
|
443
|
+
viewBox: "0 0 24 24",
|
|
444
|
+
className: `fill-current ${className}`,
|
|
445
|
+
style: { color },
|
|
446
|
+
"aria-hidden": "true",
|
|
447
|
+
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" })
|
|
448
|
+
}
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/Particle/shapes/SquareShape.tsx
|
|
453
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
454
|
+
function SquareShape({ size, color, className = "" }) {
|
|
455
|
+
return /* @__PURE__ */ jsx5(
|
|
456
|
+
"svg",
|
|
457
|
+
{
|
|
458
|
+
width: size,
|
|
459
|
+
height: size,
|
|
460
|
+
viewBox: "0 0 24 24",
|
|
461
|
+
className: `fill-current ${className}`,
|
|
462
|
+
style: { color },
|
|
463
|
+
"aria-hidden": "true",
|
|
464
|
+
children: /* @__PURE__ */ jsx5("rect", { x: "3", y: "3", width: "18", height: "18", rx: "3" })
|
|
465
|
+
}
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/Particle/shapes/StarShape.tsx
|
|
470
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
471
|
+
function StarShape({ size, color, className = "" }) {
|
|
472
|
+
return /* @__PURE__ */ jsx6(
|
|
473
|
+
"svg",
|
|
474
|
+
{
|
|
475
|
+
width: size,
|
|
476
|
+
height: size,
|
|
477
|
+
viewBox: "0 0 24 24",
|
|
478
|
+
className: `fill-current ${className}`,
|
|
479
|
+
style: { color },
|
|
480
|
+
"aria-hidden": "true",
|
|
481
|
+
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" })
|
|
482
|
+
}
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// src/Particle/shapes/utils.ts
|
|
487
|
+
function getParticleShape(shape) {
|
|
488
|
+
if (typeof shape === "object" && "render" in shape) {
|
|
489
|
+
return ({ size, color, className }) => shape.render({ size, color, className });
|
|
490
|
+
}
|
|
491
|
+
switch (shape) {
|
|
492
|
+
case "heart":
|
|
493
|
+
return HeartShape;
|
|
494
|
+
case "star":
|
|
495
|
+
return StarShape;
|
|
496
|
+
case "circle":
|
|
497
|
+
return CircleShape;
|
|
498
|
+
case "square":
|
|
499
|
+
return SquareShape;
|
|
500
|
+
case "sparkle":
|
|
501
|
+
return SparkleShape;
|
|
502
|
+
default:
|
|
503
|
+
return HeartShape;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// src/Particle/useParticle.ts
|
|
508
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
509
|
+
function useParticle({
|
|
510
|
+
angle,
|
|
511
|
+
distance,
|
|
512
|
+
scale,
|
|
513
|
+
speed,
|
|
514
|
+
easing,
|
|
515
|
+
fadeOut
|
|
516
|
+
}) {
|
|
517
|
+
const [isAnimating, setIsAnimating] = useState2(false);
|
|
518
|
+
useEffect2(() => {
|
|
519
|
+
let cancelled = false;
|
|
520
|
+
let raf2;
|
|
521
|
+
const raf1 = requestAnimationFrame(() => {
|
|
522
|
+
raf2 = requestAnimationFrame(() => {
|
|
523
|
+
if (!cancelled) {
|
|
524
|
+
setIsAnimating(true);
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
return () => {
|
|
529
|
+
cancelled = true;
|
|
530
|
+
cancelAnimationFrame(raf1);
|
|
531
|
+
if (raf2 !== void 0) cancelAnimationFrame(raf2);
|
|
532
|
+
};
|
|
533
|
+
}, []);
|
|
534
|
+
const x = Math.cos(angle * Math.PI / 180) * distance;
|
|
535
|
+
const y = Math.sin(angle * Math.PI / 180) * distance;
|
|
536
|
+
return {
|
|
537
|
+
isAnimating,
|
|
538
|
+
x,
|
|
539
|
+
y,
|
|
540
|
+
transform: isAnimating ? `translate(${x}px, ${y}px) scale(${scale})` : "translate(0, 0) scale(1)",
|
|
541
|
+
opacity: isAnimating ? 0 : 1,
|
|
542
|
+
speed,
|
|
543
|
+
easing,
|
|
544
|
+
fadeOut
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/Particle/Particle.vanilla.tsx
|
|
549
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
550
|
+
function ParticleVanilla({
|
|
551
|
+
angle,
|
|
552
|
+
distance,
|
|
553
|
+
scale,
|
|
554
|
+
color,
|
|
555
|
+
shape,
|
|
556
|
+
speed,
|
|
557
|
+
easing,
|
|
558
|
+
fadeOut
|
|
559
|
+
}) {
|
|
560
|
+
const { transform, opacity } = useParticle({
|
|
561
|
+
angle,
|
|
562
|
+
distance,
|
|
563
|
+
scale,
|
|
564
|
+
speed,
|
|
565
|
+
easing,
|
|
566
|
+
fadeOut
|
|
567
|
+
});
|
|
568
|
+
const ShapeComponent = getParticleShape(shape);
|
|
569
|
+
return /* @__PURE__ */ jsx7(
|
|
570
|
+
"div",
|
|
571
|
+
{
|
|
572
|
+
className: "particle",
|
|
573
|
+
style: {
|
|
574
|
+
color,
|
|
575
|
+
transform,
|
|
576
|
+
opacity: fadeOut ? opacity : 1,
|
|
577
|
+
transitionDuration: `${speed}ms`,
|
|
578
|
+
transitionTimingFunction: easing
|
|
579
|
+
},
|
|
580
|
+
children: /* @__PURE__ */ jsx7(ShapeComponent, { size: 40, color, className: "particle__icon" })
|
|
581
|
+
}
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// src/LikeButton/LikeButton.vanilla.tsx
|
|
586
|
+
import { Fragment, jsx as jsx8, jsxs } from "react/jsx-runtime";
|
|
587
|
+
var LikeButtonVanilla = forwardRef(
|
|
588
|
+
function LikeButtonVanilla2({
|
|
589
|
+
size = LIKE_BUTTON_DEFAULTS.size,
|
|
590
|
+
fillColor = LIKE_BUTTON_DEFAULTS.fillColor,
|
|
591
|
+
waveColor = LIKE_BUTTON_DEFAULTS.waveColor,
|
|
592
|
+
className = "",
|
|
593
|
+
showParticles = true,
|
|
594
|
+
showWaves = true,
|
|
595
|
+
renderIcon,
|
|
596
|
+
shape = "circle",
|
|
597
|
+
styles = {},
|
|
598
|
+
cursor = "heart",
|
|
599
|
+
minFillPercent = 0,
|
|
600
|
+
...hookOptions
|
|
601
|
+
}, ref) {
|
|
602
|
+
const clampedMinFill = Math.max(0, Math.min(MAX_FILL_HEIGHT, minFillPercent));
|
|
603
|
+
const reactId = useId();
|
|
604
|
+
const buttonId = `like-button${reactId.replace(/:/g, "-")}`;
|
|
605
|
+
const {
|
|
606
|
+
handleClick,
|
|
607
|
+
handleRightClick,
|
|
608
|
+
handleKeyDown,
|
|
609
|
+
disabled,
|
|
610
|
+
ariaLabel,
|
|
611
|
+
isPressed,
|
|
612
|
+
isMaxed,
|
|
613
|
+
fillPercentage,
|
|
614
|
+
particles,
|
|
615
|
+
hasRightClickAction
|
|
616
|
+
} = useLikeButton({ showParticles, ...hookOptions });
|
|
617
|
+
const mergedStyles = useMemo(() => ({ ...DEFAULT_STYLES, ...styles }), [styles]);
|
|
618
|
+
const shapeStyles = useMemo(() => getShapeStyles(shape), [shape]);
|
|
619
|
+
const buttonStyle = useMemo(
|
|
620
|
+
() => computeButtonStyles(size, mergedStyles, shapeStyles),
|
|
621
|
+
[size, mergedStyles, shapeStyles]
|
|
622
|
+
);
|
|
623
|
+
const hoverShadowOffset = useMemo(
|
|
624
|
+
() => computeHoverOffset(mergedStyles.shadowOffset),
|
|
625
|
+
[mergedStyles.shadowOffset]
|
|
626
|
+
);
|
|
627
|
+
const hoverActiveVars = useMemo(
|
|
628
|
+
() => computeHoverActiveVars(hoverShadowOffset, mergedStyles),
|
|
629
|
+
[hoverShadowOffset, mergedStyles]
|
|
630
|
+
);
|
|
631
|
+
const cursorStyle = useMemo(
|
|
632
|
+
() => disabled ? "not-allowed" : getCursorStyle(cursor),
|
|
633
|
+
[cursor, disabled]
|
|
634
|
+
);
|
|
635
|
+
const iconSize = size * ICON_SIZE_RATIO;
|
|
636
|
+
const iconRenderProps = {
|
|
637
|
+
size: iconSize,
|
|
638
|
+
className: "like-button__icon",
|
|
639
|
+
isMaxed,
|
|
640
|
+
fillPercentage
|
|
641
|
+
};
|
|
642
|
+
const renderedIcon = renderIcon === null ? null : renderIcon === void 0 ? /* @__PURE__ */ jsx8(DefaultHeartIcon, { ...iconRenderProps }) : renderIcon(iconRenderProps);
|
|
643
|
+
return /* @__PURE__ */ jsxs("div", { className: "like-button-container", children: [
|
|
644
|
+
/* @__PURE__ */ jsxs(
|
|
645
|
+
"button",
|
|
646
|
+
{
|
|
647
|
+
ref,
|
|
648
|
+
id: buttonId,
|
|
649
|
+
type: "button",
|
|
650
|
+
onClick: handleClick,
|
|
651
|
+
onContextMenu: handleRightClick,
|
|
652
|
+
onKeyDown: handleKeyDown,
|
|
653
|
+
disabled,
|
|
654
|
+
"aria-label": ariaLabel,
|
|
655
|
+
"aria-pressed": isPressed,
|
|
656
|
+
"aria-disabled": disabled,
|
|
657
|
+
"aria-keyshortcuts": hasRightClickAction ? "Shift+Enter" : void 0,
|
|
658
|
+
style: { ...buttonStyle, ...hoverActiveVars, cursor: cursorStyle },
|
|
659
|
+
className: `like-button ${className}`.trim(),
|
|
660
|
+
children: [
|
|
661
|
+
/* @__PURE__ */ jsx8(
|
|
662
|
+
"div",
|
|
663
|
+
{
|
|
664
|
+
className: "like-button__fill",
|
|
665
|
+
style: {
|
|
666
|
+
backgroundColor: fillColor,
|
|
667
|
+
height: isMaxed ? "100%" : `${clampedMinFill + fillPercentage / 100 * (MAX_FILL_HEIGHT - clampedMinFill)}%`
|
|
668
|
+
},
|
|
669
|
+
children: showWaves && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
670
|
+
/* @__PURE__ */ jsx8("div", { className: "like-button__wave like-button__wave--back", children: [0, 1].map((i) => /* @__PURE__ */ jsx8(
|
|
671
|
+
"svg",
|
|
672
|
+
{
|
|
673
|
+
className: "like-button__wave-svg",
|
|
674
|
+
style: { color: waveColor },
|
|
675
|
+
viewBox: "0 0 100 20",
|
|
676
|
+
preserveAspectRatio: "none",
|
|
677
|
+
"aria-hidden": "true",
|
|
678
|
+
children: /* @__PURE__ */ jsx8("path", { d: "M0,10 Q25,0 50,10 T100,10 V20 H0 Z" })
|
|
679
|
+
},
|
|
680
|
+
i
|
|
681
|
+
)) }),
|
|
682
|
+
/* @__PURE__ */ jsx8("div", { className: "like-button__wave like-button__wave--front", children: [0, 1].map((i) => /* @__PURE__ */ jsx8(
|
|
683
|
+
"svg",
|
|
684
|
+
{
|
|
685
|
+
className: "like-button__wave-svg",
|
|
686
|
+
style: { color: fillColor },
|
|
687
|
+
viewBox: "0 0 100 20",
|
|
688
|
+
preserveAspectRatio: "none",
|
|
689
|
+
"aria-hidden": "true",
|
|
690
|
+
children: /* @__PURE__ */ jsx8("path", { d: "M0,10 Q25,5 50,10 T100,10 V20 H0 Z" })
|
|
691
|
+
},
|
|
692
|
+
i
|
|
693
|
+
)) })
|
|
694
|
+
] })
|
|
695
|
+
}
|
|
696
|
+
),
|
|
697
|
+
renderedIcon
|
|
698
|
+
]
|
|
699
|
+
}
|
|
700
|
+
),
|
|
701
|
+
showParticles && /* @__PURE__ */ jsx8("div", { className: "like-button__particles", "aria-hidden": "true", children: particles.map((p) => /* @__PURE__ */ jsx8(ParticleVanilla, { ...p }, p.id)) })
|
|
702
|
+
] });
|
|
703
|
+
}
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
export {
|
|
707
|
+
DefaultHeartIcon,
|
|
708
|
+
getParticleShape,
|
|
709
|
+
useParticle,
|
|
710
|
+
PARTICLE_PRESETS,
|
|
711
|
+
MAX_FILL_HEIGHT,
|
|
712
|
+
ICON_SIZE_RATIO,
|
|
713
|
+
WAVE_BACK_DURATION_S,
|
|
714
|
+
WAVE_FRONT_DURATION_S,
|
|
715
|
+
DEFAULT_STYLES,
|
|
716
|
+
getShapeStyles,
|
|
717
|
+
computeHoverOffset,
|
|
718
|
+
computeButtonStyles,
|
|
719
|
+
computeHoverActiveVars,
|
|
720
|
+
getCursorStyle,
|
|
721
|
+
LIKE_BUTTON_DEFAULTS,
|
|
722
|
+
useLikeButton,
|
|
723
|
+
LikeButtonVanilla
|
|
724
|
+
};
|
|
725
|
+
//# sourceMappingURL=chunk-VLFZGMEX.js.map
|