Browse Source

removed refs to defaultIconReact and IIconregistry from non ui-x pkgs

telamonian 5 years ago
parent
commit
a7566c2a13

+ 37 - 23
packages/apputils/src/toolbar.tsx

@@ -1,25 +1,24 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
-import { UseSignal, ReactWidget } from './vdom';
-
-import { Button, DefaultIconReact } from '@jupyterlab/ui-components';
+import { Text } from '@jupyterlab/coreutils';
+import {
+  Button,
+  JLIcon,
+  refreshIcon,
+  stopIcon
+} from '@jupyterlab/ui-components';
 
 import { IIterator, find, map, some } from '@lumino/algorithm';
-
 import { CommandRegistry } from '@lumino/commands';
-
+import { ReadonlyJSONObject } from '@lumino/coreutils';
 import { Message, MessageLoop } from '@lumino/messaging';
-
 import { AttachedProperty } from '@lumino/properties';
-
 import { PanelLayout, Widget } from '@lumino/widgets';
+import * as React from 'react';
 
 import { ISessionContext, sessionContextDialogs } from './sessioncontext';
-
-import * as React from 'react';
-import { ReadonlyJSONObject } from '@lumino/coreutils';
-import { Text } from '@jupyterlab/coreutils';
+import { UseSignal, ReactWidget } from './vdom';
 
 /**
  * The class name added to toolbars.
@@ -372,7 +371,7 @@ export namespace Toolbar {
     sessionContext: ISessionContext
   ): Widget {
     return new ToolbarButton({
-      iconClassName: 'jp-StopIcon',
+      iconRenderer: stopIcon,
       onClick: () => {
         void sessionContext.session?.kernel?.interrupt();
       },
@@ -388,7 +387,7 @@ export namespace Toolbar {
     dialogs?: ISessionContext.IDialogs
   ): Widget {
     return new ToolbarButton({
-      iconClassName: 'jp-RefreshIcon',
+      iconRenderer: refreshIcon,
       onClick: () => {
         void (dialogs ?? sessionContextDialogs).restart(sessionContext);
       },
@@ -453,8 +452,9 @@ export namespace ToolbarButtonComponent {
   export interface IProps {
     className?: string;
     label?: string;
-    iconClassName?: string;
+    iconClass?: string;
     iconLabel?: string;
+    iconRenderer?: JLIcon;
     tooltip?: string;
     onClick?: () => void;
     enabled?: boolean;
@@ -499,14 +499,20 @@ export function ToolbarButtonComponent(props: ToolbarButtonComponent.IProps) {
       title={props.tooltip || props.iconLabel}
       minimal
     >
-      {props.iconClassName && (
-        <DefaultIconReact
-          name={`${props.iconClassName} jp-Icon jp-Icon-16`}
-          className={'jp-ToolbarButtonComponent-icon'}
-          fallback={true}
+      {props.iconRenderer ? (
+        <props.iconRenderer.react
+          className="jp-ToolbarButtonComponent-icon"
+          tag="span"
+          center={true}
+          kind="toolbarButton"
+        />
+      ) : (
+        <JLIcon.getReact
+          name={`${props.iconClass} jp-Icon jp-Icon-16`}
+          className="jp-ToolbarButtonComponent-icon"
+          tag="span"
           center={true}
-          kind={'toolbarButton'}
-          tag={'span'}
+          kind="toolbarButton"
         />
       )}
       {props.label && (
@@ -612,7 +618,7 @@ namespace Private {
     options: CommandToolbarButtonComponent.IProps
   ): ToolbarButtonComponent.IProps {
     let { commands, id, args } = options;
-    const iconClassName = commands.iconClass(id, args);
+    const iconClass = commands.iconClass(id, args);
     const iconLabel = commands.iconLabel(id, args);
     const label = commands.label(id, args);
     let className = commands.className(id, args);
@@ -628,7 +634,7 @@ namespace Private {
       void commands.execute(id, args);
     };
     const enabled = commands.isEnabled(id, args);
-    return { className, iconClassName, tooltip, onClick, enabled, label };
+    return { className, iconClass, tooltip, onClick, enabled, label };
   }
 
   /**
@@ -724,6 +730,14 @@ namespace Private {
 
       let status = sessionContext.kernelDisplayStatus;
 
+      // TODO: use the following to set the kernel status icon
+      // // set the icon
+      // if (this._isBusy(status)) {
+      //   circle.element({container: this.node, title: `Kernel ${Text.titleCase(status)}`, center: true, kind: 'toolbarButton'});
+      // } else {
+      //   emptyCircleIcon.element({container: this.node, title: `Kernel ${Text.titleCase(status)}`, center: true, kind: 'toolbarButton'});
+      // }
+
       const busy = this._isBusy(status);
       this.toggleClass(TOOLBAR_BUSY_CLASS, busy);
       this.toggleClass(TOOLBAR_IDLE_CLASS, !busy);

+ 9 - 9
packages/extensionmanager/src/widget.tsx

@@ -2,19 +2,19 @@
 // Distributed under the terms of the Modified BSD License.
 
 import { VDomRenderer, ToolbarButtonComponent } from '@jupyterlab/apputils';
-
 import { ServiceManager } from '@jupyterlab/services';
+import {
+  Button,
+  InputGroup,
+  Collapse,
+  refreshIcon
+} from '@jupyterlab/ui-components';
 
 import { Message } from '@lumino/messaging';
-
-import { Button, InputGroup, Collapse } from '@jupyterlab/ui-components';
-
 import * as React from 'react';
-
 import ReactPaginate from 'react-paginate';
 
 import { ListModel, IEntry, Action } from './model';
-
 import { isJupyterOrg } from './query';
 
 // TODO: Replace pagination with lazy loading of lower search results
@@ -335,6 +335,7 @@ export class CollapsibleSection extends React.Component<
     };
   }
 
+  // TODO: swtich to iconRenderer
   /**
    * Render the collapsible section using the virtual DOM.
    */
@@ -343,7 +344,7 @@ export class CollapsibleSection extends React.Component<
       <>
         <header>
           <ToolbarButtonComponent
-            iconClassName={
+            iconClass={
               this.state.isOpen
                 ? 'jp-extensionmanager-expandIcon'
                 : 'jp-extensionmanager-collapseIcon'
@@ -536,8 +537,7 @@ export class ExtensionView extends VDomRenderer<ListModel> {
           headerElements={
             <ToolbarButtonComponent
               key="refresh-button"
-              className="jp-extensionmanager-refresh"
-              iconClassName="jp-RefreshIcon"
+              iconRenderer={refreshIcon}
               onClick={() => {
                 model.refreshInstalled();
               }}

+ 3 - 5
packages/filebrowser-extension/src/index.ts

@@ -42,7 +42,7 @@ import { IStateDB } from '@jupyterlab/statedb';
 
 import { IStatusBar } from '@jupyterlab/statusbar';
 
-import { folderIcon, IIconRegistry } from '@jupyterlab/ui-components';
+import { addIcon, folderIcon } from '@jupyterlab/ui-components';
 
 import { IIterator, map, reduce, toArray } from '@lumino/algorithm';
 
@@ -133,7 +133,7 @@ const factory: JupyterFrontEndPlugin<IFileBrowserFactory> = {
   activate: activateFactory,
   id: '@jupyterlab/filebrowser-extension:factory',
   provides: IFileBrowserFactory,
-  requires: [IIconRegistry, IDocumentManager],
+  requires: [IDocumentManager],
   optional: [IStateDB, IRouter, JupyterFrontEnd.ITreeResolver]
 };
 
@@ -211,7 +211,6 @@ export default plugins;
  */
 async function activateFactory(
   app: JupyterFrontEnd,
-  icoReg: IIconRegistry,
   docManager: IDocumentManager,
   state: IStateDB | null,
   router: IRouter | null,
@@ -225,7 +224,6 @@ async function activateFactory(
   ) => {
     const model = new FileBrowserModel({
       auto: options.auto ?? true,
-      iconRegistry: icoReg,
       manager: docManager,
       driveName: options.driveName || '',
       refreshInterval: options.refreshInterval,
@@ -237,7 +235,7 @@ async function activateFactory(
 
     // Add a launcher toolbar item.
     let launcher = new ToolbarButton({
-      iconClassName: 'jp-AddIcon',
+      iconRenderer: addIcon,
       onClick: () => {
         return Private.createLauncher(commands, widget);
       },

+ 4 - 2
packages/filebrowser/src/browser.ts

@@ -7,6 +7,8 @@ import { IDocumentManager } from '@jupyterlab/docmanager';
 
 import { Contents, ServerConnection } from '@jupyterlab/services';
 
+import { newFolderIcon, refreshIcon } from '@jupyterlab/ui-components';
+
 import { IIterator } from '@lumino/algorithm';
 
 import { PanelLayout, Widget } from '@lumino/widgets';
@@ -67,7 +69,7 @@ export class FileBrowser extends Widget {
     this._directoryPending = false;
 
     const newFolder = new ToolbarButton({
-      iconClassName: 'jp-NewFolderIcon',
+      iconRenderer: newFolderIcon,
       onClick: () => {
         this.createNewDirectory();
       },
@@ -75,7 +77,7 @@ export class FileBrowser extends Widget {
     });
     const uploader = new Uploader({ model });
     const refresher = new ToolbarButton({
-      iconClassName: 'jp-RefreshIcon',
+      iconRenderer: refreshIcon,
       onClick: () => {
         void model.refresh();
       },

+ 8 - 10
packages/filebrowser/src/listing.ts

@@ -20,7 +20,7 @@ import { DocumentRegistry } from '@jupyterlab/docregistry';
 
 import { Contents } from '@jupyterlab/services';
 
-import { fileIcon, IIconRegistry, JLIcon } from '@jupyterlab/ui-components';
+import { fileIcon, JLIcon } from '@jupyterlab/ui-components';
 
 import {
   ArrayExt,
@@ -193,9 +193,7 @@ export class DirListing extends Widget {
    */
   constructor(options: DirListing.IOptions) {
     super({
-      node: (options.renderer =
-        options.renderer ||
-        new DirListing.Renderer(options.model.iconRegistry)).createNode()
+      node: (options.renderer || DirListing.defaultRenderer).createNode()
     });
     this.addClass(DIR_LISTING_CLASS);
     this._model = options.model;
@@ -205,7 +203,7 @@ export class DirListing extends Widget {
     this._editNode = document.createElement('input');
     this._editNode.className = EDITOR_CLASS;
     this._manager = this._model.manager;
-    this._renderer = options.renderer;
+    this._renderer = options.renderer || DirListing.defaultRenderer;
 
     const headerNode = DOMUtils.findElement(this.node, HEADER_CLASS);
     this._renderer.populateHeaderNode(headerNode);
@@ -1685,10 +1683,6 @@ export namespace DirListing {
    * The default implementation of an `IRenderer`.
    */
   export class Renderer implements IRenderer {
-    constructor(icoReg: IIconRegistry) {
-      this._iconRegistry = icoReg;
-    }
-
     /**
      * Create the DOM node for a dir listing.
      */
@@ -1928,8 +1922,12 @@ export namespace DirListing {
       node.appendChild(icon);
       return node;
     }
-    _iconRegistry: IIconRegistry;
   }
+
+  /**
+   * The default `IRenderer` instance.
+   */
+  export const defaultRenderer = new Renderer();
 }
 
 /**

+ 0 - 13
packages/filebrowser/src/model.ts

@@ -11,8 +11,6 @@ import { Contents, KernelSpec, Session } from '@jupyterlab/services';
 
 import { IStateDB } from '@jupyterlab/statedb';
 
-import { IIconRegistry } from '@jupyterlab/ui-components';
-
 import {
   ArrayIterator,
   each,
@@ -69,7 +67,6 @@ export class FileBrowserModel implements IDisposable {
    * Construct a new file browser model.
    */
   constructor(options: FileBrowserModel.IOptions) {
-    this.iconRegistry = options.iconRegistry;
     this.manager = options.manager;
     this._driveName = options.driveName || '';
     let rootPath = this._driveName ? this._driveName + ':' : '';
@@ -113,11 +110,6 @@ export class FileBrowserModel implements IDisposable {
     });
   }
 
-  /**
-   * The icon registry instance used by the file browser model.
-   */
-  readonly iconRegistry: IIconRegistry;
-
   /**
    * The document manager instance used by the file browser model.
    */
@@ -670,11 +662,6 @@ export namespace FileBrowserModel {
      */
     driveName?: string;
 
-    /**
-     * An icon registry instance.
-     */
-    iconRegistry: IIconRegistry;
-
     /**
      * A document manager instance.
      */

+ 1 - 17
packages/filebrowser/src/opendialog.ts

@@ -8,7 +8,6 @@ import { PathExt } from '@jupyterlab/coreutils';
 import { Dialog } from '@jupyterlab/apputils';
 import { IDocumentManager } from '@jupyterlab/docmanager';
 import { Contents } from '@jupyterlab/services';
-import { IIconRegistry } from '@jupyterlab/ui-components';
 
 import { FileBrowser } from './browser';
 import { FilterFileBrowserModel } from './model';
@@ -36,11 +35,6 @@ export namespace FileDialog {
         >
       >
     > {
-    /**
-     * An icon registry instance.
-     */
-    iconRegistry: IIconRegistry;
-
     /**
      * Document manager
      */
@@ -81,11 +75,7 @@ export namespace FileDialog {
       focusNodeSelector: options.focusNodeSelector,
       host: options.host,
       renderer: options.renderer,
-      body: new OpenDialog(
-        options.iconRegistry,
-        options.manager,
-        options.filter
-      )
+      body: new OpenDialog(options.manager, options.filter)
     };
     let dialog = new Dialog(dialogOptions);
     return dialog.launch();
@@ -117,7 +107,6 @@ export namespace FileDialog {
 class OpenDialog extends Widget
   implements Dialog.IBodyWidget<Contents.IModel[]> {
   constructor(
-    iconRegistry: IIconRegistry,
     manager: IDocumentManager,
     filter?: (value: Contents.IModel) => boolean
   ) {
@@ -126,7 +115,6 @@ class OpenDialog extends Widget
 
     this._browser = Private.createFilteredFileBrowser(
       'filtered-file-browser-dialog',
-      iconRegistry,
       manager,
       filter
     );
@@ -173,8 +161,6 @@ namespace Private {
    *
    * @param id - The widget/DOM id of the file browser.
    *
-   * @param iconRegistry - An icon registry instance.
-   *
    * @param manager - A document manager instance.
    *
    * @param filter - function to filter file browser item.
@@ -194,13 +180,11 @@ namespace Private {
    */
   export const createFilteredFileBrowser = (
     id: string,
-    iconRegistry: IIconRegistry,
     manager: IDocumentManager,
     filter?: (value: Contents.IModel) => boolean,
     options: IFileBrowserFactory.IOptions = {}
   ) => {
     const model = new FilterFileBrowserModel({
-      iconRegistry,
       manager,
       filter,
       driveName: options.driveName,

+ 2 - 1
packages/filebrowser/src/upload.ts

@@ -2,6 +2,7 @@
 // Distributed under the terms of the Modified BSD License.
 
 import { ToolbarButton, showErrorMessage } from '@jupyterlab/apputils';
+import { fileUploadIcon } from '@jupyterlab/ui-components';
 
 import { FileBrowserModel } from './model';
 
@@ -14,7 +15,7 @@ export class Uploader extends ToolbarButton {
    */
   constructor(options: Uploader.IOptions) {
     super({
-      iconClassName: 'jp-FileUploadIcon',
+      iconRenderer: fileUploadIcon,
       onClick: () => {
         this._input.click();
       },

+ 1 - 0
packages/htmlviewer/package.json

@@ -35,6 +35,7 @@
     "@jupyterlab/apputils": "^2.0.0-alpha.4",
     "@jupyterlab/coreutils": "^4.0.0-alpha.4",
     "@jupyterlab/docregistry": "^2.0.0-alpha.4",
+    "@jupyterlab/ui-components": "^2.0.0-alpha.4",
     "@lumino/coreutils": "^1.4.0",
     "@lumino/signaling": "^1.3.2",
     "react": "~16.9.0"

+ 3 - 1
packages/htmlviewer/src/index.tsx

@@ -21,6 +21,8 @@ import {
   IDocumentWidget
 } from '@jupyterlab/docregistry';
 
+import { refreshIcon } from '@jupyterlab/ui-components';
+
 import { Token } from '@lumino/coreutils';
 
 import { ISignal, Signal } from '@lumino/signaling';
@@ -87,7 +89,7 @@ export class HTMLViewer extends DocumentWidget<IFrame>
     this.toolbar.addItem(
       'refresh',
       new ToolbarButton({
-        iconClassName: 'jp-RefreshIcon',
+        iconRenderer: refreshIcon,
         onClick: () => {
           this.content.url = this.content.url;
         },

+ 3 - 0
packages/htmlviewer/tsconfig.json

@@ -14,6 +14,9 @@
     },
     {
       "path": "../docregistry"
+    },
+    {
+      "path": "../ui-components"
     }
   ]
 }

+ 18 - 46
packages/notebook/src/default-toolbar.tsx

@@ -1,13 +1,8 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
-import * as React from 'react';
-
-import { DocumentRegistry } from '@jupyterlab/docregistry';
-
 import { Widget } from '@lumino/widgets';
-
-import { NotebookActions } from './actions';
+import * as React from 'react';
 
 import {
   showDialog,
@@ -20,45 +15,22 @@ import {
   ToolbarButton,
   ISessionContextDialogs
 } from '@jupyterlab/apputils';
-
+import { DocumentRegistry } from '@jupyterlab/docregistry';
 import * as nbformat from '@jupyterlab/nbformat';
+import {
+  addIcon,
+  copyIcon,
+  cutIcon,
+  HTMLSelect,
+  pasteIcon,
+  runIcon,
+  saveIcon
+} from '@jupyterlab/ui-components';
 
-import { HTMLSelect } from '@jupyterlab/ui-components';
-
+import { NotebookActions } from './actions';
 import { NotebookPanel } from './panel';
-
 import { Notebook } from './widget';
 
-/**
- * The class name added to toolbar save button.
- */
-const TOOLBAR_SAVE_CLASS = 'jp-SaveIcon';
-
-/**
- * The class name added to toolbar insert button.
- */
-const TOOLBAR_INSERT_CLASS = 'jp-AddIcon';
-
-/**
- * The class name added to toolbar cut button.
- */
-const TOOLBAR_CUT_CLASS = 'jp-CutIcon';
-
-/**
- * The class name added to toolbar copy button.
- */
-const TOOLBAR_COPY_CLASS = 'jp-CopyIcon';
-
-/**
- * The class name added to toolbar paste button.
- */
-const TOOLBAR_PASTE_CLASS = 'jp-PasteIcon';
-
-/**
- * The class name added to toolbar run button.
- */
-const TOOLBAR_RUN_CLASS = 'jp-RunIcon';
-
 /**
  * The class name added to toolbar cell type dropdown wrapper.
  */
@@ -96,7 +68,7 @@ export namespace ToolbarItems {
         <UseSignal signal={panel.context.fileChanged}>
           {() => (
             <ToolbarButtonComponent
-              iconClassName={TOOLBAR_SAVE_CLASS}
+              iconRenderer={saveIcon}
               onClick={onClick}
               tooltip="Save the notebook contents and create checkpoint"
               enabled={
@@ -119,7 +91,7 @@ export namespace ToolbarItems {
    */
   export function createInsertButton(panel: NotebookPanel): Widget {
     return new ToolbarButton({
-      iconClassName: TOOLBAR_INSERT_CLASS,
+      iconRenderer: addIcon,
       onClick: () => {
         NotebookActions.insertBelow(panel.content);
       },
@@ -132,7 +104,7 @@ export namespace ToolbarItems {
    */
   export function createCutButton(panel: NotebookPanel): Widget {
     return new ToolbarButton({
-      iconClassName: TOOLBAR_CUT_CLASS,
+      iconRenderer: cutIcon,
       onClick: () => {
         NotebookActions.cut(panel.content);
       },
@@ -145,7 +117,7 @@ export namespace ToolbarItems {
    */
   export function createCopyButton(panel: NotebookPanel): Widget {
     return new ToolbarButton({
-      iconClassName: TOOLBAR_COPY_CLASS,
+      iconRenderer: copyIcon,
       onClick: () => {
         NotebookActions.copy(panel.content);
       },
@@ -158,7 +130,7 @@ export namespace ToolbarItems {
    */
   export function createPasteButton(panel: NotebookPanel): Widget {
     return new ToolbarButton({
-      iconClassName: TOOLBAR_PASTE_CLASS,
+      iconRenderer: pasteIcon,
       onClick: () => {
         NotebookActions.paste(panel.content);
       },
@@ -171,7 +143,7 @@ export namespace ToolbarItems {
    */
   export function createRunButton(panel: NotebookPanel): Widget {
     return new ToolbarButton({
-      iconClassName: TOOLBAR_RUN_CLASS,
+      iconRenderer: runIcon,
       onClick: () => {
         void NotebookActions.runAndAdvance(panel.content, panel.sessionContext);
       },

+ 5 - 2
packages/running/src/index.tsx

@@ -180,6 +180,8 @@ function Section(props: { manager: IRunningSessions.IManager }) {
       }
     });
   }
+
+  // TODO: replace iconClass below with iconRenderer
   return (
     <div className={SECTION_CLASS}>
       <>
@@ -187,7 +189,7 @@ function Section(props: { manager: IRunningSessions.IManager }) {
           <h2>{props.manager.name} Sessions</h2>
           <ToolbarButtonComponent
             tooltip={`Shut Down All ${props.manager.name} Sessions…`}
-            iconClassName="jp-CloseIcon jp-Icon jp-Icon-16"
+            iconClass="jp-CloseIcon jp-Icon jp-Icon-16"
             onClick={onShutdown}
           />
         </header>
@@ -203,12 +205,13 @@ function Section(props: { manager: IRunningSessions.IManager }) {
 function RunningSessionsComponent(props: {
   managers: IRunningSessionManagers;
 }) {
+  // TODO: replace iconClass below with iconRenderer
   return (
     <>
       <div className={HEADER_CLASS}>
         <ToolbarButtonComponent
           tooltip="Refresh List"
-          iconClassName="jp-RefreshIcon jp-Icon jp-Icon-16"
+          iconClass="jp-RefreshIcon jp-Icon jp-Icon-16"
           onClick={() =>
             props.managers.items().forEach(manager => manager.refreshRunning())
           }

+ 2 - 3
packages/statusbar/src/defaults/lineCol.tsx

@@ -7,7 +7,7 @@ import { VDomRenderer, VDomModel, ReactWidget } from '@jupyterlab/apputils';
 
 import { CodeEditor } from '@jupyterlab/codeeditor';
 
-import { DefaultIconReact } from '@jupyterlab/ui-components';
+import { lineFormIcon } from '@jupyterlab/ui-components';
 
 import { classes } from 'typestyle/lib';
 
@@ -116,8 +116,7 @@ class LineFormComponent extends React.Component<
               }}
             />
             <div className={lineFormButtonDiv}>
-              <DefaultIconReact
-                name="line-form"
+              <lineFormIcon.react
                 className={lineFormButtonIcon}
                 center={true}
               />

+ 3 - 8
packages/statusbar/src/defaults/runningSessions.tsx

@@ -13,7 +13,7 @@ import {
   Session
 } from '@jupyterlab/services';
 
-import { DefaultIconReact } from '@jupyterlab/ui-components';
+import { kernelIcon, terminalIcon } from '@jupyterlab/ui-components';
 
 import { GroupItem, interactiveItem, TextItem } from '..';
 
@@ -36,16 +36,11 @@ function RunningSessionsComponent(
     <GroupItem spacing={HALF_SPACING} onClick={props.handleClick}>
       <GroupItem spacing={HALF_SPACING}>
         <TextItem source={props.terminals} />
-        <DefaultIconReact
-          name={'terminal'}
-          left={'1px'}
-          top={'3px'}
-          kind={'statusBar'}
-        />
+        <terminalIcon.react left={'1px'} top={'3px'} kind={'statusBar'} />
       </GroupItem>
       <GroupItem spacing={HALF_SPACING}>
         <TextItem source={props.sessions} />
-        <DefaultIconReact name={'kernel'} top={'2px'} kind={'statusBar'} />
+        <kernelIcon.react top={'2px'} kind={'statusBar'} />
       </GroupItem>
     </GroupItem>
   );

+ 24 - 6
packages/ui-components/src/icon/jlicon.tsx

@@ -28,7 +28,8 @@ export class JLIcon {
    * @returns A JLIcon instance
    */
   private static _get(name: string, fallback?: JLIcon): JLIcon | undefined {
-    const icon = JLIcon._instances.get(name);
+    // TODO: correctly handle unordered multiple className case
+    const icon = JLIcon._instances.get(name.split(/\s+/)[0]);
 
     if (icon) {
       return icon;
@@ -132,6 +133,7 @@ export class JLIcon {
   element({
     className,
     container,
+    label,
     title,
     tag = 'div',
     ...propsStyle
@@ -164,7 +166,9 @@ export class JLIcon {
 
       ret = container;
     }
-
+    if (label != null) {
+      container.textContent = label;
+    }
     this._initContainer({ container, className, propsStyle, title });
 
     // add the svg node to the container
@@ -184,6 +188,7 @@ export class JLIcon {
     );
     const svgElement = svgDoc.documentElement;
 
+    // structure of error element varies by browser, search at top level
     if (svgDoc.getElementsByTagName('parsererror').length > 0) {
       const errmsg = `SVG HTML was malformed for icon name: ${name}`;
       // parse failed, svgElement will be an error box
@@ -236,7 +241,7 @@ export class JLIcon {
   }) {
     const classStyle = iconStyle(propsStyle);
 
-    if (className || className === '') {
+    if (className != null) {
       // override the container class with explicitly passed-in class + style class
       container.className = classes(className, classStyle);
     } else if (classStyle) {
@@ -244,7 +249,7 @@ export class JLIcon {
       container.classList.add(classStyle);
     }
 
-    if (title) {
+    if (title != null) {
       container.title = title;
     }
   }
@@ -255,6 +260,7 @@ export class JLIcon {
         {
           className,
           container,
+          label,
           title,
           tag = 'div',
           ...propsStyle
@@ -281,11 +287,17 @@ export class JLIcon {
         if (container) {
           this._initContainer({ container, className, propsStyle, title });
 
-          return svgComponent;
+          return (
+            <React.Fragment>
+              {svgComponent}
+              label
+            </React.Fragment>
+          );
         } else {
           return (
             <Tag className={classes(className, iconStyle(propsStyle))}>
               {svgComponent}
+              label
             </Tag>
           );
         }
@@ -332,6 +344,12 @@ export namespace JLIcon {
      */
     container?: HTMLElement;
 
+    /**
+     * Optional text label that will be added as a sibling to the icon's
+     * svg node
+     */
+    label?: string;
+
     /**
      * HTML element tag used to create the icon's outermost container node,
      * if no container is passed in
@@ -339,7 +357,7 @@ export namespace JLIcon {
     tag?: 'div' | 'span';
 
     /**
-     * Optional title that will be set on the icon's svg node
+     * Optional title that will be set on the icon's outermost container node
      */
     title?: string;
   }