@salesforce/design-system-react
Version:
Salesforce Lightning Design System for React
200 lines (178 loc) • 5.3 kB
JSX
/* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */
/* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */
// # Illustration Component
// Based on SLDS v2.6.2
// ## Dependencies
// ### React
import React from 'react';
import PropTypes from 'prop-types';
// This component's `checkProps` which issues warnings to developers about properties
// when in development mode (similar to React's built in development tools)
import checkProps from './check-props';
// ### classNames
// [github.com/JedWatson/classnames](https://github.com/JedWatson/classnames)
// A simple javascript utility for conditionally joining classNames together.
import classNames from '../../utilities/class-names';
// ## SVG
import Svg from '../utilities/utility-icon/svg';
// ## Constants
import { ILLUSTRATION } from '../../utilities/constants';
import componentDoc from './component.json';
const sanitizePath = (path) => {
if (!path || typeof path !== 'string') {
return undefined;
}
// Remove control characters, null bytes, and normalize whitespace
const normalizedPath = path
.replace(/[\x00-\x1f\x7f]/g, '') // eslint-disable-line no-control-regex
.trim()
.toLowerCase();
const dangerousProtocols = [
'javascript:', // eslint-disable-line no-script-url
'data:',
'vbscript:',
'file:',
'blob:',
];
const isDangerous = dangerousProtocols.some((protocol) =>
normalizedPath.startsWith(protocol)
);
if (isDangerous) {
// eslint-disable-next-line no-console
console.log(
`Illustration: Blocked potentially unsafe path "${path}". Only http, https, relative paths, and fragment identifiers are allowed.`
);
return undefined;
}
return path;
};
/**
* An illustration is an image and inline text that work in tandem to communicate a state in a more friendly way.
*/
const Illustration = ({
className,
illustration,
heading,
messageBody,
name,
path,
internalIllustration = true,
size = 'small',
style = {},
...rest
}) => {
checkProps(
'Illustration',
{
className,
illustration,
heading,
messageBody,
name,
path,
size,
style,
internalIllustration,
...rest,
},
componentDoc
);
const kababCaseName = name ? name.replace(/_| /g, '-').toLowerCase() : '';
const styles = { ...style };
let illustrationSvg;
// large illustration svg should have a default height of 400px if not already specified
if (size === 'large' && !styles.height) {
styles.height = '400px';
}
if (illustration) {
// Use SVG data passed in with `illustration` prop
illustrationSvg = (
<Svg
className="slds-illustration__svg"
aria-hidden="true"
data={illustration}
name={kababCaseName}
style={styles}
/>
);
} else if (path) {
const safePath = sanitizePath(path);
if (safePath) {
illustrationSvg = (
<svg
className="slds-illustration__svg"
aria-hidden="true"
name={kababCaseName}
style={styles}
>
<use xlinkHref={safePath} />
</svg>
);
}
}
return (
<div
className={classNames(className, 'slds-illustration', {
'slds-illustration_small': size === 'small',
'slds-illustration_large': size === 'large',
})}
>
{illustrationSvg}
<div className="slds-text-longform">
{heading ? (
<h3 className="slds-text-heading_medium">{heading}</h3>
) : null}
{messageBody ? (
<p className="slds-text-body_regular">{messageBody}</p>
) : null}
</div>
</div>
);
};
// ### Display Name
// Always use the canonical component name as the React display name.
Illustration.displayName = ILLUSTRATION;
// ### Prop Types
Illustration.propTypes = {
/**
* CSS classes that are applied to the SVG. _Tested with Mocha testing._
*/
className: PropTypes.oneOfType([
PropTypes.array,
PropTypes.object,
PropTypes.string,
]),
/**
* A heading text. It is required if illustration is present. _Tested with snapshot testing._ _Tested with Mocha testing._
*/
heading: PropTypes.string,
/**
* A custom SVG object to use instead of the supplied SLDS illustrations, look in `design-system-react/icons` for examples and syntax. _Tested with snapshot testing._ _Tested with Mocha testing._
*/
illustration: PropTypes.object,
/**
* Indicates whether the illustration SVGs are from the design-system-react repo. If yes, set to true.
*/
internalIllustration: PropTypes.bool,
/**
* A message body below the heading to further communicate the state of the component. _Tested with snapshot testing._ _Tested with Mocha testing._
*/
messageBody: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
/**
* Name of the illustration. Visit <a href='https://lightningdesignsystem.com/components/illustration/'>Lightning Design System Illustration</a> to reference illustration names. _Tested with snapshot testing._ _Tested with Mocha testing._
*/
name: PropTypes.string,
/**
* Path to the illustration SVG image. _Tested with snapshot testing._
*/
path: PropTypes.string,
/**
* Size of the illustration. _Tested with snapshot testing._ _Tested with Mocha testing._
*/
size: PropTypes.oneOf(['small', 'large']),
/**
* Custom styles to be passed to the illustration SVG. _Tested with Mocha testing._
*/
style: PropTypes.object,
};
export default Illustration;