diff --git a/.gitignore b/.gitignore index 202af42e..2d92a50b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules/ .idea/ svg-min-react/ build/ +dist/ .DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e79648a..7ddd1424 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ _The versioning refers to the React component build._ +#### v3.0.0 (2018-03-14) +* React: remove obsolete @ssr-ready pragma from components. +* React: allow importing individual icons in CommonJS module formats. +* React: substitute React.PureComponent for a JavaScript function, to minimize the bundle size of transpiled CommonJS files as much as possible. +* Build: use template literals to create the React components and centralize the _icon needs offset_ logic in the svg-to-react Grunt task. +* Build: renamed `build/` to `dist/`, which is now part of the gridicons npm package. + #### v2.1.3 (2018-02-22) * Icon added: "Shutter" diff --git a/Gruntfile.js b/Gruntfile.js index 2c352428..4534bae7 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -98,10 +98,14 @@ module.exports = function( grunt ) { ] }, dist: { - files: { - "build/index.js": "build/index.jsx", - "build/example.js": "build/example.jsx" - } + files: [{ + expand: true, + cwd: 'build/', + src: [ '**/*.jsx' ], + dest: 'dist/', + ext: '.js', + filter: 'isFile' + }] } }, diff --git a/README.md b/README.md index 59f07280..5b077adb 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ The [Calypso](https://github.com/Automattic/wp-calypso/) / [WordPress.com](https://wordpress.com) official icon set. -## Using the Gridicon Component in your project: +## Using the Gridicon Component in your project Note that this component requires [react](https://www.npmjs.com/package/react) to be installed in your project. If you don't want to use React, you can simply include the raw `.svg` files from the [`svg-min`](https://github.com/Automattic/gridicons/tree/master/svg-min) folder. @@ -16,6 +16,8 @@ npm install gridicons --save #### Usage +You can either import the whole iconset and decide at run-time which icon to use: + ``` import Gridicon from 'gridicons'; //... @@ -24,9 +26,21 @@ render() { } ``` +Or import icons individually: + +``` +import GridiconAddImage from 'gridicons/dist/add-image'; +//... +render() { + return ; +} +``` + +If you use only a few icons, the recommended way of using the Gridicon library is to import them individually. At the moment of writing this, individual icons are between 1K and 2K, and the file containing the whole iconset sits at 92K. + #### Props -* `icon`: String - the icon name. +* `icon`: String - the icon name. This is ignored when importing icons individually. * `size`: Number - (default: 24) set the size of the icon. * `onClick`: Function - (optional) if you need a click callback. @@ -35,7 +49,6 @@ render() { * The icon set is made to be used exactly at these pixel sizes: 12, 18, 24, 36, 48, 54, 72. * `gridicon-my-sites` as it's a small-size version of the WordPress logo, shouldn't be used larger than 36px. If you need to use the WordPress logo in larger sizes, see the [Social Logos project](https://github.com/Automattic/social-logos). - ## Icon Set Style Guidelines - 24dp base grid @@ -72,7 +85,7 @@ Note that the icons in this set are tied to be used in [Calypso](https://github. 1. Switch to the branch (i.e. Pull Request) with the new icon. 2. Review the SVG source of the new icons to make sure they are clean and readable. 3. Check pixel sharpness: open in Illustrator (with "Pixel Preview") or Sketch (with "Show Pixels"), adjust if needed. -4. Run `grunt` command from terminal. It will generate `svg-min`, React (`build`), `svg-sprite`, `pdf`, `php`, and `docs`. +4. Run `grunt` command from terminal. It will generate `svg-min`, `build`, `dist`, `svg-sprite`, `pdf`, `php`, and `docs`. 5. Commit 6. Merge & delete branch @@ -81,19 +94,19 @@ Note that the icons in this set are tied to be used in [Calypso](https://github. This icon set uses a few automation scripts to ease the generation of new icons in a reliable way. In short, we require `node` and `grunt`. For detailed instructions check [the installation page](https://github.com/Automattic/gridicons/wiki/Installation). -Once you checkout the repo run `npm install` in the `gridicons` folder. -To generate all the fonts, svgs and so on you run `npm run build` +Once you checkout the repo run `npm install` in the `gridicons` folder. +To generate all the fonts, svgs and so on you run `npm run build` ## Publishing to npm Note: to proceed with this you need to have write authorization to npm. -1. Create a new PR with updated `CHANGELOG.md` and updated version in `package.json` (i.e. `1.2.3-alpha.1`), see an example [here](https://github.com/Automattic/gridicons/pull/275). -2. In the "CHANGELOG.md" make sure to check all the previous commits since the previous versioned release. +1. Create a new PR with updated `CHANGELOG.md` and updated version in `package.json` (i.e. `1.2.3-alpha.1`), see an example [here](https://github.com/Automattic/gridicons/pull/275). +2. In the "CHANGELOG.md" make sure to check all the previous commits since the previous versioned release. 3. Pre-publish that PR branch on npm with `npm publish --tag next` ([more info](https://docs.npmjs.com/cli/dist-tag)). Running the `npm publish --tag next` command will send the data that you have localy to npm. Having the alpha version in the `package.json` file will create a newly tagged version npm package. Use `npm view gridicons` to look at the list of current tags. You do not need to commit changes to github in order to publish to npm, but it is recommended so folks testing know what's available. -4. Create a new update PR in a repository that makes use of Gridicons and run `npm install gridicons@next --save` (which will update `packages.json`). If you're creating the PR in [Calypso](https://github.com/Automattic/wp-calypso) and you get warnings, it might need to regenerate the shrinkwrap, in which case run `npm run update-deps`. See an example [here](https://github.com/Automattic/wp-calypso/pull/17601). +4. Create a new update PR in a repository that makes use of Gridicons and run `npm install gridicons@next --save` (which will update `packages.json`). If you're creating the PR in [Calypso](https://github.com/Automattic/wp-calypso) and you get warnings, it might need to regenerate the shrinkwrap, in which case run `npm run update-deps`. See an example [here](https://github.com/Automattic/wp-calypso/pull/17601). 5. Test if the new icons show up, and there are no regressions in the previous icons. Take a look at the `http://calypso.localhost:3000/devdocs/design/gridicons` for example. -6. If changes look good, remove the alpha postfix in the version (i.e. `1.2.3-alpha.1` to `1.2.3`) on both repositories PRs. +6. If changes look good, remove the alpha postfix in the version (i.e. `1.2.3-alpha.1` to `1.2.3`) on both repositories PRs. 7. Merge the Gridicons PR. 8. Tag the release on GitHub: `git tag -a v1.2.3 -m "Release v1.2.3"` (and push `git push origin v1.2.3`). 9. Check if it shows up in the [Releases list](https://github.com/Automattic/gridicons/releases). @@ -106,11 +119,11 @@ Gridicons is licensed under [GNU General Public License v2 (or later)](./LICENSE ## More notes on publishing to npm You need to have a npm user account. [Create one here](https://www.npmjs.com/signup). -Once you have created it, set up the account on you machine via +Once you have created it, set up the account on you machine via $ `npm adduser` -Setup the 2fa with npm -$ `npm profile enable-2fa` +Setup the 2fa with npm +$ `npm profile enable-2fa` -Now everytime before you can publish +Now everytime before you can publish You will be asked for a your [2FA code (OPT)](https://en.wikipedia.org/wiki/One-time_password) diff --git a/grunt-tasks/svg-to-react.js b/grunt-tasks/svg-to-react.js index 70d5b847..ba29adf2 100644 --- a/grunt-tasks/svg-to-react.js +++ b/grunt-tasks/svg-to-react.js @@ -3,19 +3,25 @@ module.exports = function( grunt ) { grunt.registerMultiTask( 'svg-to-react', 'Output a react component for SVGs', function() { - var component = ''; - var componentExample = ''; - var filesDest; + let components = ''; + let componentsExample = ''; + let filesDest; + const prepareIndividualIcon = require( '../sources/react/template-individual-icon' ); + const prepareAllIcons = require( '../sources/react/template-all-icons' ); + const prepareDevDocsExample = require( '../sources/react/template-devdocs-example' ); + const { + iconsThatNeedOffset, + iconsThatNeedOffsetX, + iconsThatNeedOffsetY + } = require( '../sources/react/icons-offset' ); - // Create a switch() case for each svg file this.files.forEach( function( files ) { files.src.forEach( function( svgFile ) { - // Clean up the filename to use for the react components - var name = svgFile.split( '.' ); - name = name[0]; + // Name to be used by the react components + let name = svgFile.split( '.' )[ 0 ]; // Grab the relevant bits from the file contents - var fileContent = grunt.file.read( files.cwd + svgFile ); + let fileContent = grunt.file.read( files.cwd + svgFile ); // Add className, height, and width to the svg element fileContent = fileContent.slice( 0, 4 ) + @@ -23,32 +29,38 @@ module.exports = function( grunt ) { fileContent.slice( 4, -6 ) + fileContent.slice( -6 ); - // Output the case for each icon - component += " case '" + name + "':\n" + + // Holds the switch's cases for every icon + components += " case '" + name + "':\n" + " svg = " + fileContent + ";\n" + - " break;\n";; + " break;\n"; - // Example Document + // Holds the Example Document name = name.replace( 'gridicons-', '' ); - componentExample += ' \n'; + componentsExample += ' \n'; + // Prepare and write to disk every individual icon separately + grunt.file.write( files.dest + name + '.jsx', prepareIndividualIcon( { + name, + component: fileContent, + iconsThatNeedOffset, + iconsThatNeedOffsetX, + iconsThatNeedOffsetY, + } ) ); } ); filesDest = files.dest; } ); - // React Component Wrapping - component = grunt.file.read( 'sources/react/index-header.jsx' ) + component; - component += grunt.file.read( 'sources/react/index-footer.jsx' ); + // Prepare and write to disk the Design Docs Example component + grunt.file.write( filesDest + 'example.jsx', prepareDevDocsExample( componentsExample ) ); - // Design Docs Wrapping - componentExample = grunt.file.read( 'sources/react/example-header.jsx' ) + componentExample; - componentExample += grunt.file.read( 'sources/react/example-footer.jsx' ); - - - // Write the React component to gridicon/index.jsx - grunt.file.write( filesDest + 'index.jsx', component ); - grunt.file.write( filesDest + 'example.jsx', componentExample ); + // Prepare and write to disk the Gridicon React component + grunt.file.write( filesDest + 'index.jsx', prepareAllIcons( { + components, + iconsThatNeedOffset, + iconsThatNeedOffsetX, + iconsThatNeedOffsetY, + } ) ); } ); }; diff --git a/package.json b/package.json index 302daa0d..588e52cc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,10 @@ { "name": "gridicons", - "version": "2.1.3", - "main": "build/index.js", + "version": "3.0.0", + "main": "dist/index.js", + "files": [ + "dist/" + ], "scripts": { "build": "grunt --verbose", "prepublish": "npm run build" @@ -14,12 +17,8 @@ "url": "https://github.com/Automattic/gridicons" }, "license": "GPL-2.0+", - "files": [ - "build/index.js", - "build/example.js" - ], "dependencies": { - "prop-types": "^15.5.7" + "prop-types": "^15.5.7" }, "devDependencies": { "babel-plugin-add-module-exports": "^0.2.1", diff --git a/sources/react/example-footer.jsx b/sources/react/example-footer.jsx deleted file mode 100644 index d9ffa026..00000000 --- a/sources/react/example-footer.jsx +++ /dev/null @@ -1,4 +0,0 @@ - - ); - } -} diff --git a/sources/react/icons-offset.js b/sources/react/icons-offset.js new file mode 100644 index 00000000..00a9182f --- /dev/null +++ b/sources/react/icons-offset.js @@ -0,0 +1,87 @@ +const iconsThatNeedOffset = [ + 'gridicons-add-outline', + 'gridicons-add', + 'gridicons-align-image-center', + 'gridicons-align-image-left', + 'gridicons-align-image-none', + 'gridicons-align-image-right', + 'gridicons-attachment', + 'gridicons-bold', + 'gridicons-bookmark-outline', + 'gridicons-bookmark', + 'gridicons-calendar', + 'gridicons-cart', + 'gridicons-create', + 'gridicons-custom-post-type', + 'gridicons-external', + 'gridicons-folder', + 'gridicons-heading', + 'gridicons-help-outline', + 'gridicons-help', + 'gridicons-history', + 'gridicons-info-outline', + 'gridicons-info', + 'gridicons-italic', + 'gridicons-layout-blocks', + 'gridicons-link-break', + 'gridicons-link', + 'gridicons-list-checkmark', + 'gridicons-list-ordered', + 'gridicons-list-unordered', + 'gridicons-menus', + 'gridicons-minus', + 'gridicons-my-sites', + 'gridicons-notice-outline', + 'gridicons-notice', + 'gridicons-plus-small', + 'gridicons-plus', + 'gridicons-popout', + 'gridicons-posts', + 'gridicons-scheduled', + 'gridicons-share-ios', + 'gridicons-star-outline', + 'gridicons-star', + 'gridicons-stats', + 'gridicons-status', + 'gridicons-thumbs-up', + 'gridicons-textcolor', + 'gridicons-time', + 'gridicons-trophy', + 'gridicons-user-circle', + 'gridicons-reader-follow', + 'gridicons-reader-following' +]; +const iconsThatNeedOffsetY = [ + 'gridicons-align-center', + 'gridicons-align-justify', + 'gridicons-align-left', + 'gridicons-align-right', + 'gridicons-arrow-left', + 'gridicons-arrow-right', + 'gridicons-house', + 'gridicons-indent-left', + 'gridicons-indent-right', + 'gridicons-minus-small', + 'gridicons-print', + 'gridicons-sign-out', + 'gridicons-stats-alt', + 'gridicons-trash', + 'gridicons-underline', + 'gridicons-video-camera' +]; +const iconsThatNeedOffsetX = [ + 'gridicons-arrow-down', + 'gridicons-arrow-up', + 'gridicons-comment', + 'gridicons-clear-formatting', + 'gridicons-flag', + 'gridicons-menu', + 'gridicons-reader', + 'gridicons-strikethrough' +]; + +module.exports = { + iconsThatNeedOffset, + iconsThatNeedOffsetX, + iconsThatNeedOffsetY, +}; diff --git a/sources/react/index-footer.jsx b/sources/react/index-footer.jsx deleted file mode 100644 index 2bd5cb66..00000000 --- a/sources/react/index-footer.jsx +++ /dev/null @@ -1,5 +0,0 @@ - } - - return ( svg ); - } -} diff --git a/sources/react/index-header.jsx b/sources/react/index-header.jsx deleted file mode 100644 index ad91e72e..00000000 --- a/sources/react/index-header.jsx +++ /dev/null @@ -1,155 +0,0 @@ -/** @ssr-ready **/ - -/* !!! -IF YOU ARE EDITING gridicon/index.jsx -THEN YOU ARE EDITING A FILE THAT GETS OUTPUT FROM THE GRIDICONS REPO! -DO NOT EDIT THAT FILE! EDIT index-header.jsx and index-footer.jsx instead -OR if you're looking to change now SVGs get output, you'll need to edit strings in the Gruntfile :) -!!! */ - -/** - * External dependencies - */ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; - -export default class Gridicon extends PureComponent { - - static defaultProps = { - size: 24 - }; - - static propTypes = { - icon: PropTypes.string.isRequired, - size: PropTypes.number, - onClick: PropTypes.func, - className: PropTypes.string - }; - - needsOffset( icon, size ) { - const iconNeedsOffset = [ - 'gridicons-add-outline', - 'gridicons-add', - 'gridicons-align-image-center', - 'gridicons-align-image-left', - 'gridicons-align-image-none', - 'gridicons-align-image-right', - 'gridicons-attachment', - 'gridicons-bold', - 'gridicons-bookmark-outline', - 'gridicons-bookmark', - 'gridicons-calendar', - 'gridicons-cart', - 'gridicons-create', - 'gridicons-custom-post-type', - 'gridicons-external', - 'gridicons-folder', - 'gridicons-heading', - 'gridicons-help-outline', - 'gridicons-help', - 'gridicons-history', - 'gridicons-info-outline', - 'gridicons-info', - 'gridicons-italic', - 'gridicons-layout-blocks', - 'gridicons-link-break', - 'gridicons-link', - 'gridicons-list-checkmark', - 'gridicons-list-ordered', - 'gridicons-list-unordered', - 'gridicons-menus', - 'gridicons-minus', - 'gridicons-my-sites', - 'gridicons-notice-outline', - 'gridicons-notice', - 'gridicons-plus-small', - 'gridicons-plus', - 'gridicons-popout', - 'gridicons-posts', - 'gridicons-scheduled', - 'gridicons-share-ios', - 'gridicons-star-outline', - 'gridicons-star', - 'gridicons-stats', - 'gridicons-status', - 'gridicons-thumbs-up', - 'gridicons-textcolor', - 'gridicons-time', - 'gridicons-trophy', - 'gridicons-user-circle', - 'gridicons-reader-follow', - 'gridicons-reader-following' - ]; - - if ( iconNeedsOffset.indexOf( icon ) >= 0 ) { - return ( size % 18 === 0 ); - } - return false; - } - - needsOffsetX( icon, size ) { - const iconNeedsOffsetX = [ - 'gridicons-arrow-down', - 'gridicons-arrow-up', - 'gridicons-comment', - 'gridicons-clear-formatting', - 'gridicons-flag', - 'gridicons-menu', - 'gridicons-reader', - 'gridicons-strikethrough' - ]; - - if ( iconNeedsOffsetX.indexOf( icon ) >= 0 ) { - return ( size % 18 === 0 ); - } - return false; - } - - needsOffsetY( icon, size ) { - const iconNeedsOffsetY = [ - 'gridicons-align-center', - 'gridicons-align-justify', - 'gridicons-align-left', - 'gridicons-align-right', - 'gridicons-arrow-left', - 'gridicons-arrow-right', - 'gridicons-house', - 'gridicons-indent-left', - 'gridicons-indent-right', - 'gridicons-minus-small', - 'gridicons-print', - 'gridicons-sign-out', - 'gridicons-stats-alt', - 'gridicons-trash', - 'gridicons-underline', - 'gridicons-video-camera' - ]; - - if ( iconNeedsOffsetY.indexOf( icon ) >= 0 ) { - return ( size % 18 === 0 ); - } - return false; - } - - render() { - const { size, onClick, icon: iconProp, className, ...otherProps } = this.props; - const icon = 'gridicons-' + iconProp; - const needsOffset = this.needsOffset( icon, size ); - const needsOffsetX = this.needsOffsetX( icon, size ); - const needsOffsetY = this.needsOffsetY( icon, size ); - - let svg; - - const iconClass = [ - 'gridicon', - icon, - className, - needsOffset ? 'needs-offset' : false, - needsOffsetX ? 'needs-offset-x' : false, - needsOffsetY ? 'needs-offset-y' : false, - ].filter( Boolean ).join( ' ' ); - - switch ( icon ) { - default: - svg = ; - break; diff --git a/sources/react/template-all-icons.js b/sources/react/template-all-icons.js new file mode 100644 index 00000000..ccaa7783 --- /dev/null +++ b/sources/react/template-all-icons.js @@ -0,0 +1,53 @@ +const toString = array => array.reduce( ( acc, item ) => ( acc = acc + "'" + item + "', " ), '' ); + +const prepareAllIcons = ( { + components, + iconsThatNeedOffset, + iconsThatNeedOffsetX, + iconsThatNeedOffsetY, +} ) => ` +/** + * External dependencies + */ +import React from 'react'; + +export default function( { icon: iconProp, className, onClick, size = 24, ...otherProps } ) { + + const iconsThatNeedOffset = [ ${ toString( iconsThatNeedOffset ) } ]; + + const iconsThatNeedOffsetX = [ ${ toString( iconsThatNeedOffsetX ) } ]; + + const iconsThatNeedOffsetY = [ ${ toString( iconsThatNeedOffsetY ) } ]; + + const doesItNeedOffset = ( name, icons ) => icons.indexOf( name ) >= 0; + const isModulo18 = size => size % 18 === 0; + + const icon = 'gridicons-' + iconProp; + const needsOffset = doesItNeedOffset( icon, iconsThatNeedOffset ) && isModulo18( size ); + const needsOffsetX = doesItNeedOffset( icon, iconsThatNeedOffsetX ) && isModulo18( size ); + const needsOffsetY = doesItNeedOffset( icon, iconsThatNeedOffsetY ) && isModulo18( size ); + + let svg; + const iconClass = [ + 'gridicon', + icon, + className, + needsOffset ? 'needs-offset' : false, + needsOffsetX ? 'needs-offset-x' : false, + needsOffsetY ? 'needs-offset-y' : false, + ].filter( Boolean ).join( ' ' ); + + switch ( icon ) { + default: + svg = ; + break; + + ${ components } + + } + + return ( svg ); +} +`; + +module.exports = prepareAllIcons; diff --git a/sources/react/example-header.jsx b/sources/react/template-devdocs-example.js similarity index 73% rename from sources/react/example-header.jsx rename to sources/react/template-devdocs-example.js index d976fb37..96b2573e 100644 --- a/sources/react/example-header.jsx +++ b/sources/react/template-devdocs-example.js @@ -1,4 +1,6 @@ +const prepareDevDocsExample = components => ` /* eslint-disable no-alert */ + /** * External dependencies */ @@ -7,7 +9,7 @@ import React, { PureComponent } from 'react'; /** * Internal dependencies */ -import Gridicon from './index.js'; +import Gridicon from './index'; export default class Gridicons extends PureComponent { static displayName = 'Gridicons'; // Don't remove, needed for Calypso devdocs! @@ -20,3 +22,11 @@ export default class Gridicons extends PureComponent { render() { return (
+ ${components} +
+ ); + } +} +`; + +module.exports = prepareDevDocsExample; diff --git a/sources/react/template-individual-icon.js b/sources/react/template-individual-icon.js new file mode 100644 index 00000000..6f6c1149 --- /dev/null +++ b/sources/react/template-individual-icon.js @@ -0,0 +1,46 @@ +/** @format */ + +const needsOffset = ( name, icons ) => icons.indexOf( 'gridicons-' + name ) >= 0; + +const prepareIndividualIcon = ( { + name, + component, + iconsThatNeedOffset, + iconsThatNeedOffsetX, + iconsThatNeedOffsetY, +} ) => ` +/** + * External dependencies + */ +import React from 'react'; + +// We don't want the icon prop in to otherProps, as we'll pass those to the svg component. +// That's why we destructure it from the props but not use it. +export default function( { size = 24, onClick, icon, className, ...otherProps } ) { + const isModulo18 = size => size % 18 === 0; + const iconClass = [ + 'gridicon', + 'gridicons-${ name }', + className, + ${ + needsOffset( name, iconsThatNeedOffset ) + ? "isModulo18( size ) ? 'needs-offset' : false" + : false + } , + ${ + needsOffset( name, iconsThatNeedOffsetX ) + ? "isModulo18( size ) ? 'needs-offset-x' : false" + : false + }, + ${ + needsOffset( name, iconsThatNeedOffsetY ) + ? "isModulo18( size ) ? 'needs-offset-y' : false" + : false + }, + ].filter( Boolean ).join( ' ' ); + + return ( ${ component } ); +} +`; + +module.exports = prepareIndividualIcon;