@tangle-network/sandbox-ui 0.130.1 → 0.131.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/assets.d.ts CHANGED
@@ -189,11 +189,13 @@ interface ApprovalQueueProps {
189
189
  onSave?: (spec: AssetSpec) => void;
190
190
  onRevisionRequest?: (id: string, instruction: string) => void;
191
191
  onRenderRequest?: (id: string) => void;
192
+ /** Opens one asset's detail view, with the ids the queue shows, in order, for previous and next. */
193
+ onOpen?: (id: string, visibleIds: string[]) => void;
192
194
  renderingIds?: Set<string>;
193
195
  previewUrls?: Record<string, string>;
194
196
  className?: string;
195
197
  }
196
- export declare function ApprovalQueue({ assets, variantCounts, onApprove, onReject, onEdit, onSave, onRevisionRequest, onRenderRequest, renderingIds, previewUrls, className }: ApprovalQueueProps): React$1.JSX.Element;
198
+ export declare function ApprovalQueue({ assets, variantCounts, onApprove, onReject, onEdit, onSave, onRevisionRequest, onRenderRequest, onOpen, renderingIds, previewUrls, className }: ApprovalQueueProps): React$1.JSX.Element;
197
199
  //#endregion
198
200
  //#region src/assets/asset-card.d.ts
199
201
  interface AssetCardProps {
@@ -202,9 +204,15 @@ interface AssetCardProps {
202
204
  onApprove?: (id: string) => void;
203
205
  onReject?: (id: string) => void;
204
206
  onEdit?: (id: string) => void;
207
+ /** Opens the asset's detail view. When set, the thumbnail and card body are one click target. */
208
+ onOpen?: (id: string) => void;
205
209
  className?: string;
206
210
  }
207
- export declare function AssetCard({ spec, variantCount, onApprove, onReject, onEdit, className }: AssetCardProps): import("react").JSX.Element;
211
+ /** The URL of an image slide that is one picture with nothing layered on it. */
212
+ export declare function imageOnlyUrl(content: ImageContent): string | null;
213
+ /** The still frame a video asset carries: its first image scene or image slide. */
214
+ export declare function videoPosterUrl(content: VideoContent): string | null;
215
+ export declare function AssetCard({ spec, variantCount, onApprove, onReject, onEdit, onOpen, className }: AssetCardProps): import("react").JSX.Element;
208
216
  //#endregion
209
217
  //#region src/assets/asset-editor.d.ts
210
218
  interface AssetEditorProps {
@@ -218,6 +226,12 @@ interface AssetEditorProps {
218
226
  }
219
227
  export declare function AssetEditor({ spec, previewUrl, isRendering, onSave, onRenderRequest, onRevisionRequest, className }: AssetEditorProps): React$1.JSX.Element;
220
228
  //#endregion
229
+ //#region src/assets/labels.d.ts
230
+ /** Reader-facing name for each asset format. */
231
+ export declare const ASSET_FORMAT_LABEL: Record<AssetFormat, string>;
232
+ /** Reader-facing name for each review status. */
233
+ export declare const ASSET_STATUS_LABEL: Record<AssetStatus, string>;
234
+ //#endregion
221
235
  //#region src/assets/preview/copy-preview.d.ts
222
236
  interface CopyPreviewProps {
223
237
  content: CopyContent;
package/dist/assets.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { t as cn } from "./utils-DzWbktWG.js";
2
- import { Badge, Button, Input, Label, Textarea } from "@tangle-network/ui/primitives";
3
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Badge, Button, DatePicker, FilterField, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea, Toolbar, localToday } from "@tangle-network/ui/primitives";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
4
  import * as React$1 from "react";
5
5
  import { Fragment as Fragment$1 } from "react";
6
- import { Check, CheckCheck, ChevronDown, ChevronLeft, ChevronRight, Film, GitBranch, Loader2, MessageSquare, Pencil, Play, Save, Search, X } from "lucide-react";
6
+ import { Check, CheckCheck, ChevronDown, ChevronLeft, ChevronRight, Film, GitBranch, Loader2, Maximize2, MessageSquare, Pencil, Play, Save, Search, X } from "lucide-react";
7
7
  //#region src/assets/preview/image-preview.tsx
8
8
  const ASPECT = {
9
9
  feed: "aspect-square",
@@ -118,6 +118,29 @@ function ImagePreview({ content, brand, format = "feed", className }) {
118
118
  });
119
119
  }
120
120
  //#endregion
121
+ //#region src/assets/labels.ts
122
+ /** Reader-facing name for each asset format. */
123
+ const ASSET_FORMAT_LABEL = {
124
+ email: "Email",
125
+ "image:feed": "Feed image",
126
+ "image:story": "Story",
127
+ "image:carousel": "Carousel",
128
+ "video:reel": "Reel",
129
+ "video:feed": "Feed video",
130
+ "copy:caption": "Caption",
131
+ "copy:headline": "Headline",
132
+ "copy:sms": "SMS"
133
+ };
134
+ /** Reader-facing name for each review status. */
135
+ const ASSET_STATUS_LABEL = {
136
+ draft: "Draft",
137
+ pending_review: "Pending review",
138
+ approved: "Approved",
139
+ rejected: "Rejected",
140
+ scheduled: "Scheduled",
141
+ published: "Published"
142
+ };
143
+ //#endregion
121
144
  //#region src/assets/asset-card.tsx
122
145
  const STATUS_VARIANT = {
123
146
  draft: "secondary",
@@ -127,111 +150,173 @@ const STATUS_VARIANT = {
127
150
  scheduled: "secondary",
128
151
  published: "default"
129
152
  };
130
- const STATUS_LABEL = {
131
- draft: "Draft",
132
- pending_review: "Review",
133
- approved: "Approved",
134
- rejected: "Rejected",
135
- scheduled: "Scheduled",
136
- published: "Published"
137
- };
153
+ /** The URL of an image slide that is one picture with nothing layered on it. */
154
+ function imageOnlyUrl(content) {
155
+ const slide = content.slides[0];
156
+ return slide && slide.layers.length === 0 && slide.background.type === "image" ? slide.background.url : null;
157
+ }
158
+ /** The still frame a video asset carries: its first image scene or image slide. */
159
+ function videoPosterUrl(content) {
160
+ for (const scene of content.scenes) {
161
+ if (scene.type === "image-reveal") return scene.imageUrl;
162
+ if (scene.type === "slide" && scene.slide.background.type === "image") return scene.slide.background.url;
163
+ }
164
+ return null;
165
+ }
166
+ function formatDuration(seconds) {
167
+ const whole = Math.max(0, Math.round(seconds));
168
+ return `${Math.floor(whole / 60)}:${String(whole % 60).padStart(2, "0")}`;
169
+ }
170
+ /** A short phrase that tells one asset from another of the same format. */
171
+ function assetSummary(spec) {
172
+ if (spec.format === "email") return spec.content.subject || null;
173
+ if (spec.format.startsWith("copy:")) return spec.content.headline || null;
174
+ return null;
175
+ }
138
176
  function Thumbnail({ spec }) {
139
177
  const { format, brand, content } = spec;
140
178
  if (format === "email") {
141
179
  const ec = content;
142
180
  return /* @__PURE__ */ jsxs("div", {
143
- className: "p-2 text-xs space-y-0.5",
181
+ className: "flex h-full flex-col gap-1 p-3 text-sm",
144
182
  children: [/* @__PURE__ */ jsx("div", {
145
- className: "font-medium truncate",
183
+ className: "line-clamp-3 font-medium",
146
184
  children: ec.subject
147
185
  }), /* @__PURE__ */ jsxs("div", {
148
- className: "text-muted-foreground truncate text-[10px]",
186
+ className: "text-xs text-muted-foreground",
149
187
  children: [ec.sections.length, " sections"]
150
188
  })]
151
189
  });
152
190
  }
153
- if (format.startsWith("image:")) return /* @__PURE__ */ jsx("div", {
154
- className: "overflow-hidden rounded",
155
- style: { maxHeight: 120 },
156
- children: /* @__PURE__ */ jsx(ImagePreview, {
157
- content,
158
- brand,
159
- format: format === "image:story" ? "story" : "feed"
160
- })
161
- });
191
+ if (format.startsWith("image:")) {
192
+ const ic = content;
193
+ const url = imageOnlyUrl(ic);
194
+ if (url) return /* @__PURE__ */ jsx("img", {
195
+ src: url,
196
+ alt: "",
197
+ loading: "lazy",
198
+ decoding: "async",
199
+ className: "h-full w-full object-contain"
200
+ });
201
+ return /* @__PURE__ */ jsx("div", {
202
+ className: "h-full overflow-hidden",
203
+ children: /* @__PURE__ */ jsx(ImagePreview, {
204
+ content: ic,
205
+ brand,
206
+ format: format === "image:story" ? "story" : "feed"
207
+ })
208
+ });
209
+ }
162
210
  if (format.startsWith("video:")) {
163
211
  const vc = content;
212
+ const poster = videoPosterUrl(vc);
164
213
  return /* @__PURE__ */ jsxs("div", {
165
- className: "p-2 text-xs space-y-0.5",
166
- children: [/* @__PURE__ */ jsxs("div", {
167
- className: "font-medium",
168
- children: [vc.scenes.length, " scenes"]
169
- }), /* @__PURE__ */ jsxs("div", {
170
- className: "text-muted-foreground",
171
- children: [vc.durationSeconds, "s video"]
172
- })]
214
+ className: "relative h-full w-full",
215
+ children: [
216
+ poster ? /* @__PURE__ */ jsx("img", {
217
+ src: poster,
218
+ alt: "",
219
+ loading: "lazy",
220
+ decoding: "async",
221
+ className: "h-full w-full object-contain"
222
+ }) : /* @__PURE__ */ jsx("div", {
223
+ className: "flex h-full flex-col gap-1 p-3 text-sm",
224
+ children: /* @__PURE__ */ jsxs("div", {
225
+ className: "font-medium",
226
+ children: [vc.scenes.length, " scenes"]
227
+ })
228
+ }),
229
+ /* @__PURE__ */ jsx("span", {
230
+ className: "pointer-events-none absolute top-1/2 left-1/2 flex size-10 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-black/60 text-white",
231
+ children: /* @__PURE__ */ jsx(Play, {
232
+ size: 18,
233
+ className: "translate-x-px",
234
+ "aria-hidden": true
235
+ })
236
+ }),
237
+ /* @__PURE__ */ jsx("span", {
238
+ className: "pointer-events-none absolute right-2 bottom-2 rounded bg-black/70 px-1.5 py-0.5 text-xs text-white tabular-nums",
239
+ children: formatDuration(vc.durationSeconds)
240
+ })
241
+ ]
173
242
  });
174
243
  }
175
244
  if (format.startsWith("copy:")) {
176
245
  const cc = content;
177
246
  return /* @__PURE__ */ jsxs("div", {
178
- className: "p-2 text-xs truncate space-y-0.5",
247
+ className: "flex h-full flex-col gap-1 p-3 text-sm",
179
248
  children: [/* @__PURE__ */ jsx("div", {
180
- className: "font-medium truncate",
249
+ className: "line-clamp-2 font-medium",
181
250
  children: cc.headline
182
251
  }), /* @__PURE__ */ jsx("div", {
183
- className: "text-muted-foreground line-clamp-2 text-[10px]",
252
+ className: "line-clamp-4 text-xs text-muted-foreground",
184
253
  children: cc.body
185
254
  })]
186
255
  });
187
256
  }
188
257
  return null;
189
258
  }
190
- function AssetCard({ spec, variantCount, onApprove, onReject, onEdit, className }) {
259
+ function AssetCard({ spec, variantCount, onApprove, onReject, onEdit, onOpen, className }) {
260
+ const formatLabel = ASSET_FORMAT_LABEL[spec.format] ?? spec.format;
261
+ const statusLabel = ASSET_STATUS_LABEL[spec.status] ?? spec.status;
262
+ const summary = assetSummary(spec);
263
+ const hasActions = Boolean(onEdit || spec.status === "pending_review" && (onReject || onApprove));
191
264
  return /* @__PURE__ */ jsxs("div", {
192
- className: cn("rounded-lg border border-border bg-card flex flex-col overflow-hidden hover:border-border/80 transition-colors", className),
265
+ "data-asset-id": spec.id,
266
+ className: cn("group relative flex flex-col overflow-hidden rounded-lg border border-border bg-card transition-[border-color,box-shadow]", onOpen && "hover:border-primary/60 hover:shadow-md", className),
193
267
  children: [
194
- /* @__PURE__ */ jsx("div", {
195
- className: "min-h-[80px] bg-muted/30",
196
- children: /* @__PURE__ */ jsx(Thumbnail, { spec })
268
+ onOpen && /* @__PURE__ */ jsx("button", {
269
+ type: "button",
270
+ "data-asset-open": "",
271
+ onClick: () => onOpen(spec.id),
272
+ "aria-label": `Open ${formatLabel.toLowerCase()}${summary ? `: ${summary}` : ""}, ${statusLabel.toLowerCase()}`,
273
+ className: "absolute inset-0 z-[1] cursor-pointer rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset"
274
+ }),
275
+ /* @__PURE__ */ jsxs("div", {
276
+ className: "relative aspect-square overflow-hidden bg-muted/40",
277
+ children: [/* @__PURE__ */ jsx(Thumbnail, { spec }), onOpen && /* @__PURE__ */ jsx("span", {
278
+ "aria-hidden": true,
279
+ className: "pointer-events-none absolute top-2 right-2 flex size-7 items-center justify-center rounded-md bg-background/90 text-foreground opacity-0 shadow-sm transition-opacity group-hover:opacity-100 group-focus-within:opacity-100",
280
+ children: /* @__PURE__ */ jsx(Maximize2, { size: 14 })
281
+ })]
197
282
  }),
198
283
  /* @__PURE__ */ jsxs("div", {
199
- className: "px-2 py-1.5 flex items-center gap-1.5",
284
+ className: "flex items-center gap-2 px-2.5 py-2",
200
285
  children: [
201
286
  /* @__PURE__ */ jsx("span", {
202
- className: "text-[10px] text-muted-foreground flex-1 truncate",
203
- children: spec.format
287
+ className: "min-w-0 flex-1 truncate text-xs text-muted-foreground",
288
+ children: formatLabel
204
289
  }),
205
290
  /* @__PURE__ */ jsx(Badge, {
206
291
  variant: STATUS_VARIANT[spec.status],
207
- className: "text-[10px] h-4 px-1.5",
208
- children: STATUS_LABEL[spec.status]
292
+ className: "shrink-0 text-xs",
293
+ children: statusLabel
209
294
  }),
210
295
  variantCount !== void 0 && variantCount > 0 && /* @__PURE__ */ jsxs("span", {
211
- className: "flex items-center gap-0.5 text-[10px] text-muted-foreground",
212
- children: [/* @__PURE__ */ jsx(GitBranch, { size: 10 }), variantCount]
296
+ className: "flex shrink-0 items-center gap-0.5 text-xs text-muted-foreground",
297
+ children: [/* @__PURE__ */ jsx(GitBranch, { size: 12 }), variantCount]
213
298
  })
214
299
  ]
215
300
  }),
216
- /* @__PURE__ */ jsxs("div", {
217
- className: "flex border-t border-border",
301
+ hasActions && /* @__PURE__ */ jsxs("div", {
302
+ className: "relative z-[2] flex border-t border-border bg-card",
218
303
  children: [
219
304
  onEdit && /* @__PURE__ */ jsxs("button", {
220
305
  type: "button",
221
306
  onClick: () => onEdit(spec.id),
222
- className: "flex-1 flex items-center justify-center gap-1 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors",
307
+ className: "flex flex-1 items-center justify-center gap-1 py-2 text-xs text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground",
223
308
  children: [/* @__PURE__ */ jsx(Pencil, { size: 12 }), "Edit"]
224
309
  }),
225
310
  onReject && spec.status === "pending_review" && /* @__PURE__ */ jsxs("button", {
226
311
  type: "button",
227
312
  onClick: () => onReject(spec.id),
228
- className: "flex-1 flex items-center justify-center gap-1 py-1.5 text-xs text-muted-foreground hover:text-destructive hover:bg-destructive/5 transition-colors border-l border-border",
313
+ className: "flex flex-1 items-center justify-center gap-1 border-l border-border py-2 text-xs text-muted-foreground transition-colors hover:bg-destructive/5 hover:text-destructive",
229
314
  children: [/* @__PURE__ */ jsx(X, { size: 12 }), "Reject"]
230
315
  }),
231
316
  onApprove && spec.status === "pending_review" && /* @__PURE__ */ jsxs("button", {
232
317
  type: "button",
233
318
  onClick: () => onApprove(spec.id),
234
- className: "flex-1 flex items-center justify-center gap-1 py-1.5 text-xs text-muted-foreground hover:text-green-600 hover:bg-green-600/5 transition-colors border-l border-border",
319
+ className: "flex flex-1 items-center justify-center gap-1 border-l border-border py-2 text-xs text-muted-foreground transition-colors hover:bg-green-600/5 hover:text-green-600",
235
320
  children: [/* @__PURE__ */ jsx(Check, { size: 12 }), "Approve"]
236
321
  })
237
322
  ]
@@ -886,12 +971,15 @@ const STATUS_OPTIONS = [
886
971
  label: "Published"
887
972
  }
888
973
  ];
889
- function ApprovalQueue({ assets, variantCounts = {}, onApprove, onReject, onEdit, onSave, onRevisionRequest, onRenderRequest, renderingIds = /* @__PURE__ */ new Set(), previewUrls = {}, className }) {
974
+ function ApprovalQueue({ assets, variantCounts = {}, onApprove, onReject, onEdit, onSave, onRevisionRequest, onRenderRequest, onOpen, renderingIds = /* @__PURE__ */ new Set(), previewUrls = {}, className }) {
890
975
  const [search, setSearch] = React$1.useState("");
891
976
  const [formatFilter, setFormatFilter] = React$1.useState("all");
892
977
  const [statusFilter, setStatusFilter] = React$1.useState("pending_review");
893
978
  const [editingId, setEditingId] = React$1.useState(null);
894
979
  const [scheduleDate, setScheduleDate] = React$1.useState("");
980
+ const formatId = React$1.useId();
981
+ const statusId = React$1.useId();
982
+ const scheduleId = React$1.useId();
895
983
  const filtered = assets.filter((a) => {
896
984
  if (formatFilter !== "all" && a.format !== formatFilter) return false;
897
985
  if (statusFilter !== "all" && a.status !== statusFilter) return false;
@@ -949,60 +1037,78 @@ function ApprovalQueue({ assets, variantCounts = {}, onApprove, onReject, onEdit
949
1037
  });
950
1038
  return /* @__PURE__ */ jsxs("div", {
951
1039
  className: cn("flex flex-col gap-3", className),
952
- children: [/* @__PURE__ */ jsxs("div", {
953
- className: "flex flex-wrap gap-2 items-center",
954
- children: [
955
- /* @__PURE__ */ jsxs("div", {
956
- className: "relative flex-1 min-w-40",
957
- children: [/* @__PURE__ */ jsx(Search, {
958
- size: 12,
959
- className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground"
960
- }), /* @__PURE__ */ jsx(Input, {
961
- value: search,
962
- onChange: (e) => setSearch(e.target.value),
963
- placeholder: "Filter assets…",
964
- className: "h-8 pl-7 text-sm"
965
- })]
966
- }),
967
- /* @__PURE__ */ jsx("select", {
968
- value: formatFilter,
969
- onChange: (e) => setFormatFilter(e.target.value),
970
- className: "h-8 px-2 text-xs rounded border border-input bg-background",
971
- children: FORMAT_OPTIONS.map((o) => /* @__PURE__ */ jsx("option", {
972
- value: o.value,
973
- children: o.label
974
- }, o.value))
1040
+ children: [/* @__PURE__ */ jsx(Toolbar, {
1041
+ className: "mb-0",
1042
+ search: /* @__PURE__ */ jsxs("div", {
1043
+ className: "relative",
1044
+ children: [/* @__PURE__ */ jsx(Search, {
1045
+ size: 14,
1046
+ "aria-hidden": true,
1047
+ className: "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground"
1048
+ }), /* @__PURE__ */ jsx(Input, {
1049
+ value: search,
1050
+ onChange: (e) => setSearch(e.target.value),
1051
+ placeholder: "Filter assets…",
1052
+ "aria-label": "Filter assets",
1053
+ className: "pl-9"
1054
+ })]
1055
+ }),
1056
+ filters: /* @__PURE__ */ jsxs(Fragment, { children: [
1057
+ /* @__PURE__ */ jsx(FilterField, {
1058
+ label: "Format",
1059
+ htmlFor: formatId,
1060
+ children: /* @__PURE__ */ jsxs(Select, {
1061
+ value: formatFilter,
1062
+ onValueChange: setFormatFilter,
1063
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
1064
+ id: formatId,
1065
+ className: "w-36",
1066
+ children: /* @__PURE__ */ jsx(SelectValue, {})
1067
+ }), /* @__PURE__ */ jsx(SelectContent, { children: FORMAT_OPTIONS.map((o) => /* @__PURE__ */ jsx(SelectItem, {
1068
+ value: o.value,
1069
+ children: o.label
1070
+ }, o.value)) })]
1071
+ })
975
1072
  }),
976
- /* @__PURE__ */ jsx("select", {
977
- value: statusFilter,
978
- onChange: (e) => setStatusFilter(e.target.value),
979
- className: "h-8 px-2 text-xs rounded border border-input bg-background",
980
- children: STATUS_OPTIONS.map((o) => /* @__PURE__ */ jsx("option", {
981
- value: o.value,
982
- children: o.label
983
- }, o.value))
1073
+ /* @__PURE__ */ jsx(FilterField, {
1074
+ label: "Status",
1075
+ htmlFor: statusId,
1076
+ children: /* @__PURE__ */ jsxs(Select, {
1077
+ value: statusFilter,
1078
+ onValueChange: setStatusFilter,
1079
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
1080
+ id: statusId,
1081
+ className: "w-36",
1082
+ children: /* @__PURE__ */ jsx(SelectValue, {})
1083
+ }), /* @__PURE__ */ jsx(SelectContent, { children: STATUS_OPTIONS.map((o) => /* @__PURE__ */ jsx(SelectItem, {
1084
+ value: o.value,
1085
+ children: o.label
1086
+ }, o.value)) })]
1087
+ })
984
1088
  }),
985
- pendingCount > 0 && onApprove && /* @__PURE__ */ jsxs("div", {
986
- className: "flex items-center gap-1.5",
987
- children: [/* @__PURE__ */ jsx("input", {
988
- type: "date",
1089
+ pendingCount > 0 && onApprove && /* @__PURE__ */ jsx(FilterField, {
1090
+ label: "Schedule for",
1091
+ htmlFor: scheduleId,
1092
+ children: /* @__PURE__ */ jsx(DatePicker, {
1093
+ id: scheduleId,
1094
+ className: "w-40",
1095
+ placeholder: "Not scheduled",
1096
+ min: localToday(),
989
1097
  value: scheduleDate,
990
- onChange: (e) => setScheduleDate(e.target.value),
991
- className: "h-8 px-2 text-xs rounded border border-input bg-background w-32"
992
- }), /* @__PURE__ */ jsxs(Button, {
993
- size: "sm",
994
- variant: "outline",
995
- onClick: handleBulkApprove,
996
- className: "h-8 gap-1.5 text-xs",
997
- children: [
998
- /* @__PURE__ */ jsx(CheckCheck, { size: 13 }),
999
- "Approve all (",
1000
- pendingCount,
1001
- ")"
1002
- ]
1003
- })]
1098
+ onChange: setScheduleDate
1099
+ })
1004
1100
  })
1005
- ]
1101
+ ] }),
1102
+ actions: pendingCount > 0 && onApprove ? /* @__PURE__ */ jsxs(Button, {
1103
+ variant: "outline",
1104
+ onClick: handleBulkApprove,
1105
+ children: [
1106
+ /* @__PURE__ */ jsx(CheckCheck, {}),
1107
+ "Approve all (",
1108
+ pendingCount,
1109
+ ")"
1110
+ ]
1111
+ }) : void 0
1006
1112
  }), filtered.length === 0 ? /* @__PURE__ */ jsx("div", {
1007
1113
  className: "py-12 text-center text-sm text-muted-foreground",
1008
1114
  children: "No assets match your filters."
@@ -1013,10 +1119,11 @@ function ApprovalQueue({ assets, variantCounts = {}, onApprove, onReject, onEdit
1013
1119
  variantCount: variantCounts[a.id],
1014
1120
  onApprove,
1015
1121
  onReject,
1016
- onEdit: () => setEditingId(a.id)
1122
+ onEdit: () => setEditingId(a.id),
1123
+ onOpen: onOpen ? (id) => onOpen(id, filtered.map((asset) => asset.id)) : void 0
1017
1124
  }, a.id))
1018
1125
  })]
1019
1126
  });
1020
1127
  }
1021
1128
  //#endregion
1022
- export { ApprovalQueue, AssetCard, AssetEditor, CopyPreview, EmailPreview, ImagePreview, VideoPreview };
1129
+ export { ASSET_FORMAT_LABEL, ASSET_STATUS_LABEL, ApprovalQueue, AssetCard, AssetEditor, CopyPreview, EmailPreview, ImagePreview, VideoPreview, imageOnlyUrl, videoPosterUrl };
@@ -1,4 +1,4 @@
1
- import { $n as Logo, b as Button$1 } from "./primitives-JjV1ruzp.js";
1
+ import { b as Button$1, sr as Logo } from "./primitives-DVo5ldkk.js";
2
2
  import { t as cn$1 } from "./utils-DzWbktWG.js";
3
3
  import { n as MOTION_SURFACE, r as MOTION_TRAVEL, t as MOTION_CONTROL } from "./motion-DkyBRJb9.js";
4
4
  import { i as moonshot_default } from "./model-brand-hMyDTUqB.js";
package/dist/dashboard.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as useSidebar, A as parseInsufficientBalance, B as SidebarPanel, C as SandboxStatusPill, D as ResourceMeter, E as ResourceSnapshot, F as RailHeader, G as SidebarRailNav, H as SidebarPanelHeader, I as RailModeButton, J as SIDEBAR_PANEL_WIDTH, K as RailTooltip, L as RailSeparator, M as RailButton, N as RailExpandable, O as INSUFFICIENT_BALANCE_CODE, P as RailFlyout, Q as SidebarProvider, R as Sidebar, S as SANDBOX_STATUS, T as ActivityFeed, U as SidebarRail, V as SidebarPanelContent, W as SidebarRailFooter, X as SIDEBAR_RAIL_WIDTH, Y as SIDEBAR_RAIL_LABELED_WIDTH, Z as SIDEBAR_TOTAL_WIDTH, _ as BackendSelector, a as PortsList, b as SandboxCard, c as SystemLogsViewer, d as DashboardLayout, f as HARNESS_OPTIONS, g as HarnessLogo, h as HARNESS_BRAND, i as TemplateCard, j as ProfileAvatar, k as OutOfCreditsModal, l as MetricAreaChart, m as chatCapableHarnesses, n as SnapshotList, o as GitPanel, p as HarnessPicker, q as SIDEBAR_MOBILE_WIDTH, r as ProcessList, s as UsageSummary, t as PromoBanner, u as SidebarLayout, v as SandboxTable, w as sandboxStatus, x as canAdminSandbox, y as NewSandboxCard, z as SidebarContent } from "./dashboard-BW-rPRwj.js";
1
+ import { $ as useSidebar, A as parseInsufficientBalance, B as SidebarPanel, C as SandboxStatusPill, D as ResourceMeter, E as ResourceSnapshot, F as RailHeader, G as SidebarRailNav, H as SidebarPanelHeader, I as RailModeButton, J as SIDEBAR_PANEL_WIDTH, K as RailTooltip, L as RailSeparator, M as RailButton, N as RailExpandable, O as INSUFFICIENT_BALANCE_CODE, P as RailFlyout, Q as SidebarProvider, R as Sidebar, S as SANDBOX_STATUS, T as ActivityFeed, U as SidebarRail, V as SidebarPanelContent, W as SidebarRailFooter, X as SIDEBAR_RAIL_WIDTH, Y as SIDEBAR_RAIL_LABELED_WIDTH, Z as SIDEBAR_TOTAL_WIDTH, _ as BackendSelector, a as PortsList, b as SandboxCard, c as SystemLogsViewer, d as DashboardLayout, f as HARNESS_OPTIONS, g as HarnessLogo, h as HARNESS_BRAND, i as TemplateCard, j as ProfileAvatar, k as OutOfCreditsModal, l as MetricAreaChart, m as chatCapableHarnesses, n as SnapshotList, o as GitPanel, p as HarnessPicker, q as SIDEBAR_MOBILE_WIDTH, r as ProcessList, s as UsageSummary, t as PromoBanner, u as SidebarLayout, v as SandboxTable, w as sandboxStatus, x as canAdminSandbox, y as NewSandboxCard, z as SidebarContent } from "./dashboard-B7mrHa2A.js";
2
2
  import { n as canonicalModelId, r as modelBrandFor, t as ModelBrandStack } from "./model-brand-hMyDTUqB.js";
3
3
  import { a as formatPrice, i as PricingPage, n as SandboxPageShell, o as BillingDashboard, r as UsageChart, t as DashboardPageHeader } from "./page-layout-2IRtfx51.js";
4
4
  export { ActivityFeed, BackendSelector, BillingDashboard, DashboardLayout, DashboardPageHeader, GitPanel, HARNESS_BRAND, HARNESS_OPTIONS, HarnessLogo, HarnessPicker, INSUFFICIENT_BALANCE_CODE, MetricAreaChart, ModelBrandStack, NewSandboxCard, OutOfCreditsModal, PortsList, PricingPage, ProcessList, ProfileAvatar, PromoBanner, RailButton, RailExpandable, RailFlyout, RailHeader, RailModeButton, RailSeparator, RailTooltip, ResourceMeter, ResourceSnapshot, SANDBOX_STATUS, SIDEBAR_MOBILE_WIDTH, SIDEBAR_PANEL_WIDTH, SIDEBAR_RAIL_LABELED_WIDTH, SIDEBAR_RAIL_WIDTH, SIDEBAR_TOTAL_WIDTH, SandboxCard, SandboxPageShell, SandboxStatusPill, SandboxTable, Sidebar, SidebarContent, SidebarLayout, SidebarPanel, SidebarPanelContent, SidebarPanelHeader, SidebarProvider, SidebarRail, SidebarRailFooter, SidebarRailNav, SnapshotList, SystemLogsViewer, TemplateCard, UsageChart, UsageSummary, canAdminSandbox, canonicalModelId, chatCapableHarnesses, formatPrice, modelBrandFor, parseInsufficientBalance, sandboxStatus, useSidebar };