Skip to content

Commit d7541fc

Browse files
authored
Add Favicon Generator example app (#935)
Adds a client-only example app that builds a Hoist app's favicon set from any Font Awesome Pro glyph. It gives us a quick way to brand a new app, and it shows Hoist inputs, panels and cards in a small, finished tool. - New `favicons` entry point, listed on the Example Apps tab and in the nginx entry-point routes. - Controls for the glyph and its weight, glyph and backdrop colors, backdrop shape, corner radius, padding and app name. Presets and a "Surprise me" button give quick starting points. - Live previews of the 512px icon, light and dark browser tabs, a phone home screen, and common sizes. In a full tab, the page's own tab icon also shows the design. - The design lives in the URL, so a link reopens it. The downloaded SVG embeds that link. - Download builds a zip of `favicon.svg`, 192px and 512px PNGs, and `apple-touch-icon.png`. A `configureRsbuild()` snippet shows how to install them. - Glyphs fit their actual path shape inside circle and rounded backdrops, not just their bounding box. - A `GlyphService` loads the Font Awesome Pro packs at startup and owns the glyph catalog. Rendering, layout, export and URL logic are pure functions in `lib/`. - A README in the app folder covers usage, the zip, and the patterns worth a look. - New dependencies: the Font Awesome Pro light, solid and thin packs, loaded on demand into an async chunk, and `fflate` for the zip. - Unit specs cover the glyph service and the layout, SVG, export, URL and preset logic.
1 parent e292bee commit d7541fc

48 files changed

Lines changed: 4495 additions & 149 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
* Reorganized the desktop tabs - a new Components tab gathers General components, Charts, and Choosers & Pickers, and the Other tab is now System, grouped into Concepts, Dialogs & Alerts, and Formatting. Tab URLs changed to match (e.g. `/app/charts/line` is now `/app/components/line`).
2424
* Added a Grid Helpers group to the Grids tab, with new `FilterChooser`, `GroupingChooser`, `StoreFilterField`, and `GridFindField` demo pages, each driving a live trades grid.
2525
* Renamed the App Notifications demo to Updates & Idle and App Banners to Banners, and removed the Forms > Other Controls overview page - its controls now live under Components > Choosers & Pickers.
26+
* Added Favicon Designer example app - a client-only tool that builds a Hoist app's favicon set from any Font Awesome Pro glyph, with custom colors, backdrop shapes and padding, live browser-tab and home-screen previews, shareable design URLs, and a one-click zip of `favicon.svg` plus the PNGs `configureRsbuild()` picks up.
2627

2728
### Bug Fixes
2829

‎CLAUDE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ defines an entry point, and its filename (minus the extension) becomes the URL p
388388
| Mobile app | http://localhost:3000/mobile |
389389

390390
**Example apps**: `/contact`, `/todo`, `/portfolio`, `/news`, `/recalls`, `/fileManager`,
391-
`/weather` - each at `http://localhost:3000/<name>`.
391+
`/weather`, `/favicons` - each at `http://localhost:3000/<name>`.
392392

393393
### Unit Tests
394394

‎client-app/package.json‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@
3636
"@codemirror/autocomplete": "^6.20.3",
3737
"@fortawesome/fontawesome-svg-core": "^7.3.1",
3838
"@fortawesome/free-brands-svg-icons": "^7.3.1",
39+
"@fortawesome/pro-light-svg-icons": "^7.3.1",
3940
"@fortawesome/pro-regular-svg-icons": "^7.3.1",
41+
"@fortawesome/pro-solid-svg-icons": "^7.3.1",
42+
"@fortawesome/pro-thin-svg-icons": "^7.3.1",
4043
"@ibm/plex-mono": "^2.5.0",
4144
"@ibm/plex-sans": "^1.1.0",
4245
"@xh/hoist": "next",
@@ -47,6 +50,7 @@
4750
"ag-grid-react": "~36.2.0",
4851
"classnames": "~2.5.1",
4952
"core-js": "~3.50.0",
53+
"fflate": "~0.8.3",
5054
"filesize": "~11.0.25",
5155
"highcharts": "~12.6.2",
5256
"lodash": "~4.18.1",
@@ -58,7 +62,7 @@
5862
"devDependencies": {
5963
"@testing-library/dom": "^10.4.2",
6064
"@testing-library/react": "^16.3.3",
61-
"@types/lodash": "~4.17.25",
65+
"@types/lodash": "~4.17.26",
6266
"@types/react": "~19.3.0",
6367
"@types/react-dom": "~19.3.0",
6468
"@xh/eslint-config": "^8.1.0",
@@ -69,9 +73,9 @@
6973
"husky": "~9.1.7",
7074
"jsdom": "^30.1.2",
7175
"lint-staged": "~17.5.1",
72-
"msw": "^3.0.2",
76+
"msw": "^3.0.3",
7377
"postcss": "~8.5.29",
74-
"prettier": "~3.9.9",
78+
"prettier": "~3.9.10",
7579
"stylelint": "~17.15.0",
7680
"stylelint-config-recommended-scss": "~17.0.1",
7781
"typescript": "~7.0.2",

‎client-app/pnpm-lock.yaml‎

Lines changed: 151 additions & 141 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎client-app/src/Bootstrap.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {when} from '@xh/hoist/mobx';
1515
import type {ContactService} from './examples/contact/svc/ContactService';
1616
import type {DocService} from './core/svc/DocService';
1717
import type {GitHubService} from './core/svc/GitHubService';
18+
import type {GlyphService} from './examples/favicons/svc/GlyphService';
1819
import type {PortfolioService} from './core/svc/PortfolioService';
1920
import type {TaskService} from './examples/todo/TaskService';
2021

@@ -24,6 +25,7 @@ declare module '@xh/hoist/core' {
2425
contactService: ContactService;
2526
docService: DocService;
2627
gitHubService: GitHubService;
28+
glyphService: GlyphService;
2729
portfolioService: PortfolioService;
2830
taskService: TaskService;
2931
}

‎client-app/src/apps/favicons.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import '../Bootstrap';
2+
3+
import {XH} from '@xh/hoist/core';
4+
import {AppContainer} from '@xh/hoist/desktop/appcontainer';
5+
import {AppComponent} from '../examples/favicons/AppComponent';
6+
import {AppModel} from '../examples/favicons/AppModel';
7+
import {AuthModel} from '../core/AuthModel';
8+
9+
XH.renderApp({
10+
clientAppCode: 'favicons',
11+
clientAppName: 'Hoist Favicon Designer',
12+
componentClass: AppComponent,
13+
modelClass: AppModel,
14+
containerClass: AppContainer,
15+
authModelClass: AuthModel,
16+
isMobileApp: false,
17+
enableLogout: true,
18+
checkAccess: () => true
19+
});

‎client-app/src/desktop/tabs/examples/ExamplesTabModel.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {PanelModel} from '@xh/hoist/desktop/cmp/panel';
33
import {Icon} from '@xh/hoist/icon/Icon';
44
import {bindable} from '@xh/hoist/mobx';
55
import {p, strong} from '@xh/hoist/cmp/layout';
6+
import {iconsIcon} from '../../../core/Icons';
67

78
export class ExamplesTabModel extends HoistModel {
89
@managed
@@ -111,6 +112,18 @@ export class ExamplesTabModel extends HoistModel {
111112
' to prevent arbitrary file uploads. Contact us for a walkthrough.'
112113
)
113114
]
115+
},
116+
{
117+
title: 'Favicon Designer',
118+
icon: iconsIcon(),
119+
path: 'favicons',
120+
srcPath: 'favicons',
121+
text: [
122+
p('Mint a complete favicon set for your next Hoist app in seconds.'),
123+
p(
124+
'Pick any Font Awesome Pro glyph, style it with colors, a backdrop shape and padding, preview it live in browser tabs and on a home screen, then download a zip ready to drop into client-app/public.'
125+
)
126+
]
114127
}
115128
];
116129
}

‎client-app/src/desktop/tabs/home/widgets/StartHereWidget.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const ITEMS: StartHereItem[] = [
2828
{
2929
icon: Icon.rocket(),
3030
title: 'Browse the example apps',
31-
blurb: 'Complete mini-apps - portfolio, weather, contacts, news, and more.',
31+
blurb: 'Complete mini-apps - portfolio, weather, contacts, a favicon designer, and more.',
3232
onClick: () => XH.navigate('default.examples')
3333
},
3434
{
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import {hframe} from '@xh/hoist/cmp/layout';
2+
import {hoistCmp, uses} from '@xh/hoist/core';
3+
import {appBar, appBarSeparator} from '@xh/hoist/desktop/cmp/appbar';
4+
import {button, themeToggleButton} from '@xh/hoist/desktop/cmp/button';
5+
import {clipboardButton} from '@xh/hoist/desktop/cmp/clipboard';
6+
import {panel} from '@xh/hoist/desktop/cmp/panel';
7+
import {Icon} from '@xh/hoist/icon';
8+
import {iconsIcon} from '../../core/Icons';
9+
import {AppModel} from './AppModel';
10+
import {controlsPanel} from './cmp/ControlsPanel';
11+
import {previewsPanel} from './cmp/PreviewsPanel';
12+
import '../../core/Toolbox.scss';
13+
import './Favicons.scss';
14+
15+
export const AppComponent = hoistCmp({
16+
displayName: 'App',
17+
model: uses(AppModel),
18+
19+
render({model}) {
20+
const {faviconModel} = model;
21+
return panel({
22+
className: 'tb-favicons',
23+
tbar: appBar({
24+
icon: iconsIcon({size: '2x'}),
25+
hideAppMenuButton: true,
26+
hideRefreshButton: true,
27+
hideWhatsNewButton: true,
28+
rightItems: [
29+
clipboardButton({
30+
text: 'Copy link',
31+
icon: Icon.link(),
32+
tooltip: 'Copy a link that reopens this exact design',
33+
getCopyText: () => faviconModel.shareUrl,
34+
successMessage: 'Design link copied to clipboard.'
35+
}),
36+
button({
37+
text: 'Download .zip',
38+
icon: Icon.download(),
39+
intent: 'primary',
40+
outlined: true,
41+
onClick: () => faviconModel.downloadAsync()
42+
}),
43+
appBarSeparator(),
44+
themeToggleButton()
45+
]
46+
}),
47+
mask: faviconModel.downloadTask,
48+
item: hframe(controlsPanel({model: faviconModel}), previewsPanel({model: faviconModel}))
49+
});
50+
}
51+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type {InitContext} from '@xh/hoist/core';
2+
import {managed, XH} from '@xh/hoist/core';
3+
import {BaseAppModel} from '../../BaseAppModel';
4+
import {FaviconModel} from './FaviconModel';
5+
import {GlyphService} from './svc/GlyphService';
6+
7+
export class AppModel extends BaseAppModel {
8+
static instance: AppModel;
9+
10+
/** Created once `GlyphService` is installed, so it can rely on every glyph being present. */
11+
@managed faviconModel: FaviconModel;
12+
13+
override async initAsync(ctx: InitContext) {
14+
await super.initAsync(ctx);
15+
await XH.installServicesAsync([GlyphService], ctx);
16+
this.faviconModel = new FaviconModel();
17+
}
18+
}

0 commit comments

Comments
 (0)