@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
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { COUNCIL_PERMIT_SKIP_REASON, visibleCouncilRecord } from '@brandlab-digital/site-core/council-register';
|
|
3
|
+
import { SECTION_READY, SECTION_SLOT_ATTRIBUTE, SECTION_SLOT_SKIP_ATTRIBUTE, formatSectionSlotSkips, } from '@brandlab-digital/site-core/section-order';
|
|
4
|
+
import { Anchor } from '../primitives/links.js';
|
|
5
|
+
import { Section, SectionHeading } from '../primitives/layout.js';
|
|
6
|
+
import { repeatedSlot, slot } from '../skin.js';
|
|
7
|
+
import { CouncilRecordLines } from './council-rules.js';
|
|
8
|
+
export const LOCAL_FACTS_VARIANTS = ['intro', 'detailed'];
|
|
9
|
+
/** The guard for `SectionSequence`: renders when it has at least one true item to show. */
|
|
10
|
+
export function localFactsReady({ intro, body, facts, job, permit }) {
|
|
11
|
+
const permitShown = permit ? visibleCouncilRecord(permit.record, permit.asOf) !== undefined : false;
|
|
12
|
+
if (intro || body || (facts && facts.length > 0) || job || permitShown)
|
|
13
|
+
return SECTION_READY;
|
|
14
|
+
return { render: false, reason: 'no-local-facts' };
|
|
15
|
+
}
|
|
16
|
+
function assertFacts(facts) {
|
|
17
|
+
const ids = new Set();
|
|
18
|
+
for (const fact of facts) {
|
|
19
|
+
if (!fact.id || ids.has(fact.id))
|
|
20
|
+
throw new Error(`LocalFacts: every fact needs its own id ("${fact.id}" is empty or repeated).`);
|
|
21
|
+
ids.add(fact.id);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function LocalFacts({ title, eyebrow, lead, intro, body, facts = [], job, permit, headingId = 'local-facts-heading', tone, variant = 'intro', classes, }) {
|
|
25
|
+
if (!localFactsReady({ intro, body, facts, job, permit }).render)
|
|
26
|
+
return null;
|
|
27
|
+
assertFacts(facts);
|
|
28
|
+
const record = permit ? visibleCouncilRecord(permit.record, permit.asOf) : undefined;
|
|
29
|
+
const skip = permit && !record ? { [SECTION_SLOT_SKIP_ATTRIBUTE]: formatSectionSlotSkips([{ slot: 'permit', reason: COUNCIL_PERMIT_SKIP_REASON }]) } : undefined;
|
|
30
|
+
return (_jsxs(Section, { id: "local-facts", labelledBy: headingId, tone: tone, variant: variant, classes: classes, phonePlacement: "location", hooks: skip, children: [intro ? _jsx("div", { ...slot(classes, 'intro'), children: intro }) : null, _jsx(SectionHeading, { id: headingId, title: title, eyebrow: eyebrow, lead: lead, classes: classes }), body ? _jsx("div", { ...slot(classes, 'body'), children: body }) : null, facts.length > 0 ? (_jsx("ul", { role: "list", ...slot(classes, 'facts'), children: facts.map((fact) => (_jsxs("li", { ...repeatedSlot('fact'), "data-item": fact.id, children: [fact.text, fact.source ? (_jsxs(_Fragment, { children: [' ', _jsx(Anchor, { href: fact.source.href, prefetch: false, hooks: repeatedSlot('factSource'), children: fact.source.label })] })) : null] }, fact.id))) })) : null, job ? _jsx("p", { ...slot(classes, 'job'), children: job }) : null, permit && record ? (_jsx("div", { ...slot(classes, 'permit'), [SECTION_SLOT_ATTRIBUTE]: 'permit', "data-council": record.council, children: _jsx(CouncilRecordLines, { record: record, rule: permit.rule, sourceLabel: permit.sourceLabel, councilPage: permit.councilPage, classes: classes }) })) : null] }));
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=local-facts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-facts.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/local-facts.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `LocalFacts` (S16, `local-facts`, 3.15.0): what makes a suburb or service-in-suburb page true for this place, the\n * block that keeps location pages from being doorways. Server component.\n *\n * Only true items render, all from the site's data: an optional `intro` first (the page's own local overview, before\n * the heading, 3.15.0), the site's own sentences about real work here (`body`; 2 to 4\n * sentences on standard pages, the head page's owner-confirmed copy on `detailed`), sourced facts, one line about the\n * job here or nearby, and, in permit trades, the permit note from the council register. Locked (Romie, v1 §4.4): no\n * suburb trivia, no council facts beyond the permit note, no kilometre figures or \"based in\" (L10), no copy rotated from\n * a pool (L4). A phone link inside reports the `location` placement.\n *\n * The permit note is the `permit` slot (K12-slots): its line carries `data-bl-slot=\"permit\"`. When the page passes a\n * `permit` but the register has no current record for the council (none, more than six months since it was checked,\n * or past its fees' financial year), the line renders nothing and the section lists\n * `data-bl-slot-skip=\"permit:no-current-council-record\"`, which K12-slots reports as a warning until the record is\n * checked again. A page in a trade without a permit note passes no `permit`.\n */\nimport type { ReactNode } from 'react'\nimport { COUNCIL_PERMIT_SKIP_REASON, visibleCouncilRecord, type BrandlabCouncilPermitRecord } from '@brandlab-digital/site-core/council-register'\nimport {\n SECTION_READY,\n SECTION_SLOT_ATTRIBUTE,\n SECTION_SLOT_SKIP_ATTRIBUTE,\n formatSectionSlotSkips,\n type BrandlabSectionReadiness,\n} from '@brandlab-digital/site-core/section-order'\nimport { Anchor } from '../primitives/links.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'\nimport { CouncilRecordLines, type CouncilRecordSlot, type CouncilRuleWording } from './council-rules.js'\n\nexport const LOCAL_FACTS_VARIANTS = ['intro', 'detailed'] as const\nexport type LocalFactsVariant = (typeof LOCAL_FACTS_VARIANTS)[number]\nexport type LocalFactsSlot = SectionSlot | SectionHeadingSlot | 'intro' | 'body' | 'facts' | 'fact' | 'factSource' | 'job' | 'permit' | CouncilRecordSlot | 'fee'\n/** Rendered once per fact or fee: styled from the skin stylesheet, never by class. */\nexport type LocalFactsRepeatedSlot = RepeatedSlotOf<'localFacts'>\nexport type LocalFactsSkin = SkinEntry<LocalFactsSlot, LocalFactsVariant, Record<never, never>, LocalFactsRepeatedSlot>\n\n/** One sourced fact that changes the job here (a job or owner source; never a templated terrain line). */\nexport interface LocalFact {\n /** A stable id, rendered as `data-item`. */\n readonly id: string\n readonly text: ReactNode\n /** Where the fact can be checked. */\n readonly source?: UiLink\n}\n\n/** The permit note's data: the council's register record and how this site words it. */\nexport interface LocalFactsPermit {\n /** The register record for the suburb's council; `undefined` when the register has none. */\n readonly record: BrandlabCouncilPermitRecord | undefined\n /** The site's own wording of the rule in one or two lines. Defaults to the record's rule. */\n readonly rule?: CouncilRuleWording\n /** The source link's text. Defaults to the council's name. */\n readonly sourceLabel?: string\n /** The site's own council page, for example its \"Tree removal and Hornsby Council\" guide. */\n readonly councilPage?: UiLink\n /** The day the page is built or refreshed (Sydney). Defaults to now. */\n readonly asOf?: Date\n}\n\nexport interface LocalFactsProps extends LocalFactsSkin {\n /** The H2, for example \"Tree work in Castle Hill\". */\n readonly title: ReactNode\n readonly eyebrow?: ReactNode\n readonly lead?: ReactNode\n /**\n * The page's own introduction to the place (3.15.0), for example a suburb page's local overview paragraph. Site copy,\n * rendered first inside the section, before its heading, so the suburb page's intro lives in the `local-facts` place.\n */\n readonly intro?: ReactNode\n /** The site's own copy about real work here: sentences on `intro`, the head page's copy (with H3s) on `detailed`. */\n readonly body?: ReactNode\n readonly facts?: readonly LocalFact[]\n /** One line about the job here, or the nearest one labelled as nearby. */\n readonly job?: ReactNode\n /** Permit trades only. */\n readonly permit?: LocalFactsPermit\n /** Defaults to `local-facts-heading`. */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n}\n\n/** The guard for `SectionSequence`: renders when it has at least one true item to show. */\nexport function localFactsReady({ intro, body, facts, job, permit }: Pick<LocalFactsProps, 'intro' | 'body' | 'facts' | 'job' | 'permit'>): BrandlabSectionReadiness {\n const permitShown = permit ? visibleCouncilRecord(permit.record, permit.asOf) !== undefined : false\n if (intro || body || (facts && facts.length > 0) || job || permitShown) return SECTION_READY\n return { render: false, reason: 'no-local-facts' }\n}\n\nfunction assertFacts(facts: readonly LocalFact[]) {\n const ids = new Set<string>()\n for (const fact of facts) {\n if (!fact.id || ids.has(fact.id)) throw new Error(`LocalFacts: every fact needs its own id (\"${fact.id}\" is empty or repeated).`)\n ids.add(fact.id)\n }\n}\n\nexport function LocalFacts({\n title,\n eyebrow,\n lead,\n intro,\n body,\n facts = [],\n job,\n permit,\n headingId = 'local-facts-heading',\n tone,\n variant = 'intro',\n classes,\n}: LocalFactsProps) {\n if (!localFactsReady({ intro, body, facts, job, permit }).render) return null\n assertFacts(facts)\n const record = permit ? visibleCouncilRecord(permit.record, permit.asOf) : undefined\n const skip = permit && !record ? { [SECTION_SLOT_SKIP_ATTRIBUTE]: formatSectionSlotSkips([{ slot: 'permit', reason: COUNCIL_PERMIT_SKIP_REASON }]) } : undefined\n return (\n <Section id=\"local-facts\" labelledBy={headingId} tone={tone} variant={variant} classes={classes} phonePlacement=\"location\" hooks={skip}>\n {intro ? <div {...slot(classes, 'intro')}>{intro}</div> : null}\n <SectionHeading id={headingId} title={title} eyebrow={eyebrow} lead={lead} classes={classes} />\n {body ? <div {...slot(classes, 'body')}>{body}</div> : null}\n {facts.length > 0 ? (\n <ul role=\"list\" {...slot(classes, 'facts')}>\n {facts.map((fact) => (\n <li key={fact.id} {...repeatedSlot('fact')} data-item={fact.id}>\n {fact.text}\n {fact.source ? (\n <>\n {' '}\n <Anchor href={fact.source.href} prefetch={false} hooks={repeatedSlot('factSource')}>\n {fact.source.label}\n </Anchor>\n </>\n ) : null}\n </li>\n ))}\n </ul>\n ) : null}\n {job ? <p {...slot(classes, 'job')}>{job}</p> : null}\n {permit && record ? (\n <div {...slot(classes, 'permit')} {...{ [SECTION_SLOT_ATTRIBUTE]: 'permit' }} data-council={record.council}>\n <CouncilRecordLines record={record} rule={permit.rule} sourceLabel={permit.sourceLabel} councilPage={permit.councilPage} classes={classes} />\n </div>\n ) : null}\n </Section>\n )\n}\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { type SuburbCentreLookup } from '@brandlab-digital/site-core/locations';
|
|
3
|
+
import { type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order';
|
|
4
|
+
import { type LinkListLayout } from '../primitives/lists.js';
|
|
5
|
+
import { type SectionHeadingSlot, type SectionSlot, type SectionTone } from '../primitives/layout.js';
|
|
6
|
+
import { type SkinEntry } from '../skin.js';
|
|
7
|
+
import type { UiLink } from '../types.js';
|
|
8
|
+
/** The most neighbours the section lists. */
|
|
9
|
+
export declare const NEARBY_AREAS_LIMIT = 12;
|
|
10
|
+
/** The neighbours `nearbyAreaLinks` returns by default (v1 §4.4: 4 to 6). */
|
|
11
|
+
export declare const NEARBY_AREAS_DEFAULT = 6;
|
|
12
|
+
export declare const NEARBY_AREAS_VARIANTS: readonly ["list", "chips"];
|
|
13
|
+
export type NearbyAreasVariant = (typeof NEARBY_AREAS_VARIANTS)[number];
|
|
14
|
+
export type NearbyAreasSlot = SectionSlot | SectionHeadingSlot | 'list' | 'region' | 'more';
|
|
15
|
+
export type NearbyAreasLayout = LinkListLayout;
|
|
16
|
+
export type NearbyAreasSkin = SkinEntry<NearbyAreasSlot, NearbyAreasVariant, NearbyAreasLayout>;
|
|
17
|
+
export interface NearbyAreasProps extends NearbyAreasSkin {
|
|
18
|
+
/** The H2, for example "Tree removal near Castle Hill". */
|
|
19
|
+
readonly title: ReactNode;
|
|
20
|
+
readonly eyebrow?: ReactNode;
|
|
21
|
+
readonly lead?: ReactNode;
|
|
22
|
+
/** The neighbours, nearest first (`nearbyAreaLinks`). */
|
|
23
|
+
readonly areas: readonly UiLink[];
|
|
24
|
+
/**
|
|
25
|
+
* The page of the region these suburbs belong to, for example `{ label: 'All of the Hills District', href:
|
|
26
|
+
* '/tree-services/hills-district' }` (the label is site copy). It sits after the ranked list, outside it, so it is
|
|
27
|
+
* never ranked by distance, and it is never prefetched.
|
|
28
|
+
*/
|
|
29
|
+
readonly regionLink?: UiLink;
|
|
30
|
+
/** A link after the list (and the region link), for example the areas hub. Never prefetched. */
|
|
31
|
+
readonly more?: UiLink;
|
|
32
|
+
/** Defaults to `nearby-areas-heading`. */
|
|
33
|
+
readonly headingId?: string;
|
|
34
|
+
/** Set by `SectionSequence`. */
|
|
35
|
+
readonly tone?: SectionTone;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The nearest served suburbs to `origin` as links, nearest first by real distance (`nearestSuburbsWithin`). `link`
|
|
39
|
+
* gives a suburb's page (or `undefined` to leave it out, for a suburb without that page).
|
|
40
|
+
*/
|
|
41
|
+
export declare function nearbyAreaLinks(origin: string, served: readonly string[], options: {
|
|
42
|
+
readonly centreOf: SuburbCentreLookup;
|
|
43
|
+
readonly link: (suburb: string) => UiLink | undefined;
|
|
44
|
+
readonly limit?: number;
|
|
45
|
+
readonly maxKm?: number;
|
|
46
|
+
}): UiLink[];
|
|
47
|
+
/** The guard for `SectionSequence`. */
|
|
48
|
+
export declare function nearbyAreasReady({ areas }: Pick<NearbyAreasProps, 'areas'>): BrandlabSectionReadiness;
|
|
49
|
+
export declare function NearbyAreas({ title, eyebrow, lead, areas, regionLink, more, headingId, tone, variant, layout, classes }: NearbyAreasProps): import("react/jsx-runtime").JSX.Element | null;
|
|
50
|
+
//# sourceMappingURL=nearby-areas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nearby-areas.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/nearby-areas.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,uCAAuC,CAAA;AACrG,OAAO,EAAiB,KAAK,wBAAwB,EAAE,MAAM,2CAA2C,CAAA;AACxG,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAA;AACtE,OAAO,EAA2B,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC9H,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,6CAA6C;AAC7C,eAAO,MAAM,kBAAkB,KAAK,CAAA;AACpC,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,IAAI,CAAA;AAErC,eAAO,MAAM,qBAAqB,4BAA6B,CAAA;AAC/D,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAA;AACvE,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,kBAAkB,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAA;AAC3F,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAA;AAC9C,MAAM,MAAM,eAAe,GAAG,SAAS,CAAC,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,CAAC,CAAA;AAE/F,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAA;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;IACzB,yDAAyD;IACzD,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,gGAAgG;IAChG,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,gCAAgC;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAC5B;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,OAAO,EAAE;IAAE,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1J,MAAM,EAAE,CAOV;AAED,uCAAuC;AACvC,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,gBAAgB,EAAE,OAAO,CAAC,GAAG,wBAAwB,CAErG;AAED,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,SAAkC,EAAE,IAAI,EAAE,OAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,gBAAgB,kDAyB3K"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* `NearbyAreas` (S18, `nearby-areas`, 3.15.0): links to the real neighbours of a suburb page, or the same service in
|
|
4
|
+
* the neighbouring suburbs on a service-in-suburb page. Server component.
|
|
5
|
+
*
|
|
6
|
+
* Locked (v1 §4.4, L3): the nearest served suburbs by real distance between registry centres, nearest first, never
|
|
7
|
+
* shuffled or alphabetical; `nearbyAreaLinks()` builds them with site-core's `nearestSuburbsWithin`. Distances rank the
|
|
8
|
+
* links and are never printed (L10). The list is lean (cost item 1f): `LinkList` entries are plain `<li><a>` with no
|
|
9
|
+
* class, hook or icon, so each link stays under 120 bytes, and nothing in it prefetches (`data-bl-links="nearby"`, W2).
|
|
10
|
+
* 4 to 6 links suit most pages; 12 is the most it renders.
|
|
11
|
+
*/
|
|
12
|
+
import Link from 'next/link';
|
|
13
|
+
import { nearestSuburbsWithin } from '@brandlab-digital/site-core/locations';
|
|
14
|
+
import { SECTION_READY } from '@brandlab-digital/site-core/section-order';
|
|
15
|
+
import { LinkList } from '../primitives/lists.js';
|
|
16
|
+
import { Section, SectionHeading } from '../primitives/layout.js';
|
|
17
|
+
import { slot } from '../skin.js';
|
|
18
|
+
/** The most neighbours the section lists. */
|
|
19
|
+
export const NEARBY_AREAS_LIMIT = 12;
|
|
20
|
+
/** The neighbours `nearbyAreaLinks` returns by default (v1 §4.4: 4 to 6). */
|
|
21
|
+
export const NEARBY_AREAS_DEFAULT = 6;
|
|
22
|
+
export const NEARBY_AREAS_VARIANTS = ['list', 'chips'];
|
|
23
|
+
/**
|
|
24
|
+
* The nearest served suburbs to `origin` as links, nearest first by real distance (`nearestSuburbsWithin`). `link`
|
|
25
|
+
* gives a suburb's page (or `undefined` to leave it out, for a suburb without that page).
|
|
26
|
+
*/
|
|
27
|
+
export function nearbyAreaLinks(origin, served, options) {
|
|
28
|
+
const limit = options.limit ?? NEARBY_AREAS_DEFAULT;
|
|
29
|
+
if (limit > NEARBY_AREAS_LIMIT)
|
|
30
|
+
throw new Error(`nearbyAreaLinks: at most ${NEARBY_AREAS_LIMIT} neighbours.`);
|
|
31
|
+
const candidates = served.filter((suburb) => options.link(suburb) !== undefined);
|
|
32
|
+
return nearestSuburbsWithin(origin, candidates, { centreOf: options.centreOf, limit, ...(options.maxKm !== undefined ? { maxKm: options.maxKm } : {}) }).map((entry) => options.link(entry.slug));
|
|
33
|
+
}
|
|
34
|
+
/** The guard for `SectionSequence`. */
|
|
35
|
+
export function nearbyAreasReady({ areas }) {
|
|
36
|
+
return areas.length > 0 ? SECTION_READY : { render: false, reason: 'no-nearby-areas' };
|
|
37
|
+
}
|
|
38
|
+
export function NearbyAreas({ title, eyebrow, lead, areas, regionLink, more, headingId = 'nearby-areas-heading', tone, variant = 'list', layout, classes }) {
|
|
39
|
+
if (areas.length === 0)
|
|
40
|
+
return null;
|
|
41
|
+
if (areas.length > NEARBY_AREAS_LIMIT)
|
|
42
|
+
throw new Error(`NearbyAreas: ${areas.length} neighbours; the section lists at most ${NEARBY_AREAS_LIMIT}.`);
|
|
43
|
+
if (new Set(areas.map((area) => area.href)).size !== areas.length)
|
|
44
|
+
throw new Error('NearbyAreas: a neighbour is listed twice.');
|
|
45
|
+
if (regionLink && areas.some((area) => area.href === regionLink.href))
|
|
46
|
+
throw new Error('NearbyAreas: the region link repeats a neighbour.');
|
|
47
|
+
return (_jsxs(Section, { id: "nearby-areas", labelledBy: headingId, tone: tone, variant: variant, classes: classes, children: [_jsx(SectionHeading, { id: headingId, title: title, eyebrow: eyebrow, lead: lead, classes: classes }), _jsx(LinkList, { links: areas, kind: "nearby", layout: layout, classes: { root: classes?.list } }), regionLink ? (_jsx("p", { ...slot(classes, 'region'), children: _jsx(Link, { href: regionLink.href, prefetch: false, children: regionLink.label }) })) : null, more ? (_jsx("p", { ...slot(classes, 'more'), children: _jsx(Link, { href: more.href, prefetch: false, children: more.label }) })) : null] }));
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=nearby-areas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nearby-areas.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/nearby-areas.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `NearbyAreas` (S18, `nearby-areas`, 3.15.0): links to the real neighbours of a suburb page, or the same service in\n * the neighbouring suburbs on a service-in-suburb page. Server component.\n *\n * Locked (v1 §4.4, L3): the nearest served suburbs by real distance between registry centres, nearest first, never\n * shuffled or alphabetical; `nearbyAreaLinks()` builds them with site-core's `nearestSuburbsWithin`. Distances rank the\n * links and are never printed (L10). The list is lean (cost item 1f): `LinkList` entries are plain `<li><a>` with no\n * class, hook or icon, so each link stays under 120 bytes, and nothing in it prefetches (`data-bl-links=\"nearby\"`, W2).\n * 4 to 6 links suit most pages; 12 is the most it renders.\n */\nimport Link from 'next/link'\nimport type { ReactNode } from 'react'\nimport { nearestSuburbsWithin, type SuburbCentreLookup } from '@brandlab-digital/site-core/locations'\nimport { SECTION_READY, type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order'\nimport { LinkList, type LinkListLayout } from '../primitives/lists.js'\nimport { Section, SectionHeading, type SectionHeadingSlot, type SectionSlot, type SectionTone } from '../primitives/layout.js'\nimport { slot, type SkinEntry } from '../skin.js'\nimport type { UiLink } from '../types.js'\n\n/** The most neighbours the section lists. */\nexport const NEARBY_AREAS_LIMIT = 12\n/** The neighbours `nearbyAreaLinks` returns by default (v1 §4.4: 4 to 6). */\nexport const NEARBY_AREAS_DEFAULT = 6\n\nexport const NEARBY_AREAS_VARIANTS = ['list', 'chips'] as const\nexport type NearbyAreasVariant = (typeof NEARBY_AREAS_VARIANTS)[number]\nexport type NearbyAreasSlot = SectionSlot | SectionHeadingSlot | 'list' | 'region' | 'more'\nexport type NearbyAreasLayout = LinkListLayout\nexport type NearbyAreasSkin = SkinEntry<NearbyAreasSlot, NearbyAreasVariant, NearbyAreasLayout>\n\nexport interface NearbyAreasProps extends NearbyAreasSkin {\n /** The H2, for example \"Tree removal near Castle Hill\". */\n readonly title: ReactNode\n readonly eyebrow?: ReactNode\n readonly lead?: ReactNode\n /** The neighbours, nearest first (`nearbyAreaLinks`). */\n readonly areas: readonly UiLink[]\n /**\n * The page of the region these suburbs belong to, for example `{ label: 'All of the Hills District', href:\n * '/tree-services/hills-district' }` (the label is site copy). It sits after the ranked list, outside it, so it is\n * never ranked by distance, and it is never prefetched.\n */\n readonly regionLink?: UiLink\n /** A link after the list (and the region link), for example the areas hub. Never prefetched. */\n readonly more?: UiLink\n /** Defaults to `nearby-areas-heading`. */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n}\n\n/**\n * The nearest served suburbs to `origin` as links, nearest first by real distance (`nearestSuburbsWithin`). `link`\n * gives a suburb's page (or `undefined` to leave it out, for a suburb without that page).\n */\nexport function nearbyAreaLinks(\n origin: string,\n served: readonly string[],\n options: { readonly centreOf: SuburbCentreLookup; readonly link: (suburb: string) => UiLink | undefined; readonly limit?: number; readonly maxKm?: number },\n): UiLink[] {\n const limit = options.limit ?? NEARBY_AREAS_DEFAULT\n if (limit > NEARBY_AREAS_LIMIT) throw new Error(`nearbyAreaLinks: at most ${NEARBY_AREAS_LIMIT} neighbours.`)\n const candidates = served.filter((suburb) => options.link(suburb) !== undefined)\n return nearestSuburbsWithin(origin, candidates, { centreOf: options.centreOf, limit, ...(options.maxKm !== undefined ? { maxKm: options.maxKm } : {}) }).map(\n (entry) => options.link(entry.slug)!,\n )\n}\n\n/** The guard for `SectionSequence`. */\nexport function nearbyAreasReady({ areas }: Pick<NearbyAreasProps, 'areas'>): BrandlabSectionReadiness {\n return areas.length > 0 ? SECTION_READY : { render: false, reason: 'no-nearby-areas' }\n}\n\nexport function NearbyAreas({ title, eyebrow, lead, areas, regionLink, more, headingId = 'nearby-areas-heading', tone, variant = 'list', layout, classes }: NearbyAreasProps) {\n if (areas.length === 0) return null\n if (areas.length > NEARBY_AREAS_LIMIT) throw new Error(`NearbyAreas: ${areas.length} neighbours; the section lists at most ${NEARBY_AREAS_LIMIT}.`)\n if (new Set(areas.map((area) => area.href)).size !== areas.length) throw new Error('NearbyAreas: a neighbour is listed twice.')\n if (regionLink && areas.some((area) => area.href === regionLink.href)) throw new Error('NearbyAreas: the region link repeats a neighbour.')\n return (\n <Section id=\"nearby-areas\" labelledBy={headingId} tone={tone} variant={variant} classes={classes}>\n <SectionHeading id={headingId} title={title} eyebrow={eyebrow} lead={lead} classes={classes} />\n <LinkList links={areas} kind=\"nearby\" layout={layout} classes={{ root: classes?.list }} />\n {regionLink ? (\n <p {...slot(classes, 'region')}>\n <Link href={regionLink.href} prefetch={false}>\n {regionLink.label}\n </Link>\n </p>\n ) : null}\n {more ? (\n <p {...slot(classes, 'more')}>\n <Link href={more.href} prefetch={false}>\n {more.label}\n </Link>\n </p>\n ) : null}\n </Section>\n )\n}\n"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `NearbyJobs` (S17, `nearby-jobs`, 3.15.0): proof of real work near this place. Server component.
|
|
3
|
+
*
|
|
4
|
+
* The cards come from the job log (site-core `./jobs`): `nearbyJobs(origin, jobs, { centreOf, service })` ranks the
|
|
5
|
+
* page suburb's own jobs first, then others by real distance within 10 km, at most three. Each card's heading is
|
|
6
|
+
* `jobCardLabel`: "Tree removal in Castle Hill" for the page's own suburb, "Tree removal nearby: Ryde" for any other
|
|
7
|
+
* (L4); only a job whose month is known says "Recent" (or `labelPrefix`) and ends ", {Month Year}". Distances rank the cards and are never
|
|
8
|
+
* printed (L10). A card shows the job's first photo through `Media`, and only a genuine photo of that job: a stock or AI
|
|
9
|
+
* image never stands for the client's work. With no job close enough the section renders nothing and its guard says
|
|
10
|
+
* so (`no-jobs-nearby`, D2). A card links to the job's project page when the site has one (prefetch on intent).
|
|
11
|
+
* Card photos are always lazy. `pagePhotos` names the photos the page already shows (the hero's): a card then takes the
|
|
12
|
+
* job's next genuine photo, or none, so the hero's priority photo is never repeated below as a lazy copy (3.15.0; on
|
|
13
|
+
* AA's suburb pages the repeat made Next report the hero, the LCP image, as lazy and downloaded it twice).
|
|
14
|
+
*/
|
|
15
|
+
import type { ReactNode } from 'react';
|
|
16
|
+
import { type BrandlabJob, type BrandlabNearbyJob } from '@brandlab-digital/site-core/jobs';
|
|
17
|
+
import type { BrandlabMediaManifest } from '@brandlab-digital/site-core/media';
|
|
18
|
+
import { type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order';
|
|
19
|
+
import { type MediaLayout } from '../primitives/media.js';
|
|
20
|
+
import { type SectionHeadingSlot, type SectionSlot, type SectionTone } from '../primitives/layout.js';
|
|
21
|
+
import type { RepeatedSlotOf } from '../repeated-slots.js';
|
|
22
|
+
import { type SkinEntry } from '../skin.js';
|
|
23
|
+
export declare const NEARBY_JOBS_VARIANTS: readonly ["grid", "rail"];
|
|
24
|
+
export type NearbyJobsVariant = (typeof NEARBY_JOBS_VARIANTS)[number];
|
|
25
|
+
export type NearbyJobsSlot = SectionSlot | SectionHeadingSlot | 'list' | RepeatedSlotOf<'nearbyJobs'>;
|
|
26
|
+
/** Rendered once per job: styled from the skin stylesheet, never by class. */
|
|
27
|
+
export type NearbyJobsRepeatedSlot = RepeatedSlotOf<'nearbyJobs'>;
|
|
28
|
+
export interface NearbyJobsLayout {
|
|
29
|
+
/** `grid`: columns on wide screens. Defaults to 3. */
|
|
30
|
+
readonly columns?: 2 | 3;
|
|
31
|
+
/** The photo's crop. Defaults to 4:3. */
|
|
32
|
+
readonly ratio?: NonNullable<MediaLayout['ratio']>;
|
|
33
|
+
}
|
|
34
|
+
export type NearbyJobsSkin = SkinEntry<NearbyJobsSlot, NearbyJobsVariant, NearbyJobsLayout, NearbyJobsRepeatedSlot>;
|
|
35
|
+
export interface NearbyJobsProps extends NearbyJobsSkin {
|
|
36
|
+
/** The H2, for example "Recent tree work near Castle Hill". */
|
|
37
|
+
readonly title: ReactNode;
|
|
38
|
+
readonly eyebrow?: ReactNode;
|
|
39
|
+
readonly lead?: ReactNode;
|
|
40
|
+
/** The ranked cards from `nearbyJobs()`, nearest first; at most three. */
|
|
41
|
+
readonly jobs: readonly BrandlabNearbyJob[];
|
|
42
|
+
/** The page's suburb slug: its own jobs read "in {Suburb}", every other "nearby: {Suburb}". */
|
|
43
|
+
readonly pageSuburb: string;
|
|
44
|
+
/** The service's name from the site's catalogue, for example `(slug) => services[slug].name`. */
|
|
45
|
+
readonly serviceName: (service: string) => string;
|
|
46
|
+
/** The suburb's name from the registry. */
|
|
47
|
+
readonly suburbName: (suburb: string) => string;
|
|
48
|
+
/** Replaces "Recent" on a dated card ("Completed tree removal …"); `''` for none. An undated card never has one. */
|
|
49
|
+
readonly labelPrefix?: string;
|
|
50
|
+
/** The media manifest, for the jobs' photos. Left out, the cards show no photo. */
|
|
51
|
+
readonly media?: BrandlabMediaManifest;
|
|
52
|
+
/** Photos the page already shows, such as the hero's: media ids or `src` paths. A card never repeats one. */
|
|
53
|
+
readonly pagePhotos?: readonly string[];
|
|
54
|
+
/** The job's project page, when the site has one. */
|
|
55
|
+
readonly jobHref?: (job: BrandlabJob) => string | undefined;
|
|
56
|
+
/** The photo `sizes`. Defaults to one column of the grid. */
|
|
57
|
+
readonly imageSizes?: string;
|
|
58
|
+
/** Defaults to `nearby-jobs-heading`. */
|
|
59
|
+
readonly headingId?: string;
|
|
60
|
+
/** Set by `SectionSequence`. */
|
|
61
|
+
readonly tone?: SectionTone;
|
|
62
|
+
}
|
|
63
|
+
/** The guard for `SectionSequence`: no card means no section (D2). */
|
|
64
|
+
export declare function nearbyJobsReady({ jobs }: Pick<NearbyJobsProps, 'jobs'>): BrandlabSectionReadiness;
|
|
65
|
+
export declare function NearbyJobs({ title, eyebrow, lead, jobs, pageSuburb, serviceName, suburbName, labelPrefix, media, pagePhotos, jobHref, imageSizes, headingId, tone, variant, layout, classes, }: NearbyJobsProps): import("react/jsx-runtime").JSX.Element | null;
|
|
66
|
+
//# sourceMappingURL=nearby-jobs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nearby-jobs.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/nearby-jobs.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { JOB_CARD_LIMIT, jobCardLabel, jobPhotos } from '@brandlab-digital/site-core/jobs';
|
|
3
|
+
import { SECTION_READY } from '@brandlab-digital/site-core/section-order';
|
|
4
|
+
import { LINK_PREFETCH_POLICY } from '../prefetch.js';
|
|
5
|
+
import { Anchor } from '../primitives/links.js';
|
|
6
|
+
import { Media } from '../primitives/media.js';
|
|
7
|
+
import { Section, SectionHeading } from '../primitives/layout.js';
|
|
8
|
+
import { repeatedSlot, slot } from '../skin.js';
|
|
9
|
+
export const NEARBY_JOBS_VARIANTS = ['grid', 'rail'];
|
|
10
|
+
/** The guard for `SectionSequence`: no card means no section (D2). */
|
|
11
|
+
export function nearbyJobsReady({ jobs }) {
|
|
12
|
+
return jobs.length > 0 ? SECTION_READY : { render: false, reason: 'no-jobs-nearby' };
|
|
13
|
+
}
|
|
14
|
+
function photoOf(job, media, shown) {
|
|
15
|
+
if (!media)
|
|
16
|
+
return undefined;
|
|
17
|
+
const photo = jobPhotos(job, media).find((entry) => !shown.has(entry.id) && !shown.has(entry.src));
|
|
18
|
+
if (!photo)
|
|
19
|
+
return undefined;
|
|
20
|
+
if (photo.provenance !== 'genuine') {
|
|
21
|
+
throw new Error(`NearbyJobs: "${photo.id}" is ${photo.provenance}; only a genuine photo of the job shows on its card.`);
|
|
22
|
+
}
|
|
23
|
+
if (photo.job !== undefined && photo.job !== job.id)
|
|
24
|
+
throw new Error(`NearbyJobs: "${photo.id}" is a photo of ${photo.job}, not ${job.id}.`);
|
|
25
|
+
return photo;
|
|
26
|
+
}
|
|
27
|
+
export function NearbyJobs({ title, eyebrow, lead, jobs, pageSuburb, serviceName, suburbName, labelPrefix, media, pagePhotos, jobHref, imageSizes, headingId = 'nearby-jobs-heading', tone, variant = 'grid', layout, classes, }) {
|
|
28
|
+
if (jobs.length === 0)
|
|
29
|
+
return null;
|
|
30
|
+
if (jobs.length > JOB_CARD_LIMIT)
|
|
31
|
+
throw new Error(`NearbyJobs: ${jobs.length} jobs; a page shows at most ${JOB_CARD_LIMIT} job cards.`);
|
|
32
|
+
const ids = new Set();
|
|
33
|
+
for (const { job } of jobs) {
|
|
34
|
+
if (ids.has(job.id))
|
|
35
|
+
throw new Error(`NearbyJobs: "${job.id}" is listed twice.`);
|
|
36
|
+
ids.add(job.id);
|
|
37
|
+
}
|
|
38
|
+
const shown = new Set(pagePhotos);
|
|
39
|
+
const columns = layout?.columns ?? 3;
|
|
40
|
+
const ratio = layout?.ratio ?? '4:3';
|
|
41
|
+
const sizes = imageSizes ?? `(min-width: 1024px) ${Math.round(100 / columns)}vw, (min-width: 640px) 50vw, 100vw`;
|
|
42
|
+
return (_jsxs(Section, { id: "nearby-jobs", labelledBy: headingId, tone: tone, variant: variant, classes: classes, hooks: { 'data-columns': String(columns), 'data-ratio': ratio }, children: [_jsx(SectionHeading, { id: headingId, title: title, eyebrow: eyebrow, lead: lead, classes: classes }), _jsx("ul", { role: "list", ...slot(classes, 'list'), children: jobs.map(({ job }) => {
|
|
43
|
+
const label = jobCardLabel(job, { service: serviceName(job.service), suburbName: suburbName(job.suburb), pageSuburb, ...(labelPrefix === undefined ? {} : { prefix: labelPrefix }) });
|
|
44
|
+
const href = jobHref?.(job);
|
|
45
|
+
const photo = photoOf(job, media, shown);
|
|
46
|
+
return (_jsxs("li", { ...repeatedSlot('item'), "data-item": job.id, "data-suburb": job.suburb, children: [_jsxs("div", { ...repeatedSlot('body'), children: [_jsx("h3", { ...repeatedSlot('title'), children: href ? (_jsx(Anchor, { href: href, prefetch: LINK_PREFETCH_POLICY.contentCard, hooks: repeatedSlot('link'), children: label })) : (label) }), _jsx("p", { ...repeatedSlot('outcome'), children: job.outcome })] }), photo ? (_jsx("div", { ...repeatedSlot('media'), children: _jsx(Media, { media: photo, sizes: sizes, layout: { ratio } }) })) : null] }, job.id));
|
|
47
|
+
}) })] }));
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=nearby-jobs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nearby-jobs.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/nearby-jobs.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `NearbyJobs` (S17, `nearby-jobs`, 3.15.0): proof of real work near this place. Server component.\n *\n * The cards come from the job log (site-core `./jobs`): `nearbyJobs(origin, jobs, { centreOf, service })` ranks the\n * page suburb's own jobs first, then others by real distance within 10 km, at most three. Each card's heading is\n * `jobCardLabel`: \"Tree removal in Castle Hill\" for the page's own suburb, \"Tree removal nearby: Ryde\" for any other\n * (L4); only a job whose month is known says \"Recent\" (or `labelPrefix`) and ends \", {Month Year}\". Distances rank the cards and are never\n * printed (L10). A card shows the job's first photo through `Media`, and only a genuine photo of that job: a stock or AI\n * image never stands for the client's work. With no job close enough the section renders nothing and its guard says\n * so (`no-jobs-nearby`, D2). A card links to the job's project page when the site has one (prefetch on intent).\n * Card photos are always lazy. `pagePhotos` names the photos the page already shows (the hero's): a card then takes the\n * job's next genuine photo, or none, so the hero's priority photo is never repeated below as a lazy copy (3.15.0; on\n * AA's suburb pages the repeat made Next report the hero, the LCP image, as lazy and downloaded it twice).\n */\nimport type { ReactNode } from 'react'\nimport { JOB_CARD_LIMIT, jobCardLabel, jobPhotos, type BrandlabJob, type BrandlabNearbyJob } from '@brandlab-digital/site-core/jobs'\nimport type { BrandlabMediaManifest } from '@brandlab-digital/site-core/media'\nimport { SECTION_READY, type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order'\nimport { LINK_PREFETCH_POLICY } from '../prefetch.js'\nimport { Anchor } from '../primitives/links.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'\n\nexport const NEARBY_JOBS_VARIANTS = ['grid', 'rail'] as const\nexport type NearbyJobsVariant = (typeof NEARBY_JOBS_VARIANTS)[number]\nexport type NearbyJobsSlot = SectionSlot | SectionHeadingSlot | 'list' | RepeatedSlotOf<'nearbyJobs'>\n/** Rendered once per job: styled from the skin stylesheet, never by class. */\nexport type NearbyJobsRepeatedSlot = RepeatedSlotOf<'nearbyJobs'>\nexport interface NearbyJobsLayout {\n /** `grid`: columns on wide screens. Defaults to 3. */\n readonly columns?: 2 | 3\n /** The photo's crop. Defaults to 4:3. */\n readonly ratio?: NonNullable<MediaLayout['ratio']>\n}\nexport type NearbyJobsSkin = SkinEntry<NearbyJobsSlot, NearbyJobsVariant, NearbyJobsLayout, NearbyJobsRepeatedSlot>\n\nexport interface NearbyJobsProps extends NearbyJobsSkin {\n /** The H2, for example \"Recent tree work near Castle Hill\". */\n readonly title: ReactNode\n readonly eyebrow?: ReactNode\n readonly lead?: ReactNode\n /** The ranked cards from `nearbyJobs()`, nearest first; at most three. */\n readonly jobs: readonly BrandlabNearbyJob[]\n /** The page's suburb slug: its own jobs read \"in {Suburb}\", every other \"nearby: {Suburb}\". */\n readonly pageSuburb: string\n /** The service's name from the site's catalogue, for example `(slug) => services[slug].name`. */\n readonly serviceName: (service: string) => string\n /** The suburb's name from the registry. */\n readonly suburbName: (suburb: string) => string\n /** Replaces \"Recent\" on a dated card (\"Completed tree removal …\"); `''` for none. An undated card never has one. */\n readonly labelPrefix?: string\n /** The media manifest, for the jobs' photos. Left out, the cards show no photo. */\n readonly media?: BrandlabMediaManifest\n /** Photos the page already shows, such as the hero's: media ids or `src` paths. A card never repeats one. */\n readonly pagePhotos?: readonly string[]\n /** The job's project page, when the site has one. */\n readonly jobHref?: (job: BrandlabJob) => string | undefined\n /** The photo `sizes`. Defaults to one column of the grid. */\n readonly imageSizes?: string\n /** Defaults to `nearby-jobs-heading`. */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n}\n\n/** The guard for `SectionSequence`: no card means no section (D2). */\nexport function nearbyJobsReady({ jobs }: Pick<NearbyJobsProps, 'jobs'>): BrandlabSectionReadiness {\n return jobs.length > 0 ? SECTION_READY : { render: false, reason: 'no-jobs-nearby' }\n}\n\nfunction photoOf(job: BrandlabJob, media: BrandlabMediaManifest | undefined, shown: ReadonlySet<string>) {\n if (!media) return undefined\n const photo = jobPhotos(job, media).find((entry) => !shown.has(entry.id) && !shown.has(entry.src))\n if (!photo) return undefined\n if (photo.provenance !== 'genuine') {\n throw new Error(`NearbyJobs: \"${photo.id}\" is ${photo.provenance}; only a genuine photo of the job shows on its card.`)\n }\n if (photo.job !== undefined && photo.job !== job.id) throw new Error(`NearbyJobs: \"${photo.id}\" is a photo of ${photo.job}, not ${job.id}.`)\n return photo\n}\n\nexport function NearbyJobs({\n title,\n eyebrow,\n lead,\n jobs,\n pageSuburb,\n serviceName,\n suburbName,\n labelPrefix,\n media,\n pagePhotos,\n jobHref,\n imageSizes,\n headingId = 'nearby-jobs-heading',\n tone,\n variant = 'grid',\n layout,\n classes,\n}: NearbyJobsProps) {\n if (jobs.length === 0) return null\n if (jobs.length > JOB_CARD_LIMIT) throw new Error(`NearbyJobs: ${jobs.length} jobs; a page shows at most ${JOB_CARD_LIMIT} job cards.`)\n const ids = new Set<string>()\n for (const { job } of jobs) {\n if (ids.has(job.id)) throw new Error(`NearbyJobs: \"${job.id}\" is listed twice.`)\n ids.add(job.id)\n }\n const shown = new Set(pagePhotos)\n const columns = layout?.columns ?? 3\n const ratio = layout?.ratio ?? '4:3'\n const sizes = imageSizes ?? `(min-width: 1024px) ${Math.round(100 / columns)}vw, (min-width: 640px) 50vw, 100vw`\n return (\n <Section id=\"nearby-jobs\" labelledBy={headingId} tone={tone} variant={variant} classes={classes} hooks={{ 'data-columns': String(columns), 'data-ratio': ratio }}>\n <SectionHeading id={headingId} title={title} eyebrow={eyebrow} lead={lead} classes={classes} />\n <ul role=\"list\" {...slot(classes, 'list')}>\n {jobs.map(({ job }) => {\n const label = jobCardLabel(job, { service: serviceName(job.service), suburbName: suburbName(job.suburb), pageSuburb, ...(labelPrefix === undefined ? {} : { prefix: labelPrefix }) })\n const href = jobHref?.(job)\n const photo = photoOf(job, media, shown)\n return (\n <li key={job.id} {...repeatedSlot('item')} data-item={job.id} data-suburb={job.suburb}>\n <div {...repeatedSlot('body')}>\n <h3 {...repeatedSlot('title')}>\n {href ? (\n <Anchor href={href} prefetch={LINK_PREFETCH_POLICY.contentCard} hooks={repeatedSlot('link')}>\n {label}\n </Anchor>\n ) : (\n label\n )}\n </h3>\n <p {...repeatedSlot('outcome')}>{job.outcome}</p>\n </div>\n {photo ? (\n <div {...repeatedSlot('media')}>\n <Media media={photo} sizes={sizes} layout={{ ratio }} />\n </div>\n ) : null}\n </li>\n )\n })}\n </ul>\n </Section>\n )\n}\n"]}
|
|
@@ -11,13 +11,16 @@ import type { ReactNode } from 'react';
|
|
|
11
11
|
import { type BrandlabPrice } from '@brandlab-digital/site-core/prices';
|
|
12
12
|
import { type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order';
|
|
13
13
|
import { type SectionHeadingSlot, type SectionSlot, type SectionTone } from '../primitives/layout.js';
|
|
14
|
+
import type { RepeatedSlotOf } from '../repeated-slots.js';
|
|
14
15
|
import { type SkinEntry } from '../skin.js';
|
|
15
16
|
import type { UiLink } from '../types.js';
|
|
16
17
|
import { type PriceLabels, type PriceTableSlot } from './price-table.js';
|
|
17
18
|
export declare const PRICE_GUIDE_VARIANTS: readonly ["table", "cards", "bands"];
|
|
18
19
|
export type PriceGuideVariant = (typeof PRICE_GUIDE_VARIANTS)[number];
|
|
19
20
|
export type PriceGuideSlot = SectionSlot | SectionHeadingSlot | Exclude<PriceTableSlot, 'root'> | 'cards' | 'card' | 'title' | 'figures' | 'bands' | 'band' | 'factors' | 'subheading' | 'factor' | 'name' | 'effect' | 'detail' | 'more' | 'link';
|
|
20
|
-
|
|
21
|
+
/** Rendered once per price row, card, band or factor: styled from the skin stylesheet, never by class (3.14.0). */
|
|
22
|
+
export type PriceGuideRepeatedSlot = RepeatedSlotOf<'priceGuide'>;
|
|
23
|
+
export type PriceGuideSkin = SkinEntry<PriceGuideSlot, PriceGuideVariant, Record<never, never>, PriceGuideRepeatedSlot>;
|
|
21
24
|
/** Something that moves the price. `effect` only when the owner supplied the dollar figure. */
|
|
22
25
|
export interface PriceFactor {
|
|
23
26
|
readonly label: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"price-guide.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/price-guide.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAoD,KAAK,aAAa,EAAE,MAAM,oCAAoC,CAAA;AACzH,OAAO,EAAiB,KAAK,wBAAwB,EAAE,MAAM,2CAA2C,CAAA;AAExG,OAAO,EAA2B,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC9H,OAAO,
|
|
1
|
+
{"version":3,"file":"price-guide.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/price-guide.tsx"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { PRICE_CAPTION, formatPriceBand, renderablePrices } from '@brandlab-digi
|
|
|
3
3
|
import { SECTION_READY } from '@brandlab-digital/site-core/section-order';
|
|
4
4
|
import { TextLink } from '../primitives/links.js';
|
|
5
5
|
import { Section, SectionHeading } from '../primitives/layout.js';
|
|
6
|
-
import { slot } from '../skin.js';
|
|
6
|
+
import { repeatedSlot, slot } from '../skin.js';
|
|
7
7
|
import { PriceFigures, PriceScope, PriceTable, assertPriceWording } from './price-table.js';
|
|
8
8
|
export const PRICE_GUIDE_VARIANTS = ['table', 'cards', 'bands'];
|
|
9
9
|
/** Whether the section renders: nothing without a renderable band (`no-approved-prices`). */
|
|
@@ -20,9 +20,9 @@ export function PriceGuide({ title, eyebrow, lead, prices, service, labels, capt
|
|
|
20
20
|
body = _jsx(PriceTable, { prices: rows, labels: labels, caption: caption, classes: { ...classes, root: undefined } });
|
|
21
21
|
}
|
|
22
22
|
else {
|
|
23
|
-
const list = (row) => variant === 'cards' ? (_jsxs("li", { ...
|
|
23
|
+
const list = (row) => variant === 'cards' ? (_jsxs("li", { ...repeatedSlot('card'), children: [_jsx("h3", { ...repeatedSlot('title'), children: row.job }), _jsx("div", { ...repeatedSlot('figures'), children: _jsx(PriceFigures, { price: row, labels: labels }) }), _jsx(PriceScope, { price: row, labels: labels })] }, row.job)) : (_jsxs("li", { ...repeatedSlot('band'), children: [_jsx("span", { ...repeatedSlot('job'), children: row.job }), ' ', _jsx("div", { ...repeatedSlot('figures'), children: _jsx(PriceFigures, { price: row, labels: labels }) }), _jsx(PriceScope, { price: row, labels: labels })] }, row.job));
|
|
24
24
|
body = (_jsxs(_Fragment, { children: [_jsx("p", { ...slot(classes, 'caption'), children: caption }), _jsx("ul", { role: "list", ...slot(classes, variant), children: rows.map(list) })] }));
|
|
25
25
|
}
|
|
26
|
-
return (_jsxs(Section, { id: "price-guide", labelledBy: headingId, tone: tone, variant: variant, classes: classes, children: [_jsx(SectionHeading, { id: headingId, title: title, eyebrow: eyebrow, lead: lead, classes: classes }), body, factors && factors.items.length > 0 ? (_jsxs("div", { ...slot(classes, 'factors'), children: [_jsx("h3", { ...slot(classes, 'subheading'), children: factors.title }), _jsx("ul", { children: factors.items.map((factor) => (_jsxs("li", { ...
|
|
26
|
+
return (_jsxs(Section, { id: "price-guide", labelledBy: headingId, tone: tone, variant: variant, classes: classes, children: [_jsx(SectionHeading, { id: headingId, title: title, eyebrow: eyebrow, lead: lead, classes: classes }), body, factors && factors.items.length > 0 ? (_jsxs("div", { ...slot(classes, 'factors'), children: [_jsx("h3", { ...slot(classes, 'subheading'), children: factors.title }), _jsx("ul", { children: factors.items.map((factor) => (_jsxs("li", { ...repeatedSlot('factor'), children: [_jsx("span", { ...repeatedSlot('name'), children: factor.label }), factor.effect ? (_jsxs(_Fragment, { children: [' ', _jsx("span", { ...repeatedSlot('effect'), children: formatPriceBand(factor.effect) })] })) : null, factor.detail ? _jsx("p", { ...repeatedSlot('detail'), children: factor.detail }) : null] }, factor.label))) })] })) : null, costGuide ? (_jsx("p", { ...slot(classes, 'more'), children: _jsx(TextLink, { href: costGuide.href, variant: "standalone", classes: { root: classes?.link }, children: costGuide.label }) })) : null] }));
|
|
27
27
|
}
|
|
28
28
|
//# sourceMappingURL=price-guide.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"price-guide.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/price-guide.tsx"],"names":[],"mappings":";AAUA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,gBAAgB,EAAsB,MAAM,oCAAoC,CAAA;AACzH,OAAO,EAAE,aAAa,EAAiC,MAAM,2CAA2C,CAAA;AACxG,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,cAAc,EAA+D,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"price-guide.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/price-guide.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `PriceGuide` (S19, `price-guide`): \"Typical prices\", which every service page carries. Server component. Bands come\n * from `prices.ts` in a renderable status only; with none the section renders nothing and reports\n * `no-approved-prices`, and the page links to its cost guide instead. Every variant shows the full price with its GST\n * flag, part prices only beside it, never a \"from $X\" teaser. What moves the price shows a dollar effect only where\n * the owner supplied one.\n *\n * Variants: `table` (`PriceTable`), `cards` (one card per job) or `bands` (a compact list).\n */\nimport type { ReactNode } from 'react'\nimport { PRICE_CAPTION, formatPriceBand, renderablePrices, type BrandlabPrice } from '@brandlab-digital/site-core/prices'\nimport { SECTION_READY, type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order'\nimport { TextLink } from '../primitives/links.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'\nimport { PriceFigures, PriceScope, PriceTable, assertPriceWording, type PriceLabels, type PriceTableSlot } from './price-table.js'\n\nexport const PRICE_GUIDE_VARIANTS = ['table', 'cards', 'bands'] as const\nexport type PriceGuideVariant = (typeof PRICE_GUIDE_VARIANTS)[number]\nexport type PriceGuideSlot =\n | SectionSlot\n | SectionHeadingSlot\n | Exclude<PriceTableSlot, 'root'>\n | 'cards'\n | 'card'\n | 'title'\n | 'figures'\n | 'bands'\n | 'band'\n | 'factors'\n | 'subheading'\n | 'factor'\n | 'name'\n | 'effect'\n | 'detail'\n | 'more'\n | 'link'\n/** Rendered once per price row, card, band or factor: styled from the skin stylesheet, never by class (3.14.0). */\nexport type PriceGuideRepeatedSlot = RepeatedSlotOf<'priceGuide'>\nexport type PriceGuideSkin = SkinEntry<PriceGuideSlot, PriceGuideVariant, Record<never, never>, PriceGuideRepeatedSlot>\n\n/** Something that moves the price. `effect` only when the owner supplied the dollar figure. */\nexport interface PriceFactor {\n readonly label: string\n readonly detail?: string\n readonly effect?: { readonly low: number; readonly high?: number }\n}\n\nexport interface PriceGuideProps extends PriceGuideSkin {\n /** The H2. */\n readonly title: ReactNode\n readonly eyebrow?: ReactNode\n readonly lead?: ReactNode\n readonly prices: readonly BrandlabPrice[]\n /** Only this service's bands, when `prices` holds the whole file. */\n readonly service?: string\n readonly labels: PriceLabels\n /** Defaults to \"Typical prices\"; otherwise only \"Typical {service} prices\" for `service`, or a rendered row's service. */\n readonly caption?: string\n /** What moves the price, under its own H3. */\n readonly factors?: { readonly title: ReactNode; readonly items: readonly PriceFactor[] }\n /** The service's cost guide. */\n readonly costGuide?: UiLink\n /** Defaults to `price-guide-heading`. */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n}\n\n/** Whether the section renders: nothing without a renderable band (`no-approved-prices`). */\nexport function priceGuideReady({ prices, service }: Pick<PriceGuideProps, 'prices' | 'service'>): BrandlabSectionReadiness {\n return renderablePrices(prices, service).length > 0 ? SECTION_READY : { render: false, reason: 'no-approved-prices' }\n}\n\nexport function PriceGuide({\n title,\n eyebrow,\n lead,\n prices,\n service,\n labels,\n caption = PRICE_CAPTION,\n factors,\n costGuide,\n headingId = 'price-guide-heading',\n tone,\n variant = 'table',\n classes,\n}: PriceGuideProps) {\n const rows = renderablePrices(prices, service)\n assertPriceWording(caption, labels, service ? [service] : rows.map((row) => row.service))\n if (rows.length === 0) return null\n let body: ReactNode\n if (variant === 'table') {\n body = <PriceTable prices={rows} labels={labels} caption={caption} classes={{ ...classes, root: undefined }} />\n } else {\n const list = (row: BrandlabPrice) =>\n variant === 'cards' ? (\n <li key={row.job} {...repeatedSlot('card')}>\n <h3 {...repeatedSlot('title')}>{row.job}</h3>\n <div {...repeatedSlot('figures')}>\n <PriceFigures price={row} labels={labels} />\n </div>\n <PriceScope price={row} labels={labels} />\n </li>\n ) : (\n <li key={row.job} {...repeatedSlot('band')}>\n <span {...repeatedSlot('job')}>{row.job}</span>{' '}\n <div {...repeatedSlot('figures')}>\n <PriceFigures price={row} labels={labels} />\n </div>\n <PriceScope price={row} labels={labels} />\n </li>\n )\n body = (\n <>\n <p {...slot(classes, 'caption')}>{caption}</p>\n <ul role=\"list\" {...slot(classes, variant)}>\n {rows.map(list)}\n </ul>\n </>\n )\n }\n return (\n <Section id=\"price-guide\" labelledBy={headingId} tone={tone} variant={variant} classes={classes}>\n <SectionHeading id={headingId} title={title} eyebrow={eyebrow} lead={lead} classes={classes} />\n {body}\n {factors && factors.items.length > 0 ? (\n <div {...slot(classes, 'factors')}>\n <h3 {...slot(classes, 'subheading')}>{factors.title}</h3>\n <ul>\n {factors.items.map((factor) => (\n <li key={factor.label} {...repeatedSlot('factor')}>\n <span {...repeatedSlot('name')}>{factor.label}</span>\n {factor.effect ? (\n <>\n {' '}\n <span {...repeatedSlot('effect')}>{formatPriceBand(factor.effect)}</span>\n </>\n ) : null}\n {factor.detail ? <p {...repeatedSlot('detail')}>{factor.detail}</p> : null}\n </li>\n ))}\n </ul>\n </div>\n ) : null}\n {costGuide ? (\n <p {...slot(classes, 'more')}>\n <TextLink href={costGuide.href} variant=\"standalone\" classes={{ root: classes?.link }}>\n {costGuide.label}\n </TextLink>\n </p>\n ) : null}\n </Section>\n )\n}\n"]}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { type BrandlabPrice } from '@brandlab-digital/site-core/prices';
|
|
2
|
-
import
|
|
2
|
+
import type { RepeatedSlotOf } from '../repeated-slots.js';
|
|
3
|
+
import { type SkinEntry } from '../skin.js';
|
|
3
4
|
export type PriceTableSlot = 'root' | 'table' | 'caption' | 'thead' | 'row' | 'job' | 'cell' | 'label' | 'range' | 'unit' | 'gst' | 'parts' | 'part' | 'includes' | 'excludes';
|
|
4
|
-
|
|
5
|
+
/** Rendered once per price row: styled from the skin stylesheet, never by class (3.14.0). */
|
|
6
|
+
export type PriceTableRepeatedSlot = RepeatedSlotOf<'priceTable'>;
|
|
7
|
+
export type PriceTableSkin = SkinEntry<PriceTableSlot, never, Record<never, never>, PriceTableRepeatedSlot>;
|
|
5
8
|
/** The words around the figures, in the site's Australian English. */
|
|
6
9
|
export interface PriceLabels {
|
|
7
10
|
/** The job column header, for example "Job". */
|
|
@@ -25,20 +28,15 @@ export interface PriceTableProps extends PriceTableSkin {
|
|
|
25
28
|
}
|
|
26
29
|
/** Throws when the caption (checked against the page's service slugs) or the GST labels break the price rules. */
|
|
27
30
|
export declare function assertPriceWording(caption: string, labels: PriceLabels, services: readonly string[]): void;
|
|
28
|
-
type FigureClasses = SlotClasses<'range' | 'unit' | 'gst' | 'parts' | 'part'>;
|
|
29
|
-
type ScopeClasses = SlotClasses<'includes' | 'excludes'>;
|
|
30
31
|
/** The full price, its unit and GST flag, then any part prices beside it. */
|
|
31
|
-
export declare function PriceFigures({ price, labels
|
|
32
|
+
export declare function PriceFigures({ price, labels }: {
|
|
32
33
|
price: BrandlabPrice;
|
|
33
34
|
labels: PriceLabels;
|
|
34
|
-
classes: FigureClasses | undefined;
|
|
35
35
|
}): import("react/jsx-runtime").JSX.Element;
|
|
36
36
|
/** What the price covers and leaves out. */
|
|
37
|
-
export declare function PriceScope({ price, labels
|
|
37
|
+
export declare function PriceScope({ price, labels }: {
|
|
38
38
|
price: BrandlabPrice;
|
|
39
39
|
labels: PriceLabels;
|
|
40
|
-
classes: ScopeClasses | undefined;
|
|
41
40
|
}): import("react/jsx-runtime").JSX.Element;
|
|
42
41
|
export declare function PriceTable({ prices, labels, caption, classes }: PriceTableProps): import("react/jsx-runtime").JSX.Element | null;
|
|
43
|
-
export {};
|
|
44
42
|
//# sourceMappingURL=price-table.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"price-table.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/price-table.tsx"],"names":[],"mappings":"AAWA,OAAO,EAML,KAAK,aAAa,EACnB,MAAM,oCAAoC,CAAA;AAC3C,OAAO,
|
|
1
|
+
{"version":3,"file":"price-table.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/price-table.tsx"],"names":[],"mappings":"AAWA,OAAO,EAML,KAAK,aAAa,EACnB,MAAM,oCAAoC,CAAA;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AAE/D,MAAM,MAAM,cAAc,GACtB,MAAM,GACN,OAAO,GACP,SAAS,GACT,OAAO,GACP,KAAK,GACL,KAAK,GACL,MAAM,GACN,OAAO,GACP,OAAO,GACP,MAAM,GACN,KAAK,GACL,OAAO,GACP,MAAM,GACN,UAAU,GACV,UAAU,CAAA;AACd,6FAA6F;AAC7F,MAAM,MAAM,sBAAsB,GAAG,cAAc,CAAC,YAAY,CAAC,CAAA;AACjE,MAAM,MAAM,cAAc,GAAG,SAAS,CAAC,cAAc,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,sBAAsB,CAAC,CAAA;AAE3G,sEAAsE;AACtE,MAAM,WAAW,WAAW;IAC1B,gDAAgD;IAChD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,0EAA0E;IAC1E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,0FAA0F;IAC1F,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAA;IACzC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,yGAAyG;IACzG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,kHAAkH;AAClH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,QAInG;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,2CAwB5F;AAED,4CAA4C;AAC5C,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,2CAiB1F;AAUD,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAuB,EAAE,OAAO,EAAE,EAAE,eAAe,kDA8C/F"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { PRICE_CAPTION, assertGstLabel, assertPriceCaption, formatPriceBand, renderablePrices, } from '@brandlab-digital/site-core/prices';
|
|
3
|
-
import { slot } from '../skin.js';
|
|
3
|
+
import { repeatedSlot, slot } from '../skin.js';
|
|
4
4
|
/** Throws when the caption (checked against the page's service slugs) or the GST labels break the price rules. */
|
|
5
5
|
export function assertPriceWording(caption, labels, services) {
|
|
6
6
|
assertPriceCaption(caption, services);
|
|
@@ -8,15 +8,15 @@ export function assertPriceWording(caption, labels, services) {
|
|
|
8
8
|
assertGstLabel(labels.gstExcluded, 'labels.gstExcluded');
|
|
9
9
|
}
|
|
10
10
|
/** The full price, its unit and GST flag, then any part prices beside it. */
|
|
11
|
-
export function PriceFigures({ price, labels
|
|
12
|
-
return (_jsxs(_Fragment, { children: [_jsx("span", { ...
|
|
11
|
+
export function PriceFigures({ price, labels }) {
|
|
12
|
+
return (_jsxs(_Fragment, { children: [_jsx("span", { ...repeatedSlot('range'), children: formatPriceBand(price) }), price.unit ? (_jsxs(_Fragment, { children: [' ', _jsx("span", { ...repeatedSlot('unit'), children: price.unit })] })) : null, ' ', _jsx("span", { ...repeatedSlot('gst'), "data-gst": price.gstIncluded ? 'included' : 'excluded', children: price.gstIncluded ? labels.gstIncluded : labels.gstExcluded }), price.parts.length > 0 ? (_jsx("ul", { ...repeatedSlot('parts'), children: price.parts.map((part) => (_jsxs("li", { ...repeatedSlot('part'), children: [part.label, " ", formatPriceBand(part)] }, part.label))) })) : null] }));
|
|
13
13
|
}
|
|
14
14
|
/** What the price covers and leaves out. */
|
|
15
|
-
export function PriceScope({ price, labels
|
|
16
|
-
return (_jsxs(_Fragment, { children: [price.includes.length > 0 ? (_jsx("ul", { ...
|
|
15
|
+
export function PriceScope({ price, labels }) {
|
|
16
|
+
return (_jsxs(_Fragment, { children: [price.includes.length > 0 ? (_jsx("ul", { ...repeatedSlot('includes'), children: price.includes.map((item) => (_jsx("li", { children: item }, item))) })) : null, price.excludes.length > 0 ? (_jsxs("p", { ...repeatedSlot('excludes'), children: [labels.excludes, ": ", price.excludes.join(', ')] })) : null] }));
|
|
17
17
|
}
|
|
18
|
-
function CellLabel({
|
|
19
|
-
return (_jsx("span", { ...
|
|
18
|
+
function CellLabel({ children }) {
|
|
19
|
+
return (_jsx("span", { ...repeatedSlot('label'), "aria-hidden": "true", children: children }));
|
|
20
20
|
}
|
|
21
21
|
export function PriceTable({ prices, labels, caption = PRICE_CAPTION, classes }) {
|
|
22
22
|
const rows = renderablePrices(prices);
|
|
@@ -24,6 +24,6 @@ export function PriceTable({ prices, labels, caption = PRICE_CAPTION, classes })
|
|
|
24
24
|
if (rows.length === 0)
|
|
25
25
|
return null;
|
|
26
26
|
const hasScope = rows.some((row) => row.includes.length > 0 || row.excludes.length > 0);
|
|
27
|
-
return (_jsx("div", { "data-bl": "price-table", className: classes?.root, children: _jsxs("table", { role: "table", ...slot(classes, 'table'), children: [_jsx("caption", { ...slot(classes, 'caption'), children: caption }), _jsx("thead", { role: "rowgroup", ...slot(classes, 'thead'), children: _jsxs("tr", { role: "row", children: [_jsx("th", { scope: "col", role: "columnheader", children: labels.job }), _jsx("th", { scope: "col", role: "columnheader", children: labels.price }), hasScope ? (_jsx("th", { scope: "col", role: "columnheader", children: labels.includes })) : null] }) }), _jsx("tbody", { role: "rowgroup", children: rows.map((row) => (_jsxs("tr", { role: "row", ...
|
|
27
|
+
return (_jsx("div", { "data-bl": "price-table", className: classes?.root, children: _jsxs("table", { role: "table", ...slot(classes, 'table'), children: [_jsx("caption", { ...slot(classes, 'caption'), children: caption }), _jsx("thead", { role: "rowgroup", ...slot(classes, 'thead'), children: _jsxs("tr", { role: "row", children: [_jsx("th", { scope: "col", role: "columnheader", children: labels.job }), _jsx("th", { scope: "col", role: "columnheader", children: labels.price }), hasScope ? (_jsx("th", { scope: "col", role: "columnheader", children: labels.includes })) : null] }) }), _jsx("tbody", { role: "rowgroup", children: rows.map((row) => (_jsxs("tr", { role: "row", ...repeatedSlot('row'), children: [_jsx("th", { scope: "row", role: "rowheader", ...repeatedSlot('job'), children: row.job }), _jsxs("td", { role: "cell", ...repeatedSlot('cell'), "data-column": "price", children: [_jsx(CellLabel, { children: labels.price }), _jsx(PriceFigures, { price: row, labels: labels })] }), hasScope ? (_jsxs("td", { role: "cell", ...repeatedSlot('cell'), "data-column": "includes", children: [row.includes.length > 0 || row.excludes.length > 0 ? _jsx(CellLabel, { children: labels.includes }) : null, _jsx(PriceScope, { price: row, labels: labels })] })) : null] }, `${row.service}:${row.job}`))) })] }) }));
|
|
28
28
|
}
|
|
29
29
|
//# sourceMappingURL=price-table.js.map
|