@mediaradar/design-components 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2736,21 +2736,34 @@ function FilterDropdownFooter({
2736
2736
  var import_react14 = require("react");
2737
2737
  var import_react_dom5 = require("react-dom");
2738
2738
  var import_jsx_runtime26 = require("react/jsx-runtime");
2739
- function NameListModal({ title, initialName, submitLabel, onCancel, onSubmit }) {
2739
+ function NameListModal({
2740
+ title,
2741
+ initialName,
2742
+ submitLabel,
2743
+ onCancel,
2744
+ onSubmit,
2745
+ fieldLabel = "List name",
2746
+ placeholder = "e.g. Q4 prospecting targets",
2747
+ options
2748
+ }) {
2740
2749
  const [name, setName] = (0, import_react14.useState)(initialName);
2750
+ const [checked, setChecked] = (0, import_react14.useState)(
2751
+ () => Object.fromEntries((options ?? []).map((o) => [o.key, !!o.initial]))
2752
+ );
2753
+ const toggle = (key) => setChecked((c) => ({ ...c, [key]: !c[key] }));
2741
2754
  (0, import_react14.useEffect)(() => {
2742
2755
  const onKey = (e) => e.key === "Escape" && onCancel();
2743
2756
  window.addEventListener("keydown", onKey);
2744
2757
  return () => window.removeEventListener("keydown", onKey);
2745
2758
  }, [onCancel]);
2746
2759
  return (0, import_react_dom5.createPortal)(
2747
- // The scrim is a literal: @mediaradar/design-tokens has no scrim token yet.
2748
- // Same light/dark values as insights-studio-prototype's local --color-scrim;
2749
- // the dark one keys off data-theme, the attribute design-tokens' dark.css uses.
2760
+ // bg-scrim, not a literal: design-tokens gained a scrim token in 2.5.0.
2761
+ // No in-data-[theme=dark] override needed any more -- the token redefines
2762
+ // itself under that attribute in dark.css, so one class covers both themes.
2750
2763
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2751
2764
  "div",
2752
2765
  {
2753
- className: "fixed inset-0 z-70 flex items-center justify-center bg-[rgba(20,17,13,0.55)] in-data-[theme=dark]:bg-[rgba(0,0,0,0.65)] px-4",
2766
+ className: "fixed inset-0 z-70 flex items-center justify-center bg-scrim px-4",
2754
2767
  onMouseDown: (e) => e.target === e.currentTarget && onCancel(),
2755
2768
  children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
2756
2769
  "div",
@@ -2762,7 +2775,7 @@ function NameListModal({ title, initialName, submitLabel, onCancel, onSubmit })
2762
2775
  children: [
2763
2776
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("h2", { className: "text-[16px] font-semibold text-text-primary", children: title }),
2764
2777
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "mt-4 flex flex-col gap-1.5", children: [
2765
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("label", { htmlFor: "account-list-name", className: "text-[12px] font-medium text-text-secondary", children: "List name" }),
2778
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("label", { htmlFor: "account-list-name", className: "text-[12px] font-medium text-text-secondary", children: fieldLabel }),
2766
2779
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2767
2780
  "input",
2768
2781
  {
@@ -2770,14 +2783,18 @@ function NameListModal({ title, initialName, submitLabel, onCancel, onSubmit })
2770
2783
  autoFocus: true,
2771
2784
  value: name,
2772
2785
  onChange: (e) => setName(e.target.value),
2773
- placeholder: "e.g. Q4 prospecting targets",
2786
+ placeholder,
2774
2787
  className: "h-10 rounded-control border border-border-default bg-input px-3 text-sm outline-hidden focus:border-brand-primaryAction"
2775
2788
  }
2776
2789
  )
2777
2790
  ] }),
2791
+ options && options.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "mt-4 flex flex-col gap-2.5", children: options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("label", { className: "flex cursor-pointer items-center gap-2.5 text-[13px] text-text-primary", children: [
2792
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(CheckBox, { checked: !!checked[o.key], onChange: () => toggle(o.key), ariaLabel: o.label }),
2793
+ o.label
2794
+ ] }, o.key)) }),
2778
2795
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "mt-5 flex items-center justify-end gap-2", children: [
2779
2796
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Button, { variant: "secondary", onClick: onCancel, children: "Cancel" }),
2780
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Button, { disabled: !name.trim(), onClick: () => onSubmit(name.trim()), children: submitLabel })
2797
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Button, { disabled: !name.trim(), onClick: () => onSubmit(name.trim(), checked), children: submitLabel })
2781
2798
  ] })
2782
2799
  ]
2783
2800
  }
@@ -3149,6 +3166,7 @@ var import_react15 = require("react");
3149
3166
  var import_lucide_react20 = require("lucide-react");
3150
3167
  var import_jsx_runtime34 = require("react/jsx-runtime");
3151
3168
  var MONTH_ABBR = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
3169
+ var QUARTER_ABBR = ["Q1", "Q2", "Q3", "Q4"];
3152
3170
  var PRESETS = ["Rolling 3 months", "Rolling 6 months", "Rolling 12 months"];
3153
3171
  var startOfToday = () => {
3154
3172
  const d = /* @__PURE__ */ new Date();
@@ -3197,6 +3215,7 @@ function MiniSelect({ value, onChange, options }) {
3197
3215
  }
3198
3216
  function DateRangePicker({
3199
3217
  defaultValue = "Rolling 6 months",
3218
+ granularity = "month",
3200
3219
  align = "left",
3201
3220
  presets = PRESETS,
3202
3221
  onChange,
@@ -3215,6 +3234,8 @@ function DateRangePicker({
3215
3234
  const [fromYr, setFromYr] = (0, import_react15.useState)(end.getFullYear() - 1);
3216
3235
  const [toMo, setToMo] = (0, import_react15.useState)(end.getMonth());
3217
3236
  const [toYr, setToYr] = (0, import_react15.useState)(end.getFullYear());
3237
+ const [fromQ, setFromQ] = (0, import_react15.useState)(Math.floor(end.getMonth() / 3));
3238
+ const [toQ, setToQ] = (0, import_react15.useState)(Math.floor(end.getMonth() / 3));
3218
3239
  (0, import_react15.useEffect)(() => {
3219
3240
  const [s, e] = resolveRange(value, end);
3220
3241
  onRangeChange?.(s, e);
@@ -3241,11 +3262,11 @@ function DateRangePicker({
3241
3262
  onChange?.(label);
3242
3263
  setOpen(false);
3243
3264
  };
3244
- const span = toYr * 12 + toMo - (fromYr * 12 + fromMo) + 1;
3265
+ const span = granularity === "year" ? toYr - fromYr + 1 : granularity === "quarter" ? toYr * 4 + toQ - (fromYr * 4 + fromQ) + 1 : toYr * 12 + toMo - (fromYr * 12 + fromMo) + 1;
3245
3266
  const valid = span >= 1;
3246
3267
  const applyCustom = () => {
3247
3268
  if (!valid) return;
3248
- const label = `${MONTH_ABBR[fromMo]} ${fromYr} \u2013 ${MONTH_ABBR[toMo]} ${toYr}`;
3269
+ const label = granularity === "year" ? `${fromYr} \u2013 ${toYr}` : granularity === "quarter" ? `${QUARTER_ABBR[fromQ]} ${fromYr} \u2013 ${QUARTER_ABBR[toQ]} ${toYr}` : `${MONTH_ABBR[fromMo]} ${fromYr} \u2013 ${MONTH_ABBR[toMo]} ${toYr}`;
3249
3270
  if (showFooter) setDraft(label);
3250
3271
  else commit(label);
3251
3272
  };
@@ -3284,14 +3305,16 @@ function DateRangePicker({
3284
3305
  /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("label", { className: "flex items-center justify-between gap-2 text-[12px] text-text-secondary", children: [
3285
3306
  "From",
3286
3307
  /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "flex gap-1", children: [
3287
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MiniSelect, { value: fromMo, onChange: setFromMo, options: MONTH_ABBR.map((mm, i) => ({ label: mm, value: i })) }),
3308
+ granularity === "month" && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MiniSelect, { value: fromMo, onChange: setFromMo, options: MONTH_ABBR.map((mm, i) => ({ label: mm, value: i })) }),
3309
+ granularity === "quarter" && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MiniSelect, { value: fromQ, onChange: setFromQ, options: QUARTER_ABBR.map((qq, i) => ({ label: qq, value: i })) }),
3288
3310
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MiniSelect, { value: fromYr, onChange: setFromYr, options: yearOptions.map((y) => ({ label: String(y), value: y })) })
3289
3311
  ] })
3290
3312
  ] }),
3291
3313
  /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("label", { className: "flex items-center justify-between gap-2 text-[12px] text-text-secondary", children: [
3292
3314
  "To",
3293
3315
  /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "flex gap-1", children: [
3294
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MiniSelect, { value: toMo, onChange: setToMo, options: MONTH_ABBR.map((mm, i) => ({ label: mm, value: i })) }),
3316
+ granularity === "month" && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MiniSelect, { value: toMo, onChange: setToMo, options: MONTH_ABBR.map((mm, i) => ({ label: mm, value: i })) }),
3317
+ granularity === "quarter" && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MiniSelect, { value: toQ, onChange: setToQ, options: QUARTER_ABBR.map((qq, i) => ({ label: qq, value: i })) }),
3295
3318
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MiniSelect, { value: toYr, onChange: setToYr, options: yearOptions.map((y) => ({ label: String(y), value: y })) })
3296
3319
  ] })
3297
3320
  ] }),
@@ -3320,6 +3343,8 @@ function MultiSelectFilter({
3320
3343
  caption,
3321
3344
  options = [],
3322
3345
  groups,
3346
+ counts,
3347
+ variant = "box",
3323
3348
  selected,
3324
3349
  onChange,
3325
3350
  allLabel = "All",
@@ -3377,14 +3402,18 @@ function MultiSelectFilter({
3377
3402
  return next;
3378
3403
  });
3379
3404
  const toggleGroup = (g) => setDraft((cur) => {
3380
- const ids = groupLeaves(g).map((c) => c.id);
3405
+ const ids = groupLeaves(g).map((c) => c.id).filter((id) => !counts || (counts[id] ?? 0) > 0 || cur.includes(id));
3406
+ if (!ids.length) return cur;
3381
3407
  const allSel = ids.every((id) => cur.includes(id));
3382
3408
  const next = allSel ? cur.filter((id) => !ids.includes(id)) : [.../* @__PURE__ */ new Set([...cur, ...ids])];
3383
3409
  if (next.length < min) return cur;
3384
3410
  return next;
3385
3411
  });
3412
+ const isAvailable = (id) => !counts || (counts[id] ?? 0) > 0 || draft.includes(id);
3413
+ const countFor = (id) => counts?.[id] ?? 0;
3386
3414
  const groupState = (g) => {
3387
- const ids = groupLeaves(g).map((c) => c.id);
3415
+ const ids = groupLeaves(g).map((c) => c.id).filter(isAvailable);
3416
+ if (!ids.length) return "none";
3388
3417
  const sel = ids.filter((id) => draft.includes(id)).length;
3389
3418
  return sel === 0 ? "none" : sel === ids.length ? "all" : "some";
3390
3419
  };
@@ -3392,10 +3421,12 @@ function MultiSelectFilter({
3392
3421
  const isAll = shown.length === 0 || fullIsAll && shown.length === leafOptions.length;
3393
3422
  const value = isAll ? allLabel : shown.length === 1 ? leafOptions.find((o) => o.id === shown[0])?.label ?? `${shown.length} selected` : `${shown.length} selected`;
3394
3423
  const ql = q.trim().toLowerCase();
3395
- const items = options.filter((o) => !ql || o.label.toLowerCase().includes(ql));
3424
+ const items = options.filter((o) => isAvailable(o.id) && (!ql || o.label.toLowerCase().includes(ql)));
3396
3425
  const visibleGroups = groups ? groups.map((g) => {
3397
- if (!ql || g.label.toLowerCase().includes(ql)) return g;
3398
- const kids = g.children.filter((c) => c.label.toLowerCase().includes(ql));
3426
+ const live = g.children.filter((c) => isAvailable(c.id));
3427
+ if (!live.length && !isAvailable(g.id)) return null;
3428
+ if (!ql || g.label.toLowerCase().includes(ql)) return { ...g, children: live };
3429
+ const kids = live.filter((c) => c.label.toLowerCase().includes(ql));
3399
3430
  return kids.length ? { ...g, children: kids } : null;
3400
3431
  }).filter((g) => g !== null) : [];
3401
3432
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: cn("relative", fullWidth && "w-full"), ref, children: [
@@ -3405,7 +3436,7 @@ function MultiSelectFilter({
3405
3436
  type: "button",
3406
3437
  onClick: () => open ? close() : openTray(),
3407
3438
  "aria-expanded": open,
3408
- className: cn(chipTriggerClass(), fullWidth && "w-full justify-between"),
3439
+ className: cn(chipTriggerClass(variant), fullWidth && "w-full justify-between"),
3409
3440
  children: [
3410
3441
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ChipContent, { caption, value, icon }),
3411
3442
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
@@ -3456,7 +3487,8 @@ function MultiSelectFilter({
3456
3487
  className: "flex w-full items-center gap-2.5 rounded-control px-2 py-2 text-left transition-colors hover:bg-subtle",
3457
3488
  children: [
3458
3489
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(TriCheck, { state }),
3459
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: cn("flex-1 text-[14px] text-text-primary", !plainParent && "font-semibold"), children: g.label })
3490
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: cn("flex-1 text-[14px] text-text-primary", !plainParent && "font-semibold"), children: g.label }),
3491
+ counts && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(OptionCount, { n: countFor(g.id) })
3460
3492
  ]
3461
3493
  }
3462
3494
  ),
@@ -3470,7 +3502,8 @@ function MultiSelectFilter({
3470
3502
  className: "flex w-full items-center gap-2.5 rounded-control py-2 pl-9 pr-2 text-left transition-colors hover:bg-subtle",
3471
3503
  children: [
3472
3504
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(TriCheck, { state: checked ? "all" : "none" }),
3473
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "flex-1 text-[14px] text-text-primary", children: c.label })
3505
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "flex-1 text-[14px] text-text-primary", children: c.label }),
3506
+ counts && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(OptionCount, { n: countFor(c.id) })
3474
3507
  ]
3475
3508
  },
3476
3509
  c.id
@@ -3496,7 +3529,8 @@ function MultiSelectFilter({
3496
3529
  children: checked && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react21.Check, { size: 13, strokeWidth: 3 })
3497
3530
  }
3498
3531
  ),
3499
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "flex-1 text-[14px] text-text-primary", children: o.label })
3532
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "flex-1 text-[14px] text-text-primary", children: o.label }),
3533
+ counts && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(OptionCount, { n: countFor(o.id) })
3500
3534
  ]
3501
3535
  },
3502
3536
  o.id
@@ -3537,6 +3571,13 @@ function TriCheck({ state }) {
3537
3571
  }
3538
3572
  );
3539
3573
  }
3574
+ function OptionCount({ n }) {
3575
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "shrink-0 text-[12px] tabular-nums text-text-tertiary", children: [
3576
+ "(",
3577
+ n.toLocaleString(),
3578
+ ")"
3579
+ ] });
3580
+ }
3540
3581
 
3541
3582
  // src/Toggle.tsx
3542
3583
  var import_jsx_runtime36 = require("react/jsx-runtime");