Skip to content

Commit 72d2846

Browse files
authored
ENG-6150: Collapsible component (#35)
1 parent 4427fb8 commit 72d2846

File tree

5 files changed

+434
-0
lines changed

5 files changed

+434
-0
lines changed

reflex_ui/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"components.base.button": ["button"],
99
"components.base.card": ["card"],
1010
"components.base.checkbox": ["checkbox"],
11+
"components.base.collapsible": ["collapsible"],
1112
"components.base.dialog": ["dialog"],
1213
"components.base.gradient_profile": ["gradient_profile"],
1314
"components.base.input": ["input"],

reflex_ui/__init__.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ from .components.base.badge import badge
1010
from .components.base.button import button
1111
from .components.base.card import card
1212
from .components.base.checkbox import checkbox
13+
from .components.base.collapsible import collapsible
1314
from .components.base.dialog import dialog
1415
from .components.base.gradient_profile import gradient_profile
1516
from .components.base.input import input
@@ -39,6 +40,7 @@ _REFLEX_UI_MAPPING = {
3940
"components.base.button": ["button"],
4041
"components.base.card": ["card"],
4142
"components.base.checkbox": ["checkbox"],
43+
"components.base.collapsible": ["collapsible"],
4244
"components.base.dialog": ["dialog"],
4345
"components.base.gradient_profile": ["gradient_profile"],
4446
"components.base.input": ["input"],
@@ -76,6 +78,7 @@ __all__ = [
7678
"card",
7779
"checkbox",
7880
"cn",
81+
"collapsible",
7982
"components",
8083
"dialog",
8184
"gradient_profile",

reflex_ui/components/base/__init__.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ from .badge import badge
1010
from .button import button
1111
from .card import card
1212
from .checkbox import checkbox
13+
from .collapsible import collapsible
1314
from .dialog import dialog
1415
from .gradient_profile import gradient_profile
1516
from .input import input
@@ -41,6 +42,7 @@ __all__ = [
4142
"button",
4243
"card",
4344
"checkbox",
45+
"collapsible",
4446
"dialog",
4547
"gradient_profile",
4648
"input",
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
"""Custom collapsible component."""
2+
3+
from reflex.components.component import Component, ComponentNamespace
4+
from reflex.event import EventHandler, passthrough_event_spec
5+
from reflex.utils.imports import ImportVar
6+
from reflex.vars.base import Var
7+
8+
from reflex_ui.components.base_ui import PACKAGE_NAME, BaseUIComponent
9+
10+
11+
class ClassNames:
12+
"""Class names for collapsible components."""
13+
14+
ROOT = "flex flex-col justify-center text-secondary-12"
15+
TRIGGER = "group flex items-center gap-2"
16+
PANEL = "flex h-[var(--collapsible-panel-height)] flex-col justify-end overflow-hidden text-sm transition-all ease-out data-[ending-style]:h-0 data-[starting-style]:h-0"
17+
18+
19+
class CollapsibleBaseComponent(BaseUIComponent):
20+
"""Base component for collapsible components."""
21+
22+
library = f"{PACKAGE_NAME}/collapsible"
23+
24+
@property
25+
def import_var(self):
26+
"""Return the import variable for the collapsible component."""
27+
return ImportVar(tag="Collapsible", package_path="", install=False)
28+
29+
30+
class CollapsibleRoot(CollapsibleBaseComponent):
31+
"""Groups all parts of the collapsible. Renders a <div> element."""
32+
33+
tag = "Collapsible.Root"
34+
35+
# Whether the collapsible panel is initially open. To render a controlled collapsible, use the `open` prop instead. Defaults to False.
36+
default_open: Var[bool]
37+
38+
# Whether the collapsible panel is currently open. To render an uncontrolled collapsible, use the `default_open` prop instead.
39+
open: Var[bool]
40+
41+
# Event handler called when the panel is opened or closed.
42+
on_open_change: EventHandler[passthrough_event_spec(bool)]
43+
44+
# Whether the component should ignore user interaction. Defaults to False.
45+
disabled: Var[bool]
46+
47+
# The render prop.
48+
render_: Var[Component]
49+
50+
@classmethod
51+
def create(cls, *children, **props) -> BaseUIComponent:
52+
"""Create the collapsible root component."""
53+
props["data-slot"] = "collapsible"
54+
cls.set_class_name(ClassNames.ROOT, props)
55+
return super().create(*children, **props)
56+
57+
58+
class CollapsibleTrigger(CollapsibleBaseComponent):
59+
"""A button that opens and closes the collapsible panel. Renders a <button> element."""
60+
61+
tag = "Collapsible.Trigger"
62+
63+
# 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.
64+
native_button: Var[bool]
65+
66+
# The render prop.
67+
render_: Var[Component]
68+
69+
@classmethod
70+
def create(cls, *children, **props) -> BaseUIComponent:
71+
"""Create the collapsible trigger component."""
72+
props["data-slot"] = "collapsible-trigger"
73+
cls.set_class_name(ClassNames.TRIGGER, props)
74+
return super().create(*children, **props)
75+
76+
77+
class CollapsiblePanel(CollapsibleBaseComponent):
78+
"""A panel with the collapsible contents. Renders a <div> element."""
79+
80+
tag = "Collapsible.Panel"
81+
82+
# Allows the browser's built-in page search to find and expand the panel contents. Overrides the `keep_mounted` prop and uses `hidden="until-found"` to hide the element without removing it from the DOM. Defaults to False.
83+
hidden_until_found: Var[bool]
84+
85+
# Whether to keep the element in the DOM while the panel is hidden. This prop is ignored when `hidden_until_found` is used. Defaults to False.
86+
keep_mounted: Var[bool]
87+
88+
# Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.
89+
render_: Var[Component]
90+
91+
@classmethod
92+
def create(cls, *children, **props) -> BaseUIComponent:
93+
"""Create the collapsible panel component."""
94+
props["data-slot"] = "collapsible-panel"
95+
cls.set_class_name(ClassNames.PANEL, props)
96+
return super().create(*children, **props)
97+
98+
99+
class HighLevelCollapsible(CollapsibleRoot):
100+
"""High level collapsible component."""
101+
102+
# The trigger component.
103+
trigger: Var[Component | None]
104+
105+
# The content component.
106+
content: Var[str | Component | None]
107+
108+
@classmethod
109+
def create(cls, *children, **props) -> BaseUIComponent:
110+
"""Create the collapsible component."""
111+
trigger = props.pop("trigger", None)
112+
content = props.pop("content", None)
113+
114+
return CollapsibleRoot.create(
115+
CollapsibleTrigger.create(render_=trigger) if trigger else None,
116+
CollapsiblePanel.create(
117+
content,
118+
*children,
119+
),
120+
**props,
121+
)
122+
123+
def _exclude_props(self) -> list[str]:
124+
return [
125+
*super()._exclude_props(),
126+
"trigger",
127+
"content",
128+
]
129+
130+
131+
class Collapsible(ComponentNamespace):
132+
"""Namespace for Collapsible components."""
133+
134+
root = staticmethod(CollapsibleRoot.create)
135+
trigger = staticmethod(CollapsibleTrigger.create)
136+
panel = staticmethod(CollapsiblePanel.create)
137+
__call__ = staticmethod(HighLevelCollapsible.create)
138+
139+
140+
collapsible = Collapsible()

0 commit comments

Comments
 (0)