diff --git a/demo/demo/demo.py b/demo/demo/demo.py index d884642..9d3dc5b 100644 --- a/demo/demo/demo.py +++ b/demo/demo/demo.py @@ -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" diff --git a/reflex_ui/__init__.py b/reflex_ui/__init__.py index aa7d0d6..e932c3c 100644 --- a/reflex_ui/__init__.py +++ b/reflex_ui/__init__.py @@ -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"], diff --git a/reflex_ui/components/base/collapsible.py b/reflex_ui/components/base/collapsible.py new file mode 100644 index 0000000..95811d4 --- /dev/null +++ b/reflex_ui/components/base/collapsible.py @@ -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