@brandlab-digital/site-next 3.12.1 → 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 (208) hide show
  1. package/README.md +132 -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/estate-segment.d.ts +48 -0
  23. package/dist/estate-segment.d.ts.map +1 -0
  24. package/dist/estate-segment.js +54 -0
  25. package/dist/estate-segment.js.map +1 -0
  26. package/dist/google-business-reviews-revalidate.d.ts +61 -0
  27. package/dist/google-business-reviews-revalidate.d.ts.map +1 -0
  28. package/dist/google-business-reviews-revalidate.js +151 -0
  29. package/dist/google-business-reviews-revalidate.js.map +1 -0
  30. package/dist/google-business-reviews-server.d.ts +21 -2
  31. package/dist/google-business-reviews-server.d.ts.map +1 -1
  32. package/dist/google-business-reviews-server.js +21 -4
  33. package/dist/google-business-reviews-server.js.map +1 -1
  34. package/dist/headers.d.ts.map +1 -1
  35. package/dist/headers.js +6 -0
  36. package/dist/headers.js.map +1 -1
  37. package/dist/next-config.d.ts +17 -1
  38. package/dist/next-config.d.ts.map +1 -1
  39. package/dist/next-config.js +36 -2
  40. package/dist/next-config.js.map +1 -1
  41. package/dist/robots.d.ts +26 -4
  42. package/dist/robots.d.ts.map +1 -1
  43. package/dist/robots.js +49 -2
  44. package/dist/robots.js.map +1 -1
  45. package/dist/ui-kit/brandlab-skin.d.ts +24 -1
  46. package/dist/ui-kit/brandlab-skin.d.ts.map +1 -1
  47. package/dist/ui-kit/brandlab-skin.js.map +1 -1
  48. package/dist/ui-kit/form/photo-upload.d.ts +4 -1
  49. package/dist/ui-kit/form/photo-upload.d.ts.map +1 -1
  50. package/dist/ui-kit/form/photo-upload.js +4 -4
  51. package/dist/ui-kit/form/photo-upload.js.map +1 -1
  52. package/dist/ui-kit/form/quote-form.d.ts +4 -1
  53. package/dist/ui-kit/form/quote-form.d.ts.map +1 -1
  54. package/dist/ui-kit/form/quote-form.js.map +1 -1
  55. package/dist/ui-kit/form/thank-you.d.ts +4 -1
  56. package/dist/ui-kit/form/thank-you.d.ts.map +1 -1
  57. package/dist/ui-kit/form/thank-you.js +2 -2
  58. package/dist/ui-kit/form/thank-you.js.map +1 -1
  59. package/dist/ui-kit/icon-glyphs.d.ts +41 -0
  60. package/dist/ui-kit/icon-glyphs.d.ts.map +1 -0
  61. package/dist/ui-kit/icon-glyphs.js +138 -0
  62. package/dist/ui-kit/icon-glyphs.js.map +1 -0
  63. package/dist/ui-kit/icon-sprite.d.ts +19 -0
  64. package/dist/ui-kit/icon-sprite.d.ts.map +1 -0
  65. package/dist/ui-kit/icon-sprite.js +44 -0
  66. package/dist/ui-kit/icon-sprite.js.map +1 -0
  67. package/dist/ui-kit/icons.d.ts +38 -3
  68. package/dist/ui-kit/icons.d.ts.map +1 -1
  69. package/dist/ui-kit/icons.js +47 -64
  70. package/dist/ui-kit/icons.js.map +1 -1
  71. package/dist/ui-kit/prefetch.d.ts +50 -0
  72. package/dist/ui-kit/prefetch.d.ts.map +1 -0
  73. package/dist/ui-kit/prefetch.js +50 -0
  74. package/dist/ui-kit/prefetch.js.map +1 -0
  75. package/dist/ui-kit/primitives/badge.d.ts +25 -0
  76. package/dist/ui-kit/primitives/badge.d.ts.map +1 -0
  77. package/dist/ui-kit/primitives/badge.js +21 -0
  78. package/dist/ui-kit/primitives/badge.js.map +1 -0
  79. package/dist/ui-kit/primitives/breadcrumbs.d.ts +27 -0
  80. package/dist/ui-kit/primitives/breadcrumbs.d.ts.map +1 -0
  81. package/dist/ui-kit/primitives/breadcrumbs.js +40 -0
  82. package/dist/ui-kit/primitives/breadcrumbs.js.map +1 -0
  83. package/dist/ui-kit/primitives/button.d.ts +32 -0
  84. package/dist/ui-kit/primitives/button.d.ts.map +1 -0
  85. package/dist/ui-kit/primitives/button.js +8 -0
  86. package/dist/ui-kit/primitives/button.js.map +1 -0
  87. package/dist/ui-kit/primitives/card.d.ts +40 -0
  88. package/dist/ui-kit/primitives/card.d.ts.map +1 -0
  89. package/dist/ui-kit/primitives/card.js +8 -0
  90. package/dist/ui-kit/primitives/card.js.map +1 -0
  91. package/dist/ui-kit/primitives/index.d.ts +12 -2
  92. package/dist/ui-kit/primitives/index.d.ts.map +1 -1
  93. package/dist/ui-kit/primitives/index.js +12 -2
  94. package/dist/ui-kit/primitives/index.js.map +1 -1
  95. package/dist/ui-kit/primitives/layout.d.ts +12 -0
  96. package/dist/ui-kit/primitives/layout.d.ts.map +1 -1
  97. package/dist/ui-kit/primitives/layout.js +20 -2
  98. package/dist/ui-kit/primitives/layout.js.map +1 -1
  99. package/dist/ui-kit/primitives/links.d.ts +28 -2
  100. package/dist/ui-kit/primitives/links.d.ts.map +1 -1
  101. package/dist/ui-kit/primitives/links.js +11 -6
  102. package/dist/ui-kit/primitives/links.js.map +1 -1
  103. package/dist/ui-kit/primitives/lists.d.ts +43 -0
  104. package/dist/ui-kit/primitives/lists.d.ts.map +1 -0
  105. package/dist/ui-kit/primitives/lists.js +34 -0
  106. package/dist/ui-kit/primitives/lists.js.map +1 -0
  107. package/dist/ui-kit/primitives/media-frame.d.ts +28 -0
  108. package/dist/ui-kit/primitives/media-frame.d.ts.map +1 -0
  109. package/dist/ui-kit/primitives/media-frame.js +58 -0
  110. package/dist/ui-kit/primitives/media-frame.js.map +1 -0
  111. package/dist/ui-kit/primitives/media.d.ts +38 -0
  112. package/dist/ui-kit/primitives/media.d.ts.map +1 -0
  113. package/dist/ui-kit/primitives/media.js +9 -0
  114. package/dist/ui-kit/primitives/media.js.map +1 -0
  115. package/dist/ui-kit/primitives/quote-button.d.ts +4 -1
  116. package/dist/ui-kit/primitives/quote-button.d.ts.map +1 -1
  117. package/dist/ui-kit/primitives/quote-button.js +3 -2
  118. package/dist/ui-kit/primitives/quote-button.js.map +1 -1
  119. package/dist/ui-kit/primitives/stat.d.ts +25 -0
  120. package/dist/ui-kit/primitives/stat.d.ts.map +1 -0
  121. package/dist/ui-kit/primitives/stat.js +7 -0
  122. package/dist/ui-kit/primitives/stat.js.map +1 -0
  123. package/dist/ui-kit/repeated-slots.d.ts +121 -0
  124. package/dist/ui-kit/repeated-slots.d.ts.map +1 -0
  125. package/dist/ui-kit/repeated-slots.js +81 -0
  126. package/dist/ui-kit/repeated-slots.js.map +1 -0
  127. package/dist/ui-kit/sections/accordion.d.ts +4 -1
  128. package/dist/ui-kit/sections/accordion.d.ts.map +1 -1
  129. package/dist/ui-kit/sections/accordion.js +2 -2
  130. package/dist/ui-kit/sections/accordion.js.map +1 -1
  131. package/dist/ui-kit/sections/contact-block.d.ts +4 -1
  132. package/dist/ui-kit/sections/contact-block.d.ts.map +1 -1
  133. package/dist/ui-kit/sections/contact-block.js +3 -3
  134. package/dist/ui-kit/sections/contact-block.js.map +1 -1
  135. package/dist/ui-kit/sections/faqs.d.ts +4 -1
  136. package/dist/ui-kit/sections/faqs.d.ts.map +1 -1
  137. package/dist/ui-kit/sections/faqs.js +4 -5
  138. package/dist/ui-kit/sections/faqs.js.map +1 -1
  139. package/dist/ui-kit/sections/hero.d.ts +113 -0
  140. package/dist/ui-kit/sections/hero.d.ts.map +1 -0
  141. package/dist/ui-kit/sections/hero.js +58 -0
  142. package/dist/ui-kit/sections/hero.js.map +1 -0
  143. package/dist/ui-kit/sections/index.d.ts +2 -0
  144. package/dist/ui-kit/sections/index.d.ts.map +1 -1
  145. package/dist/ui-kit/sections/index.js +2 -0
  146. package/dist/ui-kit/sections/index.js.map +1 -1
  147. package/dist/ui-kit/sections/price-guide.d.ts +4 -1
  148. package/dist/ui-kit/sections/price-guide.d.ts.map +1 -1
  149. package/dist/ui-kit/sections/price-guide.js +3 -3
  150. package/dist/ui-kit/sections/price-guide.js.map +1 -1
  151. package/dist/ui-kit/sections/price-table.d.ts +7 -9
  152. package/dist/ui-kit/sections/price-table.d.ts.map +1 -1
  153. package/dist/ui-kit/sections/price-table.js +8 -8
  154. package/dist/ui-kit/sections/price-table.js.map +1 -1
  155. package/dist/ui-kit/sections/proof-strip.d.ts +4 -1
  156. package/dist/ui-kit/sections/proof-strip.d.ts.map +1 -1
  157. package/dist/ui-kit/sections/proof-strip.js +2 -2
  158. package/dist/ui-kit/sections/proof-strip.js.map +1 -1
  159. package/dist/ui-kit/sections/reviews-band.d.ts +5 -2
  160. package/dist/ui-kit/sections/reviews-band.d.ts.map +1 -1
  161. package/dist/ui-kit/sections/reviews-band.js +2 -2
  162. package/dist/ui-kit/sections/reviews-band.js.map +1 -1
  163. package/dist/ui-kit/sections/services-grid.d.ts +82 -0
  164. package/dist/ui-kit/sections/services-grid.d.ts.map +1 -0
  165. package/dist/ui-kit/sections/services-grid.js +90 -0
  166. package/dist/ui-kit/sections/services-grid.js.map +1 -0
  167. package/dist/ui-kit/sections/site-footer.d.ts +4 -1
  168. package/dist/ui-kit/sections/site-footer.d.ts.map +1 -1
  169. package/dist/ui-kit/sections/site-footer.js +9 -6
  170. package/dist/ui-kit/sections/site-footer.js.map +1 -1
  171. package/dist/ui-kit/shell/current-link.d.ts +15 -1
  172. package/dist/ui-kit/shell/current-link.d.ts.map +1 -1
  173. package/dist/ui-kit/shell/current-link.js +10 -1
  174. package/dist/ui-kit/shell/current-link.js.map +1 -1
  175. package/dist/ui-kit/shell/index.d.ts +1 -0
  176. package/dist/ui-kit/shell/index.d.ts.map +1 -1
  177. package/dist/ui-kit/shell/index.js +1 -0
  178. package/dist/ui-kit/shell/index.js.map +1 -1
  179. package/dist/ui-kit/shell/intent-prefetch.d.ts +4 -0
  180. package/dist/ui-kit/shell/intent-prefetch.d.ts.map +1 -0
  181. package/dist/ui-kit/shell/intent-prefetch.js +61 -0
  182. package/dist/ui-kit/shell/intent-prefetch.js.map +1 -0
  183. package/dist/ui-kit/shell/mobile-drawer.d.ts +7 -3
  184. package/dist/ui-kit/shell/mobile-drawer.d.ts.map +1 -1
  185. package/dist/ui-kit/shell/mobile-drawer.js +20 -4
  186. package/dist/ui-kit/shell/mobile-drawer.js.map +1 -1
  187. package/dist/ui-kit/shell/site-header.d.ts +4 -1
  188. package/dist/ui-kit/shell/site-header.d.ts.map +1 -1
  189. package/dist/ui-kit/shell/site-header.js +9 -10
  190. package/dist/ui-kit/shell/site-header.js.map +1 -1
  191. package/dist/ui-kit/shell/social-links.d.ts +1 -2
  192. package/dist/ui-kit/shell/social-links.d.ts.map +1 -1
  193. package/dist/ui-kit/shell/social-links.js +6 -3
  194. package/dist/ui-kit/shell/social-links.js.map +1 -1
  195. package/dist/ui-kit/shell/utility-bar.d.ts +4 -1
  196. package/dist/ui-kit/shell/utility-bar.d.ts.map +1 -1
  197. package/dist/ui-kit/shell/utility-bar.js +2 -2
  198. package/dist/ui-kit/shell/utility-bar.js.map +1 -1
  199. package/dist/ui-kit/skin-index.d.ts +2 -1
  200. package/dist/ui-kit/skin-index.d.ts.map +1 -1
  201. package/dist/ui-kit/skin-index.js +2 -1
  202. package/dist/ui-kit/skin-index.js.map +1 -1
  203. package/dist/ui-kit/skin.d.ts +21 -4
  204. package/dist/ui-kit/skin.d.ts.map +1 -1
  205. package/dist/ui-kit/skin.js +14 -1
  206. package/dist/ui-kit/skin.js.map +1 -1
  207. package/dist/ui-kit/structure.css +383 -3
  208. package/package.json +10 -2
@@ -0,0 +1,19 @@
1
+ import { type SiteIconSet } from './icon-glyphs.js';
2
+ /** The public folder the sprite is written to and served from. */
3
+ export declare const ICON_SPRITE_FOLDER = "_bl";
4
+ export interface IconSprite {
5
+ /** The sprite's markup. */
6
+ readonly svg: string;
7
+ /** The first 12 hex characters of the markup's SHA-256. */
8
+ readonly hash: string;
9
+ /** The served path, for example `/_bl/icons.0123456789ab.svg`. */
10
+ readonly href: string;
11
+ }
12
+ export declare function buildIconSprite(site?: SiteIconSet): IconSprite;
13
+ /**
14
+ * Writes the sprite to `public/_bl/icons.<hash>.svg` under the project root and removes earlier sprites there, so the
15
+ * folder holds the current file only. Leaves an unchanged file untouched. Add `public/_bl/` to `.gitignore`: every
16
+ * build writes it again.
17
+ */
18
+ export declare function writeIconSprite(projectRoot: string, site?: SiteIconSet): IconSprite;
19
+ //# sourceMappingURL=icon-sprite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icon-sprite.d.ts","sourceRoot":"","sources":["../../src/ui-kit/icon-sprite.ts"],"names":[],"mappings":"AAQA,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAErE,kEAAkE;AAClE,eAAO,MAAM,kBAAkB,QAAQ,CAAA;AAGvC,MAAM,WAAW,UAAU;IACzB,2BAA2B;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,UAAU,CAI9D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,UAAU,CAiBnF"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Builds the icon sprite file (Node only; `defineBrandlabNextConfig({ icons })` calls it when the config loads). The
3
+ * sprite holds every library icon and the site's own, and its name carries a hash of its content, so it is served as
4
+ * an immutable static file and a changed icon set gets a new name.
5
+ */
6
+ import { createHash } from 'node:crypto';
7
+ import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ import { iconSpriteMarkup } from './icon-glyphs.js';
10
+ /** The public folder the sprite is written to and served from. */
11
+ export const ICON_SPRITE_FOLDER = '_bl';
12
+ const SPRITE_FILE = /^icons\.[0-9a-f]{12}\.svg$/;
13
+ export function buildIconSprite(site) {
14
+ const svg = iconSpriteMarkup(site);
15
+ const hash = createHash('sha256').update(svg).digest('hex').slice(0, 12);
16
+ return { svg, hash, href: `/${ICON_SPRITE_FOLDER}/icons.${hash}.svg` };
17
+ }
18
+ /**
19
+ * Writes the sprite to `public/_bl/icons.<hash>.svg` under the project root and removes earlier sprites there, so the
20
+ * folder holds the current file only. Leaves an unchanged file untouched. Add `public/_bl/` to `.gitignore`: every
21
+ * build writes it again.
22
+ */
23
+ export function writeIconSprite(projectRoot, site) {
24
+ const sprite = buildIconSprite(site);
25
+ const folder = join(projectRoot, 'public', ICON_SPRITE_FOLDER);
26
+ mkdirSync(folder, { recursive: true });
27
+ const name = `icons.${sprite.hash}.svg`;
28
+ for (const entry of readdirSync(folder)) {
29
+ if (entry !== name && SPRITE_FILE.test(entry))
30
+ rmSync(join(folder, entry), { force: true });
31
+ }
32
+ const path = join(folder, name);
33
+ let current = null;
34
+ try {
35
+ current = readFileSync(path, 'utf8');
36
+ }
37
+ catch {
38
+ current = null;
39
+ }
40
+ if (current !== sprite.svg)
41
+ writeFileSync(path, sprite.svg);
42
+ return sprite;
43
+ }
44
+ //# sourceMappingURL=icon-sprite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icon-sprite.js","sourceRoot":"","sources":["../../src/ui-kit/icon-sprite.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACrF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,gBAAgB,EAAoB,MAAM,kBAAkB,CAAA;AAErE,kEAAkE;AAClE,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,CAAA;AACvC,MAAM,WAAW,GAAG,4BAA4B,CAAA;AAWhD,MAAM,UAAU,eAAe,CAAC,IAAkB;IAChD,MAAM,GAAG,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAA;IAClC,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IACxE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,kBAAkB,UAAU,IAAI,MAAM,EAAE,CAAA;AACxE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,WAAmB,EAAE,IAAkB;IACrE,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;IACpC,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,QAAQ,EAAE,kBAAkB,CAAC,CAAA;IAC9D,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACtC,MAAM,IAAI,GAAG,SAAS,MAAM,CAAC,IAAI,MAAM,CAAA;IACvC,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;QACxC,IAAI,KAAK,KAAK,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IAC7F,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IAC/B,IAAI,OAAO,GAAkB,IAAI,CAAA;IACjC,IAAI,CAAC;QACH,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,IAAI,CAAA;IAChB,CAAC;IACD,IAAI,OAAO,KAAK,MAAM,CAAC,GAAG;QAAE,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAA;IAC3D,OAAO,MAAM,CAAA;AACf,CAAC","sourcesContent":["/**\n * Builds the icon sprite file (Node only; `defineBrandlabNextConfig({ icons })` calls it when the config loads). The\n * sprite holds every library icon and the site's own, and its name carries a hash of its content, so it is served as\n * an immutable static file and a changed icon set gets a new name.\n */\nimport { createHash } from 'node:crypto'\nimport { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { iconSpriteMarkup, type SiteIconSet } from './icon-glyphs.js'\n\n/** The public folder the sprite is written to and served from. */\nexport const ICON_SPRITE_FOLDER = '_bl'\nconst SPRITE_FILE = /^icons\\.[0-9a-f]{12}\\.svg$/\n\nexport interface IconSprite {\n /** The sprite's markup. */\n readonly svg: string\n /** The first 12 hex characters of the markup's SHA-256. */\n readonly hash: string\n /** The served path, for example `/_bl/icons.0123456789ab.svg`. */\n readonly href: string\n}\n\nexport function buildIconSprite(site?: SiteIconSet): IconSprite {\n const svg = iconSpriteMarkup(site)\n const hash = createHash('sha256').update(svg).digest('hex').slice(0, 12)\n return { svg, hash, href: `/${ICON_SPRITE_FOLDER}/icons.${hash}.svg` }\n}\n\n/**\n * Writes the sprite to `public/_bl/icons.<hash>.svg` under the project root and removes earlier sprites there, so the\n * folder holds the current file only. Leaves an unchanged file untouched. Add `public/_bl/` to `.gitignore`: every\n * build writes it again.\n */\nexport function writeIconSprite(projectRoot: string, site?: SiteIconSet): IconSprite {\n const sprite = buildIconSprite(site)\n const folder = join(projectRoot, 'public', ICON_SPRITE_FOLDER)\n mkdirSync(folder, { recursive: true })\n const name = `icons.${sprite.hash}.svg`\n for (const entry of readdirSync(folder)) {\n if (entry !== name && SPRITE_FILE.test(entry)) rmSync(join(folder, entry), { force: true })\n }\n const path = join(folder, name)\n let current: string | null = null\n try {\n current = readFileSync(path, 'utf8')\n } catch {\n current = null\n }\n if (current !== sprite.svg) writeFileSync(path, sprite.svg)\n return sprite\n}\n"]}
@@ -1,10 +1,25 @@
1
+ /**
2
+ * The ui-kit's icons (no icon font). Every icon is decorative (`aria-hidden`): the control around it carries the
3
+ * accessible name. Stroke and fill use `currentColor`, so a skin colours them through its classes or tokens.
4
+ * `data-icon` names the glyph for skins and tests.
5
+ *
6
+ * From 3.14.0 an icon is `<svg aria-hidden="true" viewBox data-icon><use href="/_bl/icons.<hash>.svg#name"/></svg>`:
7
+ * the glyphs live once in a static, immutable sprite that `defineBrandlabNextConfig({ icons })` builds, instead of
8
+ * once per use in the HTML and the RSC payload. Without the sprite (tests, a site that has not opted in) the same
9
+ * `<svg>` carries the glyph inline.
10
+ */
1
11
  import type { UiSocialKind } from './types.js';
12
+ import { type IconGlyph, type SiteIconSet, type UiIconName } from './icon-glyphs.js';
13
+ export { UI_ICON_NAMES, type UiIconName } from './icon-glyphs.js';
2
14
  export interface IconProps {
3
15
  readonly className?: string | undefined;
4
16
  }
5
- export declare const UI_ICON_NAMES: readonly ["phone", "chevron", "arrow", "arrow-up", "menu", "close", "star", "check", "camera", "mail", "facebook", "instagram", "google", "linkedin", "youtube", "tiktok"];
6
- export type UiIconName = (typeof UI_ICON_NAMES)[number];
7
- /** One icon from the set by name. */
17
+ /**
18
+ * The sprite's path when `defineBrandlabNextConfig({ icons })` built one, or undefined. Next inlines the variable into
19
+ * the server and browser bundles, so both render the same markup.
20
+ */
21
+ export declare function iconSpriteHref(): string | undefined;
22
+ /** One icon from the library set by name. */
8
23
  export declare function Icon({ name, className }: IconProps & {
9
24
  name: UiIconName;
10
25
  }): import("react/jsx-runtime").JSX.Element;
@@ -12,4 +27,24 @@ export declare function Icon({ name, className }: IconProps & {
12
27
  export declare function SocialIcon({ kind, className }: IconProps & {
13
28
  kind: UiSocialKind;
14
29
  }): import("react/jsx-runtime").JSX.Element;
30
+ /**
31
+ * The component for a site's own icons, typed on its names. The same markup as `Icon`: a `<use>` into the shared
32
+ * sprite once the config lists the set in `icons.site`, the glyph inline otherwise.
33
+ *
34
+ * ```tsx
35
+ * export const SiteIcon = createSiteIcon(siteIcons) // src/site/ui.tsx
36
+ * <SiteIcon name="leaf" />
37
+ * ```
38
+ */
39
+ export declare function createSiteIcon<Name extends string>(icons: SiteIconSet<Name>): ({ name, className }: IconProps & {
40
+ name: Name;
41
+ }) => import("react/jsx-runtime").JSX.Element;
42
+ /**
43
+ * The one star the rating row repeats: its `<use>` reference into the sprite, or its glyph for the inline fallback.
44
+ * `RatingStars` places five of them in one `<svg>`.
45
+ */
46
+ export declare function starGlyph(): {
47
+ readonly href?: string;
48
+ readonly glyph: IconGlyph;
49
+ };
15
50
  //# sourceMappingURL=icons.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../src/ui-kit/icons.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACxC;AAED,eAAO,MAAM,aAAa,4KAiBhB,CAAA;AACV,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAA;AAwGvD,qCAAqC;AACrC,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,2CAOzE;AAED,sDAAsD;AACtD,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,2CAEjF"}
1
+ {"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../src/ui-kit/icons.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAEjE,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACxC;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,GAAG,SAAS,CAGnD;AAgBD,6CAA6C;AAC7C,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,2CAEzE;AAED,sDAAsD;AACtD,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,2CAEjF;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,SAAS,MAAM,EAAE,KAAK,EAAE,WAAW,CAAC,IAAI,CAAC,yBACnC,SAAS,GAAG;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,6CAMlE;AAED;;;GAGG;AACH,wBAAgB,SAAS,IAAI;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;CAAE,CAGjF"}
@@ -1,72 +1,55 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- export const UI_ICON_NAMES = [
3
- 'phone',
4
- 'chevron',
5
- 'arrow',
6
- 'arrow-up',
7
- 'menu',
8
- 'close',
9
- 'star',
10
- 'check',
11
- 'camera',
12
- 'mail',
13
- 'facebook',
14
- 'instagram',
15
- 'google',
16
- 'linkedin',
17
- 'youtube',
18
- 'tiktok',
19
- ];
20
- function Svg({ name, viewBox = '0 0 24 24', className, children }) {
21
- return (_jsx("svg", { "aria-hidden": "true", focusable: "false", viewBox: viewBox, className: className, "data-icon": name, children: children }));
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { DEFAULT_ICON_VIEW_BOX, UI_ICON_GLYPHS, } from './icon-glyphs.js';
3
+ export { UI_ICON_NAMES } from './icon-glyphs.js';
4
+ /**
5
+ * The sprite's path when `defineBrandlabNextConfig({ icons })` built one, or undefined. Next inlines the variable into
6
+ * the server and browser bundles, so both render the same markup.
7
+ */
8
+ export function iconSpriteHref() {
9
+ const href = process.env.NEXT_PUBLIC_BL_ICON_SPRITE;
10
+ return typeof href === 'string' && /^\/[\w./-]+\.svg$/.test(href) ? href : undefined;
22
11
  }
23
- const stroke = { fill: 'none', stroke: 'currentColor', strokeLinecap: 'round', strokeLinejoin: 'round' };
24
- const GLYPHS = {
25
- phone: {
26
- body: (_jsx("path", { ...stroke, strokeWidth: "1.6", d: "M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 0 0 2.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106a1.125 1.125 0 0 0-1.173.417l-.97 1.293a1.125 1.125 0 0 1-1.21.38 12.035 12.035 0 0 1-7.143-7.143 1.125 1.125 0 0 1 .38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 0 0-1.091-.852H4.5A2.25 2.25 0 0 0 2.25 4.5v2.25Z" })),
27
- },
28
- chevron: { body: _jsx("path", { ...stroke, strokeWidth: "1.8", d: "m6 9 6 6 6-6" }) },
29
- arrow: { body: _jsx("path", { ...stroke, strokeWidth: "1.6", d: "M13.5 4.5 21 12l-7.5 7.5M3 12h17.25" }) },
30
- 'arrow-up': { body: _jsx("path", { ...stroke, strokeWidth: "1.8", d: "M12 20V4m0 0-7 7m7-7 7 7" }) },
31
- menu: { body: _jsx("path", { ...stroke, strokeWidth: "1.7", d: "M4 7h16M4 12h16M4 17h16" }) },
32
- close: { body: _jsx("path", { ...stroke, strokeWidth: "1.7", d: "M6 18 18 6M6 6l12 12" }) },
33
- star: {
34
- viewBox: '0 0 20 20',
35
- body: _jsx("path", { fill: "currentColor", d: "M10 1.6 12.6 7l5.9.7-4.4 4 1.2 5.8L10 14.6l-5.3 2.9L5.9 11.7l-4.4-4L7.4 7 10 1.6Z" }),
36
- },
37
- check: { body: _jsx("path", { ...stroke, strokeWidth: "2", d: "m4.5 12.75 6 6 9-13.5" }) },
38
- camera: {
39
- body: (_jsxs(_Fragment, { children: [_jsx("path", { ...stroke, strokeWidth: "1.6", d: "M3 8.25A2.25 2.25 0 0 1 5.25 6h1.372c.516 0 .966-.351 1.091-.852l.38-1.52A1.125 1.125 0 0 1 9.184 2.7h5.632c.516 0 .966.351 1.091.852l.38 1.52c.125.5.575.852 1.091.852h1.372A2.25 2.25 0 0 1 21 8.25v9.75a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 18V8.25Z" }), _jsx("circle", { ...stroke, strokeWidth: "1.6", cx: "12", cy: "12.75", r: "3.75" })] })),
40
- },
41
- mail: {
42
- body: (_jsx("path", { ...stroke, strokeWidth: "1.6", d: "M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0-8.69 5.6a2.25 2.25 0 0 1-2.12 0l-8.69-5.6" })),
43
- },
44
- facebook: {
45
- body: (_jsx("path", { fill: "currentColor", d: "M9 8H6v4h3v12h5V12h3.642L18 8h-4V6.333C14 5.378 14.192 5 15.115 5H18V0h-3.808C10.596 0 9 1.583 9 4.615V8Z" })),
46
- },
47
- instagram: {
48
- body: (_jsxs(_Fragment, { children: [_jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "5", fill: "none", stroke: "currentColor", strokeWidth: "2" }), _jsx("circle", { cx: "12", cy: "12", r: "4.2", fill: "none", stroke: "currentColor", strokeWidth: "2" }), _jsx("circle", { cx: "17.4", cy: "6.6", r: "1.3", fill: "currentColor" })] })),
49
- },
50
- google: {
51
- body: (_jsx("path", { fill: "currentColor", d: "M21.6 12.23c0-.68-.06-1.33-.17-1.96H12v3.7h5.39a4.6 4.6 0 0 1-2 3.02v2.5h3.24c1.9-1.75 2.97-4.32 2.97-7.26ZM12 22c2.7 0 4.96-.9 6.62-2.43l-3.24-2.5c-.9.6-2.04.95-3.38.95-2.6 0-4.8-1.75-5.59-4.11H3.07v2.59A10 10 0 0 0 12 22Zm-5.59-8.09A6 6 0 0 1 6.1 12c0-.66.11-1.31.31-1.91V7.5H3.07A10 10 0 0 0 2 12c0 1.61.39 3.14 1.07 4.5l3.34-2.59ZM12 5.98c1.47 0 2.79.5 3.83 1.5l2.87-2.87A9.6 9.6 0 0 0 12 2a10 10 0 0 0-8.93 5.5l3.34 2.59C7.2 7.73 9.4 5.98 12 5.98Z" })),
52
- },
53
- linkedin: {
54
- body: (_jsx("path", { fill: "currentColor", d: "M4.98 3.5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5ZM3 9.75h4v11.5H3V9.75Zm6.5 0h3.83v1.57h.06c.53-1 1.84-2.07 3.79-2.07 4.05 0 4.8 2.67 4.8 6.13v5.87h-4v-5.2c0-1.24-.02-2.84-1.73-2.84-1.73 0-2 1.35-2 2.75v5.29h-4V9.75Z" })),
55
- },
56
- youtube: {
57
- body: (_jsx("path", { fill: "currentColor", d: "M23 7.2a3 3 0 0 0-2.1-2.1C19 4.6 12 4.6 12 4.6s-7 0-8.9.5A3 3 0 0 0 1 7.2 31 31 0 0 0 .5 12a31 31 0 0 0 .5 4.8 3 3 0 0 0 2.1 2.1c1.9.5 8.9.5 8.9.5s7 0 8.9-.5a3 3 0 0 0 2.1-2.1 31 31 0 0 0 .5-4.8 31 31 0 0 0-.5-4.8ZM9.75 15.02V8.98L15.5 12l-5.75 3.02Z" })),
58
- },
59
- tiktok: {
60
- body: (_jsx("path", { fill: "currentColor", d: "M16.6 2h-3.4v13.4a2.9 2.9 0 1 1-2.9-2.9c.3 0 .6 0 .9.1V9.1a6.3 6.3 0 1 0 5.4 6.3V8.6a8.1 8.1 0 0 0 4.4 1.3V6.5a4.5 4.5 0 0 1-4.4-4.5Z" })),
61
- },
62
- };
63
- /** One icon from the set by name. */
12
+ function SvgIcon({ name, glyph, className }) {
13
+ const viewBox = glyph.viewBox ?? DEFAULT_ICON_VIEW_BOX;
14
+ const sprite = iconSpriteHref();
15
+ if (sprite) {
16
+ return (_jsx("svg", { "aria-hidden": "true", viewBox: viewBox, className: className, "data-icon": name, children: _jsx("use", { href: `${sprite}#${name}` }) }));
17
+ }
18
+ // The fallback: the glyph inline. Library glyphs are constants; site glyphs passed defineSiteIcons' shape checks.
19
+ return _jsx("svg", { "aria-hidden": "true", viewBox: viewBox, className: className, "data-icon": name, dangerouslySetInnerHTML: { __html: glyph.body } });
20
+ }
21
+ /** One icon from the library set by name. */
64
22
  export function Icon({ name, className }) {
65
- const glyph = GLYPHS[name];
66
- return (_jsx(Svg, { name: name, viewBox: glyph.viewBox, className: className, children: glyph.body }));
23
+ return _jsx(SvgIcon, { name: name, glyph: UI_ICON_GLYPHS[name], className: className });
67
24
  }
68
25
  /** The icon for a social or Business Profile link. */
69
26
  export function SocialIcon({ kind, className }) {
70
27
  return _jsx(Icon, { name: kind, className: className });
71
28
  }
29
+ /**
30
+ * The component for a site's own icons, typed on its names. The same markup as `Icon`: a `<use>` into the shared
31
+ * sprite once the config lists the set in `icons.site`, the glyph inline otherwise.
32
+ *
33
+ * ```tsx
34
+ * export const SiteIcon = createSiteIcon(siteIcons) // src/site/ui.tsx
35
+ * <SiteIcon name="leaf" />
36
+ * ```
37
+ */
38
+ export function createSiteIcon(icons) {
39
+ function SiteIcon({ name, className }) {
40
+ const glyph = icons.glyphs[name];
41
+ if (!glyph)
42
+ throw new Error(`Site icon "${name}" is not in the registered set.`);
43
+ return _jsx(SvgIcon, { name: name, glyph: glyph, className: className });
44
+ }
45
+ return SiteIcon;
46
+ }
47
+ /**
48
+ * The one star the rating row repeats: its `<use>` reference into the sprite, or its glyph for the inline fallback.
49
+ * `RatingStars` places five of them in one `<svg>`.
50
+ */
51
+ export function starGlyph() {
52
+ const sprite = iconSpriteHref();
53
+ return sprite ? { href: `${sprite}#star`, glyph: UI_ICON_GLYPHS.star } : { glyph: UI_ICON_GLYPHS.star };
54
+ }
72
55
  //# sourceMappingURL=icons.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"icons.js","sourceRoot":"","sources":["../../src/ui-kit/icons.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * The ui-kit's inline SVG icon set (no icon font). Every icon is decorative (`aria-hidden`, not focusable): the\n * control around it carries the accessible name. Stroke and fill use `currentColor`, so a skin colours them through\n * its classes or tokens. `data-icon` names the glyph for skins and tests.\n */\nimport type { ReactNode } from 'react'\nimport type { UiSocialKind } from './types.js'\n\nexport interface IconProps {\n readonly className?: string | undefined\n}\n\nexport const UI_ICON_NAMES = [\n 'phone',\n 'chevron',\n 'arrow',\n 'arrow-up',\n 'menu',\n 'close',\n 'star',\n 'check',\n 'camera',\n 'mail',\n 'facebook',\n 'instagram',\n 'google',\n 'linkedin',\n 'youtube',\n 'tiktok',\n] as const\nexport type UiIconName = (typeof UI_ICON_NAMES)[number]\n\nfunction Svg({ name, viewBox = '0 0 24 24', className, children }: IconProps & { name: UiIconName; viewBox?: string; children: ReactNode }) {\n return (\n <svg aria-hidden=\"true\" focusable=\"false\" viewBox={viewBox} className={className} data-icon={name}>\n {children}\n </svg>\n )\n}\n\nconst stroke = { fill: 'none', stroke: 'currentColor', strokeLinecap: 'round', strokeLinejoin: 'round' } as const\n\nconst GLYPHS: Record<UiIconName, { viewBox?: string; body: ReactNode }> = {\n phone: {\n body: (\n <path\n {...stroke}\n strokeWidth=\"1.6\"\n d=\"M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 0 0 2.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106a1.125 1.125 0 0 0-1.173.417l-.97 1.293a1.125 1.125 0 0 1-1.21.38 12.035 12.035 0 0 1-7.143-7.143 1.125 1.125 0 0 1 .38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 0 0-1.091-.852H4.5A2.25 2.25 0 0 0 2.25 4.5v2.25Z\"\n />\n ),\n },\n chevron: { body: <path {...stroke} strokeWidth=\"1.8\" d=\"m6 9 6 6 6-6\" /> },\n arrow: { body: <path {...stroke} strokeWidth=\"1.6\" d=\"M13.5 4.5 21 12l-7.5 7.5M3 12h17.25\" /> },\n 'arrow-up': { body: <path {...stroke} strokeWidth=\"1.8\" d=\"M12 20V4m0 0-7 7m7-7 7 7\" /> },\n menu: { body: <path {...stroke} strokeWidth=\"1.7\" d=\"M4 7h16M4 12h16M4 17h16\" /> },\n close: { body: <path {...stroke} strokeWidth=\"1.7\" d=\"M6 18 18 6M6 6l12 12\" /> },\n star: {\n viewBox: '0 0 20 20',\n body: <path fill=\"currentColor\" d=\"M10 1.6 12.6 7l5.9.7-4.4 4 1.2 5.8L10 14.6l-5.3 2.9L5.9 11.7l-4.4-4L7.4 7 10 1.6Z\" />,\n },\n check: { body: <path {...stroke} strokeWidth=\"2\" d=\"m4.5 12.75 6 6 9-13.5\" /> },\n camera: {\n body: (\n <>\n <path\n {...stroke}\n strokeWidth=\"1.6\"\n d=\"M3 8.25A2.25 2.25 0 0 1 5.25 6h1.372c.516 0 .966-.351 1.091-.852l.38-1.52A1.125 1.125 0 0 1 9.184 2.7h5.632c.516 0 .966.351 1.091.852l.38 1.52c.125.5.575.852 1.091.852h1.372A2.25 2.25 0 0 1 21 8.25v9.75a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 18V8.25Z\"\n />\n <circle {...stroke} strokeWidth=\"1.6\" cx=\"12\" cy=\"12.75\" r=\"3.75\" />\n </>\n ),\n },\n mail: {\n body: (\n <path\n {...stroke}\n strokeWidth=\"1.6\"\n d=\"M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0-8.69 5.6a2.25 2.25 0 0 1-2.12 0l-8.69-5.6\"\n />\n ),\n },\n facebook: {\n body: (\n <path\n fill=\"currentColor\"\n d=\"M9 8H6v4h3v12h5V12h3.642L18 8h-4V6.333C14 5.378 14.192 5 15.115 5H18V0h-3.808C10.596 0 9 1.583 9 4.615V8Z\"\n />\n ),\n },\n instagram: {\n body: (\n <>\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" />\n <circle cx=\"12\" cy=\"12\" r=\"4.2\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" />\n <circle cx=\"17.4\" cy=\"6.6\" r=\"1.3\" fill=\"currentColor\" />\n </>\n ),\n },\n google: {\n body: (\n <path\n fill=\"currentColor\"\n d=\"M21.6 12.23c0-.68-.06-1.33-.17-1.96H12v3.7h5.39a4.6 4.6 0 0 1-2 3.02v2.5h3.24c1.9-1.75 2.97-4.32 2.97-7.26ZM12 22c2.7 0 4.96-.9 6.62-2.43l-3.24-2.5c-.9.6-2.04.95-3.38.95-2.6 0-4.8-1.75-5.59-4.11H3.07v2.59A10 10 0 0 0 12 22Zm-5.59-8.09A6 6 0 0 1 6.1 12c0-.66.11-1.31.31-1.91V7.5H3.07A10 10 0 0 0 2 12c0 1.61.39 3.14 1.07 4.5l3.34-2.59ZM12 5.98c1.47 0 2.79.5 3.83 1.5l2.87-2.87A9.6 9.6 0 0 0 12 2a10 10 0 0 0-8.93 5.5l3.34 2.59C7.2 7.73 9.4 5.98 12 5.98Z\"\n />\n ),\n },\n linkedin: {\n body: (\n <path\n fill=\"currentColor\"\n d=\"M4.98 3.5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5ZM3 9.75h4v11.5H3V9.75Zm6.5 0h3.83v1.57h.06c.53-1 1.84-2.07 3.79-2.07 4.05 0 4.8 2.67 4.8 6.13v5.87h-4v-5.2c0-1.24-.02-2.84-1.73-2.84-1.73 0-2 1.35-2 2.75v5.29h-4V9.75Z\"\n />\n ),\n },\n youtube: {\n body: (\n <path\n fill=\"currentColor\"\n d=\"M23 7.2a3 3 0 0 0-2.1-2.1C19 4.6 12 4.6 12 4.6s-7 0-8.9.5A3 3 0 0 0 1 7.2 31 31 0 0 0 .5 12a31 31 0 0 0 .5 4.8 3 3 0 0 0 2.1 2.1c1.9.5 8.9.5 8.9.5s7 0 8.9-.5a3 3 0 0 0 2.1-2.1 31 31 0 0 0 .5-4.8 31 31 0 0 0-.5-4.8ZM9.75 15.02V8.98L15.5 12l-5.75 3.02Z\"\n />\n ),\n },\n tiktok: {\n body: (\n <path\n fill=\"currentColor\"\n d=\"M16.6 2h-3.4v13.4a2.9 2.9 0 1 1-2.9-2.9c.3 0 .6 0 .9.1V9.1a6.3 6.3 0 1 0 5.4 6.3V8.6a8.1 8.1 0 0 0 4.4 1.3V6.5a4.5 4.5 0 0 1-4.4-4.5Z\"\n />\n ),\n },\n}\n\n/** One icon from the set by name. */\nexport function Icon({ name, className }: IconProps & { name: UiIconName }) {\n const glyph = GLYPHS[name]\n return (\n <Svg name={name} viewBox={glyph.viewBox} className={className}>\n {glyph.body}\n </Svg>\n )\n}\n\n/** The icon for a social or Business Profile link. */\nexport function SocialIcon({ kind, className }: IconProps & { kind: UiSocialKind }) {\n return <Icon name={kind} className={className} />\n}\n"]}
1
+ {"version":3,"file":"icons.js","sourceRoot":"","sources":["../../src/ui-kit/icons.tsx"],"names":[],"mappings":";AAWA,OAAO,EACL,qBAAqB,EACrB,cAAc,GAIf,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,aAAa,EAAmB,MAAM,kBAAkB,CAAA;AAMjE;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAA;IACnD,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;AACtF,CAAC;AAED,SAAS,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAkD;IACzF,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,qBAAqB,CAAA;IACtD,MAAM,MAAM,GAAG,cAAc,EAAE,CAAA;IAC/B,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,CACL,6BAAiB,MAAM,EAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,eAAa,IAAI,YAC7E,cAAK,IAAI,EAAE,GAAG,MAAM,IAAI,IAAI,EAAE,GAAI,GAC9B,CACP,CAAA;IACH,CAAC;IACD,kHAAkH;IAClH,OAAO,6BAAiB,MAAM,EAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,eAAa,IAAI,EAAE,uBAAuB,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,EAAE,GAAI,CAAA;AAC7I,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAoC;IACxE,OAAO,KAAC,OAAO,IAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,GAAI,CAAA;AACnF,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,UAAU,CAAC,EAAE,IAAI,EAAE,SAAS,EAAsC;IAChF,OAAO,KAAC,IAAI,IAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,GAAI,CAAA;AACnD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAsB,KAAwB;IAC1E,SAAS,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAA8B;QAC/D,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QAChC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,iCAAiC,CAAC,CAAA;QAChF,OAAO,KAAC,OAAO,IAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,GAAI,CAAA;IACpE,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS;IACvB,MAAM,MAAM,GAAG,cAAc,EAAE,CAAA;IAC/B,OAAO,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,EAAE,KAAK,EAAE,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,IAAI,EAAE,CAAA;AACzG,CAAC","sourcesContent":["/**\n * The ui-kit's icons (no icon font). Every icon is decorative (`aria-hidden`): the control around it carries the\n * accessible name. Stroke and fill use `currentColor`, so a skin colours them through its classes or tokens.\n * `data-icon` names the glyph for skins and tests.\n *\n * From 3.14.0 an icon is `<svg aria-hidden=\"true\" viewBox data-icon><use href=\"/_bl/icons.<hash>.svg#name\"/></svg>`:\n * the glyphs live once in a static, immutable sprite that `defineBrandlabNextConfig({ icons })` builds, instead of\n * once per use in the HTML and the RSC payload. Without the sprite (tests, a site that has not opted in) the same\n * `<svg>` carries the glyph inline.\n */\nimport type { UiSocialKind } from './types.js'\nimport {\n DEFAULT_ICON_VIEW_BOX,\n UI_ICON_GLYPHS,\n type IconGlyph,\n type SiteIconSet,\n type UiIconName,\n} from './icon-glyphs.js'\n\nexport { UI_ICON_NAMES, type UiIconName } from './icon-glyphs.js'\n\nexport interface IconProps {\n readonly className?: string | undefined\n}\n\n/**\n * The sprite's path when `defineBrandlabNextConfig({ icons })` built one, or undefined. Next inlines the variable into\n * the server and browser bundles, so both render the same markup.\n */\nexport function iconSpriteHref(): string | undefined {\n const href = process.env.NEXT_PUBLIC_BL_ICON_SPRITE\n return typeof href === 'string' && /^\\/[\\w./-]+\\.svg$/.test(href) ? href : undefined\n}\n\nfunction SvgIcon({ name, glyph, className }: IconProps & { name: string; glyph: IconGlyph }) {\n const viewBox = glyph.viewBox ?? DEFAULT_ICON_VIEW_BOX\n const sprite = iconSpriteHref()\n if (sprite) {\n return (\n <svg aria-hidden=\"true\" viewBox={viewBox} className={className} data-icon={name}>\n <use href={`${sprite}#${name}`} />\n </svg>\n )\n }\n // The fallback: the glyph inline. Library glyphs are constants; site glyphs passed defineSiteIcons' shape checks.\n return <svg aria-hidden=\"true\" viewBox={viewBox} className={className} data-icon={name} dangerouslySetInnerHTML={{ __html: glyph.body }} />\n}\n\n/** One icon from the library set by name. */\nexport function Icon({ name, className }: IconProps & { name: UiIconName }) {\n return <SvgIcon name={name} glyph={UI_ICON_GLYPHS[name]} className={className} />\n}\n\n/** The icon for a social or Business Profile link. */\nexport function SocialIcon({ kind, className }: IconProps & { kind: UiSocialKind }) {\n return <Icon name={kind} className={className} />\n}\n\n/**\n * The component for a site's own icons, typed on its names. The same markup as `Icon`: a `<use>` into the shared\n * sprite once the config lists the set in `icons.site`, the glyph inline otherwise.\n *\n * ```tsx\n * export const SiteIcon = createSiteIcon(siteIcons) // src/site/ui.tsx\n * <SiteIcon name=\"leaf\" />\n * ```\n */\nexport function createSiteIcon<Name extends string>(icons: SiteIconSet<Name>) {\n function SiteIcon({ name, className }: IconProps & { name: Name }) {\n const glyph = icons.glyphs[name]\n if (!glyph) throw new Error(`Site icon \"${name}\" is not in the registered set.`)\n return <SvgIcon name={name} glyph={glyph} className={className} />\n }\n return SiteIcon\n}\n\n/**\n * The one star the rating row repeats: its `<use>` reference into the sprite, or its glyph for the inline fallback.\n * `RatingStars` places five of them in one `<svg>`.\n */\nexport function starGlyph(): { readonly href?: string; readonly glyph: IconGlyph } {\n const sprite = iconSpriteHref()\n return sprite ? { href: `${sprite}#star`, glyph: UI_ICON_GLYPHS.star } : { glyph: UI_ICON_GLYPHS.star }\n}\n"]}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Prefetch by link role (W2, 3.13.0). Next's default `<Link>` prefetches the whole route when the link comes within
3
+ * about 200 px of the viewport, so a long page of suburb, footer and map links fetched 16 to 45 pages on a full scroll
4
+ * while visitors read 1.0 to 1.3 pages a session. Each link role now has one setting:
5
+ *
6
+ * - `auto` (Next's default): the top-level header links (7 at most, never the current page) and the main quote or
7
+ * contact action;
8
+ * - `intent`: in-content service, project and blog cards (8 at most, matching K11). They render `prefetch={false}`
9
+ * and `data-prefetch="intent"`; `IntentPrefetch` (mounted once in the root layout) prefetches one of them when a
10
+ * visitor points at, focuses or touches it;
11
+ * - `false`: header panels, the drawer, the footer, area and suburb lists, nearby suburbs, area maps, suburb finder
12
+ * results, other services in this suburb and any link inside a hidden container.
13
+ *
14
+ * The ready gate's W2 journey fails a page that prefetches more than 8 destinations on landing or 12 after a full
15
+ * scroll, or prefetches from an off role, a hidden link or the current page. It finds a site's own off-role
16
+ * containers by the shared section ids (`coverage`, `nearby-areas`, `area-map`), the area map's `data-area-map` or
17
+ * `data-bl-links` with one of `area`, `nearby`, `map`, `finder` or `other-services` on the list's container.
18
+ */
19
+ /** A link's prefetch setting: Next's default, on intent only, or off. */
20
+ export type LinkPrefetch = 'auto' | 'intent' | false;
21
+ export declare const LINK_PREFETCH_POLICY: Readonly<{
22
+ readonly headerTopLevel: "auto";
23
+ readonly primaryAction: "auto";
24
+ readonly contentCard: "intent";
25
+ readonly headerPanel: false;
26
+ readonly drawer: false;
27
+ readonly footer: false;
28
+ readonly areaList: false;
29
+ readonly nearbySuburbs: false;
30
+ readonly areaMap: false;
31
+ readonly finderResults: false;
32
+ readonly otherServicesInSuburb: false;
33
+ readonly hiddenContainer: false;
34
+ }>;
35
+ export type LinkPrefetchRole = keyof typeof LINK_PREFETCH_POLICY;
36
+ /** The most links of each prefetching role on one page, and W2's page limits. */
37
+ export declare const LINK_PREFETCH_LIMITS: Readonly<{
38
+ headerTopLevel: 7;
39
+ contentCard: 8;
40
+ landingDestinations: 8;
41
+ scrolledDestinations: 12;
42
+ }>;
43
+ /** The marker `IntentPrefetch` listens for. */
44
+ export declare const INTENT_PREFETCH_SELECTOR = "a[data-prefetch=\"intent\"]";
45
+ /** The `next/link` props for a setting: nothing for `auto`, prefetch off for `false`, and off plus the marker for `intent`. */
46
+ export declare function linkPrefetchProps(prefetch: LinkPrefetch | undefined): {
47
+ prefetch?: false;
48
+ 'data-prefetch'?: 'intent';
49
+ };
50
+ //# sourceMappingURL=prefetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefetch.d.ts","sourceRoot":"","sources":["../../src/ui-kit/prefetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,yEAAyE;AACzE,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAA;AAEpD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;EAaiB,CAAA;AAElD,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,oBAAoB,CAAA;AAEhE,iFAAiF;AACjF,eAAO,MAAM,oBAAoB;;;;;EAK/B,CAAA;AAEF,+CAA+C;AAC/C,eAAO,MAAM,wBAAwB,gCAA8B,CAAA;AAEnE,+HAA+H;AAC/H,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,YAAY,GAAG,SAAS,GAAG;IAAE,QAAQ,CAAC,EAAE,KAAK,CAAC;IAAC,eAAe,CAAC,EAAE,QAAQ,CAAA;CAAE,CAItH"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Prefetch by link role (W2, 3.13.0). Next's default `<Link>` prefetches the whole route when the link comes within
3
+ * about 200 px of the viewport, so a long page of suburb, footer and map links fetched 16 to 45 pages on a full scroll
4
+ * while visitors read 1.0 to 1.3 pages a session. Each link role now has one setting:
5
+ *
6
+ * - `auto` (Next's default): the top-level header links (7 at most, never the current page) and the main quote or
7
+ * contact action;
8
+ * - `intent`: in-content service, project and blog cards (8 at most, matching K11). They render `prefetch={false}`
9
+ * and `data-prefetch="intent"`; `IntentPrefetch` (mounted once in the root layout) prefetches one of them when a
10
+ * visitor points at, focuses or touches it;
11
+ * - `false`: header panels, the drawer, the footer, area and suburb lists, nearby suburbs, area maps, suburb finder
12
+ * results, other services in this suburb and any link inside a hidden container.
13
+ *
14
+ * The ready gate's W2 journey fails a page that prefetches more than 8 destinations on landing or 12 after a full
15
+ * scroll, or prefetches from an off role, a hidden link or the current page. It finds a site's own off-role
16
+ * containers by the shared section ids (`coverage`, `nearby-areas`, `area-map`), the area map's `data-area-map` or
17
+ * `data-bl-links` with one of `area`, `nearby`, `map`, `finder` or `other-services` on the list's container.
18
+ */
19
+ export const LINK_PREFETCH_POLICY = Object.freeze({
20
+ headerTopLevel: 'auto',
21
+ primaryAction: 'auto',
22
+ contentCard: 'intent',
23
+ headerPanel: false,
24
+ drawer: false,
25
+ footer: false,
26
+ areaList: false,
27
+ nearbySuburbs: false,
28
+ areaMap: false,
29
+ finderResults: false,
30
+ otherServicesInSuburb: false,
31
+ hiddenContainer: false,
32
+ });
33
+ /** The most links of each prefetching role on one page, and W2's page limits. */
34
+ export const LINK_PREFETCH_LIMITS = Object.freeze({
35
+ headerTopLevel: 7,
36
+ contentCard: 8,
37
+ landingDestinations: 8,
38
+ scrolledDestinations: 12,
39
+ });
40
+ /** The marker `IntentPrefetch` listens for. */
41
+ export const INTENT_PREFETCH_SELECTOR = 'a[data-prefetch="intent"]';
42
+ /** The `next/link` props for a setting: nothing for `auto`, prefetch off for `false`, and off plus the marker for `intent`. */
43
+ export function linkPrefetchProps(prefetch) {
44
+ if (prefetch === 'intent')
45
+ return { prefetch: false, 'data-prefetch': 'intent' };
46
+ if (prefetch === false)
47
+ return { prefetch: false };
48
+ return {};
49
+ }
50
+ //# sourceMappingURL=prefetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefetch.js","sourceRoot":"","sources":["../../src/ui-kit/prefetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC;IAChD,cAAc,EAAE,MAAM;IACtB,aAAa,EAAE,MAAM;IACrB,WAAW,EAAE,QAAQ;IACrB,WAAW,EAAE,KAAK;IAClB,MAAM,EAAE,KAAK;IACb,MAAM,EAAE,KAAK;IACb,QAAQ,EAAE,KAAK;IACf,aAAa,EAAE,KAAK;IACpB,OAAO,EAAE,KAAK;IACd,aAAa,EAAE,KAAK;IACpB,qBAAqB,EAAE,KAAK;IAC5B,eAAe,EAAE,KAAK;CACyB,CAAC,CAAA;AAIlD,iFAAiF;AACjF,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC;IAChD,cAAc,EAAE,CAAC;IACjB,WAAW,EAAE,CAAC;IACd,mBAAmB,EAAE,CAAC;IACtB,oBAAoB,EAAE,EAAE;CACzB,CAAC,CAAA;AAEF,+CAA+C;AAC/C,MAAM,CAAC,MAAM,wBAAwB,GAAG,2BAA2B,CAAA;AAEnE,+HAA+H;AAC/H,MAAM,UAAU,iBAAiB,CAAC,QAAkC;IAClE,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAA;IAChF,IAAI,QAAQ,KAAK,KAAK;QAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;IAClD,OAAO,EAAE,CAAA;AACX,CAAC","sourcesContent":["/**\n * Prefetch by link role (W2, 3.13.0). Next's default `<Link>` prefetches the whole route when the link comes within\n * about 200 px of the viewport, so a long page of suburb, footer and map links fetched 16 to 45 pages on a full scroll\n * while visitors read 1.0 to 1.3 pages a session. Each link role now has one setting:\n *\n * - `auto` (Next's default): the top-level header links (7 at most, never the current page) and the main quote or\n * contact action;\n * - `intent`: in-content service, project and blog cards (8 at most, matching K11). They render `prefetch={false}`\n * and `data-prefetch=\"intent\"`; `IntentPrefetch` (mounted once in the root layout) prefetches one of them when a\n * visitor points at, focuses or touches it;\n * - `false`: header panels, the drawer, the footer, area and suburb lists, nearby suburbs, area maps, suburb finder\n * results, other services in this suburb and any link inside a hidden container.\n *\n * The ready gate's W2 journey fails a page that prefetches more than 8 destinations on landing or 12 after a full\n * scroll, or prefetches from an off role, a hidden link or the current page. It finds a site's own off-role\n * containers by the shared section ids (`coverage`, `nearby-areas`, `area-map`), the area map's `data-area-map` or\n * `data-bl-links` with one of `area`, `nearby`, `map`, `finder` or `other-services` on the list's container.\n */\n\n/** A link's prefetch setting: Next's default, on intent only, or off. */\nexport type LinkPrefetch = 'auto' | 'intent' | false\n\nexport const LINK_PREFETCH_POLICY = Object.freeze({\n headerTopLevel: 'auto',\n primaryAction: 'auto',\n contentCard: 'intent',\n headerPanel: false,\n drawer: false,\n footer: false,\n areaList: false,\n nearbySuburbs: false,\n areaMap: false,\n finderResults: false,\n otherServicesInSuburb: false,\n hiddenContainer: false,\n} as const satisfies Record<string, LinkPrefetch>)\n\nexport type LinkPrefetchRole = keyof typeof LINK_PREFETCH_POLICY\n\n/** The most links of each prefetching role on one page, and W2's page limits. */\nexport const LINK_PREFETCH_LIMITS = Object.freeze({\n headerTopLevel: 7,\n contentCard: 8,\n landingDestinations: 8,\n scrolledDestinations: 12,\n})\n\n/** The marker `IntentPrefetch` listens for. */\nexport const INTENT_PREFETCH_SELECTOR = 'a[data-prefetch=\"intent\"]'\n\n/** The `next/link` props for a setting: nothing for `auto`, prefetch off for `false`, and off plus the marker for `intent`. */\nexport function linkPrefetchProps(prefetch: LinkPrefetch | undefined): { prefetch?: false; 'data-prefetch'?: 'intent' } {\n if (prefetch === 'intent') return { prefetch: false, 'data-prefetch': 'intent' }\n if (prefetch === false) return { prefetch: false }\n return {}\n}\n"]}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `Badge` and `Chip` (3.14.0): a short label as text, never an image of text, with an optional decorative icon.
3
+ * Server components. A badge marks a fact ("Licensed", "Fully insured"); a chip names a filter or a tag, and is the only
4
+ * one a skin may make a pill (Romie, Absolute: "too many round elements").
5
+ *
6
+ * Both are rendered once per entry of a list, so their parts take no class (3.14.0 repeated slots): the site styles
7
+ * `[data-bl="badge"]` and `[data-bl="chip"]` once in `src/site/skin.css`.
8
+ */
9
+ import { type UiIconName } from '../icons.js';
10
+ import type { RepeatedSlotOf } from '../repeated-slots.js';
11
+ import { type SkinEntry } from '../skin.js';
12
+ export type BadgeSlot = RepeatedSlotOf<'badge'>;
13
+ export type BadgeSkin = SkinEntry<BadgeSlot, never, Record<never, never>, BadgeSlot>;
14
+ export type ChipSlot = RepeatedSlotOf<'chip'>;
15
+ export type ChipSkin = SkinEntry<ChipSlot, never, Record<never, never>, ChipSlot>;
16
+ export interface BadgeProps extends BadgeSkin {
17
+ /** The label, in the site's words. */
18
+ readonly children: string;
19
+ /** A decorative icon before the label. */
20
+ readonly icon?: UiIconName;
21
+ }
22
+ export type ChipProps = BadgeProps & ChipSkin;
23
+ export declare function Badge({ children, icon }: BadgeProps): import("react/jsx-runtime").JSX.Element;
24
+ export declare function Chip({ children, icon }: ChipProps): import("react/jsx-runtime").JSX.Element;
25
+ //# sourceMappingURL=badge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"badge.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/primitives/badge.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AAEzD,MAAM,MAAM,SAAS,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;AAC/C,MAAM,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,SAAS,CAAC,CAAA;AACpF,MAAM,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,CAAA;AAC7C,MAAM,MAAM,QAAQ,GAAG,SAAS,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAA;AAEjF,MAAM,WAAW,UAAW,SAAQ,SAAS;IAC3C,sCAAsC;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAA;CAC3B;AAED,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,QAAQ,CAAA;AAe7C,wBAAgB,KAAK,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,UAAU,2CAMnD;AAED,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,SAAS,2CAMjD"}
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * `Badge` and `Chip` (3.14.0): a short label as text, never an image of text, with an optional decorative icon.
4
+ * Server components. A badge marks a fact ("Licensed", "Fully insured"); a chip names a filter or a tag, and is the only
5
+ * one a skin may make a pill (Romie, Absolute: "too many round elements").
6
+ *
7
+ * Both are rendered once per entry of a list, so their parts take no class (3.14.0 repeated slots): the site styles
8
+ * `[data-bl="badge"]` and `[data-bl="chip"]` once in `src/site/skin.css`.
9
+ */
10
+ import { Icon } from '../icons.js';
11
+ import { repeatedSlot } from '../skin.js';
12
+ function Label({ hook, children, icon }) {
13
+ return (_jsxs("span", { "data-bl": hook, children: [icon ? (_jsx("span", { ...repeatedSlot('icon'), children: _jsx(Icon, { name: icon }) })) : null, _jsx("span", { ...repeatedSlot('label'), children: children })] }));
14
+ }
15
+ export function Badge({ children, icon }) {
16
+ return (_jsx(Label, { hook: "badge", icon: icon, children: children }));
17
+ }
18
+ export function Chip({ children, icon }) {
19
+ return (_jsx(Label, { hook: "chip", icon: icon, children: children }));
20
+ }
21
+ //# sourceMappingURL=badge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"badge.js","sourceRoot":"","sources":["../../../src/ui-kit/primitives/badge.tsx"],"names":[],"mappings":";AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,IAAI,EAAmB,MAAM,aAAa,CAAA;AAEnD,OAAO,EAAE,YAAY,EAAkB,MAAM,YAAY,CAAA;AAgBzD,SAAS,KAAK,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAA+E;IAClH,OAAO,CACL,2BAAe,IAAI,aAChB,IAAI,CAAC,CAAC,CAAC,CACN,kBAAU,YAAY,CAAC,MAAM,CAAC,YAC5B,KAAC,IAAI,IAAC,IAAI,EAAE,IAAI,GAAI,GACf,CACR,CAAC,CAAC,CAAC,IAAI,EACR,kBAAU,YAAY,CAAC,OAAO,CAAC,YAAG,QAAQ,GAAQ,IAC7C,CACR,CAAA;AACH,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAc;IAClD,OAAO,CACL,KAAC,KAAK,IAAC,IAAI,EAAC,OAAO,EAAC,IAAI,EAAE,IAAI,YAC3B,QAAQ,GACH,CACT,CAAA;AACH,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAa;IAChD,OAAO,CACL,KAAC,KAAK,IAAC,IAAI,EAAC,MAAM,EAAC,IAAI,EAAE,IAAI,YAC1B,QAAQ,GACH,CACT,CAAA;AACH,CAAC","sourcesContent":["/**\n * `Badge` and `Chip` (3.14.0): a short label as text, never an image of text, with an optional decorative icon.\n * Server components. A badge marks a fact (\"Licensed\", \"Fully insured\"); a chip names a filter or a tag, and is the only\n * one a skin may make a pill (Romie, Absolute: \"too many round elements\").\n *\n * Both are rendered once per entry of a list, so their parts take no class (3.14.0 repeated slots): the site styles\n * `[data-bl=\"badge\"]` and `[data-bl=\"chip\"]` once in `src/site/skin.css`.\n */\nimport { Icon, type UiIconName } from '../icons.js'\nimport type { RepeatedSlotOf } from '../repeated-slots.js'\nimport { repeatedSlot, type SkinEntry } from '../skin.js'\n\nexport type BadgeSlot = RepeatedSlotOf<'badge'>\nexport type BadgeSkin = SkinEntry<BadgeSlot, never, Record<never, never>, BadgeSlot>\nexport type ChipSlot = RepeatedSlotOf<'chip'>\nexport type ChipSkin = SkinEntry<ChipSlot, never, Record<never, never>, ChipSlot>\n\nexport interface BadgeProps extends BadgeSkin {\n /** The label, in the site's words. */\n readonly children: string\n /** A decorative icon before the label. */\n readonly icon?: UiIconName\n}\n\nexport type ChipProps = BadgeProps & ChipSkin\n\nfunction Label({ hook, children, icon }: { hook: 'badge' | 'chip'; children: string; icon?: UiIconName | undefined }) {\n return (\n <span data-bl={hook}>\n {icon ? (\n <span {...repeatedSlot('icon')}>\n <Icon name={icon} />\n </span>\n ) : null}\n <span {...repeatedSlot('label')}>{children}</span>\n </span>\n )\n}\n\nexport function Badge({ children, icon }: BadgeProps) {\n return (\n <Label hook=\"badge\" icon={icon}>\n {children}\n </Label>\n )\n}\n\nexport function Chip({ children, icon }: ChipProps) {\n return (\n <Label hook=\"chip\" icon={icon}>\n {children}\n </Label>\n )\n}\n"]}
@@ -0,0 +1,27 @@
1
+ import type { BreadcrumbItem } from '@brandlab-digital/site-core/breadcrumbs';
2
+ import { type Schema } from '@brandlab-digital/site-core/schema';
3
+ import type { BrandlabRouteBreadcrumbOutput } from '../../breadcrumbs.js';
4
+ import type { RepeatedSlotOf } from '../repeated-slots.js';
5
+ import { type SkinEntry } from '../skin.js';
6
+ export type BreadcrumbsSlot = 'root' | 'list' | 'item' | 'link' | 'current';
7
+ /** Rendered once per crumb: styled from the skin stylesheet, never by class. */
8
+ export type BreadcrumbsRepeatedSlot = RepeatedSlotOf<'breadcrumbs'>;
9
+ export type BreadcrumbsSkin = SkinEntry<BreadcrumbsSlot, never, Record<never, never>, BreadcrumbsRepeatedSlot>;
10
+ /** The breadcrumb landmark's accessible name. Locked. */
11
+ export declare const BREADCRUMBS_LABEL = "Breadcrumb";
12
+ /** A trail with the site's URL, for a page without a route catalogue. */
13
+ export interface BreadcrumbTrail {
14
+ readonly items: readonly BreadcrumbItem[];
15
+ /** The site's origin, for example `https://www.aatreeservices.com.au`. */
16
+ readonly siteUrl: string;
17
+ }
18
+ export interface BreadcrumbsProps extends BreadcrumbsSkin {
19
+ /** The route lineage's output (`createRouteBreadcrumbOutput`) or a trail with the site's URL. Home first. */
20
+ readonly trail: BrandlabRouteBreadcrumbOutput | BreadcrumbTrail;
21
+ /** Opt in to writing the `BreadcrumbList` JSON-LD beside the trail. Off by default: the page's schema builder emits it (F14). */
22
+ readonly jsonLd?: boolean;
23
+ }
24
+ /** The `BreadcrumbList` for a trail: the route output's own, or one built from the trail and the site's URL. */
25
+ export declare function breadcrumbsSchema(trail: BreadcrumbsProps['trail']): Schema<unknown>;
26
+ export declare function Breadcrumbs({ trail, jsonLd, classes }: BreadcrumbsProps): import("react/jsx-runtime").JSX.Element | null;
27
+ //# sourceMappingURL=breadcrumbs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"breadcrumbs.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/primitives/breadcrumbs.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAA;AAE7E,OAAO,EAA2C,KAAK,MAAM,EAAE,MAAM,oCAAoC,CAAA;AACzG,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,sBAAsB,CAAA;AAEzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AAE/D,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;AAC3E,gFAAgF;AAChF,MAAM,MAAM,uBAAuB,GAAG,cAAc,CAAC,aAAa,CAAC,CAAA;AACnE,MAAM,MAAM,eAAe,GAAG,SAAS,CAAC,eAAe,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,uBAAuB,CAAC,CAAA;AAE9G,yDAAyD;AACzD,eAAO,MAAM,iBAAiB,eAAe,CAAA;AAE7C,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAA;IACzC,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,6GAA6G;IAC7G,QAAQ,CAAC,KAAK,EAAE,6BAA6B,GAAG,eAAe,CAAA;IAC/D,iIAAiI;IACjI,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAC1B;AAMD,gHAAgH;AAChH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAGnF;AAED,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,MAAc,EAAE,OAAO,EAAE,EAAE,gBAAgB,kDA4B/E"}
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * `Breadcrumbs` (F11, 3.14.0): the visible trail and its `BreadcrumbList` JSON-LD from one source. Server component.
4
+ * A `nav` named "Breadcrumb" (locked) holding an ordered list; earlier crumbs are links with prefetch off, the current
5
+ * page is plain text with `aria-current="page"`. The list is lean: no separator elements and no icons, so a long trail
6
+ * costs a few bytes per crumb. The separator is drawn by `structure.css` (a `/` with empty alternative text), which a
7
+ * skin may restyle on `[data-bl='breadcrumbs'] [data-slot='item'] + [data-slot='item']::before`.
8
+ *
9
+ * Components never write JSON-LD (F14): the page's schema builder emits the `BreadcrumbList`, so `jsonLd` is off by
10
+ * default. `breadcrumbsSchema(trail)` gives the page the matching schema from the same trail, through the site-core
11
+ * builders (`buildBreadcrumbListSchema`), either from a route lineage (`createRouteBreadcrumbOutput`, which also checks
12
+ * the two agree) or from a trail and the site's URL. A page without its own emitter may opt in with `jsonLd`. The
13
+ * trail is checked either way, so a bad trail fails the build whether or not the JSON-LD is written here.
14
+ */
15
+ import Link from 'next/link';
16
+ import { getAbsoluteUrl } from '@brandlab-digital/site-core/metadata';
17
+ import { buildBreadcrumbListSchema, createSchema } from '@brandlab-digital/site-core/schema';
18
+ import { SchemaScript } from '../../schema.js';
19
+ import { repeatedSlot, slot } from '../skin.js';
20
+ /** The breadcrumb landmark's accessible name. Locked. */
21
+ export const BREADCRUMBS_LABEL = 'Breadcrumb';
22
+ function isRouteOutput(trail) {
23
+ return 'schema' in trail;
24
+ }
25
+ /** The `BreadcrumbList` for a trail: the route output's own, or one built from the trail and the site's URL. */
26
+ export function breadcrumbsSchema(trail) {
27
+ if (isRouteOutput(trail))
28
+ return trail.schema;
29
+ return createSchema(buildBreadcrumbListSchema([...trail.items], { absoluteUrl: (path) => getAbsoluteUrl(trail.siteUrl, path) }));
30
+ }
31
+ export function Breadcrumbs({ trail, jsonLd = false, classes }) {
32
+ const items = trail.items;
33
+ if (items.length === 0)
34
+ return null;
35
+ // Built even when not written, so a trail Google would reject fails here too.
36
+ const schema = breadcrumbsSchema(trail);
37
+ const last = items.length - 1;
38
+ return (_jsxs(_Fragment, { children: [_jsx("nav", { "aria-label": BREADCRUMBS_LABEL, "data-bl": "breadcrumbs", className: classes?.root, children: _jsx("ol", { ...slot(classes, 'list'), children: items.map((item, index) => (_jsx("li", { ...repeatedSlot('item'), children: index < last && item.href ? (_jsx(Link, { href: item.href, prefetch: false, ...repeatedSlot('link'), children: item.name })) : (_jsx("span", { ...repeatedSlot('current'), "aria-current": index === last ? 'page' : undefined, children: item.name })) }, `${item.href ?? 'current'}:${item.name}`))) }) }), jsonLd ? _jsx(SchemaScript, { id: "breadcrumbs", schema: schema }) : null] }));
39
+ }
40
+ //# sourceMappingURL=breadcrumbs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"breadcrumbs.js","sourceRoot":"","sources":["../../../src/ui-kit/primitives/breadcrumbs.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAA;AACrE,OAAO,EAAE,yBAAyB,EAAE,YAAY,EAAe,MAAM,oCAAoC,CAAA;AAEzG,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAE9C,OAAO,EAAE,YAAY,EAAE,IAAI,EAAkB,MAAM,YAAY,CAAA;AAO/D,yDAAyD;AACzD,MAAM,CAAC,MAAM,iBAAiB,GAAG,YAAY,CAAA;AAgB7C,SAAS,aAAa,CAAC,KAAgC;IACrD,OAAO,QAAQ,IAAI,KAAK,CAAA;AAC1B,CAAC;AAED,gHAAgH;AAChH,MAAM,UAAU,iBAAiB,CAAC,KAAgC;IAChE,IAAI,aAAa,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,MAAM,CAAA;IAC7C,OAAO,YAAY,CAAC,yBAAyB,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;AAClI,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,EAAE,OAAO,EAAoB;IAC9E,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAA;IACzB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACnC,8EAA8E;IAC9E,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAA;IACvC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;IAC7B,OAAO,CACL,8BACE,4BAAiB,iBAAiB,aAAU,aAAa,EAAC,SAAS,EAAE,OAAO,EAAE,IAAI,YAChF,gBAAQ,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,YAC1B,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAC1B,gBAAuD,YAAY,CAAC,MAAM,CAAC,YACxE,KAAK,GAAG,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAC3B,KAAC,IAAI,IAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,KAAM,YAAY,CAAC,MAAM,CAAC,YAC7D,IAAI,CAAC,IAAI,GACL,CACR,CAAC,CAAC,CAAC,CACF,kBAAU,YAAY,CAAC,SAAS,CAAC,kBAAgB,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,YACjF,IAAI,CAAC,IAAI,GACL,CACR,IATM,GAAG,IAAI,CAAC,IAAI,IAAI,SAAS,IAAI,IAAI,CAAC,IAAI,EAAE,CAU5C,CACN,CAAC,GACC,GACD,EACL,MAAM,CAAC,CAAC,CAAC,KAAC,YAAY,IAAC,EAAE,EAAC,aAAa,EAAC,MAAM,EAAE,MAAM,GAAI,CAAC,CAAC,CAAC,IAAI,IACjE,CACJ,CAAA;AACH,CAAC","sourcesContent":["/**\n * `Breadcrumbs` (F11, 3.14.0): the visible trail and its `BreadcrumbList` JSON-LD from one source. Server component.\n * A `nav` named \"Breadcrumb\" (locked) holding an ordered list; earlier crumbs are links with prefetch off, the current\n * page is plain text with `aria-current=\"page\"`. The list is lean: no separator elements and no icons, so a long trail\n * costs a few bytes per crumb. The separator is drawn by `structure.css` (a `/` with empty alternative text), which a\n * skin may restyle on `[data-bl='breadcrumbs'] [data-slot='item'] + [data-slot='item']::before`.\n *\n * Components never write JSON-LD (F14): the page's schema builder emits the `BreadcrumbList`, so `jsonLd` is off by\n * default. `breadcrumbsSchema(trail)` gives the page the matching schema from the same trail, through the site-core\n * builders (`buildBreadcrumbListSchema`), either from a route lineage (`createRouteBreadcrumbOutput`, which also checks\n * the two agree) or from a trail and the site's URL. A page without its own emitter may opt in with `jsonLd`. The\n * trail is checked either way, so a bad trail fails the build whether or not the JSON-LD is written here.\n */\nimport Link from 'next/link'\nimport type { BreadcrumbItem } from '@brandlab-digital/site-core/breadcrumbs'\nimport { getAbsoluteUrl } from '@brandlab-digital/site-core/metadata'\nimport { buildBreadcrumbListSchema, createSchema, type Schema } from '@brandlab-digital/site-core/schema'\nimport type { BrandlabRouteBreadcrumbOutput } from '../../breadcrumbs.js'\nimport { SchemaScript } from '../../schema.js'\nimport type { RepeatedSlotOf } from '../repeated-slots.js'\nimport { repeatedSlot, slot, type SkinEntry } from '../skin.js'\n\nexport type BreadcrumbsSlot = 'root' | 'list' | 'item' | 'link' | 'current'\n/** Rendered once per crumb: styled from the skin stylesheet, never by class. */\nexport type BreadcrumbsRepeatedSlot = RepeatedSlotOf<'breadcrumbs'>\nexport type BreadcrumbsSkin = SkinEntry<BreadcrumbsSlot, never, Record<never, never>, BreadcrumbsRepeatedSlot>\n\n/** The breadcrumb landmark's accessible name. Locked. */\nexport const BREADCRUMBS_LABEL = 'Breadcrumb'\n\n/** A trail with the site's URL, for a page without a route catalogue. */\nexport interface BreadcrumbTrail {\n readonly items: readonly BreadcrumbItem[]\n /** The site's origin, for example `https://www.aatreeservices.com.au`. */\n readonly siteUrl: string\n}\n\nexport interface BreadcrumbsProps extends BreadcrumbsSkin {\n /** The route lineage's output (`createRouteBreadcrumbOutput`) or a trail with the site's URL. Home first. */\n readonly trail: BrandlabRouteBreadcrumbOutput | BreadcrumbTrail\n /** Opt in to writing the `BreadcrumbList` JSON-LD beside the trail. Off by default: the page's schema builder emits it (F14). */\n readonly jsonLd?: boolean\n}\n\nfunction isRouteOutput(trail: BreadcrumbsProps['trail']): trail is BrandlabRouteBreadcrumbOutput {\n return 'schema' in trail\n}\n\n/** The `BreadcrumbList` for a trail: the route output's own, or one built from the trail and the site's URL. */\nexport function breadcrumbsSchema(trail: BreadcrumbsProps['trail']): Schema<unknown> {\n if (isRouteOutput(trail)) return trail.schema\n return createSchema(buildBreadcrumbListSchema([...trail.items], { absoluteUrl: (path) => getAbsoluteUrl(trail.siteUrl, path) }))\n}\n\nexport function Breadcrumbs({ trail, jsonLd = false, classes }: BreadcrumbsProps) {\n const items = trail.items\n if (items.length === 0) return null\n // Built even when not written, so a trail Google would reject fails here too.\n const schema = breadcrumbsSchema(trail)\n const last = items.length - 1\n return (\n <>\n <nav aria-label={BREADCRUMBS_LABEL} data-bl=\"breadcrumbs\" className={classes?.root}>\n <ol {...slot(classes, 'list')}>\n {items.map((item, index) => (\n <li key={`${item.href ?? 'current'}:${item.name}`} {...repeatedSlot('item')}>\n {index < last && item.href ? (\n <Link href={item.href} prefetch={false} {...repeatedSlot('link')}>\n {item.name}\n </Link>\n ) : (\n <span {...repeatedSlot('current')} aria-current={index === last ? 'page' : undefined}>\n {item.name}\n </span>\n )}\n </li>\n ))}\n </ol>\n </nav>\n {jsonLd ? <SchemaScript id=\"breadcrumbs\" schema={schema} /> : null}\n </>\n )\n}\n"]}