///|
pub(all) struct Panel {
key : String
title : String
content : @minimoon.Node
disabled : Bool
icon : @minimoon.Node?
trigger : @minimoon.Node?
} derive(Eq)
///|
pub fn panel(
key~ : String,
title~ : String,
content~ : @minimoon.Node,
disabled? : Bool = false,
icon? : @minimoon.Node,
trigger? : @minimoon.Node,
) -> Panel {
guard key != "" else { abort("panel key must not be empty") }
{ key, title, content, disabled, icon, trigger, }
}
///|
pub fn collapsible_controlled(
id~ : String,
title~ : String,
content~ : @minimoon.Val[@minimoon.Node],
open~ : @minimoon.Val[Bool],
on_change~ : @minimoon.Emit[Bool],
disabled? : Bool = false,
force_mount? : Bool = false,
show_indicator? : Bool = true,
) -> @minimoon.Val[@minimoon.Node] {
let tap = form_dispatch(open, (current, _ : Unit) => {
if disabled {
@minimoon.none
} else {
on_change(!current)
}
})
@minimoon.Val::view2(open, content, (open, content) => {
ui_container("collapsible", id~, [
@minimoon.button(
on_tap=tap(()),
disabled~,
event_key=id + "/toggle",
semantics=@minimoon.semantics(expanded=open, controls=id + "-content"),
title +
(if show_indicator { if open { " −" } else { " +" } } else { "" }),
),
if open || force_mount {
ui_container(
"collapsible-content",
id=id + "-content",
style=if open { "" } else { "display:none;" },
semantics=@minimoon.semantics(hidden=!open),
[content],
)
} else {
@minimoon.nothing()
},
])
})
}
///|
pub fn collapsible(
id~ : String,
title~ : String,
content~ : @minimoon.Val[@minimoon.Node],
initially_open? : Bool = false,
disabled? : Bool = false,
force_mount? : Bool = false,
show_indicator? : Bool = true,
) -> @minimoon.Val[@minimoon.Node] {
let (open, emit) = @minimoon.create_variable(initially_open)
collapsible_controlled(
id~,
title~,
content~,
open~,
on_change=emit.map(value => _ => value),
disabled~,
force_mount~,
show_indicator~,
)
}
///|
pub fn accordion_controlled(
id~ : String,
items~ : @minimoon.Val[Array[Panel]],
open_keys~ : @minimoon.Val[Array[String]],
on_change~ : @minimoon.Emit[Array[String]],
multiple? : Bool = false,
collapsible? : Bool = true,
disabled? : Bool = false,
force_mount? : Bool = false,
show_indicator? : Bool = true,
) -> @minimoon.Val[@minimoon.Node] {
let tap = form_dispatch(
@minimoon.Val::map2(items, open_keys, (items, keys) => (items, keys)),
(current, key : String) => {
let (items, keys) = current
validate_panels(items)
if disabled || !items.any(item => item.key == key && !item.disabled) {
return @minimoon.none
}
let keys = normalize_panel_keys(items, keys, multiple)
let next = if keys.contains(key) && collapsible {
keys.filter(value => value != key)
} else if keys.contains(key) {
keys
} else if multiple {
let next = keys.copy()
next.push(key)
next
} else {
[key]
}
on_change(next)
},
)
@minimoon.Val::view2(items, open_keys, (items, keys) => {
validate_panels(items)
let keys = normalize_panel_keys(items, keys, multiple)
ui_container(
"accordion",
id~,
items.map(item => {
let open = keys.contains(item.key)
ui_container("accordion-item", id=id + "-" + item.key, [
@minimoon.button_children(
id=id + "-trigger-" + item.key,
disabled=disabled || item.disabled,
event_key=id + "/" + item.key,
on_tap=if disabled || item.disabled {
@minimoon.none
} else {
tap(item.key)
},
semantics=@minimoon.semantics(
expanded=open,
controls=id + "-panel-" + item.key,
),
[
panel_trigger(item),
if show_indicator {
@minimoon.text(if open { "−" } else { "+" })
} else {
@minimoon.nothing()
},
],
),
if open || force_mount {
ui_container(
"accordion-panel",
id=id + "-panel-" + item.key,
style=if open { "" } else { "display:none;" },
semantics=@minimoon.semantics(
role=@minimoon.RegionRole,
labelled_by=id + "-trigger-" + item.key,
hidden=!open,
),
[item.content],
)
} else {
@minimoon.nothing()
},
])
}),
)
})
}
///|
pub fn accordion(
id~ : String,
items~ : @minimoon.Val[Array[Panel]],
initial_open_keys? : Array[String] = [],
multiple? : Bool = false,
collapsible? : Bool = true,
disabled? : Bool = false,
force_mount? : Bool = false,
show_indicator? : Bool = true,
) -> @minimoon.Val[@minimoon.Node] {
let (keys, emit) = @minimoon.create_variable(initial_open_keys)
accordion_controlled(
id~,
items~,
open_keys=keys,
on_change=emit.map(value => _ => value),
multiple~,
collapsible~,
disabled~,
force_mount~,
show_indicator~,
)
}
///|
pub fn tabs_controlled(
id~ : String,
items~ : @minimoon.Val[Array[Panel]],
selected~ : @minimoon.Val[String?],
on_change~ : @minimoon.Emit[String?],
vertical? : Bool = false,
force_mount? : Bool = false,
list_variant? : TabsListVariant = TabsDefault,
) -> @minimoon.Val[@minimoon.Node] {
let change = form_dispatch(items, (items, key : String) => {
if items.any(item => item.key == key && !item.disabled) {
on_change(Some(key))
} else {
@minimoon.none
}
})
@minimoon.Val::view2(items, selected, (items, selected) => {
validate_panels(items)
let selected = valid_tab_selection(items, selected)
let triggers = items.map(item => {
@minimoon.button_children(
id=id + "-tab-" + item.key,
class="mmui-tab",
disabled=item.disabled,
event_key=id + "/" + item.key,
on_tap=if item.disabled { @minimoon.none } else { change(item.key) },
semantics=@minimoon.semantics(
role=@minimoon.TabRole,
selected=selected == Some(item.key),
controls=id + "-panel-" + item.key,
),
panel_trigger(item),
)
})
let panels = items
.filter(item => force_mount || selected == Some(item.key))
.map(item => {
ui_container(
"tab-panel",
id=id + "-panel-" + item.key,
style=if selected == Some(item.key) { "" } else { "display:none;" },
semantics=@minimoon.semantics(
role=@minimoon.TabPanelRole,
labelled_by=id + "-tab-" + item.key,
hidden=selected != Some(item.key),
),
[item.content],
)
})
ui_container("tabs", id~, [
ui_container(
"tab-list",
class=(if vertical { "mmui-vertical" } else { "mmui-horizontal" }) +
(if list_variant == TabsLine {
" mmui-tabs-line"
} else {
" mmui-tabs-default"
}),
semantics=@minimoon.semantics(
role=@minimoon.TabListRole,
orientation=if vertical { "vertical" } else { "horizontal" },
),
triggers,
),
@minimoon.fragment(panels),
])
})
}
///|
pub fn tabs(
id~ : String,
items~ : @minimoon.Val[Array[Panel]],
initial_selected? : String,
vertical? : Bool = false,
force_mount? : Bool = false,
list_variant? : TabsListVariant = TabsDefault,
) -> @minimoon.Val[@minimoon.Node] {
let (selected, emit) = @minimoon.create_variable(initial_selected)
tabs_controlled(
id~,
items~,
selected~,
on_change=emit.map(value => _ => value),
vertical~,
force_mount~,
list_variant~,
)
}