Просмотр исходного кода

Improve the Icon component (#6245)

GitOrigin-RevId: fbb23b32c47edbe5a22badc627318accbd09e82a
Alf Eaton 4 лет назад
Родитель
Сommit
f065a7a909
37 измененных файлов с 146 добавлено и 152 удалено
  1. 2 2
      services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-projects-button.js
  2. 3 7
      services/web/frontend/js/features/editor-navigation-toolbar/components/chat-toggle-button.js
  3. 1 1
      services/web/frontend/js/features/editor-navigation-toolbar/components/history-toggle-button.js
  4. 4 4
      services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.js
  5. 1 5
      services/web/frontend/js/features/editor-navigation-toolbar/components/menu-button.js
  6. 1 1
      services/web/frontend/js/features/editor-navigation-toolbar/components/online-users-widget.js
  7. 1 1
      services/web/frontend/js/features/editor-navigation-toolbar/components/pdf-toggle-button.js
  8. 1 1
      services/web/frontend/js/features/editor-navigation-toolbar/components/project-name-editable-label.js
  9. 1 1
      services/web/frontend/js/features/editor-navigation-toolbar/components/share-project-button.js
  10. 1 1
      services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-mode.js
  11. 30 25
      services/web/frontend/js/features/file-tree/components/file-tree-doc.js
  12. 2 2
      services/web/frontend/js/features/file-tree/components/file-tree-folder.js
  13. 5 9
      services/web/frontend/js/features/file-tree/components/file-tree-toolbar.js
  14. 6 17
      services/web/frontend/js/features/file-view/components/file-view-header.js
  15. 12 0
      services/web/frontend/js/features/file-view/components/file-view-icons.js
  16. 1 1
      services/web/frontend/js/features/file-view/components/file-view.js
  17. 1 1
      services/web/frontend/js/features/outline/components/outline-item.js
  18. 1 1
      services/web/frontend/js/features/outline/components/outline-pane.js
  19. 1 1
      services/web/frontend/js/features/pdf-preview/components/pdf-code-check-failed-notice.js
  20. 6 6
      services/web/frontend/js/features/pdf-preview/components/pdf-compile-button.js
  21. 1 1
      services/web/frontend/js/features/pdf-preview/components/pdf-download-button.js
  22. 1 1
      services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-download-button.js
  23. 1 1
      services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-logs-button.js
  24. 4 12
      services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.js
  25. 1 1
      services/web/frontend/js/features/preview/components/preview-download-button.js
  26. 2 1
      services/web/frontend/js/features/preview/components/preview-logs-pane-max-entries.js
  27. 1 1
      services/web/frontend/js/features/preview/components/preview-logs-pane.js
  28. 7 7
      services/web/frontend/js/features/preview/components/preview-recompile-button.js
  29. 3 3
      services/web/frontend/js/features/share-project-modal/components/select-collaborators.js
  30. 3 1
      services/web/frontend/js/features/word-count-modal/components/word-count-modal-content.js
  31. 1 1
      services/web/frontend/js/shared/components/icon-checked.js
  32. 18 15
      services/web/frontend/js/shared/components/icon.js
  33. 1 1
      services/web/frontend/js/shared/components/loading-spinner.js
  34. 2 1
      services/web/frontend/js/shared/components/processing.js
  35. 1 1
      services/web/frontend/stories/icon.stories.js
  36. 11 4
      services/web/frontend/stylesheets/app/editor/file-tree.less
  37. 7 13
      services/web/test/frontend/shared/components/icon.test.js

+ 2 - 2
services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-projects-button.js

@@ -6,8 +6,8 @@ function BackToProjectsButton() {
   return (
     <a className="toolbar-header-back-projects" href="/project">
       <Icon
-        type="fw"
-        modifier="level-up"
+        type="level-up"
+        fw
         accessibilityLabel={t('back_to_your_projects')}
       />
     </a>

+ 3 - 7
services/web/frontend/js/features/editor-navigation-toolbar/components/chat-toggle-button.js

@@ -17,14 +17,10 @@ function ChatToggleButton({ chatIsOpen, unreadMessageCount, onClick }) {
   return (
     <div className="toolbar-item">
       <button className={classes} onClick={onClick}>
-        <Icon
-          type="fw"
-          modifier="comment"
-          classes={{ icon: hasUnreadMessages ? 'bounce' : undefined }}
-        />
-        {hasUnreadMessages ? (
+        <Icon type="comment" fw className={hasUnreadMessages ? 'bounce' : ''} />
+        {hasUnreadMessages && (
           <span className="label label-info">{unreadMessageCount}</span>
-        ) : null}
+        )}
         <p className="toolbar-label">{t('chat')}</p>
       </button>
     </div>

+ 1 - 1
services/web/frontend/js/features/editor-navigation-toolbar/components/history-toggle-button.js

@@ -13,7 +13,7 @@ function HistoryToggleButton({ historyIsOpen, onClick }) {
   return (
     <div className="toolbar-item">
       <button className={classes} onClick={onClick}>
-        <Icon type="fw" modifier="history" />
+        <Icon type="history" fw />
         <p className="toolbar-label">{t('history')}</p>
       </button>
     </div>

+ 4 - 4
services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.js

@@ -12,19 +12,19 @@ import { useLayoutContext } from '../../../shared/context/layout-context'
 import * as eventTracking from '../../../infrastructure/event-tracking'
 
 function IconPlaceholder() {
-  return <Icon type="" modifier="fw" />
+  return <Icon type="" fw />
 }
 
 function IconRefresh() {
-  return <Icon type="refresh" modifier="fw" spin />
+  return <Icon type="refresh" fw spin />
 }
 
 function IconLayout() {
-  return <Icon type="columns" modifier="fw" />
+  return <Icon type="columns" fw />
 }
 
 function IconDetach() {
-  return <Icon type="window-restore" modifier="fw" />
+  return <Icon type="window-restore" fw />
 }
 
 function IconCheckmark({ iconFor, pdfLayout, view, detachRole }) {

+ 1 - 5
services/web/frontend/js/features/editor-navigation-toolbar/components/menu-button.js

@@ -8,11 +8,7 @@ function MenuButton({ onClick }) {
   return (
     <div className="toolbar-item">
       <button className="btn btn-full-height" onClick={onClick}>
-        <Icon
-          type="bars "
-          modifier="fw"
-          classes={{ icon: 'editor-menu-icon' }}
-        />
+        <Icon type="bars" fw className="editor-menu-icon" />
         <p className="toolbar-label">{t('menu')}</p>
       </button>
     </div>

+ 1 - 1
services/web/frontend/js/features/editor-navigation-toolbar/components/online-users-widget.js

@@ -105,7 +105,7 @@ const DropDownToggleButton = React.forwardRef((props, ref) => {
         onClick={props.onClick} // required by Bootstrap Dropdown to trigger an opening
       >
         <strong>{props.onlineUserCount}</strong>
-        <Icon type="users" modifier="fw" />
+        <Icon type="users" fw />
       </button>
     </OverlayTrigger>
   )

+ 1 - 1
services/web/frontend/js/features/editor-navigation-toolbar/components/pdf-toggle-button.js

@@ -21,7 +21,7 @@ function PdfToggleButton({ onClick, pdfViewIsOpen }) {
     >
       {/* eslint-disable-next-line jsx-a11y/anchor-is-valid,jsx-a11y/click-events-have-key-events,jsx-a11y/interactive-supports-focus */}
       <a role="button" className={classes} onClick={onClick}>
-        <Icon type="file-pdf-o" modifier="fw" accessibilityLabel="PDF" />
+        <Icon type="file-pdf-o" fw accessibilityLabel="PDF" />
       </a>
     </OverlayTrigger>
   )

+ 1 - 1
services/web/frontend/js/features/editor-navigation-toolbar/components/project-name-editable-label.js

@@ -82,7 +82,7 @@ function ProjectNameEditableLabel({
         >
           {/* eslint-disable-next-line jsx-a11y/anchor-is-valid, jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus */}
           <a className="rename" role="button" onClick={startRenaming}>
-            <Icon type="pencil" modifier="fw" />
+            <Icon type="pencil" fw />
           </a>
         </OverlayTrigger>
       )}

+ 1 - 1
services/web/frontend/js/features/editor-navigation-toolbar/components/share-project-button.js

@@ -8,7 +8,7 @@ function ShareProjectButton({ onClick }) {
   return (
     <div className="toolbar-item">
       <button className="btn btn-full-height" onClick={onClick}>
-        <Icon type="fw" modifier="group" />
+        <Icon type="group" fw />
         <p className="toolbar-label">{t('share')}</p>
       </button>
     </div>

+ 1 - 1
services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-mode.js

@@ -19,7 +19,7 @@ export default function FileTreeModalCreateFileMode({ mode, icon, label }) {
         onClick={handleClick}
         className="modal-new-file-mode"
       >
-        <Icon modifier="fw" type={icon} />
+        <Icon type={icon} fw />
         &nbsp;
         {label}
       </Button>

+ 30 - 25
services/web/frontend/js/features/file-tree/components/file-tree-doc.js

@@ -1,36 +1,16 @@
 import PropTypes from 'prop-types'
-import { useTranslation } from 'react-i18next'
-
-import Icon from '../../../shared/components/icon'
-import iconTypeFromName from '../util/icon-type-from-name'
 
 import { useSelectableEntity } from '../contexts/file-tree-selectable'
 
 import FileTreeItemInner from './file-tree-item/file-tree-item-inner'
+import { useTranslation } from 'react-i18next'
+import Icon from '../../../shared/components/icon'
+import iconTypeFromName from '../util/icon-type-from-name'
+import classnames from 'classnames'
 
 function FileTreeDoc({ name, id, isLinkedFile }) {
-  const { t } = useTranslation()
-
   const { isSelected, props: selectableEntityProps } = useSelectableEntity(id)
 
-  const icons = (
-    <>
-      <Icon
-        type={iconTypeFromName(name)}
-        modifier="fw"
-        classes={{ icon: 'spaced' }}
-      >
-        {isLinkedFile ? (
-          <Icon
-            type="external-link-square"
-            modifier="rotate-180"
-            classes={{ icon: 'linked-file-highlight' }}
-            accessibilityLabel={t('linked_file')}
-          />
-        ) : null}
-      </Icon>
-    </>
-  )
   return (
     <li
       role="treeitem"
@@ -42,7 +22,7 @@ function FileTreeDoc({ name, id, isLinkedFile }) {
         id={id}
         name={name}
         isSelected={isSelected}
-        icons={icons}
+        icons={<FileTreeIcon isLinkedFile={isLinkedFile} name={name} />}
       />
     </li>
   )
@@ -54,4 +34,29 @@ FileTreeDoc.propTypes = {
   isLinkedFile: PropTypes.bool,
 }
 
+export const FileTreeIcon = ({ isLinkedFile, name }) => {
+  const { t } = useTranslation()
+
+  const className = classnames('spaced', { 'linked-file-icon': isLinkedFile })
+
+  return (
+    <>
+      &nbsp;
+      <Icon type={iconTypeFromName(name)} fw className={className} />
+      {isLinkedFile && (
+        <Icon
+          type="external-link-square"
+          modifier="rotate-180"
+          className="linked-file-highlight"
+          accessibilityLabel={t('linked_file')}
+        />
+      )}
+    </>
+  )
+}
+FileTreeIcon.propTypes = {
+  name: PropTypes.string.isRequired,
+  isLinkedFile: PropTypes.bool,
+}
+
 export default FileTreeDoc

+ 2 - 2
services/web/frontend/js/features/file-tree/components/file-tree-folder.js

@@ -45,9 +45,9 @@ function FileTreeFolder({ name, id, folders, docs, files }) {
         onClick={handleExpandCollapseClick}
         aria-label={expanded ? t('collapse') : t('expand')}
       >
-        <Icon type={expanded ? 'angle-down' : 'angle-right'} modifier="fw" />
+        <Icon type={expanded ? 'angle-down' : 'angle-right'} fw />
       </button>
-      <Icon type={expanded ? 'folder-open' : 'folder'} modifier="fw" />
+      <Icon type={expanded ? 'folder-open' : 'folder'} fw />
     </>
   )
 

+ 5 - 9
services/web/frontend/js/features/file-tree/components/file-tree-toolbar.js

@@ -42,25 +42,21 @@ function FileTreeToolbarLeft() {
         description={t('new_file')}
         onClick={startCreatingDocOrFile}
       >
-        <Icon type="file" modifier="fw" accessibilityLabel={t('new_file')} />
+        <Icon type="file" fw accessibilityLabel={t('new_file')} />
       </TooltipButton>
       <TooltipButton
         id="new_folder"
         description={t('new_folder')}
         onClick={startCreatingFolder}
       >
-        <Icon
-          type="folder"
-          modifier="fw"
-          accessibilityLabel={t('new_folder')}
-        />
+        <Icon type="folder" fw accessibilityLabel={t('new_folder')} />
       </TooltipButton>
       <TooltipButton
         id="upload"
         description={t('upload')}
         onClick={startUploadingDocOrFile}
       >
-        <Icon type="upload" modifier="fw" accessibilityLabel={t('upload')} />
+        <Icon type="upload" fw accessibilityLabel={t('upload')} />
       </TooltipButton>
     </div>
   )
@@ -83,7 +79,7 @@ function FileTreeToolbarRight() {
           description={t('rename')}
           onClick={startRenaming}
         >
-          <Icon type="pencil" modifier="fw" accessibilityLabel={t('rename')} />
+          <Icon type="pencil" fw accessibilityLabel={t('rename')} />
         </TooltipButton>
       ) : null}
       {canDelete ? (
@@ -92,7 +88,7 @@ function FileTreeToolbarRight() {
           description={t('delete')}
           onClick={startDeleting}
         >
-          <Icon type="trash-o" modifier="fw" accessibilityLabel={t('delete')} />
+          <Icon type="trash-o" fw accessibilityLabel={t('delete')} />
         </TooltipButton>
       ) : null}
     </div>

+ 6 - 17
services/web/frontend/js/features/file-view/components/file-view-header.js

@@ -10,6 +10,7 @@ import { useProjectContext } from '../../../shared/context/project-context'
 
 import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
 import useAbortController from '../../../shared/hooks/use-abort-controller'
+import { LinkedFileIcon } from './file-view-icons'
 const tprLinkedFileInfo = importOverleafModules('tprLinkedFileInfo')
 const tprLinkedFileRefreshError = importOverleafModules(
   'tprLinkedFileRefreshError'
@@ -120,7 +121,7 @@ export default function FileViewHeader({ file, storeReferencesKeys }) {
           onClick={refreshFile}
           disabled={refreshing}
         >
-          <Icon type="refresh" spin={refreshing} modifier="fw" />
+          <Icon type="refresh" spin={refreshing} fw />
           <span>{refreshing ? t('refreshing') + '...' : t('refresh')}</span>
         </button>
       )}
@@ -129,7 +130,7 @@ export default function FileViewHeader({ file, storeReferencesKeys }) {
         href={`/project/${projectId}/file/${file.id}`}
         className="btn btn-info"
       >
-        <Icon type="download" modifier="fw" />
+        <Icon type="download" fw />
         &nbsp;
         <span>{t('download')}</span>
       </a>
@@ -162,11 +163,7 @@ FileViewHeader.propTypes = {
 function UrlProvider({ file }) {
   return (
     <p>
-      <Icon
-        type="external-link-square"
-        modifier="rotate-180"
-        classes={{ icon: 'linked-file-icon' }}
-      />
+      <LinkedFileIcon />
       &nbsp;
       <Trans
         i18nKey="imported_from_external_provider_at_date"
@@ -195,11 +192,7 @@ function ProjectFilePathProvider({ file }) {
   /* eslint-disable jsx-a11y/anchor-has-content, react/jsx-key */
   return (
     <p>
-      <Icon
-        type="external-link-square"
-        modifier="rotate-180"
-        classes={{ icon: 'linked-file-icon' }}
-      />
+      <LinkedFileIcon />
       &nbsp;
       <Trans
         i18nKey="imported_from_another_project_at_date"
@@ -235,11 +228,7 @@ ProjectFilePathProvider.propTypes = {
 function ProjectOutputFileProvider({ file }) {
   return (
     <p>
-      <Icon
-        type="external-link-square"
-        modifier="rotate-180"
-        classes={{ icon: 'linked-file-icon' }}
-      />
+      <LinkedFileIcon />
       &nbsp;
       <Trans
         i18nKey="imported_from_the_output_of_another_project_at_date"

+ 12 - 0
services/web/frontend/js/features/file-view/components/file-view-icons.js

@@ -0,0 +1,12 @@
+import Icon from '../../../shared/components/icon'
+
+export const LinkedFileIcon = props => {
+  return (
+    <Icon
+      type="external-link-square"
+      modifier="rotate-180"
+      className="linked-file-icon"
+      {...props}
+    />
+  )
+}

+ 1 - 1
services/web/frontend/js/features/file-view/components/file-view.js

@@ -65,7 +65,7 @@ function FileViewLoadingIndicator() {
   return (
     <div className="loading-panel loading-panel-file-view">
       <span>
-        <Icon type="refresh" modifier="spin" />
+        <Icon type="refresh" spin />
         &nbsp;&nbsp;{t('loading')}…
       </span>
     </div>

+ 1 - 1
services/web/frontend/js/features/outline/components/outline-item.js

@@ -80,7 +80,7 @@ function OutlineItem({ outlineItem, jumpToLine, highlightedLine }) {
           >
             <Icon
               type={expanded ? 'angle-down' : 'angle-right'}
-              classes={{ icon: 'outline-caret-icon' }}
+              className="outline-caret-icon"
             />
           </button>
         ) : null}

+ 1 - 1
services/web/frontend/js/features/outline/components/outline-pane.js

@@ -57,7 +57,7 @@ const OutlinePane = React.memo(function OutlinePane({
         >
           <Icon
             type={isOpen ? 'angle-down' : 'angle-right'}
-            classes={{ icon: 'outline-caret-icon' }}
+            className="outline-caret-icon"
           />
           <h4 className="outline-header-name">{t('file_outline')}</h4>
         </button>

+ 1 - 1
services/web/frontend/js/features/pdf-preview/components/pdf-code-check-failed-notice.js

@@ -9,7 +9,7 @@ function PdfCodeCheckFailedNotice() {
     <div className="log-entry">
       <div className="log-entry-header log-entry-header-error">
         <div className="log-entry-header-icon-container">
-          <Icon type="exclamation-triangle" modifier="fw" />
+          <Icon type="exclamation-triangle" fw />
         </div>
         <h3 className="log-entry-header-title">
           {t('code_check_failed_explanation')}

+ 6 - 6
services/web/frontend/js/features/pdf-preview/components/pdf-compile-button.js

@@ -48,36 +48,36 @@ function PdfCompileButton() {
         <MenuItem header>{t('auto_compile')}</MenuItem>
 
         <MenuItem onSelect={() => setAutoCompile(true)}>
-          <Icon type={autoCompile ? 'check' : ''} modifier="fw" />
+          <Icon type={autoCompile ? 'check' : ''} fw />
           {t('on')}
         </MenuItem>
 
         <MenuItem onSelect={() => setAutoCompile(false)}>
-          <Icon type={!autoCompile ? 'check' : ''} modifier="fw" />
+          <Icon type={!autoCompile ? 'check' : ''} fw />
           {t('off')}
         </MenuItem>
 
         <MenuItem header>{t('compile_mode')}</MenuItem>
 
         <MenuItem onSelect={() => setDraft(false)}>
-          <Icon type={!draft ? 'check' : ''} modifier="fw" />
+          <Icon type={!draft ? 'check' : ''} fw />
           {t('normal')}
         </MenuItem>
 
         <MenuItem onSelect={() => setDraft(true)}>
-          <Icon type={draft ? 'check' : ''} modifier="fw" />
+          <Icon type={draft ? 'check' : ''} fw />
           {t('fast')} <span className="subdued">[draft]</span>
         </MenuItem>
 
         <MenuItem header>Syntax Checks</MenuItem>
 
         <MenuItem onSelect={() => setStopOnValidationError(true)}>
-          <Icon type={stopOnValidationError ? 'check' : ''} modifier="fw" />
+          <Icon type={stopOnValidationError ? 'check' : ''} fw />
           {t('stop_on_validation_error')}
         </MenuItem>
 
         <MenuItem onSelect={() => setStopOnValidationError(false)}>
-          <Icon type={!stopOnValidationError ? 'check' : ''} modifier="fw" />
+          <Icon type={!stopOnValidationError ? 'check' : ''} fw />
           {t('ignore_validation_errors')}
         </MenuItem>
 

+ 1 - 1
services/web/frontend/js/features/pdf-preview/components/pdf-download-button.js

@@ -26,7 +26,7 @@ function PdfDownloadButton() {
         download
         href={pdfDownloadUrl || '#'}
       >
-        <Icon type="download" modifier="fw" />
+        <Icon type="download" fw />
         <span className="toolbar-text toolbar-hide-medium toolbar-hide-small">
           {t('download_pdf')}
         </span>

+ 1 - 1
services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-download-button.js

@@ -28,7 +28,7 @@ function PdfHybridDownloadButton() {
         target="_blank"
         style={{ pointerEvents: 'auto' }}
       >
-        <Icon type="download" modifier="fw" />
+        <Icon type="download" fw />
       </Button>
     </OverlayTrigger>
   )

+ 1 - 1
services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-logs-button.js

@@ -40,7 +40,7 @@ function PdfHybridLogsButton() {
         style={{ position: 'relative' }}
         aria-label={showLogs ? t('view_pdf') : t('view_logs')}
       >
-        <Icon type="file-text-o" modifier="fw" />
+        <Icon type="file-text-o" fw />
 
         {!showLogs && totalCount > 0 && (
           <Label bsStyle={errorCount === 0 ? 'warning' : 'danger'}>

+ 4 - 12
services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.js

@@ -30,13 +30,9 @@ function GoToCodeButton({
 
   let buttonIcon = null
   if (syncToCodeInFlight) {
-    buttonIcon = (
-      <Icon type="refresh" spin classes={{ icon: 'synctex-spin-icon' }} />
-    )
+    buttonIcon = <Icon type="refresh" spin className="synctex-spin-icon" />
   } else if (!isDetachLayout) {
-    buttonIcon = (
-      <Icon type="arrow-left" classes={{ icon: 'synctex-control-icon' }} />
-    )
+    buttonIcon = <Icon type="arrow-left" className="synctex-control-icon" />
   }
 
   return (
@@ -77,13 +73,9 @@ function GoToPdfButton({
 
   let buttonIcon = null
   if (syncToPdfInFlight) {
-    buttonIcon = (
-      <Icon type="refresh" spin classes={{ icon: 'synctex-spin-icon' }} />
-    )
+    buttonIcon = <Icon type="refresh" spin className="synctex-spin-icon" />
   } else if (!isDetachLayout) {
-    buttonIcon = (
-      <Icon type="arrow-right" classes={{ icon: 'synctex-control-icon' }} />
-    )
+    buttonIcon = <Icon type="arrow-right" className="synctex-control-icon" />
   }
 
   return (

+ 1 - 1
services/web/frontend/js/features/preview/components/preview-download-button.js

@@ -30,7 +30,7 @@ function PreviewDownloadButton({
       href={pdfDownloadUrl || '#'}
       style={{ pointerEvents: 'auto' }}
     >
-      <Icon type="download" modifier="fw" />
+      <Icon type="download" fw />
       <span className="toolbar-text" style={textStyle}>
         {t('download_pdf')}
       </span>

+ 2 - 1
services/web/frontend/js/features/preview/components/preview-logs-pane-max-entries.js

@@ -15,7 +15,8 @@ function PreviewLogsPaneMaxEntries({ totalEntries, entriesShown, hasErrors }) {
     <div className="log-entry" aria-label={t('log_entry_maximum_entries')}>
       <PreviewLogEntryHeader level="raw" headerTitle={title} />
       <div className="log-entry-content">
-        <Icon type="lightbulb-o" />{' '}
+        <Icon type="lightbulb-o" />
+        &nbsp;
         {hasErrors ? (
           <Trans
             i18nKey="log_entry_maximum_entries_message"

+ 1 - 1
services/web/frontend/js/features/preview/components/preview-logs-pane.js

@@ -172,7 +172,7 @@ function AutoCompileLintingErrorEntry() {
     <div className="log-entry">
       <div className="log-entry-header log-entry-header-error">
         <div className="log-entry-header-icon-container">
-          <Icon type="exclamation-triangle" modifier="fw" />
+          <Icon type="exclamation-triangle" fw />
         </div>
         <h3 className="log-entry-header-title">
           {t('code_check_failed_explanation')}

+ 7 - 7
services/web/frontend/js/features/preview/components/preview-recompile-button.js

@@ -104,33 +104,33 @@ function PreviewRecompileButton({
       <Dropdown.Menu>
         <MenuItem header>{t('auto_compile')}</MenuItem>
         <MenuItem onSelect={handleSelectAutoCompileOn}>
-          <Icon type={isAutoCompileOn ? 'check' : ''} modifier="fw" />
+          <Icon type={isAutoCompileOn ? 'check' : ''} fw />
           {t('on')}
         </MenuItem>
         <MenuItem onSelect={handleSelectAutoCompileOff}>
-          <Icon type={!isAutoCompileOn ? 'check' : ''} modifier="fw" />
+          <Icon type={!isAutoCompileOn ? 'check' : ''} fw />
           {t('off')}
         </MenuItem>
         <MenuItem header>{t('compile_mode')}</MenuItem>
         <MenuItem onSelect={handleSelectDraftModeOff}>
-          <Icon type={!isDraftModeOn ? 'check' : ''} modifier="fw" />
+          <Icon type={!isDraftModeOn ? 'check' : ''} fw />
           {t('normal')}
         </MenuItem>
         <MenuItem onSelect={handleSelectDraftModeOn}>
-          <Icon type={isDraftModeOn ? 'check' : ''} modifier="fw" />
+          <Icon type={isDraftModeOn ? 'check' : ''} fw />
           {t('fast')} <span className="subdued">[draft]</span>
         </MenuItem>
         <MenuItem header>Syntax Checks</MenuItem>
         <MenuItem onSelect={handleSelectSyntaxCheckOn}>
-          <Icon type={isSyntaxCheckOn ? 'check' : ''} modifier="fw" />
+          <Icon type={isSyntaxCheckOn ? 'check' : ''} fw />
           {t('stop_on_validation_error')}
         </MenuItem>
         <MenuItem onSelect={handleSelectSyntaxCheckOff}>
-          <Icon type={!isSyntaxCheckOn ? 'check' : ''} modifier="fw" />
+          <Icon type={!isSyntaxCheckOn ? 'check' : ''} fw />
           {t('ignore_validation_errors')}
         </MenuItem>
         <MenuItem onSelect={onRunSyntaxCheckNow}>
-          <Icon type="" modifier="fw" />
+          <Icon type="" fw />
           {t('run_syntax_check_now')}
         </MenuItem>
         <MenuItem className={!isCompiling ? 'hidden' : ''} divider />

+ 3 - 3
services/web/frontend/js/features/share-project-modal/components/select-collaborators.js

@@ -270,7 +270,7 @@ function Option({ selected, item, getItemProps, index }) {
       className={classnames('suggestion-item', { selected })}
       {...getItemProps({ item, index })}
     >
-      <Icon type="user" modifier="fw" />
+      <Icon type="user" fw />
       &nbsp;
       {item.display}
     </li>
@@ -309,7 +309,7 @@ function SelectedItem({
       className="tag-item"
       {...getSelectedItemProps({ selectedItem, index })}
     >
-      <Icon type="user" modifier="fw" />
+      <Icon type="user" fw />
       <span>{selectedItem.display}</span>
       <button
         type="button"
@@ -317,7 +317,7 @@ function SelectedItem({
         aria-label={t('remove')}
         onClick={handleClick}
       >
-        <Icon type="close" modifier="fw" />
+        <Icon type="close" fw />
       </button>
     </span>
   )

+ 3 - 1
services/web/frontend/js/features/word-count-modal/components/word-count-modal-content.js

@@ -22,7 +22,9 @@ export default function WordCountModalContent({ handleHide }) {
       <Modal.Body>
         {loading && !error && (
           <div className="loading">
-            <Icon type="refresh" spin modifier="fw" /> &nbsp; {t('loading')}…
+            <Icon type="refresh" spin fw />
+            &nbsp;
+            {t('loading')}…
           </div>
         )}
 

+ 1 - 1
services/web/frontend/js/shared/components/icon-checked.js

@@ -4,7 +4,7 @@ import Icon from './icon'
 
 function IconChecked() {
   const { t } = useTranslation()
-  return <Icon type="check" modifier="fw" accessibilityLabel={t('selected')} />
+  return <Icon type="check" fw accessibilityLabel={t('selected')} />
 }
 
 export default IconChecked

+ 18 - 15
services/web/frontend/js/shared/components/icon.js

@@ -1,32 +1,40 @@
 import PropTypes from 'prop-types'
 import classNames from 'classnames'
 
+/**
+ * @param {string} type
+ * @param {boolean} [spin]
+ * @param {boolean} [fw]
+ * @param {string} [modifier]
+ * @param {string} [className]
+ * @param {string} [accessibilityLabel]
+ * @return {JSX.Element}
+ */
 function Icon({
   type,
   spin,
+  fw,
   modifier,
-  classes = {},
+  className = '',
   accessibilityLabel,
-  children,
 }) {
   const iconClassName = classNames(
     'fa',
     `fa-${type}`,
     {
       'fa-spin': spin,
+      'fa-fw': fw,
       [`fa-${modifier}`]: modifier,
     },
-    classes.icon
+    className
   )
 
   return (
     <>
-      <i className={iconClassName} aria-hidden="true">
-        {children}
-      </i>
-      {accessibilityLabel ? (
+      <i className={iconClassName} aria-hidden="true" />
+      {accessibilityLabel && (
         <span className="sr-only">{accessibilityLabel}</span>
-      ) : null}
+      )}
     </>
   )
 }
@@ -34,15 +42,10 @@ function Icon({
 Icon.propTypes = {
   type: PropTypes.string.isRequired,
   spin: PropTypes.bool,
+  fw: PropTypes.bool,
   modifier: PropTypes.string,
-  classes: PropTypes.exact({
-    icon: PropTypes.string,
-  }),
+  className: PropTypes.string,
   accessibilityLabel: PropTypes.string,
-  children: PropTypes.oneOfType([
-    PropTypes.arrayOf(PropTypes.node),
-    PropTypes.node,
-  ]),
 }
 
 export default Icon

+ 1 - 1
services/web/frontend/js/shared/components/loading-spinner.js

@@ -24,7 +24,7 @@ function LoadingSpinner({ delay = 500 }) {
 
   return (
     <div className="loading">
-      <Icon type="fw" modifier="refresh" spin />
+      <Icon type="refresh" fw spin />
       &nbsp;
       {t('loading')}…
     </div>

+ 2 - 1
services/web/frontend/js/shared/components/processing.js

@@ -7,7 +7,8 @@ function Processing({ isProcessing }) {
   if (isProcessing) {
     return (
       <div aria-live="polite">
-        {t('processing')}… <Icon type="fw" modifier="refresh" spin />
+        {t('processing')}…&nbsp;
+        <Icon type="refresh" fw spin />
       </div>
     )
   } else {

+ 1 - 1
services/web/frontend/stories/icon.stories.js

@@ -33,7 +33,7 @@ export const FixedWidth = args => {
 }
 FixedWidth.args = {
   type: 'tasks',
-  modifier: 'fw',
+  fw: true,
 }
 
 export const AccessibilityLabel = args => {

+ 11 - 4
services/web/frontend/stylesheets/app/editor/file-tree.less

@@ -171,13 +171,20 @@
       i.fa {
         color: @file-tree-item-icon-color;
         font-size: 14px;
-        &.linked-file-highlight {
+      }
+
+      i.fa.linked-file-icon {
+        position: relative;
+        left: -2px;
+
+        + .linked-file-highlight {
           & {
             color: white;
           }
           position: relative;
           top: 4px;
-          left: -8px;
+          width: 0;
+          left: -2px;
           font-size: 12px;
         }
       }
@@ -227,7 +234,7 @@
             .entity-menu-toggle i.fa {
               color: #fff;
             }
-            > i.fa i.linked-file-highlight {
+            > i.linked-file-highlight {
               color: @blue;
             }
             color: #fff;
@@ -296,7 +303,7 @@
           .entity-menu-toggle i.fa {
             color: @file-tree-item-selected-color;
           }
-          > i.fa i.linked-file-highlight {
+          > i.linked-file-highlight {
             color: @blue;
           }
           background-color: @file-tree-item-selected-bg;

+ 7 - 13
services/web/test/frontend/shared/components/icon.test.js

@@ -27,6 +27,12 @@ describe('<Icon />', function () {
     expect(element).to.exist
   })
 
+  it('renders with fw', function () {
+    const { container } = render(<Icon type="angle-down" fw />)
+    const element = container.querySelector('i.fa.fa-angle-down.fa-fw')
+    expect(element).to.exist
+  })
+
   it('renders with modifier', function () {
     const { container } = render(<Icon type="angle-down" modifier="2x" />)
     const element = container.querySelector('i.fa.fa-angle-down.fa-2x')
@@ -35,23 +41,11 @@ describe('<Icon />', function () {
 
   it('renders with custom clases', function () {
     const { container } = render(
-      <Icon type="angle-down" classes={{ icon: 'custom-icon-class' }} />
+      <Icon type="angle-down" className="custom-icon-class" />
     )
     const element = container.querySelector(
       'i.fa.fa-angle-down.custom-icon-class'
     )
     expect(element).to.exist
   })
-
-  it('renders children', function () {
-    const { container } = render(
-      <Icon type="angle-down">
-        <Icon type="angle-up" />
-      </Icon>
-    )
-    const element = container.querySelector(
-      'i.fa.fa-angle-down > i.fa.fa-angle-up'
-    )
-    expect(element).to.exist
-  })
 })