react-native
Version:
A framework for building native apps using React
361 lines (325 loc) • 9.61 kB
JavaScript
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
;
import type {TextStyleProp, ViewStyleProp} from '../StyleSheet/StyleSheet';
import type {GestureResponderEvent} from '../Types/CoreEventTypes';
import type {
AccessibilityActionEvent,
AccessibilityActionInfo,
AccessibilityState,
} from './View/ViewAccessibility';
import StyleSheet, {type ColorValue} from '../StyleSheet/StyleSheet';
import Text from '../Text/Text';
import Platform from '../Utilities/Platform';
import TouchableNativeFeedback from './Touchable/TouchableNativeFeedback';
import TouchableOpacity from './Touchable/TouchableOpacity';
import View from './View/View';
import invariant from 'invariant';
import * as React from 'react';
type ButtonPropsCore = Readonly<{
/**
* Text to display inside the button. On Android the given title will be
* converted to the uppercased form.
*/
title: string,
/**
* Handler called when the user taps the button.
*/
onPress?: (event?: GestureResponderEvent) => unknown,
/**
* If `true`, doesn't play system sound on touch.
*
* @platform android
*
* @default `false`
*/
touchSoundDisabled?: ?boolean,
/**
* Color of the text (iOS), or background color of the button (Android).
*
* @default {@platform android} `'#2196F3'`
* @default {@platform ios} `'#007AFF'`
*/
color?: ?ColorValue,
/**
* TV preferred focus.
*
* @platform tv
*
* @default `false`
* @deprecated Use `focusable` instead
*/
hasTVPreferredFocus?: ?boolean,
/**
* Designates the next view to receive focus when the user navigates down. See
* the [Android documentation][android:nextFocusDown].
*
* [android:nextFocusDown]:
* https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusDown
*
* @platform android, tv
*/
nextFocusDown?: ?number,
/**
* Designates the next view to receive focus when the user navigates forward.
* See the [Android documentation][android:nextFocusForward].
*
* [android:nextFocusForward]:
* https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusForward
*
* @platform android, tv
*/
nextFocusForward?: ?number,
/**
* Designates the next view to receive focus when the user navigates left. See
* the [Android documentation][android:nextFocusLeft].
*
* [android:nextFocusLeft]:
* https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusLeft
*
* @platform android, tv
*/
nextFocusLeft?: ?number,
/**
* Designates the next view to receive focus when the user navigates right. See
* the [Android documentation][android:nextFocusRight].
*
* [android:nextFocusRight]:
* https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusRight
*
* @platform android, tv
*/
nextFocusRight?: ?number,
/**
* Designates the next view to receive focus when the user navigates up. See
* the [Android documentation][android:nextFocusUp].
*
* [android:nextFocusUp]:
* https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusUp
*
* @platform android, tv
*/
nextFocusUp?: ?number,
/**
* Text to display for blindness accessibility features.
*/
accessibilityLabel?: ?string,
/**
* Alias for `accessibilityLabel`.
*/
'aria-label'?: ?string,
/**
* If `true`, disable all interactions for this component.
*
* @default `false`
*/
disabled?: ?boolean,
testID?: ?string,
accessible?: ?boolean,
accessibilityActions?: ?ReadonlyArray<AccessibilityActionInfo>,
onAccessibilityAction?: ?(event: AccessibilityActionEvent) => unknown,
accessibilityState?: ?AccessibilityState,
/**
* Alias for `accessibilityState`.
*/
'aria-busy'?: ?boolean,
'aria-checked'?: ?boolean | 'mixed',
'aria-disabled'?: ?boolean,
'aria-expanded'?: ?boolean,
'aria-selected'?: ?boolean,
importantForAccessibility?: ?('auto' | 'yes' | 'no' | 'no-hide-descendants'),
accessibilityHint?: ?string,
/**
* A BCP 47 language tag for the screen reader to use when reading text
* content.
*
* @platform ios
*/
accessibilityLanguage?: ?Stringish,
}>;
/** @build-types emit-as-interface Uniwind compatibility */
export type ButtonProps = ButtonPropsCore;
const NativeTouchable:
typeof TouchableNativeFeedback | typeof TouchableOpacity =
Platform.OS === 'android' ? TouchableNativeFeedback : TouchableOpacity;
export type ButtonInstance = React.ElementRef<typeof NativeTouchable>;
/**
* A basic button component that should render nicely on any platform. Supports a
* minimal level of customization.
*
* If this button doesn't look right for your app, you can build your own button
* using `Pressable`.
*
* Example:
*
* ```tsx
* <Button
* onPress={onPressLearnMore}
* title="Learn More"
* color="#841584"
* accessibilityLabel="Learn more about this purple button"
* />
* ```
*
* @see https://reactnative.dev/docs/button
*/
const Button: component(
ref?: React.RefSetter<ButtonInstance>,
...props: ButtonProps
) = ({
ref,
...props
}: {
ref?: React.RefSetter<ButtonInstance>,
...ButtonProps,
}) => {
const {
accessibilityLabel,
accessibilityState,
'aria-busy': ariaBusy,
'aria-checked': ariaChecked,
'aria-disabled': ariaDisabled,
'aria-expanded': ariaExpanded,
'aria-label': ariaLabel,
'aria-selected': ariaSelected,
importantForAccessibility,
color,
onPress,
touchSoundDisabled,
title,
hasTVPreferredFocus,
nextFocusDown,
nextFocusForward,
nextFocusLeft,
nextFocusRight,
nextFocusUp,
testID,
accessible,
accessibilityActions,
accessibilityHint,
accessibilityLanguage,
onAccessibilityAction,
} = props;
const buttonStyles: Array<ViewStyleProp> = [styles.button];
const textStyles: Array<TextStyleProp> = [styles.text];
if (Boolean(color)) {
if (Platform.OS === 'ios') {
textStyles.push({color: color});
} else {
buttonStyles.push({backgroundColor: color});
}
}
let _accessibilityState = {
busy: ariaBusy ?? accessibilityState?.busy,
checked: ariaChecked ?? accessibilityState?.checked,
disabled: ariaDisabled ?? accessibilityState?.disabled,
expanded: ariaExpanded ?? accessibilityState?.expanded,
selected: ariaSelected ?? accessibilityState?.selected,
};
const disabled =
props.disabled != null ? props.disabled : _accessibilityState?.disabled;
_accessibilityState =
disabled !== _accessibilityState?.disabled
? {..._accessibilityState, disabled}
: _accessibilityState;
if (disabled === true) {
buttonStyles.push(styles.buttonDisabled);
textStyles.push(styles.textDisabled);
}
invariant(
typeof title === 'string',
'The title prop of a Button must be a string',
);
const formattedTitle =
Platform.OS === 'android' ? title.toUpperCase() : title;
// If `no` is specified for `importantForAccessibility`, it will be changed to `no-hide-descendants` because the text inside should not be focused.
const _importantForAccessibility =
importantForAccessibility === 'no'
? 'no-hide-descendants'
: importantForAccessibility;
return (
<NativeTouchable
accessible={accessible}
accessibilityActions={accessibilityActions}
onAccessibilityAction={onAccessibilityAction}
accessibilityLabel={
// $FlowFixMe[sketchy-null-string]
ariaLabel || accessibilityLabel
}
accessibilityHint={accessibilityHint}
accessibilityLanguage={accessibilityLanguage}
accessibilityRole="button"
accessibilityState={_accessibilityState}
importantForAccessibility={_importantForAccessibility}
hasTVPreferredFocus={hasTVPreferredFocus}
nextFocusDown={nextFocusDown}
nextFocusForward={nextFocusForward}
nextFocusLeft={nextFocusLeft}
nextFocusRight={nextFocusRight}
nextFocusUp={nextFocusUp}
testID={testID}
disabled={disabled}
onPress={onPress}
touchSoundDisabled={touchSoundDisabled}
// $FlowFixMe[incompatible-exact]
// $FlowFixMe[prop-missing]
// $FlowFixMe[incompatible-type]
ref={ref}>
<View style={buttonStyles}>
<Text style={textStyles} disabled={disabled}>
{formattedTitle}
</Text>
</View>
</NativeTouchable>
);
};
Button.displayName = 'Button';
const styles = StyleSheet.create({
button: Platform.select({
ios: {},
android: {
elevation: 4,
// Material design blue from https://material.google.com/style/color.html#color-color-palette
backgroundColor: '#2196F3',
borderRadius: 2,
},
}),
text: {
textAlign: 'center',
margin: 8,
...Platform.select({
ios: {
// iOS blue from https://developer.apple.com/ios/human-interface-guidelines/visual-design/color/
color: '#007AFF',
fontSize: 18,
},
android: {
color: 'white',
fontWeight: '500',
},
}),
},
buttonDisabled: Platform.select({
ios: {},
android: {
elevation: 0,
backgroundColor: '#dfdfdf',
},
}),
textDisabled: Platform.select({
ios: {
color: '#cdcdcd',
},
android: {
color: '#a1a1a1',
},
}),
});
export default Button;