///|
pub fn[C : @minimoon.IsChildren] avatar(
size? : Size = Medium,
class? : String = "",
children : C,
) -> @minimoon.Node {
ui_container(
"avatar",
children.to_nodes(),
class=size_class(size) + " " + class,
)
}
///|
pub fn avatar_image(
src~ : String,
alt? : String = "",
on_load? : @minimoon.Emit[@minimoon.ImageLoadDetail],
on_error? : @minimoon.Emit[String],
id? : String = "",
) -> @minimoon.Node {
@minimoon.image(
src~,
alt~,
on_load?,
on_error?,
id~,
mode=@minimoon.AspectFill,
class="mmui-avatar-image",
)
}
///|
/// RUI image lifecycle is scoped by source; late events from an old source are inert.
pub(all) enum AvatarImageStatus {
AvatarLoading
AvatarLoaded
AvatarError
} derive(Eq, Debug)
///|
pub fn[C : @minimoon.IsChildren] avatar_with_fallback(
src~ : @minimoon.Val[String],
alt? : String = "",
size? : Size = Medium,
id? : String = "",
on_status_change? : @minimoon.Emit[AvatarImageStatus],
fallback : C,
) -> @minimoon.Val[@minimoon.Node] {
let nodes = fallback.to_nodes()
src.switch_by(
(source, _) => {
let initial = if source == "" { AvatarError } else { AvatarLoading }
let (status, emit) = @minimoon.create_state_with_init(
init=_ => {
(
initial,
on_status_change
.map(notify => notify(initial))
.unwrap_or(@minimoon.none),
)
},
update=(previous, next : AvatarImageStatus, _) => {
(
next,
if previous == next {
@minimoon.none
} else {
on_status_change
.map(notify => notify(next))
.unwrap_or(@minimoon.none)
},
)
},
)
status.map(status => {
avatar(size~, [
if status == AvatarLoaded {
@minimoon.nothing()
} else {
avatar_fallback(nodes)
},
if status == AvatarError {
@minimoon.nothing()
} else {
@minimoon.image(
id~,
class="mmui-avatar-image",
mode=@minimoon.AspectFill,
load_key=if id == "" {
""
} else {
id + "/image/" + source + "/load"
},
error_key=if id == "" {
""
} else {
id + "/image/" + source + "/error"
},
style="position:absolute;left:0;top:0;" +
(if status == AvatarLoaded { "" } else { "opacity:0;" }),
src=source,
alt~,
on_error=emit.map(_ => AvatarError),
on_load=emit.map(_ => AvatarLoaded),
)
},
])
})
},
by=source => "avatar:" + source,
)
}
///|
pub fn[C : @minimoon.IsChildren] badge(
variant? : Tone = Default,
class? : String = "",
children : C,
) -> @minimoon.Node {
ui_container(
"badge",
children.to_nodes(),
class=tone_class(variant) + " " + class,
)
}
///|
pub fn[C : @minimoon.IsChildren] card(
size? : Size = Medium,
class? : String = "",
children : C,
) -> @minimoon.Node {
ui_container(
"card",
children.to_nodes(),
class=size_class(size) + " " + class,
)
}
///|
pub fn[C : @minimoon.IsChildren] breadcrumb(
label? : String = "Breadcrumb",
children : C,
) -> @minimoon.Node {
ui_container(
"breadcrumb",
children.to_nodes(),
semantics=@minimoon.semantics(role=@minimoon.NavigationRole, label~),
)
}
///|
pub fn breadcrumb_separator() -> @minimoon.Node {
@minimoon.span(
class="mmui-breadcrumb-separator",
semantics=@minimoon.semantics(hidden=true),
"/",
)
}
///|
pub fn breadcrumb_ellipsis() -> @minimoon.Node {
@minimoon.span(
class="mmui-breadcrumb-ellipsis",
semantics=@minimoon.semantics(label="More pages"),
"…",
)
}
///|
pub fn[C : @minimoon.IsChildren] bubble(
variant? : Tone = Secondary,
alignment? : Alignment = Start,
children : C,
) -> @minimoon.Node {
ui_container(
"bubble",
children.to_nodes(),
class=tone_class(variant) +
" " +
(match alignment {
Start => "mmui-start"
Center => "mmui-center"
End => "mmui-end"
}),
)
}
///|
pub fn[C : @minimoon.IsChildren] item(
variant? : Tone = Default,
size? : Size = Medium,
children : C,
) -> @minimoon.Node {
ui_container(
"item",
children.to_nodes(),
class=tone_class(variant) + " " + size_class(size),
)
}
///|
pub fn[C : @minimoon.IsChildren] marker(
variant? : MarkerVariant = PlainMarker,
children : C,
) -> @minimoon.Node {
let nodes = children.to_nodes()
ui_container(
"marker",
if variant == SeparatorMarker {
[separator(), @minimoon.fragment(nodes), separator()]
} else {
nodes
},
class=if variant == BorderMarker { "mmui-marker-border" } else { "" },
)
}
///|
pub fn[C : @minimoon.IsChildren] message(
alignment? : Alignment = Start,
children : C,
) -> @minimoon.Node {
ui_container(
"message",
children.to_nodes(),
class=match alignment {
Start => "mmui-start"
Center => "mmui-center"
End => "mmui-end"
},
)
}
///|
pub fn[C : @minimoon.IsChildren] label(
for_id~ : String,
required? : Bool = false,
disabled? : Bool = false,
children : C,
) -> @minimoon.Node {
let nodes = children.to_nodes().copy()
if required {
nodes.push(@minimoon.span(class="mmui-required", " *"))
}
@minimoon.label(
for_id~,
class="mmui-label" + (if disabled { " mmui-disabled" } else { "" }),
nodes,
)
}
///|
pub fn separator(
orientation? : Orientation = Horizontal,
decorative? : Bool = true,
) -> @minimoon.Node {
ui_container(
"separator",
[],
class=orientation_class(orientation),
semantics=@minimoon.semantics(
role=@minimoon.SeparatorRole,
hidden=decorative,
orientation=match orientation {
Horizontal => "horizontal"
Vertical => "vertical"
},
),
)
}
///|
pub fn skeleton(
width? : Int = 100,
height? : Int = 20,
round? : Bool = false,
) -> @minimoon.Node {
guard width >= 0 && height >= 0 else {
abort("skeleton dimensions must be nonnegative")
}
ui_container(
"skeleton",
[],
style="width:" +
width.to_string() +
"px;height:" +
height.to_string() +
"px;" +
(if round { "border-radius:9999px;" } else { "" }),
semantics=@minimoon.semantics(hidden=true),
)
}
///|
pub fn spinner(
label? : String = "Loading",
size? : Size = Medium,
) -> @minimoon.Node {
ui_container(
"spinner",
[@minimoon.text("◌")],
class=size_class(size),
semantics=@minimoon.semantics(role=@minimoon.StatusRole, label~, busy=true),
)
}