@reblu/site-blocks 0.7.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/dist/index.d.cts CHANGED
@@ -1,6 +1,8 @@
1
- import { ShopErrorCode, PageDetail, PageListItem, ShopProductListItem, ShopFacets, ShopOrder } from '@reblu/site-contracts';
1
+ import { ShopErrorCode, PageDetail, PageListItem, ShopProductListItem, BlockType, PageBlock, ResolvedEmbed, ShopFacets, ShopOrder } from '@reblu/site-contracts';
2
+ import { FaqItem } from './seo.cjs';
3
+ export { FaqPageJsonLd, buildFaqPageJsonLd, collectFaqItems } from './seo.cjs';
2
4
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
- import { ReactNode, FormEvent } from 'react';
5
+ import { ComponentType, ReactNode, FormEvent } from 'react';
4
6
 
5
7
  declare function readingTime(content: string | null | undefined): number | null;
6
8
 
@@ -302,6 +304,102 @@ interface ProductCardProps {
302
304
  }
303
305
  declare function ProductCard({ product, href, currencyCode, locale, className }: ProductCardProps): react_jsx_runtime.JSX.Element;
304
306
 
307
+ /** A heading level a caller can configure — `h1`…`h6`. */
308
+ type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
309
+
310
+ /**
311
+ * REB-850 — accessible FAQ list on native `<details>`/`<summary>`.
312
+ *
313
+ * A Server Component: no hooks, no `'use client'`, no JavaScript needed to
314
+ * open or close an item — the browser owns the disclosure state, keyboard
315
+ * support and the expanded/collapsed semantics. Answers render as TEXT (React
316
+ * escapes them; line breaks are preserved), never as markup.
317
+ *
318
+ * Pair it with `buildFaqPageJsonLd` (`@reblu/site-blocks/seo`) for the
319
+ * matching schema.org `FAQPage` structured data.
320
+ */
321
+ interface FaqAccordionProps {
322
+ items: readonly FaqItem[];
323
+ /** Optional heading above the list. */
324
+ heading?: string | null;
325
+ /** Level of `heading`. Default `2`. */
326
+ headingLevel?: HeadingLevel;
327
+ /**
328
+ * When set, each question is wrapped in `<h{level}>` inside its `<summary>`
329
+ * so it appears in the document outline. Default: plain summary text.
330
+ */
331
+ questionHeadingLevel?: HeadingLevel;
332
+ /** Indexes of the items rendered open initially. */
333
+ defaultOpen?: readonly number[];
334
+ className?: string;
335
+ }
336
+ declare function FaqAccordion({ items, heading, headingLevel, questionHeadingLevel, defaultOpen, className, }: FaqAccordionProps): react_jsx_runtime.JSX.Element | null;
337
+
338
+ /**
339
+ * REB-850 — `PageBlocks`: the shared renderer for a page's registry blocks
340
+ * (`PageDetail.blocks`, REB-75), so a SITE stops hand-writing its own.
341
+ *
342
+ * A SERVER COMPONENT by default: no hooks, no `'use client'`, reachable from the
343
+ * RSC-safe root entry (`test/rsc-safety.test.ts` renders it under
344
+ * `--conditions=react-server`).
345
+ *
346
+ * Each block is wrapped in a `<section>` carrying the parent section's
347
+ * mirrored fields (REB-849): `heading`, `backgroundColor` and `cta`. The body
348
+ * comes from a per-type renderer that the `components` map can replace.
349
+ *
350
+ * Fail-closed rules:
351
+ * • an unknown `blockType` renders nothing (the whole section) and never throws;
352
+ * • an embed whose `resolved` is `null`/absent renders nothing — no heading,
353
+ * no dead link;
354
+ * • deprecated alias types (`doctor_embed`, `specialty_embed` — REB-852)
355
+ * render through their generic type's renderer (`profile_embed`, `page_embed`).
356
+ */
357
+ type DeprecatedAlias = 'doctor_embed' | 'specialty_embed';
358
+ /** Block types a renderer is registered under — deprecated aliases resolve to these. */
359
+ type CanonicalBlockType = Exclude<BlockType, DeprecatedAlias>;
360
+ type EmbedType = 'page_embed' | 'profile_embed';
361
+ type WireTypesOf<T extends CanonicalBlockType> = T extends 'profile_embed' ? 'profile_embed' | 'doctor_embed' : T extends 'page_embed' ? 'page_embed' | 'specialty_embed' : T;
362
+ /**
363
+ * The block a renderer for `T` receives — including its deprecated alias, and
364
+ * with `resolved` guaranteed non-null for embeds (unresolved ones never render).
365
+ */
366
+ type RenderableBlock<T extends CanonicalBlockType> = T extends EmbedType ? Extract<PageBlock, {
367
+ blockType: WireTypesOf<T>;
368
+ }> & {
369
+ resolved: ResolvedEmbed;
370
+ } : Extract<PageBlock, {
371
+ blockType: WireTypesOf<T>;
372
+ }>;
373
+ interface BlockRendererProps<T extends CanonicalBlockType> {
374
+ block: RenderableBlock<T>;
375
+ /** Level for headings INSIDE the block — one below the section heading when there is one. */
376
+ headingLevel: HeadingLevel;
377
+ /** Builds an embed's href (the `embedHref` prop, or the default `/${slug}`). */
378
+ embedHref: (resolved: ResolvedEmbed) => string;
379
+ }
380
+ /** Per-block-type renderer overrides, keyed by canonical type. */
381
+ type PageBlocksComponents = Partial<{
382
+ [T in CanonicalBlockType]: ComponentType<BlockRendererProps<T>>;
383
+ }>;
384
+ interface PageBlocksProps {
385
+ blocks: readonly PageBlock[] | null | undefined;
386
+ /** Replace the body renderer of any block type. */
387
+ components?: PageBlocksComponents;
388
+ /** Level of each section heading. Default `2` (the page title is the `h1`). */
389
+ headingLevel?: HeadingLevel;
390
+ /** Href for an embedded page. Default `/${resolved.slug}`. */
391
+ embedHref?: (resolved: ResolvedEmbed) => string;
392
+ /**
393
+ * Namespace for the section heading ids (`aria-labelledby` targets). Pass a
394
+ * distinct value when one document renders `PageBlocks` more than once with
395
+ * overlapping blocks. Sanitised to a valid IDREF. Default `'page-block'`.
396
+ */
397
+ idPrefix?: string;
398
+ className?: string;
399
+ sectionClassName?: string;
400
+ }
401
+ declare function PageBlocks({ blocks, components, headingLevel, embedHref, idPrefix, className, sectionClassName, }: PageBlocksProps): react_jsx_runtime.JSX.Element | null;
402
+
305
403
  /**
306
404
  * `ShopFilters` — plug-and-play SITE filter chrome over `GET /api/site/shop/facets`
307
405
  * (REB-809). Which axes appear, their order and labels come entirely from
@@ -645,6 +743,14 @@ interface CheckoutFormProps {
645
743
  contact: CheckoutContactValues;
646
744
  onContactChange: (values: CheckoutContactValues) => void;
647
745
  contactErrors?: CheckoutContactErrors;
746
+ /** Country asserted by the SITE for national phone input; omit rather than guessing. */
747
+ phoneRegion?: string;
748
+ /** Optional raw checkout API error body; only INVALID_INPUT/customerPhone marks the phone field. */
749
+ checkoutError?: {
750
+ code?: unknown;
751
+ error?: unknown;
752
+ details?: unknown;
753
+ } | null;
648
754
  discountCode: string;
649
755
  onDiscountCodeChange: (value: string) => void;
650
756
  onApplyDiscount: () => void;
@@ -670,7 +776,7 @@ interface CheckoutFormProps {
670
776
  locale?: string;
671
777
  className?: string;
672
778
  }
673
- declare function CheckoutForm({ contact, onContactChange, contactErrors, discountCode, onDiscountCodeChange, onApplyDiscount, onRemoveDiscount, appliedDiscount, discountApplying, discountError, loginPrompt, children, submitting, submitDisabled, onSubmit, labels, currencyCode, locale, className, }: CheckoutFormProps): react_jsx_runtime.JSX.Element;
779
+ declare function CheckoutForm({ contact, onContactChange, contactErrors, phoneRegion, checkoutError, discountCode, onDiscountCodeChange, onApplyDiscount, onRemoveDiscount, appliedDiscount, discountApplying, discountError, loginPrompt, children, submitting, submitDisabled, onSubmit, labels, currencyCode, locale, className, }: CheckoutFormProps): react_jsx_runtime.JSX.Element;
674
780
 
675
781
  /**
676
782
  * `OrderConfirmation` — presentational order-confirmation view (REB-829,
@@ -732,4 +838,4 @@ interface OrderConfirmationProps {
732
838
  }
733
839
  declare function OrderConfirmation({ status, order, sessionId, cartHref, onRecheck, labels, currencyCode, locale, className, }: OrderConfirmationProps): react_jsx_runtime.JSX.Element;
734
840
 
735
- export { ALLOWED_YOUTUBE_HOSTS, AddedToCartToast, type AddedToCartToastLabels, type AddedToCartToastProps, type AddedToCartToastStatus, type CartLineItemView, CartLineList, type CartLineListLabels, type CartLineListProps, CartSummary, type CartSummaryLabels, type CartSummaryProps, type CheckoutAppliedDiscount, type CheckoutContactErrors, type CheckoutContactValues, CheckoutForm, type CheckoutFormLabels, type CheckoutFormProps, CheckoutLoginPrompt, type CheckoutLoginPromptLabels, type CheckoutLoginPromptProps, DEFAULT_SHOP_ERROR_FALLBACK_MESSAGE, DEFAULT_SHOP_ERROR_MESSAGES, DEFAULT_SHOP_PRICE_BANDS, MarkdownRenderer, type MarkdownRendererProps, OrderConfirmation, type OrderConfirmationLabels, type OrderConfirmationOrder, type OrderConfirmationProps, type OrderConfirmationStatus, ProductCard, type ProductCardProps, ProfileCard, type ProfileCardProps, SafeHtml, type SafeHtmlProps, type ShareUrls, type ShopFilterPriceBand, ShopFilters, type ShopFiltersActive, type ShopFiltersHrefs, type ShopFiltersLabels, type ShopFiltersProps, Specialty, type SpecialtyProps, Treatment, type TreatmentProps, buildShareUrls, formatPrice, getShopErrorMessage, isAllowedYouTubeHost, markdownToSafeHtml, readingTime, sanitizeHtml, shouldRenderShopFilters };
841
+ export { ALLOWED_YOUTUBE_HOSTS, AddedToCartToast, type AddedToCartToastLabels, type AddedToCartToastProps, type AddedToCartToastStatus, type BlockRendererProps, type CanonicalBlockType, type CartLineItemView, CartLineList, type CartLineListLabels, type CartLineListProps, CartSummary, type CartSummaryLabels, type CartSummaryProps, type CheckoutAppliedDiscount, type CheckoutContactErrors, type CheckoutContactValues, CheckoutForm, type CheckoutFormLabels, type CheckoutFormProps, CheckoutLoginPrompt, type CheckoutLoginPromptLabels, type CheckoutLoginPromptProps, DEFAULT_SHOP_ERROR_FALLBACK_MESSAGE, DEFAULT_SHOP_ERROR_MESSAGES, DEFAULT_SHOP_PRICE_BANDS, FaqAccordion, type FaqAccordionProps, FaqItem, type HeadingLevel, MarkdownRenderer, type MarkdownRendererProps, OrderConfirmation, type OrderConfirmationLabels, type OrderConfirmationOrder, type OrderConfirmationProps, type OrderConfirmationStatus, PageBlocks, type PageBlocksComponents, type PageBlocksProps, ProductCard, type ProductCardProps, ProfileCard, type ProfileCardProps, type RenderableBlock, SafeHtml, type SafeHtmlProps, type ShareUrls, type ShopFilterPriceBand, ShopFilters, type ShopFiltersActive, type ShopFiltersHrefs, type ShopFiltersLabels, type ShopFiltersProps, Specialty, type SpecialtyProps, Treatment, type TreatmentProps, buildShareUrls, formatPrice, getShopErrorMessage, isAllowedYouTubeHost, markdownToSafeHtml, readingTime, sanitizeHtml, shouldRenderShopFilters };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
- import { ShopErrorCode, PageDetail, PageListItem, ShopProductListItem, ShopFacets, ShopOrder } from '@reblu/site-contracts';
1
+ import { ShopErrorCode, PageDetail, PageListItem, ShopProductListItem, BlockType, PageBlock, ResolvedEmbed, ShopFacets, ShopOrder } from '@reblu/site-contracts';
2
+ import { FaqItem } from './seo.js';
3
+ export { FaqPageJsonLd, buildFaqPageJsonLd, collectFaqItems } from './seo.js';
2
4
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
- import { ReactNode, FormEvent } from 'react';
5
+ import { ComponentType, ReactNode, FormEvent } from 'react';
4
6
 
5
7
  declare function readingTime(content: string | null | undefined): number | null;
6
8
 
@@ -302,6 +304,102 @@ interface ProductCardProps {
302
304
  }
303
305
  declare function ProductCard({ product, href, currencyCode, locale, className }: ProductCardProps): react_jsx_runtime.JSX.Element;
304
306
 
307
+ /** A heading level a caller can configure — `h1`…`h6`. */
308
+ type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
309
+
310
+ /**
311
+ * REB-850 — accessible FAQ list on native `<details>`/`<summary>`.
312
+ *
313
+ * A Server Component: no hooks, no `'use client'`, no JavaScript needed to
314
+ * open or close an item — the browser owns the disclosure state, keyboard
315
+ * support and the expanded/collapsed semantics. Answers render as TEXT (React
316
+ * escapes them; line breaks are preserved), never as markup.
317
+ *
318
+ * Pair it with `buildFaqPageJsonLd` (`@reblu/site-blocks/seo`) for the
319
+ * matching schema.org `FAQPage` structured data.
320
+ */
321
+ interface FaqAccordionProps {
322
+ items: readonly FaqItem[];
323
+ /** Optional heading above the list. */
324
+ heading?: string | null;
325
+ /** Level of `heading`. Default `2`. */
326
+ headingLevel?: HeadingLevel;
327
+ /**
328
+ * When set, each question is wrapped in `<h{level}>` inside its `<summary>`
329
+ * so it appears in the document outline. Default: plain summary text.
330
+ */
331
+ questionHeadingLevel?: HeadingLevel;
332
+ /** Indexes of the items rendered open initially. */
333
+ defaultOpen?: readonly number[];
334
+ className?: string;
335
+ }
336
+ declare function FaqAccordion({ items, heading, headingLevel, questionHeadingLevel, defaultOpen, className, }: FaqAccordionProps): react_jsx_runtime.JSX.Element | null;
337
+
338
+ /**
339
+ * REB-850 — `PageBlocks`: the shared renderer for a page's registry blocks
340
+ * (`PageDetail.blocks`, REB-75), so a SITE stops hand-writing its own.
341
+ *
342
+ * A SERVER COMPONENT by default: no hooks, no `'use client'`, reachable from the
343
+ * RSC-safe root entry (`test/rsc-safety.test.ts` renders it under
344
+ * `--conditions=react-server`).
345
+ *
346
+ * Each block is wrapped in a `<section>` carrying the parent section's
347
+ * mirrored fields (REB-849): `heading`, `backgroundColor` and `cta`. The body
348
+ * comes from a per-type renderer that the `components` map can replace.
349
+ *
350
+ * Fail-closed rules:
351
+ * • an unknown `blockType` renders nothing (the whole section) and never throws;
352
+ * • an embed whose `resolved` is `null`/absent renders nothing — no heading,
353
+ * no dead link;
354
+ * • deprecated alias types (`doctor_embed`, `specialty_embed` — REB-852)
355
+ * render through their generic type's renderer (`profile_embed`, `page_embed`).
356
+ */
357
+ type DeprecatedAlias = 'doctor_embed' | 'specialty_embed';
358
+ /** Block types a renderer is registered under — deprecated aliases resolve to these. */
359
+ type CanonicalBlockType = Exclude<BlockType, DeprecatedAlias>;
360
+ type EmbedType = 'page_embed' | 'profile_embed';
361
+ type WireTypesOf<T extends CanonicalBlockType> = T extends 'profile_embed' ? 'profile_embed' | 'doctor_embed' : T extends 'page_embed' ? 'page_embed' | 'specialty_embed' : T;
362
+ /**
363
+ * The block a renderer for `T` receives — including its deprecated alias, and
364
+ * with `resolved` guaranteed non-null for embeds (unresolved ones never render).
365
+ */
366
+ type RenderableBlock<T extends CanonicalBlockType> = T extends EmbedType ? Extract<PageBlock, {
367
+ blockType: WireTypesOf<T>;
368
+ }> & {
369
+ resolved: ResolvedEmbed;
370
+ } : Extract<PageBlock, {
371
+ blockType: WireTypesOf<T>;
372
+ }>;
373
+ interface BlockRendererProps<T extends CanonicalBlockType> {
374
+ block: RenderableBlock<T>;
375
+ /** Level for headings INSIDE the block — one below the section heading when there is one. */
376
+ headingLevel: HeadingLevel;
377
+ /** Builds an embed's href (the `embedHref` prop, or the default `/${slug}`). */
378
+ embedHref: (resolved: ResolvedEmbed) => string;
379
+ }
380
+ /** Per-block-type renderer overrides, keyed by canonical type. */
381
+ type PageBlocksComponents = Partial<{
382
+ [T in CanonicalBlockType]: ComponentType<BlockRendererProps<T>>;
383
+ }>;
384
+ interface PageBlocksProps {
385
+ blocks: readonly PageBlock[] | null | undefined;
386
+ /** Replace the body renderer of any block type. */
387
+ components?: PageBlocksComponents;
388
+ /** Level of each section heading. Default `2` (the page title is the `h1`). */
389
+ headingLevel?: HeadingLevel;
390
+ /** Href for an embedded page. Default `/${resolved.slug}`. */
391
+ embedHref?: (resolved: ResolvedEmbed) => string;
392
+ /**
393
+ * Namespace for the section heading ids (`aria-labelledby` targets). Pass a
394
+ * distinct value when one document renders `PageBlocks` more than once with
395
+ * overlapping blocks. Sanitised to a valid IDREF. Default `'page-block'`.
396
+ */
397
+ idPrefix?: string;
398
+ className?: string;
399
+ sectionClassName?: string;
400
+ }
401
+ declare function PageBlocks({ blocks, components, headingLevel, embedHref, idPrefix, className, sectionClassName, }: PageBlocksProps): react_jsx_runtime.JSX.Element | null;
402
+
305
403
  /**
306
404
  * `ShopFilters` — plug-and-play SITE filter chrome over `GET /api/site/shop/facets`
307
405
  * (REB-809). Which axes appear, their order and labels come entirely from
@@ -645,6 +743,14 @@ interface CheckoutFormProps {
645
743
  contact: CheckoutContactValues;
646
744
  onContactChange: (values: CheckoutContactValues) => void;
647
745
  contactErrors?: CheckoutContactErrors;
746
+ /** Country asserted by the SITE for national phone input; omit rather than guessing. */
747
+ phoneRegion?: string;
748
+ /** Optional raw checkout API error body; only INVALID_INPUT/customerPhone marks the phone field. */
749
+ checkoutError?: {
750
+ code?: unknown;
751
+ error?: unknown;
752
+ details?: unknown;
753
+ } | null;
648
754
  discountCode: string;
649
755
  onDiscountCodeChange: (value: string) => void;
650
756
  onApplyDiscount: () => void;
@@ -670,7 +776,7 @@ interface CheckoutFormProps {
670
776
  locale?: string;
671
777
  className?: string;
672
778
  }
673
- declare function CheckoutForm({ contact, onContactChange, contactErrors, discountCode, onDiscountCodeChange, onApplyDiscount, onRemoveDiscount, appliedDiscount, discountApplying, discountError, loginPrompt, children, submitting, submitDisabled, onSubmit, labels, currencyCode, locale, className, }: CheckoutFormProps): react_jsx_runtime.JSX.Element;
779
+ declare function CheckoutForm({ contact, onContactChange, contactErrors, phoneRegion, checkoutError, discountCode, onDiscountCodeChange, onApplyDiscount, onRemoveDiscount, appliedDiscount, discountApplying, discountError, loginPrompt, children, submitting, submitDisabled, onSubmit, labels, currencyCode, locale, className, }: CheckoutFormProps): react_jsx_runtime.JSX.Element;
674
780
 
675
781
  /**
676
782
  * `OrderConfirmation` — presentational order-confirmation view (REB-829,
@@ -732,4 +838,4 @@ interface OrderConfirmationProps {
732
838
  }
733
839
  declare function OrderConfirmation({ status, order, sessionId, cartHref, onRecheck, labels, currencyCode, locale, className, }: OrderConfirmationProps): react_jsx_runtime.JSX.Element;
734
840
 
735
- export { ALLOWED_YOUTUBE_HOSTS, AddedToCartToast, type AddedToCartToastLabels, type AddedToCartToastProps, type AddedToCartToastStatus, type CartLineItemView, CartLineList, type CartLineListLabels, type CartLineListProps, CartSummary, type CartSummaryLabels, type CartSummaryProps, type CheckoutAppliedDiscount, type CheckoutContactErrors, type CheckoutContactValues, CheckoutForm, type CheckoutFormLabels, type CheckoutFormProps, CheckoutLoginPrompt, type CheckoutLoginPromptLabels, type CheckoutLoginPromptProps, DEFAULT_SHOP_ERROR_FALLBACK_MESSAGE, DEFAULT_SHOP_ERROR_MESSAGES, DEFAULT_SHOP_PRICE_BANDS, MarkdownRenderer, type MarkdownRendererProps, OrderConfirmation, type OrderConfirmationLabels, type OrderConfirmationOrder, type OrderConfirmationProps, type OrderConfirmationStatus, ProductCard, type ProductCardProps, ProfileCard, type ProfileCardProps, SafeHtml, type SafeHtmlProps, type ShareUrls, type ShopFilterPriceBand, ShopFilters, type ShopFiltersActive, type ShopFiltersHrefs, type ShopFiltersLabels, type ShopFiltersProps, Specialty, type SpecialtyProps, Treatment, type TreatmentProps, buildShareUrls, formatPrice, getShopErrorMessage, isAllowedYouTubeHost, markdownToSafeHtml, readingTime, sanitizeHtml, shouldRenderShopFilters };
841
+ export { ALLOWED_YOUTUBE_HOSTS, AddedToCartToast, type AddedToCartToastLabels, type AddedToCartToastProps, type AddedToCartToastStatus, type BlockRendererProps, type CanonicalBlockType, type CartLineItemView, CartLineList, type CartLineListLabels, type CartLineListProps, CartSummary, type CartSummaryLabels, type CartSummaryProps, type CheckoutAppliedDiscount, type CheckoutContactErrors, type CheckoutContactValues, CheckoutForm, type CheckoutFormLabels, type CheckoutFormProps, CheckoutLoginPrompt, type CheckoutLoginPromptLabels, type CheckoutLoginPromptProps, DEFAULT_SHOP_ERROR_FALLBACK_MESSAGE, DEFAULT_SHOP_ERROR_MESSAGES, DEFAULT_SHOP_PRICE_BANDS, FaqAccordion, type FaqAccordionProps, FaqItem, type HeadingLevel, MarkdownRenderer, type MarkdownRendererProps, OrderConfirmation, type OrderConfirmationLabels, type OrderConfirmationOrder, type OrderConfirmationProps, type OrderConfirmationStatus, PageBlocks, type PageBlocksComponents, type PageBlocksProps, ProductCard, type ProductCardProps, ProfileCard, type ProfileCardProps, type RenderableBlock, SafeHtml, type SafeHtmlProps, type ShareUrls, type ShopFilterPriceBand, ShopFilters, type ShopFiltersActive, type ShopFiltersHrefs, type ShopFiltersLabels, type ShopFiltersProps, Specialty, type SpecialtyProps, Treatment, type TreatmentProps, buildShareUrls, formatPrice, getShopErrorMessage, isAllowedYouTubeHost, markdownToSafeHtml, readingTime, sanitizeHtml, shouldRenderShopFilters };