@brandlab-digital/site-next 3.6.0 → 3.8.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/analytics.d.ts +25 -12
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +23 -1
- package/dist/analytics.js.map +1 -1
- package/dist/contact/client.d.ts +6 -0
- package/dist/contact/client.d.ts.map +1 -1
- package/dist/contact/client.js +10 -2
- package/dist/contact/client.js.map +1 -1
- package/dist/contact/form-client.d.ts.map +1 -1
- package/dist/contact/form-client.js +26 -3
- package/dist/contact/form-client.js.map +1 -1
- package/dist/ui-kit/brandlab-skin.d.ts +17 -0
- package/dist/ui-kit/brandlab-skin.d.ts.map +1 -0
- package/dist/ui-kit/brandlab-skin.js +2 -0
- package/dist/ui-kit/brandlab-skin.js.map +1 -0
- package/dist/ui-kit/icons.d.ts +15 -0
- package/dist/ui-kit/icons.d.ts.map +1 -0
- package/dist/ui-kit/icons.js +72 -0
- package/dist/ui-kit/icons.js.map +1 -0
- package/dist/ui-kit/primitives/index.d.ts +6 -0
- package/dist/ui-kit/primitives/index.d.ts.map +1 -0
- package/dist/ui-kit/primitives/index.js +6 -0
- package/dist/ui-kit/primitives/index.js.map +1 -0
- package/dist/ui-kit/primitives/layout.d.ts +73 -0
- package/dist/ui-kit/primitives/layout.d.ts.map +1 -0
- package/dist/ui-kit/primitives/layout.js +34 -0
- package/dist/ui-kit/primitives/layout.js.map +1 -0
- package/dist/ui-kit/primitives/links.d.ts +48 -0
- package/dist/ui-kit/primitives/links.d.ts.map +1 -0
- package/dist/ui-kit/primitives/links.js +36 -0
- package/dist/ui-kit/primitives/links.js.map +1 -0
- package/dist/ui-kit/primitives/quote-button.d.ts +20 -0
- package/dist/ui-kit/primitives/quote-button.d.ts.map +1 -0
- package/dist/ui-kit/primitives/quote-button.js +15 -0
- package/dist/ui-kit/primitives/quote-button.js.map +1 -0
- package/dist/ui-kit/skin-index.d.ts +3 -0
- package/dist/ui-kit/skin-index.d.ts.map +1 -0
- package/dist/ui-kit/skin-index.js +2 -0
- package/dist/ui-kit/skin-index.js.map +1 -0
- package/dist/ui-kit/skin.d.ts +48 -0
- package/dist/ui-kit/skin.d.ts.map +1 -0
- package/dist/ui-kit/skin.js +47 -0
- package/dist/ui-kit/skin.js.map +1 -0
- package/dist/ui-kit/structure.css +123 -0
- package/dist/ui-kit/types.d.ts +27 -0
- package/dist/ui-kit/types.d.ts.map +1 -0
- package/dist/ui-kit/types.js +6 -0
- package/dist/ui-kit/types.js.map +1 -0
- package/package.json +17 -4
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* `QuoteButton`: the quote action. A server-rendered link to the page's quote form (for example `/contact#quote`), so
|
|
4
|
+
* it works before scripts run. When a `QuoteDialog` is on the page (3.10.0) it opens the dialog instead; on the
|
|
5
|
+
* contact page the link scrolls to the form. It carries the `data-bl-cta` hooks only: from 3.9.0 the shared
|
|
6
|
+
* instrumentation reads them and sends `lead_cta_click` when lead diagnostics are on. Nothing is sent in 3.8.0, and
|
|
7
|
+
* nothing here emits events itself.
|
|
8
|
+
*/
|
|
9
|
+
import Link from 'next/link';
|
|
10
|
+
import { Icon } from '../icons.js';
|
|
11
|
+
import { slot } from '../skin.js';
|
|
12
|
+
export function QuoteButton({ label, href, placement, ctaId, variant = 'primary', layout, classes }) {
|
|
13
|
+
return (_jsxs(Link, { href: href, "data-bl": "quote-button", "data-variant": variant, "data-size": layout?.size ?? 'regular', "data-bl-cta": ctaId, "data-bl-cta-placement": placement, className: classes?.root, children: [_jsx("span", { ...slot(classes, 'label'), children: label }), layout?.icon === false ? null : (_jsx("span", { ...slot(classes, 'icon'), children: _jsx(Icon, { name: "arrow" }) }))] }));
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=quote-button.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quote-button.js","sourceRoot":"","sources":["../../../src/ui-kit/primitives/quote-button.tsx"],"names":[],"mappings":";AAAA;;;;;;GAMG;AACH,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAClC,OAAO,EAAE,IAAI,EAAkB,MAAM,YAAY,CAAA;AAiBjD,MAAM,UAAU,WAAW,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,EAAE,MAAM,EAAE,OAAO,EAAoB;IACnH,OAAO,CACL,MAAC,IAAI,IACH,IAAI,EAAE,IAAI,aACF,cAAc,kBACR,OAAO,eACV,MAAM,EAAE,IAAI,IAAI,SAAS,iBACvB,KAAK,2BACK,SAAS,EAChC,SAAS,EAAE,OAAO,EAAE,IAAI,aAExB,kBAAU,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,YAAG,KAAK,GAAQ,EAC/C,MAAM,EAAE,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAC/B,kBAAU,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,YAC7B,KAAC,IAAI,IAAC,IAAI,EAAC,OAAO,GAAG,GAChB,CACR,IACI,CACR,CAAA;AACH,CAAC","sourcesContent":["/**\n * `QuoteButton`: the quote action. A server-rendered link to the page's quote form (for example `/contact#quote`), so\n * it works before scripts run. When a `QuoteDialog` is on the page (3.10.0) it opens the dialog instead; on the\n * contact page the link scrolls to the form. It carries the `data-bl-cta` hooks only: from 3.9.0 the shared\n * instrumentation reads them and sends `lead_cta_click` when lead diagnostics are on. Nothing is sent in 3.8.0, and\n * nothing here emits events itself.\n */\nimport Link from 'next/link'\nimport type { LeadCtaPlacement } from '../../analytics.js'\nimport { Icon } from '../icons.js'\nimport { slot, type SkinEntry } from '../skin.js'\nimport type { ButtonVariant } from './links.js'\n\nexport type QuoteButtonSlot = 'root' | 'label' | 'icon'\nexport type QuoteButtonSkin = SkinEntry<QuoteButtonSlot, ButtonVariant, { size?: 'regular' | 'large'; icon?: boolean }>\n\nexport interface QuoteButtonProps extends QuoteButtonSkin {\n /** The site's recorded quote label, or one from the closed list (C5), for example \"Get a free quote\". */\n readonly label: string\n /** The quote form this falls back to without scripts, for example `/contact#quote`. */\n readonly href: string\n /** Where the button sits, for `lead_cta_click`. */\n readonly placement: LeadCtaPlacement\n /** A stable id for `lead_cta_click`, for example `quote-header` or `quote-cta`. */\n readonly ctaId: string\n}\n\nexport function QuoteButton({ label, href, placement, ctaId, variant = 'primary', layout, classes }: QuoteButtonProps) {\n return (\n <Link\n href={href}\n data-bl=\"quote-button\"\n data-variant={variant}\n data-size={layout?.size ?? 'regular'}\n data-bl-cta={ctaId}\n data-bl-cta-placement={placement}\n className={classes?.root}\n >\n <span {...slot(classes, 'label')}>{label}</span>\n {layout?.icon === false ? null : (\n <span {...slot(classes, 'icon')}>\n <Icon name=\"arrow\" />\n </span>\n )}\n </Link>\n )\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skin-index.d.ts","sourceRoot":"","sources":["../../src/ui-kit/skin-index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;AACpH,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skin-index.js","sourceRoot":"","sources":["../../src/ui-kit/skin-index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAyE,MAAM,WAAW,CAAA","sourcesContent":["export { cx, skinned, slot, type SkinEntry, type SkinEntryOf, type SkinnedProps, type SlotClasses } from './skin.js'\nexport type { BrandlabSkin } from './brandlab-skin.js'\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The skin model of the Brandlab ui-kit. A site gives every shared component its own look through one skin entry per
|
|
3
|
+
* component: a variant from the component's closed list, typed layout options and one class string per named slot.
|
|
4
|
+
* Colours, fonts and shapes come from the site's `--bl-*` tokens and these classes; behaviour, semantics, accessible
|
|
5
|
+
* names and analytics hooks never appear here, so a skin can't change them.
|
|
6
|
+
*/
|
|
7
|
+
import { type ComponentType } from 'react';
|
|
8
|
+
/** One class string per named slot. Tailwind v4 utilities, CSS Module class names or nothing (plain CSS on hooks). */
|
|
9
|
+
export type SlotClasses<Slot extends string> = Readonly<Partial<Record<Slot, string>>>;
|
|
10
|
+
/** A component's skin: its variant, its layout options and its slot classes. Every field is optional. */
|
|
11
|
+
export interface SkinEntry<Slot extends string, Variant extends string = never, Layout extends object = Record<never, never>> {
|
|
12
|
+
readonly variant?: Variant;
|
|
13
|
+
readonly layout?: Readonly<Layout>;
|
|
14
|
+
readonly classes?: SlotClasses<Slot>;
|
|
15
|
+
}
|
|
16
|
+
type SkinKeys = 'variant' | 'layout' | 'classes';
|
|
17
|
+
/** The part of a component's props a skin entry supplies. */
|
|
18
|
+
export type SkinEntryOf<P> = Pick<P, Extract<keyof P, SkinKeys>>;
|
|
19
|
+
/** What a page passes to a skinned component: its data, and at most a `layout` adjustment within the contract. */
|
|
20
|
+
export type SkinnedProps<P> = Omit<P, 'variant' | 'classes'>;
|
|
21
|
+
/**
|
|
22
|
+
* Bind a component to the site's skin entry, so pages pass data only. Returns a plain function component (no
|
|
23
|
+
* context, no client code), so it works in server components and client components alike. The skin's layout merges
|
|
24
|
+
* under the page's `layout`; the variant and classes come from the skin only.
|
|
25
|
+
*
|
|
26
|
+
* ```tsx
|
|
27
|
+
* // src/site/ui.tsx
|
|
28
|
+
* export const ReviewsBand = skinned(UI.ReviewsBand, skin.reviewsBand)
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function skinned<P extends {
|
|
32
|
+
variant?: unknown;
|
|
33
|
+
layout?: object;
|
|
34
|
+
classes?: object;
|
|
35
|
+
}>(Component: ComponentType<P>, entry: SkinEntryOf<P>): ComponentType<SkinnedProps<P>>;
|
|
36
|
+
/** Join class strings, dropping empty parts. Returns `undefined` when nothing is left, so no empty `class` renders. */
|
|
37
|
+
export declare function cx(...parts: readonly (string | false | null | undefined)[]): string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* The stable hook for one named part: `data-slot` plus the skin's class for it. Spread it onto the element:
|
|
40
|
+
* `<div {...slot(classes, 'inner')}>`. Slot names, nesting and element types are public API (a minor release to
|
|
41
|
+
* change); a skin's class never replaces the hook.
|
|
42
|
+
*/
|
|
43
|
+
export declare function slot<Slot extends string>(classes: SlotClasses<Slot> | undefined, name: Slot, extra?: string): {
|
|
44
|
+
'data-slot': Slot;
|
|
45
|
+
className: string | undefined;
|
|
46
|
+
};
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=skin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skin.d.ts","sourceRoot":"","sources":["../../src/ui-kit/skin.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAiB,KAAK,aAAa,EAAkB,MAAM,OAAO,CAAA;AAEzE,sHAAsH;AACtH,MAAM,MAAM,WAAW,CAAC,IAAI,SAAS,MAAM,IAAI,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;AAEtF,yGAAyG;AACzG,MAAM,WAAW,SAAS,CACxB,IAAI,SAAS,MAAM,EACnB,OAAO,SAAS,MAAM,GAAG,KAAK,EAC9B,MAAM,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC;IAE5C,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,IAAI,CAAC,CAAA;CACrC;AAED,KAAK,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAA;AAEhD,6DAA6D;AAC7D,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAA;AAEhE,kHAAkH;AAClH,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC,CAAA;AAE5D;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,EACxF,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,EAC3B,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GACpB,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAehC;AAED,uHAAuH;AACvH,wBAAgB,EAAE,CAAC,GAAG,KAAK,EAAE,SAAS,CAAC,MAAM,GAAG,KAAK,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,GAAG,MAAM,GAAG,SAAS,CAG/F;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,IAAI,SAAS,MAAM,EACtC,OAAO,EAAE,WAAW,CAAC,IAAI,CAAC,GAAG,SAAS,EACtC,IAAI,EAAE,IAAI,EACV,KAAK,CAAC,EAAE,MAAM,GACb;IAAE,WAAW,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAEtD"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The skin model of the Brandlab ui-kit. A site gives every shared component its own look through one skin entry per
|
|
3
|
+
* component: a variant from the component's closed list, typed layout options and one class string per named slot.
|
|
4
|
+
* Colours, fonts and shapes come from the site's `--bl-*` tokens and these classes; behaviour, semantics, accessible
|
|
5
|
+
* names and analytics hooks never appear here, so a skin can't change them.
|
|
6
|
+
*/
|
|
7
|
+
import { createElement } from 'react';
|
|
8
|
+
/**
|
|
9
|
+
* Bind a component to the site's skin entry, so pages pass data only. Returns a plain function component (no
|
|
10
|
+
* context, no client code), so it works in server components and client components alike. The skin's layout merges
|
|
11
|
+
* under the page's `layout`; the variant and classes come from the skin only.
|
|
12
|
+
*
|
|
13
|
+
* ```tsx
|
|
14
|
+
* // src/site/ui.tsx
|
|
15
|
+
* export const ReviewsBand = skinned(UI.ReviewsBand, skin.reviewsBand)
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export function skinned(Component, entry) {
|
|
19
|
+
const skin = entry;
|
|
20
|
+
function Skinned(props) {
|
|
21
|
+
const pageLayout = props.layout;
|
|
22
|
+
const layout = skin.layout || pageLayout ? { ...(skin.layout ?? {}), ...(pageLayout ?? {}) } : undefined;
|
|
23
|
+
const merged = {
|
|
24
|
+
...props,
|
|
25
|
+
...(skin.variant !== undefined ? { variant: skin.variant } : {}),
|
|
26
|
+
...(layout ? { layout } : {}),
|
|
27
|
+
...(skin.classes ? { classes: skin.classes } : {}),
|
|
28
|
+
};
|
|
29
|
+
return createElement(Component, merged);
|
|
30
|
+
}
|
|
31
|
+
Skinned.displayName = `Skinned(${Component.displayName ?? Component.name ?? 'Component'})`;
|
|
32
|
+
return Skinned;
|
|
33
|
+
}
|
|
34
|
+
/** Join class strings, dropping empty parts. Returns `undefined` when nothing is left, so no empty `class` renders. */
|
|
35
|
+
export function cx(...parts) {
|
|
36
|
+
const joined = parts.filter(Boolean).join(' ');
|
|
37
|
+
return joined === '' ? undefined : joined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The stable hook for one named part: `data-slot` plus the skin's class for it. Spread it onto the element:
|
|
41
|
+
* `<div {...slot(classes, 'inner')}>`. Slot names, nesting and element types are public API (a minor release to
|
|
42
|
+
* change); a skin's class never replaces the hook.
|
|
43
|
+
*/
|
|
44
|
+
export function slot(classes, name, extra) {
|
|
45
|
+
return { 'data-slot': name, className: cx(classes?.[name], extra) };
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=skin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skin.js","sourceRoot":"","sources":["../../src/ui-kit/skin.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,aAAa,EAAsC,MAAM,OAAO,CAAA;AAwBzE;;;;;;;;;GASG;AACH,MAAM,UAAU,OAAO,CACrB,SAA2B,EAC3B,KAAqB;IAErB,MAAM,IAAI,GAAG,KAAiE,CAAA;IAC9E,SAAS,OAAO,CAAC,KAAsB;QACrC,MAAM,UAAU,GAAI,KAA6B,CAAC,MAAM,CAAA;QACxD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;QACxG,MAAM,MAAM,GAAG;YACb,GAAG,KAAK;YACR,GAAG,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAChE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7B,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACnC,CAAA;QACjB,OAAO,aAAa,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;IACzC,CAAC;IACD,OAAO,CAAC,WAAW,GAAG,WAAW,SAAS,CAAC,WAAW,IAAI,SAAS,CAAC,IAAI,IAAI,WAAW,GAAG,CAAA;IAC1F,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,uHAAuH;AACvH,MAAM,UAAU,EAAE,CAAC,GAAG,KAAqD;IACzE,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC9C,OAAO,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAA;AAC3C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,IAAI,CAClB,OAAsC,EACtC,IAAU,EACV,KAAc;IAEd,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,EAAE,CAAA;AACrE,CAAC","sourcesContent":["/**\n * The skin model of the Brandlab ui-kit. A site gives every shared component its own look through one skin entry per\n * component: a variant from the component's closed list, typed layout options and one class string per named slot.\n * Colours, fonts and shapes come from the site's `--bl-*` tokens and these classes; behaviour, semantics, accessible\n * names and analytics hooks never appear here, so a skin can't change them.\n */\nimport { createElement, type ComponentType, type ReactNode } from 'react'\n\n/** One class string per named slot. Tailwind v4 utilities, CSS Module class names or nothing (plain CSS on hooks). */\nexport type SlotClasses<Slot extends string> = Readonly<Partial<Record<Slot, string>>>\n\n/** A component's skin: its variant, its layout options and its slot classes. Every field is optional. */\nexport interface SkinEntry<\n Slot extends string,\n Variant extends string = never,\n Layout extends object = Record<never, never>,\n> {\n readonly variant?: Variant\n readonly layout?: Readonly<Layout>\n readonly classes?: SlotClasses<Slot>\n}\n\ntype SkinKeys = 'variant' | 'layout' | 'classes'\n\n/** The part of a component's props a skin entry supplies. */\nexport type SkinEntryOf<P> = Pick<P, Extract<keyof P, SkinKeys>>\n\n/** What a page passes to a skinned component: its data, and at most a `layout` adjustment within the contract. */\nexport type SkinnedProps<P> = Omit<P, 'variant' | 'classes'>\n\n/**\n * Bind a component to the site's skin entry, so pages pass data only. Returns a plain function component (no\n * context, no client code), so it works in server components and client components alike. The skin's layout merges\n * under the page's `layout`; the variant and classes come from the skin only.\n *\n * ```tsx\n * // src/site/ui.tsx\n * export const ReviewsBand = skinned(UI.ReviewsBand, skin.reviewsBand)\n * ```\n */\nexport function skinned<P extends { variant?: unknown; layout?: object; classes?: object }>(\n Component: ComponentType<P>,\n entry: SkinEntryOf<P>,\n): ComponentType<SkinnedProps<P>> {\n const skin = entry as { variant?: unknown; layout?: object; classes?: object }\n function Skinned(props: SkinnedProps<P>): ReactNode {\n const pageLayout = (props as { layout?: object }).layout\n const layout = skin.layout || pageLayout ? { ...(skin.layout ?? {}), ...(pageLayout ?? {}) } : undefined\n const merged = {\n ...props,\n ...(skin.variant !== undefined ? { variant: skin.variant } : {}),\n ...(layout ? { layout } : {}),\n ...(skin.classes ? { classes: skin.classes } : {}),\n } as unknown as P\n return createElement(Component, merged)\n }\n Skinned.displayName = `Skinned(${Component.displayName ?? Component.name ?? 'Component'})`\n return Skinned\n}\n\n/** Join class strings, dropping empty parts. Returns `undefined` when nothing is left, so no empty `class` renders. */\nexport function cx(...parts: readonly (string | false | null | undefined)[]): string | undefined {\n const joined = parts.filter(Boolean).join(' ')\n return joined === '' ? undefined : joined\n}\n\n/**\n * The stable hook for one named part: `data-slot` plus the skin's class for it. Spread it onto the element:\n * `<div {...slot(classes, 'inner')}>`. Slot names, nesting and element types are public API (a minor release to\n * change); a skin's class never replaces the hook.\n */\nexport function slot<Slot extends string>(\n classes: SlotClasses<Slot> | undefined,\n name: Slot,\n extra?: string,\n): { 'data-slot': Slot; className: string | undefined } {\n return { 'data-slot': name, className: cx(classes?.[name], extra) }\n}\n"]}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/* @brandlab-digital/site-next ui-kit structure.css: behaviour and layout only. Each site styles the components. */
|
|
2
|
+
@layer theme, base, bl-structure, components, utilities;
|
|
3
|
+
|
|
4
|
+
@layer bl-structure {
|
|
5
|
+
/* 00-base.css */
|
|
6
|
+
/* Base: scoped resets, focus, scroll padding, the visually-hidden utility and reduced motion. */
|
|
7
|
+
|
|
8
|
+
:where([data-bl], [data-bl-section]),
|
|
9
|
+
:where([data-bl], [data-bl-section]) *,
|
|
10
|
+
:where([data-bl], [data-bl-section]) *::before,
|
|
11
|
+
:where([data-bl], [data-bl-section]) *::after {
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
:where([data-bl], [data-bl-section]) :where(svg) {
|
|
16
|
+
display: block;
|
|
17
|
+
flex-shrink: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
:where([data-bl], [data-bl-section]) :where([hidden]:not([hidden='until-found'])) {
|
|
21
|
+
display: none !important;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* WCAG 2.4.11: a focused element is never hidden under the sticky header or the call bar. */
|
|
25
|
+
html {
|
|
26
|
+
scroll-padding-top: calc(var(--bl-header-height, 0px) + 0.5rem);
|
|
27
|
+
scroll-padding-bottom: calc(var(--bl-callbar-height, 0px) + 0.5rem);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
:where([data-bl], [data-bl-section]) :focus-visible {
|
|
31
|
+
outline: 2px solid var(--bl-color-focus, currentColor);
|
|
32
|
+
outline-offset: 3px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
[data-bl-visually-hidden] {
|
|
36
|
+
position: absolute !important;
|
|
37
|
+
width: 1px !important;
|
|
38
|
+
height: 1px !important;
|
|
39
|
+
overflow: hidden !important;
|
|
40
|
+
clip-path: inset(50%) !important;
|
|
41
|
+
white-space: nowrap !important;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
[data-bl='container'] {
|
|
45
|
+
width: 100%;
|
|
46
|
+
max-width: var(--bl-container, 80rem);
|
|
47
|
+
margin-inline: auto;
|
|
48
|
+
padding-inline: var(--bl-gutter, 1.25rem);
|
|
49
|
+
}
|
|
50
|
+
[data-bl='container'][data-width='narrow'] {
|
|
51
|
+
max-width: min(var(--bl-container, 80rem), 48rem);
|
|
52
|
+
}
|
|
53
|
+
[data-bl='container'][data-width='wide'] {
|
|
54
|
+
max-width: calc(var(--bl-container, 80rem) + 12rem);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
[data-bl-section] {
|
|
58
|
+
position: relative;
|
|
59
|
+
padding-block: var(--bl-space-section, 4rem);
|
|
60
|
+
}
|
|
61
|
+
[data-bl-section] > [data-slot='inner'] {
|
|
62
|
+
position: relative;
|
|
63
|
+
max-width: var(--bl-container, 80rem);
|
|
64
|
+
margin-inline: auto;
|
|
65
|
+
padding-inline: var(--bl-gutter, 1.25rem);
|
|
66
|
+
}
|
|
67
|
+
[data-bl-section] > [data-slot='decoration'] {
|
|
68
|
+
position: absolute;
|
|
69
|
+
inset: 0;
|
|
70
|
+
pointer-events: none;
|
|
71
|
+
overflow: hidden;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* WCAG 2.5.8 and the 44 px rule for primary actions. */
|
|
75
|
+
[data-bl='button'],
|
|
76
|
+
[data-bl='quote-button'] {
|
|
77
|
+
display: inline-flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
justify-content: center;
|
|
80
|
+
gap: 0.5rem;
|
|
81
|
+
min-height: 44px;
|
|
82
|
+
text-decoration: none;
|
|
83
|
+
}
|
|
84
|
+
[data-bl='phone-link'] {
|
|
85
|
+
display: inline-flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
gap: 0.5rem;
|
|
88
|
+
min-height: 24px;
|
|
89
|
+
}
|
|
90
|
+
[data-bl='phone-link'][data-variant='button'] {
|
|
91
|
+
min-height: 44px;
|
|
92
|
+
}
|
|
93
|
+
:where([data-bl='button'], [data-bl='quote-button'], [data-bl='phone-link']) [data-slot='icon'] svg {
|
|
94
|
+
width: 1.25em;
|
|
95
|
+
height: 1.25em;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
[data-bl='stars'] {
|
|
99
|
+
display: inline-flex;
|
|
100
|
+
gap: 0.125rem;
|
|
101
|
+
color: var(--bl-color-star, #f5b301);
|
|
102
|
+
}
|
|
103
|
+
[data-bl='stars'] [data-slot='star'] svg {
|
|
104
|
+
width: 1em;
|
|
105
|
+
height: 1em;
|
|
106
|
+
}
|
|
107
|
+
[data-bl='stars'] [data-slot='star'][data-state='off'] {
|
|
108
|
+
opacity: 0.3;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@media (prefers-reduced-motion: reduce) {
|
|
112
|
+
:where([data-bl], [data-bl-section]),
|
|
113
|
+
:where([data-bl], [data-bl-section]) *,
|
|
114
|
+
:where([data-bl], [data-bl-section]) *::before,
|
|
115
|
+
:where([data-bl], [data-bl-section]) *::after {
|
|
116
|
+
animation-duration: 0.01ms !important;
|
|
117
|
+
animation-iteration-count: 1 !important;
|
|
118
|
+
transition-duration: 0.01ms !important;
|
|
119
|
+
scroll-behavior: auto !important;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data shapes shared by the ui-kit components. A site passes data in these shapes; it never passes markup, ARIA or
|
|
3
|
+
* event props, so behaviour and semantics stay locked in the components.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* A phone number ready to render: the number as the business shows it and its `tel:` href. Take both from the
|
|
7
|
+
* derived business facts (`deriveBusinessFacts(business).phone`), never type them by hand: C10 holds every `tel:` to
|
|
8
|
+
* the evidence-file phone. Mobiles and landlines use `tel:+61…`; 13, 1300 and 1800 numbers use their national digits.
|
|
9
|
+
*/
|
|
10
|
+
export interface UiPhone {
|
|
11
|
+
readonly display: string;
|
|
12
|
+
readonly telHref: string;
|
|
13
|
+
}
|
|
14
|
+
/** A plain link. */
|
|
15
|
+
export interface UiLink {
|
|
16
|
+
readonly label: string;
|
|
17
|
+
readonly href: string;
|
|
18
|
+
}
|
|
19
|
+
export declare const UI_SOCIAL_KINDS: readonly ["facebook", "instagram", "google", "linkedin", "youtube", "tiktok"];
|
|
20
|
+
export type UiSocialKind = (typeof UI_SOCIAL_KINDS)[number];
|
|
21
|
+
/** A social or Business Profile link. `label` is the accessible name, for example "Abloom on Facebook". */
|
|
22
|
+
export interface UiSocial {
|
|
23
|
+
readonly kind: UiSocialKind;
|
|
24
|
+
readonly label: string;
|
|
25
|
+
readonly href: string;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/ui-kit/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED,oBAAoB;AACpB,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,eAAO,MAAM,eAAe,+EAAgF,CAAA;AAC5G,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAE3D,2GAA2G;AAC3G,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data shapes shared by the ui-kit components. A site passes data in these shapes; it never passes markup, ARIA or
|
|
3
|
+
* event props, so behaviour and semantics stay locked in the components.
|
|
4
|
+
*/
|
|
5
|
+
export const UI_SOCIAL_KINDS = ['facebook', 'instagram', 'google', 'linkedin', 'youtube', 'tiktok'];
|
|
6
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/ui-kit/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAkBH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAU,CAAA","sourcesContent":["/**\n * Data shapes shared by the ui-kit components. A site passes data in these shapes; it never passes markup, ARIA or\n * event props, so behaviour and semantics stay locked in the components.\n */\n\n/**\n * A phone number ready to render: the number as the business shows it and its `tel:` href. Take both from the\n * derived business facts (`deriveBusinessFacts(business).phone`), never type them by hand: C10 holds every `tel:` to\n * the evidence-file phone. Mobiles and landlines use `tel:+61…`; 13, 1300 and 1800 numbers use their national digits.\n */\nexport interface UiPhone {\n readonly display: string\n readonly telHref: string\n}\n\n/** A plain link. */\nexport interface UiLink {\n readonly label: string\n readonly href: string\n}\n\nexport const UI_SOCIAL_KINDS = ['facebook', 'instagram', 'google', 'linkedin', 'youtube', 'tiktok'] as const\nexport type UiSocialKind = (typeof UI_SOCIAL_KINDS)[number]\n\n/** A social or Business Profile link. `label` is the accessible name, for example \"Abloom on Facebook\". */\nexport interface UiSocial {\n readonly kind: UiSocialKind\n readonly label: string\n readonly href: string\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brandlab-digital/site-next",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"private": false,
|
|
11
11
|
"type": "module",
|
|
12
12
|
"sideEffects": [
|
|
13
|
-
"./dist/image-runtime.js"
|
|
13
|
+
"./dist/image-runtime.js",
|
|
14
|
+
"./dist/ui-kit/structure.css"
|
|
14
15
|
],
|
|
15
16
|
"files": [
|
|
16
17
|
"dist"
|
|
@@ -19,12 +20,12 @@
|
|
|
19
20
|
"access": "public"
|
|
20
21
|
},
|
|
21
22
|
"scripts": {
|
|
22
|
-
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
23
|
+
"build": "rm -rf dist && tsc -p tsconfig.json && node scripts/build-structure-css.mjs",
|
|
23
24
|
"test": "vitest run",
|
|
24
25
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
25
26
|
},
|
|
26
27
|
"dependencies": {
|
|
27
|
-
"@brandlab-digital/site-core": "3.
|
|
28
|
+
"@brandlab-digital/site-core": "3.8.0",
|
|
28
29
|
"browser-image-compression": "2.0.2",
|
|
29
30
|
"pdf-lib": "1.17.1",
|
|
30
31
|
"server-only": "0.0.1",
|
|
@@ -152,6 +153,18 @@
|
|
|
152
153
|
"./testing/server-only": {
|
|
153
154
|
"types": "./dist/testing/server-only.d.ts",
|
|
154
155
|
"import": "./dist/testing/server-only.js"
|
|
156
|
+
},
|
|
157
|
+
"./ui-kit/skin": {
|
|
158
|
+
"types": "./dist/ui-kit/skin-index.d.ts",
|
|
159
|
+
"import": "./dist/ui-kit/skin-index.js"
|
|
160
|
+
},
|
|
161
|
+
"./ui-kit/primitives": {
|
|
162
|
+
"types": "./dist/ui-kit/primitives/index.d.ts",
|
|
163
|
+
"import": "./dist/ui-kit/primitives/index.js"
|
|
164
|
+
},
|
|
165
|
+
"./ui-kit/structure.css": {
|
|
166
|
+
"style": "./dist/ui-kit/structure.css",
|
|
167
|
+
"default": "./dist/ui-kit/structure.css"
|
|
155
168
|
}
|
|
156
169
|
}
|
|
157
170
|
}
|