zkit-ui 1.0.0 → 1.0.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.
@@ -10,6 +10,8 @@
10
10
  - 默认使用 `getMaxFontSizeMultiplier()` 限制字体缩放上限,单次可用 `maxFontSizeMultiplier` 覆盖。
11
11
  - Android 默认关闭 `includeFontPadding`,用明确 lineHeight 保证 iOS / Android / Web 视觉高度更一致。
12
12
  - `color` 支持主题 token、语义色 token 和原生可解析颜色;无效颜色会回落到 `tone`。
13
+ - `style.fontSize` 作为最终 escape hatch 覆盖字号时,如果没有显式 `lineHeight`,组件不会继续注入默认行高,会交给原生 Text 按字体度量排版。
14
+ - 嵌套在 zkit `Text` 内部的 `Text`,未显式传入 `variant / size / lineHeight / style.fontSize / style.lineHeight` 时会继承父级排版。
13
15
  - 屏幕宽度变化时会重新计算排版尺寸,横竖屏和分屏场景不会卡在初始化尺寸。
14
16
 
15
17
  ## 基础用法
@@ -22,7 +24,7 @@ export function Demo() {
22
24
  }
23
25
  ```
24
26
 
25
- 默认等价于:
27
+ 单独使用时默认等价于:
26
28
 
27
29
  - `variant="body"`
28
30
  - `tone="default"`
@@ -52,7 +54,7 @@ export function Demo() {
52
54
  }
53
55
  ```
54
56
 
55
- `variant` 只决定默认 `size / weight / fontFamily`,显式传入 `size`、`weight` 或 `style` 时会覆盖对应值。
57
+ `variant` 只决定默认 `size / weight / fontFamily`,显式传入 `size`、`weight` 或 `style` 时会覆盖对应值。嵌套 `Text` 未显式声明排版时不会重新套用默认 `body`,以保持内联链接、强调文案和协议文字的字号稳定。
56
58
 
57
59
  ## 尺寸
58
60
 
@@ -88,6 +90,7 @@ export function Demo() {
88
90
  | `4xl` | `wp(36)` | `wp(44)` |
89
91
 
90
92
  传入数字 `size` 或 `lineHeight` 时,数字代表未缩放的设计像素,组件内部会统一转换。
93
+ 通过 `style.fontSize` 直接覆盖字号时,调用侧应传入已经计算好的原生字号;如果没有同时传入 `lineHeight` 或 `style.lineHeight`,组件会移除内置 token 行高,避免字号和行高不匹配。
91
94
 
92
95
  ## 颜色
93
96
 
@@ -173,6 +173,9 @@ const baseStyles = StyleSheet.create({
173
173
  },
174
174
  });
175
175
 
176
+ const DEFAULT_LINE_HEIGHT_RATIO = 1.4;
177
+ const NestedTextContext = React.createContext(false);
178
+
176
179
  function isFiniteNumber(value: unknown): value is number {
177
180
  return typeof value === 'number' && Number.isFinite(value);
178
181
  }
@@ -306,23 +309,62 @@ function resolveTypographyToken(size: TextSizeValue): TypographyToken {
306
309
  const fontSize = Math.max(0, size);
307
310
  return {
308
311
  fontSize,
309
- lineHeight: Math.ceil(fontSize * 1.4),
312
+ lineHeight: Math.ceil(fontSize * DEFAULT_LINE_HEIGHT_RATIO),
310
313
  };
311
314
  }
312
315
 
313
- function resolveTypographyStyle(size: TextSizeValue, explicitLineHeight: number | undefined): TextStyle {
314
- const token = resolveTypographyToken(size);
315
- return {
316
- fontSize: wp(token.fontSize),
317
- lineHeight: wp(isFiniteNumber(explicitLineHeight) ? Math.max(0, explicitLineHeight) : token.lineHeight),
318
- };
316
+ function readStyleNumber(style: TextStyle | undefined, key: 'fontSize' | 'lineHeight') {
317
+ const value = style?.[key];
318
+ return isFiniteNumber(value) ? Math.max(0, value) : undefined;
319
+ }
320
+
321
+ function resolveTypographyStyle({
322
+ explicitLineHeight,
323
+ hasExplicitSize,
324
+ shouldUseVariantDefaults,
325
+ size,
326
+ styleFontSize,
327
+ styleLineHeight,
328
+ }: {
329
+ explicitLineHeight: number | undefined;
330
+ hasExplicitSize: boolean;
331
+ shouldUseVariantDefaults: boolean;
332
+ size: TextSizeValue;
333
+ styleFontSize: number | undefined;
334
+ styleLineHeight: number | undefined;
335
+ }): TextStyle | undefined {
336
+ const shouldResolveSize = hasExplicitSize || shouldUseVariantDefaults;
337
+ const token = shouldResolveSize ? resolveTypographyToken(size) : undefined;
338
+ const nextStyle: TextStyle = {};
339
+ let hasStyle = false;
340
+
341
+ if (token) {
342
+ nextStyle.fontSize = wp(token.fontSize);
343
+ hasStyle = true;
344
+ }
345
+
346
+ if (styleLineHeight != null) {
347
+ return hasStyle ? nextStyle : undefined;
348
+ }
349
+
350
+ if (isFiniteNumber(explicitLineHeight)) {
351
+ nextStyle.lineHeight = wp(Math.max(0, explicitLineHeight));
352
+ hasStyle = true;
353
+ } else if (token && styleFontSize == null) {
354
+ nextStyle.lineHeight = wp(token.lineHeight);
355
+ hasStyle = true;
356
+ }
357
+
358
+ return hasStyle ? nextStyle : undefined;
319
359
  }
320
360
 
321
- function normalizeStyleFontWeight(style: StyleProp<TextStyle>): StyleProp<TextStyle> {
361
+ function normalizeStyleFontWeight(
362
+ style: StyleProp<TextStyle>,
363
+ flattenedStyle: TextStyle | undefined
364
+ ): StyleProp<TextStyle> {
322
365
  if (!style) return style;
323
366
 
324
- const flattened = StyleSheet.flatten(style);
325
- const fontWeight = flattened?.fontWeight;
367
+ const fontWeight = flattenedStyle?.fontWeight;
326
368
  if (fontWeight == null) return style;
327
369
 
328
370
  const normalizedFontWeight = normalizeFontWeight(fontWeight);
@@ -352,7 +394,7 @@ function resolveAccessibilityState(
352
394
 
353
395
  const TextBase = React.forwardRef<TextRef, TextProps>(function Text(
354
396
  {
355
- variant = 'body',
397
+ variant: variantProp,
356
398
  size,
357
399
  lineHeight,
358
400
  weight,
@@ -374,11 +416,16 @@ const TextBase = React.forwardRef<TextRef, TextProps>(function Text(
374
416
  },
375
417
  ref
376
418
  ) {
419
+ const nested = React.useContext(NestedTextContext);
377
420
  const theme = useTheme();
378
421
  const { width: viewportWidth } = useWindowDimensions();
422
+ const hasExplicitVariant = variantProp != null;
423
+ const shouldUseVariantDefaults = !nested || hasExplicitVariant;
424
+ const variant = variantProp ?? 'body';
379
425
  const variantToken: VariantToken = VARIANT_TOKENS[variant] ?? VARIANT_TOKENS.body;
426
+ const hasExplicitSize = size != null;
380
427
  const resolvedSize = size ?? variantToken.size;
381
- const resolvedWeight = weight ?? variantToken.weight;
428
+ const resolvedWeight = weight ?? (shouldUseVariantDefaults ? variantToken.weight : undefined);
382
429
  const toneColor = resolveToneColor(theme.colors, disabled ? 'disabled' : tone);
383
430
  const resolvedColor = resolveColorToken(color, toneColor, theme);
384
431
  const resolvedNumberOfLines = resolveNumberOfLines(truncate, numberOfLines);
@@ -388,20 +435,45 @@ const TextBase = React.forwardRef<TextRef, TextProps>(function Text(
388
435
  [accessibilityState, disabled]
389
436
  );
390
437
 
438
+ const flattenedStyle = React.useMemo(() => StyleSheet.flatten(style), [style]);
439
+ const styleFontSize = readStyleNumber(flattenedStyle, 'fontSize');
440
+ const styleLineHeight = readStyleNumber(flattenedStyle, 'lineHeight');
391
441
  const typographyStyle = React.useMemo(
392
- () => resolveTypographyStyle(resolvedSize, lineHeight),
393
- [lineHeight, resolvedSize, viewportWidth]
442
+ () =>
443
+ resolveTypographyStyle({
444
+ explicitLineHeight: lineHeight,
445
+ hasExplicitSize,
446
+ shouldUseVariantDefaults,
447
+ size: resolvedSize,
448
+ styleFontSize,
449
+ styleLineHeight,
450
+ }),
451
+ [
452
+ hasExplicitSize,
453
+ lineHeight,
454
+ resolvedSize,
455
+ shouldUseVariantDefaults,
456
+ styleFontSize,
457
+ styleLineHeight,
458
+ viewportWidth,
459
+ ]
394
460
  );
395
461
 
396
462
  const semanticStyle = React.useMemo<TextStyle>(() => {
397
463
  const nextStyle: TextStyle = {
398
- fontFamily: variantToken.fontFamily,
399
464
  fontVariant: tabularNumbers ? ['tabular-nums'] : undefined,
400
- fontWeight: normalizeFontWeight(resolveTextWeight(resolvedWeight)),
401
465
  textAlign: align,
402
466
  textTransform: transform,
403
467
  };
404
468
 
469
+ if (shouldUseVariantDefaults && variantToken.fontFamily !== undefined) {
470
+ nextStyle.fontFamily = variantToken.fontFamily;
471
+ }
472
+
473
+ if (resolvedWeight !== undefined) {
474
+ nextStyle.fontWeight = normalizeFontWeight(resolveTextWeight(resolvedWeight));
475
+ }
476
+
405
477
  if (resolvedColor !== undefined) {
406
478
  nextStyle.color = resolvedColor;
407
479
  }
@@ -411,12 +483,13 @@ const TextBase = React.forwardRef<TextRef, TextProps>(function Text(
411
483
  align,
412
484
  resolvedColor,
413
485
  resolvedWeight,
486
+ shouldUseVariantDefaults,
414
487
  tabularNumbers,
415
488
  transform,
416
489
  variantToken.fontFamily,
417
490
  ]);
418
491
 
419
- const normalizedStyle = React.useMemo(() => normalizeStyleFontWeight(style), [style]);
492
+ const normalizedStyle = React.useMemo(() => normalizeStyleFontWeight(style, flattenedStyle), [flattenedStyle, style]);
420
493
  const finalStyle = React.useMemo<StyleProp<TextStyle>>(
421
494
  () =>
422
495
  normalizedStyle
@@ -426,21 +499,23 @@ const TextBase = React.forwardRef<TextRef, TextProps>(function Text(
426
499
  );
427
500
 
428
501
  return (
429
- <RNText
430
- ref={ref}
431
- {...rest}
432
- accessibilityState={resolvedAccessibilityState}
433
- allowFontScaling={allowFontScaling}
434
- disabled={disabled}
435
- ellipsizeMode={resolvedEllipsizeMode}
436
- maxFontSizeMultiplier={
437
- allowFontScaling === false ? maxFontSizeMultiplier : (maxFontSizeMultiplier ?? getMaxFontSizeMultiplier())
438
- }
439
- numberOfLines={resolvedNumberOfLines}
440
- style={finalStyle}
441
- >
442
- {children}
443
- </RNText>
502
+ <NestedTextContext.Provider value>
503
+ <RNText
504
+ ref={ref}
505
+ {...rest}
506
+ accessibilityState={resolvedAccessibilityState}
507
+ allowFontScaling={allowFontScaling}
508
+ disabled={disabled}
509
+ ellipsizeMode={resolvedEllipsizeMode}
510
+ maxFontSizeMultiplier={
511
+ allowFontScaling === false ? maxFontSizeMultiplier : (maxFontSizeMultiplier ?? getMaxFontSizeMultiplier())
512
+ }
513
+ numberOfLines={resolvedNumberOfLines}
514
+ style={finalStyle}
515
+ >
516
+ {children}
517
+ </RNText>
518
+ </NestedTextContext.Provider>
444
519
  );
445
520
  });
446
521