@multiplatform.one/theme 7.47.2 → 7.47.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplatform.one/theme",
3
- "version": "7.47.2",
3
+ "version": "7.47.4",
4
4
  "description": "Tamagui theme system for multiplatform.one",
5
5
  "keywords": [
6
6
  "multiplatform",
@@ -72,8 +72,8 @@
72
72
  "access": "public"
73
73
  },
74
74
  "dependencies": {
75
- "@multiplatform.one/platform": "^7.47.2",
76
- "@multiplatform.one/store": "^7.47.2",
75
+ "@multiplatform.one/platform": "^7.47.4",
76
+ "@multiplatform.one/store": "^7.47.4",
77
77
  "@tamagui/animations-react-native": "2.7.6",
78
78
  "@tamagui/colors": "2.7.6",
79
79
  "@tamagui/config": "2.7.6",
@@ -89,8 +89,8 @@
89
89
  },
90
90
  "devDependencies": {
91
91
  "@expo/prebuild-config": "55.0.18",
92
- "@multiplatform.one/config": "7.47.2",
93
- "@multiplatform.one/test-utils": "7.47.2",
92
+ "@multiplatform.one/config": "7.47.4",
93
+ "@multiplatform.one/test-utils": "7.47.4",
94
94
  "@tamagui/animations-css": "2.7.6",
95
95
  "@tamagui/animations-reanimated": "2.7.6",
96
96
  "@tamagui/font-inter": "2.7.6",
@@ -4,6 +4,7 @@
4
4
  * H1 alone takes pageTitle: moderate $10 (40 px), display $12 (52 px), one
5
5
  * token less below $sm (LC-143).
6
6
  */
7
+ import { headingMaxFontScale } from "./dynamicType";
7
8
  import { useResolvedKnobs } from "./useResolvedKnobs";
8
9
  import {
9
10
  H1 as RawH1,
@@ -13,6 +14,9 @@ import {
13
14
  H5 as RawH5,
14
15
  H6 as RawH6,
15
16
  Heading as RawHeading,
17
+ getFontSize,
18
+ useMedia,
19
+ type FontSizeTokens,
16
20
  type HeadingProps,
17
21
  } from "tamagui";
18
22
 
@@ -20,24 +24,58 @@ export type { HeadingProps };
20
24
 
21
25
  type Weight = HeadingProps["fontWeight"];
22
26
 
23
- function useHeadingVoice(fontWeight: Weight) {
27
+ /** The size each tamagui heading draws when the use site passes none. */
28
+ type DefaultSize = "$5" | "$6" | "$7" | "$8" | "$9";
29
+
30
+ function headingPx(size: unknown, fallback: unknown): number {
31
+ if (typeof size === "number") return size;
32
+ const token = typeof size === "string" ? size : fallback;
33
+ if (typeof token === "number") return token;
34
+ return getFontSize(token as FontSizeTokens, { font: "$heading" });
35
+ }
36
+
37
+ /**
38
+ * The props that ride last: the resolved weight, then the native text props
39
+ * that keep a heading whole under Dynamic Type (web drops them). LC-158: the
40
+ * page title grows on the iOS large-title curve and smaller headings grow
41
+ * until they meet it, so a one-word title never outgrows the line and breaks
42
+ * mid-word. iOS breaks lines with UILabel's standard strategy.
43
+ */
44
+ function useHeadingVoice(
45
+ fontWeight: Weight,
46
+ props: HeadingProps,
47
+ defaultSize: DefaultSize | "title",
48
+ ) {
24
49
  const { knobProps } = useResolvedKnobs();
50
+ const { sm } = useMedia();
25
51
  const { fontWeight: knobWeight, ...heading } = knobProps.heading;
26
52
  const resolvedWeight = fontWeight ?? (knobWeight as Weight);
53
+ const pageTitle = knobProps.pageTitle;
54
+ // Below $sm the title paints its compact step (LC-143); the cap meets that.
55
+ const titleSize = (sm === false ? pageTitle["$max-sm"]?.fontSize : undefined) ?? pageTitle.size;
56
+ const titlePx = headingPx(titleSize, "$10");
57
+ const ownPx = headingPx(
58
+ props.fontSize ?? props.size,
59
+ defaultSize === "title" ? titleSize : defaultSize,
60
+ );
27
61
  return {
28
62
  heading,
29
- pageTitle: knobProps.pageTitle,
30
- weight: resolvedWeight === undefined ? {} : { fontWeight: resolvedWeight },
63
+ pageTitle,
64
+ weight: {
65
+ ...(resolvedWeight === undefined ? {} : { fontWeight: resolvedWeight }),
66
+ maxFontSizeMultiplier: props.maxFontSizeMultiplier ?? headingMaxFontScale(ownPx, titlePx),
67
+ lineBreakStrategyIOS: props.lineBreakStrategyIOS ?? "standard",
68
+ },
31
69
  };
32
70
  }
33
71
 
34
72
  export function Heading({ fontWeight, ...props }: HeadingProps) {
35
- const { heading, weight } = useHeadingVoice(fontWeight);
73
+ const { heading, weight } = useHeadingVoice(fontWeight, props, "$8");
36
74
  return <RawHeading {...heading} {...props} {...weight} />;
37
75
  }
38
76
 
39
77
  export function H1({ fontWeight, ...props }: HeadingProps) {
40
- const { heading, pageTitle, weight } = useHeadingVoice(fontWeight);
78
+ const { heading, pageTitle, weight } = useHeadingVoice(fontWeight, props, "title");
41
79
  // An explicit size ejects the whole step. Spread anyway, the knob's compact
42
80
  // `$max-sm` entry would still override that size on a phone.
43
81
  const step = props.size === undefined ? pageTitle : null;
@@ -45,26 +83,26 @@ export function H1({ fontWeight, ...props }: HeadingProps) {
45
83
  }
46
84
 
47
85
  export function H2({ fontWeight, ...props }: HeadingProps) {
48
- const { heading, weight } = useHeadingVoice(fontWeight);
86
+ const { heading, weight } = useHeadingVoice(fontWeight, props, "$9");
49
87
  return <RawH2 {...heading} {...props} {...weight} />;
50
88
  }
51
89
 
52
90
  export function H3({ fontWeight, ...props }: HeadingProps) {
53
- const { heading, weight } = useHeadingVoice(fontWeight);
91
+ const { heading, weight } = useHeadingVoice(fontWeight, props, "$8");
54
92
  return <RawH3 {...heading} {...props} {...weight} />;
55
93
  }
56
94
 
57
95
  export function H4({ fontWeight, ...props }: HeadingProps) {
58
- const { heading, weight } = useHeadingVoice(fontWeight);
96
+ const { heading, weight } = useHeadingVoice(fontWeight, props, "$7");
59
97
  return <RawH4 {...heading} {...props} {...weight} />;
60
98
  }
61
99
 
62
100
  export function H5({ fontWeight, ...props }: HeadingProps) {
63
- const { heading, weight } = useHeadingVoice(fontWeight);
101
+ const { heading, weight } = useHeadingVoice(fontWeight, props, "$6");
64
102
  return <RawH5 {...heading} {...props} {...weight} />;
65
103
  }
66
104
 
67
105
  export function H6({ fontWeight, ...props }: HeadingProps) {
68
- const { heading, weight } = useHeadingVoice(fontWeight);
106
+ const { heading, weight } = useHeadingVoice(fontWeight, props, "$5");
69
107
  return <RawH6 {...heading} {...props} {...weight} />;
70
108
  }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * LC-158 DYNAMIC-TYPE-FIT. On native, text grows with the platform text size
3
+ * (iOS Dynamic Type, Android font scale) and a fixed box does not. Text that
4
+ * lives inside a box of fixed size caps its growth at what the box holds;
5
+ * text whose box can grow (a badge pill, body copy) is never capped.
6
+ *
7
+ * React Native applies the cap per text node through `maxFontSizeMultiplier`
8
+ * (RN ignores a value below 1, so every cap here is at least 1).
9
+ */
10
+ export const dynamicTypeFit = {
11
+ /**
12
+ * The page title grows at most 1.5x, the iOS large-title curve: 34 pt at
13
+ * Large reaches 52 pt at accessibility-extra-large (1.53x) while body text
14
+ * reaches 2.6x. Smaller headings grow until they meet the capped title.
15
+ */
16
+ titleMaxScale: 1.5,
17
+ /** Initials in an avatar circle: the em fills at most half the diameter. */
18
+ medallionFill: 0.5,
19
+ /** A control label in a fixed-height control: the em fills at most 3/4 of the height. */
20
+ controlLabelFill: 0.75,
21
+ } as const;
22
+
23
+ /** Largest `maxFontSizeMultiplier` that keeps a `fontPx` em within `fill` of `boxPx`. */
24
+ export function maxFontScaleToFit(boxPx: number, fontPx: number, fill: number): number {
25
+ if (!(boxPx > 0) || !(fontPx > 0) || !(fill > 0)) return 1;
26
+ return Math.max(1, Math.floor(((boxPx * fill) / fontPx) * 100) / 100);
27
+ }
28
+
29
+ /** A heading's cap: it may grow until it meets the capped page title, never past it. */
30
+ export function headingMaxFontScale(headingPx: number, titlePx: number): number {
31
+ return maxFontScaleToFit(titlePx, headingPx, dynamicTypeFit.titleMaxScale);
32
+ }
33
+
34
+ /**
35
+ * The multiplier React Native paints a text node at: the platform font scale,
36
+ * held to `maxFontSizeMultiplier` when that is 1 or more.
37
+ */
38
+ export function effectiveFontScale(fontScale: number, maxFontSizeMultiplier?: number): number {
39
+ return maxFontSizeMultiplier != null && maxFontSizeMultiplier >= 1
40
+ ? Math.min(fontScale, maxFontSizeMultiplier)
41
+ : fontScale;
42
+ }
@@ -8,6 +8,7 @@ export * from "./createDefaultThemeConfig";
8
8
  export * from "./createThemes";
9
9
  export * from "./defaults/index";
10
10
  export * from "./devtools/ThemeDevtoolsPanel";
11
+ export * from "./dynamicType";
11
12
  export * from "./fieldFocus";
12
13
  export * from "./focusState";
13
14
  export * from "./glyphPaint";
@@ -1 +1 @@
1
- {"version":3,"file":"Heading.d.ts","sourceRoot":"","sources":["../../src/theme/Heading.tsx"],"names":[],"mappings":"AAOA,OAAO,EAQL,KAAK,YAAY,EAClB,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,YAAY,EAAE,CAAC;AAe7B,wBAAgB,OAAO,CAAC,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,2CAG7D;AAED,wBAAgB,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,2CAMxD;AAED,wBAAgB,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,2CAGxD;AAED,wBAAgB,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,2CAGxD;AAED,wBAAgB,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,2CAGxD;AAED,wBAAgB,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,2CAGxD;AAED,wBAAgB,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,2CAGxD"}
1
+ {"version":3,"file":"Heading.d.ts","sourceRoot":"","sources":["../../src/theme/Heading.tsx"],"names":[],"mappings":"AAQA,OAAO,EAWL,KAAK,YAAY,EAClB,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,YAAY,EAAE,CAAC;AAiD7B,wBAAgB,OAAO,CAAC,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,2CAG7D;AAED,wBAAgB,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,2CAMxD;AAED,wBAAgB,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,2CAGxD;AAED,wBAAgB,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,2CAGxD;AAED,wBAAgB,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,2CAGxD;AAED,wBAAgB,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,2CAGxD;AAED,wBAAgB,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,2CAGxD"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * LC-158 DYNAMIC-TYPE-FIT. On native, text grows with the platform text size
3
+ * (iOS Dynamic Type, Android font scale) and a fixed box does not. Text that
4
+ * lives inside a box of fixed size caps its growth at what the box holds;
5
+ * text whose box can grow (a badge pill, body copy) is never capped.
6
+ *
7
+ * React Native applies the cap per text node through `maxFontSizeMultiplier`
8
+ * (RN ignores a value below 1, so every cap here is at least 1).
9
+ */
10
+ export declare const dynamicTypeFit: {
11
+ /**
12
+ * The page title grows at most 1.5x, the iOS large-title curve: 34 pt at
13
+ * Large reaches 52 pt at accessibility-extra-large (1.53x) while body text
14
+ * reaches 2.6x. Smaller headings grow until they meet the capped title.
15
+ */
16
+ readonly titleMaxScale: 1.5;
17
+ /** Initials in an avatar circle: the em fills at most half the diameter. */
18
+ readonly medallionFill: 0.5;
19
+ /** A control label in a fixed-height control: the em fills at most 3/4 of the height. */
20
+ readonly controlLabelFill: 0.75;
21
+ };
22
+ /** Largest `maxFontSizeMultiplier` that keeps a `fontPx` em within `fill` of `boxPx`. */
23
+ export declare function maxFontScaleToFit(boxPx: number, fontPx: number, fill: number): number;
24
+ /** A heading's cap: it may grow until it meets the capped page title, never past it. */
25
+ export declare function headingMaxFontScale(headingPx: number, titlePx: number): number;
26
+ /**
27
+ * The multiplier React Native paints a text node at: the platform font scale,
28
+ * held to `maxFontSizeMultiplier` when that is 1 or more.
29
+ */
30
+ export declare function effectiveFontScale(fontScale: number, maxFontSizeMultiplier?: number): number;
31
+ //# sourceMappingURL=dynamicType.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamicType.d.ts","sourceRoot":"","sources":["../../src/theme/dynamicType.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc;IACzB;;;;OAIG;;IAEH,4EAA4E;;IAE5E,yFAAyF;;CAEjF,CAAC;AAEX,yFAAyF;AACzF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGrF;AAED,wFAAwF;AACxF,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,qBAAqB,CAAC,EAAE,MAAM,GAAG,MAAM,CAI5F"}
@@ -8,6 +8,7 @@ export * from "./createDefaultThemeConfig";
8
8
  export * from "./createThemes";
9
9
  export * from "./defaults/index";
10
10
  export * from "./devtools/ThemeDevtoolsPanel";
11
+ export * from "./dynamicType";
11
12
  export * from "./fieldFocus";
12
13
  export * from "./focusState";
13
14
  export * from "./glyphPaint";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,YAAY,EAAE,MAAM,IAAI,gBAAgB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACjF,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,SAAS,CAAC;AACxB,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAElC,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAElC,cAAc,UAAU,CAAC;AACzB,OAAO,EACL,eAAe,IAAI,kBAAkB,EACrC,kBAAkB,EAClB,WAAW,EACX,KAAK,UAAU,GAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,IAAI,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEpF,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,YAAY,EAAE,MAAM,IAAI,gBAAgB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACjF,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,SAAS,CAAC;AACxB,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAElC,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAElC,cAAc,UAAU,CAAC;AACzB,OAAO,EACL,eAAe,IAAI,kBAAkB,EACrC,kBAAkB,EAClB,WAAW,EACX,KAAK,UAAU,GAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,IAAI,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEpF,cAAc,YAAY,CAAC"}