@proveanything/smartlinks-utils-ui 0.10.0 → 0.10.2

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.
@@ -2427,8 +2427,17 @@ interface Props<T> {
2427
2427
  selectedItemId?: string;
2428
2428
  isLoading: boolean;
2429
2429
  error: Error | null;
2430
- /** Retained for API stability back nav now lives in EditorItemNav. */
2430
+ /** Back to the items list (mirrors the editor-header "Back to list"). */
2431
2431
  onBack?: () => void;
2432
+ /**
2433
+ * Optional context label rendered next to the back arrow. Two parts:
2434
+ * - `kind` — short noun ("Rule", "Product", "Global", "Filtered")
2435
+ * - `summary` — the specific value when relevant (rule summary, product
2436
+ * name, …); omitted for kinds that need no qualifier.
2437
+ * The shell composes both from the active scope + selection.
2438
+ */
2439
+ contextKind?: string;
2440
+ contextSummary?: string | null;
2432
2441
  onSelect: (itemId: string) => void;
2433
2442
  /**
2434
2443
  * Set of keys (recordIds + scope refs) currently dirty in the shell-level
@@ -2439,9 +2448,9 @@ interface Props<T> {
2439
2448
  dirtyKeys?: ReadonlySet<string>;
2440
2449
  /** Subset of `dirtyKeys` whose last save attempt failed. */
2441
2450
  errorKeys?: ReadonlySet<string>;
2442
- i18n: Pick<RecordsAdminI18n, 'backToScopes' | 'siblingsHeading' | 'noItemsTitle' | 'noItemsBody'>;
2451
+ i18n: Pick<RecordsAdminI18n, 'backToScopes' | 'siblingsHeading' | 'noItemsTitle' | 'noItemsBody' | 'backToList'>;
2443
2452
  }
2444
- declare function SiblingRail<T>({ items, selectedItemId, isLoading, error, onSelect, dirtyKeys, errorKeys, i18n, }: Props<T>): react_jsx_runtime.JSX.Element;
2453
+ declare function SiblingRail<T>({ items, selectedItemId, isLoading, error, onBack, onSelect, contextKind, contextSummary, dirtyKeys, errorKeys, i18n, }: Props<T>): react_jsx_runtime.JSX.Element;
2445
2454
 
2446
2455
  interface ClipboardEntry<T = unknown> {
2447
2456
  value: T;
@@ -5950,67 +5950,92 @@ function SiblingRail({
5950
5950
  selectedItemId,
5951
5951
  isLoading,
5952
5952
  error,
5953
+ onBack,
5953
5954
  onSelect,
5955
+ contextKind,
5956
+ contextSummary,
5954
5957
  dirtyKeys,
5955
5958
  errorKeys,
5956
5959
  i18n
5957
5960
  }) {
5958
5961
  const ruleLabelLookup = useRuleLabelLookup();
5959
- return /* @__PURE__ */ jsx("div", { className: "ra-sibling-rail", children: /* @__PURE__ */ jsxs("div", { className: "ra-sibling-body", children: [
5960
- isLoading && /* @__PURE__ */ jsx(LoadingState, {}),
5961
- !isLoading && error && /* @__PURE__ */ jsx(ErrorState, { error }),
5962
- !isLoading && !error && items.length === 0 && /* @__PURE__ */ jsx(EmptyState, { title: i18n.noItemsTitle, body: i18n.noItemsBody }),
5963
- !isLoading && !error && items.length > 0 && /* @__PURE__ */ jsx("ul", { className: "ra-sibling-list", children: items.map((item, idx) => {
5964
- const id = item.itemId ?? "";
5965
- const key = item.id ?? (id || anchorKey(item.scope) || `pos:${idx}`);
5966
- const akey = anchorKey(item.scope);
5967
- const selected = selectedItemId === id;
5968
- const isDirty = !!(id && dirtyKeys?.has(id) || akey && dirtyKeys?.has(akey));
5969
- const hasError = !!(id && errorKeys?.has(id) || akey && errorKeys?.has(akey));
5970
- const ruleClauses = item.facetRule ? summarizeFacetRule(item.facetRule, ruleLabelLookup) : [];
5971
- const isTargeted = ruleClauses.length > 0;
5972
- const ruleSummary = isTargeted ? ruleClauses.map((c) => c.label).join(" \xB7 ") : null;
5973
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
5962
+ return /* @__PURE__ */ jsxs("div", { className: "ra-sibling-rail", children: [
5963
+ (onBack || contextKind) && /* @__PURE__ */ jsxs("div", { className: "ra-sibling-context", children: [
5964
+ onBack && /* @__PURE__ */ jsx(
5974
5965
  "button",
5975
5966
  {
5976
5967
  type: "button",
5977
- onClick: () => onSelect(id),
5978
- className: "ra-row",
5979
- "data-selected": selected,
5980
- children: [
5981
- /* @__PURE__ */ jsxs("div", { className: "ra-row-body", children: [
5982
- /* @__PURE__ */ jsx("div", { className: "ra-row-title", children: item.label }),
5983
- item.subtitle && /* @__PURE__ */ jsx("div", { className: "ra-row-sub", children: item.subtitle })
5984
- ] }),
5985
- isTargeted && /* @__PURE__ */ jsx(
5986
- "span",
5987
- {
5988
- className: "ra-row-rule-pip",
5989
- title: ruleSummary ? `Targeted: ${ruleSummary}` : "This item has targeting rules",
5990
- "aria-label": ruleSummary ? `Targeted: ${ruleSummary}` : "This item has targeting rules",
5991
- children: /* @__PURE__ */ jsx(Target, { className: "w-3 h-3", "aria-hidden": "true" })
5992
- }
5993
- ),
5994
- hasError ? /* @__PURE__ */ jsx(
5995
- "span",
5996
- {
5997
- className: "ra-error-pip",
5998
- title: "Save failed",
5999
- "aria-label": "Save failed"
6000
- }
6001
- ) : isDirty ? /* @__PURE__ */ jsx(
6002
- "span",
6003
- {
6004
- className: "ra-dirty-pip",
6005
- title: "Unsaved changes",
6006
- "aria-label": "Unsaved changes"
6007
- }
6008
- ) : null
6009
- ]
5968
+ onClick: onBack,
5969
+ className: "ra-sibling-context-back",
5970
+ "aria-label": i18n.backToList,
5971
+ title: i18n.backToList,
5972
+ children: /* @__PURE__ */ jsx(ArrowLeft, { className: "w-3.5 h-3.5", "aria-hidden": "true" })
6010
5973
  }
6011
- ) }, key);
6012
- }) })
6013
- ] }) });
5974
+ ),
5975
+ contextKind && /* @__PURE__ */ jsxs("div", { className: "ra-sibling-context-label", title: contextSummary ? `${contextKind} \xB7 ${contextSummary}` : contextKind, children: [
5976
+ /* @__PURE__ */ jsx("span", { className: "ra-sibling-context-kind", children: contextKind }),
5977
+ contextSummary && /* @__PURE__ */ jsxs(Fragment, { children: [
5978
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ra-sibling-context-sep", children: "\xB7" }),
5979
+ /* @__PURE__ */ jsx("span", { className: "ra-sibling-context-summary", children: contextSummary })
5980
+ ] })
5981
+ ] })
5982
+ ] }),
5983
+ /* @__PURE__ */ jsxs("div", { className: "ra-sibling-body", children: [
5984
+ isLoading && /* @__PURE__ */ jsx(LoadingState, {}),
5985
+ !isLoading && error && /* @__PURE__ */ jsx(ErrorState, { error }),
5986
+ !isLoading && !error && items.length === 0 && /* @__PURE__ */ jsx(EmptyState, { title: i18n.noItemsTitle, body: i18n.noItemsBody }),
5987
+ !isLoading && !error && items.length > 0 && /* @__PURE__ */ jsx("ul", { className: "ra-sibling-list", children: items.map((item, idx) => {
5988
+ const id = item.itemId ?? "";
5989
+ const key = item.id ?? (id || anchorKey(item.scope) || `pos:${idx}`);
5990
+ const akey = anchorKey(item.scope);
5991
+ const selected = selectedItemId === id;
5992
+ const isDirty = !!(id && dirtyKeys?.has(id) || akey && dirtyKeys?.has(akey));
5993
+ const hasError = !!(id && errorKeys?.has(id) || akey && errorKeys?.has(akey));
5994
+ const ruleClauses = item.facetRule ? summarizeFacetRule(item.facetRule, ruleLabelLookup) : [];
5995
+ const isTargeted = ruleClauses.length > 0;
5996
+ const ruleSummary = isTargeted ? ruleClauses.map((c) => c.label).join(" \xB7 ") : null;
5997
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
5998
+ "button",
5999
+ {
6000
+ type: "button",
6001
+ onClick: () => onSelect(id),
6002
+ className: "ra-row",
6003
+ "data-selected": selected,
6004
+ children: [
6005
+ /* @__PURE__ */ jsxs("div", { className: "ra-row-body", children: [
6006
+ /* @__PURE__ */ jsx("div", { className: "ra-row-title", children: item.label }),
6007
+ item.subtitle && /* @__PURE__ */ jsx("div", { className: "ra-row-sub", children: item.subtitle })
6008
+ ] }),
6009
+ isTargeted && /* @__PURE__ */ jsx(
6010
+ "span",
6011
+ {
6012
+ className: "ra-row-rule-pip",
6013
+ title: ruleSummary ? `Targeted: ${ruleSummary}` : "This item has targeting rules",
6014
+ "aria-label": ruleSummary ? `Targeted: ${ruleSummary}` : "This item has targeting rules",
6015
+ children: /* @__PURE__ */ jsx(Target, { className: "w-3 h-3", "aria-hidden": "true" })
6016
+ }
6017
+ ),
6018
+ hasError ? /* @__PURE__ */ jsx(
6019
+ "span",
6020
+ {
6021
+ className: "ra-error-pip",
6022
+ title: "Save failed",
6023
+ "aria-label": "Save failed"
6024
+ }
6025
+ ) : isDirty ? /* @__PURE__ */ jsx(
6026
+ "span",
6027
+ {
6028
+ className: "ra-dirty-pip",
6029
+ title: "Unsaved changes",
6030
+ "aria-label": "Unsaved changes"
6031
+ }
6032
+ ) : null
6033
+ ]
6034
+ }
6035
+ ) }, key);
6036
+ }) })
6037
+ ] })
6038
+ ] });
6014
6039
  }
6015
6040
  var TONE_ICON = {
6016
6041
  info: Lightbulb,
@@ -6948,6 +6973,60 @@ var SaveAllProgress = ({
6948
6973
  }
6949
6974
  );
6950
6975
  };
6976
+ function PreviewReopenPill({ anchorRef, onClick, ariaLabel, title, children }) {
6977
+ const [pos, setPos] = useState(null);
6978
+ const rafRef = useRef(null);
6979
+ useLayoutEffect(() => {
6980
+ const el = anchorRef.current;
6981
+ if (!el || typeof window === "undefined") return;
6982
+ const measure = () => {
6983
+ if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
6984
+ rafRef.current = requestAnimationFrame(() => {
6985
+ const rect = el.getBoundingClientRect();
6986
+ if (rect.width === 0 && rect.height === 0) return;
6987
+ setPos({
6988
+ top: rect.top + rect.height / 2,
6989
+ right: Math.max(0, window.innerWidth - rect.right)
6990
+ });
6991
+ });
6992
+ };
6993
+ measure();
6994
+ const ro = new ResizeObserver(measure);
6995
+ ro.observe(el);
6996
+ ro.observe(document.body);
6997
+ window.addEventListener("resize", measure);
6998
+ window.addEventListener("scroll", measure, true);
6999
+ return () => {
7000
+ ro.disconnect();
7001
+ window.removeEventListener("resize", measure);
7002
+ window.removeEventListener("scroll", measure, true);
7003
+ if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
7004
+ };
7005
+ }, [anchorRef]);
7006
+ if (typeof document === "undefined" || !pos) return null;
7007
+ return createPortal(
7008
+ /* @__PURE__ */ jsx(
7009
+ "button",
7010
+ {
7011
+ type: "button",
7012
+ className: "ra-shell ra-preview-reopen ra-preview-reopen--floating",
7013
+ onClick,
7014
+ "aria-label": ariaLabel,
7015
+ title,
7016
+ style: {
7017
+ position: "fixed",
7018
+ top: pos.top,
7019
+ right: pos.right,
7020
+ // Pull half the pill width out into the gutter so it visually
7021
+ // anchors *to* the editor edge rather than sitting inside it.
7022
+ transform: "translate(50%, -50%)"
7023
+ },
7024
+ children
7025
+ }
7026
+ ),
7027
+ document.body
7028
+ );
7029
+ }
6951
7030
  var EditorMountPool = ({
6952
7031
  renderSlot,
6953
7032
  keepMountedHidden = true,
@@ -7700,6 +7779,7 @@ function RecordsAdminShellInner(props) {
7700
7779
  ]);
7701
7780
  const onLeftSelectRef = useRef(null);
7702
7781
  const onCreateItemDraftRef = useRef(null);
7782
+ const previewReopenAnchorRef = useRef(null);
7703
7783
  const { runWithGuard } = useDirtyNavigation({
7704
7784
  strategy: dirtyStrategy,
7705
7785
  isDirty: editorCtx.isDirty,
@@ -7807,6 +7887,7 @@ function RecordsAdminShellInner(props) {
7807
7887
  const itemViewCtx = baseItemViewCtx;
7808
7888
  const renderEditorWithPreview = () => {
7809
7889
  if (!editingTargetScope) return null;
7890
+ const previewAnchorRef = previewReopenAnchorRef;
7810
7891
  const previewBody = renderPreview && effectivePreviewScope ? renderPreview({ resolved: editorCtx.value, previewScope: effectivePreviewScope }) : null;
7811
7892
  const scopePicker = previewScopePicker && effectivePreviewScope ? /* @__PURE__ */ jsx(
7812
7893
  PreviewScopePicker,
@@ -7908,15 +7989,14 @@ function RecordsAdminShellInner(props) {
7908
7989
  if (previewMode === "side") {
7909
7990
  if (!sidePreviewOpen) {
7910
7991
  return withNav(
7911
- /* @__PURE__ */ jsxs("div", { className: "relative h-full", children: [
7992
+ /* @__PURE__ */ jsxs("div", { className: "relative h-full", ref: previewAnchorRef, children: [
7912
7993
  baseEditor(),
7913
7994
  /* @__PURE__ */ jsxs(
7914
- "button",
7995
+ PreviewReopenPill,
7915
7996
  {
7916
- type: "button",
7917
- className: "ra-preview-reopen",
7997
+ anchorRef: previewAnchorRef,
7918
7998
  onClick: () => setSidePreviewOpen(true),
7919
- "aria-label": i18n.openPreview,
7999
+ ariaLabel: i18n.openPreview,
7920
8000
  title: i18n.openPreview,
7921
8001
  children: [
7922
8002
  /* @__PURE__ */ jsx(Eye, { "aria-hidden": "true" }),
@@ -8438,6 +8518,8 @@ function RecordsAdminShellInner(props) {
8438
8518
  onSelect: onItemOpen,
8439
8519
  dirtyKeys,
8440
8520
  errorKeys,
8521
+ contextKind: activeScope === "rule" ? "Rule" : activeScope === "product" ? "Product" : activeScope === "collection" ? "Global" : activeScope === "all" ? "All records" : activeScope === "variant" ? "Variant" : activeScope === "batch" ? "Batch" : activeScope === "facet" ? "Facet" : void 0,
8522
+ contextSummary: activeScope === "rule" ? activeRuleSummary : activeScope === "product" ? editorHeaderLabel ?? null : null,
8441
8523
  i18n
8442
8524
  }
8443
8525
  ) : /* @__PURE__ */ jsxs(Fragment, { children: [