@yahoo/uds-mobile 2.23.3 → 2.24.0-beta.10
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.
- package/README.md +41 -13
- package/dist/bin/automated-config/dist/generated/generatedConfigs.mjs +92 -46
- package/dist/components/Avatar/AvatarIcon.cjs +8 -2
- package/dist/components/Avatar/AvatarIcon.d.cts.map +1 -1
- package/dist/components/Avatar/AvatarIcon.d.ts.map +1 -1
- package/dist/components/Avatar/AvatarIcon.js +9 -3
- package/dist/components/Avatar/AvatarIcon.js.map +1 -1
- package/dist/components/Avatar/AvatarImage.cjs +9 -2
- package/dist/components/Avatar/AvatarImage.d.cts.map +1 -1
- package/dist/components/Avatar/AvatarImage.d.ts.map +1 -1
- package/dist/components/Avatar/AvatarImage.js +10 -3
- package/dist/components/Avatar/AvatarImage.js.map +1 -1
- package/dist/components/Avatar/AvatarText.cjs +8 -2
- package/dist/components/Avatar/AvatarText.d.cts.map +1 -1
- package/dist/components/Avatar/AvatarText.d.ts.map +1 -1
- package/dist/components/Avatar/AvatarText.js +9 -3
- package/dist/components/Avatar/AvatarText.js.map +1 -1
- package/dist/components/Avatar/types.d.cts +7 -3
- package/dist/components/Avatar/types.d.cts.map +1 -1
- package/dist/components/Avatar/types.d.ts +7 -3
- package/dist/components/Avatar/types.d.ts.map +1 -1
- package/dist/components/Avatar/useAvatarFontScale.cjs +16 -0
- package/dist/components/Avatar/useAvatarFontScale.d.cts +7 -0
- package/dist/components/Avatar/useAvatarFontScale.d.cts.map +1 -0
- package/dist/components/Avatar/useAvatarFontScale.d.ts +7 -0
- package/dist/components/Avatar/useAvatarFontScale.d.ts.map +1 -0
- package/dist/components/Avatar/useAvatarFontScale.js +16 -0
- package/dist/components/Avatar/useAvatarFontScale.js.map +1 -0
- package/dist/components/Avatar/utils.cjs +9 -6
- package/dist/components/Avatar/utils.d.cts +2 -2
- package/dist/components/Avatar/utils.d.cts.map +1 -1
- package/dist/components/Avatar/utils.d.ts +2 -2
- package/dist/components/Avatar/utils.d.ts.map +1 -1
- package/dist/components/Avatar/utils.js +9 -6
- package/dist/components/Avatar/utils.js.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.cjs +1 -1
- package/dist/components/BottomSheet/BottomSheet.js +1 -1
- package/dist/components/Button/buttonTheme.cjs +31 -9
- package/dist/components/Button/buttonTheme.d.cts +24 -5
- package/dist/components/Button/buttonTheme.d.cts.map +1 -1
- package/dist/components/Button/buttonTheme.d.ts +24 -5
- package/dist/components/Button/buttonTheme.d.ts.map +1 -1
- package/dist/components/Button/buttonTheme.js +31 -10
- package/dist/components/Button/buttonTheme.js.map +1 -1
- package/dist/components/Button.cjs +35 -8
- package/dist/components/Button.d.cts +7 -0
- package/dist/components/Button.d.cts.map +1 -1
- package/dist/components/Button.d.ts +7 -0
- package/dist/components/Button.d.ts.map +1 -1
- package/dist/components/Button.js +35 -8
- package/dist/components/Button.js.map +1 -1
- package/dist/components/Checkbox.cjs +42 -9
- package/dist/components/Checkbox.d.cts +7 -0
- package/dist/components/Checkbox.d.cts.map +1 -1
- package/dist/components/Checkbox.d.ts +7 -0
- package/dist/components/Checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox.js +42 -9
- package/dist/components/Checkbox.js.map +1 -1
- package/dist/components/Chip/ChipBase.cjs +31 -14
- package/dist/components/Chip/ChipBase.d.cts.map +1 -1
- package/dist/components/Chip/ChipBase.d.ts.map +1 -1
- package/dist/components/Chip/ChipBase.js +31 -14
- package/dist/components/Chip/ChipBase.js.map +1 -1
- package/dist/components/Chip/ChipDismissible.cjs +3 -1
- package/dist/components/Chip/ChipDismissible.js +3 -1
- package/dist/components/Chip/ChipDismissible.js.map +1 -1
- package/dist/components/Chip/types.d.cts +7 -0
- package/dist/components/Chip/types.d.cts.map +1 -1
- package/dist/components/Chip/types.d.ts +7 -0
- package/dist/components/Chip/types.d.ts.map +1 -1
- package/dist/components/Icon.cjs +24 -5
- package/dist/components/Icon.d.cts +11 -2
- package/dist/components/Icon.d.cts.map +1 -1
- package/dist/components/Icon.d.ts +11 -2
- package/dist/components/Icon.d.ts.map +1 -1
- package/dist/components/Icon.js +24 -5
- package/dist/components/Icon.js.map +1 -1
- package/dist/components/IconButton.cjs +17 -6
- package/dist/components/IconButton.d.cts +6 -0
- package/dist/components/IconButton.d.cts.map +1 -1
- package/dist/components/IconButton.d.ts +6 -0
- package/dist/components/IconButton.d.ts.map +1 -1
- package/dist/components/IconButton.js +18 -7
- package/dist/components/IconButton.js.map +1 -1
- package/dist/components/Input.cjs +56 -18
- package/dist/components/Input.d.cts +5 -0
- package/dist/components/Input.d.cts.map +1 -1
- package/dist/components/Input.d.ts +5 -0
- package/dist/components/Input.d.ts.map +1 -1
- package/dist/components/Input.js +56 -18
- package/dist/components/Input.js.map +1 -1
- package/dist/components/InputHelpText.cjs +7 -1
- package/dist/components/InputHelpText.d.cts +2 -0
- package/dist/components/InputHelpText.d.cts.map +1 -1
- package/dist/components/InputHelpText.d.ts +2 -0
- package/dist/components/InputHelpText.d.ts.map +1 -1
- package/dist/components/InputHelpText.js +7 -1
- package/dist/components/InputHelpText.js.map +1 -1
- package/dist/components/Link.cjs +13 -1
- package/dist/components/Link.d.cts +7 -0
- package/dist/components/Link.d.cts.map +1 -1
- package/dist/components/Link.d.ts +7 -0
- package/dist/components/Link.d.ts.map +1 -1
- package/dist/components/Link.js +13 -1
- package/dist/components/Link.js.map +1 -1
- package/dist/components/Pagination/Pagination.cjs +18 -10
- package/dist/components/Pagination/Pagination.d.cts +2 -0
- package/dist/components/Pagination/Pagination.d.cts.map +1 -1
- package/dist/components/Pagination/Pagination.d.ts +2 -0
- package/dist/components/Pagination/Pagination.d.ts.map +1 -1
- package/dist/components/Pagination/Pagination.js +18 -10
- package/dist/components/Pagination/Pagination.js.map +1 -1
- package/dist/components/Pagination/PaginationEllipsis.cjs +8 -2
- package/dist/components/Pagination/PaginationEllipsis.d.cts.map +1 -1
- package/dist/components/Pagination/PaginationEllipsis.d.ts.map +1 -1
- package/dist/components/Pagination/PaginationEllipsis.js +9 -3
- package/dist/components/Pagination/PaginationEllipsis.js.map +1 -1
- package/dist/components/Pagination/PaginationItem.cjs +2 -1
- package/dist/components/Pagination/PaginationItem.js +2 -1
- package/dist/components/Pagination/PaginationItem.js.map +1 -1
- package/dist/components/Pagination/PaginationLink.cjs +2 -1
- package/dist/components/Pagination/PaginationLink.js +2 -1
- package/dist/components/Pagination/PaginationLink.js.map +1 -1
- package/dist/components/Pagination/PaginationNext.cjs +3 -1
- package/dist/components/Pagination/PaginationNext.js +3 -1
- package/dist/components/Pagination/PaginationNext.js.map +1 -1
- package/dist/components/Pagination/PaginationPrev.cjs +3 -1
- package/dist/components/Pagination/PaginationPrev.js +3 -1
- package/dist/components/Pagination/PaginationPrev.js.map +1 -1
- package/dist/components/Pagination/paginationContext.d.cts +2 -0
- package/dist/components/Pagination/paginationContext.d.cts.map +1 -1
- package/dist/components/Pagination/paginationContext.d.ts +2 -0
- package/dist/components/Pagination/paginationContext.d.ts.map +1 -1
- package/dist/components/Pagination/paginationContext.js.map +1 -1
- package/dist/components/Pagination/usePaginationControlStyles.cjs +8 -1
- package/dist/components/Pagination/usePaginationControlStyles.d.cts.map +1 -1
- package/dist/components/Pagination/usePaginationControlStyles.d.ts.map +1 -1
- package/dist/components/Pagination/usePaginationControlStyles.js +9 -2
- package/dist/components/Pagination/usePaginationControlStyles.js.map +1 -1
- package/dist/components/Radio/Radio.cjs +30 -11
- package/dist/components/Radio/Radio.d.cts +7 -0
- package/dist/components/Radio/Radio.d.cts.map +1 -1
- package/dist/components/Radio/Radio.d.ts +7 -0
- package/dist/components/Radio/Radio.d.ts.map +1 -1
- package/dist/components/Radio/Radio.js +30 -11
- package/dist/components/Radio/Radio.js.map +1 -1
- package/dist/components/Select/Select.cjs +29 -6
- package/dist/components/Select/Select.d.cts +2 -0
- package/dist/components/Select/Select.d.cts.map +1 -1
- package/dist/components/Select/Select.d.ts +2 -0
- package/dist/components/Select/Select.d.ts.map +1 -1
- package/dist/components/Select/Select.js +29 -6
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Select/SelectContent.cjs +31 -23
- package/dist/components/Select/SelectContent.d.cts.map +1 -1
- package/dist/components/Select/SelectContent.d.ts.map +1 -1
- package/dist/components/Select/SelectContent.js +32 -24
- package/dist/components/Select/SelectContent.js.map +1 -1
- package/dist/components/Select/SelectItem.cjs +25 -15
- package/dist/components/Select/SelectItem.d.cts.map +1 -1
- package/dist/components/Select/SelectItem.d.ts.map +1 -1
- package/dist/components/Select/SelectItem.js +25 -15
- package/dist/components/Select/SelectItem.js.map +1 -1
- package/dist/components/Select/SelectTrigger.cjs +27 -13
- package/dist/components/Select/SelectTrigger.d.cts.map +1 -1
- package/dist/components/Select/SelectTrigger.d.ts.map +1 -1
- package/dist/components/Select/SelectTrigger.js +27 -13
- package/dist/components/Select/SelectTrigger.js.map +1 -1
- package/dist/components/Select/types.d.cts +2 -0
- package/dist/components/Select/types.d.cts.map +1 -1
- package/dist/components/Select/types.d.ts +2 -0
- package/dist/components/Select/types.d.ts.map +1 -1
- package/dist/components/Switch.cjs +48 -8
- package/dist/components/Switch.d.cts +7 -0
- package/dist/components/Switch.d.cts.map +1 -1
- package/dist/components/Switch.d.ts +7 -0
- package/dist/components/Switch.d.ts.map +1 -1
- package/dist/components/Switch.js +48 -8
- package/dist/components/Switch.js.map +1 -1
- package/dist/components/Tabs/Tab.cjs +17 -3
- package/dist/components/Tabs/Tab.d.cts.map +1 -1
- package/dist/components/Tabs/Tab.d.ts.map +1 -1
- package/dist/components/Tabs/Tab.js +17 -3
- package/dist/components/Tabs/Tab.js.map +1 -1
- package/dist/components/Tabs/Tabs.cjs +8 -3
- package/dist/components/Tabs/Tabs.d.cts +9 -1
- package/dist/components/Tabs/Tabs.d.cts.map +1 -1
- package/dist/components/Tabs/Tabs.d.ts +9 -1
- package/dist/components/Tabs/Tabs.d.ts.map +1 -1
- package/dist/components/Tabs/Tabs.js +8 -3
- package/dist/components/Tabs/Tabs.js.map +1 -1
- package/dist/components/Tabs/tabsContexts.d.cts +2 -0
- package/dist/components/Tabs/tabsContexts.d.cts.map +1 -1
- package/dist/components/Tabs/tabsContexts.d.ts +2 -0
- package/dist/components/Tabs/tabsContexts.d.ts.map +1 -1
- package/dist/components/Tabs/tabsContexts.js.map +1 -1
- package/dist/components/Text.cjs +9 -1
- package/dist/components/Text.d.cts +11 -2
- package/dist/components/Text.d.cts.map +1 -1
- package/dist/components/Text.d.ts +11 -2
- package/dist/components/Text.d.ts.map +1 -1
- package/dist/components/Text.js +9 -1
- package/dist/components/Text.js.map +1 -1
- package/dist/components/UDSProvider.cjs +6 -2
- package/dist/components/UDSProvider.d.cts +9 -0
- package/dist/components/UDSProvider.d.cts.map +1 -1
- package/dist/components/UDSProvider.d.ts +9 -0
- package/dist/components/UDSProvider.d.ts.map +1 -1
- package/dist/components/UDSProvider.js +6 -2
- package/dist/components/UDSProvider.js.map +1 -1
- package/dist/components/internal/Overlay/index.cjs +1 -1
- package/dist/components/internal/Overlay/index.js +1 -1
- package/dist/fontScaling/FontScalingContext.cjs +67 -0
- package/dist/fontScaling/FontScalingContext.d.cts +53 -0
- package/dist/fontScaling/FontScalingContext.d.cts.map +1 -0
- package/dist/fontScaling/FontScalingContext.d.ts +53 -0
- package/dist/fontScaling/FontScalingContext.d.ts.map +1 -0
- package/dist/fontScaling/FontScalingContext.js +63 -0
- package/dist/fontScaling/FontScalingContext.js.map +1 -0
- package/dist/fontScaling/androidNonlinearFontScaling.cjs +250 -0
- package/dist/fontScaling/androidNonlinearFontScaling.d.cts +7 -0
- package/dist/fontScaling/androidNonlinearFontScaling.d.cts.map +1 -0
- package/dist/fontScaling/androidNonlinearFontScaling.d.ts +7 -0
- package/dist/fontScaling/androidNonlinearFontScaling.d.ts.map +1 -0
- package/dist/fontScaling/androidNonlinearFontScaling.js +251 -0
- package/dist/fontScaling/androidNonlinearFontScaling.js.map +1 -0
- package/dist/fontScaling/constants.cjs +106 -0
- package/dist/fontScaling/constants.d.cts +60 -0
- package/dist/fontScaling/constants.d.cts.map +1 -0
- package/dist/fontScaling/constants.d.ts +60 -0
- package/dist/fontScaling/constants.d.ts.map +1 -0
- package/dist/fontScaling/constants.js +103 -0
- package/dist/fontScaling/constants.js.map +1 -0
- package/dist/fontScaling/index.cjs +14 -0
- package/dist/fontScaling/index.d.cts +8 -0
- package/dist/fontScaling/index.d.ts +8 -0
- package/dist/fontScaling/index.js +6 -0
- package/dist/fontScaling/resolveMaxFontSizeMultiplier.cjs +37 -0
- package/dist/fontScaling/resolveMaxFontSizeMultiplier.d.cts +35 -0
- package/dist/fontScaling/resolveMaxFontSizeMultiplier.d.cts.map +1 -0
- package/dist/fontScaling/resolveMaxFontSizeMultiplier.d.ts +35 -0
- package/dist/fontScaling/resolveMaxFontSizeMultiplier.d.ts.map +1 -0
- package/dist/fontScaling/resolveMaxFontSizeMultiplier.js +38 -0
- package/dist/fontScaling/resolveMaxFontSizeMultiplier.js.map +1 -0
- package/dist/fontScaling/types.cjs +1 -0
- package/dist/fontScaling/types.d.cts +54 -0
- package/dist/fontScaling/types.d.cts.map +1 -0
- package/dist/fontScaling/types.d.ts +54 -0
- package/dist/fontScaling/types.d.ts.map +1 -0
- package/dist/fontScaling/types.js +1 -0
- package/dist/fontScaling/useFontScale.cjs +58 -0
- package/dist/fontScaling/useFontScale.d.cts +12 -0
- package/dist/fontScaling/useFontScale.d.cts.map +1 -0
- package/dist/fontScaling/useFontScale.d.ts +12 -0
- package/dist/fontScaling/useFontScale.d.ts.map +1 -0
- package/dist/fontScaling/useFontScale.js +57 -0
- package/dist/fontScaling/useFontScale.js.map +1 -0
- package/dist/fontScaling/useMaxFontSizeMultiplier.cjs +24 -0
- package/dist/fontScaling/useMaxFontSizeMultiplier.d.cts +16 -0
- package/dist/fontScaling/useMaxFontSizeMultiplier.d.cts.map +1 -0
- package/dist/fontScaling/useMaxFontSizeMultiplier.d.ts +16 -0
- package/dist/fontScaling/useMaxFontSizeMultiplier.d.ts.map +1 -0
- package/dist/fontScaling/useMaxFontSizeMultiplier.js +24 -0
- package/dist/fontScaling/useMaxFontSizeMultiplier.js.map +1 -0
- package/dist/jest/mocks/react-native.cjs +19 -4
- package/dist/jest/mocks/react-native.d.cts +8 -5
- package/dist/jest/mocks/react-native.d.cts.map +1 -1
- package/dist/jest/mocks/react-native.d.ts +8 -5
- package/dist/jest/mocks/react-native.d.ts.map +1 -1
- package/dist/jest/mocks/react-native.js +18 -5
- package/dist/jest/mocks/react-native.js.map +1 -1
- package/dist/jest/mocks/styles.cjs +28 -1
- package/dist/jest/mocks/styles.d.cts.map +1 -1
- package/dist/jest/mocks/styles.d.ts.map +1 -1
- package/dist/jest/mocks/styles.js +28 -1
- package/dist/jest/mocks/styles.js.map +1 -1
- package/dist/jest/setup.cjs +4 -0
- package/dist/jest/setup.d.cts.map +1 -1
- package/dist/jest/setup.d.ts.map +1 -1
- package/dist/jest/setup.js +4 -0
- package/dist/jest/setup.js.map +1 -1
- package/dist/mobile/generated/fontMetrics.cjs +530 -530
- package/dist/mobile/generated/fontMetrics.js +530 -530
- package/dist/mobile/generated/fontMetrics.js.map +1 -1
- package/dist/portal.cjs +8 -2
- package/dist/portal.d.cts.map +1 -1
- package/dist/portal.d.ts.map +1 -1
- package/dist/portal.js +8 -2
- package/dist/portal.js.map +1 -1
- package/dist/types/dist/index.d.cts.map +1 -1
- package/dist/types/dist/index.d.ts.map +1 -1
- package/fonts/index.cjs +214 -214
- package/fonts/index.mjs +214 -214
- package/fonts/uds-icons.ttf +0 -0
- package/generated/fontMetrics.ts +530 -530
- package/generated/styles.d.ts +5 -28
- package/generated/unistyles.d.ts +0 -27
- package/package.json +11 -1
|
@@ -1,7 +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 {
|
|
4
|
+
import { useComponentFontScale } from "../fontScaling/useFontScale.js";
|
|
5
|
+
import { useMaxFontSizeMultiplier } from "../fontScaling/useMaxFontSizeMultiplier.js";
|
|
6
|
+
import { getIconButtonControlMetrics, getIconButtonIconSize } from "./Button/buttonTheme.js";
|
|
5
7
|
import { Icon } from "./Icon.js";
|
|
6
8
|
import { AnimatedPressable } from "./Pressable.js";
|
|
7
9
|
import { memo, useCallback, useMemo, useState } from "react";
|
|
@@ -54,12 +56,22 @@ function interpolateShadowAlpha(shadow, alpha) {
|
|
|
54
56
|
* @see {@link Button} for buttons with text labels
|
|
55
57
|
* @see {@link Icon} for non-interactive icons
|
|
56
58
|
*/
|
|
57
|
-
const IconButton = memo(function IconButton({ name, variant = "primary", size = "md", iconVariant = "outline", iconColor, loading, disabled, style, accessibilityLabel, accessibilityHint, disableEffects = false, onPressIn, onPressOut, ref, ...props }) {
|
|
59
|
+
const IconButton = memo(function IconButton({ name, variant = "primary", size = "md", iconVariant = "outline", iconColor, loading, disabled, style, accessibilityLabel, accessibilityHint, disableEffects = false, maxFontSizeMultiplier, onPressIn, onPressOut, ref, ...props }) {
|
|
58
60
|
const isDisabled = disabled || loading;
|
|
59
61
|
const shouldAnimate = !disableEffects && !isDisabled;
|
|
60
62
|
const shouldAnimateVariantColors = Platform.OS !== "web";
|
|
61
63
|
const { theme } = useUnistyles();
|
|
62
|
-
const
|
|
64
|
+
const resolvedMaxFontSizeMultiplier = useMaxFontSizeMultiplier("control", maxFontSizeMultiplier);
|
|
65
|
+
const [pressed, setPressed] = useState(false);
|
|
66
|
+
const resolvedIconButtonStyles = iconButtonStyles.useVariants({ size }) ?? iconButtonStyles;
|
|
67
|
+
const iconBaseSize = useMemo(() => getIconButtonIconSize(theme, size), [theme, size]);
|
|
68
|
+
const iconScaleFactor = useComponentFontScale(resolvedMaxFontSizeMultiplier, iconBaseSize);
|
|
69
|
+
const { minHeight, iconCellHeight, paddingVertical, hasExplicitHeight } = useMemo(() => getIconButtonControlMetrics(theme, size, iconScaleFactor), [
|
|
70
|
+
theme,
|
|
71
|
+
size,
|
|
72
|
+
iconScaleFactor
|
|
73
|
+
]);
|
|
74
|
+
const scaledIconSize = Math.round(iconBaseSize * iconScaleFactor);
|
|
63
75
|
const controlDimensions = hasExplicitHeight ? {
|
|
64
76
|
height: "auto",
|
|
65
77
|
width: "auto",
|
|
@@ -70,8 +82,6 @@ const IconButton = memo(function IconButton({ name, variant = "primary", size =
|
|
|
70
82
|
minHeight,
|
|
71
83
|
paddingVertical
|
|
72
84
|
};
|
|
73
|
-
const [pressed, setPressed] = useState(false);
|
|
74
|
-
const resolvedIconButtonStyles = iconButtonStyles.useVariants({ size }) ?? iconButtonStyles;
|
|
75
85
|
const resolvedButtonStyles = buttonStyles.useVariants({
|
|
76
86
|
variant,
|
|
77
87
|
disabled: isDisabled,
|
|
@@ -149,7 +159,7 @@ const IconButton = memo(function IconButton({ name, variant = "primary", size =
|
|
|
149
159
|
],
|
|
150
160
|
...props,
|
|
151
161
|
children: loading ? /* @__PURE__ */ jsx(ActivityIndicator, {
|
|
152
|
-
size:
|
|
162
|
+
size: scaledIconSize,
|
|
153
163
|
color: resolvedIconColor ?? resolvedButtonStyles.icon.color
|
|
154
164
|
}) : /* @__PURE__ */ jsx(Icon, {
|
|
155
165
|
name,
|
|
@@ -159,7 +169,8 @@ const IconButton = memo(function IconButton({ name, variant = "primary", size =
|
|
|
159
169
|
resolvedButtonStyles.icon,
|
|
160
170
|
{ lineHeight: iconCellHeight }
|
|
161
171
|
],
|
|
162
|
-
dangerouslySetColor: resolvedIconColor
|
|
172
|
+
dangerouslySetColor: resolvedIconColor,
|
|
173
|
+
maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier
|
|
163
174
|
})
|
|
164
175
|
});
|
|
165
176
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IconButton.js","names":["foundationStyles"],"sources":["../../src/components/IconButton.tsx"],"sourcesContent":["import type { ButtonVariantFlat, IconButtonSize, IconVariant } from '@yahoo/uds-types';\nimport type { Ref } from 'react';\nimport { memo, useCallback, useMemo, useState } from 'react';\nimport type { View } from 'react-native';\nimport { ActivityIndicator, Platform } from 'react-native';\nimport {\n Easing,\n useAnimatedStyle,\n useDerivedValue,\n useSharedValue,\n withSpring,\n withTiming,\n} from 'react-native-reanimated';\n// eslint-disable-next-line uds/no-use-unistyles -- iconbutton control height from theme size layers\nimport { useUnistyles } from 'react-native-unistyles';\nimport { useAnimatedTheme, useAnimatedVariantColor } from 'react-native-unistyles/reanimated';\n\nimport type { StyleProps } from '../../generated/styles';\nimport { buttonStyles, iconButtonStyles, styles as foundationStyles } from '../../generated/styles';\nimport { BUTTON_SPRING_CONFIG, SCALE_EFFECTS } from '../motion';\nimport { getIconButtonControlMetrics } from './Button/buttonTheme';\nimport type { IconName } from './Icon';\nimport { Icon } from './Icon';\nimport type { PressableProps } from './Pressable';\nimport { AnimatedPressable } from './Pressable';\n\n/* -------------------------------------------------------------------------- */\n/* Animation Helpers */\n/* -------------------------------------------------------------------------- */\n\nfunction interpolateShadowAlpha(shadow: string | undefined, alpha: number): string {\n 'worklet';\n if (!shadow) {\n return '';\n }\n if (alpha >= 1) {\n return shadow;\n }\n if (alpha <= 0) {\n return '';\n }\n\n return shadow.replace(/rgba\\(([^,]+),\\s*([^,]+),\\s*([^,]+),\\s*([^)]+)\\)/g, (_, r, g, b, a) => {\n const newAlpha = parseFloat(a) * alpha;\n return `rgba(${r}, ${g}, ${b}, ${newAlpha.toFixed(3)})`;\n });\n}\n\n/* -------------------------------------------------------------------------- */\n/* IconButton Props */\n/* -------------------------------------------------------------------------- */\n\ninterface IconButtonProps extends Omit<PressableProps, 'children'> {\n /** Icon to render from the icons package */\n name: IconName;\n /** The visual style variant @default 'primary' */\n variant?: ButtonVariantFlat;\n /** The size of the button @default 'md' */\n size?: IconButtonSize;\n /** The icon style variant @default 'outline' */\n iconVariant?: IconVariant;\n /** Override the icon color token without changing the button variant tokens */\n iconColor?: StyleProps['color'];\n /** Shows a loading spinner and disables the button */\n loading?: boolean;\n /**\n * Disable motion effects (scale on press, icon animations)\n * @default false\n */\n disableEffects?: boolean;\n /** Ref to the underlying View */\n ref?: Ref<View>;\n}\n\n/* -------------------------------------------------------------------------- */\n/* IconButton Component */\n/* -------------------------------------------------------------------------- */\n\n/**\n * **An icon button element that can be used to trigger an action**\n *\n * @description\n * An icon-only button for actions where space is limited. Features animated\n * scale effect on press and smooth color transitions matching the web UDS\n * IconButton behavior.\n *\n * @category Interactive\n * @platform mobile\n *\n * @example\n * ```tsx\n * import { IconButton } from '@yahoo/uds-mobile/IconButton';\n *\n * <IconButton name=\"Add\" onPress={() => console.log('pressed')} />\n * <IconButton name=\"Close\" variant=\"secondary\" size=\"sm\" />\n * <IconButton name=\"Settings\" loading />\n * ```\n *\n * @usage\n * - Use for toolbar actions\n * - Use for closing modals/dialogs\n * - Always provide accessibilityLabel for screen readers\n *\n * @accessibility\n * - Sets `accessibilityRole=\"button\"` automatically\n * - Announces loading state to screen readers\n * - **Always** provide `accessibilityLabel` since there's no visible text\n *\n * @see {@link Button} for buttons with text labels\n * @see {@link Icon} for non-interactive icons\n */\nconst IconButton = memo(function IconButton({\n name,\n variant = 'primary',\n size = 'md',\n iconVariant = 'outline',\n iconColor,\n loading,\n disabled,\n style,\n accessibilityLabel,\n accessibilityHint,\n disableEffects = false,\n onPressIn,\n onPressOut,\n ref,\n ...props\n}: IconButtonProps) {\n const isDisabled = disabled || loading;\n const shouldAnimate = !disableEffects && !isDisabled;\n const shouldAnimateVariantColors = Platform.OS !== 'web';\n\n const { theme } = useUnistyles();\n const { minHeight, iconCellHeight, paddingVertical, hasExplicitHeight } = useMemo(\n () => getIconButtonControlMetrics(theme, size),\n [theme, size],\n );\n // Explicit token height (match-button-height) becomes a minimum with redistributed\n // padding so the safe icon cell fits and font scaling can grow the control.\n const controlDimensions = hasExplicitHeight\n ? ({\n height: 'auto',\n width: 'auto',\n minHeight,\n minWidth: minHeight,\n paddingVertical,\n } as const)\n : ({ minHeight, paddingVertical } as const);\n\n /* --------------------------------- State ---------------------------------- */\n const [pressed, setPressed] = useState(false);\n\n // On web, useVariants returns the resolved style object instead of mutating\n // the generated styles in place.\n const variantIconButtonStyles = iconButtonStyles.useVariants({ size }) as unknown as\n | typeof iconButtonStyles\n | undefined;\n const resolvedIconButtonStyles = variantIconButtonStyles ?? iconButtonStyles;\n\n const variantButtonStyles = buttonStyles.useVariants({\n variant,\n disabled: isDisabled,\n pressed,\n }) as unknown as typeof buttonStyles | undefined;\n const resolvedButtonStyles = variantButtonStyles ?? buttonStyles;\n\n const variantFoundationStyles = foundationStyles.useVariants({ color: iconColor }) as unknown as\n | typeof foundationStyles\n | undefined;\n const resolvedFoundationStyles = variantFoundationStyles ?? foundationStyles;\n\n const resolvedIconColor = iconColor\n ? (resolvedFoundationStyles.foundation.color as string | undefined)\n : undefined;\n\n // Animate colors using Unistyles' useAnimatedVariantColor\n const backgroundColor = useAnimatedVariantColor(resolvedButtonStyles.root, 'backgroundColor');\n const borderColor = useAnimatedVariantColor(resolvedButtonStyles.root, 'borderColor');\n\n // Get animated theme for boxShadow\n const animatedTheme = useAnimatedTheme();\n\n /* ------------------------------- Animation -------------------------------- */\n const scale = useSharedValue<number>(SCALE_EFFECTS.none);\n\n const handlePressIn = useCallback<NonNullable<PressableProps['onPressIn']>>(\n (event) => {\n setPressed(true);\n if (shouldAnimate) {\n scale.value = withSpring(SCALE_EFFECTS.down, BUTTON_SPRING_CONFIG);\n }\n onPressIn?.(event);\n },\n [shouldAnimate, scale, onPressIn],\n );\n\n const handlePressOut = useCallback<NonNullable<PressableProps['onPressOut']>>(\n (event) => {\n setPressed(false);\n if (shouldAnimate) {\n scale.value = withSpring(SCALE_EFFECTS.none, BUTTON_SPRING_CONFIG);\n }\n onPressOut?.(event);\n },\n [shouldAnimate, scale, onPressOut],\n );\n\n const a11yState = useMemo(() => ({ disabled: isDisabled, busy: loading }), [isDisabled, loading]);\n\n /* --------------------------------- Styles --------------------------------- */\n // Animate pressed state for shadow\n const pressProgress = useDerivedValue(\n () => withTiming(pressed ? 1 : 0, { duration: 220, easing: Easing.bezier(0, 0, 0.2, 1) }),\n [pressed],\n );\n\n // Animate using Unistyles' variant color system + boxShadow from theme\n const animatedRootStyle = useAnimatedStyle(() => {\n // Get boxShadow from theme using flattened path (no camelCase conversion needed!)\n const components = animatedTheme.value.components as unknown as Record<\n string,\n Record<string, unknown>\n >;\n const shadowPressed = components[`button/variant/${variant}/root/pressed`]?.boxShadow as\n | string\n | undefined;\n\n return {\n transform: [{ scale: scale.value }],\n // On web, Unistyles already emits the resolved variant color class. The\n // animated variant color hook can resolve to the black fallback there.\n ...(shouldAnimateVariantColors && {\n backgroundColor: withTiming(backgroundColor.value, {\n duration: 220,\n easing: Easing.bezier(0, 0, 0.2, 1),\n }),\n borderColor: withTiming(borderColor.value, {\n duration: 220,\n easing: Easing.bezier(0, 0, 0.2, 1),\n }),\n }),\n // Only animate shadow if the theme defines one for this variant\n ...(shadowPressed && {\n boxShadow: interpolateShadowAlpha(shadowPressed, pressProgress.value),\n }),\n };\n });\n\n /* --------------------------------- Render --------------------------------- */\n return (\n <AnimatedPressable\n ref={ref}\n disabled={isDisabled}\n onPressIn={handlePressIn}\n onPressOut={handlePressOut}\n flexDirection=\"row\"\n alignItems=\"center\"\n justifyContent=\"center\"\n overflow=\"hidden\"\n accessibilityLabel={loading ? `${accessibilityLabel ?? ''}, loading` : accessibilityLabel}\n accessibilityHint={accessibilityHint}\n accessibilityRole=\"button\"\n accessibilityState={a11yState}\n style={[\n resolvedIconButtonStyles.root,\n resolvedButtonStyles.root,\n controlDimensions,\n resolvedFoundationStyles.foundation,\n animatedRootStyle,\n typeof style === 'function' ? style({ pressed }) : style,\n ]}\n {...props}\n >\n {loading ? (\n <ActivityIndicator\n size={resolvedIconButtonStyles.icon.fontSize}\n color={resolvedIconColor ?? resolvedButtonStyles.icon.color}\n />\n ) : (\n <Icon\n name={name}\n variant={iconVariant}\n style={[\n resolvedIconButtonStyles.icon,\n resolvedButtonStyles.icon,\n { lineHeight: iconCellHeight },\n ]}\n dangerouslySetColor={resolvedIconColor}\n />\n )}\n </AnimatedPressable>\n );\n});\n\nIconButton.displayName = 'IconButton';\n\nexport { IconButton, type IconButtonProps };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"IconButton.js","names":["foundationStyles"],"sources":["../../src/components/IconButton.tsx"],"sourcesContent":["import type { ButtonVariantFlat, IconButtonSize, IconVariant } from '@yahoo/uds-types';\nimport type { Ref } from 'react';\nimport { memo, useCallback, useMemo, useState } from 'react';\nimport type { View } from 'react-native';\nimport { ActivityIndicator, Platform } from 'react-native';\nimport {\n Easing,\n useAnimatedStyle,\n useDerivedValue,\n useSharedValue,\n withSpring,\n withTiming,\n} from 'react-native-reanimated';\n// eslint-disable-next-line uds/no-use-unistyles -- iconbutton control height from theme size layers\nimport { useUnistyles } from 'react-native-unistyles';\nimport { useAnimatedTheme, useAnimatedVariantColor } from 'react-native-unistyles/reanimated';\n\nimport type { StyleProps } from '../../generated/styles';\nimport { buttonStyles, iconButtonStyles, styles as foundationStyles } from '../../generated/styles';\nimport { useComponentFontScale } from '../fontScaling/useFontScale';\nimport { useMaxFontSizeMultiplier } from '../fontScaling/useMaxFontSizeMultiplier';\nimport { BUTTON_SPRING_CONFIG, SCALE_EFFECTS } from '../motion';\nimport { getIconButtonControlMetrics, getIconButtonIconSize } from './Button/buttonTheme';\nimport type { IconName } from './Icon';\nimport { Icon } from './Icon';\nimport type { PressableProps } from './Pressable';\nimport { AnimatedPressable } from './Pressable';\n\n/* -------------------------------------------------------------------------- */\n/* Animation Helpers */\n/* -------------------------------------------------------------------------- */\n\nfunction interpolateShadowAlpha(shadow: string | undefined, alpha: number): string {\n 'worklet';\n if (!shadow) {\n return '';\n }\n if (alpha >= 1) {\n return shadow;\n }\n if (alpha <= 0) {\n return '';\n }\n\n return shadow.replace(/rgba\\(([^,]+),\\s*([^,]+),\\s*([^,]+),\\s*([^)]+)\\)/g, (_, r, g, b, a) => {\n const newAlpha = parseFloat(a) * alpha;\n return `rgba(${r}, ${g}, ${b}, ${newAlpha.toFixed(3)})`;\n });\n}\n\n/* -------------------------------------------------------------------------- */\n/* IconButton Props */\n/* -------------------------------------------------------------------------- */\n\ninterface IconButtonProps extends Omit<PressableProps, 'children'> {\n /** Icon to render from the icons package */\n name: IconName;\n /** The visual style variant @default 'primary' */\n variant?: ButtonVariantFlat;\n /** The size of the button @default 'md' */\n size?: IconButtonSize;\n /** The icon style variant @default 'outline' */\n iconVariant?: IconVariant;\n /** Override the icon color token without changing the button variant tokens */\n iconColor?: StyleProps['color'];\n /** Shows a loading spinner and disables the button */\n loading?: boolean;\n /**\n * Disable motion effects (scale on press, icon animations)\n * @default false\n */\n disableEffects?: boolean;\n /**\n * Caps how far the icon and control grow with the OS text-size setting.\n * Set null to remove the cap; override app-wide via UDSFontScalingProvider.\n * @default 2\n */\n maxFontSizeMultiplier?: number | null;\n /** Ref to the underlying View */\n ref?: Ref<View>;\n}\n\n/* -------------------------------------------------------------------------- */\n/* IconButton Component */\n/* -------------------------------------------------------------------------- */\n\n/**\n * **An icon button element that can be used to trigger an action**\n *\n * @description\n * An icon-only button for actions where space is limited. Features animated\n * scale effect on press and smooth color transitions matching the web UDS\n * IconButton behavior.\n *\n * @category Interactive\n * @platform mobile\n *\n * @example\n * ```tsx\n * import { IconButton } from '@yahoo/uds-mobile/IconButton';\n *\n * <IconButton name=\"Add\" onPress={() => console.log('pressed')} />\n * <IconButton name=\"Close\" variant=\"secondary\" size=\"sm\" />\n * <IconButton name=\"Settings\" loading />\n * ```\n *\n * @usage\n * - Use for toolbar actions\n * - Use for closing modals/dialogs\n * - Always provide accessibilityLabel for screen readers\n *\n * @accessibility\n * - Sets `accessibilityRole=\"button\"` automatically\n * - Announces loading state to screen readers\n * - **Always** provide `accessibilityLabel` since there's no visible text\n *\n * @see {@link Button} for buttons with text labels\n * @see {@link Icon} for non-interactive icons\n */\nconst IconButton = memo(function IconButton({\n name,\n variant = 'primary',\n size = 'md',\n iconVariant = 'outline',\n iconColor,\n loading,\n disabled,\n style,\n accessibilityLabel,\n accessibilityHint,\n disableEffects = false,\n maxFontSizeMultiplier,\n onPressIn,\n onPressOut,\n ref,\n ...props\n}: IconButtonProps) {\n const isDisabled = disabled || loading;\n const shouldAnimate = !disableEffects && !isDisabled;\n const shouldAnimateVariantColors = Platform.OS !== 'web';\n\n const { theme } = useUnistyles();\n const resolvedMaxFontSizeMultiplier = useMaxFontSizeMultiplier('control', maxFontSizeMultiplier);\n\n /* --------------------------------- State ---------------------------------- */\n const [pressed, setPressed] = useState(false);\n\n // On web, useVariants returns the resolved style object instead of mutating\n // the generated styles in place.\n const variantIconButtonStyles = iconButtonStyles.useVariants({ size }) as unknown as\n typeof iconButtonStyles | undefined;\n const resolvedIconButtonStyles = variantIconButtonStyles ?? iconButtonStyles;\n\n const iconBaseSize = useMemo(() => getIconButtonIconSize(theme, size), [theme, size]);\n const iconScaleFactor = useComponentFontScale(resolvedMaxFontSizeMultiplier, iconBaseSize);\n const { minHeight, iconCellHeight, paddingVertical, hasExplicitHeight } = useMemo(\n () => getIconButtonControlMetrics(theme, size, iconScaleFactor),\n [theme, size, iconScaleFactor],\n );\n const scaledIconSize = Math.round(iconBaseSize * iconScaleFactor);\n // Explicit token height (match-button-height) becomes a minimum with redistributed\n // padding so the safe icon cell fits and font scaling can grow the control.\n const controlDimensions = hasExplicitHeight\n ? ({\n height: 'auto',\n width: 'auto',\n minHeight,\n minWidth: minHeight,\n paddingVertical,\n } as const)\n : ({ minHeight, paddingVertical } as const);\n\n const variantButtonStyles = buttonStyles.useVariants({\n variant,\n disabled: isDisabled,\n pressed,\n }) as unknown as typeof buttonStyles | undefined;\n const resolvedButtonStyles = variantButtonStyles ?? buttonStyles;\n\n const variantFoundationStyles = foundationStyles.useVariants({ color: iconColor }) as unknown as\n typeof foundationStyles | undefined;\n const resolvedFoundationStyles = variantFoundationStyles ?? foundationStyles;\n\n const resolvedIconColor = iconColor\n ? (resolvedFoundationStyles.foundation.color as string | undefined)\n : undefined;\n\n // Animate colors using Unistyles' useAnimatedVariantColor\n const backgroundColor = useAnimatedVariantColor(resolvedButtonStyles.root, 'backgroundColor');\n const borderColor = useAnimatedVariantColor(resolvedButtonStyles.root, 'borderColor');\n\n // Get animated theme for boxShadow\n const animatedTheme = useAnimatedTheme();\n\n /* ------------------------------- Animation -------------------------------- */\n const scale = useSharedValue<number>(SCALE_EFFECTS.none);\n\n const handlePressIn = useCallback<NonNullable<PressableProps['onPressIn']>>(\n (event) => {\n setPressed(true);\n if (shouldAnimate) {\n scale.value = withSpring(SCALE_EFFECTS.down, BUTTON_SPRING_CONFIG);\n }\n onPressIn?.(event);\n },\n [shouldAnimate, scale, onPressIn],\n );\n\n const handlePressOut = useCallback<NonNullable<PressableProps['onPressOut']>>(\n (event) => {\n setPressed(false);\n if (shouldAnimate) {\n scale.value = withSpring(SCALE_EFFECTS.none, BUTTON_SPRING_CONFIG);\n }\n onPressOut?.(event);\n },\n [shouldAnimate, scale, onPressOut],\n );\n\n const a11yState = useMemo(() => ({ disabled: isDisabled, busy: loading }), [isDisabled, loading]);\n\n /* --------------------------------- Styles --------------------------------- */\n // Animate pressed state for shadow\n const pressProgress = useDerivedValue(\n () => withTiming(pressed ? 1 : 0, { duration: 220, easing: Easing.bezier(0, 0, 0.2, 1) }),\n [pressed],\n );\n\n // Animate using Unistyles' variant color system + boxShadow from theme\n const animatedRootStyle = useAnimatedStyle(() => {\n // Get boxShadow from theme using flattened path (no camelCase conversion needed!)\n const components = animatedTheme.value.components as unknown as Record<\n string,\n Record<string, unknown>\n >;\n const shadowPressed = components[`button/variant/${variant}/root/pressed`]?.boxShadow as\n string | undefined;\n\n return {\n transform: [{ scale: scale.value }],\n // On web, Unistyles already emits the resolved variant color class. The\n // animated variant color hook can resolve to the black fallback there.\n ...(shouldAnimateVariantColors && {\n backgroundColor: withTiming(backgroundColor.value, {\n duration: 220,\n easing: Easing.bezier(0, 0, 0.2, 1),\n }),\n borderColor: withTiming(borderColor.value, {\n duration: 220,\n easing: Easing.bezier(0, 0, 0.2, 1),\n }),\n }),\n // Only animate shadow if the theme defines one for this variant\n ...(shadowPressed && {\n boxShadow: interpolateShadowAlpha(shadowPressed, pressProgress.value),\n }),\n };\n });\n\n /* --------------------------------- Render --------------------------------- */\n return (\n <AnimatedPressable\n ref={ref}\n disabled={isDisabled}\n onPressIn={handlePressIn}\n onPressOut={handlePressOut}\n flexDirection=\"row\"\n alignItems=\"center\"\n justifyContent=\"center\"\n overflow=\"hidden\"\n accessibilityLabel={loading ? `${accessibilityLabel ?? ''}, loading` : accessibilityLabel}\n accessibilityHint={accessibilityHint}\n accessibilityRole=\"button\"\n accessibilityState={a11yState}\n style={[\n resolvedIconButtonStyles.root,\n resolvedButtonStyles.root,\n controlDimensions,\n resolvedFoundationStyles.foundation,\n animatedRootStyle,\n typeof style === 'function' ? style({ pressed }) : style,\n ]}\n {...props}\n >\n {loading ? (\n <ActivityIndicator\n size={scaledIconSize}\n color={resolvedIconColor ?? resolvedButtonStyles.icon.color}\n />\n ) : (\n <Icon\n name={name}\n variant={iconVariant}\n style={[\n resolvedIconButtonStyles.icon,\n resolvedButtonStyles.icon,\n { lineHeight: iconCellHeight },\n ]}\n dangerouslySetColor={resolvedIconColor}\n maxFontSizeMultiplier={resolvedMaxFontSizeMultiplier}\n />\n )}\n </AnimatedPressable>\n );\n});\n\nIconButton.displayName = 'IconButton';\n\nexport { IconButton, type IconButtonProps };\n"],"mappings":"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"}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
require("../_virtual/_rolldown/runtime.cjs");
|
|
4
4
|
const require_components_HStack = require("./HStack.cjs");
|
|
5
|
+
const require_fontScaling_useFontScale = require("../fontScaling/useFontScale.cjs");
|
|
6
|
+
const require_fontScaling_useMaxFontSizeMultiplier = require("../fontScaling/useMaxFontSizeMultiplier.cjs");
|
|
5
7
|
const require_components_IconSlot = require("./IconSlot.cjs");
|
|
6
8
|
const require_components_Text = require("./Text.cjs");
|
|
7
9
|
const require_components_InputHelpText = require("./InputHelpText.cjs");
|
|
@@ -47,8 +49,9 @@ let lodash_es = require("lodash-es");
|
|
|
47
49
|
* @see {@link Checkbox} for boolean selections
|
|
48
50
|
* @see {@link Radio} for single-select options
|
|
49
51
|
*/
|
|
50
|
-
const Input = (0, react.memo)(function Input({ label, size = "md", startIcon, endIcon, helpText, helperTextIcon, hasError, disabled, readOnly, required, width = "100%", defaultValue, value: controlledValue, onChangeText, onFocus, onBlur, placeholder, placeholderTextColor, ref, ...textInputProps }) {
|
|
52
|
+
const Input = (0, react.memo)(function Input({ label, size = "md", startIcon, endIcon, helpText, helperTextIcon, hasError, disabled, readOnly, required, width = "100%", defaultValue, value: controlledValue, onChangeText, onFocus, onBlur, placeholder, placeholderTextColor, maxFontSizeMultiplier, ref, ...textInputProps }) {
|
|
51
53
|
const uid = `uds-input-${(0, react.useId)()}`;
|
|
54
|
+
const resolvedMaxFontSizeMultiplier = require_fontScaling_useMaxFontSizeMultiplier.useMaxFontSizeMultiplier("control", maxFontSizeMultiplier);
|
|
52
55
|
const [internalValue, setInternalValue] = (0, react.useState)(defaultValue ?? "");
|
|
53
56
|
const [isFocused, setIsFocused] = (0, react.useState)(false);
|
|
54
57
|
const isControlled = controlledValue !== void 0;
|
|
@@ -73,30 +76,47 @@ const Input = (0, react.memo)(function Input({ label, size = "md", startIcon, en
|
|
|
73
76
|
readonly: readOnly,
|
|
74
77
|
invalid: hasError
|
|
75
78
|
});
|
|
79
|
+
const fontScaleFactor = require_fontScaling_useFontScale.useComponentFontScale(resolvedMaxFontSizeMultiplier, (typeof generated_styles.inputStyles.inputWrapperStatic.lineHeight === "number" ? generated_styles.inputStyles.inputWrapperStatic.lineHeight : void 0) ?? (typeof generated_styles.inputStyles.inputWrapperStatic.fontSize === "number" ? generated_styles.inputStyles.inputWrapperStatic.fontSize : void 0));
|
|
76
80
|
const computedPlaceholderColor = placeholderTextColor ?? generated_styles.inputStyles.inputPlaceholder.color;
|
|
77
81
|
const rootStyle = (0, react.useMemo)(() => [{
|
|
78
82
|
width,
|
|
79
83
|
opacity: disabled ? .5 : 1
|
|
80
84
|
}], [width, disabled]);
|
|
81
|
-
const inputWrapperStyle = (0, react.useMemo)(() =>
|
|
82
|
-
generated_styles.inputStyles.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
const inputWrapperStyle = (0, react.useMemo)(() => {
|
|
86
|
+
const { gap, paddingVertical } = generated_styles.inputStyles.inputWrapperStatic;
|
|
87
|
+
const scaledMetrics = fontScaleFactor === 1 ? void 0 : {
|
|
88
|
+
...typeof gap === "number" && { gap: Math.round(gap * fontScaleFactor) },
|
|
89
|
+
...typeof paddingVertical === "number" && { paddingVertical: Math.round(paddingVertical * fontScaleFactor) }
|
|
90
|
+
};
|
|
91
|
+
return [
|
|
92
|
+
generated_styles.inputStyles.inputWrapper,
|
|
93
|
+
generated_styles.inputStyles.inputWrapperStatic,
|
|
94
|
+
generated_styles.inputStyles.inputWrapperDynamic,
|
|
95
|
+
scaledMetrics,
|
|
96
|
+
{
|
|
97
|
+
flexDirection: "row",
|
|
98
|
+
alignItems: "center"
|
|
99
|
+
}
|
|
100
|
+
];
|
|
101
|
+
}, [
|
|
102
|
+
fontScaleFactor,
|
|
90
103
|
generated_styles.inputStyles.inputWrapper,
|
|
91
104
|
generated_styles.inputStyles.inputWrapperStatic,
|
|
92
105
|
generated_styles.inputStyles.inputWrapperDynamic
|
|
93
106
|
]);
|
|
94
|
-
const textInputStyle = (0, react.useMemo)(() =>
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
107
|
+
const textInputStyle = (0, react.useMemo)(() => {
|
|
108
|
+
const inputHeight = generated_styles.inputStyles.input.height;
|
|
109
|
+
return [
|
|
110
|
+
generated_styles.inputStyles.input,
|
|
111
|
+
typeof inputHeight === "number" ? { height: Math.round(inputHeight * fontScaleFactor) } : void 0,
|
|
112
|
+
{
|
|
113
|
+
flex: 1,
|
|
114
|
+
includeFontPadding: false,
|
|
115
|
+
textAlignVertical: "center",
|
|
116
|
+
paddingVertical: 0
|
|
117
|
+
}
|
|
118
|
+
];
|
|
119
|
+
}, [fontScaleFactor, generated_styles.inputStyles.input]);
|
|
100
120
|
const labelContent = (0, react.useMemo)(() => {
|
|
101
121
|
if (!label) return null;
|
|
102
122
|
const content = (0, lodash_es.isFunction)(label) ? label() : label;
|
|
@@ -105,9 +125,13 @@ const Input = (0, react.memo)(function Input({ label, size = "md", startIcon, en
|
|
|
105
125
|
alignItems: "flex-end",
|
|
106
126
|
spacingBottom: "2",
|
|
107
127
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_Text.Text, {
|
|
128
|
+
variant: "inherit",
|
|
129
|
+
maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
|
|
108
130
|
style: generated_styles.inputStyles.label,
|
|
109
131
|
children: content
|
|
110
132
|
}), required && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_Text.Text, {
|
|
133
|
+
variant: "inherit",
|
|
134
|
+
maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
|
|
111
135
|
style: generated_styles.inputStyles.labelRequired,
|
|
112
136
|
children: "*"
|
|
113
137
|
})]
|
|
@@ -115,6 +139,7 @@ const Input = (0, react.memo)(function Input({ label, size = "md", startIcon, en
|
|
|
115
139
|
}, [
|
|
116
140
|
label,
|
|
117
141
|
required,
|
|
142
|
+
resolvedMaxFontSizeMultiplier,
|
|
118
143
|
generated_styles.inputStyles.label,
|
|
119
144
|
generated_styles.inputStyles.labelRequired
|
|
120
145
|
]);
|
|
@@ -123,17 +148,27 @@ const Input = (0, react.memo)(function Input({ label, size = "md", startIcon, en
|
|
|
123
148
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_IconSlot.IconSlot, {
|
|
124
149
|
icon: startIcon,
|
|
125
150
|
variant: "outline",
|
|
151
|
+
maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
|
|
126
152
|
style: generated_styles.inputStyles.startIcon
|
|
127
153
|
});
|
|
128
|
-
}, [
|
|
154
|
+
}, [
|
|
155
|
+
startIcon,
|
|
156
|
+
resolvedMaxFontSizeMultiplier,
|
|
157
|
+
generated_styles.inputStyles.startIcon
|
|
158
|
+
]);
|
|
129
159
|
const endIconContent = (0, react.useMemo)(() => {
|
|
130
160
|
if (!endIcon) return null;
|
|
131
161
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_IconSlot.IconSlot, {
|
|
132
162
|
icon: endIcon,
|
|
133
163
|
variant: "outline",
|
|
164
|
+
maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
|
|
134
165
|
style: generated_styles.inputStyles.endIcon
|
|
135
166
|
});
|
|
136
|
-
}, [
|
|
167
|
+
}, [
|
|
168
|
+
endIcon,
|
|
169
|
+
resolvedMaxFontSizeMultiplier,
|
|
170
|
+
generated_styles.inputStyles.endIcon
|
|
171
|
+
]);
|
|
137
172
|
const helpTextContent = (0, react.useMemo)(() => {
|
|
138
173
|
if (!helpText) return null;
|
|
139
174
|
const content = (0, lodash_es.isFunction)(helpText) ? helpText() : helpText;
|
|
@@ -145,6 +180,7 @@ const Input = (0, react.memo)(function Input({ label, size = "md", startIcon, en
|
|
|
145
180
|
readOnly,
|
|
146
181
|
hasError,
|
|
147
182
|
pressed: isFocused,
|
|
183
|
+
maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
|
|
148
184
|
children: content
|
|
149
185
|
});
|
|
150
186
|
}, [
|
|
@@ -154,6 +190,7 @@ const Input = (0, react.memo)(function Input({ label, size = "md", startIcon, en
|
|
|
154
190
|
helperTextIcon,
|
|
155
191
|
isFocused,
|
|
156
192
|
readOnly,
|
|
193
|
+
resolvedMaxFontSizeMultiplier,
|
|
157
194
|
size,
|
|
158
195
|
valueState
|
|
159
196
|
]);
|
|
@@ -178,6 +215,7 @@ const Input = (0, react.memo)(function Input({ label, size = "md", startIcon, en
|
|
|
178
215
|
placeholder,
|
|
179
216
|
placeholderTextColor: computedPlaceholderColor,
|
|
180
217
|
editable: !disabled && !readOnly,
|
|
218
|
+
maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
|
|
181
219
|
style: textInputStyle,
|
|
182
220
|
accessibilityLabel: typeof label === "string" ? label : void 0,
|
|
183
221
|
accessibilityHint: typeof helpText === "string" ? helpText : void 0,
|
|
@@ -10,6 +10,11 @@ import { TextInput, TextInputProps } from "react-native";
|
|
|
10
10
|
interface InputProps extends Omit<UniversalInputProps<IconSlotType>, 'width'>, Omit<TextInputProps, 'style' | 'editable'>, Pick<SizeProps, 'width'> {
|
|
11
11
|
/** Ref to the underlying TextInput element */
|
|
12
12
|
ref?: Ref<TextInput>;
|
|
13
|
+
/**
|
|
14
|
+
* Caps the field text, labels, icons, and geometry at the control scale.
|
|
15
|
+
* Set null to remove the cap. @default 2
|
|
16
|
+
*/
|
|
17
|
+
maxFontSizeMultiplier?: number | null;
|
|
13
18
|
}
|
|
14
19
|
/**
|
|
15
20
|
* **📦 An input that allows users to enter text and collect data.**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Input.d.cts","names":[],"sources":["../../src/components/Input.tsx"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"Input.d.cts","names":[],"sources":["../../src/components/Input.tsx"],"mappings":";;;;;;;;;UAsBU,UAAA,SAEN,IAAA,CAAK,mBAAA,CAAoB,YAAA,aACzB,IAAA,CAAK,cAAA,yBACL,IAAA,CAAK,SAAA;;EAEP,GAAA,GAAM,GAAA,CAAI,SAAA;EALV;;;;EAUA,qBAAA;AAAA;;;;;;;;;;;;;;;;;;;;;AAAqB;;;;;;;;;;;;;;;cA0CjB,KAAA,EAAK,OAAA,CAAA,oBAAA,CAAA,UAAA"}
|
|
@@ -10,6 +10,11 @@ import { TextInput, TextInputProps } from "react-native";
|
|
|
10
10
|
interface InputProps extends Omit<UniversalInputProps<IconSlotType>, 'width'>, Omit<TextInputProps, 'style' | 'editable'>, Pick<SizeProps, 'width'> {
|
|
11
11
|
/** Ref to the underlying TextInput element */
|
|
12
12
|
ref?: Ref<TextInput>;
|
|
13
|
+
/**
|
|
14
|
+
* Caps the field text, labels, icons, and geometry at the control scale.
|
|
15
|
+
* Set null to remove the cap. @default 2
|
|
16
|
+
*/
|
|
17
|
+
maxFontSizeMultiplier?: number | null;
|
|
13
18
|
}
|
|
14
19
|
/**
|
|
15
20
|
* **📦 An input that allows users to enter text and collect data.**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Input.d.ts","names":[],"sources":["../../src/components/Input.tsx"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"Input.d.ts","names":[],"sources":["../../src/components/Input.tsx"],"mappings":";;;;;;;;;UAsBU,UAAA,SAEN,IAAA,CAAK,mBAAA,CAAoB,YAAA,aACzB,IAAA,CAAK,cAAA,yBACL,IAAA,CAAK,SAAA;;EAEP,GAAA,GAAM,GAAA,CAAI,SAAA;EALV;;;;EAUA,qBAAA;AAAA;;;;;;;;;;;;;;;;;;;;;AAAqB;;;;;;;;;;;;;;;cA0CjB,KAAA,EAAK,OAAA,CAAA,oBAAA,CAAA,UAAA"}
|
package/dist/components/Input.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/*! © 2026 Yahoo, Inc. UDS Mobile v0.0.0-development */
|
|
2
2
|
import { HStack } from "./HStack.js";
|
|
3
|
+
import { useComponentFontScale } from "../fontScaling/useFontScale.js";
|
|
4
|
+
import { useMaxFontSizeMultiplier } from "../fontScaling/useMaxFontSizeMultiplier.js";
|
|
3
5
|
import { IconSlot } from "./IconSlot.js";
|
|
4
6
|
import { Text as Text$1 } from "./Text.js";
|
|
5
7
|
import { InputHelpText } from "./InputHelpText.js";
|
|
@@ -45,8 +47,9 @@ import { isFunction } from "lodash-es";
|
|
|
45
47
|
* @see {@link Checkbox} for boolean selections
|
|
46
48
|
* @see {@link Radio} for single-select options
|
|
47
49
|
*/
|
|
48
|
-
const Input = memo(function Input({ label, size = "md", startIcon, endIcon, helpText, helperTextIcon, hasError, disabled, readOnly, required, width = "100%", defaultValue, value: controlledValue, onChangeText, onFocus, onBlur, placeholder, placeholderTextColor, ref, ...textInputProps }) {
|
|
50
|
+
const Input = memo(function Input({ label, size = "md", startIcon, endIcon, helpText, helperTextIcon, hasError, disabled, readOnly, required, width = "100%", defaultValue, value: controlledValue, onChangeText, onFocus, onBlur, placeholder, placeholderTextColor, maxFontSizeMultiplier, ref, ...textInputProps }) {
|
|
49
51
|
const uid = `uds-input-${useId()}`;
|
|
52
|
+
const resolvedMaxFontSizeMultiplier = useMaxFontSizeMultiplier("control", maxFontSizeMultiplier);
|
|
50
53
|
const [internalValue, setInternalValue] = useState(defaultValue ?? "");
|
|
51
54
|
const [isFocused, setIsFocused] = useState(false);
|
|
52
55
|
const isControlled = controlledValue !== void 0;
|
|
@@ -71,30 +74,47 @@ const Input = memo(function Input({ label, size = "md", startIcon, endIcon, help
|
|
|
71
74
|
readonly: readOnly,
|
|
72
75
|
invalid: hasError
|
|
73
76
|
});
|
|
77
|
+
const fontScaleFactor = useComponentFontScale(resolvedMaxFontSizeMultiplier, (typeof inputStyles.inputWrapperStatic.lineHeight === "number" ? inputStyles.inputWrapperStatic.lineHeight : void 0) ?? (typeof inputStyles.inputWrapperStatic.fontSize === "number" ? inputStyles.inputWrapperStatic.fontSize : void 0));
|
|
74
78
|
const computedPlaceholderColor = placeholderTextColor ?? inputStyles.inputPlaceholder.color;
|
|
75
79
|
const rootStyle = useMemo(() => [{
|
|
76
80
|
width,
|
|
77
81
|
opacity: disabled ? .5 : 1
|
|
78
82
|
}], [width, disabled]);
|
|
79
|
-
const inputWrapperStyle = useMemo(() =>
|
|
80
|
-
inputStyles.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
const inputWrapperStyle = useMemo(() => {
|
|
84
|
+
const { gap, paddingVertical } = inputStyles.inputWrapperStatic;
|
|
85
|
+
const scaledMetrics = fontScaleFactor === 1 ? void 0 : {
|
|
86
|
+
...typeof gap === "number" && { gap: Math.round(gap * fontScaleFactor) },
|
|
87
|
+
...typeof paddingVertical === "number" && { paddingVertical: Math.round(paddingVertical * fontScaleFactor) }
|
|
88
|
+
};
|
|
89
|
+
return [
|
|
90
|
+
inputStyles.inputWrapper,
|
|
91
|
+
inputStyles.inputWrapperStatic,
|
|
92
|
+
inputStyles.inputWrapperDynamic,
|
|
93
|
+
scaledMetrics,
|
|
94
|
+
{
|
|
95
|
+
flexDirection: "row",
|
|
96
|
+
alignItems: "center"
|
|
97
|
+
}
|
|
98
|
+
];
|
|
99
|
+
}, [
|
|
100
|
+
fontScaleFactor,
|
|
88
101
|
inputStyles.inputWrapper,
|
|
89
102
|
inputStyles.inputWrapperStatic,
|
|
90
103
|
inputStyles.inputWrapperDynamic
|
|
91
104
|
]);
|
|
92
|
-
const textInputStyle = useMemo(() =>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
const textInputStyle = useMemo(() => {
|
|
106
|
+
const inputHeight = inputStyles.input.height;
|
|
107
|
+
return [
|
|
108
|
+
inputStyles.input,
|
|
109
|
+
typeof inputHeight === "number" ? { height: Math.round(inputHeight * fontScaleFactor) } : void 0,
|
|
110
|
+
{
|
|
111
|
+
flex: 1,
|
|
112
|
+
includeFontPadding: false,
|
|
113
|
+
textAlignVertical: "center",
|
|
114
|
+
paddingVertical: 0
|
|
115
|
+
}
|
|
116
|
+
];
|
|
117
|
+
}, [fontScaleFactor, inputStyles.input]);
|
|
98
118
|
const labelContent = useMemo(() => {
|
|
99
119
|
if (!label) return null;
|
|
100
120
|
const content = isFunction(label) ? label() : label;
|
|
@@ -103,9 +123,13 @@ const Input = memo(function Input({ label, size = "md", startIcon, endIcon, help
|
|
|
103
123
|
alignItems: "flex-end",
|
|
104
124
|
spacingBottom: "2",
|
|
105
125
|
children: [/* @__PURE__ */ jsx(Text$1, {
|
|
126
|
+
variant: "inherit",
|
|
127
|
+
maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
|
|
106
128
|
style: inputStyles.label,
|
|
107
129
|
children: content
|
|
108
130
|
}), required && /* @__PURE__ */ jsx(Text$1, {
|
|
131
|
+
variant: "inherit",
|
|
132
|
+
maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
|
|
109
133
|
style: inputStyles.labelRequired,
|
|
110
134
|
children: "*"
|
|
111
135
|
})]
|
|
@@ -113,6 +137,7 @@ const Input = memo(function Input({ label, size = "md", startIcon, endIcon, help
|
|
|
113
137
|
}, [
|
|
114
138
|
label,
|
|
115
139
|
required,
|
|
140
|
+
resolvedMaxFontSizeMultiplier,
|
|
116
141
|
inputStyles.label,
|
|
117
142
|
inputStyles.labelRequired
|
|
118
143
|
]);
|
|
@@ -121,17 +146,27 @@ const Input = memo(function Input({ label, size = "md", startIcon, endIcon, help
|
|
|
121
146
|
return /* @__PURE__ */ jsx(IconSlot, {
|
|
122
147
|
icon: startIcon,
|
|
123
148
|
variant: "outline",
|
|
149
|
+
maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
|
|
124
150
|
style: inputStyles.startIcon
|
|
125
151
|
});
|
|
126
|
-
}, [
|
|
152
|
+
}, [
|
|
153
|
+
startIcon,
|
|
154
|
+
resolvedMaxFontSizeMultiplier,
|
|
155
|
+
inputStyles.startIcon
|
|
156
|
+
]);
|
|
127
157
|
const endIconContent = useMemo(() => {
|
|
128
158
|
if (!endIcon) return null;
|
|
129
159
|
return /* @__PURE__ */ jsx(IconSlot, {
|
|
130
160
|
icon: endIcon,
|
|
131
161
|
variant: "outline",
|
|
162
|
+
maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
|
|
132
163
|
style: inputStyles.endIcon
|
|
133
164
|
});
|
|
134
|
-
}, [
|
|
165
|
+
}, [
|
|
166
|
+
endIcon,
|
|
167
|
+
resolvedMaxFontSizeMultiplier,
|
|
168
|
+
inputStyles.endIcon
|
|
169
|
+
]);
|
|
135
170
|
const helpTextContent = useMemo(() => {
|
|
136
171
|
if (!helpText) return null;
|
|
137
172
|
const content = isFunction(helpText) ? helpText() : helpText;
|
|
@@ -143,6 +178,7 @@ const Input = memo(function Input({ label, size = "md", startIcon, endIcon, help
|
|
|
143
178
|
readOnly,
|
|
144
179
|
hasError,
|
|
145
180
|
pressed: isFocused,
|
|
181
|
+
maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
|
|
146
182
|
children: content
|
|
147
183
|
});
|
|
148
184
|
}, [
|
|
@@ -152,6 +188,7 @@ const Input = memo(function Input({ label, size = "md", startIcon, endIcon, help
|
|
|
152
188
|
helperTextIcon,
|
|
153
189
|
isFocused,
|
|
154
190
|
readOnly,
|
|
191
|
+
resolvedMaxFontSizeMultiplier,
|
|
155
192
|
size,
|
|
156
193
|
valueState
|
|
157
194
|
]);
|
|
@@ -176,6 +213,7 @@ const Input = memo(function Input({ label, size = "md", startIcon, endIcon, help
|
|
|
176
213
|
placeholder,
|
|
177
214
|
placeholderTextColor: computedPlaceholderColor,
|
|
178
215
|
editable: !disabled && !readOnly,
|
|
216
|
+
maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
|
|
179
217
|
style: textInputStyle,
|
|
180
218
|
accessibilityLabel: typeof label === "string" ? label : void 0,
|
|
181
219
|
accessibilityHint: typeof helpText === "string" ? helpText : void 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Input.js","names":["Text"],"sources":["../../src/components/Input.tsx"],"sourcesContent":["import type { UniversalInputProps } from '@yahoo/uds-types';\nimport { isFunction } from 'lodash-es';\nimport type { Ref } from 'react';\nimport { memo, useCallback, useId, useMemo, useState } from 'react';\nimport type { TextInputProps } from 'react-native';\nimport { TextInput, View } from 'react-native';\n\nimport { inputStyles } from '../../generated/styles';\nimport type { SizeProps } from '../types';\nimport { HStack } from './HStack';\nimport type { IconSlotType } from './IconSlot';\nimport { IconSlot } from './IconSlot';\nimport { InputHelpText } from './InputHelpText';\nimport { Text } from './Text';\nimport { VStack } from './VStack';\n\n/* -------------------------------------------------------------------------- */\n/* Types */\n/* -------------------------------------------------------------------------- */\n\ninterface InputProps\n extends\n Omit<UniversalInputProps<IconSlotType>, 'width'>,\n Omit<TextInputProps, 'style' | 'editable'>,\n Pick<SizeProps, 'width'> {\n /** Ref to the underlying TextInput element */\n ref?: Ref<TextInput>;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Input Component */\n/* -------------------------------------------------------------------------- */\n\n/**\n * **📦 An input that allows users to enter text and collect data.**\n *\n * @description\n * An input field is a component that takes text typed into it. It can also serve\n * as a way to display a selection and trigger a dropdown menu. Inputs are interactive\n * elements that users can click, tap, or otherwise engage with to collect data.\n *\n * @category Form\n * @platform mobile\n *\n * @example\n * ```tsx\n * import { Input } from '@yahoo/uds-mobile/Input';\n *\n * <Input label=\"Name\" placeholder=\"Enter your name\" required />\n * <Input label=\"Email\" startIcon=\"Mail\" helpText=\"We'll never share your email\" />\n * <Input label=\"Password\" secureTextEntry hasError helpText=\"Password is required\" />\n * ```\n *\n * @usage\n * - Forms: For collecting data like names, emails, passwords, etc.\n * - Search Bars: Allowing users to enter search queries\n * - Filters/Settings: When users need to specify preferences\n * - Feedback/Comments: Letting users leave reviews or comments\n *\n * @accessibility\n * - Label is automatically associated with the input\n * - Help text is announced as accessibility hint\n * - Error state is communicated to screen readers\n * - Disabled state prevents interaction\n *\n * @see {@link Checkbox} for boolean selections\n * @see {@link Radio} for single-select options\n */\nconst Input = memo(function Input({\n // Input props\n label,\n size = 'md',\n startIcon,\n endIcon,\n helpText,\n helperTextIcon,\n hasError,\n disabled,\n readOnly,\n required,\n // Size props\n width = '100%',\n // TextInput props\n defaultValue,\n value: controlledValue,\n onChangeText,\n onFocus,\n onBlur,\n placeholder,\n placeholderTextColor,\n ref,\n ...textInputProps\n}: InputProps) {\n const generatedId = useId();\n const uid = `uds-input-${generatedId}`;\n\n /* --------------------------------- State ---------------------------------- */\n const [internalValue, setInternalValue] = useState(defaultValue ?? '');\n const [isFocused, setIsFocused] = useState(false);\n\n // Support both controlled and uncontrolled modes\n const isControlled = controlledValue !== undefined;\n const value = isControlled ? controlledValue : internalValue;\n const valueState = value ? 'filled' : 'empty';\n\n /* -------------------------------- Handlers -------------------------------- */\n const handleChangeText = useCallback(\n (text: string) => {\n if (!isControlled) {\n setInternalValue(text);\n }\n onChangeText?.(text);\n },\n [isControlled, onChangeText],\n );\n\n const handleFocus = useCallback<NonNullable<TextInputProps['onFocus']>>(\n (e) => {\n setIsFocused(true);\n onFocus?.(e);\n },\n [onFocus],\n );\n\n const handleBlur = useCallback<NonNullable<TextInputProps['onBlur']>>(\n (e) => {\n setIsFocused(false);\n onBlur?.(e);\n },\n [onBlur],\n );\n\n /* --------------------------------- Styles --------------------------------- */\n inputStyles.useVariants({\n size,\n value: valueState,\n pressed: isFocused,\n readonly: readOnly,\n invalid: hasError,\n });\n\n // Get placeholder color from theme styles\n const computedPlaceholderColor = placeholderTextColor ?? inputStyles.inputPlaceholder.color;\n\n const rootStyle = useMemo(() => [{ width, opacity: disabled ? 0.5 : 1 }], [width, disabled]);\n\n // `inputWrapper` carries only background/border colors; the size-axis spacing\n // (`inputWrapperStatic`) and border width/radius (`inputWrapperDynamic`) live\n // on their own layers after the wrapper split. All three must be applied to\n // the wrapper View — matching the web component, which drives the static and\n // dynamic layers via separate variants — or the input loses its padding, gap,\n // and border sizing.\n const inputWrapperStyle = useMemo(\n () => [\n inputStyles.inputWrapper,\n inputStyles.inputWrapperStatic,\n inputStyles.inputWrapperDynamic,\n { flexDirection: 'row' as const, alignItems: 'center' as const },\n ],\n [inputStyles.inputWrapper, inputStyles.inputWrapperStatic, inputStyles.inputWrapperDynamic],\n );\n\n // Android-specific fixes: TextInput on Android has rendering issues with text visibility\n // - includeFontPadding: false removes Android's extra font padding that can clip text\n // - textAlignVertical: 'center' ensures text is vertically centered in the input\n // - paddingVertical: 0 removes default padding that interferes with flex layout\n const textInputStyle = useMemo(\n () => [\n inputStyles.input,\n {\n flex: 1,\n includeFontPadding: false,\n textAlignVertical: 'center' as const,\n paddingVertical: 0,\n },\n ],\n [inputStyles.input],\n );\n\n /* ---------------------------- Render Helpers ------------------------------ */\n const labelContent = useMemo(() => {\n if (!label) {\n return null;\n }\n const content = isFunction(label) ? label() : label;\n return (\n <HStack columnGap=\"1\" alignItems=\"flex-end\" spacingBottom=\"2\">\n <Text style={inputStyles.label}>{content}</Text>\n {required && <Text style={inputStyles.labelRequired}>*</Text>}\n </HStack>\n );\n }, [label, required, inputStyles.label, inputStyles.labelRequired]);\n\n const startIconContent = useMemo(() => {\n if (!startIcon) {\n return null;\n }\n return <IconSlot icon={startIcon} variant=\"outline\" style={inputStyles.startIcon} />;\n }, [startIcon, inputStyles.startIcon]);\n\n const endIconContent = useMemo(() => {\n if (!endIcon) {\n return null;\n }\n return <IconSlot icon={endIcon} variant=\"outline\" style={inputStyles.endIcon} />;\n }, [endIcon, inputStyles.endIcon]);\n\n const helpTextContent = useMemo(() => {\n if (!helpText) {\n return null;\n }\n const content = isFunction(helpText) ? helpText() : helpText;\n return (\n <InputHelpText\n startIcon={helperTextIcon}\n size={size}\n isFilled={valueState === 'filled'}\n disabled={disabled}\n readOnly={readOnly}\n hasError={hasError}\n pressed={isFocused}\n >\n {content}\n </InputHelpText>\n );\n }, [disabled, hasError, helpText, helperTextIcon, isFocused, readOnly, size, valueState]);\n\n /* --------------------------------- Render --------------------------------- */\n return (\n <VStack style={rootStyle}>\n {labelContent}\n\n <View\n style={inputWrapperStyle}\n accessible\n accessibilityRole=\"none\"\n accessibilityLabel={typeof label === 'string' ? label : undefined}\n >\n {startIconContent}\n\n <TextInput\n ref={ref}\n nativeID={uid}\n value={value}\n onChangeText={handleChangeText}\n onFocus={handleFocus}\n onBlur={handleBlur}\n placeholder={placeholder}\n placeholderTextColor={computedPlaceholderColor}\n editable={!disabled && !readOnly}\n style={textInputStyle}\n accessibilityLabel={typeof label === 'string' ? label : undefined}\n accessibilityHint={typeof helpText === 'string' ? helpText : undefined}\n accessibilityState={{ disabled }}\n {...textInputProps}\n />\n\n {endIconContent}\n </View>\n\n {helpTextContent}\n </VStack>\n );\n});\n\nInput.displayName = 'Input';\n\nexport { Input, type InputProps };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Input.js","names":["Text"],"sources":["../../src/components/Input.tsx"],"sourcesContent":["import type { UniversalInputProps } from '@yahoo/uds-types';\nimport { isFunction } from 'lodash-es';\nimport type { Ref } from 'react';\nimport { memo, useCallback, useId, useMemo, useState } from 'react';\nimport type { TextInputProps } from 'react-native';\nimport { TextInput, View } from 'react-native';\n\nimport { inputStyles } from '../../generated/styles';\nimport { useComponentFontScale } from '../fontScaling/useFontScale';\nimport { useMaxFontSizeMultiplier } from '../fontScaling/useMaxFontSizeMultiplier';\nimport type { SizeProps } from '../types';\nimport { HStack } from './HStack';\nimport type { IconSlotType } from './IconSlot';\nimport { IconSlot } from './IconSlot';\nimport { InputHelpText } from './InputHelpText';\nimport { Text } from './Text';\nimport { VStack } from './VStack';\n\n/* -------------------------------------------------------------------------- */\n/* Types */\n/* -------------------------------------------------------------------------- */\n\ninterface InputProps\n extends\n Omit<UniversalInputProps<IconSlotType>, 'width'>,\n Omit<TextInputProps, 'style' | 'editable'>,\n Pick<SizeProps, 'width'> {\n /** Ref to the underlying TextInput element */\n ref?: Ref<TextInput>;\n /**\n * Caps the field text, labels, icons, and geometry at the control scale.\n * Set null to remove the cap. @default 2\n */\n maxFontSizeMultiplier?: number | null;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Input Component */\n/* -------------------------------------------------------------------------- */\n\n/**\n * **📦 An input that allows users to enter text and collect data.**\n *\n * @description\n * An input field is a component that takes text typed into it. It can also serve\n * as a way to display a selection and trigger a dropdown menu. Inputs are interactive\n * elements that users can click, tap, or otherwise engage with to collect data.\n *\n * @category Form\n * @platform mobile\n *\n * @example\n * ```tsx\n * import { Input } from '@yahoo/uds-mobile/Input';\n *\n * <Input label=\"Name\" placeholder=\"Enter your name\" required />\n * <Input label=\"Email\" startIcon=\"Mail\" helpText=\"We'll never share your email\" />\n * <Input label=\"Password\" secureTextEntry hasError helpText=\"Password is required\" />\n * ```\n *\n * @usage\n * - Forms: For collecting data like names, emails, passwords, etc.\n * - Search Bars: Allowing users to enter search queries\n * - Filters/Settings: When users need to specify preferences\n * - Feedback/Comments: Letting users leave reviews or comments\n *\n * @accessibility\n * - Label is automatically associated with the input\n * - Help text is announced as accessibility hint\n * - Error state is communicated to screen readers\n * - Disabled state prevents interaction\n *\n * @see {@link Checkbox} for boolean selections\n * @see {@link Radio} for single-select options\n */\nconst Input = memo(function Input({\n // Input props\n label,\n size = 'md',\n startIcon,\n endIcon,\n helpText,\n helperTextIcon,\n hasError,\n disabled,\n readOnly,\n required,\n // Size props\n width = '100%',\n // TextInput props\n defaultValue,\n value: controlledValue,\n onChangeText,\n onFocus,\n onBlur,\n placeholder,\n placeholderTextColor,\n maxFontSizeMultiplier,\n ref,\n ...textInputProps\n}: InputProps) {\n const generatedId = useId();\n const uid = `uds-input-${generatedId}`;\n const resolvedMaxFontSizeMultiplier = useMaxFontSizeMultiplier('control', maxFontSizeMultiplier);\n\n /* --------------------------------- State ---------------------------------- */\n const [internalValue, setInternalValue] = useState(defaultValue ?? '');\n const [isFocused, setIsFocused] = useState(false);\n\n // Support both controlled and uncontrolled modes\n const isControlled = controlledValue !== undefined;\n const value = isControlled ? controlledValue : internalValue;\n const valueState = value ? 'filled' : 'empty';\n\n /* -------------------------------- Handlers -------------------------------- */\n const handleChangeText = useCallback(\n (text: string) => {\n if (!isControlled) {\n setInternalValue(text);\n }\n onChangeText?.(text);\n },\n [isControlled, onChangeText],\n );\n\n const handleFocus = useCallback<NonNullable<TextInputProps['onFocus']>>(\n (e) => {\n setIsFocused(true);\n onFocus?.(e);\n },\n [onFocus],\n );\n\n const handleBlur = useCallback<NonNullable<TextInputProps['onBlur']>>(\n (e) => {\n setIsFocused(false);\n onBlur?.(e);\n },\n [onBlur],\n );\n\n /* --------------------------------- Styles --------------------------------- */\n inputStyles.useVariants({\n size,\n value: valueState,\n pressed: isFocused,\n readonly: readOnly,\n invalid: hasError,\n });\n const fontScaleFactor = useComponentFontScale(\n resolvedMaxFontSizeMultiplier,\n (typeof inputStyles.inputWrapperStatic.lineHeight === 'number'\n ? inputStyles.inputWrapperStatic.lineHeight\n : undefined) ??\n (typeof inputStyles.inputWrapperStatic.fontSize === 'number'\n ? inputStyles.inputWrapperStatic.fontSize\n : undefined),\n );\n\n // Get placeholder color from theme styles\n const computedPlaceholderColor = placeholderTextColor ?? inputStyles.inputPlaceholder.color;\n\n const rootStyle = useMemo(() => [{ width, opacity: disabled ? 0.5 : 1 }], [width, disabled]);\n\n // `inputWrapper` carries only background/border colors; the size-axis spacing\n // (`inputWrapperStatic`) and border width/radius (`inputWrapperDynamic`) live\n // on their own layers after the wrapper split. All three must be applied to\n // the wrapper View — matching the web component, which drives the static and\n // dynamic layers via separate variants — or the input loses its padding, gap,\n // and border sizing.\n const inputWrapperStyle = useMemo(() => {\n const { gap, paddingVertical } = inputStyles.inputWrapperStatic;\n const scaledMetrics =\n fontScaleFactor === 1\n ? undefined\n : {\n ...(typeof gap === 'number' && { gap: Math.round(gap * fontScaleFactor) }),\n ...(typeof paddingVertical === 'number' && {\n paddingVertical: Math.round(paddingVertical * fontScaleFactor),\n }),\n };\n\n return [\n inputStyles.inputWrapper,\n inputStyles.inputWrapperStatic,\n inputStyles.inputWrapperDynamic,\n scaledMetrics,\n { flexDirection: 'row' as const, alignItems: 'center' as const },\n ];\n }, [\n fontScaleFactor,\n inputStyles.inputWrapper,\n inputStyles.inputWrapperStatic,\n inputStyles.inputWrapperDynamic,\n ]);\n\n // Android-specific fixes: TextInput on Android has rendering issues with text visibility\n // - includeFontPadding: false removes Android's extra font padding that can clip text\n // - textAlignVertical: 'center' ensures text is vertically centered in the input\n // - paddingVertical: 0 removes default padding that interferes with flex layout\n const textInputStyle = useMemo(() => {\n const inputHeight = inputStyles.input.height;\n return [\n inputStyles.input,\n typeof inputHeight === 'number'\n ? { height: Math.round(inputHeight * fontScaleFactor) }\n : undefined,\n {\n flex: 1,\n includeFontPadding: false,\n textAlignVertical: 'center' as const,\n paddingVertical: 0,\n },\n ];\n }, [fontScaleFactor, inputStyles.input]);\n\n /* ---------------------------- Render Helpers ------------------------------ */\n const labelContent = useMemo(() => {\n if (!label) {\n return null;\n }\n const content = isFunction(label) ? label() : label;\n return (\n <HStack columnGap=\"1\" alignItems=\"flex-end\" spacingBottom=\"2\">\n <Text\n variant=\"inherit\"\n maxFontSizeMultiplier={resolvedMaxFontSizeMultiplier}\n style={inputStyles.label}\n >\n {content}\n </Text>\n {required && (\n <Text\n variant=\"inherit\"\n maxFontSizeMultiplier={resolvedMaxFontSizeMultiplier}\n style={inputStyles.labelRequired}\n >\n *\n </Text>\n )}\n </HStack>\n );\n }, [\n label,\n required,\n resolvedMaxFontSizeMultiplier,\n inputStyles.label,\n inputStyles.labelRequired,\n ]);\n\n const startIconContent = useMemo(() => {\n if (!startIcon) {\n return null;\n }\n return (\n <IconSlot\n icon={startIcon}\n variant=\"outline\"\n maxFontSizeMultiplier={resolvedMaxFontSizeMultiplier}\n style={inputStyles.startIcon}\n />\n );\n }, [startIcon, resolvedMaxFontSizeMultiplier, inputStyles.startIcon]);\n\n const endIconContent = useMemo(() => {\n if (!endIcon) {\n return null;\n }\n return (\n <IconSlot\n icon={endIcon}\n variant=\"outline\"\n maxFontSizeMultiplier={resolvedMaxFontSizeMultiplier}\n style={inputStyles.endIcon}\n />\n );\n }, [endIcon, resolvedMaxFontSizeMultiplier, inputStyles.endIcon]);\n\n const helpTextContent = useMemo(() => {\n if (!helpText) {\n return null;\n }\n const content = isFunction(helpText) ? helpText() : helpText;\n return (\n <InputHelpText\n startIcon={helperTextIcon}\n size={size}\n isFilled={valueState === 'filled'}\n disabled={disabled}\n readOnly={readOnly}\n hasError={hasError}\n pressed={isFocused}\n maxFontSizeMultiplier={resolvedMaxFontSizeMultiplier}\n >\n {content}\n </InputHelpText>\n );\n }, [\n disabled,\n hasError,\n helpText,\n helperTextIcon,\n isFocused,\n readOnly,\n resolvedMaxFontSizeMultiplier,\n size,\n valueState,\n ]);\n\n /* --------------------------------- Render --------------------------------- */\n return (\n <VStack style={rootStyle}>\n {labelContent}\n\n <View\n style={inputWrapperStyle}\n accessible\n accessibilityRole=\"none\"\n accessibilityLabel={typeof label === 'string' ? label : undefined}\n >\n {startIconContent}\n\n <TextInput\n ref={ref}\n nativeID={uid}\n value={value}\n onChangeText={handleChangeText}\n onFocus={handleFocus}\n onBlur={handleBlur}\n placeholder={placeholder}\n placeholderTextColor={computedPlaceholderColor}\n editable={!disabled && !readOnly}\n maxFontSizeMultiplier={resolvedMaxFontSizeMultiplier}\n style={textInputStyle}\n accessibilityLabel={typeof label === 'string' ? label : undefined}\n accessibilityHint={typeof helpText === 'string' ? helpText : undefined}\n accessibilityState={{ disabled }}\n {...textInputProps}\n />\n\n {endIconContent}\n </View>\n\n {helpTextContent}\n </VStack>\n );\n});\n\nInput.displayName = 'Input';\n\nexport { Input, type InputProps };\n"],"mappings":"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"}
|
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
require("../_virtual/_rolldown/runtime.cjs");
|
|
4
4
|
const require_components_HStack = require("./HStack.cjs");
|
|
5
|
+
const require_fontScaling_useMaxFontSizeMultiplier = require("../fontScaling/useMaxFontSizeMultiplier.cjs");
|
|
5
6
|
const require_components_IconSlot = require("./IconSlot.cjs");
|
|
6
7
|
const require_components_Text = require("./Text.cjs");
|
|
7
8
|
let react = require("react");
|
|
8
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
10
|
let generated_styles = require("../../generated/styles");
|
|
10
11
|
//#region src/components/InputHelpText.tsx
|
|
11
|
-
const InputHelpText = (0, react.memo)(function InputHelpText({ children, startIcon, endIcon, iconProps, size = "md", isFilled = false, disabled: _disabled, readOnly, hasError, pressed, ref, ...hStackProps }) {
|
|
12
|
+
const InputHelpText = (0, react.memo)(function InputHelpText({ children, startIcon, endIcon, iconProps, size = "md", isFilled = false, disabled: _disabled, readOnly, hasError, pressed, maxFontSizeMultiplier, ref, ...hStackProps }) {
|
|
12
13
|
const { style: iconStyle, variant = "outline", ...resolvedIconProps } = iconProps ?? {};
|
|
13
14
|
const value = isFilled ? "filled" : "empty";
|
|
15
|
+
const resolvedMaxFontSizeMultiplier = require_fontScaling_useMaxFontSizeMultiplier.useMaxFontSizeMultiplier("control", maxFontSizeMultiplier);
|
|
14
16
|
generated_styles.inputStyles.useVariants({
|
|
15
17
|
size,
|
|
16
18
|
value,
|
|
@@ -30,16 +32,20 @@ const InputHelpText = (0, react.memo)(function InputHelpText({ children, startIc
|
|
|
30
32
|
startIcon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_IconSlot.IconSlot, {
|
|
31
33
|
icon: startIcon,
|
|
32
34
|
variant,
|
|
35
|
+
maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
|
|
33
36
|
style: resolvedIconStyle,
|
|
34
37
|
...resolvedIconProps
|
|
35
38
|
}),
|
|
36
39
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_Text.Text, {
|
|
40
|
+
variant: "inherit",
|
|
41
|
+
maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
|
|
37
42
|
style: generated_styles.inputStyles.helperText,
|
|
38
43
|
children
|
|
39
44
|
}),
|
|
40
45
|
endIcon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_IconSlot.IconSlot, {
|
|
41
46
|
icon: endIcon,
|
|
42
47
|
variant,
|
|
48
|
+
maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
|
|
43
49
|
style: resolvedIconStyle,
|
|
44
50
|
...resolvedIconProps
|
|
45
51
|
})
|
|
@@ -30,6 +30,8 @@ interface InputHelpTextProps extends Omit<HStackProps, 'children' | 'ref'> {
|
|
|
30
30
|
hasError?: boolean;
|
|
31
31
|
/** Whether the associated input is focused or pressed. */
|
|
32
32
|
pressed?: boolean;
|
|
33
|
+
/** Caps helper text and icons at the control scale. Set null to remove the cap. @default 2 */
|
|
34
|
+
maxFontSizeMultiplier?: number | null;
|
|
33
35
|
}
|
|
34
36
|
declare const InputHelpText: _$react.NamedExoticComponent<InputHelpTextProps>;
|
|
35
37
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InputHelpText.d.cts","names":[],"sources":["../../src/components/InputHelpText.tsx"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"InputHelpText.d.cts","names":[],"sources":["../../src/components/InputHelpText.tsx"],"mappings":";;;;;;;;;UAcU,kBAAA,SAA2B,IAAA,CAAK,WAAA;;EAExC,GAAA,GAAM,GAAA,CAAI,IAAA;EAFF;EAIR,QAAA,GAAW,SAAA;;EAEX,SAAA,GAAY,YAAA;EAJF;EAMV,OAAA,GAAU,YAAA;EAJC;EAMX,SAAA,GAAY,IAAA,CAAK,aAAA;EAFP;EAIV,IAAA,GAAO,SAAA;EAFK;EAIZ,QAAA;EAdmC;EAgBnC,QAAA;EAhBuC;EAkBvC,QAAA;EAlBwC;EAoBxC,QAAA;EAlBM;EAoBN,OAAA;EAlBA;EAoBA,qBAAA;AAAA;AAAA,cAGI,aAAA,EAAa,OAAA,CAAA,oBAAA,CAAA,kBAAA"}
|
|
@@ -30,6 +30,8 @@ interface InputHelpTextProps extends Omit<HStackProps, 'children' | 'ref'> {
|
|
|
30
30
|
hasError?: boolean;
|
|
31
31
|
/** Whether the associated input is focused or pressed. */
|
|
32
32
|
pressed?: boolean;
|
|
33
|
+
/** Caps helper text and icons at the control scale. Set null to remove the cap. @default 2 */
|
|
34
|
+
maxFontSizeMultiplier?: number | null;
|
|
33
35
|
}
|
|
34
36
|
declare const InputHelpText: _$react.NamedExoticComponent<InputHelpTextProps>;
|
|
35
37
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InputHelpText.d.ts","names":[],"sources":["../../src/components/InputHelpText.tsx"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"InputHelpText.d.ts","names":[],"sources":["../../src/components/InputHelpText.tsx"],"mappings":";;;;;;;;;UAcU,kBAAA,SAA2B,IAAA,CAAK,WAAA;;EAExC,GAAA,GAAM,GAAA,CAAI,IAAA;EAFF;EAIR,QAAA,GAAW,SAAA;;EAEX,SAAA,GAAY,YAAA;EAJF;EAMV,OAAA,GAAU,YAAA;EAJC;EAMX,SAAA,GAAY,IAAA,CAAK,aAAA;EAFP;EAIV,IAAA,GAAO,SAAA;EAFK;EAIZ,QAAA;EAdmC;EAgBnC,QAAA;EAhBuC;EAkBvC,QAAA;EAlBwC;EAoBxC,QAAA;EAlBM;EAoBN,OAAA;EAlBA;EAoBA,qBAAA;AAAA;AAAA,cAGI,aAAA,EAAa,OAAA,CAAA,oBAAA,CAAA,kBAAA"}
|