Files
ttrpg-tools/docs/icon-migration.md
hypercross 49bed7d279 docs: add icon migration reference
Record the Tabler pack choice, icon conventions, and the per-file
glyph-to-icon mapping for the unicode-to-solid-icons migration.
2026-09-13 14:01:10 +08:00

192 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Icon migration — unicode/emoji → `solid-icons`
**Status: implemented.** This document is both the audit and the record of the
migration. It lists the pack/conventions used and the per-file mapping.
## Goal
Replace ad-hoc unicode symbols and emoji in **UI chrome** with real SVG icon
components from [`solid-icons`](https://solid-icons.vercel.app), so icons are
consistent, themeable (`currentColor`, `size`), and render identically across
platforms (emoji fonts vary wildly between OSes).
## Scope
**In scope — UI chrome** (`src/components/**`, `src/App.tsx`, doc-entry
frontmatter, `RevealManager` inline SVGs, the editor-panel align PNGs).
**Out of scope — left as-is:**
| Thing | Why |
|---|---|
| `:[icon-name]` content directive (`src/markdown/icon.ts`, `assets/`) | Game-content icons resolved from user `assets/` dirs via CSS `background-image`. Not UI chrome. |
| CLI console output (`✅ ❌ 📦` in `src/cli/**`) | Terminal output, not a browser. `solid-icons` is a SolidJS/browser library. |
| Emoji in `README.md`, `docs/*.md` prose | Documentation text, not rendered UI. |
| Dimension text `×` in `PltPreview` / `PrintPreviewHeader` | A multiplication sign in "100mm × 50mm", not an icon. |
| `content/**` sample content | User-authored data. |
| `PltPreview.tsx` / `PrintPreview.tsx` `<svg>` | Rendered card/page artwork, not icons. |
## Pack
**Tabler** (`solid-icons/tb`). `solid-icons@1.2.0` does **not** ship Lucide; the
available packs are `ai, bi, bs, cg, fa, fi, hi, im, io, md, oc, ri, si, tb, ti,
vs, wi`. Tabler was chosen because it is stroke-based, uses `currentColor`, and
covers everything needed (dice, ruler, grip, alignment, arrows, …).
> Exports are prefixed `TbOutline*` (outline) and `TbFill*` (filled). Always use
> `TbOutline*`.
## Conventions
```tsx
import { TbOutlineFolderOpen, TbOutlineX } from "solid-icons/tb";
<TbOutlineFolderOpen size={16} class="text-gray-400" />
```
- `size?: string | number` defaults to `"1em"`; `color` defaults to
`currentColor`. Icons therefore inherit the surrounding font-size and text
color, so existing Tailwind text-color/size classes keep working.
- **Inline alignment:** an inline SVG sits on the text baseline, so icons placed
inline next to text get `class="inline-block align-[-0.125em]"`. Icons inside
a `flex`/`inline-flex` container don't need it.
- **Fixed-size buttons** get an explicit `size` (`16` for `w-7 h-7`/`w-8 h-8`,
`14` for `w-5 h-5`/`w-6 h-6`).
- Text labels next to a glyph (e.g. `📥 导出 PDF`) are preserved; only the glyph
is replaced.
## Shared modules
- **`src/components/doc-icons.ts`** — maps doc-entry frontmatter `icon:` keys
(e.g. `dice`, `table`) to Tabler components. Frontmatter is YAML, so it can't
hold a component; it stores a semantic key instead. `docIcon(key)` falls back
to `TbOutlineFile`.
- **`CompletionItem.icon`** (`src/components/journal/command-parser.ts`) — an
optional `IconTypes` rendered by `CompletionsDropdown.tsx` before the label,
so completion entries carry real icons instead of emoji in the label string.
## Inventory
### Shell / navigation
| File | Glyph | Purpose | Icon |
|---|---|---|---|
| `src/App.tsx` | `☰` | mobile menu | `TbOutlineMenu2` |
| `src/App.tsx` | `📋` | journal panel toggle | `TbOutlineClipboardList` |
| `src/App.tsx` | `?` | docs/help | `TbOutlineHelpCircle` |
| `src/components/Sidebar.tsx` | `📂` | content source | `TbOutlineFolderOpen` |
| `src/components/Sidebar.tsx` | `✕` | close sidebar | `TbOutlineX` |
| `src/components/FileTree.tsx` | `📂` / `📁` | folder open / closed | `TbOutlineFolderOpen` / `TbOutlineFolder` |
| `src/components/FileTree.tsx` | `📄` | file | `TbOutlineFile` |
| `src/components/FileTree.tsx` | `▶` | expand chevron | `TbOutlineChevronRight` |
| `src/components/DocDialog.tsx` | `▾` | dropdown caret | `TbOutlineChevronDown` |
| `src/components/DocDialog.tsx` | `📖` | directives doc set | `TbOutlineBook2` |
| `src/components/DocDialog.tsx` | `✓` | selected doc set | `TbOutlineCheck` |
| `src/components/DocDialog.tsx` | `📋` | journal doc set | `TbOutlineClipboardList` |
| `src/components/DocDialog.tsx` | `✕` | close dialog | `TbOutlineX` |
| `src/components/DocDialog.tsx` | entry `icon:` | doc entry icon | `<Dynamic component={docIcon(entry.icon)} />` |
### md-* components
| File | Glyph | Purpose | Icon |
|---|---|---|---|
| `src/components/md-dice.tsx` | `🎲` | dice reset | `TbOutlineDice5` |
| `src/components/md-table.tsx` | `🎲` | random row | `TbOutlineDice5` |
| `src/components/md-pins.tsx` | `📋` | copy pin coords | `TbOutlineCopy` |
| `src/components/md-yarn-spinner.tsx` | `🔄` | restart | `TbOutlineRotate` |
| `src/components/md-yarn-spinner.tsx` | `⏩` | advance | `TbOutlinePlayerTrackNext` |
| `src/components/md-yarn-spinner.tsx` | `→` | option bullet | `TbOutlineArrowRight` |
### Journal
| File | Glyph | Purpose | Icon |
|---|---|---|---|
| `src/components/journal/ConnectDialog.tsx` | `🗑` | delete session | `TbOutlineTrash` |
| `src/components/journal/CreateSessionDialog.tsx` | `✕` | close | `TbOutlineX` |
| `src/components/journal/DeleteSessionDialog.tsx` | `✕` | close | `TbOutlineX` |
| `src/components/journal/ErrorPopup.tsx` | `✕` | close | `TbOutlineX` |
| `src/components/journal/InviteDialog.tsx` | `✕` | close | `TbOutlineX` |
| `src/components/journal/JournalHeader.tsx` | `←` | back to session list | `TbOutlineArrowLeft` |
| `src/components/journal/JournalHeader.tsx` | `▸` | hide panel | `TbOutlineLayoutSidebarRightCollapse` |
| `src/components/journal/SessionDropdown.tsx` | `▾` | dropdown caret | `TbOutlineChevronDown` |
| `src/components/journal/SessionDropdown.tsx` | `✓` | active session | `TbOutlineCheck` |
| `src/components/journal/SessionDropdown.tsx` | `🗑` | delete session | `TbOutlineTrash` |
| `src/components/journal/StreamMessage.tsx` | `×` | revert message | `TbOutlineArrowBackUp` |
| `src/components/journal/types/intent.tsx` | `🙋` | intent | `TbOutlineHandStop` |
| `src/components/journal/types/intent.tsx` | `✅` | verdict | `TbOutlineCircleCheck` |
| `src/components/journal/types/link.tsx` | `📄` | link | `TbOutlineFileText` |
| `src/components/journal/types/roll.tsx` | `🎲` | roll | `TbOutlineDice5` |
| `src/components/journal/types/roll.tsx` | `→` | notation → total | `TbOutlineArrowRight` |
| `src/components/journal/types/var.tsx` | `🔢` | variable | `TbOutlineHash` |
| `src/components/journal/command-completions.ts` | `🎲` | dice completion | `TbOutlineDice5` (via `item.icon`) |
| `src/components/journal/command-completions.ts` | `✨` | spark completion | `TbOutlineSparkles` (via `item.icon`) |
### md-deck
| File | Glyph | Purpose | Icon |
|---|---|---|---|
| `src/components/md-deck/DeckHeader.tsx` | `✓` / `✏️` | editing toggle | `TbOutlineCheck` / `TbOutlinePencil` |
| `src/components/md-deck/DeckHeader.tsx` | `📥` | export PDF | `TbOutlineDownload` |
| `src/components/md-deck/DeckHeader.tsx` | `📋` | copy code | `TbOutlineCopy` |
| `src/components/md-deck/PltPreview.tsx` | `📥` | download PLT | `TbOutlineDownload` |
| `src/components/md-deck/PrintPreviewFooter.tsx` | `❌` | export error | `TbOutlineCircleX` |
| `src/components/md-deck/PrintPreviewFooter.tsx` | `✕` | close | `TbOutlineX` |
| `src/components/md-deck/PrintPreviewHeader.tsx` | `➕` | add deck | `TbOutlinePlus` |
| `src/components/md-deck/PrintPreviewHeader.tsx` | `📐` | PLT preview | `TbOutlineRuler` |
| `src/components/md-deck/PrintPreviewHeader.tsx` | `📥` | export PDF | `TbOutlineDownload` |
| `src/components/md-deck/CardList.tsx` | `‹` | previous page | `TbOutlineChevronLeft` |
| `src/components/md-deck/CardList.tsx` | `›` | next page | `TbOutlineChevronRight` |
| `src/components/md-deck/editor-panel/LayerRow.tsx` | `⠿` | drag handle | `TbOutlineGripVertical` |
| `src/components/md-deck/editor-panel/LayerRow.tsx` | `👁` / `👁‍🗨` | visible / hidden | `TbOutlineEye` / `TbOutlineEyeOff` |
| `src/components/md-deck/editor-panel/LayerRow.tsx` | `↑ → ↓ ←` | orientation | `TbOutlineArrowUp` / `TbOutlineArrowRight` / `TbOutlineArrowDown` / `TbOutlineArrowLeft` |
| `src/components/md-deck/editor-panel/LayerRow.tsx` | `↖ ↑ ↗ ↙ ↓ ↘` | alignment | `TbOutlineArrowUpLeft` / `TbOutlineArrowUp` / `TbOutlineArrowUpRight` / `TbOutlineArrowDownLeft` / `TbOutlineArrowDown` / `TbOutlineArrowDownRight` |
| `src/components/md-deck/editor-panel/LayerRow.tsx` | `✕` | delete layer | `TbOutlineTrash` |
| `src/components/md-deck/editor-panel/LayerRow.tsx` | `align-*.png` | align presets | `TbOutlineAlignLeft` / `TbOutlineAlignCenter` / `TbOutlineAlignRight` |
The three `align-*.png` assets were deleted; the `icons/` directory is gone.
### `RevealManager.tsx` (formerly inline SVG strings)
| Former | Purpose | Icon |
|---|---|---|
| `LINK_SVG` | floating `/link` action | `TbOutlineLink` |
| `SPARK_SVG` | floating `/roll` spark action | `TbOutlineSparkles` |
`FloatingButton.svg: string` (rendered via `innerHTML`) became
`FloatingButton.icon: IconTypes`, rendered as `<Dynamic component={icon} />`
children.
### `src/doc-entries/*.md` frontmatter `icon:`
| File / tag | Key |
|---|---|
| `journal-gm.md` | `gm` |
| `journal-link.md` | `link` |
| `journal-player.md` | `player` |
| `journal-roll.md` | `roll` |
| `journal-set.md` | `set` |
| `journal-spark.md` | `spark` |
| `md-bg.md` | `background` |
| `md-border.md` | `border` |
| `md-deck.md` | `deck` |
| `md-dice.md` | `dice` |
| `md-embed.md` | `embed` |
| `md-font.md` | `font` |
| `md-link.md` | `link` |
| `md-pins.md` | `pins` |
| `md-table.md` | `table` |
| `md-yarn-spinner.md` | `yarn` |
Keys are resolved through `docIcon()` in `src/components/doc-icons.ts`.
## Follow-ups / not migrated (by design)
- Doc-entry **body prose** that referenced a replaced button glyph was reworded
to name the button (e.g. `md-dice.md` now says "点击骰子图标"). Remaining emoji
in `src/doc-entries/*.md` are illustrative — coloured status dots
(`🟢🟡🔴⚪` in `journal-gm.md`) — not UI icons.
- CLI console / MCP output (`✅ ❌ 📦` in `src/cli/**`) keeps its emoji: it is
terminal text, not rendered UI.
- `command-completions.ts` `CompletionItem` now carries an optional `icon`; any
future completion source can use it.