Collapsible source
Inspect and copy the reviewed source included with this Free component.
Free source · 4 files
recipe.rs
// SPDX-FileCopyrightText: 2026 Proa Labs, Inc.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! Collapsible recipe — static named slots for disclosure parts.
use crate::tw_join;
use proa_core::shared::attr_value::AttrValue;
use proa_core::{DataLoader, WebContext, WriteBuf, WriteError};
const COLLAPSIBLE_ROOT: &str = "";
const COLLAPSIBLE_TRIGGER: &str = "";
const COLLAPSIBLE_CONTENT: &str = tw_join!("overflow-hidden", "data-[state=closed]:hidden");
/// Optional inputs accepted by [`CollapsibleRecipe::resolve`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct CollapsibleRecipeProps;
/// Static named classes for every Collapsible part.
#[derive(Debug, Clone, Copy)]
pub struct CollapsibleRecipe {
pub root: &'static str,
pub trigger: &'static str,
pub content: &'static str,
}
impl CollapsibleRecipe {
pub const fn resolve(&'static self, _props: CollapsibleRecipeProps) -> CollapsibleStyle {
CollapsibleStyle { recipe: self }
}
}
/// Fully resolved, streamable Collapsible root style.
#[must_use]
#[derive(Debug, Clone, Copy)]
pub struct CollapsibleStyle {
recipe: &'static CollapsibleRecipe,
}
impl AttrValue for CollapsibleStyle {
#[inline(always)]
fn should_render(&self) -> bool {
!self.recipe.root.is_empty()
}
#[inline(always)]
fn render_attr_value<B: WriteBuf, L: DataLoader>(
&self,
cx: &mut WebContext<L, B>,
) -> Result<(), WriteError> {
cx.out.extend_static(self.recipe.root.as_bytes())
}
}
pub const COLLAPSIBLE_RECIPE: CollapsibleRecipe = CollapsibleRecipe {
root: COLLAPSIBLE_ROOT,
trigger: COLLAPSIBLE_TRIGGER,
content: COLLAPSIBLE_CONTENT,
};
pub const fn collapsible_recipe() -> &'static CollapsibleRecipe {
&COLLAPSIBLE_RECIPE
}
impl crate::variant_spec::ComponentSpec for CollapsibleRecipe {
const NAME: &'static str = "Collapsible";
fn base_classes(&self) -> &'static str {
self.root
}
fn variant_metadata(&self) -> Vec<crate::variant_spec::VariantMetadata> {
Vec::new()
}
fn anatomy(&self) -> Option<crate::Anatomy> {
Some(super::COLLAPSIBLE_ANATOMY)
}
}
/// Compatibility accessors for the former standalone helpers.
pub const fn collapsible_classes() -> &'static str {
COLLAPSIBLE_RECIPE.root
}
pub const fn collapsible_trigger_classes() -> &'static str {
COLLAPSIBLE_RECIPE.trigger
}
pub const fn collapsible_content_classes() -> &'static str {
COLLAPSIBLE_RECIPE.content
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn recipe_exposes_named_slots() {
let recipe = collapsible_recipe();
assert!(recipe.root.is_empty());
assert!(recipe.trigger.is_empty());
assert!(recipe.content.contains("overflow-hidden"));
assert!(recipe.content.contains("data-[state=closed]:hidden"));
}
#[test]
fn recipe_is_const_and_copy() {
const STYLE: CollapsibleStyle = COLLAPSIBLE_RECIPE.resolve(CollapsibleRecipeProps);
let copied = STYLE;
assert!(copied.recipe.root.is_empty());
}
#[test]
fn component_metadata_uses_canonical_anatomy() {
use crate::variant_spec::ComponentSpec;
assert_eq!(
COLLAPSIBLE_RECIPE.anatomy(),
Some(super::super::COLLAPSIBLE_ANATOMY)
);
assert!(COLLAPSIBLE_RECIPE.variant_metadata().is_empty());
}
}
FreeCollapsible
Show/hide regions without a bespoke controller.
Install with the Proa CLI
$ proa ui add collapsibleInstalls the reviewed component source, dependencies, shared support files, and required legal notices.
CLI and registry setup →recipe.rs
// SPDX-FileCopyrightText: 2026 Proa Labs, Inc.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! Collapsible recipe — static named slots for disclosure parts.
use crate::tw_join;
use proa_core::shared::attr_value::AttrValue;
use proa_core::{DataLoader, WebContext, WriteBuf, WriteError};
const COLLAPSIBLE_ROOT: &str = "";
const COLLAPSIBLE_TRIGGER: &str = "";
const COLLAPSIBLE_CONTENT: &str = tw_join!("overflow-hidden", "data-[state=closed]:hidden");
/// Optional inputs accepted by [`CollapsibleRecipe::resolve`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct CollapsibleRecipeProps;
/// Static named classes for every Collapsible part.
#[derive(Debug, Clone, Copy)]
pub struct CollapsibleRecipe {
pub root: &'static str,
pub trigger: &'static str,
Collapsible source
Browse and copy the reviewed source included with this Free component.
collapsible.rsOpen raw ↗
// SPDX-FileCopyrightText: 2026 Proa Labs, Inc.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! Collapsible component implementations
//!
//! SSR markup for collapsible disclosure widgets. The JavaScript hydration
//! handles the expand/collapse behavior.
use super::recipe::{CollapsibleRecipeProps, COLLAPSIBLE_RECIPE};
use super::COLLAPSIBLE_ANATOMY;
use crate::DisclosureState;
use proa_core::{WebContext, WebRenderSync, WriteBuf};
use proa_macros::html_sync;
/// Collapsible root component - wraps trigger and content
///
/// Renders a container div with data attributes for JS hydration.
/// The `open` field controls the initial state.
///
/// # Example
///
/// ```ignore
/// use proa_ui::{Collapsible, CollapsibleTrigger, CollapsibleContent};
/// use proa_macros::html_sync;
///
/// html_sync! {
/// {Collapsible {
/// open: false,
/// children: Some(html_sync! {
/// {CollapsibleTrigger {
/// children: Some("Toggle"),
/// ..Default::default()
/// }}
/// {CollapsibleContent {
/// children: Some("Hidden content"),
/// ..Default::default()
/// }}
/// }),
/// ..Default::default()
/// }}
/// }
/// ```
pub struct Collapsible<C = ()> {
/// Initial open/closed state
pub open: bool,
/// Whether the collapsible is disabled
pub disabled: bool,
/// Additional CSS classes
pub class: Option<&'static str>,
/// Optional id attribute
pub id: Option<&'static str>,
/// Child elements (trigger + content)
pub children: Option<C>,
}
impl Collapsible<()> {
pub const fn new() -> Self {
Self {
open: false,
disabled: false,
class: None,
id: None,
children: None,
}
}
}
impl<C> Default for Collapsible<C> {
fn default() -> Self {
Self {
open: false,
disabled: false,
class: None,
id: None,
children: None,
}
}
}
impl<B: WriteBuf, C: WebRenderSync<L, B>, L: ::proa_core::DataLoader> WebRenderSync<L, B>
for Collapsible<C>
{
fn render(
self,
cx: &mut WebContext<L, B>,
) -> Result<(), proa_core::shared::write_buf::WriteError> {
let style = COLLAPSIBLE_RECIPE.resolve(CollapsibleRecipeProps);
let state = DisclosureState::from_open(self.open);
let disabled = if self.disabled { "true" } else { "false" };
let initial_display = if state.hidden() { "none" } else { "block" };
html_sync! {
<div
data-collapsible
data-collapsible-root
data-scope={COLLAPSIBLE_ANATOMY.scope()}
data-part="root"
data-slot="collapsible"
data-state={state}
data-disabled={disabled}
data-collapsible-id={self.id}
id={self.id}
class={proa_macros::text!("{} {}", style, self.class)}
style={proa_core::style_ok(proa_macros::text!(
"--proa-collapsible-display: {};",
initial_display
))}
>
{self.children}
</div>
}
.render(cx)
}
}
/// CollapsibleTrigger - button that toggles the collapsible content
///
/// Renders a `<button>` element with `data-collapsible-trigger` for JS hydration.
pub struct CollapsibleTrigger<C = ()> {
/// Additional CSS classes
pub class: Option<&'static str>,
/// Trigger content (usually text or an icon)
pub children: Option<C>,
}
impl<C> Default for CollapsibleTrigger<C> {
fn default() -> Self {
Self {
class: None,
children: None,
}
}
}
impl<B: WriteBuf, C: WebRenderSync<L, B>, L: ::proa_core::DataLoader> WebRenderSync<L, B>
for CollapsibleTrigger<C>
{
fn render(
self,
cx: &mut WebContext<L, B>,
) -> Result<(), proa_core::shared::write_buf::WriteError> {
html_sync! {
<button
type="button"
data-collapsible-trigger
data-scope={COLLAPSIBLE_ANATOMY.scope()}
data-part="trigger"
data-slot="collapsible-trigger"
class={proa_macros::text!("{} {}", COLLAPSIBLE_RECIPE.trigger, self.class)}
>
{self.children}
</button>
}
.render(cx)
}
}
/// CollapsibleContent - the content area that is shown/hidden
///
/// The root owns the initial open state. Its inherited display variable makes
/// the first SSR frame truthful; the delegated runtime then projects state and
/// ARIA attributes onto this part.
pub struct CollapsibleContent<C = ()> {
/// Additional CSS classes
pub class: Option<&'static str>,
/// The collapsible content
pub children: Option<C>,
}
impl<C> Default for CollapsibleContent<C> {
fn default() -> Self {
Self {
class: None,
children: None,
}
}
}
impl<B: WriteBuf, C: WebRenderSync<L, B>, L: ::proa_core::DataLoader> WebRenderSync<L, B>
for CollapsibleContent<C>
{
fn render(
self,
cx: &mut WebContext<L, B>,
) -> Result<(), proa_core::shared::write_buf::WriteError> {
html_sync! {
<div
data-collapsible-content
data-scope={COLLAPSIBLE_ANATOMY.scope()}
data-part="content"
data-slot="collapsible-content"
class={proa_macros::text!("{} {}", COLLAPSIBLE_RECIPE.content, self.class)}
style="display: var(--proa-collapsible-display, none);"
>
{self.children}
</div>
}
.render(cx)
}
}
#[cfg(test)]
mod tests {
use super::*;
use proa_core::ctx::Ctx;
use proa_core::text::text;
#[test]
fn collapsible_renders_with_data_attributes() {
let mut out = Vec::new();
let mut cx = Ctx::with_buffer(&mut out);
Collapsible {
open: false,
disabled: false,
class: None,
id: None,
children: Some(text("Content")),
}
.render(&mut cx)
.unwrap();
let html = String::from_utf8(out).unwrap();
assert!(html.contains("data-slot=\"collapsible\""));
assert!(html.contains("data-scope=\"collapsible\""));
assert!(html.contains("data-part=\"root\""));
assert!(html.contains("data-collapsible-root"));
assert!(html.contains("data-state=\"closed\""));
assert!(html.contains("data-disabled=\"false\""));
assert!(html.contains("Content"));
}
#[test]
fn collapsible_renders_open_state() {
let mut out = Vec::new();
let mut cx = Ctx::with_buffer(&mut out);
Collapsible {
open: true,
children: Some(text("Open content")),
..Default::default()
}
.render(&mut cx)
.unwrap();
let html = String::from_utf8(out).unwrap();
assert!(html.contains("data-state=\"open\""));
}
#[test]
fn collapsible_renders_disabled_state() {
let mut out = Vec::new();
let mut cx = Ctx::with_buffer(&mut out);
Collapsible {
disabled: true,
children: Some(text("Disabled")),
..Default::default()
}
.render(&mut cx)
.unwrap();
let html = String::from_utf8(out).unwrap();
assert!(html.contains("data-disabled=\"true\""));
}
#[test]
fn collapsible_renders_with_id() {
let mut out = Vec::new();
let mut cx = Ctx::with_buffer(&mut out);
Collapsible {
id: Some("my-collapsible"),
children: Some(text("Content")),
..Default::default()
}
.render(&mut cx)
.unwrap();
let html = String::from_utf8(out).unwrap();
assert!(html.contains("id=\"my-collapsible\""));
}
#[test]
fn collapsible_accepts_custom_class() {
let mut out = Vec::new();
let mut cx = Ctx::with_buffer(&mut out);
Collapsible {
class: Some("my-class"),
children: Some(text("Content")),
..Default::default()
}
.render(&mut cx)
.unwrap();
let html = String::from_utf8(out).unwrap();
assert!(html.contains("my-class"));
}
#[test]
fn collapsible_trigger_renders_as_button() {
let mut out = Vec::new();
let mut cx = Ctx::with_buffer(&mut out);
CollapsibleTrigger {
class: None,
children: Some(text("Toggle")),
}
.render(&mut cx)
.unwrap();
let html = String::from_utf8(out).unwrap();
assert!(html.contains("<button"));
assert!(html.contains("type=\"button\""));
assert!(html.contains("data-slot=\"collapsible-trigger\""));
assert!(html.contains("data-part=\"trigger\""));
assert!(html.contains("data-collapsible-trigger"));
assert!(html.contains("Toggle"));
}
#[test]
fn collapsible_trigger_accepts_custom_class() {
let mut out = Vec::new();
let mut cx = Ctx::with_buffer(&mut out);
CollapsibleTrigger {
class: Some("trigger-class"),
children: Some(text("Toggle")),
}
.render(&mut cx)
.unwrap();
let html = String::from_utf8(out).unwrap();
assert!(html.contains("trigger-class"));
}
#[test]
fn collapsible_content_renders_with_data_attributes() {
let mut out = Vec::new();
let mut cx = Ctx::with_buffer(&mut out);
CollapsibleContent {
class: None,
children: Some(text("Hidden content")),
}
.render(&mut cx)
.unwrap();
let html = String::from_utf8(out).unwrap();
assert!(html.contains("data-slot=\"collapsible-content\""));
assert!(html.contains("data-collapsible-content"));
assert!(!html.contains("data-state=\"closed\""));
assert!(html.contains("var(--proa-collapsible-display, none)"));
assert!(html.contains("data-part=\"content\""));
assert!(html.contains("Hidden content"));
}
#[test]
fn collapsible_content_has_overflow_hidden_class() {
let mut out = Vec::new();
let mut cx = Ctx::with_buffer(&mut out);
CollapsibleContent {
class: None,
children: Some(text("Content")),
}
.render(&mut cx)
.unwrap();
let html = String::from_utf8(out).unwrap();
assert!(html.contains("overflow-hidden"));
}
#[test]
fn collapsible_content_accepts_custom_class() {
let mut out = Vec::new();
let mut cx = Ctx::with_buffer(&mut out);
CollapsibleContent {
class: Some("content-class"),
children: Some(text("Content")),
}
.render(&mut cx)
.unwrap();
let html = String::from_utf8(out).unwrap();
assert!(html.contains("content-class"));
}
#[test]
fn collapsible_const_new() {
const _: Collapsible<()> = Collapsible::new();
let collapsible = Collapsible::new();
assert!(!collapsible.open);
assert!(!collapsible.disabled);
assert!(collapsible.class.is_none());
assert!(collapsible.id.is_none());
assert!(collapsible.children.is_none());
}
#[test]
fn full_collapsible_composition() {
let mut out = Vec::new();
let mut cx = Ctx::with_buffer(&mut out);
Collapsible {
open: false,
children: Some(html_sync! {
{CollapsibleTrigger { class: None, children: Some(text("Toggle")), }}
{CollapsibleContent {
class: None,
children: Some(text("Collapsible content here")),
}}
}),
..Default::default()
}
.render(&mut cx)
.unwrap();
let html = String::from_utf8(out).unwrap();
assert!(html.contains("data-collapsible-root"));
assert!(html.contains("data-collapsible-trigger"));
assert!(html.contains("data-collapsible-content"));
assert!(html.contains("Toggle"));
assert!(html.contains("Collapsible content here"));
}
#[test]
fn runtime_projects_root_state_ids_and_aria_to_owned_parts() {
let js = include_str!("../../../runtime/proa-free.js");
assert!(js.contains("data-collapsible-id"));
assert!(js.contains("`${id}--trigger`"));
assert!(js.contains("`${id}--content`"));
assert!(js.contains("trigger.setAttribute('aria-expanded'"));
assert!(js.contains("content.setAttribute('aria-hidden'"));
assert!(js.contains("findOwnedPart(root, this.selectors.root"));
}
}
md.rsOpen raw ↗
// SPDX-FileCopyrightText: 2026 Proa Labs, Inc.
// SPDX-License-Identifier: MIT OR Apache-2.0
use super::{Collapsible, CollapsibleContent, CollapsibleTrigger};
use crate::components::md_support::block_boundary;
use proa_core::WebContext;
use proa_core::{MdRenderSync, WriteBuf, WriteError};
use proa_macros::md_sync;
/// Collapsible is a passthrough — trigger and content handle formatting.
impl<Loader: ::proa_core::DataLoader, B: WriteBuf, C: MdRenderSync<Loader, B>>
MdRenderSync<Loader, B> for Collapsible<C>
{
fn render_md(self, cx: &mut WebContext<Loader, B>) -> Result<(), WriteError> {
self.children.render_md(cx)?;
block_boundary(cx)?;
Ok(())
}
}
/// CollapsibleTrigger renders its label as a bold line.
impl<Loader: ::proa_core::DataLoader, B: WriteBuf, C: MdRenderSync<Loader, B>>
MdRenderSync<Loader, B> for CollapsibleTrigger<C>
{
fn render_md(self, cx: &mut WebContext<Loader, B>) -> Result<(), WriteError> {
block_boundary(cx)?;
let prev = cx.md_set_inline(true);
md_sync! { "**" { self.children } "**" }.render_md(cx)?;
cx.md_set_inline(prev);
block_boundary(cx)?;
Ok(())
}
}
/// CollapsibleContent always renders its children in markdown (no collapse state).
impl<Loader: ::proa_core::DataLoader, B: WriteBuf, C: MdRenderSync<Loader, B>>
MdRenderSync<Loader, B> for CollapsibleContent<C>
{
fn render_md(self, cx: &mut WebContext<Loader, B>) -> Result<(), WriteError> {
block_boundary(cx)?;
self.children.render_md(cx)?;
block_boundary(cx)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn collapsible_renders_bold_trigger_and_content() {
let mut buf = Vec::new();
let mut cx = WebContext::with_buffer(&mut buf);
Collapsible {
children: Some((
CollapsibleTrigger {
class: None,
children: Some("Show details"),
},
CollapsibleContent {
class: None,
children: Some("The hidden details."),
},
)),
..Default::default()
}
.render_md(&mut cx)
.unwrap();
let output = String::from_utf8(buf).unwrap();
assert!(output.contains("**Show details**"), "output: {output}");
assert!(output.contains("The hidden details\\."), "output: {output}");
// Trigger line comes before content
let trigger_pos = output.find("**Show details**").unwrap();
let content_pos = output.find("The hidden details\\.").unwrap();
assert!(trigger_pos < content_pos, "output: {output}");
}
/// A collapsible rendered after mid-line prose must start on its own
/// line, blank-line separated, so the trigger stays a valid bold line.
#[test]
fn collapsible_composes_after_inline_text() {
let mut buf = Vec::new();
let mut cx = WebContext::with_buffer(&mut buf);
cx.write_md_prepared_static(b"intro prose").unwrap();
Collapsible {
children: Some((
CollapsibleTrigger {
class: None,
children: Some("Show details"),
},
CollapsibleContent {
class: None,
children: Some("The hidden details."),
},
)),
..Default::default()
}
.render_md(&mut cx)
.unwrap();
let output = String::from_utf8(buf).unwrap();
assert!(
output.contains("intro prose\n\n**Show details**"),
"trigger must be blank-line separated from preceding prose, got:\n{output}"
);
assert!(
output.contains("**Show details**\n\nThe hidden details\\."),
"content must be blank-line separated from the trigger, got:\n{output}"
);
}
#[test]
fn collapsible_content_renders_even_when_closed() {
let mut buf = Vec::new();
let mut cx = WebContext::with_buffer(&mut buf);
Collapsible {
open: false,
children: Some(CollapsibleContent {
class: None,
children: Some("Always visible in markdown"),
}),
..Default::default()
}
.render_md(&mut cx)
.unwrap();
let output = String::from_utf8(buf).unwrap();
assert!(
output.contains("Always visible in markdown"),
"output: {output}"
);
}
}
mod.rsOpen raw ↗
// SPDX-FileCopyrightText: 2026 Proa Labs, Inc.
// SPDX-License-Identifier: MIT OR Apache-2.0
#![allow(clippy::module_inception)]
//! Collapsible component - an interactive disclosure widget
//!
//! The collapsible component allows toggling visibility of content.
//! JavaScript handles the show/hide behavior.
use crate::Anatomy;
mod collapsible;
mod md;
mod recipe;
/// Static anatomy contract shared by SSR and the delegated runtime.
pub const COLLAPSIBLE_ANATOMY: Anatomy =
Anatomy::new("collapsible", &["root", "trigger", "content"]);
pub use collapsible::{Collapsible, CollapsibleContent, CollapsibleTrigger};
pub use recipe::{
collapsible_classes, collapsible_content_classes, collapsible_recipe,
collapsible_trigger_classes, CollapsibleRecipe, CollapsibleRecipeProps, CollapsibleStyle,
COLLAPSIBLE_RECIPE,
};
#[cfg(test)]
mod anatomy_tests {
use super::*;
#[test]
fn anatomy_declares_every_public_part() {
assert_eq!(COLLAPSIBLE_ANATOMY.scope(), "collapsible");
assert_eq!(COLLAPSIBLE_ANATOMY.parts(), &["root", "trigger", "content"]);
}
}
recipe.rsOpen raw ↗
// SPDX-FileCopyrightText: 2026 Proa Labs, Inc.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! Collapsible recipe — static named slots for disclosure parts.
use crate::tw_join;
use proa_core::shared::attr_value::AttrValue;
use proa_core::{DataLoader, WebContext, WriteBuf, WriteError};
const COLLAPSIBLE_ROOT: &str = "";
const COLLAPSIBLE_TRIGGER: &str = "";
const COLLAPSIBLE_CONTENT: &str = tw_join!("overflow-hidden", "data-[state=closed]:hidden");
/// Optional inputs accepted by [`CollapsibleRecipe::resolve`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct CollapsibleRecipeProps;
/// Static named classes for every Collapsible part.
#[derive(Debug, Clone, Copy)]
pub struct CollapsibleRecipe {
pub root: &'static str,
pub trigger: &'static str,
pub content: &'static str,
}
impl CollapsibleRecipe {
pub const fn resolve(&'static self, _props: CollapsibleRecipeProps) -> CollapsibleStyle {
CollapsibleStyle { recipe: self }
}
}
/// Fully resolved, streamable Collapsible root style.
#[must_use]
#[derive(Debug, Clone, Copy)]
pub struct CollapsibleStyle {
recipe: &'static CollapsibleRecipe,
}
impl AttrValue for CollapsibleStyle {
#[inline(always)]
fn should_render(&self) -> bool {
!self.recipe.root.is_empty()
}
#[inline(always)]
fn render_attr_value<B: WriteBuf, L: DataLoader>(
&self,
cx: &mut WebContext<L, B>,
) -> Result<(), WriteError> {
cx.out.extend_static(self.recipe.root.as_bytes())
}
}
pub const COLLAPSIBLE_RECIPE: CollapsibleRecipe = CollapsibleRecipe {
root: COLLAPSIBLE_ROOT,
trigger: COLLAPSIBLE_TRIGGER,
content: COLLAPSIBLE_CONTENT,
};
pub const fn collapsible_recipe() -> &'static CollapsibleRecipe {
&COLLAPSIBLE_RECIPE
}
impl crate::variant_spec::ComponentSpec for CollapsibleRecipe {
const NAME: &'static str = "Collapsible";
fn base_classes(&self) -> &'static str {
self.root
}
fn variant_metadata(&self) -> Vec<crate::variant_spec::VariantMetadata> {
Vec::new()
}
fn anatomy(&self) -> Option<crate::Anatomy> {
Some(super::COLLAPSIBLE_ANATOMY)
}
}
/// Compatibility accessors for the former standalone helpers.
pub const fn collapsible_classes() -> &'static str {
COLLAPSIBLE_RECIPE.root
}
pub const fn collapsible_trigger_classes() -> &'static str {
COLLAPSIBLE_RECIPE.trigger
}
pub const fn collapsible_content_classes() -> &'static str {
COLLAPSIBLE_RECIPE.content
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn recipe_exposes_named_slots() {
let recipe = collapsible_recipe();
assert!(recipe.root.is_empty());
assert!(recipe.trigger.is_empty());
assert!(recipe.content.contains("overflow-hidden"));
assert!(recipe.content.contains("data-[state=closed]:hidden"));
}
#[test]
fn recipe_is_const_and_copy() {
const STYLE: CollapsibleStyle = COLLAPSIBLE_RECIPE.resolve(CollapsibleRecipeProps);
let copied = STYLE;
assert!(copied.recipe.root.is_empty());
}
#[test]
fn component_metadata_uses_canonical_anatomy() {
use crate::variant_spec::ComponentSpec;
assert_eq!(
COLLAPSIBLE_RECIPE.anatomy(),
Some(super::super::COLLAPSIBLE_ANATOMY)
);
assert!(COLLAPSIBLE_RECIPE.variant_metadata().is_empty());
}
}