@bison-lab/payload-blocks 2.0.0 → 3.0.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 CHANGED
@@ -12,15 +12,17 @@ pnpm add @bison-lab/payload-blocks
12
12
 
13
13
  Peers: `payload` (required); `react`, `react-dom`, `@bison-lab/ui` and
14
14
  `@payloadcms/richtext-lexical` (needed by the renderer entries, optional if you
15
- only use the configs).
15
+ only use the configs); `@payloadcms/ui` (needed by the admin entry, which every
16
+ Payload site already has).
16
17
 
17
- ## Two entry points, and why
18
+ ## Four entry points, and why
18
19
 
19
20
  | Import | Contents | Runs where |
20
21
  | --- | --- | --- |
21
22
  | `@bison-lab/payload-blocks` | Block configs, field builders, row types, `resolveMedia` | Node. This is what `payload.config.ts` imports, and it touches no React. |
22
23
  | `@bison-lab/payload-blocks/react` | Renderers, `RenderBlocks`, the rendering types | Client (`"use client"`). |
23
24
  | `@bison-lab/payload-blocks/rich-text` | The `richText` renderer, alone | Client. Split out because it is the only thing that needs `@payloadcms/richtext-lexical`. |
25
+ | `@bison-lab/payload-blocks/admin` | `MinRowsArrayField`, the admin field the configs reference by path | Client, inside the Payload admin. Resolved through the site's import map, never imported by hand. |
24
26
 
25
27
  The renderers are client components because the blocks they render are: every
26
28
  `@bison-lab/ui` export is a client reference, and these blocks are interactive
@@ -62,7 +64,30 @@ export const Pages: CollectionConfig = {
62
64
  }
63
65
  ```
64
66
 
65
- Then `payload generate:types` and `payload migrate:create`.
67
+ Then `payload generate:types`, `payload generate:importmap` and
68
+ `payload migrate:create`.
69
+
70
+ The import map step is what wires the admin field. Every array in this package
71
+ with a `minRows` opens with that many empty rows and names
72
+ `@bison-lab/payload-blocks/admin#MinRowsArrayField` as its field component,
73
+ which refuses to remove a row once the count is down to the minimum (Payload
74
+ gates Add on `maxRows` but never Remove on `minRows`). Until the import map
75
+ has the entry Payload logs the miss and renders its stock array field, so an
76
+ upgrade that forgets the step degrades to the old behaviour rather than
77
+ breaking. A site can put the same field on its own arrays:
78
+
79
+ ```ts
80
+ import { MIN_ROWS_ARRAY_FIELD, emptyRows } from '@bison-lab/payload-blocks'
81
+
82
+ {
83
+ name: 'links',
84
+ type: 'array',
85
+ minRows: 2,
86
+ defaultValue: emptyRows(2),
87
+ admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } },
88
+ fields: [...],
89
+ }
90
+ ```
66
91
 
67
92
  **2. Own the registry.**
68
93
 
@@ -137,8 +162,8 @@ renders.
137
162
  | --- | --- | --- |
138
163
  | `hero` | plain markup | No `@bison-lab/ui` counterpart. Override this entry with your own. |
139
164
  | `richText` | `RichText` (Lexical) | From `/rich-text`. |
140
- | `showcasePanels` | `ShowcasePanelsBlock` | 3–6 panels, each with a required image. |
141
- | `processSteps` | `ProcessStepsBlock` | 3–6 ordered steps; advances on a timer. |
165
+ | `showcasePanels` | `ShowcasePanelsBlock` | 3–6 panels, each with a required image. Opens with three. |
166
+ | `processSteps` | `ProcessStepsBlock` | 3–6 ordered steps, numbered by position; advances on a timer. Opens with three. |
142
167
  | `faqColumns` | `FAQColumnsBlock` | Answers are plain text, not Lexical — see the config for why. |
143
168
  | `testimonialMasonry` | `TestimonialMasonry` | Avatars fall back to initials. |
144
169
  | `nap` | `NapBlock` + `JsonLd` | Emits schema.org `LocalBusiness`; `phoneE164` is what the `tel:` link uses. |
@@ -155,7 +180,13 @@ validation, so live preview genuinely hands renderers incomplete rows.
155
180
  interface here has an index signature — TypeScript will not assign an
156
181
  interface to a type that does.
157
182
  - **`src/index.ts` stays React-free.** Payload loads a site's config outside the
158
- bundler, in `migrate`, `generate:types` and the admin server.
183
+ bundler, in `migrate`, `generate:types` and the admin server. An admin
184
+ component is referenced from a config by its import-map string
185
+ (`MIN_ROWS_ARRAY_FIELD`), never imported.
186
+ - **`src/admin.tsx` is the only entry that loads `@payloadcms/ui`.** It wraps
187
+ Payload's stock fields rather than re-implementing them, so an upgrade
188
+ carries every upstream behaviour along. Editor-facing copy, accessible names
189
+ and control text are not config fields: the `@bison-lab/ui` defaults stand.
159
190
  - **`cn()` is off limits.** It is a client-only export of `@bison-lab/ui`; use
160
191
  the local `cx()`.
161
192
  - **Changing a field is a schema change in every consuming site.** Update
@@ -0,0 +1,27 @@
1
+
2
+ import { ArrayFieldClientComponent } from "payload";
3
+
4
+ //#region src/admin/min-rows-array-field.d.ts
5
+ /**
6
+ * Payload's array field, with *Remove* refused once the rows are down to
7
+ * `minRows`.
8
+ *
9
+ * This wraps the stock `ArrayField` rather than re-implementing it, so every
10
+ * upstream behaviour (sorting, copy/paste, collapse, localisation) stays put
11
+ * and a Payload upgrade carries it along. The catch is where the row menu
12
+ * lives: Payload portals it into `document.body`, out of reach of any CSS or
13
+ * form context this wrapper could scope. What still reaches the wrapper is
14
+ * the React event: synthetic events bubble through portals along the
15
+ * component tree, so a capture handler here sees the click on *Remove*
16
+ * before the menu's own handler does, and can stop it.
17
+ *
18
+ * Because the menu is portaled, the DOM cannot say whether a given *Remove*
19
+ * belongs to this array or to an array nested inside one of its rows. The
20
+ * trigger that opened the menu *is* in the DOM, though, and every menu opens
21
+ * from its trigger. So the handler remembers, per menu opening, whether the
22
+ * trigger sat directly in this field, and only gates the *Remove* that follows.
23
+ */
24
+ declare const MinRowsArrayField: ArrayFieldClientComponent;
25
+ //#endregion
26
+ export { MinRowsArrayField };
27
+ //# sourceMappingURL=admin.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.d.mts","names":[],"sources":["../src/admin/min-rows-array-field.tsx"],"mappings":";;;;;;AAuBA;;;;;;;;;;;;;;;;;cAAa,iBAAA,EAAmB,yBAAA"}
package/dist/admin.mjs ADDED
@@ -0,0 +1,82 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { ArrayField, toast, useField, useTranslation } from "@payloadcms/ui";
4
+ import { useCallback, useRef } from "react";
5
+ //#region src/admin/min-rows-array-field.tsx
6
+ /**
7
+ * Payload's array field, with *Remove* refused once the rows are down to
8
+ * `minRows`.
9
+ *
10
+ * This wraps the stock `ArrayField` rather than re-implementing it, so every
11
+ * upstream behaviour (sorting, copy/paste, collapse, localisation) stays put
12
+ * and a Payload upgrade carries it along. The catch is where the row menu
13
+ * lives: Payload portals it into `document.body`, out of reach of any CSS or
14
+ * form context this wrapper could scope. What still reaches the wrapper is
15
+ * the React event: synthetic events bubble through portals along the
16
+ * component tree, so a capture handler here sees the click on *Remove*
17
+ * before the menu's own handler does, and can stop it.
18
+ *
19
+ * Because the menu is portaled, the DOM cannot say whether a given *Remove*
20
+ * belongs to this array or to an array nested inside one of its rows. The
21
+ * trigger that opened the menu *is* in the DOM, though, and every menu opens
22
+ * from its trigger. So the handler remembers, per menu opening, whether the
23
+ * trigger sat directly in this field, and only gates the *Remove* that follows.
24
+ */
25
+ const MinRowsArrayField = (props) => {
26
+ const { field, path: pathFromProps } = props;
27
+ const minRows = field.minRows ?? (field.required ? 1 : 0);
28
+ const { rows = [] } = useField({
29
+ hasRows: true,
30
+ potentiallyStalePath: pathFromProps
31
+ });
32
+ const { i18n, t } = useTranslation();
33
+ const rootRef = useRef(null);
34
+ const menuIsOurs = useRef(false);
35
+ return /* @__PURE__ */ jsx("div", {
36
+ ref: rootRef,
37
+ onClickCapture: useCallback((event) => {
38
+ const target = event.target;
39
+ if (!(target instanceof Element)) return;
40
+ const trigger = target.closest(".array-actions__button");
41
+ if (trigger) {
42
+ const owner = trigger.closest(".array-field, .blocks-field");
43
+ menuIsOurs.current = owner !== null && owner === rootRef.current?.querySelector(".array-field");
44
+ return;
45
+ }
46
+ if (!target.closest(".array-actions__remove")) return;
47
+ if (!menuIsOurs.current || rows.length > minRows) return;
48
+ event.preventDefault();
49
+ event.stopPropagation();
50
+ toast.error(t("validation:requiresAtLeast", {
51
+ count: minRows,
52
+ label: rowLabel(field, minRows, i18n.language) ?? t(minRows > 1 ? "general:rows" : "general:row")
53
+ }));
54
+ }, [
55
+ field,
56
+ i18n.language,
57
+ minRows,
58
+ rows.length,
59
+ t
60
+ ]),
61
+ children: /* @__PURE__ */ jsx(ArrayField, { ...props })
62
+ });
63
+ };
64
+ /**
65
+ * The word for a row, as Payload's own "requires at least" banner picks it:
66
+ * the plural label above one, the singular otherwise, the field label failing
67
+ * both. Client-side labels are static — a string or a per-language record.
68
+ */
69
+ function rowLabel(field, minRows, language) {
70
+ return staticLabel(minRows > 1 ? field.labels?.plural : field.labels?.singular, language) ?? staticLabel(field.label, language);
71
+ }
72
+ function staticLabel(label, language) {
73
+ if (typeof label === "string") return label;
74
+ if (label && typeof label === "object") {
75
+ const byLanguage = label;
76
+ return byLanguage[language] ?? byLanguage.en ?? Object.values(byLanguage)[0];
77
+ }
78
+ }
79
+ //#endregion
80
+ export { MinRowsArrayField };
81
+
82
+ //# sourceMappingURL=admin.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.mjs","names":[],"sources":["../src/admin/min-rows-array-field.tsx"],"sourcesContent":["import { ArrayField, toast, useField, useTranslation } from \"@payloadcms/ui\";\nimport type { ArrayFieldClientComponent, ArrayFieldClientProps } from \"payload\";\nimport { useCallback, useRef } from \"react\";\n\n/**\n * Payload's array field, with *Remove* refused once the rows are down to\n * `minRows`.\n *\n * This wraps the stock `ArrayField` rather than re-implementing it, so every\n * upstream behaviour (sorting, copy/paste, collapse, localisation) stays put\n * and a Payload upgrade carries it along. The catch is where the row menu\n * lives: Payload portals it into `document.body`, out of reach of any CSS or\n * form context this wrapper could scope. What still reaches the wrapper is\n * the React event: synthetic events bubble through portals along the\n * component tree, so a capture handler here sees the click on *Remove*\n * before the menu's own handler does, and can stop it.\n *\n * Because the menu is portaled, the DOM cannot say whether a given *Remove*\n * belongs to this array or to an array nested inside one of its rows. The\n * trigger that opened the menu *is* in the DOM, though, and every menu opens\n * from its trigger. So the handler remembers, per menu opening, whether the\n * trigger sat directly in this field, and only gates the *Remove* that follows.\n */\nexport const MinRowsArrayField: ArrayFieldClientComponent = (props) => {\n const { field, path: pathFromProps } = props;\n const minRows = field.minRows ?? (field.required ? 1 : 0);\n\n const { rows = [] } = useField({\n hasRows: true,\n potentiallyStalePath: pathFromProps,\n });\n const { i18n, t } = useTranslation();\n\n const rootRef = useRef<HTMLDivElement>(null);\n const menuIsOurs = useRef(false);\n\n const onClickCapture = useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n\n const trigger = target.closest(\".array-actions__button\");\n if (trigger) {\n // The nearest array or blocks field around the trigger is the field\n // whose menu is opening. Ours is the first one inside the wrapper.\n const owner = trigger.closest(\".array-field, .blocks-field\");\n menuIsOurs.current =\n owner !== null && owner === rootRef.current?.querySelector(\".array-field\");\n return;\n }\n\n if (!target.closest(\".array-actions__remove\")) return;\n if (!menuIsOurs.current || rows.length > minRows) return;\n\n event.preventDefault();\n event.stopPropagation();\n toast.error(\n t(\"validation:requiresAtLeast\", {\n count: minRows,\n label: rowLabel(field, minRows, i18n.language) ??\n t(minRows > 1 ? \"general:rows\" : \"general:row\"),\n }),\n );\n },\n [field, i18n.language, minRows, rows.length, t],\n );\n\n return (\n <div ref={rootRef} onClickCapture={onClickCapture}>\n <ArrayField {...props} />\n </div>\n );\n};\n\n/**\n * The word for a row, as Payload's own \"requires at least\" banner picks it:\n * the plural label above one, the singular otherwise, the field label failing\n * both. Client-side labels are static — a string or a per-language record.\n */\nfunction rowLabel(\n field: ArrayFieldClientProps[\"field\"],\n minRows: number,\n language: string,\n): string | undefined {\n const label = minRows > 1 ? field.labels?.plural : field.labels?.singular;\n return staticLabel(label, language) ?? staticLabel(field.label, language);\n}\n\nfunction staticLabel(label: unknown, language: string): string | undefined {\n if (typeof label === \"string\") return label;\n if (label && typeof label === \"object\") {\n const byLanguage = label as Record<string, string | undefined>;\n return byLanguage[language] ?? byLanguage.en ?? Object.values(byLanguage)[0];\n }\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,qBAAgD,UAAU;CACrE,MAAM,EAAE,OAAO,MAAM,kBAAkB;CACvC,MAAM,UAAU,MAAM,YAAY,MAAM,WAAW,IAAI;CAEvD,MAAM,EAAE,OAAO,EAAE,KAAK,SAAS;EAC7B,SAAS;EACT,sBAAsB;EACvB,CAAC;CACF,MAAM,EAAE,MAAM,MAAM,gBAAgB;CAEpC,MAAM,UAAU,OAAuB,KAAK;CAC5C,MAAM,aAAa,OAAO,MAAM;AAiChC,QACE,oBAAC,OAAD;EAAK,KAAK;EAAS,gBAhCE,aACpB,UAA4C;GAC3C,MAAM,SAAS,MAAM;AACrB,OAAI,EAAE,kBAAkB,SAAU;GAElC,MAAM,UAAU,OAAO,QAAQ,yBAAyB;AACxD,OAAI,SAAS;IAGX,MAAM,QAAQ,QAAQ,QAAQ,8BAA8B;AAC5D,eAAW,UACT,UAAU,QAAQ,UAAU,QAAQ,SAAS,cAAc,eAAe;AAC5E;;AAGF,OAAI,CAAC,OAAO,QAAQ,yBAAyB,CAAE;AAC/C,OAAI,CAAC,WAAW,WAAW,KAAK,SAAS,QAAS;AAElD,SAAM,gBAAgB;AACtB,SAAM,iBAAiB;AACvB,SAAM,MACJ,EAAE,8BAA8B;IAC9B,OAAO;IACP,OAAO,SAAS,OAAO,SAAS,KAAK,SAAS,IAC5C,EAAE,UAAU,IAAI,iBAAiB,cAAc;IAClD,CAAC,CACH;KAEH;GAAC;GAAO,KAAK;GAAU;GAAS,KAAK;GAAQ;GAAE,CAChD;YAIG,oBAAC,YAAD,EAAY,GAAI,OAAS,CAAA;EACrB,CAAA;;;;;;;AASV,SAAS,SACP,OACA,SACA,UACoB;AAEpB,QAAO,YADO,UAAU,IAAI,MAAM,QAAQ,SAAS,MAAM,QAAQ,UACvC,SAAS,IAAI,YAAY,MAAM,OAAO,SAAS;;AAG3E,SAAS,YAAY,OAAgB,UAAsC;AACzE,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI,SAAS,OAAO,UAAU,UAAU;EACtC,MAAM,aAAa;AACnB,SAAO,WAAW,aAAa,WAAW,MAAM,OAAO,OAAO,WAAW,CAAC"}
package/dist/index.d.mts CHANGED
@@ -132,6 +132,29 @@ declare function headingFields({
132
132
  eyebrowDescription
133
133
  }?: HeadingFieldsOptions): Field[];
134
134
  //#endregion
135
+ //#region src/fields/min-rows.d.ts
136
+ /**
137
+ * The import-map path of the array field that refuses to go below `minRows`.
138
+ *
139
+ * Payload gates *Add* on `maxRows` but never gates *Remove* on `minRows`: an
140
+ * editor can delete the third showcase panel, see a "requires 3 panels"
141
+ * note, and only learn on publish that the section is invalid. Payload has no
142
+ * option for this, so it is an admin component (`@bison-lab/payload-blocks/admin`)
143
+ * referenced here by package specifier. Every array in this package with a
144
+ * `minRows` uses it, and a site can put it on its own arrays:
145
+ *
146
+ * ```ts
147
+ * { name: 'items', type: 'array', minRows: 2,
148
+ * admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } }, fields: [...] }
149
+ * ```
150
+ *
151
+ * Consuming sites pick it up by re-running `payload generate:importmap`. Until
152
+ * they do, Payload logs the missing entry and falls back to its stock field.
153
+ */
154
+ declare const MIN_ROWS_ARRAY_FIELD = "@bison-lab/payload-blocks/admin#MinRowsArrayField";
155
+ /** Empty rows for an array's `defaultValue`, so a block opens at its minimum. */
156
+ declare function emptyRows(count: number): Record<string, never>[];
157
+ //#endregion
135
158
  //#region src/media.d.ts
136
159
  /**
137
160
  * The upload side of the boundary.
@@ -248,17 +271,11 @@ interface ShowcasePanelsBlockData extends BlockRow<"showcasePanels"> {
248
271
  watermark?: string | null;
249
272
  spineVariant?: ("numbered" | "volume" | "minimal") | null;
250
273
  defaultActiveIndex?: number | null;
251
- ariaLabel?: string | null;
252
- labels?: {
253
- selected?: string | null;
254
- preview?: string | null;
255
- };
256
274
  }
257
275
  interface ProcessStepsItemData {
258
276
  title?: string | null;
259
277
  description?: string | null;
260
278
  image?: MediaValue;
261
- step?: number | null;
262
279
  id?: string | null;
263
280
  }
264
281
  interface ProcessStepsBlockData extends BlockRow<"processSteps"> {
@@ -267,11 +284,6 @@ interface ProcessStepsBlockData extends BlockRow<"processSteps"> {
267
284
  autoAdvance?: boolean | null;
268
285
  autoAdvanceDuration?: number | null;
269
286
  pauseOnHover?: boolean | null;
270
- ariaLabel?: string | null;
271
- labels?: {
272
- pause?: string | null;
273
- resume?: string | null;
274
- };
275
287
  }
276
288
  interface FaqColumnsItemData {
277
289
  question?: string | null;
@@ -340,5 +352,5 @@ type BisonBlockData = HeroBlockData | RichTextBlockData | ShowcasePanelsBlockDat
340
352
  /** The `blockType` of every block this package ships. */
341
353
  type BisonBlockType = BisonBlockData["blockType"];
342
354
  //#endregion
343
- export { type BisonBlockData, type BisonBlockType, FaqColumnsBlock, type FaqColumnsBlockData, type FaqColumnsItemData, type HeadingFieldsOptions, HeroBlock, type HeroBlockData, type ImageFieldOptions, type LinkFieldOptions, type LinkFieldsOptions, type LinkValue, type MediaDoc, type MediaValue, NapBlock, type NapBlockData, type NapDepartmentData, ProcessStepsBlock, type ProcessStepsBlockData, type ProcessStepsItemData, type ResolvedMedia, RichTextBlock, type RichTextBlockData, type RichTextContent, ShowcasePanelsBlock, type ShowcasePanelsBlockData, type ShowcasePanelsItemData, TestimonialMasonryBlock, type TestimonialMasonryBlockData, type TestimonialMasonryItemData, headingFields, imageField, linkField, linkFields, resolveMedia };
355
+ export { type BisonBlockData, type BisonBlockType, FaqColumnsBlock, type FaqColumnsBlockData, type FaqColumnsItemData, type HeadingFieldsOptions, HeroBlock, type HeroBlockData, type ImageFieldOptions, type LinkFieldOptions, type LinkFieldsOptions, type LinkValue, MIN_ROWS_ARRAY_FIELD, type MediaDoc, type MediaValue, NapBlock, type NapBlockData, type NapDepartmentData, ProcessStepsBlock, type ProcessStepsBlockData, type ProcessStepsItemData, type ResolvedMedia, RichTextBlock, type RichTextBlockData, type RichTextContent, ShowcasePanelsBlock, type ShowcasePanelsBlockData, type ShowcasePanelsItemData, TestimonialMasonryBlock, type TestimonialMasonryBlockData, type TestimonialMasonryItemData, emptyRows, headingFields, imageField, linkField, linkFields, resolveMedia };
344
356
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/blocks/hero/config.ts","../src/blocks/rich-text/config.ts","../src/blocks/showcase-panels/config.ts","../src/blocks/process-steps/config.ts","../src/blocks/faq-columns/config.ts","../src/blocks/testimonial-masonry/config.ts","../src/blocks/nap/config.ts","../src/fields/image.ts","../src/fields/link.ts","../src/fields/heading.ts","../src/media.ts","../src/types.ts"],"mappings":";;;;;AAcA;;;;;;;cAAa,SAAA,EAAW,KAAA;;;;cCXX,aAAA,EAAe,KAAA;;;;cCEf,mBAAA,EAAqB,KAAA;;;;cCArB,iBAAA,EAAmB,KAAA;;;;cCAnB,eAAA,EAAiB,KAAA;;;;cCEjB,uBAAA,EAAyB,KAAA;;;;;ALOtC;;;;;;cMJa,QAAA,EAAU,KAAA;;;;;ANIvB;;;KOPK,gBAAA,GAAmB,OAAA,CACtB,WAAA;EACE,OAAA;EAAiB,UAAA,EAAY,cAAA;AAAA;AAAA,UAGhB,iBAAA,SAA0B,OAAA,CAAQ,gBAAA;ENTtC;;;;EMcX,UAAA,GAAa,cAAA;AAAA;;;ALZf;;;;;;iBKuBgB,UAAA,CAAW,SAAA,GAAW,iBAAA,GAAyB,gBAAA;;;UC1B9C,iBAAA;;EAEf,QAAA;ER8CD;EQ5CC,eAAA;AAAA;;;;;APHF;;;;;;;iBOiBgB,UAAA,CAAA;EACd,QAAA;EACA;AAAA,IACC,iBAAA,GAAyB,KAAA;AAAA,UAqBX,gBAAA,SAAyB,iBAAA;ENyCzC;EMvCC,IAAA;EACA,KAAA,GAAQ,UAAA;EACR,KAAA,GAAQ,UAAA;AAAA;;iBAIM,SAAA,CAAA;EACd,IAAA;EACA,KAAA;EACA,KAAA;EAAA,GACG;AAAA,IACF,gBAAA,GAAwB,UAAA;ALpD3B;AAAA,UK+DiB,SAAA;EACf,KAAA;EACA,IAAA;EACA,MAAA;AAAA;;;UCrEe,oBAAA;;EAEf,QAAA;ET8CD;ES5CC,kBAAA;AAAA;;;;;ARHF;;;;iBQcgB,aAAA,CAAA;EACd,QAAA;EACA;AAAA,IACC,oBAAA,GAA4B,KAAA;;;;;;ATN/B;;;;;;;;ACXA;;;;;;;;USiBiB,QAAA;EACf,GAAA;EACA,GAAA;EACA,KAAA;EACA,MAAA;AAAA;;KAIU,UAAA,qBAA+B,QAAA;APvB3C;AAAA,UO0BiB,aAAA;EACf,GAAA;EACA,GAAA;EACA,KAAA;EACA,MAAA;AAAA;;AN9BF;;;;;;;;ACEA;;;iBK+CgB,YAAA,CAAa,KAAA,EAAO,UAAA,GAAa,aAAA;;;;AVxCjD;;;;;;;;ACXA;;;;;;;;ACEA;;;;;;USsBU,QAAA;EACR,EAAA;EACA,SAAA;EACA,SAAA,EAAW,CAAA;AAAA;;;;;UAOI,eAAA;EACf,IAAA;IACE,IAAA;IACA,QAAA;IACA,SAAA;IACA,MAAA;IACA,MAAA;IACA,OAAA;EAAA;AAAA;AAAA,UAIa,aAAA,SAAsB,QAAA;EACrC,OAAA;EACA,OAAA;EACA,IAAA;EACA,IAAA;EACA,KAAA,GAAQ,UAAA;EACR,KAAA,GAAQ,SAAA;AAAA;AAAA,UAGO,iBAAA,SAA0B,QAAA;EACzC,OAAA,GAAU,eAAA;AAAA;AAAA,UAGK,sBAAA;EACf,KAAA;EACA,OAAA;EACA,KAAA,GAAQ,UAAA;EACR,IAAA;EACA,OAAA;EACA,EAAA;AAAA;AAAA,UAGe,uBAAA,SAAgC,QAAA;EAC/C,KAAA,GAAQ,sBAAA;EACR,SAAA;EACA,YAAA;EACA,kBAAA;EACA,SAAA;EACA,MAAA;IACE,QAAA;IACA,OAAA;EAAA;AAAA;AAAA,UAIa,oBAAA;EACf,KAAA;EACA,WAAA;EACA,KAAA,GAAQ,UAAA;EACR,IAAA;EACA,EAAA;AAAA;AAAA,UAGe,qBAAA,SAA8B,QAAA;EAC7C,KAAA,GAAQ,oBAAA;EACR,kBAAA;EACA,WAAA;EACA,mBAAA;EACA,YAAA;EACA,SAAA;EACA,MAAA;IACE,KAAA;IACA,MAAA;EAAA;AAAA;AAAA,UAIa,kBAAA;EACf,QAAA;EJ5E6E;EI8E7E,MAAA;EACA,EAAA;AAAA;AAAA,UAGe,mBAAA,SAA4B,QAAA;EAC3C,OAAA;EACA,KAAA;EACA,WAAA;EACA,KAAA,GAAQ,kBAAA;EACR,GAAA;IACE,KAAA;IACA,QAAA;IACA,IAAA;IACA,MAAA;EAAA;EAEF,MAAA;EACA,IAAA;EACA,WAAA;AAAA;AAAA,UAGe,0BAAA;EACf,OAAA;EACA,MAAA;IACE,IAAA;IACA,KAAA;IACA,MAAA,GAAS,UAAA;EAAA;EAEX,EAAA;AAAA;AAAA,UAGe,2BAAA,SACP,QAAA;EACR,OAAA;EACA,KAAA;EACA,WAAA;EACA,KAAA,GAAQ,0BAAA;EACR,IAAA,GAAO,SAAA;EACP,eAAA;EACA,cAAA;AAAA;AAAA,UAGe,iBAAA;EACf,IAAA;EACA,cAAA;EACA,SAAA;EACA,YAAA;EACA,EAAA;AAAA;AAAA,UAGe,YAAA,SAAqB,QAAA;EACpC,YAAA;EACA,YAAA;EACA,OAAA;IACE,aAAA;IACA,eAAA;IACA,aAAA;IACA,UAAA;IACA,cAAA;EAAA;EAEF,WAAA,GAAc,iBAAA;EACd,GAAA;EACA,QAAA;EACA,YAAA;EACA,UAAA;AAAA;;KAIU,cAAA,GACR,aAAA,GACA,iBAAA,GACA,uBAAA,GACA,qBAAA,GACA,mBAAA,GACA,2BAAA,GACA,YAAA;;KAGQ,cAAA,GAAiB,cAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/blocks/hero/config.ts","../src/blocks/rich-text/config.ts","../src/blocks/showcase-panels/config.ts","../src/blocks/process-steps/config.ts","../src/blocks/faq-columns/config.ts","../src/blocks/testimonial-masonry/config.ts","../src/blocks/nap/config.ts","../src/fields/image.ts","../src/fields/link.ts","../src/fields/heading.ts","../src/fields/min-rows.ts","../src/media.ts","../src/types.ts"],"mappings":";;;;;AAcA;;;;;;;cAAa,SAAA,EAAW,KAAA;;;;cCXX,aAAA,EAAe,KAAA;;;;cCGf,mBAAA,EAAqB,KAAA;;;;cCArB,iBAAA,EAAmB,KAAA;;;;cCAnB,eAAA,EAAiB,KAAA;;;;cCEjB,uBAAA,EAAyB,KAAA;;;;;ALMtC;;;;;;cMFa,QAAA,EAAU,KAAA;;;;;ANEvB;;;KOPK,gBAAA,GAAmB,OAAA,CACtB,WAAA;EACE,OAAA;EAAiB,UAAA,EAAY,cAAA;AAAA;AAAA,UAGhB,iBAAA,SAA0B,OAAA,CAAQ,gBAAA;ENTtC;;;;EMcX,UAAA,GAAa,cAAA;AAAA;;;ALXf;;;;;;iBKsBgB,UAAA,CAAW,SAAA,GAAW,iBAAA,GAAyB,gBAAA;;;UC1B9C,iBAAA;;EAEf,QAAA;ERgDD;EQ9CC,eAAA;AAAA;;;;;APHF;;;;;;;iBOiBgB,UAAA,CAAA;EACd,QAAA;EACA;AAAA,IACC,iBAAA,GAAyB,KAAA;AAAA,UAqBX,gBAAA,SAAyB,iBAAA;EN+BzC;EM7BC,IAAA;EACA,KAAA,GAAQ,UAAA;EACR,KAAA,GAAQ,UAAA;AAAA;;iBAIM,SAAA,CAAA;EACd,IAAA;EACA,KAAA;EACA,KAAA;EAAA,GACG;AAAA,IACF,gBAAA,GAAwB,UAAA;ALnD3B;AAAA,UK8DiB,SAAA;EACf,KAAA;EACA,IAAA;EACA,MAAA;AAAA;;;UCrEe,oBAAA;;EAEf,QAAA;ETgDD;ES9CC,kBAAA;AAAA;;;;;ARHF;;;;iBQcgB,aAAA,CAAA;EACd,QAAA;EACA;AAAA,IACC,oBAAA,GAA4B,KAAA;;;;;;ATN/B;;;;;;;;ACXA;;;;;;;cSea,oBAAA;ARZb;AAAA,iBQgBgB,SAAA,CAAU,KAAA,WAAgB,MAAA;;;;;;AVR1C;;;;;;;;ACXA;;;;;;;;UUiBiB,QAAA;EACf,GAAA;EACA,GAAA;EACA,KAAA;EACA,MAAA;AAAA;;KAIU,UAAA,qBAA+B,QAAA;ARtB3C;AAAA,UQyBiB,aAAA;EACf,GAAA;EACA,GAAA;EACA,KAAA;EACA,MAAA;AAAA;;AP7BF;;;;;;;;ACEA;;;iBM8CgB,YAAA,CAAa,KAAA,EAAO,UAAA,GAAa,aAAA;;;;AXxCjD;;;;;;;;ACXA;;;;;;;;ACGA;;;;;;UUqBU,QAAA;EACR,EAAA;EACA,SAAA;EACA,SAAA,EAAW,CAAA;AAAA;;;;;UAOI,eAAA;EACf,IAAA;IACE,IAAA;IACA,QAAA;IACA,SAAA;IACA,MAAA;IACA,MAAA;IACA,OAAA;EAAA;AAAA;AAAA,UAIa,aAAA,SAAsB,QAAA;EACrC,OAAA;EACA,OAAA;EACA,IAAA;EACA,IAAA;EACA,KAAA,GAAQ,UAAA;EACR,KAAA,GAAQ,SAAA;AAAA;AAAA,UAGO,iBAAA,SAA0B,QAAA;EACzC,OAAA,GAAU,eAAA;AAAA;AAAA,UAGK,sBAAA;EACf,KAAA;EACA,OAAA;EACA,KAAA,GAAQ,UAAA;EACR,IAAA;EACA,OAAA;EACA,EAAA;AAAA;AAAA,UAGe,uBAAA,SAAgC,QAAA;EAC/C,KAAA,GAAQ,sBAAA;EACR,SAAA;EACA,YAAA;EACA,kBAAA;AAAA;AAAA,UAGe,oBAAA;EACf,KAAA;EACA,WAAA;EACA,KAAA,GAAQ,UAAA;EACR,EAAA;AAAA;AAAA,UAGe,qBAAA,SAA8B,QAAA;EAC7C,KAAA,GAAQ,oBAAA;EACR,kBAAA;EACA,WAAA;EACA,mBAAA;EACA,YAAA;AAAA;AAAA,UAGe,kBAAA;EACf,QAAA;EL5EA;EK8EA,MAAA;EACA,EAAA;AAAA;AAAA,UAGe,mBAAA,SAA4B,QAAA;EAC3C,OAAA;EACA,KAAA;EACA,WAAA;EACA,KAAA,GAAQ,kBAAA;EACR,GAAA;IACE,KAAA;IACA,QAAA;IACA,IAAA;IACA,MAAA;EAAA;EAEF,MAAA;EACA,IAAA;EACA,WAAA;AAAA;AAAA,UAGe,0BAAA;EACf,OAAA;EACA,MAAA;IACE,IAAA;IACA,KAAA;IACA,MAAA,GAAS,UAAA;EAAA;EAEX,EAAA;AAAA;AAAA,UAGe,2BAAA,SACP,QAAA;EACR,OAAA;EACA,KAAA;EACA,WAAA;EACA,KAAA,GAAQ,0BAAA;EACR,IAAA,GAAO,SAAA;EACP,eAAA;EACA,cAAA;AAAA;AAAA,UAGe,iBAAA;EACf,IAAA;EACA,cAAA;EACA,SAAA;EACA,YAAA;EACA,EAAA;AAAA;AAAA,UAGe,YAAA,SAAqB,QAAA;EACpC,YAAA;EACA,YAAA;EACA,OAAA;IACE,aAAA;IACA,eAAA;IACA,aAAA;IACA,UAAA;IACA,cAAA;EAAA;EAEF,WAAA,GAAc,iBAAA;EACd,GAAA;EACA,QAAA;EACA,YAAA;EACA,UAAA;AAAA;;KAIU,cAAA,GACR,aAAA,GACA,iBAAA,GACA,uBAAA,GACA,qBAAA,GACA,mBAAA,GACA,2BAAA,GACA,YAAA;;KAGQ,cAAA,GAAiB,cAAA"}
package/dist/index.mjs CHANGED
@@ -108,7 +108,7 @@ const HeroBlock = {
108
108
  value: "dark"
109
109
  },
110
110
  {
111
- label: "Light (page background)",
111
+ label: "Plain (page surface)",
112
112
  value: "light"
113
113
  }
114
114
  ],
@@ -144,6 +144,31 @@ const RichTextBlock = {
144
144
  }]
145
145
  };
146
146
  //#endregion
147
+ //#region src/fields/min-rows.ts
148
+ /**
149
+ * The import-map path of the array field that refuses to go below `minRows`.
150
+ *
151
+ * Payload gates *Add* on `maxRows` but never gates *Remove* on `minRows`: an
152
+ * editor can delete the third showcase panel, see a "requires 3 panels"
153
+ * note, and only learn on publish that the section is invalid. Payload has no
154
+ * option for this, so it is an admin component (`@bison-lab/payload-blocks/admin`)
155
+ * referenced here by package specifier. Every array in this package with a
156
+ * `minRows` uses it, and a site can put it on its own arrays:
157
+ *
158
+ * ```ts
159
+ * { name: 'items', type: 'array', minRows: 2,
160
+ * admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } }, fields: [...] }
161
+ * ```
162
+ *
163
+ * Consuming sites pick it up by re-running `payload generate:importmap`. Until
164
+ * they do, Payload logs the missing entry and falls back to its stock field.
165
+ */
166
+ const MIN_ROWS_ARRAY_FIELD = "@bison-lab/payload-blocks/admin#MinRowsArrayField";
167
+ /** Empty rows for an array's `defaultValue`, so a block opens at its minimum. */
168
+ function emptyRows(count) {
169
+ return Array.from({ length: count }, () => ({}));
170
+ }
171
+ //#endregion
147
172
  //#region src/blocks/showcase-panels/config.ts
148
173
  /** Expanding panels, one per service or product line. */
149
174
  const ShowcasePanelsBlock = {
@@ -160,10 +185,12 @@ const ShowcasePanelsBlock = {
160
185
  required: true,
161
186
  minRows: 3,
162
187
  maxRows: 6,
188
+ defaultValue: emptyRows(3),
163
189
  labels: {
164
190
  singular: "Panel",
165
191
  plural: "Panels"
166
192
  },
193
+ admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } },
167
194
  fields: [
168
195
  {
169
196
  name: "title",
@@ -219,23 +246,6 @@ const ShowcasePanelsBlock = {
219
246
  defaultValue: 0,
220
247
  min: 0,
221
248
  admin: { description: "Which panel is open on load, counting from 0." }
222
- },
223
- {
224
- name: "ariaLabel",
225
- type: "text",
226
- admin: { description: "Names the region for screen readers. Defaults to \"Showcase panels\"." }
227
- },
228
- {
229
- name: "labels",
230
- type: "group",
231
- admin: { description: "Overrides for the panel state text." },
232
- fields: [{
233
- name: "selected",
234
- type: "text"
235
- }, {
236
- name: "preview",
237
- type: "text"
238
- }]
239
249
  }
240
250
  ]
241
251
  };
@@ -256,10 +266,12 @@ const ProcessStepsBlock = {
256
266
  required: true,
257
267
  minRows: 3,
258
268
  maxRows: 6,
269
+ defaultValue: emptyRows(3),
259
270
  labels: {
260
271
  singular: "Step",
261
272
  plural: "Steps"
262
273
  },
274
+ admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } },
263
275
  fields: [
264
276
  {
265
277
  name: "title",
@@ -271,13 +283,7 @@ const ProcessStepsBlock = {
271
283
  type: "textarea",
272
284
  required: true
273
285
  },
274
- imageField({ required: true }),
275
- {
276
- name: "step",
277
- type: "number",
278
- min: 1,
279
- admin: { description: "Overrides the displayed number. Leave empty to number by position." }
280
- }
286
+ imageField({ required: true })
281
287
  ]
282
288
  },
283
289
  {
@@ -304,23 +310,6 @@ const ProcessStepsBlock = {
304
310
  defaultValue: 0,
305
311
  min: 0,
306
312
  admin: { description: "Which step is active on load, counting from 0." }
307
- },
308
- {
309
- name: "ariaLabel",
310
- type: "text",
311
- admin: { description: "Names the region for screen readers. Defaults to \"Process steps\"." }
312
- },
313
- {
314
- name: "labels",
315
- type: "group",
316
- admin: { description: "Overrides for the play/pause control text." },
317
- fields: [{
318
- name: "pause",
319
- type: "text"
320
- }, {
321
- name: "resume",
322
- type: "text"
323
- }]
324
313
  }
325
314
  ]
326
315
  };
@@ -369,10 +358,12 @@ const FaqColumnsBlock = {
369
358
  type: "array",
370
359
  required: true,
371
360
  minRows: 1,
361
+ defaultValue: emptyRows(1),
372
362
  labels: {
373
363
  singular: "Question",
374
364
  plural: "Questions"
375
365
  },
366
+ admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } },
376
367
  fields: [{
377
368
  name: "question",
378
369
  type: "text",
@@ -453,10 +444,12 @@ const TestimonialMasonryBlock = {
453
444
  type: "array",
454
445
  required: true,
455
446
  minRows: 1,
447
+ defaultValue: emptyRows(1),
456
448
  labels: {
457
449
  singular: "Testimonial",
458
450
  plural: "Testimonials"
459
451
  },
452
+ admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } },
460
453
  fields: [{
461
454
  name: "content",
462
455
  type: "textarea",
@@ -572,11 +565,15 @@ const NapBlock = {
572
565
  type: "array",
573
566
  required: true,
574
567
  minRows: 1,
568
+ defaultValue: emptyRows(1),
575
569
  labels: {
576
570
  singular: "Department",
577
571
  plural: "Departments"
578
572
  },
579
- admin: { description: "A single-location business has one entry, usually named after the business." },
573
+ admin: {
574
+ components: { Field: MIN_ROWS_ARRAY_FIELD },
575
+ description: "A single-location business has one entry, usually named after the business."
576
+ },
580
577
  fields: [
581
578
  {
582
579
  name: "name",
@@ -672,6 +669,6 @@ function resolveMedia(value) {
672
669
  };
673
670
  }
674
671
  //#endregion
675
- export { FaqColumnsBlock, HeroBlock, NapBlock, ProcessStepsBlock, RichTextBlock, ShowcasePanelsBlock, TestimonialMasonryBlock, headingFields, imageField, linkField, linkFields, resolveMedia };
672
+ export { FaqColumnsBlock, HeroBlock, MIN_ROWS_ARRAY_FIELD, NapBlock, ProcessStepsBlock, RichTextBlock, ShowcasePanelsBlock, TestimonialMasonryBlock, emptyRows, headingFields, imageField, linkField, linkFields, resolveMedia };
676
673
 
677
674
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/fields/image.ts","../src/fields/link.ts","../src/blocks/hero/config.ts","../src/blocks/rich-text/config.ts","../src/blocks/showcase-panels/config.ts","../src/blocks/process-steps/config.ts","../src/fields/heading.ts","../src/blocks/faq-columns/config.ts","../src/blocks/testimonial-masonry/config.ts","../src/blocks/nap/config.ts","../src/media.ts"],"sourcesContent":["import type { CollectionSlug, UploadField } from \"payload\";\n\n/**\n * Payload's `UploadField` is a union over the polymorphic (`relationTo: [...]`)\n * and `hasMany` variants. A block image is neither, and narrowing here is what\n * lets `imageField({ name: 'portrait' })` stay type-checked at the call site.\n */\ntype MediaUploadField = Extract<\n UploadField,\n { hasMany?: false; relationTo: CollectionSlug }\n>;\n\nexport interface ImageFieldOptions extends Partial<MediaUploadField> {\n /**\n * The upload collection to point at. Every Bison Lab site names it `media`,\n * but a site that does not can say so without forking the field.\n */\n relationTo?: CollectionSlug;\n}\n\n/**\n * A single image from the site's upload collection, for any block with a\n * picture in it.\n *\n * Optional unless the block says otherwise: `imageField({ name: 'portrait',\n * required: true })`. `admin` is replaced, not merged, so a block that passes\n * its own description writes the whole thing.\n */\nexport function imageField(overrides: ImageFieldOptions = {}): MediaUploadField {\n return {\n name: \"image\",\n type: \"upload\",\n relationTo: \"media\" as CollectionSlug,\n // Payload stars a required field and says nothing about an optional one,\n // so an optional image says so itself, and only then.\n ...(overrides.required ? {} : { admin: { description: \"Optional.\" } }),\n ...overrides,\n } as MediaUploadField;\n}\n","import type { Field, GroupField } from \"payload\";\n\nexport interface LinkFieldsOptions {\n /** Require the label and destination. Defaults to `false`. */\n required?: boolean;\n /** Wording for the visible text field. Defaults to \"Label\". */\n labelFieldLabel?: string;\n}\n\n/**\n * The three fields a call to action is made of, unwrapped.\n *\n * `href` is plain text rather than a relationship to a `pages` row: a block in\n * this package has to work on a site whose page collection is named something\n * else, or which links out more often than in. Resolving an internal reference\n * is the site's job.\n *\n * `newTab` drives `target=\"_blank\"` *and* the matching `rel`, which is why no\n * renderer takes one without the other.\n */\nexport function linkFields({\n required = false,\n labelFieldLabel = \"Label\",\n}: LinkFieldsOptions = {}): Field[] {\n return [\n { name: \"label\", type: \"text\", label: labelFieldLabel, required },\n {\n name: \"href\",\n type: \"text\",\n required,\n admin: {\n description:\n 'A site path (\"/contact\") or a full URL (\"https://example.com\").',\n },\n },\n {\n name: \"newTab\",\n type: \"checkbox\",\n label: \"Open in a new tab\",\n defaultValue: false,\n },\n ];\n}\n\nexport interface LinkFieldOptions extends LinkFieldsOptions {\n /** Field name. Defaults to `link`. */\n name?: string;\n label?: GroupField[\"label\"];\n admin?: GroupField[\"admin\"];\n}\n\n/** `linkFields()` as one named group, for a block with a single CTA. */\nexport function linkField({\n name = \"link\",\n label,\n admin,\n ...rest\n}: LinkFieldOptions = {}): GroupField {\n return {\n name,\n type: \"group\",\n fields: linkFields(rest),\n ...(label === undefined ? {} : { label }),\n ...(admin === undefined ? {} : { admin }),\n };\n}\n\n/** The shape `linkField`/`linkFields` produce once Payload generates types. */\nexport interface LinkValue {\n label?: string | null;\n href?: string | null;\n newTab?: boolean | null;\n}\n","import type { Block } from \"payload\";\n\nimport { imageField } from \"../../fields/image\";\nimport { linkFields } from \"../../fields/link\";\n\n/**\n * The band a page opens with.\n *\n * Unlike every other block here this one has no `@bison-lab/ui` counterpart:\n * a hero is where a site's brand is loudest, and the shipped renderer is\n * deliberately plain markup so a site can swap in its own by overriding this\n * one registry entry. The *fields* are the shared part — that is what makes a\n * page portable between sites.\n */\nexport const HeroBlock: Block = {\n slug: \"hero\",\n interfaceName: \"HeroBlock\",\n labels: { singular: \"Hero\", plural: \"Heroes\" },\n fields: [\n {\n name: \"eyebrow\",\n type: \"text\",\n admin: { description: \"Small label above the heading. Optional.\" },\n },\n { name: \"heading\", type: \"text\", required: true },\n { name: \"body\", type: \"textarea\" },\n {\n name: \"tone\",\n type: \"select\",\n defaultValue: \"sweep\",\n options: [\n { label: \"Sweep (brand gradient)\", value: \"sweep\" },\n { label: \"Dark (flat brand band)\", value: \"dark\" },\n { label: \"Light (page background)\", value: \"light\" },\n ],\n admin: {\n description:\n \"How the band is painted. Sites map these to their own surfaces.\",\n },\n },\n imageField({ admin: { description: \"Optional background or lead image.\" } }),\n {\n name: \"links\",\n type: \"array\",\n // Two is the point at which a hero stops having a primary action.\n maxRows: 2,\n labels: { singular: \"Call to action\", plural: \"Calls to action\" },\n fields: linkFields({ required: true }),\n },\n ],\n};\n","import type { Block } from \"payload\";\n\n/** A prose section: one Lexical document at reading measure. */\nexport const RichTextBlock: Block = {\n slug: \"richText\",\n interfaceName: \"RichTextBlock\",\n labels: { singular: \"Rich Text\", plural: \"Rich Text\" },\n fields: [{ name: \"content\", type: \"richText\", required: true }],\n};\n","import type { Block } from \"payload\";\n\nimport { imageField } from \"../../fields/image\";\n\n/** Expanding panels, one per service or product line. */\nexport const ShowcasePanelsBlock: Block = {\n slug: \"showcasePanels\",\n interfaceName: \"ShowcasePanelsBlock\",\n labels: { singular: \"Showcase Panels\", plural: \"Showcase Panels\" },\n fields: [\n {\n name: \"items\",\n type: \"array\",\n required: true,\n // The layout is designed around 3-6 panels: fewer and the accordion\n // reads as a mistake, more and each collapsed spine is unreadable.\n minRows: 3,\n maxRows: 6,\n labels: { singular: \"Panel\", plural: \"Panels\" },\n fields: [\n { name: \"title\", type: \"text\", required: true },\n {\n name: \"summary\",\n type: \"textarea\",\n required: true,\n admin: { description: \"Revealed when the panel expands.\" },\n },\n imageField({ required: true }),\n {\n name: \"href\",\n type: \"text\",\n admin: {\n description: 'Optional \"read more\" destination for this panel.',\n },\n },\n {\n name: \"numeral\",\n type: \"text\",\n admin: {\n description:\n 'Overrides the spine numeral (\"01\", \"II\"). Leave empty to number automatically.',\n },\n },\n ],\n },\n {\n name: \"spineVariant\",\n type: \"select\",\n defaultValue: \"numbered\",\n options: [\n { label: \"Numbered (01, 02)\", value: \"numbered\" },\n { label: \"Volume (I, II)\", value: \"volume\" },\n { label: \"Minimal (no numeral)\", value: \"minimal\" },\n ],\n },\n {\n name: \"watermark\",\n type: \"text\",\n admin: { description: 'Faint mark behind the panels, e.g. a brand word.' },\n },\n {\n name: \"defaultActiveIndex\",\n type: \"number\",\n defaultValue: 0,\n min: 0,\n admin: { description: \"Which panel is open on load, counting from 0.\" },\n },\n {\n name: \"ariaLabel\",\n type: \"text\",\n admin: {\n description:\n 'Names the region for screen readers. Defaults to \"Showcase panels\".',\n },\n },\n {\n name: \"labels\",\n type: \"group\",\n admin: { description: \"Overrides for the panel state text.\" },\n fields: [\n { name: \"selected\", type: \"text\" },\n { name: \"preview\", type: \"text\" },\n ],\n },\n ],\n};\n","import type { Block } from \"payload\";\n\nimport { imageField } from \"../../fields/image\";\n\n/** An ordered walkthrough that advances on its own. */\nexport const ProcessStepsBlock: Block = {\n slug: \"processSteps\",\n interfaceName: \"ProcessStepsBlock\",\n labels: { singular: \"Process Steps\", plural: \"Process Steps\" },\n fields: [\n {\n name: \"items\",\n type: \"array\",\n required: true,\n // Same reasoning as the showcase panels: the step rail is designed\n // around 3-6 entries, in order.\n minRows: 3,\n maxRows: 6,\n labels: { singular: \"Step\", plural: \"Steps\" },\n fields: [\n { name: \"title\", type: \"text\", required: true },\n { name: \"description\", type: \"textarea\", required: true },\n imageField({ required: true }),\n {\n name: \"step\",\n type: \"number\",\n min: 1,\n admin: {\n description:\n \"Overrides the displayed number. Leave empty to number by position.\",\n },\n },\n ],\n },\n {\n name: \"autoAdvance\",\n type: \"checkbox\",\n defaultValue: true,\n admin: {\n description:\n \"Advance on a timer. Always off for visitors who prefer reduced motion.\",\n },\n },\n {\n name: \"autoAdvanceDuration\",\n type: \"number\",\n defaultValue: 6000,\n min: 1000,\n admin: { description: \"Milliseconds each step holds before advancing.\" },\n },\n { name: \"pauseOnHover\", type: \"checkbox\", defaultValue: true },\n {\n name: \"defaultActiveIndex\",\n type: \"number\",\n defaultValue: 0,\n min: 0,\n admin: { description: \"Which step is active on load, counting from 0.\" },\n },\n {\n name: \"ariaLabel\",\n type: \"text\",\n admin: {\n description:\n 'Names the region for screen readers. Defaults to \"Process steps\".',\n },\n },\n {\n name: \"labels\",\n type: \"group\",\n admin: { description: \"Overrides for the play/pause control text.\" },\n fields: [\n { name: \"pause\", type: \"text\" },\n { name: \"resume\", type: \"text\" },\n ],\n },\n ],\n};\n","import type { Field } from \"payload\";\n\nexport interface HeadingFieldsOptions {\n /** Require the section heading. Defaults to `true`. */\n required?: boolean;\n /** Description shown under the eyebrow input. */\n eyebrowDescription?: string;\n}\n\n/**\n * The eyebrow / title / description trio every marketing band opens with.\n *\n * They are three top-level fields rather than a group: editors read a section\n * heading as the first thing in the block, and burying it one collapse deep\n * puts the least-optional copy behind a click. The field names match the\n * `@bison-lab/ui` prop names so the renderers stay a pass-through.\n */\nexport function headingFields({\n required = true,\n eyebrowDescription = \"Small label above the heading. Leave empty to hide the row.\",\n}: HeadingFieldsOptions = {}): Field[] {\n return [\n {\n name: \"eyebrow\",\n type: \"text\",\n admin: { description: eyebrowDescription },\n },\n { name: \"title\", type: \"text\", required },\n { name: \"description\", type: \"textarea\" },\n ];\n}\n","import type { Block } from \"payload\";\n\nimport { headingFields } from \"../../fields/heading\";\n\n/** A sticky intro beside a column of questions. */\nexport const FaqColumnsBlock: Block = {\n slug: \"faqColumns\",\n interfaceName: \"FaqColumnsBlock\",\n labels: { singular: \"FAQ Columns\", plural: \"FAQ Columns\" },\n fields: [\n ...headingFields(),\n {\n name: \"items\",\n type: \"array\",\n required: true,\n minRows: 1,\n labels: { singular: \"Question\", plural: \"Questions\" },\n fields: [\n { name: \"question\", type: \"text\", required: true },\n {\n name: \"answer\",\n // Plain text on purpose. A Lexical answer would pull\n // `@payloadcms/richtext-lexical` into the `./react` entry, which\n // every consumer loads; the `richText` block is where prose with\n // links and lists belongs.\n type: \"textarea\",\n required: true,\n },\n ],\n },\n {\n name: \"cta\",\n type: \"group\",\n label: \"Call to action\",\n admin: {\n description: \"Shown under the intro. Leave the link empty to hide it.\",\n },\n fields: [\n {\n name: \"title\",\n type: \"text\",\n admin: { description: 'Lead-in line, e.g. \"Still have questions?\".' },\n },\n { name: \"linkText\", type: \"text\" },\n { name: \"href\", type: \"text\" },\n {\n name: \"newTab\",\n type: \"checkbox\",\n label: \"Open in a new tab\",\n defaultValue: false,\n },\n ],\n },\n {\n name: \"sticky\",\n type: \"checkbox\",\n defaultValue: true,\n admin: { description: \"Pin the intro column while the answers scroll.\" },\n },\n {\n name: \"type\",\n type: \"select\",\n defaultValue: \"single\",\n options: [\n { label: \"One answer open at a time\", value: \"single\" },\n { label: \"Several answers open at once\", value: \"multiple\" },\n ],\n },\n {\n name: \"collapsible\",\n type: \"checkbox\",\n defaultValue: true,\n admin: {\n description: \"Allow the open answer to be closed again. Single mode only.\",\n },\n },\n ],\n};\n","import type { Block } from \"payload\";\n\nimport { headingFields } from \"../../fields/heading\";\nimport { imageField } from \"../../fields/image\";\nimport { linkField } from \"../../fields/link\";\n\n/** Quotes in a masonry grid, clipped behind a fade once there are enough. */\nexport const TestimonialMasonryBlock: Block = {\n slug: \"testimonialMasonry\",\n interfaceName: \"TestimonialMasonryBlock\",\n labels: { singular: \"Testimonial Masonry\", plural: \"Testimonial Masonry\" },\n fields: [\n ...headingFields(),\n {\n name: \"items\",\n type: \"array\",\n required: true,\n minRows: 1,\n labels: { singular: \"Testimonial\", plural: \"Testimonials\" },\n fields: [\n { name: \"content\", type: \"textarea\", required: true, label: \"Quote\" },\n {\n name: \"author\",\n type: \"group\",\n fields: [\n { name: \"name\", type: \"text\", required: true },\n {\n name: \"title\",\n type: \"text\",\n admin: {\n description: 'Role or organisation, e.g. \"Orthopaedic surgeon\".',\n },\n },\n imageField({\n name: \"avatar\",\n admin: {\n description:\n \"Optional. Initials from the name are used when empty.\",\n },\n }),\n ],\n },\n ],\n },\n linkField({\n label: \"Link\",\n admin: {\n description:\n \"Shown under the fade, once there are enough quotes to clip.\",\n },\n }),\n {\n name: \"minItemsForFade\",\n type: \"number\",\n defaultValue: 7,\n min: 1,\n admin: {\n description:\n \"How many quotes before the grid clips and the bottom fade appears.\",\n },\n },\n {\n name: \"maxVisibleRows\",\n type: \"number\",\n min: 1,\n admin: { description: \"Rows shown before the fade. Optional.\" },\n },\n ],\n};\n","import type { Block } from \"payload\";\n\n/**\n * Name, address, phone — the block local SEO is graded on.\n *\n * The `e164` phone number is a separate field from the displayed one because\n * `NapBlock` uses it, and only it, for the `tel:` href and the JSON-LD\n * `telephone`. A prettified string can never silently become an unreachable\n * link.\n */\nexport const NapBlock: Block = {\n slug: \"nap\",\n interfaceName: \"NapBlock\",\n labels: { singular: \"Name, Address, Phone\", plural: \"Name, Address, Phone\" },\n fields: [\n { name: \"businessName\", type: \"text\", required: true, label: \"Business name\" },\n {\n name: \"businessType\",\n type: \"text\",\n required: true,\n defaultValue: \"LocalBusiness\",\n admin: {\n description:\n 'schema.org type, e.g. \"MedicalBusiness\", \"Chiropractor\", \"LocalBusiness\".',\n },\n },\n {\n name: \"address\",\n type: \"group\",\n fields: [\n { name: \"streetAddress\", type: \"text\", required: true },\n { name: \"addressLocality\", type: \"text\", required: true, label: \"City\" },\n {\n name: \"addressRegion\",\n type: \"text\",\n required: true,\n label: \"State or region\",\n },\n { name: \"postalCode\", type: \"text\", required: true },\n {\n name: \"addressCountry\",\n type: \"text\",\n defaultValue: \"US\",\n admin: { description: \"ISO 3166-1 alpha-2, e.g. US.\" },\n },\n ],\n },\n {\n name: \"departments\",\n type: \"array\",\n required: true,\n minRows: 1,\n labels: { singular: \"Department\", plural: \"Departments\" },\n admin: {\n description:\n \"A single-location business has one entry, usually named after the business.\",\n },\n fields: [\n { name: \"name\", type: \"text\", required: true },\n {\n name: \"departmentType\",\n type: \"text\",\n admin: {\n description:\n \"schema.org type for this department. Falls back to the business type.\",\n },\n },\n {\n name: \"phoneE164\",\n type: \"text\",\n required: true,\n label: \"Phone (E.164)\",\n admin: {\n description:\n 'Dialable form, e.g. \"+15551234567\". This is what the tel: link and the structured data use.',\n },\n },\n {\n name: \"phoneDisplay\",\n type: \"text\",\n label: \"Phone (as displayed)\",\n admin: {\n description:\n \"Optional. US numbers are formatted automatically when this is empty.\",\n },\n },\n ],\n },\n {\n name: \"url\",\n type: \"text\",\n admin: { description: \"Canonical URL for the business. Optional.\" },\n },\n {\n name: \"showName\",\n type: \"checkbox\",\n defaultValue: true,\n admin: {\n description:\n \"Turn off when a heading above the block already names the business.\",\n },\n },\n {\n name: \"headingLevel\",\n type: \"select\",\n defaultValue: \"h2\",\n options: [\n { label: \"H2\", value: \"h2\" },\n { label: \"H3\", value: \"h3\" },\n { label: \"H4\", value: \"h4\" },\n ],\n admin: { description: \"Keeps the page's heading order intact.\" },\n },\n {\n name: \"emitJsonLd\",\n type: \"checkbox\",\n defaultValue: true,\n label: \"Emit structured data\",\n admin: {\n description:\n \"Adds a schema.org LocalBusiness script. Turn off if the page emits its own.\",\n },\n },\n ],\n};\n","/**\n * The upload side of the boundary.\n *\n * A Payload upload field holds either the row's id or, once depth resolves it,\n * the whole document. Neither shape can be imported from here: `Media` is\n * generated per site, and this package must never reach for a site's\n * `payload-types`. So the doc is described structurally, and every renderer\n * goes through `resolveMedia` rather than reading `.url` itself.\n */\n\n/**\n * The subset of a Payload upload document a block renderer actually reads.\n * Every site's generated `Media` interface is assignable to this, whatever\n * else it carries.\n *\n * Deliberately no index signature: TypeScript refuses to assign an interface\n * to a type with one, and a site's generated `Media` is always an interface.\n * An index signature here would break exactly the structural match this type\n * exists to provide.\n */\nexport interface MediaDoc {\n url?: string | null;\n alt?: string | null;\n width?: number | null;\n height?: number | null;\n}\n\n/** What an upload field holds before and after `depth` populates it. */\nexport type MediaValue = number | string | MediaDoc | null | undefined;\n\n/** A resolved image, in the shape every `@bison-lab/ui` block asks for. */\nexport interface ResolvedMedia {\n src: string;\n alt: string;\n width?: number;\n height?: number;\n}\n\nfunction isMediaDoc(value: MediaValue): value is MediaDoc {\n return typeof value === \"object\" && value !== null;\n}\n\n/**\n * Narrows an upload value to something renderable, or `undefined`.\n *\n * `undefined` covers three cases a renderer must treat identically: the field\n * is empty, `depth: 0` left it as a bare id, or the document exists but has no\n * `url` yet (an upload mid-flight). A renderer that gets `undefined` omits the\n * image rather than emitting a broken `<img>`.\n *\n * `alt` is always a string: the media collection requires it, but a draft saved\n * before that validation ran can still reach a renderer, and an `alt` of\n * `undefined` would silently drop the attribute entirely.\n */\nexport function resolveMedia(value: MediaValue): ResolvedMedia | undefined {\n if (!isMediaDoc(value)) return undefined;\n const { url, alt, width, height } = value;\n if (typeof url !== \"string\" || url.length === 0) return undefined;\n return {\n src: url,\n alt: typeof alt === \"string\" ? alt : \"\",\n ...(typeof width === \"number\" ? { width } : {}),\n ...(typeof height === \"number\" ? { height } : {}),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/fields/image.ts","../src/fields/link.ts","../src/blocks/hero/config.ts","../src/blocks/rich-text/config.ts","../src/fields/min-rows.ts","../src/blocks/showcase-panels/config.ts","../src/blocks/process-steps/config.ts","../src/fields/heading.ts","../src/blocks/faq-columns/config.ts","../src/blocks/testimonial-masonry/config.ts","../src/blocks/nap/config.ts","../src/media.ts"],"sourcesContent":["import type { CollectionSlug, UploadField } from \"payload\";\n\n/**\n * Payload's `UploadField` is a union over the polymorphic (`relationTo: [...]`)\n * and `hasMany` variants. A block image is neither, and narrowing here is what\n * lets `imageField({ name: 'portrait' })` stay type-checked at the call site.\n */\ntype MediaUploadField = Extract<\n UploadField,\n { hasMany?: false; relationTo: CollectionSlug }\n>;\n\nexport interface ImageFieldOptions extends Partial<MediaUploadField> {\n /**\n * The upload collection to point at. Every Bison Lab site names it `media`,\n * but a site that does not can say so without forking the field.\n */\n relationTo?: CollectionSlug;\n}\n\n/**\n * A single image from the site's upload collection, for any block with a\n * picture in it.\n *\n * Optional unless the block says otherwise: `imageField({ name: 'portrait',\n * required: true })`. `admin` is replaced, not merged, so a block that passes\n * its own description writes the whole thing.\n */\nexport function imageField(overrides: ImageFieldOptions = {}): MediaUploadField {\n return {\n name: \"image\",\n type: \"upload\",\n relationTo: \"media\" as CollectionSlug,\n // Payload stars a required field and says nothing about an optional one,\n // so an optional image says so itself, and only then.\n ...(overrides.required ? {} : { admin: { description: \"Optional.\" } }),\n ...overrides,\n } as MediaUploadField;\n}\n","import type { Field, GroupField } from \"payload\";\n\nexport interface LinkFieldsOptions {\n /** Require the label and destination. Defaults to `false`. */\n required?: boolean;\n /** Wording for the visible text field. Defaults to \"Label\". */\n labelFieldLabel?: string;\n}\n\n/**\n * The three fields a call to action is made of, unwrapped.\n *\n * `href` is plain text rather than a relationship to a `pages` row: a block in\n * this package has to work on a site whose page collection is named something\n * else, or which links out more often than in. Resolving an internal reference\n * is the site's job.\n *\n * `newTab` drives `target=\"_blank\"` *and* the matching `rel`, which is why no\n * renderer takes one without the other.\n */\nexport function linkFields({\n required = false,\n labelFieldLabel = \"Label\",\n}: LinkFieldsOptions = {}): Field[] {\n return [\n { name: \"label\", type: \"text\", label: labelFieldLabel, required },\n {\n name: \"href\",\n type: \"text\",\n required,\n admin: {\n description:\n 'A site path (\"/contact\") or a full URL (\"https://example.com\").',\n },\n },\n {\n name: \"newTab\",\n type: \"checkbox\",\n label: \"Open in a new tab\",\n defaultValue: false,\n },\n ];\n}\n\nexport interface LinkFieldOptions extends LinkFieldsOptions {\n /** Field name. Defaults to `link`. */\n name?: string;\n label?: GroupField[\"label\"];\n admin?: GroupField[\"admin\"];\n}\n\n/** `linkFields()` as one named group, for a block with a single CTA. */\nexport function linkField({\n name = \"link\",\n label,\n admin,\n ...rest\n}: LinkFieldOptions = {}): GroupField {\n return {\n name,\n type: \"group\",\n fields: linkFields(rest),\n ...(label === undefined ? {} : { label }),\n ...(admin === undefined ? {} : { admin }),\n };\n}\n\n/** The shape `linkField`/`linkFields` produce once Payload generates types. */\nexport interface LinkValue {\n label?: string | null;\n href?: string | null;\n newTab?: boolean | null;\n}\n","import type { Block } from \"payload\";\n\nimport { imageField } from \"../../fields/image\";\nimport { linkFields } from \"../../fields/link\";\n\n/**\n * The band a page opens with.\n *\n * Unlike every other block here this one has no `@bison-lab/ui` counterpart:\n * a hero is where a site's brand is loudest, and the shipped renderer is\n * deliberately plain markup so a site can swap in its own by overriding this\n * one registry entry. The *fields* are the shared part — that is what makes a\n * page portable between sites.\n */\nexport const HeroBlock: Block = {\n slug: \"hero\",\n interfaceName: \"HeroBlock\",\n labels: { singular: \"Hero\", plural: \"Heroes\" },\n fields: [\n {\n name: \"eyebrow\",\n type: \"text\",\n admin: { description: \"Small label above the heading. Optional.\" },\n },\n { name: \"heading\", type: \"text\", required: true },\n { name: \"body\", type: \"textarea\" },\n {\n name: \"tone\",\n type: \"select\",\n defaultValue: \"sweep\",\n options: [\n { label: \"Sweep (brand gradient)\", value: \"sweep\" },\n { label: \"Dark (flat brand band)\", value: \"dark\" },\n // Describes the surface, not a lightness: in the dark theme the page\n // surface is dark. The value stays `light` so no site migrates.\n { label: \"Plain (page surface)\", value: \"light\" },\n ],\n admin: {\n description:\n \"How the band is painted. Sites map these to their own surfaces.\",\n },\n },\n imageField({ admin: { description: \"Optional background or lead image.\" } }),\n {\n name: \"links\",\n type: \"array\",\n // Two is the point at which a hero stops having a primary action.\n maxRows: 2,\n labels: { singular: \"Call to action\", plural: \"Calls to action\" },\n fields: linkFields({ required: true }),\n },\n ],\n};\n","import type { Block } from \"payload\";\n\n/** A prose section: one Lexical document at reading measure. */\nexport const RichTextBlock: Block = {\n slug: \"richText\",\n interfaceName: \"RichTextBlock\",\n labels: { singular: \"Rich Text\", plural: \"Rich Text\" },\n fields: [{ name: \"content\", type: \"richText\", required: true }],\n};\n","/**\n * The import-map path of the array field that refuses to go below `minRows`.\n *\n * Payload gates *Add* on `maxRows` but never gates *Remove* on `minRows`: an\n * editor can delete the third showcase panel, see a \"requires 3 panels\"\n * note, and only learn on publish that the section is invalid. Payload has no\n * option for this, so it is an admin component (`@bison-lab/payload-blocks/admin`)\n * referenced here by package specifier. Every array in this package with a\n * `minRows` uses it, and a site can put it on its own arrays:\n *\n * ```ts\n * { name: 'items', type: 'array', minRows: 2,\n * admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } }, fields: [...] }\n * ```\n *\n * Consuming sites pick it up by re-running `payload generate:importmap`. Until\n * they do, Payload logs the missing entry and falls back to its stock field.\n */\nexport const MIN_ROWS_ARRAY_FIELD =\n \"@bison-lab/payload-blocks/admin#MinRowsArrayField\";\n\n/** Empty rows for an array's `defaultValue`, so a block opens at its minimum. */\nexport function emptyRows(count: number): Record<string, never>[] {\n return Array.from({ length: count }, () => ({}));\n}\n","import type { Block } from \"payload\";\n\nimport { imageField } from \"../../fields/image\";\nimport { emptyRows, MIN_ROWS_ARRAY_FIELD } from \"../../fields/min-rows\";\n\n/** Expanding panels, one per service or product line. */\nexport const ShowcasePanelsBlock: Block = {\n slug: \"showcasePanels\",\n interfaceName: \"ShowcasePanelsBlock\",\n labels: { singular: \"Showcase Panels\", plural: \"Showcase Panels\" },\n fields: [\n {\n name: \"items\",\n type: \"array\",\n required: true,\n // The layout is designed around 3-6 panels: fewer and the accordion\n // reads as a mistake, more and each collapsed spine is unreadable. The\n // block opens with the minimum already in place, and the admin field\n // refuses to go below it.\n minRows: 3,\n maxRows: 6,\n defaultValue: emptyRows(3),\n labels: { singular: \"Panel\", plural: \"Panels\" },\n admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } },\n fields: [\n { name: \"title\", type: \"text\", required: true },\n {\n name: \"summary\",\n type: \"textarea\",\n required: true,\n admin: { description: \"Revealed when the panel expands.\" },\n },\n imageField({ required: true }),\n {\n name: \"href\",\n type: \"text\",\n admin: {\n description: 'Optional \"read more\" destination for this panel.',\n },\n },\n {\n name: \"numeral\",\n type: \"text\",\n admin: {\n description:\n 'Overrides the spine numeral (\"01\", \"II\"). Leave empty to number automatically.',\n },\n },\n ],\n },\n {\n name: \"spineVariant\",\n type: \"select\",\n defaultValue: \"numbered\",\n options: [\n { label: \"Numbered (01, 02)\", value: \"numbered\" },\n { label: \"Volume (I, II)\", value: \"volume\" },\n { label: \"Minimal (no numeral)\", value: \"minimal\" },\n ],\n },\n {\n name: \"watermark\",\n type: \"text\",\n admin: { description: 'Faint mark behind the panels, e.g. a brand word.' },\n },\n {\n name: \"defaultActiveIndex\",\n type: \"number\",\n defaultValue: 0,\n min: 0,\n admin: { description: \"Which panel is open on load, counting from 0.\" },\n },\n // The region's accessible name and the selected/preview state text are\n // not editor concerns; the `@bison-lab/ui` defaults stand.\n ],\n};\n","import type { Block } from \"payload\";\n\nimport { imageField } from \"../../fields/image\";\nimport { emptyRows, MIN_ROWS_ARRAY_FIELD } from \"../../fields/min-rows\";\n\n/** An ordered walkthrough that advances on its own. */\nexport const ProcessStepsBlock: Block = {\n slug: \"processSteps\",\n interfaceName: \"ProcessStepsBlock\",\n labels: { singular: \"Process Steps\", plural: \"Process Steps\" },\n fields: [\n {\n name: \"items\",\n type: \"array\",\n required: true,\n // Same reasoning as the showcase panels: the step rail is designed\n // around 3-6 entries, in order. The block opens with the minimum\n // already in place, and the admin field refuses to go below it.\n minRows: 3,\n maxRows: 6,\n defaultValue: emptyRows(3),\n labels: { singular: \"Step\", plural: \"Steps\" },\n admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } },\n // Steps are numbered by position; ordering is drag and drop. There is\n // deliberately no per-step number override.\n fields: [\n { name: \"title\", type: \"text\", required: true },\n { name: \"description\", type: \"textarea\", required: true },\n imageField({ required: true }),\n ],\n },\n {\n name: \"autoAdvance\",\n type: \"checkbox\",\n defaultValue: true,\n admin: {\n description:\n \"Advance on a timer. Always off for visitors who prefer reduced motion.\",\n },\n },\n {\n name: \"autoAdvanceDuration\",\n type: \"number\",\n defaultValue: 6000,\n min: 1000,\n admin: { description: \"Milliseconds each step holds before advancing.\" },\n },\n { name: \"pauseOnHover\", type: \"checkbox\", defaultValue: true },\n {\n name: \"defaultActiveIndex\",\n type: \"number\",\n defaultValue: 0,\n min: 0,\n admin: { description: \"Which step is active on load, counting from 0.\" },\n },\n // The region's accessible name and the play/pause control text are not\n // editor concerns; the `@bison-lab/ui` defaults stand.\n ],\n};\n","import type { Field } from \"payload\";\n\nexport interface HeadingFieldsOptions {\n /** Require the section heading. Defaults to `true`. */\n required?: boolean;\n /** Description shown under the eyebrow input. */\n eyebrowDescription?: string;\n}\n\n/**\n * The eyebrow / title / description trio every marketing band opens with.\n *\n * They are three top-level fields rather than a group: editors read a section\n * heading as the first thing in the block, and burying it one collapse deep\n * puts the least-optional copy behind a click. The field names match the\n * `@bison-lab/ui` prop names so the renderers stay a pass-through.\n */\nexport function headingFields({\n required = true,\n eyebrowDescription = \"Small label above the heading. Leave empty to hide the row.\",\n}: HeadingFieldsOptions = {}): Field[] {\n return [\n {\n name: \"eyebrow\",\n type: \"text\",\n admin: { description: eyebrowDescription },\n },\n { name: \"title\", type: \"text\", required },\n { name: \"description\", type: \"textarea\" },\n ];\n}\n","import type { Block } from \"payload\";\n\nimport { headingFields } from \"../../fields/heading\";\nimport { emptyRows, MIN_ROWS_ARRAY_FIELD } from \"../../fields/min-rows\";\n\n/** A sticky intro beside a column of questions. */\nexport const FaqColumnsBlock: Block = {\n slug: \"faqColumns\",\n interfaceName: \"FaqColumnsBlock\",\n labels: { singular: \"FAQ Columns\", plural: \"FAQ Columns\" },\n fields: [\n ...headingFields(),\n {\n name: \"items\",\n type: \"array\",\n required: true,\n minRows: 1,\n defaultValue: emptyRows(1),\n labels: { singular: \"Question\", plural: \"Questions\" },\n admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } },\n fields: [\n { name: \"question\", type: \"text\", required: true },\n {\n name: \"answer\",\n // Plain text on purpose. A Lexical answer would pull\n // `@payloadcms/richtext-lexical` into the `./react` entry, which\n // every consumer loads; the `richText` block is where prose with\n // links and lists belongs.\n type: \"textarea\",\n required: true,\n },\n ],\n },\n {\n name: \"cta\",\n type: \"group\",\n label: \"Call to action\",\n admin: {\n description: \"Shown under the intro. Leave the link empty to hide it.\",\n },\n fields: [\n {\n name: \"title\",\n type: \"text\",\n admin: { description: 'Lead-in line, e.g. \"Still have questions?\".' },\n },\n { name: \"linkText\", type: \"text\" },\n { name: \"href\", type: \"text\" },\n {\n name: \"newTab\",\n type: \"checkbox\",\n label: \"Open in a new tab\",\n defaultValue: false,\n },\n ],\n },\n {\n name: \"sticky\",\n type: \"checkbox\",\n defaultValue: true,\n admin: { description: \"Pin the intro column while the answers scroll.\" },\n },\n {\n name: \"type\",\n type: \"select\",\n defaultValue: \"single\",\n options: [\n { label: \"One answer open at a time\", value: \"single\" },\n { label: \"Several answers open at once\", value: \"multiple\" },\n ],\n },\n {\n name: \"collapsible\",\n type: \"checkbox\",\n defaultValue: true,\n admin: {\n description: \"Allow the open answer to be closed again. Single mode only.\",\n },\n },\n ],\n};\n","import type { Block } from \"payload\";\n\nimport { headingFields } from \"../../fields/heading\";\nimport { imageField } from \"../../fields/image\";\nimport { linkField } from \"../../fields/link\";\nimport { emptyRows, MIN_ROWS_ARRAY_FIELD } from \"../../fields/min-rows\";\n\n/** Quotes in a masonry grid, clipped behind a fade once there are enough. */\nexport const TestimonialMasonryBlock: Block = {\n slug: \"testimonialMasonry\",\n interfaceName: \"TestimonialMasonryBlock\",\n labels: { singular: \"Testimonial Masonry\", plural: \"Testimonial Masonry\" },\n fields: [\n ...headingFields(),\n {\n name: \"items\",\n type: \"array\",\n required: true,\n minRows: 1,\n defaultValue: emptyRows(1),\n labels: { singular: \"Testimonial\", plural: \"Testimonials\" },\n admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } },\n fields: [\n { name: \"content\", type: \"textarea\", required: true, label: \"Quote\" },\n {\n name: \"author\",\n type: \"group\",\n fields: [\n { name: \"name\", type: \"text\", required: true },\n {\n name: \"title\",\n type: \"text\",\n admin: {\n description: 'Role or organisation, e.g. \"Orthopaedic surgeon\".',\n },\n },\n imageField({\n name: \"avatar\",\n admin: {\n description:\n \"Optional. Initials from the name are used when empty.\",\n },\n }),\n ],\n },\n ],\n },\n linkField({\n label: \"Link\",\n admin: {\n description:\n \"Shown under the fade, once there are enough quotes to clip.\",\n },\n }),\n {\n name: \"minItemsForFade\",\n type: \"number\",\n defaultValue: 7,\n min: 1,\n admin: {\n description:\n \"How many quotes before the grid clips and the bottom fade appears.\",\n },\n },\n {\n name: \"maxVisibleRows\",\n type: \"number\",\n min: 1,\n admin: { description: \"Rows shown before the fade. Optional.\" },\n },\n ],\n};\n","import type { Block } from \"payload\";\n\nimport { emptyRows, MIN_ROWS_ARRAY_FIELD } from \"../../fields/min-rows\";\n\n/**\n * Name, address, phone — the block local SEO is graded on.\n *\n * The `e164` phone number is a separate field from the displayed one because\n * `NapBlock` uses it, and only it, for the `tel:` href and the JSON-LD\n * `telephone`. A prettified string can never silently become an unreachable\n * link.\n */\nexport const NapBlock: Block = {\n slug: \"nap\",\n interfaceName: \"NapBlock\",\n labels: { singular: \"Name, Address, Phone\", plural: \"Name, Address, Phone\" },\n fields: [\n { name: \"businessName\", type: \"text\", required: true, label: \"Business name\" },\n {\n name: \"businessType\",\n type: \"text\",\n required: true,\n defaultValue: \"LocalBusiness\",\n admin: {\n description:\n 'schema.org type, e.g. \"MedicalBusiness\", \"Chiropractor\", \"LocalBusiness\".',\n },\n },\n {\n name: \"address\",\n type: \"group\",\n fields: [\n { name: \"streetAddress\", type: \"text\", required: true },\n { name: \"addressLocality\", type: \"text\", required: true, label: \"City\" },\n {\n name: \"addressRegion\",\n type: \"text\",\n required: true,\n label: \"State or region\",\n },\n { name: \"postalCode\", type: \"text\", required: true },\n {\n name: \"addressCountry\",\n type: \"text\",\n defaultValue: \"US\",\n admin: { description: \"ISO 3166-1 alpha-2, e.g. US.\" },\n },\n ],\n },\n {\n name: \"departments\",\n type: \"array\",\n required: true,\n minRows: 1,\n defaultValue: emptyRows(1),\n labels: { singular: \"Department\", plural: \"Departments\" },\n admin: {\n components: { Field: MIN_ROWS_ARRAY_FIELD },\n description:\n \"A single-location business has one entry, usually named after the business.\",\n },\n fields: [\n { name: \"name\", type: \"text\", required: true },\n {\n name: \"departmentType\",\n type: \"text\",\n admin: {\n description:\n \"schema.org type for this department. Falls back to the business type.\",\n },\n },\n {\n name: \"phoneE164\",\n type: \"text\",\n required: true,\n label: \"Phone (E.164)\",\n admin: {\n description:\n 'Dialable form, e.g. \"+15551234567\". This is what the tel: link and the structured data use.',\n },\n },\n {\n name: \"phoneDisplay\",\n type: \"text\",\n label: \"Phone (as displayed)\",\n admin: {\n description:\n \"Optional. US numbers are formatted automatically when this is empty.\",\n },\n },\n ],\n },\n {\n name: \"url\",\n type: \"text\",\n admin: { description: \"Canonical URL for the business. Optional.\" },\n },\n {\n name: \"showName\",\n type: \"checkbox\",\n defaultValue: true,\n admin: {\n description:\n \"Turn off when a heading above the block already names the business.\",\n },\n },\n {\n name: \"headingLevel\",\n type: \"select\",\n defaultValue: \"h2\",\n options: [\n { label: \"H2\", value: \"h2\" },\n { label: \"H3\", value: \"h3\" },\n { label: \"H4\", value: \"h4\" },\n ],\n admin: { description: \"Keeps the page's heading order intact.\" },\n },\n {\n name: \"emitJsonLd\",\n type: \"checkbox\",\n defaultValue: true,\n label: \"Emit structured data\",\n admin: {\n description:\n \"Adds a schema.org LocalBusiness script. Turn off if the page emits its own.\",\n },\n },\n ],\n};\n","/**\n * The upload side of the boundary.\n *\n * A Payload upload field holds either the row's id or, once depth resolves it,\n * the whole document. Neither shape can be imported from here: `Media` is\n * generated per site, and this package must never reach for a site's\n * `payload-types`. So the doc is described structurally, and every renderer\n * goes through `resolveMedia` rather than reading `.url` itself.\n */\n\n/**\n * The subset of a Payload upload document a block renderer actually reads.\n * Every site's generated `Media` interface is assignable to this, whatever\n * else it carries.\n *\n * Deliberately no index signature: TypeScript refuses to assign an interface\n * to a type with one, and a site's generated `Media` is always an interface.\n * An index signature here would break exactly the structural match this type\n * exists to provide.\n */\nexport interface MediaDoc {\n url?: string | null;\n alt?: string | null;\n width?: number | null;\n height?: number | null;\n}\n\n/** What an upload field holds before and after `depth` populates it. */\nexport type MediaValue = number | string | MediaDoc | null | undefined;\n\n/** A resolved image, in the shape every `@bison-lab/ui` block asks for. */\nexport interface ResolvedMedia {\n src: string;\n alt: string;\n width?: number;\n height?: number;\n}\n\nfunction isMediaDoc(value: MediaValue): value is MediaDoc {\n return typeof value === \"object\" && value !== null;\n}\n\n/**\n * Narrows an upload value to something renderable, or `undefined`.\n *\n * `undefined` covers three cases a renderer must treat identically: the field\n * is empty, `depth: 0` left it as a bare id, or the document exists but has no\n * `url` yet (an upload mid-flight). A renderer that gets `undefined` omits the\n * image rather than emitting a broken `<img>`.\n *\n * `alt` is always a string: the media collection requires it, but a draft saved\n * before that validation ran can still reach a renderer, and an `alt` of\n * `undefined` would silently drop the attribute entirely.\n */\nexport function resolveMedia(value: MediaValue): ResolvedMedia | undefined {\n if (!isMediaDoc(value)) return undefined;\n const { url, alt, width, height } = value;\n if (typeof url !== \"string\" || url.length === 0) return undefined;\n return {\n src: url,\n alt: typeof alt === \"string\" ? alt : \"\",\n ...(typeof width === \"number\" ? { width } : {}),\n ...(typeof height === \"number\" ? { height } : {}),\n };\n}\n"],"mappings":"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"}
package/dist/react.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- import { c as BlockLike, d as DEFAULT_CONTAINER, f as RenderBlocks, g as DefaultBlockImage, h as BlockImageProps, i as ProcessStepsBlockData, l as BlockRegistryFor, m as BlockImageComponent, n as HeroBlockData, o as ShowcasePanelsBlockData, p as RenderBlocksProps, r as NapBlockData, s as TestimonialMasonryBlockData, t as FaqColumnsBlockData, u as BlockRendererProps } from "./types-BvRvDCDT.mjs";
2
+ import { c as BlockLike, d as DEFAULT_CONTAINER, f as RenderBlocks, g as DefaultBlockImage, h as BlockImageProps, i as ProcessStepsBlockData, l as BlockRegistryFor, m as BlockImageComponent, n as HeroBlockData, o as ShowcasePanelsBlockData, p as RenderBlocksProps, r as NapBlockData, s as TestimonialMasonryBlockData, t as FaqColumnsBlockData, u as BlockRendererProps } from "./types-BBumyFd-.mjs";
3
3
  import * as react_jsx_runtime0 from "react/jsx-runtime";
4
4
 
5
5
  //#region src/blocks/hero/component.d.ts
package/dist/react.mjs CHANGED
@@ -192,11 +192,6 @@ function ShowcasePanelsBlockRenderer({ block, containerClassName, imageComponent
192
192
  spineVariant: block.spineVariant ?? "numbered",
193
193
  defaultActiveIndex: Math.min(Math.max(block.defaultActiveIndex ?? 0, 0), items.length - 1),
194
194
  ...block.watermark ? { watermark: block.watermark } : {},
195
- ...block.ariaLabel ? { ariaLabel: block.ariaLabel } : {},
196
- ...block.labels?.selected || block.labels?.preview ? { labels: {
197
- ...block.labels.selected ? { selected: block.labels.selected } : {},
198
- ...block.labels.preview ? { preview: block.labels.preview } : {}
199
- } } : {},
200
195
  renderImage: (item, i) => /* @__PURE__ */ jsx(Image, {
201
196
  src: item.image.src,
202
197
  alt: item.image.alt ?? "",
@@ -224,8 +219,7 @@ function ProcessStepsBlockRenderer({ block, containerClassName, imageComponent:
224
219
  image: {
225
220
  src: image.src,
226
221
  alt: image.alt
227
- },
228
- ...typeof row.step === "number" ? { step: row.step } : {}
222
+ }
229
223
  });
230
224
  }
231
225
  if (items.length === 0) return null;
@@ -237,11 +231,6 @@ function ProcessStepsBlockRenderer({ block, containerClassName, imageComponent:
237
231
  autoAdvanceDuration: block.autoAdvanceDuration ?? 6e3,
238
232
  pauseOnHover: block.pauseOnHover ?? true,
239
233
  defaultActiveIndex: Math.min(Math.max(block.defaultActiveIndex ?? 0, 0), items.length - 1),
240
- ...block.ariaLabel ? { ariaLabel: block.ariaLabel } : {},
241
- ...block.labels?.pause || block.labels?.resume ? { labels: {
242
- ...block.labels.pause ? { pause: block.labels.pause } : {},
243
- ...block.labels.resume ? { resume: block.labels.resume } : {}
244
- } } : {},
245
234
  renderImage: (item, i) => /* @__PURE__ */ jsx(Image, {
246
235
  src: item.image.src,
247
236
  alt: item.image.alt ?? "",
@@ -1 +1 @@
1
- {"version":3,"file":"react.mjs","names":[],"sources":["../src/image.tsx","../src/render-blocks.tsx","../src/media.ts","../src/blocks/hero/component.tsx","../src/blocks/showcase-panels/component.tsx","../src/blocks/process-steps/component.tsx","../src/blocks/faq-columns/component.tsx","../src/blocks/testimonial-masonry/component.tsx","../src/blocks/nap/component.tsx"],"sourcesContent":["import type { ComponentType } from \"react\";\n\n/**\n * The image injection point.\n *\n * The package deliberately does not depend on `next` — a Payload site is not\n * necessarily a Next.js site, and `next/image` needs per-site configuration\n * (`remotePatterns` for the media route) that a package cannot supply. So\n * renderers never construct an image element themselves; they render the\n * component they were handed. A Next site writes one adapter around\n * `next/image` and every block gets optimisation from it.\n *\n * This is a *component type*, not a render function, on purpose: a Server\n * Component may pass a client component reference across the boundary, but not\n * a closure. `imageComponent` therefore survives the trip from a server page\n * into these client renderers, where a `renderImage` callback would not.\n */\n\nexport interface BlockImageProps {\n src: string;\n /** Always a string — see `resolveMedia`. Empty means decorative. */\n alt: string;\n width?: number;\n height?: number;\n className?: string;\n /** Passed through to `next/image`'s `sizes`; ignored by the default. */\n sizes?: string;\n /** Set on the one image above the fold, typically the hero's. */\n priority?: boolean;\n}\n\nexport type BlockImageComponent = ComponentType<BlockImageProps>;\n\n/**\n * The fallback when no `imageComponent` is supplied: a plain `<img>`. Correct\n * everywhere and optimised nowhere, which is the right default for a package\n * that cannot know what framework it landed in.\n */\nexport function DefaultBlockImage({\n src,\n alt,\n width,\n height,\n className,\n sizes,\n priority,\n}: BlockImageProps) {\n return (\n <img\n src={src}\n alt={alt}\n width={width}\n height={height}\n className={className}\n sizes={sizes}\n loading={priority ? \"eager\" : \"lazy\"}\n decoding=\"async\"\n />\n );\n}\n","import type { ComponentType, ReactElement } from \"react\";\n\nimport { type BlockImageComponent, DefaultBlockImage } from \"./image\";\n\n/** The minimum a row must be for the dispatch to place it. */\nexport interface BlockLike {\n blockType: string;\n id?: string | null;\n}\n\n/**\n * What every renderer is handed.\n *\n * The neighbour props let a block adapt to what sits around it: `prevType` to\n * close a seam against the block above, `runIndex` to alternate surfaces across\n * back-to-back blocks of one type, `isLast` because the final block sits\n * against the site footer.\n *\n * `containerClassName` and `imageComponent` are the two things a package block\n * cannot decide for itself — the site's page measure, and how an image gets\n * optimised. Both arrive already defaulted, so a renderer never checks them.\n */\nexport interface BlockRendererProps<B extends BlockLike = BlockLike> {\n block: B;\n /** Position on the page, hero included. */\n index: number;\n /** The type of the block before this one; absent on the first block. */\n prevType?: string;\n /** Position within a run of consecutive same-type blocks, from 0. */\n runIndex: number;\n isLast: boolean;\n /** The site's page measure, for the band wrapper. */\n containerClassName: string;\n /** How this block turns a resolved image into an element. */\n imageComponent: BlockImageComponent;\n}\n\n/**\n * One renderer per block type, each typed to its own block.\n *\n * Written as a mapped type over the union so `hero` gets `HeroBlockData`, not\n * the whole union. A site writes `satisfies BlockRegistryFor<AnyBlock>` where\n * `AnyBlock` comes off its *generated* `Page` type — that is the compile-time\n * layout lock, and it has to stay in the site, because only the site has\n * generated types. The registry is a parameter here for exactly that reason.\n */\nexport type BlockRegistryFor<B extends BlockLike> = {\n [K in B[\"blockType\"]]: ComponentType<\n BlockRendererProps<Extract<B, { blockType: K }>>\n >;\n};\n\n/** Fallback page measure for a site that does not pass its own. */\nexport const DEFAULT_CONTAINER = \"mx-auto w-full max-w-7xl px-6\";\n\ntype LooseRenderer = ComponentType<BlockRendererProps<BlockLike>>;\n\n/**\n * A row saved against a block that has since left the config has no renderer.\n * It is dropped before anything is counted, so its neighbours see each other,\n * not a gap: the block after it gets the real `prevType`, a run of one type is\n * not broken by it, and the block before it is still `isLast` when it ends the\n * page.\n */\nfunction renderable(\n blocks: BlockLike[],\n registry: Record<string, LooseRenderer | undefined>,\n): { block: BlockLike; Renderer: LooseRenderer }[] {\n return blocks.flatMap((block) => {\n const Renderer = registry[block.blockType];\n return Renderer ? [{ block, Renderer }] : [];\n });\n}\n\n/** Position of each block within its run of consecutive same-type blocks. */\nfunction runIndexes(blocks: BlockLike[]): number[] {\n const out: number[] = [];\n for (let i = 0; i < blocks.length; i += 1) {\n out.push(\n i > 0 && blocks[i - 1].blockType === blocks[i].blockType\n ? out[i - 1] + 1\n : 0,\n );\n }\n return out;\n}\n\nexport interface RenderBlocksProps<B extends BlockLike> {\n /** Pass `[...page.hero, ...page.layout]`, so the hero counts as index 0. */\n blocks: B[];\n registry: BlockRegistryFor<B>;\n /** The site's page measure. Defaults to `DEFAULT_CONTAINER`. */\n containerClassName?: string;\n /** Defaults to a plain `<img>`; a Next site passes a `next/image` adapter. */\n imageComponent?: BlockImageComponent;\n}\n\n/**\n * Renders a page's blocks in order through the registry. A Server Component:\n * it only maps and looks up, and each renderer decides its own nature.\n */\nexport function RenderBlocks<B extends BlockLike>({\n blocks,\n registry,\n containerClassName = DEFAULT_CONTAINER,\n imageComponent = DefaultBlockImage,\n}: RenderBlocksProps<B>): ReactElement {\n const rows = renderable(\n blocks,\n // The mapped registry type is per-block, and the dispatch is not; the\n // lookup below is what guarantees each renderer only ever sees its own\n // block type, and no signature can express that to the compiler.\n registry as unknown as Record<string, LooseRenderer | undefined>,\n );\n const runs = runIndexes(rows.map((row) => row.block));\n return (\n <>\n {rows.map(({ block, Renderer }, index) => (\n <Renderer\n key={block.id ?? index}\n block={block}\n index={index}\n prevType={index > 0 ? rows[index - 1].block.blockType : undefined}\n runIndex={runs[index]}\n isLast={index === rows.length - 1}\n containerClassName={containerClassName}\n imageComponent={imageComponent}\n />\n ))}\n </>\n );\n}\n","/**\n * The upload side of the boundary.\n *\n * A Payload upload field holds either the row's id or, once depth resolves it,\n * the whole document. Neither shape can be imported from here: `Media` is\n * generated per site, and this package must never reach for a site's\n * `payload-types`. So the doc is described structurally, and every renderer\n * goes through `resolveMedia` rather than reading `.url` itself.\n */\n\n/**\n * The subset of a Payload upload document a block renderer actually reads.\n * Every site's generated `Media` interface is assignable to this, whatever\n * else it carries.\n *\n * Deliberately no index signature: TypeScript refuses to assign an interface\n * to a type with one, and a site's generated `Media` is always an interface.\n * An index signature here would break exactly the structural match this type\n * exists to provide.\n */\nexport interface MediaDoc {\n url?: string | null;\n alt?: string | null;\n width?: number | null;\n height?: number | null;\n}\n\n/** What an upload field holds before and after `depth` populates it. */\nexport type MediaValue = number | string | MediaDoc | null | undefined;\n\n/** A resolved image, in the shape every `@bison-lab/ui` block asks for. */\nexport interface ResolvedMedia {\n src: string;\n alt: string;\n width?: number;\n height?: number;\n}\n\nfunction isMediaDoc(value: MediaValue): value is MediaDoc {\n return typeof value === \"object\" && value !== null;\n}\n\n/**\n * Narrows an upload value to something renderable, or `undefined`.\n *\n * `undefined` covers three cases a renderer must treat identically: the field\n * is empty, `depth: 0` left it as a bare id, or the document exists but has no\n * `url` yet (an upload mid-flight). A renderer that gets `undefined` omits the\n * image rather than emitting a broken `<img>`.\n *\n * `alt` is always a string: the media collection requires it, but a draft saved\n * before that validation ran can still reach a renderer, and an `alt` of\n * `undefined` would silently drop the attribute entirely.\n */\nexport function resolveMedia(value: MediaValue): ResolvedMedia | undefined {\n if (!isMediaDoc(value)) return undefined;\n const { url, alt, width, height } = value;\n if (typeof url !== \"string\" || url.length === 0) return undefined;\n return {\n src: url,\n alt: typeof alt === \"string\" ? alt : \"\",\n ...(typeof width === \"number\" ? { width } : {}),\n ...(typeof height === \"number\" ? { height } : {}),\n };\n}\n","import { Button } from \"@bison-lab/ui\";\n\nimport { cx } from \"../../cx\";\nimport { resolveMedia } from \"../../media\";\nimport type { BlockRendererProps } from \"../../render-blocks\";\nimport type { HeroBlockData } from \"../../types\";\n\n/**\n * How each tone paints. Semantic tokens only — a site's brand reaches these\n * through its theme, never through a class named here.\n */\nconst TONES = {\n sweep: \"bg-primary text-primary-foreground\",\n dark: \"bg-foreground text-background\",\n light: \"bg-background text-foreground\",\n} as const;\n\n/**\n * The package's hero: deliberately plain.\n *\n * A hero is where a site's brand is loudest, so this exists to make a page\n * render at all, not to be the final answer. A site overrides this one registry\n * entry with its own and keeps every other block.\n */\nexport function HeroBlockRenderer({\n block,\n index,\n containerClassName,\n imageComponent: Image,\n}: BlockRendererProps<HeroBlockData>) {\n const image = resolveMedia(block.image);\n const links = (block.links ?? []).filter((link) => link.href && link.label);\n const tone = TONES[block.tone ?? \"sweep\"];\n\n return (\n <section className={cx(\"relative isolate overflow-hidden\", tone)}>\n {image ? (\n <div className=\"absolute inset-0 -z-10 opacity-25\">\n <Image\n src={image.src}\n alt=\"\"\n width={image.width}\n height={image.height}\n className=\"h-full w-full object-cover\"\n sizes=\"100vw\"\n // The hero is the page's LCP element whenever it opens the page.\n priority={index === 0}\n />\n </div>\n ) : null}\n <div className={cx(containerClassName, \"py-20 lg:py-28\")}>\n <div className=\"max-w-3xl\">\n {block.eyebrow ? (\n <p className=\"text-sm font-semibold tracking-widest uppercase opacity-80\">\n {block.eyebrow}\n </p>\n ) : null}\n <h1 className=\"mt-3 text-4xl font-bold tracking-tight text-balance lg:text-5xl\">\n {block.heading}\n </h1>\n {block.body ? (\n <p className=\"mt-6 max-w-2xl text-lg leading-relaxed opacity-90\">\n {block.body}\n </p>\n ) : null}\n {links.length > 0 ? (\n <div className=\"mt-8 flex flex-wrap gap-3\">\n {links.map((link, i) => (\n <Button\n key={`${link.href}-${i}`}\n asChild\n variant={i === 0 ? \"default\" : \"outline\"}\n >\n <a\n href={link.href as string}\n {...(link.newTab\n ? { target: \"_blank\", rel: \"noreferrer noopener\" }\n : {})}\n >\n {link.label}\n </a>\n </Button>\n ))}\n </div>\n ) : null}\n </div>\n </div>\n </section>\n );\n}\n","import { ShowcasePanelsBlock, type ShowcasePanelItem } from \"@bison-lab/ui\";\n\nimport { cx } from \"../../cx\";\nimport { resolveMedia, type ResolvedMedia } from \"../../media\";\nimport type { BlockRendererProps } from \"../../render-blocks\";\nimport type { ShowcasePanelsBlockData } from \"../../types\";\n\nexport function ShowcasePanelsBlockRenderer({\n block,\n containerClassName,\n imageComponent: Image,\n}: BlockRendererProps<ShowcasePanelsBlockData>) {\n // A panel without a picture has nothing to expand into, so a row whose\n // upload has not resolved is dropped rather than rendered empty. `dims`\n // stays index-aligned with `items` so the image callback can reach the\n // width and height the library's own prop shape has no room for.\n const dims: ResolvedMedia[] = [];\n const items: ShowcasePanelItem[] = [];\n for (const [i, row] of (block.items ?? []).entries()) {\n const image = resolveMedia(row.image);\n if (!image) continue;\n dims.push(image);\n items.push({\n id: row.id ?? String(i),\n title: row.title ?? \"\",\n summary: row.summary ?? \"\",\n image: { src: image.src, alt: image.alt },\n ...(row.href ? { href: row.href } : {}),\n ...(row.numeral ? { numeral: row.numeral } : {}),\n });\n }\n if (items.length === 0) return null;\n\n return (\n <section className={cx(containerClassName, \"py-16 lg:py-24\")}>\n <ShowcasePanelsBlock\n items={items}\n spineVariant={block.spineVariant ?? \"numbered\"}\n defaultActiveIndex={Math.min(\n Math.max(block.defaultActiveIndex ?? 0, 0),\n items.length - 1,\n )}\n {...(block.watermark ? { watermark: block.watermark } : {})}\n {...(block.ariaLabel ? { ariaLabel: block.ariaLabel } : {})}\n {...(block.labels?.selected || block.labels?.preview\n ? {\n labels: {\n ...(block.labels.selected\n ? { selected: block.labels.selected }\n : {}),\n ...(block.labels.preview\n ? { preview: block.labels.preview }\n : {}),\n },\n }\n : {})}\n renderImage={(item, i) => (\n <Image\n src={item.image.src}\n alt={item.image.alt ?? \"\"}\n width={dims[i]?.width}\n height={dims[i]?.height}\n className=\"absolute inset-0 h-full w-full object-cover\"\n sizes=\"(min-width: 1024px) 50vw, 100vw\"\n />\n )}\n />\n </section>\n );\n}\n","import { ProcessStepsBlock, type ProcessStepItem } from \"@bison-lab/ui\";\n\nimport { cx } from \"../../cx\";\nimport { resolveMedia, type ResolvedMedia } from \"../../media\";\nimport type { BlockRendererProps } from \"../../render-blocks\";\nimport type { ProcessStepsBlockData } from \"../../types\";\n\nexport function ProcessStepsBlockRenderer({\n block,\n containerClassName,\n imageComponent: Image,\n}: BlockRendererProps<ProcessStepsBlockData>) {\n // Same rule as the showcase panels: a step is its picture, so an unresolved\n // upload drops the row instead of rendering a hole in the rail.\n const dims: ResolvedMedia[] = [];\n const items: ProcessStepItem[] = [];\n for (const [i, row] of (block.items ?? []).entries()) {\n const image = resolveMedia(row.image);\n if (!image) continue;\n dims.push(image);\n items.push({\n id: row.id ?? String(i),\n title: row.title ?? \"\",\n description: row.description ?? \"\",\n image: { src: image.src, alt: image.alt },\n ...(typeof row.step === \"number\" ? { step: row.step } : {}),\n });\n }\n if (items.length === 0) return null;\n\n return (\n <section className={cx(containerClassName, \"py-16 lg:py-24\")}>\n <ProcessStepsBlock\n items={items}\n autoAdvance={block.autoAdvance ?? true}\n autoAdvanceDuration={block.autoAdvanceDuration ?? 6000}\n pauseOnHover={block.pauseOnHover ?? true}\n defaultActiveIndex={Math.min(\n Math.max(block.defaultActiveIndex ?? 0, 0),\n items.length - 1,\n )}\n {...(block.ariaLabel ? { ariaLabel: block.ariaLabel } : {})}\n {...(block.labels?.pause || block.labels?.resume\n ? {\n labels: {\n ...(block.labels.pause ? { pause: block.labels.pause } : {}),\n ...(block.labels.resume ? { resume: block.labels.resume } : {}),\n },\n }\n : {})}\n renderImage={(item, i) => (\n <Image\n src={item.image.src}\n alt={item.image.alt ?? \"\"}\n width={dims[i]?.width}\n height={dims[i]?.height}\n className=\"size-full object-cover\"\n sizes=\"(min-width: 768px) 66vw, 100vw\"\n />\n )}\n />\n </section>\n );\n}\n","import { FAQColumnsBlock, type FAQItem } from \"@bison-lab/ui\";\n\nimport { cx } from \"../../cx\";\nimport type { BlockRendererProps } from \"../../render-blocks\";\nimport type { FaqColumnsBlockData } from \"../../types\";\n\nexport function FaqColumnsBlockRenderer({\n block,\n containerClassName,\n}: BlockRendererProps<FaqColumnsBlockData>) {\n const items: FAQItem[] = (block.items ?? [])\n .filter((row) => row.question && row.answer)\n .map((row, i) => ({\n id: row.id ?? String(i),\n question: row.question as string,\n // The field is a textarea, so blank lines are the editor's paragraph\n // breaks and have to survive into the DOM.\n answer: <span className=\"whitespace-pre-line\">{row.answer}</span>,\n }));\n if (items.length === 0) return null;\n\n const cta = block.cta;\n const hasCta = Boolean(cta?.href && cta?.linkText);\n\n return (\n <section>\n <div className={cx(containerClassName, \"py-16 lg:py-24\")}>\n <FAQColumnsBlock\n // The site measure is on the wrapper above; the block's own\n // container and vertical rhythm are reset so they cannot fight it.\n className=\"py-0 md:py-0\"\n containerClassName=\"max-w-none px-0 sm:px-0\"\n items={items}\n title={block.title ?? \"\"}\n {...(block.eyebrow ? { eyebrow: block.eyebrow } : {})}\n {...(block.description ? { description: block.description } : {})}\n sticky={block.sticky ?? true}\n type={block.type ?? \"single\"}\n collapsible={block.collapsible ?? true}\n {...(hasCta && cta\n ? {\n cta: {\n title: cta.title ?? \"\",\n linkText: cta.linkText as string,\n href: cta.href as string,\n newTab: cta.newTab ?? false,\n },\n }\n : {})}\n />\n </div>\n </section>\n );\n}\n","import { TestimonialMasonry, type TestimonialItem } from \"@bison-lab/ui\";\n\nimport { cx } from \"../../cx\";\nimport { resolveMedia } from \"../../media\";\nimport type { BlockRendererProps } from \"../../render-blocks\";\nimport type { TestimonialMasonryBlockData } from \"../../types\";\n\nexport function TestimonialMasonryBlockRenderer({\n block,\n containerClassName,\n}: BlockRendererProps<TestimonialMasonryBlockData>) {\n const items: TestimonialItem[] = (block.items ?? [])\n .filter((row) => row.content && row.author?.name)\n .map((row, i) => {\n const avatar = resolveMedia(row.author?.avatar);\n return {\n id: row.id ?? String(i),\n content: row.content as string,\n author: {\n name: row.author?.name as string,\n ...(row.author?.title ? { title: row.author.title } : {}),\n // The library falls back to initials when this is absent, which is\n // why an unresolved upload must not become an empty string.\n ...(avatar ? { avatarUrl: avatar.src } : {}),\n },\n };\n });\n if (items.length === 0) return null;\n\n const link = block.link;\n const hasLink = Boolean(link?.href && link?.label);\n\n return (\n // The band's surface is full-bleed; only its contents sit at the measure.\n <section className=\"bg-accent\">\n <div className={cx(containerClassName, \"py-16 lg:py-24\")}>\n <TestimonialMasonry\n className=\"bg-transparent px-0 lg:px-0\"\n containerClassName=\"max-w-none px-0 py-0 sm:py-0 md:px-0 md:py-0\"\n items={items}\n title={block.title ?? \"\"}\n {...(block.eyebrow ? { eyebrow: block.eyebrow } : {})}\n {...(block.description ? { description: block.description } : {})}\n {...(typeof block.minItemsForFade === \"number\"\n ? { minItemsForFade: block.minItemsForFade }\n : {})}\n {...(typeof block.maxVisibleRows === \"number\"\n ? { maxVisibleRows: block.maxVisibleRows }\n : {})}\n {...(hasLink && link\n ? {\n link: {\n label: link.label as string,\n href: link.href as string,\n newTab: link.newTab ?? false,\n },\n }\n : {})}\n />\n </div>\n </section>\n );\n}\n","import {\n JsonLd,\n NapBlock,\n type NapDepartment,\n type NapRecord,\n} from \"@bison-lab/ui\";\n\nimport { cx } from \"../../cx\";\nimport type { BlockRendererProps } from \"../../render-blocks\";\nimport type { NapBlockData } from \"../../types\";\n\nexport function NapBlockRenderer({\n block,\n containerClassName,\n}: BlockRendererProps<NapBlockData>) {\n const address = block.address;\n const departments: NapDepartment[] = (block.departments ?? [])\n // `phoneE164` is what becomes the `tel:` href and the structured data's\n // `telephone`, so a department without one is not publishable as a NAP\n // entry at all — dropping it beats emitting an unreachable number.\n .filter((row) => row.name && row.phoneE164)\n .map((row, i) => ({\n id: row.id ?? String(i),\n name: row.name as string,\n ...(row.departmentType ? { type: row.departmentType } : {}),\n phone: {\n e164: row.phoneE164 as string,\n ...(row.phoneDisplay ? { display: row.phoneDisplay } : {}),\n },\n }));\n\n if (!block.businessName || !address?.streetAddress || departments.length === 0)\n return null;\n\n const record: NapRecord = {\n name: block.businessName,\n type: block.businessType ?? \"LocalBusiness\",\n address: {\n streetAddress: address.streetAddress,\n addressLocality: address.addressLocality ?? \"\",\n addressRegion: address.addressRegion ?? \"\",\n postalCode: address.postalCode ?? \"\",\n ...(address.addressCountry\n ? { addressCountry: address.addressCountry }\n : {}),\n },\n departments,\n ...(block.url ? { url: block.url } : {}),\n };\n\n return (\n <section>\n <div className={cx(containerClassName, \"py-16 lg:py-24\")}>\n <NapBlock\n record={record}\n showName={block.showName ?? true}\n headingLevel={block.headingLevel ?? \"h2\"}\n />\n {block.emitJsonLd === false ? null : <JsonLd data={record} />}\n </div>\n </section>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"react.mjs","names":[],"sources":["../src/image.tsx","../src/render-blocks.tsx","../src/media.ts","../src/blocks/hero/component.tsx","../src/blocks/showcase-panels/component.tsx","../src/blocks/process-steps/component.tsx","../src/blocks/faq-columns/component.tsx","../src/blocks/testimonial-masonry/component.tsx","../src/blocks/nap/component.tsx"],"sourcesContent":["import type { ComponentType } from \"react\";\n\n/**\n * The image injection point.\n *\n * The package deliberately does not depend on `next` — a Payload site is not\n * necessarily a Next.js site, and `next/image` needs per-site configuration\n * (`remotePatterns` for the media route) that a package cannot supply. So\n * renderers never construct an image element themselves; they render the\n * component they were handed. A Next site writes one adapter around\n * `next/image` and every block gets optimisation from it.\n *\n * This is a *component type*, not a render function, on purpose: a Server\n * Component may pass a client component reference across the boundary, but not\n * a closure. `imageComponent` therefore survives the trip from a server page\n * into these client renderers, where a `renderImage` callback would not.\n */\n\nexport interface BlockImageProps {\n src: string;\n /** Always a string — see `resolveMedia`. Empty means decorative. */\n alt: string;\n width?: number;\n height?: number;\n className?: string;\n /** Passed through to `next/image`'s `sizes`; ignored by the default. */\n sizes?: string;\n /** Set on the one image above the fold, typically the hero's. */\n priority?: boolean;\n}\n\nexport type BlockImageComponent = ComponentType<BlockImageProps>;\n\n/**\n * The fallback when no `imageComponent` is supplied: a plain `<img>`. Correct\n * everywhere and optimised nowhere, which is the right default for a package\n * that cannot know what framework it landed in.\n */\nexport function DefaultBlockImage({\n src,\n alt,\n width,\n height,\n className,\n sizes,\n priority,\n}: BlockImageProps) {\n return (\n <img\n src={src}\n alt={alt}\n width={width}\n height={height}\n className={className}\n sizes={sizes}\n loading={priority ? \"eager\" : \"lazy\"}\n decoding=\"async\"\n />\n );\n}\n","import type { ComponentType, ReactElement } from \"react\";\n\nimport { type BlockImageComponent, DefaultBlockImage } from \"./image\";\n\n/** The minimum a row must be for the dispatch to place it. */\nexport interface BlockLike {\n blockType: string;\n id?: string | null;\n}\n\n/**\n * What every renderer is handed.\n *\n * The neighbour props let a block adapt to what sits around it: `prevType` to\n * close a seam against the block above, `runIndex` to alternate surfaces across\n * back-to-back blocks of one type, `isLast` because the final block sits\n * against the site footer.\n *\n * `containerClassName` and `imageComponent` are the two things a package block\n * cannot decide for itself — the site's page measure, and how an image gets\n * optimised. Both arrive already defaulted, so a renderer never checks them.\n */\nexport interface BlockRendererProps<B extends BlockLike = BlockLike> {\n block: B;\n /** Position on the page, hero included. */\n index: number;\n /** The type of the block before this one; absent on the first block. */\n prevType?: string;\n /** Position within a run of consecutive same-type blocks, from 0. */\n runIndex: number;\n isLast: boolean;\n /** The site's page measure, for the band wrapper. */\n containerClassName: string;\n /** How this block turns a resolved image into an element. */\n imageComponent: BlockImageComponent;\n}\n\n/**\n * One renderer per block type, each typed to its own block.\n *\n * Written as a mapped type over the union so `hero` gets `HeroBlockData`, not\n * the whole union. A site writes `satisfies BlockRegistryFor<AnyBlock>` where\n * `AnyBlock` comes off its *generated* `Page` type — that is the compile-time\n * layout lock, and it has to stay in the site, because only the site has\n * generated types. The registry is a parameter here for exactly that reason.\n */\nexport type BlockRegistryFor<B extends BlockLike> = {\n [K in B[\"blockType\"]]: ComponentType<\n BlockRendererProps<Extract<B, { blockType: K }>>\n >;\n};\n\n/** Fallback page measure for a site that does not pass its own. */\nexport const DEFAULT_CONTAINER = \"mx-auto w-full max-w-7xl px-6\";\n\ntype LooseRenderer = ComponentType<BlockRendererProps<BlockLike>>;\n\n/**\n * A row saved against a block that has since left the config has no renderer.\n * It is dropped before anything is counted, so its neighbours see each other,\n * not a gap: the block after it gets the real `prevType`, a run of one type is\n * not broken by it, and the block before it is still `isLast` when it ends the\n * page.\n */\nfunction renderable(\n blocks: BlockLike[],\n registry: Record<string, LooseRenderer | undefined>,\n): { block: BlockLike; Renderer: LooseRenderer }[] {\n return blocks.flatMap((block) => {\n const Renderer = registry[block.blockType];\n return Renderer ? [{ block, Renderer }] : [];\n });\n}\n\n/** Position of each block within its run of consecutive same-type blocks. */\nfunction runIndexes(blocks: BlockLike[]): number[] {\n const out: number[] = [];\n for (let i = 0; i < blocks.length; i += 1) {\n out.push(\n i > 0 && blocks[i - 1].blockType === blocks[i].blockType\n ? out[i - 1] + 1\n : 0,\n );\n }\n return out;\n}\n\nexport interface RenderBlocksProps<B extends BlockLike> {\n /** Pass `[...page.hero, ...page.layout]`, so the hero counts as index 0. */\n blocks: B[];\n registry: BlockRegistryFor<B>;\n /** The site's page measure. Defaults to `DEFAULT_CONTAINER`. */\n containerClassName?: string;\n /** Defaults to a plain `<img>`; a Next site passes a `next/image` adapter. */\n imageComponent?: BlockImageComponent;\n}\n\n/**\n * Renders a page's blocks in order through the registry. A Server Component:\n * it only maps and looks up, and each renderer decides its own nature.\n */\nexport function RenderBlocks<B extends BlockLike>({\n blocks,\n registry,\n containerClassName = DEFAULT_CONTAINER,\n imageComponent = DefaultBlockImage,\n}: RenderBlocksProps<B>): ReactElement {\n const rows = renderable(\n blocks,\n // The mapped registry type is per-block, and the dispatch is not; the\n // lookup below is what guarantees each renderer only ever sees its own\n // block type, and no signature can express that to the compiler.\n registry as unknown as Record<string, LooseRenderer | undefined>,\n );\n const runs = runIndexes(rows.map((row) => row.block));\n return (\n <>\n {rows.map(({ block, Renderer }, index) => (\n <Renderer\n key={block.id ?? index}\n block={block}\n index={index}\n prevType={index > 0 ? rows[index - 1].block.blockType : undefined}\n runIndex={runs[index]}\n isLast={index === rows.length - 1}\n containerClassName={containerClassName}\n imageComponent={imageComponent}\n />\n ))}\n </>\n );\n}\n","/**\n * The upload side of the boundary.\n *\n * A Payload upload field holds either the row's id or, once depth resolves it,\n * the whole document. Neither shape can be imported from here: `Media` is\n * generated per site, and this package must never reach for a site's\n * `payload-types`. So the doc is described structurally, and every renderer\n * goes through `resolveMedia` rather than reading `.url` itself.\n */\n\n/**\n * The subset of a Payload upload document a block renderer actually reads.\n * Every site's generated `Media` interface is assignable to this, whatever\n * else it carries.\n *\n * Deliberately no index signature: TypeScript refuses to assign an interface\n * to a type with one, and a site's generated `Media` is always an interface.\n * An index signature here would break exactly the structural match this type\n * exists to provide.\n */\nexport interface MediaDoc {\n url?: string | null;\n alt?: string | null;\n width?: number | null;\n height?: number | null;\n}\n\n/** What an upload field holds before and after `depth` populates it. */\nexport type MediaValue = number | string | MediaDoc | null | undefined;\n\n/** A resolved image, in the shape every `@bison-lab/ui` block asks for. */\nexport interface ResolvedMedia {\n src: string;\n alt: string;\n width?: number;\n height?: number;\n}\n\nfunction isMediaDoc(value: MediaValue): value is MediaDoc {\n return typeof value === \"object\" && value !== null;\n}\n\n/**\n * Narrows an upload value to something renderable, or `undefined`.\n *\n * `undefined` covers three cases a renderer must treat identically: the field\n * is empty, `depth: 0` left it as a bare id, or the document exists but has no\n * `url` yet (an upload mid-flight). A renderer that gets `undefined` omits the\n * image rather than emitting a broken `<img>`.\n *\n * `alt` is always a string: the media collection requires it, but a draft saved\n * before that validation ran can still reach a renderer, and an `alt` of\n * `undefined` would silently drop the attribute entirely.\n */\nexport function resolveMedia(value: MediaValue): ResolvedMedia | undefined {\n if (!isMediaDoc(value)) return undefined;\n const { url, alt, width, height } = value;\n if (typeof url !== \"string\" || url.length === 0) return undefined;\n return {\n src: url,\n alt: typeof alt === \"string\" ? alt : \"\",\n ...(typeof width === \"number\" ? { width } : {}),\n ...(typeof height === \"number\" ? { height } : {}),\n };\n}\n","import { Button } from \"@bison-lab/ui\";\n\nimport { cx } from \"../../cx\";\nimport { resolveMedia } from \"../../media\";\nimport type { BlockRendererProps } from \"../../render-blocks\";\nimport type { HeroBlockData } from \"../../types\";\n\n/**\n * How each tone paints. Semantic tokens only — a site's brand reaches these\n * through its theme, never through a class named here.\n */\nconst TONES = {\n sweep: \"bg-primary text-primary-foreground\",\n dark: \"bg-foreground text-background\",\n light: \"bg-background text-foreground\",\n} as const;\n\n/**\n * The package's hero: deliberately plain.\n *\n * A hero is where a site's brand is loudest, so this exists to make a page\n * render at all, not to be the final answer. A site overrides this one registry\n * entry with its own and keeps every other block.\n */\nexport function HeroBlockRenderer({\n block,\n index,\n containerClassName,\n imageComponent: Image,\n}: BlockRendererProps<HeroBlockData>) {\n const image = resolveMedia(block.image);\n const links = (block.links ?? []).filter((link) => link.href && link.label);\n const tone = TONES[block.tone ?? \"sweep\"];\n\n return (\n <section className={cx(\"relative isolate overflow-hidden\", tone)}>\n {image ? (\n <div className=\"absolute inset-0 -z-10 opacity-25\">\n <Image\n src={image.src}\n alt=\"\"\n width={image.width}\n height={image.height}\n className=\"h-full w-full object-cover\"\n sizes=\"100vw\"\n // The hero is the page's LCP element whenever it opens the page.\n priority={index === 0}\n />\n </div>\n ) : null}\n <div className={cx(containerClassName, \"py-20 lg:py-28\")}>\n <div className=\"max-w-3xl\">\n {block.eyebrow ? (\n <p className=\"text-sm font-semibold tracking-widest uppercase opacity-80\">\n {block.eyebrow}\n </p>\n ) : null}\n <h1 className=\"mt-3 text-4xl font-bold tracking-tight text-balance lg:text-5xl\">\n {block.heading}\n </h1>\n {block.body ? (\n <p className=\"mt-6 max-w-2xl text-lg leading-relaxed opacity-90\">\n {block.body}\n </p>\n ) : null}\n {links.length > 0 ? (\n <div className=\"mt-8 flex flex-wrap gap-3\">\n {links.map((link, i) => (\n <Button\n key={`${link.href}-${i}`}\n asChild\n variant={i === 0 ? \"default\" : \"outline\"}\n >\n <a\n href={link.href as string}\n {...(link.newTab\n ? { target: \"_blank\", rel: \"noreferrer noopener\" }\n : {})}\n >\n {link.label}\n </a>\n </Button>\n ))}\n </div>\n ) : null}\n </div>\n </div>\n </section>\n );\n}\n","import { ShowcasePanelsBlock, type ShowcasePanelItem } from \"@bison-lab/ui\";\n\nimport { cx } from \"../../cx\";\nimport { resolveMedia, type ResolvedMedia } from \"../../media\";\nimport type { BlockRendererProps } from \"../../render-blocks\";\nimport type { ShowcasePanelsBlockData } from \"../../types\";\n\nexport function ShowcasePanelsBlockRenderer({\n block,\n containerClassName,\n imageComponent: Image,\n}: BlockRendererProps<ShowcasePanelsBlockData>) {\n // A panel without a picture has nothing to expand into, so a row whose\n // upload has not resolved is dropped rather than rendered empty. `dims`\n // stays index-aligned with `items` so the image callback can reach the\n // width and height the library's own prop shape has no room for.\n const dims: ResolvedMedia[] = [];\n const items: ShowcasePanelItem[] = [];\n for (const [i, row] of (block.items ?? []).entries()) {\n const image = resolveMedia(row.image);\n if (!image) continue;\n dims.push(image);\n items.push({\n id: row.id ?? String(i),\n title: row.title ?? \"\",\n summary: row.summary ?? \"\",\n image: { src: image.src, alt: image.alt },\n ...(row.href ? { href: row.href } : {}),\n ...(row.numeral ? { numeral: row.numeral } : {}),\n });\n }\n if (items.length === 0) return null;\n\n return (\n <section className={cx(containerClassName, \"py-16 lg:py-24\")}>\n <ShowcasePanelsBlock\n items={items}\n spineVariant={block.spineVariant ?? \"numbered\"}\n defaultActiveIndex={Math.min(\n Math.max(block.defaultActiveIndex ?? 0, 0),\n items.length - 1,\n )}\n {...(block.watermark ? { watermark: block.watermark } : {})}\n renderImage={(item, i) => (\n <Image\n src={item.image.src}\n alt={item.image.alt ?? \"\"}\n width={dims[i]?.width}\n height={dims[i]?.height}\n className=\"absolute inset-0 h-full w-full object-cover\"\n sizes=\"(min-width: 1024px) 50vw, 100vw\"\n />\n )}\n />\n </section>\n );\n}\n","import { ProcessStepsBlock, type ProcessStepItem } from \"@bison-lab/ui\";\n\nimport { cx } from \"../../cx\";\nimport { resolveMedia, type ResolvedMedia } from \"../../media\";\nimport type { BlockRendererProps } from \"../../render-blocks\";\nimport type { ProcessStepsBlockData } from \"../../types\";\n\nexport function ProcessStepsBlockRenderer({\n block,\n containerClassName,\n imageComponent: Image,\n}: BlockRendererProps<ProcessStepsBlockData>) {\n // Same rule as the showcase panels: a step is its picture, so an unresolved\n // upload drops the row instead of rendering a hole in the rail.\n const dims: ResolvedMedia[] = [];\n const items: ProcessStepItem[] = [];\n for (const [i, row] of (block.items ?? []).entries()) {\n const image = resolveMedia(row.image);\n if (!image) continue;\n dims.push(image);\n items.push({\n id: row.id ?? String(i),\n title: row.title ?? \"\",\n description: row.description ?? \"\",\n image: { src: image.src, alt: image.alt },\n });\n }\n if (items.length === 0) return null;\n\n return (\n <section className={cx(containerClassName, \"py-16 lg:py-24\")}>\n <ProcessStepsBlock\n items={items}\n autoAdvance={block.autoAdvance ?? true}\n autoAdvanceDuration={block.autoAdvanceDuration ?? 6000}\n pauseOnHover={block.pauseOnHover ?? true}\n defaultActiveIndex={Math.min(\n Math.max(block.defaultActiveIndex ?? 0, 0),\n items.length - 1,\n )}\n renderImage={(item, i) => (\n <Image\n src={item.image.src}\n alt={item.image.alt ?? \"\"}\n width={dims[i]?.width}\n height={dims[i]?.height}\n className=\"size-full object-cover\"\n sizes=\"(min-width: 768px) 66vw, 100vw\"\n />\n )}\n />\n </section>\n );\n}\n","import { FAQColumnsBlock, type FAQItem } from \"@bison-lab/ui\";\n\nimport { cx } from \"../../cx\";\nimport type { BlockRendererProps } from \"../../render-blocks\";\nimport type { FaqColumnsBlockData } from \"../../types\";\n\nexport function FaqColumnsBlockRenderer({\n block,\n containerClassName,\n}: BlockRendererProps<FaqColumnsBlockData>) {\n const items: FAQItem[] = (block.items ?? [])\n .filter((row) => row.question && row.answer)\n .map((row, i) => ({\n id: row.id ?? String(i),\n question: row.question as string,\n // The field is a textarea, so blank lines are the editor's paragraph\n // breaks and have to survive into the DOM.\n answer: <span className=\"whitespace-pre-line\">{row.answer}</span>,\n }));\n if (items.length === 0) return null;\n\n const cta = block.cta;\n const hasCta = Boolean(cta?.href && cta?.linkText);\n\n return (\n <section>\n <div className={cx(containerClassName, \"py-16 lg:py-24\")}>\n <FAQColumnsBlock\n // The site measure is on the wrapper above; the block's own\n // container and vertical rhythm are reset so they cannot fight it.\n className=\"py-0 md:py-0\"\n containerClassName=\"max-w-none px-0 sm:px-0\"\n items={items}\n title={block.title ?? \"\"}\n {...(block.eyebrow ? { eyebrow: block.eyebrow } : {})}\n {...(block.description ? { description: block.description } : {})}\n sticky={block.sticky ?? true}\n type={block.type ?? \"single\"}\n collapsible={block.collapsible ?? true}\n {...(hasCta && cta\n ? {\n cta: {\n title: cta.title ?? \"\",\n linkText: cta.linkText as string,\n href: cta.href as string,\n newTab: cta.newTab ?? false,\n },\n }\n : {})}\n />\n </div>\n </section>\n );\n}\n","import { TestimonialMasonry, type TestimonialItem } from \"@bison-lab/ui\";\n\nimport { cx } from \"../../cx\";\nimport { resolveMedia } from \"../../media\";\nimport type { BlockRendererProps } from \"../../render-blocks\";\nimport type { TestimonialMasonryBlockData } from \"../../types\";\n\nexport function TestimonialMasonryBlockRenderer({\n block,\n containerClassName,\n}: BlockRendererProps<TestimonialMasonryBlockData>) {\n const items: TestimonialItem[] = (block.items ?? [])\n .filter((row) => row.content && row.author?.name)\n .map((row, i) => {\n const avatar = resolveMedia(row.author?.avatar);\n return {\n id: row.id ?? String(i),\n content: row.content as string,\n author: {\n name: row.author?.name as string,\n ...(row.author?.title ? { title: row.author.title } : {}),\n // The library falls back to initials when this is absent, which is\n // why an unresolved upload must not become an empty string.\n ...(avatar ? { avatarUrl: avatar.src } : {}),\n },\n };\n });\n if (items.length === 0) return null;\n\n const link = block.link;\n const hasLink = Boolean(link?.href && link?.label);\n\n return (\n // The band's surface is full-bleed; only its contents sit at the measure.\n <section className=\"bg-accent\">\n <div className={cx(containerClassName, \"py-16 lg:py-24\")}>\n <TestimonialMasonry\n className=\"bg-transparent px-0 lg:px-0\"\n containerClassName=\"max-w-none px-0 py-0 sm:py-0 md:px-0 md:py-0\"\n items={items}\n title={block.title ?? \"\"}\n {...(block.eyebrow ? { eyebrow: block.eyebrow } : {})}\n {...(block.description ? { description: block.description } : {})}\n {...(typeof block.minItemsForFade === \"number\"\n ? { minItemsForFade: block.minItemsForFade }\n : {})}\n {...(typeof block.maxVisibleRows === \"number\"\n ? { maxVisibleRows: block.maxVisibleRows }\n : {})}\n {...(hasLink && link\n ? {\n link: {\n label: link.label as string,\n href: link.href as string,\n newTab: link.newTab ?? false,\n },\n }\n : {})}\n />\n </div>\n </section>\n );\n}\n","import {\n JsonLd,\n NapBlock,\n type NapDepartment,\n type NapRecord,\n} from \"@bison-lab/ui\";\n\nimport { cx } from \"../../cx\";\nimport type { BlockRendererProps } from \"../../render-blocks\";\nimport type { NapBlockData } from \"../../types\";\n\nexport function NapBlockRenderer({\n block,\n containerClassName,\n}: BlockRendererProps<NapBlockData>) {\n const address = block.address;\n const departments: NapDepartment[] = (block.departments ?? [])\n // `phoneE164` is what becomes the `tel:` href and the structured data's\n // `telephone`, so a department without one is not publishable as a NAP\n // entry at all — dropping it beats emitting an unreachable number.\n .filter((row) => row.name && row.phoneE164)\n .map((row, i) => ({\n id: row.id ?? String(i),\n name: row.name as string,\n ...(row.departmentType ? { type: row.departmentType } : {}),\n phone: {\n e164: row.phoneE164 as string,\n ...(row.phoneDisplay ? { display: row.phoneDisplay } : {}),\n },\n }));\n\n if (!block.businessName || !address?.streetAddress || departments.length === 0)\n return null;\n\n const record: NapRecord = {\n name: block.businessName,\n type: block.businessType ?? \"LocalBusiness\",\n address: {\n streetAddress: address.streetAddress,\n addressLocality: address.addressLocality ?? \"\",\n addressRegion: address.addressRegion ?? \"\",\n postalCode: address.postalCode ?? \"\",\n ...(address.addressCountry\n ? { addressCountry: address.addressCountry }\n : {}),\n },\n departments,\n ...(block.url ? { url: block.url } : {}),\n };\n\n return (\n <section>\n <div className={cx(containerClassName, \"py-16 lg:py-24\")}>\n <NapBlock\n record={record}\n showName={block.showName ?? true}\n headingLevel={block.headingLevel ?? \"h2\"}\n />\n {block.emitJsonLd === false ? null : <JsonLd data={record} />}\n </div>\n </section>\n );\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
 
2
- import { a as RichTextBlockData, u as BlockRendererProps } from "./types-BvRvDCDT.mjs";
2
+ import { a as RichTextBlockData, u as BlockRendererProps } from "./types-BBumyFd-.mjs";
3
3
  import * as react_jsx_runtime0 from "react/jsx-runtime";
4
4
 
5
5
  //#region src/blocks/rich-text/component.d.ts
@@ -215,17 +215,11 @@ interface ShowcasePanelsBlockData extends BlockRow<"showcasePanels"> {
215
215
  watermark?: string | null;
216
216
  spineVariant?: ("numbered" | "volume" | "minimal") | null;
217
217
  defaultActiveIndex?: number | null;
218
- ariaLabel?: string | null;
219
- labels?: {
220
- selected?: string | null;
221
- preview?: string | null;
222
- };
223
218
  }
224
219
  interface ProcessStepsItemData {
225
220
  title?: string | null;
226
221
  description?: string | null;
227
222
  image?: MediaValue;
228
- step?: number | null;
229
223
  id?: string | null;
230
224
  }
231
225
  interface ProcessStepsBlockData extends BlockRow<"processSteps"> {
@@ -234,11 +228,6 @@ interface ProcessStepsBlockData extends BlockRow<"processSteps"> {
234
228
  autoAdvance?: boolean | null;
235
229
  autoAdvanceDuration?: number | null;
236
230
  pauseOnHover?: boolean | null;
237
- ariaLabel?: string | null;
238
- labels?: {
239
- pause?: string | null;
240
- resume?: string | null;
241
- };
242
231
  }
243
232
  interface FaqColumnsItemData {
244
233
  question?: string | null;
@@ -304,4 +293,4 @@ interface NapBlockData extends BlockRow<"nap"> {
304
293
  }
305
294
  //#endregion
306
295
  export { RichTextBlockData as a, BlockLike as c, DEFAULT_CONTAINER as d, RenderBlocks as f, DefaultBlockImage as g, BlockImageProps as h, ProcessStepsBlockData as i, BlockRegistryFor as l, BlockImageComponent as m, HeroBlockData as n, ShowcasePanelsBlockData as o, RenderBlocksProps as p, NapBlockData as r, TestimonialMasonryBlockData as s, FaqColumnsBlockData as t, BlockRendererProps as u };
307
- //# sourceMappingURL=types-BvRvDCDT.d.mts.map
296
+ //# sourceMappingURL=types-BBumyFd-.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-BvRvDCDT.d.mts","names":[],"sources":["../src/image.tsx","../src/render-blocks.tsx","../src/fields/link.ts","../src/media.ts","../src/types.ts"],"mappings":"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"}
1
+ {"version":3,"file":"types-BBumyFd-.d.mts","names":[],"sources":["../src/image.tsx","../src/render-blocks.tsx","../src/fields/link.ts","../src/media.ts","../src/types.ts"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bison-lab/payload-blocks",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "Payload CMS block configs and renderers for the Bison Lab marketing blocks",
5
5
  "homepage": "https://components.bisonlab.ai",
6
6
  "repository": {
@@ -23,6 +23,10 @@
23
23
  "import": "./dist/react.mjs",
24
24
  "types": "./dist/react.d.mts"
25
25
  },
26
+ "./admin": {
27
+ "import": "./dist/admin.mjs",
28
+ "types": "./dist/admin.d.mts"
29
+ },
26
30
  "./rich-text": {
27
31
  "import": "./dist/rich-text.mjs",
28
32
  "types": "./dist/rich-text.d.mts"
@@ -32,8 +36,9 @@
32
36
  "dist"
33
37
  ],
34
38
  "peerDependencies": {
35
- "@bison-lab/ui": "^2.0.0",
39
+ "@bison-lab/ui": "^3.0.0",
36
40
  "@payloadcms/richtext-lexical": "^3.0.0",
41
+ "@payloadcms/ui": "^3.0.0",
37
42
  "payload": "^3.0.0",
38
43
  "react": "^18.0.0 || ^19.0.0",
39
44
  "react-dom": "^18.0.0 || ^19.0.0"
@@ -45,6 +50,9 @@
45
50
  "@payloadcms/richtext-lexical": {
46
51
  "optional": true
47
52
  },
53
+ "@payloadcms/ui": {
54
+ "optional": true
55
+ },
48
56
  "react": {
49
57
  "optional": true
50
58
  },
@@ -54,6 +62,7 @@
54
62
  },
55
63
  "devDependencies": {
56
64
  "@payloadcms/richtext-lexical": "^3.88.0",
65
+ "@payloadcms/ui": "^3.88.0",
57
66
  "@testing-library/jest-dom": "^6.9.1",
58
67
  "@testing-library/react": "^16.3.2",
59
68
  "@types/react": "^19.2.14",
@@ -65,7 +74,7 @@
65
74
  "tsdown": "^0.21.0",
66
75
  "typescript": "^5.9.3",
67
76
  "vitest": "^4.1.2",
68
- "@bison-lab/ui": "2.0.0"
77
+ "@bison-lab/ui": "3.0.0"
69
78
  },
70
79
  "publishConfig": {
71
80
  "access": "public"