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

This commit is contained in:
Ogidi Ifechukwu 2024-04-24 21:45:22 +01:00 committed by GitHub
parent cd1a30d758
commit ce2bd2286e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 347 additions and 9 deletions

View File

@ -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"),

View File

@ -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()

View File

@ -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

View File

@ -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.
"""
...

View File

@ -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)

View File

@ -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,13 +59,13 @@ def upload_component_with_props():
return upload_component_with_props()
def test_upload_component_render(upload_component):
def test_upload_root_component_render(upload_root_component):
"""Test that the render function is set correctly.
Args:
upload_component: component fixture
upload_root_component: component fixture
"""
upload = upload_component.render()
upload = upload_root_component.render()
# upload
assert upload["name"] == "ReactDropzone"
@ -82,6 +100,47 @@ def test_upload_component_render(upload_component):
)
def test_upload_component_render(upload_component):
"""Test that the render function is set correctly.
Args:
upload_component: component fixture
"""
upload = upload_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", "padding": "5em", "textAlign": "center"}}',
"{...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_with_props_render(upload_component_with_props):
"""Test that the render function is set correctly.