@orion-studios/payload-studio 0.5.0-beta.39 → 0.5.0-beta.40

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.
@@ -1184,11 +1184,41 @@ var TestimonialsBlock = {
1184
1184
  type: "text",
1185
1185
  required: true
1186
1186
  },
1187
+ {
1188
+ name: "visibleCount",
1189
+ type: "number",
1190
+ defaultValue: 3,
1191
+ min: 1,
1192
+ max: 6,
1193
+ admin: {
1194
+ description: "How many testimonials to show at once.",
1195
+ step: 1
1196
+ }
1197
+ },
1198
+ {
1199
+ name: "autoRotate",
1200
+ type: "checkbox",
1201
+ defaultValue: true,
1202
+ admin: {
1203
+ description: "Automatically rotates through all testimonials."
1204
+ }
1205
+ },
1206
+ {
1207
+ name: "rotateIntervalSeconds",
1208
+ type: "number",
1209
+ defaultValue: 7,
1210
+ min: 2,
1211
+ max: 30,
1212
+ admin: {
1213
+ description: "How often to rotate (in seconds).",
1214
+ step: 1
1215
+ }
1216
+ },
1187
1217
  {
1188
1218
  name: "items",
1189
1219
  type: "array",
1190
1220
  minRows: 1,
1191
- maxRows: 6,
1221
+ maxRows: 30,
1192
1222
  fields: [
1193
1223
  {
1194
1224
  name: "quote",
@@ -17,7 +17,7 @@ import {
17
17
  defaultPageLayoutBlocks,
18
18
  sectionPresets,
19
19
  templateStarterPresets
20
- } from "../chunk-V72WMS7V.mjs";
20
+ } from "../chunk-I4NH636V.mjs";
21
21
  import "../chunk-SIL2J5MF.mjs";
22
22
  import "../chunk-6BWS3CLP.mjs";
23
23
  export {
@@ -151,8 +151,11 @@ var defaultNodeData = {
151
151
  },
152
152
  testimonials: {
153
153
  ...withSectionStyleDefaults({}),
154
+ autoRotate: true,
154
155
  items: [{ location: "City, ST", name: "Customer Name", quote: "Customer feedback goes here." }],
155
- title: "What Customers Say"
156
+ rotateIntervalSeconds: 7,
157
+ title: "What Customers Say",
158
+ visibleCount: 3
156
159
  },
157
160
  stats: withSectionStyleDefaults({
158
161
  items: [
@@ -1020,11 +1020,41 @@ var TestimonialsBlock = {
1020
1020
  type: "text",
1021
1021
  required: true
1022
1022
  },
1023
+ {
1024
+ name: "visibleCount",
1025
+ type: "number",
1026
+ defaultValue: 3,
1027
+ min: 1,
1028
+ max: 6,
1029
+ admin: {
1030
+ description: "How many testimonials to show at once.",
1031
+ step: 1
1032
+ }
1033
+ },
1034
+ {
1035
+ name: "autoRotate",
1036
+ type: "checkbox",
1037
+ defaultValue: true,
1038
+ admin: {
1039
+ description: "Automatically rotates through all testimonials."
1040
+ }
1041
+ },
1042
+ {
1043
+ name: "rotateIntervalSeconds",
1044
+ type: "number",
1045
+ defaultValue: 7,
1046
+ min: 2,
1047
+ max: 30,
1048
+ admin: {
1049
+ description: "How often to rotate (in seconds).",
1050
+ step: 1
1051
+ }
1052
+ },
1023
1053
  {
1024
1054
  name: "items",
1025
1055
  type: "array",
1026
1056
  minRows: 1,
1027
- maxRows: 6,
1057
+ maxRows: 30,
1028
1058
  fields: [
1029
1059
  {
1030
1060
  name: "quote",
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-N67KVM2S.mjs";
4
4
  import {
5
5
  studioDocumentToLayout
6
- } from "./chunk-7UWYJU4M.mjs";
6
+ } from "./chunk-34J4T7X3.mjs";
7
7
  import {
8
8
  __export
9
9
  } from "./chunk-6BWS3CLP.mjs";
package/dist/index.js CHANGED
@@ -1725,11 +1725,41 @@ var TestimonialsBlock = {
1725
1725
  type: "text",
1726
1726
  required: true
1727
1727
  },
1728
+ {
1729
+ name: "visibleCount",
1730
+ type: "number",
1731
+ defaultValue: 3,
1732
+ min: 1,
1733
+ max: 6,
1734
+ admin: {
1735
+ description: "How many testimonials to show at once.",
1736
+ step: 1
1737
+ }
1738
+ },
1739
+ {
1740
+ name: "autoRotate",
1741
+ type: "checkbox",
1742
+ defaultValue: true,
1743
+ admin: {
1744
+ description: "Automatically rotates through all testimonials."
1745
+ }
1746
+ },
1747
+ {
1748
+ name: "rotateIntervalSeconds",
1749
+ type: "number",
1750
+ defaultValue: 7,
1751
+ min: 2,
1752
+ max: 30,
1753
+ admin: {
1754
+ description: "How often to rotate (in seconds).",
1755
+ step: 1
1756
+ }
1757
+ },
1728
1758
  {
1729
1759
  name: "items",
1730
1760
  type: "array",
1731
1761
  minRows: 1,
1732
- maxRows: 6,
1762
+ maxRows: 30,
1733
1763
  fields: [
1734
1764
  {
1735
1765
  name: "quote",
@@ -2387,8 +2417,11 @@ var defaultNodeData = {
2387
2417
  },
2388
2418
  testimonials: {
2389
2419
  ...withSectionStyleDefaults({}),
2420
+ autoRotate: true,
2390
2421
  items: [{ location: "City, ST", name: "Customer Name", quote: "Customer feedback goes here." }],
2391
- title: "What Customers Say"
2422
+ rotateIntervalSeconds: 7,
2423
+ title: "What Customers Say",
2424
+ visibleCount: 3
2392
2425
  },
2393
2426
  stats: withSectionStyleDefaults({
2394
2427
  items: [
package/dist/index.mjs CHANGED
@@ -6,16 +6,16 @@ import {
6
6
  } from "./chunk-XVH5SCBD.mjs";
7
7
  import {
8
8
  blocks_exports
9
- } from "./chunk-V72WMS7V.mjs";
9
+ } from "./chunk-I4NH636V.mjs";
10
10
  import {
11
11
  nextjs_exports
12
- } from "./chunk-WIAJNENP.mjs";
12
+ } from "./chunk-RXXPFQWL.mjs";
13
13
  import {
14
14
  studio_exports
15
15
  } from "./chunk-N67KVM2S.mjs";
16
16
  import {
17
17
  studio_pages_exports
18
- } from "./chunk-7UWYJU4M.mjs";
18
+ } from "./chunk-34J4T7X3.mjs";
19
19
  import "./chunk-SIL2J5MF.mjs";
20
20
  import "./chunk-6BWS3CLP.mjs";
21
21
  export {
@@ -240,8 +240,11 @@ var defaultNodeData = {
240
240
  },
241
241
  testimonials: {
242
242
  ...withSectionStyleDefaults({}),
243
+ autoRotate: true,
243
244
  items: [{ location: "City, ST", name: "Customer Name", quote: "Customer feedback goes here." }],
244
- title: "What Customers Say"
245
+ rotateIntervalSeconds: 7,
246
+ title: "What Customers Say",
247
+ visibleCount: 3
245
248
  },
246
249
  stats: withSectionStyleDefaults({
247
250
  items: [
@@ -4,9 +4,9 @@ import {
4
4
  createPayloadClient,
5
5
  createSiteQueries,
6
6
  resolveMedia
7
- } from "../chunk-WIAJNENP.mjs";
7
+ } from "../chunk-RXXPFQWL.mjs";
8
8
  import "../chunk-N67KVM2S.mjs";
9
- import "../chunk-7UWYJU4M.mjs";
9
+ import "../chunk-34J4T7X3.mjs";
10
10
  import "../chunk-SIL2J5MF.mjs";
11
11
  import "../chunk-6BWS3CLP.mjs";
12
12
  export {
@@ -189,8 +189,11 @@ var defaultNodeData = {
189
189
  },
190
190
  testimonials: {
191
191
  ...withSectionStyleDefaults({}),
192
+ autoRotate: true,
192
193
  items: [{ location: "City, ST", name: "Customer Name", quote: "Customer feedback goes here." }],
193
- title: "What Customers Say"
194
+ rotateIntervalSeconds: 7,
195
+ title: "What Customers Say",
196
+ visibleCount: 3
194
197
  },
195
198
  stats: withSectionStyleDefaults({
196
199
  items: [
@@ -1385,6 +1388,7 @@ function BuilderPageEditor({ initialDoc, pageID }) {
1385
1388
  const [selectedHeroMediaID, setSelectedHeroMediaID] = (0, import_react.useState)("");
1386
1389
  const [selectedItemIndex, setSelectedItemIndex] = (0, import_react.useState)(null);
1387
1390
  const [expandedItemIndex, setExpandedItemIndex] = (0, import_react.useState)(null);
1391
+ const [testimonialsOffsets, setTestimonialsOffsets] = (0, import_react.useState)({});
1388
1392
  const [presetQuery, setPresetQuery] = (0, import_react.useState)("");
1389
1393
  const [pastSnapshots, setPastSnapshots] = (0, import_react.useState)([]);
1390
1394
  const [futureSnapshots, setFutureSnapshots] = (0, import_react.useState)([]);
@@ -2007,6 +2011,37 @@ function BuilderPageEditor({ initialDoc, pageID }) {
2007
2011
  setSidebarOpen(true);
2008
2012
  setActiveSidebarPanel("addSections");
2009
2013
  }, [layout.length]);
2014
+ (0, import_react.useEffect)(() => {
2015
+ const timers = [];
2016
+ layout.forEach((block, index) => {
2017
+ if (normalizeText(block.blockType, "") !== "testimonials") {
2018
+ return;
2019
+ }
2020
+ const key = typeof block.id === "string" && block.id ? String(block.id) : `testimonials-${index}`;
2021
+ const items = Array.isArray(block.items) ? block.items : [];
2022
+ const visibleCountRaw = Math.floor(parsePixelNumber(block.visibleCount, 3));
2023
+ const visibleCount = Math.max(1, Math.min(6, visibleCountRaw));
2024
+ const autoRotate = typeof block.autoRotate === "boolean" ? Boolean(block.autoRotate) : true;
2025
+ const intervalSeconds = Math.max(
2026
+ 2,
2027
+ Math.floor(parsePixelNumber(block.rotateIntervalSeconds, 7))
2028
+ );
2029
+ if (!autoRotate || items.length <= visibleCount) {
2030
+ return;
2031
+ }
2032
+ const timer = window.setInterval(() => {
2033
+ setTestimonialsOffsets((current) => {
2034
+ const offset = typeof current[key] === "number" ? current[key] : 0;
2035
+ const next = (offset + visibleCount) % items.length;
2036
+ return offset === next ? current : { ...current, [key]: next };
2037
+ });
2038
+ }, intervalSeconds * 1e3);
2039
+ timers.push(timer);
2040
+ });
2041
+ return () => {
2042
+ timers.forEach((timer) => window.clearInterval(timer));
2043
+ };
2044
+ }, [layout]);
2010
2045
  (0, import_react.useEffect)(() => {
2011
2046
  if (historyBypassRef.current) {
2012
2047
  historyBypassRef.current = false;
@@ -2796,6 +2831,15 @@ function BuilderPageEditor({ initialDoc, pageID }) {
2796
2831
  }
2797
2832
  if (type === "testimonials") {
2798
2833
  const items = Array.isArray(block.items) ? block.items : [];
2834
+ const visibleCountRaw = Math.floor(parsePixelNumber(block.visibleCount, 3));
2835
+ const visibleCount = Math.max(1, Math.min(6, visibleCountRaw));
2836
+ const autoRotate = typeof block.autoRotate === "boolean" ? Boolean(block.autoRotate) : true;
2837
+ const key = typeof block.id === "string" && block.id ? String(block.id) : `testimonials-${index}`;
2838
+ const offset = typeof testimonialsOffsets[key] === "number" ? testimonialsOffsets[key] : 0;
2839
+ const windowedItems = !autoRotate || items.length <= visibleCount ? items.map((item, itemIndex) => ({ item, itemIndex })) : Array.from({ length: visibleCount }, (_, slotIndex) => {
2840
+ const itemIndex = (offset + slotIndex) % items.length;
2841
+ return { item: items[itemIndex], itemIndex };
2842
+ });
2799
2843
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2800
2844
  BlockFrame,
2801
2845
  {
@@ -2819,45 +2863,60 @@ function BuilderPageEditor({ initialDoc, pageID }) {
2819
2863
  value: normalizeText(block.title)
2820
2864
  }
2821
2865
  ) }) }),
2822
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "split testimonials-grid", children: items.map((item, itemIndex) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("article", { className: "panel", children: [
2823
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "quote", children: [
2824
- '"',
2825
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2826
- InlineText,
2827
- {
2828
- as: "span",
2829
- multiline: true,
2830
- onCommit: (value) => updateArrayItemField(index, "items", itemIndex, "quote", value),
2831
- placeholder: "Customer quote",
2832
- value: normalizeText(item?.quote)
2833
- }
2834
- ),
2835
- '"'
2836
- ] }),
2837
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "quote-author", children: [
2838
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "quote-avatar", children: normalizeText(item?.name, "C").split(" ").slice(0, 2).map((part) => part[0]).join("").toUpperCase() }),
2839
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
2840
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2841
- InlineText,
2842
- {
2843
- as: "span",
2844
- onCommit: (value) => updateArrayItemField(index, "items", itemIndex, "name", value),
2845
- placeholder: "Customer name",
2846
- value: normalizeText(item?.name)
2847
- }
2848
- ),
2849
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { color: "var(--ink-500)", fontSize: "0.88rem" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2850
- InlineText,
2851
- {
2852
- as: "span",
2853
- onCommit: (value) => updateArrayItemField(index, "items", itemIndex, "location", value),
2854
- placeholder: "Location",
2855
- value: normalizeText(item?.location)
2856
- }
2857
- ) })
2858
- ] })
2859
- ] })
2860
- ] }, `testimonial-${itemIndex}`)) })
2866
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "split testimonials-grid", children: windowedItems.map(({ item, itemIndex }) => {
2867
+ const isItemSelected = selectedIndex === index && selectedItemIndex === itemIndex;
2868
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2869
+ "article",
2870
+ {
2871
+ className: "panel",
2872
+ onMouseDownCapture: () => {
2873
+ setSelectedIndex(index);
2874
+ openSelectedItem(itemIndex);
2875
+ },
2876
+ style: isItemSelected ? { outline: "2px solid rgba(15, 125, 82, 0.55)", outlineOffset: 3 } : void 0,
2877
+ children: [
2878
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "quote", children: [
2879
+ '"',
2880
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2881
+ InlineText,
2882
+ {
2883
+ as: "span",
2884
+ multiline: true,
2885
+ onCommit: (value) => updateArrayItemField(index, "items", itemIndex, "quote", value),
2886
+ placeholder: "Customer quote",
2887
+ value: normalizeText(item?.quote)
2888
+ }
2889
+ ),
2890
+ '"'
2891
+ ] }),
2892
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "quote-author", children: [
2893
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "quote-avatar", children: normalizeText(item?.name, "C").split(" ").slice(0, 2).map((part) => part[0]).join("").toUpperCase() }),
2894
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
2895
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2896
+ InlineText,
2897
+ {
2898
+ as: "span",
2899
+ onCommit: (value) => updateArrayItemField(index, "items", itemIndex, "name", value),
2900
+ placeholder: "Customer name",
2901
+ value: normalizeText(item?.name)
2902
+ }
2903
+ ),
2904
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { color: "var(--ink-500)", fontSize: "0.88rem" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2905
+ InlineText,
2906
+ {
2907
+ as: "span",
2908
+ onCommit: (value) => updateArrayItemField(index, "items", itemIndex, "location", value),
2909
+ placeholder: "Location",
2910
+ value: normalizeText(item?.location)
2911
+ }
2912
+ ) })
2913
+ ] })
2914
+ ] })
2915
+ ]
2916
+ },
2917
+ `testimonial-${itemIndex}`
2918
+ );
2919
+ }) })
2861
2920
  ] })
2862
2921
  )
2863
2922
  },
@@ -5664,19 +5723,157 @@ function BuilderPageEditor({ initialDoc, pageID }) {
5664
5723
  children: "Add FAQ Item"
5665
5724
  }
5666
5725
  ) : null,
5667
- selectedType === "testimonials" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5668
- "button",
5669
- {
5670
- onClick: () => appendItemToSelected("items", {
5671
- location: "City, ST",
5672
- name: "Customer Name",
5673
- quote: "Add testimonial text."
5674
- }),
5675
- style: { borderRadius: 999, cursor: "pointer", fontSize: 12, padding: "7px 10px" },
5676
- type: "button",
5677
- children: "Add Testimonial"
5678
- }
5679
- ) : null,
5726
+ selectedType === "testimonials" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
5727
+ (() => {
5728
+ const visibleCountValue = Math.max(
5729
+ 1,
5730
+ Math.min(6, Math.floor(parsePixelNumber(selectedBlock.visibleCount, 3)))
5731
+ );
5732
+ const rotateIntervalValue = Math.max(
5733
+ 2,
5734
+ Math.min(30, Math.floor(parsePixelNumber(selectedBlock.rotateIntervalSeconds, 7)))
5735
+ );
5736
+ const autoRotateValue = typeof selectedBlock.autoRotate === "boolean" ? selectedBlock.autoRotate : true;
5737
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
5738
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: sidebarLabelStyle, children: [
5739
+ "Visible At Once",
5740
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5741
+ "input",
5742
+ {
5743
+ max: 6,
5744
+ min: 1,
5745
+ onChange: (event) => updateSelectedField("visibleCount", Number(event.target.value)),
5746
+ style: sidebarInputStyle,
5747
+ type: "number",
5748
+ value: visibleCountValue
5749
+ }
5750
+ )
5751
+ ] }),
5752
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { ...sidebarLabelStyle, alignItems: "center", gridTemplateColumns: "auto 1fr" }, children: [
5753
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5754
+ "input",
5755
+ {
5756
+ checked: autoRotateValue,
5757
+ onChange: (event) => updateSelectedField("autoRotate", event.target.checked),
5758
+ type: "checkbox"
5759
+ }
5760
+ ),
5761
+ "Auto Rotate"
5762
+ ] }),
5763
+ autoRotateValue ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: sidebarLabelStyle, children: [
5764
+ "Rotate Interval (seconds)",
5765
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5766
+ "input",
5767
+ {
5768
+ max: 30,
5769
+ min: 2,
5770
+ onChange: (event) => updateSelectedField("rotateIntervalSeconds", Number(event.target.value)),
5771
+ style: sidebarInputStyle,
5772
+ type: "number",
5773
+ value: rotateIntervalValue
5774
+ }
5775
+ )
5776
+ ] }) : null
5777
+ ] });
5778
+ })(),
5779
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5780
+ "button",
5781
+ {
5782
+ onClick: () => appendItemToSelected("items", {
5783
+ location: "City, ST",
5784
+ name: "Customer Name",
5785
+ quote: "Add testimonial text."
5786
+ }),
5787
+ style: { borderRadius: 999, cursor: "pointer", fontSize: 12, padding: "7px 10px" },
5788
+ type: "button",
5789
+ children: "Add Testimonial"
5790
+ }
5791
+ ),
5792
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "grid", gap: 8 }, children: selectedItems.map((item, itemIndex) => {
5793
+ const isOpen = expandedItemIndex === itemIndex;
5794
+ const label = normalizeText(item.name, "") || `Testimonial ${itemIndex + 1}`;
5795
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
5796
+ "div",
5797
+ {
5798
+ style: {
5799
+ border: "1px solid rgba(13, 74, 55, 0.16)",
5800
+ borderRadius: 10,
5801
+ display: "grid",
5802
+ gap: 8,
5803
+ padding: 8
5804
+ },
5805
+ children: [
5806
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
5807
+ "button",
5808
+ {
5809
+ onClick: () => toggleSelectedItem(itemIndex),
5810
+ style: {
5811
+ alignItems: "center",
5812
+ background: isOpen ? "rgba(18, 74, 55, 0.06)" : "transparent",
5813
+ border: "none",
5814
+ cursor: "pointer",
5815
+ display: "flex",
5816
+ fontSize: 12,
5817
+ fontWeight: 800,
5818
+ gap: 8,
5819
+ justifyContent: "space-between",
5820
+ padding: "6px 8px",
5821
+ textAlign: "left",
5822
+ width: "100%"
5823
+ },
5824
+ type: "button",
5825
+ children: [
5826
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: label }),
5827
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--ink-700)", fontSize: 11 }, children: isOpen ? "Hide" : "Show" })
5828
+ ]
5829
+ }
5830
+ ),
5831
+ isOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "grid", gap: 6 }, children: [
5832
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5833
+ "textarea",
5834
+ {
5835
+ onChange: (event) => updateArrayItemField(selectedIndex ?? 0, "items", itemIndex, "quote", event.target.value),
5836
+ placeholder: "Customer quote",
5837
+ style: { ...sidebarInputStyle, minHeight: 110, resize: "vertical" },
5838
+ value: normalizeText(item.quote)
5839
+ }
5840
+ ),
5841
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5842
+ "input",
5843
+ {
5844
+ onChange: (event) => updateArrayItemField(selectedIndex ?? 0, "items", itemIndex, "name", event.target.value),
5845
+ placeholder: "Customer name",
5846
+ style: sidebarInputStyle,
5847
+ type: "text",
5848
+ value: normalizeText(item.name)
5849
+ }
5850
+ ),
5851
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5852
+ "input",
5853
+ {
5854
+ onChange: (event) => updateArrayItemField(selectedIndex ?? 0, "items", itemIndex, "location", event.target.value),
5855
+ placeholder: "Location",
5856
+ style: sidebarInputStyle,
5857
+ type: "text",
5858
+ value: normalizeText(item.location)
5859
+ }
5860
+ ),
5861
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
5862
+ "button",
5863
+ {
5864
+ onClick: () => removeItemFromSelected("items", itemIndex),
5865
+ style: { borderRadius: 999, cursor: "pointer", fontSize: 12, padding: "7px 10px" },
5866
+ type: "button",
5867
+ children: "Remove Testimonial"
5868
+ }
5869
+ )
5870
+ ] }) : null
5871
+ ]
5872
+ },
5873
+ `testimonial-item-control-${itemIndex}`
5874
+ );
5875
+ }) })
5876
+ ] }) : null,
5680
5877
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: sidebarLabelStyle, children: [
5681
5878
  "Upload Alt Text",
5682
5879
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -161,8 +161,11 @@ var defaultNodeData = {
161
161
  },
162
162
  testimonials: {
163
163
  ...withSectionStyleDefaults({}),
164
+ autoRotate: true,
164
165
  items: [{ location: "City, ST", name: "Customer Name", quote: "Customer feedback goes here." }],
165
- title: "What Customers Say"
166
+ rotateIntervalSeconds: 7,
167
+ title: "What Customers Say",
168
+ visibleCount: 3
166
169
  },
167
170
  stats: withSectionStyleDefaults({
168
171
  items: [
@@ -1357,6 +1360,7 @@ function BuilderPageEditor({ initialDoc, pageID }) {
1357
1360
  const [selectedHeroMediaID, setSelectedHeroMediaID] = useState("");
1358
1361
  const [selectedItemIndex, setSelectedItemIndex] = useState(null);
1359
1362
  const [expandedItemIndex, setExpandedItemIndex] = useState(null);
1363
+ const [testimonialsOffsets, setTestimonialsOffsets] = useState({});
1360
1364
  const [presetQuery, setPresetQuery] = useState("");
1361
1365
  const [pastSnapshots, setPastSnapshots] = useState([]);
1362
1366
  const [futureSnapshots, setFutureSnapshots] = useState([]);
@@ -1979,6 +1983,37 @@ function BuilderPageEditor({ initialDoc, pageID }) {
1979
1983
  setSidebarOpen(true);
1980
1984
  setActiveSidebarPanel("addSections");
1981
1985
  }, [layout.length]);
1986
+ useEffect(() => {
1987
+ const timers = [];
1988
+ layout.forEach((block, index) => {
1989
+ if (normalizeText(block.blockType, "") !== "testimonials") {
1990
+ return;
1991
+ }
1992
+ const key = typeof block.id === "string" && block.id ? String(block.id) : `testimonials-${index}`;
1993
+ const items = Array.isArray(block.items) ? block.items : [];
1994
+ const visibleCountRaw = Math.floor(parsePixelNumber(block.visibleCount, 3));
1995
+ const visibleCount = Math.max(1, Math.min(6, visibleCountRaw));
1996
+ const autoRotate = typeof block.autoRotate === "boolean" ? Boolean(block.autoRotate) : true;
1997
+ const intervalSeconds = Math.max(
1998
+ 2,
1999
+ Math.floor(parsePixelNumber(block.rotateIntervalSeconds, 7))
2000
+ );
2001
+ if (!autoRotate || items.length <= visibleCount) {
2002
+ return;
2003
+ }
2004
+ const timer = window.setInterval(() => {
2005
+ setTestimonialsOffsets((current) => {
2006
+ const offset = typeof current[key] === "number" ? current[key] : 0;
2007
+ const next = (offset + visibleCount) % items.length;
2008
+ return offset === next ? current : { ...current, [key]: next };
2009
+ });
2010
+ }, intervalSeconds * 1e3);
2011
+ timers.push(timer);
2012
+ });
2013
+ return () => {
2014
+ timers.forEach((timer) => window.clearInterval(timer));
2015
+ };
2016
+ }, [layout]);
1982
2017
  useEffect(() => {
1983
2018
  if (historyBypassRef.current) {
1984
2019
  historyBypassRef.current = false;
@@ -2768,6 +2803,15 @@ function BuilderPageEditor({ initialDoc, pageID }) {
2768
2803
  }
2769
2804
  if (type === "testimonials") {
2770
2805
  const items = Array.isArray(block.items) ? block.items : [];
2806
+ const visibleCountRaw = Math.floor(parsePixelNumber(block.visibleCount, 3));
2807
+ const visibleCount = Math.max(1, Math.min(6, visibleCountRaw));
2808
+ const autoRotate = typeof block.autoRotate === "boolean" ? Boolean(block.autoRotate) : true;
2809
+ const key = typeof block.id === "string" && block.id ? String(block.id) : `testimonials-${index}`;
2810
+ const offset = typeof testimonialsOffsets[key] === "number" ? testimonialsOffsets[key] : 0;
2811
+ const windowedItems = !autoRotate || items.length <= visibleCount ? items.map((item, itemIndex) => ({ item, itemIndex })) : Array.from({ length: visibleCount }, (_, slotIndex) => {
2812
+ const itemIndex = (offset + slotIndex) % items.length;
2813
+ return { item: items[itemIndex], itemIndex };
2814
+ });
2771
2815
  return /* @__PURE__ */ jsx(
2772
2816
  BlockFrame,
2773
2817
  {
@@ -2791,45 +2835,60 @@ function BuilderPageEditor({ initialDoc, pageID }) {
2791
2835
  value: normalizeText(block.title)
2792
2836
  }
2793
2837
  ) }) }),
2794
- /* @__PURE__ */ jsx("div", { className: "split testimonials-grid", children: items.map((item, itemIndex) => /* @__PURE__ */ jsxs("article", { className: "panel", children: [
2795
- /* @__PURE__ */ jsxs("p", { className: "quote", children: [
2796
- '"',
2797
- /* @__PURE__ */ jsx(
2798
- InlineText,
2799
- {
2800
- as: "span",
2801
- multiline: true,
2802
- onCommit: (value) => updateArrayItemField(index, "items", itemIndex, "quote", value),
2803
- placeholder: "Customer quote",
2804
- value: normalizeText(item?.quote)
2805
- }
2806
- ),
2807
- '"'
2808
- ] }),
2809
- /* @__PURE__ */ jsxs("div", { className: "quote-author", children: [
2810
- /* @__PURE__ */ jsx("div", { className: "quote-avatar", children: normalizeText(item?.name, "C").split(" ").slice(0, 2).map((part) => part[0]).join("").toUpperCase() }),
2811
- /* @__PURE__ */ jsxs("div", { children: [
2812
- /* @__PURE__ */ jsx(
2813
- InlineText,
2814
- {
2815
- as: "span",
2816
- onCommit: (value) => updateArrayItemField(index, "items", itemIndex, "name", value),
2817
- placeholder: "Customer name",
2818
- value: normalizeText(item?.name)
2819
- }
2820
- ),
2821
- /* @__PURE__ */ jsx("div", { style: { color: "var(--ink-500)", fontSize: "0.88rem" }, children: /* @__PURE__ */ jsx(
2822
- InlineText,
2823
- {
2824
- as: "span",
2825
- onCommit: (value) => updateArrayItemField(index, "items", itemIndex, "location", value),
2826
- placeholder: "Location",
2827
- value: normalizeText(item?.location)
2828
- }
2829
- ) })
2830
- ] })
2831
- ] })
2832
- ] }, `testimonial-${itemIndex}`)) })
2838
+ /* @__PURE__ */ jsx("div", { className: "split testimonials-grid", children: windowedItems.map(({ item, itemIndex }) => {
2839
+ const isItemSelected = selectedIndex === index && selectedItemIndex === itemIndex;
2840
+ return /* @__PURE__ */ jsxs(
2841
+ "article",
2842
+ {
2843
+ className: "panel",
2844
+ onMouseDownCapture: () => {
2845
+ setSelectedIndex(index);
2846
+ openSelectedItem(itemIndex);
2847
+ },
2848
+ style: isItemSelected ? { outline: "2px solid rgba(15, 125, 82, 0.55)", outlineOffset: 3 } : void 0,
2849
+ children: [
2850
+ /* @__PURE__ */ jsxs("p", { className: "quote", children: [
2851
+ '"',
2852
+ /* @__PURE__ */ jsx(
2853
+ InlineText,
2854
+ {
2855
+ as: "span",
2856
+ multiline: true,
2857
+ onCommit: (value) => updateArrayItemField(index, "items", itemIndex, "quote", value),
2858
+ placeholder: "Customer quote",
2859
+ value: normalizeText(item?.quote)
2860
+ }
2861
+ ),
2862
+ '"'
2863
+ ] }),
2864
+ /* @__PURE__ */ jsxs("div", { className: "quote-author", children: [
2865
+ /* @__PURE__ */ jsx("div", { className: "quote-avatar", children: normalizeText(item?.name, "C").split(" ").slice(0, 2).map((part) => part[0]).join("").toUpperCase() }),
2866
+ /* @__PURE__ */ jsxs("div", { children: [
2867
+ /* @__PURE__ */ jsx(
2868
+ InlineText,
2869
+ {
2870
+ as: "span",
2871
+ onCommit: (value) => updateArrayItemField(index, "items", itemIndex, "name", value),
2872
+ placeholder: "Customer name",
2873
+ value: normalizeText(item?.name)
2874
+ }
2875
+ ),
2876
+ /* @__PURE__ */ jsx("div", { style: { color: "var(--ink-500)", fontSize: "0.88rem" }, children: /* @__PURE__ */ jsx(
2877
+ InlineText,
2878
+ {
2879
+ as: "span",
2880
+ onCommit: (value) => updateArrayItemField(index, "items", itemIndex, "location", value),
2881
+ placeholder: "Location",
2882
+ value: normalizeText(item?.location)
2883
+ }
2884
+ ) })
2885
+ ] })
2886
+ ] })
2887
+ ]
2888
+ },
2889
+ `testimonial-${itemIndex}`
2890
+ );
2891
+ }) })
2833
2892
  ] })
2834
2893
  )
2835
2894
  },
@@ -5636,19 +5695,157 @@ function BuilderPageEditor({ initialDoc, pageID }) {
5636
5695
  children: "Add FAQ Item"
5637
5696
  }
5638
5697
  ) : null,
5639
- selectedType === "testimonials" ? /* @__PURE__ */ jsx(
5640
- "button",
5641
- {
5642
- onClick: () => appendItemToSelected("items", {
5643
- location: "City, ST",
5644
- name: "Customer Name",
5645
- quote: "Add testimonial text."
5646
- }),
5647
- style: { borderRadius: 999, cursor: "pointer", fontSize: 12, padding: "7px 10px" },
5648
- type: "button",
5649
- children: "Add Testimonial"
5650
- }
5651
- ) : null,
5698
+ selectedType === "testimonials" ? /* @__PURE__ */ jsxs(Fragment, { children: [
5699
+ (() => {
5700
+ const visibleCountValue = Math.max(
5701
+ 1,
5702
+ Math.min(6, Math.floor(parsePixelNumber(selectedBlock.visibleCount, 3)))
5703
+ );
5704
+ const rotateIntervalValue = Math.max(
5705
+ 2,
5706
+ Math.min(30, Math.floor(parsePixelNumber(selectedBlock.rotateIntervalSeconds, 7)))
5707
+ );
5708
+ const autoRotateValue = typeof selectedBlock.autoRotate === "boolean" ? selectedBlock.autoRotate : true;
5709
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5710
+ /* @__PURE__ */ jsxs("label", { style: sidebarLabelStyle, children: [
5711
+ "Visible At Once",
5712
+ /* @__PURE__ */ jsx(
5713
+ "input",
5714
+ {
5715
+ max: 6,
5716
+ min: 1,
5717
+ onChange: (event) => updateSelectedField("visibleCount", Number(event.target.value)),
5718
+ style: sidebarInputStyle,
5719
+ type: "number",
5720
+ value: visibleCountValue
5721
+ }
5722
+ )
5723
+ ] }),
5724
+ /* @__PURE__ */ jsxs("label", { style: { ...sidebarLabelStyle, alignItems: "center", gridTemplateColumns: "auto 1fr" }, children: [
5725
+ /* @__PURE__ */ jsx(
5726
+ "input",
5727
+ {
5728
+ checked: autoRotateValue,
5729
+ onChange: (event) => updateSelectedField("autoRotate", event.target.checked),
5730
+ type: "checkbox"
5731
+ }
5732
+ ),
5733
+ "Auto Rotate"
5734
+ ] }),
5735
+ autoRotateValue ? /* @__PURE__ */ jsxs("label", { style: sidebarLabelStyle, children: [
5736
+ "Rotate Interval (seconds)",
5737
+ /* @__PURE__ */ jsx(
5738
+ "input",
5739
+ {
5740
+ max: 30,
5741
+ min: 2,
5742
+ onChange: (event) => updateSelectedField("rotateIntervalSeconds", Number(event.target.value)),
5743
+ style: sidebarInputStyle,
5744
+ type: "number",
5745
+ value: rotateIntervalValue
5746
+ }
5747
+ )
5748
+ ] }) : null
5749
+ ] });
5750
+ })(),
5751
+ /* @__PURE__ */ jsx(
5752
+ "button",
5753
+ {
5754
+ onClick: () => appendItemToSelected("items", {
5755
+ location: "City, ST",
5756
+ name: "Customer Name",
5757
+ quote: "Add testimonial text."
5758
+ }),
5759
+ style: { borderRadius: 999, cursor: "pointer", fontSize: 12, padding: "7px 10px" },
5760
+ type: "button",
5761
+ children: "Add Testimonial"
5762
+ }
5763
+ ),
5764
+ /* @__PURE__ */ jsx("div", { style: { display: "grid", gap: 8 }, children: selectedItems.map((item, itemIndex) => {
5765
+ const isOpen = expandedItemIndex === itemIndex;
5766
+ const label = normalizeText(item.name, "") || `Testimonial ${itemIndex + 1}`;
5767
+ return /* @__PURE__ */ jsxs(
5768
+ "div",
5769
+ {
5770
+ style: {
5771
+ border: "1px solid rgba(13, 74, 55, 0.16)",
5772
+ borderRadius: 10,
5773
+ display: "grid",
5774
+ gap: 8,
5775
+ padding: 8
5776
+ },
5777
+ children: [
5778
+ /* @__PURE__ */ jsxs(
5779
+ "button",
5780
+ {
5781
+ onClick: () => toggleSelectedItem(itemIndex),
5782
+ style: {
5783
+ alignItems: "center",
5784
+ background: isOpen ? "rgba(18, 74, 55, 0.06)" : "transparent",
5785
+ border: "none",
5786
+ cursor: "pointer",
5787
+ display: "flex",
5788
+ fontSize: 12,
5789
+ fontWeight: 800,
5790
+ gap: 8,
5791
+ justifyContent: "space-between",
5792
+ padding: "6px 8px",
5793
+ textAlign: "left",
5794
+ width: "100%"
5795
+ },
5796
+ type: "button",
5797
+ children: [
5798
+ /* @__PURE__ */ jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: label }),
5799
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--ink-700)", fontSize: 11 }, children: isOpen ? "Hide" : "Show" })
5800
+ ]
5801
+ }
5802
+ ),
5803
+ isOpen ? /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 6 }, children: [
5804
+ /* @__PURE__ */ jsx(
5805
+ "textarea",
5806
+ {
5807
+ onChange: (event) => updateArrayItemField(selectedIndex ?? 0, "items", itemIndex, "quote", event.target.value),
5808
+ placeholder: "Customer quote",
5809
+ style: { ...sidebarInputStyle, minHeight: 110, resize: "vertical" },
5810
+ value: normalizeText(item.quote)
5811
+ }
5812
+ ),
5813
+ /* @__PURE__ */ jsx(
5814
+ "input",
5815
+ {
5816
+ onChange: (event) => updateArrayItemField(selectedIndex ?? 0, "items", itemIndex, "name", event.target.value),
5817
+ placeholder: "Customer name",
5818
+ style: sidebarInputStyle,
5819
+ type: "text",
5820
+ value: normalizeText(item.name)
5821
+ }
5822
+ ),
5823
+ /* @__PURE__ */ jsx(
5824
+ "input",
5825
+ {
5826
+ onChange: (event) => updateArrayItemField(selectedIndex ?? 0, "items", itemIndex, "location", event.target.value),
5827
+ placeholder: "Location",
5828
+ style: sidebarInputStyle,
5829
+ type: "text",
5830
+ value: normalizeText(item.location)
5831
+ }
5832
+ ),
5833
+ /* @__PURE__ */ jsx(
5834
+ "button",
5835
+ {
5836
+ onClick: () => removeItemFromSelected("items", itemIndex),
5837
+ style: { borderRadius: 999, cursor: "pointer", fontSize: 12, padding: "7px 10px" },
5838
+ type: "button",
5839
+ children: "Remove Testimonial"
5840
+ }
5841
+ )
5842
+ ] }) : null
5843
+ ]
5844
+ },
5845
+ `testimonial-item-control-${itemIndex}`
5846
+ );
5847
+ }) })
5848
+ ] }) : null,
5652
5849
  /* @__PURE__ */ jsxs("label", { style: sidebarLabelStyle, children: [
5653
5850
  "Upload Alt Text",
5654
5851
  /* @__PURE__ */ jsx(
@@ -186,8 +186,11 @@ var defaultNodeData = {
186
186
  },
187
187
  testimonials: {
188
188
  ...withSectionStyleDefaults({}),
189
+ autoRotate: true,
189
190
  items: [{ location: "City, ST", name: "Customer Name", quote: "Customer feedback goes here." }],
190
- title: "What Customers Say"
191
+ rotateIntervalSeconds: 7,
192
+ title: "What Customers Say",
193
+ visibleCount: 3
191
194
  },
192
195
  stats: withSectionStyleDefaults({
193
196
  items: [
@@ -6,7 +6,7 @@ import {
6
6
  pagePaletteGroups,
7
7
  pageStudioModuleManifest,
8
8
  studioDocumentToLayout
9
- } from "../chunk-7UWYJU4M.mjs";
9
+ } from "../chunk-34J4T7X3.mjs";
10
10
  import "../chunk-SIL2J5MF.mjs";
11
11
  import "../chunk-6BWS3CLP.mjs";
12
12
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orion-studios/payload-studio",
3
- "version": "0.5.0-beta.39",
3
+ "version": "0.5.0-beta.40",
4
4
  "description": "Unified Payload CMS toolkit for Orion Studios",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./dist/index.js",