From d00e970a6a05fd755ce440ca246358be986f4c3f Mon Sep 17 00:00:00 2001
From: luoxiao <luoxiaozero@163.com>
Date: Fri, 10 May 2024 21:51:35 +0800
Subject: [PATCH] refactor: avatar

---
 demo_markdown/docs/avatar/mod.md |  49 ++++++++++++-
 thaw/src/avatar/avatar.css       |  54 ++++++++++++--
 thaw/src/avatar/mod.rs           | 118 +++++++++++++++++++++++--------
 thaw/src/avatar/theme.rs         |  20 ------
 thaw/src/theme/color.rs          |   8 +++
 thaw/src/theme/common.rs         |  16 ++++-
 thaw/src/theme/mod.rs            |   5 +-
 7 files changed, 208 insertions(+), 62 deletions(-)
 delete mode 100644 thaw/src/avatar/theme.rs

diff --git a/demo_markdown/docs/avatar/mod.md b/demo_markdown/docs/avatar/mod.md
index 117633c..4ad2e7e 100644
--- a/demo_markdown/docs/avatar/mod.md
+++ b/demo_markdown/docs/avatar/mod.md
@@ -1,11 +1,54 @@
 # Avatar
 
+```rust demo
+view! {
+    <Avatar />
+}
+```
+
+### Name
+
+```rust demo
+view! {
+    <Avatar name="Ashley McCarthy" />
+}
+```
+
+### Image
+
+```rust demo
+view! {
+    <Avatar src="https://s3.bmp.ovh/imgs/2021/10/723d457d627fe706.jpg" />
+}
+```
+
+### Shape
+
+```rust demo
+view! {
+    <Avatar shape=AvatarShape::Square />
+}
+```
+
+### Size
+
 ```rust demo
 view! {
     <Space>
-        <Avatar src="https://s3.bmp.ovh/imgs/2021/10/723d457d627fe706.jpg"/>
-        <Avatar src="https://s3.bmp.ovh/imgs/2021/10/723d457d627fe706.jpg" round=true/>
-        <Avatar src="https://s3.bmp.ovh/imgs/2021/10/723d457d627fe706.jpg" size=50/>
+        <Avatar initials="16" size=16 />
+        <Avatar initials="20" size=20 />
+        <Avatar initials="24" size=24 />
+        <Avatar initials="28" size=28 />
+        <Avatar initials="32" size=32 />
+        <Avatar initials="36" size=36 />
+        <Avatar initials="40" size=40 />
+        <Avatar initials="48" size=48 />
+        <Avatar initials="56" size=56 />
+        <Avatar initials="64" size=64 />
+        <Avatar initials="72" size=72 />
+        <Avatar initials="96" size=96 />
+        <Avatar initials="120" size=120 />
+        <Avatar initials="128" size=128 />
     </Space>
 }
 ```
diff --git a/thaw/src/avatar/avatar.css b/thaw/src/avatar/avatar.css
index 68f131d..d10fb3d 100644
--- a/thaw/src/avatar/avatar.css
+++ b/thaw/src/avatar/avatar.css
@@ -1,13 +1,55 @@
 .thaw-avatar {
     display: inline-block;
-    width: var(--thaw-size);
-    height: var(--thaw-size);
-    background-color: var(--thaw-background-color);
-    border-radius: var(--thaw-border-radius);
+    flex-shrink: 0;
+    position: relative;
+    vertical-align: middle;
+    border-radius: var(--borderRadiusCircular);
+    font-family: var(--fontFamilyBase);
+    font-weight: var(--fontWeightSemibold);
+    font-size: var(--fontSizeBase300);
+    width: 32px;
+    height: 32px;
 }
 
-.thaw-avatar img {
+.thaw-avatar--square {
+    border-radius: var(--borderRadiusMedium);
+}
+
+.thaw-avatar__icon,
+.thaw-avatar__initials {
+    position: absolute;
+    box-sizing: border-box;
+    top: 0px;
+    left: 0px;
     width: 100%;
     height: 100%;
-    border-radius: var(--thaw-border-radius);
+    line-height: 1;
+    border: var(--strokeWidthThin) solid var(--colorTransparentStroke);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    text-align: center;
+    user-select: none;
+    border-radius: inherit;
+
+    background-color: var(--colorNeutralBackground6);
+    color: var(--colorNeutralForeground3);
+}
+
+.thaw-avatar__icon {
+    font-size: 20px;
+}
+
+.thaw-avatar__image {
+    position: absolute;
+    top: 0px;
+    left: 0px;
+    width: 100%;
+    height: 100%;
+    border-radius: inherit;
+    object-fit: cover;
+    vertical-align: top;
+
+    background-color: var(--colorNeutralBackground6);
+    color: var(--colorNeutralForeground3);
 }
diff --git a/thaw/src/avatar/mod.rs b/thaw/src/avatar/mod.rs
index 1ea5270..ccb33ca 100644
--- a/thaw/src/avatar/mod.rs
+++ b/thaw/src/avatar/mod.rs
@@ -1,45 +1,107 @@
-mod theme;
-
-pub use theme::AvatarTheme;
-
-use crate::{use_theme, Theme};
 use leptos::*;
 use thaw_components::OptionComp;
-use thaw_utils::{class_list, mount_style, OptionalProp};
+use thaw_utils::{class_list, mount_style, OptionalProp, StoredMaybeSignal};
 
 #[component]
 pub fn Avatar(
-    #[prop(optional, into)] src: Option<MaybeSignal<String>>,
-    #[prop(optional, into)] round: MaybeSignal<bool>,
-    #[prop(default = MaybeSignal::Static(30), into)] size: MaybeSignal<u16>,
+    /// The Avatar's image.
+    #[prop(optional, into)]
+    src: Option<MaybeSignal<String>>,
+    /// The name of the person or entity represented by this Avatar.
+    #[prop(optional, into)]
+    name: Option<MaybeSignal<String>>,
+    /// Custom initials.
+    #[prop(optional, into)]
+    initials: Option<MaybeSignal<String>>,
+    /// The avatar can have a circular or square shape.
+    #[prop(optional, into)]
+    shape: MaybeSignal<AvatarShape>,
+    /// Size of the avatar in pixels.
+    #[prop(optional, into)] size: Option<MaybeSignal<u8>>,
     #[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
 ) -> impl IntoView {
-    let theme = use_theme(Theme::light);
-    let css_vars = create_memo(move |_| {
-        let mut css_vars = String::new();
-        css_vars.push_str(&format!("--thaw-size: {}px;", size.get()));
-        css_vars.push_str(&format!(
-            "--thaw-border-radius: {};",
-            if round.get() { "50%" } else { "3px" }
-        ));
-        theme.with(|theme| {
-            css_vars.push_str(&format!(
-                "--thaw-background-color: {}",
-                theme.avatar.background_color
-            ));
-        });
-        css_vars
-    });
     mount_style("avatar", include_str!("./avatar.css"));
 
+    let style = move || {
+        let size = size?.get();
+
+        let mut style = format!("width: {0}px; height: {0}px;", size);
+
+        if let Some(font_size) = match size {
+            0..=24 => Some(100),
+            25..=28 => Some(200),
+            29..=40 => None,
+            41..=56 => Some(400),
+            57..=96 => Some(500),
+            97..=128 => Some(600),
+            _ => Some(600),
+        } {
+            style.push_str(&format!("font-size: var(--fontSizeBase{});", font_size))
+        }
+
+        Some(style)
+    };
+
+    let is_show_default_icon = src.is_none() && initials.is_none() && name.is_none();
+    let name: Option<StoredMaybeSignal<_>> = name.map(|n| n.into());
+
     view! {
         <span
-            class=class_list!["thaw-avatar", class.map(| c | move || c.get())]
-            style=move || css_vars.get()
+            class=class_list!["thaw-avatar", move || format!("thaw-avatar--{}", shape.get().as_str()), class.map(| c | move || c.get())]
+            style=style
+            role="img"
+            aria-label=move || name.as_ref().map(|n| n.get())
         >
+            {
+                move || {
+                    if let Some(initials) = initials.as_ref().map_or_else(|| name.as_ref().map(|n| initials_name(n.get())), |i| Some(i.get())) {
+                        view! {
+                            <span class="thaw-avatar__initials">
+                                {initials}
+                            </span>
+                        }.into()
+                    } else {
+                        None
+                    }
+                }
+            }
             <OptionComp value=src let:src>
-                <img src=move || src.get()/>
+                <img src=move || src.get() class="thaw-avatar__image"/>
             </OptionComp>
+            {
+                if is_show_default_icon {
+                    view! {
+                        <span aria-hidden="true" class="thaw-avatar__icon">
+                            <svg fill="currentColor" aria-hidden="true" width="1em" height="1em" viewBox="0 0 20 20">
+                                <path d="M10 2a4 4 0 1 0 0 8 4 4 0 0 0 0-8ZM7 6a3 3 0 1 1 6 0 3 3 0 0 1-6 0Zm-2 5a2 2 0 0 0-2 2c0 1.7.83 2.97 2.13 3.8A9.14 9.14 0 0 0 10 18c1.85 0 3.58-.39 4.87-1.2A4.35 4.35 0 0 0 17 13a2 2 0 0 0-2-2H5Zm-1 2a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1c0 1.3-.62 2.28-1.67 2.95A8.16 8.16 0 0 1 10 17a8.16 8.16 0 0 1-4.33-1.05A3.36 3.36 0 0 1 4 13Z" fill="currentColor"></path>
+                            </svg>
+                        </span>
+                    }.into()
+                } else {
+                    None
+                }
+            }
         </span>
     }
 }
+
+// TODO
+fn initials_name(name: String) -> String {
+    name.split_at(2).0.to_string().to_ascii_uppercase()
+}
+
+#[derive(Default, Clone)]
+pub enum AvatarShape {
+    #[default]
+    Circular,
+    Square,
+}
+
+impl AvatarShape {
+    pub fn as_str(&self) -> &'static str {
+        match self {
+            Self::Circular => "circular",
+            Self::Square => "square",
+        }
+    }
+}
diff --git a/thaw/src/avatar/theme.rs b/thaw/src/avatar/theme.rs
deleted file mode 100644
index a335f79..0000000
--- a/thaw/src/avatar/theme.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-use crate::theme::ThemeMethod;
-
-#[derive(Clone)]
-pub struct AvatarTheme {
-    pub background_color: String,
-}
-
-impl ThemeMethod for AvatarTheme {
-    fn light() -> Self {
-        Self {
-            background_color: "#f7f7f7".into(),
-        }
-    }
-
-    fn dark() -> Self {
-        Self {
-            background_color: "#424245".into(),
-        }
-    }
-}
diff --git a/thaw/src/theme/color.rs b/thaw/src/theme/color.rs
index c7ba47c..fa259d9 100644
--- a/thaw/src/theme/color.rs
+++ b/thaw/src/theme/color.rs
@@ -6,6 +6,7 @@ pub struct ColorTheme {
     pub color_neutral_background_1: String,
     pub color_neutral_background_1_hover: String,
     pub color_neutral_background_1_pressed: String,
+    pub color_neutral_background_6: String,
     
     pub color_neutral_foreground_disabled: String,
     pub color_neutral_foreground_1: String,
@@ -16,6 +17,7 @@ pub struct ColorTheme {
     pub color_neutral_foreground_2_pressed: String,
     pub color_neutral_foreground_2_brand_hover: String,
     pub color_neutral_foreground_2_brand_pressed: String,
+    pub color_neutral_foreground_3: String,
     pub color_neutral_foreground_4: String,
     pub color_neutral_foreground_on_brand: String,
 
@@ -47,6 +49,8 @@ impl ColorTheme {
             color_neutral_background_1: "#fff".into(),
             color_neutral_background_1_hover: "#f5f5f5".into(),
             color_neutral_background_1_pressed: "#e0e0e0".into(),
+            color_neutral_background_6: "#e6e6e6".into(),
+
             color_neutral_foreground_disabled: "#bdbdbd".into(),
             color_neutral_foreground_1: "#242424".into(),
             color_neutral_foreground_1_hover: "#242424".into(),
@@ -56,6 +60,7 @@ impl ColorTheme {
             color_neutral_foreground_2_pressed: "#242424".into(),
             color_neutral_foreground_2_brand_hover: "#0f6cbd".into(),
             color_neutral_foreground_2_brand_pressed: "#115ea3".into(),
+            color_neutral_foreground_3: "#616161".into(),
             color_neutral_foreground_4: "#707070".into(),
             color_neutral_foreground_on_brand: "#fff".into(),
 
@@ -87,6 +92,8 @@ impl ColorTheme {
             color_neutral_background_1: "#292929".into(),
             color_neutral_background_1_hover: "#3d3d3d".into(),
             color_neutral_background_1_pressed: "#1f1f1f".into(),
+            color_neutral_background_6: "#333333".into(),
+
             color_neutral_foreground_disabled: "#5c5c5c".into(),
             color_neutral_foreground_1: "#fff".into(),
             color_neutral_foreground_1_hover: "#fff".into(),
@@ -96,6 +103,7 @@ impl ColorTheme {
             color_neutral_foreground_2_pressed: "#fff".into(),
             color_neutral_foreground_2_brand_hover: "#479ef5".into(),
             color_neutral_foreground_2_brand_pressed: "#2886de".into(),
+            color_neutral_foreground_3: "#adadad".into(),
             color_neutral_foreground_4: "#999999".into(),
             color_neutral_foreground_on_brand: "#fff".into(),
 
diff --git a/thaw/src/theme/common.rs b/thaw/src/theme/common.rs
index 828ead4..a430588 100644
--- a/thaw/src/theme/common.rs
+++ b/thaw/src/theme/common.rs
@@ -20,9 +20,16 @@ pub struct CommonTheme {
     pub color_error_hover: String,
     pub color_error_active: String,
 
+    pub font_size_base_100: String,
     pub font_size_base_200: String,
     pub font_size_base_300: String,
     pub font_size_base_400: String,
+    pub font_size_base_500: String,
+    pub font_size_base_600: String,
+    pub font_size_base_700: String,
+    pub font_size_base_800: String,
+    pub font_size_base_900: String,
+    pub font_size_base_1000: String,
 
     pub line_height_base_200: String,
     pub line_height_base_300: String,
@@ -37,7 +44,7 @@ pub struct CommonTheme {
     pub border_radius_none: String,
     pub border_radius_medium: String,
     pub border_radius_circular: String,
-    
+
     pub spacing_horizontal_x_x_s: String,
     pub spacing_horizontal_s_nudge: String,
     pub spacing_horizontal_s: String,
@@ -81,9 +88,16 @@ impl CommonTheme {
             color_error_hover: "".into(),
             color_error_active: "".into(),
 
+            font_size_base_100: "10px".into(),
             font_size_base_200: "12px".into(),
             font_size_base_300: "14px".into(),
             font_size_base_400: "16px".into(),
+            font_size_base_500: "20px".into(),
+            font_size_base_600: "24px".into(),
+            font_size_base_700: "28px".into(),
+            font_size_base_800: "32px".into(),
+            font_size_base_900: "40px".into(),
+            font_size_base_1000: "60px".into(),
 
             line_height_base_200: "16px".into(),
             line_height_base_300: "20px".into(),
diff --git a/thaw/src/theme/mod.rs b/thaw/src/theme/mod.rs
index e270590..ff9943f 100644
--- a/thaw/src/theme/mod.rs
+++ b/thaw/src/theme/mod.rs
@@ -4,7 +4,7 @@ mod common;
 use self::common::CommonTheme;
 use crate::{
     mobile::{NavBarTheme, TabbarTheme},
-    AlertTheme, AnchorTheme, AutoCompleteTheme, AvatarTheme, BackTopTheme, BreadcrumbTheme,
+    AlertTheme, AnchorTheme, AutoCompleteTheme, BackTopTheme, BreadcrumbTheme,
     CalendarTheme, CollapseTheme, ColorPickerTheme, DatePickerTheme, InputTheme, MenuTheme,
     MessageTheme, PopoverTheme, ProgressTheme, ScrollbarTheme, SelectTheme, SkeletionTheme,
     SliderTheme, SpinnerTheme, SwitchTheme, TableTheme, TagTheme, TimePickerTheme, TypographyTheme,
@@ -29,7 +29,6 @@ pub struct Theme {
     pub alert: AlertTheme,
     pub skeletion: SkeletionTheme,
     pub tag: TagTheme,
-    pub avatar: AvatarTheme,
     pub message: MessageTheme,
     pub select: SelectTheme,
     pub slider: SliderTheme,
@@ -65,7 +64,6 @@ impl Theme {
             alert: AlertTheme::light(),
             skeletion: SkeletionTheme::light(),
             tag: TagTheme::light(),
-            avatar: AvatarTheme::light(),
             message: MessageTheme::light(),
             select: SelectTheme::light(),
             slider: SliderTheme::light(),
@@ -100,7 +98,6 @@ impl Theme {
             alert: AlertTheme::dark(),
             skeletion: SkeletionTheme::dark(),
             tag: TagTheme::dark(),
-            avatar: AvatarTheme::dark(),
             message: MessageTheme::dark(),
             select: SelectTheme::dark(),
             slider: SliderTheme::dark(),