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 ![react-native-action-button demo](http://i.giphy.com/l0K7psuhDQGLeT3d6.gif) ![react-native-action-button demo](http://i.giphy.com/xTcnSOtuet39cM46s0.gif) +### 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; +}