@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
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ui-kit/primitives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,UAAU,EACV,SAAS,EACT,QAAQ,GAWT,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,WAAW,EACX,aAAa,EACb,OAAO,EACP,cAAc,EACd,cAAc,GASf,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,WAAW,EAAqE,MAAM,mBAAmB,CAAA;AAClH,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,aAAa,EAAmC,MAAM,aAAa,CAAA;AAC9F,OAAO,EAAE,eAAe,EAA+D,MAAM,aAAa,CAAA;AAC1G,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,GAGlB,MAAM,gBAAgB,CAAA","sourcesContent":["export {\n BUTTON_VARIANTS,\n ButtonLink,\n PhoneLink,\n TextLink,\n type ButtonLinkProps,\n type ButtonLinkSkin,\n type ButtonLinkSlot,\n type ButtonVariant,\n type PhoneLinkProps,\n type PhoneLinkSkin,\n type PhoneLinkSlot,\n type TextLinkProps,\n type TextLinkSkin,\n type TextLinkSlot,\n} from './links.js'\nexport {\n CONTAINER_WIDTHS,\n Container,\n RatingStars,\n SECTION_TONES,\n Section,\n SectionHeading,\n VisuallyHidden,\n type ContainerSkin,\n type ContainerWidth,\n type SectionHeadingProps,\n type SectionHeadingSkin,\n type SectionHeadingSlot,\n type SectionProps,\n type SectionSlot,\n type SectionTone,\n} from './layout.js'\nexport { QuoteButton, type QuoteButtonProps, type QuoteButtonSkin, type QuoteButtonSlot } from './quote-button.js'\nexport { Icon, SocialIcon, UI_ICON_NAMES, type IconProps, type UiIconName } from '../icons.js'\nexport { UI_SOCIAL_KINDS, type UiLink, type UiPhone, type UiSocial, type UiSocialKind } from '../types.js'\nexport {\n INTENT_PREFETCH_SELECTOR,\n LINK_PREFETCH_LIMITS,\n LINK_PREFETCH_POLICY,\n linkPrefetchProps,\n type LinkPrefetch,\n type LinkPrefetchRole,\n} from '../prefetch.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ui-kit/primitives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,UAAU,EACV,SAAS,EACT,QAAQ,GAWT,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,OAAO,EACP,WAAW,EACX,aAAa,EACb,OAAO,EACP,cAAc,EACd,cAAc,GAWf,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,MAAM,EAAsD,MAAM,aAAa,CAAA;AACxF,OAAO,EAAE,IAAI,EAAiE,MAAM,WAAW,CAAA;AAC/F,OAAO,EAAE,KAAK,EAAE,IAAI,EAAiG,MAAM,YAAY,CAAA;AACvI,OAAO,EAAE,IAAI,EAAgD,MAAM,WAAW,CAAA;AAC9E,OAAO,EACL,eAAe,EACf,QAAQ,EACR,QAAQ,GAYT,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,kBAAkB,EAClB,UAAU,GAMX,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,wBAAwB,EAAE,KAAK,EAAqE,MAAM,YAAY,CAAA;AAC/H,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,iBAAiB,GAMlB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,WAAW,EAAqE,MAAM,mBAAmB,CAAA;AAClH,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAmC,MAAM,aAAa,CAAA;AAC9H,OAAO,EAAE,eAAe,EAAoC,MAAM,mBAAmB,CAAA;AACrF,OAAO,EAAE,eAAe,EAA+D,MAAM,aAAa,CAAA;AAC1G,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,GAGlB,MAAM,gBAAgB,CAAA","sourcesContent":["export {\n BUTTON_VARIANTS,\n ButtonLink,\n PhoneLink,\n TextLink,\n type ButtonLinkProps,\n type ButtonLinkSkin,\n type ButtonLinkSlot,\n type ButtonVariant,\n type PhoneLinkProps,\n type PhoneLinkSkin,\n type PhoneLinkSlot,\n type TextLinkProps,\n type TextLinkSkin,\n type TextLinkSlot,\n} from './links.js'\nexport {\n CONTAINER_WIDTHS,\n Container,\n Eyebrow,\n RatingStars,\n SECTION_TONES,\n Section,\n SectionHeading,\n VisuallyHidden,\n type ContainerSkin,\n type ContainerWidth,\n type EyebrowSkin,\n type EyebrowSlot,\n type SectionHeadingProps,\n type SectionHeadingSkin,\n type SectionHeadingSlot,\n type SectionProps,\n type SectionSlot,\n type SectionTone,\n} from './layout.js'\nexport { Button, type ButtonProps, type ButtonSkin, type ButtonSlot } from './button.js'\nexport { Card, type CardLayout, type CardProps, type CardSkin, type CardSlot } from './card.js'\nexport { Badge, Chip, type BadgeProps, type BadgeSkin, type BadgeSlot, type ChipProps, type ChipSkin, type ChipSlot } from './badge.js'\nexport { Stat, type StatProps, type StatSkin, type StatSlot } from './stat.js'\nexport {\n LINK_LIST_KINDS,\n LinkList,\n TickList,\n type LinkListKind,\n type LinkListLayout,\n type LinkListProps,\n type LinkListSkin,\n type LinkListSlot,\n type TickItem,\n type TickListLayout,\n type TickListProps,\n type TickListRepeatedSlot,\n type TickListSkin,\n type TickListSlot,\n} from './lists.js'\nexport {\n ILLUSTRATION_LABEL,\n MediaFrame,\n type MediaFrameProps,\n type MediaFrameSizing,\n type MediaFrameSkin,\n type MediaFrameSlot,\n type UiMedia,\n} from './media-frame.js'\nexport { MEDIA_ILLUSTRATION_LABEL, Media, type MediaLayout, type MediaProps, type MediaSkin, type MediaSlot } from './media.js'\nexport {\n BREADCRUMBS_LABEL,\n Breadcrumbs,\n breadcrumbsSchema,\n type BreadcrumbTrail,\n type BreadcrumbsProps,\n type BreadcrumbsRepeatedSlot,\n type BreadcrumbsSkin,\n type BreadcrumbsSlot,\n} from './breadcrumbs.js'\nexport { QuoteButton, type QuoteButtonProps, type QuoteButtonSkin, type QuoteButtonSlot } from './quote-button.js'\nexport { Icon, SocialIcon, UI_ICON_NAMES, createSiteIcon, iconSpriteHref, type IconProps, type UiIconName } from '../icons.js'\nexport { defineSiteIcons, type IconGlyph, type SiteIconSet } from '../icon-glyphs.js'\nexport { UI_SOCIAL_KINDS, type UiLink, type UiPhone, type UiSocial, type UiSocialKind } from '../types.js'\nexport {\n INTENT_PREFETCH_SELECTOR,\n LINK_PREFETCH_LIMITS,\n LINK_PREFETCH_POLICY,\n linkPrefetchProps,\n type LinkPrefetch,\n type LinkPrefetchRole,\n} from '../prefetch.js'\n"]}
@@ -57,9 +57,21 @@ export interface SectionHeadingProps extends SectionHeadingSkin {
57
57
  }
58
58
  /** A section's heading block: an optional eyebrow, the H2 and an optional lead paragraph. */
59
59
  export declare function SectionHeading({ id, title, eyebrow, lead, layout, classes }: SectionHeadingProps): import("react/jsx-runtime").JSX.Element;
60
+ export type EyebrowSlot = 'root';
61
+ export type EyebrowSkin = SkinEntry<EyebrowSlot>;
62
+ /**
63
+ * A short label above a site's own heading (3.14.0), for signature sections that don't use `SectionHeading`. At
64
+ * least 13 px; never the only label for a section. `SectionHeading` and the hero keep their own `eyebrow` slot.
65
+ */
66
+ export declare function Eyebrow({ children, classes }: EyebrowSkin & {
67
+ readonly children: ReactNode;
68
+ }): import("react/jsx-runtime").JSX.Element;
60
69
  /**
61
70
  * Five stars with one text alternative ("Rated 4.9 out of 5"). The stars use `--bl-color-star`; whole stars only,
62
71
  * rounded to the nearest. The rating always comes from the feed, never typed by hand (T4).
72
+ *
73
+ * From 3.14.0 the row is one `<svg>` holding five `<use>` references to the sprite's star (`data-slot="star"`,
74
+ * `data-state="on" | "off"`), not five icons; without the sprite each star is a `<g>` with the glyph inline.
63
75
  */
64
76
  export declare function RatingStars({ rating, label, className }: {
65
77
  readonly rating: number;
@@ -1 +1 @@
1
- {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/primitives/layout.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/primitives/layout.tsx"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Icon } from '../icons.js';
2
+ import { starGlyph } from '../icons.js';
3
3
  import { slot } from '../skin.js';
4
4
  export const CONTAINER_WIDTHS = ['default', 'narrow', 'wide'];
5
5
  /** One container width shared by the header, the page and the footer (`--bl-container`, `--bl-gutter`). */
@@ -19,13 +19,31 @@ export function Section({ id, labelledBy, tone, variant, classes, decoration, ho
19
19
  export function SectionHeading({ id, title, eyebrow, lead, layout, classes }) {
20
20
  return (_jsxs("div", { ...slot(classes, 'head'), "data-align": layout?.align ?? 'start', children: [eyebrow ? _jsx("p", { ...slot(classes, 'eyebrow'), children: eyebrow }) : null, _jsx("h2", { id: id, ...slot(classes, 'heading'), children: title }), lead ? _jsx("p", { ...slot(classes, 'lead'), children: lead }) : null] }));
21
21
  }
22
+ /**
23
+ * A short label above a site's own heading (3.14.0), for signature sections that don't use `SectionHeading`. At
24
+ * least 13 px; never the only label for a section. `SectionHeading` and the hero keep their own `eyebrow` slot.
25
+ */
26
+ export function Eyebrow({ children, classes }) {
27
+ return (_jsx("p", { "data-bl": "eyebrow", className: classes?.root, children: children }));
28
+ }
29
+ /** The rating row's geometry: five 20-unit stars, 2 units apart, in one `viewBox`. */
30
+ const STAR_SIZE = 20;
31
+ const STAR_STRIDE = 22;
32
+ const STARS_VIEW_BOX = `0 0 ${STAR_STRIDE * 4 + STAR_SIZE} ${STAR_SIZE}`;
22
33
  /**
23
34
  * Five stars with one text alternative ("Rated 4.9 out of 5"). The stars use `--bl-color-star`; whole stars only,
24
35
  * rounded to the nearest. The rating always comes from the feed, never typed by hand (T4).
36
+ *
37
+ * From 3.14.0 the row is one `<svg>` holding five `<use>` references to the sprite's star (`data-slot="star"`,
38
+ * `data-state="on" | "off"`), not five icons; without the sprite each star is a `<g>` with the glyph inline.
25
39
  */
26
40
  export function RatingStars({ rating, label, className }) {
27
41
  const filled = Math.max(0, Math.min(5, Math.round(rating)));
28
- return (_jsx("span", { "data-bl": "stars", role: "img", "aria-label": label ?? `Rated ${rating.toFixed(1)} out of 5`, className: className, children: Array.from({ length: 5 }, (_, index) => (_jsx("span", { "data-slot": "star", "data-state": index < filled ? 'on' : 'off', children: _jsx(Icon, { name: "star" }) }, index))) }));
42
+ const star = starGlyph();
43
+ return (_jsx("span", { "data-bl": "stars", role: "img", "aria-label": label ?? `Rated ${rating.toFixed(1)} out of 5`, className: className, children: _jsx("svg", { "aria-hidden": "true", viewBox: STARS_VIEW_BOX, children: Array.from({ length: 5 }, (_, index) => {
44
+ const state = index < filled ? 'on' : 'off';
45
+ return star.href ? (_jsx("use", { href: star.href, x: index * STAR_STRIDE, width: STAR_SIZE, height: STAR_SIZE, "data-slot": "star", "data-state": state }, index)) : (_jsx("g", { transform: index === 0 ? undefined : `translate(${index * STAR_STRIDE})`, "data-slot": "star", "data-state": state, dangerouslySetInnerHTML: { __html: star.glyph.body } }, index));
46
+ }) }) }));
29
47
  }
30
48
  /** Text for assistive technology only. */
31
49
  export function VisuallyHidden({ children }) {
@@ -1 +1 @@
1
- {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../../src/ui-kit/primitives/layout.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * Layout primitives: `Container`, `Section`, `SectionHeading`, `Eyebrow`, `RatingStars` and `VisuallyHidden`.\n * Server components. Heading levels come from the page outline (one H1 in the hero or page header, an H2 per\n * section), never from a look.\n */\nimport type { ReactNode } from 'react'\nimport type { PhoneCallPlacement } from '../../analytics.js'\nimport { Icon } from '../icons.js'\nimport { slot, type SkinEntry } from '../skin.js'\n\nexport const CONTAINER_WIDTHS = ['default', 'narrow', 'wide'] as const\nexport type ContainerWidth = (typeof CONTAINER_WIDTHS)[number]\nexport type ContainerSkin = SkinEntry<'root', never, { width?: ContainerWidth }>\n\n/** One container width shared by the header, the page and the footer (`--bl-container`, `--bl-gutter`). */\nexport function Container({ children, layout, classes }: ContainerSkin & { readonly children: ReactNode }) {\n return (\n <div data-bl=\"container\" data-width={layout?.width ?? 'default'} className={classes?.root}>\n {children}\n </div>\n )\n}\n\n/** The four section tones `SectionSequence` assigns: two alternating grounds, one optional band, one photo ground. */\nexport const SECTION_TONES = ['canvas', 'surface', 'band', 'photo'] as const\nexport type SectionTone = (typeof SECTION_TONES)[number]\n\nexport type SectionSlot = 'root' | 'inner'\n\nexport interface SectionProps {\n /** The matrix id, rendered as `data-bl-section` (for example `reviews`, `cta`, `faqs`). */\n readonly id: string\n /** The id of the section's H2. A section is a `<section>` labelled by its heading. */\n readonly labelledBy: string\n readonly tone?: SectionTone | undefined\n readonly variant?: string | undefined\n readonly classes?: Readonly<Partial<Record<SectionSlot, string>>> | undefined\n /** Signature art, rendered `aria-hidden`. */\n readonly decoration?: ReactNode\n /**\n * Extra layout hooks on the root, for example `{ 'data-columns': '3' }`. They cannot override `data-bl-section`,\n * `data-variant`, `data-tone` or `data-phone-location`.\n */\n readonly hooks?: Readonly<Record<`data-${string}`, string | undefined>>\n /** The placement a `tel:` link inside the section reports when it has none of its own. */\n readonly phonePlacement?: PhoneCallPlacement | undefined\n readonly children: ReactNode\n}\n\n/**\n * The root every section component renders: `<section data-bl-section aria-labelledby>`, its tone and variant, an\n * optional `aria-hidden` decoration and the inner container slot.\n */\nexport function Section({ id, labelledBy, tone, variant, classes, decoration, hooks, phonePlacement, children }: SectionProps) {\n return (\n <section\n {...hooks}\n data-bl-section={id}\n data-variant={variant}\n data-tone={tone}\n data-phone-location={phonePlacement}\n aria-labelledby={labelledBy}\n className={classes?.root}\n >\n {decoration ? (\n <div data-slot=\"decoration\" aria-hidden=\"true\">\n {decoration}\n </div>\n ) : null}\n <div {...slot(classes, 'inner')}>{children}</div>\n </section>\n )\n}\n\nexport type SectionHeadingSlot = 'head' | 'eyebrow' | 'heading' | 'lead'\nexport type SectionHeadingSkin = SkinEntry<SectionHeadingSlot, never, { align?: 'start' | 'center' }>\n\nexport interface SectionHeadingProps extends SectionHeadingSkin {\n /** The H2's id, which the section's `aria-labelledby` points at. */\n readonly id: string\n readonly title: ReactNode\n /** A short label above the H2 (at least 13 px; never the only label for the section). */\n readonly eyebrow?: ReactNode\n readonly lead?: ReactNode\n}\n\n/** A section's heading block: an optional eyebrow, the H2 and an optional lead paragraph. */\nexport function SectionHeading({ id, title, eyebrow, lead, layout, classes }: SectionHeadingProps) {\n return (\n <div {...slot(classes, 'head')} data-align={layout?.align ?? 'start'}>\n {eyebrow ? <p {...slot(classes, 'eyebrow')}>{eyebrow}</p> : null}\n <h2 id={id} {...slot(classes, 'heading')}>\n {title}\n </h2>\n {lead ? <p {...slot(classes, 'lead')}>{lead}</p> : null}\n </div>\n )\n}\n\n/**\n * Five stars with one text alternative (\"Rated 4.9 out of 5\"). The stars use `--bl-color-star`; whole stars only,\n * rounded to the nearest. The rating always comes from the feed, never typed by hand (T4).\n */\nexport function RatingStars({ rating, label, className }: { readonly rating: number; readonly label?: string; readonly className?: string }) {\n const filled = Math.max(0, Math.min(5, Math.round(rating)))\n return (\n <span data-bl=\"stars\" role=\"img\" aria-label={label ?? `Rated ${rating.toFixed(1)} out of 5`} className={className}>\n {Array.from({ length: 5 }, (_, index) => (\n <span key={index} data-slot=\"star\" data-state={index < filled ? 'on' : 'off'}>\n <Icon name=\"star\" />\n </span>\n ))}\n </span>\n )\n}\n\n/** Text for assistive technology only. */\nexport function VisuallyHidden({ children }: { readonly children: ReactNode }) {\n return <span data-bl-visually-hidden=\"\">{children}</span>\n}\n"]}
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../../src/ui-kit/primitives/layout.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * Layout primitives: `Container`, `Section`, `SectionHeading`, `Eyebrow`, `RatingStars` and `VisuallyHidden`.\n * Server components. Heading levels come from the page outline (one H1 in the hero or page header, an H2 per\n * section), never from a look.\n */\nimport type { ReactNode } from 'react'\nimport type { PhoneCallPlacement } from '../../analytics.js'\nimport { starGlyph } from '../icons.js'\nimport { slot, type SkinEntry } from '../skin.js'\n\nexport const CONTAINER_WIDTHS = ['default', 'narrow', 'wide'] as const\nexport type ContainerWidth = (typeof CONTAINER_WIDTHS)[number]\nexport type ContainerSkin = SkinEntry<'root', never, { width?: ContainerWidth }>\n\n/** One container width shared by the header, the page and the footer (`--bl-container`, `--bl-gutter`). */\nexport function Container({ children, layout, classes }: ContainerSkin & { readonly children: ReactNode }) {\n return (\n <div data-bl=\"container\" data-width={layout?.width ?? 'default'} className={classes?.root}>\n {children}\n </div>\n )\n}\n\n/** The four section tones `SectionSequence` assigns: two alternating grounds, one optional band, one photo ground. */\nexport const SECTION_TONES = ['canvas', 'surface', 'band', 'photo'] as const\nexport type SectionTone = (typeof SECTION_TONES)[number]\n\nexport type SectionSlot = 'root' | 'inner'\n\nexport interface SectionProps {\n /** The matrix id, rendered as `data-bl-section` (for example `reviews`, `cta`, `faqs`). */\n readonly id: string\n /** The id of the section's H2. A section is a `<section>` labelled by its heading. */\n readonly labelledBy: string\n readonly tone?: SectionTone | undefined\n readonly variant?: string | undefined\n readonly classes?: Readonly<Partial<Record<SectionSlot, string>>> | undefined\n /** Signature art, rendered `aria-hidden`. */\n readonly decoration?: ReactNode\n /**\n * Extra layout hooks on the root, for example `{ 'data-columns': '3' }`. They cannot override `data-bl-section`,\n * `data-variant`, `data-tone` or `data-phone-location`.\n */\n readonly hooks?: Readonly<Record<`data-${string}`, string | undefined>>\n /** The placement a `tel:` link inside the section reports when it has none of its own. */\n readonly phonePlacement?: PhoneCallPlacement | undefined\n readonly children: ReactNode\n}\n\n/**\n * The root every section component renders: `<section data-bl-section aria-labelledby>`, its tone and variant, an\n * optional `aria-hidden` decoration and the inner container slot.\n */\nexport function Section({ id, labelledBy, tone, variant, classes, decoration, hooks, phonePlacement, children }: SectionProps) {\n return (\n <section\n {...hooks}\n data-bl-section={id}\n data-variant={variant}\n data-tone={tone}\n data-phone-location={phonePlacement}\n aria-labelledby={labelledBy}\n className={classes?.root}\n >\n {decoration ? (\n <div data-slot=\"decoration\" aria-hidden=\"true\">\n {decoration}\n </div>\n ) : null}\n <div {...slot(classes, 'inner')}>{children}</div>\n </section>\n )\n}\n\nexport type SectionHeadingSlot = 'head' | 'eyebrow' | 'heading' | 'lead'\nexport type SectionHeadingSkin = SkinEntry<SectionHeadingSlot, never, { align?: 'start' | 'center' }>\n\nexport interface SectionHeadingProps extends SectionHeadingSkin {\n /** The H2's id, which the section's `aria-labelledby` points at. */\n readonly id: string\n readonly title: ReactNode\n /** A short label above the H2 (at least 13 px; never the only label for the section). */\n readonly eyebrow?: ReactNode\n readonly lead?: ReactNode\n}\n\n/** A section's heading block: an optional eyebrow, the H2 and an optional lead paragraph. */\nexport function SectionHeading({ id, title, eyebrow, lead, layout, classes }: SectionHeadingProps) {\n return (\n <div {...slot(classes, 'head')} data-align={layout?.align ?? 'start'}>\n {eyebrow ? <p {...slot(classes, 'eyebrow')}>{eyebrow}</p> : null}\n <h2 id={id} {...slot(classes, 'heading')}>\n {title}\n </h2>\n {lead ? <p {...slot(classes, 'lead')}>{lead}</p> : null}\n </div>\n )\n}\n\nexport type EyebrowSlot = 'root'\nexport type EyebrowSkin = SkinEntry<EyebrowSlot>\n\n/**\n * A short label above a site's own heading (3.14.0), for signature sections that don't use `SectionHeading`. At\n * least 13 px; never the only label for a section. `SectionHeading` and the hero keep their own `eyebrow` slot.\n */\nexport function Eyebrow({ children, classes }: EyebrowSkin & { readonly children: ReactNode }) {\n return (\n <p data-bl=\"eyebrow\" className={classes?.root}>\n {children}\n </p>\n )\n}\n\n/** The rating row's geometry: five 20-unit stars, 2 units apart, in one `viewBox`. */\nconst STAR_SIZE = 20\nconst STAR_STRIDE = 22\nconst STARS_VIEW_BOX = `0 0 ${STAR_STRIDE * 4 + STAR_SIZE} ${STAR_SIZE}`\n\n/**\n * Five stars with one text alternative (\"Rated 4.9 out of 5\"). The stars use `--bl-color-star`; whole stars only,\n * rounded to the nearest. The rating always comes from the feed, never typed by hand (T4).\n *\n * From 3.14.0 the row is one `<svg>` holding five `<use>` references to the sprite's star (`data-slot=\"star\"`,\n * `data-state=\"on\" | \"off\"`), not five icons; without the sprite each star is a `<g>` with the glyph inline.\n */\nexport function RatingStars({ rating, label, className }: { readonly rating: number; readonly label?: string; readonly className?: string }) {\n const filled = Math.max(0, Math.min(5, Math.round(rating)))\n const star = starGlyph()\n return (\n <span data-bl=\"stars\" role=\"img\" aria-label={label ?? `Rated ${rating.toFixed(1)} out of 5`} className={className}>\n <svg aria-hidden=\"true\" viewBox={STARS_VIEW_BOX}>\n {Array.from({ length: 5 }, (_, index) => {\n const state = index < filled ? 'on' : 'off'\n return star.href ? (\n <use key={index} href={star.href} x={index * STAR_STRIDE} width={STAR_SIZE} height={STAR_SIZE} data-slot=\"star\" data-state={state} />\n ) : (\n <g\n key={index}\n transform={index === 0 ? undefined : `translate(${index * STAR_STRIDE})`}\n data-slot=\"star\"\n data-state={state}\n dangerouslySetInnerHTML={{ __html: star.glyph.body }}\n />\n )\n })}\n </svg>\n </span>\n )\n}\n\n/** Text for assistive technology only. */\nexport function VisuallyHidden({ children }: { readonly children: ReactNode }) {\n return <span data-bl-visually-hidden=\"\">{children}</span>\n}\n"]}
@@ -4,6 +4,21 @@ import { type UiIconName } from '../icons.js';
4
4
  import { type LinkPrefetch } from '../prefetch.js';
5
5
  import { type SkinEntry } from '../skin.js';
6
6
  import type { UiPhone } from '../types.js';
7
+ /** Data hooks and the skin class for an anchor's root. */
8
+ export type AnchorHooks = {
9
+ readonly className?: string | undefined;
10
+ } & Readonly<Record<`data-${string}`, string | undefined>>;
11
+ /**
12
+ * The one anchor every ui-kit link renders (internal; not exported from the subpath): `next/link` with the prefetch
13
+ * role for internal links, a plain `<a target="_blank" rel="noopener noreferrer">` for external ones.
14
+ */
15
+ export declare function Anchor({ href, external, prefetch, hooks, children, }: {
16
+ href: string;
17
+ external?: boolean | undefined;
18
+ prefetch?: LinkPrefetch | undefined;
19
+ hooks: AnchorHooks;
20
+ children: ReactNode;
21
+ }): import("react/jsx-runtime").JSX.Element;
7
22
  export type ButtonLinkSlot = 'root' | 'label' | 'icon';
8
23
  export declare const BUTTON_VARIANTS: readonly ["primary", "secondary", "quiet"];
9
24
  export type ButtonVariant = (typeof BUTTON_VARIANTS)[number];
@@ -1 +1 @@
1
- {"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/primitives/links.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAA;AACrE,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAoC1C,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAA;AACtD,eAAO,MAAM,eAAe,4CAA6C,CAAA;AACzE,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAC5D,MAAM,MAAM,cAAc,GAAG,SAAS,CAAC,cAAc,EAAE,aAAa,EAAE;IAAE,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,CAAA;CAAE,CAAC,CAAA;AAErG,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;IAC5B,gEAAgE;IAChE,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAA;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,KAAK,CAAA;IACvC,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAC3B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAA;CACjC;AAED,0FAA0F;AAC1F,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAoB,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAmB,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,eAAe,2CAcnJ;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,CAAA;AACjC,MAAM,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,EAAE,QAAQ,GAAG,YAAY,CAAC,CAAA;AAE3E,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAC3B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAA;CACjC;AAED,yCAAyC;AACzC,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAkB,EAAE,OAAO,EAAE,EAAE,aAAa,2CAM1G;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;AAChE,MAAM,MAAM,aAAa,GAAG,SAAS,CAAC,aAAa,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAA;AAEvE,MAAM,WAAW,cAAe,SAAQ,aAAa;IACnD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,qFAAqF;IACrF,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAA;IACtC,4GAA4G;IAC5G,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,QAAgB,EAAE,OAAgB,EAAE,OAAO,EAAE,EAAE,cAAc,2CAgBjH"}
1
+ {"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/primitives/links.tsx"],"names":[],"mappings":"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"}
@@ -11,7 +11,11 @@ import { slot } from '../skin.js';
11
11
  function isExternal(href) {
12
12
  return /^https?:\/\//i.test(href);
13
13
  }
14
- function Anchor({ href, external, prefetch, hooks, children, }) {
14
+ /**
15
+ * The one anchor every ui-kit link renders (internal; not exported from the subpath): `next/link` with the prefetch
16
+ * role for internal links, a plain `<a target="_blank" rel="noopener noreferrer">` for external ones.
17
+ */
18
+ export function Anchor({ href, external, prefetch, hooks, children, }) {
15
19
  if (external ?? isExternal(href)) {
16
20
  return (_jsx("a", { href: href, target: "_blank", rel: "noopener noreferrer", ...hooks, children: children }));
17
21
  }
@@ -1 +1 @@
1
- {"version":3,"file":"links.js","sourceRoot":"","sources":["../../../src/ui-kit/primitives/links.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * Link primitives: `ButtonLink`, `TextLink` and `PhoneLink`. Server components; every link is a real `<a href>`.\n * External links open in a new tab with `rel=\"noopener noreferrer\"`. `PhoneLink` requires a typed placement, which the\n * shared instrumentation reads to send one `phone_link_click` per tap; nothing here emits events itself.\n */\nimport Link from 'next/link'\nimport type { ReactNode } from 'react'\nimport type { PhoneCallPlacement } from '../../analytics.js'\nimport { Icon, type UiIconName } from '../icons.js'\nimport { linkPrefetchProps, type LinkPrefetch } from '../prefetch.js'\nimport { slot, type SkinEntry } from '../skin.js'\nimport type { UiPhone } from '../types.js'\n\nfunction isExternal(href: string) {\n return /^https?:\\/\\//i.test(href)\n}\n\n/** Data hooks and the skin class for an anchor's root. */\ntype AnchorHooks = { readonly className: string | undefined } & Readonly<Record<`data-${string}`, string | undefined>>\n\nfunction Anchor({\n href,\n external,\n prefetch,\n hooks,\n children,\n}: {\n href: string\n external?: boolean | undefined\n prefetch?: LinkPrefetch | undefined\n hooks: AnchorHooks\n children: ReactNode\n}) {\n if (external ?? isExternal(href)) {\n return (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\" {...hooks}>\n {children}\n </a>\n )\n }\n return (\n <Link href={href} {...linkPrefetchProps(prefetch)} {...hooks}>\n {children}\n </Link>\n )\n}\n\nexport type ButtonLinkSlot = 'root' | 'label' | 'icon'\nexport const BUTTON_VARIANTS = ['primary', 'secondary', 'quiet'] as const\nexport type ButtonVariant = (typeof BUTTON_VARIANTS)[number]\nexport type ButtonLinkSkin = SkinEntry<ButtonLinkSlot, ButtonVariant, { size?: 'regular' | 'large' }>\n\nexport interface ButtonLinkProps extends ButtonLinkSkin {\n readonly href: string\n readonly children: ReactNode\n /** An icon after the label (`arrow`) or before it (`phone`). */\n readonly icon?: UiIconName\n readonly iconPosition?: 'start' | 'end'\n /** Force external handling; by default `http(s)://` links are external. */\n readonly external?: boolean\n /**\n * Prefetch by link role (`LINK_PREFETCH_POLICY`): `auto` (the default, Next's prefetch in view) for a main action,\n * `intent` for an in-content card link, `false` for footer, area, nearby, map and finder links.\n */\n readonly prefetch?: LinkPrefetch\n}\n\n/** A link styled as a button. A primary action is at least 44 px tall (structure.css). */\nexport function ButtonLink({ href, children, icon, iconPosition = 'end', external, prefetch, variant = 'primary', layout, classes }: ButtonLinkProps) {\n const iconNode = icon ? <span {...slot(classes, 'icon')}><Icon name={icon} /></span> : null\n return (\n <Anchor\n href={href}\n external={external}\n prefetch={prefetch}\n hooks={{ 'data-bl': 'button', 'data-variant': variant, 'data-size': layout?.size ?? 'regular', className: classes?.root }}\n >\n {iconPosition === 'start' ? iconNode : null}\n <span {...slot(classes, 'label')}>{children}</span>\n {iconPosition === 'end' ? iconNode : null}\n </Anchor>\n )\n}\n\nexport type TextLinkSlot = 'root'\nexport type TextLinkSkin = SkinEntry<TextLinkSlot, 'inline' | 'standalone'>\n\nexport interface TextLinkProps extends TextLinkSkin {\n readonly href: string\n readonly children: ReactNode\n readonly external?: boolean\n /**\n * Prefetch by link role (`LINK_PREFETCH_POLICY`): `auto` (the default, Next's prefetch in view) for a main action,\n * `intent` for an in-content card link, `false` for footer, area, nearby, map and finder links.\n */\n readonly prefetch?: LinkPrefetch\n}\n\n/** An inline or standalone text link. */\nexport function TextLink({ href, children, external, prefetch, variant = 'inline', classes }: TextLinkProps) {\n return (\n <Anchor href={href} external={external} prefetch={prefetch} hooks={{ 'data-bl': 'text-link', 'data-variant': variant, className: classes?.root }}>\n {children}\n </Anchor>\n )\n}\n\nexport type PhoneLinkSlot = 'root' | 'icon' | 'label' | 'number'\nexport type PhoneLinkSkin = SkinEntry<PhoneLinkSlot, 'text' | 'button'>\n\nexport interface PhoneLinkProps extends PhoneLinkSkin {\n readonly phone: UiPhone\n /** Where the link sits, for `phone_link_click`. Required: a section sets its own. */\n readonly placement: PhoneCallPlacement\n /** Optional words before the number, for example \"Call\". The accessible name is always the visible text. */\n readonly label?: string\n /** The phone icon on the left (header and hero). */\n readonly showIcon?: boolean\n}\n\n/**\n * A `tel:` link. Its accessible name is its visible text, which always includes the number (WCAG 2.5.3). Use it for\n * every call link: a literal `tel:` anywhere else fails lint (K1) and C10 checks the href against the evidence file.\n */\nexport function PhoneLink({ phone, placement, label, showIcon = false, variant = 'text', classes }: PhoneLinkProps) {\n return (\n <a href={phone.telHref} data-bl=\"phone-link\" data-variant={variant} data-phone-location={placement} className={classes?.root}>\n {showIcon ? (\n <span {...slot(classes, 'icon')}>\n <Icon name=\"phone\" />\n </span>\n ) : null}\n {label ? (\n <>\n <span {...slot(classes, 'label')}>{label}</span>{' '}\n </>\n ) : null}\n <span {...slot(classes, 'number')}>{phone.display}</span>\n </a>\n )\n}\n"]}
1
+ {"version":3,"file":"links.js","sourceRoot":"","sources":["../../../src/ui-kit/primitives/links.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * Link primitives: `ButtonLink`, `TextLink` and `PhoneLink`. Server components; every link is a real `<a href>`.\n * External links open in a new tab with `rel=\"noopener noreferrer\"`. `PhoneLink` requires a typed placement, which the\n * shared instrumentation reads to send one `phone_link_click` per tap; nothing here emits events itself.\n */\nimport Link from 'next/link'\nimport type { ReactNode } from 'react'\nimport type { PhoneCallPlacement } from '../../analytics.js'\nimport { Icon, type UiIconName } from '../icons.js'\nimport { linkPrefetchProps, type LinkPrefetch } from '../prefetch.js'\nimport { slot, type SkinEntry } from '../skin.js'\nimport type { UiPhone } from '../types.js'\n\nfunction isExternal(href: string) {\n return /^https?:\\/\\//i.test(href)\n}\n\n/** Data hooks and the skin class for an anchor's root. */\nexport type AnchorHooks = { readonly className?: string | undefined } & Readonly<Record<`data-${string}`, string | undefined>>\n\n/**\n * The one anchor every ui-kit link renders (internal; not exported from the subpath): `next/link` with the prefetch\n * role for internal links, a plain `<a target=\"_blank\" rel=\"noopener noreferrer\">` for external ones.\n */\nexport function Anchor({\n href,\n external,\n prefetch,\n hooks,\n children,\n}: {\n href: string\n external?: boolean | undefined\n prefetch?: LinkPrefetch | undefined\n hooks: AnchorHooks\n children: ReactNode\n}) {\n if (external ?? isExternal(href)) {\n return (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\" {...hooks}>\n {children}\n </a>\n )\n }\n return (\n <Link href={href} {...linkPrefetchProps(prefetch)} {...hooks}>\n {children}\n </Link>\n )\n}\n\nexport type ButtonLinkSlot = 'root' | 'label' | 'icon'\nexport const BUTTON_VARIANTS = ['primary', 'secondary', 'quiet'] as const\nexport type ButtonVariant = (typeof BUTTON_VARIANTS)[number]\nexport type ButtonLinkSkin = SkinEntry<ButtonLinkSlot, ButtonVariant, { size?: 'regular' | 'large' }>\n\nexport interface ButtonLinkProps extends ButtonLinkSkin {\n readonly href: string\n readonly children: ReactNode\n /** An icon after the label (`arrow`) or before it (`phone`). */\n readonly icon?: UiIconName\n readonly iconPosition?: 'start' | 'end'\n /** Force external handling; by default `http(s)://` links are external. */\n readonly external?: boolean\n /**\n * Prefetch by link role (`LINK_PREFETCH_POLICY`): `auto` (the default, Next's prefetch in view) for a main action,\n * `intent` for an in-content card link, `false` for footer, area, nearby, map and finder links.\n */\n readonly prefetch?: LinkPrefetch\n}\n\n/** A link styled as a button. A primary action is at least 44 px tall (structure.css). */\nexport function ButtonLink({ href, children, icon, iconPosition = 'end', external, prefetch, variant = 'primary', layout, classes }: ButtonLinkProps) {\n const iconNode = icon ? <span {...slot(classes, 'icon')}><Icon name={icon} /></span> : null\n return (\n <Anchor\n href={href}\n external={external}\n prefetch={prefetch}\n hooks={{ 'data-bl': 'button', 'data-variant': variant, 'data-size': layout?.size ?? 'regular', className: classes?.root }}\n >\n {iconPosition === 'start' ? iconNode : null}\n <span {...slot(classes, 'label')}>{children}</span>\n {iconPosition === 'end' ? iconNode : null}\n </Anchor>\n )\n}\n\nexport type TextLinkSlot = 'root'\nexport type TextLinkSkin = SkinEntry<TextLinkSlot, 'inline' | 'standalone'>\n\nexport interface TextLinkProps extends TextLinkSkin {\n readonly href: string\n readonly children: ReactNode\n readonly external?: boolean\n /**\n * Prefetch by link role (`LINK_PREFETCH_POLICY`): `auto` (the default, Next's prefetch in view) for a main action,\n * `intent` for an in-content card link, `false` for footer, area, nearby, map and finder links.\n */\n readonly prefetch?: LinkPrefetch\n}\n\n/** An inline or standalone text link. */\nexport function TextLink({ href, children, external, prefetch, variant = 'inline', classes }: TextLinkProps) {\n return (\n <Anchor href={href} external={external} prefetch={prefetch} hooks={{ 'data-bl': 'text-link', 'data-variant': variant, className: classes?.root }}>\n {children}\n </Anchor>\n )\n}\n\nexport type PhoneLinkSlot = 'root' | 'icon' | 'label' | 'number'\nexport type PhoneLinkSkin = SkinEntry<PhoneLinkSlot, 'text' | 'button'>\n\nexport interface PhoneLinkProps extends PhoneLinkSkin {\n readonly phone: UiPhone\n /** Where the link sits, for `phone_link_click`. Required: a section sets its own. */\n readonly placement: PhoneCallPlacement\n /** Optional words before the number, for example \"Call\". The accessible name is always the visible text. */\n readonly label?: string\n /** The phone icon on the left (header and hero). */\n readonly showIcon?: boolean\n}\n\n/**\n * A `tel:` link. Its accessible name is its visible text, which always includes the number (WCAG 2.5.3). Use it for\n * every call link: a literal `tel:` anywhere else fails lint (K1) and C10 checks the href against the evidence file.\n */\nexport function PhoneLink({ phone, placement, label, showIcon = false, variant = 'text', classes }: PhoneLinkProps) {\n return (\n <a href={phone.telHref} data-bl=\"phone-link\" data-variant={variant} data-phone-location={placement} className={classes?.root}>\n {showIcon ? (\n <span {...slot(classes, 'icon')}>\n <Icon name=\"phone\" />\n </span>\n ) : null}\n {label ? (\n <>\n <span {...slot(classes, 'label')}>{label}</span>{' '}\n </>\n ) : null}\n <span {...slot(classes, 'number')}>{phone.display}</span>\n </a>\n )\n}\n"]}
@@ -0,0 +1,43 @@
1
+ import type { LinkPrefetch } from '../prefetch.js';
2
+ import type { RepeatedSlotOf } from '../repeated-slots.js';
3
+ import { type SkinEntry } from '../skin.js';
4
+ import type { UiLink } from '../types.js';
5
+ /** One checklist entry: the fact, and optionally where to check it. */
6
+ export type TickItem = string | {
7
+ readonly text: string;
8
+ readonly link?: UiLink;
9
+ };
10
+ export type TickListSlot = 'root' | RepeatedSlotOf<'tickList'>;
11
+ export type TickListRepeatedSlot = RepeatedSlotOf<'tickList'>;
12
+ export interface TickListLayout {
13
+ /** One column (the default) or two, from the phone breakpoint up. */
14
+ readonly columns?: 1 | 2;
15
+ /**
16
+ * The tick: `icon` (the default) is the sprite's check; `rule` ships no svg, only an empty decorative element the
17
+ * stylesheet draws as a short rule (`data-tick="rule"` on the list), about 120 bytes less a fact.
18
+ */
19
+ readonly tick?: 'icon' | 'rule';
20
+ }
21
+ export type TickListSkin = SkinEntry<TickListSlot, never, TickListLayout, TickListRepeatedSlot>;
22
+ export interface TickListProps extends TickListSkin {
23
+ readonly items: readonly TickItem[];
24
+ }
25
+ export declare function TickList({ items, layout, classes }: TickListProps): import("react/jsx-runtime").JSX.Element | null;
26
+ /** The `data-bl-links` roles W2 reads: links in these lists never prefetch. */
27
+ export declare const LINK_LIST_KINDS: readonly ["area", "nearby", "map", "finder", "other-services"];
28
+ export type LinkListKind = (typeof LINK_LIST_KINDS)[number];
29
+ export type LinkListSlot = 'root';
30
+ export interface LinkListLayout {
31
+ /** Columns at the widest width (default 3); phones show one, then two. */
32
+ readonly columns?: 1 | 2 | 3 | 4;
33
+ }
34
+ export type LinkListSkin = SkinEntry<LinkListSlot, never, LinkListLayout>;
35
+ export interface LinkListProps extends LinkListSkin {
36
+ readonly links: readonly UiLink[];
37
+ /** What the list holds, rendered as `data-bl-links` for W2. Leave it out for a list of other destinations. */
38
+ readonly kind?: LinkListKind;
39
+ /** Off by default; `intent` only for a short list of in-content destinations. */
40
+ readonly prefetch?: Exclude<LinkPrefetch, 'auto'>;
41
+ }
42
+ export declare function LinkList({ links, kind, prefetch, layout, classes }: LinkListProps): import("react/jsx-runtime").JSX.Element | null;
43
+ //# sourceMappingURL=lists.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lists.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/primitives/lists.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGzC,uEAAuE;AACvE,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEjF,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,cAAc,CAAC,UAAU,CAAC,CAAA;AAC9D,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,UAAU,CAAC,CAAA;AAC7D,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;IACxB;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAChC;AACD,MAAM,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,EAAE,KAAK,EAAE,cAAc,EAAE,oBAAoB,CAAC,CAAA;AAE/F,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAA;CACpC;AAID,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,aAAa,kDAkCjE;AAED,+EAA+E;AAC/E,eAAO,MAAM,eAAe,gEAAiE,CAAA;AAC7F,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAE3D,MAAM,MAAM,YAAY,GAAG,MAAM,CAAA;AACjC,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;CACjC;AACD,MAAM,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,EAAE,KAAK,EAAE,cAAc,CAAC,CAAA;AAEzE,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,8GAA8G;IAC9G,QAAQ,CAAC,IAAI,CAAC,EAAE,YAAY,CAAA;IAC5B,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,CAAA;CAClD;AAED,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,QAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,aAAa,kDAazF"}
@@ -0,0 +1,34 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * `TickList` and `LinkList` (3.14.0). Server components; both are a real `<ul>`.
4
+ *
5
+ * `TickList`: verified facts or the scope of a service, each after a decorative tick (the check icon, or a ruled mark
6
+ * with `layout.tick: 'rule'`). One or two columns; the hero's trust checklist uses two (Romie, Prestige and Sydney
7
+ * Waterproofing). An item may end with a link to where the fact can be checked, for example the licence register.
8
+ *
9
+ * `LinkList`: a ruled list of destinations (AA's `RuledLinkList`): service areas, nearby suburbs, other services. Lean
10
+ * by design (cost item 1f): each entry is a plain `<li><a>` with no class, hook or icon, so a 120-suburb hub stays near
11
+ * 120 bytes a link; a skin draws rules and arrows from `src/site/skin.css`. Its links never prefetch (area and nearby
12
+ * lists are `false` in `LINK_PREFETCH_POLICY`), and `kind` marks the list for W2 (`data-bl-links`).
13
+ */
14
+ import { Icon } from '../icons.js';
15
+ import { repeatedSlot } from '../skin.js';
16
+ import { Anchor } from './links.js';
17
+ const itemText = (item) => (typeof item === 'string' ? item : item.text);
18
+ export function TickList({ items, layout, classes }) {
19
+ if (items.length === 0)
20
+ return null;
21
+ const rule = layout?.tick === 'rule';
22
+ return (_jsx("ul", { role: "list", "data-bl": "tick-list", "data-columns": String(layout?.columns ?? 1), "data-tick": rule ? 'rule' : undefined, className: classes?.root, children: items.map((item) => {
23
+ const link = typeof item === 'string' ? undefined : item.link;
24
+ return (_jsxs("li", { ...repeatedSlot('item'), children: [_jsx("span", { ...repeatedSlot('tick'), "aria-hidden": "true", children: rule ? null : _jsx(Icon, { name: "check" }) }), _jsxs("span", { ...repeatedSlot('text'), children: [itemText(item), link ? (_jsxs(_Fragment, { children: [' ', _jsx(Anchor, { href: link.href, prefetch: false, hooks: { 'data-slot': 'link' }, children: link.label })] })) : null] })] }, itemText(item)));
25
+ }) }));
26
+ }
27
+ /** The `data-bl-links` roles W2 reads: links in these lists never prefetch. */
28
+ export const LINK_LIST_KINDS = ['area', 'nearby', 'map', 'finder', 'other-services'];
29
+ export function LinkList({ links, kind, prefetch = false, layout, classes }) {
30
+ if (links.length === 0)
31
+ return null;
32
+ return (_jsx("ul", { role: "list", "data-bl": "link-list", "data-columns": String(layout?.columns ?? 3), "data-bl-links": kind, className: classes?.root, children: links.map((link) => (_jsx("li", { children: _jsx(Anchor, { href: link.href, prefetch: prefetch, hooks: {}, children: link.label }) }, link.href))) }));
33
+ }
34
+ //# sourceMappingURL=lists.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lists.js","sourceRoot":"","sources":["../../../src/ui-kit/primitives/lists.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAGlC,OAAO,EAAE,YAAY,EAAkB,MAAM,YAAY,CAAA;AAEzD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAsBnC,MAAM,QAAQ,GAAG,CAAC,IAAc,EAAE,EAAE,CAAC,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAElF,MAAM,UAAU,QAAQ,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAiB;IAChE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACnC,MAAM,IAAI,GAAG,MAAM,EAAE,IAAI,KAAK,MAAM,CAAA;IACpC,OAAO,CACL,aACE,IAAI,EAAC,MAAM,aACH,WAAW,kBACL,MAAM,CAAC,MAAM,EAAE,OAAO,IAAI,CAAC,CAAC,eAC/B,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EACpC,SAAS,EAAE,OAAO,EAAE,IAAI,YAEvB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YAClB,MAAM,IAAI,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAA;YAC7D,OAAO,CACL,iBAA6B,YAAY,CAAC,MAAM,CAAC,aAC/C,kBAAU,YAAY,CAAC,MAAM,CAAC,iBAAc,MAAM,YAC/C,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAC,IAAI,IAAC,IAAI,EAAC,OAAO,GAAG,GAC/B,EACP,mBAAU,YAAY,CAAC,MAAM,CAAC,aAC3B,QAAQ,CAAC,IAAI,CAAC,EACd,IAAI,CAAC,CAAC,CAAC,CACN,8BACG,GAAG,EACJ,KAAC,MAAM,IAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,YACrE,IAAI,CAAC,KAAK,GACJ,IACR,CACJ,CAAC,CAAC,CAAC,IAAI,IACH,KAdA,QAAQ,CAAC,IAAI,CAAC,CAelB,CACN,CAAA;QACH,CAAC,CAAC,GACC,CACN,CAAA;AACH,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,gBAAgB,CAAU,CAAA;AAkB7F,MAAM,UAAU,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,GAAG,KAAK,EAAE,MAAM,EAAE,OAAO,EAAiB;IACxF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACnC,OAAO,CACL,aAAI,IAAI,EAAC,MAAM,aAAS,WAAW,kBAAe,MAAM,CAAC,MAAM,EAAE,OAAO,IAAI,CAAC,CAAC,mBAAiB,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,YAC1H,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACnB,uBACE,KAAC,MAAM,IAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,YACnD,IAAI,CAAC,KAAK,GACJ,IAHF,IAAI,CAAC,IAAI,CAIb,CACN,CAAC,GACC,CACN,CAAA;AACH,CAAC","sourcesContent":["/**\n * `TickList` and `LinkList` (3.14.0). Server components; both are a real `<ul>`.\n *\n * `TickList`: verified facts or the scope of a service, each after a decorative tick (the check icon, or a ruled mark\n * with `layout.tick: 'rule'`). One or two columns; the hero's trust checklist uses two (Romie, Prestige and Sydney\n * Waterproofing). An item may end with a link to where the fact can be checked, for example the licence register.\n *\n * `LinkList`: a ruled list of destinations (AA's `RuledLinkList`): service areas, nearby suburbs, other services. Lean\n * by design (cost item 1f): each entry is a plain `<li><a>` with no class, hook or icon, so a 120-suburb hub stays near\n * 120 bytes a link; a skin draws rules and arrows from `src/site/skin.css`. Its links never prefetch (area and nearby\n * lists are `false` in `LINK_PREFETCH_POLICY`), and `kind` marks the list for W2 (`data-bl-links`).\n */\nimport { Icon } from '../icons.js'\nimport type { LinkPrefetch } from '../prefetch.js'\nimport type { RepeatedSlotOf } from '../repeated-slots.js'\nimport { repeatedSlot, type SkinEntry } from '../skin.js'\nimport type { UiLink } from '../types.js'\nimport { Anchor } from './links.js'\n\n/** One checklist entry: the fact, and optionally where to check it. */\nexport type TickItem = string | { readonly text: string; readonly link?: UiLink }\n\nexport type TickListSlot = 'root' | RepeatedSlotOf<'tickList'>\nexport type TickListRepeatedSlot = RepeatedSlotOf<'tickList'>\nexport interface TickListLayout {\n /** One column (the default) or two, from the phone breakpoint up. */\n readonly columns?: 1 | 2\n /**\n * The tick: `icon` (the default) is the sprite's check; `rule` ships no svg, only an empty decorative element the\n * stylesheet draws as a short rule (`data-tick=\"rule\"` on the list), about 120 bytes less a fact.\n */\n readonly tick?: 'icon' | 'rule'\n}\nexport type TickListSkin = SkinEntry<TickListSlot, never, TickListLayout, TickListRepeatedSlot>\n\nexport interface TickListProps extends TickListSkin {\n readonly items: readonly TickItem[]\n}\n\nconst itemText = (item: TickItem) => (typeof item === 'string' ? item : item.text)\n\nexport function TickList({ items, layout, classes }: TickListProps) {\n if (items.length === 0) return null\n const rule = layout?.tick === 'rule'\n return (\n <ul\n role=\"list\"\n data-bl=\"tick-list\"\n data-columns={String(layout?.columns ?? 1)}\n data-tick={rule ? 'rule' : undefined}\n className={classes?.root}\n >\n {items.map((item) => {\n const link = typeof item === 'string' ? undefined : item.link\n return (\n <li key={itemText(item)} {...repeatedSlot('item')}>\n <span {...repeatedSlot('tick')} aria-hidden=\"true\">\n {rule ? null : <Icon name=\"check\" />}\n </span>\n <span {...repeatedSlot('text')}>\n {itemText(item)}\n {link ? (\n <>\n {' '}\n <Anchor href={link.href} prefetch={false} hooks={{ 'data-slot': 'link' }}>\n {link.label}\n </Anchor>\n </>\n ) : null}\n </span>\n </li>\n )\n })}\n </ul>\n )\n}\n\n/** The `data-bl-links` roles W2 reads: links in these lists never prefetch. */\nexport const LINK_LIST_KINDS = ['area', 'nearby', 'map', 'finder', 'other-services'] as const\nexport type LinkListKind = (typeof LINK_LIST_KINDS)[number]\n\nexport type LinkListSlot = 'root'\nexport interface LinkListLayout {\n /** Columns at the widest width (default 3); phones show one, then two. */\n readonly columns?: 1 | 2 | 3 | 4\n}\nexport type LinkListSkin = SkinEntry<LinkListSlot, never, LinkListLayout>\n\nexport interface LinkListProps extends LinkListSkin {\n readonly links: readonly UiLink[]\n /** What the list holds, rendered as `data-bl-links` for W2. Leave it out for a list of other destinations. */\n readonly kind?: LinkListKind\n /** Off by default; `intent` only for a short list of in-content destinations. */\n readonly prefetch?: Exclude<LinkPrefetch, 'auto'>\n}\n\nexport function LinkList({ links, kind, prefetch = false, layout, classes }: LinkListProps) {\n if (links.length === 0) return null\n return (\n <ul role=\"list\" data-bl=\"link-list\" data-columns={String(layout?.columns ?? 3)} data-bl-links={kind} className={classes?.root}>\n {links.map((link) => (\n <li key={link.href}>\n <Anchor href={link.href} prefetch={prefetch} hooks={{}}>\n {link.label}\n </Anchor>\n </li>\n ))}\n </ul>\n )\n}\n"]}
@@ -0,0 +1,28 @@
1
+ import type { ReactNode } from 'react';
2
+ import { type BrandlabMediaEntry, type BrandlabMediaRatio } from '@brandlab-digital/site-core/media';
3
+ import { type SkinEntry } from '../skin.js';
4
+ /** What a frame needs from a `media.ts` entry; a whole `BrandlabMediaEntry` fits. */
5
+ export type UiMedia = Pick<BrandlabMediaEntry, 'src' | 'width' | 'height' | 'alt' | 'provenance'> & Partial<Pick<BrandlabMediaEntry, 'decorative' | 'focalPoint'>>;
6
+ export type MediaFrameSlot = 'root' | 'img' | 'caption' | 'label';
7
+ export type MediaFrameSkin = SkinEntry<MediaFrameSlot>;
8
+ /** The locked visible label on an AI image (F13; truth wording, not site copy). */
9
+ export declare const ILLUSTRATION_LABEL = "Illustration";
10
+ /** Exactly one sizing: `sizes` with a `vw` part for a fluid image, or `displayWidth` for a fixed-size one. */
11
+ export type MediaFrameSizing = {
12
+ readonly sizes: string;
13
+ readonly displayWidth?: never;
14
+ } | {
15
+ readonly displayWidth: number;
16
+ readonly sizes?: never;
17
+ };
18
+ export type MediaFrameProps = MediaFrameSkin & MediaFrameSizing & {
19
+ readonly media: UiMedia;
20
+ /** Crop to an allowed ratio; by default the frame keeps the image's own shape. */
21
+ readonly ratio?: BrandlabMediaRatio;
22
+ /** "{what}, {suburb}, {month year}", only when the photo really shows that job. */
23
+ readonly caption?: ReactNode;
24
+ /** The page's largest image: preloaded, high fetch priority, never lazy. */
25
+ readonly priority?: boolean;
26
+ };
27
+ export declare function MediaFrame(props: MediaFrameProps): import("react/jsx-runtime").JSX.Element;
28
+ //# sourceMappingURL=media-frame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-frame.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/primitives/media-frame.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAyB,KAAK,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AAC3H,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AAEjD,qFAAqF;AACrF,MAAM,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,YAAY,CAAC,GAC/F,OAAO,CAAC,IAAI,CAAC,kBAAkB,EAAE,YAAY,GAAG,YAAY,CAAC,CAAC,CAAA;AAEhE,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,GAAG,SAAS,GAAG,OAAO,CAAA;AACjE,MAAM,MAAM,cAAc,GAAG,SAAS,CAAC,cAAc,CAAC,CAAA;AAEtD,mFAAmF;AACnF,eAAO,MAAM,kBAAkB,iBAAiB,CAAA;AAEhD,8GAA8G;AAC9G,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,KAAK,CAAA;CAAE,GACzD;IAAE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,CAAA;AAE7D,MAAM,MAAM,eAAe,GAAG,cAAc,GAC1C,gBAAgB,GAAG;IACjB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,kFAAkF;IAClF,QAAQ,CAAC,KAAK,CAAC,EAAE,kBAAkB,CAAA;IACnC,mFAAmF;IACnF,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAA;IAC5B,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAC5B,CAAA;AAUH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,2CAqDhD"}
@@ -0,0 +1,58 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * `MediaFrame` (3.14.0, F13): one image from the site's media manifest in a frame of an allowed ratio. Server
4
+ * component over `next/image`, and the library's one image path: `Media` and the hero render through it, so the
5
+ * "Illustration" label and the sizing rule below live here only. It is provenance-aware: the root carries
6
+ * `data-provenance`, and an AI image shows the locked visible label "Illustration" inside the `<figcaption>`, so the
7
+ * figure's accessible caption names it an illustration too. A caption ("{what}, {suburb}, {month year}") or the label
8
+ * makes the root a `<figure>`; otherwise it is a `<div>`.
9
+ *
10
+ * Sizing (cost item 1g) is one of two kinds, and the type takes exactly one:
11
+ * - **fluid** (`sizes`, which must have a `vw` part, for example `(min-width: 1024px) 50vw, 100vw`): Next keeps only the
12
+ * widths the layout can use.
13
+ * - **fixed** (`displayWidth`, the CSS width in pixels): no `sizes` at all, so Next emits a 1x and a 2x candidate only.
14
+ * A `sizes` without `vw` (for example `sizes="148px"`) makes Next list all 16 widths, which was 28–30% of some pages'
15
+ * HTML; it is refused here.
16
+ *
17
+ * `priority` is for the page's largest image (the hero): preloaded and fetched with high priority, never lazy. Every
18
+ * other image loads lazily.
19
+ */
20
+ import Image from 'next/image';
21
+ import { BRANDLAB_MEDIA_RATIOS } from '@brandlab-digital/site-core/media';
22
+ import { slot } from '../skin.js';
23
+ /** The locked visible label on an AI image (F13; truth wording, not site copy). */
24
+ export const ILLUSTRATION_LABEL = 'Illustration';
25
+ /** Next's own test for a `vw` part in `sizes` (shared/lib/get-img-props). */
26
+ const VIEWPORT_WIDTH = /(^|\s)(1?\d?\d)vw/;
27
+ function ratioOf(ratio) {
28
+ const [width, height] = ratio.split(':').map(Number);
29
+ return height / width;
30
+ }
31
+ export function MediaFrame(props) {
32
+ const { media, ratio, caption, priority = false, classes } = props;
33
+ if (ratio !== undefined && !BRANDLAB_MEDIA_RATIOS.includes(ratio)) {
34
+ throw new Error(`MediaFrame ${media.src}: ratio "${ratio}" is not one of ${BRANDLAB_MEDIA_RATIOS.join(', ')}.`);
35
+ }
36
+ let width = media.width;
37
+ let height = media.height;
38
+ let sizes;
39
+ if (props.sizes !== undefined) {
40
+ if (!VIEWPORT_WIDTH.test(props.sizes)) {
41
+ throw new Error(`MediaFrame ${media.src}: sizes "${props.sizes}" has no vw part, so Next would list every width. Give a fluid image a vw size, or a fixed-size image its displayWidth instead.`);
42
+ }
43
+ sizes = props.sizes;
44
+ }
45
+ else {
46
+ if (!Number.isInteger(props.displayWidth) || props.displayWidth <= 0) {
47
+ throw new Error(`MediaFrame ${media.src}: displayWidth must be the image's CSS width in whole pixels.`);
48
+ }
49
+ width = props.displayWidth;
50
+ height = Math.round(props.displayWidth * (ratio ? ratioOf(ratio) : media.height / media.width));
51
+ }
52
+ const illustration = media.provenance === 'illustrative-ai';
53
+ const hasCaption = caption !== undefined && caption !== null && caption !== '';
54
+ const Root = illustration || hasCaption ? 'figure' : 'div';
55
+ const focal = media.focalPoint ? { objectPosition: `${media.focalPoint.x}% ${media.focalPoint.y}%` } : undefined;
56
+ return (_jsxs(Root, { "data-bl": "media", "data-ratio": ratio, "data-fit": sizes ? 'fluid' : 'fixed', "data-provenance": media.provenance, className: classes?.root, children: [_jsx(Image, { src: media.src, alt: media.decorative ? '' : media.alt, width: width, height: height, sizes: sizes, style: focal, ...(priority ? { preload: true, fetchPriority: 'high' } : { loading: 'lazy' }), ...slot(classes, 'img') }), illustration || hasCaption ? (_jsxs("figcaption", { ...slot(classes, 'caption'), children: [illustration ? _jsx("span", { ...slot(classes, 'label'), children: ILLUSTRATION_LABEL }) : null, illustration && hasCaption ? ' ' : null, hasCaption ? caption : null] })) : null] }));
57
+ }
58
+ //# sourceMappingURL=media-frame.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-frame.js","sourceRoot":"","sources":["../../../src/ui-kit/primitives/media-frame.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `MediaFrame` (3.14.0, F13): one image from the site's media manifest in a frame of an allowed ratio. Server\n * component over `next/image`, and the library's one image path: `Media` and the hero render through it, so the\n * \"Illustration\" label and the sizing rule below live here only. It is provenance-aware: the root carries\n * `data-provenance`, and an AI image shows the locked visible label \"Illustration\" inside the `<figcaption>`, so the\n * figure's accessible caption names it an illustration too. A caption (\"{what}, {suburb}, {month year}\") or the label\n * makes the root a `<figure>`; otherwise it is a `<div>`.\n *\n * Sizing (cost item 1g) is one of two kinds, and the type takes exactly one:\n * - **fluid** (`sizes`, which must have a `vw` part, for example `(min-width: 1024px) 50vw, 100vw`): Next keeps only the\n * widths the layout can use.\n * - **fixed** (`displayWidth`, the CSS width in pixels): no `sizes` at all, so Next emits a 1x and a 2x candidate only.\n * A `sizes` without `vw` (for example `sizes=\"148px\"`) makes Next list all 16 widths, which was 28–30% of some pages'\n * HTML; it is refused here.\n *\n * `priority` is for the page's largest image (the hero): preloaded and fetched with high priority, never lazy. Every\n * other image loads lazily.\n */\nimport Image from 'next/image'\nimport type { ReactNode } from 'react'\nimport { BRANDLAB_MEDIA_RATIOS, type BrandlabMediaEntry, type BrandlabMediaRatio } from '@brandlab-digital/site-core/media'\nimport { slot, type SkinEntry } from '../skin.js'\n\n/** What a frame needs from a `media.ts` entry; a whole `BrandlabMediaEntry` fits. */\nexport type UiMedia = Pick<BrandlabMediaEntry, 'src' | 'width' | 'height' | 'alt' | 'provenance'> &\n Partial<Pick<BrandlabMediaEntry, 'decorative' | 'focalPoint'>>\n\nexport type MediaFrameSlot = 'root' | 'img' | 'caption' | 'label'\nexport type MediaFrameSkin = SkinEntry<MediaFrameSlot>\n\n/** The locked visible label on an AI image (F13; truth wording, not site copy). */\nexport const ILLUSTRATION_LABEL = 'Illustration'\n\n/** Exactly one sizing: `sizes` with a `vw` part for a fluid image, or `displayWidth` for a fixed-size one. */\nexport type MediaFrameSizing =\n | { readonly sizes: string; readonly displayWidth?: never }\n | { readonly displayWidth: number; readonly sizes?: never }\n\nexport type MediaFrameProps = MediaFrameSkin &\n MediaFrameSizing & {\n readonly media: UiMedia\n /** Crop to an allowed ratio; by default the frame keeps the image's own shape. */\n readonly ratio?: BrandlabMediaRatio\n /** \"{what}, {suburb}, {month year}\", only when the photo really shows that job. */\n readonly caption?: ReactNode\n /** The page's largest image: preloaded, high fetch priority, never lazy. */\n readonly priority?: boolean\n }\n\n/** Next's own test for a `vw` part in `sizes` (shared/lib/get-img-props). */\nconst VIEWPORT_WIDTH = /(^|\\s)(1?\\d?\\d)vw/\n\nfunction ratioOf(ratio: BrandlabMediaRatio): number {\n const [width, height] = ratio.split(':').map(Number) as [number, number]\n return height / width\n}\n\nexport function MediaFrame(props: MediaFrameProps) {\n const { media, ratio, caption, priority = false, classes } = props\n if (ratio !== undefined && !(BRANDLAB_MEDIA_RATIOS as readonly string[]).includes(ratio)) {\n throw new Error(`MediaFrame ${media.src}: ratio \"${ratio}\" is not one of ${BRANDLAB_MEDIA_RATIOS.join(', ')}.`)\n }\n let width = media.width\n let height = media.height\n let sizes: string | undefined\n if (props.sizes !== undefined) {\n if (!VIEWPORT_WIDTH.test(props.sizes)) {\n throw new Error(\n `MediaFrame ${media.src}: sizes \"${props.sizes}\" has no vw part, so Next would list every width. Give a fluid image a vw size, or a fixed-size image its displayWidth instead.`,\n )\n }\n sizes = props.sizes\n } else {\n if (!Number.isInteger(props.displayWidth) || props.displayWidth <= 0) {\n throw new Error(`MediaFrame ${media.src}: displayWidth must be the image's CSS width in whole pixels.`)\n }\n width = props.displayWidth\n height = Math.round(props.displayWidth * (ratio ? ratioOf(ratio) : media.height / media.width))\n }\n const illustration = media.provenance === 'illustrative-ai'\n const hasCaption = caption !== undefined && caption !== null && caption !== ''\n const Root = illustration || hasCaption ? 'figure' : 'div'\n const focal = media.focalPoint ? { objectPosition: `${media.focalPoint.x}% ${media.focalPoint.y}%` } : undefined\n return (\n <Root\n data-bl=\"media\"\n data-ratio={ratio}\n data-fit={sizes ? 'fluid' : 'fixed'}\n data-provenance={media.provenance}\n className={classes?.root}\n >\n <Image\n src={media.src}\n alt={media.decorative ? '' : media.alt}\n width={width}\n height={height}\n sizes={sizes}\n style={focal}\n {...(priority ? { preload: true, fetchPriority: 'high' as const } : { loading: 'lazy' as const })}\n {...slot(classes, 'img')}\n />\n {illustration || hasCaption ? (\n <figcaption {...slot(classes, 'caption')}>\n {illustration ? <span {...slot(classes, 'label')}>{ILLUSTRATION_LABEL}</span> : null}\n {illustration && hasCaption ? ' ' : null}\n {hasCaption ? caption : null}\n </figcaption>\n ) : null}\n </Root>\n )\n}\n"]}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * `Media` (F13, 3.14.0): one image from the site's media manifest (`src/data/media.ts`, `defineMedia`), rendered through
3
+ * `MediaFrame`, the library's one image path. Server component. It takes a skin entry with `layout.ratio`, which the
4
+ * services grid uses for its photos; everything else (provenance, the locked "Illustration" label, the focal point
5
+ * and the sizing rule) is `MediaFrame`'s.
6
+ *
7
+ * - `sizes` is required. A number is a fixed CSS width (logos, avatars, thumbnails, the header's featured image): it
8
+ * becomes `MediaFrame`'s `displayWidth`, so the image renders no `sizes` and Next emits only the 1x and 2x candidates
9
+ * (cost item 1g). A string is a fluid `sizes` value and must have a `vw` part.
10
+ * - `preload` is for the page's main (hero) image only: preloaded at high priority and never lazy.
11
+ * - `layout.ratio` crops to one of the allowed ratios, keeping the entry's focal point in view.
12
+ */
13
+ import type { ReactNode } from 'react';
14
+ import type { BrandlabMediaEntry, BrandlabMediaRatio } from '@brandlab-digital/site-core/media';
15
+ import type { SkinEntry } from '../skin.js';
16
+ export type MediaSlot = 'root' | 'img' | 'caption' | 'label';
17
+ export interface MediaLayout {
18
+ /** Crop to one of the allowed ratios (16:9, 4:5, 4:3, 3:2). Left out, the image keeps its own shape. */
19
+ readonly ratio?: BrandlabMediaRatio;
20
+ }
21
+ export type MediaSkin = SkinEntry<MediaSlot, never, MediaLayout>;
22
+ /** The visible label on an AI image (F13). Locked: a site can't rename or hide it. The same value as `ILLUSTRATION_LABEL`. */
23
+ export declare const MEDIA_ILLUSTRATION_LABEL = "Illustration";
24
+ export interface MediaProps extends MediaSkin {
25
+ /** The manifest entry, for example `media['castle-hill-gum-removal']`. */
26
+ readonly media: BrandlabMediaEntry;
27
+ /**
28
+ * The rendered width: a whole number of CSS pixels for a fixed-size image (no `sizes`, so 1x and 2x only), or a
29
+ * fluid `sizes` string with a `vw` part such as `(min-width: 1024px) 50vw, 100vw`.
30
+ */
31
+ readonly sizes: number | string;
32
+ /** The page's main image: preloaded at high priority, never lazy. One per page. */
33
+ readonly preload?: boolean;
34
+ /** A visible caption, for example "Gum removal over a pool, Epping, March 2026". */
35
+ readonly caption?: ReactNode;
36
+ }
37
+ export declare function Media({ media, sizes, preload, caption, layout, classes }: MediaProps): import("react/jsx-runtime").JSX.Element;
38
+ //# sourceMappingURL=media.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/primitives/media.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AAC/F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAG3C,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,KAAK,GAAG,SAAS,GAAG,OAAO,CAAA;AAC5D,MAAM,WAAW,WAAW;IAC1B,wGAAwG;IACxG,QAAQ,CAAC,KAAK,CAAC,EAAE,kBAAkB,CAAA;CACpC;AACD,MAAM,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,EAAE,KAAK,EAAE,WAAW,CAAC,CAAA;AAEhE,8HAA8H;AAC9H,eAAO,MAAM,wBAAwB,iBAAqB,CAAA;AAE1D,MAAM,WAAW,UAAW,SAAQ,SAAS;IAC3C,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAA;IAClC;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;IAC/B,mFAAmF;IACnF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B,oFAAoF;IACpF,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAA;CAC7B;AAED,wBAAgB,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAe,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,UAAU,2CAK5F"}
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ILLUSTRATION_LABEL, MediaFrame } from './media-frame.js';
3
+ /** The visible label on an AI image (F13). Locked: a site can't rename or hide it. The same value as `ILLUSTRATION_LABEL`. */
4
+ export const MEDIA_ILLUSTRATION_LABEL = ILLUSTRATION_LABEL;
5
+ export function Media({ media, sizes, preload = false, caption, layout, classes }) {
6
+ const sizing = typeof sizes === 'number' ? { displayWidth: sizes } : { sizes };
7
+ return (_jsx(MediaFrame, { media: media, ...sizing, ratio: layout?.ratio, caption: caption, priority: preload, classes: classes }));
8
+ }
9
+ //# sourceMappingURL=media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.js","sourceRoot":"","sources":["../../../src/ui-kit/primitives/media.tsx"],"names":[],"mappings":";AAeA,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AASjE,8HAA8H;AAC9H,MAAM,CAAC,MAAM,wBAAwB,GAAG,kBAAkB,CAAA;AAgB1D,MAAM,UAAU,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,GAAG,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAc;IAC3F,MAAM,MAAM,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAA;IAC9E,OAAO,CACL,KAAC,UAAU,IAAC,KAAK,EAAE,KAAK,KAAM,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAI,CACtH,CAAA;AACH,CAAC","sourcesContent":["/**\n * `Media` (F13, 3.14.0): one image from the site's media manifest (`src/data/media.ts`, `defineMedia`), rendered through\n * `MediaFrame`, the library's one image path. Server component. It takes a skin entry with `layout.ratio`, which the\n * services grid uses for its photos; everything else (provenance, the locked \"Illustration\" label, the focal point\n * and the sizing rule) is `MediaFrame`'s.\n *\n * - `sizes` is required. A number is a fixed CSS width (logos, avatars, thumbnails, the header's featured image): it\n * becomes `MediaFrame`'s `displayWidth`, so the image renders no `sizes` and Next emits only the 1x and 2x candidates\n * (cost item 1g). A string is a fluid `sizes` value and must have a `vw` part.\n * - `preload` is for the page's main (hero) image only: preloaded at high priority and never lazy.\n * - `layout.ratio` crops to one of the allowed ratios, keeping the entry's focal point in view.\n */\nimport type { ReactNode } from 'react'\nimport type { BrandlabMediaEntry, BrandlabMediaRatio } from '@brandlab-digital/site-core/media'\nimport type { SkinEntry } from '../skin.js'\nimport { ILLUSTRATION_LABEL, MediaFrame } from './media-frame.js'\n\nexport type MediaSlot = 'root' | 'img' | 'caption' | 'label'\nexport interface MediaLayout {\n /** Crop to one of the allowed ratios (16:9, 4:5, 4:3, 3:2). Left out, the image keeps its own shape. */\n readonly ratio?: BrandlabMediaRatio\n}\nexport type MediaSkin = SkinEntry<MediaSlot, never, MediaLayout>\n\n/** The visible label on an AI image (F13). Locked: a site can't rename or hide it. The same value as `ILLUSTRATION_LABEL`. */\nexport const MEDIA_ILLUSTRATION_LABEL = ILLUSTRATION_LABEL\n\nexport interface MediaProps extends MediaSkin {\n /** The manifest entry, for example `media['castle-hill-gum-removal']`. */\n readonly media: BrandlabMediaEntry\n /**\n * The rendered width: a whole number of CSS pixels for a fixed-size image (no `sizes`, so 1x and 2x only), or a\n * fluid `sizes` string with a `vw` part such as `(min-width: 1024px) 50vw, 100vw`.\n */\n readonly sizes: number | string\n /** The page's main image: preloaded at high priority, never lazy. One per page. */\n readonly preload?: boolean\n /** A visible caption, for example \"Gum removal over a pool, Epping, March 2026\". */\n readonly caption?: ReactNode\n}\n\nexport function Media({ media, sizes, preload = false, caption, layout, classes }: MediaProps) {\n const sizing = typeof sizes === 'number' ? { displayWidth: sizes } : { sizes }\n return (\n <MediaFrame media={media} {...sizing} ratio={layout?.ratio} caption={caption} priority={preload} classes={classes} />\n )\n}\n"]}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `Stat` (3.14.0): one proof figure, set large with its label (AA's proof strip, Astute's work numbers). Server
3
+ * component. The value comes from a verified fact and is the final text in the server HTML: no count-up and nothing
4
+ * hidden before scripts run. An optional link says where the figure can be checked (the reviews, the licence register).
5
+ *
6
+ * Stats are rendered once per entry of a strip, so their parts take no class (3.14.0 repeated slots): the site styles
7
+ * `[data-bl="stat"]` once in `src/site/skin.css`.
8
+ */
9
+ import type { RepeatedSlotOf } from '../repeated-slots.js';
10
+ import { type SkinEntry } from '../skin.js';
11
+ import type { UiLink } from '../types.js';
12
+ export type StatSlot = RepeatedSlotOf<'stat'>;
13
+ export type StatSkin = SkinEntry<StatSlot, never, Record<never, never>, StatSlot>;
14
+ export interface StatProps extends StatSkin {
15
+ /** The figure itself, for example "1,200+" or "4.9". From a verified fact, never typed for effect. */
16
+ readonly value: string;
17
+ /** What it counts, for example "trees removed safely since 2009". */
18
+ readonly label: string;
19
+ /** Where to check it, for example `{ label: 'Read the reviews', href: '/reviews' }`. Never prefetched. */
20
+ readonly link?: UiLink;
21
+ /** `li` inside a list the caller renders, or `div` (the default). */
22
+ readonly as?: 'div' | 'li';
23
+ }
24
+ export declare function Stat({ value, label, link, as: Root }: StatProps): import("react/jsx-runtime").JSX.Element;
25
+ //# sourceMappingURL=stat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stat.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/primitives/stat.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGzC,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,SAAU,SAAQ,QAAQ;IACzC,sGAAsG;IACtG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,0GAA0G;IAC1G,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,CAAC,EAAE,KAAK,GAAG,IAAI,CAAA;CAC3B;AAED,wBAAgB,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,IAAY,EAAE,EAAE,SAAS,2CAWvE"}
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { repeatedSlot } from '../skin.js';
3
+ import { Anchor } from './links.js';
4
+ export function Stat({ value, label, link, as: Root = 'div' }) {
5
+ return (_jsxs(Root, { "data-bl": "stat", children: [_jsx("span", { ...repeatedSlot('value'), children: value }), " ", _jsx("span", { ...repeatedSlot('label'), children: label }), link ? (_jsx(Anchor, { href: link.href, prefetch: false, hooks: { 'data-slot': 'link' }, children: link.label })) : null] }));
6
+ }
7
+ //# sourceMappingURL=stat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stat.js","sourceRoot":"","sources":["../../../src/ui-kit/primitives/stat.tsx"],"names":[],"mappings":";AASA,OAAO,EAAE,YAAY,EAAkB,MAAM,YAAY,CAAA;AAEzD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAgBnC,MAAM,UAAU,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,GAAG,KAAK,EAAa;IACtE,OAAO,CACL,MAAC,IAAI,eAAS,MAAM,aAClB,kBAAU,YAAY,CAAC,OAAO,CAAC,YAAG,KAAK,GAAQ,OAAC,kBAAU,YAAY,CAAC,OAAO,CAAC,YAAG,KAAK,GAAQ,EAC9F,IAAI,CAAC,CAAC,CAAC,CACN,KAAC,MAAM,IAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,YACrE,IAAI,CAAC,KAAK,GACJ,CACV,CAAC,CAAC,CAAC,IAAI,IACH,CACR,CAAA;AACH,CAAC","sourcesContent":["/**\n * `Stat` (3.14.0): one proof figure, set large with its label (AA's proof strip, Astute's work numbers). Server\n * component. The value comes from a verified fact and is the final text in the server HTML: no count-up and nothing\n * hidden before scripts run. An optional link says where the figure can be checked (the reviews, the licence register).\n *\n * Stats are rendered once per entry of a strip, so their parts take no class (3.14.0 repeated slots): the site styles\n * `[data-bl=\"stat\"]` once in `src/site/skin.css`.\n */\nimport type { RepeatedSlotOf } from '../repeated-slots.js'\nimport { repeatedSlot, type SkinEntry } from '../skin.js'\nimport type { UiLink } from '../types.js'\nimport { Anchor } from './links.js'\n\nexport type StatSlot = RepeatedSlotOf<'stat'>\nexport type StatSkin = SkinEntry<StatSlot, never, Record<never, never>, StatSlot>\n\nexport interface StatProps extends StatSkin {\n /** The figure itself, for example \"1,200+\" or \"4.9\". From a verified fact, never typed for effect. */\n readonly value: string\n /** What it counts, for example \"trees removed safely since 2009\". */\n readonly label: string\n /** Where to check it, for example `{ label: 'Read the reviews', href: '/reviews' }`. Never prefetched. */\n readonly link?: UiLink\n /** `li` inside a list the caller renders, or `div` (the default). */\n readonly as?: 'div' | 'li'\n}\n\nexport function Stat({ value, label, link, as: Root = 'div' }: StatProps) {\n return (\n <Root data-bl=\"stat\">\n <span {...repeatedSlot('value')}>{value}</span> <span {...repeatedSlot('label')}>{label}</span>\n {link ? (\n <Anchor href={link.href} prefetch={false} hooks={{ 'data-slot': 'link' }}>\n {link.label}\n </Anchor>\n ) : null}\n </Root>\n )\n}\n"]}