@brandlab-digital/site-next 3.10.0 → 3.11.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/dist/ui-kit/brandlab-skin.d.ts +22 -0
- 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/sections/accordion.d.ts +25 -0
- package/dist/ui-kit/sections/accordion.d.ts.map +1 -0
- package/dist/ui-kit/sections/accordion.js +14 -0
- package/dist/ui-kit/sections/accordion.js.map +1 -0
- package/dist/ui-kit/sections/contact-block.d.ts +51 -0
- package/dist/ui-kit/sections/contact-block.d.ts.map +1 -0
- package/dist/ui-kit/sections/contact-block.js +15 -0
- package/dist/ui-kit/sections/contact-block.js.map +1 -0
- package/dist/ui-kit/sections/cta-band.d.ts +59 -0
- package/dist/ui-kit/sections/cta-band.d.ts.map +1 -0
- package/dist/ui-kit/sections/cta-band.js +11 -0
- package/dist/ui-kit/sections/cta-band.js.map +1 -0
- package/dist/ui-kit/sections/faqs.d.ts +43 -0
- package/dist/ui-kit/sections/faqs.d.ts.map +1 -0
- package/dist/ui-kit/sections/faqs.js +40 -0
- package/dist/ui-kit/sections/faqs.js.map +1 -0
- package/dist/ui-kit/sections/index.d.ts +13 -0
- package/dist/ui-kit/sections/index.d.ts.map +1 -0
- package/dist/ui-kit/sections/index.js +13 -0
- package/dist/ui-kit/sections/index.js.map +1 -0
- package/dist/ui-kit/sections/page-header.d.ts +29 -0
- package/dist/ui-kit/sections/page-header.d.ts.map +1 -0
- package/dist/ui-kit/sections/page-header.js +9 -0
- package/dist/ui-kit/sections/page-header.js.map +1 -0
- package/dist/ui-kit/sections/price-guide.d.ts +56 -0
- package/dist/ui-kit/sections/price-guide.d.ts.map +1 -0
- package/dist/ui-kit/sections/price-guide.js +28 -0
- package/dist/ui-kit/sections/price-guide.js.map +1 -0
- package/dist/ui-kit/sections/price-table.d.ts +44 -0
- package/dist/ui-kit/sections/price-table.d.ts.map +1 -0
- package/dist/ui-kit/sections/price-table.js +29 -0
- package/dist/ui-kit/sections/price-table.js.map +1 -0
- package/dist/ui-kit/sections/proof-strip.d.ts +57 -0
- package/dist/ui-kit/sections/proof-strip.d.ts.map +1 -0
- package/dist/ui-kit/sections/proof-strip.js +20 -0
- package/dist/ui-kit/sections/proof-strip.js.map +1 -0
- package/dist/ui-kit/sections/rating-chip.d.ts +25 -0
- package/dist/ui-kit/sections/rating-chip.d.ts.map +1 -0
- package/dist/ui-kit/sections/rating-chip.js +15 -0
- package/dist/ui-kit/sections/rating-chip.js.map +1 -0
- package/dist/ui-kit/sections/reviews-band.d.ts +110 -0
- package/dist/ui-kit/sections/reviews-band.d.ts.map +1 -0
- package/dist/ui-kit/sections/reviews-band.js +104 -0
- package/dist/ui-kit/sections/reviews-band.js.map +1 -0
- package/dist/ui-kit/sections/section-sequence.d.ts +39 -0
- package/dist/ui-kit/sections/section-sequence.d.ts.map +1 -0
- package/dist/ui-kit/sections/section-sequence.js +34 -0
- package/dist/ui-kit/sections/section-sequence.js.map +1 -0
- package/dist/ui-kit/sections/site-footer-columns.d.ts +12 -0
- package/dist/ui-kit/sections/site-footer-columns.d.ts.map +1 -0
- package/dist/ui-kit/sections/site-footer-columns.js +63 -0
- package/dist/ui-kit/sections/site-footer-columns.js.map +1 -0
- package/dist/ui-kit/sections/site-footer.d.ts +59 -0
- package/dist/ui-kit/sections/site-footer.d.ts.map +1 -0
- package/dist/ui-kit/sections/site-footer.js +41 -0
- package/dist/ui-kit/sections/site-footer.js.map +1 -0
- package/dist/ui-kit/structure.css +569 -0
- package/package.json +6 -2
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `PriceGuide` (S19, `price-guide`): "Typical prices", which every service page carries. Server component. Bands come
|
|
3
|
+
* from `prices.ts` in a renderable status only; with none the section renders nothing and reports
|
|
4
|
+
* `no-approved-prices`, and the page links to its cost guide instead. Every variant shows the full price with its GST
|
|
5
|
+
* flag, part prices only beside it, never a "from $X" teaser. What moves the price shows a dollar effect only where
|
|
6
|
+
* the owner supplied one.
|
|
7
|
+
*
|
|
8
|
+
* Variants: `table` (`PriceTable`), `cards` (one card per job) or `bands` (a compact list).
|
|
9
|
+
*/
|
|
10
|
+
import type { ReactNode } from 'react';
|
|
11
|
+
import { type BrandlabPrice } from '@brandlab-digital/site-core/prices';
|
|
12
|
+
import { type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order';
|
|
13
|
+
import { type SectionHeadingSlot, type SectionSlot, type SectionTone } from '../primitives/layout.js';
|
|
14
|
+
import { type SkinEntry } from '../skin.js';
|
|
15
|
+
import type { UiLink } from '../types.js';
|
|
16
|
+
import { type PriceLabels, type PriceTableSlot } from './price-table.js';
|
|
17
|
+
export declare const PRICE_GUIDE_VARIANTS: readonly ["table", "cards", "bands"];
|
|
18
|
+
export type PriceGuideVariant = (typeof PRICE_GUIDE_VARIANTS)[number];
|
|
19
|
+
export type PriceGuideSlot = SectionSlot | SectionHeadingSlot | Exclude<PriceTableSlot, 'root'> | 'cards' | 'card' | 'title' | 'figures' | 'bands' | 'band' | 'factors' | 'subheading' | 'factor' | 'name' | 'effect' | 'detail' | 'more' | 'link';
|
|
20
|
+
export type PriceGuideSkin = SkinEntry<PriceGuideSlot, PriceGuideVariant>;
|
|
21
|
+
/** Something that moves the price. `effect` only when the owner supplied the dollar figure. */
|
|
22
|
+
export interface PriceFactor {
|
|
23
|
+
readonly label: string;
|
|
24
|
+
readonly detail?: string;
|
|
25
|
+
readonly effect?: {
|
|
26
|
+
readonly low: number;
|
|
27
|
+
readonly high?: number;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface PriceGuideProps extends PriceGuideSkin {
|
|
31
|
+
/** The H2. */
|
|
32
|
+
readonly title: ReactNode;
|
|
33
|
+
readonly eyebrow?: ReactNode;
|
|
34
|
+
readonly lead?: ReactNode;
|
|
35
|
+
readonly prices: readonly BrandlabPrice[];
|
|
36
|
+
/** Only this service's bands, when `prices` holds the whole file. */
|
|
37
|
+
readonly service?: string;
|
|
38
|
+
readonly labels: PriceLabels;
|
|
39
|
+
/** Defaults to "Typical prices"; otherwise only "Typical {service} prices" for `service`, or a rendered row's service. */
|
|
40
|
+
readonly caption?: string;
|
|
41
|
+
/** What moves the price, under its own H3. */
|
|
42
|
+
readonly factors?: {
|
|
43
|
+
readonly title: ReactNode;
|
|
44
|
+
readonly items: readonly PriceFactor[];
|
|
45
|
+
};
|
|
46
|
+
/** The service's cost guide. */
|
|
47
|
+
readonly costGuide?: UiLink;
|
|
48
|
+
/** Defaults to `price-guide-heading`. */
|
|
49
|
+
readonly headingId?: string;
|
|
50
|
+
/** Set by `SectionSequence`. */
|
|
51
|
+
readonly tone?: SectionTone;
|
|
52
|
+
}
|
|
53
|
+
/** Whether the section renders: nothing without a renderable band (`no-approved-prices`). */
|
|
54
|
+
export declare function priceGuideReady({ prices, service }: Pick<PriceGuideProps, 'prices' | 'service'>): BrandlabSectionReadiness;
|
|
55
|
+
export declare function PriceGuide({ title, eyebrow, lead, prices, service, labels, caption, factors, costGuide, headingId, tone, variant, classes, }: PriceGuideProps): import("react/jsx-runtime").JSX.Element | null;
|
|
56
|
+
//# sourceMappingURL=price-guide.d.ts.map
|
|
@@ -0,0 +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,EAAQ,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAA4D,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAElI,eAAO,MAAM,oBAAoB,sCAAuC,CAAA;AACxE,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAA;AACrE,MAAM,MAAM,cAAc,GACtB,WAAW,GACX,kBAAkB,GAClB,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,GAC/B,OAAO,GACP,MAAM,GACN,OAAO,GACP,SAAS,GACT,OAAO,GACP,MAAM,GACN,SAAS,GACT,YAAY,GACZ,QAAQ,GACR,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,MAAM,CAAA;AACV,MAAM,MAAM,cAAc,GAAG,SAAS,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAAA;AAEzE,+FAA+F;AAC/F,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACnE;AAED,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,cAAc;IACd,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAA;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAA;IACzC,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,0HAA0H;IAC1H,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,8CAA8C;IAC9C,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAA;KAAE,CAAA;IACxF,gCAAgC;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,yCAAyC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,gCAAgC;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAC5B;AAED,6FAA6F;AAC7F,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,GAAG,SAAS,CAAC,GAAG,wBAAwB,CAE1H;AAED,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,OAAO,EACP,IAAI,EACJ,MAAM,EACN,OAAO,EACP,MAAM,EACN,OAAuB,EACvB,OAAO,EACP,SAAS,EACT,SAAiC,EACjC,IAAI,EACJ,OAAiB,EACjB,OAAO,GACR,EAAE,eAAe,kDAmEjB"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { PRICE_CAPTION, formatPriceBand, renderablePrices } from '@brandlab-digital/site-core/prices';
|
|
3
|
+
import { SECTION_READY } from '@brandlab-digital/site-core/section-order';
|
|
4
|
+
import { TextLink } from '../primitives/links.js';
|
|
5
|
+
import { Section, SectionHeading } from '../primitives/layout.js';
|
|
6
|
+
import { slot } from '../skin.js';
|
|
7
|
+
import { PriceFigures, PriceScope, PriceTable, assertPriceWording } from './price-table.js';
|
|
8
|
+
export const PRICE_GUIDE_VARIANTS = ['table', 'cards', 'bands'];
|
|
9
|
+
/** Whether the section renders: nothing without a renderable band (`no-approved-prices`). */
|
|
10
|
+
export function priceGuideReady({ prices, service }) {
|
|
11
|
+
return renderablePrices(prices, service).length > 0 ? SECTION_READY : { render: false, reason: 'no-approved-prices' };
|
|
12
|
+
}
|
|
13
|
+
export function PriceGuide({ title, eyebrow, lead, prices, service, labels, caption = PRICE_CAPTION, factors, costGuide, headingId = 'price-guide-heading', tone, variant = 'table', classes, }) {
|
|
14
|
+
const rows = renderablePrices(prices, service);
|
|
15
|
+
assertPriceWording(caption, labels, service ? [service] : rows.map((row) => row.service));
|
|
16
|
+
if (rows.length === 0)
|
|
17
|
+
return null;
|
|
18
|
+
let body;
|
|
19
|
+
if (variant === 'table') {
|
|
20
|
+
body = _jsx(PriceTable, { prices: rows, labels: labels, caption: caption, classes: { ...classes, root: undefined } });
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const list = (row) => variant === 'cards' ? (_jsxs("li", { ...slot(classes, 'card'), children: [_jsx("h3", { ...slot(classes, 'title'), children: row.job }), _jsx("div", { ...slot(classes, 'figures'), children: _jsx(PriceFigures, { price: row, labels: labels, classes: classes }) }), _jsx(PriceScope, { price: row, labels: labels, classes: classes })] }, row.job)) : (_jsxs("li", { ...slot(classes, 'band'), children: [_jsx("span", { ...slot(classes, 'job'), children: row.job }), ' ', _jsx("div", { ...slot(classes, 'figures'), children: _jsx(PriceFigures, { price: row, labels: labels, classes: classes }) }), _jsx(PriceScope, { price: row, labels: labels, classes: classes })] }, row.job));
|
|
24
|
+
body = (_jsxs(_Fragment, { children: [_jsx("p", { ...slot(classes, 'caption'), children: caption }), _jsx("ul", { role: "list", ...slot(classes, variant), children: rows.map(list) })] }));
|
|
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", { ...slot(classes, 'factor'), children: [_jsx("span", { ...slot(classes, 'name'), children: factor.label }), factor.effect ? (_jsxs(_Fragment, { children: [' ', _jsx("span", { ...slot(classes, 'effect'), children: formatPriceBand(factor.effect) })] })) : null, factor.detail ? _jsx("p", { ...slot(classes, '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
|
+
}
|
|
28
|
+
//# sourceMappingURL=price-guide.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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 { 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'\nexport type PriceGuideSkin = SkinEntry<PriceGuideSlot, PriceGuideVariant>\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} {...slot(classes, 'card')}>\n <h3 {...slot(classes, 'title')}>{row.job}</h3>\n <div {...slot(classes, 'figures')}>\n <PriceFigures price={row} labels={labels} classes={classes} />\n </div>\n <PriceScope price={row} labels={labels} classes={classes} />\n </li>\n ) : (\n <li key={row.job} {...slot(classes, 'band')}>\n <span {...slot(classes, 'job')}>{row.job}</span>{' '}\n <div {...slot(classes, 'figures')}>\n <PriceFigures price={row} labels={labels} classes={classes} />\n </div>\n <PriceScope price={row} labels={labels} classes={classes} />\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} {...slot(classes, 'factor')}>\n <span {...slot(classes, 'name')}>{factor.label}</span>\n {factor.effect ? (\n <>\n {' '}\n <span {...slot(classes, 'effect')}>{formatPriceBand(factor.effect)}</span>\n </>\n ) : null}\n {factor.detail ? <p {...slot(classes, '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"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type BrandlabPrice } from '@brandlab-digital/site-core/prices';
|
|
2
|
+
import { type SkinEntry, type SlotClasses } from '../skin.js';
|
|
3
|
+
export type PriceTableSlot = 'root' | 'table' | 'caption' | 'thead' | 'row' | 'job' | 'cell' | 'label' | 'range' | 'unit' | 'gst' | 'parts' | 'part' | 'includes' | 'excludes';
|
|
4
|
+
export type PriceTableSkin = SkinEntry<PriceTableSlot>;
|
|
5
|
+
/** The words around the figures, in the site's Australian English. */
|
|
6
|
+
export interface PriceLabels {
|
|
7
|
+
/** The job column header, for example "Job". */
|
|
8
|
+
readonly job: string;
|
|
9
|
+
/** The price column header, for example "Typical price". */
|
|
10
|
+
readonly price: string;
|
|
11
|
+
/** The scope column header, for example "What's included". */
|
|
12
|
+
readonly includes: string;
|
|
13
|
+
/** Before what the price leaves out, for example "Not included". */
|
|
14
|
+
readonly excludes: string;
|
|
15
|
+
/** The GST flag beside a GST-inclusive price, for example "incl. GST". */
|
|
16
|
+
readonly gstIncluded: string;
|
|
17
|
+
/** The GST flag beside a GST-exclusive price, for example "excl. GST" (never "+ GST"). */
|
|
18
|
+
readonly gstExcluded: string;
|
|
19
|
+
}
|
|
20
|
+
export interface PriceTableProps extends PriceTableSkin {
|
|
21
|
+
readonly prices: readonly BrandlabPrice[];
|
|
22
|
+
readonly labels: PriceLabels;
|
|
23
|
+
/** Defaults to "Typical prices"; otherwise only "Typical {service} prices" for a service in `prices`. */
|
|
24
|
+
readonly caption?: string;
|
|
25
|
+
}
|
|
26
|
+
/** Throws when the caption (checked against the page's service slugs) or the GST labels break the price rules. */
|
|
27
|
+
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
|
+
/** The full price, its unit and GST flag, then any part prices beside it. */
|
|
31
|
+
export declare function PriceFigures({ price, labels, classes }: {
|
|
32
|
+
price: BrandlabPrice;
|
|
33
|
+
labels: PriceLabels;
|
|
34
|
+
classes: FigureClasses | undefined;
|
|
35
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
36
|
+
/** What the price covers and leaves out. */
|
|
37
|
+
export declare function PriceScope({ price, labels, classes }: {
|
|
38
|
+
price: BrandlabPrice;
|
|
39
|
+
labels: PriceLabels;
|
|
40
|
+
classes: ScopeClasses | undefined;
|
|
41
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
export declare function PriceTable({ prices, labels, caption, classes }: PriceTableProps): import("react/jsx-runtime").JSX.Element | null;
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=price-table.d.ts.map
|
|
@@ -0,0 +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,EAAQ,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAA;AAEnE,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,MAAM,MAAM,cAAc,GAAG,SAAS,CAAC,cAAc,CAAC,CAAA;AAEtD,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,KAAK,aAAa,GAAG,WAAW,CAAC,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,CAAC,CAAA;AAC7E,KAAK,YAAY,GAAG,WAAW,CAAC,UAAU,GAAG,UAAU,CAAC,CAAA;AAExD,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,aAAa,GAAG,SAAS,CAAA;CAAE,2CAwBzI;AAED,4CAA4C;AAC5C,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,YAAY,GAAG,SAAS,CAAA;CAAE,2CAiBtI;AAUD,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAuB,EAAE,OAAO,EAAE,EAAE,eAAe,kDA8C/F"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PRICE_CAPTION, assertGstLabel, assertPriceCaption, formatPriceBand, renderablePrices, } from '@brandlab-digital/site-core/prices';
|
|
3
|
+
import { slot } from '../skin.js';
|
|
4
|
+
/** Throws when the caption (checked against the page's service slugs) or the GST labels break the price rules. */
|
|
5
|
+
export function assertPriceWording(caption, labels, services) {
|
|
6
|
+
assertPriceCaption(caption, services);
|
|
7
|
+
assertGstLabel(labels.gstIncluded, 'labels.gstIncluded');
|
|
8
|
+
assertGstLabel(labels.gstExcluded, 'labels.gstExcluded');
|
|
9
|
+
}
|
|
10
|
+
/** The full price, its unit and GST flag, then any part prices beside it. */
|
|
11
|
+
export function PriceFigures({ price, labels, classes }) {
|
|
12
|
+
return (_jsxs(_Fragment, { children: [_jsx("span", { ...slot(classes, 'range'), children: formatPriceBand(price) }), price.unit ? (_jsxs(_Fragment, { children: [' ', _jsx("span", { ...slot(classes, 'unit'), children: price.unit })] })) : null, ' ', _jsx("span", { ...slot(classes, 'gst'), "data-gst": price.gstIncluded ? 'included' : 'excluded', children: price.gstIncluded ? labels.gstIncluded : labels.gstExcluded }), price.parts.length > 0 ? (_jsx("ul", { ...slot(classes, 'parts'), children: price.parts.map((part) => (_jsxs("li", { ...slot(classes, 'part'), children: [part.label, " ", formatPriceBand(part)] }, part.label))) })) : null] }));
|
|
13
|
+
}
|
|
14
|
+
/** What the price covers and leaves out. */
|
|
15
|
+
export function PriceScope({ price, labels, classes }) {
|
|
16
|
+
return (_jsxs(_Fragment, { children: [price.includes.length > 0 ? (_jsx("ul", { ...slot(classes, 'includes'), children: price.includes.map((item) => (_jsx("li", { children: item }, item))) })) : null, price.excludes.length > 0 ? (_jsxs("p", { ...slot(classes, 'excludes'), children: [labels.excludes, ": ", price.excludes.join(', ')] })) : null] }));
|
|
17
|
+
}
|
|
18
|
+
function CellLabel({ classes, children }) {
|
|
19
|
+
return (_jsx("span", { ...slot(classes, 'label'), "aria-hidden": "true", children: children }));
|
|
20
|
+
}
|
|
21
|
+
export function PriceTable({ prices, labels, caption = PRICE_CAPTION, classes }) {
|
|
22
|
+
const rows = renderablePrices(prices);
|
|
23
|
+
assertPriceWording(caption, labels, rows.map((row) => row.service));
|
|
24
|
+
if (rows.length === 0)
|
|
25
|
+
return null;
|
|
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", ...slot(classes, 'row'), children: [_jsx("th", { scope: "row", role: "rowheader", ...slot(classes, 'job'), children: row.job }), _jsxs("td", { role: "cell", ...slot(classes, 'cell'), "data-column": "price", children: [_jsx(CellLabel, { classes: classes, children: labels.price }), _jsx(PriceFigures, { price: row, labels: labels, classes: classes })] }), hasScope ? (_jsxs("td", { role: "cell", ...slot(classes, 'cell'), "data-column": "includes", children: [row.includes.length > 0 || row.excludes.length > 0 ? _jsx(CellLabel, { classes: classes, children: labels.includes }) : null, _jsx(PriceScope, { price: row, labels: labels, classes: classes })] })) : null] }, `${row.service}:${row.job}`))) })] }) }));
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=price-table.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"price-table.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/price-table.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `PriceTable`: typical prices from `prices.ts` as a real `<table>`. Server component. The caption is \"Typical prices\"\n * by default and may only become \"Typical {service} prices\" (never a place name or \"market rates\"). Every row shows\n * the full price, the GST flag and, beside them only, any part price such as a call-out fee (NSW Fair Trading). Held\n * bands never render, and nothing renders without a renderable band.\n *\n * On narrow widths (container below 40rem) rows stack visually. Changing a table's CSS `display` can drop its\n * semantics in some screen readers, so every element carries its table role explicitly, and each cell repeats its\n * column label for sighted readers only (`aria-hidden`).\n */\nimport type { ReactNode } from 'react'\nimport {\n PRICE_CAPTION,\n assertGstLabel,\n assertPriceCaption,\n formatPriceBand,\n renderablePrices,\n type BrandlabPrice,\n} from '@brandlab-digital/site-core/prices'\nimport { slot, type SkinEntry, type SlotClasses } from '../skin.js'\n\nexport type PriceTableSlot =\n | 'root'\n | 'table'\n | 'caption'\n | 'thead'\n | 'row'\n | 'job'\n | 'cell'\n | 'label'\n | 'range'\n | 'unit'\n | 'gst'\n | 'parts'\n | 'part'\n | 'includes'\n | 'excludes'\nexport type PriceTableSkin = SkinEntry<PriceTableSlot>\n\n/** The words around the figures, in the site's Australian English. */\nexport interface PriceLabels {\n /** The job column header, for example \"Job\". */\n readonly job: string\n /** The price column header, for example \"Typical price\". */\n readonly price: string\n /** The scope column header, for example \"What's included\". */\n readonly includes: string\n /** Before what the price leaves out, for example \"Not included\". */\n readonly excludes: string\n /** The GST flag beside a GST-inclusive price, for example \"incl. GST\". */\n readonly gstIncluded: string\n /** The GST flag beside a GST-exclusive price, for example \"excl. GST\" (never \"+ GST\"). */\n readonly gstExcluded: string\n}\n\nexport interface PriceTableProps extends PriceTableSkin {\n readonly prices: readonly BrandlabPrice[]\n readonly labels: PriceLabels\n /** Defaults to \"Typical prices\"; otherwise only \"Typical {service} prices\" for a service in `prices`. */\n readonly caption?: string\n}\n\n/** Throws when the caption (checked against the page's service slugs) or the GST labels break the price rules. */\nexport function assertPriceWording(caption: string, labels: PriceLabels, services: readonly string[]) {\n assertPriceCaption(caption, services)\n assertGstLabel(labels.gstIncluded, 'labels.gstIncluded')\n assertGstLabel(labels.gstExcluded, 'labels.gstExcluded')\n}\n\ntype FigureClasses = SlotClasses<'range' | 'unit' | 'gst' | 'parts' | 'part'>\ntype ScopeClasses = SlotClasses<'includes' | 'excludes'>\n\n/** The full price, its unit and GST flag, then any part prices beside it. */\nexport function PriceFigures({ price, labels, classes }: { price: BrandlabPrice; labels: PriceLabels; classes: FigureClasses | undefined }) {\n return (\n <>\n <span {...slot(classes, 'range')}>{formatPriceBand(price)}</span>\n {price.unit ? (\n <>\n {' '}\n <span {...slot(classes, 'unit')}>{price.unit}</span>\n </>\n ) : null}{' '}\n <span {...slot(classes, 'gst')} data-gst={price.gstIncluded ? 'included' : 'excluded'}>\n {price.gstIncluded ? labels.gstIncluded : labels.gstExcluded}\n </span>\n {price.parts.length > 0 ? (\n <ul {...slot(classes, 'parts')}>\n {price.parts.map((part) => (\n <li key={part.label} {...slot(classes, 'part')}>\n {part.label} {formatPriceBand(part)}\n </li>\n ))}\n </ul>\n ) : null}\n </>\n )\n}\n\n/** What the price covers and leaves out. */\nexport function PriceScope({ price, labels, classes }: { price: BrandlabPrice; labels: PriceLabels; classes: ScopeClasses | undefined }) {\n return (\n <>\n {price.includes.length > 0 ? (\n <ul {...slot(classes, 'includes')}>\n {price.includes.map((item) => (\n <li key={item}>{item}</li>\n ))}\n </ul>\n ) : null}\n {price.excludes.length > 0 ? (\n <p {...slot(classes, 'excludes')}>\n {labels.excludes}: {price.excludes.join(', ')}\n </p>\n ) : null}\n </>\n )\n}\n\nfunction CellLabel({ classes, children }: { classes: SlotClasses<'label'> | undefined; children: ReactNode }) {\n return (\n <span {...slot(classes, 'label')} aria-hidden=\"true\">\n {children}\n </span>\n )\n}\n\nexport function PriceTable({ prices, labels, caption = PRICE_CAPTION, classes }: PriceTableProps) {\n const rows = renderablePrices(prices)\n assertPriceWording(caption, labels, rows.map((row) => row.service))\n if (rows.length === 0) return null\n const hasScope = rows.some((row) => row.includes.length > 0 || row.excludes.length > 0)\n return (\n <div data-bl=\"price-table\" className={classes?.root}>\n <table role=\"table\" {...slot(classes, 'table')}>\n <caption {...slot(classes, 'caption')}>{caption}</caption>\n <thead role=\"rowgroup\" {...slot(classes, 'thead')}>\n <tr role=\"row\">\n <th scope=\"col\" role=\"columnheader\">\n {labels.job}\n </th>\n <th scope=\"col\" role=\"columnheader\">\n {labels.price}\n </th>\n {hasScope ? (\n <th scope=\"col\" role=\"columnheader\">\n {labels.includes}\n </th>\n ) : null}\n </tr>\n </thead>\n <tbody role=\"rowgroup\">\n {rows.map((row) => (\n <tr key={`${row.service}:${row.job}`} role=\"row\" {...slot(classes, 'row')}>\n <th scope=\"row\" role=\"rowheader\" {...slot(classes, 'job')}>\n {row.job}\n </th>\n <td role=\"cell\" {...slot(classes, 'cell')} data-column=\"price\">\n <CellLabel classes={classes}>{labels.price}</CellLabel>\n <PriceFigures price={row} labels={labels} classes={classes} />\n </td>\n {hasScope ? (\n <td role=\"cell\" {...slot(classes, 'cell')} data-column=\"includes\">\n {row.includes.length > 0 || row.excludes.length > 0 ? <CellLabel classes={classes}>{labels.includes}</CellLabel> : null}\n <PriceScope price={row} labels={labels} classes={classes} />\n </td>\n ) : null}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )\n}\n"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ProofStrip` (S3, `proof-strip`): the business's verified strengths straight after the hero: the live rating from
|
|
3
|
+
* the feed, licence lines, insurance, years trading and partner logos. Server component. It takes items that are
|
|
4
|
+
* already verified (`verified` or `legacy-carried` facts, T4); it never counts up or hides text before scripts run.
|
|
5
|
+
* With fewer than three items it renders nothing and reports `merge-into-hero-checklist`: the facts belong in the
|
|
6
|
+
* hero's checklist instead (v1 §7.2).
|
|
7
|
+
*/
|
|
8
|
+
import type { ReactNode } from 'react';
|
|
9
|
+
import { type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order';
|
|
10
|
+
import { type UiIconName } from '../icons.js';
|
|
11
|
+
import { type SectionSlot, type SectionTone } from '../primitives/layout.js';
|
|
12
|
+
import { type SkinEntry } from '../skin.js';
|
|
13
|
+
export declare const PROOF_STRIP_VARIANTS: readonly ["inline", "tiles"];
|
|
14
|
+
export type ProofStripVariant = (typeof PROOF_STRIP_VARIANTS)[number];
|
|
15
|
+
export type ProofStripSlot = SectionSlot | 'heading' | 'list' | 'item' | 'icon' | 'stars' | 'value' | 'label' | 'logos';
|
|
16
|
+
/** `heading`: the section's H2 is visually hidden by default; a skin may show it. */
|
|
17
|
+
export type ProofStripSkin = SkinEntry<ProofStripSlot, ProofStripVariant, {
|
|
18
|
+
heading?: 'hidden' | 'visible';
|
|
19
|
+
}>;
|
|
20
|
+
/** The fewest items a strip shows; below this the facts merge into the hero checklist. */
|
|
21
|
+
export declare const PROOF_STRIP_MINIMUM = 3;
|
|
22
|
+
interface ProofItemBase {
|
|
23
|
+
/** A stable slug, rendered as `data-item`. */
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly label: string;
|
|
26
|
+
readonly icon?: UiIconName;
|
|
27
|
+
}
|
|
28
|
+
/** One verified fact, for example `{ id: 'licence', value: '276621C', label: 'NSW contractor licence' }`. */
|
|
29
|
+
export interface ProofFactItem extends ProofItemBase {
|
|
30
|
+
readonly value?: string;
|
|
31
|
+
readonly rating?: never;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The live rating from the reviews feed (F9), never typed by hand (T4). Its value is the rating itself ("4.9"), with
|
|
35
|
+
* the stars beside it, and the item is marked `data-bl-source="feed"`, as the reviews band marks its root.
|
|
36
|
+
*/
|
|
37
|
+
export interface ProofRatingItem extends ProofItemBase {
|
|
38
|
+
readonly rating: number;
|
|
39
|
+
readonly value?: never;
|
|
40
|
+
}
|
|
41
|
+
export type ProofItem = ProofFactItem | ProofRatingItem;
|
|
42
|
+
export interface ProofStripProps extends ProofStripSkin {
|
|
43
|
+
/** The H2, for example "Why homeowners choose Omega". Visually hidden unless the skin shows it. */
|
|
44
|
+
readonly title: string;
|
|
45
|
+
readonly items: readonly ProofItem[];
|
|
46
|
+
/** Partner logos (a `LogoStrip` of verified relationships). Counts as one item. */
|
|
47
|
+
readonly logos?: ReactNode;
|
|
48
|
+
/** Defaults to `proof-heading`. */
|
|
49
|
+
readonly headingId?: string;
|
|
50
|
+
/** Set by `SectionSequence`. */
|
|
51
|
+
readonly tone?: SectionTone;
|
|
52
|
+
}
|
|
53
|
+
/** Whether the strip renders: at least three items, logos counting as one (`merge-into-hero-checklist`). */
|
|
54
|
+
export declare function proofStripReady({ items, logos }: Pick<ProofStripProps, 'items' | 'logos'>): BrandlabSectionReadiness;
|
|
55
|
+
export declare function ProofStrip({ title, items, logos, headingId, tone, variant, layout, classes }: ProofStripProps): import("react/jsx-runtime").JSX.Element | null;
|
|
56
|
+
export {};
|
|
57
|
+
//# sourceMappingURL=proof-strip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proof-strip.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/proof-strip.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAiB,KAAK,wBAAwB,EAAE,MAAM,2CAA2C,CAAA;AACxG,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,EAAwB,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAClG,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AAEjD,eAAO,MAAM,oBAAoB,8BAA+B,CAAA;AAChE,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAA;AACrE,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAA;AACvH,qFAAqF;AACrF,MAAM,MAAM,cAAc,GAAG,SAAS,CAAC,cAAc,EAAE,iBAAiB,EAAE;IAAE,OAAO,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;CAAE,CAAC,CAAA;AAE7G,0FAA0F;AAC1F,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAEpC,UAAU,aAAa;IACrB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAA;CAC3B;AAED,6GAA6G;AAC7G,MAAM,WAAW,aAAc,SAAQ,aAAa;IAClD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,CAAA;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CACvB;AAED,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,eAAe,CAAA;AAEvD,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,mGAAmG;IACnG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAA;IACpC,mFAAmF;IACnF,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAA;IAC1B,mCAAmC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,gCAAgC;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAC5B;AAED,4GAA4G;AAC5G,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,wBAAwB,CAGpH;AAED,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAA2B,EAAE,IAAI,EAAE,OAAkB,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,eAAe,kDAmC1I"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { SECTION_READY } from '@brandlab-digital/site-core/section-order';
|
|
3
|
+
import { Icon } from '../icons.js';
|
|
4
|
+
import { RatingStars, Section } from '../primitives/layout.js';
|
|
5
|
+
import { slot } from '../skin.js';
|
|
6
|
+
export const PROOF_STRIP_VARIANTS = ['inline', 'tiles'];
|
|
7
|
+
/** The fewest items a strip shows; below this the facts merge into the hero checklist. */
|
|
8
|
+
export const PROOF_STRIP_MINIMUM = 3;
|
|
9
|
+
/** Whether the strip renders: at least three items, logos counting as one (`merge-into-hero-checklist`). */
|
|
10
|
+
export function proofStripReady({ items, logos }) {
|
|
11
|
+
const count = items.length + (logos ? 1 : 0);
|
|
12
|
+
return count >= PROOF_STRIP_MINIMUM ? SECTION_READY : { render: false, reason: 'merge-into-hero-checklist' };
|
|
13
|
+
}
|
|
14
|
+
export function ProofStrip({ title, items, logos, headingId = 'proof-heading', tone, variant = 'inline', layout, classes }) {
|
|
15
|
+
if (!proofStripReady({ items, logos }).render)
|
|
16
|
+
return null;
|
|
17
|
+
const hidden = (layout?.heading ?? 'hidden') === 'hidden';
|
|
18
|
+
return (_jsxs(Section, { id: "proof-strip", labelledBy: headingId, tone: tone, variant: variant, classes: classes, children: [_jsx("h2", { id: headingId, ...slot(classes, 'heading'), "data-bl-visually-hidden": hidden ? '' : undefined, children: title }), items.length > 0 ? (_jsx("ul", { role: "list", ...slot(classes, 'list'), children: items.map((item) => (_jsxs("li", { ...slot(classes, 'item'), "data-item": item.id, "data-bl-source": item.rating !== undefined ? 'feed' : undefined, children: [item.icon ? (_jsx("span", { ...slot(classes, 'icon'), children: _jsx(Icon, { name: item.icon }) })) : null, item.rating !== undefined ? (_jsx("span", { ...slot(classes, 'stars'), "aria-hidden": "true", children: _jsx(RatingStars, { rating: item.rating }) })) : null, item.rating !== undefined ? (_jsx("span", { ...slot(classes, 'value'), children: item.rating.toFixed(1) })) : item.value ? (_jsx("span", { ...slot(classes, 'value'), children: item.value })) : null, ' ', _jsx("span", { ...slot(classes, 'label'), children: item.label })] }, item.id))) })) : null, logos ? _jsx("div", { ...slot(classes, 'logos'), children: logos }) : null] }));
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=proof-strip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proof-strip.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/proof-strip.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * `ProofStrip` (S3, `proof-strip`): the business's verified strengths straight after the hero: the live rating from\n * the feed, licence lines, insurance, years trading and partner logos. Server component. It takes items that are\n * already verified (`verified` or `legacy-carried` facts, T4); it never counts up or hides text before scripts run.\n * With fewer than three items it renders nothing and reports `merge-into-hero-checklist`: the facts belong in the\n * hero's checklist instead (v1 §7.2).\n */\nimport type { ReactNode } from 'react'\nimport { SECTION_READY, type BrandlabSectionReadiness } from '@brandlab-digital/site-core/section-order'\nimport { Icon, type UiIconName } from '../icons.js'\nimport { RatingStars, Section, type SectionSlot, type SectionTone } from '../primitives/layout.js'\nimport { slot, type SkinEntry } from '../skin.js'\n\nexport const PROOF_STRIP_VARIANTS = ['inline', 'tiles'] as const\nexport type ProofStripVariant = (typeof PROOF_STRIP_VARIANTS)[number]\nexport type ProofStripSlot = SectionSlot | 'heading' | 'list' | 'item' | 'icon' | 'stars' | 'value' | 'label' | 'logos'\n/** `heading`: the section's H2 is visually hidden by default; a skin may show it. */\nexport type ProofStripSkin = SkinEntry<ProofStripSlot, ProofStripVariant, { heading?: 'hidden' | 'visible' }>\n\n/** The fewest items a strip shows; below this the facts merge into the hero checklist. */\nexport const PROOF_STRIP_MINIMUM = 3\n\ninterface ProofItemBase {\n /** A stable slug, rendered as `data-item`. */\n readonly id: string\n readonly label: string\n readonly icon?: UiIconName\n}\n\n/** One verified fact, for example `{ id: 'licence', value: '276621C', label: 'NSW contractor licence' }`. */\nexport interface ProofFactItem extends ProofItemBase {\n readonly value?: string\n readonly rating?: never\n}\n\n/**\n * The live rating from the reviews feed (F9), never typed by hand (T4). Its value is the rating itself (\"4.9\"), with\n * the stars beside it, and the item is marked `data-bl-source=\"feed\"`, as the reviews band marks its root.\n */\nexport interface ProofRatingItem extends ProofItemBase {\n readonly rating: number\n readonly value?: never\n}\n\nexport type ProofItem = ProofFactItem | ProofRatingItem\n\nexport interface ProofStripProps extends ProofStripSkin {\n /** The H2, for example \"Why homeowners choose Omega\". Visually hidden unless the skin shows it. */\n readonly title: string\n readonly items: readonly ProofItem[]\n /** Partner logos (a `LogoStrip` of verified relationships). Counts as one item. */\n readonly logos?: ReactNode\n /** Defaults to `proof-heading`. */\n readonly headingId?: string\n /** Set by `SectionSequence`. */\n readonly tone?: SectionTone\n}\n\n/** Whether the strip renders: at least three items, logos counting as one (`merge-into-hero-checklist`). */\nexport function proofStripReady({ items, logos }: Pick<ProofStripProps, 'items' | 'logos'>): BrandlabSectionReadiness {\n const count = items.length + (logos ? 1 : 0)\n return count >= PROOF_STRIP_MINIMUM ? SECTION_READY : { render: false, reason: 'merge-into-hero-checklist' }\n}\n\nexport function ProofStrip({ title, items, logos, headingId = 'proof-heading', tone, variant = 'inline', layout, classes }: ProofStripProps) {\n if (!proofStripReady({ items, logos }).render) return null\n const hidden = (layout?.heading ?? 'hidden') === 'hidden'\n return (\n <Section id=\"proof-strip\" labelledBy={headingId} tone={tone} variant={variant} classes={classes}>\n <h2 id={headingId} {...slot(classes, 'heading')} data-bl-visually-hidden={hidden ? '' : undefined}>\n {title}\n </h2>\n {items.length > 0 ? (\n <ul role=\"list\" {...slot(classes, 'list')}>\n {items.map((item) => (\n <li key={item.id} {...slot(classes, 'item')} data-item={item.id} data-bl-source={item.rating !== undefined ? 'feed' : undefined}>\n {item.icon ? (\n <span {...slot(classes, 'icon')}>\n <Icon name={item.icon} />\n </span>\n ) : null}\n {item.rating !== undefined ? (\n <span {...slot(classes, 'stars')} aria-hidden=\"true\">\n <RatingStars rating={item.rating} />\n </span>\n ) : null}\n {item.rating !== undefined ? (\n <span {...slot(classes, 'value')}>{item.rating.toFixed(1)}</span>\n ) : item.value ? (\n <span {...slot(classes, 'value')}>{item.value}</span>\n ) : null}{' '}\n <span {...slot(classes, 'label')}>{item.label}</span>\n </li>\n ))}\n </ul>\n ) : null}\n {logos ? <div {...slot(classes, 'logos')}>{logos}</div> : null}\n </Section>\n )\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `RatingChip`: Google's own average and total in words ("4.9 out of 5 from 99 Google reviews"), with decorative stars
|
|
3
|
+
* and a link to the Business Profile. Server component. The numbers come only from the reviews feed (T4), never typed
|
|
4
|
+
* by hand; without both an average and a total of 1 or more it renders nothing. `data-bl-source="feed"` marks the
|
|
5
|
+
* numbers as the feed's for the ready gate (T4, T5).
|
|
6
|
+
*/
|
|
7
|
+
import type { PreparedBusinessProfileReviews } from '@brandlab-digital/site-core/google-business-reviews';
|
|
8
|
+
import { type SkinEntry } from '../skin.js';
|
|
9
|
+
export type RatingChipSlot = 'root' | 'stars' | 'rating' | 'source';
|
|
10
|
+
export type RatingChipSkin = SkinEntry<RatingChipSlot, never, {
|
|
11
|
+
stars?: boolean;
|
|
12
|
+
}>;
|
|
13
|
+
/** The part of the prepared reviews the chip reads: Google's average, total and profile link. */
|
|
14
|
+
export type RatingChipData = Pick<PreparedBusinessProfileReviews, 'averageRating' | 'totalReviewCount' | 'profileUrl'>;
|
|
15
|
+
export interface RatingChipProps extends RatingChipSkin {
|
|
16
|
+
/** From `prepareBusinessProfileReviews`; null renders nothing. */
|
|
17
|
+
readonly reviews: RatingChipData | null | undefined;
|
|
18
|
+
}
|
|
19
|
+
/** Whether the feed gave Google's average and a total of 1 or more, so a rating can be shown. */
|
|
20
|
+
export declare function hasGoogleRating(reviews: RatingChipData | null | undefined): reviews is RatingChipData & {
|
|
21
|
+
averageRating: number;
|
|
22
|
+
totalReviewCount: number;
|
|
23
|
+
};
|
|
24
|
+
export declare function RatingChip({ reviews, layout, classes }: RatingChipProps): import("react/jsx-runtime").JSX.Element | null;
|
|
25
|
+
//# sourceMappingURL=rating-chip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rating-chip.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/rating-chip.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,qDAAqD,CAAA;AAEzG,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AAEjD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAA;AACnE,MAAM,MAAM,cAAc,GAAG,SAAS,CAAC,cAAc,EAAE,KAAK,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAA;AAElF,iGAAiG;AACjG,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,8BAA8B,EAAE,eAAe,GAAG,kBAAkB,GAAG,YAAY,CAAC,CAAA;AAEtH,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,CAAA;CACpD;AAID,iGAAiG;AACjG,wBAAgB,eAAe,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,IAAI,cAAc,GAAG;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,CAE3J;AAED,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,eAAe,kDAgBvE"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { RatingStars } from '../primitives/layout.js';
|
|
3
|
+
import { slot } from '../skin.js';
|
|
4
|
+
const COUNT = new Intl.NumberFormat('en-AU');
|
|
5
|
+
/** Whether the feed gave Google's average and a total of 1 or more, so a rating can be shown. */
|
|
6
|
+
export function hasGoogleRating(reviews) {
|
|
7
|
+
return typeof reviews?.averageRating === 'number' && typeof reviews.totalReviewCount === 'number' && reviews.totalReviewCount > 0;
|
|
8
|
+
}
|
|
9
|
+
export function RatingChip({ reviews, layout, classes }) {
|
|
10
|
+
if (!hasGoogleRating(reviews))
|
|
11
|
+
return null;
|
|
12
|
+
const total = reviews.totalReviewCount;
|
|
13
|
+
return (_jsxs("p", { "data-bl": "rating-chip", "data-bl-source": "feed", className: classes?.root, children: [layout?.stars === false ? null : (_jsx("span", { ...slot(classes, 'stars'), "aria-hidden": "true", children: _jsx(RatingStars, { rating: reviews.averageRating }) })), _jsxs("span", { ...slot(classes, 'rating'), children: [reviews.averageRating.toFixed(1), " out of 5"] }), " from", ' ', _jsxs("a", { ...slot(classes, 'source'), href: reviews.profileUrl, target: "_blank", rel: "noopener noreferrer", children: [COUNT.format(total), " Google ", total === 1 ? 'review' : 'reviews'] })] }));
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=rating-chip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rating-chip.js","sourceRoot":"","sources":["../../../src/ui-kit/sections/rating-chip.tsx"],"names":[],"mappings":";AAOA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AACrD,OAAO,EAAE,IAAI,EAAkB,MAAM,YAAY,CAAA;AAajD,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;AAE5C,iGAAiG;AACjG,MAAM,UAAU,eAAe,CAAC,OAA0C;IACxE,OAAO,OAAO,OAAO,EAAE,aAAa,KAAK,QAAQ,IAAI,OAAO,OAAO,CAAC,gBAAgB,KAAK,QAAQ,IAAI,OAAO,CAAC,gBAAgB,GAAG,CAAC,CAAA;AACnI,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAmB;IACtE,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAA;IAC1C,MAAM,KAAK,GAAG,OAAO,CAAC,gBAAgB,CAAA;IACtC,OAAO,CACL,wBAAW,aAAa,oBAAgB,MAAM,EAAC,SAAS,EAAE,OAAO,EAAE,IAAI,aACpE,MAAM,EAAE,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAChC,kBAAU,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,iBAAc,MAAM,YAClD,KAAC,WAAW,IAAC,MAAM,EAAE,OAAO,CAAC,aAAa,GAAI,GACzC,CACR,EACD,mBAAU,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,aAAG,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,iBAAiB,WAAM,GAAG,EAC9F,gBAAO,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,UAAU,EAAE,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,qBAAqB,aAChG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,cAAU,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,IAC9D,IACF,CACL,CAAA;AACH,CAAC","sourcesContent":["/**\n * `RatingChip`: Google's own average and total in words (\"4.9 out of 5 from 99 Google reviews\"), with decorative stars\n * and a link to the Business Profile. Server component. The numbers come only from the reviews feed (T4), never typed\n * by hand; without both an average and a total of 1 or more it renders nothing. `data-bl-source=\"feed\"` marks the\n * numbers as the feed's for the ready gate (T4, T5).\n */\nimport type { PreparedBusinessProfileReviews } from '@brandlab-digital/site-core/google-business-reviews'\nimport { RatingStars } from '../primitives/layout.js'\nimport { slot, type SkinEntry } from '../skin.js'\n\nexport type RatingChipSlot = 'root' | 'stars' | 'rating' | 'source'\nexport type RatingChipSkin = SkinEntry<RatingChipSlot, never, { stars?: boolean }>\n\n/** The part of the prepared reviews the chip reads: Google's average, total and profile link. */\nexport type RatingChipData = Pick<PreparedBusinessProfileReviews, 'averageRating' | 'totalReviewCount' | 'profileUrl'>\n\nexport interface RatingChipProps extends RatingChipSkin {\n /** From `prepareBusinessProfileReviews`; null renders nothing. */\n readonly reviews: RatingChipData | null | undefined\n}\n\nconst COUNT = new Intl.NumberFormat('en-AU')\n\n/** Whether the feed gave Google's average and a total of 1 or more, so a rating can be shown. */\nexport function hasGoogleRating(reviews: RatingChipData | null | undefined): reviews is RatingChipData & { averageRating: number; totalReviewCount: number } {\n return typeof reviews?.averageRating === 'number' && typeof reviews.totalReviewCount === 'number' && reviews.totalReviewCount > 0\n}\n\nexport function RatingChip({ reviews, layout, classes }: RatingChipProps) {\n if (!hasGoogleRating(reviews)) return null\n const total = reviews.totalReviewCount\n return (\n <p data-bl=\"rating-chip\" data-bl-source=\"feed\" className={classes?.root}>\n {layout?.stars === false ? null : (\n <span {...slot(classes, 'stars')} aria-hidden=\"true\">\n <RatingStars rating={reviews.averageRating} />\n </span>\n )}\n <span {...slot(classes, 'rating')}>{reviews.averageRating.toFixed(1)} out of 5</span> from{' '}\n <a {...slot(classes, 'source')} href={reviews.profileUrl} target=\"_blank\" rel=\"noopener noreferrer\">\n {COUNT.format(total)} Google {total === 1 ? 'review' : 'reviews'}\n </a>\n </p>\n )\n}\n"]}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S7 `ReviewsBand` (`reviews`): prominent, honest proof from the Business Profile feed. Server component, no client
|
|
3
|
+
* JS. Google's own average and total sit beside the selected reviews with a link to the profile; the text is verbatim
|
|
4
|
+
* and always in the HTML (a long review folds behind a native "Read more" disclosure); the band says how the reviews
|
|
5
|
+
* were chosen ("Selected five-star Google reviews" or "Recent Google reviews"). No review or rating schema: a business
|
|
6
|
+
* that controls its own reviews isn't eligible for review stars.
|
|
7
|
+
*
|
|
8
|
+
* Data comes only from `prepareBusinessProfileReviews` (site-core), which applies the policy, mention ordering, the
|
|
9
|
+
* 3.7.0 exclude guard and the limit. No prepared reviews, or none and no rating, renders nothing at all (no heading).
|
|
10
|
+
* With no written reviews but a rating, every variant renders as `summary`: the heading and the rating chip only.
|
|
11
|
+
* Never place it on the contact page or inside another section.
|
|
12
|
+
*
|
|
13
|
+
* Sites that arrange their own layout use the parts (`ReviewsBand.Root`, `.Heading`, `.Summary`, `.List`, `.Item`):
|
|
14
|
+
* the root keeps the section semantics, the empty rule and the disclosure line; the parts keep the data and markup.
|
|
15
|
+
*/
|
|
16
|
+
import { type ReactNode } from 'react';
|
|
17
|
+
import { type BusinessProfileReview, type PreparedBusinessProfileReviews } from '@brandlab-digital/site-core/google-business-reviews';
|
|
18
|
+
import { type SectionTone } from '../primitives/layout.js';
|
|
19
|
+
import { type SkinEntry, type SlotClasses } from '../skin.js';
|
|
20
|
+
export declare const REVIEWS_BAND_VARIANTS: readonly ["band", "feature", "cards", "wall", "summary"];
|
|
21
|
+
export type ReviewsBandVariant = (typeof REVIEWS_BAND_VARIANTS)[number];
|
|
22
|
+
export type ReviewsBandSlot = 'root' | 'inner' | 'top' | 'head' | 'eyebrow' | 'heading' | 'lead' | 'summary' | 'rating' | 'source' | 'list' | 'item' | 'stars' | 'quote' | 'more' | 'author' | 'avatar' | 'name' | 'date' | 'disclosure' | 'profileLink';
|
|
23
|
+
export interface ReviewsBandLayout {
|
|
24
|
+
readonly columns?: 1 | 2 | 3;
|
|
25
|
+
readonly align?: 'start' | 'center';
|
|
26
|
+
/** Reviewer photos beside the names (Google's own small photos, decorative). */
|
|
27
|
+
readonly avatars?: boolean;
|
|
28
|
+
/** Characters shown before a long review folds behind "Read more" (default 320); `false` never folds. */
|
|
29
|
+
readonly excerpt?: number | false;
|
|
30
|
+
}
|
|
31
|
+
export type ReviewsBandSkin = SkinEntry<ReviewsBandSlot, ReviewsBandVariant, ReviewsBandLayout>;
|
|
32
|
+
type Classes = SlotClasses<ReviewsBandSlot> | undefined;
|
|
33
|
+
export declare const REVIEWS_BAND_HEADING_ID = "bl-reviews-heading";
|
|
34
|
+
/**
|
|
35
|
+
* A long review split at a sentence end (or else a space) near `limit`, so the first part reads on its own. The two
|
|
36
|
+
* parts together are the review word for word. Short reviews, and reviews only a little over the limit, don't fold.
|
|
37
|
+
*/
|
|
38
|
+
export declare function splitReviewText(text: string, limit: number | false): readonly [string, string] | null;
|
|
39
|
+
export interface ReviewsBandRootProps extends ReviewsBandSkin {
|
|
40
|
+
readonly reviews: PreparedBusinessProfileReviews | null | undefined;
|
|
41
|
+
/** The id of the band's H2 (default `bl-reviews-heading`). */
|
|
42
|
+
readonly headingId?: string;
|
|
43
|
+
/** Set by `SectionSequence`. */
|
|
44
|
+
readonly tone?: SectionTone;
|
|
45
|
+
/** Signature art, rendered `aria-hidden`. */
|
|
46
|
+
readonly decoration?: ReactNode;
|
|
47
|
+
readonly children: ReactNode;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The section: `data-bl-section="reviews"` with `data-bl-source="feed"` (the ready gate's mark for feed numbers),
|
|
51
|
+
* the empty rule, and the locked disclosure line after the children.
|
|
52
|
+
*/
|
|
53
|
+
export declare function ReviewsBandRoot({ reviews, headingId, tone, decoration, variant, layout, classes, children, }: ReviewsBandRootProps): import("react/jsx-runtime").JSX.Element | null;
|
|
54
|
+
export interface ReviewsBandHeadingProps {
|
|
55
|
+
/** The H2's id; the same `headingId` the root has (default `bl-reviews-heading`). */
|
|
56
|
+
readonly id?: string;
|
|
57
|
+
/** The H2 text the site owns. */
|
|
58
|
+
readonly children: string;
|
|
59
|
+
readonly eyebrow?: string;
|
|
60
|
+
readonly intro?: string;
|
|
61
|
+
readonly layout?: Pick<ReviewsBandLayout, 'align'>;
|
|
62
|
+
readonly classes?: Classes;
|
|
63
|
+
}
|
|
64
|
+
/** The heading block: an optional eyebrow, the H2 and an optional intro. */
|
|
65
|
+
export declare function ReviewsBandHeading({ id, children, eyebrow, intro, layout, classes }: ReviewsBandHeadingProps): import("react/jsx-runtime").JSX.Element;
|
|
66
|
+
export interface ReviewsBandSummaryProps {
|
|
67
|
+
readonly reviews: PreparedBusinessProfileReviews | null | undefined;
|
|
68
|
+
readonly classes?: Classes;
|
|
69
|
+
}
|
|
70
|
+
/** Google's own average and total with the profile link; nothing when the feed has no rating. */
|
|
71
|
+
export declare function ReviewsBandSummary({ reviews, classes }: ReviewsBandSummaryProps): import("react/jsx-runtime").JSX.Element | null;
|
|
72
|
+
/** The review list; nothing without items. */
|
|
73
|
+
export declare function ReviewsBandList({ classes, children }: {
|
|
74
|
+
readonly classes?: Classes;
|
|
75
|
+
readonly children: ReactNode;
|
|
76
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
77
|
+
export interface ReviewsBandItemProps {
|
|
78
|
+
readonly review: BusinessProfileReview;
|
|
79
|
+
/** The `feature` variant's wide lead card. */
|
|
80
|
+
readonly lead?: boolean;
|
|
81
|
+
readonly layout?: Pick<ReviewsBandLayout, 'avatars' | 'excerpt'>;
|
|
82
|
+
readonly classes?: Classes;
|
|
83
|
+
}
|
|
84
|
+
/** One review, word for word: its stars, the quote and the reviewer with the month. */
|
|
85
|
+
export declare function ReviewsBandItem({ review, lead, layout, classes }: ReviewsBandItemProps): import("react/jsx-runtime").JSX.Element;
|
|
86
|
+
export interface ReviewsBandProps extends ReviewsBandSkin {
|
|
87
|
+
/** From `prepareBusinessProfileReviews`; null renders nothing at all. */
|
|
88
|
+
readonly reviews: PreparedBusinessProfileReviews | null | undefined;
|
|
89
|
+
/** The H2 text the site owns. */
|
|
90
|
+
readonly heading: string;
|
|
91
|
+
readonly eyebrow?: string;
|
|
92
|
+
readonly intro?: string;
|
|
93
|
+
/** The id of the band's H2 (default `bl-reviews-heading`). */
|
|
94
|
+
readonly headingId?: string;
|
|
95
|
+
/** Set by `SectionSequence`. */
|
|
96
|
+
readonly tone?: SectionTone;
|
|
97
|
+
/** Signature art, rendered `aria-hidden`. */
|
|
98
|
+
readonly decoration?: ReactNode;
|
|
99
|
+
}
|
|
100
|
+
declare function ReviewsBandSection({ reviews, heading, eyebrow, intro, headingId, tone, decoration, variant, layout, classes }: ReviewsBandProps): import("react/jsx-runtime").JSX.Element | null;
|
|
101
|
+
/** The reviews band, with its parts for sites that arrange their own layout. */
|
|
102
|
+
export declare const ReviewsBand: typeof ReviewsBandSection & {
|
|
103
|
+
Root: typeof ReviewsBandRoot;
|
|
104
|
+
Heading: typeof ReviewsBandHeading;
|
|
105
|
+
Summary: typeof ReviewsBandSummary;
|
|
106
|
+
List: typeof ReviewsBandList;
|
|
107
|
+
Item: typeof ReviewsBandItem;
|
|
108
|
+
};
|
|
109
|
+
export {};
|
|
110
|
+
//# sourceMappingURL=reviews-band.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reviews-band.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/sections/reviews-band.tsx"],"names":[],"mappings":"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"}
|