version-pill-react 1.0.0 → 1.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.
package/dist/index.mjs CHANGED
@@ -4,7 +4,34 @@
4
4
  import { useState, useEffect, useCallback } from "react";
5
5
  import clsx from "clsx";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
- var DEFAULT_BASE_URL = "https://versionpill.com";
7
+ var DEFAULT_BASE_URL = "https://www.versionpill.com";
8
+ var ACCENT_COLORS = {
9
+ green: {
10
+ light: { bg: "rgba(34,197,94,0.1)", border: "rgba(34,197,94,0.25)", text: "#16a34a", dot: "#22c55e" },
11
+ dark: { bg: "rgba(34,197,94,0.15)", border: "rgba(34,197,94,0.3)", text: "#4ade80", dot: "#22c55e" }
12
+ },
13
+ blue: {
14
+ light: { bg: "rgba(59,130,246,0.1)", border: "rgba(59,130,246,0.25)", text: "#2563eb", dot: "#3b82f6" },
15
+ dark: { bg: "rgba(59,130,246,0.15)", border: "rgba(59,130,246,0.3)", text: "#60a5fa", dot: "#3b82f6" }
16
+ },
17
+ purple: {
18
+ light: { bg: "rgba(147,51,234,0.1)", border: "rgba(147,51,234,0.25)", text: "#7c3aed", dot: "#a855f7" },
19
+ dark: { bg: "rgba(147,51,234,0.15)", border: "rgba(147,51,234,0.3)", text: "#c084fc", dot: "#a855f7" }
20
+ },
21
+ orange: {
22
+ light: { bg: "rgba(249,115,22,0.1)", border: "rgba(249,115,22,0.25)", text: "#ea580c", dot: "#f97316" },
23
+ dark: { bg: "rgba(249,115,22,0.15)", border: "rgba(249,115,22,0.3)", text: "#fb923c", dot: "#f97316" }
24
+ },
25
+ neutral: {
26
+ light: { bg: "rgba(0,0,0,0.05)", border: "rgba(0,0,0,0.1)", text: "#52525b", dot: "#71717a" },
27
+ dark: { bg: "rgba(255,255,255,0.08)", border: "rgba(255,255,255,0.12)", text: "#a1a1aa", dot: "#71717a" }
28
+ }
29
+ };
30
+ var SIZE_CONFIG = {
31
+ sm: { height: 22, padding: "0 8px", gap: 4, font: 10, dotSize: 5 },
32
+ md: { height: 26, padding: "0 10px", gap: 5, font: 11, dotSize: 6 },
33
+ lg: { height: 30, padding: "0 12px", gap: 6, font: 12, dotSize: 7 }
34
+ };
8
35
  var styles = {
9
36
  pill: `
10
37
  inline-flex items-center gap-1.5 px-2.5 py-1
@@ -61,10 +88,14 @@ function VersionPill({
61
88
  position = "inline",
62
89
  className,
63
90
  theme = "auto",
91
+ style = "dot",
92
+ size = "md",
64
93
  showBranding = true,
65
- accentColor,
66
- onNewVersion
94
+ accent = "green",
95
+ onNewVersion,
96
+ onClick
67
97
  }) {
98
+ const [project, setProject] = useState(null);
68
99
  const [versions, setVersions] = useState([]);
69
100
  const [loading, setLoading] = useState(true);
70
101
  const [error, setError] = useState(null);
@@ -72,34 +103,60 @@ function VersionPill({
72
103
  const [hasNewVersion, setHasNewVersion] = useState(false);
73
104
  const resolvedTheme = useTheme(theme);
74
105
  const isLight = resolvedTheme === "light";
75
- const fetchChangelog = useCallback(async () => {
106
+ const colors = ACCENT_COLORS[accent][isLight ? "light" : "dark"];
107
+ const sizeConfig = SIZE_CONFIG[size];
108
+ const fetchVersion = useCallback(async () => {
76
109
  try {
77
- const response = await fetch(`${baseUrl}/api/changelog/${projectId}?limit=10`);
78
- if (!response.ok) throw new Error("Failed to fetch changelog");
110
+ const response = await fetch(`${baseUrl}/api/badge/${projectId}`);
111
+ if (!response.ok) throw new Error("Failed to fetch version");
79
112
  const data = await response.json();
80
- setVersions(data);
81
- const storedVersion = localStorage.getItem(`vp_${projectId}_seen`);
82
- if (data.length > 0 && data[0].version !== storedVersion) {
83
- setHasNewVersion(true);
84
- onNewVersion?.(data[0]);
113
+ if (data.version) {
114
+ setProject({ name: data.name, slug: data.slug, currentVersion: data.version });
115
+ const storedVersion = localStorage.getItem(`vp_${projectId}_seen`);
116
+ if (data.version !== storedVersion) {
117
+ setHasNewVersion(true);
118
+ }
85
119
  }
86
120
  } catch (err) {
87
121
  setError(err.message);
88
122
  } finally {
89
123
  setLoading(false);
90
124
  }
125
+ }, [projectId, baseUrl]);
126
+ const fetchChangelog = useCallback(async () => {
127
+ try {
128
+ const response = await fetch(`${baseUrl}/api/changelog/${projectId}?limit=10`);
129
+ if (!response.ok) throw new Error("Failed to fetch changelog");
130
+ const data = await response.json();
131
+ setVersions(data.changelog);
132
+ setProject(data.project);
133
+ if (data.changelog.length > 0) {
134
+ const storedVersion = localStorage.getItem(`vp_${projectId}_seen`);
135
+ if (data.changelog[0].version !== storedVersion) {
136
+ setHasNewVersion(true);
137
+ onNewVersion?.(data.changelog[0]);
138
+ }
139
+ }
140
+ } catch (err) {
141
+ console.error("Failed to fetch changelog:", err);
142
+ }
91
143
  }, [projectId, baseUrl, onNewVersion]);
92
144
  useEffect(() => {
93
- fetchChangelog();
94
- }, [fetchChangelog]);
145
+ fetchVersion();
146
+ }, [fetchVersion]);
95
147
  const handleOpen = () => {
148
+ if (onClick) {
149
+ onClick();
150
+ return;
151
+ }
96
152
  setIsOpen(true);
97
- if (versions.length > 0) {
98
- localStorage.setItem(`vp_${projectId}_seen`, versions[0].version);
153
+ fetchChangelog();
154
+ if (project?.currentVersion) {
155
+ localStorage.setItem(`vp_${projectId}_seen`, project.currentVersion);
99
156
  setHasNewVersion(false);
100
157
  }
101
158
  };
102
- const currentVersion = versions[0];
159
+ const currentVersion = project?.currentVersion;
103
160
  const positionStyles = {
104
161
  "top-left": "fixed top-4 left-4 z-[9999]",
105
162
  "top-right": "fixed top-4 right-4 z-[9999]",
@@ -108,34 +165,195 @@ function VersionPill({
108
165
  inline: ""
109
166
  };
110
167
  if (loading) {
111
- return /* @__PURE__ */ jsx("div", { className: clsx(positionStyles[position], className), children: /* @__PURE__ */ jsx("div", { className: clsx(styles.pill, isLight ? styles.pillLight : styles.pillDark), children: /* @__PURE__ */ jsx("span", { className: "opacity-50", children: "..." }) }) });
112
- }
113
- if (error || !currentVersion) return null;
114
- return /* @__PURE__ */ jsxs(Fragment, { children: [
115
- /* @__PURE__ */ jsx("div", { className: clsx(positionStyles[position], className), children: /* @__PURE__ */ jsxs(
116
- "button",
168
+ return /* @__PURE__ */ jsx("div", { className: clsx(positionStyles[position], className), style: { display: "inline-flex" }, children: /* @__PURE__ */ jsxs(
169
+ "span",
117
170
  {
118
- onClick: handleOpen,
119
- className: clsx(styles.pill, isLight ? styles.pillLight : styles.pillDark),
120
- style: accentColor ? { borderColor: accentColor } : void 0,
171
+ style: {
172
+ display: "inline-flex",
173
+ alignItems: "center",
174
+ gap: sizeConfig.gap,
175
+ height: sizeConfig.height,
176
+ padding: sizeConfig.padding,
177
+ fontSize: sizeConfig.font,
178
+ fontFamily: "ui-monospace, monospace",
179
+ fontWeight: 500,
180
+ borderRadius: 999,
181
+ background: colors.bg,
182
+ border: `1px solid ${colors.border}`,
183
+ color: colors.text,
184
+ opacity: 0.6
185
+ },
121
186
  children: [
122
- hasNewVersion && /* @__PURE__ */ jsx("span", { className: styles.newDot }),
123
- /* @__PURE__ */ jsxs("span", { children: [
124
- "v",
125
- currentVersion.version
126
- ] }),
127
- /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx("path", { d: "M6 9l6 6 6-6" }) })
187
+ /* @__PURE__ */ jsx(
188
+ "span",
189
+ {
190
+ style: {
191
+ width: sizeConfig.dotSize,
192
+ height: sizeConfig.dotSize,
193
+ borderRadius: "50%",
194
+ background: colors.dot,
195
+ opacity: 0.4
196
+ }
197
+ }
198
+ ),
199
+ /* @__PURE__ */ jsx("span", { style: { width: 32, height: sizeConfig.font, background: colors.border, borderRadius: 2 } })
128
200
  ]
129
201
  }
130
- ) }),
202
+ ) });
203
+ }
204
+ if (error || !currentVersion) return null;
205
+ const renderBadge = () => {
206
+ const baseButtonStyle = {
207
+ display: "inline-flex",
208
+ alignItems: "center",
209
+ gap: sizeConfig.gap,
210
+ height: sizeConfig.height,
211
+ padding: sizeConfig.padding,
212
+ fontSize: sizeConfig.font,
213
+ fontFamily: "ui-monospace, monospace",
214
+ fontWeight: 600,
215
+ borderRadius: 999,
216
+ cursor: "pointer",
217
+ border: "none",
218
+ transition: "all 150ms ease",
219
+ outline: "none"
220
+ };
221
+ switch (style) {
222
+ case "glass":
223
+ return /* @__PURE__ */ jsxs(
224
+ "button",
225
+ {
226
+ onClick: handleOpen,
227
+ style: {
228
+ ...baseButtonStyle,
229
+ background: isLight ? "rgba(255,255,255,0.7)" : "rgba(255,255,255,0.1)",
230
+ backdropFilter: "blur(12px)",
231
+ WebkitBackdropFilter: "blur(12px)",
232
+ border: `1px solid ${isLight ? "rgba(255,255,255,0.8)" : "rgba(255,255,255,0.15)"}`,
233
+ boxShadow: "0 4px 12px rgba(0,0,0,0.08), inset 0 1px 0 rgba(255,255,255,0.2)",
234
+ color: isLight ? "#374151" : "#f3f4f6"
235
+ },
236
+ children: [
237
+ hasNewVersion && /* @__PURE__ */ jsx(PulseDot, { color: colors.dot, size: sizeConfig.dotSize }),
238
+ /* @__PURE__ */ jsxs("span", { children: [
239
+ "v",
240
+ currentVersion
241
+ ] })
242
+ ]
243
+ }
244
+ );
245
+ case "gradient":
246
+ return /* @__PURE__ */ jsxs(
247
+ "button",
248
+ {
249
+ onClick: handleOpen,
250
+ style: {
251
+ ...baseButtonStyle,
252
+ background: "linear-gradient(135deg, #22c55e 0%, #16a34a 100%)",
253
+ color: "#fff",
254
+ boxShadow: "0 4px 12px rgba(34,197,94,0.3)"
255
+ },
256
+ children: [
257
+ hasNewVersion && /* @__PURE__ */ jsx(PulseDot, { color: "#fff", size: sizeConfig.dotSize }),
258
+ /* @__PURE__ */ jsxs("span", { children: [
259
+ "v",
260
+ currentVersion
261
+ ] })
262
+ ]
263
+ }
264
+ );
265
+ case "pill":
266
+ return /* @__PURE__ */ jsxs(
267
+ "button",
268
+ {
269
+ onClick: handleOpen,
270
+ style: {
271
+ ...baseButtonStyle,
272
+ background: colors.bg,
273
+ border: `1px solid ${colors.border}`,
274
+ color: colors.text,
275
+ boxShadow: `0 0 0 0 ${colors.dot}`
276
+ },
277
+ onMouseEnter: (e) => {
278
+ e.currentTarget.style.boxShadow = `0 0 12px 2px ${colors.dot}40`;
279
+ },
280
+ onMouseLeave: (e) => {
281
+ e.currentTarget.style.boxShadow = `0 0 0 0 ${colors.dot}`;
282
+ },
283
+ children: [
284
+ hasNewVersion && /* @__PURE__ */ jsx(PulseDot, { color: colors.dot, size: sizeConfig.dotSize }),
285
+ /* @__PURE__ */ jsxs("span", { children: [
286
+ "v",
287
+ currentVersion
288
+ ] })
289
+ ]
290
+ }
291
+ );
292
+ case "minimal":
293
+ return /* @__PURE__ */ jsxs(
294
+ "button",
295
+ {
296
+ onClick: handleOpen,
297
+ style: {
298
+ ...baseButtonStyle,
299
+ background: "transparent",
300
+ padding: 0,
301
+ height: "auto",
302
+ color: colors.text
303
+ },
304
+ children: [
305
+ hasNewVersion && /* @__PURE__ */ jsx(PulseDot, { color: colors.dot, size: sizeConfig.dotSize }),
306
+ /* @__PURE__ */ jsxs("span", { children: [
307
+ "v",
308
+ currentVersion
309
+ ] })
310
+ ]
311
+ }
312
+ );
313
+ case "dot":
314
+ default:
315
+ return /* @__PURE__ */ jsxs(
316
+ "button",
317
+ {
318
+ onClick: handleOpen,
319
+ style: {
320
+ ...baseButtonStyle,
321
+ background: colors.bg,
322
+ border: `1px solid ${colors.border}`,
323
+ color: colors.text
324
+ },
325
+ children: [
326
+ /* @__PURE__ */ jsx(
327
+ "span",
328
+ {
329
+ style: {
330
+ width: sizeConfig.dotSize,
331
+ height: sizeConfig.dotSize,
332
+ borderRadius: "50%",
333
+ background: colors.dot,
334
+ flexShrink: 0
335
+ }
336
+ }
337
+ ),
338
+ /* @__PURE__ */ jsxs("span", { children: [
339
+ "v",
340
+ currentVersion
341
+ ] })
342
+ ]
343
+ }
344
+ );
345
+ }
346
+ };
347
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
348
+ /* @__PURE__ */ jsx("div", { className: clsx(positionStyles[position], className), style: { display: "inline-flex" }, children: renderBadge() }),
131
349
  isOpen && /* @__PURE__ */ jsxs("div", { className: styles.modal, children: [
132
350
  /* @__PURE__ */ jsx("div", { className: styles.backdrop, onClick: () => setIsOpen(false) }),
133
351
  /* @__PURE__ */ jsxs("div", { className: clsx(styles.panel, isLight ? styles.panelLight : styles.panelDark), children: [
134
352
  /* @__PURE__ */ jsxs("div", { className: clsx(styles.header, isLight ? styles.headerLight : styles.headerDark), children: [
135
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
136
- /* @__PURE__ */ jsx("span", { className: "text-lg", children: currentVersion.emoji || "\u2728" }),
353
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
354
+ project?.icon && /* @__PURE__ */ jsx("span", { style: { fontSize: 20 }, children: project.icon }),
137
355
  /* @__PURE__ */ jsxs("div", { children: [
138
- /* @__PURE__ */ jsx("h2", { className: clsx("font-semibold", isLight ? "text-gray-900" : "text-white"), children: "What's New" }),
356
+ /* @__PURE__ */ jsx("h2", { className: clsx("font-semibold", isLight ? "text-gray-900" : "text-white"), children: project?.name || "What's New" }),
139
357
  /* @__PURE__ */ jsx("p", { className: clsx("text-xs", isLight ? "text-gray-500" : "text-gray-400"), children: "Latest updates" })
140
358
  ] })
141
359
  ] }),
@@ -144,13 +362,17 @@ function VersionPill({
144
362
  {
145
363
  onClick: () => setIsOpen(false),
146
364
  className: clsx("p-1 rounded hover:bg-gray-100", !isLight && "hover:bg-gray-800"),
365
+ style: { background: "transparent", border: "none", cursor: "pointer", color: isLight ? "#374151" : "#9ca3af" },
147
366
  children: /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" }) })
148
367
  }
149
368
  )
150
369
  ] }),
151
- /* @__PURE__ */ jsx("div", { className: styles.content, children: versions.slice(0, 5).map((version) => /* @__PURE__ */ jsxs("div", { className: styles.version, children: [
370
+ /* @__PURE__ */ jsx("div", { className: styles.content, children: versions.length === 0 ? /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", padding: 40, color: isLight ? "#6b7280" : "#9ca3af" }, children: [
371
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 32, marginBottom: 8 }, children: "\u{1F680}" }),
372
+ /* @__PURE__ */ jsx("div", { children: "No releases yet" })
373
+ ] }) : versions.slice(0, 5).map((version, idx) => /* @__PURE__ */ jsxs("div", { className: styles.version, children: [
152
374
  /* @__PURE__ */ jsxs("div", { className: styles.versionHeader, children: [
153
- /* @__PURE__ */ jsx("span", { className: "text-lg", children: version.emoji || "\u{1F4E6}" }),
375
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 16 }, children: version.emoji || "\u{1F4E6}" }),
154
376
  /* @__PURE__ */ jsx("span", { className: clsx(styles.versionTitle, isLight ? "text-gray-900" : "text-white"), children: version.title }),
155
377
  /* @__PURE__ */ jsx(
156
378
  "span",
@@ -167,14 +389,14 @@ function VersionPill({
167
389
  "v",
168
390
  version.version,
169
391
  " \xB7 ",
170
- new Date(version.releaseDate).toLocaleDateString()
392
+ new Date(version.date).toLocaleDateString()
171
393
  ] }),
172
394
  version.description && /* @__PURE__ */ jsx("p", { className: clsx("text-sm mb-2", isLight ? "text-gray-600" : "text-gray-300"), children: version.description }),
173
395
  version.features && version.features.length > 0 && /* @__PURE__ */ jsx("ul", { className: styles.versionFeatures, children: version.features.map((feature, i) => /* @__PURE__ */ jsxs("li", { className: clsx(styles.versionFeature, isLight ? "text-gray-600" : "text-gray-300"), children: [
174
- /* @__PURE__ */ jsx("span", { className: "text-green-500 mt-0.5", children: "\u2713" }),
396
+ /* @__PURE__ */ jsx("span", { style: { color: "#22c55e", marginTop: 2 }, children: "\u2713" }),
175
397
  feature
176
398
  ] }, i)) })
177
- ] }, version.id)) }),
399
+ ] }, idx)) }),
178
400
  /* @__PURE__ */ jsxs("div", { className: clsx(styles.footer, isLight ? styles.footerLight : styles.footerDark), children: [
179
401
  showBranding && /* @__PURE__ */ jsx(
180
402
  "a",
@@ -186,7 +408,7 @@ function VersionPill({
186
408
  children: /* @__PURE__ */ jsx("span", { children: "Powered by Version Pill" })
187
409
  }
188
410
  ),
189
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
411
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
190
412
  /* @__PURE__ */ jsx(
191
413
  "a",
192
414
  {
@@ -204,7 +426,6 @@ function VersionPill({
204
426
  target: "_blank",
205
427
  rel: "noopener noreferrer",
206
428
  className: clsx(styles.button, styles.buttonPrimary),
207
- style: accentColor ? { backgroundColor: accentColor } : void 0,
208
429
  children: "View All"
209
430
  }
210
431
  )
@@ -214,6 +435,179 @@ function VersionPill({
214
435
  ] })
215
436
  ] });
216
437
  }
438
+ function PulseDot({ color, size }) {
439
+ return /* @__PURE__ */ jsxs("span", { style: { position: "relative", display: "inline-flex", width: size, height: size }, children: [
440
+ /* @__PURE__ */ jsx(
441
+ "span",
442
+ {
443
+ style: {
444
+ position: "absolute",
445
+ inset: 0,
446
+ borderRadius: "50%",
447
+ background: color,
448
+ opacity: 0.75,
449
+ animation: "vp-ping 1s cubic-bezier(0, 0, 0.2, 1) infinite"
450
+ }
451
+ }
452
+ ),
453
+ /* @__PURE__ */ jsx(
454
+ "span",
455
+ {
456
+ style: {
457
+ position: "relative",
458
+ display: "inline-flex",
459
+ width: size,
460
+ height: size,
461
+ borderRadius: "50%",
462
+ background: color
463
+ }
464
+ }
465
+ ),
466
+ /* @__PURE__ */ jsx("style", { children: `@keyframes vp-ping { 75%, 100% { transform: scale(2); opacity: 0; } }` })
467
+ ] });
468
+ }
469
+ function VersionBadge({
470
+ projectId,
471
+ baseUrl = DEFAULT_BASE_URL,
472
+ theme = "auto",
473
+ style = "dot",
474
+ size = "md",
475
+ accent = "green",
476
+ className,
477
+ href
478
+ }) {
479
+ const [version, setVersion] = useState(null);
480
+ const [loading, setLoading] = useState(true);
481
+ const resolvedTheme = useTheme(theme);
482
+ const isLight = resolvedTheme === "light";
483
+ const colors = ACCENT_COLORS[accent][isLight ? "light" : "dark"];
484
+ const sizeConfig = SIZE_CONFIG[size];
485
+ useEffect(() => {
486
+ fetch(`${baseUrl}/api/badge/${projectId}`).then((res) => res.json()).then((data) => setVersion(data.version)).catch(() => setVersion(null)).finally(() => setLoading(false));
487
+ }, [projectId, baseUrl]);
488
+ if (loading) {
489
+ return /* @__PURE__ */ jsxs(
490
+ "span",
491
+ {
492
+ className,
493
+ style: {
494
+ display: "inline-flex",
495
+ alignItems: "center",
496
+ gap: sizeConfig.gap,
497
+ height: sizeConfig.height,
498
+ padding: sizeConfig.padding,
499
+ fontSize: sizeConfig.font,
500
+ fontFamily: "ui-monospace, monospace",
501
+ fontWeight: 500,
502
+ borderRadius: 999,
503
+ background: colors.bg,
504
+ border: `1px solid ${colors.border}`,
505
+ color: colors.text,
506
+ opacity: 0.6
507
+ },
508
+ children: [
509
+ /* @__PURE__ */ jsx(
510
+ "span",
511
+ {
512
+ style: {
513
+ width: sizeConfig.dotSize,
514
+ height: sizeConfig.dotSize,
515
+ borderRadius: "50%",
516
+ background: colors.dot,
517
+ opacity: 0.4
518
+ }
519
+ }
520
+ ),
521
+ /* @__PURE__ */ jsx("span", { style: { width: 32, height: sizeConfig.font, background: colors.border, borderRadius: 2 } })
522
+ ]
523
+ }
524
+ );
525
+ }
526
+ if (!version) return null;
527
+ const getStyleVariant = () => {
528
+ const base = {
529
+ display: "inline-flex",
530
+ alignItems: "center",
531
+ gap: sizeConfig.gap,
532
+ fontSize: sizeConfig.font,
533
+ fontFamily: "ui-monospace, monospace",
534
+ fontWeight: 600,
535
+ borderRadius: 999,
536
+ textDecoration: "none",
537
+ transition: "opacity 150ms ease"
538
+ };
539
+ if (style === "minimal") {
540
+ return { ...base, background: "transparent", border: "none", padding: 0, color: colors.text };
541
+ }
542
+ if (style === "glass") {
543
+ return {
544
+ ...base,
545
+ height: sizeConfig.height,
546
+ padding: sizeConfig.padding,
547
+ background: isLight ? "rgba(255,255,255,0.7)" : "rgba(255,255,255,0.1)",
548
+ backdropFilter: "blur(12px)",
549
+ WebkitBackdropFilter: "blur(12px)",
550
+ border: `1px solid ${isLight ? "rgba(255,255,255,0.8)" : "rgba(255,255,255,0.15)"}`,
551
+ boxShadow: "0 4px 12px rgba(0,0,0,0.08), inset 0 1px 0 rgba(255,255,255,0.2)",
552
+ color: isLight ? "#374151" : "#f3f4f6"
553
+ };
554
+ }
555
+ if (style === "gradient") {
556
+ return {
557
+ ...base,
558
+ height: sizeConfig.height,
559
+ padding: sizeConfig.padding,
560
+ background: "linear-gradient(135deg, #22c55e 0%, #16a34a 100%)",
561
+ border: "none",
562
+ color: "#fff",
563
+ boxShadow: "0 4px 12px rgba(34,197,94,0.3)"
564
+ };
565
+ }
566
+ return {
567
+ ...base,
568
+ height: sizeConfig.height,
569
+ padding: sizeConfig.padding,
570
+ background: colors.bg,
571
+ border: `1px solid ${colors.border}`,
572
+ color: colors.text
573
+ };
574
+ };
575
+ const badgeStyle = getStyleVariant();
576
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
577
+ style === "dot" && /* @__PURE__ */ jsx(
578
+ "span",
579
+ {
580
+ style: {
581
+ width: sizeConfig.dotSize,
582
+ height: sizeConfig.dotSize,
583
+ borderRadius: "50%",
584
+ background: colors.dot,
585
+ flexShrink: 0
586
+ }
587
+ }
588
+ ),
589
+ /* @__PURE__ */ jsxs("span", { children: [
590
+ "v",
591
+ version
592
+ ] })
593
+ ] });
594
+ if (href) {
595
+ return /* @__PURE__ */ jsx(
596
+ "a",
597
+ {
598
+ href,
599
+ target: "_blank",
600
+ rel: "noopener noreferrer",
601
+ className,
602
+ style: badgeStyle,
603
+ onMouseEnter: (e) => e.currentTarget.style.opacity = "0.8",
604
+ onMouseLeave: (e) => e.currentTarget.style.opacity = "1",
605
+ children: content
606
+ }
607
+ );
608
+ }
609
+ return /* @__PURE__ */ jsx("span", { className, style: badgeStyle, children: content });
610
+ }
217
611
  function Changelog({
218
612
  projectId,
219
613
  baseUrl = DEFAULT_BASE_URL,
@@ -250,11 +644,56 @@ function Roadmap({
250
644
  }
251
645
  );
252
646
  }
647
+ var versionCache = {};
648
+ function useVersion({
649
+ projectId,
650
+ baseUrl = DEFAULT_BASE_URL,
651
+ refetchInterval = 0
652
+ }) {
653
+ const cacheKey = `${baseUrl}:${projectId}`;
654
+ const cached = versionCache[cacheKey];
655
+ const [version, setVersion] = useState(cached?.version ?? null);
656
+ const [name, setName] = useState(cached?.name ?? null);
657
+ const [slug, setSlug] = useState(cached?.slug ?? null);
658
+ const [loading, setLoading] = useState(!cached);
659
+ const [error, setError] = useState(null);
660
+ const fetchVersion = useCallback(async () => {
661
+ if (typeof window === "undefined") return;
662
+ try {
663
+ setError(null);
664
+ const response = await fetch(`${baseUrl}/api/badge/${projectId}`);
665
+ if (!response.ok) throw new Error("Failed to fetch version");
666
+ const data = await response.json();
667
+ versionCache[cacheKey] = { version: data.version, name: data.name, slug: data.slug };
668
+ setVersion(data.version);
669
+ setName(data.name);
670
+ setSlug(data.slug);
671
+ } catch (err) {
672
+ setError(err.message);
673
+ } finally {
674
+ setLoading(false);
675
+ }
676
+ }, [projectId, baseUrl, cacheKey]);
677
+ useEffect(() => {
678
+ if (!cached) {
679
+ fetchVersion();
680
+ }
681
+ }, [fetchVersion, cached]);
682
+ useEffect(() => {
683
+ if (refetchInterval > 0) {
684
+ const interval = setInterval(fetchVersion, refetchInterval);
685
+ return () => clearInterval(interval);
686
+ }
687
+ }, [refetchInterval, fetchVersion]);
688
+ return { version, name, slug, loading, error, refetch: fetchVersion };
689
+ }
253
690
  var index_default = VersionPill;
254
691
  export {
255
692
  Changelog,
256
693
  Roadmap,
694
+ VersionBadge,
257
695
  VersionPill,
258
- index_default as default
696
+ index_default as default,
697
+ useVersion
259
698
  };
260
699
  //# sourceMappingURL=index.mjs.map