@bygghammar/cms-platform 0.73.0 → 0.74.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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.74.0
|
|
4
|
+
|
|
5
|
+
- `CountdownBlock`: typography controls for the heading, sub text and fine print
|
|
6
|
+
— size (small/medium/large/extra large), weight (normal → black), serif/sans,
|
|
7
|
+
letter spacing, capitals and line height, each resettable. Defaults reproduce
|
|
8
|
+
the previous appearance, so saved pages are unchanged.
|
|
9
|
+
|
|
10
|
+
|
|
3
11
|
## 0.73.0
|
|
4
12
|
|
|
5
13
|
- New **Countdown** block: timezone-aware deadline (IANA zone + wall clock),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bygghammar/cms-platform",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.74.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": [
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"./host": "./src/host/index.tsx",
|
|
31
31
|
"./blocks": "./src/blocks/index.ts",
|
|
32
32
|
"./blocks/types": "./src/blocks/types.ts",
|
|
33
|
+
"./blocks/countdown-typography": "./src/blocks/countdown-typography.ts",
|
|
33
34
|
"./blocks/icons": "./src/blocks/icons.ts",
|
|
34
35
|
"./blocks/block-props-context": "./src/blocks/block-props-context.tsx",
|
|
35
36
|
"./blocks/components/*": "./src/blocks/components/*.tsx",
|
|
@@ -7,6 +7,17 @@
|
|
|
7
7
|
import { useEffect, useMemo, useState } from "react";
|
|
8
8
|
import { BUTTON_COLOR_DEFAULTS, type ButtonColors } from "../../blocks/button-colors";
|
|
9
9
|
import { COUNTDOWN_COLOR_DEFAULTS, type CountdownColors } from "../../blocks/countdown-colors";
|
|
10
|
+
import {
|
|
11
|
+
COUNTDOWN_TEXT_DEFAULTS,
|
|
12
|
+
COUNTDOWN_TEXT_FAMILY_OPTIONS,
|
|
13
|
+
COUNTDOWN_TEXT_LEADING_OPTIONS,
|
|
14
|
+
COUNTDOWN_TEXT_SIZE_OPTIONS,
|
|
15
|
+
COUNTDOWN_TEXT_TRACKING_OPTIONS,
|
|
16
|
+
COUNTDOWN_TEXT_TRANSFORM_OPTIONS,
|
|
17
|
+
COUNTDOWN_TEXT_WEIGHT_OPTIONS,
|
|
18
|
+
type CountdownTextSlot,
|
|
19
|
+
type CountdownTextStyle,
|
|
20
|
+
} from "../../blocks/countdown-typography";
|
|
10
21
|
import {
|
|
11
22
|
COUNTDOWN_DEFAULT_TIMEZONE,
|
|
12
23
|
COUNTDOWN_TIMEZONES,
|
|
@@ -2467,6 +2478,47 @@ export function CountdownInspector({ block, patch }: BlockPatchProps) {
|
|
|
2467
2478
|
</label>
|
|
2468
2479
|
);
|
|
2469
2480
|
|
|
2481
|
+
const textStyle = (
|
|
2482
|
+
title: string,
|
|
2483
|
+
slot: CountdownTextSlot,
|
|
2484
|
+
key: "titleType" | "bodyType" | "noteType",
|
|
2485
|
+
) => {
|
|
2486
|
+
const current = (p[key] ?? {}) as CountdownTextStyle;
|
|
2487
|
+
const d = COUNTDOWN_TEXT_DEFAULTS[slot];
|
|
2488
|
+
const set = (patchStyle: CountdownTextStyle) =>
|
|
2489
|
+
update({ ...p, [key]: { ...current, ...patchStyle } });
|
|
2490
|
+
return (
|
|
2491
|
+
<div key={key} className="space-y-2 border border-ink/10 bg-paper/60 p-3">
|
|
2492
|
+
<p className="text-[0.7rem] uppercase tracking-[0.22em] text-ink/70">{title}</p>
|
|
2493
|
+
<div className="grid grid-cols-2 gap-2">
|
|
2494
|
+
{select("Size", current.size, d.size, COUNTDOWN_TEXT_SIZE_OPTIONS, (v) => set({ size: v }))}
|
|
2495
|
+
{select("Weight", current.weight, d.weight, COUNTDOWN_TEXT_WEIGHT_OPTIONS, (v) =>
|
|
2496
|
+
set({ weight: v }),
|
|
2497
|
+
)}
|
|
2498
|
+
{select("Style", current.family, d.family, COUNTDOWN_TEXT_FAMILY_OPTIONS, (v) =>
|
|
2499
|
+
set({ family: v }),
|
|
2500
|
+
)}
|
|
2501
|
+
{select("Letter spacing", current.tracking, d.tracking, COUNTDOWN_TEXT_TRACKING_OPTIONS, (v) =>
|
|
2502
|
+
set({ tracking: v }),
|
|
2503
|
+
)}
|
|
2504
|
+
{select("Capitals", current.transform, d.transform, COUNTDOWN_TEXT_TRANSFORM_OPTIONS, (v) =>
|
|
2505
|
+
set({ transform: v }),
|
|
2506
|
+
)}
|
|
2507
|
+
{select("Line height", current.leading, d.leading, COUNTDOWN_TEXT_LEADING_OPTIONS, (v) =>
|
|
2508
|
+
set({ leading: v }),
|
|
2509
|
+
)}
|
|
2510
|
+
</div>
|
|
2511
|
+
<button
|
|
2512
|
+
type="button"
|
|
2513
|
+
onClick={() => update({ ...p, [key]: {} })}
|
|
2514
|
+
className="text-xs uppercase tracking-[0.18em] text-ink/60 underline hover:text-ink"
|
|
2515
|
+
>
|
|
2516
|
+
Reset to default
|
|
2517
|
+
</button>
|
|
2518
|
+
</div>
|
|
2519
|
+
);
|
|
2520
|
+
};
|
|
2521
|
+
|
|
2470
2522
|
const toggle = (label: string, value: boolean, onPick: (v: boolean) => void) => (
|
|
2471
2523
|
<label className="flex items-center gap-2 text-sm text-ink">
|
|
2472
2524
|
<input type="checkbox" checked={value} onChange={(e) => onPick(e.target.checked)} />
|
|
@@ -2482,6 +2534,15 @@ export function CountdownInspector({ block, patch }: BlockPatchProps) {
|
|
|
2482
2534
|
<LocalizedField label="Fine print (under the timer)" value={p.note} onChange={(v) => update({ ...p, note: v })} />
|
|
2483
2535
|
</InspectorSection>
|
|
2484
2536
|
|
|
2537
|
+
<InspectorSection title="Typography">
|
|
2538
|
+
<p className="text-xs leading-relaxed text-ink/60">
|
|
2539
|
+
Size and weight for the three texts above and under the timer.
|
|
2540
|
+
</p>
|
|
2541
|
+
{textStyle("Heading", "heading", "titleType")}
|
|
2542
|
+
{textStyle("Sub text", "sub", "bodyType")}
|
|
2543
|
+
{textStyle("Fine print", "note", "noteType")}
|
|
2544
|
+
</InspectorSection>
|
|
2545
|
+
|
|
2485
2546
|
<InspectorSection title="Deadline">
|
|
2486
2547
|
<label className="block space-y-1.5">
|
|
2487
2548
|
<span className="block text-[0.7rem] uppercase tracking-[0.22em] text-ink/60">
|
|
@@ -5,6 +5,7 @@ import type { LocalizedString } from "../../i18n";
|
|
|
5
5
|
import { Reveal } from "../../ui/Reveal";
|
|
6
6
|
import { countdownColorStyle, countdownRadius } from "../countdown-colors";
|
|
7
7
|
import { countdownParts, resolveCountdownTarget } from "../countdown-time";
|
|
8
|
+
import { countdownTextClasses } from "../countdown-typography";
|
|
8
9
|
import { useCountdownReplacement } from "../countdown-replacement";
|
|
9
10
|
import type { CountdownProps } from "../types";
|
|
10
11
|
|
|
@@ -93,12 +94,16 @@ export function CountdownBlock({ props: p }: { props: CountdownProps }) {
|
|
|
93
94
|
const heading = (title?: Partial<LocalizedString>, body?: Partial<LocalizedString>) => (
|
|
94
95
|
<>
|
|
95
96
|
{t(title) ? (
|
|
96
|
-
<h2
|
|
97
|
+
<h2
|
|
98
|
+
className={`${countdownTextClasses("heading", p.titleType)} whitespace-pre-line text-[var(--cd-heading)]`}
|
|
99
|
+
>
|
|
97
100
|
{t(title)}
|
|
98
101
|
</h2>
|
|
99
102
|
) : null}
|
|
100
103
|
{t(body) ? (
|
|
101
|
-
<p
|
|
104
|
+
<p
|
|
105
|
+
className={`mt-4 max-w-2xl ${countdownTextClasses("sub", p.bodyType)} whitespace-pre-line text-[var(--cd-sub)]`}
|
|
106
|
+
>
|
|
102
107
|
{t(body)}
|
|
103
108
|
</p>
|
|
104
109
|
) : null}
|
|
@@ -179,7 +184,7 @@ export function CountdownBlock({ props: p }: { props: CountdownProps }) {
|
|
|
179
184
|
))}
|
|
180
185
|
</div>
|
|
181
186
|
{t(p.note) ? (
|
|
182
|
-
<p className=
|
|
187
|
+
<p className={`mt-5 ${countdownTextClasses("note", p.noteType)} text-[var(--cd-sub)]`}>
|
|
183
188
|
{t(p.note)}
|
|
184
189
|
</p>
|
|
185
190
|
) : null}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typography options for the Countdown block's texts (heading, sub text and
|
|
3
|
+
* fine print). Every value is optional; leaving a field unset keeps the
|
|
4
|
+
* original theme-driven look, so saved pages never change appearance.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type CountdownTextSize = "sm" | "md" | "lg" | "xl";
|
|
8
|
+
export type CountdownTextWeight = "normal" | "medium" | "semibold" | "bold" | "black";
|
|
9
|
+
export type CountdownTextFamily = "serif" | "sans";
|
|
10
|
+
export type CountdownTextTracking = "tight" | "normal" | "wide" | "wider";
|
|
11
|
+
export type CountdownTextTransform = "none" | "uppercase" | "lowercase";
|
|
12
|
+
export type CountdownTextLeading = "tight" | "normal" | "relaxed";
|
|
13
|
+
|
|
14
|
+
export type CountdownTextStyle = {
|
|
15
|
+
size?: CountdownTextSize;
|
|
16
|
+
weight?: CountdownTextWeight;
|
|
17
|
+
family?: CountdownTextFamily;
|
|
18
|
+
tracking?: CountdownTextTracking;
|
|
19
|
+
transform?: CountdownTextTransform;
|
|
20
|
+
leading?: CountdownTextLeading;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type CountdownTextSlot = "heading" | "sub" | "note";
|
|
24
|
+
|
|
25
|
+
/** Responsive sizes per slot; `md` reproduces the original hardcoded size. */
|
|
26
|
+
const SIZE: Record<CountdownTextSlot, Record<CountdownTextSize, string>> = {
|
|
27
|
+
heading: {
|
|
28
|
+
sm: "text-[clamp(1.2rem,2.4vw,1.75rem)]",
|
|
29
|
+
md: "text-[clamp(1.6rem,3.6vw,2.9rem)]",
|
|
30
|
+
lg: "text-[clamp(2rem,5vw,4rem)]",
|
|
31
|
+
xl: "text-[clamp(2.4rem,6.5vw,5.5rem)]",
|
|
32
|
+
},
|
|
33
|
+
sub: {
|
|
34
|
+
sm: "text-sm md:text-base",
|
|
35
|
+
md: "text-base md:text-lg",
|
|
36
|
+
lg: "text-lg md:text-2xl",
|
|
37
|
+
xl: "text-xl md:text-3xl",
|
|
38
|
+
},
|
|
39
|
+
note: {
|
|
40
|
+
sm: "text-[0.7rem]",
|
|
41
|
+
md: "text-xs",
|
|
42
|
+
lg: "text-sm md:text-base",
|
|
43
|
+
xl: "text-base md:text-lg",
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const WEIGHT: Record<CountdownTextWeight, string> = {
|
|
48
|
+
normal: "font-normal",
|
|
49
|
+
medium: "font-medium",
|
|
50
|
+
semibold: "font-semibold",
|
|
51
|
+
bold: "font-bold",
|
|
52
|
+
black: "font-black",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const FAMILY: Record<CountdownTextFamily, string> = {
|
|
56
|
+
serif: "font-serif",
|
|
57
|
+
sans: "font-sans",
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const TRACKING: Record<CountdownTextTracking, string> = {
|
|
61
|
+
tight: "tracking-[-0.02em]",
|
|
62
|
+
normal: "tracking-normal",
|
|
63
|
+
wide: "tracking-[0.08em]",
|
|
64
|
+
wider: "tracking-[0.22em]",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const TRANSFORM: Record<CountdownTextTransform, string> = {
|
|
68
|
+
none: "normal-case",
|
|
69
|
+
uppercase: "uppercase",
|
|
70
|
+
lowercase: "lowercase",
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const LEADING: Record<CountdownTextLeading, string> = {
|
|
74
|
+
tight: "leading-[1.1]",
|
|
75
|
+
normal: "leading-snug",
|
|
76
|
+
relaxed: "leading-relaxed",
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** Defaults matching the block's original appearance per slot. */
|
|
80
|
+
export const COUNTDOWN_TEXT_DEFAULTS: Record<CountdownTextSlot, Required<CountdownTextStyle>> = {
|
|
81
|
+
heading: {
|
|
82
|
+
size: "md",
|
|
83
|
+
weight: "normal",
|
|
84
|
+
family: "serif",
|
|
85
|
+
tracking: "tight",
|
|
86
|
+
transform: "none",
|
|
87
|
+
leading: "tight",
|
|
88
|
+
},
|
|
89
|
+
sub: {
|
|
90
|
+
size: "md",
|
|
91
|
+
weight: "normal",
|
|
92
|
+
family: "sans",
|
|
93
|
+
tracking: "normal",
|
|
94
|
+
transform: "none",
|
|
95
|
+
leading: "relaxed",
|
|
96
|
+
},
|
|
97
|
+
note: {
|
|
98
|
+
size: "md",
|
|
99
|
+
weight: "normal",
|
|
100
|
+
family: "sans",
|
|
101
|
+
tracking: "wider",
|
|
102
|
+
transform: "uppercase",
|
|
103
|
+
leading: "normal",
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/** Tailwind classes for one countdown text, falling back to the slot default. */
|
|
108
|
+
export function countdownTextClasses(slot: CountdownTextSlot, style?: CountdownTextStyle): string {
|
|
109
|
+
const d = COUNTDOWN_TEXT_DEFAULTS[slot];
|
|
110
|
+
const s = style ?? {};
|
|
111
|
+
return [
|
|
112
|
+
FAMILY[s.family ?? d.family],
|
|
113
|
+
SIZE[slot][s.size ?? d.size],
|
|
114
|
+
WEIGHT[s.weight ?? d.weight],
|
|
115
|
+
TRACKING[s.tracking ?? d.tracking],
|
|
116
|
+
TRANSFORM[s.transform ?? d.transform],
|
|
117
|
+
LEADING[s.leading ?? d.leading],
|
|
118
|
+
].join(" ");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const COUNTDOWN_TEXT_SIZE_OPTIONS: { value: CountdownTextSize; label: string }[] = [
|
|
122
|
+
{ value: "sm", label: "Small" },
|
|
123
|
+
{ value: "md", label: "Medium" },
|
|
124
|
+
{ value: "lg", label: "Large" },
|
|
125
|
+
{ value: "xl", label: "Extra large" },
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
export const COUNTDOWN_TEXT_WEIGHT_OPTIONS: { value: CountdownTextWeight; label: string }[] = [
|
|
129
|
+
{ value: "normal", label: "Normal" },
|
|
130
|
+
{ value: "medium", label: "Medium" },
|
|
131
|
+
{ value: "semibold", label: "Semibold" },
|
|
132
|
+
{ value: "bold", label: "Bold" },
|
|
133
|
+
{ value: "black", label: "Black" },
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
export const COUNTDOWN_TEXT_FAMILY_OPTIONS: { value: CountdownTextFamily; label: string }[] = [
|
|
137
|
+
{ value: "serif", label: "Serif (display)" },
|
|
138
|
+
{ value: "sans", label: "Sans (body)" },
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
export const COUNTDOWN_TEXT_TRACKING_OPTIONS: { value: CountdownTextTracking; label: string }[] = [
|
|
142
|
+
{ value: "tight", label: "Tight" },
|
|
143
|
+
{ value: "normal", label: "Normal" },
|
|
144
|
+
{ value: "wide", label: "Wide" },
|
|
145
|
+
{ value: "wider", label: "Extra wide" },
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
export const COUNTDOWN_TEXT_TRANSFORM_OPTIONS: { value: CountdownTextTransform; label: string }[] = [
|
|
149
|
+
{ value: "none", label: "As typed" },
|
|
150
|
+
{ value: "uppercase", label: "UPPERCASE" },
|
|
151
|
+
{ value: "lowercase", label: "lowercase" },
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
export const COUNTDOWN_TEXT_LEADING_OPTIONS: { value: CountdownTextLeading; label: string }[] = [
|
|
155
|
+
{ value: "tight", label: "Tight" },
|
|
156
|
+
{ value: "normal", label: "Normal" },
|
|
157
|
+
{ value: "relaxed", label: "Relaxed" },
|
|
158
|
+
];
|
package/src/blocks/index.ts
CHANGED
package/src/blocks/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ButtonColors } from "./button-colors";
|
|
2
2
|
import type { CountdownColors, CountdownRounding } from "./countdown-colors";
|
|
3
|
+
import type { CountdownTextStyle } from "./countdown-typography";
|
|
3
4
|
import type { LocalizedString } from "../i18n";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -463,6 +464,11 @@ export type CountdownProps = {
|
|
|
463
464
|
/** Fine print under the timer. */
|
|
464
465
|
note?: Partial<LocalizedString>;
|
|
465
466
|
|
|
467
|
+
/** Typography for the heading, sub text and fine print. */
|
|
468
|
+
titleType?: CountdownTextStyle;
|
|
469
|
+
bodyType?: CountdownTextStyle;
|
|
470
|
+
noteType?: CountdownTextStyle;
|
|
471
|
+
|
|
466
472
|
/** Deadline as wall-clock time, `YYYY-MM-DDTHH:mm`. */
|
|
467
473
|
targetLocal?: string;
|
|
468
474
|
/** IANA timezone the deadline is expressed in, e.g. `Europe/Stockholm`. */
|