Skip to content

Inline category icons as SVG markup with theme/hover-aware colors - #201

Merged
sdunster merged 1 commit into
mainfrom
claude/svg-icon-approaches-ylqw45
Sep 4, 2026
Merged

Inline category icons as SVG markup with theme/hover-aware colors#201
sdunster merged 1 commit into
mainfrom
claude/svg-icon-approaches-ylqw45

Conversation

@sdunster

@sdunster sdunster commented Sep 4, 2026

Copy link
Copy Markdown
Member

Summary

The 66 category icons were served as opaque '&lt;img src="/image/categories/*.svg"&gt;' files with colors baked into hex values in the file - fine for the kiosk's current look, but impossible to theme (dark mode) or react to hover/active state, since an <img>-loaded SVG can't be reached by CSS.

  • Moved the icons from web/public/image/categories/ to web/src/assets/categories/, inlined into the DOM at build time via import.meta.glob(..., {eager: true}) (no runtime fetch, no extra request per icon) and a new CategoryIcon component.
  • Each icon keeps its original navy/orange as a literal presentation attribute, so the file still opens correctly in a design tool with no stylesheet present, and layers a class on top that a real stylesheet rule always outranks:
    • icon-stroke / icon-fill ride ordinary currentColor inheritance - a text-navy / group-hover:text-... class on the icon's wrapper drives them, the same mechanism HomeIcons.tsx already uses.
    • icon-accent / icon-accent-stroke read a new --icon-accent custom property (falling back to the existing --color-accent token), so the orange accent can be overridden independently of the navy - by theme, hover, or active state - without fighting over the single currentColor channel.
  • Verified the color/hover/dark-mode mechanism against the actual compiled app CSS (screenshots, light/dark/hover) before wiring it into the kiosk screens.

Knock-on fix

Unlike <img>, inlined SVG is real DOM content: raw whitespace between tags in the source files, and any in-icon <text> (e.g. Flood Operator L1/2/3's badge digit), now show up in textContent. Fixed by stripping inter-tag whitespace once in the loader, marking the icon aria-hidden + data-icon="category", and scoping ScanScreenCategories.test.tsx's label assertions past the icon.

Follow-up

A matching PR applies the same treatment to the two homepage icons (home/HomeIcons.tsx) for consistency.

Test plan

  • npx tsc -b - clean
  • npm run lint - clean
  • npx prettier --check on touched files - clean
  • npm run test:unit - 146/146 passing (after fixing the ScanScreenCategories.test.tsx label assertion)
  • npm run build - succeeds; confirmed the new icon-stroke/icon-fill/icon-accent/icon-accent-stroke rules compile into the output CSS
  • Rendered several icons with the actual compiled CSS in a headless browser to confirm navy follows dark mode and both navy + orange respond to a simulated hover override
  • Not run against the live kiosk UI with a backend (dev-local) in this session - the change is otherwise fully covered by type-check/build/tests above

🤖 Generated with Claude Code

https://claude.ai/code/session_01A3vVVqpFCjgdVXQvRz1hh8


Generated by Claude Code

@sdunster
sdunster marked this pull request as ready for review September 4, 2026 20:48
The 66 category icons were served as opaque <img src="/image/categories/*.svg">
files with colors baked into hex values in the file - fine for the kiosk's
current look, but impossible to theme (dark mode) or react to hover/active
state, since an <img>-loaded SVG can't be reached by CSS.

- Move the icons from web/public/image/categories/ to web/src/assets/categories/,
  inlined into the DOM at build time via import.meta.glob(..., {eager: true})
  (no runtime fetch, no extra request per icon) and a new CategoryIcon
  component, used in place of the old categoryIconSrc()-built <img> tags.
- Each icon keeps its original navy/orange as a literal presentation
  attribute, so the file still opens correctly in a design tool with no
  stylesheet present, and layers a class on top that a real stylesheet rule
  always outranks:
  - icon-stroke / icon-fill ride ordinary currentColor inheritance - a
    text-navy / group-hover:text-... class on the icon's wrapper drives
    them, the same mechanism HomeIcons.tsx already uses.
  - icon-accent / icon-accent-stroke read a new --icon-accent custom
    property (falling back to the existing --color-accent token), so the
    orange accent can be overridden independently of the navy - by theme,
    hover, or active state - without fighting over the single currentColor
    channel.
  - Defined via Tailwind's @Utility mechanism, not a plain @layer base
    class: eslint-plugin-better-tailwindcss's no-unknown-classes rule only
    recognizes classes visible in Tailwind's own utility set, and these
    are referenced as literal JSX classes by a follow-up PR applying the
    same treatment to the homepage icons.
- Update the /demo/category preview page (added to main after this branch's
  first commit) to render icons via CategoryIcon instead of <img>, and
  correct its comment claiming the icon's stroke color doesn't adapt to
  dark mode - it now does.
- Unlike <img>, inlined SVG is real DOM content: raw whitespace between
  tags in the source files, and any in-icon <text> (e.g. Flood Operator
  L1/2/3's badge digit), show up in textContent. Strip inter-tag
  whitespace once in the loader, mark the icon aria-hidden +
  data-icon="category", and scope ScanScreenCategories.test.tsx's label
  assertions past it.
- Verified the color/hover/dark-mode mechanism, and icon sizing under the
  existing max-h-12/max-w-12 wrapper classes, against the actual compiled
  app CSS in a headless browser before wiring it into the kiosk screens.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3vVVqpFCjgdVXQvRz1hh8
@sdunster
sdunster force-pushed the claude/svg-icon-approaches-ylqw45 branch from 20b231e to ee8c6fc Compare September 4, 2026 21:04
@sdunster
sdunster merged commit 8e51199 into main Sep 4, 2026
1 check passed
@sdunster
sdunster deleted the claude/svg-icon-approaches-ylqw45 branch September 4, 2026 21:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants