avatar: Add Avatar element. (#1028)
<img width="1384" alt="image" src="https://github.com/user-attachments/assets/1e66aa8c-0c87-4d8d-abb7-38e5f616fe3e" /> <img width="1384" alt="image" src="https://github.com/user-attachments/assets/bd32b6e0-aa0f-4d01-af9f-4bfaea1a3718" />
This commit is contained in:
parent
a28ad0b1f1
commit
66a9b3e9df
10 changed files with 513 additions and 0 deletions
16
assets/icons/building-2.svg
Normal file
16
assets/icons/building-2.svg
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-building2-icon lucide-building-2"
|
||||
><path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z" /><path
|
||||
d="M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2"
|
||||
/><path d="M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2" /><path
|
||||
d="M10 6h4"
|
||||
/><path d="M10 10h4" /><path d="M10 14h4" /><path d="M10 18h4" /></svg>
|
||||
|
After Width: | Height: | Size: 550 B |
16
assets/icons/user.svg
Normal file
16
assets/icons/user.svg
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-user-icon lucide-user"
|
||||
><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle
|
||||
cx="12"
|
||||
cy="7"
|
||||
r="4"
|
||||
/></svg>
|
||||
|
After Width: | Height: | Size: 386 B |
154
crates/story/src/avatar_story.rs
Normal file
154
crates/story/src/avatar_story.rs
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
use gpui::{
|
||||
px, App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement,
|
||||
Render, Styled, Window,
|
||||
};
|
||||
use gpui_component::{
|
||||
avatar::{Avatar, AvatarGroup},
|
||||
dock::PanelControl,
|
||||
v_flex, ActiveTheme, IconName, Sizable as _, StyledExt,
|
||||
};
|
||||
|
||||
use crate::section;
|
||||
|
||||
pub struct AvatarStory {
|
||||
focus_handle: gpui::FocusHandle,
|
||||
}
|
||||
|
||||
impl AvatarStory {
|
||||
fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||
cx.new(|cx| Self::new(window, cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Story for AvatarStory {
|
||||
fn title() -> &'static str {
|
||||
"Avatar"
|
||||
}
|
||||
|
||||
fn description() -> &'static str {
|
||||
"Avatar is an image that represents a user or organization."
|
||||
}
|
||||
|
||||
fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render + Focusable> {
|
||||
Self::view(window, cx)
|
||||
}
|
||||
|
||||
fn zoomable() -> Option<PanelControl> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for AvatarStory {
|
||||
fn focus_handle(&self, _: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AvatarStory {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.child(
|
||||
section("Avatar with image")
|
||||
.max_w_md()
|
||||
.child(
|
||||
Avatar::new()
|
||||
.name("Jason lee")
|
||||
.src("https://i.pravatar.cc/200?u=a")
|
||||
.with_size(px(100.)),
|
||||
)
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=b").large())
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=c"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=d").small())
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=e").xsmall()),
|
||||
)
|
||||
.child(
|
||||
section("Avatar with text")
|
||||
.max_w_md()
|
||||
.child(Avatar::new().name("Jason Lee").large())
|
||||
.child(Avatar::new().name("Floyd Wang"))
|
||||
.child(Avatar::new().name("xda").small())
|
||||
.child(Avatar::new().name("ihavecoke").xsmall()),
|
||||
)
|
||||
.child(
|
||||
section("Placeholder")
|
||||
.max_w_md()
|
||||
.child(Avatar::new().large())
|
||||
.child(Avatar::new())
|
||||
.child(Avatar::new().small())
|
||||
.child(Avatar::new().xsmall())
|
||||
.child(Avatar::new().placeholder(IconName::Building2)),
|
||||
)
|
||||
.child(
|
||||
section("Avatar Group")
|
||||
.v_flex()
|
||||
.max_w_md()
|
||||
.child(
|
||||
AvatarGroup::new()
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=a"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=b"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=c"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=d"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=e"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=f")),
|
||||
)
|
||||
.child(
|
||||
AvatarGroup::new()
|
||||
.small()
|
||||
.limit(5)
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=a"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=b"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=c"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=d"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=e"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=f"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=g"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=h"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=i"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=j"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=k")),
|
||||
)
|
||||
.child(
|
||||
AvatarGroup::new()
|
||||
.xsmall()
|
||||
.limit(6)
|
||||
.ellipsis()
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=a"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=b"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=c"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=d"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=e"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=f"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=g"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=h"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=i"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=j"))
|
||||
.child(Avatar::new().src("https://i.pravatar.cc/200?u=k")),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
section("Custom rounded").child(
|
||||
Avatar::new()
|
||||
.src("https://i.pravatar.cc/200?u=a")
|
||||
.with_size(px(100.))
|
||||
.rounded(px(20.)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
section("Custom Style").child(
|
||||
Avatar::new()
|
||||
.src("https://i.pravatar.cc/200?u=c")
|
||||
.with_size(px(100.))
|
||||
.border_3()
|
||||
.border_color(cx.theme().foreground)
|
||||
.shadow_sm(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
mod accordion_story;
|
||||
mod alert_story;
|
||||
mod assets;
|
||||
mod avatar_story;
|
||||
mod badge_story;
|
||||
mod button_story;
|
||||
mod calendar_story;
|
||||
|
|
@ -52,6 +53,7 @@ use gpui::{
|
|||
|
||||
pub use accordion_story::AccordionStory;
|
||||
pub use alert_story::AlertStory;
|
||||
pub use avatar_story::AvatarStory;
|
||||
pub use badge_story::BadgeStory;
|
||||
pub use button_story::ButtonStory;
|
||||
pub use calendar_story::CalendarStory;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ impl Gallery {
|
|||
vec![
|
||||
StoryContainer::panel::<AccordionStory>(window, cx),
|
||||
StoryContainer::panel::<AlertStory>(window, cx),
|
||||
StoryContainer::panel::<AvatarStory>(window, cx),
|
||||
StoryContainer::panel::<BadgeStory>(window, cx),
|
||||
StoryContainer::panel::<ButtonStory>(window, cx),
|
||||
StoryContainer::panel::<CalendarStory>(window, cx),
|
||||
|
|
|
|||
172
crates/ui/src/avatar/avatar.rs
Normal file
172
crates/ui/src/avatar/avatar.rs
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
use std::sync::LazyLock;
|
||||
|
||||
use gpui::{
|
||||
div, img, prelude::FluentBuilder, App, Div, Hsla, ImageSource, InteractiveElement,
|
||||
Interactivity, IntoElement, ParentElement as _, RenderOnce, SharedString, StyleRefinement,
|
||||
Styled, Window,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
avatar::{avatar_size, AvatarSized as _},
|
||||
ActiveTheme, Icon, IconName, Sizable, Size, StyledExt,
|
||||
};
|
||||
|
||||
static AVATAR_COLORS: LazyLock<[Hsla; 17]> = LazyLock::new(|| {
|
||||
[
|
||||
crate::red_500(),
|
||||
crate::orange_500(),
|
||||
crate::amber_500(),
|
||||
crate::yellow_500(),
|
||||
crate::lime_500(),
|
||||
crate::green_500(),
|
||||
crate::emerald_500(),
|
||||
crate::teal_500(),
|
||||
crate::cyan_500(),
|
||||
crate::sky_500(),
|
||||
crate::blue_500(),
|
||||
crate::indigo_500(),
|
||||
crate::violet_500(),
|
||||
crate::fuchsia_500(),
|
||||
crate::purple_500(),
|
||||
crate::pink_500(),
|
||||
crate::rose_500(),
|
||||
]
|
||||
});
|
||||
|
||||
/// User avatar element.
|
||||
///
|
||||
/// We can use [`Sizable`] trait to set the size of the avatar (see also: [`avatar_size`] about the size in pixels).
|
||||
#[derive(IntoElement)]
|
||||
pub struct Avatar {
|
||||
base: Div,
|
||||
style: StyleRefinement,
|
||||
src: Option<ImageSource>,
|
||||
name: Option<SharedString>,
|
||||
short_name: SharedString,
|
||||
placeholder: Icon,
|
||||
size: Size,
|
||||
}
|
||||
|
||||
impl Avatar {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
base: div(),
|
||||
style: StyleRefinement::default(),
|
||||
src: None,
|
||||
name: None,
|
||||
short_name: SharedString::default(),
|
||||
placeholder: Icon::new(IconName::User),
|
||||
size: Size::Medium,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set to use image source for the avatar.
|
||||
pub fn src(mut self, source: impl Into<ImageSource>) -> Self {
|
||||
self.src = Some(source.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set name of the avatar user, if `src` is none, will use this name as placeholder.
|
||||
pub fn name(mut self, name: impl Into<SharedString>) -> Self {
|
||||
let name: SharedString = name.into();
|
||||
let short: SharedString = extract_text_initials(&name).into();
|
||||
|
||||
self.name = Some(name);
|
||||
self.short_name = short;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set placeholder icon, default: [`IconName::User`]
|
||||
pub fn placeholder(mut self, icon: impl Into<Icon>) -> Self {
|
||||
self.placeholder = icon.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
impl Sizable for Avatar {
|
||||
fn with_size(mut self, size: impl Into<Size>) -> Self {
|
||||
self.size = size.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
impl Styled for Avatar {
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
&mut self.style
|
||||
}
|
||||
}
|
||||
|
||||
impl InteractiveElement for Avatar {
|
||||
fn interactivity(&mut self) -> &mut Interactivity {
|
||||
self.base.interactivity()
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Avatar {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let corner_radii = self.style.corner_radii.clone();
|
||||
let mut inner_style = StyleRefinement::default();
|
||||
inner_style.corner_radii = corner_radii;
|
||||
|
||||
const BG_OPACITY: f32 = 0.2;
|
||||
|
||||
self.base
|
||||
.avatar_size(self.size)
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.flex_shrink_0()
|
||||
.rounded_full()
|
||||
.overflow_hidden()
|
||||
.bg(cx.theme().secondary)
|
||||
.text_color(cx.theme().background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().background)
|
||||
.when(self.name.is_none() && self.src.is_none(), |this| {
|
||||
this.text_size(avatar_size(self.size) * 0.6)
|
||||
.child(self.placeholder)
|
||||
})
|
||||
.map(|this| match self.src {
|
||||
None => this.when(self.name.is_some(), |this| {
|
||||
let color_ix = gpui::hash(&self.short_name) % AVATAR_COLORS.len() as u64;
|
||||
let color = AVATAR_COLORS[color_ix as usize];
|
||||
|
||||
this.bg(color.opacity(BG_OPACITY))
|
||||
.text_color(color)
|
||||
.child(div().avatar_text_size(self.size).child(self.short_name))
|
||||
}),
|
||||
Some(src) => this.child(
|
||||
img(src)
|
||||
.avatar_size(self.size)
|
||||
.rounded_full()
|
||||
.refine_style(&inner_style),
|
||||
),
|
||||
})
|
||||
.refine_style(&self.style)
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_text_initials(text: &str) -> String {
|
||||
let mut result = text
|
||||
.split(" ")
|
||||
.flat_map(|word| word.chars().next().map(|c| c.to_string()))
|
||||
.take(2)
|
||||
.collect::<Vec<String>>()
|
||||
.join("");
|
||||
|
||||
if result.len() == 1 {
|
||||
result = text.chars().take(2).collect::<String>();
|
||||
}
|
||||
|
||||
result.to_uppercase()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_avatar_text_initials() {
|
||||
assert_eq!(extract_text_initials(&"Jason Lee"), "JL".to_string());
|
||||
assert_eq!(extract_text_initials(&"Foo Bar Dar"), "FB".to_string());
|
||||
assert_eq!(extract_text_initials(&"huacnlee"), "HU".to_string());
|
||||
}
|
||||
}
|
||||
108
crates/ui/src/avatar/avatar_group.rs
Normal file
108
crates/ui/src/avatar/avatar_group.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, Div, InteractiveElement, Interactivity, IntoElement,
|
||||
ParentElement as _, RenderOnce, StyleRefinement, Styled,
|
||||
};
|
||||
|
||||
use crate::{avatar::Avatar, ActiveTheme, Sizable, Size, StyledExt as _};
|
||||
|
||||
/// A grouped avatars to display in a compact layout.
|
||||
#[derive(IntoElement)]
|
||||
pub struct AvatarGroup {
|
||||
base: Div,
|
||||
style: StyleRefinement,
|
||||
avatars: Vec<Avatar>,
|
||||
size: Size,
|
||||
limit: usize,
|
||||
ellipsis: bool,
|
||||
}
|
||||
|
||||
impl AvatarGroup {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
base: div(),
|
||||
style: StyleRefinement::default(),
|
||||
avatars: Vec::new(),
|
||||
size: Size::default(),
|
||||
limit: 3,
|
||||
ellipsis: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a child avatar to the group.
|
||||
pub fn child(mut self, avatar: Avatar) -> Self {
|
||||
self.avatars.push(avatar);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add multiple child avatars to the group.
|
||||
pub fn children(mut self, avatars: impl IntoIterator<Item = Avatar>) -> Self {
|
||||
self.avatars.extend(avatars);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the maximum number of avatars to display before showing a "more" avatar.
|
||||
pub fn limit(mut self, limit: usize) -> Self {
|
||||
self.limit = limit;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether to show an ellipsis when the limit is reached, default: false
|
||||
pub fn ellipsis(mut self) -> Self {
|
||||
self.ellipsis = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Sizable for AvatarGroup {
|
||||
fn with_size(mut self, size: impl Into<Size>) -> Self {
|
||||
self.size = size.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled for AvatarGroup {
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
&mut self.style
|
||||
}
|
||||
}
|
||||
|
||||
impl InteractiveElement for AvatarGroup {
|
||||
fn interactivity(&mut self) -> &mut Interactivity {
|
||||
self.base.interactivity()
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for AvatarGroup {
|
||||
fn render(self, _: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {
|
||||
let item_ml = -super::avatar_size(self.size) * 0.3;
|
||||
let avatars_len = self.avatars.len();
|
||||
|
||||
self.base
|
||||
.h_flex()
|
||||
.flex_row_reverse()
|
||||
.refine_style(&self.style)
|
||||
.children(if self.ellipsis && avatars_len > self.limit {
|
||||
Some(
|
||||
Avatar::new()
|
||||
.name("⋯")
|
||||
.bg(cx.theme().secondary)
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.with_size(self.size)
|
||||
.ml_1(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.children(
|
||||
self.avatars
|
||||
.into_iter()
|
||||
.take(self.limit)
|
||||
.enumerate()
|
||||
.rev()
|
||||
.map(|(ix, item)| {
|
||||
item.with_size(self.size)
|
||||
.when(ix > 0, |this| this.ml(item_ml))
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
39
crates/ui/src/avatar/mod.rs
Normal file
39
crates/ui/src/avatar/mod.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
mod avatar;
|
||||
mod avatar_group;
|
||||
|
||||
pub use avatar::*;
|
||||
pub use avatar_group::*;
|
||||
|
||||
use crate::{Icon, Size, StyledExt as _};
|
||||
use gpui::{px, rems, Div, Img, IntoElement, Pixels, Styled};
|
||||
|
||||
/// Returns the size of the avatar based on the given [`Size`].
|
||||
pub(super) fn avatar_size(size: Size) -> Pixels {
|
||||
match size {
|
||||
Size::Large => px(80.),
|
||||
Size::Medium => px(48.),
|
||||
Size::Small => px(24.),
|
||||
Size::XSmall => px(20.),
|
||||
Size::Size(size) => size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension for add `avatar_size` method to `IntoElement` to apply avatar size to element.
|
||||
pub(super) trait AvatarSized: IntoElement + Styled {
|
||||
fn avatar_size(self, size: Size) -> Self {
|
||||
self.size(avatar_size(size))
|
||||
}
|
||||
|
||||
fn avatar_text_size(self, size: Size) -> Self {
|
||||
match size {
|
||||
Size::Large => self.text_3xl().font_semibold(),
|
||||
Size::Medium => self.text_sm(),
|
||||
Size::Small => self.text_xs(),
|
||||
Size::XSmall => self.text_size(rems(0.65)),
|
||||
Size::Size(size) => self.size(size * 0.5),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl AvatarSized for Div {}
|
||||
impl AvatarSized for Icon {}
|
||||
impl AvatarSized for Img {}
|
||||
|
|
@ -16,6 +16,7 @@ pub enum IconName {
|
|||
Bell,
|
||||
BookOpen,
|
||||
Bot,
|
||||
Building2,
|
||||
Calendar,
|
||||
ChartPie,
|
||||
Check,
|
||||
|
|
@ -77,6 +78,7 @@ pub enum IconName {
|
|||
ThumbsDown,
|
||||
ThumbsUp,
|
||||
TriangleAlert,
|
||||
User,
|
||||
WindowClose,
|
||||
WindowMaximize,
|
||||
WindowMinimize,
|
||||
|
|
@ -95,6 +97,7 @@ impl IconName {
|
|||
Self::Bell => "icons/bell.svg",
|
||||
Self::BookOpen => "icons/book-open.svg",
|
||||
Self::Bot => "icons/bot.svg",
|
||||
Self::Building2 => "icons/building-2.svg",
|
||||
Self::Calendar => "icons/calendar.svg",
|
||||
Self::ChartPie => "icons/chart-pie.svg",
|
||||
Self::Check => "icons/check.svg",
|
||||
|
|
@ -156,6 +159,7 @@ impl IconName {
|
|||
Self::ThumbsDown => "icons/thumbs-down.svg",
|
||||
Self::ThumbsUp => "icons/thumbs-up.svg",
|
||||
Self::TriangleAlert => "icons/triangle-alert.svg",
|
||||
Self::User => "icons/user.svg",
|
||||
Self::WindowClose => "icons/window-close.svg",
|
||||
Self::WindowMaximize => "icons/window-maximize.svg",
|
||||
Self::WindowMinimize => "icons/window-minimize.svg",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ pub(crate) mod actions;
|
|||
pub mod accordion;
|
||||
pub mod alert;
|
||||
pub mod animation;
|
||||
pub mod avatar;
|
||||
pub mod badge;
|
||||
pub mod breadcrumb;
|
||||
pub mod button;
|
||||
|
|
|
|||
Loading…
Reference in a new issue