Skip to content

Latest commit

Β 

History

History
302 lines (233 loc) Β· 13.8 KB

File metadata and controls

302 lines (233 loc) Β· 13.8 KB

Icon

Hoist's icon system provides a factory-based API for rendering FontAwesome Pro icons throughout an application. Rather than importing individual FA icon definitions in each file that uses them, applications use the Icon singleton β€” a centralized catalog of 150+ direct icon factories and ~40 semantic aliases, all pre-registered with the FA library in four weight variants (regular, solid, light, thin).

Overview

Icons are a core visual element across Hoist UIs β€” they appear in buttons, toolbars, grid columns, menus, tabs, tree nodes, toast messages, and more. The Icon singleton standardizes access to a curated set of FA Pro icons and provides:

  • Named factory methods β€” 150+ direct factories (Icon.check(), Icon.gear(), Icon.user(), etc.) plus ~40 semantic aliases
  • Semantic aliases β€” Icon.add(), Icon.edit(), Icon.delete(), Icon.search(), Icon.save(), Icon.refresh() that delegate to specific visual icons, providing a consistent vocabulary across apps
  • Weight variants β€” Switch between regular (default), solid, light, or thin via the prefix prop
  • Intent coloring β€” Apply primary, success, warning, or danger intent for consistent semantic styling
  • Size control β€” FA size values from 2xs through 10x
  • FA animation props β€” spin, pulse, beat, bounce, rotation, flip, and other FontAwesome props are passed through to the underlying FontAwesomeIcon component
  • Fixed-width default β€” All icons receive the fa-fw (fixed-width) and xh-icon CSS classes automatically, ensuring consistent spacing in menus, buttons, and toolbars
  • HTML mode β€” Render as raw SVG strings for non-React contexts (e.g. Highcharts tooltips)
  • File-type icons β€” Icon.fileIcon({filename}) maps extensions to appropriate icons

Architecture

icon/
β”œβ”€β”€ Icon.ts              # Icon singleton with all factory methods + IconProps type
β”œβ”€β”€ XHLogo.tsx           # XH corporate logo SVG component (theme-aware)
β”œβ”€β”€ index.ts             # Barrel exports + FA library registration (all icon imports)
└── impl/
    β”œβ”€β”€ IconCmp.ts       # React component wrapping FontAwesomeIcon
    └── IconHtml.ts      # Raw SVG string renderer for asHtml mode

The index.ts barrel file is where all FontAwesome icon definitions are imported from the @fortawesome/pro-regular-svg-icons, @fortawesome/pro-solid-svg-icons, @fortawesome/pro-light-svg-icons, and @fortawesome/pro-thin-svg-icons packages and registered with the FA library. This single registration point ensures every icon used by Hoist's factory methods is available at runtime.

Each factory method on Icon delegates to Icon.icon(), which creates either an IconCmp (a React component wrapping FA's FontAwesomeIcon) or an IconHtml (a raw SVG string), depending on the asHtml flag.

Usage Patterns

Basic Icons

import {Icon} from '@xh/hoist/icon';

// Named factory methods β€” the most common usage
Icon.check()
Icon.gear()
Icon.users()
Icon.chartLine()

// Semantic aliases β€” use these for common actions
Icon.add()       // β†’ Icon.plus()
Icon.edit()      // β†’ Icon.penToSquare()
Icon.delete()    // β†’ Icon.minusCircle()
Icon.search()    // β†’ Icon.magnifyingGlass()
Icon.save()      // β†’ Icon.floppyDisk()
Icon.refresh()   // β†’ Icon.arrowsRotate()
Icon.close()     // β†’ Icon.x()
Icon.download()  // β†’ Icon.arrowDownToBracket()
Icon.upload()    // β†’ Icon.arrowUpFromBracket()

With Intent and Size

// Intent applies an xh-intent-{name} CSS class for semantic coloring
Icon.check({intent: 'success'})
Icon.warning({intent: 'danger'})
Icon.infoCircle({intent: 'primary'})

// Size uses FA's size scale
Icon.spinner({size: 'lg'})
Icon.gear({size: '2x'})

FA Animation and Transform Props

Since IconProps extends FontAwesome's FontAwesomeIconProps, you can pass through FA animation and transform props directly:

Icon.spinner({spin: true})       // spinning loading indicator
Icon.bullhorn({shake: true})     // attention-grabbing announcement
Icon.star({rotation: 90})        // rotated 90 degrees
Icon.warning({bounce: true})     // bouncing warning

Spinner Component

The Spinner component (cmp/spinner/) renders an animated FA icon for use by Mask and LoadingIndicator. The rotation animation is applied via Hoist-owned CSS (@keyframes xh-spin on .xh-spinner) rather than FA's animation props. This ensures the spinner remains functional when the OS-level prefers-reduced-motion preference is enabled (FA disables all its animations in that case) and keeps performance predictable in remote desktop environments such as Citrix.

Spinner ships with several pre-registered icon choices - faSpinnerThird, faCircleNotch, and faSpinnerScale - all available in all four weight variants. The default icon and prefix can be configured globally via Spinner.defaults, typically set in an app's Bootstrap.ts:

import {Spinner} from '@xh/hoist/cmp/spinner';

// Override icon and/or weight globally
Spinner.defaults.iconName = 'circle-notch';
Spinner.defaults.prefix = 'far';
Default Type Default Description
Spinner.defaults.iconName IconName 'spinner-third' FA icon name for the spinner
Spinner.defaults.prefix HoistIconPrefix 'fal' FA icon weight/prefix
Spinner.defaults.usePng boolean false Fall back to animated PNG

Per-instance overrides can be passed as props to spinner() or via LoadingIndicator's spinner prop, which accepts either true (use defaults) or a SpinnerProps object:

loadingIndicator({
    bind: myTask,
    spinner: {iconName: 'circle-notch'}
})

A legacy PNG fallback is retained for environments where even CSS animations may be problematic. Set Spinner.defaults.usePng = true globally to revert to the original animated PNG behavior.

Weight Variants

// Default prefix is 'far' (regular)
Icon.star()                      // regular outline
Icon.star({prefix: 'fas'})      // solid fill
Icon.star({prefix: 'fal'})      // light stroke
Icon.star({prefix: 'fat'})      // thin stroke

File-Type Icons

Icon.fileIcon() maps file extensions to appropriate icons with optional type-specific CSS classes:

Icon.fileIcon({filename: 'report.pdf'})    // β†’ filePdf with xh-file-icon-pdf
Icon.fileIcon({filename: 'data.xlsx'})     // β†’ fileExcel with xh-file-icon-excel
Icon.fileIcon({filename: 'photo.jpg'})     // β†’ fileImage
Icon.fileIcon({filename: 'unknown.xyz'})   // β†’ file (generic fallback)

HTML Mode

Use asHtml: true to get a raw SVG string instead of a React element. This is needed in contexts that build HTML strings directly, such as Highcharts tooltip formatters.

Icon.check({asHtml: true})  // returns '<svg class="..."...'

Placeholder

Use Icon.placeholder() to create an empty element that takes up the same space as an icon. Useful for aligning items in menus or lists where some items have icons and others don't:

menuItem({icon: Icon.check(), text: 'Option A'}),
menuItem({icon: Icon.placeholder(), text: 'Option B'})  // aligned with A

App-Level Icon Catalogs

Applications are strongly encouraged to create their own Icons.ts file (typically in a core/ or common/ directory) to centralize icon usage across the app. This file serves two purposes:

  1. Register custom FA icons not included in Hoist's built-in set β€” import from the @fortawesome/pro-*-svg-icons packages and call library.add() to make them available at runtime. (This can also be done in Bootstrap.ts, but co-locating registration with the factories that use them keeps things organized.)
  2. Define app-specific semantic factories that map domain concepts to consistent icons

This pattern ensures that domain-specific icons are used consistently throughout the app. When a concept like "loan" or "invoice" always maps to the same icon, the app's visual language becomes more coherent β€” and changing an icon later requires only a single edit.

// src/core/Icons.ts
import {library} from '@fortawesome/fontawesome-svg-core';
import {faFileInvoiceDollar} from '@fortawesome/pro-regular-svg-icons';
import {Icon, IconProps} from '@xh/hoist/icon';

// 1. Register custom FA icons not in Hoist's built-in set
library.add(faFileInvoiceDollar);

// 2. Define app-specific semantic factories
//    Use Icon.icon() with iconName for custom-registered icons
export const invoiceIcon = (opts: IconProps = {}) =>
    Icon.icon({iconName: 'file-invoice-dollar', ...opts});

//    Delegate to existing Hoist factories to give them app-specific names
export const dealIcon = (opts: IconProps = {}) =>
    Icon.handshake(opts);
export const dashboardIcon = (opts: IconProps = {}) =>
    Icon.layout(opts);

// Factories can embed defaults for intent, size, or weight
export const approvedIcon = (opts: IconProps = {}) =>
    Icon.checkCircle({intent: 'success', ...opts});
export const rejectedIcon = (opts: IconProps = {}) =>
    Icon.xCircle({intent: 'danger', ...opts});

App-level factories follow the same (opts?) => Icon.xxx(opts) signature as Hoist's own factories, so they can be used interchangeably β€” in buttons, grid columns, menus, and anywhere else that accepts an icon element.

IconProps Reference

Prop Type Description
iconName IconName FA icon name (e.g. 'check', 'gear'). Required for Icon.icon(), provided automatically by named factories
prefix HoistIconPrefix Weight variant: 'far' (regular, default), 'fas' (solid), 'fal' (light), 'fat' (thin), 'fab' (brands)
intent Intent Applies xh-intent-{intent} CSS class for semantic coloring
title string Tooltip text rendered as SVG <title>
size string FA size: '2xs' through '10x'
asHtml boolean Return raw SVG string instead of React element
className string Additional CSS class(es)
omit Thunkable<boolean> Skip rendering this icon when true

Common Pitfalls

Importing FA Icons Directly Instead of Using Icon Factories

The Icon singleton pre-registers all its icons with the FA library. Importing individual FA icons in application code is only needed for icons not already in Hoist's set.

// βœ… Do: Use the Icon singleton
import {Icon} from '@xh/hoist/icon';
Icon.check()

// ❌ Don't: Import FA icons directly for icons Hoist already provides
import {faCheck} from '@fortawesome/pro-regular-svg-icons';

Forgetting prefix with Icon.icon()

When using Icon.icon() directly for custom icons, the default prefix is 'far' (regular). If you registered a solid-only icon, you must specify prefix: 'fas':

// βœ… Do: Match the prefix to how you registered the icon
Icon.icon({iconName: 'custom-icon', prefix: 'fas'})

// ❌ Don't: Assume regular prefix when you registered a solid icon β€” renders blank
Icon.icon({iconName: 'custom-icon'})

Using Non-FontAwesome Icon Libraries

Always use FontAwesome icons via Hoist's Icon singleton or the app-level Icons.ts pattern. Do not pull icons from other libraries (e.g. Blueprint icons, Material icons) unless the app has an explicit directive to do so. Mixing icon libraries breaks the cohesive visual language that FA provides, and FontAwesome Pro's catalog is extensive enough to cover virtually any use case. If you can't find the right icon in Hoist's pre-registered set, register a custom one from the FA Pro packages β€” don't reach for a different library.

Referencing Icons From the Wrong FontAwesome Version

FontAwesome updates frequently and adds new icons with each release. When browsing the FA site to find an icon for your app, use the version picker to filter results to the version Hoist currently depends on (check @fortawesome/pro-regular-svg-icons in package.json). Attempting to import an icon that only exists in a newer FA version will fail at build time. Hoist endeavors to keep its FA dependency up to date, but always verify the version before spending time wiring up a new icon.

Using Brand Icons Without Registration

The 'fab' (brands) prefix is supported but requires a separate import of @fortawesome/free-brands-svg-icons in your app's bootstrap code. Brand icons are not bundled with Hoist by default.

Related Packages

  • /cmp/spinner/ β€” Spinner component renders an animated FA icon, configurable via static defaults on the Spinner class
  • /desktop/ β€” Desktop components use icons extensively in buttons, toolbars, menus, and grid columns