select.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. """Interactive components provided by @radix-ui/themes."""
  2. from collections.abc import Sequence
  3. from typing import ClassVar, Literal
  4. import reflex as rx
  5. from reflex.components.component import Component, ComponentNamespace
  6. from reflex.components.core.breakpoints import Responsive
  7. from reflex.constants.compiler import MemoizationMode
  8. from reflex.event import no_args_event_spec, passthrough_event_spec
  9. from reflex.vars.base import Var
  10. from ..base import LiteralAccentColor, LiteralRadius, RadixThemesComponent
  11. class SelectRoot(RadixThemesComponent):
  12. """Displays a list of options for the user to pick from, triggered by a button."""
  13. tag = "Select.Root"
  14. # The size of the select: "1" | "2" | "3"
  15. size: Var[Responsive[Literal["1", "2", "3"]]]
  16. # The value of the select when initially rendered. Use when you do not need to control the state of the select.
  17. default_value: Var[str]
  18. # The controlled value of the select. Should be used in conjunction with on_change.
  19. value: Var[str]
  20. # The open state of the select when it is initially rendered. Use when you do not need to control its open state.
  21. default_open: Var[bool]
  22. # The controlled open state of the select. Must be used in conjunction with on_open_change.
  23. open: Var[bool]
  24. # The name of the select control when submitting the form.
  25. name: Var[str]
  26. # When True, prevents the user from interacting with select.
  27. disabled: Var[bool]
  28. # When True, indicates that the user must select a value before the owning form can be submitted.
  29. required: Var[bool]
  30. # Props to rename
  31. _rename_props = {"onChange": "onValueChange"}
  32. # Fired when the value of the select changes.
  33. on_change: rx.EventHandler[passthrough_event_spec(str)]
  34. # Fired when the select is opened or closed.
  35. on_open_change: rx.EventHandler[passthrough_event_spec(bool)]
  36. class SelectTrigger(RadixThemesComponent):
  37. """The button that toggles the select."""
  38. tag = "Select.Trigger"
  39. # Variant of the select trigger
  40. variant: Var[Literal["classic", "surface", "soft", "ghost"]]
  41. # The color of the select trigger
  42. color_scheme: Var[LiteralAccentColor]
  43. # The radius of the select trigger
  44. radius: Var[LiteralRadius]
  45. # The placeholder of the select trigger
  46. placeholder: Var[str]
  47. _valid_parents: ClassVar[list[str]] = ["SelectRoot"]
  48. _memoization_mode = MemoizationMode(recursive=False)
  49. class SelectContent(RadixThemesComponent):
  50. """The component that pops out when the select is open."""
  51. tag = "Select.Content"
  52. # The variant of the select content
  53. variant: Var[Literal["solid", "soft"]]
  54. # The color of the select content
  55. color_scheme: Var[LiteralAccentColor]
  56. # Whether to render the select content with higher contrast color against background
  57. high_contrast: Var[bool]
  58. # The positioning mode to use, item-aligned is the default and behaves similarly to a native MacOS menu by positioning content relative to the active item. popper positions content in the same way as our other primitives, for example Popover or DropdownMenu.
  59. position: Var[Literal["item-aligned", "popper"]]
  60. # The preferred side of the anchor to render against when open. Will be reversed when collisions occur and avoidCollisions is enabled. Only available when position is set to popper.
  61. side: Var[Literal["top", "right", "bottom", "left"]]
  62. # The distance in pixels from the anchor. Only available when position is set to popper.
  63. side_offset: Var[int]
  64. # The preferred alignment against the anchor. May change when collisions occur. Only available when position is set to popper.
  65. align: Var[Literal["start", "center", "end"]]
  66. # The vertical distance in pixels from the anchor. Only available when position is set to popper.
  67. align_offset: Var[int]
  68. # Fired when the select content is closed.
  69. on_close_auto_focus: rx.EventHandler[no_args_event_spec]
  70. # Fired when the escape key is pressed.
  71. on_escape_key_down: rx.EventHandler[no_args_event_spec]
  72. # Fired when a pointer down event happens outside the select content.
  73. on_pointer_down_outside: rx.EventHandler[no_args_event_spec]
  74. class SelectGroup(RadixThemesComponent):
  75. """Used to group multiple items."""
  76. tag = "Select.Group"
  77. _valid_parents: ClassVar[list[str]] = ["SelectContent"]
  78. class SelectItem(RadixThemesComponent):
  79. """The component that contains the select items."""
  80. tag = "Select.Item"
  81. # The value given as data when submitting a form with a name.
  82. value: Var[str]
  83. # Whether the select item is disabled
  84. disabled: Var[bool]
  85. _valid_parents: ClassVar[list[str]] = ["SelectGroup", "SelectContent"]
  86. class SelectLabel(RadixThemesComponent):
  87. """Used to render the label of a group, it isn't focusable using arrow keys."""
  88. tag = "Select.Label"
  89. _valid_parents: ClassVar[list[str]] = ["SelectGroup"]
  90. class SelectSeparator(RadixThemesComponent):
  91. """Used to visually separate items in the Select."""
  92. tag = "Select.Separator"
  93. class HighLevelSelect(SelectRoot):
  94. """High level wrapper for the Select component."""
  95. # The items of the select.
  96. items: Var[Sequence[str]]
  97. # The placeholder of the select.
  98. placeholder: Var[str]
  99. # The label of the select.
  100. label: Var[str]
  101. # The color of the select.
  102. color_scheme: Var[LiteralAccentColor]
  103. # Whether to render the select with higher contrast color against background.
  104. high_contrast: Var[bool]
  105. # The variant of the select.
  106. variant: Var[Literal["classic", "surface", "soft", "ghost"]]
  107. # The radius of the select.
  108. radius: Var[LiteralRadius]
  109. # The width of the select.
  110. width: Var[str]
  111. # The positioning mode to use. Default is "item-aligned".
  112. position: Var[Literal["item-aligned", "popper"]]
  113. @classmethod
  114. def create(cls, items: list[str] | Var[list[str]], **props) -> Component:
  115. """Create a select component.
  116. Args:
  117. items: The items of the select.
  118. **props: Additional properties to apply to the select component.
  119. Returns:
  120. The select component.
  121. """
  122. trigger_prop_list = [
  123. "id",
  124. "placeholder",
  125. "variant",
  126. "radius",
  127. "width",
  128. "flex_shrink",
  129. "custom_attrs",
  130. ]
  131. content_props = {
  132. prop: props.pop(prop)
  133. for prop in ["high_contrast", "position"]
  134. if prop in props
  135. }
  136. trigger_props = {
  137. prop: props.pop(prop) for prop in trigger_prop_list if prop in props
  138. }
  139. color_scheme = props.pop("color_scheme", None)
  140. if color_scheme is not None:
  141. content_props["color_scheme"] = color_scheme
  142. trigger_props["color_scheme"] = color_scheme
  143. label = props.pop("label", None)
  144. if isinstance(items, Var):
  145. child = [
  146. rx.foreach(items, lambda item: SelectItem.create(item, value=item))
  147. ]
  148. else:
  149. child = [SelectItem.create(item, value=item) for item in items]
  150. return SelectRoot.create(
  151. SelectTrigger.create(
  152. **trigger_props,
  153. ),
  154. SelectContent.create(
  155. SelectGroup.create(
  156. SelectLabel.create(label) if label is not None else "",
  157. *child,
  158. ),
  159. **content_props,
  160. ),
  161. **props,
  162. )
  163. class Select(ComponentNamespace):
  164. """Select components namespace."""
  165. root = staticmethod(SelectRoot.create)
  166. trigger = staticmethod(SelectTrigger.create)
  167. content = staticmethod(SelectContent.create)
  168. group = staticmethod(SelectGroup.create)
  169. item = staticmethod(SelectItem.create)
  170. separator = staticmethod(SelectSeparator.create)
  171. label = staticmethod(SelectLabel.create)
  172. __call__ = staticmethod(HighLevelSelect.create)
  173. select = Select()