@medialane/ui 0.36.0 → 0.37.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.
Files changed (55) hide show
  1. package/dist/components/load-more-sentinel.cjs +54 -0
  2. package/dist/components/load-more-sentinel.cjs.map +1 -0
  3. package/dist/components/load-more-sentinel.d.cts +17 -0
  4. package/dist/components/load-more-sentinel.d.ts +17 -0
  5. package/dist/components/load-more-sentinel.js +30 -0
  6. package/dist/components/load-more-sentinel.js.map +1 -0
  7. package/dist/components/rewards/badge-shelf.cjs +90 -0
  8. package/dist/components/rewards/badge-shelf.cjs.map +1 -0
  9. package/dist/components/rewards/badge-shelf.d.cts +24 -0
  10. package/dist/components/rewards/badge-shelf.d.ts +24 -0
  11. package/dist/components/rewards/badge-shelf.js +56 -0
  12. package/dist/components/rewards/badge-shelf.js.map +1 -0
  13. package/dist/components/rewards/leaderboard-table.cjs +96 -0
  14. package/dist/components/rewards/leaderboard-table.cjs.map +1 -0
  15. package/dist/components/rewards/leaderboard-table.d.cts +34 -0
  16. package/dist/components/rewards/leaderboard-table.d.ts +34 -0
  17. package/dist/components/rewards/leaderboard-table.js +71 -0
  18. package/dist/components/rewards/leaderboard-table.js.map +1 -0
  19. package/dist/components/rewards/level-badge.cjs +60 -0
  20. package/dist/components/rewards/level-badge.cjs.map +1 -0
  21. package/dist/components/rewards/level-badge.d.cts +14 -0
  22. package/dist/components/rewards/level-badge.d.ts +14 -0
  23. package/dist/components/rewards/level-badge.js +36 -0
  24. package/dist/components/rewards/level-badge.js.map +1 -0
  25. package/dist/components/rewards/level-ladder.cjs +54 -0
  26. package/dist/components/rewards/level-ladder.cjs.map +1 -0
  27. package/dist/components/rewards/level-ladder.d.cts +17 -0
  28. package/dist/components/rewards/level-ladder.d.ts +17 -0
  29. package/dist/components/rewards/level-ladder.js +30 -0
  30. package/dist/components/rewards/level-ladder.js.map +1 -0
  31. package/dist/components/rewards/score-summary-card.cjs +83 -0
  32. package/dist/components/rewards/score-summary-card.cjs.map +1 -0
  33. package/dist/components/rewards/score-summary-card.d.cts +26 -0
  34. package/dist/components/rewards/score-summary-card.d.ts +26 -0
  35. package/dist/components/rewards/score-summary-card.js +59 -0
  36. package/dist/components/rewards/score-summary-card.js.map +1 -0
  37. package/dist/components/rewards/xp-progress.cjs +81 -0
  38. package/dist/components/rewards/xp-progress.cjs.map +1 -0
  39. package/dist/components/rewards/xp-progress.d.cts +18 -0
  40. package/dist/components/rewards/xp-progress.d.ts +18 -0
  41. package/dist/components/rewards/xp-progress.js +57 -0
  42. package/dist/components/rewards/xp-progress.js.map +1 -0
  43. package/dist/components/rewards/xp-toast-content.cjs +49 -0
  44. package/dist/components/rewards/xp-toast-content.cjs.map +1 -0
  45. package/dist/components/rewards/xp-toast-content.d.cts +14 -0
  46. package/dist/components/rewards/xp-toast-content.d.ts +14 -0
  47. package/dist/components/rewards/xp-toast-content.js +25 -0
  48. package/dist/components/rewards/xp-toast-content.js.map +1 -0
  49. package/dist/index.cjs +3 -0
  50. package/dist/index.cjs.map +1 -1
  51. package/dist/index.d.cts +10 -128
  52. package/dist/index.d.ts +10 -128
  53. package/dist/index.js +2 -0
  54. package/dist/index.js.map +1 -1
  55. package/package.json +1 -1
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ "use client";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+ var load_more_sentinel_exports = {};
21
+ __export(load_more_sentinel_exports, {
22
+ LoadMoreSentinel: () => LoadMoreSentinel
23
+ });
24
+ module.exports = __toCommonJS(load_more_sentinel_exports);
25
+ var import_jsx_runtime = require("react/jsx-runtime");
26
+ var import_react = require("react");
27
+ function LoadMoreSentinel({
28
+ hasMore,
29
+ isLoading,
30
+ onLoadMore,
31
+ rootMargin = "400px"
32
+ }) {
33
+ const ref = (0, import_react.useRef)(null);
34
+ (0, import_react.useEffect)(() => {
35
+ if (!hasMore || isLoading) return;
36
+ const el = ref.current;
37
+ if (!el) return;
38
+ const observer = new IntersectionObserver(
39
+ ([entry]) => {
40
+ if (entry.isIntersecting) onLoadMore();
41
+ },
42
+ { rootMargin }
43
+ );
44
+ observer.observe(el);
45
+ return () => observer.disconnect();
46
+ }, [hasMore, isLoading, rootMargin, onLoadMore]);
47
+ if (!hasMore) return null;
48
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref, "aria-hidden": true, className: "h-px w-full" });
49
+ }
50
+ // Annotate the CommonJS export names for ESM import in node:
51
+ 0 && (module.exports = {
52
+ LoadMoreSentinel
53
+ });
54
+ //# sourceMappingURL=load-more-sentinel.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/load-more-sentinel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef } from \"react\";\n\nexport interface LoadMoreSentinelProps {\n hasMore: boolean;\n isLoading: boolean;\n onLoadMore: () => void;\n rootMargin?: string;\n}\n\n/**\n * Invisible trigger that calls `onLoadMore` when scrolled near the viewport.\n * Renders nothing once `hasMore` is false. Skips firing while `isLoading` is\n * true so callers can pass their existing \"currently fetching\" flag straight\n * through with no extra guard logic.\n */\nexport function LoadMoreSentinel({\n hasMore,\n isLoading,\n onLoadMore,\n rootMargin = \"400px\",\n}: LoadMoreSentinelProps) {\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!hasMore || isLoading) return;\n const el = ref.current;\n if (!el) return;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) onLoadMore();\n },\n { rootMargin }\n );\n observer.observe(el);\n return () => observer.disconnect();\n }, [hasMore, isLoading, rootMargin, onLoadMore]);\n\n if (!hasMore) return null;\n return <div ref={ref} aria-hidden className=\"h-px w-full\" />;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAyCS;AAvCT,mBAAkC;AAe3B,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AACf,GAA0B;AACxB,QAAM,UAAM,qBAAuB,IAAI;AAEvC,8BAAU,MAAM;AACd,QAAI,CAAC,WAAW,UAAW;AAC3B,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,GAAI;AAET,UAAM,WAAW,IAAI;AAAA,MACnB,CAAC,CAAC,KAAK,MAAM;AACX,YAAI,MAAM,eAAgB,YAAW;AAAA,MACvC;AAAA,MACA,EAAE,WAAW;AAAA,IACf;AACA,aAAS,QAAQ,EAAE;AACnB,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,SAAS,WAAW,YAAY,UAAU,CAAC;AAE/C,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,4CAAC,SAAI,KAAU,eAAW,MAAC,WAAU,eAAc;AAC5D;","names":[]}
@@ -0,0 +1,17 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface LoadMoreSentinelProps {
4
+ hasMore: boolean;
5
+ isLoading: boolean;
6
+ onLoadMore: () => void;
7
+ rootMargin?: string;
8
+ }
9
+ /**
10
+ * Invisible trigger that calls `onLoadMore` when scrolled near the viewport.
11
+ * Renders nothing once `hasMore` is false. Skips firing while `isLoading` is
12
+ * true so callers can pass their existing "currently fetching" flag straight
13
+ * through with no extra guard logic.
14
+ */
15
+ declare function LoadMoreSentinel({ hasMore, isLoading, onLoadMore, rootMargin, }: LoadMoreSentinelProps): react_jsx_runtime.JSX.Element | null;
16
+
17
+ export { LoadMoreSentinel, type LoadMoreSentinelProps };
@@ -0,0 +1,17 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface LoadMoreSentinelProps {
4
+ hasMore: boolean;
5
+ isLoading: boolean;
6
+ onLoadMore: () => void;
7
+ rootMargin?: string;
8
+ }
9
+ /**
10
+ * Invisible trigger that calls `onLoadMore` when scrolled near the viewport.
11
+ * Renders nothing once `hasMore` is false. Skips firing while `isLoading` is
12
+ * true so callers can pass their existing "currently fetching" flag straight
13
+ * through with no extra guard logic.
14
+ */
15
+ declare function LoadMoreSentinel({ hasMore, isLoading, onLoadMore, rootMargin, }: LoadMoreSentinelProps): react_jsx_runtime.JSX.Element | null;
16
+
17
+ export { LoadMoreSentinel, type LoadMoreSentinelProps };
@@ -0,0 +1,30 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useEffect, useRef } from "react";
4
+ function LoadMoreSentinel({
5
+ hasMore,
6
+ isLoading,
7
+ onLoadMore,
8
+ rootMargin = "400px"
9
+ }) {
10
+ const ref = useRef(null);
11
+ useEffect(() => {
12
+ if (!hasMore || isLoading) return;
13
+ const el = ref.current;
14
+ if (!el) return;
15
+ const observer = new IntersectionObserver(
16
+ ([entry]) => {
17
+ if (entry.isIntersecting) onLoadMore();
18
+ },
19
+ { rootMargin }
20
+ );
21
+ observer.observe(el);
22
+ return () => observer.disconnect();
23
+ }, [hasMore, isLoading, rootMargin, onLoadMore]);
24
+ if (!hasMore) return null;
25
+ return /* @__PURE__ */ jsx("div", { ref, "aria-hidden": true, className: "h-px w-full" });
26
+ }
27
+ export {
28
+ LoadMoreSentinel
29
+ };
30
+ //# sourceMappingURL=load-more-sentinel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/load-more-sentinel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef } from \"react\";\n\nexport interface LoadMoreSentinelProps {\n hasMore: boolean;\n isLoading: boolean;\n onLoadMore: () => void;\n rootMargin?: string;\n}\n\n/**\n * Invisible trigger that calls `onLoadMore` when scrolled near the viewport.\n * Renders nothing once `hasMore` is false. Skips firing while `isLoading` is\n * true so callers can pass their existing \"currently fetching\" flag straight\n * through with no extra guard logic.\n */\nexport function LoadMoreSentinel({\n hasMore,\n isLoading,\n onLoadMore,\n rootMargin = \"400px\",\n}: LoadMoreSentinelProps) {\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!hasMore || isLoading) return;\n const el = ref.current;\n if (!el) return;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) onLoadMore();\n },\n { rootMargin }\n );\n observer.observe(el);\n return () => observer.disconnect();\n }, [hasMore, isLoading, rootMargin, onLoadMore]);\n\n if (!hasMore) return null;\n return <div ref={ref} aria-hidden className=\"h-px w-full\" />;\n}\n"],"mappings":";AAyCS;AAvCT,SAAS,WAAW,cAAc;AAe3B,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AACf,GAA0B;AACxB,QAAM,MAAM,OAAuB,IAAI;AAEvC,YAAU,MAAM;AACd,QAAI,CAAC,WAAW,UAAW;AAC3B,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,GAAI;AAET,UAAM,WAAW,IAAI;AAAA,MACnB,CAAC,CAAC,KAAK,MAAM;AACX,YAAI,MAAM,eAAgB,YAAW;AAAA,MACvC;AAAA,MACA,EAAE,WAAW;AAAA,IACf;AACA,aAAS,QAAQ,EAAE;AACnB,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,SAAS,WAAW,YAAY,UAAU,CAAC;AAE/C,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,oBAAC,SAAI,KAAU,eAAW,MAAC,WAAU,eAAc;AAC5D;","names":[]}
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ "use client";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+ var badge_shelf_exports = {};
31
+ __export(badge_shelf_exports, {
32
+ BadgeShelf: () => BadgeShelf
33
+ });
34
+ module.exports = __toCommonJS(badge_shelf_exports);
35
+ var import_jsx_runtime = require("react/jsx-runtime");
36
+ var import_react = __toESM(require("react"), 1);
37
+ var import_cn = require("../../utils/cn.js");
38
+ const iconCache = /* @__PURE__ */ new Map();
39
+ function BadgeIcon({ name, color, className }) {
40
+ const [Icon, setIcon] = import_react.default.useState(
41
+ () => iconCache.get(name) ?? null
42
+ );
43
+ import_react.default.useEffect(() => {
44
+ if (iconCache.has(name)) {
45
+ setIcon(() => iconCache.get(name));
46
+ return;
47
+ }
48
+ import("lucide-react").then((mod) => {
49
+ const C = mod[name];
50
+ if (C) {
51
+ iconCache.set(name, C);
52
+ setIcon(() => C);
53
+ }
54
+ });
55
+ }, [name]);
56
+ if (!Icon) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: (0, import_cn.cn)("rounded-full", className), style: { backgroundColor: color } });
57
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { className, style: { color } });
58
+ }
59
+ function BadgeShelf({ badges, earnedKeys, showLocked = false, className }) {
60
+ const earned = earnedKeys ? new Set(earnedKeys) : null;
61
+ const visible = badges.filter((b) => showLocked || !earned || earned.has(b.key));
62
+ if (visible.length === 0) return null;
63
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: (0, import_cn.cn)("flex flex-wrap gap-2", className), children: visible.map((badge) => {
64
+ const isLocked = earned ? !earned.has(badge.key) : false;
65
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
66
+ "div",
67
+ {
68
+ title: isLocked ? `${badge.description} \u2014 keep going to earn this` : badge.description,
69
+ className: (0, import_cn.cn)(
70
+ "flex h-7 select-none items-center gap-1.5 rounded-full border px-2.5",
71
+ isLocked && "opacity-45 grayscale"
72
+ ),
73
+ style: {
74
+ borderColor: `${badge.color}60`,
75
+ backgroundColor: `${badge.color}14`
76
+ },
77
+ children: [
78
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BadgeIcon, { name: badge.icon, color: badge.color, className: "h-3.5 w-3.5 shrink-0" }),
79
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-xs font-semibold", style: { color: badge.color }, children: badge.name })
80
+ ]
81
+ },
82
+ badge.key
83
+ );
84
+ }) });
85
+ }
86
+ // Annotate the CommonJS export names for ESM import in node:
87
+ 0 && (module.exports = {
88
+ BadgeShelf
89
+ });
90
+ //# sourceMappingURL=badge-shelf.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/rewards/badge-shelf.tsx"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\nimport type { LucideProps } from \"lucide-react\";\nimport { cn } from \"../../utils/cn.js\";\n\nexport interface BadgeShelfBadge {\n key: string;\n name: string;\n description: string;\n icon: string;\n color: string;\n category: string;\n}\n\nexport interface BadgeShelfProps {\n /** The badges to render — the earned set, or the full catalog with `earnedKeys`. */\n badges: BadgeShelfBadge[];\n /** When set, badges outside this set render locked (grayscale, reduced opacity). */\n earnedKeys?: string[];\n /** Render locked badges too (requires `earnedKeys`); default hides them. */\n showLocked?: boolean;\n className?: string;\n}\n\n// Lazily resolve lucide icons by name at runtime (icon names come from the API).\nconst iconCache = new Map<string, React.ComponentType<LucideProps>>();\n\nfunction BadgeIcon({ name, color, className }: { name: string; color: string; className?: string }) {\n const [Icon, setIcon] = React.useState<React.ComponentType<LucideProps> | null>(\n () => iconCache.get(name) ?? null\n );\n\n React.useEffect(() => {\n if (iconCache.has(name)) {\n setIcon(() => iconCache.get(name)!);\n return;\n }\n import(\"lucide-react\").then((mod) => {\n const C = (mod as Record<string, unknown>)[name] as React.ComponentType<LucideProps> | undefined;\n if (C) {\n iconCache.set(name, C);\n setIcon(() => C);\n }\n });\n }, [name]);\n\n if (!Icon) return <span className={cn(\"rounded-full\", className)} style={{ backgroundColor: color }} />;\n return <Icon className={className} style={{ color }} />;\n}\n\n/** Badge chips with native tooltips. Earned badges render in their color;\n * locked ones (present in `badges` but not `earnedKeys`) render muted. */\nexport function BadgeShelf({ badges, earnedKeys, showLocked = false, className }: BadgeShelfProps) {\n const earned = earnedKeys ? new Set(earnedKeys) : null;\n const visible = badges.filter((b) => showLocked || !earned || earned.has(b.key));\n if (visible.length === 0) return null;\n\n return (\n <div className={cn(\"flex flex-wrap gap-2\", className)}>\n {visible.map((badge) => {\n const isLocked = earned ? !earned.has(badge.key) : false;\n return (\n <div\n key={badge.key}\n title={isLocked ? `${badge.description} — keep going to earn this` : badge.description}\n className={cn(\n \"flex h-7 select-none items-center gap-1.5 rounded-full border px-2.5\",\n isLocked && \"opacity-45 grayscale\"\n )}\n style={{\n borderColor: `${badge.color}60`,\n backgroundColor: `${badge.color}14`,\n }}\n >\n <BadgeIcon name={badge.icon} color={badge.color} className=\"h-3.5 w-3.5 shrink-0\" />\n <span className=\"text-xs font-semibold\" style={{ color: badge.color }}>\n {badge.name}\n </span>\n </div>\n );\n })}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA+CoB;AA7CpB,mBAAkB;AAElB,gBAAmB;AAsBnB,MAAM,YAAY,oBAAI,IAA8C;AAEpE,SAAS,UAAU,EAAE,MAAM,OAAO,UAAU,GAAwD;AAClG,QAAM,CAAC,MAAM,OAAO,IAAI,aAAAA,QAAM;AAAA,IAC5B,MAAM,UAAU,IAAI,IAAI,KAAK;AAAA,EAC/B;AAEA,eAAAA,QAAM,UAAU,MAAM;AACpB,QAAI,UAAU,IAAI,IAAI,GAAG;AACvB,cAAQ,MAAM,UAAU,IAAI,IAAI,CAAE;AAClC;AAAA,IACF;AACA,WAAO,cAAc,EAAE,KAAK,CAAC,QAAQ;AACnC,YAAM,IAAK,IAAgC,IAAI;AAC/C,UAAI,GAAG;AACL,kBAAU,IAAI,MAAM,CAAC;AACrB,gBAAQ,MAAM,CAAC;AAAA,MACjB;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,IAAI,CAAC;AAET,MAAI,CAAC,KAAM,QAAO,4CAAC,UAAK,eAAW,cAAG,gBAAgB,SAAS,GAAG,OAAO,EAAE,iBAAiB,MAAM,GAAG;AACrG,SAAO,4CAAC,QAAK,WAAsB,OAAO,EAAE,MAAM,GAAG;AACvD;AAIO,SAAS,WAAW,EAAE,QAAQ,YAAY,aAAa,OAAO,UAAU,GAAoB;AACjG,QAAM,SAAS,aAAa,IAAI,IAAI,UAAU,IAAI;AAClD,QAAM,UAAU,OAAO,OAAO,CAAC,MAAM,cAAc,CAAC,UAAU,OAAO,IAAI,EAAE,GAAG,CAAC;AAC/E,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,SACE,4CAAC,SAAI,eAAW,cAAG,wBAAwB,SAAS,GACjD,kBAAQ,IAAI,CAAC,UAAU;AACtB,UAAM,WAAW,SAAS,CAAC,OAAO,IAAI,MAAM,GAAG,IAAI;AACnD,WACE;AAAA,MAAC;AAAA;AAAA,QAEC,OAAO,WAAW,GAAG,MAAM,WAAW,oCAA+B,MAAM;AAAA,QAC3E,eAAW;AAAA,UACT;AAAA,UACA,YAAY;AAAA,QACd;AAAA,QACA,OAAO;AAAA,UACL,aAAa,GAAG,MAAM,KAAK;AAAA,UAC3B,iBAAiB,GAAG,MAAM,KAAK;AAAA,QACjC;AAAA,QAEA;AAAA,sDAAC,aAAU,MAAM,MAAM,MAAM,OAAO,MAAM,OAAO,WAAU,wBAAuB;AAAA,UAClF,4CAAC,UAAK,WAAU,yBAAwB,OAAO,EAAE,OAAO,MAAM,MAAM,GACjE,gBAAM,MACT;AAAA;AAAA;AAAA,MAdK,MAAM;AAAA,IAeb;AAAA,EAEJ,CAAC,GACH;AAEJ;","names":["React"]}
@@ -0,0 +1,24 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface BadgeShelfBadge {
4
+ key: string;
5
+ name: string;
6
+ description: string;
7
+ icon: string;
8
+ color: string;
9
+ category: string;
10
+ }
11
+ interface BadgeShelfProps {
12
+ /** The badges to render — the earned set, or the full catalog with `earnedKeys`. */
13
+ badges: BadgeShelfBadge[];
14
+ /** When set, badges outside this set render locked (grayscale, reduced opacity). */
15
+ earnedKeys?: string[];
16
+ /** Render locked badges too (requires `earnedKeys`); default hides them. */
17
+ showLocked?: boolean;
18
+ className?: string;
19
+ }
20
+ /** Badge chips with native tooltips. Earned badges render in their color;
21
+ * locked ones (present in `badges` but not `earnedKeys`) render muted. */
22
+ declare function BadgeShelf({ badges, earnedKeys, showLocked, className }: BadgeShelfProps): react_jsx_runtime.JSX.Element | null;
23
+
24
+ export { BadgeShelf, type BadgeShelfBadge, type BadgeShelfProps };
@@ -0,0 +1,24 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface BadgeShelfBadge {
4
+ key: string;
5
+ name: string;
6
+ description: string;
7
+ icon: string;
8
+ color: string;
9
+ category: string;
10
+ }
11
+ interface BadgeShelfProps {
12
+ /** The badges to render — the earned set, or the full catalog with `earnedKeys`. */
13
+ badges: BadgeShelfBadge[];
14
+ /** When set, badges outside this set render locked (grayscale, reduced opacity). */
15
+ earnedKeys?: string[];
16
+ /** Render locked badges too (requires `earnedKeys`); default hides them. */
17
+ showLocked?: boolean;
18
+ className?: string;
19
+ }
20
+ /** Badge chips with native tooltips. Earned badges render in their color;
21
+ * locked ones (present in `badges` but not `earnedKeys`) render muted. */
22
+ declare function BadgeShelf({ badges, earnedKeys, showLocked, className }: BadgeShelfProps): react_jsx_runtime.JSX.Element | null;
23
+
24
+ export { BadgeShelf, type BadgeShelfBadge, type BadgeShelfProps };
@@ -0,0 +1,56 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import React from "react";
4
+ import { cn } from "../../utils/cn.js";
5
+ const iconCache = /* @__PURE__ */ new Map();
6
+ function BadgeIcon({ name, color, className }) {
7
+ const [Icon, setIcon] = React.useState(
8
+ () => iconCache.get(name) ?? null
9
+ );
10
+ React.useEffect(() => {
11
+ if (iconCache.has(name)) {
12
+ setIcon(() => iconCache.get(name));
13
+ return;
14
+ }
15
+ import("lucide-react").then((mod) => {
16
+ const C = mod[name];
17
+ if (C) {
18
+ iconCache.set(name, C);
19
+ setIcon(() => C);
20
+ }
21
+ });
22
+ }, [name]);
23
+ if (!Icon) return /* @__PURE__ */ jsx("span", { className: cn("rounded-full", className), style: { backgroundColor: color } });
24
+ return /* @__PURE__ */ jsx(Icon, { className, style: { color } });
25
+ }
26
+ function BadgeShelf({ badges, earnedKeys, showLocked = false, className }) {
27
+ const earned = earnedKeys ? new Set(earnedKeys) : null;
28
+ const visible = badges.filter((b) => showLocked || !earned || earned.has(b.key));
29
+ if (visible.length === 0) return null;
30
+ return /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap gap-2", className), children: visible.map((badge) => {
31
+ const isLocked = earned ? !earned.has(badge.key) : false;
32
+ return /* @__PURE__ */ jsxs(
33
+ "div",
34
+ {
35
+ title: isLocked ? `${badge.description} \u2014 keep going to earn this` : badge.description,
36
+ className: cn(
37
+ "flex h-7 select-none items-center gap-1.5 rounded-full border px-2.5",
38
+ isLocked && "opacity-45 grayscale"
39
+ ),
40
+ style: {
41
+ borderColor: `${badge.color}60`,
42
+ backgroundColor: `${badge.color}14`
43
+ },
44
+ children: [
45
+ /* @__PURE__ */ jsx(BadgeIcon, { name: badge.icon, color: badge.color, className: "h-3.5 w-3.5 shrink-0" }),
46
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold", style: { color: badge.color }, children: badge.name })
47
+ ]
48
+ },
49
+ badge.key
50
+ );
51
+ }) });
52
+ }
53
+ export {
54
+ BadgeShelf
55
+ };
56
+ //# sourceMappingURL=badge-shelf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/rewards/badge-shelf.tsx"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\nimport type { LucideProps } from \"lucide-react\";\nimport { cn } from \"../../utils/cn.js\";\n\nexport interface BadgeShelfBadge {\n key: string;\n name: string;\n description: string;\n icon: string;\n color: string;\n category: string;\n}\n\nexport interface BadgeShelfProps {\n /** The badges to render — the earned set, or the full catalog with `earnedKeys`. */\n badges: BadgeShelfBadge[];\n /** When set, badges outside this set render locked (grayscale, reduced opacity). */\n earnedKeys?: string[];\n /** Render locked badges too (requires `earnedKeys`); default hides them. */\n showLocked?: boolean;\n className?: string;\n}\n\n// Lazily resolve lucide icons by name at runtime (icon names come from the API).\nconst iconCache = new Map<string, React.ComponentType<LucideProps>>();\n\nfunction BadgeIcon({ name, color, className }: { name: string; color: string; className?: string }) {\n const [Icon, setIcon] = React.useState<React.ComponentType<LucideProps> | null>(\n () => iconCache.get(name) ?? null\n );\n\n React.useEffect(() => {\n if (iconCache.has(name)) {\n setIcon(() => iconCache.get(name)!);\n return;\n }\n import(\"lucide-react\").then((mod) => {\n const C = (mod as Record<string, unknown>)[name] as React.ComponentType<LucideProps> | undefined;\n if (C) {\n iconCache.set(name, C);\n setIcon(() => C);\n }\n });\n }, [name]);\n\n if (!Icon) return <span className={cn(\"rounded-full\", className)} style={{ backgroundColor: color }} />;\n return <Icon className={className} style={{ color }} />;\n}\n\n/** Badge chips with native tooltips. Earned badges render in their color;\n * locked ones (present in `badges` but not `earnedKeys`) render muted. */\nexport function BadgeShelf({ badges, earnedKeys, showLocked = false, className }: BadgeShelfProps) {\n const earned = earnedKeys ? new Set(earnedKeys) : null;\n const visible = badges.filter((b) => showLocked || !earned || earned.has(b.key));\n if (visible.length === 0) return null;\n\n return (\n <div className={cn(\"flex flex-wrap gap-2\", className)}>\n {visible.map((badge) => {\n const isLocked = earned ? !earned.has(badge.key) : false;\n return (\n <div\n key={badge.key}\n title={isLocked ? `${badge.description} — keep going to earn this` : badge.description}\n className={cn(\n \"flex h-7 select-none items-center gap-1.5 rounded-full border px-2.5\",\n isLocked && \"opacity-45 grayscale\"\n )}\n style={{\n borderColor: `${badge.color}60`,\n backgroundColor: `${badge.color}14`,\n }}\n >\n <BadgeIcon name={badge.icon} color={badge.color} className=\"h-3.5 w-3.5 shrink-0\" />\n <span className=\"text-xs font-semibold\" style={{ color: badge.color }}>\n {badge.name}\n </span>\n </div>\n );\n })}\n </div>\n );\n}\n"],"mappings":";AA+CoB,cAgBV,YAhBU;AA7CpB,OAAO,WAAW;AAElB,SAAS,UAAU;AAsBnB,MAAM,YAAY,oBAAI,IAA8C;AAEpE,SAAS,UAAU,EAAE,MAAM,OAAO,UAAU,GAAwD;AAClG,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM;AAAA,IAC5B,MAAM,UAAU,IAAI,IAAI,KAAK;AAAA,EAC/B;AAEA,QAAM,UAAU,MAAM;AACpB,QAAI,UAAU,IAAI,IAAI,GAAG;AACvB,cAAQ,MAAM,UAAU,IAAI,IAAI,CAAE;AAClC;AAAA,IACF;AACA,WAAO,cAAc,EAAE,KAAK,CAAC,QAAQ;AACnC,YAAM,IAAK,IAAgC,IAAI;AAC/C,UAAI,GAAG;AACL,kBAAU,IAAI,MAAM,CAAC;AACrB,gBAAQ,MAAM,CAAC;AAAA,MACjB;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,IAAI,CAAC;AAET,MAAI,CAAC,KAAM,QAAO,oBAAC,UAAK,WAAW,GAAG,gBAAgB,SAAS,GAAG,OAAO,EAAE,iBAAiB,MAAM,GAAG;AACrG,SAAO,oBAAC,QAAK,WAAsB,OAAO,EAAE,MAAM,GAAG;AACvD;AAIO,SAAS,WAAW,EAAE,QAAQ,YAAY,aAAa,OAAO,UAAU,GAAoB;AACjG,QAAM,SAAS,aAAa,IAAI,IAAI,UAAU,IAAI;AAClD,QAAM,UAAU,OAAO,OAAO,CAAC,MAAM,cAAc,CAAC,UAAU,OAAO,IAAI,EAAE,GAAG,CAAC;AAC/E,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,SACE,oBAAC,SAAI,WAAW,GAAG,wBAAwB,SAAS,GACjD,kBAAQ,IAAI,CAAC,UAAU;AACtB,UAAM,WAAW,SAAS,CAAC,OAAO,IAAI,MAAM,GAAG,IAAI;AACnD,WACE;AAAA,MAAC;AAAA;AAAA,QAEC,OAAO,WAAW,GAAG,MAAM,WAAW,oCAA+B,MAAM;AAAA,QAC3E,WAAW;AAAA,UACT;AAAA,UACA,YAAY;AAAA,QACd;AAAA,QACA,OAAO;AAAA,UACL,aAAa,GAAG,MAAM,KAAK;AAAA,UAC3B,iBAAiB,GAAG,MAAM,KAAK;AAAA,QACjC;AAAA,QAEA;AAAA,8BAAC,aAAU,MAAM,MAAM,MAAM,OAAO,MAAM,OAAO,WAAU,wBAAuB;AAAA,UAClF,oBAAC,UAAK,WAAU,yBAAwB,OAAO,EAAE,OAAO,MAAM,MAAM,GACjE,gBAAM,MACT;AAAA;AAAA;AAAA,MAdK,MAAM;AAAA,IAeb;AAAA,EAEJ,CAAC,GACH;AAEJ;","names":[]}
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var leaderboard_table_exports = {};
20
+ __export(leaderboard_table_exports, {
21
+ LeaderboardTable: () => LeaderboardTable,
22
+ LeaderboardWidget: () => LeaderboardWidget
23
+ });
24
+ module.exports = __toCommonJS(leaderboard_table_exports);
25
+ var import_jsx_runtime = require("react/jsx-runtime");
26
+ var import_cn = require("../../utils/cn.js");
27
+ var import_address = require("../../utils/address.js");
28
+ var import_level_badge = require("./level-badge.js");
29
+ function rankAccent(rank) {
30
+ if (rank === 1) return "#f59e0b";
31
+ if (rank === 2) return "#94a3b8";
32
+ if (rank === 3) return "#b45309";
33
+ return null;
34
+ }
35
+ function LeaderboardTable({ entries, highlightAddress, renderAddress, className }) {
36
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: (0, import_cn.cn)("overflow-hidden rounded-xl border border-border", className), children: entries.map((e) => {
37
+ const accent = rankAccent(e.rank);
38
+ const isViewer = highlightAddress != null && e.address === highlightAddress;
39
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
40
+ "div",
41
+ {
42
+ className: (0, import_cn.cn)(
43
+ "flex items-center gap-3 border-b border-border/60 bg-card px-3 py-2.5 last:border-b-0 sm:px-4",
44
+ isViewer && "bg-primary/10"
45
+ ),
46
+ children: [
47
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
48
+ "span",
49
+ {
50
+ className: "w-8 shrink-0 text-center text-sm font-black tabular-nums",
51
+ style: accent ? { color: accent } : void 0,
52
+ children: e.rank
53
+ }
54
+ ),
55
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: [
56
+ renderAddress ? renderAddress(e.address) : (0, import_address.shortenAddress)(e.address),
57
+ isViewer && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-1.5 text-xs text-muted-foreground", children: "(you)" })
58
+ ] }),
59
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_level_badge.LevelBadge, { level: e.currentLevel, name: e.currentLevelName, badgeColor: e.badgeColor, size: "sm", className: "hidden sm:inline-flex" }),
60
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "shrink-0 text-sm font-semibold tabular-nums", children: [
61
+ e.totalXp.toLocaleString(),
62
+ " XP"
63
+ ] })
64
+ ]
65
+ },
66
+ e.address
67
+ );
68
+ }) });
69
+ }
70
+ function LeaderboardWidget({ entries, title = "Top Creators", href, renderAddress, className }) {
71
+ if (entries.length === 0) return null;
72
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: (0, import_cn.cn)("rounded-xl border border-border bg-card p-4 sm:p-5", className), children: [
73
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mb-3 flex items-center justify-between", children: [
74
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-base font-black", children: title }),
75
+ href && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href, className: "text-xs font-semibold text-muted-foreground active:opacity-70", children: "View leaderboard \u2192" })
76
+ ] }),
77
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "space-y-2.5", children: entries.map((e) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: "flex items-center gap-2.5", children: [
78
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
79
+ "span",
80
+ {
81
+ className: "w-5 shrink-0 text-center text-sm font-black tabular-nums",
82
+ style: rankAccent(e.rank) ? { color: rankAccent(e.rank) } : void 0,
83
+ children: e.rank
84
+ }
85
+ ),
86
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: renderAddress ? renderAddress(e.address) : (0, import_address.shortenAddress)(e.address) }),
87
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_level_badge.LevelBadge, { level: e.currentLevel, name: e.currentLevelName, badgeColor: e.badgeColor, size: "sm" })
88
+ ] }, e.address)) })
89
+ ] });
90
+ }
91
+ // Annotate the CommonJS export names for ESM import in node:
92
+ 0 && (module.exports = {
93
+ LeaderboardTable,
94
+ LeaderboardWidget
95
+ });
96
+ //# sourceMappingURL=leaderboard-table.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/rewards/leaderboard-table.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { cn } from \"../../utils/cn.js\";\nimport { shortenAddress } from \"../../utils/address.js\";\nimport { LevelBadge } from \"./level-badge.js\";\n\nexport interface LeaderboardEntryLike {\n rank: number;\n address: string;\n publicId?: string | null;\n totalXp: number;\n currentLevel: number;\n currentLevelName: string;\n badgeColor: string;\n}\n\nexport interface LeaderboardTableProps {\n entries: LeaderboardEntryLike[];\n /** The viewer's address — its row gets highlighted. */\n highlightAddress?: string | null;\n /** Render the address cell (e.g. a profile link). Defaults to a shortened address. */\n renderAddress?: (address: string) => ReactNode;\n className?: string;\n}\n\nfunction rankAccent(rank: number): string | null {\n if (rank === 1) return \"#f59e0b\";\n if (rank === 2) return \"#94a3b8\";\n if (rank === 3) return \"#b45309\";\n return null;\n}\n\n/** Full leaderboard rows: rank, address, level chip, XP. */\nexport function LeaderboardTable({ entries, highlightAddress, renderAddress, className }: LeaderboardTableProps) {\n return (\n <div className={cn(\"overflow-hidden rounded-xl border border-border\", className)}>\n {entries.map((e) => {\n const accent = rankAccent(e.rank);\n const isViewer = highlightAddress != null && e.address === highlightAddress;\n return (\n <div\n key={e.address}\n className={cn(\n \"flex items-center gap-3 border-b border-border/60 bg-card px-3 py-2.5 last:border-b-0 sm:px-4\",\n isViewer && \"bg-primary/10\"\n )}\n >\n <span\n className=\"w-8 shrink-0 text-center text-sm font-black tabular-nums\"\n style={accent ? { color: accent } : undefined}\n >\n {e.rank}\n </span>\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium\">\n {renderAddress ? renderAddress(e.address) : shortenAddress(e.address)}\n {isViewer && <span className=\"ml-1.5 text-xs text-muted-foreground\">(you)</span>}\n </span>\n <LevelBadge level={e.currentLevel} name={e.currentLevelName} badgeColor={e.badgeColor} size=\"sm\" className=\"hidden sm:inline-flex\" />\n <span className=\"shrink-0 text-sm font-semibold tabular-nums\">{e.totalXp.toLocaleString()} XP</span>\n </div>\n );\n })}\n </div>\n );\n}\n\nexport interface LeaderboardWidgetProps {\n entries: LeaderboardEntryLike[];\n title?: string;\n /** Link to the full leaderboard (\"/rewards\"). */\n href?: string;\n renderAddress?: (address: string) => ReactNode;\n className?: string;\n}\n\n/** Compact top-N card for homepage/discover rails. */\nexport function LeaderboardWidget({ entries, title = \"Top Creators\", href, renderAddress, className }: LeaderboardWidgetProps) {\n if (entries.length === 0) return null;\n return (\n <section className={cn(\"rounded-xl border border-border bg-card p-4 sm:p-5\", className)}>\n <div className=\"mb-3 flex items-center justify-between\">\n <h2 className=\"text-base font-black\">{title}</h2>\n {href && (\n <a href={href} className=\"text-xs font-semibold text-muted-foreground active:opacity-70\">\n View leaderboard →\n </a>\n )}\n </div>\n <ol className=\"space-y-2.5\">\n {entries.map((e) => (\n <li key={e.address} className=\"flex items-center gap-2.5\">\n <span\n className=\"w-5 shrink-0 text-center text-sm font-black tabular-nums\"\n style={rankAccent(e.rank) ? { color: rankAccent(e.rank)! } : undefined}\n >\n {e.rank}\n </span>\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium\">\n {renderAddress ? renderAddress(e.address) : shortenAddress(e.address)}\n </span>\n <LevelBadge level={e.currentLevel} name={e.currentLevelName} badgeColor={e.badgeColor} size=\"sm\" />\n </li>\n ))}\n </ol>\n </section>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8CY;AA7CZ,gBAAmB;AACnB,qBAA+B;AAC/B,yBAA2B;AAqB3B,SAAS,WAAW,MAA6B;AAC/C,MAAI,SAAS,EAAG,QAAO;AACvB,MAAI,SAAS,EAAG,QAAO;AACvB,MAAI,SAAS,EAAG,QAAO;AACvB,SAAO;AACT;AAGO,SAAS,iBAAiB,EAAE,SAAS,kBAAkB,eAAe,UAAU,GAA0B;AAC/G,SACE,4CAAC,SAAI,eAAW,cAAG,mDAAmD,SAAS,GAC5E,kBAAQ,IAAI,CAAC,MAAM;AAClB,UAAM,SAAS,WAAW,EAAE,IAAI;AAChC,UAAM,WAAW,oBAAoB,QAAQ,EAAE,YAAY;AAC3D,WACE;AAAA,MAAC;AAAA;AAAA,QAEC,eAAW;AAAA,UACT;AAAA,UACA,YAAY;AAAA,QACd;AAAA,QAEA;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,OAAO,SAAS,EAAE,OAAO,OAAO,IAAI;AAAA,cAEnC,YAAE;AAAA;AAAA,UACL;AAAA,UACA,6CAAC,UAAK,WAAU,+CACb;AAAA,4BAAgB,cAAc,EAAE,OAAO,QAAI,+BAAe,EAAE,OAAO;AAAA,YACnE,YAAY,4CAAC,UAAK,WAAU,wCAAuC,mBAAK;AAAA,aAC3E;AAAA,UACA,4CAAC,iCAAW,OAAO,EAAE,cAAc,MAAM,EAAE,kBAAkB,YAAY,EAAE,YAAY,MAAK,MAAK,WAAU,yBAAwB;AAAA,UACnI,6CAAC,UAAK,WAAU,+CAA+C;AAAA,cAAE,QAAQ,eAAe;AAAA,YAAE;AAAA,aAAG;AAAA;AAAA;AAAA,MAjBxF,EAAE;AAAA,IAkBT;AAAA,EAEJ,CAAC,GACH;AAEJ;AAYO,SAAS,kBAAkB,EAAE,SAAS,QAAQ,gBAAgB,MAAM,eAAe,UAAU,GAA2B;AAC7H,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,SACE,6CAAC,aAAQ,eAAW,cAAG,sDAAsD,SAAS,GACpF;AAAA,iDAAC,SAAI,WAAU,0CACb;AAAA,kDAAC,QAAG,WAAU,wBAAwB,iBAAM;AAAA,MAC3C,QACC,4CAAC,OAAE,MAAY,WAAU,iEAAgE,qCAEzF;AAAA,OAEJ;AAAA,IACA,4CAAC,QAAG,WAAU,eACX,kBAAQ,IAAI,CAAC,MACZ,6CAAC,QAAmB,WAAU,6BAC5B;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,OAAO,WAAW,EAAE,IAAI,IAAI,EAAE,OAAO,WAAW,EAAE,IAAI,EAAG,IAAI;AAAA,UAE5D,YAAE;AAAA;AAAA,MACL;AAAA,MACA,4CAAC,UAAK,WAAU,+CACb,0BAAgB,cAAc,EAAE,OAAO,QAAI,+BAAe,EAAE,OAAO,GACtE;AAAA,MACA,4CAAC,iCAAW,OAAO,EAAE,cAAc,MAAM,EAAE,kBAAkB,YAAY,EAAE,YAAY,MAAK,MAAK;AAAA,SAV1F,EAAE,OAWX,CACD,GACH;AAAA,KACF;AAEJ;","names":[]}
@@ -0,0 +1,34 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ interface LeaderboardEntryLike {
5
+ rank: number;
6
+ address: string;
7
+ publicId?: string | null;
8
+ totalXp: number;
9
+ currentLevel: number;
10
+ currentLevelName: string;
11
+ badgeColor: string;
12
+ }
13
+ interface LeaderboardTableProps {
14
+ entries: LeaderboardEntryLike[];
15
+ /** The viewer's address — its row gets highlighted. */
16
+ highlightAddress?: string | null;
17
+ /** Render the address cell (e.g. a profile link). Defaults to a shortened address. */
18
+ renderAddress?: (address: string) => ReactNode;
19
+ className?: string;
20
+ }
21
+ /** Full leaderboard rows: rank, address, level chip, XP. */
22
+ declare function LeaderboardTable({ entries, highlightAddress, renderAddress, className }: LeaderboardTableProps): react_jsx_runtime.JSX.Element;
23
+ interface LeaderboardWidgetProps {
24
+ entries: LeaderboardEntryLike[];
25
+ title?: string;
26
+ /** Link to the full leaderboard ("/rewards"). */
27
+ href?: string;
28
+ renderAddress?: (address: string) => ReactNode;
29
+ className?: string;
30
+ }
31
+ /** Compact top-N card for homepage/discover rails. */
32
+ declare function LeaderboardWidget({ entries, title, href, renderAddress, className }: LeaderboardWidgetProps): react_jsx_runtime.JSX.Element | null;
33
+
34
+ export { type LeaderboardEntryLike, LeaderboardTable, type LeaderboardTableProps, LeaderboardWidget, type LeaderboardWidgetProps };
@@ -0,0 +1,34 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ interface LeaderboardEntryLike {
5
+ rank: number;
6
+ address: string;
7
+ publicId?: string | null;
8
+ totalXp: number;
9
+ currentLevel: number;
10
+ currentLevelName: string;
11
+ badgeColor: string;
12
+ }
13
+ interface LeaderboardTableProps {
14
+ entries: LeaderboardEntryLike[];
15
+ /** The viewer's address — its row gets highlighted. */
16
+ highlightAddress?: string | null;
17
+ /** Render the address cell (e.g. a profile link). Defaults to a shortened address. */
18
+ renderAddress?: (address: string) => ReactNode;
19
+ className?: string;
20
+ }
21
+ /** Full leaderboard rows: rank, address, level chip, XP. */
22
+ declare function LeaderboardTable({ entries, highlightAddress, renderAddress, className }: LeaderboardTableProps): react_jsx_runtime.JSX.Element;
23
+ interface LeaderboardWidgetProps {
24
+ entries: LeaderboardEntryLike[];
25
+ title?: string;
26
+ /** Link to the full leaderboard ("/rewards"). */
27
+ href?: string;
28
+ renderAddress?: (address: string) => ReactNode;
29
+ className?: string;
30
+ }
31
+ /** Compact top-N card for homepage/discover rails. */
32
+ declare function LeaderboardWidget({ entries, title, href, renderAddress, className }: LeaderboardWidgetProps): react_jsx_runtime.JSX.Element | null;
33
+
34
+ export { type LeaderboardEntryLike, LeaderboardTable, type LeaderboardTableProps, LeaderboardWidget, type LeaderboardWidgetProps };
@@ -0,0 +1,71 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../../utils/cn.js";
3
+ import { shortenAddress } from "../../utils/address.js";
4
+ import { LevelBadge } from "./level-badge.js";
5
+ function rankAccent(rank) {
6
+ if (rank === 1) return "#f59e0b";
7
+ if (rank === 2) return "#94a3b8";
8
+ if (rank === 3) return "#b45309";
9
+ return null;
10
+ }
11
+ function LeaderboardTable({ entries, highlightAddress, renderAddress, className }) {
12
+ return /* @__PURE__ */ jsx("div", { className: cn("overflow-hidden rounded-xl border border-border", className), children: entries.map((e) => {
13
+ const accent = rankAccent(e.rank);
14
+ const isViewer = highlightAddress != null && e.address === highlightAddress;
15
+ return /* @__PURE__ */ jsxs(
16
+ "div",
17
+ {
18
+ className: cn(
19
+ "flex items-center gap-3 border-b border-border/60 bg-card px-3 py-2.5 last:border-b-0 sm:px-4",
20
+ isViewer && "bg-primary/10"
21
+ ),
22
+ children: [
23
+ /* @__PURE__ */ jsx(
24
+ "span",
25
+ {
26
+ className: "w-8 shrink-0 text-center text-sm font-black tabular-nums",
27
+ style: accent ? { color: accent } : void 0,
28
+ children: e.rank
29
+ }
30
+ ),
31
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: [
32
+ renderAddress ? renderAddress(e.address) : shortenAddress(e.address),
33
+ isViewer && /* @__PURE__ */ jsx("span", { className: "ml-1.5 text-xs text-muted-foreground", children: "(you)" })
34
+ ] }),
35
+ /* @__PURE__ */ jsx(LevelBadge, { level: e.currentLevel, name: e.currentLevelName, badgeColor: e.badgeColor, size: "sm", className: "hidden sm:inline-flex" }),
36
+ /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-sm font-semibold tabular-nums", children: [
37
+ e.totalXp.toLocaleString(),
38
+ " XP"
39
+ ] })
40
+ ]
41
+ },
42
+ e.address
43
+ );
44
+ }) });
45
+ }
46
+ function LeaderboardWidget({ entries, title = "Top Creators", href, renderAddress, className }) {
47
+ if (entries.length === 0) return null;
48
+ return /* @__PURE__ */ jsxs("section", { className: cn("rounded-xl border border-border bg-card p-4 sm:p-5", className), children: [
49
+ /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between", children: [
50
+ /* @__PURE__ */ jsx("h2", { className: "text-base font-black", children: title }),
51
+ href && /* @__PURE__ */ jsx("a", { href, className: "text-xs font-semibold text-muted-foreground active:opacity-70", children: "View leaderboard \u2192" })
52
+ ] }),
53
+ /* @__PURE__ */ jsx("ol", { className: "space-y-2.5", children: entries.map((e) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2.5", children: [
54
+ /* @__PURE__ */ jsx(
55
+ "span",
56
+ {
57
+ className: "w-5 shrink-0 text-center text-sm font-black tabular-nums",
58
+ style: rankAccent(e.rank) ? { color: rankAccent(e.rank) } : void 0,
59
+ children: e.rank
60
+ }
61
+ ),
62
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: renderAddress ? renderAddress(e.address) : shortenAddress(e.address) }),
63
+ /* @__PURE__ */ jsx(LevelBadge, { level: e.currentLevel, name: e.currentLevelName, badgeColor: e.badgeColor, size: "sm" })
64
+ ] }, e.address)) })
65
+ ] });
66
+ }
67
+ export {
68
+ LeaderboardTable,
69
+ LeaderboardWidget
70
+ };
71
+ //# sourceMappingURL=leaderboard-table.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/rewards/leaderboard-table.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { cn } from \"../../utils/cn.js\";\nimport { shortenAddress } from \"../../utils/address.js\";\nimport { LevelBadge } from \"./level-badge.js\";\n\nexport interface LeaderboardEntryLike {\n rank: number;\n address: string;\n publicId?: string | null;\n totalXp: number;\n currentLevel: number;\n currentLevelName: string;\n badgeColor: string;\n}\n\nexport interface LeaderboardTableProps {\n entries: LeaderboardEntryLike[];\n /** The viewer's address — its row gets highlighted. */\n highlightAddress?: string | null;\n /** Render the address cell (e.g. a profile link). Defaults to a shortened address. */\n renderAddress?: (address: string) => ReactNode;\n className?: string;\n}\n\nfunction rankAccent(rank: number): string | null {\n if (rank === 1) return \"#f59e0b\";\n if (rank === 2) return \"#94a3b8\";\n if (rank === 3) return \"#b45309\";\n return null;\n}\n\n/** Full leaderboard rows: rank, address, level chip, XP. */\nexport function LeaderboardTable({ entries, highlightAddress, renderAddress, className }: LeaderboardTableProps) {\n return (\n <div className={cn(\"overflow-hidden rounded-xl border border-border\", className)}>\n {entries.map((e) => {\n const accent = rankAccent(e.rank);\n const isViewer = highlightAddress != null && e.address === highlightAddress;\n return (\n <div\n key={e.address}\n className={cn(\n \"flex items-center gap-3 border-b border-border/60 bg-card px-3 py-2.5 last:border-b-0 sm:px-4\",\n isViewer && \"bg-primary/10\"\n )}\n >\n <span\n className=\"w-8 shrink-0 text-center text-sm font-black tabular-nums\"\n style={accent ? { color: accent } : undefined}\n >\n {e.rank}\n </span>\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium\">\n {renderAddress ? renderAddress(e.address) : shortenAddress(e.address)}\n {isViewer && <span className=\"ml-1.5 text-xs text-muted-foreground\">(you)</span>}\n </span>\n <LevelBadge level={e.currentLevel} name={e.currentLevelName} badgeColor={e.badgeColor} size=\"sm\" className=\"hidden sm:inline-flex\" />\n <span className=\"shrink-0 text-sm font-semibold tabular-nums\">{e.totalXp.toLocaleString()} XP</span>\n </div>\n );\n })}\n </div>\n );\n}\n\nexport interface LeaderboardWidgetProps {\n entries: LeaderboardEntryLike[];\n title?: string;\n /** Link to the full leaderboard (\"/rewards\"). */\n href?: string;\n renderAddress?: (address: string) => ReactNode;\n className?: string;\n}\n\n/** Compact top-N card for homepage/discover rails. */\nexport function LeaderboardWidget({ entries, title = \"Top Creators\", href, renderAddress, className }: LeaderboardWidgetProps) {\n if (entries.length === 0) return null;\n return (\n <section className={cn(\"rounded-xl border border-border bg-card p-4 sm:p-5\", className)}>\n <div className=\"mb-3 flex items-center justify-between\">\n <h2 className=\"text-base font-black\">{title}</h2>\n {href && (\n <a href={href} className=\"text-xs font-semibold text-muted-foreground active:opacity-70\">\n View leaderboard →\n </a>\n )}\n </div>\n <ol className=\"space-y-2.5\">\n {entries.map((e) => (\n <li key={e.address} className=\"flex items-center gap-2.5\">\n <span\n className=\"w-5 shrink-0 text-center text-sm font-black tabular-nums\"\n style={rankAccent(e.rank) ? { color: rankAccent(e.rank)! } : undefined}\n >\n {e.rank}\n </span>\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium\">\n {renderAddress ? renderAddress(e.address) : shortenAddress(e.address)}\n </span>\n <LevelBadge level={e.currentLevel} name={e.currentLevelName} badgeColor={e.badgeColor} size=\"sm\" />\n </li>\n ))}\n </ol>\n </section>\n );\n}\n"],"mappings":"AA8CY,cAMA,YANA;AA7CZ,SAAS,UAAU;AACnB,SAAS,sBAAsB;AAC/B,SAAS,kBAAkB;AAqB3B,SAAS,WAAW,MAA6B;AAC/C,MAAI,SAAS,EAAG,QAAO;AACvB,MAAI,SAAS,EAAG,QAAO;AACvB,MAAI,SAAS,EAAG,QAAO;AACvB,SAAO;AACT;AAGO,SAAS,iBAAiB,EAAE,SAAS,kBAAkB,eAAe,UAAU,GAA0B;AAC/G,SACE,oBAAC,SAAI,WAAW,GAAG,mDAAmD,SAAS,GAC5E,kBAAQ,IAAI,CAAC,MAAM;AAClB,UAAM,SAAS,WAAW,EAAE,IAAI;AAChC,UAAM,WAAW,oBAAoB,QAAQ,EAAE,YAAY;AAC3D,WACE;AAAA,MAAC;AAAA;AAAA,QAEC,WAAW;AAAA,UACT;AAAA,UACA,YAAY;AAAA,QACd;AAAA,QAEA;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,OAAO,SAAS,EAAE,OAAO,OAAO,IAAI;AAAA,cAEnC,YAAE;AAAA;AAAA,UACL;AAAA,UACA,qBAAC,UAAK,WAAU,+CACb;AAAA,4BAAgB,cAAc,EAAE,OAAO,IAAI,eAAe,EAAE,OAAO;AAAA,YACnE,YAAY,oBAAC,UAAK,WAAU,wCAAuC,mBAAK;AAAA,aAC3E;AAAA,UACA,oBAAC,cAAW,OAAO,EAAE,cAAc,MAAM,EAAE,kBAAkB,YAAY,EAAE,YAAY,MAAK,MAAK,WAAU,yBAAwB;AAAA,UACnI,qBAAC,UAAK,WAAU,+CAA+C;AAAA,cAAE,QAAQ,eAAe;AAAA,YAAE;AAAA,aAAG;AAAA;AAAA;AAAA,MAjBxF,EAAE;AAAA,IAkBT;AAAA,EAEJ,CAAC,GACH;AAEJ;AAYO,SAAS,kBAAkB,EAAE,SAAS,QAAQ,gBAAgB,MAAM,eAAe,UAAU,GAA2B;AAC7H,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,SACE,qBAAC,aAAQ,WAAW,GAAG,sDAAsD,SAAS,GACpF;AAAA,yBAAC,SAAI,WAAU,0CACb;AAAA,0BAAC,QAAG,WAAU,wBAAwB,iBAAM;AAAA,MAC3C,QACC,oBAAC,OAAE,MAAY,WAAU,iEAAgE,qCAEzF;AAAA,OAEJ;AAAA,IACA,oBAAC,QAAG,WAAU,eACX,kBAAQ,IAAI,CAAC,MACZ,qBAAC,QAAmB,WAAU,6BAC5B;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,OAAO,WAAW,EAAE,IAAI,IAAI,EAAE,OAAO,WAAW,EAAE,IAAI,EAAG,IAAI;AAAA,UAE5D,YAAE;AAAA;AAAA,MACL;AAAA,MACA,oBAAC,UAAK,WAAU,+CACb,0BAAgB,cAAc,EAAE,OAAO,IAAI,eAAe,EAAE,OAAO,GACtE;AAAA,MACA,oBAAC,cAAW,OAAO,EAAE,cAAc,MAAM,EAAE,kBAAkB,YAAY,EAAE,YAAY,MAAK,MAAK;AAAA,SAV1F,EAAE,OAWX,CACD,GACH;AAAA,KACF;AAEJ;","names":[]}