mirror of
https://github.com/expo/react-native-action-sheet.git
synced 2026-11-15 09:18:06 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe8c66dd0b | ||
|
|
bc3c27d767 | ||
|
|
33c7d9301c | ||
|
|
b20bf029ee | ||
|
|
37156dacf1 | ||
|
|
0d98570861 | ||
|
|
6fe293d4e2 | ||
|
|
ead0ed7dc1 | ||
|
|
27cef1a658 | ||
|
|
bfae6b4ebc | ||
|
|
eecc79ffb0 | ||
|
|
370e6e855f | ||
|
|
6fa494f9de | ||
|
|
fc86afb87e | ||
|
|
40aee02227 | ||
|
|
ac6f9f6ccd | ||
|
|
0131ebbb44 | ||
|
|
0627e77fba | ||
|
|
d114cfb321 | ||
|
|
8016008781 | ||
|
|
39fc431c97 | ||
|
|
76397fa7c4 | ||
|
|
8448370658 | ||
|
|
223d35fb85 | ||
|
|
979da65d59 | ||
|
|
b4f1ae39f9 | ||
|
|
817582e054 | ||
|
|
1a34e86f43 | ||
|
|
e571b97653 | ||
|
|
a4e11daeec | ||
|
|
08a929d477 | ||
|
|
cd966b48ad | ||
|
|
3fc5e9f478 | ||
|
|
7f83be5f7a | ||
|
|
4e3914df1b | ||
|
|
22c0c52c6a | ||
|
|
032e7c632b | ||
|
|
d4a8c0398e | ||
|
|
638173e2ec | ||
|
|
a2b40112a9 | ||
|
|
31a25c27be | ||
|
|
7e0f6173b3 | ||
|
|
113353e853 | ||
|
|
a5c88169a7 | ||
|
|
384386f85b | ||
|
|
ebbca8a1ac | ||
|
|
764fef3359 | ||
|
|
c27e88deb5 | ||
|
|
3091a2ab25 | ||
|
|
46ec2db2d3 | ||
|
|
22753e0c0c | ||
|
|
a58dff38d0 | ||
|
|
3df9bae02f | ||
|
|
5051be3c0e | ||
|
|
44d5ae3e77 | ||
|
|
5d42516a87 | ||
|
|
e19ae91d3d | ||
|
|
f5ec6b4ab8 | ||
|
|
fb302a5bce | ||
|
|
973dfb4f3f | ||
|
|
874930c050 | ||
|
|
de9635aa2c | ||
|
|
cf5251594a | ||
|
|
2c82eaa89c | ||
|
|
e1964bce80 | ||
|
|
02f99ebb83 | ||
|
|
dc2176e463 | ||
|
|
16d1ef0b41 | ||
|
|
7666619df5 | ||
|
|
39f4e94550 | ||
|
|
eacda36ec0 | ||
|
|
3b26304031 | ||
|
|
41a06917b0 | ||
|
|
337057a635 | ||
|
|
424660ada5 | ||
|
|
2e74084272 | ||
|
|
56d379bc23 | ||
|
|
2ea391f668 | ||
|
|
d58d80fd89 | ||
|
|
e9b2c8399c | ||
|
|
5618219c30 | ||
|
|
40f945a035 | ||
|
|
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 | ||
|
|
42740e93a0 | ||
|
|
3c95b8a86d | ||
|
|
f35de5dd25 | ||
|
|
8ef0ee36e7 | ||
|
|
3a9bc50e84 | ||
|
|
7fe0aff0fe | ||
|
|
98528d9399 | ||
|
|
8ec19d3bfc | ||
|
|
3e2a8d6c1d | ||
|
|
81f64772c7 | ||
|
|
fa027c0f81 | ||
|
|
f9376eb0ca | ||
|
|
72d2e2a0ea | ||
|
|
acfd89c465 | ||
|
|
b2862ba08c | ||
|
|
dfe724247c | ||
|
|
150cf5fcca | ||
|
|
b25206f7e6 | ||
|
|
ccfc3b448a | ||
|
|
38150163c1 | ||
|
|
88d27eba92 | ||
|
|
9fee7dc244 |
No files matched your search
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
exmaple/node_modules
|
||||
|
||||
# generated by bob
|
||||
lib/
|
||||
@@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
'@react-native-community',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier/@typescript-eslint',
|
||||
],
|
||||
rules: {
|
||||
// Turn off rules which are useless and annoying for this project
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-member-accessibility': 'off',
|
||||
'@typescript-eslint/no-use-before-define': 'off',
|
||||
'react-native/no-inline-styles': 'off',
|
||||
},
|
||||
};
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
[ignore]
|
||||
|
||||
# We fork some components by platform.
|
||||
.*/*.web.js
|
||||
.*/node_modules/.*/*.android.js
|
||||
|
||||
# Some modules have their own node_modules with overlap
|
||||
.*/node_modules/node-haste/.*
|
||||
|
||||
# Ugh
|
||||
.*/node_modules/babel.*
|
||||
.*/node_modules/babylon.*
|
||||
.*/node_modules/invariant.*
|
||||
|
||||
# Ignore react and fbjs where there are overlaps, but don't ignore
|
||||
# anything that react-native relies on
|
||||
.*/node_modules/fbjs/lib/Map.js
|
||||
.*/node_modules/fbjs/lib/ErrorUtils.js
|
||||
|
||||
# Flow has a built-in definition for the 'react' module which we prefer to use
|
||||
# over the currently-untyped source
|
||||
.*/node_modules/react/react.js
|
||||
.*/node_modules/react/lib/React.js
|
||||
.*/node_modules/react/lib/ReactDOM.js
|
||||
|
||||
.*/__mocks__/.*
|
||||
.*/__tests__/.*
|
||||
|
||||
.*/commoner/test/source/widget/share.js
|
||||
|
||||
# Ignore commoner tests
|
||||
.*/node_modules/commoner/test/.*
|
||||
|
||||
# See https://github.com/facebook/flow/issues/442
|
||||
.*/react-tools/node_modules/commoner/lib/reader.js
|
||||
|
||||
# Ignore jest
|
||||
.*/node_modules/jest-cli/.*
|
||||
|
||||
# Ignore Website
|
||||
.*/website/.*
|
||||
|
||||
# Ignore generators
|
||||
.*/local-cli/generator.*
|
||||
|
||||
# Ignore BUCK generated folders
|
||||
.*\.buckd/
|
||||
|
||||
# Ignore RNPM
|
||||
.*/local-cli/rnpm/.*
|
||||
|
||||
.*/node_modules/is-my-json-valid/test/.*\.json
|
||||
.*/node_modules/iconv-lite/encodings/tables/.*\.json
|
||||
.*/node_modules/y18n/test/.*\.json
|
||||
.*/node_modules/spdx-license-ids/spdx-license-ids.json
|
||||
.*/node_modules/spdx-exceptions/index.json
|
||||
.*/node_modules/resolve/test/subdirs/node_modules/a/b/c/x.json
|
||||
.*/node_modules/resolve/lib/core.json
|
||||
.*/node_modules/jsonparse/samplejson/.*\.json
|
||||
.*/node_modules/json5/test/.*\.json
|
||||
.*/node_modules/ua-parser-js/test/.*\.json
|
||||
.*/node_modules/builtin-modules/builtin-modules.json
|
||||
.*/node_modules/binary-extensions/binary-extensions.json
|
||||
.*/node_modules/url-regex/tlds.json
|
||||
.*/node_modules/joi/.*\.json
|
||||
.*/node_modules/isemail/.*\.json
|
||||
.*/node_modules/tr46/.*\.json
|
||||
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
node_modules/react-native/Libraries/react-native/react-native-interface.js
|
||||
node_modules/react-native/flow
|
||||
flow/
|
||||
|
||||
[options]
|
||||
module.system=haste
|
||||
|
||||
esproposal.class_static_fields=enable
|
||||
esproposal.class_instance_fields=enable
|
||||
|
||||
experimental.strict_type_args=true
|
||||
|
||||
munge_underscores=true
|
||||
|
||||
module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub'
|
||||
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
|
||||
|
||||
suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
suppress_type=$FixMe
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-6]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-6]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
|
||||
[version]
|
||||
^0.26.0
|
||||
@@ -35,6 +35,7 @@ local.properties
|
||||
#
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# BUCK
|
||||
buck-out/
|
||||
@@ -43,3 +44,6 @@ android/app/libs
|
||||
android/keystores/debug.keystore
|
||||
|
||||
coverage
|
||||
|
||||
# generated by bob
|
||||
lib/
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
||||
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
example/node_modules
|
||||
|
||||
# generated by bob
|
||||
lib/
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"jsxBracketSameLine": true,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
[
|
||||
"@semantic-release/changelog",
|
||||
{
|
||||
"changelogFile": "CHANGELOG.md",
|
||||
"changelogTitle": "# Changelog"
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"assets": ["CHANGELOG.md", "package.json"]
|
||||
}
|
||||
],
|
||||
"@semantic-release/npm",
|
||||
"@semantic-release/github"
|
||||
]
|
||||
}
|
||||
@@ -1,408 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Animated,
|
||||
BackHandler,
|
||||
Easing,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
Image,
|
||||
NativeModules,
|
||||
TouchableOpacity,
|
||||
TouchableNativeFeedback,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
ScrollView,
|
||||
} from 'react-native';
|
||||
|
||||
type ActionSheetOptions = {
|
||||
options: Array<string>,
|
||||
icons: ?Array<number>,
|
||||
destructiveButtonIndex: ?number,
|
||||
cancelButtonIndex: ?number,
|
||||
textStyle: ?any,
|
||||
};
|
||||
|
||||
type ActionGroupProps = {
|
||||
options: Array<string>,
|
||||
icons: ?Array<number>,
|
||||
destructiveButtonIndex: ?number,
|
||||
onSelect: (i: number) => boolean,
|
||||
startIndex: number,
|
||||
length: number,
|
||||
textStyle: ?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)
|
||||
|
||||
class ActionGroup extends React.Component {
|
||||
props: ActionGroupProps;
|
||||
|
||||
static propTypes = {
|
||||
options: PropTypes.array.isRequired,
|
||||
icons: PropTypes.array,
|
||||
destructiveButtonIndex: PropTypes.number,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
startIndex: PropTypes.number.isRequired,
|
||||
length: PropTypes.number.isRequired,
|
||||
textStyle: Text.propTypes.style,
|
||||
};
|
||||
|
||||
render() {
|
||||
let {
|
||||
options,
|
||||
icons,
|
||||
destructiveButtonIndex,
|
||||
onSelect,
|
||||
startIndex,
|
||||
length,
|
||||
textStyle,
|
||||
} = this.props;
|
||||
|
||||
let optionViews = [];
|
||||
|
||||
let nativeFeedbackBackground = TouchableNativeFeedbackSafe.Ripple(
|
||||
'rgba(180, 180, 180, 1)',
|
||||
false
|
||||
);
|
||||
|
||||
for (let i = startIndex; i < startIndex + length; i++) {
|
||||
let color = '#212121';
|
||||
if (i === destructiveButtonIndex) {
|
||||
color = '#d32f2f';
|
||||
}
|
||||
|
||||
let iconElement = undefined;
|
||||
|
||||
if (icons && icons[i]) {
|
||||
const iconStyle = [styles.icon]
|
||||
if (textStyle.color !== undefined && textStyle.color !== null) {
|
||||
iconStyle.push({ tintColor: textStyle.color })
|
||||
}
|
||||
iconElement = <Image source={icons[i]} resizeMode="contain" style={iconStyle} />;
|
||||
}
|
||||
|
||||
optionViews.push(
|
||||
<TouchableNativeFeedbackSafe
|
||||
key={i}
|
||||
pressInDelay={0}
|
||||
background={nativeFeedbackBackground}
|
||||
onPress={() => onSelect(i)}
|
||||
style={styles.button}>
|
||||
{iconElement}
|
||||
<Text style={[styles.text, { color }, textStyle]}>{options[i]}</Text>
|
||||
</TouchableNativeFeedbackSafe>
|
||||
);
|
||||
|
||||
if (i < startIndex + length - 1) {
|
||||
optionViews.push(<View key={`separator-${i}`} style={styles.rowSeparator} />);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.groupContainer}>
|
||||
<ScrollView>{optionViews}</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 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),
|
||||
};
|
||||
|
||||
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: [
|
||||
{
|
||||
translateY: this.state.sheetOpacity.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [this._actionSheetHeight, 0],
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
]}>
|
||||
<View style={styles.sheet}
|
||||
onLayout={(event) => { this._actionSheetHeight = event.nativeEvent.layout.height }}>
|
||||
<ActionGroup
|
||||
options={this.state.options.options}
|
||||
icons={this.state.options.icons}
|
||||
destructiveButtonIndex={this.state.options.destructiveButtonIndex}
|
||||
onSelect={this._onSelect}
|
||||
startIndex={0}
|
||||
length={numOptions}
|
||||
textStyle={this.state.options.textStyle}
|
||||
/>
|
||||
</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.2,
|
||||
easing: EASING_OUT,
|
||||
duration: OPACITY_ANIMATION_IN_TIME,
|
||||
useNativeDriver: this.props.useNativeDriver,
|
||||
}),
|
||||
Animated.timing(this.state.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 = () => {
|
||||
if (!this.state.options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
BackHandler.removeEventListener(
|
||||
'actionSheetHardwareBackPress',
|
||||
this._selectCancelButton
|
||||
);
|
||||
|
||||
this.setState({
|
||||
isAnimating: true,
|
||||
});
|
||||
|
||||
Animated.parallel([
|
||||
Animated.timing(this.state.overlayOpacity, {
|
||||
toValue: 0,
|
||||
easing: EASING_IN,
|
||||
duration: OPACITY_ANIMATION_OUT_TIME,
|
||||
useNativeDriver: this.props.useNativeDriver,
|
||||
}),
|
||||
Animated.timing(this.state.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;
|
||||
|
||||
TouchableComponent = 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>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let styles = StyleSheet.create({
|
||||
groupContainer: {
|
||||
backgroundColor: '#fefefe',
|
||||
borderColor: '#ffffff',
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
overflow: 'hidden',
|
||||
paddingVertical: 8,
|
||||
},
|
||||
button: {
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
height: 48,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
icon: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
marginRight: 32,
|
||||
},
|
||||
text: {
|
||||
fontSize: 16,
|
||||
textAlignVertical: 'center',
|
||||
},
|
||||
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: 'flex-end',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
sheet: {
|
||||
flex: 1,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
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>;
|
||||
}
|
||||
|
||||
showActionSheetWithOptions(options, onSelect) {
|
||||
ActionSheetIOS.showActionSheetWithOptions(options, onSelect);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ActionSheet from './ActionSheet';
|
||||
|
||||
export default class ActionSheetProvider extends React.Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.any,
|
||||
};
|
||||
|
||||
static childContextTypes = {
|
||||
showActionSheetWithOptions: PropTypes.func,
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
showActionSheetWithOptions: (...args) =>
|
||||
this._actionSheetRef.showActionSheetWithOptions(...args),
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ActionSheet ref={component => (this._actionSheetRef = component)}>
|
||||
{React.Children.only(this.props.children)}
|
||||
</ActionSheet>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
# Changelog
|
||||
|
||||
# [3.6.0](https://github.com/expo/react-native-action-sheet/compare/v3.5.0...v3.6.0) (2020-03-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Android 'Super expression must either be null or a function' ([37156da](https://github.com/expo/react-native-action-sheet/commit/37156dacf114aa5146da153db9ee00c1aea8eaca))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add accessibility label to Action options ([#158](https://github.com/expo/react-native-action-sheet/issues/158)) ([bc3c27d](https://github.com/expo/react-native-action-sheet/commit/bc3c27d767a5657032b73f70aa1aacfea0481f66))
|
||||
|
||||
# [3.5.0](https://github.com/expo/react-native-action-sheet/compare/v3.4.1...v3.5.0) (2020-02-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add anchor option for iPads ([#155](https://github.com/expo/react-native-action-sheet/issues/155)) ([370e6e8](https://github.com/expo/react-native-action-sheet/commit/370e6e855fbacbfc683cc6ab16304f26c159e6b5))
|
||||
|
||||
## [3.4.1](https://github.com/expo/react-native-action-sheet/compare/v3.4.0...v3.4.1) (2019-12-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **iOS:** crash when using Android only options ([#144](https://github.com/expo/react-native-action-sheet/issues/144)) ([8016008](https://github.com/expo/react-native-action-sheet/commit/8016008781cbe418dc03f04a324d5e5ef5bcdaa7))
|
||||
|
||||
# [3.4.0](https://github.com/expo/react-native-action-sheet/compare/v3.3.2...v3.4.0) (2019-11-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **android:** allow customizing container styles ([#142](https://github.com/expo/react-native-action-sheet/issues/142)) ([39fc431](https://github.com/expo/react-native-action-sheet/commit/39fc431c97cb0ec03ab2a8386bd1b836d9f1cd2f))
|
||||
* Allow pointerEvents prop to pass through to containing view ([#124](https://github.com/expo/react-native-action-sheet/issues/124)) ([76397fa](https://github.com/expo/react-native-action-sheet/commit/76397fa7c4bff7318780ac72600156d753fad3f0))
|
||||
@@ -1,67 +1,195 @@
|
||||
# react-native-action-sheet [](https://slack.expo.io)
|
||||
# react-native-action-sheet
|
||||
[](https://www.npmjs.com/package/@expo/react-native-action-sheet)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](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.
|
||||
|
||||
|
||||
| iOS | Android | Web |
|
||||
|---------------------------|---------------------------|---------------------------|
|
||||
| <img src="https://raw.githubusercontent.com/expo/react-native-action-sheet/master/gif/ios.gif" width="200" height="400"/> | <img src="https://raw.githubusercontent.com/expo/react-native-action-sheet/master/gif/android.gif" width="200" height="400"/> | <img src="https://raw.githubusercontent.com/expo/react-native-action-sheet/master/gif/web.gif" width="400" height="400"/> |
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
npm install @expo/react-native-action-sheet
|
||||
$ npm install @expo/react-native-action-sheet -S
|
||||
```
|
||||
or
|
||||
```
|
||||
$ yarn add @expo/react-native-action-sheet
|
||||
```
|
||||
|
||||
## A basic ActionSheet Setup
|
||||
|
||||
import ActionSheetProvider & connectActionSheet
|
||||
### 1. Wrap your top-level component with `<ActionSheetProvider />`
|
||||
|
||||
```es6
|
||||
import { ActionSheetProvider, connectActionSheet } from '@expo/react-native-action-sheet';
|
||||
```
|
||||
|
||||
wrap your top-level component with `<ActionSheetProvider />`
|
||||
import { ActionSheetProvider } from '@expo/react-native-action-sheet'
|
||||
|
||||
```es6
|
||||
class AppContainer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ActionSheetProvider>
|
||||
<App />
|
||||
<ConnectedApp />
|
||||
</ActionSheetProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
decorate the component you want to use the action sheet with `@connectActionSheet`
|
||||
|
||||
### 2. Connect your component which uses showActionSheetWithOptions.
|
||||
```es6
|
||||
@connectActionSheet
|
||||
class App extends React.Component { /* ... */ }
|
||||
import { connectActionSheet } from '@expo/react-native-action-sheet'
|
||||
|
||||
class App extends React.Component {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
const ConnectedApp = connectActionSheet(App)
|
||||
|
||||
export default ConnectedApp
|
||||
```
|
||||
|
||||
access actionSheet method as `this.props.showActionSheetWithOptions`
|
||||
`App` component can access the actionSheet method as `this.props.showActionSheetWithOptions`
|
||||
|
||||
```es6
|
||||
_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.props.showActionSheetWithOptions({
|
||||
options,
|
||||
cancelButtonIndex,
|
||||
destructiveButtonIndex,
|
||||
},
|
||||
(buttonIndex) => {
|
||||
// Do something here depending on the button index selected
|
||||
});
|
||||
const options = ['Delete', 'Save', 'Cancel'];
|
||||
const destructiveButtonIndex = 0;
|
||||
const cancelButtonIndex = 2;
|
||||
|
||||
this.props.showActionSheetWithOptions(
|
||||
{
|
||||
options,
|
||||
cancelButtonIndex,
|
||||
destructiveButtonIndex,
|
||||
},
|
||||
buttonIndex => {
|
||||
// Do something here depending on the button index selected
|
||||
},
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
You can use a hook instead of the higher order component if you are on React 16.8 or newer.
|
||||
|
||||
```es6
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
|
||||
export default function App () {
|
||||
const { showActionSheetWithOptions } = useActionSheet();
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
## 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 in the browser with Expo for web.
|
||||
|
||||
### 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.
|
||||
|
||||
### iOS Only Props
|
||||
|
||||
| Name | Type | Required | Default |
|
||||
| -----------------| -------| -------- | ------- |
|
||||
| anchor | number | No | |
|
||||
|
||||
#### `anchor` (optional)
|
||||
iPad only option that allows for docking the action sheet to a node. See [ShowActionSheetButton.tsx](/example/ShowActionSheetButton.tsx) for an example on how to implement this.
|
||||
|
||||
### Android/Web-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.
|
||||
|
||||
|
||||
| Name | Type | Required | Default |
|
||||
| -----------------| ----------------------------------| -------- | ------- |
|
||||
| icons | array of required images or icons | No | |
|
||||
| tintIcons | boolean | No | true |
|
||||
| textStyle | TextStyle | No | |
|
||||
| titleTextStyle | TextStyle | No | |
|
||||
| messageTextStyle | TextStyle | No | |
|
||||
| showSeparators | boolean | No | false |
|
||||
| containerStyle | ViewStyle | No | |
|
||||
| separatorStyle | ViewStyle | No | |
|
||||
|
||||
#### `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` (optional)
|
||||
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` (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` (optional)
|
||||
Apply any text style props to the title if present.
|
||||
|
||||
#### `messageTextStyle` (optional)
|
||||
Apply any text style props to the message if present.
|
||||
|
||||
#### `showSeparators`: (optional)
|
||||
Show separators between items. On iOS, separators always show so this prop has no effect.
|
||||
|
||||
#### `containerStyle`: (optional)
|
||||
Apply any view style props to the container rather than use the default look (e.g. dark mode).
|
||||
|
||||
#### `separatorStyle`: (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
|
||||
## Example
|
||||
|
||||
[See the example app source](https://github.com/expo/react-native-action-sheet/tree/master/example)
|
||||
See the [example app](https://github.com/expo/react-native-action-sheet/tree/master/example).
|
||||
|
||||
### Usage
|
||||
```
|
||||
$ cd example
|
||||
$ yarn
|
||||
|
||||
// build simulator
|
||||
$ yarn ios
|
||||
$ yarn android
|
||||
|
||||
// web
|
||||
$ yarn web
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Setup
|
||||
```
|
||||
$ git clone [email protected]:expo/react-native-action-sheet.git
|
||||
$ cd react-native-action-sheet
|
||||
$ yarn
|
||||
```
|
||||
|
||||
### Build
|
||||
We use [bob](https://github.com/react-native-community/bob).
|
||||
```
|
||||
$ yarn build
|
||||
```
|
||||
|
||||
### Lint & Format
|
||||
```
|
||||
// tsc
|
||||
$ yarn type-check
|
||||
|
||||
// ESLint
|
||||
$ yarn lint
|
||||
|
||||
// prettier
|
||||
$ yarn fmt
|
||||
```
|
||||
@@ -1,20 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"presets": ["babel-preset-expo"],
|
||||
"env": {
|
||||
"development": {
|
||||
"plugins": ["transform-react-jsx-source"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import Expo from 'expo';
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { Entypo } from '@expo/vector-icons';
|
||||
import { ActionSheetProvider, connectActionSheet } from '@expo/react-native-action-sheet';
|
||||
|
||||
export default class AppContainer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ActionSheetProvider>
|
||||
<App />
|
||||
</ActionSheetProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@connectActionSheet
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={{ marginBottom: 30 }}>
|
||||
<Text style={{ textAlign: 'center' }}>
|
||||
Hello! This is a simple example app to demonstrate @expo/react-native-action-sheet.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Entypo.Button name="code" backgroundColor="#3e3e3e" onPress={this._onOpenActionSheet}>
|
||||
<Text style={{ fontSize: 15, color: '#fff' }}>Open action sheet</Text>
|
||||
</Entypo.Button>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_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.props.showActionSheetWithOptions(
|
||||
{
|
||||
options,
|
||||
cancelButtonIndex,
|
||||
destructiveButtonIndex,
|
||||
},
|
||||
buttonIndex => {
|
||||
// Do something here depending on the button index selected
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
});
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
import * as React from 'react';
|
||||
import { StyleSheet, Text, View, ScrollView, SafeAreaView } from 'react-native';
|
||||
import {
|
||||
ActionSheetProvider,
|
||||
connectActionSheet,
|
||||
ActionSheetOptions,
|
||||
ActionSheetProps,
|
||||
} from '@expo/react-native-action-sheet';
|
||||
import ShowActionSheetButton from './ShowActionSheetButton';
|
||||
|
||||
type Props = ActionSheetProps;
|
||||
|
||||
interface State {
|
||||
selectedIndex: number | null;
|
||||
}
|
||||
|
||||
class App extends React.Component<Props, State> {
|
||||
state: State = {
|
||||
selectedIndex: null,
|
||||
};
|
||||
|
||||
_updateSelectionText = (selectedIndex: number) => {
|
||||
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: string) => {
|
||||
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}
|
||||
/>
|
||||
<ShowActionSheetButton
|
||||
title="iPad Anchor"
|
||||
withAnchor
|
||||
withTitle
|
||||
onSelection={this._updateSelectionText}
|
||||
showActionSheetWithOptions={showActionSheetWithOptions}
|
||||
/>
|
||||
<ShowActionSheetButton
|
||||
title="Nested Action Sheets"
|
||||
onSelection={index => {
|
||||
if (index < 3) {
|
||||
showActionSheetWithOptions(
|
||||
{
|
||||
title: 'Sub Action Sheet',
|
||||
options: ['One', 'Two', 'Three', 'Done'],
|
||||
cancelButtonIndex: 3,
|
||||
},
|
||||
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 ConnectedApp = connectActionSheet<{}>(App);
|
||||
|
||||
export default class AppContainer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ActionSheetProvider>
|
||||
<ConnectedApp />
|
||||
</ActionSheetProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,144 @@
|
||||
import * as React from 'react';
|
||||
import { Text, View, TextStyle, ViewStyle, findNodeHandle, Button } from 'react-native';
|
||||
import { MaterialIcons, Entypo } from '@expo/vector-icons';
|
||||
import { ActionSheetOptions } from '@expo/react-native-action-sheet';
|
||||
|
||||
const icon = (name: string) => <MaterialIcons key={name} name={name} size={24} />;
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
showActionSheetWithOptions: (
|
||||
optons: ActionSheetOptions,
|
||||
callback: (buttonIndex: number) => void
|
||||
) => void;
|
||||
onSelection: (index: number) => void;
|
||||
withTitle?: boolean;
|
||||
withMessage?: boolean;
|
||||
withIcons?: boolean;
|
||||
withSeparators?: boolean;
|
||||
withCustomStyles?: boolean;
|
||||
withAnchor?: boolean;
|
||||
}
|
||||
|
||||
// A custom button that shows examples of different share sheet configurations
|
||||
export default class ShowActionSheetButton extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
withTitle: false,
|
||||
withMessage: false,
|
||||
withIcons: false,
|
||||
withSeparators: false,
|
||||
withCustomStyles: false,
|
||||
withAnchor: false,
|
||||
onSelection: null,
|
||||
};
|
||||
|
||||
_anchorRef = React.createRef<Button>();
|
||||
|
||||
_showActionSheet = () => {
|
||||
const {
|
||||
withAnchor,
|
||||
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')]
|
||||
: undefined;
|
||||
const title = withTitle ? 'Choose An Action' : undefined;
|
||||
const message = withMessage
|
||||
? 'This library tries to mimic the native share sheets as close as possible.'
|
||||
: undefined;
|
||||
const destructiveButtonIndex = 0;
|
||||
const cancelButtonIndex = 3;
|
||||
const textStyle: TextStyle | undefined = withCustomStyles
|
||||
? {
|
||||
fontSize: 20,
|
||||
fontWeight: '500',
|
||||
color: 'blue',
|
||||
}
|
||||
: undefined;
|
||||
const titleTextStyle: TextStyle | undefined = withCustomStyles
|
||||
? {
|
||||
fontSize: 24,
|
||||
textAlign: 'center',
|
||||
fontWeight: '700',
|
||||
color: 'orange',
|
||||
}
|
||||
: undefined;
|
||||
const messageTextStyle: TextStyle | undefined = withCustomStyles
|
||||
? {
|
||||
fontSize: 12,
|
||||
color: 'purple',
|
||||
textAlign: 'right',
|
||||
}
|
||||
: undefined;
|
||||
const containerStyle: ViewStyle | undefined = withCustomStyles
|
||||
? {
|
||||
backgroundColor: 'lightgrey',
|
||||
}
|
||||
: undefined;
|
||||
const anchor: number | null = this._anchorRef.current
|
||||
? findNodeHandle(this._anchorRef.current)
|
||||
: null;
|
||||
|
||||
showActionSheetWithOptions(
|
||||
{
|
||||
options,
|
||||
cancelButtonIndex,
|
||||
destructiveButtonIndex,
|
||||
title,
|
||||
message,
|
||||
icons,
|
||||
//iPad only
|
||||
anchor: withAnchor && anchor ? anchor : undefined,
|
||||
// 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,
|
||||
containerStyle,
|
||||
},
|
||||
(buttonIndex: number) => {
|
||||
// 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}
|
||||
ref={this._anchorRef}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 15,
|
||||
color: '#fff',
|
||||
}}>
|
||||
{title}
|
||||
</Text>
|
||||
</Entypo.Button>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
+8
-6
@@ -4,13 +4,15 @@
|
||||
"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",
|
||||
"sdkVersion": "35.0.0",
|
||||
"version": "3.0.0",
|
||||
"orientation": "portrait",
|
||||
"primaryColor": "#cccccc",
|
||||
"icon": "https://s3.amazonaws.com/exp-brand-assets/ExponentEmptyManifest_192.png",
|
||||
"packagerOpts": {
|
||||
"assetExts": ["ttf", "mp4"]
|
||||
}
|
||||
"platforms": [
|
||||
"android",
|
||||
"ios",
|
||||
"web"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = function(api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
};
|
||||
};
|
||||
+23
-7
@@ -1,14 +1,30 @@
|
||||
{
|
||||
"name": "example",
|
||||
"version": "0.0.0",
|
||||
"description": "Hello Expo!",
|
||||
"author": "[email protected]",
|
||||
"version": "3.0.0",
|
||||
"description": "Hello react native action sheet!!",
|
||||
"private": true,
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"ios": "expo start --ios",
|
||||
"android": "expo start --android",
|
||||
"web": "expo start --web",
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"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"
|
||||
"@expo/react-native-action-sheet": "../src",
|
||||
"@expo/vector-icons": "^10.0.0",
|
||||
"expo": "^35.0.0",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"react": "16.8.3",
|
||||
"react-dom": "16.8.3",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-35.0.0.tar.gz",
|
||||
"react-native-web": "^0.11.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^16.8.23",
|
||||
"@types/react-native": "^0.57.65",
|
||||
"babel-preset-expo": "^7.0.0",
|
||||
"typescript": "^3.5.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react-native",
|
||||
"lib": ["esnext"],
|
||||
"moduleResolution": "node",
|
||||
"noEmit": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "esnext",
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
},
|
||||
"include": ["./**.tsx"],
|
||||
}
|
||||
+3622
-2254
File diff suppressed because it is too large.
Load diff
Binary file not shown.
|
After Width: | Height: | Size: 8.7 MiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 4.5 MiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 7.4 MiB |
Vendored
-14
@@ -1,14 +0,0 @@
|
||||
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,4 +0,0 @@
|
||||
module.exports = require('./ActionSheet');
|
||||
|
||||
module.exports.ActionSheetProvider = require('./ActionSheetProvider').default;
|
||||
module.exports.connectActionSheet = require('./connectActionSheet').default;
|
||||
+61
-21
@@ -1,43 +1,83 @@
|
||||
{
|
||||
"name": "@expo/react-native-action-sheet",
|
||||
"version": "1.1.0",
|
||||
"version": "3.3.0",
|
||||
"description": "A cross-platform ActionSheet for React Native",
|
||||
"main": "index.js",
|
||||
"react-native": "lib/module/index.js",
|
||||
"types": "lib/typescript/index.d.ts",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"module": "lib/module/index.js",
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"flow": "flow; test $? -eq 0 -o $? -eq 2"
|
||||
"prepare": "bob build",
|
||||
"build": "bob build",
|
||||
"type-check": "tsc --noEmit",
|
||||
"lint": "eslint . --ext .ts,.tsx --fix",
|
||||
"fmt": "prettier --write \"{src,example}/**/*.{ts,tsx}\"",
|
||||
"semantic-release": "semantic-release"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/expo/react-native-action-sheet.git"
|
||||
},
|
||||
"keywords": [
|
||||
"expo",
|
||||
"react-native",
|
||||
"action-sheet"
|
||||
],
|
||||
"files": [
|
||||
"ActionSheet.android.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/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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^16.4.6",
|
||||
"@types/react-native": "^0.56.0",
|
||||
"flow-bin": "^0.26.0",
|
||||
"react": "^15.6.1",
|
||||
"react-native": "^0.28.0"
|
||||
"@react-native-community/bob": "^0.6.1",
|
||||
"@react-native-community/eslint-config": "^0.0.5",
|
||||
"@semantic-release/changelog": "^3.0.5",
|
||||
"@semantic-release/git": "^7.0.18",
|
||||
"@types/react": "^16.8.23",
|
||||
"@types/react-native": "^0.57.65",
|
||||
"@typescript-eslint/eslint-plugin": "^1.12.0",
|
||||
"@typescript-eslint/parser": "^1.12.0",
|
||||
"eslint": "^6.0.1",
|
||||
"eslint-config-prettier": "^6.0.0",
|
||||
"eslint-plugin-prettier": "3.1.0",
|
||||
"husky": "^3.0.0",
|
||||
"lint-staged": "^9.2.0",
|
||||
"prettier": "^1.18.2",
|
||||
"react": "^16.8.6",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-33.0.0.tar.gz",
|
||||
"semantic-release": "^15.13.28",
|
||||
"typescript": "^3.5.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.3.0"
|
||||
},
|
||||
"@react-native-community/bob": {
|
||||
"source": "src",
|
||||
"output": "lib",
|
||||
"targets": [
|
||||
"commonjs",
|
||||
"module",
|
||||
"typescript"
|
||||
]
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,tsx}": [
|
||||
"yarn lint",
|
||||
"yarn fmt",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import * as React from 'react';
|
||||
import { StyleSheet, Text, Image, View, ScrollView } from 'react-native';
|
||||
import TouchableNativeFeedbackSafe from './TouchableNativeFeedbackSafe';
|
||||
import { ActionSheetOptions } from '../types';
|
||||
|
||||
type Props = ActionSheetOptions & {
|
||||
tintIcons: boolean | null;
|
||||
onSelect: (i: number) => boolean;
|
||||
startIndex: number;
|
||||
length: number;
|
||||
};
|
||||
|
||||
const BLACK_54PC_TRANSPARENT = '#0000008a';
|
||||
const BLACK_87PC_TRANSPARENT = '#000000de';
|
||||
const DESTRUCTIVE_COLOR = '#d32f2f';
|
||||
|
||||
export default class ActionGroup extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
title: null,
|
||||
message: null,
|
||||
showSeparators: false,
|
||||
tintIcons: true,
|
||||
textStyle: {},
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={[styles.groupContainer, this.props.containerStyle]}>
|
||||
{this._renderTitleContent()}
|
||||
<ScrollView>{this._renderOptionViews()}</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_renderRowSeparator = (key: string | number) => {
|
||||
return (
|
||||
<View key={`separator-${key}`} 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>
|
||||
);
|
||||
};
|
||||
|
||||
_renderIconElement = (iconSource: React.ReactNode | null, color: string) => {
|
||||
const { tintIcons } = this.props;
|
||||
if (!iconSource) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof iconSource === 'number') {
|
||||
const iconStyle = [styles.icon, { tintColor: tintIcons ? color : undefined }];
|
||||
return <Image fadeDuration={0} source={iconSource} resizeMode="contain" style={iconStyle} />;
|
||||
} else {
|
||||
return <View style={styles.icon}>{iconSource}</View>;
|
||||
}
|
||||
};
|
||||
|
||||
_renderOptionViews = () => {
|
||||
const {
|
||||
options,
|
||||
icons,
|
||||
destructiveButtonIndex,
|
||||
onSelect,
|
||||
startIndex,
|
||||
length,
|
||||
textStyle,
|
||||
tintColor,
|
||||
showSeparators,
|
||||
} = this.props;
|
||||
const optionViews: React.ReactNode[] = [];
|
||||
const 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] : null;
|
||||
|
||||
optionViews.push(
|
||||
<TouchableNativeFeedbackSafe
|
||||
key={i}
|
||||
pressInDelay={0}
|
||||
background={nativeFeedbackBackground}
|
||||
onPress={() => onSelect(i)}
|
||||
style={styles.button}
|
||||
accessibilityLabel={options[i]}>
|
||||
{this._renderIconElement(iconSource, color)}
|
||||
<Text style={[styles.text, textStyle, { color }]}>{options[i]}</Text>
|
||||
</TouchableNativeFeedbackSafe>
|
||||
);
|
||||
|
||||
if (showSeparators && i < startIndex + length - 1) {
|
||||
optionViews.push(this._renderRowSeparator(i));
|
||||
}
|
||||
}
|
||||
|
||||
return optionViews;
|
||||
};
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
height: 56,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
groupContainer: {
|
||||
backgroundColor: '#ffffff',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
icon: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
marginRight: 32,
|
||||
},
|
||||
message: {
|
||||
marginTop: 12,
|
||||
fontSize: 14,
|
||||
color: BLACK_54PC_TRANSPARENT,
|
||||
textAlignVertical: 'center',
|
||||
},
|
||||
rowSeparator: {
|
||||
backgroundColor: '#dddddd',
|
||||
height: 1,
|
||||
width: '100%',
|
||||
},
|
||||
text: {
|
||||
fontSize: 16,
|
||||
color: BLACK_87PC_TRANSPARENT,
|
||||
textAlignVertical: 'center',
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
color: BLACK_54PC_TRANSPARENT,
|
||||
textAlignVertical: 'center',
|
||||
},
|
||||
titleContainer: {
|
||||
alignItems: 'flex-start',
|
||||
padding: 16,
|
||||
paddingTop: 24,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Platform,
|
||||
TouchableNativeFeedback,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
TouchableWithoutFeedbackProps,
|
||||
} from 'react-native';
|
||||
|
||||
// This TouchableOpacity has the same staic method of TouchableNativeFeedback
|
||||
class CustomTouchableOpacity extends React.Component {
|
||||
static SelectableBackground = () => ({});
|
||||
static SelectableBackgroundBorderless = () => ({});
|
||||
static Ripple = (color: string, borderless?: boolean) => ({});
|
||||
|
||||
render() {
|
||||
return <TouchableOpacity {...this.props}>{this.props.children}</TouchableOpacity>;
|
||||
}
|
||||
}
|
||||
|
||||
const TouchableComponent = Platform.select({
|
||||
web: CustomTouchableOpacity,
|
||||
default: Platform.Version <= 20 ? CustomTouchableOpacity : TouchableNativeFeedback,
|
||||
});
|
||||
|
||||
type Props = TouchableWithoutFeedbackProps & {
|
||||
pressInDelay: number;
|
||||
background: any;
|
||||
};
|
||||
|
||||
export default class TouchableNativeFeedbackSafe extends React.Component<Props> {
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
// @ts-ignore: JSX element type 'TouchableComponent' does not have any construct or call signatures
|
||||
return <TouchableComponent {...this.props}>{this.props.children}</TouchableComponent>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { ActionSheetIOS, View, ViewProps } from 'react-native';
|
||||
|
||||
import { ActionSheetIOSOptions } from '../types';
|
||||
|
||||
interface Props {
|
||||
readonly children: React.ReactNode;
|
||||
readonly pointerEvents?: ViewProps['pointerEvents'];
|
||||
}
|
||||
|
||||
type onSelect = (buttonIndex: number) => void;
|
||||
|
||||
export default class ActionSheet extends React.Component<Props> {
|
||||
render() {
|
||||
return (
|
||||
<View pointerEvents={this.props.pointerEvents} style={{ flex: 1 }}>
|
||||
{React.Children.only(this.props.children)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
showActionSheetWithOptions(dataOptions: ActionSheetIOSOptions, onSelect: onSelect) {
|
||||
// ...dataOptions include other keys which use in android and web, thats why `Android-Only options` Crash on IOS
|
||||
const { cancelButtonIndex, destructiveButtonIndex, options, tintColor } = dataOptions;
|
||||
const iosOptions: ActionSheetIOSOptions = {
|
||||
cancelButtonIndex,
|
||||
destructiveButtonIndex,
|
||||
options,
|
||||
tintColor,
|
||||
// A null title or message on iOS causes a crash
|
||||
title: dataOptions.title || undefined,
|
||||
message: dataOptions.message || undefined,
|
||||
anchor: dataOptions.anchor || undefined,
|
||||
};
|
||||
ActionSheetIOS.showActionSheetWithOptions(iosOptions, onSelect);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Animated,
|
||||
BackHandler,
|
||||
Easing,
|
||||
StyleSheet,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
ViewProps,
|
||||
} from 'react-native';
|
||||
import ActionGroup from './ActionGroup';
|
||||
import { ActionSheetOptions } from '../types';
|
||||
|
||||
interface State {
|
||||
isVisible: boolean;
|
||||
isAnimating: boolean;
|
||||
options: ActionSheetOptions | null;
|
||||
onSelect: ((i: number) => void) | null;
|
||||
overlayOpacity: any;
|
||||
sheetOpacity: any;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
readonly useNativeDriver: boolean | undefined;
|
||||
readonly pointerEvents?: ViewProps['pointerEvents'];
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Has same API as https://facebook.github.io/react-native/docs/actionsheetios.html
|
||||
export default class ActionSheet extends React.Component<Props, State> {
|
||||
static defaultProps = {
|
||||
useNativeDriver: true,
|
||||
};
|
||||
|
||||
_actionSheetHeight = 360;
|
||||
|
||||
state: State = {
|
||||
isVisible: false,
|
||||
isAnimating: false,
|
||||
options: null,
|
||||
onSelect: null,
|
||||
overlayOpacity: new Animated.Value(0),
|
||||
sheetOpacity: new Animated.Value(0),
|
||||
};
|
||||
|
||||
_deferNextShow?: () => void = undefined;
|
||||
|
||||
_setActionSheetHeight = ({ nativeEvent }: any) =>
|
||||
(this._actionSheetHeight = nativeEvent.layout.height);
|
||||
|
||||
render() {
|
||||
const { isVisible, overlayOpacity } = this.state;
|
||||
const overlay = isVisible ? (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.overlay,
|
||||
{
|
||||
opacity: overlayOpacity,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : null;
|
||||
return (
|
||||
<View
|
||||
pointerEvents={this.props.pointerEvents}
|
||||
style={{
|
||||
flex: 1,
|
||||
}}>
|
||||
{React.Children.only(this.props.children)}
|
||||
{overlay}
|
||||
{isVisible ? this._renderSheet() : null}
|
||||
</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,
|
||||
containerStyle,
|
||||
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 || undefined}
|
||||
titleTextStyle={titleTextStyle}
|
||||
message={message || undefined}
|
||||
messageTextStyle={messageTextStyle}
|
||||
showSeparators={showSeparators}
|
||||
containerStyle={containerStyle}
|
||||
separatorStyle={separatorStyle}
|
||||
/>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
}
|
||||
|
||||
showActionSheetWithOptions = (options: ActionSheetOptions, onSelect: (i: number) => void) => {
|
||||
const { isVisible, isAnimating, overlayOpacity, sheetOpacity } = this.state;
|
||||
|
||||
if (isVisible) {
|
||||
this._deferNextShow = this.showActionSheetWithOptions.bind(this, options, onSelect);
|
||||
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._deferNextShow = undefined;
|
||||
}
|
||||
});
|
||||
// @ts-ignore: Argument of type '"actionSheetHardwareBackPress"' is not assignable to parameter of type '"hardwareBackPress"'
|
||||
BackHandler.addEventListener('actionSheetHardwareBackPress', this._selectCancelButton);
|
||||
};
|
||||
|
||||
_selectCancelButton = () => {
|
||||
const { options } = this.state;
|
||||
|
||||
if (!options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof options.cancelButtonIndex === 'undefined') {
|
||||
return;
|
||||
} else 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;
|
||||
}
|
||||
|
||||
// @ts-ignore: Argument of type '"actionSheetHardwareBackPress"' is not assignable to parameter of type '"hardwareBackPress"'
|
||||
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 (this._deferNextShow) {
|
||||
this._deferNextShow();
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
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',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import * as React from 'react';
|
||||
import ActionSheet from './ActionSheet';
|
||||
import { Provider } from './context';
|
||||
import { ActionSheetOptions } from './types';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default class ActionSheetProvider extends React.Component<Props> {
|
||||
_actionSheetRef: React.RefObject<ActionSheet>;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this._actionSheetRef = React.createRef();
|
||||
}
|
||||
|
||||
getContext = () => {
|
||||
return {
|
||||
showActionSheetWithOptions: (options: ActionSheetOptions, callback: (i: number) => void) => {
|
||||
this._actionSheetRef.current !== null &&
|
||||
this._actionSheetRef.current.showActionSheetWithOptions(options, callback);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Provider value={this.getContext()}>
|
||||
<ActionSheet ref={this._actionSheetRef}>
|
||||
{React.Children.only(this.props.children)}
|
||||
</ActionSheet>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import * as React from 'react';
|
||||
import { Consumer } from './context';
|
||||
import hoistNonReactStatic from 'hoist-non-react-statics';
|
||||
import { ActionSheetProps } from './types';
|
||||
|
||||
export default function connectActionSheet<OwnProps = any>(
|
||||
WrappedComponent: React.ComponentType<OwnProps & ActionSheetProps>
|
||||
) {
|
||||
const ConnectedActionSheet = (props: OwnProps) => {
|
||||
return (
|
||||
<Consumer>
|
||||
{({ showActionSheetWithOptions }) => {
|
||||
return (
|
||||
<WrappedComponent {...props} showActionSheetWithOptions={showActionSheetWithOptions} />
|
||||
);
|
||||
}}
|
||||
</Consumer>
|
||||
);
|
||||
};
|
||||
|
||||
return hoistNonReactStatic(ConnectedActionSheet, WrappedComponent);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import { ActionSheetOptions, ActionSheetProps } from './types';
|
||||
|
||||
const context = React.createContext<ActionSheetProps>({
|
||||
showActionSheetWithOptions: (options: ActionSheetOptions, callback: (i: number) => void) => {},
|
||||
});
|
||||
|
||||
export function useActionSheet() {
|
||||
return React.useContext(context);
|
||||
}
|
||||
|
||||
const { Provider, Consumer } = context;
|
||||
|
||||
export { Provider, Consumer };
|
||||
@@ -0,0 +1,4 @@
|
||||
export { default as ActionSheetProvider } from './ActionSheetProvider';
|
||||
export { default as connectActionSheet } from './connectActionSheet';
|
||||
export { useActionSheet } from './context';
|
||||
export * from './types';
|
||||
@@ -0,0 +1,29 @@
|
||||
import * as React from 'react';
|
||||
import { TextStyle, ViewStyle } from 'react-native';
|
||||
|
||||
export interface ActionSheetProps {
|
||||
showActionSheetWithOptions: (options: ActionSheetOptions, callback: (i: number) => void) => void;
|
||||
}
|
||||
|
||||
// for iOS
|
||||
export interface ActionSheetIOSOptions {
|
||||
options: string[];
|
||||
title?: string;
|
||||
message?: string;
|
||||
tintColor?: string;
|
||||
cancelButtonIndex?: number;
|
||||
destructiveButtonIndex?: number;
|
||||
anchor?: number;
|
||||
}
|
||||
|
||||
// for Android or Web
|
||||
export interface ActionSheetOptions extends ActionSheetIOSOptions {
|
||||
icons?: React.ReactNode[];
|
||||
tintIcons?: boolean;
|
||||
textStyle?: TextStyle;
|
||||
titleTextStyle?: TextStyle;
|
||||
messageTextStyle?: TextStyle;
|
||||
showSeparators?: boolean;
|
||||
containerStyle?: ViewStyle;
|
||||
separatorStyle?: ViewStyle;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
"lib": ["es2015", "es2016", "esnext"],
|
||||
"jsx": "react-native",
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
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