Last active
May 10, 2026 21:04
-
-
Save tony-rowan/a9d2b61db134c573b4cd644b66a27d4b to your computer and use it in GitHub Desktop.
A fully encapsulated component to handle inlining SVGs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # frozen_string_literal: true | |
| class IconComponent < ViewComponent::Base | |
| class IconNotFoundError < StandardError; end | |
| attr_reader :icon, :size, :options | |
| # Chnage default size class based on your project | |
| def initialize(icon, size: "size-6", **options) | |
| @icon = icon | |
| @size = size | |
| @options = options | |
| end | |
| def call | |
| return render_icon_not_found if icon_not_found? | |
| svg = parsed_svg | |
| set_classes!(svg) | |
| set_aria_attributes!(svg, options[:aria]) | |
| set_data_attributes!(svg, options[:data]) | |
| set_base_attributes!(svg, base_attributes) | |
| set_role!(svg) | |
| svg.to_xml.strip.html_safe | |
| end | |
| private | |
| def icon_name | |
| icon.to_s | |
| end | |
| def class_list | |
| [size, options[:class]].compact_blank.join(" ") | |
| end | |
| def base_attributes | |
| options.except(:class, :aria, :data) | |
| end | |
| def set_classes!(svg) | |
| svg.set("class", (class_list.split + ["icon-#{icon_name.dasherize}"]).join(" ")) | |
| end | |
| def set_aria_attributes!(svg, aria_attributes) | |
| aria_attributes&.each do |attribute, value| | |
| svg.set("aria-#{attribute.to_s.parameterize(preserve_case: true).dasherize}", value) | |
| end | |
| end | |
| def set_data_attributes!(svg, data_attributes) | |
| data_attributes&.each do |attribute, value| | |
| svg.set("data-#{attribute.to_s.parameterize(preserve_case: true).dasherize}", value) | |
| end | |
| end | |
| def set_base_attributes!(svg, attributes) | |
| attributes&.each do |attribute, value| | |
| svg.set(attribute.to_s.parameterize(preserve_case: true).dasherize, value) | |
| end | |
| end | |
| def set_role!(svg) | |
| svg.set("role", svg.get("role").presence || (svg.get("aria-label").blank? ? "presentation" : "img")) | |
| end | |
| def icon_not_found? | |
| !File.exist?(icon_asset_path) | |
| end | |
| def render_icon_not_found | |
| if Rails.env.local? | |
| raise IconNotFoundError, "Could not find icon: #{icon_name}" | |
| else | |
| "(Icon Not Found)" | |
| end | |
| end | |
| def parsed_svg | |
| Oga.parse_xml(File.read(icon_asset_path)).at_css("svg") | |
| end | |
| # Update this to suit your project | |
| def icon_asset_path | |
| @_icon_asset_path ||= Rails.root.join("app/assets/images/icons/#{icon_name}.svg").to_s | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment