@bison-lab/payload-blocks 3.4.0 → 3.11.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
@@ -23,7 +23,7 @@ Payload site already has).
23
23
  | --- | --- | --- |
24
24
  | `@bison-lab/payload-blocks` | Block configs, field builders, row types, `resolveMedia` | Node. This is what `payload.config.ts` imports, and it touches no React. |
25
25
  | `@bison-lab/payload-blocks/react` | Renderers, `RenderBlocks`, the image and link seams, the rendering types | Client (`"use client"`). |
26
- | `@bison-lab/payload-blocks/rich-text` | The `richText` renderer, and `richTextBlockRenderer()` to build one that resolves internal links | Client. Split out because it is the only thing that needs `@payloadcms/richtext-lexical`. |
26
+ | `@bison-lab/payload-blocks/rich-text` | The `richText` renderer, `internalDocToHrefFrom`, and `richTextBlockRenderer()` to build one that resolves internal links | Client. Split out because it is the only thing that needs `@payloadcms/richtext-lexical`. |
27
27
  | `@bison-lab/payload-blocks/admin` | `MinRowsArrayField` and `LinkField`, the admin fields the configs reference by path | Client, inside the Payload admin. Resolved through the site's import map, never imported by hand. |
28
28
 
29
29
  The renderers are client components because the blocks they render are: every
@@ -110,6 +110,22 @@ for `type: external`, and `required` binds whichever is active. A site's own
110
110
  block takes the same field through `linkFields()` or `linkField()`, and can
111
111
  point it at another collection with `pagesCollection`.
112
112
 
113
+ `lookField()` and `colorTokenField()` read the published Theme. Options are
114
+ the scales Color Settings offers page editors (Primary, Secondary, Accent,
115
+ Highlight, plus every custom color) and only the included steps. Success and
116
+ Destructive stay off that list. Adding Coral on Colors makes `coral` /
117
+ `coral-400` appear with no package bump. The picker is
118
+ `@bison-lab/payload-core/admin#LookField` — run `payload generate:importmap`.
119
+ Pass the Theme document into `themeHeadFromDoc` so those keys paint
120
+ (`[data-look]` + `--coral-*`).
121
+
122
+ ```ts
123
+ import { lookField, colorTokenField } from '@bison-lab/payload-blocks'
124
+
125
+ lookField()
126
+ colorTokenField({ name: 'fill' })
127
+ ```
128
+
113
129
  **2. Own the registry.**
114
130
 
115
131
  The registry is a parameter, not an export, so the compile-time layout lock
@@ -241,33 +257,32 @@ Links in a `richText` section go through `linkComponent` too, the ones Lexical
241
257
  auto-detects included. One kind needs more: a link an editor makes to another
242
258
  *document* has no URL, only the document, and the route is the site's to know.
243
259
  That is the same knowledge `resolveLink` carries, but Lexical hands the
244
- renderer a link *node* rather than one of the link rows above, so it is a
245
- separate resolver taking a separate shape. Build that site's renderer with
246
- `richTextBlockRenderer({ internalDocToHref })` and register it in place of
247
- `RichTextBlockRenderer`. Do it in a `'use client'` module, like the adapters
248
- above: the `/rich-text` entry carries the client banner, so the factory is a
249
- client reference a Server Component can pass along but cannot call, and the
250
- resolver is a closure that could not cross the boundary as a prop.
260
+ renderer a link *node* rather than one of the link rows above. The helper
261
+ `internalDocToHrefFrom` unpacks that node into a page destination and calls
262
+ the site's `resolveLink`, so the page path is defined once. Pass that same
263
+ function to the factory (`richTextBlockRenderer({ resolveLink })`) and
264
+ register the result in place of `RichTextBlockRenderer`. Do it in a
265
+ `'use client'` module, like the adapters above: the `/rich-text` entry
266
+ carries the client banner, so the factory is a client reference a Server
267
+ Component can pass along but cannot call, and the resolver is a closure that
268
+ could not cross the boundary as a prop. A hand-written `internalDocToHref`
269
+ still works; if both options are passed, `internalDocToHref` wins.
251
270
 
252
271
  ```tsx
253
272
  'use client'
254
273
  import { richTextBlockRenderer } from '@bison-lab/payload-blocks/rich-text'
274
+ import { resolveSiteLink } from './resolve-site-link'
255
275
 
256
276
  export const SiteRichText = richTextBlockRenderer({
257
- internalDocToHref: ({ linkNode }) => {
258
- const doc = linkNode.fields.doc
259
- if (!doc) return '#'
260
- // `value` is the related document when the page was fetched deep enough
261
- // to populate it, otherwise its id. Fetch pages at depth 1 or more.
262
- const slug = typeof doc.value === 'object' ? doc.value.slug : doc.value
263
- return doc.relationTo === 'pages' ? `/${slug}` : `/${doc.relationTo}/${slug}`
264
- },
277
+ resolveLink: resolveSiteLink,
265
278
  })
266
279
  ```
267
280
 
268
281
  Without a resolver an internal link still renders through the adapter, at `#`,
269
282
  with a console error naming the option. A site whose editors link between
270
- documents should not ship that way.
283
+ documents should not ship that way. An unpopulated or unpublished `doc`
284
+ follows `resolveLink`'s `null` rule and the helper maps that to `#` — it does
285
+ not invent a second unpublished policy.
271
286
 
272
287
  One rich-text node still bypasses both seams: an *upload* an editor drops into
273
288
  the prose renders through Payload's own converter, a file as a bare `<a>` and
@@ -396,7 +411,7 @@ renders nothing, and `headerItemsFromBlocks` drops it.
396
411
  | Slug | Renders | Notes |
397
412
  | --- | --- | --- |
398
413
  | `hero` | plain markup | No `@bison-lab/ui` counterpart. Override this entry with your own. |
399
- | `richText` | `RichText` (Lexical) | From `/rich-text`. Links go through `linkComponent`; a link to another document needs `richTextBlockRenderer({ internalDocToHref })`. Uploads dropped into the prose do not use the seams yet (BIS-76). |
414
+ | `richText` | `RichText` (Lexical) | From `/rich-text`. Links go through `linkComponent`; a link to another document needs `richTextBlockRenderer({ resolveLink })` (or a hand-written `internalDocToHref`). Uploads dropped into the prose do not use the seams yet (BIS-76). |
400
415
  | `showcasePanels` | `ShowcasePanelsBlock` | 3–6 panels, each with a required image. Opens with three. |
401
416
  | `processSteps` | `ProcessStepsBlock` | 3–6 ordered steps, numbered by position; advances on a timer. Opens with three. |
402
417
  | `faqColumns` | `FAQColumnsBlock` | Answers are plain text, not Lexical — see the config for why. |
package/dist/index.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import { colorTokenField, lookField } from "@bison-lab/payload-core";
1
2
  import { ArrayFieldValidation, Block, CollectionSlug, Condition, Field, GroupField, RowField, TextFieldSingleValidation, UploadField } from "payload";
2
3
 
3
4
  //#region src/blocks/hero/config.d.ts
@@ -623,5 +624,5 @@ declare function sampleImage({
623
624
  alt
624
625
  }: SampleImageOptions): MediaDoc;
625
626
  //#endregion
626
- export { type BisonBlockData, type BisonBlockType, type BlockSamples, FaqColumnsBlock, type FaqColumnsBlockData, type FaqColumnsItemData, type HeadingFieldsOptions, HeroBlock, type HeroBlockData, type ImageFieldOptions, LINK_FIELD, LinkBlock, type LinkDestination, type LinkDestinationOptions, type LinkFieldOptions, type LinkFieldsOptions, type LinkPageDoc, type LinkType, type LinkValue, MEGA_MENU_WIDTHS, MIN_ROWS_ARRAY_FIELD, type MediaDoc, type MediaValue, type MegaMenuBlockData, type MegaMenuBlockOptions, type MegaMenuColumnData, type MegaMenuLinkData, type MegaMenuOption, type MegaMenuSectionData, type MegaMenuWidthValue, NapBlock, type NapBlockData, type NapDepartmentData, type NavLinkBlockData, ProcessStepsBlock, type ProcessStepsBlockData, type ProcessStepsItemData, type ResolveLink, type ResolvedMedia, RichTextBlock, type RichTextBlockData, type RichTextContent, type SampleImageOptions, ShowcasePanelsBlock, type ShowcasePanelsBlockData, type ShowcasePanelsItemData, StatsBandBlock, type StatsBandBlockData, type StatsBandItemData, TestimonialMasonryBlock, type TestimonialMasonryBlockData, type TestimonialMasonryItemData, blockSamples, emptyRows, headingFields, imageField, linkDestinationFields, linkField, linkFields, linkTypeOf, megaMenuBlock, megaMenuSampleOptions, resolveLink, resolveMedia, sampleImage, validateColumnWidths, validateRemWidth };
627
+ export { type BisonBlockData, type BisonBlockType, type BlockSamples, FaqColumnsBlock, type FaqColumnsBlockData, type FaqColumnsItemData, type HeadingFieldsOptions, HeroBlock, type HeroBlockData, type ImageFieldOptions, LINK_FIELD, LinkBlock, type LinkDestination, type LinkDestinationOptions, type LinkFieldOptions, type LinkFieldsOptions, type LinkPageDoc, type LinkType, type LinkValue, MEGA_MENU_WIDTHS, MIN_ROWS_ARRAY_FIELD, type MediaDoc, type MediaValue, type MegaMenuBlockData, type MegaMenuBlockOptions, type MegaMenuColumnData, type MegaMenuLinkData, type MegaMenuOption, type MegaMenuSectionData, type MegaMenuWidthValue, NapBlock, type NapBlockData, type NapDepartmentData, type NavLinkBlockData, ProcessStepsBlock, type ProcessStepsBlockData, type ProcessStepsItemData, type ResolveLink, type ResolvedMedia, RichTextBlock, type RichTextBlockData, type RichTextContent, type SampleImageOptions, ShowcasePanelsBlock, type ShowcasePanelsBlockData, type ShowcasePanelsItemData, StatsBandBlock, type StatsBandBlockData, type StatsBandItemData, TestimonialMasonryBlock, type TestimonialMasonryBlockData, type TestimonialMasonryItemData, blockSamples, colorTokenField, emptyRows, headingFields, imageField, linkDestinationFields, linkField, linkFields, linkTypeOf, lookField, megaMenuBlock, megaMenuSampleOptions, resolveLink, resolveMedia, sampleImage, validateColumnWidths, validateRemWidth };
627
628
  //# 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/blocks/stats-band/config.ts","../src/blocks/mega-menu/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","../src/samples.ts","../src/blocks/mega-menu/sample.ts","../src/sample-image.ts"],"mappings":"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"}
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/blocks/stats-band/config.ts","../src/blocks/mega-menu/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","../src/samples.ts","../src/blocks/mega-menu/sample.ts","../src/sample-image.ts"],"mappings":";;;;;;;AAcA;;;;;;cAAa,SAAA,EAAW,KAAA;;;;cCXX,aAAA,EAAe,KAAA;;;;cCGf,mBAAA,EAAqB,KAAA;;;;cCArB,iBAAA,EAAmB,KAAA;;;;cCCnB,eAAA,EAAiB,KAAA;;;;cCCjB,uBAAA,EAAyB,KAAA;;;;;;ALMtC;;;;;cMFa,QAAA,EAAU,KAAA;;;;;;AJNvB;;;;;cKyCa,cAAA,EAAgB,KAAA;;;;;;APjC7B;;;;;;;;ACXA;;;UO0BiB,cAAA;EACf,KAAA;EACA,KAAA;AAAA;AAAA,UAGe,oBAAA;EN5BJ;EM8BX,QAAA,GAAW,cAAA;;EAEX,KAAA,GAAQ,cAAA;AAAA;;cAIG,gBAAA;AAAA,KACD,kBAAA,WAA6B,gBAAA;ALrCzC;;;;;;;AAAA,cK4Da,oBAAA,EAAsB,oBAAA;AJ3DnC;AAAA,cIuGa,gBAAA,EAAkB,yBAAA;AAAA,iBAqBf,aAAA,CAAA;EACd,QAAA;EACA;AAAA,IACC,oBAAA,GAA4B,KAAA;;cA2KlB,SAAA,EAAW,KAAA;;;;;;ARnSxB;;KSPK,gBAAA,GAAmB,OAAA,CACtB,WAAA;EACE,OAAA;EAAiB,UAAA,EAAY,cAAA;AAAA;AAAA,UAGhB,iBAAA,SAA0B,OAAA,CAAQ,gBAAA;;ARTnD;;;EQcE,UAAA,GAAa,cAAA;AAAA;;;;APXf;;;;;iBOsBgB,UAAA,CAAW,SAAA,GAAW,iBAAA,GAAyB,gBAAA;;;;;;ATd/D;;;;;;;;ACXA;cSWa,UAAA;AAAA,KAED,QAAA;;;;;;ARVZ;UQkBiB,WAAA;EACf,EAAA;EACA,KAAA;EACA,IAAA;EACA,OAAA;AAAA;;UAIe,eAAA;EP0BhB;EOxBC,IAAA,GAAO,QAAA;EP5BuB;EO8B9B,IAAA,GAAO,WAAA;;EAEP,IAAA;AAAA;AN/BF;AAAA,UMmCiB,SAAA,SAAkB,eAAA;EACjC,KAAA;EACA,MAAA;AAAA;;;;ALpCF;;;;KK8CY,WAAA,IAAe,IAAA,EAAM,eAAA;;;;AJ1CjC;;;;;;;;ACmCA;;;iBGuBgB,WAAA,CACd,IAAA,EAAM,eAAA;;;;;AF1CR;;iBE4DgB,UAAA,CAAW,IAAA,EAAM,eAAA,sBAAqC,QAAA;AAAA,UAYrD,sBAAA;EFtEV;EEwEL,QAAA;EFrEmC;EEuEnC,eAAA;AAAA;;;;;;;AF/DF;;;;;iBE6EgB,qBAAA,CAAA;EACd,QAAA;EACA;AAAA,IACC,sBAAA,GAA8B,QAAA;AAAA,UAyChB,iBAAA,SAA0B,sBAAA;;EAEzC,QAAA;EF1HuD;EE4HvD,eAAA;AAAA;;;;AFzDF;;;;iBEmEgB,UAAA,CAAA;EACd,QAAA;EACA,eAAA;EACA;AAAA,IACC,iBAAA,GAAyB,KAAA;AAAA,UAaX,gBAAA,SAAyB,iBAAA;EF/Db;EEiE3B,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;;;UC7MV,oBAAA;;EAEf,QAAA;EXUW;EWRX,kBAAA;;;;;;EAMA,SAAA,GAAY,SAAA;AAAA;;;;;;;;ATNd;iBSiBgB,aAAA,CAAA;EACd,QAAA;EACA,kBAAA;EACA;AAAA,IACC,oBAAA,GAA4B,KAAA;;;;;;;AXb/B;;;;;;;;ACXA;;;;;;cWea,oBAAA;;iBAIG,SAAA,CAAU,KAAA,WAAgB,MAAA;;;;;;;AZR1C;;;;;;;;ACXA;;;;;;;UYiBiB,QAAA;EACf,GAAA;EACA,GAAA;EACA,KAAA;EACA,MAAA;AAAA;;KAIU,UAAA,qBAA+B,QAAA;;UAG1B,aAAA;EACf,GAAA;EACA,GAAA;EACA,KAAA;EACA,MAAA;AAAA;;;AT5BF;;;;;;;;ACCA;;iBQ8CgB,YAAA,CAAa,KAAA,EAAO,UAAA,GAAa,aAAA;;;;;AbxCjD;;;;;;;;ACXA;;;;;;;;ACGA;;;;;UYqBU,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;ENnD4E;EMqD5E,MAAA;EACA,EAAA;AAAA;AAAA,UAGe,mBAAA,SAA4B,QAAA;EAC3C,OAAA;EACA,KAAA;EACA,WAAA;EACA,KAAA,GAAQ,kBAAA;ENrCG;EMuCX,GAAA;IACE,KAAA;IACA,QAAA;IACA,MAAA;EAAA,IACE,eAAA;EACJ,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,KAAA;EACA,KAAA;EACA,IAAA;EACA,IAAA;EACA,EAAA;AAAA;AAAA,UAGe,kBAAA,SAA2B,QAAA;EAC1C,OAAA;EACA,KAAA;EACA,WAAA;EACA,KAAA,GAAQ,iBAAA;EL7Ic;EK+ItB,OAAA;EACA,IAAA;EACA,KAAA;EACA,OAAA;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;AAAA,UAGe,gBAAA,SAAyB,SAAA;EACxC,WAAA;EL1J6E;EK4J7E,OAAA;;EAEA,IAAA;EACA,EAAA;AAAA;AAAA,UAGe,mBAAA;EACf,OAAA;EACA,OAAA;EACA,wBAAA;EACA,KAAA,GAAQ,gBAAA;EACR,EAAA;AAAA;AAAA,UAGe,kBAAA;EACf,KAAA;EACA,WAAA;EACA,OAAA;EACA,QAAA,GAAW,mBAAA;EACX,EAAA;AAAA;AAAA,UAGe,iBAAA,SAA0B,QAAA;EACzC,KAAA;EACA,IAAA;EACA,KAAA;IACE,QAAA;IACA,OAAA,GAAU,kBAAA;IACV,MAAA;MACE,QAAA,GAAW,SAAA;MACX,GAAA,GAAM,SAAA;IAAA;IAER,YAAA,mBJpLF;IIsLE,cAAA;EAAA;AAAA;AAAA,UAIa,gBAAA,SAAyB,QAAA,aAAqB,SAAA;;KAGnD,cAAA,GACR,aAAA,GACA,iBAAA,GACA,uBAAA,GACA,qBAAA,GACA,mBAAA,GACA,2BAAA,GACA,YAAA,GACA,kBAAA,GACA,iBAAA,GACA,gBAAA;;KAGQ,cAAA,GAAiB,cAAA;;;;;;AdhO7B;KeCY,YAAA,WACJ,cAAA,GAAiB,OAAA,CAAQ,cAAA;EAAkB,SAAA,EAAW,CAAA;AAAA;;;Adb9D;;;;;;;;ACGA;;;;;ca4Ba,YAAA,EAAc,YAAA;;;;;;AfpB3B;;cgBNa,qBAAA;;;;;;;;;;;;UCNI,kBAAA;;EAEf,KAAA;EACA,KAAA;EACA,MAAA;;EAEA,GAAA;AAAA;;;;AhBLF;;;;;;;;ACGA;;iBe0BgB,WAAA,CAAA;EACd,KAAA;EACA,KAAA;EACA,MAAA;EACA;AAAA,GACC,kBAAA,GAAqB,QAAA"}
package/dist/index.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { colorTokenField, lookField } from "@bison-lab/payload-core";
1
2
  //#region src/fields/image.ts
2
3
  /**
3
4
  * A single image from the site's upload collection, for any block with a
@@ -1851,6 +1852,6 @@ const blockSamples = {
1851
1852
  navLink: navLinkSample
1852
1853
  };
1853
1854
  //#endregion
1854
- export { FaqColumnsBlock, HeroBlock, LINK_FIELD, LinkBlock, MEGA_MENU_WIDTHS, MIN_ROWS_ARRAY_FIELD, NapBlock, ProcessStepsBlock, RichTextBlock, ShowcasePanelsBlock, StatsBandBlock, TestimonialMasonryBlock, blockSamples, emptyRows, headingFields, imageField, linkDestinationFields, linkField, linkFields, linkTypeOf, megaMenuBlock, megaMenuSampleOptions, resolveLink, resolveMedia, sampleImage, validateColumnWidths, validateRemWidth };
1855
+ export { FaqColumnsBlock, HeroBlock, LINK_FIELD, LinkBlock, MEGA_MENU_WIDTHS, MIN_ROWS_ARRAY_FIELD, NapBlock, ProcessStepsBlock, RichTextBlock, ShowcasePanelsBlock, StatsBandBlock, TestimonialMasonryBlock, blockSamples, colorTokenField, emptyRows, headingFields, imageField, linkDestinationFields, linkField, linkFields, linkTypeOf, lookField, megaMenuBlock, megaMenuSampleOptions, resolveLink, resolveMedia, sampleImage, validateColumnWidths, validateRemWidth };
1855
1856
 
1856
1857
  //# 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/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/fields/row-limits.ts","../src/blocks/stats-band/config.ts","../src/blocks/mega-menu/rem-width.ts","../src/blocks/mega-menu/config.ts","../src/media.ts","../src/blocks/faq-columns/sample.ts","../src/sample-image.ts","../src/blocks/hero/sample.ts","../src/blocks/mega-menu/sample.ts","../src/blocks/nap/sample.ts","../src/blocks/process-steps/sample.ts","../src/blocks/rich-text/sample.ts","../src/blocks/showcase-panels/sample.ts","../src/blocks/stats-band/sample.ts","../src/blocks/testimonial-masonry/sample.ts","../src/samples.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 { Condition, Field, GroupField, RowField } from \"payload\";\n\n/**\n * The import-map path of the link picker, `LinkField` in\n * `@bison-lab/payload-blocks/admin`.\n *\n * It sits on the row that holds a link's `type`, `page` and `href`, and\n * replaces those three inputs with one box: type to search published pages,\n * or paste a URL. Referenced here by package specifier, like\n * `MIN_ROWS_ARRAY_FIELD`; a site picks it up with `payload generate:importmap`.\n * Until it does, Payload logs the missing entry and renders the row as\n * nothing (a custom `Field` with no component behind it is an empty element,\n * not the stock fields), so the step is part of adopting this release.\n */\nexport const LINK_FIELD = \"@bison-lab/payload-blocks/admin#LinkField\";\n\nexport type LinkType = \"page\" | \"external\";\n\n/**\n * A page as the `page` relationship populates it: what `resolveLink` reads,\n * and what the picker shows. A site's generated `Page` is assignable to it.\n * `_status` is absent on a collection without drafts, which counts as\n * published.\n */\nexport interface LinkPageDoc {\n id: number | string;\n title?: string | null;\n slug?: string | null;\n _status?: (\"draft\" | \"published\") | null;\n}\n\n/** Where a link goes: the three fields the picker writes. */\nexport interface LinkDestination {\n /** `page` unless the editor pasted a URL. Rows saved before links had a type carry only `href`. */\n type?: LinkType | null;\n /** The page's id, or the page itself once a `depth >= 1` read populates it. */\n page?: LinkPageDoc | number | string | null;\n /** The pasted URL of an external link. */\n href?: string | null;\n}\n\n/** The shape `linkField`/`linkFields` produce once Payload generates types. */\nexport interface LinkValue extends LinkDestination {\n label?: string | null;\n newTab?: boolean | null;\n}\n\n/**\n * Turns a link's destination into the `href` a renderer emits, or `null`\n * when there is nothing to point at, in which case the renderer shows the\n * label as text. A site passes its own (`RenderBlocks`' `resolveLink`) when\n * a page's path is not `/<slug>`: the package has no way to know a site's\n * routes, the same reason it takes an `imageComponent`.\n */\nexport type ResolveLink = (link: LinkDestination) => string | null;\n\n/**\n * The package's resolver: a published, populated page is `/<slug>`; an\n * external link is its `href` as typed.\n *\n * A page that did not populate is `null`: Payload hands the public read the\n * bare id when the reader may not see the page, which is what an unpublished\n * page looks like from the site. A populated page whose `_status` is `draft`\n * is `null` too, for a read that can see drafts. A row from before links had\n * a `type` is an external link by `linkTypeOf`, here, in the stock fields'\n * conditions and in the picker alike: the site keeps rendering its `href`,\n * the admin shows it as an External chip, and the site's migration to\n * `type: external` only makes the stored row say what every reader already\n * assumes.\n */\nexport function resolveLink(\n link: LinkDestination | null | undefined,\n): string | null {\n if (!link) return null;\n if (linkTypeOf(link) === \"page\") {\n const page = link.page;\n if (!page || typeof page !== \"object\") return null;\n if (page._status === \"draft\") return null;\n return page.slug ? `/${page.slug}` : null;\n }\n return link.href ? link.href : null;\n}\n\n/**\n * Which of the two stored destinations a row is using. `type` decides; a row\n * with no `type` yet (saved before links had one) is read by what it holds,\n * an `href` making it external, the same reading `resolveLink` and the picker\n * make, so all three agree on every row.\n */\nexport function linkTypeOf(link: LinkDestination | null | undefined): LinkType {\n if (link?.type === \"external\") return \"external\";\n if (link?.type === \"page\") return \"page\";\n return link?.href ? \"external\" : \"page\";\n}\n\nconst whenPage: Condition = (_data, siblingData) =>\n linkTypeOf(siblingData as LinkDestination) === \"page\";\n\nconst whenExternal: Condition = (_data, siblingData) =>\n linkTypeOf(siblingData as LinkDestination) === \"external\";\n\nexport interface LinkDestinationOptions {\n /** Require a destination. Defaults to `false`. */\n required?: boolean;\n /** The collection a page link points into. Defaults to `pages`. */\n pagesCollection?: string;\n}\n\n/**\n * Where a link goes, as stored: `type`, `page` and `href` in one row.\n *\n * The row carries the picker (`LINK_FIELD`), which replaces all three inputs\n * with one box. The stored fields are the website template's shape and stay\n * editable on their own: `page` shows for a page link, `href` for an external\n * one, and `required` binds whichever is active, since Payload skips\n * validation on a field whose condition is false. `page` offers published\n * rows only, and Payload re-checks that on publish, so a page that was\n * unpublished after being picked has to be picked again or republished.\n */\nexport function linkDestinationFields({\n required = false,\n pagesCollection = \"pages\",\n}: LinkDestinationOptions = {}): RowField[] {\n return [\n {\n type: \"row\",\n admin: { components: { Field: LINK_FIELD } },\n fields: [\n {\n name: \"type\",\n type: \"radio\",\n label: \"Goes to\",\n defaultValue: \"page\",\n options: [\n { label: \"Page\", value: \"page\" },\n { label: \"URL\", value: \"external\" },\n ],\n admin: { layout: \"horizontal\" },\n },\n {\n name: \"page\",\n type: \"relationship\",\n relationTo: pagesCollection,\n required,\n filterOptions: { _status: { equals: \"published\" } },\n admin: { condition: whenPage },\n },\n {\n name: \"href\",\n type: \"text\",\n label: \"URL\",\n required,\n admin: {\n condition: whenExternal,\n description:\n 'A full URL (\"https://example.com\"), \"mailto:\" or \"tel:\", or a site path (\"/contact\").',\n },\n },\n ],\n },\n ];\n}\n\nexport interface LinkFieldsOptions extends LinkDestinationOptions {\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 fields a call to action is made of, unwrapped: the destination row,\n * then the label and the new-tab flag.\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 pagesCollection,\n}: LinkFieldsOptions = {}): Field[] {\n return [\n ...linkDestinationFields({ required, pagesCollection }),\n { name: \"label\", type: \"text\", label: labelFieldLabel, required },\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","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 { Condition, 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 * Show the three fields only when this holds, e.g. unless the block floats.\n * Pair it with `required: false`: a hidden required field can never be\n * satisfied, and the block could not publish.\n */\n condition?: Condition;\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 condition,\n}: HeadingFieldsOptions = {}): Field[] {\n const when = condition ? { condition } : {};\n return [\n {\n name: \"eyebrow\",\n type: \"text\",\n admin: { description: eyebrowDescription, ...when },\n },\n { name: \"title\", type: \"text\", required, admin: when },\n { name: \"description\", type: \"textarea\", admin: when },\n ];\n}\n","import type { Block } from \"payload\";\n\nimport { headingFields } from \"../../fields/heading\";\nimport { linkDestinationFields } from \"../../fields/link\";\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 // The destination is `linkFields()`' shape (the picker, a page or a\n // URL); only the visible text keeps its own name here.\n ...linkDestinationFields(),\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","import type { ArrayFieldValidation } from \"payload\";\n\n/**\n * A custom `validate` replaces Payload's stock array check, so any array\n * with its own rule re-applies the row limits first or `minRows` and\n * `maxRows` stop being enforced. Same messages as the stock check, through\n * the request's translator when the admin supplies one.\n */\nexport function rowLimits(\n value: unknown[] | null | undefined,\n { minRows, maxRows, required, req }: Parameters<ArrayFieldValidation>[1],\n): string | true {\n const count = value?.length ?? 0;\n const t = req?.t;\n if (required && count === 0) {\n return t ? t(\"validation:required\") : \"This field is required.\";\n }\n if (minRows && count < minRows) {\n return t\n ? t(\"validation:requiresAtLeast\", {\n count: minRows,\n label: t(\"general:rows\"),\n })\n : `This field requires at least ${minRows} rows.`;\n }\n if (maxRows && count > maxRows) {\n return t\n ? t(\"validation:requiresNoMoreThan\", {\n count: maxRows,\n label: t(\"general:rows\"),\n })\n : `This field requires no more than ${maxRows} rows.`;\n }\n return true;\n}\n","import type { ArrayFieldValidation, Block, Condition } from \"payload\";\n\nimport { headingFields } from \"../../fields/heading\";\nimport { emptyRows, MIN_ROWS_ARRAY_FIELD } from \"../../fields/min-rows\";\nimport { rowLimits } from \"../../fields/row-limits\";\n\n/**\n * The rule on `wide`: on phones the band is two per row, so only an odd\n * count has a stat on a row of its own, only one stat can take it, and it\n * has to be a stat that starts a row (the 1st, 3rd or 5th), since a full-row\n * cell beside another cell would leave a hole. The array's own `validate`,\n * so the editor sees the message as they build, and Payload runs it again on\n * publish.\n */\nexport const validateWideFlag: ArrayFieldValidation = (value, options) => {\n const limits = rowLimits(value, options);\n if (limits !== true) return limits;\n const rows = (value ?? []) as { wide?: boolean | null }[];\n const wide = rows.flatMap((row, i) => (row?.wide ? [i] : []));\n if (wide.length > 1) return \"Only one stat can be full width on phones.\";\n if (wide.length === 0) return true;\n if (rows.length % 2 === 0) {\n return \"Full width on phones only applies to an odd number of stats. With an even count every row is already full.\";\n }\n if (wide[0] % 2 !== 0) {\n return \"Only a stat that starts a phone row can be full width: the 1st, 3rd or 5th. Move it up or down one place, or flag another.\";\n }\n return true;\n};\n\n/**\n * An overlapping band is headless: it floats across the seam as a card, and a\n * heading pulled up into the block above would land on the wrong surface.\n * The heading fields disappear from the admin the moment `overlap` is on,\n * and the renderer ignores them if they were filled in first.\n */\nexport const unlessOverlap: Condition = (_data, siblingData) =>\n !(siblingData as { overlap?: boolean | null })?.overlap;\n\n/**\n * A strip of figures: the facts a visitor should take in at a glance.\n *\n * The count, the order and the desktop column count are the editor's. The\n * phone and tablet layouts are the library's: two per row and three per row,\n * with `wide` naming the one stat that takes a full phone row when the count\n * is odd.\n */\nexport const StatsBandBlock: Block = {\n slug: \"statsBand\",\n interfaceName: \"StatsBandBlock\",\n labels: { singular: \"Stats Band\", plural: \"Stats Bands\" },\n fields: [\n ...headingFields({\n required: false,\n eyebrowDescription:\n \"Small label above the heading. The heading and eyebrow are optional; without them the band stands alone.\",\n condition: unlessOverlap,\n }),\n {\n name: \"items\",\n type: \"array\",\n required: true,\n // Designed around four, workable from two: the block opens at its\n // minimum, the house rule for every array here, and the admin field\n // refuses to go below it.\n minRows: 2,\n maxRows: 6,\n defaultValue: emptyRows(2),\n labels: { singular: \"Stat\", plural: \"Stats\" },\n admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } },\n validate: validateWideFlag,\n fields: [\n {\n name: \"value\",\n type: \"text\",\n required: true,\n admin: {\n description:\n 'The figure, as it should read: \"98%\", \"12,000+\", \"L1–S1\".',\n },\n },\n { name: \"label\", type: \"textarea\", required: true },\n {\n name: \"href\",\n type: \"text\",\n admin: {\n description:\n 'Optional. Makes the whole stat a link: a site path (\"/outcomes\") or a full URL.',\n },\n },\n {\n name: \"wide\",\n type: \"checkbox\",\n label: \"Full width on phones\",\n defaultValue: false,\n admin: {\n description:\n \"Phones show two per row. With an odd number of stats, this one takes a row to itself. Only the 1st, 3rd or 5th stat can.\",\n },\n },\n ],\n },\n {\n name: \"columns\",\n type: \"select\",\n defaultValue: \"4\",\n options: [\n { label: \"2\", value: \"2\" },\n { label: \"3\", value: \"3\" },\n { label: \"4\", value: \"4\" },\n { label: \"5\", value: \"5\" },\n { label: \"6\", value: \"6\" },\n ],\n admin: {\n description:\n \"From large screens up, and never more than there are stats. Phones show two per row and tablets three.\",\n },\n },\n {\n name: \"tone\",\n type: \"select\",\n defaultValue: \"card\",\n options: [\n { label: \"Card (bordered, raised)\", value: \"card\" },\n { label: \"Plain (dividers only)\", value: \"plain\" },\n ],\n },\n {\n name: \"align\",\n type: \"select\",\n defaultValue: \"start\",\n options: [\n { label: \"Left\", value: \"start\" },\n { label: \"Centred\", value: \"center\" },\n ],\n },\n {\n name: \"overlap\",\n type: \"checkbox\",\n label: \"Float over the seam with the block above\",\n defaultValue: false,\n admin: {\n description:\n \"The band sits across the join between the block above and the block below. Both make room for it automatically. A floating band is headless: the heading fields above are hidden while this is on.\",\n },\n },\n ],\n};\n","/**\n * The one rule for a typed panel width, shared by the config's validator and\n * the renderer's reader so the two cannot drift: if the validator accepted a\n * value the renderer dropped, the editor would be back to a width that\n * silently does nothing. React-free, because `config.ts` ships from the Node\n * entry.\n */\nconst REM_WIDTH = /^\\s*(\\d+(?:\\.\\d+)?)\\s*(rem)?\\s*$/;\n\n/** \"30\" and \"30rem\" both mean 30rem; anything else is not a width. */\nexport function remWidth(\n value: string | null | undefined,\n): `${number}rem` | null {\n const match = REM_WIDTH.exec(value ?? \"\");\n return match ? (`${Number(match[1])}rem` as `${number}rem`) : null;\n}\n","import type {\n ArrayFieldValidation,\n Block,\n Condition,\n Field,\n SelectField,\n TextFieldSingleValidation,\n} from \"payload\";\n\nimport { linkField, linkFields } from \"../../fields/link\";\nimport { emptyRows, MIN_ROWS_ARRAY_FIELD } from \"../../fields/min-rows\";\nimport { rowLimits } from \"../../fields/row-limits\";\nimport { remWidth } from \"./rem-width\";\n\n/**\n * The blocks a header global's `items` array takes.\n *\n * `megaMenuBlock({ variants, icons })` is the first factory-built block in the\n * package. Every other config is a static `Block`; this one cannot be, because\n * the featured-link variants and the icon list are the site's: the CMS only\n * ever offers approved values, the same rule the sites use for block\n * backgrounds. `LinkBlock` is the plain bar item beside it.\n *\n * Until SPI-52 lands in the Spinal Simplicity repo, no site renders its header\n * from these rows; the Storybook `CMS Blocks/MegaMenu` fixture is the only\n * header built from them.\n */\n\n/** One approved value, as a select option. */\nexport interface MegaMenuOption {\n label: string;\n value: string;\n}\n\nexport interface MegaMenuBlockOptions {\n /** Looks a featured link can take. Empty or absent drops the select. */\n variants?: MegaMenuOption[];\n /** Glyphs a list link can carry. Empty or absent drops the select. */\n icons?: MegaMenuOption[];\n}\n\n/** The percent tokens a column can take, as Payload stores them. */\nexport const MEGA_MENU_WIDTHS = [\"25\", \"33\", \"50\", \"66\", \"75\", \"100\"] as const;\nexport type MegaMenuWidthValue = (typeof MEGA_MENU_WIDTHS)[number];\n\n/** Twelfths, so 33 and 66 are real thirds and 33 + 33 + 33 is a full row. */\nconst TWELFTHS: Record<MegaMenuWidthValue, number> = {\n \"25\": 3,\n \"33\": 4,\n \"50\": 6,\n \"66\": 8,\n \"75\": 9,\n \"100\": 12,\n};\n\ninterface ColumnRow {\n width?: MegaMenuWidthValue | null;\n}\n\n/**\n * The rule on `columns`: read as fractions, the widths must total 100%. It is\n * the array's own `validate`, so the editor sees the message as they build,\n * and Payload runs field validation again on publish (only draft saves skip\n * it), so nothing publishes with a short row. Skipped when Advanced has custom\n * widths on.\n */\nexport const validateColumnWidths: ArrayFieldValidation = (value, options) => {\n const limits = rowLimits(value, options);\n if (limits !== true) return limits;\n const siblingData = options.siblingData as { customWidths?: boolean | null };\n if (siblingData?.customWidths || !value?.length) return true;\n\n const twelfths = (value as ColumnRow[]).reduce(\n (sum, row) => sum + TWELFTHS[row?.width ?? \"100\"],\n 0,\n );\n if (twelfths === 12) return true;\n const percent = Math.round((twelfths / 12) * 100);\n return `The columns add up to ${percent}%. They need to add up to 100%.`;\n};\n\n/**\n * The row `levels` segments above the field at `path`, read off the whole\n * document. A condition on a link needs its section; a condition on a column\n * needs its panel. `data` carries the full form, so the path is enough.\n */\nfunction ancestor(\n data: Record<string, unknown>,\n path: (number | string)[],\n levels: number,\n): Record<string, unknown> | undefined {\n let node: unknown = data;\n for (const segment of path.slice(0, path.length - levels)) {\n if (node === null || typeof node !== \"object\") return undefined;\n node = (node as Record<string, unknown>)[segment];\n }\n return node !== null && typeof node === \"object\"\n ? (node as Record<string, unknown>)\n : undefined;\n}\n\n/** `links.N.variant` → its section, three segments up. */\nconst whenSectionFeatured: Condition = (data, _siblingData, { path }) =>\n ancestor(data, path, 3)?.display === \"featured\";\n\n/** A featured link's glyph comes from its variant, so `icon` is a list link's field. */\nconst unlessSectionFeatured: Condition = (data, siblingData, ctx) =>\n !whenSectionFeatured(data, siblingData, ctx);\n\n/** The renderer would fall back to the preset silently; the editor should hear it here instead. */\nexport const validateRemWidth: TextFieldSingleValidation = (value) =>\n !value || remWidth(value) !== null\n ? true\n : \"Type a width in rem, like 30 or 30rem.\";\n\n/** `columns.N.width` → its panel, three segments up. */\nconst whenCustomWidths: Condition = (data, _siblingData, { path }) =>\n Boolean(ancestor(data, path, 3)?.customWidths);\n\nconst unlessCustomWidths: Condition = (data, siblingData, ctx) =>\n !whenCustomWidths(data, siblingData, ctx);\n\nfunction approvedSelect(\n name: string,\n options: MegaMenuOption[] | undefined,\n admin: SelectField[\"admin\"],\n): SelectField[] {\n if (!options?.length) return [];\n return [{ name, type: \"select\", options, admin }];\n}\n\nexport function megaMenuBlock({\n variants,\n icons,\n}: MegaMenuBlockOptions = {}): Block {\n const linkRowFields: Field[] = [\n ...linkFields({ required: true }),\n {\n name: \"description\",\n type: \"textarea\",\n admin: { description: \"Supporting line under the label. Optional.\" },\n },\n ...approvedSelect(\"variant\", variants, {\n description: \"The look of this featured link.\",\n condition: whenSectionFeatured,\n }),\n ...approvedSelect(\"icon\", icons, {\n description: \"Glyph shown before a list link. Optional.\",\n condition: unlessSectionFeatured,\n }),\n ];\n\n return {\n slug: \"megaMenu\",\n interfaceName: \"MegaMenuBlock\",\n labels: { singular: \"Mega menu\", plural: \"Mega menus\" },\n fields: [\n { name: \"label\", type: \"text\", required: true },\n {\n name: \"href\",\n type: \"text\",\n admin: {\n description:\n \"The landing page this menu opens. Optional: a trigger with no landing page is allowed.\",\n },\n },\n {\n name: \"panel\",\n type: \"group\",\n fields: [\n {\n name: \"maxWidth\",\n type: \"select\",\n defaultValue: \"standard\",\n // No \"Full\": a panel anchors under its own trigger, so it cannot\n // span the bar, and capped at a trigger's room it would only\n // equal Wide. A typed rem width lives under Advanced.\n options: [\n { label: \"Narrow\", value: \"narrow\" },\n { label: \"Standard\", value: \"standard\" },\n { label: \"Wide\", value: \"wide\" },\n ],\n },\n {\n name: \"columns\",\n type: \"array\",\n required: true,\n minRows: 1,\n maxRows: 4,\n defaultValue: emptyRows(1),\n labels: { singular: \"Column\", plural: \"Columns\" },\n validate: validateColumnWidths,\n admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } },\n fields: [\n {\n name: \"width\",\n type: \"select\",\n defaultValue: \"100\",\n options: MEGA_MENU_WIDTHS.map((value) => ({\n label: `${value}%`,\n value,\n })),\n admin: { condition: unlessCustomWidths },\n },\n {\n name: \"customWidth\",\n type: \"text\",\n admin: {\n description: \"A CSS width: 13rem, 1fr, minmax(0, 1fr)\",\n condition: whenCustomWidths,\n },\n },\n {\n name: \"divider\",\n type: \"checkbox\",\n label: \"Draw a line on the left of this column\",\n defaultValue: false,\n },\n {\n name: \"sections\",\n type: \"array\",\n labels: { singular: \"Section\", plural: \"Sections\" },\n fields: [\n {\n name: \"eyebrow\",\n type: \"text\",\n admin: {\n description:\n \"Small heading above this group; leave empty for none\",\n },\n },\n {\n name: \"display\",\n type: \"select\",\n defaultValue: \"list\",\n options: [\n { label: \"Featured\", value: \"featured\" },\n { label: \"List\", value: \"list\" },\n ],\n },\n {\n name: \"hideDescriptionsOnMobile\",\n type: \"checkbox\",\n label: \"Hide descriptions on mobile\",\n defaultValue: true,\n },\n {\n name: \"links\",\n type: \"array\",\n labels: { singular: \"Link\", plural: \"Links\" },\n fields: linkRowFields,\n },\n ],\n },\n ],\n },\n {\n name: \"footer\",\n type: \"group\",\n admin: {\n description:\n \"The strip under the columns. Leave both empty for no footer.\",\n },\n fields: [\n linkField({\n name: \"overview\",\n label: \"Overview link\",\n admin: {\n description:\n \"The section's own landing page, so it stays reachable from the bar.\",\n },\n }),\n linkField({ name: \"cta\", label: \"Call to action\" }),\n ],\n },\n {\n type: \"collapsible\",\n label: \"Advanced\",\n admin: { initCollapsed: true },\n fields: [\n {\n name: \"customWidths\",\n type: \"checkbox\",\n label:\n \"Type a CSS width for each column instead of choosing a percentage\",\n defaultValue: false,\n },\n {\n name: \"customMaxWidth\",\n type: \"text\",\n validate: validateRemWidth,\n admin: {\n description:\n \"Panel width in rem, e.g. 30. Leave empty to use the preset above.\",\n },\n },\n ],\n },\n ],\n },\n ],\n };\n}\n\n/** A plain bar item: label and destination, nothing to open. */\nexport const LinkBlock: Block = {\n slug: \"navLink\",\n interfaceName: \"NavLinkBlock\",\n labels: { singular: \"Link\", plural: \"Links\" },\n fields: linkFields({ required: true }),\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 type { FaqColumnsBlockData } from \"../../types\";\n\nexport const faqColumnsSample: FaqColumnsBlockData = {\n id: \"sample-faq-columns\",\n blockType: \"faqColumns\",\n eyebrow: \"Good to know\",\n title: \"Questions patients ask before their first visit\",\n description:\n \"If yours is not here, the front desk answers the phone between eight and six on weekdays.\",\n items: [\n {\n id: \"sample-faq-referral\",\n question: \"Do I need a referral?\",\n answer:\n \"No. You can book directly. Some insurers ask for one before they reimburse, so check your policy if you plan to claim.\",\n },\n {\n id: \"sample-faq-sessions\",\n question: \"How many sessions will I need?\",\n answer:\n \"Most plans finish in six to eight sessions. You will get a written estimate after your assessment, and we revise it with you as you progress.\",\n },\n {\n id: \"sample-faq-wear\",\n question: \"What should I wear?\",\n answer:\n \"Anything you can move in. Shorts for a knee or hip, a vest or loose top for a shoulder or neck.\\n\\nThere are changing rooms if you are coming from work.\",\n },\n {\n id: \"sample-faq-insurance\",\n question: \"Is treatment covered by insurance?\",\n answer:\n \"Usually, once conservative treatment has been recommended. We invoice you directly and give you everything the insurer needs to reimburse you.\",\n },\n {\n id: \"sample-faq-cancel\",\n question: \"What is the cancellation policy?\",\n answer:\n \"Twenty-four hours' notice, by phone or through the booking link in your confirmation email. Later than that and the session is charged.\",\n },\n ],\n cta: {\n title: \"Still have questions?\",\n linkText: \"Call the front desk\",\n type: \"external\",\n href: \"tel:+13035550142\",\n newTab: false,\n },\n sticky: true,\n type: \"single\",\n collapsible: true,\n};\n","import type { MediaDoc } from \"./media\";\n\nexport interface SampleImageOptions {\n /** Text drawn across the placeholder, e.g. \"Panel 1 · 1400 × 1000\". */\n label: string;\n width: number;\n height: number;\n /** Alt text for the document. Defaults to the label. */\n alt?: string;\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\");\n}\n\n/**\n * A placeholder upload document for a block sample.\n *\n * A preview has no site media behind it, so the image has to travel with the\n * sample. This is an inline SVG as a `data:` URL, in the `MediaDoc` shape\n * `resolveMedia` already narrows, with `width` and `height` set so an image\n * adapter can reserve the box. The scheme is what tells an adapter which kind\n * of source it has: `next/image` treats a `data:` src as `unoptimized` on its\n * own, so a site's adapter needs no special case for samples.\n *\n * Neutral greys rather than theme tokens: the SVG is a standalone document\n * and cannot see the page's custom properties.\n */\nexport function sampleImage({\n label,\n width,\n height,\n alt,\n}: SampleImageOptions): MediaDoc {\n const fontSize = Math.max(12, Math.round(Math.min(width, height) / 12));\n const svg =\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\">` +\n `<rect width=\"100%\" height=\"100%\" fill=\"#d4d4d8\"/>` +\n `<text x=\"50%\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"middle\" ` +\n `font-family=\"system-ui, sans-serif\" font-size=\"${fontSize}\" fill=\"#52525b\">` +\n `${escapeXml(label)}</text></svg>`;\n return {\n url: `data:image/svg+xml,${encodeURIComponent(svg)}`,\n alt: alt ?? label,\n width,\n height,\n };\n}\n","import { sampleImage } from \"../../sample-image\";\nimport type { HeroBlockData } from \"../../types\";\n\n/**\n * The placeholder hero's sample. The hero family (BIS-45) ships a sample per\n * hero as each one is added; this is the one for the plain renderer.\n */\nexport const heroSample: HeroBlockData = {\n id: \"sample-hero\",\n blockType: \"hero\",\n eyebrow: \"Now booking spring appointments\",\n heading: \"Move well again, without the long wait\",\n body: \"Same-week assessments with a physiotherapist who stays with you from the first visit to the last. Most treatment plans finish in six to eight sessions.\",\n tone: \"sweep\",\n image: sampleImage({\n label: \"Hero background · 2000 × 1000\",\n width: 2000,\n height: 1000,\n alt: \"A bright, open treatment room\",\n }),\n // One link per shape: a page, carried inline as `depth: 1` would populate\n // it, so the preview needs no pages collection; and an external URL.\n links: [\n {\n type: \"page\",\n page: {\n id: \"sample-page-book\",\n title: \"Book an assessment\",\n slug: \"book\",\n _status: \"published\",\n },\n label: \"Book an assessment\",\n newTab: false,\n },\n {\n type: \"external\",\n href: \"https://example.com/approach\",\n label: \"See our approach\",\n newTab: true,\n },\n ],\n};\n","import type { MegaMenuBlockData, NavLinkBlockData } from \"../../types\";\nimport type { MegaMenuBlockOptions } from \"./config\";\n\n/**\n * The options the mega menu sample was written against. A factory block's\n * sample only fits a config built with the same approved values, so a\n * preview builds its block with `megaMenuBlock(megaMenuSampleOptions)`.\n */\nexport const megaMenuSampleOptions = {\n variants: [\n { label: \"Lumbar\", value: \"lumbar\" },\n { label: \"SI joint\", value: \"si\" },\n ],\n icons: [\n { label: \"Help\", value: \"help\" },\n { label: \"Mail\", value: \"mail\" },\n ],\n} satisfies MegaMenuBlockOptions;\n\n/**\n * Spinal Simplicity's Patients panel as a saved row: two featured territories\n * in a 75% column, two rail sections in a divided 25% column, overview and\n * CTA. Every field is set at least once; the Advanced fields are set but off,\n * so the percent widths are what renders. Most links are site paths typed as\n * external links; Testimonials and the overview are page links carried\n * inline, as `depth: 1` would populate them, so the preview needs no pages\n * collection.\n */\nexport const megaMenuSample: MegaMenuBlockData = {\n id: \"sample-mega-menu\",\n blockType: \"megaMenu\",\n label: \"Patients\",\n href: \"/patients\",\n panel: {\n maxWidth: \"standard\",\n columns: [\n {\n id: \"treatment\",\n width: \"75\",\n customWidth: \"minmax(0, 1fr)\",\n divider: false,\n sections: [\n {\n id: \"territories\",\n eyebrow: \"Treatment\",\n display: \"featured\",\n hideDescriptionsOnMobile: true,\n links: [\n {\n id: \"lumbar\",\n label: \"Low Back Pain\",\n type: \"external\",\n href: \"/patients/low-back-pain\",\n newTab: false,\n description:\n \"Persistent low back pain from lumbar instability, often with leg pain that limits standing or walking.\",\n variant: \"lumbar\",\n },\n {\n id: \"si\",\n label: \"Hip Pain\",\n type: \"external\",\n href: \"/patients/hip-pain\",\n newTab: false,\n description:\n \"Pain centered on the sacroiliac joint, often worse with sitting or climbing stairs.\",\n variant: \"si\",\n },\n ],\n },\n ],\n },\n {\n id: \"rail\",\n width: \"25\",\n customWidth: \"13rem\",\n divider: true,\n sections: [\n {\n id: \"outcomes\",\n eyebrow: \"Patient Outcomes\",\n display: \"list\",\n hideDescriptionsOnMobile: true,\n links: [\n {\n id: \"stories\",\n label: \"Testimonials\",\n type: \"page\",\n page: {\n id: \"sample-page-stories\",\n title: \"Testimonials\",\n slug: \"patients/stories\",\n _status: \"published\",\n },\n newTab: false,\n },\n { id: \"research\", label: \"Research\", type: \"external\", href: \"/patients/research\", newTab: false },\n { id: \"path\", label: \"Path to Relief\", type: \"external\", href: \"/patients/path-to-relief\", newTab: false },\n ],\n },\n {\n id: \"support\",\n eyebrow: \"Support\",\n display: \"list\",\n hideDescriptionsOnMobile: false,\n links: [\n {\n id: \"faqs\",\n label: \"FAQs\",\n type: \"external\",\n href: \"/patients/faqs\",\n newTab: false,\n description: \"Short answers to the questions patients ask first.\",\n icon: \"help\",\n },\n {\n id: \"contact\",\n label: \"Contact\",\n type: \"external\",\n href: \"https://example.com/contact\",\n newTab: true,\n icon: \"mail\",\n },\n ],\n },\n ],\n },\n ],\n footer: {\n overview: {\n label: \"All patient resources\",\n type: \"page\",\n page: { id: \"sample-page-patients\", title: \"Patients\", slug: \"patients\", _status: \"published\" },\n newTab: false,\n },\n cta: { label: \"Find a Doctor\", type: \"external\", href: \"/find-a-doctor\", newTab: false },\n },\n customWidths: false,\n customMaxWidth: \"42rem\",\n },\n};\n\n/** A plain bar item. */\nexport const navLinkSample: NavLinkBlockData = {\n id: \"sample-nav-link\",\n blockType: \"navLink\",\n label: \"Contact\",\n type: \"external\",\n href: \"/contact\",\n newTab: false,\n};\n","import type { NapBlockData } from \"../../types\";\n\n/**\n * A fictional clinic. `emitJsonLd` is off: the block emits schema.org\n * `LocalBusiness` by default, and a preview must not put a business that does\n * not exist into a site's structured data. The phone numbers are in the\n * 555-01xx range reserved for fiction.\n */\nexport const napSample: NapBlockData = {\n id: \"sample-nap\",\n blockType: \"nap\",\n businessName: \"Larkspur Physiotherapy\",\n businessType: \"Physiotherapy\",\n address: {\n streetAddress: \"400 Larkspur Lane, Suite 120\",\n addressLocality: \"Boulder\",\n addressRegion: \"CO\",\n postalCode: \"80302\",\n addressCountry: \"US\",\n },\n departments: [\n {\n id: \"sample-dept-front-desk\",\n name: \"Front desk\",\n phoneE164: \"+13035550142\",\n phoneDisplay: \"(303) 555-0142\",\n },\n {\n id: \"sample-dept-billing\",\n name: \"Billing and insurance\",\n departmentType: \"AccountingService\",\n phoneE164: \"+13035550143\",\n },\n ],\n url: \"https://example.com\",\n showName: true,\n headingLevel: \"h2\",\n emitJsonLd: false,\n};\n","import { sampleImage } from \"../../sample-image\";\nimport type { ProcessStepsBlockData } from \"../../types\";\n\n/** Four steps: enough to show the rail advancing through a real sequence. */\nexport const processStepsSample: ProcessStepsBlockData = {\n id: \"sample-process-steps\",\n blockType: \"processSteps\",\n items: [\n {\n id: \"sample-step-assess\",\n title: \"Assessment\",\n description:\n \"A fifty-minute first visit: your history, a movement screen, and a clear explanation of what we found.\",\n image: sampleImage({\n label: \"Step 1 · 1600 × 1000\",\n width: 1600,\n height: 1000,\n alt: \"A physiotherapist taking notes during an assessment\",\n }),\n },\n {\n id: \"sample-step-plan\",\n title: \"Your plan\",\n description:\n \"A written plan with the number of sessions we expect, what each one is for, and the exercises between them.\",\n image: sampleImage({\n label: \"Step 2 · 1600 × 1000\",\n width: 1600,\n height: 1000,\n alt: \"A printed treatment plan on a desk\",\n }),\n },\n {\n id: \"sample-step-treat\",\n title: \"Treatment\",\n description:\n \"Hands-on work where it helps, and progressive loading where it matters. You leave every session knowing what to do next.\",\n image: sampleImage({\n label: \"Step 3 · 1600 × 1000\",\n width: 1600,\n height: 1000,\n alt: \"A patient lifting a light kettlebell under supervision\",\n }),\n },\n {\n id: \"sample-step-discharge\",\n title: \"Discharge and beyond\",\n description:\n \"A final review, a maintenance programme, and an open door if anything flares up later.\",\n image: sampleImage({\n label: \"Step 4 · 1600 × 1000\",\n width: 1600,\n height: 1000,\n alt: \"A patient walking out of the clinic\",\n }),\n },\n ],\n autoAdvance: true,\n autoAdvanceDuration: 5000,\n pauseOnHover: true,\n defaultActiveIndex: 0,\n};\n","import type { RichTextBlockData, RichTextContent } from \"../../types\";\n\n/**\n * A serialized Lexical document, written by hand in the shapes the default\n * JSX converters read (`heading`, `paragraph`, `text`, `list`, `link`).\n * `version` is on every serialized Lexical node, and `direction`, `format` and\n * `indent` are what `RichTextContent` requires on the root; the converters\n * read none of them, and the sample carries them so it is shaped like a row\n * the editor saved.\n */\nconst block = {\n direction: \"ltr\" as const,\n format: \"\",\n indent: 0,\n version: 1,\n};\n\nfunction text(value: string, format = 0) {\n return {\n type: \"text\",\n text: value,\n format,\n detail: 0,\n mode: \"normal\",\n style: \"\",\n version: 1,\n };\n}\n\nfunction paragraph(children: unknown[]) {\n return { type: \"paragraph\", children, textFormat: 0, textStyle: \"\", ...block };\n}\n\nconst BOLD = 1;\n\nconst content: RichTextContent = {\n root: {\n type: \"root\",\n ...block,\n children: [\n {\n type: \"heading\",\n tag: \"h2\",\n children: [text(\"What to expect at your first visit\")],\n ...block,\n },\n paragraph([\n text(\"Your first appointment runs about \"),\n text(\"fifty minutes\", BOLD),\n text(\n \". We start with a conversation about what brought you in, then a movement assessment, and you leave with a written plan and the first two exercises.\",\n ),\n ]),\n paragraph([\n text(\"Bring comfortable clothes and any recent imaging. If you have questions before you arrive, \"),\n {\n type: \"link\",\n fields: { url: \"https://example.com/contact\", newTab: true, linkType: \"custom\" },\n children: [text(\"get in touch\")],\n ...block,\n version: 3,\n },\n text(\".\"),\n ]),\n {\n type: \"list\",\n listType: \"bullet\",\n tag: \"ul\",\n start: 1,\n children: [\n \"Assessment and written plan\",\n \"Hands-on treatment where it helps\",\n \"Exercises you can do at home, with video\",\n ].map((item, i) => ({\n type: \"listitem\",\n value: i + 1,\n children: [text(item)],\n ...block,\n })),\n ...block,\n },\n ],\n },\n};\n\nexport const richTextSample: RichTextBlockData = {\n id: \"sample-rich-text\",\n blockType: \"richText\",\n content,\n};\n","import { sampleImage } from \"../../sample-image\";\nimport type { ShowcasePanelsBlockData } from \"../../types\";\n\n/** Three panels: the minimum the layout is designed around. */\nexport const showcasePanelsSample: ShowcasePanelsBlockData = {\n id: \"sample-showcase-panels\",\n blockType: \"showcasePanels\",\n items: [\n {\n id: \"sample-panel-back\",\n title: \"Back and neck pain\",\n summary:\n \"Most back pain settles with the right movement, not rest. We find what is driving yours and build a plan around your week, not ours.\",\n image: sampleImage({\n label: \"Panel 1 · 1400 × 1000\",\n width: 1400,\n height: 1000,\n alt: \"A physiotherapist guiding a patient through a stretch\",\n }),\n href: \"/services/back-and-neck\",\n // Matches what automatic numbering would show, so the preview does not\n // mislead; it is here to exercise the override.\n numeral: \"01\",\n },\n {\n id: \"sample-panel-sport\",\n title: \"Sports injuries\",\n summary:\n \"From a rolled ankle to a post-surgical knee: a return-to-play plan with clear milestones, so you know when you are ready.\",\n image: sampleImage({\n label: \"Panel 2 · 1400 × 1000\",\n width: 1400,\n height: 1000,\n alt: \"A runner mid-stride on a track\",\n }),\n href: \"/services/sports-injuries\",\n },\n {\n id: \"sample-panel-post-op\",\n title: \"Post-operative rehab\",\n summary:\n \"We work from your surgeon's protocol and keep them in the loop, so every stage of recovery is signed off before the next begins.\",\n image: sampleImage({\n label: \"Panel 3 · 1400 × 1000\",\n width: 1400,\n height: 1000,\n alt: \"A patient on a rehabilitation bike\",\n }),\n },\n ],\n spineVariant: \"numbered\",\n watermark: \"LARKSPUR\",\n defaultActiveIndex: 0,\n};\n","import type { StatsBandBlockData } from \"../../types\";\n\n/**\n * Four figures, the count the strip was designed around: one row on a\n * desktop, two by two on a tablet and a phone. `wide` is set (to `false`) so\n * the preview exercises the field, and an even count is where it is ignored.\n * `overlap` is off so the heading shows: a floating band is headless, and a\n * preview page has no hero for it to float over.\n */\nexport const statsBandSample: StatsBandBlockData = {\n id: \"sample-stats-band\",\n blockType: \"statsBand\",\n eyebrow: \"At a glance\",\n title: \"The clinic in numbers\",\n description:\n \"What a year looks like across our two rooms, counted from the front desk rather than estimated.\",\n items: [\n {\n id: \"sample-stat-pain\",\n value: \"94%\",\n label: \"of patients report less pain by their sixth session.\",\n href: \"/outcomes\",\n wide: false,\n },\n {\n id: \"sample-stat-visits\",\n value: \"4,200+\",\n label: \"appointments a year across two treatment rooms.\",\n },\n {\n id: \"sample-stat-wait\",\n value: \"15 min\",\n label: \"average wait from the front desk to the treatment room.\",\n },\n {\n id: \"sample-stat-years\",\n value: \"12\",\n label: \"years in the same building, on the same street.\",\n },\n ],\n columns: \"4\",\n tone: \"card\",\n align: \"start\",\n overlap: false,\n};\n","import { sampleImage } from \"../../sample-image\";\nimport type { TestimonialMasonryBlockData } from \"../../types\";\n\nfunction avatar(name: string) {\n return sampleImage({\n label: name\n .split(\" \")\n .map((part) => part[0])\n .join(\"\"),\n width: 160,\n height: 160,\n alt: `Portrait of ${name}`,\n });\n}\n\n/**\n * Six quotes, two of them without an avatar so the initials fallback shows.\n * `minItemsForFade` is lowered to six so the preview also shows the fade and\n * the link beneath it, which the default of seven would hide at this count.\n */\nexport const testimonialMasonrySample: TestimonialMasonryBlockData = {\n id: \"sample-testimonial-masonry\",\n blockType: \"testimonialMasonry\",\n eyebrow: \"From our patients\",\n title: \"What people say after they finish\",\n description:\n \"Every review here was left by a patient we discharged. We do not edit them.\",\n items: [\n {\n id: \"sample-quote-1\",\n content:\n \"I had written off running. Eight weeks later I did a parkrun, and the plan I was given actually fitted around a job and two kids.\",\n author: {\n name: \"Priya Natarajan\",\n title: \"Recovered from a hamstring tear\",\n avatar: avatar(\"Priya Natarajan\"),\n },\n },\n {\n id: \"sample-quote-2\",\n content:\n \"The first physio who explained what was wrong in words I understood.\",\n author: {\n name: \"Tom Ferreira\",\n title: \"Lower back pain\",\n avatar: avatar(\"Tom Ferreira\"),\n },\n },\n {\n id: \"sample-quote-3\",\n content:\n \"My surgeon said my knee was ahead of schedule at every check-in. That was the rehab, not me.\",\n author: { name: \"Aisha Bello\", title: \"ACL reconstruction\" },\n },\n {\n id: \"sample-quote-4\",\n content:\n \"Booked on a Tuesday, seen on the Thursday. The shoulder I had put up with for a year was sorted in six visits.\",\n author: {\n name: \"Marcus Whitfield\",\n title: \"Frozen shoulder\",\n avatar: avatar(\"Marcus Whitfield\"),\n },\n },\n {\n id: \"sample-quote-5\",\n content:\n \"They kept my consultant in the loop the whole way through, which nobody else had bothered to do.\",\n author: { name: \"Helen Ostrowski\" },\n },\n {\n id: \"sample-quote-6\",\n content:\n \"Honest about what would take time and what would not. I would send my parents here.\",\n author: {\n name: \"Daniel Kim\",\n title: \"Ankle sprain\",\n avatar: avatar(\"Daniel Kim\"),\n },\n },\n ],\n link: {\n type: \"external\",\n href: \"https://example.com/reviews\",\n label: \"Read every review\",\n newTab: true,\n },\n minItemsForFade: 6,\n maxVisibleRows: 2,\n};\n","import { faqColumnsSample } from \"./blocks/faq-columns/sample\";\nimport { heroSample } from \"./blocks/hero/sample\";\nimport { megaMenuSample, navLinkSample } from \"./blocks/mega-menu/sample\";\nimport { napSample } from \"./blocks/nap/sample\";\nimport { processStepsSample } from \"./blocks/process-steps/sample\";\nimport { richTextSample } from \"./blocks/rich-text/sample\";\nimport { showcasePanelsSample } from \"./blocks/showcase-panels/sample\";\nimport { statsBandSample } from \"./blocks/stats-band/sample\";\nimport { testimonialMasonrySample } from \"./blocks/testimonial-masonry/sample\";\nimport type { BisonBlockData, BisonBlockType } from \"./types\";\n\n/**\n * One sample row per block, each typed to its own row shape. Assignable to\n * `Record<BisonBlockType, BisonBlockData>` wherever the wider type is wanted.\n */\nexport type BlockSamples = {\n [K in BisonBlockType]: Extract<BisonBlockData, { blockType: K }>;\n};\n\n/**\n * Every block the package ships, rendered without a CMS row.\n *\n * A Block library page (BIS-52) renders these live so an admin can see each\n * block before enabling it. Each sample lives beside its block's `config.ts`\n * and `component.tsx` as `sample.ts`, and `src/__tests__/samples.test.tsx`\n * locks the contract: one entry per slug, `blockType` matching the key, every\n * config field exercised at least once, and every image self-contained.\n *\n * React-free on purpose: it is read from a Global's field config, which\n * Payload loads in plain Node like the rest of this entry, as well as from a\n * route handler. Only what this package ships is here; a site supplies samples\n * for its own blocks through the Block library factory, whose option BIS-52\n * names.\n */\nexport const blockSamples: BlockSamples = {\n hero: heroSample,\n richText: richTextSample,\n showcasePanels: showcasePanelsSample,\n processSteps: processStepsSample,\n faqColumns: faqColumnsSample,\n testimonialMasonry: testimonialMasonrySample,\n nap: napSample,\n statsBand: statsBandSample,\n // Header blocks: rendered by `MegaMenuBlockRenderer` and `headerItemsFromBlocks`,\n // never by `RenderBlocks`. The mega menu sample fits `megaMenuBlock(megaMenuSampleOptions)`.\n megaMenu: megaMenuSample,\n navLink: navLinkSample,\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/fields/row-limits.ts","../src/blocks/stats-band/config.ts","../src/blocks/mega-menu/rem-width.ts","../src/blocks/mega-menu/config.ts","../src/media.ts","../src/blocks/faq-columns/sample.ts","../src/sample-image.ts","../src/blocks/hero/sample.ts","../src/blocks/mega-menu/sample.ts","../src/blocks/nap/sample.ts","../src/blocks/process-steps/sample.ts","../src/blocks/rich-text/sample.ts","../src/blocks/showcase-panels/sample.ts","../src/blocks/stats-band/sample.ts","../src/blocks/testimonial-masonry/sample.ts","../src/samples.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 { Condition, Field, GroupField, RowField } from \"payload\";\n\n/**\n * The import-map path of the link picker, `LinkField` in\n * `@bison-lab/payload-blocks/admin`.\n *\n * It sits on the row that holds a link's `type`, `page` and `href`, and\n * replaces those three inputs with one box: type to search published pages,\n * or paste a URL. Referenced here by package specifier, like\n * `MIN_ROWS_ARRAY_FIELD`; a site picks it up with `payload generate:importmap`.\n * Until it does, Payload logs the missing entry and renders the row as\n * nothing (a custom `Field` with no component behind it is an empty element,\n * not the stock fields), so the step is part of adopting this release.\n */\nexport const LINK_FIELD = \"@bison-lab/payload-blocks/admin#LinkField\";\n\nexport type LinkType = \"page\" | \"external\";\n\n/**\n * A page as the `page` relationship populates it: what `resolveLink` reads,\n * and what the picker shows. A site's generated `Page` is assignable to it.\n * `_status` is absent on a collection without drafts, which counts as\n * published.\n */\nexport interface LinkPageDoc {\n id: number | string;\n title?: string | null;\n slug?: string | null;\n _status?: (\"draft\" | \"published\") | null;\n}\n\n/** Where a link goes: the three fields the picker writes. */\nexport interface LinkDestination {\n /** `page` unless the editor pasted a URL. Rows saved before links had a type carry only `href`. */\n type?: LinkType | null;\n /** The page's id, or the page itself once a `depth >= 1` read populates it. */\n page?: LinkPageDoc | number | string | null;\n /** The pasted URL of an external link. */\n href?: string | null;\n}\n\n/** The shape `linkField`/`linkFields` produce once Payload generates types. */\nexport interface LinkValue extends LinkDestination {\n label?: string | null;\n newTab?: boolean | null;\n}\n\n/**\n * Turns a link's destination into the `href` a renderer emits, or `null`\n * when there is nothing to point at, in which case the renderer shows the\n * label as text. A site passes its own (`RenderBlocks`' `resolveLink`) when\n * a page's path is not `/<slug>`: the package has no way to know a site's\n * routes, the same reason it takes an `imageComponent`.\n */\nexport type ResolveLink = (link: LinkDestination) => string | null;\n\n/**\n * The package's resolver: a published, populated page is `/<slug>`; an\n * external link is its `href` as typed.\n *\n * A page that did not populate is `null`: Payload hands the public read the\n * bare id when the reader may not see the page, which is what an unpublished\n * page looks like from the site. A populated page whose `_status` is `draft`\n * is `null` too, for a read that can see drafts. A row from before links had\n * a `type` is an external link by `linkTypeOf`, here, in the stock fields'\n * conditions and in the picker alike: the site keeps rendering its `href`,\n * the admin shows it as an External chip, and the site's migration to\n * `type: external` only makes the stored row say what every reader already\n * assumes.\n */\nexport function resolveLink(\n link: LinkDestination | null | undefined,\n): string | null {\n if (!link) return null;\n if (linkTypeOf(link) === \"page\") {\n const page = link.page;\n if (!page || typeof page !== \"object\") return null;\n if (page._status === \"draft\") return null;\n return page.slug ? `/${page.slug}` : null;\n }\n return link.href ? link.href : null;\n}\n\n/**\n * Which of the two stored destinations a row is using. `type` decides; a row\n * with no `type` yet (saved before links had one) is read by what it holds,\n * an `href` making it external, the same reading `resolveLink` and the picker\n * make, so all three agree on every row.\n */\nexport function linkTypeOf(link: LinkDestination | null | undefined): LinkType {\n if (link?.type === \"external\") return \"external\";\n if (link?.type === \"page\") return \"page\";\n return link?.href ? \"external\" : \"page\";\n}\n\nconst whenPage: Condition = (_data, siblingData) =>\n linkTypeOf(siblingData as LinkDestination) === \"page\";\n\nconst whenExternal: Condition = (_data, siblingData) =>\n linkTypeOf(siblingData as LinkDestination) === \"external\";\n\nexport interface LinkDestinationOptions {\n /** Require a destination. Defaults to `false`. */\n required?: boolean;\n /** The collection a page link points into. Defaults to `pages`. */\n pagesCollection?: string;\n}\n\n/**\n * Where a link goes, as stored: `type`, `page` and `href` in one row.\n *\n * The row carries the picker (`LINK_FIELD`), which replaces all three inputs\n * with one box. The stored fields are the website template's shape and stay\n * editable on their own: `page` shows for a page link, `href` for an external\n * one, and `required` binds whichever is active, since Payload skips\n * validation on a field whose condition is false. `page` offers published\n * rows only, and Payload re-checks that on publish, so a page that was\n * unpublished after being picked has to be picked again or republished.\n */\nexport function linkDestinationFields({\n required = false,\n pagesCollection = \"pages\",\n}: LinkDestinationOptions = {}): RowField[] {\n return [\n {\n type: \"row\",\n admin: { components: { Field: LINK_FIELD } },\n fields: [\n {\n name: \"type\",\n type: \"radio\",\n label: \"Goes to\",\n defaultValue: \"page\",\n options: [\n { label: \"Page\", value: \"page\" },\n { label: \"URL\", value: \"external\" },\n ],\n admin: { layout: \"horizontal\" },\n },\n {\n name: \"page\",\n type: \"relationship\",\n relationTo: pagesCollection,\n required,\n filterOptions: { _status: { equals: \"published\" } },\n admin: { condition: whenPage },\n },\n {\n name: \"href\",\n type: \"text\",\n label: \"URL\",\n required,\n admin: {\n condition: whenExternal,\n description:\n 'A full URL (\"https://example.com\"), \"mailto:\" or \"tel:\", or a site path (\"/contact\").',\n },\n },\n ],\n },\n ];\n}\n\nexport interface LinkFieldsOptions extends LinkDestinationOptions {\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 fields a call to action is made of, unwrapped: the destination row,\n * then the label and the new-tab flag.\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 pagesCollection,\n}: LinkFieldsOptions = {}): Field[] {\n return [\n ...linkDestinationFields({ required, pagesCollection }),\n { name: \"label\", type: \"text\", label: labelFieldLabel, required },\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","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 { Condition, 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 * Show the three fields only when this holds, e.g. unless the block floats.\n * Pair it with `required: false`: a hidden required field can never be\n * satisfied, and the block could not publish.\n */\n condition?: Condition;\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 condition,\n}: HeadingFieldsOptions = {}): Field[] {\n const when = condition ? { condition } : {};\n return [\n {\n name: \"eyebrow\",\n type: \"text\",\n admin: { description: eyebrowDescription, ...when },\n },\n { name: \"title\", type: \"text\", required, admin: when },\n { name: \"description\", type: \"textarea\", admin: when },\n ];\n}\n","import type { Block } from \"payload\";\n\nimport { headingFields } from \"../../fields/heading\";\nimport { linkDestinationFields } from \"../../fields/link\";\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 // The destination is `linkFields()`' shape (the picker, a page or a\n // URL); only the visible text keeps its own name here.\n ...linkDestinationFields(),\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","import type { ArrayFieldValidation } from \"payload\";\n\n/**\n * A custom `validate` replaces Payload's stock array check, so any array\n * with its own rule re-applies the row limits first or `minRows` and\n * `maxRows` stop being enforced. Same messages as the stock check, through\n * the request's translator when the admin supplies one.\n */\nexport function rowLimits(\n value: unknown[] | null | undefined,\n { minRows, maxRows, required, req }: Parameters<ArrayFieldValidation>[1],\n): string | true {\n const count = value?.length ?? 0;\n const t = req?.t;\n if (required && count === 0) {\n return t ? t(\"validation:required\") : \"This field is required.\";\n }\n if (minRows && count < minRows) {\n return t\n ? t(\"validation:requiresAtLeast\", {\n count: minRows,\n label: t(\"general:rows\"),\n })\n : `This field requires at least ${minRows} rows.`;\n }\n if (maxRows && count > maxRows) {\n return t\n ? t(\"validation:requiresNoMoreThan\", {\n count: maxRows,\n label: t(\"general:rows\"),\n })\n : `This field requires no more than ${maxRows} rows.`;\n }\n return true;\n}\n","import type { ArrayFieldValidation, Block, Condition } from \"payload\";\n\nimport { headingFields } from \"../../fields/heading\";\nimport { emptyRows, MIN_ROWS_ARRAY_FIELD } from \"../../fields/min-rows\";\nimport { rowLimits } from \"../../fields/row-limits\";\n\n/**\n * The rule on `wide`: on phones the band is two per row, so only an odd\n * count has a stat on a row of its own, only one stat can take it, and it\n * has to be a stat that starts a row (the 1st, 3rd or 5th), since a full-row\n * cell beside another cell would leave a hole. The array's own `validate`,\n * so the editor sees the message as they build, and Payload runs it again on\n * publish.\n */\nexport const validateWideFlag: ArrayFieldValidation = (value, options) => {\n const limits = rowLimits(value, options);\n if (limits !== true) return limits;\n const rows = (value ?? []) as { wide?: boolean | null }[];\n const wide = rows.flatMap((row, i) => (row?.wide ? [i] : []));\n if (wide.length > 1) return \"Only one stat can be full width on phones.\";\n if (wide.length === 0) return true;\n if (rows.length % 2 === 0) {\n return \"Full width on phones only applies to an odd number of stats. With an even count every row is already full.\";\n }\n if (wide[0] % 2 !== 0) {\n return \"Only a stat that starts a phone row can be full width: the 1st, 3rd or 5th. Move it up or down one place, or flag another.\";\n }\n return true;\n};\n\n/**\n * An overlapping band is headless: it floats across the seam as a card, and a\n * heading pulled up into the block above would land on the wrong surface.\n * The heading fields disappear from the admin the moment `overlap` is on,\n * and the renderer ignores them if they were filled in first.\n */\nexport const unlessOverlap: Condition = (_data, siblingData) =>\n !(siblingData as { overlap?: boolean | null })?.overlap;\n\n/**\n * A strip of figures: the facts a visitor should take in at a glance.\n *\n * The count, the order and the desktop column count are the editor's. The\n * phone and tablet layouts are the library's: two per row and three per row,\n * with `wide` naming the one stat that takes a full phone row when the count\n * is odd.\n */\nexport const StatsBandBlock: Block = {\n slug: \"statsBand\",\n interfaceName: \"StatsBandBlock\",\n labels: { singular: \"Stats Band\", plural: \"Stats Bands\" },\n fields: [\n ...headingFields({\n required: false,\n eyebrowDescription:\n \"Small label above the heading. The heading and eyebrow are optional; without them the band stands alone.\",\n condition: unlessOverlap,\n }),\n {\n name: \"items\",\n type: \"array\",\n required: true,\n // Designed around four, workable from two: the block opens at its\n // minimum, the house rule for every array here, and the admin field\n // refuses to go below it.\n minRows: 2,\n maxRows: 6,\n defaultValue: emptyRows(2),\n labels: { singular: \"Stat\", plural: \"Stats\" },\n admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } },\n validate: validateWideFlag,\n fields: [\n {\n name: \"value\",\n type: \"text\",\n required: true,\n admin: {\n description:\n 'The figure, as it should read: \"98%\", \"12,000+\", \"L1–S1\".',\n },\n },\n { name: \"label\", type: \"textarea\", required: true },\n {\n name: \"href\",\n type: \"text\",\n admin: {\n description:\n 'Optional. Makes the whole stat a link: a site path (\"/outcomes\") or a full URL.',\n },\n },\n {\n name: \"wide\",\n type: \"checkbox\",\n label: \"Full width on phones\",\n defaultValue: false,\n admin: {\n description:\n \"Phones show two per row. With an odd number of stats, this one takes a row to itself. Only the 1st, 3rd or 5th stat can.\",\n },\n },\n ],\n },\n {\n name: \"columns\",\n type: \"select\",\n defaultValue: \"4\",\n options: [\n { label: \"2\", value: \"2\" },\n { label: \"3\", value: \"3\" },\n { label: \"4\", value: \"4\" },\n { label: \"5\", value: \"5\" },\n { label: \"6\", value: \"6\" },\n ],\n admin: {\n description:\n \"From large screens up, and never more than there are stats. Phones show two per row and tablets three.\",\n },\n },\n {\n name: \"tone\",\n type: \"select\",\n defaultValue: \"card\",\n options: [\n { label: \"Card (bordered, raised)\", value: \"card\" },\n { label: \"Plain (dividers only)\", value: \"plain\" },\n ],\n },\n {\n name: \"align\",\n type: \"select\",\n defaultValue: \"start\",\n options: [\n { label: \"Left\", value: \"start\" },\n { label: \"Centred\", value: \"center\" },\n ],\n },\n {\n name: \"overlap\",\n type: \"checkbox\",\n label: \"Float over the seam with the block above\",\n defaultValue: false,\n admin: {\n description:\n \"The band sits across the join between the block above and the block below. Both make room for it automatically. A floating band is headless: the heading fields above are hidden while this is on.\",\n },\n },\n ],\n};\n","/**\n * The one rule for a typed panel width, shared by the config's validator and\n * the renderer's reader so the two cannot drift: if the validator accepted a\n * value the renderer dropped, the editor would be back to a width that\n * silently does nothing. React-free, because `config.ts` ships from the Node\n * entry.\n */\nconst REM_WIDTH = /^\\s*(\\d+(?:\\.\\d+)?)\\s*(rem)?\\s*$/;\n\n/** \"30\" and \"30rem\" both mean 30rem; anything else is not a width. */\nexport function remWidth(\n value: string | null | undefined,\n): `${number}rem` | null {\n const match = REM_WIDTH.exec(value ?? \"\");\n return match ? (`${Number(match[1])}rem` as `${number}rem`) : null;\n}\n","import type {\n ArrayFieldValidation,\n Block,\n Condition,\n Field,\n SelectField,\n TextFieldSingleValidation,\n} from \"payload\";\n\nimport { linkField, linkFields } from \"../../fields/link\";\nimport { emptyRows, MIN_ROWS_ARRAY_FIELD } from \"../../fields/min-rows\";\nimport { rowLimits } from \"../../fields/row-limits\";\nimport { remWidth } from \"./rem-width\";\n\n/**\n * The blocks a header global's `items` array takes.\n *\n * `megaMenuBlock({ variants, icons })` is the first factory-built block in the\n * package. Every other config is a static `Block`; this one cannot be, because\n * the featured-link variants and the icon list are the site's: the CMS only\n * ever offers approved values, the same rule the sites use for block\n * backgrounds. `LinkBlock` is the plain bar item beside it.\n *\n * Until SPI-52 lands in the Spinal Simplicity repo, no site renders its header\n * from these rows; the Storybook `CMS Blocks/MegaMenu` fixture is the only\n * header built from them.\n */\n\n/** One approved value, as a select option. */\nexport interface MegaMenuOption {\n label: string;\n value: string;\n}\n\nexport interface MegaMenuBlockOptions {\n /** Looks a featured link can take. Empty or absent drops the select. */\n variants?: MegaMenuOption[];\n /** Glyphs a list link can carry. Empty or absent drops the select. */\n icons?: MegaMenuOption[];\n}\n\n/** The percent tokens a column can take, as Payload stores them. */\nexport const MEGA_MENU_WIDTHS = [\"25\", \"33\", \"50\", \"66\", \"75\", \"100\"] as const;\nexport type MegaMenuWidthValue = (typeof MEGA_MENU_WIDTHS)[number];\n\n/** Twelfths, so 33 and 66 are real thirds and 33 + 33 + 33 is a full row. */\nconst TWELFTHS: Record<MegaMenuWidthValue, number> = {\n \"25\": 3,\n \"33\": 4,\n \"50\": 6,\n \"66\": 8,\n \"75\": 9,\n \"100\": 12,\n};\n\ninterface ColumnRow {\n width?: MegaMenuWidthValue | null;\n}\n\n/**\n * The rule on `columns`: read as fractions, the widths must total 100%. It is\n * the array's own `validate`, so the editor sees the message as they build,\n * and Payload runs field validation again on publish (only draft saves skip\n * it), so nothing publishes with a short row. Skipped when Advanced has custom\n * widths on.\n */\nexport const validateColumnWidths: ArrayFieldValidation = (value, options) => {\n const limits = rowLimits(value, options);\n if (limits !== true) return limits;\n const siblingData = options.siblingData as { customWidths?: boolean | null };\n if (siblingData?.customWidths || !value?.length) return true;\n\n const twelfths = (value as ColumnRow[]).reduce(\n (sum, row) => sum + TWELFTHS[row?.width ?? \"100\"],\n 0,\n );\n if (twelfths === 12) return true;\n const percent = Math.round((twelfths / 12) * 100);\n return `The columns add up to ${percent}%. They need to add up to 100%.`;\n};\n\n/**\n * The row `levels` segments above the field at `path`, read off the whole\n * document. A condition on a link needs its section; a condition on a column\n * needs its panel. `data` carries the full form, so the path is enough.\n */\nfunction ancestor(\n data: Record<string, unknown>,\n path: (number | string)[],\n levels: number,\n): Record<string, unknown> | undefined {\n let node: unknown = data;\n for (const segment of path.slice(0, path.length - levels)) {\n if (node === null || typeof node !== \"object\") return undefined;\n node = (node as Record<string, unknown>)[segment];\n }\n return node !== null && typeof node === \"object\"\n ? (node as Record<string, unknown>)\n : undefined;\n}\n\n/** `links.N.variant` → its section, three segments up. */\nconst whenSectionFeatured: Condition = (data, _siblingData, { path }) =>\n ancestor(data, path, 3)?.display === \"featured\";\n\n/** A featured link's glyph comes from its variant, so `icon` is a list link's field. */\nconst unlessSectionFeatured: Condition = (data, siblingData, ctx) =>\n !whenSectionFeatured(data, siblingData, ctx);\n\n/** The renderer would fall back to the preset silently; the editor should hear it here instead. */\nexport const validateRemWidth: TextFieldSingleValidation = (value) =>\n !value || remWidth(value) !== null\n ? true\n : \"Type a width in rem, like 30 or 30rem.\";\n\n/** `columns.N.width` → its panel, three segments up. */\nconst whenCustomWidths: Condition = (data, _siblingData, { path }) =>\n Boolean(ancestor(data, path, 3)?.customWidths);\n\nconst unlessCustomWidths: Condition = (data, siblingData, ctx) =>\n !whenCustomWidths(data, siblingData, ctx);\n\nfunction approvedSelect(\n name: string,\n options: MegaMenuOption[] | undefined,\n admin: SelectField[\"admin\"],\n): SelectField[] {\n if (!options?.length) return [];\n return [{ name, type: \"select\", options, admin }];\n}\n\nexport function megaMenuBlock({\n variants,\n icons,\n}: MegaMenuBlockOptions = {}): Block {\n const linkRowFields: Field[] = [\n ...linkFields({ required: true }),\n {\n name: \"description\",\n type: \"textarea\",\n admin: { description: \"Supporting line under the label. Optional.\" },\n },\n ...approvedSelect(\"variant\", variants, {\n description: \"The look of this featured link.\",\n condition: whenSectionFeatured,\n }),\n ...approvedSelect(\"icon\", icons, {\n description: \"Glyph shown before a list link. Optional.\",\n condition: unlessSectionFeatured,\n }),\n ];\n\n return {\n slug: \"megaMenu\",\n interfaceName: \"MegaMenuBlock\",\n labels: { singular: \"Mega menu\", plural: \"Mega menus\" },\n fields: [\n { name: \"label\", type: \"text\", required: true },\n {\n name: \"href\",\n type: \"text\",\n admin: {\n description:\n \"The landing page this menu opens. Optional: a trigger with no landing page is allowed.\",\n },\n },\n {\n name: \"panel\",\n type: \"group\",\n fields: [\n {\n name: \"maxWidth\",\n type: \"select\",\n defaultValue: \"standard\",\n // No \"Full\": a panel anchors under its own trigger, so it cannot\n // span the bar, and capped at a trigger's room it would only\n // equal Wide. A typed rem width lives under Advanced.\n options: [\n { label: \"Narrow\", value: \"narrow\" },\n { label: \"Standard\", value: \"standard\" },\n { label: \"Wide\", value: \"wide\" },\n ],\n },\n {\n name: \"columns\",\n type: \"array\",\n required: true,\n minRows: 1,\n maxRows: 4,\n defaultValue: emptyRows(1),\n labels: { singular: \"Column\", plural: \"Columns\" },\n validate: validateColumnWidths,\n admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } },\n fields: [\n {\n name: \"width\",\n type: \"select\",\n defaultValue: \"100\",\n options: MEGA_MENU_WIDTHS.map((value) => ({\n label: `${value}%`,\n value,\n })),\n admin: { condition: unlessCustomWidths },\n },\n {\n name: \"customWidth\",\n type: \"text\",\n admin: {\n description: \"A CSS width: 13rem, 1fr, minmax(0, 1fr)\",\n condition: whenCustomWidths,\n },\n },\n {\n name: \"divider\",\n type: \"checkbox\",\n label: \"Draw a line on the left of this column\",\n defaultValue: false,\n },\n {\n name: \"sections\",\n type: \"array\",\n labels: { singular: \"Section\", plural: \"Sections\" },\n fields: [\n {\n name: \"eyebrow\",\n type: \"text\",\n admin: {\n description:\n \"Small heading above this group; leave empty for none\",\n },\n },\n {\n name: \"display\",\n type: \"select\",\n defaultValue: \"list\",\n options: [\n { label: \"Featured\", value: \"featured\" },\n { label: \"List\", value: \"list\" },\n ],\n },\n {\n name: \"hideDescriptionsOnMobile\",\n type: \"checkbox\",\n label: \"Hide descriptions on mobile\",\n defaultValue: true,\n },\n {\n name: \"links\",\n type: \"array\",\n labels: { singular: \"Link\", plural: \"Links\" },\n fields: linkRowFields,\n },\n ],\n },\n ],\n },\n {\n name: \"footer\",\n type: \"group\",\n admin: {\n description:\n \"The strip under the columns. Leave both empty for no footer.\",\n },\n fields: [\n linkField({\n name: \"overview\",\n label: \"Overview link\",\n admin: {\n description:\n \"The section's own landing page, so it stays reachable from the bar.\",\n },\n }),\n linkField({ name: \"cta\", label: \"Call to action\" }),\n ],\n },\n {\n type: \"collapsible\",\n label: \"Advanced\",\n admin: { initCollapsed: true },\n fields: [\n {\n name: \"customWidths\",\n type: \"checkbox\",\n label:\n \"Type a CSS width for each column instead of choosing a percentage\",\n defaultValue: false,\n },\n {\n name: \"customMaxWidth\",\n type: \"text\",\n validate: validateRemWidth,\n admin: {\n description:\n \"Panel width in rem, e.g. 30. Leave empty to use the preset above.\",\n },\n },\n ],\n },\n ],\n },\n ],\n };\n}\n\n/** A plain bar item: label and destination, nothing to open. */\nexport const LinkBlock: Block = {\n slug: \"navLink\",\n interfaceName: \"NavLinkBlock\",\n labels: { singular: \"Link\", plural: \"Links\" },\n fields: linkFields({ required: true }),\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 type { FaqColumnsBlockData } from \"../../types\";\n\nexport const faqColumnsSample: FaqColumnsBlockData = {\n id: \"sample-faq-columns\",\n blockType: \"faqColumns\",\n eyebrow: \"Good to know\",\n title: \"Questions patients ask before their first visit\",\n description:\n \"If yours is not here, the front desk answers the phone between eight and six on weekdays.\",\n items: [\n {\n id: \"sample-faq-referral\",\n question: \"Do I need a referral?\",\n answer:\n \"No. You can book directly. Some insurers ask for one before they reimburse, so check your policy if you plan to claim.\",\n },\n {\n id: \"sample-faq-sessions\",\n question: \"How many sessions will I need?\",\n answer:\n \"Most plans finish in six to eight sessions. You will get a written estimate after your assessment, and we revise it with you as you progress.\",\n },\n {\n id: \"sample-faq-wear\",\n question: \"What should I wear?\",\n answer:\n \"Anything you can move in. Shorts for a knee or hip, a vest or loose top for a shoulder or neck.\\n\\nThere are changing rooms if you are coming from work.\",\n },\n {\n id: \"sample-faq-insurance\",\n question: \"Is treatment covered by insurance?\",\n answer:\n \"Usually, once conservative treatment has been recommended. We invoice you directly and give you everything the insurer needs to reimburse you.\",\n },\n {\n id: \"sample-faq-cancel\",\n question: \"What is the cancellation policy?\",\n answer:\n \"Twenty-four hours' notice, by phone or through the booking link in your confirmation email. Later than that and the session is charged.\",\n },\n ],\n cta: {\n title: \"Still have questions?\",\n linkText: \"Call the front desk\",\n type: \"external\",\n href: \"tel:+13035550142\",\n newTab: false,\n },\n sticky: true,\n type: \"single\",\n collapsible: true,\n};\n","import type { MediaDoc } from \"./media\";\n\nexport interface SampleImageOptions {\n /** Text drawn across the placeholder, e.g. \"Panel 1 · 1400 × 1000\". */\n label: string;\n width: number;\n height: number;\n /** Alt text for the document. Defaults to the label. */\n alt?: string;\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\");\n}\n\n/**\n * A placeholder upload document for a block sample.\n *\n * A preview has no site media behind it, so the image has to travel with the\n * sample. This is an inline SVG as a `data:` URL, in the `MediaDoc` shape\n * `resolveMedia` already narrows, with `width` and `height` set so an image\n * adapter can reserve the box. The scheme is what tells an adapter which kind\n * of source it has: `next/image` treats a `data:` src as `unoptimized` on its\n * own, so a site's adapter needs no special case for samples.\n *\n * Neutral greys rather than theme tokens: the SVG is a standalone document\n * and cannot see the page's custom properties.\n */\nexport function sampleImage({\n label,\n width,\n height,\n alt,\n}: SampleImageOptions): MediaDoc {\n const fontSize = Math.max(12, Math.round(Math.min(width, height) / 12));\n const svg =\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\">` +\n `<rect width=\"100%\" height=\"100%\" fill=\"#d4d4d8\"/>` +\n `<text x=\"50%\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"middle\" ` +\n `font-family=\"system-ui, sans-serif\" font-size=\"${fontSize}\" fill=\"#52525b\">` +\n `${escapeXml(label)}</text></svg>`;\n return {\n url: `data:image/svg+xml,${encodeURIComponent(svg)}`,\n alt: alt ?? label,\n width,\n height,\n };\n}\n","import { sampleImage } from \"../../sample-image\";\nimport type { HeroBlockData } from \"../../types\";\n\n/**\n * The placeholder hero's sample. The hero family (BIS-45) ships a sample per\n * hero as each one is added; this is the one for the plain renderer.\n */\nexport const heroSample: HeroBlockData = {\n id: \"sample-hero\",\n blockType: \"hero\",\n eyebrow: \"Now booking spring appointments\",\n heading: \"Move well again, without the long wait\",\n body: \"Same-week assessments with a physiotherapist who stays with you from the first visit to the last. Most treatment plans finish in six to eight sessions.\",\n tone: \"sweep\",\n image: sampleImage({\n label: \"Hero background · 2000 × 1000\",\n width: 2000,\n height: 1000,\n alt: \"A bright, open treatment room\",\n }),\n // One link per shape: a page, carried inline as `depth: 1` would populate\n // it, so the preview needs no pages collection; and an external URL.\n links: [\n {\n type: \"page\",\n page: {\n id: \"sample-page-book\",\n title: \"Book an assessment\",\n slug: \"book\",\n _status: \"published\",\n },\n label: \"Book an assessment\",\n newTab: false,\n },\n {\n type: \"external\",\n href: \"https://example.com/approach\",\n label: \"See our approach\",\n newTab: true,\n },\n ],\n};\n","import type { MegaMenuBlockData, NavLinkBlockData } from \"../../types\";\nimport type { MegaMenuBlockOptions } from \"./config\";\n\n/**\n * The options the mega menu sample was written against. A factory block's\n * sample only fits a config built with the same approved values, so a\n * preview builds its block with `megaMenuBlock(megaMenuSampleOptions)`.\n */\nexport const megaMenuSampleOptions = {\n variants: [\n { label: \"Lumbar\", value: \"lumbar\" },\n { label: \"SI joint\", value: \"si\" },\n ],\n icons: [\n { label: \"Help\", value: \"help\" },\n { label: \"Mail\", value: \"mail\" },\n ],\n} satisfies MegaMenuBlockOptions;\n\n/**\n * Spinal Simplicity's Patients panel as a saved row: two featured territories\n * in a 75% column, two rail sections in a divided 25% column, overview and\n * CTA. Every field is set at least once; the Advanced fields are set but off,\n * so the percent widths are what renders. Most links are site paths typed as\n * external links; Testimonials and the overview are page links carried\n * inline, as `depth: 1` would populate them, so the preview needs no pages\n * collection.\n */\nexport const megaMenuSample: MegaMenuBlockData = {\n id: \"sample-mega-menu\",\n blockType: \"megaMenu\",\n label: \"Patients\",\n href: \"/patients\",\n panel: {\n maxWidth: \"standard\",\n columns: [\n {\n id: \"treatment\",\n width: \"75\",\n customWidth: \"minmax(0, 1fr)\",\n divider: false,\n sections: [\n {\n id: \"territories\",\n eyebrow: \"Treatment\",\n display: \"featured\",\n hideDescriptionsOnMobile: true,\n links: [\n {\n id: \"lumbar\",\n label: \"Low Back Pain\",\n type: \"external\",\n href: \"/patients/low-back-pain\",\n newTab: false,\n description:\n \"Persistent low back pain from lumbar instability, often with leg pain that limits standing or walking.\",\n variant: \"lumbar\",\n },\n {\n id: \"si\",\n label: \"Hip Pain\",\n type: \"external\",\n href: \"/patients/hip-pain\",\n newTab: false,\n description:\n \"Pain centered on the sacroiliac joint, often worse with sitting or climbing stairs.\",\n variant: \"si\",\n },\n ],\n },\n ],\n },\n {\n id: \"rail\",\n width: \"25\",\n customWidth: \"13rem\",\n divider: true,\n sections: [\n {\n id: \"outcomes\",\n eyebrow: \"Patient Outcomes\",\n display: \"list\",\n hideDescriptionsOnMobile: true,\n links: [\n {\n id: \"stories\",\n label: \"Testimonials\",\n type: \"page\",\n page: {\n id: \"sample-page-stories\",\n title: \"Testimonials\",\n slug: \"patients/stories\",\n _status: \"published\",\n },\n newTab: false,\n },\n { id: \"research\", label: \"Research\", type: \"external\", href: \"/patients/research\", newTab: false },\n { id: \"path\", label: \"Path to Relief\", type: \"external\", href: \"/patients/path-to-relief\", newTab: false },\n ],\n },\n {\n id: \"support\",\n eyebrow: \"Support\",\n display: \"list\",\n hideDescriptionsOnMobile: false,\n links: [\n {\n id: \"faqs\",\n label: \"FAQs\",\n type: \"external\",\n href: \"/patients/faqs\",\n newTab: false,\n description: \"Short answers to the questions patients ask first.\",\n icon: \"help\",\n },\n {\n id: \"contact\",\n label: \"Contact\",\n type: \"external\",\n href: \"https://example.com/contact\",\n newTab: true,\n icon: \"mail\",\n },\n ],\n },\n ],\n },\n ],\n footer: {\n overview: {\n label: \"All patient resources\",\n type: \"page\",\n page: { id: \"sample-page-patients\", title: \"Patients\", slug: \"patients\", _status: \"published\" },\n newTab: false,\n },\n cta: { label: \"Find a Doctor\", type: \"external\", href: \"/find-a-doctor\", newTab: false },\n },\n customWidths: false,\n customMaxWidth: \"42rem\",\n },\n};\n\n/** A plain bar item. */\nexport const navLinkSample: NavLinkBlockData = {\n id: \"sample-nav-link\",\n blockType: \"navLink\",\n label: \"Contact\",\n type: \"external\",\n href: \"/contact\",\n newTab: false,\n};\n","import type { NapBlockData } from \"../../types\";\n\n/**\n * A fictional clinic. `emitJsonLd` is off: the block emits schema.org\n * `LocalBusiness` by default, and a preview must not put a business that does\n * not exist into a site's structured data. The phone numbers are in the\n * 555-01xx range reserved for fiction.\n */\nexport const napSample: NapBlockData = {\n id: \"sample-nap\",\n blockType: \"nap\",\n businessName: \"Larkspur Physiotherapy\",\n businessType: \"Physiotherapy\",\n address: {\n streetAddress: \"400 Larkspur Lane, Suite 120\",\n addressLocality: \"Boulder\",\n addressRegion: \"CO\",\n postalCode: \"80302\",\n addressCountry: \"US\",\n },\n departments: [\n {\n id: \"sample-dept-front-desk\",\n name: \"Front desk\",\n phoneE164: \"+13035550142\",\n phoneDisplay: \"(303) 555-0142\",\n },\n {\n id: \"sample-dept-billing\",\n name: \"Billing and insurance\",\n departmentType: \"AccountingService\",\n phoneE164: \"+13035550143\",\n },\n ],\n url: \"https://example.com\",\n showName: true,\n headingLevel: \"h2\",\n emitJsonLd: false,\n};\n","import { sampleImage } from \"../../sample-image\";\nimport type { ProcessStepsBlockData } from \"../../types\";\n\n/** Four steps: enough to show the rail advancing through a real sequence. */\nexport const processStepsSample: ProcessStepsBlockData = {\n id: \"sample-process-steps\",\n blockType: \"processSteps\",\n items: [\n {\n id: \"sample-step-assess\",\n title: \"Assessment\",\n description:\n \"A fifty-minute first visit: your history, a movement screen, and a clear explanation of what we found.\",\n image: sampleImage({\n label: \"Step 1 · 1600 × 1000\",\n width: 1600,\n height: 1000,\n alt: \"A physiotherapist taking notes during an assessment\",\n }),\n },\n {\n id: \"sample-step-plan\",\n title: \"Your plan\",\n description:\n \"A written plan with the number of sessions we expect, what each one is for, and the exercises between them.\",\n image: sampleImage({\n label: \"Step 2 · 1600 × 1000\",\n width: 1600,\n height: 1000,\n alt: \"A printed treatment plan on a desk\",\n }),\n },\n {\n id: \"sample-step-treat\",\n title: \"Treatment\",\n description:\n \"Hands-on work where it helps, and progressive loading where it matters. You leave every session knowing what to do next.\",\n image: sampleImage({\n label: \"Step 3 · 1600 × 1000\",\n width: 1600,\n height: 1000,\n alt: \"A patient lifting a light kettlebell under supervision\",\n }),\n },\n {\n id: \"sample-step-discharge\",\n title: \"Discharge and beyond\",\n description:\n \"A final review, a maintenance programme, and an open door if anything flares up later.\",\n image: sampleImage({\n label: \"Step 4 · 1600 × 1000\",\n width: 1600,\n height: 1000,\n alt: \"A patient walking out of the clinic\",\n }),\n },\n ],\n autoAdvance: true,\n autoAdvanceDuration: 5000,\n pauseOnHover: true,\n defaultActiveIndex: 0,\n};\n","import type { RichTextBlockData, RichTextContent } from \"../../types\";\n\n/**\n * A serialized Lexical document, written by hand in the shapes the default\n * JSX converters read (`heading`, `paragraph`, `text`, `list`, `link`).\n * `version` is on every serialized Lexical node, and `direction`, `format` and\n * `indent` are what `RichTextContent` requires on the root; the converters\n * read none of them, and the sample carries them so it is shaped like a row\n * the editor saved.\n */\nconst block = {\n direction: \"ltr\" as const,\n format: \"\",\n indent: 0,\n version: 1,\n};\n\nfunction text(value: string, format = 0) {\n return {\n type: \"text\",\n text: value,\n format,\n detail: 0,\n mode: \"normal\",\n style: \"\",\n version: 1,\n };\n}\n\nfunction paragraph(children: unknown[]) {\n return { type: \"paragraph\", children, textFormat: 0, textStyle: \"\", ...block };\n}\n\nconst BOLD = 1;\n\nconst content: RichTextContent = {\n root: {\n type: \"root\",\n ...block,\n children: [\n {\n type: \"heading\",\n tag: \"h2\",\n children: [text(\"What to expect at your first visit\")],\n ...block,\n },\n paragraph([\n text(\"Your first appointment runs about \"),\n text(\"fifty minutes\", BOLD),\n text(\n \". We start with a conversation about what brought you in, then a movement assessment, and you leave with a written plan and the first two exercises.\",\n ),\n ]),\n paragraph([\n text(\"Bring comfortable clothes and any recent imaging. If you have questions before you arrive, \"),\n {\n type: \"link\",\n fields: { url: \"https://example.com/contact\", newTab: true, linkType: \"custom\" },\n children: [text(\"get in touch\")],\n ...block,\n version: 3,\n },\n text(\".\"),\n ]),\n {\n type: \"list\",\n listType: \"bullet\",\n tag: \"ul\",\n start: 1,\n children: [\n \"Assessment and written plan\",\n \"Hands-on treatment where it helps\",\n \"Exercises you can do at home, with video\",\n ].map((item, i) => ({\n type: \"listitem\",\n value: i + 1,\n children: [text(item)],\n ...block,\n })),\n ...block,\n },\n ],\n },\n};\n\nexport const richTextSample: RichTextBlockData = {\n id: \"sample-rich-text\",\n blockType: \"richText\",\n content,\n};\n","import { sampleImage } from \"../../sample-image\";\nimport type { ShowcasePanelsBlockData } from \"../../types\";\n\n/** Three panels: the minimum the layout is designed around. */\nexport const showcasePanelsSample: ShowcasePanelsBlockData = {\n id: \"sample-showcase-panels\",\n blockType: \"showcasePanels\",\n items: [\n {\n id: \"sample-panel-back\",\n title: \"Back and neck pain\",\n summary:\n \"Most back pain settles with the right movement, not rest. We find what is driving yours and build a plan around your week, not ours.\",\n image: sampleImage({\n label: \"Panel 1 · 1400 × 1000\",\n width: 1400,\n height: 1000,\n alt: \"A physiotherapist guiding a patient through a stretch\",\n }),\n href: \"/services/back-and-neck\",\n // Matches what automatic numbering would show, so the preview does not\n // mislead; it is here to exercise the override.\n numeral: \"01\",\n },\n {\n id: \"sample-panel-sport\",\n title: \"Sports injuries\",\n summary:\n \"From a rolled ankle to a post-surgical knee: a return-to-play plan with clear milestones, so you know when you are ready.\",\n image: sampleImage({\n label: \"Panel 2 · 1400 × 1000\",\n width: 1400,\n height: 1000,\n alt: \"A runner mid-stride on a track\",\n }),\n href: \"/services/sports-injuries\",\n },\n {\n id: \"sample-panel-post-op\",\n title: \"Post-operative rehab\",\n summary:\n \"We work from your surgeon's protocol and keep them in the loop, so every stage of recovery is signed off before the next begins.\",\n image: sampleImage({\n label: \"Panel 3 · 1400 × 1000\",\n width: 1400,\n height: 1000,\n alt: \"A patient on a rehabilitation bike\",\n }),\n },\n ],\n spineVariant: \"numbered\",\n watermark: \"LARKSPUR\",\n defaultActiveIndex: 0,\n};\n","import type { StatsBandBlockData } from \"../../types\";\n\n/**\n * Four figures, the count the strip was designed around: one row on a\n * desktop, two by two on a tablet and a phone. `wide` is set (to `false`) so\n * the preview exercises the field, and an even count is where it is ignored.\n * `overlap` is off so the heading shows: a floating band is headless, and a\n * preview page has no hero for it to float over.\n */\nexport const statsBandSample: StatsBandBlockData = {\n id: \"sample-stats-band\",\n blockType: \"statsBand\",\n eyebrow: \"At a glance\",\n title: \"The clinic in numbers\",\n description:\n \"What a year looks like across our two rooms, counted from the front desk rather than estimated.\",\n items: [\n {\n id: \"sample-stat-pain\",\n value: \"94%\",\n label: \"of patients report less pain by their sixth session.\",\n href: \"/outcomes\",\n wide: false,\n },\n {\n id: \"sample-stat-visits\",\n value: \"4,200+\",\n label: \"appointments a year across two treatment rooms.\",\n },\n {\n id: \"sample-stat-wait\",\n value: \"15 min\",\n label: \"average wait from the front desk to the treatment room.\",\n },\n {\n id: \"sample-stat-years\",\n value: \"12\",\n label: \"years in the same building, on the same street.\",\n },\n ],\n columns: \"4\",\n tone: \"card\",\n align: \"start\",\n overlap: false,\n};\n","import { sampleImage } from \"../../sample-image\";\nimport type { TestimonialMasonryBlockData } from \"../../types\";\n\nfunction avatar(name: string) {\n return sampleImage({\n label: name\n .split(\" \")\n .map((part) => part[0])\n .join(\"\"),\n width: 160,\n height: 160,\n alt: `Portrait of ${name}`,\n });\n}\n\n/**\n * Six quotes, two of them without an avatar so the initials fallback shows.\n * `minItemsForFade` is lowered to six so the preview also shows the fade and\n * the link beneath it, which the default of seven would hide at this count.\n */\nexport const testimonialMasonrySample: TestimonialMasonryBlockData = {\n id: \"sample-testimonial-masonry\",\n blockType: \"testimonialMasonry\",\n eyebrow: \"From our patients\",\n title: \"What people say after they finish\",\n description:\n \"Every review here was left by a patient we discharged. We do not edit them.\",\n items: [\n {\n id: \"sample-quote-1\",\n content:\n \"I had written off running. Eight weeks later I did a parkrun, and the plan I was given actually fitted around a job and two kids.\",\n author: {\n name: \"Priya Natarajan\",\n title: \"Recovered from a hamstring tear\",\n avatar: avatar(\"Priya Natarajan\"),\n },\n },\n {\n id: \"sample-quote-2\",\n content:\n \"The first physio who explained what was wrong in words I understood.\",\n author: {\n name: \"Tom Ferreira\",\n title: \"Lower back pain\",\n avatar: avatar(\"Tom Ferreira\"),\n },\n },\n {\n id: \"sample-quote-3\",\n content:\n \"My surgeon said my knee was ahead of schedule at every check-in. That was the rehab, not me.\",\n author: { name: \"Aisha Bello\", title: \"ACL reconstruction\" },\n },\n {\n id: \"sample-quote-4\",\n content:\n \"Booked on a Tuesday, seen on the Thursday. The shoulder I had put up with for a year was sorted in six visits.\",\n author: {\n name: \"Marcus Whitfield\",\n title: \"Frozen shoulder\",\n avatar: avatar(\"Marcus Whitfield\"),\n },\n },\n {\n id: \"sample-quote-5\",\n content:\n \"They kept my consultant in the loop the whole way through, which nobody else had bothered to do.\",\n author: { name: \"Helen Ostrowski\" },\n },\n {\n id: \"sample-quote-6\",\n content:\n \"Honest about what would take time and what would not. I would send my parents here.\",\n author: {\n name: \"Daniel Kim\",\n title: \"Ankle sprain\",\n avatar: avatar(\"Daniel Kim\"),\n },\n },\n ],\n link: {\n type: \"external\",\n href: \"https://example.com/reviews\",\n label: \"Read every review\",\n newTab: true,\n },\n minItemsForFade: 6,\n maxVisibleRows: 2,\n};\n","import { faqColumnsSample } from \"./blocks/faq-columns/sample\";\nimport { heroSample } from \"./blocks/hero/sample\";\nimport { megaMenuSample, navLinkSample } from \"./blocks/mega-menu/sample\";\nimport { napSample } from \"./blocks/nap/sample\";\nimport { processStepsSample } from \"./blocks/process-steps/sample\";\nimport { richTextSample } from \"./blocks/rich-text/sample\";\nimport { showcasePanelsSample } from \"./blocks/showcase-panels/sample\";\nimport { statsBandSample } from \"./blocks/stats-band/sample\";\nimport { testimonialMasonrySample } from \"./blocks/testimonial-masonry/sample\";\nimport type { BisonBlockData, BisonBlockType } from \"./types\";\n\n/**\n * One sample row per block, each typed to its own row shape. Assignable to\n * `Record<BisonBlockType, BisonBlockData>` wherever the wider type is wanted.\n */\nexport type BlockSamples = {\n [K in BisonBlockType]: Extract<BisonBlockData, { blockType: K }>;\n};\n\n/**\n * Every block the package ships, rendered without a CMS row.\n *\n * A Block library page (BIS-52) renders these live so an admin can see each\n * block before enabling it. Each sample lives beside its block's `config.ts`\n * and `component.tsx` as `sample.ts`, and `src/__tests__/samples.test.tsx`\n * locks the contract: one entry per slug, `blockType` matching the key, every\n * config field exercised at least once, and every image self-contained.\n *\n * React-free on purpose: it is read from a Global's field config, which\n * Payload loads in plain Node like the rest of this entry, as well as from a\n * route handler. Only what this package ships is here; a site supplies samples\n * for its own blocks through the Block library factory, whose option BIS-52\n * names.\n */\nexport const blockSamples: BlockSamples = {\n hero: heroSample,\n richText: richTextSample,\n showcasePanels: showcasePanelsSample,\n processSteps: processStepsSample,\n faqColumns: faqColumnsSample,\n testimonialMasonry: testimonialMasonrySample,\n nap: napSample,\n statsBand: statsBandSample,\n // Header blocks: rendered by `MegaMenuBlockRenderer` and `headerItemsFromBlocks`,\n // never by `RenderBlocks`. The mega menu sample fits `megaMenuBlock(megaMenuSampleOptions)`.\n megaMenu: megaMenuSample,\n navLink: navLinkSample,\n};\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
 
2
- import { m as BlockRendererProps, s as RichTextBlockData } from "./types-ODMRyOpH.mjs";
2
+ import { m as BlockRendererProps, s as RichTextBlockData, x as ResolveLink } from "./types-ODMRyOpH.mjs";
3
3
  import * as react_jsx_runtime0 from "react/jsx-runtime";
4
4
  import { SerializedLinkNode } from "@payloadcms/richtext-lexical";
5
5
 
@@ -14,22 +14,41 @@ type InternalDocToHref = (args: {
14
14
  linkNode: SerializedLinkNode;
15
15
  }) => string;
16
16
  interface RichTextBlockRendererOptions {
17
+ /**
18
+ * Wins when both this and `resolveLink` are passed, so a site that already
19
+ * wrote a custom Lexical resolver is unchanged.
20
+ */
17
21
  internalDocToHref?: InternalDocToHref;
22
+ /**
23
+ * The same function `RenderBlocks` takes. When `internalDocToHref` is
24
+ * omitted, the factory builds one with `internalDocToHrefFrom`.
25
+ */
26
+ resolveLink?: ResolveLink;
18
27
  }
28
+ /**
29
+ * Turns the site's `resolveLink` into the Lexical resolver the rich-text
30
+ * factory needs. Unpacks `linkNode.fields.doc` into a page destination and
31
+ * calls `resolveLink`; `null` (unpopulated or unpublished) becomes `#`, the
32
+ * same fallback BIS-69 uses when no resolver is registered.
33
+ */
34
+ declare function internalDocToHrefFrom(resolve: ResolveLink): InternalDocToHref;
19
35
  /**
20
36
  * Builds the `richText` renderer for a site.
21
37
  *
22
38
  * `RichTextBlockRenderer` below is the plain registry value and is what a
23
39
  * site with no internal links needs. A site whose editors link to other
24
- * documents builds its own with `internalDocToHref`, and it must do so in a
25
- * `"use client"` module: this entry carries the client banner, so the
26
- * factory is a client reference that a Server Component can pass along but
27
- * cannot call, and the resolver is a closure that cannot cross the boundary
28
- * as a prop either. The site's `next/link` adapter lives in the same kind of
29
- * module for the same reason.
40
+ * documents builds its own with `resolveLink` (the same function
41
+ * `RenderBlocks` takes; the factory wraps it with `internalDocToHrefFrom`)
42
+ * or a hand-written `internalDocToHref`. When both are passed,
43
+ * `internalDocToHref` wins. It must do so in a `"use client"` module: this
44
+ * entry carries the client banner, so the factory is a client reference
45
+ * that a Server Component can pass along but cannot call, and the resolver
46
+ * is a closure that cannot cross the boundary as a prop either. The site's
47
+ * `next/link` adapter lives in the same kind of module for the same reason.
30
48
  */
31
49
  declare function richTextBlockRenderer({
32
- internalDocToHref
50
+ internalDocToHref,
51
+ resolveLink: resolve
33
52
  }?: RichTextBlockRendererOptions): ({
34
53
  block,
35
54
  overlapAbove,
@@ -46,5 +65,5 @@ declare const RichTextBlockRenderer: ({
46
65
  linkComponent
47
66
  }: BlockRendererProps<RichTextBlockData>) => react_jsx_runtime0.JSX.Element | null;
48
67
  //#endregion
49
- export { type InternalDocToHref, RichTextBlockRenderer, type RichTextBlockRendererOptions, richTextBlockRenderer };
68
+ export { type InternalDocToHref, RichTextBlockRenderer, type RichTextBlockRendererOptions, internalDocToHrefFrom, richTextBlockRenderer };
50
69
  //# sourceMappingURL=rich-text.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rich-text.d.mts","names":[],"sources":["../src/blocks/rich-text/component.tsx"],"mappings":";;;;;;;;AAoBA;;;;KAAY,iBAAA,IAAqB,IAAA;EAAQ,QAAA,EAAU,kBAAA;AAAA;AAAA,UAElC,4BAAA;EACf,iBAAA,GAAoB,iBAAA;AAAA;;;;;AAmEtB;;;;;;;;iBAAgB,qBAAA,CAAA;EAAwB;AAAA,IAAqB,4BAAA;EAAiC,KAAA;EAAA,YAAA;EAAA,YAAA;EAAA,kBAAA;EAAA;AAAA,GAmBzF,kBAAA,CAAmB,iBAAA,MAAkB,kBAAA,CAAA,GAAA,CAAA,OAAA;;cAkB7B,qBAAA;EAAqB,KAAA;EAAA,YAAA;EAAA,YAAA;EAAA,kBAAA;EAAA;AAAA,GAlB7B,kBAAA,CAAmB,iBAAA,MAAkB,kBAAA,CAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"rich-text.d.mts","names":[],"sources":["../src/blocks/rich-text/component.tsx"],"mappings":";;;;;;;AAqBA;;;;;KAAY,iBAAA,IAAqB,IAAA;EAAQ,QAAA,EAAU,kBAAA;AAAA;AAAA,UAElC,4BAAA;EAAA;;;;EAKf,iBAAA,GAAoB,iBAAA;EAAA;;;;EAKpB,WAAA,GAAc,WAAA;AAAA;;;;;;;iBASA,qBAAA,CAAsB,OAAA,EAAS,WAAA,GAAc,iBAAA;;AA2E7D;;;;;;;;;;;;;iBAAgB,qBAAA,CAAA;EACd,iBAAA;EACA,WAAA,EAAa;AAAA,IACZ,4BAAA;EAAiC,KAAA;EAAA,YAAA;EAAA,YAAA;EAAA,kBAAA;EAAA;AAAA,GAoB/B,kBAAA,CAAmB,iBAAA,MAAkB,kBAAA,CAAA,GAAA,CAAA,OAAA;;cAkB7B,qBAAA;EAAqB,KAAA;EAAA,YAAA;EAAA,YAAA;EAAA,kBAAA;EAAA;AAAA,GAlB7B,kBAAA,CAAmB,iBAAA,MAAkB,kBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -4,6 +4,21 @@ import { jsx } from "react/jsx-runtime";
4
4
  import { RichText } from "@payloadcms/richtext-lexical/react";
5
5
  //#region src/blocks/rich-text/component.tsx
6
6
  /**
7
+ * Turns the site's `resolveLink` into the Lexical resolver the rich-text
8
+ * factory needs. Unpacks `linkNode.fields.doc` into a page destination and
9
+ * calls `resolveLink`; `null` (unpopulated or unpublished) becomes `#`, the
10
+ * same fallback BIS-69 uses when no resolver is registered.
11
+ */
12
+ function internalDocToHrefFrom(resolve) {
13
+ return ({ linkNode }) => {
14
+ const doc = linkNode.fields.doc;
15
+ return resolve({
16
+ type: "page",
17
+ page: (doc && typeof doc === "object" ? doc.value : void 0) ?? null
18
+ }) ?? "#";
19
+ };
20
+ }
21
+ /**
7
22
  * The `link` and `autolink` converters, through the seam. Payload's default
8
23
  * converters render both as a bare `<a>`, a full document load on a Next
9
24
  * site; these take their place and leave every other node to
@@ -17,7 +32,7 @@ import { RichText } from "@payloadcms/richtext-lexical/react";
17
32
  * is BIS-76, pinned in renderers.test.tsx; until it ships, a rich-text
18
33
  * section is client-side for its links and not for its uploads.
19
34
  *
20
- * An internal link with no `internalDocToHref` cannot be resolved here, so it
35
+ * An internal link with no resolver cannot be resolved here, so it
21
36
  * renders through the seam at `#`, the href Payload's own converter falls back
22
37
  * to, and says so on the console rather than shipping a dead link quietly.
23
38
  */
@@ -35,7 +50,7 @@ function linkConverters(Link, internalDocToHref) {
35
50
  let href = node.fields.url ?? "";
36
51
  if (node.fields.linkType === "internal") if (internalDocToHref) href = internalDocToHref({ linkNode: node });
37
52
  else {
38
- console.error("@bison-lab/payload-blocks: a rich-text link points at another document, but the richText renderer has no internalDocToHref to turn it into a URL. Register richTextBlockRenderer({ internalDocToHref }) in a client module instead of RichTextBlockRenderer.");
53
+ console.error("@bison-lab/payload-blocks: a rich-text link points at another document, but the richText renderer has no internalDocToHref to turn it into a URL. Register richTextBlockRenderer({ resolveLink }) or richTextBlockRenderer({ internalDocToHref }) in a client module instead of RichTextBlockRenderer.");
39
54
  href = "#";
40
55
  }
41
56
  return through(href, node, nodesToJSX({ nodes: node.children }));
@@ -47,14 +62,17 @@ function linkConverters(Link, internalDocToHref) {
47
62
  *
48
63
  * `RichTextBlockRenderer` below is the plain registry value and is what a
49
64
  * site with no internal links needs. A site whose editors link to other
50
- * documents builds its own with `internalDocToHref`, and it must do so in a
51
- * `"use client"` module: this entry carries the client banner, so the
52
- * factory is a client reference that a Server Component can pass along but
53
- * cannot call, and the resolver is a closure that cannot cross the boundary
54
- * as a prop either. The site's `next/link` adapter lives in the same kind of
55
- * module for the same reason.
65
+ * documents builds its own with `resolveLink` (the same function
66
+ * `RenderBlocks` takes; the factory wraps it with `internalDocToHrefFrom`)
67
+ * or a hand-written `internalDocToHref`. When both are passed,
68
+ * `internalDocToHref` wins. It must do so in a `"use client"` module: this
69
+ * entry carries the client banner, so the factory is a client reference
70
+ * that a Server Component can pass along but cannot call, and the resolver
71
+ * is a closure that cannot cross the boundary as a prop either. The site's
72
+ * `next/link` adapter lives in the same kind of module for the same reason.
56
73
  */
57
- function richTextBlockRenderer({ internalDocToHref } = {}) {
74
+ function richTextBlockRenderer({ internalDocToHref, resolveLink: resolve } = {}) {
75
+ const toHref = internalDocToHref ?? (resolve ? internalDocToHrefFrom(resolve) : void 0);
58
76
  /**
59
77
  * A Lexical document as prose.
60
78
  *
@@ -78,7 +96,7 @@ function richTextBlockRenderer({ internalDocToHref } = {}) {
78
96
  className: cx(containerClassName, "py-16 lg:py-20"),
79
97
  children: /* @__PURE__ */ jsx(RichText, {
80
98
  data: block.content,
81
- converters: linkConverters(linkComponent, internalDocToHref),
99
+ converters: linkConverters(linkComponent, toHref),
82
100
  className: "max-w-prose"
83
101
  })
84
102
  })
@@ -89,6 +107,6 @@ function richTextBlockRenderer({ internalDocToHref } = {}) {
89
107
  /** The `richText` renderer with no internal-link resolver; see the factory. */
90
108
  const RichTextBlockRenderer = richTextBlockRenderer();
91
109
  //#endregion
92
- export { RichTextBlockRenderer, richTextBlockRenderer };
110
+ export { RichTextBlockRenderer, internalDocToHrefFrom, richTextBlockRenderer };
93
111
 
94
112
  //# sourceMappingURL=rich-text.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"rich-text.mjs","names":[],"sources":["../src/blocks/rich-text/component.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { RichText, type JSXConvertersFunction } from \"@payloadcms/richtext-lexical/react\";\nimport type { SerializedEditorState } from \"@payloadcms/richtext-lexical/lexical\";\nimport type {\n SerializedAutoLinkNode,\n SerializedLinkNode,\n} from \"@payloadcms/richtext-lexical\";\n\nimport { cx } from \"../../cx\";\nimport { type BlockLinkComponent, newTabAttributes } from \"../../link\";\nimport type { BlockRendererProps } from \"../../render-blocks\";\nimport { seamClasses } from \"../../seam\";\nimport type { RichTextBlockData } from \"../../types\";\n\n/**\n * How an editor's link to another document becomes a URL. The same argument\n * Payload's own `LinkJSXConverter` takes: `linkNode.fields.doc` carries the\n * `relationTo` and the related document (populated, or just its id, by the\n * depth the page was fetched at). Only the site knows its routes.\n */\nexport type InternalDocToHref = (args: { linkNode: SerializedLinkNode }) => string;\n\nexport interface RichTextBlockRendererOptions {\n internalDocToHref?: InternalDocToHref;\n}\n\n/**\n * The `link` and `autolink` converters, through the seam. Payload's default\n * converters render both as a bare `<a>`, a full document load on a Next\n * site; these take their place and leave every other node to\n * `defaultJSXConverters`. `newTab` is expanded to `target`/`rel` the way\n * every other renderer does it, so an adapter that only spreads the anchor\n * attributes is still safe.\n *\n * One default converter still writes markup the seams exist to own: `upload`\n * renders a file an editor drops into the prose as a bare `<a>` and an image\n * as a raw `<img>`, outside `linkComponent` and `imageComponent` both. That\n * is BIS-76, pinned in renderers.test.tsx; until it ships, a rich-text\n * section is client-side for its links and not for its uploads.\n *\n * An internal link with no `internalDocToHref` cannot be resolved here, so it\n * renders through the seam at `#`, the href Payload's own converter falls back\n * to, and says so on the console rather than shipping a dead link quietly.\n */\nfunction linkConverters(\n Link: BlockLinkComponent,\n internalDocToHref: InternalDocToHref | undefined,\n): JSXConvertersFunction {\n const through = (\n href: string,\n node: SerializedAutoLinkNode | SerializedLinkNode,\n children: ReactNode,\n ) => (\n <Link href={href} newTab={node.fields.newTab} {...newTabAttributes(node.fields.newTab)}>\n {children}\n </Link>\n );\n return ({ defaultConverters }) => ({\n ...defaultConverters,\n autolink: ({ node, nodesToJSX }) =>\n through(node.fields.url ?? \"\", node, nodesToJSX({ nodes: node.children })),\n link: ({ node, nodesToJSX }) => {\n let href = node.fields.url ?? \"\";\n if (node.fields.linkType === \"internal\") {\n if (internalDocToHref) {\n href = internalDocToHref({ linkNode: node });\n } else {\n console.error(\n \"@bison-lab/payload-blocks: a rich-text link points at another document, but the richText renderer has no internalDocToHref to turn it into a URL. Register richTextBlockRenderer({ internalDocToHref }) in a client module instead of RichTextBlockRenderer.\",\n );\n href = \"#\";\n }\n }\n return through(href, node, nodesToJSX({ nodes: node.children }));\n },\n });\n}\n\n/**\n * Builds the `richText` renderer for a site.\n *\n * `RichTextBlockRenderer` below is the plain registry value and is what a\n * site with no internal links needs. A site whose editors link to other\n * documents builds its own with `internalDocToHref`, and it must do so in a\n * `\"use client\"` module: this entry carries the client banner, so the\n * factory is a client reference that a Server Component can pass along but\n * cannot call, and the resolver is a closure that cannot cross the boundary\n * as a prop either. The site's `next/link` adapter lives in the same kind of\n * module for the same reason.\n */\nexport function richTextBlockRenderer({ internalDocToHref }: RichTextBlockRendererOptions = {}) {\n /**\n * A Lexical document as prose.\n *\n * This is the one renderer that needs `@payloadcms/richtext-lexical`, which\n * is why it ships from its own entry (`@bison-lab/payload-blocks/rich-text`)\n * rather than the `./react` barrel every consumer loads.\n *\n * The cast at the boundary is the price of the package's no-generated-types\n * rule: `RichTextContent` describes the document structurally so a site's\n * generated block type matches it, and Lexical's own type is the same shape\n * with tighter unions.\n */\n function RichTextBlockRenderer({\n block,\n overlapAbove,\n overlapBelow,\n containerClassName,\n linkComponent,\n }: BlockRendererProps<RichTextBlockData>) {\n if (!block.content) return null;\n return (\n <section className={seamClasses({ overlapAbove, overlapBelow })}>\n <div className={cx(containerClassName, \"py-16 lg:py-20\")}>\n <RichText\n data={block.content as unknown as SerializedEditorState}\n converters={linkConverters(linkComponent, internalDocToHref)}\n className=\"max-w-prose\"\n />\n </div>\n </section>\n );\n }\n return RichTextBlockRenderer;\n}\n\n/** The `richText` renderer with no internal-link resolver; see the factory. */\nexport const RichTextBlockRenderer = richTextBlockRenderer();\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA4CA,SAAS,eACP,MACA,mBACuB;CACvB,MAAM,WACJ,MACA,MACA,aAEA,oBAAC,MAAD;EAAY;EAAM,QAAQ,KAAK,OAAO;EAAQ,GAAI,iBAAiB,KAAK,OAAO,OAAO;EACnF;EACI,CAAA;AAET,SAAQ,EAAE,yBAAyB;EACjC,GAAG;EACH,WAAW,EAAE,MAAM,iBACjB,QAAQ,KAAK,OAAO,OAAO,IAAI,MAAM,WAAW,EAAE,OAAO,KAAK,UAAU,CAAC,CAAC;EAC5E,OAAO,EAAE,MAAM,iBAAiB;GAC9B,IAAI,OAAO,KAAK,OAAO,OAAO;AAC9B,OAAI,KAAK,OAAO,aAAa,WAC3B,KAAI,kBACF,QAAO,kBAAkB,EAAE,UAAU,MAAM,CAAC;QACvC;AACL,YAAQ,MACN,+PACD;AACD,WAAO;;AAGX,UAAO,QAAQ,MAAM,MAAM,WAAW,EAAE,OAAO,KAAK,UAAU,CAAC,CAAC;;EAEnE;;;;;;;;;;;;;;AAeH,SAAgB,sBAAsB,EAAE,sBAAoD,EAAE,EAAE;;;;;;;;;;;;;CAa9F,SAAS,sBAAsB,EAC7B,OACA,cACA,cACA,oBACA,iBACwC;AACxC,MAAI,CAAC,MAAM,QAAS,QAAO;AAC3B,SACE,oBAAC,WAAD;GAAS,WAAW,YAAY;IAAE;IAAc;IAAc,CAAC;aAC7D,oBAAC,OAAD;IAAK,WAAW,GAAG,oBAAoB,iBAAiB;cACtD,oBAAC,UAAD;KACE,MAAM,MAAM;KACZ,YAAY,eAAe,eAAe,kBAAkB;KAC5D,WAAU;KACV,CAAA;IACE,CAAA;GACE,CAAA;;AAGd,QAAO;;;AAIT,MAAa,wBAAwB,uBAAuB"}
1
+ {"version":3,"file":"rich-text.mjs","names":[],"sources":["../src/blocks/rich-text/component.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { RichText, type JSXConvertersFunction } from \"@payloadcms/richtext-lexical/react\";\nimport type { SerializedEditorState } from \"@payloadcms/richtext-lexical/lexical\";\nimport type {\n SerializedAutoLinkNode,\n SerializedLinkNode,\n} from \"@payloadcms/richtext-lexical\";\n\nimport { cx } from \"../../cx\";\nimport type { LinkDestination, ResolveLink } from \"../../fields/link\";\nimport { type BlockLinkComponent, newTabAttributes } from \"../../link\";\nimport type { BlockRendererProps } from \"../../render-blocks\";\nimport { seamClasses } from \"../../seam\";\nimport type { RichTextBlockData } from \"../../types\";\n\n/**\n * How an editor's link to another document becomes a URL. The same argument\n * Payload's own `LinkJSXConverter` takes: `linkNode.fields.doc` carries the\n * `relationTo` and the related document (populated, or just its id, by the\n * depth the page was fetched at). Only the site knows its routes.\n */\nexport type InternalDocToHref = (args: { linkNode: SerializedLinkNode }) => string;\n\nexport interface RichTextBlockRendererOptions {\n /**\n * Wins when both this and `resolveLink` are passed, so a site that already\n * wrote a custom Lexical resolver is unchanged.\n */\n internalDocToHref?: InternalDocToHref;\n /**\n * The same function `RenderBlocks` takes. When `internalDocToHref` is\n * omitted, the factory builds one with `internalDocToHrefFrom`.\n */\n resolveLink?: ResolveLink;\n}\n\n/**\n * Turns the site's `resolveLink` into the Lexical resolver the rich-text\n * factory needs. Unpacks `linkNode.fields.doc` into a page destination and\n * calls `resolveLink`; `null` (unpopulated or unpublished) becomes `#`, the\n * same fallback BIS-69 uses when no resolver is registered.\n */\nexport function internalDocToHrefFrom(resolve: ResolveLink): InternalDocToHref {\n return ({ linkNode }) => {\n const doc = linkNode.fields.doc;\n const value = doc && typeof doc === \"object\" ? doc.value : undefined;\n const page = (value ?? null) as LinkDestination[\"page\"];\n return resolve({ type: \"page\", page }) ?? \"#\";\n };\n}\n\n/**\n * The `link` and `autolink` converters, through the seam. Payload's default\n * converters render both as a bare `<a>`, a full document load on a Next\n * site; these take their place and leave every other node to\n * `defaultJSXConverters`. `newTab` is expanded to `target`/`rel` the way\n * every other renderer does it, so an adapter that only spreads the anchor\n * attributes is still safe.\n *\n * One default converter still writes markup the seams exist to own: `upload`\n * renders a file an editor drops into the prose as a bare `<a>` and an image\n * as a raw `<img>`, outside `linkComponent` and `imageComponent` both. That\n * is BIS-76, pinned in renderers.test.tsx; until it ships, a rich-text\n * section is client-side for its links and not for its uploads.\n *\n * An internal link with no resolver cannot be resolved here, so it\n * renders through the seam at `#`, the href Payload's own converter falls back\n * to, and says so on the console rather than shipping a dead link quietly.\n */\nfunction linkConverters(\n Link: BlockLinkComponent,\n internalDocToHref: InternalDocToHref | undefined,\n): JSXConvertersFunction {\n const through = (\n href: string,\n node: SerializedAutoLinkNode | SerializedLinkNode,\n children: ReactNode,\n ) => (\n <Link href={href} newTab={node.fields.newTab} {...newTabAttributes(node.fields.newTab)}>\n {children}\n </Link>\n );\n return ({ defaultConverters }) => ({\n ...defaultConverters,\n autolink: ({ node, nodesToJSX }) =>\n through(node.fields.url ?? \"\", node, nodesToJSX({ nodes: node.children })),\n link: ({ node, nodesToJSX }) => {\n let href = node.fields.url ?? \"\";\n if (node.fields.linkType === \"internal\") {\n if (internalDocToHref) {\n href = internalDocToHref({ linkNode: node });\n } else {\n console.error(\n \"@bison-lab/payload-blocks: a rich-text link points at another document, but the richText renderer has no internalDocToHref to turn it into a URL. Register richTextBlockRenderer({ resolveLink }) or richTextBlockRenderer({ internalDocToHref }) in a client module instead of RichTextBlockRenderer.\",\n );\n href = \"#\";\n }\n }\n return through(href, node, nodesToJSX({ nodes: node.children }));\n },\n });\n}\n\n/**\n * Builds the `richText` renderer for a site.\n *\n * `RichTextBlockRenderer` below is the plain registry value and is what a\n * site with no internal links needs. A site whose editors link to other\n * documents builds its own with `resolveLink` (the same function\n * `RenderBlocks` takes; the factory wraps it with `internalDocToHrefFrom`)\n * or a hand-written `internalDocToHref`. When both are passed,\n * `internalDocToHref` wins. It must do so in a `\"use client\"` module: this\n * entry carries the client banner, so the factory is a client reference\n * that a Server Component can pass along but cannot call, and the resolver\n * is a closure that cannot cross the boundary as a prop either. The site's\n * `next/link` adapter lives in the same kind of module for the same reason.\n */\nexport function richTextBlockRenderer({\n internalDocToHref,\n resolveLink: resolve,\n}: RichTextBlockRendererOptions = {}) {\n const toHref = internalDocToHref ?? (resolve ? internalDocToHrefFrom(resolve) : undefined);\n /**\n * A Lexical document as prose.\n *\n * This is the one renderer that needs `@payloadcms/richtext-lexical`, which\n * is why it ships from its own entry (`@bison-lab/payload-blocks/rich-text`)\n * rather than the `./react` barrel every consumer loads.\n *\n * The cast at the boundary is the price of the package's no-generated-types\n * rule: `RichTextContent` describes the document structurally so a site's\n * generated block type matches it, and Lexical's own type is the same shape\n * with tighter unions.\n */\n function RichTextBlockRenderer({\n block,\n overlapAbove,\n overlapBelow,\n containerClassName,\n linkComponent,\n }: BlockRendererProps<RichTextBlockData>) {\n if (!block.content) return null;\n return (\n <section className={seamClasses({ overlapAbove, overlapBelow })}>\n <div className={cx(containerClassName, \"py-16 lg:py-20\")}>\n <RichText\n data={block.content as unknown as SerializedEditorState}\n converters={linkConverters(linkComponent, toHref)}\n className=\"max-w-prose\"\n />\n </div>\n </section>\n );\n }\n return RichTextBlockRenderer;\n}\n\n/** The `richText` renderer with no internal-link resolver; see the factory. */\nexport const RichTextBlockRenderer = richTextBlockRenderer();\n"],"mappings":";;;;;;;;;;;AA0CA,SAAgB,sBAAsB,SAAyC;AAC7E,SAAQ,EAAE,eAAe;EACvB,MAAM,MAAM,SAAS,OAAO;AAG5B,SAAO,QAAQ;GAAE,MAAM;GAAQ,OAFjB,OAAO,OAAO,QAAQ,WAAW,IAAI,QAAQ,KAAA,MACpC;GACc,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;AAsB9C,SAAS,eACP,MACA,mBACuB;CACvB,MAAM,WACJ,MACA,MACA,aAEA,oBAAC,MAAD;EAAY;EAAM,QAAQ,KAAK,OAAO;EAAQ,GAAI,iBAAiB,KAAK,OAAO,OAAO;EACnF;EACI,CAAA;AAET,SAAQ,EAAE,yBAAyB;EACjC,GAAG;EACH,WAAW,EAAE,MAAM,iBACjB,QAAQ,KAAK,OAAO,OAAO,IAAI,MAAM,WAAW,EAAE,OAAO,KAAK,UAAU,CAAC,CAAC;EAC5E,OAAO,EAAE,MAAM,iBAAiB;GAC9B,IAAI,OAAO,KAAK,OAAO,OAAO;AAC9B,OAAI,KAAK,OAAO,aAAa,WAC3B,KAAI,kBACF,QAAO,kBAAkB,EAAE,UAAU,MAAM,CAAC;QACvC;AACL,YAAQ,MACN,ySACD;AACD,WAAO;;AAGX,UAAO,QAAQ,MAAM,MAAM,WAAW,EAAE,OAAO,KAAK,UAAU,CAAC,CAAC;;EAEnE;;;;;;;;;;;;;;;;AAiBH,SAAgB,sBAAsB,EACpC,mBACA,aAAa,YACmB,EAAE,EAAE;CACpC,MAAM,SAAS,sBAAsB,UAAU,sBAAsB,QAAQ,GAAG,KAAA;;;;;;;;;;;;;CAahF,SAAS,sBAAsB,EAC7B,OACA,cACA,cACA,oBACA,iBACwC;AACxC,MAAI,CAAC,MAAM,QAAS,QAAO;AAC3B,SACE,oBAAC,WAAD;GAAS,WAAW,YAAY;IAAE;IAAc;IAAc,CAAC;aAC7D,oBAAC,OAAD;IAAK,WAAW,GAAG,oBAAoB,iBAAiB;cACtD,oBAAC,UAAD;KACE,MAAM,MAAM;KACZ,YAAY,eAAe,eAAe,OAAO;KACjD,WAAU;KACV,CAAA;IACE,CAAA;GACE,CAAA;;AAGd,QAAO;;;AAIT,MAAa,wBAAwB,uBAAuB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bison-lab/payload-blocks",
3
- "version": "3.4.0",
3
+ "version": "3.11.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": {
@@ -36,6 +36,7 @@
36
36
  "dist"
37
37
  ],
38
38
  "peerDependencies": {
39
+ "@bison-lab/payload-core": "^3.0.0",
39
40
  "@bison-lab/ui": "^3.0.0",
40
41
  "@payloadcms/richtext-lexical": "^3.0.0",
41
42
  "@payloadcms/ui": "^3.0.0",
@@ -74,7 +75,8 @@
74
75
  "tsdown": "^0.21.0",
75
76
  "typescript": "^5.9.3",
76
77
  "vitest": "^4.1.2",
77
- "@bison-lab/ui": "3.3.0"
78
+ "@bison-lab/payload-core": "3.11.0",
79
+ "@bison-lab/ui": "3.11.0"
78
80
  },
79
81
  "publishConfig": {
80
82
  "access": "public"