@bison-lab/payload-blocks 3.1.0 → 3.3.0

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.
package/dist/index.mjs CHANGED
@@ -323,21 +323,27 @@ const ProcessStepsBlock = {
323
323
  * puts the least-optional copy behind a click. The field names match the
324
324
  * `@bison-lab/ui` prop names so the renderers stay a pass-through.
325
325
  */
326
- function headingFields({ required = true, eyebrowDescription = "Small label above the heading. Leave empty to hide the row." } = {}) {
326
+ function headingFields({ required = true, eyebrowDescription = "Small label above the heading. Leave empty to hide the row.", condition } = {}) {
327
+ const when = condition ? { condition } : {};
327
328
  return [
328
329
  {
329
330
  name: "eyebrow",
330
331
  type: "text",
331
- admin: { description: eyebrowDescription }
332
+ admin: {
333
+ description: eyebrowDescription,
334
+ ...when
335
+ }
332
336
  },
333
337
  {
334
338
  name: "title",
335
339
  type: "text",
336
- required
340
+ required,
341
+ admin: when
337
342
  },
338
343
  {
339
344
  name: "description",
340
- type: "textarea"
345
+ type: "textarea",
346
+ admin: when
341
347
  }
342
348
  ];
343
349
  }
@@ -641,6 +647,442 @@ const NapBlock = {
641
647
  ]
642
648
  };
643
649
  //#endregion
650
+ //#region src/fields/row-limits.ts
651
+ /**
652
+ * A custom `validate` replaces Payload's stock array check, so any array
653
+ * with its own rule re-applies the row limits first or `minRows` and
654
+ * `maxRows` stop being enforced. Same messages as the stock check, through
655
+ * the request's translator when the admin supplies one.
656
+ */
657
+ function rowLimits(value, { minRows, maxRows, required, req }) {
658
+ const count = value?.length ?? 0;
659
+ const t = req?.t;
660
+ if (required && count === 0) return t ? t("validation:required") : "This field is required.";
661
+ if (minRows && count < minRows) return t ? t("validation:requiresAtLeast", {
662
+ count: minRows,
663
+ label: t("general:rows")
664
+ }) : `This field requires at least ${minRows} rows.`;
665
+ if (maxRows && count > maxRows) return t ? t("validation:requiresNoMoreThan", {
666
+ count: maxRows,
667
+ label: t("general:rows")
668
+ }) : `This field requires no more than ${maxRows} rows.`;
669
+ return true;
670
+ }
671
+ //#endregion
672
+ //#region src/blocks/stats-band/config.ts
673
+ /**
674
+ * The rule on `wide`: on phones the band is two per row, so only an odd
675
+ * count has a stat on a row of its own, only one stat can take it, and it
676
+ * has to be a stat that starts a row (the 1st, 3rd or 5th), since a full-row
677
+ * cell beside another cell would leave a hole. The array's own `validate`,
678
+ * so the editor sees the message as they build, and Payload runs it again on
679
+ * publish.
680
+ */
681
+ const validateWideFlag = (value, options) => {
682
+ const limits = rowLimits(value, options);
683
+ if (limits !== true) return limits;
684
+ const rows = value ?? [];
685
+ const wide = rows.flatMap((row, i) => row?.wide ? [i] : []);
686
+ if (wide.length > 1) return "Only one stat can be full width on phones.";
687
+ if (wide.length === 0) return true;
688
+ if (rows.length % 2 === 0) return "Full width on phones only applies to an odd number of stats. With an even count every row is already full.";
689
+ if (wide[0] % 2 !== 0) return "Only a stat that starts a phone row can be full width: the 1st, 3rd or 5th. Move it up or down one place, or flag another.";
690
+ return true;
691
+ };
692
+ /**
693
+ * An overlapping band is headless: it floats across the seam as a card, and a
694
+ * heading pulled up into the block above would land on the wrong surface.
695
+ * The heading fields disappear from the admin the moment `overlap` is on,
696
+ * and the renderer ignores them if they were filled in first.
697
+ */
698
+ const unlessOverlap = (_data, siblingData) => !siblingData?.overlap;
699
+ /**
700
+ * A strip of figures: the facts a visitor should take in at a glance.
701
+ *
702
+ * The count, the order and the desktop column count are the editor's. The
703
+ * phone and tablet layouts are the library's: two per row and three per row,
704
+ * with `wide` naming the one stat that takes a full phone row when the count
705
+ * is odd.
706
+ */
707
+ const StatsBandBlock = {
708
+ slug: "statsBand",
709
+ interfaceName: "StatsBandBlock",
710
+ labels: {
711
+ singular: "Stats Band",
712
+ plural: "Stats Bands"
713
+ },
714
+ fields: [
715
+ ...headingFields({
716
+ required: false,
717
+ eyebrowDescription: "Small label above the heading. The heading and eyebrow are optional; without them the band stands alone.",
718
+ condition: unlessOverlap
719
+ }),
720
+ {
721
+ name: "items",
722
+ type: "array",
723
+ required: true,
724
+ minRows: 2,
725
+ maxRows: 6,
726
+ defaultValue: emptyRows(2),
727
+ labels: {
728
+ singular: "Stat",
729
+ plural: "Stats"
730
+ },
731
+ admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } },
732
+ validate: validateWideFlag,
733
+ fields: [
734
+ {
735
+ name: "value",
736
+ type: "text",
737
+ required: true,
738
+ admin: { description: "The figure, as it should read: \"98%\", \"12,000+\", \"L1–S1\"." }
739
+ },
740
+ {
741
+ name: "label",
742
+ type: "textarea",
743
+ required: true
744
+ },
745
+ {
746
+ name: "href",
747
+ type: "text",
748
+ admin: { description: "Optional. Makes the whole stat a link: a site path (\"/outcomes\") or a full URL." }
749
+ },
750
+ {
751
+ name: "wide",
752
+ type: "checkbox",
753
+ label: "Full width on phones",
754
+ defaultValue: false,
755
+ admin: { description: "Phones show two per row. With an odd number of stats, this one takes a row to itself. Only the 1st, 3rd or 5th stat can." }
756
+ }
757
+ ]
758
+ },
759
+ {
760
+ name: "columns",
761
+ type: "select",
762
+ defaultValue: "4",
763
+ options: [
764
+ {
765
+ label: "2",
766
+ value: "2"
767
+ },
768
+ {
769
+ label: "3",
770
+ value: "3"
771
+ },
772
+ {
773
+ label: "4",
774
+ value: "4"
775
+ },
776
+ {
777
+ label: "5",
778
+ value: "5"
779
+ },
780
+ {
781
+ label: "6",
782
+ value: "6"
783
+ }
784
+ ],
785
+ admin: { description: "From large screens up, and never more than there are stats. Phones show two per row and tablets three." }
786
+ },
787
+ {
788
+ name: "tone",
789
+ type: "select",
790
+ defaultValue: "card",
791
+ options: [{
792
+ label: "Card (bordered, raised)",
793
+ value: "card"
794
+ }, {
795
+ label: "Plain (dividers only)",
796
+ value: "plain"
797
+ }]
798
+ },
799
+ {
800
+ name: "align",
801
+ type: "select",
802
+ defaultValue: "start",
803
+ options: [{
804
+ label: "Left",
805
+ value: "start"
806
+ }, {
807
+ label: "Centred",
808
+ value: "center"
809
+ }]
810
+ },
811
+ {
812
+ name: "overlap",
813
+ type: "checkbox",
814
+ label: "Float over the seam with the block above",
815
+ defaultValue: false,
816
+ admin: { description: "The band sits across the join between the block above and the block below. Both make room for it automatically. A floating band is headless: the heading fields above are hidden while this is on." }
817
+ }
818
+ ]
819
+ };
820
+ //#endregion
821
+ //#region src/blocks/mega-menu/rem-width.ts
822
+ /**
823
+ * The one rule for a typed panel width, shared by the config's validator and
824
+ * the renderer's reader so the two cannot drift: if the validator accepted a
825
+ * value the renderer dropped, the editor would be back to a width that
826
+ * silently does nothing. React-free, because `config.ts` ships from the Node
827
+ * entry.
828
+ */
829
+ const REM_WIDTH = /^\s*(\d+(?:\.\d+)?)\s*(rem)?\s*$/;
830
+ /** "30" and "30rem" both mean 30rem; anything else is not a width. */
831
+ function remWidth(value) {
832
+ const match = REM_WIDTH.exec(value ?? "");
833
+ return match ? `${Number(match[1])}rem` : null;
834
+ }
835
+ //#endregion
836
+ //#region src/blocks/mega-menu/config.ts
837
+ /** The percent tokens a column can take, as Payload stores them. */
838
+ const MEGA_MENU_WIDTHS = [
839
+ "25",
840
+ "33",
841
+ "50",
842
+ "66",
843
+ "75",
844
+ "100"
845
+ ];
846
+ /** Twelfths, so 33 and 66 are real thirds and 33 + 33 + 33 is a full row. */
847
+ const TWELFTHS = {
848
+ "25": 3,
849
+ "33": 4,
850
+ "50": 6,
851
+ "66": 8,
852
+ "75": 9,
853
+ "100": 12
854
+ };
855
+ /**
856
+ * The rule on `columns`: read as fractions, the widths must total 100%. It is
857
+ * the array's own `validate`, so the editor sees the message as they build,
858
+ * and Payload runs field validation again on publish (only draft saves skip
859
+ * it), so nothing publishes with a short row. Skipped when Advanced has custom
860
+ * widths on.
861
+ */
862
+ const validateColumnWidths = (value, options) => {
863
+ const limits = rowLimits(value, options);
864
+ if (limits !== true) return limits;
865
+ if (options.siblingData?.customWidths || !value?.length) return true;
866
+ const twelfths = value.reduce((sum, row) => sum + TWELFTHS[row?.width ?? "100"], 0);
867
+ if (twelfths === 12) return true;
868
+ return `The columns add up to ${Math.round(twelfths / 12 * 100)}%. They need to add up to 100%.`;
869
+ };
870
+ /**
871
+ * The row `levels` segments above the field at `path`, read off the whole
872
+ * document. A condition on a link needs its section; a condition on a column
873
+ * needs its panel. `data` carries the full form, so the path is enough.
874
+ */
875
+ function ancestor(data, path, levels) {
876
+ let node = data;
877
+ for (const segment of path.slice(0, path.length - levels)) {
878
+ if (node === null || typeof node !== "object") return void 0;
879
+ node = node[segment];
880
+ }
881
+ return node !== null && typeof node === "object" ? node : void 0;
882
+ }
883
+ /** `links.N.variant` → its section, three segments up. */
884
+ const whenSectionFeatured = (data, _siblingData, { path }) => ancestor(data, path, 3)?.display === "featured";
885
+ /** A featured link's glyph comes from its variant, so `icon` is a list link's field. */
886
+ const unlessSectionFeatured = (data, siblingData, ctx) => !whenSectionFeatured(data, siblingData, ctx);
887
+ /** The renderer would fall back to the preset silently; the editor should hear it here instead. */
888
+ const validateRemWidth = (value) => !value || remWidth(value) !== null ? true : "Type a width in rem, like 30 or 30rem.";
889
+ /** `columns.N.width` → its panel, three segments up. */
890
+ const whenCustomWidths = (data, _siblingData, { path }) => Boolean(ancestor(data, path, 3)?.customWidths);
891
+ const unlessCustomWidths = (data, siblingData, ctx) => !whenCustomWidths(data, siblingData, ctx);
892
+ function approvedSelect(name, options, admin) {
893
+ if (!options?.length) return [];
894
+ return [{
895
+ name,
896
+ type: "select",
897
+ options,
898
+ admin
899
+ }];
900
+ }
901
+ function megaMenuBlock({ variants, icons } = {}) {
902
+ const linkRowFields = [
903
+ ...linkFields({ required: true }),
904
+ {
905
+ name: "description",
906
+ type: "textarea",
907
+ admin: { description: "Supporting line under the label. Optional." }
908
+ },
909
+ ...approvedSelect("variant", variants, {
910
+ description: "The look of this featured link.",
911
+ condition: whenSectionFeatured
912
+ }),
913
+ ...approvedSelect("icon", icons, {
914
+ description: "Glyph shown before a list link. Optional.",
915
+ condition: unlessSectionFeatured
916
+ })
917
+ ];
918
+ return {
919
+ slug: "megaMenu",
920
+ interfaceName: "MegaMenuBlock",
921
+ labels: {
922
+ singular: "Mega menu",
923
+ plural: "Mega menus"
924
+ },
925
+ fields: [
926
+ {
927
+ name: "label",
928
+ type: "text",
929
+ required: true
930
+ },
931
+ {
932
+ name: "href",
933
+ type: "text",
934
+ admin: { description: "The landing page this menu opens. Optional: a trigger with no landing page is allowed." }
935
+ },
936
+ {
937
+ name: "panel",
938
+ type: "group",
939
+ fields: [
940
+ {
941
+ name: "maxWidth",
942
+ type: "select",
943
+ defaultValue: "standard",
944
+ options: [
945
+ {
946
+ label: "Narrow",
947
+ value: "narrow"
948
+ },
949
+ {
950
+ label: "Standard",
951
+ value: "standard"
952
+ },
953
+ {
954
+ label: "Wide",
955
+ value: "wide"
956
+ }
957
+ ]
958
+ },
959
+ {
960
+ name: "columns",
961
+ type: "array",
962
+ required: true,
963
+ minRows: 1,
964
+ maxRows: 4,
965
+ defaultValue: emptyRows(1),
966
+ labels: {
967
+ singular: "Column",
968
+ plural: "Columns"
969
+ },
970
+ validate: validateColumnWidths,
971
+ admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } },
972
+ fields: [
973
+ {
974
+ name: "width",
975
+ type: "select",
976
+ defaultValue: "100",
977
+ options: MEGA_MENU_WIDTHS.map((value) => ({
978
+ label: `${value}%`,
979
+ value
980
+ })),
981
+ admin: { condition: unlessCustomWidths }
982
+ },
983
+ {
984
+ name: "customWidth",
985
+ type: "text",
986
+ admin: {
987
+ description: "A CSS width: 13rem, 1fr, minmax(0, 1fr)",
988
+ condition: whenCustomWidths
989
+ }
990
+ },
991
+ {
992
+ name: "divider",
993
+ type: "checkbox",
994
+ label: "Draw a line on the left of this column",
995
+ defaultValue: false
996
+ },
997
+ {
998
+ name: "sections",
999
+ type: "array",
1000
+ labels: {
1001
+ singular: "Section",
1002
+ plural: "Sections"
1003
+ },
1004
+ fields: [
1005
+ {
1006
+ name: "eyebrow",
1007
+ type: "text",
1008
+ admin: { description: "Small heading above this group; leave empty for none" }
1009
+ },
1010
+ {
1011
+ name: "display",
1012
+ type: "select",
1013
+ defaultValue: "list",
1014
+ options: [{
1015
+ label: "Featured",
1016
+ value: "featured"
1017
+ }, {
1018
+ label: "List",
1019
+ value: "list"
1020
+ }]
1021
+ },
1022
+ {
1023
+ name: "hideDescriptionsOnMobile",
1024
+ type: "checkbox",
1025
+ label: "Hide descriptions on mobile",
1026
+ defaultValue: true
1027
+ },
1028
+ {
1029
+ name: "links",
1030
+ type: "array",
1031
+ labels: {
1032
+ singular: "Link",
1033
+ plural: "Links"
1034
+ },
1035
+ fields: linkRowFields
1036
+ }
1037
+ ]
1038
+ }
1039
+ ]
1040
+ },
1041
+ {
1042
+ name: "footer",
1043
+ type: "group",
1044
+ admin: { description: "The strip under the columns. Leave both empty for no footer." },
1045
+ fields: [linkField({
1046
+ name: "overview",
1047
+ label: "Overview link",
1048
+ admin: { description: "The section's own landing page, so it stays reachable from the bar." }
1049
+ }), linkField({
1050
+ name: "cta",
1051
+ label: "Call to action"
1052
+ })]
1053
+ },
1054
+ {
1055
+ type: "collapsible",
1056
+ label: "Advanced",
1057
+ admin: { initCollapsed: true },
1058
+ fields: [{
1059
+ name: "customWidths",
1060
+ type: "checkbox",
1061
+ label: "Type a CSS width for each column instead of choosing a percentage",
1062
+ defaultValue: false
1063
+ }, {
1064
+ name: "customMaxWidth",
1065
+ type: "text",
1066
+ validate: validateRemWidth,
1067
+ admin: { description: "Panel width in rem, e.g. 30. Leave empty to use the preset above." }
1068
+ }]
1069
+ }
1070
+ ]
1071
+ }
1072
+ ]
1073
+ };
1074
+ }
1075
+ /** A plain bar item: label and destination, nothing to open. */
1076
+ const LinkBlock = {
1077
+ slug: "navLink",
1078
+ interfaceName: "NavLinkBlock",
1079
+ labels: {
1080
+ singular: "Link",
1081
+ plural: "Links"
1082
+ },
1083
+ fields: linkFields({ required: true })
1084
+ };
1085
+ //#endregion
644
1086
  //#region src/media.ts
645
1087
  function isMediaDoc(value) {
646
1088
  return typeof value === "object" && value !== null;
@@ -770,6 +1212,143 @@ const heroSample = {
770
1212
  }]
771
1213
  };
772
1214
  //#endregion
1215
+ //#region src/blocks/mega-menu/sample.ts
1216
+ /**
1217
+ * The options the mega menu sample was written against. A factory block's
1218
+ * sample only fits a config built with the same approved values, so a
1219
+ * preview builds its block with `megaMenuBlock(megaMenuSampleOptions)`.
1220
+ */
1221
+ const megaMenuSampleOptions = {
1222
+ variants: [{
1223
+ label: "Lumbar",
1224
+ value: "lumbar"
1225
+ }, {
1226
+ label: "SI joint",
1227
+ value: "si"
1228
+ }],
1229
+ icons: [{
1230
+ label: "Help",
1231
+ value: "help"
1232
+ }, {
1233
+ label: "Mail",
1234
+ value: "mail"
1235
+ }]
1236
+ };
1237
+ /**
1238
+ * Spinal Simplicity's Patients panel as a saved row: two featured territories
1239
+ * in a 75% column, two rail sections in a divided 25% column, overview and
1240
+ * CTA. Every field is set at least once; the Advanced fields are set but off,
1241
+ * so the percent widths are what renders.
1242
+ */
1243
+ const megaMenuSample = {
1244
+ id: "sample-mega-menu",
1245
+ blockType: "megaMenu",
1246
+ label: "Patients",
1247
+ href: "/patients",
1248
+ panel: {
1249
+ maxWidth: "standard",
1250
+ columns: [{
1251
+ id: "treatment",
1252
+ width: "75",
1253
+ customWidth: "minmax(0, 1fr)",
1254
+ divider: false,
1255
+ sections: [{
1256
+ id: "territories",
1257
+ eyebrow: "Treatment",
1258
+ display: "featured",
1259
+ hideDescriptionsOnMobile: true,
1260
+ links: [{
1261
+ id: "lumbar",
1262
+ label: "Low Back Pain",
1263
+ href: "/patients/low-back-pain",
1264
+ newTab: false,
1265
+ description: "Persistent low back pain from lumbar instability, often with leg pain that limits standing or walking.",
1266
+ variant: "lumbar"
1267
+ }, {
1268
+ id: "si",
1269
+ label: "Hip Pain",
1270
+ href: "/patients/hip-pain",
1271
+ newTab: false,
1272
+ description: "Pain centered on the sacroiliac joint, often worse with sitting or climbing stairs.",
1273
+ variant: "si"
1274
+ }]
1275
+ }]
1276
+ }, {
1277
+ id: "rail",
1278
+ width: "25",
1279
+ customWidth: "13rem",
1280
+ divider: true,
1281
+ sections: [{
1282
+ id: "outcomes",
1283
+ eyebrow: "Patient Outcomes",
1284
+ display: "list",
1285
+ hideDescriptionsOnMobile: true,
1286
+ links: [
1287
+ {
1288
+ id: "stories",
1289
+ label: "Testimonials",
1290
+ href: "/patients/stories",
1291
+ newTab: false
1292
+ },
1293
+ {
1294
+ id: "research",
1295
+ label: "Research",
1296
+ href: "/patients/research",
1297
+ newTab: false
1298
+ },
1299
+ {
1300
+ id: "path",
1301
+ label: "Path to Relief",
1302
+ href: "/patients/path-to-relief",
1303
+ newTab: false
1304
+ }
1305
+ ]
1306
+ }, {
1307
+ id: "support",
1308
+ eyebrow: "Support",
1309
+ display: "list",
1310
+ hideDescriptionsOnMobile: false,
1311
+ links: [{
1312
+ id: "faqs",
1313
+ label: "FAQs",
1314
+ href: "/patients/faqs",
1315
+ newTab: false,
1316
+ description: "Short answers to the questions patients ask first.",
1317
+ icon: "help"
1318
+ }, {
1319
+ id: "contact",
1320
+ label: "Contact",
1321
+ href: "https://example.com/contact",
1322
+ newTab: true,
1323
+ icon: "mail"
1324
+ }]
1325
+ }]
1326
+ }],
1327
+ footer: {
1328
+ overview: {
1329
+ label: "All patient resources",
1330
+ href: "/patients",
1331
+ newTab: false
1332
+ },
1333
+ cta: {
1334
+ label: "Find a Doctor",
1335
+ href: "/find-a-doctor",
1336
+ newTab: false
1337
+ }
1338
+ },
1339
+ customWidths: false,
1340
+ customMaxWidth: "42rem"
1341
+ }
1342
+ };
1343
+ /** A plain bar item. */
1344
+ const navLinkSample = {
1345
+ id: "sample-nav-link",
1346
+ blockType: "navLink",
1347
+ label: "Contact",
1348
+ href: "/contact",
1349
+ newTab: false
1350
+ };
1351
+ //#endregion
773
1352
  //#region src/blocks/nap/sample.ts
774
1353
  /**
775
1354
  * A fictional clinic. `emitJsonLd` is off: the block emits schema.org
@@ -1001,6 +1580,50 @@ const showcasePanelsSample = {
1001
1580
  defaultActiveIndex: 0
1002
1581
  };
1003
1582
  //#endregion
1583
+ //#region src/blocks/stats-band/sample.ts
1584
+ /**
1585
+ * Four figures, the count the strip was designed around: one row on a
1586
+ * desktop, two by two on a tablet and a phone. `wide` is set (to `false`) so
1587
+ * the preview exercises the field, and an even count is where it is ignored.
1588
+ * `overlap` is off so the heading shows: a floating band is headless, and a
1589
+ * preview page has no hero for it to float over.
1590
+ */
1591
+ const statsBandSample = {
1592
+ id: "sample-stats-band",
1593
+ blockType: "statsBand",
1594
+ eyebrow: "At a glance",
1595
+ title: "The clinic in numbers",
1596
+ description: "What a year looks like across our two rooms, counted from the front desk rather than estimated.",
1597
+ items: [
1598
+ {
1599
+ id: "sample-stat-pain",
1600
+ value: "94%",
1601
+ label: "of patients report less pain by their sixth session.",
1602
+ href: "/outcomes",
1603
+ wide: false
1604
+ },
1605
+ {
1606
+ id: "sample-stat-visits",
1607
+ value: "4,200+",
1608
+ label: "appointments a year across two treatment rooms."
1609
+ },
1610
+ {
1611
+ id: "sample-stat-wait",
1612
+ value: "15 min",
1613
+ label: "average wait from the front desk to the treatment room."
1614
+ },
1615
+ {
1616
+ id: "sample-stat-years",
1617
+ value: "12",
1618
+ label: "years in the same building, on the same street."
1619
+ }
1620
+ ],
1621
+ columns: "4",
1622
+ tone: "card",
1623
+ align: "start",
1624
+ overlap: false
1625
+ };
1626
+ //#endregion
1004
1627
  //#region src/blocks/testimonial-masonry/sample.ts
1005
1628
  function avatar(name) {
1006
1629
  return sampleImage({
@@ -1098,9 +1721,12 @@ const blockSamples = {
1098
1721
  minItemsForFade: 6,
1099
1722
  maxVisibleRows: 2
1100
1723
  },
1101
- nap: napSample
1724
+ nap: napSample,
1725
+ statsBand: statsBandSample,
1726
+ megaMenu: megaMenuSample,
1727
+ navLink: navLinkSample
1102
1728
  };
1103
1729
  //#endregion
1104
- export { FaqColumnsBlock, HeroBlock, MIN_ROWS_ARRAY_FIELD, NapBlock, ProcessStepsBlock, RichTextBlock, ShowcasePanelsBlock, TestimonialMasonryBlock, blockSamples, emptyRows, headingFields, imageField, linkField, linkFields, resolveMedia, sampleImage };
1730
+ export { FaqColumnsBlock, HeroBlock, LinkBlock, MEGA_MENU_WIDTHS, MIN_ROWS_ARRAY_FIELD, NapBlock, ProcessStepsBlock, RichTextBlock, ShowcasePanelsBlock, StatsBandBlock, TestimonialMasonryBlock, blockSamples, emptyRows, headingFields, imageField, linkField, linkFields, megaMenuBlock, megaMenuSampleOptions, resolveMedia, sampleImage, validateColumnWidths, validateRemWidth };
1105
1731
 
1106
1732
  //# sourceMappingURL=index.mjs.map