@streamplace/components 0.7.27 → 0.7.29

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 (43) hide show
  1. package/dist/components/chat/chat-box.js +2 -2
  2. package/dist/components/chat/chat.js +1 -1
  3. package/dist/components/mobile-player/ui/autoplay-button.js +1 -0
  4. package/dist/components/mobile-player/ui/viewer-context-menu.js +1 -1
  5. package/dist/components/mobile-player/ui/viewer-loading-overlay.js +2 -2
  6. package/dist/components/mobile-player/use-webrtc.js +30 -0
  7. package/dist/components/ui/button.js +107 -155
  8. package/dist/components/ui/dialog.js +83 -116
  9. package/dist/components/ui/dropdown.js +41 -18
  10. package/dist/components/ui/input.js +53 -128
  11. package/dist/components/ui/primitives/button.js +0 -2
  12. package/dist/components/ui/primitives/modal.js +2 -2
  13. package/dist/components/ui/primitives/text.js +48 -8
  14. package/dist/components/ui/text.js +37 -66
  15. package/dist/components/ui/toast.js +78 -40
  16. package/dist/components/ui/view.js +28 -41
  17. package/dist/lib/theme/index.js +1 -2
  18. package/dist/lib/theme/theme.js +106 -54
  19. package/dist/lib/theme/tokens.js +94 -1
  20. package/dist/ui/index.js +2 -3
  21. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
  22. package/package.json +3 -2
  23. package/src/components/chat/chat-box.tsx +6 -3
  24. package/src/components/chat/chat.tsx +1 -0
  25. package/src/components/mobile-player/ui/autoplay-button.tsx +1 -0
  26. package/src/components/mobile-player/ui/viewer-context-menu.tsx +2 -2
  27. package/src/components/mobile-player/ui/viewer-loading-overlay.tsx +2 -2
  28. package/src/components/mobile-player/use-webrtc.tsx +31 -0
  29. package/src/components/ui/button.tsx +110 -172
  30. package/src/components/ui/dialog.tsx +96 -138
  31. package/src/components/ui/dropdown.tsx +60 -22
  32. package/src/components/ui/input.tsx +57 -144
  33. package/src/components/ui/primitives/button.tsx +0 -2
  34. package/src/components/ui/primitives/modal.tsx +0 -2
  35. package/src/components/ui/primitives/text.tsx +51 -8
  36. package/src/components/ui/text.tsx +42 -67
  37. package/src/components/ui/toast.tsx +108 -90
  38. package/src/components/ui/view.tsx +27 -41
  39. package/src/lib/theme/index.ts +0 -2
  40. package/src/lib/theme/theme.tsx +179 -72
  41. package/src/lib/theme/tokens.ts +97 -0
  42. package/src/ui/index.ts +0 -2
  43. package/tsconfig.tsbuildinfo +1 -1
@@ -19,9 +19,7 @@ import {
19
19
  } from "react-native";
20
20
  import {
21
21
  a,
22
- bg,
23
22
  borderRadius,
24
- colors,
25
23
  fontSize,
26
24
  gap,
27
25
  layout,
@@ -35,8 +33,8 @@ import {
35
33
  px,
36
34
  py,
37
35
  right,
38
- textColors,
39
36
  } from "../../lib/theme/atoms";
37
+ import { useTheme } from "../../ui";
40
38
  import {
41
39
  objectFromObjects,
42
40
  TextContext as TextClassContext,
@@ -60,6 +58,7 @@ export const DropdownMenuBottomSheet = forwardRef<
60
58
  ) {
61
59
  // Use the primitives' context to know if open
62
60
  const { open, onOpenChange } = DropdownMenuPrimitive.useRootContext();
61
+ const { zero: zt } = useTheme();
63
62
  const snapPoints = useMemo(() => ["25%", "50%", "80%"], []);
64
63
  const sheetRef = useRef<BottomSheet>(null);
65
64
 
@@ -81,11 +80,11 @@ export const DropdownMenuBottomSheet = forwardRef<
81
80
  )}
82
81
  onClose={() => onOpenChange?.(false)}
83
82
  style={[overlayStyle]}
84
- backgroundStyle={[bg.black, a.radius.all.md, a.shadows.md, p[1]]}
83
+ backgroundStyle={[zt.bg.popover, a.radius.all.md, a.shadows.md, p[1]]}
85
84
  handleIndicatorStyle={[
86
85
  a.sizes.width[12],
87
86
  a.sizes.height[1],
88
- bg.gray[500],
87
+ zt.bg.mutedForeground,
89
88
  ]}
90
89
  >
91
90
  <BottomSheetView style={[px[2]]}>
@@ -108,6 +107,7 @@ export const DropdownMenuSubTrigger = forwardRef<
108
107
  }
109
108
  >(({ inset, children, ...props }, ref) => {
110
109
  const { open } = DropdownMenuPrimitive.useSubContext();
110
+ const { icons } = useTheme();
111
111
  const Icon =
112
112
  Platform.OS === "web" ? ChevronRight : open ? ChevronUp : ChevronDown;
113
113
  return (
@@ -130,7 +130,7 @@ export const DropdownMenuSubTrigger = forwardRef<
130
130
  >
131
131
  {children}
132
132
  <View style={[a.layout.position.absolute, a.position.right[1]]}>
133
- <Icon size={18} color={colors.gray[200]} />
133
+ <Icon size={18} color={icons.color.muted} />
134
134
  </View>
135
135
  </View>
136
136
  </DropdownMenuPrimitive.SubTrigger>
@@ -142,6 +142,7 @@ export const DropdownMenuSubContent = forwardRef<
142
142
  any,
143
143
  DropdownMenuPrimitive.SubContentProps
144
144
  >((props, ref) => {
145
+ const { zero: zt } = useTheme();
145
146
  return (
146
147
  <DropdownMenuPrimitive.SubContent
147
148
  ref={ref}
@@ -151,9 +152,9 @@ export const DropdownMenuSubContent = forwardRef<
151
152
  a.overflow.hidden,
152
153
  a.radius.all.md,
153
154
  a.borders.width.thin,
154
- a.borders.color.gray[600],
155
+ zt.border.default,
155
156
  mt[1],
156
- bg.black,
157
+ zt.bg.popover,
157
158
  p[1],
158
159
  a.shadows.md,
159
160
  ]}
@@ -169,6 +170,7 @@ export const DropdownMenuContent = forwardRef<
169
170
  portalHost?: string;
170
171
  }
171
172
  >(({ overlayStyle, portalHost, ...props }, ref) => {
173
+ const { zero: zt } = useTheme();
172
174
  return (
173
175
  <DropdownMenuPrimitive.Portal hostName={portalHost}>
174
176
  <DropdownMenuPrimitive.Overlay
@@ -187,8 +189,8 @@ export const DropdownMenuContent = forwardRef<
187
189
  a.overflow.hidden,
188
190
  a.radius.all.md,
189
191
  a.borders.width.thin,
190
- a.borders.color.gray[800],
191
- bg.gray[950],
192
+ zt.border.default,
193
+ zt.bg.popover,
192
194
  p[2],
193
195
  a.shadows.md,
194
196
  ] as any
@@ -206,6 +208,7 @@ export const DropdownMenuContentWithoutPortal = forwardRef<
206
208
  overlayStyle?: any;
207
209
  }
208
210
  >(({ overlayStyle, ...props }, ref) => {
211
+ const { theme } = useTheme();
209
212
  return (
210
213
  <DropdownMenuPrimitive.Overlay
211
214
  style={[
@@ -223,8 +226,8 @@ export const DropdownMenuContentWithoutPortal = forwardRef<
223
226
  a.overflow.hidden,
224
227
  a.radius.all.md,
225
228
  a.borders.width.thin,
226
- a.borders.color.gray[800],
227
- bg.gray[950],
229
+ { borderColor: theme.colors.border },
230
+ { backgroundColor: theme.colors.popover },
228
231
  p[2],
229
232
  a.shadows.md,
230
233
  ] as any
@@ -263,10 +266,14 @@ export const DropdownMenuItem = forwardRef<
263
266
  any,
264
267
  DropdownMenuPrimitive.ItemProps & { inset?: boolean; disabled?: boolean }
265
268
  >(({ inset, disabled, style, children, ...props }, ref) => {
269
+ const { theme } = useTheme();
266
270
  return (
267
271
  <Pressable {...props}>
268
272
  <TextClassContext.Provider
269
- value={objectFromObjects([a.textColors.gray[900], a.fontSize.base])}
273
+ value={objectFromObjects([
274
+ { color: theme.colors.popoverForeground },
275
+ a.fontSize.base,
276
+ ])}
270
277
  >
271
278
  <View
272
279
  style={[
@@ -294,6 +301,7 @@ export const DropdownMenuCheckboxItem = forwardRef<
294
301
  children?: React.ReactNode;
295
302
  }
296
303
  >(({ children, checked, ...props }, ref) => {
304
+ const { theme } = useTheme();
297
305
  return (
298
306
  <DropdownMenuPrimitive.CheckboxItem
299
307
  ref={ref}
@@ -315,9 +323,17 @@ export const DropdownMenuCheckboxItem = forwardRef<
315
323
  {children}
316
324
  <View style={[pl[1], layout.position.absolute, right[1]]}>
317
325
  {checked ? (
318
- <CheckCircle size={14} strokeWidth={3} color="white" />
326
+ <CheckCircle
327
+ size={14}
328
+ strokeWidth={3}
329
+ color={theme.colors.foreground}
330
+ />
319
331
  ) : (
320
- <Circle size={14} strokeWidth={3} color={a.colors.gray[400]} />
332
+ <Circle
333
+ size={14}
334
+ strokeWidth={3}
335
+ color={theme.colors.mutedForeground}
336
+ />
321
337
  )}
322
338
  </View>
323
339
  </View>
@@ -332,6 +348,7 @@ export const DropdownMenuRadioItem = forwardRef<
332
348
  children?: React.ReactNode;
333
349
  }
334
350
  >(({ children, ...props }, ref) => {
351
+ const { theme } = useTheme();
335
352
  return (
336
353
  <DropdownMenuPrimitive.RadioItem
337
354
  ref={ref}
@@ -350,7 +367,7 @@ export const DropdownMenuRadioItem = forwardRef<
350
367
  >
351
368
  <View style={[pl[1], layout.position.absolute, right[1]]}>
352
369
  <DropdownMenuPrimitive.ItemIndicator>
353
- <Check size={14} strokeWidth={3} color="white" />
370
+ <Check size={14} strokeWidth={3} color={theme.colors.foreground} />
354
371
  </DropdownMenuPrimitive.ItemIndicator>
355
372
  </View>
356
373
  {children}
@@ -363,13 +380,14 @@ export const DropdownMenuLabel = forwardRef<
363
380
  any,
364
381
  DropdownMenuPrimitive.LabelProps & { inset?: boolean }
365
382
  >(({ inset, ...props }, ref) => {
383
+ const { theme } = useTheme();
366
384
  return (
367
385
  <Text
368
386
  ref={ref}
369
387
  style={[
370
388
  px[2],
371
389
  py[2],
372
- a.textColors.gray[200],
390
+ { color: theme.colors.textMuted },
373
391
  a.fontSize.base,
374
392
  inset && gap[2],
375
393
  ]}
@@ -382,15 +400,23 @@ export const DropdownMenuSeparator = forwardRef<
382
400
  any,
383
401
  DropdownMenuPrimitive.SeparatorProps
384
402
  >((props, ref) => {
385
- return <View ref={ref} style={[{ height: 0.5 }, bg.gray[800]]} {...props} />;
403
+ const { theme } = useTheme();
404
+ return (
405
+ <View
406
+ ref={ref}
407
+ style={[{ height: 0.5 }, { backgroundColor: theme.colors.border }]}
408
+ {...props}
409
+ />
410
+ );
386
411
  });
387
412
 
388
413
  export function DropdownMenuShortcut(props: any) {
414
+ const { theme } = useTheme();
389
415
  return (
390
416
  <Text
391
417
  style={[
392
418
  ml.auto,
393
- a.textColors.gray[500],
419
+ { color: theme.colors.textMuted },
394
420
  a.fontSize.sm,
395
421
  a.letterSpacing.widest,
396
422
  ]}
@@ -403,15 +429,18 @@ export const DropdownMenuGroup = forwardRef<
403
429
  any,
404
430
  { inset?: boolean; title?: string; children: ReactNode }
405
431
  >((props, ref) => {
432
+ const { theme } = useTheme();
406
433
  const { inset, title, children, ...rest } = props;
407
434
  return (
408
435
  <View style={[pt[2], inset && gap[2]]} ref={ref} {...rest}>
409
436
  {title && (
410
- <Text style={[textColors.gray[400], pb[1], pl[2]]}>{title}</Text>
437
+ <Text style={[{ color: theme.colors.textMuted }, pb[1], pl[2]]}>
438
+ {title}
439
+ </Text>
411
440
  )}
412
441
  <View
413
442
  style={[
414
- bg.gray[900],
443
+ { backgroundColor: theme.colors.muted },
415
444
  Platform.OS === "web" ? [px[2], py[1]] : p[2],
416
445
  gap.all[1],
417
446
  { borderRadius: borderRadius.lg },
@@ -425,8 +454,17 @@ export const DropdownMenuGroup = forwardRef<
425
454
 
426
455
  export const DropdownMenuInfo = forwardRef<any, any>(
427
456
  ({ description, ...props }, ref) => {
457
+ const { theme } = useTheme();
428
458
  return (
429
- <Text style={[textColors.gray[400], pt[1], pl[2], pb[2], fontSize.sm]}>
459
+ <Text
460
+ style={[
461
+ { color: theme.colors.textMuted },
462
+ pt[1],
463
+ pl[2],
464
+ pb[2],
465
+ fontSize.sm,
466
+ ]}
467
+ >
430
468
  {description}
431
469
  </Text>
432
470
  );
@@ -1,7 +1,8 @@
1
1
  import { cva, type VariantProps } from "class-variance-authority";
2
2
  import React, { forwardRef } from "react";
3
- import { Platform, StyleSheet, TouchableWithoutFeedback } from "react-native";
3
+ import { TouchableWithoutFeedback } from "react-native";
4
4
  import { useTheme } from "../../lib/theme/theme";
5
+ import * as zero from "../../ui";
5
6
  import { InputPrimitive, InputPrimitiveProps } from "./primitives/input";
6
7
 
7
8
  const inputVariants = cva("", {
@@ -54,26 +55,66 @@ export const Input = forwardRef<any, InputProps>(
54
55
  },
55
56
  ref,
56
57
  ) => {
57
- const { theme } = useTheme();
58
+ const { zero: zt, theme } = useTheme();
58
59
  const [isFocused, setIsFocused] = React.useState(false);
59
60
  const inputRef = React.useRef<any>(null);
60
61
 
61
- // Create dynamic styles based on theme
62
- const styles = React.useMemo(() => createStyles(theme), [theme]);
63
-
64
- // Get variant and size styles
62
+ // Get variant and size styles using theme.zero
65
63
  const containerStyles = React.useMemo(() => {
66
- const variantStyle = styles[`${variant}Container` as keyof typeof styles];
67
- const sizeStyle = styles[`${size}Container` as keyof typeof styles];
68
- const focusStyle = isFocused ? styles.focusedContainer : null;
69
- return [variantStyle, sizeStyle, focusStyle];
70
- }, [variant, size, styles, isFocused]);
64
+ const variantStyle = (() => {
65
+ switch (variant) {
66
+ case "filled":
67
+ return [zt.bg.muted];
68
+ case "underlined":
69
+ return [
70
+ zt.bg.transparent,
71
+ zt.border.bottom.default,
72
+ { borderRadius: 0, paddingHorizontal: 0 },
73
+ ];
74
+ default:
75
+ return [zt.bg.background, zt.border.default];
76
+ }
77
+ })();
78
+
79
+ const sizeStyle = (() => {
80
+ switch (size) {
81
+ case "sm":
82
+ return [
83
+ zero.px[3],
84
+ zero.py[2],
85
+ { borderRadius: zero.borderRadius.md },
86
+ ];
87
+ case "lg":
88
+ return [
89
+ zero.px[4],
90
+ zero.py[3],
91
+ { borderRadius: zero.borderRadius.md },
92
+ ];
93
+ default:
94
+ return [
95
+ zero.px[3],
96
+ zero.py[2],
97
+ { borderRadius: zero.borderRadius.md },
98
+ ];
99
+ }
100
+ })();
101
+
102
+ const focusStyle = isFocused ? zt.border.primary : null;
103
+ return [variantStyle, sizeStyle, focusStyle].filter(Boolean);
104
+ }, [variant, size, zt, isFocused]);
71
105
 
72
106
  const textStyles = React.useMemo(() => {
73
- const variantTextStyle = styles[`${variant}Input` as keyof typeof styles];
74
- const sizeTextStyle = styles[`${size}Input` as keyof typeof styles];
75
- return [variantTextStyle, sizeTextStyle];
76
- }, [variant, size, styles]);
107
+ const baseTextStyle = [zt.text.foreground, zt.bg.transparent];
108
+
109
+ switch (size) {
110
+ case "sm":
111
+ return [...baseTextStyle, zt.text.sm];
112
+ case "lg":
113
+ return [...baseTextStyle, zt.text.lg];
114
+ default:
115
+ return [...baseTextStyle, zt.text.md];
116
+ }
117
+ }, [size, zt]);
77
118
 
78
119
  const handleFocus = React.useCallback(
79
120
  (event: any) => {
@@ -144,12 +185,7 @@ export const Input = forwardRef<any, InputProps>(
144
185
  error={!!error}
145
186
  onFocus={handleFocus}
146
187
  onBlur={handleBlur}
147
- style={[
148
- textStyles,
149
- styles.inputInContainer,
150
- inputStyle,
151
- { outline: "none" },
152
- ]}
188
+ style={[textStyles, inputStyle, { outline: "none" }]}
153
189
  placeholderTextColor={
154
190
  disabled ? theme.colors.textDisabled : theme.colors.textMuted
155
191
  }
@@ -223,128 +259,5 @@ export const Input = forwardRef<any, InputProps>(
223
259
 
224
260
  Input.displayName = "Input";
225
261
 
226
- // Create theme-aware styles
227
- function createStyles(theme: any) {
228
- return StyleSheet.create({
229
- // Variant styles for containers
230
- defaultContainer: {
231
- backgroundColor: theme.colors.background,
232
- borderWidth: 1,
233
- borderColor: theme.colors.border,
234
- borderRadius: theme.borderRadius.md,
235
- },
236
-
237
- filledContainer: {
238
- backgroundColor: theme.colors.muted,
239
- borderWidth: 0,
240
- borderRadius: theme.borderRadius.md,
241
- },
242
-
243
- underlinedContainer: {
244
- backgroundColor: "transparent",
245
- borderWidth: 0,
246
- borderBottomWidth: 1,
247
- borderBottomColor: theme.colors.border,
248
- borderRadius: 0,
249
- paddingHorizontal: 0,
250
- },
251
-
252
- // Variant styles for inputs
253
- defaultInput: {
254
- color: theme.colors.text,
255
- backgroundColor: "transparent",
256
- },
257
-
258
- filledInput: {
259
- color: theme.colors.text,
260
- backgroundColor: "transparent",
261
- },
262
-
263
- underlinedInput: {
264
- color: theme.colors.text,
265
- backgroundColor: "transparent",
266
- },
267
-
268
- // Size styles for containers
269
- smContainer: {
270
- paddingHorizontal: theme.spacing[3],
271
- paddingVertical: theme.spacing[2],
272
- minHeight: theme.touchTargets.minimum - 8,
273
- },
274
-
275
- mdContainer: {
276
- paddingHorizontal: theme.spacing[3],
277
- paddingVertical: theme.spacing[3],
278
- minHeight: theme.touchTargets.minimum,
279
- },
280
-
281
- lgContainer: {
282
- paddingHorizontal: theme.spacing[4],
283
- paddingVertical: theme.spacing[4],
284
- minHeight: theme.touchTargets.comfortable,
285
- },
286
-
287
- // Size styles for inputs
288
- smInput: {
289
- fontSize: 14,
290
- lineHeight: 18,
291
- ...Platform.select({
292
- ios: {
293
- paddingVertical: 0,
294
- },
295
- android: {
296
- paddingVertical: 0,
297
- textAlignVertical: "center",
298
- },
299
- }),
300
- },
301
-
302
- mdInput: {
303
- fontSize: 16,
304
- lineHeight: 20,
305
- ...Platform.select({
306
- ios: {
307
- paddingVertical: 0,
308
- },
309
- android: {
310
- paddingVertical: 0,
311
- textAlignVertical: "center",
312
- },
313
- }),
314
- },
315
-
316
- lgInput: {
317
- fontSize: 18,
318
- lineHeight: 22,
319
- ...Platform.select({
320
- ios: {
321
- paddingVertical: 0,
322
- },
323
- android: {
324
- paddingVertical: 0,
325
- textAlignVertical: "center",
326
- },
327
- }),
328
- },
329
-
330
- // Special style for inputs inside containers
331
- inputInContainer: {
332
- flex: 1,
333
- paddingHorizontal: 0,
334
- paddingVertical: 0,
335
- borderWidth: 0,
336
- backgroundColor: "transparent",
337
- minHeight: "auto",
338
- borderRadius: 0,
339
- },
340
-
341
- // Focus styles
342
- focusedContainer: {
343
- borderColor: theme.colors.primary,
344
- borderWidth: 1,
345
- },
346
- });
347
- }
348
-
349
262
  // Export input variants for external use
350
263
  export { inputVariants };
@@ -245,7 +245,6 @@ const primitiveStyles = StyleSheet.create({
245
245
  alignItems: "center",
246
246
  justifyContent: "center",
247
247
  minHeight: 44, // iOS minimum touch target
248
- minWidth: 44,
249
248
  },
250
249
  disabled: {
251
250
  opacity: 0.5,
@@ -254,7 +253,6 @@ const primitiveStyles = StyleSheet.create({
254
253
  flexDirection: "row",
255
254
  alignItems: "center",
256
255
  justifyContent: "center",
257
- flex: 1,
258
256
  },
259
257
  text: {
260
258
  textAlign: "center" as const,
@@ -33,7 +33,6 @@ export const ModalRoot = forwardRef<View, ModalPrimitiveProps>(
33
33
  onRequestClose,
34
34
  animationType = "fade",
35
35
  presentationStyle = Platform.OS === "ios" ? "pageSheet" : "fullScreen",
36
- transparent = true,
37
36
  statusBarTranslucent = Platform.OS === "android",
38
37
  ...props
39
38
  },
@@ -57,7 +56,6 @@ export const ModalRoot = forwardRef<View, ModalPrimitiveProps>(
57
56
  onRequestClose={handleRequestClose}
58
57
  animationType={animationType}
59
58
  presentationStyle={presentationStyle}
60
- transparent={transparent}
61
59
  statusBarTranslucent={statusBarTranslucent}
62
60
  {...props}
63
61
  >
@@ -129,6 +129,18 @@ const sizeMap = {
129
129
  "4xl": 36,
130
130
  } as const;
131
131
 
132
+ // Size-specific line height mapping (provides better default line heights for each size)
133
+ const sizeLineHeightMap = {
134
+ xs: 16, // 12px * 1.33 = tight but readable
135
+ sm: 20, // 14px * 1.43 = good for small text
136
+ base: 24, // 16px * 1.5 = standard body text
137
+ lg: 28, // 18px * 1.56 = comfortable for larger text
138
+ xl: 30, // 20px * 1.5 = balanced
139
+ "2xl": 32, // 24px * 1.33 = tighter for headings
140
+ "3xl": 36, // 30px * 1.2 = tight for large headings
141
+ "4xl": 40, // 36px * 1.11 = very tight for display text
142
+ } as const;
143
+
132
144
  // Weight mapping
133
145
  const weightMap = {
134
146
  thin: "100",
@@ -199,12 +211,12 @@ const getVariantStyles = () => {
199
211
  };
200
212
  } else if (typographicPlatform === "android") {
201
213
  return {
202
- h1: platformTypography.headline1,
203
- h2: platformTypography.headline2,
204
- h3: platformTypography.headline3,
205
- h4: platformTypography.headline4,
206
- h5: platformTypography.headline5,
207
- h6: platformTypography.headline6,
214
+ h1: platformTypography.headline4, // 34px instead of 96px
215
+ h2: platformTypography.headline5, // 24px instead of 60px
216
+ h3: platformTypography.headline6, // 20px instead of 48px
217
+ h4: platformTypography.subtitle1, // 16px instead of 34px
218
+ h5: platformTypography.subtitle2, // 14px instead of 24px
219
+ h6: platformTypography.subtitle2, // 14px - consistent with h5
208
220
  subtitle1: platformTypography.subtitle1,
209
221
  subtitle2: platformTypography.subtitle2,
210
222
  body1: platformTypography.body1,
@@ -286,9 +298,16 @@ export const TextRoot = forwardRef<RNText, TextPrimitiveProps>(
286
298
 
287
299
  // Apply explicit prop styles (these should override inherited and variant)
288
300
 
289
- // Apply size
301
+ // Apply size (with corresponding line height if not explicitly set)
290
302
  ...(size && {
291
303
  fontSize: typeof size === "number" ? size : sizeMap[size],
304
+ // Apply size-specific line height only if leading is not explicitly set
305
+ ...(leading === undefined && {
306
+ lineHeight:
307
+ typeof size === "number"
308
+ ? size // Auto line height for numeric sizes
309
+ : sizeLineHeightMap[size],
310
+ }),
292
311
  }),
293
312
 
294
313
  // Apply weight
@@ -360,6 +379,23 @@ export const TextRoot = forwardRef<RNText, TextPrimitiveProps>(
360
379
  finalStyles.color = finalStyles.color as ColorValue;
361
380
 
362
381
  // Create context value for children
382
+ // Process custom styles to auto-add line height for fontSize
383
+ const processedStyle = Array.isArray(style)
384
+ ? style
385
+ : [style].filter(Boolean);
386
+ const enhancedStyles = processedStyle.map((styleObj) => {
387
+ if (styleObj && typeof styleObj === "object" && "fontSize" in styleObj) {
388
+ const fontSize = styleObj.fontSize;
389
+ if (typeof fontSize === "number" && !styleObj.lineHeight && !leading) {
390
+ return {
391
+ ...styleObj,
392
+ lineHeight: fontSize * 1.2,
393
+ };
394
+ }
395
+ }
396
+ return styleObj;
397
+ });
398
+
363
399
  const contextValue: TextContextValue = {
364
400
  fontSize:
365
401
  typeof finalStyles.fontSize === "number"
@@ -386,7 +422,7 @@ export const TextRoot = forwardRef<RNText, TextPrimitiveProps>(
386
422
 
387
423
  return (
388
424
  <TextContext.Provider value={contextValue}>
389
- <RNText ref={ref} style={[finalStyles, style]} {...props}>
425
+ <RNText ref={ref} style={[finalStyles, ...enhancedStyles]} {...props}>
390
426
  {children}
391
427
  </RNText>
392
428
  </TextContext.Provider>
@@ -438,6 +474,13 @@ export function createTextStyle(
438
474
  if (props.size) {
439
475
  style.fontSize =
440
476
  typeof props.size === "number" ? props.size : sizeMap[props.size];
477
+ // Apply size-specific line height only if leading is not explicitly set
478
+ if (props.leading === undefined) {
479
+ style.lineHeight =
480
+ typeof props.size === "number"
481
+ ? props.size * 1.2 // Auto line height for numeric sizes
482
+ : sizeLineHeightMap[props.size];
483
+ }
441
484
  }
442
485
 
443
486
  if (props.weight) {