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 | |
|---|---|---|---|
|
|
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 | ||
|
|
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
@@ -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
|
||||
@@ -0,0 +1,27 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
- name: Type check
|
||||
run: npm run type-check
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
- name: Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npx semantic-release
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
# 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
|
||||
yarn-error.log
|
||||
|
||||
# BUCK
|
||||
buck-out/
|
||||
\.buckd/
|
||||
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,9 @@
|
||||
{
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
"@semantic-release/changelog",
|
||||
"@semantic-release/npm",
|
||||
"@semantic-release/github"
|
||||
]
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
@@ -1,21 +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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
# Changelog
|
||||
@@ -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,182 @@
|
||||
# react-native-action-sheet [](http://slack.exponentjs.com)
|
||||
# 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 @exponent/react-native-action-sheet
|
||||
$ npm install @expo/react-native-action-sheet -S
|
||||
```
|
||||
or
|
||||
```
|
||||
$ yarn add @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.
|
||||
### 1. Connect your component which uses showActionSheetWithOptions.
|
||||
```es6
|
||||
import { connectActionSheet } from '@expo/react-native-action-sheet'
|
||||
|
||||
```js
|
||||
class MainApp extends React.Component {
|
||||
class App extends React.Component {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
static childContextTypes = {
|
||||
actionSheet: PropTypes.func,
|
||||
};
|
||||
const ConnectedApp = connectActionSheet(App)
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
actionSheet: () => this._actionSheetRef,
|
||||
};
|
||||
}
|
||||
export default ConnectedApp
|
||||
```
|
||||
|
||||
`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
|
||||
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();
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Wrap your top-level component with `<ActionSheetProvider />`
|
||||
|
||||
```es6
|
||||
|
||||
import { ActionSheetProvider } from '@expo/react-native-action-sheet'
|
||||
|
||||
class AppContainer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ActionSheet ref={component => this._actionSheetRef = component}>
|
||||
// Render the rest of your app here.
|
||||
</ActionSheet>
|
||||
<ActionSheetProvider>
|
||||
<ConnectedApp />
|
||||
</ActionSheetProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To open the action sheet:
|
||||
## Options
|
||||
|
||||
```js
|
||||
class OtherComponent extends React.Component {
|
||||
The goal of this library is to mimic the native iOS and Android ActionSheets as closely as possible.
|
||||
|
||||
static contextTypes = {
|
||||
actionSheet: PropTypes.func,
|
||||
};
|
||||
This library can also be used with on web with Expo.
|
||||
|
||||
_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({
|
||||
options,
|
||||
cancelButtonIndex,
|
||||
destructiveButtonIndex,
|
||||
},
|
||||
(buttonIndex) => {
|
||||
// Do something here
|
||||
});
|
||||
}
|
||||
}
|
||||
### 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/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 |
|
||||
| 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.
|
||||
|
||||
#### `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
|
||||
|
||||
## 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
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
node_modules/**/*
|
||||
.expo/*
|
||||
npm-debug.*
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
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="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,122 @@
|
||||
import * as React from 'react';
|
||||
import { Text, View, TextStyle } 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;
|
||||
}
|
||||
|
||||
// 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,
|
||||
onSelection: null,
|
||||
};
|
||||
|
||||
_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')]
|
||||
: 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;
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"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": "35.0.0",
|
||||
"version": "3.0.0",
|
||||
"orientation": "portrait",
|
||||
"primaryColor": "#cccccc",
|
||||
"icon": "https://s3.amazonaws.com/exp-brand-assets/ExponentEmptyManifest_192.png",
|
||||
"platforms": [
|
||||
"android",
|
||||
"ios",
|
||||
"web"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = function(api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "example",
|
||||
"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": "../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"],
|
||||
}
|
||||
+5353
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 |
+64
-10
@@ -1,28 +1,82 @@
|
||||
{
|
||||
"name": "@exponent/react-native-action-sheet",
|
||||
"version": "0.2.1",
|
||||
"name": "@expo/react-native-action-sheet",
|
||||
"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/exponentjs/react-native-action-sheet.git"
|
||||
"url": "https://github.com/expo/react-native-action-sheet.git"
|
||||
},
|
||||
"keywords": [
|
||||
"expo",
|
||||
"react-native",
|
||||
"action-sheet"
|
||||
],
|
||||
"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/exponentjs/react-native-action-sheet",
|
||||
"homepage": "https://github.com/expo/react-native-action-sheet",
|
||||
"devDependencies": {
|
||||
"flow-bin": "^0.26.0",
|
||||
"react": "^15.2.0",
|
||||
"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",
|
||||
"@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,162 @@
|
||||
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._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}>
|
||||
{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,44 @@
|
||||
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 TouchableOpacity {
|
||||
static SelectableBackground = () => ({});
|
||||
static SelectableBackgroundBorderless = () => ({});
|
||||
static Ripple = (color: string, borderless?: boolean) => ({});
|
||||
}
|
||||
|
||||
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,25 @@
|
||||
import * as React from 'react';
|
||||
import { ActionSheetIOS, View } from 'react-native';
|
||||
import { ActionSheetIOSOptions } from '../types';
|
||||
|
||||
interface Props {
|
||||
readonly children: React.ReactNode;
|
||||
}
|
||||
|
||||
type onSelect = (buttonIndex: number) => void;
|
||||
|
||||
export default class ActionSheet extends React.Component<Props> {
|
||||
render() {
|
||||
return <View style={{ flex: 1 }}>{React.Children.only(this.props.children)}</View>;
|
||||
}
|
||||
|
||||
showActionSheetWithOptions(options: ActionSheetIOSOptions, onSelect: onSelect) {
|
||||
const iosOptions: ActionSheetIOSOptions = {
|
||||
...options,
|
||||
// A null title or message on iOS causes a crash
|
||||
title: options.title || undefined,
|
||||
message: options.message || undefined,
|
||||
};
|
||||
ActionSheetIOS.showActionSheetWithOptions(iosOptions, onSelect);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Animated,
|
||||
BackHandler,
|
||||
Easing,
|
||||
StyleSheet,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
} 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;
|
||||
}
|
||||
|
||||
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
|
||||
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,
|
||||
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}
|
||||
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,27 @@
|
||||
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;
|
||||
}
|
||||
|
||||
// for Android or Web
|
||||
export interface ActionSheetOptions extends ActionSheetIOSOptions {
|
||||
icons?: React.ReactNode[];
|
||||
tintIcons?: boolean;
|
||||
textStyle?: TextStyle;
|
||||
titleTextStyle?: TextStyle;
|
||||
messageTextStyle?: TextStyle;
|
||||
showSeparators?: boolean;
|
||||
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"]
|
||||
}
|
||||
Reference in new issue
Block a user