@proveanything/smartlinks-utils-ui 0.9.7 → 0.9.8

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.
@@ -1235,6 +1235,14 @@ interface Props$a<T> {
1235
1235
  headerLabel?: string;
1236
1236
  /** Optional subtitle shown beneath `headerLabel` (e.g. facet name). */
1237
1237
  headerSubtitle?: string;
1238
+ /**
1239
+ * Optional content rendered at the very start of the header's left
1240
+ * cluster, before the title/breadcrumb. Used by the shell to embed the
1241
+ * collection-mode `EditorItemNav` (Back / position / arrows) inline so
1242
+ * it shares the row with the Global / scope chip instead of consuming
1243
+ * its own strip above the editor.
1244
+ */
1245
+ headerLeading?: ReactNode;
1238
1246
  /**
1239
1247
  * Optional technical reference (e.g. productId / SKU) shown as a tiny muted
1240
1248
  * caption pinned to the top-right of the header. Power users can scan it
@@ -1259,7 +1267,7 @@ interface Props$a<T> {
1259
1267
  /** Host-provided icons rendered before save / discard / delete labels. */
1260
1268
  actionIcons?: Partial<Record<RecordsAdminActionKey, RecordsAdminActionIcon>>;
1261
1269
  }
1262
- declare function RecordEditor<T>({ ctx, i18n, children, preview, targeting, targetingControl, bulkActions, footerExtra, onBeforeDelete, headerLabel, headerSubtitle, headerMeta, clipboard, actionLabels, actionIcons, }: Props$a<T>): react_jsx_runtime.JSX.Element;
1270
+ declare function RecordEditor<T>({ ctx, i18n, children, preview, targeting, targetingControl, bulkActions, footerExtra, onBeforeDelete, headerLabel, headerSubtitle, headerMeta, headerLeading, clipboard, actionLabels, actionIcons, }: Props$a<T>): react_jsx_runtime.JSX.Element;
1263
1271
 
1264
1272
  interface InheritanceCtx {
1265
1273
  parentValue?: Record<string, unknown> | null;
@@ -1897,6 +1905,16 @@ interface UseScopeCountsResult {
1897
1905
  error: Error | null;
1898
1906
  /** True when we hit `maxRecords` and stopped paginating. */
1899
1907
  truncated: boolean;
1908
+ /**
1909
+ * Set of distinct `productId`s observed across the scanned records.
1910
+ * Used by the rail to:
1911
+ * • surface "X products configured" as the Products tab badge
1912
+ * (consistent with Global/Rules counts — never catalogue size);
1913
+ * • sort configured products to the top of the browse rail;
1914
+ * • back the Configured/Not-configured filter pills.
1915
+ * Lower bound when `truncated` is true.
1916
+ */
1917
+ productIds: ReadonlySet<string>;
1900
1918
  }
1901
1919
  declare const useScopeCounts: (args: UseScopeCountsArgs) => UseScopeCountsResult;
1902
1920
  /** React Query key factory for cache invalidation from save/delete sites. */
@@ -2283,8 +2301,15 @@ interface Props$1 {
2283
2301
  canPrev: boolean;
2284
2302
  canNext: boolean;
2285
2303
  i18n: Pick<RecordsAdminI18n, 'backToList' | 'prevItem' | 'nextItem'>;
2304
+ /**
2305
+ * When true, render without the row-level border/background/padding so
2306
+ * the nav can sit inline inside another header strip (the shell embeds
2307
+ * it in `RecordEditor`'s header to avoid a dedicated row of whitespace
2308
+ * above the editor).
2309
+ */
2310
+ embedded?: boolean;
2286
2311
  }
2287
- declare const EditorItemNav: ({ label, position, total, onBack, onPrev, onNext, canPrev, canNext, i18n, }: Props$1) => react_jsx_runtime.JSX.Element;
2312
+ declare const EditorItemNav: ({ label, position, total, onBack, onPrev, onNext, canPrev, canNext, i18n, embedded, }: Props$1) => react_jsx_runtime.JSX.Element;
2288
2313
 
2289
2314
  interface Props<T> {
2290
2315
  items: RecordSummary<T>[];
@@ -531,14 +531,20 @@ var useScopeCounts = (args) => {
531
531
  rule: 0,
532
532
  all: 0
533
533
  };
534
- for (const rec of records) counts[classify(rec)] += 1;
534
+ const productIds = /* @__PURE__ */ new Set();
535
+ for (const rec of records) {
536
+ counts[classify(rec)] += 1;
537
+ const pid = rec.productId ?? void 0;
538
+ if (pid) productIds.add(pid);
539
+ }
535
540
  counts.all = records.length;
536
541
  return {
537
542
  counts,
538
543
  total: records.length,
539
544
  isLoading: query.isLoading,
540
545
  error: query.error ?? null,
541
- truncated
546
+ truncated,
547
+ productIds
542
548
  };
543
549
  }, [query.data, query.isLoading, query.error]);
544
550
  return result;
@@ -4245,6 +4251,7 @@ function RecordEditor({
4245
4251
  headerLabel,
4246
4252
  headerSubtitle,
4247
4253
  headerMeta,
4254
+ headerLeading,
4248
4255
  clipboard,
4249
4256
  actionLabels,
4250
4257
  actionIcons
@@ -4261,7 +4268,7 @@ function RecordEditor({
4261
4268
  const s = ctx.scope;
4262
4269
  return Boolean(s?.facetId || s?.productId || s?.variantId || s?.batchId);
4263
4270
  })();
4264
- const hasLeftContent = Boolean(headerLabel) || hasBreadcrumb;
4271
+ const hasLeftContent = Boolean(headerLabel) || hasBreadcrumb || Boolean(headerLeading);
4265
4272
  const hasRightContent = showInherited || showEmpty || Boolean(headerMeta) || Boolean(bulkActions) || Boolean(targetingControl);
4266
4273
  const showHeader = hasLeftContent || hasRightContent;
4267
4274
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
@@ -4271,26 +4278,29 @@ function RecordEditor({
4271
4278
  className: "sticky top-0 z-40 px-5 py-3 border-b flex items-start justify-between gap-3",
4272
4279
  style: { borderColor: "hsl(var(--ra-border))", background: "hsl(var(--ra-surface))" },
4273
4280
  children: [
4274
- /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: headerLabel ? /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
4275
- /* @__PURE__ */ jsx(
4276
- "div",
4277
- {
4278
- className: "text-sm font-medium truncate",
4279
- style: { color: "hsl(var(--ra-text))" },
4280
- title: headerLabel,
4281
- children: headerLabel
4282
- }
4283
- ),
4284
- headerSubtitle && /* @__PURE__ */ jsx(
4285
- "div",
4286
- {
4287
- className: "text-xs truncate",
4288
- style: { color: "hsl(var(--ra-muted-text))" },
4289
- title: headerSubtitle,
4290
- children: headerSubtitle
4291
- }
4292
- )
4293
- ] }) : /* @__PURE__ */ jsx(ScopeBreadcrumb, { scope: ctx.scope }) }),
4281
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 flex items-center gap-3", children: [
4282
+ headerLeading && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: headerLeading }),
4283
+ headerLabel ? /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
4284
+ /* @__PURE__ */ jsx(
4285
+ "div",
4286
+ {
4287
+ className: "text-sm font-medium truncate",
4288
+ style: { color: "hsl(var(--ra-text))" },
4289
+ title: headerLabel,
4290
+ children: headerLabel
4291
+ }
4292
+ ),
4293
+ headerSubtitle && /* @__PURE__ */ jsx(
4294
+ "div",
4295
+ {
4296
+ className: "text-xs truncate",
4297
+ style: { color: "hsl(var(--ra-muted-text))" },
4298
+ title: headerSubtitle,
4299
+ children: headerSubtitle
4300
+ }
4301
+ )
4302
+ ] }) : /* @__PURE__ */ jsx(ScopeBreadcrumb, { scope: ctx.scope })
4303
+ ] }),
4294
4304
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 shrink-0", children: [
4295
4305
  showInherited && /* @__PURE__ */ jsxs(
4296
4306
  "span",
@@ -5759,8 +5769,9 @@ var EditorItemNav = ({
5759
5769
  onNext,
5760
5770
  canPrev,
5761
5771
  canNext,
5762
- i18n
5763
- }) => /* @__PURE__ */ jsxs("div", { className: "ra-item-nav", children: [
5772
+ i18n,
5773
+ embedded
5774
+ }) => /* @__PURE__ */ jsxs("div", { className: "ra-item-nav", "data-embedded": embedded ? "true" : void 0, children: [
5764
5775
  /* @__PURE__ */ jsxs(
5765
5776
  "button",
5766
5777
  {
@@ -5774,12 +5785,7 @@ var EditorItemNav = ({
5774
5785
  ]
5775
5786
  }
5776
5787
  ),
5777
- typeof position === "number" && typeof total === "number" && total > 0 && /* @__PURE__ */ jsxs("span", { className: "ra-item-nav-position", "aria-label": label, children: [
5778
- position,
5779
- " / ",
5780
- total
5781
- ] }),
5782
- /* @__PURE__ */ jsxs("div", { className: "ra-item-nav-arrows", children: [
5788
+ /* @__PURE__ */ jsxs("div", { className: "ra-item-nav-cluster", children: [
5783
5789
  /* @__PURE__ */ jsx(
5784
5790
  "button",
5785
5791
  {
@@ -5792,6 +5798,11 @@ var EditorItemNav = ({
5792
5798
  children: /* @__PURE__ */ jsx(ChevronLeft, { className: "w-4 h-4" })
5793
5799
  }
5794
5800
  ),
5801
+ typeof position === "number" && typeof total === "number" && total > 0 && /* @__PURE__ */ jsxs("span", { className: "ra-item-nav-position", "aria-label": label, children: [
5802
+ position,
5803
+ " / ",
5804
+ total
5805
+ ] }),
5795
5806
  /* @__PURE__ */ jsx(
5796
5807
  "button",
5797
5808
  {
@@ -7665,6 +7676,7 @@ function RecordsAdminShellInner(props) {
7665
7676
  const itemNav = isCollection && selectedItemId && itemPosition && ruleWizardStep === null ? /* @__PURE__ */ jsx(
7666
7677
  EditorItemNav,
7667
7678
  {
7679
+ embedded: true,
7668
7680
  label: editorHeaderLabel,
7669
7681
  position: itemPosition.index + 1,
7670
7682
  total: itemPosition.total,
@@ -7717,6 +7729,7 @@ function RecordsAdminShellInner(props) {
7717
7729
  headerLabel: editorHeaderLabel,
7718
7730
  headerSubtitle: editorHeaderSubtitle,
7719
7731
  headerMeta: editorHeaderMeta,
7732
+ headerLeading: itemNav,
7720
7733
  clipboard: editorClipboard,
7721
7734
  actionLabels,
7722
7735
  actionIcons,
@@ -7735,10 +7748,7 @@ function RecordsAdminShellInner(props) {
7735
7748
  )
7736
7749
  }
7737
7750
  );
7738
- const withNav = (node) => itemNav ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full min-h-0", children: [
7739
- itemNav,
7740
- /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-hidden", children: node })
7741
- ] }) : node;
7751
+ const withNav = (node) => node;
7742
7752
  if (!previewBody) return withNav(baseEditor());
7743
7753
  if (previewMode === "inline") {
7744
7754
  return withNav(baseEditor(
@@ -7841,8 +7851,26 @@ function RecordsAdminShellInner(props) {
7841
7851
  subtitle: pinnedProduct.item?.sku ?? void 0
7842
7852
  }];
7843
7853
  }
7844
- return productBrowse.items.map(productItemToSummary);
7845
- }, [productPinned, contextScope, productBrowse.items, pinnedProduct.item]);
7854
+ const configured = scopeCounts.productIds;
7855
+ const all = productBrowse.items.map(productItemToSummary);
7856
+ const isConfigured = (s) => {
7857
+ const pid = s.scope.productId;
7858
+ return !!pid && configured.has(pid);
7859
+ };
7860
+ if (filter === "configured") return all.filter(isConfigured);
7861
+ if (filter === "empty") return all.filter((s) => !isConfigured(s));
7862
+ const yes = [];
7863
+ const no = [];
7864
+ for (const s of all) (isConfigured(s) ? yes : no).push(s);
7865
+ return [...yes, ...no];
7866
+ }, [
7867
+ productPinned,
7868
+ contextScope,
7869
+ productBrowse.items,
7870
+ pinnedProduct.item,
7871
+ scopeCounts.productIds,
7872
+ filter
7873
+ ]);
7846
7874
  const isRuleTab = activeScope === "rule";
7847
7875
  const isGlobalTab = activeScope === "collection";
7848
7876
  const isAllTab = activeScope === "all";
@@ -8281,10 +8309,14 @@ function RecordsAdminShellInner(props) {
8281
8309
  },
8282
8310
  loading: probe.isLoading,
8283
8311
  counts: {
8284
- // `product` continues to show the catalogue size (browse
8285
- // affordance), not the per-product record count that's
8286
- // the long-standing semantics for the Products tab.
8287
- product: productBrowse.items.length,
8312
+ // Products badge counts DISTINCT products that have at
8313
+ // least one custom record — same semantics as Global /
8314
+ // Rules. Catalogue size (which can be 1k–10k+) was
8315
+ // misleading: "Products: 247" implied 247 customised
8316
+ // products when in fact zero might be configured. Falls
8317
+ // back to a lower bound when scope-counts truncated at
8318
+ // the hard cap.
8319
+ product: scopeCounts.productIds.size,
8288
8320
  // The remaining tabs show actual record counts so hidden
8289
8321
  // state (e.g. a rule-scoped competition) is visible from
8290
8322
  // any tab. `useScopeCounts` returns 0 while loading, which
@@ -8365,6 +8397,27 @@ function RecordsAdminShellInner(props) {
8365
8397
  i18n
8366
8398
  }
8367
8399
  ),
8400
+ isProductTab && !productPinned && (() => {
8401
+ const cfg = scopeCounts.productIds;
8402
+ let configured = 0;
8403
+ for (const p of productBrowse.items) if (cfg.has(p.id)) configured += 1;
8404
+ const total = productBrowse.items.length;
8405
+ return /* @__PURE__ */ jsx(
8406
+ StatusFilterPills,
8407
+ {
8408
+ value: filter,
8409
+ onChange: setFilter,
8410
+ counts: {
8411
+ all: total,
8412
+ configured,
8413
+ partial: 0,
8414
+ empty: total - configured
8415
+ },
8416
+ hideZero: ["partial"],
8417
+ i18n
8418
+ }
8419
+ );
8420
+ })(),
8368
8421
  isRuleTab && /* @__PURE__ */ jsx(
8369
8422
  RuleFilterChips,
8370
8423
  {