diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..27e3ca1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,40 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# 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 directories
+node_modules
+jspm_packages
+
+# Optional npm cache directory
+.npm
+
+# Optional REPL history
+.node_repl_history
+
+
+.idea
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..33a9488
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1 @@
+example
diff --git a/ActionButton.js b/ActionButton.js
index e902c83..ec1b9e3 100644
--- a/ActionButton.js
+++ b/ActionButton.js
@@ -1,270 +1,342 @@
-import React, { Component, PropTypes } from 'react';
-import { StyleSheet, Text, View, Animated, TouchableOpacity, Platform } from 'react-native';
-import ActionButtonItem from './ActionButtonItem';
-
-const alignItemsMap = {
- "center" : "center",
- "left" : "flex-start",
- "right" : "flex-end"
-}
-
-const shadowHeight = 12;
-
-export default class ActionButton extends Component {
+import React, { Component, useState, useRef, useEffect } from "react";
+import PropTypes from "prop-types";
+import {
+ StyleSheet,
+ Text,
+ View,
+ Animated,
+ TouchableOpacity
+} from "react-native";
+import ActionButtonItem from "./ActionButtonItem";
+import {
+ shadowStyle,
+ alignItemsMap,
+ getTouchableComponent,
+ isAndroid,
+ touchableBackground,
+ DEFAULT_ACTIVE_OPACITY
+} from "./shared";
+
+const ActionButton = props => {
+ const [, setResetToken] = useState(props.resetToken);
+ const [active, setActive] = useState(props.active);
+ const anim = useRef(new Animated.Value(props.active ? 1 : 0));
+ const timeout = useRef(null);
+ const mounted = useRef(false);
+
+ useEffect(() => {
+ mounted.current = true;
+
+ return () => {
+ mounted.current = false;
+ timeout.current && clearTimeout(timeout.current);
+ };
+ }, []);
- constructor(props) {
- super(props);
+ useEffect(() => {
+ if (props.active) {
+ Animated.spring(anim.current, { toValue: 1 }).start();
+ setActive(true);
+ setResetToken(props.resetToken);
+ } else {
+ props.onReset && props.onReset();
- this.state = {
- active: props.active,
+ Animated.spring(anim.current, { toValue: 0 }).start();
+ timeout.current = setTimeout(() => {
+ setActive(false);
+ setResetToken(props.resetToken);
+ }, 250);
}
-
- this.anim = new Animated.Value(props.active ? 1 : 0);
- this.timeout = null;
- }
-
- componentWillUnmount() {
- clearTimeout(this.timeout);
- }
-
+ }, [props.resetToken, props.active]);
//////////////////////
// STYLESHEET GETTERS
//////////////////////
- getContainerStyles() {
- return [this.getOverlayStyles(), this.getOrientation(), this.getOffsetXY()];
- }
-
- getActionButtonStyles() {
- const actionButtonStyles = [styles.actionBarItem, this.getButtonSize()];
- return actionButtonStyles;
- }
-
- getOrientation() {
- return { alignItems: alignItemsMap[this.props.position] };
- }
+ const getOrientation = () => {
+ return { alignItems: alignItemsMap[props.position] };
+ };
- getButtonSize() {
+ const getOffsetXY = () => {
return {
- width: this.props.size + 16,
- height: this.props.size + shadowHeight,
- }
- }
-
- getOffsetXY() {
- return {
- paddingHorizontal: this.props.offsetX - 8,
- paddingBottom: this.props.verticalOrientation === 'up' ? this.props.offsetY : 0,
- paddingTop: this.props.verticalOrientation === 'down' ? this.props.offsetY : 0
+ // paddingHorizontal: props.offsetX,
+ paddingVertical: props.offsetY
};
- }
+ };
- getActionsStyle() {
- return [
- styles.actionsVertical,
- this.getOrientation(),
- {
- flexDirection: 'column',
- justifyContent: this.props.verticalOrientation === 'up' ? 'flex-end' : 'flex-start'
- },
- ];
- }
-
- getOverlayStyles() {
+ const getOverlayStyles = () => {
return [
styles.overlay,
{
- justifyContent: this.props.verticalOrientation === 'up' ? 'flex-end' : 'flex-start'
+ elevation: props.elevation,
+ zIndex: props.zIndex,
+ justifyContent:
+ props.verticalOrientation === "up" ? "flex-end" : "flex-start"
}
- ]
- }
+ ];
+ };
+
+ const _renderMainButton = () => {
+ const animatedViewStyle = {
+ transform: [
+ {
+ scale: anim.current.interpolate({
+ inputRange: [0, 1],
+ outputRange: [1, props.outRangeScale]
+ })
+ },
+ {
+ rotate: anim.current.interpolate({
+ inputRange: [0, 1],
+ outputRange: ["0deg", props.degrees + "deg"]
+ })
+ }
+ ]
+ };
+ const wrapperStyle = {
+ backgroundColor: anim.current.interpolate({
+ inputRange: [0, 1],
+ outputRange: [props.buttonColor, props.btnOutRange || props.buttonColor]
+ }),
+ width: props.size,
+ height: props.size,
+ borderRadius: props.size / 2
+ };
- //////////////////////
- // RENDER METHODS
- //////////////////////
+ const buttonStyle = {
+ width: props.size,
+ height: props.size,
+ borderRadius: props.size / 2,
+ alignItems: "center",
+ justifyContent: "center"
+ };
+
+ const Touchable = getTouchableComponent(props.useNativeFeedback);
+ const parentStyle =
+ isAndroid && props.fixNativeFeedbackRadius
+ ? {
+ right: props.offsetX,
+ zIndex: props.zIndex,
+ borderRadius: props.size / 2,
+ width: props.size
+ }
+ : { marginHorizontal: props.offsetX, zIndex: props.zIndex };
- render() {
return (
-
-
- {this.props.backdrop}
-
-
- {(this.state.active && !this.props.backgroundTappable) && this._renderTappableBackground()}
-
- {this.props.verticalOrientation === 'up' &&
- this.props.children && this._renderActions()}
- {this._renderButton()}
- {this.props.verticalOrientation === 'down' &&
- this.props.children && this._renderActions()}
-
+
+ {
+ props.onPress();
+ if (props.children) animateButton();
+ }}
+ onPressIn={props.onPressIn}
+ onPressOut={props.onPressOut}
+ >
+
+
+ {_renderButtonIcon()}
+
+
+
);
- }
-
- _renderButton() {
- const buttonColorMax = this.props.btnOutRange ? this.props.btnOutRange : this.props.buttonColor;
-
- const animatedViewStyle = [
- styles.btn,
- {
- backgroundColor: this.anim.interpolate({
- inputRange: [0, 1],
- outputRange: [this.props.buttonColor, buttonColorMax]
- }),
- transform: [{
- scale: this.anim.interpolate({
- inputRange: [0, 1],
- outputRange: [1, this.props.outRangeScale]
- }),
- }, {
- rotate: this.anim.interpolate({
- inputRange: [0, 1],
- outputRange: ['0deg', this.props.degrees + 'deg']
- })
- }],
- },
- ];
-
- const combinedStyle = {
- width: this.props.size,
- height: this.props.size,
- borderRadius: this.props.size / 2,
- marginBottom: shadowHeight,
- backgroundColor: this.props.buttonColor
+ };
+
+ const _renderButtonIcon = () => {
+ const {
+ icon,
+ renderIcon,
+ btnOutRangeTxt,
+ buttonTextStyle,
+ buttonText
+ } = props;
+ if (renderIcon) return renderIcon(active);
+ if (icon) {
+ console.warn(
+ "react-native-action-button: The `icon` prop is deprecated! Use `renderIcon` instead."
+ );
+ return icon;
}
- const actionButtonStyles = [ this.getActionButtonStyles(), combinedStyle, animatedViewStyle ]
+ const textColor = buttonTextStyle.color || "rgba(255,255,255,1)";
return (
-
-
- {
- this.props.onPress()
- if (this.props.children) this.animateButton()
- }}>
- {this._renderButtonIcon()}
-
-
-
+
+ {buttonText}
+
);
- }
-
- _renderButtonIcon() {
- const { icon, btnOutRangeTxt, buttonTextColor } = this.props;
+ };
- if (icon) return icon;
+ const _renderActions = () => {
+ const { children, verticalOrientation } = props;
- const buttonTextColorMax = btnOutRangeTxt ? btnOutRangeTxt : buttonTextColor;
+ if (!active) return null;
- return (
-
- +
-
- )
- }
+ let actionButtons = !Array.isArray(children) ? [children] : children;
- _renderActions() {
- if (!this.state.active) return null;
-
- let actionButtons = this.props.children
+ actionButtons = actionButtons.filter(
+ actionButton => typeof actionButton == "object"
+ );
- if (!Array.isArray(this.props.children)) {
- actionButtons = [this.props.children]
- }
+ const actionStyle = {
+ flex: 1,
+ alignSelf: "stretch",
+ // backgroundColor: 'purple',
+ justifyContent: verticalOrientation === "up" ? "flex-end" : "flex-start",
+ paddingTop: props.verticalOrientation === "down" ? props.spacing : 0,
+ zIndex: props.zIndex
+ };
return (
-
- {actionButtons.map((ActionButton, index) => {
- return (
- {
- if (this.props.autoInactive){
- this.timeout = setTimeout(this.reset.bind(this), 200);
- }
- ActionButton.props.onPress();
- }}
- />
- )
- })}
-
+
+ {actionButtons.map((ActionButton, idx) => (
+ {
+ if (props.autoInactive) {
+ timeout.current = setTimeout(reset, 200);
+ }
+ ActionButton.props.onPress();
+ }}
+ />
+ ))}
+
);
- }
+ };
- _renderTappableBackground() {
+ const _renderTappableBackground = () => {
return (
);
- }
-
+ };
//////////////////////
// Animation Methods
//////////////////////
- animateButton(animate=true) {
- if (this.state.active) return this.reset();
+ const animateButton = (animate = true) => {
+ if (active) return reset(animate);
if (animate) {
- Animated.spring(this.anim, { toValue: 1 }).start();
+ Animated.spring(anim.current, { toValue: 1 }).start();
} else {
- this.anim.setValue(1);
+ anim.current.setValue(1);
}
- this.setState({ active: true });
- }
+ setActive(true);
+ };
- reset(animate=true) {
- if (this.props.onReset) this.props.onReset();
+ const reset = (animate = true) => {
+ if (props.onReset) props.onReset();
if (animate) {
- Animated.spring(this.anim, { toValue: 0 }).start();
+ Animated.spring(anim.current, { toValue: 0 }).start();
} else {
- this.anim.setValue(0);
+ anim.current.setValue(0);
}
- setTimeout(() => this.setState({ active: false }), 250);
- }
-}
+ timeout.current = setTimeout(() => {
+ if (mounted.current) {
+ setActive(false);
+ }
+ }, 250);
+ };
+
+ return (
+
+
+ {props.backdrop}
+
+
+ {active && !props.backgroundTappable && _renderTappableBackground()}
+
+ {props.verticalOrientation === "up" &&
+ props.children &&
+ _renderActions()}
+ {_renderMainButton()}
+ {props.verticalOrientation === "down" &&
+ props.children &&
+ _renderActions()}
+
+
+ );
+};
ActionButton.Item = ActionButtonItem;
ActionButton.propTypes = {
+ resetToken: PropTypes.any,
active: PropTypes.bool,
position: PropTypes.string,
+ elevation: PropTypes.number,
+ zIndex: PropTypes.number,
hideShadow: PropTypes.bool,
+ shadowStyle: PropTypes.oneOfType([
+ PropTypes.object,
+ PropTypes.array,
+ PropTypes.number
+ ]),
+
+ renderIcon: PropTypes.func,
bgColor: PropTypes.string,
+ bgOpacity: PropTypes.number,
buttonColor: PropTypes.string,
- buttonTextColor: PropTypes.string,
+ buttonTextStyle: Text.propTypes.style,
+ buttonText: PropTypes.string,
offsetX: PropTypes.number,
offsetY: PropTypes.number,
@@ -272,69 +344,67 @@ ActionButton.propTypes = {
size: PropTypes.number,
autoInactive: PropTypes.bool,
onPress: PropTypes.func,
- backdrop: PropTypes.oneOfType([
- PropTypes.bool,
- PropTypes.object
- ]),
+ onPressIn: PropTypes.func,
+ onPressOut: PropTypes.func,
+ backdrop: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
degrees: PropTypes.number,
- verticalOrientation: PropTypes.oneOf(['up', 'down']),
+ verticalOrientation: PropTypes.oneOf(["up", "down"]),
backgroundTappable: PropTypes.bool,
+ activeOpacity: PropTypes.number,
+
+ useNativeFeedback: PropTypes.bool,
+ fixNativeFeedbackRadius: PropTypes.bool,
+ nativeFeedbackRippleColor: PropTypes.string,
+
+ testID: PropTypes.string,
+ accessibilityLabel: PropTypes.string,
+ accessible: PropTypes.bool
};
ActionButton.defaultProps = {
+ resetToken: null,
active: false,
- bgColor: 'transparent',
- buttonColor: 'rgba(0,0,0,1)',
- buttonTextColor: 'rgba(255,255,255,1)',
+ bgColor: "transparent",
+ bgOpacity: 1,
+ buttonColor: "rgba(0,0,0,1)",
+ buttonTextStyle: {},
+ buttonText: "+",
spacing: 20,
outRangeScale: 1,
autoInactive: true,
onPress: () => {},
+ onPressIn: () => {},
+ onPressOn: () => {},
backdrop: false,
- degrees: 135,
- position: 'right',
+ degrees: 45,
+ position: "right",
offsetX: 30,
offsetY: 30,
size: 56,
- verticalOrientation: 'up',
+ verticalOrientation: "up",
backgroundTappable: false,
+ useNativeFeedback: true,
+ activeOpacity: DEFAULT_ACTIVE_OPACITY,
+ fixNativeFeedbackRadius: false,
+ nativeFeedbackRippleColor: "rgba(255,255,255,0.75)",
+ testID: undefined,
+ accessibilityLabel: undefined,
+ accessible: undefined
};
const styles = StyleSheet.create({
overlay: {
- position: 'absolute',
+ position: "absolute",
bottom: 0,
left: 0,
right: 0,
top: 0,
- backgroundColor: 'transparent',
- },
- actionBarItem: {
- alignItems: 'center',
- justifyContent: 'center',
- backgroundColor: 'transparent',
- marginBottom: 12,
- },
- btn: {
- justifyContent: 'center',
- alignItems: 'center',
+ backgroundColor: "transparent"
},
btnText: {
marginTop: -4,
fontSize: 24,
- backgroundColor: 'transparent',
- position: 'relative',
- },
- btnShadow: {
- shadowOpacity: 0.3,
- shadowOffset: {
- width: 0, height: 8,
- },
- shadowColor: '#000',
- shadowRadius: 4,
- elevation: 8,
- },
- actionsVertical: {
- flex: 1,
+ backgroundColor: "transparent"
}
});
+export default ActionButton;
diff --git a/ActionButtonItem.js b/ActionButtonItem.js
index 5449589..5e2bd2b 100644
--- a/ActionButtonItem.js
+++ b/ActionButtonItem.js
@@ -1,155 +1,217 @@
-import React, { Component } from 'react';
-import { StyleSheet, Text, View, Animated, TouchableOpacity, Dimensions } from 'react-native';
-const { width } = Dimensions.get('window');
-
-const alignItemsMap = {
- center: "center",
- left: "flex-start",
- right: "flex-end"
-}
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import {
+ StyleSheet,
+ Text,
+ View,
+ Animated,
+ TouchableNativeFeedback,
+ TouchableWithoutFeedback,
+ Dimensions,
+} from "react-native";
+import {
+ shadowStyle,
+ alignItemsMap,
+ getTouchableComponent,
+ isAndroid,
+ touchableBackground,
+ DEFAULT_ACTIVE_OPACITY
+} from "./shared";
-export default class ActionButtonItem extends Component {
+const { width: WIDTH } = Dimensions.get("window");
+const SHADOW_SPACE = 10;
+const TEXT_HEIGHT = 22;
- constructor(props) {
- super(props);
- this.state = {
- spaceBetween: this.props.spaceBetween || 15,
- alignItems: alignItemsMap[this.props.position]
+const TextTouchable = isAndroid
+ ? TouchableNativeFeedback
+ : TouchableWithoutFeedback;
+
+export default class ActionButtonItem extends Component {
+ static get defaultProps() {
+ return {
+ active: true,
+ spaceBetween: 15,
+ useNativeFeedback: true,
+ activeOpacity: DEFAULT_ACTIVE_OPACITY,
+ fixNativeFeedbackRadius: false,
+ nativeFeedbackRippleColor: "rgba(255,255,255,0.75)",
+ numberOfLines: 1,
};
+ }
- if (!props.children || Array.isArray(props.children)) {
- throw new Error("ActionButtonItem must have a Child component.");
- }
+ static get propTypes() {
+ return {
+ active: PropTypes.bool,
+ useNativeFeedback: PropTypes.bool,
+ fixNativeFeedbackRadius: PropTypes.bool,
+ nativeFeedbackRippleColor: PropTypes.string,
+ activeOpacity: PropTypes.number,
+ numberOfLines: PropTypes.number,
+ };
}
render() {
- const translateXMap = {
- center: 0,
- left: (this.props.parentSize - this.props.size) / 2 - 8,
- right: -(this.props.parentSize - this.props.size) / 2 + 8,
- }
+ const {
+ size,
+ position,
+ verticalOrientation,
+ hideShadow,
+ spacing
+ } = this.props;
+
+ if (!this.props.active) return null;
+
+ const animatedViewStyle = {
+ marginBottom: -SHADOW_SPACE,
+ alignItems: alignItemsMap[position],
+
+ // backgroundColor: this.props.buttonColor,
+ opacity: this.props.anim,
+ transform: [
+ {
+ translateY: this.props.anim.interpolate({
+ inputRange: [0, 1],
+ outputRange: [verticalOrientation === "down" ? -40 : 40, 0]
+ })
+ }
+ ]
+ };
+
+ const buttonStyle = {
+ justifyContent: "center",
+ alignItems: "center",
+ width: size,
+ height: size,
+ borderRadius: size / 2,
+ backgroundColor: this.props.buttonColor || this.props.btnColor
+ };
+
+ if (position !== "center")
+ buttonStyle[position] = (this.props.parentSize - size) / 2;
- const translateX = translateXMap[this.props.position];
- const margin = (this.props.spacing < 12) ? 0 : (this.props.spacing - 12);
+ const Touchable = getTouchableComponent(this.props.useNativeFeedback);
+ const parentStyle = isAndroid &&
+ this.props.fixNativeFeedbackRadius
+ ? {
+ height: size,
+ marginBottom: spacing,
+ right: this.props.offsetX,
+ borderRadius: this.props.size / 2
+ }
+ : {
+ paddingHorizontal: this.props.offsetX,
+ height: size + SHADOW_SPACE + spacing
+ };
return (
-
-
- {this.props.children}
-
-
- {this.props.title && (
-
+
-
- {this.props.title}
-
-
- )}
+
+ {this.props.children}
+
+
+
+ {this._renderTitle()}
);
}
- getTextStyles() {
- // to align the center of the label with the center of the button,
- // offset = (half the size of the btn) - (half the size of the label)
- let directionOffset = this.props.verticalOrientation === 'down' ? -12 : 0
- let offsetTop = this.props.size >= 28 ? (this.props.size / 2) - 14 + directionOffset : 0;
-
- let positionStyles = {
- right: this.props.size + this.state.spaceBetween + 8,
- top: offsetTop
- }
+ _renderTitle() {
+ if (!this.props.title) return null;
- let bgStyle = { backgroundColor : 'white' };
+ const {
+ textContainerStyle,
+ hideLabelShadow,
+ offsetX,
+ parentSize,
+ size,
+ position,
+ spaceBetween,
+ numberOfLines,
+ } = this.props;
+ const offsetTop = Math.max(size / 2 - TEXT_HEIGHT / 2, 0);
+ const positionStyles = { top: offsetTop };
+ const hideShadow = hideLabelShadow === undefined
+ ? this.props.hideShadow
+ : hideLabelShadow;
- if (this.props.titleBgColor) bgStyle = {
- backgroundColor:this.props.titleBgColor
+ if (position !== "center") {
+ positionStyles[position] =
+ offsetX + (parentSize - size) / 2 + size + spaceBetween;
+ } else {
+ positionStyles.right = WIDTH / 2 + size / 2 + spaceBetween;
}
- if (this.props.position == 'left') positionStyles = {
- left: this.props.size + this.state.spaceBetween + 8,
- top: offsetTop
- }
+ const textStyles = [
+ styles.textContainer,
+ positionStyles,
+ !hideShadow && shadowStyle,
+ textContainerStyle
+ ];
- if (this.props.position == 'center') positionStyles = {
- right: this.props.size/2 + width/2 + this.state.spaceBetween,
- top: offsetTop
- }
+ const title = (
+ React.isValidElement(this.props.title) ?
+ this.props.title
+ : (
+
+ {this.props.title}
+
+ )
+ )
- return [styles.actionTextView, positionStyles, bgStyle];
+ return (
+
+
+ {title}
+
+
+ );
}
}
const styles = StyleSheet.create({
- actionButtonWrap: {
- width
- },
- actionButton: {
- justifyContent: 'center',
- alignItems: 'center',
- flexDirection: 'row',
- },
- shadow: {
- shadowOpacity: 0.3,
- shadowOffset: {
- width: 0, height: 8,
- },
- shadowColor: '#000',
- shadowRadius: 4,
- elevation: 6,
- },
- actionTextView: {
- position: 'absolute',
- paddingVertical: 4,
- paddingHorizontal: 10,
+ textContainer: {
+ position: "absolute",
+ paddingVertical: isAndroid ? 2 : 3,
+ paddingHorizontal: 8,
borderRadius: 3,
borderWidth: StyleSheet.hairlineWidth,
- borderColor: '#eee',
+ borderColor: "#eee",
+ backgroundColor: "white",
+ height: TEXT_HEIGHT
},
- actionText: {
+ text: {
flex: 1,
- fontSize: 14,
+ fontSize: 12,
+ color: "#444"
}
});
diff --git a/README.md b/README.md
index 0c35e20..6402389 100644
--- a/README.md
+++ b/README.md
@@ -6,10 +6,22 @@ customizable multi-action-button component for react-native


+### Known Issues
+- Doesn't Work While Android Debugging. See issue [#79](https://github.com/mastermoo/react-native-action-button/issues/79).
+
### Installation
```bash
npm i react-native-action-button --save
```
+Link `react-native-vector-icons` native dependencies to your project with:
+```bash
+react-native link react-native-vector-icons
+```
+or just:
+```bash
+react-native link
+```
+to link all libraries with native dependencies in your project.
### Usage
@@ -40,7 +52,7 @@ class App extends Component {
render() {
return (
- {/*Rest of App come ABOVE the action button component!*/}
+ {/* Rest of the app comes ABOVE the action button component !*/}
console.log("notes tapped!")}>
@@ -86,6 +98,8 @@ Take a look at [this gist](https://gist.github.com/mmazzarolo/cfd467436f9d110e94
##### ActionButton:
| Property | Type | Default | Description |
| ------------- |:-------------:|:------------: | ----------- |
+| size | number | 56 | use this to change the size of the Button
+| resetToken | any | null | use this to reset the internal component state (expand/collapse) in a re-render cycle. Synchronize the component state.
| active | boolean | false | action buttons visible or not
| autoInactive | boolean | true | Auto hide ActionButtons when ActionButton.Item is pressed.
| hideShadow | boolean | false | use this to hide the default elevation and boxShadow
@@ -93,29 +107,46 @@ Take a look at [this gist](https://gist.github.com/mmazzarolo/cfd467436f9d110e94
| bgColor | string | "transparent" | background color when ActionButtons are visible
| buttonColor | string | "rgba(0,0,0,1)" | background color of the +Button **(must be rgba value!)**
| spacing | number | 20 | spacing between the `ActionButton.Item`s
-| offsetX | number | 10 / 30 | offset from the left/right side of the screen for `left`/`right` position respectively
-| offsetY | number | 4 / 30 | offset from the bottom/top of the screen for `up`/`down` verticalOrientation respectively
+| offsetX | number | 30 | offset from the left/right side of the screen
+| offsetY | number | 30 | offset from the bottom/top of the screen
| btnOutRange | string | props.buttonColor | button background color to animate to
| outRangeScale | number | 1 | changes size of button during animation
| onPress | function | null | fires, when ActionButton is tapped
+| onPressIn | function | null | fires, before ActionButton is released
+| onPressOut | function | null | fires, after ActionButton is released
| onLongPress | function | null | fires, when ActionButton is long pressed
-| icon | Component | + | Custom component for ActionButton Icon
+| renderIcon | function | null | Function to render the component for ActionButton Icon. It is passed a boolean, `active`, which is true if the FAB has been expanded, and false if it is collapsed, allowing you to show a different icon when the ActionButton Items are expanded.
+| icon | Component | + | **Deprecated, use `renderIcon`** Custom component for ActionButton Icon
| backdrop | Component | false | Custom component for use as Backdrop (i.e. [BlurView](https://github.com/react-native-fellowship/react-native-blur#blur-view), [VibrancyView](https://github.com/react-native-fellowship/react-native-blur#vibrancy-view))
| degrees | number | 135 | degrees to rotate icon
-| text | string | null | use this to set a different text on the button
-| buttonStyle | style | null | use this to set the textstyle of the button's text
+| buttonText | string | + | use this to set a different text on the button
+| buttonTextStyle | style | null | use this to set the textstyle of the button's text
| onReset | function | null | use this to set the callback that will be called after the button reset's it's items
| verticalOrientation | string | "up" | direction action buttons should expand. One of: `up` or `down`
| backgroundTappable | boolean | false | make background tappable in active state of ActionButton
+| activeOpacity | number | 0.85 | activeOpacity props of TouchableOpacity
+| shadowStyle | style | null | The custom shadow style you want to pass in the action button
+| useNativeFeedback | boolean | true | Android: Whether to use a TouchableNativeFeedback
+| fixNativeFeedbackRadius | boolean | false | Android: Activate this to fix TouchableNativeFeedback Ripple UI problems
+| nativeFeedbackRippleColor | string | 'rgba(255,255,255,0.75)' | Android: Pass a color to the Ripple Effect of a TouchableNativeFeedback
+
##### ActionButton.Item:
| Property | Type | Default | Description |
| ------------- |:-------------:|:------------: | ----------- |
-| title | string | undefined | the title shown next to the button, not shown when empty
+| size | number | parentSize | use this to change the size of the Button
+| title | string | undefined | the title shown next to the button. When `undefined` the title is not hidden
| onPress | func | null | **required** function, triggers when a button is tapped
| buttonColor | string | same as + button | background color of the Button
-| titleColor | string | "#444" | color of title
-| titleBgColor | string | "white" | background color of title
+| titleColor | string | "#444" | color of title, *removed* in v2.5. use `textStyle` instead
+| titleBgColor | string | "white" | background color of title, *removed* in v2.5. use `textStyle` instead
| textContainerStyle | style | null | use this to set the textstyle of the button's item text container
| textStyle | style | null | use this to set the textstyle of the button's item text
| spaceBetween | number | 15 | use this to set the space between the Button and the text container
+| numberOfLines | number | 1 | use this to set the number of lines on the button's item text
+| activeOpacity | number | 0.85 | activeOpacity props of TouchableOpacity
+| hideLabelShadow | boolean | same as hideShadow | use this to hide the button's label default elevation and boxShadow
+| shadowStyle | style | null | The custom shadow style you want to pass in the action button item
+| useNativeFeedback | boolean | true | Android: Whether to use a TouchableNativeFeedback
+| fixNativeFeedbackRadius | boolean | false | Android: Activate this to fix TouchableNativeFeedback Ripple UI problems
+| nativeFeedbackRippleColor | string | 'rgba(255,255,255,0.75)' | Android: Pass a color to the Ripple Effect of a TouchableNativeFeedback
diff --git a/index.d.ts b/index.d.ts
new file mode 100644
index 0000000..2ba74e9
--- /dev/null
+++ b/index.d.ts
@@ -0,0 +1,62 @@
+import * as React from 'react'
+import {
+ ViewStyle,
+ ViewProperties,
+ TextStyle
+} from 'react-native'
+
+
+export interface ActionButtonProperties extends ViewProperties {
+ resetToken?: any,
+ active?: boolean,
+
+ position?: string,
+ elevation?: number,
+ zIndex?: number,
+
+ hideShadow?: boolean,
+ shadowStyle?: {} | [any] | number,
+ bgColor?: string,
+ bgOpacity?: number,
+ buttonColor?: string,
+ buttonTextStyle?: TextStyle,
+ buttonText?: string,
+
+ offsetX?: number,
+ offsetY?: number,
+ spacing?: number,
+ size?: number,
+ autoInactive?: boolean,
+ onPress?: () => void,
+ renderIcon?: (active: boolean) => React.ReactElement,
+ backdrop?: boolean | object,
+ degrees?: number,
+ verticalOrientation?: 'up' | 'down',
+ backgroundTappable?: boolean,
+ activeOpacity?: number,
+
+ useNativeFeedback?: boolean,
+ fixNativeFeedbackRadius?: boolean,
+ nativeFeedbackRippleColor?: string
+}
+
+export interface ActionButtonItemProperties extends ViewProperties {
+ size?: number,
+ title?: string
+ onPress?: () => void
+ buttonColor?: string
+ textContainerStyle?: ViewStyle
+ textStyle?: TextStyle
+ spaceBetween?: number
+ activeOpacity?: number
+ hideLabelShadow?: boolean
+ shadowStyle?: ViewStyle
+ useNativeFeedback?: boolean
+ fixNativeFeedbackRadius?: boolean
+ nativeFeedbackRippleColor?: string
+}
+
+export class ActionButtonItem extends React.Component {}
+export default class ActionButton extends React.Component {
+ static Item: typeof ActionButtonItem
+}
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..cc96fdf
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,127 @@
+{
+ "name": "react-native-action-button",
+ "version": "2.8.5",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "asap": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+ "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
+ },
+ "core-js": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
+ "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
+ },
+ "encoding": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+ "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+ "requires": {
+ "iconv-lite": "0.4.23"
+ }
+ },
+ "fbjs": {
+ "version": "0.8.16",
+ "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
+ "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=",
+ "requires": {
+ "core-js": "1.2.7",
+ "isomorphic-fetch": "2.2.1",
+ "loose-envify": "1.3.1",
+ "object-assign": "4.1.1",
+ "promise": "7.3.1",
+ "setimmediate": "1.0.5",
+ "ua-parser-js": "0.7.18"
+ }
+ },
+ "iconv-lite": {
+ "version": "0.4.23",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
+ "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
+ "requires": {
+ "safer-buffer": "2.1.2"
+ }
+ },
+ "is-stream": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
+ },
+ "isomorphic-fetch": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
+ "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
+ "requires": {
+ "node-fetch": "1.7.3",
+ "whatwg-fetch": "2.0.4"
+ }
+ },
+ "js-tokens": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
+ },
+ "loose-envify": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
+ "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
+ "requires": {
+ "js-tokens": "3.0.2"
+ }
+ },
+ "node-fetch": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
+ "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
+ "requires": {
+ "encoding": "0.1.12",
+ "is-stream": "1.1.0"
+ }
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+ },
+ "promise": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
+ "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+ "requires": {
+ "asap": "2.0.6"
+ }
+ },
+ "prop-types": {
+ "version": "15.6.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz",
+ "integrity": "sha512-4ec7bY1Y66LymSUOH/zARVYObB23AT2h8cf6e/O6ZALB/N0sqZFEx7rq6EYPX2MkOdKORuooI/H5k9TlR4q7kQ==",
+ "requires": {
+ "fbjs": "0.8.16",
+ "loose-envify": "1.3.1",
+ "object-assign": "4.1.1"
+ }
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
+ },
+ "ua-parser-js": {
+ "version": "0.7.18",
+ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.18.tgz",
+ "integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA=="
+ },
+ "whatwg-fetch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
+ "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng=="
+ }
+ }
+}
diff --git a/package.json b/package.json
index 5be2383..75a66d5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-native-action-button",
- "version": "2.0.17",
+ "version": "2.9.0",
"description": "customizable multi-action-button component for react-native",
"main": "ActionButton.js",
"scripts": {
@@ -24,5 +24,8 @@
"bugs": {
"url": "https://github.com/mastermoo/react-native-action-button/issues"
},
- "homepage": "https://github.com/mastermoo/react-native-action-button"
+ "homepage": "https://github.com/mastermoo/react-native-action-button",
+ "dependencies": {
+ "prop-types": "^15.5.10"
+ }
}
diff --git a/shared.js b/shared.js
new file mode 100644
index 0000000..44aa9c7
--- /dev/null
+++ b/shared.js
@@ -0,0 +1,47 @@
+import {
+ Platform,
+ TouchableOpacity,
+ TouchableNativeFeedback
+} from "react-native";
+
+export const DEFAULT_ACTIVE_OPACITY = 0.85;
+
+export const shadowStyle = {
+ shadowOpacity: 0.35,
+ shadowOffset: {
+ width: 0,
+ height: 5
+ },
+ shadowColor: "#000",
+ shadowRadius: 3,
+ elevation: 5
+};
+
+export const alignItemsMap = {
+ center: "center",
+ left: "flex-start",
+ right: "flex-end"
+};
+
+export const isAndroid = Platform.OS === "android";
+
+export function getTouchableComponent(useNativeFeedback) {
+ if (useNativeFeedback === true && isAndroid === true) {
+ return TouchableNativeFeedback;
+ }
+ return TouchableOpacity;
+}
+
+export function touchableBackground(color, fixRadius) {
+ if (isAndroid) {
+ if (Platform["Version"] >= 21) {
+ return TouchableNativeFeedback.Ripple(
+ color || "rgba(255,255,255,0.75)",
+ fixRadius
+ );
+ } else {
+ TouchableNativeFeedback.SelectableBackground();
+ }
+ }
+ return undefined;
+}