@brandlab-digital/site-next 3.13.0 → 3.15.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 (274) hide show
  1. package/README.md +79 -0
  2. package/dist/analytics.d.ts +1 -1
  3. package/dist/analytics.d.ts.map +1 -1
  4. package/dist/analytics.js +2 -0
  5. package/dist/analytics.js.map +1 -1
  6. package/dist/contact/delivery.d.ts +81 -0
  7. package/dist/contact/delivery.d.ts.map +1 -0
  8. package/dist/contact/delivery.js +138 -0
  9. package/dist/contact/delivery.js.map +1 -0
  10. package/dist/contact/resend-webhook.d.ts +72 -0
  11. package/dist/contact/resend-webhook.d.ts.map +1 -0
  12. package/dist/contact/resend-webhook.js +300 -0
  13. package/dist/contact/resend-webhook.js.map +1 -0
  14. package/dist/contact/route-handler.d.ts +16 -0
  15. package/dist/contact/route-handler.d.ts.map +1 -1
  16. package/dist/contact/route-handler.js +8 -0
  17. package/dist/contact/route-handler.js.map +1 -1
  18. package/dist/contact/server-entry.d.ts +2 -0
  19. package/dist/contact/server-entry.d.ts.map +1 -1
  20. package/dist/contact/server-entry.js +2 -0
  21. package/dist/contact/server-entry.js.map +1 -1
  22. package/dist/contact/server.d.ts +26 -0
  23. package/dist/contact/server.d.ts.map +1 -1
  24. package/dist/contact/server.js +48 -3
  25. package/dist/contact/server.js.map +1 -1
  26. package/dist/navigation/constants.d.ts +11 -2
  27. package/dist/navigation/constants.d.ts.map +1 -1
  28. package/dist/navigation/constants.js +11 -2
  29. package/dist/navigation/constants.js.map +1 -1
  30. package/dist/navigation/index.d.ts +1 -1
  31. package/dist/navigation/index.d.ts.map +1 -1
  32. package/dist/navigation/index.js +1 -1
  33. package/dist/navigation/index.js.map +1 -1
  34. package/dist/navigation/use-back-to-top.d.ts.map +1 -1
  35. package/dist/navigation/use-back-to-top.js +12 -4
  36. package/dist/navigation/use-back-to-top.js.map +1 -1
  37. package/dist/navigation/use-header-metrics.d.ts +1 -1
  38. package/dist/navigation/use-header-metrics.js +1 -1
  39. package/dist/navigation/use-header-metrics.js.map +1 -1
  40. package/dist/navigation/use-sticky-call-bar.d.ts +2 -1
  41. package/dist/navigation/use-sticky-call-bar.d.ts.map +1 -1
  42. package/dist/navigation/use-sticky-call-bar.js +11 -2
  43. package/dist/navigation/use-sticky-call-bar.js.map +1 -1
  44. package/dist/next-config.d.ts +17 -1
  45. package/dist/next-config.d.ts.map +1 -1
  46. package/dist/next-config.js +36 -2
  47. package/dist/next-config.js.map +1 -1
  48. package/dist/ui-kit/brandlab-skin.d.ts +50 -1
  49. package/dist/ui-kit/brandlab-skin.d.ts.map +1 -1
  50. package/dist/ui-kit/brandlab-skin.js.map +1 -1
  51. package/dist/ui-kit/form/photo-upload.d.ts +4 -1
  52. package/dist/ui-kit/form/photo-upload.d.ts.map +1 -1
  53. package/dist/ui-kit/form/photo-upload.js +4 -4
  54. package/dist/ui-kit/form/photo-upload.js.map +1 -1
  55. package/dist/ui-kit/form/quote-form.d.ts +4 -1
  56. package/dist/ui-kit/form/quote-form.d.ts.map +1 -1
  57. package/dist/ui-kit/form/quote-form.js.map +1 -1
  58. package/dist/ui-kit/form/thank-you.d.ts +4 -1
  59. package/dist/ui-kit/form/thank-you.d.ts.map +1 -1
  60. package/dist/ui-kit/form/thank-you.js +2 -2
  61. package/dist/ui-kit/form/thank-you.js.map +1 -1
  62. package/dist/ui-kit/icon-glyphs.d.ts +41 -0
  63. package/dist/ui-kit/icon-glyphs.d.ts.map +1 -0
  64. package/dist/ui-kit/icon-glyphs.js +138 -0
  65. package/dist/ui-kit/icon-glyphs.js.map +1 -0
  66. package/dist/ui-kit/icon-sprite.d.ts +19 -0
  67. package/dist/ui-kit/icon-sprite.d.ts.map +1 -0
  68. package/dist/ui-kit/icon-sprite.js +44 -0
  69. package/dist/ui-kit/icon-sprite.js.map +1 -0
  70. package/dist/ui-kit/icons.d.ts +38 -3
  71. package/dist/ui-kit/icons.d.ts.map +1 -1
  72. package/dist/ui-kit/icons.js +47 -64
  73. package/dist/ui-kit/icons.js.map +1 -1
  74. package/dist/ui-kit/prefetch.d.ts +19 -10
  75. package/dist/ui-kit/prefetch.d.ts.map +1 -1
  76. package/dist/ui-kit/prefetch.js +23 -10
  77. package/dist/ui-kit/prefetch.js.map +1 -1
  78. package/dist/ui-kit/primitives/badge.d.ts +25 -0
  79. package/dist/ui-kit/primitives/badge.d.ts.map +1 -0
  80. package/dist/ui-kit/primitives/badge.js +21 -0
  81. package/dist/ui-kit/primitives/badge.js.map +1 -0
  82. package/dist/ui-kit/primitives/breadcrumbs.d.ts +27 -0
  83. package/dist/ui-kit/primitives/breadcrumbs.d.ts.map +1 -0
  84. package/dist/ui-kit/primitives/breadcrumbs.js +40 -0
  85. package/dist/ui-kit/primitives/breadcrumbs.js.map +1 -0
  86. package/dist/ui-kit/primitives/button.d.ts +32 -0
  87. package/dist/ui-kit/primitives/button.d.ts.map +1 -0
  88. package/dist/ui-kit/primitives/button.js +8 -0
  89. package/dist/ui-kit/primitives/button.js.map +1 -0
  90. package/dist/ui-kit/primitives/card.d.ts +40 -0
  91. package/dist/ui-kit/primitives/card.d.ts.map +1 -0
  92. package/dist/ui-kit/primitives/card.js +8 -0
  93. package/dist/ui-kit/primitives/card.js.map +1 -0
  94. package/dist/ui-kit/primitives/index.d.ts +11 -2
  95. package/dist/ui-kit/primitives/index.d.ts.map +1 -1
  96. package/dist/ui-kit/primitives/index.js +11 -2
  97. package/dist/ui-kit/primitives/index.js.map +1 -1
  98. package/dist/ui-kit/primitives/layout.d.ts +12 -0
  99. package/dist/ui-kit/primitives/layout.d.ts.map +1 -1
  100. package/dist/ui-kit/primitives/layout.js +20 -2
  101. package/dist/ui-kit/primitives/layout.js.map +1 -1
  102. package/dist/ui-kit/primitives/links.d.ts +15 -0
  103. package/dist/ui-kit/primitives/links.d.ts.map +1 -1
  104. package/dist/ui-kit/primitives/links.js +6 -2
  105. package/dist/ui-kit/primitives/links.js.map +1 -1
  106. package/dist/ui-kit/primitives/lists.d.ts +51 -0
  107. package/dist/ui-kit/primitives/lists.d.ts.map +1 -0
  108. package/dist/ui-kit/primitives/lists.js +34 -0
  109. package/dist/ui-kit/primitives/lists.js.map +1 -0
  110. package/dist/ui-kit/primitives/media-frame.d.ts +28 -0
  111. package/dist/ui-kit/primitives/media-frame.d.ts.map +1 -0
  112. package/dist/ui-kit/primitives/media-frame.js +58 -0
  113. package/dist/ui-kit/primitives/media-frame.js.map +1 -0
  114. package/dist/ui-kit/primitives/media.d.ts +38 -0
  115. package/dist/ui-kit/primitives/media.d.ts.map +1 -0
  116. package/dist/ui-kit/primitives/media.js +9 -0
  117. package/dist/ui-kit/primitives/media.js.map +1 -0
  118. package/dist/ui-kit/primitives/quote-button.d.ts +25 -2
  119. package/dist/ui-kit/primitives/quote-button.d.ts.map +1 -1
  120. package/dist/ui-kit/primitives/quote-button.js +25 -3
  121. package/dist/ui-kit/primitives/quote-button.js.map +1 -1
  122. package/dist/ui-kit/primitives/stat.d.ts +25 -0
  123. package/dist/ui-kit/primitives/stat.d.ts.map +1 -0
  124. package/dist/ui-kit/primitives/stat.js +7 -0
  125. package/dist/ui-kit/primitives/stat.js.map +1 -0
  126. package/dist/ui-kit/repeated-slots.d.ts +153 -0
  127. package/dist/ui-kit/repeated-slots.d.ts.map +1 -0
  128. package/dist/ui-kit/repeated-slots.js +97 -0
  129. package/dist/ui-kit/repeated-slots.js.map +1 -0
  130. package/dist/ui-kit/sections/accordion.d.ts +4 -1
  131. package/dist/ui-kit/sections/accordion.d.ts.map +1 -1
  132. package/dist/ui-kit/sections/accordion.js +2 -2
  133. package/dist/ui-kit/sections/accordion.js.map +1 -1
  134. package/dist/ui-kit/sections/area-map.d.ts +64 -0
  135. package/dist/ui-kit/sections/area-map.d.ts.map +1 -0
  136. package/dist/ui-kit/sections/area-map.js +142 -0
  137. package/dist/ui-kit/sections/area-map.js.map +1 -0
  138. package/dist/ui-kit/sections/before-after.d.ts +59 -0
  139. package/dist/ui-kit/sections/before-after.d.ts.map +1 -0
  140. package/dist/ui-kit/sections/before-after.js +42 -0
  141. package/dist/ui-kit/sections/before-after.js.map +1 -0
  142. package/dist/ui-kit/sections/contact-block.d.ts +4 -1
  143. package/dist/ui-kit/sections/contact-block.d.ts.map +1 -1
  144. package/dist/ui-kit/sections/contact-block.js +3 -3
  145. package/dist/ui-kit/sections/contact-block.js.map +1 -1
  146. package/dist/ui-kit/sections/council-rules.d.ts +64 -0
  147. package/dist/ui-kit/sections/council-rules.d.ts.map +1 -0
  148. package/dist/ui-kit/sections/council-rules.js +46 -0
  149. package/dist/ui-kit/sections/council-rules.js.map +1 -0
  150. package/dist/ui-kit/sections/coverage.d.ts +53 -0
  151. package/dist/ui-kit/sections/coverage.d.ts.map +1 -0
  152. package/dist/ui-kit/sections/coverage.js +57 -0
  153. package/dist/ui-kit/sections/coverage.js.map +1 -0
  154. package/dist/ui-kit/sections/emergency-band.d.ts +60 -0
  155. package/dist/ui-kit/sections/emergency-band.d.ts.map +1 -0
  156. package/dist/ui-kit/sections/emergency-band.js +43 -0
  157. package/dist/ui-kit/sections/emergency-band.js.map +1 -0
  158. package/dist/ui-kit/sections/faqs.d.ts +4 -1
  159. package/dist/ui-kit/sections/faqs.d.ts.map +1 -1
  160. package/dist/ui-kit/sections/faqs.js +4 -5
  161. package/dist/ui-kit/sections/faqs.js.map +1 -1
  162. package/dist/ui-kit/sections/gallery.d.ts +60 -0
  163. package/dist/ui-kit/sections/gallery.d.ts.map +1 -0
  164. package/dist/ui-kit/sections/gallery.js +39 -0
  165. package/dist/ui-kit/sections/gallery.js.map +1 -0
  166. package/dist/ui-kit/sections/hero.d.ts +118 -0
  167. package/dist/ui-kit/sections/hero.d.ts.map +1 -0
  168. package/dist/ui-kit/sections/hero.js +58 -0
  169. package/dist/ui-kit/sections/hero.js.map +1 -0
  170. package/dist/ui-kit/sections/index.d.ts +16 -1
  171. package/dist/ui-kit/sections/index.d.ts.map +1 -1
  172. package/dist/ui-kit/sections/index.js +16 -1
  173. package/dist/ui-kit/sections/index.js.map +1 -1
  174. package/dist/ui-kit/sections/licences.d.ts +72 -0
  175. package/dist/ui-kit/sections/licences.d.ts.map +1 -0
  176. package/dist/ui-kit/sections/licences.js +46 -0
  177. package/dist/ui-kit/sections/licences.js.map +1 -0
  178. package/dist/ui-kit/sections/local-facts.d.ts +78 -0
  179. package/dist/ui-kit/sections/local-facts.d.ts.map +1 -0
  180. package/dist/ui-kit/sections/local-facts.js +32 -0
  181. package/dist/ui-kit/sections/local-facts.js.map +1 -0
  182. package/dist/ui-kit/sections/nearby-areas.d.ts +50 -0
  183. package/dist/ui-kit/sections/nearby-areas.d.ts.map +1 -0
  184. package/dist/ui-kit/sections/nearby-areas.js +49 -0
  185. package/dist/ui-kit/sections/nearby-areas.js.map +1 -0
  186. package/dist/ui-kit/sections/nearby-jobs.d.ts +66 -0
  187. package/dist/ui-kit/sections/nearby-jobs.d.ts.map +1 -0
  188. package/dist/ui-kit/sections/nearby-jobs.js +49 -0
  189. package/dist/ui-kit/sections/nearby-jobs.js.map +1 -0
  190. package/dist/ui-kit/sections/price-guide.d.ts +4 -1
  191. package/dist/ui-kit/sections/price-guide.d.ts.map +1 -1
  192. package/dist/ui-kit/sections/price-guide.js +3 -3
  193. package/dist/ui-kit/sections/price-guide.js.map +1 -1
  194. package/dist/ui-kit/sections/price-table.d.ts +7 -9
  195. package/dist/ui-kit/sections/price-table.d.ts.map +1 -1
  196. package/dist/ui-kit/sections/price-table.js +8 -8
  197. package/dist/ui-kit/sections/price-table.js.map +1 -1
  198. package/dist/ui-kit/sections/projects.d.ts +79 -0
  199. package/dist/ui-kit/sections/projects.d.ts.map +1 -0
  200. package/dist/ui-kit/sections/projects.js +69 -0
  201. package/dist/ui-kit/sections/projects.js.map +1 -0
  202. package/dist/ui-kit/sections/proof-strip.d.ts +4 -1
  203. package/dist/ui-kit/sections/proof-strip.d.ts.map +1 -1
  204. package/dist/ui-kit/sections/proof-strip.js +2 -2
  205. package/dist/ui-kit/sections/proof-strip.js.map +1 -1
  206. package/dist/ui-kit/sections/quote-checklist.d.ts +43 -0
  207. package/dist/ui-kit/sections/quote-checklist.d.ts.map +1 -0
  208. package/dist/ui-kit/sections/quote-checklist.js +19 -0
  209. package/dist/ui-kit/sections/quote-checklist.js.map +1 -0
  210. package/dist/ui-kit/sections/related-services.d.ts +41 -0
  211. package/dist/ui-kit/sections/related-services.d.ts.map +1 -0
  212. package/dist/ui-kit/sections/related-services.js +52 -0
  213. package/dist/ui-kit/sections/related-services.js.map +1 -0
  214. package/dist/ui-kit/sections/reviews-band.d.ts +16 -2
  215. package/dist/ui-kit/sections/reviews-band.d.ts.map +1 -1
  216. package/dist/ui-kit/sections/reviews-band.js +13 -2
  217. package/dist/ui-kit/sections/reviews-band.js.map +1 -1
  218. package/dist/ui-kit/sections/services-grid.d.ts +82 -0
  219. package/dist/ui-kit/sections/services-grid.d.ts.map +1 -0
  220. package/dist/ui-kit/sections/services-grid.js +90 -0
  221. package/dist/ui-kit/sections/services-grid.js.map +1 -0
  222. package/dist/ui-kit/sections/site-footer.d.ts +4 -1
  223. package/dist/ui-kit/sections/site-footer.d.ts.map +1 -1
  224. package/dist/ui-kit/sections/site-footer.js +6 -6
  225. package/dist/ui-kit/sections/site-footer.js.map +1 -1
  226. package/dist/ui-kit/sections/work-photos.d.ts +15 -0
  227. package/dist/ui-kit/sections/work-photos.d.ts.map +1 -0
  228. package/dist/ui-kit/sections/work-photos.js +24 -0
  229. package/dist/ui-kit/sections/work-photos.js.map +1 -0
  230. package/dist/ui-kit/shell/back-to-top.js +1 -1
  231. package/dist/ui-kit/shell/back-to-top.js.map +1 -1
  232. package/dist/ui-kit/shell/current-link.d.ts +21 -5
  233. package/dist/ui-kit/shell/current-link.d.ts.map +1 -1
  234. package/dist/ui-kit/shell/current-link.js +16 -5
  235. package/dist/ui-kit/shell/current-link.js.map +1 -1
  236. package/dist/ui-kit/shell/drawer-client.js +1 -1
  237. package/dist/ui-kit/shell/drawer-client.js.map +1 -1
  238. package/dist/ui-kit/shell/intent-prefetch.d.ts.map +1 -1
  239. package/dist/ui-kit/shell/intent-prefetch.js +3 -2
  240. package/dist/ui-kit/shell/intent-prefetch.js.map +1 -1
  241. package/dist/ui-kit/shell/mobile-call-bar.d.ts +6 -1
  242. package/dist/ui-kit/shell/mobile-call-bar.d.ts.map +1 -1
  243. package/dist/ui-kit/shell/mobile-call-bar.js +6 -3
  244. package/dist/ui-kit/shell/mobile-call-bar.js.map +1 -1
  245. package/dist/ui-kit/shell/mobile-drawer.d.ts +7 -3
  246. package/dist/ui-kit/shell/mobile-drawer.d.ts.map +1 -1
  247. package/dist/ui-kit/shell/mobile-drawer.js +21 -5
  248. package/dist/ui-kit/shell/mobile-drawer.js.map +1 -1
  249. package/dist/ui-kit/shell/shell-quote.d.ts +10 -0
  250. package/dist/ui-kit/shell/shell-quote.d.ts.map +1 -0
  251. package/dist/ui-kit/shell/shell-quote.js +13 -0
  252. package/dist/ui-kit/shell/shell-quote.js.map +1 -0
  253. package/dist/ui-kit/shell/site-header.d.ts +5 -2
  254. package/dist/ui-kit/shell/site-header.d.ts.map +1 -1
  255. package/dist/ui-kit/shell/site-header.js +9 -9
  256. package/dist/ui-kit/shell/site-header.js.map +1 -1
  257. package/dist/ui-kit/shell/social-links.d.ts +1 -2
  258. package/dist/ui-kit/shell/social-links.d.ts.map +1 -1
  259. package/dist/ui-kit/shell/social-links.js +6 -3
  260. package/dist/ui-kit/shell/social-links.js.map +1 -1
  261. package/dist/ui-kit/shell/utility-bar.d.ts +4 -1
  262. package/dist/ui-kit/shell/utility-bar.d.ts.map +1 -1
  263. package/dist/ui-kit/shell/utility-bar.js +2 -2
  264. package/dist/ui-kit/shell/utility-bar.js.map +1 -1
  265. package/dist/ui-kit/skin-index.d.ts +2 -1
  266. package/dist/ui-kit/skin-index.d.ts.map +1 -1
  267. package/dist/ui-kit/skin-index.js +2 -1
  268. package/dist/ui-kit/skin-index.js.map +1 -1
  269. package/dist/ui-kit/skin.d.ts +21 -4
  270. package/dist/ui-kit/skin.d.ts.map +1 -1
  271. package/dist/ui-kit/skin.js +14 -1
  272. package/dist/ui-kit/skin.js.map +1 -1
  273. package/dist/ui-kit/structure.css +793 -10
  274. package/package.json +6 -2
@@ -1 +1 @@
1
- {"version":3,"file":"price-table.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/price-table.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `PriceTable`: typical prices from `prices.ts` as a real `<table>`. Server component. The caption is \"Typical prices\"\n * by default and may only become \"Typical {service} prices\" (never a place name or \"market rates\"). Every row shows\n * the full price, the GST flag and, beside them only, any part price such as a call-out fee (NSW Fair Trading). Held\n * bands never render, and nothing renders without a renderable band.\n *\n * On narrow widths (container below 40rem) rows stack visually. Changing a table's CSS `display` can drop its\n * semantics in some screen readers, so every element carries its table role explicitly, and each cell repeats its\n * column label for sighted readers only (`aria-hidden`).\n */\nimport type { ReactNode } from 'react'\nimport {\n PRICE_CAPTION,\n assertGstLabel,\n assertPriceCaption,\n formatPriceBand,\n renderablePrices,\n type BrandlabPrice,\n} from '@brandlab-digital/site-core/prices'\nimport { slot, type SkinEntry, type SlotClasses } from '../skin.js'\n\nexport type PriceTableSlot =\n | 'root'\n | 'table'\n | 'caption'\n | 'thead'\n | 'row'\n | 'job'\n | 'cell'\n | 'label'\n | 'range'\n | 'unit'\n | 'gst'\n | 'parts'\n | 'part'\n | 'includes'\n | 'excludes'\nexport type PriceTableSkin = SkinEntry<PriceTableSlot>\n\n/** The words around the figures, in the site's Australian English. */\nexport interface PriceLabels {\n /** The job column header, for example \"Job\". */\n readonly job: string\n /** The price column header, for example \"Typical price\". */\n readonly price: string\n /** The scope column header, for example \"What's included\". */\n readonly includes: string\n /** Before what the price leaves out, for example \"Not included\". */\n readonly excludes: string\n /** The GST flag beside a GST-inclusive price, for example \"incl. GST\". */\n readonly gstIncluded: string\n /** The GST flag beside a GST-exclusive price, for example \"excl. GST\" (never \"+ GST\"). */\n readonly gstExcluded: string\n}\n\nexport interface PriceTableProps extends PriceTableSkin {\n readonly prices: readonly BrandlabPrice[]\n readonly labels: PriceLabels\n /** Defaults to \"Typical prices\"; otherwise only \"Typical {service} prices\" for a service in `prices`. */\n readonly caption?: string\n}\n\n/** Throws when the caption (checked against the page's service slugs) or the GST labels break the price rules. */\nexport function assertPriceWording(caption: string, labels: PriceLabels, services: readonly string[]) {\n assertPriceCaption(caption, services)\n assertGstLabel(labels.gstIncluded, 'labels.gstIncluded')\n assertGstLabel(labels.gstExcluded, 'labels.gstExcluded')\n}\n\ntype FigureClasses = SlotClasses<'range' | 'unit' | 'gst' | 'parts' | 'part'>\ntype ScopeClasses = SlotClasses<'includes' | 'excludes'>\n\n/** The full price, its unit and GST flag, then any part prices beside it. */\nexport function PriceFigures({ price, labels, classes }: { price: BrandlabPrice; labels: PriceLabels; classes: FigureClasses | undefined }) {\n return (\n <>\n <span {...slot(classes, 'range')}>{formatPriceBand(price)}</span>\n {price.unit ? (\n <>\n {' '}\n <span {...slot(classes, 'unit')}>{price.unit}</span>\n </>\n ) : null}{' '}\n <span {...slot(classes, 'gst')} data-gst={price.gstIncluded ? 'included' : 'excluded'}>\n {price.gstIncluded ? labels.gstIncluded : labels.gstExcluded}\n </span>\n {price.parts.length > 0 ? (\n <ul {...slot(classes, 'parts')}>\n {price.parts.map((part) => (\n <li key={part.label} {...slot(classes, 'part')}>\n {part.label} {formatPriceBand(part)}\n </li>\n ))}\n </ul>\n ) : null}\n </>\n )\n}\n\n/** What the price covers and leaves out. */\nexport function PriceScope({ price, labels, classes }: { price: BrandlabPrice; labels: PriceLabels; classes: ScopeClasses | undefined }) {\n return (\n <>\n {price.includes.length > 0 ? (\n <ul {...slot(classes, 'includes')}>\n {price.includes.map((item) => (\n <li key={item}>{item}</li>\n ))}\n </ul>\n ) : null}\n {price.excludes.length > 0 ? (\n <p {...slot(classes, 'excludes')}>\n {labels.excludes}: {price.excludes.join(', ')}\n </p>\n ) : null}\n </>\n )\n}\n\nfunction CellLabel({ classes, children }: { classes: SlotClasses<'label'> | undefined; children: ReactNode }) {\n return (\n <span {...slot(classes, 'label')} aria-hidden=\"true\">\n {children}\n </span>\n )\n}\n\nexport function PriceTable({ prices, labels, caption = PRICE_CAPTION, classes }: PriceTableProps) {\n const rows = renderablePrices(prices)\n assertPriceWording(caption, labels, rows.map((row) => row.service))\n if (rows.length === 0) return null\n const hasScope = rows.some((row) => row.includes.length > 0 || row.excludes.length > 0)\n return (\n <div data-bl=\"price-table\" className={classes?.root}>\n <table role=\"table\" {...slot(classes, 'table')}>\n <caption {...slot(classes, 'caption')}>{caption}</caption>\n <thead role=\"rowgroup\" {...slot(classes, 'thead')}>\n <tr role=\"row\">\n <th scope=\"col\" role=\"columnheader\">\n {labels.job}\n </th>\n <th scope=\"col\" role=\"columnheader\">\n {labels.price}\n </th>\n {hasScope ? (\n <th scope=\"col\" role=\"columnheader\">\n {labels.includes}\n </th>\n ) : null}\n </tr>\n </thead>\n <tbody role=\"rowgroup\">\n {rows.map((row) => (\n <tr key={`${row.service}:${row.job}`} role=\"row\" {...slot(classes, 'row')}>\n <th scope=\"row\" role=\"rowheader\" {...slot(classes, 'job')}>\n {row.job}\n </th>\n <td role=\"cell\" {...slot(classes, 'cell')} data-column=\"price\">\n <CellLabel classes={classes}>{labels.price}</CellLabel>\n <PriceFigures price={row} labels={labels} classes={classes} />\n </td>\n {hasScope ? (\n <td role=\"cell\" {...slot(classes, 'cell')} data-column=\"includes\">\n {row.includes.length > 0 || row.excludes.length > 0 ? <CellLabel classes={classes}>{labels.includes}</CellLabel> : null}\n <PriceScope price={row} labels={labels} classes={classes} />\n </td>\n ) : null}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )\n}\n"]}
1
+ {"version":3,"file":"price-table.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/price-table.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `PriceTable`: typical prices from `prices.ts` as a real `<table>`. Server component. The caption is \"Typical prices\"\n * by default and may only become \"Typical {service} prices\" (never a place name or \"market rates\"). Every row shows\n * the full price, the GST flag and, beside them only, any part price such as a call-out fee (NSW Fair Trading). Held\n * bands never render, and nothing renders without a renderable band.\n *\n * On narrow widths (container below 40rem) rows stack visually. Changing a table's CSS `display` can drop its\n * semantics in some screen readers, so every element carries its table role explicitly, and each cell repeats its\n * column label for sighted readers only (`aria-hidden`).\n */\nimport type { ReactNode } from 'react'\nimport {\n PRICE_CAPTION,\n assertGstLabel,\n assertPriceCaption,\n formatPriceBand,\n renderablePrices,\n type BrandlabPrice,\n} from '@brandlab-digital/site-core/prices'\nimport type { RepeatedSlotOf } from '../repeated-slots.js'\nimport { repeatedSlot, slot, type SkinEntry } from '../skin.js'\n\nexport type PriceTableSlot =\n | 'root'\n | 'table'\n | 'caption'\n | 'thead'\n | 'row'\n | 'job'\n | 'cell'\n | 'label'\n | 'range'\n | 'unit'\n | 'gst'\n | 'parts'\n | 'part'\n | 'includes'\n | 'excludes'\n/** Rendered once per price row: styled from the skin stylesheet, never by class (3.14.0). */\nexport type PriceTableRepeatedSlot = RepeatedSlotOf<'priceTable'>\nexport type PriceTableSkin = SkinEntry<PriceTableSlot, never, Record<never, never>, PriceTableRepeatedSlot>\n\n/** The words around the figures, in the site's Australian English. */\nexport interface PriceLabels {\n /** The job column header, for example \"Job\". */\n readonly job: string\n /** The price column header, for example \"Typical price\". */\n readonly price: string\n /** The scope column header, for example \"What's included\". */\n readonly includes: string\n /** Before what the price leaves out, for example \"Not included\". */\n readonly excludes: string\n /** The GST flag beside a GST-inclusive price, for example \"incl. GST\". */\n readonly gstIncluded: string\n /** The GST flag beside a GST-exclusive price, for example \"excl. GST\" (never \"+ GST\"). */\n readonly gstExcluded: string\n}\n\nexport interface PriceTableProps extends PriceTableSkin {\n readonly prices: readonly BrandlabPrice[]\n readonly labels: PriceLabels\n /** Defaults to \"Typical prices\"; otherwise only \"Typical {service} prices\" for a service in `prices`. */\n readonly caption?: string\n}\n\n/** Throws when the caption (checked against the page's service slugs) or the GST labels break the price rules. */\nexport function assertPriceWording(caption: string, labels: PriceLabels, services: readonly string[]) {\n assertPriceCaption(caption, services)\n assertGstLabel(labels.gstIncluded, 'labels.gstIncluded')\n assertGstLabel(labels.gstExcluded, 'labels.gstExcluded')\n}\n\n/** The full price, its unit and GST flag, then any part prices beside it. */\nexport function PriceFigures({ price, labels }: { price: BrandlabPrice; labels: PriceLabels }) {\n return (\n <>\n <span {...repeatedSlot('range')}>{formatPriceBand(price)}</span>\n {price.unit ? (\n <>\n {' '}\n <span {...repeatedSlot('unit')}>{price.unit}</span>\n </>\n ) : null}{' '}\n <span {...repeatedSlot('gst')} data-gst={price.gstIncluded ? 'included' : 'excluded'}>\n {price.gstIncluded ? labels.gstIncluded : labels.gstExcluded}\n </span>\n {price.parts.length > 0 ? (\n <ul {...repeatedSlot('parts')}>\n {price.parts.map((part) => (\n <li key={part.label} {...repeatedSlot('part')}>\n {part.label} {formatPriceBand(part)}\n </li>\n ))}\n </ul>\n ) : null}\n </>\n )\n}\n\n/** What the price covers and leaves out. */\nexport function PriceScope({ price, labels }: { price: BrandlabPrice; labels: PriceLabels }) {\n return (\n <>\n {price.includes.length > 0 ? (\n <ul {...repeatedSlot('includes')}>\n {price.includes.map((item) => (\n <li key={item}>{item}</li>\n ))}\n </ul>\n ) : null}\n {price.excludes.length > 0 ? (\n <p {...repeatedSlot('excludes')}>\n {labels.excludes}: {price.excludes.join(', ')}\n </p>\n ) : null}\n </>\n )\n}\n\nfunction CellLabel({ children }: { children: ReactNode }) {\n return (\n <span {...repeatedSlot('label')} aria-hidden=\"true\">\n {children}\n </span>\n )\n}\n\nexport function PriceTable({ prices, labels, caption = PRICE_CAPTION, classes }: PriceTableProps) {\n const rows = renderablePrices(prices)\n assertPriceWording(caption, labels, rows.map((row) => row.service))\n if (rows.length === 0) return null\n const hasScope = rows.some((row) => row.includes.length > 0 || row.excludes.length > 0)\n return (\n <div data-bl=\"price-table\" className={classes?.root}>\n <table role=\"table\" {...slot(classes, 'table')}>\n <caption {...slot(classes, 'caption')}>{caption}</caption>\n <thead role=\"rowgroup\" {...slot(classes, 'thead')}>\n <tr role=\"row\">\n <th scope=\"col\" role=\"columnheader\">\n {labels.job}\n </th>\n <th scope=\"col\" role=\"columnheader\">\n {labels.price}\n </th>\n {hasScope ? (\n <th scope=\"col\" role=\"columnheader\">\n {labels.includes}\n </th>\n ) : null}\n </tr>\n </thead>\n <tbody role=\"rowgroup\">\n {rows.map((row) => (\n <tr key={`${row.service}:${row.job}`} role=\"row\" {...repeatedSlot('row')}>\n <th scope=\"row\" role=\"rowheader\" {...repeatedSlot('job')}>\n {row.job}\n </th>\n <td role=\"cell\" {...repeatedSlot('cell')} data-column=\"price\">\n <CellLabel>{labels.price}</CellLabel>\n <PriceFigures price={row} labels={labels} />\n </td>\n {hasScope ? (\n <td role=\"cell\" {...repeatedSlot('cell')} data-column=\"includes\">\n {row.includes.length > 0 || row.excludes.length > 0 ? <CellLabel>{labels.includes}</CellLabel> : null}\n <PriceScope price={row} labels={labels} />\n </td>\n ) : null}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )\n}\n"]}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * `Projects` (`projects`, 3.15.0): completed jobs as cards, each linking its project page (`/projects/{slug}`). Server
3
+ * component. AA's `ProjectGrid`, re-implemented on the `Card` primitive.
4
+ *
5
+ * Locked:
6
+ * - genuine photos only (v2 §11 imagery rule 4): a stock or AI image on a project card fails the build, and a project
7
+ * without a photo shows as a card without one;
8
+ * - at most 8 cards (K11; the in-content intent-prefetch limit); each card is one link, its title, which prefetches on
9
+ * intent. The hub link after the list never prefetches;
10
+ * - each card links its own project page, a child of the site's projects hub (`/projects/{slug}`, `/our-work/{slug}`);
11
+ * - no default visible copy: the heading, titles, summaries and the suburb-and-month line all come from the site.
12
+ *
13
+ * `projectCardFromJob` builds a card from a site-core job log row (`./jobs`): the job's first genuine photo and its
14
+ * outcome line; the title, the link and the meta line stay the site's.
15
+ */
16
+ import type { ReactNode } from 'react';
17
+ import type { BrandlabJob } from '@brandlab-digital/site-core/jobs';
18
+ import type { BrandlabMediaManifest, BrandlabMediaRatio } from '@brandlab-digital/site-core/media';
19
+ import { type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order';
20
+ import { type SectionHeadingSlot, type SectionSlot, type SectionTone } from '../primitives/layout.js';
21
+ import { type UiMedia } from '../primitives/media-frame.js';
22
+ import { type SkinEntry } from '../skin.js';
23
+ import type { UiLink } from '../types.js';
24
+ /** The cards' parts are the `Card` primitive's (`[data-bl="card"]`), styled from the skin stylesheet. */
25
+ export type ProjectsSlot = SectionSlot | SectionHeadingSlot | 'list' | 'more';
26
+ export interface ProjectsLayout {
27
+ /** Columns on wide screens. Defaults to 3. */
28
+ readonly columns?: 2 | 3 | 4;
29
+ /** The photos' crop. Defaults to 4:3. */
30
+ readonly ratio?: BrandlabMediaRatio;
31
+ }
32
+ export type ProjectsSkin = SkinEntry<ProjectsSlot, never, ProjectsLayout>;
33
+ /** The most project cards a section shows (K11; the in-content intent-prefetch limit). */
34
+ export declare const PROJECTS_CARD_LIMIT: 8;
35
+ /** One completed job as a card. */
36
+ export interface ProjectCardData {
37
+ /** A stable slug (the job id), rendered as `data-item`. */
38
+ readonly id: string;
39
+ /** The card's H3 and its link's whole name, for example "Gum removal over a pool, Epping". */
40
+ readonly title: string;
41
+ /** The project page, for example `/projects/epping-gum-removal`. From the site's data. */
42
+ readonly href: string;
43
+ /** What was done, in a line. */
44
+ readonly summary?: ReactNode;
45
+ /** A short line under the summary, for example the suburb and month. */
46
+ readonly meta?: ReactNode;
47
+ /** A genuine photo of this job from the media manifest. */
48
+ readonly image?: UiMedia;
49
+ }
50
+ /**
51
+ * A card from the job log: the job's first photo (`jobPhotos`, genuine ones only) and its outcome as the summary. The
52
+ * site gives the title, the project page and the meta line in its own words.
53
+ */
54
+ export declare function projectCardFromJob(job: BrandlabJob, options: {
55
+ readonly media: BrandlabMediaManifest;
56
+ readonly title: string;
57
+ readonly href: string;
58
+ readonly meta?: ReactNode;
59
+ }): ProjectCardData;
60
+ export interface ProjectsProps extends ProjectsSkin {
61
+ /** The H2, for example "Recent tree removal jobs". */
62
+ readonly title: ReactNode;
63
+ readonly eyebrow?: ReactNode;
64
+ readonly lead?: ReactNode;
65
+ /** Up to 8 projects, newest or nearest first. */
66
+ readonly projects: readonly ProjectCardData[];
67
+ /** A link after the cards, for example the projects hub. Never prefetched. */
68
+ readonly more?: UiLink;
69
+ /** The photos' `sizes`: a fluid `vw` string, or a number for a fixed CSS width. Defaults to one grid column. */
70
+ readonly imageSizes?: string | number;
71
+ /** Defaults to `projects-heading`. */
72
+ readonly headingId?: string;
73
+ /** Set by `SectionSequence`. */
74
+ readonly tone?: SectionTone;
75
+ }
76
+ /** The section's guard for `SectionSequence`: skipped when no project is recorded. */
77
+ export declare function projectsReady({ projects }: Pick<ProjectsProps, 'projects'>): BrandlabSectionReadiness;
78
+ export declare function Projects({ title, eyebrow, lead, projects, more, imageSizes, headingId, tone, layout, classes, }: ProjectsProps): import("react/jsx-runtime").JSX.Element | null;
79
+ //# sourceMappingURL=projects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/projects.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jobPhotos } from '@brandlab-digital/site-core/jobs';
3
+ import { SECTION_READY } from '@brandlab-digital/site-core/section-order';
4
+ import { Card } from '../primitives/card.js';
5
+ import { Section, SectionHeading } from '../primitives/layout.js';
6
+ import { Anchor } from '../primitives/links.js';
7
+ import { MediaFrame } from '../primitives/media-frame.js';
8
+ import { LINK_PREFETCH_LIMITS, LINK_PREFETCH_POLICY } from '../prefetch.js';
9
+ import { slot } from '../skin.js';
10
+ import { assertGenuineWork, frameSizing, gridImageSizes, isInternalPath } from './work-photos.js';
11
+ /** The most project cards a section shows (K11; the in-content intent-prefetch limit). */
12
+ export const PROJECTS_CARD_LIMIT = LINK_PREFETCH_LIMITS.contentCard;
13
+ /**
14
+ * A card from the job log: the job's first photo (`jobPhotos`, genuine ones only) and its outcome as the summary. The
15
+ * site gives the title, the project page and the meta line in its own words.
16
+ */
17
+ export function projectCardFromJob(job, options) {
18
+ const photo = jobPhotos(job, options.media).find((entry) => entry.provenance === 'genuine');
19
+ return {
20
+ id: job.id,
21
+ title: options.title,
22
+ href: options.href,
23
+ summary: job.outcome,
24
+ ...(options.meta !== undefined ? { meta: options.meta } : {}),
25
+ ...(photo ? { image: photo } : {}),
26
+ };
27
+ }
28
+ /** The section's guard for `SectionSequence`: skipped when no project is recorded. */
29
+ export function projectsReady({ projects }) {
30
+ return projects.length > 0 ? SECTION_READY : { render: false, reason: 'no-projects' };
31
+ }
32
+ function assertProjects(projects) {
33
+ if (projects.length > PROJECTS_CARD_LIMIT) {
34
+ throw new Error(`Projects: ${projects.length} projects; a section shows at most ${PROJECTS_CARD_LIMIT} (K11). Link the projects hub for the rest.`);
35
+ }
36
+ const ids = new Set();
37
+ const hrefs = new Set();
38
+ const images = new Map();
39
+ for (const project of projects) {
40
+ if (!project.id || ids.has(project.id))
41
+ throw new Error(`Projects: every project needs its own id ("${project.id}" is empty or repeated).`);
42
+ ids.add(project.id);
43
+ if (!project.title?.trim())
44
+ throw new Error(`Projects: the project "${project.id}" has no title.`);
45
+ if (!isInternalPath(project.href) || project.href.split('/').filter(Boolean).length < 2) {
46
+ throw new Error(`Projects: "${project.id}" links ${project.href}; a project card links its own page under the projects hub, such as /projects/${project.id}.`);
47
+ }
48
+ if (hrefs.has(project.href))
49
+ throw new Error(`Projects: two projects link to ${project.href}.`);
50
+ hrefs.add(project.href);
51
+ if (project.image) {
52
+ assertGenuineWork('Projects', project.image, `the photo of "${project.id}"`);
53
+ const other = images.get(project.image.src);
54
+ if (other)
55
+ throw new Error(`Projects: "${project.id}" and "${other}" use the same photo (${project.image.src}).`);
56
+ images.set(project.image.src, project.id);
57
+ }
58
+ }
59
+ }
60
+ export function Projects({ title, eyebrow, lead, projects, more, imageSizes, headingId = 'projects-heading', tone, layout, classes, }) {
61
+ if (!projectsReady({ projects }).render)
62
+ return null;
63
+ assertProjects(projects);
64
+ const columns = layout?.columns ?? 3;
65
+ const ratio = layout?.ratio ?? '4:3';
66
+ const sizing = frameSizing(imageSizes ?? gridImageSizes(columns));
67
+ return (_jsxs(Section, { id: "projects", labelledBy: headingId, tone: tone, classes: classes, hooks: { 'data-columns': String(columns), 'data-ratio': ratio }, children: [_jsx(SectionHeading, { id: headingId, title: title, eyebrow: eyebrow, lead: lead, classes: classes }), _jsx("ul", { role: "list", ...slot(classes, 'list'), children: projects.map((project) => (_jsx(Card, { as: "li", title: project.title, href: project.href, prefetch: LINK_PREFETCH_POLICY.contentCard, media: project.image ? _jsx(MediaFrame, { media: project.image, ratio: ratio, ...sizing }) : undefined, meta: project.meta, children: project.summary }, project.id))) }), more ? (_jsx("p", { ...slot(classes, 'more'), children: _jsx(Anchor, { href: more.href, prefetch: false, hooks: {}, children: more.label }) })) : null] }));
68
+ }
69
+ //# sourceMappingURL=projects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/projects.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `Projects` (`projects`, 3.15.0): completed jobs as cards, each linking its project page (`/projects/{slug}`). Server\n * component. AA's `ProjectGrid`, re-implemented on the `Card` primitive.\n *\n * Locked:\n * - genuine photos only (v2 §11 imagery rule 4): a stock or AI image on a project card fails the build, and a project\n * without a photo shows as a card without one;\n * - at most 8 cards (K11; the in-content intent-prefetch limit); each card is one link, its title, which prefetches on\n * intent. The hub link after the list never prefetches;\n * - each card links its own project page, a child of the site's projects hub (`/projects/{slug}`, `/our-work/{slug}`);\n * - no default visible copy: the heading, titles, summaries and the suburb-and-month line all come from the site.\n *\n * `projectCardFromJob` builds a card from a site-core job log row (`./jobs`): the job's first genuine photo and its\n * outcome line; the title, the link and the meta line stay the site's.\n */\nimport type { ReactNode } from 'react'\nimport type { BrandlabJob } from '@brandlab-digital/site-core/jobs'\nimport { jobPhotos } from '@brandlab-digital/site-core/jobs'\nimport type { BrandlabMediaManifest, BrandlabMediaRatio } from '@brandlab-digital/site-core/media'\nimport { SECTION_READY, type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order'\nimport { Card } from '../primitives/card.js'\nimport { Section, SectionHeading, type SectionHeadingSlot, type SectionSlot, type SectionTone } from '../primitives/layout.js'\nimport { Anchor } from '../primitives/links.js'\nimport { MediaFrame, type UiMedia } from '../primitives/media-frame.js'\nimport { LINK_PREFETCH_LIMITS, LINK_PREFETCH_POLICY } from '../prefetch.js'\nimport { slot, type SkinEntry } from '../skin.js'\nimport type { UiLink } from '../types.js'\nimport { assertGenuineWork, frameSizing, gridImageSizes, isInternalPath } from './work-photos.js'\n\n/** The cards' parts are the `Card` primitive's (`[data-bl=\"card\"]`), styled from the skin stylesheet. */\nexport type ProjectsSlot = SectionSlot | SectionHeadingSlot | 'list' | 'more'\nexport interface ProjectsLayout {\n /** Columns on wide screens. Defaults to 3. */\n readonly columns?: 2 | 3 | 4\n /** The photos' crop. Defaults to 4:3. */\n readonly ratio?: BrandlabMediaRatio\n}\nexport type ProjectsSkin = SkinEntry<ProjectsSlot, never, ProjectsLayout>\n\n/** The most project cards a section shows (K11; the in-content intent-prefetch limit). */\nexport const PROJECTS_CARD_LIMIT = LINK_PREFETCH_LIMITS.contentCard\n\n/** One completed job as a card. */\nexport interface ProjectCardData {\n /** A stable slug (the job id), rendered as `data-item`. */\n readonly id: string\n /** The card's H3 and its link's whole name, for example \"Gum removal over a pool, Epping\". */\n readonly title: string\n /** The project page, for example `/projects/epping-gum-removal`. From the site's data. */\n readonly href: string\n /** What was done, in a line. */\n readonly summary?: ReactNode\n /** A short line under the summary, for example the suburb and month. */\n readonly meta?: ReactNode\n /** A genuine photo of this job from the media manifest. */\n readonly image?: UiMedia\n}\n\n/**\n * A card from the job log: the job's first photo (`jobPhotos`, genuine ones only) and its outcome as the summary. The\n * site gives the title, the project page and the meta line in its own words.\n */\nexport function projectCardFromJob(\n job: BrandlabJob,\n options: { readonly media: BrandlabMediaManifest; readonly title: string; readonly href: string; readonly meta?: ReactNode },\n): ProjectCardData {\n const photo = jobPhotos(job, options.media).find((entry) => entry.provenance === 'genuine')\n return {\n id: job.id,\n title: options.title,\n href: options.href,\n summary: job.outcome,\n ...(options.meta !== undefined ? { meta: options.meta } : {}),\n ...(photo ? { image: photo } : {}),\n }\n}\n\nexport interface ProjectsProps extends ProjectsSkin {\n /** The H2, for example \"Recent tree removal jobs\". */\n readonly title: ReactNode\n readonly eyebrow?: ReactNode\n readonly lead?: ReactNode\n /** Up to 8 projects, newest or nearest first. */\n readonly projects: readonly ProjectCardData[]\n /** A link after the cards, for example the projects hub. Never prefetched. */\n readonly more?: UiLink\n /** The photos' `sizes`: a fluid `vw` string, or a number for a fixed CSS width. Defaults to one grid column. */\n readonly imageSizes?: string | number\n /** Defaults to `projects-heading`. */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n}\n\n/** The section's guard for `SectionSequence`: skipped when no project is recorded. */\nexport function projectsReady({ projects }: Pick<ProjectsProps, 'projects'>): BrandlabSectionReadiness {\n return projects.length > 0 ? SECTION_READY : { render: false, reason: 'no-projects' }\n}\n\nfunction assertProjects(projects: readonly ProjectCardData[]): void {\n if (projects.length > PROJECTS_CARD_LIMIT) {\n throw new Error(`Projects: ${projects.length} projects; a section shows at most ${PROJECTS_CARD_LIMIT} (K11). Link the projects hub for the rest.`)\n }\n const ids = new Set<string>()\n const hrefs = new Set<string>()\n const images = new Map<string, string>()\n for (const project of projects) {\n if (!project.id || ids.has(project.id)) throw new Error(`Projects: every project needs its own id (\"${project.id}\" is empty or repeated).`)\n ids.add(project.id)\n if (!project.title?.trim()) throw new Error(`Projects: the project \"${project.id}\" has no title.`)\n if (!isInternalPath(project.href) || project.href.split('/').filter(Boolean).length < 2) {\n throw new Error(`Projects: \"${project.id}\" links ${project.href}; a project card links its own page under the projects hub, such as /projects/${project.id}.`)\n }\n if (hrefs.has(project.href)) throw new Error(`Projects: two projects link to ${project.href}.`)\n hrefs.add(project.href)\n if (project.image) {\n assertGenuineWork('Projects', project.image, `the photo of \"${project.id}\"`)\n const other = images.get(project.image.src)\n if (other) throw new Error(`Projects: \"${project.id}\" and \"${other}\" use the same photo (${project.image.src}).`)\n images.set(project.image.src, project.id)\n }\n }\n}\n\nexport function Projects({\n title,\n eyebrow,\n lead,\n projects,\n more,\n imageSizes,\n headingId = 'projects-heading',\n tone,\n layout,\n classes,\n}: ProjectsProps) {\n if (!projectsReady({ projects }).render) return null\n assertProjects(projects)\n const columns = layout?.columns ?? 3\n const ratio = layout?.ratio ?? '4:3'\n const sizing = frameSizing(imageSizes ?? gridImageSizes(columns))\n return (\n <Section id=\"projects\" labelledBy={headingId} tone={tone} classes={classes} hooks={{ 'data-columns': String(columns), 'data-ratio': ratio }}>\n <SectionHeading id={headingId} title={title} eyebrow={eyebrow} lead={lead} classes={classes} />\n <ul role=\"list\" {...slot(classes, 'list')}>\n {projects.map((project) => (\n <Card\n key={project.id}\n as=\"li\"\n title={project.title}\n href={project.href}\n prefetch={LINK_PREFETCH_POLICY.contentCard}\n media={project.image ? <MediaFrame media={project.image} ratio={ratio} {...sizing} /> : undefined}\n meta={project.meta}\n >\n {project.summary}\n </Card>\n ))}\n </ul>\n {more ? (\n <p {...slot(classes, 'more')}>\n <Anchor href={more.href} prefetch={false} hooks={{}}>\n {more.label}\n </Anchor>\n </p>\n ) : null}\n </Section>\n )\n}\n"]}
@@ -9,14 +9,17 @@ import type { ReactNode } from 'react';
9
9
  import { type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order';
10
10
  import { type UiIconName } from '../icons.js';
11
11
  import { type SectionSlot, type SectionTone } from '../primitives/layout.js';
12
+ import type { RepeatedSlotOf } from '../repeated-slots.js';
12
13
  import { type SkinEntry } from '../skin.js';
13
14
  export declare const PROOF_STRIP_VARIANTS: readonly ["inline", "tiles"];
14
15
  export type ProofStripVariant = (typeof PROOF_STRIP_VARIANTS)[number];
15
16
  export type ProofStripSlot = SectionSlot | 'heading' | 'list' | 'item' | 'icon' | 'stars' | 'value' | 'label' | 'logos';
16
17
  /** `heading`: the section's H2 is visually hidden by default; a skin may show it. */
18
+ /** Rendered once per proof item: styled from the skin stylesheet, never by class (3.14.0). */
19
+ export type ProofStripRepeatedSlot = RepeatedSlotOf<'proofStrip'>;
17
20
  export type ProofStripSkin = SkinEntry<ProofStripSlot, ProofStripVariant, {
18
21
  heading?: 'hidden' | 'visible';
19
- }>;
22
+ }, ProofStripRepeatedSlot>;
20
23
  /** The fewest items a strip shows; below this the facts merge into the hero checklist. */
21
24
  export declare const PROOF_STRIP_MINIMUM = 3;
22
25
  interface ProofItemBase {
@@ -1 +1 @@
1
- {"version":3,"file":"proof-strip.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/proof-strip.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAiB,KAAK,wBAAwB,EAAE,MAAM,2CAA2C,CAAA;AACxG,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,EAAwB,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAClG,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AAEjD,eAAO,MAAM,oBAAoB,8BAA+B,CAAA;AAChE,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAA;AACrE,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAA;AACvH,qFAAqF;AACrF,MAAM,MAAM,cAAc,GAAG,SAAS,CAAC,cAAc,EAAE,iBAAiB,EAAE;IAAE,OAAO,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;CAAE,CAAC,CAAA;AAE7G,0FAA0F;AAC1F,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAEpC,UAAU,aAAa;IACrB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAA;CAC3B;AAED,6GAA6G;AAC7G,MAAM,WAAW,aAAc,SAAQ,aAAa;IAClD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,CAAA;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CACvB;AAED,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,eAAe,CAAA;AAEvD,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,mGAAmG;IACnG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAA;IACpC,mFAAmF;IACnF,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAA;IAC1B,mCAAmC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,gCAAgC;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAC5B;AAED,4GAA4G;AAC5G,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,wBAAwB,CAGpH;AAED,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAA2B,EAAE,IAAI,EAAE,OAAkB,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,eAAe,kDAmC1I"}
1
+ {"version":3,"file":"proof-strip.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/proof-strip.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAiB,KAAK,wBAAwB,EAAE,MAAM,2CAA2C,CAAA;AACxG,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,EAAwB,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAClG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AAE/D,eAAO,MAAM,oBAAoB,8BAA+B,CAAA;AAChE,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAA;AACrE,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAA;AACvH,qFAAqF;AACrF,8FAA8F;AAC9F,MAAM,MAAM,sBAAsB,GAAG,cAAc,CAAC,YAAY,CAAC,CAAA;AACjE,MAAM,MAAM,cAAc,GAAG,SAAS,CAAC,cAAc,EAAE,iBAAiB,EAAE;IAAE,OAAO,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;CAAE,EAAE,sBAAsB,CAAC,CAAA;AAErI,0FAA0F;AAC1F,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAEpC,UAAU,aAAa;IACrB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAA;CAC3B;AAED,6GAA6G;AAC7G,MAAM,WAAW,aAAc,SAAQ,aAAa;IAClD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,CAAA;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CACvB;AAED,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,eAAe,CAAA;AAEvD,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,mGAAmG;IACnG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAA;IACpC,mFAAmF;IACnF,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAA;IAC1B,mCAAmC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,gCAAgC;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAC5B;AAED,4GAA4G;AAC5G,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,wBAAwB,CAGpH;AAED,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAA2B,EAAE,IAAI,EAAE,OAAkB,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,eAAe,kDAmC1I"}
@@ -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"]}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * `QuoteChecklist` (`data-bl="quote-checklist"`, 3.15.0): what to send for a fast, fair quote in this trade (photos of
3
+ * the tree and its access, the suburb, what's underneath it…), as a real list of ticks, then the button to the quote
4
+ * form. Server component built on `TickList`. It is a block, not a sequenced section of the matrix: a site places it
5
+ * inside its service detail, beside the contact form or as its own band (`headingLevel`).
6
+ *
7
+ * Locked: the list comes before the action; the action is the site's quote button (`lead_cta_click` placement
8
+ * `section`, id `quote-checklist`) to its quote form. No default visible copy: the heading, every item and the button
9
+ * label are the site's (spec: checklist content lives in each site's copy file, so same-trade sites never repeat each
10
+ * other). At most 8 items, so the list stays a checklist.
11
+ */
12
+ import type { ReactNode } from 'react';
13
+ import { type TickItem } from '../primitives/lists.js';
14
+ import type { ButtonVariant } from '../primitives/links.js';
15
+ import { type SkinEntry } from '../skin.js';
16
+ export type QuoteChecklistSlot = 'root' | 'heading' | 'lead' | 'list' | 'action' | 'quote';
17
+ export interface QuoteChecklistLayout {
18
+ /** The list's columns from the phone breakpoint up. Defaults to 1. */
19
+ readonly columns?: 1 | 2;
20
+ /** The quote button's variant. Defaults to `primary`. */
21
+ readonly quoteVariant?: ButtonVariant;
22
+ }
23
+ export type QuoteChecklistSkin = SkinEntry<QuoteChecklistSlot, never, QuoteChecklistLayout>;
24
+ /** The most items a checklist holds. */
25
+ export declare const QUOTE_CHECKLIST_MAX = 8;
26
+ export interface QuoteChecklistProps extends QuoteChecklistSkin {
27
+ /** The heading, for example "What to send for a fast quote". */
28
+ readonly title: ReactNode;
29
+ readonly lead?: ReactNode;
30
+ /** What to send, in the site's words: 2 to 8 items. */
31
+ readonly items: readonly TickItem[];
32
+ /** The site's recorded quote label and its quote form, for example `{ label: 'Get a free quote', href: '#quote-hero' }`. */
33
+ readonly quote: {
34
+ readonly label: string;
35
+ readonly href: string;
36
+ };
37
+ /** 2 (the default) makes the block its own `<section>` labelled by an H2; 3 sits it inside a section's outline. */
38
+ readonly headingLevel?: 2 | 3;
39
+ /** Defaults to `quote-checklist-heading`. */
40
+ readonly headingId?: string;
41
+ }
42
+ export declare function QuoteChecklist({ title, lead, items, quote, headingLevel, headingId, layout, classes }: QuoteChecklistProps): import("react/jsx-runtime").JSX.Element | null;
43
+ //# sourceMappingURL=quote-checklist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quote-checklist.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/quote-checklist.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AAEhE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AAEjD,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAA;AAC1F,MAAM,WAAW,oBAAoB;IACnC,sEAAsE;IACtE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;IACxB,yDAAyD;IACzD,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAA;CACtC;AACD,MAAM,MAAM,kBAAkB,GAAG,SAAS,CAAC,kBAAkB,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAAA;AAE3F,wCAAwC;AACxC,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAEpC,MAAM,WAAW,mBAAoB,SAAQ,kBAAkB;IAC7D,gEAAgE;IAChE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;IACzB,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAA;IACnC,4HAA4H;IAC5H,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACjE,mHAAmH;IACnH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;IAC7B,6CAA6C;IAC7C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,YAAgB,EAAE,SAAqC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,mBAAmB,kDAoB1J"}
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { TickList } from '../primitives/lists.js';
3
+ import { QuoteButton } from '../primitives/quote-button.js';
4
+ import { slot } from '../skin.js';
5
+ /** The most items a checklist holds. */
6
+ export const QUOTE_CHECKLIST_MAX = 8;
7
+ export function QuoteChecklist({ title, lead, items, quote, headingLevel = 2, headingId = 'quote-checklist-heading', layout, classes }) {
8
+ if (items.length === 0)
9
+ return null;
10
+ if (items.length < 2 || items.length > QUOTE_CHECKLIST_MAX) {
11
+ throw new Error(`QuoteChecklist: ${items.length} items; a checklist holds 2 to ${QUOTE_CHECKLIST_MAX}.`);
12
+ }
13
+ if (!quote?.label?.trim() || !quote.href?.trim())
14
+ throw new Error('QuoteChecklist: the quote button needs the site\'s label and its quote form.');
15
+ const Root = headingLevel === 2 ? 'section' : 'div';
16
+ const Heading = `h${headingLevel}`;
17
+ return (_jsxs(Root, { "data-bl": "quote-checklist", "aria-labelledby": headingLevel === 2 ? headingId : undefined, className: classes?.root, children: [_jsx(Heading, { id: headingId, ...slot(classes, 'heading'), children: title }), lead ? _jsx("p", { ...slot(classes, 'lead'), children: lead }) : null, _jsx(TickList, { items: items, layout: { columns: layout?.columns ?? 1 }, classes: { root: classes?.list } }), _jsx("p", { ...slot(classes, 'action'), children: _jsx(QuoteButton, { label: quote.label, href: quote.href, placement: "section", ctaId: "quote-checklist", variant: layout?.quoteVariant ?? 'primary', classes: { root: classes?.quote } }) })] }));
18
+ }
19
+ //# sourceMappingURL=quote-checklist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quote-checklist.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/quote-checklist.tsx"],"names":[],"mappings":";AAYA,OAAO,EAAE,QAAQ,EAAiB,MAAM,wBAAwB,CAAA;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAE3D,OAAO,EAAE,IAAI,EAAkB,MAAM,YAAY,CAAA;AAWjD,wCAAwC;AACxC,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAA;AAgBpC,MAAM,UAAU,cAAc,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,GAAG,CAAC,EAAE,SAAS,GAAG,yBAAyB,EAAE,MAAM,EAAE,OAAO,EAAuB;IACzJ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACnC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,mBAAmB,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,mBAAmB,KAAK,CAAC,MAAM,kCAAkC,mBAAmB,GAAG,CAAC,CAAA;IAC1G,CAAC;IACD,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,8EAA8E,CAAC,CAAA;IACjJ,MAAM,IAAI,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAA;IACnD,MAAM,OAAO,GAAG,IAAI,YAAY,EAAW,CAAA;IAC3C,OAAO,CACL,MAAC,IAAI,eAAS,iBAAiB,qBAAkB,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,aACnH,KAAC,OAAO,IAAC,EAAE,EAAE,SAAS,KAAM,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,YACjD,KAAK,GACE,EACT,IAAI,CAAC,CAAC,CAAC,eAAO,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,YAAG,IAAI,GAAK,CAAC,CAAC,CAAC,IAAI,EACvD,KAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,IAAI,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAAI,EACvG,eAAO,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,YAC5B,KAAC,WAAW,IAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,SAAS,EAAC,SAAS,EAAC,KAAK,EAAC,iBAAiB,EAAC,OAAO,EAAE,MAAM,EAAE,YAAY,IAAI,SAAS,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,GAAI,GAC9K,IACC,CACR,CAAA;AACH,CAAC","sourcesContent":["/**\n * `QuoteChecklist` (`data-bl=\"quote-checklist\"`, 3.15.0): what to send for a fast, fair quote in this trade (photos of\n * the tree and its access, the suburb, what's underneath it…), as a real list of ticks, then the button to the quote\n * form. Server component built on `TickList`. It is a block, not a sequenced section of the matrix: a site places it\n * inside its service detail, beside the contact form or as its own band (`headingLevel`).\n *\n * Locked: the list comes before the action; the action is the site's quote button (`lead_cta_click` placement\n * `section`, id `quote-checklist`) to its quote form. No default visible copy: the heading, every item and the button\n * label are the site's (spec: checklist content lives in each site's copy file, so same-trade sites never repeat each\n * other). At most 8 items, so the list stays a checklist.\n */\nimport type { ReactNode } from 'react'\nimport { TickList, type TickItem } from '../primitives/lists.js'\nimport { QuoteButton } from '../primitives/quote-button.js'\nimport type { ButtonVariant } from '../primitives/links.js'\nimport { slot, type SkinEntry } from '../skin.js'\n\nexport type QuoteChecklistSlot = 'root' | 'heading' | 'lead' | 'list' | 'action' | 'quote'\nexport interface QuoteChecklistLayout {\n /** The list's columns from the phone breakpoint up. Defaults to 1. */\n readonly columns?: 1 | 2\n /** The quote button's variant. Defaults to `primary`. */\n readonly quoteVariant?: ButtonVariant\n}\nexport type QuoteChecklistSkin = SkinEntry<QuoteChecklistSlot, never, QuoteChecklistLayout>\n\n/** The most items a checklist holds. */\nexport const QUOTE_CHECKLIST_MAX = 8\n\nexport interface QuoteChecklistProps extends QuoteChecklistSkin {\n /** The heading, for example \"What to send for a fast quote\". */\n readonly title: ReactNode\n readonly lead?: ReactNode\n /** What to send, in the site's words: 2 to 8 items. */\n readonly items: readonly TickItem[]\n /** The site's recorded quote label and its quote form, for example `{ label: 'Get a free quote', href: '#quote-hero' }`. */\n readonly quote: { readonly label: string; readonly href: string }\n /** 2 (the default) makes the block its own `<section>` labelled by an H2; 3 sits it inside a section's outline. */\n readonly headingLevel?: 2 | 3\n /** Defaults to `quote-checklist-heading`. */\n readonly headingId?: string\n}\n\nexport function QuoteChecklist({ title, lead, items, quote, headingLevel = 2, headingId = 'quote-checklist-heading', layout, classes }: QuoteChecklistProps) {\n if (items.length === 0) return null\n if (items.length < 2 || items.length > QUOTE_CHECKLIST_MAX) {\n throw new Error(`QuoteChecklist: ${items.length} items; a checklist holds 2 to ${QUOTE_CHECKLIST_MAX}.`)\n }\n if (!quote?.label?.trim() || !quote.href?.trim()) throw new Error('QuoteChecklist: the quote button needs the site\\'s label and its quote form.')\n const Root = headingLevel === 2 ? 'section' : 'div'\n const Heading = `h${headingLevel}` as const\n return (\n <Root data-bl=\"quote-checklist\" aria-labelledby={headingLevel === 2 ? headingId : undefined} className={classes?.root}>\n <Heading id={headingId} {...slot(classes, 'heading')}>\n {title}\n </Heading>\n {lead ? <p {...slot(classes, 'lead')}>{lead}</p> : null}\n <TickList items={items} layout={{ columns: layout?.columns ?? 1 }} classes={{ root: classes?.list }} />\n <p {...slot(classes, 'action')}>\n <QuoteButton label={quote.label} href={quote.href} placement=\"section\" ctaId=\"quote-checklist\" variant={layout?.quoteVariant ?? 'primary'} classes={{ root: classes?.quote }} />\n </p>\n </Root>\n )\n}\n"]}
@@ -0,0 +1,41 @@
1
+ import type { ReactNode } from 'react';
2
+ import { type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order';
3
+ import { type SectionHeadingSlot, type SectionSlot, type SectionTone } from '../primitives/layout.js';
4
+ import type { RepeatedSlotOf } from '../repeated-slots.js';
5
+ import { type SkinEntry } from '../skin.js';
6
+ import type { UiLink } from '../types.js';
7
+ import { type ServiceCardData, type ServicesGridLayout } from './services-grid.js';
8
+ /** The most services the section lists (K11; the in-content intent-prefetch limit). */
9
+ export declare const RELATED_SERVICES_LIMIT: 8;
10
+ export declare const RELATED_SERVICES_VARIANTS: readonly ["list", "cards"];
11
+ export type RelatedServicesVariant = (typeof RELATED_SERVICES_VARIANTS)[number];
12
+ export type RelatedServicesSlot = SectionSlot | SectionHeadingSlot | 'list' | 'more' | RepeatedSlotOf<'relatedServices'>;
13
+ /** `cards`: rendered once per service, styled from the skin stylesheet, never by class. */
14
+ export type RelatedServicesRepeatedSlot = RepeatedSlotOf<'relatedServices'>;
15
+ export interface RelatedServicesLayout {
16
+ /** Columns on wide screens: `list` 1 to 4 (default 3), `cards` 2 to 4 (default 3). */
17
+ readonly columns?: 1 | 2 | 3 | 4;
18
+ /** `cards`: the photo's crop. Defaults to 4:3. */
19
+ readonly ratio?: ServicesGridLayout['ratio'];
20
+ }
21
+ export type RelatedServicesSkin = SkinEntry<RelatedServicesSlot, RelatedServicesVariant, RelatedServicesLayout, RelatedServicesRepeatedSlot>;
22
+ export interface RelatedServicesProps extends RelatedServicesSkin {
23
+ /** The H2, for example "Other services in Castle Hill". */
24
+ readonly title: ReactNode;
25
+ readonly eyebrow?: ReactNode;
26
+ readonly lead?: ReactNode;
27
+ /** The other services, in money order; `cards` uses their summaries and photos. */
28
+ readonly services: readonly ServiceCardData[];
29
+ /** A link after the list, for example the services hub. Never prefetched. */
30
+ readonly more?: UiLink;
31
+ /** `cards`: the photo `sizes`. */
32
+ readonly imageSizes?: string;
33
+ /** Defaults to `related-services-heading`. */
34
+ readonly headingId?: string;
35
+ /** Set by `SectionSequence`. */
36
+ readonly tone?: SectionTone;
37
+ }
38
+ /** The guard for `SectionSequence`. */
39
+ export declare function relatedServicesReady({ services }: Pick<RelatedServicesProps, 'services'>): BrandlabSectionReadiness;
40
+ export declare function RelatedServices({ title, eyebrow, lead, services, more, imageSizes, headingId, tone, variant, layout, classes, }: RelatedServicesProps): import("react/jsx-runtime").JSX.Element | null;
41
+ //# sourceMappingURL=related-services.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"related-services.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/related-services.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAiB,KAAK,wBAAwB,EAAE,MAAM,2CAA2C,CAAA;AAGxG,OAAO,EAA2B,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC9H,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAkC,KAAK,eAAe,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAElH,uFAAuF;AACvF,eAAO,MAAM,sBAAsB,GAAmC,CAAA;AAEtE,eAAO,MAAM,yBAAyB,4BAA6B,CAAA;AACnE,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAA;AAC/E,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,kBAAkB,GAAG,MAAM,GAAG,MAAM,GAAG,cAAc,CAAC,iBAAiB,CAAC,CAAA;AACxH,2FAA2F;AAC3F,MAAM,MAAM,2BAA2B,GAAG,cAAc,CAAC,iBAAiB,CAAC,CAAA;AAC3E,MAAM,WAAW,qBAAqB;IACpC,sFAAsF;IACtF,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAChC,kDAAkD;IAClD,QAAQ,CAAC,KAAK,CAAC,EAAE,kBAAkB,CAAC,OAAO,CAAC,CAAA;CAC7C;AACD,MAAM,MAAM,mBAAmB,GAAG,SAAS,CAAC,mBAAmB,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,2BAA2B,CAAC,CAAA;AAE5I,MAAM,WAAW,oBAAqB,SAAQ,mBAAmB;IAC/D,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAA;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;IACzB,mFAAmF;IACnF,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAA;IAC7C,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,kCAAkC;IAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,gCAAgC;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAC5B;AAED,uCAAuC;AACvC,wBAAgB,oBAAoB,CAAC,EAAE,QAAQ,EAAE,EAAE,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC,GAAG,wBAAwB,CAEnH;AAiBD,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,UAAU,EACV,SAAsC,EACtC,IAAI,EACJ,OAAgB,EAChB,MAAM,EACN,OAAO,GACR,EAAE,oBAAoB,kDA+CtB"}
@@ -0,0 +1,52 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * `RelatedServices` (S28, `related-services`, 3.15.0): the other services in this place ("Other services in Castle
4
+ * Hill") or a service page's siblings. Server component.
5
+ *
6
+ * Each entry links to its service-in-suburb page where one exists, or to its section on the suburb page
7
+ * (`#tree-pruning`); the href always comes from the site's data. At most 8 (K11). Variants:
8
+ * - `list` (the default, lean): a `LinkList` of plain `<li><a>` entries, nothing prefetched
9
+ * (`data-bl-links="other-services"`, the `otherServicesInSuburb` role in `LINK_PREFETCH_POLICY`);
10
+ * - `cards`: `ServiceCard`s with a summary and photo, each card's one link prefetching on intent (`contentCard`).
11
+ */
12
+ import Link from 'next/link';
13
+ import { SECTION_READY } from '@brandlab-digital/site-core/section-order';
14
+ import { LINK_PREFETCH_LIMITS, LINK_PREFETCH_POLICY } from '../prefetch.js';
15
+ import { LinkList } from '../primitives/lists.js';
16
+ import { Section, SectionHeading } from '../primitives/layout.js';
17
+ import { slot } from '../skin.js';
18
+ import { ServiceCard, defaultImageSizes } from './services-grid.js';
19
+ /** The most services the section lists (K11; the in-content intent-prefetch limit). */
20
+ export const RELATED_SERVICES_LIMIT = LINK_PREFETCH_LIMITS.contentCard;
21
+ export const RELATED_SERVICES_VARIANTS = ['list', 'cards'];
22
+ /** The guard for `SectionSequence`. */
23
+ export function relatedServicesReady({ services }) {
24
+ return services.length > 0 ? SECTION_READY : { render: false, reason: 'no-other-services' };
25
+ }
26
+ function assertServices(services) {
27
+ if (services.length > RELATED_SERVICES_LIMIT) {
28
+ throw new Error(`RelatedServices: ${services.length} services; the section lists at most ${RELATED_SERVICES_LIMIT} (K11). Link the services hub for the rest.`);
29
+ }
30
+ const ids = new Set();
31
+ const hrefs = new Set();
32
+ for (const service of services) {
33
+ if (!service.id || ids.has(service.id))
34
+ throw new Error(`RelatedServices: every service needs its own id ("${service.id}" is empty or repeated).`);
35
+ ids.add(service.id);
36
+ if (!service.title?.trim() || !service.href?.trim())
37
+ throw new Error(`RelatedServices: "${service.id}" needs a title and a link.`);
38
+ if (hrefs.has(service.href))
39
+ throw new Error(`RelatedServices: two services link to ${service.href}.`);
40
+ hrefs.add(service.href);
41
+ }
42
+ }
43
+ export function RelatedServices({ title, eyebrow, lead, services, more, imageSizes, headingId = 'related-services-heading', tone, variant = 'list', layout, classes, }) {
44
+ if (services.length === 0)
45
+ return null;
46
+ assertServices(services);
47
+ const columns = layout?.columns ?? 3;
48
+ const ratio = layout?.ratio ?? '4:3';
49
+ const cardColumns = (columns < 2 ? 2 : columns);
50
+ return (_jsxs(Section, { id: "related-services", labelledBy: headingId, tone: tone, variant: variant, classes: classes, hooks: variant === 'cards' ? { 'data-columns': String(cardColumns), 'data-ratio': ratio } : undefined, children: [_jsx(SectionHeading, { id: headingId, title: title, eyebrow: eyebrow, lead: lead, classes: classes }), variant === 'cards' ? (_jsx("ul", { role: "list", ...slot(classes, 'list'), children: services.map((service) => (_jsx(ServiceCard, { service: service, variant: "cards", imageSizes: imageSizes ?? defaultImageSizes('cards', cardColumns), ratio: ratio, prefetch: LINK_PREFETCH_POLICY.contentCard }, service.id))) })) : (_jsx(LinkList, { links: services.map((service) => ({ label: service.title, href: service.href })), kind: "other-services", prefetch: LINK_PREFETCH_POLICY.otherServicesInSuburb, layout: { columns }, classes: { root: classes?.list } })), more ? (_jsx("p", { ...slot(classes, 'more'), children: _jsx(Link, { href: more.href, prefetch: false, children: more.label }) })) : null] }));
51
+ }
52
+ //# sourceMappingURL=related-services.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"related-services.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/related-services.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `RelatedServices` (S28, `related-services`, 3.15.0): the other services in this place (\"Other services in Castle\n * Hill\") or a service page's siblings. Server component.\n *\n * Each entry links to its service-in-suburb page where one exists, or to its section on the suburb page\n * (`#tree-pruning`); the href always comes from the site's data. At most 8 (K11). Variants:\n * - `list` (the default, lean): a `LinkList` of plain `<li><a>` entries, nothing prefetched\n * (`data-bl-links=\"other-services\"`, the `otherServicesInSuburb` role in `LINK_PREFETCH_POLICY`);\n * - `cards`: `ServiceCard`s with a summary and photo, each card's one link prefetching on intent (`contentCard`).\n */\nimport Link from 'next/link'\nimport type { ReactNode } from 'react'\nimport { SECTION_READY, type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order'\nimport { LINK_PREFETCH_LIMITS, LINK_PREFETCH_POLICY } from '../prefetch.js'\nimport { LinkList } from '../primitives/lists.js'\nimport { Section, SectionHeading, type SectionHeadingSlot, type SectionSlot, type SectionTone } from '../primitives/layout.js'\nimport type { RepeatedSlotOf } from '../repeated-slots.js'\nimport { slot, type SkinEntry } from '../skin.js'\nimport type { UiLink } from '../types.js'\nimport { ServiceCard, defaultImageSizes, type ServiceCardData, type ServicesGridLayout } from './services-grid.js'\n\n/** The most services the section lists (K11; the in-content intent-prefetch limit). */\nexport const RELATED_SERVICES_LIMIT = LINK_PREFETCH_LIMITS.contentCard\n\nexport const RELATED_SERVICES_VARIANTS = ['list', 'cards'] as const\nexport type RelatedServicesVariant = (typeof RELATED_SERVICES_VARIANTS)[number]\nexport type RelatedServicesSlot = SectionSlot | SectionHeadingSlot | 'list' | 'more' | RepeatedSlotOf<'relatedServices'>\n/** `cards`: rendered once per service, styled from the skin stylesheet, never by class. */\nexport type RelatedServicesRepeatedSlot = RepeatedSlotOf<'relatedServices'>\nexport interface RelatedServicesLayout {\n /** Columns on wide screens: `list` 1 to 4 (default 3), `cards` 2 to 4 (default 3). */\n readonly columns?: 1 | 2 | 3 | 4\n /** `cards`: the photo's crop. Defaults to 4:3. */\n readonly ratio?: ServicesGridLayout['ratio']\n}\nexport type RelatedServicesSkin = SkinEntry<RelatedServicesSlot, RelatedServicesVariant, RelatedServicesLayout, RelatedServicesRepeatedSlot>\n\nexport interface RelatedServicesProps extends RelatedServicesSkin {\n /** The H2, for example \"Other services in Castle Hill\". */\n readonly title: ReactNode\n readonly eyebrow?: ReactNode\n readonly lead?: ReactNode\n /** The other services, in money order; `cards` uses their summaries and photos. */\n readonly services: readonly ServiceCardData[]\n /** A link after the list, for example the services hub. Never prefetched. */\n readonly more?: UiLink\n /** `cards`: the photo `sizes`. */\n readonly imageSizes?: string\n /** Defaults to `related-services-heading`. */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n}\n\n/** The guard for `SectionSequence`. */\nexport function relatedServicesReady({ services }: Pick<RelatedServicesProps, 'services'>): BrandlabSectionReadiness {\n return services.length > 0 ? SECTION_READY : { render: false, reason: 'no-other-services' }\n}\n\nfunction assertServices(services: readonly ServiceCardData[]) {\n if (services.length > RELATED_SERVICES_LIMIT) {\n throw new Error(`RelatedServices: ${services.length} services; the section lists at most ${RELATED_SERVICES_LIMIT} (K11). Link the services hub for the rest.`)\n }\n const ids = new Set<string>()\n const hrefs = new Set<string>()\n for (const service of services) {\n if (!service.id || ids.has(service.id)) throw new Error(`RelatedServices: every service needs its own id (\"${service.id}\" is empty or repeated).`)\n ids.add(service.id)\n if (!service.title?.trim() || !service.href?.trim()) throw new Error(`RelatedServices: \"${service.id}\" needs a title and a link.`)\n if (hrefs.has(service.href)) throw new Error(`RelatedServices: two services link to ${service.href}.`)\n hrefs.add(service.href)\n }\n}\n\nexport function RelatedServices({\n title,\n eyebrow,\n lead,\n services,\n more,\n imageSizes,\n headingId = 'related-services-heading',\n tone,\n variant = 'list',\n layout,\n classes,\n}: RelatedServicesProps) {\n if (services.length === 0) return null\n assertServices(services)\n const columns = layout?.columns ?? 3\n const ratio = layout?.ratio ?? '4:3'\n const cardColumns = (columns < 2 ? 2 : columns) as 2 | 3 | 4\n return (\n <Section\n id=\"related-services\"\n labelledBy={headingId}\n tone={tone}\n variant={variant}\n classes={classes}\n hooks={variant === 'cards' ? { 'data-columns': String(cardColumns), 'data-ratio': ratio } : undefined}\n >\n <SectionHeading id={headingId} title={title} eyebrow={eyebrow} lead={lead} classes={classes} />\n {variant === 'cards' ? (\n <ul role=\"list\" {...slot(classes, 'list')}>\n {services.map((service) => (\n <ServiceCard\n key={service.id}\n service={service}\n variant=\"cards\"\n imageSizes={imageSizes ?? defaultImageSizes('cards', cardColumns)}\n ratio={ratio}\n prefetch={LINK_PREFETCH_POLICY.contentCard}\n />\n ))}\n </ul>\n ) : (\n <LinkList\n links={services.map((service) => ({ label: service.title, href: service.href }))}\n kind=\"other-services\"\n prefetch={LINK_PREFETCH_POLICY.otherServicesInSuburb}\n layout={{ columns }}\n classes={{ root: classes?.list }}\n />\n )}\n {more ? (\n <p {...slot(classes, 'more')}>\n <Link href={more.href} prefetch={false}>\n {more.label}\n </Link>\n </p>\n ) : null}\n </Section>\n )\n}\n"]}
@@ -15,7 +15,9 @@
15
15
  */
16
16
  import { type ReactNode } from 'react';
17
17
  import { type BusinessProfileReview, type PreparedBusinessProfileReviews } from '@brandlab-digital/site-core/google-business-reviews';
18
+ import { type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order';
18
19
  import { type SectionTone } from '../primitives/layout.js';
20
+ import type { RepeatedSlotOf } from '../repeated-slots.js';
19
21
  import { type SkinEntry, type SlotClasses } from '../skin.js';
20
22
  export declare const REVIEWS_BAND_VARIANTS: readonly ["band", "feature", "cards", "wall", "summary"];
21
23
  export type ReviewsBandVariant = (typeof REVIEWS_BAND_VARIANTS)[number];
@@ -28,9 +30,21 @@ export interface ReviewsBandLayout {
28
30
  /** Characters shown before a long review folds behind "Read more" (default 320); `false` never folds. */
29
31
  readonly excerpt?: number | false;
30
32
  }
31
- export type ReviewsBandSkin = SkinEntry<ReviewsBandSlot, ReviewsBandVariant, ReviewsBandLayout>;
32
- type Classes = SlotClasses<ReviewsBandSlot> | undefined;
33
+ /** Rendered once per review: styled from the skin stylesheet, never by class (3.14.0). */
34
+ export type ReviewsBandRepeatedSlot = RepeatedSlotOf<'reviewsBand'>;
35
+ export type ReviewsBandSkin = SkinEntry<ReviewsBandSlot, ReviewsBandVariant, ReviewsBandLayout, ReviewsBandRepeatedSlot>;
36
+ type Classes = SlotClasses<Exclude<ReviewsBandSlot, ReviewsBandRepeatedSlot>> | undefined;
33
37
  export declare const REVIEWS_BAND_HEADING_ID = "bl-reviews-heading";
38
+ /** The skip reason a band with nothing to show records in the section manifest: the feed has no reviews or rating. */
39
+ export declare const REVIEWS_BAND_SKIP_REASON = "no-feed-reviews";
40
+ /**
41
+ * The guard for `SectionSequence` (3.15.0), from the same prepared reviews the band renders: ready when the band
42
+ * renders (written reviews, or Google's rating alone as the summary fallback); otherwise `no-feed-reviews`, for a
43
+ * missing feed, an empty one, or the `summary` variant without a rating.
44
+ */
45
+ export declare function reviewsBandReady(reviews: PreparedBusinessProfileReviews | null | undefined, { variant }?: {
46
+ readonly variant?: ReviewsBandVariant;
47
+ }): BrandlabSectionReadiness;
34
48
  /**
35
49
  * A long review split at a sentence end (or else a space) near `limit`, so the first part reads on its own. The two
36
50
  * parts together are the review word for word. Short reviews, and reviews only a little over the limit, don't fold.