Skip to content

Instantly share code, notes, and snippets.

@Cornally
Last active June 23, 2021 21:36
Show Gist options
  • Save Cornally/8a78f057be9d5992293f8960817ce417 to your computer and use it in GitHub Desktop.
Save Cornally/8a78f057be9d5992293f8960817ce417 to your computer and use it in GitHub Desktop.
ReactJS — Ultimate SVG Icon Workflow. The following will guide you through an SVG icon workflow I've refined for easing the use of icons in small to large React applications. The end result is a tidy component that works in your React 16.8+ application and Storybook 6.
// React icon component
import React, { useMemo } from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import Actions from '~images/icons/actions.inline.svg'
import ArrowUp from '~images/icons/arrow-up.inline.svg'
import ArrowRight from '~images/icons/arrow-right.inline.svg'
import Beaker from '~images/icons/beaker.inline.svg'
import Comment from '~images/icons/comment.inline.svg'
import Delete from '~images/icons/delete.inline.svg'
import Heart from '~images/icons/heart.inline.svg'
import LoaderRings from '~images/icons/loader-rings.inline.svg'
import Padlock from '~images/icons/padlock.inline.svg'
import useStyles from './icon.styles'
const Icon = ({ className, name, rotate, size }) => {
const classes = useStyles()
const getClasses = useMemo(() => classNames(
[classes.icon], {
[className]: !!className
}
), [className, classes.icon])
const getIconContents = name => {
switch (name) {
case 'actions':
return Actions
case 'arrow-right':
return ArrowRight
case 'arrow-up':
return ArrowUp
case 'beaker':
return Beaker
case 'comment':
return Comment
case 'delete':
return Delete
case 'heart':
return Heart
case 'loader-rings':
return LoaderRings
case 'padlock':
return Padlock
default:
return null
}
}
return (
<div
dangerouslySetInnerHTML={{ __html: getIconContents(name) }}
className={getClasses}
style={size || rotate ? {
width: size,
height: size,
transform: `rotate(${rotate}deg)`
} : null}
/>
)
}
Icon.propTypes = {
/** Select icon to display */
name: PropTypes.string.isRequired,
/** Pass a value to rotate the icon */
rotate: PropTypes.number,
/** Set a size in px, em, etc. */
size: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
}
Icon.defaultProps = {
rotate: 0,
size: '1em'
}
export default Icon
// Icon component styles
import { createUseStyles } from 'react-jss'
export default createUseStyles({
icon: {
fill: 'currentColor',
width: '1em',
height: '1em',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
'& svg': {
width: '100%',
height: '100%'
}
}
})
// Customize CRA Configuration
const {
addWebpackAlias,
addWebpackModuleRule,
override
} = require('customize-cra');
const path = require('path')
module.exports = override(
addWebpackModuleRule({
test: /\.inline\.svg$/,
use: 'svg-inline-loader'
}),
addWebpackAlias({
'~components': path.resolve(__dirname, 'src/components'),
'~constants': path.resolve(__dirname, 'src/common/constants/constants'),
'~images': path.resolve(__dirname, 'src/images'),
'~middleware': path.resolve(__dirname, 'src/common/middleware'),
'~mixins': path.resolve(__dirname, 'src/styles/mixins'),
'~styles': path.resolve(__dirname, 'src/styles/styles'),
// Redux aliases — note the unique prefix, '@'
'@': path.resolve(__dirname, 'src/data'),
})
)
// Storybook 6.x Configuration
const path = require('path')
module.exports = {
stories: ['../src/**/*.stories.@(ts|js)'],
addons: [
'@storybook/preset-create-react-app',
'@storybook/addon-actions',
'@storybook/addon-links',
],
webpackFinal: async (config, { configType }) => {
// By default, storybook 5.32/6+ throw all files without a respective loader at `file-loader` in a `oneOf` loop.
// Add the exclusion of inline SVGs (e.g. icon-name.inline.svg) to mimic webpack overridden CRA configuration.
config.module.rules = config.module.rules.map(rule => {
if (rule.oneOf) {
rule.oneOf = rule.oneOf.map(item => {
if (item.loader && item.loader.includes('file-loader')) {
item.exclude.push(/\.inline\.svg$/)
}
return item
})
}
return rule
})
config.module.rules.push({
test: /\.inline\.svg$/,
use: [
{ loader: require.resolve('svg-inline-loader') }
]
});
config.resolve.alias = {
...config.resolve.alias,
'~components': path.resolve(__dirname, '../src/components'),
'~constants': path.resolve(__dirname, '../src/common/constants/constants'),
'~images': path.resolve(__dirname, '../src/images'),
'~middleware': path.resolve(__dirname, '../src/common/middleware'),
'~mixins': path.resolve(__dirname, '../src/styles/mixins'),
'~styles': path.resolve(__dirname, '../src/styles/styles'),
// Redux aliases — note the unique prefix, '@'
'@': path.resolve(__dirname, '../src/data')
}
return config
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment