Explorar o código

add next/image and next-video in next namespace (#2223)

Thomas Brandého hai 1 ano
pai
achega
caf32605ca

+ 29 - 6
reflex/__init__.py

@@ -4,10 +4,13 @@ Anything imported here will be available in the default Reflex import as `rx.*`.
 To signal to typecheckers that something should be reexported,
 To signal to typecheckers that something should be reexported,
 we use the Flask "import name as name" syntax.
 we use the Flask "import name as name" syntax.
 """
 """
+from __future__ import annotations
+
 import importlib
 import importlib
 from typing import Type
 from typing import Type
 
 
 from reflex.page import page as page
 from reflex.page import page as page
+from reflex.utils import console
 from reflex.utils.format import to_snake_case
 from reflex.utils.format import to_snake_case
 
 
 _ALL_COMPONENTS = [
 _ALL_COMPONENTS = [
@@ -181,10 +184,6 @@ _ALL_COMPONENTS = [
     "StatGroup",
     "StatGroup",
     "StatHelpText",
     "StatHelpText",
     "StatLabel",
     "StatLabel",
-    "StatArrow",
-    "StatGroup",
-    "StatHelpText",
-    "StatLabel",
     "StatNumber",
     "StatNumber",
     "Step",
     "Step",
     "StepDescription",
     "StepDescription",
@@ -250,7 +249,7 @@ _MAPPING = {
     "reflex.base": ["base", "Base"],
     "reflex.base": ["base", "Base"],
     "reflex.compiler": ["compiler"],
     "reflex.compiler": ["compiler"],
     "reflex.compiler.utils": ["get_asset_path"],
     "reflex.compiler.utils": ["get_asset_path"],
-    "reflex.components": _ALL_COMPONENTS,
+    "reflex.components": _ALL_COMPONENTS + ["chakra", "next"],
     "reflex.components.component": ["memo"],
     "reflex.components.component": ["memo"],
     "reflex.components.graphing": ["recharts"],
     "reflex.components.graphing": ["recharts"],
     "reflex.components.datadisplay.moment": ["MomentDelta"],
     "reflex.components.datadisplay.moment": ["MomentDelta"],
@@ -286,7 +285,31 @@ _MAPPING = {
     "reflex.utils": ["utils"],
     "reflex.utils": ["utils"],
     "reflex.vars": ["vars", "cached_var", "Var"],
     "reflex.vars": ["vars", "cached_var", "Var"],
 }
 }
-_MAPPING = {value: key for key, values in _MAPPING.items() for value in values}
+
+
+def _reverse_mapping(mapping: dict[str, list]) -> dict[str, str]:
+    """Reverse the mapping used to lazy loading, and check for conflicting name.
+
+    Args:
+        mapping: The mapping to reverse.
+
+    Returns:
+        The reversed mapping.
+    """
+    reversed_mapping = {}
+    for key, values in mapping.items():
+        for value in values:
+            if value not in reversed_mapping:
+                reversed_mapping[value] = key
+            else:
+                console.warn(
+                    f"Key {value} is present multiple times in the imports _MAPPING: {key} / {reversed_mapping[value]}"
+                )
+    return reversed_mapping
+
+
+# _MAPPING = {value: key for key, values in _MAPPING.items() for value in values}
+_MAPPING = _reverse_mapping(_MAPPING)
 
 
 
 
 def _removeprefix(text, prefix):
 def _removeprefix(text, prefix):

+ 2 - 0
reflex/__init__.pyi

@@ -440,6 +440,8 @@ from reflex.components import clear_selected_files as clear_selected_files
 from reflex.components import EditorButtonList as EditorButtonList
 from reflex.components import EditorButtonList as EditorButtonList
 from reflex.components import EditorOptions as EditorOptions
 from reflex.components import EditorOptions as EditorOptions
 from reflex.components import NoSSRComponent as NoSSRComponent
 from reflex.components import NoSSRComponent as NoSSRComponent
+from reflex.components import chakra as chakra
+from reflex.components import next as next
 from reflex.components.component import memo as memo
 from reflex.components.component import memo as memo
 from reflex.components.graphing import recharts as recharts
 from reflex.components.graphing import recharts as recharts
 from reflex.components.datadisplay.moment import MomentDelta as MomentDelta
 from reflex.components.datadisplay.moment import MomentDelta as MomentDelta

+ 2 - 0
reflex/components/__init__.py

@@ -1,7 +1,9 @@
 """Import all the components."""
 """Import all the components."""
 from __future__ import annotations
 from __future__ import annotations
 
 
+from . import next as next
 from .base import Script
 from .base import Script
+from .chakra import *
 from .component import Component
 from .component import Component
 from .component import NoSSRComponent as NoSSRComponent
 from .component import NoSSRComponent as NoSSRComponent
 from .datadisplay import *
 from .datadisplay import *

+ 23 - 0
reflex/components/chakra/__init__.py

@@ -0,0 +1,23 @@
+"""Chakra components."""
+
+
+import importlib
+from typing import Type
+
+from .media.image import Image
+
+image = Image.create
+
+# _MAPPING = {
+#     "image": "media",
+# }
+
+
+# def __getattr__(name: str) -> Type:
+#     print(f"importing {name}")
+#     if name not in _MAPPING:
+#         return importlib.import_module(name)
+
+#     module = importlib.import_module(_MAPPING[name], package=".")
+
+#     return getattr(module, name) if name != _MAPPING[name].rsplit(".")[-1] else module

+ 1 - 28
reflex/components/media/image.py → reflex/components/chakra/media/image.py

@@ -1,13 +1,10 @@
 """An image component."""
 """An image component."""
 from __future__ import annotations
 from __future__ import annotations
 
 
-import base64
-import io
 from typing import Any, Optional, Union
 from typing import Any, Optional, Union
 
 
 from reflex.components.component import Component
 from reflex.components.component import Component
 from reflex.components.libs.chakra import ChakraComponent, LiteralImageLoading
 from reflex.components.libs.chakra import ChakraComponent, LiteralImageLoading
-from reflex.utils.serializers import serializer
 from reflex.vars import Var
 from reflex.vars import Var
 
 
 
 
@@ -15,7 +12,7 @@ class Image(ChakraComponent):
     """Display an image."""
     """Display an image."""
 
 
     tag = "Image"
     tag = "Image"
-
+    alias = "ChakraImage"
     # How to align the image within its bounds. It maps to css `object-position` property.
     # How to align the image within its bounds. It maps to css `object-position` property.
     align: Var[str]
     align: Var[str]
 
 
@@ -79,27 +76,3 @@ class Image(ChakraComponent):
         if src is not None and not isinstance(src, (Var)):
         if src is not None and not isinstance(src, (Var)):
             props["src"] = Var.create(value=src, _var_is_string=True)
             props["src"] = Var.create(value=src, _var_is_string=True)
         return super().create(*children, **props)
         return super().create(*children, **props)
-
-
-try:
-    from PIL.Image import Image as Img
-
-    @serializer
-    def serialize_image(image: Img) -> str:
-        """Serialize a plotly figure.
-
-        Args:
-            image: The image to serialize.
-
-        Returns:
-            The serialized image.
-        """
-        buff = io.BytesIO()
-        image.save(buff, format=getattr(image, "format", None) or "PNG")
-        image_bytes = buff.getvalue()
-        base64_image = base64.b64encode(image_bytes).decode("utf-8")
-        mime_type = getattr(image, "get_format_mimetype", lambda: "image/png")()
-        return f"data:{mime_type};base64,{base64_image}"
-
-except ImportError:
-    pass

+ 120 - 0
reflex/components/chakra/media/image.pyi

@@ -0,0 +1,120 @@
+"""Stub file for reflex/components/chakra/media/image.py"""
+# ------------------- DO NOT EDIT ----------------------
+# This file was generated by `scripts/pyi_generator.py`!
+# ------------------------------------------------------
+
+from typing import Any, Dict, Literal, Optional, Union, overload
+from reflex.vars import Var, BaseVar, ComputedVar
+from reflex.event import EventChain, EventHandler, EventSpec
+from reflex.style import Style
+from typing import Any, Optional, Union
+from reflex.components.component import Component
+from reflex.components.libs.chakra import ChakraComponent, LiteralImageLoading
+from reflex.vars import Var
+
+class Image(ChakraComponent):
+    def get_event_triggers(self) -> dict[str, Union[Var, Any]]: ...
+    @overload
+    @classmethod
+    def create(  # type: ignore
+        cls,
+        *children,
+        align: Optional[Union[Var[str], str]] = None,
+        fallback: Optional[Component] = None,
+        fallback_src: Optional[Union[Var[str], str]] = None,
+        fit: Optional[Union[Var[str], str]] = None,
+        html_height: Optional[Union[Var[str], str]] = None,
+        html_width: Optional[Union[Var[str], str]] = None,
+        ignore_fallback: Optional[Union[Var[bool], bool]] = None,
+        loading: Optional[
+            Union[Var[Literal["eager", "lazy"]], Literal["eager", "lazy"]]
+        ] = None,
+        src: Optional[Union[Var[Any], Any]] = None,
+        alt: Optional[Union[Var[str], str]] = None,
+        src_set: Optional[Union[Var[str], str]] = None,
+        style: Optional[Style] = None,
+        key: Optional[Any] = None,
+        id: Optional[Any] = None,
+        class_name: Optional[Any] = None,
+        autofocus: Optional[bool] = None,
+        custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
+        on_blur: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_click: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_context_menu: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_double_click: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_error: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_focus: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_load: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mount: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_down: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_enter: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_leave: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_move: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_out: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_over: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_up: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_scroll: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_unmount: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        **props
+    ) -> "Image":
+        """Create an Image component.
+
+        Args:
+            *children: The children of the image.
+            align: How to align the image within its bounds. It maps to css `object-position` property.
+            fallback: Fallback Reflex component to show if image is loading or image fails.
+            fallback_src: Fallback image src to show if image is loading or image fails.
+            fit: How the image to fit within its bounds. It maps to css `object-fit` property.
+            html_height: The native HTML height attribute to the passed to the img.
+            html_width: The native HTML width attribute to the passed to the img.
+            ignore_fallback: If true, opt out of the fallbackSrc logic and use as img.
+            loading: "eager" | "lazy"
+            src: The path/url to the image or PIL image object.
+            alt: The alt text of the image.
+            src_set: Provide multiple sources for an image, allowing the browser  to select the most appropriate source based on factors like  screen resolution and device capabilities.  Learn more _[here](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images)_
+            style: The style of the component.
+            key: A unique key for the component.
+            id: The id for the component.
+            class_name: The class name for the component.
+            autofocus: Whether the component should take the focus once the page is loaded
+            custom_attrs: custom attribute
+            **props: The props of the image.
+
+        Returns:
+            The Image component.
+        """
+        ...

+ 4 - 0
reflex/components/component.py

@@ -785,6 +785,10 @@ class Component(BaseComponent, ABC):
         for child in self.children:
         for child in self.children:
             dynamic_imports |= child.get_dynamic_imports()
             dynamic_imports |= child.get_dynamic_imports()
 
 
+        for prop in self.get_component_props():
+            if getattr(self, prop) is not None:
+                dynamic_imports |= getattr(self, prop).get_dynamic_imports()
+
         # Return the dynamic imports
         # Return the dynamic imports
         return dynamic_imports
         return dynamic_imports
 
 

+ 0 - 1
reflex/components/media/__init__.py

@@ -3,7 +3,6 @@
 from .audio import Audio
 from .audio import Audio
 from .avatar import Avatar, AvatarBadge, AvatarGroup
 from .avatar import Avatar, AvatarBadge, AvatarGroup
 from .icon import Icon
 from .icon import Icon
-from .image import Image
 from .video import Video
 from .video import Video
 
 
 __all__ = [f for f in dir() if f[0].isupper()]  # type: ignore
 __all__ = [f for f in dir() if f[0].isupper()]  # type: ignore

+ 8 - 0
reflex/components/next/__init__.py

@@ -0,0 +1,8 @@
+"""Namespace for components provided by next packages."""
+
+from .base import NextComponent
+from .image import Image
+from .video import Video
+
+image = Image.create
+video = Video.create

+ 8 - 0
reflex/components/next/base.py

@@ -0,0 +1,8 @@
+"""Base for NextJS components."""
+from reflex.components.component import Component
+
+
+class NextComponent(Component):
+    """A Component used as based for any NextJS component."""
+
+    ...

+ 91 - 0
reflex/components/next/base.pyi

@@ -0,0 +1,91 @@
+"""Stub file for reflex/components/next/base.py"""
+# ------------------- DO NOT EDIT ----------------------
+# This file was generated by `scripts/pyi_generator.py`!
+# ------------------------------------------------------
+
+from typing import Any, Dict, Literal, Optional, Union, overload
+from reflex.vars import Var, BaseVar, ComputedVar
+from reflex.event import EventChain, EventHandler, EventSpec
+from reflex.style import Style
+from reflex.components.component import Component
+
+class NextComponent(Component):
+    ...
+
+    @overload
+    @classmethod
+    def create(  # type: ignore
+        cls,
+        *children,
+        style: Optional[Style] = None,
+        key: Optional[Any] = None,
+        id: Optional[Any] = None,
+        class_name: Optional[Any] = None,
+        autofocus: Optional[bool] = None,
+        custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
+        on_blur: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_click: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_context_menu: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_double_click: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_focus: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mount: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_down: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_enter: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_leave: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_move: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_out: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_over: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_up: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_scroll: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_unmount: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        **props
+    ) -> "NextComponent":
+        """Create the component.
+
+        Args:
+            *children: The children of the component.
+            style: The style of the component.
+            key: A unique key for the component.
+            id: The id for the component.
+            class_name: The class name for the component.
+            autofocus: Whether the component should take the focus once the page is loaded
+            custom_attrs: custom attribute
+            **props: The props of the component.
+
+        Returns:
+            The component.
+
+        Raises:
+            TypeError: If an invalid child is passed.
+        """
+        ...

+ 140 - 0
reflex/components/next/image.py

@@ -0,0 +1,140 @@
+"""Image component from next/image."""
+import base64
+import io
+from typing import Any, Dict, Literal, Optional, Union
+
+from reflex.utils import types
+from reflex.utils.serializers import serializer
+from reflex.vars import Var
+
+from .base import NextComponent
+
+
+class Image(NextComponent):
+    """Display an image."""
+
+    tag = "Image"
+    library = "next/image"
+    is_default = True
+
+    # This can be either an absolute external URL, or an internal path
+    src: Var[Any]
+
+    # Represents the rendered width in pixels, so it will affect how large the image appears.
+    width: Var[Any]
+
+    # Represents the rendered height in pixels, so it will affect how large the image appears.
+    height: Var[Any]
+
+    # Used to describe the image for screen readers and search engines.
+    alt: Var[str]
+
+    # A custom function used to resolve image URLs.
+    loader: Var[Any]
+
+    # A boolean that causes the image to fill the parent element, which is useful when the width and height are unknown. Default to True
+    fill: Var[bool]
+
+    # A string, similar to a media query, that provides information about how wide the image will be at different breakpoints.
+    sizes: Var[str]
+
+    # The quality of the optimized image, an integer between 1 and 100, where 100 is the best quality and therefore largest file size. Defaults to 75.
+    quality: Var[int]
+
+    # When true, the image will be considered high priority and preload. Lazy loading is automatically disabled for images using priority.
+    priority: Var[bool]
+
+    # A placeholder to use while the image is loading. Possible values are blur, empty, or data:image/.... Defaults to empty.
+    placeholder: Var[str]
+
+    # Allows passing CSS styles to the underlying image element.
+    # style: Var[Any]
+
+    # The loading behavior of the image. Defaults to lazy. Can hurt performance, recommended to use `priority` instead.
+    loading: Var[Literal["lazy", "eager"]]
+
+    # A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur".
+    blurDataURL: Var[str]
+
+    def get_event_triggers(self) -> Dict[str, Any]:
+        """The event triggers of the component.
+
+        Returns:
+            The dict describing the event triggers.
+        """
+        return {
+            **super().get_event_triggers(),
+            "on_load": lambda: [],
+            "on_error": lambda: [],
+        }
+
+    @classmethod
+    def create(
+        cls,
+        *children,
+        width: Optional[Union[int, str]] = None,
+        height: Optional[Union[int, str]] = None,
+        **props,
+    ):
+        """Create an Image component from next/image.
+
+        Args:
+            *children: The children of the component.
+            width: The width of the image.
+            height: The height of the image.
+            **props:The props of the component.
+
+        Returns:
+            _type_: _description_
+        """
+        style = props.get("style", {})
+        DEFAULT_W_H = "100%"
+
+        def check_prop_type(prop_name, prop_value):
+            if types.check_prop_in_allowed_types(prop_value, allowed_types=[int]):
+                props[prop_name] = prop_value
+
+            elif types.check_prop_in_allowed_types(prop_value, allowed_types=[str]):
+                props[prop_name] = 0
+                style[prop_name] = prop_value
+            else:
+                props[prop_name] = 0
+                style[prop_name] = DEFAULT_W_H
+
+        check_prop_type("width", width)
+        check_prop_type("height", height)
+
+        props["style"] = style
+
+        # mysteriously, following `sizes` prop is needed to avoid blury images.
+        props["sizes"] = "100vw"
+
+        src = props.get("src", None)
+        if src is not None and not isinstance(src, (Var)):
+            props["src"] = Var.create(value=src, _var_is_string=True)
+
+        return super().create(*children, **props)
+
+
+try:
+    from PIL.Image import Image as Img
+
+    @serializer
+    def serialize_image(image: Img) -> str:
+        """Serialize a plotly figure.
+
+        Args:
+            image: The image to serialize.
+
+        Returns:
+            The serialized image.
+        """
+        buff = io.BytesIO()
+        image.save(buff, format=getattr(image, "format", None) or "PNG")
+        image_bytes = buff.getvalue()
+        base64_image = base64.b64encode(image_bytes).decode("utf-8")
+        mime_type = getattr(image, "get_format_mimetype", lambda: "image/png")()
+        return f"data:{mime_type};base64,{base64_image}"
+
+except ImportError:
+    pass

+ 134 - 0
reflex/components/next/image.pyi

@@ -0,0 +1,134 @@
+"""Stub file for reflex/components/next/image.py"""
+# ------------------- DO NOT EDIT ----------------------
+# This file was generated by `scripts/pyi_generator.py`!
+# ------------------------------------------------------
+
+from typing import Any, Dict, Literal, Optional, Union, overload
+from reflex.vars import Var, BaseVar, ComputedVar
+from reflex.event import EventChain, EventHandler, EventSpec
+from reflex.style import Style
+import base64
+import io
+from typing import Any, Dict, Literal, Optional, Union
+from reflex.utils import types
+from reflex.utils.serializers import serializer
+from reflex.vars import Var
+from .base import NextComponent
+
+class Image(NextComponent):
+    def get_event_triggers(self) -> Dict[str, Any]: ...
+    @overload
+    @classmethod
+    def create(  # type: ignore
+        cls,
+        *children,
+        width: Optional[Union[str, int]] = None,
+        height: Optional[Union[str, int]] = None,
+        src: Optional[Union[Var[Any], Any]] = None,
+        alt: Optional[Union[Var[str], str]] = None,
+        loader: Optional[Union[Var[Any], Any]] = None,
+        fill: Optional[Union[Var[bool], bool]] = None,
+        sizes: Optional[Union[Var[str], str]] = None,
+        quality: Optional[Union[Var[int], int]] = None,
+        priority: Optional[Union[Var[bool], bool]] = None,
+        placeholder: Optional[Union[Var[str], str]] = None,
+        loading: Optional[
+            Union[Var[Literal["lazy", "eager"]], Literal["lazy", "eager"]]
+        ] = None,
+        blurDataURL: Optional[Union[Var[str], str]] = None,
+        style: Optional[Style] = None,
+        key: Optional[Any] = None,
+        id: Optional[Any] = None,
+        class_name: Optional[Any] = None,
+        autofocus: Optional[bool] = None,
+        custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
+        on_blur: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_click: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_context_menu: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_double_click: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_error: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_focus: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_load: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mount: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_down: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_enter: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_leave: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_move: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_out: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_over: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_up: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_scroll: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_unmount: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        **props
+    ) -> "Image":
+        """Create an Image component from next/image.
+
+        Args:
+            *children: The children of the component.
+            width: The width of the image.
+            height: The height of the image.
+            src: This can be either an absolute external URL, or an internal path
+            alt: Used to describe the image for screen readers and search engines.
+            loader: A custom function used to resolve image URLs.
+            fill: A boolean that causes the image to fill the parent element, which is useful when the width and height are unknown. Default to True
+            sizes: A string, similar to a media query, that provides information about how wide the image will be at different breakpoints.
+            quality: The quality of the optimized image, an integer between 1 and 100, where 100 is the best quality and therefore largest file size. Defaults to 75.
+            priority: When true, the image will be considered high priority and preload. Lazy loading is automatically disabled for images using priority.
+            placeholder: A placeholder to use while the image is loading. Possible values are blur, empty, or data:image/.... Defaults to empty.
+            loading: Allows passing CSS styles to the underlying image element.  style: Var[Any]  The loading behavior of the image. Defaults to lazy. Can hurt performance, recommended to use `priority` instead.
+            blurDataURL: A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur".
+            style: The style of the component.
+            key: A unique key for the component.
+            id: The id for the component.
+            class_name: The class name for the component.
+            autofocus: Whether the component should take the focus once the page is loaded
+            custom_attrs: custom attribute
+            **props:The props of the component.
+
+        Returns:
+            _type_: _description_
+        """
+        ...
+
+try:
+    from PIL.Image import Image as Img
+
+    @serializer
+    def serialize_image(image: Img) -> str: ...
+
+except ImportError:
+    pass

+ 33 - 0
reflex/components/next/video.py

@@ -0,0 +1,33 @@
+"""Wrapping of the next-video component."""
+
+from typing import Optional
+
+from reflex.components.component import Component
+from reflex.vars import Var
+
+from .base import NextComponent
+
+
+class Video(NextComponent):
+    """A video component from NextJS."""
+
+    tag = "Video"
+    library = "next-video"
+    is_default = True
+    # the URL
+    src: Var[str]
+
+    as_: Optional[Component]
+
+    @classmethod
+    def create(cls, *children, **props) -> NextComponent:
+        """Create a Video component.
+
+        Args:
+            *children: The children of the component.
+            **props: The props of the component.
+
+        Returns:
+            The Video component.
+        """
+        return super().create(*children, **props)

+ 92 - 0
reflex/components/next/video.pyi

@@ -0,0 +1,92 @@
+"""Stub file for reflex/components/next/video.py"""
+# ------------------- DO NOT EDIT ----------------------
+# This file was generated by `scripts/pyi_generator.py`!
+# ------------------------------------------------------
+
+from typing import Any, Dict, Literal, Optional, Union, overload
+from reflex.vars import Var, BaseVar, ComputedVar
+from reflex.event import EventChain, EventHandler, EventSpec
+from reflex.style import Style
+from typing import Optional
+from reflex.components.component import Component
+from reflex.vars import Var
+from .base import NextComponent
+
+class Video(NextComponent):
+    @overload
+    @classmethod
+    def create(  # type: ignore
+        cls,
+        *children,
+        src: Optional[Union[Var[str], str]] = None,
+        as_: Optional[Component] = None,
+        style: Optional[Style] = None,
+        key: Optional[Any] = None,
+        id: Optional[Any] = None,
+        class_name: Optional[Any] = None,
+        autofocus: Optional[bool] = None,
+        custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
+        on_blur: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_click: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_context_menu: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_double_click: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_focus: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mount: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_down: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_enter: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_leave: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_move: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_out: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_over: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_up: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_scroll: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_unmount: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        **props
+    ) -> "Video":
+        """Create a Video component.
+
+        Args:
+            *children: The children of the component.
+            src: the URL
+            style: The style of the component.
+            key: A unique key for the component.
+            id: The id for the component.
+            class_name: The class name for the component.
+            autofocus: Whether the component should take the focus once the page is loaded
+            custom_attrs: custom attribute
+            **props: The props of the component.
+
+        Returns:
+            The Video component.
+        """
+        ...

+ 17 - 0
reflex/utils/types.py

@@ -272,6 +272,23 @@ def check_type_in_allowed_types(value_type: Type, allowed_types: Iterable) -> bo
     return get_base_class(value_type) in allowed_types
     return get_base_class(value_type) in allowed_types
 
 
 
 
+def check_prop_in_allowed_types(prop: Any, allowed_types: Iterable) -> bool:
+    """Check that a prop value is in a list of allowed types.
+    Does the check in a way that works regardless if it's a raw value or a state Var.
+
+    Args:
+        prop: The prop to check.
+        allowed_types: The list of allowed types.
+
+    Returns:
+        If the prop type match one of the allowed_types.
+    """
+    from reflex.vars import Var
+
+    type_ = prop._var_type if _isinstance(prop, Var) else type(prop)
+    return type_ in allowed_types
+
+
 # Store this here for performance.
 # Store this here for performance.
 StateBases = get_base_class(StateVar)
 StateBases = get_base_class(StateVar)
 StateIterBases = get_base_class(StateIterVar)
 StateIterBases = get_base_class(StateIterVar)

+ 56 - 61
tests/components/media/test_image.py

@@ -1,63 +1,58 @@
+# PIL is only available in python 3.8+
+import numpy as np
+import PIL
 import pytest
 import pytest
+from PIL.Image import Image as Img
 
 
-try:
-    # PIL is only available in python 3.8+
-    import numpy as np
-    import PIL
-    from PIL.Image import Image as Img
-
-    import reflex as rx
-    from reflex.components.media.image import Image, serialize_image  # type: ignore
-    from reflex.utils.serializers import serialize
-
-    @pytest.fixture
-    def pil_image() -> Img:
-        """Get an image.
-
-        Returns:
-            A random PIL image.
-        """
-        imarray = np.random.rand(100, 100, 3) * 255
-        return PIL.Image.fromarray(imarray.astype("uint8")).convert("RGBA")  # type: ignore
-
-    def test_serialize_image(pil_image: Img):
-        """Test that serializing an image works.
-
-        Args:
-            pil_image: The image to serialize.
-        """
-        data = serialize(pil_image)
-        assert isinstance(data, str)
-        assert data == serialize_image(pil_image)
-        assert data.startswith("data:image/png;base64,")
-
-    def test_set_src_str():
-        """Test that setting the src works."""
-        image = rx.image(src="pic2.jpeg")
-        assert str(image.src) == "{`pic2.jpeg`}"  # type: ignore
-
-    def test_set_src_img(pil_image: Img):
-        """Test that setting the src works.
-
-        Args:
-            pil_image: The image to serialize.
-        """
-        image = Image.create(src=pil_image)
-        assert str(image.src._var_name) == serialize_image(pil_image)  # type: ignore
-
-    def test_render(pil_image: Img):
-        """Test that rendering an image works.
-
-        Args:
-            pil_image: The image to serialize.
-        """
-        image = Image.create(src=pil_image)
-        assert image.src._var_is_string  # type: ignore
-
-except ImportError:
-
-    def test_pillow_import():
-        """Make sure the Python version is less than 3.8."""
-        import sys
-
-        assert sys.version_info < (3, 8)
+import reflex as rx
+from reflex.components.next.image import Image, serialize_image  # type: ignore
+from reflex.utils.serializers import serialize
+
+
+@pytest.fixture
+def pil_image() -> Img:
+    """Get an image.
+
+    Returns:
+        A random PIL image.
+    """
+    imarray = np.random.rand(100, 100, 3) * 255
+    return PIL.Image.fromarray(imarray.astype("uint8")).convert("RGBA")  # type: ignore
+
+
+def test_serialize_image(pil_image: Img):
+    """Test that serializing an image works.
+
+    Args:
+        pil_image: The image to serialize.
+    """
+    data = serialize(pil_image)
+    assert isinstance(data, str)
+    assert data == serialize_image(pil_image)
+    assert data.startswith("data:image/png;base64,")
+
+
+def test_set_src_str():
+    """Test that setting the src works."""
+    image = rx.image(src="pic2.jpeg")
+    assert str(image.src) == "{`pic2.jpeg`}"  # type: ignore
+
+
+def test_set_src_img(pil_image: Img):
+    """Test that setting the src works.
+
+    Args:
+        pil_image: The image to serialize.
+    """
+    image = Image.create(src=pil_image)
+    assert str(image.src._var_name) == serialize_image(pil_image)  # type: ignore
+
+
+def test_render(pil_image: Img):
+    """Test that rendering an image works.
+
+    Args:
+        pil_image: The image to serialize.
+    """
+    image = Image.create(src=pil_image)
+    assert image.src._var_is_string  # type: ignore