Преглед изворни кода

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

Thomas Brandého пре 1 година
родитељ
комит
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,
 we use the Flask "import name as name" syntax.
 """
+from __future__ import annotations
+
 import importlib
 from typing import Type
 
 from reflex.page import page as page
+from reflex.utils import console
 from reflex.utils.format import to_snake_case
 
 _ALL_COMPONENTS = [
@@ -181,10 +184,6 @@ _ALL_COMPONENTS = [
     "StatGroup",
     "StatHelpText",
     "StatLabel",
-    "StatArrow",
-    "StatGroup",
-    "StatHelpText",
-    "StatLabel",
     "StatNumber",
     "Step",
     "StepDescription",
@@ -250,7 +249,7 @@ _MAPPING = {
     "reflex.base": ["base", "Base"],
     "reflex.compiler": ["compiler"],
     "reflex.compiler.utils": ["get_asset_path"],
-    "reflex.components": _ALL_COMPONENTS,
+    "reflex.components": _ALL_COMPONENTS + ["chakra", "next"],
     "reflex.components.component": ["memo"],
     "reflex.components.graphing": ["recharts"],
     "reflex.components.datadisplay.moment": ["MomentDelta"],
@@ -286,7 +285,31 @@ _MAPPING = {
     "reflex.utils": ["utils"],
     "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):

+ 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 EditorOptions as EditorOptions
 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.graphing import recharts as recharts
 from reflex.components.datadisplay.moment import MomentDelta as MomentDelta

+ 2 - 0
reflex/components/__init__.py

@@ -1,7 +1,9 @@
 """Import all the components."""
 from __future__ import annotations
 
+from . import next as next
 from .base import Script
+from .chakra import *
 from .component import Component
 from .component import NoSSRComponent as NoSSRComponent
 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."""
 from __future__ import annotations
 
-import base64
-import io
 from typing import Any, Optional, Union
 
 from reflex.components.component import Component
 from reflex.components.libs.chakra import ChakraComponent, LiteralImageLoading
-from reflex.utils.serializers import serializer
 from reflex.vars import Var
 
 
@@ -15,7 +12,7 @@ class Image(ChakraComponent):
     """Display an image."""
 
     tag = "Image"
-
+    alias = "ChakraImage"
     # How to align the image within its bounds. It maps to css `object-position` property.
     align: Var[str]
 
@@ -79,27 +76,3 @@ class Image(ChakraComponent):
         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

+ 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:
             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 dynamic_imports
 

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

@@ -3,7 +3,6 @@
 from .audio import Audio
 from .avatar import Avatar, AvatarBadge, AvatarGroup
 from .icon import Icon
-from .image import Image
 from .video import Video
 
 __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
 
 
+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.
 StateBases = get_base_class(StateVar)
 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
+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