@yahoo/uds-mobile 2.21.1 → 2.21.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/automated-config/dist/generated/generatedConfigs.mjs +67 -51
- package/dist/components/Button/buttonTheme.cjs +69 -0
- package/dist/components/Button/buttonTheme.d.cts +24 -0
- package/dist/components/Button/buttonTheme.d.cts.map +1 -0
- package/dist/components/Button/buttonTheme.d.ts +24 -0
- package/dist/components/Button/buttonTheme.d.ts.map +1 -0
- package/dist/components/Button/buttonTheme.js +68 -0
- package/dist/components/Button/buttonTheme.js.map +1 -0
- package/dist/components/Button.cjs +7 -1
- package/dist/components/Button.d.cts.map +1 -1
- package/dist/components/Button.d.ts.map +1 -1
- package/dist/components/Button.js +7 -1
- package/dist/components/Button.js.map +1 -1
- package/dist/components/IconButton.cjs +9 -0
- package/dist/components/IconButton.d.cts.map +1 -1
- package/dist/components/IconButton.d.ts.map +1 -1
- package/dist/components/IconButton.js +9 -0
- package/dist/components/IconButton.js.map +1 -1
- package/generated/styles.d.ts +1 -1
- package/package.json +1 -1
|
@@ -18617,7 +18617,7 @@ const IconButtonConfig = {
|
|
|
18617
18617
|
label: "icon",
|
|
18618
18618
|
properties: { size: {
|
|
18619
18619
|
defaults: {
|
|
18620
|
-
lg: "
|
|
18620
|
+
lg: "sm",
|
|
18621
18621
|
md: "sm",
|
|
18622
18622
|
sm: "sm",
|
|
18623
18623
|
xl: "lg",
|
|
@@ -18636,57 +18636,73 @@ const IconButtonConfig = {
|
|
|
18636
18636
|
},
|
|
18637
18637
|
root: {
|
|
18638
18638
|
label: "root",
|
|
18639
|
-
properties: {
|
|
18640
|
-
|
|
18641
|
-
|
|
18642
|
-
|
|
18643
|
-
|
|
18644
|
-
|
|
18645
|
-
|
|
18639
|
+
properties: {
|
|
18640
|
+
controlHeight: {
|
|
18641
|
+
defaults: {
|
|
18642
|
+
lg: 44,
|
|
18643
|
+
md: 32,
|
|
18644
|
+
sm: 24,
|
|
18645
|
+
xl: 64,
|
|
18646
|
+
xs: 28
|
|
18647
|
+
},
|
|
18648
|
+
label: "Control height",
|
|
18649
|
+
name: "controlHeight",
|
|
18650
|
+
optionalInDefaultSchema: true,
|
|
18651
|
+
typeOfFixture: ["positiveIntegers"],
|
|
18652
|
+
values: []
|
|
18646
18653
|
},
|
|
18647
|
-
|
|
18648
|
-
|
|
18649
|
-
|
|
18650
|
-
|
|
18651
|
-
|
|
18652
|
-
|
|
18653
|
-
|
|
18654
|
-
|
|
18655
|
-
"
|
|
18656
|
-
"
|
|
18657
|
-
"
|
|
18658
|
-
|
|
18659
|
-
|
|
18660
|
-
|
|
18661
|
-
|
|
18662
|
-
|
|
18663
|
-
|
|
18664
|
-
|
|
18665
|
-
|
|
18666
|
-
|
|
18667
|
-
|
|
18668
|
-
|
|
18669
|
-
|
|
18670
|
-
|
|
18671
|
-
|
|
18672
|
-
|
|
18673
|
-
|
|
18674
|
-
|
|
18675
|
-
|
|
18676
|
-
|
|
18677
|
-
|
|
18678
|
-
|
|
18679
|
-
|
|
18680
|
-
|
|
18681
|
-
|
|
18682
|
-
|
|
18683
|
-
|
|
18684
|
-
|
|
18685
|
-
|
|
18686
|
-
|
|
18687
|
-
|
|
18688
|
-
|
|
18689
|
-
|
|
18654
|
+
spacing: {
|
|
18655
|
+
defaults: {
|
|
18656
|
+
lg: "3.5",
|
|
18657
|
+
md: "3",
|
|
18658
|
+
sm: "2",
|
|
18659
|
+
xl: "4",
|
|
18660
|
+
xs: "2"
|
|
18661
|
+
},
|
|
18662
|
+
label: "Spacing",
|
|
18663
|
+
name: "spacing",
|
|
18664
|
+
typeOfFixture: ["spacingAliases"],
|
|
18665
|
+
values: [[
|
|
18666
|
+
"0",
|
|
18667
|
+
"px",
|
|
18668
|
+
"0.5",
|
|
18669
|
+
"1",
|
|
18670
|
+
"1.5",
|
|
18671
|
+
"2",
|
|
18672
|
+
"2.5",
|
|
18673
|
+
"3",
|
|
18674
|
+
"3.5",
|
|
18675
|
+
"4",
|
|
18676
|
+
"4.5",
|
|
18677
|
+
"5",
|
|
18678
|
+
"5.5",
|
|
18679
|
+
"6",
|
|
18680
|
+
"7",
|
|
18681
|
+
"8",
|
|
18682
|
+
"9",
|
|
18683
|
+
"10",
|
|
18684
|
+
"11",
|
|
18685
|
+
"12",
|
|
18686
|
+
"14",
|
|
18687
|
+
"16",
|
|
18688
|
+
"20",
|
|
18689
|
+
"24",
|
|
18690
|
+
"28",
|
|
18691
|
+
"32",
|
|
18692
|
+
"36",
|
|
18693
|
+
"40",
|
|
18694
|
+
"44",
|
|
18695
|
+
"48",
|
|
18696
|
+
"52",
|
|
18697
|
+
"56",
|
|
18698
|
+
"60",
|
|
18699
|
+
"64",
|
|
18700
|
+
"72",
|
|
18701
|
+
"80",
|
|
18702
|
+
"96"
|
|
18703
|
+
]]
|
|
18704
|
+
}
|
|
18705
|
+
}
|
|
18690
18706
|
}
|
|
18691
18707
|
},
|
|
18692
18708
|
options: [
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/*! © 2026 Yahoo, Inc. UDS Mobile v0.0.0-development */
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
//#region src/components/Button/buttonTheme.ts
|
|
4
|
+
const SHARED_BUTTON_ICONBUTTON_SIZES = new Set([
|
|
5
|
+
"xs",
|
|
6
|
+
"sm",
|
|
7
|
+
"md",
|
|
8
|
+
"lg"
|
|
9
|
+
]);
|
|
10
|
+
function buttonSizePath(size, layer) {
|
|
11
|
+
return `button/size/${size}/${layer}/rest`;
|
|
12
|
+
}
|
|
13
|
+
function iconButtonSizePath(size, layer) {
|
|
14
|
+
return `iconButton/size/${size}/${layer}/rest`;
|
|
15
|
+
}
|
|
16
|
+
function getLayerStyle(theme, path) {
|
|
17
|
+
const style = theme.components[path];
|
|
18
|
+
return style && typeof style === "object" ? style : {};
|
|
19
|
+
}
|
|
20
|
+
function getIconSize(iconStyle) {
|
|
21
|
+
return (typeof iconStyle.fontSize === "number" ? iconStyle.fontSize : void 0) ?? (typeof iconStyle.lineHeight === "number" ? iconStyle.lineHeight : void 0) ?? 16;
|
|
22
|
+
}
|
|
23
|
+
function getVerticalPadding(rootStyle) {
|
|
24
|
+
return (typeof rootStyle.paddingVertical === "number" ? rootStyle.paddingVertical : void 0) ?? (typeof rootStyle.padding === "number" ? rootStyle.padding : void 0) ?? 0;
|
|
25
|
+
}
|
|
26
|
+
function getButtonContentSize(iconSize, textStyle) {
|
|
27
|
+
const lineHeight = typeof textStyle.lineHeight === "number" ? textStyle.lineHeight : void 0;
|
|
28
|
+
const fontSize = typeof textStyle.fontSize === "number" ? textStyle.fontSize : iconSize;
|
|
29
|
+
return Math.max(iconSize, lineHeight ?? fontSize);
|
|
30
|
+
}
|
|
31
|
+
/** Control height from vertical padding plus max(icon size, text line-height), matching web. */
|
|
32
|
+
function getButtonControlMetrics(theme, size) {
|
|
33
|
+
const rootStyle = getLayerStyle(theme, buttonSizePath(size, "root"));
|
|
34
|
+
const iconStyle = getLayerStyle(theme, buttonSizePath(size, "icon"));
|
|
35
|
+
const textStyle = getLayerStyle(theme, buttonSizePath(size, "rootText"));
|
|
36
|
+
const paddingVertical = getVerticalPadding(rootStyle);
|
|
37
|
+
const iconSize = getIconSize(iconStyle);
|
|
38
|
+
const contentSize = getButtonContentSize(iconSize, textStyle);
|
|
39
|
+
return {
|
|
40
|
+
controlHeight: Math.ceil(paddingVertical * 2 + contentSize),
|
|
41
|
+
contentLineHeight: Math.ceil(contentSize),
|
|
42
|
+
iconSize
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** IconButton uses explicit control height only when CSS sets height (match-button-height opt-in). */
|
|
46
|
+
function getIconButtonControlMetrics(theme, size) {
|
|
47
|
+
const rootStyle = getLayerStyle(theme, iconButtonSizePath(size, "root"));
|
|
48
|
+
const iconSize = getIconSize(getLayerStyle(theme, iconButtonSizePath(size, "icon")));
|
|
49
|
+
const explicitHeight = typeof rootStyle.height === "number" ? rootStyle.height : void 0;
|
|
50
|
+
if (SHARED_BUTTON_ICONBUTTON_SIZES.has(size) && explicitHeight !== void 0) return {
|
|
51
|
+
controlHeight: explicitHeight,
|
|
52
|
+
iconSize
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
controlHeight: 0,
|
|
56
|
+
iconSize
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Label line-height matches content box (max of icon size and line-height). */
|
|
60
|
+
function getButtonTextStyleForControlHeight(textStyle, contentLineHeight) {
|
|
61
|
+
return {
|
|
62
|
+
...textStyle,
|
|
63
|
+
lineHeight: contentLineHeight
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
exports.getButtonControlMetrics = getButtonControlMetrics;
|
|
68
|
+
exports.getButtonTextStyleForControlHeight = getButtonTextStyleForControlHeight;
|
|
69
|
+
exports.getIconButtonControlMetrics = getIconButtonControlMetrics;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
import { ButtonSize, IconButtonSize } from "../../types/dist/index.cjs";
|
|
3
|
+
import { TextStyle } from "react-native";
|
|
4
|
+
|
|
5
|
+
//#region src/components/Button/buttonTheme.d.ts
|
|
6
|
+
type ButtonTheme = {
|
|
7
|
+
components: Record<string, any>;
|
|
8
|
+
};
|
|
9
|
+
/** Control height from vertical padding plus max(icon size, text line-height), matching web. */
|
|
10
|
+
declare function getButtonControlMetrics(theme: ButtonTheme, size: ButtonSize): {
|
|
11
|
+
controlHeight: number;
|
|
12
|
+
contentLineHeight: number;
|
|
13
|
+
iconSize: number;
|
|
14
|
+
};
|
|
15
|
+
/** IconButton uses explicit control height only when CSS sets height (match-button-height opt-in). */
|
|
16
|
+
declare function getIconButtonControlMetrics(theme: ButtonTheme, size: IconButtonSize): {
|
|
17
|
+
controlHeight: number;
|
|
18
|
+
iconSize: number;
|
|
19
|
+
};
|
|
20
|
+
/** Label line-height matches content box (max of icon size and line-height). */
|
|
21
|
+
declare function getButtonTextStyleForControlHeight(textStyle: TextStyle, contentLineHeight: number): TextStyle;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { getButtonControlMetrics, getButtonTextStyleForControlHeight, getIconButtonControlMetrics };
|
|
24
|
+
//# sourceMappingURL=buttonTheme.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buttonTheme.d.cts","names":[],"sources":["../../../src/components/Button/buttonTheme.ts"],"mappings":";;;;;KAGK,WAAA;EAEH,UAAA,EAAY,MAAA;AAAA;;iBAyCE,uBAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,UAAA;EACH,aAAA;EAAuB,iBAAA;EAA2B,QAAA;AAAA;;iBAgBvC,2BAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,cAAA;EACH,aAAA;EAAuB,QAAA;AAAA;;iBAcZ,kCAAA,CACd,SAAA,EAAW,SAAA,EACX,iBAAA,WACC,SAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
import { ButtonSize, IconButtonSize } from "../../types/dist/index.js";
|
|
3
|
+
import { TextStyle } from "react-native";
|
|
4
|
+
|
|
5
|
+
//#region src/components/Button/buttonTheme.d.ts
|
|
6
|
+
type ButtonTheme = {
|
|
7
|
+
components: Record<string, any>;
|
|
8
|
+
};
|
|
9
|
+
/** Control height from vertical padding plus max(icon size, text line-height), matching web. */
|
|
10
|
+
declare function getButtonControlMetrics(theme: ButtonTheme, size: ButtonSize): {
|
|
11
|
+
controlHeight: number;
|
|
12
|
+
contentLineHeight: number;
|
|
13
|
+
iconSize: number;
|
|
14
|
+
};
|
|
15
|
+
/** IconButton uses explicit control height only when CSS sets height (match-button-height opt-in). */
|
|
16
|
+
declare function getIconButtonControlMetrics(theme: ButtonTheme, size: IconButtonSize): {
|
|
17
|
+
controlHeight: number;
|
|
18
|
+
iconSize: number;
|
|
19
|
+
};
|
|
20
|
+
/** Label line-height matches content box (max of icon size and line-height). */
|
|
21
|
+
declare function getButtonTextStyleForControlHeight(textStyle: TextStyle, contentLineHeight: number): TextStyle;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { getButtonControlMetrics, getButtonTextStyleForControlHeight, getIconButtonControlMetrics };
|
|
24
|
+
//# sourceMappingURL=buttonTheme.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buttonTheme.d.ts","names":[],"sources":["../../../src/components/Button/buttonTheme.ts"],"mappings":";;;;;KAGK,WAAA;EAEH,UAAA,EAAY,MAAA;AAAA;;iBAyCE,uBAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,UAAA;EACH,aAAA;EAAuB,iBAAA;EAA2B,QAAA;AAAA;;iBAgBvC,2BAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,cAAA;EACH,aAAA;EAAuB,QAAA;AAAA;;iBAcZ,kCAAA,CACd,SAAA,EAAW,SAAA,EACX,iBAAA,WACC,SAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*! © 2026 Yahoo, Inc. UDS Mobile v0.0.0-development */
|
|
2
|
+
//#region src/components/Button/buttonTheme.ts
|
|
3
|
+
const SHARED_BUTTON_ICONBUTTON_SIZES = new Set([
|
|
4
|
+
"xs",
|
|
5
|
+
"sm",
|
|
6
|
+
"md",
|
|
7
|
+
"lg"
|
|
8
|
+
]);
|
|
9
|
+
function buttonSizePath(size, layer) {
|
|
10
|
+
return `button/size/${size}/${layer}/rest`;
|
|
11
|
+
}
|
|
12
|
+
function iconButtonSizePath(size, layer) {
|
|
13
|
+
return `iconButton/size/${size}/${layer}/rest`;
|
|
14
|
+
}
|
|
15
|
+
function getLayerStyle(theme, path) {
|
|
16
|
+
const style = theme.components[path];
|
|
17
|
+
return style && typeof style === "object" ? style : {};
|
|
18
|
+
}
|
|
19
|
+
function getIconSize(iconStyle) {
|
|
20
|
+
return (typeof iconStyle.fontSize === "number" ? iconStyle.fontSize : void 0) ?? (typeof iconStyle.lineHeight === "number" ? iconStyle.lineHeight : void 0) ?? 16;
|
|
21
|
+
}
|
|
22
|
+
function getVerticalPadding(rootStyle) {
|
|
23
|
+
return (typeof rootStyle.paddingVertical === "number" ? rootStyle.paddingVertical : void 0) ?? (typeof rootStyle.padding === "number" ? rootStyle.padding : void 0) ?? 0;
|
|
24
|
+
}
|
|
25
|
+
function getButtonContentSize(iconSize, textStyle) {
|
|
26
|
+
const lineHeight = typeof textStyle.lineHeight === "number" ? textStyle.lineHeight : void 0;
|
|
27
|
+
const fontSize = typeof textStyle.fontSize === "number" ? textStyle.fontSize : iconSize;
|
|
28
|
+
return Math.max(iconSize, lineHeight ?? fontSize);
|
|
29
|
+
}
|
|
30
|
+
/** Control height from vertical padding plus max(icon size, text line-height), matching web. */
|
|
31
|
+
function getButtonControlMetrics(theme, size) {
|
|
32
|
+
const rootStyle = getLayerStyle(theme, buttonSizePath(size, "root"));
|
|
33
|
+
const iconStyle = getLayerStyle(theme, buttonSizePath(size, "icon"));
|
|
34
|
+
const textStyle = getLayerStyle(theme, buttonSizePath(size, "rootText"));
|
|
35
|
+
const paddingVertical = getVerticalPadding(rootStyle);
|
|
36
|
+
const iconSize = getIconSize(iconStyle);
|
|
37
|
+
const contentSize = getButtonContentSize(iconSize, textStyle);
|
|
38
|
+
return {
|
|
39
|
+
controlHeight: Math.ceil(paddingVertical * 2 + contentSize),
|
|
40
|
+
contentLineHeight: Math.ceil(contentSize),
|
|
41
|
+
iconSize
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** IconButton uses explicit control height only when CSS sets height (match-button-height opt-in). */
|
|
45
|
+
function getIconButtonControlMetrics(theme, size) {
|
|
46
|
+
const rootStyle = getLayerStyle(theme, iconButtonSizePath(size, "root"));
|
|
47
|
+
const iconSize = getIconSize(getLayerStyle(theme, iconButtonSizePath(size, "icon")));
|
|
48
|
+
const explicitHeight = typeof rootStyle.height === "number" ? rootStyle.height : void 0;
|
|
49
|
+
if (SHARED_BUTTON_ICONBUTTON_SIZES.has(size) && explicitHeight !== void 0) return {
|
|
50
|
+
controlHeight: explicitHeight,
|
|
51
|
+
iconSize
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
controlHeight: 0,
|
|
55
|
+
iconSize
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Label line-height matches content box (max of icon size and line-height). */
|
|
59
|
+
function getButtonTextStyleForControlHeight(textStyle, contentLineHeight) {
|
|
60
|
+
return {
|
|
61
|
+
...textStyle,
|
|
62
|
+
lineHeight: contentLineHeight
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { getButtonControlMetrics, getButtonTextStyleForControlHeight, getIconButtonControlMetrics };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=buttonTheme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buttonTheme.js","names":[],"sources":["../../../src/components/Button/buttonTheme.ts"],"sourcesContent":["import type { ButtonSize, IconButtonSize } from '@yahoo/uds-types';\nimport type { TextStyle, ViewStyle } from 'react-native';\n\ntype ButtonTheme = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n components: Record<string, any>;\n};\n\nconst SHARED_BUTTON_ICONBUTTON_SIZES = new Set<IconButtonSize>(['xs', 'sm', 'md', 'lg']);\n\nfunction buttonSizePath(size: ButtonSize, layer: 'root' | 'icon' | 'rootText'): string {\n return `button/size/${size}/${layer}/rest`;\n}\n\nfunction iconButtonSizePath(size: IconButtonSize, layer: 'root' | 'icon'): string {\n return `iconButton/size/${size}/${layer}/rest`;\n}\n\nfunction getLayerStyle(theme: ButtonTheme, path: string): ViewStyle | TextStyle {\n const style = theme.components[path];\n return style && typeof style === 'object' ? style : {};\n}\n\nfunction getIconSize(iconStyle: TextStyle): number {\n return (\n (typeof iconStyle.fontSize === 'number' ? iconStyle.fontSize : undefined) ??\n (typeof iconStyle.lineHeight === 'number' ? iconStyle.lineHeight : undefined) ??\n 16\n );\n}\n\nfunction getVerticalPadding(rootStyle: ViewStyle): number {\n return (\n (typeof rootStyle.paddingVertical === 'number' ? rootStyle.paddingVertical : undefined) ??\n (typeof rootStyle.padding === 'number' ? rootStyle.padding : undefined) ??\n 0\n );\n}\n\nfunction getButtonContentSize(iconSize: number, textStyle: TextStyle): number {\n const lineHeight = typeof textStyle.lineHeight === 'number' ? textStyle.lineHeight : undefined;\n const fontSize = typeof textStyle.fontSize === 'number' ? textStyle.fontSize : iconSize;\n return Math.max(iconSize, lineHeight ?? fontSize);\n}\n\n/** Control height from vertical padding plus max(icon size, text line-height), matching web. */\nexport function getButtonControlMetrics(\n theme: ButtonTheme,\n size: ButtonSize,\n): { controlHeight: number; contentLineHeight: number; iconSize: number } {\n const rootStyle = getLayerStyle(theme, buttonSizePath(size, 'root')) as ViewStyle;\n const iconStyle = getLayerStyle(theme, buttonSizePath(size, 'icon')) as TextStyle;\n const textStyle = getLayerStyle(theme, buttonSizePath(size, 'rootText')) as TextStyle;\n const paddingVertical = getVerticalPadding(rootStyle);\n const iconSize = getIconSize(iconStyle);\n const contentSize = getButtonContentSize(iconSize, textStyle);\n\n return {\n controlHeight: Math.ceil(paddingVertical * 2 + contentSize),\n contentLineHeight: Math.ceil(contentSize),\n iconSize,\n };\n}\n\n/** IconButton uses explicit control height only when CSS sets height (match-button-height opt-in). */\nexport function getIconButtonControlMetrics(\n theme: ButtonTheme,\n size: IconButtonSize,\n): { controlHeight: number; iconSize: number } {\n const rootStyle = getLayerStyle(theme, iconButtonSizePath(size, 'root')) as ViewStyle;\n const iconStyle = getLayerStyle(theme, iconButtonSizePath(size, 'icon')) as TextStyle;\n const iconSize = getIconSize(iconStyle);\n const explicitHeight = typeof rootStyle.height === 'number' ? rootStyle.height : undefined;\n\n if (SHARED_BUTTON_ICONBUTTON_SIZES.has(size) && explicitHeight !== undefined) {\n return { controlHeight: explicitHeight, iconSize };\n }\n\n return { controlHeight: 0, iconSize };\n}\n\n/** Label line-height matches content box (max of icon size and line-height). */\nexport function getButtonTextStyleForControlHeight(\n textStyle: TextStyle,\n contentLineHeight: number,\n): TextStyle {\n return {\n ...textStyle,\n lineHeight: contentLineHeight,\n };\n}\n"],"mappings":";;AAQA,MAAM,iCAAiC,IAAI,IAAoB;CAAC;CAAM;CAAM;CAAM;CAAK,CAAC;AAExF,SAAS,eAAe,MAAkB,OAA6C;CACrF,OAAO,eAAe,KAAK,GAAG,MAAM;;AAGtC,SAAS,mBAAmB,MAAsB,OAAgC;CAChF,OAAO,mBAAmB,KAAK,GAAG,MAAM;;AAG1C,SAAS,cAAc,OAAoB,MAAqC;CAC9E,MAAM,QAAQ,MAAM,WAAW;CAC/B,OAAO,SAAS,OAAO,UAAU,WAAW,QAAQ,EAAE;;AAGxD,SAAS,YAAY,WAA8B;CACjD,QACG,OAAO,UAAU,aAAa,WAAW,UAAU,WAAW,KAAA,OAC9D,OAAO,UAAU,eAAe,WAAW,UAAU,aAAa,KAAA,MACnE;;AAIJ,SAAS,mBAAmB,WAA8B;CACxD,QACG,OAAO,UAAU,oBAAoB,WAAW,UAAU,kBAAkB,KAAA,OAC5E,OAAO,UAAU,YAAY,WAAW,UAAU,UAAU,KAAA,MAC7D;;AAIJ,SAAS,qBAAqB,UAAkB,WAA8B;CAC5E,MAAM,aAAa,OAAO,UAAU,eAAe,WAAW,UAAU,aAAa,KAAA;CACrF,MAAM,WAAW,OAAO,UAAU,aAAa,WAAW,UAAU,WAAW;CAC/E,OAAO,KAAK,IAAI,UAAU,cAAc,SAAS;;;AAInD,SAAgB,wBACd,OACA,MACwE;CACxE,MAAM,YAAY,cAAc,OAAO,eAAe,MAAM,OAAO,CAAC;CACpE,MAAM,YAAY,cAAc,OAAO,eAAe,MAAM,OAAO,CAAC;CACpE,MAAM,YAAY,cAAc,OAAO,eAAe,MAAM,WAAW,CAAC;CACxE,MAAM,kBAAkB,mBAAmB,UAAU;CACrD,MAAM,WAAW,YAAY,UAAU;CACvC,MAAM,cAAc,qBAAqB,UAAU,UAAU;CAE7D,OAAO;EACL,eAAe,KAAK,KAAK,kBAAkB,IAAI,YAAY;EAC3D,mBAAmB,KAAK,KAAK,YAAY;EACzC;EACD;;;AAIH,SAAgB,4BACd,OACA,MAC6C;CAC7C,MAAM,YAAY,cAAc,OAAO,mBAAmB,MAAM,OAAO,CAAC;CAExE,MAAM,WAAW,YADC,cAAc,OAAO,mBAAmB,MAAM,OAAO,CACjC,CAAC;CACvC,MAAM,iBAAiB,OAAO,UAAU,WAAW,WAAW,UAAU,SAAS,KAAA;CAEjF,IAAI,+BAA+B,IAAI,KAAK,IAAI,mBAAmB,KAAA,GACjE,OAAO;EAAE,eAAe;EAAgB;EAAU;CAGpD,OAAO;EAAE,eAAe;EAAG;EAAU;;;AAIvC,SAAgB,mCACd,WACA,mBACW;CACX,OAAO;EACL,GAAG;EACH,YAAY;EACb"}
|
|
@@ -5,11 +5,13 @@ const require_index = require("../motion-tokens/dist/index.cjs");
|
|
|
5
5
|
const require_motion = require("../motion.cjs");
|
|
6
6
|
const require_components_IconSlot = require("./IconSlot.cjs");
|
|
7
7
|
const require_components_Text = require("./Text.cjs");
|
|
8
|
+
const require_components_Button_buttonTheme = require("./Button/buttonTheme.cjs");
|
|
8
9
|
const require_components_Pressable = require("./Pressable.cjs");
|
|
9
10
|
let react = require("react");
|
|
10
11
|
let react_native = require("react-native");
|
|
11
12
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
12
13
|
let generated_styles = require("../../generated/styles");
|
|
14
|
+
let react_native_unistyles = require("react-native-unistyles");
|
|
13
15
|
let react_native_reanimated = require("react-native-reanimated");
|
|
14
16
|
react_native_reanimated = require_runtime.__toESM(react_native_reanimated);
|
|
15
17
|
let react_native_unistyles_reanimated = require("react-native-unistyles/reanimated");
|
|
@@ -82,6 +84,8 @@ function AnimatedIconSlot({ children, visible, iconSize, gap }) {
|
|
|
82
84
|
*/
|
|
83
85
|
const Button = (0, react.memo)(function Button({ variant = "primary", size = "md", iconVariant = "outline", startIcon, endIcon, loading, disabled, width: _width, children, style, accessibilityLabel, accessibilityHint, disableEffects = false, onPressIn, onPressOut, ref, ...props }) {
|
|
84
86
|
const shouldAnimate = !disableEffects;
|
|
87
|
+
const { theme } = (0, react_native_unistyles.useUnistyles)();
|
|
88
|
+
const { controlHeight, contentLineHeight } = (0, react.useMemo)(() => require_components_Button_buttonTheme.getButtonControlMetrics(theme, size), [theme, size]);
|
|
85
89
|
const [pressed, setPressed] = (0, react.useState)(false);
|
|
86
90
|
generated_styles.buttonStyles.useVariants({
|
|
87
91
|
size,
|
|
@@ -114,7 +118,7 @@ const Button = (0, react.memo)(function Button({ variant = "primary", size = "md
|
|
|
114
118
|
const childrenNode = children && ((0, react.isValidElement)(children) ? children : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_Text.Text, {
|
|
115
119
|
numberOfLines: 1,
|
|
116
120
|
textAlign: "center",
|
|
117
|
-
style: generated_styles.buttonStyles.text,
|
|
121
|
+
style: require_components_Button_buttonTheme.getButtonTextStyleForControlHeight(generated_styles.buttonStyles.text, contentLineHeight),
|
|
118
122
|
children
|
|
119
123
|
}));
|
|
120
124
|
const a11yState = (0, react.useMemo)(() => ({
|
|
@@ -163,10 +167,12 @@ const Button = (0, react.memo)(function Button({ variant = "primary", size = "md
|
|
|
163
167
|
});
|
|
164
168
|
const rootStyles = (0, react.useMemo)(() => [
|
|
165
169
|
generated_styles.buttonStyles.root,
|
|
170
|
+
{ height: controlHeight },
|
|
166
171
|
animatedStyles,
|
|
167
172
|
typeof style === "function" ? style({ pressed }) : style
|
|
168
173
|
], [
|
|
169
174
|
generated_styles.buttonStyles.root,
|
|
175
|
+
controlHeight,
|
|
170
176
|
animatedStyles,
|
|
171
177
|
style,
|
|
172
178
|
pressed
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.d.cts","names":[],"sources":["../../src/components/Button.tsx"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"Button.d.cts","names":[],"sources":["../../src/components/Button.tsx"],"mappings":";;;;;;;;;UAiHU,WAAA,SAAoB,IAAA,CAAK,gBAAA;;EAEjC,OAAA,GAAU,iBAAA;EAFF;EAIR,IAAA,GAAO,UAAA;;EAEP,WAAA,GAAc,WAAA;EAJJ;EAMV,SAAA,GAAY,YAAA;EAFE;EAId,OAAA,GAAU,YAAA;EAAA;EAEV,OAAA;EAWU;EATV,QAAA;EAd4B;EAgB5B,QAAA,GAAW,KAAA,CAAM,SAAA;EAhBe;;;;EAqBhC,cAAA;EAjBA;EAmBA,GAAA,GAAM,GAAA,CAAI,IAAA;AAAA;;;;;;;;;;;;;;;;;AAAI;;;;;;;;;;;;;;;;cAuCV,MAAA,EAAM,OAAA,CAAA,oBAAA,CAAA,WAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.d.ts","names":[],"sources":["../../src/components/Button.tsx"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"Button.d.ts","names":[],"sources":["../../src/components/Button.tsx"],"mappings":";;;;;;;;;UAiHU,WAAA,SAAoB,IAAA,CAAK,gBAAA;;EAEjC,OAAA,GAAU,iBAAA;EAFF;EAIR,IAAA,GAAO,UAAA;;EAEP,WAAA,GAAc,WAAA;EAJJ;EAMV,SAAA,GAAY,YAAA;EAFE;EAId,OAAA,GAAU,YAAA;EAAA;EAEV,OAAA;EAWU;EATV,QAAA;EAd4B;EAgB5B,QAAA,GAAW,KAAA,CAAM,SAAA;EAhBe;;;;EAqBhC,cAAA;EAjBA;EAmBA,GAAA,GAAM,GAAA,CAAI,IAAA;AAAA;;;;;;;;;;;;;;;;;AAAI;;;;;;;;;;;;;;;;cAuCV,MAAA,EAAM,OAAA,CAAA,oBAAA,CAAA,WAAA"}
|
|
@@ -3,11 +3,13 @@ import { SCALE_EFFECTS } from "../motion-tokens/dist/index.js";
|
|
|
3
3
|
import { BUTTON_SPRING_CONFIG } from "../motion.js";
|
|
4
4
|
import { IconSlot } from "./IconSlot.js";
|
|
5
5
|
import { Text as Text$1 } from "./Text.js";
|
|
6
|
+
import { getButtonControlMetrics, getButtonTextStyleForControlHeight } from "./Button/buttonTheme.js";
|
|
6
7
|
import { AnimatedPressable } from "./Pressable.js";
|
|
7
8
|
import { isValidElement, memo, useCallback, useMemo, useState } from "react";
|
|
8
9
|
import { ActivityIndicator } from "react-native";
|
|
9
10
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
11
|
import { buttonStyles } from "../../generated/styles";
|
|
12
|
+
import { useUnistyles } from "react-native-unistyles";
|
|
11
13
|
import Animated, { Easing, interpolate, useAnimatedStyle, useDerivedValue, useSharedValue, withSpring, withTiming } from "react-native-reanimated";
|
|
12
14
|
import { useAnimatedTheme } from "react-native-unistyles/reanimated";
|
|
13
15
|
//#region src/components/Button.tsx
|
|
@@ -79,6 +81,8 @@ function AnimatedIconSlot({ children, visible, iconSize, gap }) {
|
|
|
79
81
|
*/
|
|
80
82
|
const Button = memo(function Button({ variant = "primary", size = "md", iconVariant = "outline", startIcon, endIcon, loading, disabled, width: _width, children, style, accessibilityLabel, accessibilityHint, disableEffects = false, onPressIn, onPressOut, ref, ...props }) {
|
|
81
83
|
const shouldAnimate = !disableEffects;
|
|
84
|
+
const { theme } = useUnistyles();
|
|
85
|
+
const { controlHeight, contentLineHeight } = useMemo(() => getButtonControlMetrics(theme, size), [theme, size]);
|
|
82
86
|
const [pressed, setPressed] = useState(false);
|
|
83
87
|
buttonStyles.useVariants({
|
|
84
88
|
size,
|
|
@@ -111,7 +115,7 @@ const Button = memo(function Button({ variant = "primary", size = "md", iconVari
|
|
|
111
115
|
const childrenNode = children && (isValidElement(children) ? children : /* @__PURE__ */ jsx(Text$1, {
|
|
112
116
|
numberOfLines: 1,
|
|
113
117
|
textAlign: "center",
|
|
114
|
-
style: buttonStyles.text,
|
|
118
|
+
style: getButtonTextStyleForControlHeight(buttonStyles.text, contentLineHeight),
|
|
115
119
|
children
|
|
116
120
|
}));
|
|
117
121
|
const a11yState = useMemo(() => ({
|
|
@@ -160,10 +164,12 @@ const Button = memo(function Button({ variant = "primary", size = "md", iconVari
|
|
|
160
164
|
});
|
|
161
165
|
const rootStyles = useMemo(() => [
|
|
162
166
|
buttonStyles.root,
|
|
167
|
+
{ height: controlHeight },
|
|
163
168
|
animatedStyles,
|
|
164
169
|
typeof style === "function" ? style({ pressed }) : style
|
|
165
170
|
], [
|
|
166
171
|
buttonStyles.root,
|
|
172
|
+
controlHeight,
|
|
167
173
|
animatedStyles,
|
|
168
174
|
style,
|
|
169
175
|
pressed
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.js","names":["Text"],"sources":["../../src/components/Button.tsx"],"sourcesContent":["import type { ButtonSize, ButtonVariantFlat, IconSize, IconVariant } from '@yahoo/uds-types';\nimport type { Ref } from 'react';\nimport { isValidElement, memo, useCallback, useMemo, useState } from 'react';\nimport type { View } from 'react-native';\nimport { ActivityIndicator } from 'react-native';\nimport Animated, {\n Easing,\n interpolate,\n useAnimatedStyle,\n useDerivedValue,\n useSharedValue,\n withSpring,\n withTiming,\n} from 'react-native-reanimated';\nimport { useAnimatedTheme } from 'react-native-unistyles/reanimated';\n\nimport { buttonStyles } from '../../generated/styles';\nimport { BUTTON_SPRING_CONFIG, SCALE_EFFECTS } from '../motion';\nimport type { IconSlotType } from './IconSlot';\nimport { IconSlot } from './IconSlot';\nimport type { PressableProps } from './Pressable';\nimport { AnimatedPressable } from './Pressable';\nimport { Text } from './Text';\n\n/* -------------------------------------------------------------------------- */\n/* Animation Helpers */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Interpolates a boxShadow string by scaling the alpha of all colors.\n * This allows smooth fade-in/out of shadows.\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/* Animated Icon Slot */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Animated wrapper for icon/loading content.\n * Matches web Button icon animation: scale 0.7→1, opacity 0→1, width 0→auto\n * Uses staggered animation: opacity waits until halfway through width animation.\n */\nfunction AnimatedIconSlot({\n children,\n visible,\n iconSize,\n gap,\n}: {\n children: React.ReactNode;\n visible: boolean;\n iconSize: number;\n gap: number;\n}) {\n // Use useDerivedValue instead of useEffect + useSharedValue\n // This is the idiomatic Reanimated pattern for deriving animated values from React state\n const progress = useDerivedValue(\n () => withSpring(visible ? 1 : 0, BUTTON_SPRING_CONFIG),\n [visible],\n );\n\n const animatedStyle = useAnimatedStyle(() => {\n // Total width includes icon + gap when visible\n const totalWidth = iconSize + gap;\n const width = interpolate(progress.value, [0, 1], [0, totalWidth]);\n\n // Staggered animation: opacity starts at 50% of width animation\n // On enter: width expands first, then icon fades in\n // On exit: icon fades out first, then width collapses\n const opacity = interpolate(progress.value, [0.5, 1], [0, 1], 'clamp');\n const scale = interpolate(progress.value, [0.5, 1], [0.7, 1], 'clamp');\n\n return {\n width,\n opacity,\n transform: [{ scale }],\n overflow: 'hidden' as const,\n };\n });\n\n return <Animated.View style={animatedStyle}>{children}</Animated.View>;\n}\n\n// function LoadingIcon({ size, variant }: { size: ButtonSize, variant: ButtonVariantFlat }) {\n// const { theme } = useUnistyles();\n// const themeKey = `buttonVariant${variantToCapitalMap[variant]}IconRest` as const;\n// const iconSize = theme.components.buttonSizeLgIconRest.fontSize;\n// return <ActivityIndicator size={iconSize} color={theme.colors.text.primary} />;\n// }\n\n/* -------------------------------------------------------------------------- */\n/* Button Props */\n/* -------------------------------------------------------------------------- */\n\ninterface ButtonProps extends Omit<PressableProps, 'children' | 'disabled'> {\n /** The visual style variant of the button @default 'primary' */\n variant?: ButtonVariantFlat;\n /** The size of the button @default 'md' */\n size?: ButtonSize;\n /** The icon style variant @default 'outline' */\n iconVariant?: IconVariant;\n /** Icon displayed before the button label */\n startIcon?: IconSlotType;\n /** Icon displayed after the button label */\n endIcon?: IconSlotType;\n /** Shows a loading spinner and disables the button */\n loading?: boolean;\n /** Whether the button is disabled */\n disabled?: boolean;\n /** Button label content */\n children?: React.ReactNode;\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/* Button Component */\n/* -------------------------------------------------------------------------- */\n\n/**\n * **🖲️ A button element that can be used to trigger an action**\n *\n * @description\n * A button is a fundamental component used to trigger an action or event.\n * Buttons are interactive elements that users can click, tap, or otherwise\n * engage with to submit forms, open dialogues, or perform any other interaction.\n *\n * Features animated scale effect on press and smooth icon transitions matching\n * the web UDS Button behavior.\n *\n * @category Interactive\n * @platform mobile\n *\n * @example\n * ```tsx\n * import { Button } from '@yahoo/uds-mobile/Button';\n *\n * <Button onPress={() => console.log('pressed')}>Save</Button>\n * <Button variant=\"secondary\">Cancel</Button>\n * <Button startIcon=\"Add\" variant=\"brand\">Add Item</Button>\n * <Button loading>Saving...</Button>\n * ```\n *\n * @accessibility\n * - Sets `accessibilityRole=\"button\"` automatically\n * - Announces loading state to screen readers\n * - Use `accessibilityLabel` for icon-only buttons\n *\n * @see {@link IconButton} for icon-only buttons\n * @see {@link Link} for navigation actions\n */\nconst Button = memo(function Button({\n variant = 'primary',\n size = 'md',\n iconVariant = 'outline',\n startIcon,\n endIcon,\n loading,\n disabled,\n width: _width,\n children,\n style,\n accessibilityLabel,\n accessibilityHint,\n disableEffects = false,\n onPressIn,\n onPressOut,\n ref,\n ...props\n}: ButtonProps) {\n const shouldAnimate = !disableEffects;\n\n /* --------------------------------- State ---------------------------------- */\n const [pressed, setPressed] = useState(false);\n\n buttonStyles.useVariants({ size, variant, disabled, pressed });\n\n // Get gap and icon size from current variant styles\n const buttonGap = buttonStyles.root.gap;\n const iconSize = buttonStyles.icon.fontSize;\n\n // Get animated theme for boxShadow (useAnimatedVariantColor doesn't support non-color props)\n const animatedTheme = useAnimatedTheme();\n\n /* ------------------------------- Animation -------------------------------- */\n const scale = useSharedValue<number>(SCALE_EFFECTS.none);\n\n const handlePressIn = useCallback(\n (event: Parameters<NonNullable<PressableProps['onPressIn']>>[0]) => {\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(\n (event: Parameters<NonNullable<PressableProps['onPressOut']>>[0]) => {\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 /* -------------------------------- Content --------------------------------- */\n const childrenNode =\n children &&\n (isValidElement(children) ? (\n children\n ) : (\n <Text numberOfLines={1} textAlign=\"center\" style={buttonStyles.text}>\n {children}\n </Text>\n ));\n\n const a11yState = useMemo(() => ({ disabled, busy: loading }), [disabled, loading]);\n\n /* --------------------------------- Styles --------------------------------- */\n // Animate pressed state for shadow (0 = rest, 1 = pressed)\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 animatedStyles = useAnimatedStyle(() => {\n // Get boxShadow from theme using flattened path (no camelCase conversion needed!)\n const components = animatedTheme.value.components;\n const buttonVariantPath = `button/variant/${variant}/root/pressed` as const;\n const shadowPressed = components[buttonVariantPath]?.boxShadow;\n\n return {\n transform: [{ scale: scale.value }],\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 // Determine what should be visible in start slot\n const showLoading = !!loading;\n const showStartIcon = !!startIcon && !loading;\n const showEndIcon = !!endIcon && !loading;\n\n const iconSizeToken = (buttonStyles.icon.iconSizeToken as IconSize) ?? 'sm';\n\n // Start slot: either loading spinner or start icon\n const startContent = (\n <AnimatedIconSlot visible={showLoading || showStartIcon} iconSize={iconSize} gap={buttonGap}>\n {showLoading ? (\n <ActivityIndicator size={buttonStyles.icon.fontSize} color={buttonStyles.icon.color} />\n ) : (\n <IconSlot\n icon={startIcon}\n size={iconSizeToken}\n variant={iconVariant}\n style={buttonStyles.icon}\n />\n )}\n </AnimatedIconSlot>\n );\n\n // End slot: only end icon (no loading here)\n const endContent = (\n <AnimatedIconSlot visible={showEndIcon} iconSize={iconSize} gap={buttonGap}>\n <IconSlot\n icon={endIcon}\n size={iconSizeToken}\n variant={iconVariant}\n style={buttonStyles.icon}\n />\n </AnimatedIconSlot>\n );\n\n const rootStyles = useMemo(\n () => [\n buttonStyles.root,\n animatedStyles,\n typeof style === 'function' ? style({ pressed }) : style,\n ],\n [buttonStyles.root, animatedStyles, style, pressed],\n );\n\n /* --------------------------------- Render --------------------------------- */\n return (\n <AnimatedPressable\n ref={ref}\n disabled={disabled}\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 alignContent=\"center\"\n style={rootStyles}\n {...props}\n >\n {startContent}\n {childrenNode}\n {endContent}\n </AnimatedPressable>\n );\n});\n\nButton.displayName = 'Button';\n\nexport { Button, type ButtonProps };\n"],"mappings":";;;;;;;;;;;;;;;;;AAgCA,SAAS,uBAAuB,QAA4B,OAAuB;AACjF;CACA,IAAI,CAAC,QACH,OAAO;CAET,IAAI,SAAS,GACX,OAAO;CAET,IAAI,SAAS,GACX,OAAO;CAGT,OAAO,OAAO,QAAQ,sDAAsD,GAAG,GAAG,GAAG,GAAG,MAAM;EAE5F,OAAO,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,KADZ,WAAW,EAAE,GAAG,OACS,QAAQ,EAAE,CAAC;GACrD;;;;;;;AAYJ,SAAS,iBAAiB,EACxB,UACA,SACA,UACA,OAMC;CAGD,MAAM,WAAW,sBACT,WAAW,UAAU,IAAI,GAAG,qBAAqB,EACvD,CAAC,QAAQ,CACV;CAED,MAAM,gBAAgB,uBAAuB;EAE3C,MAAM,aAAa,WAAW;EAS9B,OAAO;GACL,OATY,YAAY,SAAS,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,WAAW,CAS1D;GACL,SALc,YAAY,SAAS,OAAO,CAAC,IAAK,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,QAKrD;GACP,WAAW,CAAC,EAAE,OALF,YAAY,SAAS,OAAO,CAAC,IAAK,EAAE,EAAE,CAAC,IAAK,EAAE,EAAE,QAKzC,EAAE,CAAC;GACtB,UAAU;GACX;GACD;CAEF,OAAO,oBAAC,SAAS,MAAV;EAAe,OAAO;EAAgB;EAAyB,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4ExE,MAAM,SAAS,KAAK,SAAS,OAAO,EAClC,UAAU,WACV,OAAO,MACP,cAAc,WACd,WACA,SACA,SACA,UACA,OAAO,QACP,UACA,OACA,oBACA,mBACA,iBAAiB,OACjB,WACA,YACA,KACA,GAAG,SACW;CACd,MAAM,gBAAgB,CAAC;CAGvB,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAE7C,aAAa,YAAY;EAAE;EAAM;EAAS;EAAU;EAAS,CAAC;CAG9D,MAAM,YAAY,aAAa,KAAK;CACpC,MAAM,WAAW,aAAa,KAAK;CAGnC,MAAM,gBAAgB,kBAAkB;CAGxC,MAAM,QAAQ,eAAuB,cAAc,KAAK;CAExD,MAAM,gBAAgB,aACnB,UAAmE;EAClE,WAAW,KAAK;EAChB,IAAI,eACF,MAAM,QAAQ,WAAW,cAAc,MAAM,qBAAqB;EAEpE,YAAY,MAAM;IAEpB;EAAC;EAAe;EAAO;EAAU,CAClC;CAED,MAAM,iBAAiB,aACpB,UAAoE;EACnE,WAAW,MAAM;EACjB,IAAI,eACF,MAAM,QAAQ,WAAW,cAAc,MAAM,qBAAqB;EAEpE,aAAa,MAAM;IAErB;EAAC;EAAe;EAAO;EAAW,CACnC;CAGD,MAAM,eACJ,aACC,eAAe,SAAS,GACvB,WAEA,oBAACA,QAAD;EAAM,eAAe;EAAG,WAAU;EAAS,OAAO,aAAa;EAC5D;EACI,CAAA;CAGX,MAAM,YAAY,eAAe;EAAE;EAAU,MAAM;EAAS,GAAG,CAAC,UAAU,QAAQ,CAAC;CAInF,MAAM,gBAAgB,sBACd,WAAW,UAAU,IAAI,GAAG;EAAE,UAAU;EAAK,QAAQ,OAAO,OAAO,GAAG,GAAG,IAAK,EAAE;EAAE,CAAC,EACzF,CAAC,QAAQ,CACV;CAGD,MAAM,iBAAiB,uBAAuB;EAI5C,MAAM,gBAFa,cAAc,MAAM,WAEN,kBADW,QAAQ,iBACC;EAErD,OAAO;GACL,WAAW,CAAC,EAAE,OAAO,MAAM,OAAO,CAAC;GAEnC,GAAI,iBAAiB,EACnB,WAAW,uBAAuB,eAAe,cAAc,MAAM,EACtE;GACF;GACD;CAGF,MAAM,cAAc,CAAC,CAAC;CACtB,MAAM,gBAAgB,CAAC,CAAC,aAAa,CAAC;CACtC,MAAM,cAAc,CAAC,CAAC,WAAW,CAAC;CAElC,MAAM,gBAAiB,aAAa,KAAK,iBAA8B;CAGvE,MAAM,eACJ,oBAAC,kBAAD;EAAkB,SAAS,eAAe;EAAyB;EAAU,KAAK;YAC/E,cACC,oBAAC,mBAAD;GAAmB,MAAM,aAAa,KAAK;GAAU,OAAO,aAAa,KAAK;GAAS,CAAA,GAEvF,oBAAC,UAAD;GACE,MAAM;GACN,MAAM;GACN,SAAS;GACT,OAAO,aAAa;GACpB,CAAA;EAEa,CAAA;CAIrB,MAAM,aACJ,oBAAC,kBAAD;EAAkB,SAAS;EAAuB;EAAU,KAAK;YAC/D,oBAAC,UAAD;GACE,MAAM;GACN,MAAM;GACN,SAAS;GACT,OAAO,aAAa;GACpB,CAAA;EACe,CAAA;CAGrB,MAAM,aAAa,cACX;EACJ,aAAa;EACb;EACA,OAAO,UAAU,aAAa,MAAM,EAAE,SAAS,CAAC,GAAG;EACpD,EACD;EAAC,aAAa;EAAM;EAAgB;EAAO;EAAQ,CACpD;CAGD,OACE,qBAAC,mBAAD;EACO;EACK;EACV,WAAW;EACX,YAAY;EACZ,eAAc;EACd,YAAW;EACX,gBAAe;EACf,UAAS;EACT,oBAAoB,UAAU,GAAG,sBAAsB,GAAG,aAAa;EACpD;EACnB,mBAAkB;EAClB,oBAAoB;EACpB,cAAa;EACb,OAAO;EACP,GAAI;YAfN;GAiBG;GACA;GACA;GACiB;;EAEtB;AAEF,OAAO,cAAc"}
|
|
1
|
+
{"version":3,"file":"Button.js","names":["Text"],"sources":["../../src/components/Button.tsx"],"sourcesContent":["import type { ButtonSize, ButtonVariantFlat, IconSize, IconVariant } from '@yahoo/uds-types';\nimport type { Ref } from 'react';\nimport { isValidElement, memo, useCallback, useMemo, useState } from 'react';\nimport type { View } from 'react-native';\nimport { ActivityIndicator } from 'react-native';\nimport Animated, {\n Easing,\n interpolate,\n useAnimatedStyle,\n useDerivedValue,\n useSharedValue,\n withSpring,\n withTiming,\n} from 'react-native-reanimated';\n// eslint-disable-next-line uds/no-use-unistyles -- button control height from theme size layers\nimport { useUnistyles } from 'react-native-unistyles';\nimport { useAnimatedTheme } from 'react-native-unistyles/reanimated';\n\nimport { buttonStyles } from '../../generated/styles';\nimport { BUTTON_SPRING_CONFIG, SCALE_EFFECTS } from '../motion';\nimport { getButtonControlMetrics, getButtonTextStyleForControlHeight } from './Button/buttonTheme';\nimport type { IconSlotType } from './IconSlot';\nimport { IconSlot } from './IconSlot';\nimport type { PressableProps } from './Pressable';\nimport { AnimatedPressable } from './Pressable';\nimport { Text } from './Text';\n\n/* -------------------------------------------------------------------------- */\n/* Animation Helpers */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Interpolates a boxShadow string by scaling the alpha of all colors.\n * This allows smooth fade-in/out of shadows.\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/* Animated Icon Slot */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Animated wrapper for icon/loading content.\n * Matches web Button icon animation: scale 0.7→1, opacity 0→1, width 0→auto\n * Uses staggered animation: opacity waits until halfway through width animation.\n */\nfunction AnimatedIconSlot({\n children,\n visible,\n iconSize,\n gap,\n}: {\n children: React.ReactNode;\n visible: boolean;\n iconSize: number;\n gap: number;\n}) {\n // Use useDerivedValue instead of useEffect + useSharedValue\n // This is the idiomatic Reanimated pattern for deriving animated values from React state\n const progress = useDerivedValue(\n () => withSpring(visible ? 1 : 0, BUTTON_SPRING_CONFIG),\n [visible],\n );\n\n const animatedStyle = useAnimatedStyle(() => {\n // Total width includes icon + gap when visible\n const totalWidth = iconSize + gap;\n const width = interpolate(progress.value, [0, 1], [0, totalWidth]);\n\n // Staggered animation: opacity starts at 50% of width animation\n // On enter: width expands first, then icon fades in\n // On exit: icon fades out first, then width collapses\n const opacity = interpolate(progress.value, [0.5, 1], [0, 1], 'clamp');\n const scale = interpolate(progress.value, [0.5, 1], [0.7, 1], 'clamp');\n\n return {\n width,\n opacity,\n transform: [{ scale }],\n overflow: 'hidden' as const,\n };\n });\n\n return <Animated.View style={animatedStyle}>{children}</Animated.View>;\n}\n\n// function LoadingIcon({ size, variant }: { size: ButtonSize, variant: ButtonVariantFlat }) {\n// const { theme } = useUnistyles();\n// const themeKey = `buttonVariant${variantToCapitalMap[variant]}IconRest` as const;\n// const iconSize = theme.components.buttonSizeLgIconRest.fontSize;\n// return <ActivityIndicator size={iconSize} color={theme.colors.text.primary} />;\n// }\n\n/* -------------------------------------------------------------------------- */\n/* Button Props */\n/* -------------------------------------------------------------------------- */\n\ninterface ButtonProps extends Omit<PressableProps, 'children' | 'disabled'> {\n /** The visual style variant of the button @default 'primary' */\n variant?: ButtonVariantFlat;\n /** The size of the button @default 'md' */\n size?: ButtonSize;\n /** The icon style variant @default 'outline' */\n iconVariant?: IconVariant;\n /** Icon displayed before the button label */\n startIcon?: IconSlotType;\n /** Icon displayed after the button label */\n endIcon?: IconSlotType;\n /** Shows a loading spinner and disables the button */\n loading?: boolean;\n /** Whether the button is disabled */\n disabled?: boolean;\n /** Button label content */\n children?: React.ReactNode;\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/* Button Component */\n/* -------------------------------------------------------------------------- */\n\n/**\n * **🖲️ A button element that can be used to trigger an action**\n *\n * @description\n * A button is a fundamental component used to trigger an action or event.\n * Buttons are interactive elements that users can click, tap, or otherwise\n * engage with to submit forms, open dialogues, or perform any other interaction.\n *\n * Features animated scale effect on press and smooth icon transitions matching\n * the web UDS Button behavior.\n *\n * @category Interactive\n * @platform mobile\n *\n * @example\n * ```tsx\n * import { Button } from '@yahoo/uds-mobile/Button';\n *\n * <Button onPress={() => console.log('pressed')}>Save</Button>\n * <Button variant=\"secondary\">Cancel</Button>\n * <Button startIcon=\"Add\" variant=\"brand\">Add Item</Button>\n * <Button loading>Saving...</Button>\n * ```\n *\n * @accessibility\n * - Sets `accessibilityRole=\"button\"` automatically\n * - Announces loading state to screen readers\n * - Use `accessibilityLabel` for icon-only buttons\n *\n * @see {@link IconButton} for icon-only buttons\n * @see {@link Link} for navigation actions\n */\nconst Button = memo(function Button({\n variant = 'primary',\n size = 'md',\n iconVariant = 'outline',\n startIcon,\n endIcon,\n loading,\n disabled,\n width: _width,\n children,\n style,\n accessibilityLabel,\n accessibilityHint,\n disableEffects = false,\n onPressIn,\n onPressOut,\n ref,\n ...props\n}: ButtonProps) {\n const shouldAnimate = !disableEffects;\n\n const { theme } = useUnistyles();\n const { controlHeight, contentLineHeight } = useMemo(\n () => getButtonControlMetrics(theme, size),\n [theme, size],\n );\n\n /* --------------------------------- State ---------------------------------- */\n const [pressed, setPressed] = useState(false);\n\n buttonStyles.useVariants({ size, variant, disabled, pressed });\n\n // Get gap and icon size from current variant styles\n const buttonGap = buttonStyles.root.gap;\n const iconSize = buttonStyles.icon.fontSize;\n\n // Get animated theme for boxShadow (useAnimatedVariantColor doesn't support non-color props)\n const animatedTheme = useAnimatedTheme();\n\n /* ------------------------------- Animation -------------------------------- */\n const scale = useSharedValue<number>(SCALE_EFFECTS.none);\n\n const handlePressIn = useCallback(\n (event: Parameters<NonNullable<PressableProps['onPressIn']>>[0]) => {\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(\n (event: Parameters<NonNullable<PressableProps['onPressOut']>>[0]) => {\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 /* -------------------------------- Content --------------------------------- */\n const childrenNode =\n children &&\n (isValidElement(children) ? (\n children\n ) : (\n <Text\n numberOfLines={1}\n textAlign=\"center\"\n style={getButtonTextStyleForControlHeight(buttonStyles.text, contentLineHeight)}\n >\n {children}\n </Text>\n ));\n\n const a11yState = useMemo(() => ({ disabled, busy: loading }), [disabled, loading]);\n\n /* --------------------------------- Styles --------------------------------- */\n // Animate pressed state for shadow (0 = rest, 1 = pressed)\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 animatedStyles = useAnimatedStyle(() => {\n // Get boxShadow from theme using flattened path (no camelCase conversion needed!)\n const components = animatedTheme.value.components;\n const buttonVariantPath = `button/variant/${variant}/root/pressed` as const;\n const shadowPressed = components[buttonVariantPath]?.boxShadow;\n\n return {\n transform: [{ scale: scale.value }],\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 // Determine what should be visible in start slot\n const showLoading = !!loading;\n const showStartIcon = !!startIcon && !loading;\n const showEndIcon = !!endIcon && !loading;\n\n const iconSizeToken = (buttonStyles.icon.iconSizeToken as IconSize) ?? 'sm';\n\n // Start slot: either loading spinner or start icon\n const startContent = (\n <AnimatedIconSlot visible={showLoading || showStartIcon} iconSize={iconSize} gap={buttonGap}>\n {showLoading ? (\n <ActivityIndicator size={buttonStyles.icon.fontSize} color={buttonStyles.icon.color} />\n ) : (\n <IconSlot\n icon={startIcon}\n size={iconSizeToken}\n variant={iconVariant}\n style={buttonStyles.icon}\n />\n )}\n </AnimatedIconSlot>\n );\n\n // End slot: only end icon (no loading here)\n const endContent = (\n <AnimatedIconSlot visible={showEndIcon} iconSize={iconSize} gap={buttonGap}>\n <IconSlot\n icon={endIcon}\n size={iconSizeToken}\n variant={iconVariant}\n style={buttonStyles.icon}\n />\n </AnimatedIconSlot>\n );\n\n const rootStyles = useMemo(\n () => [\n buttonStyles.root,\n { height: controlHeight },\n animatedStyles,\n typeof style === 'function' ? style({ pressed }) : style,\n ],\n [buttonStyles.root, controlHeight, animatedStyles, style, pressed],\n );\n\n /* --------------------------------- Render --------------------------------- */\n return (\n <AnimatedPressable\n ref={ref}\n disabled={disabled}\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 alignContent=\"center\"\n style={rootStyles}\n {...props}\n >\n {startContent}\n {childrenNode}\n {endContent}\n </AnimatedPressable>\n );\n});\n\nButton.displayName = 'Button';\n\nexport { Button, type ButtonProps };\n"],"mappings":"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"}
|
|
@@ -4,11 +4,13 @@ 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
6
|
const require_components_Icon = require("./Icon.cjs");
|
|
7
|
+
const require_components_Button_buttonTheme = require("./Button/buttonTheme.cjs");
|
|
7
8
|
const require_components_Pressable = require("./Pressable.cjs");
|
|
8
9
|
let react = require("react");
|
|
9
10
|
let react_native = require("react-native");
|
|
10
11
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
11
12
|
let generated_styles = require("../../generated/styles");
|
|
13
|
+
let react_native_unistyles = require("react-native-unistyles");
|
|
12
14
|
let react_native_reanimated = require("react-native-reanimated");
|
|
13
15
|
let react_native_unistyles_reanimated = require("react-native-unistyles/reanimated");
|
|
14
16
|
//#region src/components/IconButton.tsx
|
|
@@ -57,6 +59,12 @@ function interpolateShadowAlpha(shadow, alpha) {
|
|
|
57
59
|
const IconButton = (0, react.memo)(function IconButton({ name, variant = "primary", size = "md", iconVariant = "outline", iconColor, loading, disabled, style, accessibilityLabel, accessibilityHint, disableEffects = false, onPressIn, onPressOut, ref, ...props }) {
|
|
58
60
|
const isDisabled = disabled || loading;
|
|
59
61
|
const shouldAnimate = !disableEffects && !isDisabled;
|
|
62
|
+
const { theme } = (0, react_native_unistyles.useUnistyles)();
|
|
63
|
+
const { controlHeight } = (0, react.useMemo)(() => require_components_Button_buttonTheme.getIconButtonControlMetrics(theme, size), [theme, size]);
|
|
64
|
+
const matchedControlDimensions = controlHeight > 0 ? {
|
|
65
|
+
height: controlHeight,
|
|
66
|
+
width: controlHeight
|
|
67
|
+
} : void 0;
|
|
60
68
|
const [pressed, setPressed] = (0, react.useState)(false);
|
|
61
69
|
generated_styles.iconButtonStyles.useVariants({ size });
|
|
62
70
|
generated_styles.buttonStyles.useVariants({
|
|
@@ -127,6 +135,7 @@ const IconButton = (0, react.memo)(function IconButton({ name, variant = "primar
|
|
|
127
135
|
style: [
|
|
128
136
|
generated_styles.iconButtonStyles.root,
|
|
129
137
|
generated_styles.buttonStyles.root,
|
|
138
|
+
matchedControlDimensions,
|
|
130
139
|
generated_styles.styles.foundation,
|
|
131
140
|
animatedRootStyle,
|
|
132
141
|
typeof style === "function" ? style({ pressed }) : style
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IconButton.d.cts","names":[],"sources":["../../src/components/IconButton.tsx"],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"IconButton.d.cts","names":[],"sources":["../../src/components/IconButton.tsx"],"mappings":";;;;;;;;;;UAoDU,eAAA,SAAwB,IAAA,CAAK,gBAAA;;EAErC,IAAA,EAAM,QAAA;EAFE;EAIR,OAAA,GAAU,iBAAA;;EAEV,IAAA,GAAO,cAAA;EAJD;EAMN,WAAA,GAAc,WAAA;EAFP;EAIP,SAAA,GAAY,UAAA;EAAA;EAEZ,OAAA;EAOM;;;;EAFN,cAAA;EAjBqC;EAmBrC,GAAA,GAAM,GAAA,CAAI,IAAA;AAAA;;;;;;;;;;;;;;;;AAAI;;;;;;;;;;;;;;;;;;cAwCV,UAAA,EAAU,OAAA,CAAA,oBAAA,CAAA,eAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IconButton.d.ts","names":[],"sources":["../../src/components/IconButton.tsx"],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"IconButton.d.ts","names":[],"sources":["../../src/components/IconButton.tsx"],"mappings":";;;;;;;;;;UAoDU,eAAA,SAAwB,IAAA,CAAK,gBAAA;;EAErC,IAAA,EAAM,QAAA;EAFE;EAIR,OAAA,GAAU,iBAAA;;EAEV,IAAA,GAAO,cAAA;EAJD;EAMN,WAAA,GAAc,WAAA;EAFP;EAIP,SAAA,GAAY,UAAA;EAAA;EAEZ,OAAA;EAOM;;;;EAFN,cAAA;EAjBqC;EAmBrC,GAAA,GAAM,GAAA,CAAI,IAAA;AAAA;;;;;;;;;;;;;;;;AAAI;;;;;;;;;;;;;;;;;;cAwCV,UAAA,EAAU,OAAA,CAAA,oBAAA,CAAA,eAAA"}
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
import { SCALE_EFFECTS } from "../motion-tokens/dist/index.js";
|
|
3
3
|
import { BUTTON_SPRING_CONFIG } from "../motion.js";
|
|
4
4
|
import { Icon } from "./Icon.js";
|
|
5
|
+
import { getIconButtonControlMetrics } from "./Button/buttonTheme.js";
|
|
5
6
|
import { AnimatedPressable } from "./Pressable.js";
|
|
6
7
|
import { memo, useCallback, useMemo, useState } from "react";
|
|
7
8
|
import { ActivityIndicator } from "react-native";
|
|
8
9
|
import { jsx } from "react/jsx-runtime";
|
|
9
10
|
import { buttonStyles, iconButtonStyles, styles } from "../../generated/styles";
|
|
11
|
+
import { useUnistyles } from "react-native-unistyles";
|
|
10
12
|
import { Easing, useAnimatedStyle, useDerivedValue, useSharedValue, withSpring, withTiming } from "react-native-reanimated";
|
|
11
13
|
import { useAnimatedTheme, useAnimatedVariantColor } from "react-native-unistyles/reanimated";
|
|
12
14
|
//#region src/components/IconButton.tsx
|
|
@@ -55,6 +57,12 @@ function interpolateShadowAlpha(shadow, alpha) {
|
|
|
55
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 }) {
|
|
56
58
|
const isDisabled = disabled || loading;
|
|
57
59
|
const shouldAnimate = !disableEffects && !isDisabled;
|
|
60
|
+
const { theme } = useUnistyles();
|
|
61
|
+
const { controlHeight } = useMemo(() => getIconButtonControlMetrics(theme, size), [theme, size]);
|
|
62
|
+
const matchedControlDimensions = controlHeight > 0 ? {
|
|
63
|
+
height: controlHeight,
|
|
64
|
+
width: controlHeight
|
|
65
|
+
} : void 0;
|
|
58
66
|
const [pressed, setPressed] = useState(false);
|
|
59
67
|
iconButtonStyles.useVariants({ size });
|
|
60
68
|
buttonStyles.useVariants({
|
|
@@ -125,6 +133,7 @@ const IconButton = memo(function IconButton({ name, variant = "primary", size =
|
|
|
125
133
|
style: [
|
|
126
134
|
iconButtonStyles.root,
|
|
127
135
|
buttonStyles.root,
|
|
136
|
+
matchedControlDimensions,
|
|
128
137
|
styles.foundation,
|
|
129
138
|
animatedRootStyle,
|
|
130
139
|
typeof style === "function" ? style({ pressed }) : style
|
|
@@ -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 } from 'react-native';\nimport {\n Easing,\n useAnimatedStyle,\n useDerivedValue,\n useSharedValue,\n withSpring,\n withTiming,\n} from 'react-native-reanimated';\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 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\n /* --------------------------------- State ---------------------------------- */\n const [pressed, setPressed] = useState(false);\n\n // Apply layer-based styles with compound variant support\n iconButtonStyles.useVariants({ size });\n buttonStyles.useVariants({ variant, disabled: isDisabled, pressed });\n foundationStyles.useVariants({ color: iconColor });\n\n const resolvedIconColor = iconColor\n ? (foundationStyles.foundation.color as string | undefined)\n : undefined;\n\n // Animate colors using Unistyles' useAnimatedVariantColor\n const backgroundColor = useAnimatedVariantColor(buttonStyles.root, 'backgroundColor');\n const borderColor = useAnimatedVariantColor(buttonStyles.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 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 // 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 iconButtonStyles.root,\n buttonStyles.root,\n foundationStyles.foundation,\n animatedRootStyle,\n typeof style === 'function' ? style({ pressed }) : style,\n ]}\n {...props}\n >\n {loading ? (\n <ActivityIndicator\n size={iconButtonStyles.icon.fontSize}\n color={resolvedIconColor ?? buttonStyles.icon.color}\n />\n ) : (\n <Icon\n name={name}\n variant={iconVariant}\n style={[iconButtonStyles.icon, buttonStyles.icon]}\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 } 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\n const { theme } = useUnistyles();\n const { controlHeight } = useMemo(() => getIconButtonControlMetrics(theme, size), [theme, size]);\n const matchedControlDimensions =\n controlHeight > 0 ? ({ height: controlHeight, width: controlHeight } as const) : undefined;\n\n /* --------------------------------- State ---------------------------------- */\n const [pressed, setPressed] = useState(false);\n\n // Apply layer-based styles with compound variant support\n iconButtonStyles.useVariants({ size });\n buttonStyles.useVariants({ variant, disabled: isDisabled, pressed });\n foundationStyles.useVariants({ color: iconColor });\n\n const resolvedIconColor = iconColor\n ? (foundationStyles.foundation.color as string | undefined)\n : undefined;\n\n // Animate colors using Unistyles' useAnimatedVariantColor\n const backgroundColor = useAnimatedVariantColor(buttonStyles.root, 'backgroundColor');\n const borderColor = useAnimatedVariantColor(buttonStyles.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 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 // 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 iconButtonStyles.root,\n buttonStyles.root,\n matchedControlDimensions,\n foundationStyles.foundation,\n animatedRootStyle,\n typeof style === 'function' ? style({ pressed }) : style,\n ]}\n {...props}\n >\n {loading ? (\n <ActivityIndicator\n size={iconButtonStyles.icon.fontSize}\n color={resolvedIconColor ?? buttonStyles.icon.color}\n />\n ) : (\n <Icon\n name={name}\n variant={iconVariant}\n style={[iconButtonStyles.icon, buttonStyles.icon]}\n dangerouslySetColor={resolvedIconColor}\n />\n )}\n </AnimatedPressable>\n );\n});\n\nIconButton.displayName = 'IconButton';\n\nexport { IconButton, type IconButtonProps };\n"],"mappings":"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"}
|
package/generated/styles.d.ts
CHANGED
|
@@ -1717,7 +1717,7 @@ export declare const dividerStyles: {
|
|
|
1717
1717
|
|
|
1718
1718
|
export declare const iconButtonStyles: {
|
|
1719
1719
|
root: { padding: number };
|
|
1720
|
-
icon: { fontSize: number; iconSizeToken: '
|
|
1720
|
+
icon: { fontSize: number; iconSizeToken: 'sm' | 'lg' | 'xs'; lineHeight: number };
|
|
1721
1721
|
} & {
|
|
1722
1722
|
useVariants: (
|
|
1723
1723
|
variants:
|