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