drawer.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. """Drawer components based on Radix primitives."""
  2. # Based on Vaul: https://github.com/emilkowalski/vaul
  3. # Style based on https://ui.shadcn.com/docs/components/drawer
  4. from __future__ import annotations
  5. from typing import Any, List, Literal, Optional, Union
  6. from reflex.components.component import Component, ComponentNamespace
  7. from reflex.components.radix.primitives.base import RadixPrimitiveComponent
  8. from reflex.components.radix.themes.base import Theme
  9. from reflex.components.radix.themes.layout.flex import Flex
  10. from reflex.event import EventHandler
  11. from reflex.utils import console
  12. from reflex.vars.base import Var
  13. class DrawerComponent(RadixPrimitiveComponent):
  14. """A Drawer component."""
  15. library = "vaul"
  16. lib_dependencies: List[str] = ["@radix-ui/react-dialog@^1.0.5"]
  17. LiteralDirectionType = Literal["top", "bottom", "left", "right"]
  18. class DrawerRoot(DrawerComponent):
  19. """The Root component of a Drawer, contains all parts of a drawer."""
  20. tag = "Drawer.Root"
  21. alias = "Vaul" + tag
  22. # Whether the drawer is open or not.
  23. open: Var[bool]
  24. # Enable background scaling, it requires an element with [vaul-drawer-wrapper] data attribute to scale its background.
  25. should_scale_background: Var[bool]
  26. # Number between 0 and 1 that determines when the drawer should be closed.
  27. close_threshold: Var[float]
  28. # 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.
  29. snap_points: Optional[List[Union[str, float]]]
  30. # Index of a snapPoint from which the overlay fade should be applied. Defaults to the last snap point.
  31. fade_from_index: Var[int]
  32. # Duration for which the drawer is not draggable after scrolling content inside of the drawer. Defaults to 500ms
  33. scroll_lock_timeout: Var[int]
  34. # When `False`, it allows to interact with elements outside of the drawer without closing it. Defaults to `True`.
  35. modal: Var[bool]
  36. # Direction of the drawer. Defaults to `"bottom"`
  37. direction: Var[LiteralDirectionType]
  38. # When `True`, it prevents scroll restoration. Defaults to `True`.
  39. preventScrollRestoration: Var[bool]
  40. # Fires when the drawer is opened.
  41. on_open_change: EventHandler[lambda e0: [e0]]
  42. class DrawerTrigger(DrawerComponent):
  43. """The button that opens the dialog."""
  44. tag = "Drawer.Trigger"
  45. alias = "Vaul" + tag
  46. # Defaults to true, if the first child acts as the trigger.
  47. as_child: Var[bool] = True # type: ignore
  48. @classmethod
  49. def create(cls, *children: Any, **props: Any) -> Component:
  50. """Create a new DrawerTrigger instance.
  51. Args:
  52. children: The children of the element.
  53. props: The properties of the element.
  54. Returns:
  55. The new DrawerTrigger instance.
  56. """
  57. for child in children:
  58. if "on_click" in getattr(child, "event_triggers", {}):
  59. children = (Flex.create(*children),)
  60. break
  61. return super().create(*children, **props)
  62. class DrawerPortal(DrawerComponent):
  63. """Portals your drawer into the body."""
  64. tag = "Drawer.Portal"
  65. alias = "Vaul" + tag
  66. # Based on https://www.radix-ui.com/primitives/docs/components/dialog#content
  67. class DrawerContent(DrawerComponent):
  68. """Content that should be rendered in the drawer."""
  69. tag = "Drawer.Content"
  70. alias = "Vaul" + tag
  71. # Style set partially based on the source code at https://ui.shadcn.com/docs/components/drawer
  72. def _get_style(self) -> dict:
  73. """Get the style for the component.
  74. Returns:
  75. The dictionary of the component style as value and the style notation as key.
  76. """
  77. base_style = {
  78. "left": "0",
  79. "right": "0",
  80. "bottom": "0",
  81. "top": "0",
  82. "position": "fixed",
  83. "z_index": 50,
  84. "display": "flex",
  85. }
  86. style = self.style or {}
  87. base_style.update(style)
  88. return {"css": base_style}
  89. # Fired when the drawer content is opened. Deprecated.
  90. on_open_auto_focus: EventHandler[lambda e0: []]
  91. # Fired when the drawer content is closed. Deprecated.
  92. on_close_auto_focus: EventHandler[lambda e0: []]
  93. # Fired when the escape key is pressed. Deprecated.
  94. on_escape_key_down: EventHandler[lambda e0: []]
  95. # Fired when the pointer is down outside the drawer content. Deprecated.
  96. on_pointer_down_outside: EventHandler[lambda e0: []]
  97. # Fired when interacting outside the drawer content. Deprecated.
  98. on_interact_outside: EventHandler[lambda e0: []]
  99. @classmethod
  100. def create(cls, *children, **props):
  101. """Create a Drawer Content.
  102. We wrap the Drawer content in an `rx.theme` to make radix themes definitions available to
  103. rendered div in the DOM. This is because Vaul Drawer injects the Drawer overlay content in a sibling
  104. div to the root div rendered by radix which contains styling definitions. Wrapping in `rx.theme`
  105. makes the styling available to the overlay.
  106. Args:
  107. *children: The list of children to use.
  108. **props: Additional properties to apply to the drawer content.
  109. Returns:
  110. The drawer content.
  111. """
  112. deprecated_properties = [
  113. "on_open_auto_focus",
  114. "on_close_auto_focus",
  115. "on_escape_key_down",
  116. "on_pointer_down_outside",
  117. "on_interact_outside",
  118. ]
  119. for prop in deprecated_properties:
  120. if prop in props:
  121. console.deprecate(
  122. feature_name="drawer content events",
  123. reason=f"The `{prop}` event is deprecated and will be removed in 0.7.0.",
  124. deprecation_version="0.6.3",
  125. removal_version="0.7.0",
  126. )
  127. comp = super().create(*children, **props)
  128. return Theme.create(comp)
  129. class DrawerOverlay(DrawerComponent):
  130. """A layer that covers the inert portion of the view when the dialog is open."""
  131. tag = "Drawer.Overlay"
  132. alias = "Vaul" + tag
  133. # Style set based on the source code at https://ui.shadcn.com/docs/components/drawer
  134. def _get_style(self) -> dict:
  135. """Get the style for the component.
  136. Returns:
  137. The dictionary of the component style as value and the style notation as key.
  138. """
  139. base_style = {
  140. "position": "fixed",
  141. "left": "0",
  142. "right": "0",
  143. "bottom": "0",
  144. "top": "0",
  145. "z_index": 50,
  146. "background": "rgba(0, 0, 0, 0.5)",
  147. }
  148. style = self.style or {}
  149. base_style.update(style)
  150. return {"css": base_style}
  151. class DrawerClose(DrawerTrigger):
  152. """A button that closes the drawer."""
  153. tag = "Drawer.Close"
  154. alias = "Vaul" + tag
  155. class DrawerTitle(DrawerComponent):
  156. """A title for the drawer."""
  157. tag = "Drawer.Title"
  158. alias = "Vaul" + tag
  159. # Style set based on the source code at https://ui.shadcn.com/docs/components/drawer
  160. def _get_style(self) -> dict:
  161. """Get the style for the component.
  162. Returns:
  163. The dictionary of the component style as value and the style notation as key.
  164. """
  165. base_style = {
  166. "font-size": "1.125rem",
  167. "font-weight": "600",
  168. "line-weight": "1",
  169. "letter-spacing": "-0.05em",
  170. }
  171. style = self.style or {}
  172. base_style.update(style)
  173. return {"css": base_style}
  174. class DrawerDescription(DrawerComponent):
  175. """A description for the drawer."""
  176. tag = "Drawer.Description"
  177. alias = "Vaul" + tag
  178. # Style set based on the source code at https://ui.shadcn.com/docs/components/drawer
  179. def _get_style(self) -> dict:
  180. """Get the style for the component.
  181. Returns:
  182. The dictionary of the component style as value and the style notation as key.
  183. """
  184. base_style = {
  185. "font-size": "0.875rem",
  186. }
  187. style = self.style or {}
  188. base_style.update(style)
  189. return {"css": base_style}
  190. class Drawer(ComponentNamespace):
  191. """A namespace for Drawer components."""
  192. root = __call__ = staticmethod(DrawerRoot.create)
  193. trigger = staticmethod(DrawerTrigger.create)
  194. portal = staticmethod(DrawerPortal.create)
  195. content = staticmethod(DrawerContent.create)
  196. overlay = staticmethod(DrawerOverlay.create)
  197. close = staticmethod(DrawerClose.create)
  198. title = staticmethod(DrawerTitle.create)
  199. description = staticmethod(DrawerDescription.create)
  200. drawer = Drawer()