@bison-lab/payload-blocks 3.1.0 → 3.2.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/README.md +102 -1
- package/dist/index.d.mts +102 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +425 -2
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +64 -7
- package/dist/react.d.mts.map +1 -1
- package/dist/react.mjs +182 -15
- package/dist/react.mjs.map +1 -1
- package/dist/rich-text.d.mts +1 -1
- package/dist/{types-BBumyFd-.d.mts → types-CJAvoZMk.d.mts} +97 -7
- package/dist/types-CJAvoZMk.d.mts.map +1 -0
- package/package.json +2 -2
- package/dist/types-BBumyFd-.d.mts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -641,6 +641,290 @@ const NapBlock = {
|
|
|
641
641
|
]
|
|
642
642
|
};
|
|
643
643
|
//#endregion
|
|
644
|
+
//#region src/blocks/mega-menu/rem-width.ts
|
|
645
|
+
/**
|
|
646
|
+
* The one rule for a typed panel width, shared by the config's validator and
|
|
647
|
+
* the renderer's reader so the two cannot drift: if the validator accepted a
|
|
648
|
+
* value the renderer dropped, the editor would be back to a width that
|
|
649
|
+
* silently does nothing. React-free, because `config.ts` ships from the Node
|
|
650
|
+
* entry.
|
|
651
|
+
*/
|
|
652
|
+
const REM_WIDTH = /^\s*(\d+(?:\.\d+)?)\s*(rem)?\s*$/;
|
|
653
|
+
/** "30" and "30rem" both mean 30rem; anything else is not a width. */
|
|
654
|
+
function remWidth(value) {
|
|
655
|
+
const match = REM_WIDTH.exec(value ?? "");
|
|
656
|
+
return match ? `${Number(match[1])}rem` : null;
|
|
657
|
+
}
|
|
658
|
+
//#endregion
|
|
659
|
+
//#region src/blocks/mega-menu/config.ts
|
|
660
|
+
/** The percent tokens a column can take, as Payload stores them. */
|
|
661
|
+
const MEGA_MENU_WIDTHS = [
|
|
662
|
+
"25",
|
|
663
|
+
"33",
|
|
664
|
+
"50",
|
|
665
|
+
"66",
|
|
666
|
+
"75",
|
|
667
|
+
"100"
|
|
668
|
+
];
|
|
669
|
+
/** Twelfths, so 33 and 66 are real thirds and 33 + 33 + 33 is a full row. */
|
|
670
|
+
const TWELFTHS = {
|
|
671
|
+
"25": 3,
|
|
672
|
+
"33": 4,
|
|
673
|
+
"50": 6,
|
|
674
|
+
"66": 8,
|
|
675
|
+
"75": 9,
|
|
676
|
+
"100": 12
|
|
677
|
+
};
|
|
678
|
+
/**
|
|
679
|
+
* A custom `validate` replaces Payload's stock array check, so the row limits
|
|
680
|
+
* are re-applied here or five columns would pass. Same messages as the stock
|
|
681
|
+
* check, through the request's translator when the admin supplies one.
|
|
682
|
+
*/
|
|
683
|
+
function rowLimits(value, { minRows, maxRows, required, req }) {
|
|
684
|
+
const count = value?.length ?? 0;
|
|
685
|
+
const t = req?.t;
|
|
686
|
+
if (required && count === 0) return t ? t("validation:required") : "This field is required.";
|
|
687
|
+
if (minRows && count < minRows) return t ? t("validation:requiresAtLeast", {
|
|
688
|
+
count: minRows,
|
|
689
|
+
label: t("general:rows")
|
|
690
|
+
}) : `This field requires at least ${minRows} rows.`;
|
|
691
|
+
if (maxRows && count > maxRows) return t ? t("validation:requiresNoMoreThan", {
|
|
692
|
+
count: maxRows,
|
|
693
|
+
label: t("general:rows")
|
|
694
|
+
}) : `This field requires no more than ${maxRows} rows.`;
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* The rule on `columns`: read as fractions, the widths must total 100%. It is
|
|
699
|
+
* the array's own `validate`, so the editor sees the message as they build,
|
|
700
|
+
* and Payload runs field validation again on publish (only draft saves skip
|
|
701
|
+
* it), so nothing publishes with a short row. Skipped when Advanced has custom
|
|
702
|
+
* widths on.
|
|
703
|
+
*/
|
|
704
|
+
const validateColumnWidths = (value, options) => {
|
|
705
|
+
const limits = rowLimits(value, options);
|
|
706
|
+
if (limits !== true) return limits;
|
|
707
|
+
if (options.siblingData?.customWidths || !value?.length) return true;
|
|
708
|
+
const twelfths = value.reduce((sum, row) => sum + TWELFTHS[row?.width ?? "100"], 0);
|
|
709
|
+
if (twelfths === 12) return true;
|
|
710
|
+
return `The columns add up to ${Math.round(twelfths / 12 * 100)}%. They need to add up to 100%.`;
|
|
711
|
+
};
|
|
712
|
+
/**
|
|
713
|
+
* The row `levels` segments above the field at `path`, read off the whole
|
|
714
|
+
* document. A condition on a link needs its section; a condition on a column
|
|
715
|
+
* needs its panel. `data` carries the full form, so the path is enough.
|
|
716
|
+
*/
|
|
717
|
+
function ancestor(data, path, levels) {
|
|
718
|
+
let node = data;
|
|
719
|
+
for (const segment of path.slice(0, path.length - levels)) {
|
|
720
|
+
if (node === null || typeof node !== "object") return void 0;
|
|
721
|
+
node = node[segment];
|
|
722
|
+
}
|
|
723
|
+
return node !== null && typeof node === "object" ? node : void 0;
|
|
724
|
+
}
|
|
725
|
+
/** `links.N.variant` → its section, three segments up. */
|
|
726
|
+
const whenSectionFeatured = (data, _siblingData, { path }) => ancestor(data, path, 3)?.display === "featured";
|
|
727
|
+
/** A featured link's glyph comes from its variant, so `icon` is a list link's field. */
|
|
728
|
+
const unlessSectionFeatured = (data, siblingData, ctx) => !whenSectionFeatured(data, siblingData, ctx);
|
|
729
|
+
/** The renderer would fall back to the preset silently; the editor should hear it here instead. */
|
|
730
|
+
const validateRemWidth = (value) => !value || remWidth(value) !== null ? true : "Type a width in rem, like 30 or 30rem.";
|
|
731
|
+
/** `columns.N.width` → its panel, three segments up. */
|
|
732
|
+
const whenCustomWidths = (data, _siblingData, { path }) => Boolean(ancestor(data, path, 3)?.customWidths);
|
|
733
|
+
const unlessCustomWidths = (data, siblingData, ctx) => !whenCustomWidths(data, siblingData, ctx);
|
|
734
|
+
function approvedSelect(name, options, admin) {
|
|
735
|
+
if (!options?.length) return [];
|
|
736
|
+
return [{
|
|
737
|
+
name,
|
|
738
|
+
type: "select",
|
|
739
|
+
options,
|
|
740
|
+
admin
|
|
741
|
+
}];
|
|
742
|
+
}
|
|
743
|
+
function megaMenuBlock({ variants, icons } = {}) {
|
|
744
|
+
const linkRowFields = [
|
|
745
|
+
...linkFields({ required: true }),
|
|
746
|
+
{
|
|
747
|
+
name: "description",
|
|
748
|
+
type: "textarea",
|
|
749
|
+
admin: { description: "Supporting line under the label. Optional." }
|
|
750
|
+
},
|
|
751
|
+
...approvedSelect("variant", variants, {
|
|
752
|
+
description: "The look of this featured link.",
|
|
753
|
+
condition: whenSectionFeatured
|
|
754
|
+
}),
|
|
755
|
+
...approvedSelect("icon", icons, {
|
|
756
|
+
description: "Glyph shown before a list link. Optional.",
|
|
757
|
+
condition: unlessSectionFeatured
|
|
758
|
+
})
|
|
759
|
+
];
|
|
760
|
+
return {
|
|
761
|
+
slug: "megaMenu",
|
|
762
|
+
interfaceName: "MegaMenuBlock",
|
|
763
|
+
labels: {
|
|
764
|
+
singular: "Mega menu",
|
|
765
|
+
plural: "Mega menus"
|
|
766
|
+
},
|
|
767
|
+
fields: [
|
|
768
|
+
{
|
|
769
|
+
name: "label",
|
|
770
|
+
type: "text",
|
|
771
|
+
required: true
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
name: "href",
|
|
775
|
+
type: "text",
|
|
776
|
+
admin: { description: "The landing page this menu opens. Optional: a trigger with no landing page is allowed." }
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
name: "panel",
|
|
780
|
+
type: "group",
|
|
781
|
+
fields: [
|
|
782
|
+
{
|
|
783
|
+
name: "maxWidth",
|
|
784
|
+
type: "select",
|
|
785
|
+
defaultValue: "standard",
|
|
786
|
+
options: [
|
|
787
|
+
{
|
|
788
|
+
label: "Narrow",
|
|
789
|
+
value: "narrow"
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
label: "Standard",
|
|
793
|
+
value: "standard"
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
label: "Wide",
|
|
797
|
+
value: "wide"
|
|
798
|
+
}
|
|
799
|
+
]
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
name: "columns",
|
|
803
|
+
type: "array",
|
|
804
|
+
required: true,
|
|
805
|
+
minRows: 1,
|
|
806
|
+
maxRows: 4,
|
|
807
|
+
defaultValue: emptyRows(1),
|
|
808
|
+
labels: {
|
|
809
|
+
singular: "Column",
|
|
810
|
+
plural: "Columns"
|
|
811
|
+
},
|
|
812
|
+
validate: validateColumnWidths,
|
|
813
|
+
admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } },
|
|
814
|
+
fields: [
|
|
815
|
+
{
|
|
816
|
+
name: "width",
|
|
817
|
+
type: "select",
|
|
818
|
+
defaultValue: "100",
|
|
819
|
+
options: MEGA_MENU_WIDTHS.map((value) => ({
|
|
820
|
+
label: `${value}%`,
|
|
821
|
+
value
|
|
822
|
+
})),
|
|
823
|
+
admin: { condition: unlessCustomWidths }
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
name: "customWidth",
|
|
827
|
+
type: "text",
|
|
828
|
+
admin: {
|
|
829
|
+
description: "A CSS width: 13rem, 1fr, minmax(0, 1fr)",
|
|
830
|
+
condition: whenCustomWidths
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
name: "divider",
|
|
835
|
+
type: "checkbox",
|
|
836
|
+
label: "Draw a line on the left of this column",
|
|
837
|
+
defaultValue: false
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
name: "sections",
|
|
841
|
+
type: "array",
|
|
842
|
+
labels: {
|
|
843
|
+
singular: "Section",
|
|
844
|
+
plural: "Sections"
|
|
845
|
+
},
|
|
846
|
+
fields: [
|
|
847
|
+
{
|
|
848
|
+
name: "eyebrow",
|
|
849
|
+
type: "text",
|
|
850
|
+
admin: { description: "Small heading above this group; leave empty for none" }
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
name: "display",
|
|
854
|
+
type: "select",
|
|
855
|
+
defaultValue: "list",
|
|
856
|
+
options: [{
|
|
857
|
+
label: "Featured",
|
|
858
|
+
value: "featured"
|
|
859
|
+
}, {
|
|
860
|
+
label: "List",
|
|
861
|
+
value: "list"
|
|
862
|
+
}]
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
name: "hideDescriptionsOnMobile",
|
|
866
|
+
type: "checkbox",
|
|
867
|
+
label: "Hide descriptions on mobile",
|
|
868
|
+
defaultValue: true
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
name: "links",
|
|
872
|
+
type: "array",
|
|
873
|
+
labels: {
|
|
874
|
+
singular: "Link",
|
|
875
|
+
plural: "Links"
|
|
876
|
+
},
|
|
877
|
+
fields: linkRowFields
|
|
878
|
+
}
|
|
879
|
+
]
|
|
880
|
+
}
|
|
881
|
+
]
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
name: "footer",
|
|
885
|
+
type: "group",
|
|
886
|
+
admin: { description: "The strip under the columns. Leave both empty for no footer." },
|
|
887
|
+
fields: [linkField({
|
|
888
|
+
name: "overview",
|
|
889
|
+
label: "Overview link",
|
|
890
|
+
admin: { description: "The section's own landing page, so it stays reachable from the bar." }
|
|
891
|
+
}), linkField({
|
|
892
|
+
name: "cta",
|
|
893
|
+
label: "Call to action"
|
|
894
|
+
})]
|
|
895
|
+
},
|
|
896
|
+
{
|
|
897
|
+
type: "collapsible",
|
|
898
|
+
label: "Advanced",
|
|
899
|
+
admin: { initCollapsed: true },
|
|
900
|
+
fields: [{
|
|
901
|
+
name: "customWidths",
|
|
902
|
+
type: "checkbox",
|
|
903
|
+
label: "Type a CSS width for each column instead of choosing a percentage",
|
|
904
|
+
defaultValue: false
|
|
905
|
+
}, {
|
|
906
|
+
name: "customMaxWidth",
|
|
907
|
+
type: "text",
|
|
908
|
+
validate: validateRemWidth,
|
|
909
|
+
admin: { description: "Panel width in rem, e.g. 30. Leave empty to use the preset above." }
|
|
910
|
+
}]
|
|
911
|
+
}
|
|
912
|
+
]
|
|
913
|
+
}
|
|
914
|
+
]
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
/** A plain bar item: label and destination, nothing to open. */
|
|
918
|
+
const LinkBlock = {
|
|
919
|
+
slug: "navLink",
|
|
920
|
+
interfaceName: "NavLinkBlock",
|
|
921
|
+
labels: {
|
|
922
|
+
singular: "Link",
|
|
923
|
+
plural: "Links"
|
|
924
|
+
},
|
|
925
|
+
fields: linkFields({ required: true })
|
|
926
|
+
};
|
|
927
|
+
//#endregion
|
|
644
928
|
//#region src/media.ts
|
|
645
929
|
function isMediaDoc(value) {
|
|
646
930
|
return typeof value === "object" && value !== null;
|
|
@@ -770,6 +1054,143 @@ const heroSample = {
|
|
|
770
1054
|
}]
|
|
771
1055
|
};
|
|
772
1056
|
//#endregion
|
|
1057
|
+
//#region src/blocks/mega-menu/sample.ts
|
|
1058
|
+
/**
|
|
1059
|
+
* The options the mega menu sample was written against. A factory block's
|
|
1060
|
+
* sample only fits a config built with the same approved values, so a
|
|
1061
|
+
* preview builds its block with `megaMenuBlock(megaMenuSampleOptions)`.
|
|
1062
|
+
*/
|
|
1063
|
+
const megaMenuSampleOptions = {
|
|
1064
|
+
variants: [{
|
|
1065
|
+
label: "Lumbar",
|
|
1066
|
+
value: "lumbar"
|
|
1067
|
+
}, {
|
|
1068
|
+
label: "SI joint",
|
|
1069
|
+
value: "si"
|
|
1070
|
+
}],
|
|
1071
|
+
icons: [{
|
|
1072
|
+
label: "Help",
|
|
1073
|
+
value: "help"
|
|
1074
|
+
}, {
|
|
1075
|
+
label: "Mail",
|
|
1076
|
+
value: "mail"
|
|
1077
|
+
}]
|
|
1078
|
+
};
|
|
1079
|
+
/**
|
|
1080
|
+
* Spinal Simplicity's Patients panel as a saved row: two featured territories
|
|
1081
|
+
* in a 75% column, two rail sections in a divided 25% column, overview and
|
|
1082
|
+
* CTA. Every field is set at least once; the Advanced fields are set but off,
|
|
1083
|
+
* so the percent widths are what renders.
|
|
1084
|
+
*/
|
|
1085
|
+
const megaMenuSample = {
|
|
1086
|
+
id: "sample-mega-menu",
|
|
1087
|
+
blockType: "megaMenu",
|
|
1088
|
+
label: "Patients",
|
|
1089
|
+
href: "/patients",
|
|
1090
|
+
panel: {
|
|
1091
|
+
maxWidth: "standard",
|
|
1092
|
+
columns: [{
|
|
1093
|
+
id: "treatment",
|
|
1094
|
+
width: "75",
|
|
1095
|
+
customWidth: "minmax(0, 1fr)",
|
|
1096
|
+
divider: false,
|
|
1097
|
+
sections: [{
|
|
1098
|
+
id: "territories",
|
|
1099
|
+
eyebrow: "Treatment",
|
|
1100
|
+
display: "featured",
|
|
1101
|
+
hideDescriptionsOnMobile: true,
|
|
1102
|
+
links: [{
|
|
1103
|
+
id: "lumbar",
|
|
1104
|
+
label: "Low Back Pain",
|
|
1105
|
+
href: "/patients/low-back-pain",
|
|
1106
|
+
newTab: false,
|
|
1107
|
+
description: "Persistent low back pain from lumbar instability, often with leg pain that limits standing or walking.",
|
|
1108
|
+
variant: "lumbar"
|
|
1109
|
+
}, {
|
|
1110
|
+
id: "si",
|
|
1111
|
+
label: "Hip Pain",
|
|
1112
|
+
href: "/patients/hip-pain",
|
|
1113
|
+
newTab: false,
|
|
1114
|
+
description: "Pain centered on the sacroiliac joint, often worse with sitting or climbing stairs.",
|
|
1115
|
+
variant: "si"
|
|
1116
|
+
}]
|
|
1117
|
+
}]
|
|
1118
|
+
}, {
|
|
1119
|
+
id: "rail",
|
|
1120
|
+
width: "25",
|
|
1121
|
+
customWidth: "13rem",
|
|
1122
|
+
divider: true,
|
|
1123
|
+
sections: [{
|
|
1124
|
+
id: "outcomes",
|
|
1125
|
+
eyebrow: "Patient Outcomes",
|
|
1126
|
+
display: "list",
|
|
1127
|
+
hideDescriptionsOnMobile: true,
|
|
1128
|
+
links: [
|
|
1129
|
+
{
|
|
1130
|
+
id: "stories",
|
|
1131
|
+
label: "Testimonials",
|
|
1132
|
+
href: "/patients/stories",
|
|
1133
|
+
newTab: false
|
|
1134
|
+
},
|
|
1135
|
+
{
|
|
1136
|
+
id: "research",
|
|
1137
|
+
label: "Research",
|
|
1138
|
+
href: "/patients/research",
|
|
1139
|
+
newTab: false
|
|
1140
|
+
},
|
|
1141
|
+
{
|
|
1142
|
+
id: "path",
|
|
1143
|
+
label: "Path to Relief",
|
|
1144
|
+
href: "/patients/path-to-relief",
|
|
1145
|
+
newTab: false
|
|
1146
|
+
}
|
|
1147
|
+
]
|
|
1148
|
+
}, {
|
|
1149
|
+
id: "support",
|
|
1150
|
+
eyebrow: "Support",
|
|
1151
|
+
display: "list",
|
|
1152
|
+
hideDescriptionsOnMobile: false,
|
|
1153
|
+
links: [{
|
|
1154
|
+
id: "faqs",
|
|
1155
|
+
label: "FAQs",
|
|
1156
|
+
href: "/patients/faqs",
|
|
1157
|
+
newTab: false,
|
|
1158
|
+
description: "Short answers to the questions patients ask first.",
|
|
1159
|
+
icon: "help"
|
|
1160
|
+
}, {
|
|
1161
|
+
id: "contact",
|
|
1162
|
+
label: "Contact",
|
|
1163
|
+
href: "https://example.com/contact",
|
|
1164
|
+
newTab: true,
|
|
1165
|
+
icon: "mail"
|
|
1166
|
+
}]
|
|
1167
|
+
}]
|
|
1168
|
+
}],
|
|
1169
|
+
footer: {
|
|
1170
|
+
overview: {
|
|
1171
|
+
label: "All patient resources",
|
|
1172
|
+
href: "/patients",
|
|
1173
|
+
newTab: false
|
|
1174
|
+
},
|
|
1175
|
+
cta: {
|
|
1176
|
+
label: "Find a Doctor",
|
|
1177
|
+
href: "/find-a-doctor",
|
|
1178
|
+
newTab: false
|
|
1179
|
+
}
|
|
1180
|
+
},
|
|
1181
|
+
customWidths: false,
|
|
1182
|
+
customMaxWidth: "42rem"
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
/** A plain bar item. */
|
|
1186
|
+
const navLinkSample = {
|
|
1187
|
+
id: "sample-nav-link",
|
|
1188
|
+
blockType: "navLink",
|
|
1189
|
+
label: "Contact",
|
|
1190
|
+
href: "/contact",
|
|
1191
|
+
newTab: false
|
|
1192
|
+
};
|
|
1193
|
+
//#endregion
|
|
773
1194
|
//#region src/blocks/nap/sample.ts
|
|
774
1195
|
/**
|
|
775
1196
|
* A fictional clinic. `emitJsonLd` is off: the block emits schema.org
|
|
@@ -1098,9 +1519,11 @@ const blockSamples = {
|
|
|
1098
1519
|
minItemsForFade: 6,
|
|
1099
1520
|
maxVisibleRows: 2
|
|
1100
1521
|
},
|
|
1101
|
-
nap: napSample
|
|
1522
|
+
nap: napSample,
|
|
1523
|
+
megaMenu: megaMenuSample,
|
|
1524
|
+
navLink: navLinkSample
|
|
1102
1525
|
};
|
|
1103
1526
|
//#endregion
|
|
1104
|
-
export { FaqColumnsBlock, HeroBlock, MIN_ROWS_ARRAY_FIELD, NapBlock, ProcessStepsBlock, RichTextBlock, ShowcasePanelsBlock, TestimonialMasonryBlock, blockSamples, emptyRows, headingFields, imageField, linkField, linkFields, resolveMedia, sampleImage };
|
|
1527
|
+
export { FaqColumnsBlock, HeroBlock, LinkBlock, MEGA_MENU_WIDTHS, MIN_ROWS_ARRAY_FIELD, NapBlock, ProcessStepsBlock, RichTextBlock, ShowcasePanelsBlock, TestimonialMasonryBlock, blockSamples, emptyRows, headingFields, imageField, linkField, linkFields, megaMenuBlock, megaMenuSampleOptions, resolveMedia, sampleImage, validateColumnWidths, validateRemWidth };
|
|
1105
1528
|
|
|
1106
1529
|
//# sourceMappingURL=index.mjs.map
|