drawer.py 8.2 KB

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