@evinvest/kitstart 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/dist/react/AreaChips.d.ts +2 -1
- package/dist/react/AreaChips.js +2 -2
- package/dist/react/Coverage.d.ts +2 -1
- package/dist/react/Coverage.js +2 -1
- package/dist/react/Faq.d.ts +1 -1
- package/dist/react/Faq.js +2 -1
- package/dist/react/LangSwitch.js +8 -1
- package/dist/react/MapFacade.d.ts +4 -1
- package/dist/react/MapFacade.js +2 -2
- package/dist/react/PlaceDirectory.d.ts +1 -1
- package/dist/react/PlaceDirectory.js +2 -1
- package/dist/react/StatusScreen.d.ts +2 -2
- package/dist/react/StatusScreen.js +2 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/parts.d.ts +7 -0
- package/dist/react/parts.js +33 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -103,7 +103,14 @@ Each widget is a Server Component unless it needs the browser (`MapFacade`,
|
|
|
103
103
|
`StatusScreen`, `PlaceDirectory`, `Coverage`), through its named parts:
|
|
104
104
|
`classNames={{ list: "rounded-none", answer: "px-2" }}`, merged after the
|
|
105
105
|
kit's classes so the brand's utility wins. No descendant selectors: they
|
|
106
|
-
break silently when a widget's markup moves.
|
|
106
|
+
break silently when a widget's markup moves. A size in a part keeps the
|
|
107
|
+
kit's line height: tailwind-merge drops an earlier `leading-*` for any later
|
|
108
|
+
font size, so the widgets put their `leading-*` after the brand's classes —
|
|
109
|
+
`code: "text-[88px]"` stays `leading-none`. To change the line height, name
|
|
110
|
+
it (`"text-[88px] leading-tight"`, or `"text-lg/7"`). The header's language
|
|
111
|
+
switch is `StatusScreen`'s `lang`: below `md` it takes a row of its own
|
|
112
|
+
(`order-last w-full`); `lang: "order-none w-auto"` keeps one row.
|
|
113
|
+
`QuoteFormShell` is headless: it owns the hidden fields and the
|
|
107
114
|
honeypot the funnel reads; the visible fields are the brand's children.
|
|
108
115
|
`StatusScreen` takes the brand's name and marks as props, so the client error
|
|
109
116
|
boundary never imports the site config.
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Where the van goes, as chips. A visitor's first question is whether you
|
|
3
3
|
* come to them at all; a service-area business has no map to show instead.
|
|
4
4
|
*/
|
|
5
|
-
export declare function AreaChips({ areas, className }: {
|
|
5
|
+
export declare function AreaChips({ areas, className, chipClassName }: {
|
|
6
6
|
areas: readonly string[];
|
|
7
7
|
className?: string;
|
|
8
|
+
chipClassName?: string;
|
|
8
9
|
}): import("react").JSX.Element | null;
|
package/dist/react/AreaChips.js
CHANGED
|
@@ -4,8 +4,8 @@ import { cn } from "@evinvest/uikit";
|
|
|
4
4
|
* Where the van goes, as chips. A visitor's first question is whether you
|
|
5
5
|
* come to them at all; a service-area business has no map to show instead.
|
|
6
6
|
*/
|
|
7
|
-
export function AreaChips({ areas, className }) {
|
|
7
|
+
export function AreaChips({ areas, className, chipClassName }) {
|
|
8
8
|
if (areas.length === 0)
|
|
9
9
|
return null;
|
|
10
|
-
return (_jsx("ul", { className: cn("flex flex-wrap gap-2 md:gap-2.5", className), children: areas.map(area => (_jsx("li", { className: "rounded-full border border-border bg-muted px-4 py-2 text-sm font-medium text-ink-mid", children: area }, area))) }));
|
|
10
|
+
return (_jsx("ul", { className: cn("flex flex-wrap gap-2 md:gap-2.5", className), children: areas.map(area => (_jsx("li", { className: cn("rounded-full border border-border bg-muted px-4 py-2 text-sm font-medium text-ink-mid", chipClassName), children: area }, area))) }));
|
|
11
11
|
}
|
package/dist/react/Coverage.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { type Place } from "../core/place/types.js";
|
|
3
3
|
import type { PartClassNames } from "./parts.js";
|
|
4
|
-
|
|
4
|
+
/** `chip` is each commune in `chips`; `mapShow` and `mapAddress` are the map button's two lines. */
|
|
5
|
+
export type CoveragePart = "chips" | "chip" | "landmark" | "map" | "mapShow" | "mapAddress";
|
|
5
6
|
/**
|
|
6
7
|
* Where a place works: its communes as chips, and — for a storefront only —
|
|
7
8
|
* its landmark and a map behind a click. A service-area business has no
|
package/dist/react/Coverage.js
CHANGED
|
@@ -6,5 +6,6 @@ import { MapFacade } from "./MapFacade.js";
|
|
|
6
6
|
export function Coverage({ place, locale, map, head, className, classNames: c }) {
|
|
7
7
|
const front = storefrontOf(place);
|
|
8
8
|
const address = front && `${front.address.street}, ${front.address.postalCode} ${front.address.locality}`;
|
|
9
|
-
|
|
9
|
+
const mapParts = { ...(c?.mapShow ? { show: c.mapShow } : {}), ...(c?.mapAddress ? { address: c.mapAddress } : {}) };
|
|
10
|
+
return (_jsxs("div", { className: cn("flex flex-col gap-6 md:gap-8", className), children: [head, _jsx(AreaChips, { areas: servedLocalities(place), ...(c?.chips ? { className: c.chips } : {}), ...(c?.chip ? { chipClassName: c.chip } : {}) }), front?.landmark && _jsx("p", { className: cn("text-sm text-ink-soft", c?.landmark), children: front.landmark[locale] }), address && map && _jsx(MapFacade, { query: `${place.gbpName}, ${address}`, title: map.title, show: map.show, address: address, ...(c?.map ? { className: c.map } : {}), classNames: mapParts })] }));
|
|
10
11
|
}
|
package/dist/react/Faq.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type { QuestionAnswer } from "../core/seo/ld.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type PartClassNames } from "./parts.js";
|
|
4
4
|
export type FaqPart = "list" | "item" | "summary" | "question" | "icon" | "answer";
|
|
5
5
|
/**
|
|
6
6
|
* `<details>`, not an accordion component: it opens without hydration, is
|
package/dist/react/Faq.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "@evinvest/uikit";
|
|
3
|
+
import { partWithLeading } from "./parts.js";
|
|
3
4
|
export function Faq({ items, head, id = "faq", className, classNames: c }) {
|
|
4
|
-
return (_jsxs("div", { id: id, className: cn("flex flex-col gap-6 md:gap-11", className), children: [head, _jsx("div", { className: cn("overflow-hidden rounded-xl border border-border bg-background", c?.list), children: items.map(item => (_jsxs("details", { className: cn("group border-b border-border last:border-b-0", c?.item), children: [_jsxs("summary", { className: cn("flex cursor-pointer list-none items-start gap-4 px-5 py-5 md:px-8 md:py-6 [&::-webkit-details-marker]:hidden", c?.summary), children: [_jsx("span", { className:
|
|
5
|
+
return (_jsxs("div", { id: id, className: cn("flex flex-col gap-6 md:gap-11", className), children: [head, _jsx("div", { className: cn("overflow-hidden rounded-xl border border-border bg-background", c?.list), children: items.map(item => (_jsxs("details", { className: cn("group border-b border-border last:border-b-0", c?.item), children: [_jsxs("summary", { className: cn("flex cursor-pointer list-none items-start gap-4 px-5 py-5 md:px-8 md:py-6 [&::-webkit-details-marker]:hidden", c?.summary), children: [_jsx("span", { className: partWithLeading("flex-1 font-display text-lg font-bold text-ink", "leading-snug", c?.question), children: item.q }), _jsx("span", { className: cn("text-lg text-primary-ink transition-transform group-open:rotate-45", c?.icon), "aria-hidden": "true", children: "+" })] }), _jsx("p", { className: partWithLeading("px-5 pb-5 text-base text-ink-soft md:px-8 md:pb-6", "leading-relaxed", c?.answer), children: item.a })] }, item.q))) })] }));
|
|
5
6
|
}
|
package/dist/react/LangSwitch.js
CHANGED
|
@@ -12,6 +12,13 @@ export function withLang(href, locale) {
|
|
|
12
12
|
return `//${url.host}${rest}`;
|
|
13
13
|
return /^[a-z][a-z0-9+.-]*:/i.test(href) ? `${url.origin}${rest}` : rest;
|
|
14
14
|
}
|
|
15
|
+
// A hit area around each code, centred on it by a pseudo-element so the visual
|
|
16
|
+
// size and the focus ring stay the link's own: 44 px tall (Apple's HIG), and
|
|
17
|
+
// 6 px wider on each side — about half the gap-dot-gap between codes, so
|
|
18
|
+
// neighbours never overlap and a tap always lands on the code it is nearest to.
|
|
19
|
+
// That still clears WCAG 2.5.8's 24 px; a full 44 px width would need wider
|
|
20
|
+
// spacing between the codes.
|
|
21
|
+
const HIT_AREA = "relative after:absolute after:top-1/2 after:left-1/2 after:h-full after:min-h-11 after:w-[calc(100%+0.75rem)] after:-translate-x-1/2 after:-translate-y-1/2";
|
|
15
22
|
export function LangSwitch({ current, locales, hrefs, labels, label = "Language", className }) {
|
|
16
|
-
return (_jsx("nav", { "aria-label": label, className: cn("flex items-center gap-1.5", className), children: locales.map((locale, i) => (_jsxs("span", { className: "flex items-center gap-1.5", children: [i > 0 && (_jsx("span", { "aria-hidden": "true", className: "opacity-40", children: "\u00B7" })), _jsx("a", { href: withLang(hrefs[locale], locale), hrefLang: locale, lang: locale, "aria-label": labels?.[locale], "aria-current": locale === current ? "true" : undefined, className: locale === current ? "font-semibold" : "opacity-60 hover:opacity-100", children: locale.toUpperCase() })] }, locale))) }));
|
|
23
|
+
return (_jsx("nav", { "aria-label": label, className: cn("flex items-center gap-1.5", className), children: locales.map((locale, i) => (_jsxs("span", { className: "flex items-center gap-1.5", children: [i > 0 && (_jsx("span", { "aria-hidden": "true", className: "opacity-40", children: "\u00B7" })), _jsx("a", { href: withLang(hrefs[locale], locale), hrefLang: locale, lang: locale, "aria-label": labels?.[locale], "aria-current": locale === current ? "true" : undefined, className: cn(HIT_AREA, locale === current ? "font-semibold" : "opacity-60 hover:opacity-100"), children: locale.toUpperCase() })] }, locale))) }));
|
|
17
24
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { PartClassNames } from "./parts.js";
|
|
2
|
+
export type MapFacadePart = "show" | "address";
|
|
1
3
|
/**
|
|
2
4
|
* Google's map behind a click. Until the visitor asks, the page holds a button
|
|
3
5
|
* and nothing else — no Google request, no cookie — so a cookieless page stays
|
|
@@ -14,5 +16,6 @@ export interface MapFacadeProps {
|
|
|
14
16
|
/** The address, printed under the call to action. */
|
|
15
17
|
address: string;
|
|
16
18
|
className?: string;
|
|
19
|
+
classNames?: PartClassNames<MapFacadePart>;
|
|
17
20
|
}
|
|
18
|
-
export declare function MapFacade({ query, title, show, address, className }: MapFacadeProps): import("react").JSX.Element;
|
|
21
|
+
export declare function MapFacade({ query, title, show, address, className, classNames: c }: MapFacadeProps): import("react").JSX.Element;
|
package/dist/react/MapFacade.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { ClickToLoad } from "@evinvest/marketing/click-to-load";
|
|
4
4
|
import { cn } from "@evinvest/uikit";
|
|
5
|
-
export function MapFacade({ query, title, show, address, className }) {
|
|
5
|
+
export function MapFacade({ query, title, show, address, className, classNames: c }) {
|
|
6
6
|
const src = `https://www.google.com/maps?q=${encodeURIComponent(query)}&output=embed`;
|
|
7
|
-
return (_jsx(ClickToLoad, { className: cn("relative aspect-video w-full overflow-hidden rounded-xl border border-border bg-muted md:aspect-[21/9]", className), placeholder: load => (_jsxs("button", { type: "button", onClick: load, className: "flex size-full flex-col items-center justify-center gap-2 px-6 text-center hover:bg-hover", children: [_jsx("span", { className: "font-display text-lg font-bold text-ink", children: show }), _jsx("span", { className: "text-sm text-ink-soft", children: address })] })), children: _jsx("iframe", { title: title, src: src, loading: "lazy", referrerPolicy: "no-referrer-when-downgrade", className: "absolute inset-0 size-full border-0" }) }));
|
|
7
|
+
return (_jsx(ClickToLoad, { className: cn("relative aspect-video w-full overflow-hidden rounded-xl border border-border bg-muted md:aspect-[21/9]", className), placeholder: load => (_jsxs("button", { type: "button", onClick: load, className: "flex size-full flex-col items-center justify-center gap-2 px-6 text-center hover:bg-hover", children: [_jsx("span", { className: cn("font-display text-lg font-bold text-ink", c?.show), children: show }), _jsx("span", { className: cn("text-sm text-ink-soft", c?.address), children: address })] })), children: _jsx("iframe", { title: title, src: src, loading: "lazy", referrerPolicy: "no-referrer-when-downgrade", className: "absolute inset-0 size-full border-0" }) }));
|
|
8
8
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { type Place } from "../core/place/types.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type PartClassNames } from "./parts.js";
|
|
4
4
|
export type PlaceDirectoryPart = "list" | "card" | "name" | "address" | "phone" | "link";
|
|
5
5
|
/**
|
|
6
6
|
* The apex's one job on a `subdomains` site: send the visitor to their place.
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { cn } from "@evinvest/uikit";
|
|
3
3
|
import { telHref } from "@evinvest/marketing";
|
|
4
4
|
import { storefrontOf } from "../core/place/types.js";
|
|
5
|
+
import { partWithLeading } from "./parts.js";
|
|
5
6
|
export function PlaceDirectory(props) {
|
|
6
7
|
const { places, head, id = "points", className } = props;
|
|
7
8
|
return (_jsxs("div", { id: id, className: cn("flex flex-col gap-7 md:gap-10", className), children: [head, _jsx("ul", { className: cn("grid gap-4 sm:grid-cols-2 lg:grid-cols-3", props.classNames?.list), children: places.map(place => (_jsx(PlaceCard, { place: place, ...props }, place.slug))) })] }));
|
|
@@ -9,5 +10,5 @@ export function PlaceDirectory(props) {
|
|
|
9
10
|
function PlaceCard({ place, locale, hrefOf, phoneOf, openLabel, classNames: c }) {
|
|
10
11
|
const phone = phoneOf(place);
|
|
11
12
|
const front = storefrontOf(place);
|
|
12
|
-
return (_jsxs("li", { className: cn("flex flex-col gap-3 rounded-xl border border-border bg-card p-6 md:p-7", c?.card), children: [_jsx("p", { className: cn("font-display text-xl font-bold text-ink", c?.name), children: place.name[locale] }), front && (_jsxs("address", { className:
|
|
13
|
+
return (_jsxs("li", { className: cn("flex flex-col gap-3 rounded-xl border border-border bg-card p-6 md:p-7", c?.card), children: [_jsx("p", { className: cn("font-display text-xl font-bold text-ink", c?.name), children: place.name[locale] }), front && (_jsxs("address", { className: partWithLeading("text-sm not-italic text-ink-soft", "leading-relaxed", c?.address), children: [front.address.street, _jsx("br", {}), front.address.postalCode, " ", front.address.locality] })), phone && (_jsx("a", { href: telHref(phone), className: cn("font-display text-lg font-bold text-primary-ink", c?.phone), children: phone })), _jsxs("a", { href: hrefOf(place), className: cn("mt-auto text-sm font-medium text-ink underline-offset-4 hover:underline", c?.link), children: [openLabel, " \u2192"] })] }));
|
|
13
14
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type { CopySlice, StatusCopy, StatusScreenText } from "../core/content.js";
|
|
3
3
|
import type { StatusTarget } from "../core/status.js";
|
|
4
|
-
import type
|
|
5
|
-
export type StatusScreenPart = "header" | "logo" | "phone" | "main" | "eyebrow" | "code" | "headline" | "body" | "actions" | "primaryButton" | "secondaryButton" | "strip" | "footer";
|
|
4
|
+
import { type PartClassNames } from "./parts.js";
|
|
5
|
+
export type StatusScreenPart = "header" | "logo" | "lang" | "phone" | "main" | "eyebrow" | "code" | "headline" | "body" | "actions" | "primaryButton" | "secondaryButton" | "strip" | "footer";
|
|
6
6
|
/**
|
|
7
7
|
* The 404, the 500 and the post-submit confirmation: one screen over a
|
|
8
8
|
* `StatusCopy`, differing only in eyebrow, numeral, headline and buttons. A
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { telHref } from "@evinvest/marketing";
|
|
3
3
|
import { Button, Check, cn } from "@evinvest/uikit";
|
|
4
4
|
import { LangSwitch } from "./LangSwitch.js";
|
|
5
|
+
import { partWithLeading } from "./parts.js";
|
|
5
6
|
export function StatusScreen(props) {
|
|
6
7
|
const { copy, status, target, locales, labels, brandName, logo, mark, className, buttonClassName, classNames: c } = props;
|
|
7
8
|
const { t, f } = copy;
|
|
@@ -15,7 +16,7 @@ export function StatusScreen(props) {
|
|
|
15
16
|
// A brand with no phone falls back to "home" rather than a dead button.
|
|
16
17
|
const primary = action(status.primary) ?? action("home");
|
|
17
18
|
const secondary = action(status.secondary);
|
|
18
|
-
return (_jsxs("div", { className: cn("relative flex min-h-screen flex-col bg-background text-ink", className), children: [_jsxs("header", { className: cn("relative flex flex-wrap items-center gap-x-4 gap-y-2 border-b border-border px-5 py-3 md:flex-nowrap md:gap-5 md:px-12 md:py-5", c?.header), children: [_jsx("a", { href: target.home, "aria-label": brandName, className: cn("shrink-0 text-ink", c?.logo), children: logo ?? _jsx("span", { className: "font-display text-lg font-bold md:text-xl", children: brandName }) }), _jsx("div", { className: "flex-1" }), _jsx(LangSwitch, { current: copy.locale, locales: locales, hrefs: target.langHrefs, ...(labels ? { labels } : {}), className: "order-last w-full text-sm font-medium text-ink-soft md:order-none md:w-auto" }), target.phone && (_jsx("a", { href: telHref(target.phone), className: cn("whitespace-nowrap font-display text-base font-bold text-primary-ink md:text-xl", c?.phone), children: target.phone }))] }), _jsxs("main", { className: cn("relative flex flex-1 flex-col items-center gap-5 px-5 py-12 text-center md:gap-6 md:py-24", c?.main), children: [mark, _jsx("p", { className: cn("text-xs font-medium tracking-widest text-primary-ink", c?.eyebrow), children: status.eyebrow }), _jsx("p", { className:
|
|
19
|
+
return (_jsxs("div", { className: cn("relative flex min-h-screen flex-col bg-background text-ink", className), children: [_jsxs("header", { className: cn("relative flex flex-wrap items-center gap-x-4 gap-y-2 border-b border-border px-5 py-3 md:flex-nowrap md:gap-5 md:px-12 md:py-5", c?.header), children: [_jsx("a", { href: target.home, "aria-label": brandName, className: cn("shrink-0 text-ink", c?.logo), children: logo ?? _jsx("span", { className: "font-display text-lg font-bold md:text-xl", children: brandName }) }), _jsx("div", { className: "flex-1" }), _jsx(LangSwitch, { current: copy.locale, locales: locales, hrefs: target.langHrefs, ...(labels ? { labels } : {}), className: cn("order-last w-full text-sm font-medium text-ink-soft md:order-none md:w-auto", c?.lang) }), target.phone && (_jsx("a", { href: telHref(target.phone), className: cn("whitespace-nowrap font-display text-base font-bold text-primary-ink md:text-xl", c?.phone), children: target.phone }))] }), _jsxs("main", { className: cn("relative flex flex-1 flex-col items-center gap-5 px-5 py-12 text-center md:gap-6 md:py-24", c?.main), children: [mark, _jsx("p", { className: cn("text-xs font-medium tracking-widest text-primary-ink", c?.eyebrow), children: status.eyebrow }), _jsx("p", { className: partWithLeading("font-display text-8xl font-bold tracking-tight text-ink md:text-9xl", "leading-none", c?.code), children: status.code }), _jsxs("h1", { className: partWithLeading("font-display text-2xl font-bold text-ink md:text-4xl", "leading-snug", c?.headline), children: [status.headline[0], _jsx("span", { className: "text-primary-ink", children: status.headline[1] })] }), _jsx("p", { className: partWithLeading("max-w-2xl text-base text-ink-soft md:text-lg", "leading-relaxed", c?.body), children: status.body(f) }), _jsxs("div", { className: cn("flex flex-col gap-3.5 sm:flex-row", c?.actions), children: [primary && (_jsx(Button, { href: primary.href, size: "xl", className: cn(buttonClassName, c?.primaryButton), children: primary.label })), secondary && secondary.href !== primary?.href && (
|
|
19
20
|
// The kit's outline border is `--border`, 14 % ink: 1.4:1 on the
|
|
20
21
|
// dark polarity. Half ink clears 3:1 for a control on either one.
|
|
21
22
|
_jsx(Button, { href: secondary.href, size: "xl", variant: "outline", className: cn("border-ink/50", buttonClassName, c?.secondaryButton), children: secondary.label }))] }), _jsx("ul", { className: cn("flex flex-col items-center gap-3 text-sm sm:flex-row sm:gap-6", c?.strip), children: t.statusStrip.map(term => (_jsxs("li", { className: "flex items-center gap-2", children: [_jsx(Check, {}), _jsx("span", { className: "font-medium text-ink-soft", children: term })] }, term))) })] }), _jsx("footer", { className: cn("relative border-t border-border px-5 py-6 text-xs uppercase tracking-wider text-ink-soft md:px-12 md:py-7", c?.footer), children: _jsx("p", { children: t.facts(f).join(" · ") }) })] }));
|
package/dist/react/index.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export { CallBar, type CallBarPart, type CallBarProps } from "./CallBar.js";
|
|
|
16
16
|
export { Coverage, type CoveragePart, type CoverageProps } from "./Coverage.js";
|
|
17
17
|
export { Faq, type FaqPart, type FaqProps } from "./Faq.js";
|
|
18
18
|
export { LangSwitch, withLang, type LangSwitchProps } from "./LangSwitch.js";
|
|
19
|
-
export { MapFacade, type MapFacadeProps } from "./MapFacade.js";
|
|
19
|
+
export { MapFacade, type MapFacadePart, type MapFacadeProps } from "./MapFacade.js";
|
|
20
20
|
export { PlaceDirectory, type PlaceDirectoryPart, type PlaceDirectoryProps } from "./PlaceDirectory.js";
|
|
21
21
|
export { PHONE_INPUT_PROPS, QuoteFormShell, type QuoteFormShellProps } from "./QuoteFormShell.js";
|
|
22
22
|
export { StatusScreen, type StatusScreenPart, type StatusScreenProps } from "./StatusScreen.js";
|
package/dist/react/parts.d.ts
CHANGED
|
@@ -5,3 +5,10 @@
|
|
|
5
5
|
* when the widget's markup moves.
|
|
6
6
|
*/
|
|
7
7
|
export type PartClassNames<P extends string> = Readonly<Partial<Record<P, string>>>;
|
|
8
|
+
/**
|
|
9
|
+
* `cn(base, part)` for a part that has a line height: tailwind-merge drops an
|
|
10
|
+
* earlier `leading-*` for any later font size, so `text-[88px]` from a brand
|
|
11
|
+
* would silently take the kit's `leading-none` with it. `leading` goes after
|
|
12
|
+
* the brand's classes instead — unless they set a line height themselves.
|
|
13
|
+
*/
|
|
14
|
+
export declare function partWithLeading(base: string, leading: string, part: string | undefined): string;
|
package/dist/react/parts.js
CHANGED
|
@@ -1 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
import { cn } from "@evinvest/uikit";
|
|
2
|
+
// Probes no brand writes: a probe equal to a brand's class would merge into it
|
|
3
|
+
// and read as "no conflict".
|
|
4
|
+
const LEADING_PROBE = "leading-[0.001]";
|
|
5
|
+
const SIZE_PROBE = "text-[0.001px]";
|
|
6
|
+
const conflictsWith = (probe, token) => !cn(probe, token).split(" ").includes(probe);
|
|
7
|
+
/**
|
|
8
|
+
* Whether a part's classes set a line height of their own — a `leading-*`, or
|
|
9
|
+
* a size with one (`text-lg/7`) — at the base variant. tailwind-merge decides
|
|
10
|
+
* the conflicts, so variants and `!` are read the way the merge reads them.
|
|
11
|
+
*/
|
|
12
|
+
function setsLineHeight(part) {
|
|
13
|
+
return part
|
|
14
|
+
.split(/\s+/)
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
.some(token => {
|
|
17
|
+
if (!conflictsWith(LEADING_PROBE, token))
|
|
18
|
+
return false;
|
|
19
|
+
if (!conflictsWith(SIZE_PROBE, token))
|
|
20
|
+
return true;
|
|
21
|
+
// A size: it carries a line height only after a `/` outside brackets.
|
|
22
|
+
return token.replace(/\[[^\]]*\]/g, "").includes("/");
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* `cn(base, part)` for a part that has a line height: tailwind-merge drops an
|
|
27
|
+
* earlier `leading-*` for any later font size, so `text-[88px]` from a brand
|
|
28
|
+
* would silently take the kit's `leading-none` with it. `leading` goes after
|
|
29
|
+
* the brand's classes instead — unless they set a line height themselves.
|
|
30
|
+
*/
|
|
31
|
+
export function partWithLeading(base, leading, part) {
|
|
32
|
+
return part && setsLineHeight(part) ? cn(base, part) : cn(base, part, leading);
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evinvest/kitstart",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Landing-site machinery for local-service brands: a site composition root, the Place/Presence/ServiceArea model with a publication gate, host/locale routing, schema.org and SEO builders, the lead funnel with its antispam, and analytics events. Pure core at '.', more subpaths per runtime.",
|
|
5
5
|
"license": "BlueOak-1.0.0",
|
|
6
6
|
"publishConfig": {
|