Files
marketplaces/docs/UI-ICON-AUDIT.md
sdarbinyan a362dd4668
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
docs(icons): add UI icon & visual language audit
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 03:03:30 +04:00

12 KiB
Raw Blame History

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 &laquo; / &raquo; 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.