Document the full-set searchable Font Awesome picker (task e2fc2dbc) (#292)
Co-authored-by: dev_agent <dev_agent@cubecraftlabs.com> Co-committed-by: dev_agent <dev_agent@cubecraftlabs.com>
This commit was merged in pull request #292.
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
# Third-party notices
|
# Third-party notices
|
||||||
|
|
||||||
NestQuest bundles glyph data from the icon sets below. The curated names are listed in
|
NestQuest bundles glyph data from the icon sets below. The curated Lucide names and the
|
||||||
`tools/icons/curated-icons.json`; `tools/icons/generate.mjs` reads each glyph's SVG path
|
Font Awesome quick-pick names are listed in `tools/icons/curated-icons.json`;
|
||||||
data from the locally installed packages and generates it into both bundles
|
`tools/icons/generate.mjs` reads each glyph's SVG path data from the locally installed packages and generates it into both bundles
|
||||||
(`admin/src/icons/registry.generated.ts` and `frontend/src/icons/registry.generated.ts`).
|
(`admin/src/icons/registry.generated.ts` and `frontend/src/icons/registry.generated.ts`).
|
||||||
Nothing is loaded from a CDN or fetched at runtime. The package versions used are recorded
|
Nothing is loaded from a CDN or fetched at runtime. The package versions used are recorded
|
||||||
in the header of each generated file.
|
in the header of each generated file.
|
||||||
@@ -15,8 +15,8 @@ in the header of each generated file.
|
|||||||
- Icons: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
- Icons: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
||||||
- Fonts: [SIL OFL 1.1](https://openfontlicense.org)
|
- Fonts: [SIL OFL 1.1](https://openfontlicense.org)
|
||||||
- Code: [MIT](https://opensource.org/licenses/MIT)
|
- Code: [MIT](https://opensource.org/licenses/MIT)
|
||||||
- Use in NestQuest: a curated subset of the Free **solid** icon set, embedded as SVG path
|
- Use in NestQuest: the full Free **solid** icon set, embedded as SVG path data (a curated
|
||||||
data. No fonts are bundled.
|
subset doubles as the admin picker's quick picks). No fonts are bundled.
|
||||||
|
|
||||||
Font Awesome Free by @fontawesome — https://fontawesome.com — icons licensed under CC BY 4.0.
|
Font Awesome Free by @fontawesome — https://fontawesome.com — icons licensed under CC BY 4.0.
|
||||||
|
|
||||||
|
|||||||
@@ -1355,7 +1355,7 @@ describe("DefinitionsTab Font Awesome and emoji icons", () => {
|
|||||||
fireEvent.click(within(s).getByRole("button", { name: "Morning" }));
|
fireEvent.click(within(s).getByRole("button", { name: "Morning" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
it("ships the curated Font Awesome subset, each drawn as a filled glyph", () => {
|
it("ships the curated Font Awesome quick picks, each drawn as a filled glyph", () => {
|
||||||
expect(FA_DEFINITION_ICONS.length).toBeGreaterThan(0);
|
expect(FA_DEFINITION_ICONS.length).toBeGreaterThan(0);
|
||||||
for (const entry of FA_DEFINITION_ICONS) {
|
for (const entry of FA_DEFINITION_ICONS) {
|
||||||
expect(entry.kind).toBe("fa");
|
expect(entry.kind).toBe("fa");
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* The icons a definition's `icon` may name (ADMIN-SPEC §3.3): the curated
|
* The icons a definition's `icon` may name (ADMIN-SPEC §3.3): the curated
|
||||||
* Lucide set, the curated Font Awesome Free solid subset, or an emoji. The
|
* Lucide set, any icon in the full Font Awesome Free Solid set, or an emoji.
|
||||||
* names, labels and path data come from the shared icon registry
|
* The Font Awesome quick picks (`FA_DEFINITION_ICONS`) are only a curated
|
||||||
* (`src/icons/registry.generated.ts`, generated from
|
* shortcut list; `fa:<name>` resolves against the full set
|
||||||
* `tools/icons/curated-icons.json`) — no CDN, no runtime fetch. The stored
|
* (`FA_SOLID_ICONS`). The names, labels and path data come from the shared
|
||||||
* value is namespaced — `lucide:<name>`, `fa:<name>` or `emoji:<grapheme>` —
|
* icon registry (`src/icons/registry.generated.ts`, generated by
|
||||||
* and a legacy bare `<kebab-name>` still names the same Lucide entry.
|
* `tools/icons/generate.mjs` from `tools/icons/curated-icons.json` and the
|
||||||
|
* installed Free Solid package) — no CDN, no runtime fetch. The stored value
|
||||||
|
* is namespaced — `lucide:<name>`, `fa:<name>` or `emoji:<grapheme>` — and a
|
||||||
|
* legacy bare `<kebab-name>` still names the same Lucide entry.
|
||||||
*/
|
*/
|
||||||
import { GlyphSvg } from "../icons/GlyphSvg";
|
import { GlyphSvg } from "../icons/GlyphSvg";
|
||||||
import {
|
import {
|
||||||
@@ -105,8 +108,9 @@ export function emojiIconText(value: string | null): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The curated glyph (Lucide or Font Awesome) an icon value resolves to via
|
* The glyph an icon value resolves to via the shared parser: a curated
|
||||||
* the shared parser; emoji, unknown and null values give null.
|
* Lucide entry or any Font Awesome Free Solid icon (quick pick or not);
|
||||||
|
* emoji, unknown and null values give null.
|
||||||
*/
|
*/
|
||||||
export function findDefinitionIcon(value: string | null): DefinitionIconEntry | null {
|
export function findDefinitionIcon(value: string | null): DefinitionIconEntry | null {
|
||||||
const icon = resolveIcon(value);
|
const icon = resolveIcon(value);
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ describe("offline bundling", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("no bundled source imports an icon package — only the curated generated data ships", () => {
|
it("no bundled source imports an icon package — only the generated registry data ships", () => {
|
||||||
const sources = [join(REPO_ROOT, "admin", "src"), join(REPO_ROOT, "frontend", "src")]
|
const sources = [join(REPO_ROOT, "admin", "src"), join(REPO_ROOT, "frontend", "src")]
|
||||||
.flatMap(filesUnder)
|
.flatMap(filesUnder)
|
||||||
.filter((path) => /\.(ts|tsx)$/.test(path) && !path.endsWith(".test.ts") && !path.endsWith(".test.tsx"));
|
.filter((path) => /\.(ts|tsx)$/.test(path) && !path.endsWith(".test.ts") && !path.endsWith(".test.tsx"));
|
||||||
|
|||||||
+16
-5
@@ -105,14 +105,25 @@ Each definition carries one optional icon, shown on both surfaces. The picker of
|
|||||||
sources:
|
sources:
|
||||||
|
|
||||||
- **Lucide** — a grid of the curated Lucide glyphs, drawn stroked.
|
- **Lucide** — a grid of the curated Lucide glyphs, drawn stroked.
|
||||||
- **Font Awesome** — a grid of a curated subset of the Font Awesome Free *solid* set,
|
- **Font Awesome** — a search over the **full** Font Awesome Free *solid* set (every
|
||||||
drawn filled.
|
glyph in `@fortawesome/free-solid-svg-icons`), plus a curated quick-pick grid, all drawn
|
||||||
|
filled. With the search field empty the quick-pick grid shows; typing filters the full
|
||||||
|
set by name or label (an exact name ranks first, then name prefixes, then the rest),
|
||||||
|
shown 40 results per page with *Previous* / *Show more*.
|
||||||
|
**Free-text rule:** an admin may type a Free Solid icon name directly (trimmed,
|
||||||
|
case-insensitive — `Dog` stores `fa:dog`). Text that is not the exact name of a Free
|
||||||
|
Solid icon — a partial search, a misspelling, or a Pro or Brands name — is rejected in
|
||||||
|
the picker with an inline error and cannot be saved until a result is selected or the
|
||||||
|
full name is typed.
|
||||||
- **Emoji** — a text input; the typed emoji is stored as entered (not trimmed) and must be
|
- **Emoji** — a text input; the typed emoji is stored as entered (not trimmed) and must be
|
||||||
1–8 code points with no whitespace, control characters, `<`, `>` or `&`.
|
1–8 code points with no whitespace, control characters, `<`, `>` or `&`.
|
||||||
|
|
||||||
Both curated lists come from `tools/icons/curated-icons.json`; their glyph path data is
|
The curated Lucide set and the Font Awesome quick-pick list come from
|
||||||
generated into the admin and panel bundles by `tools/icons/generate.mjs` (no CDN, no
|
`tools/icons/curated-icons.json`; `tools/icons/generate.mjs` generates their glyph path
|
||||||
runtime fetch). See `THIRD-PARTY-NOTICES.md` for licences.
|
data, together with the full Font Awesome Free Solid set, into the admin and panel bundles
|
||||||
|
(no CDN, no runtime fetch). Embedding the full set makes both bundles heavier — the panel
|
||||||
|
bundle (`nestquest-cards.js`) is about 1 MB raw / 276 kB gzip. See
|
||||||
|
`THIRD-PARTY-NOTICES.md` for licences.
|
||||||
|
|
||||||
The stored value is a **namespaced key**:
|
The stored value is a **namespaced key**:
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,6 @@ The panel speaks tabletop; the admin speaks plainly. Both sit on the same `quest
|
|||||||
decide before shipping the card.
|
decide before shipping the card.
|
||||||
- No gamification is in scope. XP, levels, streaks and rarity were explicitly deferred,
|
- No gamification is in scope. XP, levels, streaks and rarity were explicitly deferred,
|
||||||
but the append-only event log already supports computing them later.
|
but the append-only event log already supports computing them later.
|
||||||
- Icon per quest definition is chosen by the parent from curated Lucide and Font Awesome
|
- Icon per quest definition is chosen by the parent from the curated Lucide set, the full
|
||||||
Free sets or as an emoji, stored as a namespaced key (`lucide:`/`fa:`/`emoji:`) —
|
Font Awesome Free Solid set (searchable, with curated quick picks) or as an emoji, stored as a namespaced key (`lucide:`/`fa:`/`emoji:`) —
|
||||||
resolved 2026-09-26, see `ADMIN-SPEC.md` §3.3.
|
resolved 2026-09-26, see `ADMIN-SPEC.md` §3.3.
|
||||||
|
|||||||
@@ -106,7 +106,8 @@ collapse the column, the three-column rhythm is the layout.
|
|||||||
1. **Icon tile** 64×64, radius 10px, `--nq-p-icon-tile`, glyph 34px white — the
|
1. **Icon tile** 64×64, radius 10px, `--nq-p-icon-tile`, glyph 34px white — the
|
||||||
definition's icon, resolved from its namespaced key (`ADMIN-SPEC.md` §3.3):
|
definition's icon, resolved from its namespaced key (`ADMIN-SPEC.md` §3.3):
|
||||||
`lucide:<name>` draws the curated Lucide glyph **stroked** (fill none, stroke-width 2,
|
`lucide:<name>` draws the curated Lucide glyph **stroked** (fill none, stroke-width 2,
|
||||||
round caps and joins); `fa:<name>` draws the curated Font Awesome glyph **filled**;
|
round caps and joins); `fa:<name>` draws the Font Awesome Free Solid glyph **filled** (any icon in the full
|
||||||
|
set, not only the quick picks);
|
||||||
`emoji:<grapheme>` renders the emoji **as text**. A legacy bare name resolves as
|
`emoji:<grapheme>` renders the emoji **as text**. A legacy bare name resolves as
|
||||||
`lucide:<name>`. No icon, or any key that does not resolve (unknown namespace or name,
|
`lucide:<name>`. No icon, or any key that does not resolve (unknown namespace or name,
|
||||||
malformed emoji), falls back to the **star** glyph. Glyph data is bundled from the
|
malformed emoji), falls back to the **star** glyph. Glyph data is bundled from the
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* Lit rendering for the shared icon registry (`registry.generated.ts`,
|
* Lit rendering for the shared icon registry (`registry.generated.ts`,
|
||||||
* generated from tools/icons/curated-icons.json). The glyph path data is
|
* generated by tools/icons/generate.mjs: the curated Lucide set plus the full
|
||||||
* bundled — no CDN and no runtime fetch.
|
* Font Awesome Free Solid set). The glyph path data is bundled — no CDN and
|
||||||
|
* no runtime fetch.
|
||||||
*/
|
*/
|
||||||
import { html, svg, type TemplateResult } from "lit";
|
import { html, svg, type TemplateResult } from "lit";
|
||||||
|
|
||||||
|
|||||||
@@ -16,13 +16,15 @@
|
|||||||
* Lucide circles, rects and lines are converted to path `d` strings so every
|
* Lucide circles, rects and lines are converted to path `d` strings so every
|
||||||
* glyph is plain `{ viewBox, paths }` data any renderer can draw.
|
* glyph is plain `{ viewBox, paths }` data any renderer can draw.
|
||||||
*
|
*
|
||||||
* Besides the curated sets, the module embeds the complete Font Awesome Free
|
* Besides the curated sets (the Lucide set and the Font Awesome quick picks),
|
||||||
* Solid set (`FA_SOLID_ICONS`). Its source is the installed
|
* the module embeds the complete Font Awesome Free Solid set (`FA_SOLID_ICONS`),
|
||||||
|
* which is what `fa:<name>` resolves against. Its source is the installed
|
||||||
* @fortawesome/free-solid-svg-icons package itself: every exported icon
|
* @fortawesome/free-solid-svg-icons package itself: every exported icon
|
||||||
* definition, de-duplicated by canonical `iconName` (the package also exports
|
* definition, de-duplicated by canonical `iconName` (the package also exports
|
||||||
* each legacy alias, e.g. faHome for `house`, as the same definition). The
|
* each legacy alias, e.g. faHome for `house`, as the same definition). The
|
||||||
* package carries no human labels, so each label is the kebab name in Title
|
* package carries no human labels, so each label is the kebab name in Title
|
||||||
* Case (`spray-can-sparkles` → "Spray Can Sparkles").
|
* Case (`spray-can-sparkles` → "Spray Can Sparkles"). Embedding the full set
|
||||||
|
* makes both bundles heavier: the panel bundle is about 1 MB raw / 276 kB gzip.
|
||||||
*/
|
*/
|
||||||
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
||||||
import { createRequire } from "node:module";
|
import { createRequire } from "node:module";
|
||||||
|
|||||||
Reference in New Issue
Block a user