@bison-lab/payload-blocks 2.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,15 +12,17 @@ pnpm add @bison-lab/payload-blocks
12
12
 
13
13
  Peers: `payload` (required); `react`, `react-dom`, `@bison-lab/ui` and
14
14
  `@payloadcms/richtext-lexical` (needed by the renderer entries, optional if you
15
- only use the configs).
15
+ only use the configs); `@payloadcms/ui` (needed by the admin entry, which every
16
+ Payload site already has).
16
17
 
17
- ## Two entry points, and why
18
+ ## Four entry points, and why
18
19
 
19
20
  | Import | Contents | Runs where |
20
21
  | --- | --- | --- |
21
22
  | `@bison-lab/payload-blocks` | Block configs, field builders, row types, `resolveMedia` | Node. This is what `payload.config.ts` imports, and it touches no React. |
22
23
  | `@bison-lab/payload-blocks/react` | Renderers, `RenderBlocks`, the rendering types | Client (`"use client"`). |
23
24
  | `@bison-lab/payload-blocks/rich-text` | The `richText` renderer, alone | Client. Split out because it is the only thing that needs `@payloadcms/richtext-lexical`. |
25
+ | `@bison-lab/payload-blocks/admin` | `MinRowsArrayField`, the admin field the configs reference by path | Client, inside the Payload admin. Resolved through the site's import map, never imported by hand. |
24
26
 
25
27
  The renderers are client components because the blocks they render are: every
26
28
  `@bison-lab/ui` export is a client reference, and these blocks are interactive
@@ -62,7 +64,30 @@ export const Pages: CollectionConfig = {
62
64
  }
63
65
  ```
64
66
 
65
- Then `payload generate:types` and `payload migrate:create`.
67
+ Then `payload generate:types`, `payload generate:importmap` and
68
+ `payload migrate:create`.
69
+
70
+ The import map step is what wires the admin field. Every array in this package
71
+ with a `minRows` opens with that many empty rows and names
72
+ `@bison-lab/payload-blocks/admin#MinRowsArrayField` as its field component,
73
+ which refuses to remove a row once the count is down to the minimum (Payload
74
+ gates Add on `maxRows` but never Remove on `minRows`). Until the import map
75
+ has the entry Payload logs the miss and renders its stock array field, so an
76
+ upgrade that forgets the step degrades to the old behaviour rather than
77
+ breaking. A site can put the same field on its own arrays:
78
+
79
+ ```ts
80
+ import { MIN_ROWS_ARRAY_FIELD, emptyRows } from '@bison-lab/payload-blocks'
81
+
82
+ {
83
+ name: 'links',
84
+ type: 'array',
85
+ minRows: 2,
86
+ defaultValue: emptyRows(2),
87
+ admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } },
88
+ fields: [...],
89
+ }
90
+ ```
66
91
 
67
92
  **2. Own the registry.**
68
93
 
@@ -137,8 +162,8 @@ renders.
137
162
  | --- | --- | --- |
138
163
  | `hero` | plain markup | No `@bison-lab/ui` counterpart. Override this entry with your own. |
139
164
  | `richText` | `RichText` (Lexical) | From `/rich-text`. |
140
- | `showcasePanels` | `ShowcasePanelsBlock` | 3–6 panels, each with a required image. |
141
- | `processSteps` | `ProcessStepsBlock` | 3–6 ordered steps; advances on a timer. |
165
+ | `showcasePanels` | `ShowcasePanelsBlock` | 3–6 panels, each with a required image. Opens with three. |
166
+ | `processSteps` | `ProcessStepsBlock` | 3–6 ordered steps, numbered by position; advances on a timer. Opens with three. |
142
167
  | `faqColumns` | `FAQColumnsBlock` | Answers are plain text, not Lexical — see the config for why. |
143
168
  | `testimonialMasonry` | `TestimonialMasonry` | Avatars fall back to initials. |
144
169
  | `nap` | `NapBlock` + `JsonLd` | Emits schema.org `LocalBusiness`; `phoneE164` is what the `tel:` link uses. |
@@ -147,6 +172,44 @@ A row whose upload has not resolved is dropped rather than rendered empty, and a
147
172
  block left with nothing to show renders nothing at all. Draft saves skip Payload
148
173
  validation, so live preview genuinely hands renderers incomplete rows.
149
174
 
175
+ ## Samples
176
+
177
+ Every block ships a sample row, so it can be rendered with no CMS behind it:
178
+
179
+ ```ts
180
+ import { blockSamples } from '@bison-lab/payload-blocks'
181
+
182
+ blockSamples.showcasePanels // a ShowcasePanelsBlockData with three panels
183
+ ```
184
+
185
+ `blockSamples` is one map keyed by `blockType`, typed per block
186
+ (`BlockSamples`) and assignable to `Record<BisonBlockType, BisonBlockData>`.
187
+ It comes from the main entry and is React-free, so a Global's field config and
188
+ a route handler can both read it. The Block library page that renders these
189
+ live for an admin is a separate piece of work (BIS-52); this package ships the
190
+ data, not the page.
191
+
192
+ Each sample has realistic copy at the length the layout was designed for
193
+ (three showcase panels, four process steps, six testimonials), and sets every
194
+ field its config has at least once, so a preview shows the block's full range.
195
+
196
+ **Images travel with the sample.** No site media exists in a preview, so every
197
+ upload in a sample is an inline SVG `data:` URL in the `MediaDoc` shape, with
198
+ `width` and `height` set. `resolveMedia` accepts it as-is, and the `data:`
199
+ scheme is how an adapter knows what it has: `next/image` marks a `data:` src
200
+ `unoptimized` by itself, so the adapter above needs no special case. A site
201
+ that wants the same placeholders for its own blocks can build them with
202
+ `sampleImage({ label, width, height, alt })`.
203
+
204
+ The `nap` sample sets `emitJsonLd: false`. The block emits schema.org
205
+ `LocalBusiness` by default, and a preview must not put a fictional clinic into
206
+ a site's structured data.
207
+
208
+ Only what this package ships has a sample here. A site's own blocks supply
209
+ theirs through the Block library factory, whose option BIS-52 names. The hero
210
+ family (BIS-45) adds a sample per hero as each one lands; the plain `hero`
211
+ renderer's is here.
212
+
150
213
  ## Conventions for contributors
151
214
 
152
215
  - **No generated types.** `src/types.ts` hand-writes each block's row shape to
@@ -155,9 +218,21 @@ validation, so live preview genuinely hands renderers incomplete rows.
155
218
  interface here has an index signature — TypeScript will not assign an
156
219
  interface to a type that does.
157
220
  - **`src/index.ts` stays React-free.** Payload loads a site's config outside the
158
- bundler, in `migrate`, `generate:types` and the admin server.
221
+ bundler, in `migrate`, `generate:types` and the admin server. An admin
222
+ component is referenced from a config by its import-map string
223
+ (`MIN_ROWS_ARRAY_FIELD`), never imported.
224
+ - **`src/admin.tsx` is the only entry that loads `@payloadcms/ui`.** It wraps
225
+ Payload's stock fields rather than re-implementing them, so an upgrade
226
+ carries every upstream behaviour along. Editor-facing copy, accessible names
227
+ and control text are not config fields: the `@bison-lab/ui` defaults stand.
159
228
  - **`cn()` is off limits.** It is a client-only export of `@bison-lab/ui`; use
160
229
  the local `cx()`.
161
230
  - **Changing a field is a schema change in every consuming site.** Update
162
231
  `src/types.ts` and the field-name lock in `src/__tests__/configs.test.ts` in
163
232
  the same change, and say "run `payload migrate:create`" in the changeset.
233
+ - **Every block ships a sample.** A new block adds `src/blocks/<slug>/sample.ts`
234
+ beside its `config.ts` and `component.tsx`, typed to its row shape, using
235
+ `sampleImage` for every upload, and registers it in `src/samples.ts`.
236
+ `src/__tests__/samples.test.tsx` fails until it does, and again if the sample
237
+ leaves a config field unset. A new field on an existing block means its
238
+ sample sets that field too.
@@ -0,0 +1,27 @@
1
+
2
+ import { ArrayFieldClientComponent } from "payload";
3
+
4
+ //#region src/admin/min-rows-array-field.d.ts
5
+ /**
6
+ * Payload's array field, with *Remove* refused once the rows are down to
7
+ * `minRows`.
8
+ *
9
+ * This wraps the stock `ArrayField` rather than re-implementing it, so every
10
+ * upstream behaviour (sorting, copy/paste, collapse, localisation) stays put
11
+ * and a Payload upgrade carries it along. The catch is where the row menu
12
+ * lives: Payload portals it into `document.body`, out of reach of any CSS or
13
+ * form context this wrapper could scope. What still reaches the wrapper is
14
+ * the React event: synthetic events bubble through portals along the
15
+ * component tree, so a capture handler here sees the click on *Remove*
16
+ * before the menu's own handler does, and can stop it.
17
+ *
18
+ * Because the menu is portaled, the DOM cannot say whether a given *Remove*
19
+ * belongs to this array or to an array nested inside one of its rows. The
20
+ * trigger that opened the menu *is* in the DOM, though, and every menu opens
21
+ * from its trigger. So the handler remembers, per menu opening, whether the
22
+ * trigger sat directly in this field, and only gates the *Remove* that follows.
23
+ */
24
+ declare const MinRowsArrayField: ArrayFieldClientComponent;
25
+ //#endregion
26
+ export { MinRowsArrayField };
27
+ //# sourceMappingURL=admin.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.d.mts","names":[],"sources":["../src/admin/min-rows-array-field.tsx"],"mappings":";;;;;;AAuBA;;;;;;;;;;;;;;;;;cAAa,iBAAA,EAAmB,yBAAA"}
package/dist/admin.mjs ADDED
@@ -0,0 +1,82 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { ArrayField, toast, useField, useTranslation } from "@payloadcms/ui";
4
+ import { useCallback, useRef } from "react";
5
+ //#region src/admin/min-rows-array-field.tsx
6
+ /**
7
+ * Payload's array field, with *Remove* refused once the rows are down to
8
+ * `minRows`.
9
+ *
10
+ * This wraps the stock `ArrayField` rather than re-implementing it, so every
11
+ * upstream behaviour (sorting, copy/paste, collapse, localisation) stays put
12
+ * and a Payload upgrade carries it along. The catch is where the row menu
13
+ * lives: Payload portals it into `document.body`, out of reach of any CSS or
14
+ * form context this wrapper could scope. What still reaches the wrapper is
15
+ * the React event: synthetic events bubble through portals along the
16
+ * component tree, so a capture handler here sees the click on *Remove*
17
+ * before the menu's own handler does, and can stop it.
18
+ *
19
+ * Because the menu is portaled, the DOM cannot say whether a given *Remove*
20
+ * belongs to this array or to an array nested inside one of its rows. The
21
+ * trigger that opened the menu *is* in the DOM, though, and every menu opens
22
+ * from its trigger. So the handler remembers, per menu opening, whether the
23
+ * trigger sat directly in this field, and only gates the *Remove* that follows.
24
+ */
25
+ const MinRowsArrayField = (props) => {
26
+ const { field, path: pathFromProps } = props;
27
+ const minRows = field.minRows ?? (field.required ? 1 : 0);
28
+ const { rows = [] } = useField({
29
+ hasRows: true,
30
+ potentiallyStalePath: pathFromProps
31
+ });
32
+ const { i18n, t } = useTranslation();
33
+ const rootRef = useRef(null);
34
+ const menuIsOurs = useRef(false);
35
+ return /* @__PURE__ */ jsx("div", {
36
+ ref: rootRef,
37
+ onClickCapture: useCallback((event) => {
38
+ const target = event.target;
39
+ if (!(target instanceof Element)) return;
40
+ const trigger = target.closest(".array-actions__button");
41
+ if (trigger) {
42
+ const owner = trigger.closest(".array-field, .blocks-field");
43
+ menuIsOurs.current = owner !== null && owner === rootRef.current?.querySelector(".array-field");
44
+ return;
45
+ }
46
+ if (!target.closest(".array-actions__remove")) return;
47
+ if (!menuIsOurs.current || rows.length > minRows) return;
48
+ event.preventDefault();
49
+ event.stopPropagation();
50
+ toast.error(t("validation:requiresAtLeast", {
51
+ count: minRows,
52
+ label: rowLabel(field, minRows, i18n.language) ?? t(minRows > 1 ? "general:rows" : "general:row")
53
+ }));
54
+ }, [
55
+ field,
56
+ i18n.language,
57
+ minRows,
58
+ rows.length,
59
+ t
60
+ ]),
61
+ children: /* @__PURE__ */ jsx(ArrayField, { ...props })
62
+ });
63
+ };
64
+ /**
65
+ * The word for a row, as Payload's own "requires at least" banner picks it:
66
+ * the plural label above one, the singular otherwise, the field label failing
67
+ * both. Client-side labels are static — a string or a per-language record.
68
+ */
69
+ function rowLabel(field, minRows, language) {
70
+ return staticLabel(minRows > 1 ? field.labels?.plural : field.labels?.singular, language) ?? staticLabel(field.label, language);
71
+ }
72
+ function staticLabel(label, language) {
73
+ if (typeof label === "string") return label;
74
+ if (label && typeof label === "object") {
75
+ const byLanguage = label;
76
+ return byLanguage[language] ?? byLanguage.en ?? Object.values(byLanguage)[0];
77
+ }
78
+ }
79
+ //#endregion
80
+ export { MinRowsArrayField };
81
+
82
+ //# sourceMappingURL=admin.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.mjs","names":[],"sources":["../src/admin/min-rows-array-field.tsx"],"sourcesContent":["import { ArrayField, toast, useField, useTranslation } from \"@payloadcms/ui\";\nimport type { ArrayFieldClientComponent, ArrayFieldClientProps } from \"payload\";\nimport { useCallback, useRef } from \"react\";\n\n/**\n * Payload's array field, with *Remove* refused once the rows are down to\n * `minRows`.\n *\n * This wraps the stock `ArrayField` rather than re-implementing it, so every\n * upstream behaviour (sorting, copy/paste, collapse, localisation) stays put\n * and a Payload upgrade carries it along. The catch is where the row menu\n * lives: Payload portals it into `document.body`, out of reach of any CSS or\n * form context this wrapper could scope. What still reaches the wrapper is\n * the React event: synthetic events bubble through portals along the\n * component tree, so a capture handler here sees the click on *Remove*\n * before the menu's own handler does, and can stop it.\n *\n * Because the menu is portaled, the DOM cannot say whether a given *Remove*\n * belongs to this array or to an array nested inside one of its rows. The\n * trigger that opened the menu *is* in the DOM, though, and every menu opens\n * from its trigger. So the handler remembers, per menu opening, whether the\n * trigger sat directly in this field, and only gates the *Remove* that follows.\n */\nexport const MinRowsArrayField: ArrayFieldClientComponent = (props) => {\n const { field, path: pathFromProps } = props;\n const minRows = field.minRows ?? (field.required ? 1 : 0);\n\n const { rows = [] } = useField({\n hasRows: true,\n potentiallyStalePath: pathFromProps,\n });\n const { i18n, t } = useTranslation();\n\n const rootRef = useRef<HTMLDivElement>(null);\n const menuIsOurs = useRef(false);\n\n const onClickCapture = useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n\n const trigger = target.closest(\".array-actions__button\");\n if (trigger) {\n // The nearest array or blocks field around the trigger is the field\n // whose menu is opening. Ours is the first one inside the wrapper.\n const owner = trigger.closest(\".array-field, .blocks-field\");\n menuIsOurs.current =\n owner !== null && owner === rootRef.current?.querySelector(\".array-field\");\n return;\n }\n\n if (!target.closest(\".array-actions__remove\")) return;\n if (!menuIsOurs.current || rows.length > minRows) return;\n\n event.preventDefault();\n event.stopPropagation();\n toast.error(\n t(\"validation:requiresAtLeast\", {\n count: minRows,\n label: rowLabel(field, minRows, i18n.language) ??\n t(minRows > 1 ? \"general:rows\" : \"general:row\"),\n }),\n );\n },\n [field, i18n.language, minRows, rows.length, t],\n );\n\n return (\n <div ref={rootRef} onClickCapture={onClickCapture}>\n <ArrayField {...props} />\n </div>\n );\n};\n\n/**\n * The word for a row, as Payload's own \"requires at least\" banner picks it:\n * the plural label above one, the singular otherwise, the field label failing\n * both. Client-side labels are static — a string or a per-language record.\n */\nfunction rowLabel(\n field: ArrayFieldClientProps[\"field\"],\n minRows: number,\n language: string,\n): string | undefined {\n const label = minRows > 1 ? field.labels?.plural : field.labels?.singular;\n return staticLabel(label, language) ?? staticLabel(field.label, language);\n}\n\nfunction staticLabel(label: unknown, language: string): string | undefined {\n if (typeof label === \"string\") return label;\n if (label && typeof label === \"object\") {\n const byLanguage = label as Record<string, string | undefined>;\n return byLanguage[language] ?? byLanguage.en ?? Object.values(byLanguage)[0];\n }\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,qBAAgD,UAAU;CACrE,MAAM,EAAE,OAAO,MAAM,kBAAkB;CACvC,MAAM,UAAU,MAAM,YAAY,MAAM,WAAW,IAAI;CAEvD,MAAM,EAAE,OAAO,EAAE,KAAK,SAAS;EAC7B,SAAS;EACT,sBAAsB;EACvB,CAAC;CACF,MAAM,EAAE,MAAM,MAAM,gBAAgB;CAEpC,MAAM,UAAU,OAAuB,KAAK;CAC5C,MAAM,aAAa,OAAO,MAAM;AAiChC,QACE,oBAAC,OAAD;EAAK,KAAK;EAAS,gBAhCE,aACpB,UAA4C;GAC3C,MAAM,SAAS,MAAM;AACrB,OAAI,EAAE,kBAAkB,SAAU;GAElC,MAAM,UAAU,OAAO,QAAQ,yBAAyB;AACxD,OAAI,SAAS;IAGX,MAAM,QAAQ,QAAQ,QAAQ,8BAA8B;AAC5D,eAAW,UACT,UAAU,QAAQ,UAAU,QAAQ,SAAS,cAAc,eAAe;AAC5E;;AAGF,OAAI,CAAC,OAAO,QAAQ,yBAAyB,CAAE;AAC/C,OAAI,CAAC,WAAW,WAAW,KAAK,SAAS,QAAS;AAElD,SAAM,gBAAgB;AACtB,SAAM,iBAAiB;AACvB,SAAM,MACJ,EAAE,8BAA8B;IAC9B,OAAO;IACP,OAAO,SAAS,OAAO,SAAS,KAAK,SAAS,IAC5C,EAAE,UAAU,IAAI,iBAAiB,cAAc;IAClD,CAAC,CACH;KAEH;GAAC;GAAO,KAAK;GAAU;GAAS,KAAK;GAAQ;GAAE,CAChD;YAIG,oBAAC,YAAD,EAAY,GAAI,OAAS,CAAA;EACrB,CAAA;;;;;;;AASV,SAAS,SACP,OACA,SACA,UACoB;AAEpB,QAAO,YADO,UAAU,IAAI,MAAM,QAAQ,SAAS,MAAM,QAAQ,UACvC,SAAS,IAAI,YAAY,MAAM,OAAO,SAAS;;AAG3E,SAAS,YAAY,OAAgB,UAAsC;AACzE,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI,SAAS,OAAO,UAAU,UAAU;EACtC,MAAM,aAAa;AACnB,SAAO,WAAW,aAAa,WAAW,MAAM,OAAO,OAAO,WAAW,CAAC"}
package/dist/index.d.mts CHANGED
@@ -132,6 +132,29 @@ declare function headingFields({
132
132
  eyebrowDescription
133
133
  }?: HeadingFieldsOptions): Field[];
134
134
  //#endregion
135
+ //#region src/fields/min-rows.d.ts
136
+ /**
137
+ * The import-map path of the array field that refuses to go below `minRows`.
138
+ *
139
+ * Payload gates *Add* on `maxRows` but never gates *Remove* on `minRows`: an
140
+ * editor can delete the third showcase panel, see a "requires 3 panels"
141
+ * note, and only learn on publish that the section is invalid. Payload has no
142
+ * option for this, so it is an admin component (`@bison-lab/payload-blocks/admin`)
143
+ * referenced here by package specifier. Every array in this package with a
144
+ * `minRows` uses it, and a site can put it on its own arrays:
145
+ *
146
+ * ```ts
147
+ * { name: 'items', type: 'array', minRows: 2,
148
+ * admin: { components: { Field: MIN_ROWS_ARRAY_FIELD } }, fields: [...] }
149
+ * ```
150
+ *
151
+ * Consuming sites pick it up by re-running `payload generate:importmap`. Until
152
+ * they do, Payload logs the missing entry and falls back to its stock field.
153
+ */
154
+ declare const MIN_ROWS_ARRAY_FIELD = "@bison-lab/payload-blocks/admin#MinRowsArrayField";
155
+ /** Empty rows for an array's `defaultValue`, so a block opens at its minimum. */
156
+ declare function emptyRows(count: number): Record<string, never>[];
157
+ //#endregion
135
158
  //#region src/media.d.ts
136
159
  /**
137
160
  * The upload side of the boundary.
@@ -248,17 +271,11 @@ interface ShowcasePanelsBlockData extends BlockRow<"showcasePanels"> {
248
271
  watermark?: string | null;
249
272
  spineVariant?: ("numbered" | "volume" | "minimal") | null;
250
273
  defaultActiveIndex?: number | null;
251
- ariaLabel?: string | null;
252
- labels?: {
253
- selected?: string | null;
254
- preview?: string | null;
255
- };
256
274
  }
257
275
  interface ProcessStepsItemData {
258
276
  title?: string | null;
259
277
  description?: string | null;
260
278
  image?: MediaValue;
261
- step?: number | null;
262
279
  id?: string | null;
263
280
  }
264
281
  interface ProcessStepsBlockData extends BlockRow<"processSteps"> {
@@ -267,11 +284,6 @@ interface ProcessStepsBlockData extends BlockRow<"processSteps"> {
267
284
  autoAdvance?: boolean | null;
268
285
  autoAdvanceDuration?: number | null;
269
286
  pauseOnHover?: boolean | null;
270
- ariaLabel?: string | null;
271
- labels?: {
272
- pause?: string | null;
273
- resume?: string | null;
274
- };
275
287
  }
276
288
  interface FaqColumnsItemData {
277
289
  question?: string | null;
@@ -340,5 +352,59 @@ type BisonBlockData = HeroBlockData | RichTextBlockData | ShowcasePanelsBlockDat
340
352
  /** The `blockType` of every block this package ships. */
341
353
  type BisonBlockType = BisonBlockData["blockType"];
342
354
  //#endregion
343
- export { type BisonBlockData, type BisonBlockType, FaqColumnsBlock, type FaqColumnsBlockData, type FaqColumnsItemData, type HeadingFieldsOptions, HeroBlock, type HeroBlockData, type ImageFieldOptions, type LinkFieldOptions, type LinkFieldsOptions, type LinkValue, type MediaDoc, type MediaValue, NapBlock, type NapBlockData, type NapDepartmentData, ProcessStepsBlock, type ProcessStepsBlockData, type ProcessStepsItemData, type ResolvedMedia, RichTextBlock, type RichTextBlockData, type RichTextContent, ShowcasePanelsBlock, type ShowcasePanelsBlockData, type ShowcasePanelsItemData, TestimonialMasonryBlock, type TestimonialMasonryBlockData, type TestimonialMasonryItemData, headingFields, imageField, linkField, linkFields, resolveMedia };
355
+ //#region src/samples.d.ts
356
+ /**
357
+ * One sample row per block, each typed to its own row shape. Assignable to
358
+ * `Record<BisonBlockType, BisonBlockData>` wherever the wider type is wanted.
359
+ */
360
+ type BlockSamples = { [K in BisonBlockType]: Extract<BisonBlockData, {
361
+ blockType: K;
362
+ }> };
363
+ /**
364
+ * Every block the package ships, rendered without a CMS row.
365
+ *
366
+ * A Block library page (BIS-52) renders these live so an admin can see each
367
+ * block before enabling it. Each sample lives beside its block's `config.ts`
368
+ * and `component.tsx` as `sample.ts`, and `src/__tests__/samples.test.tsx`
369
+ * locks the contract: one entry per slug, `blockType` matching the key, every
370
+ * config field exercised at least once, and every image self-contained.
371
+ *
372
+ * React-free on purpose: it is read from a Global's field config, which
373
+ * Payload loads in plain Node like the rest of this entry, as well as from a
374
+ * route handler. Only what this package ships is here; a site supplies samples
375
+ * for its own blocks through the Block library factory, whose option BIS-52
376
+ * names.
377
+ */
378
+ declare const blockSamples: BlockSamples;
379
+ //#endregion
380
+ //#region src/sample-image.d.ts
381
+ interface SampleImageOptions {
382
+ /** Text drawn across the placeholder, e.g. "Panel 1 · 1400 × 1000". */
383
+ label: string;
384
+ width: number;
385
+ height: number;
386
+ /** Alt text for the document. Defaults to the label. */
387
+ alt?: string;
388
+ }
389
+ /**
390
+ * A placeholder upload document for a block sample.
391
+ *
392
+ * A preview has no site media behind it, so the image has to travel with the
393
+ * sample. This is an inline SVG as a `data:` URL, in the `MediaDoc` shape
394
+ * `resolveMedia` already narrows, with `width` and `height` set so an image
395
+ * adapter can reserve the box. The scheme is what tells an adapter which kind
396
+ * of source it has: `next/image` treats a `data:` src as `unoptimized` on its
397
+ * own, so a site's adapter needs no special case for samples.
398
+ *
399
+ * Neutral greys rather than theme tokens: the SVG is a standalone document
400
+ * and cannot see the page's custom properties.
401
+ */
402
+ declare function sampleImage({
403
+ label,
404
+ width,
405
+ height,
406
+ alt
407
+ }: SampleImageOptions): MediaDoc;
408
+ //#endregion
409
+ export { type BisonBlockData, type BisonBlockType, type BlockSamples, FaqColumnsBlock, type FaqColumnsBlockData, type FaqColumnsItemData, type HeadingFieldsOptions, HeroBlock, type HeroBlockData, type ImageFieldOptions, type LinkFieldOptions, type LinkFieldsOptions, type LinkValue, MIN_ROWS_ARRAY_FIELD, type MediaDoc, type MediaValue, NapBlock, type NapBlockData, type NapDepartmentData, ProcessStepsBlock, type ProcessStepsBlockData, type ProcessStepsItemData, type ResolvedMedia, RichTextBlock, type RichTextBlockData, type RichTextContent, type SampleImageOptions, ShowcasePanelsBlock, type ShowcasePanelsBlockData, type ShowcasePanelsItemData, TestimonialMasonryBlock, type TestimonialMasonryBlockData, type TestimonialMasonryItemData, blockSamples, emptyRows, headingFields, imageField, linkField, linkFields, resolveMedia, sampleImage };
344
410
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/blocks/hero/config.ts","../src/blocks/rich-text/config.ts","../src/blocks/showcase-panels/config.ts","../src/blocks/process-steps/config.ts","../src/blocks/faq-columns/config.ts","../src/blocks/testimonial-masonry/config.ts","../src/blocks/nap/config.ts","../src/fields/image.ts","../src/fields/link.ts","../src/fields/heading.ts","../src/media.ts","../src/types.ts"],"mappings":"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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/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/sample-image.ts"],"mappings":"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"}