Current section
Files
Jump to
Current section
Files
src/ui/disclosure.gleam
// MIT License
// Copyright (c) 2026 Koncreate
// See LICENSE for details
import gleam/option.{type Option, None, Some}
import gleam/string
import lustre/attribute.{type Attribute, attribute, class}
import lustre/element.{type Element}
import lustre/element/html
import ui/css
import ui/keyboard
import ui/size as size_helpers
pub type Variant {
Default
Muted
}
pub type Size {
Small
Medium
Large
}
pub fn disclosure(
attributes: List(Attribute(a)),
children: List(Element(a)),
) -> Element(a) {
html.div([class("rounded-md border border-input"), ..attributes], children)
}
pub fn trigger(
attributes: List(Attribute(a)),
children: List(Element(a)),
) -> Element(a) {
html.button(
[
class(
[
"flex w-full items-center justify-between px-4 py-2 text-left text-sm font-medium",
css.focus_ring(),
]
|> string.join(" "),
),
attribute("type", "button"),
attribute("aria-expanded", "false"),
attribute("aria-controls", "disclosure-panel"),
..attributes
],
children,
)
}
pub fn panel(
attributes: List(Attribute(a)),
children: List(Element(a)),
) -> Element(a) {
html.div(
[
class("px-4 pb-4 text-sm"),
attribute("id", "disclosure-panel"),
attribute("role", "region"),
..attributes
],
children,
)
}
pub fn variant(v: Variant) -> Attribute(a) {
case v {
Default -> class("bg-background text-foreground")
Muted -> class("bg-muted text-muted-foreground")
}
}
pub fn size(s: Size) -> Attribute(a) {
case s {
Small -> class(size_helpers.text_class(size_helpers.Small))
Medium -> class(size_helpers.text_class(size_helpers.Medium))
Large -> class(size_helpers.text_class(size_helpers.Large))
}
}
pub fn aria_controls(id: String) -> Attribute(a) {
attribute("aria-controls", id)
}
pub fn aria_expanded(expanded: Bool) -> Attribute(a) {
attribute("aria-expanded", case expanded {
True -> "true"
False -> "false"
})
}
pub fn aria_label(label: String) -> Attribute(a) {
attribute("aria-label", label)
}
// ============================================================================
// Keyboard Navigation
// ============================================================================
/// Keyboard messages for disclosure component.
pub type Msg {
/// Toggle expanded/collapsed state (Enter or Space)
Toggle
}
/// Keymap for disclosure keyboard navigation.
/// Follows WAI-ARIA disclosure pattern:
/// - Enter: Toggle expanded/collapsed state
/// - Space: Toggle expanded/collapsed state
pub fn keymap(key_event: keyboard.KeyEvent) -> Option(Msg) {
case keyboard.decode_key(key_event.key) {
keyboard.Enter | keyboard.Space -> Some(Toggle)
_ -> None
}
}