Skip to main content

ui_storybook/components/primitives/
icon_tile.rs

1//! `IconTile` — small square tile holding an icon or monogram
2//! (M-UI.1 / AUT-121).
3//!
4//! Five kinds for the recurring tile usages: workspace monogram, device
5//! avatar (mic / camera), app icon, action chip, user avatar.
6
7use leptos::prelude::*;
8
9/// Semantic icon-tile kind.
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
11pub enum IconTileKind {
12    /// Workspace monogram — 2-letter initials over a neutral background.
13    #[default]
14    Workspace,
15    /// Device avatar — microphone / camera / display row entries.
16    Device,
17    /// App icon — a system app emitting audio or window-capture target.
18    App,
19    /// Action tile — neutral chrome that hosts a leading glyph in a
20    /// menu row.
21    Action,
22    /// User avatar — square with rounded corners.
23    User,
24}
25
26impl IconTileKind {
27    /// CSS class for the kind.
28    #[must_use]
29    pub fn css(self) -> &'static str {
30        match self {
31            IconTileKind::Workspace => "icon-tile-workspace",
32            IconTileKind::Device => "icon-tile-device",
33            IconTileKind::App => "icon-tile-app",
34            IconTileKind::Action => "icon-tile-action",
35            IconTileKind::User => "icon-tile-user",
36        }
37    }
38}
39
40#[component]
41pub fn IconTile(
42    #[prop(optional)] kind: IconTileKind,
43    #[prop(optional, into)] extra_class: String,
44    children: Children,
45) -> impl IntoView {
46    let class = format!(
47        "icon-tile {}{}{}",
48        kind.css(),
49        if extra_class.is_empty() { "" } else { " " },
50        extra_class,
51    );
52    view! { <span class=class aria-hidden="true">{children()}</span> }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn each_kind_has_unique_class() {
61        let classes = [
62            IconTileKind::Workspace.css(),
63            IconTileKind::Device.css(),
64            IconTileKind::App.css(),
65            IconTileKind::Action.css(),
66            IconTileKind::User.css(),
67        ];
68        let mut sorted = classes.to_vec();
69        sorted.sort_unstable();
70        sorted.dedup();
71        assert_eq!(sorted.len(), classes.len());
72    }
73
74    #[test]
75    fn each_class_starts_with_icon_tile_prefix() {
76        for k in [
77            IconTileKind::Workspace,
78            IconTileKind::Device,
79            IconTileKind::App,
80            IconTileKind::Action,
81            IconTileKind::User,
82        ] {
83            assert!(k.css().starts_with("icon-tile-"));
84        }
85    }
86}