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 | |
|---|---|---|---|
|
|
fc9cefa2c1 | ||
|
|
372dc80650 | ||
|
|
86fabab32b | ||
|
|
b75a00a233 | ||
|
|
1df8d22613 | ||
|
|
2dcd54c56f | ||
|
|
6244df4549 | ||
|
|
f46a5cfc47 | ||
|
|
9c6f5be31d | ||
|
|
0147d326f9 | ||
|
|
47a1711c3c | ||
|
|
758050480d | ||
|
|
e4f4246880 | ||
|
|
da6105e8a0 | ||
|
|
3822febbf8 | ||
|
|
8999650323 | ||
|
|
6e22c372d3 | ||
|
|
02b2db8947 | ||
|
|
880fe21224 | ||
|
|
df363ead72 | ||
|
|
9ea2d088e6 | ||
|
|
0f534dc1f5 | ||
|
|
55005ba0fb | ||
|
|
bd0c001215 | ||
|
|
c4c3f8b873 | ||
|
|
1fb3411f57 | ||
|
|
2f43b7e956 | ||
|
|
feed1f78b4 | ||
|
|
a561f34cda | ||
|
|
c6cc00fdc5 | ||
|
|
c237838c63 | ||
|
|
d735b696ab | ||
|
|
7736e0e162 | ||
|
|
b54b9f16c2 | ||
|
|
4d8ed00b66 | ||
|
|
22fc2144b3 | ||
|
|
224d992a53 | ||
|
|
688fd47227 | ||
|
|
e2e141bf53 | ||
|
|
be3425a4fd | ||
|
|
7a388210b1 | ||
|
|
d3fe451642 | ||
|
|
682e46136f | ||
|
|
0e5559fb45 | ||
|
|
f9ac44a6cb | ||
|
|
d31681dfea | ||
|
|
8a1659d71d | ||
|
|
fd731f0867 | ||
|
|
4dc8fcf7b2 | ||
|
|
81112b9553 | ||
|
|
f0b38daaac | ||
|
|
bb7c00fb72 | ||
|
|
7c757ac7e5 | ||
|
|
51cd2d1009 | ||
|
|
97505a2041 | ||
|
|
0a5821e1a3 | ||
|
|
c465495515 | ||
|
|
51efe1c0e5 | ||
|
|
b1cbd92a2a | ||
|
|
00a3e8354a | ||
|
|
af226d6aa0 | ||
|
|
f9a8e47b49 | ||
|
|
4af170533f | ||
|
|
735cfa54ba | ||
|
|
1af3809e07 | ||
|
|
d76a61f593 | ||
|
|
3ef49be264 | ||
|
|
18e63e275d | ||
|
|
d48c323e45 | ||
|
|
d0d5182780 | ||
|
|
eb30b1ed14 | ||
|
|
f509356bdd | ||
|
|
86007a32a0 | ||
|
|
76da5f5fe5 | ||
|
|
e1bd0b0415 | ||
|
|
d0cf2b8c8d | ||
|
|
5549e36a92 | ||
|
|
b605fb8a3b | ||
|
|
b95117a8cd | ||
|
|
9ed955dbba | ||
|
|
d85c167b55 | ||
|
|
128a48f2ff | ||
|
|
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 | ||
|
|
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,4 @@
|
||||
node_modules
|
||||
|
||||
# generated by bob
|
||||
lib/
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: ['universe/native', 'universe/web'],
|
||||
};
|
||||
-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,22 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
cache: 'yarn'
|
||||
- run: yarn install
|
||||
- run: npm run type-check
|
||||
- run: npm run lint-check
|
||||
@@ -0,0 +1,23 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
cache: 'yarn'
|
||||
- run: yarn install
|
||||
- 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,4 @@
|
||||
node_modules
|
||||
|
||||
# generated by bob
|
||||
lib/
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"bracketSameLine": true,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
[
|
||||
"@semantic-release/changelog",
|
||||
{
|
||||
"changelogFile": "CHANGELOG.md",
|
||||
"changelogTitle": "# Changelog"
|
||||
}
|
||||
],
|
||||
"@semantic-release/npm",
|
||||
[
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"assets": [
|
||||
"CHANGELOG.md",
|
||||
"package.json"
|
||||
]
|
||||
}
|
||||
],
|
||||
"@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);
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
# Changelog
|
||||
|
||||
# [4.0.0](https://github.com/expo/react-native-action-sheet/compare/v3.14.0...v4.0.0) (2022-10-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* iOS use custom action sheet ([#281](https://github.com/expo/react-native-action-sheet/issues/281)) ([372dc80](https://github.com/expo/react-native-action-sheet/commit/372dc8065006a4da510ef5f31c27225d14a71ff3))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This implementation is backwards compatible, but just to be safe I'm marking this as breaking so that it bumps the major version.
|
||||
|
||||
# [3.14.0](https://github.com/expo/react-native-action-sheet/compare/v3.13.1...v3.14.0) (2022-10-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support cancelButtonTintColor ([#278](https://github.com/expo/react-native-action-sheet/issues/278)) ([2dcd54c](https://github.com/expo/react-native-action-sheet/commit/2dcd54c56f35a64c66f8d9a7d09a9475801cf037))
|
||||
|
||||
## [3.13.1](https://github.com/expo/react-native-action-sheet/compare/v3.13.0...v3.13.1) (2022-10-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add return type to connectActionSheet ([#273](https://github.com/expo/react-native-action-sheet/issues/273)) ([f46a5cf](https://github.com/expo/react-native-action-sheet/commit/f46a5cfc473807478c558a733c8adb1440941017))
|
||||
|
||||
# [3.13.0](https://github.com/expo/react-native-action-sheet/compare/v3.12.0...v3.13.0) (2022-01-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* defer calling onSelect until after animation ([#248](https://github.com/expo/react-native-action-sheet/issues/248)) ([1fb3411](https://github.com/expo/react-native-action-sheet/commit/1fb3411f575b4fa1cb762a804fc38cf3e2a5c73b)), closes [#203](https://github.com/expo/react-native-action-sheet/issues/203)
|
||||
* stabilize the identity of the context provider value ([#251](https://github.com/expo/react-native-action-sheet/issues/251)) ([55005ba](https://github.com/expo/react-native-action-sheet/commit/55005ba0fb4bc6aff546390cf324619bf2e0ed2a))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support closing actionsheet with Esc key on web ([#247](https://github.com/expo/react-native-action-sheet/issues/247)) ([c4c3f8b](https://github.com/expo/react-native-action-sheet/commit/c4c3f8b873d76ff40daf8d2061d9f43346a3577f)), closes [#194](https://github.com/expo/react-native-action-sheet/issues/194)
|
||||
|
||||
# [3.12.0](https://github.com/expo/react-native-action-sheet/compare/v3.11.0...v3.12.0) (2021-10-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use correct callback type allowing for undefined `i` ([#239](https://github.com/expo/react-native-action-sheet/issues/239)) ([b54b9f1](https://github.com/expo/react-native-action-sheet/commit/b54b9f16c251cb2ca5f1bd41ca7cc41b1393c6b3))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* adds support for disabledButtonIndices ([#246](https://github.com/expo/react-native-action-sheet/issues/246)) ([feed1f7](https://github.com/expo/react-native-action-sheet/commit/feed1f78b4c85855d48c1742fe9f8a67c0604609)), closes [#224](https://github.com/expo/react-native-action-sheet/issues/224)
|
||||
|
||||
# [3.11.0](https://github.com/expo/react-native-action-sheet/compare/v3.10.0...v3.11.0) (2021-09-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* yarn lock issues ([#238](https://github.com/expo/react-native-action-sheet/issues/238)) ([22fc214](https://github.com/expo/react-native-action-sheet/commit/22fc2144b3ce3b5fc1892eefd86f484dc1af58e9))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* default useModal to true when autoFocus is set ([688fd47](https://github.com/expo/react-native-action-sheet/commit/688fd47227dab6994b59ed85af622cb04ebed220))
|
||||
|
||||
# [3.10.0](https://github.com/expo/react-native-action-sheet/compare/v3.8.0...v3.10.0) (2021-08-09)
|
||||
|
||||
### Features
|
||||
* Allow multiple destructive indices ([#210](https://github.com/expo/react-native-action-sheet/issues/210)) ([81112b9](https://github.com/expo/react-native-action-sheet/commit/81112b9553dc34725e2708b02b64b7a5e5c57509))
|
||||
|
||||
# [3.9.0](https://github.com/expo/react-native-action-sheet/compare/v3.8.0...v3.9.0) (2021-03-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow callback to be async ([#183](https://github.com/expo/react-native-action-sheet/issues/183)) ([d0d5182](https://github.com/expo/react-native-action-sheet/commit/d0d518278028556a80deec3d9b57c332b82ba28d))
|
||||
* center icons with titles ([#188](https://github.com/expo/react-native-action-sheet/issues/188)) ([d48c323](https://github.com/expo/react-native-action-sheet/commit/d48c323e4563363c8683c427d1803e1ff4cddcb5))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add `userInterfaceStyle` option for iOS ([#206](https://github.com/expo/react-native-action-sheet/issues/206)) ([735cfa5](https://github.com/expo/react-native-action-sheet/commit/735cfa54ba1bfd90139671489aca525fdbd59626))
|
||||
|
||||
# [3.8.0](https://github.com/expo/react-native-action-sheet/compare/v3.7.0...v3.8.0) (2020-05-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add destructiveColor option ([#171](https://github.com/expo/react-native-action-sheet/issues/171)) ([5549e36](https://github.com/expo/react-native-action-sheet/commit/5549e36a92d8afcc0ce2bf6a601e83209b5ce080))
|
||||
* Improvements to Android accessibility ([#168](https://github.com/expo/react-native-action-sheet/issues/168)) ([b605fb8](https://github.com/expo/react-native-action-sheet/commit/b605fb8a3b6421c244f7f18d934bb5ea0753d461))
|
||||
|
||||
# [3.7.0](https://github.com/expo/react-native-action-sheet/compare/v3.6.0...v3.7.0) (2020-05-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* adds `useModal` prop wrap action sheet in a modal ([9ed955d](https://github.com/expo/react-native-action-sheet/commit/9ed955dbba7259cbcf5e31ef3d4ed091ae4a3145)), closes [#169](https://github.com/expo/react-native-action-sheet/issues/169)
|
||||
|
||||
# [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,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,221 @@
|
||||
# react-native-action-sheet [](http://slack.exponentjs.com)
|
||||
# @expo/react-native-action-sheet
|
||||
|
||||
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.
|
||||
[](https://www.npmjs.com/package/@expo/react-native-action-sheet)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://discord.gg/4gtbPAdpaE)
|
||||
|
||||
React Native Action Sheet is a cross-platform React Native component that uses the native UIActionSheet on iOS and a pure JS implementation on Android.
|
||||
|
||||
| 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"/> |
|
||||
|
||||
## [Check out the example snack here!](https://snack.expo.dev/@expo-action-sheet/example)
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
npm install @exponent/react-native-action-sheet
|
||||
npm install @expo/react-native-action-sheet
|
||||
```
|
||||
|
||||
## Usage
|
||||
or
|
||||
|
||||
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.
|
||||
```
|
||||
yarn add @expo/react-native-action-sheet
|
||||
```
|
||||
|
||||
```js
|
||||
class MainApp extends React.Component {
|
||||
## A basic ActionSheet Setup
|
||||
|
||||
static childContextTypes = {
|
||||
actionSheet: PropTypes.func,
|
||||
};
|
||||
### 1. Wrap your top-level component with `<ActionSheetProvider />`
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
actionSheet: () => this._actionSheetRef,
|
||||
};
|
||||
}
|
||||
ReactNativeActionSheet uses React context to allow your components to invoke the menu. This means your app needs to be wrapped with the `ActionSheetProvider` component first.
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ActionSheet ref={component => this._actionSheetRef = component}>
|
||||
// Render the rest of your app here.
|
||||
</ActionSheet>
|
||||
);
|
||||
}
|
||||
```jsx
|
||||
import { ActionSheetProvider } from '@expo/react-native-action-sheet';
|
||||
|
||||
export default function AppContainer() {
|
||||
return (
|
||||
<ActionSheetProvider>
|
||||
<App />
|
||||
</ActionSheetProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
To open the action sheet:
|
||||
### 2. Call the `showActionSheetWithOptions` method with a hook or a higher order component.
|
||||
|
||||
```js
|
||||
class OtherComponent extends React.Component {
|
||||
```tsx
|
||||
// Using the provided hook
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet';
|
||||
|
||||
static contextTypes = {
|
||||
actionSheet: PropTypes.func,
|
||||
};
|
||||
export default Menu() {
|
||||
const { showActionSheetWithOptions } = useActionSheet();
|
||||
|
||||
_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({
|
||||
const onPress() => {
|
||||
const options = ['Delete', 'Save', 'Cancel'];
|
||||
const destructiveButtonIndex = 0;
|
||||
const cancelButtonIndex = 2;
|
||||
|
||||
showActionSheetWithOptions({
|
||||
options,
|
||||
cancelButtonIndex,
|
||||
destructiveButtonIndex,
|
||||
},
|
||||
(buttonIndex) => {
|
||||
// Do something here
|
||||
});
|
||||
destructiveButtonIndex
|
||||
}, (selectedIndex: number) => {
|
||||
switch (selectedIndex) {
|
||||
case 1:
|
||||
// Save
|
||||
break;
|
||||
|
||||
case destructiveButtonIndex:
|
||||
// Delete
|
||||
break;
|
||||
|
||||
case cancelButtonIndex:
|
||||
// Canceled
|
||||
}});
|
||||
}
|
||||
|
||||
return (
|
||||
<Button title="Menu" onPress={onPress}/>
|
||||
)
|
||||
};
|
||||
```
|
||||
|
||||
Alternatively, any component can use the higher order component to access the context and pass the `showActionSheetWithOptions` as a prop.
|
||||
|
||||
```tsx
|
||||
// Using a Higher Order Component to wrap your component
|
||||
import { connectActionSheet } from '@expo/react-native-action-sheet';
|
||||
|
||||
function Menu({ showActionSheetWithOptions }) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
export default connectActionSheet(Menu);
|
||||
```
|
||||
|
||||
`Menu` component can now access the actionSheet prop as `showActionSheetWithOptions`.
|
||||
|
||||
## 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
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------------------------ | -------------------------- | ------------------------------------------------------------- |
|
||||
| `options` | array of strings | A list of button titles **(required)** |
|
||||
| `cancelButtonIndex` | number | Index of cancel button in options |
|
||||
| `cancelButtonTintColor` | string | Color used for the change the text color of the cancel button |
|
||||
| `destructiveButtonIndex` | number or array of numbers | Indices of destructive buttons in options |
|
||||
| `title` | string | Title to show above the action sheet |
|
||||
| `message` | string | Message to show below the title |
|
||||
| `tintColor` | string | Color used for non-destructive button titles |
|
||||
| `disabledButtonIndices` | array of numbers | Indices of disabled buttons in options |
|
||||
|
||||
### iOS Only Props
|
||||
|
||||
| Name | Type | Description |
|
||||
| -------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `anchor` | number | 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. |
|
||||
| `userInterfaceStyle` | string | The interface style used for the action sheet, can be set to `light` or `dark`, otherwise the default system style will be used. |
|
||||
|
||||
### Custom Action Sheet Only (Android/Web) 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 | Description |
|
||||
| ------------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `icons` | array of required images or icons | Show icons to go along with each option. If image source paths are provided via `require`, images will be rendered for you. Alternatively, you can provide an array of elements such as vector icons, pre-rendered Images, etc. |
|
||||
| `tintIcons` | boolean | 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` | TextStyle | Apply any text style props to the options. If the `tintColor` option is provided, it takes precedence over a color text style prop. |
|
||||
| `titleTextStyle` | TextStyle | Apply any text style props to the title if present. |
|
||||
| `messageTextStyle` | TextStyle | Apply any text style props to the message if present. |
|
||||
| `autoFocus` | boolean | If `true`, this will give the first option screen reader focus automatically when the action sheet becomes visible. On iOS, this is the default behavior of the native action sheet. |
|
||||
| `showSeparators` | boolean | Show separators between items. On iOS, separators always show so this prop has no effect. |
|
||||
| `containerStyle` | ViewStyle | Apply any view style props to the container rather than use the default look (e.g. dark mode). |
|
||||
| `separatorStyle` | ViewStyle | Modify the look of the separators rather than use the default look. |
|
||||
| `useModal` | boolean | Defaults to `false` (`true` if autoFocus is also `true`) Wraps the ActionSheet with a Modal, in order to show in front of other Modals that were already opened ([issue reference](https://github.com/expo/react-native-action-sheet/issues/164)). |
|
||||
| `destructiveColor` | string | Modify color for text of destructive option. Defaults to `#d32f2f`. |
|
||||
|
||||
## ActionSheetProvider Props
|
||||
|
||||
The following props can be set directly on the `ActionSheetProvider`
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `useCustomActionSheet` | boolean | iOS only prop that uses the custom pure JS action sheet (Android/Web version) instead of the native ActionSheetIOS component. Defaults to `false`. |
|
||||
| `useNativeDriver` | boolean | Windows only option that provides the option to disable the [native animation](https://reactnative.dev/docs/animated#using-the-native-driver) driver for React Native Windows projects targeting _Windows 10 Version-1809 ; Build-10.0.17763.0_ and earlier. `useNativeDriver` is [supported in Version-1903 and later](https://microsoft.github.io/react-native-windows/docs/win10-compat) so if your project is targeting that, you don't need to set this prop. |
|
||||
|
||||
```jsx
|
||||
// example of using useCustomActionSheet on iOS
|
||||
export default function AppContainer() {
|
||||
return (
|
||||
<ActionSheetProvider useCustomActionSheet={true}>
|
||||
<App />
|
||||
</ActionSheetProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Callback
|
||||
|
||||
The second parameter of the `showActionSheetWithOptions` function is a callback for when a button is selected. The callback takes a single argument which will be the zero-based index of the pressed option. You can check the value against your `cancelButtonIndex` to determine if the action was cancelled or not.
|
||||
|
||||
```tsx
|
||||
function onButtonPress(selectedIndex: number) {
|
||||
// handle it!
|
||||
}
|
||||
```
|
||||
|
||||
## Try it out
|
||||
|
||||
Try it in Expo Snack: https://snack.expo.dev/@expo-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 + Prettier
|
||||
$ yarn lint
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
node_modules/**/*
|
||||
.expo/*
|
||||
npm-debug.*
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
import {
|
||||
ActionSheetProvider,
|
||||
connectActionSheet,
|
||||
ActionSheetProps,
|
||||
} from '@expo/react-native-action-sheet';
|
||||
import { Entypo } from '@expo/vector-icons';
|
||||
import * as React from 'react';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Modal,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
ScrollView,
|
||||
SafeAreaView,
|
||||
TouchableOpacity,
|
||||
Share,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
|
||||
import ShowActionSheetButton from './ShowActionSheetButton';
|
||||
|
||||
type Props = ActionSheetProps & {
|
||||
useCustomActionSheet: boolean;
|
||||
setUseCustomActionSheet: (next: boolean) => void;
|
||||
};
|
||||
|
||||
interface State {
|
||||
selectedIndex?: number | null;
|
||||
isModalOpen: boolean;
|
||||
}
|
||||
|
||||
class App extends React.Component<Props, State> {
|
||||
state: State = {
|
||||
selectedIndex: null,
|
||||
isModalOpen: false,
|
||||
};
|
||||
|
||||
_updateSelectionText = (selectedIndex?: number) => {
|
||||
this.setState({
|
||||
selectedIndex,
|
||||
});
|
||||
};
|
||||
|
||||
_renderSelectionText = () => {
|
||||
const { selectedIndex } = this.state;
|
||||
const text =
|
||||
selectedIndex === null || selectedIndex === undefined
|
||||
? 'No Option Selected'
|
||||
: `Option #${selectedIndex + 1} Selected`;
|
||||
return <Text style={styles.selectionText}>{text}</Text>;
|
||||
};
|
||||
|
||||
_renderSectionHeader = (text: string) => {
|
||||
return <Text style={styles.sectionHeaderText}>{text}</Text>;
|
||||
};
|
||||
|
||||
_toggleModal = () => {
|
||||
this.setState((prevState) => ({ isModalOpen: !prevState.isModalOpen }));
|
||||
};
|
||||
|
||||
async _onShare() {
|
||||
try {
|
||||
const result = await Share.share({
|
||||
message: 'React Native | A framework for building native apps using React',
|
||||
});
|
||||
if (result.action === Share.sharedAction) {
|
||||
if (result.activityType) {
|
||||
// shared with activity type of result.activityType
|
||||
} else {
|
||||
// shared
|
||||
}
|
||||
} else if (result.action === Share.dismissedAction) {
|
||||
// dismissed
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
_renderButtons() {
|
||||
const { showActionSheetWithOptions } = this.props;
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<View
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
display: Platform.OS === 'ios' ? 'flex' : 'none',
|
||||
}}>
|
||||
{this._renderSectionHeader('Use Custom Action Sheet')}
|
||||
<Text style={{ marginBottom: 10 }}>
|
||||
On iOS the default action sheet will be the native UI. However, you can optionally
|
||||
enable the custom JS action sheet by setting the useCustomActionSheet prop on the
|
||||
provider.
|
||||
</Text>
|
||||
<Entypo.Button
|
||||
backgroundColor={this.props.useCustomActionSheet ? '#3e3e3e' : 'white'}
|
||||
name={this.props.useCustomActionSheet ? 'check' : 'circle'}
|
||||
color={this.props.useCustomActionSheet ? '#fff' : '#3e3e3e'}
|
||||
style={{
|
||||
borderColor: '#3e3e3e',
|
||||
borderWidth: 2,
|
||||
}}
|
||||
onPress={() => this.props.setUseCustomActionSheet(!this.props.useCustomActionSheet)}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 15,
|
||||
color: this.props.useCustomActionSheet ? '#fff' : '#3e3e3e',
|
||||
}}>
|
||||
Use Custom Action Sheet
|
||||
</Text>
|
||||
</Entypo.Button>
|
||||
</View>
|
||||
{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="Cancel Button Tint Color"
|
||||
withCancelButtonTintColor
|
||||
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 || index < 3) {
|
||||
showActionSheetWithOptions(
|
||||
{
|
||||
title: 'Sub Action Sheet',
|
||||
options: ['One', 'Two', 'Three', 'Done'],
|
||||
cancelButtonIndex: 3,
|
||||
},
|
||||
this._updateSelectionText
|
||||
);
|
||||
}
|
||||
}}
|
||||
showActionSheetWithOptions={showActionSheetWithOptions}
|
||||
/>
|
||||
<ShowActionSheetButton
|
||||
title="Share Menu"
|
||||
showActionSheetWithOptions={() =>
|
||||
showActionSheetWithOptions(
|
||||
{
|
||||
title: 'Share Menu',
|
||||
options: ['Share', 'Cancel'],
|
||||
cancelButtonIndex: 1,
|
||||
},
|
||||
(i) => {
|
||||
i === 0 && this._onShare();
|
||||
}
|
||||
)
|
||||
}
|
||||
/>
|
||||
{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}
|
||||
/>
|
||||
{this._renderSectionHeader('Special Cases')}
|
||||
<TouchableOpacity onPress={this._toggleModal}>
|
||||
<Text style={styles.link}>Open Modal</Text>
|
||||
</TouchableOpacity>
|
||||
{this.state.isModalOpen && (
|
||||
<Modal>
|
||||
<View style={{ flex: 1, padding: 30 }}>
|
||||
<ShowActionSheetButton
|
||||
useModal
|
||||
title="Options Only"
|
||||
onSelection={this._updateSelectionText}
|
||||
showActionSheetWithOptions={showActionSheetWithOptions}
|
||||
/>
|
||||
|
||||
<TouchableOpacity onPress={this._toggleModal}>
|
||||
<Text style={styles.link}>Close Modal</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</Modal>
|
||||
)}
|
||||
</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<any>(App);
|
||||
|
||||
export default function WrappedApp() {
|
||||
const [useCustomActionSheet, setUseCustomActionSheet] = useState(false);
|
||||
|
||||
return (
|
||||
<ActionSheetProvider useCustomActionSheet={useCustomActionSheet}>
|
||||
<ConnectedApp
|
||||
useCustomActionSheet={useCustomActionSheet}
|
||||
setUseCustomActionSheet={setUseCustomActionSheet}
|
||||
/>
|
||||
</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,
|
||||
},
|
||||
link: {
|
||||
fontSize: 15,
|
||||
textDecorationLine: 'underline',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,157 @@
|
||||
import { ActionSheetOptions } from '@expo/react-native-action-sheet';
|
||||
import { MaterialIcons, Entypo } from '@expo/vector-icons';
|
||||
import * as React from 'react';
|
||||
import { Text, View, TextStyle, ViewStyle, findNodeHandle } from 'react-native';
|
||||
|
||||
const icon = (name: React.ComponentProps<typeof MaterialIcons>['name']) => (
|
||||
<MaterialIcons key={name} name={name} size={24} />
|
||||
);
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
showActionSheetWithOptions: (
|
||||
options: ActionSheetOptions,
|
||||
callback: (buttonIndex?: number) => void
|
||||
) => void;
|
||||
onSelection: (index?: number) => void;
|
||||
withTitle?: boolean;
|
||||
withMessage?: boolean;
|
||||
withIcons?: boolean;
|
||||
withSeparators?: boolean;
|
||||
withCustomStyles?: boolean;
|
||||
withCancelButtonTintColor?: boolean;
|
||||
withAnchor?: boolean;
|
||||
useModal?: 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,
|
||||
withCancelButtonTintColor: false,
|
||||
onSelection: null,
|
||||
useModal: false,
|
||||
};
|
||||
|
||||
_anchorRef = React.createRef<any>();
|
||||
|
||||
_showActionSheet = () => {
|
||||
const {
|
||||
withAnchor,
|
||||
withTitle,
|
||||
withMessage,
|
||||
withIcons,
|
||||
withSeparators,
|
||||
withCustomStyles,
|
||||
withCancelButtonTintColor,
|
||||
onSelection,
|
||||
showActionSheetWithOptions,
|
||||
useModal,
|
||||
} = this.props;
|
||||
|
||||
// Same interface as https://facebook.github.io/react-native/docs/actionsheetios.html
|
||||
const options = ['Delete', 'Disabled', 'Save', '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 disabledButtonIndices = [1];
|
||||
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,
|
||||
cancelButtonTintColor: withCancelButtonTintColor ? '#D93F0B' : undefined,
|
||||
destructiveButtonIndex,
|
||||
disabledButtonIndices,
|
||||
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,
|
||||
// Android only,
|
||||
useModal,
|
||||
},
|
||||
(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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"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",
|
||||
"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": "^13.0.0",
|
||||
"expo": "^46.0.0",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"react": "18.0.0",
|
||||
"react-dom": "18.0.0",
|
||||
"react-native": "0.69.6",
|
||||
"react-native-web": "~0.18.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "~18.0.0",
|
||||
"@types/react-native": "~0.69.1",
|
||||
"babel-preset-expo": "~9.2.0",
|
||||
"typescript": "^4.6.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"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"
|
||||
],
|
||||
"extends": "expo/tsconfig.base"
|
||||
}
|
||||
+8047
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 |
+60
-10
@@ -1,28 +1,78 @@
|
||||
{
|
||||
"name": "@exponent/react-native-action-sheet",
|
||||
"version": "0.2.1",
|
||||
"name": "@expo/react-native-action-sheet",
|
||||
"version": "4.0.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 . --fix",
|
||||
"lint-check": "eslint",
|
||||
"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",
|
||||
"@semantic-release/changelog": "^5.0.1",
|
||||
"@semantic-release/git": "^9.0.0",
|
||||
"@types/react": "^16.8.23",
|
||||
"@types/react-native": "^0.57.65",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-universe": "^8.0.0",
|
||||
"husky": "^3.0.0",
|
||||
"lint-staged": "^9.2.0",
|
||||
"prettier": "^2.3.2",
|
||||
"react": "^16.8.6",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-33.0.0.tar.gz",
|
||||
"semantic-release": "^19.0.3",
|
||||
"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",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
AccessibilityInfo,
|
||||
findNodeHandle,
|
||||
Image,
|
||||
Platform,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
UIManager,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import { ActionSheetOptions } from '../types';
|
||||
import TouchableNativeFeedbackSafe from './TouchableNativeFeedbackSafe';
|
||||
|
||||
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';
|
||||
|
||||
/**
|
||||
* Can be used as a React ref for a component to auto-focus for accessibility on render.
|
||||
* @param ref The component to auto-focus
|
||||
*/
|
||||
const focusViewOnRender = (ref: React.Component | null) => {
|
||||
if (ref) {
|
||||
const reactTag = findNodeHandle(ref);
|
||||
if (reactTag) {
|
||||
if (Platform.OS === 'android') {
|
||||
// @ts-ignore: sendAccessibilityEvent is missing from @types/react-native
|
||||
UIManager.sendAccessibilityEvent(
|
||||
reactTag,
|
||||
// @ts-ignore: AccessibilityEventTypes is missing from @types/react-native
|
||||
UIManager.AccessibilityEventTypes.typeViewFocused
|
||||
);
|
||||
} else {
|
||||
AccessibilityInfo.setAccessibilityFocus(reactTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const isIndexDestructive = (index: number, destructiveIndex?: number | number[]) => {
|
||||
if (Array.isArray(destructiveIndex)) {
|
||||
return destructiveIndex.includes(index);
|
||||
}
|
||||
|
||||
return index === destructiveIndex;
|
||||
};
|
||||
|
||||
const isIndexDisabled = (index: number, disabledButtonIndices: number[] = []) => {
|
||||
return disabledButtonIndices.includes(index);
|
||||
};
|
||||
|
||||
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,
|
||||
cancelButtonIndex,
|
||||
cancelButtonTintColor,
|
||||
destructiveButtonIndex,
|
||||
disabledButtonIndices,
|
||||
destructiveColor = DESTRUCTIVE_COLOR,
|
||||
onSelect,
|
||||
startIndex,
|
||||
length,
|
||||
textStyle,
|
||||
tintColor,
|
||||
autoFocus,
|
||||
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 disabled = isIndexDisabled(i, disabledButtonIndices);
|
||||
const isCancelButton = i === cancelButtonIndex;
|
||||
const color = isIndexDestructive(i, destructiveButtonIndex)
|
||||
? destructiveColor
|
||||
: isCancelButton
|
||||
? cancelButtonTintColor || defaultColor
|
||||
: defaultColor;
|
||||
const iconSource = icons != null ? icons[i] : null;
|
||||
|
||||
optionViews.push(
|
||||
<TouchableNativeFeedbackSafe
|
||||
ref={autoFocus && i === 0 ? focusViewOnRender : undefined}
|
||||
key={i}
|
||||
pressInDelay={0}
|
||||
background={nativeFeedbackBackground}
|
||||
disabled={disabled}
|
||||
onPress={() => onSelect(i)}
|
||||
style={[styles.button, disabled && styles.disabledButton]}
|
||||
accessibilityRole="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,
|
||||
},
|
||||
disabledButton: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
groupContainer: {
|
||||
backgroundColor: '#ffffff',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
icon: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
marginRight: 32,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
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,333 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Animated,
|
||||
BackHandler,
|
||||
Easing,
|
||||
Modal,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
ViewProps,
|
||||
} from 'react-native';
|
||||
|
||||
import { ActionSheetOptions } from '../types';
|
||||
import ActionGroup from './ActionGroup';
|
||||
|
||||
interface State {
|
||||
isVisible: boolean;
|
||||
isAnimating: boolean;
|
||||
options: ActionSheetOptions | null;
|
||||
onSelect: ((i: number) => void) | null;
|
||||
overlayOpacity: any;
|
||||
sheetOpacity: any;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
readonly children: React.ReactNode;
|
||||
readonly useNativeDriver?: boolean;
|
||||
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);
|
||||
const ESCAPE_KEY = 'Escape';
|
||||
|
||||
// Has same API as https://facebook.github.io/react-native/docs/actionsheetios.html
|
||||
export default class CustomActionSheet extends React.Component<Props, State> {
|
||||
_actionSheetHeight = 360;
|
||||
|
||||
state: State = {
|
||||
isVisible: false,
|
||||
isAnimating: false,
|
||||
options: null,
|
||||
onSelect: null,
|
||||
overlayOpacity: new Animated.Value(0),
|
||||
sheetOpacity: new Animated.Value(0),
|
||||
};
|
||||
|
||||
_deferAfterAnimation?: () => void = undefined;
|
||||
|
||||
componentDidMount() {
|
||||
if (Platform.OS === 'web') {
|
||||
document.addEventListener('keydown', this._handleWebKeyDown);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (Platform.OS === 'web') {
|
||||
document.removeEventListener('keydown', this._handleWebKeyDown);
|
||||
}
|
||||
}
|
||||
|
||||
_handleWebKeyDown = (event: KeyboardEvent): void => {
|
||||
if (event.key === ESCAPE_KEY && this.state.isVisible) {
|
||||
event.preventDefault();
|
||||
this._selectCancelButton();
|
||||
}
|
||||
};
|
||||
|
||||
_setActionSheetHeight = ({ nativeEvent }: any) =>
|
||||
(this._actionSheetHeight = nativeEvent.layout.height);
|
||||
|
||||
render() {
|
||||
const { isVisible, overlayOpacity, options } = this.state;
|
||||
const useModal = options ? options.autoFocus || options.useModal === true : false;
|
||||
const overlay = isVisible ? (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.overlay,
|
||||
{
|
||||
opacity: overlayOpacity,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
// While the sheet is visible, hide the rest of the app's content from screen readers.
|
||||
const appContent = (
|
||||
<View
|
||||
style={styles.flexContainer}
|
||||
importantForAccessibility={isVisible ? 'no-hide-descendants' : 'auto'}>
|
||||
{React.Children.only(this.props.children)}
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View pointerEvents={this.props.pointerEvents} style={styles.flexContainer}>
|
||||
{appContent}
|
||||
{isVisible && !useModal && (
|
||||
<>
|
||||
{overlay}
|
||||
{this._renderSheet()}
|
||||
</>
|
||||
)}
|
||||
{isVisible && useModal && (
|
||||
<Modal animationType="none" transparent onRequestClose={this._selectCancelButton}>
|
||||
{overlay}
|
||||
{this._renderSheet()}
|
||||
</Modal>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_renderSheet() {
|
||||
const { options, isAnimating, sheetOpacity } = this.state;
|
||||
|
||||
if (!options) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
options: optionsArray,
|
||||
icons,
|
||||
tintIcons,
|
||||
destructiveButtonIndex,
|
||||
disabledButtonIndices,
|
||||
destructiveColor,
|
||||
textStyle,
|
||||
tintColor,
|
||||
title,
|
||||
titleTextStyle,
|
||||
message,
|
||||
messageTextStyle,
|
||||
autoFocus,
|
||||
showSeparators,
|
||||
containerStyle,
|
||||
separatorStyle,
|
||||
cancelButtonIndex,
|
||||
cancelButtonTintColor,
|
||||
} = options;
|
||||
return (
|
||||
<TouchableWithoutFeedback importantForAccessibility="yes" 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}
|
||||
cancelButtonIndex={cancelButtonIndex}
|
||||
cancelButtonTintColor={cancelButtonTintColor}
|
||||
destructiveButtonIndex={destructiveButtonIndex}
|
||||
destructiveColor={destructiveColor}
|
||||
disabledButtonIndices={disabledButtonIndices}
|
||||
onSelect={this._onSelect}
|
||||
startIndex={0}
|
||||
length={optionsArray.length}
|
||||
textStyle={textStyle || {}}
|
||||
tintColor={tintColor}
|
||||
title={title || undefined}
|
||||
titleTextStyle={titleTextStyle}
|
||||
message={message || undefined}
|
||||
messageTextStyle={messageTextStyle}
|
||||
autoFocus={autoFocus}
|
||||
showSeparators={showSeparators}
|
||||
containerStyle={containerStyle}
|
||||
separatorStyle={separatorStyle}
|
||||
/>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
}
|
||||
|
||||
showActionSheetWithOptions = (options: ActionSheetOptions, onSelect: (i: number) => void) => {
|
||||
const { isVisible, overlayOpacity, sheetOpacity } = this.state;
|
||||
const { useNativeDriver = true } = this.props;
|
||||
|
||||
if (isVisible) {
|
||||
this._deferAfterAnimation = 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,
|
||||
}),
|
||||
Animated.timing(sheetOpacity, {
|
||||
toValue: 1,
|
||||
easing: EASING_OUT,
|
||||
duration: OPACITY_ANIMATION_IN_TIME,
|
||||
useNativeDriver,
|
||||
}),
|
||||
]).start((result) => {
|
||||
if (result.finished) {
|
||||
this.setState({
|
||||
isAnimating: false,
|
||||
});
|
||||
this._deferAfterAnimation = 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 false;
|
||||
} 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;
|
||||
}
|
||||
|
||||
if (onSelect) {
|
||||
this._deferAfterAnimation = onSelect.bind(this, index);
|
||||
}
|
||||
|
||||
return this._animateOut();
|
||||
};
|
||||
|
||||
_animateOut = (): boolean => {
|
||||
const { isAnimating, overlayOpacity, sheetOpacity } = this.state;
|
||||
const { useNativeDriver = true } = this.props;
|
||||
|
||||
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,
|
||||
}),
|
||||
Animated.timing(sheetOpacity, {
|
||||
toValue: 0,
|
||||
easing: EASING_IN,
|
||||
duration: OPACITY_ANIMATION_OUT_TIME,
|
||||
useNativeDriver,
|
||||
}),
|
||||
]).start((result) => {
|
||||
if (result.finished) {
|
||||
this.setState({
|
||||
isVisible: false,
|
||||
isAnimating: false,
|
||||
});
|
||||
|
||||
if (this._deferAfterAnimation) {
|
||||
this._deferAfterAnimation();
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
flexContainer: {
|
||||
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',
|
||||
},
|
||||
});
|
||||
@@ -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,48 @@
|
||||
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,
|
||||
cancelButtonTintColor,
|
||||
disabledButtonIndices,
|
||||
} = dataOptions;
|
||||
const iosOptions: ActionSheetIOSOptions = {
|
||||
cancelButtonIndex,
|
||||
destructiveButtonIndex,
|
||||
options,
|
||||
tintColor,
|
||||
cancelButtonTintColor,
|
||||
disabledButtonIndices,
|
||||
// A null title or message on iOS causes a crash
|
||||
title: dataOptions.title || undefined,
|
||||
message: dataOptions.message || undefined,
|
||||
anchor: dataOptions.anchor || undefined,
|
||||
userInterfaceStyle: dataOptions.userInterfaceStyle || undefined,
|
||||
};
|
||||
// @ts-ignore: Even though ActionSheetIOS supports array of numbers for `destructiveIndex` the types are not yet updated. See https://github.com/facebook/react-native/pull/18254.
|
||||
ActionSheetIOS.showActionSheetWithOptions(iosOptions, onSelect);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
import CustomActionSheet from './CustomActionSheet';
|
||||
export default CustomActionSheet;
|
||||
@@ -0,0 +1,45 @@
|
||||
import * as React from 'react';
|
||||
|
||||
// NativeActionSheet will always be custom when on Android/web
|
||||
import NativeActionSheet from './ActionSheet';
|
||||
import CustomActionSheet from './ActionSheet/CustomActionSheet';
|
||||
import { Provider } from './context';
|
||||
import { ActionSheetOptions } from './types';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
useNativeDriver?: boolean;
|
||||
useCustomActionSheet?: boolean;
|
||||
}
|
||||
|
||||
export default function ActionSheetProvider({
|
||||
children,
|
||||
useNativeDriver,
|
||||
useCustomActionSheet = false,
|
||||
}: Props) {
|
||||
const actionSheetRef = React.useRef<NativeActionSheet>(null);
|
||||
|
||||
const context = React.useMemo(
|
||||
() => ({
|
||||
showActionSheetWithOptions: (options: ActionSheetOptions, callback: (i: number) => void) => {
|
||||
if (actionSheetRef.current) {
|
||||
actionSheetRef.current.showActionSheetWithOptions(options, callback);
|
||||
}
|
||||
},
|
||||
}),
|
||||
[actionSheetRef]
|
||||
);
|
||||
|
||||
const ActionSheet = React.useMemo(
|
||||
() => (useCustomActionSheet ? CustomActionSheet : NativeActionSheet),
|
||||
[useCustomActionSheet]
|
||||
);
|
||||
|
||||
return (
|
||||
<Provider value={context}>
|
||||
<ActionSheet ref={actionSheetRef} useNativeDriver={useNativeDriver}>
|
||||
{React.Children.only(children)}
|
||||
</ActionSheet>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import hoistNonReactStatic from 'hoist-non-react-statics';
|
||||
import * as React from 'react';
|
||||
|
||||
import { Consumer } from './context';
|
||||
import { ActionSheetProps } from './types';
|
||||
|
||||
export default function connectActionSheet<OwnProps = any>(
|
||||
WrappedComponent: React.ComponentType<OwnProps & ActionSheetProps>
|
||||
): React.FunctionComponent<OwnProps & ActionSheetProps> {
|
||||
const ConnectedActionSheet = (props: OwnProps) => {
|
||||
return (
|
||||
<Consumer>
|
||||
{({ showActionSheetWithOptions }) => {
|
||||
return (
|
||||
<WrappedComponent {...props} showActionSheetWithOptions={showActionSheetWithOptions} />
|
||||
);
|
||||
}}
|
||||
</Consumer>
|
||||
);
|
||||
};
|
||||
|
||||
return hoistNonReactStatic(ConnectedActionSheet, WrappedComponent);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
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,38 @@
|
||||
import * as React from 'react';
|
||||
import { TextStyle, ViewStyle } from 'react-native';
|
||||
|
||||
export interface ActionSheetProps {
|
||||
showActionSheetWithOptions: (
|
||||
options: ActionSheetOptions,
|
||||
callback: (i?: number) => void | Promise<void>
|
||||
) => void;
|
||||
}
|
||||
|
||||
// for iOS
|
||||
export interface ActionSheetIOSOptions {
|
||||
options: string[];
|
||||
title?: string;
|
||||
message?: string;
|
||||
tintColor?: string;
|
||||
cancelButtonIndex?: number;
|
||||
cancelButtonTintColor?: string;
|
||||
destructiveButtonIndex?: number | number[];
|
||||
anchor?: number;
|
||||
userInterfaceStyle?: 'light' | 'dark';
|
||||
disabledButtonIndices?: number[];
|
||||
}
|
||||
|
||||
// for Android or Web
|
||||
export interface ActionSheetOptions extends ActionSheetIOSOptions {
|
||||
icons?: React.ReactNode[];
|
||||
tintIcons?: boolean;
|
||||
textStyle?: TextStyle;
|
||||
titleTextStyle?: TextStyle;
|
||||
messageTextStyle?: TextStyle;
|
||||
autoFocus?: boolean;
|
||||
showSeparators?: boolean;
|
||||
containerStyle?: ViewStyle;
|
||||
separatorStyle?: ViewStyle;
|
||||
useModal?: boolean;
|
||||
destructiveColor?: string;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
"lib": ["dom", "es2015", "es2016", "esnext"],
|
||||
"jsx": "react-native",
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in new issue
Block a user