list.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. """List components."""
  2. from __future__ import annotations
  3. from collections.abc import Iterable
  4. from typing import Any, Literal
  5. from reflex.components.component import ComponentNamespace
  6. from reflex.components.core.foreach import Foreach
  7. from reflex.components.el.elements.base import BaseHTML
  8. from reflex.components.el.elements.typography import Li, Ol, Ul
  9. from reflex.components.lucide.icon import Icon
  10. from reflex.components.markdown.markdown import MarkdownComponentMap
  11. from reflex.components.radix.themes.typography.text import Text
  12. from reflex.vars.base import Var
  13. LiteralListStyleTypeUnordered = Literal[
  14. "none",
  15. "disc",
  16. "circle",
  17. "square",
  18. ]
  19. LiteralListStyleTypeOrdered = Literal[
  20. "none",
  21. "decimal",
  22. "decimal-leading-zero",
  23. "lower-roman",
  24. "upper-roman",
  25. "lower-greek",
  26. "lower-latin",
  27. "upper-latin",
  28. "armenian",
  29. "georgian",
  30. "lower-alpha",
  31. "upper-alpha",
  32. "hiragana",
  33. "katakana",
  34. ]
  35. class BaseList(BaseHTML, MarkdownComponentMap):
  36. """Base class for ordered and unordered lists."""
  37. tag = "ul"
  38. # The style of the list. Default to "none".
  39. list_style_type: Var[
  40. LiteralListStyleTypeUnordered | LiteralListStyleTypeOrdered
  41. ] = Var.create("none")
  42. # A list of items to add to the list.
  43. items: Var[Iterable] = Var.create([])
  44. @classmethod
  45. def create(
  46. cls,
  47. *children,
  48. **props,
  49. ):
  50. """Create a list component.
  51. Args:
  52. *children: The children of the component.
  53. **props: The properties of the component.
  54. Returns:
  55. The list component.
  56. """
  57. items = props.pop("items", None)
  58. list_style_type = props.pop("list_style_type", "none")
  59. if not children and items is not None:
  60. if isinstance(items, Var):
  61. children = [Foreach.create(items, ListItem.create)]
  62. else:
  63. children = [ListItem.create(item) for item in items]
  64. props["direction"] = "column"
  65. style = props.setdefault("style", {})
  66. style["list_style_type"] = list_style_type
  67. if "gap" in props:
  68. style["gap"] = props["gap"]
  69. return super().create(*children, **props)
  70. def add_style(self) -> dict[str, Any] | None:
  71. """Add style to the component.
  72. Returns:
  73. The style of the component.
  74. """
  75. return {
  76. "direction": "column",
  77. }
  78. def _exclude_props(self) -> list[str]:
  79. return ["items", "list_style_type"]
  80. class UnorderedList(BaseList, Ul):
  81. """Display an unordered list."""
  82. tag = "ul"
  83. @classmethod
  84. def create(
  85. cls,
  86. *children,
  87. **props,
  88. ):
  89. """Create an unordered list component.
  90. Args:
  91. *children: The children of the component.
  92. **props: The properties of the component.
  93. Returns:
  94. The list component.
  95. """
  96. items = props.pop("items", None)
  97. list_style_type = props.pop("list_style_type", "disc")
  98. props["margin_left"] = props.get("margin_left", "1.5rem")
  99. return super().create(
  100. *children, items=items, list_style_type=list_style_type, **props
  101. )
  102. class OrderedList(BaseList, Ol):
  103. """Display an ordered list."""
  104. tag = "ol"
  105. @classmethod
  106. def create(
  107. cls,
  108. *children,
  109. **props,
  110. ):
  111. """Create an ordered list component.
  112. Args:
  113. *children: The children of the component.
  114. **props: The properties of the component.
  115. Returns:
  116. The list component.
  117. """
  118. items = props.pop("items", None)
  119. list_style_type = props.pop("list_style_type", "decimal")
  120. props["margin_left"] = props.get("margin_left", "1.5rem")
  121. return super().create(
  122. *children, items=items, list_style_type=list_style_type, **props
  123. )
  124. class ListItem(Li, MarkdownComponentMap):
  125. """Display an item of an ordered or unordered list."""
  126. @classmethod
  127. def create(cls, *children, **props):
  128. """Create a list item component.
  129. Args:
  130. *children: The children of the component.
  131. **props: The properties of the component.
  132. Returns:
  133. The list item component.
  134. """
  135. for child in children:
  136. if isinstance(child, Text):
  137. child.as_ = "span"
  138. elif isinstance(child, Icon) and "display" not in child.style:
  139. child.style["display"] = "inline"
  140. return super().create(*children, **props)
  141. class List(ComponentNamespace):
  142. """List components."""
  143. item = staticmethod(ListItem.create)
  144. ordered = staticmethod(OrderedList.create)
  145. unordered = staticmethod(UnorderedList.create)
  146. __call__ = staticmethod(BaseList.create)
  147. list_ns = List()
  148. list_item = list_ns.item
  149. ordered_list = list_ns.ordered
  150. unordered_list = list_ns.unordered
  151. def __getattr__(name: Any):
  152. # special case for when accessing list to avoid shadowing
  153. # python's built in list object.
  154. if name == "list":
  155. return list_ns
  156. try:
  157. return globals()[name]
  158. except KeyError:
  159. raise AttributeError(f"module '{__name__} has no attribute '{name}'") from None