@mediaradar/design-components 0.1.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2531 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1137 -1
- package/dist/index.d.ts +1137 -1
- package/dist/index.js +2503 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -20,7 +20,34 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
|
|
23
|
+
AdvertiserNoResults: () => AdvertiserNoResults,
|
|
24
|
+
AdvertiserQuickFilter: () => AdvertiserQuickFilter,
|
|
25
|
+
AdvertiserSearchPrompt: () => AdvertiserSearchPrompt,
|
|
26
|
+
BarChartPlaceholder: () => BarChartPlaceholder,
|
|
27
|
+
Card: () => Card,
|
|
28
|
+
CheckBox: () => CheckBox,
|
|
29
|
+
DEFAULT_SEARCH_MATCH: () => DEFAULT_SEARCH_MATCH,
|
|
30
|
+
DataScopeFilter: () => DataScopeFilter,
|
|
31
|
+
DateRangeFilter: () => DateRangeFilter,
|
|
32
|
+
InfoTip: () => InfoTip,
|
|
33
|
+
LineChartPlaceholder: () => LineChartPlaceholder,
|
|
34
|
+
NO_RESULTS_BODY: () => NO_RESULTS_BODY,
|
|
35
|
+
NO_RESULTS_TITLE: () => NO_RESULTS_TITLE,
|
|
36
|
+
NoResultsEmptyState: () => NoResultsEmptyState,
|
|
37
|
+
PRIMARY_ACTION_CLASS: () => PRIMARY_ACTION_CLASS,
|
|
38
|
+
PieChartPlaceholder: () => PieChartPlaceholder,
|
|
39
|
+
PublisherBreakdownList: () => PublisherBreakdownList,
|
|
40
|
+
QUIET_ACTION_CLASS: () => QUIET_ACTION_CLASS,
|
|
41
|
+
SearchBar: () => SearchBar,
|
|
42
|
+
SectionRail: () => SectionRail,
|
|
43
|
+
SelectAllVisible: () => SelectAllVisible,
|
|
44
|
+
SelectFilter: () => SelectFilter,
|
|
45
|
+
SelectedSection: () => SelectedSection,
|
|
46
|
+
SelectionPicker: () => SelectionPicker,
|
|
47
|
+
StackedColumnPlaceholder: () => StackedColumnPlaceholder,
|
|
48
|
+
Tooltip: () => Tooltip,
|
|
49
|
+
WorkspaceSelectedCard: () => WorkspaceSelectedCard,
|
|
50
|
+
noResultsBody: () => noResultsBody
|
|
24
51
|
});
|
|
25
52
|
module.exports = __toCommonJS(index_exports);
|
|
26
53
|
|
|
@@ -96,8 +123,2510 @@ function InfoTip({ text, size = 11, label = "More information", trigger = "click
|
|
|
96
123
|
)
|
|
97
124
|
] });
|
|
98
125
|
}
|
|
126
|
+
|
|
127
|
+
// src/AdvertiserQuickFilter.tsx
|
|
128
|
+
var import_react2 = require("react");
|
|
129
|
+
var import_lucide_react3 = require("lucide-react");
|
|
130
|
+
|
|
131
|
+
// src/AdvertiserSearchPrompt.tsx
|
|
132
|
+
var import_lucide_react2 = require("lucide-react");
|
|
133
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
134
|
+
function AdvertiserSearchPrompt({ label = "an advertiser name" }) {
|
|
135
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-2 px-4 py-8 text-center", children: [
|
|
136
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "flex h-9 w-9 items-center justify-center rounded-full bg-subtle text-text-tertiary", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.Search, { size: 16, "aria-hidden": "true" }) }),
|
|
137
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "text-[12px] leading-snug text-text-tertiary", children: [
|
|
138
|
+
"Start typing ",
|
|
139
|
+
label,
|
|
140
|
+
" to filter."
|
|
141
|
+
] })
|
|
142
|
+
] });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/AdvertiserQuickFilter.tsx
|
|
146
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
147
|
+
function cx(...parts) {
|
|
148
|
+
return parts.filter(Boolean).join(" ");
|
|
149
|
+
}
|
|
150
|
+
var SELECT_ALL_THRESHOLD = 5;
|
|
151
|
+
function slug(value) {
|
|
152
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
153
|
+
}
|
|
154
|
+
function AdvertiserQuickFilter({
|
|
155
|
+
caption = "Advertiser",
|
|
156
|
+
options,
|
|
157
|
+
selected,
|
|
158
|
+
onChange,
|
|
159
|
+
align = "left",
|
|
160
|
+
icon,
|
|
161
|
+
showFooter = false,
|
|
162
|
+
clearValue = [],
|
|
163
|
+
parked = [],
|
|
164
|
+
searchPlaceholder = "Search for an advertiser",
|
|
165
|
+
promptLabel = "an advertiser name",
|
|
166
|
+
noResultsHint = "Try checking your spelling or searching for a broader brand name.",
|
|
167
|
+
clearLabel = "Clear advertisers",
|
|
168
|
+
testIdPrefix
|
|
169
|
+
}) {
|
|
170
|
+
const [open, setOpen] = (0, import_react2.useState)(false);
|
|
171
|
+
const [q, setQ] = (0, import_react2.useState)("");
|
|
172
|
+
const [draft, setDraft] = (0, import_react2.useState)(selected);
|
|
173
|
+
const [selectedOpen, setSelectedOpen] = (0, import_react2.useState)(true);
|
|
174
|
+
const ref = (0, import_react2.useRef)(null);
|
|
175
|
+
const inputRef = (0, import_react2.useRef)(null);
|
|
176
|
+
const clearSearch = () => {
|
|
177
|
+
setQ("");
|
|
178
|
+
setSelectedOpen(false);
|
|
179
|
+
};
|
|
180
|
+
const close = (0, import_react2.useCallback)(() => {
|
|
181
|
+
setOpen(false);
|
|
182
|
+
setQ("");
|
|
183
|
+
if (showFooter) return;
|
|
184
|
+
if (draft.length !== selected.length || draft.some((id) => !selected.includes(id))) {
|
|
185
|
+
onChange(draft);
|
|
186
|
+
}
|
|
187
|
+
}, [draft, selected, onChange, showFooter]);
|
|
188
|
+
const openTray = () => {
|
|
189
|
+
setDraft(selected);
|
|
190
|
+
setSelectedOpen(true);
|
|
191
|
+
setOpen(true);
|
|
192
|
+
};
|
|
193
|
+
const apply = () => {
|
|
194
|
+
onChange(draft);
|
|
195
|
+
setOpen(false);
|
|
196
|
+
setQ("");
|
|
197
|
+
};
|
|
198
|
+
(0, import_react2.useEffect)(() => {
|
|
199
|
+
if (!open) return;
|
|
200
|
+
inputRef.current?.focus();
|
|
201
|
+
const onKey = (e) => e.key === "Escape" && close();
|
|
202
|
+
const onClick = (e) => {
|
|
203
|
+
if (ref.current && !ref.current.contains(e.target)) close();
|
|
204
|
+
};
|
|
205
|
+
window.addEventListener("keydown", onKey);
|
|
206
|
+
window.addEventListener("mousedown", onClick);
|
|
207
|
+
return () => {
|
|
208
|
+
window.removeEventListener("keydown", onKey);
|
|
209
|
+
window.removeEventListener("mousedown", onClick);
|
|
210
|
+
};
|
|
211
|
+
}, [open, close]);
|
|
212
|
+
const toggle = (id) => setDraft((cur) => cur.includes(id) ? cur.filter((x) => x !== id) : [...cur, id]);
|
|
213
|
+
const ql = q.trim().toLowerCase();
|
|
214
|
+
const selectedOptions = (0, import_react2.useMemo)(
|
|
215
|
+
() => draft.map((id) => options.find((o) => o.id === id)).filter(Boolean),
|
|
216
|
+
[draft, options]
|
|
217
|
+
);
|
|
218
|
+
const results = (0, import_react2.useMemo)(
|
|
219
|
+
() => ql ? options.filter((o) => !draft.includes(o.id) && o.label.toLowerCase().includes(ql)) : [],
|
|
220
|
+
[ql, options, draft]
|
|
221
|
+
);
|
|
222
|
+
const visibleIds = results.map((o) => o.id);
|
|
223
|
+
const allVisibleSelected = visibleIds.length > 0 && visibleIds.every((id) => draft.includes(id));
|
|
224
|
+
const toggleAllVisible = () => setDraft(
|
|
225
|
+
(cur) => allVisibleSelected ? cur.filter((id) => !visibleIds.includes(id)) : [.../* @__PURE__ */ new Set([...cur, ...visibleIds])]
|
|
226
|
+
);
|
|
227
|
+
const shown = open && !showFooter ? draft : selected;
|
|
228
|
+
const counted = parked.length ? shown.filter((id) => !parked.includes(id)) : shown;
|
|
229
|
+
const value = counted.length === 0 ? "All" : counted.length === 1 ? options.find((o) => o.id === counted[0])?.label ?? "1 selected" : `${counted.length} selected`;
|
|
230
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "relative", ref, children: [
|
|
231
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
232
|
+
"button",
|
|
233
|
+
{
|
|
234
|
+
type: "button",
|
|
235
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-trigger`,
|
|
236
|
+
onClick: () => open ? close() : openTray(),
|
|
237
|
+
"aria-haspopup": "dialog",
|
|
238
|
+
"aria-expanded": open,
|
|
239
|
+
className: "flex h-11 items-center gap-2 rounded-control border border-border-strong bg-input px-2.5 text-text-secondary transition-colors hover:bg-subtle hover:text-text-primary focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-border-focus",
|
|
240
|
+
children: [
|
|
241
|
+
icon && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "shrink-0 text-text-tertiary", children: icon }),
|
|
242
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "flex flex-col gap-1 text-left leading-none", children: [
|
|
243
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-text-tertiary", children: caption }),
|
|
244
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[12px] font-medium text-text-primary", children: value })
|
|
245
|
+
] }),
|
|
246
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
247
|
+
import_lucide_react3.ChevronDown,
|
|
248
|
+
{
|
|
249
|
+
size: 14,
|
|
250
|
+
"aria-hidden": "true",
|
|
251
|
+
className: cx("shrink-0 text-text-secondary transition-transform", open && "rotate-180")
|
|
252
|
+
}
|
|
253
|
+
)
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
),
|
|
257
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
258
|
+
"div",
|
|
259
|
+
{
|
|
260
|
+
className: cx(
|
|
261
|
+
"absolute top-[calc(100%+8px)] z-30 flex w-72 flex-col overflow-hidden rounded-card border border-border-default bg-surface shadow-overlay",
|
|
262
|
+
align === "right" ? "right-0" : "left-0"
|
|
263
|
+
),
|
|
264
|
+
role: "dialog",
|
|
265
|
+
"aria-label": caption,
|
|
266
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-popover`,
|
|
267
|
+
children: [
|
|
268
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "border-b border-border-default p-2", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex h-9 items-center gap-2 rounded-control border border-border-default bg-input px-2.5 focus-within:border-border-focus focus-within:ring-2 focus-within:ring-border-focus/30", children: [
|
|
269
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react3.Search, { size: 14, className: "shrink-0 text-text-secondary", "aria-hidden": "true" }),
|
|
270
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
271
|
+
"input",
|
|
272
|
+
{
|
|
273
|
+
ref: inputRef,
|
|
274
|
+
type: "text",
|
|
275
|
+
value: q,
|
|
276
|
+
onChange: (e) => setQ(e.target.value),
|
|
277
|
+
onMouseDown: () => setSelectedOpen(false),
|
|
278
|
+
placeholder: searchPlaceholder,
|
|
279
|
+
"aria-label": searchPlaceholder,
|
|
280
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-search`,
|
|
281
|
+
className: "h-full min-w-0 flex-1 bg-transparent text-[13px] outline-hidden placeholder:text-text-tertiary"
|
|
282
|
+
}
|
|
283
|
+
),
|
|
284
|
+
q && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
285
|
+
"button",
|
|
286
|
+
{
|
|
287
|
+
type: "button",
|
|
288
|
+
onClick: clearSearch,
|
|
289
|
+
"aria-label": "Clear search",
|
|
290
|
+
className: "shrink-0 text-text-tertiary transition-colors hover:text-text-primary",
|
|
291
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react3.X, { size: 13 })
|
|
292
|
+
}
|
|
293
|
+
)
|
|
294
|
+
] }) }),
|
|
295
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "max-h-[300px] overflow-y-auto p-1", children: [
|
|
296
|
+
selectedOptions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mb-1 border-b border-border-default pb-1", children: [
|
|
297
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
298
|
+
"button",
|
|
299
|
+
{
|
|
300
|
+
type: "button",
|
|
301
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-selected-toggle`,
|
|
302
|
+
onClick: () => setSelectedOpen((v) => !v),
|
|
303
|
+
"aria-expanded": selectedOpen,
|
|
304
|
+
className: "flex w-full items-center gap-1.5 rounded-control px-2 py-1 text-left text-[11px] font-semibold uppercase tracking-wide text-text-tertiary transition-colors hover:text-text-primary",
|
|
305
|
+
children: [
|
|
306
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
307
|
+
import_lucide_react3.ChevronDown,
|
|
308
|
+
{
|
|
309
|
+
size: 13,
|
|
310
|
+
"aria-hidden": "true",
|
|
311
|
+
className: cx("transition-transform", !selectedOpen && "-rotate-90")
|
|
312
|
+
}
|
|
313
|
+
),
|
|
314
|
+
"Selected (",
|
|
315
|
+
selectedOptions.length,
|
|
316
|
+
")"
|
|
317
|
+
]
|
|
318
|
+
}
|
|
319
|
+
),
|
|
320
|
+
selectedOpen && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex flex-col gap-0.5", children: selectedOptions.map((o) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
321
|
+
OptionRow,
|
|
322
|
+
{
|
|
323
|
+
label: o.label,
|
|
324
|
+
checked: true,
|
|
325
|
+
onToggle: () => toggle(o.id),
|
|
326
|
+
testId: testIdPrefix && `${testIdPrefix}-option-${slug(o.id)}`
|
|
327
|
+
},
|
|
328
|
+
o.id
|
|
329
|
+
)) })
|
|
330
|
+
] }),
|
|
331
|
+
!ql ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AdvertiserSearchPrompt, { label: promptLabel }) : results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col items-center gap-1.5 px-4 py-8 text-center", children: [
|
|
332
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "text-[13px] font-medium text-text-primary", children: [
|
|
333
|
+
"No results found for \u201C",
|
|
334
|
+
q.trim(),
|
|
335
|
+
"\u201D"
|
|
336
|
+
] }),
|
|
337
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-[12px] leading-snug text-text-tertiary", children: noResultsHint })
|
|
338
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
339
|
+
results.length > SELECT_ALL_THRESHOLD && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
340
|
+
"button",
|
|
341
|
+
{
|
|
342
|
+
type: "button",
|
|
343
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-select-all-visible`,
|
|
344
|
+
onClick: toggleAllVisible,
|
|
345
|
+
className: "flex w-full items-center gap-2 rounded-control px-2 py-1.5 text-left text-[12px] font-semibold text-text-link transition-colors hover:bg-subtle",
|
|
346
|
+
children: [
|
|
347
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react3.ListChecks, { size: 14, "aria-hidden": "true" }),
|
|
348
|
+
allVisibleSelected ? `Deselect ${results.length} visible` : `Select all ${results.length} visible`
|
|
349
|
+
]
|
|
350
|
+
}
|
|
351
|
+
),
|
|
352
|
+
results.map((o) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
353
|
+
OptionRow,
|
|
354
|
+
{
|
|
355
|
+
label: o.label,
|
|
356
|
+
checked: false,
|
|
357
|
+
onToggle: () => toggle(o.id),
|
|
358
|
+
testId: testIdPrefix && `${testIdPrefix}-option-${slug(o.id)}`
|
|
359
|
+
},
|
|
360
|
+
o.id
|
|
361
|
+
))
|
|
362
|
+
] })
|
|
363
|
+
] }),
|
|
364
|
+
showFooter ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between border-t border-border-default px-3 py-2.5", children: [
|
|
365
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
366
|
+
"button",
|
|
367
|
+
{
|
|
368
|
+
type: "button",
|
|
369
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-clear-all`,
|
|
370
|
+
onClick: () => setDraft(clearValue),
|
|
371
|
+
disabled: draft.length === 0,
|
|
372
|
+
className: "text-[13px] font-medium text-text-secondary transition-colors hover:text-text-primary disabled:opacity-40",
|
|
373
|
+
children: "Clear all"
|
|
374
|
+
}
|
|
375
|
+
),
|
|
376
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
377
|
+
"button",
|
|
378
|
+
{
|
|
379
|
+
type: "button",
|
|
380
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-apply`,
|
|
381
|
+
onClick: apply,
|
|
382
|
+
className: "rounded-control bg-brand-primaryAction px-5 py-2 text-[13px] font-semibold text-brand-onprimary transition hover:brightness-110",
|
|
383
|
+
children: "Apply"
|
|
384
|
+
}
|
|
385
|
+
)
|
|
386
|
+
] }) : draft.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between border-t border-border-default p-2", children: [
|
|
387
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "text-[11px] text-text-tertiary", children: [
|
|
388
|
+
draft.length,
|
|
389
|
+
" selected"
|
|
390
|
+
] }),
|
|
391
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
392
|
+
"button",
|
|
393
|
+
{
|
|
394
|
+
type: "button",
|
|
395
|
+
onClick: () => setDraft([]),
|
|
396
|
+
className: "text-[12px] font-medium text-text-link hover:underline",
|
|
397
|
+
children: clearLabel
|
|
398
|
+
}
|
|
399
|
+
)
|
|
400
|
+
] })
|
|
401
|
+
]
|
|
402
|
+
}
|
|
403
|
+
)
|
|
404
|
+
] });
|
|
405
|
+
}
|
|
406
|
+
function OptionRow({
|
|
407
|
+
label,
|
|
408
|
+
checked,
|
|
409
|
+
onToggle,
|
|
410
|
+
testId
|
|
411
|
+
}) {
|
|
412
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
413
|
+
"div",
|
|
414
|
+
{
|
|
415
|
+
onClick: onToggle,
|
|
416
|
+
className: "flex cursor-pointer items-center gap-2.5 rounded-control px-2 py-2 transition-colors hover:bg-subtle",
|
|
417
|
+
children: [
|
|
418
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
419
|
+
"button",
|
|
420
|
+
{
|
|
421
|
+
type: "button",
|
|
422
|
+
role: "checkbox",
|
|
423
|
+
"aria-checked": checked,
|
|
424
|
+
"aria-label": label,
|
|
425
|
+
"data-testid": testId,
|
|
426
|
+
onClick: (e) => {
|
|
427
|
+
e.stopPropagation();
|
|
428
|
+
onToggle();
|
|
429
|
+
},
|
|
430
|
+
className: cx(
|
|
431
|
+
"flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors",
|
|
432
|
+
"focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-border-focus",
|
|
433
|
+
checked ? "border-transparent bg-brand-primaryAction text-brand-onprimary" : "border-border-interactive bg-surface hover:border-brand-primaryAction"
|
|
434
|
+
),
|
|
435
|
+
children: checked && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react3.Check, { size: 13, strokeWidth: 3 })
|
|
436
|
+
}
|
|
437
|
+
),
|
|
438
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex-1 text-[14px] text-text-primary", children: label })
|
|
439
|
+
]
|
|
440
|
+
}
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/CheckBox.tsx
|
|
445
|
+
var import_lucide_react4 = require("lucide-react");
|
|
446
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
447
|
+
function CheckBox({ checked, indeterminate, onChange, ariaLabel, disabled, testId }) {
|
|
448
|
+
const on = checked || indeterminate;
|
|
449
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
450
|
+
"button",
|
|
451
|
+
{
|
|
452
|
+
type: "button",
|
|
453
|
+
role: "checkbox",
|
|
454
|
+
"aria-checked": indeterminate ? "mixed" : checked,
|
|
455
|
+
"aria-label": ariaLabel,
|
|
456
|
+
disabled,
|
|
457
|
+
"data-testid": testId,
|
|
458
|
+
onClick: (e) => {
|
|
459
|
+
e.stopPropagation();
|
|
460
|
+
onChange();
|
|
461
|
+
},
|
|
462
|
+
className: [
|
|
463
|
+
"flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors",
|
|
464
|
+
"focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-border-focus",
|
|
465
|
+
on ? "border-transparent bg-brand-primaryAction text-white" : "border-border-interactive bg-input hover:border-brand-primaryAction",
|
|
466
|
+
disabled && "cursor-not-allowed opacity-50"
|
|
467
|
+
].filter(Boolean).join(" "),
|
|
468
|
+
children: indeterminate ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react4.Minus, { size: 13, strokeWidth: 3 }) : checked ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react4.Check, { size: 13, strokeWidth: 3 }) : null
|
|
469
|
+
}
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// src/DateRangeFilter.tsx
|
|
474
|
+
var import_react3 = require("react");
|
|
475
|
+
var import_lucide_react5 = require("lucide-react");
|
|
476
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
477
|
+
var DEFAULT_SHORTCUTS = [
|
|
478
|
+
"Year to date",
|
|
479
|
+
"Rolling 3 months",
|
|
480
|
+
"Rolling 6 months",
|
|
481
|
+
"Rolling 12 months",
|
|
482
|
+
"Previous quarter",
|
|
483
|
+
"Current quarter to date",
|
|
484
|
+
"Previous month",
|
|
485
|
+
"Current month",
|
|
486
|
+
"Custom"
|
|
487
|
+
];
|
|
488
|
+
var CREATIVE_INTEL_SHORTCUTS = [
|
|
489
|
+
"Today",
|
|
490
|
+
"Yesterday",
|
|
491
|
+
"Last 7 days",
|
|
492
|
+
"Last 14 days",
|
|
493
|
+
"Last month",
|
|
494
|
+
"Rolling 3 months",
|
|
495
|
+
"Rolling 6 months",
|
|
496
|
+
"Rolling 12 months",
|
|
497
|
+
"Year to date",
|
|
498
|
+
"Previous year",
|
|
499
|
+
"All available history",
|
|
500
|
+
"Custom"
|
|
501
|
+
];
|
|
502
|
+
var WEEKDAYS = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
|
|
503
|
+
var MONTHS = [
|
|
504
|
+
"January",
|
|
505
|
+
"February",
|
|
506
|
+
"March",
|
|
507
|
+
"April",
|
|
508
|
+
"May",
|
|
509
|
+
"June",
|
|
510
|
+
"July",
|
|
511
|
+
"August",
|
|
512
|
+
"September",
|
|
513
|
+
"October",
|
|
514
|
+
"November",
|
|
515
|
+
"December"
|
|
516
|
+
];
|
|
517
|
+
var startOfDay = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
518
|
+
var addDays = (d, n) => {
|
|
519
|
+
const x = new Date(d);
|
|
520
|
+
x.setDate(x.getDate() + n);
|
|
521
|
+
return x;
|
|
522
|
+
};
|
|
523
|
+
var addMonths = (d, n) => new Date(d.getFullYear(), d.getMonth() + n, 1);
|
|
524
|
+
var sameDay = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
525
|
+
var fmt = (d) => `${MONTHS[d.getMonth()].slice(0, 3)} ${d.getDate()}, ${d.getFullYear()}`;
|
|
526
|
+
var fmtFull = (d) => `${MONTHS[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`;
|
|
527
|
+
var fullRangeLabel = (s, e) => `${fmtFull(s)} - ${fmtFull(e)}`;
|
|
528
|
+
var presetLabel = (sc) => sc === "Custom" ? "Custom range" : sc;
|
|
529
|
+
function presetRange(key, today) {
|
|
530
|
+
const t = startOfDay(today);
|
|
531
|
+
const qMonth = Math.floor(t.getMonth() / 3) * 3;
|
|
532
|
+
switch (key) {
|
|
533
|
+
case "Year to date":
|
|
534
|
+
return [new Date(t.getFullYear(), 0, 1), t];
|
|
535
|
+
case "Rolling 3 months":
|
|
536
|
+
return [addMonths(t, -3), t];
|
|
537
|
+
case "Rolling 6 months":
|
|
538
|
+
return [addMonths(t, -6), t];
|
|
539
|
+
case "Rolling 12 months":
|
|
540
|
+
return [addMonths(t, -12), t];
|
|
541
|
+
case "Previous quarter": {
|
|
542
|
+
const s = new Date(t.getFullYear(), qMonth - 3, 1);
|
|
543
|
+
const e = new Date(t.getFullYear(), qMonth, 0);
|
|
544
|
+
return [s, e];
|
|
545
|
+
}
|
|
546
|
+
case "Current quarter to date":
|
|
547
|
+
return [new Date(t.getFullYear(), qMonth, 1), t];
|
|
548
|
+
case "Previous month": {
|
|
549
|
+
const s = new Date(t.getFullYear(), t.getMonth() - 1, 1);
|
|
550
|
+
const e = new Date(t.getFullYear(), t.getMonth(), 0);
|
|
551
|
+
return [s, e];
|
|
552
|
+
}
|
|
553
|
+
case "Current month":
|
|
554
|
+
return [new Date(t.getFullYear(), t.getMonth(), 1), t];
|
|
555
|
+
case "Today":
|
|
556
|
+
return [t, t];
|
|
557
|
+
case "Yesterday": {
|
|
558
|
+
const y = addDays(t, -1);
|
|
559
|
+
return [y, y];
|
|
560
|
+
}
|
|
561
|
+
case "Last 7 days":
|
|
562
|
+
return [addDays(t, -6), t];
|
|
563
|
+
case "Last 14 days":
|
|
564
|
+
return [addDays(t, -13), t];
|
|
565
|
+
case "Last month": {
|
|
566
|
+
const s = new Date(t.getFullYear(), t.getMonth() - 1, 1);
|
|
567
|
+
const e = new Date(t.getFullYear(), t.getMonth(), 0);
|
|
568
|
+
return [s, e];
|
|
569
|
+
}
|
|
570
|
+
case "Previous year":
|
|
571
|
+
return [new Date(t.getFullYear() - 1, 0, 1), new Date(t.getFullYear() - 1, 11, 31)];
|
|
572
|
+
case "All available history":
|
|
573
|
+
return [new Date(2e3, 0, 1), t];
|
|
574
|
+
default:
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function monthGrid(year, month) {
|
|
579
|
+
const first = new Date(year, month, 1);
|
|
580
|
+
const dow = (first.getDay() + 6) % 7;
|
|
581
|
+
const gridStart = addDays(first, -dow);
|
|
582
|
+
return Array.from({ length: 42 }, (_, i) => addDays(gridStart, i));
|
|
583
|
+
}
|
|
584
|
+
function matchShortcut(shortcuts, label) {
|
|
585
|
+
if (!label) return "Rolling 12 months";
|
|
586
|
+
const hit = shortcuts.find((s) => s.toLowerCase() === label.toLowerCase());
|
|
587
|
+
return hit || "Custom";
|
|
588
|
+
}
|
|
589
|
+
function slug2(value) {
|
|
590
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
591
|
+
}
|
|
592
|
+
function DateRangeFilter({
|
|
593
|
+
caption = "Date Range",
|
|
594
|
+
defaultValue,
|
|
595
|
+
value: controlledValue,
|
|
596
|
+
range: controlledRange,
|
|
597
|
+
align = "left",
|
|
598
|
+
onChange,
|
|
599
|
+
onRangeChange,
|
|
600
|
+
showDates = false,
|
|
601
|
+
shortDates = false,
|
|
602
|
+
icon,
|
|
603
|
+
presetSet = "default",
|
|
604
|
+
testIdPrefix
|
|
605
|
+
}) {
|
|
606
|
+
const controlled = controlledValue !== void 0 && controlledRange !== void 0;
|
|
607
|
+
const SHORTCUTS = presetSet === "creativeIntel" ? CREATIVE_INTEL_SHORTCUTS : DEFAULT_SHORTCUTS;
|
|
608
|
+
const today = (0, import_react3.useMemo)(() => /* @__PURE__ */ new Date(), []);
|
|
609
|
+
const initShortcut = matchShortcut(SHORTCUTS, controlled ? controlledValue : defaultValue);
|
|
610
|
+
const initRange = controlled ? controlledRange : presetRange(initShortcut === "Custom" ? "Rolling 12 months" : initShortcut, today);
|
|
611
|
+
const dateLine = (s, e) => shortDates ? `${fmt(s)} \u2013 ${fmt(e)}` : fullRangeLabel(s, e);
|
|
612
|
+
const [open, setOpen] = (0, import_react3.useState)(false);
|
|
613
|
+
const [uncontrolledValue, setUncontrolledValue] = (0, import_react3.useState)(
|
|
614
|
+
showDates ? presetLabel(initShortcut) : defaultValue || "Last 7 days"
|
|
615
|
+
);
|
|
616
|
+
const [uncontrolledDatesLine, setUncontrolledDatesLine] = (0, import_react3.useState)(dateLine(initRange[0], initRange[1]));
|
|
617
|
+
const committed = (0, import_react3.useRef)({ shortcut: initShortcut, start: initRange[0], end: initRange[1] });
|
|
618
|
+
const displayValue = controlled ? showDates ? presetLabel(initShortcut) : controlledValue : uncontrolledValue;
|
|
619
|
+
const displayDatesLine = controlled ? dateLine(initRange[0], initRange[1]) : uncontrolledDatesLine;
|
|
620
|
+
const [shortcut, setShortcut] = (0, import_react3.useState)(initShortcut);
|
|
621
|
+
const [start, setStart] = (0, import_react3.useState)(initRange[0]);
|
|
622
|
+
const [end, setEnd] = (0, import_react3.useState)(initRange[1]);
|
|
623
|
+
const [leftMonth, setLeftMonth] = (0, import_react3.useState)(new Date(initRange[0].getFullYear(), initRange[0].getMonth(), 1));
|
|
624
|
+
const ref = (0, import_react3.useRef)(null);
|
|
625
|
+
(0, import_react3.useEffect)(() => {
|
|
626
|
+
if (controlled) return;
|
|
627
|
+
onRangeChange?.(committed.current.start, committed.current.end);
|
|
628
|
+
}, []);
|
|
629
|
+
(0, import_react3.useEffect)(() => {
|
|
630
|
+
if (!open) return;
|
|
631
|
+
const committedShortcut = controlled ? initShortcut : committed.current.shortcut;
|
|
632
|
+
const committedStart = controlled ? initRange[0] : committed.current.start;
|
|
633
|
+
const committedEnd = controlled ? initRange[1] : committed.current.end;
|
|
634
|
+
setShortcut(committedShortcut);
|
|
635
|
+
setStart(committedStart);
|
|
636
|
+
setEnd(committedEnd);
|
|
637
|
+
setLeftMonth(new Date(committedStart.getFullYear(), committedStart.getMonth(), 1));
|
|
638
|
+
const onKey = (e) => e.key === "Escape" && setOpen(false);
|
|
639
|
+
const onClick = (e) => {
|
|
640
|
+
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
641
|
+
};
|
|
642
|
+
window.addEventListener("keydown", onKey);
|
|
643
|
+
window.addEventListener("mousedown", onClick);
|
|
644
|
+
return () => {
|
|
645
|
+
window.removeEventListener("keydown", onKey);
|
|
646
|
+
window.removeEventListener("mousedown", onClick);
|
|
647
|
+
};
|
|
648
|
+
}, [open]);
|
|
649
|
+
const choosePreset = (key) => {
|
|
650
|
+
setShortcut(key);
|
|
651
|
+
if (key === "Custom") return;
|
|
652
|
+
const r = presetRange(key, today);
|
|
653
|
+
if (r) {
|
|
654
|
+
setStart(r[0]);
|
|
655
|
+
setEnd(r[1]);
|
|
656
|
+
setLeftMonth(new Date(r[0].getFullYear(), r[0].getMonth(), 1));
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
const pickDay = (d) => {
|
|
660
|
+
setShortcut("Custom");
|
|
661
|
+
if (!start || start && end) {
|
|
662
|
+
setStart(d);
|
|
663
|
+
setEnd(null);
|
|
664
|
+
} else if (d < start) {
|
|
665
|
+
setEnd(start);
|
|
666
|
+
setStart(d);
|
|
667
|
+
} else setEnd(d);
|
|
668
|
+
};
|
|
669
|
+
const apply = () => {
|
|
670
|
+
const s = start || initRange[0];
|
|
671
|
+
const e = end || s;
|
|
672
|
+
const v = showDates ? presetLabel(shortcut) : shortcut === "Custom" ? `${fmt(s)} \u2014 ${fmt(e)}` : shortcut;
|
|
673
|
+
if (!controlled) {
|
|
674
|
+
committed.current = { shortcut, start: s, end: e };
|
|
675
|
+
setUncontrolledValue(v);
|
|
676
|
+
setUncontrolledDatesLine(dateLine(s, e));
|
|
677
|
+
}
|
|
678
|
+
onChange?.(v);
|
|
679
|
+
onRangeChange?.(s, e);
|
|
680
|
+
setOpen(false);
|
|
681
|
+
};
|
|
682
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "relative", ref, children: [
|
|
683
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
684
|
+
"button",
|
|
685
|
+
{
|
|
686
|
+
type: "button",
|
|
687
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-trigger`,
|
|
688
|
+
onClick: () => setOpen((v) => !v),
|
|
689
|
+
"aria-expanded": open,
|
|
690
|
+
className: "flex h-11 items-center gap-2 rounded-control border border-border-strong bg-input px-2.5 text-text-secondary transition-colors hover:bg-subtle hover:text-text-primary focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-border-focus",
|
|
691
|
+
children: [
|
|
692
|
+
icon && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "shrink-0 text-text-tertiary", children: icon }),
|
|
693
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "flex flex-col gap-1 text-left leading-none", children: [
|
|
694
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: showDates ? "text-[11px] text-text-tertiary" : "text-[10px] uppercase tracking-wide text-text-tertiary", children: showDates ? displayDatesLine : caption }),
|
|
695
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[12px] font-medium text-text-primary", children: displayValue })
|
|
696
|
+
] }),
|
|
697
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react5.ChevronDown, { size: 14, "aria-hidden": "true", className: `shrink-0 text-text-secondary transition-transform ${open ? "rotate-180" : ""}` })
|
|
698
|
+
]
|
|
699
|
+
}
|
|
700
|
+
),
|
|
701
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
702
|
+
"div",
|
|
703
|
+
{
|
|
704
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-popover`,
|
|
705
|
+
className: `absolute top-[calc(100%+8px)] z-30 flex overflow-hidden rounded-card border border-border-default bg-surface shadow-overlay ${align === "right" ? "right-0" : "left-0"}`,
|
|
706
|
+
role: "dialog",
|
|
707
|
+
"aria-label": "Select date range",
|
|
708
|
+
children: [
|
|
709
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex w-44 shrink-0 flex-col gap-0.5 border-r border-border-default p-2", children: SHORTCUTS.map((s) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
710
|
+
"button",
|
|
711
|
+
{
|
|
712
|
+
type: "button",
|
|
713
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-option-${slug2(s)}`,
|
|
714
|
+
onClick: () => choosePreset(s),
|
|
715
|
+
className: `rounded-control px-3 py-2 text-left text-[13px] font-medium transition-colors ${shortcut === s ? "bg-subtle text-text-primary" : "text-text-secondary hover:bg-subtle hover:text-text-primary"}`,
|
|
716
|
+
children: s
|
|
717
|
+
},
|
|
718
|
+
s
|
|
719
|
+
)) }),
|
|
720
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col p-4", children: [
|
|
721
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-end gap-3", children: [
|
|
722
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Field, { label: "Start Date", value: start ? fmt(start) : "\u2014" }),
|
|
723
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "pb-2.5 text-text-tertiary", children: "\u2014" }),
|
|
724
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Field, { label: "End Date", value: end ? fmt(end) : start ? fmt(start) : "\u2014" })
|
|
725
|
+
] }),
|
|
726
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "my-4 border-t border-border-default" }),
|
|
727
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex gap-8", children: [0, 1].map((offset) => {
|
|
728
|
+
const m = addMonths(leftMonth, offset);
|
|
729
|
+
const grid = monthGrid(m.getFullYear(), m.getMonth());
|
|
730
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "w-[252px]", children: [
|
|
731
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mb-2 flex items-center justify-between", children: [
|
|
732
|
+
offset === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NavBtn, { onClick: () => setLeftMonth(addMonths(leftMonth, -1)), "aria-label": "Previous month", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react5.ChevronLeft, { size: 16 }) }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "h-7 w-7" }),
|
|
733
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "text-[15px] font-semibold text-text-primary", children: [
|
|
734
|
+
MONTHS[m.getMonth()],
|
|
735
|
+
" ",
|
|
736
|
+
m.getFullYear()
|
|
737
|
+
] }),
|
|
738
|
+
offset === 1 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NavBtn, { onClick: () => setLeftMonth(addMonths(leftMonth, 1)), "aria-label": "Next month", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react5.ChevronRight, { size: 16 }) }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "h-7 w-7" })
|
|
739
|
+
] }),
|
|
740
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "grid grid-cols-7", children: [
|
|
741
|
+
WEEKDAYS.map((w) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex h-8 items-center justify-center text-[12px] font-semibold text-text-secondary", children: w }, w)),
|
|
742
|
+
grid.map((d, i) => {
|
|
743
|
+
const inMonth = d.getMonth() === m.getMonth();
|
|
744
|
+
const hasRange = !!(start && end);
|
|
745
|
+
const isStart = !!start && sameDay(d, start);
|
|
746
|
+
const isEnd = end ? sameDay(d, end) : isStart && !end;
|
|
747
|
+
const inRange = hasRange && d >= startOfDay(start) && d <= startOfDay(end);
|
|
748
|
+
const single = isStart && isEnd;
|
|
749
|
+
const endpoint = isStart || !!end && isEnd;
|
|
750
|
+
let band = "";
|
|
751
|
+
if (inRange && !single) {
|
|
752
|
+
if (isStart) band = "left-1/2 right-0";
|
|
753
|
+
else if (end && isEnd) band = "left-0 right-1/2";
|
|
754
|
+
else band = "left-0 right-0";
|
|
755
|
+
}
|
|
756
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("button", { type: "button", onClick: () => pickDay(d), className: "relative flex h-9 items-center justify-center", children: [
|
|
757
|
+
band && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: `absolute inset-y-[3px] bg-ai-tint ${band}`, "aria-hidden": "true" }),
|
|
758
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
759
|
+
"span",
|
|
760
|
+
{
|
|
761
|
+
className: `relative z-10 flex h-8 w-8 items-center justify-center rounded-full text-[13px] tabular-nums ${endpoint ? "bg-brand-primaryAction font-semibold text-white" : inMonth ? "text-text-primary hover:bg-subtle" : "text-text-tertiary"}`,
|
|
762
|
+
children: d.getDate()
|
|
763
|
+
}
|
|
764
|
+
)
|
|
765
|
+
] }, i);
|
|
766
|
+
})
|
|
767
|
+
] })
|
|
768
|
+
] }, offset);
|
|
769
|
+
}) }),
|
|
770
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mt-4 flex justify-end gap-2", children: [
|
|
771
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", "data-testid": testIdPrefix && `${testIdPrefix}-cancel`, onClick: () => setOpen(false), className: "rounded-control border border-border-default bg-surface px-5 py-2 text-[13px] font-semibold text-text-primary transition-colors hover:bg-subtle", children: "Cancel" }),
|
|
772
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", "data-testid": testIdPrefix && `${testIdPrefix}-apply`, onClick: apply, className: "rounded-control bg-brand-primaryAction px-5 py-2 text-[13px] font-semibold text-white transition-[filter] hover:brightness-110", children: "Apply" })
|
|
773
|
+
] })
|
|
774
|
+
] })
|
|
775
|
+
]
|
|
776
|
+
}
|
|
777
|
+
)
|
|
778
|
+
] });
|
|
779
|
+
}
|
|
780
|
+
function Field({ label, value }) {
|
|
781
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
|
|
782
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[12px] font-medium text-text-secondary", children: label }),
|
|
783
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex h-10 min-w-[140px] items-center rounded-control border border-border-default bg-input px-3 text-[14px] font-medium text-text-primary", children: value })
|
|
784
|
+
] });
|
|
785
|
+
}
|
|
786
|
+
function NavBtn({ children, onClick, ...rest }) {
|
|
787
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick, className: "flex h-7 w-7 items-center justify-center rounded-full text-text-secondary transition-colors hover:bg-subtle hover:text-text-primary", ...rest, children });
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// src/SearchBar.tsx
|
|
791
|
+
var import_react4 = require("react");
|
|
792
|
+
var import_lucide_react6 = require("lucide-react");
|
|
793
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
794
|
+
var DEFAULT_SEARCH_MATCH = { mode: "anyWord", wordJoin: "Or" };
|
|
795
|
+
var MODE_LABEL = {
|
|
796
|
+
anyWord: "Any Word",
|
|
797
|
+
exactMatch: "Exact Match",
|
|
798
|
+
contextual: "Contextual",
|
|
799
|
+
adCode: "Ad Code"
|
|
800
|
+
};
|
|
801
|
+
function cx2(...classes) {
|
|
802
|
+
return classes.filter(Boolean).join(" ");
|
|
803
|
+
}
|
|
804
|
+
function SearchBar({
|
|
805
|
+
placeholder = "Search\u2026",
|
|
806
|
+
value,
|
|
807
|
+
onChange,
|
|
808
|
+
variant = "default",
|
|
809
|
+
matchValue,
|
|
810
|
+
onMatchChange,
|
|
811
|
+
className,
|
|
812
|
+
testIdPrefix
|
|
813
|
+
}) {
|
|
814
|
+
const [open, setOpen] = (0, import_react4.useState)(false);
|
|
815
|
+
const ref = (0, import_react4.useRef)(null);
|
|
816
|
+
(0, import_react4.useEffect)(() => {
|
|
817
|
+
if (!open) return;
|
|
818
|
+
const onKey = (e) => e.key === "Escape" && setOpen(false);
|
|
819
|
+
const onClick = (e) => {
|
|
820
|
+
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
821
|
+
};
|
|
822
|
+
window.addEventListener("keydown", onKey);
|
|
823
|
+
window.addEventListener("mousedown", onClick);
|
|
824
|
+
return () => {
|
|
825
|
+
window.removeEventListener("keydown", onKey);
|
|
826
|
+
window.removeEventListener("mousedown", onClick);
|
|
827
|
+
};
|
|
828
|
+
}, [open]);
|
|
829
|
+
const showMatch = variant === "filterable" && matchValue;
|
|
830
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
831
|
+
"div",
|
|
832
|
+
{
|
|
833
|
+
className: cx2(
|
|
834
|
+
"flex h-11 min-w-[200px] flex-1 items-stretch rounded-control border border-border-strong bg-input",
|
|
835
|
+
className
|
|
836
|
+
),
|
|
837
|
+
children: [
|
|
838
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex min-w-0 flex-1 items-center gap-2 px-2.5", children: [
|
|
839
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react6.Search, { size: 15, "aria-hidden": "true", className: "shrink-0 text-text-tertiary" }),
|
|
840
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
841
|
+
"input",
|
|
842
|
+
{
|
|
843
|
+
type: "text",
|
|
844
|
+
value,
|
|
845
|
+
onChange: (e) => onChange?.(e.target.value),
|
|
846
|
+
placeholder,
|
|
847
|
+
"aria-label": placeholder,
|
|
848
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-search`,
|
|
849
|
+
className: "min-w-0 flex-1 bg-transparent text-[13px] text-text-primary outline-hidden placeholder:text-text-tertiary"
|
|
850
|
+
}
|
|
851
|
+
)
|
|
852
|
+
] }),
|
|
853
|
+
showMatch && // border-border-default, not the bare `border-border` both source
|
|
854
|
+
// implementations used -- confirmed by actually compiling against
|
|
855
|
+
// @mediaradar/design-tokens' real published theme.css (not assumed):
|
|
856
|
+
// there is no bare `--color-border` in that file, only the
|
|
857
|
+
// -default/-interactive/-focus/-focusSoft/-strong suffixed variants,
|
|
858
|
+
// so `border-border` silently generates no utility at all there. It
|
|
859
|
+
// only "worked" in each source app because each one separately
|
|
860
|
+
// defines its own local `border-border` alias outside the shared
|
|
861
|
+
// token package (insights-studio-prototype's own src/index.css;
|
|
862
|
+
// mr-clientapps' own tailwind.config.ts `colors.border.DEFAULT`) --
|
|
863
|
+
// an alias this shared component can't rely on existing.
|
|
864
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "relative shrink-0 border-l border-border-default", ref, children: [
|
|
865
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
866
|
+
"button",
|
|
867
|
+
{
|
|
868
|
+
type: "button",
|
|
869
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-mode-trigger`,
|
|
870
|
+
onClick: () => setOpen((o) => !o),
|
|
871
|
+
"aria-expanded": open,
|
|
872
|
+
"aria-haspopup": "dialog",
|
|
873
|
+
className: "flex h-full items-center gap-1 px-2.5 text-[13px] font-medium text-text-primary transition-colors hover:bg-subtle",
|
|
874
|
+
children: [
|
|
875
|
+
matchValue.mode === "anyWord" ? `${MODE_LABEL.anyWord} (${matchValue.wordJoin})` : MODE_LABEL[matchValue.mode],
|
|
876
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
877
|
+
import_lucide_react6.ChevronDown,
|
|
878
|
+
{
|
|
879
|
+
size: 14,
|
|
880
|
+
"aria-hidden": "true",
|
|
881
|
+
className: cx2("shrink-0 text-text-secondary transition-transform", open && "rotate-180")
|
|
882
|
+
}
|
|
883
|
+
)
|
|
884
|
+
]
|
|
885
|
+
}
|
|
886
|
+
),
|
|
887
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
888
|
+
"div",
|
|
889
|
+
{
|
|
890
|
+
role: "dialog",
|
|
891
|
+
"aria-label": "Search match type",
|
|
892
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-mode-popover`,
|
|
893
|
+
className: "absolute right-0 top-[calc(100%+8px)] z-50 w-[400px] rounded-card border border-border-default bg-surface p-3 shadow-overlay",
|
|
894
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { role: "radiogroup", "aria-label": "Search match type", className: "flex flex-col", children: [
|
|
895
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
896
|
+
ModeRow,
|
|
897
|
+
{
|
|
898
|
+
title: "Any Word Text Search",
|
|
899
|
+
selected: matchValue.mode === "anyWord",
|
|
900
|
+
onSelect: () => onMatchChange?.({ mode: "anyWord", wordJoin: matchValue.wordJoin }),
|
|
901
|
+
testId: testIdPrefix && `${testIdPrefix}-mode-anyWord`,
|
|
902
|
+
description: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: "Find creatives that contain your keyword(s) using an \u201COr\u201D (match any) or \u201CAnd\u201D (match all) conditional in any order." }),
|
|
903
|
+
infoText: "The Or/And choice applies only when Any Word is the active search mode.",
|
|
904
|
+
extra: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "flex items-center gap-3", children: [
|
|
905
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
906
|
+
RadioOption,
|
|
907
|
+
{
|
|
908
|
+
label: "Or",
|
|
909
|
+
checked: matchValue.wordJoin === "Or",
|
|
910
|
+
onSelect: () => onMatchChange?.({ mode: "anyWord", wordJoin: "Or" }),
|
|
911
|
+
testId: testIdPrefix && `${testIdPrefix}-word-join-or`
|
|
912
|
+
}
|
|
913
|
+
),
|
|
914
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
915
|
+
RadioOption,
|
|
916
|
+
{
|
|
917
|
+
label: "And",
|
|
918
|
+
checked: matchValue.wordJoin === "And",
|
|
919
|
+
onSelect: () => onMatchChange?.({ mode: "anyWord", wordJoin: "And" }),
|
|
920
|
+
testId: testIdPrefix && `${testIdPrefix}-word-join-and`
|
|
921
|
+
}
|
|
922
|
+
)
|
|
923
|
+
] })
|
|
924
|
+
}
|
|
925
|
+
),
|
|
926
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Divider, {}),
|
|
927
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
928
|
+
ModeRow,
|
|
929
|
+
{
|
|
930
|
+
title: "Exact Match Text Search",
|
|
931
|
+
selected: matchValue.mode === "exactMatch",
|
|
932
|
+
onSelect: () => onMatchChange?.({ mode: "exactMatch", wordJoin: matchValue.wordJoin }),
|
|
933
|
+
testId: testIdPrefix && `${testIdPrefix}-mode-exactMatch`,
|
|
934
|
+
description: "Find creatives that mention your exact keyword phrase.",
|
|
935
|
+
infoText: "Keywords are matched as a single contiguous phrase, in the order you typed them."
|
|
936
|
+
}
|
|
937
|
+
),
|
|
938
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Divider, {}),
|
|
939
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
940
|
+
ModeRow,
|
|
941
|
+
{
|
|
942
|
+
title: "Contextual Search",
|
|
943
|
+
badge: "Beta",
|
|
944
|
+
selected: matchValue.mode === "contextual",
|
|
945
|
+
onSelect: () => onMatchChange?.({ mode: "contextual", wordJoin: matchValue.wordJoin }),
|
|
946
|
+
testId: testIdPrefix && `${testIdPrefix}-mode-contextual`,
|
|
947
|
+
description: "Find creatives that may contain OR have similar meaning as your keyword(s).",
|
|
948
|
+
infoText: "Uses semantic matching, so results can include related terms even when the exact keyword isn't present."
|
|
949
|
+
}
|
|
950
|
+
),
|
|
951
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Divider, {}),
|
|
952
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
953
|
+
ModeRow,
|
|
954
|
+
{
|
|
955
|
+
title: "Ad Code Search",
|
|
956
|
+
selected: matchValue.mode === "adCode",
|
|
957
|
+
onSelect: () => onMatchChange?.({ mode: "adCode", wordJoin: matchValue.wordJoin }),
|
|
958
|
+
testId: testIdPrefix && `${testIdPrefix}-mode-adCode`,
|
|
959
|
+
description: "Find creatives based on a list of one or more ad codes that you provide.",
|
|
960
|
+
infoText: "Separate multiple ad codes with a comma or a new line."
|
|
961
|
+
}
|
|
962
|
+
)
|
|
963
|
+
] })
|
|
964
|
+
}
|
|
965
|
+
)
|
|
966
|
+
] })
|
|
967
|
+
]
|
|
968
|
+
}
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
function Divider() {
|
|
972
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "my-2.5 border-t border-border-default", "aria-hidden": "true" });
|
|
973
|
+
}
|
|
974
|
+
function ModeRow({
|
|
975
|
+
title,
|
|
976
|
+
badge,
|
|
977
|
+
extra,
|
|
978
|
+
description,
|
|
979
|
+
infoText,
|
|
980
|
+
selected,
|
|
981
|
+
onSelect,
|
|
982
|
+
testId
|
|
983
|
+
}) {
|
|
984
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
985
|
+
"div",
|
|
986
|
+
{
|
|
987
|
+
role: "radio",
|
|
988
|
+
tabIndex: 0,
|
|
989
|
+
"aria-checked": selected,
|
|
990
|
+
"data-testid": testId,
|
|
991
|
+
onClick: onSelect,
|
|
992
|
+
onKeyDown: (e) => {
|
|
993
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
994
|
+
e.preventDefault();
|
|
995
|
+
onSelect();
|
|
996
|
+
}
|
|
997
|
+
},
|
|
998
|
+
className: "flex cursor-pointer flex-col items-stretch gap-1 rounded-control p-1.5 text-left transition-colors hover:bg-subtle focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-border-focus",
|
|
999
|
+
children: [
|
|
1000
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
|
|
1001
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-wrap items-center gap-3", children: [
|
|
1002
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "flex items-center gap-1", children: [
|
|
1003
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-[14px] font-semibold text-text-primary", children: title }),
|
|
1004
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RowInfoTip, { text: infoText })
|
|
1005
|
+
] }),
|
|
1006
|
+
badge && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "rounded-full bg-subtle px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-text-secondary", children: badge }),
|
|
1007
|
+
extra
|
|
1008
|
+
] }),
|
|
1009
|
+
selected && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react6.Check, { size: 16, strokeWidth: 2.5, "aria-hidden": "true", className: "shrink-0 text-brand-primaryAction" })
|
|
1010
|
+
] }),
|
|
1011
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "block text-[12.5px] leading-snug text-text-secondary", children: description })
|
|
1012
|
+
]
|
|
1013
|
+
}
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
function RadioOption({
|
|
1017
|
+
label,
|
|
1018
|
+
checked,
|
|
1019
|
+
onSelect,
|
|
1020
|
+
testId
|
|
1021
|
+
}) {
|
|
1022
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1023
|
+
"button",
|
|
1024
|
+
{
|
|
1025
|
+
type: "button",
|
|
1026
|
+
"data-testid": testId,
|
|
1027
|
+
onClick: (e) => {
|
|
1028
|
+
e.stopPropagation();
|
|
1029
|
+
onSelect();
|
|
1030
|
+
},
|
|
1031
|
+
role: "radio",
|
|
1032
|
+
"aria-checked": checked,
|
|
1033
|
+
className: "flex items-center gap-1.5 text-[13px] font-medium text-text-primary",
|
|
1034
|
+
children: [
|
|
1035
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1036
|
+
"span",
|
|
1037
|
+
{
|
|
1038
|
+
className: cx2(
|
|
1039
|
+
"flex h-4 w-4 shrink-0 items-center justify-center rounded-full border-2 transition-colors",
|
|
1040
|
+
checked ? "border-brand-primaryAction" : "border-border-interactive"
|
|
1041
|
+
),
|
|
1042
|
+
children: checked && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "h-2 w-2 rounded-full bg-brand-primaryAction" })
|
|
1043
|
+
}
|
|
1044
|
+
),
|
|
1045
|
+
label
|
|
1046
|
+
]
|
|
1047
|
+
}
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
function RowInfoTip({ text }) {
|
|
1051
|
+
const [open, setOpen] = (0, import_react4.useState)(false);
|
|
1052
|
+
const ref = (0, import_react4.useRef)(null);
|
|
1053
|
+
(0, import_react4.useEffect)(() => {
|
|
1054
|
+
if (!open) return;
|
|
1055
|
+
const onDown = (e) => {
|
|
1056
|
+
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
1057
|
+
};
|
|
1058
|
+
window.addEventListener("mousedown", onDown);
|
|
1059
|
+
return () => window.removeEventListener("mousedown", onDown);
|
|
1060
|
+
}, [open]);
|
|
1061
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "relative inline-flex shrink-0 items-center", ref, children: [
|
|
1062
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1063
|
+
"button",
|
|
1064
|
+
{
|
|
1065
|
+
type: "button",
|
|
1066
|
+
onClick: (e) => {
|
|
1067
|
+
e.stopPropagation();
|
|
1068
|
+
setOpen((o) => !o);
|
|
1069
|
+
},
|
|
1070
|
+
"aria-label": "More information",
|
|
1071
|
+
"aria-expanded": open,
|
|
1072
|
+
className: "inline-flex text-text-tertiary transition-colors hover:text-text-primary",
|
|
1073
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react6.AlertCircle, { size: 13, "aria-hidden": "true" })
|
|
1074
|
+
}
|
|
1075
|
+
),
|
|
1076
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1077
|
+
"span",
|
|
1078
|
+
{
|
|
1079
|
+
role: "tooltip",
|
|
1080
|
+
className: "absolute left-0 top-[calc(100%+6px)] z-40 w-[220px] rounded-control border border-border-default bg-surface px-2.5 py-1.5 text-[11.5px] font-normal leading-snug text-text-secondary shadow-overlay",
|
|
1081
|
+
children: text
|
|
1082
|
+
}
|
|
1083
|
+
)
|
|
1084
|
+
] });
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// src/SectionRail.tsx
|
|
1088
|
+
var import_react5 = require("react");
|
|
1089
|
+
var import_lucide_react7 = require("lucide-react");
|
|
1090
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1091
|
+
function SectionRail({
|
|
1092
|
+
title,
|
|
1093
|
+
titleSerif,
|
|
1094
|
+
subtitle,
|
|
1095
|
+
viewAllLabel,
|
|
1096
|
+
viewAllHref,
|
|
1097
|
+
children
|
|
1098
|
+
}) {
|
|
1099
|
+
const scrollRef = (0, import_react5.useRef)(null);
|
|
1100
|
+
const scroll = (direction) => {
|
|
1101
|
+
scrollRef.current?.scrollBy({ left: direction * 320, behavior: "smooth" });
|
|
1102
|
+
};
|
|
1103
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("section", { className: "flex flex-col gap-4", children: [
|
|
1104
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("header", { className: "flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between", children: [
|
|
1105
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
|
|
1106
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1107
|
+
"h2",
|
|
1108
|
+
{
|
|
1109
|
+
className: titleSerif ? "font-serif text-2xl font-normal tracking-tight text-text-primary sm:text-[28px]" : "text-xl font-medium tracking-tight text-text-primary sm:text-2xl",
|
|
1110
|
+
children: title
|
|
1111
|
+
}
|
|
1112
|
+
),
|
|
1113
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "mt-1 text-sm text-text-secondary", children: subtitle })
|
|
1114
|
+
] }),
|
|
1115
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1116
|
+
viewAllLabel && viewAllHref && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1117
|
+
"a",
|
|
1118
|
+
{
|
|
1119
|
+
href: viewAllHref,
|
|
1120
|
+
className: "text-sm font-medium text-text-link transition-colors hover:underline focus-visible:rounded-sm focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-border-focus",
|
|
1121
|
+
children: [
|
|
1122
|
+
viewAllLabel,
|
|
1123
|
+
" \u2192"
|
|
1124
|
+
]
|
|
1125
|
+
}
|
|
1126
|
+
),
|
|
1127
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1128
|
+
"button",
|
|
1129
|
+
{
|
|
1130
|
+
type: "button",
|
|
1131
|
+
onClick: () => scroll(-1),
|
|
1132
|
+
"aria-label": "Previous",
|
|
1133
|
+
className: "hidden h-10 w-10 items-center justify-center rounded-control border border-border text-text-secondary transition-colors hover:bg-subtle hover:text-text-primary sm:flex lg:hidden",
|
|
1134
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react7.ChevronLeft, { size: 16 })
|
|
1135
|
+
}
|
|
1136
|
+
),
|
|
1137
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1138
|
+
"button",
|
|
1139
|
+
{
|
|
1140
|
+
type: "button",
|
|
1141
|
+
onClick: () => scroll(1),
|
|
1142
|
+
"aria-label": "Next",
|
|
1143
|
+
className: "hidden h-10 w-10 items-center justify-center rounded-control border border-border text-text-secondary transition-colors hover:bg-subtle hover:text-text-primary sm:flex lg:hidden",
|
|
1144
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react7.ChevronRight, { size: 16 })
|
|
1145
|
+
}
|
|
1146
|
+
)
|
|
1147
|
+
] })
|
|
1148
|
+
] }),
|
|
1149
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1150
|
+
"div",
|
|
1151
|
+
{
|
|
1152
|
+
ref: scrollRef,
|
|
1153
|
+
className: "flex gap-4 overflow-x-auto scroll-smooth pb-2 snap-x snap-mandatory scrollbar-none [&::-webkit-scrollbar]:hidden -mx-4 px-4 sm:mx-0 sm:px-0 *:w-[268px] *:shrink-0 *:snap-start lg:grid lg:grid-cols-[repeat(4,minmax(0,320px))] lg:gap-5 lg:overflow-visible lg:justify-start lg:*:w-auto lg:*:shrink lg:*:snap-none",
|
|
1154
|
+
children
|
|
1155
|
+
}
|
|
1156
|
+
)
|
|
1157
|
+
] });
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// src/DataScopeFilter.tsx
|
|
1161
|
+
var import_react6 = require("react");
|
|
1162
|
+
var import_lucide_react8 = require("lucide-react");
|
|
1163
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1164
|
+
var FINAL_ONLY_TEXT = "Trims time periods to data that has finalized across the selected media formats. Some formats (e.g. Network TV and Cinema) finalize later, so the most recent periods may still change. Turn on for stable, report-ready numbers.";
|
|
1165
|
+
function cx3(...classes) {
|
|
1166
|
+
return classes.filter(Boolean).join(" ");
|
|
1167
|
+
}
|
|
1168
|
+
function DataScopeFilter({ value, onChange, align = "right" }) {
|
|
1169
|
+
const [open, setOpen] = (0, import_react6.useState)(false);
|
|
1170
|
+
const ref = (0, import_react6.useRef)(null);
|
|
1171
|
+
const display = value ? "Final only" : "All";
|
|
1172
|
+
(0, import_react6.useEffect)(() => {
|
|
1173
|
+
if (!open) return;
|
|
1174
|
+
const onDown = (e) => {
|
|
1175
|
+
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
1176
|
+
};
|
|
1177
|
+
const onKey = (e) => e.key === "Escape" && setOpen(false);
|
|
1178
|
+
window.addEventListener("mousedown", onDown);
|
|
1179
|
+
window.addEventListener("keydown", onKey);
|
|
1180
|
+
return () => {
|
|
1181
|
+
window.removeEventListener("mousedown", onDown);
|
|
1182
|
+
window.removeEventListener("keydown", onKey);
|
|
1183
|
+
};
|
|
1184
|
+
}, [open]);
|
|
1185
|
+
const choose = (v) => {
|
|
1186
|
+
onChange(v);
|
|
1187
|
+
setOpen(false);
|
|
1188
|
+
};
|
|
1189
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "relative", ref, children: [
|
|
1190
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1191
|
+
"button",
|
|
1192
|
+
{
|
|
1193
|
+
type: "button",
|
|
1194
|
+
onClick: () => setOpen((o) => !o),
|
|
1195
|
+
"aria-expanded": open,
|
|
1196
|
+
"aria-haspopup": "listbox",
|
|
1197
|
+
className: "flex h-11 items-center gap-2 rounded-control border border-border-strong bg-input px-2.5 text-text-secondary transition-colors hover:bg-subtle hover:text-text-primary focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-border-focus",
|
|
1198
|
+
children: [
|
|
1199
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "flex flex-col gap-1 text-left leading-none", children: [
|
|
1200
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-text-tertiary", children: "Data" }),
|
|
1201
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-[12px] font-medium text-text-primary", children: display })
|
|
1202
|
+
] }),
|
|
1203
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1204
|
+
import_lucide_react8.ChevronDown,
|
|
1205
|
+
{
|
|
1206
|
+
size: 14,
|
|
1207
|
+
"aria-hidden": "true",
|
|
1208
|
+
className: cx3("shrink-0 text-text-secondary transition-transform", open && "rotate-180")
|
|
1209
|
+
}
|
|
1210
|
+
)
|
|
1211
|
+
]
|
|
1212
|
+
}
|
|
1213
|
+
),
|
|
1214
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1215
|
+
"div",
|
|
1216
|
+
{
|
|
1217
|
+
role: "listbox",
|
|
1218
|
+
"aria-label": "Data",
|
|
1219
|
+
className: cx3(
|
|
1220
|
+
"absolute top-[calc(100%+8px)] z-30 min-w-[220px] overflow-hidden rounded-card border border-border-default bg-surface p-1 shadow-overlay",
|
|
1221
|
+
align === "right" ? "right-0" : "left-0"
|
|
1222
|
+
),
|
|
1223
|
+
children: [
|
|
1224
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Option, { label: "All", selected: !value, onClick: () => choose(false) }),
|
|
1225
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Option, { label: "Final only", selected: value, onClick: () => choose(true), hint: FINAL_ONLY_TEXT })
|
|
1226
|
+
]
|
|
1227
|
+
}
|
|
1228
|
+
)
|
|
1229
|
+
] });
|
|
1230
|
+
}
|
|
1231
|
+
function Option({
|
|
1232
|
+
label,
|
|
1233
|
+
selected,
|
|
1234
|
+
onClick,
|
|
1235
|
+
hint
|
|
1236
|
+
}) {
|
|
1237
|
+
const option = /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1238
|
+
"button",
|
|
1239
|
+
{
|
|
1240
|
+
type: "button",
|
|
1241
|
+
role: "option",
|
|
1242
|
+
"aria-selected": selected,
|
|
1243
|
+
onClick,
|
|
1244
|
+
className: cx3(
|
|
1245
|
+
"flex items-center gap-2 rounded-control px-2.5 py-2 text-left text-[13px] transition-colors hover:bg-subtle",
|
|
1246
|
+
hint ? "flex-1" : "w-full",
|
|
1247
|
+
selected ? "font-semibold text-text-primary" : "text-text-secondary"
|
|
1248
|
+
),
|
|
1249
|
+
children: [
|
|
1250
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react8.Check, { size: 14, "aria-hidden": "true", className: cx3("shrink-0 text-brand-primaryAction", !selected && "invisible") }),
|
|
1251
|
+
label
|
|
1252
|
+
]
|
|
1253
|
+
}
|
|
1254
|
+
);
|
|
1255
|
+
if (!hint) return option;
|
|
1256
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center pr-2.5", children: [
|
|
1257
|
+
option,
|
|
1258
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(InfoTip, { text: hint, trigger: "hover", label: `About ${label}` })
|
|
1259
|
+
] });
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
// src/SelectFilter.tsx
|
|
1263
|
+
var import_react7 = require("react");
|
|
1264
|
+
var import_react_dom2 = require("react-dom");
|
|
1265
|
+
var import_lucide_react9 = require("lucide-react");
|
|
1266
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1267
|
+
function cx4(...classes) {
|
|
1268
|
+
return classes.filter(Boolean).join(" ");
|
|
1269
|
+
}
|
|
1270
|
+
function slug3(value) {
|
|
1271
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
1272
|
+
}
|
|
1273
|
+
function SelectFilter({
|
|
1274
|
+
caption,
|
|
1275
|
+
value,
|
|
1276
|
+
options,
|
|
1277
|
+
onChange,
|
|
1278
|
+
align = "left",
|
|
1279
|
+
fullWidth = false,
|
|
1280
|
+
className,
|
|
1281
|
+
testIdPrefix
|
|
1282
|
+
}) {
|
|
1283
|
+
const [open, setOpen] = (0, import_react7.useState)(false);
|
|
1284
|
+
const [pos, setPos] = (0, import_react7.useState)(null);
|
|
1285
|
+
const ref = (0, import_react7.useRef)(null);
|
|
1286
|
+
const popoverRef = (0, import_react7.useRef)(null);
|
|
1287
|
+
const close = (0, import_react7.useCallback)(() => setOpen(false), []);
|
|
1288
|
+
const reposition = (0, import_react7.useCallback)(() => {
|
|
1289
|
+
if (!ref.current) return;
|
|
1290
|
+
const r = ref.current.getBoundingClientRect();
|
|
1291
|
+
setPos({ top: r.bottom + 8, left: align === "right" ? r.right - r.width : r.left, width: r.width });
|
|
1292
|
+
}, [align]);
|
|
1293
|
+
(0, import_react7.useLayoutEffect)(() => {
|
|
1294
|
+
if (!open) return;
|
|
1295
|
+
reposition();
|
|
1296
|
+
}, [open, reposition]);
|
|
1297
|
+
(0, import_react7.useEffect)(() => {
|
|
1298
|
+
if (!open) return;
|
|
1299
|
+
const onKey = (e) => e.key === "Escape" && close();
|
|
1300
|
+
const onClick = (e) => {
|
|
1301
|
+
const t = e.target;
|
|
1302
|
+
if (ref.current?.contains(t) || popoverRef.current?.contains(t)) return;
|
|
1303
|
+
close();
|
|
1304
|
+
};
|
|
1305
|
+
window.addEventListener("keydown", onKey);
|
|
1306
|
+
window.addEventListener("mousedown", onClick);
|
|
1307
|
+
window.addEventListener("resize", reposition);
|
|
1308
|
+
window.addEventListener("scroll", reposition, true);
|
|
1309
|
+
return () => {
|
|
1310
|
+
window.removeEventListener("keydown", onKey);
|
|
1311
|
+
window.removeEventListener("mousedown", onClick);
|
|
1312
|
+
window.removeEventListener("resize", reposition);
|
|
1313
|
+
window.removeEventListener("scroll", reposition, true);
|
|
1314
|
+
};
|
|
1315
|
+
}, [open, close, reposition]);
|
|
1316
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: cx4("relative", fullWidth && "w-full"), ref, children: [
|
|
1317
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1318
|
+
"button",
|
|
1319
|
+
{
|
|
1320
|
+
type: "button",
|
|
1321
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-trigger`,
|
|
1322
|
+
onClick: () => setOpen((o) => !o),
|
|
1323
|
+
"aria-expanded": open,
|
|
1324
|
+
"aria-haspopup": "listbox",
|
|
1325
|
+
className: cx4(
|
|
1326
|
+
"flex h-11 items-center gap-2 rounded-control border border-border-strong bg-input px-2.5 text-text-secondary transition-colors hover:bg-subtle hover:text-text-primary focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-border-focus",
|
|
1327
|
+
fullWidth && "w-full justify-between",
|
|
1328
|
+
className
|
|
1329
|
+
),
|
|
1330
|
+
children: [
|
|
1331
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "flex flex-col gap-1 text-left leading-none", children: [
|
|
1332
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-text-tertiary", children: caption }),
|
|
1333
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-[12px] font-medium text-text-primary", children: value })
|
|
1334
|
+
] }),
|
|
1335
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1336
|
+
import_lucide_react9.ChevronDown,
|
|
1337
|
+
{
|
|
1338
|
+
size: 14,
|
|
1339
|
+
"aria-hidden": "true",
|
|
1340
|
+
className: cx4("shrink-0 text-text-secondary transition-transform", open && "rotate-180")
|
|
1341
|
+
}
|
|
1342
|
+
)
|
|
1343
|
+
]
|
|
1344
|
+
}
|
|
1345
|
+
),
|
|
1346
|
+
open && pos && (0, import_react_dom2.createPortal)(
|
|
1347
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1348
|
+
"div",
|
|
1349
|
+
{
|
|
1350
|
+
ref: popoverRef,
|
|
1351
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-popover`,
|
|
1352
|
+
style: { position: "fixed", top: pos.top, left: pos.left, width: fullWidth ? pos.width : void 0 },
|
|
1353
|
+
className: cx4(
|
|
1354
|
+
"z-[65] flex flex-col overflow-hidden rounded-card border border-border-default bg-surface shadow-overlay",
|
|
1355
|
+
!fullWidth && "w-56"
|
|
1356
|
+
),
|
|
1357
|
+
role: "listbox",
|
|
1358
|
+
"aria-label": caption,
|
|
1359
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "max-h-[280px] overflow-y-auto p-1", children: options.map((o) => {
|
|
1360
|
+
const selected = o === value;
|
|
1361
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1362
|
+
"button",
|
|
1363
|
+
{
|
|
1364
|
+
type: "button",
|
|
1365
|
+
role: "option",
|
|
1366
|
+
"aria-selected": selected,
|
|
1367
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-option-${slug3(o)}`,
|
|
1368
|
+
onClick: () => {
|
|
1369
|
+
onChange(o);
|
|
1370
|
+
close();
|
|
1371
|
+
},
|
|
1372
|
+
className: "flex w-full items-center gap-2.5 rounded-control px-2 py-2 text-left transition-colors hover:bg-subtle",
|
|
1373
|
+
children: [
|
|
1374
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "flex h-4 w-4 shrink-0 items-center justify-center text-brand-primaryAction", children: selected && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react9.Check, { size: 15, strokeWidth: 3 }) }),
|
|
1375
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1376
|
+
"span",
|
|
1377
|
+
{
|
|
1378
|
+
className: cx4("flex-1 text-[14px]", selected ? "font-semibold text-text-primary" : "text-text-primary"),
|
|
1379
|
+
children: o
|
|
1380
|
+
}
|
|
1381
|
+
)
|
|
1382
|
+
]
|
|
1383
|
+
},
|
|
1384
|
+
o
|
|
1385
|
+
);
|
|
1386
|
+
}) })
|
|
1387
|
+
}
|
|
1388
|
+
),
|
|
1389
|
+
document.body
|
|
1390
|
+
)
|
|
1391
|
+
] });
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// src/Card.tsx
|
|
1395
|
+
var import_react8 = require("react");
|
|
1396
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1397
|
+
var Card = (0, import_react8.forwardRef)(function Card2({ staticCard, title, subtitle, action, className, children, ...rest }, ref) {
|
|
1398
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1399
|
+
"article",
|
|
1400
|
+
{
|
|
1401
|
+
ref,
|
|
1402
|
+
className: [
|
|
1403
|
+
"flex h-full flex-col overflow-hidden rounded-card border border-border-default bg-surface shadow-raised",
|
|
1404
|
+
// Interactive hover: deeper drop shadow only -- no elevation/transform.
|
|
1405
|
+
!staticCard && "group transition-shadow hover:shadow-overlay",
|
|
1406
|
+
className
|
|
1407
|
+
].filter(Boolean).join(" "),
|
|
1408
|
+
...rest,
|
|
1409
|
+
children: title ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-1 flex-col p-card-padding", children: [
|
|
1410
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [
|
|
1411
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
|
|
1412
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { className: "text-[15px] font-semibold leading-snug text-text-primary", children: title }),
|
|
1413
|
+
subtitle && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "mt-0.5 text-[12px] leading-snug text-text-tertiary", children: subtitle })
|
|
1414
|
+
] }),
|
|
1415
|
+
action && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "shrink-0 text-text-tertiary", children: action })
|
|
1416
|
+
] }),
|
|
1417
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-4 flex flex-1 flex-col", children })
|
|
1418
|
+
] }) : children
|
|
1419
|
+
}
|
|
1420
|
+
);
|
|
1421
|
+
});
|
|
1422
|
+
|
|
1423
|
+
// src/NoResultsEmptyState.tsx
|
|
1424
|
+
var import_lucide_react10 = require("lucide-react");
|
|
1425
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1426
|
+
var NO_RESULTS_TITLE = "No results match your filters";
|
|
1427
|
+
var NO_RESULTS_BODY = "Your filters may be too narrow, conflict with each other, or have no activity in this period.";
|
|
1428
|
+
function noResultsBody(workspaceName) {
|
|
1429
|
+
return workspaceName ? `Nothing in the \u201C${workspaceName}\u201D workspace matches. ${NO_RESULTS_BODY}` : NO_RESULTS_BODY;
|
|
1430
|
+
}
|
|
1431
|
+
var PRIMARY_ACTION_CLASS = "rounded-control bg-brand-primaryAction px-3.5 py-2 text-[13px] font-semibold text-white transition-colors hover:opacity-90";
|
|
1432
|
+
var QUIET_ACTION_CLASS = "rounded-control text-[13px] font-medium text-text-link transition-colors hover:underline focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-border-focus";
|
|
1433
|
+
function NoResultsEmptyState({
|
|
1434
|
+
title = NO_RESULTS_TITLE,
|
|
1435
|
+
body,
|
|
1436
|
+
workspaceName,
|
|
1437
|
+
hasFilters,
|
|
1438
|
+
onClearFilters,
|
|
1439
|
+
onEditFilters,
|
|
1440
|
+
actions,
|
|
1441
|
+
testIdPrefix
|
|
1442
|
+
}) {
|
|
1443
|
+
const bodyText = body ?? noResultsBody(workspaceName);
|
|
1444
|
+
const showClear = hasFilters !== false && Boolean(onClearFilters);
|
|
1445
|
+
const showEdit = Boolean(onEditFilters);
|
|
1446
|
+
const hasActions = showClear || showEdit || Boolean(actions && actions.length > 0);
|
|
1447
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col items-center justify-center gap-3 text-center", "data-testid": testIdPrefix, children: [
|
|
1448
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "flex h-11 w-11 items-center justify-center rounded-full bg-subtle text-text-tertiary", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react10.FilterX, { size: 20, "aria-hidden": "true" }) }),
|
|
1449
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h2", { className: "text-base font-semibold text-text-primary", children: title }),
|
|
1450
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "max-w-[420px] text-[13px] leading-relaxed text-text-secondary", children: bodyText }),
|
|
1451
|
+
hasActions && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "mt-1 flex flex-col items-center gap-3", children: [
|
|
1452
|
+
showClear && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1453
|
+
"button",
|
|
1454
|
+
{
|
|
1455
|
+
type: "button",
|
|
1456
|
+
onClick: onClearFilters,
|
|
1457
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-clear`,
|
|
1458
|
+
className: PRIMARY_ACTION_CLASS,
|
|
1459
|
+
children: "Clear all filters"
|
|
1460
|
+
}
|
|
1461
|
+
),
|
|
1462
|
+
showEdit && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1463
|
+
"button",
|
|
1464
|
+
{
|
|
1465
|
+
type: "button",
|
|
1466
|
+
onClick: onEditFilters,
|
|
1467
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-edit`,
|
|
1468
|
+
className: QUIET_ACTION_CLASS,
|
|
1469
|
+
children: "Edit filters"
|
|
1470
|
+
}
|
|
1471
|
+
),
|
|
1472
|
+
actions?.map((action, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1473
|
+
"button",
|
|
1474
|
+
{
|
|
1475
|
+
type: "button",
|
|
1476
|
+
onClick: action.onClick,
|
|
1477
|
+
className: action.primary ? PRIMARY_ACTION_CLASS : QUIET_ACTION_CLASS,
|
|
1478
|
+
children: action.label
|
|
1479
|
+
},
|
|
1480
|
+
`${i}-${action.label}`
|
|
1481
|
+
))
|
|
1482
|
+
] })
|
|
1483
|
+
] });
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
// src/WorkspaceSelectedCard.tsx
|
|
1487
|
+
var import_react9 = require("react");
|
|
1488
|
+
var import_lucide_react11 = require("lucide-react");
|
|
1489
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1490
|
+
function cx5(...parts) {
|
|
1491
|
+
return parts.filter(Boolean).join(" ");
|
|
1492
|
+
}
|
|
1493
|
+
function WorkspaceSelectedCard({ groups, testIdPrefix }) {
|
|
1494
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("aside", { className: "sticky top-4 flex min-w-0 flex-col gap-4 self-start rounded-card border border-border bg-surface px-5 pb-5 pt-4 shadow-raised sm:px-6 sm:pb-6", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex flex-col divide-y divide-border", children: groups.map((g) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SummaryGroupItem, { group: g, testIdPrefix }, g.id)) }) });
|
|
1495
|
+
}
|
|
1496
|
+
function SummaryGroupItem({
|
|
1497
|
+
group,
|
|
1498
|
+
nested = false,
|
|
1499
|
+
testIdPrefix
|
|
1500
|
+
}) {
|
|
1501
|
+
const ownCount = group.items.length;
|
|
1502
|
+
const subCount = (group.subGroups ?? []).reduce((n, sg) => n + sg.items.length, 0);
|
|
1503
|
+
const expandCount = ownCount + subCount;
|
|
1504
|
+
const [open, setOpen] = (0, import_react9.useState)(expandCount > 0);
|
|
1505
|
+
const wasEmpty = (0, import_react9.useRef)(expandCount === 0);
|
|
1506
|
+
(0, import_react9.useEffect)(() => {
|
|
1507
|
+
if (wasEmpty.current && expandCount > 0) setOpen(true);
|
|
1508
|
+
else if (expandCount === 0) setOpen(false);
|
|
1509
|
+
wasEmpty.current = expandCount === 0;
|
|
1510
|
+
}, [expandCount]);
|
|
1511
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: nested ? "" : "py-3 first:pt-0 last:pb-0", children: [
|
|
1512
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1513
|
+
"button",
|
|
1514
|
+
{
|
|
1515
|
+
type: "button",
|
|
1516
|
+
onClick: () => setOpen((v) => !v),
|
|
1517
|
+
"aria-expanded": open,
|
|
1518
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-group-${group.id}`,
|
|
1519
|
+
className: "flex w-full min-w-0 items-center gap-2 text-left",
|
|
1520
|
+
children: [
|
|
1521
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1522
|
+
import_lucide_react11.ChevronDown,
|
|
1523
|
+
{
|
|
1524
|
+
size: 14,
|
|
1525
|
+
"aria-hidden": "true",
|
|
1526
|
+
className: cx5("shrink-0 text-text-secondary transition-transform", !open && "-rotate-90")
|
|
1527
|
+
}
|
|
1528
|
+
),
|
|
1529
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: cx5("truncate font-semibold text-text-primary", nested ? "text-[12px]" : "text-[13px]"), children: group.label }),
|
|
1530
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "shrink-0 rounded-full bg-subtle px-1.5 py-0.5 text-[11px] font-semibold text-text-secondary", children: ownCount })
|
|
1531
|
+
]
|
|
1532
|
+
}
|
|
1533
|
+
),
|
|
1534
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "pt-2.5", children: ownCount === 0 && subCount === 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-[12px] text-text-secondary", children: "Nothing selected." }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-2.5", children: [
|
|
1535
|
+
ownCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
|
|
1536
|
+
group.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1537
|
+
"div",
|
|
1538
|
+
{
|
|
1539
|
+
className: "flex items-center justify-between gap-2 rounded-control border border-border bg-canvas px-2 py-1",
|
|
1540
|
+
children: [
|
|
1541
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "min-w-0 flex-1", children: [
|
|
1542
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "block truncate text-[12px] text-text-primary", children: item.name }),
|
|
1543
|
+
item.meta && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "block truncate text-[11px] text-text-secondary", children: item.meta })
|
|
1544
|
+
] }),
|
|
1545
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1546
|
+
"button",
|
|
1547
|
+
{
|
|
1548
|
+
type: "button",
|
|
1549
|
+
"aria-label": `Remove ${item.name}`,
|
|
1550
|
+
onClick: () => group.onRemove(item.id),
|
|
1551
|
+
className: "flex h-5 w-5 shrink-0 items-center justify-center rounded-control text-text-secondary hover:bg-subtle hover:text-text-danger",
|
|
1552
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react11.X, { size: 12, "aria-hidden": "true" })
|
|
1553
|
+
}
|
|
1554
|
+
)
|
|
1555
|
+
]
|
|
1556
|
+
},
|
|
1557
|
+
item.id
|
|
1558
|
+
)),
|
|
1559
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1560
|
+
"button",
|
|
1561
|
+
{
|
|
1562
|
+
type: "button",
|
|
1563
|
+
onClick: group.onRemoveAll,
|
|
1564
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-remove-all-${group.id}`,
|
|
1565
|
+
className: "mt-0.5 self-start text-[11px] font-medium text-text-link hover:underline",
|
|
1566
|
+
children: "Remove all"
|
|
1567
|
+
}
|
|
1568
|
+
)
|
|
1569
|
+
] }),
|
|
1570
|
+
group.subGroups?.map((sg) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SummaryGroupItem, { group: sg, nested: true, testIdPrefix }, sg.id))
|
|
1571
|
+
] }) })
|
|
1572
|
+
] });
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
// src/chart-placeholder-shared.tsx
|
|
1576
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1577
|
+
var GRID_ROWS = [0, 25, 50, 75, 100];
|
|
1578
|
+
var Y_TICK_WIDTHS = [26, 30, 30, 26, 20];
|
|
1579
|
+
var Y_AXIS_W = 48;
|
|
1580
|
+
var X_AXIS_H = 22;
|
|
1581
|
+
var LEGEND_WIDTHS = [58, 44, 66, 50, 38, 62, 46, 54];
|
|
1582
|
+
function AxisFrame({
|
|
1583
|
+
height,
|
|
1584
|
+
xTicks,
|
|
1585
|
+
children
|
|
1586
|
+
}) {
|
|
1587
|
+
const gutter = xTicks > 0 ? X_AXIS_H : 0;
|
|
1588
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "w-full", style: { height }, role: "presentation", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "relative h-full w-full", children: [
|
|
1589
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "absolute inset-y-0 right-0", style: { left: Y_AXIS_W, bottom: gutter }, children: [
|
|
1590
|
+
GRID_ROWS.map((pct) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1591
|
+
"div",
|
|
1592
|
+
{
|
|
1593
|
+
className: "absolute inset-x-0 border-t border-dashed",
|
|
1594
|
+
style: { top: `${pct}%`, borderColor: "var(--color-border-default)" }
|
|
1595
|
+
},
|
|
1596
|
+
pct
|
|
1597
|
+
)),
|
|
1598
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "absolute inset-0", children })
|
|
1599
|
+
] }),
|
|
1600
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "absolute left-0 top-0", style: { width: Y_AXIS_W, bottom: gutter }, children: GRID_ROWS.map((pct, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1601
|
+
"div",
|
|
1602
|
+
{
|
|
1603
|
+
className: "absolute right-2 h-2 -translate-y-1/2 rounded-xs bg-subtle",
|
|
1604
|
+
style: { top: `${pct}%`, width: Y_TICK_WIDTHS[i] }
|
|
1605
|
+
},
|
|
1606
|
+
pct
|
|
1607
|
+
)) }),
|
|
1608
|
+
xTicks > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1609
|
+
"div",
|
|
1610
|
+
{
|
|
1611
|
+
className: "absolute bottom-0 right-0 flex items-center",
|
|
1612
|
+
style: { left: Y_AXIS_W, height: X_AXIS_H },
|
|
1613
|
+
children: Array.from({ length: xTicks }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex flex-1 justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "h-2 w-full max-w-[26px] rounded-xs bg-subtle" }) }, i))
|
|
1614
|
+
}
|
|
1615
|
+
)
|
|
1616
|
+
] }) });
|
|
1617
|
+
}
|
|
1618
|
+
function BlankLegend({ rows, reverse = false }) {
|
|
1619
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1620
|
+
"ul",
|
|
1621
|
+
{
|
|
1622
|
+
className: `flex flex-1 justify-center gap-1.5 ${reverse ? "flex-col-reverse" : "flex-col"}`,
|
|
1623
|
+
role: "presentation",
|
|
1624
|
+
"aria-hidden": "true",
|
|
1625
|
+
children: Array.from({ length: rows }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("li", { className: "flex items-center justify-between gap-2", children: [
|
|
1626
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "flex items-center gap-1.5", children: [
|
|
1627
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "h-2.5 w-2.5 rounded-xs bg-subtle" }),
|
|
1628
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1629
|
+
"span",
|
|
1630
|
+
{
|
|
1631
|
+
className: "h-2 rounded-xs bg-subtle",
|
|
1632
|
+
style: { width: LEGEND_WIDTHS[i % LEGEND_WIDTHS.length] }
|
|
1633
|
+
}
|
|
1634
|
+
)
|
|
1635
|
+
] }),
|
|
1636
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "h-2 w-6 rounded-xs bg-subtle" })
|
|
1637
|
+
] }, i))
|
|
1638
|
+
}
|
|
1639
|
+
);
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
// src/BarChartPlaceholder.tsx
|
|
1643
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1644
|
+
var BAR_PROFILE = [92, 86, 76, 65, 57, 40, 30, 24, 18, 13];
|
|
1645
|
+
function BarChartPlaceholder({ height = 220 }) {
|
|
1646
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AxisFrame, { height, xTicks: BAR_PROFILE.length, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "flex h-full w-full items-end gap-2", children: BAR_PROFILE.map((pct, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "flex-1 rounded-t-[3px] bg-subtle", style: { height: `${pct}%` } }, i)) }) });
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// src/LineChartPlaceholder.tsx
|
|
1650
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1651
|
+
var LINE_SERIES = [
|
|
1652
|
+
[58, 52, 55, 41, 45, 33, 36, 25, 28, 17],
|
|
1653
|
+
[74, 71, 66, 68, 59, 61, 54, 56, 48, 45],
|
|
1654
|
+
[40, 44, 37, 41, 34, 38, 31, 34, 27, 26],
|
|
1655
|
+
[86, 82, 84, 77, 79, 71, 73, 66, 68, 61]
|
|
1656
|
+
];
|
|
1657
|
+
var LINE_FORECAST_FROM = 7;
|
|
1658
|
+
function LineChartPlaceholder({ height = 280 }) {
|
|
1659
|
+
const toPath = (pts, from, to) => pts.slice(from, to).map((y, i) => `${(i + from) / (pts.length - 1) * 100},${y}`).join(" ");
|
|
1660
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(AxisFrame, { height, xTicks: LINE_SERIES[0].length, children: [
|
|
1661
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("svg", { viewBox: "0 0 100 100", preserveAspectRatio: "none", className: "absolute inset-0 h-full w-full", children: LINE_SERIES.map((pts, s) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("g", { children: [
|
|
1662
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
1663
|
+
"polyline",
|
|
1664
|
+
{
|
|
1665
|
+
points: toPath(pts, 0, LINE_FORECAST_FROM + 1),
|
|
1666
|
+
fill: "none",
|
|
1667
|
+
stroke: "var(--color-border-strong)",
|
|
1668
|
+
strokeWidth: "2",
|
|
1669
|
+
vectorEffect: "non-scaling-stroke",
|
|
1670
|
+
strokeLinecap: "round",
|
|
1671
|
+
strokeLinejoin: "round"
|
|
1672
|
+
}
|
|
1673
|
+
),
|
|
1674
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
1675
|
+
"polyline",
|
|
1676
|
+
{
|
|
1677
|
+
points: toPath(pts, LINE_FORECAST_FROM, pts.length),
|
|
1678
|
+
fill: "none",
|
|
1679
|
+
stroke: "var(--color-border-strong)",
|
|
1680
|
+
strokeWidth: "2",
|
|
1681
|
+
strokeDasharray: "5 4",
|
|
1682
|
+
vectorEffect: "non-scaling-stroke",
|
|
1683
|
+
strokeLinecap: "round",
|
|
1684
|
+
strokeLinejoin: "round"
|
|
1685
|
+
}
|
|
1686
|
+
)
|
|
1687
|
+
] }, s)) }),
|
|
1688
|
+
LINE_SERIES.map(
|
|
1689
|
+
(pts, s) => pts.map((y, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
1690
|
+
"div",
|
|
1691
|
+
{
|
|
1692
|
+
className: "absolute h-1.5 w-1.5 -translate-x-1/2 -translate-y-1/2 rounded-full",
|
|
1693
|
+
style: {
|
|
1694
|
+
left: `${i / (pts.length - 1) * 100}%`,
|
|
1695
|
+
top: `${y}%`,
|
|
1696
|
+
backgroundColor: "var(--color-border-strong)"
|
|
1697
|
+
}
|
|
1698
|
+
},
|
|
1699
|
+
`${s}-${i}`
|
|
1700
|
+
))
|
|
1701
|
+
)
|
|
1702
|
+
] });
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
// src/PieChartPlaceholder.tsx
|
|
1706
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1707
|
+
var PIE_SEAMS = [0, 115, 180, 230, 262, 288, 310, 332];
|
|
1708
|
+
function PieChartPlaceholder({ height = 220, legendRows = 8 }) {
|
|
1709
|
+
const r = 45;
|
|
1710
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex w-full items-center gap-3", style: { height }, role: "presentation", "aria-hidden": "true", children: [
|
|
1711
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex h-full items-center justify-center", style: { width: "60%" }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("svg", { viewBox: "0 0 100 100", className: "h-full", style: { aspectRatio: "1 / 1" }, children: [
|
|
1712
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("circle", { cx: "50", cy: "50", r, fill: "var(--color-subtle)" }),
|
|
1713
|
+
PIE_SEAMS.map((deg) => {
|
|
1714
|
+
const rad = (deg - 90) * Math.PI / 180;
|
|
1715
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1716
|
+
"line",
|
|
1717
|
+
{
|
|
1718
|
+
x1: "50",
|
|
1719
|
+
y1: "50",
|
|
1720
|
+
x2: 50 + r * Math.cos(rad),
|
|
1721
|
+
y2: 50 + r * Math.sin(rad),
|
|
1722
|
+
stroke: "var(--color-border-default)",
|
|
1723
|
+
strokeWidth: "1"
|
|
1724
|
+
},
|
|
1725
|
+
deg
|
|
1726
|
+
);
|
|
1727
|
+
})
|
|
1728
|
+
] }) }),
|
|
1729
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(BlankLegend, { rows: legendRows })
|
|
1730
|
+
] });
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
// src/StackedColumnPlaceholder.tsx
|
|
1734
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1735
|
+
var COLUMN_WEIGHTS = [20, 30, 15, 35, 24, 12, 28, 18];
|
|
1736
|
+
function columnBands(count) {
|
|
1737
|
+
const weights = Array.from({ length: count }, (_, i) => COLUMN_WEIGHTS[i % COLUMN_WEIGHTS.length]);
|
|
1738
|
+
const total = weights.reduce((a, b) => a + b, 0);
|
|
1739
|
+
return weights.map((w) => w / total * 100);
|
|
1740
|
+
}
|
|
1741
|
+
function StackedColumnPlaceholder({
|
|
1742
|
+
height = 220,
|
|
1743
|
+
width = 96,
|
|
1744
|
+
legendRows = 4,
|
|
1745
|
+
axis = false
|
|
1746
|
+
}) {
|
|
1747
|
+
const column = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex h-full flex-col overflow-hidden rounded-control bg-subtle", style: { width }, children: columnBands(legendRows).map((pct, i) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1748
|
+
"div",
|
|
1749
|
+
{
|
|
1750
|
+
style: {
|
|
1751
|
+
height: `${pct}%`,
|
|
1752
|
+
/* border-strong, not border-default: on a subtle fill the softer
|
|
1753
|
+
seam disappears and the stack reads as one undivided block. */
|
|
1754
|
+
borderTop: i === 0 ? void 0 : "1px solid var(--color-border-strong)"
|
|
1755
|
+
}
|
|
1756
|
+
},
|
|
1757
|
+
i
|
|
1758
|
+
)) });
|
|
1759
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex w-full gap-4", style: { height }, role: "presentation", "aria-hidden": "true", children: [
|
|
1760
|
+
axis ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: { width: "55%" }, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(AxisFrame, { height, xTicks: 0, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex h-full items-stretch justify-center", children: column }) }) }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex items-stretch py-1", children: column }),
|
|
1761
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(BlankLegend, { rows: legendRows, reverse: !axis })
|
|
1762
|
+
] });
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
// src/Tooltip.tsx
|
|
1766
|
+
var import_react10 = require("react");
|
|
1767
|
+
var import_react_dom3 = require("react-dom");
|
|
1768
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
1769
|
+
function cx6(...parts) {
|
|
1770
|
+
return parts.filter(Boolean).join(" ");
|
|
1771
|
+
}
|
|
1772
|
+
function Tooltip({
|
|
1773
|
+
label,
|
|
1774
|
+
children,
|
|
1775
|
+
anchorRef,
|
|
1776
|
+
className,
|
|
1777
|
+
whenTruncated = false,
|
|
1778
|
+
interactive = false,
|
|
1779
|
+
placement = "top",
|
|
1780
|
+
align = "center",
|
|
1781
|
+
delay = 0,
|
|
1782
|
+
variant = "surface",
|
|
1783
|
+
testIdPrefix
|
|
1784
|
+
}) {
|
|
1785
|
+
const [anchor, setAnchor] = (0, import_react10.useState)(null);
|
|
1786
|
+
const [boxWidth, setBoxWidth] = (0, import_react10.useState)(null);
|
|
1787
|
+
const ref = (0, import_react10.useRef)(null);
|
|
1788
|
+
const boxRef = (0, import_react10.useRef)(null);
|
|
1789
|
+
const hideTimer = (0, import_react10.useRef)(null);
|
|
1790
|
+
const showTimer = (0, import_react10.useRef)(null);
|
|
1791
|
+
const id = (0, import_react10.useId)();
|
|
1792
|
+
const clearHideTimer = () => {
|
|
1793
|
+
if (hideTimer.current) {
|
|
1794
|
+
clearTimeout(hideTimer.current);
|
|
1795
|
+
hideTimer.current = null;
|
|
1796
|
+
}
|
|
1797
|
+
};
|
|
1798
|
+
const clearShowTimer = () => {
|
|
1799
|
+
if (showTimer.current) {
|
|
1800
|
+
clearTimeout(showTimer.current);
|
|
1801
|
+
showTimer.current = null;
|
|
1802
|
+
}
|
|
1803
|
+
};
|
|
1804
|
+
(0, import_react10.useEffect)(
|
|
1805
|
+
() => () => {
|
|
1806
|
+
clearHideTimer();
|
|
1807
|
+
clearShowTimer();
|
|
1808
|
+
},
|
|
1809
|
+
[]
|
|
1810
|
+
);
|
|
1811
|
+
const reveal = () => {
|
|
1812
|
+
const el = anchorRef ? anchorRef.current : ref.current;
|
|
1813
|
+
if (!el) return;
|
|
1814
|
+
if (whenTruncated && el.scrollWidth <= el.clientWidth) return;
|
|
1815
|
+
const r = el.getBoundingClientRect();
|
|
1816
|
+
setBoxWidth(null);
|
|
1817
|
+
setAnchor({ top: r.top, left: r.left, width: r.width, height: r.height, bottom: r.bottom, right: r.right });
|
|
1818
|
+
if (anchorRef) el.setAttribute("aria-describedby", id);
|
|
1819
|
+
};
|
|
1820
|
+
const show = () => {
|
|
1821
|
+
clearHideTimer();
|
|
1822
|
+
if (delay > 0) {
|
|
1823
|
+
showTimer.current = setTimeout(reveal, delay);
|
|
1824
|
+
} else {
|
|
1825
|
+
reveal();
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1828
|
+
const hideNow = () => {
|
|
1829
|
+
clearShowTimer();
|
|
1830
|
+
clearHideTimer();
|
|
1831
|
+
setAnchor(null);
|
|
1832
|
+
setBoxWidth(null);
|
|
1833
|
+
if (anchorRef?.current) anchorRef.current.removeAttribute("aria-describedby");
|
|
1834
|
+
};
|
|
1835
|
+
const hide = () => {
|
|
1836
|
+
clearShowTimer();
|
|
1837
|
+
if (!interactive) {
|
|
1838
|
+
hideNow();
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
clearHideTimer();
|
|
1842
|
+
hideTimer.current = setTimeout(hideNow, 120);
|
|
1843
|
+
};
|
|
1844
|
+
(0, import_react10.useEffect)(() => {
|
|
1845
|
+
if (!anchorRef) return;
|
|
1846
|
+
const el = anchorRef.current;
|
|
1847
|
+
if (!el) return;
|
|
1848
|
+
el.addEventListener("mouseenter", show);
|
|
1849
|
+
el.addEventListener("mouseleave", hide);
|
|
1850
|
+
el.addEventListener("focus", show);
|
|
1851
|
+
el.addEventListener("blur", hide);
|
|
1852
|
+
return () => {
|
|
1853
|
+
el.removeEventListener("mouseenter", show);
|
|
1854
|
+
el.removeEventListener("mouseleave", hide);
|
|
1855
|
+
el.removeEventListener("focus", show);
|
|
1856
|
+
el.removeEventListener("blur", hide);
|
|
1857
|
+
};
|
|
1858
|
+
}, [anchorRef, placement, delay]);
|
|
1859
|
+
(0, import_react10.useLayoutEffect)(() => {
|
|
1860
|
+
if (!anchor) return;
|
|
1861
|
+
const el = boxRef.current;
|
|
1862
|
+
if (!el) return;
|
|
1863
|
+
setBoxWidth(el.getBoundingClientRect().width);
|
|
1864
|
+
}, [anchor, label]);
|
|
1865
|
+
const margin = 8;
|
|
1866
|
+
let style = { top: 0, left: 0 };
|
|
1867
|
+
let translate = "";
|
|
1868
|
+
if (anchor) {
|
|
1869
|
+
if (placement === "right") {
|
|
1870
|
+
style = { top: anchor.top + anchor.height / 2, left: anchor.right + 8 };
|
|
1871
|
+
translate = "-translate-y-1/2";
|
|
1872
|
+
} else if (placement === "bottom-end") {
|
|
1873
|
+
style = { top: anchor.bottom + 8, right: window.innerWidth - anchor.right };
|
|
1874
|
+
} else {
|
|
1875
|
+
const w = boxWidth ?? anchor.width;
|
|
1876
|
+
const desired = align === "start" ? anchor.left : anchor.left + anchor.width / 2 - w / 2;
|
|
1877
|
+
const left = Math.min(Math.max(desired, margin), Math.max(margin, window.innerWidth - w - margin));
|
|
1878
|
+
style = { left, top: placement === "bottom" ? anchor.bottom : anchor.top };
|
|
1879
|
+
translate = placement === "bottom" ? "translate-y-2" : "-translate-y-[calc(100%+8px)]";
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
const floating = anchor != null && typeof document !== "undefined" ? (0, import_react_dom3.createPortal)(
|
|
1883
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
1884
|
+
"span",
|
|
1885
|
+
{
|
|
1886
|
+
ref: boxRef,
|
|
1887
|
+
role: "tooltip",
|
|
1888
|
+
id,
|
|
1889
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-tooltip`,
|
|
1890
|
+
style: { top: style.top, left: style.left, right: style.right, visibility: boxWidth == null ? "hidden" : "visible" },
|
|
1891
|
+
onMouseEnter: interactive ? clearHideTimer : void 0,
|
|
1892
|
+
onMouseLeave: interactive ? hide : void 0,
|
|
1893
|
+
className: cx6(
|
|
1894
|
+
"fixed z-[90] w-max max-w-xs whitespace-normal break-words px-2.5 py-1.5 text-xs leading-snug",
|
|
1895
|
+
interactive ? "pointer-events-auto" : "pointer-events-none",
|
|
1896
|
+
variant === "inverted" ? "rounded-control bg-text-primary text-surface" : "rounded-control border border-border-default bg-surface text-text-primary shadow-overlay",
|
|
1897
|
+
translate
|
|
1898
|
+
),
|
|
1899
|
+
children: label
|
|
1900
|
+
}
|
|
1901
|
+
),
|
|
1902
|
+
document.body
|
|
1903
|
+
) : null;
|
|
1904
|
+
if (anchorRef) return floating;
|
|
1905
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
1906
|
+
"span",
|
|
1907
|
+
{
|
|
1908
|
+
ref,
|
|
1909
|
+
className,
|
|
1910
|
+
onMouseEnter: show,
|
|
1911
|
+
onMouseLeave: hide,
|
|
1912
|
+
onFocus: show,
|
|
1913
|
+
onBlur: hide,
|
|
1914
|
+
"aria-describedby": anchor ? id : void 0,
|
|
1915
|
+
children: [
|
|
1916
|
+
children,
|
|
1917
|
+
floating
|
|
1918
|
+
]
|
|
1919
|
+
}
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
// src/AdvertiserNoResults.tsx
|
|
1924
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
1925
|
+
function AdvertiserNoResults({
|
|
1926
|
+
query,
|
|
1927
|
+
hint = "Try checking your spelling or searching for a broader brand name."
|
|
1928
|
+
}) {
|
|
1929
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-col items-center gap-1.5 px-4 py-8 text-center", children: [
|
|
1930
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("p", { className: "text-[13px] font-medium text-text-primary", children: [
|
|
1931
|
+
"No results found for \u201C",
|
|
1932
|
+
query,
|
|
1933
|
+
"\u201D"
|
|
1934
|
+
] }),
|
|
1935
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-[12px] leading-snug text-text-tertiary", children: hint })
|
|
1936
|
+
] });
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
// src/SelectAllVisible.tsx
|
|
1940
|
+
var import_lucide_react12 = require("lucide-react");
|
|
1941
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
1942
|
+
function SelectAllVisible({ count, allSelected, onToggle, testIdPrefix }) {
|
|
1943
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
1944
|
+
"button",
|
|
1945
|
+
{
|
|
1946
|
+
type: "button",
|
|
1947
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-select-all-visible`,
|
|
1948
|
+
onClick: onToggle,
|
|
1949
|
+
className: "flex w-full items-center gap-2 rounded-control px-2 py-1.5 text-left text-[12px] font-semibold text-text-link transition-colors hover:bg-subtle",
|
|
1950
|
+
children: [
|
|
1951
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react12.ListChecks, { size: 14, "aria-hidden": "true" }),
|
|
1952
|
+
allSelected ? `Deselect ${count} visible` : `Select all ${count} visible`
|
|
1953
|
+
]
|
|
1954
|
+
}
|
|
1955
|
+
);
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
// src/SelectedSection.tsx
|
|
1959
|
+
var import_lucide_react13 = require("lucide-react");
|
|
1960
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
1961
|
+
function cx7(...parts) {
|
|
1962
|
+
return parts.filter(Boolean).join(" ");
|
|
1963
|
+
}
|
|
1964
|
+
function SelectedSection({ count, open, onToggle, children, testIdPrefix }) {
|
|
1965
|
+
if (count === 0) return null;
|
|
1966
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-col", children: [
|
|
1967
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
1968
|
+
"button",
|
|
1969
|
+
{
|
|
1970
|
+
type: "button",
|
|
1971
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-selected-toggle`,
|
|
1972
|
+
onClick: onToggle,
|
|
1973
|
+
"aria-expanded": open,
|
|
1974
|
+
className: "flex w-full items-center gap-1.5 rounded-control px-2 py-1 text-left text-[11px] font-semibold uppercase tracking-wide text-text-tertiary transition-colors hover:text-text-primary",
|
|
1975
|
+
children: [
|
|
1976
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react13.ChevronDown, { size: 13, "aria-hidden": "true", className: cx7("transition-transform", !open && "-rotate-90") }),
|
|
1977
|
+
"Selected (",
|
|
1978
|
+
count,
|
|
1979
|
+
")"
|
|
1980
|
+
]
|
|
1981
|
+
}
|
|
1982
|
+
),
|
|
1983
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex flex-col gap-0.5", children })
|
|
1984
|
+
] });
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
// src/SelectionPicker.tsx
|
|
1988
|
+
var import_react11 = require("react");
|
|
1989
|
+
var import_react_dom4 = require("react-dom");
|
|
1990
|
+
var import_lucide_react14 = require("lucide-react");
|
|
1991
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
1992
|
+
function cx8(...parts) {
|
|
1993
|
+
return parts.filter(Boolean).join(" ");
|
|
1994
|
+
}
|
|
1995
|
+
function slug4(value) {
|
|
1996
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
1997
|
+
}
|
|
1998
|
+
function SelectionPicker({
|
|
1999
|
+
catalog = [],
|
|
2000
|
+
selected,
|
|
2001
|
+
onChange,
|
|
2002
|
+
columns,
|
|
2003
|
+
filters,
|
|
2004
|
+
searchPlaceholder = "Search\u2026",
|
|
2005
|
+
getChildren,
|
|
2006
|
+
helper,
|
|
2007
|
+
showInfoColumn = true,
|
|
2008
|
+
defaultFilters,
|
|
2009
|
+
requireQuery = false,
|
|
2010
|
+
showResultsWithoutSearch = false,
|
|
2011
|
+
accountLists,
|
|
2012
|
+
selectedListIds,
|
|
2013
|
+
onToggleList,
|
|
2014
|
+
lockedIds,
|
|
2015
|
+
results,
|
|
2016
|
+
onQueryChange,
|
|
2017
|
+
loading = false,
|
|
2018
|
+
error = false,
|
|
2019
|
+
errorMessage = "Couldn't load options. Please try again.",
|
|
2020
|
+
hasMore = false,
|
|
2021
|
+
loadingMore = false,
|
|
2022
|
+
onLoadMore,
|
|
2023
|
+
toolbarExtra,
|
|
2024
|
+
testIdPrefix
|
|
2025
|
+
}) {
|
|
2026
|
+
const hasAccountLists = !!accountLists;
|
|
2027
|
+
const [source, setSource] = (0, import_react11.useState)("search");
|
|
2028
|
+
const [query, setQuery] = (0, import_react11.useState)("");
|
|
2029
|
+
const [activeFilters, setActiveFilters] = (0, import_react11.useState)(() => {
|
|
2030
|
+
const init = {};
|
|
2031
|
+
if (defaultFilters) for (const [label, opts] of Object.entries(defaultFilters)) init[label] = new Set(opts);
|
|
2032
|
+
return init;
|
|
2033
|
+
});
|
|
2034
|
+
const [expanded, setExpanded] = (0, import_react11.useState)(/* @__PURE__ */ new Set());
|
|
2035
|
+
const selectedIds = (0, import_react11.useMemo)(() => new Set(selected.map((s) => s.id)), [selected]);
|
|
2036
|
+
const toggleFilter = (label, opt) => {
|
|
2037
|
+
setActiveFilters((prev) => {
|
|
2038
|
+
const next = { ...prev };
|
|
2039
|
+
const set = new Set(next[label] ?? []);
|
|
2040
|
+
set.has(opt) ? set.delete(opt) : set.add(opt);
|
|
2041
|
+
next[label] = set;
|
|
2042
|
+
return next;
|
|
2043
|
+
});
|
|
2044
|
+
};
|
|
2045
|
+
const activeFilterCount = filters.reduce((n, f) => n + (activeFilters[f.label]?.size ?? 0), 0);
|
|
2046
|
+
const hasSearched = showResultsWithoutSearch || query.trim().length > 0 || !requireQuery && activeFilterCount > 0;
|
|
2047
|
+
const filteredCatalog = (0, import_react11.useMemo)(() => {
|
|
2048
|
+
if (!hasSearched || results !== void 0) return [];
|
|
2049
|
+
const q = query.trim().toLowerCase();
|
|
2050
|
+
return catalog.filter((row) => {
|
|
2051
|
+
if (q && !row.name.toLowerCase().includes(q)) return false;
|
|
2052
|
+
for (const f of filters) {
|
|
2053
|
+
const active = activeFilters[f.label];
|
|
2054
|
+
if (active && active.size > 0) {
|
|
2055
|
+
const v = f.value(row);
|
|
2056
|
+
if (!v || !active.has(v)) return false;
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
return true;
|
|
2060
|
+
});
|
|
2061
|
+
}, [hasSearched, results, catalog, query, filters, activeFilters]);
|
|
2062
|
+
const rows = results !== void 0 ? results : filteredCatalog;
|
|
2063
|
+
const addRows = (add) => {
|
|
2064
|
+
const toAdd = add.filter((r) => !selectedIds.has(r.id));
|
|
2065
|
+
if (toAdd.length) onChange([...selected, ...toAdd]);
|
|
2066
|
+
};
|
|
2067
|
+
const toggleRow = (row) => {
|
|
2068
|
+
if (selectedIds.has(row.id)) {
|
|
2069
|
+
onChange(selected.filter((s) => s.id !== row.id));
|
|
2070
|
+
} else {
|
|
2071
|
+
addRows([row]);
|
|
2072
|
+
}
|
|
2073
|
+
};
|
|
2074
|
+
const toggleable = lockedIds ? rows.filter((r) => !lockedIds.has(r.id)) : rows;
|
|
2075
|
+
const allSelected = toggleable.length > 0 && toggleable.every((r) => selectedIds.has(r.id));
|
|
2076
|
+
const someSelected = toggleable.some((r) => selectedIds.has(r.id));
|
|
2077
|
+
const toggleAll = () => {
|
|
2078
|
+
if (allSelected) {
|
|
2079
|
+
const rowIds = new Set(toggleable.map((r) => r.id));
|
|
2080
|
+
onChange(selected.filter((s) => !rowIds.has(s.id)));
|
|
2081
|
+
} else {
|
|
2082
|
+
addRows(toggleable);
|
|
2083
|
+
}
|
|
2084
|
+
};
|
|
2085
|
+
const rowGridCols = showInfoColumn ? "grid-cols-[36px_1fr_44px]" : "grid-cols-[36px_1fr]";
|
|
2086
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex flex-col gap-4", children: [
|
|
2087
|
+
helper && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
|
|
2088
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-[13px] text-text-secondary", children: helper }),
|
|
2089
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("hr", { className: "-mx-5 -mt-2 border-border-default sm:-mx-6" })
|
|
2090
|
+
] }),
|
|
2091
|
+
hasAccountLists && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "inline-flex gap-0.5 self-start rounded-control border border-border-default bg-canvas p-0.5", children: [
|
|
2092
|
+
{ id: "search", label: "Search", icon: import_lucide_react14.Search },
|
|
2093
|
+
{ id: "lists", label: "Account lists", icon: import_lucide_react14.List }
|
|
2094
|
+
].map((t) => {
|
|
2095
|
+
const on = source === t.id;
|
|
2096
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
2097
|
+
"button",
|
|
2098
|
+
{
|
|
2099
|
+
type: "button",
|
|
2100
|
+
onClick: () => setSource(t.id),
|
|
2101
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-source-${t.id}`,
|
|
2102
|
+
className: cx8(
|
|
2103
|
+
"inline-flex items-center gap-1.5 rounded-[6px] px-3 py-1.5 text-[13px] font-medium transition-colors",
|
|
2104
|
+
on ? "bg-surface text-text-primary shadow-raised" : "text-text-secondary hover:text-text-primary"
|
|
2105
|
+
),
|
|
2106
|
+
children: [
|
|
2107
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(t.icon, { size: 14, "aria-hidden": "true" }),
|
|
2108
|
+
t.label
|
|
2109
|
+
]
|
|
2110
|
+
},
|
|
2111
|
+
t.id
|
|
2112
|
+
);
|
|
2113
|
+
}) }),
|
|
2114
|
+
source === "search" && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex min-w-0 flex-col gap-3", children: [
|
|
2115
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
2116
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex h-9 min-w-[200px] flex-1 items-center gap-2 rounded-control border border-border-default bg-input px-3", children: [
|
|
2117
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react14.Search, { size: 15, className: "text-text-secondary", "aria-hidden": "true" }),
|
|
2118
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
2119
|
+
"input",
|
|
2120
|
+
{
|
|
2121
|
+
value: query,
|
|
2122
|
+
onChange: (e) => {
|
|
2123
|
+
const next = e.target.value;
|
|
2124
|
+
setQuery(next);
|
|
2125
|
+
onQueryChange?.(next);
|
|
2126
|
+
},
|
|
2127
|
+
placeholder: searchPlaceholder,
|
|
2128
|
+
"aria-label": searchPlaceholder,
|
|
2129
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-search`,
|
|
2130
|
+
className: "h-full flex-1 bg-transparent text-sm outline-hidden placeholder:text-text-tertiary"
|
|
2131
|
+
}
|
|
2132
|
+
)
|
|
2133
|
+
] }),
|
|
2134
|
+
filters.map((f) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
2135
|
+
FilterDropdown,
|
|
2136
|
+
{
|
|
2137
|
+
filter: f,
|
|
2138
|
+
active: activeFilters[f.label] ?? /* @__PURE__ */ new Set(),
|
|
2139
|
+
onToggle: (opt) => toggleFilter(f.label, opt),
|
|
2140
|
+
testIdPrefix
|
|
2141
|
+
},
|
|
2142
|
+
f.label
|
|
2143
|
+
)),
|
|
2144
|
+
toolbarExtra
|
|
2145
|
+
] }),
|
|
2146
|
+
!hasSearched ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex flex-col items-center gap-3 rounded-card border border-dashed border-border-default py-10 text-center", children: [
|
|
2147
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-subtle", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react14.Search, { size: 20, className: "text-text-tertiary", "aria-hidden": "true" }) }),
|
|
2148
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
2149
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-[14px] font-semibold text-text-primary", children: "Search to get started" }),
|
|
2150
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-[13px] text-text-tertiary", children: "Use the search or filters above to find and add options." })
|
|
2151
|
+
] })
|
|
2152
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "overflow-hidden rounded-control border border-border-default", children: [
|
|
2153
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
2154
|
+
"div",
|
|
2155
|
+
{
|
|
2156
|
+
className: cx8(
|
|
2157
|
+
"grid items-center gap-2 border-b border-border-default bg-canvas px-3 py-2 text-[11px] font-semibold uppercase tracking-[0.06em] text-text-secondary",
|
|
2158
|
+
rowGridCols
|
|
2159
|
+
),
|
|
2160
|
+
children: [
|
|
2161
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
2162
|
+
CheckBox,
|
|
2163
|
+
{
|
|
2164
|
+
checked: allSelected,
|
|
2165
|
+
indeterminate: !allSelected && someSelected,
|
|
2166
|
+
disabled: toggleable.length === 0,
|
|
2167
|
+
onChange: toggleAll,
|
|
2168
|
+
ariaLabel: "Select all",
|
|
2169
|
+
testId: testIdPrefix && `${testIdPrefix}-select-all`
|
|
2170
|
+
}
|
|
2171
|
+
),
|
|
2172
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: cx8("grid gap-2", gridForColumns(columns)), children: columns.map((c) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: c.className, children: c.header }, c.key)) }),
|
|
2173
|
+
showInfoColumn && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-right", children: "Info" })
|
|
2174
|
+
]
|
|
2175
|
+
}
|
|
2176
|
+
),
|
|
2177
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "max-h-[360px] overflow-y-auto", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex items-center justify-center py-6", role: "status", "aria-label": "Loading", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react14.Loader2, { size: 18, className: "animate-spin text-text-tertiary", "aria-hidden": "true" }) }) : error ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "px-3 py-8 text-center text-[13px] text-text-tertiary", children: errorMessage }) : rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "px-3 py-8 text-center text-[13px] text-text-tertiary", children: "No matches. Try a different search or filter." }) : rows.map((row) => {
|
|
2178
|
+
const isSel = selectedIds.has(row.id);
|
|
2179
|
+
const isLocked = lockedIds?.has(row.id) ?? false;
|
|
2180
|
+
const kids = getChildren?.(row);
|
|
2181
|
+
const isOpen = expanded.has(row.id);
|
|
2182
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "border-t border-border-default/60 first:border-t-0", children: [
|
|
2183
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
2184
|
+
"div",
|
|
2185
|
+
{
|
|
2186
|
+
onClick: isLocked ? void 0 : () => toggleRow(row),
|
|
2187
|
+
"aria-disabled": isLocked || void 0,
|
|
2188
|
+
className: cx8(
|
|
2189
|
+
"grid items-center gap-2 px-3 py-2.5",
|
|
2190
|
+
rowGridCols,
|
|
2191
|
+
isLocked ? "cursor-default opacity-50" : "cursor-pointer hover:bg-subtle",
|
|
2192
|
+
!isLocked && isSel && "opacity-60"
|
|
2193
|
+
),
|
|
2194
|
+
children: [
|
|
2195
|
+
isLocked ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { "aria-hidden": "true" }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
2196
|
+
CheckBox,
|
|
2197
|
+
{
|
|
2198
|
+
checked: isSel,
|
|
2199
|
+
onChange: () => toggleRow(row),
|
|
2200
|
+
ariaLabel: row.name,
|
|
2201
|
+
testId: testIdPrefix && `${testIdPrefix}-row-${slug4(row.id)}`
|
|
2202
|
+
}
|
|
2203
|
+
),
|
|
2204
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: cx8("grid gap-2 text-[13px] text-text-primary", gridForColumns(columns)), children: columns.map((c) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: cx8("truncate", c.className), children: c.render(row) }, c.key)) }),
|
|
2205
|
+
showInfoColumn && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex items-center justify-end", children: kids && kids.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
2206
|
+
"button",
|
|
2207
|
+
{
|
|
2208
|
+
type: "button",
|
|
2209
|
+
"aria-label": "Show details",
|
|
2210
|
+
onClick: (e) => {
|
|
2211
|
+
e.stopPropagation();
|
|
2212
|
+
setExpanded((p) => {
|
|
2213
|
+
const n = new Set(p);
|
|
2214
|
+
n.has(row.id) ? n.delete(row.id) : n.add(row.id);
|
|
2215
|
+
return n;
|
|
2216
|
+
});
|
|
2217
|
+
},
|
|
2218
|
+
className: "flex h-7 w-7 items-center justify-center rounded-control text-text-secondary hover:bg-canvas hover:text-text-primary",
|
|
2219
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react14.ChevronRight, { size: 15, className: cx8("transition-transform", isOpen && "rotate-90") })
|
|
2220
|
+
}
|
|
2221
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
2222
|
+
"span",
|
|
2223
|
+
{
|
|
2224
|
+
className: "flex h-7 w-7 items-center justify-center text-text-tertiary",
|
|
2225
|
+
title: "No additional details",
|
|
2226
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react14.Info, { size: 14 })
|
|
2227
|
+
}
|
|
2228
|
+
) })
|
|
2229
|
+
]
|
|
2230
|
+
}
|
|
2231
|
+
),
|
|
2232
|
+
isOpen && kids && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "bg-canvas/60 pb-1.5 pl-10 pr-3", children: kids.map((kid) => {
|
|
2233
|
+
const kidSel = selectedIds.has(kid.id);
|
|
2234
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
2235
|
+
"div",
|
|
2236
|
+
{
|
|
2237
|
+
className: "flex items-center justify-between gap-2 border-t border-border-default/40 py-2 first:border-t-0",
|
|
2238
|
+
children: [
|
|
2239
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "truncate text-[13px] text-text-secondary", children: kid.name }),
|
|
2240
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
2241
|
+
"button",
|
|
2242
|
+
{
|
|
2243
|
+
type: "button",
|
|
2244
|
+
disabled: kidSel,
|
|
2245
|
+
onClick: () => addRows([kid]),
|
|
2246
|
+
className: cx8(
|
|
2247
|
+
"inline-flex items-center gap-1 rounded-control px-2 py-1 text-[12px] font-medium",
|
|
2248
|
+
kidSel ? "text-text-tertiary" : "text-text-link hover:bg-subtle"
|
|
2249
|
+
),
|
|
2250
|
+
children: [
|
|
2251
|
+
kidSel ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react14.Check, { size: 13 }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react14.Plus, { size: 13 }),
|
|
2252
|
+
kidSel ? "Added" : "Add"
|
|
2253
|
+
]
|
|
2254
|
+
}
|
|
2255
|
+
)
|
|
2256
|
+
]
|
|
2257
|
+
},
|
|
2258
|
+
kid.id
|
|
2259
|
+
);
|
|
2260
|
+
}) })
|
|
2261
|
+
] }, row.id);
|
|
2262
|
+
}) }),
|
|
2263
|
+
!loading && !error && hasMore && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
2264
|
+
"button",
|
|
2265
|
+
{
|
|
2266
|
+
type: "button",
|
|
2267
|
+
onClick: onLoadMore,
|
|
2268
|
+
disabled: loadingMore,
|
|
2269
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-load-more`,
|
|
2270
|
+
className: "flex w-full items-center justify-center gap-2 border-t border-border-default px-2 py-1.5 text-[13px] text-brand-primaryAction transition-colors hover:bg-subtle disabled:opacity-50",
|
|
2271
|
+
children: [
|
|
2272
|
+
loadingMore ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react14.Loader2, { size: 13, className: "animate-spin", "aria-hidden": "true" }) : null,
|
|
2273
|
+
loadingMore ? "Loading\u2026" : "Load more"
|
|
2274
|
+
]
|
|
2275
|
+
}
|
|
2276
|
+
)
|
|
2277
|
+
] })
|
|
2278
|
+
] }),
|
|
2279
|
+
source === "lists" && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex min-w-0 flex-col gap-3", children: [
|
|
2280
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-[13px] text-text-secondary", children: "Select an account list to add all of its accounts at once. Your workspace stays in sync as the list is updated." }),
|
|
2281
|
+
accountLists && accountLists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
|
|
2282
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ScrollFade, { className: "flex max-h-[396px] flex-col gap-2 overflow-y-auto pr-0.5", children: accountLists.map((list) => {
|
|
2283
|
+
const on = (selectedListIds ?? []).includes(list.id);
|
|
2284
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
2285
|
+
"button",
|
|
2286
|
+
{
|
|
2287
|
+
type: "button",
|
|
2288
|
+
onClick: () => onToggleList?.(list.id),
|
|
2289
|
+
"aria-pressed": on,
|
|
2290
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-list-${list.id}`,
|
|
2291
|
+
className: cx8(
|
|
2292
|
+
"flex items-center gap-3 rounded-control border px-3.5 py-3 text-left transition-colors",
|
|
2293
|
+
on ? "border-brand-primaryAction bg-brand-primarySoft" : "border-border-default bg-surface hover:bg-subtle"
|
|
2294
|
+
),
|
|
2295
|
+
children: [
|
|
2296
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
2297
|
+
"span",
|
|
2298
|
+
{
|
|
2299
|
+
className: cx8(
|
|
2300
|
+
"flex h-5 w-5 shrink-0 items-center justify-center rounded-full",
|
|
2301
|
+
on ? "text-brand-primaryAction" : "text-text-secondary"
|
|
2302
|
+
),
|
|
2303
|
+
children: on ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react14.Check, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react14.Plus, { size: 16 })
|
|
2304
|
+
}
|
|
2305
|
+
),
|
|
2306
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { className: "min-w-0 flex-1", children: [
|
|
2307
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "flex min-w-0 items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "truncate text-[14px] font-medium text-text-primary", children: list.name }) }),
|
|
2308
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { className: "block truncate text-[12px] text-text-secondary", children: [
|
|
2309
|
+
list.count,
|
|
2310
|
+
" accounts \xB7 ",
|
|
2311
|
+
list.entityTypes.join(", "),
|
|
2312
|
+
" \xB7 Updated ",
|
|
2313
|
+
formatListDate(list.updatedAt)
|
|
2314
|
+
] })
|
|
2315
|
+
] }),
|
|
2316
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: cx8("shrink-0 text-[12px] font-medium", on ? "text-brand-primaryAction" : "text-text-secondary"), children: on ? "Added" : "Add" })
|
|
2317
|
+
]
|
|
2318
|
+
},
|
|
2319
|
+
list.id
|
|
2320
|
+
);
|
|
2321
|
+
}) }),
|
|
2322
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
2323
|
+
"p",
|
|
2324
|
+
{
|
|
2325
|
+
className: "border-t border-border-default pt-2 text-right text-[12px] font-medium text-text-tertiary",
|
|
2326
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-list-count`,
|
|
2327
|
+
children: [
|
|
2328
|
+
accountLists.length,
|
|
2329
|
+
" ",
|
|
2330
|
+
accountLists.length === 1 ? "list" : "lists"
|
|
2331
|
+
]
|
|
2332
|
+
}
|
|
2333
|
+
)
|
|
2334
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex flex-col items-center gap-3 rounded-card border border-dashed border-border-default py-10 text-center", children: [
|
|
2335
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-subtle", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react14.List, { size: 20, className: "text-text-tertiary", "aria-hidden": "true" }) }),
|
|
2336
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
2337
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-[14px] font-semibold text-text-primary", children: "No account lists yet" }),
|
|
2338
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-[13px] text-text-tertiary", children: "Account lists set up for you will appear here." })
|
|
2339
|
+
] })
|
|
2340
|
+
] })
|
|
2341
|
+
] })
|
|
2342
|
+
] });
|
|
2343
|
+
}
|
|
2344
|
+
var EDGE_FADE = 44;
|
|
2345
|
+
function ScrollFade({ className, children }) {
|
|
2346
|
+
const elRef = (0, import_react11.useRef)(null);
|
|
2347
|
+
const [edges, setEdges] = (0, import_react11.useState)({ top: false, bottom: false });
|
|
2348
|
+
const sync = (0, import_react11.useCallback)(() => {
|
|
2349
|
+
const el = elRef.current;
|
|
2350
|
+
if (!el) return;
|
|
2351
|
+
const top = el.scrollTop > 2;
|
|
2352
|
+
const bottom = el.scrollTop + el.clientHeight < el.scrollHeight - 2;
|
|
2353
|
+
setEdges((prev) => prev.top === top && prev.bottom === bottom ? prev : { top, bottom });
|
|
2354
|
+
}, []);
|
|
2355
|
+
const attach = (0, import_react11.useCallback)(
|
|
2356
|
+
(el) => {
|
|
2357
|
+
elRef.current = el;
|
|
2358
|
+
if (!el) return;
|
|
2359
|
+
sync();
|
|
2360
|
+
const ro = new ResizeObserver(sync);
|
|
2361
|
+
ro.observe(el);
|
|
2362
|
+
return () => {
|
|
2363
|
+
ro.disconnect();
|
|
2364
|
+
elRef.current = null;
|
|
2365
|
+
};
|
|
2366
|
+
},
|
|
2367
|
+
[sync]
|
|
2368
|
+
);
|
|
2369
|
+
const mask = edges.top || edges.bottom ? `linear-gradient(to bottom, transparent 0, #000 ${edges.top ? EDGE_FADE : 0}px, #000 calc(100% - ${edges.bottom ? EDGE_FADE : 0}px), transparent 100%)` : void 0;
|
|
2370
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { ref: attach, onScroll: sync, className, style: { maskImage: mask, WebkitMaskImage: mask }, children });
|
|
2371
|
+
}
|
|
2372
|
+
function formatListDate(iso) {
|
|
2373
|
+
try {
|
|
2374
|
+
return new Date(iso).toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
2375
|
+
} catch {
|
|
2376
|
+
return iso;
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
function FilterDropdown({
|
|
2380
|
+
filter,
|
|
2381
|
+
active,
|
|
2382
|
+
onToggle,
|
|
2383
|
+
testIdPrefix
|
|
2384
|
+
}) {
|
|
2385
|
+
const [open, setOpen] = (0, import_react11.useState)(false);
|
|
2386
|
+
const [q, setQ] = (0, import_react11.useState)("");
|
|
2387
|
+
const btnRef = (0, import_react11.useRef)(null);
|
|
2388
|
+
const menuRef = (0, import_react11.useRef)(null);
|
|
2389
|
+
const [pos, setPos] = (0, import_react11.useState)(null);
|
|
2390
|
+
const searchable = filter.options.length > 8;
|
|
2391
|
+
const shownOptions = searchable && q.trim() ? filter.options.filter((o) => o.toLowerCase().includes(q.trim().toLowerCase())) : filter.options;
|
|
2392
|
+
(0, import_react11.useLayoutEffect)(() => {
|
|
2393
|
+
if (!open || !btnRef.current) return;
|
|
2394
|
+
const r = btnRef.current.getBoundingClientRect();
|
|
2395
|
+
setPos({ top: r.bottom + 6, left: r.left });
|
|
2396
|
+
}, [open]);
|
|
2397
|
+
(0, import_react11.useEffect)(() => {
|
|
2398
|
+
if (!open) {
|
|
2399
|
+
setQ("");
|
|
2400
|
+
return;
|
|
2401
|
+
}
|
|
2402
|
+
const onDown = (e) => {
|
|
2403
|
+
const t = e.target;
|
|
2404
|
+
if (!btnRef.current?.contains(t) && !menuRef.current?.contains(t)) setOpen(false);
|
|
2405
|
+
};
|
|
2406
|
+
const onKey = (e) => e.key === "Escape" && setOpen(false);
|
|
2407
|
+
const onScroll = () => setOpen(false);
|
|
2408
|
+
window.addEventListener("mousedown", onDown);
|
|
2409
|
+
window.addEventListener("keydown", onKey);
|
|
2410
|
+
window.addEventListener("scroll", onScroll, true);
|
|
2411
|
+
return () => {
|
|
2412
|
+
window.removeEventListener("mousedown", onDown);
|
|
2413
|
+
window.removeEventListener("keydown", onKey);
|
|
2414
|
+
window.removeEventListener("scroll", onScroll, true);
|
|
2415
|
+
};
|
|
2416
|
+
}, [open]);
|
|
2417
|
+
const count = active.size;
|
|
2418
|
+
const label = count === 0 ? filter.label : count === 1 ? [...active][0] : "Multiple";
|
|
2419
|
+
const filterSlug = slug4(filter.label);
|
|
2420
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "relative", children: [
|
|
2421
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
2422
|
+
"button",
|
|
2423
|
+
{
|
|
2424
|
+
ref: btnRef,
|
|
2425
|
+
type: "button",
|
|
2426
|
+
onClick: () => setOpen((v) => !v),
|
|
2427
|
+
"aria-haspopup": "menu",
|
|
2428
|
+
"aria-expanded": open,
|
|
2429
|
+
"data-testid": testIdPrefix && `${testIdPrefix}-filter-${filterSlug}`,
|
|
2430
|
+
className: "inline-flex h-9 items-center gap-1.5 whitespace-nowrap rounded-control border border-border-default bg-input px-3 text-[13px] font-medium text-text-secondary transition-colors hover:bg-subtle",
|
|
2431
|
+
children: [
|
|
2432
|
+
label,
|
|
2433
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react14.ChevronDown, { size: 13, "aria-hidden": "true", className: cx8("transition-transform", open && "rotate-180") })
|
|
2434
|
+
]
|
|
2435
|
+
}
|
|
2436
|
+
),
|
|
2437
|
+
open && pos && (0, import_react_dom4.createPortal)(
|
|
2438
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
2439
|
+
"div",
|
|
2440
|
+
{
|
|
2441
|
+
ref: menuRef,
|
|
2442
|
+
role: "menu",
|
|
2443
|
+
"aria-label": filter.label,
|
|
2444
|
+
style: { top: pos.top, left: pos.left },
|
|
2445
|
+
className: "fixed z-[80] min-w-[220px] rounded-card border border-border-default bg-surface p-2 shadow-overlay",
|
|
2446
|
+
children: [
|
|
2447
|
+
searchable && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "mb-1.5 flex h-8 items-center gap-2 rounded-control border border-border-default bg-input px-2.5", children: [
|
|
2448
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react14.Search, { size: 14, className: "shrink-0 text-text-secondary", "aria-hidden": "true" }),
|
|
2449
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
2450
|
+
"input",
|
|
2451
|
+
{
|
|
2452
|
+
autoFocus: true,
|
|
2453
|
+
value: q,
|
|
2454
|
+
onChange: (e) => setQ(e.target.value),
|
|
2455
|
+
placeholder: `Search ${filter.label.toLowerCase()}\u2026`,
|
|
2456
|
+
className: "h-full flex-1 bg-transparent text-[13px] outline-hidden placeholder:text-text-tertiary"
|
|
2457
|
+
}
|
|
2458
|
+
)
|
|
2459
|
+
] }),
|
|
2460
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: cx8("flex flex-col gap-0.5", searchable && "max-h-[240px] overflow-y-auto"), children: shownOptions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "px-2 py-3 text-center text-[12px] text-text-secondary", children: "No matches." }) : shownOptions.map((opt) => {
|
|
2461
|
+
const on = active.has(opt);
|
|
2462
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
2463
|
+
"label",
|
|
2464
|
+
{
|
|
2465
|
+
className: "flex cursor-pointer items-center gap-2 rounded-control px-2 py-1.5 text-[13px] text-text-primary hover:bg-subtle",
|
|
2466
|
+
children: [
|
|
2467
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
2468
|
+
CheckBox,
|
|
2469
|
+
{
|
|
2470
|
+
checked: on,
|
|
2471
|
+
onChange: () => onToggle(opt),
|
|
2472
|
+
ariaLabel: opt,
|
|
2473
|
+
testId: testIdPrefix && `${testIdPrefix}-filter-${filterSlug}-${slug4(opt)}`
|
|
2474
|
+
}
|
|
2475
|
+
),
|
|
2476
|
+
opt
|
|
2477
|
+
]
|
|
2478
|
+
},
|
|
2479
|
+
opt
|
|
2480
|
+
);
|
|
2481
|
+
}) })
|
|
2482
|
+
]
|
|
2483
|
+
}
|
|
2484
|
+
),
|
|
2485
|
+
document.body
|
|
2486
|
+
)
|
|
2487
|
+
] });
|
|
2488
|
+
}
|
|
2489
|
+
function gridForColumns(columns) {
|
|
2490
|
+
if (columns.length <= 1) return "grid-cols-1";
|
|
2491
|
+
if (columns.length === 2) return "grid-cols-[1.6fr_1fr]";
|
|
2492
|
+
if (columns.length === 3) return "grid-cols-[1.6fr_1fr_1fr]";
|
|
2493
|
+
return "grid-cols-[1.6fr_1fr_1fr_1fr]";
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
// src/PublisherBreakdownList.tsx
|
|
2497
|
+
var import_react12 = require("react");
|
|
2498
|
+
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
2499
|
+
function cx9(...parts) {
|
|
2500
|
+
return parts.filter(Boolean).join(" ");
|
|
2501
|
+
}
|
|
2502
|
+
var fmtM = (v) => v >= 100 ? `$${Math.round(v)}M` : `$${v.toFixed(1)}M`;
|
|
2503
|
+
function priorYearAmt(spendN, yoy) {
|
|
2504
|
+
if (yoy === "NEW" || yoy <= -100) return null;
|
|
2505
|
+
return fmtM(spendN / (1 + yoy / 100));
|
|
2506
|
+
}
|
|
2507
|
+
function initials(name) {
|
|
2508
|
+
const words = name.replace(/[^a-zA-Z0-9 ]/g, " ").split(/\s+/).filter(Boolean);
|
|
2509
|
+
if (words.length >= 2) return (words[0][0] + words[1][0]).toUpperCase();
|
|
2510
|
+
return name.replace(/[^a-zA-Z0-9]/g, "").slice(0, 2).toUpperCase();
|
|
2511
|
+
}
|
|
2512
|
+
function Monogram({ name, size = 28 }) {
|
|
2513
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
2514
|
+
"span",
|
|
2515
|
+
{
|
|
2516
|
+
"aria-hidden": "true",
|
|
2517
|
+
className: "flex shrink-0 items-center justify-center rounded-[6px] bg-subtle font-bold leading-none text-text-secondary",
|
|
2518
|
+
style: { width: size, height: size, fontSize: Math.round(size * 0.36) },
|
|
2519
|
+
children: initials(name)
|
|
2520
|
+
}
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
function BreakdownTag({ children }) {
|
|
2524
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "inline-flex items-center whitespace-nowrap rounded-full bg-subtle px-2 py-0.5 text-[11px] font-medium text-text-secondary", children });
|
|
2525
|
+
}
|
|
2526
|
+
function DelayedTip({ label, children }) {
|
|
2527
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { className: "group/dt relative inline-flex", tabIndex: 0, "aria-label": label, children: [
|
|
2528
|
+
children,
|
|
2529
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
2530
|
+
"span",
|
|
2531
|
+
{
|
|
2532
|
+
role: "tooltip",
|
|
2533
|
+
className: "pointer-events-none invisible absolute bottom-[calc(100%+6px)] left-1/2 z-40 w-[160px] -translate-x-1/2 rounded-control border border-border-default bg-surface px-2 py-1.5 text-center text-[11px] font-medium leading-snug text-text-secondary opacity-0 shadow-overlay transition-[opacity,visibility] delay-1000 duration-150 group-hover/dt:visible group-hover/dt:opacity-100 group-focus/dt:visible group-focus/dt:opacity-100",
|
|
2534
|
+
children: label
|
|
2535
|
+
}
|
|
2536
|
+
)
|
|
2537
|
+
] });
|
|
2538
|
+
}
|
|
2539
|
+
var COL_CURRENT = 76;
|
|
2540
|
+
var COL_PREVIOUS = 76;
|
|
2541
|
+
var COL_YOY = 44;
|
|
2542
|
+
function MetricListHeader({ className }) {
|
|
2543
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: cx9("flex items-baseline gap-2 pb-2", className), children: [
|
|
2544
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "flex-1" }),
|
|
2545
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { className: "flex shrink-0 items-center gap-2 pr-1.5", children: [
|
|
2546
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-right text-[11px] font-medium text-text-tertiary", style: { width: COL_CURRENT }, children: "Current" }),
|
|
2547
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(DelayedTip, { label: "Spend during the same time period one year ago.", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-right text-[11px] font-medium text-text-tertiary", style: { width: COL_PREVIOUS }, children: "Previous" }) }),
|
|
2548
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(DelayedTip, { label: "Percent change from this time period vs. the same time one year ago.", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-right text-[11px] font-medium text-text-tertiary", style: { width: COL_YOY }, children: "YoY" }) })
|
|
2549
|
+
] })
|
|
2550
|
+
] });
|
|
2551
|
+
}
|
|
2552
|
+
function MetricCells({ spendN, yoy }) {
|
|
2553
|
+
const prior = priorYearAmt(spendN, yoy);
|
|
2554
|
+
const up = yoy !== "NEW" && yoy >= 0;
|
|
2555
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { className: "flex shrink-0 items-center gap-2 pr-1.5", children: [
|
|
2556
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-right text-[12px] font-bold tabular-nums text-text-primary", style: { width: COL_CURRENT }, children: fmtM(spendN) }),
|
|
2557
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-right text-[12px] font-normal tabular-nums text-text-primary", style: { width: COL_PREVIOUS }, children: prior ?? "\u2014" }),
|
|
2558
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "flex justify-end", style: { width: COL_YOY }, children: yoy === "NEW" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "rounded-full bg-status-warning/15 px-1.5 text-[10px] font-semibold text-text-warning", children: "NEW" }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { className: cx9("text-right text-[12px] font-semibold tabular-nums", up ? "text-text-success" : "text-text-danger"), children: [
|
|
2559
|
+
up ? "+" : "\u2212",
|
|
2560
|
+
Math.abs(yoy),
|
|
2561
|
+
"%"
|
|
2562
|
+
] }) })
|
|
2563
|
+
] });
|
|
2564
|
+
}
|
|
2565
|
+
function PublisherBreakdownList({ rows, defaultVisibleCount = 10, padded = true, viewAllHref, renderLogo }) {
|
|
2566
|
+
const [showAll, setShowAll] = (0, import_react12.useState)(false);
|
|
2567
|
+
const visible = showAll ? rows : rows.slice(0, defaultVisibleCount);
|
|
2568
|
+
const rowPad = padded ? "px-5" : "";
|
|
2569
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col", children: [
|
|
2570
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col", children: [
|
|
2571
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(MetricListHeader, { className: rowPad }),
|
|
2572
|
+
visible.map((p) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: cx9("flex items-center justify-between gap-3 border-t border-border-default/60 py-2.5 first:border-t-0", rowPad), children: [
|
|
2573
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex min-w-0 items-center gap-2.5", children: [
|
|
2574
|
+
renderLogo ? renderLogo(p) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Monogram, { name: p.brand, size: 28 }),
|
|
2575
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "min-w-0", children: [
|
|
2576
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5", children: [
|
|
2577
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-[14px] font-semibold text-text-primary", children: p.brand }),
|
|
2578
|
+
p.tags.map((t) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(BreakdownTag, { children: t }, t))
|
|
2579
|
+
] }),
|
|
2580
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "truncate text-[11.5px] text-text-tertiary", children: p.owner })
|
|
2581
|
+
] })
|
|
2582
|
+
] }),
|
|
2583
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(MetricCells, { spendN: p.spendN, yoy: p.yoy })
|
|
2584
|
+
] }, p.brand))
|
|
2585
|
+
] }),
|
|
2586
|
+
rows.length > defaultVisibleCount && (viewAllHref ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: cx9("border-t border-border-default py-3", rowPad), children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("a", { href: viewAllHref, className: "text-[12px] font-semibold text-text-link hover:underline", children: "See all publishers" }) }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: cx9("flex items-center gap-2 border-t border-border-default py-3 text-[12px] text-text-tertiary", rowPad), children: [
|
|
2587
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { children: [
|
|
2588
|
+
"Showing ",
|
|
2589
|
+
visible.length,
|
|
2590
|
+
" of ",
|
|
2591
|
+
rows.length
|
|
2592
|
+
] }),
|
|
2593
|
+
!showAll ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("button", { type: "button", onClick: () => setShowAll(true), className: "font-semibold text-text-link hover:underline", children: [
|
|
2594
|
+
"See all (",
|
|
2595
|
+
rows.length - defaultVisibleCount,
|
|
2596
|
+
" more)"
|
|
2597
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { type: "button", onClick: () => setShowAll(false), className: "font-semibold text-text-link hover:underline", children: "Show less" })
|
|
2598
|
+
] }))
|
|
2599
|
+
] });
|
|
2600
|
+
}
|
|
99
2601
|
// Annotate the CommonJS export names for ESM import in node:
|
|
100
2602
|
0 && (module.exports = {
|
|
101
|
-
|
|
2603
|
+
AdvertiserNoResults,
|
|
2604
|
+
AdvertiserQuickFilter,
|
|
2605
|
+
AdvertiserSearchPrompt,
|
|
2606
|
+
BarChartPlaceholder,
|
|
2607
|
+
Card,
|
|
2608
|
+
CheckBox,
|
|
2609
|
+
DEFAULT_SEARCH_MATCH,
|
|
2610
|
+
DataScopeFilter,
|
|
2611
|
+
DateRangeFilter,
|
|
2612
|
+
InfoTip,
|
|
2613
|
+
LineChartPlaceholder,
|
|
2614
|
+
NO_RESULTS_BODY,
|
|
2615
|
+
NO_RESULTS_TITLE,
|
|
2616
|
+
NoResultsEmptyState,
|
|
2617
|
+
PRIMARY_ACTION_CLASS,
|
|
2618
|
+
PieChartPlaceholder,
|
|
2619
|
+
PublisherBreakdownList,
|
|
2620
|
+
QUIET_ACTION_CLASS,
|
|
2621
|
+
SearchBar,
|
|
2622
|
+
SectionRail,
|
|
2623
|
+
SelectAllVisible,
|
|
2624
|
+
SelectFilter,
|
|
2625
|
+
SelectedSection,
|
|
2626
|
+
SelectionPicker,
|
|
2627
|
+
StackedColumnPlaceholder,
|
|
2628
|
+
Tooltip,
|
|
2629
|
+
WorkspaceSelectedCard,
|
|
2630
|
+
noResultsBody
|
|
102
2631
|
});
|
|
103
2632
|
//# sourceMappingURL=index.cjs.map
|