docs(icons): add UI icon & visual language audit
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
110
docs/UI-ICON-AUDIT.md
Normal file
110
docs/UI-ICON-AUDIT.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# RC UI Sprint — Icon & Visual Language Audit
|
||||||
|
|
||||||
|
Full-application audit and consolidation onto **one icon library** (Lucide, via
|
||||||
|
`@lucide/angular`) across storefront, Marketplace Builder, and backoffice. Before this
|
||||||
|
pass the app mixed three icon systems in the same screens: **PrimeIcons** (`pi pi-*`
|
||||||
|
CSS classes), **dozens of hand-rolled inline `<svg>` blocks** (often the same icon
|
||||||
|
redrawn from scratch at different sizes/colors), and **bare text glyphs** (`♥`, `⇄`,
|
||||||
|
`«`, `»`) standing in for icons entirely outside any icon system.
|
||||||
|
|
||||||
|
## Foundation
|
||||||
|
|
||||||
|
- Added `@lucide/angular` (the maintained package; the unscoped `lucide-angular` is
|
||||||
|
deprecated upstream — installed the correct one directly).
|
||||||
|
- One shared entry point for every icon: `app-icon` (`src/app/shared/ui/icon/`),
|
||||||
|
backed by a canonical `name → Lucide icon` registry (`icon-registry.ts`). Default
|
||||||
|
size 20px, default stroke-width 2 — the same visual weight everywhere unless a
|
||||||
|
call site explicitly overrides it for a specific context (e.g. a 48px empty-state
|
||||||
|
illustration vs. a 14px inline chevron).
|
||||||
|
- `app-icon` is decorative (`aria-hidden`) by default. An `ariaLabel` input exists for
|
||||||
|
the rare case an icon carries meaning with no adjacent text; in practice, the label
|
||||||
|
almost always belongs on the surrounding interactive element instead.
|
||||||
|
|
||||||
|
## Old icon → New icon → Reason
|
||||||
|
|
||||||
|
| Area | Old | New | Reason |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Admin dashboard | `pi-file-edit`, `pi-heart`, `pi-clock`, `pi-question-circle`, `pi-check-circle`, `pi-save`, `pi-upload`, `pi-book`, `pi-bolt`, `pi-flag`, `pi-megaphone`, `pi-circle` (data-driven via `icon: 'pi-*'` fields) | `edit`, `heart`, `clock`, `help`, `checkCircle`, `save`, `upload`, `book`, `zap`, `flag`, `megaphone`, `circle` | PrimeIcons → Lucide; icon fields retyped `AppIconName` so an unmapped name is a compile error, not a silently blank icon |
|
||||||
|
| Backoffice shell (sidebar nav + topbar) | `pi-home`, `pi-box`, `pi-tags`, `pi-shopping-cart`, `pi-user`, `pi-credit-card`, `pi-star`, `pi-chart-bar`, `pi-sitemap`, `pi-users`, `pi-cog`, `pi-desktop`, `pi-chart-line`, `pi-sign-out`, `pi-bars`, `pi-search`, `pi-upload`, `pi-building`, `pi-bell` | `home`, `package`, `tags`, `cart`, `user`, `creditCard`, `star`, `chartBar`, `network`, `users`, `settings`, `monitor`, `chartLine`, `logOut`, `menu`, `search`, `upload`, `building`, `bell` | PrimeIcons → Lucide |
|
||||||
|
| Marketplace Builder (overview, nav, sections, HTML editor toolbar) | `pi-arrow-left/right-circle/up/down`, `pi-check-circle`, `pi-circle`, `pi-circle-fill`, `pi-question-circle`, `pi-desktop`, `pi-shop`, `pi-image`, `pi-th-large`, `pi-box`, `pi-images`, `pi-history`, `pi-megaphone`, `pi-verified`, `pi-code`, `pi-globe`, `pi-sliders-h`, `pi-compass`, `pi-copy`, `pi-trash`, `pi-list`, `pi-link`, `pi-table`, `pi-minus`, `pi-video`, **`pi-bars` reused for drag handles** | `arrowLeft/Right/Up/Down`, `checkCircle`, `circle`, `help`, `monitor`, `store`, `image`, `layoutGrid`, `package`, `images`, `history`, `megaphone`, `verified`, `code`, `globe`, `slidersHorizontal`, `compass`, `copy`, `trash`, `list`, `link`, `table`, `minus`, `video`, **new dedicated `grip` icon** | PrimeIcons → Lucide. `pi-bars` had been reused for both the hamburger menu **and** every drag-to-reorder handle — two unrelated meanings sharing one icon, exactly the "duplicated icon, different meaning" pattern the audit brief calls out. Added `GripVertical` as a distinct drag-handle icon. |
|
||||||
|
| Header (storefront) | Two hand-drawn magnifying-glass SVGs (`#576463` desktop / `#1e3c38` mobile — same icon, two different hardcoded colors), `♥`/`⇄` **text glyphs** for wishlist/compare, hand-drawn cart SVG, hand-drawn home/catalog mobile-menu icons, 3× duplicated inline chevron SVG | `search` (color via `currentColor`), `heart`, `scale`, `cart`, `home`, `layoutGrid`, `chevronRight` | Consolidates one icon drawn twice with two different colors into one; replaces bare text characters (`♥`/`⇄`) that weren't part of any icon system at all |
|
||||||
|
| Search page | Same magnifying-glass path hand-duplicated **4 times** (input icon, empty-query, no-results, no-query states) at 3 sizes/3 colors | `search` ×4, color preserved per state via a CSS `color` property on the wrapper | Textbook duplication — one icon, four copy-pasted SVGs |
|
||||||
|
| Cart | Trash/X/plus/minus inline SVGs, standalone `EmptyCartIconComponent` (an 80px duplicate of the same cart glyph, only used once), chat-bubble "login gate" icon, duplicated close-X icon (2 modals), duplicated refresh icon (QR expired/error) | `trash`, `x`, `plus`, `minus`, `cart` (inline, component deleted), `lock`, `x`, `refresh` | Removed a whole component that existed only to duplicate an icon already available; unified the "login required" icon with telegram-login's identical icon (same concept, was drawn twice) |
|
||||||
|
| Language/region selectors | 3× duplicated chevron SVG (language dropdown, currency dropdown, region dropdown — identical path, copy-pasted), map-pin SVG, crosshair "locate" SVG, globe SVG | `chevronDown` ×3, `mapPin`, `locate`, `globe` | Same chevron redrawn three times in two components |
|
||||||
|
| Items carousel, category/subcategories, item-detail | Hand-drawn star (hardcoded `#497671` fill), hand-drawn cart icon (×3 separate redraws across 3 files), no-image/package/grid empty-state illustrations, check/X status icons, thumbs-up/down vote icons, dynamic fill/stroke rating stars | `star` (with new `color` input + `.dx-star--filled` CSS class for the solid/outline toggle), `cart`, `image`/`package`/`layoutGrid`, `check`/`x`, `thumbsUp`/`thumbsDown` | The cart icon alone had been hand-drawn from scratch in 5 different files across this audit (header, items-carousel, subcategories, item-detail, cart) — now one icon, one registry entry |
|
||||||
|
| Shared `app-select` | Browser-native `<select>` dropdown indicator (renders differently per browser, no relation to the icon system) | `chevronDown` (native indicator hidden via `appearance: none`) | "Selects: replace browser default indicators" — this component is used by nearly every admin form, so the fix applies everywhere at once |
|
||||||
|
| Shared `app-pagination` | `«` / `»` HTML entities | `chevronLeft` / `chevronRight` | "Pagination: use proper chevrons" |
|
||||||
|
| Every native `<details>`/`<summary>` expander (7 call sites: admin product form ×4, page editor, builder widget panel) | Browser-default disclosure triangle (differs Chrome/Firefox/Safari) | Lucide `ChevronDown` path drawn via one global CSS rule (`details > summary::after`), rotated 180° on `[open]` | Covers all 7 expanders with a single CSS change rather than touching each template — no shared class existed to hang a component-based fix on |
|
||||||
|
|
||||||
|
## Consistency improvements
|
||||||
|
|
||||||
|
- **One icon family everywhere.** Zero PrimeIcons (`pi-*`) class usages remain
|
||||||
|
anywhere in the app (verified by full-repo sweep after each batch). PrimeIcons
|
||||||
|
(`primeicons` package) is still installed as a PrimeNG peer dependency but no
|
||||||
|
longer used directly for any icon in app code.
|
||||||
|
- **One default size/stroke-width.** `app-icon` defaults to 20px / stroke-width 2;
|
||||||
|
every call site that deviates does so for a legible, deliberate reason (48px empty-
|
||||||
|
state illustration, 14px inline chevron), not arbitrary per-instance sizing.
|
||||||
|
- **Color via `currentColor`, not hardcoded hex.** Every hand-rolled SVG that baked a
|
||||||
|
specific hex into its `fill`/`stroke` attribute now inherits color from its
|
||||||
|
surrounding CSS `color`, so hover/active/disabled states that already change text
|
||||||
|
color also correctly recolor the icon — previously several icons ignored those
|
||||||
|
states entirely because their color was hardcoded in the SVG markup.
|
||||||
|
- **Duplicated icons resolved to one instance:**
|
||||||
|
- Magnifying glass: was hand-drawn independently in header (×2), search page (×4).
|
||||||
|
Now one `search` icon everywhere.
|
||||||
|
- Shopping cart: was hand-drawn independently in header, items-carousel,
|
||||||
|
subcategories, item-detail, and cart (via the now-deleted
|
||||||
|
`EmptyCartIconComponent`). Now one `cart` icon everywhere.
|
||||||
|
- Chevron/dropdown arrow: was hand-drawn independently in language-selector (×2),
|
||||||
|
region-selector, and the builder mobile nav (×3). Now one `chevronDown`/
|
||||||
|
`chevronRight` everywhere.
|
||||||
|
- "Login required" icon (chat-bubble shape): was drawn identically in both
|
||||||
|
telegram-login and cart's login gate. Now one `lock` icon in both, matching the
|
||||||
|
actual semantic ("authentication required") better than a chat bubble did.
|
||||||
|
- **Bare text glyphs replaced with real icons.** `♥` and `⇄` in the header were plain
|
||||||
|
Unicode characters, not part of any icon system, inconsistent stroke weight and
|
||||||
|
optical size versus every other icon on the same toolbar. Now `heart` and `scale`.
|
||||||
|
- **Corrected a genuine meaning collision**, not just a style one: PrimeIcons'
|
||||||
|
`pi-bars` (hamburger lines) was reused in the Marketplace Builder for both the
|
||||||
|
mobile menu toggle *and* every drag-to-reorder handle. A user scanning the builder
|
||||||
|
UI would see the same glyph mean "open navigation" in the header and "drag this
|
||||||
|
row" in a list — added a dedicated `grip` icon (`GripVertical`) so the two concepts
|
||||||
|
are now visually distinct.
|
||||||
|
- **Icon-only buttons audited for accessible names.** Swept every button whose only
|
||||||
|
content is an icon; found three relying on `title` alone (not reliably announced by
|
||||||
|
screen readers) or nothing at all (region-selector detect-location, carousel
|
||||||
|
add-to-cart, subcategories add-to-cart) and added `aria-label` to all three.
|
||||||
|
- **Decorative icons intentionally kept as custom SVG (not migrated):**
|
||||||
|
- The **Telegram brand logo** (cart, telegram-login) — a brand mark, not a generic
|
||||||
|
icon; replacing it with a generic Lucide icon would misrepresent the brand.
|
||||||
|
- **`layout-switcher`'s grid-pattern preview icons** — these show the actual layout
|
||||||
|
being selected (2-column, compact grid, list, etc.) as a literal visual preview,
|
||||||
|
not a stand-in for a word. Lucide has no equivalent "this exact grid pattern"
|
||||||
|
icon set; redrawing them as generic layout icons would lose the preview function.
|
||||||
|
|
||||||
|
## Remaining issues / backlog
|
||||||
|
|
||||||
|
- **No sortable table columns exist anywhere in the app** to add sort-direction
|
||||||
|
icons to (every admin list table has static, non-interactive `<th>` labels). Adding
|
||||||
|
actual column-sort interactivity would be new functionality, out of scope for an
|
||||||
|
icon/visual-language pass — flagged here rather than invented.
|
||||||
|
- **Bundle size**: `app-icon` renders each Lucide icon as its own standalone Angular
|
||||||
|
component (the current `@lucide/angular` API — no tree-shakeable "icon font" or
|
||||||
|
sprite sheet). The initial bundle grew from the prior build's already-over-budget
|
||||||
|
~585 KB over the 700 KB target to ~760 KB over, mostly from icon components now
|
||||||
|
bundled eagerly in the admin shell/dashboard/builder (loaded on every admin route).
|
||||||
|
Worth a follow-up pass to lazy-load icon-heavy admin sections if bundle size becomes
|
||||||
|
a concrete problem.
|
||||||
|
- **No `docs/DESIGN.md` exists** in this repo (confirmed across all three RC sprints
|
||||||
|
this session). The `impeccable` design-quality hook flagged pre-existing font-size/
|
||||||
|
radius/color values against a document that doesn't exist — none of those findings
|
||||||
|
were introduced by this pass; they're pre-existing values in files this pass
|
||||||
|
touched for unrelated reasons (colors, radii, spacing untouched).
|
||||||
|
- **PrimeIcons (`primeicons` npm package) is still installed** — it's a transitive
|
||||||
|
dependency PrimeNG components may rely on internally (calendar, carousel nav
|
||||||
|
arrows rendered by `p-carousel`, etc.), so it wasn't removed from `package.json`.
|
||||||
|
No app code imports `pi-*` classes directly anymore, but a future pass could audit
|
||||||
|
whether PrimeNG's own internal icon usage (e.g. `p-carousel`'s built-in prev/next
|
||||||
|
arrows) should also be re-skinned to match, or left as PrimeNG's own visual
|
||||||
|
language since those are framework-owned, not hand-authored.
|
||||||
Reference in New Issue
Block a user