form.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. """Radix form component."""
  2. from __future__ import annotations
  3. from typing import Any, Literal
  4. from reflex.components.component import ComponentNamespace
  5. from reflex.components.core.debounce import DebounceInput
  6. from reflex.components.el.elements.forms import Form as HTMLForm
  7. from reflex.components.radix.themes.components.text_field import TextFieldRoot
  8. from reflex.event import EventHandler, no_args_event_spec
  9. from reflex.vars.base import Var
  10. from .base import RadixPrimitiveComponentWithClassName
  11. class FormComponent(RadixPrimitiveComponentWithClassName):
  12. """Base class for all @radix-ui/react-form components."""
  13. library = "@radix-ui/react-form@^0.1.0"
  14. class FormRoot(FormComponent, HTMLForm):
  15. """The root component of a radix form."""
  16. tag = "Root"
  17. alias = "RadixFormRoot"
  18. # Fired when the errors are cleared.
  19. on_clear_server_errors: EventHandler[no_args_event_spec]
  20. def add_style(self) -> dict[str, Any] | None:
  21. """Add style to the component.
  22. Returns:
  23. The style of the component.
  24. """
  25. return {"width": "100%"}
  26. class FormField(FormComponent):
  27. """A form field component."""
  28. tag = "Field"
  29. alias = "RadixFormField"
  30. # The name of the form field, that is passed down to the control and used to match with validation messages.
  31. name: Var[str]
  32. # Flag to mark the form field as invalid, for server side validation.
  33. server_invalid: Var[bool]
  34. def add_style(self) -> dict[str, Any] | None:
  35. """Add style to the component.
  36. Returns:
  37. The style of the component.
  38. """
  39. return {"display": "grid", "margin_bottom": "10px"}
  40. class FormLabel(FormComponent):
  41. """A form label component."""
  42. tag = "Label"
  43. alias = "RadixFormLabel"
  44. def add_style(self) -> dict[str, Any] | None:
  45. """Add style to the component.
  46. Returns:
  47. The style of the component.
  48. """
  49. return {"font_size": "15px", "font_weight": "500", "line_height": "35px"}
  50. class FormControl(FormComponent):
  51. """A form control component."""
  52. tag = "Control"
  53. alias = "RadixFormControl"
  54. @classmethod
  55. def create(cls, *children, **props):
  56. """Create a Form Control component.
  57. Args:
  58. *children: The children of the form.
  59. **props: The properties of the form.
  60. Raises:
  61. ValueError: If the number of children is greater than 1.
  62. TypeError: If a child exists but it is not a TextFieldInput.
  63. Returns:
  64. The form control component.
  65. """
  66. if len(children) > 1:
  67. raise ValueError(
  68. f"FormControl can only have at most one child, got {len(children)} children"
  69. )
  70. for child in children:
  71. if not isinstance(child, (TextFieldRoot, DebounceInput)):
  72. raise TypeError(
  73. "Only Radix TextFieldRoot and DebounceInput are allowed as children of FormControl"
  74. )
  75. return super().create(*children, **props)
  76. LiteralMatcher = Literal[
  77. "badInput",
  78. "patternMismatch",
  79. "rangeOverflow",
  80. "rangeUnderflow",
  81. "stepMismatch",
  82. "tooLong",
  83. "tooShort",
  84. "typeMismatch",
  85. "valid",
  86. "valueMissing",
  87. ]
  88. class FormMessage(FormComponent):
  89. """A form message component."""
  90. tag = "Message"
  91. alias = "RadixFormMessage"
  92. # Used to target a specific field by name when rendering outside of a Field part.
  93. name: Var[str]
  94. # Used to indicate on which condition the message should be visible.
  95. match: Var[LiteralMatcher]
  96. # Forces the message to be shown. This is useful when using server-side validation.
  97. force_match: Var[bool]
  98. def add_style(self) -> dict[str, Any] | None:
  99. """Add style to the component.
  100. Returns:
  101. The style of the component.
  102. """
  103. return {"font_size": "13px", "opacity": "0.8", "color": "white"}
  104. class FormValidityState(FormComponent):
  105. """A form validity state component."""
  106. tag = "ValidityState"
  107. alias = "RadixFormValidityState"
  108. class FormSubmit(FormComponent):
  109. """A form submit component."""
  110. tag = "Submit"
  111. alias = "RadixFormSubmit"
  112. # This class is created mainly for reflex-web docs.
  113. class Form(FormRoot):
  114. """The Form component."""
  115. pass
  116. class FormNamespace(ComponentNamespace):
  117. """Form components."""
  118. root = staticmethod(FormRoot.create)
  119. control = staticmethod(FormControl.create)
  120. field = staticmethod(FormField.create)
  121. label = staticmethod(FormLabel.create)
  122. message = staticmethod(FormMessage.create)
  123. submit = staticmethod(FormSubmit.create)
  124. validity_state = staticmethod(FormValidityState.create)
  125. __call__ = staticmethod(Form.create)
  126. form = FormNamespace()