react-native-terra-ui 0.13.2 → 0.13.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.
Files changed (106) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/lib/module/components/accordion/parts/AccordionContent.js +2 -2
  3. package/lib/module/components/accordion/parts/AccordionContent.js.map +1 -1
  4. package/lib/module/components/accordion/parts/AccordionTrigger.js +4 -4
  5. package/lib/module/components/accordion/parts/AccordionTrigger.js.map +1 -1
  6. package/lib/module/components/avatar/Avatar.js +3 -3
  7. package/lib/module/components/avatar/Avatar.js.map +1 -1
  8. package/lib/module/components/badge/Badge.js +2 -2
  9. package/lib/module/components/badge/Badge.js.map +1 -1
  10. package/lib/module/components/button/Button.js +3 -3
  11. package/lib/module/components/button/Button.js.map +1 -1
  12. package/lib/module/components/chip/Chip.js +2 -2
  13. package/lib/module/components/chip/Chip.js.map +1 -1
  14. package/lib/module/components/empty-state/EmptyState.js +6 -6
  15. package/lib/module/components/empty-state/EmptyState.js.map +1 -1
  16. package/lib/module/components/header/hooks/use-large-title-header.js +8 -1
  17. package/lib/module/components/header/hooks/use-large-title-header.js.map +1 -1
  18. package/lib/module/components/header/parts/HeaderBar.js +7 -13
  19. package/lib/module/components/header/parts/HeaderBar.js.map +1 -1
  20. package/lib/module/components/header/variants/LargeTitleHeader/index.js +8 -0
  21. package/lib/module/components/header/variants/LargeTitleHeader/index.js.map +1 -1
  22. package/lib/module/components/header/variants/LargeTitleHeader/parts/LargeTitleContent.js +11 -20
  23. package/lib/module/components/header/variants/LargeTitleHeader/parts/LargeTitleContent.js.map +1 -1
  24. package/lib/module/components/header/variants/TitleHeader/index.js +4 -0
  25. package/lib/module/components/header/variants/TitleHeader/index.js.map +1 -1
  26. package/lib/module/components/image/Image.js +11 -5
  27. package/lib/module/components/image/Image.js.map +1 -1
  28. package/lib/module/components/image/utils.js +11 -0
  29. package/lib/module/components/image/utils.js.map +1 -0
  30. package/lib/module/components/input/parts/InputField.js +3 -3
  31. package/lib/module/components/input/parts/InputField.js.map +1 -1
  32. package/lib/module/components/screen/ScreenContext.js +4 -1
  33. package/lib/module/components/screen/ScreenContext.js.map +1 -1
  34. package/lib/module/components/screen/ScreenList.js +7 -4
  35. package/lib/module/components/screen/ScreenList.js.map +1 -1
  36. package/lib/module/components/screen/ScreenScrollView.js +6 -3
  37. package/lib/module/components/screen/ScreenScrollView.js.map +1 -1
  38. package/lib/module/components/screen/parts/ScreenHeaderContent.js +5 -1
  39. package/lib/module/components/screen/parts/ScreenHeaderContent.js.map +1 -1
  40. package/lib/module/components/surface/Surface.js +1 -1
  41. package/lib/module/components/text/Text.js +30 -8
  42. package/lib/module/components/text/Text.js.map +1 -1
  43. package/lib/module/components/toast/Toast.js +1 -1
  44. package/lib/module/components/toast/Toast.js.map +1 -1
  45. package/lib/module/components/toolbar/Toolbar.js +2 -2
  46. package/lib/module/components/toolbar/Toolbar.js.map +1 -1
  47. package/lib/module/theme/tokens/primitives.js +90 -84
  48. package/lib/module/theme/tokens/primitives.js.map +1 -1
  49. package/lib/typescript/src/components/header/hooks/use-large-title-header.d.ts.map +1 -1
  50. package/lib/typescript/src/components/header/parts/HeaderBar.d.ts +4 -1
  51. package/lib/typescript/src/components/header/parts/HeaderBar.d.ts.map +1 -1
  52. package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts +6 -1
  53. package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts.map +1 -1
  54. package/lib/typescript/src/components/header/variants/LargeTitleHeader/parts/LargeTitleContent.d.ts +1 -1
  55. package/lib/typescript/src/components/header/variants/LargeTitleHeader/parts/LargeTitleContent.d.ts.map +1 -1
  56. package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts +9 -2
  57. package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts.map +1 -1
  58. package/lib/typescript/src/components/image/Image.d.ts +3 -3
  59. package/lib/typescript/src/components/image/Image.d.ts.map +1 -1
  60. package/lib/typescript/src/components/image/index.d.ts +1 -1
  61. package/lib/typescript/src/components/image/index.d.ts.map +1 -1
  62. package/lib/typescript/src/components/image/types.d.ts +7 -0
  63. package/lib/typescript/src/components/image/types.d.ts.map +1 -1
  64. package/lib/typescript/src/components/image/utils.d.ts +3 -0
  65. package/lib/typescript/src/components/image/utils.d.ts.map +1 -0
  66. package/lib/typescript/src/components/screen/ScreenContext.d.ts +8 -0
  67. package/lib/typescript/src/components/screen/ScreenContext.d.ts.map +1 -1
  68. package/lib/typescript/src/components/screen/ScreenList.d.ts.map +1 -1
  69. package/lib/typescript/src/components/screen/ScreenScrollView.d.ts.map +1 -1
  70. package/lib/typescript/src/components/screen/parts/ScreenHeaderContent.d.ts +5 -1
  71. package/lib/typescript/src/components/screen/parts/ScreenHeaderContent.d.ts.map +1 -1
  72. package/lib/typescript/src/components/surface/Surface.d.ts +1 -1
  73. package/lib/typescript/src/components/text/Text.d.ts +1 -1
  74. package/lib/typescript/src/components/text/Text.d.ts.map +1 -1
  75. package/lib/typescript/src/theme/tokens/primitives.d.ts +60 -65
  76. package/lib/typescript/src/theme/tokens/primitives.d.ts.map +1 -1
  77. package/lib/typescript/src/theme/types.d.ts +8 -2
  78. package/lib/typescript/src/theme/types.d.ts.map +1 -1
  79. package/package.json +1 -1
  80. package/src/components/accordion/parts/AccordionContent.tsx +2 -2
  81. package/src/components/accordion/parts/AccordionTrigger.tsx +2 -2
  82. package/src/components/avatar/Avatar.tsx +3 -3
  83. package/src/components/badge/Badge.tsx +2 -2
  84. package/src/components/button/Button.tsx +3 -3
  85. package/src/components/chip/Chip.tsx +2 -2
  86. package/src/components/empty-state/EmptyState.tsx +3 -3
  87. package/src/components/header/hooks/use-large-title-header.ts +7 -1
  88. package/src/components/header/parts/HeaderBar.tsx +10 -13
  89. package/src/components/header/variants/LargeTitleHeader/index.tsx +13 -0
  90. package/src/components/header/variants/LargeTitleHeader/parts/LargeTitleContent.tsx +14 -22
  91. package/src/components/header/variants/TitleHeader/index.tsx +12 -1
  92. package/src/components/image/Image.tsx +7 -4
  93. package/src/components/image/index.ts +1 -1
  94. package/src/components/image/types.ts +8 -0
  95. package/src/components/image/utils.ts +11 -0
  96. package/src/components/input/parts/InputField.tsx +4 -4
  97. package/src/components/screen/ScreenContext.tsx +12 -0
  98. package/src/components/screen/ScreenList.tsx +14 -3
  99. package/src/components/screen/ScreenScrollView.tsx +12 -2
  100. package/src/components/screen/parts/ScreenHeaderContent.tsx +5 -1
  101. package/src/components/surface/Surface.tsx +1 -1
  102. package/src/components/text/Text.tsx +30 -11
  103. package/src/components/toast/Toast.tsx +1 -1
  104. package/src/components/toolbar/Toolbar.tsx +2 -2
  105. package/src/theme/tokens/primitives.ts +90 -84
  106. package/src/theme/types.ts +25 -6
@@ -5,17 +5,23 @@ import Animated from "react-native-reanimated";
5
5
  import { StyleSheet, useUnistyles } from "react-native-unistyles";
6
6
 
7
7
  import { FONT_WEIGHT_VALUE, SYSTEM_FONT } from "#utils/typography";
8
+ import { Text as ThemedText } from "../../../../text";
8
9
  import { isTextTitle } from "../../../utils";
9
10
  import type { LargeTitleHeaderProps } from "..";
10
11
 
11
12
  export function LargeTitleContent({
12
13
  title,
14
+ largeTitleVariant = "large-title",
15
+ largeTitleWeight,
13
16
  overline,
14
17
  subtitle,
15
18
  hasAccessoryBelow,
16
19
  onHeightChange,
17
20
  animatedStyle,
18
- }: Pick<LargeTitleHeaderProps, "title" | "overline" | "subtitle"> & {
21
+ }: Pick<
22
+ LargeTitleHeaderProps,
23
+ "title" | "largeTitleVariant" | "largeTitleWeight" | "overline" | "subtitle"
24
+ > & {
19
25
  /** Reserve a gap before a sticky accessory below this block. */
20
26
  hasAccessoryBelow: boolean;
21
27
  /** Reports the large-title block's layout height in dp. */
@@ -37,19 +43,16 @@ export function LargeTitleContent({
37
43
  </Text>
38
44
  )}
39
45
  {isTextTitle(title) ? (
40
- <Text
41
- style={styles.largeTitle}
42
- maxFontSizeMultiplier={theme.typography.variants["headline-lg"].maxFontSizeMultiplier}
43
- >
46
+ <ThemedText variant={largeTitleVariant} weight={largeTitleWeight}>
44
47
  {title}
45
- </Text>
48
+ </ThemedText>
46
49
  ) : (
47
50
  title
48
51
  )}
49
52
  {!!subtitle && (
50
53
  <Text
51
54
  style={styles.subtitle}
52
- maxFontSizeMultiplier={theme.typography.variants["body-lg"].maxFontSizeMultiplier}
55
+ maxFontSizeMultiplier={theme.typography.variants["body"].maxFontSizeMultiplier}
53
56
  >
54
57
  {subtitle}
55
58
  </Text>
@@ -64,10 +67,7 @@ const styles = StyleSheet.create((theme) => {
64
67
  const overlineWeight = theme.typography.variants["label-sm"].fontWeight;
65
68
  const overlineFont = theme.typography.fonts[overlineWeight];
66
69
  const isSystemOverlineFont = overlineFont === SYSTEM_FONT;
67
- const titleWeight = theme.typography.variants["headline-lg"].fontWeight;
68
- const titleFont = theme.typography.fonts[titleWeight];
69
- const isSystemTitleFont = titleFont === SYSTEM_FONT;
70
- const subtitleWeight = theme.typography.variants["body-lg"].fontWeight;
70
+ const subtitleWeight = theme.typography.variants["body"].fontWeight;
71
71
  const subtitleFont = theme.typography.fonts[subtitleWeight];
72
72
  const isSystemSubtitleFont = subtitleFont === SYSTEM_FONT;
73
73
 
@@ -86,18 +86,10 @@ const styles = StyleSheet.create((theme) => {
86
86
  fontWeight: isSystemOverlineFont ? FONT_WEIGHT_VALUE[overlineWeight] : undefined,
87
87
  color: (theme.color as unknown as Record<string, string | undefined>)["text.tertiary"] ?? "",
88
88
  },
89
- largeTitle: {
90
- fontSize: theme.typography.variants["headline-lg"].fontSize,
91
- lineHeight: theme.typography.variants["headline-lg"].lineHeight,
92
- letterSpacing: theme.typography.variants["headline-lg"].letterSpacing,
93
- fontFamily: isSystemTitleFont ? undefined : titleFont,
94
- fontWeight: isSystemTitleFont ? FONT_WEIGHT_VALUE[titleWeight] : undefined,
95
- color: (theme.color as unknown as Record<string, string | undefined>)["text.primary"] ?? "",
96
- },
97
89
  subtitle: {
98
- fontSize: theme.typography.variants["body-lg"].fontSize,
99
- lineHeight: theme.typography.variants["body-lg"].lineHeight,
100
- letterSpacing: theme.typography.variants["body-lg"].letterSpacing,
90
+ fontSize: theme.typography.variants["body"].fontSize,
91
+ lineHeight: theme.typography.variants["body"].lineHeight,
92
+ letterSpacing: theme.typography.variants["body"].letterSpacing,
101
93
  fontFamily: isSystemSubtitleFont ? undefined : subtitleFont,
102
94
  fontWeight: isSystemSubtitleFont ? FONT_WEIGHT_VALUE[subtitleWeight] : undefined,
103
95
  color: (theme.color as unknown as Record<string, string | undefined>)["text.tertiary"] ?? "",
@@ -3,7 +3,7 @@ import { View } from "react-native";
3
3
 
4
4
  import { StyleSheet } from "react-native-unistyles";
5
5
 
6
- import type { ColorToken } from "#theme/types";
6
+ import type { ColorToken, FontWeightToken, TextVariant } from "#theme/types";
7
7
  import { useHeaderBackground } from "../../../screen/parts/ScreenHeaderAppearance";
8
8
  import { ScreenContext } from "../../../screen/ScreenContext";
9
9
  import type { HeaderDismissProps } from "../../header-buttons";
@@ -19,6 +19,13 @@ export interface TitleHeaderProps extends HeaderDismissProps {
19
19
  * the header only places it according to `titleAlignment`.
20
20
  */
21
21
  title?: ReactNode;
22
+ /**
23
+ * Typography variant for a text title in the compact bar. Defaults to
24
+ * `'headline'`. Ignored when `title` is a custom node.
25
+ */
26
+ titleVariant?: TextVariant;
27
+ /** Font weight for a text title in the compact bar. Defaults to the variant's weight. */
28
+ titleWeight?: FontWeightToken;
22
29
  /** Optional secondary line shown under the title, in the `label-sm` style. */
23
30
  subtitle?: string;
24
31
  /**
@@ -58,6 +65,8 @@ export interface TitleHeaderProps extends HeaderDismissProps {
58
65
  /** Compact header. Screen owns the safe area when present; standalone headers reserve it here. */
59
66
  export function TitleHeader({
60
67
  title,
68
+ titleVariant,
69
+ titleWeight,
61
70
  subtitle,
62
71
  leadingActions,
63
72
  trailingActions,
@@ -79,6 +88,8 @@ export function TitleHeader({
79
88
  <HeaderBar
80
89
  {...dismissProps}
81
90
  title={title}
91
+ titleVariant={titleVariant}
92
+ titleWeight={titleWeight}
82
93
  subtitle={subtitle}
83
94
  titleAlignment={titleAlignment}
84
95
  color={color}
@@ -5,6 +5,7 @@ import { StyleSheet } from "react-native-unistyles";
5
5
  import { getImageComponent } from "#theme/registry";
6
6
  import type { RadiusKey } from "#theme/types";
7
7
  import type { ImageProps } from "./types";
8
+ import { toAspectRatio } from "./utils";
8
9
 
9
10
  /**
10
11
  * Renders the image implementation configured via
@@ -14,8 +15,8 @@ import type { ImageProps } from "./types";
14
15
  * ```tsx
15
16
  * <Image
16
17
  * source={{ uri: 'https://example.com/photo.jpg' }}
17
- * contentFit="cover"
18
- * style={{ width: 40, height: 40 }}
18
+ * width={320}
19
+ * aspect="16:9"
19
20
  * />
20
21
  * ```
21
22
  */
@@ -23,6 +24,7 @@ export function Image({
23
24
  contentFit = "cover",
24
25
  width,
25
26
  height,
27
+ aspect,
26
28
  radius = "none",
27
29
  style,
28
30
  // Defaults to `contentFit` here (rather than leaving it to each backend's
@@ -34,7 +36,7 @@ export function Image({
34
36
  const ImageComponent = getImageComponent();
35
37
 
36
38
  return (
37
- <View style={[styles.container(width, height, radius), style]}>
39
+ <View style={[styles.container(width, height, toAspectRatio(aspect), radius), style]}>
38
40
  <ImageComponent
39
41
  contentFit={contentFit}
40
42
  placeholderContentFit={placeholderContentFit}
@@ -46,9 +48,10 @@ export function Image({
46
48
  }
47
49
 
48
50
  const styles = StyleSheet.create((_theme) => ({
49
- container: (w: number | undefined, h: number | undefined, r: RadiusKey) => ({
51
+ container: (w: number | undefined, h: number | undefined, ratio: number | undefined, r: RadiusKey) => ({
50
52
  ...(w ? { width: w } : {}),
51
53
  ...(h ? { height: h } : {}),
54
+ ...(ratio ? { aspectRatio: ratio } : {}),
52
55
  borderRadius: _theme.radius[r],
53
56
  overflow: "hidden",
54
57
  }),
@@ -1,2 +1,2 @@
1
1
  export { Image } from "./Image";
2
- export type { ImageProps } from "./types";
2
+ export type { ImageAspect, ImageProps } from "./types";
@@ -1,7 +1,15 @@
1
1
  import type { RadiusKey, TerraImageProps } from "#theme/types";
2
2
 
3
+ /** Width-to-height ratio. A number is used as is; `"3:4"` means 3 wide by 4 tall. */
4
+ export type ImageAspect = number | "1:1" | "3:4" | "16:9";
5
+
3
6
  export interface ImageProps extends TerraImageProps {
4
7
  width?: number;
5
8
  height?: number;
9
+ /**
10
+ * Ratio that derives the missing dimension from the one given. Ignored when
11
+ * both `width` and `height` are set.
12
+ */
13
+ aspect?: ImageAspect;
6
14
  radius?: RadiusKey;
7
15
  }
@@ -0,0 +1,11 @@
1
+ import type { ImageAspect } from "./types";
2
+
3
+ const PRESET_RATIOS: Record<Exclude<ImageAspect, number>, number> = {
4
+ "1:1": 1,
5
+ "3:4": 3 / 4,
6
+ "16:9": 16 / 9,
7
+ };
8
+
9
+ export function toAspectRatio(aspect: ImageAspect | undefined) {
10
+ return typeof aspect === "string" ? PRESET_RATIOS[aspect] : aspect;
11
+ }
@@ -9,10 +9,10 @@ import { FONT_WEIGHT_VALUE, SYSTEM_FONT } from "#utils/typography";
9
9
  import type { InputBaseProps, InputSize } from "../types";
10
10
 
11
11
  /** Typographic role of the text, per size. */
12
- const TEXT_VARIANT: Record<InputSize, "body-sm" | "body-md" | "body-lg"> = {
13
- sm: "body-sm",
14
- md: "body-md",
15
- lg: "body-lg",
12
+ const TEXT_VARIANT: Record<InputSize, "subheadline" | "callout" | "body"> = {
13
+ sm: "subheadline",
14
+ md: "callout",
15
+ lg: "body",
16
16
  };
17
17
 
18
18
  /**
@@ -78,6 +78,14 @@ export interface ScreenContextValue {
78
78
  * accessory; zero releases the reserved space.
79
79
  */
80
80
  setHeaderAccessoryHeight: (height: number) => void;
81
+ /**
82
+ * Whether the header renders a large title in the scroll content. The large
83
+ * title then stands in for the content's top padding: scroll containers start
84
+ * it directly under the bar, and the padding is paid below it instead.
85
+ */
86
+ hasLargeTitle: boolean;
87
+ /** Reported by the header that renders the large title; false releases it. */
88
+ setHasLargeTitle: (value: boolean) => void;
81
89
  /**
82
90
  * Primary scroll container's resolved top padding in dp, including header clearance.
83
91
  * Used to position a sticky accessory below the large title at scroll offset zero.
@@ -149,6 +157,7 @@ export function ScreenScrollProvider({
149
157
  }: ScreenScrollProviderProps) {
150
158
  const scrollY = useSharedValue(0);
151
159
  const [headerAccessoryHeight, setAccessoryHeight] = useState(0);
160
+ const [hasLargeTitle, setHasLargeTitle] = useState(false);
152
161
  const [contentTopPadding, setTopPadding] = useState(0);
153
162
  const headerCollapseHeight = useSharedValue(0);
154
163
  const [headerSnapOffsets, setHeaderSnapOffsets] = useState<number[]>();
@@ -191,6 +200,8 @@ export function ScreenScrollProvider({
191
200
  hasHeader,
192
201
  headerAccessoryHeight,
193
202
  setHeaderAccessoryHeight,
203
+ hasLargeTitle,
204
+ setHasLargeTitle,
194
205
  contentTopPadding,
195
206
  setContentTopPadding,
196
207
  scrollY,
@@ -210,6 +221,7 @@ export function ScreenScrollProvider({
210
221
  hasHeader,
211
222
  headerAccessoryHeight,
212
223
  setHeaderAccessoryHeight,
224
+ hasLargeTitle,
213
225
  contentTopPadding,
214
226
  setContentTopPadding,
215
227
  scrollY,
@@ -182,6 +182,7 @@ const ScreenListBase = forwardRef(function ScreenList(
182
182
  contentPadding: screenPadding,
183
183
  hasHeader,
184
184
  headerAccessoryHeight,
185
+ hasLargeTitle,
185
186
  setContentTopPadding,
186
187
  contentOwnsBottomInset,
187
188
  platformAdjustsInsets,
@@ -196,7 +197,7 @@ const ScreenListBase = forwardRef(function ScreenList(
196
197
  const { y: paddingY } = resolveScreenPadding(theme, rt.breakpoint);
197
198
  const defaultTopPadding =
198
199
  (hasHeader ? theme.layout.header.height + headerAccessoryHeight : 0) +
199
- (hasVerticalPadding(resolvedPadding) ? paddingY : 0);
200
+ (hasVerticalPadding(resolvedPadding) && !hasLargeTitle ? paddingY : 0);
200
201
  const paddingTopOverride = NativeStyleSheet.flatten(contentContainerStyle)?.paddingTop;
201
202
  const contentTopPadding = typeof paddingTopOverride === "number" ? paddingTopOverride : defaultTopPadding;
202
203
  // ListHeaderComponent is wrapped by the list, so its local layout.y excludes
@@ -245,7 +246,14 @@ const ScreenListBase = forwardRef(function ScreenList(
245
246
  automaticallyAdjustContentInsets={false}
246
247
  automaticallyAdjustsScrollIndicatorInsets={false}
247
248
  contentContainerStyle={[
248
- styles.content(resolvedPadding, hasHeader, bottomInset, extraBottomPadding, headerAccessoryHeight),
249
+ styles.content(
250
+ resolvedPadding,
251
+ hasHeader,
252
+ hasLargeTitle,
253
+ bottomInset,
254
+ extraBottomPadding,
255
+ headerAccessoryHeight
256
+ ),
249
257
  contentContainerStyle,
250
258
  ]}
251
259
  ListHeaderComponent={header}
@@ -285,6 +293,7 @@ const styles = StyleSheet.create((theme, runtime) => ({
285
293
  content: (
286
294
  padding: ScreenContentPadding,
287
295
  hasHeader: boolean,
296
+ hasLargeTitle: boolean,
288
297
  bottomInset: number | undefined,
289
298
  extraBottomPadding: number,
290
299
  accessoryHeight: number
@@ -293,9 +302,11 @@ const styles = StyleSheet.create((theme, runtime) => ({
293
302
  return {
294
303
  flexGrow: 1,
295
304
  // The theme height covers the bar; an accessory pinned under it is extra.
305
+ // A large title takes the place of the top padding, which the header
306
+ // content host pays below it instead (see ScreenHeaderContent).
296
307
  paddingTop:
297
308
  (hasHeader ? theme.layout.header.height + accessoryHeight : 0) +
298
- (hasVerticalPadding(padding) ? y : 0),
309
+ (hasVerticalPadding(padding) && !hasLargeTitle ? y : 0),
299
310
  paddingHorizontal: hasHorizontalPadding(padding) ? x : 0,
300
311
  paddingBottom: (bottomInset ?? (hasVerticalPadding(padding) ? y : 0)) + extraBottomPadding,
301
312
  };
@@ -79,6 +79,7 @@ export const ScreenScrollView = forwardRef<ComponentRef<typeof ScrollView>, Scre
79
79
  contentPadding: screenPadding,
80
80
  hasHeader,
81
81
  headerAccessoryHeight,
82
+ hasLargeTitle,
82
83
  setContentTopPadding,
83
84
  background,
84
85
  contentOwnsBottomInset,
@@ -119,7 +120,13 @@ export const ScreenScrollView = forwardRef<ComponentRef<typeof ScrollView>, Scre
119
120
  showsVerticalScrollIndicator={false}
120
121
  scrollEventThrottle={16}
121
122
  contentContainerStyle={[
122
- styles.scrollContent(hasHeader, resolvedPadding, extraBottomPadding, headerAccessoryHeight),
123
+ styles.scrollContent(
124
+ hasHeader,
125
+ hasLargeTitle,
126
+ resolvedPadding,
127
+ extraBottomPadding,
128
+ headerAccessoryHeight
129
+ ),
123
130
  contentContainerStyle,
124
131
  ]}
125
132
  {...rest}
@@ -140,6 +147,7 @@ ScreenScrollView.displayName = "Screen.ScrollView";
140
147
  const styles = StyleSheet.create((theme, runtime) => ({
141
148
  scrollContent: (
142
149
  hasHeader: boolean,
150
+ hasLargeTitle: boolean,
143
151
  padding: ScreenContentPadding,
144
152
  extraBottomPadding: number,
145
153
  accessoryHeight: number
@@ -148,9 +156,11 @@ const styles = StyleSheet.create((theme, runtime) => ({
148
156
  return {
149
157
  flexGrow: 1,
150
158
  // The theme height covers the bar; an accessory pinned under it is extra.
159
+ // A large title takes the place of the top padding, which the header
160
+ // content host pays below it instead (see ScreenHeaderContent).
151
161
  paddingTop:
152
162
  (hasHeader ? theme.layout.header.height + accessoryHeight : 0) +
153
- (hasVerticalPadding(padding) ? y : 0),
163
+ (hasVerticalPadding(padding) && !hasLargeTitle ? y : 0),
154
164
  paddingHorizontal: hasHorizontalPadding(padding) ? x : 0,
155
165
  paddingBottom: (hasVerticalPadding(padding) ? y : 0) + extraBottomPadding,
156
166
  };
@@ -9,7 +9,11 @@ import { SCREEN_HEADER_PORTAL_HOST } from "../ScreenContext";
9
9
  import type { ScreenContentPadding } from "../types";
10
10
  import { hasVerticalPadding } from "../utils";
11
11
 
12
- /** Separates the large title and sticky accessory from the screen body. */
12
+ /**
13
+ * Hosts the large title and sticky accessory at the top of the scroll content.
14
+ * While it has content it pays the content's vertical padding below itself; the
15
+ * scroll container skips that padding above it (see `hasLargeTitle`).
16
+ */
13
17
  export function ScreenHeaderContent({ contentPadding }: { contentPadding: ScreenContentPadding }) {
14
18
  const [hasContent, setHasContent] = useState(false);
15
19
  const handleLayout = useCallback((event: LayoutChangeEvent) => {
@@ -64,7 +64,7 @@ const resolveElevation = (variant: SurfaceVariant, elevation?: ElevationKey): El
64
64
  * @example
65
65
  * ```tsx
66
66
  * <Surface variant="raised" elevation="md" p="4" radius="lg">
67
- * <Text variant="title-sm">Card title</Text>
67
+ * <Text variant="headline">Card title</Text>
68
68
  * </Surface>
69
69
  * ```
70
70
  */
@@ -7,6 +7,7 @@ import type {
7
7
  ColorToken,
8
8
  FontWeightToken,
9
9
  HeadingTextVariant,
10
+ LegacyTextVariant,
10
11
  RoleTextVariant,
11
12
  TextVariant,
12
13
  } from "#theme/types";
@@ -17,22 +18,40 @@ import { FONT_WEIGHT_VALUE, SYSTEM_FONT } from "#utils/typography";
17
18
  export type TextAlign = "left" | "center" | "right" | "justify";
18
19
 
19
20
  const HEADING_VARIANT_ALIAS: Record<HeadingTextVariant, RoleTextVariant> = {
20
- h1: "headline-lg",
21
- h2: "headline-md",
22
- h3: "headline-sm",
23
- h4: "title-lg",
24
- h5: "title-md",
25
- h6: "title-sm",
21
+ h1: "large-title",
22
+ h2: "title-1",
23
+ h3: "title-2",
24
+ h4: "title-3",
25
+ h5: "headline",
26
+ h6: "callout",
26
27
  };
27
28
 
29
+ // Pre-0.14 names, mapped to the nearest role on the iOS-style scale. Only the
30
+ // name is aliased; a legacy variant takes the new role's weight and cap.
31
+ const LEGACY_VARIANT_ALIAS: Record<LegacyTextVariant, RoleTextVariant> = {
32
+ "display-lg": "display",
33
+ "display-md": "display",
34
+ "display-sm": "large-title",
35
+ "headline-lg": "title-1",
36
+ "headline-md": "title-2",
37
+ "headline-sm": "title-2",
38
+ "title-lg": "title-3",
39
+ "title-md": "headline",
40
+ "title-sm": "headline",
41
+ "body-lg": "body",
42
+ "body-md": "body",
43
+ "body-sm": "subheadline",
44
+ caption: "caption-1",
45
+ };
46
+
47
+ const VARIANT_ALIAS: Record<string, RoleTextVariant> = { ...HEADING_VARIANT_ALIAS, ...LEGACY_VARIANT_ALIAS };
48
+
28
49
  function resolveTextVariant(variant: TextVariant): RoleTextVariant {
29
- return variant in HEADING_VARIANT_ALIAS
30
- ? HEADING_VARIANT_ALIAS[variant as HeadingTextVariant]
31
- : (variant as RoleTextVariant);
50
+ return VARIANT_ALIAS[variant] ?? (variant as RoleTextVariant);
32
51
  }
33
52
 
34
53
  export interface TextProps extends RNTextProps {
35
- /** Typographic role. Defaults to `body-md`. */
54
+ /** Typographic role. Defaults to `body`. */
36
55
  variant?: TextVariant;
37
56
  /** Semantic color token (e.g. `text.secondary`, `danger.soft.fg`). */
38
57
  color?: ColorToken;
@@ -53,7 +72,7 @@ export interface TextProps extends RNTextProps {
53
72
  */
54
73
  export const Text = forwardRef<ComponentRef<typeof RNText>, TextProps>(function Text(
55
74
  {
56
- variant = "body-md",
75
+ variant = "body",
57
76
  color,
58
77
  weight,
59
78
  align,
@@ -187,7 +187,7 @@ const ToastTitle = forwardRef<ComponentRef<typeof Text>, ToastTitleProps>(functi
187
187
  export interface ToastDescriptionProps extends TextProps {}
188
188
 
189
189
  const ToastDescription = forwardRef<ComponentRef<typeof Text>, ToastDescriptionProps>(
190
- function ToastDescription({ color, nativeID, variant = "body-sm", ...rest }, ref) {
190
+ function ToastDescription({ color, nativeID, variant = "subheadline", ...rest }, ref) {
191
191
  const { theme } = useUnistyles();
192
192
  const { nativeID: rootNativeID, variant: toastVariant } = useToastContext();
193
193
  const colors = getToastColors(toastVariant, theme);
@@ -207,14 +207,14 @@ function renderLabel(children: ReactNode, tint: ColorToken): ReactNode {
207
207
  return Children.map(children, (child) => {
208
208
  if (typeof child === "string") {
209
209
  return child.trim().length > 0 ? (
210
- <Text variant={LABEL_VARIANT} color={tint} weight="bold">
210
+ <Text variant={LABEL_VARIANT} color={tint} weight="medium">
211
211
  {child}
212
212
  </Text>
213
213
  ) : null;
214
214
  }
215
215
  if (typeof child === "number") {
216
216
  return (
217
- <Text variant={LABEL_VARIANT} color={tint} weight="bold">
217
+ <Text variant={LABEL_VARIANT} color={tint} weight="medium">
218
218
  {child}
219
219
  </Text>
220
220
  );