@multiplatform.one/theme 6.4.1 → 6.4.2
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 +5 -5
- package/src/theme/knobs.ts +25 -0
- package/src/theme/presets.ts +21 -0
- package/src/theme/recipes.ts +8 -1
- package/src/theme/resolveKnobs.ts +25 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiplatform.one/theme",
|
|
3
|
-
"version": "6.4.
|
|
3
|
+
"version": "6.4.2",
|
|
4
4
|
"description": "Tamagui theme system for multiplatform.one",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"multiplatform",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"@tamagui/toast": "2.0.0-rc.41",
|
|
54
54
|
"@tamagui/web": "2.0.0-rc.41",
|
|
55
55
|
"react-cookie": "^8.1.2",
|
|
56
|
-
"@multiplatform.one/platform": "6.4.
|
|
57
|
-
"@multiplatform.one/store": "6.4.
|
|
56
|
+
"@multiplatform.one/platform": "6.4.2",
|
|
57
|
+
"@multiplatform.one/store": "6.4.2"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@tamagui/animations-css": "2.0.0-rc.41",
|
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
"tamagui": "2.0.0-rc.41",
|
|
70
70
|
"typescript": "~5.9.3",
|
|
71
71
|
"vitest": "^4.1.5",
|
|
72
|
-
"@multiplatform.one/
|
|
73
|
-
"@multiplatform.one/
|
|
72
|
+
"@multiplatform.one/config": "6.4.2",
|
|
73
|
+
"@multiplatform.one/test-utils": "6.4.2"
|
|
74
74
|
},
|
|
75
75
|
"peerDependencies": {
|
|
76
76
|
"@tamagui/animations-css": "^2.0.0-rc",
|
package/src/theme/knobs.ts
CHANGED
|
@@ -179,6 +179,25 @@ export const FontWeight = {
|
|
|
179
179
|
} as const;
|
|
180
180
|
export type FontWeight = (typeof FontWeight)[keyof typeof FontWeight];
|
|
181
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Page-title type step (D-11 hero-H1 dial, owner ruling 2026-08-12).
|
|
184
|
+
*
|
|
185
|
+
* `moderate` ($8 = 32px) is the product default: a page title is wayfinding,
|
|
186
|
+
* not content, so it does not spend the display scale's emphasis budget
|
|
187
|
+
* (Axiom 10) — and the 64px step is the direct amplifier of the DF-02 title
|
|
188
|
+
* clip. `display` ($10 = 64px) is the deliberate marketing/hero opt-in,
|
|
189
|
+
* reached through the `hero` preset or an explicit knob override; it is never
|
|
190
|
+
* the ambient default.
|
|
191
|
+
*
|
|
192
|
+
* Two values, not a token dial: a free size invites per-screen drift
|
|
193
|
+
* (Axiom 5), and the semantic level stays `headingLevel`'s job either way.
|
|
194
|
+
*/
|
|
195
|
+
export const PageTitleScale = {
|
|
196
|
+
Moderate: "moderate",
|
|
197
|
+
Display: "display",
|
|
198
|
+
} as const;
|
|
199
|
+
export type PageTitleScale = (typeof PageTitleScale)[keyof typeof PageTitleScale];
|
|
200
|
+
|
|
182
201
|
// ── Animation ──────────────────────────────────────────────
|
|
183
202
|
|
|
184
203
|
import type { AnimationName } from "./animations/index";
|
|
@@ -223,6 +242,11 @@ export interface Knobs {
|
|
|
223
242
|
headingFont: HeadingFont;
|
|
224
243
|
bodyFont: BodyFont;
|
|
225
244
|
fontWeight: FontWeight;
|
|
245
|
+
/**
|
|
246
|
+
* Page-title step (D-11). Optional so existing preset/knob constructions
|
|
247
|
+
* stay valid; resolveKnobs defaults to `moderate`.
|
|
248
|
+
*/
|
|
249
|
+
pageTitleScale?: PageTitleScale;
|
|
226
250
|
animation: Animation;
|
|
227
251
|
/** House: field label placement. Default top. */
|
|
228
252
|
fieldLabelPlacement: FieldLabelPlacement;
|
|
@@ -263,6 +287,7 @@ export const defaultKnobs: Knobs = {
|
|
|
263
287
|
headingFont: "sans-serif",
|
|
264
288
|
bodyFont: "sans-serif",
|
|
265
289
|
fontWeight: "regular",
|
|
290
|
+
pageTitleScale: "moderate",
|
|
266
291
|
animation: "quick",
|
|
267
292
|
fieldLabelPlacement: "top",
|
|
268
293
|
requiredMarking: "minority",
|
package/src/theme/presets.ts
CHANGED
|
@@ -17,6 +17,7 @@ export const defaultPreset: Preset = {
|
|
|
17
17
|
headingFont: "sans-serif",
|
|
18
18
|
bodyFont: "sans-serif",
|
|
19
19
|
fontWeight: "regular",
|
|
20
|
+
pageTitleScale: "moderate",
|
|
20
21
|
animation: "quick",
|
|
21
22
|
fieldLabelPlacement: "top",
|
|
22
23
|
requiredMarking: "minority",
|
|
@@ -50,6 +51,7 @@ export const boldPreset: Preset = {
|
|
|
50
51
|
headingFont: "sans-serif",
|
|
51
52
|
bodyFont: "sans-serif",
|
|
52
53
|
fontWeight: "bold",
|
|
54
|
+
pageTitleScale: "moderate",
|
|
53
55
|
animation: "bouncy",
|
|
54
56
|
fieldLabelPlacement: "top",
|
|
55
57
|
requiredMarking: "minority",
|
|
@@ -67,3 +69,22 @@ export const boldPreset: Preset = {
|
|
|
67
69
|
intents: defaultIntents,
|
|
68
70
|
tints: ["pink", "purple", "pink", "purple", "pink", "purple", "pink"] as ThemeName[],
|
|
69
71
|
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Marketing / hero surface preset (D-11 hero-H1 dial, owner ruling
|
|
75
|
+
* 2026-08-12). Identical to `defaultPreset` except the page title returns to
|
|
76
|
+
* the display step ($10 = 64px): product screens are wayfinding and take the
|
|
77
|
+
* moderate step by default, so the loud scale has to be ASKED for.
|
|
78
|
+
*
|
|
79
|
+
* @example Whole marketing app
|
|
80
|
+
* createDefaultThemeConfig({ presets: { default: defaultPreset, hero: heroPreset } })
|
|
81
|
+
*
|
|
82
|
+
* @example One hero region inside a product app (cascades onto the live preset)
|
|
83
|
+
* <Preset overrides={{ pageTitleScale: "display" }}>
|
|
84
|
+
* <PageHeader title="Ship faster" />
|
|
85
|
+
* </Preset>
|
|
86
|
+
*/
|
|
87
|
+
export const heroPreset: Preset = {
|
|
88
|
+
...defaultPreset,
|
|
89
|
+
knobs: { ...defaultPreset.knobs, pageTitleScale: "display" },
|
|
90
|
+
};
|
package/src/theme/recipes.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ColorTokens, TransitionProp } from "tamagui";
|
|
1
|
+
import type { ColorTokens, FontSizeTokens, TransitionProp } from "tamagui";
|
|
2
2
|
import type { Knobs } from "./knobs";
|
|
3
3
|
|
|
4
4
|
// ── State knob prop types (by concern) ───────────────────────────────────
|
|
@@ -154,6 +154,13 @@ export interface KnobProps {
|
|
|
154
154
|
gapLg: { gap: string };
|
|
155
155
|
sizeToken: string;
|
|
156
156
|
heading: { fontFamily: string; fontWeight: string };
|
|
157
|
+
/**
|
|
158
|
+
* Page-title type step (D-11 hero-H1 dial) — the complete size fragment for
|
|
159
|
+
* the single page-title heading, spread whole onto the title node. Product
|
|
160
|
+
* screens land on the moderate step; the `hero` preset (or an explicit
|
|
161
|
+
* `pageTitleScale` override) re-opens the display scale for marketing.
|
|
162
|
+
*/
|
|
163
|
+
pageTitle: { size: FontSizeTokens };
|
|
157
164
|
/** color is set only for solid semantic-intent Buttons (error/warning/success). */
|
|
158
165
|
body: { fontFamily: string; fontWeight: string; color?: ColorTokens };
|
|
159
166
|
textWeight: { fontWeight: string };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isWeb } from "@multiplatform.one/platform";
|
|
2
|
-
import type { TransitionProp } from "tamagui";
|
|
2
|
+
import type { FontSizeTokens, TransitionProp } from "tamagui";
|
|
3
3
|
// Importing the class constant also mounts the corner-shape stylesheet at
|
|
4
4
|
// theme load (web only, idempotent) — see cornerSmoothing.ts.
|
|
5
5
|
import { cornerSmoothClassName } from "./cornerSmoothing";
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
STATE_LAYER_PRESS,
|
|
10
10
|
withDefaultStateLayer,
|
|
11
11
|
} from "./focusState";
|
|
12
|
-
import type { CornerSmoothing, Knobs, StateKnobs } from "./knobs";
|
|
12
|
+
import type { CornerSmoothing, Knobs, PageTitleScale, StateKnobs } from "./knobs";
|
|
13
13
|
import type {
|
|
14
14
|
ControlStateProps,
|
|
15
15
|
DisabledRecipe,
|
|
@@ -101,6 +101,28 @@ const panelPaddingMap = {
|
|
|
101
101
|
large: "$6",
|
|
102
102
|
} as const;
|
|
103
103
|
|
|
104
|
+
// ── Page-title step (D-11 hero-H1 dial) ───────────────────────
|
|
105
|
+
// The one canonical definition of how big a page title is. On the heading
|
|
106
|
+
// scale (1.4x Inter, defaults/fonts.ts) $8 = 32px and $10 = 64px, so
|
|
107
|
+
// `moderate` lands in the page-title class while `display` keeps the
|
|
108
|
+
// marketing hero step. Screens never pick a heading step by hand — they
|
|
109
|
+
// spread `knobProps.pageTitle`, so one knob flip re-scales every title.
|
|
110
|
+
const pageTitleSizeMap: Record<PageTitleScale, FontSizeTokens> = {
|
|
111
|
+
moderate: "$8",
|
|
112
|
+
display: "$10",
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Resolve a page-title scale to its complete size fragment (D-11).
|
|
117
|
+
*
|
|
118
|
+
* Exported so a per-instance eject (`PageHeader titleScale="display"` on a
|
|
119
|
+
* marketing hero) resolves through the same table as the knob — never a
|
|
120
|
+
* second hardcoded step.
|
|
121
|
+
*/
|
|
122
|
+
export function resolvePageTitleScale(scale: PageTitleScale): { size: FontSizeTokens } {
|
|
123
|
+
return { size: pageTitleSizeMap[scale] };
|
|
124
|
+
}
|
|
125
|
+
|
|
104
126
|
// ── Container radius cap (Axiom 1 CLIP) ──────────────────────
|
|
105
127
|
// A padded container's effective radius must not exceed its own padding:
|
|
106
128
|
// past that, the corner curve sweeps through the cell where children sit
|
|
@@ -433,6 +455,7 @@ export function resolveKnobs(knobs: Knobs, override?: KnobPropsOverride): Resolv
|
|
|
433
455
|
fontFamily: headingFontMap[knobs.headingFont],
|
|
434
456
|
fontWeight: fontWeightValue,
|
|
435
457
|
},
|
|
458
|
+
pageTitle: resolvePageTitleScale(knobs.pageTitleScale ?? "moderate"),
|
|
436
459
|
body: {
|
|
437
460
|
fontFamily: bodyFontValue,
|
|
438
461
|
fontWeight: fontWeightValue,
|