diff --git a/assets/stylesheets/_vendor.scss b/assets/stylesheets/_vendor.scss index 44276c9e566bbd..b2b0a3a722ed1e 100644 --- a/assets/stylesheets/_vendor.scss +++ b/assets/stylesheets/_vendor.scss @@ -5,15 +5,18 @@ .gridicon { fill: currentColor; - &.needs-offset g { + &.needs-offset > use:first-child, + &.needs-offset > g:first-child { transform: translate( 1px, 1px ); /* translates to .5px because it's in a child element */ } - &.needs-offset-x g { + &.needs-offset-x > use:first-child, + &.needs-offset-x > g:first-child { transform: translate( 1px, 0 ); /* only nudges horizontally */ } - &.needs-offset-y g { + &.needs-offset-y > use:first-child, + &.needs-offset-y > g:first-child { transform: translate( 0, 1px ); /* only nudges vertically */ } } diff --git a/client/blocks/inline-help/style.scss b/client/blocks/inline-help/style.scss index 5e471dc7e39993..29ebeb2a752be8 100644 --- a/client/blocks/inline-help/style.scss +++ b/client/blocks/inline-help/style.scss @@ -74,7 +74,8 @@ height: 36px; width: 36px; - g { + > use:first-child, + > g:first-child { transform: none; } } diff --git a/client/components/async-gridicons/fallback.jsx b/client/components/async-gridicons/fallback.jsx deleted file mode 100644 index 935e1845eb3aa8..00000000000000 --- a/client/components/async-gridicons/fallback.jsx +++ /dev/null @@ -1,23 +0,0 @@ -/** @format */ - -/** - * External dependencies - */ -import React from 'react'; - -/** - * Internal dependencies - */ - -export default function FallbackIcon() { - /* eslint-disable wpcalypso/jsx-classname-namespace */ - return ( - - ); -} diff --git a/client/components/async-gridicons/index.jsx b/client/components/async-gridicons/index.jsx deleted file mode 100644 index 1331189d64e4e1..00000000000000 --- a/client/components/async-gridicons/index.jsx +++ /dev/null @@ -1,65 +0,0 @@ -/** @format */ - -/** - * External dependencies - */ -import React, { Component } from 'react'; - -/** - * Internal dependencies - */ -import FallbackIcon from './fallback'; - -const loadedIcons = new Map(); - -function loadIcon( icon ) { - return import( - /* webpackChunkName: "gridicons", webpackInclude: /\.js$/, webpackExclude: /dist\/(index\.js|example\.js)$/, webpackMode: "lazy-once" */ - `gridicons/dist/${ icon }` - ).then( - g => { - loadedIcons.set( icon, g.default ); - return g.default; - }, - err => { - loadedIcons.set( icon, false ); - console.warn( `Error loading icon '${ icon }':`, err.message ); // eslint-disable-line no-console - } - ); -} - -class AsyncGridicon extends Component { - constructor( props ) { - super( props ); - } - checkAndLoad() { - if ( this.props.icon && ! loadedIcons.has( this.props.icon ) ) { - loadIcon( this.props.icon ).then( () => this.update() ); - } - } - - componentDidMount() { - this.checkAndLoad(); - } - componentDidUpdate() { - this.checkAndLoad(); - } - - update = () => this.forceUpdate(); - - componentWillUnmount() { - this.update = () => {}; - } - - render() { - const { icon = '', ...rest } = this.props; - if ( loadedIcons.get( icon ) ) { - const Icon = loadedIcons.get( icon ); - return ; - } - - return ; - } -} - -export default AsyncGridicon; diff --git a/client/components/gridicon/docs/example.jsx b/client/components/gridicon/docs/example.jsx new file mode 100644 index 00000000000000..641def0611099f --- /dev/null +++ b/client/components/gridicon/docs/example.jsx @@ -0,0 +1,233 @@ +/** + * External dependencies + */ + +import React from 'react'; + +/** + * Internal dependencies + */ +import Gridicon from '../index'; + +const icons = [ + 'add-image', + 'add-outline', + 'add', + 'align-center', + 'align-image-center', + 'align-image-left', + 'align-image-none', + 'align-image-right', + 'align-justify', + 'align-left', + 'align-right', + 'arrow-down', + 'arrow-left', + 'arrow-right', + 'arrow-up', + 'aside', + 'attachment', + 'audio', + 'bell', + 'block', + 'bold', + 'book', + 'bookmark-outline', + 'bookmark', + 'briefcase', + 'bug', + 'calendar', + 'camera', + 'caption', + 'cart', + 'chat', + 'checkmark-circle', + 'checkmark', + 'chevron-down', + 'chevron-left', + 'chevron-right', + 'chevron-up', + 'clear-formatting', + 'clipboard', + 'cloud-download', + 'cloud-outline', + 'cloud-upload', + 'cloud', + 'code', + 'cog', + 'comment', + 'computer', + 'coupon', + 'create', + 'credit-card', + 'crop', + 'cross-circle', + 'cross-small', + 'cross', + 'custom-post-type', + 'customize', + 'domains', + 'dropdown', + 'ellipsis-circle', + 'ellipsis', + 'external', + 'filter', + 'flag', + 'flip-horizontal', + 'flip-vertical', + 'folder-multiple', + 'folder', + 'fullscreen-exit', + 'fullscreen', + 'gift', + 'globe', + 'grid', + 'heading-h1', + 'heading-h2', + 'heading-h3', + 'heading-h4', + 'heading-h5', + 'heading-h6', + 'heading', + 'heart-outline', + 'heart', + 'help-outline', + 'help', + 'history', + 'house', + 'image-multiple', + 'image-remove', + 'image', + 'indent-left', + 'indent-right', + 'info-outline', + 'info', + 'ink', + 'institution', + 'italic', + 'layout-blocks', + 'layout', + 'line-graph', + 'link-break', + 'link', + 'list-checkmark', + 'list-ordered-rtl', + 'list-ordered', + 'list-unordered', + 'location', + 'lock', + 'mail', + 'mention', + 'menu', + 'menus', + 'microphone', + 'minus-small', + 'minus', + 'money', + 'multiple-users', + 'my-sites-horizon', + 'my-sites', + 'nametag', + 'next-page', + 'not-visible', + 'notice-outline', + 'notice', + 'offline', + 'pages', + 'pause', + 'pencil', + 'phone', + 'pin', + 'plans', + 'play', + 'plugins', + 'plus-small', + 'plus', + 'popout', + 'posts', + 'print', + 'product-downloadable', + 'product-external', + 'product-virtual', + 'product', + 'quote', + 'read-more', + 'reader-follow-conversation', + 'reader-follow', + 'reader-following-conversation', + 'reader-following', + 'reader', + 'reblog', + 'redo', + 'refresh', + 'refund', + 'reply', + 'resize', + 'rotate', + 'scheduled', + 'search', + 'share-computer', + 'share-ios', + 'share', + 'shipping', + 'shutter', + 'sign-out', + 'site', + 'spam', + 'speaker', + 'special-character', + 'star-outline', + 'star', + 'stats', + 'stats-alt', + 'stats-down', + 'stats-down-alt', + 'stats-up', + 'stats-up-alt', + 'status', + 'strikethrough', + 'sync', + 'tablet', + 'tag', + 'text-color', + 'themes', + 'thumbs-up', + 'time', + 'trash', + 'trophy', + 'types', + 'underline', + 'undo', + 'user-add', + 'user-circle', + 'user', + 'video-camera', + 'video-remove', + 'video', + 'visible', + 'zoom-in', + 'zoom-out', +]; + +export default function GridiconExample() { + function handleClick( icon ) { + const toCopy = ``; + window.prompt( 'Copy component code:', toCopy ); + } + + return ( + // eslint-disable-next-line wpcalypso/jsx-classname-namespace +
+

+ Social Logo +

+
+ { icons.map( icon => ( + handleClick( icon ) } /> + ) ) } +
+
+ ); +} + +GridiconExample.displayName = 'Gridicon'; diff --git a/client/components/gridicon/index.jsx b/client/components/gridicon/index.jsx new file mode 100644 index 00000000000000..a85b316c28ff9d --- /dev/null +++ b/client/components/gridicon/index.jsx @@ -0,0 +1,56 @@ +/** + * External dependencies + */ +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import { + iconsThatNeedOffset, + iconsThatNeedOffsetX, + iconsThatNeedOffsetY, +} from 'gridicons/dist/util/icons-offset'; +import spritePath from 'gridicons/svg-sprite/gridicons.svg'; + +function needsOffset( name, icons ) { + return icons.indexOf( name ) >= 0; +} + +function Gridicon( props ) { + const { size = 24, icon, onClick, className, ...otherProps } = props; + const isModulo18 = size % 18 === 0; + + // Using a missing icon doesn't produce any errors, just a blank icon, which is the exact intended behaviour. + // This means we don't need to perform any checks on the icon name. + const iconName = `gridicons-${ icon }`; + const offsetClasses = isModulo18 + ? [ + needsOffset( iconName, iconsThatNeedOffset ) ? 'needs-offset' : false, + needsOffset( iconName, iconsThatNeedOffsetX ) ? 'needs-offset-x' : false, + needsOffset( iconName, iconsThatNeedOffsetY ) ? 'needs-offset-y' : false, + ] + : []; + const iconClass = classnames( 'gridicon', iconName, className, ...offsetClasses ); + + return ( + + + + ); +} + +Gridicon.propTypes = { + icon: PropTypes.string.isRequired, + size: PropTypes.number, + onClick: PropTypes.func, + className: PropTypes.string, +}; + +export default React.memo( Gridicon ); diff --git a/client/components/happiness-support/index.jsx b/client/components/happiness-support/index.jsx index e0db08e4ff7e8f..c77a9471e92669 100644 --- a/client/components/happiness-support/index.jsx +++ b/client/components/happiness-support/index.jsx @@ -14,7 +14,7 @@ import { localize } from 'i18n-calypso'; * Internal dependencies */ import Button from 'components/button'; -import GridiconExternal from 'gridicons/dist/external'; +import Gridicon from 'components/gridicon'; import isHappychatAvailable from 'state/happychat/selectors/is-happychat-available'; import { CALYPSO_CONTACT, @@ -149,7 +149,7 @@ export class HappinessSupport extends Component { rel="noopener noreferrer" className="happiness-support__support-button" > - + { this.props.translate( 'Support documentation' ) } ); diff --git a/client/components/tinymce/plugins/advanced/plugin.jsx b/client/components/tinymce/plugins/advanced/plugin.jsx index 63e3fb2cc06dfc..38cebeb0f377b7 100644 --- a/client/components/tinymce/plugins/advanced/plugin.jsx +++ b/client/components/tinymce/plugins/advanced/plugin.jsx @@ -8,7 +8,6 @@ import React from 'react'; import ReactDomServer from 'react-dom/server'; import tinymce from 'tinymce/tinymce'; import { translate } from 'i18n-calypso'; -import GridiconEllipsis from 'gridicons/dist/ellipsis'; /** * Internal dependencies @@ -16,6 +15,7 @@ import GridiconEllipsis from 'gridicons/dist/ellipsis'; import { isWithinBreakpoint } from 'lib/viewport'; import { savePreference, fetchPreferences } from 'state/preferences/actions'; import { getPreference, isFetchingPreferences } from 'state/preferences/selectors'; +import Gridicon from 'components/gridicon'; function advanced( editor ) { const store = editor.getParam( 'redux_store' ); @@ -72,9 +72,10 @@ function advanced( editor ) { this.innerHtml( ReactDomServer.renderToStaticMarkup( + // eslint-disable-next-line jsx-a11y/no-interactive-element-to-noninteractive-role ) diff --git a/client/components/tinymce/plugins/contact-form/plugin.jsx b/client/components/tinymce/plugins/contact-form/plugin.jsx index 9f4aca1ea67cc7..e5a575571e9de6 100644 --- a/client/components/tinymce/plugins/contact-form/plugin.jsx +++ b/client/components/tinymce/plugins/contact-form/plugin.jsx @@ -9,7 +9,6 @@ import i18n from 'i18n-calypso'; import React, { createElement } from 'react'; import { unmountComponentAtNode } from 'react-dom'; import { renderToStaticMarkup } from 'react-dom/server'; -import GridiconMention from 'gridicons/dist/mention'; /** * Internal Dependencies @@ -25,6 +24,7 @@ import { } from 'state/ui/editor/contact-form/actions'; import { serialize, deserialize } from './shortcode-utils'; import { renderWithReduxStore } from 'lib/react-helpers'; +import Gridicon from 'components/gridicon'; function wpcomContactForm( editor ) { let node; @@ -102,7 +102,7 @@ function wpcomContactForm( editor ) { renderToStaticMarkup( // eslint-disable-next-line jsx-a11y/no-interactive-element-to-noninteractive-role ) ); diff --git a/client/components/tinymce/plugins/insert-menu/menu-items.jsx b/client/components/tinymce/plugins/insert-menu/menu-items.jsx index bfc54d0ad72f87..af8865a565fd62 100644 --- a/client/components/tinymce/plugins/insert-menu/menu-items.jsx +++ b/client/components/tinymce/plugins/insert-menu/menu-items.jsx @@ -6,16 +6,12 @@ import React from 'react'; import i18n from 'i18n-calypso'; -import GridiconImage from 'gridicons/dist/image'; -import GridiconShutter from 'gridicons/dist/shutter'; -import GridiconMoney from 'gridicons/dist/money'; -import GridiconImageMultiple from 'gridicons/dist/image-multiple'; -import GridiconMention from 'gridicons/dist/mention'; /** * Internal dependencies */ import config from 'config'; +import Gridicon from 'components/gridicon'; /* eslint-disable wpcalypso/jsx-classname-namespace */ export const GridiconButton = ( { icon, label, e2e } ) => ( @@ -32,7 +28,11 @@ export const menuItems = [ { name: 'insert_media_item', item: ( - } label={ i18n.translate( 'Media' ) } e2e="media" /> + } + label={ i18n.translate( 'Media' ) } + e2e="media" + /> ), cmd: 'wpcomAddMedia', }, @@ -44,7 +44,7 @@ if ( config.isEnabled( 'external-media' ) ) { name: 'insert_from_google', item: ( } + icon={ } label={ i18n.translate( 'Google Photos library' ) } e2e="google-media" /> @@ -57,7 +57,7 @@ if ( config.isEnabled( 'external-media' ) ) { name: 'insert_from_pexels', item: ( } + icon={ } label={ i18n.translate( 'Free photo library' ) } e2e="stock-media-pexels" /> @@ -71,7 +71,7 @@ menuItems.push( { name: 'insert_contact_form', item: ( } + icon={ } label={ i18n.translate( 'Contact form' ) } e2e="contact-form" /> @@ -83,7 +83,7 @@ menuItems.push( { name: 'insert_payment_button', item: ( } + icon={ } label={ i18n.translate( 'Payment button' ) } e2e="payment-button" /> @@ -96,7 +96,7 @@ if ( config.isEnabled( 'memberships' ) ) { name: 'insert_memberships_button', item: ( } + icon={ } label={ i18n.translate( 'Recurring Payment' ) } e2e="memberships" /> diff --git a/client/components/tinymce/plugins/insert-menu/plugin.jsx b/client/components/tinymce/plugins/insert-menu/plugin.jsx index ed1660526052ee..d8cc10d99c5e6f 100644 --- a/client/components/tinymce/plugins/insert-menu/plugin.jsx +++ b/client/components/tinymce/plugins/insert-menu/plugin.jsx @@ -6,12 +6,12 @@ import React from 'react'; import tinymce from 'tinymce/tinymce'; import { renderToString } from 'react-dom/server'; import i18n from 'i18n-calypso'; -import GridiconAddOutline from 'gridicons/dist/add-outline'; /** * Internal dependencies */ import { menuItems, GridiconButton } from './menu-items'; +import Gridicon from 'components/gridicon'; function initialize( editor ) { menuItems.forEach( item => @@ -33,7 +33,10 @@ function initialize( editor ) { const [ insertContentElm ] = this.$el[ 0 ].children; insertContentElm.innerHTML = renderToString( - } label={ i18n.translate( 'Add' ) } /> + } + label={ i18n.translate( 'Add' ) } + /> ); }, } ); diff --git a/client/components/tinymce/plugins/media/advanced/index.jsx b/client/components/tinymce/plugins/media/advanced/index.jsx index 600559e281b644..1da316621766b1 100644 --- a/client/components/tinymce/plugins/media/advanced/index.jsx +++ b/client/components/tinymce/plugins/media/advanced/index.jsx @@ -6,7 +6,6 @@ import React from 'react'; import ReactDom from 'react-dom'; import ReactDomServer from 'react-dom/server'; import i18n from 'i18n-calypso'; -import GridiconPencil from 'gridicons/dist/pencil'; /** * Internal dependencies @@ -15,6 +14,7 @@ import { deserialize } from 'lib/media-serialization'; import config from 'config'; import EditorMediaAdvanced from 'post-editor/editor-media-advanced'; import { renderWithReduxStore } from 'lib/react-helpers'; +import Gridicon from 'components/gridicon'; export default function( editor ) { const store = editor.getParam( 'redux_store' ); @@ -70,8 +70,8 @@ export default function( editor ) { onPostRender() { this.innerHtml( ReactDomServer.renderToStaticMarkup( - ) ); diff --git a/client/components/tinymce/plugins/media/plugin.jsx b/client/components/tinymce/plugins/media/plugin.jsx index 5e27357874edc2..e7a58c13e2d007 100644 --- a/client/components/tinymce/plugins/media/plugin.jsx +++ b/client/components/tinymce/plugins/media/plugin.jsx @@ -10,7 +10,6 @@ import { assign, debounce, find, findLast, pick, values } from 'lodash'; import i18n from 'i18n-calypso'; import { parse, stringify } from 'lib/shortcode'; import closest from 'component-closest'; -import GridiconImageMultiple from 'gridicons/dist/image-multiple'; /** * Internal dependencies @@ -33,6 +32,7 @@ import { unblockSave } from 'state/ui/editor/save-blockers/actions'; import { getEditorRawContent, isEditorSaveBlocked } from 'state/ui/editor/selectors'; import { ModalViews } from 'state/ui/media-modal/constants'; import { renderWithReduxStore } from 'lib/react-helpers'; +import Gridicon from 'components/gridicon'; /** * Module variables @@ -52,9 +52,8 @@ function mediaButton( editor ) { const { dispatch, getState } = store; - let nodes = {}, - resizeEditor, - updateMedia; // eslint-disable-line + let nodes = {}; + let updateMedia; // eslint-disable-line const getSelectedSiteFromState = () => getSelectedSite( getState() ); @@ -453,9 +452,10 @@ function mediaButton( editor ) { onPostRender: function() { this.innerHtml( ReactDomServer.renderToStaticMarkup( + // eslint-disable-next-line jsx-a11y/no-interactive-element-to-noninteractive-role ) @@ -753,7 +753,7 @@ function mediaButton( editor ) { ); } ); - resizeEditor = debounce( + const resizeEditor = debounce( function() { // eslint-disable-line editor.execCommand( 'wpcomAutoResize', null, null, { skip_focus: true } ); diff --git a/client/components/tinymce/plugins/wplink/dialog.jsx b/client/components/tinymce/plugins/wplink/dialog.jsx index 4883f62f99a7df..c3182e7c8140f1 100644 --- a/client/components/tinymce/plugins/wplink/dialog.jsx +++ b/client/components/tinymce/plugins/wplink/dialog.jsx @@ -1,4 +1,3 @@ -/** @format */ /** * External dependencies */ @@ -8,7 +7,6 @@ import React from 'react'; import tinymce from 'tinymce/tinymce'; import { connect } from 'react-redux'; import { find } from 'lodash'; -import GridiconLinkBreak from 'gridicons/dist/link-break'; /** * Internal dependencies @@ -27,13 +25,14 @@ import { getSelectedSite } from 'state/ui/selectors'; import { getSitePosts } from 'state/posts/selectors'; import { decodeEntities } from 'lib/formatting'; import { recordEditorEvent, recordEditorStat } from 'state/posts/stats'; +import Gridicon from 'components/gridicon'; /** * Module variables */ -let REGEXP_EMAIL = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i, - REGEXP_URL = /^(https?|ftp):\/\/[A-Z0-9.-]+\.[A-Z]{2,4}[^ "]*$/i, - REGEXP_STANDALONE_URL = /^(?:[a-z]+:|#|\?|\.|\/)/; +const REGEXP_EMAIL = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i; +const REGEXP_URL = /^(https?|ftp):\/\/[A-Z0-9.-]+\.[A-Z]{2,4}[^ "]*$/i; +const REGEXP_STANDALONE_URL = /^(?:[a-z]+:|#|\?|\.|\/)/; class LinkDialog extends React.Component { static propTypes = { @@ -57,7 +56,7 @@ class LinkDialog extends React.Component { } getLink = () => { - const editor = this.props.editor; + const { editor } = this.props; return editor.dom.getParent( editor.selection.getNode(), 'a' ); }; @@ -77,10 +76,7 @@ class LinkDialog extends React.Component { }; updateEditor = () => { - let editor = this.props.editor, - attrs, - link, - linkText; + const { editor } = this.props; editor.focus(); @@ -93,9 +89,9 @@ class LinkDialog extends React.Component { return; } - link = this.getLink(); - linkText = this.state.linkText; - attrs = { + const link = this.getLink(); + let { linkText } = this.state; + const attrs = { href: this.getCorrectedUrl(), target: this.state.newWindow ? '_blank' : '', }; @@ -121,10 +117,9 @@ class LinkDialog extends React.Component { }; hasSelectedText = linkNode => { - let editor = this.props.editor, - html = editor.selection.getContent(), - nodes, - i; + const { editor } = this.props; + const html = editor.selection.getContent(); + let nodes; // Partial html and not a fully selected anchor element if ( @@ -141,7 +136,7 @@ class LinkDialog extends React.Component { return false; } - for ( i = nodes.length - 1; i >= 0; i-- ) { + for ( let i = nodes.length - 1; i >= 0; i-- ) { if ( nodes[ i ].nodeType !== 3 ) { return false; } @@ -152,10 +147,9 @@ class LinkDialog extends React.Component { }; getInferredUrl = () => { - let selectedText = this.props.editor.selection.getContent(), - selectedNode, - parsedImage, - knownImage; + const selectedText = this.props.editor.selection.getContent(); + let parsedImage; + let knownImage; if ( REGEXP_EMAIL.test( selectedText ) ) { return 'mailto:' + selectedText; @@ -163,7 +157,7 @@ class LinkDialog extends React.Component { return selectedText.replace( /&|�?38;/gi, '&' ); } - selectedNode = this.props.editor.selection.getNode(); + const selectedNode = this.props.editor.selection.getNode(); if ( selectedNode && 'IMG' === selectedNode.nodeName ) { parsedImage = deserialize( selectedNode ); if ( this.props.site && parsedImage.media.ID ) { @@ -179,18 +173,18 @@ class LinkDialog extends React.Component { }; getState = () => { - let editor = this.props.editor, - selectedNode = editor.selection.getNode(), - linkNode = editor.dom.getParent( selectedNode, 'a[href]' ), - onlyText = this.hasSelectedText( linkNode ), - nextState = { - isNew: true, - newWindow: false, - showLinkText: true, - linkText: '', - url: '', - isUserDefinedLinkText: false, - }; + const { editor } = this.props; + const selectedNode = editor.selection.getNode(); + const linkNode = editor.dom.getParent( selectedNode, 'a[href]' ); + const onlyText = this.hasSelectedText( linkNode ); + const nextState = { + isNew: true, + newWindow: false, + showLinkText: true, + linkText: '', + url: '', + isUserDefinedLinkText: false, + }; if ( linkNode ) { nextState.linkText = linkNode.innerText || linkNode.textContent; @@ -244,7 +238,7 @@ class LinkDialog extends React.Component { }; getButtons = () => { - let buttonText, buttons; + let buttonText; if ( this.state.isNew ) { buttonText = this.props.translate( 'Add Link' ); @@ -252,7 +246,7 @@ class LinkDialog extends React.Component { buttonText = this.props.translate( 'Save' ); } - buttons = [ + const buttons = [ { buttonText } , @@ -264,7 +258,7 @@ class LinkDialog extends React.Component { if ( this.state.url && ! this.state.isNew ) { buttons.push( ); @@ -305,6 +299,7 @@ class LinkDialog extends React.Component { state = this.getState(); + /* eslint-disable jsx-a11y/no-autofocus */ render() { return ( { this.props.translate( 'URL' ) } ); } + /* eslint-enable jsx-a11y/no-autofocus */ } export default connect( diff --git a/client/devdocs/design/index.jsx b/client/devdocs/design/index.jsx index 8b8e3a53bf7714..c1a72b099a145c 100644 --- a/client/devdocs/design/index.jsx +++ b/client/devdocs/design/index.jsx @@ -10,7 +10,6 @@ import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { slugToCamelCase } from 'devdocs/docs-example/util'; import { trim } from 'lodash'; -import Gridicons from 'gridicons/example'; /** * Internal dependencies @@ -68,6 +67,7 @@ import Gauge from 'components/gauge/docs/example'; import GlobalNotices from 'components/global-notices/docs/example'; import Gravatar from 'components/gravatar/docs/example'; import GravatarCaterpillar from 'components/gravatar-caterpillar/docs/example'; +import Gridicon from 'components/gridicon/docs/example'; import HeaderButton from 'components/header-button/docs/example'; import Headers from 'components/header-cake/docs/example'; import ImagePreloader from 'components/image-preloader/docs/example'; @@ -226,7 +226,7 @@ class DesignAssets extends React.Component { - + diff --git a/client/my-sites/checkout/cart/cart-summary-bar.jsx b/client/my-sites/checkout/cart/cart-summary-bar.jsx index cf48562f69bb2e..48e7a3a6f00b59 100644 --- a/client/my-sites/checkout/cart/cart-summary-bar.jsx +++ b/client/my-sites/checkout/cart/cart-summary-bar.jsx @@ -11,7 +11,7 @@ import { localize } from 'i18n-calypso'; * Internal dependencies */ import SectionHeader from 'components/section-header'; -import GridiconCart from 'gridicons/dist/cart'; +import Gridicon from 'components/gridicon'; class CartSummaryBar extends React.Component { render() { @@ -28,7 +28,7 @@ class CartSummaryBar extends React.Component { return (
- +
); diff --git a/client/my-sites/plugins/plugin-action/style.scss b/client/my-sites/plugins/plugin-action/style.scss index 7a91ebdfd8664e..80ba8a1fe28028 100644 --- a/client/my-sites/plugins/plugin-action/style.scss +++ b/client/my-sites/plugins/plugin-action/style.scss @@ -75,7 +75,8 @@ display: block; } - .gridicons-info-outline g { + .gridicons-info-outline > use:first-child, + .gridicons-info-outline > g:first-child { // revert the translate(1px,1px) done by needs-offset transform: none; } diff --git a/client/my-sites/plugins/plugin-automated-transfer/style.scss b/client/my-sites/plugins/plugin-automated-transfer/style.scss index 10079c75e150ff..4f3a30fd8a5c50 100644 --- a/client/my-sites/plugins/plugin-automated-transfer/style.scss +++ b/client/my-sites/plugins/plugin-automated-transfer/style.scss @@ -1,10 +1,11 @@ .plugin-automated-transfer__notice { - .gridicons-sync g { + .gridicons-sync > use:first-child, + .gridicons-sync > g:first-child { animation: spinning-sync-icon linear 2s infinite; transform-origin: center; } - .gridicons-checkmark g, - .gridicons-notice g { + .gridicons-checkmark > use:first-child, + .gridicons-notice > g:first-child { transform: rotate( 0deg ); } } diff --git a/docs/icons.md b/docs/icons.md index 43f5e61c1fe31d..f64ef0ec66029a 100644 --- a/docs/icons.md +++ b/docs/icons.md @@ -10,28 +10,23 @@ This document will cover how to use icons in Calypso, as well as how to create i Gridicons are born with a 24px base grid. Strokes are 2px thick and icons are solid. If an icon is hollow, it generally means the "inactive" version of that icon. For example an outline bookmark icon becomes solid once clicked. +Calypso has a specific Gridicon component that should be used instead of the one included in the `gridicons` package, since it offers better loading performance. +Any usage of `gridicons` gets rewritten to `components/gridicon`. +You should not use `gridicons/dist/...`, as that will load a legacy gridicon and cause duplication in the bundles. + ### Usage Import the iconset and decide at run-time which icon to use: ``` -import Gridicon from 'gridicons'; +import Gridicon from 'components/gridicon'; +// or `import Gridicon from 'gridicons'`; //... render() { return ; } ``` -If your project is using a small number of icons, the recommendation is to import them individually. By doing so, your JavaScript bundle will be smaller because only the icons you actually use will be added to your bundle. - -``` -import GridiconExternal from 'gridicons/dist/external'; -//... -render() { - -} -``` - **Props** - `icon`: String - the icon name. This is ignored when importing icons individually. @@ -56,7 +51,7 @@ Some icons at 18 and 36px size needs an extra feature in order to look crisp. Th What this basically does is nudge the pixels up and to the left by half a pixel. In the case of 36px icons (1.5 * 24) what it means is that icons can be **perfectly crisp**. In the case of 18px icons, it means icons will be **crisper**, though not perfect. Just trust me on the math. -The tricky part is that not all icons need this `offset-adjust` hack, only some icons do. We are currently working out how to best roll this feature out. +The tricky part is that not all icons need this `offset-adjust` hack, only some icons do. A list of icons that need adjustment is kept in the `gridicons` package and used by the Gridicon component at run-time, to determine which icons to adjust and when to do so. ### Do's and Don'ts diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 21ca08509e12e3..926dcb12c5a38b 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -41,7 +41,7 @@ "css-loader": "2.1.1", "duplicate-package-checker-webpack-plugin": "3.0.0", "file-loader": "3.0.1", - "mini-css-extract-plugin-with-rtl": "github:Automattic/mini-css-extract-plugin-with-rtl#af1300db7027af8caa9a3015f54a34aec545cc54", + "mini-css-extract-plugin-with-rtl": "github:Automattic/mini-css-extract-plugin-with-rtl", "node-sass": "4.11.0", "postcss-custom-properties": "8.0.9", "postcss-loader": "3.0.0", @@ -57,7 +57,7 @@ "dependencies": { "@babel/core": { "version": "7.4.0", - "bundled": true, + "resolved": "", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -78,7 +78,7 @@ }, "@babel/plugin-transform-runtime": { "version": "7.4.0", - "bundled": true, + "resolved": "", "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", @@ -89,7 +89,7 @@ }, "@babel/preset-env": { "version": "7.4.2", - "bundled": true, + "resolved": "", "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", @@ -141,7 +141,7 @@ }, "autoprefixer": { "version": "9.4.4", - "bundled": true, + "resolved": "", "dev": true, "requires": { "browserslist": "^4.3.7", @@ -154,12 +154,14 @@ }, "camelcase": { "version": "5.3.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, "cross-spawn": { "version": "6.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { "nice-try": "^1.0.4", @@ -171,7 +173,7 @@ }, "debug": { "version": "4.1.1", - "bundled": true, + "resolved": "", "dev": true, "requires": { "ms": "^2.1.1" @@ -179,7 +181,8 @@ }, "execa": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { "cross-spawn": "^6.0.0", @@ -193,7 +196,8 @@ }, "find-up": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { "locate-path": "^3.0.0" @@ -201,7 +205,8 @@ }, "findup-sync": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", "dev": true, "requires": { "detect-file": "^1.0.0", @@ -212,7 +217,8 @@ }, "get-stream": { "version": "4.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, "requires": { "pump": "^3.0.0" @@ -220,7 +226,8 @@ }, "import-local": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", "dev": true, "requires": { "pkg-dir": "^3.0.0", @@ -229,12 +236,14 @@ }, "invert-kv": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, "is-glob": { "version": "3.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { "is-extglob": "^2.1.0" @@ -242,7 +251,8 @@ }, "lcid": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { "invert-kv": "^2.0.0" @@ -250,7 +260,8 @@ }, "locate-path": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { "p-locate": "^3.0.0", @@ -259,7 +270,8 @@ }, "mem": { "version": "4.3.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", "dev": true, "requires": { "map-age-cleaner": "^0.1.1", @@ -269,12 +281,14 @@ }, "mimic-fn": { "version": "2.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, "os-locale": { "version": "3.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { "execa": "^1.0.0", @@ -284,7 +298,8 @@ }, "p-limit": { "version": "2.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -292,7 +307,8 @@ }, "p-locate": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { "p-limit": "^2.0.0" @@ -300,12 +316,13 @@ }, "p-try": { "version": "2.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, "source-map": { "version": "0.5.7", - "bundled": true, + "resolved": "", "dev": true }, "webpack": { @@ -342,7 +359,8 @@ }, "webpack-cli": { "version": "3.3.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.0.tgz", + "integrity": "sha512-t1M7G4z5FhHKJ92WRKwZ1rtvi7rHc0NZoZRbSkol0YKl4HvcC8+DsmGDmK7MmZxHSAetHagiOsjOB6MmzC2TUw==", "dev": true, "requires": { "chalk": "^2.4.1", @@ -360,7 +378,8 @@ }, "yargs": { "version": "12.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", "dev": true, "requires": { "cliui": "^4.0.0", @@ -379,7 +398,8 @@ }, "yargs-parser": { "version": "11.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -10357,9 +10377,9 @@ "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" }, "gridicons": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/gridicons/-/gridicons-3.2.0.tgz", - "integrity": "sha512-82bdTGEV12d9Ig5gTYw0uR2gf/Q/PNytAfLQdmBdZyj/iqCTw2jOipLq/GpG+VgiRQEXjc5TujBj7Jg/R2aP6Q==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/gridicons/-/gridicons-3.3.1.tgz", + "integrity": "sha512-eQsmujjLptLtyhGuu31US3mXkcptYHkgEE/s277HWv+j6c3Z2gYyjoHcBKwSFbQwxbfhToRd5uzYimR2ExWJdQ==", "requires": { "prop-types": "^15.5.7" } diff --git a/package.json b/package.json index a1e71dd840b002..48ff76ad28c28e 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "get-video-id": "3.1.1", "gfm-code-blocks": "1.0.0", "globby": "9.2.0", - "gridicons": "3.2.0", + "gridicons": "3.3.1", "gzip-size": "5.1.0", "hash.js": "1.1.7", "he": "1.2.0", diff --git a/test/client/jest.config.js b/test/client/jest.config.js index 068992f0da3b9f..0d8fe1c506d5f6 100644 --- a/test/client/jest.config.js +++ b/test/client/jest.config.js @@ -13,7 +13,7 @@ module.exports = { roots: [ '/client/' ], testEnvironment: 'node', transformIgnorePatterns: [ - 'node_modules[\\/\\\\](?!flag-icon-css|redux-form|simple-html-tokenizer|draft-js|social-logos)', + 'node_modules[\\/\\\\](?!flag-icon-css|redux-form|simple-html-tokenizer|draft-js|social-logos|gridicons)', ], testMatch: [ '/client/**/test/*.js?(x)', '!**/.eslintrc.*' ], testURL: 'https://example.com', diff --git a/webpack.config.js b/webpack.config.js index 00d02d9f128399..173dfdc1150f3b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -247,11 +247,10 @@ const webpackConfig = { modules: [ path.join( __dirname, 'client' ), 'node_modules' ], alias: Object.assign( { - 'gridicons/example': 'gridicons/dist/example', 'react-virtualized': 'react-virtualized/dist/es', debug: path.resolve( __dirname, 'node_modules/debug' ), store: 'store/dist/store.modern', - gridicons$: path.resolve( __dirname, 'client/components/async-gridicons' ), + gridicons$: path.resolve( __dirname, 'client/components/gridicon' ), }, getAliasesForExtensions( { extensionsDirectory: path.join( __dirname, 'client', 'extensions' ),