@brandlab-digital/site-next 3.13.0 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +79 -0
  2. package/dist/contact/delivery.d.ts +81 -0
  3. package/dist/contact/delivery.d.ts.map +1 -0
  4. package/dist/contact/delivery.js +138 -0
  5. package/dist/contact/delivery.js.map +1 -0
  6. package/dist/contact/resend-webhook.d.ts +72 -0
  7. package/dist/contact/resend-webhook.d.ts.map +1 -0
  8. package/dist/contact/resend-webhook.js +300 -0
  9. package/dist/contact/resend-webhook.js.map +1 -0
  10. package/dist/contact/route-handler.d.ts +16 -0
  11. package/dist/contact/route-handler.d.ts.map +1 -1
  12. package/dist/contact/route-handler.js +8 -0
  13. package/dist/contact/route-handler.js.map +1 -1
  14. package/dist/contact/server-entry.d.ts +2 -0
  15. package/dist/contact/server-entry.d.ts.map +1 -1
  16. package/dist/contact/server-entry.js +2 -0
  17. package/dist/contact/server-entry.js.map +1 -1
  18. package/dist/contact/server.d.ts +26 -0
  19. package/dist/contact/server.d.ts.map +1 -1
  20. package/dist/contact/server.js +48 -3
  21. package/dist/contact/server.js.map +1 -1
  22. package/dist/next-config.d.ts +17 -1
  23. package/dist/next-config.d.ts.map +1 -1
  24. package/dist/next-config.js +36 -2
  25. package/dist/next-config.js.map +1 -1
  26. package/dist/ui-kit/brandlab-skin.d.ts +24 -1
  27. package/dist/ui-kit/brandlab-skin.d.ts.map +1 -1
  28. package/dist/ui-kit/brandlab-skin.js.map +1 -1
  29. package/dist/ui-kit/form/photo-upload.d.ts +4 -1
  30. package/dist/ui-kit/form/photo-upload.d.ts.map +1 -1
  31. package/dist/ui-kit/form/photo-upload.js +4 -4
  32. package/dist/ui-kit/form/photo-upload.js.map +1 -1
  33. package/dist/ui-kit/form/quote-form.d.ts +4 -1
  34. package/dist/ui-kit/form/quote-form.d.ts.map +1 -1
  35. package/dist/ui-kit/form/quote-form.js.map +1 -1
  36. package/dist/ui-kit/form/thank-you.d.ts +4 -1
  37. package/dist/ui-kit/form/thank-you.d.ts.map +1 -1
  38. package/dist/ui-kit/form/thank-you.js +2 -2
  39. package/dist/ui-kit/form/thank-you.js.map +1 -1
  40. package/dist/ui-kit/icon-glyphs.d.ts +41 -0
  41. package/dist/ui-kit/icon-glyphs.d.ts.map +1 -0
  42. package/dist/ui-kit/icon-glyphs.js +138 -0
  43. package/dist/ui-kit/icon-glyphs.js.map +1 -0
  44. package/dist/ui-kit/icon-sprite.d.ts +19 -0
  45. package/dist/ui-kit/icon-sprite.d.ts.map +1 -0
  46. package/dist/ui-kit/icon-sprite.js +44 -0
  47. package/dist/ui-kit/icon-sprite.js.map +1 -0
  48. package/dist/ui-kit/icons.d.ts +38 -3
  49. package/dist/ui-kit/icons.d.ts.map +1 -1
  50. package/dist/ui-kit/icons.js +47 -64
  51. package/dist/ui-kit/icons.js.map +1 -1
  52. package/dist/ui-kit/primitives/badge.d.ts +25 -0
  53. package/dist/ui-kit/primitives/badge.d.ts.map +1 -0
  54. package/dist/ui-kit/primitives/badge.js +21 -0
  55. package/dist/ui-kit/primitives/badge.js.map +1 -0
  56. package/dist/ui-kit/primitives/breadcrumbs.d.ts +27 -0
  57. package/dist/ui-kit/primitives/breadcrumbs.d.ts.map +1 -0
  58. package/dist/ui-kit/primitives/breadcrumbs.js +40 -0
  59. package/dist/ui-kit/primitives/breadcrumbs.js.map +1 -0
  60. package/dist/ui-kit/primitives/button.d.ts +32 -0
  61. package/dist/ui-kit/primitives/button.d.ts.map +1 -0
  62. package/dist/ui-kit/primitives/button.js +8 -0
  63. package/dist/ui-kit/primitives/button.js.map +1 -0
  64. package/dist/ui-kit/primitives/card.d.ts +40 -0
  65. package/dist/ui-kit/primitives/card.d.ts.map +1 -0
  66. package/dist/ui-kit/primitives/card.js +8 -0
  67. package/dist/ui-kit/primitives/card.js.map +1 -0
  68. package/dist/ui-kit/primitives/index.d.ts +11 -2
  69. package/dist/ui-kit/primitives/index.d.ts.map +1 -1
  70. package/dist/ui-kit/primitives/index.js +11 -2
  71. package/dist/ui-kit/primitives/index.js.map +1 -1
  72. package/dist/ui-kit/primitives/layout.d.ts +12 -0
  73. package/dist/ui-kit/primitives/layout.d.ts.map +1 -1
  74. package/dist/ui-kit/primitives/layout.js +20 -2
  75. package/dist/ui-kit/primitives/layout.js.map +1 -1
  76. package/dist/ui-kit/primitives/links.d.ts +15 -0
  77. package/dist/ui-kit/primitives/links.d.ts.map +1 -1
  78. package/dist/ui-kit/primitives/links.js +5 -1
  79. package/dist/ui-kit/primitives/links.js.map +1 -1
  80. package/dist/ui-kit/primitives/lists.d.ts +43 -0
  81. package/dist/ui-kit/primitives/lists.d.ts.map +1 -0
  82. package/dist/ui-kit/primitives/lists.js +34 -0
  83. package/dist/ui-kit/primitives/lists.js.map +1 -0
  84. package/dist/ui-kit/primitives/media-frame.d.ts +28 -0
  85. package/dist/ui-kit/primitives/media-frame.d.ts.map +1 -0
  86. package/dist/ui-kit/primitives/media-frame.js +58 -0
  87. package/dist/ui-kit/primitives/media-frame.js.map +1 -0
  88. package/dist/ui-kit/primitives/media.d.ts +38 -0
  89. package/dist/ui-kit/primitives/media.d.ts.map +1 -0
  90. package/dist/ui-kit/primitives/media.js +9 -0
  91. package/dist/ui-kit/primitives/media.js.map +1 -0
  92. package/dist/ui-kit/primitives/stat.d.ts +25 -0
  93. package/dist/ui-kit/primitives/stat.d.ts.map +1 -0
  94. package/dist/ui-kit/primitives/stat.js +7 -0
  95. package/dist/ui-kit/primitives/stat.js.map +1 -0
  96. package/dist/ui-kit/repeated-slots.d.ts +121 -0
  97. package/dist/ui-kit/repeated-slots.d.ts.map +1 -0
  98. package/dist/ui-kit/repeated-slots.js +81 -0
  99. package/dist/ui-kit/repeated-slots.js.map +1 -0
  100. package/dist/ui-kit/sections/accordion.d.ts +4 -1
  101. package/dist/ui-kit/sections/accordion.d.ts.map +1 -1
  102. package/dist/ui-kit/sections/accordion.js +2 -2
  103. package/dist/ui-kit/sections/accordion.js.map +1 -1
  104. package/dist/ui-kit/sections/contact-block.d.ts +4 -1
  105. package/dist/ui-kit/sections/contact-block.d.ts.map +1 -1
  106. package/dist/ui-kit/sections/contact-block.js +3 -3
  107. package/dist/ui-kit/sections/contact-block.js.map +1 -1
  108. package/dist/ui-kit/sections/faqs.d.ts +4 -1
  109. package/dist/ui-kit/sections/faqs.d.ts.map +1 -1
  110. package/dist/ui-kit/sections/faqs.js +4 -5
  111. package/dist/ui-kit/sections/faqs.js.map +1 -1
  112. package/dist/ui-kit/sections/hero.d.ts +113 -0
  113. package/dist/ui-kit/sections/hero.d.ts.map +1 -0
  114. package/dist/ui-kit/sections/hero.js +58 -0
  115. package/dist/ui-kit/sections/hero.js.map +1 -0
  116. package/dist/ui-kit/sections/index.d.ts +2 -0
  117. package/dist/ui-kit/sections/index.d.ts.map +1 -1
  118. package/dist/ui-kit/sections/index.js +2 -0
  119. package/dist/ui-kit/sections/index.js.map +1 -1
  120. package/dist/ui-kit/sections/price-guide.d.ts +4 -1
  121. package/dist/ui-kit/sections/price-guide.d.ts.map +1 -1
  122. package/dist/ui-kit/sections/price-guide.js +3 -3
  123. package/dist/ui-kit/sections/price-guide.js.map +1 -1
  124. package/dist/ui-kit/sections/price-table.d.ts +7 -9
  125. package/dist/ui-kit/sections/price-table.d.ts.map +1 -1
  126. package/dist/ui-kit/sections/price-table.js +8 -8
  127. package/dist/ui-kit/sections/price-table.js.map +1 -1
  128. package/dist/ui-kit/sections/proof-strip.d.ts +4 -1
  129. package/dist/ui-kit/sections/proof-strip.d.ts.map +1 -1
  130. package/dist/ui-kit/sections/proof-strip.js +2 -2
  131. package/dist/ui-kit/sections/proof-strip.js.map +1 -1
  132. package/dist/ui-kit/sections/reviews-band.d.ts +5 -2
  133. package/dist/ui-kit/sections/reviews-band.d.ts.map +1 -1
  134. package/dist/ui-kit/sections/reviews-band.js +2 -2
  135. package/dist/ui-kit/sections/reviews-band.js.map +1 -1
  136. package/dist/ui-kit/sections/services-grid.d.ts +82 -0
  137. package/dist/ui-kit/sections/services-grid.d.ts.map +1 -0
  138. package/dist/ui-kit/sections/services-grid.js +90 -0
  139. package/dist/ui-kit/sections/services-grid.js.map +1 -0
  140. package/dist/ui-kit/sections/site-footer.d.ts +4 -1
  141. package/dist/ui-kit/sections/site-footer.d.ts.map +1 -1
  142. package/dist/ui-kit/sections/site-footer.js +6 -6
  143. package/dist/ui-kit/sections/site-footer.js.map +1 -1
  144. package/dist/ui-kit/shell/mobile-drawer.d.ts +7 -3
  145. package/dist/ui-kit/shell/mobile-drawer.d.ts.map +1 -1
  146. package/dist/ui-kit/shell/mobile-drawer.js +20 -4
  147. package/dist/ui-kit/shell/mobile-drawer.js.map +1 -1
  148. package/dist/ui-kit/shell/site-header.d.ts +4 -1
  149. package/dist/ui-kit/shell/site-header.d.ts.map +1 -1
  150. package/dist/ui-kit/shell/site-header.js +8 -8
  151. package/dist/ui-kit/shell/site-header.js.map +1 -1
  152. package/dist/ui-kit/shell/social-links.d.ts +1 -2
  153. package/dist/ui-kit/shell/social-links.d.ts.map +1 -1
  154. package/dist/ui-kit/shell/social-links.js +6 -3
  155. package/dist/ui-kit/shell/social-links.js.map +1 -1
  156. package/dist/ui-kit/shell/utility-bar.d.ts +4 -1
  157. package/dist/ui-kit/shell/utility-bar.d.ts.map +1 -1
  158. package/dist/ui-kit/shell/utility-bar.js +2 -2
  159. package/dist/ui-kit/shell/utility-bar.js.map +1 -1
  160. package/dist/ui-kit/skin-index.d.ts +2 -1
  161. package/dist/ui-kit/skin-index.d.ts.map +1 -1
  162. package/dist/ui-kit/skin-index.js +2 -1
  163. package/dist/ui-kit/skin-index.js.map +1 -1
  164. package/dist/ui-kit/skin.d.ts +21 -4
  165. package/dist/ui-kit/skin.d.ts.map +1 -1
  166. package/dist/ui-kit/skin.js +14 -1
  167. package/dist/ui-kit/skin.js.map +1 -1
  168. package/dist/ui-kit/structure.css +383 -3
  169. package/package.json +6 -2
@@ -0,0 +1,121 @@
1
+ /**
2
+ * The repeated slots of every shared component (3.14.0): the parts a component renders once per entry of a list in
3
+ * its data. They carry their `data-slot` hook and never a class, so a long class string is not repeated in the HTML and
4
+ * the RSC payload for every link, review, star, card or chip. A site styles them once in `src/site/skin.css`:
5
+ *
6
+ * ```css
7
+ * @layer components {
8
+ * [data-bl="footer"] [data-slot="link"] { @apply text-sm text-white/80 hover:text-white; }
9
+ * [data-bl="header"] [data-slot="list"] [data-slot="panelLink"] { padding: 0.5rem 0.75rem; }
10
+ * }
11
+ * ```
12
+ *
13
+ * `scope` is the selector that holds the repeated slots without reaching a nested component that reuses a slot name
14
+ * (the drawer sits inside the header; the rating chip inside the reviews band uses `stars` too).
15
+ * `repeatedSlotSelector()` joins the two. Pure data: safe to import anywhere.
16
+ */
17
+ export declare const REPEATED_SLOTS: {
18
+ readonly siteHeader: {
19
+ readonly scope: "[data-bl=\"header\"] [data-slot=\"list\"]";
20
+ readonly slots: readonly ["item", "itemRow", "link", "trigger", "panel", "panelBody", "panelList", "panelGroup", "panelHeading", "panelLink", "panelLabel", "panelText", "featured", "featuredMedia", "featuredLabel", "featuredText"];
21
+ };
22
+ readonly mobileDrawer: {
23
+ readonly scope: "[data-bl=\"drawer\"]";
24
+ readonly slots: readonly ["group", "groupTrigger", "groupLinks", "groupLink", "link", "socialLink"];
25
+ };
26
+ readonly utilityBar: {
27
+ readonly scope: "[data-bl=\"utility\"]";
28
+ readonly slots: readonly ["item", "licence", "socialLink"];
29
+ };
30
+ readonly siteFooter: {
31
+ readonly scope: "[data-bl=\"footer\"]";
32
+ readonly slots: readonly ["licenceLink", "socialLink", "column", "heading", "toggle", "list", "link", "policyLink"];
33
+ };
34
+ readonly accordion: {
35
+ readonly scope: "[data-bl=\"accordion\"]";
36
+ readonly slots: readonly ["item", "summary", "label", "marker", "content"];
37
+ };
38
+ readonly faqs: {
39
+ readonly scope: "[data-bl-section=\"faqs\"] [data-slot=\"columns\"]";
40
+ readonly slots: readonly ["column", "group", "subheading", "accordion", "item", "summary", "label", "marker", "content"];
41
+ /** The nested accordion's root is its own component: `data-bl="accordion"`, not a `data-slot`. */
42
+ readonly hooks: {
43
+ readonly accordion: "[data-bl=\"accordion\"]";
44
+ };
45
+ };
46
+ readonly priceTable: {
47
+ readonly scope: "[data-bl=\"price-table\"]";
48
+ readonly slots: readonly ["row", "job", "cell", "label", "range", "unit", "gst", "parts", "part", "includes", "excludes"];
49
+ };
50
+ readonly priceGuide: {
51
+ readonly scope: "[data-bl-section=\"price-guide\"]";
52
+ readonly slots: readonly ["row", "job", "cell", "label", "range", "unit", "gst", "parts", "part", "includes", "excludes", "card", "title", "figures", "band", "factor", "name", "effect", "detail"];
53
+ };
54
+ readonly proofStrip: {
55
+ readonly scope: "[data-bl-section=\"proof-strip\"] [data-slot=\"list\"]";
56
+ readonly slots: readonly ["item", "icon", "stars", "value", "label"];
57
+ };
58
+ readonly reviewsBand: {
59
+ readonly scope: "[data-bl-section=\"reviews\"] [data-slot=\"list\"]";
60
+ readonly slots: readonly ["item", "stars", "quote", "more", "author", "avatar", "name", "date"];
61
+ };
62
+ readonly contactBlock: {
63
+ readonly scope: "[data-bl-section=\"contact\"] [data-slot=\"details\"]";
64
+ readonly slots: readonly ["line"];
65
+ };
66
+ readonly servicesGrid: {
67
+ readonly scope: "[data-bl-section=\"services\"] [data-slot=\"list\"]";
68
+ readonly slots: readonly ["item", "body", "media", "title", "summary", "points", "point", "action", "link"];
69
+ };
70
+ readonly breadcrumbs: {
71
+ readonly scope: "[data-bl=\"breadcrumbs\"]";
72
+ readonly slots: readonly ["item", "link", "current"];
73
+ };
74
+ readonly thankYou: {
75
+ readonly scope: "[data-bl=\"thank-you\"]";
76
+ readonly slots: readonly ["step"];
77
+ };
78
+ readonly photoUpload: {
79
+ readonly scope: "[data-bl=\"photo-upload\"]";
80
+ readonly slots: readonly ["photo", "thumb", "remove"];
81
+ };
82
+ readonly quoteForm: {
83
+ readonly scope: "[data-bl=\"quote-form\"] [data-bl=\"photo-upload\"]";
84
+ readonly slots: readonly ["photo", "thumb", "remove"];
85
+ };
86
+ readonly card: {
87
+ readonly scope: "[data-bl=\"card\"]";
88
+ readonly slots: readonly ["media", "body", "title", "link", "text", "meta"];
89
+ };
90
+ readonly badge: {
91
+ readonly scope: "[data-bl=\"badge\"]";
92
+ readonly slots: readonly ["icon", "label"];
93
+ };
94
+ readonly chip: {
95
+ readonly scope: "[data-bl=\"chip\"]";
96
+ readonly slots: readonly ["icon", "label"];
97
+ };
98
+ readonly stat: {
99
+ readonly scope: "[data-bl=\"stat\"]";
100
+ readonly slots: readonly ["value", "label", "link"];
101
+ };
102
+ readonly tickList: {
103
+ readonly scope: "[data-bl=\"tick-list\"]";
104
+ readonly slots: readonly ["item", "tick", "text", "link"];
105
+ };
106
+ /** A lean list (cost item 1f): plain `<li><a>` entries with no `data-slot`, reached by element. */
107
+ readonly linkList: {
108
+ readonly scope: "[data-bl=\"link-list\"]";
109
+ readonly slots: readonly ["item", "link"];
110
+ readonly hooks: {
111
+ readonly item: "> li";
112
+ readonly link: "> li > a";
113
+ };
114
+ };
115
+ };
116
+ export type RepeatedSlotComponent = keyof typeof REPEATED_SLOTS;
117
+ /** The repeated slots of one component, for example `RepeatedSlotOf<'siteFooter'>` is `'licenceLink' | … | 'policyLink'`. */
118
+ export type RepeatedSlotOf<Component extends RepeatedSlotComponent> = (typeof REPEATED_SLOTS)[Component]['slots'][number];
119
+ /** The selector a skin stylesheet uses for one repeated slot, for example `[data-bl="footer"] [data-slot="link"]`. */
120
+ export declare function repeatedSlotSelector<Component extends RepeatedSlotComponent>(component: Component, slot: RepeatedSlotOf<Component>): string;
121
+ //# sourceMappingURL=repeated-slots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repeated-slots.d.ts","sourceRoot":"","sources":["../../src/ui-kit/repeated-slots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;QAqBvB,kGAAkG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAmCpG,mGAAmG;;;;;;;;;CAE3F,CAAA;AAEV,MAAM,MAAM,qBAAqB,GAAG,MAAM,OAAO,cAAc,CAAA;AAC/D,6HAA6H;AAC7H,MAAM,MAAM,cAAc,CAAC,SAAS,SAAS,qBAAqB,IAAI,CAAC,OAAO,cAAc,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAA;AAEzH,sHAAsH;AACtH,wBAAgB,oBAAoB,CAAC,SAAS,SAAS,qBAAqB,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,CAAC,SAAS,CAAC,GAAG,MAAM,CAG3I"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The repeated slots of every shared component (3.14.0): the parts a component renders once per entry of a list in
3
+ * its data. They carry their `data-slot` hook and never a class, so a long class string is not repeated in the HTML and
4
+ * the RSC payload for every link, review, star, card or chip. A site styles them once in `src/site/skin.css`:
5
+ *
6
+ * ```css
7
+ * @layer components {
8
+ * [data-bl="footer"] [data-slot="link"] { @apply text-sm text-white/80 hover:text-white; }
9
+ * [data-bl="header"] [data-slot="list"] [data-slot="panelLink"] { padding: 0.5rem 0.75rem; }
10
+ * }
11
+ * ```
12
+ *
13
+ * `scope` is the selector that holds the repeated slots without reaching a nested component that reuses a slot name
14
+ * (the drawer sits inside the header; the rating chip inside the reviews band uses `stars` too).
15
+ * `repeatedSlotSelector()` joins the two. Pure data: safe to import anywhere.
16
+ */
17
+ export const REPEATED_SLOTS = {
18
+ siteHeader: {
19
+ scope: '[data-bl="header"] [data-slot="list"]',
20
+ slots: [
21
+ 'item', 'itemRow', 'link', 'trigger', 'panel', 'panelBody', 'panelList', 'panelGroup', 'panelHeading', 'panelLink',
22
+ 'panelLabel', 'panelText', 'featured', 'featuredMedia', 'featuredLabel', 'featuredText',
23
+ ],
24
+ },
25
+ mobileDrawer: {
26
+ scope: '[data-bl="drawer"]',
27
+ slots: ['group', 'groupTrigger', 'groupLinks', 'groupLink', 'link', 'socialLink'],
28
+ },
29
+ utilityBar: { scope: '[data-bl="utility"]', slots: ['item', 'licence', 'socialLink'] },
30
+ siteFooter: {
31
+ scope: '[data-bl="footer"]',
32
+ slots: ['licenceLink', 'socialLink', 'column', 'heading', 'toggle', 'list', 'link', 'policyLink'],
33
+ },
34
+ accordion: { scope: '[data-bl="accordion"]', slots: ['item', 'summary', 'label', 'marker', 'content'] },
35
+ faqs: {
36
+ scope: '[data-bl-section="faqs"] [data-slot="columns"]',
37
+ slots: ['column', 'group', 'subheading', 'accordion', 'item', 'summary', 'label', 'marker', 'content'],
38
+ /** The nested accordion's root is its own component: `data-bl="accordion"`, not a `data-slot`. */
39
+ hooks: { accordion: '[data-bl="accordion"]' },
40
+ },
41
+ priceTable: {
42
+ scope: '[data-bl="price-table"]',
43
+ slots: ['row', 'job', 'cell', 'label', 'range', 'unit', 'gst', 'parts', 'part', 'includes', 'excludes'],
44
+ },
45
+ priceGuide: {
46
+ scope: '[data-bl-section="price-guide"]',
47
+ slots: [
48
+ 'row', 'job', 'cell', 'label', 'range', 'unit', 'gst', 'parts', 'part', 'includes', 'excludes', 'card', 'title',
49
+ 'figures', 'band', 'factor', 'name', 'effect', 'detail',
50
+ ],
51
+ },
52
+ proofStrip: { scope: '[data-bl-section="proof-strip"] [data-slot="list"]', slots: ['item', 'icon', 'stars', 'value', 'label'] },
53
+ reviewsBand: {
54
+ scope: '[data-bl-section="reviews"] [data-slot="list"]',
55
+ slots: ['item', 'stars', 'quote', 'more', 'author', 'avatar', 'name', 'date'],
56
+ },
57
+ contactBlock: { scope: '[data-bl-section="contact"] [data-slot="details"]', slots: ['line'] },
58
+ servicesGrid: {
59
+ scope: '[data-bl-section="services"] [data-slot="list"]',
60
+ slots: ['item', 'body', 'media', 'title', 'summary', 'points', 'point', 'action', 'link'],
61
+ },
62
+ breadcrumbs: { scope: '[data-bl="breadcrumbs"]', slots: ['item', 'link', 'current'] },
63
+ thankYou: { scope: '[data-bl="thank-you"]', slots: ['step'] },
64
+ photoUpload: { scope: '[data-bl="photo-upload"]', slots: ['photo', 'thumb', 'remove'] },
65
+ quoteForm: { scope: '[data-bl="quote-form"] [data-bl="photo-upload"]', slots: ['photo', 'thumb', 'remove'] },
66
+ // Primitives rendered once per list entry (3.14.0): every part is repeated, the root included (style the root
67
+ // through its own hook, `[data-bl="card"]`).
68
+ card: { scope: '[data-bl="card"]', slots: ['media', 'body', 'title', 'link', 'text', 'meta'] },
69
+ badge: { scope: '[data-bl="badge"]', slots: ['icon', 'label'] },
70
+ chip: { scope: '[data-bl="chip"]', slots: ['icon', 'label'] },
71
+ stat: { scope: '[data-bl="stat"]', slots: ['value', 'label', 'link'] },
72
+ tickList: { scope: '[data-bl="tick-list"]', slots: ['item', 'tick', 'text', 'link'] },
73
+ /** A lean list (cost item 1f): plain `<li><a>` entries with no `data-slot`, reached by element. */
74
+ linkList: { scope: '[data-bl="link-list"]', slots: ['item', 'link'], hooks: { item: '> li', link: '> li > a' } },
75
+ };
76
+ /** The selector a skin stylesheet uses for one repeated slot, for example `[data-bl="footer"] [data-slot="link"]`. */
77
+ export function repeatedSlotSelector(component, slot) {
78
+ const entry = REPEATED_SLOTS[component];
79
+ return `${entry.scope} ${entry.hooks?.[slot] ?? `[data-slot="${slot}"]`}`;
80
+ }
81
+ //# sourceMappingURL=repeated-slots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repeated-slots.js","sourceRoot":"","sources":["../../src/ui-kit/repeated-slots.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The repeated slots of every shared component (3.14.0): the parts a component renders once per entry of a list in\n * its data. They carry their `data-slot` hook and never a class, so a long class string is not repeated in the HTML and\n * the RSC payload for every link, review, star, card or chip. A site styles them once in `src/site/skin.css`:\n *\n * ```css\n * @layer components {\n * [data-bl=\"footer\"] [data-slot=\"link\"] { @apply text-sm text-white/80 hover:text-white; }\n * [data-bl=\"header\"] [data-slot=\"list\"] [data-slot=\"panelLink\"] { padding: 0.5rem 0.75rem; }\n * }\n * ```\n *\n * `scope` is the selector that holds the repeated slots without reaching a nested component that reuses a slot name\n * (the drawer sits inside the header; the rating chip inside the reviews band uses `stars` too).\n * `repeatedSlotSelector()` joins the two. Pure data: safe to import anywhere.\n */\nexport const REPEATED_SLOTS = {\n siteHeader: {\n scope: '[data-bl=\"header\"] [data-slot=\"list\"]',\n slots: [\n 'item', 'itemRow', 'link', 'trigger', 'panel', 'panelBody', 'panelList', 'panelGroup', 'panelHeading', 'panelLink',\n 'panelLabel', 'panelText', 'featured', 'featuredMedia', 'featuredLabel', 'featuredText',\n ],\n },\n mobileDrawer: {\n scope: '[data-bl=\"drawer\"]',\n slots: ['group', 'groupTrigger', 'groupLinks', 'groupLink', 'link', 'socialLink'],\n },\n utilityBar: { scope: '[data-bl=\"utility\"]', slots: ['item', 'licence', 'socialLink'] },\n siteFooter: {\n scope: '[data-bl=\"footer\"]',\n slots: ['licenceLink', 'socialLink', 'column', 'heading', 'toggle', 'list', 'link', 'policyLink'],\n },\n accordion: { scope: '[data-bl=\"accordion\"]', slots: ['item', 'summary', 'label', 'marker', 'content'] },\n faqs: {\n scope: '[data-bl-section=\"faqs\"] [data-slot=\"columns\"]',\n slots: ['column', 'group', 'subheading', 'accordion', 'item', 'summary', 'label', 'marker', 'content'],\n /** The nested accordion's root is its own component: `data-bl=\"accordion\"`, not a `data-slot`. */\n hooks: { accordion: '[data-bl=\"accordion\"]' },\n },\n priceTable: {\n scope: '[data-bl=\"price-table\"]',\n slots: ['row', 'job', 'cell', 'label', 'range', 'unit', 'gst', 'parts', 'part', 'includes', 'excludes'],\n },\n priceGuide: {\n scope: '[data-bl-section=\"price-guide\"]',\n slots: [\n 'row', 'job', 'cell', 'label', 'range', 'unit', 'gst', 'parts', 'part', 'includes', 'excludes', 'card', 'title',\n 'figures', 'band', 'factor', 'name', 'effect', 'detail',\n ],\n },\n proofStrip: { scope: '[data-bl-section=\"proof-strip\"] [data-slot=\"list\"]', slots: ['item', 'icon', 'stars', 'value', 'label'] },\n reviewsBand: {\n scope: '[data-bl-section=\"reviews\"] [data-slot=\"list\"]',\n slots: ['item', 'stars', 'quote', 'more', 'author', 'avatar', 'name', 'date'],\n },\n contactBlock: { scope: '[data-bl-section=\"contact\"] [data-slot=\"details\"]', slots: ['line'] },\n servicesGrid: {\n scope: '[data-bl-section=\"services\"] [data-slot=\"list\"]',\n slots: ['item', 'body', 'media', 'title', 'summary', 'points', 'point', 'action', 'link'],\n },\n breadcrumbs: { scope: '[data-bl=\"breadcrumbs\"]', slots: ['item', 'link', 'current'] },\n thankYou: { scope: '[data-bl=\"thank-you\"]', slots: ['step'] },\n photoUpload: { scope: '[data-bl=\"photo-upload\"]', slots: ['photo', 'thumb', 'remove'] },\n quoteForm: { scope: '[data-bl=\"quote-form\"] [data-bl=\"photo-upload\"]', slots: ['photo', 'thumb', 'remove'] },\n // Primitives rendered once per list entry (3.14.0): every part is repeated, the root included (style the root\n // through its own hook, `[data-bl=\"card\"]`).\n card: { scope: '[data-bl=\"card\"]', slots: ['media', 'body', 'title', 'link', 'text', 'meta'] },\n badge: { scope: '[data-bl=\"badge\"]', slots: ['icon', 'label'] },\n chip: { scope: '[data-bl=\"chip\"]', slots: ['icon', 'label'] },\n stat: { scope: '[data-bl=\"stat\"]', slots: ['value', 'label', 'link'] },\n tickList: { scope: '[data-bl=\"tick-list\"]', slots: ['item', 'tick', 'text', 'link'] },\n /** A lean list (cost item 1f): plain `<li><a>` entries with no `data-slot`, reached by element. */\n linkList: { scope: '[data-bl=\"link-list\"]', slots: ['item', 'link'], hooks: { item: '> li', link: '> li > a' } },\n} as const\n\nexport type RepeatedSlotComponent = keyof typeof REPEATED_SLOTS\n/** The repeated slots of one component, for example `RepeatedSlotOf<'siteFooter'>` is `'licenceLink' | … | 'policyLink'`. */\nexport type RepeatedSlotOf<Component extends RepeatedSlotComponent> = (typeof REPEATED_SLOTS)[Component]['slots'][number]\n\n/** The selector a skin stylesheet uses for one repeated slot, for example `[data-bl=\"footer\"] [data-slot=\"link\"]`. */\nexport function repeatedSlotSelector<Component extends RepeatedSlotComponent>(component: Component, slot: RepeatedSlotOf<Component>): string {\n const entry: { readonly scope: string; readonly hooks?: Readonly<Record<string, string>> } = REPEATED_SLOTS[component]\n return `${entry.scope} ${entry.hooks?.[slot] ?? `[data-slot=\"${slot}\"]`}`\n}\n"]}
@@ -5,9 +5,12 @@
5
5
  * the native `open` attribute, so skins style `[open]`.
6
6
  */
7
7
  import type { ReactNode } from 'react';
8
+ import type { RepeatedSlotOf } from '../repeated-slots.js';
8
9
  import { type SkinEntry } from '../skin.js';
9
10
  export type AccordionSlot = 'root' | 'item' | 'summary' | 'label' | 'marker' | 'content';
10
- export type AccordionSkin = SkinEntry<AccordionSlot>;
11
+ /** Rendered once per item: styled from the skin stylesheet, never by class (3.14.0). */
12
+ export type AccordionRepeatedSlot = RepeatedSlotOf<'accordion'>;
13
+ export type AccordionSkin = SkinEntry<AccordionSlot, never, Record<never, never>, AccordionRepeatedSlot>;
11
14
  export interface AccordionItem {
12
15
  /** A stable slug, unique in the group. The item's element id is `{name}-{id}`, so it can be linked to. */
13
16
  readonly id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"accordion.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/accordion.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AAEjD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAA;AACxF,MAAM,MAAM,aAAa,GAAG,SAAS,CAAC,aAAa,CAAC,CAAA;AAEpD,MAAM,WAAW,aAAa;IAC5B,0GAA0G;IAC1G,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAA;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAA;CAC5B;AAED,MAAM,WAAW,cAAe,SAAQ,aAAa;IACnD,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAA;IACxC,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,wEAAwE;IACxE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC1C;AAED,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,cAAc,kDAoB9E"}
1
+ {"version":3,"file":"accordion.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/accordion.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AAE/D,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAA;AACxF,wFAAwF;AACxF,MAAM,MAAM,qBAAqB,GAAG,cAAc,CAAC,WAAW,CAAC,CAAA;AAC/D,MAAM,MAAM,aAAa,GAAG,SAAS,CAAC,aAAa,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,qBAAqB,CAAC,CAAA;AAExG,MAAM,WAAW,aAAa;IAC5B,0GAA0G;IAC1G,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAA;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAA;CAC5B;AAED,MAAM,WAAW,cAAe,SAAQ,aAAa;IACnD,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAA;IACxC,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,wEAAwE;IACxE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC1C;AAED,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,cAAc,kDAoB9E"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Icon } from '../icons.js';
3
- import { slot } from '../skin.js';
3
+ import { repeatedSlot, slot } from '../skin.js';
4
4
  export function Accordion({ items, name, defaultOpen, classes }) {
5
5
  if (items.length === 0)
6
6
  return null;
@@ -9,6 +9,6 @@ export function Accordion({ items, name, defaultOpen, classes }) {
9
9
  throw new Error(`Accordion "${name}": item ids must be unique.`);
10
10
  if (defaultOpen !== undefined && !ids.has(defaultOpen))
11
11
  throw new Error(`Accordion "${name}": defaultOpen "${defaultOpen}" is not an item.`);
12
- return (_jsx("div", { "data-bl": "accordion", className: classes?.root, children: items.map((item) => (_jsxs("details", { id: `${name}-${item.id}`, name: name, open: item.id === defaultOpen, ...slot(classes, 'item'), children: [_jsxs("summary", { ...slot(classes, 'summary'), children: [_jsx("span", { ...slot(classes, 'label'), children: item.summary }), _jsx("span", { ...slot(classes, 'marker'), "aria-hidden": "true", children: _jsx(Icon, { name: "chevron" }) })] }), _jsx("div", { ...slot(classes, 'content'), children: item.content })] }, item.id))) }));
12
+ return (_jsx("div", { "data-bl": "accordion", className: classes?.root, children: items.map((item) => (_jsxs("details", { id: `${name}-${item.id}`, name: name, open: item.id === defaultOpen, ...repeatedSlot('item'), children: [_jsxs("summary", { ...repeatedSlot('summary'), children: [_jsx("span", { ...repeatedSlot('label'), children: item.summary }), _jsx("span", { ...repeatedSlot('marker'), "aria-hidden": "true", children: _jsx(Icon, { name: "chevron" }) })] }), _jsx("div", { ...repeatedSlot('content'), children: item.content })] }, item.id))) }));
13
13
  }
14
14
  //# sourceMappingURL=accordion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"accordion.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/accordion.tsx"],"names":[],"mappings":";AAOA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAClC,OAAO,EAAE,IAAI,EAAkB,MAAM,YAAY,CAAA;AAoBjD,MAAM,UAAU,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAkB;IAC7E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACnC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;IACjD,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,6BAA6B,CAAC,CAAA;IAC/F,IAAI,WAAW,KAAK,SAAS,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,mBAAmB,WAAW,mBAAmB,CAAC,CAAA;IAC5I,OAAO,CACL,yBAAa,WAAW,EAAC,SAAS,EAAE,OAAO,EAAE,IAAI,YAC9C,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACnB,mBAAuB,EAAE,EAAE,GAAG,IAAI,IAAI,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,KAAK,WAAW,KAAM,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,aACnH,sBAAa,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,aACnC,kBAAU,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,YAAG,IAAI,CAAC,OAAO,GAAQ,EACvD,kBAAU,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,iBAAc,MAAM,YACnD,KAAC,IAAI,IAAC,IAAI,EAAC,SAAS,GAAG,GAClB,IACC,EACV,iBAAS,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,YAAG,IAAI,CAAC,OAAO,GAAO,KAP3C,IAAI,CAAC,EAAE,CAQX,CACX,CAAC,GACE,CACP,CAAA;AACH,CAAC","sourcesContent":["/**\n * `Accordion`: a list of disclosures on `<details name>`. Server component: items sharing a `name` open one at a time\n * natively (no script), and every item's content is in the server HTML whether open or closed (K7). Items are closed\n * by default; `defaultOpen` opens one only where a site recorded it (Gabrael: Eastern Suburbs open). The open state is\n * the native `open` attribute, so skins style `[open]`.\n */\nimport type { ReactNode } from 'react'\nimport { Icon } from '../icons.js'\nimport { slot, type SkinEntry } from '../skin.js'\n\nexport type AccordionSlot = 'root' | 'item' | 'summary' | 'label' | 'marker' | 'content'\nexport type AccordionSkin = SkinEntry<AccordionSlot>\n\nexport interface AccordionItem {\n /** A stable slug, unique in the group. The item's element id is `{name}-{id}`, so it can be linked to. */\n readonly id: string\n readonly summary: ReactNode\n readonly content: ReactNode\n}\n\nexport interface AccordionProps extends AccordionSkin {\n readonly items: readonly AccordionItem[]\n /** The group: items sharing a name open one at a time. Unique on the page. */\n readonly name: string\n /** The id of the item open on load, only when the site recorded one. */\n readonly defaultOpen?: string | undefined\n}\n\nexport function Accordion({ items, name, defaultOpen, classes }: AccordionProps) {\n if (items.length === 0) return null\n const ids = new Set(items.map((item) => item.id))\n if (ids.size !== items.length) throw new Error(`Accordion \"${name}\": item ids must be unique.`)\n if (defaultOpen !== undefined && !ids.has(defaultOpen)) throw new Error(`Accordion \"${name}\": defaultOpen \"${defaultOpen}\" is not an item.`)\n return (\n <div data-bl=\"accordion\" className={classes?.root}>\n {items.map((item) => (\n <details key={item.id} id={`${name}-${item.id}`} name={name} open={item.id === defaultOpen} {...slot(classes, 'item')}>\n <summary {...slot(classes, 'summary')}>\n <span {...slot(classes, 'label')}>{item.summary}</span>\n <span {...slot(classes, 'marker')} aria-hidden=\"true\">\n <Icon name=\"chevron\" />\n </span>\n </summary>\n <div {...slot(classes, 'content')}>{item.content}</div>\n </details>\n ))}\n </div>\n )\n}\n"]}
1
+ {"version":3,"file":"accordion.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/accordion.tsx"],"names":[],"mappings":";AAOA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAElC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAkB,MAAM,YAAY,CAAA;AAsB/D,MAAM,UAAU,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAkB;IAC7E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACnC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;IACjD,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,6BAA6B,CAAC,CAAA;IAC/F,IAAI,WAAW,KAAK,SAAS,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,mBAAmB,WAAW,mBAAmB,CAAC,CAAA;IAC5I,OAAO,CACL,yBAAa,WAAW,EAAC,SAAS,EAAE,OAAO,EAAE,IAAI,YAC9C,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACnB,mBAAuB,EAAE,EAAE,GAAG,IAAI,IAAI,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,KAAK,WAAW,KAAM,YAAY,CAAC,MAAM,CAAC,aAClH,sBAAa,YAAY,CAAC,SAAS,CAAC,aAClC,kBAAU,YAAY,CAAC,OAAO,CAAC,YAAG,IAAI,CAAC,OAAO,GAAQ,EACtD,kBAAU,YAAY,CAAC,QAAQ,CAAC,iBAAc,MAAM,YAClD,KAAC,IAAI,IAAC,IAAI,EAAC,SAAS,GAAG,GAClB,IACC,EACV,iBAAS,YAAY,CAAC,SAAS,CAAC,YAAG,IAAI,CAAC,OAAO,GAAO,KAP1C,IAAI,CAAC,EAAE,CAQX,CACX,CAAC,GACE,CACP,CAAA;AACH,CAAC","sourcesContent":["/**\n * `Accordion`: a list of disclosures on `<details name>`. Server component: items sharing a `name` open one at a time\n * natively (no script), and every item's content is in the server HTML whether open or closed (K7). Items are closed\n * by default; `defaultOpen` opens one only where a site recorded it (Gabrael: Eastern Suburbs open). The open state is\n * the native `open` attribute, so skins style `[open]`.\n */\nimport type { ReactNode } from 'react'\nimport { Icon } from '../icons.js'\nimport type { RepeatedSlotOf } from '../repeated-slots.js'\nimport { repeatedSlot, slot, type SkinEntry } from '../skin.js'\n\nexport type AccordionSlot = 'root' | 'item' | 'summary' | 'label' | 'marker' | 'content'\n/** Rendered once per item: styled from the skin stylesheet, never by class (3.14.0). */\nexport type AccordionRepeatedSlot = RepeatedSlotOf<'accordion'>\nexport type AccordionSkin = SkinEntry<AccordionSlot, never, Record<never, never>, AccordionRepeatedSlot>\n\nexport interface AccordionItem {\n /** A stable slug, unique in the group. The item's element id is `{name}-{id}`, so it can be linked to. */\n readonly id: string\n readonly summary: ReactNode\n readonly content: ReactNode\n}\n\nexport interface AccordionProps extends AccordionSkin {\n readonly items: readonly AccordionItem[]\n /** The group: items sharing a name open one at a time. Unique on the page. */\n readonly name: string\n /** The id of the item open on load, only when the site recorded one. */\n readonly defaultOpen?: string | undefined\n}\n\nexport function Accordion({ items, name, defaultOpen, classes }: AccordionProps) {\n if (items.length === 0) return null\n const ids = new Set(items.map((item) => item.id))\n if (ids.size !== items.length) throw new Error(`Accordion \"${name}\": item ids must be unique.`)\n if (defaultOpen !== undefined && !ids.has(defaultOpen)) throw new Error(`Accordion \"${name}\": defaultOpen \"${defaultOpen}\" is not an item.`)\n return (\n <div data-bl=\"accordion\" className={classes?.root}>\n {items.map((item) => (\n <details key={item.id} id={`${name}-${item.id}`} name={name} open={item.id === defaultOpen} {...repeatedSlot('item')}>\n <summary {...repeatedSlot('summary')}>\n <span {...repeatedSlot('label')}>{item.summary}</span>\n <span {...repeatedSlot('marker')} aria-hidden=\"true\">\n <Icon name=\"chevron\" />\n </span>\n </summary>\n <div {...repeatedSlot('content')}>{item.content}</div>\n </details>\n ))}\n </div>\n )\n}\n"]}
@@ -7,13 +7,16 @@
7
7
  */
8
8
  import type { ReactNode } from 'react';
9
9
  import { type SectionTone } from '../primitives/layout.js';
10
+ import type { RepeatedSlotOf } from '../repeated-slots.js';
10
11
  import { type SkinEntry } from '../skin.js';
11
12
  import type { UiLink, UiPhone } from '../types.js';
12
13
  /** `phone` and `link` are the classes for the nested `PhoneLink` and the email and areas links. */
13
14
  export type ContactBlockSlot = 'root' | 'inner' | 'form' | 'details' | 'heading' | 'list' | 'item' | 'label' | 'value' | 'line' | 'phone' | 'link';
15
+ /** Rendered once per line of hours, address, licences or areas: styled from the skin stylesheet, never by class (3.14.0). */
16
+ export type ContactBlockRepeatedSlot = RepeatedSlotOf<'contactBlock'>;
14
17
  export type ContactBlockSkin = SkinEntry<ContactBlockSlot, never, {
15
18
  formSide?: 'start' | 'end';
16
- }>;
19
+ }, ContactBlockRepeatedSlot>;
17
20
  /** A labelled detail of one or more lines, for example hours or a licence. */
18
21
  export interface ContactDetailLines {
19
22
  readonly label: string;
@@ -1 +1 @@
1
- {"version":3,"file":"contact-block.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/contact-block.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAW,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAEnE,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAElD,mGAAmG;AACnG,MAAM,MAAM,gBAAgB,GACxB,MAAM,GACN,OAAO,GACP,MAAM,GACN,SAAS,GACT,SAAS,GACT,MAAM,GACN,MAAM,GACN,OAAO,GACP,OAAO,GACP,MAAM,GACN,OAAO,GACP,MAAM,CAAA;AACV,MAAM,MAAM,gBAAgB,GAAG,SAAS,CAAC,gBAAgB,EAAE,KAAK,EAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,KAAK,CAAA;CAAE,CAAC,CAAA;AAEjG,8EAA8E;AAC9E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAClC;AAED,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACzD,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,kEAAkE;IAClE,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAA;IACpE,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;IACrE,QAAQ,CAAC,KAAK,CAAC,EAAE,kBAAkB,CAAA;IACnC,wGAAwG;IACxG,QAAQ,CAAC,OAAO,CAAC,EAAE,kBAAkB,CAAA;IACrC,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAA;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1F,qCAAqC;IACrC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,gCAAgC;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAC5B;AAID,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,IAAI,EACJ,KAAK,EACL,KAAK,EACL,KAAK,EACL,OAAO,EACP,QAAQ,EACR,KAAK,EACL,SAA6B,EAC7B,IAAI,EACJ,MAAM,EACN,OAAO,GACR,EAAE,iBAAiB,2CA4DnB"}
1
+ {"version":3,"file":"contact-block.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/contact-block.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAW,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAEnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AAC/D,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAElD,mGAAmG;AACnG,MAAM,MAAM,gBAAgB,GACxB,MAAM,GACN,OAAO,GACP,MAAM,GACN,SAAS,GACT,SAAS,GACT,MAAM,GACN,MAAM,GACN,OAAO,GACP,OAAO,GACP,MAAM,GACN,OAAO,GACP,MAAM,CAAA;AACV,6HAA6H;AAC7H,MAAM,MAAM,wBAAwB,GAAG,cAAc,CAAC,cAAc,CAAC,CAAA;AACrE,MAAM,MAAM,gBAAgB,GAAG,SAAS,CAAC,gBAAgB,EAAE,KAAK,EAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,KAAK,CAAA;CAAE,EAAE,wBAAwB,CAAC,CAAA;AAE3H,8EAA8E;AAC9E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAClC;AAED,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACzD,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,kEAAkE;IAClE,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAA;IACpE,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;IACrE,QAAQ,CAAC,KAAK,CAAC,EAAE,kBAAkB,CAAA;IACnC,wGAAwG;IACxG,QAAQ,CAAC,OAAO,CAAC,EAAE,kBAAkB,CAAA;IACrC,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAA;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1F,qCAAqC;IACrC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,gCAAgC;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAC5B;AAID,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,IAAI,EACJ,KAAK,EACL,KAAK,EACL,KAAK,EACL,OAAO,EACP,QAAQ,EACR,KAAK,EACL,SAA6B,EAC7B,IAAI,EACJ,MAAM,EACN,OAAO,GACR,EAAE,iBAAiB,2CA4DnB"}
@@ -1,15 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Section } from '../primitives/layout.js';
3
3
  import { PhoneLink, TextLink } from '../primitives/links.js';
4
- import { slot } from '../skin.js';
4
+ import { repeatedSlot, slot } from '../skin.js';
5
5
  const EMAIL = /^[^\s@<>()]+@[^\s@<>()]+\.[^\s@<>()]+$/;
6
6
  export function ContactBlock({ title, form, phone, email, hours, address, licences, areas, headingId = 'contact-heading', tone, layout, classes, }) {
7
7
  if (email && !EMAIL.test(email.address))
8
8
  throw new Error(`ContactBlock: "${email.address}" is not an email address.`);
9
9
  const item = (key, label, value) => (_jsxs("div", { ...slot(classes, 'item'), "data-item": key, children: [_jsx("dt", { ...slot(classes, 'label'), children: label }), _jsx("dd", { ...slot(classes, 'value'), children: value })] }, key));
10
- const lines = (detail) => detail.lines.map((line) => (_jsx("span", { ...slot(classes, 'line'), children: line }, line)));
10
+ const lines = (detail) => detail.lines.map((line) => (_jsx("span", { ...repeatedSlot('line'), children: line }, line)));
11
11
  return (_jsxs(Section, { id: "contact", labelledBy: headingId, tone: tone, classes: classes, phonePlacement: "contact", hooks: { 'data-form-side': layout?.formSide ?? 'start' }, children: [_jsx("div", { id: "quote", ...slot(classes, 'form'), children: form }), _jsxs("div", { ...slot(classes, 'details'), children: [_jsx("h2", { id: headingId, ...slot(classes, 'heading'), children: title }), phone || email || hours || address || licences || areas ? (_jsxs("dl", { ...slot(classes, 'list'), children: [phone ? item('phone', phone.label, _jsx(PhoneLink, { phone: phone.phone, placement: "contact", classes: { root: classes?.phone } })) : null, email ? item('email', email.label, _jsx("a", { href: `mailto:${email.address}`, ...slot(classes, 'link'), children: email.address })) : null, hours ? item('hours', hours.label, lines(hours)) : null, address ? item('address', address.label, lines(address)) : null, licences ? item('licences', licences.label, lines(licences)) : null, areas
12
- ? item('areas', areas.label, _jsxs(_Fragment, { children: [_jsx("span", { ...slot(classes, 'line'), children: areas.text }), areas.link ? (_jsx("span", { ...slot(classes, 'line'), children: _jsx(TextLink, { href: areas.link.href, classes: { root: classes?.link }, children: areas.link.label }) })) : null] }))
12
+ ? item('areas', areas.label, _jsxs(_Fragment, { children: [_jsx("span", { ...repeatedSlot('line'), children: areas.text }), areas.link ? (_jsx("span", { ...repeatedSlot('line'), children: _jsx(TextLink, { href: areas.link.href, classes: { root: classes?.link }, children: areas.link.label }) })) : null] }))
13
13
  : null] })) : null] })] }));
14
14
  }
15
15
  //# sourceMappingURL=contact-block.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contact-block.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/contact-block.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `ContactBlock` (S25, `contact`): the contact page itself. Server component. The site's quote form on one side\n * (the start by default: form left, details right) and the contact details on the other: phone (placement\n * `contact`), email, hours, the public address only where the site passes it, licences and areas. Nothing else: it\n * takes no other content, and the page is the page header then this block (K12-contact). The form side always has\n * `id=\"quote\"`, the target of `QuoteButton`'s no-script fallback (`/contact#quote`).\n */\nimport type { ReactNode } from 'react'\nimport { Section, type SectionTone } from '../primitives/layout.js'\nimport { PhoneLink, TextLink } from '../primitives/links.js'\nimport { slot, type SkinEntry } from '../skin.js'\nimport type { UiLink, UiPhone } from '../types.js'\n\n/** `phone` and `link` are the classes for the nested `PhoneLink` and the email and areas links. */\nexport type ContactBlockSlot =\n | 'root'\n | 'inner'\n | 'form'\n | 'details'\n | 'heading'\n | 'list'\n | 'item'\n | 'label'\n | 'value'\n | 'line'\n | 'phone'\n | 'link'\nexport type ContactBlockSkin = SkinEntry<ContactBlockSlot, never, { formSide?: 'start' | 'end' }>\n\n/** A labelled detail of one or more lines, for example hours or a licence. */\nexport interface ContactDetailLines {\n readonly label: string\n readonly lines: readonly string[]\n}\n\nexport interface ContactBlockProps extends ContactBlockSkin {\n /** The details' H2, for example \"Contact details\". */\n readonly title: ReactNode\n /** The site's quote form (`QuoteForm`, plain). */\n readonly form: ReactNode\n /** From the derived business facts; never typed by hand (C10). */\n readonly phone?: { readonly label: string; readonly phone: UiPhone }\n readonly email?: { readonly label: string; readonly address: string }\n readonly hours?: ContactDetailLines\n /** The public address, only where it shows (customers visit, and Romie said so). Never derived here. */\n readonly address?: ContactDetailLines\n readonly licences?: ContactDetailLines\n readonly areas?: { readonly label: string; readonly text: string; readonly link?: UiLink }\n /** Defaults to `contact-heading`. */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n}\n\nconst EMAIL = /^[^\\s@<>()]+@[^\\s@<>()]+\\.[^\\s@<>()]+$/\n\nexport function ContactBlock({\n title,\n form,\n phone,\n email,\n hours,\n address,\n licences,\n areas,\n headingId = 'contact-heading',\n tone,\n layout,\n classes,\n}: ContactBlockProps) {\n if (email && !EMAIL.test(email.address)) throw new Error(`ContactBlock: \"${email.address}\" is not an email address.`)\n const item = (key: string, label: string, value: ReactNode) => (\n <div key={key} {...slot(classes, 'item')} data-item={key}>\n <dt {...slot(classes, 'label')}>{label}</dt>\n <dd {...slot(classes, 'value')}>{value}</dd>\n </div>\n )\n const lines = (detail: ContactDetailLines) =>\n detail.lines.map((line) => (\n <span key={line} {...slot(classes, 'line')}>\n {line}\n </span>\n ))\n return (\n <Section\n id=\"contact\"\n labelledBy={headingId}\n tone={tone}\n classes={classes}\n phonePlacement=\"contact\"\n hooks={{ 'data-form-side': layout?.formSide ?? 'start' }}\n >\n <div id=\"quote\" {...slot(classes, 'form')}>\n {form}\n </div>\n <div {...slot(classes, 'details')}>\n <h2 id={headingId} {...slot(classes, 'heading')}>\n {title}\n </h2>\n {phone || email || hours || address || licences || areas ? (\n <dl {...slot(classes, 'list')}>\n {phone ? item('phone', phone.label, <PhoneLink phone={phone.phone} placement=\"contact\" classes={{ root: classes?.phone }} />) : null}\n {email ? item('email', email.label, <a href={`mailto:${email.address}`} {...slot(classes, 'link')}>\n {email.address}\n </a>) : null}\n {hours ? item('hours', hours.label, lines(hours)) : null}\n {address ? item('address', address.label, lines(address)) : null}\n {licences ? item('licences', licences.label, lines(licences)) : null}\n {areas\n ? item(\n 'areas',\n areas.label,\n <>\n <span {...slot(classes, 'line')}>{areas.text}</span>\n {areas.link ? (\n <span {...slot(classes, 'line')}>\n <TextLink href={areas.link.href} classes={{ root: classes?.link }}>\n {areas.link.label}\n </TextLink>\n </span>\n ) : null}\n </>,\n )\n : null}\n </dl>\n ) : null}\n </div>\n </Section>\n )\n}\n"]}
1
+ {"version":3,"file":"contact-block.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/contact-block.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `ContactBlock` (S25, `contact`): the contact page itself. Server component. The site's quote form on one side\n * (the start by default: form left, details right) and the contact details on the other: phone (placement\n * `contact`), email, hours, the public address only where the site passes it, licences and areas. Nothing else: it\n * takes no other content, and the page is the page header then this block (K12-contact). The form side always has\n * `id=\"quote\"`, the target of `QuoteButton`'s no-script fallback (`/contact#quote`).\n */\nimport type { ReactNode } from 'react'\nimport { Section, type SectionTone } from '../primitives/layout.js'\nimport { PhoneLink, TextLink } from '../primitives/links.js'\nimport type { RepeatedSlotOf } from '../repeated-slots.js'\nimport { repeatedSlot, slot, type SkinEntry } from '../skin.js'\nimport type { UiLink, UiPhone } from '../types.js'\n\n/** `phone` and `link` are the classes for the nested `PhoneLink` and the email and areas links. */\nexport type ContactBlockSlot =\n | 'root'\n | 'inner'\n | 'form'\n | 'details'\n | 'heading'\n | 'list'\n | 'item'\n | 'label'\n | 'value'\n | 'line'\n | 'phone'\n | 'link'\n/** Rendered once per line of hours, address, licences or areas: styled from the skin stylesheet, never by class (3.14.0). */\nexport type ContactBlockRepeatedSlot = RepeatedSlotOf<'contactBlock'>\nexport type ContactBlockSkin = SkinEntry<ContactBlockSlot, never, { formSide?: 'start' | 'end' }, ContactBlockRepeatedSlot>\n\n/** A labelled detail of one or more lines, for example hours or a licence. */\nexport interface ContactDetailLines {\n readonly label: string\n readonly lines: readonly string[]\n}\n\nexport interface ContactBlockProps extends ContactBlockSkin {\n /** The details' H2, for example \"Contact details\". */\n readonly title: ReactNode\n /** The site's quote form (`QuoteForm`, plain). */\n readonly form: ReactNode\n /** From the derived business facts; never typed by hand (C10). */\n readonly phone?: { readonly label: string; readonly phone: UiPhone }\n readonly email?: { readonly label: string; readonly address: string }\n readonly hours?: ContactDetailLines\n /** The public address, only where it shows (customers visit, and Romie said so). Never derived here. */\n readonly address?: ContactDetailLines\n readonly licences?: ContactDetailLines\n readonly areas?: { readonly label: string; readonly text: string; readonly link?: UiLink }\n /** Defaults to `contact-heading`. */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n}\n\nconst EMAIL = /^[^\\s@<>()]+@[^\\s@<>()]+\\.[^\\s@<>()]+$/\n\nexport function ContactBlock({\n title,\n form,\n phone,\n email,\n hours,\n address,\n licences,\n areas,\n headingId = 'contact-heading',\n tone,\n layout,\n classes,\n}: ContactBlockProps) {\n if (email && !EMAIL.test(email.address)) throw new Error(`ContactBlock: \"${email.address}\" is not an email address.`)\n const item = (key: string, label: string, value: ReactNode) => (\n <div key={key} {...slot(classes, 'item')} data-item={key}>\n <dt {...slot(classes, 'label')}>{label}</dt>\n <dd {...slot(classes, 'value')}>{value}</dd>\n </div>\n )\n const lines = (detail: ContactDetailLines) =>\n detail.lines.map((line) => (\n <span key={line} {...repeatedSlot('line')}>\n {line}\n </span>\n ))\n return (\n <Section\n id=\"contact\"\n labelledBy={headingId}\n tone={tone}\n classes={classes}\n phonePlacement=\"contact\"\n hooks={{ 'data-form-side': layout?.formSide ?? 'start' }}\n >\n <div id=\"quote\" {...slot(classes, 'form')}>\n {form}\n </div>\n <div {...slot(classes, 'details')}>\n <h2 id={headingId} {...slot(classes, 'heading')}>\n {title}\n </h2>\n {phone || email || hours || address || licences || areas ? (\n <dl {...slot(classes, 'list')}>\n {phone ? item('phone', phone.label, <PhoneLink phone={phone.phone} placement=\"contact\" classes={{ root: classes?.phone }} />) : null}\n {email ? item('email', email.label, <a href={`mailto:${email.address}`} {...slot(classes, 'link')}>\n {email.address}\n </a>) : null}\n {hours ? item('hours', hours.label, lines(hours)) : null}\n {address ? item('address', address.label, lines(address)) : null}\n {licences ? item('licences', licences.label, lines(licences)) : null}\n {areas\n ? item(\n 'areas',\n areas.label,\n <>\n <span {...repeatedSlot('line')}>{areas.text}</span>\n {areas.link ? (\n <span {...repeatedSlot('line')}>\n <TextLink href={areas.link.href} classes={{ root: classes?.link }}>\n {areas.link.label}\n </TextLink>\n </span>\n ) : null}\n </>,\n )\n : null}\n </dl>\n ) : null}\n </div>\n </Section>\n )\n}\n"]}
@@ -6,14 +6,17 @@
6
6
  import type { ReactNode } from 'react';
7
7
  import { type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order';
8
8
  import { type SectionHeadingSlot, type SectionSlot, type SectionTone } from '../primitives/layout.js';
9
+ import type { RepeatedSlotOf } from '../repeated-slots.js';
9
10
  import { type SkinEntry } from '../skin.js';
10
11
  import { type AccordionSlot } from './accordion.js';
11
12
  export declare const FAQS_VARIANTS: readonly ["list", "grouped"];
12
13
  export type FAQsVariant = (typeof FAQS_VARIANTS)[number];
13
14
  export type FAQsSlot = SectionSlot | SectionHeadingSlot | 'columns' | 'column' | 'group' | 'subheading' | 'accordion' | Exclude<AccordionSlot, 'root'>;
15
+ /** Rendered once per column, group or question: styled from the skin stylesheet, never by class (3.14.0). */
16
+ export type FAQsRepeatedSlot = RepeatedSlotOf<'faqs'>;
14
17
  export type FAQsSkin = SkinEntry<FAQsSlot, FAQsVariant, {
15
18
  columns?: 1 | 2;
16
- }>;
19
+ }, FAQsRepeatedSlot>;
17
20
  export interface FAQItem {
18
21
  /** A stable slug, unique on the page. */
19
22
  readonly id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"faqs.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/faqs.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAiB,KAAK,wBAAwB,EAAE,MAAM,2CAA2C,CAAA;AACxG,OAAO,EAA2B,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC9H,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAE9D,eAAO,MAAM,aAAa,8BAA+B,CAAA;AACzD,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAA;AACxD,MAAM,MAAM,QAAQ,GAChB,WAAW,GACX,kBAAkB,GAClB,SAAS,GACT,QAAQ,GACR,OAAO,GACP,YAAY,GACZ,WAAW,GACX,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC,CAAA;AAClC,MAAM,MAAM,QAAQ,GAAG,SAAS,CAAC,QAAQ,EAAE,WAAW,EAAE;IAAE,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;CAAE,CAAC,CAAA;AAE5E,MAAM,WAAW,OAAO;IACtB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAA;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,SAAU,SAAQ,QAAQ;IACzC,cAAc;IACd,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAA;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,CAAA;IAClC,mEAAmE;IACnE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,iEAAiE;IACjE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,kCAAkC;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,gCAAgC;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAC5B;AAED,0EAA0E;AAC1E,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,wBAAwB,CAEvF;AAeD,wBAAgB,IAAI,CAAC,EACnB,KAAK,EACL,OAAO,EACP,IAAI,EACJ,KAAK,EACL,WAAW,EACX,IAAY,EACZ,SAA0B,EAC1B,IAAI,EACJ,OAAgB,EAChB,MAAM,EACN,OAAO,GACR,EAAE,SAAS,kDAwCX"}
1
+ {"version":3,"file":"faqs.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/faqs.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAiB,KAAK,wBAAwB,EAAE,MAAM,2CAA2C,CAAA;AACxG,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,EAAsB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AAC/D,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAE9D,eAAO,MAAM,aAAa,8BAA+B,CAAA;AACzD,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAA;AACxD,MAAM,MAAM,QAAQ,GAChB,WAAW,GACX,kBAAkB,GAClB,SAAS,GACT,QAAQ,GACR,OAAO,GACP,YAAY,GACZ,WAAW,GACX,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC,CAAA;AAClC,6GAA6G;AAC7G,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,MAAM,CAAC,CAAA;AACrD,MAAM,MAAM,QAAQ,GAAG,SAAS,CAAC,QAAQ,EAAE,WAAW,EAAE;IAAE,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;CAAE,EAAE,gBAAgB,CAAC,CAAA;AAE9F,MAAM,WAAW,OAAO;IACtB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAA;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,SAAU,SAAQ,QAAQ;IACzC,cAAc;IACd,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAA;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,CAAA;IAClC,mEAAmE;IACnE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,iEAAiE;IACjE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,kCAAkC;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,gCAAgC;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAC5B;AAED,0EAA0E;AAC1E,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,wBAAwB,CAEvF;AAeD,wBAAgB,IAAI,CAAC,EACnB,KAAK,EACL,OAAO,EACP,IAAI,EACJ,KAAK,EACL,WAAW,EACX,IAAY,EACZ,SAA0B,EAC1B,IAAI,EACJ,OAAgB,EAChB,MAAM,EACN,OAAO,GACR,EAAE,SAAS,kDAsCX"}
@@ -1,7 +1,7 @@
1
1
  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 { Section, SectionHeading } from '../primitives/layout.js';
4
- import { slot } from '../skin.js';
4
+ import { repeatedSlot, slot } from '../skin.js';
5
5
  import { Accordion } from './accordion.js';
6
6
  export const FAQS_VARIANTS = ['list', 'grouped'];
7
7
  /** Whether the section renders: nothing without questions (`no-faqs`). */
@@ -30,11 +30,10 @@ export function FAQs({ title, eyebrow, lead, items, defaultOpen, name = 'faq', h
30
30
  throw new Error(`FAQs: defaultOpen "${defaultOpen}" is not a question on this page.`);
31
31
  }
32
32
  const columns = layout?.columns ?? 1;
33
- const accordionClasses = { ...classes, root: classes?.accordion };
34
- const accordion = (list) => (_jsx(Accordion, { name: name, defaultOpen: list.some((item) => item.id === defaultOpen) ? defaultOpen : undefined, items: list.map((item) => ({ id: item.id, summary: item.question, content: item.answer })), classes: accordionClasses }));
33
+ const accordion = (list) => (_jsx(Accordion, { name: name, defaultOpen: list.some((item) => item.id === defaultOpen) ? defaultOpen : undefined, items: list.map((item) => ({ id: item.id, summary: item.question, content: item.answer })) }));
35
34
  const groups = variant === 'grouped' ? groupItems(items) : null;
36
35
  return (_jsxs(Section, { id: "faqs", labelledBy: headingId, tone: tone, variant: variant, classes: classes, children: [_jsx(SectionHeading, { id: headingId, title: title, eyebrow: eyebrow, lead: lead, classes: classes }), _jsx("div", { ...slot(classes, 'columns'), "data-columns": String(columns), children: groups
37
- ? halves(groups, columns).map((column, index) => (_jsx("div", { ...slot(classes, 'column'), children: column.map(([group, list]) => (_jsxs("div", { ...slot(classes, 'group'), children: [group ? _jsx("h3", { ...slot(classes, 'subheading'), children: group }) : null, accordion(list)] }, group))) }, index)))
38
- : halves(items, columns).map((column, index) => (_jsx("div", { ...slot(classes, 'column'), children: accordion(column) }, index))) })] }));
36
+ ? halves(groups, columns).map((column, index) => (_jsx("div", { ...repeatedSlot('column'), children: column.map(([group, list]) => (_jsxs("div", { ...repeatedSlot('group'), children: [group ? _jsx("h3", { ...repeatedSlot('subheading'), children: group }) : null, accordion(list)] }, group))) }, index)))
37
+ : halves(items, columns).map((column, index) => (_jsx("div", { ...repeatedSlot('column'), children: accordion(column) }, index))) })] }));
39
38
  }
40
39
  //# sourceMappingURL=faqs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"faqs.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/faqs.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `FAQs` (S9, `faqs`): real questions and answers from the page's data, on `Accordion` (closed, one open at a time,\n * answers in the server HTML). Never emits `FAQPage` or any other schema (F14). Variants: `list`, or `grouped` for\n * the FAQs page (an H3 per group); one or two columns.\n */\nimport type { ReactNode } from 'react'\nimport { SECTION_READY, type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order'\nimport { Section, SectionHeading, type SectionHeadingSlot, type SectionSlot, type SectionTone } from '../primitives/layout.js'\nimport { slot, type SkinEntry } from '../skin.js'\nimport { Accordion, type AccordionSlot } from './accordion.js'\n\nexport const FAQS_VARIANTS = ['list', 'grouped'] as const\nexport type FAQsVariant = (typeof FAQS_VARIANTS)[number]\nexport type FAQsSlot =\n | SectionSlot\n | SectionHeadingSlot\n | 'columns'\n | 'column'\n | 'group'\n | 'subheading'\n | 'accordion'\n | Exclude<AccordionSlot, 'root'>\nexport type FAQsSkin = SkinEntry<FAQsSlot, FAQsVariant, { columns?: 1 | 2 }>\n\nexport interface FAQItem {\n /** A stable slug, unique on the page. */\n readonly id: string\n readonly question: string\n readonly answer: ReactNode\n /** The group on the FAQs page (`grouped`), for example \"Prices and quotes\". */\n readonly group?: string\n}\n\nexport interface FAQsProps extends FAQsSkin {\n /** The H2. */\n readonly title: ReactNode\n readonly eyebrow?: ReactNode\n readonly lead?: ReactNode\n readonly items: readonly FAQItem[]\n /** The question open on load, only where the site recorded one. */\n readonly defaultOpen?: string\n /** The accordion group name and id prefix. Defaults to `faq`. */\n readonly name?: string\n /** Defaults to `faqs-heading`. */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n}\n\n/** Whether the section renders: nothing without questions (`no-faqs`). */\nexport function faqsReady({ items }: Pick<FAQsProps, 'items'>): BrandlabSectionReadiness {\n return items.length > 0 ? SECTION_READY : { render: false, reason: 'no-faqs' }\n}\n\n/** Questions by group, in first-seen order; questions without a group come under no subheading. */\nfunction groupItems(items: readonly FAQItem[]): (readonly [string, readonly FAQItem[]])[] {\n const groups = new Map<string, FAQItem[]>()\n for (const item of items) groups.set(item.group ?? '', [...(groups.get(item.group ?? '') ?? []), item])\n return [...groups]\n}\n\nfunction halves<T>(list: readonly T[], columns: 1 | 2): (readonly T[])[] {\n if (columns === 1 || list.length < 2) return [list]\n const split = Math.ceil(list.length / 2)\n return [list.slice(0, split), list.slice(split)]\n}\n\nexport function FAQs({\n title,\n eyebrow,\n lead,\n items,\n defaultOpen,\n name = 'faq',\n headingId = 'faqs-heading',\n tone,\n variant = 'list',\n layout,\n classes,\n}: FAQsProps) {\n if (!faqsReady({ items }).render) return null\n if (new Set(items.map((item) => item.id)).size !== items.length) throw new Error('FAQs: question ids must be unique on the page.')\n if (defaultOpen !== undefined && !items.some((item) => item.id === defaultOpen)) {\n throw new Error(`FAQs: defaultOpen \"${defaultOpen}\" is not a question on this page.`)\n }\n const columns = layout?.columns ?? 1\n const accordionClasses = { ...classes, root: classes?.accordion }\n const accordion = (list: readonly FAQItem[]) => (\n <Accordion\n name={name}\n defaultOpen={list.some((item) => item.id === defaultOpen) ? defaultOpen : undefined}\n items={list.map((item) => ({ id: item.id, summary: item.question, content: item.answer }))}\n classes={accordionClasses}\n />\n )\n const groups = variant === 'grouped' ? groupItems(items) : null\n return (\n <Section id=\"faqs\" labelledBy={headingId} tone={tone} variant={variant} classes={classes}>\n <SectionHeading id={headingId} title={title} eyebrow={eyebrow} lead={lead} classes={classes} />\n <div {...slot(classes, 'columns')} data-columns={String(columns)}>\n {groups\n ? halves(groups, columns).map((column, index) => (\n <div key={index} {...slot(classes, 'column')}>\n {column.map(([group, list]) => (\n <div key={group} {...slot(classes, 'group')}>\n {group ? <h3 {...slot(classes, 'subheading')}>{group}</h3> : null}\n {accordion(list)}\n </div>\n ))}\n </div>\n ))\n : halves(items, columns).map((column, index) => (\n <div key={index} {...slot(classes, 'column')}>\n {accordion(column)}\n </div>\n ))}\n </div>\n </Section>\n )\n}\n"]}
1
+ {"version":3,"file":"faqs.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/faqs.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `FAQs` (S9, `faqs`): real questions and answers from the page's data, on `Accordion` (closed, one open at a time,\n * answers in the server HTML). Never emits `FAQPage` or any other schema (F14). Variants: `list`, or `grouped` for\n * the FAQs page (an H3 per group); one or two columns.\n */\nimport type { ReactNode } from 'react'\nimport { SECTION_READY, type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order'\nimport { Section, SectionHeading, type SectionHeadingSlot, type SectionSlot, type SectionTone } from '../primitives/layout.js'\nimport type { RepeatedSlotOf } from '../repeated-slots.js'\nimport { repeatedSlot, slot, type SkinEntry } from '../skin.js'\nimport { Accordion, type AccordionSlot } from './accordion.js'\n\nexport const FAQS_VARIANTS = ['list', 'grouped'] as const\nexport type FAQsVariant = (typeof FAQS_VARIANTS)[number]\nexport type FAQsSlot =\n | SectionSlot\n | SectionHeadingSlot\n | 'columns'\n | 'column'\n | 'group'\n | 'subheading'\n | 'accordion'\n | Exclude<AccordionSlot, 'root'>\n/** Rendered once per column, group or question: styled from the skin stylesheet, never by class (3.14.0). */\nexport type FAQsRepeatedSlot = RepeatedSlotOf<'faqs'>\nexport type FAQsSkin = SkinEntry<FAQsSlot, FAQsVariant, { columns?: 1 | 2 }, FAQsRepeatedSlot>\n\nexport interface FAQItem {\n /** A stable slug, unique on the page. */\n readonly id: string\n readonly question: string\n readonly answer: ReactNode\n /** The group on the FAQs page (`grouped`), for example \"Prices and quotes\". */\n readonly group?: string\n}\n\nexport interface FAQsProps extends FAQsSkin {\n /** The H2. */\n readonly title: ReactNode\n readonly eyebrow?: ReactNode\n readonly lead?: ReactNode\n readonly items: readonly FAQItem[]\n /** The question open on load, only where the site recorded one. */\n readonly defaultOpen?: string\n /** The accordion group name and id prefix. Defaults to `faq`. */\n readonly name?: string\n /** Defaults to `faqs-heading`. */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n}\n\n/** Whether the section renders: nothing without questions (`no-faqs`). */\nexport function faqsReady({ items }: Pick<FAQsProps, 'items'>): BrandlabSectionReadiness {\n return items.length > 0 ? SECTION_READY : { render: false, reason: 'no-faqs' }\n}\n\n/** Questions by group, in first-seen order; questions without a group come under no subheading. */\nfunction groupItems(items: readonly FAQItem[]): (readonly [string, readonly FAQItem[]])[] {\n const groups = new Map<string, FAQItem[]>()\n for (const item of items) groups.set(item.group ?? '', [...(groups.get(item.group ?? '') ?? []), item])\n return [...groups]\n}\n\nfunction halves<T>(list: readonly T[], columns: 1 | 2): (readonly T[])[] {\n if (columns === 1 || list.length < 2) return [list]\n const split = Math.ceil(list.length / 2)\n return [list.slice(0, split), list.slice(split)]\n}\n\nexport function FAQs({\n title,\n eyebrow,\n lead,\n items,\n defaultOpen,\n name = 'faq',\n headingId = 'faqs-heading',\n tone,\n variant = 'list',\n layout,\n classes,\n}: FAQsProps) {\n if (!faqsReady({ items }).render) return null\n if (new Set(items.map((item) => item.id)).size !== items.length) throw new Error('FAQs: question ids must be unique on the page.')\n if (defaultOpen !== undefined && !items.some((item) => item.id === defaultOpen)) {\n throw new Error(`FAQs: defaultOpen \"${defaultOpen}\" is not a question on this page.`)\n }\n const columns = layout?.columns ?? 1\n const accordion = (list: readonly FAQItem[]) => (\n <Accordion\n name={name}\n defaultOpen={list.some((item) => item.id === defaultOpen) ? defaultOpen : undefined}\n items={list.map((item) => ({ id: item.id, summary: item.question, content: item.answer }))}\n />\n )\n const groups = variant === 'grouped' ? groupItems(items) : null\n return (\n <Section id=\"faqs\" labelledBy={headingId} tone={tone} variant={variant} classes={classes}>\n <SectionHeading id={headingId} title={title} eyebrow={eyebrow} lead={lead} classes={classes} />\n <div {...slot(classes, 'columns')} data-columns={String(columns)}>\n {groups\n ? halves(groups, columns).map((column, index) => (\n <div key={index} {...repeatedSlot('column')}>\n {column.map(([group, list]) => (\n <div key={group} {...repeatedSlot('group')}>\n {group ? <h3 {...repeatedSlot('subheading')}>{group}</h3> : null}\n {accordion(list)}\n </div>\n ))}\n </div>\n ))\n : halves(items, columns).map((column, index) => (\n <div key={index} {...repeatedSlot('column')}>\n {accordion(column)}\n </div>\n ))}\n </div>\n </Section>\n )\n}\n"]}