@reblu/site-blocks 0.8.0 → 0.9.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/CHANGELOG.md +24 -0
- package/README.md +109 -0
- package/dist/index.cjs +469 -141
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +101 -3
- package/dist/index.d.ts +101 -3
- package/dist/index.js +466 -142
- package/dist/index.js.map +1 -1
- package/dist/seo.cjs +59 -0
- package/dist/seo.cjs.map +1 -0
- package/dist/seo.d.cts +47 -0
- package/dist/seo.d.ts +47 -0
- package/dist/seo.js +31 -0
- package/dist/seo.js.map +1 -0
- package/package.json +13 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @reblu/site-blocks
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 14814b0: Add a shared renderer for page registry blocks and FAQ structured data (REB-850).
|
|
8
|
+
- `PageBlocks` (root entry, Server Component): renders `PageDetail.blocks`,
|
|
9
|
+
applying each block's mirrored section `heading`, `backgroundColor` and `cta`.
|
|
10
|
+
Every block type's body is overridable through a `components` map keyed by
|
|
11
|
+
canonical type. Unknown block types and embeds with `resolved: null` render
|
|
12
|
+
nothing; the deprecated `doctor_embed`/`specialty_embed` aliases render
|
|
13
|
+
through the `profile_embed`/`page_embed` renderers. Blocks render in `order`;
|
|
14
|
+
a `cta` slot with no heading, no usable CTA and no override renders nothing.
|
|
15
|
+
Pass `idPrefix` when a page renders more than one `PageBlocks` instance, so
|
|
16
|
+
section heading ids stay unique.
|
|
17
|
+
- `FaqAccordion` (root entry, Server Component): native `<details>`/`<summary>`
|
|
18
|
+
FAQ list with configurable heading levels; no JavaScript required.
|
|
19
|
+
- `buildFaqPageJsonLd` and `collectFaqItems` on the new directive-free
|
|
20
|
+
`@reblu/site-blocks/seo` subpath (also on the root): build a schema.org
|
|
21
|
+
`FAQPage` object, callable from Server Components. Questions follow block
|
|
22
|
+
`order` and keep the author's plain text (whitespace collapsed only), so the
|
|
23
|
+
structured data matches what `FaqAccordion` shows.
|
|
24
|
+
|
|
25
|
+
Additive only — no existing export changes.
|
|
26
|
+
|
|
3
27
|
## 0.8.0
|
|
4
28
|
|
|
5
29
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -18,6 +18,8 @@ a different-looking site for every tenant, with zero forks.
|
|
|
18
18
|
| `Treatment`, `Specialty`, `ProfileCard`, `ProductCard` | `.` | Presentational organisms over the SITE API contracts. RSC-safe. |
|
|
19
19
|
| `ShopFilters` | `.` | Plug-and-play shop filter chrome over `GET /api/site/shop/facets`. RSC-safe. See "Shop filters" below. |
|
|
20
20
|
| `CartLineList`, `CartSummary`, `AddedToCartToast`, `CheckoutLoginPrompt`, `CheckoutForm`, `OrderConfirmation` | `.` | Cart & checkout organisms. RSC-safe by construction. See "Cart & checkout" below. |
|
|
21
|
+
| `PageBlocks`, `FaqAccordion` | `.` | Registry-block renderer (`PageDetail.blocks`) and native `<details>` FAQ. Server Components. See "Page blocks" below. |
|
|
22
|
+
| `buildFaqPageJsonLd`, `collectFaqItems` | `./seo` | schema.org `FAQPage` structured data. Directive-free (also on `.`). See "Page blocks" below. |
|
|
21
23
|
| `useCollection` | `./client` | Headless filterable/sortable/paginated collection hook (facets, category, tag, search). Serves both page and blog indexes. |
|
|
22
24
|
| `Collection` | `./client` | Token-driven presentational grid over `useCollection`. |
|
|
23
25
|
| `ShareButtons` | `./client` | Social share (X / LinkedIn / WhatsApp / copy). |
|
|
@@ -378,6 +380,113 @@ Every title/description is overridable via `labels` (see
|
|
|
378
380
|
"`useShopOrderStatus` — the order-confirmation flow" for the full wiring
|
|
379
381
|
recipe.
|
|
380
382
|
|
|
383
|
+
## Page blocks
|
|
384
|
+
|
|
385
|
+
`PageBlocks` renders a page's registry blocks (`PageDetail.blocks` from
|
|
386
|
+
`GET /api/site/pages/[slug]`) so a SITE does not hand-write its own block
|
|
387
|
+
switch. It is a **Server Component** — no hooks, no `'use client'` — exported
|
|
388
|
+
from the RSC-safe root entry.
|
|
389
|
+
|
|
390
|
+
```tsx
|
|
391
|
+
import { PageBlocks } from '@reblu/site-blocks'
|
|
392
|
+
import { buildFaqPageJsonLd, collectFaqItems } from '@reblu/site-blocks/seo'
|
|
393
|
+
import { JsonLd } from '@reblu/site-next/json-ld'
|
|
394
|
+
|
|
395
|
+
export default async function Page({ params }) {
|
|
396
|
+
const page = await getPage((await params).slug)
|
|
397
|
+
const faq = buildFaqPageJsonLd(collectFaqItems(page.blocks))
|
|
398
|
+
return (
|
|
399
|
+
<main>
|
|
400
|
+
<h1>{page.title}</h1>
|
|
401
|
+
<PageBlocks blocks={page.blocks} embedHref={(r) => `/${r.slug}`} />
|
|
402
|
+
{faq && <JsonLd id='faq' data={faq} />}
|
|
403
|
+
</main>
|
|
404
|
+
)
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Each block is wrapped in a `<section data-slot="page-block" data-block-type="…">`
|
|
409
|
+
that applies the parent section's mirrored fields (REB-849):
|
|
410
|
+
|
|
411
|
+
- **`heading`** — rendered at `headingLevel` (default `2`) and used as the
|
|
412
|
+
section's accessible name. Headings inside the block go one level deeper.
|
|
413
|
+
- **`backgroundColor`** — applied as the section background. Theme tokens
|
|
414
|
+
(`var(--primary)`, `var(--secondary)`, `var(--accent)`) get their paired
|
|
415
|
+
`-foreground` token; a custom hex gets white or near-black text by WCAG
|
|
416
|
+
luminance; any other value keeps the theme foreground.
|
|
417
|
+
- **`cta`** — rendered after the body as a link styled with `buttonVariants`;
|
|
418
|
+
external CTAs open in a new tab with `rel="noopener noreferrer"`.
|
|
419
|
+
|
|
420
|
+
| Prop | Default | Purpose |
|
|
421
|
+
| ------------------- | -------------------- | ---------------------------------------------------------- |
|
|
422
|
+
| `blocks` | — | `PageDetail.blocks`. `null`/empty renders nothing. |
|
|
423
|
+
| `components` | `{}` | Per-type body renderer overrides (see below). |
|
|
424
|
+
| `headingLevel` | `2` | Level of each section heading. |
|
|
425
|
+
| `embedHref` | `` r => `/${r.slug}` `` | Href of an embedded page — route it per `r.pageType`. |
|
|
426
|
+
| `idPrefix` | `'page-block'` | Namespace for heading ids; pass distinct values when one document renders `PageBlocks` twice with the same blocks. |
|
|
427
|
+
| `className`, `sectionClassName` | — | Extra classes on the list / on each section. |
|
|
428
|
+
|
|
429
|
+
### Fail-closed rules
|
|
430
|
+
|
|
431
|
+
- An **unknown `blockType`** (e.g. a type added to the API before this package
|
|
432
|
+
is upgraded) renders nothing — not even its heading — and never throws.
|
|
433
|
+
- An **embed** (`page_embed`, `profile_embed`) whose `resolved` is `null` or
|
|
434
|
+
absent renders nothing: no heading, no dead link.
|
|
435
|
+
- A **`cta` block** references a live CTA by `data.slot`, which only the SITE
|
|
436
|
+
can resolve; by default it renders just the section heading and mirrored
|
|
437
|
+
CTA, and nothing at all when it has neither. Supply `components.cta` to
|
|
438
|
+
render the slot.
|
|
439
|
+
- **Deprecated aliases** `doctor_embed` and `specialty_embed` (REB-852) render
|
|
440
|
+
through the `profile_embed` and `page_embed` renderers respectively.
|
|
441
|
+
|
|
442
|
+
### Overriding a block type
|
|
443
|
+
|
|
444
|
+
`components` is a partial map keyed by **canonical** block type. An override
|
|
445
|
+
replaces only the block body; the section wrapper (heading, background, CTA)
|
|
446
|
+
still applies.
|
|
447
|
+
|
|
448
|
+
```tsx
|
|
449
|
+
import { PageBlocks, type BlockRendererProps } from '@reblu/site-blocks'
|
|
450
|
+
|
|
451
|
+
function TeamCard({ block, headingLevel, embedHref }: BlockRendererProps<'profile_embed'>) {
|
|
452
|
+
// `block.resolved` is never null here; `block.blockType` may be the deprecated alias.
|
|
453
|
+
return <a href={embedHref(block.resolved)}>{block.resolved.title}</a>
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
<PageBlocks blocks={page.blocks} components={{ profile_embed: TeamCard }} />
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Every renderer receives `{ block, headingLevel, embedHref }`. Overrides may be
|
|
460
|
+
async Server Components, or Client Components when they need interactivity.
|
|
461
|
+
|
|
462
|
+
### `FaqAccordion`
|
|
463
|
+
|
|
464
|
+
Native `<details>`/`<summary>` — opens and closes with no JavaScript, keyboard
|
|
465
|
+
and screen-reader semantics come from the browser. Answers render as text,
|
|
466
|
+
never as markup; line breaks are kept.
|
|
467
|
+
|
|
468
|
+
| Prop | Default | Purpose |
|
|
469
|
+
| ---------------------- | ------- | ------------------------------------------------------------- |
|
|
470
|
+
| `items` | — | `{ q, a }[]` — the `faq` block's `data.items`. |
|
|
471
|
+
| `heading` | — | Optional heading above the list. |
|
|
472
|
+
| `headingLevel` | `2` | Level of `heading`. |
|
|
473
|
+
| `questionHeadingLevel` | — | When set, wraps each question in `<h{n}>` inside its summary. |
|
|
474
|
+
| `defaultOpen` | `[]` | Indexes of items open on first render. |
|
|
475
|
+
|
|
476
|
+
### `buildFaqPageJsonLd(items)`
|
|
477
|
+
|
|
478
|
+
Returns a schema.org `FAQPage` object (or `null` when no item has both a
|
|
479
|
+
question and an answer — an empty `FAQPage` is invalid). Questions and answers
|
|
480
|
+
are plain text and pass through verbatim — only whitespace is collapsed — so the
|
|
481
|
+
structured data says exactly what `FaqAccordion` shows (`a < b` survives).
|
|
482
|
+
`collectFaqItems` orders blocks by `order` with the same helper `PageBlocks`
|
|
483
|
+
uses, so question order matches the page. It is a pure function exported from the directive-free
|
|
484
|
+
`@reblu/site-blocks/seo` entry, so Server Components and `generateMetadata` can
|
|
485
|
+
call it — a helper reachable only through a `'use client'` entry cannot be
|
|
486
|
+
called on the server (REB-846). `test/seo-entry.test.ts` guards this on the
|
|
487
|
+
emitted `dist`. The caller serialises; `@reblu/site-next`'s `<JsonLd>` escapes
|
|
488
|
+
the `</script>` boundary.
|
|
489
|
+
|
|
381
490
|
## Security boundaries
|
|
382
491
|
|
|
383
492
|
Two sinks, two different helpers, in two different packages — do not reach for
|