Repository navigation
Conversation
Reduces the FontAwesome boilerplate apps carry today, and adds a control that lets end users pick
an icon from whatever the app has registered.
Icon registry
* `Icon.register()` / `Icon.registerAll()` take imported FA definitions, add them to the FA
library, install a factory on `Icon`, and return that factory for direct export - replacing the
manual `library.add()` + hand-rolled `Icon.icon({iconName})` wrapper pattern.
* Multiple weight variants register as one icon. A request for a weight the app never imported now
falls back to the icon's default variant rather than rendering blank - the most common trap when
wiring up a custom icon.
* `replace: true` supports overriding any existing factory, including Hoist's semantic aliases.
Required, so a name collision throws rather than silently clobbering a built-in.
* `Icon.get()`, `Icon.getFactory()`, `Icon.exists()`, `Icon.getCatalogEntry()` and
`Icon.getCatalog()` resolve icons by name, for dynamic and persisted icon values.
New `icon/impl/IconRegistry.ts` holds the catalog. Hoist's own icons are cataloged lazily by
calling each factory in `Icon.ts` and reading the icon it renders, so there is no parallel list to
maintain as icons are added there. To support that, the glyph and alias factories move into
exported `iconFactories` / `aliasFactories` maps, spread back onto the `Icon` singleton - no change
to the public API.
IconPicker
* New desktop input rendering a trigger button that opens a searchable grid of icons, with
keyboard navigation, a hover/highlight name in the footer, and an optional clear action.
* Options come from `Icon.getCatalog()`, so app-registered icons appear with no extra wiring, and
`keywords` supplied at registration are searchable.
* Value is the icon's FA name - a stable identifier to persist and render back via `Icon.get()`.
Apps now decide what the picker stores rather than taking Hoist's default. `valueField: 'iconName'` (default) persists the FA name of the glyph - unambiguous, pinned to what the user actually saw. `valueField: 'name'` persists the `Icon` factory name - more readable in stored data, and follows the app if it later re-points that factory at a different glyph. Both forms round-trip through `Icon.get()` and both are accepted as an incoming value regardless of the setting, so switching does not orphan already-persisted values. Supporting change: `IconCatalogEntry` gains `name`, the icon's primary `Icon` factory name - Hoist's own name for a built-in, or the registered name for a custom icon. Where several factories render the same glyph (`folder` and `tab`, `gridPanel` and `table`, ...), the one whose name matches the FA name wins, so the picker no longer labelled the folder icon "Tab". Verified against the full built-in catalog: every entry has a name, all 217 are unique, and both `name` and `iconName` resolve back to their own entry.
The configurable identifier was a false choice. An icon's `Icon` factory name belongs to the app, so persisting it couples stored data to a name the app is free to change: rename a registration from `invoice` to `invoiceIcon` and every value written under the old name is silently dead. That risk is worst exactly where custom icons live. The FA name comes from FontAwesome and is unaffected by anything an app does to its own factories, so it is the only identifier worth offering as a default - and having offered it, there is no case for the alternative. Apps that do want their own names can still convert on the way out via `Icon.getCatalogEntry(iconName).name`. `IconCatalogEntry.name` is retained - it remains the icon's primary factory name, and drives the display name shown in pickers.
Catches the branch up across the v88 major release (TC39 decorators, MobX 7) and 88.1.0. - CHANGELOG: kept develop's history and moved the Icon registry / IconPicker entries to the 89.0.0-SNAPSHOT New Features section. - desktop/cmp/input/index.ts: export both IconPicker and develop's new IntentInput. - IconPicker: migrated to TC39 decorators via the v88 codemods (`accessor` fields, no makeObservable constructor). - IconPicker: spread `domAttrs` onto the trigger button, matching Picker and the other inputs since develop added the prop to HoistInputProps.
…icons - React ignores autoFocus on a div, so with enableFilter: false keyboard nav did nothing until the user clicked or tabbed in. The menu is now focused via onOpened. - Icons given in the icons prop that resolve to the same glyph (e.g. 'gear' and 'cog') no longer render twice under a duplicate key.
Icon.invoice() does not compile in app code, as Icon is a const object. Point apps at the factory returned by Icon.register() and explain why runtime installation still matters for replacing built-ins.
The Icon singleton now holds lookup and registration methods next to its factories, so iterating Object.keys(Icon) no longer yields only icons. Noted as an 89 breaking change, and the icon README now points listing use cases at Icon.getCatalog().
Applied the clear-writing house style to the icon README, the IconPicker section of the desktop README, and this branch's CHANGELOG entries: no em dashes in prose, sentences under the length cap, active voice where an actor exists, and changelog bullets of three lines or fewer.
… iconName to faName Registration (from app-perspective review): - A name conflict without replace: true now logs a warning and keeps the existing icon instead of throwing, so a later Hoist built-in can never stop an app from starting. Names of Icon's own methods still throw. - Re-registering the same name and glyph (e.g. hot reload) updates quietly. - Registering an existing glyph under a new name adds an alias. The glyph keeps its name, label, weight, visibility and plain factory, so baked-in props never leak into FA-name lookups or pickers. IconPicker: - valueField: 'faName' (default) | 'name'. Name mode offers each listed name as its own option, so semantic aliases such as businessRule can be stored and later re-pointed app-wide. Naming: - IconProps.faName replaces iconName, which still works with a deprecation warning until v91. Registration config, IconCatalogEntry and rendered icon elements use faName. - Internal IconRegistry renamed IconCatalog. Public Icon.getCatalog() unchanged. Consumers: - Spinner takes icon: a catalog name or icon element. SpinnerProps.iconName and Spinner.defaults.iconName are deprecated. - Alert banners render via the catalog and the admin editor uses IconPicker over Hoist's built-in set. Stored specs keep their iconName field unchanged.
amcclain
marked this pull request as ready for review
October 6, 2026 00:48
Resolved the CHANGELOG conflict by keeping both sides: develop's app option preset and aggregator entries, plus this branch's icon entries, with sections in the documented order.
amcclain
commented
Oct 6, 2026
- Renamed hidden to hideFromPicker on IconRegistrationConfig and IconCatalogEntry. A picker still offers such an icon when it is listed in icons. - Removed IconPicker.includeHidden - it read as a contradiction next to hideFromPicker, and an explicit icons list covers the case. - Replaced IconCatalogEntry.isCustom with source: 'hoist' | 'app'. "Custom" suggested a bespoke glyph, not one the app registered. - Clarified the replace docs: a clash with one of Hoist's own icons or an earlier app registration keeps the existing icon and warns.
# Conflicts: # CHANGELOG.md
✅ Unit tests passed - 1,178 tests1,178 tests · 96 spec files · 32 packages · 145.6s · commit 🐞 36 known bugs documented by
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Apps that need an icon outside Hoist's built-in set must call
library.add()and wrapIcon.icon({iconName})by hand. A request for a weight the app never imported renders blank. This PR adds a supported registration API, a catalog of every known icon, and a desktop input that lets users pick an icon.Toolbox demo: xh/toolbox#923
Changes
Icon.register()andIcon.registerAll()add FA definitions to the library, install a factory onIcon, and return that factory. A request for an unimported weight falls back to the icon's default variant.replace: truelogs a console warning and keeps the existing icon, so a later Hoist built-in never stops an app from starting. Registering the same name and glyph again, for example on hot reload, updates quietly.Icon.get(),getFactory(),exists(),getCatalogEntry()andgetCatalog()resolve icons by name. Hoist's built-ins come from the factories inIcon.ts, so there is no second list to keep in sync. Apps that iterateObject.keys(Icon)must switch toIcon.getCatalog().IconPicker: a desktop input that opens a searchable grid with keyboard navigation. It stores the FA name by default. WithvalueField: 'name'it storesIconnames instead, so an app can store semantic names such asbusinessRuleand point them at another glyph later.faName:IconProps.faNamereplacesiconName, which still works with a deprecation warning until v91. Rendered icon elements carryprops.faName.icon, either a catalog name or an icon element. ItsiconNameprop and default are deprecated.IconPickerover Hoist's built-in set. Stored banner specs do not change.icon/Icon.spec.tscovers registration, name conflicts, the weight fallback, name lookup, and the catalog.The CHANGELOG lists the two breaking changes: listing icons by key, and the element prop rename.
Verify
Run Toolbox from its
icon-pickerbranch withpnpm startWithHoist.