diff --git a/packages/react-native-web/src/exports/Image/ImageStylePropTypes.js b/packages/react-native-web/src/exports/Image/ImageStylePropTypes.js
index 7a32aa8d..9e219f80 100644
--- a/packages/react-native-web/src/exports/Image/ImageStylePropTypes.js
+++ b/packages/react-native-web/src/exports/Image/ImageStylePropTypes.js
@@ -22,11 +22,11 @@ const ImageStylePropTypes = {
backgroundColor: ColorPropType,
opacity: number,
resizeMode: oneOf(Object.keys(ImageResizeMode)),
+ tintColor: ColorPropType,
/**
* @platform unsupported
*/
overlayColor: string,
- tintColor: ColorPropType,
/**
* @platform web
*/
diff --git a/packages/react-native-web/src/exports/Image/__tests__/__snapshots__/index-test.js.snap b/packages/react-native-web/src/exports/Image/__tests__/__snapshots__/index-test.js.snap
index a4cfd27d..bdd491de 100644
--- a/packages/react-native-web/src/exports/Image/__tests__/__snapshots__/index-test.js.snap
+++ b/packages/react-native-web/src/exports/Image/__tests__/__snapshots__/index-test.js.snap
@@ -1,5 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`components/Image prop "blurRadius" 1`] = `"blur(5px)"`;
+
exports[`components/Image prop "defaultSource" sets background image when value is a string 1`] = `"url(\\"https://google.com/favicon.ico\\")"`;
exports[`components/Image prop "defaultSource" sets background image when value is an object 1`] = `"url(\\"https://google.com/favicon.ico\\")"`;
@@ -14,4 +16,6 @@ exports[`components/Image prop "resizeMode" value "stretch" 1`] = `"100% 100%"`;
exports[`components/Image prop "resizeMode" value "undefined" 1`] = `"cover"`;
-exports[`components/Image prop "style" correctly supports "resizeMode" property 1`] = `"contain"`;
+exports[`components/Image prop "style" supports "resizeMode" property 1`] = `"contain"`;
+
+exports[`components/Image prop "style" supports "shadow" properties (convert to filter) 1`] = `"drop-shadow(1px 1px 0px rgba(255,0,0,1.00))"`;
diff --git a/packages/react-native-web/src/exports/Image/__tests__/index-test.js b/packages/react-native-web/src/exports/Image/__tests__/index-test.js
index c7f81d9b..6477ae75 100644
--- a/packages/react-native-web/src/exports/Image/__tests__/index-test.js
+++ b/packages/react-native-web/src/exports/Image/__tests__/index-test.js
@@ -36,6 +36,12 @@ describe('components/Image', () => {
expect(component.prop('accessible')).toBe(false);
});
+ test('prop "blurRadius"', () => {
+ const defaultSource = { uri: 'https://google.com/favicon.ico' };
+ const component = shallow();
+ expect(findImageSurfaceStyle(component).filter).toMatchSnapshot();
+ });
+
describe('prop "defaultSource"', () => {
test('sets background image when value is an object', () => {
const defaultSource = { uri: 'https://google.com/favicon.ico' };
@@ -207,11 +213,29 @@ describe('components/Image', () => {
});
describe('prop "style"', () => {
- test('correctly supports "resizeMode" property', () => {
+ test('supports "resizeMode" property', () => {
const component = shallow();
expect(findImageSurfaceStyle(component).backgroundSize).toMatchSnapshot();
});
+ test('supports "shadow" properties (convert to filter)', () => {
+ const component = shallow(
+
+ );
+ expect(findImageSurfaceStyle(component).filter).toMatchSnapshot();
+ });
+
+ test('supports "tintcolor" property (convert to filter)', () => {
+ const defaultSource = { uri: 'https://google.com/favicon.ico' };
+ const component = shallow(
+
+ );
+ // filter
+ expect(findImageSurfaceStyle(component).filter).toContain('url(#tint-');
+ // svg
+ expect(component.childAt(2).type()).toBe('svg');
+ });
+
test('removes other unsupported View styles', () => {
const component = shallow();
expect(component.props().style.overlayColor).toBeUndefined();
diff --git a/packages/react-native-web/src/exports/Image/index.js b/packages/react-native-web/src/exports/Image/index.js
index 8e4216b5..77061c42 100644
--- a/packages/react-native-web/src/exports/Image/index.js
+++ b/packages/react-native-web/src/exports/Image/index.js
@@ -11,6 +11,7 @@
import applyNativeMethods from '../../modules/applyNativeMethods';
import createElement from '../createElement';
import { getAssetByID } from '../../modules/AssetRegistry';
+import resolveShadowValue from '../StyleSheet/resolveShadowValue';
import ImageLoader from '../../modules/ImageLoader';
import ImageResizeMode from './ImageResizeMode';
import ImageSourcePropType from './ImageSourcePropType';
@@ -73,6 +74,20 @@ const resolveAssetUri = source => {
return uri;
};
+let filterId = 0;
+
+const createTintColorSVG = (tintColor, id) =>
+ tintColor && id != null ? (
+
+ ) : null;
+
type State = {
shouldDisplaySource: boolean
};
@@ -86,6 +101,7 @@ class Image extends Component<*, State> {
static propTypes = {
...ViewPropTypes,
+ blurRadius: number,
defaultSource: ImageSourcePropType,
draggable: bool,
onError: func,
@@ -98,7 +114,6 @@ class Image extends Component<*, State> {
style: StyleSheetPropType(ImageStylePropTypes),
// compatibility with React Native
/* eslint-disable react/sort-prop-types */
- blurRadius: number,
capInsets: shape({ top: number, left: number, bottom: number, right: number }),
resizeMethod: oneOf(['auto', 'resize', 'scale'])
/* eslint-enable react/sort-prop-types */
@@ -118,6 +133,7 @@ class Image extends Component<*, State> {
static resizeMode = ImageResizeMode;
+ _filterId = 0;
_imageRef = null;
_imageRequestId = null;
_imageState = null;
@@ -130,6 +146,8 @@ class Image extends Component<*, State> {
const shouldDisplaySource = ImageUriCache.has(uri);
this.state = { shouldDisplaySource };
this._imageState = getImageState(uri, shouldDisplaySource);
+ this._filterId = filterId;
+ filterId++;
}
componentDidMount() {
@@ -170,13 +188,13 @@ class Image extends Component<*, State> {
const {
accessibilityLabel,
accessible,
+ blurRadius,
defaultSource,
draggable,
onLayout,
source,
testID,
/* eslint-disable */
- blurRadius,
capInsets,
onError,
onLoad,
@@ -206,10 +224,35 @@ class Image extends Component<*, State> {
const backgroundImage = displayImageUri ? `url("${displayImageUri}")` : null;
const flatStyle = { ...StyleSheet.flatten(this.props.style) };
const finalResizeMode = resizeMode || flatStyle.resizeMode || ImageResizeMode.cover;
- // View doesn't support these styles
+
+ // CSS filters
+ const filters = [];
+ const tintColor = flatStyle.tintColor;
+ if (flatStyle.filter) {
+ filters.push(flatStyle.filter);
+ }
+ if (blurRadius) {
+ filters.push(`blur(${blurRadius}px)`);
+ }
+ if (flatStyle.shadowOffset) {
+ const shadowString = resolveShadowValue(flatStyle);
+ if (shadowString) {
+ filters.push(`drop-shadow(${shadowString})`);
+ }
+ }
+ if (flatStyle.tintColor) {
+ filters.push(`url(#tint-${this._filterId})`);
+ }
+
+ // these styles were converted to filters
+ delete flatStyle.shadowColor;
+ delete flatStyle.shadowOpacity;
+ delete flatStyle.shadowOffset;
+ delete flatStyle.shadowRadius;
+ delete flatStyle.tintColor;
+ // these styles are not supported on View
delete flatStyle.overlayColor;
delete flatStyle.resizeMode;
- delete flatStyle.tintColor;
// Accessibility image allows users to trigger the browser's image context menu
const hiddenImage = displayImageUri
@@ -240,10 +283,12 @@ class Image extends Component<*, State> {
style={[
styles.image,
resizeModeStyles[finalResizeMode],
- backgroundImage && { backgroundImage }
+ backgroundImage && { backgroundImage },
+ filters.length > 0 && { filter: filters.join(' ') }
]}
/>
{hiddenImage}
+ {createTintColorSVG(tintColor, this._filterId)}
);
}
diff --git a/packages/react-native-web/src/exports/StyleSheet/createReactDOMStyle.js b/packages/react-native-web/src/exports/StyleSheet/createReactDOMStyle.js
index a50472f7..399ba6dc 100644
--- a/packages/react-native-web/src/exports/StyleSheet/createReactDOMStyle.js
+++ b/packages/react-native-web/src/exports/StyleSheet/createReactDOMStyle.js
@@ -9,6 +9,7 @@
import normalizeColor from '../../modules/normalizeColor';
import normalizeValue from './normalizeValue';
+import resolveShadowValue from './resolveShadowValue';
/**
* The browser implements the CSS cascade, where the order of properties is a
@@ -82,16 +83,9 @@ const defaultOffset = { height: 0, width: 0 };
* Shadow
*/
-// TODO: add inset and spread support
const resolveShadow = (resolvedStyle, style) => {
- const { boxShadow, shadowColor, shadowOffset, shadowOpacity, shadowRadius } = style;
- const { height, width } = shadowOffset || defaultOffset;
- const offsetX = normalizeValue(null, width);
- const offsetY = normalizeValue(null, height);
- const blurRadius = normalizeValue(null, shadowRadius || 0);
- const color = normalizeColor(shadowColor || 'black', shadowOpacity);
-
- const shadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`;
+ const { boxShadow } = style;
+ const shadow = resolveShadowValue(style);
resolvedStyle.boxShadow = boxShadow ? `${boxShadow}, ${shadow}` : shadow;
};
diff --git a/packages/react-native-web/src/exports/StyleSheet/resolveShadowValue.js b/packages/react-native-web/src/exports/StyleSheet/resolveShadowValue.js
new file mode 100644
index 00000000..2126d597
--- /dev/null
+++ b/packages/react-native-web/src/exports/StyleSheet/resolveShadowValue.js
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2018-present, Nicolas Gallagher.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import normalizeColor from '../../modules/normalizeColor';
+import normalizeValue from './normalizeValue';
+
+const defaultOffset = { height: 0, width: 0 };
+
+const resolveShadowValue = (style: Object) => {
+ const { shadowColor, shadowOffset, shadowOpacity, shadowRadius } = style;
+ const { height, width } = shadowOffset || defaultOffset;
+ const offsetX = normalizeValue(null, width);
+ const offsetY = normalizeValue(null, height);
+ const blurRadius = normalizeValue(null, shadowRadius || 0);
+ const color = normalizeColor(shadowColor || 'black', shadowOpacity);
+ if (color) {
+ return `${offsetX} ${offsetY} ${blurRadius} ${color}`;
+ }
+};
+
+export default resolveShadowValue;
diff --git a/packages/website/storybook/1-components/Image/ImageScreen.js b/packages/website/storybook/1-components/Image/ImageScreen.js
index 8662a814..34efc807 100644
--- a/packages/website/storybook/1-components/Image/ImageScreen.js
+++ b/packages/website/storybook/1-components/Image/ImageScreen.js
@@ -21,6 +21,7 @@ import UIExplorer, {
Description,
DocItem,
Section,
+ StyleList,
storiesOf
} from '../../ui-explorer';
@@ -34,6 +35,12 @@ const ImageScreen = () => (
@@ -168,4 +179,17 @@ const ImageScreen = () => (
);
+const stylePropTypes = [
+ {
+ name: '...View#style'
+ },
+ {
+ name: 'resizeMode'
+ },
+ {
+ name: 'tintColor',
+ typeInfo: 'color'
+ }
+];
+
storiesOf('Components', module).add('Image', ImageScreen);