mirror of
https://github.com/expo/react-native-action-sheet.git
synced 2026-11-14 19:11:42 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b4439dd09 | ||
|
|
5ee7d9af74 | ||
|
|
68ff6a1df9 | ||
|
|
3be83131c6 | ||
|
|
8e943863db | ||
|
|
040980edcf | ||
|
|
231630b875 | ||
|
|
3c2ccec2d1 | ||
|
|
e35a4bffbb | ||
|
|
c87602ea3b | ||
|
|
8c1a319a3a | ||
|
|
ac17d305db | ||
|
|
2b9d8e5ec6 | ||
|
|
fe8df1b685 | ||
|
|
74473dc4bd | ||
|
|
2ca8d6217e | ||
|
|
cebaef95a3 | ||
|
|
15460b6a08 | ||
|
|
afb0fefd7b | ||
|
|
04a99ae94b | ||
|
|
432ae92569 | ||
|
|
2fa0e21086 | ||
|
|
33d77f4d04 | ||
|
|
883399be33 | ||
|
|
59e34c83b4 | ||
|
|
2e75f9551d | ||
|
|
2fc359b9c0 | ||
|
|
945028d34f | ||
|
|
ed82cb0047 | ||
|
|
42740e93a0 | ||
|
|
3c95b8a86d | ||
|
|
f35de5dd25 | ||
|
|
8ef0ee36e7 | ||
|
|
3a9bc50e84 | ||
|
|
7fe0aff0fe | ||
|
|
98528d9399 | ||
|
|
8ec19d3bfc | ||
|
|
3e2a8d6c1d | ||
|
|
bf5b9680aa | ||
|
|
cbba57e9c7 | ||
|
|
ebd285c342 | ||
|
|
81f64772c7 | ||
|
|
fa027c0f81 | ||
|
|
f9376eb0ca | ||
|
|
72d2e2a0ea | ||
|
|
acfd89c465 | ||
|
|
b2862ba08c | ||
|
|
dad86d74e8 | ||
|
|
f1b58141b0 | ||
|
|
dbd9da903c | ||
|
|
dfe724247c | ||
|
|
150cf5fcca | ||
|
|
da74bb03c4 | ||
|
|
eb3249f978 | ||
|
|
f1065b7ad6 | ||
|
|
ad32e911e6 | ||
|
|
621b0ad795 | ||
|
|
768a12ed0f | ||
|
|
bde52949cb | ||
|
|
1d8837090f | ||
|
|
ffdb5335d6 | ||
|
|
07b7676691 | ||
|
|
ca5c544b9e | ||
|
|
bf2c37859f | ||
|
|
215a0326a7 | ||
|
|
12623f953c | ||
|
|
ac2e2960ee | ||
|
|
15856cca3d | ||
|
|
2f3ba1b72d | ||
|
|
2ff7205f96 | ||
|
|
3c2a61de8e | ||
|
|
82887ca778 | ||
|
|
e73314d994 | ||
|
|
e901568a98 | ||
|
|
54fe453a5d | ||
|
|
d5dc960e0f | ||
|
|
83c98addea | ||
|
|
1fdcc54c92 | ||
|
|
2d1d25325a | ||
|
|
8bf86b1bf8 | ||
|
|
60fbd7d117 | ||
|
|
34a3ca1498 | ||
|
|
9b769eadc4 | ||
|
|
5d59088fea | ||
|
|
b25206f7e6 | ||
|
|
ccfc3b448a | ||
|
|
38150163c1 | ||
|
|
88d27eba92 | ||
|
|
9fee7dc244 | ||
|
|
b707cec0ae | ||
|
|
1501652c7f | ||
|
|
f577ec4908 | ||
|
|
3477e4d284 | ||
|
|
7dcd315ab2 | ||
|
|
2db7d67870 | ||
|
|
c3c57ea1d9 | ||
|
|
600b2fffc6 | ||
|
|
f799f58dba |
No files matched your search
+45
@@ -0,0 +1,45 @@
|
||||
# OSX
|
||||
#
|
||||
.DS_Store
|
||||
|
||||
# Xcode
|
||||
#
|
||||
build/
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
|
||||
# Android/IJ
|
||||
#
|
||||
.idea
|
||||
.gradle
|
||||
local.properties
|
||||
*.iml
|
||||
/android/app/*.keystore
|
||||
/android/app/*.apk
|
||||
|
||||
# node.js
|
||||
#
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
|
||||
# BUCK
|
||||
buck-out/
|
||||
\.buckd/
|
||||
android/app/libs
|
||||
android/keystores/debug.keystore
|
||||
|
||||
coverage
|
||||
@@ -1,369 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { PropTypes } from 'react';
|
||||
import {
|
||||
Animated,
|
||||
BackAndroid,
|
||||
Easing,
|
||||
PixelRatio,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
NativeModules,
|
||||
TouchableOpacity,
|
||||
TouchableNativeFeedback,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
// Not everybody has this enabled yet
|
||||
const useNativeDriver = !!NativeModules.NativeAnimatedModule;
|
||||
|
||||
type ActionSheetOptions = {
|
||||
options: Array<string>,
|
||||
destructiveButtonIndex: ?number,
|
||||
cancelButtonIndex: ?number,
|
||||
}
|
||||
|
||||
type ActionGroupProps = {
|
||||
options: Array<string>,
|
||||
destructiveButtonIndex: ?number,
|
||||
onSelect: (i: number) => boolean,
|
||||
startIndex: number,
|
||||
length: number,
|
||||
}
|
||||
|
||||
type ActionSheetState = {
|
||||
isVisible: boolean,
|
||||
isAnimating: boolean,
|
||||
options: ?ActionSheetOptions,
|
||||
onSelect: ?(i: number) => void,
|
||||
overlayOpacity: any,
|
||||
sheetOpacity: any,
|
||||
}
|
||||
|
||||
type ActionSheetProps = {
|
||||
children: ?any,
|
||||
}
|
||||
|
||||
const OPACITY_ANIMATION_TIME = 150;
|
||||
const PIXEL = 1 / PixelRatio.get();
|
||||
|
||||
class ActionGroup extends React.Component {
|
||||
props: ActionGroupProps;
|
||||
|
||||
static propTypes = {
|
||||
options: PropTypes.array.isRequired,
|
||||
destructiveButtonIndex: PropTypes.number,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
startIndex: PropTypes.number.isRequired,
|
||||
length: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
let {
|
||||
options,
|
||||
destructiveButtonIndex,
|
||||
onSelect,
|
||||
startIndex,
|
||||
length,
|
||||
} = this.props;
|
||||
|
||||
let optionViews = [];
|
||||
|
||||
let nativeFeedbackBackground = TouchableNativeFeedbackSafe.Ripple(
|
||||
'rgba(180, 180, 180, 1)',
|
||||
false
|
||||
);
|
||||
|
||||
for (let i = startIndex; i < startIndex + length; i++) {
|
||||
let color = '#444444';
|
||||
if (i === destructiveButtonIndex) {
|
||||
color = '#ff3b30';
|
||||
}
|
||||
|
||||
optionViews.push(
|
||||
<TouchableNativeFeedbackSafe
|
||||
key={i}
|
||||
pressInDelay={0}
|
||||
background={nativeFeedbackBackground}
|
||||
onPress={() => onSelect(i)}
|
||||
style={styles.button}>
|
||||
<Text style={[styles.text, {color}]}>
|
||||
{options[i]}
|
||||
</Text>
|
||||
</TouchableNativeFeedbackSafe>
|
||||
);
|
||||
|
||||
if (i < startIndex + length - 1) {
|
||||
optionViews.push(
|
||||
<View key={`separator-${i}`} style={styles.rowSeparator} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.groupContainer}>
|
||||
{optionViews}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Has same API as https://facebook.github.io/react-native/docs/actionsheetios.html
|
||||
export default class ActionSheet extends React.Component {
|
||||
props: ActionSheetProps;
|
||||
_animateOutCallback: ?() => void = null;
|
||||
|
||||
state: ActionSheetState = {
|
||||
isVisible: false,
|
||||
isAnimating: false,
|
||||
options: null,
|
||||
onSelect: null,
|
||||
overlayOpacity: new Animated.Value(0),
|
||||
sheetOpacity: new Animated.Value(0),
|
||||
};
|
||||
|
||||
render() {
|
||||
let { isVisible } = this.state;
|
||||
let overlay = isVisible ? (
|
||||
<Animated.View style={[styles.overlay, {
|
||||
opacity: this.state.overlayOpacity,
|
||||
}]} />
|
||||
) : null;
|
||||
|
||||
let sheet = isVisible ? this._renderSheet() : null;
|
||||
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
{React.Children.only(this.props.children)}
|
||||
{overlay}
|
||||
{sheet}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_renderSheet() {
|
||||
if (!this.state.options) {
|
||||
return;
|
||||
}
|
||||
|
||||
let numOptions = this.state.options.options.length;
|
||||
|
||||
return (
|
||||
<TouchableWithoutFeedback onPress={this._selectCancelButton}>
|
||||
<Animated.View
|
||||
needsOffscreenAlphaCompositing={this.state.isAnimating}
|
||||
style={[styles.sheetContainer, {
|
||||
opacity: this.state.sheetOpacity,
|
||||
transform: [{scale: this.state.sheetOpacity.interpolate({inputRange: [0, 0.5, 1], outputRange: [0.6, 1, 1]})}],
|
||||
}]}>
|
||||
<View style={styles.sheet}>
|
||||
<ActionGroup
|
||||
options={this.state.options.options}
|
||||
destructiveButtonIndex={this.state.options.destructiveButtonIndex}
|
||||
onSelect={this._onSelect}
|
||||
startIndex={0}
|
||||
length={numOptions}
|
||||
/>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
}
|
||||
|
||||
showActionSheetWithOptions(
|
||||
options: ActionSheetOptions,
|
||||
onSelect: (i: number) => void,
|
||||
onAnimateOut: () => void
|
||||
) {
|
||||
if (this.state.isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
options,
|
||||
onSelect,
|
||||
isVisible: true,
|
||||
isAnimating: true,
|
||||
});
|
||||
|
||||
this.state.overlayOpacity.setValue(0);
|
||||
this.state.sheetOpacity.setValue(0);
|
||||
|
||||
Animated.parallel([
|
||||
Animated.timing(this.state.overlayOpacity, {
|
||||
toValue: 0.5,
|
||||
easing: Easing.in(Easing.linear),
|
||||
duration: OPACITY_ANIMATION_TIME,
|
||||
useNativeDriver,
|
||||
}),
|
||||
Animated.timing(this.state.sheetOpacity, {
|
||||
toValue: 1,
|
||||
easing: Easing.in(Easing.linear),
|
||||
duration: OPACITY_ANIMATION_TIME,
|
||||
useNativeDriver,
|
||||
}),
|
||||
]).start(result => {
|
||||
if (result.finished) {
|
||||
this.setState({
|
||||
isAnimating: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this._animateOutCallback = onAnimateOut;
|
||||
|
||||
BackAndroid.addEventListener('actionSheetHardwareBackPress', this._selectCancelButton);
|
||||
}
|
||||
|
||||
_selectCancelButton = () => {
|
||||
if (!this.state.options) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof this.state.options.cancelButtonIndex === 'number') {
|
||||
return this._onSelect(this.state.options.cancelButtonIndex);
|
||||
} else {
|
||||
return this._animateOut();
|
||||
}
|
||||
}
|
||||
|
||||
_onSelect = (index: number): boolean => {
|
||||
if (this.state.isAnimating) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.state.onSelect && this.state.onSelect(index);
|
||||
return this._animateOut();
|
||||
}
|
||||
|
||||
_animateOut = (): boolean => {
|
||||
if (this.state.isAnimating) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BackAndroid.removeEventListener('actionSheetHardwareBackPress', this._selectCancelButton);
|
||||
|
||||
this.setState({
|
||||
isAnimating: true,
|
||||
});
|
||||
|
||||
Animated.parallel([
|
||||
Animated.timing(this.state.overlayOpacity, {
|
||||
toValue: 0,
|
||||
easing: Easing.in(Easing.linear),
|
||||
duration: OPACITY_ANIMATION_TIME,
|
||||
useNativeDriver,
|
||||
}),
|
||||
Animated.timing(this.state.sheetOpacity, {
|
||||
toValue: 0,
|
||||
easing: Easing.in(Easing.linear),
|
||||
duration: OPACITY_ANIMATION_TIME,
|
||||
useNativeDriver,
|
||||
}),
|
||||
]).start(result => {
|
||||
if (result.finished) {
|
||||
this.setState({
|
||||
isVisible: false,
|
||||
isAnimating: false,
|
||||
});
|
||||
if (typeof this._animateOutCallback === 'function') {
|
||||
this._animateOutCallback();
|
||||
this._animateOutCallback = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
let TouchableComponent;
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
TouchableComponent = Platform.Version <= 20 ? TouchableOpacity : TouchableNativeFeedback;
|
||||
} else {
|
||||
TouchableComponent = TouchableOpacity;
|
||||
}
|
||||
|
||||
if (TouchableComponent !== TouchableNativeFeedback) {
|
||||
TouchableComponent.SelectableBackground = () => ({});
|
||||
TouchableComponent.SelectableBackgroundBorderless = () => ({});
|
||||
TouchableComponent.Ripple = (color, borderless) => ({});
|
||||
}
|
||||
|
||||
class TouchableNativeFeedbackSafe extends React.Component {
|
||||
|
||||
static SelectableBackground = TouchableComponent.SelectableBackground;
|
||||
static SelectableBackgroundBorderless = TouchableComponent.SelectableBackgroundBorderless;
|
||||
static Ripple = TouchableComponent.Ripple;
|
||||
|
||||
render() {
|
||||
if (TouchableComponent === TouchableNativeFeedback) {
|
||||
return (
|
||||
<TouchableComponent {...this.props} style={{}}>
|
||||
<View style={this.props.style}>
|
||||
{this.props.children}
|
||||
</View>
|
||||
</TouchableComponent>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<TouchableComponent {...this.props}>
|
||||
{this.props.children}
|
||||
</TouchableComponent>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let styles = StyleSheet.create({
|
||||
groupContainer: {
|
||||
backgroundColor: '#fefefe',
|
||||
borderRadius: 4,
|
||||
borderColor: '#cbcbcb',
|
||||
borderWidth: PIXEL,
|
||||
overflow: 'hidden',
|
||||
marginHorizontal: 16,
|
||||
marginBottom: 8,
|
||||
},
|
||||
button: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-start',
|
||||
height: 50,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
text: {
|
||||
fontSize: 17,
|
||||
fontWeight: '700',
|
||||
},
|
||||
rowSeparator: {
|
||||
backgroundColor: '#dddddd',
|
||||
height: 1,
|
||||
flex: 1,
|
||||
},
|
||||
overlay: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
backgroundColor: 'black',
|
||||
},
|
||||
sheetContainer: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
backgroundColor: 'transparent',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
sheet: {
|
||||
flex: 1,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
});
|
||||
+11
-10
@@ -1,21 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
ActionSheetIOS,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { ActionSheetIOS, View } from 'react-native';
|
||||
|
||||
export default class ActionSheet extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
{React.Children.only(this.props.children)}
|
||||
</View>
|
||||
);
|
||||
return <View style={{ flex: 1 }}>{React.Children.only(this.props.children)}</View>;
|
||||
}
|
||||
|
||||
showActionSheetWithOptions(options, onSelect) {
|
||||
ActionSheetIOS.showActionSheetWithOptions(options, onSelect);
|
||||
const iosOptions = {
|
||||
...options,
|
||||
// A null title or message on iOS causes a crash
|
||||
title: options.title || undefined,
|
||||
message: options.message || undefined,
|
||||
icons: undefined,
|
||||
};
|
||||
|
||||
ActionSheetIOS.showActionSheetWithOptions(iosOptions, onSelect);
|
||||
}
|
||||
}
|
||||
+519
@@ -0,0 +1,519 @@
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Animated,
|
||||
BackHandler,
|
||||
Easing,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
Image,
|
||||
TouchableOpacity,
|
||||
TouchableNativeFeedback,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
ScrollView,
|
||||
} from 'react-native';
|
||||
|
||||
type ActionSheetOptions = {
|
||||
options: Array<string>,
|
||||
icons?: ?Array<number | React.Node>,
|
||||
tintIcons?: ?boolean,
|
||||
destructiveButtonIndex?: ?number,
|
||||
cancelButtonIndex?: ?number,
|
||||
textStyle?: ?any,
|
||||
tintColor?: ?string,
|
||||
title?: ?string,
|
||||
titleTextStyle?: ?any,
|
||||
message?: ?string,
|
||||
messageTextStyle?: ?any,
|
||||
showSeparators?: ?boolean,
|
||||
separatorStyle?: ?any,
|
||||
};
|
||||
|
||||
type ActionGroupProps = {
|
||||
options: Array<string>,
|
||||
icons: ?Array<number | React.Node>,
|
||||
tintIcons: ?boolean,
|
||||
destructiveButtonIndex: ?number,
|
||||
onSelect: (i: number) => boolean,
|
||||
startIndex: number,
|
||||
length: number,
|
||||
textStyle: ?any,
|
||||
tintColor: ?string,
|
||||
title: ?string,
|
||||
titleTextStyle: ?any,
|
||||
message: ?string,
|
||||
messageTextStyle: ?any,
|
||||
showSeparators: ?boolean,
|
||||
separatorStyle: ?any,
|
||||
};
|
||||
|
||||
type ActionSheetState = {
|
||||
isVisible: boolean,
|
||||
isAnimating: boolean,
|
||||
options: ?ActionSheetOptions,
|
||||
onSelect: ?(i: number) => void,
|
||||
overlayOpacity: any,
|
||||
sheetOpacity: any,
|
||||
};
|
||||
|
||||
type ActionSheetProps = {
|
||||
children: ?any,
|
||||
useNativeDriver: ?boolean,
|
||||
};
|
||||
|
||||
const OPACITY_ANIMATION_IN_TIME = 225;
|
||||
const OPACITY_ANIMATION_OUT_TIME = 195;
|
||||
const EASING_OUT = Easing.bezier(0.25, 0.46, 0.45, 0.94);
|
||||
const EASING_IN = Easing.out(EASING_OUT);
|
||||
const BLACK_54PC_TRANSPARENT = '#0000008a';
|
||||
const BLACK_87PC_TRANSPARENT = '#000000de';
|
||||
const DESTRUCTIVE_COLOR = '#d32f2f';
|
||||
|
||||
class ActionGroup extends React.Component {
|
||||
props: ActionGroupProps;
|
||||
|
||||
render() {
|
||||
let {
|
||||
options,
|
||||
icons,
|
||||
tintIcons,
|
||||
destructiveButtonIndex,
|
||||
onSelect,
|
||||
startIndex,
|
||||
length,
|
||||
textStyle,
|
||||
tintColor,
|
||||
showSeparators,
|
||||
} = this.props;
|
||||
|
||||
let optionViews = [];
|
||||
|
||||
let nativeFeedbackBackground = TouchableNativeFeedbackSafe.Ripple(
|
||||
'rgba(180, 180, 180, 1)',
|
||||
false
|
||||
);
|
||||
|
||||
for (let i = startIndex; i < startIndex + length; i++) {
|
||||
const defaultColor = tintColor
|
||||
? tintColor
|
||||
: (textStyle || {}).color || BLACK_87PC_TRANSPARENT;
|
||||
const color = i === destructiveButtonIndex ? DESTRUCTIVE_COLOR : defaultColor;
|
||||
const iconSource = icons != null && icons[i];
|
||||
let iconElement = undefined;
|
||||
|
||||
if (iconSource) {
|
||||
if (typeof iconSource === 'number') {
|
||||
const iconStyle = [styles.icon, { tintColor: tintIcons ? color : null }];
|
||||
iconElement = <Image
|
||||
fadeDuration={0}
|
||||
source={iconSource}
|
||||
resizeMode="contain"
|
||||
style={iconStyle}/>;
|
||||
} else {
|
||||
iconElement = <View style={styles.icon}>{iconSource}</View>;
|
||||
}
|
||||
}
|
||||
|
||||
optionViews.push(
|
||||
<TouchableNativeFeedbackSafe
|
||||
key={i}
|
||||
pressInDelay={0}
|
||||
background={nativeFeedbackBackground}
|
||||
onPress={() => onSelect(i)}
|
||||
style={styles.button}>
|
||||
{iconElement}
|
||||
<Text style={[styles.text, textStyle, { color }]}>{options[i]}</Text>
|
||||
</TouchableNativeFeedbackSafe>
|
||||
);
|
||||
|
||||
if (showSeparators && i < startIndex + length - 1) {
|
||||
optionViews.push(this._renderRowSeparator(i));
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.groupContainer}>
|
||||
{this._renderTitleContent()}
|
||||
<ScrollView>{optionViews}</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_renderRowSeparator(key) {
|
||||
return <View key={key ? `separator-${key}` : null} style={[styles.rowSeparator, this.props.separatorStyle]} />;
|
||||
}
|
||||
|
||||
_renderTitleContent() {
|
||||
const { title, titleTextStyle, message, messageTextStyle, showSeparators } = this.props;
|
||||
|
||||
if (!title && !message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style={[styles.titleContainer, { paddingBottom: showSeparators ? 24 : 16 }]}>
|
||||
{!!title && <Text style={[styles.title, titleTextStyle]}>{title}</Text>}
|
||||
{!!message && <Text style={[styles.message, messageTextStyle]}>{message}</Text>}
|
||||
</View>
|
||||
{!!showSeparators && this._renderRowSeparator('title')}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ActionGroup.propTypes = {
|
||||
options: PropTypes.array.isRequired,
|
||||
icons: PropTypes.array,
|
||||
tintIcons: PropTypes.bool,
|
||||
destructiveButtonIndex: PropTypes.number,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
startIndex: PropTypes.number.isRequired,
|
||||
length: PropTypes.number.isRequired,
|
||||
textStyle: Text.propTypes.style,
|
||||
tintColor: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
titleTextStyle: Text.propTypes.style,
|
||||
message: PropTypes.string,
|
||||
messageTextStyle: Text.propTypes.style,
|
||||
showSeparators: PropTypes.bool,
|
||||
separatorStyle: PropTypes.object,
|
||||
};
|
||||
|
||||
ActionGroup.defaultProps = {
|
||||
title: null,
|
||||
message: null,
|
||||
showSeparators: false,
|
||||
tintIcons: true,
|
||||
textStyle: {},
|
||||
};
|
||||
|
||||
// Has same API as https://facebook.github.io/react-native/docs/actionsheetios.html
|
||||
export default class ActionSheet extends React.Component {
|
||||
props: ActionSheetProps;
|
||||
|
||||
_actionSheetHeight = 360;
|
||||
_animateOutCallback: ?() => void = null;
|
||||
|
||||
state: ActionSheetState = {
|
||||
isVisible: false,
|
||||
isAnimating: false,
|
||||
options: null,
|
||||
onSelect: null,
|
||||
overlayOpacity: new Animated.Value(0),
|
||||
sheetOpacity: new Animated.Value(0),
|
||||
};
|
||||
|
||||
_setActionSheetHeight = ({ nativeEvent }) =>
|
||||
this._actionSheetHeight = nativeEvent.layout.height;
|
||||
|
||||
render() {
|
||||
const { isVisible, overlayOpacity } = this.state;
|
||||
const overlay = !!isVisible && (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.overlay,
|
||||
{
|
||||
opacity: overlayOpacity,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
{React.Children.only(this.props.children)}
|
||||
{overlay}
|
||||
{!!isVisible && this._renderSheet()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_renderSheet() {
|
||||
const { options, isAnimating, sheetOpacity } = this.state;
|
||||
|
||||
if (!options) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
options: optionsArray,
|
||||
icons,
|
||||
tintIcons,
|
||||
destructiveButtonIndex,
|
||||
textStyle,
|
||||
tintColor,
|
||||
title,
|
||||
titleTextStyle,
|
||||
message,
|
||||
messageTextStyle,
|
||||
showSeparators,
|
||||
separatorStyle,
|
||||
} = options;
|
||||
|
||||
return (
|
||||
<TouchableWithoutFeedback onPress={this._selectCancelButton}>
|
||||
<Animated.View
|
||||
needsOffscreenAlphaCompositing={isAnimating}
|
||||
style={[
|
||||
styles.sheetContainer,
|
||||
{
|
||||
opacity: sheetOpacity,
|
||||
transform: [
|
||||
{
|
||||
translateY: sheetOpacity.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [this._actionSheetHeight, 0],
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
]}>
|
||||
<View style={styles.sheet} onLayout={this._setActionSheetHeight}>
|
||||
<ActionGroup
|
||||
options={optionsArray}
|
||||
icons={icons}
|
||||
tintIcons={tintIcons === undefined ? true : tintIcons}
|
||||
destructiveButtonIndex={destructiveButtonIndex}
|
||||
onSelect={this._onSelect}
|
||||
startIndex={0}
|
||||
length={optionsArray.length}
|
||||
textStyle={textStyle || {}}
|
||||
tintColor={tintColor}
|
||||
title={title || null}
|
||||
titleTextStyle={titleTextStyle}
|
||||
message={message || null}
|
||||
messageTextStyle={messageTextStyle}
|
||||
showSeparators={showSeparators}
|
||||
separatorStyle={separatorStyle}
|
||||
/>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
}
|
||||
|
||||
showActionSheetWithOptions(
|
||||
options: ActionSheetOptions,
|
||||
onSelect: (i: number) => void,
|
||||
onAnimateOut: () => void
|
||||
) {
|
||||
const { isVisible, overlayOpacity, sheetOpacity } = this.state;
|
||||
|
||||
if (isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
options,
|
||||
onSelect,
|
||||
isVisible: true,
|
||||
isAnimating: true,
|
||||
});
|
||||
|
||||
overlayOpacity.setValue(0);
|
||||
sheetOpacity.setValue(0);
|
||||
|
||||
Animated.parallel([
|
||||
Animated.timing(overlayOpacity, {
|
||||
toValue: 0.32,
|
||||
easing: EASING_OUT,
|
||||
duration: OPACITY_ANIMATION_IN_TIME,
|
||||
useNativeDriver: this.props.useNativeDriver,
|
||||
}),
|
||||
Animated.timing(sheetOpacity, {
|
||||
toValue: 1,
|
||||
easing: EASING_OUT,
|
||||
duration: OPACITY_ANIMATION_IN_TIME,
|
||||
useNativeDriver: this.props.useNativeDriver,
|
||||
}),
|
||||
]).start(result => {
|
||||
if (result.finished) {
|
||||
this.setState({
|
||||
isAnimating: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this._animateOutCallback = onAnimateOut;
|
||||
|
||||
BackHandler.addEventListener(
|
||||
'actionSheetHardwareBackPress',
|
||||
this._selectCancelButton
|
||||
);
|
||||
}
|
||||
|
||||
_selectCancelButton = () => {
|
||||
const { options } = this.state;
|
||||
if (!options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof options.cancelButtonIndex === 'number') {
|
||||
return this._onSelect(options.cancelButtonIndex);
|
||||
} else {
|
||||
return this._animateOut();
|
||||
}
|
||||
};
|
||||
|
||||
_onSelect = (index: number): boolean => {
|
||||
const { isAnimating, onSelect } = this.state;
|
||||
|
||||
if (isAnimating) {
|
||||
return false;
|
||||
}
|
||||
|
||||
onSelect && onSelect(index);
|
||||
return this._animateOut();
|
||||
};
|
||||
|
||||
_animateOut = (): boolean => {
|
||||
const { isAnimating, overlayOpacity, sheetOpacity } = this.state;
|
||||
|
||||
if (isAnimating) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BackHandler.removeEventListener(
|
||||
'actionSheetHardwareBackPress',
|
||||
this._selectCancelButton
|
||||
);
|
||||
|
||||
this.setState({
|
||||
isAnimating: true,
|
||||
});
|
||||
|
||||
Animated.parallel([
|
||||
Animated.timing(overlayOpacity, {
|
||||
toValue: 0,
|
||||
easing: EASING_IN,
|
||||
duration: OPACITY_ANIMATION_OUT_TIME,
|
||||
useNativeDriver: this.props.useNativeDriver,
|
||||
}),
|
||||
Animated.timing(sheetOpacity, {
|
||||
toValue: 0,
|
||||
easing: EASING_IN,
|
||||
duration: OPACITY_ANIMATION_OUT_TIME,
|
||||
useNativeDriver: this.props.useNativeDriver,
|
||||
}),
|
||||
]).start(result => {
|
||||
if (result.finished) {
|
||||
this.setState({
|
||||
isVisible: false,
|
||||
isAnimating: false,
|
||||
});
|
||||
if (typeof this._animateOutCallback === 'function') {
|
||||
this._animateOutCallback();
|
||||
this._animateOutCallback = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
ActionSheet.defaultProps = {
|
||||
useNativeDriver: true,
|
||||
};
|
||||
|
||||
let TouchableComponent = Platform.select({
|
||||
web: TouchableOpacity,
|
||||
default: Platform.Version <= 20 ? TouchableOpacity : TouchableNativeFeedback,
|
||||
});
|
||||
|
||||
if (TouchableComponent !== TouchableNativeFeedback) {
|
||||
TouchableComponent.SelectableBackground = () => ({});
|
||||
TouchableComponent.SelectableBackgroundBorderless = () => ({});
|
||||
TouchableComponent.Ripple = (color, borderless) => ({});
|
||||
}
|
||||
|
||||
class TouchableNativeFeedbackSafe extends React.Component {
|
||||
static SelectableBackground = TouchableComponent.SelectableBackground;
|
||||
static SelectableBackgroundBorderless = TouchableComponent.SelectableBackgroundBorderless;
|
||||
static Ripple = TouchableComponent.Ripple;
|
||||
|
||||
render() {
|
||||
if (TouchableComponent === TouchableNativeFeedback) {
|
||||
return (
|
||||
<TouchableComponent {...this.props} style={{}}>
|
||||
<View style={this.props.style}>{this.props.children}</View>
|
||||
</TouchableComponent>
|
||||
);
|
||||
} else {
|
||||
return <TouchableComponent {...this.props}>{this.props.children}</TouchableComponent>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
groupContainer: {
|
||||
backgroundColor: '#ffffff',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
button: {
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
height: 56,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
icon: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
marginRight: 32,
|
||||
},
|
||||
text: {
|
||||
fontSize: 16,
|
||||
color: BLACK_87PC_TRANSPARENT,
|
||||
textAlignVertical: 'center',
|
||||
},
|
||||
rowSeparator: {
|
||||
backgroundColor: '#dddddd',
|
||||
height: 1,
|
||||
width: '100%',
|
||||
},
|
||||
overlay: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
backgroundColor: 'black',
|
||||
},
|
||||
sheetContainer: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
backgroundColor: 'transparent',
|
||||
alignItems: 'flex-end',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
sheet: {
|
||||
flex: 1,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
titleContainer: {
|
||||
alignItems: 'flex-start',
|
||||
padding: 16,
|
||||
paddingTop: 24,
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
color: BLACK_54PC_TRANSPARENT,
|
||||
textAlignVertical: 'center',
|
||||
},
|
||||
message: {
|
||||
marginTop: 12,
|
||||
fontSize: 14,
|
||||
color: BLACK_54PC_TRANSPARENT,
|
||||
textAlignVertical: 'center',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ActionSheet from './ActionSheet';
|
||||
|
||||
export default class ActionSheetProvider extends React.Component {
|
||||
static propTypes = {
|
||||
textStyle: PropTypes.object,
|
||||
tintIcons: PropTypes.bool,
|
||||
tintColor: PropTypes.string,
|
||||
titleTextStyle: PropTypes.object,
|
||||
messageTextStyle: PropTypes.object,
|
||||
showSeparators: PropTypes.bool,
|
||||
separatorStyle: PropTypes.object,
|
||||
children: PropTypes.any.isRequired,
|
||||
};
|
||||
|
||||
static childContextTypes = {
|
||||
showActionSheetWithOptions: PropTypes.func,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
textStyle: {},
|
||||
tintIcons: true,
|
||||
titleTextStyle: {},
|
||||
messageTextStyle: {},
|
||||
showSeparators: false,
|
||||
separatorStyle: {},
|
||||
};
|
||||
|
||||
getNextValue = (config, propKey) => config[propKey] !== undefined ? config[propKey] : this.props[propKey];
|
||||
|
||||
getNextObject = (config, propKey) => ({...this.props[propKey], ...config[propKey]});
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
showActionSheetWithOptions: (...args) => {
|
||||
const [config, ...rest] = args;
|
||||
const nextConfig = {
|
||||
...config,
|
||||
textStyle: this.getNextObject(config, 'textStyle'),
|
||||
tintIcons: this.getNextValue(config, 'tintIcons'),
|
||||
tintColor: this.getNextValue(config, 'tintColor'),
|
||||
titleTextStyle: this.getNextObject(config, 'titleTextStyle'),
|
||||
messageTextStyle: this.getNextObject(config, 'messageTextStyle'),
|
||||
showSeparators: this.getNextValue(config, 'showSeparators'),
|
||||
separatorStyle: this.getNextObject(config, 'separatorStyle'),
|
||||
};
|
||||
|
||||
this._actionSheetRef.showActionSheetWithOptions(nextConfig, ...rest);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ActionSheet ref={component => (this._actionSheetRef = component)}>
|
||||
{React.Children.only(this.props.children)}
|
||||
</ActionSheet>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Exponent
|
||||
Copyright (c) 2015 Expo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,61 +1,95 @@
|
||||
# react-native-action-sheet [](http://slack.exponentjs.com)
|
||||
# react-native-action-sheet [](https://slack.expo.io)
|
||||
|
||||
ActionSheet is a cross-platform React Native component that uses the native UIActionSheet on iOS and a JS implementation on Android. Almost a drop in replacement for [ActionSheetIOS](https://facebook.github.io/react-native/docs/actionsheetios.html) except it cannot be called statically.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
npm install @exponent/react-native-action-sheet
|
||||
npm install @expo/react-native-action-sheet
|
||||
```
|
||||
|
||||
## Usage
|
||||
## A basic ActionSheet Setup
|
||||
|
||||
Wrap your entire app in `ActionSheet`. We recommend using [context](https://facebook.github.io/react/docs/context.html) to pass `ActionSheet` to the rest of your app.
|
||||
import ActionSheetProvider & connectActionSheet
|
||||
|
||||
```js
|
||||
class MainApp extends React.Component {
|
||||
```es6
|
||||
import {
|
||||
ActionSheetProvider,
|
||||
connectActionSheet,
|
||||
} from '@expo/react-native-action-sheet';
|
||||
```
|
||||
|
||||
static childContextTypes = {
|
||||
actionSheet: PropTypes.func,
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
actionSheet: () => this._actionSheetRef,
|
||||
};
|
||||
}
|
||||
wrap your top-level component with `<ActionSheetProvider />`
|
||||
|
||||
```es6
|
||||
class AppContainer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ActionSheet ref={component => this._actionSheetRef = component}>
|
||||
// Render the rest of your app here.
|
||||
</ActionSheet>
|
||||
<ActionSheetProvider>
|
||||
<App />
|
||||
</ActionSheetProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To open the action sheet:
|
||||
decorate the component you want to use the action sheet with `@connectActionSheet`
|
||||
|
||||
```js
|
||||
class OtherComponent extends React.Component {
|
||||
```es6
|
||||
@connectActionSheet
|
||||
class App extends React.Component {
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
static contextTypes = {
|
||||
actionSheet: PropTypes.func,
|
||||
};
|
||||
access actionSheet method as `this.props.showActionSheetWithOptions`
|
||||
|
||||
_onOpenActionSheet() {
|
||||
// Same interface as https://facebook.github.io/react-native/docs/actionsheetios.html
|
||||
let options = ['Delete', 'Save', 'Cancel'];
|
||||
let destructiveButtonIndex = 0;
|
||||
let cancelButtonIndex = 2;
|
||||
this.context.actionSheet().showActionSheetWithOptions({
|
||||
```es6
|
||||
_onOpenActionSheet = () => {
|
||||
// Same interface as https://facebook.github.io/react-native/docs/actionsheetios.html
|
||||
const options = ['Delete', 'Save', 'Cancel'];
|
||||
const destructiveButtonIndex = 0;
|
||||
const cancelButtonIndex = 2;
|
||||
|
||||
this.props.showActionSheetWithOptions(
|
||||
{
|
||||
options,
|
||||
cancelButtonIndex,
|
||||
destructiveButtonIndex,
|
||||
},
|
||||
(buttonIndex) => {
|
||||
// Do something here
|
||||
});
|
||||
}
|
||||
}
|
||||
buttonIndex => {
|
||||
// Do something here depending on the button index selected
|
||||
},
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
The goal of this library is to mimic the native iOS and Android ActionSheets as closely as possible.
|
||||
|
||||
This library can also be used with on web with Expo.
|
||||
|
||||
### Universal Props
|
||||
|
||||
The same options available on React Native's [ActionSheetIOS](https://facebook.github.io/react-native/docs/actionsheetios.html#showactionsheetwithoptions) component exist for both iOS and Android in this library.
|
||||
|
||||
### Android-Only Props
|
||||
|
||||
The below props allow modification of the Android ActionSheet. They have no effect on the look on iOS as the native iOS Action Sheet does not have options for modifying these options.
|
||||
|
||||
- `icons` (array of required images or icons; optional): Show icons to go along with each option. If image source paths are provided via `require`, images will be rendered for you. Alternatively, you can provide an array of elements such as vector icons, pre-rendered Images, etc.
|
||||
- `tintIcons` (boolean; optional; default: `true`): Icons by default will be tinted to match the text color. When set to false, the icons will be the color of the source image. This is useful if you want to use multicolor icons. If you provide your own nodes/pre-rendered icons rather than required images in the `icons` array, you will need to tint them appropriately before providing them in the array of `icons`; `tintColor` will not be applied to icons unless they are images from a required source.
|
||||
- `textStyle` (Text.propTypes.style; optional): Apply any text style props to the options. If the `tintColor` option is provided, it takes precedence over a color text style prop.
|
||||
- `titleTextStyle` (Text.propTypes.style; optional): Apply any text style props to the title if present.
|
||||
- `messageTextStyle` (Text.propTypes.style; optional): Apply any text style props to the message if present.
|
||||
- `showSeparators`: (boolean; optional; default: false): Show separators between items. On iOS, separators always show so this prop has no effect.
|
||||
- `separatorStyle`: (View.propTypes.style; optional): Modify the look of the separators rather than use the default look.
|
||||
|
||||
## Try it out
|
||||
|
||||
Try it in Expo: https://expo.io/@community/react-native-action-sheet-example
|
||||
|
||||
## Usage
|
||||
|
||||
See the [example app](https://github.com/expo/react-native-action-sheet/tree/master/example) for examples of how to apply different options.
|
||||
@@ -0,0 +1,20 @@
|
||||
import hoistStatics from 'hoist-non-react-statics';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default function connectActionSheet(WrappedComponent) {
|
||||
const ConnectedActionSheet = (props, context) => {
|
||||
return (
|
||||
<WrappedComponent
|
||||
{...props}
|
||||
showActionSheetWithOptions={context.showActionSheetWithOptions}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
ConnectedActionSheet.contextTypes = {
|
||||
showActionSheetWithOptions: PropTypes.func,
|
||||
};
|
||||
|
||||
return hoistStatics(ConnectedActionSheet, WrappedComponent);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"presets": ["babel-preset-expo"],
|
||||
"env": {
|
||||
"development": {
|
||||
"plugins": ["transform-react-jsx-source"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
node_modules/**/*
|
||||
.expo/*
|
||||
npm-debug.*
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
import Expo from 'expo';
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text, View, ScrollView, SafeAreaView } from 'react-native';
|
||||
import { ActionSheetProvider, connectActionSheet } from '@expo/react-native-action-sheet';
|
||||
import ShowActionSheetButton from './ShowActionSheetButton';
|
||||
|
||||
export default class AppContainer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ActionSheetProvider>
|
||||
<App />
|
||||
</ActionSheetProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@connectActionSheet
|
||||
class App extends React.Component {
|
||||
state = {
|
||||
selectedIndex: null,
|
||||
};
|
||||
|
||||
_updateSelectionText = (selectedIndex) => {
|
||||
this.setState({ selectedIndex });
|
||||
};
|
||||
|
||||
_renderSelectionText() {
|
||||
const { selectedIndex } = this.state;
|
||||
const text = selectedIndex === null
|
||||
? 'No Option Selected'
|
||||
: `Option #${selectedIndex + 1} Selected`;
|
||||
|
||||
return <Text style={styles.selectionText}>{text}</Text>
|
||||
}
|
||||
|
||||
_renderSectionHeader(text) {
|
||||
return <Text style={styles.sectionHeaderText}>{text}</Text>;
|
||||
}
|
||||
|
||||
_renderButtons() {
|
||||
const { showActionSheetWithOptions } = this.props;
|
||||
return (
|
||||
<View style={{ alignItems: 'center' }}>
|
||||
{this._renderSectionHeader('Universal Options')}
|
||||
<ShowActionSheetButton
|
||||
title="Options Only"
|
||||
onSelection={this._updateSelectionText}
|
||||
showActionSheetWithOptions={showActionSheetWithOptions} />
|
||||
<ShowActionSheetButton
|
||||
title="Title"
|
||||
withTitle
|
||||
onSelection={this._updateSelectionText}
|
||||
showActionSheetWithOptions={showActionSheetWithOptions} />
|
||||
<ShowActionSheetButton
|
||||
title="Title & Message"
|
||||
withTitle
|
||||
withMessage
|
||||
onSelection={this._updateSelectionText}
|
||||
showActionSheetWithOptions={showActionSheetWithOptions} />
|
||||
{this._renderSectionHeader('Android-Only Options')}
|
||||
<ShowActionSheetButton
|
||||
title="Icons"
|
||||
withIcons
|
||||
onSelection={this._updateSelectionText}
|
||||
showActionSheetWithOptions={showActionSheetWithOptions} />
|
||||
<ShowActionSheetButton
|
||||
title="Title, Message, & Icons"
|
||||
withTitle
|
||||
withMessage
|
||||
withIcons
|
||||
onSelection={this._updateSelectionText}
|
||||
showActionSheetWithOptions={showActionSheetWithOptions} />
|
||||
<ShowActionSheetButton
|
||||
title="Use Separators"
|
||||
withTitle
|
||||
withIcons
|
||||
withSeparators
|
||||
onSelection={this._updateSelectionText}
|
||||
showActionSheetWithOptions={showActionSheetWithOptions} />
|
||||
<ShowActionSheetButton
|
||||
title="Custom Styles"
|
||||
withTitle
|
||||
withMessage
|
||||
withIcons
|
||||
withCustomStyles
|
||||
onSelection={this._updateSelectionText}
|
||||
showActionSheetWithOptions={showActionSheetWithOptions} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SafeAreaView style={styles.flex}>
|
||||
<ScrollView style={styles.flex} contentContainerStyle={styles.contentContainer}>
|
||||
<Text style={styles.headerText}>
|
||||
{"Hello!\n\nThis is a simple example app to demonstrate @expo/react-native-action-sheet."}
|
||||
</Text>
|
||||
{this._renderButtons()}
|
||||
{this._renderSelectionText()}
|
||||
<Text style={styles.notes}>
|
||||
Note: Icons and custom text styles are only available on Android. Separators can only be toggled on Android; they always show on iOS.
|
||||
</Text>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
contentContainer: {
|
||||
padding: 16,
|
||||
paddingVertical: 20,
|
||||
},
|
||||
headerText: {
|
||||
textAlign: 'center',
|
||||
fontSize: 16,
|
||||
marginBottom: 10,
|
||||
},
|
||||
notes: {
|
||||
marginTop: 32,
|
||||
},
|
||||
sectionHeaderText: {
|
||||
color: 'orange',
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 20,
|
||||
marginTop: 20,
|
||||
marginBottom: 10,
|
||||
},
|
||||
selectionText: {
|
||||
textAlign: 'center',
|
||||
color: 'blue',
|
||||
fontSize: 16,
|
||||
marginTop: 20,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MaterialIcons, Entypo } from '@expo/vector-icons';
|
||||
|
||||
const icon = (name) => <MaterialIcons key={name} name={name} size={24} />;
|
||||
|
||||
// A custom button that shows examples of different share sheet configurations
|
||||
class ShowActionSheetButton extends React.PureComponent {
|
||||
_showActionSheet = () => {
|
||||
const {
|
||||
withTitle,
|
||||
withMessage,
|
||||
withIcons,
|
||||
withSeparators,
|
||||
withCustomStyles,
|
||||
onSelection,
|
||||
showActionSheetWithOptions,
|
||||
} = this.props;
|
||||
// Same interface as https://facebook.github.io/react-native/docs/actionsheetios.html
|
||||
const options = ['Delete', 'Save', 'Share', 'Cancel'];
|
||||
const icons = withIcons ? [icon('delete'), icon('save'), icon('share'), icon('cancel')] : null;
|
||||
const title = withTitle ? 'Choose An Action' : null;
|
||||
const message = withMessage ? 'This library tries to mimic the native share sheets as close as possible.' : null;
|
||||
const destructiveButtonIndex = 0;
|
||||
const cancelButtonIndex = 3;
|
||||
const textStyle = withCustomStyles ? { fontSize: 20, fontWeight: '500', color: 'blue' } : null;
|
||||
const titleTextStyle = withCustomStyles ? { fontSize: 24, textAlign: 'center', fontWeight: '700', color: 'orange' } : null;
|
||||
const messageTextStyle = withCustomStyles ? { fontSize: 12, color: 'purple', textAlign: 'right' } : null;
|
||||
|
||||
showActionSheetWithOptions(
|
||||
{
|
||||
options,
|
||||
cancelButtonIndex,
|
||||
destructiveButtonIndex,
|
||||
title,
|
||||
message,
|
||||
icons, // Android only
|
||||
tintIcons: true, // Android only; default is true
|
||||
showSeparators: withSeparators, // Affects Android only; default is false
|
||||
textStyle, // Android only
|
||||
titleTextStyle, // Android only
|
||||
messageTextStyle, // Android only
|
||||
},
|
||||
buttonIndex => {
|
||||
// Do something here depending on the button index selected
|
||||
onSelection(buttonIndex);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { title } = this.props;
|
||||
return (
|
||||
<View style={{ margin: 6 }}>
|
||||
<Entypo.Button
|
||||
name="code"
|
||||
backgroundColor="#3e3e3e"
|
||||
onPress={this._showActionSheet}>
|
||||
<Text style={{
|
||||
fontSize: 15,
|
||||
color: '#fff'
|
||||
}}>{title}</Text>
|
||||
</Entypo.Button>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ShowActionSheetButton.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
onSelection: PropTypes.func,
|
||||
showActionSheetWithOptions: PropTypes.func.isRequired,
|
||||
withTitle: PropTypes.bool,
|
||||
withMessage: PropTypes.bool,
|
||||
withIcons: PropTypes.bool,
|
||||
withSeparators: PropTypes.bool,
|
||||
withCustomStyles: PropTypes.bool,
|
||||
};
|
||||
|
||||
ShowActionSheetButton.defaultProps = {
|
||||
withTitle: false,
|
||||
withMessage: false,
|
||||
withIcons: false,
|
||||
withSeparators: false,
|
||||
withCustomStyles: false,
|
||||
onSelection: null,
|
||||
};
|
||||
|
||||
export default ShowActionSheetButton;
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "@expo/react-native-action-sheet-example",
|
||||
"description": "A cross-platform ActionSheet component. https://github.com/expo/react-native-action-sheet",
|
||||
"slug": "react-native-action-sheet-example",
|
||||
"privacy": "public",
|
||||
"sdkVersion": "25.0.0",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"primaryColor": "#cccccc",
|
||||
"icon": "https://s3.amazonaws.com/exp-brand-assets/ExponentEmptyManifest_192.png",
|
||||
"packagerOpts": {
|
||||
"assetExts": ["ttf", "mp4"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "example",
|
||||
"version": "0.0.0",
|
||||
"description": "Hello Expo!",
|
||||
"author": "[email protected]",
|
||||
"private": true,
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"dependencies": {
|
||||
"@expo/react-native-action-sheet": "../",
|
||||
"expo": "^25.0.0",
|
||||
"react": "16.2.0",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-25.0.0.tar.gz"
|
||||
}
|
||||
}
|
||||
+3985
File diff suppressed because it is too large.
Load diff
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
import { ActionSheetIOSOptions } from 'react-native';
|
||||
|
||||
export interface ActionSheetProps {
|
||||
showActionSheetWithOptions: (
|
||||
options: ActionSheetIOSOptions,
|
||||
callback: (buttonIndex: number) => void,
|
||||
) => void;
|
||||
}
|
||||
|
||||
export function connectActionSheet<T>(
|
||||
WrappedComponent: React.ComponentType<T & ActionSheetProps>,
|
||||
): React.ComponentClass<T>;
|
||||
|
||||
export const ActionSheetProvider: React.ComponentClass;
|
||||
@@ -1 +1,4 @@
|
||||
module.exports = require('./ActionSheet');
|
||||
|
||||
module.exports.ActionSheetProvider = require('./ActionSheetProvider').default;
|
||||
module.exports.connectActionSheet = require('./connectActionSheet').default;
|
||||
+21
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@exponent/react-native-action-sheet",
|
||||
"version": "0.2.1",
|
||||
"name": "@expo/react-native-action-sheet",
|
||||
"version": "2.1.0",
|
||||
"description": "A cross-platform ActionSheet for React Native",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -8,21 +8,36 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/exponentjs/react-native-action-sheet.git"
|
||||
"url": "https://github.com/expo/react-native-action-sheet.git"
|
||||
},
|
||||
"keywords": [
|
||||
"react-native",
|
||||
"action-sheet"
|
||||
],
|
||||
"files": [
|
||||
"ActionSheet.js",
|
||||
"ActionSheet.ios.js",
|
||||
"ActionSheetProvider.js",
|
||||
"connectActionSheet.js",
|
||||
"index.js",
|
||||
"index.d.ts",
|
||||
"README.md"
|
||||
],
|
||||
"author": "Jesse Ruder <[email protected]>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/exponentjs/react-native-action-sheet/issues"
|
||||
"url": "https://github.com/expo/react-native-action-sheet/issues"
|
||||
},
|
||||
"homepage": "https://github.com/expo/react-native-action-sheet",
|
||||
"dependencies": {
|
||||
"hoist-non-react-statics": "^2.2.2",
|
||||
"prop-types": "^15.5.10"
|
||||
},
|
||||
"homepage": "https://github.com/exponentjs/react-native-action-sheet",
|
||||
"devDependencies": {
|
||||
"@types/react": "^16.4.6",
|
||||
"@types/react-native": "^0.56.0",
|
||||
"flow-bin": "^0.26.0",
|
||||
"react": "^15.2.0",
|
||||
"react": "^15.6.1",
|
||||
"react-native": "^0.28.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
ActionSheetProps,
|
||||
ActionSheetProvider,
|
||||
connectActionSheet
|
||||
} from "./index";
|
||||
import * as React from "react";
|
||||
|
||||
interface ChildOwnProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
type ChildProps = ChildOwnProps & ActionSheetProps;
|
||||
|
||||
class Child extends React.Component<ChildProps> {
|
||||
showMore = () => {
|
||||
this.props.showActionSheetWithOptions(
|
||||
{ options: ["Delete"] },
|
||||
buttonIndex => {
|
||||
console.log(buttonIndex);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return this.props.name;
|
||||
}
|
||||
}
|
||||
|
||||
const EnhancedChild = connectActionSheet(Child);
|
||||
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ActionSheetProvider>
|
||||
<EnhancedChild name="lool" />
|
||||
</ActionSheetProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user