@@ -26,6 +26,9 @@ defmodule PhoenixIconify do
2626 attr ( :size , :any , default: nil , doc: "Width and height to apply together" )
2727 attr ( :width , :any , default: nil , doc: "SVG width attribute" )
2828 attr ( :height , :any , default: nil , doc: "SVG height attribute" )
29+ attr ( :color , :string , default: nil , doc: "CSS color for currentColor icons" )
30+ attr ( :inline , :boolean , default: false , doc: "Align icon with text baseline" )
31+ attr ( :mode , :string , default: "svg" , doc: "Render mode: svg, mask, or bg" )
2932 attr ( :rotate , :integer , default: 0 , doc: "Additional 90-degree rotations" )
3033 attr ( :flip , :string , default: nil , doc: "Flip direction: horizontal, vertical, or both" )
3134 attr ( :h_flip , :boolean , default: false , doc: "Apply horizontal flip" )
@@ -35,15 +38,17 @@ defmodule PhoenixIconify do
3538 def icon ( assigns ) do
3639 icon_data = get_icon ( assigns . name )
3740 render_data = render_data ( icon_data , assigns )
38- svg_attrs = svg_attrs ( assigns , render_data )
3941
4042 assigns =
4143 assigns
4244 |> assign ( :body , render_data . body )
43- |> assign ( :svg_attrs , svg_attrs )
45+ |> assign ( :svg_attrs , svg_attrs ( assigns , render_data ) )
46+ |> assign ( :span_attrs , span_attrs ( assigns , render_data ) )
47+ |> assign ( :svg_mode? , svg_mode? ( assigns . mode ) )
4448
4549 ~H"""
46- < svg { @ svg_attrs } > <%= if @ title do %> < title > <%= @ title %> </ title > <% end %> <%= HTML . raw ( @ body ) %> </ svg >
50+ < svg :if = { @ svg_mode? } { @ svg_attrs } > <%= if @ title do %> < title > <%= @ title %> </ title > <% end %> <%= HTML . raw ( @ body ) %> </ svg >
51+ < span :if = { ! @ svg_mode? } { @ span_attrs } > </ span >
4752 """
4853 end
4954
@@ -105,10 +110,13 @@ defmodule PhoenixIconify do
105110 defp render_data ( % Iconify.Icon { } = icon , assigns ) do
106111 { h_flip , v_flip } = flip_options ( assigns . flip , assigns . h_flip , assigns . v_flip )
107112
108- { body , viewbox } =
109- Iconify.SVG . build_body ( icon , rotate: assigns . rotate , h_flip: h_flip , v_flip: v_flip )
110-
111- % { body: body , viewbox: viewbox }
113+ Iconify.SVG . build_data ( icon ,
114+ width: assigns . width || assigns . size ,
115+ height: assigns . height || assigns . size ,
116+ rotate: assigns . rotate ,
117+ h_flip: h_flip ,
118+ v_flip: v_flip
119+ )
112120 end
113121
114122 defp flip_options ( flip , h_flip , v_flip ) do
@@ -125,16 +133,38 @@ defmodule PhoenixIconify do
125133 xmlns: "http://www.w3.org/2000/svg" ,
126134 viewBox: render_data . viewbox ,
127135 fill: "currentColor" ,
128- class: assigns . class
136+ class: assigns . class ,
137+ width: render_data . width ,
138+ height: render_data . height ,
139+ style: style ( assigns . color , assigns . inline )
129140 }
130141
131142 base
132- |> maybe_put ( :width , assigns . width || assigns . size )
133- |> maybe_put ( :height , assigns . height || assigns . size )
143+ |> Map . merge ( accessibility_attrs ( assigns ) )
144+ |> Map . merge ( assigns . rest )
145+ |> Enum . reject ( fn { _key , value } -> is_nil ( value ) or unset_keyword? ( value ) end )
146+ |> Map . new ( )
147+ end
148+
149+ defp span_attrs ( % { mode: "svg" } , _render_data ) , do: % { }
150+
151+ defp span_attrs ( assigns , render_data ) do
152+ style =
153+ assigns . mode
154+ |> String . to_existing_atom ( )
155+ |> span_style ( render_data )
156+ |> style ( assigns . color , assigns . inline )
157+
158+ % {
159+ class: assigns . class ,
160+ style: style
161+ }
134162 |> Map . merge ( accessibility_attrs ( assigns ) )
135163 |> Map . merge ( assigns . rest )
136164 |> Enum . reject ( fn { _key , value } -> is_nil ( value ) end )
137165 |> Map . new ( )
166+ rescue
167+ ArgumentError -> % { }
138168 end
139169
140170 defp accessibility_attrs ( % { label: label , title: title } )
@@ -147,8 +177,77 @@ defmodule PhoenixIconify do
147177
148178 defp accessibility_attrs ( _assigns ) , do: % { "aria-hidden": "true" }
149179
150- defp maybe_put ( map , _key , nil ) , do: map
151- defp maybe_put ( map , key , value ) , do: Map . put ( map , key , value )
180+ defp style ( color , inline ) do
181+ nil
182+ |> maybe_style ( "color" , color )
183+ |> maybe_style ( "vertical-align" , if ( inline , do: "-0.125em" ) )
184+ end
185+
186+ defp style ( style , color , inline ) do
187+ style
188+ |> maybe_style ( "color" , color )
189+ |> maybe_style ( "vertical-align" , if ( inline , do: "-0.125em" ) )
190+ end
191+
192+ defp span_style ( :mask , render_data ) do
193+ svg_url = svg_url ( render_data )
194+
195+ [
196+ "display:inline-block" ,
197+ "width:#{ format_size ( render_data . width ) } " ,
198+ "height:#{ format_size ( render_data . height ) } " ,
199+ "background-color:currentColor" ,
200+ "mask:var(--svg) no-repeat 50% 50% / 100% 100%" ,
201+ "-webkit-mask:var(--svg) no-repeat 50% 50% / 100% 100%" ,
202+ "--svg:url(\" #{ svg_url } \" )"
203+ ]
204+ |> Enum . join ( ";" )
205+ end
206+
207+ defp span_style ( :bg , render_data ) do
208+ svg_url = svg_url ( render_data )
209+
210+ [
211+ "display:inline-block" ,
212+ "width:#{ format_size ( render_data . width ) } " ,
213+ "height:#{ format_size ( render_data . height ) } " ,
214+ "background:transparent var(--svg) no-repeat 50% 50% / 100% 100%" ,
215+ "--svg:url(\" #{ svg_url } \" )"
216+ ]
217+ |> Enum . join ( ";" )
218+ end
219+
220+ defp maybe_style ( style , _key , nil ) , do: style
221+ defp maybe_style ( nil , key , value ) , do: "#{ key } :#{ value } "
222+ defp maybe_style ( style , key , value ) , do: style <> ";#{ key } :#{ value } "
223+
224+ defp svg_url ( render_data ) do
225+ [
226+ "<svg xmlns=\" http://www.w3.org/2000/svg\" viewBox=\" #{ render_data . viewbox } \" width=\" #{ render_data . width } \" height=\" #{ render_data . height } \" >" ,
227+ render_data . body ,
228+ "</svg>"
229+ ]
230+ |> IO . iodata_to_binary ( )
231+ |> svg_to_url ( )
232+ end
233+
234+ defp svg_to_url ( svg ) do
235+ svg
236+ |> String . replace ( "%" , "%25" )
237+ |> String . replace ( "#" , "%23" )
238+ |> String . replace ( "<" , "%3C" )
239+ |> String . replace ( ">" , "%3E" )
240+ |> String . replace ( "\" " , "'" )
241+ |> String . replace ( "&" , "%26" )
242+ end
243+
244+ defp format_size ( value ) when is_number ( value ) , do: to_string ( value ) <> "px"
245+ defp format_size ( value ) , do: value
246+
247+ defp svg_mode? ( mode ) , do: to_string ( mode ) == "svg"
248+
249+ defp unset_keyword? ( value ) when value in [ "unset" , "undefined" , "none" ] , do: true
250+ defp unset_keyword? ( _ ) , do: false
152251
153252 defp handle_missing_icon ( normalized , original ) do
154253 maybe_warn ( missing_icon_message ( normalized , original ) )
0 commit comments