Extending Enums in Rust
The yew_icons library comes with a nice helper component <Icon ... />
that can be passed an enum IconId. If you want to mix in icons from other libraries or your own custom svg icons and want to keep the same interface you need a but of glue to achieve this. You can't simply extend the existing IconId
enum so you have to either fork the yew_icons library or wrap the values yourself. While this takes a lot of glue code, being able to add methods to enums and have exhaustive pattern matching provides us some guarantees that this won't break unexpectedly.
Extending Enums
To start with we create our own intermediate enum which will wrap other enum values:
use yew_icons::{IconId as YewIconId};
#[derive(Clone, Copy, PartialEq)]
pub enum CustomIconId {
Bento,
... // list of custom icon ids
}
#[derive(Clone, Copy, PartialEq)]
pub enum IconType {
Custom(CustomIconId),
Yew(YewIconId)
}
and then recreate the actual enum interface we want to work with:
#[derive(Clone, Copy, PartialEq)]
pub enum IconId {
Github,
Mastodon,
Twitter,
Bento,
...
}
and implement a consistent way to get the underlying value:
impl IconId {
pub fn value(&self) -> IconType {
match self {
IconId::Github => IconType::Yew(YewIconId::BootstrapGithub),
IconId::Mastodon => IconType::Yew(YewIconId::BootstrapMastodon),
IconId::Twitter => IconType::Yew(YewIconId::BootstrapTwitter),
IconId::Bento => IconType::Custom(CustomIconid::Bento),
...
}
}
}
Yew Component
All that's left to do is make our own Yew component wrapper, which will require a component for our custom icon type:
#[derive(Properties, PartialEq)]
pub struct CustomIconProps {
pub id: CustomIconId,
}
#[function_component(CustomIcon)]
pub fn custom_icon(props: &CustomIconProps) -> Html {
match props.id {
CustomIconId::Bento => html! { <Bento /> },
...
}
}
Where the <Bento />
component is a custom SVG function component. If you have a bunch of custom SVGs you can generate these components at compile time, which is what I believe yew_icons does, or you can hand-roll them as you need them. The last component we need to create is the one we will actually use in our app code:
#[derive(Properties, PartialEq)]
pub struct IconProps {
pub id: IconId,
}
#[function_component(Icon)]
pub fn icon(props: &IconProps) -> Html {
match props.id.value() {
IconType::Custom(id) => html! { <CustomIcon id={id} /> },
IconType::Yew(id) => html! {<YewIcon icon_id={id} />},
}
}
Now you can use your own <Icon id={...} />
component in the same way you would using yew_icons and enums without leaking the abstraction of using multiple icon libraries.