accordion.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. """Radix accordion components."""
  2. from typing import Literal
  3. from reflex.components.component import Component
  4. from reflex.components.radix.primitives.base import RadixPrimitiveComponent
  5. from reflex.components.radix.themes.components.icons import Icon
  6. from reflex.style import Style
  7. from reflex.utils import imports
  8. from reflex.vars import Var
  9. LiteralAccordionType = Literal["single", "multiple"]
  10. LiteralAccordionDir = Literal["ltr", "rtl"]
  11. LiteralAccordionOrientation = Literal["vertical", "horizontal"]
  12. DEFAULT_ANIMATION_DURATION = 250
  13. class AccordionComponent(RadixPrimitiveComponent):
  14. """Base class for all @radix-ui/accordion components."""
  15. library = "@radix-ui/react-accordion@^1.1.2"
  16. class AccordionRoot(AccordionComponent):
  17. """An accordion component."""
  18. tag = "Root"
  19. alias = "RadixAccordionRoot"
  20. # The type of accordion (single or multiple).
  21. type_: Var[LiteralAccordionType]
  22. # The value of the item to expand.
  23. value: Var[str]
  24. # The default value of the item to expand.
  25. default_value: Var[str]
  26. # Whether or not the accordion is collapsible.
  27. collapsible: Var[bool]
  28. # Whether or not the accordion is disabled.
  29. disabled: Var[bool]
  30. # The reading direction of the accordion when applicable.
  31. dir: Var[LiteralAccordionDir]
  32. # The orientation of the accordion.
  33. orientation: Var[LiteralAccordionOrientation]
  34. def _apply_theme(self, theme: Component):
  35. self.style = Style(
  36. {
  37. "border_radius": "6px",
  38. "background_color": "var(--accent-6)",
  39. "box_shadow": "0 2px 10px var(--black-a4)",
  40. **self.style,
  41. }
  42. )
  43. class AccordionItem(AccordionComponent):
  44. """An accordion component."""
  45. tag = "Item"
  46. alias = "RadixAccordionItem"
  47. # A unique identifier for the item.
  48. value: Var[str]
  49. # When true, prevents the user from interacting with the item.
  50. disabled: Var[bool]
  51. def _apply_theme(self, theme: Component):
  52. self.style = Style(
  53. {
  54. "overflow": "hidden",
  55. "margin_top": "1px",
  56. "&:first-child": {
  57. "margin_top": 0,
  58. "border_top_left_radius": "4px",
  59. "border_top_right_radius": "4px",
  60. },
  61. "&:last-child": {
  62. "border_bottom_left_radius": "4px",
  63. "border_bottom_right_radius": "4px",
  64. },
  65. "&:focus-within": {
  66. "position": "relative",
  67. "z_index": 1,
  68. "box_shadow": "0 0 0 2px var(--accent-7)",
  69. },
  70. **self.style,
  71. }
  72. )
  73. class AccordionHeader(AccordionComponent):
  74. """An accordion component."""
  75. tag = "Header"
  76. alias = "RadixAccordionHeader"
  77. def _apply_theme(self, theme: Component):
  78. self.style = Style(
  79. {
  80. "display": "flex",
  81. **self.style,
  82. }
  83. )
  84. class AccordionTrigger(AccordionComponent):
  85. """An accordion component."""
  86. tag = "Trigger"
  87. alias = "RadixAccordionTrigger"
  88. def _apply_theme(self, theme: Component):
  89. self.style = Style(
  90. {
  91. "font_family": "inherit",
  92. "padding": "0 20px",
  93. "height": "45px",
  94. "flex": 1,
  95. "display": "flex",
  96. "align_items": "center",
  97. "justify_content": "space-between",
  98. "font_size": "15px",
  99. "line_height": 1,
  100. "color": "var(--accent-11)",
  101. "box_shadow": "0 1px 0 var(--accent-6)",
  102. "&:hover": {
  103. "background_color": "var(--gray-2)",
  104. },
  105. "& > .AccordionChevron": {
  106. "color": "var(--accent-10)",
  107. "transition": f"transform {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)",
  108. },
  109. "&[data-state='open'] > .AccordionChevron": {
  110. "transform": "rotate(180deg)",
  111. },
  112. **self.style,
  113. }
  114. )
  115. class AccordionContent(AccordionComponent):
  116. """An accordion component."""
  117. tag = "Content"
  118. alias = "RadixAccordionContent"
  119. def _apply_theme(self, theme: Component):
  120. self.style = Style(
  121. {
  122. "overflow": "hidden",
  123. "fontSize": "15px",
  124. "color": "var(--accent-11)",
  125. "backgroundColor": "var(--accent-2)",
  126. "padding": "15px, 20px",
  127. "&[data-state='open']": {
  128. "animation": Var.create(
  129. f"${{slideDown}} {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)",
  130. _var_is_string=True,
  131. ),
  132. },
  133. "&[data-state='closed']": {
  134. "animation": Var.create(
  135. f"${{slideUp}} {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)",
  136. _var_is_string=True,
  137. ),
  138. },
  139. **self.style,
  140. }
  141. )
  142. def _get_imports(self):
  143. return {
  144. **super()._get_imports(),
  145. "@emotion/react": [imports.ImportVar(tag="keyframes")],
  146. }
  147. def _get_custom_code(self) -> str:
  148. return """
  149. const slideDown = keyframes`
  150. from {
  151. height: 0;
  152. }
  153. to {
  154. height: var(--radix-accordion-content-height);
  155. }
  156. `
  157. const slideUp = keyframes`
  158. from {
  159. height: var(--radix-accordion-content-height);
  160. }
  161. to {
  162. height: 0;
  163. }
  164. `
  165. """
  166. def accordion_item(header: Component, content: Component, **props) -> Component:
  167. """Create an accordion item.
  168. Args:
  169. header: The header of the accordion item.
  170. content: The content of the accordion item.
  171. **props: Additional properties to apply to the accordion item.
  172. Returns:
  173. The accordion item.
  174. """
  175. # The item requires a value to toggle (use the header as the default value).
  176. value = props.pop("value", str(header))
  177. return AccordionItem.create(
  178. AccordionHeader.create(
  179. AccordionTrigger.create(
  180. header,
  181. Icon.create(
  182. tag="chevron_down",
  183. class_name="AccordionChevron",
  184. ),
  185. ),
  186. ),
  187. AccordionContent.create(
  188. content,
  189. ),
  190. value=value,
  191. **props,
  192. )
  193. accordion = AccordionRoot.create