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