1import {
2 FilledIconButton,
3 FilledTonalIconButton,
4 Host,
5 IconButton,
6} from "@expo/ui/jetpack-compose";
7import { size } from "@expo/ui/jetpack-compose/modifiers";
8import { View } from "react-native";
9
10import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider";
11import { useAndroidControlSizing } from "./useAndroidControlSizing";
12import { SymbolView, type AppSymbolName } from "./AppSymbol";
13
14export function MaterialIconButton(props: {
15 readonly accessibilityLabel: string;
16 readonly icon: AppSymbolName;
17 readonly onPress?: () => void;
18 readonly disabled?: boolean;
19 readonly selected?: boolean;
20 readonly variant?: "standard" | "primary" | "tonal" | "danger";
21 readonly tintColorClassName?: string;
22}) {
23 const { themeAppearance, themeVariables: colors } = useAppearancePreferences();
24 const { iconSize, buttonSize } = useAndroidControlSizing();
25 const variant = props.variant ?? "standard";
26 const Component =
27 variant === "standard"
28 ? IconButton
29 : variant === "tonal"
30 ? FilledTonalIconButton
31 : FilledIconButton;
32 const containerColor =
33 variant === "primary"
34 ? colors["--color-primary"]
35 : variant === "danger"
36 ? colors["--color-danger"]
37 : colors["--color-secondary"];
38 const iconTint = props.disabled
39 ? "accent-icon-subtle"
40 : variant === "primary"
41 ? "accent-primary-foreground"
42 : variant === "danger"
43 ? "accent-danger-foreground"
44 : variant === "tonal"
45 ? "accent-secondary-foreground"
46 : (props.tintColorClassName ?? "accent-foreground");
47 return (
48 <View
49 accessible
50 accessibilityLabel={props.accessibilityLabel}
51 accessibilityRole="button"
52 accessibilityState={{ disabled: Boolean(props.disabled), selected: props.selected }}
53 accessibilityActions={[{ name: "activate" }]}
54 onAccessibilityAction={() => {
55 if (!props.disabled) props.onPress?.();
56 }}
57 style={{ width: buttonSize, height: buttonSize }}
58 >
59 <View importantForAccessibility="no-hide-descendants">
60 <Host
61 colorScheme={themeAppearance}
62 ignoreSafeAreaKeyboardInsets
63 style={{ width: buttonSize, height: buttonSize }}
64 >
65 <Component
66 onClick={props.onPress}
67 enabled={!props.disabled}
68 modifiers={[size(buttonSize, buttonSize)]}
69 colors={
70 variant === "standard"
71 ? undefined
72 : { containerColor, disabledContainerColor: colors["--color-subtle-strong"] }
73 }
74 >
75 {null}
76 </Component>
77 </Host>
78 </View>
79 {/* Keep RN SVG measurement outside Compose; the native button owns touch and ripple. */}
80 <View pointerEvents="none" className="absolute inset-0 items-center justify-center">
81 <SymbolView
82 name={props.icon === "ellipsis" ? { ios: "ellipsis", android: "more_vert" } : props.icon}
83 size={iconSize}
84 tintColorClassName={iconTint}
85 type="monochrome"
86 />
87 </View>
88 </View>
89 );
90}