Skip to content

Support SVG image #1226

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions demo/src/configurations.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ Assets.loadAssetsGroup('images.demo', {
brokenImage: require('./assets/images/placeholderMissingImage.png')
});

Assets.loadAssetsGroup('svgs.demo', {
logo: require('../../uilib-docs/src/images/newDesign/headerLogo.svg').default
});

Typography.loadTypographies({
h1: {...Typography.text40},
h2: {...Typography.text50},
Expand Down
117 changes: 95 additions & 22 deletions demo/src/screens/componentScreens/ImageScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,53 @@
import React, {Component} from 'react';
import {ScrollView} from 'react-native';
import {View, Text, Image, Colors, Assets} from 'react-native-ui-lib';
import {renderBooleanOption, renderRadioGroup, renderSliderOption} from '../ExampleScreenPresenter';


const IMAGE_URL =
'https://images.pexels.com/photos/748837/pexels-photo-748837.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260';
const BROKEN_URL = 'file:///Desktop/website/img/cupcake.jpg';
const DEFAULT_SIZE = 100;

const file = Assets.svgs.demo.logo;
const xml = `
<svg width="32" height="32" viewBox="0 0 32 32">
<path
fill-rule="evenodd"
clip-rule="evenodd"
fill="url(#gradient)"
d="M4 0C1.79086 0 0 1.79086 0 4V28C0 30.2091 1.79086 32 4 32H28C30.2091 32 32 30.2091 32 28V4C32 1.79086 30.2091 0 28 0H4ZM17 6C17 5.44772 17.4477 5 18 5H20C20.5523 5 21 5.44772 21 6V25C21 25.5523 20.5523 26 20 26H18C17.4477 26 17 25.5523 17 25V6ZM12 11C11.4477 11 11 11.4477 11 12V25C11 25.5523 11.4477 26 12 26H14C14.5523 26 15 25.5523 15 25V12C15 11.4477 14.5523 11 14 11H12ZM6 18C5.44772 18 5 18.4477 5 19V25C5 25.5523 5.44772 26 6 26H8C8.55228 26 9 25.5523 9 25V19C9 18.4477 8.55228 18 8 18H6ZM24 14C23.4477 14 23 14.4477 23 15V25C23 25.5523 23.4477 26 24 26H26C26.5523 26 27 25.5523 27 25V15C27 14.4477 26.5523 14 26 14H24Z"
/>
<defs>
<linearGradient
id="gradient"
x1="0"
y1="0"
x2="8.46631"
y2="37.3364"
gradient-units="userSpaceOnUse">
<stop offset="0" stop-color="#FEA267" />
<stop offset="1" stop-color="#E75A4C" />
</linearGradient>
</defs>
</svg>
`;

enum SizeType {
None = '',
Fixed = '50',
Percentage = '50%'
}

class ImageScreen extends Component {
state = {
cover: true,
showOverlayContent: false,
overlayType: 'none',
margin: 0,
showErrorImage: false
showErrorImage: false,
showSvg: false,
isFile: false,
sizeType: SizeType.None
};

renderOverlayContent() {
Expand All @@ -41,34 +74,74 @@ class ImageScreen extends Component {
}
}

render() {
renderImage() {
const {cover, overlayType, margin, showErrorImage} = this.state;
return (
<Image
source={{uri: showErrorImage ? BROKEN_URL : IMAGE_URL}}
errorSource={Assets.images.demo.brokenImage}
cover={cover}
overlayType={overlayType !== 'none' ? overlayType : undefined}
style={!cover && {width: DEFAULT_SIZE, height: DEFAULT_SIZE}}
customOverlayContent={this.renderOverlayContent()}
{...{[`margin-${margin}`]: true}}
/>
);
}

renderSvgImage() {
const {isFile, sizeType} = this.state;
const size: any = Number(sizeType) || sizeType;
return (
<>
{size ? (
<Image source={isFile ? file : xml} width={size} height={size}/>
) : (
<Image source={isFile ? file : xml}/>
)}
</>
);
}

renderOptions() {
return (
<>
{renderBooleanOption.call(this, 'Show as Cover Image', 'cover')}
{renderBooleanOption.call(this, 'Show Overlay Content', 'showOverlayContent')}
{renderBooleanOption.call(this, 'Show Error Image', 'showErrorImage')}
{renderRadioGroup.call(this, 'Overlay Type', 'overlayType', {none: 'none', ...Image.overlayTypes})}
{renderSliderOption.call(this, 'Margin(margin-XX)', 'margin', {step: 4, min: 0, max: 40})}
</>
);
}

renderSvgOptions() {
const {isFile} = this.state;
return (
<>
{renderBooleanOption.call(this, isFile ? 'Load from file' : 'Use xml const', 'isFile')}
{renderRadioGroup.call(this, 'Size Type', 'sizeType', SizeType, {isRow: true})}
</>
);
}

render() {
const {showSvg} = this.state;
return (
<View flex>
<View centerH height={250}>
<Image
source={{uri: showErrorImage ? BROKEN_URL : IMAGE_URL}}
errorSource={Assets.images.demo.brokenImage}
cover={cover}
overlayType={overlayType !== 'none' ? overlayType : undefined}
style={!cover && {width: DEFAULT_SIZE, height: DEFAULT_SIZE}}
customOverlayContent={this.renderOverlayContent()}
{...{[`margin-${margin}`]: true}}
/>
{showSvg ? this.renderSvgImage() : this.renderImage()}
</View>
<View height={2} bg-grey60/>
<Text margin-20 text40>
Image Screen
</Text>
<View useSafeArea flex>
<View padding-20 bottom flex>
<View flex>
{renderBooleanOption.call(this, 'Show as Cover Image', 'cover')}
{renderBooleanOption.call(this, 'Show Overlay Content', 'showOverlayContent')}
{renderBooleanOption.call(this, 'Show Error Image', 'showErrorImage')}
{renderRadioGroup.call(this, 'Overlay Type', 'overlayType', {none: 'none', ...Image.overlayTypes})}
{renderSliderOption.call(this, 'Margin(margin-XX)', 'margin', {step: 4, min: 0, max: 40})}
<ScrollView>
<View padding-20 bottom flex>
{renderBooleanOption.call(this, 'Show SVG', 'showSvg')}
{showSvg ? this.renderSvgOptions() : this.renderOptions()}
</View>
<Text text40>Image Screen</Text>
</View>
</ScrollView>
</View>
</View>
);
Expand Down
9 changes: 9 additions & 0 deletions generatedTypes/components/image/SvgImage.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/// <reference types="react" />
export interface SvgImageProps {
data: any;
}
declare function SvgImage(props: SvgImageProps): JSX.Element | null;
declare namespace SvgImage {
var displayName: string;
}
export default SvgImage;
5 changes: 5 additions & 0 deletions generatedTypes/components/image/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ declare type State = {
* @description: Image wrapper with extra functionality like source transform and assets support
* @extends: Image
* @extendsLink: https://facebook.github.io/react-native/docs/image.html
* @notes: please note that for SVG support you need to add both
* `react-native-svg` and `react-native-svg-transformer`,
* and also configure them (see `metro.config.js`)
*/
declare class Image extends PureComponent<Props, State> {
static displayName: string;
Expand All @@ -81,6 +84,8 @@ declare class Image extends PureComponent<Props, State> {
getVerifiedSource(source?: ImageSourcePropType): any;
getImageSource(): any;
onError: (event: NativeSyntheticEvent<ImageErrorEventData>) => void;
renderSvg: () => JSX.Element;
renderRegularImage(): JSX.Element;
render(): JSX.Element;
}
export { Image };
Expand Down
2 changes: 2 additions & 0 deletions generatedTypes/optionalDependencies/SvgPackage.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
declare let SvgPackage: any;
export default SvgPackage;
1 change: 1 addition & 0 deletions generatedTypes/optionalDependencies/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { default as DateTimePickerPackage } from './DateTimePickerPackage';
export { default as BlurViewPackage } from './BlurViewPackage';
export { default as NetInfoPackage } from './NetInfoPackage';
export { PickerPackage, CommunityPickerPackage } from './PickerPackage';
export { default as SvgPackage } from './SvgPackage';
33 changes: 22 additions & 11 deletions metro.config.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
const {getDefaultConfig} = require('metro-config');

/**
* Metro configuration for React Native
* https://github.com/facebook/react-native
*
* @format
*/

module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
};
module.exports = (async () => {
const {
resolver: {sourceExts, assetExts}
} = await getDefaultConfig();
return {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false
}
}),
babelTransformerPath: require.resolve('react-native-svg-transformer')
},
resolver: {
assetExts: assetExts.filter(ext => ext !== 'svg'),
sourceExts: [...sourceExts, 'svg']
}
};
})();
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@
"react-native-gesture-handler": "1.9.0",
"react-native-keyboard-tracking-view": "^5.6.1",
"react-native-navigation": "7.6.0",
"react-native-svg": "^12.1.0",
"react-native-svg-transformer": "^0.14.3",
"react-native-reanimated": "2.0.0",
"react-test-renderer": "^16.13.1",
"shell-utils": "^1.0.10",
Expand Down
31 changes: 31 additions & 0 deletions src/components/image/SvgImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import {SvgPackage} from '../../optionalDependencies';
const SvgXml = SvgPackage?.SvgXml;
// const SvgProps = SvgPackage?.SvgProps; TODO: not sure how (or if) we can use their props

export interface SvgImageProps {
data: any; // TODO: I thought this should be string | React.ReactNode but it doesn't work properly
}

function SvgImage(props: SvgImageProps) {
const {data, ...others} = props;

if (!SvgXml) {
// eslint-disable-next-line max-len
console.error(`RNUILib Image "svg" prop requires installing "react-native-svg" and "react-native-svg-transformer" dependencies`);
return null;
}

if (typeof data === 'string') {
return <SvgXml xml={data} {...others}/>;
} else if (data) {
const File = data; // Must be with capital letter
return <File {...others}/>;
}

return null;
}

SvgImage.displayName = 'IGNORE';

export default SvgImage;
21 changes: 20 additions & 1 deletion src/components/image/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {asBaseComponent, ForwardRefInjectedProps, BaseComponentInjectedProps, Ma
// @ts-ignore
import Assets from '../../assets';
import Overlay, {OverlayTypeType} from '../overlay';
import SvgImage from './SvgImage';


export type ImageProps = RNImageProps & MarginModifiers & {
Expand Down Expand Up @@ -77,6 +78,9 @@ type State = {
* @description: Image wrapper with extra functionality like source transform and assets support
* @extends: Image
* @extendsLink: https://facebook.github.io/react-native/docs/image.html
* @notes: please note that for SVG support you need to add both
* `react-native-svg` and `react-native-svg-transformer`,
* and also configure them (see `metro.config.js`)
*/
class Image extends PureComponent<Props, State> {
static displayName = 'Image';
Expand Down Expand Up @@ -153,7 +157,12 @@ class Image extends PureComponent<Props, State> {
}
}

render() {
renderSvg = () => {
const {source, ...others} = this.props;
return <SvgImage data={source} {...others}/>;
}

renderRegularImage() {
const {error} = this.state;
const source = error ? this.getVerifiedSource(this.props.errorSource) : this.getImageSource();
const {
Expand Down Expand Up @@ -196,6 +205,16 @@ class Image extends PureComponent<Props, State> {
</ImageView>
);
}

render() {
const {source} = this.props;
const isSvg = typeof source === 'string' || typeof source === 'function';
if (isSvg) {
return this.renderSvg();
} else {
return this.renderRegularImage();
}
}
}

const styles = StyleSheet.create({
Expand Down
7 changes: 7 additions & 0 deletions src/optionalDependencies/SvgPackage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
let SvgPackage: any;
try {
SvgPackage = require('react-native-svg');
// Sadly we cannot verify with require('react-native-svg-transformer');
} catch (error) {}

export default SvgPackage;
1 change: 1 addition & 0 deletions src/optionalDependencies/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export {default as DateTimePickerPackage} from './DateTimePickerPackage';
export {default as BlurViewPackage} from './BlurViewPackage';
export {default as NetInfoPackage} from './NetInfoPackage';
export {PickerPackage, CommunityPickerPackage} from './PickerPackage';
export {default as SvgPackage} from './SvgPackage';