@proveanything/smartlinks-utils-ui 0.12.7 → 0.12.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.
@@ -7,7 +7,7 @@ import { assertComponentStylesLoaded } from '../../chunk-OLYC54YT.js';
7
7
  import '../../chunk-5UQQYXCX.js';
8
8
  import { cn } from '../../chunk-L7FQ52F5.js';
9
9
  import { createContext, useMemo, useState, useEffect, useCallback, useRef, isValidElement, useLayoutEffect, useContext, useSyncExternalStore, createElement } from 'react';
10
- import { ChevronDown, Database, Lightbulb, SearchX, Inbox, LayoutGrid, Eye, MoreHorizontal, Download, Upload, Trash2, Copy, Pencil, Plus, CircleDashed, ArrowDownLeft, CheckCircle2, List, SlidersHorizontal, Globe, Tag, Boxes, Layers, Package, Target, Check, Rows3, ChevronRight, Eraser, FilePlus2, CopyPlus, ClipboardPaste, Box, X, Search, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, CornerDownLeft, Circle, ArrowUpDown, ArrowUp, ArrowDown, MinusCircle, XCircle, AlertCircle, Undo2, Save, Loader2, Archive, ArrowRight, Globe2, Sparkles, Settings2 } from 'lucide-react';
10
+ import { ChevronDown, Database, Lightbulb, SearchX, Inbox, LayoutGrid, Eye, MoreHorizontal, Download, Upload, Trash2, Copy, Pencil, Plus, CircleDashed, ArrowDownLeft, CheckCircle2, List, SlidersHorizontal, Globe, Tag, Boxes, Layers, Package, Target, Check, Rows3, ChevronRight, Eraser, FilePlus2, CopyPlus, ClipboardPaste, Box, X, Search, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, PanelLeftClose, CornerDownLeft, Circle, ArrowUpDown, ArrowUp, ArrowDown, MinusCircle, XCircle, AlertCircle, Undo2, Save, Loader2, Archive, ArrowRight, Globe2, Sparkles, Settings2 } from 'lucide-react';
11
11
  import { useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
12
12
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
13
13
  import { createPortal } from 'react-dom';
@@ -183,6 +183,8 @@ var DEFAULT_I18N = {
183
183
  editor: "Editor",
184
184
  closePreview: "Close preview",
185
185
  openPreview: "Open preview",
186
+ closeList: "Hide list",
187
+ openList: "Show list",
186
188
  previewAs: "Preview as",
187
189
  previewAsDefault: "Same as edited",
188
190
  confirmDelete: "Confirm delete",
@@ -2212,10 +2214,54 @@ function useShellClipboard(args) {
2212
2214
  };
2213
2215
  }
2214
2216
  var useShellPreviewPane = (args) => {
2215
- const { editingScope, activeScope, selectedProductId, productBrowseItems } = args;
2217
+ const {
2218
+ editingScope,
2219
+ activeScope,
2220
+ selectedProductId,
2221
+ productBrowseItems,
2222
+ pinPreviewMinWidth = 1400,
2223
+ preferenceStorageKey = "smartlinks-ui:recordsAdmin:sidePreviewOpen"
2224
+ } = args;
2216
2225
  const [previewScope, setPreviewScope] = useState(null);
2217
2226
  const [drawerOpen, setDrawerOpen] = useState(false);
2218
- const [sidePreviewOpen, setSidePreviewOpen] = useState(true);
2227
+ const readStoredPref = () => {
2228
+ if (typeof window === "undefined") return null;
2229
+ try {
2230
+ const raw = window.sessionStorage.getItem(preferenceStorageKey);
2231
+ if (raw === "1") return true;
2232
+ if (raw === "0") return false;
2233
+ } catch {
2234
+ }
2235
+ return null;
2236
+ };
2237
+ const initialIsWide = typeof window !== "undefined" ? window.innerWidth >= pinPreviewMinWidth : true;
2238
+ const [isWide, setIsWide] = useState(initialIsWide);
2239
+ const userPrefRef = useRef(readStoredPref());
2240
+ const [sidePreviewOpen, setSidePreviewOpenState] = useState(
2241
+ userPrefRef.current ?? initialIsWide
2242
+ );
2243
+ useEffect(() => {
2244
+ if (typeof window === "undefined") return;
2245
+ const mql = window.matchMedia(`(min-width: ${pinPreviewMinWidth}px)`);
2246
+ const onChange = () => {
2247
+ const wide = mql.matches;
2248
+ setIsWide(wide);
2249
+ if (userPrefRef.current === null) {
2250
+ setSidePreviewOpenState(wide);
2251
+ }
2252
+ };
2253
+ onChange();
2254
+ mql.addEventListener("change", onChange);
2255
+ return () => mql.removeEventListener("change", onChange);
2256
+ }, [pinPreviewMinWidth]);
2257
+ const setSidePreviewOpen = useCallback((next) => {
2258
+ userPrefRef.current = next;
2259
+ try {
2260
+ window.sessionStorage.setItem(preferenceStorageKey, next ? "1" : "0");
2261
+ } catch {
2262
+ }
2263
+ setSidePreviewOpenState(next);
2264
+ }, [preferenceStorageKey]);
2219
2265
  useEffect(() => {
2220
2266
  if (!editingScope) return;
2221
2267
  setPreviewScope((cur) => cur === null ? editingScope : cur);
@@ -2249,6 +2295,7 @@ var useShellPreviewPane = (args) => {
2249
2295
  setDrawerOpen,
2250
2296
  sidePreviewOpen,
2251
2297
  setSidePreviewOpen,
2298
+ isPreviewNarrow: !isWide,
2252
2299
  editorHeaderLabel,
2253
2300
  editorHeaderSubtitle,
2254
2301
  editorHeaderMeta
@@ -8606,14 +8653,21 @@ var SaveAllProgress = ({
8606
8653
  }
8607
8654
  );
8608
8655
  };
8609
- function PreviewReopenPill({ anchorRef, onClick, ariaLabel, title, children }) {
8656
+ function PreviewReopenPill({
8657
+ anchorRef,
8658
+ onClick,
8659
+ ariaLabel,
8660
+ title,
8661
+ children,
8662
+ side = "right"
8663
+ }) {
8610
8664
  const [pos, setPos] = useState(null);
8611
8665
  const rafRef = useRef(null);
8612
8666
  useLayoutEffect(() => {
8613
8667
  const el = anchorRef.current;
8614
8668
  if (typeof window === "undefined") return;
8615
8669
  if (!el) {
8616
- setPos({ top: window.innerHeight / 2, right: 8 });
8670
+ setPos({ top: window.innerHeight / 2, right: 8, left: 8 });
8617
8671
  return;
8618
8672
  }
8619
8673
  const measure = () => {
@@ -8621,12 +8675,13 @@ function PreviewReopenPill({ anchorRef, onClick, ariaLabel, title, children }) {
8621
8675
  rafRef.current = requestAnimationFrame(() => {
8622
8676
  const rect = el.getBoundingClientRect();
8623
8677
  if (rect.width === 0 && rect.height === 0) {
8624
- setPos({ top: window.innerHeight / 2, right: 8 });
8678
+ setPos({ top: window.innerHeight / 2, right: 8, left: 8 });
8625
8679
  return;
8626
8680
  }
8627
8681
  setPos({
8628
8682
  top: rect.top + rect.height / 2,
8629
- right: Math.max(0, window.innerWidth - rect.right)
8683
+ right: Math.max(0, window.innerWidth - rect.right),
8684
+ left: Math.max(0, rect.left)
8630
8685
  });
8631
8686
  });
8632
8687
  };
@@ -8646,7 +8701,8 @@ function PreviewReopenPill({ anchorRef, onClick, ariaLabel, title, children }) {
8646
8701
  if (typeof document === "undefined") return null;
8647
8702
  const effectivePos = pos ?? {
8648
8703
  top: typeof window !== "undefined" ? window.innerHeight / 2 : 200,
8649
- right: 8
8704
+ right: 8,
8705
+ left: 8
8650
8706
  };
8651
8707
  return createPortal(
8652
8708
  /* @__PURE__ */ jsx(
@@ -8660,10 +8716,10 @@ function PreviewReopenPill({ anchorRef, onClick, ariaLabel, title, children }) {
8660
8716
  style: {
8661
8717
  position: "fixed",
8662
8718
  top: effectivePos.top,
8663
- right: effectivePos.right,
8719
+ ...side === "right" ? { right: effectivePos.right } : { left: effectivePos.left },
8664
8720
  // Pull half the pill width out into the gutter so it visually
8665
8721
  // anchors *to* the editor edge rather than sitting inside it.
8666
- transform: "translate(50%, -50%)"
8722
+ transform: side === "right" ? "translate(50%, -50%)" : "translate(-50%, -50%)"
8667
8723
  },
8668
8724
  children
8669
8725
  }
@@ -9696,6 +9752,25 @@ function RecordsAdminShellInner(props) {
9696
9752
  const ruleCatalogueRef = useRef([]);
9697
9753
  const onCreateRuleFromClipboardRef = useRef(null);
9698
9754
  const previewReopenAnchorRef = useRef(null);
9755
+ const railReopenAnchorRef = useRef(null);
9756
+ const RAIL_OPEN_STORAGE_KEY = "smartlinks-ui:recordsAdmin:railOpen";
9757
+ const [railOpen, setRailOpenState] = useState(() => {
9758
+ if (typeof window === "undefined") return true;
9759
+ try {
9760
+ const raw = window.sessionStorage.getItem(RAIL_OPEN_STORAGE_KEY);
9761
+ if (raw === "0") return false;
9762
+ if (raw === "1") return true;
9763
+ } catch {
9764
+ }
9765
+ return true;
9766
+ });
9767
+ const setRailOpen = useCallback((next) => {
9768
+ try {
9769
+ window.sessionStorage.setItem(RAIL_OPEN_STORAGE_KEY, next ? "1" : "0");
9770
+ } catch {
9771
+ }
9772
+ setRailOpenState(next);
9773
+ }, []);
9699
9774
  const { runWithGuard } = useDirtyNavigation({
9700
9775
  strategy: dirtyStrategy,
9701
9776
  isDirty: editorCtx.isDirty,
@@ -9726,6 +9801,7 @@ function RecordsAdminShellInner(props) {
9726
9801
  setDrawerOpen,
9727
9802
  sidePreviewOpen,
9728
9803
  setSidePreviewOpen,
9804
+ isPreviewNarrow,
9729
9805
  editorHeaderLabel: editorHeaderLabelRaw,
9730
9806
  editorHeaderSubtitle,
9731
9807
  editorHeaderMeta
@@ -10047,6 +10123,23 @@ function RecordsAdminShellInner(props) {
10047
10123
  ] })
10048
10124
  );
10049
10125
  }
10126
+ if (isPreviewNarrow) {
10127
+ return withNav(
10128
+ /* @__PURE__ */ jsxs("div", { className: "relative h-full", ref: previewAnchorRef, children: [
10129
+ baseEditor(),
10130
+ /* @__PURE__ */ jsx(
10131
+ DrawerPreview,
10132
+ {
10133
+ open: true,
10134
+ onClose: () => setSidePreviewOpen(false),
10135
+ label: i18n.preview,
10136
+ scopePicker,
10137
+ children: previewBody
10138
+ }
10139
+ )
10140
+ ] })
10141
+ );
10142
+ }
10050
10143
  return withNav(
10051
10144
  /* @__PURE__ */ jsxs("div", { className: "grid h-full", style: { gridTemplateColumns: "minmax(0, 1fr) minmax(280px, 420px)" }, children: [
10052
10145
  /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: baseEditor() }),
@@ -10758,323 +10851,351 @@ function RecordsAdminShellInner(props) {
10758
10851
  {
10759
10852
  className: "flex-1 grid border-t overflow-hidden",
10760
10853
  style: {
10761
- gridTemplateColumns: railHidden ? "1fr" : "minmax(260px, 320px) 1fr",
10854
+ gridTemplateColumns: railHidden || !railOpen ? "1fr" : "minmax(260px, 320px) 1fr",
10762
10855
  borderColor: "hsl(var(--ra-border))"
10763
10856
  },
10764
10857
  children: [
10765
- !railHidden && /* @__PURE__ */ jsx("aside", { className: "border-r overflow-hidden flex flex-col", style: { borderColor: "hsl(var(--ra-border))", background: "hsl(var(--ra-surface))" }, children: isCollection && selectedItemId && collectionRailMode === "siblings" && ruleWizardStep === null ? /* @__PURE__ */ jsx(
10766
- SiblingRail,
10767
- {
10768
- items: scopedCollectionItemsList,
10769
- selectedItemId,
10770
- isLoading: collectionItems.isLoading,
10771
- error: collectionItems.error,
10772
- onBack: onItemBack,
10773
- onSelect: onItemOpen,
10774
- onCreate: onItemCreate,
10775
- itemNoun: itemNounLabel,
10776
- dirtyKeys,
10777
- errorKeys,
10778
- hasNextPage: !!collectionItems.hasNextPage,
10779
- isFetchingNextPage: !!collectionItems.isFetchingNextPage,
10780
- onLoadMore: () => {
10781
- void collectionItems.fetchNextPage();
10782
- },
10783
- rowClipboard,
10784
- rowActions: wrappedRecordActions,
10785
- groupByLifecycle: true,
10786
- lifecycle: lifecycleConfig,
10787
- contextKind: isLifecycleRailEarly && lifecycleBucketLabel ? lifecycleBucketLabel : activeScope === "rule" ? "Rule" : activeScope === "product" ? "Product" : activeScope === "collection" ? "Global" : activeScope === "all" ? "All records" : activeScope === "variant" ? "Variant" : activeScope === "batch" ? "Batch" : activeScope === "facet" ? "Facet" : void 0,
10788
- contextSummary: isLifecycleRailEarly && lifecycleBucketLabel ? `${scopedCollectionItemsList.length} ${itemNounLabel}${scopedCollectionItemsList.length === 1 ? "" : "s"}` : activeScope === "rule" ? activeRuleSummary : activeScope === "product" ? editorHeaderLabel ?? null : null,
10789
- i18n
10790
- }
10791
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
10792
- /* @__PURE__ */ jsx("div", { className: "px-1.5 py-2", children: /* @__PURE__ */ jsx(
10793
- ScopeTabs,
10858
+ !railHidden && railOpen && /* @__PURE__ */ jsxs("aside", { className: "border-r overflow-hidden flex flex-col relative", style: { borderColor: "hsl(var(--ra-border))", background: "hsl(var(--ra-surface))" }, children: [
10859
+ /* @__PURE__ */ jsx(
10860
+ "button",
10794
10861
  {
10795
- scopes: effectiveTopLevelScopes,
10796
- active: activeScope,
10797
- onChange: (s) => {
10798
- void runWithGuard(() => {
10799
- onTelemetry?.({ type: "scope.change", recordType, from: activeScope, to: s });
10800
- if (ruleWizardStep !== null) {
10801
- setRuleWizardStep(null);
10802
- setRuleWizardRule(null);
10803
- setDraftKind(null);
10804
- }
10805
- setActiveScope(s);
10806
- });
10807
- },
10808
- loading: probe.isLoading,
10809
- counts: {
10810
- // Products badge counts DISTINCT products that have at
10811
- // least one custom record — same semantics as Global /
10812
- // Rules. Catalogue size (which can be 1k–10k+) was
10813
- // misleading: "Products: 247" implied 247 customised
10814
- // products when in fact zero might be configured. Falls
10815
- // back to a lower bound when scope-counts truncated at
10816
- // the hard cap.
10817
- product: scopeCounts.productIds.size,
10818
- // The remaining tabs show actual record counts so hidden
10819
- // state (e.g. a rule-scoped competition) is visible from
10820
- // any tab. `useScopeCounts` returns 0 while loading, which
10821
- // is fine — the badges just appear once data lands.
10822
- collection: scopeCounts.counts.collection,
10823
- rule: scopeCounts.counts.rule,
10824
- variant: scopeCounts.counts.variant,
10825
- batch: scopeCounts.counts.batch,
10826
- facet: scopeCounts.counts.facet,
10827
- all: scopeCounts.counts.all
10862
+ type: "button",
10863
+ onClick: () => setRailOpen(false),
10864
+ "aria-label": i18n.closeList,
10865
+ title: i18n.closeList,
10866
+ className: "absolute top-1.5 right-1.5 z-10 p-1 rounded hover:bg-[hsl(var(--ra-muted))]",
10867
+ style: { color: "hsl(var(--ra-muted-text))" },
10868
+ children: /* @__PURE__ */ jsx(PanelLeftClose, { className: "w-3.5 h-3.5" })
10869
+ }
10870
+ ),
10871
+ isCollection && selectedItemId && collectionRailMode === "siblings" && ruleWizardStep === null ? /* @__PURE__ */ jsx(
10872
+ SiblingRail,
10873
+ {
10874
+ items: scopedCollectionItemsList,
10875
+ selectedItemId,
10876
+ isLoading: collectionItems.isLoading,
10877
+ error: collectionItems.error,
10878
+ onBack: onItemBack,
10879
+ onSelect: onItemOpen,
10880
+ onCreate: onItemCreate,
10881
+ itemNoun: itemNounLabel,
10882
+ dirtyKeys,
10883
+ errorKeys,
10884
+ hasNextPage: !!collectionItems.hasNextPage,
10885
+ isFetchingNextPage: !!collectionItems.isFetchingNextPage,
10886
+ onLoadMore: () => {
10887
+ void collectionItems.fetchNextPage();
10828
10888
  },
10829
- tooltips: { rule: i18n.rulesTabTooltip },
10830
- icons: icons.scope
10889
+ rowClipboard,
10890
+ rowActions: wrappedRecordActions,
10891
+ groupByLifecycle: true,
10892
+ lifecycle: lifecycleConfig,
10893
+ contextKind: isLifecycleRailEarly && lifecycleBucketLabel ? lifecycleBucketLabel : activeScope === "rule" ? "Rule" : activeScope === "product" ? "Product" : activeScope === "collection" ? "Global" : activeScope === "all" ? "All records" : activeScope === "variant" ? "Variant" : activeScope === "batch" ? "Batch" : activeScope === "facet" ? "Facet" : void 0,
10894
+ contextSummary: isLifecycleRailEarly && lifecycleBucketLabel ? `${scopedCollectionItemsList.length} ${itemNounLabel}${scopedCollectionItemsList.length === 1 ? "" : "s"}` : activeScope === "rule" ? activeRuleSummary : activeScope === "product" ? editorHeaderLabel ?? null : null,
10895
+ i18n
10831
10896
  }
10832
- ) }),
10833
- /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-2.5 border-b", style: { borderColor: "hsl(var(--ra-border))" }, children: [
10834
- isRuleTab && /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1.5", children: /* @__PURE__ */ jsxs(
10835
- "button",
10897
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
10898
+ /* @__PURE__ */ jsx("div", { className: "px-1.5 py-2", children: /* @__PURE__ */ jsx(
10899
+ ScopeTabs,
10836
10900
  {
10837
- type: "button",
10838
- onClick: () => onCreateRule(),
10839
- className: "ra-btn w-full",
10840
- "data-variant": "primary",
10841
- "aria-label": "New rule",
10842
- children: [
10843
- /* @__PURE__ */ jsx(Plus, { className: "w-3.5 h-3.5", "aria-hidden": "true" }),
10844
- /* @__PURE__ */ jsx("span", { children: "New rule" })
10845
- ]
10901
+ scopes: effectiveTopLevelScopes,
10902
+ active: activeScope,
10903
+ onChange: (s) => {
10904
+ void runWithGuard(() => {
10905
+ onTelemetry?.({ type: "scope.change", recordType, from: activeScope, to: s });
10906
+ if (ruleWizardStep !== null) {
10907
+ setRuleWizardStep(null);
10908
+ setRuleWizardRule(null);
10909
+ setDraftKind(null);
10910
+ }
10911
+ setActiveScope(s);
10912
+ });
10913
+ },
10914
+ loading: probe.isLoading,
10915
+ counts: {
10916
+ // Products badge counts DISTINCT products that have at
10917
+ // least one custom record — same semantics as Global /
10918
+ // Rules. Catalogue size (which can be 1k–10k+) was
10919
+ // misleading: "Products: 247" implied 247 customised
10920
+ // products when in fact zero might be configured. Falls
10921
+ // back to a lower bound when scope-counts truncated at
10922
+ // the hard cap.
10923
+ product: scopeCounts.productIds.size,
10924
+ // The remaining tabs show actual record counts so hidden
10925
+ // state (e.g. a rule-scoped competition) is visible from
10926
+ // any tab. `useScopeCounts` returns 0 while loading, which
10927
+ // is fine — the badges just appear once data lands.
10928
+ collection: scopeCounts.counts.collection,
10929
+ rule: scopeCounts.counts.rule,
10930
+ variant: scopeCounts.counts.variant,
10931
+ batch: scopeCounts.counts.batch,
10932
+ facet: scopeCounts.counts.facet,
10933
+ all: scopeCounts.counts.all
10934
+ },
10935
+ tooltips: { rule: i18n.rulesTabTooltip },
10936
+ icons: icons.scope
10846
10937
  }
10847
10938
  ) }),
10848
- showCreateGlobal && /* @__PURE__ */ jsxs(
10849
- "button",
10850
- {
10851
- type: "button",
10852
- onClick: onCreateGlobal,
10853
- className: "ra-btn w-full",
10854
- "data-variant": "primary",
10855
- "aria-label": "New global default",
10856
- children: [
10857
- /* @__PURE__ */ jsx(Plus, { className: "w-3.5 h-3.5", "aria-hidden": "true" }),
10858
- /* @__PURE__ */ jsx("span", { children: "New global default" })
10859
- ]
10860
- }
10861
- ),
10862
- !(isGlobalTab && !isCollection) && /* @__PURE__ */ jsxs(Fragment, { children: [
10863
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
10864
- /* @__PURE__ */ jsxs("div", { className: "relative flex-1 min-w-0", children: [
10865
- /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 opacity-50" }),
10939
+ /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-2.5 border-b", style: { borderColor: "hsl(var(--ra-border))" }, children: [
10940
+ isRuleTab && /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1.5", children: /* @__PURE__ */ jsxs(
10941
+ "button",
10942
+ {
10943
+ type: "button",
10944
+ onClick: () => onCreateRule(),
10945
+ className: "ra-btn w-full",
10946
+ "data-variant": "primary",
10947
+ "aria-label": "New rule",
10948
+ children: [
10949
+ /* @__PURE__ */ jsx(Plus, { className: "w-3.5 h-3.5", "aria-hidden": "true" }),
10950
+ /* @__PURE__ */ jsx("span", { children: "New rule" })
10951
+ ]
10952
+ }
10953
+ ) }),
10954
+ showCreateGlobal && /* @__PURE__ */ jsxs(
10955
+ "button",
10956
+ {
10957
+ type: "button",
10958
+ onClick: onCreateGlobal,
10959
+ className: "ra-btn w-full",
10960
+ "data-variant": "primary",
10961
+ "aria-label": "New global default",
10962
+ children: [
10963
+ /* @__PURE__ */ jsx(Plus, { className: "w-3.5 h-3.5", "aria-hidden": "true" }),
10964
+ /* @__PURE__ */ jsx("span", { children: "New global default" })
10965
+ ]
10966
+ }
10967
+ ),
10968
+ !(isGlobalTab && !isCollection) && /* @__PURE__ */ jsxs(Fragment, { children: [
10969
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
10970
+ /* @__PURE__ */ jsxs("div", { className: "relative flex-1 min-w-0", children: [
10971
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 opacity-50" }),
10972
+ /* @__PURE__ */ jsx(
10973
+ "input",
10974
+ {
10975
+ type: "text",
10976
+ value: search,
10977
+ onChange: (e) => setSearch(e.target.value),
10978
+ placeholder: i18n.searchPlaceholder,
10979
+ className: "w-full pl-8 pr-3 py-1.5 text-xs rounded-md border bg-transparent focus:outline-none focus:ring-1",
10980
+ style: { borderColor: "hsl(var(--ra-border))", color: "hsl(var(--ra-text))" }
10981
+ }
10982
+ )
10983
+ ] }),
10866
10984
  /* @__PURE__ */ jsx(
10867
- "input",
10985
+ PresentationSwitcher,
10868
10986
  {
10869
- type: "text",
10870
- value: search,
10871
- onChange: (e) => setSearch(e.target.value),
10872
- placeholder: i18n.searchPlaceholder,
10873
- className: "w-full pl-8 pr-3 py-1.5 text-xs rounded-md border bg-transparent focus:outline-none focus:ring-1",
10874
- style: { borderColor: "hsl(var(--ra-border))", color: "hsl(var(--ra-text))" }
10987
+ options: showPresentationSwitcher ? presentations : [],
10988
+ value: presentation,
10989
+ onChange: onPresentationChange,
10990
+ i18n
10875
10991
  }
10876
10992
  )
10877
10993
  ] }),
10878
- /* @__PURE__ */ jsx(
10879
- PresentationSwitcher,
10880
- {
10881
- options: showPresentationSwitcher ? presentations : [],
10882
- value: presentation,
10883
- onChange: onPresentationChange,
10884
- i18n
10885
- }
10886
- )
10887
- ] }),
10888
- isProductTab && !productPinned && (() => {
10889
- const cfg = scopeCounts.productIds;
10890
- let configured = 0;
10891
- for (const p of productBrowse.items) if (cfg.has(p.id)) configured += 1;
10892
- const total = productBrowse.items.length;
10893
- return /* @__PURE__ */ jsx(
10894
- StatusFilterPills,
10895
- {
10896
- value: filter,
10897
- onChange: setFilter,
10898
- counts: {
10899
- all: total,
10900
- configured,
10901
- partial: 0,
10902
- empty: total - configured
10903
- },
10904
- hideZero: ["partial"],
10905
- i18n
10906
- }
10907
- );
10908
- })(),
10909
- isRuleTab && /* @__PURE__ */ jsx(
10910
- FacetBrowseFilter,
10911
- {
10912
- facets: facetBrowseFacets,
10913
- value: facetBrowseFilter,
10914
- onChange: setFacetBrowseFilter,
10915
- isLoading: facetBrowse.isLoading
10916
- }
10917
- ),
10918
- isRuleTab && isCollection && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
10919
- /* @__PURE__ */ jsx(
10920
- "label",
10994
+ isProductTab && !productPinned && (() => {
10995
+ const cfg = scopeCounts.productIds;
10996
+ let configured = 0;
10997
+ for (const p of productBrowse.items) if (cfg.has(p.id)) configured += 1;
10998
+ const total = productBrowse.items.length;
10999
+ return /* @__PURE__ */ jsx(
11000
+ StatusFilterPills,
11001
+ {
11002
+ value: filter,
11003
+ onChange: setFilter,
11004
+ counts: {
11005
+ all: total,
11006
+ configured,
11007
+ partial: 0,
11008
+ empty: total - configured
11009
+ },
11010
+ hideZero: ["partial"],
11011
+ i18n
11012
+ }
11013
+ );
11014
+ })(),
11015
+ isRuleTab && /* @__PURE__ */ jsx(
11016
+ FacetBrowseFilter,
10921
11017
  {
10922
- htmlFor: "ra-rule-sort",
10923
- style: { color: "hsl(var(--ra-text-muted))" },
10924
- children: i18n.ruleSortLabel
11018
+ facets: facetBrowseFacets,
11019
+ value: facetBrowseFilter,
11020
+ onChange: setFacetBrowseFilter,
11021
+ isLoading: facetBrowse.isLoading
10925
11022
  }
10926
11023
  ),
10927
- /* @__PURE__ */ jsxs(
10928
- "select",
10929
- {
10930
- id: "ra-rule-sort",
10931
- value: ruleSort,
10932
- onChange: (e) => setRuleSort(e.target.value),
10933
- className: "text-xs px-2 py-1 rounded-md border bg-transparent focus:outline-none focus:ring-1",
10934
- style: { borderColor: "hsl(var(--ra-border))", color: "hsl(var(--ra-text))" },
10935
- children: [
10936
- /* @__PURE__ */ jsx("option", { value: "recent", children: i18n.ruleSortRecent }),
10937
- /* @__PURE__ */ jsx("option", { value: "name", children: i18n.ruleSortName }),
10938
- /* @__PURE__ */ jsx("option", { value: "activeCount", children: i18n.ruleSortActiveCount }),
10939
- /* @__PURE__ */ jsx("option", { value: "hasArchived", children: i18n.ruleSortHasArchived })
10940
- ]
10941
- }
10942
- )
11024
+ isRuleTab && isCollection && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
11025
+ /* @__PURE__ */ jsx(
11026
+ "label",
11027
+ {
11028
+ htmlFor: "ra-rule-sort",
11029
+ style: { color: "hsl(var(--ra-text-muted))" },
11030
+ children: i18n.ruleSortLabel
11031
+ }
11032
+ ),
11033
+ /* @__PURE__ */ jsxs(
11034
+ "select",
11035
+ {
11036
+ id: "ra-rule-sort",
11037
+ value: ruleSort,
11038
+ onChange: (e) => setRuleSort(e.target.value),
11039
+ className: "text-xs px-2 py-1 rounded-md border bg-transparent focus:outline-none focus:ring-1",
11040
+ style: { borderColor: "hsl(var(--ra-border))", color: "hsl(var(--ra-text))" },
11041
+ children: [
11042
+ /* @__PURE__ */ jsx("option", { value: "recent", children: i18n.ruleSortRecent }),
11043
+ /* @__PURE__ */ jsx("option", { value: "name", children: i18n.ruleSortName }),
11044
+ /* @__PURE__ */ jsx("option", { value: "activeCount", children: i18n.ruleSortActiveCount }),
11045
+ /* @__PURE__ */ jsx("option", { value: "hasArchived", children: i18n.ruleSortHasArchived })
11046
+ ]
11047
+ }
11048
+ )
11049
+ ] })
10943
11050
  ] })
10944
- ] })
10945
- ] }),
10946
- /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto", children: [
10947
- hasSingletonConflicts && /* @__PURE__ */ jsx(
10948
- SingletonConflictBanner,
10949
- {
10950
- conflictCount: singletonConflicts.length,
10951
- duplicateCount: totalDuplicateCount,
10952
- onResolve: () => {
10953
- const first = singletonConflicts[0]?.duplicates[0] ?? singletonConflicts[0]?.active;
10954
- if (first?.id) {
10955
- void runWithGuard(() => {
10956
- setSelectedRecordId(first.id);
10957
- });
10958
- }
10959
- },
10960
- onArchiveDuplicates: enableArchiveDuplicates ? async () => {
10961
- const ids = singletonConflicts.flatMap((c) => c.duplicates.map((d) => d.id)).filter((id) => !!id);
10962
- for (const id of ids) {
10963
- try {
10964
- const updated = await SL.app.records.update(collectionId, appId, id, { status: archivedStatusValue }, true);
10965
- if (updated) patchRecordIntoCaches(queryClient, ctx, updated);
10966
- onTelemetry?.({
10967
- type: "recordAction.invoke",
10968
- recordType,
10969
- key: "conflict.archiveDuplicates",
10970
- ref: id
11051
+ ] }),
11052
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto", children: [
11053
+ hasSingletonConflicts && /* @__PURE__ */ jsx(
11054
+ SingletonConflictBanner,
11055
+ {
11056
+ conflictCount: singletonConflicts.length,
11057
+ duplicateCount: totalDuplicateCount,
11058
+ onResolve: () => {
11059
+ const first = singletonConflicts[0]?.duplicates[0] ?? singletonConflicts[0]?.active;
11060
+ if (first?.id) {
11061
+ void runWithGuard(() => {
11062
+ setSelectedRecordId(first.id);
10971
11063
  });
10972
- } catch (err) {
10973
- console.warn("[RecordsAdminShell] archive-duplicate failed", id, err);
10974
11064
  }
10975
- }
10976
- queryClient.invalidateQueries({
10977
- queryKey: scopeCountsQueryKey(ctx.collectionId, ctx.appId, ctx.recordType)
10978
- });
10979
- } : void 0,
10980
- onDeleteDuplicates: enableDeleteDuplicates ? async () => {
10981
- const ids = singletonConflicts.flatMap((c) => c.duplicates.map((d) => d.id)).filter((id) => !!id);
10982
- for (const id of ids) {
10983
- try {
10984
- await SL.app.records.remove(collectionId, appId, id, true);
10985
- removeRecordFromCaches(queryClient, ctx, id);
10986
- onTelemetry?.({
10987
- type: "recordAction.invoke",
10988
- recordType,
10989
- key: "conflict.deleteDuplicates",
10990
- ref: id
10991
- });
10992
- } catch (err) {
10993
- console.warn("[RecordsAdminShell] delete-duplicate failed", id, err);
11065
+ },
11066
+ onArchiveDuplicates: enableArchiveDuplicates ? async () => {
11067
+ const ids = singletonConflicts.flatMap((c) => c.duplicates.map((d) => d.id)).filter((id) => !!id);
11068
+ for (const id of ids) {
11069
+ try {
11070
+ const updated = await SL.app.records.update(collectionId, appId, id, { status: archivedStatusValue }, true);
11071
+ if (updated) patchRecordIntoCaches(queryClient, ctx, updated);
11072
+ onTelemetry?.({
11073
+ type: "recordAction.invoke",
11074
+ recordType,
11075
+ key: "conflict.archiveDuplicates",
11076
+ ref: id
11077
+ });
11078
+ } catch (err) {
11079
+ console.warn("[RecordsAdminShell] archive-duplicate failed", id, err);
11080
+ }
10994
11081
  }
11082
+ queryClient.invalidateQueries({
11083
+ queryKey: scopeCountsQueryKey(ctx.collectionId, ctx.appId, ctx.recordType)
11084
+ });
11085
+ } : void 0,
11086
+ onDeleteDuplicates: enableDeleteDuplicates ? async () => {
11087
+ const ids = singletonConflicts.flatMap((c) => c.duplicates.map((d) => d.id)).filter((id) => !!id);
11088
+ for (const id of ids) {
11089
+ try {
11090
+ await SL.app.records.remove(collectionId, appId, id, true);
11091
+ removeRecordFromCaches(queryClient, ctx, id);
11092
+ onTelemetry?.({
11093
+ type: "recordAction.invoke",
11094
+ recordType,
11095
+ key: "conflict.deleteDuplicates",
11096
+ ref: id
11097
+ });
11098
+ } catch (err) {
11099
+ console.warn("[RecordsAdminShell] delete-duplicate failed", id, err);
11100
+ }
11101
+ }
11102
+ queryClient.invalidateQueries({
11103
+ queryKey: scopeCountsQueryKey(ctx.collectionId, ctx.appId, ctx.recordType)
11104
+ });
11105
+ } : void 0,
11106
+ i18n: {
11107
+ title: i18n.conflictBannerTitle,
11108
+ bodyOne: i18n.conflictBannerBodyOne,
11109
+ bodyMany: i18n.conflictBannerBodyMany,
11110
+ archiveLabel: i18n.conflictArchiveDuplicates,
11111
+ deleteLabel: i18n.conflictDeleteDuplicates,
11112
+ deleteConfirm: i18n.conflictDeleteConfirm,
11113
+ resolveLabel: i18n.conflictResolveLabel
10995
11114
  }
10996
- queryClient.invalidateQueries({
10997
- queryKey: scopeCountsQueryKey(ctx.collectionId, ctx.appId, ctx.recordType)
10998
- });
10999
- } : void 0,
11000
- i18n: {
11001
- title: i18n.conflictBannerTitle,
11002
- bodyOne: i18n.conflictBannerBodyOne,
11003
- bodyMany: i18n.conflictBannerBodyMany,
11004
- archiveLabel: i18n.conflictArchiveDuplicates,
11005
- deleteLabel: i18n.conflictDeleteDuplicates,
11006
- deleteConfirm: i18n.conflictDeleteConfirm,
11007
- resolveLabel: i18n.conflictResolveLabel
11008
- }
11009
- }
11010
- ),
11011
- isGlobalTab && !isCollection && !hasSingletonConflicts ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
11012
- leftLoading && /* @__PURE__ */ jsx(LoadingState, {}),
11013
- !leftLoading && leftError && /* @__PURE__ */ jsx(ErrorState, { error: leftError }),
11014
- !leftLoading && !leftError && decoratedLeftItems.length === 0 && (renderEmptyState ? renderEmptyState({ scope: activeScope }) : /* @__PURE__ */ jsx(
11015
- EmptyState,
11016
- {
11017
- icon: search ? icons.empty.search : icons.empty.default,
11018
- title: search ? i18n.noResults : i18n.railEmptyTitle,
11019
- body: search ? void 0 : isRuleTab ? i18n.rulesEmptyBody : i18n.railEmptyBody
11020
11115
  }
11021
- )),
11022
- !leftLoading && !leftError && decoratedLeftItems.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
11023
- /* @__PURE__ */ jsx(
11024
- RecordList,
11116
+ ),
11117
+ isGlobalTab && !isCollection && !hasSingletonConflicts ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
11118
+ leftLoading && /* @__PURE__ */ jsx(LoadingState, {}),
11119
+ !leftLoading && leftError && /* @__PURE__ */ jsx(ErrorState, { error: leftError }),
11120
+ !leftLoading && !leftError && decoratedLeftItems.length === 0 && (renderEmptyState ? renderEmptyState({ scope: activeScope }) : /* @__PURE__ */ jsx(
11121
+ EmptyState,
11025
11122
  {
11026
- items: decoratedLeftItems,
11027
- selectedId: leftSelectedId,
11028
- selectedAnchorKey: leftSelectedAnchorKey,
11029
- onSelect: onLeftSelect,
11030
- dirtyId,
11031
- dirtyAnchorKey,
11032
- dirtyKeys,
11033
- errorKeys,
11034
- presentation: effectivePresentation,
11035
- renderListRow,
11036
- groupBy: (
11037
- // The synthetic "All items" row in collection mode is a
11038
- // navigational anchor, not a real record — applying the
11039
- // host's groupBy bucketed it under "Other" (its
11040
- // data is null). Skip grouping for that single-row rail.
11041
- (isGlobalTab || isAllTab) && isCollection || isLifecycleRail ? void 0 : effectiveGroupBy
11042
- ),
11043
- renderGroupActions: renderRuleGroupActions,
11044
- rowClipboard,
11045
- rowActions: wrappedRecordActions,
11046
- i18n,
11047
- historyBySlot: railHistoryBySlot
11123
+ icon: search ? icons.empty.search : icons.empty.default,
11124
+ title: search ? i18n.noResults : i18n.railEmptyTitle,
11125
+ body: search ? void 0 : isRuleTab ? i18n.rulesEmptyBody : i18n.railEmptyBody
11048
11126
  }
11049
- ),
11050
- isProductTab && !productPinned && /* @__PURE__ */ jsx(
11051
- LoadMoreFooter,
11052
- {
11053
- shown: leftItems.length,
11054
- hasNextPage: !!productBrowse.hasNextPage,
11055
- isFetchingNextPage: !!productBrowse.isFetchingNextPage,
11056
- onLoadMore: () => {
11057
- void productBrowse.fetchNextPage();
11127
+ )),
11128
+ !leftLoading && !leftError && decoratedLeftItems.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
11129
+ /* @__PURE__ */ jsx(
11130
+ RecordList,
11131
+ {
11132
+ items: decoratedLeftItems,
11133
+ selectedId: leftSelectedId,
11134
+ selectedAnchorKey: leftSelectedAnchorKey,
11135
+ onSelect: onLeftSelect,
11136
+ dirtyId,
11137
+ dirtyAnchorKey,
11138
+ dirtyKeys,
11139
+ errorKeys,
11140
+ presentation: effectivePresentation,
11141
+ renderListRow,
11142
+ groupBy: (
11143
+ // The synthetic "All items" row in collection mode is a
11144
+ // navigational anchor, not a real record — applying the
11145
+ // host's groupBy bucketed it under "Other" (its
11146
+ // data is null). Skip grouping for that single-row rail.
11147
+ (isGlobalTab || isAllTab) && isCollection || isLifecycleRail ? void 0 : effectiveGroupBy
11148
+ ),
11149
+ renderGroupActions: renderRuleGroupActions,
11150
+ rowClipboard,
11151
+ rowActions: wrappedRecordActions,
11152
+ i18n,
11153
+ historyBySlot: railHistoryBySlot
11058
11154
  }
11059
- }
11060
- ),
11061
- isRecordsTab && (!isCollection || !(isAllTab || isGlobalTab)) && /* @__PURE__ */ jsx(
11062
- LoadMoreFooter,
11063
- {
11064
- shown: recordList.items.length,
11065
- total: recordList.total,
11066
- hasNextPage: !!recordList.hasNextPage,
11067
- isFetchingNextPage: !!recordList.isFetchingNextPage,
11068
- onLoadMore: () => {
11069
- void recordList.fetchNextPage();
11155
+ ),
11156
+ isProductTab && !productPinned && /* @__PURE__ */ jsx(
11157
+ LoadMoreFooter,
11158
+ {
11159
+ shown: leftItems.length,
11160
+ hasNextPage: !!productBrowse.hasNextPage,
11161
+ isFetchingNextPage: !!productBrowse.isFetchingNextPage,
11162
+ onLoadMore: () => {
11163
+ void productBrowse.fetchNextPage();
11164
+ }
11070
11165
  }
11071
- }
11072
- )
11166
+ ),
11167
+ isRecordsTab && (!isCollection || !(isAllTab || isGlobalTab)) && /* @__PURE__ */ jsx(
11168
+ LoadMoreFooter,
11169
+ {
11170
+ shown: recordList.items.length,
11171
+ total: recordList.total,
11172
+ hasNextPage: !!recordList.hasNextPage,
11173
+ isFetchingNextPage: !!recordList.isFetchingNextPage,
11174
+ onLoadMore: () => {
11175
+ void recordList.fetchNextPage();
11176
+ }
11177
+ }
11178
+ )
11179
+ ] })
11073
11180
  ] })
11074
11181
  ] })
11075
11182
  ] })
11076
- ] }) }),
11077
- /* @__PURE__ */ jsxs("main", { className: "overflow-hidden", children: [
11183
+ ] }),
11184
+ /* @__PURE__ */ jsxs("main", { className: "overflow-hidden relative", ref: railReopenAnchorRef, children: [
11185
+ !railHidden && !railOpen && /* @__PURE__ */ jsxs(
11186
+ PreviewReopenPill,
11187
+ {
11188
+ anchorRef: railReopenAnchorRef,
11189
+ side: "left",
11190
+ onClick: () => setRailOpen(true),
11191
+ ariaLabel: i18n.openList,
11192
+ title: i18n.openList,
11193
+ children: [
11194
+ /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" }),
11195
+ i18n.openList
11196
+ ]
11197
+ }
11198
+ ),
11078
11199
  ruleWizardStep !== null && /* @__PURE__ */ jsxs(
11079
11200
  NewRuleWizard,
11080
11201
  {