@brandlab-digital/site-next 3.13.0 → 3.14.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.
Files changed (169) hide show
  1. package/README.md +79 -0
  2. package/dist/contact/delivery.d.ts +81 -0
  3. package/dist/contact/delivery.d.ts.map +1 -0
  4. package/dist/contact/delivery.js +138 -0
  5. package/dist/contact/delivery.js.map +1 -0
  6. package/dist/contact/resend-webhook.d.ts +72 -0
  7. package/dist/contact/resend-webhook.d.ts.map +1 -0
  8. package/dist/contact/resend-webhook.js +300 -0
  9. package/dist/contact/resend-webhook.js.map +1 -0
  10. package/dist/contact/route-handler.d.ts +16 -0
  11. package/dist/contact/route-handler.d.ts.map +1 -1
  12. package/dist/contact/route-handler.js +8 -0
  13. package/dist/contact/route-handler.js.map +1 -1
  14. package/dist/contact/server-entry.d.ts +2 -0
  15. package/dist/contact/server-entry.d.ts.map +1 -1
  16. package/dist/contact/server-entry.js +2 -0
  17. package/dist/contact/server-entry.js.map +1 -1
  18. package/dist/contact/server.d.ts +26 -0
  19. package/dist/contact/server.d.ts.map +1 -1
  20. package/dist/contact/server.js +48 -3
  21. package/dist/contact/server.js.map +1 -1
  22. package/dist/next-config.d.ts +17 -1
  23. package/dist/next-config.d.ts.map +1 -1
  24. package/dist/next-config.js +36 -2
  25. package/dist/next-config.js.map +1 -1
  26. package/dist/ui-kit/brandlab-skin.d.ts +24 -1
  27. package/dist/ui-kit/brandlab-skin.d.ts.map +1 -1
  28. package/dist/ui-kit/brandlab-skin.js.map +1 -1
  29. package/dist/ui-kit/form/photo-upload.d.ts +4 -1
  30. package/dist/ui-kit/form/photo-upload.d.ts.map +1 -1
  31. package/dist/ui-kit/form/photo-upload.js +4 -4
  32. package/dist/ui-kit/form/photo-upload.js.map +1 -1
  33. package/dist/ui-kit/form/quote-form.d.ts +4 -1
  34. package/dist/ui-kit/form/quote-form.d.ts.map +1 -1
  35. package/dist/ui-kit/form/quote-form.js.map +1 -1
  36. package/dist/ui-kit/form/thank-you.d.ts +4 -1
  37. package/dist/ui-kit/form/thank-you.d.ts.map +1 -1
  38. package/dist/ui-kit/form/thank-you.js +2 -2
  39. package/dist/ui-kit/form/thank-you.js.map +1 -1
  40. package/dist/ui-kit/icon-glyphs.d.ts +41 -0
  41. package/dist/ui-kit/icon-glyphs.d.ts.map +1 -0
  42. package/dist/ui-kit/icon-glyphs.js +138 -0
  43. package/dist/ui-kit/icon-glyphs.js.map +1 -0
  44. package/dist/ui-kit/icon-sprite.d.ts +19 -0
  45. package/dist/ui-kit/icon-sprite.d.ts.map +1 -0
  46. package/dist/ui-kit/icon-sprite.js +44 -0
  47. package/dist/ui-kit/icon-sprite.js.map +1 -0
  48. package/dist/ui-kit/icons.d.ts +38 -3
  49. package/dist/ui-kit/icons.d.ts.map +1 -1
  50. package/dist/ui-kit/icons.js +47 -64
  51. package/dist/ui-kit/icons.js.map +1 -1
  52. package/dist/ui-kit/primitives/badge.d.ts +25 -0
  53. package/dist/ui-kit/primitives/badge.d.ts.map +1 -0
  54. package/dist/ui-kit/primitives/badge.js +21 -0
  55. package/dist/ui-kit/primitives/badge.js.map +1 -0
  56. package/dist/ui-kit/primitives/breadcrumbs.d.ts +27 -0
  57. package/dist/ui-kit/primitives/breadcrumbs.d.ts.map +1 -0
  58. package/dist/ui-kit/primitives/breadcrumbs.js +40 -0
  59. package/dist/ui-kit/primitives/breadcrumbs.js.map +1 -0
  60. package/dist/ui-kit/primitives/button.d.ts +32 -0
  61. package/dist/ui-kit/primitives/button.d.ts.map +1 -0
  62. package/dist/ui-kit/primitives/button.js +8 -0
  63. package/dist/ui-kit/primitives/button.js.map +1 -0
  64. package/dist/ui-kit/primitives/card.d.ts +40 -0
  65. package/dist/ui-kit/primitives/card.d.ts.map +1 -0
  66. package/dist/ui-kit/primitives/card.js +8 -0
  67. package/dist/ui-kit/primitives/card.js.map +1 -0
  68. package/dist/ui-kit/primitives/index.d.ts +11 -2
  69. package/dist/ui-kit/primitives/index.d.ts.map +1 -1
  70. package/dist/ui-kit/primitives/index.js +11 -2
  71. package/dist/ui-kit/primitives/index.js.map +1 -1
  72. package/dist/ui-kit/primitives/layout.d.ts +12 -0
  73. package/dist/ui-kit/primitives/layout.d.ts.map +1 -1
  74. package/dist/ui-kit/primitives/layout.js +20 -2
  75. package/dist/ui-kit/primitives/layout.js.map +1 -1
  76. package/dist/ui-kit/primitives/links.d.ts +15 -0
  77. package/dist/ui-kit/primitives/links.d.ts.map +1 -1
  78. package/dist/ui-kit/primitives/links.js +5 -1
  79. package/dist/ui-kit/primitives/links.js.map +1 -1
  80. package/dist/ui-kit/primitives/lists.d.ts +43 -0
  81. package/dist/ui-kit/primitives/lists.d.ts.map +1 -0
  82. package/dist/ui-kit/primitives/lists.js +34 -0
  83. package/dist/ui-kit/primitives/lists.js.map +1 -0
  84. package/dist/ui-kit/primitives/media-frame.d.ts +28 -0
  85. package/dist/ui-kit/primitives/media-frame.d.ts.map +1 -0
  86. package/dist/ui-kit/primitives/media-frame.js +58 -0
  87. package/dist/ui-kit/primitives/media-frame.js.map +1 -0
  88. package/dist/ui-kit/primitives/media.d.ts +38 -0
  89. package/dist/ui-kit/primitives/media.d.ts.map +1 -0
  90. package/dist/ui-kit/primitives/media.js +9 -0
  91. package/dist/ui-kit/primitives/media.js.map +1 -0
  92. package/dist/ui-kit/primitives/stat.d.ts +25 -0
  93. package/dist/ui-kit/primitives/stat.d.ts.map +1 -0
  94. package/dist/ui-kit/primitives/stat.js +7 -0
  95. package/dist/ui-kit/primitives/stat.js.map +1 -0
  96. package/dist/ui-kit/repeated-slots.d.ts +121 -0
  97. package/dist/ui-kit/repeated-slots.d.ts.map +1 -0
  98. package/dist/ui-kit/repeated-slots.js +81 -0
  99. package/dist/ui-kit/repeated-slots.js.map +1 -0
  100. package/dist/ui-kit/sections/accordion.d.ts +4 -1
  101. package/dist/ui-kit/sections/accordion.d.ts.map +1 -1
  102. package/dist/ui-kit/sections/accordion.js +2 -2
  103. package/dist/ui-kit/sections/accordion.js.map +1 -1
  104. package/dist/ui-kit/sections/contact-block.d.ts +4 -1
  105. package/dist/ui-kit/sections/contact-block.d.ts.map +1 -1
  106. package/dist/ui-kit/sections/contact-block.js +3 -3
  107. package/dist/ui-kit/sections/contact-block.js.map +1 -1
  108. package/dist/ui-kit/sections/faqs.d.ts +4 -1
  109. package/dist/ui-kit/sections/faqs.d.ts.map +1 -1
  110. package/dist/ui-kit/sections/faqs.js +4 -5
  111. package/dist/ui-kit/sections/faqs.js.map +1 -1
  112. package/dist/ui-kit/sections/hero.d.ts +113 -0
  113. package/dist/ui-kit/sections/hero.d.ts.map +1 -0
  114. package/dist/ui-kit/sections/hero.js +58 -0
  115. package/dist/ui-kit/sections/hero.js.map +1 -0
  116. package/dist/ui-kit/sections/index.d.ts +2 -0
  117. package/dist/ui-kit/sections/index.d.ts.map +1 -1
  118. package/dist/ui-kit/sections/index.js +2 -0
  119. package/dist/ui-kit/sections/index.js.map +1 -1
  120. package/dist/ui-kit/sections/price-guide.d.ts +4 -1
  121. package/dist/ui-kit/sections/price-guide.d.ts.map +1 -1
  122. package/dist/ui-kit/sections/price-guide.js +3 -3
  123. package/dist/ui-kit/sections/price-guide.js.map +1 -1
  124. package/dist/ui-kit/sections/price-table.d.ts +7 -9
  125. package/dist/ui-kit/sections/price-table.d.ts.map +1 -1
  126. package/dist/ui-kit/sections/price-table.js +8 -8
  127. package/dist/ui-kit/sections/price-table.js.map +1 -1
  128. package/dist/ui-kit/sections/proof-strip.d.ts +4 -1
  129. package/dist/ui-kit/sections/proof-strip.d.ts.map +1 -1
  130. package/dist/ui-kit/sections/proof-strip.js +2 -2
  131. package/dist/ui-kit/sections/proof-strip.js.map +1 -1
  132. package/dist/ui-kit/sections/reviews-band.d.ts +5 -2
  133. package/dist/ui-kit/sections/reviews-band.d.ts.map +1 -1
  134. package/dist/ui-kit/sections/reviews-band.js +2 -2
  135. package/dist/ui-kit/sections/reviews-band.js.map +1 -1
  136. package/dist/ui-kit/sections/services-grid.d.ts +82 -0
  137. package/dist/ui-kit/sections/services-grid.d.ts.map +1 -0
  138. package/dist/ui-kit/sections/services-grid.js +90 -0
  139. package/dist/ui-kit/sections/services-grid.js.map +1 -0
  140. package/dist/ui-kit/sections/site-footer.d.ts +4 -1
  141. package/dist/ui-kit/sections/site-footer.d.ts.map +1 -1
  142. package/dist/ui-kit/sections/site-footer.js +6 -6
  143. package/dist/ui-kit/sections/site-footer.js.map +1 -1
  144. package/dist/ui-kit/shell/mobile-drawer.d.ts +7 -3
  145. package/dist/ui-kit/shell/mobile-drawer.d.ts.map +1 -1
  146. package/dist/ui-kit/shell/mobile-drawer.js +20 -4
  147. package/dist/ui-kit/shell/mobile-drawer.js.map +1 -1
  148. package/dist/ui-kit/shell/site-header.d.ts +4 -1
  149. package/dist/ui-kit/shell/site-header.d.ts.map +1 -1
  150. package/dist/ui-kit/shell/site-header.js +8 -8
  151. package/dist/ui-kit/shell/site-header.js.map +1 -1
  152. package/dist/ui-kit/shell/social-links.d.ts +1 -2
  153. package/dist/ui-kit/shell/social-links.d.ts.map +1 -1
  154. package/dist/ui-kit/shell/social-links.js +6 -3
  155. package/dist/ui-kit/shell/social-links.js.map +1 -1
  156. package/dist/ui-kit/shell/utility-bar.d.ts +4 -1
  157. package/dist/ui-kit/shell/utility-bar.d.ts.map +1 -1
  158. package/dist/ui-kit/shell/utility-bar.js +2 -2
  159. package/dist/ui-kit/shell/utility-bar.js.map +1 -1
  160. package/dist/ui-kit/skin-index.d.ts +2 -1
  161. package/dist/ui-kit/skin-index.d.ts.map +1 -1
  162. package/dist/ui-kit/skin-index.js +2 -1
  163. package/dist/ui-kit/skin-index.js.map +1 -1
  164. package/dist/ui-kit/skin.d.ts +21 -4
  165. package/dist/ui-kit/skin.d.ts.map +1 -1
  166. package/dist/ui-kit/skin.js +14 -1
  167. package/dist/ui-kit/skin.js.map +1 -1
  168. package/dist/ui-kit/structure.css +383 -3
  169. package/package.json +6 -2
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { SECTION_READY } from '@brandlab-digital/site-core/section-order';
3
3
  import { Icon } from '../icons.js';
4
4
  import { RatingStars, Section } from '../primitives/layout.js';
5
- import { slot } from '../skin.js';
5
+ import { repeatedSlot, slot } from '../skin.js';
6
6
  export const PROOF_STRIP_VARIANTS = ['inline', 'tiles'];
7
7
  /** The fewest items a strip shows; below this the facts merge into the hero checklist. */
8
8
  export const PROOF_STRIP_MINIMUM = 3;
@@ -15,6 +15,6 @@ export function ProofStrip({ title, items, logos, headingId = 'proof-heading', t
15
15
  if (!proofStripReady({ items, logos }).render)
16
16
  return null;
17
17
  const hidden = (layout?.heading ?? 'hidden') === 'hidden';
18
- return (_jsxs(Section, { id: "proof-strip", labelledBy: headingId, tone: tone, variant: variant, classes: classes, children: [_jsx("h2", { id: headingId, ...slot(classes, 'heading'), "data-bl-visually-hidden": hidden ? '' : undefined, children: title }), items.length > 0 ? (_jsx("ul", { role: "list", ...slot(classes, 'list'), children: items.map((item) => (_jsxs("li", { ...slot(classes, 'item'), "data-item": item.id, "data-bl-source": item.rating !== undefined ? 'feed' : undefined, children: [item.icon ? (_jsx("span", { ...slot(classes, 'icon'), children: _jsx(Icon, { name: item.icon }) })) : null, item.rating !== undefined ? (_jsx("span", { ...slot(classes, 'stars'), "aria-hidden": "true", children: _jsx(RatingStars, { rating: item.rating }) })) : null, item.rating !== undefined ? (_jsx("span", { ...slot(classes, 'value'), children: item.rating.toFixed(1) })) : item.value ? (_jsx("span", { ...slot(classes, 'value'), children: item.value })) : null, ' ', _jsx("span", { ...slot(classes, 'label'), children: item.label })] }, item.id))) })) : null, logos ? _jsx("div", { ...slot(classes, 'logos'), children: logos }) : null] }));
18
+ return (_jsxs(Section, { id: "proof-strip", labelledBy: headingId, tone: tone, variant: variant, classes: classes, children: [_jsx("h2", { id: headingId, ...slot(classes, 'heading'), "data-bl-visually-hidden": hidden ? '' : undefined, children: title }), items.length > 0 ? (_jsx("ul", { role: "list", ...slot(classes, 'list'), children: items.map((item) => (_jsxs("li", { ...repeatedSlot('item'), "data-item": item.id, "data-bl-source": item.rating !== undefined ? 'feed' : undefined, children: [item.icon ? (_jsx("span", { ...repeatedSlot('icon'), children: _jsx(Icon, { name: item.icon }) })) : null, item.rating !== undefined ? (_jsx("span", { ...repeatedSlot('stars'), "aria-hidden": "true", children: _jsx(RatingStars, { rating: item.rating }) })) : null, item.rating !== undefined ? (_jsx("span", { ...repeatedSlot('value'), children: item.rating.toFixed(1) })) : item.value ? (_jsx("span", { ...repeatedSlot('value'), children: item.value })) : null, ' ', _jsx("span", { ...repeatedSlot('label'), children: item.label })] }, item.id))) })) : null, logos ? _jsx("div", { ...slot(classes, 'logos'), children: logos }) : null] }));
19
19
  }
20
20
  //# sourceMappingURL=proof-strip.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"proof-strip.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/proof-strip.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `ProofStrip` (S3, `proof-strip`): the business's verified strengths straight after the hero: the live rating from\n * the feed, licence lines, insurance, years trading and partner logos. Server component. It takes items that are\n * already verified (`verified` or `legacy-carried` facts, T4); it never counts up or hides text before scripts run.\n * With fewer than three items it renders nothing and reports `merge-into-hero-checklist`: the facts belong in the\n * hero's checklist instead (v1 §7.2).\n */\nimport type { ReactNode } from 'react'\nimport { SECTION_READY, type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order'\nimport { Icon, type UiIconName } from '../icons.js'\nimport { RatingStars, Section, type SectionSlot, type SectionTone } from '../primitives/layout.js'\nimport { slot, type SkinEntry } from '../skin.js'\n\nexport const PROOF_STRIP_VARIANTS = ['inline', 'tiles'] as const\nexport type ProofStripVariant = (typeof PROOF_STRIP_VARIANTS)[number]\nexport type ProofStripSlot = SectionSlot | 'heading' | 'list' | 'item' | 'icon' | 'stars' | 'value' | 'label' | 'logos'\n/** `heading`: the section's H2 is visually hidden by default; a skin may show it. */\nexport type ProofStripSkin = SkinEntry<ProofStripSlot, ProofStripVariant, { heading?: 'hidden' | 'visible' }>\n\n/** The fewest items a strip shows; below this the facts merge into the hero checklist. */\nexport const PROOF_STRIP_MINIMUM = 3\n\ninterface ProofItemBase {\n /** A stable slug, rendered as `data-item`. */\n readonly id: string\n readonly label: string\n readonly icon?: UiIconName\n}\n\n/** One verified fact, for example `{ id: 'licence', value: '276621C', label: 'NSW contractor licence' }`. */\nexport interface ProofFactItem extends ProofItemBase {\n readonly value?: string\n readonly rating?: never\n}\n\n/**\n * The live rating from the reviews feed (F9), never typed by hand (T4). Its value is the rating itself (\"4.9\"), with\n * the stars beside it, and the item is marked `data-bl-source=\"feed\"`, as the reviews band marks its root.\n */\nexport interface ProofRatingItem extends ProofItemBase {\n readonly rating: number\n readonly value?: never\n}\n\nexport type ProofItem = ProofFactItem | ProofRatingItem\n\nexport interface ProofStripProps extends ProofStripSkin {\n /** The H2, for example \"Why homeowners choose Omega\". Visually hidden unless the skin shows it. */\n readonly title: string\n readonly items: readonly ProofItem[]\n /** Partner logos (a `LogoStrip` of verified relationships). Counts as one item. */\n readonly logos?: ReactNode\n /** Defaults to `proof-heading`. */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n}\n\n/** Whether the strip renders: at least three items, logos counting as one (`merge-into-hero-checklist`). */\nexport function proofStripReady({ items, logos }: Pick<ProofStripProps, 'items' | 'logos'>): BrandlabSectionReadiness {\n const count = items.length + (logos ? 1 : 0)\n return count >= PROOF_STRIP_MINIMUM ? SECTION_READY : { render: false, reason: 'merge-into-hero-checklist' }\n}\n\nexport function ProofStrip({ title, items, logos, headingId = 'proof-heading', tone, variant = 'inline', layout, classes }: ProofStripProps) {\n if (!proofStripReady({ items, logos }).render) return null\n const hidden = (layout?.heading ?? 'hidden') === 'hidden'\n return (\n <Section id=\"proof-strip\" labelledBy={headingId} tone={tone} variant={variant} classes={classes}>\n <h2 id={headingId} {...slot(classes, 'heading')} data-bl-visually-hidden={hidden ? '' : undefined}>\n {title}\n </h2>\n {items.length > 0 ? (\n <ul role=\"list\" {...slot(classes, 'list')}>\n {items.map((item) => (\n <li key={item.id} {...slot(classes, 'item')} data-item={item.id} data-bl-source={item.rating !== undefined ? 'feed' : undefined}>\n {item.icon ? (\n <span {...slot(classes, 'icon')}>\n <Icon name={item.icon} />\n </span>\n ) : null}\n {item.rating !== undefined ? (\n <span {...slot(classes, 'stars')} aria-hidden=\"true\">\n <RatingStars rating={item.rating} />\n </span>\n ) : null}\n {item.rating !== undefined ? (\n <span {...slot(classes, 'value')}>{item.rating.toFixed(1)}</span>\n ) : item.value ? (\n <span {...slot(classes, 'value')}>{item.value}</span>\n ) : null}{' '}\n <span {...slot(classes, 'label')}>{item.label}</span>\n </li>\n ))}\n </ul>\n ) : null}\n {logos ? <div {...slot(classes, 'logos')}>{logos}</div> : null}\n </Section>\n )\n}\n"]}
1
+ {"version":3,"file":"proof-strip.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/proof-strip.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `ProofStrip` (S3, `proof-strip`): the business's verified strengths straight after the hero: the live rating from\n * the feed, licence lines, insurance, years trading and partner logos. Server component. It takes items that are\n * already verified (`verified` or `legacy-carried` facts, T4); it never counts up or hides text before scripts run.\n * With fewer than three items it renders nothing and reports `merge-into-hero-checklist`: the facts belong in the\n * hero's checklist instead (v1 §7.2).\n */\nimport type { ReactNode } from 'react'\nimport { SECTION_READY, type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order'\nimport { Icon, type UiIconName } from '../icons.js'\nimport { RatingStars, Section, type SectionSlot, type SectionTone } from '../primitives/layout.js'\nimport type { RepeatedSlotOf } from '../repeated-slots.js'\nimport { repeatedSlot, slot, type SkinEntry } from '../skin.js'\n\nexport const PROOF_STRIP_VARIANTS = ['inline', 'tiles'] as const\nexport type ProofStripVariant = (typeof PROOF_STRIP_VARIANTS)[number]\nexport type ProofStripSlot = SectionSlot | 'heading' | 'list' | 'item' | 'icon' | 'stars' | 'value' | 'label' | 'logos'\n/** `heading`: the section's H2 is visually hidden by default; a skin may show it. */\n/** Rendered once per proof item: styled from the skin stylesheet, never by class (3.14.0). */\nexport type ProofStripRepeatedSlot = RepeatedSlotOf<'proofStrip'>\nexport type ProofStripSkin = SkinEntry<ProofStripSlot, ProofStripVariant, { heading?: 'hidden' | 'visible' }, ProofStripRepeatedSlot>\n\n/** The fewest items a strip shows; below this the facts merge into the hero checklist. */\nexport const PROOF_STRIP_MINIMUM = 3\n\ninterface ProofItemBase {\n /** A stable slug, rendered as `data-item`. */\n readonly id: string\n readonly label: string\n readonly icon?: UiIconName\n}\n\n/** One verified fact, for example `{ id: 'licence', value: '276621C', label: 'NSW contractor licence' }`. */\nexport interface ProofFactItem extends ProofItemBase {\n readonly value?: string\n readonly rating?: never\n}\n\n/**\n * The live rating from the reviews feed (F9), never typed by hand (T4). Its value is the rating itself (\"4.9\"), with\n * the stars beside it, and the item is marked `data-bl-source=\"feed\"`, as the reviews band marks its root.\n */\nexport interface ProofRatingItem extends ProofItemBase {\n readonly rating: number\n readonly value?: never\n}\n\nexport type ProofItem = ProofFactItem | ProofRatingItem\n\nexport interface ProofStripProps extends ProofStripSkin {\n /** The H2, for example \"Why homeowners choose Omega\". Visually hidden unless the skin shows it. */\n readonly title: string\n readonly items: readonly ProofItem[]\n /** Partner logos (a `LogoStrip` of verified relationships). Counts as one item. */\n readonly logos?: ReactNode\n /** Defaults to `proof-heading`. */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n}\n\n/** Whether the strip renders: at least three items, logos counting as one (`merge-into-hero-checklist`). */\nexport function proofStripReady({ items, logos }: Pick<ProofStripProps, 'items' | 'logos'>): BrandlabSectionReadiness {\n const count = items.length + (logos ? 1 : 0)\n return count >= PROOF_STRIP_MINIMUM ? SECTION_READY : { render: false, reason: 'merge-into-hero-checklist' }\n}\n\nexport function ProofStrip({ title, items, logos, headingId = 'proof-heading', tone, variant = 'inline', layout, classes }: ProofStripProps) {\n if (!proofStripReady({ items, logos }).render) return null\n const hidden = (layout?.heading ?? 'hidden') === 'hidden'\n return (\n <Section id=\"proof-strip\" labelledBy={headingId} tone={tone} variant={variant} classes={classes}>\n <h2 id={headingId} {...slot(classes, 'heading')} data-bl-visually-hidden={hidden ? '' : undefined}>\n {title}\n </h2>\n {items.length > 0 ? (\n <ul role=\"list\" {...slot(classes, 'list')}>\n {items.map((item) => (\n <li key={item.id} {...repeatedSlot('item')} data-item={item.id} data-bl-source={item.rating !== undefined ? 'feed' : undefined}>\n {item.icon ? (\n <span {...repeatedSlot('icon')}>\n <Icon name={item.icon} />\n </span>\n ) : null}\n {item.rating !== undefined ? (\n <span {...repeatedSlot('stars')} aria-hidden=\"true\">\n <RatingStars rating={item.rating} />\n </span>\n ) : null}\n {item.rating !== undefined ? (\n <span {...repeatedSlot('value')}>{item.rating.toFixed(1)}</span>\n ) : item.value ? (\n <span {...repeatedSlot('value')}>{item.value}</span>\n ) : null}{' '}\n <span {...repeatedSlot('label')}>{item.label}</span>\n </li>\n ))}\n </ul>\n ) : null}\n {logos ? <div {...slot(classes, 'logos')}>{logos}</div> : null}\n </Section>\n )\n}\n"]}
@@ -16,6 +16,7 @@
16
16
  import { type ReactNode } from 'react';
17
17
  import { type BusinessProfileReview, type PreparedBusinessProfileReviews } from '@brandlab-digital/site-core/google-business-reviews';
18
18
  import { type SectionTone } from '../primitives/layout.js';
19
+ import type { RepeatedSlotOf } from '../repeated-slots.js';
19
20
  import { type SkinEntry, type SlotClasses } from '../skin.js';
20
21
  export declare const REVIEWS_BAND_VARIANTS: readonly ["band", "feature", "cards", "wall", "summary"];
21
22
  export type ReviewsBandVariant = (typeof REVIEWS_BAND_VARIANTS)[number];
@@ -28,8 +29,10 @@ export interface ReviewsBandLayout {
28
29
  /** Characters shown before a long review folds behind "Read more" (default 320); `false` never folds. */
29
30
  readonly excerpt?: number | false;
30
31
  }
31
- export type ReviewsBandSkin = SkinEntry<ReviewsBandSlot, ReviewsBandVariant, ReviewsBandLayout>;
32
- type Classes = SlotClasses<ReviewsBandSlot> | undefined;
32
+ /** Rendered once per review: styled from the skin stylesheet, never by class (3.14.0). */
33
+ export type ReviewsBandRepeatedSlot = RepeatedSlotOf<'reviewsBand'>;
34
+ export type ReviewsBandSkin = SkinEntry<ReviewsBandSlot, ReviewsBandVariant, ReviewsBandLayout, ReviewsBandRepeatedSlot>;
35
+ type Classes = SlotClasses<Exclude<ReviewsBandSlot, ReviewsBandRepeatedSlot>> | undefined;
33
36
  export declare const REVIEWS_BAND_HEADING_ID = "bl-reviews-heading";
34
37
  /**
35
38
  * A long review split at a sentence end (or else a space) near `limit`, so the first part reads on its own. The two
@@ -1 +1 @@
1
- {"version":3,"file":"reviews-band.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/reviews-band.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"reviews-band.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/reviews-band.tsx"],"names":[],"mappings":"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"}
@@ -17,7 +17,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
17
17
  import { Children } from 'react';
18
18
  import { businessProfileReviewMentions, } from '@brandlab-digital/site-core/google-business-reviews';
19
19
  import { RatingStars, Section, SectionHeading } from '../primitives/layout.js';
20
- import { slot } from '../skin.js';
20
+ import { repeatedSlot, slot } from '../skin.js';
21
21
  import { hasGoogleRating, RatingChip } from './rating-chip.js';
22
22
  export const REVIEWS_BAND_VARIANTS = ['band', 'feature', 'cards', 'wall', 'summary'];
23
23
  export const REVIEWS_BAND_HEADING_ID = 'bl-reviews-heading';
@@ -84,7 +84,7 @@ export function ReviewsBandList({ classes, children }) {
84
84
  export function ReviewsBandItem({ review, lead = false, layout, classes }) {
85
85
  const parts = splitReviewText(review.text, layout?.excerpt ?? DEFAULT_EXCERPT);
86
86
  const date = new Date(review.createTime);
87
- return (_jsx("li", { ...slot(classes, 'item'), "data-lead": lead ? 'true' : undefined, children: _jsxs("figure", { children: [_jsx("span", { ...slot(classes, 'stars'), children: _jsx(RatingStars, { rating: review.rating, label: `Rated ${review.rating} out of 5` }) }), _jsx("blockquote", { ...slot(classes, 'quote'), children: parts ? (_jsxs(_Fragment, { children: [_jsx("p", { children: parts[0] }), _jsxs("details", { ...slot(classes, 'more'), children: [_jsx("summary", { children: "Read more" }), _jsx("p", { children: parts[1] })] })] })) : (_jsx("p", { children: review.text })) }), _jsxs("figcaption", { ...slot(classes, 'author'), children: [layout?.avatars && review.authorPhotoUrl ? (_jsx("img", { ...slot(classes, 'avatar'), src: review.authorPhotoUrl, alt: "", width: 48, height: 48, loading: "lazy", decoding: "async", referrerPolicy: "no-referrer" })) : null, _jsx("span", { ...slot(classes, 'name'), children: review.authorName }), ' ', _jsx("time", { ...slot(classes, 'date'), dateTime: DAY.format(date), children: MONTH.format(date) })] })] }) }));
87
+ return (_jsx("li", { ...repeatedSlot('item'), "data-lead": lead ? 'true' : undefined, children: _jsxs("figure", { children: [_jsx("span", { ...repeatedSlot('stars'), children: _jsx(RatingStars, { rating: review.rating, label: `Rated ${review.rating} out of 5` }) }), _jsx("blockquote", { ...repeatedSlot('quote'), children: parts ? (_jsxs(_Fragment, { children: [_jsx("p", { children: parts[0] }), _jsxs("details", { ...repeatedSlot('more'), children: [_jsx("summary", { children: "Read more" }), _jsx("p", { children: parts[1] })] })] })) : (_jsx("p", { children: review.text })) }), _jsxs("figcaption", { ...repeatedSlot('author'), children: [layout?.avatars && review.authorPhotoUrl ? (_jsx("img", { ...repeatedSlot('avatar'), src: review.authorPhotoUrl, alt: "", width: 48, height: 48, loading: "lazy", decoding: "async", referrerPolicy: "no-referrer" })) : null, _jsx("span", { ...repeatedSlot('name'), children: review.authorName }), ' ', _jsx("time", { ...repeatedSlot('date'), dateTime: DAY.format(date), children: MONTH.format(date) })] })] }) }));
88
88
  }
89
89
  function ReviewsBandSection({ reviews, heading, eyebrow, intro, headingId = REVIEWS_BAND_HEADING_ID, tone, decoration, variant = 'band', layout, classes }) {
90
90
  const shown = shownVariant(reviews, variant);
@@ -1 +1 @@
1
- {"version":3,"file":"reviews-band.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/reviews-band.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * S7 `ReviewsBand` (`reviews`): prominent, honest proof from the Business Profile feed. Server component, no client\n * JS. Google's own average and total sit beside the selected reviews with a link to the profile; the text is verbatim\n * and always in the HTML (a long review folds behind a native \"Read more\" disclosure); the band says how the reviews\n * were chosen (\"Selected five-star Google reviews\" or \"Recent Google reviews\"). No review or rating schema: a business\n * that controls its own reviews isn't eligible for review stars.\n *\n * Data comes only from `prepareBusinessProfileReviews` (site-core), which applies the policy, mention ordering, the\n * 3.7.0 exclude guard and the limit. No prepared reviews, or none and no rating, renders nothing at all (no heading).\n * With no written reviews but a rating, every variant renders as `summary`: the heading and the rating chip only.\n * Never place it on the contact page or inside another section.\n *\n * Sites that arrange their own layout use the parts (`ReviewsBand.Root`, `.Heading`, `.Summary`, `.List`, `.Item`):\n * the root keeps the section semantics, the empty rule and the disclosure line; the parts keep the data and markup.\n */\nimport { Children, type ReactNode } from 'react'\nimport {\n businessProfileReviewMentions,\n type BusinessProfileReview,\n type PreparedBusinessProfileReviews,\n} from '@brandlab-digital/site-core/google-business-reviews'\nimport { RatingStars, Section, SectionHeading, type SectionTone } from '../primitives/layout.js'\nimport { slot, type SkinEntry, type SlotClasses } from '../skin.js'\nimport { hasGoogleRating, RatingChip } from './rating-chip.js'\n\nexport const REVIEWS_BAND_VARIANTS = ['band', 'feature', 'cards', 'wall', 'summary'] as const\nexport type ReviewsBandVariant = (typeof REVIEWS_BAND_VARIANTS)[number]\n\nexport type ReviewsBandSlot =\n | 'root'\n | 'inner'\n | 'top'\n | 'head'\n | 'eyebrow'\n | 'heading'\n | 'lead'\n | 'summary'\n | 'rating'\n | 'source'\n | 'list'\n | 'item'\n | 'stars'\n | 'quote'\n | 'more'\n | 'author'\n | 'avatar'\n | 'name'\n | 'date'\n | 'disclosure'\n | 'profileLink'\n\nexport interface ReviewsBandLayout {\n readonly columns?: 1 | 2 | 3\n readonly align?: 'start' | 'center'\n /** Reviewer photos beside the names (Google's own small photos, decorative). */\n readonly avatars?: boolean\n /** Characters shown before a long review folds behind \"Read more\" (default 320); `false` never folds. */\n readonly excerpt?: number | false\n}\n\nexport type ReviewsBandSkin = SkinEntry<ReviewsBandSlot, ReviewsBandVariant, ReviewsBandLayout>\n\ntype Classes = SlotClasses<ReviewsBandSlot> | undefined\n\nexport const REVIEWS_BAND_HEADING_ID = 'bl-reviews-heading'\nconst DEFAULT_EXCERPT = 320\n\n/**\n * The variant a band renders, or null when it has nothing true to show. With no written reviews every variant is\n * `summary`, and `summary` needs Google's rating: a heading over nothing never renders.\n */\nfunction shownVariant(reviews: PreparedBusinessProfileReviews | null | undefined, variant: ReviewsBandVariant): ReviewsBandVariant | null {\n if (!reviews) return null\n const shown = reviews.reviews.length === 0 ? 'summary' : variant\n return shown === 'summary' && !hasGoogleRating(reviews) ? null : shown\n}\n\nconst MONTH = new Intl.DateTimeFormat('en-AU', { month: 'long', year: 'numeric', timeZone: 'Australia/Sydney' })\nconst DAY = new Intl.DateTimeFormat('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit', timeZone: 'Australia/Sydney' })\n\n/**\n * A long review split at a sentence end (or else a space) near `limit`, so the first part reads on its own. The two\n * parts together are the review word for word. Short reviews, and reviews only a little over the limit, don't fold.\n */\nexport function splitReviewText(text: string, limit: number | false): readonly [string, string] | null {\n if (limit === false || text.length <= limit * 1.25) return null\n const window = text.slice(0, limit)\n const sentence = Math.max(...['. ', '! ', '? ', '\\n'].map((mark) => window.lastIndexOf(mark)))\n const space = window.lastIndexOf(' ')\n const cut = sentence >= limit * 0.4 ? sentence + 1 : space > 0 ? space : limit\n return [text.slice(0, cut).trimEnd(), text.slice(cut).trimStart()]\n}\n\n/** The `feature` variant leads with the longest review naming a mention word, or else the longest review. */\nfunction featureOrder(reviews: PreparedBusinessProfileReviews): BusinessProfileReview[] {\n const named = reviews.mention.length > 0 ? reviews.reviews.filter((review) => businessProfileReviewMentions(review, reviews.mention)) : []\n const pool = named.length > 0 ? named : reviews.reviews\n const lead = pool.reduce<BusinessProfileReview | undefined>((longest, review) => (!longest || review.text.length > longest.text.length ? review : longest), undefined)\n return lead ? [lead, ...reviews.reviews.filter((review) => review !== lead)] : []\n}\n\nexport interface ReviewsBandRootProps extends ReviewsBandSkin {\n readonly reviews: PreparedBusinessProfileReviews | null | undefined\n /** The id of the band's H2 (default `bl-reviews-heading`). */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n /** Signature art, rendered `aria-hidden`. */\n readonly decoration?: ReactNode\n readonly children: ReactNode\n}\n\n/**\n * The section: `data-bl-section=\"reviews\"` with `data-bl-source=\"feed\"` (the ready gate's mark for feed numbers),\n * the empty rule, and the locked disclosure line after the children.\n */\nexport function ReviewsBandRoot({\n reviews,\n headingId = REVIEWS_BAND_HEADING_ID,\n tone,\n decoration,\n variant = 'band',\n layout,\n classes,\n children,\n}: ReviewsBandRootProps) {\n const shown = shownVariant(reviews, variant)\n if (!shown || !reviews) return null\n return (\n <Section\n id=\"reviews\"\n labelledBy={headingId}\n tone={tone}\n variant={shown}\n classes={{ root: classes?.root, inner: classes?.inner }}\n decoration={decoration}\n hooks={{ 'data-bl-source': 'feed', 'data-columns': String(layout?.columns ?? 3), 'data-align': layout?.align ?? 'start' }}\n >\n {children}\n {shown === 'summary' ? null : (\n <p {...slot(classes, 'disclosure')}>\n {reviews.policy === 'five-star' ? 'Selected five-star Google reviews.' : 'Recent Google reviews.'}{' '}\n <a {...slot(classes, 'profileLink')} href={reviews.profileUrl} target=\"_blank\" rel=\"noopener noreferrer\">\n Read all reviews on Google\n </a>\n </p>\n )}\n </Section>\n )\n}\n\nexport interface ReviewsBandHeadingProps {\n /** The H2's id; the same `headingId` the root has (default `bl-reviews-heading`). */\n readonly id?: string\n /** The H2 text the site owns. */\n readonly children: string\n readonly eyebrow?: string\n readonly intro?: string\n readonly layout?: Pick<ReviewsBandLayout, 'align'>\n readonly classes?: Classes\n}\n\n/** The heading block: an optional eyebrow, the H2 and an optional intro. */\nexport function ReviewsBandHeading({ id = REVIEWS_BAND_HEADING_ID, children, eyebrow, intro, layout, classes }: ReviewsBandHeadingProps) {\n return (\n <SectionHeading\n id={id}\n title={children}\n eyebrow={eyebrow}\n lead={intro}\n layout={{ align: layout?.align ?? 'start' }}\n classes={{ head: classes?.head, eyebrow: classes?.eyebrow, heading: classes?.heading, lead: classes?.lead }}\n />\n )\n}\n\nexport interface ReviewsBandSummaryProps {\n readonly reviews: PreparedBusinessProfileReviews | null | undefined\n readonly classes?: Classes\n}\n\n/** Google's own average and total with the profile link; nothing when the feed has no rating. */\nexport function ReviewsBandSummary({ reviews, classes }: ReviewsBandSummaryProps) {\n if (!hasGoogleRating(reviews)) return null\n return (\n <div {...slot(classes, 'summary')}>\n <RatingChip reviews={reviews} classes={{ rating: classes?.rating, source: classes?.source }} />\n </div>\n )\n}\n\n/** The review list; nothing without items. */\nexport function ReviewsBandList({ classes, children }: { readonly classes?: Classes; readonly children: ReactNode }) {\n if (Children.toArray(children).length === 0) return null\n return <ul {...slot(classes, 'list')}>{children}</ul>\n}\n\nexport interface ReviewsBandItemProps {\n readonly review: BusinessProfileReview\n /** The `feature` variant's wide lead card. */\n readonly lead?: boolean\n readonly layout?: Pick<ReviewsBandLayout, 'avatars' | 'excerpt'>\n readonly classes?: Classes\n}\n\n/** One review, word for word: its stars, the quote and the reviewer with the month. */\nexport function ReviewsBandItem({ review, lead = false, layout, classes }: ReviewsBandItemProps) {\n const parts = splitReviewText(review.text, layout?.excerpt ?? DEFAULT_EXCERPT)\n const date = new Date(review.createTime)\n return (\n <li {...slot(classes, 'item')} data-lead={lead ? 'true' : undefined}>\n <figure>\n <span {...slot(classes, 'stars')}>\n <RatingStars rating={review.rating} label={`Rated ${review.rating} out of 5`} />\n </span>\n <blockquote {...slot(classes, 'quote')}>\n {parts ? (\n <>\n <p>{parts[0]}</p>\n <details {...slot(classes, 'more')}>\n <summary>Read more</summary>\n <p>{parts[1]}</p>\n </details>\n </>\n ) : (\n <p>{review.text}</p>\n )}\n </blockquote>\n <figcaption {...slot(classes, 'author')}>\n {layout?.avatars && review.authorPhotoUrl ? (\n <img\n {...slot(classes, 'avatar')}\n src={review.authorPhotoUrl}\n alt=\"\"\n width={48}\n height={48}\n loading=\"lazy\"\n decoding=\"async\"\n referrerPolicy=\"no-referrer\"\n />\n ) : null}\n <span {...slot(classes, 'name')}>{review.authorName}</span>{' '}\n <time {...slot(classes, 'date')} dateTime={DAY.format(date)}>\n {MONTH.format(date)}\n </time>\n </figcaption>\n </figure>\n </li>\n )\n}\n\nexport interface ReviewsBandProps extends ReviewsBandSkin {\n /** From `prepareBusinessProfileReviews`; null renders nothing at all. */\n readonly reviews: PreparedBusinessProfileReviews | null | undefined\n /** The H2 text the site owns. */\n readonly heading: string\n readonly eyebrow?: string\n readonly intro?: string\n /** The id of the band's H2 (default `bl-reviews-heading`). */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n /** Signature art, rendered `aria-hidden`. */\n readonly decoration?: ReactNode\n}\n\nfunction ReviewsBandSection({ reviews, heading, eyebrow, intro, headingId = REVIEWS_BAND_HEADING_ID, tone, decoration, variant = 'band', layout, classes }: ReviewsBandProps) {\n const shown = shownVariant(reviews, variant)\n if (!shown || !reviews) return null\n const items = shown === 'summary' ? [] : shown === 'feature' ? featureOrder(reviews) : reviews.reviews\n return (\n <ReviewsBandRoot reviews={reviews} headingId={headingId} tone={tone} decoration={decoration} variant={shown} layout={layout} classes={classes}>\n <div {...slot(classes, 'top')}>\n <ReviewsBandHeading id={headingId} eyebrow={eyebrow} intro={intro} layout={layout} classes={classes}>\n {heading}\n </ReviewsBandHeading>\n <ReviewsBandSummary reviews={reviews} classes={classes} />\n </div>\n <ReviewsBandList classes={classes}>\n {items.map((review, index) => (\n <ReviewsBandItem key={review.id} review={review} lead={shown === 'feature' && index === 0} layout={layout} classes={classes} />\n ))}\n </ReviewsBandList>\n </ReviewsBandRoot>\n )\n}\n\n/** The reviews band, with its parts for sites that arrange their own layout. */\nexport const ReviewsBand = Object.assign(ReviewsBandSection, {\n Root: ReviewsBandRoot,\n Heading: ReviewsBandHeading,\n Summary: ReviewsBandSummary,\n List: ReviewsBandList,\n Item: ReviewsBandItem,\n})\n"]}
1
+ {"version":3,"file":"reviews-band.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/reviews-band.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * S7 `ReviewsBand` (`reviews`): prominent, honest proof from the Business Profile feed. Server component, no client\n * JS. Google's own average and total sit beside the selected reviews with a link to the profile; the text is verbatim\n * and always in the HTML (a long review folds behind a native \"Read more\" disclosure); the band says how the reviews\n * were chosen (\"Selected five-star Google reviews\" or \"Recent Google reviews\"). No review or rating schema: a business\n * that controls its own reviews isn't eligible for review stars.\n *\n * Data comes only from `prepareBusinessProfileReviews` (site-core), which applies the policy, mention ordering, the\n * 3.7.0 exclude guard and the limit. No prepared reviews, or none and no rating, renders nothing at all (no heading).\n * With no written reviews but a rating, every variant renders as `summary`: the heading and the rating chip only.\n * Never place it on the contact page or inside another section.\n *\n * Sites that arrange their own layout use the parts (`ReviewsBand.Root`, `.Heading`, `.Summary`, `.List`, `.Item`):\n * the root keeps the section semantics, the empty rule and the disclosure line; the parts keep the data and markup.\n */\nimport { Children, type ReactNode } from 'react'\nimport {\n businessProfileReviewMentions,\n type BusinessProfileReview,\n type PreparedBusinessProfileReviews,\n} from '@brandlab-digital/site-core/google-business-reviews'\nimport { RatingStars, Section, SectionHeading, type SectionTone } from '../primitives/layout.js'\nimport type { RepeatedSlotOf } from '../repeated-slots.js'\nimport { repeatedSlot, slot, type SkinEntry, type SlotClasses } from '../skin.js'\nimport { hasGoogleRating, RatingChip } from './rating-chip.js'\n\nexport const REVIEWS_BAND_VARIANTS = ['band', 'feature', 'cards', 'wall', 'summary'] as const\nexport type ReviewsBandVariant = (typeof REVIEWS_BAND_VARIANTS)[number]\n\nexport type ReviewsBandSlot =\n | 'root'\n | 'inner'\n | 'top'\n | 'head'\n | 'eyebrow'\n | 'heading'\n | 'lead'\n | 'summary'\n | 'rating'\n | 'source'\n | 'list'\n | 'item'\n | 'stars'\n | 'quote'\n | 'more'\n | 'author'\n | 'avatar'\n | 'name'\n | 'date'\n | 'disclosure'\n | 'profileLink'\n\nexport interface ReviewsBandLayout {\n readonly columns?: 1 | 2 | 3\n readonly align?: 'start' | 'center'\n /** Reviewer photos beside the names (Google's own small photos, decorative). */\n readonly avatars?: boolean\n /** Characters shown before a long review folds behind \"Read more\" (default 320); `false` never folds. */\n readonly excerpt?: number | false\n}\n\n/** Rendered once per review: styled from the skin stylesheet, never by class (3.14.0). */\nexport type ReviewsBandRepeatedSlot = RepeatedSlotOf<'reviewsBand'>\nexport type ReviewsBandSkin = SkinEntry<ReviewsBandSlot, ReviewsBandVariant, ReviewsBandLayout, ReviewsBandRepeatedSlot>\n\ntype Classes = SlotClasses<Exclude<ReviewsBandSlot, ReviewsBandRepeatedSlot>> | undefined\n\nexport const REVIEWS_BAND_HEADING_ID = 'bl-reviews-heading'\nconst DEFAULT_EXCERPT = 320\n\n/**\n * The variant a band renders, or null when it has nothing true to show. With no written reviews every variant is\n * `summary`, and `summary` needs Google's rating: a heading over nothing never renders.\n */\nfunction shownVariant(reviews: PreparedBusinessProfileReviews | null | undefined, variant: ReviewsBandVariant): ReviewsBandVariant | null {\n if (!reviews) return null\n const shown = reviews.reviews.length === 0 ? 'summary' : variant\n return shown === 'summary' && !hasGoogleRating(reviews) ? null : shown\n}\n\nconst MONTH = new Intl.DateTimeFormat('en-AU', { month: 'long', year: 'numeric', timeZone: 'Australia/Sydney' })\nconst DAY = new Intl.DateTimeFormat('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit', timeZone: 'Australia/Sydney' })\n\n/**\n * A long review split at a sentence end (or else a space) near `limit`, so the first part reads on its own. The two\n * parts together are the review word for word. Short reviews, and reviews only a little over the limit, don't fold.\n */\nexport function splitReviewText(text: string, limit: number | false): readonly [string, string] | null {\n if (limit === false || text.length <= limit * 1.25) return null\n const window = text.slice(0, limit)\n const sentence = Math.max(...['. ', '! ', '? ', '\\n'].map((mark) => window.lastIndexOf(mark)))\n const space = window.lastIndexOf(' ')\n const cut = sentence >= limit * 0.4 ? sentence + 1 : space > 0 ? space : limit\n return [text.slice(0, cut).trimEnd(), text.slice(cut).trimStart()]\n}\n\n/** The `feature` variant leads with the longest review naming a mention word, or else the longest review. */\nfunction featureOrder(reviews: PreparedBusinessProfileReviews): BusinessProfileReview[] {\n const named = reviews.mention.length > 0 ? reviews.reviews.filter((review) => businessProfileReviewMentions(review, reviews.mention)) : []\n const pool = named.length > 0 ? named : reviews.reviews\n const lead = pool.reduce<BusinessProfileReview | undefined>((longest, review) => (!longest || review.text.length > longest.text.length ? review : longest), undefined)\n return lead ? [lead, ...reviews.reviews.filter((review) => review !== lead)] : []\n}\n\nexport interface ReviewsBandRootProps extends ReviewsBandSkin {\n readonly reviews: PreparedBusinessProfileReviews | null | undefined\n /** The id of the band's H2 (default `bl-reviews-heading`). */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n /** Signature art, rendered `aria-hidden`. */\n readonly decoration?: ReactNode\n readonly children: ReactNode\n}\n\n/**\n * The section: `data-bl-section=\"reviews\"` with `data-bl-source=\"feed\"` (the ready gate's mark for feed numbers),\n * the empty rule, and the locked disclosure line after the children.\n */\nexport function ReviewsBandRoot({\n reviews,\n headingId = REVIEWS_BAND_HEADING_ID,\n tone,\n decoration,\n variant = 'band',\n layout,\n classes,\n children,\n}: ReviewsBandRootProps) {\n const shown = shownVariant(reviews, variant)\n if (!shown || !reviews) return null\n return (\n <Section\n id=\"reviews\"\n labelledBy={headingId}\n tone={tone}\n variant={shown}\n classes={{ root: classes?.root, inner: classes?.inner }}\n decoration={decoration}\n hooks={{ 'data-bl-source': 'feed', 'data-columns': String(layout?.columns ?? 3), 'data-align': layout?.align ?? 'start' }}\n >\n {children}\n {shown === 'summary' ? null : (\n <p {...slot(classes, 'disclosure')}>\n {reviews.policy === 'five-star' ? 'Selected five-star Google reviews.' : 'Recent Google reviews.'}{' '}\n <a {...slot(classes, 'profileLink')} href={reviews.profileUrl} target=\"_blank\" rel=\"noopener noreferrer\">\n Read all reviews on Google\n </a>\n </p>\n )}\n </Section>\n )\n}\n\nexport interface ReviewsBandHeadingProps {\n /** The H2's id; the same `headingId` the root has (default `bl-reviews-heading`). */\n readonly id?: string\n /** The H2 text the site owns. */\n readonly children: string\n readonly eyebrow?: string\n readonly intro?: string\n readonly layout?: Pick<ReviewsBandLayout, 'align'>\n readonly classes?: Classes\n}\n\n/** The heading block: an optional eyebrow, the H2 and an optional intro. */\nexport function ReviewsBandHeading({ id = REVIEWS_BAND_HEADING_ID, children, eyebrow, intro, layout, classes }: ReviewsBandHeadingProps) {\n return (\n <SectionHeading\n id={id}\n title={children}\n eyebrow={eyebrow}\n lead={intro}\n layout={{ align: layout?.align ?? 'start' }}\n classes={{ head: classes?.head, eyebrow: classes?.eyebrow, heading: classes?.heading, lead: classes?.lead }}\n />\n )\n}\n\nexport interface ReviewsBandSummaryProps {\n readonly reviews: PreparedBusinessProfileReviews | null | undefined\n readonly classes?: Classes\n}\n\n/** Google's own average and total with the profile link; nothing when the feed has no rating. */\nexport function ReviewsBandSummary({ reviews, classes }: ReviewsBandSummaryProps) {\n if (!hasGoogleRating(reviews)) return null\n return (\n <div {...slot(classes, 'summary')}>\n <RatingChip reviews={reviews} classes={{ rating: classes?.rating, source: classes?.source }} />\n </div>\n )\n}\n\n/** The review list; nothing without items. */\nexport function ReviewsBandList({ classes, children }: { readonly classes?: Classes; readonly children: ReactNode }) {\n if (Children.toArray(children).length === 0) return null\n return <ul {...slot(classes, 'list')}>{children}</ul>\n}\n\nexport interface ReviewsBandItemProps {\n readonly review: BusinessProfileReview\n /** The `feature` variant's wide lead card. */\n readonly lead?: boolean\n readonly layout?: Pick<ReviewsBandLayout, 'avatars' | 'excerpt'>\n readonly classes?: Classes\n}\n\n/** One review, word for word: its stars, the quote and the reviewer with the month. */\nexport function ReviewsBandItem({ review, lead = false, layout, classes }: ReviewsBandItemProps) {\n const parts = splitReviewText(review.text, layout?.excerpt ?? DEFAULT_EXCERPT)\n const date = new Date(review.createTime)\n return (\n <li {...repeatedSlot('item')} data-lead={lead ? 'true' : undefined}>\n <figure>\n <span {...repeatedSlot('stars')}>\n <RatingStars rating={review.rating} label={`Rated ${review.rating} out of 5`} />\n </span>\n <blockquote {...repeatedSlot('quote')}>\n {parts ? (\n <>\n <p>{parts[0]}</p>\n <details {...repeatedSlot('more')}>\n <summary>Read more</summary>\n <p>{parts[1]}</p>\n </details>\n </>\n ) : (\n <p>{review.text}</p>\n )}\n </blockquote>\n <figcaption {...repeatedSlot('author')}>\n {layout?.avatars && review.authorPhotoUrl ? (\n <img\n {...repeatedSlot('avatar')}\n src={review.authorPhotoUrl}\n alt=\"\"\n width={48}\n height={48}\n loading=\"lazy\"\n decoding=\"async\"\n referrerPolicy=\"no-referrer\"\n />\n ) : null}\n <span {...repeatedSlot('name')}>{review.authorName}</span>{' '}\n <time {...repeatedSlot('date')} dateTime={DAY.format(date)}>\n {MONTH.format(date)}\n </time>\n </figcaption>\n </figure>\n </li>\n )\n}\n\nexport interface ReviewsBandProps extends ReviewsBandSkin {\n /** From `prepareBusinessProfileReviews`; null renders nothing at all. */\n readonly reviews: PreparedBusinessProfileReviews | null | undefined\n /** The H2 text the site owns. */\n readonly heading: string\n readonly eyebrow?: string\n readonly intro?: string\n /** The id of the band's H2 (default `bl-reviews-heading`). */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n /** Signature art, rendered `aria-hidden`. */\n readonly decoration?: ReactNode\n}\n\nfunction ReviewsBandSection({ reviews, heading, eyebrow, intro, headingId = REVIEWS_BAND_HEADING_ID, tone, decoration, variant = 'band', layout, classes }: ReviewsBandProps) {\n const shown = shownVariant(reviews, variant)\n if (!shown || !reviews) return null\n const items = shown === 'summary' ? [] : shown === 'feature' ? featureOrder(reviews) : reviews.reviews\n return (\n <ReviewsBandRoot reviews={reviews} headingId={headingId} tone={tone} decoration={decoration} variant={shown} layout={layout} classes={classes}>\n <div {...slot(classes, 'top')}>\n <ReviewsBandHeading id={headingId} eyebrow={eyebrow} intro={intro} layout={layout} classes={classes}>\n {heading}\n </ReviewsBandHeading>\n <ReviewsBandSummary reviews={reviews} classes={classes} />\n </div>\n <ReviewsBandList classes={classes}>\n {items.map((review, index) => (\n <ReviewsBandItem key={review.id} review={review} lead={shown === 'feature' && index === 0} layout={layout} classes={classes} />\n ))}\n </ReviewsBandList>\n </ReviewsBandRoot>\n )\n}\n\n/** The reviews band, with its parts for sites that arrange their own layout. */\nexport const ReviewsBand = Object.assign(ReviewsBandSection, {\n Root: ReviewsBandRoot,\n Heading: ReviewsBandHeading,\n Summary: ReviewsBandSummary,\n List: ReviewsBandList,\n Item: ReviewsBandItem,\n})\n"]}
@@ -0,0 +1,82 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { BrandlabMediaEntry } from '@brandlab-digital/site-core/media';
3
+ import { type LinkPrefetch } from '../prefetch.js';
4
+ import { type MediaLayout } from '../primitives/media.js';
5
+ import { type SectionHeadingSlot, type SectionSlot, type SectionTone } from '../primitives/layout.js';
6
+ import type { RepeatedSlotOf } from '../repeated-slots.js';
7
+ import { type SkinEntry } from '../skin.js';
8
+ import type { UiLink } from '../types.js';
9
+ export declare const SERVICES_GRID_VARIANTS: readonly ["rows", "cards", "compact"];
10
+ export type ServicesGridVariant = (typeof SERVICES_GRID_VARIANTS)[number];
11
+ export type ServiceCardSlot = 'item' | 'media' | 'body' | 'title' | 'summary' | 'points' | 'point' | 'action' | 'link';
12
+ export type ServicesGridSlot = SectionSlot | SectionHeadingSlot | 'list' | 'more' | ServiceCardSlot;
13
+ /** Rendered once per service: styled from the skin stylesheet, never by class. */
14
+ export type ServicesGridRepeatedSlot = RepeatedSlotOf<'servicesGrid'>;
15
+ export interface ServicesGridLayout {
16
+ /** `cards`: columns on wide screens. Defaults to 3. */
17
+ readonly columns?: 2 | 3 | 4;
18
+ /** `rows` and `cards`: the photo's crop. Defaults to 4:3. */
19
+ readonly ratio?: NonNullable<MediaLayout['ratio']>;
20
+ /** `rows`: the photo's side on wide screens. Defaults to `alternate` (first photo at the start). */
21
+ readonly mediaSide?: 'alternate' | 'start' | 'end';
22
+ }
23
+ export type ServicesGridSkin = SkinEntry<ServicesGridSlot, ServicesGridVariant, ServicesGridLayout, ServicesGridRepeatedSlot>;
24
+ /** The most services `rows` and `cards` show (K11; the in-content intent-prefetch limit). */
25
+ export declare const SERVICES_GRID_CARD_LIMIT: 8;
26
+ /** One service, in money order. */
27
+ export interface ServiceCardData {
28
+ /** A stable slug, rendered as `data-item`. */
29
+ readonly id: string;
30
+ /** The service's name, the card's H3 (`rows`, `cards`) or the link text (`compact`). */
31
+ readonly title: string;
32
+ /**
33
+ * Where the card goes: the service page, its service-in-suburb page or its anchored section on this page
34
+ * (`#tree-pruning`). From the site's data, never built here.
35
+ */
36
+ readonly href: string;
37
+ /** One line on what the service is. `rows` and `cards`; never a price. */
38
+ readonly summary?: ReactNode;
39
+ /** `rows`: a few scope points under the summary (verified service scope, never prices). */
40
+ readonly points?: readonly string[];
41
+ /** The service's own photo from the media manifest. `rows` and `cards`; no image on two cards. */
42
+ readonly image?: BrandlabMediaEntry;
43
+ /**
44
+ * `rows`: the link under the text, for example "Tree removal in Castle Hill". Left out, the title is the link, as on
45
+ * `cards`. Name the service: never "Learn more".
46
+ */
47
+ readonly linkLabel?: string;
48
+ }
49
+ export interface ServiceCardProps {
50
+ readonly service: ServiceCardData;
51
+ readonly variant: ServicesGridVariant;
52
+ /** `rows` and `cards`: the photo's `sizes`. */
53
+ readonly imageSizes?: string;
54
+ readonly ratio?: ServicesGridLayout['ratio'];
55
+ /** `rows` and `cards` default to `intent`; `compact` is always off. */
56
+ readonly prefetch?: LinkPrefetch;
57
+ }
58
+ /**
59
+ * One service as a list item of `ServicesGrid` (also the building block for `RelatedServices`). Its parts are the
60
+ * grid's repeated slots: the root sets no classes on them.
61
+ */
62
+ export declare function ServiceCard({ service, variant, imageSizes, ratio, prefetch }: ServiceCardProps): import("react/jsx-runtime").JSX.Element;
63
+ /** The photo `sizes` for each variant: half the container in rows, one column of the grid in cards. */
64
+ export declare function defaultImageSizes(variant: Exclude<ServicesGridVariant, 'compact'>, columns: 2 | 3 | 4): string;
65
+ export interface ServicesGridProps extends ServicesGridSkin {
66
+ /** The H2, for example "Our tree services" or "Tree services in Castle Hill". */
67
+ readonly title: ReactNode;
68
+ readonly eyebrow?: ReactNode;
69
+ readonly lead?: ReactNode;
70
+ /** Every service the page offers, in money order. */
71
+ readonly services: readonly ServiceCardData[];
72
+ /** A link after the list, for example the services hub or "Find your area". Never prefetched. */
73
+ readonly more?: UiLink;
74
+ /** `rows` and `cards`: override the photo `sizes` when the skin's grid differs from the defaults. */
75
+ readonly imageSizes?: string;
76
+ /** Defaults to `services-heading`. */
77
+ readonly headingId?: string;
78
+ /** Set by `SectionSequence`. */
79
+ readonly tone?: SectionTone;
80
+ }
81
+ export declare function ServicesGrid({ title, eyebrow, lead, services, more, imageSizes, headingId, tone, variant, layout, classes, }: ServicesGridProps): import("react/jsx-runtime").JSX.Element | null;
82
+ //# sourceMappingURL=services-grid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"services-grid.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/services-grid.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * `ServicesGrid` (S4, `services`, 3.14.0) and `ServiceCard`: route the visitor to the right service. Server components.
4
+ *
5
+ * Variants:
6
+ * - `rows` (the fleet reference, AA's `ServiceRows`): one full-width row per service, the photo beside the text and
7
+ * alternating sides on wide screens, with an optional line of scope points and the service's own link;
8
+ * - `cards`: a grid of cards, each headed by its linked title;
9
+ * - `compact` (suburb and region pages): a lean list of every service offered there, each a single link to its
10
+ * service-in-suburb page or to its anchored section on the same page (`#tree-pruning`). No photos and no summaries.
11
+ *
12
+ * Locked rules (S4): one link per card; no prices and no before-and-afters in cards; no image on two cards; at most 8
13
+ * `rows` or `cards` (K11's card limit, and the most in-content links that prefetch on intent), so a page that offers
14
+ * more services lists them `compact` or links its services hub. Card links prefetch on intent (`LINK_PREFETCH_POLICY`
15
+ * `contentCard`); compact links, the hub link and same-page anchors never prefetch.
16
+ */
17
+ import Link from 'next/link';
18
+ import { LINK_PREFETCH_LIMITS, LINK_PREFETCH_POLICY, linkPrefetchProps } from '../prefetch.js';
19
+ import { Media } from '../primitives/media.js';
20
+ import { Section, SectionHeading } from '../primitives/layout.js';
21
+ import { repeatedSlot, slot } from '../skin.js';
22
+ export const SERVICES_GRID_VARIANTS = ['rows', 'cards', 'compact'];
23
+ /** The most services `rows` and `cards` show (K11; the in-content intent-prefetch limit). */
24
+ export const SERVICES_GRID_CARD_LIMIT = LINK_PREFETCH_LIMITS.contentCard;
25
+ function ServiceLink({ href, prefetch, children }) {
26
+ // A same-page section needs no route prefetch: a plain anchor.
27
+ if (href.startsWith('#')) {
28
+ return (_jsx("a", { href: href, ...repeatedSlot('link'), children: children }));
29
+ }
30
+ return (_jsx(Link, { href: href, ...linkPrefetchProps(prefetch), ...repeatedSlot('link'), children: children }));
31
+ }
32
+ /**
33
+ * One service as a list item of `ServicesGrid` (also the building block for `RelatedServices`). Its parts are the
34
+ * grid's repeated slots: the root sets no classes on them.
35
+ */
36
+ export function ServiceCard({ service, variant, imageSizes, ratio = '4:3', prefetch }) {
37
+ if (variant === 'compact') {
38
+ return (_jsx("li", { ...repeatedSlot('item'), "data-item": service.id, children: _jsx(ServiceLink, { href: service.href, prefetch: LINK_PREFETCH_POLICY.otherServicesInSuburb, children: service.title }) }));
39
+ }
40
+ const linkPrefetch = prefetch ?? LINK_PREFETCH_POLICY.contentCard;
41
+ const separateLink = variant === 'rows' && service.linkLabel !== undefined && service.linkLabel.trim() !== '';
42
+ return (_jsxs("li", { ...repeatedSlot('item'), "data-item": service.id, children: [_jsxs("div", { ...repeatedSlot('body'), children: [_jsx("h3", { ...repeatedSlot('title'), children: separateLink ? (service.title) : (_jsx(ServiceLink, { href: service.href, prefetch: linkPrefetch, children: service.title })) }), service.summary ? _jsx("p", { ...repeatedSlot('summary'), children: service.summary }) : null, variant === 'rows' && service.points && service.points.length > 0 ? (_jsx("ul", { role: "list", ...repeatedSlot('points'), children: service.points.map((point) => (_jsx("li", { ...repeatedSlot('point'), children: point }, point))) })) : null, separateLink ? (_jsx("p", { ...repeatedSlot('action'), children: _jsx(ServiceLink, { href: service.href, prefetch: linkPrefetch, children: service.linkLabel }) })) : null] }), service.image ? (_jsx("div", { ...repeatedSlot('media'), children: _jsx(Media, { media: service.image, sizes: imageSizes ?? defaultImageSizes(variant, 3), layout: { ratio } }) })) : null] }));
43
+ }
44
+ /** The photo `sizes` for each variant: half the container in rows, one column of the grid in cards. */
45
+ export function defaultImageSizes(variant, columns) {
46
+ if (variant === 'rows')
47
+ return '(min-width: 1024px) 50vw, 100vw';
48
+ return `(min-width: 1024px) ${Math.round(100 / columns)}vw, (min-width: 640px) 50vw, 100vw`;
49
+ }
50
+ function assertServices(services, variant) {
51
+ const ids = new Set();
52
+ const hrefs = new Set();
53
+ const images = new Map();
54
+ for (const service of services) {
55
+ if (!service.id || ids.has(service.id))
56
+ throw new Error(`ServicesGrid: every service needs its own id ("${service.id}" is empty or repeated).`);
57
+ ids.add(service.id);
58
+ if (!service.title?.trim())
59
+ throw new Error(`ServicesGrid: the service "${service.id}" has no title.`);
60
+ if (!service.href?.trim())
61
+ throw new Error(`ServicesGrid: the service "${service.id}" has no link.`);
62
+ if (hrefs.has(service.href))
63
+ throw new Error(`ServicesGrid: two services link to ${service.href}; each card has its own page or section.`);
64
+ hrefs.add(service.href);
65
+ if (variant !== 'compact' && service.image) {
66
+ const other = images.get(service.image.src);
67
+ if (other)
68
+ throw new Error(`ServicesGrid: "${service.id}" and "${other}" use the same image (${service.image.src}); no image is on two cards.`);
69
+ images.set(service.image.src, service.id);
70
+ }
71
+ }
72
+ if (variant !== 'compact' && services.length > SERVICES_GRID_CARD_LIMIT) {
73
+ throw new Error(`ServicesGrid: ${services.length} services in ${variant}; ${variant} shows at most ${SERVICES_GRID_CARD_LIMIT} (K11). List every service with the compact variant, or show the money services and link the services hub.`);
74
+ }
75
+ }
76
+ export function ServicesGrid({ title, eyebrow, lead, services, more, imageSizes, headingId = 'services-heading', tone, variant = 'rows', layout, classes, }) {
77
+ if (services.length === 0)
78
+ return null;
79
+ assertServices(services, variant);
80
+ const columns = layout?.columns ?? 3;
81
+ const ratio = layout?.ratio ?? '4:3';
82
+ const hooks = variant === 'compact'
83
+ ? undefined
84
+ : variant === 'cards'
85
+ ? { 'data-columns': String(columns), 'data-ratio': ratio }
86
+ : { 'data-media-side': layout?.mediaSide ?? 'alternate', 'data-ratio': ratio };
87
+ const sizes = variant === 'compact' ? undefined : (imageSizes ?? defaultImageSizes(variant, columns));
88
+ return (_jsxs(Section, { id: "services", labelledBy: headingId, tone: tone, variant: variant, classes: classes, hooks: hooks, children: [_jsx(SectionHeading, { id: headingId, title: title, eyebrow: eyebrow, lead: lead, classes: classes }), _jsx("ul", { role: "list", ...slot(classes, 'list'), children: services.map((service) => (_jsx(ServiceCard, { service: service, variant: variant, imageSizes: sizes, ratio: ratio }, service.id))) }), more ? (_jsx("p", { ...slot(classes, 'more'), children: more.href.startsWith('#') ? (_jsx("a", { href: more.href, children: more.label })) : (_jsx(Link, { href: more.href, prefetch: false, children: more.label })) })) : null] }));
89
+ }
90
+ //# sourceMappingURL=services-grid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"services-grid.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/services-grid.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `ServicesGrid` (S4, `services`, 3.14.0) and `ServiceCard`: route the visitor to the right service. Server components.\n *\n * Variants:\n * - `rows` (the fleet reference, AA's `ServiceRows`): one full-width row per service, the photo beside the text and\n * alternating sides on wide screens, with an optional line of scope points and the service's own link;\n * - `cards`: a grid of cards, each headed by its linked title;\n * - `compact` (suburb and region pages): a lean list of every service offered there, each a single link to its\n * service-in-suburb page or to its anchored section on the same page (`#tree-pruning`). No photos and no summaries.\n *\n * Locked rules (S4): one link per card; no prices and no before-and-afters in cards; no image on two cards; at most 8\n * `rows` or `cards` (K11's card limit, and the most in-content links that prefetch on intent), so a page that offers\n * more services lists them `compact` or links its services hub. Card links prefetch on intent (`LINK_PREFETCH_POLICY`\n * `contentCard`); compact links, the hub link and same-page anchors never prefetch.\n */\nimport Link from 'next/link'\nimport type { ReactNode } from 'react'\nimport type { BrandlabMediaEntry } from '@brandlab-digital/site-core/media'\nimport { LINK_PREFETCH_LIMITS, LINK_PREFETCH_POLICY, linkPrefetchProps, type LinkPrefetch } from '../prefetch.js'\nimport { Media, type MediaLayout } from '../primitives/media.js'\nimport { Section, SectionHeading, type SectionHeadingSlot, type SectionSlot, type SectionTone } from '../primitives/layout.js'\nimport type { RepeatedSlotOf } from '../repeated-slots.js'\nimport { repeatedSlot, slot, type SkinEntry } from '../skin.js'\nimport type { UiLink } from '../types.js'\n\nexport const SERVICES_GRID_VARIANTS = ['rows', 'cards', 'compact'] as const\nexport type ServicesGridVariant = (typeof SERVICES_GRID_VARIANTS)[number]\nexport type ServiceCardSlot = 'item' | 'media' | 'body' | 'title' | 'summary' | 'points' | 'point' | 'action' | 'link'\nexport type ServicesGridSlot = SectionSlot | SectionHeadingSlot | 'list' | 'more' | ServiceCardSlot\n/** Rendered once per service: styled from the skin stylesheet, never by class. */\nexport type ServicesGridRepeatedSlot = RepeatedSlotOf<'servicesGrid'>\nexport interface ServicesGridLayout {\n /** `cards`: columns on wide screens. Defaults to 3. */\n readonly columns?: 2 | 3 | 4\n /** `rows` and `cards`: the photo's crop. Defaults to 4:3. */\n readonly ratio?: NonNullable<MediaLayout['ratio']>\n /** `rows`: the photo's side on wide screens. Defaults to `alternate` (first photo at the start). */\n readonly mediaSide?: 'alternate' | 'start' | 'end'\n}\nexport type ServicesGridSkin = SkinEntry<ServicesGridSlot, ServicesGridVariant, ServicesGridLayout, ServicesGridRepeatedSlot>\n\n/** The most services `rows` and `cards` show (K11; the in-content intent-prefetch limit). */\nexport const SERVICES_GRID_CARD_LIMIT = LINK_PREFETCH_LIMITS.contentCard\n\n/** One service, in money order. */\nexport interface ServiceCardData {\n /** A stable slug, rendered as `data-item`. */\n readonly id: string\n /** The service's name, the card's H3 (`rows`, `cards`) or the link text (`compact`). */\n readonly title: string\n /**\n * Where the card goes: the service page, its service-in-suburb page or its anchored section on this page\n * (`#tree-pruning`). From the site's data, never built here.\n */\n readonly href: string\n /** One line on what the service is. `rows` and `cards`; never a price. */\n readonly summary?: ReactNode\n /** `rows`: a few scope points under the summary (verified service scope, never prices). */\n readonly points?: readonly string[]\n /** The service's own photo from the media manifest. `rows` and `cards`; no image on two cards. */\n readonly image?: BrandlabMediaEntry\n /**\n * `rows`: the link under the text, for example \"Tree removal in Castle Hill\". Left out, the title is the link, as on\n * `cards`. Name the service: never \"Learn more\".\n */\n readonly linkLabel?: string\n}\n\nexport interface ServiceCardProps {\n readonly service: ServiceCardData\n readonly variant: ServicesGridVariant\n /** `rows` and `cards`: the photo's `sizes`. */\n readonly imageSizes?: string\n readonly ratio?: ServicesGridLayout['ratio']\n /** `rows` and `cards` default to `intent`; `compact` is always off. */\n readonly prefetch?: LinkPrefetch\n}\n\nfunction ServiceLink({ href, prefetch, children }: { href: string; prefetch: LinkPrefetch; children: ReactNode }) {\n // A same-page section needs no route prefetch: a plain anchor.\n if (href.startsWith('#')) {\n return (\n <a href={href} {...repeatedSlot('link')}>\n {children}\n </a>\n )\n }\n return (\n <Link href={href} {...linkPrefetchProps(prefetch)} {...repeatedSlot('link')}>\n {children}\n </Link>\n )\n}\n\n/**\n * One service as a list item of `ServicesGrid` (also the building block for `RelatedServices`). Its parts are the\n * grid's repeated slots: the root sets no classes on them.\n */\nexport function ServiceCard({ service, variant, imageSizes, ratio = '4:3', prefetch }: ServiceCardProps) {\n if (variant === 'compact') {\n return (\n <li {...repeatedSlot('item')} data-item={service.id}>\n <ServiceLink href={service.href} prefetch={LINK_PREFETCH_POLICY.otherServicesInSuburb}>\n {service.title}\n </ServiceLink>\n </li>\n )\n }\n const linkPrefetch = prefetch ?? LINK_PREFETCH_POLICY.contentCard\n const separateLink = variant === 'rows' && service.linkLabel !== undefined && service.linkLabel.trim() !== ''\n return (\n <li {...repeatedSlot('item')} data-item={service.id}>\n <div {...repeatedSlot('body')}>\n <h3 {...repeatedSlot('title')}>\n {separateLink ? (\n service.title\n ) : (\n <ServiceLink href={service.href} prefetch={linkPrefetch}>\n {service.title}\n </ServiceLink>\n )}\n </h3>\n {service.summary ? <p {...repeatedSlot('summary')}>{service.summary}</p> : null}\n {variant === 'rows' && service.points && service.points.length > 0 ? (\n <ul role=\"list\" {...repeatedSlot('points')}>\n {service.points.map((point) => (\n <li key={point} {...repeatedSlot('point')}>\n {point}\n </li>\n ))}\n </ul>\n ) : null}\n {separateLink ? (\n <p {...repeatedSlot('action')}>\n <ServiceLink href={service.href} prefetch={linkPrefetch}>\n {service.linkLabel}\n </ServiceLink>\n </p>\n ) : null}\n </div>\n {service.image ? (\n <div {...repeatedSlot('media')}>\n <Media media={service.image} sizes={imageSizes ?? defaultImageSizes(variant, 3)} layout={{ ratio }} />\n </div>\n ) : null}\n </li>\n )\n}\n\n/** The photo `sizes` for each variant: half the container in rows, one column of the grid in cards. */\nexport function defaultImageSizes(variant: Exclude<ServicesGridVariant, 'compact'>, columns: 2 | 3 | 4): string {\n if (variant === 'rows') return '(min-width: 1024px) 50vw, 100vw'\n return `(min-width: 1024px) ${Math.round(100 / columns)}vw, (min-width: 640px) 50vw, 100vw`\n}\n\nexport interface ServicesGridProps extends ServicesGridSkin {\n /** The H2, for example \"Our tree services\" or \"Tree services in Castle Hill\". */\n readonly title: ReactNode\n readonly eyebrow?: ReactNode\n readonly lead?: ReactNode\n /** Every service the page offers, in money order. */\n readonly services: readonly ServiceCardData[]\n /** A link after the list, for example the services hub or \"Find your area\". Never prefetched. */\n readonly more?: UiLink\n /** `rows` and `cards`: override the photo `sizes` when the skin's grid differs from the defaults. */\n readonly imageSizes?: string\n /** Defaults to `services-heading`. */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n}\n\nfunction assertServices(services: readonly ServiceCardData[], variant: ServicesGridVariant): void {\n const ids = new Set<string>()\n const hrefs = new Set<string>()\n const images = new Map<string, string>()\n for (const service of services) {\n if (!service.id || ids.has(service.id)) throw new Error(`ServicesGrid: every service needs its own id (\"${service.id}\" is empty or repeated).`)\n ids.add(service.id)\n if (!service.title?.trim()) throw new Error(`ServicesGrid: the service \"${service.id}\" has no title.`)\n if (!service.href?.trim()) throw new Error(`ServicesGrid: the service \"${service.id}\" has no link.`)\n if (hrefs.has(service.href)) throw new Error(`ServicesGrid: two services link to ${service.href}; each card has its own page or section.`)\n hrefs.add(service.href)\n if (variant !== 'compact' && service.image) {\n const other = images.get(service.image.src)\n if (other) throw new Error(`ServicesGrid: \"${service.id}\" and \"${other}\" use the same image (${service.image.src}); no image is on two cards.`)\n images.set(service.image.src, service.id)\n }\n }\n if (variant !== 'compact' && services.length > SERVICES_GRID_CARD_LIMIT) {\n throw new Error(\n `ServicesGrid: ${services.length} services in ${variant}; ${variant} shows at most ${SERVICES_GRID_CARD_LIMIT} (K11). List every service with the compact variant, or show the money services and link the services hub.`,\n )\n }\n}\n\nexport function ServicesGrid({\n title,\n eyebrow,\n lead,\n services,\n more,\n imageSizes,\n headingId = 'services-heading',\n tone,\n variant = 'rows',\n layout,\n classes,\n}: ServicesGridProps) {\n if (services.length === 0) return null\n assertServices(services, variant)\n const columns = layout?.columns ?? 3\n const ratio = layout?.ratio ?? '4:3'\n const hooks =\n variant === 'compact'\n ? undefined\n : variant === 'cards'\n ? { 'data-columns': String(columns), 'data-ratio': ratio }\n : { 'data-media-side': layout?.mediaSide ?? 'alternate', 'data-ratio': ratio }\n const sizes = variant === 'compact' ? undefined : (imageSizes ?? defaultImageSizes(variant, columns))\n return (\n <Section id=\"services\" labelledBy={headingId} tone={tone} variant={variant} classes={classes} hooks={hooks}>\n <SectionHeading id={headingId} title={title} eyebrow={eyebrow} lead={lead} classes={classes} />\n <ul role=\"list\" {...slot(classes, 'list')}>\n {services.map((service) => (\n <ServiceCard key={service.id} service={service} variant={variant} imageSizes={sizes} ratio={ratio} />\n ))}\n </ul>\n {more ? (\n <p {...slot(classes, 'more')}>\n {more.href.startsWith('#') ? (\n <a href={more.href}>{more.label}</a>\n ) : (\n <Link href={more.href} prefetch={false}>\n {more.label}\n </Link>\n )}\n </p>\n ) : null}\n </Section>\n )\n}\n"]}
@@ -1,10 +1,13 @@
1
1
  import type { ReactNode } from 'react';
2
+ import type { RepeatedSlotOf } from '../repeated-slots.js';
2
3
  import { type SkinEntry } from '../skin.js';
3
4
  import type { UiLink, UiPhone, UiSocial } from '../types.js';
4
5
  export declare const SITE_FOOTER_VARIANTS: readonly ["columns", "photo"];
5
6
  export type SiteFooterVariant = (typeof SITE_FOOTER_VARIANTS)[number];
6
7
  export type SiteFooterSlot = 'root' | 'panel' | 'media' | 'inner' | 'brand' | 'logo' | 'description' | 'licences' | 'licenceLink' | 'social' | 'socialLink' | 'contact' | 'phone' | 'phoneLabel' | 'phoneNumber' | 'email' | 'address' | 'columns' | 'column' | 'heading' | 'toggle' | 'list' | 'link' | 'bottom' | 'bottomInner' | 'legal' | 'policies' | 'policyLink';
7
- export type SiteFooterSkin = SkinEntry<SiteFooterSlot, SiteFooterVariant>;
8
+ /** Rendered once per licence, profile, column, link or policy: styled from the skin stylesheet, never by class (3.14.0). */
9
+ export type SiteFooterRepeatedSlot = RepeatedSlotOf<'siteFooter'>;
10
+ export type SiteFooterSkin = SkinEntry<SiteFooterSlot, SiteFooterVariant, Record<never, never>, SiteFooterRepeatedSlot>;
8
11
  /** One licence line: the text as the register shows it, and the register link when there is one. */
9
12
  export interface SiteFooterLicence {
10
13
  readonly text: string;
@@ -1 +1 @@
1
- {"version":3,"file":"site-footer.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/site-footer.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"site-footer.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/site-footer.tsx"],"names":[],"mappings":"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"}
@@ -18,27 +18,27 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
18
18
  import Link from 'next/link';
19
19
  import { Icon, SocialIcon } from '../icons.js';
20
20
  import { PhoneLink } from '../primitives/links.js';
21
- import { slot } from '../skin.js';
21
+ import { repeatedSlot, slot } from '../skin.js';
22
22
  import { SiteFooterColumns } from './site-footer-columns.js';
23
23
  export const SITE_FOOTER_VARIANTS = ['columns', 'photo'];
24
24
  const COLUMN_KEYS = ['services', 'areas', 'company'];
25
25
  function isExternal(href) {
26
26
  return /^https?:\/\//i.test(href);
27
27
  }
28
- function FooterLink({ link, hooks }) {
29
- return isExternal(link.href) ? (_jsx("a", { href: link.href, ...hooks, target: "_blank", rel: "noopener noreferrer", children: link.label })) : (_jsx(Link, { href: link.href, prefetch: false, ...hooks, children: link.label }));
28
+ function FooterLink({ link, slotName }) {
29
+ return isExternal(link.href) ? (_jsx("a", { href: link.href, ...repeatedSlot(slotName), target: "_blank", rel: "noopener noreferrer", children: link.label })) : (_jsx(Link, { href: link.href, prefetch: false, ...repeatedSlot(slotName), children: link.label }));
30
30
  }
31
31
  function currentYear() {
32
32
  return Number(new Intl.DateTimeFormat('en-AU', { year: 'numeric', timeZone: 'Australia/Sydney' }).format(new Date()));
33
33
  }
34
34
  export function SiteFooter({ brandName, homeHref = '/', logo, description, licences = [], socials = [], phone, phoneLabel, email, address, columns, legal, media, decoration, idPrefix = 'bl-footer', variant = 'columns', classes, }) {
35
35
  const policies = [legal.privacy, legal.terms].filter((link) => !!link);
36
- return (_jsxs("footer", { "data-bl": "footer", "data-variant": variant, className: classes?.root, children: [decoration ? (_jsx("div", { "data-slot": "decoration", "aria-hidden": "true", children: decoration })) : null, _jsxs("div", { ...slot(classes, 'panel'), children: [variant === 'photo' && media ? (_jsx("div", { ...slot(classes, 'media'), "aria-hidden": "true", children: media })) : null, _jsxs("div", { ...slot(classes, 'inner'), children: [_jsxs("div", { ...slot(classes, 'brand'), children: [_jsx(Link, { href: homeHref, prefetch: false, ...slot(classes, 'logo'), "aria-label": brandName, children: logo }), _jsx("p", { ...slot(classes, 'description'), children: description }), licences.length > 0 ? (_jsx("ul", { ...slot(classes, 'licences'), children: licences.map((licence) => (_jsx("li", { children: licence.href ? (_jsx("a", { ...slot(classes, 'licenceLink'), href: licence.href, target: "_blank", rel: "noopener noreferrer", children: licence.text })) : (licence.text) }, licence.text))) })) : null, socials.length > 0 ? (_jsx("ul", { ...slot(classes, 'social'), children: socials.map((social) => (_jsx("li", { children: _jsx("a", { ...slot(classes, 'socialLink'), href: social.href, "aria-label": social.label, target: "_blank", rel: "noopener noreferrer", children: _jsx(SocialIcon, { kind: social.kind }) }) }, social.href))) })) : null, _jsxs("div", { ...slot(classes, 'contact'), children: [_jsx(PhoneLink, { phone: phone, placement: "footer", label: phoneLabel, classes: { root: classes?.phone, label: classes?.phoneLabel, number: classes?.phoneNumber } }), email ? (_jsx("a", { ...slot(classes, 'email'), href: `mailto:${email}`, children: email })) : null, address ? _jsx("p", { ...slot(classes, 'address'), children: address }) : null] })] }), _jsx(SiteFooterColumns, { className: classes?.columns, children: COLUMN_KEYS.map((key) => {
36
+ return (_jsxs("footer", { "data-bl": "footer", "data-variant": variant, className: classes?.root, children: [decoration ? (_jsx("div", { "data-slot": "decoration", "aria-hidden": "true", children: decoration })) : null, _jsxs("div", { ...slot(classes, 'panel'), children: [variant === 'photo' && media ? (_jsx("div", { ...slot(classes, 'media'), "aria-hidden": "true", children: media })) : null, _jsxs("div", { ...slot(classes, 'inner'), children: [_jsxs("div", { ...slot(classes, 'brand'), children: [_jsx(Link, { href: homeHref, prefetch: false, ...slot(classes, 'logo'), "aria-label": brandName, children: logo }), _jsx("p", { ...slot(classes, 'description'), children: description }), licences.length > 0 ? (_jsx("ul", { ...slot(classes, 'licences'), children: licences.map((licence) => (_jsx("li", { children: licence.href ? (_jsx("a", { ...repeatedSlot('licenceLink'), href: licence.href, target: "_blank", rel: "noopener noreferrer", children: licence.text })) : (licence.text) }, licence.text))) })) : null, socials.length > 0 ? (_jsx("ul", { ...slot(classes, 'social'), children: socials.map((social) => (_jsx("li", { children: _jsx("a", { ...repeatedSlot('socialLink'), href: social.href, "aria-label": social.label, target: "_blank", rel: "noopener noreferrer", children: _jsx(SocialIcon, { kind: social.kind }) }) }, social.href))) })) : null, _jsxs("div", { ...slot(classes, 'contact'), children: [_jsx(PhoneLink, { phone: phone, placement: "footer", label: phoneLabel, classes: { root: classes?.phone, label: classes?.phoneLabel, number: classes?.phoneNumber } }), email ? (_jsx("a", { ...slot(classes, 'email'), href: `mailto:${email}`, children: email })) : null, address ? _jsx("p", { ...slot(classes, 'address'), children: address }) : null] })] }), _jsx(SiteFooterColumns, { className: classes?.columns, children: COLUMN_KEYS.map((key) => {
37
37
  const column = columns[key];
38
38
  if (!column || column.links.length === 0)
39
39
  return null;
40
40
  const listId = `${idPrefix}-${key}`;
41
- return (_jsxs("div", { ...slot(classes, 'column'), "data-column": key, children: [_jsxs("h2", { ...slot(classes, 'heading'), children: [_jsx("span", { "data-slot": "headingText", children: column.heading }), _jsxs("button", { type: "button", ...slot(classes, 'toggle'), "aria-expanded": "false", "aria-controls": listId, hidden: true, children: [_jsx("span", { children: column.heading }), _jsx(Icon, { name: "chevron" })] })] }), _jsx("ul", { id: listId, ...slot(classes, 'list'), children: column.links.map((link) => (_jsx("li", { children: _jsx(FooterLink, { link: link, hooks: slot(classes, 'link') }) }, link.href))) })] }, key));
42
- }) })] })] }), _jsx("div", { ...slot(classes, 'bottom'), children: _jsxs("div", { ...slot(classes, 'bottomInner'), children: [_jsxs("p", { ...slot(classes, 'legal'), children: ["\u00A9 ", legal.year ?? currentYear(), " ", legal.name, legal.abn ? (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", children: " \u00B7 " }), _jsxs("span", { children: ["ABN ", legal.abn] })] })) : null] }), policies.length > 0 ? (_jsx("ul", { ...slot(classes, 'policies'), children: policies.map((link) => (_jsx("li", { children: _jsx(FooterLink, { link: link, hooks: slot(classes, 'policyLink') }) }, link.href))) })) : null] }) })] }));
41
+ return (_jsxs("div", { ...repeatedSlot('column'), "data-column": key, children: [_jsxs("h2", { ...repeatedSlot('heading'), children: [_jsx("span", { "data-slot": "headingText", children: column.heading }), _jsxs("button", { type: "button", ...repeatedSlot('toggle'), "aria-expanded": "false", "aria-controls": listId, hidden: true, children: [_jsx("span", { children: column.heading }), _jsx(Icon, { name: "chevron" })] })] }), _jsx("ul", { id: listId, ...repeatedSlot('list'), children: column.links.map((link) => (_jsx("li", { children: _jsx(FooterLink, { link: link, slotName: "link" }) }, link.href))) })] }, key));
42
+ }) })] })] }), _jsx("div", { ...slot(classes, 'bottom'), children: _jsxs("div", { ...slot(classes, 'bottomInner'), children: [_jsxs("p", { ...slot(classes, 'legal'), children: ["\u00A9 ", legal.year ?? currentYear(), " ", legal.name, legal.abn ? (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", children: " \u00B7 " }), _jsxs("span", { children: ["ABN ", legal.abn] })] })) : null] }), policies.length > 0 ? (_jsx("ul", { ...slot(classes, 'policies'), children: policies.map((link) => (_jsx("li", { children: _jsx(FooterLink, { link: link, slotName: "policyLink" }) }, link.href))) })) : null] }) })] }));
43
43
  }
44
44
  //# sourceMappingURL=site-footer.js.map