@medialane/ui 0.137.0 → 0.138.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 (43) hide show
  1. package/dist/components/button.d.cts +2 -2
  2. package/dist/components/button.d.ts +2 -2
  3. package/dist/components/coin-row.cjs +10 -2
  4. package/dist/components/coin-row.cjs.map +1 -1
  5. package/dist/components/coin-row.js +10 -2
  6. package/dist/components/coin-row.js.map +1 -1
  7. package/dist/components/coins-explorer.cjs +46 -105
  8. package/dist/components/coins-explorer.cjs.map +1 -1
  9. package/dist/components/coins-explorer.d.cts +7 -2
  10. package/dist/components/coins-explorer.d.ts +7 -2
  11. package/dist/components/coins-explorer.js +49 -108
  12. package/dist/components/coins-explorer.js.map +1 -1
  13. package/dist/components/dual-price.cjs +17 -14
  14. package/dist/components/dual-price.cjs.map +1 -1
  15. package/dist/components/dual-price.d.cts +2 -1
  16. package/dist/components/dual-price.d.ts +2 -1
  17. package/dist/components/dual-price.js +17 -14
  18. package/dist/components/dual-price.js.map +1 -1
  19. package/dist/components/stat-tile.cjs +23 -15
  20. package/dist/components/stat-tile.cjs.map +1 -1
  21. package/dist/components/stat-tile.d.cts +6 -2
  22. package/dist/components/stat-tile.d.ts +6 -2
  23. package/dist/components/stat-tile.js +23 -15
  24. package/dist/components/stat-tile.js.map +1 -1
  25. package/dist/data/coin-order.cjs +43 -0
  26. package/dist/data/coin-order.cjs.map +1 -0
  27. package/dist/data/coin-order.d.cts +6 -0
  28. package/dist/data/coin-order.d.ts +6 -0
  29. package/dist/data/coin-order.js +19 -0
  30. package/dist/data/coin-order.js.map +1 -0
  31. package/dist/data/coins.cjs +6 -0
  32. package/dist/data/coins.cjs.map +1 -1
  33. package/dist/data/coins.d.cts +2 -1
  34. package/dist/data/coins.d.ts +2 -1
  35. package/dist/data/coins.js +5 -0
  36. package/dist/data/coins.js.map +1 -1
  37. package/dist/index.cjs +5 -0
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +3 -2
  40. package/dist/index.d.ts +3 -2
  41. package/dist/index.js +4 -0
  42. package/dist/index.js.map +1 -1
  43. package/package.json +1 -1
@@ -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" | "default" | "destructive" | "outline" | "secondary" | "ghost" | "premium" | "tonal" | "gradient-border" | "gradient-fill" | null | undefined;
7
- size?: "default" | "sm" | "lg" | "icon" | null | undefined;
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" | "default" | "destructive" | "outline" | "secondary" | "ghost" | "premium" | "tonal" | "gradient-border" | "gradient-fill" | null | undefined;
7
- size?: "default" | "sm" | "lg" | "icon" | null | undefined;
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;
@@ -68,7 +68,14 @@ function CoinAvatar({ collection, size = 36 }) {
68
68
  function PriceCell({ price, status, isLoading }) {
69
69
  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" });
70
70
  if (!price) {
71
- return status === "pre-launch" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "block text-right text-xs text-muted-foreground", children: "Not trading yet" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "block text-right text-muted-foreground", children: "\u2014" });
71
+ return status === "pre-launch" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "block text-right text-xs text-muted-foreground", children: "Not trading yet" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
72
+ "span",
73
+ {
74
+ className: "block text-right text-muted-foreground",
75
+ title: "No liquidity route was found for this coin on any Starknet DEX yet.",
76
+ children: "\u2014"
77
+ }
78
+ );
72
79
  }
73
80
  const usd = price.quoteUsdRate != null ? (0, import_format.formatUsdPrice)(price.quotePerCoin * price.quoteUsdRate) : null;
74
81
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -77,7 +84,8 @@ function PriceCell({ price, status, isLoading }) {
77
84
  amountFormatted: String(price.quotePerCoin),
78
85
  currency: price.quoteSymbol,
79
86
  usdValue: usd,
80
- scale: "card"
87
+ scale: "card",
88
+ secondaryClassName: "hidden sm:flex"
81
89
  }
82
90
  ) });
83
91
  }
@@ -1 +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 { DualPrice } from \"./dual-price.js\";\nimport {\n coinKind, coinKindLabel, coinAccentToken,\n type CoinCollectionLike, type CoinPriceLike,\n} from \"../data/coins.js\";\nimport { formatUsdPrice } from \"../utils/format.js\";\n\nexport type CoinMarketStatus = \"live\" | \"pre-launch\" | \"unavailable\";\n\nexport type UseCoinPrice = (collection: CoinCollectionLike) => {\n price: CoinPriceLike | null;\n status?: CoinMarketStatus;\n isLoading: boolean;\n};\n\nexport const COIN_GRID = \"grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3 sm:gap-4\";\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 accent = coinAccentToken(collection.symbol ?? collection.name ?? collection.contractAddress);\n\n return (\n <div\n className={cn(\"relative shrink-0 overflow-hidden rounded-full\", accent)}\n style={{ width: size, height: size }}\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, status, isLoading }: { price: CoinPriceLike | null; status?: CoinMarketStatus; 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) {\n return status === \"pre-launch\" ? (\n <span className=\"block text-right text-xs text-muted-foreground\">Not trading yet</span>\n ) : (\n <span className=\"block text-right text-muted-foreground\">—</span>\n );\n }\n\n const usd = price.quoteUsdRate != null ? formatUsdPrice(price.quotePerCoin * price.quoteUsdRate) : null;\n\n return (\n <span className=\"flex justify-end\">\n <DualPrice\n amountFormatted={String(price.quotePerCoin)}\n currency={price.quoteSymbol}\n usdValue={usd}\n scale=\"card\"\n />\n </span>\n );\n}\n\nexport function CoinRow({ collection, usePrice, href, showKind = false }: CoinRowProps) {\n const { price, status, isLoading } = usePrice(collection);\n\n return (\n <Link\n href={href}\n className={cn(\n COIN_GRID,\n \"border-b border-border/40 px-2 py-2.5 text-sm transition-colors\",\n \"hover:bg-muted/40 active:bg-muted/60\"\n )}\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\"> · {coinKindLabel(coinKind(collection.service))}</span>}\n </span>\n </span>\n </span>\n\n <PriceCell price={price} status={status} 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"]}
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 { DualPrice } from \"./dual-price.js\";\nimport {\n coinKind, coinKindLabel, coinAccentToken,\n type CoinCollectionLike, type CoinPriceLike,\n} from \"../data/coins.js\";\nimport { formatUsdPrice } from \"../utils/format.js\";\n\nexport type CoinMarketStatus = \"live\" | \"pre-launch\" | \"unavailable\";\n\nexport type UseCoinPrice = (collection: CoinCollectionLike) => {\n price: CoinPriceLike | null;\n status?: CoinMarketStatus;\n isLoading: boolean;\n};\n\nexport const COIN_GRID = \"grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3 sm:gap-4\";\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 accent = coinAccentToken(collection.symbol ?? collection.name ?? collection.contractAddress);\n\n return (\n <div\n className={cn(\"relative shrink-0 overflow-hidden rounded-full\", accent)}\n style={{ width: size, height: size }}\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, status, isLoading }: { price: CoinPriceLike | null; status?: CoinMarketStatus; 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) {\n return status === \"pre-launch\" ? (\n <span className=\"block text-right text-xs text-muted-foreground\">Not trading yet</span>\n ) : (\n <span\n className=\"block text-right text-muted-foreground\"\n title=\"No liquidity route was found for this coin on any Starknet DEX yet.\"\n >\n —\n </span>\n );\n }\n\n const usd = price.quoteUsdRate != null ? formatUsdPrice(price.quotePerCoin * price.quoteUsdRate) : null;\n\n return (\n <span className=\"flex justify-end\">\n <DualPrice\n amountFormatted={String(price.quotePerCoin)}\n currency={price.quoteSymbol}\n usdValue={usd}\n scale=\"card\"\n secondaryClassName=\"hidden sm:flex\"\n />\n </span>\n );\n}\n\nexport function CoinRow({ collection, usePrice, href, showKind = false }: CoinRowProps) {\n const { price, status, isLoading } = usePrice(collection);\n\n return (\n <Link\n href={href}\n className={cn(\n COIN_GRID,\n \"border-b border-border/40 px-2 py-2.5 text-sm transition-colors\",\n \"hover:bg-muted/40 active:bg-muted/60\"\n )}\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\"> · {coinKindLabel(coinKind(collection.service))}</span>}\n </span>\n </span>\n </span>\n\n <PriceCell price={price} status={status} 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"]}
@@ -36,7 +36,14 @@ function CoinAvatar({ collection, size = 36 }) {
36
36
  function PriceCell({ price, status, isLoading }) {
37
37
  if (isLoading) return /* @__PURE__ */ jsx("span", { className: "ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20" });
38
38
  if (!price) {
39
- return status === "pre-launch" ? /* @__PURE__ */ jsx("span", { className: "block text-right text-xs text-muted-foreground", children: "Not trading yet" }) : /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: "\u2014" });
39
+ return status === "pre-launch" ? /* @__PURE__ */ jsx("span", { className: "block text-right text-xs text-muted-foreground", children: "Not trading yet" }) : /* @__PURE__ */ jsx(
40
+ "span",
41
+ {
42
+ className: "block text-right text-muted-foreground",
43
+ title: "No liquidity route was found for this coin on any Starknet DEX yet.",
44
+ children: "\u2014"
45
+ }
46
+ );
40
47
  }
41
48
  const usd = price.quoteUsdRate != null ? formatUsdPrice(price.quotePerCoin * price.quoteUsdRate) : null;
42
49
  return /* @__PURE__ */ jsx("span", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
@@ -45,7 +52,8 @@ function PriceCell({ price, status, isLoading }) {
45
52
  amountFormatted: String(price.quotePerCoin),
46
53
  currency: price.quoteSymbol,
47
54
  usdValue: usd,
48
- scale: "card"
55
+ scale: "card",
56
+ secondaryClassName: "hidden sm:flex"
49
57
  }
50
58
  ) });
51
59
  }
@@ -1 +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 { DualPrice } from \"./dual-price.js\";\nimport {\n coinKind, coinKindLabel, coinAccentToken,\n type CoinCollectionLike, type CoinPriceLike,\n} from \"../data/coins.js\";\nimport { formatUsdPrice } from \"../utils/format.js\";\n\nexport type CoinMarketStatus = \"live\" | \"pre-launch\" | \"unavailable\";\n\nexport type UseCoinPrice = (collection: CoinCollectionLike) => {\n price: CoinPriceLike | null;\n status?: CoinMarketStatus;\n isLoading: boolean;\n};\n\nexport const COIN_GRID = \"grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3 sm:gap-4\";\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 accent = coinAccentToken(collection.symbol ?? collection.name ?? collection.contractAddress);\n\n return (\n <div\n className={cn(\"relative shrink-0 overflow-hidden rounded-full\", accent)}\n style={{ width: size, height: size }}\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, status, isLoading }: { price: CoinPriceLike | null; status?: CoinMarketStatus; 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) {\n return status === \"pre-launch\" ? (\n <span className=\"block text-right text-xs text-muted-foreground\">Not trading yet</span>\n ) : (\n <span className=\"block text-right text-muted-foreground\">—</span>\n );\n }\n\n const usd = price.quoteUsdRate != null ? formatUsdPrice(price.quotePerCoin * price.quoteUsdRate) : null;\n\n return (\n <span className=\"flex justify-end\">\n <DualPrice\n amountFormatted={String(price.quotePerCoin)}\n currency={price.quoteSymbol}\n usdValue={usd}\n scale=\"card\"\n />\n </span>\n );\n}\n\nexport function CoinRow({ collection, usePrice, href, showKind = false }: CoinRowProps) {\n const { price, status, isLoading } = usePrice(collection);\n\n return (\n <Link\n href={href}\n className={cn(\n COIN_GRID,\n \"border-b border-border/40 px-2 py-2.5 text-sm transition-colors\",\n \"hover:bg-muted/40 active:bg-muted/60\"\n )}\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\"> · {coinKindLabel(coinKind(collection.service))}</span>}\n </span>\n </span>\n </span>\n\n <PriceCell price={price} status={status} 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":[]}
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 { DualPrice } from \"./dual-price.js\";\nimport {\n coinKind, coinKindLabel, coinAccentToken,\n type CoinCollectionLike, type CoinPriceLike,\n} from \"../data/coins.js\";\nimport { formatUsdPrice } from \"../utils/format.js\";\n\nexport type CoinMarketStatus = \"live\" | \"pre-launch\" | \"unavailable\";\n\nexport type UseCoinPrice = (collection: CoinCollectionLike) => {\n price: CoinPriceLike | null;\n status?: CoinMarketStatus;\n isLoading: boolean;\n};\n\nexport const COIN_GRID = \"grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3 sm:gap-4\";\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 accent = coinAccentToken(collection.symbol ?? collection.name ?? collection.contractAddress);\n\n return (\n <div\n className={cn(\"relative shrink-0 overflow-hidden rounded-full\", accent)}\n style={{ width: size, height: size }}\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, status, isLoading }: { price: CoinPriceLike | null; status?: CoinMarketStatus; 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) {\n return status === \"pre-launch\" ? (\n <span className=\"block text-right text-xs text-muted-foreground\">Not trading yet</span>\n ) : (\n <span\n className=\"block text-right text-muted-foreground\"\n title=\"No liquidity route was found for this coin on any Starknet DEX yet.\"\n >\n —\n </span>\n );\n }\n\n const usd = price.quoteUsdRate != null ? formatUsdPrice(price.quotePerCoin * price.quoteUsdRate) : null;\n\n return (\n <span className=\"flex justify-end\">\n <DualPrice\n amountFormatted={String(price.quotePerCoin)}\n currency={price.quoteSymbol}\n usdValue={usd}\n scale=\"card\"\n secondaryClassName=\"hidden sm:flex\"\n />\n </span>\n );\n}\n\nexport function CoinRow({ collection, usePrice, href, showKind = false }: CoinRowProps) {\n const { price, status, isLoading } = usePrice(collection);\n\n return (\n <Link\n href={href}\n className={cn(\n COIN_GRID,\n \"border-b border-border/40 px-2 py-2.5 text-sm transition-colors\",\n \"hover:bg-muted/40 active:bg-muted/60\"\n )}\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\"> · {coinKindLabel(coinKind(collection.service))}</span>}\n </span>\n </span>\n </span>\n\n <PriceCell price={price} status={status} 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":";AA0CQ,cA6DiB,YA7DjB;AAxCR,OAAO,UAAU;AACjB,OAAO,WAAW;AAClB,SAAS,UAAU;AACnB,SAAS,kBAAkB;AAC3B,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,EAAU;AAAA,EAAe;AAAA,OAEpB;AACP,SAAS,sBAAsB;AAUxB,MAAM,YAAY;AASlB,SAAS,WAAW,EAAE,YAAY,OAAO,GAAG,GAAsD;AACvG,QAAM,UAAU,WAAW,SAAS,SAAS,WAAW;AACxD,QAAM,OAAO,UAAU,WAAW,OAAO,IAAI;AAC7C,QAAM,YAAY,WAAW,UAAU,WAAW,QAAQ,KAAK,KAAK,EAAE,MAAM,GAAG,CAAC,EAAE,YAAY;AAC9F,QAAM,SAAS,gBAAgB,WAAW,UAAU,WAAW,QAAQ,WAAW,eAAe;AAEjG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,kDAAkD,MAAM;AAAA,MACtE,OAAO,EAAE,OAAO,MAAM,QAAQ,KAAK;AAAA,MAElC,iBACC,oBAAC,SAAM,KAAK,MAAM,KAAI,IAAG,MAAI,MAAC,OAAO,GAAG,IAAI,MAAM,WAAU,gBAAe,aAAW,MAAC,IAEvF;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,OAAO,EAAE,UAAU,OAAO,KAAK;AAAA,UAE9B;AAAA;AAAA,MACH;AAAA;AAAA,EAEJ;AAEJ;AAEA,SAAS,UAAU,EAAE,OAAO,QAAQ,UAAU,GAAmF;AAC/H,MAAI,UAAW,QAAO,oBAAC,UAAK,WAAU,uEAAsE;AAC5G,MAAI,CAAC,OAAO;AACV,WAAO,WAAW,eAChB,oBAAC,UAAK,WAAU,kDAAiD,6BAAe,IAEhF;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,OAAM;AAAA,QACP;AAAA;AAAA,IAED;AAAA,EAEJ;AAEA,QAAM,MAAM,MAAM,gBAAgB,OAAO,eAAe,MAAM,eAAe,MAAM,YAAY,IAAI;AAEnG,SACE,oBAAC,UAAK,WAAU,oBACd;AAAA,IAAC;AAAA;AAAA,MACC,iBAAiB,OAAO,MAAM,YAAY;AAAA,MAC1C,UAAU,MAAM;AAAA,MAChB,UAAU;AAAA,MACV,OAAM;AAAA,MACN,oBAAmB;AAAA;AAAA,EACrB,GACF;AAEJ;AAEO,SAAS,QAAQ,EAAE,YAAY,UAAU,MAAM,WAAW,MAAM,GAAiB;AACtF,QAAM,EAAE,OAAO,QAAQ,UAAU,IAAI,SAAS,UAAU;AAExD,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MAEA;AAAA,6BAAC,UAAK,WAAU,mCACd;AAAA,8BAAC,cAAW,YAAwB;AAAA,UACpC,qBAAC,UAAK,WAAU,WACd;AAAA,gCAAC,UAAK,WAAU,8CAA8C,qBAAW,QAAQ,iBAAgB;AAAA,YACjG,qBAAC,UAAK,WAAU,gDACb;AAAA,yBAAW,UAAU;AAAA,cACrB,YAAY,qBAAC,UAAK,WAAU,4BAA2B;AAAA;AAAA,gBAAI,cAAc,SAAS,WAAW,OAAO,CAAC;AAAA,iBAAE;AAAA,eAC1G;AAAA,aACF;AAAA,WACF;AAAA,QAEA,oBAAC,aAAU,OAAc,QAAgB,WAAsB;AAAA;AAAA;AAAA,EACjE;AAEJ;AAEO,SAAS,kBAAkB;AAChC,SACE,qBAAC,SAAI,WAAW,GAAG,WAAW,uCAAuC,GACnE;AAAA,yBAAC,UAAK,WAAU,2BACd;AAAA,0BAAC,UAAK,WAAU,sEAAqE;AAAA,MACrF,qBAAC,UAAK,WAAU,eACd;AAAA,4BAAC,UAAK,WAAU,+DAA8D;AAAA,QAC9E,oBAAC,UAAK,WAAU,+DAA8D;AAAA,SAChF;AAAA,OACF;AAAA,IACA,oBAAC,UAAK,WAAU,uEAAsE;AAAA,KACxF;AAEJ;","names":[]}
@@ -28,54 +28,52 @@ var import_lucide_react = require("lucide-react");
28
28
  var import_cn = require("../utils/cn.js");
29
29
  var import_coin_row = require("./coin-row.js");
30
30
  var import_stat_tile = require("./stat-tile.js");
31
+ var import_coin_order = require("../data/coin-order.js");
31
32
  var import_coins = require("../data/coins.js");
32
- const FILTER_TABS = [
33
- { label: "All", value: "all" },
34
- ...import_coins.COIN_KINDS.map((kind) => ({ label: (0, import_coins.coinKindLabel)(kind), value: kind }))
35
- ];
36
33
  const SORT_OPTIONS = [
37
34
  { label: "Recently launched", value: "recent" },
38
35
  { label: "Name", value: "name" }
39
36
  ];
40
- const filterLabel = (v) => FILTER_TABS.find((t) => t.value === v)?.label ?? "";
41
- const sortLabel = (v) => SORT_OPTIONS.find((o) => o.value === v)?.label ?? "";
42
- function CoinsExplorer({ useCoins, usePrice, coinHref, heading = true, action }) {
37
+ const noPriceMap = () => ({ prices: {}, isLoading: false });
38
+ function CoinsExplorer({ useCoins, usePrice, usePriceMap, coinHref, heading = true, action }) {
43
39
  const [filter, setFilter] = (0, import_react.useState)("all");
44
40
  const [sort, setSort] = (0, import_react.useState)("recent");
45
41
  const [query, setQuery] = (0, import_react.useState)("");
46
- const [filtersOpen, setFiltersOpen] = (0, import_react.useState)(false);
47
- const filterCount = (filter !== "all" ? 1 : 0) + (sort !== "recent" ? 1 : 0);
48
- (0, import_react.useEffect)(() => {
49
- if (!filtersOpen) return;
50
- const onKey = (e) => e.key === "Escape" && setFiltersOpen(false);
51
- window.addEventListener("keydown", onKey);
52
- return () => window.removeEventListener("keydown", onKey);
53
- }, [filtersOpen]);
42
+ const [priceSort, setPriceSort] = (0, import_react.useState)(void 0);
54
43
  const { collections, isLoading, counts } = useCoins({ filter, sort });
44
+ const { prices } = (usePriceMap ?? noPriceMap)();
55
45
  const items = (0, import_react.useMemo)(() => {
56
46
  const q = query.trim().toLowerCase();
57
- if (!q) return collections;
58
- return collections.filter(
59
- (c) => (c.name ?? "").toLowerCase().includes(q) || (c.symbol ?? "").toLowerCase().includes(q)
60
- );
61
- }, [collections, query]);
47
+ const matched = q ? collections.filter((c) => (c.name ?? "").toLowerCase().includes(q) || (c.symbol ?? "").toLowerCase().includes(q)) : collections;
48
+ return usePriceMap ? (0, import_coin_order.orderCoins)(matched, prices, priceSort) : matched;
49
+ }, [collections, query, usePriceMap, prices, priceSort]);
62
50
  const showKind = (0, import_react.useMemo)(() => new Set(items.map((c) => (0, import_coins.coinKind)(c.service))).size > 1, [items]);
63
- const statItems = (0, import_react.useMemo)(() => {
64
- if (!counts) return [];
51
+ const { statItems, pillFilters } = (0, import_react.useMemo)(() => {
52
+ if (!counts) return { statItems: [], pillFilters: [] };
65
53
  const forKind = (kind) => (0, import_coins.coinServiceIds)(kind).reduce((sum, id) => sum + (counts[id] ?? 0), 0);
66
54
  const total = Object.values(counts).reduce((a, b) => a + b, 0);
67
- return [
68
- { label: total === 1 ? "Coin" : "Coins", value: total },
69
- ...import_coins.COIN_KINDS.map((kind) => ({ label: (0, import_coins.coinKindLabel)(kind), value: forKind(kind) })).filter(
70
- (s) => s.value > 0
71
- )
72
- ];
55
+ const kinds = import_coins.COIN_KINDS.filter((kind) => forKind(kind) > 0);
56
+ return {
57
+ statItems: [
58
+ { label: total === 1 ? "Coin" : "Coins", value: total },
59
+ ...kinds.map((kind) => ({ label: (0, import_coins.coinKindLabelPlural)(kind), value: forKind(kind) }))
60
+ ],
61
+ pillFilters: ["all", ...kinds]
62
+ };
73
63
  }, [counts]);
64
+ const activeIndex = pillFilters.indexOf(filter);
74
65
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-5", children: [
75
66
  heading && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-wrap items-start justify-between gap-4", children: [
76
67
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-2", children: [
77
68
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: "text-3xl font-bold", children: "Creator coins & memecoins" }),
78
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_stat_tile.StatPillRow, { items: statItems })
69
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
70
+ import_stat_tile.StatPillRow,
71
+ {
72
+ items: statItems,
73
+ onSelect: (index) => setFilter(pillFilters[index]),
74
+ activeIndex
75
+ }
76
+ )
79
77
  ] }),
80
78
  action
81
79
  ] }),
@@ -92,27 +90,32 @@ function CoinsExplorer({ useCoins, usePrice, coinHref, heading = true, action })
92
90
  }
93
91
  )
94
92
  ] }),
95
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
96
- "button",
93
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
94
+ "select",
97
95
  {
98
- onClick: () => setFiltersOpen(true),
99
- 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",
100
- children: [
101
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.SlidersHorizontal, { className: "h-3.5 w-3.5" }),
102
- "Filters",
103
- 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 })
104
- ]
96
+ value: sort,
97
+ onChange: (e) => setSort(e.target.value),
98
+ "aria-label": "Sort coins",
99
+ className: "shrink-0 rounded-lg border border-border bg-background px-2.5 py-2 text-xs font-medium text-foreground outline-none focus:border-primary/50",
100
+ children: SORT_OPTIONS.map(({ label, value }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value, children: label }, value))
105
101
  }
106
102
  )
107
103
  ] }),
108
- filterCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5", children: [
109
- filter !== "all" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Chip, { onClear: () => setFilter("all"), children: filterLabel(filter) }),
110
- sort !== "recent" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Chip, { onClear: () => setSort("recent"), children: sortLabel(sort) })
111
- ] }),
112
104
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
113
105
  /* @__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: [
114
106
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Token" }),
115
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-right", children: "Price" })
107
+ usePriceMap ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
108
+ "button",
109
+ {
110
+ type: "button",
111
+ onClick: () => setPriceSort(priceSort === void 0 ? "desc" : priceSort === "desc" ? "asc" : void 0),
112
+ className: (0, import_cn.cn)("text-right uppercase tracking-wide", priceSort ? "text-foreground" : "text-muted-foreground"),
113
+ children: [
114
+ "Price",
115
+ priceSort === "desc" ? " \u2193" : priceSort === "asc" ? " \u2191" : ""
116
+ ]
117
+ }
118
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-right", children: "Price" })
116
119
  ] }),
117
120
  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)(
118
121
  import_coin_row.CoinRow,
@@ -124,71 +127,9 @@ function CoinsExplorer({ useCoins, usePrice, coinHref, heading = true, action })
124
127
  },
125
128
  `${c.chain}-${c.contractAddress}`
126
129
  ))
127
- ] }),
128
- 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: [
129
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-0 bg-background/70 backdrop-blur-sm", onClick: () => setFiltersOpen(false) }),
130
- /* @__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: [
131
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between", children: [
132
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h2", { className: "flex items-center gap-2 text-base font-bold", children: [
133
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.SlidersHorizontal, { className: "h-4 w-4 text-primary" }),
134
- "Filters"
135
- ] }),
136
- filterCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
137
- "button",
138
- {
139
- onClick: () => {
140
- setFilter("all");
141
- setSort("recent");
142
- },
143
- className: "text-xs font-medium text-muted-foreground hover:text-foreground",
144
- children: "Clear all"
145
- }
146
- )
147
- ] }),
148
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilterGroup, { label: "Type", children: FILTER_TABS.map(({ label, value }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PillButton, { active: filter === value, onClick: () => setFilter(value), children: label }, value)) }),
149
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilterGroup, { label: "Sort", children: SORT_OPTIONS.map(({ label, value }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PillButton, { active: sort === value, onClick: () => setSort(value), children: label }, value)) }),
150
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
151
- "button",
152
- {
153
- onClick: () => setFiltersOpen(false),
154
- className: "w-full rounded-lg bg-gradient-to-r from-brand-blue to-brand-purple py-2.5 text-sm font-semibold text-white",
155
- children: [
156
- "Show ",
157
- items.length,
158
- " ",
159
- items.length === 1 ? "coin" : "coins"
160
- ]
161
- }
162
- )
163
- ] })
164
130
  ] })
165
131
  ] });
166
132
  }
167
- function FilterGroup({ label, children }) {
168
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-2", children: [
169
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-2xs font-medium text-muted-foreground", children: label }),
170
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-wrap gap-1.5", children })
171
- ] });
172
- }
173
- function PillButton({ active, onClick, children }) {
174
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
175
- "button",
176
- {
177
- onClick,
178
- className: (0, import_cn.cn)(
179
- "rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors",
180
- active ? "border-primary bg-primary/10 text-primary" : "border-border text-muted-foreground hover:border-primary/50 hover:text-foreground"
181
- ),
182
- children
183
- }
184
- );
185
- }
186
- function Chip({ children, onClear }) {
187
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("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", children: [
188
- children,
189
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: onClear, "aria-label": "Clear filter", className: "hover:text-primary/60", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.X, { className: "h-3 w-3" }) })
190
- ] });
191
- }
192
133
  // Annotate the CommonJS export names for ESM import in node:
193
134
  0 && (module.exports = {
194
135
  CoinsExplorer
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/coins-explorer.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useMemo, useEffect } from \"react\";\nimport { Search, SlidersHorizontal, X } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { CoinRow, CoinRowSkeleton, COIN_GRID, type UseCoinPrice } from \"./coin-row.js\";\nimport { StatPillRow } from \"./stat-tile.js\";\nimport { coinKind, coinKindLabel, coinServiceIds, COIN_KINDS, type CoinCollectionLike, type CoinKind } from \"../data/coins.js\";\n\nexport type CoinFilter = \"all\" | CoinKind;\nexport type CoinSort = \"recent\" | \"name\";\nexport type CoinCounts = Record<string, number>;\n\nexport type UseCoins = (opts: { filter: CoinFilter; sort: CoinSort }) => {\n collections: CoinCollectionLike[];\n isLoading: boolean;\n counts?: CoinCounts;\n};\n\nexport interface CoinsExplorerProps {\n useCoins: UseCoins;\n usePrice: UseCoinPrice;\n\n coinHref: (collection: CoinCollectionLike) => string;\n heading?: boolean;\n\n action?: React.ReactNode;\n}\n\nconst FILTER_TABS: { label: string; value: CoinFilter }[] = [\n { label: \"All\", value: \"all\" },\n ...COIN_KINDS.map((kind) => ({ label: coinKindLabel(kind), value: kind as CoinFilter })),\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, action }: 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, counts } = 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 const statItems = useMemo(() => {\n if (!counts) return [];\n const forKind = (kind: CoinKind) =>\n coinServiceIds(kind).reduce((sum, id) => sum + (counts[id] ?? 0), 0);\n const total = Object.values(counts).reduce((a, b) => a + b, 0);\n return [\n { label: total === 1 ? \"Coin\" : \"Coins\", value: total },\n ...COIN_KINDS.map((kind) => ({ label: coinKindLabel(kind), value: forKind(kind) })).filter(\n (s) => s.value > 0,\n ),\n ];\n }, [counts]);\n\n return (\n <div className=\"space-y-5\">\n {heading && (\n <div className=\"flex flex-wrap items-start justify-between gap-4\">\n <div className=\"space-y-2\">\n <h1 className=\"text-3xl font-bold\">Creator coins &amp; memecoins</h1>\n <StatPillRow items={statItems} />\n </div>\n {action}\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":[]}
1
+ {"version":3,"sources":["../../src/components/coins-explorer.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useMemo } from \"react\";\nimport { Search } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { CoinRow, CoinRowSkeleton, COIN_GRID, type UseCoinPrice } from \"./coin-row.js\";\nimport { StatPillRow } from \"./stat-tile.js\";\nimport { orderCoins, type CoinPriceSort } from \"../data/coin-order.js\";\nimport { coinKind, coinKindLabelPlural, coinServiceIds, COIN_KINDS, type CoinCollectionLike, type CoinKind } from \"../data/coins.js\";\n\nexport type CoinFilter = \"all\" | CoinKind;\nexport type CoinSort = \"recent\" | \"name\";\nexport type CoinCounts = Record<string, number>;\n\nexport type UseCoins = (opts: { filter: CoinFilter; sort: CoinSort }) => {\n collections: CoinCollectionLike[];\n isLoading: boolean;\n counts?: CoinCounts;\n};\n\nexport type UsePriceMap = () => { prices: Record<string, number | null>; isLoading: boolean };\n\nexport interface CoinsExplorerProps {\n useCoins: UseCoins;\n usePrice: UseCoinPrice;\n usePriceMap?: UsePriceMap;\n\n coinHref: (collection: CoinCollectionLike) => string;\n heading?: boolean;\n\n action?: React.ReactNode;\n}\n\nconst SORT_OPTIONS: { label: string; value: CoinSort }[] = [\n { label: \"Recently launched\", value: \"recent\" },\n { label: \"Name\", value: \"name\" },\n];\n\nconst noPriceMap: UsePriceMap = () => ({ prices: {}, isLoading: false });\n\nexport function CoinsExplorer({ useCoins, usePrice, usePriceMap, coinHref, heading = true, action }: CoinsExplorerProps) {\n const [filter, setFilter] = useState<CoinFilter>(\"all\");\n const [sort, setSort] = useState<CoinSort>(\"recent\");\n const [query, setQuery] = useState(\"\");\n const [priceSort, setPriceSort] = useState<CoinPriceSort | undefined>(undefined);\n\n const { collections, isLoading, counts } = useCoins({ filter, sort });\n const { prices } = (usePriceMap ?? noPriceMap)();\n const items = useMemo(() => {\n const q = query.trim().toLowerCase();\n const matched = q\n ? collections.filter((c) => (c.name ?? \"\").toLowerCase().includes(q) || (c.symbol ?? \"\").toLowerCase().includes(q))\n : collections;\n return usePriceMap ? orderCoins(matched, prices, priceSort) : matched;\n }, [collections, query, usePriceMap, prices, priceSort]);\n\n const showKind = useMemo(() => new Set(items.map((c) => coinKind(c.service))).size > 1, [items]);\n\n const { statItems, pillFilters } = useMemo(() => {\n if (!counts) return { statItems: [], pillFilters: [] as CoinFilter[] };\n const forKind = (kind: CoinKind) =>\n coinServiceIds(kind).reduce((sum, id) => sum + (counts[id] ?? 0), 0);\n const total = Object.values(counts).reduce((a, b) => a + b, 0);\n const kinds = COIN_KINDS.filter((kind) => forKind(kind) > 0);\n return {\n statItems: [\n { label: total === 1 ? \"Coin\" : \"Coins\", value: total },\n ...kinds.map((kind) => ({ label: coinKindLabelPlural(kind), value: forKind(kind) })),\n ],\n pillFilters: [\"all\", ...kinds] as CoinFilter[],\n };\n }, [counts]);\n\n const activeIndex = pillFilters.indexOf(filter);\n\n return (\n <div className=\"space-y-5\">\n {heading && (\n <div className=\"flex flex-wrap items-start justify-between gap-4\">\n <div className=\"space-y-2\">\n <h1 className=\"text-3xl font-bold\">Creator coins &amp; memecoins</h1>\n <StatPillRow\n items={statItems}\n onSelect={(index) => setFilter(pillFilters[index])}\n activeIndex={activeIndex}\n />\n </div>\n {action}\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 <select\n value={sort}\n onChange={(e) => setSort(e.target.value as CoinSort)}\n aria-label=\"Sort coins\"\n className=\"shrink-0 rounded-lg border border-border bg-background px-2.5 py-2 text-xs font-medium text-foreground outline-none focus:border-primary/50\"\n >\n {SORT_OPTIONS.map(({ label, value }) => (\n <option key={value} value={value}>{label}</option>\n ))}\n </select>\n </div>\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 {usePriceMap ? (\n <button\n type=\"button\"\n onClick={() => setPriceSort(priceSort === undefined ? \"desc\" : priceSort === \"desc\" ? \"asc\" : undefined)}\n className={cn(\"text-right uppercase tracking-wide\", priceSort ? \"text-foreground\" : \"text-muted-foreground\")}\n >\n Price{priceSort === \"desc\" ? \" ↓\" : priceSort === \"asc\" ? \" ↑\" : \"\"}\n </button>\n ) : (\n <span className=\"text-right\">Price</span>\n )}\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 </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -14,13 +14,18 @@ type UseCoins = (opts: {
14
14
  isLoading: boolean;
15
15
  counts?: CoinCounts;
16
16
  };
17
+ type UsePriceMap = () => {
18
+ prices: Record<string, number | null>;
19
+ isLoading: boolean;
20
+ };
17
21
  interface CoinsExplorerProps {
18
22
  useCoins: UseCoins;
19
23
  usePrice: UseCoinPrice;
24
+ usePriceMap?: UsePriceMap;
20
25
  coinHref: (collection: CoinCollectionLike) => string;
21
26
  heading?: boolean;
22
27
  action?: React.ReactNode;
23
28
  }
24
- declare function CoinsExplorer({ useCoins, usePrice, coinHref, heading, action }: CoinsExplorerProps): react_jsx_runtime.JSX.Element;
29
+ declare function CoinsExplorer({ useCoins, usePrice, usePriceMap, coinHref, heading, action }: CoinsExplorerProps): react_jsx_runtime.JSX.Element;
25
30
 
26
- export { type CoinCounts, type CoinFilter, type CoinSort, CoinsExplorer, type CoinsExplorerProps, type UseCoins };
31
+ export { type CoinCounts, type CoinFilter, type CoinSort, CoinsExplorer, type CoinsExplorerProps, type UseCoins, type UsePriceMap };
@@ -14,13 +14,18 @@ type UseCoins = (opts: {
14
14
  isLoading: boolean;
15
15
  counts?: CoinCounts;
16
16
  };
17
+ type UsePriceMap = () => {
18
+ prices: Record<string, number | null>;
19
+ isLoading: boolean;
20
+ };
17
21
  interface CoinsExplorerProps {
18
22
  useCoins: UseCoins;
19
23
  usePrice: UseCoinPrice;
24
+ usePriceMap?: UsePriceMap;
20
25
  coinHref: (collection: CoinCollectionLike) => string;
21
26
  heading?: boolean;
22
27
  action?: React.ReactNode;
23
28
  }
24
- declare function CoinsExplorer({ useCoins, usePrice, coinHref, heading, action }: CoinsExplorerProps): react_jsx_runtime.JSX.Element;
29
+ declare function CoinsExplorer({ useCoins, usePrice, usePriceMap, coinHref, heading, action }: CoinsExplorerProps): react_jsx_runtime.JSX.Element;
25
30
 
26
- export { type CoinCounts, type CoinFilter, type CoinSort, CoinsExplorer, type CoinsExplorerProps, type UseCoins };
31
+ export { type CoinCounts, type CoinFilter, type CoinSort, CoinsExplorer, type CoinsExplorerProps, type UseCoins, type UsePriceMap };