@fluid-app/portal-sdk 0.1.217 → 0.1.219

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.
Files changed (81) hide show
  1. package/dist/{ContactsScreen-D-g7XEB-.cjs → ContactsScreen-DYVR-UvO.cjs} +1 -1
  2. package/dist/{FluidProvider-02beRTpR.mjs → FluidProvider-DTttgSXi.mjs} +8 -8
  3. package/dist/{FluidProvider-02beRTpR.mjs.map → FluidProvider-DTttgSXi.mjs.map} +1 -1
  4. package/dist/{FluidProvider-BgFmXtHo.cjs → FluidProvider-Dm4BhF_t.cjs} +8 -8
  5. package/dist/{FluidProvider-BgFmXtHo.cjs.map → FluidProvider-Dm4BhF_t.cjs.map} +1 -1
  6. package/dist/{ListWidget-C0ltFhxE.cjs → ListWidget-BE7uA4W4.cjs} +1 -2
  7. package/dist/{ListWidget-Ct8IDkio.mjs → ListWidget-D7y8bfvT.mjs} +252 -120
  8. package/dist/ListWidget-D7y8bfvT.mjs.map +1 -0
  9. package/dist/{ListWidget-C7ouNpEo.cjs → ListWidget-DJ-_dMOK.cjs} +251 -119
  10. package/dist/ListWidget-DJ-_dMOK.cjs.map +1 -0
  11. package/dist/{MessagingScreen-CJlVOY5R.mjs → MessagingScreen-CwEXQJlW.mjs} +2 -2
  12. package/dist/{MessagingScreen-CJlVOY5R.mjs.map → MessagingScreen-CwEXQJlW.mjs.map} +1 -1
  13. package/dist/{MessagingScreen-BkLcqJbs.cjs → MessagingScreen-DLQ5V0m4.cjs} +2 -2
  14. package/dist/{MessagingScreen-BkLcqJbs.cjs.map → MessagingScreen-DLQ5V0m4.cjs.map} +1 -1
  15. package/dist/{MessagingScreen-sN7aBvGk.cjs → MessagingScreen-YBXJL7a9.cjs} +6 -6
  16. package/dist/{MySiteScreen-DUN5TTvU.mjs → MySiteScreen-CyzM9hX3.mjs} +11 -30
  17. package/dist/{MySiteScreen-DUN5TTvU.mjs.map → MySiteScreen-CyzM9hX3.mjs.map} +1 -1
  18. package/dist/{MySiteScreen-B0aOIzU4.cjs → MySiteScreen-DPQ66oRs.cjs} +2 -2
  19. package/dist/{MySiteScreen-B1L8coGs.cjs → MySiteScreen-DxLcG3-S.cjs} +11 -30
  20. package/dist/MySiteScreen-DxLcG3-S.cjs.map +1 -0
  21. package/dist/{MySiteWidget-CDQjyrRA.cjs → MySiteWidget-8UsfEK-w.cjs} +1 -1
  22. package/dist/{MySiteWidget-CDQjyrRA.cjs.map → MySiteWidget-8UsfEK-w.cjs.map} +1 -1
  23. package/dist/{MySiteWidget-BlUbduit.mjs → MySiteWidget-D46TVWnf.mjs} +1 -1
  24. package/dist/{MySiteWidget-BlUbduit.mjs.map → MySiteWidget-D46TVWnf.mjs.map} +1 -1
  25. package/dist/{NestedWidget-ChAWwsRP.cjs → NestedWidget-BjT-UrSi.cjs} +2 -2
  26. package/dist/{NestedWidget-ChAWwsRP.cjs.map → NestedWidget-BjT-UrSi.cjs.map} +1 -1
  27. package/dist/{NestedWidget-Dw0QHoqw.mjs → NestedWidget-CEeQeCq0.mjs} +2 -2
  28. package/dist/{NestedWidget-Dw0QHoqw.mjs.map → NestedWidget-CEeQeCq0.mjs.map} +1 -1
  29. package/dist/{NestedWidget--suTLM6l.cjs → NestedWidget-D4amXykk.cjs} +2 -2
  30. package/dist/{PortalContentApiProvider-RXBp8FNj.cjs → PortalContentApiProvider-DXnplIOD.cjs} +273 -82
  31. package/dist/PortalContentApiProvider-DXnplIOD.cjs.map +1 -0
  32. package/dist/{PortalContentApiProvider-C9FeVwRb.mjs → PortalContentApiProvider-x81DXmOR.mjs} +273 -82
  33. package/dist/PortalContentApiProvider-x81DXmOR.mjs.map +1 -0
  34. package/dist/{ProductsScreen-DNpzJ6lh.cjs → ProductsScreen-3yjIMjYY.cjs} +2 -2
  35. package/dist/{ProductsScreen-pkOeOW8M.mjs → ProductsScreen-BVyLOe2K.mjs} +2 -2
  36. package/dist/{ProductsScreen-BD53vh6Y.cjs → ProductsScreen-R0MfWYZJ.cjs} +2 -2
  37. package/dist/{ProductsScreen-BD53vh6Y.cjs.map → ProductsScreen-R0MfWYZJ.cjs.map} +1 -1
  38. package/dist/{ProductsScreen-BtUZxJCt.mjs → ProductsScreen-z9hXfFeJ.mjs} +2 -2
  39. package/dist/{ProductsScreen-BtUZxJCt.mjs.map → ProductsScreen-z9hXfFeJ.mjs.map} +1 -1
  40. package/dist/{ProfileScreen-rPqgsNCc.cjs → ProfileScreen-B0WRifk_.cjs} +2 -2
  41. package/dist/{ProfileScreen-rPqgsNCc.cjs.map → ProfileScreen-B0WRifk_.cjs.map} +1 -1
  42. package/dist/{ProfileScreen-CNYqUDNB.mjs → ProfileScreen-BuejQU_V.mjs} +2 -2
  43. package/dist/{ProfileScreen-CNYqUDNB.mjs.map → ProfileScreen-BuejQU_V.mjs.map} +1 -1
  44. package/dist/{ProfileScreen-Bgo6iTKe.cjs → ProfileScreen-g3se9Jw-.cjs} +6 -6
  45. package/dist/{ShareablesScreen-DC8xXUo4.cjs → ShareablesScreen-Co_gFZva.cjs} +3 -3
  46. package/dist/{ShareablesScreen-DC8xXUo4.cjs.map → ShareablesScreen-Co_gFZva.cjs.map} +1 -1
  47. package/dist/{ShareablesScreen-D1J2Kljk.mjs → ShareablesScreen-DpwFh0ky.mjs} +3 -3
  48. package/dist/{ShareablesScreen-smU5pGyH.cjs → ShareablesScreen-YlMqyP-B.cjs} +3 -3
  49. package/dist/{ShareablesScreen-CW1e9x4K.mjs → ShareablesScreen-tkaf9R5N.mjs} +3 -3
  50. package/dist/{ShareablesScreen-CW1e9x4K.mjs.map → ShareablesScreen-tkaf9R5N.mjs.map} +1 -1
  51. package/dist/{ShopScreen-B9lHJ8L2.mjs → ShopScreen-B4afB5sE.mjs} +2 -2
  52. package/dist/{ShopScreen-B9lHJ8L2.mjs.map → ShopScreen-B4afB5sE.mjs.map} +1 -1
  53. package/dist/{ShopScreen-g6FQ4R1_.cjs → ShopScreen-BOjri6Dm.cjs} +6 -6
  54. package/dist/{ShopScreen-DUHzufCU.cjs → ShopScreen-Untg6XBY.cjs} +2 -2
  55. package/dist/{ShopScreen-DUHzufCU.cjs.map → ShopScreen-Untg6XBY.cjs.map} +1 -1
  56. package/dist/index.cjs +27 -27
  57. package/dist/index.d.cts.map +1 -1
  58. package/dist/index.d.mts.map +1 -1
  59. package/dist/index.mjs +25 -25
  60. package/dist/{portal_tenant_content-0zpnjBot.cjs → portal_tenant_content-BvYxmADB.cjs} +18 -1
  61. package/dist/portal_tenant_content-BvYxmADB.cjs.map +1 -0
  62. package/dist/{portal_tenant_content-DzIQtSLE.mjs → portal_tenant_content-nHEI2qEY.mjs} +13 -2
  63. package/dist/portal_tenant_content-nHEI2qEY.mjs.map +1 -0
  64. package/dist/{scroll-arrows-fK_MFlSX.mjs → scroll-arrows-COrfvJk9.mjs} +1 -1
  65. package/dist/{scroll-arrows-fK_MFlSX.mjs.map → scroll-arrows-COrfvJk9.mjs.map} +1 -1
  66. package/dist/{scroll-arrows-DVwMDTt3.cjs → scroll-arrows-i0E2N-ct.cjs} +1 -1
  67. package/dist/{scroll-arrows-DVwMDTt3.cjs.map → scroll-arrows-i0E2N-ct.cjs.map} +1 -1
  68. package/dist/{use-mysite-portal-BV-BP3CE.mjs → use-mysite-portal-D29HLD1z.mjs} +3 -2
  69. package/dist/use-mysite-portal-D29HLD1z.mjs.map +1 -0
  70. package/dist/{use-mysite-portal-DzDYRU0u.cjs → use-mysite-portal-DxNQ3uTi.cjs} +3 -2
  71. package/dist/use-mysite-portal-DxNQ3uTi.cjs.map +1 -0
  72. package/package.json +9 -9
  73. package/dist/ListWidget-C7ouNpEo.cjs.map +0 -1
  74. package/dist/ListWidget-Ct8IDkio.mjs.map +0 -1
  75. package/dist/MySiteScreen-B1L8coGs.cjs.map +0 -1
  76. package/dist/PortalContentApiProvider-C9FeVwRb.mjs.map +0 -1
  77. package/dist/PortalContentApiProvider-RXBp8FNj.cjs.map +0 -1
  78. package/dist/portal_tenant_content-0zpnjBot.cjs.map +0 -1
  79. package/dist/portal_tenant_content-DzIQtSLE.mjs.map +0 -1
  80. package/dist/use-mysite-portal-BV-BP3CE.mjs.map +0 -1
  81. package/dist/use-mysite-portal-DzDYRU0u.cjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { t as useDropzone } from "./es-Cw_Kikmu.mjs";
2
- import { A as playlists_update, C as playlists_create, D as playlists_items_remove, E as playlists_items_list, M as shares_list, O as playlists_list, T as playlists_items_add, _ as media_products_add, a as content_playlists_metrics_share_visits, b as media_show, c as dam_asset_paths_list, d as dam_assets_discard, f as dam_assets_list, g as media_list, h as media_destroy, i as content_pages_metrics_visits, j as shares_create, k as playlists_show, l as dam_assets_create, m as media_create, n as content_media_metrics_visits, o as content_playlists_metrics_visits, p as dam_query, r as content_pages_metrics_share_visits, s as dam_asset_paths_create, t as content_media_metrics_share_visits, u as dam_assets_destroy, v as media_products_list, w as playlists_destroy, x as media_update, y as media_products_remove } from "./portal_tenant_content-DzIQtSLE.mjs";
2
+ import { A as playlists_show, C as playlists_create, D as playlists_items_remove, E as playlists_items_list, M as shares_create, N as shares_list, O as playlists_items_reorder, T as playlists_items_add, _ as media_products_add, a as content_playlists_metrics_share_visits, b as media_show, c as dam_asset_paths_list, d as dam_assets_discard, f as dam_assets_list, g as media_list, h as media_destroy, i as content_pages_metrics_visits, j as playlists_update, k as playlists_list, l as dam_assets_create, m as media_create, n as content_media_metrics_visits, o as content_playlists_metrics_visits, p as dam_query, r as content_pages_metrics_share_visits, s as dam_asset_paths_create, t as content_media_metrics_share_visits, u as dam_assets_destroy, v as media_products_list, w as playlists_destroy, x as media_update, y as media_products_remove } from "./portal_tenant_content-nHEI2qEY.mjs";
3
3
  import { n as usePortalTenantClient } from "./PortalTenantClientProvider-4ZmY6hac.mjs";
4
4
  import { $ as DropdownMenuSeparator, A as Select, At as Checkbox, B as Form, C as Skeleton, Cn as AlertDialogFooter, Dn as Content, En as Button, Et as CardContent, G as FormMessage, H as FormField, J as DropdownMenuContent, K as Label, L as Input, M as SelectItem, N as SelectTrigger, Nn as useZodForm, P as SelectValue, Pn as cn, S as Slider, Sn as AlertDialogDescription, Tn as AlertDialogTitle, U as FormItem, V as FormControl, W as FormLabel, Y as DropdownMenuItem, _ as Switch, bn as AlertDialogCancel, c as Tabs, cn as Breadcrumb, dn as BreadcrumbList, dt as DialogPortal, et as DropdownMenuSub, fn as BreadcrumbPage, ft as DialogTitle, ht as PopoverTrigger, i as TooltipTrigger, it as Dialog, j as SelectContent, k as Separator, l as TabsList, ln as BreadcrumbItem, lt as DialogHeader, mn as Badge, mt as PopoverContent, n as TooltipContent, nt as DropdownMenuSubTrigger, ot as DialogContent, pn as BreadcrumbSeparator, pt as Popover, q as DropdownMenu, r as TooltipProvider, rt as DropdownMenuTrigger, s as Textarea, st as DialogDescription, t as Tooltip, tt as DropdownMenuSubContent, u as TabsTrigger, un as BreadcrumbLink, ut as DialogOverlay, vn as AlertDialog, wn as AlertDialogHeader, wt as Card, xn as AlertDialogContent, y as Spinner, yn as AlertDialogAction } from "./src-BEKH1DXO.mjs";
5
5
  import { n as useScreenHeaderActions, r as useScreenHeaderBreadcrumbs } from "./ScreenHeaderContext-BDjNSUfr.mjs";
@@ -286,6 +286,25 @@ const useUpdatePlaylistMutation = (options) => {
286
286
  }
287
287
  });
288
288
  };
289
+ const useReorderPlaylistItemsMutation = (options) => {
290
+ const api = useShareablesApi();
291
+ const queryClient = useQueryClient();
292
+ return useMutation({
293
+ mutationFn: ({ playlistId, data }) => {
294
+ if (!api.playlists.reorderPlaylistItems) return Promise.resolve();
295
+ return api.playlists.reorderPlaylistItems(playlistId, data);
296
+ },
297
+ onSuccess: (_, { playlistId }) => {
298
+ if (api.playlists.reorderPlaylistItems) queryClient.invalidateQueries({ queryKey: shareablesKeys.playlists.detail(playlistId) });
299
+ options?.onSuccess?.(playlistId);
300
+ },
301
+ onError: (error, { playlistId }) => {
302
+ if (isCancellationError(error)) return;
303
+ console.error("Error reordering playlist items:", error);
304
+ options?.onError?.(error, playlistId);
305
+ }
306
+ });
307
+ };
289
308
  //#endregion
290
309
  //#region ../../shareables/ui/src/context.tsx
291
310
  const MISSING_PROVIDER = Symbol("missing-shareables-provider");
@@ -576,6 +595,83 @@ function ShareableListLayout({ filters, children, isLoading, error, errorMessage
576
595
  });
577
596
  }
578
597
  //#endregion
598
+ //#region ../../shareables/ui/src/components/shared/ShareableListRow.tsx
599
+ /**
600
+ * Container className for a list-view rendering of shareables. Pairs with
601
+ * `ShareableListRow` and mirrors `SHAREABLE_GRID_CLASS` for symmetry.
602
+ */
603
+ const SHAREABLE_LIST_CLASS = "divide-border divide-y rounded-lg border";
604
+ /**
605
+ * Single row in a shareables list view. The row body (thumbnail + title) is
606
+ * a `<button>` for keyboard/click navigation; `leading` and `trailing` slots
607
+ * are siblings so their interactive elements don't nest inside the body
608
+ * button. Hover styling applies to the entire row container.
609
+ */
610
+ function ShareableListRow({ imageUrl, imageAlt, title, subtitle, onClick, leading, trailing }) {
611
+ return /* @__PURE__ */ jsxs("div", {
612
+ className: "hover:bg-muted flex items-center gap-3 px-4 py-3 transition-colors",
613
+ children: [
614
+ leading,
615
+ /* @__PURE__ */ jsxs("button", {
616
+ type: "button",
617
+ onClick,
618
+ className: "flex min-w-0 flex-1 items-center gap-4 text-left",
619
+ children: [/* @__PURE__ */ jsx("div", {
620
+ className: "bg-muted h-12 w-12 shrink-0 overflow-hidden rounded-md",
621
+ children: imageUrl ? /* @__PURE__ */ jsx("img", {
622
+ src: imageUrl,
623
+ alt: imageAlt ?? title,
624
+ className: "h-full w-full object-cover"
625
+ }) : /* @__PURE__ */ jsx("div", { className: "bg-muted h-full w-full" })
626
+ }), /* @__PURE__ */ jsxs("div", {
627
+ className: "min-w-0 flex-1",
628
+ children: [/* @__PURE__ */ jsx("p", {
629
+ className: "text-foreground truncate text-sm font-medium",
630
+ children: title || "Untitled"
631
+ }), subtitle != null && subtitle !== false && /* @__PURE__ */ jsx("p", {
632
+ className: "text-muted-foreground flex items-center gap-1.5 text-xs",
633
+ children: subtitle
634
+ })]
635
+ })]
636
+ }),
637
+ trailing
638
+ ]
639
+ });
640
+ }
641
+ //#endregion
642
+ //#region ../../shareables/ui/src/components/shared/ViewModeToggle.tsx
643
+ /**
644
+ * Segmented list/grid toggle used in the filter bar of every shareable
645
+ * listing screen. Stateless — callers own the `viewMode` state.
646
+ */
647
+ function ViewModeToggle({ value, onChange }) {
648
+ return /* @__PURE__ */ jsxs("div", {
649
+ className: "border-input bg-muted flex items-center rounded-lg border p-0.5",
650
+ children: [/* @__PURE__ */ jsx(ToggleButton, {
651
+ active: value === "list",
652
+ label: "List view",
653
+ onClick: () => onChange("list"),
654
+ children: /* @__PURE__ */ jsx(List, { className: "h-4 w-4" })
655
+ }), /* @__PURE__ */ jsx(ToggleButton, {
656
+ active: value === "grid",
657
+ label: "Grid view",
658
+ onClick: () => onChange("grid"),
659
+ children: /* @__PURE__ */ jsx(LayoutGrid, { className: "h-4 w-4" })
660
+ })]
661
+ });
662
+ }
663
+ function ToggleButton({ active, label, onClick, children }) {
664
+ return /* @__PURE__ */ jsx("button", {
665
+ type: "button",
666
+ onClick,
667
+ title: label,
668
+ "aria-label": label,
669
+ "aria-pressed": active,
670
+ className: cn("flex h-8 w-8 items-center justify-center rounded-md transition-all", active ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"),
671
+ children
672
+ });
673
+ }
674
+ //#endregion
579
675
  //#region ../../shareables/ui/src/components/screens/ProductsScreen.tsx
580
676
  const PAGE_SIZE$4 = 24;
581
677
  const SORT_OPTIONS$1 = [
@@ -606,6 +702,7 @@ const SORT_OPTIONS$1 = [
606
702
  ];
607
703
  function ProductsScreen({ countryCode, fetchProducts: fetchPortalProducts, onNavigate }) {
608
704
  const client = useShareablesClient();
705
+ const { navigate } = useShareablesUI();
609
706
  useScreenHeaderBreadcrumbs(useMemo(() => /* @__PURE__ */ jsx(Breadcrumb, { children: /* @__PURE__ */ jsx(BreadcrumbList, {
610
707
  className: "text-lg",
611
708
  children: /* @__PURE__ */ jsx(BreadcrumbItem, { children: /* @__PURE__ */ jsx(BreadcrumbPage, {
@@ -616,6 +713,7 @@ function ProductsScreen({ countryCode, fetchProducts: fetchPortalProducts, onNav
616
713
  const [searchTerm, setSearchTerm] = useState("");
617
714
  const [debouncedSearch, setDebouncedSearch] = useState("");
618
715
  const [sortValue, setSortValue] = useState("created_at_desc");
716
+ const [viewMode, setViewMode] = useState("grid");
619
717
  const observerTarget = useRef(null);
620
718
  useEffect(() => {
621
719
  const timer = setTimeout(() => {
@@ -703,9 +801,9 @@ function ProductsScreen({ countryCode, fetchProducts: fetchPortalProducts, onNav
703
801
  errorMessage: "Failed to load products. Please try again.",
704
802
  isEmpty: products.length === 0,
705
803
  emptyMessage: debouncedSearch ? "No products match your search." : "No products available.",
706
- filters: /* @__PURE__ */ jsx("div", {
707
- className: "flex justify-end",
708
- children: /* @__PURE__ */ jsx("div", {
804
+ filters: /* @__PURE__ */ jsxs("div", {
805
+ className: "flex items-center justify-end gap-2",
806
+ children: [/* @__PURE__ */ jsx("div", {
709
807
  className: "w-full max-w-sm",
710
808
  children: /* @__PURE__ */ jsx(SearchSort, {
711
809
  searchValue: searchTerm,
@@ -715,14 +813,18 @@ function ProductsScreen({ countryCode, fetchProducts: fetchPortalProducts, onNav
715
813
  sortValue,
716
814
  onSortChange: setSortValue
717
815
  })
718
- })
816
+ }), /* @__PURE__ */ jsx(ViewModeToggle, {
817
+ value: viewMode,
818
+ onChange: setViewMode
819
+ })]
719
820
  }),
821
+ loadingFilterShape: "search-view",
720
822
  footer: isFetchingNextPage && /* @__PURE__ */ jsx("div", {
721
823
  className: "flex justify-center py-4",
722
824
  children: /* @__PURE__ */ jsx("div", { className: "border-primary h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" })
723
825
  }),
724
826
  sentinelRef: observerTarget,
725
- children: /* @__PURE__ */ jsx("div", {
827
+ children: viewMode === "grid" ? /* @__PURE__ */ jsx("div", {
726
828
  className: SHAREABLE_GRID_CLASS,
727
829
  children: products.map((product) => /* @__PURE__ */ jsx(ShareProductCard, {
728
830
  product: {
@@ -732,6 +834,14 @@ function ProductsScreen({ countryCode, fetchProducts: fetchPortalProducts, onNav
732
834
  },
733
835
  mediaCount: product.media_count
734
836
  }, product.id))
837
+ }) : /* @__PURE__ */ jsx("div", {
838
+ className: SHAREABLE_LIST_CLASS,
839
+ children: products.map((product) => /* @__PURE__ */ jsx(ShareableListRow, {
840
+ imageUrl: product.image_url,
841
+ title: product.title,
842
+ subtitle: product.media_count != null ? `${product.media_count} assets` : void 0,
843
+ onClick: () => navigate(`product/${product.id}`)
844
+ }, product.id))
735
845
  })
736
846
  });
737
847
  }
@@ -1701,21 +1811,9 @@ function MediaListingScreen({ onNavigate }) {
1701
1811
  }), kindFilter === opt.value && /* @__PURE__ */ jsx(Check, { className: "text-muted-foreground size-4" })]
1702
1812
  }, opt.value))
1703
1813
  })] }),
1704
- /* @__PURE__ */ jsxs("div", {
1705
- className: "border-input bg-muted flex items-center rounded-lg border p-0.5",
1706
- children: [/* @__PURE__ */ jsx("button", {
1707
- type: "button",
1708
- onClick: () => setViewMode("list"),
1709
- className: `flex h-8 w-8 items-center justify-center rounded-md transition-all ${viewMode === "list" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
1710
- title: "List view",
1711
- children: /* @__PURE__ */ jsx(List, { className: "h-4 w-4" })
1712
- }), /* @__PURE__ */ jsx("button", {
1713
- type: "button",
1714
- onClick: () => setViewMode("grid"),
1715
- className: `flex h-8 w-8 items-center justify-center rounded-md transition-all ${viewMode === "grid" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
1716
- title: "Grid view",
1717
- children: /* @__PURE__ */ jsx(LayoutGrid, { className: "h-4 w-4" })
1718
- })]
1814
+ /* @__PURE__ */ jsx(ViewModeToggle, {
1815
+ value: viewMode,
1816
+ onChange: setViewMode
1719
1817
  })
1720
1818
  ]
1721
1819
  })]
@@ -1785,37 +1883,20 @@ function MediaListingScreen({ onNavigate }) {
1785
1883
  }, item.id);
1786
1884
  })
1787
1885
  }) : /* @__PURE__ */ jsx("div", {
1788
- className: "divide-border divide-y rounded-lg border",
1886
+ className: SHAREABLE_LIST_CLASS,
1789
1887
  children: filteredItems.map((item) => {
1790
1888
  const canEdit = !readOnly && item.owner_type === "user";
1791
- return /* @__PURE__ */ jsxs("div", {
1792
- className: "hover:bg-muted flex items-center gap-4 px-4 py-3 transition-colors",
1793
- children: [/* @__PURE__ */ jsxs("button", {
1794
- type: "button",
1795
- onClick: () => navigate(`media/${item.id}`),
1796
- className: "flex min-w-0 flex-1 items-center gap-4 text-left",
1797
- children: [/* @__PURE__ */ jsx("div", {
1798
- className: "bg-muted h-12 w-12 shrink-0 overflow-hidden rounded-md",
1799
- children: item.image_url ? /* @__PURE__ */ jsx("img", {
1800
- src: item.image_url,
1801
- alt: item.title ?? "",
1802
- className: "h-full w-full object-cover"
1803
- }) : /* @__PURE__ */ jsx("div", { className: "bg-muted h-full w-full" })
1804
- }), /* @__PURE__ */ jsxs("div", {
1805
- className: "min-w-0 flex-1",
1806
- children: [/* @__PURE__ */ jsx("p", {
1807
- className: "text-foreground truncate text-sm font-medium",
1808
- children: item.title ?? "Untitled"
1809
- }), /* @__PURE__ */ jsxs("p", {
1810
- className: "text-muted-foreground flex items-center gap-1.5 text-xs",
1811
- children: [getMediaKindLabel(item.kind), item.owner_type === "user" && /* @__PURE__ */ jsx(Badge, {
1812
- variant: "secondary",
1813
- className: "px-1.5 py-0 text-[10px] leading-4",
1814
- children: "My Media"
1815
- })]
1816
- })]
1817
- })]
1818
- }), canEdit && /* @__PURE__ */ jsx(MediaRowActionsMenu, { onDelete: () => setPendingDeleteId(item.id) })]
1889
+ return /* @__PURE__ */ jsx(ShareableListRow, {
1890
+ imageUrl: item.image_url,
1891
+ imageAlt: item.title ?? "",
1892
+ title: item.title ?? "Untitled",
1893
+ subtitle: /* @__PURE__ */ jsxs(Fragment$1, { children: [getMediaKindLabel(item.kind), item.owner_type === "user" && /* @__PURE__ */ jsx(Badge, {
1894
+ variant: "secondary",
1895
+ className: "px-1.5 py-0 text-[10px] leading-4",
1896
+ children: "My Media"
1897
+ })] }),
1898
+ onClick: () => navigate(`media/${item.id}`),
1899
+ trailing: canEdit && /* @__PURE__ */ jsx(MediaRowActionsMenu, { onDelete: () => setPendingDeleteId(item.id) })
1819
1900
  }, item.id);
1820
1901
  })
1821
1902
  })
@@ -8934,6 +9015,7 @@ function PlaylistsListingScreen(_props) {
8934
9015
  const [debouncedSearch, setDebouncedSearch] = useState("");
8935
9016
  const [sortValue, setSortValue] = useState("title");
8936
9017
  const [ownerFilter, setOwnerFilter] = useState("all");
9018
+ const [viewMode, setViewMode] = useState("grid");
8937
9019
  const [selectedIds, setSelectedIds] = useState(/* @__PURE__ */ new Set());
8938
9020
  const [pendingDeleteId, setPendingDeleteId] = useState(null);
8939
9021
  useEffect(() => {
@@ -9104,33 +9186,39 @@ function PlaylistsListingScreen(_props) {
9104
9186
  value: ownerFilter,
9105
9187
  onValueChange: setOwnerFilter,
9106
9188
  myLabel: "My Playlists"
9107
- }), /* @__PURE__ */ jsx("div", {
9108
- className: "ml-auto w-full max-w-sm",
9109
- children: /* @__PURE__ */ jsx(SearchSort, {
9110
- searchValue: searchTerm,
9111
- onSearchChange: setSearchTerm,
9112
- placeholder: "Search playlists...",
9113
- sortOptions: [
9114
- {
9115
- label: "Name (A-Z)",
9116
- value: "title"
9117
- },
9118
- {
9119
- label: "Name (Z-A)",
9120
- value: "-title"
9121
- },
9122
- {
9123
- label: "Date Created (Newest)",
9124
- value: "-created_at"
9125
- },
9126
- {
9127
- label: "Date Created (Oldest)",
9128
- value: "created_at"
9129
- }
9130
- ],
9131
- sortValue,
9132
- onSortChange: setSortValue
9133
- })
9189
+ }), /* @__PURE__ */ jsxs("div", {
9190
+ className: "ml-auto flex items-center gap-2",
9191
+ children: [/* @__PURE__ */ jsx("div", {
9192
+ className: "w-full max-w-sm",
9193
+ children: /* @__PURE__ */ jsx(SearchSort, {
9194
+ searchValue: searchTerm,
9195
+ onSearchChange: setSearchTerm,
9196
+ placeholder: "Search playlists...",
9197
+ sortOptions: [
9198
+ {
9199
+ label: "Name (A-Z)",
9200
+ value: "title"
9201
+ },
9202
+ {
9203
+ label: "Name (Z-A)",
9204
+ value: "-title"
9205
+ },
9206
+ {
9207
+ label: "Date Created (Newest)",
9208
+ value: "-created_at"
9209
+ },
9210
+ {
9211
+ label: "Date Created (Oldest)",
9212
+ value: "created_at"
9213
+ }
9214
+ ],
9215
+ sortValue,
9216
+ onSortChange: setSortValue
9217
+ })
9218
+ }), /* @__PURE__ */ jsx(ViewModeToggle, {
9219
+ value: viewMode,
9220
+ onChange: setViewMode
9221
+ })]
9134
9222
  })]
9135
9223
  });
9136
9224
  const footer = /* @__PURE__ */ jsxs(Fragment$1, { children: [isFetchingNextPage && /* @__PURE__ */ jsx("div", {
@@ -9152,8 +9240,8 @@ function PlaylistsListingScreen(_props) {
9152
9240
  }),
9153
9241
  footer,
9154
9242
  sentinelRef: observerTarget,
9155
- loadingFilterShape: "search-action",
9156
- children: /* @__PURE__ */ jsx("div", {
9243
+ loadingFilterShape: "search-view",
9244
+ children: viewMode === "grid" ? /* @__PURE__ */ jsx("div", {
9157
9245
  className: GRID_CLASS,
9158
9246
  children: filteredPlaylists.map((playlist) => {
9159
9247
  const firstItem = playlist.items?.[0];
@@ -9175,6 +9263,61 @@ function PlaylistsListingScreen(_props) {
9175
9263
  onDelete: onDeletePlaylist ? () => setPendingDeleteId(playlist.id) : void 0
9176
9264
  }, playlist.id);
9177
9265
  })
9266
+ }) : /* @__PURE__ */ jsx("div", {
9267
+ className: SHAREABLE_LIST_CLASS,
9268
+ children: filteredPlaylists.map((playlist) => {
9269
+ const firstItem = playlist.items?.[0];
9270
+ const imageUrl = playlist.image_url ?? firstItem?.image_url ?? firstItem?.relateable?.image_url ?? firstItem?.relateable?.compressed_image_url;
9271
+ const itemCount = playlist.items_count ?? playlist.items?.length ?? 0;
9272
+ const canEdit = !readOnly && playlist.user_id === user?.id;
9273
+ const isSelected = selectedIds.has(playlist.id);
9274
+ const title = playlist.title || "Untitled Playlist";
9275
+ return /* @__PURE__ */ jsx(ShareableListRow, {
9276
+ imageUrl,
9277
+ title,
9278
+ subtitle: /* @__PURE__ */ jsxs(Badge, {
9279
+ variant: "secondary",
9280
+ className: "px-1.5 py-0 text-[10px] leading-4",
9281
+ children: [
9282
+ itemCount,
9283
+ " ",
9284
+ itemCount === 1 ? "item" : "items"
9285
+ ]
9286
+ }),
9287
+ onClick: () => navigate(`playlists/${playlist.id}`),
9288
+ leading: /* @__PURE__ */ jsx(Checkbox, {
9289
+ checked: isSelected,
9290
+ onCheckedChange: (checked) => handleToggleSelection(playlist.id, checked === true),
9291
+ "aria-label": `Select ${title}`
9292
+ }),
9293
+ trailing: /* @__PURE__ */ jsxs(Fragment$1, { children: [onToggleFavorite && /* @__PURE__ */ jsx("button", {
9294
+ type: "button",
9295
+ onClick: () => handleFavorite(playlist.id),
9296
+ "aria-label": playlist.is_favorited ? "Unfavorite" : "Favorite",
9297
+ className: "hover:bg-muted-foreground/10 flex h-8 w-8 items-center justify-center rounded-md transition-colors",
9298
+ children: /* @__PURE__ */ jsx(Heart, { className: cn("h-4 w-4 transition-colors", playlist.is_favorited ? "fill-destructive text-destructive" : "text-muted-foreground") })
9299
+ }), canEdit && /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
9300
+ asChild: true,
9301
+ children: /* @__PURE__ */ jsx(Button, {
9302
+ variant: "ghost",
9303
+ size: "sm",
9304
+ className: "h-8 w-8 p-0",
9305
+ "aria-label": "Playlist actions",
9306
+ children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-4 w-4" })
9307
+ })
9308
+ }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
9309
+ align: "end",
9310
+ children: [/* @__PURE__ */ jsxs(DropdownMenuItem, {
9311
+ onClick: () => handleEdit(playlist.id),
9312
+ children: [/* @__PURE__ */ jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Edit"]
9313
+ }), onDeletePlaylist && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(DropdownMenuSeparator, {}), /* @__PURE__ */ jsxs(DropdownMenuItem, {
9314
+ variant: "destructive",
9315
+ onClick: () => setPendingDeleteId(playlist.id),
9316
+ children: [/* @__PURE__ */ jsx(Trash2, { className: "mr-2 h-4 w-4" }), "Delete"]
9317
+ })] })]
9318
+ })] })] })
9319
+ }, playlist.id);
9320
+ })
9178
9321
  })
9179
9322
  }), /* @__PURE__ */ jsx(AlertDialog, {
9180
9323
  open: pendingDeleteId !== null,
@@ -10060,6 +10203,7 @@ function SortableTable({ items, setItems, onDeleteItem, isDeletePending, enableR
10060
10203
  }
10061
10204
  //#endregion
10062
10205
  //#region ../../shareables/ui/src/components/playlists/form/PlaylistItemsSection.tsx
10206
+ const REORDER_DEBOUNCE_MS = 500;
10063
10207
  const PLAYLIST_CONTENT_TYPES = new Set([
10064
10208
  "Medium",
10065
10209
  "Page",
@@ -10129,9 +10273,53 @@ function PlaylistItemsSection({ playlistId }) {
10129
10273
  });
10130
10274
  }
10131
10275
  });
10276
+ const reorderItemsMutation = useReorderPlaylistItemsMutation();
10277
+ const reorderTimerRef = useRef(null);
10278
+ const reorderRollbackRef = useRef(null);
10279
+ useEffect(() => () => {
10280
+ if (reorderTimerRef.current) clearTimeout(reorderTimerRef.current);
10281
+ }, []);
10282
+ const canPersistReorder = !!api.playlists.reorderPlaylistItems;
10132
10283
  const mergeAndOrder = useCallback((reorderedContent) => {
10133
- updateItems(computeOrderedItems([...reorderedContent, ...tailItems]));
10134
- }, [tailItems, updateItems]);
10284
+ const ordered = computeOrderedItems([...reorderedContent, ...tailItems]);
10285
+ if (playlistId && canPersistReorder && reorderTimerRef.current === null && reorderRollbackRef.current === null) reorderRollbackRef.current = tableItems;
10286
+ updateItems(ordered);
10287
+ if (!playlistId || !canPersistReorder) return;
10288
+ if (reorderTimerRef.current) clearTimeout(reorderTimerRef.current);
10289
+ reorderTimerRef.current = setTimeout(() => {
10290
+ reorderTimerRef.current = null;
10291
+ const rollback = reorderRollbackRef.current;
10292
+ reorderItemsMutation.mutate({
10293
+ playlistId,
10294
+ data: { items: ordered.filter((item) => Number.isInteger(item.id) && typeof item.order === "number").map((item) => ({
10295
+ id: item.id,
10296
+ order: item.order
10297
+ })) }
10298
+ }, {
10299
+ onSuccess: () => {
10300
+ if (reorderTimerRef.current === null) reorderRollbackRef.current = null;
10301
+ else reorderRollbackRef.current = ordered;
10302
+ },
10303
+ onError: (error) => {
10304
+ if (rollback) updateItems(rollback);
10305
+ reorderRollbackRef.current = null;
10306
+ showToast({
10307
+ title: "Failed to save new item order",
10308
+ type: "error",
10309
+ error
10310
+ });
10311
+ }
10312
+ });
10313
+ }, REORDER_DEBOUNCE_MS);
10314
+ }, [
10315
+ tailItems,
10316
+ tableItems,
10317
+ updateItems,
10318
+ playlistId,
10319
+ canPersistReorder,
10320
+ reorderItemsMutation.mutate,
10321
+ showToast
10322
+ ]);
10135
10323
  const handleDeleteItem = (itemId) => {
10136
10324
  removeItem(itemId);
10137
10325
  if (playlistId) removeItemMutation.mutate({
@@ -11485,6 +11673,9 @@ function createPlaylistsAdapter(client) {
11485
11673
  items: [],
11486
11674
  items_count: 0
11487
11675
  };
11676
+ },
11677
+ reorderPlaylistItems: async (playlistId, data) => {
11678
+ await playlists_items_reorder(client, playlistId, { items: data.items });
11488
11679
  }
11489
11680
  };
11490
11681
  }
@@ -11853,4 +12044,4 @@ function PortalContentApiProvider({ children }) {
11853
12044
  //#endregion
11854
12045
  export { ShareablesApp as a, ShareablesCoreProvider as c, ProductsApp as i, usePortalContentContext as n, useFilePickerApi as o, toggleFavorite as r, ShareablesUIProvider as s, PortalContentApiProvider as t };
11855
12046
 
11856
- //# sourceMappingURL=PortalContentApiProvider-C9FeVwRb.mjs.map
12047
+ //# sourceMappingURL=PortalContentApiProvider-x81DXmOR.mjs.map