Skip to main content

native_theme/model/
bundled.rs

1// Feature-gated bundled SVG icon access via include_bytes!
2//
3// With `material-icons` or `lucide-icons` features enabled, this module
4// embeds SVG files at compile time and makes them available via
5// `bundled_icon_svg()`. Without features, the function always returns None.
6
7use super::icons::{IconRole, IconSet};
8
9/// Returns the raw SVG bytes for a bundled icon, if the corresponding
10/// feature flag is enabled.
11///
12/// Returns `None` when:
13/// - The requested `IconSet` is not `Material` or `Lucide`
14/// - The feature flag for the requested set is not enabled
15///
16/// Returns `Some(&[u8])` containing valid SVG bytes when the feature
17/// is enabled. Callers typically convert to `IconData::Svg(bytes.to_vec())`.
18///
19/// # Examples
20///
21/// ```
22/// use native_theme::{IconSet, IconRole, bundled_icon_svg};
23///
24/// // Without features enabled, bundled icons return None
25/// let result = bundled_icon_svg(IconSet::SfSymbols, IconRole::ActionCopy);
26/// assert!(result.is_none());
27/// ```
28#[must_use = "this returns SVG bytes; it does not render the icon"]
29#[allow(unreachable_patterns, unused_variables)]
30pub fn bundled_icon_svg(set: IconSet, role: IconRole) -> Option<&'static [u8]> {
31    match set {
32        #[cfg(feature = "material-icons")]
33        IconSet::Material => material_svg(role),
34
35        #[cfg(feature = "lucide-icons")]
36        IconSet::Lucide => lucide_svg(role),
37
38        _ => None,
39    }
40}
41
42#[cfg(feature = "material-icons")]
43#[allow(unreachable_patterns)]
44fn material_svg(role: IconRole) -> Option<&'static [u8]> {
45    Some(match role {
46        // Dialog / Alert (6)
47        IconRole::DialogWarning => include_bytes!("../../icons/material/warning.svg"),
48        IconRole::DialogError => include_bytes!("../../icons/material/error.svg"),
49        IconRole::DialogInfo => include_bytes!("../../icons/material/info.svg"),
50        IconRole::DialogQuestion => include_bytes!("../../icons/material/help.svg"),
51        IconRole::DialogSuccess => include_bytes!("../../icons/material/check_circle.svg"),
52        IconRole::Shield => include_bytes!("../../icons/material/shield.svg"),
53
54        // Window Controls (4)
55        IconRole::WindowClose => include_bytes!("../../icons/material/close.svg"),
56        IconRole::WindowMinimize => include_bytes!("../../icons/material/minimize.svg"),
57        IconRole::WindowMaximize => include_bytes!("../../icons/material/open_in_full.svg"),
58        IconRole::WindowRestore => include_bytes!("../../icons/material/close_fullscreen.svg"),
59
60        // Common Actions (14)
61        IconRole::ActionSave => include_bytes!("../../icons/material/save.svg"),
62        IconRole::ActionDelete => include_bytes!("../../icons/material/delete.svg"),
63        IconRole::ActionCopy => include_bytes!("../../icons/material/content_copy.svg"),
64        IconRole::ActionPaste => include_bytes!("../../icons/material/content_paste.svg"),
65        IconRole::ActionCut => include_bytes!("../../icons/material/content_cut.svg"),
66        IconRole::ActionUndo => include_bytes!("../../icons/material/undo.svg"),
67        IconRole::ActionRedo => include_bytes!("../../icons/material/redo.svg"),
68        IconRole::ActionSearch => include_bytes!("../../icons/material/search.svg"),
69        IconRole::ActionSettings => include_bytes!("../../icons/material/settings.svg"),
70        IconRole::ActionEdit => include_bytes!("../../icons/material/edit.svg"),
71        IconRole::ActionAdd => include_bytes!("../../icons/material/add.svg"),
72        IconRole::ActionRemove => include_bytes!("../../icons/material/remove.svg"),
73        IconRole::ActionRefresh => include_bytes!("../../icons/material/refresh.svg"),
74        IconRole::ActionPrint => include_bytes!("../../icons/material/print.svg"),
75
76        // Navigation (6)
77        IconRole::NavBack => include_bytes!("../../icons/material/arrow_back.svg"),
78        IconRole::NavForward => include_bytes!("../../icons/material/arrow_forward.svg"),
79        IconRole::NavUp => include_bytes!("../../icons/material/arrow_upward.svg"),
80        IconRole::NavDown => include_bytes!("../../icons/material/arrow_downward.svg"),
81        IconRole::NavHome => include_bytes!("../../icons/material/home.svg"),
82        IconRole::NavMenu => include_bytes!("../../icons/material/menu.svg"),
83
84        // Files / Places (5)
85        IconRole::FileGeneric => include_bytes!("../../icons/material/description.svg"),
86        IconRole::FolderClosed => include_bytes!("../../icons/material/folder.svg"),
87        IconRole::FolderOpen => include_bytes!("../../icons/material/folder_open.svg"),
88        IconRole::TrashEmpty => include_bytes!("../../icons/material/delete.svg"),
89        IconRole::TrashFull => include_bytes!("../../icons/material/delete.svg"), // reuse delete
90
91        // Status (3)
92        IconRole::StatusBusy => include_bytes!("../../icons/material/progress_activity.svg"),
93        IconRole::StatusCheck => include_bytes!("../../icons/material/check.svg"),
94        IconRole::StatusError => include_bytes!("../../icons/material/error.svg"), // reuse error
95
96        // System (4)
97        IconRole::UserAccount => include_bytes!("../../icons/material/person.svg"),
98        IconRole::Notification => include_bytes!("../../icons/material/notifications.svg"),
99        IconRole::Help => include_bytes!("../../icons/material/help.svg"), // reuse help
100        IconRole::Lock => include_bytes!("../../icons/material/lock.svg"),
101
102        _ => return None, // #[non_exhaustive] forward compat
103    })
104}
105
106#[cfg(feature = "lucide-icons")]
107#[allow(unreachable_patterns)]
108fn lucide_svg(role: IconRole) -> Option<&'static [u8]> {
109    Some(match role {
110        // Dialog / Alert (6)
111        IconRole::DialogWarning => include_bytes!("../../icons/lucide/triangle-alert.svg"),
112        IconRole::DialogError => include_bytes!("../../icons/lucide/circle-x.svg"),
113        IconRole::DialogInfo => include_bytes!("../../icons/lucide/info.svg"),
114        IconRole::DialogQuestion => include_bytes!("../../icons/lucide/circle-question-mark.svg"),
115        IconRole::DialogSuccess => include_bytes!("../../icons/lucide/circle-check.svg"),
116        IconRole::Shield => include_bytes!("../../icons/lucide/shield.svg"),
117
118        // Window Controls (4)
119        IconRole::WindowClose => include_bytes!("../../icons/lucide/x.svg"),
120        IconRole::WindowMinimize => include_bytes!("../../icons/lucide/minimize.svg"),
121        IconRole::WindowMaximize => include_bytes!("../../icons/lucide/maximize.svg"),
122        IconRole::WindowRestore => include_bytes!("../../icons/lucide/minimize-2.svg"),
123
124        // Common Actions (14)
125        IconRole::ActionSave => include_bytes!("../../icons/lucide/save.svg"),
126        IconRole::ActionDelete => include_bytes!("../../icons/lucide/trash-2.svg"),
127        IconRole::ActionCopy => include_bytes!("../../icons/lucide/copy.svg"),
128        IconRole::ActionPaste => include_bytes!("../../icons/lucide/clipboard-paste.svg"),
129        IconRole::ActionCut => include_bytes!("../../icons/lucide/scissors.svg"),
130        IconRole::ActionUndo => include_bytes!("../../icons/lucide/undo-2.svg"),
131        IconRole::ActionRedo => include_bytes!("../../icons/lucide/redo-2.svg"),
132        IconRole::ActionSearch => include_bytes!("../../icons/lucide/search.svg"),
133        IconRole::ActionSettings => include_bytes!("../../icons/lucide/settings.svg"),
134        IconRole::ActionEdit => include_bytes!("../../icons/lucide/pencil.svg"),
135        IconRole::ActionAdd => include_bytes!("../../icons/lucide/plus.svg"),
136        IconRole::ActionRemove => include_bytes!("../../icons/lucide/minus.svg"),
137        IconRole::ActionRefresh => include_bytes!("../../icons/lucide/refresh-cw.svg"),
138        IconRole::ActionPrint => include_bytes!("../../icons/lucide/printer.svg"),
139
140        // Navigation (6)
141        IconRole::NavBack => include_bytes!("../../icons/lucide/chevron-left.svg"),
142        IconRole::NavForward => include_bytes!("../../icons/lucide/chevron-right.svg"),
143        IconRole::NavUp => include_bytes!("../../icons/lucide/chevron-up.svg"),
144        IconRole::NavDown => include_bytes!("../../icons/lucide/chevron-down.svg"),
145        IconRole::NavHome => include_bytes!("../../icons/lucide/house.svg"),
146        IconRole::NavMenu => include_bytes!("../../icons/lucide/menu.svg"),
147
148        // Files / Places (5)
149        IconRole::FileGeneric => include_bytes!("../../icons/lucide/file.svg"),
150        IconRole::FolderClosed => include_bytes!("../../icons/lucide/folder-closed.svg"),
151        IconRole::FolderOpen => include_bytes!("../../icons/lucide/folder-open.svg"),
152        IconRole::TrashEmpty => include_bytes!("../../icons/lucide/trash-2.svg"),
153        IconRole::TrashFull => include_bytes!("../../icons/lucide/trash-2.svg"), // reuse trash-2
154
155        // Status (3)
156        IconRole::StatusBusy => include_bytes!("../../icons/lucide/loader.svg"),
157        IconRole::StatusCheck => include_bytes!("../../icons/lucide/check.svg"),
158        IconRole::StatusError => include_bytes!("../../icons/lucide/circle-x.svg"), // reuse circle-x
159
160        // System (4)
161        IconRole::UserAccount => include_bytes!("../../icons/lucide/user.svg"),
162        IconRole::Notification => include_bytes!("../../icons/lucide/bell.svg"),
163        IconRole::Help => include_bytes!("../../icons/lucide/circle-question-mark.svg"), // reuse
164        IconRole::Lock => include_bytes!("../../icons/lucide/lock.svg"),
165
166        _ => return None, // #[non_exhaustive] forward compat
167    })
168}
169
170/// Returns raw SVG bytes for a bundled icon looked up by its canonical name
171/// within the icon set.
172///
173/// Names use each set's canonical format:
174/// - Lucide: kebab-case (e.g., `"arrow-down"`, `"circle-check"`)
175/// - Material: snake_case (e.g., `"arrow_downward"`, `"check_circle"`)
176///
177/// Returns `None` for non-bundled sets, disabled features, or unknown names.
178///
179/// # Examples
180///
181/// ```
182/// use native_theme::{IconSet, bundled_icon_by_name};
183///
184/// // Without features enabled, bundled icons return None
185/// let result = bundled_icon_by_name(IconSet::SfSymbols, "check");
186/// assert!(result.is_none());
187/// ```
188#[must_use = "this returns SVG bytes; it does not render the icon"]
189#[allow(unreachable_patterns, unused_variables)]
190pub fn bundled_icon_by_name(set: IconSet, name: &str) -> Option<&'static [u8]> {
191    match set {
192        #[cfg(feature = "material-icons")]
193        IconSet::Material => material_svg_by_name(name),
194
195        #[cfg(feature = "lucide-icons")]
196        IconSet::Lucide => lucide_svg_by_name(name),
197
198        _ => None,
199    }
200}
201
202#[cfg(feature = "lucide-icons")]
203#[allow(unreachable_patterns)]
204fn lucide_svg_by_name(name: &str) -> Option<&'static [u8]> {
205    Some(match name {
206        "a-large-small" => include_bytes!("../../icons/lucide/a-large-small.svg"),
207        "arrow-down" => include_bytes!("../../icons/lucide/arrow-down.svg"),
208        "arrow-left" => include_bytes!("../../icons/lucide/arrow-left.svg"),
209        "arrow-right" => include_bytes!("../../icons/lucide/arrow-right.svg"),
210        "arrow-up" => include_bytes!("../../icons/lucide/arrow-up.svg"),
211        "asterisk" => include_bytes!("../../icons/lucide/asterisk.svg"),
212        "bell" => include_bytes!("../../icons/lucide/bell.svg"),
213        "book-open" => include_bytes!("../../icons/lucide/book-open.svg"),
214        "bot" => include_bytes!("../../icons/lucide/bot.svg"),
215        "building-2" => include_bytes!("../../icons/lucide/building-2.svg"),
216        "calendar" => include_bytes!("../../icons/lucide/calendar.svg"),
217        "case-sensitive" => include_bytes!("../../icons/lucide/case-sensitive.svg"),
218        "chart-pie" => include_bytes!("../../icons/lucide/chart-pie.svg"),
219        "check" => include_bytes!("../../icons/lucide/check.svg"),
220        "chevron-down" => include_bytes!("../../icons/lucide/chevron-down.svg"),
221        "chevron-left" => include_bytes!("../../icons/lucide/chevron-left.svg"),
222        "chevron-right" => include_bytes!("../../icons/lucide/chevron-right.svg"),
223        "chevrons-up-down" => include_bytes!("../../icons/lucide/chevrons-up-down.svg"),
224        "chevron-up" => include_bytes!("../../icons/lucide/chevron-up.svg"),
225        "circle-check" => include_bytes!("../../icons/lucide/circle-check.svg"),
226        "circle-question-mark" => include_bytes!("../../icons/lucide/circle-question-mark.svg"),
227        "circle-user" => include_bytes!("../../icons/lucide/circle-user.svg"),
228        "circle-x" => include_bytes!("../../icons/lucide/circle-x.svg"),
229        "clipboard-paste" => include_bytes!("../../icons/lucide/clipboard-paste.svg"),
230        "close" => include_bytes!("../../icons/lucide/close.svg"),
231        "copy" => include_bytes!("../../icons/lucide/copy.svg"),
232        "dash" => include_bytes!("../../icons/lucide/dash.svg"),
233        "delete" => include_bytes!("../../icons/lucide/delete.svg"),
234        "ellipsis" => include_bytes!("../../icons/lucide/ellipsis.svg"),
235        "ellipsis-vertical" => include_bytes!("../../icons/lucide/ellipsis-vertical.svg"),
236        "external-link" => include_bytes!("../../icons/lucide/external-link.svg"),
237        "eye" => include_bytes!("../../icons/lucide/eye.svg"),
238        "eye-off" => include_bytes!("../../icons/lucide/eye-off.svg"),
239        "file" => include_bytes!("../../icons/lucide/file.svg"),
240        "folder" => include_bytes!("../../icons/lucide/folder.svg"),
241        "folder-closed" => include_bytes!("../../icons/lucide/folder-closed.svg"),
242        "folder-open" => include_bytes!("../../icons/lucide/folder-open.svg"),
243        "frame" => include_bytes!("../../icons/lucide/frame.svg"),
244        "gallery-vertical-end" => include_bytes!("../../icons/lucide/gallery-vertical-end.svg"),
245        "github" => include_bytes!("../../icons/lucide/github.svg"),
246        "globe" => include_bytes!("../../icons/lucide/globe.svg"),
247        "heart" => include_bytes!("../../icons/lucide/heart.svg"),
248        "heart-off" => include_bytes!("../../icons/lucide/heart-off.svg"),
249        "house" => include_bytes!("../../icons/lucide/house.svg"),
250        "inbox" => include_bytes!("../../icons/lucide/inbox.svg"),
251        "info" => include_bytes!("../../icons/lucide/info.svg"),
252        "inspect" => include_bytes!("../../icons/lucide/inspect.svg"),
253        "layout-dashboard" => include_bytes!("../../icons/lucide/layout-dashboard.svg"),
254        "loader" => include_bytes!("../../icons/lucide/loader.svg"),
255        "loader-circle" => include_bytes!("../../icons/lucide/loader-circle.svg"),
256        "lock" => include_bytes!("../../icons/lucide/lock.svg"),
257        "map" => include_bytes!("../../icons/lucide/map.svg"),
258        "maximize" => include_bytes!("../../icons/lucide/maximize.svg"),
259        "menu" => include_bytes!("../../icons/lucide/menu.svg"),
260        "minimize" => include_bytes!("../../icons/lucide/minimize.svg"),
261        "minimize-2" => include_bytes!("../../icons/lucide/minimize-2.svg"),
262        "minus" => include_bytes!("../../icons/lucide/minus.svg"),
263        "moon" => include_bytes!("../../icons/lucide/moon.svg"),
264        "palette" => include_bytes!("../../icons/lucide/palette.svg"),
265        "panel-bottom" => include_bytes!("../../icons/lucide/panel-bottom.svg"),
266        "panel-bottom-open" => include_bytes!("../../icons/lucide/panel-bottom-open.svg"),
267        "panel-left" => include_bytes!("../../icons/lucide/panel-left.svg"),
268        "panel-left-close" => include_bytes!("../../icons/lucide/panel-left-close.svg"),
269        "panel-left-open" => include_bytes!("../../icons/lucide/panel-left-open.svg"),
270        "panel-right" => include_bytes!("../../icons/lucide/panel-right.svg"),
271        "panel-right-close" => include_bytes!("../../icons/lucide/panel-right-close.svg"),
272        "panel-right-open" => include_bytes!("../../icons/lucide/panel-right-open.svg"),
273        "pencil" => include_bytes!("../../icons/lucide/pencil.svg"),
274        "plus" => include_bytes!("../../icons/lucide/plus.svg"),
275        "printer" => include_bytes!("../../icons/lucide/printer.svg"),
276        "redo" => include_bytes!("../../icons/lucide/redo.svg"),
277        "redo-2" => include_bytes!("../../icons/lucide/redo-2.svg"),
278        "refresh-cw" => include_bytes!("../../icons/lucide/refresh-cw.svg"),
279        "replace" => include_bytes!("../../icons/lucide/replace.svg"),
280        "resize-corner" => include_bytes!("../../icons/lucide/resize-corner.svg"),
281        "save" => include_bytes!("../../icons/lucide/save.svg"),
282        "scissors" => include_bytes!("../../icons/lucide/scissors.svg"),
283        "search" => include_bytes!("../../icons/lucide/search.svg"),
284        "settings" => include_bytes!("../../icons/lucide/settings.svg"),
285        "settings-2" => include_bytes!("../../icons/lucide/settings-2.svg"),
286        "shield" => include_bytes!("../../icons/lucide/shield.svg"),
287        "sort-ascending" => include_bytes!("../../icons/lucide/sort-ascending.svg"),
288        "sort-descending" => include_bytes!("../../icons/lucide/sort-descending.svg"),
289        "square-terminal" => include_bytes!("../../icons/lucide/square-terminal.svg"),
290        "star" => include_bytes!("../../icons/lucide/star.svg"),
291        "star-off" => include_bytes!("../../icons/lucide/star-off.svg"),
292        "sun" => include_bytes!("../../icons/lucide/sun.svg"),
293        "thumbs-down" => include_bytes!("../../icons/lucide/thumbs-down.svg"),
294        "thumbs-up" => include_bytes!("../../icons/lucide/thumbs-up.svg"),
295        "trash-2" => include_bytes!("../../icons/lucide/trash-2.svg"),
296        "triangle-alert" => include_bytes!("../../icons/lucide/triangle-alert.svg"),
297        "undo" => include_bytes!("../../icons/lucide/undo.svg"),
298        "undo-2" => include_bytes!("../../icons/lucide/undo-2.svg"),
299        "user" => include_bytes!("../../icons/lucide/user.svg"),
300        "window-close" => include_bytes!("../../icons/lucide/window-close.svg"),
301        "window-maximize" => include_bytes!("../../icons/lucide/window-maximize.svg"),
302        "window-minimize" => include_bytes!("../../icons/lucide/window-minimize.svg"),
303        "window-restore" => include_bytes!("../../icons/lucide/window-restore.svg"),
304        "x" => include_bytes!("../../icons/lucide/x.svg"),
305        _ => return None,
306    })
307}
308
309#[cfg(feature = "material-icons")]
310#[allow(unreachable_patterns)]
311fn material_svg_by_name(name: &str) -> Option<&'static [u8]> {
312    Some(match name {
313        "account_circle" => include_bytes!("../../icons/material/account_circle.svg"),
314        "add" => include_bytes!("../../icons/material/add.svg"),
315        "apartment" => include_bytes!("../../icons/material/apartment.svg"),
316        "arrow_back" => include_bytes!("../../icons/material/arrow_back.svg"),
317        "arrow_downward" => include_bytes!("../../icons/material/arrow_downward.svg"),
318        "arrow_forward" => include_bytes!("../../icons/material/arrow_forward.svg"),
319        "arrow_upward" => include_bytes!("../../icons/material/arrow_upward.svg"),
320        "autorenew" => include_bytes!("../../icons/material/autorenew.svg"),
321        "calendar_today" => include_bytes!("../../icons/material/calendar_today.svg"),
322        "cancel" => include_bytes!("../../icons/material/cancel.svg"),
323        "check" => include_bytes!("../../icons/material/check.svg"),
324        "check_circle" => include_bytes!("../../icons/material/check_circle.svg"),
325        "chevron_left" => include_bytes!("../../icons/material/chevron_left.svg"),
326        "chevron_right" => include_bytes!("../../icons/material/chevron_right.svg"),
327        "close" => include_bytes!("../../icons/material/close.svg"),
328        "close_fullscreen" => include_bytes!("../../icons/material/close_fullscreen.svg"),
329        "code" => include_bytes!("../../icons/material/code.svg"),
330        "content_copy" => include_bytes!("../../icons/material/content_copy.svg"),
331        "crop_free" => include_bytes!("../../icons/material/crop_free.svg"),
332        "dark_mode" => include_bytes!("../../icons/material/dark_mode.svg"),
333        "dashboard" => include_bytes!("../../icons/material/dashboard.svg"),
334        "delete" => include_bytes!("../../icons/material/delete.svg"),
335        "description" => include_bytes!("../../icons/material/description.svg"),
336        "developer_mode" => include_bytes!("../../icons/material/developer_mode.svg"),
337        "dock_to_bottom" => include_bytes!("../../icons/material/dock_to_bottom.svg"),
338        "drag_indicator" => include_bytes!("../../icons/material/drag_indicator.svg"),
339        "emergency" => include_bytes!("../../icons/material/emergency.svg"),
340        "expand_less" => include_bytes!("../../icons/material/expand_less.svg"),
341        "expand_more" => include_bytes!("../../icons/material/expand_more.svg"),
342        "favorite" => include_bytes!("../../icons/material/favorite.svg"),
343        "find_replace" => include_bytes!("../../icons/material/find_replace.svg"),
344        "folder" => include_bytes!("../../icons/material/folder.svg"),
345        "folder_open" => include_bytes!("../../icons/material/folder_open.svg"),
346        "font_size" => include_bytes!("../../icons/material/font_size.svg"),
347        "heart_broken" => include_bytes!("../../icons/material/heart_broken.svg"),
348        "inbox" => include_bytes!("../../icons/material/inbox.svg"),
349        "info" => include_bytes!("../../icons/material/info.svg"),
350        "language" => include_bytes!("../../icons/material/language.svg"),
351        "left_panel_close" => include_bytes!("../../icons/material/left_panel_close.svg"),
352        "left_panel_open" => include_bytes!("../../icons/material/left_panel_open.svg"),
353        "light_mode" => include_bytes!("../../icons/material/light_mode.svg"),
354        "map" => include_bytes!("../../icons/material/map.svg"),
355        "match_case" => include_bytes!("../../icons/material/match_case.svg"),
356        "menu" => include_bytes!("../../icons/material/menu.svg"),
357        "menu_book" => include_bytes!("../../icons/material/menu_book.svg"),
358        "minimize" => include_bytes!("../../icons/material/minimize.svg"),
359        "more_horiz" => include_bytes!("../../icons/material/more_horiz.svg"),
360        "more_vert" => include_bytes!("../../icons/material/more_vert.svg"),
361        "notifications" => include_bytes!("../../icons/material/notifications.svg"),
362        "open_in_full" => include_bytes!("../../icons/material/open_in_full.svg"),
363        "open_in_new" => include_bytes!("../../icons/material/open_in_new.svg"),
364        "palette" => include_bytes!("../../icons/material/palette.svg"),
365        "person" => include_bytes!("../../icons/material/person.svg"),
366        "pie_chart" => include_bytes!("../../icons/material/pie_chart.svg"),
367        "progress_activity" => include_bytes!("../../icons/material/progress_activity.svg"),
368        "redo" => include_bytes!("../../icons/material/redo.svg"),
369        "remove" => include_bytes!("../../icons/material/remove.svg"),
370        "right_panel_close" => include_bytes!("../../icons/material/right_panel_close.svg"),
371        "right_panel_open" => include_bytes!("../../icons/material/right_panel_open.svg"),
372        "search" => include_bytes!("../../icons/material/search.svg"),
373        "settings" => include_bytes!("../../icons/material/settings.svg"),
374        "side_navigation" => include_bytes!("../../icons/material/side_navigation.svg"),
375        "smart_toy" => include_bytes!("../../icons/material/smart_toy.svg"),
376        "star" => include_bytes!("../../icons/material/star.svg"),
377        "star_border" => include_bytes!("../../icons/material/star_border.svg"),
378        "terminal" => include_bytes!("../../icons/material/terminal.svg"),
379        "thumb_down" => include_bytes!("../../icons/material/thumb_down.svg"),
380        "thumb_up" => include_bytes!("../../icons/material/thumb_up.svg"),
381        "tune" => include_bytes!("../../icons/material/tune.svg"),
382        "undo" => include_bytes!("../../icons/material/undo.svg"),
383        "unfold_more" => include_bytes!("../../icons/material/unfold_more.svg"),
384        "view_carousel" => include_bytes!("../../icons/material/view_carousel.svg"),
385        "visibility" => include_bytes!("../../icons/material/visibility.svg"),
386        "visibility_off" => include_bytes!("../../icons/material/visibility_off.svg"),
387        "warning" => include_bytes!("../../icons/material/warning.svg"),
388        "web_asset" => include_bytes!("../../icons/material/web_asset.svg"),
389        _ => return None,
390    })
391}
392
393#[cfg(test)]
394mod tests {
395    use super::*;
396
397    // === Feature-gated tests: Material ===
398
399    #[test]
400    #[cfg(feature = "material-icons")]
401    fn material_icons_cover_all_roles() {
402        for role in IconRole::ALL {
403            let svg = bundled_icon_svg(IconSet::Material, role);
404            assert!(svg.is_some(), "Material icons missing SVG for {:?}", role);
405            let bytes = svg.unwrap();
406            let content = std::str::from_utf8(bytes).expect("SVG should be valid UTF-8");
407            assert!(
408                content.contains("<svg"),
409                "Material {:?} does not contain <svg tag",
410                role
411            );
412        }
413    }
414
415    #[test]
416    #[cfg(feature = "material-icons")]
417    fn material_icons_total_size_under_200kb() {
418        let total: usize = IconRole::ALL
419            .iter()
420            .filter_map(|role| bundled_icon_svg(IconSet::Material, *role))
421            .map(|svg| svg.len())
422            .sum();
423        assert!(
424            total < 400 * 1024,
425            "Material icons total size {} bytes exceeds 400KB budget",
426            total
427        );
428    }
429
430    // === Feature-gated tests: Lucide ===
431
432    #[test]
433    #[cfg(feature = "lucide-icons")]
434    fn lucide_icons_cover_all_roles() {
435        for role in IconRole::ALL {
436            let svg = bundled_icon_svg(IconSet::Lucide, role);
437            assert!(svg.is_some(), "Lucide icons missing SVG for {:?}", role);
438            let bytes = svg.unwrap();
439            let content = std::str::from_utf8(bytes).expect("SVG should be valid UTF-8");
440            assert!(
441                content.contains("<svg"),
442                "Lucide {:?} does not contain <svg tag",
443                role
444            );
445        }
446    }
447
448    #[test]
449    #[cfg(feature = "lucide-icons")]
450    fn lucide_icons_total_size_under_100kb() {
451        let total: usize = IconRole::ALL
452            .iter()
453            .filter_map(|role| bundled_icon_svg(IconSet::Lucide, *role))
454            .map(|svg| svg.len())
455            .sum();
456        assert!(
457            total < 200 * 1024,
458            "Lucide icons total size {} bytes exceeds 200KB budget",
459            total
460        );
461    }
462
463    // === Non-feature-gated tests ===
464
465    #[test]
466    fn non_bundled_sets_return_none() {
467        assert!(
468            bundled_icon_svg(IconSet::SfSymbols, IconRole::ActionCopy).is_none(),
469            "SfSymbols should not be a bundled set"
470        );
471        assert!(
472            bundled_icon_svg(IconSet::Freedesktop, IconRole::ActionCopy).is_none(),
473            "Freedesktop should not be a bundled set"
474        );
475        assert!(
476            bundled_icon_svg(IconSet::SegoeIcons, IconRole::ActionCopy).is_none(),
477            "SegoeIcons should not be a bundled set"
478        );
479    }
480
481    // === bundled_icon_by_name tests ===
482
483    #[test]
484    #[cfg(feature = "lucide-icons")]
485    fn lucide_by_name_covers_gpui_icons() {
486        let names = [
487            "a-large-small",
488            "arrow-down",
489            "arrow-left",
490            "arrow-right",
491            "arrow-up",
492            "asterisk",
493            "bell",
494            "book-open",
495            "bot",
496            "building-2",
497            "calendar",
498            "case-sensitive",
499            "chart-pie",
500            "check",
501            "chevron-down",
502            "chevron-left",
503            "chevron-right",
504            "chevrons-up-down",
505            "chevron-up",
506            "circle-check",
507            "circle-user",
508            "circle-x",
509            "close",
510            "copy",
511            "dash",
512            "delete",
513            "ellipsis",
514            "ellipsis-vertical",
515            "external-link",
516            "eye",
517            "eye-off",
518            "file",
519            "folder",
520            "folder-closed",
521            "folder-open",
522            "frame",
523            "gallery-vertical-end",
524            "github",
525            "globe",
526            "heart",
527            "heart-off",
528            "inbox",
529            "info",
530            "inspect",
531            "layout-dashboard",
532            "loader",
533            "loader-circle",
534            "map",
535            "maximize",
536            "menu",
537            "minimize",
538            "minus",
539            "moon",
540            "palette",
541            "panel-bottom",
542            "panel-bottom-open",
543            "panel-left",
544            "panel-left-close",
545            "panel-left-open",
546            "panel-right",
547            "panel-right-close",
548            "panel-right-open",
549            "plus",
550            "redo",
551            "redo-2",
552            "replace",
553            "resize-corner",
554            "search",
555            "settings",
556            "settings-2",
557            "sort-ascending",
558            "sort-descending",
559            "square-terminal",
560            "star",
561            "star-off",
562            "sun",
563            "thumbs-down",
564            "thumbs-up",
565            "triangle-alert",
566            "undo",
567            "undo-2",
568            "user",
569            "window-close",
570            "window-maximize",
571            "window-minimize",
572            "window-restore",
573        ];
574        for name in names {
575            let svg = bundled_icon_by_name(IconSet::Lucide, name);
576            assert!(svg.is_some(), "Lucide by-name missing: {}", name);
577            let bytes = svg.unwrap();
578            let content = std::str::from_utf8(bytes).expect("SVG should be valid UTF-8");
579            assert!(
580                content.contains("<svg"),
581                "Lucide {} does not contain <svg tag",
582                name
583            );
584        }
585    }
586
587    #[test]
588    #[cfg(feature = "material-icons")]
589    fn material_by_name_covers_gpui_icons() {
590        let names = [
591            "font_size",
592            "arrow_downward",
593            "arrow_back",
594            "arrow_forward",
595            "arrow_upward",
596            "emergency",
597            "notifications",
598            "menu_book",
599            "smart_toy",
600            "apartment",
601            "calendar_today",
602            "match_case",
603            "pie_chart",
604            "check",
605            "expand_more",
606            "chevron_left",
607            "chevron_right",
608            "unfold_more",
609            "expand_less",
610            "check_circle",
611            "account_circle",
612            "cancel",
613            "close",
614            "content_copy",
615            "remove",
616            "delete",
617            "more_horiz",
618            "more_vert",
619            "open_in_new",
620            "visibility",
621            "visibility_off",
622            "description",
623            "folder",
624            "folder_open",
625            "crop_free",
626            "view_carousel",
627            "code",
628            "language",
629            "favorite",
630            "heart_broken",
631            "inbox",
632            "info",
633            "developer_mode",
634            "dashboard",
635            "progress_activity",
636            "autorenew",
637            "map",
638            "open_in_full",
639            "menu",
640            "minimize",
641            "dark_mode",
642            "palette",
643            "dock_to_bottom",
644            "web_asset",
645            "side_navigation",
646            "left_panel_close",
647            "left_panel_open",
648            "right_panel_close",
649            "right_panel_open",
650            "add",
651            "redo",
652            "find_replace",
653            "drag_indicator",
654            "search",
655            "settings",
656            "tune",
657            "terminal",
658            "star",
659            "star_border",
660            "light_mode",
661            "thumb_down",
662            "thumb_up",
663            "warning",
664            "undo",
665            "person",
666            "close_fullscreen",
667        ];
668        for name in names {
669            let svg = bundled_icon_by_name(IconSet::Material, name);
670            assert!(svg.is_some(), "Material by-name missing: {}", name);
671            let bytes = svg.unwrap();
672            let content = std::str::from_utf8(bytes).expect("SVG should be valid UTF-8");
673            assert!(
674                content.contains("<svg"),
675                "Material {} does not contain <svg tag",
676                name
677            );
678        }
679    }
680
681    #[test]
682    fn by_name_non_bundled_sets_return_none() {
683        assert!(bundled_icon_by_name(IconSet::SfSymbols, "check").is_none());
684        assert!(bundled_icon_by_name(IconSet::Freedesktop, "check").is_none());
685        assert!(bundled_icon_by_name(IconSet::SegoeIcons, "check").is_none());
686    }
687
688    #[test]
689    fn by_name_unknown_name_returns_none() {
690        assert!(bundled_icon_by_name(IconSet::Lucide, "nonexistent-icon-xyz").is_none());
691        assert!(bundled_icon_by_name(IconSet::Material, "nonexistent_icon_xyz").is_none());
692    }
693}