diff --git a/examples/components/View/ViewExample.js b/examples/components/View/ViewExample.js index 18f3a6e8..87a8eb04 100644 --- a/examples/components/View/ViewExample.js +++ b/examples/components/View/ViewExample.js @@ -31,6 +31,17 @@ var styles = StyleSheet.create({ borderColor: '#000033', borderWidth: 1, }, + shadowBox: { + width: 100, + height: 100, + borderWidth: 2, + }, + shadow: { + shadowOpacity: 0.5, + shadowColor: 'red', + shadowRadius: 3, + shadowOffset: { width: 3, height: 3 }, + }, zIndex: { justifyContent: 'space-around', width: 100, @@ -242,6 +253,19 @@ const examples = [ return ; }, }, + { + title: 'Basic shadow', + render() { + return ; + } + }, + { + title: 'Shaped shadow', + description: 'borderRadius: 50', + render() { + return ; + } + } ]; examples.forEach((example) => { diff --git a/src/apis/StyleSheet/__tests__/processBoxShadow-test.js b/src/apis/StyleSheet/__tests__/processBoxShadow-test.js new file mode 100644 index 00000000..74707a8a --- /dev/null +++ b/src/apis/StyleSheet/__tests__/processBoxShadow-test.js @@ -0,0 +1,47 @@ +/* eslint-env jasmine, jest */ + +import processBoxShadow from '../processBoxShadow'; + +describe('apis/StyleSheet/processBoxShadow', () => { + test('missing shadowColor', () => { + const style = { + shadowOffset: { width: 1, height: 2 } + }; + + expect(processBoxShadow(style)).toEqual({}); + }); + + test('shadowColor only', () => { + const style = { + shadowColor: 'red' + }; + + expect(processBoxShadow(style)).toEqual({ + boxShadow: '0px 0px 0px rgba(255,0,0,1)' + }); + }); + + test('shadowColor and shadowOpacity only', () => { + const style = { + shadowColor: 'red', + shadowOpacity: 0.5 + }; + + expect(processBoxShadow(style)).toEqual({ + boxShadow: '0px 0px 0px rgba(255,0,0,0.5)' + }); + }); + + test('shadowOffset, shadowRadius, shadowSpread', () => { + const style = { + shadowColor: 'rgba(50,60,70,0.5)', + shadowOffset: { width: 1, height: 2 }, + shadowOpacity: 0.5, + shadowRadius: 3 + }; + + expect(processBoxShadow(style)).toEqual({ + boxShadow: '2px 1px 3px rgba(50,60,70,0.25)' + }); + }); +}); diff --git a/src/apis/StyleSheet/createReactStyleObject.js b/src/apis/StyleSheet/createReactStyleObject.js index f0ce3159..e0092e31 100644 --- a/src/apis/StyleSheet/createReactStyleObject.js +++ b/src/apis/StyleSheet/createReactStyleObject.js @@ -1,11 +1,13 @@ import expandStyle from './expandStyle'; import flattenStyle from '../../modules/flattenStyle'; import i18nStyle from './i18nStyle'; +import processBoxShadow from './processBoxShadow'; import processTextShadow from './processTextShadow'; import processTransform from './processTransform'; import processVendorPrefixes from './processVendorPrefixes'; const processors = [ + processBoxShadow, processTextShadow, processTransform, processVendorPrefixes diff --git a/src/apis/StyleSheet/normalizeValue.js b/src/apis/StyleSheet/normalizeValue.js index 889446d4..cb4c2421 100644 --- a/src/apis/StyleSheet/normalizeValue.js +++ b/src/apis/StyleSheet/normalizeValue.js @@ -24,7 +24,9 @@ const unitlessNumbers = { scale: true, scaleX: true, scaleY: true, - scaleZ: true + scaleZ: true, + // RN properties + shadowOpacity: true }; const normalizeValue = (property, value) => { diff --git a/src/apis/StyleSheet/processBoxShadow.js b/src/apis/StyleSheet/processBoxShadow.js new file mode 100644 index 00000000..843121f3 --- /dev/null +++ b/src/apis/StyleSheet/processBoxShadow.js @@ -0,0 +1,33 @@ +import normalizeColor from '../../modules/normalizeColor'; +import normalizeValue from './normalizeValue'; + +const applyOpacity = (color, opacity) => { + const normalizedColor = normalizeColor(color); + const colorNumber = normalizedColor === null ? 0x00000000 : normalizedColor; + const r = (colorNumber & 0xff000000) >>> 24; + const g = (colorNumber & 0x00ff0000) >>> 16; + const b = (colorNumber & 0x0000ff00) >>> 8; + const a = (((colorNumber & 0x000000ff) >>> 0) / 255).toFixed(2); + return `rgba(${r},${g},${b},${a * opacity})`; +}; + +// TODO: add inset and spread support +const processBoxShadow = (style) => { + if (style && style.shadowColor) { + const { height, width } = style.shadowOffset || {}; + const opacity = style.shadowOpacity != null ? style.shadowOpacity : 1; + const color = applyOpacity(style.shadowColor, opacity); + const blurRadius = normalizeValue(null, style.shadowRadius || 0); + const offsetX = normalizeValue(null, height || 0); + const offsetY = normalizeValue(null, width || 0); + const boxShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`; + style.boxShadow = style.boxShadow ? `${style.boxShadow}, ${boxShadow}` : boxShadow; + } + delete style.shadowColor; + delete style.shadowOffset; + delete style.shadowOpacity; + delete style.shadowRadius; + return style; +}; + +module.exports = processBoxShadow; diff --git a/src/components/View/ViewStylePropTypes.js b/src/components/View/ViewStylePropTypes.js index 07677223..5cf1500a 100644 --- a/src/components/View/ViewStylePropTypes.js +++ b/src/components/View/ViewStylePropTypes.js @@ -2,6 +2,7 @@ import BorderPropTypes from '../../propTypes/BorderPropTypes'; import ColorPropType from '../../propTypes/ColorPropType'; import LayoutPropTypes from '../../propTypes/LayoutPropTypes'; import { PropTypes } from 'react'; +import ShadowPropTypes from '../../propTypes/ShadowPropTypes'; import TransformPropTypes from '../../propTypes/TransformPropTypes'; const { number, oneOf, string } = PropTypes; @@ -11,6 +12,7 @@ const hiddenOrVisible = oneOf([ 'hidden', 'visible' ]); module.exports = process.env.NODE_ENV !== 'production' ? { ...BorderPropTypes, ...LayoutPropTypes, + ...ShadowPropTypes, ...TransformPropTypes, backfaceVisibility: hiddenOrVisible, backgroundColor: ColorPropType, diff --git a/src/propTypes/ShadowPropTypes.js b/src/propTypes/ShadowPropTypes.js new file mode 100644 index 00000000..80b4dd1e --- /dev/null +++ b/src/propTypes/ShadowPropTypes.js @@ -0,0 +1,18 @@ +import ColorPropType from './ColorPropType'; +import { PropTypes } from 'react'; + +const { number, oneOfType, shape, string } = PropTypes; +const numberOrString = oneOfType([ number, string ]); + +const ShadowPropTypes = { + shadowColor: ColorPropType, + shadowOffset: shape({ + width: numberOrString, + height: numberOrString + }), + shadowOpacity: number, + shadowRadius: numberOrString, + shadowSpread: numberOrString +}; + +module.exports = ShadowPropTypes;