icons 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 258de5224f1792f19bc6024149d3b83796c215fb2dafcdb4ad832817396fa497
4
- data.tar.gz: 735659b38f49f975bfd758c7260e7ea5ac56971a9cd3942293b8f0ae2c240f3d
3
+ metadata.gz: eddb62f576eb4a0f7914cd2fba762e6306bed0fefd719671c9f631a3d4b84644
4
+ data.tar.gz: a108e7b8f54aada59206d3c2a125d4b75c6e05386ac84ca760f13a801d2dbb8d
5
5
  SHA512:
6
- metadata.gz: 62fa8a1399597b1e99e524f16db444af2680a42e23b5b86d938ed9855fa0859e134f2810aed56d51e398e79f366c71a9106bd12de70e0f62a26ccdbe449e1e32
7
- data.tar.gz: 233185f2705e719ec0230091aec8b2390a70e7ea74bdbe09376942388fa6db1dbf5ab6f5757afbf5fab89f5f83b0c7ce90c2a72ea732a9dfc52ac5e96db1f6cc
6
+ metadata.gz: 2f6d4f4a1e29efc77f46622ab78b4aa041d7a5116d3c4ec945a610d66f60f8f8cd66266f0cbfcb0b9ccff6d7f4c515c726d69ece0af5bc41c729da31054d4f66
7
+ data.tar.gz: 22dcc6888aa46bf59476e9d511df57d04ff4ff7e5c32230372164f06c8ccd6654e3875a28d2a986874eae162330d1dd581e24e051d861511b7fb4126c3a9e038
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- icons (0.8.1)
4
+ icons (0.9.0)
5
5
  nokogiri (~> 1.16, >= 1.16.4)
6
6
 
7
7
  GEM
@@ -119,7 +119,7 @@ CHECKSUMS
119
119
  date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
120
120
  debug (1.11.1) sha256=2e0b0ac6119f2207a6f8ac7d4a73ca8eb4e440f64da0a3136c30343146e952b6
121
121
  erb (6.0.1) sha256=28ecdd99c5472aebd5674d6061e3c6b0a45c049578b071e5a52c2a7f13c197e5
122
- icons (0.8.1)
122
+ icons (0.9.0)
123
123
  io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
124
124
  irb (1.16.0) sha256=2abe56c9ac947cdcb2f150572904ba798c1e93c890c256f8429981a7675b0806
125
125
  json (2.18.0) sha256=b10506aee4183f5cf49e0efc48073d7b75843ce3782c68dbeb763351c08fd505
data/README.md CHANGED
@@ -34,6 +34,22 @@ Icons::Sync.new("lucide").now
34
34
  # Render an icon
35
35
  icon = Icons::Icon.new(name: "check", library: "lucide", variant: "outline", arguments: { class: "text-gray-500" })
36
36
  svg = icon.svg
37
+
38
+ # Generate SVG sprite for performance
39
+ sprite = Icons::Sprite.new(icons: ["check", "search"], library: "lucide", variant: "outline")
40
+ svg = sprite.svg
41
+ # => <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
42
+ # <symbol id="lucide_outline_check" viewBox="0 0 24 24">...</symbol>
43
+ # <symbol id="lucide_outline_search" viewBox="0 0 24 24">...</symbol>
44
+ # </svg>
45
+
46
+ # Or configure globally
47
+ Icons.configure do |config|
48
+ config.sprite = { lucide: { outline: ["check", "search"] } }
49
+ end
50
+
51
+ sprite = Icons::Sprite.new
52
+ sprite.svg
37
53
  ```
38
54
 
39
55
  The resulting SVG will include the proper attributes and the SVG content from the library’s asset path.
@@ -50,7 +50,11 @@ module Icons
50
50
  {
51
51
  filenames: {
52
52
  delete_prefix: ["bxl-", "bx-", "bxs-"]
53
- }
53
+ },
54
+
55
+ svg: [
56
+ {element: "path", action: :set_attribute, attribute: "fill", value: "currentColor"}
57
+ ]
54
58
  }
55
59
  end
56
60
 
@@ -5,7 +5,10 @@ require "icons/configuration/options"
5
5
 
6
6
  module Icons
7
7
  class Configuration
8
- attr_accessor :default_library, :icons_path, :default_variant
8
+ # @return [String, nil]
9
+ attr_accessor :default_library, :icons_path, :default_variant, :sprite, :default_sprite_location, :validate_sprite_icons
10
+
11
+ # @return [Options]
9
12
  attr_reader :libraries
10
13
 
11
14
  def initialize
@@ -15,22 +18,31 @@ module Icons
15
18
  set_libraries_config
16
19
  end
17
20
 
21
+ # @deprecated Use {#icons_path} instead
22
+ # @return [String]
23
+ #
18
24
  def destination_path
19
25
  warn "[DEPRECATION] `destination_path` is deprecated. Use `icons_path` instead."
20
26
 
21
27
  @icons_path
22
28
  end
23
29
 
30
+ # @deprecated Use {#icons_path=} instead
31
+ #
24
32
  def destination_path=(value)
25
33
  warn "[DEPRECATION] `destination_path=` is deprecated. Use `icons_path=` instead."
26
34
 
27
35
  @icons_path = value
28
36
  end
29
37
 
38
+ # @return [Pathname]
39
+ #
30
40
  def base_path
31
41
  @base_path ||= Pathname.new(Dir.pwd)
32
42
  end
33
43
 
44
+ # @param value [Pathname, String]
45
+ #
34
46
  def base_path=(value)
35
47
  @base_path = value.is_a?(Pathname) ? value : Pathname.new(value)
36
48
  end
@@ -41,6 +53,9 @@ module Icons
41
53
  @default_library = nil
42
54
  @default_variant = nil
43
55
  @icons_path = "app/assets/svg/icons"
56
+ @sprite = {}
57
+ @default_sprite_location = nil
58
+ @validate_sprite_icons = false
44
59
  end
45
60
 
46
61
  def set_libraries_config
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Icons
4
+ class Icon
5
+ module Configurable
6
+ private
7
+
8
+ def set_variant
9
+ value = @config.libraries.dig(@library, :default_variant) || @config.default_variant
10
+
11
+ value.to_s.empty? ? nil : value
12
+ end
13
+
14
+ def error_message
15
+ attributes = [
16
+ @library,
17
+ @variant,
18
+ @name
19
+ ].compact
20
+
21
+ "Icon not found: `#{attributes.join(" / ")}`"
22
+ end
23
+
24
+ def attach_attributes(to:)
25
+ Icons::Icon::Attributes
26
+ .new(default_attributes: default_attributes, arguments: @arguments)
27
+ .attach(to: to)
28
+ end
29
+
30
+ def default_attributes
31
+ {
32
+ "stroke-width": default(:stroke_width),
33
+ data: default(:data),
34
+ class: default(:css)
35
+ }.compact
36
+ end
37
+
38
+ def default(key)
39
+ library_attributes.dig(:default, key)
40
+ end
41
+
42
+ def library_attributes
43
+ keys = [@library, @variant].compact
44
+
45
+ @config.libraries.dig(*keys) || {}
46
+ end
47
+ end
48
+ end
49
+ end
data/lib/icons/icon.rb CHANGED
@@ -4,8 +4,16 @@ require "nokogiri"
4
4
 
5
5
  require "icons/icon/file_path"
6
6
  require "icons/icon/attributes"
7
+ require "icons/icon/configurable"
7
8
 
8
9
  class Icons::Icon
10
+ include Icons::Icon::Configurable
11
+
12
+ # @param name [String] The icon name
13
+ # @param library [String, Symbol] The icon library
14
+ # @param variant [String, Symbol, nil] The icon variant (optional)
15
+ # @param arguments [Hash] Additional attributes including class, data, stroke_width, etc.
16
+ #
9
17
  def initialize(name:, library:, arguments:, variant: nil)
10
18
  @config = Icons.configuration
11
19
 
@@ -15,6 +23,12 @@ class Icons::Icon
15
23
  @arguments = arguments
16
24
  end
17
25
 
26
+ # Returns the rendered SVG markup for the icon
27
+ #
28
+ # @return [String] The SVG markup as an HTML string
29
+ #
30
+ # @raise [Icons::IconNotFound] If the icon file does not exist
31
+ #
18
32
  def svg
19
33
  Nokogiri::HTML::DocumentFragment.parse(File.read(file_path))
20
34
  .at_css("svg")
@@ -26,48 +40,7 @@ class Icons::Icon
26
40
 
27
41
  private
28
42
 
29
- def set_variant
30
- value = @config.libraries.dig(@library, :default_variant) ||
31
- @config.default_variant
32
-
33
- value.to_s.empty? ? nil : value
34
- end
35
-
36
- def error_message
37
- attributes = [
38
- @library,
39
- @variant,
40
- @name
41
- ].compact
42
-
43
- "Icon not found: `#{attributes.join(" / ")}`"
44
- end
45
-
46
43
  def file_path
47
44
  Icons::Icon::FilePath.new(name: @name, library: @library, variant: @variant).call
48
45
  end
49
-
50
- def attach_attributes(to:)
51
- Icons::Icon::Attributes
52
- .new(default_attributes: default_attributes, arguments: @arguments)
53
- .attach(to: to)
54
- end
55
-
56
- def default_attributes
57
- {
58
- "stroke-width": default(:stroke_width),
59
- data: default(:data),
60
- class: default(:css)
61
- }
62
- end
63
-
64
- def default(key)
65
- library_attributes.dig(:default, key)
66
- end
67
-
68
- def library_attributes
69
- keys = [@library, @variant].compact
70
-
71
- @config.libraries.dig(*keys) || {}
72
- end
73
46
  end
@@ -15,6 +15,8 @@ require "icons/configuration/weather"
15
15
  module Icons
16
16
  extend self
17
17
 
18
+ # @return [Hash{Symbol => Class}] A map of library names to their configuration classes
19
+ #
18
20
  def libraries
19
21
  {
20
22
  boxicons: Icons::Configuration::Boxicons,
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Icons
4
+ class Sprite
5
+ Reference = Data.define(:name, :library, :variant) do
6
+ def id = [library, variant, name].join("_")
7
+
8
+ def file_path
9
+ Icons::Icon::FilePath.new(
10
+ name: name.to_s,
11
+ library: library.to_s,
12
+ variant: variant.to_s
13
+ ).call
14
+ end
15
+
16
+ def exists?
17
+ File.exist?(file_path)
18
+ rescue Icons::IconNotFound
19
+ false
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Icons
4
+ class Sprite
5
+ # @param config [Configuration] The configuration object (defaults to Icons.configuration)
6
+ # @param icons [Array<String>, nil] Optional list of icon names to include (defaults to configured icons)
7
+ # @param library [String, Symbol, nil] The icon library to use when icons are provided
8
+ # @param variant [String, Symbol, nil] The icon variant to use when icons are provided
9
+ #
10
+ def initialize(config: Icons.configuration, icons: nil, library: nil, variant: nil)
11
+ @config = config
12
+ @icons = icons
13
+ @library = library
14
+ @variant = variant
15
+ end
16
+
17
+ # Returns the combined SVG sprite markup containing all icon symbols
18
+ #
19
+ # @return [String] The SVG markup with `<symbol>` elements wrapped in a hidden `<svg>`
20
+ #
21
+ def svg
22
+ symbols = references.filter_map { |reference| symbol_from(reference) }
23
+
24
+ <<~SVG
25
+ <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
26
+ #{symbols.join("\n ")}
27
+ </svg>
28
+ SVG
29
+ end
30
+
31
+ private
32
+
33
+ def references
34
+ @icons ? override_references : configured_references
35
+ end
36
+
37
+ def override_references
38
+ library = @library || @config.default_library
39
+ variant = @variant || @config.default_variant
40
+
41
+ @icons.map { |name| Sprite::Reference.new(name: name, library: library, variant: variant) }
42
+ end
43
+
44
+ def configured_references
45
+ sprite_config = @config.sprite || {}
46
+
47
+ sprite_config.flat_map do |library, variants|
48
+ variants.flat_map do |variant, names|
49
+ names.map { |name| Sprite::Reference.new(name: name, library: library, variant: variant) }
50
+ end
51
+ end
52
+ end
53
+
54
+ def symbol_from(reference)
55
+ return unless reference.exists?
56
+
57
+ svg_element = Nokogiri::XML(File.read(reference.file_path)).at_css("svg")
58
+ view_box = svg_element["viewBox"] || "0 0 24 24"
59
+ content = svg_element.children.map(&:to_s).join
60
+
61
+ %(<symbol id="#{reference.id}" viewBox="#{view_box}">#{content}</symbol>)
62
+ rescue Icons::IconNotFound
63
+ warn "Icon not found: #{reference.name} from #{reference.library}/#{reference.variant}"
64
+
65
+ nil
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ require "icons/icon/attributes"
6
+ require "icons/icon/configurable"
7
+
8
+ class Icons::SpriteIcon
9
+ include Icons::Icon::Configurable
10
+
11
+ # @param name [String] The icon name
12
+ # @param library [String, Symbol] The icon library
13
+ # @param variant [String, Symbol, nil] The icon variant (optional)
14
+ # @param arguments [Hash] Additional attributes including class, data, stroke_width, etc.
15
+ # @param sprite_location [String, nil] Override URL for the sprite file (optional)
16
+ # @param config [Configuration] The configuration object (defaults to Icons.configuration)
17
+ #
18
+ def initialize(name:, library:, arguments:, variant: nil, sprite_location: nil, config: Icons.configuration)
19
+ @config = config
20
+
21
+ @name = name
22
+ @library = library.to_sym
23
+ @variant = (variant || set_variant)&.to_sym
24
+ @arguments = arguments
25
+ @sprite_location = sprite_location || @config.default_sprite_location
26
+ end
27
+
28
+ # Returns the SVG markup referencing the icon from a sprite sheet
29
+ #
30
+ # @return [String] The SVG markup using a `<use>` tag referencing the sprite symbol
31
+ #
32
+ # @raise [Icons::IconNotFound] If validate_sprite_icons is enabled and the icon does not exist
33
+ #
34
+ def svg
35
+ if @config.validate_sprite_icons
36
+ raise Icons::IconNotFound, error_message unless reference.exists?
37
+ end
38
+
39
+ sprite_svg
40
+ end
41
+
42
+ private
43
+
44
+ def reference
45
+ @reference ||= Icons::Sprite::Reference.new(name: @name, library: @library, variant: @variant)
46
+ end
47
+
48
+ def sprite_svg
49
+ sprite_href = @sprite_location.nil? ? "##{reference.id}" : "#{@sprite_location}##{reference.id}"
50
+
51
+ svg_content = <<~SVG
52
+ <svg><use href="#{sprite_href}"></use></svg>
53
+ SVG
54
+
55
+ Nokogiri::HTML::DocumentFragment.parse(svg_content)
56
+ .at_css("svg")
57
+ .tap { |svg| attach_attributes(to: svg) }
58
+ .to_html
59
+ end
60
+ end
@@ -47,10 +47,13 @@ module Icons
47
47
 
48
48
  def apply_transformations_to(destination)
49
49
  Dir.each_child(destination) do |filename|
50
- File.rename(
51
- File.join(destination, filename),
52
- File.join(destination, Sync::Transformations.transform(filename, transformations.fetch(:filenames, {})))
53
- )
50
+ original_file_path = File.join(destination, filename)
51
+ transformed_filename = Sync::Transformations.transform(filename, transformations)
52
+ transformed_file_path = File.join(destination, transformed_filename)
53
+
54
+ File.rename(original_file_path, transformed_file_path)
55
+
56
+ transform_svg(transformed_file_path)
54
57
  end
55
58
  end
56
59
 
@@ -75,6 +78,14 @@ module Icons
75
78
  {}
76
79
  end
77
80
  end
81
+
82
+ def transform_svg(file_path)
83
+ return if File.extname(file_path) != ".svg"
84
+
85
+ svg_transformations = transformations.fetch(:svg, [])
86
+
87
+ Sync::Transformations.transform_svg(file_path, svg_transformations)
88
+ end
78
89
  end
79
90
  end
80
91
  end
@@ -1,21 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "nokogiri"
4
+
3
5
  module Icons
4
6
  class Sync
5
7
  class Transformations
6
8
  def self.transform(filename, rules = {})
7
9
  basename = File.basename(filename, File.extname(filename))
8
10
 
9
- transformed = rules.reduce(basename) do |fn, (type, value)|
10
- TRANSFORMERS.fetch(type).call(fn, value)
11
+ transformed = rules.fetch(:filenames, {}).reduce(basename) do |fn, (type, value)|
12
+ FILENAME_TRANSFORMERS.fetch(type).call(fn, value)
11
13
  end
12
14
 
13
15
  [transformed, File.extname(filename)].join
14
16
  end
15
17
 
18
+ def self.transform_svg(file_path, rules = [])
19
+ return unless rules.any?
20
+
21
+ svg_document = Nokogiri::HTML::DocumentFragment.parse(File.read(file_path))
22
+
23
+ rules.each do |rule|
24
+ SVG_TRANSFORMERS.fetch(rule[:action]).call(
25
+ svg_document,
26
+ rule[:element],
27
+ rule[:attribute],
28
+ rule[:value]
29
+ )
30
+ end
31
+
32
+ File.write(file_path, svg_document.to_html)
33
+ end
34
+
16
35
  private
17
36
 
18
- TRANSFORMERS = {
37
+ FILENAME_TRANSFORMERS = {
19
38
  delete_prefix: ->(filename, prefixes) {
20
39
  Array(prefixes).reduce(filename) { |fn, prefix| fn.delete_prefix(prefix) }
21
40
  },
@@ -24,6 +43,14 @@ module Icons
24
43
  Array(suffixes).reduce(filename) { |fn, suffix| fn.delete_suffix(suffix) }
25
44
  }
26
45
  }
46
+
47
+ SVG_TRANSFORMERS = {
48
+ set_attribute: ->(document, element_selector, attribute_name, attribute_value) {
49
+ document.css(element_selector).each do |element|
50
+ element[attribute_name] = attribute_value
51
+ end
52
+ }
53
+ }
27
54
  end
28
55
  end
29
56
  end
data/lib/icons/sync.rb CHANGED
@@ -6,7 +6,7 @@ require "icons/sync/process_variants"
6
6
  module Icons
7
7
  class Sync
8
8
  def initialize(name)
9
- raise "[Icons] Not a valid library" if Icons.libraries.keys.exclude?(name.to_sym)
9
+ raise "[Icons] Not a valid library" unless Icons.libraries.key?(name.to_sym)
10
10
 
11
11
  @name = name
12
12
  @library = Icons.libraries.fetch(name.to_sym).source
@@ -36,11 +36,25 @@ module Icons
36
36
  end
37
37
 
38
38
  def clone_repository
39
- raise "[Icons] Failed to clone repository" unless system("git clone '#{@library[:url]}' '#{@temp_directory}'")
39
+ unless clone_repository_sparse
40
+ puts "[Icons] Sparse clone failed, falling back to full clone"
41
+ FileUtils.rm_rf(@temp_directory)
42
+
43
+ raise "[Icons] Failed to clone repository" unless system("git clone '#{@library[:url]}' '#{@temp_directory}'")
44
+ end
40
45
 
41
46
  puts "[Icons] '#{@name}' repository cloned"
42
47
  end
43
48
 
49
+ def clone_repository_sparse
50
+ system("git clone --depth 1 --filter=blob:none --sparse '#{@library[:url]}' '#{@temp_directory}'") &&
51
+ system("git -C '#{@temp_directory}' sparse-checkout set #{sparse_checkout_paths}")
52
+ end
53
+
54
+ def sparse_checkout_paths
55
+ @library[:variants].values.map { |path| "'#{path}'" }.join(" ")
56
+ end
57
+
44
58
  def process_variants
45
59
  Sync::ProcessVariants.new(@temp_directory, @name, @library).process
46
60
  end
data/lib/icons/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Icons
2
- VERSION = "0.8.1"
2
+ VERSION = "0.9.0"
3
3
  end
data/lib/icons.rb CHANGED
@@ -5,17 +5,25 @@ require "icons/errors"
5
5
  require "icons/libraries"
6
6
  require "icons/configuration"
7
7
  require "icons/icon"
8
+ require "icons/sprite/reference"
9
+ require "icons/sprite"
8
10
  require "icons/sync"
9
11
 
10
12
  module Icons
11
13
  class << self
14
+ # @return [Configuration]
12
15
  attr_accessor :configuration
13
16
 
17
+ # @yield [config] Yields the configuration object for customization
18
+ # @yieldparam config [Configuration]
19
+ #
14
20
  def configure
15
21
  self.configuration ||= Configuration.new
16
22
  yield(configuration) if block_given?
17
23
  end
18
24
 
25
+ # @return [Configuration]
26
+ #
19
27
  def config
20
28
  configuration || configure {}
21
29
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: icons
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rails Designer
@@ -67,8 +67,12 @@ files:
67
67
  - lib/icons/errors.rb
68
68
  - lib/icons/icon.rb
69
69
  - lib/icons/icon/attributes.rb
70
+ - lib/icons/icon/configurable.rb
70
71
  - lib/icons/icon/file_path.rb
71
72
  - lib/icons/libraries.rb
73
+ - lib/icons/sprite.rb
74
+ - lib/icons/sprite/reference.rb
75
+ - lib/icons/sprite_icon.rb
72
76
  - lib/icons/sync.rb
73
77
  - lib/icons/sync/process_variants.rb
74
78
  - lib/icons/sync/transformations.rb
@@ -93,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
93
97
  - !ruby/object:Gem::Version
94
98
  version: '0'
95
99
  requirements: []
96
- rubygems_version: 4.0.8
100
+ rubygems_version: 4.0.14
97
101
  specification_version: 4
98
102
  summary: Add any icon library to a Ruby app
99
103
  test_files: []