form.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  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.7"
  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. msg = f"FormControl can only have at most one child, got {len(children)} children"
  68. raise ValueError(msg)
  69. for child in children:
  70. if not isinstance(child, (TextFieldRoot, DebounceInput)):
  71. msg = "Only Radix TextFieldRoot and DebounceInput are allowed as children of FormControl"
  72. raise TypeError(msg)
  73. return super().create(*children, **props)
  74. LiteralMatcher = Literal[
  75. "badInput",
  76. "patternMismatch",
  77. "rangeOverflow",
  78. "rangeUnderflow",
  79. "stepMismatch",
  80. "tooLong",
  81. "tooShort",
  82. "typeMismatch",
  83. "valid",
  84. "valueMissing",
  85. ]
  86. class FormMessage(FormComponent):
  87. """A form message component."""
  88. tag = "Message"
  89. alias = "RadixFormMessage"
  90. # Used to target a specific field by name when rendering outside of a Field part.
  91. name: Var[str]
  92. # Used to indicate on which condition the message should be visible.
  93. match: Var[LiteralMatcher]
  94. # Forces the message to be shown. This is useful when using server-side validation.
  95. force_match: Var[bool]
  96. def add_style(self) -> dict[str, Any] | None:
  97. """Add style to the component.
  98. Returns:
  99. The style of the component.
  100. """
  101. return {"font_size": "13px", "opacity": "0.8", "color": "white"}
  102. class FormValidityState(FormComponent):
  103. """A form validity state component."""
  104. tag = "ValidityState"
  105. alias = "RadixFormValidityState"
  106. class FormSubmit(FormComponent):
  107. """A form submit component."""
  108. tag = "Submit"
  109. alias = "RadixFormSubmit"
  110. # This class is created mainly for reflex-web docs.
  111. class Form(FormRoot):
  112. """The Form component."""
  113. class FormNamespace(ComponentNamespace):
  114. """Form components."""
  115. root = staticmethod(FormRoot.create)
  116. control = staticmethod(FormControl.create)
  117. field = staticmethod(FormField.create)
  118. label = staticmethod(FormLabel.create)
  119. message = staticmethod(FormMessage.create)
  120. submit = staticmethod(FormSubmit.create)
  121. validity_state = staticmethod(FormValidityState.create)
  122. __call__ = staticmethod(Form.create)
  123. form = FormNamespace()