@medialane/ui 0.133.0 → 0.134.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/components/badge.d.cts +1 -1
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button.d.cts +2 -2
- package/dist/components/button.d.ts +2 -2
- package/dist/components/coin-row.cjs +126 -0
- package/dist/components/coin-row.cjs.map +1 -0
- package/dist/components/coin-row.d.cts +22 -0
- package/dist/components/coin-row.d.ts +22 -0
- package/dist/components/coin-row.js +93 -0
- package/dist/components/coin-row.js.map +1 -0
- package/dist/components/coins-explorer.cjs +46 -42
- package/dist/components/coins-explorer.cjs.map +1 -1
- package/dist/components/coins-explorer.d.cts +1 -1
- package/dist/components/coins-explorer.d.ts +1 -1
- package/dist/components/coins-explorer.js +47 -43
- package/dist/components/coins-explorer.js.map +1 -1
- package/dist/data/coins.cjs +33 -8
- package/dist/data/coins.cjs.map +1 -1
- package/dist/data/coins.d.cts +4 -1
- package/dist/data/coins.d.ts +4 -1
- package/dist/data/coins.js +29 -7
- package/dist/data/coins.js.map +1 -1
- package/dist/index.cjs +18 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +14 -5
- package/dist/index.js.map +1 -1
- package/dist/utils/format.cjs +20 -0
- package/dist/utils/format.cjs.map +1 -1
- package/dist/utils/format.d.cts +3 -1
- package/dist/utils/format.d.ts +3 -1
- package/dist/utils/format.js +18 -0
- package/dist/utils/format.js.map +1 -1
- package/package.json +1 -1
- package/dist/components/coin-card.cjs +0 -141
- package/dist/components/coin-card.cjs.map +0 -1
- package/dist/components/coin-card.d.cts +0 -17
- package/dist/components/coin-card.d.ts +0 -17
- package/dist/components/coin-card.js +0 -108
- package/dist/components/coin-card.js.map +0 -1
|
@@ -4,7 +4,7 @@ import * as React from 'react';
|
|
|
4
4
|
import { VariantProps } from 'class-variance-authority';
|
|
5
5
|
|
|
6
6
|
declare const badgeVariants: (props?: ({
|
|
7
|
-
variant?: "
|
|
7
|
+
variant?: "default" | "destructive" | "outline" | "secondary" | null | undefined;
|
|
8
8
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
9
9
|
interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {
|
|
10
10
|
}
|
|
@@ -4,7 +4,7 @@ import * as React from 'react';
|
|
|
4
4
|
import { VariantProps } from 'class-variance-authority';
|
|
5
5
|
|
|
6
6
|
declare const badgeVariants: (props?: ({
|
|
7
|
-
variant?: "
|
|
7
|
+
variant?: "default" | "destructive" | "outline" | "secondary" | null | undefined;
|
|
8
8
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
9
9
|
interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {
|
|
10
10
|
}
|
|
@@ -3,8 +3,8 @@ import * as React from 'react';
|
|
|
3
3
|
import { VariantProps } from 'class-variance-authority';
|
|
4
4
|
|
|
5
5
|
declare const buttonVariants: (props?: ({
|
|
6
|
-
variant?: "link" | "
|
|
7
|
-
size?: "
|
|
6
|
+
variant?: "link" | "default" | "destructive" | "outline" | "ghost" | "secondary" | "premium" | "tonal" | "gradient-border" | "gradient-fill" | null | undefined;
|
|
7
|
+
size?: "icon" | "default" | "sm" | "lg" | null | undefined;
|
|
8
8
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
9
9
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
|
10
10
|
asChild?: boolean;
|
|
@@ -3,8 +3,8 @@ import * as React from 'react';
|
|
|
3
3
|
import { VariantProps } from 'class-variance-authority';
|
|
4
4
|
|
|
5
5
|
declare const buttonVariants: (props?: ({
|
|
6
|
-
variant?: "link" | "
|
|
7
|
-
size?: "
|
|
6
|
+
variant?: "link" | "default" | "destructive" | "outline" | "ghost" | "secondary" | "premium" | "tonal" | "gradient-border" | "gradient-fill" | null | undefined;
|
|
7
|
+
size?: "icon" | "default" | "sm" | "lg" | null | undefined;
|
|
8
8
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
9
9
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
|
10
10
|
asChild?: boolean;
|
|
@@ -0,0 +1,126 @@
|
|
|
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 coin_row_exports = {};
|
|
31
|
+
__export(coin_row_exports, {
|
|
32
|
+
COIN_GRID: () => COIN_GRID,
|
|
33
|
+
CoinAvatar: () => CoinAvatar,
|
|
34
|
+
CoinRow: () => CoinRow,
|
|
35
|
+
CoinRowSkeleton: () => CoinRowSkeleton
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(coin_row_exports);
|
|
38
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
39
|
+
var import_link = __toESM(require("./link.js"), 1);
|
|
40
|
+
var import_image = __toESM(require("./image.js"), 1);
|
|
41
|
+
var import_cn = require("../utils/cn.js");
|
|
42
|
+
var import_ipfs = require("../utils/ipfs.js");
|
|
43
|
+
var import_currency_icon = require("./currency-icon.js");
|
|
44
|
+
var import_coins = require("../data/coins.js");
|
|
45
|
+
var import_format = require("../utils/format.js");
|
|
46
|
+
const COIN_GRID = "grid grid-cols-[minmax(0,1fr)_8rem] items-center gap-3 sm:gap-4";
|
|
47
|
+
const kindLabel = (kind) => kind === "creator" ? "Creator Coin" : "Memecoin";
|
|
48
|
+
function CoinAvatar({ collection, size = 36 }) {
|
|
49
|
+
const logoUri = collection.profile?.image ?? collection.image;
|
|
50
|
+
const logo = logoUri ? (0, import_ipfs.ipfsToHttp)(logoUri) : null;
|
|
51
|
+
const initials = (collection.symbol ?? collection.name ?? "?").trim().slice(0, 2).toUpperCase();
|
|
52
|
+
const hue = (0, import_coins.coinAccentHue)(collection.symbol ?? collection.name ?? collection.contractAddress);
|
|
53
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
54
|
+
"div",
|
|
55
|
+
{
|
|
56
|
+
className: "relative shrink-0 overflow-hidden rounded-full",
|
|
57
|
+
style: { width: size, height: size, backgroundColor: `hsl(${hue} 70% 52%)` },
|
|
58
|
+
children: logo ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_image.default, { src: logo, alt: "", fill: true, sizes: `${size}px`, className: "object-cover", unoptimized: true }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
59
|
+
"span",
|
|
60
|
+
{
|
|
61
|
+
className: "flex h-full w-full items-center justify-center font-bold leading-none text-white",
|
|
62
|
+
style: { fontSize: size * 0.36 },
|
|
63
|
+
children: initials
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
function PriceCell({ price, isLoading }) {
|
|
70
|
+
if (isLoading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20" });
|
|
71
|
+
if (!price) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "block text-right text-muted-foreground", children: "\u2014" });
|
|
72
|
+
const usd = price.quoteUsdRate != null ? (0, import_format.formatUsdPrice)(price.quotePerCoin * price.quoteUsdRate) : null;
|
|
73
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "block text-right leading-tight", children: [
|
|
74
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "block font-semibold tabular-nums", children: usd ?? (0, import_coins.formatCoinPrice)(price.quotePerCoin) }),
|
|
75
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "mt-0.5 inline-flex items-center justify-end gap-1 text-2xs tabular-nums text-muted-foreground", children: [
|
|
76
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_currency_icon.CurrencyIcon, { symbol: price.quoteSymbol, size: 10 }),
|
|
77
|
+
(0, import_coins.formatCoinPrice)(price.quotePerCoin)
|
|
78
|
+
] })
|
|
79
|
+
] });
|
|
80
|
+
}
|
|
81
|
+
function CoinRow({ collection, usePrice, href, showKind = false }) {
|
|
82
|
+
const { price, isLoading } = usePrice(collection);
|
|
83
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
84
|
+
import_link.default,
|
|
85
|
+
{
|
|
86
|
+
href,
|
|
87
|
+
className: (0, import_cn.cn)(COIN_GRID, "border-b border-border/40 px-2 py-2.5 text-sm transition-colors hover:bg-muted/40"),
|
|
88
|
+
children: [
|
|
89
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex min-w-0 items-center gap-3", children: [
|
|
90
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CoinAvatar, { collection }),
|
|
91
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "min-w-0", children: [
|
|
92
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "block truncate font-semibold leading-tight", children: collection.name ?? "Untitled coin" }),
|
|
93
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "block truncate text-xs text-muted-foreground", children: [
|
|
94
|
+
collection.symbol ?? "\u2014",
|
|
95
|
+
showKind && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-muted-foreground/60", children: [
|
|
96
|
+
" \xB7 ",
|
|
97
|
+
kindLabel((0, import_coins.coinKind)(collection.service))
|
|
98
|
+
] })
|
|
99
|
+
] })
|
|
100
|
+
] })
|
|
101
|
+
] }),
|
|
102
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PriceCell, { price, isLoading })
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
function CoinRowSkeleton() {
|
|
108
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: (0, import_cn.cn)(COIN_GRID, "border-b border-border/40 px-2 py-2.5"), children: [
|
|
109
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex items-center gap-3", children: [
|
|
110
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "h-9 w-9 shrink-0 animate-pulse rounded-full bg-muted-foreground/20" }),
|
|
111
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "space-y-1.5", children: [
|
|
112
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "block h-4 w-28 animate-pulse rounded bg-muted-foreground/20" }),
|
|
113
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "block h-3 w-14 animate-pulse rounded bg-muted-foreground/10" })
|
|
114
|
+
] })
|
|
115
|
+
] }),
|
|
116
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20" })
|
|
117
|
+
] });
|
|
118
|
+
}
|
|
119
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
120
|
+
0 && (module.exports = {
|
|
121
|
+
COIN_GRID,
|
|
122
|
+
CoinAvatar,
|
|
123
|
+
CoinRow,
|
|
124
|
+
CoinRowSkeleton
|
|
125
|
+
});
|
|
126
|
+
//# sourceMappingURL=coin-row.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/coin-row.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"./link.js\";\nimport Image from \"./image.js\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { CurrencyIcon } from \"./currency-icon.js\";\nimport {\n coinKind, coinAccentHue, formatCoinPrice,\n type CoinCollectionLike, type CoinPriceLike,\n} from \"../data/coins.js\";\nimport { formatUsdPrice } from \"../utils/format.js\";\n\nexport type UseCoinPrice = (collection: CoinCollectionLike) => {\n price: CoinPriceLike | null;\n isLoading: boolean;\n};\n\nexport const COIN_GRID = \"grid grid-cols-[minmax(0,1fr)_8rem] items-center gap-3 sm:gap-4\";\n\nconst kindLabel = (kind: string) => (kind === \"creator\" ? \"Creator Coin\" : \"Memecoin\");\n\nexport interface CoinRowProps {\n collection: CoinCollectionLike;\n usePrice: UseCoinPrice;\n href: string;\n showKind?: boolean;\n}\n\nexport function CoinAvatar({ collection, size = 36 }: { collection: CoinCollectionLike; size?: number }) {\n const logoUri = collection.profile?.image ?? collection.image;\n const logo = logoUri ? ipfsToHttp(logoUri) : null;\n const initials = (collection.symbol ?? collection.name ?? \"?\").trim().slice(0, 2).toUpperCase();\n const hue = coinAccentHue(collection.symbol ?? collection.name ?? collection.contractAddress);\n\n return (\n <div\n className=\"relative shrink-0 overflow-hidden rounded-full\"\n style={{ width: size, height: size, backgroundColor: `hsl(${hue} 70% 52%)` }}\n >\n {logo ? (\n <Image src={logo} alt=\"\" fill sizes={`${size}px`} className=\"object-cover\" unoptimized />\n ) : (\n <span\n className=\"flex h-full w-full items-center justify-center font-bold leading-none text-white\"\n style={{ fontSize: size * 0.36 }}\n >\n {initials}\n </span>\n )}\n </div>\n );\n}\n\nfunction PriceCell({ price, isLoading }: { price: CoinPriceLike | null; isLoading: boolean }) {\n if (isLoading) return <span className=\"ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20\" />;\n if (!price) return <span className=\"block text-right text-muted-foreground\">—</span>;\n\n const usd = price.quoteUsdRate != null ? formatUsdPrice(price.quotePerCoin * price.quoteUsdRate) : null;\n\n return (\n <span className=\"block text-right leading-tight\">\n <span className=\"block font-semibold tabular-nums\">{usd ?? formatCoinPrice(price.quotePerCoin)}</span>\n <span className=\"mt-0.5 inline-flex items-center justify-end gap-1 text-2xs tabular-nums text-muted-foreground\">\n <CurrencyIcon symbol={price.quoteSymbol} size={10} />\n {formatCoinPrice(price.quotePerCoin)}\n </span>\n </span>\n );\n}\n\nexport function CoinRow({ collection, usePrice, href, showKind = false }: CoinRowProps) {\n const { price, isLoading } = usePrice(collection);\n\n return (\n <Link\n href={href}\n className={cn(COIN_GRID, \"border-b border-border/40 px-2 py-2.5 text-sm transition-colors hover:bg-muted/40\")}\n >\n <span className=\"flex min-w-0 items-center gap-3\">\n <CoinAvatar collection={collection} />\n <span className=\"min-w-0\">\n <span className=\"block truncate font-semibold leading-tight\">{collection.name ?? \"Untitled coin\"}</span>\n <span className=\"block truncate text-xs text-muted-foreground\">\n {collection.symbol ?? \"—\"}\n {showKind && <span className=\"text-muted-foreground/60\"> · {kindLabel(coinKind(collection.service))}</span>}\n </span>\n </span>\n </span>\n\n <PriceCell price={price} isLoading={isLoading} />\n </Link>\n );\n}\n\nexport function CoinRowSkeleton() {\n return (\n <div className={cn(COIN_GRID, \"border-b border-border/40 px-2 py-2.5\")}>\n <span className=\"flex items-center gap-3\">\n <span className=\"h-9 w-9 shrink-0 animate-pulse rounded-full bg-muted-foreground/20\" />\n <span className=\"space-y-1.5\">\n <span className=\"block h-4 w-28 animate-pulse rounded bg-muted-foreground/20\" />\n <span className=\"block h-3 w-14 animate-pulse rounded bg-muted-foreground/10\" />\n </span>\n </span>\n <span className=\"ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20\" />\n </div>\n );\n}\n"],"mappings":"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","names":["Image","Link"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { CoinCollectionLike, CoinPriceLike } from '../data/coins.cjs';
|
|
3
|
+
|
|
4
|
+
type UseCoinPrice = (collection: CoinCollectionLike) => {
|
|
5
|
+
price: CoinPriceLike | null;
|
|
6
|
+
isLoading: boolean;
|
|
7
|
+
};
|
|
8
|
+
declare const COIN_GRID = "grid grid-cols-[minmax(0,1fr)_8rem] items-center gap-3 sm:gap-4";
|
|
9
|
+
interface CoinRowProps {
|
|
10
|
+
collection: CoinCollectionLike;
|
|
11
|
+
usePrice: UseCoinPrice;
|
|
12
|
+
href: string;
|
|
13
|
+
showKind?: boolean;
|
|
14
|
+
}
|
|
15
|
+
declare function CoinAvatar({ collection, size }: {
|
|
16
|
+
collection: CoinCollectionLike;
|
|
17
|
+
size?: number;
|
|
18
|
+
}): react_jsx_runtime.JSX.Element;
|
|
19
|
+
declare function CoinRow({ collection, usePrice, href, showKind }: CoinRowProps): react_jsx_runtime.JSX.Element;
|
|
20
|
+
declare function CoinRowSkeleton(): react_jsx_runtime.JSX.Element;
|
|
21
|
+
|
|
22
|
+
export { COIN_GRID, CoinAvatar, CoinRow, type CoinRowProps, CoinRowSkeleton, type UseCoinPrice };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { CoinCollectionLike, CoinPriceLike } from '../data/coins.js';
|
|
3
|
+
|
|
4
|
+
type UseCoinPrice = (collection: CoinCollectionLike) => {
|
|
5
|
+
price: CoinPriceLike | null;
|
|
6
|
+
isLoading: boolean;
|
|
7
|
+
};
|
|
8
|
+
declare const COIN_GRID = "grid grid-cols-[minmax(0,1fr)_8rem] items-center gap-3 sm:gap-4";
|
|
9
|
+
interface CoinRowProps {
|
|
10
|
+
collection: CoinCollectionLike;
|
|
11
|
+
usePrice: UseCoinPrice;
|
|
12
|
+
href: string;
|
|
13
|
+
showKind?: boolean;
|
|
14
|
+
}
|
|
15
|
+
declare function CoinAvatar({ collection, size }: {
|
|
16
|
+
collection: CoinCollectionLike;
|
|
17
|
+
size?: number;
|
|
18
|
+
}): react_jsx_runtime.JSX.Element;
|
|
19
|
+
declare function CoinRow({ collection, usePrice, href, showKind }: CoinRowProps): react_jsx_runtime.JSX.Element;
|
|
20
|
+
declare function CoinRowSkeleton(): react_jsx_runtime.JSX.Element;
|
|
21
|
+
|
|
22
|
+
export { COIN_GRID, CoinAvatar, CoinRow, type CoinRowProps, CoinRowSkeleton, type UseCoinPrice };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import Link from "./link.js";
|
|
4
|
+
import Image from "./image.js";
|
|
5
|
+
import { cn } from "../utils/cn.js";
|
|
6
|
+
import { ipfsToHttp } from "../utils/ipfs.js";
|
|
7
|
+
import { CurrencyIcon } from "./currency-icon.js";
|
|
8
|
+
import {
|
|
9
|
+
coinKind,
|
|
10
|
+
coinAccentHue,
|
|
11
|
+
formatCoinPrice
|
|
12
|
+
} from "../data/coins.js";
|
|
13
|
+
import { formatUsdPrice } from "../utils/format.js";
|
|
14
|
+
const COIN_GRID = "grid grid-cols-[minmax(0,1fr)_8rem] items-center gap-3 sm:gap-4";
|
|
15
|
+
const kindLabel = (kind) => kind === "creator" ? "Creator Coin" : "Memecoin";
|
|
16
|
+
function CoinAvatar({ collection, size = 36 }) {
|
|
17
|
+
const logoUri = collection.profile?.image ?? collection.image;
|
|
18
|
+
const logo = logoUri ? ipfsToHttp(logoUri) : null;
|
|
19
|
+
const initials = (collection.symbol ?? collection.name ?? "?").trim().slice(0, 2).toUpperCase();
|
|
20
|
+
const hue = coinAccentHue(collection.symbol ?? collection.name ?? collection.contractAddress);
|
|
21
|
+
return /* @__PURE__ */ jsx(
|
|
22
|
+
"div",
|
|
23
|
+
{
|
|
24
|
+
className: "relative shrink-0 overflow-hidden rounded-full",
|
|
25
|
+
style: { width: size, height: size, backgroundColor: `hsl(${hue} 70% 52%)` },
|
|
26
|
+
children: logo ? /* @__PURE__ */ jsx(Image, { src: logo, alt: "", fill: true, sizes: `${size}px`, className: "object-cover", unoptimized: true }) : /* @__PURE__ */ jsx(
|
|
27
|
+
"span",
|
|
28
|
+
{
|
|
29
|
+
className: "flex h-full w-full items-center justify-center font-bold leading-none text-white",
|
|
30
|
+
style: { fontSize: size * 0.36 },
|
|
31
|
+
children: initials
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
function PriceCell({ price, isLoading }) {
|
|
38
|
+
if (isLoading) return /* @__PURE__ */ jsx("span", { className: "ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20" });
|
|
39
|
+
if (!price) return /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: "\u2014" });
|
|
40
|
+
const usd = price.quoteUsdRate != null ? formatUsdPrice(price.quotePerCoin * price.quoteUsdRate) : null;
|
|
41
|
+
return /* @__PURE__ */ jsxs("span", { className: "block text-right leading-tight", children: [
|
|
42
|
+
/* @__PURE__ */ jsx("span", { className: "block font-semibold tabular-nums", children: usd ?? formatCoinPrice(price.quotePerCoin) }),
|
|
43
|
+
/* @__PURE__ */ jsxs("span", { className: "mt-0.5 inline-flex items-center justify-end gap-1 text-2xs tabular-nums text-muted-foreground", children: [
|
|
44
|
+
/* @__PURE__ */ jsx(CurrencyIcon, { symbol: price.quoteSymbol, size: 10 }),
|
|
45
|
+
formatCoinPrice(price.quotePerCoin)
|
|
46
|
+
] })
|
|
47
|
+
] });
|
|
48
|
+
}
|
|
49
|
+
function CoinRow({ collection, usePrice, href, showKind = false }) {
|
|
50
|
+
const { price, isLoading } = usePrice(collection);
|
|
51
|
+
return /* @__PURE__ */ jsxs(
|
|
52
|
+
Link,
|
|
53
|
+
{
|
|
54
|
+
href,
|
|
55
|
+
className: cn(COIN_GRID, "border-b border-border/40 px-2 py-2.5 text-sm transition-colors hover:bg-muted/40"),
|
|
56
|
+
children: [
|
|
57
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-3", children: [
|
|
58
|
+
/* @__PURE__ */ jsx(CoinAvatar, { collection }),
|
|
59
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
|
|
60
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate font-semibold leading-tight", children: collection.name ?? "Untitled coin" }),
|
|
61
|
+
/* @__PURE__ */ jsxs("span", { className: "block truncate text-xs text-muted-foreground", children: [
|
|
62
|
+
collection.symbol ?? "\u2014",
|
|
63
|
+
showKind && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/60", children: [
|
|
64
|
+
" \xB7 ",
|
|
65
|
+
kindLabel(coinKind(collection.service))
|
|
66
|
+
] })
|
|
67
|
+
] })
|
|
68
|
+
] })
|
|
69
|
+
] }),
|
|
70
|
+
/* @__PURE__ */ jsx(PriceCell, { price, isLoading })
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
function CoinRowSkeleton() {
|
|
76
|
+
return /* @__PURE__ */ jsxs("div", { className: cn(COIN_GRID, "border-b border-border/40 px-2 py-2.5"), children: [
|
|
77
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-3", children: [
|
|
78
|
+
/* @__PURE__ */ jsx("span", { className: "h-9 w-9 shrink-0 animate-pulse rounded-full bg-muted-foreground/20" }),
|
|
79
|
+
/* @__PURE__ */ jsxs("span", { className: "space-y-1.5", children: [
|
|
80
|
+
/* @__PURE__ */ jsx("span", { className: "block h-4 w-28 animate-pulse rounded bg-muted-foreground/20" }),
|
|
81
|
+
/* @__PURE__ */ jsx("span", { className: "block h-3 w-14 animate-pulse rounded bg-muted-foreground/10" })
|
|
82
|
+
] })
|
|
83
|
+
] }),
|
|
84
|
+
/* @__PURE__ */ jsx("span", { className: "ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20" })
|
|
85
|
+
] });
|
|
86
|
+
}
|
|
87
|
+
export {
|
|
88
|
+
COIN_GRID,
|
|
89
|
+
CoinAvatar,
|
|
90
|
+
CoinRow,
|
|
91
|
+
CoinRowSkeleton
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=coin-row.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/coin-row.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"./link.js\";\nimport Image from \"./image.js\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { CurrencyIcon } from \"./currency-icon.js\";\nimport {\n coinKind, coinAccentHue, formatCoinPrice,\n type CoinCollectionLike, type CoinPriceLike,\n} from \"../data/coins.js\";\nimport { formatUsdPrice } from \"../utils/format.js\";\n\nexport type UseCoinPrice = (collection: CoinCollectionLike) => {\n price: CoinPriceLike | null;\n isLoading: boolean;\n};\n\nexport const COIN_GRID = \"grid grid-cols-[minmax(0,1fr)_8rem] items-center gap-3 sm:gap-4\";\n\nconst kindLabel = (kind: string) => (kind === \"creator\" ? \"Creator Coin\" : \"Memecoin\");\n\nexport interface CoinRowProps {\n collection: CoinCollectionLike;\n usePrice: UseCoinPrice;\n href: string;\n showKind?: boolean;\n}\n\nexport function CoinAvatar({ collection, size = 36 }: { collection: CoinCollectionLike; size?: number }) {\n const logoUri = collection.profile?.image ?? collection.image;\n const logo = logoUri ? ipfsToHttp(logoUri) : null;\n const initials = (collection.symbol ?? collection.name ?? \"?\").trim().slice(0, 2).toUpperCase();\n const hue = coinAccentHue(collection.symbol ?? collection.name ?? collection.contractAddress);\n\n return (\n <div\n className=\"relative shrink-0 overflow-hidden rounded-full\"\n style={{ width: size, height: size, backgroundColor: `hsl(${hue} 70% 52%)` }}\n >\n {logo ? (\n <Image src={logo} alt=\"\" fill sizes={`${size}px`} className=\"object-cover\" unoptimized />\n ) : (\n <span\n className=\"flex h-full w-full items-center justify-center font-bold leading-none text-white\"\n style={{ fontSize: size * 0.36 }}\n >\n {initials}\n </span>\n )}\n </div>\n );\n}\n\nfunction PriceCell({ price, isLoading }: { price: CoinPriceLike | null; isLoading: boolean }) {\n if (isLoading) return <span className=\"ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20\" />;\n if (!price) return <span className=\"block text-right text-muted-foreground\">—</span>;\n\n const usd = price.quoteUsdRate != null ? formatUsdPrice(price.quotePerCoin * price.quoteUsdRate) : null;\n\n return (\n <span className=\"block text-right leading-tight\">\n <span className=\"block font-semibold tabular-nums\">{usd ?? formatCoinPrice(price.quotePerCoin)}</span>\n <span className=\"mt-0.5 inline-flex items-center justify-end gap-1 text-2xs tabular-nums text-muted-foreground\">\n <CurrencyIcon symbol={price.quoteSymbol} size={10} />\n {formatCoinPrice(price.quotePerCoin)}\n </span>\n </span>\n );\n}\n\nexport function CoinRow({ collection, usePrice, href, showKind = false }: CoinRowProps) {\n const { price, isLoading } = usePrice(collection);\n\n return (\n <Link\n href={href}\n className={cn(COIN_GRID, \"border-b border-border/40 px-2 py-2.5 text-sm transition-colors hover:bg-muted/40\")}\n >\n <span className=\"flex min-w-0 items-center gap-3\">\n <CoinAvatar collection={collection} />\n <span className=\"min-w-0\">\n <span className=\"block truncate font-semibold leading-tight\">{collection.name ?? \"Untitled coin\"}</span>\n <span className=\"block truncate text-xs text-muted-foreground\">\n {collection.symbol ?? \"—\"}\n {showKind && <span className=\"text-muted-foreground/60\"> · {kindLabel(coinKind(collection.service))}</span>}\n </span>\n </span>\n </span>\n\n <PriceCell price={price} isLoading={isLoading} />\n </Link>\n );\n}\n\nexport function CoinRowSkeleton() {\n return (\n <div className={cn(COIN_GRID, \"border-b border-border/40 px-2 py-2.5\")}>\n <span className=\"flex items-center gap-3\">\n <span className=\"h-9 w-9 shrink-0 animate-pulse rounded-full bg-muted-foreground/20\" />\n <span className=\"space-y-1.5\">\n <span className=\"block h-4 w-28 animate-pulse rounded bg-muted-foreground/20\" />\n <span className=\"block h-3 w-14 animate-pulse rounded bg-muted-foreground/10\" />\n </span>\n </span>\n <span className=\"ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20\" />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -26,7 +26,8 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
26
26
|
var import_react = require("react");
|
|
27
27
|
var import_lucide_react = require("lucide-react");
|
|
28
28
|
var import_cn = require("../utils/cn.js");
|
|
29
|
-
var
|
|
29
|
+
var import_coin_row = require("./coin-row.js");
|
|
30
|
+
var import_coins = require("../data/coins.js");
|
|
30
31
|
const FILTER_TABS = [
|
|
31
32
|
{ label: "All", value: "all" },
|
|
32
33
|
{ label: "Creator Coins", value: "creator" },
|
|
@@ -41,7 +42,6 @@ const sortLabel = (v) => SORT_OPTIONS.find((o) => o.value === v)?.label ?? "";
|
|
|
41
42
|
function CoinsExplorer({ useCoins, usePrice, coinHref, heading = true }) {
|
|
42
43
|
const [filter, setFilter] = (0, import_react.useState)("all");
|
|
43
44
|
const [sort, setSort] = (0, import_react.useState)("recent");
|
|
44
|
-
const [view, setView] = (0, import_react.useState)("grid");
|
|
45
45
|
const [query, setQuery] = (0, import_react.useState)("");
|
|
46
46
|
const [filtersOpen, setFiltersOpen] = (0, import_react.useState)(false);
|
|
47
47
|
const filterCount = (filter !== "all" ? 1 : 0) + (sort !== "recent" ? 1 : 0);
|
|
@@ -59,7 +59,8 @@ function CoinsExplorer({ useCoins, usePrice, coinHref, heading = true }) {
|
|
|
59
59
|
(c) => (c.name ?? "").toLowerCase().includes(q) || (c.symbol ?? "").toLowerCase().includes(q)
|
|
60
60
|
);
|
|
61
61
|
}, [collections, query]);
|
|
62
|
-
|
|
62
|
+
const showKind = (0, import_react.useMemo)(() => new Set(items.map((c) => (0, import_coins.coinKind)(c.service))).size > 1, [items]);
|
|
63
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-5", children: [
|
|
63
64
|
heading && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-2", children: [
|
|
64
65
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2 text-primary", children: [
|
|
65
66
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Coins, { className: "h-5 w-5" }),
|
|
@@ -67,49 +68,52 @@ function CoinsExplorer({ useCoins, usePrice, coinHref, heading = true }) {
|
|
|
67
68
|
] }),
|
|
68
69
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: "text-3xl", children: "Creator coins & memecoins" })
|
|
69
70
|
] }),
|
|
70
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "
|
|
71
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex
|
|
72
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"input",
|
|
76
|
-
{
|
|
77
|
-
value: query,
|
|
78
|
-
onChange: (e) => setQuery(e.target.value),
|
|
79
|
-
placeholder: "Search coins by name or symbol\u2026",
|
|
80
|
-
className: "w-full rounded-lg border border-border bg-background py-2 pl-9 pr-3 text-sm outline-none focus:border-primary/50"
|
|
81
|
-
}
|
|
82
|
-
)
|
|
83
|
-
] }),
|
|
84
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
85
|
-
"button",
|
|
71
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
72
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative flex-1", children: [
|
|
73
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Search, { className: "pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }),
|
|
74
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
75
|
+
"input",
|
|
86
76
|
{
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
"Filters",
|
|
92
|
-
filterCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-2xs font-semibold text-primary-foreground", children: filterCount })
|
|
93
|
-
]
|
|
77
|
+
value: query,
|
|
78
|
+
onChange: (e) => setQuery(e.target.value),
|
|
79
|
+
placeholder: "Search coins by name or symbol\u2026",
|
|
80
|
+
className: "w-full rounded-lg border border-border bg-background py-2 pl-9 pr-3 text-sm outline-none focus:border-primary/50"
|
|
94
81
|
}
|
|
95
|
-
)
|
|
96
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "inline-flex shrink-0 rounded-lg border border-border p-0.5", children: [{ v: "grid", Icon: import_lucide_react.LayoutGrid }, { v: "table", Icon: import_lucide_react.List }].map(({ v, Icon }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
97
|
-
"button",
|
|
98
|
-
{
|
|
99
|
-
onClick: () => setView(v),
|
|
100
|
-
"aria-label": v === "grid" ? "Grid view" : "Table view",
|
|
101
|
-
className: (0, import_cn.cn)("rounded-md p-1.5 transition-colors", view === v ? "bg-primary/10 text-primary" : "text-muted-foreground hover:text-foreground"),
|
|
102
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { className: "h-4 w-4" })
|
|
103
|
-
},
|
|
104
|
-
v
|
|
105
|
-
)) })
|
|
82
|
+
)
|
|
106
83
|
] }),
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
84
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
85
|
+
"button",
|
|
86
|
+
{
|
|
87
|
+
onClick: () => setFiltersOpen(true),
|
|
88
|
+
className: "inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-border px-3 py-2 text-xs font-medium text-foreground hover:border-primary/50",
|
|
89
|
+
children: [
|
|
90
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.SlidersHorizontal, { className: "h-3.5 w-3.5" }),
|
|
91
|
+
"Filters",
|
|
92
|
+
filterCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-2xs font-semibold text-primary-foreground", children: filterCount })
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
] }),
|
|
97
|
+
filterCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5", children: [
|
|
98
|
+
filter !== "all" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Chip, { onClear: () => setFilter("all"), children: filterLabel(filter) }),
|
|
99
|
+
sort !== "recent" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Chip, { onClear: () => setSort("recent"), children: sortLabel(sort) })
|
|
100
|
+
] }),
|
|
101
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
102
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: (0, import_cn.cn)(import_coin_row.COIN_GRID, "border-b border-border px-2 pb-2 text-2xs font-medium uppercase tracking-wide text-muted-foreground"), children: [
|
|
103
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Token" }),
|
|
104
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-right", children: "Price" })
|
|
105
|
+
] }),
|
|
106
|
+
isLoading && items.length === 0 ? Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_coin_row.CoinRowSkeleton, {}, i)) : items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "py-16 text-center text-sm text-muted-foreground", children: query.trim() ? `No coins match "${query.trim()}".` : "No coins yet." }) : items.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
107
|
+
import_coin_row.CoinRow,
|
|
108
|
+
{
|
|
109
|
+
collection: c,
|
|
110
|
+
usePrice,
|
|
111
|
+
href: coinHref(c),
|
|
112
|
+
showKind
|
|
113
|
+
},
|
|
114
|
+
`${c.chain}-${c.contractAddress}`
|
|
115
|
+
))
|
|
111
116
|
] }),
|
|
112
|
-
isLoading && items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4", children: Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_coin_card.CoinCardSkeleton, {}, i)) }) : items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rounded-xl border border-border/60 py-16 text-center text-muted-foreground", children: query.trim() ? `No coins match "${query.trim()}".` : "No coins yet." }) : view === "table" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "space-y-2", children: items.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_coin_card.CoinRow, { collection: c, usePrice, href: coinHref(c) }, `${c.chain}-${c.contractAddress}`)) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4", children: items.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_coin_card.CoinCard, { collection: c, usePrice, href: coinHref(c) }, `${c.chain}-${c.contractAddress}`)) }),
|
|
113
117
|
filtersOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4", role: "dialog", "aria-modal": "true", "aria-label": "Filters", children: [
|
|
114
118
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-0 bg-background/70 backdrop-blur-sm", onClick: () => setFiltersOpen(false) }),
|
|
115
119
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative z-10 w-full max-w-sm space-y-5 overflow-hidden rounded-[calc(var(--radius)*1.25)] bg-card p-5", children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/coins-explorer.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useMemo, useEffect } from \"react\";\nimport { Coins, LayoutGrid, List, Search, SlidersHorizontal, X } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { CoinCard, CoinRow, CoinCardSkeleton, type UseCoinPrice } from \"./coin-card.js\";\nimport type { CoinCollectionLike } from \"../data/coins.js\";\n\nexport type CoinFilter = \"all\" | \"creator\" | \"memecoin\";\nexport type CoinSort = \"recent\" | \"name\";\nexport type UseCoins = (opts: { filter: CoinFilter; sort: CoinSort }) => {\n collections: CoinCollectionLike[];\n isLoading: boolean;\n};\n\nexport interface CoinsExplorerProps {\n useCoins: UseCoins;\n usePrice: UseCoinPrice;\n\n coinHref: (collection: CoinCollectionLike) => string;\n heading?: boolean;\n}\n\nconst FILTER_TABS: { label: string; value: CoinFilter }[] = [\n { label: \"All\", value: \"all\" },\n { label: \"Creator Coins\", value: \"creator\" },\n { label: \"Memecoins\", value: \"memecoin\" },\n];\n\nconst SORT_OPTIONS: { label: string; value: CoinSort }[] = [\n { label: \"Recently launched\", value: \"recent\" },\n { label: \"Name\", value: \"name\" },\n];\n\nconst filterLabel = (v: CoinFilter) => FILTER_TABS.find((t) => t.value === v)?.label ?? \"\";\nconst sortLabel = (v: CoinSort) => SORT_OPTIONS.find((o) => o.value === v)?.label ?? \"\";\n\nexport function CoinsExplorer({ useCoins, usePrice, coinHref, heading = true }: CoinsExplorerProps) {\n const [filter, setFilter] = useState<CoinFilter>(\"all\");\n const [sort, setSort] = useState<CoinSort>(\"recent\");\n const [view, setView] = useState<\"grid\" | \"table\">(\"grid\");\n const [query, setQuery] = useState(\"\");\n const [filtersOpen, setFiltersOpen] = useState(false);\n\n const filterCount = (filter !== \"all\" ? 1 : 0) + (sort !== \"recent\" ? 1 : 0);\n\n useEffect(() => {\n if (!filtersOpen) return;\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && setFiltersOpen(false);\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [filtersOpen]);\n\n const { collections, isLoading } = useCoins({ filter, sort });\n const items = useMemo(() => {\n const q = query.trim().toLowerCase();\n if (!q) return collections;\n return collections.filter(\n (c) => (c.name ?? \"\").toLowerCase().includes(q) || (c.symbol ?? \"\").toLowerCase().includes(q)\n );\n }, [collections, query]);\n\n return (\n <div className=\"space-y-6\">\n {heading && (\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2 text-primary\">\n <Coins className=\"h-5 w-5\" />\n <span className=\"text-sm font-semibold\">Coins</span>\n </div>\n <h1 className=\"text-3xl\">Creator coins & memecoins</h1>\n </div>\n )}\n\n <div className=\"space-y-3 border-b border-border/60 pb-3\">\n <div className=\"flex items-center gap-2\">\n <div className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground\" />\n <input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search coins by name or symbol…\"\n className=\"w-full rounded-lg border border-border bg-background py-2 pl-9 pr-3 text-sm outline-none focus:border-primary/50\"\n />\n </div>\n <button\n onClick={() => setFiltersOpen(true)}\n className=\"inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-border px-3 py-2 text-xs font-medium text-foreground hover:border-primary/50\"\n >\n <SlidersHorizontal className=\"h-3.5 w-3.5\" />\n Filters\n {filterCount > 0 && (\n <span className=\"ml-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-2xs font-semibold text-primary-foreground\">\n {filterCount}\n </span>\n )}\n </button>\n <div className=\"inline-flex shrink-0 rounded-lg border border-border p-0.5\">\n {([{ v: \"grid\", Icon: LayoutGrid }, { v: \"table\", Icon: List }] as const).map(({ v, Icon }) => (\n <button\n key={v}\n onClick={() => setView(v)}\n aria-label={v === \"grid\" ? \"Grid view\" : \"Table view\"}\n className={cn(\"rounded-md p-1.5 transition-colors\", view === v ? \"bg-primary/10 text-primary\" : \"text-muted-foreground hover:text-foreground\")}\n >\n <Icon className=\"h-4 w-4\" />\n </button>\n ))}\n </div>\n </div>\n\n {filterCount > 0 && (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {filter !== \"all\" && (\n <Chip onClear={() => setFilter(\"all\")}>{filterLabel(filter)}</Chip>\n )}\n {sort !== \"recent\" && (\n <Chip onClear={() => setSort(\"recent\")}>{sortLabel(sort)}</Chip>\n )}\n </div>\n )}\n </div>\n\n {isLoading && items.length === 0 ? (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4\">\n {Array.from({ length: 8 }).map((_, i) => <CoinCardSkeleton key={i} />)}\n </div>\n ) : items.length === 0 ? (\n <div className=\"rounded-xl border border-border/60 py-16 text-center text-muted-foreground\">\n {query.trim() ? `No coins match \"${query.trim()}\".` : \"No coins yet.\"}\n </div>\n ) : view === \"table\" ? (\n <div className=\"space-y-2\">\n {items.map((c) => <CoinRow key={`${c.chain}-${c.contractAddress}`} collection={c} usePrice={usePrice} href={coinHref(c)} />)}\n </div>\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4\">\n {items.map((c) => <CoinCard key={`${c.chain}-${c.contractAddress}`} collection={c} usePrice={usePrice} href={coinHref(c)} />)}\n </div>\n )}\n\n {filtersOpen && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center p-4\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Filters\">\n <div className=\"absolute inset-0 bg-background/70 backdrop-blur-sm\" onClick={() => setFiltersOpen(false)} />\n <div className=\"relative z-10 w-full max-w-sm space-y-5 overflow-hidden rounded-[calc(var(--radius)*1.25)] bg-card p-5\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"flex items-center gap-2 text-base font-bold\">\n <SlidersHorizontal className=\"h-4 w-4 text-primary\" />\n Filters\n </h2>\n {filterCount > 0 && (\n <button\n onClick={() => { setFilter(\"all\"); setSort(\"recent\"); }}\n className=\"text-xs font-medium text-muted-foreground hover:text-foreground\"\n >\n Clear all\n </button>\n )}\n </div>\n\n <FilterGroup label=\"Type\">\n {FILTER_TABS.map(({ label, value }) => (\n <PillButton key={value} active={filter === value} onClick={() => setFilter(value)}>{label}</PillButton>\n ))}\n </FilterGroup>\n\n <FilterGroup label=\"Sort\">\n {SORT_OPTIONS.map(({ label, value }) => (\n <PillButton key={value} active={sort === value} onClick={() => setSort(value)}>{label}</PillButton>\n ))}\n </FilterGroup>\n\n <button\n onClick={() => setFiltersOpen(false)}\n className=\"w-full rounded-lg bg-gradient-to-r from-brand-blue to-brand-purple py-2.5 text-sm font-semibold text-white\"\n >\n Show {items.length} {items.length === 1 ? \"coin\" : \"coins\"}\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n\nfunction FilterGroup({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className=\"space-y-2\">\n <p className=\"text-2xs font-medium text-muted-foreground\">{label}</p>\n <div className=\"flex flex-wrap gap-1.5\">{children}</div>\n </div>\n );\n}\n\nfunction PillButton({ active, onClick, children }: { active: boolean; onClick: () => void; children: React.ReactNode }) {\n return (\n <button\n onClick={onClick}\n className={cn(\n \"rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors\",\n active ? \"border-primary bg-primary/10 text-primary\" : \"border-border text-muted-foreground hover:border-primary/50 hover:text-foreground\"\n )}\n >\n {children}\n </button>\n );\n}\n\nfunction Chip({ children, onClear }: { children: React.ReactNode; onClear: () => void }) {\n return (\n <span className=\"inline-flex items-center gap-1 rounded-full border border-primary/30 bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary\">\n {children}\n <button onClick={onClear} aria-label=\"Clear filter\" className=\"hover:text-primary/60\">\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/coins-explorer.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useMemo, useEffect } from \"react\";\nimport { Coins, Search, SlidersHorizontal, X } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { CoinRow, CoinRowSkeleton, COIN_GRID, type UseCoinPrice } from \"./coin-row.js\";\nimport { coinKind, type CoinCollectionLike } from \"../data/coins.js\";\n\nexport type CoinFilter = \"all\" | \"creator\" | \"memecoin\";\nexport type CoinSort = \"recent\" | \"name\";\nexport type UseCoins = (opts: { filter: CoinFilter; sort: CoinSort }) => {\n collections: CoinCollectionLike[];\n isLoading: boolean;\n};\n\nexport interface CoinsExplorerProps {\n useCoins: UseCoins;\n usePrice: UseCoinPrice;\n\n coinHref: (collection: CoinCollectionLike) => string;\n heading?: boolean;\n}\n\nconst FILTER_TABS: { label: string; value: CoinFilter }[] = [\n { label: \"All\", value: \"all\" },\n { label: \"Creator Coins\", value: \"creator\" },\n { label: \"Memecoins\", value: \"memecoin\" },\n];\n\nconst SORT_OPTIONS: { label: string; value: CoinSort }[] = [\n { label: \"Recently launched\", value: \"recent\" },\n { label: \"Name\", value: \"name\" },\n];\n\nconst filterLabel = (v: CoinFilter) => FILTER_TABS.find((t) => t.value === v)?.label ?? \"\";\nconst sortLabel = (v: CoinSort) => SORT_OPTIONS.find((o) => o.value === v)?.label ?? \"\";\n\nexport function CoinsExplorer({ useCoins, usePrice, coinHref, heading = true }: CoinsExplorerProps) {\n const [filter, setFilter] = useState<CoinFilter>(\"all\");\n const [sort, setSort] = useState<CoinSort>(\"recent\");\n const [query, setQuery] = useState(\"\");\n const [filtersOpen, setFiltersOpen] = useState(false);\n\n const filterCount = (filter !== \"all\" ? 1 : 0) + (sort !== \"recent\" ? 1 : 0);\n\n useEffect(() => {\n if (!filtersOpen) return;\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && setFiltersOpen(false);\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [filtersOpen]);\n\n const { collections, isLoading } = useCoins({ filter, sort });\n const items = useMemo(() => {\n const q = query.trim().toLowerCase();\n if (!q) return collections;\n return collections.filter(\n (c) => (c.name ?? \"\").toLowerCase().includes(q) || (c.symbol ?? \"\").toLowerCase().includes(q)\n );\n }, [collections, query]);\n\n const showKind = useMemo(() => new Set(items.map((c) => coinKind(c.service))).size > 1, [items]);\n\n return (\n <div className=\"space-y-5\">\n {heading && (\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2 text-primary\">\n <Coins className=\"h-5 w-5\" />\n <span className=\"text-sm font-semibold\">Coins</span>\n </div>\n <h1 className=\"text-3xl\">Creator coins & memecoins</h1>\n </div>\n )}\n\n <div className=\"flex items-center gap-2\">\n <div className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground\" />\n <input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search coins by name or symbol…\"\n className=\"w-full rounded-lg border border-border bg-background py-2 pl-9 pr-3 text-sm outline-none focus:border-primary/50\"\n />\n </div>\n <button\n onClick={() => setFiltersOpen(true)}\n className=\"inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-border px-3 py-2 text-xs font-medium text-foreground hover:border-primary/50\"\n >\n <SlidersHorizontal className=\"h-3.5 w-3.5\" />\n Filters\n {filterCount > 0 && (\n <span className=\"ml-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-2xs font-semibold text-primary-foreground\">\n {filterCount}\n </span>\n )}\n </button>\n </div>\n\n {filterCount > 0 && (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {filter !== \"all\" && <Chip onClear={() => setFilter(\"all\")}>{filterLabel(filter)}</Chip>}\n {sort !== \"recent\" && <Chip onClear={() => setSort(\"recent\")}>{sortLabel(sort)}</Chip>}\n </div>\n )}\n\n <div>\n <div className={cn(COIN_GRID, \"border-b border-border px-2 pb-2 text-2xs font-medium uppercase tracking-wide text-muted-foreground\")}>\n <span>Token</span>\n <span className=\"text-right\">Price</span>\n </div>\n\n {isLoading && items.length === 0 ? (\n Array.from({ length: 6 }).map((_, i) => <CoinRowSkeleton key={i} />)\n ) : items.length === 0 ? (\n <p className=\"py-16 text-center text-sm text-muted-foreground\">\n {query.trim() ? `No coins match \"${query.trim()}\".` : \"No coins yet.\"}\n </p>\n ) : (\n items.map((c) => (\n <CoinRow\n key={`${c.chain}-${c.contractAddress}`}\n collection={c}\n usePrice={usePrice}\n href={coinHref(c)}\n showKind={showKind}\n />\n ))\n )}\n </div>\n\n {filtersOpen && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center p-4\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Filters\">\n <div className=\"absolute inset-0 bg-background/70 backdrop-blur-sm\" onClick={() => setFiltersOpen(false)} />\n <div className=\"relative z-10 w-full max-w-sm space-y-5 overflow-hidden rounded-[calc(var(--radius)*1.25)] bg-card p-5\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"flex items-center gap-2 text-base font-bold\">\n <SlidersHorizontal className=\"h-4 w-4 text-primary\" />\n Filters\n </h2>\n {filterCount > 0 && (\n <button\n onClick={() => { setFilter(\"all\"); setSort(\"recent\"); }}\n className=\"text-xs font-medium text-muted-foreground hover:text-foreground\"\n >\n Clear all\n </button>\n )}\n </div>\n\n <FilterGroup label=\"Type\">\n {FILTER_TABS.map(({ label, value }) => (\n <PillButton key={value} active={filter === value} onClick={() => setFilter(value)}>{label}</PillButton>\n ))}\n </FilterGroup>\n\n <FilterGroup label=\"Sort\">\n {SORT_OPTIONS.map(({ label, value }) => (\n <PillButton key={value} active={sort === value} onClick={() => setSort(value)}>{label}</PillButton>\n ))}\n </FilterGroup>\n\n <button\n onClick={() => setFiltersOpen(false)}\n className=\"w-full rounded-lg bg-gradient-to-r from-brand-blue to-brand-purple py-2.5 text-sm font-semibold text-white\"\n >\n Show {items.length} {items.length === 1 ? \"coin\" : \"coins\"}\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n\nfunction FilterGroup({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className=\"space-y-2\">\n <p className=\"text-2xs font-medium text-muted-foreground\">{label}</p>\n <div className=\"flex flex-wrap gap-1.5\">{children}</div>\n </div>\n );\n}\n\nfunction PillButton({ active, onClick, children }: { active: boolean; onClick: () => void; children: React.ReactNode }) {\n return (\n <button\n onClick={onClick}\n className={cn(\n \"rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors\",\n active ? \"border-primary bg-primary/10 text-primary\" : \"border-border text-muted-foreground hover:border-primary/50 hover:text-foreground\"\n )}\n >\n {children}\n </button>\n );\n}\n\nfunction Chip({ children, onClear }: { children: React.ReactNode; onClear: () => void }) {\n return (\n <span className=\"inline-flex items-center gap-1 rounded-full border border-primary/30 bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary\">\n {children}\n <button onClick={onClear} aria-label=\"Clear filter\" className=\"hover:text-primary/60\">\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n );\n}\n"],"mappings":"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","names":[]}
|