@yahoo/uds-mobile 2.23.1 → 2.23.3

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 (32) hide show
  1. package/dist/components/BottomSheet/BottomSheet.cjs +1 -1
  2. package/dist/components/BottomSheet/BottomSheet.js +1 -1
  3. package/dist/components/Button/buttonTheme.cjs +48 -21
  4. package/dist/components/Button/buttonTheme.d.cts +15 -10
  5. package/dist/components/Button/buttonTheme.d.cts.map +1 -1
  6. package/dist/components/Button/buttonTheme.d.ts +15 -10
  7. package/dist/components/Button/buttonTheme.d.ts.map +1 -1
  8. package/dist/components/Button/buttonTheme.js +47 -21
  9. package/dist/components/Button/buttonTheme.js.map +1 -1
  10. package/dist/components/Button.cjs +17 -9
  11. package/dist/components/Button.d.cts.map +1 -1
  12. package/dist/components/Button.d.ts.map +1 -1
  13. package/dist/components/Button.js +17 -9
  14. package/dist/components/Button.js.map +1 -1
  15. package/dist/components/Icon.cjs +3 -2
  16. package/dist/components/Icon.d.cts.map +1 -1
  17. package/dist/components/Icon.d.ts.map +1 -1
  18. package/dist/components/Icon.js +3 -2
  19. package/dist/components/Icon.js.map +1 -1
  20. package/dist/components/IconButton.cjs +18 -8
  21. package/dist/components/IconButton.js +18 -8
  22. package/dist/components/IconButton.js.map +1 -1
  23. package/dist/components/Switch.cjs +1 -1
  24. package/dist/components/Switch.js +2 -2
  25. package/dist/components/Switch.js.map +1 -1
  26. package/dist/components/internal/Overlay/index.cjs +1 -1
  27. package/dist/components/internal/Overlay/index.js +1 -1
  28. package/dist/mobile/generated/fontMetrics.cjs +1166 -0
  29. package/dist/mobile/generated/fontMetrics.js +1168 -0
  30. package/dist/mobile/generated/fontMetrics.js.map +1 -0
  31. package/generated/fontMetrics.ts +1173 -0
  32. package/package.json +1 -1
@@ -21,8 +21,8 @@ let generated_styles = require("../../../generated/styles");
21
21
  let react_native_reanimated = require("react-native-reanimated");
22
22
  react_native_reanimated = require_runtime.__toESM(react_native_reanimated);
23
23
  let react_native_gesture_handler = require("react-native-gesture-handler");
24
- let react_native_safe_area_context = require("react-native-safe-area-context");
25
24
  let react_native_screens = require("react-native-screens");
25
+ let react_native_safe_area_context = require("react-native-safe-area-context");
26
26
  //#region src/components/BottomSheet/BottomSheet.tsx
27
27
  function useScreenCornerRadius() {
28
28
  const [, forceUpdate] = (0, react.useState)(0);
@@ -18,8 +18,8 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
18
18
  import { bottomSheetStyles } from "../../../generated/styles";
19
19
  import Animated, { interpolate, useAnimatedStyle, useSharedValue } from "react-native-reanimated";
20
20
  import { GestureDetector, GestureHandlerRootView } from "react-native-gesture-handler";
21
- import { useSafeAreaInsets } from "react-native-safe-area-context";
22
21
  import { FullWindowOverlay } from "react-native-screens";
22
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
23
23
  //#region src/components/BottomSheet/BottomSheet.tsx
24
24
  function useScreenCornerRadius() {
25
25
  const [, forceUpdate] = useState(0);
@@ -1,5 +1,6 @@
1
1
  /*! © 2026 Yahoo, Inc. UDS Mobile v0.0.0-development */
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require_fontMetrics = require("../../mobile/generated/fontMetrics.cjs");
3
4
  //#region src/components/Button/buttonTheme.ts
4
5
  const SHARED_BUTTON_ICONBUTTON_SIZES = new Set([
5
6
  "xs",
@@ -7,6 +8,7 @@ const SHARED_BUTTON_ICONBUTTON_SIZES = new Set([
7
8
  "md",
8
9
  "lg"
9
10
  ]);
11
+ const UNKNOWN_FONT_LINE_HEIGHT_RATIO = 1.3;
10
12
  function buttonSizePath(size, layer) {
11
13
  return `button/size/${size}/${layer}/rest`;
12
14
  }
@@ -23,47 +25,72 @@ function getIconSize(iconStyle) {
23
25
  function getVerticalPadding(rootStyle) {
24
26
  return (typeof rootStyle.paddingVertical === "number" ? rootStyle.paddingVertical : void 0) ?? (typeof rootStyle.padding === "number" ? rootStyle.padding : void 0) ?? 0;
25
27
  }
26
- function getButtonContentSize(iconSize, textStyle) {
27
- const lineHeight = typeof textStyle.lineHeight === "number" ? textStyle.lineHeight : void 0;
28
- const fontSize = typeof textStyle.fontSize === "number" ? textStyle.fontSize : iconSize;
29
- return Math.max(iconSize, lineHeight ?? fontSize);
28
+ function getMetricHeight(value, unitsPerEm, fontSize) {
29
+ return Math.ceil(Math.max(0, value) / unitsPerEm * fontSize);
30
+ }
31
+ function getSafeLabelLineHeight(textStyle, fallbackFontSize) {
32
+ const fontSize = typeof textStyle.fontSize === "number" ? textStyle.fontSize : fallbackFontSize;
33
+ const tokenLineHeight = typeof textStyle.lineHeight === "number" ? textStyle.lineHeight : fontSize;
34
+ const metrics = typeof textStyle.fontFamily === "string" ? require_fontMetrics.fontMetrics[textStyle.fontFamily] : void 0;
35
+ if (!metrics || metrics.unitsPerEm <= 0) return Math.max(tokenLineHeight, Math.ceil(fontSize * UNKNOWN_FONT_LINE_HEIGHT_RATIO));
36
+ const metricsLineHeight = getMetricHeight(metrics.ascent, metrics.unitsPerEm, fontSize) + getMetricHeight(-metrics.descent, metrics.unitsPerEm, fontSize) + getMetricHeight(metrics.lineGap, metrics.unitsPerEm, fontSize);
37
+ return Math.max(tokenLineHeight, metricsLineHeight);
30
38
  }
31
- /** Control height from vertical padding plus max(icon size, text line-height), matching web. */
39
+ /** Safe line box for the icon font's em square plus any bbox overflow. */
40
+ function getSafeIconCell(fontSize) {
41
+ const metrics = require_fontMetrics.fontMetrics["uds-icons"];
42
+ if (!metrics || metrics.unitsPerEm <= 0) return Math.ceil(fontSize * UNKNOWN_FONT_LINE_HEIGHT_RATIO);
43
+ const overflowAboveEm = Math.max(0, metrics.bbox.maxY - metrics.unitsPerEm);
44
+ const overflowBelowEm = Math.max(0, -metrics.bbox.minY);
45
+ return Math.ceil(fontSize) + getMetricHeight(overflowAboveEm, metrics.unitsPerEm, fontSize) + getMetricHeight(overflowBelowEm, metrics.unitsPerEm, fontSize);
46
+ }
47
+ /** Safe native content metrics while retaining the token-derived target control height. */
32
48
  function getButtonControlMetrics(theme, size) {
33
49
  const rootStyle = getLayerStyle(theme, buttonSizePath(size, "root"));
34
50
  const iconStyle = getLayerStyle(theme, buttonSizePath(size, "icon"));
35
51
  const textStyle = getLayerStyle(theme, buttonSizePath(size, "rootText"));
36
52
  const paddingVertical = getVerticalPadding(rootStyle);
37
53
  const iconSize = getIconSize(iconStyle);
38
- const contentSize = getButtonContentSize(iconSize, textStyle);
54
+ const fontSize = typeof textStyle.fontSize === "number" ? textStyle.fontSize : iconSize;
55
+ const tokenLineHeight = typeof textStyle.lineHeight === "number" ? textStyle.lineHeight : fontSize;
56
+ const targetContentHeight = Math.max(iconSize, tokenLineHeight);
57
+ const minHeight = Math.ceil(paddingVertical * 2 + targetContentHeight);
58
+ const labelLineHeight = getSafeLabelLineHeight(textStyle, iconSize);
59
+ const iconCellHeight = getSafeIconCell(iconSize);
39
60
  return {
40
- controlHeight: Math.ceil(paddingVertical * 2 + contentSize),
41
- contentLineHeight: Math.ceil(contentSize),
42
- iconSize
61
+ minHeight,
62
+ labelLineHeight,
63
+ iconCellHeight,
64
+ paddingVertical: Math.max(0, (minHeight - Math.max(labelLineHeight, iconCellHeight)) / 2)
43
65
  };
44
66
  }
45
- /** IconButton uses explicit control height only when CSS sets height (match-button-height opt-in). */
67
+ /** Safe icon cell within the token-derived control size; explicit token height (match-button-height opt-in) wins as the target. */
46
68
  function getIconButtonControlMetrics(theme, size) {
47
69
  const rootStyle = getLayerStyle(theme, iconButtonSizePath(size, "root"));
48
- const iconSize = getIconSize(getLayerStyle(theme, iconButtonSizePath(size, "icon")));
70
+ const iconStyle = getLayerStyle(theme, iconButtonSizePath(size, "icon"));
71
+ const iconSize = getIconSize(iconStyle);
72
+ const tokenLineHeight = typeof iconStyle.lineHeight === "number" ? iconStyle.lineHeight : iconSize;
73
+ const paddingVertical = getVerticalPadding(rootStyle);
49
74
  const explicitHeight = typeof rootStyle.height === "number" ? rootStyle.height : void 0;
50
- if (SHARED_BUTTON_ICONBUTTON_SIZES.has(size) && explicitHeight !== void 0) return {
51
- controlHeight: explicitHeight,
52
- iconSize
53
- };
75
+ const hasExplicitHeight = SHARED_BUTTON_ICONBUTTON_SIZES.has(size) && explicitHeight !== void 0;
76
+ const minHeight = hasExplicitHeight ? explicitHeight : Math.ceil(paddingVertical * 2 + Math.max(iconSize, tokenLineHeight));
77
+ const iconCellHeight = getSafeIconCell(iconSize);
54
78
  return {
55
- controlHeight: 0,
56
- iconSize
79
+ minHeight,
80
+ iconCellHeight,
81
+ paddingVertical: Math.max(0, (minHeight - Math.max(iconSize, iconCellHeight)) / 2),
82
+ hasExplicitHeight
57
83
  };
58
84
  }
59
- /** Label line-height matches content box (max of icon size and line-height). */
60
- function getButtonTextStyleForControlHeight(textStyle, contentLineHeight) {
85
+ /** Apply the metric-safe line height after the token typography styles. */
86
+ function getButtonTextStyle(textStyle, labelLineHeight) {
61
87
  return {
62
88
  ...textStyle,
63
- lineHeight: contentLineHeight
89
+ lineHeight: labelLineHeight
64
90
  };
65
91
  }
66
92
  //#endregion
67
93
  exports.getButtonControlMetrics = getButtonControlMetrics;
68
- exports.getButtonTextStyleForControlHeight = getButtonTextStyleForControlHeight;
94
+ exports.getButtonTextStyle = getButtonTextStyle;
69
95
  exports.getIconButtonControlMetrics = getIconButtonControlMetrics;
96
+ exports.getSafeIconCell = getSafeIconCell;
@@ -6,19 +6,24 @@ import { TextStyle } from "react-native";
6
6
  type ButtonTheme = {
7
7
  components: Record<string, any>;
8
8
  };
9
- /** Control height from vertical padding plus max(icon size, text line-height), matching web. */
9
+ /** Safe line box for the icon font's em square plus any bbox overflow. */
10
+ declare function getSafeIconCell(fontSize: number): number;
11
+ /** Safe native content metrics while retaining the token-derived target control height. */
10
12
  declare function getButtonControlMetrics(theme: ButtonTheme, size: ButtonSize): {
11
- controlHeight: number;
12
- contentLineHeight: number;
13
- iconSize: number;
13
+ minHeight: number;
14
+ labelLineHeight: number;
15
+ iconCellHeight: number;
16
+ paddingVertical: number;
14
17
  };
15
- /** IconButton uses explicit control height only when CSS sets height (match-button-height opt-in). */
18
+ /** Safe icon cell within the token-derived control size; explicit token height (match-button-height opt-in) wins as the target. */
16
19
  declare function getIconButtonControlMetrics(theme: ButtonTheme, size: IconButtonSize): {
17
- controlHeight: number;
18
- iconSize: number;
20
+ minHeight: number;
21
+ iconCellHeight: number;
22
+ paddingVertical: number;
23
+ hasExplicitHeight: boolean;
19
24
  };
20
- /** Label line-height matches content box (max of icon size and line-height). */
21
- declare function getButtonTextStyleForControlHeight(textStyle: TextStyle, contentLineHeight: number): TextStyle;
25
+ /** Apply the metric-safe line height after the token typography styles. */
26
+ declare function getButtonTextStyle(textStyle: TextStyle, labelLineHeight: number): TextStyle;
22
27
  //#endregion
23
- export { getButtonControlMetrics, getButtonTextStyleForControlHeight, getIconButtonControlMetrics };
28
+ export { getButtonControlMetrics, getButtonTextStyle, getIconButtonControlMetrics, getSafeIconCell };
24
29
  //# sourceMappingURL=buttonTheme.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"buttonTheme.d.cts","names":[],"sources":["../../../src/components/Button/buttonTheme.ts"],"mappings":";;;;;KAGK,WAAA;EAEH,UAAA,EAAY,MAAA;AAAA;;iBAyCE,uBAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,UAAA;EACH,aAAA;EAAuB,iBAAA;EAA2B,QAAA;AAAA;;iBAgBvC,2BAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,cAAA;EACH,aAAA;EAAuB,QAAA;AAAA;;iBAcZ,kCAAA,CACd,SAAA,EAAW,SAAA,EACX,iBAAA,WACC,SAAA"}
1
+ {"version":3,"file":"buttonTheme.d.cts","names":[],"sources":["../../../src/components/Button/buttonTheme.ts"],"mappings":";;;;;KAKK,WAAA;EAEH,UAAA,EAAY,MAAA;AAAA;;iBA4DE,eAAA,CAAgB,QAAA;;iBAiBhB,uBAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,UAAA;EAEN,SAAA;EACA,eAAA;EACA,cAAA;EACA,eAAA;AAAA;AAPF;AAAA,iBAgCgB,2BAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,cAAA;EAEN,SAAA;EACA,cAAA;EACA,eAAA;EACA,iBAAA;AAAA;;iBAyBc,kBAAA,CAAmB,SAAA,EAAW,SAAA,EAAW,eAAA,WAA0B,SAAA"}
@@ -6,19 +6,24 @@ import { TextStyle } from "react-native";
6
6
  type ButtonTheme = {
7
7
  components: Record<string, any>;
8
8
  };
9
- /** Control height from vertical padding plus max(icon size, text line-height), matching web. */
9
+ /** Safe line box for the icon font's em square plus any bbox overflow. */
10
+ declare function getSafeIconCell(fontSize: number): number;
11
+ /** Safe native content metrics while retaining the token-derived target control height. */
10
12
  declare function getButtonControlMetrics(theme: ButtonTheme, size: ButtonSize): {
11
- controlHeight: number;
12
- contentLineHeight: number;
13
- iconSize: number;
13
+ minHeight: number;
14
+ labelLineHeight: number;
15
+ iconCellHeight: number;
16
+ paddingVertical: number;
14
17
  };
15
- /** IconButton uses explicit control height only when CSS sets height (match-button-height opt-in). */
18
+ /** Safe icon cell within the token-derived control size; explicit token height (match-button-height opt-in) wins as the target. */
16
19
  declare function getIconButtonControlMetrics(theme: ButtonTheme, size: IconButtonSize): {
17
- controlHeight: number;
18
- iconSize: number;
20
+ minHeight: number;
21
+ iconCellHeight: number;
22
+ paddingVertical: number;
23
+ hasExplicitHeight: boolean;
19
24
  };
20
- /** Label line-height matches content box (max of icon size and line-height). */
21
- declare function getButtonTextStyleForControlHeight(textStyle: TextStyle, contentLineHeight: number): TextStyle;
25
+ /** Apply the metric-safe line height after the token typography styles. */
26
+ declare function getButtonTextStyle(textStyle: TextStyle, labelLineHeight: number): TextStyle;
22
27
  //#endregion
23
- export { getButtonControlMetrics, getButtonTextStyleForControlHeight, getIconButtonControlMetrics };
28
+ export { getButtonControlMetrics, getButtonTextStyle, getIconButtonControlMetrics, getSafeIconCell };
24
29
  //# sourceMappingURL=buttonTheme.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"buttonTheme.d.ts","names":[],"sources":["../../../src/components/Button/buttonTheme.ts"],"mappings":";;;;;KAGK,WAAA;EAEH,UAAA,EAAY,MAAA;AAAA;;iBAyCE,uBAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,UAAA;EACH,aAAA;EAAuB,iBAAA;EAA2B,QAAA;AAAA;;iBAgBvC,2BAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,cAAA;EACH,aAAA;EAAuB,QAAA;AAAA;;iBAcZ,kCAAA,CACd,SAAA,EAAW,SAAA,EACX,iBAAA,WACC,SAAA"}
1
+ {"version":3,"file":"buttonTheme.d.ts","names":[],"sources":["../../../src/components/Button/buttonTheme.ts"],"mappings":";;;;;KAKK,WAAA;EAEH,UAAA,EAAY,MAAA;AAAA;;iBA4DE,eAAA,CAAgB,QAAA;;iBAiBhB,uBAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,UAAA;EAEN,SAAA;EACA,eAAA;EACA,cAAA;EACA,eAAA;AAAA;AAPF;AAAA,iBAgCgB,2BAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,cAAA;EAEN,SAAA;EACA,cAAA;EACA,eAAA;EACA,iBAAA;AAAA;;iBAyBc,kBAAA,CAAmB,SAAA,EAAW,SAAA,EAAW,eAAA,WAA0B,SAAA"}
@@ -1,4 +1,5 @@
1
1
  /*! © 2026 Yahoo, Inc. UDS Mobile v0.0.0-development */
2
+ import { fontMetrics } from "../../mobile/generated/fontMetrics.js";
2
3
  //#region src/components/Button/buttonTheme.ts
3
4
  const SHARED_BUTTON_ICONBUTTON_SIZES = new Set([
4
5
  "xs",
@@ -6,6 +7,7 @@ const SHARED_BUTTON_ICONBUTTON_SIZES = new Set([
6
7
  "md",
7
8
  "lg"
8
9
  ]);
10
+ const UNKNOWN_FONT_LINE_HEIGHT_RATIO = 1.3;
9
11
  function buttonSizePath(size, layer) {
10
12
  return `button/size/${size}/${layer}/rest`;
11
13
  }
@@ -22,47 +24,71 @@ function getIconSize(iconStyle) {
22
24
  function getVerticalPadding(rootStyle) {
23
25
  return (typeof rootStyle.paddingVertical === "number" ? rootStyle.paddingVertical : void 0) ?? (typeof rootStyle.padding === "number" ? rootStyle.padding : void 0) ?? 0;
24
26
  }
25
- function getButtonContentSize(iconSize, textStyle) {
26
- const lineHeight = typeof textStyle.lineHeight === "number" ? textStyle.lineHeight : void 0;
27
- const fontSize = typeof textStyle.fontSize === "number" ? textStyle.fontSize : iconSize;
28
- return Math.max(iconSize, lineHeight ?? fontSize);
27
+ function getMetricHeight(value, unitsPerEm, fontSize) {
28
+ return Math.ceil(Math.max(0, value) / unitsPerEm * fontSize);
29
+ }
30
+ function getSafeLabelLineHeight(textStyle, fallbackFontSize) {
31
+ const fontSize = typeof textStyle.fontSize === "number" ? textStyle.fontSize : fallbackFontSize;
32
+ const tokenLineHeight = typeof textStyle.lineHeight === "number" ? textStyle.lineHeight : fontSize;
33
+ const metrics = typeof textStyle.fontFamily === "string" ? fontMetrics[textStyle.fontFamily] : void 0;
34
+ if (!metrics || metrics.unitsPerEm <= 0) return Math.max(tokenLineHeight, Math.ceil(fontSize * UNKNOWN_FONT_LINE_HEIGHT_RATIO));
35
+ const metricsLineHeight = getMetricHeight(metrics.ascent, metrics.unitsPerEm, fontSize) + getMetricHeight(-metrics.descent, metrics.unitsPerEm, fontSize) + getMetricHeight(metrics.lineGap, metrics.unitsPerEm, fontSize);
36
+ return Math.max(tokenLineHeight, metricsLineHeight);
29
37
  }
30
- /** Control height from vertical padding plus max(icon size, text line-height), matching web. */
38
+ /** Safe line box for the icon font's em square plus any bbox overflow. */
39
+ function getSafeIconCell(fontSize) {
40
+ const metrics = fontMetrics["uds-icons"];
41
+ if (!metrics || metrics.unitsPerEm <= 0) return Math.ceil(fontSize * UNKNOWN_FONT_LINE_HEIGHT_RATIO);
42
+ const overflowAboveEm = Math.max(0, metrics.bbox.maxY - metrics.unitsPerEm);
43
+ const overflowBelowEm = Math.max(0, -metrics.bbox.minY);
44
+ return Math.ceil(fontSize) + getMetricHeight(overflowAboveEm, metrics.unitsPerEm, fontSize) + getMetricHeight(overflowBelowEm, metrics.unitsPerEm, fontSize);
45
+ }
46
+ /** Safe native content metrics while retaining the token-derived target control height. */
31
47
  function getButtonControlMetrics(theme, size) {
32
48
  const rootStyle = getLayerStyle(theme, buttonSizePath(size, "root"));
33
49
  const iconStyle = getLayerStyle(theme, buttonSizePath(size, "icon"));
34
50
  const textStyle = getLayerStyle(theme, buttonSizePath(size, "rootText"));
35
51
  const paddingVertical = getVerticalPadding(rootStyle);
36
52
  const iconSize = getIconSize(iconStyle);
37
- const contentSize = getButtonContentSize(iconSize, textStyle);
53
+ const fontSize = typeof textStyle.fontSize === "number" ? textStyle.fontSize : iconSize;
54
+ const tokenLineHeight = typeof textStyle.lineHeight === "number" ? textStyle.lineHeight : fontSize;
55
+ const targetContentHeight = Math.max(iconSize, tokenLineHeight);
56
+ const minHeight = Math.ceil(paddingVertical * 2 + targetContentHeight);
57
+ const labelLineHeight = getSafeLabelLineHeight(textStyle, iconSize);
58
+ const iconCellHeight = getSafeIconCell(iconSize);
38
59
  return {
39
- controlHeight: Math.ceil(paddingVertical * 2 + contentSize),
40
- contentLineHeight: Math.ceil(contentSize),
41
- iconSize
60
+ minHeight,
61
+ labelLineHeight,
62
+ iconCellHeight,
63
+ paddingVertical: Math.max(0, (minHeight - Math.max(labelLineHeight, iconCellHeight)) / 2)
42
64
  };
43
65
  }
44
- /** IconButton uses explicit control height only when CSS sets height (match-button-height opt-in). */
66
+ /** Safe icon cell within the token-derived control size; explicit token height (match-button-height opt-in) wins as the target. */
45
67
  function getIconButtonControlMetrics(theme, size) {
46
68
  const rootStyle = getLayerStyle(theme, iconButtonSizePath(size, "root"));
47
- const iconSize = getIconSize(getLayerStyle(theme, iconButtonSizePath(size, "icon")));
69
+ const iconStyle = getLayerStyle(theme, iconButtonSizePath(size, "icon"));
70
+ const iconSize = getIconSize(iconStyle);
71
+ const tokenLineHeight = typeof iconStyle.lineHeight === "number" ? iconStyle.lineHeight : iconSize;
72
+ const paddingVertical = getVerticalPadding(rootStyle);
48
73
  const explicitHeight = typeof rootStyle.height === "number" ? rootStyle.height : void 0;
49
- if (SHARED_BUTTON_ICONBUTTON_SIZES.has(size) && explicitHeight !== void 0) return {
50
- controlHeight: explicitHeight,
51
- iconSize
52
- };
74
+ const hasExplicitHeight = SHARED_BUTTON_ICONBUTTON_SIZES.has(size) && explicitHeight !== void 0;
75
+ const minHeight = hasExplicitHeight ? explicitHeight : Math.ceil(paddingVertical * 2 + Math.max(iconSize, tokenLineHeight));
76
+ const iconCellHeight = getSafeIconCell(iconSize);
53
77
  return {
54
- controlHeight: 0,
55
- iconSize
78
+ minHeight,
79
+ iconCellHeight,
80
+ paddingVertical: Math.max(0, (minHeight - Math.max(iconSize, iconCellHeight)) / 2),
81
+ hasExplicitHeight
56
82
  };
57
83
  }
58
- /** Label line-height matches content box (max of icon size and line-height). */
59
- function getButtonTextStyleForControlHeight(textStyle, contentLineHeight) {
84
+ /** Apply the metric-safe line height after the token typography styles. */
85
+ function getButtonTextStyle(textStyle, labelLineHeight) {
60
86
  return {
61
87
  ...textStyle,
62
- lineHeight: contentLineHeight
88
+ lineHeight: labelLineHeight
63
89
  };
64
90
  }
65
91
  //#endregion
66
- export { getButtonControlMetrics, getButtonTextStyleForControlHeight, getIconButtonControlMetrics };
92
+ export { getButtonControlMetrics, getButtonTextStyle, getIconButtonControlMetrics, getSafeIconCell };
67
93
 
68
94
  //# sourceMappingURL=buttonTheme.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"buttonTheme.js","names":[],"sources":["../../../src/components/Button/buttonTheme.ts"],"sourcesContent":["import type { ButtonSize, IconButtonSize } from '@yahoo/uds-types';\nimport type { TextStyle, ViewStyle } from 'react-native';\n\ntype ButtonTheme = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n components: Record<string, any>;\n};\n\nconst SHARED_BUTTON_ICONBUTTON_SIZES = new Set<IconButtonSize>(['xs', 'sm', 'md', 'lg']);\n\nfunction buttonSizePath(size: ButtonSize, layer: 'root' | 'icon' | 'rootText'): string {\n return `button/size/${size}/${layer}/rest`;\n}\n\nfunction iconButtonSizePath(size: IconButtonSize, layer: 'root' | 'icon'): string {\n return `iconButton/size/${size}/${layer}/rest`;\n}\n\nfunction getLayerStyle(theme: ButtonTheme, path: string): ViewStyle | TextStyle {\n const style = theme.components[path];\n return style && typeof style === 'object' ? style : {};\n}\n\nfunction getIconSize(iconStyle: TextStyle): number {\n return (\n (typeof iconStyle.fontSize === 'number' ? iconStyle.fontSize : undefined) ??\n (typeof iconStyle.lineHeight === 'number' ? iconStyle.lineHeight : undefined) ??\n 16\n );\n}\n\nfunction getVerticalPadding(rootStyle: ViewStyle): number {\n return (\n (typeof rootStyle.paddingVertical === 'number' ? rootStyle.paddingVertical : undefined) ??\n (typeof rootStyle.padding === 'number' ? rootStyle.padding : undefined) ??\n 0\n );\n}\n\nfunction getButtonContentSize(iconSize: number, textStyle: TextStyle): number {\n const lineHeight = typeof textStyle.lineHeight === 'number' ? textStyle.lineHeight : undefined;\n const fontSize = typeof textStyle.fontSize === 'number' ? textStyle.fontSize : iconSize;\n return Math.max(iconSize, lineHeight ?? fontSize);\n}\n\n/** Control height from vertical padding plus max(icon size, text line-height), matching web. */\nexport function getButtonControlMetrics(\n theme: ButtonTheme,\n size: ButtonSize,\n): { controlHeight: number; contentLineHeight: number; iconSize: number } {\n const rootStyle = getLayerStyle(theme, buttonSizePath(size, 'root')) as ViewStyle;\n const iconStyle = getLayerStyle(theme, buttonSizePath(size, 'icon')) as TextStyle;\n const textStyle = getLayerStyle(theme, buttonSizePath(size, 'rootText')) as TextStyle;\n const paddingVertical = getVerticalPadding(rootStyle);\n const iconSize = getIconSize(iconStyle);\n const contentSize = getButtonContentSize(iconSize, textStyle);\n\n return {\n controlHeight: Math.ceil(paddingVertical * 2 + contentSize),\n contentLineHeight: Math.ceil(contentSize),\n iconSize,\n };\n}\n\n/** IconButton uses explicit control height only when CSS sets height (match-button-height opt-in). */\nexport function getIconButtonControlMetrics(\n theme: ButtonTheme,\n size: IconButtonSize,\n): { controlHeight: number; iconSize: number } {\n const rootStyle = getLayerStyle(theme, iconButtonSizePath(size, 'root')) as ViewStyle;\n const iconStyle = getLayerStyle(theme, iconButtonSizePath(size, 'icon')) as TextStyle;\n const iconSize = getIconSize(iconStyle);\n const explicitHeight = typeof rootStyle.height === 'number' ? rootStyle.height : undefined;\n\n if (SHARED_BUTTON_ICONBUTTON_SIZES.has(size) && explicitHeight !== undefined) {\n return { controlHeight: explicitHeight, iconSize };\n }\n\n return { controlHeight: 0, iconSize };\n}\n\n/** Label line-height matches content box (max of icon size and line-height). */\nexport function getButtonTextStyleForControlHeight(\n textStyle: TextStyle,\n contentLineHeight: number,\n): TextStyle {\n return {\n ...textStyle,\n lineHeight: contentLineHeight,\n };\n}\n"],"mappings":";;AAQA,MAAM,iCAAiC,IAAI,IAAoB;CAAC;CAAM;CAAM;CAAM;CAAK,CAAC;AAExF,SAAS,eAAe,MAAkB,OAA6C;CACrF,OAAO,eAAe,KAAK,GAAG,MAAM;;AAGtC,SAAS,mBAAmB,MAAsB,OAAgC;CAChF,OAAO,mBAAmB,KAAK,GAAG,MAAM;;AAG1C,SAAS,cAAc,OAAoB,MAAqC;CAC9E,MAAM,QAAQ,MAAM,WAAW;CAC/B,OAAO,SAAS,OAAO,UAAU,WAAW,QAAQ,EAAE;;AAGxD,SAAS,YAAY,WAA8B;CACjD,QACG,OAAO,UAAU,aAAa,WAAW,UAAU,WAAW,KAAA,OAC9D,OAAO,UAAU,eAAe,WAAW,UAAU,aAAa,KAAA,MACnE;;AAIJ,SAAS,mBAAmB,WAA8B;CACxD,QACG,OAAO,UAAU,oBAAoB,WAAW,UAAU,kBAAkB,KAAA,OAC5E,OAAO,UAAU,YAAY,WAAW,UAAU,UAAU,KAAA,MAC7D;;AAIJ,SAAS,qBAAqB,UAAkB,WAA8B;CAC5E,MAAM,aAAa,OAAO,UAAU,eAAe,WAAW,UAAU,aAAa,KAAA;CACrF,MAAM,WAAW,OAAO,UAAU,aAAa,WAAW,UAAU,WAAW;CAC/E,OAAO,KAAK,IAAI,UAAU,cAAc,SAAS;;;AAInD,SAAgB,wBACd,OACA,MACwE;CACxE,MAAM,YAAY,cAAc,OAAO,eAAe,MAAM,OAAO,CAAC;CACpE,MAAM,YAAY,cAAc,OAAO,eAAe,MAAM,OAAO,CAAC;CACpE,MAAM,YAAY,cAAc,OAAO,eAAe,MAAM,WAAW,CAAC;CACxE,MAAM,kBAAkB,mBAAmB,UAAU;CACrD,MAAM,WAAW,YAAY,UAAU;CACvC,MAAM,cAAc,qBAAqB,UAAU,UAAU;CAE7D,OAAO;EACL,eAAe,KAAK,KAAK,kBAAkB,IAAI,YAAY;EAC3D,mBAAmB,KAAK,KAAK,YAAY;EACzC;EACD;;;AAIH,SAAgB,4BACd,OACA,MAC6C;CAC7C,MAAM,YAAY,cAAc,OAAO,mBAAmB,MAAM,OAAO,CAAC;CAExE,MAAM,WAAW,YADC,cAAc,OAAO,mBAAmB,MAAM,OAAO,CACjC,CAAC;CACvC,MAAM,iBAAiB,OAAO,UAAU,WAAW,WAAW,UAAU,SAAS,KAAA;CAEjF,IAAI,+BAA+B,IAAI,KAAK,IAAI,mBAAmB,KAAA,GACjE,OAAO;EAAE,eAAe;EAAgB;EAAU;CAGpD,OAAO;EAAE,eAAe;EAAG;EAAU;;;AAIvC,SAAgB,mCACd,WACA,mBACW;CACX,OAAO;EACL,GAAG;EACH,YAAY;EACb"}
1
+ {"version":3,"file":"buttonTheme.js","names":[],"sources":["../../../src/components/Button/buttonTheme.ts"],"sourcesContent":["import type { ButtonSize, IconButtonSize } from '@yahoo/uds-types';\nimport type { TextStyle, ViewStyle } from 'react-native';\n\nimport { fontMetrics } from '../../../generated/fontMetrics';\n\ntype ButtonTheme = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n components: Record<string, any>;\n};\n\nconst SHARED_BUTTON_ICONBUTTON_SIZES = new Set<IconButtonSize>(['xs', 'sm', 'md', 'lg']);\n// Covers common native font boxes (roughly 1.2em) plus pixel-rounding headroom.\nconst UNKNOWN_FONT_LINE_HEIGHT_RATIO = 1.3;\n\nfunction buttonSizePath(size: ButtonSize, layer: 'root' | 'icon' | 'rootText'): string {\n return `button/size/${size}/${layer}/rest`;\n}\n\nfunction iconButtonSizePath(size: IconButtonSize, layer: 'root' | 'icon'): string {\n return `iconButton/size/${size}/${layer}/rest`;\n}\n\nfunction getLayerStyle(theme: ButtonTheme, path: string): ViewStyle | TextStyle {\n const style = theme.components[path];\n return style && typeof style === 'object' ? style : {};\n}\n\nfunction getIconSize(iconStyle: TextStyle): number {\n return (\n (typeof iconStyle.fontSize === 'number' ? iconStyle.fontSize : undefined) ??\n (typeof iconStyle.lineHeight === 'number' ? iconStyle.lineHeight : undefined) ??\n 16\n );\n}\n\nfunction getVerticalPadding(rootStyle: ViewStyle): number {\n return (\n (typeof rootStyle.paddingVertical === 'number' ? rootStyle.paddingVertical : undefined) ??\n (typeof rootStyle.padding === 'number' ? rootStyle.padding : undefined) ??\n 0\n );\n}\n\nfunction getMetricHeight(value: number, unitsPerEm: number, fontSize: number): number {\n return Math.ceil((Math.max(0, value) / unitsPerEm) * fontSize);\n}\n\nfunction getSafeLabelLineHeight(textStyle: TextStyle, fallbackFontSize: number): number {\n const fontSize = typeof textStyle.fontSize === 'number' ? textStyle.fontSize : fallbackFontSize;\n const tokenLineHeight =\n typeof textStyle.lineHeight === 'number' ? textStyle.lineHeight : fontSize;\n const metrics =\n typeof textStyle.fontFamily === 'string' ? fontMetrics[textStyle.fontFamily] : undefined;\n\n if (!metrics || metrics.unitsPerEm <= 0) {\n return Math.max(tokenLineHeight, Math.ceil(fontSize * UNKNOWN_FONT_LINE_HEIGHT_RATIO));\n }\n\n const metricsLineHeight =\n getMetricHeight(metrics.ascent, metrics.unitsPerEm, fontSize) +\n getMetricHeight(-metrics.descent, metrics.unitsPerEm, fontSize) +\n getMetricHeight(metrics.lineGap, metrics.unitsPerEm, fontSize);\n\n return Math.max(tokenLineHeight, metricsLineHeight);\n}\n\n/** Safe line box for the icon font's em square plus any bbox overflow. */\nexport function getSafeIconCell(fontSize: number): number {\n const metrics = fontMetrics['uds-icons'];\n if (!metrics || metrics.unitsPerEm <= 0) {\n return Math.ceil(fontSize * UNKNOWN_FONT_LINE_HEIGHT_RATIO);\n }\n\n const overflowAboveEm = Math.max(0, metrics.bbox.maxY - metrics.unitsPerEm);\n const overflowBelowEm = Math.max(0, -metrics.bbox.minY);\n\n return (\n Math.ceil(fontSize) +\n getMetricHeight(overflowAboveEm, metrics.unitsPerEm, fontSize) +\n getMetricHeight(overflowBelowEm, metrics.unitsPerEm, fontSize)\n );\n}\n\n/** Safe native content metrics while retaining the token-derived target control height. */\nexport function getButtonControlMetrics(\n theme: ButtonTheme,\n size: ButtonSize,\n): {\n minHeight: number;\n labelLineHeight: number;\n iconCellHeight: number;\n paddingVertical: number;\n} {\n const rootStyle = getLayerStyle(theme, buttonSizePath(size, 'root')) as ViewStyle;\n const iconStyle = getLayerStyle(theme, buttonSizePath(size, 'icon')) as TextStyle;\n const textStyle = getLayerStyle(theme, buttonSizePath(size, 'rootText')) as TextStyle;\n const paddingVertical = getVerticalPadding(rootStyle);\n const iconSize = getIconSize(iconStyle);\n const fontSize = typeof textStyle.fontSize === 'number' ? textStyle.fontSize : iconSize;\n const tokenLineHeight =\n typeof textStyle.lineHeight === 'number' ? textStyle.lineHeight : fontSize;\n const targetContentHeight = Math.max(iconSize, tokenLineHeight);\n const minHeight = Math.ceil(paddingVertical * 2 + targetContentHeight);\n const labelLineHeight = getSafeLabelLineHeight(textStyle, iconSize);\n const iconCellHeight = getSafeIconCell(iconSize);\n const safeContentHeight = Math.max(labelLineHeight, iconCellHeight);\n\n return {\n minHeight,\n labelLineHeight,\n iconCellHeight,\n paddingVertical: Math.max(0, (minHeight - safeContentHeight) / 2),\n };\n}\n\n/** Safe icon cell within the token-derived control size; explicit token height (match-button-height opt-in) wins as the target. */\nexport function getIconButtonControlMetrics(\n theme: ButtonTheme,\n size: IconButtonSize,\n): {\n minHeight: number;\n iconCellHeight: number;\n paddingVertical: number;\n hasExplicitHeight: boolean;\n} {\n const rootStyle = getLayerStyle(theme, iconButtonSizePath(size, 'root')) as ViewStyle;\n const iconStyle = getLayerStyle(theme, iconButtonSizePath(size, 'icon')) as TextStyle;\n const iconSize = getIconSize(iconStyle);\n const tokenLineHeight =\n typeof iconStyle.lineHeight === 'number' ? iconStyle.lineHeight : iconSize;\n const paddingVertical = getVerticalPadding(rootStyle);\n const explicitHeight = typeof rootStyle.height === 'number' ? rootStyle.height : undefined;\n const hasExplicitHeight =\n SHARED_BUTTON_ICONBUTTON_SIZES.has(size) && explicitHeight !== undefined;\n const minHeight = hasExplicitHeight\n ? (explicitHeight as number)\n : Math.ceil(paddingVertical * 2 + Math.max(iconSize, tokenLineHeight));\n const iconCellHeight = getSafeIconCell(iconSize);\n\n return {\n minHeight,\n iconCellHeight,\n paddingVertical: Math.max(0, (minHeight - Math.max(iconSize, iconCellHeight)) / 2),\n hasExplicitHeight,\n };\n}\n\n/** Apply the metric-safe line height after the token typography styles. */\nexport function getButtonTextStyle(textStyle: TextStyle, labelLineHeight: number): TextStyle {\n return {\n ...textStyle,\n lineHeight: labelLineHeight,\n };\n}\n"],"mappings":"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"}
@@ -3,9 +3,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
4
4
  const require_index = require("../motion-tokens/dist/index.cjs");
5
5
  const require_motion = require("../motion.cjs");
6
+ const require_components_Button_buttonTheme = require("./Button/buttonTheme.cjs");
6
7
  const require_components_IconSlot = require("./IconSlot.cjs");
7
8
  const require_components_Text = require("./Text.cjs");
8
- const require_components_Button_buttonTheme = require("./Button/buttonTheme.cjs");
9
9
  const require_components_Pressable = require("./Pressable.cjs");
10
10
  let react = require("react");
11
11
  let react_native = require("react-native");
@@ -34,7 +34,7 @@ function interpolateShadowAlpha(shadow, alpha) {
34
34
  * Matches web Button icon animation: scale 0.7→1, opacity 0→1, width 0→auto
35
35
  * Uses staggered animation: opacity waits until halfway through width animation.
36
36
  */
37
- function AnimatedIconSlot({ children, visible, iconSize, gap }) {
37
+ function AnimatedIconSlot({ children, visible, iconSize, iconCellHeight, gap }) {
38
38
  const progress = (0, react_native_reanimated.useDerivedValue)(() => (0, react_native_reanimated.withSpring)(visible ? 1 : 0, require_motion.BUTTON_SPRING_CONFIG), [visible]);
39
39
  const animatedStyle = (0, react_native_reanimated.useAnimatedStyle)(() => {
40
40
  const totalWidth = iconSize + gap;
@@ -42,7 +42,9 @@ function AnimatedIconSlot({ children, visible, iconSize, gap }) {
42
42
  width: (0, react_native_reanimated.interpolate)(progress.value, [0, 1], [0, totalWidth]),
43
43
  opacity: (0, react_native_reanimated.interpolate)(progress.value, [.5, 1], [0, 1], "clamp"),
44
44
  transform: [{ scale: (0, react_native_reanimated.interpolate)(progress.value, [.5, 1], [.7, 1], "clamp") }],
45
- overflow: "hidden"
45
+ overflow: "hidden",
46
+ minHeight: iconCellHeight,
47
+ justifyContent: "center"
46
48
  };
47
49
  });
48
50
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native_reanimated.default.View, {
@@ -85,7 +87,7 @@ function AnimatedIconSlot({ children, visible, iconSize, gap }) {
85
87
  const Button = (0, react.memo)(function Button({ variant = "primary", size = "md", iconVariant = "outline", startIcon, endIcon, loading, disabled, width: _width, children, style, accessibilityLabel, accessibilityHint, disableEffects = false, onPressIn, onPressOut, ref, ...props }) {
86
88
  const shouldAnimate = !disableEffects;
87
89
  const { theme } = (0, react_native_unistyles.useUnistyles)();
88
- const { controlHeight, contentLineHeight } = (0, react.useMemo)(() => require_components_Button_buttonTheme.getButtonControlMetrics(theme, size), [theme, size]);
90
+ const { minHeight, labelLineHeight, iconCellHeight, paddingVertical } = (0, react.useMemo)(() => require_components_Button_buttonTheme.getButtonControlMetrics(theme, size), [theme, size]);
89
91
  const [pressed, setPressed] = (0, react.useState)(false);
90
92
  generated_styles.buttonStyles.useVariants({
91
93
  size,
@@ -118,7 +120,7 @@ const Button = (0, react.memo)(function Button({ variant = "primary", size = "md
118
120
  const childrenNode = children && ((0, react.isValidElement)(children) ? children : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_Text.Text, {
119
121
  numberOfLines: 1,
120
122
  textAlign: "center",
121
- style: require_components_Button_buttonTheme.getButtonTextStyleForControlHeight(generated_styles.buttonStyles.text, contentLineHeight),
123
+ style: require_components_Button_buttonTheme.getButtonTextStyle(generated_styles.buttonStyles.text, labelLineHeight),
122
124
  children
123
125
  }));
124
126
  const a11yState = (0, react.useMemo)(() => ({
@@ -143,6 +145,7 @@ const Button = (0, react.memo)(function Button({ variant = "primary", size = "md
143
145
  const startContent = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnimatedIconSlot, {
144
146
  visible: showLoading || showStartIcon,
145
147
  iconSize,
148
+ iconCellHeight,
146
149
  gap: buttonGap,
147
150
  children: showLoading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native.ActivityIndicator, {
148
151
  size: generated_styles.buttonStyles.icon.fontSize,
@@ -151,28 +154,33 @@ const Button = (0, react.memo)(function Button({ variant = "primary", size = "md
151
154
  icon: startIcon,
152
155
  size: iconSizeToken,
153
156
  variant: iconVariant,
154
- style: generated_styles.buttonStyles.icon
157
+ style: [generated_styles.buttonStyles.icon, { lineHeight: iconCellHeight }]
155
158
  })
156
159
  });
157
160
  const endContent = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnimatedIconSlot, {
158
161
  visible: showEndIcon,
159
162
  iconSize,
163
+ iconCellHeight,
160
164
  gap: buttonGap,
161
165
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_IconSlot.IconSlot, {
162
166
  icon: endIcon,
163
167
  size: iconSizeToken,
164
168
  variant: iconVariant,
165
- style: generated_styles.buttonStyles.icon
169
+ style: [generated_styles.buttonStyles.icon, { lineHeight: iconCellHeight }]
166
170
  })
167
171
  });
168
172
  const rootStyles = (0, react.useMemo)(() => [
169
173
  generated_styles.buttonStyles.root,
170
- { height: controlHeight },
174
+ {
175
+ minHeight,
176
+ paddingVertical
177
+ },
171
178
  animatedStyles,
172
179
  typeof style === "function" ? style({ pressed }) : style
173
180
  ], [
174
181
  generated_styles.buttonStyles.root,
175
- controlHeight,
182
+ minHeight,
183
+ paddingVertical,
176
184
  animatedStyles,
177
185
  style,
178
186
  pressed
@@ -1 +1 @@
1
- {"version":3,"file":"Button.d.cts","names":[],"sources":["../../src/components/Button.tsx"],"mappings":";;;;;;;;;UAiHU,WAAA,SAAoB,IAAA,CAAK,gBAAA;;EAEjC,OAAA,GAAU,iBAAA;EAFF;EAIR,IAAA,GAAO,UAAA;;EAEP,WAAA,GAAc,WAAA;EAJJ;EAMV,SAAA,GAAY,YAAA;EAFE;EAId,OAAA,GAAU,YAAA;EAAA;EAEV,OAAA;EAWU;EATV,QAAA;EAd4B;EAgB5B,QAAA,GAAW,KAAA,CAAM,SAAA;EAhBe;;;;EAqBhC,cAAA;EAjBA;EAmBA,GAAA,GAAM,GAAA,CAAI,IAAA;AAAA;;;;;;;;;;;;;;;;;AAAI;;;;;;;;;;;;;;;;cAuCV,MAAA,EAAM,OAAA,CAAA,oBAAA,CAAA,WAAA"}
1
+ {"version":3,"file":"Button.d.cts","names":[],"sources":["../../src/components/Button.tsx"],"mappings":";;;;;;;;;UAqHU,WAAA,SAAoB,IAAA,CAAK,gBAAA;;EAEjC,OAAA,GAAU,iBAAA;EAFF;EAIR,IAAA,GAAO,UAAA;;EAEP,WAAA,GAAc,WAAA;EAJJ;EAMV,SAAA,GAAY,YAAA;EAFE;EAId,OAAA,GAAU,YAAA;EAAA;EAEV,OAAA;EAWU;EATV,QAAA;EAd4B;EAgB5B,QAAA,GAAW,KAAA,CAAM,SAAA;EAhBe;;;;EAqBhC,cAAA;EAjBA;EAmBA,GAAA,GAAM,GAAA,CAAI,IAAA;AAAA;;;;;;;;;;;;;;;;;AAAI;;;;;;;;;;;;;;;;cAuCV,MAAA,EAAM,OAAA,CAAA,oBAAA,CAAA,WAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"Button.d.ts","names":[],"sources":["../../src/components/Button.tsx"],"mappings":";;;;;;;;;UAiHU,WAAA,SAAoB,IAAA,CAAK,gBAAA;;EAEjC,OAAA,GAAU,iBAAA;EAFF;EAIR,IAAA,GAAO,UAAA;;EAEP,WAAA,GAAc,WAAA;EAJJ;EAMV,SAAA,GAAY,YAAA;EAFE;EAId,OAAA,GAAU,YAAA;EAAA;EAEV,OAAA;EAWU;EATV,QAAA;EAd4B;EAgB5B,QAAA,GAAW,KAAA,CAAM,SAAA;EAhBe;;;;EAqBhC,cAAA;EAjBA;EAmBA,GAAA,GAAM,GAAA,CAAI,IAAA;AAAA;;;;;;;;;;;;;;;;;AAAI;;;;;;;;;;;;;;;;cAuCV,MAAA,EAAM,OAAA,CAAA,oBAAA,CAAA,WAAA"}
1
+ {"version":3,"file":"Button.d.ts","names":[],"sources":["../../src/components/Button.tsx"],"mappings":";;;;;;;;;UAqHU,WAAA,SAAoB,IAAA,CAAK,gBAAA;;EAEjC,OAAA,GAAU,iBAAA;EAFF;EAIR,IAAA,GAAO,UAAA;;EAEP,WAAA,GAAc,WAAA;EAJJ;EAMV,SAAA,GAAY,YAAA;EAFE;EAId,OAAA,GAAU,YAAA;EAAA;EAEV,OAAA;EAWU;EATV,QAAA;EAd4B;EAgB5B,QAAA,GAAW,KAAA,CAAM,SAAA;EAhBe;;;;EAqBhC,cAAA;EAjBA;EAmBA,GAAA,GAAM,GAAA,CAAI,IAAA;AAAA;;;;;;;;;;;;;;;;;AAAI;;;;;;;;;;;;;;;;cAuCV,MAAA,EAAM,OAAA,CAAA,oBAAA,CAAA,WAAA"}
@@ -1,9 +1,9 @@
1
1
  /*! © 2026 Yahoo, Inc. UDS Mobile v0.0.0-development */
2
2
  import { SCALE_EFFECTS } from "../motion-tokens/dist/index.js";
3
3
  import { BUTTON_SPRING_CONFIG } from "../motion.js";
4
+ import { getButtonControlMetrics, getButtonTextStyle } from "./Button/buttonTheme.js";
4
5
  import { IconSlot } from "./IconSlot.js";
5
6
  import { Text as Text$1 } from "./Text.js";
6
- import { getButtonControlMetrics, getButtonTextStyleForControlHeight } from "./Button/buttonTheme.js";
7
7
  import { AnimatedPressable } from "./Pressable.js";
8
8
  import { isValidElement, memo, useCallback, useMemo, useState } from "react";
9
9
  import { ActivityIndicator } from "react-native";
@@ -31,7 +31,7 @@ function interpolateShadowAlpha(shadow, alpha) {
31
31
  * Matches web Button icon animation: scale 0.7→1, opacity 0→1, width 0→auto
32
32
  * Uses staggered animation: opacity waits until halfway through width animation.
33
33
  */
34
- function AnimatedIconSlot({ children, visible, iconSize, gap }) {
34
+ function AnimatedIconSlot({ children, visible, iconSize, iconCellHeight, gap }) {
35
35
  const progress = useDerivedValue(() => withSpring(visible ? 1 : 0, BUTTON_SPRING_CONFIG), [visible]);
36
36
  const animatedStyle = useAnimatedStyle(() => {
37
37
  const totalWidth = iconSize + gap;
@@ -39,7 +39,9 @@ function AnimatedIconSlot({ children, visible, iconSize, gap }) {
39
39
  width: interpolate(progress.value, [0, 1], [0, totalWidth]),
40
40
  opacity: interpolate(progress.value, [.5, 1], [0, 1], "clamp"),
41
41
  transform: [{ scale: interpolate(progress.value, [.5, 1], [.7, 1], "clamp") }],
42
- overflow: "hidden"
42
+ overflow: "hidden",
43
+ minHeight: iconCellHeight,
44
+ justifyContent: "center"
43
45
  };
44
46
  });
45
47
  return /* @__PURE__ */ jsx(Animated.View, {
@@ -82,7 +84,7 @@ function AnimatedIconSlot({ children, visible, iconSize, gap }) {
82
84
  const Button = memo(function Button({ variant = "primary", size = "md", iconVariant = "outline", startIcon, endIcon, loading, disabled, width: _width, children, style, accessibilityLabel, accessibilityHint, disableEffects = false, onPressIn, onPressOut, ref, ...props }) {
83
85
  const shouldAnimate = !disableEffects;
84
86
  const { theme } = useUnistyles();
85
- const { controlHeight, contentLineHeight } = useMemo(() => getButtonControlMetrics(theme, size), [theme, size]);
87
+ const { minHeight, labelLineHeight, iconCellHeight, paddingVertical } = useMemo(() => getButtonControlMetrics(theme, size), [theme, size]);
86
88
  const [pressed, setPressed] = useState(false);
87
89
  buttonStyles.useVariants({
88
90
  size,
@@ -115,7 +117,7 @@ const Button = memo(function Button({ variant = "primary", size = "md", iconVari
115
117
  const childrenNode = children && (isValidElement(children) ? children : /* @__PURE__ */ jsx(Text$1, {
116
118
  numberOfLines: 1,
117
119
  textAlign: "center",
118
- style: getButtonTextStyleForControlHeight(buttonStyles.text, contentLineHeight),
120
+ style: getButtonTextStyle(buttonStyles.text, labelLineHeight),
119
121
  children
120
122
  }));
121
123
  const a11yState = useMemo(() => ({
@@ -140,6 +142,7 @@ const Button = memo(function Button({ variant = "primary", size = "md", iconVari
140
142
  const startContent = /* @__PURE__ */ jsx(AnimatedIconSlot, {
141
143
  visible: showLoading || showStartIcon,
142
144
  iconSize,
145
+ iconCellHeight,
143
146
  gap: buttonGap,
144
147
  children: showLoading ? /* @__PURE__ */ jsx(ActivityIndicator, {
145
148
  size: buttonStyles.icon.fontSize,
@@ -148,28 +151,33 @@ const Button = memo(function Button({ variant = "primary", size = "md", iconVari
148
151
  icon: startIcon,
149
152
  size: iconSizeToken,
150
153
  variant: iconVariant,
151
- style: buttonStyles.icon
154
+ style: [buttonStyles.icon, { lineHeight: iconCellHeight }]
152
155
  })
153
156
  });
154
157
  const endContent = /* @__PURE__ */ jsx(AnimatedIconSlot, {
155
158
  visible: showEndIcon,
156
159
  iconSize,
160
+ iconCellHeight,
157
161
  gap: buttonGap,
158
162
  children: /* @__PURE__ */ jsx(IconSlot, {
159
163
  icon: endIcon,
160
164
  size: iconSizeToken,
161
165
  variant: iconVariant,
162
- style: buttonStyles.icon
166
+ style: [buttonStyles.icon, { lineHeight: iconCellHeight }]
163
167
  })
164
168
  });
165
169
  const rootStyles = useMemo(() => [
166
170
  buttonStyles.root,
167
- { height: controlHeight },
171
+ {
172
+ minHeight,
173
+ paddingVertical
174
+ },
168
175
  animatedStyles,
169
176
  typeof style === "function" ? style({ pressed }) : style
170
177
  ], [
171
178
  buttonStyles.root,
172
- controlHeight,
179
+ minHeight,
180
+ paddingVertical,
173
181
  animatedStyles,
174
182
  style,
175
183
  pressed