@brandlab-digital/site-next 3.13.0 → 3.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -0
- package/dist/analytics.d.ts +1 -1
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +2 -0
- package/dist/analytics.js.map +1 -1
- package/dist/contact/delivery.d.ts +81 -0
- package/dist/contact/delivery.d.ts.map +1 -0
- package/dist/contact/delivery.js +138 -0
- package/dist/contact/delivery.js.map +1 -0
- package/dist/contact/resend-webhook.d.ts +72 -0
- package/dist/contact/resend-webhook.d.ts.map +1 -0
- package/dist/contact/resend-webhook.js +300 -0
- package/dist/contact/resend-webhook.js.map +1 -0
- package/dist/contact/route-handler.d.ts +16 -0
- package/dist/contact/route-handler.d.ts.map +1 -1
- package/dist/contact/route-handler.js +8 -0
- package/dist/contact/route-handler.js.map +1 -1
- package/dist/contact/server-entry.d.ts +2 -0
- package/dist/contact/server-entry.d.ts.map +1 -1
- package/dist/contact/server-entry.js +2 -0
- package/dist/contact/server-entry.js.map +1 -1
- package/dist/contact/server.d.ts +26 -0
- package/dist/contact/server.d.ts.map +1 -1
- package/dist/contact/server.js +48 -3
- package/dist/contact/server.js.map +1 -1
- package/dist/navigation/constants.d.ts +11 -2
- package/dist/navigation/constants.d.ts.map +1 -1
- package/dist/navigation/constants.js +11 -2
- package/dist/navigation/constants.js.map +1 -1
- package/dist/navigation/index.d.ts +1 -1
- package/dist/navigation/index.d.ts.map +1 -1
- package/dist/navigation/index.js +1 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/navigation/use-back-to-top.d.ts.map +1 -1
- package/dist/navigation/use-back-to-top.js +12 -4
- package/dist/navigation/use-back-to-top.js.map +1 -1
- package/dist/navigation/use-header-metrics.d.ts +1 -1
- package/dist/navigation/use-header-metrics.js +1 -1
- package/dist/navigation/use-header-metrics.js.map +1 -1
- package/dist/navigation/use-sticky-call-bar.d.ts +2 -1
- package/dist/navigation/use-sticky-call-bar.d.ts.map +1 -1
- package/dist/navigation/use-sticky-call-bar.js +11 -2
- package/dist/navigation/use-sticky-call-bar.js.map +1 -1
- package/dist/next-config.d.ts +17 -1
- package/dist/next-config.d.ts.map +1 -1
- package/dist/next-config.js +36 -2
- package/dist/next-config.js.map +1 -1
- package/dist/ui-kit/brandlab-skin.d.ts +50 -1
- package/dist/ui-kit/brandlab-skin.d.ts.map +1 -1
- package/dist/ui-kit/brandlab-skin.js.map +1 -1
- package/dist/ui-kit/form/photo-upload.d.ts +4 -1
- package/dist/ui-kit/form/photo-upload.d.ts.map +1 -1
- package/dist/ui-kit/form/photo-upload.js +4 -4
- package/dist/ui-kit/form/photo-upload.js.map +1 -1
- package/dist/ui-kit/form/quote-form.d.ts +4 -1
- package/dist/ui-kit/form/quote-form.d.ts.map +1 -1
- package/dist/ui-kit/form/quote-form.js.map +1 -1
- package/dist/ui-kit/form/thank-you.d.ts +4 -1
- package/dist/ui-kit/form/thank-you.d.ts.map +1 -1
- package/dist/ui-kit/form/thank-you.js +2 -2
- package/dist/ui-kit/form/thank-you.js.map +1 -1
- package/dist/ui-kit/icon-glyphs.d.ts +41 -0
- package/dist/ui-kit/icon-glyphs.d.ts.map +1 -0
- package/dist/ui-kit/icon-glyphs.js +138 -0
- package/dist/ui-kit/icon-glyphs.js.map +1 -0
- package/dist/ui-kit/icon-sprite.d.ts +19 -0
- package/dist/ui-kit/icon-sprite.d.ts.map +1 -0
- package/dist/ui-kit/icon-sprite.js +44 -0
- package/dist/ui-kit/icon-sprite.js.map +1 -0
- package/dist/ui-kit/icons.d.ts +38 -3
- package/dist/ui-kit/icons.d.ts.map +1 -1
- package/dist/ui-kit/icons.js +47 -64
- package/dist/ui-kit/icons.js.map +1 -1
- package/dist/ui-kit/prefetch.d.ts +19 -10
- package/dist/ui-kit/prefetch.d.ts.map +1 -1
- package/dist/ui-kit/prefetch.js +23 -10
- package/dist/ui-kit/prefetch.js.map +1 -1
- package/dist/ui-kit/primitives/badge.d.ts +25 -0
- package/dist/ui-kit/primitives/badge.d.ts.map +1 -0
- package/dist/ui-kit/primitives/badge.js +21 -0
- package/dist/ui-kit/primitives/badge.js.map +1 -0
- package/dist/ui-kit/primitives/breadcrumbs.d.ts +27 -0
- package/dist/ui-kit/primitives/breadcrumbs.d.ts.map +1 -0
- package/dist/ui-kit/primitives/breadcrumbs.js +40 -0
- package/dist/ui-kit/primitives/breadcrumbs.js.map +1 -0
- package/dist/ui-kit/primitives/button.d.ts +32 -0
- package/dist/ui-kit/primitives/button.d.ts.map +1 -0
- package/dist/ui-kit/primitives/button.js +8 -0
- package/dist/ui-kit/primitives/button.js.map +1 -0
- package/dist/ui-kit/primitives/card.d.ts +40 -0
- package/dist/ui-kit/primitives/card.d.ts.map +1 -0
- package/dist/ui-kit/primitives/card.js +8 -0
- package/dist/ui-kit/primitives/card.js.map +1 -0
- package/dist/ui-kit/primitives/index.d.ts +11 -2
- package/dist/ui-kit/primitives/index.d.ts.map +1 -1
- package/dist/ui-kit/primitives/index.js +11 -2
- package/dist/ui-kit/primitives/index.js.map +1 -1
- package/dist/ui-kit/primitives/layout.d.ts +12 -0
- package/dist/ui-kit/primitives/layout.d.ts.map +1 -1
- package/dist/ui-kit/primitives/layout.js +20 -2
- package/dist/ui-kit/primitives/layout.js.map +1 -1
- package/dist/ui-kit/primitives/links.d.ts +15 -0
- package/dist/ui-kit/primitives/links.d.ts.map +1 -1
- package/dist/ui-kit/primitives/links.js +6 -2
- package/dist/ui-kit/primitives/links.js.map +1 -1
- package/dist/ui-kit/primitives/lists.d.ts +51 -0
- package/dist/ui-kit/primitives/lists.d.ts.map +1 -0
- package/dist/ui-kit/primitives/lists.js +34 -0
- package/dist/ui-kit/primitives/lists.js.map +1 -0
- package/dist/ui-kit/primitives/media-frame.d.ts +28 -0
- package/dist/ui-kit/primitives/media-frame.d.ts.map +1 -0
- package/dist/ui-kit/primitives/media-frame.js +58 -0
- package/dist/ui-kit/primitives/media-frame.js.map +1 -0
- package/dist/ui-kit/primitives/media.d.ts +38 -0
- package/dist/ui-kit/primitives/media.d.ts.map +1 -0
- package/dist/ui-kit/primitives/media.js +9 -0
- package/dist/ui-kit/primitives/media.js.map +1 -0
- package/dist/ui-kit/primitives/quote-button.d.ts +25 -2
- package/dist/ui-kit/primitives/quote-button.d.ts.map +1 -1
- package/dist/ui-kit/primitives/quote-button.js +25 -3
- package/dist/ui-kit/primitives/quote-button.js.map +1 -1
- package/dist/ui-kit/primitives/stat.d.ts +25 -0
- package/dist/ui-kit/primitives/stat.d.ts.map +1 -0
- package/dist/ui-kit/primitives/stat.js +7 -0
- package/dist/ui-kit/primitives/stat.js.map +1 -0
- package/dist/ui-kit/repeated-slots.d.ts +153 -0
- package/dist/ui-kit/repeated-slots.d.ts.map +1 -0
- package/dist/ui-kit/repeated-slots.js +97 -0
- package/dist/ui-kit/repeated-slots.js.map +1 -0
- package/dist/ui-kit/sections/accordion.d.ts +4 -1
- package/dist/ui-kit/sections/accordion.d.ts.map +1 -1
- package/dist/ui-kit/sections/accordion.js +2 -2
- package/dist/ui-kit/sections/accordion.js.map +1 -1
- package/dist/ui-kit/sections/area-map.d.ts +64 -0
- package/dist/ui-kit/sections/area-map.d.ts.map +1 -0
- package/dist/ui-kit/sections/area-map.js +142 -0
- package/dist/ui-kit/sections/area-map.js.map +1 -0
- package/dist/ui-kit/sections/before-after.d.ts +59 -0
- package/dist/ui-kit/sections/before-after.d.ts.map +1 -0
- package/dist/ui-kit/sections/before-after.js +42 -0
- package/dist/ui-kit/sections/before-after.js.map +1 -0
- package/dist/ui-kit/sections/contact-block.d.ts +4 -1
- package/dist/ui-kit/sections/contact-block.d.ts.map +1 -1
- package/dist/ui-kit/sections/contact-block.js +3 -3
- package/dist/ui-kit/sections/contact-block.js.map +1 -1
- package/dist/ui-kit/sections/council-rules.d.ts +64 -0
- package/dist/ui-kit/sections/council-rules.d.ts.map +1 -0
- package/dist/ui-kit/sections/council-rules.js +46 -0
- package/dist/ui-kit/sections/council-rules.js.map +1 -0
- package/dist/ui-kit/sections/coverage.d.ts +53 -0
- package/dist/ui-kit/sections/coverage.d.ts.map +1 -0
- package/dist/ui-kit/sections/coverage.js +57 -0
- package/dist/ui-kit/sections/coverage.js.map +1 -0
- package/dist/ui-kit/sections/emergency-band.d.ts +60 -0
- package/dist/ui-kit/sections/emergency-band.d.ts.map +1 -0
- package/dist/ui-kit/sections/emergency-band.js +43 -0
- package/dist/ui-kit/sections/emergency-band.js.map +1 -0
- package/dist/ui-kit/sections/faqs.d.ts +4 -1
- package/dist/ui-kit/sections/faqs.d.ts.map +1 -1
- package/dist/ui-kit/sections/faqs.js +4 -5
- package/dist/ui-kit/sections/faqs.js.map +1 -1
- package/dist/ui-kit/sections/gallery.d.ts +60 -0
- package/dist/ui-kit/sections/gallery.d.ts.map +1 -0
- package/dist/ui-kit/sections/gallery.js +39 -0
- package/dist/ui-kit/sections/gallery.js.map +1 -0
- package/dist/ui-kit/sections/hero.d.ts +118 -0
- package/dist/ui-kit/sections/hero.d.ts.map +1 -0
- package/dist/ui-kit/sections/hero.js +58 -0
- package/dist/ui-kit/sections/hero.js.map +1 -0
- package/dist/ui-kit/sections/index.d.ts +16 -1
- package/dist/ui-kit/sections/index.d.ts.map +1 -1
- package/dist/ui-kit/sections/index.js +16 -1
- package/dist/ui-kit/sections/index.js.map +1 -1
- package/dist/ui-kit/sections/licences.d.ts +72 -0
- package/dist/ui-kit/sections/licences.d.ts.map +1 -0
- package/dist/ui-kit/sections/licences.js +46 -0
- package/dist/ui-kit/sections/licences.js.map +1 -0
- package/dist/ui-kit/sections/local-facts.d.ts +78 -0
- package/dist/ui-kit/sections/local-facts.d.ts.map +1 -0
- package/dist/ui-kit/sections/local-facts.js +32 -0
- package/dist/ui-kit/sections/local-facts.js.map +1 -0
- package/dist/ui-kit/sections/nearby-areas.d.ts +50 -0
- package/dist/ui-kit/sections/nearby-areas.d.ts.map +1 -0
- package/dist/ui-kit/sections/nearby-areas.js +49 -0
- package/dist/ui-kit/sections/nearby-areas.js.map +1 -0
- package/dist/ui-kit/sections/nearby-jobs.d.ts +66 -0
- package/dist/ui-kit/sections/nearby-jobs.d.ts.map +1 -0
- package/dist/ui-kit/sections/nearby-jobs.js +49 -0
- package/dist/ui-kit/sections/nearby-jobs.js.map +1 -0
- package/dist/ui-kit/sections/price-guide.d.ts +4 -1
- package/dist/ui-kit/sections/price-guide.d.ts.map +1 -1
- package/dist/ui-kit/sections/price-guide.js +3 -3
- package/dist/ui-kit/sections/price-guide.js.map +1 -1
- package/dist/ui-kit/sections/price-table.d.ts +7 -9
- package/dist/ui-kit/sections/price-table.d.ts.map +1 -1
- package/dist/ui-kit/sections/price-table.js +8 -8
- package/dist/ui-kit/sections/price-table.js.map +1 -1
- package/dist/ui-kit/sections/projects.d.ts +79 -0
- package/dist/ui-kit/sections/projects.d.ts.map +1 -0
- package/dist/ui-kit/sections/projects.js +69 -0
- package/dist/ui-kit/sections/projects.js.map +1 -0
- package/dist/ui-kit/sections/proof-strip.d.ts +4 -1
- package/dist/ui-kit/sections/proof-strip.d.ts.map +1 -1
- package/dist/ui-kit/sections/proof-strip.js +2 -2
- package/dist/ui-kit/sections/proof-strip.js.map +1 -1
- package/dist/ui-kit/sections/quote-checklist.d.ts +43 -0
- package/dist/ui-kit/sections/quote-checklist.d.ts.map +1 -0
- package/dist/ui-kit/sections/quote-checklist.js +19 -0
- package/dist/ui-kit/sections/quote-checklist.js.map +1 -0
- package/dist/ui-kit/sections/related-services.d.ts +41 -0
- package/dist/ui-kit/sections/related-services.d.ts.map +1 -0
- package/dist/ui-kit/sections/related-services.js +52 -0
- package/dist/ui-kit/sections/related-services.js.map +1 -0
- package/dist/ui-kit/sections/reviews-band.d.ts +16 -2
- package/dist/ui-kit/sections/reviews-band.d.ts.map +1 -1
- package/dist/ui-kit/sections/reviews-band.js +13 -2
- package/dist/ui-kit/sections/reviews-band.js.map +1 -1
- package/dist/ui-kit/sections/services-grid.d.ts +82 -0
- package/dist/ui-kit/sections/services-grid.d.ts.map +1 -0
- package/dist/ui-kit/sections/services-grid.js +90 -0
- package/dist/ui-kit/sections/services-grid.js.map +1 -0
- package/dist/ui-kit/sections/site-footer.d.ts +4 -1
- package/dist/ui-kit/sections/site-footer.d.ts.map +1 -1
- package/dist/ui-kit/sections/site-footer.js +6 -6
- package/dist/ui-kit/sections/site-footer.js.map +1 -1
- package/dist/ui-kit/sections/work-photos.d.ts +15 -0
- package/dist/ui-kit/sections/work-photos.d.ts.map +1 -0
- package/dist/ui-kit/sections/work-photos.js +24 -0
- package/dist/ui-kit/sections/work-photos.js.map +1 -0
- package/dist/ui-kit/shell/back-to-top.js +1 -1
- package/dist/ui-kit/shell/back-to-top.js.map +1 -1
- package/dist/ui-kit/shell/current-link.d.ts +21 -5
- package/dist/ui-kit/shell/current-link.d.ts.map +1 -1
- package/dist/ui-kit/shell/current-link.js +16 -5
- package/dist/ui-kit/shell/current-link.js.map +1 -1
- package/dist/ui-kit/shell/drawer-client.js +1 -1
- package/dist/ui-kit/shell/drawer-client.js.map +1 -1
- package/dist/ui-kit/shell/intent-prefetch.d.ts.map +1 -1
- package/dist/ui-kit/shell/intent-prefetch.js +3 -2
- package/dist/ui-kit/shell/intent-prefetch.js.map +1 -1
- package/dist/ui-kit/shell/mobile-call-bar.d.ts +6 -1
- package/dist/ui-kit/shell/mobile-call-bar.d.ts.map +1 -1
- package/dist/ui-kit/shell/mobile-call-bar.js +6 -3
- package/dist/ui-kit/shell/mobile-call-bar.js.map +1 -1
- package/dist/ui-kit/shell/mobile-drawer.d.ts +7 -3
- package/dist/ui-kit/shell/mobile-drawer.d.ts.map +1 -1
- package/dist/ui-kit/shell/mobile-drawer.js +21 -5
- package/dist/ui-kit/shell/mobile-drawer.js.map +1 -1
- package/dist/ui-kit/shell/shell-quote.d.ts +10 -0
- package/dist/ui-kit/shell/shell-quote.d.ts.map +1 -0
- package/dist/ui-kit/shell/shell-quote.js +13 -0
- package/dist/ui-kit/shell/shell-quote.js.map +1 -0
- package/dist/ui-kit/shell/site-header.d.ts +5 -2
- package/dist/ui-kit/shell/site-header.d.ts.map +1 -1
- package/dist/ui-kit/shell/site-header.js +9 -9
- package/dist/ui-kit/shell/site-header.js.map +1 -1
- package/dist/ui-kit/shell/social-links.d.ts +1 -2
- package/dist/ui-kit/shell/social-links.d.ts.map +1 -1
- package/dist/ui-kit/shell/social-links.js +6 -3
- package/dist/ui-kit/shell/social-links.js.map +1 -1
- package/dist/ui-kit/shell/utility-bar.d.ts +4 -1
- package/dist/ui-kit/shell/utility-bar.d.ts.map +1 -1
- package/dist/ui-kit/shell/utility-bar.js +2 -2
- package/dist/ui-kit/shell/utility-bar.js.map +1 -1
- package/dist/ui-kit/skin-index.d.ts +2 -1
- package/dist/ui-kit/skin-index.d.ts.map +1 -1
- package/dist/ui-kit/skin-index.js +2 -1
- package/dist/ui-kit/skin-index.js.map +1 -1
- package/dist/ui-kit/skin.d.ts +21 -4
- package/dist/ui-kit/skin.d.ts.map +1 -1
- package/dist/ui-kit/skin.js +14 -1
- package/dist/ui-kit/skin.js.map +1 -1
- package/dist/ui-kit/structure.css +793 -10
- package/package.json +6 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviews-band.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/reviews-band.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAChD,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,EACpC,MAAM,qDAAqD,CAAA;AAC5D,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAChG,OAAO,
|
|
1
|
+
{"version":3,"file":"reviews-band.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/reviews-band.tsx"],"names":[],"mappings":"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"}
|
|
@@ -16,8 +16,9 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
16
16
|
*/
|
|
17
17
|
import { Children } from 'react';
|
|
18
18
|
import { businessProfileReviewMentions, } from '@brandlab-digital/site-core/google-business-reviews';
|
|
19
|
+
import { SECTION_READY } from '@brandlab-digital/site-core/section-order';
|
|
19
20
|
import { RatingStars, Section, SectionHeading } from '../primitives/layout.js';
|
|
20
|
-
import { slot } from '../skin.js';
|
|
21
|
+
import { repeatedSlot, slot } from '../skin.js';
|
|
21
22
|
import { hasGoogleRating, RatingChip } from './rating-chip.js';
|
|
22
23
|
export const REVIEWS_BAND_VARIANTS = ['band', 'feature', 'cards', 'wall', 'summary'];
|
|
23
24
|
export const REVIEWS_BAND_HEADING_ID = 'bl-reviews-heading';
|
|
@@ -34,6 +35,16 @@ function shownVariant(reviews, variant) {
|
|
|
34
35
|
}
|
|
35
36
|
const MONTH = new Intl.DateTimeFormat('en-AU', { month: 'long', year: 'numeric', timeZone: 'Australia/Sydney' });
|
|
36
37
|
const DAY = new Intl.DateTimeFormat('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit', timeZone: 'Australia/Sydney' });
|
|
38
|
+
/** The skip reason a band with nothing to show records in the section manifest: the feed has no reviews or rating. */
|
|
39
|
+
export const REVIEWS_BAND_SKIP_REASON = 'no-feed-reviews';
|
|
40
|
+
/**
|
|
41
|
+
* The guard for `SectionSequence` (3.15.0), from the same prepared reviews the band renders: ready when the band
|
|
42
|
+
* renders (written reviews, or Google's rating alone as the summary fallback); otherwise `no-feed-reviews`, for a
|
|
43
|
+
* missing feed, an empty one, or the `summary` variant without a rating.
|
|
44
|
+
*/
|
|
45
|
+
export function reviewsBandReady(reviews, { variant = 'band' } = {}) {
|
|
46
|
+
return shownVariant(reviews, variant) ? SECTION_READY : { render: false, reason: REVIEWS_BAND_SKIP_REASON };
|
|
47
|
+
}
|
|
37
48
|
/**
|
|
38
49
|
* A long review split at a sentence end (or else a space) near `limit`, so the first part reads on its own. The two
|
|
39
50
|
* parts together are the review word for word. Short reviews, and reviews only a little over the limit, don't fold.
|
|
@@ -84,7 +95,7 @@ export function ReviewsBandList({ classes, children }) {
|
|
|
84
95
|
export function ReviewsBandItem({ review, lead = false, layout, classes }) {
|
|
85
96
|
const parts = splitReviewText(review.text, layout?.excerpt ?? DEFAULT_EXCERPT);
|
|
86
97
|
const date = new Date(review.createTime);
|
|
87
|
-
return (_jsx("li", { ...
|
|
98
|
+
return (_jsx("li", { ...repeatedSlot('item'), "data-lead": lead ? 'true' : undefined, children: _jsxs("figure", { children: [_jsx("span", { ...repeatedSlot('stars'), children: _jsx(RatingStars, { rating: review.rating, label: `Rated ${review.rating} out of 5` }) }), _jsx("blockquote", { ...repeatedSlot('quote'), children: parts ? (_jsxs(_Fragment, { children: [_jsx("p", { children: parts[0] }), _jsxs("details", { ...repeatedSlot('more'), children: [_jsx("summary", { children: "Read more" }), _jsx("p", { children: parts[1] })] })] })) : (_jsx("p", { children: review.text })) }), _jsxs("figcaption", { ...repeatedSlot('author'), children: [layout?.avatars && review.authorPhotoUrl ? (_jsx("img", { ...repeatedSlot('avatar'), src: review.authorPhotoUrl, alt: "", width: 48, height: 48, loading: "lazy", decoding: "async", referrerPolicy: "no-referrer" })) : null, _jsx("span", { ...repeatedSlot('name'), children: review.authorName }), ' ', _jsx("time", { ...repeatedSlot('date'), dateTime: DAY.format(date), children: MONTH.format(date) })] })] }) }));
|
|
88
99
|
}
|
|
89
100
|
function ReviewsBandSection({ reviews, heading, eyebrow, intro, headingId = REVIEWS_BAND_HEADING_ID, tone, decoration, variant = 'band', layout, classes }) {
|
|
90
101
|
const shown = shownVariant(reviews, variant);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviews-band.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/reviews-band.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * S7 `ReviewsBand` (`reviews`): prominent, honest proof from the Business Profile feed. Server component, no client\n * JS. Google's own average and total sit beside the selected reviews with a link to the profile; the text is verbatim\n * and always in the HTML (a long review folds behind a native \"Read more\" disclosure); the band says how the reviews\n * were chosen (\"Selected five-star Google reviews\" or \"Recent Google reviews\"). No review or rating schema: a business\n * that controls its own reviews isn't eligible for review stars.\n *\n * Data comes only from `prepareBusinessProfileReviews` (site-core), which applies the policy, mention ordering, the\n * 3.7.0 exclude guard and the limit. No prepared reviews, or none and no rating, renders nothing at all (no heading).\n * With no written reviews but a rating, every variant renders as `summary`: the heading and the rating chip only.\n * Never place it on the contact page or inside another section.\n *\n * Sites that arrange their own layout use the parts (`ReviewsBand.Root`, `.Heading`, `.Summary`, `.List`, `.Item`):\n * the root keeps the section semantics, the empty rule and the disclosure line; the parts keep the data and markup.\n */\nimport { Children, type ReactNode } from 'react'\nimport {\n businessProfileReviewMentions,\n type BusinessProfileReview,\n type PreparedBusinessProfileReviews,\n} from '@brandlab-digital/site-core/google-business-reviews'\nimport { RatingStars, Section, SectionHeading, type SectionTone } from '../primitives/layout.js'\nimport { slot, type SkinEntry, type SlotClasses } from '../skin.js'\nimport { hasGoogleRating, RatingChip } from './rating-chip.js'\n\nexport const REVIEWS_BAND_VARIANTS = ['band', 'feature', 'cards', 'wall', 'summary'] as const\nexport type ReviewsBandVariant = (typeof REVIEWS_BAND_VARIANTS)[number]\n\nexport type ReviewsBandSlot =\n | 'root'\n | 'inner'\n | 'top'\n | 'head'\n | 'eyebrow'\n | 'heading'\n | 'lead'\n | 'summary'\n | 'rating'\n | 'source'\n | 'list'\n | 'item'\n | 'stars'\n | 'quote'\n | 'more'\n | 'author'\n | 'avatar'\n | 'name'\n | 'date'\n | 'disclosure'\n | 'profileLink'\n\nexport interface ReviewsBandLayout {\n readonly columns?: 1 | 2 | 3\n readonly align?: 'start' | 'center'\n /** Reviewer photos beside the names (Google's own small photos, decorative). */\n readonly avatars?: boolean\n /** Characters shown before a long review folds behind \"Read more\" (default 320); `false` never folds. */\n readonly excerpt?: number | false\n}\n\nexport type ReviewsBandSkin = SkinEntry<ReviewsBandSlot, ReviewsBandVariant, ReviewsBandLayout>\n\ntype Classes = SlotClasses<ReviewsBandSlot> | undefined\n\nexport const REVIEWS_BAND_HEADING_ID = 'bl-reviews-heading'\nconst DEFAULT_EXCERPT = 320\n\n/**\n * The variant a band renders, or null when it has nothing true to show. With no written reviews every variant is\n * `summary`, and `summary` needs Google's rating: a heading over nothing never renders.\n */\nfunction shownVariant(reviews: PreparedBusinessProfileReviews | null | undefined, variant: ReviewsBandVariant): ReviewsBandVariant | null {\n if (!reviews) return null\n const shown = reviews.reviews.length === 0 ? 'summary' : variant\n return shown === 'summary' && !hasGoogleRating(reviews) ? null : shown\n}\n\nconst MONTH = new Intl.DateTimeFormat('en-AU', { month: 'long', year: 'numeric', timeZone: 'Australia/Sydney' })\nconst DAY = new Intl.DateTimeFormat('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit', timeZone: 'Australia/Sydney' })\n\n/**\n * A long review split at a sentence end (or else a space) near `limit`, so the first part reads on its own. The two\n * parts together are the review word for word. Short reviews, and reviews only a little over the limit, don't fold.\n */\nexport function splitReviewText(text: string, limit: number | false): readonly [string, string] | null {\n if (limit === false || text.length <= limit * 1.25) return null\n const window = text.slice(0, limit)\n const sentence = Math.max(...['. ', '! ', '? ', '\\n'].map((mark) => window.lastIndexOf(mark)))\n const space = window.lastIndexOf(' ')\n const cut = sentence >= limit * 0.4 ? sentence + 1 : space > 0 ? space : limit\n return [text.slice(0, cut).trimEnd(), text.slice(cut).trimStart()]\n}\n\n/** The `feature` variant leads with the longest review naming a mention word, or else the longest review. */\nfunction featureOrder(reviews: PreparedBusinessProfileReviews): BusinessProfileReview[] {\n const named = reviews.mention.length > 0 ? reviews.reviews.filter((review) => businessProfileReviewMentions(review, reviews.mention)) : []\n const pool = named.length > 0 ? named : reviews.reviews\n const lead = pool.reduce<BusinessProfileReview | undefined>((longest, review) => (!longest || review.text.length > longest.text.length ? review : longest), undefined)\n return lead ? [lead, ...reviews.reviews.filter((review) => review !== lead)] : []\n}\n\nexport interface ReviewsBandRootProps extends ReviewsBandSkin {\n readonly reviews: PreparedBusinessProfileReviews | null | undefined\n /** The id of the band's H2 (default `bl-reviews-heading`). */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n /** Signature art, rendered `aria-hidden`. */\n readonly decoration?: ReactNode\n readonly children: ReactNode\n}\n\n/**\n * The section: `data-bl-section=\"reviews\"` with `data-bl-source=\"feed\"` (the ready gate's mark for feed numbers),\n * the empty rule, and the locked disclosure line after the children.\n */\nexport function ReviewsBandRoot({\n reviews,\n headingId = REVIEWS_BAND_HEADING_ID,\n tone,\n decoration,\n variant = 'band',\n layout,\n classes,\n children,\n}: ReviewsBandRootProps) {\n const shown = shownVariant(reviews, variant)\n if (!shown || !reviews) return null\n return (\n <Section\n id=\"reviews\"\n labelledBy={headingId}\n tone={tone}\n variant={shown}\n classes={{ root: classes?.root, inner: classes?.inner }}\n decoration={decoration}\n hooks={{ 'data-bl-source': 'feed', 'data-columns': String(layout?.columns ?? 3), 'data-align': layout?.align ?? 'start' }}\n >\n {children}\n {shown === 'summary' ? null : (\n <p {...slot(classes, 'disclosure')}>\n {reviews.policy === 'five-star' ? 'Selected five-star Google reviews.' : 'Recent Google reviews.'}{' '}\n <a {...slot(classes, 'profileLink')} href={reviews.profileUrl} target=\"_blank\" rel=\"noopener noreferrer\">\n Read all reviews on Google\n </a>\n </p>\n )}\n </Section>\n )\n}\n\nexport interface ReviewsBandHeadingProps {\n /** The H2's id; the same `headingId` the root has (default `bl-reviews-heading`). */\n readonly id?: string\n /** The H2 text the site owns. */\n readonly children: string\n readonly eyebrow?: string\n readonly intro?: string\n readonly layout?: Pick<ReviewsBandLayout, 'align'>\n readonly classes?: Classes\n}\n\n/** The heading block: an optional eyebrow, the H2 and an optional intro. */\nexport function ReviewsBandHeading({ id = REVIEWS_BAND_HEADING_ID, children, eyebrow, intro, layout, classes }: ReviewsBandHeadingProps) {\n return (\n <SectionHeading\n id={id}\n title={children}\n eyebrow={eyebrow}\n lead={intro}\n layout={{ align: layout?.align ?? 'start' }}\n classes={{ head: classes?.head, eyebrow: classes?.eyebrow, heading: classes?.heading, lead: classes?.lead }}\n />\n )\n}\n\nexport interface ReviewsBandSummaryProps {\n readonly reviews: PreparedBusinessProfileReviews | null | undefined\n readonly classes?: Classes\n}\n\n/** Google's own average and total with the profile link; nothing when the feed has no rating. */\nexport function ReviewsBandSummary({ reviews, classes }: ReviewsBandSummaryProps) {\n if (!hasGoogleRating(reviews)) return null\n return (\n <div {...slot(classes, 'summary')}>\n <RatingChip reviews={reviews} classes={{ rating: classes?.rating, source: classes?.source }} />\n </div>\n )\n}\n\n/** The review list; nothing without items. */\nexport function ReviewsBandList({ classes, children }: { readonly classes?: Classes; readonly children: ReactNode }) {\n if (Children.toArray(children).length === 0) return null\n return <ul {...slot(classes, 'list')}>{children}</ul>\n}\n\nexport interface ReviewsBandItemProps {\n readonly review: BusinessProfileReview\n /** The `feature` variant's wide lead card. */\n readonly lead?: boolean\n readonly layout?: Pick<ReviewsBandLayout, 'avatars' | 'excerpt'>\n readonly classes?: Classes\n}\n\n/** One review, word for word: its stars, the quote and the reviewer with the month. */\nexport function ReviewsBandItem({ review, lead = false, layout, classes }: ReviewsBandItemProps) {\n const parts = splitReviewText(review.text, layout?.excerpt ?? DEFAULT_EXCERPT)\n const date = new Date(review.createTime)\n return (\n <li {...slot(classes, 'item')} data-lead={lead ? 'true' : undefined}>\n <figure>\n <span {...slot(classes, 'stars')}>\n <RatingStars rating={review.rating} label={`Rated ${review.rating} out of 5`} />\n </span>\n <blockquote {...slot(classes, 'quote')}>\n {parts ? (\n <>\n <p>{parts[0]}</p>\n <details {...slot(classes, 'more')}>\n <summary>Read more</summary>\n <p>{parts[1]}</p>\n </details>\n </>\n ) : (\n <p>{review.text}</p>\n )}\n </blockquote>\n <figcaption {...slot(classes, 'author')}>\n {layout?.avatars && review.authorPhotoUrl ? (\n <img\n {...slot(classes, 'avatar')}\n src={review.authorPhotoUrl}\n alt=\"\"\n width={48}\n height={48}\n loading=\"lazy\"\n decoding=\"async\"\n referrerPolicy=\"no-referrer\"\n />\n ) : null}\n <span {...slot(classes, 'name')}>{review.authorName}</span>{' '}\n <time {...slot(classes, 'date')} dateTime={DAY.format(date)}>\n {MONTH.format(date)}\n </time>\n </figcaption>\n </figure>\n </li>\n )\n}\n\nexport interface ReviewsBandProps extends ReviewsBandSkin {\n /** From `prepareBusinessProfileReviews`; null renders nothing at all. */\n readonly reviews: PreparedBusinessProfileReviews | null | undefined\n /** The H2 text the site owns. */\n readonly heading: string\n readonly eyebrow?: string\n readonly intro?: string\n /** The id of the band's H2 (default `bl-reviews-heading`). */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n /** Signature art, rendered `aria-hidden`. */\n readonly decoration?: ReactNode\n}\n\nfunction ReviewsBandSection({ reviews, heading, eyebrow, intro, headingId = REVIEWS_BAND_HEADING_ID, tone, decoration, variant = 'band', layout, classes }: ReviewsBandProps) {\n const shown = shownVariant(reviews, variant)\n if (!shown || !reviews) return null\n const items = shown === 'summary' ? [] : shown === 'feature' ? featureOrder(reviews) : reviews.reviews\n return (\n <ReviewsBandRoot reviews={reviews} headingId={headingId} tone={tone} decoration={decoration} variant={shown} layout={layout} classes={classes}>\n <div {...slot(classes, 'top')}>\n <ReviewsBandHeading id={headingId} eyebrow={eyebrow} intro={intro} layout={layout} classes={classes}>\n {heading}\n </ReviewsBandHeading>\n <ReviewsBandSummary reviews={reviews} classes={classes} />\n </div>\n <ReviewsBandList classes={classes}>\n {items.map((review, index) => (\n <ReviewsBandItem key={review.id} review={review} lead={shown === 'feature' && index === 0} layout={layout} classes={classes} />\n ))}\n </ReviewsBandList>\n </ReviewsBandRoot>\n )\n}\n\n/** The reviews band, with its parts for sites that arrange their own layout. */\nexport const ReviewsBand = Object.assign(ReviewsBandSection, {\n Root: ReviewsBandRoot,\n Heading: ReviewsBandHeading,\n Summary: ReviewsBandSummary,\n List: ReviewsBandList,\n Item: ReviewsBandItem,\n})\n"]}
|
|
1
|
+
{"version":3,"file":"reviews-band.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/reviews-band.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * S7 `ReviewsBand` (`reviews`): prominent, honest proof from the Business Profile feed. Server component, no client\n * JS. Google's own average and total sit beside the selected reviews with a link to the profile; the text is verbatim\n * and always in the HTML (a long review folds behind a native \"Read more\" disclosure); the band says how the reviews\n * were chosen (\"Selected five-star Google reviews\" or \"Recent Google reviews\"). No review or rating schema: a business\n * that controls its own reviews isn't eligible for review stars.\n *\n * Data comes only from `prepareBusinessProfileReviews` (site-core), which applies the policy, mention ordering, the\n * 3.7.0 exclude guard and the limit. No prepared reviews, or none and no rating, renders nothing at all (no heading).\n * With no written reviews but a rating, every variant renders as `summary`: the heading and the rating chip only.\n * Never place it on the contact page or inside another section.\n *\n * Sites that arrange their own layout use the parts (`ReviewsBand.Root`, `.Heading`, `.Summary`, `.List`, `.Item`):\n * the root keeps the section semantics, the empty rule and the disclosure line; the parts keep the data and markup.\n */\nimport { Children, type ReactNode } from 'react'\nimport {\n businessProfileReviewMentions,\n type BusinessProfileReview,\n type PreparedBusinessProfileReviews,\n} from '@brandlab-digital/site-core/google-business-reviews'\nimport { SECTION_READY, type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order'\nimport { RatingStars, Section, SectionHeading, type SectionTone } from '../primitives/layout.js'\nimport type { RepeatedSlotOf } from '../repeated-slots.js'\nimport { repeatedSlot, slot, type SkinEntry, type SlotClasses } from '../skin.js'\nimport { hasGoogleRating, RatingChip } from './rating-chip.js'\n\nexport const REVIEWS_BAND_VARIANTS = ['band', 'feature', 'cards', 'wall', 'summary'] as const\nexport type ReviewsBandVariant = (typeof REVIEWS_BAND_VARIANTS)[number]\n\nexport type ReviewsBandSlot =\n | 'root'\n | 'inner'\n | 'top'\n | 'head'\n | 'eyebrow'\n | 'heading'\n | 'lead'\n | 'summary'\n | 'rating'\n | 'source'\n | 'list'\n | 'item'\n | 'stars'\n | 'quote'\n | 'more'\n | 'author'\n | 'avatar'\n | 'name'\n | 'date'\n | 'disclosure'\n | 'profileLink'\n\nexport interface ReviewsBandLayout {\n readonly columns?: 1 | 2 | 3\n readonly align?: 'start' | 'center'\n /** Reviewer photos beside the names (Google's own small photos, decorative). */\n readonly avatars?: boolean\n /** Characters shown before a long review folds behind \"Read more\" (default 320); `false` never folds. */\n readonly excerpt?: number | false\n}\n\n/** Rendered once per review: styled from the skin stylesheet, never by class (3.14.0). */\nexport type ReviewsBandRepeatedSlot = RepeatedSlotOf<'reviewsBand'>\nexport type ReviewsBandSkin = SkinEntry<ReviewsBandSlot, ReviewsBandVariant, ReviewsBandLayout, ReviewsBandRepeatedSlot>\n\ntype Classes = SlotClasses<Exclude<ReviewsBandSlot, ReviewsBandRepeatedSlot>> | undefined\n\nexport const REVIEWS_BAND_HEADING_ID = 'bl-reviews-heading'\nconst DEFAULT_EXCERPT = 320\n\n/**\n * The variant a band renders, or null when it has nothing true to show. With no written reviews every variant is\n * `summary`, and `summary` needs Google's rating: a heading over nothing never renders.\n */\nfunction shownVariant(reviews: PreparedBusinessProfileReviews | null | undefined, variant: ReviewsBandVariant): ReviewsBandVariant | null {\n if (!reviews) return null\n const shown = reviews.reviews.length === 0 ? 'summary' : variant\n return shown === 'summary' && !hasGoogleRating(reviews) ? null : shown\n}\n\nconst MONTH = new Intl.DateTimeFormat('en-AU', { month: 'long', year: 'numeric', timeZone: 'Australia/Sydney' })\nconst DAY = new Intl.DateTimeFormat('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit', timeZone: 'Australia/Sydney' })\n\n/** The skip reason a band with nothing to show records in the section manifest: the feed has no reviews or rating. */\nexport const REVIEWS_BAND_SKIP_REASON = 'no-feed-reviews'\n\n/**\n * The guard for `SectionSequence` (3.15.0), from the same prepared reviews the band renders: ready when the band\n * renders (written reviews, or Google's rating alone as the summary fallback); otherwise `no-feed-reviews`, for a\n * missing feed, an empty one, or the `summary` variant without a rating.\n */\nexport function reviewsBandReady(\n reviews: PreparedBusinessProfileReviews | null | undefined,\n { variant = 'band' }: { readonly variant?: ReviewsBandVariant } = {},\n): BrandlabSectionReadiness {\n return shownVariant(reviews, variant) ? SECTION_READY : { render: false, reason: REVIEWS_BAND_SKIP_REASON }\n}\n\n/**\n * A long review split at a sentence end (or else a space) near `limit`, so the first part reads on its own. The two\n * parts together are the review word for word. Short reviews, and reviews only a little over the limit, don't fold.\n */\nexport function splitReviewText(text: string, limit: number | false): readonly [string, string] | null {\n if (limit === false || text.length <= limit * 1.25) return null\n const window = text.slice(0, limit)\n const sentence = Math.max(...['. ', '! ', '? ', '\\n'].map((mark) => window.lastIndexOf(mark)))\n const space = window.lastIndexOf(' ')\n const cut = sentence >= limit * 0.4 ? sentence + 1 : space > 0 ? space : limit\n return [text.slice(0, cut).trimEnd(), text.slice(cut).trimStart()]\n}\n\n/** The `feature` variant leads with the longest review naming a mention word, or else the longest review. */\nfunction featureOrder(reviews: PreparedBusinessProfileReviews): BusinessProfileReview[] {\n const named = reviews.mention.length > 0 ? reviews.reviews.filter((review) => businessProfileReviewMentions(review, reviews.mention)) : []\n const pool = named.length > 0 ? named : reviews.reviews\n const lead = pool.reduce<BusinessProfileReview | undefined>((longest, review) => (!longest || review.text.length > longest.text.length ? review : longest), undefined)\n return lead ? [lead, ...reviews.reviews.filter((review) => review !== lead)] : []\n}\n\nexport interface ReviewsBandRootProps extends ReviewsBandSkin {\n readonly reviews: PreparedBusinessProfileReviews | null | undefined\n /** The id of the band's H2 (default `bl-reviews-heading`). */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n /** Signature art, rendered `aria-hidden`. */\n readonly decoration?: ReactNode\n readonly children: ReactNode\n}\n\n/**\n * The section: `data-bl-section=\"reviews\"` with `data-bl-source=\"feed\"` (the ready gate's mark for feed numbers),\n * the empty rule, and the locked disclosure line after the children.\n */\nexport function ReviewsBandRoot({\n reviews,\n headingId = REVIEWS_BAND_HEADING_ID,\n tone,\n decoration,\n variant = 'band',\n layout,\n classes,\n children,\n}: ReviewsBandRootProps) {\n const shown = shownVariant(reviews, variant)\n if (!shown || !reviews) return null\n return (\n <Section\n id=\"reviews\"\n labelledBy={headingId}\n tone={tone}\n variant={shown}\n classes={{ root: classes?.root, inner: classes?.inner }}\n decoration={decoration}\n hooks={{ 'data-bl-source': 'feed', 'data-columns': String(layout?.columns ?? 3), 'data-align': layout?.align ?? 'start' }}\n >\n {children}\n {shown === 'summary' ? null : (\n <p {...slot(classes, 'disclosure')}>\n {reviews.policy === 'five-star' ? 'Selected five-star Google reviews.' : 'Recent Google reviews.'}{' '}\n <a {...slot(classes, 'profileLink')} href={reviews.profileUrl} target=\"_blank\" rel=\"noopener noreferrer\">\n Read all reviews on Google\n </a>\n </p>\n )}\n </Section>\n )\n}\n\nexport interface ReviewsBandHeadingProps {\n /** The H2's id; the same `headingId` the root has (default `bl-reviews-heading`). */\n readonly id?: string\n /** The H2 text the site owns. */\n readonly children: string\n readonly eyebrow?: string\n readonly intro?: string\n readonly layout?: Pick<ReviewsBandLayout, 'align'>\n readonly classes?: Classes\n}\n\n/** The heading block: an optional eyebrow, the H2 and an optional intro. */\nexport function ReviewsBandHeading({ id = REVIEWS_BAND_HEADING_ID, children, eyebrow, intro, layout, classes }: ReviewsBandHeadingProps) {\n return (\n <SectionHeading\n id={id}\n title={children}\n eyebrow={eyebrow}\n lead={intro}\n layout={{ align: layout?.align ?? 'start' }}\n classes={{ head: classes?.head, eyebrow: classes?.eyebrow, heading: classes?.heading, lead: classes?.lead }}\n />\n )\n}\n\nexport interface ReviewsBandSummaryProps {\n readonly reviews: PreparedBusinessProfileReviews | null | undefined\n readonly classes?: Classes\n}\n\n/** Google's own average and total with the profile link; nothing when the feed has no rating. */\nexport function ReviewsBandSummary({ reviews, classes }: ReviewsBandSummaryProps) {\n if (!hasGoogleRating(reviews)) return null\n return (\n <div {...slot(classes, 'summary')}>\n <RatingChip reviews={reviews} classes={{ rating: classes?.rating, source: classes?.source }} />\n </div>\n )\n}\n\n/** The review list; nothing without items. */\nexport function ReviewsBandList({ classes, children }: { readonly classes?: Classes; readonly children: ReactNode }) {\n if (Children.toArray(children).length === 0) return null\n return <ul {...slot(classes, 'list')}>{children}</ul>\n}\n\nexport interface ReviewsBandItemProps {\n readonly review: BusinessProfileReview\n /** The `feature` variant's wide lead card. */\n readonly lead?: boolean\n readonly layout?: Pick<ReviewsBandLayout, 'avatars' | 'excerpt'>\n readonly classes?: Classes\n}\n\n/** One review, word for word: its stars, the quote and the reviewer with the month. */\nexport function ReviewsBandItem({ review, lead = false, layout, classes }: ReviewsBandItemProps) {\n const parts = splitReviewText(review.text, layout?.excerpt ?? DEFAULT_EXCERPT)\n const date = new Date(review.createTime)\n return (\n <li {...repeatedSlot('item')} data-lead={lead ? 'true' : undefined}>\n <figure>\n <span {...repeatedSlot('stars')}>\n <RatingStars rating={review.rating} label={`Rated ${review.rating} out of 5`} />\n </span>\n <blockquote {...repeatedSlot('quote')}>\n {parts ? (\n <>\n <p>{parts[0]}</p>\n <details {...repeatedSlot('more')}>\n <summary>Read more</summary>\n <p>{parts[1]}</p>\n </details>\n </>\n ) : (\n <p>{review.text}</p>\n )}\n </blockquote>\n <figcaption {...repeatedSlot('author')}>\n {layout?.avatars && review.authorPhotoUrl ? (\n <img\n {...repeatedSlot('avatar')}\n src={review.authorPhotoUrl}\n alt=\"\"\n width={48}\n height={48}\n loading=\"lazy\"\n decoding=\"async\"\n referrerPolicy=\"no-referrer\"\n />\n ) : null}\n <span {...repeatedSlot('name')}>{review.authorName}</span>{' '}\n <time {...repeatedSlot('date')} dateTime={DAY.format(date)}>\n {MONTH.format(date)}\n </time>\n </figcaption>\n </figure>\n </li>\n )\n}\n\nexport interface ReviewsBandProps extends ReviewsBandSkin {\n /** From `prepareBusinessProfileReviews`; null renders nothing at all. */\n readonly reviews: PreparedBusinessProfileReviews | null | undefined\n /** The H2 text the site owns. */\n readonly heading: string\n readonly eyebrow?: string\n readonly intro?: string\n /** The id of the band's H2 (default `bl-reviews-heading`). */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n /** Signature art, rendered `aria-hidden`. */\n readonly decoration?: ReactNode\n}\n\nfunction ReviewsBandSection({ reviews, heading, eyebrow, intro, headingId = REVIEWS_BAND_HEADING_ID, tone, decoration, variant = 'band', layout, classes }: ReviewsBandProps) {\n const shown = shownVariant(reviews, variant)\n if (!shown || !reviews) return null\n const items = shown === 'summary' ? [] : shown === 'feature' ? featureOrder(reviews) : reviews.reviews\n return (\n <ReviewsBandRoot reviews={reviews} headingId={headingId} tone={tone} decoration={decoration} variant={shown} layout={layout} classes={classes}>\n <div {...slot(classes, 'top')}>\n <ReviewsBandHeading id={headingId} eyebrow={eyebrow} intro={intro} layout={layout} classes={classes}>\n {heading}\n </ReviewsBandHeading>\n <ReviewsBandSummary reviews={reviews} classes={classes} />\n </div>\n <ReviewsBandList classes={classes}>\n {items.map((review, index) => (\n <ReviewsBandItem key={review.id} review={review} lead={shown === 'feature' && index === 0} layout={layout} classes={classes} />\n ))}\n </ReviewsBandList>\n </ReviewsBandRoot>\n )\n}\n\n/** The reviews band, with its parts for sites that arrange their own layout. */\nexport const ReviewsBand = Object.assign(ReviewsBandSection, {\n Root: ReviewsBandRoot,\n Heading: ReviewsBandHeading,\n Summary: ReviewsBandSummary,\n List: ReviewsBandList,\n Item: ReviewsBandItem,\n})\n"]}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { BrandlabMediaEntry } from '@brandlab-digital/site-core/media';
|
|
3
|
+
import { type LinkPrefetch } from '../prefetch.js';
|
|
4
|
+
import { type MediaLayout } from '../primitives/media.js';
|
|
5
|
+
import { type SectionHeadingSlot, type SectionSlot, type SectionTone } from '../primitives/layout.js';
|
|
6
|
+
import type { RepeatedSlotOf } from '../repeated-slots.js';
|
|
7
|
+
import { type SkinEntry } from '../skin.js';
|
|
8
|
+
import type { UiLink } from '../types.js';
|
|
9
|
+
export declare const SERVICES_GRID_VARIANTS: readonly ["rows", "cards", "compact"];
|
|
10
|
+
export type ServicesGridVariant = (typeof SERVICES_GRID_VARIANTS)[number];
|
|
11
|
+
export type ServiceCardSlot = 'item' | 'media' | 'body' | 'title' | 'summary' | 'points' | 'point' | 'action' | 'link';
|
|
12
|
+
export type ServicesGridSlot = SectionSlot | SectionHeadingSlot | 'list' | 'more' | ServiceCardSlot;
|
|
13
|
+
/** Rendered once per service: styled from the skin stylesheet, never by class. */
|
|
14
|
+
export type ServicesGridRepeatedSlot = RepeatedSlotOf<'servicesGrid'>;
|
|
15
|
+
export interface ServicesGridLayout {
|
|
16
|
+
/** `cards`: columns on wide screens. Defaults to 3. */
|
|
17
|
+
readonly columns?: 2 | 3 | 4;
|
|
18
|
+
/** `rows` and `cards`: the photo's crop. Defaults to 4:3. */
|
|
19
|
+
readonly ratio?: NonNullable<MediaLayout['ratio']>;
|
|
20
|
+
/** `rows`: the photo's side on wide screens. Defaults to `alternate` (first photo at the start). */
|
|
21
|
+
readonly mediaSide?: 'alternate' | 'start' | 'end';
|
|
22
|
+
}
|
|
23
|
+
export type ServicesGridSkin = SkinEntry<ServicesGridSlot, ServicesGridVariant, ServicesGridLayout, ServicesGridRepeatedSlot>;
|
|
24
|
+
/** The most services `rows` and `cards` show (K11; the in-content intent-prefetch limit). */
|
|
25
|
+
export declare const SERVICES_GRID_CARD_LIMIT: 8;
|
|
26
|
+
/** One service, in money order. */
|
|
27
|
+
export interface ServiceCardData {
|
|
28
|
+
/** A stable slug, rendered as `data-item`. */
|
|
29
|
+
readonly id: string;
|
|
30
|
+
/** The service's name, the card's H3 (`rows`, `cards`) or the link text (`compact`). */
|
|
31
|
+
readonly title: string;
|
|
32
|
+
/**
|
|
33
|
+
* Where the card goes: the service page, its service-in-suburb page or its anchored section on this page
|
|
34
|
+
* (`#tree-pruning`). From the site's data, never built here.
|
|
35
|
+
*/
|
|
36
|
+
readonly href: string;
|
|
37
|
+
/** One line on what the service is. `rows` and `cards`; never a price. */
|
|
38
|
+
readonly summary?: ReactNode;
|
|
39
|
+
/** `rows`: a few scope points under the summary (verified service scope, never prices). */
|
|
40
|
+
readonly points?: readonly string[];
|
|
41
|
+
/** The service's own photo from the media manifest. `rows` and `cards`; no image on two cards. */
|
|
42
|
+
readonly image?: BrandlabMediaEntry;
|
|
43
|
+
/**
|
|
44
|
+
* `rows`: the link under the text, for example "Tree removal in Castle Hill". Left out, the title is the link, as on
|
|
45
|
+
* `cards`. Name the service: never "Learn more".
|
|
46
|
+
*/
|
|
47
|
+
readonly linkLabel?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface ServiceCardProps {
|
|
50
|
+
readonly service: ServiceCardData;
|
|
51
|
+
readonly variant: ServicesGridVariant;
|
|
52
|
+
/** `rows` and `cards`: the photo's `sizes`. */
|
|
53
|
+
readonly imageSizes?: string;
|
|
54
|
+
readonly ratio?: ServicesGridLayout['ratio'];
|
|
55
|
+
/** `rows` and `cards` default to `intent`; `compact` is always off. */
|
|
56
|
+
readonly prefetch?: LinkPrefetch;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* One service as a list item of `ServicesGrid` (also the building block for `RelatedServices`). Its parts are the
|
|
60
|
+
* grid's repeated slots: the root sets no classes on them.
|
|
61
|
+
*/
|
|
62
|
+
export declare function ServiceCard({ service, variant, imageSizes, ratio, prefetch }: ServiceCardProps): import("react/jsx-runtime").JSX.Element;
|
|
63
|
+
/** The photo `sizes` for each variant: half the container in rows, one column of the grid in cards. */
|
|
64
|
+
export declare function defaultImageSizes(variant: Exclude<ServicesGridVariant, 'compact'>, columns: 2 | 3 | 4): string;
|
|
65
|
+
export interface ServicesGridProps extends ServicesGridSkin {
|
|
66
|
+
/** The H2, for example "Our tree services" or "Tree services in Castle Hill". */
|
|
67
|
+
readonly title: ReactNode;
|
|
68
|
+
readonly eyebrow?: ReactNode;
|
|
69
|
+
readonly lead?: ReactNode;
|
|
70
|
+
/** Every service the page offers, in money order. */
|
|
71
|
+
readonly services: readonly ServiceCardData[];
|
|
72
|
+
/** A link after the list, for example the services hub or "Find your area". Never prefetched. */
|
|
73
|
+
readonly more?: UiLink;
|
|
74
|
+
/** `rows` and `cards`: override the photo `sizes` when the skin's grid differs from the defaults. */
|
|
75
|
+
readonly imageSizes?: string;
|
|
76
|
+
/** Defaults to `services-heading`. */
|
|
77
|
+
readonly headingId?: string;
|
|
78
|
+
/** Set by `SectionSequence`. */
|
|
79
|
+
readonly tone?: SectionTone;
|
|
80
|
+
}
|
|
81
|
+
export declare function ServicesGrid({ title, eyebrow, lead, services, more, imageSizes, headingId, tone, variant, layout, classes, }: ServicesGridProps): import("react/jsx-runtime").JSX.Element | null;
|
|
82
|
+
//# sourceMappingURL=services-grid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"services-grid.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/services-grid.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* `ServicesGrid` (S4, `services`, 3.14.0) and `ServiceCard`: route the visitor to the right service. Server components.
|
|
4
|
+
*
|
|
5
|
+
* Variants:
|
|
6
|
+
* - `rows` (the fleet reference, AA's `ServiceRows`): one full-width row per service, the photo beside the text and
|
|
7
|
+
* alternating sides on wide screens, with an optional line of scope points and the service's own link;
|
|
8
|
+
* - `cards`: a grid of cards, each headed by its linked title;
|
|
9
|
+
* - `compact` (suburb and region pages): a lean list of every service offered there, each a single link to its
|
|
10
|
+
* service-in-suburb page or to its anchored section on the same page (`#tree-pruning`). No photos and no summaries.
|
|
11
|
+
*
|
|
12
|
+
* Locked rules (S4): one link per card; no prices and no before-and-afters in cards; no image on two cards; at most 8
|
|
13
|
+
* `rows` or `cards` (K11's card limit, and the most in-content links that prefetch on intent), so a page that offers
|
|
14
|
+
* more services lists them `compact` or links its services hub. Card links prefetch on intent (`LINK_PREFETCH_POLICY`
|
|
15
|
+
* `contentCard`); compact links, the hub link and same-page anchors never prefetch.
|
|
16
|
+
*/
|
|
17
|
+
import Link from 'next/link';
|
|
18
|
+
import { LINK_PREFETCH_LIMITS, LINK_PREFETCH_POLICY, linkPrefetchProps } from '../prefetch.js';
|
|
19
|
+
import { Media } from '../primitives/media.js';
|
|
20
|
+
import { Section, SectionHeading } from '../primitives/layout.js';
|
|
21
|
+
import { repeatedSlot, slot } from '../skin.js';
|
|
22
|
+
export const SERVICES_GRID_VARIANTS = ['rows', 'cards', 'compact'];
|
|
23
|
+
/** The most services `rows` and `cards` show (K11; the in-content intent-prefetch limit). */
|
|
24
|
+
export const SERVICES_GRID_CARD_LIMIT = LINK_PREFETCH_LIMITS.contentCard;
|
|
25
|
+
function ServiceLink({ href, prefetch, children }) {
|
|
26
|
+
// A same-page section needs no route prefetch: a plain anchor.
|
|
27
|
+
if (href.startsWith('#')) {
|
|
28
|
+
return (_jsx("a", { href: href, ...repeatedSlot('link'), children: children }));
|
|
29
|
+
}
|
|
30
|
+
return (_jsx(Link, { href: href, ...linkPrefetchProps(prefetch, href), ...repeatedSlot('link'), children: children }));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* One service as a list item of `ServicesGrid` (also the building block for `RelatedServices`). Its parts are the
|
|
34
|
+
* grid's repeated slots: the root sets no classes on them.
|
|
35
|
+
*/
|
|
36
|
+
export function ServiceCard({ service, variant, imageSizes, ratio = '4:3', prefetch }) {
|
|
37
|
+
if (variant === 'compact') {
|
|
38
|
+
return (_jsx("li", { ...repeatedSlot('item'), "data-item": service.id, children: _jsx(ServiceLink, { href: service.href, prefetch: LINK_PREFETCH_POLICY.otherServicesInSuburb, children: service.title }) }));
|
|
39
|
+
}
|
|
40
|
+
const linkPrefetch = prefetch ?? LINK_PREFETCH_POLICY.contentCard;
|
|
41
|
+
const separateLink = variant === 'rows' && service.linkLabel !== undefined && service.linkLabel.trim() !== '';
|
|
42
|
+
return (_jsxs("li", { ...repeatedSlot('item'), "data-item": service.id, children: [_jsxs("div", { ...repeatedSlot('body'), children: [_jsx("h3", { ...repeatedSlot('title'), children: separateLink ? (service.title) : (_jsx(ServiceLink, { href: service.href, prefetch: linkPrefetch, children: service.title })) }), service.summary ? _jsx("p", { ...repeatedSlot('summary'), children: service.summary }) : null, variant === 'rows' && service.points && service.points.length > 0 ? (_jsx("ul", { role: "list", ...repeatedSlot('points'), children: service.points.map((point) => (_jsx("li", { ...repeatedSlot('point'), children: point }, point))) })) : null, separateLink ? (_jsx("p", { ...repeatedSlot('action'), children: _jsx(ServiceLink, { href: service.href, prefetch: linkPrefetch, children: service.linkLabel }) })) : null] }), service.image ? (_jsx("div", { ...repeatedSlot('media'), children: _jsx(Media, { media: service.image, sizes: imageSizes ?? defaultImageSizes(variant, 3), layout: { ratio } }) })) : null] }));
|
|
43
|
+
}
|
|
44
|
+
/** The photo `sizes` for each variant: half the container in rows, one column of the grid in cards. */
|
|
45
|
+
export function defaultImageSizes(variant, columns) {
|
|
46
|
+
if (variant === 'rows')
|
|
47
|
+
return '(min-width: 1024px) 50vw, 100vw';
|
|
48
|
+
return `(min-width: 1024px) ${Math.round(100 / columns)}vw, (min-width: 640px) 50vw, 100vw`;
|
|
49
|
+
}
|
|
50
|
+
function assertServices(services, variant) {
|
|
51
|
+
const ids = new Set();
|
|
52
|
+
const hrefs = new Set();
|
|
53
|
+
const images = new Map();
|
|
54
|
+
for (const service of services) {
|
|
55
|
+
if (!service.id || ids.has(service.id))
|
|
56
|
+
throw new Error(`ServicesGrid: every service needs its own id ("${service.id}" is empty or repeated).`);
|
|
57
|
+
ids.add(service.id);
|
|
58
|
+
if (!service.title?.trim())
|
|
59
|
+
throw new Error(`ServicesGrid: the service "${service.id}" has no title.`);
|
|
60
|
+
if (!service.href?.trim())
|
|
61
|
+
throw new Error(`ServicesGrid: the service "${service.id}" has no link.`);
|
|
62
|
+
if (hrefs.has(service.href))
|
|
63
|
+
throw new Error(`ServicesGrid: two services link to ${service.href}; each card has its own page or section.`);
|
|
64
|
+
hrefs.add(service.href);
|
|
65
|
+
if (variant !== 'compact' && service.image) {
|
|
66
|
+
const other = images.get(service.image.src);
|
|
67
|
+
if (other)
|
|
68
|
+
throw new Error(`ServicesGrid: "${service.id}" and "${other}" use the same image (${service.image.src}); no image is on two cards.`);
|
|
69
|
+
images.set(service.image.src, service.id);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (variant !== 'compact' && services.length > SERVICES_GRID_CARD_LIMIT) {
|
|
73
|
+
throw new Error(`ServicesGrid: ${services.length} services in ${variant}; ${variant} shows at most ${SERVICES_GRID_CARD_LIMIT} (K11). List every service with the compact variant, or show the money services and link the services hub.`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function ServicesGrid({ title, eyebrow, lead, services, more, imageSizes, headingId = 'services-heading', tone, variant = 'rows', layout, classes, }) {
|
|
77
|
+
if (services.length === 0)
|
|
78
|
+
return null;
|
|
79
|
+
assertServices(services, variant);
|
|
80
|
+
const columns = layout?.columns ?? 3;
|
|
81
|
+
const ratio = layout?.ratio ?? '4:3';
|
|
82
|
+
const hooks = variant === 'compact'
|
|
83
|
+
? undefined
|
|
84
|
+
: variant === 'cards'
|
|
85
|
+
? { 'data-columns': String(columns), 'data-ratio': ratio }
|
|
86
|
+
: { 'data-media-side': layout?.mediaSide ?? 'alternate', 'data-ratio': ratio };
|
|
87
|
+
const sizes = variant === 'compact' ? undefined : (imageSizes ?? defaultImageSizes(variant, columns));
|
|
88
|
+
return (_jsxs(Section, { id: "services", labelledBy: headingId, tone: tone, variant: variant, classes: classes, hooks: hooks, children: [_jsx(SectionHeading, { id: headingId, title: title, eyebrow: eyebrow, lead: lead, classes: classes }), _jsx("ul", { role: "list", ...slot(classes, 'list'), children: services.map((service) => (_jsx(ServiceCard, { service: service, variant: variant, imageSizes: sizes, ratio: ratio }, service.id))) }), more ? (_jsx("p", { ...slot(classes, 'more'), children: more.href.startsWith('#') ? (_jsx("a", { href: more.href, children: more.label })) : (_jsx(Link, { href: more.href, prefetch: false, children: more.label })) })) : null] }));
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=services-grid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"services-grid.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/services-grid.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `ServicesGrid` (S4, `services`, 3.14.0) and `ServiceCard`: route the visitor to the right service. Server components.\n *\n * Variants:\n * - `rows` (the fleet reference, AA's `ServiceRows`): one full-width row per service, the photo beside the text and\n * alternating sides on wide screens, with an optional line of scope points and the service's own link;\n * - `cards`: a grid of cards, each headed by its linked title;\n * - `compact` (suburb and region pages): a lean list of every service offered there, each a single link to its\n * service-in-suburb page or to its anchored section on the same page (`#tree-pruning`). No photos and no summaries.\n *\n * Locked rules (S4): one link per card; no prices and no before-and-afters in cards; no image on two cards; at most 8\n * `rows` or `cards` (K11's card limit, and the most in-content links that prefetch on intent), so a page that offers\n * more services lists them `compact` or links its services hub. Card links prefetch on intent (`LINK_PREFETCH_POLICY`\n * `contentCard`); compact links, the hub link and same-page anchors never prefetch.\n */\nimport Link from 'next/link'\nimport type { ReactNode } from 'react'\nimport type { BrandlabMediaEntry } from '@brandlab-digital/site-core/media'\nimport { LINK_PREFETCH_LIMITS, LINK_PREFETCH_POLICY, linkPrefetchProps, type LinkPrefetch } from '../prefetch.js'\nimport { Media, type MediaLayout } from '../primitives/media.js'\nimport { Section, SectionHeading, type SectionHeadingSlot, type SectionSlot, type SectionTone } from '../primitives/layout.js'\nimport type { RepeatedSlotOf } from '../repeated-slots.js'\nimport { repeatedSlot, slot, type SkinEntry } from '../skin.js'\nimport type { UiLink } from '../types.js'\n\nexport const SERVICES_GRID_VARIANTS = ['rows', 'cards', 'compact'] as const\nexport type ServicesGridVariant = (typeof SERVICES_GRID_VARIANTS)[number]\nexport type ServiceCardSlot = 'item' | 'media' | 'body' | 'title' | 'summary' | 'points' | 'point' | 'action' | 'link'\nexport type ServicesGridSlot = SectionSlot | SectionHeadingSlot | 'list' | 'more' | ServiceCardSlot\n/** Rendered once per service: styled from the skin stylesheet, never by class. */\nexport type ServicesGridRepeatedSlot = RepeatedSlotOf<'servicesGrid'>\nexport interface ServicesGridLayout {\n /** `cards`: columns on wide screens. Defaults to 3. */\n readonly columns?: 2 | 3 | 4\n /** `rows` and `cards`: the photo's crop. Defaults to 4:3. */\n readonly ratio?: NonNullable<MediaLayout['ratio']>\n /** `rows`: the photo's side on wide screens. Defaults to `alternate` (first photo at the start). */\n readonly mediaSide?: 'alternate' | 'start' | 'end'\n}\nexport type ServicesGridSkin = SkinEntry<ServicesGridSlot, ServicesGridVariant, ServicesGridLayout, ServicesGridRepeatedSlot>\n\n/** The most services `rows` and `cards` show (K11; the in-content intent-prefetch limit). */\nexport const SERVICES_GRID_CARD_LIMIT = LINK_PREFETCH_LIMITS.contentCard\n\n/** One service, in money order. */\nexport interface ServiceCardData {\n /** A stable slug, rendered as `data-item`. */\n readonly id: string\n /** The service's name, the card's H3 (`rows`, `cards`) or the link text (`compact`). */\n readonly title: string\n /**\n * Where the card goes: the service page, its service-in-suburb page or its anchored section on this page\n * (`#tree-pruning`). From the site's data, never built here.\n */\n readonly href: string\n /** One line on what the service is. `rows` and `cards`; never a price. */\n readonly summary?: ReactNode\n /** `rows`: a few scope points under the summary (verified service scope, never prices). */\n readonly points?: readonly string[]\n /** The service's own photo from the media manifest. `rows` and `cards`; no image on two cards. */\n readonly image?: BrandlabMediaEntry\n /**\n * `rows`: the link under the text, for example \"Tree removal in Castle Hill\". Left out, the title is the link, as on\n * `cards`. Name the service: never \"Learn more\".\n */\n readonly linkLabel?: string\n}\n\nexport interface ServiceCardProps {\n readonly service: ServiceCardData\n readonly variant: ServicesGridVariant\n /** `rows` and `cards`: the photo's `sizes`. */\n readonly imageSizes?: string\n readonly ratio?: ServicesGridLayout['ratio']\n /** `rows` and `cards` default to `intent`; `compact` is always off. */\n readonly prefetch?: LinkPrefetch\n}\n\nfunction ServiceLink({ href, prefetch, children }: { href: string; prefetch: LinkPrefetch; children: ReactNode }) {\n // A same-page section needs no route prefetch: a plain anchor.\n if (href.startsWith('#')) {\n return (\n <a href={href} {...repeatedSlot('link')}>\n {children}\n </a>\n )\n }\n return (\n <Link href={href} {...linkPrefetchProps(prefetch, href)} {...repeatedSlot('link')}>\n {children}\n </Link>\n )\n}\n\n/**\n * One service as a list item of `ServicesGrid` (also the building block for `RelatedServices`). Its parts are the\n * grid's repeated slots: the root sets no classes on them.\n */\nexport function ServiceCard({ service, variant, imageSizes, ratio = '4:3', prefetch }: ServiceCardProps) {\n if (variant === 'compact') {\n return (\n <li {...repeatedSlot('item')} data-item={service.id}>\n <ServiceLink href={service.href} prefetch={LINK_PREFETCH_POLICY.otherServicesInSuburb}>\n {service.title}\n </ServiceLink>\n </li>\n )\n }\n const linkPrefetch = prefetch ?? LINK_PREFETCH_POLICY.contentCard\n const separateLink = variant === 'rows' && service.linkLabel !== undefined && service.linkLabel.trim() !== ''\n return (\n <li {...repeatedSlot('item')} data-item={service.id}>\n <div {...repeatedSlot('body')}>\n <h3 {...repeatedSlot('title')}>\n {separateLink ? (\n service.title\n ) : (\n <ServiceLink href={service.href} prefetch={linkPrefetch}>\n {service.title}\n </ServiceLink>\n )}\n </h3>\n {service.summary ? <p {...repeatedSlot('summary')}>{service.summary}</p> : null}\n {variant === 'rows' && service.points && service.points.length > 0 ? (\n <ul role=\"list\" {...repeatedSlot('points')}>\n {service.points.map((point) => (\n <li key={point} {...repeatedSlot('point')}>\n {point}\n </li>\n ))}\n </ul>\n ) : null}\n {separateLink ? (\n <p {...repeatedSlot('action')}>\n <ServiceLink href={service.href} prefetch={linkPrefetch}>\n {service.linkLabel}\n </ServiceLink>\n </p>\n ) : null}\n </div>\n {service.image ? (\n <div {...repeatedSlot('media')}>\n <Media media={service.image} sizes={imageSizes ?? defaultImageSizes(variant, 3)} layout={{ ratio }} />\n </div>\n ) : null}\n </li>\n )\n}\n\n/** The photo `sizes` for each variant: half the container in rows, one column of the grid in cards. */\nexport function defaultImageSizes(variant: Exclude<ServicesGridVariant, 'compact'>, columns: 2 | 3 | 4): string {\n if (variant === 'rows') return '(min-width: 1024px) 50vw, 100vw'\n return `(min-width: 1024px) ${Math.round(100 / columns)}vw, (min-width: 640px) 50vw, 100vw`\n}\n\nexport interface ServicesGridProps extends ServicesGridSkin {\n /** The H2, for example \"Our tree services\" or \"Tree services in Castle Hill\". */\n readonly title: ReactNode\n readonly eyebrow?: ReactNode\n readonly lead?: ReactNode\n /** Every service the page offers, in money order. */\n readonly services: readonly ServiceCardData[]\n /** A link after the list, for example the services hub or \"Find your area\". Never prefetched. */\n readonly more?: UiLink\n /** `rows` and `cards`: override the photo `sizes` when the skin's grid differs from the defaults. */\n readonly imageSizes?: string\n /** Defaults to `services-heading`. */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n}\n\nfunction assertServices(services: readonly ServiceCardData[], variant: ServicesGridVariant): void {\n const ids = new Set<string>()\n const hrefs = new Set<string>()\n const images = new Map<string, string>()\n for (const service of services) {\n if (!service.id || ids.has(service.id)) throw new Error(`ServicesGrid: every service needs its own id (\"${service.id}\" is empty or repeated).`)\n ids.add(service.id)\n if (!service.title?.trim()) throw new Error(`ServicesGrid: the service \"${service.id}\" has no title.`)\n if (!service.href?.trim()) throw new Error(`ServicesGrid: the service \"${service.id}\" has no link.`)\n if (hrefs.has(service.href)) throw new Error(`ServicesGrid: two services link to ${service.href}; each card has its own page or section.`)\n hrefs.add(service.href)\n if (variant !== 'compact' && service.image) {\n const other = images.get(service.image.src)\n if (other) throw new Error(`ServicesGrid: \"${service.id}\" and \"${other}\" use the same image (${service.image.src}); no image is on two cards.`)\n images.set(service.image.src, service.id)\n }\n }\n if (variant !== 'compact' && services.length > SERVICES_GRID_CARD_LIMIT) {\n throw new Error(\n `ServicesGrid: ${services.length} services in ${variant}; ${variant} shows at most ${SERVICES_GRID_CARD_LIMIT} (K11). List every service with the compact variant, or show the money services and link the services hub.`,\n )\n }\n}\n\nexport function ServicesGrid({\n title,\n eyebrow,\n lead,\n services,\n more,\n imageSizes,\n headingId = 'services-heading',\n tone,\n variant = 'rows',\n layout,\n classes,\n}: ServicesGridProps) {\n if (services.length === 0) return null\n assertServices(services, variant)\n const columns = layout?.columns ?? 3\n const ratio = layout?.ratio ?? '4:3'\n const hooks =\n variant === 'compact'\n ? undefined\n : variant === 'cards'\n ? { 'data-columns': String(columns), 'data-ratio': ratio }\n : { 'data-media-side': layout?.mediaSide ?? 'alternate', 'data-ratio': ratio }\n const sizes = variant === 'compact' ? undefined : (imageSizes ?? defaultImageSizes(variant, columns))\n return (\n <Section id=\"services\" labelledBy={headingId} tone={tone} variant={variant} classes={classes} hooks={hooks}>\n <SectionHeading id={headingId} title={title} eyebrow={eyebrow} lead={lead} classes={classes} />\n <ul role=\"list\" {...slot(classes, 'list')}>\n {services.map((service) => (\n <ServiceCard key={service.id} service={service} variant={variant} imageSizes={sizes} ratio={ratio} />\n ))}\n </ul>\n {more ? (\n <p {...slot(classes, 'more')}>\n {more.href.startsWith('#') ? (\n <a href={more.href}>{more.label}</a>\n ) : (\n <Link href={more.href} prefetch={false}>\n {more.label}\n </Link>\n )}\n </p>\n ) : null}\n </Section>\n )\n}\n"]}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
+
import type { RepeatedSlotOf } from '../repeated-slots.js';
|
|
2
3
|
import { type SkinEntry } from '../skin.js';
|
|
3
4
|
import type { UiLink, UiPhone, UiSocial } from '../types.js';
|
|
4
5
|
export declare const SITE_FOOTER_VARIANTS: readonly ["columns", "photo"];
|
|
5
6
|
export type SiteFooterVariant = (typeof SITE_FOOTER_VARIANTS)[number];
|
|
6
7
|
export type SiteFooterSlot = 'root' | 'panel' | 'media' | 'inner' | 'brand' | 'logo' | 'description' | 'licences' | 'licenceLink' | 'social' | 'socialLink' | 'contact' | 'phone' | 'phoneLabel' | 'phoneNumber' | 'email' | 'address' | 'columns' | 'column' | 'heading' | 'toggle' | 'list' | 'link' | 'bottom' | 'bottomInner' | 'legal' | 'policies' | 'policyLink';
|
|
7
|
-
|
|
8
|
+
/** Rendered once per licence, profile, column, link or policy: styled from the skin stylesheet, never by class (3.14.0). */
|
|
9
|
+
export type SiteFooterRepeatedSlot = RepeatedSlotOf<'siteFooter'>;
|
|
10
|
+
export type SiteFooterSkin = SkinEntry<SiteFooterSlot, SiteFooterVariant, Record<never, never>, SiteFooterRepeatedSlot>;
|
|
8
11
|
/** One licence line: the text as the register shows it, and the register link when there is one. */
|
|
9
12
|
export interface SiteFooterLicence {
|
|
10
13
|
readonly text: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"site-footer.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/site-footer.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,OAAO,
|
|
1
|
+
{"version":3,"file":"site-footer.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/site-footer.tsx"],"names":[],"mappings":"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"}
|
|
@@ -18,27 +18,27 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
18
18
|
import Link from 'next/link';
|
|
19
19
|
import { Icon, SocialIcon } from '../icons.js';
|
|
20
20
|
import { PhoneLink } from '../primitives/links.js';
|
|
21
|
-
import { slot } from '../skin.js';
|
|
21
|
+
import { repeatedSlot, slot } from '../skin.js';
|
|
22
22
|
import { SiteFooterColumns } from './site-footer-columns.js';
|
|
23
23
|
export const SITE_FOOTER_VARIANTS = ['columns', 'photo'];
|
|
24
24
|
const COLUMN_KEYS = ['services', 'areas', 'company'];
|
|
25
25
|
function isExternal(href) {
|
|
26
26
|
return /^https?:\/\//i.test(href);
|
|
27
27
|
}
|
|
28
|
-
function FooterLink({ link,
|
|
29
|
-
return isExternal(link.href) ? (_jsx("a", { href: link.href, ...
|
|
28
|
+
function FooterLink({ link, slotName }) {
|
|
29
|
+
return isExternal(link.href) ? (_jsx("a", { href: link.href, ...repeatedSlot(slotName), target: "_blank", rel: "noopener noreferrer", children: link.label })) : (_jsx(Link, { href: link.href, prefetch: false, ...repeatedSlot(slotName), children: link.label }));
|
|
30
30
|
}
|
|
31
31
|
function currentYear() {
|
|
32
32
|
return Number(new Intl.DateTimeFormat('en-AU', { year: 'numeric', timeZone: 'Australia/Sydney' }).format(new Date()));
|
|
33
33
|
}
|
|
34
34
|
export function SiteFooter({ brandName, homeHref = '/', logo, description, licences = [], socials = [], phone, phoneLabel, email, address, columns, legal, media, decoration, idPrefix = 'bl-footer', variant = 'columns', classes, }) {
|
|
35
35
|
const policies = [legal.privacy, legal.terms].filter((link) => !!link);
|
|
36
|
-
return (_jsxs("footer", { "data-bl": "footer", "data-variant": variant, className: classes?.root, children: [decoration ? (_jsx("div", { "data-slot": "decoration", "aria-hidden": "true", children: decoration })) : null, _jsxs("div", { ...slot(classes, 'panel'), children: [variant === 'photo' && media ? (_jsx("div", { ...slot(classes, 'media'), "aria-hidden": "true", children: media })) : null, _jsxs("div", { ...slot(classes, 'inner'), children: [_jsxs("div", { ...slot(classes, 'brand'), children: [_jsx(Link, { href: homeHref, prefetch: false, ...slot(classes, 'logo'), "aria-label": brandName, children: logo }), _jsx("p", { ...slot(classes, 'description'), children: description }), licences.length > 0 ? (_jsx("ul", { ...slot(classes, 'licences'), children: licences.map((licence) => (_jsx("li", { children: licence.href ? (_jsx("a", { ...
|
|
36
|
+
return (_jsxs("footer", { "data-bl": "footer", "data-variant": variant, className: classes?.root, children: [decoration ? (_jsx("div", { "data-slot": "decoration", "aria-hidden": "true", children: decoration })) : null, _jsxs("div", { ...slot(classes, 'panel'), children: [variant === 'photo' && media ? (_jsx("div", { ...slot(classes, 'media'), "aria-hidden": "true", children: media })) : null, _jsxs("div", { ...slot(classes, 'inner'), children: [_jsxs("div", { ...slot(classes, 'brand'), children: [_jsx(Link, { href: homeHref, prefetch: false, ...slot(classes, 'logo'), "aria-label": brandName, children: logo }), _jsx("p", { ...slot(classes, 'description'), children: description }), licences.length > 0 ? (_jsx("ul", { ...slot(classes, 'licences'), children: licences.map((licence) => (_jsx("li", { children: licence.href ? (_jsx("a", { ...repeatedSlot('licenceLink'), href: licence.href, target: "_blank", rel: "noopener noreferrer", children: licence.text })) : (licence.text) }, licence.text))) })) : null, socials.length > 0 ? (_jsx("ul", { ...slot(classes, 'social'), children: socials.map((social) => (_jsx("li", { children: _jsx("a", { ...repeatedSlot('socialLink'), href: social.href, "aria-label": social.label, target: "_blank", rel: "noopener noreferrer", children: _jsx(SocialIcon, { kind: social.kind }) }) }, social.href))) })) : null, _jsxs("div", { ...slot(classes, 'contact'), children: [_jsx(PhoneLink, { phone: phone, placement: "footer", label: phoneLabel, classes: { root: classes?.phone, label: classes?.phoneLabel, number: classes?.phoneNumber } }), email ? (_jsx("a", { ...slot(classes, 'email'), href: `mailto:${email}`, children: email })) : null, address ? _jsx("p", { ...slot(classes, 'address'), children: address }) : null] })] }), _jsx(SiteFooterColumns, { className: classes?.columns, children: COLUMN_KEYS.map((key) => {
|
|
37
37
|
const column = columns[key];
|
|
38
38
|
if (!column || column.links.length === 0)
|
|
39
39
|
return null;
|
|
40
40
|
const listId = `${idPrefix}-${key}`;
|
|
41
|
-
return (_jsxs("div", { ...
|
|
42
|
-
}) })] })] }), _jsx("div", { ...slot(classes, 'bottom'), children: _jsxs("div", { ...slot(classes, 'bottomInner'), children: [_jsxs("p", { ...slot(classes, 'legal'), children: ["\u00A9 ", legal.year ?? currentYear(), " ", legal.name, legal.abn ? (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", children: " \u00B7 " }), _jsxs("span", { children: ["ABN ", legal.abn] })] })) : null] }), policies.length > 0 ? (_jsx("ul", { ...slot(classes, 'policies'), children: policies.map((link) => (_jsx("li", { children: _jsx(FooterLink, { link: link,
|
|
41
|
+
return (_jsxs("div", { ...repeatedSlot('column'), "data-column": key, children: [_jsxs("h2", { ...repeatedSlot('heading'), children: [_jsx("span", { "data-slot": "headingText", children: column.heading }), _jsxs("button", { type: "button", ...repeatedSlot('toggle'), "aria-expanded": "false", "aria-controls": listId, hidden: true, children: [_jsx("span", { children: column.heading }), _jsx(Icon, { name: "chevron" })] })] }), _jsx("ul", { id: listId, ...repeatedSlot('list'), children: column.links.map((link) => (_jsx("li", { children: _jsx(FooterLink, { link: link, slotName: "link" }) }, link.href))) })] }, key));
|
|
42
|
+
}) })] })] }), _jsx("div", { ...slot(classes, 'bottom'), children: _jsxs("div", { ...slot(classes, 'bottomInner'), children: [_jsxs("p", { ...slot(classes, 'legal'), children: ["\u00A9 ", legal.year ?? currentYear(), " ", legal.name, legal.abn ? (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", children: " \u00B7 " }), _jsxs("span", { children: ["ABN ", legal.abn] })] })) : null] }), policies.length > 0 ? (_jsx("ul", { ...slot(classes, 'policies'), children: policies.map((link) => (_jsx("li", { children: _jsx(FooterLink, { link: link, slotName: "policyLink" }) }, link.href))) })) : null] }) })] }));
|
|
43
43
|
}
|
|
44
44
|
//# sourceMappingURL=site-footer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"site-footer.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/site-footer.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * S27 `SiteFooter`: the v1 §6 footer, structured the same on every site. Server component; one small client island\n * (`SiteFooterColumns`) turns the columns into a phone accordion.\n *\n * Column 1: the logo, a short description, one line per licence (with its register link when given), social and\n * Business Profile icons, then the phone (`phone_link_click` placement `footer`), the email and the address only when\n * it is public. Then Services, Areas and Company, each under a real H2, in that order. A bottom bar: © year name ·\n * ABN · the privacy and terms links. Never a quote button, \"based in\", opening hours, a guides column or a \"back to\n * top\" text link: the props have no place for them.\n *\n * Variants: `columns`; `photo`, with the site's backdrop image for the panel in `media` (decorative, `aria-hidden`),\n * for example Superior's sunlit water. Signature art goes in `decoration`.\n *\n * Prefetch is off on every internal footer link and the logo (`LINK_PREFETCH_POLICY.footer`): the footer comes into\n * view on every full scroll, and visitors rarely follow its links.\n */\nimport Link from 'next/link'\nimport type { ReactNode } from 'react'\nimport { Icon, SocialIcon } from '../icons.js'\nimport { PhoneLink } from '../primitives/links.js'\nimport { slot, type SkinEntry } from '../skin.js'\nimport type { UiLink, UiPhone, UiSocial } from '../types.js'\nimport { SiteFooterColumns } from './site-footer-columns.js'\n\nexport const SITE_FOOTER_VARIANTS = ['columns', 'photo'] as const\nexport type SiteFooterVariant = (typeof SITE_FOOTER_VARIANTS)[number]\n\nexport type SiteFooterSlot =\n | 'root'\n | 'panel'\n | 'media'\n | 'inner'\n | 'brand'\n | 'logo'\n | 'description'\n | 'licences'\n | 'licenceLink'\n | 'social'\n | 'socialLink'\n | 'contact'\n | 'phone'\n | 'phoneLabel'\n | 'phoneNumber'\n | 'email'\n | 'address'\n | 'columns'\n | 'column'\n | 'heading'\n | 'toggle'\n | 'list'\n | 'link'\n | 'bottom'\n | 'bottomInner'\n | 'legal'\n | 'policies'\n | 'policyLink'\n\nexport type SiteFooterSkin = SkinEntry<SiteFooterSlot, SiteFooterVariant>\n\n/** One licence line: the text as the register shows it, and the register link when there is one. */\nexport interface SiteFooterLicence {\n readonly text: string\n readonly href?: string\n}\n\nexport interface SiteFooterColumn {\n /** The H2 text, for example \"Services\". */\n readonly heading: string\n readonly links: readonly UiLink[]\n}\n\nexport interface SiteFooterProps extends SiteFooterSkin {\n /** The logo link's accessible name. */\n readonly brandName: string\n readonly homeHref?: string\n /** The site's logo node (decorative inside the named link). */\n readonly logo: ReactNode\n /** Two or three lines about the business. */\n readonly description: string\n readonly licences?: readonly SiteFooterLicence[]\n readonly socials?: readonly UiSocial[]\n readonly phone: UiPhone\n /** Optional words before the number, for example \"Call us\". */\n readonly phoneLabel?: string\n readonly email?: string\n /** Only when the business shows its address publicly. */\n readonly address?: string\n /** Services, Areas and Company, rendered in that order; leave one out when the site has nothing true for it. */\n readonly columns: {\n readonly services?: SiteFooterColumn\n readonly areas?: SiteFooterColumn\n readonly company?: SiteFooterColumn\n }\n readonly legal: {\n /** The legal or trading name. */\n readonly name: string\n readonly abn?: string\n /** Defaults to the current year in Sydney. */\n readonly year?: number\n /** The privacy policy link, labelled by the site. */\n readonly privacy?: UiLink\n /** The terms link, labelled by the site. */\n readonly terms?: UiLink\n }\n /** The `photo` variant's backdrop image for the panel (decorative). */\n readonly media?: ReactNode\n /** Signature art, rendered `aria-hidden`. */\n readonly decoration?: ReactNode\n /** Prefix for the column list ids (default `bl-footer`). */\n readonly idPrefix?: string\n}\n\nconst COLUMN_KEYS = ['services', 'areas', 'company'] as const\n\nfunction isExternal(href: string) {\n return /^https?:\\/\\//i.test(href)\n}\n\nfunction FooterLink({ link, hooks }: { link: UiLink; hooks?: { 'data-slot': string; className: string | undefined } }) {\n return isExternal(link.href) ? (\n <a href={link.href} {...hooks} target=\"_blank\" rel=\"noopener noreferrer\">\n {link.label}\n </a>\n ) : (\n <Link href={link.href} prefetch={false} {...hooks}>\n {link.label}\n </Link>\n )\n}\n\nfunction currentYear() {\n return Number(new Intl.DateTimeFormat('en-AU', { year: 'numeric', timeZone: 'Australia/Sydney' }).format(new Date()))\n}\n\nexport function SiteFooter({\n brandName,\n homeHref = '/',\n logo,\n description,\n licences = [],\n socials = [],\n phone,\n phoneLabel,\n email,\n address,\n columns,\n legal,\n media,\n decoration,\n idPrefix = 'bl-footer',\n variant = 'columns',\n classes,\n}: SiteFooterProps) {\n const policies = [legal.privacy, legal.terms].filter((link): link is UiLink => !!link)\n return (\n <footer data-bl=\"footer\" data-variant={variant} className={classes?.root}>\n {decoration ? (\n <div data-slot=\"decoration\" aria-hidden=\"true\">\n {decoration}\n </div>\n ) : null}\n <div {...slot(classes, 'panel')}>\n {variant === 'photo' && media ? (\n <div {...slot(classes, 'media')} aria-hidden=\"true\">\n {media}\n </div>\n ) : null}\n <div {...slot(classes, 'inner')}>\n <div {...slot(classes, 'brand')}>\n <Link href={homeHref} prefetch={false} {...slot(classes, 'logo')} aria-label={brandName}>\n {logo}\n </Link>\n <p {...slot(classes, 'description')}>{description}</p>\n {licences.length > 0 ? (\n <ul {...slot(classes, 'licences')}>\n {licences.map((licence) => (\n <li key={licence.text}>\n {licence.href ? (\n <a {...slot(classes, 'licenceLink')} href={licence.href} target=\"_blank\" rel=\"noopener noreferrer\">\n {licence.text}\n </a>\n ) : (\n licence.text\n )}\n </li>\n ))}\n </ul>\n ) : null}\n {socials.length > 0 ? (\n <ul {...slot(classes, 'social')}>\n {socials.map((social) => (\n <li key={social.href}>\n <a {...slot(classes, 'socialLink')} href={social.href} aria-label={social.label} target=\"_blank\" rel=\"noopener noreferrer\">\n <SocialIcon kind={social.kind} />\n </a>\n </li>\n ))}\n </ul>\n ) : null}\n <div {...slot(classes, 'contact')}>\n <PhoneLink phone={phone} placement=\"footer\" label={phoneLabel} classes={{ root: classes?.phone, label: classes?.phoneLabel, number: classes?.phoneNumber }} />\n {email ? (\n <a {...slot(classes, 'email')} href={`mailto:${email}`}>\n {email}\n </a>\n ) : null}\n {address ? <p {...slot(classes, 'address')}>{address}</p> : null}\n </div>\n </div>\n <SiteFooterColumns className={classes?.columns}>\n {COLUMN_KEYS.map((key) => {\n const column = columns[key]\n if (!column || column.links.length === 0) return null\n const listId = `${idPrefix}-${key}`\n return (\n <div key={key} {...slot(classes, 'column')} data-column={key}>\n <h2 {...slot(classes, 'heading')}>\n <span data-slot=\"headingText\">{column.heading}</span>\n <button type=\"button\" {...slot(classes, 'toggle')} aria-expanded=\"false\" aria-controls={listId} hidden>\n <span>{column.heading}</span>\n <Icon name=\"chevron\" />\n </button>\n </h2>\n <ul id={listId} {...slot(classes, 'list')}>\n {column.links.map((link) => (\n <li key={link.href}>\n <FooterLink link={link} hooks={slot(classes, 'link')} />\n </li>\n ))}\n </ul>\n </div>\n )\n })}\n </SiteFooterColumns>\n </div>\n </div>\n <div {...slot(classes, 'bottom')}>\n <div {...slot(classes, 'bottomInner')}>\n <p {...slot(classes, 'legal')}>\n © {legal.year ?? currentYear()} {legal.name}\n {legal.abn ? (\n <>\n <span aria-hidden=\"true\"> · </span>\n <span>ABN {legal.abn}</span>\n </>\n ) : null}\n </p>\n {policies.length > 0 ? (\n <ul {...slot(classes, 'policies')}>\n {policies.map((link) => (\n <li key={link.href}>\n <FooterLink link={link} hooks={slot(classes, 'policyLink')} />\n </li>\n ))}\n </ul>\n ) : null}\n </div>\n </div>\n </footer>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"site-footer.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/site-footer.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * S27 `SiteFooter`: the v1 §6 footer, structured the same on every site. Server component; one small client island\n * (`SiteFooterColumns`) turns the columns into a phone accordion.\n *\n * Column 1: the logo, a short description, one line per licence (with its register link when given), social and\n * Business Profile icons, then the phone (`phone_link_click` placement `footer`), the email and the address only when\n * it is public. Then Services, Areas and Company, each under a real H2, in that order. A bottom bar: © year name ·\n * ABN · the privacy and terms links. Never a quote button, \"based in\", opening hours, a guides column or a \"back to\n * top\" text link: the props have no place for them.\n *\n * Variants: `columns`; `photo`, with the site's backdrop image for the panel in `media` (decorative, `aria-hidden`),\n * for example Superior's sunlit water. Signature art goes in `decoration`.\n *\n * Prefetch is off on every internal footer link and the logo (`LINK_PREFETCH_POLICY.footer`): the footer comes into\n * view on every full scroll, and visitors rarely follow its links.\n */\nimport Link from 'next/link'\nimport type { ReactNode } from 'react'\nimport { Icon, SocialIcon } from '../icons.js'\nimport { PhoneLink } from '../primitives/links.js'\nimport type { RepeatedSlotOf } from '../repeated-slots.js'\nimport { repeatedSlot, slot, type SkinEntry } from '../skin.js'\nimport type { UiLink, UiPhone, UiSocial } from '../types.js'\nimport { SiteFooterColumns } from './site-footer-columns.js'\n\nexport const SITE_FOOTER_VARIANTS = ['columns', 'photo'] as const\nexport type SiteFooterVariant = (typeof SITE_FOOTER_VARIANTS)[number]\n\nexport type SiteFooterSlot =\n | 'root'\n | 'panel'\n | 'media'\n | 'inner'\n | 'brand'\n | 'logo'\n | 'description'\n | 'licences'\n | 'licenceLink'\n | 'social'\n | 'socialLink'\n | 'contact'\n | 'phone'\n | 'phoneLabel'\n | 'phoneNumber'\n | 'email'\n | 'address'\n | 'columns'\n | 'column'\n | 'heading'\n | 'toggle'\n | 'list'\n | 'link'\n | 'bottom'\n | 'bottomInner'\n | 'legal'\n | 'policies'\n | 'policyLink'\n\n/** Rendered once per licence, profile, column, link or policy: styled from the skin stylesheet, never by class (3.14.0). */\nexport type SiteFooterRepeatedSlot = RepeatedSlotOf<'siteFooter'>\nexport type SiteFooterSkin = SkinEntry<SiteFooterSlot, SiteFooterVariant, Record<never, never>, SiteFooterRepeatedSlot>\n\n/** One licence line: the text as the register shows it, and the register link when there is one. */\nexport interface SiteFooterLicence {\n readonly text: string\n readonly href?: string\n}\n\nexport interface SiteFooterColumn {\n /** The H2 text, for example \"Services\". */\n readonly heading: string\n readonly links: readonly UiLink[]\n}\n\nexport interface SiteFooterProps extends SiteFooterSkin {\n /** The logo link's accessible name. */\n readonly brandName: string\n readonly homeHref?: string\n /** The site's logo node (decorative inside the named link). */\n readonly logo: ReactNode\n /** Two or three lines about the business. */\n readonly description: string\n readonly licences?: readonly SiteFooterLicence[]\n readonly socials?: readonly UiSocial[]\n readonly phone: UiPhone\n /** Optional words before the number, for example \"Call us\". */\n readonly phoneLabel?: string\n readonly email?: string\n /** Only when the business shows its address publicly. */\n readonly address?: string\n /** Services, Areas and Company, rendered in that order; leave one out when the site has nothing true for it. */\n readonly columns: {\n readonly services?: SiteFooterColumn\n readonly areas?: SiteFooterColumn\n readonly company?: SiteFooterColumn\n }\n readonly legal: {\n /** The legal or trading name. */\n readonly name: string\n readonly abn?: string\n /** Defaults to the current year in Sydney. */\n readonly year?: number\n /** The privacy policy link, labelled by the site. */\n readonly privacy?: UiLink\n /** The terms link, labelled by the site. */\n readonly terms?: UiLink\n }\n /** The `photo` variant's backdrop image for the panel (decorative). */\n readonly media?: ReactNode\n /** Signature art, rendered `aria-hidden`. */\n readonly decoration?: ReactNode\n /** Prefix for the column list ids (default `bl-footer`). */\n readonly idPrefix?: string\n}\n\nconst COLUMN_KEYS = ['services', 'areas', 'company'] as const\n\nfunction isExternal(href: string) {\n return /^https?:\\/\\//i.test(href)\n}\n\nfunction FooterLink({ link, slotName }: { link: UiLink; slotName: 'link' | 'policyLink' }) {\n return isExternal(link.href) ? (\n <a href={link.href} {...repeatedSlot(slotName)} target=\"_blank\" rel=\"noopener noreferrer\">\n {link.label}\n </a>\n ) : (\n <Link href={link.href} prefetch={false} {...repeatedSlot(slotName)}>\n {link.label}\n </Link>\n )\n}\n\nfunction currentYear() {\n return Number(new Intl.DateTimeFormat('en-AU', { year: 'numeric', timeZone: 'Australia/Sydney' }).format(new Date()))\n}\n\nexport function SiteFooter({\n brandName,\n homeHref = '/',\n logo,\n description,\n licences = [],\n socials = [],\n phone,\n phoneLabel,\n email,\n address,\n columns,\n legal,\n media,\n decoration,\n idPrefix = 'bl-footer',\n variant = 'columns',\n classes,\n}: SiteFooterProps) {\n const policies = [legal.privacy, legal.terms].filter((link): link is UiLink => !!link)\n return (\n <footer data-bl=\"footer\" data-variant={variant} className={classes?.root}>\n {decoration ? (\n <div data-slot=\"decoration\" aria-hidden=\"true\">\n {decoration}\n </div>\n ) : null}\n <div {...slot(classes, 'panel')}>\n {variant === 'photo' && media ? (\n <div {...slot(classes, 'media')} aria-hidden=\"true\">\n {media}\n </div>\n ) : null}\n <div {...slot(classes, 'inner')}>\n <div {...slot(classes, 'brand')}>\n <Link href={homeHref} prefetch={false} {...slot(classes, 'logo')} aria-label={brandName}>\n {logo}\n </Link>\n <p {...slot(classes, 'description')}>{description}</p>\n {licences.length > 0 ? (\n <ul {...slot(classes, 'licences')}>\n {licences.map((licence) => (\n <li key={licence.text}>\n {licence.href ? (\n <a {...repeatedSlot('licenceLink')} href={licence.href} target=\"_blank\" rel=\"noopener noreferrer\">\n {licence.text}\n </a>\n ) : (\n licence.text\n )}\n </li>\n ))}\n </ul>\n ) : null}\n {socials.length > 0 ? (\n <ul {...slot(classes, 'social')}>\n {socials.map((social) => (\n <li key={social.href}>\n <a {...repeatedSlot('socialLink')} href={social.href} aria-label={social.label} target=\"_blank\" rel=\"noopener noreferrer\">\n <SocialIcon kind={social.kind} />\n </a>\n </li>\n ))}\n </ul>\n ) : null}\n <div {...slot(classes, 'contact')}>\n <PhoneLink phone={phone} placement=\"footer\" label={phoneLabel} classes={{ root: classes?.phone, label: classes?.phoneLabel, number: classes?.phoneNumber }} />\n {email ? (\n <a {...slot(classes, 'email')} href={`mailto:${email}`}>\n {email}\n </a>\n ) : null}\n {address ? <p {...slot(classes, 'address')}>{address}</p> : null}\n </div>\n </div>\n <SiteFooterColumns className={classes?.columns}>\n {COLUMN_KEYS.map((key) => {\n const column = columns[key]\n if (!column || column.links.length === 0) return null\n const listId = `${idPrefix}-${key}`\n return (\n <div key={key} {...repeatedSlot('column')} data-column={key}>\n <h2 {...repeatedSlot('heading')}>\n <span data-slot=\"headingText\">{column.heading}</span>\n <button type=\"button\" {...repeatedSlot('toggle')} aria-expanded=\"false\" aria-controls={listId} hidden>\n <span>{column.heading}</span>\n <Icon name=\"chevron\" />\n </button>\n </h2>\n <ul id={listId} {...repeatedSlot('list')}>\n {column.links.map((link) => (\n <li key={link.href}>\n <FooterLink link={link} slotName=\"link\" />\n </li>\n ))}\n </ul>\n </div>\n )\n })}\n </SiteFooterColumns>\n </div>\n </div>\n <div {...slot(classes, 'bottom')}>\n <div {...slot(classes, 'bottomInner')}>\n <p {...slot(classes, 'legal')}>\n © {legal.year ?? currentYear()} {legal.name}\n {legal.abn ? (\n <>\n <span aria-hidden=\"true\"> · </span>\n <span>ABN {legal.abn}</span>\n </>\n ) : null}\n </p>\n {policies.length > 0 ? (\n <ul {...slot(classes, 'policies')}>\n {policies.map((link) => (\n <li key={link.href}>\n <FooterLink link={link} slotName=\"policyLink\" />\n </li>\n ))}\n </ul>\n ) : null}\n </div>\n </div>\n </footer>\n )\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rules the "our work" sections share (3.15.0): `Projects`, `Gallery` and `BeforeAfter` show the client's real work
|
|
3
|
+
* only (v2 §11 imagery rule 4, F13). A stock or AI image never stands for a job, so these sections refuse one and fail
|
|
4
|
+
* the build, the same way the hero refuses an AI photo; when there are no genuine photos the section is skipped.
|
|
5
|
+
*/
|
|
6
|
+
import type { MediaFrameSizing, UiMedia } from '../primitives/media-frame.js';
|
|
7
|
+
/** Throws unless the image is a genuine, described photo of the work. */
|
|
8
|
+
export declare function assertGenuineWork(component: string, media: UiMedia, where: string): void;
|
|
9
|
+
/** Fluid `sizes` for one column of a grid that is one column on phones, two from 640 px and `columns` from 1024 px. */
|
|
10
|
+
export declare function gridImageSizes(columns: number): string;
|
|
11
|
+
/** `MediaFrame`'s sizing from a section's `imageSizes`: a number is a fixed CSS width, a string a fluid `vw` size. */
|
|
12
|
+
export declare function frameSizing(sizes: number | string): MediaFrameSizing;
|
|
13
|
+
/** An internal page path: starts with one `/`, no spaces. */
|
|
14
|
+
export declare function isInternalPath(href: string): boolean;
|
|
15
|
+
//# sourceMappingURL=work-photos.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"work-photos.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/work-photos.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAA;AAE7E,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CASxF;AAED,uHAAuH;AACvH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGtD;AAED,sHAAsH;AACtH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,gBAAgB,CAEpE;AAED,6DAA6D;AAC7D,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD"}
|