@medialane/ui 0.77.0 → 0.78.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.
@@ -0,0 +1,69 @@
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 asset_picker_cell_exports = {};
31
+ __export(asset_picker_cell_exports, {
32
+ AssetPickerCell: () => AssetPickerCell,
33
+ isSameAsset: () => isSameAsset
34
+ });
35
+ module.exports = __toCommonJS(asset_picker_cell_exports);
36
+ var import_jsx_runtime = require("react/jsx-runtime");
37
+ var import_image = __toESM(require("next/image"), 1);
38
+ var import_lucide_react = require("lucide-react");
39
+ var import_cn = require("../utils/cn.js");
40
+ function AssetPickerCell({
41
+ asset,
42
+ active,
43
+ onSelect
44
+ }) {
45
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
46
+ "button",
47
+ {
48
+ type: "button",
49
+ onClick: () => onSelect(asset),
50
+ "aria-pressed": active,
51
+ className: (0, import_cn.cn)(
52
+ "group relative aspect-square rounded-xl overflow-hidden border-2 transition-colors text-left",
53
+ active ? "border-brand-purple" : "border-border/50 active:border-border sm:hover:border-border"
54
+ ),
55
+ children: [
56
+ asset.image ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_image.default, { src: asset.image, alt: asset.name, fill: true, sizes: "120px", className: "object-cover" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-full w-full flex items-center justify-center bg-muted/60", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ImageIcon, { className: "h-5 w-5 text-muted-foreground/50" }) }),
57
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent px-1.5 py-1", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "truncate text-[11px] font-medium text-white", children: asset.name }) }),
58
+ active ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute top-1.5 right-1.5 h-5 w-5 rounded-full bg-brand-purple flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Check, { className: "h-3 w-3 text-white" }) }) : null
59
+ ]
60
+ }
61
+ );
62
+ }
63
+ const isSameAsset = (a, b) => !!b && a.contractAddress === b.contractAddress && a.tokenId === b.tokenId;
64
+ // Annotate the CommonJS export names for ESM import in node:
65
+ 0 && (module.exports = {
66
+ AssetPickerCell,
67
+ isSameAsset
68
+ });
69
+ //# sourceMappingURL=asset-picker-cell.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/asset-picker-cell.tsx"],"sourcesContent":["\"use client\";\n\nimport Image from \"next/image\";\nimport { Check, ImageIcon } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport type { OwnedAsset } from \"./asset-picker.js\";\n\n/** Shared grid-cell button — the image/fallback/name/selected-check tile\n * both `AssetPicker` (my owned assets) and `AssetSearchPicker` (platform\n * search) render identically. Internal, not exported from the package\n * root — both callers stay the public API surface. */\nexport function AssetPickerCell({\n asset, active, onSelect,\n}: { asset: OwnedAsset; active: boolean; onSelect: (asset: OwnedAsset) => void }) {\n return (\n <button\n type=\"button\"\n onClick={() => onSelect(asset)}\n aria-pressed={active}\n className={cn(\n \"group relative aspect-square rounded-xl overflow-hidden border-2 transition-colors text-left\",\n active ? \"border-brand-purple\" : \"border-border/50 active:border-border sm:hover:border-border\",\n )}\n >\n {asset.image ? (\n <Image src={asset.image} alt={asset.name} fill sizes=\"120px\" className=\"object-cover\" />\n ) : (\n <div className=\"h-full w-full flex items-center justify-center bg-muted/60\">\n <ImageIcon className=\"h-5 w-5 text-muted-foreground/50\" />\n </div>\n )}\n <div className=\"absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent px-1.5 py-1\">\n <p className=\"truncate text-[11px] font-medium text-white\">{asset.name}</p>\n </div>\n {active ? (\n <div className=\"absolute top-1.5 right-1.5 h-5 w-5 rounded-full bg-brand-purple flex items-center justify-center\">\n <Check className=\"h-3 w-3 text-white\" />\n </div>\n ) : null}\n </button>\n );\n}\n\nexport const isSameAsset = (a: OwnedAsset, b: OwnedAsset | null) =>\n !!b && a.contractAddress === b.contractAddress && a.tokenId === b.tokenId;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAeI;AAbJ,mBAAkB;AAClB,0BAAiC;AACjC,gBAAmB;AAOZ,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EAAO;AAAA,EAAQ;AACjB,GAAkF;AAChF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,SAAS,MAAM,SAAS,KAAK;AAAA,MAC7B,gBAAc;AAAA,MACd,eAAW;AAAA,QACT;AAAA,QACA,SAAS,wBAAwB;AAAA,MACnC;AAAA,MAEC;AAAA,cAAM,QACL,4CAAC,aAAAA,SAAA,EAAM,KAAK,MAAM,OAAO,KAAK,MAAM,MAAM,MAAI,MAAC,OAAM,SAAQ,WAAU,gBAAe,IAEtF,4CAAC,SAAI,WAAU,8DACb,sDAAC,iCAAU,WAAU,oCAAmC,GAC1D;AAAA,QAEF,4CAAC,SAAI,WAAU,yFACb,sDAAC,OAAE,WAAU,+CAA+C,gBAAM,MAAK,GACzE;AAAA,QACC,SACC,4CAAC,SAAI,WAAU,oGACb,sDAAC,6BAAM,WAAU,sBAAqB,GACxC,IACE;AAAA;AAAA;AAAA,EACN;AAEJ;AAEO,MAAM,cAAc,CAAC,GAAe,MACzC,CAAC,CAAC,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,YAAY,EAAE;","names":["Image"]}
@@ -0,0 +1,15 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { OwnedAsset } from './asset-picker.cjs';
3
+
4
+ /** Shared grid-cell button — the image/fallback/name/selected-check tile
5
+ * both `AssetPicker` (my owned assets) and `AssetSearchPicker` (platform
6
+ * search) render identically. Internal, not exported from the package
7
+ * root — both callers stay the public API surface. */
8
+ declare function AssetPickerCell({ asset, active, onSelect, }: {
9
+ asset: OwnedAsset;
10
+ active: boolean;
11
+ onSelect: (asset: OwnedAsset) => void;
12
+ }): react_jsx_runtime.JSX.Element;
13
+ declare const isSameAsset: (a: OwnedAsset, b: OwnedAsset | null) => boolean;
14
+
15
+ export { AssetPickerCell, isSameAsset };
@@ -0,0 +1,15 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { OwnedAsset } from './asset-picker.js';
3
+
4
+ /** Shared grid-cell button — the image/fallback/name/selected-check tile
5
+ * both `AssetPicker` (my owned assets) and `AssetSearchPicker` (platform
6
+ * search) render identically. Internal, not exported from the package
7
+ * root — both callers stay the public API surface. */
8
+ declare function AssetPickerCell({ asset, active, onSelect, }: {
9
+ asset: OwnedAsset;
10
+ active: boolean;
11
+ onSelect: (asset: OwnedAsset) => void;
12
+ }): react_jsx_runtime.JSX.Element;
13
+ declare const isSameAsset: (a: OwnedAsset, b: OwnedAsset | null) => boolean;
14
+
15
+ export { AssetPickerCell, isSameAsset };
@@ -0,0 +1,34 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import Image from "next/image";
4
+ import { Check, ImageIcon } from "lucide-react";
5
+ import { cn } from "../utils/cn.js";
6
+ function AssetPickerCell({
7
+ asset,
8
+ active,
9
+ onSelect
10
+ }) {
11
+ return /* @__PURE__ */ jsxs(
12
+ "button",
13
+ {
14
+ type: "button",
15
+ onClick: () => onSelect(asset),
16
+ "aria-pressed": active,
17
+ className: cn(
18
+ "group relative aspect-square rounded-xl overflow-hidden border-2 transition-colors text-left",
19
+ active ? "border-brand-purple" : "border-border/50 active:border-border sm:hover:border-border"
20
+ ),
21
+ children: [
22
+ asset.image ? /* @__PURE__ */ jsx(Image, { src: asset.image, alt: asset.name, fill: true, sizes: "120px", className: "object-cover" }) : /* @__PURE__ */ jsx("div", { className: "h-full w-full flex items-center justify-center bg-muted/60", children: /* @__PURE__ */ jsx(ImageIcon, { className: "h-5 w-5 text-muted-foreground/50" }) }),
23
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent px-1.5 py-1", children: /* @__PURE__ */ jsx("p", { className: "truncate text-[11px] font-medium text-white", children: asset.name }) }),
24
+ active ? /* @__PURE__ */ jsx("div", { className: "absolute top-1.5 right-1.5 h-5 w-5 rounded-full bg-brand-purple flex items-center justify-center", children: /* @__PURE__ */ jsx(Check, { className: "h-3 w-3 text-white" }) }) : null
25
+ ]
26
+ }
27
+ );
28
+ }
29
+ const isSameAsset = (a, b) => !!b && a.contractAddress === b.contractAddress && a.tokenId === b.tokenId;
30
+ export {
31
+ AssetPickerCell,
32
+ isSameAsset
33
+ };
34
+ //# sourceMappingURL=asset-picker-cell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/asset-picker-cell.tsx"],"sourcesContent":["\"use client\";\n\nimport Image from \"next/image\";\nimport { Check, ImageIcon } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport type { OwnedAsset } from \"./asset-picker.js\";\n\n/** Shared grid-cell button — the image/fallback/name/selected-check tile\n * both `AssetPicker` (my owned assets) and `AssetSearchPicker` (platform\n * search) render identically. Internal, not exported from the package\n * root — both callers stay the public API surface. */\nexport function AssetPickerCell({\n asset, active, onSelect,\n}: { asset: OwnedAsset; active: boolean; onSelect: (asset: OwnedAsset) => void }) {\n return (\n <button\n type=\"button\"\n onClick={() => onSelect(asset)}\n aria-pressed={active}\n className={cn(\n \"group relative aspect-square rounded-xl overflow-hidden border-2 transition-colors text-left\",\n active ? \"border-brand-purple\" : \"border-border/50 active:border-border sm:hover:border-border\",\n )}\n >\n {asset.image ? (\n <Image src={asset.image} alt={asset.name} fill sizes=\"120px\" className=\"object-cover\" />\n ) : (\n <div className=\"h-full w-full flex items-center justify-center bg-muted/60\">\n <ImageIcon className=\"h-5 w-5 text-muted-foreground/50\" />\n </div>\n )}\n <div className=\"absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent px-1.5 py-1\">\n <p className=\"truncate text-[11px] font-medium text-white\">{asset.name}</p>\n </div>\n {active ? (\n <div className=\"absolute top-1.5 right-1.5 h-5 w-5 rounded-full bg-brand-purple flex items-center justify-center\">\n <Check className=\"h-3 w-3 text-white\" />\n </div>\n ) : null}\n </button>\n );\n}\n\nexport const isSameAsset = (a: OwnedAsset, b: OwnedAsset | null) =>\n !!b && a.contractAddress === b.contractAddress && a.tokenId === b.tokenId;\n"],"mappings":";AAeI,SAUI,KAVJ;AAbJ,OAAO,WAAW;AAClB,SAAS,OAAO,iBAAiB;AACjC,SAAS,UAAU;AAOZ,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EAAO;AAAA,EAAQ;AACjB,GAAkF;AAChF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,SAAS,MAAM,SAAS,KAAK;AAAA,MAC7B,gBAAc;AAAA,MACd,WAAW;AAAA,QACT;AAAA,QACA,SAAS,wBAAwB;AAAA,MACnC;AAAA,MAEC;AAAA,cAAM,QACL,oBAAC,SAAM,KAAK,MAAM,OAAO,KAAK,MAAM,MAAM,MAAI,MAAC,OAAM,SAAQ,WAAU,gBAAe,IAEtF,oBAAC,SAAI,WAAU,8DACb,8BAAC,aAAU,WAAU,oCAAmC,GAC1D;AAAA,QAEF,oBAAC,SAAI,WAAU,yFACb,8BAAC,OAAE,WAAU,+CAA+C,gBAAM,MAAK,GACzE;AAAA,QACC,SACC,oBAAC,SAAI,WAAU,oGACb,8BAAC,SAAM,WAAU,sBAAqB,GACxC,IACE;AAAA;AAAA;AAAA,EACN;AAEJ;AAEO,MAAM,cAAc,CAAC,GAAe,MACzC,CAAC,CAAC,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,YAAY,EAAE;","names":[]}
@@ -1,10 +1,8 @@
1
1
  "use strict";
2
2
  "use client";
3
- var __create = Object.create;
4
3
  var __defProp = Object.defineProperty;
5
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
7
  var __export = (target, all) => {
10
8
  for (var name in all)
@@ -18,14 +16,6 @@ var __copyProps = (to, from, except, desc) => {
18
16
  }
19
17
  return to;
20
18
  };
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
19
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
20
  var asset_picker_exports = {};
31
21
  __export(asset_picker_exports, {
@@ -34,10 +24,9 @@ __export(asset_picker_exports, {
34
24
  module.exports = __toCommonJS(asset_picker_exports);
35
25
  var import_jsx_runtime = require("react/jsx-runtime");
36
26
  var import_react = require("react");
37
- var import_image = __toESM(require("next/image"), 1);
38
27
  var import_lucide_react = require("lucide-react");
39
28
  var import_cn = require("../utils/cn.js");
40
- const isSame = (a, b) => !!b && a.contractAddress === b.contractAddress && a.tokenId === b.tokenId;
29
+ var import_asset_picker_cell = require("./asset-picker-cell.js");
41
30
  function AssetPicker({
42
31
  assets,
43
32
  isLoading,
@@ -72,27 +61,15 @@ function AssetPicker({
72
61
  }
73
62
  )
74
63
  ] }) : null,
75
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid grid-cols-3 sm:grid-cols-4 gap-2.5 max-h-80 overflow-y-auto pr-0.5", children: filtered.map((asset) => {
76
- const active = isSame(asset, selected);
77
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
78
- "button",
79
- {
80
- type: "button",
81
- onClick: () => onSelect(asset),
82
- "aria-pressed": active,
83
- className: (0, import_cn.cn)(
84
- "group relative aspect-square rounded-xl overflow-hidden border-2 transition-colors text-left",
85
- active ? "border-brand-purple" : "border-border/50 active:border-border sm:hover:border-border"
86
- ),
87
- children: [
88
- asset.image ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_image.default, { src: asset.image, alt: asset.name, fill: true, sizes: "120px", className: "object-cover" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-full w-full flex items-center justify-center bg-muted/60", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ImageIcon, { className: "h-5 w-5 text-muted-foreground/50" }) }),
89
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent px-1.5 py-1", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "truncate text-[11px] font-medium text-white", children: asset.name }) }),
90
- active ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute top-1.5 right-1.5 h-5 w-5 rounded-full bg-brand-purple flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Check, { className: "h-3 w-3 text-white" }) }) : null
91
- ]
92
- },
93
- `${asset.contractAddress}-${asset.tokenId}`
94
- );
95
- }) }),
64
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid grid-cols-3 sm:grid-cols-4 gap-2.5 max-h-80 overflow-y-auto pr-0.5", children: filtered.map((asset) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
65
+ import_asset_picker_cell.AssetPickerCell,
66
+ {
67
+ asset,
68
+ active: (0, import_asset_picker_cell.isSameAsset)(asset, selected),
69
+ onSelect
70
+ },
71
+ `${asset.contractAddress}-${asset.tokenId}`
72
+ )) }),
96
73
  query && filtered.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-xs text-muted-foreground text-center py-4", children: [
97
74
  'No assets match "',
98
75
  query,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/asset-picker.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport Image from \"next/image\";\nimport { Check, ImageIcon, Search } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\n\nexport interface OwnedAsset {\n contractAddress: string;\n tokenId: string;\n name: string;\n image: string | null;\n}\n\nexport interface AssetPickerProps {\n /** The caller's already-fetched, already-resolved asset list — this\n * component takes data, it doesn't fetch it. Plug in `useTokensByOwner`\n * (or an app's equivalent) and map to `OwnedAsset` at the call site. */\n assets: OwnedAsset[];\n isLoading: boolean;\n selected: OwnedAsset | null;\n onSelect: (asset: OwnedAsset) => void;\n /** Shown in the empty state when the wallet owns nothing yet. */\n emptyStateHref?: string;\n emptyStateLabel?: string;\n className?: string;\n}\n\nconst isSame = (a: OwnedAsset, b: OwnedAsset | null) =>\n !!b && a.contractAddress === b.contractAddress && a.tokenId === b.tokenId;\n\nexport function AssetPicker({\n assets, isLoading, selected, onSelect, emptyStateHref, emptyStateLabel, className,\n}: AssetPickerProps) {\n const [query, setQuery] = useState(\"\");\n const filtered = query\n ? assets.filter((a) => a.name.toLowerCase().includes(query.toLowerCase()))\n : assets;\n\n if (isLoading) {\n return (\n <div className={cn(\"grid grid-cols-3 sm:grid-cols-4 gap-2.5\", className)}>\n {Array.from({ length: 8 }).map((_, i) => (\n <div key={i} className=\"aspect-square rounded-xl bg-muted/60 animate-pulse\" />\n ))}\n </div>\n );\n }\n\n if (assets.length === 0) {\n return (\n <div className={cn(\"rounded-xl border border-dashed border-border/60 p-6 text-center text-sm text-muted-foreground\", className)}>\n You don&apos;t own any assets yet.\n {emptyStateHref ? (\n <a href={emptyStateHref} className=\"ml-1 font-semibold text-foreground underline underline-offset-2\">\n {emptyStateLabel ?? \"Create one\"}\n </a>\n ) : null}\n </div>\n );\n }\n\n return (\n <div className={cn(\"space-y-3\", className)}>\n {assets.length > 8 ? (\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none\" />\n <input\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search your assets\"\n className=\"w-full h-10 rounded-full border border-border bg-card pl-9 pr-4 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-brand-purple/40 focus:border-brand-purple/40\"\n />\n </div>\n ) : null}\n\n <div className=\"grid grid-cols-3 sm:grid-cols-4 gap-2.5 max-h-80 overflow-y-auto pr-0.5\">\n {filtered.map((asset) => {\n const active = isSame(asset, selected);\n return (\n <button\n key={`${asset.contractAddress}-${asset.tokenId}`}\n type=\"button\"\n onClick={() => onSelect(asset)}\n aria-pressed={active}\n className={cn(\n \"group relative aspect-square rounded-xl overflow-hidden border-2 transition-colors text-left\",\n active ? \"border-brand-purple\" : \"border-border/50 active:border-border sm:hover:border-border\",\n )}\n >\n {asset.image ? (\n <Image src={asset.image} alt={asset.name} fill sizes=\"120px\" className=\"object-cover\" />\n ) : (\n <div className=\"h-full w-full flex items-center justify-center bg-muted/60\">\n <ImageIcon className=\"h-5 w-5 text-muted-foreground/50\" />\n </div>\n )}\n <div className=\"absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent px-1.5 py-1\">\n <p className=\"truncate text-[11px] font-medium text-white\">{asset.name}</p>\n </div>\n {active ? (\n <div className=\"absolute top-1.5 right-1.5 h-5 w-5 rounded-full bg-brand-purple flex items-center justify-center\">\n <Check className=\"h-3 w-3 text-white\" />\n </div>\n ) : null}\n </button>\n );\n })}\n </div>\n\n {query && filtered.length === 0 ? (\n <p className=\"text-xs text-muted-foreground text-center py-4\">No assets match &quot;{query}&quot;.</p>\n ) : null}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA2CU;AAzCV,mBAAyB;AACzB,mBAAkB;AAClB,0BAAyC;AACzC,gBAAmB;AAuBnB,MAAM,SAAS,CAAC,GAAe,MAC7B,CAAC,CAAC,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,YAAY,EAAE;AAE7D,SAAS,YAAY;AAAA,EAC1B;AAAA,EAAQ;AAAA,EAAW;AAAA,EAAU;AAAA,EAAU;AAAA,EAAgB;AAAA,EAAiB;AAC1E,GAAqB;AACnB,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAS,EAAE;AACrC,QAAM,WAAW,QACb,OAAO,OAAO,CAAC,MAAM,EAAE,KAAK,YAAY,EAAE,SAAS,MAAM,YAAY,CAAC,CAAC,IACvE;AAEJ,MAAI,WAAW;AACb,WACE,4CAAC,SAAI,eAAW,cAAG,2CAA2C,SAAS,GACpE,gBAAM,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,MACjC,4CAAC,SAAY,WAAU,wDAAb,CAAkE,CAC7E,GACH;AAAA,EAEJ;AAEA,MAAI,OAAO,WAAW,GAAG;AACvB,WACE,6CAAC,SAAI,eAAW,cAAG,kGAAkG,SAAS,GAAG;AAAA;AAAA,MAE9H,iBACC,4CAAC,OAAE,MAAM,gBAAgB,WAAU,mEAChC,6BAAmB,cACtB,IACE;AAAA,OACN;AAAA,EAEJ;AAEA,SACE,6CAAC,SAAI,eAAW,cAAG,aAAa,SAAS,GACtC;AAAA,WAAO,SAAS,IACf,6CAAC,SAAI,WAAU,YACb;AAAA,kDAAC,8BAAO,WAAU,8FAA6F;AAAA,MAC/G;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,UACxC,aAAY;AAAA,UACZ,WAAU;AAAA;AAAA,MACZ;AAAA,OACF,IACE;AAAA,IAEJ,4CAAC,SAAI,WAAU,2EACZ,mBAAS,IAAI,CAAC,UAAU;AACvB,YAAM,SAAS,OAAO,OAAO,QAAQ;AACrC,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,SAAS,MAAM,SAAS,KAAK;AAAA,UAC7B,gBAAc;AAAA,UACd,eAAW;AAAA,YACT;AAAA,YACA,SAAS,wBAAwB;AAAA,UACnC;AAAA,UAEC;AAAA,kBAAM,QACL,4CAAC,aAAAA,SAAA,EAAM,KAAK,MAAM,OAAO,KAAK,MAAM,MAAM,MAAI,MAAC,OAAM,SAAQ,WAAU,gBAAe,IAEtF,4CAAC,SAAI,WAAU,8DACb,sDAAC,iCAAU,WAAU,oCAAmC,GAC1D;AAAA,YAEF,4CAAC,SAAI,WAAU,yFACb,sDAAC,OAAE,WAAU,+CAA+C,gBAAM,MAAK,GACzE;AAAA,YACC,SACC,4CAAC,SAAI,WAAU,oGACb,sDAAC,6BAAM,WAAU,sBAAqB,GACxC,IACE;AAAA;AAAA;AAAA,QAvBC,GAAG,MAAM,eAAe,IAAI,MAAM,OAAO;AAAA,MAwBhD;AAAA,IAEJ,CAAC,GACH;AAAA,IAEC,SAAS,SAAS,WAAW,IAC5B,6CAAC,OAAE,WAAU,kDAAiD;AAAA;AAAA,MAAuB;AAAA,MAAM;AAAA,OAAO,IAChG;AAAA,KACN;AAEJ;","names":["Image"]}
1
+ {"version":3,"sources":["../../src/components/asset-picker.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport { Search } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { AssetPickerCell, isSameAsset } from \"./asset-picker-cell.js\";\n\nexport interface OwnedAsset {\n contractAddress: string;\n tokenId: string;\n name: string;\n image: string | null;\n}\n\nexport interface AssetPickerProps {\n /** The caller's already-fetched, already-resolved asset list — this\n * component takes data, it doesn't fetch it. Plug in `useTokensByOwner`\n * (or an app's equivalent) and map to `OwnedAsset` at the call site. */\n assets: OwnedAsset[];\n isLoading: boolean;\n selected: OwnedAsset | null;\n onSelect: (asset: OwnedAsset) => void;\n /** Shown in the empty state when the wallet owns nothing yet. */\n emptyStateHref?: string;\n emptyStateLabel?: string;\n className?: string;\n}\n\nexport function AssetPicker({\n assets, isLoading, selected, onSelect, emptyStateHref, emptyStateLabel, className,\n}: AssetPickerProps) {\n const [query, setQuery] = useState(\"\");\n const filtered = query\n ? assets.filter((a) => a.name.toLowerCase().includes(query.toLowerCase()))\n : assets;\n\n if (isLoading) {\n return (\n <div className={cn(\"grid grid-cols-3 sm:grid-cols-4 gap-2.5\", className)}>\n {Array.from({ length: 8 }).map((_, i) => (\n <div key={i} className=\"aspect-square rounded-xl bg-muted/60 animate-pulse\" />\n ))}\n </div>\n );\n }\n\n if (assets.length === 0) {\n return (\n <div className={cn(\"rounded-xl border border-dashed border-border/60 p-6 text-center text-sm text-muted-foreground\", className)}>\n You don&apos;t own any assets yet.\n {emptyStateHref ? (\n <a href={emptyStateHref} className=\"ml-1 font-semibold text-foreground underline underline-offset-2\">\n {emptyStateLabel ?? \"Create one\"}\n </a>\n ) : null}\n </div>\n );\n }\n\n return (\n <div className={cn(\"space-y-3\", className)}>\n {assets.length > 8 ? (\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none\" />\n <input\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search your assets\"\n className=\"w-full h-10 rounded-full border border-border bg-card pl-9 pr-4 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-brand-purple/40 focus:border-brand-purple/40\"\n />\n </div>\n ) : null}\n\n <div className=\"grid grid-cols-3 sm:grid-cols-4 gap-2.5 max-h-80 overflow-y-auto pr-0.5\">\n {filtered.map((asset) => (\n <AssetPickerCell\n key={`${asset.contractAddress}-${asset.tokenId}`}\n asset={asset}\n active={isSameAsset(asset, selected)}\n onSelect={onSelect}\n />\n ))}\n </div>\n\n {query && filtered.length === 0 ? (\n <p className=\"text-xs text-muted-foreground text-center py-4\">No assets match &quot;{query}&quot;.</p>\n ) : null}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAwCU;AAtCV,mBAAyB;AACzB,0BAAuB;AACvB,gBAAmB;AACnB,+BAA6C;AAuBtC,SAAS,YAAY;AAAA,EAC1B;AAAA,EAAQ;AAAA,EAAW;AAAA,EAAU;AAAA,EAAU;AAAA,EAAgB;AAAA,EAAiB;AAC1E,GAAqB;AACnB,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAS,EAAE;AACrC,QAAM,WAAW,QACb,OAAO,OAAO,CAAC,MAAM,EAAE,KAAK,YAAY,EAAE,SAAS,MAAM,YAAY,CAAC,CAAC,IACvE;AAEJ,MAAI,WAAW;AACb,WACE,4CAAC,SAAI,eAAW,cAAG,2CAA2C,SAAS,GACpE,gBAAM,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,MACjC,4CAAC,SAAY,WAAU,wDAAb,CAAkE,CAC7E,GACH;AAAA,EAEJ;AAEA,MAAI,OAAO,WAAW,GAAG;AACvB,WACE,6CAAC,SAAI,eAAW,cAAG,kGAAkG,SAAS,GAAG;AAAA;AAAA,MAE9H,iBACC,4CAAC,OAAE,MAAM,gBAAgB,WAAU,mEAChC,6BAAmB,cACtB,IACE;AAAA,OACN;AAAA,EAEJ;AAEA,SACE,6CAAC,SAAI,eAAW,cAAG,aAAa,SAAS,GACtC;AAAA,WAAO,SAAS,IACf,6CAAC,SAAI,WAAU,YACb;AAAA,kDAAC,8BAAO,WAAU,8FAA6F;AAAA,MAC/G;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,UACxC,aAAY;AAAA,UACZ,WAAU;AAAA;AAAA,MACZ;AAAA,OACF,IACE;AAAA,IAEJ,4CAAC,SAAI,WAAU,2EACZ,mBAAS,IAAI,CAAC,UACb;AAAA,MAAC;AAAA;AAAA,QAEC;AAAA,QACA,YAAQ,sCAAY,OAAO,QAAQ;AAAA,QACnC;AAAA;AAAA,MAHK,GAAG,MAAM,eAAe,IAAI,MAAM,OAAO;AAAA,IAIhD,CACD,GACH;AAAA,IAEC,SAAS,SAAS,WAAW,IAC5B,6CAAC,OAAE,WAAU,kDAAiD;AAAA;AAAA,MAAuB;AAAA,MAAM;AAAA,OAAO,IAChG;AAAA,KACN;AAEJ;","names":[]}
@@ -1,10 +1,9 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
- import Image from "next/image";
5
- import { Check, ImageIcon, Search } from "lucide-react";
4
+ import { Search } from "lucide-react";
6
5
  import { cn } from "../utils/cn.js";
7
- const isSame = (a, b) => !!b && a.contractAddress === b.contractAddress && a.tokenId === b.tokenId;
6
+ import { AssetPickerCell, isSameAsset } from "./asset-picker-cell.js";
8
7
  function AssetPicker({
9
8
  assets,
10
9
  isLoading,
@@ -39,27 +38,15 @@ function AssetPicker({
39
38
  }
40
39
  )
41
40
  ] }) : null,
42
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 sm:grid-cols-4 gap-2.5 max-h-80 overflow-y-auto pr-0.5", children: filtered.map((asset) => {
43
- const active = isSame(asset, selected);
44
- return /* @__PURE__ */ jsxs(
45
- "button",
46
- {
47
- type: "button",
48
- onClick: () => onSelect(asset),
49
- "aria-pressed": active,
50
- className: cn(
51
- "group relative aspect-square rounded-xl overflow-hidden border-2 transition-colors text-left",
52
- active ? "border-brand-purple" : "border-border/50 active:border-border sm:hover:border-border"
53
- ),
54
- children: [
55
- asset.image ? /* @__PURE__ */ jsx(Image, { src: asset.image, alt: asset.name, fill: true, sizes: "120px", className: "object-cover" }) : /* @__PURE__ */ jsx("div", { className: "h-full w-full flex items-center justify-center bg-muted/60", children: /* @__PURE__ */ jsx(ImageIcon, { className: "h-5 w-5 text-muted-foreground/50" }) }),
56
- /* @__PURE__ */ jsx("div", { className: "absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent px-1.5 py-1", children: /* @__PURE__ */ jsx("p", { className: "truncate text-[11px] font-medium text-white", children: asset.name }) }),
57
- active ? /* @__PURE__ */ jsx("div", { className: "absolute top-1.5 right-1.5 h-5 w-5 rounded-full bg-brand-purple flex items-center justify-center", children: /* @__PURE__ */ jsx(Check, { className: "h-3 w-3 text-white" }) }) : null
58
- ]
59
- },
60
- `${asset.contractAddress}-${asset.tokenId}`
61
- );
62
- }) }),
41
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 sm:grid-cols-4 gap-2.5 max-h-80 overflow-y-auto pr-0.5", children: filtered.map((asset) => /* @__PURE__ */ jsx(
42
+ AssetPickerCell,
43
+ {
44
+ asset,
45
+ active: isSameAsset(asset, selected),
46
+ onSelect
47
+ },
48
+ `${asset.contractAddress}-${asset.tokenId}`
49
+ )) }),
63
50
  query && filtered.length === 0 ? /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground text-center py-4", children: [
64
51
  'No assets match "',
65
52
  query,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/asset-picker.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport Image from \"next/image\";\nimport { Check, ImageIcon, Search } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\n\nexport interface OwnedAsset {\n contractAddress: string;\n tokenId: string;\n name: string;\n image: string | null;\n}\n\nexport interface AssetPickerProps {\n /** The caller's already-fetched, already-resolved asset list — this\n * component takes data, it doesn't fetch it. Plug in `useTokensByOwner`\n * (or an app's equivalent) and map to `OwnedAsset` at the call site. */\n assets: OwnedAsset[];\n isLoading: boolean;\n selected: OwnedAsset | null;\n onSelect: (asset: OwnedAsset) => void;\n /** Shown in the empty state when the wallet owns nothing yet. */\n emptyStateHref?: string;\n emptyStateLabel?: string;\n className?: string;\n}\n\nconst isSame = (a: OwnedAsset, b: OwnedAsset | null) =>\n !!b && a.contractAddress === b.contractAddress && a.tokenId === b.tokenId;\n\nexport function AssetPicker({\n assets, isLoading, selected, onSelect, emptyStateHref, emptyStateLabel, className,\n}: AssetPickerProps) {\n const [query, setQuery] = useState(\"\");\n const filtered = query\n ? assets.filter((a) => a.name.toLowerCase().includes(query.toLowerCase()))\n : assets;\n\n if (isLoading) {\n return (\n <div className={cn(\"grid grid-cols-3 sm:grid-cols-4 gap-2.5\", className)}>\n {Array.from({ length: 8 }).map((_, i) => (\n <div key={i} className=\"aspect-square rounded-xl bg-muted/60 animate-pulse\" />\n ))}\n </div>\n );\n }\n\n if (assets.length === 0) {\n return (\n <div className={cn(\"rounded-xl border border-dashed border-border/60 p-6 text-center text-sm text-muted-foreground\", className)}>\n You don&apos;t own any assets yet.\n {emptyStateHref ? (\n <a href={emptyStateHref} className=\"ml-1 font-semibold text-foreground underline underline-offset-2\">\n {emptyStateLabel ?? \"Create one\"}\n </a>\n ) : null}\n </div>\n );\n }\n\n return (\n <div className={cn(\"space-y-3\", className)}>\n {assets.length > 8 ? (\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none\" />\n <input\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search your assets\"\n className=\"w-full h-10 rounded-full border border-border bg-card pl-9 pr-4 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-brand-purple/40 focus:border-brand-purple/40\"\n />\n </div>\n ) : null}\n\n <div className=\"grid grid-cols-3 sm:grid-cols-4 gap-2.5 max-h-80 overflow-y-auto pr-0.5\">\n {filtered.map((asset) => {\n const active = isSame(asset, selected);\n return (\n <button\n key={`${asset.contractAddress}-${asset.tokenId}`}\n type=\"button\"\n onClick={() => onSelect(asset)}\n aria-pressed={active}\n className={cn(\n \"group relative aspect-square rounded-xl overflow-hidden border-2 transition-colors text-left\",\n active ? \"border-brand-purple\" : \"border-border/50 active:border-border sm:hover:border-border\",\n )}\n >\n {asset.image ? (\n <Image src={asset.image} alt={asset.name} fill sizes=\"120px\" className=\"object-cover\" />\n ) : (\n <div className=\"h-full w-full flex items-center justify-center bg-muted/60\">\n <ImageIcon className=\"h-5 w-5 text-muted-foreground/50\" />\n </div>\n )}\n <div className=\"absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent px-1.5 py-1\">\n <p className=\"truncate text-[11px] font-medium text-white\">{asset.name}</p>\n </div>\n {active ? (\n <div className=\"absolute top-1.5 right-1.5 h-5 w-5 rounded-full bg-brand-purple flex items-center justify-center\">\n <Check className=\"h-3 w-3 text-white\" />\n </div>\n ) : null}\n </button>\n );\n })}\n </div>\n\n {query && filtered.length === 0 ? (\n <p className=\"text-xs text-muted-foreground text-center py-4\">No assets match &quot;{query}&quot;.</p>\n ) : null}\n </div>\n );\n}\n"],"mappings":";AA2CU,cAQJ,YARI;AAzCV,SAAS,gBAAgB;AACzB,OAAO,WAAW;AAClB,SAAS,OAAO,WAAW,cAAc;AACzC,SAAS,UAAU;AAuBnB,MAAM,SAAS,CAAC,GAAe,MAC7B,CAAC,CAAC,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,YAAY,EAAE;AAE7D,SAAS,YAAY;AAAA,EAC1B;AAAA,EAAQ;AAAA,EAAW;AAAA,EAAU;AAAA,EAAU;AAAA,EAAgB;AAAA,EAAiB;AAC1E,GAAqB;AACnB,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,WAAW,QACb,OAAO,OAAO,CAAC,MAAM,EAAE,KAAK,YAAY,EAAE,SAAS,MAAM,YAAY,CAAC,CAAC,IACvE;AAEJ,MAAI,WAAW;AACb,WACE,oBAAC,SAAI,WAAW,GAAG,2CAA2C,SAAS,GACpE,gBAAM,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,MACjC,oBAAC,SAAY,WAAU,wDAAb,CAAkE,CAC7E,GACH;AAAA,EAEJ;AAEA,MAAI,OAAO,WAAW,GAAG;AACvB,WACE,qBAAC,SAAI,WAAW,GAAG,kGAAkG,SAAS,GAAG;AAAA;AAAA,MAE9H,iBACC,oBAAC,OAAE,MAAM,gBAAgB,WAAU,mEAChC,6BAAmB,cACtB,IACE;AAAA,OACN;AAAA,EAEJ;AAEA,SACE,qBAAC,SAAI,WAAW,GAAG,aAAa,SAAS,GACtC;AAAA,WAAO,SAAS,IACf,qBAAC,SAAI,WAAU,YACb;AAAA,0BAAC,UAAO,WAAU,8FAA6F;AAAA,MAC/G;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,UACxC,aAAY;AAAA,UACZ,WAAU;AAAA;AAAA,MACZ;AAAA,OACF,IACE;AAAA,IAEJ,oBAAC,SAAI,WAAU,2EACZ,mBAAS,IAAI,CAAC,UAAU;AACvB,YAAM,SAAS,OAAO,OAAO,QAAQ;AACrC,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,SAAS,MAAM,SAAS,KAAK;AAAA,UAC7B,gBAAc;AAAA,UACd,WAAW;AAAA,YACT;AAAA,YACA,SAAS,wBAAwB;AAAA,UACnC;AAAA,UAEC;AAAA,kBAAM,QACL,oBAAC,SAAM,KAAK,MAAM,OAAO,KAAK,MAAM,MAAM,MAAI,MAAC,OAAM,SAAQ,WAAU,gBAAe,IAEtF,oBAAC,SAAI,WAAU,8DACb,8BAAC,aAAU,WAAU,oCAAmC,GAC1D;AAAA,YAEF,oBAAC,SAAI,WAAU,yFACb,8BAAC,OAAE,WAAU,+CAA+C,gBAAM,MAAK,GACzE;AAAA,YACC,SACC,oBAAC,SAAI,WAAU,oGACb,8BAAC,SAAM,WAAU,sBAAqB,GACxC,IACE;AAAA;AAAA;AAAA,QAvBC,GAAG,MAAM,eAAe,IAAI,MAAM,OAAO;AAAA,MAwBhD;AAAA,IAEJ,CAAC,GACH;AAAA,IAEC,SAAS,SAAS,WAAW,IAC5B,qBAAC,OAAE,WAAU,kDAAiD;AAAA;AAAA,MAAuB;AAAA,MAAM;AAAA,OAAO,IAChG;AAAA,KACN;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/asset-picker.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport { Search } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { AssetPickerCell, isSameAsset } from \"./asset-picker-cell.js\";\n\nexport interface OwnedAsset {\n contractAddress: string;\n tokenId: string;\n name: string;\n image: string | null;\n}\n\nexport interface AssetPickerProps {\n /** The caller's already-fetched, already-resolved asset list — this\n * component takes data, it doesn't fetch it. Plug in `useTokensByOwner`\n * (or an app's equivalent) and map to `OwnedAsset` at the call site. */\n assets: OwnedAsset[];\n isLoading: boolean;\n selected: OwnedAsset | null;\n onSelect: (asset: OwnedAsset) => void;\n /** Shown in the empty state when the wallet owns nothing yet. */\n emptyStateHref?: string;\n emptyStateLabel?: string;\n className?: string;\n}\n\nexport function AssetPicker({\n assets, isLoading, selected, onSelect, emptyStateHref, emptyStateLabel, className,\n}: AssetPickerProps) {\n const [query, setQuery] = useState(\"\");\n const filtered = query\n ? assets.filter((a) => a.name.toLowerCase().includes(query.toLowerCase()))\n : assets;\n\n if (isLoading) {\n return (\n <div className={cn(\"grid grid-cols-3 sm:grid-cols-4 gap-2.5\", className)}>\n {Array.from({ length: 8 }).map((_, i) => (\n <div key={i} className=\"aspect-square rounded-xl bg-muted/60 animate-pulse\" />\n ))}\n </div>\n );\n }\n\n if (assets.length === 0) {\n return (\n <div className={cn(\"rounded-xl border border-dashed border-border/60 p-6 text-center text-sm text-muted-foreground\", className)}>\n You don&apos;t own any assets yet.\n {emptyStateHref ? (\n <a href={emptyStateHref} className=\"ml-1 font-semibold text-foreground underline underline-offset-2\">\n {emptyStateLabel ?? \"Create one\"}\n </a>\n ) : null}\n </div>\n );\n }\n\n return (\n <div className={cn(\"space-y-3\", className)}>\n {assets.length > 8 ? (\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none\" />\n <input\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search your assets\"\n className=\"w-full h-10 rounded-full border border-border bg-card pl-9 pr-4 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-brand-purple/40 focus:border-brand-purple/40\"\n />\n </div>\n ) : null}\n\n <div className=\"grid grid-cols-3 sm:grid-cols-4 gap-2.5 max-h-80 overflow-y-auto pr-0.5\">\n {filtered.map((asset) => (\n <AssetPickerCell\n key={`${asset.contractAddress}-${asset.tokenId}`}\n asset={asset}\n active={isSameAsset(asset, selected)}\n onSelect={onSelect}\n />\n ))}\n </div>\n\n {query && filtered.length === 0 ? (\n <p className=\"text-xs text-muted-foreground text-center py-4\">No assets match &quot;{query}&quot;.</p>\n ) : null}\n </div>\n );\n}\n"],"mappings":";AAwCU,cAQJ,YARI;AAtCV,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,SAAS,UAAU;AACnB,SAAS,iBAAiB,mBAAmB;AAuBtC,SAAS,YAAY;AAAA,EAC1B;AAAA,EAAQ;AAAA,EAAW;AAAA,EAAU;AAAA,EAAU;AAAA,EAAgB;AAAA,EAAiB;AAC1E,GAAqB;AACnB,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,WAAW,QACb,OAAO,OAAO,CAAC,MAAM,EAAE,KAAK,YAAY,EAAE,SAAS,MAAM,YAAY,CAAC,CAAC,IACvE;AAEJ,MAAI,WAAW;AACb,WACE,oBAAC,SAAI,WAAW,GAAG,2CAA2C,SAAS,GACpE,gBAAM,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,MACjC,oBAAC,SAAY,WAAU,wDAAb,CAAkE,CAC7E,GACH;AAAA,EAEJ;AAEA,MAAI,OAAO,WAAW,GAAG;AACvB,WACE,qBAAC,SAAI,WAAW,GAAG,kGAAkG,SAAS,GAAG;AAAA;AAAA,MAE9H,iBACC,oBAAC,OAAE,MAAM,gBAAgB,WAAU,mEAChC,6BAAmB,cACtB,IACE;AAAA,OACN;AAAA,EAEJ;AAEA,SACE,qBAAC,SAAI,WAAW,GAAG,aAAa,SAAS,GACtC;AAAA,WAAO,SAAS,IACf,qBAAC,SAAI,WAAU,YACb;AAAA,0BAAC,UAAO,WAAU,8FAA6F;AAAA,MAC/G;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,UACxC,aAAY;AAAA,UACZ,WAAU;AAAA;AAAA,MACZ;AAAA,OACF,IACE;AAAA,IAEJ,oBAAC,SAAI,WAAU,2EACZ,mBAAS,IAAI,CAAC,UACb;AAAA,MAAC;AAAA;AAAA,QAEC;AAAA,QACA,QAAQ,YAAY,OAAO,QAAQ;AAAA,QACnC;AAAA;AAAA,MAHK,GAAG,MAAM,eAAe,IAAI,MAAM,OAAO;AAAA,IAIhD,CACD,GACH;AAAA,IAEC,SAAS,SAAS,WAAW,IAC5B,qBAAC,OAAE,WAAU,kDAAiD;AAAA;AAAA,MAAuB;AAAA,MAAM;AAAA,OAAO,IAChG;AAAA,KACN;AAEJ;","names":[]}
@@ -1,10 +1,8 @@
1
1
  "use strict";
2
2
  "use client";
3
- var __create = Object.create;
4
3
  var __defProp = Object.defineProperty;
5
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
7
  var __export = (target, all) => {
10
8
  for (var name in all)
@@ -18,14 +16,6 @@ var __copyProps = (to, from, except, desc) => {
18
16
  }
19
17
  return to;
20
18
  };
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
19
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
20
  var asset_search_picker_exports = {};
31
21
  __export(asset_search_picker_exports, {
@@ -34,10 +24,9 @@ __export(asset_search_picker_exports, {
34
24
  module.exports = __toCommonJS(asset_search_picker_exports);
35
25
  var import_jsx_runtime = require("react/jsx-runtime");
36
26
  var import_react = require("react");
37
- var import_image = __toESM(require("next/image"), 1);
38
27
  var import_lucide_react = require("lucide-react");
39
28
  var import_cn = require("../utils/cn.js");
40
- const isSame = (a, b) => !!b && a.contractAddress === b.contractAddress && a.tokenId === b.tokenId;
29
+ var import_asset_picker_cell = require("./asset-picker-cell.js");
41
30
  function AssetSearchPicker({
42
31
  search,
43
32
  selected,
@@ -89,27 +78,15 @@ function AssetSearchPicker({
89
78
  isLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Loader2, { className: "absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 animate-spin text-muted-foreground" }) : null
90
79
  ] }),
91
80
  query.trim().length > 0 && query.trim().length < 2 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-xs text-muted-foreground text-center py-2", children: "Keep typing \u2014 2+ characters to search." }) : null,
92
- results.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid grid-cols-3 sm:grid-cols-4 gap-2.5 max-h-80 overflow-y-auto pr-0.5", children: results.map((asset) => {
93
- const active = isSame(asset, selected);
94
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
95
- "button",
96
- {
97
- type: "button",
98
- onClick: () => onSelect(asset),
99
- "aria-pressed": active,
100
- className: (0, import_cn.cn)(
101
- "group relative aspect-square rounded-xl overflow-hidden border-2 transition-colors text-left",
102
- active ? "border-brand-purple" : "border-border/50 active:border-border sm:hover:border-border"
103
- ),
104
- children: [
105
- asset.image ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_image.default, { src: asset.image, alt: asset.name, fill: true, sizes: "120px", className: "object-cover" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-full w-full flex items-center justify-center bg-muted/60", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ImageIcon, { className: "h-5 w-5 text-muted-foreground/50" }) }),
106
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent px-1.5 py-1", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "truncate text-[11px] font-medium text-white", children: asset.name }) }),
107
- active ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute top-1.5 right-1.5 h-5 w-5 rounded-full bg-brand-purple flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Check, { className: "h-3 w-3 text-white" }) }) : null
108
- ]
109
- },
110
- `${asset.contractAddress}-${asset.tokenId}`
111
- );
112
- }) }) : hasSearched && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-xs text-muted-foreground text-center py-4", children: [
81
+ results.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid grid-cols-3 sm:grid-cols-4 gap-2.5 max-h-80 overflow-y-auto pr-0.5", children: results.map((asset) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
82
+ import_asset_picker_cell.AssetPickerCell,
83
+ {
84
+ asset,
85
+ active: (0, import_asset_picker_cell.isSameAsset)(asset, selected),
86
+ onSelect
87
+ },
88
+ `${asset.contractAddress}-${asset.tokenId}`
89
+ )) }) : hasSearched && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-xs text-muted-foreground text-center py-4", children: [
113
90
  'No assets match "',
114
91
  query.trim(),
115
92
  '".'
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/asset-search-picker.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport Image from \"next/image\";\nimport { Check, ImageIcon, Loader2, Search } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport type { OwnedAsset } from \"./asset-picker.js\";\n\nexport interface AssetSearchPickerProps {\n /** App-supplied search — the app owns the actual fetch (e.g. GET /v1/search).\n * Called only when the query is 2+ chars, debounced by this component. */\n search: (query: string) => Promise<OwnedAsset[]>;\n selected: OwnedAsset | null;\n onSelect: (asset: OwnedAsset) => void;\n placeholder?: string;\n className?: string;\n}\n\nconst isSame = (a: OwnedAsset, b: OwnedAsset | null) =>\n !!b && a.contractAddress === b.contractAddress && a.tokenId === b.tokenId;\n\nexport function AssetSearchPicker({\n search, selected, onSelect, placeholder = \"Search for an asset or creator\", className,\n}: AssetSearchPickerProps) {\n const [query, setQuery] = useState(\"\");\n const [results, setResults] = useState<OwnedAsset[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n const [hasSearched, setHasSearched] = useState(false);\n\n useEffect(() => {\n const q = query.trim();\n if (q.length < 2) {\n setResults([]);\n setHasSearched(false);\n return;\n }\n let cancelled = false;\n setIsLoading(true);\n const timer = setTimeout(() => {\n search(q)\n .then((assets) => {\n if (!cancelled) {\n setResults(assets);\n setHasSearched(true);\n }\n })\n .finally(() => {\n if (!cancelled) setIsLoading(false);\n });\n }, 350);\n return () => {\n cancelled = true;\n clearTimeout(timer);\n };\n }, [query, search]);\n\n return (\n <div className={cn(\"space-y-3\", className)}>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none\" />\n <input\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={placeholder}\n className=\"w-full h-10 rounded-full border border-border bg-card pl-9 pr-4 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-brand-purple/40 focus:border-brand-purple/40\"\n />\n {isLoading ? (\n <Loader2 className=\"absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 animate-spin text-muted-foreground\" />\n ) : null}\n </div>\n\n {query.trim().length > 0 && query.trim().length < 2 ? (\n <p className=\"text-xs text-muted-foreground text-center py-2\">Keep typing — 2+ characters to search.</p>\n ) : null}\n\n {results.length > 0 ? (\n <div className=\"grid grid-cols-3 sm:grid-cols-4 gap-2.5 max-h-80 overflow-y-auto pr-0.5\">\n {results.map((asset) => {\n const active = isSame(asset, selected);\n return (\n <button\n key={`${asset.contractAddress}-${asset.tokenId}`}\n type=\"button\"\n onClick={() => onSelect(asset)}\n aria-pressed={active}\n className={cn(\n \"group relative aspect-square rounded-xl overflow-hidden border-2 transition-colors text-left\",\n active ? \"border-brand-purple\" : \"border-border/50 active:border-border sm:hover:border-border\",\n )}\n >\n {asset.image ? (\n <Image src={asset.image} alt={asset.name} fill sizes=\"120px\" className=\"object-cover\" />\n ) : (\n <div className=\"h-full w-full flex items-center justify-center bg-muted/60\">\n <ImageIcon className=\"h-5 w-5 text-muted-foreground/50\" />\n </div>\n )}\n <div className=\"absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent px-1.5 py-1\">\n <p className=\"truncate text-[11px] font-medium text-white\">{asset.name}</p>\n </div>\n {active ? (\n <div className=\"absolute top-1.5 right-1.5 h-5 w-5 rounded-full bg-brand-purple flex items-center justify-center\">\n <Check className=\"h-3 w-3 text-white\" />\n </div>\n ) : null}\n </button>\n );\n })}\n </div>\n ) : hasSearched && !isLoading ? (\n <p className=\"text-xs text-muted-foreground text-center py-4\">No assets match &quot;{query.trim()}&quot;.</p>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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","names":["Image"]}
1
+ {"version":3,"sources":["../../src/components/asset-search-picker.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { Loader2, Search } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport type { OwnedAsset } from \"./asset-picker.js\";\nimport { AssetPickerCell, isSameAsset } from \"./asset-picker-cell.js\";\n\nexport interface AssetSearchPickerProps {\n /** App-supplied search — the app owns the actual fetch (e.g. GET /v1/search).\n * Called only when the query is 2+ chars, debounced by this component. */\n search: (query: string) => Promise<OwnedAsset[]>;\n selected: OwnedAsset | null;\n onSelect: (asset: OwnedAsset) => void;\n placeholder?: string;\n className?: string;\n}\n\nexport function AssetSearchPicker({\n search, selected, onSelect, placeholder = \"Search for an asset or creator\", className,\n}: AssetSearchPickerProps) {\n const [query, setQuery] = useState(\"\");\n const [results, setResults] = useState<OwnedAsset[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n const [hasSearched, setHasSearched] = useState(false);\n\n useEffect(() => {\n const q = query.trim();\n if (q.length < 2) {\n setResults([]);\n setHasSearched(false);\n return;\n }\n let cancelled = false;\n setIsLoading(true);\n const timer = setTimeout(() => {\n search(q)\n .then((assets) => {\n if (!cancelled) {\n setResults(assets);\n setHasSearched(true);\n }\n })\n .finally(() => {\n if (!cancelled) setIsLoading(false);\n });\n }, 350);\n return () => {\n cancelled = true;\n clearTimeout(timer);\n };\n }, [query, search]);\n\n return (\n <div className={cn(\"space-y-3\", className)}>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none\" />\n <input\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={placeholder}\n className=\"w-full h-10 rounded-full border border-border bg-card pl-9 pr-4 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-brand-purple/40 focus:border-brand-purple/40\"\n />\n {isLoading ? (\n <Loader2 className=\"absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 animate-spin text-muted-foreground\" />\n ) : null}\n </div>\n\n {query.trim().length > 0 && query.trim().length < 2 ? (\n <p className=\"text-xs text-muted-foreground text-center py-2\">Keep typing — 2+ characters to search.</p>\n ) : null}\n\n {results.length > 0 ? (\n <div className=\"grid grid-cols-3 sm:grid-cols-4 gap-2.5 max-h-80 overflow-y-auto pr-0.5\">\n {results.map((asset) => (\n <AssetPickerCell\n key={`${asset.contractAddress}-${asset.tokenId}`}\n asset={asset}\n active={isSameAsset(asset, selected)}\n onSelect={onSelect}\n />\n ))}\n </div>\n ) : hasSearched && !isLoading ? (\n <p className=\"text-xs text-muted-foreground text-center py-4\">No assets match &quot;{query.trim()}&quot;.</p>\n ) : null}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAuDM;AArDN,mBAAoC;AACpC,0BAAgC;AAChC,gBAAmB;AAEnB,+BAA6C;AAYtC,SAAS,kBAAkB;AAAA,EAChC;AAAA,EAAQ;AAAA,EAAU;AAAA,EAAU,cAAc;AAAA,EAAkC;AAC9E,GAA2B;AACzB,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAS,EAAE;AACrC,QAAM,CAAC,SAAS,UAAU,QAAI,uBAAuB,CAAC,CAAC;AACvD,QAAM,CAAC,WAAW,YAAY,QAAI,uBAAS,KAAK;AAChD,QAAM,CAAC,aAAa,cAAc,QAAI,uBAAS,KAAK;AAEpD,8BAAU,MAAM;AACd,UAAM,IAAI,MAAM,KAAK;AACrB,QAAI,EAAE,SAAS,GAAG;AAChB,iBAAW,CAAC,CAAC;AACb,qBAAe,KAAK;AACpB;AAAA,IACF;AACA,QAAI,YAAY;AAChB,iBAAa,IAAI;AACjB,UAAM,QAAQ,WAAW,MAAM;AAC7B,aAAO,CAAC,EACL,KAAK,CAAC,WAAW;AAChB,YAAI,CAAC,WAAW;AACd,qBAAW,MAAM;AACjB,yBAAe,IAAI;AAAA,QACrB;AAAA,MACF,CAAC,EACA,QAAQ,MAAM;AACb,YAAI,CAAC,UAAW,cAAa,KAAK;AAAA,MACpC,CAAC;AAAA,IACL,GAAG,GAAG;AACN,WAAO,MAAM;AACX,kBAAY;AACZ,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,OAAO,MAAM,CAAC;AAElB,SACE,6CAAC,SAAI,eAAW,cAAG,aAAa,SAAS,GACvC;AAAA,iDAAC,SAAI,WAAU,YACb;AAAA,kDAAC,8BAAO,WAAU,8FAA6F;AAAA,MAC/G;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,UACxC;AAAA,UACA,WAAU;AAAA;AAAA,MACZ;AAAA,MACC,YACC,4CAAC,+BAAQ,WAAU,wFAAuF,IACxG;AAAA,OACN;AAAA,IAEC,MAAM,KAAK,EAAE,SAAS,KAAK,MAAM,KAAK,EAAE,SAAS,IAChD,4CAAC,OAAE,WAAU,kDAAiD,yDAAsC,IAClG;AAAA,IAEH,QAAQ,SAAS,IAChB,4CAAC,SAAI,WAAU,2EACZ,kBAAQ,IAAI,CAAC,UACZ;AAAA,MAAC;AAAA;AAAA,QAEC;AAAA,QACA,YAAQ,sCAAY,OAAO,QAAQ;AAAA,QACnC;AAAA;AAAA,MAHK,GAAG,MAAM,eAAe,IAAI,MAAM,OAAO;AAAA,IAIhD,CACD,GACH,IACE,eAAe,CAAC,YAClB,6CAAC,OAAE,WAAU,kDAAiD;AAAA;AAAA,MAAuB,MAAM,KAAK;AAAA,MAAE;AAAA,OAAO,IACvG;AAAA,KACN;AAEJ;","names":[]}
@@ -1,10 +1,9 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useState } from "react";
4
- import Image from "next/image";
5
- import { Check, ImageIcon, Loader2, Search } from "lucide-react";
4
+ import { Loader2, Search } from "lucide-react";
6
5
  import { cn } from "../utils/cn.js";
7
- const isSame = (a, b) => !!b && a.contractAddress === b.contractAddress && a.tokenId === b.tokenId;
6
+ import { AssetPickerCell, isSameAsset } from "./asset-picker-cell.js";
8
7
  function AssetSearchPicker({
9
8
  search,
10
9
  selected,
@@ -56,27 +55,15 @@ function AssetSearchPicker({
56
55
  isLoading ? /* @__PURE__ */ jsx(Loader2, { className: "absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 animate-spin text-muted-foreground" }) : null
57
56
  ] }),
58
57
  query.trim().length > 0 && query.trim().length < 2 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground text-center py-2", children: "Keep typing \u2014 2+ characters to search." }) : null,
59
- results.length > 0 ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 sm:grid-cols-4 gap-2.5 max-h-80 overflow-y-auto pr-0.5", children: results.map((asset) => {
60
- const active = isSame(asset, selected);
61
- return /* @__PURE__ */ jsxs(
62
- "button",
63
- {
64
- type: "button",
65
- onClick: () => onSelect(asset),
66
- "aria-pressed": active,
67
- className: cn(
68
- "group relative aspect-square rounded-xl overflow-hidden border-2 transition-colors text-left",
69
- active ? "border-brand-purple" : "border-border/50 active:border-border sm:hover:border-border"
70
- ),
71
- children: [
72
- asset.image ? /* @__PURE__ */ jsx(Image, { src: asset.image, alt: asset.name, fill: true, sizes: "120px", className: "object-cover" }) : /* @__PURE__ */ jsx("div", { className: "h-full w-full flex items-center justify-center bg-muted/60", children: /* @__PURE__ */ jsx(ImageIcon, { className: "h-5 w-5 text-muted-foreground/50" }) }),
73
- /* @__PURE__ */ jsx("div", { className: "absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent px-1.5 py-1", children: /* @__PURE__ */ jsx("p", { className: "truncate text-[11px] font-medium text-white", children: asset.name }) }),
74
- active ? /* @__PURE__ */ jsx("div", { className: "absolute top-1.5 right-1.5 h-5 w-5 rounded-full bg-brand-purple flex items-center justify-center", children: /* @__PURE__ */ jsx(Check, { className: "h-3 w-3 text-white" }) }) : null
75
- ]
76
- },
77
- `${asset.contractAddress}-${asset.tokenId}`
78
- );
79
- }) }) : hasSearched && !isLoading ? /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground text-center py-4", children: [
58
+ results.length > 0 ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 sm:grid-cols-4 gap-2.5 max-h-80 overflow-y-auto pr-0.5", children: results.map((asset) => /* @__PURE__ */ jsx(
59
+ AssetPickerCell,
60
+ {
61
+ asset,
62
+ active: isSameAsset(asset, selected),
63
+ onSelect
64
+ },
65
+ `${asset.contractAddress}-${asset.tokenId}`
66
+ )) }) : hasSearched && !isLoading ? /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground text-center py-4", children: [
80
67
  'No assets match "',
81
68
  query.trim(),
82
69
  '".'
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/asset-search-picker.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport Image from \"next/image\";\nimport { Check, ImageIcon, Loader2, Search } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport type { OwnedAsset } from \"./asset-picker.js\";\n\nexport interface AssetSearchPickerProps {\n /** App-supplied search — the app owns the actual fetch (e.g. GET /v1/search).\n * Called only when the query is 2+ chars, debounced by this component. */\n search: (query: string) => Promise<OwnedAsset[]>;\n selected: OwnedAsset | null;\n onSelect: (asset: OwnedAsset) => void;\n placeholder?: string;\n className?: string;\n}\n\nconst isSame = (a: OwnedAsset, b: OwnedAsset | null) =>\n !!b && a.contractAddress === b.contractAddress && a.tokenId === b.tokenId;\n\nexport function AssetSearchPicker({\n search, selected, onSelect, placeholder = \"Search for an asset or creator\", className,\n}: AssetSearchPickerProps) {\n const [query, setQuery] = useState(\"\");\n const [results, setResults] = useState<OwnedAsset[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n const [hasSearched, setHasSearched] = useState(false);\n\n useEffect(() => {\n const q = query.trim();\n if (q.length < 2) {\n setResults([]);\n setHasSearched(false);\n return;\n }\n let cancelled = false;\n setIsLoading(true);\n const timer = setTimeout(() => {\n search(q)\n .then((assets) => {\n if (!cancelled) {\n setResults(assets);\n setHasSearched(true);\n }\n })\n .finally(() => {\n if (!cancelled) setIsLoading(false);\n });\n }, 350);\n return () => {\n cancelled = true;\n clearTimeout(timer);\n };\n }, [query, search]);\n\n return (\n <div className={cn(\"space-y-3\", className)}>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none\" />\n <input\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={placeholder}\n className=\"w-full h-10 rounded-full border border-border bg-card pl-9 pr-4 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-brand-purple/40 focus:border-brand-purple/40\"\n />\n {isLoading ? (\n <Loader2 className=\"absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 animate-spin text-muted-foreground\" />\n ) : null}\n </div>\n\n {query.trim().length > 0 && query.trim().length < 2 ? (\n <p className=\"text-xs text-muted-foreground text-center py-2\">Keep typing — 2+ characters to search.</p>\n ) : null}\n\n {results.length > 0 ? (\n <div className=\"grid grid-cols-3 sm:grid-cols-4 gap-2.5 max-h-80 overflow-y-auto pr-0.5\">\n {results.map((asset) => {\n const active = isSame(asset, selected);\n return (\n <button\n key={`${asset.contractAddress}-${asset.tokenId}`}\n type=\"button\"\n onClick={() => onSelect(asset)}\n aria-pressed={active}\n className={cn(\n \"group relative aspect-square rounded-xl overflow-hidden border-2 transition-colors text-left\",\n active ? \"border-brand-purple\" : \"border-border/50 active:border-border sm:hover:border-border\",\n )}\n >\n {asset.image ? (\n <Image src={asset.image} alt={asset.name} fill sizes=\"120px\" className=\"object-cover\" />\n ) : (\n <div className=\"h-full w-full flex items-center justify-center bg-muted/60\">\n <ImageIcon className=\"h-5 w-5 text-muted-foreground/50\" />\n </div>\n )}\n <div className=\"absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent px-1.5 py-1\">\n <p className=\"truncate text-[11px] font-medium text-white\">{asset.name}</p>\n </div>\n {active ? (\n <div className=\"absolute top-1.5 right-1.5 h-5 w-5 rounded-full bg-brand-purple flex items-center justify-center\">\n <Check className=\"h-3 w-3 text-white\" />\n </div>\n ) : null}\n </button>\n );\n })}\n </div>\n ) : hasSearched && !isLoading ? (\n <p className=\"text-xs text-muted-foreground text-center py-4\">No assets match &quot;{query.trim()}&quot;.</p>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/asset-search-picker.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { Loader2, Search } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport type { OwnedAsset } from \"./asset-picker.js\";\nimport { AssetPickerCell, isSameAsset } from \"./asset-picker-cell.js\";\n\nexport interface AssetSearchPickerProps {\n /** App-supplied search — the app owns the actual fetch (e.g. GET /v1/search).\n * Called only when the query is 2+ chars, debounced by this component. */\n search: (query: string) => Promise<OwnedAsset[]>;\n selected: OwnedAsset | null;\n onSelect: (asset: OwnedAsset) => void;\n placeholder?: string;\n className?: string;\n}\n\nexport function AssetSearchPicker({\n search, selected, onSelect, placeholder = \"Search for an asset or creator\", className,\n}: AssetSearchPickerProps) {\n const [query, setQuery] = useState(\"\");\n const [results, setResults] = useState<OwnedAsset[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n const [hasSearched, setHasSearched] = useState(false);\n\n useEffect(() => {\n const q = query.trim();\n if (q.length < 2) {\n setResults([]);\n setHasSearched(false);\n return;\n }\n let cancelled = false;\n setIsLoading(true);\n const timer = setTimeout(() => {\n search(q)\n .then((assets) => {\n if (!cancelled) {\n setResults(assets);\n setHasSearched(true);\n }\n })\n .finally(() => {\n if (!cancelled) setIsLoading(false);\n });\n }, 350);\n return () => {\n cancelled = true;\n clearTimeout(timer);\n };\n }, [query, search]);\n\n return (\n <div className={cn(\"space-y-3\", className)}>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none\" />\n <input\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={placeholder}\n className=\"w-full h-10 rounded-full border border-border bg-card pl-9 pr-4 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-brand-purple/40 focus:border-brand-purple/40\"\n />\n {isLoading ? (\n <Loader2 className=\"absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 animate-spin text-muted-foreground\" />\n ) : null}\n </div>\n\n {query.trim().length > 0 && query.trim().length < 2 ? (\n <p className=\"text-xs text-muted-foreground text-center py-2\">Keep typing — 2+ characters to search.</p>\n ) : null}\n\n {results.length > 0 ? (\n <div className=\"grid grid-cols-3 sm:grid-cols-4 gap-2.5 max-h-80 overflow-y-auto pr-0.5\">\n {results.map((asset) => (\n <AssetPickerCell\n key={`${asset.contractAddress}-${asset.tokenId}`}\n asset={asset}\n active={isSameAsset(asset, selected)}\n onSelect={onSelect}\n />\n ))}\n </div>\n ) : hasSearched && !isLoading ? (\n <p className=\"text-xs text-muted-foreground text-center py-4\">No assets match &quot;{query.trim()}&quot;.</p>\n ) : null}\n </div>\n );\n}\n"],"mappings":";AAuDM,SACE,KADF;AArDN,SAAS,WAAW,gBAAgB;AACpC,SAAS,SAAS,cAAc;AAChC,SAAS,UAAU;AAEnB,SAAS,iBAAiB,mBAAmB;AAYtC,SAAS,kBAAkB;AAAA,EAChC;AAAA,EAAQ;AAAA,EAAU;AAAA,EAAU,cAAc;AAAA,EAAkC;AAC9E,GAA2B;AACzB,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAuB,CAAC,CAAC;AACvD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AAEpD,YAAU,MAAM;AACd,UAAM,IAAI,MAAM,KAAK;AACrB,QAAI,EAAE,SAAS,GAAG;AAChB,iBAAW,CAAC,CAAC;AACb,qBAAe,KAAK;AACpB;AAAA,IACF;AACA,QAAI,YAAY;AAChB,iBAAa,IAAI;AACjB,UAAM,QAAQ,WAAW,MAAM;AAC7B,aAAO,CAAC,EACL,KAAK,CAAC,WAAW;AAChB,YAAI,CAAC,WAAW;AACd,qBAAW,MAAM;AACjB,yBAAe,IAAI;AAAA,QACrB;AAAA,MACF,CAAC,EACA,QAAQ,MAAM;AACb,YAAI,CAAC,UAAW,cAAa,KAAK;AAAA,MACpC,CAAC;AAAA,IACL,GAAG,GAAG;AACN,WAAO,MAAM;AACX,kBAAY;AACZ,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,OAAO,MAAM,CAAC;AAElB,SACE,qBAAC,SAAI,WAAW,GAAG,aAAa,SAAS,GACvC;AAAA,yBAAC,SAAI,WAAU,YACb;AAAA,0BAAC,UAAO,WAAU,8FAA6F;AAAA,MAC/G;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,UACxC;AAAA,UACA,WAAU;AAAA;AAAA,MACZ;AAAA,MACC,YACC,oBAAC,WAAQ,WAAU,wFAAuF,IACxG;AAAA,OACN;AAAA,IAEC,MAAM,KAAK,EAAE,SAAS,KAAK,MAAM,KAAK,EAAE,SAAS,IAChD,oBAAC,OAAE,WAAU,kDAAiD,yDAAsC,IAClG;AAAA,IAEH,QAAQ,SAAS,IAChB,oBAAC,SAAI,WAAU,2EACZ,kBAAQ,IAAI,CAAC,UACZ;AAAA,MAAC;AAAA;AAAA,QAEC;AAAA,QACA,QAAQ,YAAY,OAAO,QAAQ;AAAA,QACnC;AAAA;AAAA,MAHK,GAAG,MAAM,eAAe,IAAI,MAAM,OAAO;AAAA,IAIhD,CACD,GACH,IACE,eAAe,CAAC,YAClB,qBAAC,OAAE,WAAU,kDAAiD;AAAA;AAAA,MAAuB,MAAM,KAAK;AAAA,MAAE;AAAA,OAAO,IACvG;AAAA,KACN;AAEJ;","names":[]}