@atlaskit/icon
Version:
A React package that contains icons
126 lines (114 loc) • 3.82 kB
Flow
// @flow
import React, { Component, type Node } from 'react';
import styled from 'styled-components';
import uuid from 'uuid';
import { colors } from '@atlaskit/theme';
import { sizes } from '../constants';
const getSize = props => {
if (props.size) {
return `height: ${sizes[props.size]}; width: ${sizes[props.size]};`;
}
return null;
};
export const IconWrapper = styled.span`
${getSize} color: ${p => p.primaryColor || 'currentColor'};
display: inline-block;
fill: ${p => p.secondaryColor || colors.background};
flex-shrink: 0;
line-height: 1;
> svg {
${getSize} max-height: 100%;
max-width: 100%;
overflow: hidden;
pointer-events: none;
vertical-align: bottom;
}
/* Stop-color doesn't properly apply in chrome when the inherited/current color changes.
* We have to initially set stop-color to inherit (either via DOM attribute or an initial CSS
* rule) and then override it with currentColor for the color changes to be picked up.
*/
stop {
stop-color: currentColor;
}
`;
type Props = {
/** Glyph to show by Icon component (not required when you import a glyph directly) */
glyph?: (props?: {}) => Node,
/** More performant than the glyph prop, but potentially dangerous if the SVG string hasn't
been "sanitised" */
dangerouslySetGlyph?: string,
/** String to use as the aria-label for the icon. Set to an empty string if you are rendering the icon with visible text to prevent accessibility label duplication. */
label: string,
/** onClick handler for the icon element */
onClick?: () => mixed,
/** For primary colour for icons */
primaryColor?: string,
/** For secondary colour for 2-color icons. Set to inherit to control this via "fill" in CSS */
secondaryColor?: string,
/** Control the size of the icon */
size?: 'small' | 'medium' | 'large' | 'xlarge',
};
export default class Icon extends Component<Props, {}> {
static defaultProps = {
onClick: () => {},
};
/* Icons need unique gradient IDs across instances for different gradient definitions to work
* correctly.
* A step in the icon build process replaces linear gradient IDs and their references in paths
* to a placeholder string so we can replace them with a dynamic ID here.
* Replacing the original IDs with placeholders in the build process is more robust than not
* using placeholders as we do not have to rely on regular expressions to find specific element
* to replace.
*/
static insertDynamicGradientID(svgStr: string) {
const id = uuid();
const replacedSvgStr = svgStr
.replace(/id="([^"]+)-idPlaceholder"/g, `id=$1-${id}`)
.replace(
/fill="url\(#([^"]+)-idPlaceholder\)"/g,
`fill="url(#$1-${id})"`,
);
return replacedSvgStr;
}
render() {
const {
glyph: Glyph,
dangerouslySetGlyph,
onClick,
primaryColor,
secondaryColor,
size,
} = this.props;
// handling the glyphs as strings
if (dangerouslySetGlyph) {
return (
<IconWrapper
onClick={onClick}
primaryColor={primaryColor}
secondaryColor={secondaryColor}
size={size}
aria-label={this.props.label}
dangerouslySetInnerHTML={{
__html: Icon.insertDynamicGradientID(dangerouslySetGlyph),
}}
/>
);
}
// handling the glyphs when passed through as functions
return (
<IconWrapper
onClick={onClick}
primaryColor={primaryColor}
secondaryColor={secondaryColor}
size={size}
aria-label={this.props.label}
>
{Glyph ? <Glyph role="presentation" /> : null}
</IconWrapper>
);
}
}
export const size = Object.keys(sizes).reduce(
(p, c) => Object.assign(p, { [c]: c }),
{},
);