@yahoo/uds-mobile 2.23.2 → 2.24.0-beta.1

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 (165) hide show
  1. package/README.md +4 -12
  2. package/dist/bin/automated-config/dist/generated/generatedConfigs.mjs +92 -46
  3. package/dist/components/Button/buttonTheme.cjs +63 -23
  4. package/dist/components/Button/buttonTheme.d.cts +29 -12
  5. package/dist/components/Button/buttonTheme.d.cts.map +1 -1
  6. package/dist/components/Button/buttonTheme.d.ts +29 -12
  7. package/dist/components/Button/buttonTheme.d.ts.map +1 -1
  8. package/dist/components/Button/buttonTheme.js +62 -23
  9. package/dist/components/Button/buttonTheme.js.map +1 -1
  10. package/dist/components/Button.cjs +34 -13
  11. package/dist/components/Button.d.cts +7 -0
  12. package/dist/components/Button.d.cts.map +1 -1
  13. package/dist/components/Button.d.ts +7 -0
  14. package/dist/components/Button.d.ts.map +1 -1
  15. package/dist/components/Button.js +34 -13
  16. package/dist/components/Button.js.map +1 -1
  17. package/dist/components/Checkbox.cjs +30 -7
  18. package/dist/components/Checkbox.d.cts +7 -0
  19. package/dist/components/Checkbox.d.cts.map +1 -1
  20. package/dist/components/Checkbox.d.ts +7 -0
  21. package/dist/components/Checkbox.d.ts.map +1 -1
  22. package/dist/components/Checkbox.js +30 -7
  23. package/dist/components/Checkbox.js.map +1 -1
  24. package/dist/components/Chip/ChipBase.cjs +23 -9
  25. package/dist/components/Chip/ChipBase.d.cts.map +1 -1
  26. package/dist/components/Chip/ChipBase.d.ts.map +1 -1
  27. package/dist/components/Chip/ChipBase.js +23 -9
  28. package/dist/components/Chip/ChipBase.js.map +1 -1
  29. package/dist/components/Chip/ChipDismissible.cjs +3 -1
  30. package/dist/components/Chip/ChipDismissible.js +3 -1
  31. package/dist/components/Chip/ChipDismissible.js.map +1 -1
  32. package/dist/components/Chip/types.d.cts +7 -0
  33. package/dist/components/Chip/types.d.cts.map +1 -1
  34. package/dist/components/Chip/types.d.ts +7 -0
  35. package/dist/components/Chip/types.d.ts.map +1 -1
  36. package/dist/components/Icon.cjs +7 -3
  37. package/dist/components/Icon.d.cts +9 -1
  38. package/dist/components/Icon.d.cts.map +1 -1
  39. package/dist/components/Icon.d.ts +9 -1
  40. package/dist/components/Icon.d.ts.map +1 -1
  41. package/dist/components/Icon.js +7 -3
  42. package/dist/components/Icon.js.map +1 -1
  43. package/dist/components/IconButton.cjs +18 -8
  44. package/dist/components/IconButton.js +18 -8
  45. package/dist/components/IconButton.js.map +1 -1
  46. package/dist/components/Radio/Radio.cjs +29 -11
  47. package/dist/components/Radio/Radio.d.cts +7 -0
  48. package/dist/components/Radio/Radio.d.cts.map +1 -1
  49. package/dist/components/Radio/Radio.d.ts +7 -0
  50. package/dist/components/Radio/Radio.d.ts.map +1 -1
  51. package/dist/components/Radio/Radio.js +29 -11
  52. package/dist/components/Radio/Radio.js.map +1 -1
  53. package/dist/components/Switch.cjs +25 -2
  54. package/dist/components/Switch.d.cts +7 -0
  55. package/dist/components/Switch.d.cts.map +1 -1
  56. package/dist/components/Switch.d.ts +7 -0
  57. package/dist/components/Switch.d.ts.map +1 -1
  58. package/dist/components/Switch.js +25 -2
  59. package/dist/components/Switch.js.map +1 -1
  60. package/dist/components/Tabs/Tab.cjs +12 -2
  61. package/dist/components/Tabs/Tab.d.cts.map +1 -1
  62. package/dist/components/Tabs/Tab.d.ts.map +1 -1
  63. package/dist/components/Tabs/Tab.js +12 -2
  64. package/dist/components/Tabs/Tab.js.map +1 -1
  65. package/dist/components/Tabs/Tabs.cjs +8 -3
  66. package/dist/components/Tabs/Tabs.d.cts +9 -1
  67. package/dist/components/Tabs/Tabs.d.cts.map +1 -1
  68. package/dist/components/Tabs/Tabs.d.ts +9 -1
  69. package/dist/components/Tabs/Tabs.d.ts.map +1 -1
  70. package/dist/components/Tabs/Tabs.js +8 -3
  71. package/dist/components/Tabs/Tabs.js.map +1 -1
  72. package/dist/components/Tabs/tabsContexts.d.cts +2 -0
  73. package/dist/components/Tabs/tabsContexts.d.cts.map +1 -1
  74. package/dist/components/Tabs/tabsContexts.d.ts +2 -0
  75. package/dist/components/Tabs/tabsContexts.d.ts.map +1 -1
  76. package/dist/components/Tabs/tabsContexts.js.map +1 -1
  77. package/dist/components/Text.cjs +4 -1
  78. package/dist/components/Text.d.cts +10 -2
  79. package/dist/components/Text.d.cts.map +1 -1
  80. package/dist/components/Text.d.ts +10 -2
  81. package/dist/components/Text.d.ts.map +1 -1
  82. package/dist/components/Text.js +4 -1
  83. package/dist/components/Text.js.map +1 -1
  84. package/dist/components/UDSProvider.cjs +6 -2
  85. package/dist/components/UDSProvider.d.cts +8 -0
  86. package/dist/components/UDSProvider.d.cts.map +1 -1
  87. package/dist/components/UDSProvider.d.ts +8 -0
  88. package/dist/components/UDSProvider.d.ts.map +1 -1
  89. package/dist/components/UDSProvider.js +6 -2
  90. package/dist/components/UDSProvider.js.map +1 -1
  91. package/dist/fontScaling/FontScalingContext.cjs +35 -0
  92. package/dist/fontScaling/FontScalingContext.d.cts +38 -0
  93. package/dist/fontScaling/FontScalingContext.d.cts.map +1 -0
  94. package/dist/fontScaling/FontScalingContext.d.ts +38 -0
  95. package/dist/fontScaling/FontScalingContext.d.ts.map +1 -0
  96. package/dist/fontScaling/FontScalingContext.js +34 -0
  97. package/dist/fontScaling/FontScalingContext.js.map +1 -0
  98. package/dist/fontScaling/constants.cjs +72 -0
  99. package/dist/fontScaling/constants.d.cts +49 -0
  100. package/dist/fontScaling/constants.d.cts.map +1 -0
  101. package/dist/fontScaling/constants.d.ts +49 -0
  102. package/dist/fontScaling/constants.d.ts.map +1 -0
  103. package/dist/fontScaling/constants.js +70 -0
  104. package/dist/fontScaling/constants.js.map +1 -0
  105. package/dist/fontScaling/index.cjs +13 -0
  106. package/dist/fontScaling/index.d.cts +8 -0
  107. package/dist/fontScaling/index.d.ts +8 -0
  108. package/dist/fontScaling/index.js +6 -0
  109. package/dist/fontScaling/resolveMaxFontSizeMultiplier.cjs +38 -0
  110. package/dist/fontScaling/resolveMaxFontSizeMultiplier.d.cts +36 -0
  111. package/dist/fontScaling/resolveMaxFontSizeMultiplier.d.cts.map +1 -0
  112. package/dist/fontScaling/resolveMaxFontSizeMultiplier.d.ts +36 -0
  113. package/dist/fontScaling/resolveMaxFontSizeMultiplier.d.ts.map +1 -0
  114. package/dist/fontScaling/resolveMaxFontSizeMultiplier.js +39 -0
  115. package/dist/fontScaling/resolveMaxFontSizeMultiplier.js.map +1 -0
  116. package/dist/fontScaling/types.cjs +1 -0
  117. package/dist/fontScaling/types.d.cts +51 -0
  118. package/dist/fontScaling/types.d.cts.map +1 -0
  119. package/dist/fontScaling/types.d.ts +51 -0
  120. package/dist/fontScaling/types.d.ts.map +1 -0
  121. package/dist/fontScaling/types.js +1 -0
  122. package/dist/fontScaling/useFontScale.cjs +32 -0
  123. package/dist/fontScaling/useFontScale.d.cts +18 -0
  124. package/dist/fontScaling/useFontScale.d.cts.map +1 -0
  125. package/dist/fontScaling/useFontScale.d.ts +18 -0
  126. package/dist/fontScaling/useFontScale.d.ts.map +1 -0
  127. package/dist/fontScaling/useFontScale.js +32 -0
  128. package/dist/fontScaling/useFontScale.js.map +1 -0
  129. package/dist/fontScaling/useMaxFontSizeMultiplier.cjs +24 -0
  130. package/dist/fontScaling/useMaxFontSizeMultiplier.d.cts +16 -0
  131. package/dist/fontScaling/useMaxFontSizeMultiplier.d.cts.map +1 -0
  132. package/dist/fontScaling/useMaxFontSizeMultiplier.d.ts +16 -0
  133. package/dist/fontScaling/useMaxFontSizeMultiplier.d.ts.map +1 -0
  134. package/dist/fontScaling/useMaxFontSizeMultiplier.js +24 -0
  135. package/dist/fontScaling/useMaxFontSizeMultiplier.js.map +1 -0
  136. package/dist/jest/mocks/react-native.cjs +19 -4
  137. package/dist/jest/mocks/react-native.d.cts +8 -5
  138. package/dist/jest/mocks/react-native.d.cts.map +1 -1
  139. package/dist/jest/mocks/react-native.d.ts +8 -5
  140. package/dist/jest/mocks/react-native.d.ts.map +1 -1
  141. package/dist/jest/mocks/react-native.js +18 -5
  142. package/dist/jest/mocks/react-native.js.map +1 -1
  143. package/dist/jest/mocks/styles.cjs +14 -0
  144. package/dist/jest/mocks/styles.d.cts.map +1 -1
  145. package/dist/jest/mocks/styles.d.ts.map +1 -1
  146. package/dist/jest/mocks/styles.js +14 -0
  147. package/dist/jest/mocks/styles.js.map +1 -1
  148. package/dist/jest/setup.cjs +4 -0
  149. package/dist/jest/setup.d.cts.map +1 -1
  150. package/dist/jest/setup.d.ts.map +1 -1
  151. package/dist/jest/setup.js +4 -0
  152. package/dist/jest/setup.js.map +1 -1
  153. package/dist/mobile/generated/fontMetrics.cjs +1166 -0
  154. package/dist/mobile/generated/fontMetrics.js +1168 -0
  155. package/dist/mobile/generated/fontMetrics.js.map +1 -0
  156. package/dist/portal.d.cts.map +1 -1
  157. package/dist/portal.d.ts.map +1 -1
  158. package/dist/portal.js.map +1 -1
  159. package/dist/types/dist/index.d.cts.map +1 -1
  160. package/dist/types/dist/index.d.ts.map +1 -1
  161. package/fonts/uds-icons.ttf +0 -0
  162. package/generated/fontMetrics.ts +1173 -0
  163. package/generated/styles.d.ts +5 -28
  164. package/generated/unistyles.d.ts +0 -27
  165. package/package.json +11 -1
@@ -3,6 +3,8 @@ 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_fontScaling_useMaxFontSizeMultiplier = require("../../fontScaling/useMaxFontSizeMultiplier.cjs");
7
+ const require_fontScaling_useFontScale = require("../../fontScaling/useFontScale.cjs");
6
8
  const require_components_Pressable = require("../Pressable.cjs");
7
9
  const require_components_FormLabel = require("../FormLabel.cjs");
8
10
  const require_components_Radio_RadioGroupContext = require("./RadioGroupContext.cjs");
@@ -49,9 +51,11 @@ const VARIANT_ERROR_MAP = {
49
51
  * @see {@link Checkbox} for multi-select options
50
52
  * @see {@link Switch} for binary toggles
51
53
  */
52
- const Radio = (0, react.memo)(function Radio({ label, labelPosition = "start", variant: variantInput, size: sizeInput, value: valueProp, checked: checkedProp, hasError: hasErrorProp, reduceMotion: reduceMotionProp, defaultChecked, disabled: disabledProp, required, onChange, ref, ...viewProps }) {
54
+ const Radio = (0, react.memo)(function Radio({ label, labelPosition = "start", variant: variantInput, size: sizeInput, value: valueProp, checked: checkedProp, hasError: hasErrorProp, reduceMotion: reduceMotionProp, defaultChecked, disabled: disabledProp, required, maxFontSizeMultiplier, onChange, ref, ...viewProps }) {
53
55
  const uid = `uds-radio-${(0, react.useId)()}`;
54
56
  const radioGroup = require_components_Radio_RadioGroupContext.useRadioGroupContext();
57
+ const resolvedMaxFontSizeMultiplier = require_fontScaling_useMaxFontSizeMultiplier.useMaxFontSizeMultiplier("control", maxFontSizeMultiplier) ?? 0;
58
+ const fontScaleFactor = require_fontScaling_useFontScale.useFontScale(resolvedMaxFontSizeMultiplier);
55
59
  const disabled = Boolean(disabledProp || radioGroup?.disabled);
56
60
  const hasError = hasErrorProp ?? radioGroup?.hasError;
57
61
  const reduceMotion = reduceMotionProp ?? radioGroup?.reduceMotion;
@@ -107,21 +111,33 @@ const Radio = (0, react.memo)(function Radio({ label, labelPosition = "start", v
107
111
  pressed
108
112
  });
109
113
  const rootStyle = (0, react.useMemo)(() => [generated_styles.radioStyles.root, { opacity: disabled ? .5 : 1 }], [generated_styles.radioStyles.root, disabled]);
110
- const radioBoxStyle = (0, react.useMemo)(() => [
114
+ const radioBoxStyle = (0, react.useMemo)(() => {
115
+ const { width, height } = generated_styles.radioStyles.radio;
116
+ const scaledBox = fontScaleFactor !== 1 && typeof width === "number" && typeof height === "number" ? {
117
+ width: Math.round(width * fontScaleFactor),
118
+ height: Math.round(height * fontScaleFactor)
119
+ } : void 0;
120
+ return [
121
+ generated_styles.radioStyles.radio,
122
+ scaledBox,
123
+ {
124
+ alignItems: "center",
125
+ justifyContent: "center",
126
+ borderRadius: 9999
127
+ },
128
+ radioAnimatedStyle
129
+ ];
130
+ }, [
111
131
  generated_styles.radioStyles.radio,
112
- {
113
- alignItems: "center",
114
- justifyContent: "center",
115
- borderRadius: 9999
116
- },
132
+ fontScaleFactor,
117
133
  radioAnimatedStyle
118
- ], [generated_styles.radioStyles.radio, radioAnimatedStyle]);
134
+ ]);
119
135
  const INNER_CIRCLE_SIZE = 8;
120
136
  const circleStyle = (0, react.useMemo)(() => [generated_styles.radioStyles.radioCircle, {
121
- width: INNER_CIRCLE_SIZE,
122
- height: INNER_CIRCLE_SIZE,
137
+ width: Math.round(INNER_CIRCLE_SIZE * fontScaleFactor),
138
+ height: Math.round(INNER_CIRCLE_SIZE * fontScaleFactor),
123
139
  borderRadius: 9999
124
- }], [generated_styles.radioStyles.radioCircle]);
140
+ }], [generated_styles.radioStyles.radioCircle, fontScaleFactor]);
125
141
  const labelContent = (0, react.useMemo)(() => {
126
142
  if (!label) return null;
127
143
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_FormLabel.FormLabel, {
@@ -131,12 +147,14 @@ const Radio = (0, react.memo)(function Radio({ label, labelPosition = "start", v
131
147
  required,
132
148
  showRequiredAsterisk: required,
133
149
  hasError,
150
+ maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
134
151
  style: generated_styles.radioStyles.text
135
152
  });
136
153
  }, [
137
154
  label,
138
155
  required,
139
156
  hasError,
157
+ resolvedMaxFontSizeMultiplier,
140
158
  generated_styles.radioStyles.text
141
159
  ]);
142
160
  const a11yState = (0, react.useMemo)(() => ({
@@ -14,6 +14,13 @@ interface RadioProps extends Omit<ViewProps, 'style'>, UniversalRadioProps {
14
14
  disabled?: boolean;
15
15
  /** Whether the radio is required */
16
16
  required?: boolean;
17
+ /**
18
+ * Caps how far the circle, dot, and label grow with the OS text-size
19
+ * setting; all three scale by the same factor. Set null to remove the cap;
20
+ * override app-wide via UDSFontScalingProvider.
21
+ * @default 2
22
+ */
23
+ maxFontSizeMultiplier?: number | null;
17
24
  /** Callback fired when the checked state changes */
18
25
  onChange?: (checked: boolean) => void;
19
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Radio.d.cts","names":[],"sources":["../../../src/components/Radio/Radio.tsx"],"mappings":";;;;;;;UAoBU,UAAA,SAAmB,IAAA,CAAK,SAAA,YAAqB,mBAAA;;EAErD,GAAA,GAAM,GAAA,CAAI,IAAA;EAFS;EAInB,cAAA;EAJgC;EAMhC,QAAA;EAJM;EAMN,QAAA;EARqD;EAUrD,QAAA,IAAY,OAAA;AAAA;;;;;;;;;;;;;;AAAgB;;;;;;;;;;;;;;;;;;;cAmDxB,KAAA,EAAK,OAAA,CAAA,oBAAA,CAAA,UAAA"}
1
+ {"version":3,"file":"Radio.d.cts","names":[],"sources":["../../../src/components/Radio/Radio.tsx"],"mappings":";;;;;;;UAsBU,UAAA,SAAmB,IAAA,CAAK,SAAA,YAAqB,mBAAA;;EAErD,GAAA,GAAM,GAAA,CAAI,IAAA;EAFS;EAInB,cAAA;EAJgC;EAMhC,QAAA;EAJM;EAMN,QAAA;EARqD;;;;;;EAerD,qBAAA;EAbM;EAeN,QAAA,IAAY,OAAA;AAAA;;;;;;;;AAAgB;;;;;;;;;;;;;;;;;;;;;;;;;cAmDxB,KAAA,EAAK,OAAA,CAAA,oBAAA,CAAA,UAAA"}
@@ -14,6 +14,13 @@ interface RadioProps extends Omit<ViewProps, 'style'>, UniversalRadioProps {
14
14
  disabled?: boolean;
15
15
  /** Whether the radio is required */
16
16
  required?: boolean;
17
+ /**
18
+ * Caps how far the circle, dot, and label grow with the OS text-size
19
+ * setting; all three scale by the same factor. Set null to remove the cap;
20
+ * override app-wide via UDSFontScalingProvider.
21
+ * @default 2
22
+ */
23
+ maxFontSizeMultiplier?: number | null;
17
24
  /** Callback fired when the checked state changes */
18
25
  onChange?: (checked: boolean) => void;
19
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Radio.d.ts","names":[],"sources":["../../../src/components/Radio/Radio.tsx"],"mappings":";;;;;;;UAoBU,UAAA,SAAmB,IAAA,CAAK,SAAA,YAAqB,mBAAA;;EAErD,GAAA,GAAM,GAAA,CAAI,IAAA;EAFS;EAInB,cAAA;EAJgC;EAMhC,QAAA;EAJM;EAMN,QAAA;EARqD;EAUrD,QAAA,IAAY,OAAA;AAAA;;;;;;;;;;;;;;AAAgB;;;;;;;;;;;;;;;;;;;cAmDxB,KAAA,EAAK,OAAA,CAAA,oBAAA,CAAA,UAAA"}
1
+ {"version":3,"file":"Radio.d.ts","names":[],"sources":["../../../src/components/Radio/Radio.tsx"],"mappings":";;;;;;;UAsBU,UAAA,SAAmB,IAAA,CAAK,SAAA,YAAqB,mBAAA;;EAErD,GAAA,GAAM,GAAA,CAAI,IAAA;EAFS;EAInB,cAAA;EAJgC;EAMhC,QAAA;EAJM;EAMN,QAAA;EARqD;;;;;;EAerD,qBAAA;EAbM;EAeN,QAAA,IAAY,OAAA;AAAA;;;;;;;;AAAgB;;;;;;;;;;;;;;;;;;;;;;;;;cAmDxB,KAAA,EAAK,OAAA,CAAA,oBAAA,CAAA,UAAA"}
@@ -1,6 +1,8 @@
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 { useMaxFontSizeMultiplier } from "../../fontScaling/useMaxFontSizeMultiplier.js";
5
+ import { useFontScale } from "../../fontScaling/useFontScale.js";
4
6
  import { Pressable as Pressable$1 } from "../Pressable.js";
5
7
  import { FormLabel } from "../FormLabel.js";
6
8
  import { useRadioGroupContext } from "./RadioGroupContext.js";
@@ -46,9 +48,11 @@ const VARIANT_ERROR_MAP = {
46
48
  * @see {@link Checkbox} for multi-select options
47
49
  * @see {@link Switch} for binary toggles
48
50
  */
49
- const Radio = memo(function Radio({ label, labelPosition = "start", variant: variantInput, size: sizeInput, value: valueProp, checked: checkedProp, hasError: hasErrorProp, reduceMotion: reduceMotionProp, defaultChecked, disabled: disabledProp, required, onChange, ref, ...viewProps }) {
51
+ const Radio = memo(function Radio({ label, labelPosition = "start", variant: variantInput, size: sizeInput, value: valueProp, checked: checkedProp, hasError: hasErrorProp, reduceMotion: reduceMotionProp, defaultChecked, disabled: disabledProp, required, maxFontSizeMultiplier, onChange, ref, ...viewProps }) {
50
52
  const uid = `uds-radio-${useId()}`;
51
53
  const radioGroup = useRadioGroupContext();
54
+ const resolvedMaxFontSizeMultiplier = useMaxFontSizeMultiplier("control", maxFontSizeMultiplier) ?? 0;
55
+ const fontScaleFactor = useFontScale(resolvedMaxFontSizeMultiplier);
52
56
  const disabled = Boolean(disabledProp || radioGroup?.disabled);
53
57
  const hasError = hasErrorProp ?? radioGroup?.hasError;
54
58
  const reduceMotion = reduceMotionProp ?? radioGroup?.reduceMotion;
@@ -104,21 +108,33 @@ const Radio = memo(function Radio({ label, labelPosition = "start", variant: var
104
108
  pressed
105
109
  });
106
110
  const rootStyle = useMemo(() => [radioStyles.root, { opacity: disabled ? .5 : 1 }], [radioStyles.root, disabled]);
107
- const radioBoxStyle = useMemo(() => [
111
+ const radioBoxStyle = useMemo(() => {
112
+ const { width, height } = radioStyles.radio;
113
+ const scaledBox = fontScaleFactor !== 1 && typeof width === "number" && typeof height === "number" ? {
114
+ width: Math.round(width * fontScaleFactor),
115
+ height: Math.round(height * fontScaleFactor)
116
+ } : void 0;
117
+ return [
118
+ radioStyles.radio,
119
+ scaledBox,
120
+ {
121
+ alignItems: "center",
122
+ justifyContent: "center",
123
+ borderRadius: 9999
124
+ },
125
+ radioAnimatedStyle
126
+ ];
127
+ }, [
108
128
  radioStyles.radio,
109
- {
110
- alignItems: "center",
111
- justifyContent: "center",
112
- borderRadius: 9999
113
- },
129
+ fontScaleFactor,
114
130
  radioAnimatedStyle
115
- ], [radioStyles.radio, radioAnimatedStyle]);
131
+ ]);
116
132
  const INNER_CIRCLE_SIZE = 8;
117
133
  const circleStyle = useMemo(() => [radioStyles.radioCircle, {
118
- width: INNER_CIRCLE_SIZE,
119
- height: INNER_CIRCLE_SIZE,
134
+ width: Math.round(INNER_CIRCLE_SIZE * fontScaleFactor),
135
+ height: Math.round(INNER_CIRCLE_SIZE * fontScaleFactor),
120
136
  borderRadius: 9999
121
- }], [radioStyles.radioCircle]);
137
+ }], [radioStyles.radioCircle, fontScaleFactor]);
122
138
  const labelContent = useMemo(() => {
123
139
  if (!label) return null;
124
140
  return /* @__PURE__ */ jsx(FormLabel, {
@@ -128,12 +144,14 @@ const Radio = memo(function Radio({ label, labelPosition = "start", variant: var
128
144
  required,
129
145
  showRequiredAsterisk: required,
130
146
  hasError,
147
+ maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
131
148
  style: radioStyles.text
132
149
  });
133
150
  }, [
134
151
  label,
135
152
  required,
136
153
  hasError,
154
+ resolvedMaxFontSizeMultiplier,
137
155
  radioStyles.text
138
156
  ]);
139
157
  const a11yState = useMemo(() => ({
@@ -1 +1 @@
1
- {"version":3,"file":"Radio.js","names":["Pressable"],"sources":["../../../src/components/Radio/Radio.tsx"],"sourcesContent":["import type { RadioVariant, UniversalRadioProps } from '@yahoo/uds-types';\nimport type { Ref } from 'react';\nimport { memo, useCallback, useEffect, useId, useMemo, useState } from 'react';\nimport type { ViewProps } from 'react-native';\nimport { View } from 'react-native';\nimport Animated, { useAnimatedStyle, useDerivedValue, withSpring } from 'react-native-reanimated';\n\nimport { radioStyles } from '../../../generated/styles';\nimport { BUTTON_SPRING_CONFIG, SCALE_EFFECTS } from '../../motion';\nimport { FormLabel } from '../FormLabel';\nimport { Pressable } from '../Pressable';\nimport { useRadioGroupContext } from './RadioGroupContext';\n\n/* -------------------------------------------------------------------------- */\n/* Types */\n/* -------------------------------------------------------------------------- */\n\n/** Derived from RadioValue - maps boolean to 'checked'/'unchecked' string literals */\ntype RadioValueState = 'checked' | 'unchecked';\n\ninterface RadioProps extends Omit<ViewProps, 'style'>, UniversalRadioProps {\n /** Ref to the underlying View */\n ref?: Ref<View>;\n /** Default checked state for uncontrolled mode */\n defaultChecked?: boolean;\n /** Whether the radio is disabled */\n disabled?: boolean;\n /** Whether the radio is required */\n required?: boolean;\n /** Callback fired when the checked state changes */\n onChange?: (checked: boolean) => void;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Variant Mapping */\n/* -------------------------------------------------------------------------- */\n\n/** All style variants available in the generated styles (includes error variants) */\ntype RadioStyleVariant = 'primary' | 'secondary' | 'alert' | 'alert-secondary';\n\nconst VARIANT_ERROR_MAP: Record<RadioVariant, RadioStyleVariant> = {\n primary: 'alert',\n secondary: 'alert-secondary',\n};\n\n/* -------------------------------------------------------------------------- */\n/* Radio Component */\n/* -------------------------------------------------------------------------- */\n\n/**\n * **⚙️ A radio component**\n *\n * @description\n * A radio component allows users to select one option from a set.\n * Unlike checkbox, radio buttons cannot have multiple selected in a group,\n * allowing only one selection at a time.\n *\n * @category Form\n * @platform mobile\n *\n * @example\n * ```tsx\n * import { Radio } from '@yahoo/uds-mobile/Radio';\n *\n * <Radio label=\"Option A\" />\n * <Radio label=\"Option B\" checked={true} onChange={setChecked} />\n * ```\n *\n * @usage\n * - Forms: For selecting a single option from multiple choices\n * - Settings: For toggling between mutually exclusive preferences\n *\n * @accessibility\n * - Sets `accessibilityRole=\"radio\"` automatically\n * - Announces checked/unchecked state\n * - Label is used as accessibility label\n * - Supports `reduceMotion` for users who prefer reduced motion\n *\n * @see {@link Checkbox} for multi-select options\n * @see {@link Switch} for binary toggles\n */\nconst Radio = memo(function Radio({\n // Radio props\n label,\n labelPosition = 'start',\n variant: variantInput,\n size: sizeInput,\n value: valueProp,\n checked: checkedProp,\n hasError: hasErrorProp,\n reduceMotion: reduceMotionProp,\n // Native props\n defaultChecked,\n disabled: disabledProp,\n required,\n onChange,\n ref,\n ...viewProps\n}: RadioProps) {\n const generatedId = useId();\n const uid = `uds-radio-${generatedId}`;\n const radioGroup = useRadioGroupContext();\n\n const disabled = Boolean(disabledProp || radioGroup?.disabled);\n const hasError = hasErrorProp ?? radioGroup?.hasError;\n const reduceMotion = reduceMotionProp ?? radioGroup?.reduceMotion;\n const size = sizeInput ?? radioGroup?.size ?? 'md';\n const variantProp = variantInput ?? radioGroup?.variant ?? 'primary';\n const value = valueProp ?? '';\n\n /* --------------------------------- State ---------------------------------- */\n const isInRadioGroup = radioGroup !== undefined;\n const isControlled = checkedProp !== undefined;\n const [internalChecked, setInternalChecked] = useState<boolean>(\n isControlled ? checkedProp : (defaultChecked ?? false),\n );\n const [pressed, setPressed] = useState(false);\n\n // Use controlled value if provided, otherwise use internal state\n const checked = isInRadioGroup\n ? radioGroup.value === value\n : isControlled\n ? checkedProp\n : internalChecked;\n\n useEffect(() => {\n if (!__DEV__ || !isInRadioGroup || valueProp) {\n return;\n }\n\n console.warn(\n '[UDS Mobile] Radio: A Radio rendered inside RadioGroup requires a unique, non-empty value prop.',\n );\n }, [isInRadioGroup, valueProp]);\n\n /* ------------------------------ Derived Values ---------------------------- */\n const valueState: RadioValueState = useMemo(() => {\n return checked ? 'checked' : 'unchecked';\n }, [checked]);\n\n /* -------------------------------- Animation ------------------------------- */\n const shouldAnimate = !reduceMotion;\n\n // Scale animation for press feedback (disabled when reduceMotion is true)\n const scaleProgress = useDerivedValue(\n () =>\n shouldAnimate\n ? withSpring(pressed ? SCALE_EFFECTS.down : SCALE_EFFECTS.none, BUTTON_SPRING_CONFIG)\n : SCALE_EFFECTS.none, // No scale effect when reduceMotion is true\n [pressed, shouldAnimate],\n );\n\n const radioAnimatedStyle = useAnimatedStyle(() => ({\n transform: [{ scale: scaleProgress.value }],\n }));\n\n /* -------------------------------- Handlers -------------------------------- */\n const handlePress = useCallback(() => {\n if (disabled) {\n return;\n }\n\n if (isInRadioGroup) {\n if (radioGroup.value === value) {\n return;\n }\n\n radioGroup.setValue(value);\n onChange?.(true);\n return;\n }\n\n const newChecked = !checked;\n\n if (!isControlled) {\n setInternalChecked(newChecked);\n }\n\n onChange?.(newChecked);\n }, [disabled, isInRadioGroup, checked, isControlled, onChange, radioGroup, value]);\n\n const handlePressIn = useCallback(() => {\n setPressed(true);\n }, []);\n\n const handlePressOut = useCallback(() => {\n setPressed(false);\n }, []);\n\n /* --------------------------------- Styles --------------------------------- */\n // Compute the style variant (primary/secondary maps to alert variants when hasError)\n const styleVariant: RadioStyleVariant = hasError ? VARIANT_ERROR_MAP[variantProp] : variantProp;\n\n radioStyles.useVariants({\n size,\n variant: styleVariant, // primary, secondary, alert, alert-secondary\n value: valueState, // checked, unchecked\n pressed,\n });\n\n const rootStyle = useMemo(\n () => [radioStyles.root, { opacity: disabled ? 0.5 : 1 }],\n [radioStyles.root, disabled],\n );\n\n const radioBoxStyle = useMemo(\n () => [\n radioStyles.radio,\n {\n alignItems: 'center' as const,\n justifyContent: 'center' as const,\n borderRadius: 9999,\n },\n radioAnimatedStyle,\n ],\n [radioStyles.radio, radioAnimatedStyle],\n );\n\n // Inner circle size: fixed 8x8px to match web implementation\n const INNER_CIRCLE_SIZE = 8;\n\n const circleStyle = useMemo(\n () => [\n radioStyles.radioCircle,\n {\n width: INNER_CIRCLE_SIZE,\n height: INNER_CIRCLE_SIZE,\n borderRadius: 9999,\n },\n ],\n [radioStyles.radioCircle],\n );\n\n /* ---------------------------- Render Helpers ------------------------------ */\n const labelContent = useMemo(() => {\n if (!label) {\n return null;\n }\n\n return (\n <FormLabel\n color=\"inherit\"\n variant=\"inherit\"\n label={label}\n required={required}\n showRequiredAsterisk={required}\n hasError={hasError}\n style={radioStyles.text}\n />\n );\n }, [label, required, hasError, radioStyles.text]);\n\n const a11yState = useMemo(\n () => ({\n disabled,\n checked,\n }),\n [disabled, checked],\n );\n\n /* --------------------------------- Render --------------------------------- */\n return (\n <Pressable\n ref={ref}\n nativeID={uid}\n onPress={handlePress}\n onPressIn={handlePressIn}\n onPressOut={handlePressOut}\n disabled={disabled}\n flexDirection={labelPosition === 'start' ? 'row' : 'row-reverse'}\n alignItems=\"center\"\n alignSelf=\"flex-start\"\n accessibilityRole=\"radio\"\n accessibilityState={a11yState}\n accessibilityLabel={typeof label === 'string' ? label : undefined}\n {...viewProps}\n style={rootStyle}\n >\n <Animated.View style={radioBoxStyle}>{checked && <View style={circleStyle} />}</Animated.View>\n\n {labelContent}\n </Pressable>\n );\n});\n\nRadio.displayName = 'Radio';\n\nexport { Radio, type RadioProps };\n"],"mappings":"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"}
1
+ {"version":3,"file":"Radio.js","names":["Pressable"],"sources":["../../../src/components/Radio/Radio.tsx"],"sourcesContent":["import type { RadioVariant, UniversalRadioProps } from '@yahoo/uds-types';\nimport type { Ref } from 'react';\nimport { memo, useCallback, useEffect, useId, useMemo, useState } from 'react';\nimport type { ViewProps } from 'react-native';\nimport { View } from 'react-native';\nimport Animated, { useAnimatedStyle, useDerivedValue, withSpring } from 'react-native-reanimated';\n\nimport { radioStyles } from '../../../generated/styles';\nimport { useFontScale } from '../../fontScaling/useFontScale';\nimport { useMaxFontSizeMultiplier } from '../../fontScaling/useMaxFontSizeMultiplier';\nimport { BUTTON_SPRING_CONFIG, SCALE_EFFECTS } from '../../motion';\nimport { FormLabel } from '../FormLabel';\nimport { Pressable } from '../Pressable';\nimport { useRadioGroupContext } from './RadioGroupContext';\n\n/* -------------------------------------------------------------------------- */\n/* Types */\n/* -------------------------------------------------------------------------- */\n\n/** Derived from RadioValue - maps boolean to 'checked'/'unchecked' string literals */\ntype RadioValueState = 'checked' | 'unchecked';\n\ninterface RadioProps extends Omit<ViewProps, 'style'>, UniversalRadioProps {\n /** Ref to the underlying View */\n ref?: Ref<View>;\n /** Default checked state for uncontrolled mode */\n defaultChecked?: boolean;\n /** Whether the radio is disabled */\n disabled?: boolean;\n /** Whether the radio is required */\n required?: boolean;\n /**\n * Caps how far the circle, dot, and label grow with the OS text-size\n * setting; all three scale by the same factor. Set null to remove the cap;\n * override app-wide via UDSFontScalingProvider.\n * @default 2\n */\n maxFontSizeMultiplier?: number | null;\n /** Callback fired when the checked state changes */\n onChange?: (checked: boolean) => void;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Variant Mapping */\n/* -------------------------------------------------------------------------- */\n\n/** All style variants available in the generated styles (includes error variants) */\ntype RadioStyleVariant = 'primary' | 'secondary' | 'alert' | 'alert-secondary';\n\nconst VARIANT_ERROR_MAP: Record<RadioVariant, RadioStyleVariant> = {\n primary: 'alert',\n secondary: 'alert-secondary',\n};\n\n/* -------------------------------------------------------------------------- */\n/* Radio Component */\n/* -------------------------------------------------------------------------- */\n\n/**\n * **⚙️ A radio component**\n *\n * @description\n * A radio component allows users to select one option from a set.\n * Unlike checkbox, radio buttons cannot have multiple selected in a group,\n * allowing only one selection at a time.\n *\n * @category Form\n * @platform mobile\n *\n * @example\n * ```tsx\n * import { Radio } from '@yahoo/uds-mobile/Radio';\n *\n * <Radio label=\"Option A\" />\n * <Radio label=\"Option B\" checked={true} onChange={setChecked} />\n * ```\n *\n * @usage\n * - Forms: For selecting a single option from multiple choices\n * - Settings: For toggling between mutually exclusive preferences\n *\n * @accessibility\n * - Sets `accessibilityRole=\"radio\"` automatically\n * - Announces checked/unchecked state\n * - Label is used as accessibility label\n * - Supports `reduceMotion` for users who prefer reduced motion\n *\n * @see {@link Checkbox} for multi-select options\n * @see {@link Switch} for binary toggles\n */\nconst Radio = memo(function Radio({\n // Radio props\n label,\n labelPosition = 'start',\n variant: variantInput,\n size: sizeInput,\n value: valueProp,\n checked: checkedProp,\n hasError: hasErrorProp,\n reduceMotion: reduceMotionProp,\n // Native props\n defaultChecked,\n disabled: disabledProp,\n required,\n maxFontSizeMultiplier,\n onChange,\n ref,\n ...viewProps\n}: RadioProps) {\n const generatedId = useId();\n const uid = `uds-radio-${generatedId}`;\n const radioGroup = useRadioGroupContext();\n\n // One capped factor governs circle, dot, and label so they stay cohesive.\n const resolvedMaxFontSizeMultiplier =\n useMaxFontSizeMultiplier('control', maxFontSizeMultiplier) ?? 0;\n const fontScaleFactor = useFontScale(resolvedMaxFontSizeMultiplier);\n\n const disabled = Boolean(disabledProp || radioGroup?.disabled);\n const hasError = hasErrorProp ?? radioGroup?.hasError;\n const reduceMotion = reduceMotionProp ?? radioGroup?.reduceMotion;\n const size = sizeInput ?? radioGroup?.size ?? 'md';\n const variantProp = variantInput ?? radioGroup?.variant ?? 'primary';\n const value = valueProp ?? '';\n\n /* --------------------------------- State ---------------------------------- */\n const isInRadioGroup = radioGroup !== undefined;\n const isControlled = checkedProp !== undefined;\n const [internalChecked, setInternalChecked] = useState<boolean>(\n isControlled ? checkedProp : (defaultChecked ?? false),\n );\n const [pressed, setPressed] = useState(false);\n\n // Use controlled value if provided, otherwise use internal state\n const checked = isInRadioGroup\n ? radioGroup.value === value\n : isControlled\n ? checkedProp\n : internalChecked;\n\n useEffect(() => {\n if (!__DEV__ || !isInRadioGroup || valueProp) {\n return;\n }\n\n console.warn(\n '[UDS Mobile] Radio: A Radio rendered inside RadioGroup requires a unique, non-empty value prop.',\n );\n }, [isInRadioGroup, valueProp]);\n\n /* ------------------------------ Derived Values ---------------------------- */\n const valueState: RadioValueState = useMemo(() => {\n return checked ? 'checked' : 'unchecked';\n }, [checked]);\n\n /* -------------------------------- Animation ------------------------------- */\n const shouldAnimate = !reduceMotion;\n\n // Scale animation for press feedback (disabled when reduceMotion is true)\n const scaleProgress = useDerivedValue(\n () =>\n shouldAnimate\n ? withSpring(pressed ? SCALE_EFFECTS.down : SCALE_EFFECTS.none, BUTTON_SPRING_CONFIG)\n : SCALE_EFFECTS.none, // No scale effect when reduceMotion is true\n [pressed, shouldAnimate],\n );\n\n const radioAnimatedStyle = useAnimatedStyle(() => ({\n transform: [{ scale: scaleProgress.value }],\n }));\n\n /* -------------------------------- Handlers -------------------------------- */\n const handlePress = useCallback(() => {\n if (disabled) {\n return;\n }\n\n if (isInRadioGroup) {\n if (radioGroup.value === value) {\n return;\n }\n\n radioGroup.setValue(value);\n onChange?.(true);\n return;\n }\n\n const newChecked = !checked;\n\n if (!isControlled) {\n setInternalChecked(newChecked);\n }\n\n onChange?.(newChecked);\n }, [disabled, isInRadioGroup, checked, isControlled, onChange, radioGroup, value]);\n\n const handlePressIn = useCallback(() => {\n setPressed(true);\n }, []);\n\n const handlePressOut = useCallback(() => {\n setPressed(false);\n }, []);\n\n /* --------------------------------- Styles --------------------------------- */\n // Compute the style variant (primary/secondary maps to alert variants when hasError)\n const styleVariant: RadioStyleVariant = hasError ? VARIANT_ERROR_MAP[variantProp] : variantProp;\n\n radioStyles.useVariants({\n size,\n variant: styleVariant, // primary, secondary, alert, alert-secondary\n value: valueState, // checked, unchecked\n pressed,\n });\n\n const rootStyle = useMemo(\n () => [radioStyles.root, { opacity: disabled ? 0.5 : 1 }],\n [radioStyles.root, disabled],\n );\n\n const radioBoxStyle = useMemo(() => {\n // The circle grows by the capped factor alongside its label; borderWidth\n // stays fixed (stroke weight doesn't scale in the OS's own controls).\n const { width, height } = radioStyles.radio;\n const scaledBox =\n fontScaleFactor !== 1 && typeof width === 'number' && typeof height === 'number'\n ? {\n width: Math.round(width * fontScaleFactor),\n height: Math.round(height * fontScaleFactor),\n }\n : undefined;\n\n return [\n radioStyles.radio,\n scaledBox,\n {\n alignItems: 'center' as const,\n justifyContent: 'center' as const,\n borderRadius: 9999,\n },\n radioAnimatedStyle,\n ];\n }, [radioStyles.radio, fontScaleFactor, radioAnimatedStyle]);\n\n // Inner circle size: fixed 8x8px to match web implementation\n const INNER_CIRCLE_SIZE = 8;\n\n const circleStyle = useMemo(\n () => [\n radioStyles.radioCircle,\n {\n width: Math.round(INNER_CIRCLE_SIZE * fontScaleFactor),\n height: Math.round(INNER_CIRCLE_SIZE * fontScaleFactor),\n borderRadius: 9999,\n },\n ],\n [radioStyles.radioCircle, fontScaleFactor],\n );\n\n /* ---------------------------- Render Helpers ------------------------------ */\n const labelContent = useMemo(() => {\n if (!label) {\n return null;\n }\n\n return (\n <FormLabel\n color=\"inherit\"\n variant=\"inherit\"\n label={label}\n required={required}\n showRequiredAsterisk={required}\n hasError={hasError}\n maxFontSizeMultiplier={resolvedMaxFontSizeMultiplier}\n style={radioStyles.text}\n />\n );\n }, [label, required, hasError, resolvedMaxFontSizeMultiplier, radioStyles.text]);\n\n const a11yState = useMemo(\n () => ({\n disabled,\n checked,\n }),\n [disabled, checked],\n );\n\n /* --------------------------------- Render --------------------------------- */\n return (\n <Pressable\n ref={ref}\n nativeID={uid}\n onPress={handlePress}\n onPressIn={handlePressIn}\n onPressOut={handlePressOut}\n disabled={disabled}\n flexDirection={labelPosition === 'start' ? 'row' : 'row-reverse'}\n alignItems=\"center\"\n alignSelf=\"flex-start\"\n accessibilityRole=\"radio\"\n accessibilityState={a11yState}\n accessibilityLabel={typeof label === 'string' ? label : undefined}\n {...viewProps}\n style={rootStyle}\n >\n <Animated.View style={radioBoxStyle}>{checked && <View style={circleStyle} />}</Animated.View>\n\n {labelContent}\n </Pressable>\n );\n});\n\nRadio.displayName = 'Radio';\n\nexport { Radio, type RadioProps };\n"],"mappings":"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"}
@@ -1,13 +1,16 @@
1
1
  /*! © 2026 Yahoo, Inc. UDS Mobile v0.0.0-development */
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
4
+ const require_fontScaling_useMaxFontSizeMultiplier = require("../fontScaling/useMaxFontSizeMultiplier.cjs");
4
5
  const require_components_IconSlot = require("./IconSlot.cjs");
6
+ const require_fontScaling_useFontScale = require("../fontScaling/useFontScale.cjs");
5
7
  const require_components_FormLabel = require("./FormLabel.cjs");
6
8
  let react = require("react");
7
9
  let react_native = require("react-native");
8
10
  let react_jsx_runtime = require("react/jsx-runtime");
9
11
  let generated_styles = require("../../generated/styles");
10
12
  let react_native_unistyles = require("react-native-unistyles");
13
+ let _yahoo_uds_icons_tokens = require("@yahoo/uds-icons/tokens");
11
14
  let react_native_reanimated = require("react-native-reanimated");
12
15
  react_native_reanimated = require_runtime.__toESM(react_native_reanimated);
13
16
  let react_native_unistyles_reanimated = require("react-native-unistyles/reanimated");
@@ -50,13 +53,16 @@ const ANIMATION_DURATION = 120;
50
53
  * @see {@link Checkbox} for forms with submit actions
51
54
  * @see {@link Radio} for single-select options
52
55
  */
53
- const Switch = (0, react.memo)(function Switch({ isOn: isOnProp, defaultIsOn = false, onChange, label, labelPosition = "start", size = "md", onIcon, offIcon, disabled = false, required, accessibilityHint, reduceMotion = false, ref, ...viewProps }) {
56
+ const Switch = (0, react.memo)(function Switch({ isOn: isOnProp, defaultIsOn = false, onChange, label, labelPosition = "start", size = "md", onIcon, offIcon, disabled = false, required, maxFontSizeMultiplier, accessibilityHint, reduceMotion = false, ref, ...viewProps }) {
54
57
  const isControlled = isOnProp !== void 0;
55
58
  const [internalIsOn, setInternalIsOn] = (0, react.useState)(defaultIsOn);
56
59
  const [prefersReducedMotion, setPrefersReducedMotion] = (0, react.useState)(false);
57
60
  const isOn = isControlled ? isOnProp : internalIsOn;
58
61
  const activeVariant = isOn ? "on" : "off";
59
62
  const { theme } = (0, react_native_unistyles.useUnistyles)();
63
+ const resolvedMaxFontSizeMultiplier = require_fontScaling_useMaxFontSizeMultiplier.useMaxFontSizeMultiplier("control", maxFontSizeMultiplier) ?? 0;
64
+ const fontScaleFactor = require_fontScaling_useFontScale.useFontScale(resolvedMaxFontSizeMultiplier);
65
+ const scaledHandleIconSize = Math.round(_yahoo_uds_icons_tokens.ICON_SIZE_MAP.sm * fontScaleFactor);
60
66
  (0, react.useEffect)(() => {
61
67
  const checkReducedMotion = async () => {
62
68
  setPrefersReducedMotion(await react_native.AccessibilityInfo.isReduceMotionEnabled());
@@ -67,7 +73,7 @@ const Switch = (0, react.memo)(function Switch({ isOn: isOnProp, defaultIsOn = f
67
73
  }, []);
68
74
  const animationDuration = reduceMotion || prefersReducedMotion ? 0 : ANIMATION_DURATION;
69
75
  const progress = (0, react_native_reanimated.useDerivedValue)(() => (0, react_native_reanimated.withTiming)(isOn ? 1 : 0, { duration: animationDuration }), [isOn, animationDuration]);
70
- const travelDistance = HANDLE_TRAVEL[size] * (react_native.I18nManager.isRTL ? -1 : 1);
76
+ const travelDistance = Math.round(HANDLE_TRAVEL[size] * fontScaleFactor) * (react_native.I18nManager.isRTL ? -1 : 1);
71
77
  const handlePress = (0, react.useCallback)(() => {
72
78
  if (disabled) return;
73
79
  const newValue = !isOn;
@@ -103,24 +109,36 @@ const Switch = (0, react.memo)(function Switch({ isOn: isOnProp, defaultIsOn = f
103
109
  return { transform: [{ translateX: progress.value * travelDistance }] };
104
110
  });
105
111
  const rootStyle = (0, react.useMemo)(() => [resolvedSwitchStyles.root, switchStaticStyles.root({ disabled })], [resolvedSwitchStyles.root, disabled]);
112
+ const scaleDimensions = (0, react.useCallback)((style) => {
113
+ if (!style || fontScaleFactor === 1) return;
114
+ const { width, height } = style;
115
+ return typeof width === "number" && typeof height === "number" ? {
116
+ width: Math.round(width * fontScaleFactor),
117
+ height: Math.round(height * fontScaleFactor)
118
+ } : void 0;
119
+ }, [fontScaleFactor]);
106
120
  const trackStyle = (0, react.useMemo)(() => [
107
121
  resolvedSwitchStyles.switch,
108
122
  switchStaticStyles.track,
109
123
  variantLayerStyles.switch,
124
+ scaleDimensions(resolvedSwitchStyles.switch),
110
125
  react_native.Platform.OS !== "web" && animatedTrackStyle
111
126
  ], [
112
127
  resolvedSwitchStyles.switch,
113
128
  variantLayerStyles.switch,
129
+ scaleDimensions,
114
130
  animatedTrackStyle
115
131
  ]);
116
132
  const handleStyle = (0, react.useMemo)(() => [
117
133
  resolvedSwitchStyles.handle,
118
134
  switchStaticStyles.handle,
119
135
  variantLayerStyles.handle,
136
+ scaleDimensions(resolvedSwitchStyles.handle),
120
137
  animatedHandleStyle
121
138
  ], [
122
139
  resolvedSwitchStyles.handle,
123
140
  variantLayerStyles.handle,
141
+ scaleDimensions,
124
142
  animatedHandleStyle
125
143
  ]);
126
144
  const accessibilityLabel = typeof label === "string" ? label : void 0;
@@ -131,6 +149,7 @@ const Switch = (0, react.memo)(function Switch({ isOn: isOnProp, defaultIsOn = f
131
149
  label,
132
150
  required,
133
151
  showRequiredAsterisk: required,
152
+ maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
134
153
  style: [resolvedSwitchStyles.text, variantLayerStyles.text]
135
154
  });
136
155
  const a11yValue = (0, react.useMemo)(() => ({ text: isOn ? "On" : "Off" }), [isOn]);
@@ -161,6 +180,8 @@ const Switch = (0, react.memo)(function Switch({ isOn: isOnProp, defaultIsOn = f
161
180
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_IconSlot.IconSlot, {
162
181
  icon: onIcon,
163
182
  variant: "fill",
183
+ dangerouslySetSize: scaledHandleIconSize,
184
+ allowFontScaling: false,
164
185
  style: [resolvedSwitchStyles.handleIcon, variantLayerStyles.handleIcon]
165
186
  })
166
187
  }), offIcon && !isOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_native_reanimated.default.View, {
@@ -168,6 +189,8 @@ const Switch = (0, react.memo)(function Switch({ isOn: isOnProp, defaultIsOn = f
168
189
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_IconSlot.IconSlot, {
169
190
  icon: offIcon,
170
191
  variant: "fill",
192
+ dangerouslySetSize: scaledHandleIconSize,
193
+ allowFontScaling: false,
171
194
  style: [resolvedSwitchStyles.handleIcon, variantLayerStyles.handleIcon]
172
195
  })
173
196
  })]
@@ -15,6 +15,13 @@ interface SwitchProps extends Omit<ViewProps, 'style'>, UniversalSwitchProps<Ico
15
15
  disabled?: boolean;
16
16
  /** Whether the switch is required (shows asterisk with label) */
17
17
  required?: boolean;
18
+ /**
19
+ * Caps how far the track, handle, and label grow with the OS text-size
20
+ * setting; all scale by the same factor. Set null to remove the cap;
21
+ * override app-wide via UDSFontScalingProvider.
22
+ * @default 2
23
+ */
24
+ maxFontSizeMultiplier?: number | null;
18
25
  /** Accessibility hint describing what happens when activated */
19
26
  accessibilityHint?: AccessibilityProps['accessibilityHint'];
20
27
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Switch.d.cts","names":[],"sources":["../../src/components/Switch.tsx"],"mappings":";;;;;;;;UAsBU,WAAA,SAAoB,IAAA,CAAK,SAAA,YAAqB,oBAAA,CAAqB,YAAA;;EAE3E,GAAA,GAAM,GAAA,CAAI,IAAA;EAFF;EAIR,QAAA,IAAY,KAAA;;EAEZ,QAAA;EAN2E;EAQ3E,QAAA;EANM;EAQN,iBAAA,GAAoB,kBAAA;AAAA;;;;;;;;;;;;;;;;;;;AAAkB;;;;;;;;;;;;;;;cA2ClC,MAAA,EAAM,OAAA,CAAA,oBAAA,CAAA,WAAA"}
1
+ {"version":3,"file":"Switch.d.cts","names":[],"sources":["../../src/components/Switch.tsx"],"mappings":";;;;;;;;UAyBU,WAAA,SAAoB,IAAA,CAAK,SAAA,YAAqB,oBAAA,CAAqB,YAAA;;EAE3E,GAAA,GAAM,GAAA,CAAI,IAAA;EAFF;EAIR,QAAA,IAAY,KAAA;;EAEZ,QAAA;EAN2E;EAQ3E,QAAA;EANM;;;;;;EAaN,qBAAA;EAfiC;EAiBjC,iBAAA,GAAoB,kBAAA;AAAA;;;;;;;;;;;;;AAAkB;;;;;;;;;;;;;;;;;;;;;cA2ClC,MAAA,EAAM,OAAA,CAAA,oBAAA,CAAA,WAAA"}
@@ -15,6 +15,13 @@ interface SwitchProps extends Omit<ViewProps, 'style'>, UniversalSwitchProps<Ico
15
15
  disabled?: boolean;
16
16
  /** Whether the switch is required (shows asterisk with label) */
17
17
  required?: boolean;
18
+ /**
19
+ * Caps how far the track, handle, and label grow with the OS text-size
20
+ * setting; all scale by the same factor. Set null to remove the cap;
21
+ * override app-wide via UDSFontScalingProvider.
22
+ * @default 2
23
+ */
24
+ maxFontSizeMultiplier?: number | null;
18
25
  /** Accessibility hint describing what happens when activated */
19
26
  accessibilityHint?: AccessibilityProps['accessibilityHint'];
20
27
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Switch.d.ts","names":[],"sources":["../../src/components/Switch.tsx"],"mappings":";;;;;;;;UAsBU,WAAA,SAAoB,IAAA,CAAK,SAAA,YAAqB,oBAAA,CAAqB,YAAA;;EAE3E,GAAA,GAAM,GAAA,CAAI,IAAA;EAFF;EAIR,QAAA,IAAY,KAAA;;EAEZ,QAAA;EAN2E;EAQ3E,QAAA;EANM;EAQN,iBAAA,GAAoB,kBAAA;AAAA;;;;;;;;;;;;;;;;;;;AAAkB;;;;;;;;;;;;;;;cA2ClC,MAAA,EAAM,OAAA,CAAA,oBAAA,CAAA,WAAA"}
1
+ {"version":3,"file":"Switch.d.ts","names":[],"sources":["../../src/components/Switch.tsx"],"mappings":";;;;;;;;UAyBU,WAAA,SAAoB,IAAA,CAAK,SAAA,YAAqB,oBAAA,CAAqB,YAAA;;EAE3E,GAAA,GAAM,GAAA,CAAI,IAAA;EAFF;EAIR,QAAA,IAAY,KAAA;;EAEZ,QAAA;EAN2E;EAQ3E,QAAA;EANM;;;;;;EAaN,qBAAA;EAfiC;EAiBjC,iBAAA,GAAoB,kBAAA;AAAA;;;;;;;;;;;;;AAAkB;;;;;;;;;;;;;;;;;;;;;cA2ClC,MAAA,EAAM,OAAA,CAAA,oBAAA,CAAA,WAAA"}
@@ -1,11 +1,14 @@
1
1
  /*! © 2026 Yahoo, Inc. UDS Mobile v0.0.0-development */
2
+ import { useMaxFontSizeMultiplier } from "../fontScaling/useMaxFontSizeMultiplier.js";
2
3
  import { IconSlot } from "./IconSlot.js";
4
+ import { useFontScale } from "../fontScaling/useFontScale.js";
3
5
  import { FormLabel } from "./FormLabel.js";
4
6
  import { memo, useCallback, useEffect, useMemo, useState } from "react";
5
7
  import { AccessibilityInfo, I18nManager, Platform, Pressable } from "react-native";
6
8
  import { jsx, jsxs } from "react/jsx-runtime";
7
9
  import { switchStyles } from "../../generated/styles";
8
10
  import { StyleSheet as StyleSheet$1, useUnistyles } from "react-native-unistyles";
11
+ import { ICON_SIZE_MAP } from "@yahoo/uds-icons/tokens";
9
12
  import Animated, { useAnimatedStyle, useDerivedValue, withTiming } from "react-native-reanimated";
10
13
  import { useAnimatedVariantColor } from "react-native-unistyles/reanimated";
11
14
  //#region src/components/Switch.tsx
@@ -47,13 +50,16 @@ const ANIMATION_DURATION = 120;
47
50
  * @see {@link Checkbox} for forms with submit actions
48
51
  * @see {@link Radio} for single-select options
49
52
  */
50
- const Switch = memo(function Switch({ isOn: isOnProp, defaultIsOn = false, onChange, label, labelPosition = "start", size = "md", onIcon, offIcon, disabled = false, required, accessibilityHint, reduceMotion = false, ref, ...viewProps }) {
53
+ const Switch = memo(function Switch({ isOn: isOnProp, defaultIsOn = false, onChange, label, labelPosition = "start", size = "md", onIcon, offIcon, disabled = false, required, maxFontSizeMultiplier, accessibilityHint, reduceMotion = false, ref, ...viewProps }) {
51
54
  const isControlled = isOnProp !== void 0;
52
55
  const [internalIsOn, setInternalIsOn] = useState(defaultIsOn);
53
56
  const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
54
57
  const isOn = isControlled ? isOnProp : internalIsOn;
55
58
  const activeVariant = isOn ? "on" : "off";
56
59
  const { theme } = useUnistyles();
60
+ const resolvedMaxFontSizeMultiplier = useMaxFontSizeMultiplier("control", maxFontSizeMultiplier) ?? 0;
61
+ const fontScaleFactor = useFontScale(resolvedMaxFontSizeMultiplier);
62
+ const scaledHandleIconSize = Math.round(ICON_SIZE_MAP.sm * fontScaleFactor);
57
63
  useEffect(() => {
58
64
  const checkReducedMotion = async () => {
59
65
  setPrefersReducedMotion(await AccessibilityInfo.isReduceMotionEnabled());
@@ -64,7 +70,7 @@ const Switch = memo(function Switch({ isOn: isOnProp, defaultIsOn = false, onCha
64
70
  }, []);
65
71
  const animationDuration = reduceMotion || prefersReducedMotion ? 0 : ANIMATION_DURATION;
66
72
  const progress = useDerivedValue(() => withTiming(isOn ? 1 : 0, { duration: animationDuration }), [isOn, animationDuration]);
67
- const travelDistance = HANDLE_TRAVEL[size] * (I18nManager.isRTL ? -1 : 1);
73
+ const travelDistance = Math.round(HANDLE_TRAVEL[size] * fontScaleFactor) * (I18nManager.isRTL ? -1 : 1);
68
74
  const handlePress = useCallback(() => {
69
75
  if (disabled) return;
70
76
  const newValue = !isOn;
@@ -100,24 +106,36 @@ const Switch = memo(function Switch({ isOn: isOnProp, defaultIsOn = false, onCha
100
106
  return { transform: [{ translateX: progress.value * travelDistance }] };
101
107
  });
102
108
  const rootStyle = useMemo(() => [resolvedSwitchStyles.root, switchStaticStyles.root({ disabled })], [resolvedSwitchStyles.root, disabled]);
109
+ const scaleDimensions = useCallback((style) => {
110
+ if (!style || fontScaleFactor === 1) return;
111
+ const { width, height } = style;
112
+ return typeof width === "number" && typeof height === "number" ? {
113
+ width: Math.round(width * fontScaleFactor),
114
+ height: Math.round(height * fontScaleFactor)
115
+ } : void 0;
116
+ }, [fontScaleFactor]);
103
117
  const trackStyle = useMemo(() => [
104
118
  resolvedSwitchStyles.switch,
105
119
  switchStaticStyles.track,
106
120
  variantLayerStyles.switch,
121
+ scaleDimensions(resolvedSwitchStyles.switch),
107
122
  Platform.OS !== "web" && animatedTrackStyle
108
123
  ], [
109
124
  resolvedSwitchStyles.switch,
110
125
  variantLayerStyles.switch,
126
+ scaleDimensions,
111
127
  animatedTrackStyle
112
128
  ]);
113
129
  const handleStyle = useMemo(() => [
114
130
  resolvedSwitchStyles.handle,
115
131
  switchStaticStyles.handle,
116
132
  variantLayerStyles.handle,
133
+ scaleDimensions(resolvedSwitchStyles.handle),
117
134
  animatedHandleStyle
118
135
  ], [
119
136
  resolvedSwitchStyles.handle,
120
137
  variantLayerStyles.handle,
138
+ scaleDimensions,
121
139
  animatedHandleStyle
122
140
  ]);
123
141
  const accessibilityLabel = typeof label === "string" ? label : void 0;
@@ -128,6 +146,7 @@ const Switch = memo(function Switch({ isOn: isOnProp, defaultIsOn = false, onCha
128
146
  label,
129
147
  required,
130
148
  showRequiredAsterisk: required,
149
+ maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
131
150
  style: [resolvedSwitchStyles.text, variantLayerStyles.text]
132
151
  });
133
152
  const a11yValue = useMemo(() => ({ text: isOn ? "On" : "Off" }), [isOn]);
@@ -158,6 +177,8 @@ const Switch = memo(function Switch({ isOn: isOnProp, defaultIsOn = false, onCha
158
177
  children: /* @__PURE__ */ jsx(IconSlot, {
159
178
  icon: onIcon,
160
179
  variant: "fill",
180
+ dangerouslySetSize: scaledHandleIconSize,
181
+ allowFontScaling: false,
161
182
  style: [resolvedSwitchStyles.handleIcon, variantLayerStyles.handleIcon]
162
183
  })
163
184
  }), offIcon && !isOn && /* @__PURE__ */ jsx(Animated.View, {
@@ -165,6 +186,8 @@ const Switch = memo(function Switch({ isOn: isOnProp, defaultIsOn = false, onCha
165
186
  children: /* @__PURE__ */ jsx(IconSlot, {
166
187
  icon: offIcon,
167
188
  variant: "fill",
189
+ dangerouslySetSize: scaledHandleIconSize,
190
+ allowFontScaling: false,
168
191
  style: [resolvedSwitchStyles.handleIcon, variantLayerStyles.handleIcon]
169
192
  })
170
193
  })]
@@ -1 +1 @@
1
- {"version":3,"file":"Switch.js","names":["StyleSheet"],"sources":["../../src/components/Switch.tsx"],"sourcesContent":["import type { SwitchSize, UniversalSwitchProps } from '@yahoo/uds-types';\nimport type { Ref } from 'react';\nimport { memo, useCallback, useEffect, useMemo, useState } from 'react';\nimport type {\n AccessibilityProps,\n StyleProp,\n TextStyle,\n View,\n ViewProps,\n ViewStyle,\n} from 'react-native';\nimport { AccessibilityInfo, I18nManager, Platform, Pressable } from 'react-native';\nimport Animated, { useAnimatedStyle, useDerivedValue, withTiming } from 'react-native-reanimated';\n// eslint-disable-next-line uds/no-use-unistyles -- switch variant layers need concrete web styles\nimport { StyleSheet, useUnistyles } from 'react-native-unistyles';\nimport { useAnimatedVariantColor } from 'react-native-unistyles/reanimated';\n\nimport { switchStyles } from '../../generated/styles';\nimport { FormLabel } from './FormLabel';\nimport type { IconSlotType } from './IconSlot';\nimport { IconSlot } from './IconSlot';\n\ninterface SwitchProps extends Omit<ViewProps, 'style'>, UniversalSwitchProps<IconSlotType> {\n /** Ref to the underlying View */\n ref?: Ref<View>;\n /** Callback when the switch value changes */\n onChange?: (value: boolean) => void;\n /** Whether the switch is disabled */\n disabled?: boolean;\n /** Whether the switch is required (shows asterisk with label) */\n required?: boolean;\n /** Accessibility hint describing what happens when activated */\n accessibilityHint?: AccessibilityProps['accessibilityHint'];\n}\n\nconst HANDLE_TRAVEL: Record<SwitchSize, number> = {\n md: 20,\n sm: 12,\n};\n\nconst ANIMATION_DURATION = 120;\n\n/**\n * **Switch component for toggling options**\n *\n * @description\n * A switch (also called a toggle) is a binary on/off input control.\n * It allows users to pick between two clearly opposite choices.\n *\n * @category Form\n * @platform mobile\n *\n * @example\n * ```tsx\n * import { Switch } from '@yahoo/uds-mobile/Switch';\n *\n * <Switch label=\"Notifications\" />\n * <Switch isOn={enabled} onChange={setEnabled} label=\"Dark mode\" />\n * <Switch onIcon=\"Check\" offIcon=\"Cross\" label=\"Sync\" />\n * ```\n *\n * @usage\n * - Settings: For toggling preferences on/off\n * - Feature flags: For enabling/disabling features\n * - Immediate effect toggles (no submit button needed)\n *\n * @accessibility\n * - Sets `accessibilityRole=\"switch\"` automatically\n * - Announces on/off state to screen readers\n * - Respects system reduce motion preference\n * - Supports `reduceMotion` prop to disable animations\n *\n * @see {@link Checkbox} for forms with submit actions\n * @see {@link Radio} for single-select options\n */\nconst Switch = memo(function Switch({\n isOn: isOnProp,\n defaultIsOn = false,\n onChange,\n label,\n labelPosition = 'start',\n size = 'md',\n onIcon,\n offIcon,\n disabled = false,\n required,\n accessibilityHint,\n reduceMotion = false,\n ref,\n ...viewProps\n}: SwitchProps) {\n const isControlled = isOnProp !== undefined;\n const [internalIsOn, setInternalIsOn] = useState(defaultIsOn);\n const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);\n const isOn = isControlled ? isOnProp : internalIsOn;\n const activeVariant = isOn ? 'on' : 'off';\n const { theme } = useUnistyles();\n\n // Check system reduced motion preference\n useEffect(() => {\n const checkReducedMotion = async () => {\n const isReduceMotionEnabled = await AccessibilityInfo.isReduceMotionEnabled();\n setPrefersReducedMotion(isReduceMotionEnabled);\n };\n checkReducedMotion();\n\n const subscription = AccessibilityInfo.addEventListener(\n 'reduceMotionChanged',\n setPrefersReducedMotion,\n );\n return () => subscription.remove();\n }, []);\n\n const shouldReduceMotion = reduceMotion || prefersReducedMotion;\n const animationDuration = shouldReduceMotion ? 0 : ANIMATION_DURATION;\n\n const progress = useDerivedValue(\n () => withTiming(isOn ? 1 : 0, { duration: animationDuration }),\n [isOn, animationDuration],\n );\n\n // RTL mirrors the track layout but not transforms, so travel flips sign\n const travelDistance = HANDLE_TRAVEL[size] * (I18nManager.isRTL ? -1 : 1);\n\n const handlePress = useCallback(() => {\n if (disabled) {\n return;\n }\n\n const newValue = !isOn;\n\n if (!isControlled) {\n setInternalIsOn(newValue);\n }\n\n onChange?.(newValue);\n }, [disabled, isOn, isControlled, onChange]);\n\n // On web, useVariants returns the resolved style object instead of mutating\n // switchStyles in place.\n const variantSwitchStyles = switchStyles.useVariants({\n size,\n variant: activeVariant,\n }) as unknown as typeof switchStyles | undefined;\n const resolvedSwitchStyles = variantSwitchStyles ?? switchStyles;\n\n const variantLayerStyles = useMemo(() => {\n const components = theme.components as unknown as Record<string, Record<string, unknown>>;\n const getLayerStyle = <TStyle,>(layer: string) =>\n components[`switch/variant/default/active/${activeVariant}/${layer}/rest`] as\n | TStyle\n | undefined;\n\n return {\n handle: getLayerStyle<ViewStyle>('handle'),\n handleIcon: getLayerStyle<TextStyle>('handleIcon'),\n switch: getLayerStyle<ViewStyle>('switch'),\n text: getLayerStyle<TextStyle>('rootText'),\n };\n }, [activeVariant, theme]);\n\n // Get animated track color from design tokens (changes when variant changes)\n const trackBackgroundColor = useAnimatedVariantColor(\n resolvedSwitchStyles.switch,\n 'backgroundColor',\n );\n\n const animatedTrackStyle = useAnimatedStyle(() => {\n 'worklet';\n return {\n backgroundColor: withTiming(trackBackgroundColor.value, { duration: animationDuration }),\n };\n });\n\n const animatedHandleStyle = useAnimatedStyle(() => {\n 'worklet';\n return {\n transform: [{ translateX: progress.value * travelDistance }],\n };\n });\n\n const rootStyle: StyleProp<ViewStyle> = useMemo(\n () => [resolvedSwitchStyles.root, switchStaticStyles.root({ disabled })],\n [resolvedSwitchStyles.root, disabled],\n );\n\n const trackStyle: StyleProp<ViewStyle> = useMemo(\n () => [\n resolvedSwitchStyles.switch,\n switchStaticStyles.track,\n variantLayerStyles.switch,\n // On web, the animated variant color hook currently resolves to Unistyles'\n // black fallback, so the concrete variant layer provides the track color.\n Platform.OS !== 'web' && animatedTrackStyle,\n ],\n [resolvedSwitchStyles.switch, variantLayerStyles.switch, animatedTrackStyle],\n );\n\n const handleStyle: StyleProp<ViewStyle> = useMemo(\n () => [\n resolvedSwitchStyles.handle,\n switchStaticStyles.handle,\n variantLayerStyles.handle,\n animatedHandleStyle,\n ],\n [resolvedSwitchStyles.handle, variantLayerStyles.handle, animatedHandleStyle],\n );\n\n const accessibilityLabel = typeof label === 'string' ? label : undefined;\n const resolvedAccessibilityHint = accessibilityHint ?? 'Double tap to toggle';\n\n const labelContent = label && (\n <FormLabel\n color=\"inherit\"\n variant=\"inherit\"\n label={label}\n required={required}\n showRequiredAsterisk={required}\n style={[resolvedSwitchStyles.text, variantLayerStyles.text]}\n />\n );\n\n const a11yValue = useMemo(() => ({ text: isOn ? 'On' : 'Off' }), [isOn]);\n\n return (\n <Pressable\n ref={ref}\n onPress={handlePress}\n disabled={disabled}\n accessible\n accessibilityRole=\"switch\"\n accessibilityState={{ checked: isOn, disabled }}\n accessibilityLabel={accessibilityLabel}\n accessibilityHint={resolvedAccessibilityHint}\n accessibilityValue={a11yValue}\n {...viewProps}\n style={rootStyle}\n >\n {labelPosition === 'start' && labelContent}\n\n <Animated.View style={trackStyle} importantForAccessibility=\"no-hide-descendants\">\n <Animated.View style={handleStyle}>\n {onIcon && isOn && (\n <Animated.View style={switchStaticStyles.iconContainer}>\n <IconSlot\n icon={onIcon}\n variant=\"fill\"\n style={[resolvedSwitchStyles.handleIcon, variantLayerStyles.handleIcon]}\n />\n </Animated.View>\n )}\n {offIcon && !isOn && (\n <Animated.View style={switchStaticStyles.iconContainer}>\n <IconSlot\n icon={offIcon}\n variant=\"fill\"\n style={[resolvedSwitchStyles.handleIcon, variantLayerStyles.handleIcon]}\n />\n </Animated.View>\n )}\n </Animated.View>\n </Animated.View>\n\n {labelPosition === 'end' && labelContent}\n </Pressable>\n );\n});\n\nSwitch.displayName = 'Switch';\n\nconst switchStaticStyles = StyleSheet.create((theme) => ({\n handle: {\n borderRadius: theme.borderRadius.full,\n alignItems: 'center',\n justifyContent: 'center',\n },\n iconContainer: {\n position: 'absolute',\n alignItems: 'center',\n justifyContent: 'center',\n },\n track: {\n justifyContent: 'center',\n borderRadius: theme.borderRadius.full,\n },\n root: ({ disabled }: { disabled: boolean }) => ({\n flexDirection: 'row',\n alignItems: 'center',\n alignSelf: 'flex-start',\n opacity: disabled ? 0.5 : 1,\n }),\n}));\n\nexport { Switch, type SwitchProps };\n"],"mappings":"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"}
1
+ {"version":3,"file":"Switch.js","names":["StyleSheet"],"sources":["../../src/components/Switch.tsx"],"sourcesContent":["import { ICON_SIZE_MAP } from '@yahoo/uds-icons/tokens';\nimport type { SwitchSize, UniversalSwitchProps } from '@yahoo/uds-types';\nimport type { Ref } from 'react';\nimport { memo, useCallback, useEffect, useMemo, useState } from 'react';\nimport type {\n AccessibilityProps,\n StyleProp,\n TextStyle,\n View,\n ViewProps,\n ViewStyle,\n} from 'react-native';\nimport { AccessibilityInfo, I18nManager, Platform, Pressable } from 'react-native';\nimport Animated, { useAnimatedStyle, useDerivedValue, withTiming } from 'react-native-reanimated';\n// eslint-disable-next-line uds/no-use-unistyles -- switch variant layers need concrete web styles\nimport { StyleSheet, useUnistyles } from 'react-native-unistyles';\nimport { useAnimatedVariantColor } from 'react-native-unistyles/reanimated';\n\nimport { switchStyles } from '../../generated/styles';\nimport { useFontScale } from '../fontScaling/useFontScale';\nimport { useMaxFontSizeMultiplier } from '../fontScaling/useMaxFontSizeMultiplier';\nimport { FormLabel } from './FormLabel';\nimport type { IconSlotType } from './IconSlot';\nimport { IconSlot } from './IconSlot';\n\ninterface SwitchProps extends Omit<ViewProps, 'style'>, UniversalSwitchProps<IconSlotType> {\n /** Ref to the underlying View */\n ref?: Ref<View>;\n /** Callback when the switch value changes */\n onChange?: (value: boolean) => void;\n /** Whether the switch is disabled */\n disabled?: boolean;\n /** Whether the switch is required (shows asterisk with label) */\n required?: boolean;\n /**\n * Caps how far the track, handle, and label grow with the OS text-size\n * setting; all scale by the same factor. Set null to remove the cap;\n * override app-wide via UDSFontScalingProvider.\n * @default 2\n */\n maxFontSizeMultiplier?: number | null;\n /** Accessibility hint describing what happens when activated */\n accessibilityHint?: AccessibilityProps['accessibilityHint'];\n}\n\nconst HANDLE_TRAVEL: Record<SwitchSize, number> = {\n md: 20,\n sm: 12,\n};\n\nconst ANIMATION_DURATION = 120;\n\n/**\n * **Switch component for toggling options**\n *\n * @description\n * A switch (also called a toggle) is a binary on/off input control.\n * It allows users to pick between two clearly opposite choices.\n *\n * @category Form\n * @platform mobile\n *\n * @example\n * ```tsx\n * import { Switch } from '@yahoo/uds-mobile/Switch';\n *\n * <Switch label=\"Notifications\" />\n * <Switch isOn={enabled} onChange={setEnabled} label=\"Dark mode\" />\n * <Switch onIcon=\"Check\" offIcon=\"Cross\" label=\"Sync\" />\n * ```\n *\n * @usage\n * - Settings: For toggling preferences on/off\n * - Feature flags: For enabling/disabling features\n * - Immediate effect toggles (no submit button needed)\n *\n * @accessibility\n * - Sets `accessibilityRole=\"switch\"` automatically\n * - Announces on/off state to screen readers\n * - Respects system reduce motion preference\n * - Supports `reduceMotion` prop to disable animations\n *\n * @see {@link Checkbox} for forms with submit actions\n * @see {@link Radio} for single-select options\n */\nconst Switch = memo(function Switch({\n isOn: isOnProp,\n defaultIsOn = false,\n onChange,\n label,\n labelPosition = 'start',\n size = 'md',\n onIcon,\n offIcon,\n disabled = false,\n required,\n maxFontSizeMultiplier,\n accessibilityHint,\n reduceMotion = false,\n ref,\n ...viewProps\n}: SwitchProps) {\n const isControlled = isOnProp !== undefined;\n const [internalIsOn, setInternalIsOn] = useState(defaultIsOn);\n const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);\n const isOn = isControlled ? isOnProp : internalIsOn;\n const activeVariant = isOn ? 'on' : 'off';\n const { theme } = useUnistyles();\n\n // One capped factor governs track, handle, travel, and label.\n const resolvedMaxFontSizeMultiplier =\n useMaxFontSizeMultiplier('control', maxFontSizeMultiplier) ?? 0;\n const fontScaleFactor = useFontScale(resolvedMaxFontSizeMultiplier);\n // Size the handle glyph from the same factor as the handle it sits in, rather\n // than letting native font scaling size it on a separate path.\n const scaledHandleIconSize = Math.round(ICON_SIZE_MAP.sm * fontScaleFactor);\n\n // Check system reduced motion preference\n useEffect(() => {\n const checkReducedMotion = async () => {\n const isReduceMotionEnabled = await AccessibilityInfo.isReduceMotionEnabled();\n setPrefersReducedMotion(isReduceMotionEnabled);\n };\n checkReducedMotion();\n\n const subscription = AccessibilityInfo.addEventListener(\n 'reduceMotionChanged',\n setPrefersReducedMotion,\n );\n return () => subscription.remove();\n }, []);\n\n const shouldReduceMotion = reduceMotion || prefersReducedMotion;\n const animationDuration = shouldReduceMotion ? 0 : ANIMATION_DURATION;\n\n const progress = useDerivedValue(\n () => withTiming(isOn ? 1 : 0, { duration: animationDuration }),\n [isOn, animationDuration],\n );\n\n // RTL mirrors the track layout but not transforms, so travel flips sign.\n // Travel scales with the track/handle so the handle still lands flush.\n const travelDistance =\n Math.round(HANDLE_TRAVEL[size] * fontScaleFactor) * (I18nManager.isRTL ? -1 : 1);\n\n const handlePress = useCallback(() => {\n if (disabled) {\n return;\n }\n\n const newValue = !isOn;\n\n if (!isControlled) {\n setInternalIsOn(newValue);\n }\n\n onChange?.(newValue);\n }, [disabled, isOn, isControlled, onChange]);\n\n // On web, useVariants returns the resolved style object instead of mutating\n // switchStyles in place.\n const variantSwitchStyles = switchStyles.useVariants({\n size,\n variant: activeVariant,\n }) as unknown as typeof switchStyles | undefined;\n const resolvedSwitchStyles = variantSwitchStyles ?? switchStyles;\n\n const variantLayerStyles = useMemo(() => {\n const components = theme.components as unknown as Record<string, Record<string, unknown>>;\n const getLayerStyle = <TStyle,>(layer: string) =>\n components[`switch/variant/default/active/${activeVariant}/${layer}/rest`] as\n TStyle | undefined;\n\n return {\n handle: getLayerStyle<ViewStyle>('handle'),\n handleIcon: getLayerStyle<TextStyle>('handleIcon'),\n switch: getLayerStyle<ViewStyle>('switch'),\n text: getLayerStyle<TextStyle>('rootText'),\n };\n }, [activeVariant, theme]);\n\n // Get animated track color from design tokens (changes when variant changes)\n const trackBackgroundColor = useAnimatedVariantColor(\n resolvedSwitchStyles.switch,\n 'backgroundColor',\n );\n\n const animatedTrackStyle = useAnimatedStyle(() => {\n 'worklet';\n return {\n backgroundColor: withTiming(trackBackgroundColor.value, { duration: animationDuration }),\n };\n });\n\n const animatedHandleStyle = useAnimatedStyle(() => {\n 'worklet';\n return {\n transform: [{ translateX: progress.value * travelDistance }],\n };\n });\n\n const rootStyle: StyleProp<ViewStyle> = useMemo(\n () => [resolvedSwitchStyles.root, switchStaticStyles.root({ disabled })],\n [resolvedSwitchStyles.root, disabled],\n );\n\n // Track and handle grow by the capped factor so the handle (and its icon,\n // which native scales by the same capped multiplier) stays contained.\n const scaleDimensions = useCallback(\n (style: ViewStyle | undefined): ViewStyle | undefined => {\n if (!style || fontScaleFactor === 1) {\n return undefined;\n }\n const { width, height } = style;\n return typeof width === 'number' && typeof height === 'number'\n ? {\n width: Math.round(width * fontScaleFactor),\n height: Math.round(height * fontScaleFactor),\n }\n : undefined;\n },\n [fontScaleFactor],\n );\n\n const trackStyle: StyleProp<ViewStyle> = useMemo(\n () => [\n resolvedSwitchStyles.switch,\n switchStaticStyles.track,\n variantLayerStyles.switch,\n scaleDimensions(resolvedSwitchStyles.switch as ViewStyle),\n // On web, the animated variant color hook currently resolves to Unistyles'\n // black fallback, so the concrete variant layer provides the track color.\n Platform.OS !== 'web' && animatedTrackStyle,\n ],\n [resolvedSwitchStyles.switch, variantLayerStyles.switch, scaleDimensions, animatedTrackStyle],\n );\n\n const handleStyle: StyleProp<ViewStyle> = useMemo(\n () => [\n resolvedSwitchStyles.handle,\n switchStaticStyles.handle,\n variantLayerStyles.handle,\n scaleDimensions(resolvedSwitchStyles.handle as ViewStyle),\n animatedHandleStyle,\n ],\n [resolvedSwitchStyles.handle, variantLayerStyles.handle, scaleDimensions, animatedHandleStyle],\n );\n\n const accessibilityLabel = typeof label === 'string' ? label : undefined;\n const resolvedAccessibilityHint = accessibilityHint ?? 'Double tap to toggle';\n\n const labelContent = label && (\n <FormLabel\n color=\"inherit\"\n variant=\"inherit\"\n label={label}\n required={required}\n showRequiredAsterisk={required}\n maxFontSizeMultiplier={resolvedMaxFontSizeMultiplier}\n style={[resolvedSwitchStyles.text, variantLayerStyles.text]}\n />\n );\n\n const a11yValue = useMemo(() => ({ text: isOn ? 'On' : 'Off' }), [isOn]);\n\n return (\n <Pressable\n ref={ref}\n onPress={handlePress}\n disabled={disabled}\n accessible\n accessibilityRole=\"switch\"\n accessibilityState={{ checked: isOn, disabled }}\n accessibilityLabel={accessibilityLabel}\n accessibilityHint={resolvedAccessibilityHint}\n accessibilityValue={a11yValue}\n {...viewProps}\n style={rootStyle}\n >\n {labelPosition === 'start' && labelContent}\n\n <Animated.View style={trackStyle} importantForAccessibility=\"no-hide-descendants\">\n <Animated.View style={handleStyle}>\n {onIcon && isOn && (\n <Animated.View style={switchStaticStyles.iconContainer}>\n <IconSlot\n icon={onIcon}\n variant=\"fill\"\n dangerouslySetSize={scaledHandleIconSize}\n allowFontScaling={false}\n style={[resolvedSwitchStyles.handleIcon, variantLayerStyles.handleIcon]}\n />\n </Animated.View>\n )}\n {offIcon && !isOn && (\n <Animated.View style={switchStaticStyles.iconContainer}>\n <IconSlot\n icon={offIcon}\n variant=\"fill\"\n dangerouslySetSize={scaledHandleIconSize}\n allowFontScaling={false}\n style={[resolvedSwitchStyles.handleIcon, variantLayerStyles.handleIcon]}\n />\n </Animated.View>\n )}\n </Animated.View>\n </Animated.View>\n\n {labelPosition === 'end' && labelContent}\n </Pressable>\n );\n});\n\nSwitch.displayName = 'Switch';\n\nconst switchStaticStyles = StyleSheet.create((theme) => ({\n handle: {\n borderRadius: theme.borderRadius.full,\n alignItems: 'center',\n justifyContent: 'center',\n },\n iconContainer: {\n position: 'absolute',\n alignItems: 'center',\n justifyContent: 'center',\n },\n track: {\n justifyContent: 'center',\n borderRadius: theme.borderRadius.full,\n },\n root: ({ disabled }: { disabled: boolean }) => ({\n flexDirection: 'row',\n alignItems: 'center',\n alignSelf: 'flex-start',\n opacity: disabled ? 0.5 : 1,\n }),\n}));\n\nexport { Switch, type SwitchProps };\n"],"mappings":"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"}