Skip to content

Commit ec6b6a6

Browse files
authored
ENG-6167: Drawer component (#37)
1 parent 4714695 commit ec6b6a6

File tree

5 files changed

+846
-0
lines changed

5 files changed

+846
-0
lines changed

reflex_ui/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"components.base.checkbox": ["checkbox"],
1212
"components.base.collapsible": ["collapsible"],
1313
"components.base.dialog": ["dialog"],
14+
"components.base.drawer": ["drawer"],
1415
"components.base.gradient_profile": ["gradient_profile"],
1516
"components.base.input": ["input"],
1617
"components.base.link": ["link"],

reflex_ui/__init__.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ from .components.base.card import card
1313
from .components.base.checkbox import checkbox
1414
from .components.base.collapsible import collapsible
1515
from .components.base.dialog import dialog
16+
from .components.base.drawer import drawer
1617
from .components.base.gradient_profile import gradient_profile
1718
from .components.base.input import input
1819
from .components.base.link import link
@@ -44,6 +45,7 @@ _REFLEX_UI_MAPPING = {
4445
"components.base.checkbox": ["checkbox"],
4546
"components.base.collapsible": ["collapsible"],
4647
"components.base.dialog": ["dialog"],
48+
"components.base.drawer": ["drawer"],
4749
"components.base.gradient_profile": ["gradient_profile"],
4850
"components.base.input": ["input"],
4951
"components.base.link": ["link"],
@@ -84,6 +86,7 @@ __all__ = [
8486
"collapsible",
8587
"components",
8688
"dialog",
89+
"drawer",
8790
"gradient_profile",
8891
"hi",
8992
"icon",

reflex_ui/components/base/__init__.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ from .card import card
1313
from .checkbox import checkbox
1414
from .collapsible import collapsible
1515
from .dialog import dialog
16+
from .drawer import drawer
1617
from .gradient_profile import gradient_profile
1718
from .input import input
1819
from .link import link
@@ -46,6 +47,7 @@ __all__ = [
4647
"checkbox",
4748
"collapsible",
4849
"dialog",
50+
"drawer",
4951
"gradient_profile",
5052
"input",
5153
"link",
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
"""Custom drawer component."""
2+
3+
from collections.abc import Sequence
4+
from typing import Literal
5+
6+
from reflex.components.component import Component, ComponentNamespace
7+
from reflex.event import EventHandler, passthrough_event_spec
8+
from reflex.vars.base import Var
9+
10+
from reflex_ui.components.component import CoreComponent
11+
12+
LiteralDirectionType = Literal["top", "bottom", "left", "right"]
13+
14+
15+
class ClassNames:
16+
"""Class names for the drawer component."""
17+
18+
ROOT = ""
19+
TRIGGER = ""
20+
PORTAL = ""
21+
CONTENT = "fixed right-0 bottom-0 z-50 bg-secondary-1 max-w-96 border-l border-secondary-a4 size-full flex"
22+
OVERLAY = "fixed inset-0 z-50 bg-black/50"
23+
CLOSE = ""
24+
TITLE = "text-2xl font-semibold text-secondary-12"
25+
DESCRIPTION = "text-sm text-secondary-11"
26+
HANDLE = ""
27+
28+
29+
class DrawerBaseComponent(CoreComponent):
30+
"""Base component for drawer components."""
31+
32+
library = "[email protected]"
33+
34+
35+
class DrawerRoot(DrawerBaseComponent):
36+
"""The Root component of a Drawer, contains all parts of a drawer."""
37+
38+
tag = "Drawer.Root"
39+
40+
# The open state of the drawer when it is initially rendered. Use when you do not need to control its open state.
41+
default_open: Var[bool]
42+
43+
# Whether the drawer is open or not.
44+
open: Var[bool]
45+
46+
# Fires when the drawer is opened or closed.
47+
on_open_change: EventHandler[passthrough_event_spec(bool)]
48+
49+
# When False, it allows interaction with elements outside of the drawer without closing it. Defaults to True.
50+
modal: Var[bool]
51+
52+
# Direction of the drawer. This adjusts the animations and the drag direction. Defaults to "bottom"
53+
direction: Var[LiteralDirectionType]
54+
55+
# Gets triggered after the open or close animation ends, it receives an open argument with the open state of the drawer by the time the function was triggered.
56+
on_animation_end: EventHandler[passthrough_event_spec(bool)]
57+
58+
# When False, dragging, clicking outside, pressing esc, etc. will not close the drawer. Use this in combination with the open prop, otherwise you won't be able to open/close the drawer.
59+
dismissible: Var[bool]
60+
61+
# When True, dragging will only be possible by the handle.
62+
handle_only: Var[bool]
63+
64+
# Container element to render the portal into. Defaults to document.body.
65+
container: Var[str]
66+
67+
# Whether to reposition inputs when the drawer opens. Defaults to True.
68+
reposition_inputs: Var[bool]
69+
70+
# Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up. Should go from least visible. Also Accept px values, which doesn't take screen height into account.
71+
snap_points: Sequence[str | float] | None
72+
73+
# Current active snap point.
74+
active_snap_point: Var[bool]
75+
76+
# Function to set the active snap point.
77+
set_active_snap_point: EventHandler[passthrough_event_spec(int)]
78+
79+
# Index of a snap point from which the overlay fade should be applied. Defaults to the last snap point.
80+
fade_from_index: Var[int]
81+
82+
# Whether to snap to sequential points.
83+
snap_to_sequential_point: Var[bool]
84+
85+
@classmethod
86+
def create(cls, *children, **props) -> Component:
87+
"""Create the drawer root component."""
88+
props["data-slot"] = "drawer-root"
89+
cls.set_class_name(ClassNames.ROOT, props)
90+
return super().create(*children, **props)
91+
92+
93+
class DrawerTrigger(DrawerBaseComponent):
94+
"""The button that opens the drawer."""
95+
96+
tag = "Drawer.Trigger"
97+
98+
# Render the trigger as a child. Defaults to False.
99+
as_child: Var[bool]
100+
101+
# The render prop
102+
render_: Var[Component]
103+
104+
@classmethod
105+
def create(cls, *children, **props) -> Component:
106+
"""Create the drawer trigger component."""
107+
props["data-slot"] = "drawer-trigger"
108+
cls.set_class_name(ClassNames.TRIGGER, props)
109+
return super().create(*children, **props)
110+
111+
112+
class DrawerPortal(DrawerBaseComponent):
113+
"""Portals your drawer into the body."""
114+
115+
tag = "Drawer.Portal"
116+
117+
@classmethod
118+
def create(cls, *children, **props) -> Component:
119+
"""Create the drawer portal component."""
120+
props["data-slot"] = "drawer-portal"
121+
cls.set_class_name(ClassNames.PORTAL, props)
122+
return super().create(*children, **props)
123+
124+
125+
class DrawerContent(DrawerBaseComponent):
126+
"""Content that should be rendered in the drawer."""
127+
128+
tag = "Drawer.Content"
129+
130+
# Render the content as a child. Defaults to False.
131+
as_child: Var[bool]
132+
133+
@classmethod
134+
def create(cls, *children, **props) -> Component:
135+
"""Create the drawer content component."""
136+
props["data-slot"] = "drawer-content"
137+
cls.set_class_name(ClassNames.CONTENT, props)
138+
return super().create(*children, **props)
139+
140+
141+
class DrawerOverlay(DrawerBaseComponent):
142+
"""A layer that covers the inert portion of the view when the drawer is open."""
143+
144+
tag = "Drawer.Overlay"
145+
146+
# Render the overlay as a child. Defaults to False.
147+
as_child: Var[bool]
148+
149+
@classmethod
150+
def create(cls, *children, **props) -> Component:
151+
"""Create the drawer overlay component."""
152+
props["data-slot"] = "drawer-overlay"
153+
cls.set_class_name(ClassNames.OVERLAY, props)
154+
return super().create(*children, **props)
155+
156+
157+
class DrawerClose(DrawerBaseComponent):
158+
"""A button that closes the drawer."""
159+
160+
tag = "Drawer.Close"
161+
162+
# Render the close button as a child. Defaults to False.
163+
as_child: Var[bool]
164+
165+
@classmethod
166+
def create(cls, *children, **props) -> Component:
167+
"""Create the drawer close component."""
168+
props["data-slot"] = "drawer-close"
169+
cls.set_class_name(ClassNames.CLOSE, props)
170+
return super().create(*children, **props)
171+
172+
173+
class DrawerTitle(DrawerBaseComponent):
174+
"""An optional accessible title to be announced when the drawer is opened."""
175+
176+
tag = "Drawer.Title"
177+
178+
# Render the title as a child. Defaults to False.
179+
as_child: Var[bool]
180+
181+
@classmethod
182+
def create(cls, *children, **props) -> Component:
183+
"""Create the drawer title component."""
184+
props["data-slot"] = "drawer-title"
185+
cls.set_class_name(ClassNames.TITLE, props)
186+
return super().create(*children, **props)
187+
188+
189+
class DrawerDescription(DrawerBaseComponent):
190+
"""An optional accessible description to be announced when the drawer is opened."""
191+
192+
tag = "Drawer.Description"
193+
194+
# Render the description as a child. Defaults to False.
195+
as_child: Var[bool]
196+
197+
@classmethod
198+
def create(cls, *children, **props) -> Component:
199+
"""Create the drawer description component."""
200+
props["data-slot"] = "drawer-description"
201+
cls.set_class_name(ClassNames.DESCRIPTION, props)
202+
return super().create(*children, **props)
203+
204+
205+
class DrawerHandle(DrawerBaseComponent):
206+
"""An optional handle to drag the drawer."""
207+
208+
tag = "Drawer.Handle"
209+
210+
alias = "Vaul" + tag
211+
212+
@classmethod
213+
def create(cls, *children, **props) -> Component:
214+
"""Create the drawer handle component."""
215+
props["data-slot"] = "drawer-handle"
216+
cls.set_class_name(ClassNames.HANDLE, props)
217+
return super().create(*children, **props)
218+
219+
220+
class HighLevelDrawer(DrawerRoot):
221+
"""High level wrapper for the Drawer component."""
222+
223+
# Drawer props
224+
trigger: Var[Component | None]
225+
content: Var[str | Component | None]
226+
title: Var[str | Component | None]
227+
description: Var[str | Component | None]
228+
229+
@classmethod
230+
def create(cls, *children, **props) -> Component:
231+
"""Create the high level drawer component."""
232+
trigger = props.get("trigger")
233+
content = props.get("content")
234+
title = props.get("title")
235+
description = props.get("description")
236+
237+
return super().create(
238+
DrawerTrigger.create(render_=trigger) if trigger else None,
239+
DrawerPortal.create(
240+
DrawerOverlay.create(),
241+
DrawerContent.create(
242+
DrawerTitle.create(title) if title else None,
243+
DrawerDescription.create(description) if description else None,
244+
content,
245+
*children,
246+
),
247+
),
248+
**props,
249+
)
250+
251+
def _exclude_props(self) -> list[str]:
252+
return [
253+
*super()._exclude_props(),
254+
"trigger",
255+
"content",
256+
"title",
257+
"description",
258+
]
259+
260+
261+
class Drawer(ComponentNamespace):
262+
"""A namespace for Drawer components."""
263+
264+
root = staticmethod(DrawerRoot.create)
265+
trigger = staticmethod(DrawerTrigger.create)
266+
portal = staticmethod(DrawerPortal.create)
267+
content = staticmethod(DrawerContent.create)
268+
overlay = staticmethod(DrawerOverlay.create)
269+
close = staticmethod(DrawerClose.create)
270+
title = staticmethod(DrawerTitle.create)
271+
description = staticmethod(DrawerDescription.create)
272+
handle = staticmethod(DrawerHandle.create)
273+
__call__ = staticmethod(HighLevelDrawer.create)
274+
275+
276+
drawer = Drawer()

0 commit comments

Comments
 (0)