@bygghammar/cms-platform 0.69.1 → 0.71.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/package.json +1 -1
- package/src/backoffice/inspectors/blocks.tsx +71 -0
- package/src/blocks/button-colors.ts +46 -0
- package/src/blocks/components/LinkTilesBlock.tsx +8 -1
- package/src/blocks/components/PriceTableBlock.tsx +3 -1
- package/src/blocks/index.ts +6 -0
- package/src/blocks/types.ts +5 -0
- package/src/site/routes/preview-page.tsx +3 -1
- package/src/site/routes/public-page.tsx +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bygghammar/cms-platform",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.71.0",
|
|
4
4
|
"description": "Shared CMS platform: blocks, block types, i18n core, UI primitives and design-token contract used by every Bygghammar/Voyara site.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* stay in each site's own inspector router.
|
|
6
6
|
*/
|
|
7
7
|
import { useEffect, useMemo, useState } from "react";
|
|
8
|
+
import { BUTTON_COLOR_DEFAULTS, type ButtonColors } from "../../blocks/button-colors";
|
|
8
9
|
import type {
|
|
9
10
|
BlockInstance,
|
|
10
11
|
BlockProps,
|
|
@@ -1281,6 +1282,12 @@ function LinkTilesInspector({ block, patch }: BlockPatchProps) {
|
|
|
1281
1282
|
</label>
|
|
1282
1283
|
<TextField label="Anchor id" value={p.anchor} onChange={(v) => update({ ...p, anchor: v })} />
|
|
1283
1284
|
</InspectorSection>
|
|
1285
|
+
<InspectorSection title="Button colours">
|
|
1286
|
+
<ButtonColorFields
|
|
1287
|
+
value={p.buttonColors}
|
|
1288
|
+
onChange={(next) => update({ ...p, buttonColors: next })}
|
|
1289
|
+
/>
|
|
1290
|
+
</InspectorSection>
|
|
1284
1291
|
<InspectorSection title={`Tiles (${items.length})`}>
|
|
1285
1292
|
<div className="space-y-4">
|
|
1286
1293
|
{items.map((item, i) => (
|
|
@@ -1503,6 +1510,64 @@ function CabinTabsInspector({ block, patch }: BlockPatchProps) {
|
|
|
1503
1510
|
);
|
|
1504
1511
|
}
|
|
1505
1512
|
|
|
1513
|
+
/* ---------------- Button colours ---------------- */
|
|
1514
|
+
/**
|
|
1515
|
+
* Colour controls for a block's CTA button. Empty values mean "use the brand
|
|
1516
|
+
* default", which matches the shared price-table button.
|
|
1517
|
+
*/
|
|
1518
|
+
function ButtonColorFields({
|
|
1519
|
+
value,
|
|
1520
|
+
onChange,
|
|
1521
|
+
}: {
|
|
1522
|
+
value?: ButtonColors;
|
|
1523
|
+
onChange: (next: ButtonColors) => void;
|
|
1524
|
+
}) {
|
|
1525
|
+
const v = value ?? {};
|
|
1526
|
+
const row = (label: string, key: keyof ButtonColors) => {
|
|
1527
|
+
const current = v[key]?.trim() || "";
|
|
1528
|
+
const swatch = current.startsWith("#") ? current : "#ffffff";
|
|
1529
|
+
return (
|
|
1530
|
+
<label key={key} className="block space-y-1.5">
|
|
1531
|
+
<span className="block text-[0.7rem] uppercase tracking-[0.22em] text-ink/60">{label}</span>
|
|
1532
|
+
<span className="flex items-center gap-2">
|
|
1533
|
+
<input
|
|
1534
|
+
type="color"
|
|
1535
|
+
value={swatch}
|
|
1536
|
+
onChange={(e) => onChange({ ...v, [key]: e.target.value })}
|
|
1537
|
+
className="h-9 w-12 shrink-0 cursor-pointer border border-ink/15 bg-white p-0.5"
|
|
1538
|
+
aria-label={`${label} colour picker`}
|
|
1539
|
+
/>
|
|
1540
|
+
<input
|
|
1541
|
+
type="text"
|
|
1542
|
+
value={current}
|
|
1543
|
+
placeholder={BUTTON_COLOR_DEFAULTS[key]}
|
|
1544
|
+
onChange={(e) => onChange({ ...v, [key]: e.target.value })}
|
|
1545
|
+
className="w-full border border-ink/15 bg-white px-2.5 py-1.5 text-sm text-ink focus:outline-none focus:border-sea"
|
|
1546
|
+
/>
|
|
1547
|
+
</span>
|
|
1548
|
+
</label>
|
|
1549
|
+
);
|
|
1550
|
+
};
|
|
1551
|
+
return (
|
|
1552
|
+
<>
|
|
1553
|
+
<p className="text-xs leading-relaxed text-ink/60">
|
|
1554
|
+
Leave a field empty to use the site default. Accepts hex values such as #ff4f9a.
|
|
1555
|
+
</p>
|
|
1556
|
+
{row("Background", "bg")}
|
|
1557
|
+
{row("Text", "text")}
|
|
1558
|
+
{row("Background on hover", "hoverBg")}
|
|
1559
|
+
{row("Text on hover", "hoverText")}
|
|
1560
|
+
<button
|
|
1561
|
+
type="button"
|
|
1562
|
+
onClick={() => onChange({})}
|
|
1563
|
+
className="text-xs uppercase tracking-[0.18em] text-ink/60 underline hover:text-ink"
|
|
1564
|
+
>
|
|
1565
|
+
Reset to defaults
|
|
1566
|
+
</button>
|
|
1567
|
+
</>
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1506
1571
|
/* ---------------- Price table ---------------- */
|
|
1507
1572
|
function PriceTableInspector({ block, patch }: BlockPatchProps) {
|
|
1508
1573
|
const p = (block.props ?? {}) as PriceTableProps;
|
|
@@ -1532,6 +1597,12 @@ function PriceTableInspector({ block, patch }: BlockPatchProps) {
|
|
|
1532
1597
|
<LocalizedField label="Button label" value={p.ctaLabel} onChange={(v) => update({ ...p, ctaLabel: v })} />
|
|
1533
1598
|
<TextField label="Button link" value={p.ctaHref} placeholder="/boka" onChange={(v) => update({ ...p, ctaHref: v })} />
|
|
1534
1599
|
</InspectorSection>
|
|
1600
|
+
<InspectorSection title="Button colours">
|
|
1601
|
+
<ButtonColorFields
|
|
1602
|
+
value={p.ctaColors}
|
|
1603
|
+
onChange={(next) => update({ ...p, ctaColors: next })}
|
|
1604
|
+
/>
|
|
1605
|
+
</InspectorSection>
|
|
1535
1606
|
<InspectorSection title="Layout">
|
|
1536
1607
|
<label className="block space-y-1.5">
|
|
1537
1608
|
<span className="block text-[0.7rem] uppercase tracking-[0.22em] text-ink/60">Columns (desktop)</span>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Editable colours for a block's call-to-action button.
|
|
5
|
+
* Every value is a plain CSS colour string (hex, rgb(), oklch(), var(--token)).
|
|
6
|
+
* Empty/undefined values fall back to the brand defaults below, which match the
|
|
7
|
+
* shared price-table CTA styling.
|
|
8
|
+
*/
|
|
9
|
+
export type ButtonColors = {
|
|
10
|
+
/** Background colour. */
|
|
11
|
+
bg?: string;
|
|
12
|
+
/** Label colour. */
|
|
13
|
+
text?: string;
|
|
14
|
+
/** Background colour on hover/focus. */
|
|
15
|
+
hoverBg?: string;
|
|
16
|
+
/** Label colour on hover/focus. */
|
|
17
|
+
hoverText?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const BUTTON_COLOR_DEFAULTS: Required<ButtonColors> = {
|
|
21
|
+
bg: "var(--sea)",
|
|
22
|
+
text: "#ffffff",
|
|
23
|
+
hoverBg: "var(--brass)",
|
|
24
|
+
hoverText: "#ffffff",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Tailwind classes that read the CSS variables produced by
|
|
29
|
+
* {@link buttonColorStyle}. Kept as a static literal so the compiler keeps them.
|
|
30
|
+
*/
|
|
31
|
+
export const BUTTON_COLOR_CLASS =
|
|
32
|
+
"bg-[var(--btn-bg)] text-[var(--btn-text)] transition-colors hover:bg-[var(--btn-bg-hover)] hover:text-[var(--btn-text-hover)] focus-visible:bg-[var(--btn-bg-hover)] focus-visible:text-[var(--btn-text-hover)]";
|
|
33
|
+
|
|
34
|
+
/** Inline CSS variables for a button's colours, with brand defaults applied. */
|
|
35
|
+
export function buttonColorStyle(colors?: ButtonColors): CSSProperties {
|
|
36
|
+
const bg = colors?.bg?.trim() || BUTTON_COLOR_DEFAULTS.bg;
|
|
37
|
+
const text = colors?.text?.trim() || BUTTON_COLOR_DEFAULTS.text;
|
|
38
|
+
const hoverBg = colors?.hoverBg?.trim() || BUTTON_COLOR_DEFAULTS.hoverBg;
|
|
39
|
+
const hoverText = colors?.hoverText?.trim() || BUTTON_COLOR_DEFAULTS.hoverText;
|
|
40
|
+
return {
|
|
41
|
+
["--btn-bg" as string]: bg,
|
|
42
|
+
["--btn-text" as string]: text,
|
|
43
|
+
["--btn-bg-hover" as string]: hoverBg,
|
|
44
|
+
["--btn-text-hover" as string]: hoverText,
|
|
45
|
+
} as CSSProperties;
|
|
46
|
+
}
|
|
@@ -2,6 +2,7 @@ import { useT } from "../../i18n";
|
|
|
2
2
|
import { Reveal } from "../../ui/Reveal";
|
|
3
3
|
import { FocalImage } from "../../host";
|
|
4
4
|
import type { LinkTilesProps, LinkTilesItem } from "../types";
|
|
5
|
+
import { BUTTON_COLOR_CLASS, buttonColorStyle, type ButtonColors } from "../button-colors";
|
|
5
6
|
|
|
6
7
|
const TONE: Record<string, string> = {
|
|
7
8
|
white: "bg-white",
|
|
@@ -36,12 +37,14 @@ function Tile({
|
|
|
36
37
|
rounded,
|
|
37
38
|
overlay,
|
|
38
39
|
labelAlign,
|
|
40
|
+
buttonColors,
|
|
39
41
|
}: {
|
|
40
42
|
item: LinkTilesItem;
|
|
41
43
|
aspectClass: string;
|
|
42
44
|
rounded: boolean;
|
|
43
45
|
overlay: "none" | "light" | "medium" | "strong" | "brand";
|
|
44
46
|
labelAlign: "left" | "center";
|
|
47
|
+
buttonColors?: ButtonColors;
|
|
45
48
|
}) {
|
|
46
49
|
const { t } = useT();
|
|
47
50
|
const label = t(item.label);
|
|
@@ -80,7 +83,10 @@ function Tile({
|
|
|
80
83
|
</span>
|
|
81
84
|
) : null}
|
|
82
85
|
{label ? (
|
|
83
|
-
<span
|
|
86
|
+
<span
|
|
87
|
+
style={buttonColorStyle(buttonColors)}
|
|
88
|
+
className={`mt-3 inline-flex items-center justify-center px-6 py-3 text-xs uppercase tracking-[0.22em] shadow-lg ${BUTTON_COLOR_CLASS}`}
|
|
89
|
+
>
|
|
84
90
|
{label}
|
|
85
91
|
</span>
|
|
86
92
|
) : null}
|
|
@@ -172,6 +178,7 @@ export function LinkTilesBlock({ props }: { props: LinkTilesProps }) {
|
|
|
172
178
|
rounded={props.rounded ?? false}
|
|
173
179
|
overlay={props.overlay ?? "medium"}
|
|
174
180
|
labelAlign={props.labelAlign ?? "left"}
|
|
181
|
+
buttonColors={props.buttonColors}
|
|
175
182
|
/>
|
|
176
183
|
))}
|
|
177
184
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useT } from "../../i18n";
|
|
2
|
+
import { BUTTON_COLOR_CLASS, buttonColorStyle } from "../button-colors";
|
|
2
3
|
import { Reveal } from "../../ui/Reveal";
|
|
3
4
|
import type { PriceTableProps } from "../types";
|
|
4
5
|
|
|
@@ -143,7 +144,8 @@ export function PriceTableBlock({ props }: { props: PriceTableProps }) {
|
|
|
143
144
|
{props.ctaLabel && props.ctaHref ? (
|
|
144
145
|
<a
|
|
145
146
|
href={props.ctaHref}
|
|
146
|
-
|
|
147
|
+
style={buttonColorStyle(props.ctaColors)}
|
|
148
|
+
className={`inline-flex items-center px-6 py-3 text-[0.7rem] uppercase tracking-[0.22em] ${BUTTON_COLOR_CLASS}`}
|
|
147
149
|
>
|
|
148
150
|
{t(props.ctaLabel)}
|
|
149
151
|
</a>
|
package/src/blocks/index.ts
CHANGED
|
@@ -11,3 +11,9 @@ export { MediaSplitBlock } from "./components/MediaSplitBlock";
|
|
|
11
11
|
export { PriceTableBlock } from "./components/PriceTableBlock";
|
|
12
12
|
export { ProseBlock } from "./components/ProseBlock";
|
|
13
13
|
export { RichTextBlock } from "./components/RichTextBlock";
|
|
14
|
+
export {
|
|
15
|
+
BUTTON_COLOR_CLASS,
|
|
16
|
+
BUTTON_COLOR_DEFAULTS,
|
|
17
|
+
buttonColorStyle,
|
|
18
|
+
type ButtonColors,
|
|
19
|
+
} from "./button-colors";
|
package/src/blocks/types.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ButtonColors } from "./button-colors";
|
|
1
2
|
import type { LocalizedString } from "../i18n";
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -344,6 +345,8 @@ export type LinkTilesItem = {
|
|
|
344
345
|
};
|
|
345
346
|
|
|
346
347
|
export type LinkTilesProps = {
|
|
348
|
+
/** Colours for the tile label buttons. */
|
|
349
|
+
buttonColors?: ButtonColors;
|
|
347
350
|
eyebrow?: Partial<LocalizedString>;
|
|
348
351
|
title?: Partial<LocalizedString>;
|
|
349
352
|
lead?: Partial<LocalizedString>;
|
|
@@ -408,6 +411,8 @@ export type PriceGroup = {
|
|
|
408
411
|
};
|
|
409
412
|
|
|
410
413
|
export type PriceTableProps = {
|
|
414
|
+
/** Colours for the CTA button. */
|
|
415
|
+
ctaColors?: ButtonColors;
|
|
411
416
|
eyebrow?: Partial<LocalizedString>;
|
|
412
417
|
title?: Partial<LocalizedString>;
|
|
413
418
|
lead?: Partial<LocalizedString>;
|
|
@@ -220,7 +220,9 @@ export function createPreviewPage(deps: PreviewPageDeps): ComponentType<{ pageKe
|
|
|
220
220
|
</div>
|
|
221
221
|
)}
|
|
222
222
|
<deps.Header />
|
|
223
|
-
<div className={key === (deps.fullBleedKey ?? "home")
|
|
223
|
+
<div className={key === (deps.fullBleedKey ?? "home")
|
|
224
|
+
? ""
|
|
225
|
+
: "pt-24 md:pt-32 has-[[data-site-hero]]:pt-0 md:has-[[data-site-hero]]:pt-0"}>
|
|
224
226
|
{ready ? (
|
|
225
227
|
<deps.DraftPageRenderer
|
|
226
228
|
blocks={blocks}
|
|
@@ -91,7 +91,7 @@ export function createPublicPageRoute<Ctx>(deps: PublicPageRouteDeps<Ctx>): Publ
|
|
|
91
91
|
|
|
92
92
|
PublicPage: ({ pageKey }) => (
|
|
93
93
|
<Shell>
|
|
94
|
-
<div className={deps.contentClassName ?? "pt-24 md:pt-32"}>
|
|
94
|
+
<div className={deps.contentClassName ?? "pt-24 md:pt-32 has-[[data-site-hero]]:pt-0 md:has-[[data-site-hero]]:pt-0"}>
|
|
95
95
|
<deps.PageRenderer pageKey={pageKey} />
|
|
96
96
|
</div>
|
|
97
97
|
</Shell>
|