소스 검색

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",
             ),
             rx.heading("Default Upload"),
-            rx.upload(
+            rx.upload.root(
                 rx.vstack(
                     rx.button("Select File"),
                     rx.text("Drag and drop files here or click to select files"),
@@ -73,7 +73,7 @@ def UploadFile():
                 id="clear_button",
             ),
             rx.heading("Secondary Upload"),
-            rx.upload(
+            rx.upload.root(
                 rx.vstack(
                     rx.button("Select File"),
                     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,
 )
 from .upload import (
-    Upload,
+    UploadNamespace,
     cancel_upload,
     clear_selected_files,
     get_upload_dir,
@@ -31,4 +31,4 @@ debounce_input = DebounceInput.create
 foreach = Foreach.create
 html = Html.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.components.chakra.forms.input import Input
 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.event import (
     CallableEventSpec,
@@ -299,3 +299,38 @@ class Upload(MemoizationLeaf):
         return {
             (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.components.chakra.forms.input import Input
 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.event import (
     CallableEventSpec,
@@ -219,3 +219,201 @@ class Upload(MemoizationLeaf):
         """
         ...
     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 (
+    StyledUpload,
     Upload,
+    UploadNamespace,
     _on_drop_spec,  # type: ignore
     cancel_upload,
     get_upload_url,
@@ -77,3 +79,47 @@ def test_upload_create():
     )
     assert isinstance(up_comp_4, Upload)
     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
 
 
+@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
 def upload_component():
     """A test upload component function.
@@ -41,6 +59,47 @@ def 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):
     """Test that the render function is set correctly.
 
@@ -63,7 +122,7 @@ def test_upload_component_render(upload_component):
     [box] = upload["children"]
     assert box["name"] == "Box"
     assert box["props"] == [
-        'sx={{"border": "1px dotted black"}}',
+        'sx={{"border": "1px dotted black", "padding": "5em", "textAlign": "center"}}',
         "{...getRootProps()}",
     ]