瀏覽代碼

Refactor upload component and add styled upload component (#3035)

Ogidi Ifechukwu 1 年之前
父節點
當前提交
ce2bd2286e

+ 2 - 2
integration/test_upload.py

@@ -49,7 +49,7 @@ def UploadFile():
                 id="token",
                 id="token",
             ),
             ),
             rx.heading("Default Upload"),
             rx.heading("Default Upload"),
-            rx.upload(
+            rx.upload.root(
                 rx.vstack(
                 rx.vstack(
                     rx.button("Select File"),
                     rx.button("Select File"),
                     rx.text("Drag and drop files here or click to select files"),
                     rx.text("Drag and drop files here or click to select files"),
@@ -73,7 +73,7 @@ def UploadFile():
                 id="clear_button",
                 id="clear_button",
             ),
             ),
             rx.heading("Secondary Upload"),
             rx.heading("Secondary Upload"),
-            rx.upload(
+            rx.upload.root(
                 rx.vstack(
                 rx.vstack(
                     rx.button("Select File"),
                     rx.button("Select File"),
                     rx.text("Drag and drop files here or click to select files"),
                     rx.text("Drag and drop files here or click to select files"),

+ 2 - 2
reflex/components/core/__init__.py

@@ -16,7 +16,7 @@ from .responsive import (
     tablet_only,
     tablet_only,
 )
 )
 from .upload import (
 from .upload import (
-    Upload,
+    UploadNamespace,
     cancel_upload,
     cancel_upload,
     clear_selected_files,
     clear_selected_files,
     get_upload_dir,
     get_upload_dir,
@@ -31,4 +31,4 @@ debounce_input = DebounceInput.create
 foreach = Foreach.create
 foreach = Foreach.create
 html = Html.create
 html = Html.create
 match = Match.create
 match = Match.create
-upload = Upload.create
+upload = UploadNamespace()

+ 36 - 1
reflex/components/core/upload.py

@@ -9,7 +9,7 @@ from typing import Any, Callable, ClassVar, Dict, List, Optional, Union
 from reflex import constants
 from reflex import constants
 from reflex.components.chakra.forms.input import Input
 from reflex.components.chakra.forms.input import Input
 from reflex.components.chakra.layout.box import Box
 from reflex.components.chakra.layout.box import Box
-from reflex.components.component import Component, MemoizationLeaf
+from reflex.components.component import Component, ComponentNamespace, MemoizationLeaf
 from reflex.constants import Dirs
 from reflex.constants import Dirs
 from reflex.event import (
 from reflex.event import (
     CallableEventSpec,
     CallableEventSpec,
@@ -299,3 +299,38 @@ class Upload(MemoizationLeaf):
         return {
         return {
             (5, "UploadFilesProvider"): UploadFilesProvider.create(),
             (5, "UploadFilesProvider"): UploadFilesProvider.create(),
         }
         }
+
+
+class StyledUpload(Upload):
+    """The styled Upload Component."""
+
+    @classmethod
+    def create(cls, *children, **props) -> Component:
+        """Create the styled upload component.
+
+        Args:
+            *children: The children of the component.
+            **props: The properties of the component.
+
+        Returns:
+            The styled upload component.
+        """
+        # Set default props.
+        props.setdefault("border", "1px dashed var(--accent-12)")
+        props.setdefault("padding", "5em")
+        props.setdefault("textAlign", "center")
+
+        # Mark the Upload component as used in the app.
+        Upload.is_used = True
+
+        return super().create(
+            *children,
+            **props,
+        )
+
+
+class UploadNamespace(ComponentNamespace):
+    """Upload component namespace."""
+
+    root = Upload.create
+    __call__ = StyledUpload.create

+ 199 - 1
reflex/components/core/upload.pyi

@@ -13,7 +13,7 @@ from typing import Any, Callable, ClassVar, Dict, List, Optional, Union
 from reflex import constants
 from reflex import constants
 from reflex.components.chakra.forms.input import Input
 from reflex.components.chakra.forms.input import Input
 from reflex.components.chakra.layout.box import Box
 from reflex.components.chakra.layout.box import Box
-from reflex.components.component import Component, MemoizationLeaf
+from reflex.components.component import Component, ComponentNamespace, MemoizationLeaf
 from reflex.constants import Dirs
 from reflex.constants import Dirs
 from reflex.event import (
 from reflex.event import (
     CallableEventSpec,
     CallableEventSpec,
@@ -219,3 +219,201 @@ class Upload(MemoizationLeaf):
         """
         """
         ...
         ...
     def get_event_triggers(self) -> dict[str, Union[Var, Any]]: ...
     def get_event_triggers(self) -> dict[str, Union[Var, Any]]: ...
+
+class StyledUpload(Upload):
+    @overload
+    @classmethod
+    def create(  # type: ignore
+        cls,
+        *children,
+        accept: Optional[
+            Union[Var[Optional[Dict[str, List]]], Optional[Dict[str, List]]]
+        ] = None,
+        disabled: Optional[Union[Var[bool], bool]] = None,
+        max_files: Optional[Union[Var[int], int]] = None,
+        max_size: Optional[Union[Var[int], int]] = None,
+        min_size: Optional[Union[Var[int], int]] = None,
+        multiple: Optional[Union[Var[bool], bool]] = None,
+        no_click: Optional[Union[Var[bool], bool]] = None,
+        no_drag: Optional[Union[Var[bool], bool]] = None,
+        no_keyboard: Optional[Union[Var[bool], bool]] = 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_drop: 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
+    ) -> "StyledUpload":
+        """Create the styled upload component.
+
+        Args:
+            *children: The children of the component.
+            accept: The list of accepted file types. This should be a dictionary of MIME types as keys and array of file formats as  values.  supported MIME types: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
+            disabled: Whether the dropzone is disabled.
+            max_files: The maximum number of files that can be uploaded.
+            max_size: The maximum file size (bytes) that can be uploaded.
+            min_size: The minimum file size (bytes) that can be uploaded.
+            multiple: Whether to allow multiple files to be uploaded.
+            no_click: Whether to disable click to upload.
+            no_drag: Whether to disable drag and drop.
+            no_keyboard: Whether to disable using the space/enter keys to upload.
+            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 properties of the component.
+
+        Returns:
+            The styled upload component.
+        """
+        ...
+
+class UploadNamespace(ComponentNamespace):
+    root = Upload.create
+
+    @staticmethod
+    def __call__(
+        *children,
+        accept: Optional[
+            Union[Var[Optional[Dict[str, List]]], Optional[Dict[str, List]]]
+        ] = None,
+        disabled: Optional[Union[Var[bool], bool]] = None,
+        max_files: Optional[Union[Var[int], int]] = None,
+        max_size: Optional[Union[Var[int], int]] = None,
+        min_size: Optional[Union[Var[int], int]] = None,
+        multiple: Optional[Union[Var[bool], bool]] = None,
+        no_click: Optional[Union[Var[bool], bool]] = None,
+        no_drag: Optional[Union[Var[bool], bool]] = None,
+        no_keyboard: Optional[Union[Var[bool], bool]] = 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_drop: 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
+    ) -> "StyledUpload":
+        """Create the styled upload component.
+
+        Args:
+            *children: The children of the component.
+            accept: The list of accepted file types. This should be a dictionary of MIME types as keys and array of file formats as  values.  supported MIME types: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
+            disabled: Whether the dropzone is disabled.
+            max_files: The maximum number of files that can be uploaded.
+            max_size: The maximum file size (bytes) that can be uploaded.
+            min_size: The minimum file size (bytes) that can be uploaded.
+            multiple: Whether to allow multiple files to be uploaded.
+            no_click: Whether to disable click to upload.
+            no_drag: Whether to disable drag and drop.
+            no_keyboard: Whether to disable using the space/enter keys to upload.
+            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 properties of the component.
+
+        Returns:
+            The styled upload component.
+        """
+        ...

+ 46 - 0
tests/components/core/test_upload.py

@@ -1,5 +1,7 @@
 from reflex.components.core.upload import (
 from reflex.components.core.upload import (
+    StyledUpload,
     Upload,
     Upload,
+    UploadNamespace,
     _on_drop_spec,  # type: ignore
     _on_drop_spec,  # type: ignore
     cancel_upload,
     cancel_upload,
     get_upload_url,
     get_upload_url,
@@ -77,3 +79,47 @@ def test_upload_create():
     )
     )
     assert isinstance(up_comp_4, Upload)
     assert isinstance(up_comp_4, Upload)
     assert up_comp_4.is_used
     assert up_comp_4.is_used
+
+
+def test_styled_upload_create():
+    styled_up_comp_1 = StyledUpload.create()
+    assert isinstance(styled_up_comp_1, StyledUpload)
+    assert styled_up_comp_1.is_used
+
+    # reset is_used
+    StyledUpload.is_used = False
+
+    styled_up_comp_2 = StyledUpload.create(
+        id="foo_id",
+        on_drop=TestUploadState.drop_handler([]),  # type: ignore
+    )
+    assert isinstance(styled_up_comp_2, StyledUpload)
+    assert styled_up_comp_2.is_used
+
+    # reset is_used
+    StyledUpload.is_used = False
+
+    styled_up_comp_3 = StyledUpload.create(
+        id="foo_id",
+        on_drop=TestUploadState.drop_handler,
+    )
+    assert isinstance(styled_up_comp_3, StyledUpload)
+    assert styled_up_comp_3.is_used
+
+    # reset is_used
+    StyledUpload.is_used = False
+
+    styled_up_comp_4 = StyledUpload.create(
+        id="foo_id",
+        on_drop=TestUploadState.not_drop_handler([]),  # type: ignore
+    )
+    assert isinstance(styled_up_comp_4, StyledUpload)
+    assert styled_up_comp_4.is_used
+
+
+def test_upload_namespace():
+    up_ns = UploadNamespace()
+    assert isinstance(up_ns, UploadNamespace)
+
+    assert isinstance(up_ns(id="foo_id"), StyledUpload)
+    assert isinstance(up_ns.root(id="foo_id"), Upload)

+ 60 - 1
tests/components/forms/test_uploads.py

@@ -3,6 +3,24 @@ import pytest
 import reflex as rx
 import reflex as rx
 
 
 
 
+@pytest.fixture
+def upload_root_component():
+    """A test upload component function.
+
+    Returns:
+        A test upload component function.
+    """
+
+    def upload_root_component():
+        return rx.upload.root(
+            rx.button("select file"),
+            rx.text("Drag and drop files here or click to select files"),
+            border="1px dotted black",
+        )
+
+    return upload_root_component()
+
+
 @pytest.fixture
 @pytest.fixture
 def upload_component():
 def upload_component():
     """A test upload component function.
     """A test upload component function.
@@ -41,6 +59,47 @@ def upload_component_with_props():
     return upload_component_with_props()
     return upload_component_with_props()
 
 
 
 
+def test_upload_root_component_render(upload_root_component):
+    """Test that the render function is set correctly.
+
+    Args:
+        upload_root_component: component fixture
+    """
+    upload = upload_root_component.render()
+
+    # upload
+    assert upload["name"] == "ReactDropzone"
+    assert upload["props"] == [
+        "id={`default`}",
+        "multiple={true}",
+        "onDrop={e => setFilesById(filesById => ({...filesById, default: e}))}",
+        "ref={ref_default}",
+    ]
+    assert upload["args"] == ("getRootProps", "getInputProps")
+
+    # box inside of upload
+    [box] = upload["children"]
+    assert box["name"] == "Box"
+    assert box["props"] == [
+        'sx={{"border": "1px dotted black"}}',
+        "{...getRootProps()}",
+    ]
+
+    # input, button and text inside of box
+    [input, button, text] = box["children"]
+    assert input["name"] == "Input"
+    assert input["props"] == ["type={`file`}", "{...getInputProps()}"]
+
+    assert button["name"] == "RadixThemesButton"
+    assert button["children"][0]["contents"] == "{`select file`}"
+
+    assert text["name"] == "RadixThemesText"
+    assert (
+        text["children"][0]["contents"]
+        == "{`Drag and drop files here or click to select files`}"
+    )
+
+
 def test_upload_component_render(upload_component):
 def test_upload_component_render(upload_component):
     """Test that the render function is set correctly.
     """Test that the render function is set correctly.
 
 
@@ -63,7 +122,7 @@ def test_upload_component_render(upload_component):
     [box] = upload["children"]
     [box] = upload["children"]
     assert box["name"] == "Box"
     assert box["name"] == "Box"
     assert box["props"] == [
     assert box["props"] == [
-        'sx={{"border": "1px dotted black"}}',
+        'sx={{"border": "1px dotted black", "padding": "5em", "textAlign": "center"}}',
         "{...getRootProps()}",
         "{...getRootProps()}",
     ]
     ]