Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions demo/demo/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,22 @@ def index() -> rx.Component:
on_value_change=lambda value: rx.toast.success(f"Value: {value}"),
on_open_change=lambda value: rx.toast.success(f"Open: {value}"),
),
ui.collapsible(
trigger=ui.button(
rx.el.span("▶", class_name="transition-all ease-out group-data-[panel-open]:rotate-90 inline-block mr-2"),
"Recovery keys",
variant="outline",
class_name="group flex items-center gap-2 rounded-sm bg-gray-100 px-2 py-1 text-sm font-medium hover:bg-gray-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-800 active:bg-gray-200",
),
content=rx.el.div(
rx.el.div("alien-bean-pasta", class_name="text-sm"),
rx.el.div("wild-irish-burrito", class_name="text-sm"),
rx.el.div("horse-battery-staple", class_name="text-sm"),
class_name="mt-1 flex cursor-text flex-col gap-2 rounded-sm bg-gray-100 py-2 pl-7",
),
class_name="flex min-h-36 w-56 flex-col justify-center text-gray-900",
on_open_change=lambda value: rx.toast.success(f"Collapsible open: {value}"),
),
ui.theme_switcher(class_name="absolute top-4 right-4"),
class_name=ui.cn(
"flex flex-col gap-6 items-center justify-center h-screen", "bg-secondary-1"
Expand Down
1 change: 1 addition & 0 deletions reflex_ui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"components.base.button": ["button"],
"components.base.card": ["card"],
"components.base.checkbox": ["checkbox"],
"components.base.collapsible": ["collapsible"],
"components.base.dialog": ["dialog"],
"components.base.gradient_profile": ["gradient_profile"],
"components.base.input": ["input"],
Expand Down
133 changes: 133 additions & 0 deletions reflex_ui/components/base/collapsible.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""Custom collapsible component."""

from reflex.components.component import Component, ComponentNamespace
from reflex.event import EventHandler, passthrough_event_spec
from reflex.utils.imports import ImportVar
from reflex.vars.base import Var

from reflex_ui.components.base_ui import PACKAGE_NAME, BaseUIComponent


class ClassNames:
"""Class names for collapsible components."""

ROOT = ""
TRIGGER = "cursor-pointer focus:outline-none focus-visible:ring-1 focus-visible:ring-primary-4"
PANEL = "flex h-[var(--collapsible-panel-height)] flex-col justify-end overflow-hidden transition-all ease-out data-[ending-style]:h-0 data-[starting-style]:h-0"


class CollapsibleBaseComponent(BaseUIComponent):
"""Base component for collapsible components."""

library = f"{PACKAGE_NAME}/collapsible"

@property
def import_var(self):
"""Return the import variable for the collapsible component."""
return ImportVar(tag="Collapsible", package_path="", install=False)


class CollapsibleRoot(CollapsibleBaseComponent):
"""Groups all parts of the collapsible. Doesn't render its own HTML element."""

tag = "Collapsible.Root"

default_open: Var[bool]

open: Var[bool]

on_open_change: EventHandler[passthrough_event_spec(bool)]

# Whether the component should ignore user interaction.
disabled: Var[bool]

@classmethod
def create(cls, *children, **props) -> BaseUIComponent:
"""Create the collapsible root component."""
props["data-slot"] = "collapsible"
cls.set_class_name(ClassNames.ROOT, props)
return super().create(*children, **props)


class CollapsibleTrigger(CollapsibleBaseComponent):
"""A button that opens and closes the collapsible panel. Renders a <button> element."""

tag = "Collapsible.Trigger"

# Whether the component renders a native <button> element when replacing it via the render prop. Set to false if the rendered element is not a button (e.g. <div>). Defaults to True.
native_button: Var[bool]

# The render prop.
render_: Var[Component]

@classmethod
def create(cls, *children, **props) -> BaseUIComponent:
"""Create the collapsible trigger component."""
props["data-slot"] = "collapsible-trigger"
cls.set_class_name(ClassNames.TRIGGER, props)
return super().create(*children, **props)


class CollapsiblePanel(CollapsibleBaseComponent):
"""A panel with the collapsible contents. Renders a <div> element."""

tag = "Collapsible.Panel"

# Allows the browser's built-in page search to find and expand the panel contents. Overrides the `keepMounted` prop and uses `hidden="until-found"` to hide the element without removing it from the DOM.
hidden_until_found: Var[bool]

# Whether to keep the element in the DOM while the panel is hidden. This prop is ignored when `hiddenUntilFound` is used.
keep_mounted: Var[bool]

# The render prop.
render_: Var[Component]

@classmethod
def create(cls, *children, **props) -> BaseUIComponent:
"""Create the collapsible panel component."""
props["data-slot"] = "collapsible-panel"
cls.set_class_name(ClassNames.PANEL, props)
return super().create(*children, **props)


class HighLevelCollapsible(CollapsibleRoot):
"""High level collapsible component."""

trigger: Var[Component | None]
content: Var[str | Component | None]

@classmethod
def create(cls, *children, **props) -> BaseUIComponent:
"""Create the collapsible component."""
trigger = props.pop("trigger", None)
content = props.pop("content", None)
class_name = props.pop("class_name", "")

return CollapsibleRoot.create(
CollapsibleTrigger.create(render_=trigger) if trigger else None,
CollapsiblePanel.create(
content,
*children,
class_name=class_name,
),
**props,
)

def _exclude_props(self) -> list[str]:
return [
*super()._exclude_props(),
"trigger",
"content",
]


class Collapsible(ComponentNamespace):
"""Namespace for Collapsible components."""

root = staticmethod(CollapsibleRoot.create)
trigger = staticmethod(CollapsibleTrigger.create)
panel = staticmethod(CollapsiblePanel.create)
__call__ = staticmethod(HighLevelCollapsible.create)


collapsible = Collapsible()
88 changes: 88 additions & 0 deletions reflex_ui/components/base/collapsible.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""Type stubs for collapsible component."""

from reflex.components.component import Component, ComponentNamespace
from reflex.event import EventType
from reflex.vars.base import Var

from reflex_ui.components.base_ui import BaseUIComponent

class ClassNames:
ROOT: str
TRIGGER: str
PANEL: str

class CollapsibleBaseComponent(BaseUIComponent):
library: str
@property
def import_var(self): ...

class CollapsibleRoot(CollapsibleBaseComponent):
tag: str
default_open: Var[bool]
open: Var[bool]
on_open_change: EventType[bool]
disabled: Var[bool]
@classmethod
def create(
cls,
*children,
default_open: bool | Var[bool] | None = None,
open: bool | Var[bool] | None = None,
on_open_change: EventType[bool] | None = None,
disabled: bool | Var[bool] | None = None,
**props,
) -> CollapsibleRoot: ...

class CollapsibleTrigger(CollapsibleBaseComponent):
tag: str
native_button: Var[bool]
render_: Var[Component]
@classmethod
def create(
cls,
*children,
native_button: bool | Var[bool] | None = None,
render_: Component | Var[Component] | None = None,
**props,
) -> CollapsibleTrigger: ...

class CollapsiblePanel(CollapsibleBaseComponent):
tag: str
# Allows the browser's built-in page search to find and expand the panel contents. Overrides the `keepMounted` prop and uses `hidden="until-found"` to hide the element without removing it from the DOM.
hidden_until_found: Var[bool]
# Whether to keep the element in the DOM while the panel is hidden. This prop is ignored when `hiddenUntilFound` is used.
keep_mounted: Var[bool]
render_: Var[Component]
@classmethod
def create(
cls,
*children,
hidden_until_found: bool | Var[bool] | None = None,
keep_mounted: bool | Var[bool] | None = None,
render_: Component | Var[Component] | None = None,
**props,
) -> CollapsiblePanel: ...

class HighLevelCollapsible(CollapsibleRoot):
trigger: Var[Component | None]
content: Var[str | Component | None]
@classmethod
def create(
cls,
*children,
trigger: Component | Var[Component] | None = None,
content: str | Component | Var[str | Component] | None = None,
default_open: bool | Var[bool] | None = None,
open: bool | Var[bool] | None = None,
on_open_change: EventType[bool] | None = None,
disabled: bool | Var[bool] | None = None,
**props,
) -> HighLevelCollapsible: ...

class Collapsible(ComponentNamespace):
root: staticmethod
trigger: staticmethod
panel: staticmethod
__call__: staticmethod

collapsible: Collapsible