-
Notifications
You must be signed in to change notification settings - Fork 0
Containers
Components that wrap other components for collapsible sections or state-driven content.
Vertical stack of collapsible sections. Headers are always visible; clicking a header toggles the body underneath. Bodies are attached for the entire lifetime of the accordion (state inside a body persists across collapses).
Two variants:
-
Accordion— multi-open. Any combination of sections may be open. -
AccordionSingle— single-open. Opening one section closes the others.
// Section factories
AccordionSection(String title, UIComponent body)
AccordionSection(String title, UIComponent body, boolean defaultOpen)
// Multi-open
Accordion(AccordionSection... sections)
Accordion(Style headerStyle, AccordionSection... sections)
Accordion(State<Set<Integer>> openSet, AccordionSection... sections)
Accordion(State<Set<Integer>> openSet, Style headerStyle, AccordionSection... sections)
// Single-open
AccordionSingle(AccordionSection... sections)
AccordionSingle(Style headerStyle, AccordionSection... sections)
AccordionSingle(State<Integer> openIndex, AccordionSection... sections)
AccordionSingle(State<Integer> openIndex, Style headerStyle, AccordionSection... sections)Accordion(
AccordionSection("General", Column(
Text("Name: ..."),
TextField(name)
), /* defaultOpen */ true),
AccordionSection("Audio", Column(
Text("Master volume"),
SliderInt(volume, 0, 100)
)),
AccordionSection("Advanced", Text("..."))
);Pass your own State<Set<Integer>> to control which sections are open programmatically:
State<Set<Integer>> open = State.of(Set.of(0)); // section 0 starts open
Accordion(open,
AccordionSection("First", /* body */),
AccordionSection("Second", /* body */),
AccordionSection("Third", /* body */));
open.set(Set.of(0, 2)); // open first and thirdAccordionSingle(State<Integer> openIndex, ...) exposes the active section as a single integer (-1 = nothing open). The library bridges the underlying Set<Integer> to your Integer state in both directions, with the equals-check on State.set preventing the round-trip from looping.
State<Integer> active = State.of(0);
AccordionSingle(active,
AccordionSection("Profile", /* body */),
AccordionSection("Settings", /* body */),
AccordionSection("Help", /* body */));
active.set(2); // jump to "Help"Container whose single child is derived from a State<T>. The builder runs every time the state changes; the resulting component replaces the previous child. Use this for tabs, paginated views, conditional sections, list rendering — anywhere the shape of the tree depends on state.
<T> Dynamic(State<T> state, Function<T, UIComponent> builder)
<T> Dynamic(State<T> state, Supplier<UIComponent> builder)State<Integer> tab = State.of(0);
Column(
Row(
Button("Profile", Style.onClick((x, y, b) -> tab.set(0)).build()),
Button("Settings", Style.onClick((x, y, b) -> tab.set(1)).build()),
Button("About", Style.onClick((x, y, b) -> tab.set(2)).build())),
Dynamic(tab, t -> switch (t) {
case 0 -> renderProfile();
case 1 -> renderSettings();
default -> renderAbout();
})
);Dynamic only swaps when the State<T> actually changes. If you derive a value (e.g. state.map(...) then Dynamic on the derived state), the equals-short-circuit on State.set keeps things efficient — equal mapped values won't trigger a rebuild.
Tabs are not yet a first-class component. The pattern above (Row of buttons +
Dynamicon aState<Integer>) is the recommended substitute until a dedicatedTabscomponent lands.