Loading ...
",
+ ),
+ )
diff --git a/docs/examples/python/pyscript_component_multiple_files_root.py b/docs/examples/python/pyscript_component_multiple_files_root.py
new file mode 100644
index 00000000..fd826137
--- /dev/null
+++ b/docs/examples/python/pyscript_component_multiple_files_root.py
@@ -0,0 +1,13 @@
+from reactpy import component, html
+
+from reactpy_django.components import pyscript_component
+
+
+@component
+def server_side_component():
+ return html.div(
+ pyscript_component(
+ "./example_project/my_app/components/root.py",
+ "./example_project/my_app/components/child.py",
+ ),
+ )
diff --git a/docs/examples/python/pyscript_component_root.py b/docs/examples/python/pyscript_component_root.py
new file mode 100644
index 00000000..3d795247
--- /dev/null
+++ b/docs/examples/python/pyscript_component_root.py
@@ -0,0 +1,13 @@
+from reactpy import component, html
+
+from reactpy_django.components import pyscript_component
+
+
+@component
+def server_side_component():
+ return html.div(
+ pyscript_component(
+ "./example_project/my_app/components/main.py",
+ root="main",
+ ),
+ )
diff --git a/docs/examples/python/pyscript_ffi.py b/docs/examples/python/pyscript_ffi.py
new file mode 100644
index 00000000..d744dd88
--- /dev/null
+++ b/docs/examples/python/pyscript_ffi.py
@@ -0,0 +1,14 @@
+from pyscript import document, window
+from reactpy import component, html
+
+
+@component
+def root():
+ def on_click(event):
+ my_element = document.querySelector("#example")
+ my_element.innerText = window.location.hostname
+
+ return html.div(
+ {"id": "example"},
+ html.button({"onClick": on_click}, "Click Me!"),
+ )
diff --git a/docs/examples/python/pyscript_hello_world.py b/docs/examples/python/pyscript_hello_world.py
new file mode 100644
index 00000000..d5737421
--- /dev/null
+++ b/docs/examples/python/pyscript_hello_world.py
@@ -0,0 +1,6 @@
+from reactpy import component, html
+
+
+@component
+def root():
+ return html.div("Hello, World!")
diff --git a/docs/examples/python/pyscript_initial_object.py b/docs/examples/python/pyscript_initial_object.py
new file mode 100644
index 00000000..1742ff87
--- /dev/null
+++ b/docs/examples/python/pyscript_initial_object.py
@@ -0,0 +1,10 @@
+from django.shortcuts import render
+from reactpy import html
+
+
+def index(request):
+ return render(
+ request,
+ "my_template.html",
+ context={"my_initial_object": html.div("Loading ...")},
+ )
diff --git a/docs/examples/python/pyscript_local_import.py b/docs/examples/python/pyscript_local_import.py
new file mode 100644
index 00000000..221b5bae
--- /dev/null
+++ b/docs/examples/python/pyscript_local_import.py
@@ -0,0 +1,12 @@
+from reactpy import component, html
+
+
+@component
+def root():
+ from pyscript.js_modules import moment
+
+ return html.div(
+ {"id": "moment"},
+ "Using the JavaScript package 'moment' to calculate time: ",
+ moment.default().format("YYYY-MM-DD HH:mm:ss"),
+ )
diff --git a/docs/examples/python/pyscript_multiple_files_child.py b/docs/examples/python/pyscript_multiple_files_child.py
new file mode 100644
index 00000000..73dbb189
--- /dev/null
+++ b/docs/examples/python/pyscript_multiple_files_child.py
@@ -0,0 +1,6 @@
+from reactpy import component, html
+
+
+@component
+def child_component():
+ return html.div("This is a child component from a different file.")
diff --git a/docs/examples/python/pyscript_multiple_files_root.py b/docs/examples/python/pyscript_multiple_files_root.py
new file mode 100644
index 00000000..9ae8e549
--- /dev/null
+++ b/docs/examples/python/pyscript_multiple_files_root.py
@@ -0,0 +1,8 @@
+from reactpy import component, html
+
+from example.components import child_component
+
+
+@component
+def root():
+ return html.div("This text is from the root component.", child_component())
diff --git a/docs/examples/python/pyscript_root.py b/docs/examples/python/pyscript_root.py
new file mode 100644
index 00000000..f39fd01e
--- /dev/null
+++ b/docs/examples/python/pyscript_root.py
@@ -0,0 +1,6 @@
+from reactpy import component, html
+
+
+@component
+def main():
+ return html.div("Hello, World!")
diff --git a/docs/examples/python/pyscript_setup_config_object.py b/docs/examples/python/pyscript_setup_config_object.py
new file mode 100644
index 00000000..85db2751
--- /dev/null
+++ b/docs/examples/python/pyscript_setup_config_object.py
@@ -0,0 +1,9 @@
+from django.shortcuts import render
+
+
+def index(request):
+ return render(
+ request,
+ "my_template.html",
+ context={"my_config_object": {"experimental_create_proxy": "auto"}},
+ )
diff --git a/docs/examples/python/pyscript_setup_extra_js_object.py b/docs/examples/python/pyscript_setup_extra_js_object.py
new file mode 100644
index 00000000..805365cf
--- /dev/null
+++ b/docs/examples/python/pyscript_setup_extra_js_object.py
@@ -0,0 +1,10 @@
+from django.shortcuts import render
+from django.templatetags.static import static
+
+
+def index(request):
+ return render(
+ request,
+ "my_template.html",
+ context={"my_extra_js_object": {static("moment.js"): "moment"}},
+ )
diff --git a/docs/examples/python/pyscript_ssr_child.py b/docs/examples/python/pyscript_ssr_child.py
new file mode 100644
index 00000000..d2566c88
--- /dev/null
+++ b/docs/examples/python/pyscript_ssr_child.py
@@ -0,0 +1,6 @@
+from reactpy import component, html
+
+
+@component
+def root():
+ return html.div("This text is from my client-side component")
diff --git a/docs/examples/python/pyscript_ssr_parent.py b/docs/examples/python/pyscript_ssr_parent.py
new file mode 100644
index 00000000..524cdc52
--- /dev/null
+++ b/docs/examples/python/pyscript_ssr_parent.py
@@ -0,0 +1,11 @@
+from reactpy import component, html
+
+from reactpy_django.components import pyscript_component
+
+
+@component
+def server_side_component():
+ return html.div(
+ "This text is from my server-side component",
+ pyscript_component("./example_project/my_app/components/root.py"),
+ )
diff --git a/docs/examples/python/pyscript_tag.py b/docs/examples/python/pyscript_tag.py
new file mode 100644
index 00000000..a038b267
--- /dev/null
+++ b/docs/examples/python/pyscript_tag.py
@@ -0,0 +1,16 @@
+from reactpy import component, html
+
+from reactpy_django.html import pyscript
+
+example_source_code = """
+import js
+
+js.console.log("Hello, World!")
+"""
+
+
+@component
+def server_side_component():
+ return html.div(
+ pyscript(example_source_code.strip()),
+ )
diff --git a/docs/examples/python/register_component.py b/docs/examples/python/register_component.py
new file mode 100644
index 00000000..6d7d3831
--- /dev/null
+++ b/docs/examples/python/register_component.py
@@ -0,0 +1,10 @@
+from django.apps import AppConfig
+
+from reactpy_django.utils import register_component
+
+
+class ExampleAppConfig(AppConfig):
+ name = "example"
+
+ def ready(self):
+ register_component("example_project.my_app.components.hello_world")
diff --git a/docs/python/template-tag-args-kwargs.py b/docs/examples/python/template_tag_args_kwargs.py
similarity index 100%
rename from docs/python/template-tag-args-kwargs.py
rename to docs/examples/python/template_tag_args_kwargs.py
diff --git a/docs/examples/python/template_tag_bad_view.py b/docs/examples/python/template_tag_bad_view.py
new file mode 100644
index 00000000..ef16c845
--- /dev/null
+++ b/docs/examples/python/template_tag_bad_view.py
@@ -0,0 +1,9 @@
+from django.shortcuts import render
+
+
+def example_view(request):
+ return render(
+ request,
+ "my_template.html",
+ context={"my_variable": "example_project.my_app.components.hello_world"},
+ )
diff --git a/docs/python/example/models.py b/docs/examples/python/todo_item_model.py
similarity index 100%
rename from docs/python/example/models.py
rename to docs/examples/python/todo_item_model.py
diff --git a/docs/examples/python/use_auth.py b/docs/examples/python/use_auth.py
new file mode 100644
index 00000000..2bb1bcbb
--- /dev/null
+++ b/docs/examples/python/use_auth.py
@@ -0,0 +1,23 @@
+from django.contrib.auth import get_user_model
+from reactpy import component, html
+
+from reactpy_django.hooks import use_auth, use_user
+
+
+@component
+def my_component():
+ auth = use_auth()
+ user = use_user()
+
+ async def login_user(event):
+ new_user, _created = await get_user_model().objects.aget_or_create(username="ExampleUser")
+ await auth.login(new_user)
+
+ async def logout_user(event):
+ await auth.logout()
+
+ return html.div(
+ f"Current User: {user}",
+ html.button({"onClick": login_user}, "Login"),
+ html.button({"onClick": logout_user}, "Logout"),
+ )
diff --git a/docs/examples/python/use_channel_layer.py b/docs/examples/python/use_channel_layer.py
new file mode 100644
index 00000000..ff9330a4
--- /dev/null
+++ b/docs/examples/python/use_channel_layer.py
@@ -0,0 +1,23 @@
+from reactpy import component, hooks, html
+
+from reactpy_django.hooks import use_channel_layer
+
+
+@component
+def my_component():
+ async def receive_message(message):
+ set_message_data(message["text"])
+
+ async def send_message(event):
+ if event["key"] == "Enter":
+ await sender({"text": event["target"]["value"]})
+
+ message_data, set_message_data = hooks.use_state("")
+ sender = use_channel_layer(group="my-group-name", receiver=receive_message)
+
+ return html.div(
+ f"Received: {message_data}",
+ html.br(),
+ "Send: ",
+ html.input({"type": "text", "onKeyDown": send_message}),
+ )
diff --git a/docs/examples/python/use_channel_layer_group.py b/docs/examples/python/use_channel_layer_group.py
new file mode 100644
index 00000000..4189b47c
--- /dev/null
+++ b/docs/examples/python/use_channel_layer_group.py
@@ -0,0 +1,41 @@
+from reactpy import component, hooks, html
+
+from reactpy_django.hooks import use_channel_layer
+
+
+@component
+def my_sender_component():
+ sender = use_channel_layer(group="my-group-name")
+
+ async def submit_event(event):
+ if event["key"] == "Enter":
+ await sender({"text": event["target"]["value"]})
+
+ return html.div(
+ "Message Sender: ",
+ html.input({"type": "text", "onKeyDown": submit_event}),
+ )
+
+
+@component
+def my_receiver_component_1():
+ message, set_message = hooks.use_state("")
+
+ async def receive_message(message):
+ set_message(message["text"])
+
+ use_channel_layer(group="my-group-name", receiver=receive_message)
+
+ return html.div(f"Message Receiver 1: {message}")
+
+
+@component
+def my_receiver_component_2():
+ message, set_message = hooks.use_state("")
+
+ async def receive_message(message):
+ set_message(message["text"])
+
+ use_channel_layer(group="my-group-name", receiver=receive_message)
+
+ return html.div(f"Message Receiver 2: {message}")
diff --git a/docs/examples/python/use_channel_layer_signal_receiver.py b/docs/examples/python/use_channel_layer_signal_receiver.py
new file mode 100644
index 00000000..d858df3f
--- /dev/null
+++ b/docs/examples/python/use_channel_layer_signal_receiver.py
@@ -0,0 +1,16 @@
+from reactpy import component, hooks, html
+
+from reactpy_django.hooks import use_channel_layer
+
+
+@component
+def my_receiver_component():
+ message, set_message = hooks.use_state("")
+
+ async def receive_message(message):
+ set_message(message["text"])
+
+ # This is defined to receive any messages from both "my-channel-name" and "my-group-name".
+ use_channel_layer(channel="my-channel-name", group="my-group-name", receiver=receive_message)
+
+ return html.div(f"Message Receiver: {message}")
diff --git a/docs/examples/python/use_channel_layer_signal_sender.py b/docs/examples/python/use_channel_layer_signal_sender.py
new file mode 100644
index 00000000..b900bd2c
--- /dev/null
+++ b/docs/examples/python/use_channel_layer_signal_sender.py
@@ -0,0 +1,21 @@
+from asgiref.sync import async_to_sync
+from channels.layers import get_channel_layer
+from django.db.models import Model
+from django.db.models.signals import pre_save
+from django.dispatch import receiver
+
+
+class ExampleModel(Model): ...
+
+
+@receiver(pre_save, sender=ExampleModel)
+def my_sender_signal(sender, instance, **kwargs):
+ layer = get_channel_layer()
+
+ # EXAMPLE 1: Sending a message to a group.
+ # Note that `group_send` requires using the `group` argument in `use_channel_layer`.
+ async_to_sync(layer.group_send)("my-group-name", {"text": "Hello World!"})
+
+ # EXAMPLE 2: Sending a message to a single channel.
+ # Note that this is typically only used for channels that use point-to-point communication
+ async_to_sync(layer.send)("my-channel-name", {"text": "Hello World!"})
diff --git a/docs/examples/python/use_channel_layer_single.py b/docs/examples/python/use_channel_layer_single.py
new file mode 100644
index 00000000..394cbc48
--- /dev/null
+++ b/docs/examples/python/use_channel_layer_single.py
@@ -0,0 +1,29 @@
+from reactpy import component, hooks, html
+
+from reactpy_django.hooks import use_channel_layer
+
+
+@component
+def my_sender_component():
+ sender = use_channel_layer(channel="my-channel-name")
+
+ async def submit_event(event):
+ if event["key"] == "Enter":
+ await sender({"text": event["target"]["value"]})
+
+ return html.div(
+ "Message Sender: ",
+ html.input({"type": "text", "onKeyDown": submit_event}),
+ )
+
+
+@component
+def my_receiver_component():
+ message, set_message = hooks.use_state("")
+
+ async def receive_message(message):
+ set_message(message["text"])
+
+ use_channel_layer(channel="my-channel-name", receiver=receive_message)
+
+ return html.div(f"Message Receiver 1: {message}")
diff --git a/docs/python/use-connection.py b/docs/examples/python/use_connection.py
similarity index 60%
rename from docs/python/use-connection.py
rename to docs/examples/python/use_connection.py
index 6050e744..a15cd39b 100644
--- a/docs/python/use-connection.py
+++ b/docs/examples/python/use_connection.py
@@ -5,5 +5,6 @@
@component
def my_component():
- my_connection = use_connection()
- return html.div(str(my_connection))
+ connection = use_connection()
+
+ return html.div(str(connection))
diff --git a/docs/python/use-location.py b/docs/examples/python/use_location.py
similarity index 56%
rename from docs/python/use-location.py
rename to docs/examples/python/use_location.py
index 42303b64..454da7f6 100644
--- a/docs/python/use-location.py
+++ b/docs/examples/python/use_location.py
@@ -5,5 +5,6 @@
@component
def my_component():
- my_location = use_location()
- return html.div(str(my_location))
+ location = use_location()
+
+ return html.div(location.pathname + location.search)
diff --git a/docs/python/use-mutation.py b/docs/examples/python/use_mutation.py
similarity index 83%
rename from docs/python/use-mutation.py
rename to docs/examples/python/use_mutation.py
index 80a698d8..471a4a4c 100644
--- a/docs/python/use-mutation.py
+++ b/docs/examples/python/use_mutation.py
@@ -1,20 +1,21 @@
-from example.models import TodoItem
from reactpy import component, html
+from example.models import TodoItem
from reactpy_django.hooks import use_mutation
-def add_item(text: str):
- TodoItem(text=text).save()
+async def add_item(text: str):
+ await TodoItem(text=text).asave()
@component
def todo_list():
+ item_mutation = use_mutation(add_item)
+
def submit_event(event):
if event["key"] == "Enter":
- item_mutation.execute(text=event["target"]["value"])
+ item_mutation(text=event["target"]["value"])
- item_mutation = use_mutation(add_item)
if item_mutation.loading:
mutation_status = html.h2("Adding...")
elif item_mutation.error:
diff --git a/docs/python/use-mutation-args-kwargs.py b/docs/examples/python/use_mutation_args_kwargs.py
similarity index 56%
rename from docs/python/use-mutation-args-kwargs.py
rename to docs/examples/python/use_mutation_args_kwargs.py
index 19453aba..9a4b1e0a 100644
--- a/docs/python/use-mutation-args-kwargs.py
+++ b/docs/examples/python/use_mutation_args_kwargs.py
@@ -3,14 +3,11 @@
from reactpy_django.hooks import use_mutation
-def example_mutation(value: int, other_value: bool = False):
- ...
+def example_mutation(value: int, other_value: bool = False): ...
@component
def my_component():
mutation = use_mutation(example_mutation)
- mutation.execute(123, other_value=True)
-
- ...
+ mutation(123, other_value=True)
diff --git a/docs/python/use-mutation-query-refetch.py b/docs/examples/python/use_mutation_query_refetch.py
similarity index 88%
rename from docs/python/use-mutation-query-refetch.py
rename to docs/examples/python/use_mutation_query_refetch.py
index c3651e4f..f2e014e9 100644
--- a/docs/python/use-mutation-query-refetch.py
+++ b/docs/examples/python/use_mutation_query_refetch.py
@@ -1,6 +1,6 @@
-from example.models import TodoItem
from reactpy import component, html
+from example.models import TodoItem
from reactpy_django.hooks import use_mutation, use_query
@@ -19,7 +19,7 @@ def todo_list():
def submit_event(event):
if event["key"] == "Enter":
- item_mutation.execute(text=event["target"]["value"])
+ item_mutation(text=event["target"]["value"])
# Handle all possible query states
if item_query.loading:
@@ -27,7 +27,7 @@ def submit_event(event):
elif item_query.error or not item_query.data:
rendered_items = html.h2("Error when loading!")
else:
- rendered_items = html.ul(html.li(item, key=item) for item in item_query.data)
+ rendered_items = html.ul(html.li(item.text, key=item.pk) for item in item_query.data)
# Handle all possible mutation states
if item_mutation.loading:
diff --git a/docs/python/use-mutation-reset.py b/docs/examples/python/use_mutation_reset.py
similarity index 92%
rename from docs/python/use-mutation-reset.py
rename to docs/examples/python/use_mutation_reset.py
index 874cb002..486f0464 100644
--- a/docs/python/use-mutation-reset.py
+++ b/docs/examples/python/use_mutation_reset.py
@@ -1,6 +1,6 @@
-from example.models import TodoItem
from reactpy import component, html
+from example.models import TodoItem
from reactpy_django.hooks import use_mutation
@@ -17,7 +17,7 @@ def reset_event(event):
def submit_event(event):
if event["key"] == "Enter":
- item_mutation.execute(text=event["target"]["value"])
+ item_mutation(text=event["target"]["value"])
if item_mutation.loading:
mutation_status = html.h2("Adding...")
diff --git a/docs/examples/python/use_mutation_thread_sensitive.py b/docs/examples/python/use_mutation_thread_sensitive.py
new file mode 100644
index 00000000..2d047696
--- /dev/null
+++ b/docs/examples/python/use_mutation_thread_sensitive.py
@@ -0,0 +1,31 @@
+from reactpy import component, html
+
+from reactpy_django.hooks import use_mutation
+
+
+def execute_thread_safe_mutation(text):
+ """This is an example mutation function that does some thread-safe operation."""
+
+
+@component
+def my_component():
+ item_mutation = use_mutation(
+ execute_thread_safe_mutation,
+ thread_sensitive=False,
+ )
+
+ def submit_event(event):
+ if event["key"] == "Enter":
+ item_mutation(text=event["target"]["value"])
+
+ if item_mutation.loading or item_mutation.error:
+ mutation_status = html.h2("Doing something...")
+ elif item_mutation.error:
+ mutation_status = html.h2("Error!")
+ else:
+ mutation_status = html.h2("Done.")
+
+ return html.div(
+ html.input({"type": "text", "onKeyDown": submit_event}),
+ mutation_status,
+ )
diff --git a/docs/python/use-origin.py b/docs/examples/python/use_origin.py
similarity index 60%
rename from docs/python/use-origin.py
rename to docs/examples/python/use_origin.py
index 6908fb17..f0713db9 100644
--- a/docs/python/use-origin.py
+++ b/docs/examples/python/use_origin.py
@@ -5,5 +5,6 @@
@component
def my_component():
- my_origin = use_origin()
- return html.div(my_origin or "No origin")
+ origin = use_origin()
+
+ return html.div(origin or "No origin")
diff --git a/docs/python/use-query-async.py b/docs/examples/python/use_query.py
similarity index 85%
rename from docs/python/use-query-async.py
rename to docs/examples/python/use_query.py
index 3b532fc5..9cadbd25 100644
--- a/docs/python/use-query-async.py
+++ b/docs/examples/python/use_query.py
@@ -1,7 +1,7 @@
from channels.db import database_sync_to_async
-from example.models import TodoItem
from reactpy import component, html
+from example.models import TodoItem
from reactpy_django.hooks import use_query
@@ -18,6 +18,6 @@ def todo_list():
elif item_query.error or not item_query.data:
rendered_items = html.h2("Error when loading!")
else:
- rendered_items = html.ul([html.li(item, key=item) for item in item_query.data])
+ rendered_items = html.ul([html.li(item.text, key=item.pk) for item in item_query.data])
return html.div("Rendered items: ", rendered_items)
diff --git a/docs/examples/python/use_query_args.py b/docs/examples/python/use_query_args.py
new file mode 100644
index 00000000..a37f7277
--- /dev/null
+++ b/docs/examples/python/use_query_args.py
@@ -0,0 +1,13 @@
+from reactpy import component
+
+from reactpy_django.hooks import use_query
+
+
+def example_query(value: int, other_value: bool = False): ...
+
+
+@component
+def my_component():
+ query = use_query(example_query, {"value": 123, "other_value": True})
+
+ return str(query.data)
diff --git a/docs/python/use-query-postprocessor-change.py b/docs/examples/python/use_query_postprocessor_change.py
similarity index 72%
rename from docs/python/use-query-postprocessor-change.py
rename to docs/examples/python/use_query_postprocessor_change.py
index 0c3f552a..2faba050 100644
--- a/docs/python/use-query-postprocessor-change.py
+++ b/docs/examples/python/use_query_postprocessor_change.py
@@ -1,7 +1,6 @@
from reactpy import component
from reactpy_django.hooks import use_query
-from reactpy_django.types import QueryOptions
def my_postprocessor(data, example_kwarg=True):
@@ -13,17 +12,14 @@ def my_postprocessor(data, example_kwarg=True):
def execute_io_intensive_operation():
"""This is an example query function that does something IO intensive."""
- pass
@component
def my_component():
query = use_query(
- QueryOptions(
- postprocessor=my_postprocessor,
- postprocessor_kwargs={"example_kwarg": False},
- ),
execute_io_intensive_operation,
+ postprocessor=my_postprocessor,
+ postprocessor_kwargs={"example_kwarg": False},
)
if query.loading or query.error:
diff --git a/docs/python/use-query-postprocessor-disable.py b/docs/examples/python/use_query_postprocessor_disable.py
similarity index 79%
rename from docs/python/use-query-postprocessor-disable.py
rename to docs/examples/python/use_query_postprocessor_disable.py
index 722366f8..a22f7a96 100644
--- a/docs/python/use-query-postprocessor-disable.py
+++ b/docs/examples/python/use_query_postprocessor_disable.py
@@ -1,19 +1,17 @@
from reactpy import component
from reactpy_django.hooks import use_query
-from reactpy_django.types import QueryOptions
def execute_io_intensive_operation():
"""This is an example query function that does something IO intensive."""
- pass
@component
def my_component():
query = use_query(
- QueryOptions(postprocessor=None),
execute_io_intensive_operation,
+ postprocessor=None,
)
if query.loading or query.error:
diff --git a/docs/python/use-query-postprocessor-kwargs.py b/docs/examples/python/use_query_postprocessor_kwargs.py
similarity index 84%
rename from docs/python/use-query-postprocessor-kwargs.py
rename to docs/examples/python/use_query_postprocessor_kwargs.py
index 39f0f965..18ba2999 100644
--- a/docs/python/use-query-postprocessor-kwargs.py
+++ b/docs/examples/python/use_query_postprocessor_kwargs.py
@@ -1,8 +1,7 @@
-from example.models import TodoItem
from reactpy import component
+from example.models import TodoItem
from reactpy_django.hooks import use_query
-from reactpy_django.types import QueryOptions
def get_model_with_relationships():
@@ -18,10 +17,8 @@ def get_model_with_relationships():
@component
def my_component():
query = use_query(
- QueryOptions(
- postprocessor_kwargs={"many_to_many": False, "many_to_one": False}
- ),
get_model_with_relationships,
+ postprocessor_kwargs={"many_to_many": False, "many_to_one": False},
)
if query.loading or query.error or not query.data:
diff --git a/docs/examples/python/use_query_refetch.py b/docs/examples/python/use_query_refetch.py
new file mode 100644
index 00000000..c31c12c7
--- /dev/null
+++ b/docs/examples/python/use_query_refetch.py
@@ -0,0 +1,26 @@
+from channels.db import database_sync_to_async
+from reactpy import component, html
+
+from example.models import TodoItem
+from reactpy_django.hooks import use_query
+
+
+async def get_items():
+ return await database_sync_to_async(TodoItem.objects.all)()
+
+
+@component
+def todo_list():
+ item_query = use_query(get_items)
+
+ if item_query.loading:
+ rendered_items = html.h2("Loading...")
+ elif item_query.error or not item_query.data:
+ rendered_items = html.h2("Error when loading!")
+ else:
+ rendered_items = html.ul([html.li(item.text, key=item.pk) for item in item_query.data])
+
+ def on_click(event):
+ item_query.refetch()
+
+ return html.div("Rendered items: ", rendered_items, html.button({"onClick": on_click}, "Refresh"))
diff --git a/docs/python/use-query-thread-sensitive.py b/docs/examples/python/use_query_thread_sensitive.py
similarity index 65%
rename from docs/python/use-query-thread-sensitive.py
rename to docs/examples/python/use_query_thread_sensitive.py
index ab806bf4..9b929e3a 100644
--- a/docs/python/use-query-thread-sensitive.py
+++ b/docs/examples/python/use_query_thread_sensitive.py
@@ -1,20 +1,15 @@
from reactpy import component
from reactpy_django.hooks import use_query
-from reactpy_django.types import QueryOptions
def execute_thread_safe_operation():
"""This is an example query function that does some thread-safe operation."""
- pass
@component
def my_component():
- query = use_query(
- QueryOptions(thread_sensitive=False),
- execute_thread_safe_operation,
- )
+ query = use_query(execute_thread_safe_operation, thread_sensitive=False)
if query.loading or query.error:
return None
diff --git a/docs/examples/python/use_rerender.py b/docs/examples/python/use_rerender.py
new file mode 100644
index 00000000..cd160e17
--- /dev/null
+++ b/docs/examples/python/use_rerender.py
@@ -0,0 +1,15 @@
+from uuid import uuid4
+
+from reactpy import component, html
+
+from reactpy_django.hooks import use_rerender
+
+
+@component
+def my_component():
+ rerender = use_rerender()
+
+ def on_click():
+ rerender()
+
+ return html.div(f"UUID: {uuid4()}", html.button({"onClick": on_click}, "Rerender"))
diff --git a/docs/examples/python/use_root_id.py b/docs/examples/python/use_root_id.py
new file mode 100644
index 00000000..246a8da1
--- /dev/null
+++ b/docs/examples/python/use_root_id.py
@@ -0,0 +1,10 @@
+from reactpy import component, html
+
+from reactpy_django.hooks import use_root_id
+
+
+@component
+def my_component():
+ root_id = use_root_id()
+
+ return html.div(f"Root ID: {root_id}")
diff --git a/docs/python/use-scope.py b/docs/examples/python/use_scope.py
similarity index 64%
rename from docs/python/use-scope.py
rename to docs/examples/python/use_scope.py
index 07f12aad..2bd8f483 100644
--- a/docs/python/use-scope.py
+++ b/docs/examples/python/use_scope.py
@@ -5,5 +5,6 @@
@component
def my_component():
- my_scope = use_scope()
- return html.div(str(my_scope))
+ scope = use_scope()
+
+ return html.div(str(scope))
diff --git a/docs/examples/python/use_user.py b/docs/examples/python/use_user.py
new file mode 100644
index 00000000..597e9f67
--- /dev/null
+++ b/docs/examples/python/use_user.py
@@ -0,0 +1,10 @@
+from reactpy import component, html
+
+from reactpy_django.hooks import use_user
+
+
+@component
+def my_component():
+ user = use_user()
+
+ return html.div(user.username)
diff --git a/docs/examples/python/use_user_data.py b/docs/examples/python/use_user_data.py
new file mode 100644
index 00000000..1526d6a8
--- /dev/null
+++ b/docs/examples/python/use_user_data.py
@@ -0,0 +1,20 @@
+from reactpy import component, html
+
+from reactpy_django.hooks import use_user_data
+
+
+@component
+def my_component():
+ query, mutation = use_user_data()
+
+ def on_submit(event):
+ if event["key"] == "Enter" and query.data:
+ new_key = str(len(query.data))
+ mutation({**query.data, new_key: event["target"]["value"]})
+
+ return html.div(
+ html.div(f"Data: {query.data}"),
+ html.div(f"Loading: {query.loading | mutation.loading}"),
+ html.div(f"Error(s): {query.error} {mutation.error}"),
+ html.input({"onKeyPress": on_submit}),
+ )
diff --git a/docs/examples/python/use_user_data_defaults.py b/docs/examples/python/use_user_data_defaults.py
new file mode 100644
index 00000000..2c066ad7
--- /dev/null
+++ b/docs/examples/python/use_user_data_defaults.py
@@ -0,0 +1,26 @@
+from reactpy import component, html
+
+from reactpy_django.hooks import use_user_data
+
+
+@component
+def my_component():
+ user_data = use_user_data(
+ default_data={
+ "basic_example": "123",
+ "computed_example_sync": sync_default,
+ "computed_example_async": async_default,
+ }
+ )
+
+ return html.div(
+ html.div(f"Data: {user_data.query.data}"),
+ )
+
+
+def sync_default():
+ return ...
+
+
+async def async_default():
+ return ...
diff --git a/docs/examples/python/user_passes_test.py b/docs/examples/python/user_passes_test.py
new file mode 100644
index 00000000..37160c1b
--- /dev/null
+++ b/docs/examples/python/user_passes_test.py
@@ -0,0 +1,13 @@
+from reactpy import component, html
+
+from reactpy_django.decorators import user_passes_test
+
+
+def is_authenticated(user):
+ return user.is_authenticated
+
+
+@user_passes_test(is_authenticated)
+@component
+def my_component():
+ return html.div("I am logged in!")
diff --git a/docs/python/auth-required-component-fallback.py b/docs/examples/python/user_passes_test_component_fallback.py
similarity index 51%
rename from docs/python/auth-required-component-fallback.py
rename to docs/examples/python/user_passes_test_component_fallback.py
index 4d134eca..b18330d1 100644
--- a/docs/python/auth-required-component-fallback.py
+++ b/docs/examples/python/user_passes_test_component_fallback.py
@@ -1,6 +1,6 @@
from reactpy import component, html
-from reactpy_django.decorators import auth_required
+from reactpy_django.decorators import user_passes_test
@component
@@ -8,7 +8,11 @@ def my_component_fallback():
return html.div("I am NOT logged in!")
+def is_authenticated(user):
+ return user.is_authenticated
+
+
+@user_passes_test(is_authenticated, fallback=my_component_fallback)
@component
-@auth_required(fallback=my_component_fallback)
def my_component():
return html.div("I am logged in!")
diff --git a/docs/examples/python/user_passes_test_vdom_fallback.py b/docs/examples/python/user_passes_test_vdom_fallback.py
new file mode 100644
index 00000000..9dd1ad65
--- /dev/null
+++ b/docs/examples/python/user_passes_test_vdom_fallback.py
@@ -0,0 +1,13 @@
+from reactpy import component, html
+
+from reactpy_django.decorators import user_passes_test
+
+
+def is_authenticated(user):
+ return user.is_authenticated
+
+
+@user_passes_test(is_authenticated, fallback=html.div("I am NOT logged in!"))
+@component
+def my_component():
+ return html.div("I am logged in!")
diff --git a/docs/python/vtc-func.py b/docs/examples/python/vtc.py
similarity index 56%
rename from docs/python/vtc-func.py
rename to docs/examples/python/vtc.py
index f7f9582e..84c7aeb2 100644
--- a/docs/python/vtc-func.py
+++ b/docs/examples/python/vtc.py
@@ -1,14 +1,13 @@
-from example.views import example_view
from reactpy import component, html
+from example import views
from reactpy_django.components import view_to_component
-
-example_vtc = view_to_component(example_view)
+hello_world_component = view_to_component(views.hello_world)
@component
def my_component():
return html.div(
- example_vtc(),
+ hello_world_component(),
)
diff --git a/docs/examples/python/vtc_args.py b/docs/examples/python/vtc_args.py
new file mode 100644
index 00000000..9ce081b5
--- /dev/null
+++ b/docs/examples/python/vtc_args.py
@@ -0,0 +1,23 @@
+from django.http import HttpRequest
+from reactpy import component, html
+
+from example import views
+from reactpy_django.components import view_to_component
+
+hello_world_component = view_to_component(views.hello_world)
+
+
+@component
+def my_component():
+ request = HttpRequest()
+ request.method = "GET"
+
+ return html.div(
+ hello_world_component(
+ request, # This request object is optional.
+ "value_1",
+ "value_2",
+ kwarg1="abc",
+ kwarg2="123",
+ ),
+ )
diff --git a/docs/examples/python/vtc_cbv.py b/docs/examples/python/vtc_cbv.py
new file mode 100644
index 00000000..38e40efe
--- /dev/null
+++ b/docs/examples/python/vtc_cbv.py
@@ -0,0 +1,13 @@
+from reactpy import component, html
+
+from example import views
+from reactpy_django.components import view_to_component
+
+hello_world_component = view_to_component(views.HelloWorld.as_view())
+
+
+@component
+def my_component():
+ return html.div(
+ hello_world_component(),
+ )
diff --git a/docs/examples/python/vtc_strict_parsing.py b/docs/examples/python/vtc_strict_parsing.py
new file mode 100644
index 00000000..84c7aeb2
--- /dev/null
+++ b/docs/examples/python/vtc_strict_parsing.py
@@ -0,0 +1,13 @@
+from reactpy import component, html
+
+from example import views
+from reactpy_django.components import view_to_component
+
+hello_world_component = view_to_component(views.hello_world)
+
+
+@component
+def my_component():
+ return html.div(
+ hello_world_component(),
+ )
diff --git a/docs/python/vtc-transforms.py b/docs/examples/python/vtc_transforms.py
similarity index 52%
rename from docs/python/vtc-transforms.py
rename to docs/examples/python/vtc_transforms.py
index 4c3ba446..b8402481 100644
--- a/docs/python/vtc-transforms.py
+++ b/docs/examples/python/vtc_transforms.py
@@ -1,22 +1,20 @@
-from django.http import HttpResponse
from reactpy import component, html
+from example import views
from reactpy_django.components import view_to_component
def example_transform(vdom):
attributes = vdom.get("attributes")
if attributes and attributes.get("id") == "hello-world":
- vdom["children"][0] = "Good Bye World!"
+ vdom["children"][0] = "Farewell World!"
-@view_to_component(transforms=[example_transform])
-def hello_world_view(request):
- return HttpResponse(" Hello World!
")
+hello_world_component = view_to_component(views.hello_world, transforms=[example_transform])
@component
def my_component():
return html.div(
- hello_world_view(),
+ hello_world_component(),
)
diff --git a/docs/examples/python/vti.py b/docs/examples/python/vti.py
new file mode 100644
index 00000000..207e5bc5
--- /dev/null
+++ b/docs/examples/python/vti.py
@@ -0,0 +1,13 @@
+from reactpy import component, html
+
+from example import views
+from reactpy_django.components import view_to_iframe
+
+hello_world_iframe = view_to_iframe(views.hello_world)
+
+
+@component
+def my_component():
+ return html.div(
+ hello_world_iframe(),
+ )
diff --git a/docs/examples/python/vti_args.py b/docs/examples/python/vti_args.py
new file mode 100644
index 00000000..a26c3d3a
--- /dev/null
+++ b/docs/examples/python/vti_args.py
@@ -0,0 +1,20 @@
+from reactpy import component, html
+
+from example import views
+from reactpy_django.components import view_to_iframe
+
+hello_world_iframe = view_to_iframe(
+ views.hello_world,
+)
+
+
+@component
+def my_component():
+ return html.div(
+ hello_world_iframe(
+ "value_1",
+ "value_2",
+ kwarg1="abc",
+ kwarg2="123",
+ ),
+ )
diff --git a/docs/examples/python/vti_cbv.py b/docs/examples/python/vti_cbv.py
new file mode 100644
index 00000000..63f182ae
--- /dev/null
+++ b/docs/examples/python/vti_cbv.py
@@ -0,0 +1,13 @@
+from reactpy import component, html
+
+from example import views
+from reactpy_django.components import view_to_iframe
+
+hello_world_iframe = view_to_iframe(views.HelloWorld.as_view())
+
+
+@component
+def my_component():
+ return html.div(
+ hello_world_iframe(),
+ )
diff --git a/docs/examples/python/vti_extra_props.py b/docs/examples/python/vti_extra_props.py
new file mode 100644
index 00000000..09846a1c
--- /dev/null
+++ b/docs/examples/python/vti_extra_props.py
@@ -0,0 +1,13 @@
+from reactpy import component, html
+
+from example import views
+from reactpy_django.components import view_to_iframe
+
+hello_world_iframe = view_to_iframe(views.hello_world, extra_props={"title": "Hello World!"})
+
+
+@component
+def my_component():
+ return html.div(
+ hello_world_iframe(),
+ )
diff --git a/docs/includes/auth-middleware-stack.md b/docs/includes/auth-middleware-stack.md
new file mode 100644
index 00000000..7cc0c7f8
--- /dev/null
+++ b/docs/includes/auth-middleware-stack.md
@@ -0,0 +1,3 @@
+```python linenums="0"
+{% include "../examples/python/configure_asgi_middleware.py" start="# start" %}
+```
diff --git a/docs/includes/orm.md b/docs/includes/orm.md
index 22151a74..a2a18dfe 100644
--- a/docs/includes/orm.md
+++ b/docs/includes/orm.md
@@ -1,13 +1,13 @@
-Due to Django's ORM design, database queries must be deferred using hooks. Otherwise, you will see a `SynchronousOnlyOperation` exception.
+Due to Django's ORM design, database queries must be deferred using hooks. Otherwise, you will see a `#!python SynchronousOnlyOperation` exception.
-These `SynchronousOnlyOperation` exceptions may be resolved in a future version of Django containing an asynchronous ORM. However, it is best practice to always perform ORM calls in the background via hooks.
+These `#!python SynchronousOnlyOperation` exceptions may be removed in a future version of Django. However, it is best practice to always perform IO operations (such as ORM queries) via hooks to prevent performance issues.
-By default, automatic recursive fetching of `ManyToMany` or `ForeignKey` fields is enabled within the default `QueryOptions.postprocessor`. This is needed to prevent `SynchronousOnlyOperation` exceptions when accessing these fields within your ReactPy components.
+By default, automatic recursive fetching of `#!python ManyToMany` or `#!python ForeignKey` fields is enabled within the `#!python django_query_postprocessor`. This is needed to prevent `#!python SynchronousOnlyOperation` exceptions when accessing these fields within your ReactPy components.
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
new file mode 100644
index 00000000..bde5ac08
--- /dev/null
+++ b/docs/mkdocs.yml
@@ -0,0 +1,129 @@
+---
+nav:
+ - Home: index.md
+ - Get Started:
+ - Add ReactPy to a Django Project: learn/add-reactpy-to-a-django-project.md
+ - Your First Component: learn/your-first-component.md
+ - Reference:
+ - Components: reference/components.md
+ - Hooks: reference/hooks.md
+ - HTML: reference/html.md
+ - URL Router: reference/router.md
+ - Decorators: reference/decorators.md
+ - Utilities: reference/utils.md
+ - Template Tag: reference/template-tag.md
+ - Settings: reference/settings.md
+ - Management Commands: reference/management-commands.md
+ - About:
+ - Changelog: about/changelog.md
+ - Contributor Guide: about/contributing.md
+ - Community:
+ - GitHub Discussions: https://github.com/reactive-python/reactpy-django/discussions
+ - Discord: https://discord.gg/uNb5P4hA9X
+ - Reddit: https://www.reddit.com/r/ReactPy/
+ - License: about/license.md
+
+theme:
+ name: material
+ custom_dir: overrides
+ palette:
+ - media: "(prefers-color-scheme: dark)"
+ scheme: slate
+ toggle:
+ icon: material/white-balance-sunny
+ name: Switch to light mode
+ primary: red # We use red to indicate that something is unthemed
+ accent: red
+ - media: "(prefers-color-scheme: light)"
+ scheme: default
+ toggle:
+ icon: material/weather-night
+ name: Switch to dark mode
+ primary: white
+ accent: red
+ features:
+ - navigation.instant
+ - navigation.tabs
+ - navigation.tabs.sticky
+ - navigation.top
+ - content.code.copy
+ - search.highlight
+ icon:
+ repo: fontawesome/brands/github
+ admonition:
+ note: fontawesome/solid/note-sticky
+ logo: https://raw.githubusercontent.com/reactive-python/reactpy/main/branding/svg/reactpy-logo-square.svg
+ favicon: https://raw.githubusercontent.com/reactive-python/reactpy/main/branding/svg/reactpy-logo-square.svg
+
+markdown_extensions:
+ - toc:
+ permalink: true
+ - pymdownx.emoji:
+ emoji_index: !!python/name:material.extensions.emoji.twemoji
+ emoji_generator: !!python/name:material.extensions.emoji.to_svg
+ - pymdownx.tabbed:
+ alternate_style: true
+ - pymdownx.highlight:
+ linenums: true
+ - pymdownx.superfences
+ - pymdownx.details
+ - pymdownx.inlinehilite
+ - admonition
+ - attr_list
+ - md_in_html
+ - pymdownx.keys
+
+plugins:
+ - search
+ - include-markdown
+ - git-authors
+ - minify:
+ minify_html: true
+ minify_js: true
+ minify_css: true
+ cache_safe: true
+ - git-revision-date-localized:
+ fallback_to_build_date: true
+ - spellcheck:
+ known_words: dictionary.txt
+ allow_unicode: no
+
+extra:
+ generator: false
+ version:
+ provider: mike
+ analytics:
+ provider: google
+ property: G-4KFQN2LWBG
+
+extra_javascript:
+ - assets/js/main.js
+
+extra_css:
+ - assets/css/main.css
+ - assets/css/button.css
+ - assets/css/admonition.css
+ - assets/css/banner.css
+ - assets/css/sidebar.css
+ - assets/css/navbar.css
+ - assets/css/table-of-contents.css
+ - assets/css/code.css
+ - assets/css/footer.css
+ - assets/css/home.css
+
+watch:
+ - ../docs
+ - ../README.md
+ - ../CHANGELOG.md
+ - ../LICENSE.md
+ - ../.mailmap
+
+site_name: ReactPy-Django
+site_author: Archmonger
+site_description: It's React, but in Python. Now with Django integration.
+copyright: '©
Reactive Python and affiliates. '
+repo_url: https://github.com/reactive-python/reactpy-django
+site_url: https://reactive-python.github.io/reactpy-django
+repo_name: ReactPy Django
+edit_uri: edit/main/docs/src
+docs_dir: src
diff --git a/docs/overrides/home.html b/docs/overrides/home.html
new file mode 100644
index 00000000..93d5ca29
--- /dev/null
+++ b/docs/overrides/home.html
@@ -0,0 +1,135 @@
+
+{% extends "main.html" %}
+
+
+{% block content %}{% endblock %}
+
+
+{% block tabs %}
+
+
+
+

+
{{ config.site_name }}
+
{{ config.site_description }}
+
+
+
+
+
Create user interfaces from components
+
+ ReactPy lets you build user interfaces out of individual pieces called components. Create your own ReactPy
+ components like thumbnail
, like_button
, and video
. Then combine
+ them into entire screens, pages, and apps.
+
+
+ {% with image="create-user-interfaces.png", class="pop-left" %}
+ {% include "homepage_examples/code_block.html" %}
+ {% endwith %}
+ {% include "homepage_examples/create_user_interfaces_demo.html" %}
+
+
+ Whether you work on your own or with thousands of other developers, using React feels the same. It is
+ designed to let you seamlessly combine components written by independent people, teams, and
+ organizations.
+
+
+
+
+
Write components with pure Python code
+
+ ReactPy components are Python functions. Want to show some content conditionally? Use an
+ if
statement. Displaying a list? Try using
+ list comprehension.
+ Learning ReactPy is learning programming.
+
+
+ {% with image="write-components-with-python.png", class="pop-left" %}
+ {% include "homepage_examples/code_block.html" %}
+ {% endwith %}
+ {% include "homepage_examples/write_components_with_python_demo.html" %}
+
+
+
+
+
+
Add interactivity wherever you need it
+
+ ReactPy components receive data and return what should appear on the screen. You can pass them new data in
+ response to an interaction, like when the user types into an input. ReactPy will then update the screen to
+ match the new data.
+
+
+ {% with image="add-interactivity.png" %}
+ {% include "homepage_examples/code_block.html" %}
+ {% endwith %}
+ {% include "homepage_examples/add_interactivity_demo.html" %}
+
+
+ You don't have to build your whole page in ReactPy. Add React to your existing HTML page, and render
+ interactive ReactPy components anywhere on it.
+
+
+
+
+
Go full-stack with the Django framework
+
+ ReactPy is a library. It lets you put components together, but it doesn't prescribe how to do routing and
+ data fetching. To build an entire app with ReactPy, we recommend a backend framework like
+ Django.
+
+
+ Get Started
+
+
+
+{% endblock %}
diff --git a/docs/overrides/homepage_examples/add_interactivity.py b/docs/overrides/homepage_examples/add_interactivity.py
new file mode 100644
index 00000000..9a7bf76f
--- /dev/null
+++ b/docs/overrides/homepage_examples/add_interactivity.py
@@ -0,0 +1,29 @@
+# ruff: noqa: INP001
+from reactpy import component, html, use_state
+
+
+def filter_videos(*_, **__):
+ return []
+
+
+def search_input(*_, **__): ...
+
+
+def video_list(*_, **__): ...
+
+
+@component
+def searchable_video_list(videos):
+ search_text, set_search_text = use_state("")
+ found_videos = filter_videos(videos, search_text)
+
+ return html._(
+ search_input(
+ {"onChange": lambda event: set_search_text(event["target"]["value"])},
+ value=search_text,
+ ),
+ video_list(
+ videos=found_videos,
+ empty_heading=f"No matches for â{search_text}â",
+ ),
+ )
diff --git a/docs/overrides/homepage_examples/add_interactivity_demo.html b/docs/overrides/homepage_examples/add_interactivity_demo.html
new file mode 100644
index 00000000..48ac19a7
--- /dev/null
+++ b/docs/overrides/homepage_examples/add_interactivity_demo.html
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+ example.com/videos.html
+
+
+
+
+
+
+
5 Videos
+
+
+
+
+
ReactPy: The Documentary
+
From web library to taco delivery service
+
+
+
+
+
+
+
+
Code using Worst Practices
+
Harriet Potter (2013)
+
+
+
+
+
+
+
+
Introducing ReactPy Foriegn
+
Tim Cooker (2015)
+
+
+
+
+
+
+
+
Introducing ReactPy Cooks
+
Soap Boat and Dinosaur Dan (2018)
+
+
+
+
+
+
+
+
Introducing Quantum Components
+
Isaac Asimov and Lauren-kun (2020)
+
+
+
+
+
+
+
+
+
diff --git a/docs/overrides/homepage_examples/code_block.html b/docs/overrides/homepage_examples/code_block.html
new file mode 100644
index 00000000..c1f14e5d
--- /dev/null
+++ b/docs/overrides/homepage_examples/code_block.html
@@ -0,0 +1,7 @@
+
+
+
+
+

+
+
diff --git a/docs/overrides/homepage_examples/create_user_interfaces.py b/docs/overrides/homepage_examples/create_user_interfaces.py
new file mode 100644
index 00000000..7878aa6b
--- /dev/null
+++ b/docs/overrides/homepage_examples/create_user_interfaces.py
@@ -0,0 +1,21 @@
+# ruff: noqa: INP001
+from reactpy import component, html
+
+
+def thumbnail(*_, **__): ...
+
+
+def like_button(*_, **__): ...
+
+
+@component
+def video(data):
+ return html.div(
+ thumbnail(data),
+ html.a(
+ {"href": data.url},
+ html.h3(data.title),
+ html.p(data.description),
+ ),
+ like_button(data),
+ )
diff --git a/docs/overrides/homepage_examples/create_user_interfaces_demo.html b/docs/overrides/homepage_examples/create_user_interfaces_demo.html
new file mode 100644
index 00000000..9a684d3c
--- /dev/null
+++ b/docs/overrides/homepage_examples/create_user_interfaces_demo.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
My video
+
Video description
+
+
+
+
+
diff --git a/docs/overrides/homepage_examples/write_components_with_python.py b/docs/overrides/homepage_examples/write_components_with_python.py
new file mode 100644
index 00000000..5993046c
--- /dev/null
+++ b/docs/overrides/homepage_examples/write_components_with_python.py
@@ -0,0 +1,19 @@
+# ruff: noqa: INP001
+from reactpy import component, html
+
+
+def video(*_, **__): ...
+
+
+@component
+def video_list(videos, empty_heading):
+ count = len(videos)
+ heading = empty_heading
+ if count > 0:
+ noun = "Videos" if count > 1 else "Video"
+ heading = f"{count} {noun}"
+
+ return html.section(
+ html.h2(heading),
+ [video(x, key=x.id) for x in videos],
+ )
diff --git a/docs/overrides/homepage_examples/write_components_with_python_demo.html b/docs/overrides/homepage_examples/write_components_with_python_demo.html
new file mode 100644
index 00000000..203287c6
--- /dev/null
+++ b/docs/overrides/homepage_examples/write_components_with_python_demo.html
@@ -0,0 +1,65 @@
+
+
+
3 Videos
+
+
+
+
First video
+
Video description
+
+
+
+
+
+
+
Second video
+
Video description
+
+
+
+
+
+
+
Third video
+
Video description
+
+
+
+
+
diff --git a/docs/overrides/main.html b/docs/overrides/main.html
index e70aa10c..c63ca9e7 100644
--- a/docs/overrides/main.html
+++ b/docs/overrides/main.html
@@ -11,3 +11,10 @@
{% endif %}
{% endblock %}
+
+{% block outdated %}
+You're not viewing the latest release.
+
+ Click here to go to latest.
+
+{% endblock %}
diff --git a/docs/python/auth-required-attribute.py b/docs/python/auth-required-attribute.py
deleted file mode 100644
index eaef7aec..00000000
--- a/docs/python/auth-required-attribute.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from reactpy import component, html
-
-from reactpy_django.decorators import auth_required
-
-
-@component
-@auth_required(auth_attribute="is_staff")
-def my_component():
- return html.div("I am logged in!")
diff --git a/docs/python/auth-required-custom-attribute-model.py b/docs/python/auth-required-custom-attribute-model.py
deleted file mode 100644
index cecb6fa4..00000000
--- a/docs/python/auth-required-custom-attribute-model.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from django.contrib.auth.models import AbstractBaseUser
-
-
-class CustomUserModel(AbstractBaseUser):
- @property
- def is_really_cool(self):
- return True
diff --git a/docs/python/auth-required-custom-attribute.py b/docs/python/auth-required-custom-attribute.py
deleted file mode 100644
index ad38011c..00000000
--- a/docs/python/auth-required-custom-attribute.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from reactpy import component, html
-
-from reactpy_django.decorators import auth_required
-
-
-@component
-@auth_required(auth_attribute="is_really_cool")
-def my_component():
- return html.div("I am logged in!")
diff --git a/docs/python/auth-required-vdom-fallback.py b/docs/python/auth-required-vdom-fallback.py
deleted file mode 100644
index c1a6ae3e..00000000
--- a/docs/python/auth-required-vdom-fallback.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from reactpy import component, html
-
-from reactpy_django.decorators import auth_required
-
-
-@component
-@auth_required(fallback=html.div("I am NOT logged in!"))
-def my_component():
- return html.div("I am logged in!")
diff --git a/docs/python/auth-required.py b/docs/python/auth-required.py
deleted file mode 100644
index 57337a65..00000000
--- a/docs/python/auth-required.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from reactpy import component, html
-
-from reactpy_django.decorators import auth_required
-
-
-@component
-@auth_required
-def my_component():
- return html.div("I am logged in!")
diff --git a/docs/python/configure-asgi-middleware.py b/docs/python/configure-asgi-middleware.py
deleted file mode 100644
index 0ee33a8a..00000000
--- a/docs/python/configure-asgi-middleware.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# Broken load order, only used for linting
-from channels.routing import ProtocolTypeRouter, URLRouter
-
-from reactpy_django import REACTPY_WEBSOCKET_PATH
-
-
-django_asgi_app = ""
-
-
-# start
-from channels.auth import AuthMiddlewareStack # noqa: E402
-from channels.sessions import SessionMiddlewareStack # noqa: E402
-
-
-application = ProtocolTypeRouter(
- {
- "http": django_asgi_app,
- "websocket": SessionMiddlewareStack(
- AuthMiddlewareStack(
- URLRouter(
- [REACTPY_WEBSOCKET_PATH],
- )
- )
- ),
- }
-)
diff --git a/docs/python/settings.py b/docs/python/settings.py
deleted file mode 100644
index a08dbc55..00000000
--- a/docs/python/settings.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# ReactPy requires a multiprocessing-safe and thread-safe cache.
-REACTPY_CACHE = "default"
-
-# ReactPy requires a multiprocessing-safe and thread-safe database.
-REACTPY_DATABASE = "default"
-
-# Maximum seconds between reconnection attempts before giving up.
-# Use `0` to prevent component reconnection.
-REACTPY_RECONNECT_MAX = 259200
-
-# The URL for ReactPy to serve the component rendering websocket
-REACTPY_WEBSOCKET_URL = "reactpy/"
-
-# Dotted path to the default `reactpy_django.hooks.use_query` postprocessor function, or `None`
-REACTPY_DEFAULT_QUERY_POSTPROCESSOR = "reactpy_django.utils.django_query_postprocessor"
-
-# Dotted path to the Django authentication backend to use for ReactPy components
-# This is only needed if:
-# 1. You are using `AuthMiddlewareStack` and...
-# 2. You are using Django's `AUTHENTICATION_BACKENDS` setting and...
-# 3. Your Django user model does not define a `backend` attribute
-REACTPY_AUTH_BACKEND = None
diff --git a/docs/python/template-tag-bad-view.py b/docs/python/template-tag-bad-view.py
deleted file mode 100644
index a798abb0..00000000
--- a/docs/python/template-tag-bad-view.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from django.shortcuts import render
-
-
-def example_view(request):
- context_vars = {"dont_do_this": "example_project.my_app.components.hello_world"}
- return render(request, "my-template.html", context_vars)
diff --git a/docs/python/use-query-args.py b/docs/python/use-query-args.py
deleted file mode 100644
index b2c705de..00000000
--- a/docs/python/use-query-args.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from reactpy import component
-
-from reactpy_django.hooks import use_query
-
-
-def example_query(value: int, other_value: bool = False):
- ...
-
-
-@component
-def my_component():
- query = use_query(
- example_query,
- 123,
- other_value=True,
- )
-
- return str(query.data)
diff --git a/docs/python/use-query.py b/docs/python/use-query.py
deleted file mode 100644
index 9a15c525..00000000
--- a/docs/python/use-query.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from example.models import TodoItem
-from reactpy import component, html
-
-from reactpy_django.hooks import use_query
-
-
-def get_items():
- return TodoItem.objects.all()
-
-
-@component
-def todo_list():
- item_query = use_query(get_items)
-
- if item_query.loading:
- rendered_items = html.h2("Loading...")
- elif item_query.error or not item_query.data:
- rendered_items = html.h2("Error when loading!")
- else:
- rendered_items = html.ul([html.li(item, key=item) for item in item_query.data])
-
- return html.div("Rendered items: ", rendered_items)
diff --git a/docs/python/vtc-args-kwargs.py b/docs/python/vtc-args-kwargs.py
deleted file mode 100644
index c9187415..00000000
--- a/docs/python/vtc-args-kwargs.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from django.http import HttpResponse
-from reactpy import component, html
-
-from reactpy_django.components import view_to_component
-
-
-@view_to_component
-def hello_world_view(request, arg1, arg2, key1=None, key2=None):
- return HttpResponse(f"Hello World! {arg1} {arg2} {key1} {key2}")
-
-
-@component
-def my_component():
- return html.div(
- hello_world_view(
- None, # Your request object (optional)
- "value_1",
- "value_2",
- key1="abc",
- key2="123",
- ),
- )
diff --git a/docs/python/vtc-cbv-compatibility.py b/docs/python/vtc-cbv-compatibility.py
deleted file mode 100644
index 9667562c..00000000
--- a/docs/python/vtc-cbv-compatibility.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from django.contrib.auth.decorators import user_passes_test
-from django.utils.decorators import method_decorator
-from django.views.generic import TemplateView
-
-from reactpy_django.components import view_to_component
-
-
-@view_to_component(compatibility=True)
-@method_decorator(user_passes_test(lambda u: u.is_superuser), name="dispatch") # type: ignore[union-attr]
-class ExampleView(TemplateView):
- ...
diff --git a/docs/python/vtc-cbv.py b/docs/python/vtc-cbv.py
deleted file mode 100644
index d9b5ed29..00000000
--- a/docs/python/vtc-cbv.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from django.http import HttpResponse
-from django.views import View
-from reactpy import component, html
-
-from reactpy_django.components import view_to_component
-
-
-class HelloWorldView(View):
- def get(self, request):
- return HttpResponse("Hello World!")
-
-
-vtc = view_to_component(HelloWorldView)
-
-
-@component
-def my_component():
- return html.div(
- vtc(),
- )
diff --git a/docs/python/vtc-compatibility.py b/docs/python/vtc-compatibility.py
deleted file mode 100644
index 337096b7..00000000
--- a/docs/python/vtc-compatibility.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from django.http import HttpResponse
-from reactpy import component, html
-
-from reactpy_django.components import view_to_component
-
-
-@view_to_component(compatibility=True)
-def hello_world_view(request):
- return HttpResponse("Hello World!")
-
-
-@component
-def my_component():
- return html.div(
- hello_world_view(),
- )
diff --git a/docs/python/vtc-fbv-compat.py b/docs/python/vtc-fbv-compat.py
deleted file mode 100644
index 3cb8016c..00000000
--- a/docs/python/vtc-fbv-compat.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from django.contrib.auth.decorators import user_passes_test
-
-from reactpy_django.components import view_to_component
-
-
-@view_to_component(compatibility=True)
-@user_passes_test(lambda u: u.is_superuser) # type: ignore[union-attr]
-def example_view(request):
- ...
diff --git a/docs/python/vtc-request.py b/docs/python/vtc-request.py
deleted file mode 100644
index 9eff206c..00000000
--- a/docs/python/vtc-request.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from django.http import HttpRequest, HttpResponse
-from reactpy import component, html
-
-from reactpy_django.components import view_to_component
-
-
-example_request = HttpRequest()
-example_request.method = "PUT"
-
-
-@view_to_component
-def hello_world_view(request):
- return HttpResponse(f"Hello World! {request.method}")
-
-
-@component
-def my_component():
- return html.div(
- hello_world_view(
- example_request,
- ),
- )
diff --git a/docs/python/vtc-strict-parsing.py b/docs/python/vtc-strict-parsing.py
deleted file mode 100644
index 1946ca93..00000000
--- a/docs/python/vtc-strict-parsing.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from django.http import HttpResponse
-from reactpy import component, html
-
-from reactpy_django.components import view_to_component
-
-
-@view_to_component(strict_parsing=False)
-def hello_world_view(request):
- return HttpResponse("
Hello World ")
-
-
-@component
-def my_component():
- return html.div(
- hello_world_view(),
- )
diff --git a/docs/python/vtc.py b/docs/python/vtc.py
deleted file mode 100644
index 47618afc..00000000
--- a/docs/python/vtc.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from django.http import HttpResponse
-from reactpy import component, html
-
-from reactpy_django.components import view_to_component
-
-
-@view_to_component
-def hello_world_view(request):
- return HttpResponse("Hello World!")
-
-
-@component
-def my_component():
- return html.div(
- hello_world_view(),
- )
diff --git a/docs/src/about/changelog.md b/docs/src/about/changelog.md
new file mode 100644
index 00000000..e08ee1f9
--- /dev/null
+++ b/docs/src/about/changelog.md
@@ -0,0 +1,20 @@
+---
+hide:
+ - toc
+---
+
+
+
+
+
+{% include-markdown "../../../CHANGELOG.md" start="" end="" %}
+
+
+
+---
+
+{% include-markdown "../../../CHANGELOG.md" start="" %}
diff --git a/docs/src/about/contributing.md b/docs/src/about/contributing.md
new file mode 100644
index 00000000..06499c3e
--- /dev/null
+++ b/docs/src/about/contributing.md
@@ -0,0 +1,94 @@
+## Overview
+
+
+
+ You will need to set up a Python environment to develop ReactPy-Django.
+
+
+
+!!! abstract "Note"
+
+ Looking to contribute features that are not Django specific?
+
+ Everything within the `reactpy-django` repository must be specific to Django integration. Check out the [ReactPy Core documentation](https://reactpy.dev/docs/about/contributor-guide.html) to contribute general features such as components, hooks, and events.
+
+---
+
+## Creating a development environment
+
+If you plan to make code changes to this repository, you will need to install the following dependencies first:
+
+- [Git](https://git-scm.com/downloads)
+- [Python 3.9+](https://www.python.org/downloads/)
+- [Hatch](https://hatch.pypa.io/latest/)
+- [Bun](https://bun.sh/)
+
+Once you finish installing these dependencies, you can clone this repository:
+
+```bash linenums="0"
+git clone https://github.com/reactive-python/reactpy-django.git
+cd reactpy-django
+```
+
+## Executing test environment commands
+
+By utilizing `hatch`, the following commands are available to manage the development environment.
+
+### Tests
+
+| Command | Description |
+| --- | --- |
+| `hatch test` | Run Python tests using the current environment's Python version |
+| `hatch test --all` | Run tests using all compatible Python versions |
+| `hatch test --python 3.9` | Run tests using a specific Python version |
+| `hatch test --include "django=5.1"` | Run tests using a specific Django version |
+| `hatch test -k test_object_in_templatetag` | Run only a specific test |
+| `hatch test --ds test_app.settings_multi_db` | Run tests with a specific Django settings file |
+| `hatch run django:runserver` | Manually run the Django development server without running tests |
+
+??? question "What other arguments are available to me?"
+
+ The `hatch test` command is a wrapper for `pytest`. Hatch "intercepts" a handful of arguments, which can be previewed by typing `hatch test --help`.
+
+ Any additional arguments in the `test` command are provided directly to pytest. See the [pytest documentation](https://docs.pytest.org/en/stable/reference/reference.html#command-line-flags) for what additional arguments are available.
+
+### Linting and Formatting
+
+| Command | Description |
+| --- | --- |
+| `hatch fmt` | Run all linters and formatters |
+| `hatch fmt --check` | Run all linters and formatters, but do not save fixes to the disk |
+| `hatch fmt --linter` | Run only linters |
+| `hatch fmt --formatter` | Run only formatters |
+| `hatch run javascript:check` | Run the JavaScript linter/formatter |
+| `hatch run javascript:fix` | Run the JavaScript linter/formatter and write fixes to disk |
+| `hatch run python:type_check` | Run the Python type checker |
+
+??? tip "Configure your IDE for linting"
+
+ This repository uses `hatch fmt` for linting and formatting, which is a [modestly customized](https://hatch.pypa.io/latest/config/internal/static-analysis/#default-settings) version of [`ruff`](https://github.com/astral-sh/ruff).
+
+ You can install `ruff` as a plugin to your preferred code editor to create a similar environment.
+
+### Documentation
+
+| Command | Description |
+| --- | --- |
+| `hatch run docs:serve` | Start the [`mkdocs`](https://www.mkdocs.org/) server to view documentation locally |
+| `hatch run docs:build` | Build the documentation |
+| `hatch run docs:linkcheck` | Check for broken links in the documentation |
+| `hatch fmt docs --check` | Run linter on code examples in the documentation |
+
+### Environment Management
+
+| Command | Description |
+| --- | --- |
+| `hatch build --clean` | Build the package from source |
+| `hatch env prune` | Delete all virtual environments created by `hatch` |
+| `hatch python install 3.12` | Install a specific Python version to your system |
+
+??? tip "Check out Hatch for all available commands!"
+
+ This documentation only covers commonly used commands.
+
+ You can type `hatch --help` to see all available commands.
diff --git a/docs/src/about/license.md b/docs/src/about/license.md
new file mode 100644
index 00000000..15d975db
--- /dev/null
+++ b/docs/src/about/license.md
@@ -0,0 +1,8 @@
+---
+hide:
+ - toc
+---
+
+---
+
+{% include "../../../LICENSE.md" %}
diff --git a/docs/src/assets/css/admonition.css b/docs/src/assets/css/admonition.css
new file mode 100644
index 00000000..c93892a8
--- /dev/null
+++ b/docs/src/assets/css/admonition.css
@@ -0,0 +1,161 @@
+[data-md-color-scheme="slate"] {
+ --admonition-border-color: transparent;
+ --admonition-expanded-border-color: rgba(255, 255, 255, 0.1);
+ --note-bg-color: rgba(43, 110, 98, 0.2);
+ --terminal-bg-color: #0c0c0c;
+ --terminal-title-bg-color: #000;
+ --deep-dive-bg-color: rgba(43, 52, 145, 0.2);
+ --you-will-learn-bg-color: #353a45;
+ --pitfall-bg-color: rgba(182, 87, 0, 0.2);
+}
+[data-md-color-scheme="default"] {
+ --admonition-border-color: rgba(0, 0, 0, 0.08);
+ --admonition-expanded-border-color: var(--admonition-border-color);
+ --note-bg-color: rgb(244, 251, 249);
+ --terminal-bg-color: rgb(64, 71, 86);
+ --terminal-title-bg-color: rgb(35, 39, 47);
+ --deep-dive-bg-color: rgb(243, 244, 253);
+ --you-will-learn-bg-color: rgb(246, 247, 249);
+ --pitfall-bg-color: rgb(254, 245, 231);
+}
+
+.md-typeset details,
+.md-typeset .admonition {
+ border-color: var(--admonition-border-color) !important;
+ box-shadow: none;
+}
+
+.md-typeset :is(.admonition, details) {
+ margin: 0 0;
+}
+
+.md-typeset .admonition {
+ font-size: 0.7rem;
+}
+
+.md-typeset .admonition:focus-within,
+.md-typeset details:focus-within {
+ box-shadow: none !important;
+}
+
+.md-typeset details[open] {
+ border-color: var(--admonition-expanded-border-color) !important;
+}
+
+/*
+Admonition: "summary"
+React Name: "You will learn"
+*/
+.md-typeset .admonition.summary {
+ background: var(--you-will-learn-bg-color);
+ padding: 0.8rem 1.4rem;
+ border-radius: 0.8rem;
+}
+
+.md-typeset .summary .admonition-title {
+ font-size: 1rem;
+ background: transparent;
+ padding-left: 0.6rem;
+ padding-bottom: 0;
+}
+
+.md-typeset .summary .admonition-title:before {
+ display: none;
+}
+
+.md-typeset .admonition.summary {
+ border-color: #ffffff17 !important;
+}
+
+/*
+Admonition: "abstract"
+React Name: "Note"
+*/
+.md-typeset .admonition.abstract {
+ background: var(--note-bg-color);
+ padding: 0.8rem 1.4rem;
+ border-radius: 0.8rem;
+}
+
+.md-typeset .abstract .admonition-title {
+ font-size: 1rem;
+ background: transparent;
+ padding-bottom: 0;
+ color: rgb(68, 172, 153);
+}
+
+.md-typeset .abstract .admonition-title:before {
+ font-size: 1.1rem;
+ background: rgb(68, 172, 153);
+}
+
+/*
+Admonition: "warning"
+React Name: "Pitfall"
+*/
+.md-typeset .admonition.warning {
+ background: var(--pitfall-bg-color);
+ padding: 0.8rem 1.4rem;
+ border-radius: 0.8rem;
+}
+
+.md-typeset .warning .admonition-title {
+ font-size: 1rem;
+ background: transparent;
+ padding-bottom: 0;
+ color: rgb(219, 125, 39);
+}
+
+.md-typeset .warning .admonition-title:before {
+ font-size: 1.1rem;
+ background: rgb(219, 125, 39);
+}
+
+/*
+Admonition: "info"
+React Name: "Deep Dive"
+*/
+.md-typeset .admonition.info {
+ background: var(--deep-dive-bg-color);
+ padding: 0.8rem 1.4rem;
+ border-radius: 0.8rem;
+}
+
+.md-typeset .info .admonition-title {
+ font-size: 1rem;
+ background: transparent;
+ padding-bottom: 0;
+ color: rgb(136, 145, 236);
+}
+
+.md-typeset .info .admonition-title:before {
+ font-size: 1.1rem;
+ background: rgb(136, 145, 236);
+}
+
+/*
+Admonition: "example"
+React Name: "Terminal"
+*/
+.md-typeset .admonition.example {
+ background: var(--terminal-bg-color);
+ border-radius: 0.4rem;
+ overflow: hidden;
+ border: none;
+ margin: 0.5rem 0;
+}
+
+.md-typeset .example .admonition-title {
+ background: var(--terminal-title-bg-color);
+ color: rgb(246, 247, 249);
+}
+
+.md-typeset .example .admonition-title:before {
+ background: rgb(246, 247, 249);
+}
+
+.md-typeset .admonition.example code {
+ background: transparent;
+ color: #fff;
+ box-shadow: none;
+}
diff --git a/docs/src/assets/css/banner.css b/docs/src/assets/css/banner.css
new file mode 100644
index 00000000..3739a73c
--- /dev/null
+++ b/docs/src/assets/css/banner.css
@@ -0,0 +1,15 @@
+body[data-md-color-scheme="slate"] {
+ --md-banner-bg-color: rgb(55, 81, 78);
+ --md-banner-font-color: #fff;
+}
+
+body[data-md-color-scheme="default"] {
+ --md-banner-bg-color: #ff9;
+ --md-banner-font-color: #000;
+}
+
+.md-banner--warning {
+ background-color: var(--md-banner-bg-color);
+ color: var(--md-banner-font-color);
+ text-align: center;
+}
diff --git a/docs/src/assets/css/button.css b/docs/src/assets/css/button.css
new file mode 100644
index 00000000..8f71391a
--- /dev/null
+++ b/docs/src/assets/css/button.css
@@ -0,0 +1,41 @@
+[data-md-color-scheme="slate"] {
+ --md-button-font-color: #fff;
+ --md-button-border-color: #404756;
+}
+
+[data-md-color-scheme="default"] {
+ --md-button-font-color: #000;
+ --md-button-border-color: #8d8d8d;
+}
+
+.md-typeset .md-button {
+ border-width: 1px;
+ border-color: var(--md-button-border-color);
+ border-radius: 9999px;
+ color: var(--md-button-font-color);
+ transition: color 125ms, background 125ms, border-color 125ms,
+ transform 125ms;
+}
+
+.md-typeset .md-button:focus,
+.md-typeset .md-button:hover {
+ border-color: var(--md-button-border-color);
+ color: var(--md-button-font-color);
+ background: rgba(78, 87, 105, 0.05);
+}
+
+.md-typeset .md-button.md-button--primary {
+ color: #fff;
+ border-color: transparent;
+ background: var(--reactpy-color-dark);
+}
+
+.md-typeset .md-button.md-button--primary:focus,
+.md-typeset .md-button.md-button--primary:hover {
+ border-color: transparent;
+ background: var(--reactpy-color-darker);
+}
+
+.md-typeset .md-button:focus {
+ transform: scale(0.98);
+}
diff --git a/docs/src/assets/css/code.css b/docs/src/assets/css/code.css
new file mode 100644
index 00000000..c5465498
--- /dev/null
+++ b/docs/src/assets/css/code.css
@@ -0,0 +1,111 @@
+:root {
+ --code-max-height: 17.25rem;
+ --md-code-backdrop: rgba(0, 0, 0, 0) 0px 0px 0px 0px,
+ rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.03) 0px 0.8px 2px 0px,
+ rgba(0, 0, 0, 0.047) 0px 2.7px 6.7px 0px,
+ rgba(0, 0, 0, 0.08) 0px 12px 30px 0px;
+}
+[data-md-color-scheme="slate"] {
+ --md-code-hl-color: #ffffcf1c;
+ --md-code-bg-color: #16181d;
+ --md-code-hl-comment-color: hsla(var(--md-hue), 75%, 90%, 0.43);
+ --code-tab-color: rgb(52, 58, 70);
+ --md-code-hl-name-color: #aadafc;
+ --md-code-hl-string-color: hsl(21 49% 63% / 1);
+ --md-code-hl-keyword-color: hsl(289.67deg 35% 60%);
+ --md-code-hl-constant-color: hsl(213.91deg 68% 61%);
+ --md-code-hl-number-color: #bfd9ab;
+ --func-and-decorator-color: #dcdcae;
+ --module-import-color: #60c4ac;
+}
+[data-md-color-scheme="default"] {
+ --md-code-hl-color: #ffffcf1c;
+ --md-code-bg-color: rgba(208, 211, 220, 0.4);
+ --md-code-fg-color: rgb(64, 71, 86);
+ --code-tab-color: #fff;
+ --func-and-decorator-color: var(--md-code-hl-function-color);
+ --module-import-color: #e153e5;
+}
+[data-md-color-scheme="default"] .md-typeset .highlight > pre > code,
+[data-md-color-scheme="default"] .md-typeset .highlight > table.highlighttable {
+ --md-code-bg-color: #fff;
+}
+
+/* All code blocks */
+.md-typeset pre > code {
+ max-height: var(--code-max-height);
+}
+
+/* Code blocks with no line number */
+.md-typeset .highlight > pre > code {
+ border-radius: 16px;
+ max-height: var(--code-max-height);
+ box-shadow: var(--md-code-backdrop);
+}
+
+/* Code blocks with line numbers */
+.md-typeset .highlighttable .linenos {
+ max-height: var(--code-max-height);
+ overflow: hidden;
+}
+.md-typeset .highlighttable {
+ box-shadow: var(--md-code-backdrop);
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+/* Tabbed code blocks */
+.md-typeset .tabbed-set {
+ box-shadow: var(--md-code-backdrop);
+ border-radius: 8px;
+ overflow: hidden;
+ border: 1px solid var(--md-default-fg-color--lightest);
+}
+.md-typeset .tabbed-set .tabbed-block {
+ overflow: hidden;
+}
+.js .md-typeset .tabbed-set .tabbed-labels {
+ background: var(--code-tab-color);
+ margin: 0;
+ padding-left: 0.8rem;
+}
+.md-typeset .tabbed-set .tabbed-labels > label {
+ font-weight: 400;
+ font-size: 0.7rem;
+ padding-top: 0.55em;
+ padding-bottom: 0.35em;
+}
+.md-typeset .tabbed-set .highlighttable {
+ border-radius: 0;
+}
+
+/* Code hightlighting colors */
+
+/* Module imports */
+.highlight .nc,
+.highlight .ne,
+.highlight .nn,
+.highlight .nv {
+ color: var(--module-import-color);
+}
+
+/* Function def name and decorator */
+.highlight .nd,
+.highlight .nf {
+ color: var(--func-and-decorator-color);
+}
+
+/* None type */
+.highlight .kc {
+ color: var(--md-code-hl-constant-color);
+}
+
+/* Keywords such as def and return */
+.highlight .k {
+ color: var(--md-code-hl-constant-color);
+}
+
+/* HTML tags */
+.highlight .nt {
+ color: var(--md-code-hl-constant-color);
+}
diff --git a/docs/src/assets/css/footer.css b/docs/src/assets/css/footer.css
new file mode 100644
index 00000000..b3408286
--- /dev/null
+++ b/docs/src/assets/css/footer.css
@@ -0,0 +1,33 @@
+[data-md-color-scheme="slate"] {
+ --md-footer-bg-color: var(--md-default-bg-color);
+ --md-footer-bg-color--dark: var(--md-default-bg-color);
+ --md-footer-border-color: var(--md-header-border-color);
+}
+
+[data-md-color-scheme="default"] {
+ --md-footer-fg-color: var(--md-typeset-color);
+ --md-footer-fg-color--light: var(--md-typeset-color);
+ --md-footer-bg-color: var(--md-default-bg-color);
+ --md-footer-bg-color--dark: var(--md-default-bg-color);
+ --md-footer-border-color: var(--md-header-border-color);
+}
+
+.md-footer {
+ border-top: 1px solid var(--md-footer-border-color);
+}
+
+.md-copyright {
+ width: 100%;
+}
+
+.md-copyright__highlight {
+ width: 100%;
+}
+
+.legal-footer-right {
+ float: right;
+}
+
+.md-copyright__highlight div {
+ display: inline;
+}
diff --git a/docs/src/assets/css/home.css b/docs/src/assets/css/home.css
new file mode 100644
index 00000000..70f05cf2
--- /dev/null
+++ b/docs/src/assets/css/home.css
@@ -0,0 +1,337 @@
+img.home-logo {
+ height: 120px;
+}
+
+.home .row {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+ padding: 6rem 0.8rem;
+}
+
+.home .row:not(.first, .stripe) {
+ background: var(--row-bg-color);
+}
+
+.home .row.stripe {
+ background: var(--row-stripe-bg-color);
+ border: 0 solid var(--stripe-border-color);
+ border-top-width: 1px;
+ border-bottom-width: 1px;
+}
+
+.home .row.first {
+ text-align: center;
+}
+
+.home .row h1 {
+ max-width: 28rem;
+ line-height: 1.15;
+ font-weight: 500;
+ margin-bottom: 0.55rem;
+ margin-top: -1rem;
+}
+
+.home .row.first h1 {
+ margin-top: 0.55rem;
+ margin-bottom: -0.75rem;
+}
+
+.home .row > p {
+ max-width: 35rem;
+ line-height: 1.5;
+ font-weight: 400;
+}
+
+.home .row.first > p {
+ font-size: 32px;
+ font-weight: 500;
+}
+
+/* Code blocks */
+.home .row .tabbed-set {
+ background: var(--home-tabbed-set-bg-color);
+ margin: 0;
+}
+
+.home .row .tabbed-content {
+ padding: 20px 18px;
+ overflow-x: auto;
+}
+
+.home .row .tabbed-content img {
+ user-select: none;
+ -moz-user-select: none;
+ -webkit-user-drag: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ max-width: 580px;
+}
+
+.home .row .tabbed-content {
+ -webkit-filter: var(--code-block-filter);
+ filter: var(--code-block-filter);
+}
+
+/* Code examples */
+.home .example-container {
+ background: radial-gradient(
+ circle at 0% 100%,
+ rgb(41 84 147 / 11%) 0%,
+ rgb(22 89 189 / 4%) 70%,
+ rgb(48 99 175 / 0%) 80%
+ ),
+ radial-gradient(
+ circle at 100% 100%,
+ rgb(24 87 45 / 55%) 0%,
+ rgb(29 61 12 / 4%) 70%,
+ rgb(94 116 93 / 0%) 80%
+ ),
+ radial-gradient(
+ circle at 100% 0%,
+ rgba(54, 66, 84, 0.55) 0%,
+ rgb(102 111 125 / 4%) 70%,
+ rgba(54, 66, 84, 0) 80%
+ ),
+ radial-gradient(
+ circle at 0% 0%,
+ rgba(91, 114, 135, 0.55) 0%,
+ rgb(45 111 171 / 4%) 70%,
+ rgb(5 82 153 / 0%) 80%
+ ),
+ rgb(0, 0, 0) center center/cover no-repeat fixed;
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ align-items: center;
+ border-radius: 16px;
+ margin: 30px 0;
+ max-width: 100%;
+ grid-column-gap: 20px;
+ padding-left: 20px;
+ padding-right: 20px;
+}
+
+.home .demo .white-bg {
+ background: #fff;
+ border-radius: 16px;
+ display: flex;
+ flex-direction: column;
+ max-width: 590px;
+ min-width: -webkit-min-content;
+ min-width: -moz-min-content;
+ min-width: min-content;
+ row-gap: 1rem;
+ padding: 1rem;
+ border: 1px rgb(0 0 0 / 20%) solid;
+ overflow: hidden;
+}
+
+.home .demo .vid-row {
+ display: flex;
+ flex-direction: row;
+ -moz-column-gap: 12px;
+ column-gap: 12px;
+}
+
+.home .demo {
+ color: #000;
+}
+
+.home .demo .vid-thumbnail {
+ background: radial-gradient(
+ circle at 0% 100%,
+ rgb(41 84 147 / 55%) 0%,
+ rgb(22 89 189 / 4%) 70%,
+ rgb(48 99 175 / 0%) 80%
+ ),
+ radial-gradient(
+ circle at 100% 100%,
+ rgb(24 63 87 / 55%) 0%,
+ rgb(29 61 12 / 4%) 70%,
+ rgb(94 116 93 / 0%) 80%
+ ),
+ radial-gradient(
+ circle at 100% 0%,
+ rgba(54, 66, 84, 0.55) 0%,
+ rgb(102 111 125 / 4%) 70%,
+ rgba(54, 66, 84, 0) 80%
+ ),
+ radial-gradient(
+ circle at 0% 0%,
+ rgba(91, 114, 135, 0.55) 0%,
+ rgb(45 111 171 / 4%) 70%,
+ rgb(5 82 153 / 0%) 80%
+ ),
+ rgb(0, 0, 0) center center/cover no-repeat fixed;
+ width: 9rem;
+ aspect-ratio: 16 / 9;
+ border-radius: 8px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.home .demo .vid-text {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: flex-start;
+ width: 100%;
+}
+
+.home .demo h2 {
+ font-size: 18px;
+ line-height: 1.375;
+ margin: 0;
+ text-align: left;
+ font-weight: 700;
+}
+
+.home .demo h3 {
+ font-size: 16px;
+ line-height: 1.25;
+ margin: 0;
+}
+
+.home .demo p {
+ font-size: 14px;
+ line-height: 1.375;
+ margin: 0;
+}
+
+.home .demo .browser-nav-url {
+ background: rgba(153, 161, 179, 0.2);
+ border-radius: 9999px;
+ font-size: 14px;
+ color: grey;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ -moz-column-gap: 5px;
+ column-gap: 5px;
+}
+
+.home .demo .browser-navbar {
+ margin: -1rem;
+ margin-bottom: 0;
+ padding: 0.75rem 1rem;
+ border-bottom: 1px solid darkgrey;
+}
+
+.home .demo .browser-viewport {
+ background: #fff;
+ border-radius: 16px;
+ display: flex;
+ flex-direction: column;
+ row-gap: 1rem;
+ height: 400px;
+ overflow-y: scroll;
+ margin: -1rem;
+ padding: 1rem;
+}
+
+.home .demo .browser-viewport .search-header > h1 {
+ color: #000;
+ text-align: left;
+ font-size: 24px;
+ margin: 0;
+}
+
+.home .demo .browser-viewport .search-header > p {
+ text-align: left;
+ font-size: 16px;
+ margin: 10px 0;
+}
+
+.home .demo .search-bar input {
+ width: 100%;
+ background: rgba(153, 161, 179, 0.2);
+ border-radius: 9999px;
+ padding-left: 40px;
+ padding-right: 40px;
+ height: 40px;
+ color: #000;
+}
+
+.home .demo .search-bar svg {
+ height: 40px;
+ position: absolute;
+ transform: translateX(75%);
+}
+
+.home .demo .search-bar {
+ position: relative;
+}
+
+/* Desktop Styling */
+@media screen and (min-width: 60em) {
+ .home .row {
+ text-align: center;
+ }
+ .home .row > p {
+ font-size: 21px;
+ }
+ .home .row > h1 {
+ font-size: 52px;
+ }
+ .home .row .pop-left {
+ margin-left: -20px;
+ margin-right: 0;
+ margin-top: -20px;
+ margin-bottom: -20px;
+ }
+ .home .row .pop-right {
+ margin-left: 0px;
+ margin-right: 0px;
+ margin-top: -20px;
+ margin-bottom: -20px;
+ }
+}
+
+/* Mobile Styling */
+@media screen and (max-width: 60em) {
+ .home .row {
+ padding: 4rem 0.8rem;
+ }
+ .home .row > h1,
+ .home .row > p {
+ padding-left: 1rem;
+ padding-right: 1rem;
+ }
+ .home .row.first {
+ padding-top: 2rem;
+ }
+ .home-btns {
+ width: 100%;
+ display: grid;
+ grid-gap: 0.5rem;
+ gap: 0.5rem;
+ }
+ .home .example-container {
+ display: flex;
+ flex-direction: column;
+ row-gap: 20px;
+ width: 100%;
+ justify-content: center;
+ border-radius: 0;
+ padding: 1rem 0;
+ }
+ .home .row {
+ padding-left: 0;
+ padding-right: 0;
+ }
+ .home .tabbed-set {
+ width: 100%;
+ border-radius: 0;
+ }
+ .home .demo {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ }
+ .home .demo > .white-bg {
+ width: 80%;
+ max-width: 80%;
+ }
+}
diff --git a/docs/src/assets/css/main.css b/docs/src/assets/css/main.css
new file mode 100644
index 00000000..6eefdf2f
--- /dev/null
+++ b/docs/src/assets/css/main.css
@@ -0,0 +1,85 @@
+/* Variable overrides */
+:root {
+ --reactpy-color: #58b962;
+ --reactpy-color-dark: #42914a;
+ --reactpy-color-darker: #34743b;
+ --reactpy-color-opacity-10: rgba(88, 185, 98, 0.1);
+}
+
+[data-md-color-accent="red"] {
+ --md-primary-fg-color--light: var(--reactpy-color);
+ --md-primary-fg-color--dark: var(--reactpy-color-dark);
+}
+
+[data-md-color-scheme="slate"] {
+ --md-default-bg-color: rgb(35, 39, 47);
+ --md-default-bg-color--light: hsla(var(--md-hue), 15%, 16%, 0.54);
+ --md-default-bg-color--lighter: hsla(var(--md-hue), 15%, 16%, 0.26);
+ --md-default-bg-color--lightest: hsla(var(--md-hue), 15%, 16%, 0.07);
+ --md-primary-fg-color: var(--md-default-bg-color);
+ --md-default-fg-color: hsla(var(--md-hue), 75%, 95%, 1);
+ --md-default-fg-color--light: #fff;
+ --md-typeset-a-color: var(--reactpy-color);
+ --md-accent-fg-color: var(--reactpy-color-dark);
+}
+
+[data-md-color-scheme="default"] {
+ --md-primary-fg-color: var(--md-default-bg-color);
+ --md-default-fg-color--light: #000;
+ --md-default-fg-color--lighter: #0000007e;
+ --md-default-fg-color--lightest: #00000029;
+ --md-typeset-color: rgb(35, 39, 47);
+ --md-typeset-a-color: var(--reactpy-color);
+ --md-accent-fg-color: var(--reactpy-color-dark);
+}
+
+/* Font changes */
+.md-typeset {
+ font-weight: 300;
+}
+
+.md-typeset h1 {
+ font-weight: 600;
+ margin: 0;
+ font-size: 2.5em;
+}
+
+.md-typeset h2 {
+ font-weight: 500;
+}
+
+.md-typeset h3 {
+ font-weight: 400;
+}
+
+/* Intro section styling */
+p.intro {
+ font-size: 0.9rem;
+ font-weight: 500;
+}
+
+/* Hide "Overview" jump selector */
+h2#overview {
+ visibility: hidden;
+ height: 0;
+ margin: 0;
+ padding: 0;
+}
+
+/* Reduce size of the outdated banner */
+.md-banner__inner {
+ margin: 0.45rem auto;
+}
+
+/* Desktop Styles */
+@media screen and (min-width: 60em) {
+ /* Remove max width on desktop */
+ .md-grid {
+ max-width: none;
+ }
+}
+
+/* Max size of page content */
+.md-content {
+ max-width: 56rem;
+}
diff --git a/docs/src/assets/css/navbar.css b/docs/src/assets/css/navbar.css
new file mode 100644
index 00000000..33e8b14f
--- /dev/null
+++ b/docs/src/assets/css/navbar.css
@@ -0,0 +1,185 @@
+[data-md-color-scheme="slate"] {
+ --md-header-border-color: rgb(255 255 255 / 5%);
+ --md-version-bg-color: #ffffff0d;
+}
+
+[data-md-color-scheme="default"] {
+ --md-header-border-color: rgb(0 0 0 / 7%);
+ --md-version-bg-color: #ae58ee2e;
+}
+
+.md-header {
+ border: 0 solid transparent;
+ border-bottom-width: 1px;
+}
+
+.md-header--shadow {
+ box-shadow: none;
+ border-color: var(--md-header-border-color);
+ transition: border-color 0.35s cubic-bezier(0.1, 0.7, 0.1, 1);
+}
+
+/* Version selector */
+.md-header__topic .md-ellipsis,
+.md-header__title [data-md-component="header-topic"] {
+ display: none;
+}
+
+[dir="ltr"] .md-version__current {
+ margin: 0;
+}
+
+.md-version__list {
+ margin: 0;
+ left: 0;
+ right: 0;
+ top: 2.5rem;
+}
+
+.md-version {
+ background: var(--md-version-bg-color);
+ border-radius: 999px;
+ padding: 0 0.8rem;
+ margin: 0.3rem 0;
+ height: 1.8rem;
+ display: flex;
+ font-size: 0.7rem;
+}
+
+/* Mobile Styling */
+@media screen and (max-width: 60em) {
+ label.md-header__button.md-icon[for="__drawer"] {
+ order: 1;
+ }
+ .md-header__button.md-logo {
+ display: initial;
+ order: 2;
+ margin-right: auto;
+ }
+ .md-header__title {
+ order: 3;
+ }
+ .md-header__button[for="__search"] {
+ order: 4;
+ }
+ .md-header__option[data-md-component="palette"] {
+ order: 5;
+ }
+ .md-header__source {
+ display: initial;
+ order: 6;
+ }
+ .md-header__source .md-source__repository {
+ display: none;
+ }
+}
+
+/* Desktop Styling */
+@media screen and (min-width: 60em) {
+ /* Nav container */
+ nav.md-header__inner {
+ display: contents;
+ }
+ header.md-header {
+ display: flex;
+ align-items: center;
+ }
+
+ /* Logo */
+ .md-header__button.md-logo {
+ order: 1;
+ padding-right: 0.4rem;
+ padding-top: 0;
+ padding-bottom: 0;
+ }
+ .md-header__button.md-logo img {
+ height: 2rem;
+ }
+
+ /* Version selector */
+ [dir="ltr"] .md-header__title {
+ order: 2;
+ margin: 0;
+ margin-right: 0.8rem;
+ margin-left: 0.2rem;
+ flex-grow: 0;
+ }
+ .md-header__topic {
+ position: relative;
+ }
+ .md-header__title--active .md-header__topic {
+ transform: none;
+ opacity: 1;
+ pointer-events: auto;
+ z-index: 4;
+ }
+
+ /* Search */
+ .md-search {
+ order: 3;
+ width: 100%;
+ margin-right: 0.6rem;
+ }
+ .md-search__inner {
+ width: 100%;
+ float: unset !important;
+ }
+ .md-search__form {
+ border-radius: 9999px;
+ }
+ [data-md-toggle="search"]:checked ~ .md-header .md-header__option {
+ max-width: unset;
+ opacity: unset;
+ transition: unset;
+ }
+
+ /* Tabs */
+ .md-tabs {
+ order: 4;
+ min-width: -webkit-fit-content;
+ min-width: -moz-fit-content;
+ min-width: fit-content;
+ width: -webkit-fit-content;
+ width: -moz-fit-content;
+ width: fit-content;
+ z-index: -1;
+ overflow: visible;
+ border: none !important;
+ }
+ li.md-tabs__item.md-tabs__item--active {
+ background: var(--reactpy-color-opacity-10);
+ border-radius: 9999px;
+ color: var(--md-typeset-a-color);
+ }
+ .md-tabs__link {
+ margin: 0;
+ }
+ .md-tabs__item {
+ height: 1.8rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+ }
+
+ /* Dark/Light Selector */
+ .md-header__option[data-md-component="palette"] {
+ order: 5;
+ }
+
+ /* GitHub info */
+ .md-header__source {
+ order: 6;
+ margin-left: 0 !important;
+ }
+}
+
+/* Ultrawide Desktop Styles */
+@media screen and (min-width: 1919px) {
+ .md-search {
+ order: 2;
+ width: 100%;
+ max-width: 34.4rem;
+ margin: 0 auto;
+ }
+}
diff --git a/docs/src/assets/css/sidebar.css b/docs/src/assets/css/sidebar.css
new file mode 100644
index 00000000..b6507d96
--- /dev/null
+++ b/docs/src/assets/css/sidebar.css
@@ -0,0 +1,104 @@
+:root {
+ --sizebar-font-size: 0.62rem;
+}
+
+.md-nav__link {
+ word-break: break-word;
+}
+
+/* Desktop Styling */
+@media screen and (min-width: 76.1875em) {
+ /* Move the sidebar and TOC to the edge of the page */
+ .md-main__inner.md-grid {
+ margin-left: 0;
+ margin-right: 0;
+ max-width: unset;
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+ }
+
+ .md-content {
+ justify-self: center;
+ width: 100%;
+ }
+ /* Made the sidebar buttons look React-like */
+ .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link {
+ text-transform: uppercase;
+ }
+
+ .md-nav__title[for="__toc"] {
+ text-transform: uppercase;
+ margin: 0.5rem;
+ }
+
+ .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link {
+ color: rgb(133, 142, 159);
+ margin: 0.5rem;
+ }
+
+ .md-nav__item .md-nav__link {
+ position: relative;
+ }
+
+ .md-nav__link:is(:focus, :hover):not(.md-nav__link--active) {
+ color: unset;
+ }
+
+ .md-nav__item
+ .md-nav__link:is(:focus, :hover):not(.md-nav__link--active):before {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ opacity: 0.2;
+ z-index: -1;
+ background: grey;
+ }
+
+ .md-nav__item .md-nav__link--active:before {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: -1;
+ background: var(--reactpy-color-opacity-10);
+ }
+
+ .md-nav__link {
+ padding: 0.5rem 0.5rem 0.5rem 1rem;
+ margin: 0;
+ border-radius: 0 10px 10px 0;
+ font-weight: 500;
+ overflow: hidden;
+ font-size: var(--sizebar-font-size);
+ }
+
+ .md-sidebar__scrollwrap {
+ margin: 0;
+ }
+
+ [dir="ltr"]
+ .md-nav--lifted
+ .md-nav[data-md-level="1"]
+ > .md-nav__list
+ > .md-nav__item {
+ padding: 0;
+ }
+
+ .md-nav__item--nested .md-nav__item .md-nav__item {
+ padding: 0;
+ }
+
+ .md-nav__item--nested .md-nav__item .md-nav__item .md-nav__link {
+ font-weight: 300;
+ }
+
+ .md-nav__item--nested .md-nav__item .md-nav__item .md-nav__link {
+ font-weight: 400;
+ padding-left: 1.25rem;
+ }
+}
diff --git a/docs/src/assets/css/table-of-contents.css b/docs/src/assets/css/table-of-contents.css
new file mode 100644
index 00000000..6c94f06e
--- /dev/null
+++ b/docs/src/assets/css/table-of-contents.css
@@ -0,0 +1,48 @@
+/* Table of Contents styling */
+@media screen and (min-width: 60em) {
+ [data-md-component="sidebar"] .md-nav__title[for="__toc"] {
+ text-transform: uppercase;
+ margin: 0.5rem;
+ margin-left: 0;
+ font-size: var(--sizebar-font-size);
+ }
+
+ [data-md-component="toc"] .md-nav__item .md-nav__link--active {
+ position: relative;
+ }
+
+ [data-md-component="toc"] .md-nav__item .md-nav__link--active:before {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ opacity: 0.15;
+ z-index: -1;
+ background: var(--md-typeset-a-color);
+ }
+
+ [data-md-component="toc"] .md-nav__link {
+ padding: 0.5rem 0.5rem;
+ margin: 0;
+ border-radius: 10px 0 0 10px;
+ font-weight: 400;
+ }
+
+ [data-md-component="toc"]
+ .md-nav__item
+ .md-nav__list
+ .md-nav__item
+ .md-nav__link {
+ padding-left: 1.25rem;
+ }
+
+ [dir="ltr"] .md-sidebar__inner {
+ padding: 0;
+ }
+
+ .md-nav__item {
+ padding: 0;
+ }
+}
diff --git a/docs/src/assets/img/add-interactivity.png b/docs/src/assets/img/add-interactivity.png
new file mode 100644
index 00000000..c3243190
Binary files /dev/null and b/docs/src/assets/img/add-interactivity.png differ
diff --git a/docs/src/assets/img/create-user-interfaces.png b/docs/src/assets/img/create-user-interfaces.png
new file mode 100644
index 00000000..06f6ea0c
Binary files /dev/null and b/docs/src/assets/img/create-user-interfaces.png differ
diff --git a/docs/src/assets/img/write-components-with-python.png b/docs/src/assets/img/write-components-with-python.png
new file mode 100644
index 00000000..380d2c3a
Binary files /dev/null and b/docs/src/assets/img/write-components-with-python.png differ
diff --git a/docs/src/assets/js/main.js b/docs/src/assets/js/main.js
new file mode 100644
index 00000000..50e2dda3
--- /dev/null
+++ b/docs/src/assets/js/main.js
@@ -0,0 +1,19 @@
+// Sync scrolling between the code node and the line number node
+// Event needs to be a separate function, otherwise the event will be triggered multiple times
+let code_with_lineno_scroll_event = function () {
+ let tr = this.parentNode.parentNode.parentNode.parentNode;
+ let lineno = tr.querySelector(".linenos");
+ lineno.scrollTop = this.scrollTop;
+};
+
+const observer = new MutationObserver((mutations) => {
+ let lineno = document.querySelectorAll(".linenos~.code");
+ lineno.forEach(function (element) {
+ let code = element.parentNode.querySelector("code");
+ code.addEventListener("scroll", code_with_lineno_scroll_event);
+ });
+});
+
+observer.observe(document.body, {
+ childList: true,
+});
diff --git a/docs/src/changelog/index.md b/docs/src/changelog/index.md
deleted file mode 100644
index 9c2bef97..00000000
--- a/docs/src/changelog/index.md
+++ /dev/null
@@ -1,10 +0,0 @@
----
-hide:
- - toc
----
-
-!!! summary "Attribution"
-
- {% include-markdown "../../../CHANGELOG.md" start="" end="" %}
-
-{% include-markdown "../../../CHANGELOG.md" start="" %}
diff --git a/docs/src/contribute/code.md b/docs/src/contribute/code.md
deleted file mode 100644
index 07824927..00000000
--- a/docs/src/contribute/code.md
+++ /dev/null
@@ -1,46 +0,0 @@
-## Overview
-
-!!! summary "Overview"
-
- You will need to set up a Python environment to develop ReactPy-Django.
-
-??? tip "Looking to contribute features that are not Django specific?"
-
- Everything within the `reactpy-django` repository must be specific to Django integration. Check out the [ReactPy Core documentation](https://reactpy.dev/docs/about/contributor-guide.html) to contribute general features such as: components, hooks, events, and more.
-
-## Modifying Code
-
-If you plan to make code changes to this repository, you will need to install the following dependencies first:
-
-- [Python 3.8+](https://www.python.org/downloads/)
-- [Git](https://git-scm.com/downloads)
-- [NPM](https://docs.npmjs.com/try-the-latest-stable-version-of-npm) for installing and managing Javascript
-
-Once done, you should clone this repository:
-
-```bash linenums="0"
-git clone https://github.com/reactive-python/reactpy-django.git
-cd reactpy-django
-```
-
-Then, by running the command below you can:
-
-- Install an editable version of the Python code
-- Download, build, and install Javascript dependencies
-
-```bash linenums="0"
-pip install -e . -r requirements.txt
-```
-
-Finally, to verify that everything is working properly, you can manually run the test webserver.
-
-```bash linenums="0"
-cd tests
-python manage.py runserver
-```
-
-Navigate to `http://127.0.0.1:8000` to see if the tests are rendering correctly.
-
-## GitHub Pull Request
-
-{% include-markdown "../../includes/pr.md" %}
diff --git a/docs/src/contribute/docs.md b/docs/src/contribute/docs.md
deleted file mode 100644
index d5a02be7..00000000
--- a/docs/src/contribute/docs.md
+++ /dev/null
@@ -1,40 +0,0 @@
-## Overview
-
-!!! summary "Overview"
-
- You will need to set up a Python environment to preview docs changes.
-
-## Modifying Docs
-
-If you plan to make changes to this documentation, you will need to install the following dependencies first:
-
-- [Python 3.8+](https://www.python.org/downloads/)
-- [Git](https://git-scm.com/downloads)
-
-Once done, you should clone this repository:
-
-```bash linenums="0"
-git clone https://github.com/reactive-python/reactpy-django.git
-cd reactpy-django
-```
-
-Then, by running the command below you can:
-
-- Install an editable version of the documentation
-- Self-host a test server for the documentation
-
-```bash linenums="0"
-pip install -e . -r requirements.txt --upgrade
-```
-
-Finally, to verify that everything is working properly, you can manually run the docs preview webserver.
-
-```bash linenums="0"
-mkdocs serve
-```
-
-Navigate to `http://127.0.0.1:8000` to view a preview of the documentation.
-
-## GitHub Pull Request
-
-{% include-markdown "../../includes/pr.md" %}
diff --git a/docs/src/contribute/running-tests.md b/docs/src/contribute/running-tests.md
deleted file mode 100644
index b714c5cd..00000000
--- a/docs/src/contribute/running-tests.md
+++ /dev/null
@@ -1,45 +0,0 @@
-## Overview
-
-!!! summary "Overview"
-
- You will need to set up a Python environment to run out test suite.
-
-## Running Tests
-
-This repository uses [Nox](https://nox.thea.codes/en/stable/) to run tests. For a full test of available scripts run `nox -l`.
-
-If you plan to run tests, you will need to install the following dependencies first:
-
-- [Python 3.8+](https://www.python.org/downloads/)
-- [Git](https://git-scm.com/downloads)
-
-Once done, you should clone this repository:
-
-```bash linenums="0"
-git clone https://github.com/reactive-python/reactpy-django.git
-cd reactpy-django
-pip install -e . -r requirements.txt --upgrade
-```
-
-## Full Test Suite
-
-By running the command below you can run the full test suite:
-
-```bash linenums="0"
-nox -s test
-```
-
-Or, if you want to run the tests in the foreground:
-
-```bash linenums="0"
-nox -s test -- --headed
-```
-
-## Only Django Tests
-
-Alternatively, if you want to only run Django related tests, you can use the following command:
-
-```bash linenums="0"
-cd tests
-python mange.py test
-```
diff --git a/docs/src/dictionary.txt b/docs/src/dictionary.txt
index d4f5b4b8..d2ff722d 100644
--- a/docs/src/dictionary.txt
+++ b/docs/src/dictionary.txt
@@ -1,37 +1,51 @@
+asgi
+async
+backend
+backends
+backhaul
+broadcasted
+changelog
django
-sanic
-plotly
+frontend
+frontends
+hello_world
+html
+iframe
+jupyter
+keyworded
+middleware
+misconfiguration
+misconfigurations
+my_template
nox
-websocket
-websockets
-changelog
-async
+plotly
+postfixed
+postprocessing
+postprocessor
pre
prefetch
prefetching
preloader
-whitespace
+preprocessor
+py
+pyodide
+pyscript
+reactpy
refetch
refetched
refetching
-html
-jupyter
-webserver
-iframe
-keyworded
+sanic
+serializable
stylesheet
stylesheets
+sublicense
unstyled
-py
-reactpy
-asgi
-postfixed
-postprocessing
-serializable
-postprocessor
-preprocessor
-middleware
-backends
-backend
-frontend
-frontends
+WebSocket
+WebSockets
+whitespace
+pytest
+linter
+linters
+linting
+formatters
+bootstrap_form
diff --git a/docs/src/features/components.md b/docs/src/features/components.md
deleted file mode 100644
index d7926803..00000000
--- a/docs/src/features/components.md
+++ /dev/null
@@ -1,268 +0,0 @@
-## Overview
-
-!!! summary "Overview"
-
- Prefabricated components can be used within your `components.py` to help simplify development.
-
-## View To Component
-
-Convert any Django view into a ReactPy component by using this decorator. Compatible with [Function Based Views](https://docs.djangoproject.com/en/dev/topics/http/views/) and [Class Based Views](https://docs.djangoproject.com/en/dev/topics/class-based-views/). Views can be sync or async.
-
-=== "components.py"
-
- ```python
- {% include "../../python/vtc.py" %}
- ```
-
-??? example "See Interface"
-
-
**Parameters**
-
- | Name | Type | Description | Default |
- | --- | --- | --- | --- |
- | `view` | `Callable | View` | The view function or class to convert. | N/A |
- | `compatibility` | `bool` | If True, the component will be rendered in an iframe. When using compatibility mode `tranforms`, `strict_parsing`, `request`, `args`, and `kwargs` arguments will be ignored. | `False` |
- | `transforms` | `Sequence[Callable[[VdomDict], Any]]` | A list of functions that transforms the newly generated VDOM. The functions will be called on each VDOM node. | `tuple` |
- | `strict_parsing` | `bool` | If True, an exception will be generated if the HTML does not perfectly adhere to HTML5. | `True` |
-
-
**Returns**
-
- | Type | Description |
- | --- | --- |
- | `_ViewComponentConstructor` | A function that takes `request, *args, key, **kwargs` and returns an ReactPy component. All parameters are directly provided to your view, besides `key` which is used by ReactPy. |
-
-??? Warning "Potential information exposure when using `compatibility = True`"
-
- When using `compatibility` mode, ReactPy automatically exposes a URL to your view.
-
- It is your responsibility to ensure privileged information is not leaked via this method.
-
- This can be done via directly writing conditionals into your view, or by adding decorators such as [`user_passes_test`](https://docs.djangoproject.com/en/dev/topics/auth/default/#django.contrib.auth.decorators.user_passes_test) to your views prior to using `view_to_component`.
-
- === "Function Based View"
-
- ```python
- {% include "../../python/vtc-fbv-compat.py" %}
- ```
-
- === "Class Based View"
-
- ```python
- {% include "../../python/vtc-cbv-compatibility.py" %}
- ```
-
-??? info "Existing limitations"
-
- There are currently several limitations of using `view_to_component` that may be resolved in a future version of `reactpy_django`.
-
- - Requires manual intervention to change request methods beyond `GET`.
- - ReactPy events cannot conveniently be attached to converted view HTML.
- - Has no option to automatically intercept local anchor link (such as `#!html
`) click events.
-
- _Please note these limitations do not exist when using `compatibility` mode._
-
-??? question "How do I use this for Class Based Views?"
-
- You can simply pass your Class Based View directly into `view_to_component`.
-
- === "components.py"
-
- ```python
- {% include "../../python/vtc-cbv.py" %}
- ```
-
-??? question "How do I transform views from external libraries?"
-
- In order to convert external views, you can utilize `view_to_component` as a function, rather than a decorator.
-
- === "components.py"
-
- ```python
- {% include "../../python/vtc-func.py" %}
- ```
-
-??? question "How do I provide `request`, `args`, and `kwargs` to a view?"
-
-
**`Request`**
-
- You can use the `request` parameter to provide the view a custom request object.
-
- === "components.py"
-
- ```python
- {% include "../../python/vtc-request.py" %}
- ```
-
- ---
-
-
**`args` and `kwargs`**
-
- You can use the `args` and `kwargs` parameters to provide positional and keyworded arguments to a view.
-
- === "components.py"
-
- ```python
- {% include "../../python/vtc-args-kwargs.py" %}
- ```
-
-??? question "How do I use `strict_parsing`, `compatibility`, and `transforms`?"
-
-
**`strict_parsing`**
-
- By default, an exception will be generated if your view's HTML does not perfectly adhere to HTML5.
-
- However, there are some circumstances where you may not have control over the original HTML, so you may be unable to fix it. Or you may be relying on non-standard HTML tags such as `#!html
Hello World `.
-
- In these scenarios, you may want to rely on best-fit parsing by setting the `strict_parsing` parameter to `False`.
-
- === "components.py"
-
- ```python
- {% include "../../python/vtc-strict-parsing.py" %}
- ```
-
- _Note: Best-fit parsing is designed to be similar to how web browsers would handle non-standard or broken HTML._
-
- ---
-
-
**`compatibility`**
-
- For views that rely on HTTP responses other than `GET` (such as `PUT`, `POST`, `PATCH`, etc), you should consider using compatibility mode to render your view within an iframe.
-
- Any view can be rendered within compatibility mode. However, the `transforms`, `strict_parsing`, `request`, `args`, and `kwargs` arguments do not apply to compatibility mode.
-
-
-
- === "components.py"
-
- ```python
- {% include "../../python/vtc-compatibility.py" %}
- ```
-
- _Note: By default the `compatibility` iframe is unstyled, and thus won't look pretty until you add some CSS._
-
- ---
-
-
**`transforms`**
-
- After your view has been turned into [VDOM](https://reactpy.dev/docs/reference/specifications.html#vdom) (python dictionaries), `view_to_component` will call your `transforms` functions on every VDOM node.
-
- This allows you to modify your view prior to rendering.
-
- For example, if you are trying to modify the text of a node with a certain `id`, you can create a transform like such:
-
- === "components.py"
-
- ```python
- {% include "../../python/vtc-transforms.py" %}
- ```
-
-## Django CSS
-
-Allows you to defer loading a CSS stylesheet until a component begins rendering. This stylesheet must be stored within [Django's static files](https://docs.djangoproject.com/en/dev/howto/static-files/).
-
-=== "components.py"
-
- ```python
- {% include "../../python/django-css.py" %}
- ```
-
-??? example "See Interface"
-
-
**Parameters**
-
- | Name | Type | Description | Default |
- | --- | --- | --- | --- |
- | `static_path` | `str` | The path to the static file. This path is identical to what you would use on a `static` template tag. | N/A |
- | `key` | `Key | None` | A key to uniquely identify this component which is unique amongst a component's immediate siblings | `None` |
-
-
**Returns**
-
- | Type | Description |
- | --- | --- |
- | `Component` | An ReactPy component. |
-
-??? question "Should I put `django_css` at the top of my HTML?"
-
- Yes, if the stylesheet contains styling for your component.
-
-??? question "Can I load static CSS using `html.link` instead?"
-
- While you can load stylesheets with `html.link`, keep in mind that loading this way **does not** ensure load order. Thus, your stylesheet will be loaded after your component is displayed. This would likely cause unintended visual behavior, so use this at your own discretion.
-
- Here's an example on what you should avoid doing for Django static files:
-
- ```python
- {% include "../../python/django-css-local-link.py" %}
- ```
-
-??? question "How do I load external CSS?"
-
- `django_css` can only be used with local static files.
-
- For external CSS, substitute `django_css` with `html.link`.
-
- ```python
- {% include "../../python/django-css-external-link.py" %}
- ```
-
-??? question "Why not load my CSS in `#!html `?"
-
- Traditionally, stylesheets are loaded in your `#!html ` using the `#!jinja {% load static %}` template tag.
-
- To help improve webpage load times, you can use the `django_css` component to defer loading your stylesheet until it is needed.
-
-## Django JS
-
-Allows you to defer loading JavaScript until a component begins rendering. This JavaScript must be stored within [Django's static files](https://docs.djangoproject.com/en/dev/howto/static-files/).
-
-=== "components.py"
-
- ```python
- {% include "../../python/django-js.py" %}
- ```
-
-??? example "See Interface"
-
-
**Parameters**
-
- | Name | Type | Description | Default |
- | --- | --- | --- | --- |
- | `static_path` | `str` | The path to the static file. This path is identical to what you would use on a `static` template tag. | N/A |
- | `key` | `Key | None` | A key to uniquely identify this component which is unique amongst a component's immediate siblings | `None` |
-
-
**Returns**
-
- | Type | Description |
- | --- | --- |
- | `Component` | An ReactPy component. |
-
-??? question "Should I put `django_js` at the bottom of my HTML?"
-
- Yes, if your scripts are reliant on the contents of the component.
-
-??? question "Can I load static JavaScript using `html.script` instead?"
-
- While you can load JavaScript with `html.script`, keep in mind that loading this way **does not** ensure load order. Thus, your JavaScript will likely be loaded at an arbitrary time after your component is displayed.
-
- Here's an example on what you should avoid doing for Django static files:
-
- ```python
- {% include "../../python/django-js-local-script.py" %}
- ```
-
-??? question "How do I load external JS?"
-
- `django_js` can only be used with local static files.
-
- For external JavaScript, substitute `django_js` with `html.script`.
-
- ```python
- {% include "../../python/django-js-remote-script.py" %}
- ```
-
-??? question "Why not load my JS in `#!html `?"
-
- Traditionally, JavaScript is loaded in your `#!html ` using the `#!jinja {% load static %}` template tag.
-
- To help improve webpage load times, you can use the `django_js` component to defer loading your JavaScript until it is needed.
diff --git a/docs/src/features/decorators.md b/docs/src/features/decorators.md
deleted file mode 100644
index 45548799..00000000
--- a/docs/src/features/decorators.md
+++ /dev/null
@@ -1,86 +0,0 @@
-## Overview
-
-!!! summary "Overview"
-
- Decorator utilities can be used within your `components.py` to help simplify development.
-
-## Auth Required
-
-You can limit access to a component to users with a specific `auth_attribute` by using this decorator (with or without parentheses).
-
-By default, this decorator checks if the user is logged in and not deactivated (`is_active`).
-
-This decorator is commonly used to selectively render a component only if a user [`is_staff`](https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.User.is_staff) or [`is_superuser`](https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.User.is_superuser).
-
-=== "components.py"
-
- ```python
- {% include "../../python/auth-required.py" %}
- ```
-
-??? example "See Interface"
-
-
**Parameters**
-
- | Name | Type | Description | Default |
- | --- | --- | --- | --- |
- | `auth_attribute` | `str` | The value to check within the user object. This is checked in the form of `UserModel.
`. | `#!python "is_active"` |
- | `fallback` | `ComponentType`, `VdomDict`, `None` | The `component` or `reactpy.html` snippet to render if the user is not authenticated. | `None` |
-
- **Returns**
-
- | Type | Description |
- | --- | --- |
- | `Component` | An ReactPy component. |
- | `VdomDict` | An `reactpy.html` snippet. |
- | `None` | No component render. |
-
-??? question "How do I render a different component if authentication fails?"
-
- You can use a component with the `fallback` argument, as seen below.
-
- === "components.py"
-
- ```python
- {% include "../../python/auth-required-component-fallback.py" %}
- ```
-
-??? question "How do I render a simple `reactpy.html` snippet if authentication fails?"
-
- You can use a `reactpy.html` snippet with the `fallback` argument, as seen below.
-
- === "components.py"
-
- ```python
- {% include "../../python/auth-required-vdom-fallback.py" %}
- ```
-
-??? question "How can I check if a user `is_staff`?"
-
- You can set the `auth_attribute` to `is_staff`, as seen blow.
-
- === "components.py"
-
- ```python
- {% include "../../python/auth-required-attribute.py" %}
- ```
-
-??? question "How can I check for a custom attribute?"
-
- You will need to be using a [custom user model](https://docs.djangoproject.com/en/dev/topics/auth/customizing/#specifying-a-custom-user-model) within your Django instance.
-
- For example, if your user model has the field `is_really_cool` ...
-
- === "models.py"
-
- ```python
- {% include "../../python/auth-required-custom-attribute-model.py" %}
- ```
-
- ... then you would do the following within your decorator:
-
- === "components.py"
-
- ```python
- {% include "../../python/auth-required-custom-attribute.py" %}
- ```
diff --git a/docs/src/features/hooks.md b/docs/src/features/hooks.md
deleted file mode 100644
index 5916776a..00000000
--- a/docs/src/features/hooks.md
+++ /dev/null
@@ -1,324 +0,0 @@
-## Overview
-
-!!! summary "Overview"
-
- Prefabricated hooks can be used within your `components.py` to help simplify development.
-
-??? tip "Looking for standard React hooks?"
-
- The `reactpy-django` package only contains django specific hooks. Standard hooks can be found within [`reactive-python/reactpy`](https://github.com/reactive-python/reactpy). Since `reactpy` is installed alongside `reactpy-django`, you can import them at any time.
-
- Check out the [ReactPy Core docs](https://reactpy.dev/docs/reference/hooks-api.html#basic-hooks) to see what hooks are available!
-
-## Use Query
-
-The `use_query` hook is used fetch Django ORM queries.
-
-The function you provide into this hook must return either a `Model` or `QuerySet`.
-
-=== "components.py"
-
- ```python
- {% include "../../python/use-query.py" %}
- ```
-
-=== "models.py"
-
- ```python
- {% include "../../python/example/models.py" %}
- ```
-
-??? example "See Interface"
-
- **Parameters**
-
- | Name | Type | Description | Default |
- | --- | --- | --- | --- |
- | `options` | `QueryOptions | None` | An optional `QueryOptions` object that can modify how the query is executed. | None |
- | `query` | `Callable[_Params, _Result | None]` | A callable that returns a Django `Model` or `QuerySet`. | N/A |
- | `*args` | `_Params.args` | Positional arguments to pass into `query`. | N/A |
- | `**kwargs` | `_Params.kwargs` | Keyword arguments to pass into `query`. | N/A |
-
- **Returns**
-
- | Type | Description |
- | --- | --- |
- | `Query[_Result | None]` | An object containing `loading`/`error` states, your `data` (if the query has successfully executed), and a `refetch` callable that can be used to re-run the query. |
-
-??? question "How can I provide arguments to my query function?"
-
- `*args` and `**kwargs` can be provided to your query function via `use_query` parameters.
-
- === "components.py"
-
- ```python
- {% include "../../python/use-query-args.py" %}
- ```
-
-??? question "Why does `get_items` in the example return `TodoItem.objects.all()`?"
-
- This was a technical design decision to based on [Apollo's `useQuery` hook](https://www.apollographql.com/docs/react/data/queries/), but ultimately helps avoid Django's `SynchronousOnlyOperation` exceptions.
-
- The `use_query` hook ensures the provided `Model` or `QuerySet` executes all [deferred](https://docs.djangoproject.com/en/dev/ref/models/instances/#django.db.models.Model.get_deferred_fields)/[lazy queries](https://docs.djangoproject.com/en/dev/topics/db/queries/#querysets-are-lazy) safely prior to reaching your components.
-
-??? question "How can I use `QueryOptions` to customize fetching behavior?"
-
- **`thread_sensitive`**
-
- Whether to run your synchronous query function in thread-sensitive mode. Thread-sensitive mode is turned on by default due to Django ORM limitations. See Django's [`sync_to_async` docs](https://docs.djangoproject.com/en/dev/topics/async/#sync-to-async) docs for more information.
-
- This setting only applies to sync query functions, and will be ignored for async functions.
-
- === "components.py"
-
- ```python
- {% include "../../python/use-query-thread-sensitive.py" %}
- ```
-
- ---
-
- **`postprocessor`**
-
- {% include-markdown "../../includes/orm.md" start="" end="" %}
-
- However, if you...
-
- 1. Want to use this hook to defer IO intensive tasks to be computed in the background
- 2. Want to to utilize `use_query` with a different ORM
-
- ... then you can either set a custom `postprocessor`, or disable all postprocessing behavior by modifying the `QueryOptions.postprocessor` parameter. In the example below, we will set the `postprocessor` to `None` to disable postprocessing behavior.
-
- === "components.py"
-
- ```python
- {% include "../../python/use-query-postprocessor-disable.py" %}
- ```
-
- If you wish to create a custom `postprocessor`, you will need to create a callable.
-
- The first argument of `postprocessor` must be the query `data`. All proceeding arguments
- are optional `postprocessor_kwargs` (see below). This `postprocessor` must return
- the modified `data`.
-
- === "components.py"
-
- ```python
- {% include "../../python/use-query-postprocessor-change.py" %}
- ```
-
- ---
-
- **`postprocessor_kwargs`**
-
- {% include-markdown "../../includes/orm.md" start="" end="" %}
-
- However, if you have deep nested trees of relational data, this may not be a desirable behavior. In these scenarios, you may prefer to manually fetch these relational fields using a second `use_query` hook.
-
- You can disable the prefetching behavior of the default `postprocessor` (located at `reactpy_django.utils.django_query_postprocessor`) via the `QueryOptions.postprocessor_kwargs` parameter.
-
- === "components.py"
-
- ```python
- {% include "../../python/use-query-postprocessor-kwargs.py" %}
- ```
-
- _Note: In Django's ORM design, the field name to access foreign keys is [postfixed with `_set`](https://docs.djangoproject.com/en/dev/topics/db/examples/many_to_one/) by default._
-
-??? question "Can I define async query functions?"
-
- Async functions are supported by `use_query`. You can use them in the same way as a sync query function.
-
- However, be mindful of Django async ORM restrictions.
-
- === "components.py"
-
- ```python
- {% include "../../python/use-query-async.py" %}
- ```
-
-??? question "Can I make ORM calls without hooks?"
-
- {% include-markdown "../../includes/orm.md" start="" end="" %}
-
-## Use Mutation
-
-The `use_mutation` hook is used to create, update, or delete Django ORM objects.
-
-The function you provide into this hook will have no return value.
-
-=== "components.py"
-
- ```python
- {% include "../../python/use-mutation.py" %}
- ```
-
-=== "models.py"
-
- ```python
- {% include "../../python/example/models.py" %}
- ```
-
-??? example "See Interface"
-
- **Parameters**
-
- | Name | Type | Description | Default |
- | --- | --- | --- | --- |
- | `mutate` | `Callable[_Params, bool | None]` | A callable that performs Django ORM create, update, or delete functionality. If this function returns `False`, then your `refetch` function will not be used. | N/A |
- | `refetch` | `Callable[..., Any] | Sequence[Callable[..., Any]] | None` | A `query` function (used by the `use_query` hook) or a sequence of `query` functions that will be called if the mutation succeeds. This is useful for refetching data after a mutation has been performed. | `None` |
-
- **Returns**
-
- | Type | Description |
- | --- | --- |
- | `Mutation[_Params]` | An object containing `loading`/`error` states, a `reset` callable that will set `loading`/`error` states to defaults, and a `execute` callable that will run the query. |
-
-??? question "How can I provide arguments to my mutation function?"
-
- `*args` and `**kwargs` can be provided to your mutation function via `mutation.execute` parameters.
-
- === "components.py"
-
- ```python
- {% include "../../python/use-mutation-args-kwargs.py" %}
- ```
-
-??? question "Can `use_mutation` trigger a refetch of `use_query`?"
-
- Yes, `use_mutation` can queue a refetch of a `use_query` via the `refetch=...` argument.
-
- The example below is a merge of the `use_query` and `use_mutation` examples above with the addition of a `refetch` argument on `use_mutation`.
-
- Please note that any `use_query` hooks that use `get_items` will be refetched upon a successful mutation.
-
- === "components.py"
-
- ```python
- {% include "../../python/use-mutation-query-refetch.py" %}
- ```
-
- === "models.py"
-
- ```python
- {% include "../../python/example/models.py" %}
- ```
-
-??? question "Can I make a failed `use_mutation` try again?"
-
- Yes, a `use_mutation` can be re-performed by calling `reset()` on your `use_mutation` instance.
-
- For example, take a look at `reset_event` below.
-
- === "components.py"
-
- ```python
- {% include "../../python/use-mutation-reset.py" %}
- ```
-
- === "models.py"
-
- ```python
- {% include "../../python/example/models.py" %}
- ```
-
-??? question "Can I make ORM calls without hooks?"
-
- {% include-markdown "../../includes/orm.md" start="" end="" %}
-
-## Use Connection
-
-You can fetch the Django Channels [websocket](https://channels.readthedocs.io/en/stable/topics/consumers.html#asyncjsonwebsocketconsumer) at any time by using `use_connection`.
-
-=== "components.py"
-
- ```python
- {% include "../../python/use-connection.py" %}
- ```
-
-??? example "See Interface"
-
- **Parameters**
-
- `None`
-
- **Returns**
-
- | Type | Description |
- | --- | --- |
- | `Connection` | The component's websocket. |
-
-## Use Scope
-
-This is a shortcut that returns the Websocket's [`scope`](https://channels.readthedocs.io/en/stable/topics/consumers.html#scope).
-
-=== "components.py"
-
- ```python
- {% include "../../python/use-scope.py" %}
- ```
-
-??? example "See Interface"
-
- **Parameters**
-
- `None`
-
- **Returns**
-
- | Type | Description |
- | --- | --- |
- | `MutableMapping[str, Any]` | The websocket's `scope`. |
-
-## Use Location
-
-This is a shortcut that returns the Websocket's `path`.
-
-You can expect this hook to provide strings such as `/reactpy/my_path`.
-
-=== "components.py"
-
- ```python
- {% include "../../python/use-location.py" %}
- ```
-
-??? example "See Interface"
-
- **Parameters**
-
- `None`
-
- **Returns**
-
- | Type | Description |
- | --- | --- |
- | `Location` | A object containing the current URL's `pathname` and `search` query. |
-
-??? info "This hook's behavior will be changed in a future update"
-
- This hook will be updated to return the browser's currently active path. This change will come in alongside ReactPy URL routing support.
-
- Check out [reactive-python/reactpy-router#2](https://github.com/idom-team/idom-router/issues/2) for more information.
-
-## Use Origin
-
-This is a shortcut that returns the Websocket's `origin`.
-
-You can expect this hook to provide strings such as `http://example.com`.
-
-=== "components.py"
-
- ```python
- {% include "../../python/use-origin.py" %}
- ```
-
-??? example "See Interface"
-
- **Parameters**
-
- `None`
-
- **Returns**
-
- | Type | Description |
- | --- | --- |
- | `str | None` | A string containing the browser's current origin, obtained from websocket headers (if available). |
diff --git a/docs/src/features/settings.md b/docs/src/features/settings.md
deleted file mode 100644
index 3abd6575..00000000
--- a/docs/src/features/settings.md
+++ /dev/null
@@ -1,21 +0,0 @@
-## Overview
-
-!!! summary "Overview"
-
- Your **Django project's** `settings.py` can modify the behavior of ReactPy.
-
-## Primary Configuration
-
-These are ReactPy-Django's default settings values. You can modify these values in your **Django project's** `settings.py` to change the behavior of ReactPy.
-
-=== "settings.py"
-
- ```python
- {% include "../../python/settings.py" %}
- ```
-
-??? question "Do I need to modify my settings?"
-
- The default configuration of ReactPy is adequate for the majority of use cases.
-
- You should only consider changing settings when the necessity arises.
diff --git a/docs/src/features/template-tag.md b/docs/src/features/template-tag.md
deleted file mode 100644
index 9d4ca18c..00000000
--- a/docs/src/features/template-tag.md
+++ /dev/null
@@ -1,120 +0,0 @@
-## Overview
-
-!!! summary "Overview"
-
- Template tags can be used within your Django templates such as `my-template.html` to import ReactPy features.
-
-## Component
-
-The `component` template tag can be used to insert any number of ReactPy components onto your page.
-
-=== "my-template.html"
-
- {% include-markdown "../../../README.md" start="" end="" %}
-
-??? example "See Interface"
-
- **Parameters**
-
- | Name | Type | Description | Default |
- | --- | --- | --- | --- |
- | `dotted_path` | `str` | The dotted path to the component to render. | N/A |
- | `*args` | `Any` | The positional arguments to provide to the component. | N/A |
- | `**kwargs` | `Any` | The keyword arguments to provide to the component. | N/A |
-
- **Returns**
-
- | Type | Description |
- | --- | --- |
- | `Component` | An ReactPy component. |
-
-
-
-??? warning "Do not use context variables for the ReactPy component name"
-
- Our preprocessor relies on the template tag containing a string.
-
- **Do not** use Django template/context variables for the component path. Failure to follow this warning can result in unexpected behavior.
-
- For example, **do not** do the following:
-
- === "my-template.html"
-
- ```jinja
-
- {% component "example_project.my_app.components.hello_world" recipient="World" %}
-
-
- {% component dont_do_this recipient="World" %}
- ```
-
- === "views.py"
-
- ```python
- {% include "../../python/template-tag-bad-view.py" %}
- ```
-
-
-
-
-??? info "Reserved keyword arguments: `class` and `key`"
-
- For this template tag, there are two reserved keyword arguments: `class` and `key`
-
- - `class` allows you to apply a HTML class to the top-level component div. This is useful for styling purposes.
- - `key` allows you to force the component's root node to use a [specific key value](https://reactpy.dev/docs/guides/creating-interfaces/rendering-data/index.html#organizing-items-with-keys). Using `key` within a template tag is effectively useless.
-
- === "my-template.html"
-
- ```jinja
- ...
- {% component "example.components.my_component" class="my-html-class" key=123 %}
- ...
- ```
-
-
-
-
-??? question "Can I use multiple components on one page?"
-
- You can add as many components to a webpage as needed by using the template tag multiple times. Retrofitting legacy sites to use ReactPy will typically involve many components on one page.
-
- === "my-template.html"
-
- ```jinja
- {% load reactpy %}
-
-
-
- {% component "example_project.my_app.components.my_title" %}
- {% component "example_project.my_app_2.components.goodbye_world" class="bold small-font" %}
- {% component "example_project.my_app_3.components.simple_button" %}
-
-
- ```
-
- Please note that components separated like this will not be able to interact with each other, except through database queries.
-
- Additionally, in scenarios where you are trying to create a Single Page Application (SPA) within Django, you will only have one component within your `#!html ` tag.
-
-
-
-
-
-??? question "Can I use positional arguments instead of keyword arguments?"
-
- You can use any combination of `*args`/`**kwargs` in your template tag.
-
- === "my-template.html"
-
- ```jinja
- {% component "example_project.my_app.components.frog_greeter" 123 "Mr. Froggles" species="Grey Treefrog" %}
- ```
-
- === "components.py"
-
- ```python
- {% include "../../python/template-tag-args-kwargs.py" %}
- ```
-
-
diff --git a/docs/src/features/utils.md b/docs/src/features/utils.md
deleted file mode 100644
index 9cec1aa4..00000000
--- a/docs/src/features/utils.md
+++ /dev/null
@@ -1,39 +0,0 @@
-## Overview
-
-!!! summary "Overview"
-
- Utility functions that you can use when needed.
-
-## Django Query Postprocessor
-
-This is the default postprocessor for the `use_query` hook.
-
-This postprocessor is designed to avoid Django's `SynchronousOnlyException` by recursively fetching all fields within a `Model` or `QuerySet` to prevent [lazy execution](https://docs.djangoproject.com/en/dev/topics/db/queries/#querysets-are-lazy).
-
-=== "components.py"
-
- ```python
- {% include "../../python/django-query-postprocessor.py" %}
- ```
-
-=== "models.py"
-
- ```python
- {% include "../../python/example/models.py" %}
- ```
-
-??? example "See Interface"
-
- **Parameters**
-
- | Name | Type | Description | Default |
- | --- | --- | --- | --- |
- | `data` | `QuerySet | Model` | The `Model` or `QuerySet` to recursively fetch fields from. | N/A |
- | `many_to_many` | `bool` | Whether or not to recursively fetch `ManyToManyField` relationships. | `True` |
- | `many_to_one` | `bool` | Whether or not to recursively fetch `ForeignKey` relationships. | `True` |
-
- **Returns**
-
- | Type | Description |
- | --- | --- |
- | `QuerySet | Model` | The `Model` or `QuerySet` with all fields fetched. |
diff --git a/docs/src/get-started/choose-django-app.md b/docs/src/get-started/choose-django-app.md
deleted file mode 100644
index 61dcfdba..00000000
--- a/docs/src/get-started/choose-django-app.md
+++ /dev/null
@@ -1,17 +0,0 @@
-## Overview
-
-!!! summary "Overview"
-
- Set up a **Django Project** with at least one app.
-
-## Choose a Django App
-
-If you have reached this point, you should have already [installed ReactPy-Django](../get-started/installation.md) through the previous steps.
-
-You will now need to pick at least one **Django app** to start using ReactPy-Django on.
-
-For the examples within this section, we will assume you have placed the files [generated by `startapp`](https://docs.djangoproject.com/en/dev/intro/tutorial01/#creating-the-polls-app) directly into your **Django project** folder. This is common for small projects.
-
-??? question "How do I organize my Django project for ReactPy?"
-
- ReactPy-Django has no project structure requirements. Organize everything as you wish, just like any **Django project**.
diff --git a/docs/src/get-started/create-component.md b/docs/src/get-started/create-component.md
deleted file mode 100644
index 032906e7..00000000
--- a/docs/src/get-started/create-component.md
+++ /dev/null
@@ -1,21 +0,0 @@
-## Overview
-
-!!! summary "Overview"
-
- Create a component function using our decorator.
-
-## Create a Component
-
-{% include-markdown "../../../README.md" start="" end="" %}
-
-=== "components.py"
-
- {% include-markdown "../../../README.md" start="" end="" %}
-
-??? question "What should I name my ReactPy files and functions?"
-
- You have full freedom in naming/placement of your files and functions.
-
- We recommend creating a `components.py` for small **Django apps**. If your app has a lot of components, you should consider breaking them apart into individual modules such as `components/navbar.py`.
-
- Ultimately, components are referenced by Python dotted path in `my-template.html` (_see next step_). So, at minimum this path needs to be valid to Python's `importlib`.
diff --git a/docs/src/get-started/installation.md b/docs/src/get-started/installation.md
deleted file mode 100644
index 7aa2ea65..00000000
--- a/docs/src/get-started/installation.md
+++ /dev/null
@@ -1,97 +0,0 @@
-## Overview
-
-!!! summary "Overview"
-
- ReactPy-Django can be installed from PyPI to an existing **Django project** with minimal configuration.
-
-## Step 0: Create a Django Project
-
-These docs assumes you have already created [a **Django project**](https://docs.djangoproject.com/en/dev/intro/tutorial01/), which involves creating and installing at least one **Django app**. If not, check out this [9 minute YouTube tutorial](https://www.youtube.com/watch?v=ZsJRXS_vrw0) created by _IDG TECHtalk_.
-
-## Step 1: Install from PyPI
-
-```bash linenums="0"
-pip install reactpy-django
-```
-
-## Step 2: Configure [`settings.py`](https://docs.djangoproject.com/en/dev/topics/settings/)
-
-In your settings you will need to add `reactpy_django` to [`INSTALLED_APPS`](https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-INSTALLED_APPS).
-
-=== "settings.py"
-
- ```python
- {% include "../../python/configure-installed-apps.py" %}
- ```
-
-??? warning "Enable Django Channels ASGI (Required)"
-
- ReactPy-Django requires ASGI Websockets from [Django Channels](https://github.com/django/channels).
-
- If you have not enabled ASGI on your **Django project** yet, you will need to
-
- 1. Install `channels[daphne]`
- 2. Add `daphne` to `INSTALLED_APPS`
- 3. Set your `ASGI_APPLICATION` variable.
-
- === "settings.py"
-
- ```python
- {% include "../../python/configure-channels.py" %}
- ```
-
- Consider reading the [Django Channels Docs](https://channels.readthedocs.io/en/stable/installation.html) for more info.
-
-??? note "Configure ReactPy settings (Optional)"
-
- Below are a handful of values you can change within `settings.py` to modify the behavior of ReactPy.
-
- ```python linenums="0"
- {% include "../../python/settings.py" %}
- ```
-
-## Step 3: Configure [`urls.py`](https://docs.djangoproject.com/en/dev/topics/http/urls/)
-
-Add ReactPy HTTP paths to your `urlpatterns`.
-
-=== "urls.py"
-
- ```python
- {% include "../../python/configure-urls.py" %}
- ```
-
-## Step 4: Configure [`asgi.py`](https://docs.djangoproject.com/en/dev/howto/deployment/asgi/)
-
-Register ReactPy's Websocket using `REACTPY_WEBSOCKET_PATH`.
-
-=== "asgi.py"
-
- ```python
- {% include "../../python/configure-asgi.py" %}
- ```
-
-??? note "Add `AuthMiddlewareStack` and `SessionMiddlewareStack` (Optional)"
-
- There are many situations where you need to access the Django `User` or `Session` objects within ReactPy components. For example, if you want to:
-
- 1. Access the `User` that is currently logged in
- 2. Login or logout the current `User`
- 3. Access Django's `Sesssion` object
-
- In these situations will need to ensure you are using `AuthMiddlewareStack` and/or `SessionMiddlewareStack`.
-
- ```python linenums="0"
- {% include "../../python/configure-asgi-middleware.py" start="# start" %}
- ```
-
-??? question "Where is my `asgi.py`?"
-
- If you do not have an `asgi.py`, follow the [`channels` installation guide](https://channels.readthedocs.io/en/stable/installation.html).
-
-## Step 5: Run Migrations
-
-Run Django's database migrations to initialize ReactPy-Django's database table.
-
-```bash linenums="0"
-python manage.py migrate
-```
diff --git a/docs/src/get-started/learn-more.md b/docs/src/get-started/learn-more.md
deleted file mode 100644
index 8b58400d..00000000
--- a/docs/src/get-started/learn-more.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# :confetti_ball: Congratulations :confetti_ball:
-
-If you followed the previous steps, you have now created a "Hello World" component!
-
-The docs you are reading only covers our Django integration. To learn more about features, such as interactive events and hooks, check out the [ReactPy Core Documentation](https://reactpy.dev/docs/guides/creating-interfaces/index.html)!
-
-Additionally, the vast majority of tutorials/guides you find for ReactJS can be applied to ReactPy.
-
-=== "Learn More"
-
- [ReactPy-Django Advanced Usage](../features/components.md){ .md-button .md-button--primary} [ReactPy Core Documentation](https://reactpy.dev/docs/guides/creating-interfaces/index.html){ .md-button .md-button--primary } [Ask Questions on Discord](https://discord.gg/uNb5P4hA9X){ .md-button .md-button--primary }
diff --git a/docs/src/get-started/register-view.md b/docs/src/get-started/register-view.md
deleted file mode 100644
index 7f21bd66..00000000
--- a/docs/src/get-started/register-view.md
+++ /dev/null
@@ -1,35 +0,0 @@
-## Overview
-
-!!! summary "Overview"
-
- Select your template containing an ReactPy component, and render it using a Django view.
-
-## Register a View
-
-We will assume you have [created a Django View](https://docs.djangoproject.com/en/dev/intro/tutorial01/#write-your-first-view) before, but here's a simple example below.
-
-Within your **Django app**'s `views.py` file, you will need to create a function to render the HTML template containing your ReactPy components.
-
-In this example, we will create a view that renders `my-template.html` (_from the previous step_).
-
-=== "views.py"
-
- ```python
- {% include "../../python/example/views.py" %}
- ```
-
-We will add this new view into your [`urls.py`](https://docs.djangoproject.com/en/dev/intro/tutorial01/#write-your-first-view).
-
-=== "urls.py"
-
- ```python
- {% include "../../python/example/urls.py" %}
- ```
-
-??? question "Which urls.py do I add my views to?"
-
- For simple **Django projects**, you can easily add all of your views directly into the **Django project's** `urls.py`. However, as you start increase your project's complexity you might end up with way too much within one file.
-
- Once you reach that point, we recommend creating an individual `urls.py` within each of your **Django apps**.
-
- Then, within your **Django project's** `urls.py` you will use Django's [`include` function](https://docs.djangoproject.com/en/dev/ref/urls/#include) to link it all together.
diff --git a/docs/src/get-started/run-webserver.md b/docs/src/get-started/run-webserver.md
deleted file mode 100644
index 5a1d27dd..00000000
--- a/docs/src/get-started/run-webserver.md
+++ /dev/null
@@ -1,21 +0,0 @@
-## Overview
-
-!!! summary "Overview"
-
- Run a webserver to display your Django view.
-
-## Run the Webserver
-
-To test your new Django view, run the following command to start up a development webserver.
-
-```bash linenums="0"
-python manage.py runserver
-```
-
-Now you can navigate to your **Django project** URL that contains an ReactPy component, such as `http://127.0.0.1:8000/example/` (_from the previous step_).
-
-If you copy-pasted our example component, you will now see your component display "Hello World".
-
-??? warning "Do not use `manage.py runserver` for production."
-
- The webserver contained within `manage.py runserver` is only intended for development and testing purposes. For production deployments make sure to read [Django's documentation](https://docs.djangoproject.com/en/dev/howto/deployment/).
diff --git a/docs/src/get-started/use-template-tag.md b/docs/src/get-started/use-template-tag.md
deleted file mode 100644
index e108e407..00000000
--- a/docs/src/get-started/use-template-tag.md
+++ /dev/null
@@ -1,23 +0,0 @@
-## Overview
-
-!!! summary "Overview"
-
- Decide where the component will be displayed by using our template tag.
-
-## Use the Template Tag
-
-{% include-markdown "../../../README.md" start="" end="" %}
-
-=== "my-template.html"
-
- {% include-markdown "../../../README.md" start="" end="" %}
-
-{% include-markdown "../features/template-tag.md" start="" end="" %}
-
-{% include-markdown "../features/template-tag.md" start="" end="" %}
-
-{% include-markdown "../features/template-tag.md" start="" end="" %}
-
-??? question "Where is my templates folder?"
-
- If you do not have a `templates` folder in your **Django app**, you can simply create one! Keep in mind, templates within this folder will not be detected by Django unless you [add the corresponding **Django app** to `settings.py:INSTALLED_APPS`](https://docs.djangoproject.com/en/dev/ref/applications/#configuring-applications).
diff --git a/docs/src/index.md b/docs/src/index.md
index 6b8b3aaa..384ec5b6 100644
--- a/docs/src/index.md
+++ b/docs/src/index.md
@@ -1,17 +1,6 @@
---
+template: home.html
hide:
- navigation
- toc
---
-
-{ align=left style=height:40px }
-
-# ReactPy Django
-
-{% include-markdown "../../README.md" start="" end="" %}
-
-{% include-markdown "../../README.md" start="" end="" %}
-
-## Resources
-
-{% include-markdown "../../README.md" start="" end="" %}
diff --git a/docs/src/learn/add-reactpy-to-a-django-project.md b/docs/src/learn/add-reactpy-to-a-django-project.md
new file mode 100644
index 00000000..371893e1
--- /dev/null
+++ b/docs/src/learn/add-reactpy-to-a-django-project.md
@@ -0,0 +1,128 @@
+## Overview
+
+
+
+If you want to add some interactivity to your existing **Django project**, you don't have to rewrite it in ReactPy. Use [ReactPy-Django](https://github.com/reactive-python/reactpy-django) to add [ReactPy](https://github.com/reactive-python/reactpy) to your existing stack, and render interactive components anywhere.
+
+
+
+!!! abstract "Note"
+
+ These docs assumes you have already created [a **Django project**](https://docs.djangoproject.com/en/stable/intro/tutorial01/), which involves creating and installing at least one **Django app**.
+
+ If do not have a **Django project**, check out this [9 minute YouTube tutorial](https://www.youtube.com/watch?v=ZsJRXS_vrw0) created by _IDG TECHtalk_.
+
+---
+
+## Step 1: Install from PyPI
+
+Run the following command to install [`reactpy-django`](https://pypi.org/project/reactpy-django/) in your Python environment.
+
+```bash linenums="0"
+pip install reactpy-django
+```
+
+## Step 2: Configure `settings.py`
+
+Add `#!python "reactpy_django"` to [`INSTALLED_APPS`](https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-INSTALLED_APPS) in your [`settings.py`](https://docs.djangoproject.com/en/stable/topics/settings/) file.
+
+=== "settings.py"
+
+ ```python
+ {% include "../../examples/python/configure_installed_apps.py" %}
+ ```
+
+??? warning "Enable ASGI and Django Channels (Required)"
+
+ ReactPy-Django requires Django ASGI and [Django Channels](https://github.com/django/channels) WebSockets.
+
+ If you have not enabled ASGI on your **Django project** yet, here is a summary of the [`django`](https://docs.djangoproject.com/en/stable/howto/deployment/asgi/) and [`channels`](https://channels.readthedocs.io/en/stable/installation.html) installation docs:
+
+ 1. Install `channels[daphne]`
+ 2. Add `#!python "daphne"` to `#!python INSTALLED_APPS`.
+
+ ```python linenums="0"
+ {% include "../../examples/python/configure_channels_installed_app.py" %}
+ ```
+
+ 3. Set your `#!python ASGI_APPLICATION` variable.
+
+ ```python linenums="0"
+ {% include "../../examples/python/configure_channels_asgi_app.py" %}
+ ```
+
+??? info "Configure ReactPy settings (Optional)"
+
+ ReactPy's has additional configuration available to fit a variety of use cases.
+
+ See the [ReactPy settings](../reference/settings.md) documentation to learn more.
+
+## Step 3: Configure `urls.py`
+
+Add ReactPy HTTP paths to your `#!python urlpatterns` in your [`urls.py`](https://docs.djangoproject.com/en/stable/topics/http/urls/) file.
+
+=== "urls.py"
+
+ ```python
+ {% include "../../examples/python/configure_urls.py" %}
+ ```
+
+## Step 4: Configure `asgi.py`
+
+Register ReactPy's WebSocket using `#!python REACTPY_WEBSOCKET_ROUTE` in your [`asgi.py`](https://docs.djangoproject.com/en/stable/howto/deployment/asgi/) file.
+
+=== "asgi.py"
+
+ ```python
+ {% include "../../examples/python/configure_asgi.py" %}
+ ```
+
+??? info "Add `#!python AuthMiddlewareStack` (Optional)"
+
+ There are many situations where you need to access the Django `#!python User` or `#!python Session` objects within ReactPy components. For example, if you want to:
+
+ 1. Access the `#!python User` that is currently logged in
+ 3. Access Django's `#!python Session` object
+ 2. Login or logout the current `#!python User`
+
+ In these situations will need to ensure you are using `#!python AuthMiddlewareStack`.
+
+ {% include "../../includes/auth-middleware-stack.md" %}
+
+??? question "Where is my `asgi.py`?"
+
+ If you do not have an `asgi.py`, follow the [`channels` installation guide](https://channels.readthedocs.io/en/stable/installation.html).
+
+## Step 5: Run database migrations
+
+Run Django's [`migrate` command](https://docs.djangoproject.com/en/stable/topics/migrations/) to initialize ReactPy-Django's database table.
+
+```bash linenums="0"
+python manage.py migrate
+```
+
+## Step 6: Check your configuration
+
+Run Django's [`check` command](https://docs.djangoproject.com/en/stable/ref/django-admin/#check) to verify if ReactPy was set up correctly.
+
+```bash linenums="0"
+python manage.py check
+```
+
+## Step 7: Create your first component
+
+The [next page](./your-first-component.md) will show you how to create your first ReactPy component.
+
+Prefer a quick summary? Read the **At a Glance** section below.
+
+!!! info "At a Glance"
+
+ **`my_app/components.py`**
+
+ {% include-markdown "../../../README.md" start="" end="" %}
+
+ ---
+
+ **`my_app/templates/my_template.html`**
+
+ {% include-markdown "../../../README.md" start="" end="" %}
diff --git a/docs/src/learn/your-first-component.md b/docs/src/learn/your-first-component.md
new file mode 100644
index 00000000..e3be5da4
--- /dev/null
+++ b/docs/src/learn/your-first-component.md
@@ -0,0 +1,137 @@
+## Overview
+
+
+
+Components are one of the core concepts of ReactPy. They are the foundation upon which you build user interfaces (UI), which makes them the perfect place to start your journey!
+
+
+
+!!! abstract "Note"
+
+ If you have reached this point, you should have already [installed ReactPy-Django](./add-reactpy-to-a-django-project.md) through the previous steps.
+
+---
+
+## Selecting a Django App
+
+You will now need to pick at least one **Django app** to start using ReactPy-Django on.
+
+For the following examples, we will assume the following:
+
+1. You have a **Django app** named `my_app`, which was created by Django's [`startapp` command](https://docs.djangoproject.com/en/stable/intro/tutorial01/#creating-the-polls-app).
+2. You have placed `my_app` directly into your **Django project** folder (`./example_project/my_app`). This is common for small projects.
+
+??? question "How do I organize my Django project for ReactPy?"
+
+ ReactPy-Django has no project structure requirements. Organize everything as you wish, just like any **Django project**.
+
+## Defining a component
+
+You will need a file to start creating ReactPy components.
+
+We recommend creating a `components.py` file within your chosen **Django app** to start out. For this example, the file path will look like this: `./example_project/my_app/components.py`.
+
+Within this file, you will define your component function(s) using the `#!python @component` decorator.
+
+=== "components.py"
+
+ {% include-markdown "../../../README.md" start="" end="" %}
+
+??? question "What should I name my ReactPy files and functions?"
+
+ You have full freedom in naming/placement of your files and functions.
+
+ We recommend creating a `components.py` for small **Django apps**. If your app has a lot of components, you should consider breaking them apart into individual modules such as `components/navbar.py`.
+
+ Ultimately, components are referenced by Python dotted path in `my_template.html` ([_see next step_](#embedding-in-a-template)). This dotted path must be valid to Python's `#!python importlib`.
+
+??? question "What does the decorator actually do?"
+
+ While not all components need to be decorated, there are a few features this decorator adds to your components.
+
+ 1. The ability to be used as a root component.
+ - The decorator is required for any component that you want to reference in your Django templates ([_see next step_](#embedding-in-a-template)).
+ 2. The ability to use [hooks](../reference/hooks.md).
+ - The decorator is required on any component where hooks are defined.
+ 3. Scoped failures.
+ - If a decorated component generates an exception, then only that one component will fail to render.
+
+## Embedding in a template
+
+In your **Django app**'s HTML template, you can now embed your ReactPy component using the `#!jinja {% component %}` template tag. Within this tag, you will need to type in the dotted path to the component.
+
+Additionally, you can pass in `#!python args` and `#!python kwargs` into your component function. After reading the code below, pay attention to how the function definition for `#!python hello_world` ([_from the previous step_](#defining-a-component)) accepts a `#!python recipient` argument.
+
+=== "my_template.html"
+
+ {% include-markdown "../../../README.md" start="" end="" %}
+
+???+ tip "Components are automatically registered!"
+
+ ReactPy-Django will automatically register any component that is referenced in a Django HTML template. This means you typically do not need to [manually register](../reference/utils.md#register-component) components in your **Django app**.
+
+ Please note that this HTML template must be properly stored within a registered Django app. ReactPy-Django will output a console log message containing all detected components when the server starts up.
+
+{% include-markdown "../reference/template-tag.md" start="" end="" %}
+
+{% include-markdown "../reference/template-tag.md" start="" end="" %}
+
+??? question "Where is my templates folder?"
+
+ If you do not have a `./templates/` folder in your **Django app**, you can simply create one! Keep in mind, templates within this folder will not be detected by Django unless you [add the corresponding **Django app** to `settings.py:INSTALLED_APPS`](https://docs.djangoproject.com/en/stable/ref/applications/#configuring-applications).
+
+## Setting up a Django view
+
+Within your **Django app**'s `views.py` file, you will need to [create a view function](https://docs.djangoproject.com/en/stable/intro/tutorial01/#write-your-first-view) to render the HTML template `my_template.html` ([_from the previous step_](#embedding-in-a-template)).
+
+=== "views.py"
+
+ ```python
+ {% include "../../examples/python/first_view.py" %}
+ ```
+
+We will add this new view into your [`urls.py`](https://docs.djangoproject.com/en/stable/intro/tutorial01/#write-your-first-view) and define what URL it should be accessible at.
+
+=== "urls.py"
+
+ ```python
+ {% include "../../examples/python/first_urls.py" %}
+ ```
+
+??? question "Which urls.py do I add my views to?"
+
+ For simple **Django projects**, you can easily add all of your views directly into the **Django project's** `urls.py`. However, as you start increase your project's complexity you might end up with way too much within one file.
+
+ Once you reach that point, we recommend creating an individual `urls.py` within each of your **Django apps**.
+
+ Then, within your **Django project's** `urls.py` you will use Django's [`include` function](https://docs.djangoproject.com/en/stable/ref/urls/#include) to link it all together.
+
+## Viewing your component
+
+To test your new Django view, run the following command to start up a development web server.
+
+```bash linenums="0"
+python manage.py runserver
+```
+
+Now you can navigate to your **Django project** URL that contains a ReactPy component, such as [`http://127.0.0.1:8000/example/`](http://127.0.0.1:8000/example/) ([_from the previous step_](#setting-up-a-django-view)).
+
+If you copy-pasted our example component, you will now see your component display "Hello World".
+
+??? warning "Do not use `manage.py runserver` for production"
+
+ This command is only intended for development purposes. For production deployments make sure to read [Django's documentation](https://docs.djangoproject.com/en/stable/howto/deployment/).
+
+## Learn more
+
+**Congratulations!** If you followed the previous steps, you have now created a "Hello World" component using ReactPy-Django!
+
+!!! info "Deep Dive"
+
+ The docs you are reading only covers our Django integration. To learn more, check out one of the following links:
+
+ - [ReactPy-Django Feature Reference](../reference/components.md)
+ - [ReactPy Core Documentation](https://reactpy.dev/docs/guides/creating-interfaces/index.html)
+ - [Ask Questions on Discord](https://discord.gg/uNb5P4hA9X)
+
+ Additionally, the vast majority of tutorials/guides you find for ReactJS can be applied to ReactPy.
diff --git a/docs/src/reference/components.md b/docs/src/reference/components.md
new file mode 100644
index 00000000..448af463
--- /dev/null
+++ b/docs/src/reference/components.md
@@ -0,0 +1,595 @@
+## Overview
+
+
+
+We supply some pre-designed that components can be used to help simplify development.
+
+
+
+---
+
+## PyScript Component
+
+This allows you to embedded any number of client-side PyScript components within traditional ReactPy components.
+
+{% include-markdown "./template-tag.md" start="" end="" %}
+
+{% include-markdown "./template-tag.md" start="" end="" %}
+
+=== "components.py"
+
+ ```python
+ {% include "../../examples/python/pyscript_ssr_parent.py" %}
+ ```
+
+=== "root.py"
+
+ ```python
+ {% include "../../examples/python/pyscript_ssr_child.py" %}
+ ```
+
+=== "my_template.html"
+
+ ```jinja
+ {% include "../../examples/html/pyscript_ssr_parent.html" %}
+ ```
+
+??? example "See Interface"
+
+ **Parameters**
+
+ | Name | Type | Description | Default |
+ | --- | --- | --- | --- |
+ | `#!python *file_paths` | `#!python str` | File path to your client-side component. If multiple paths are provided, the contents are automatically merged. | N/A |
+ | `#!python initial` | `#!python str | VdomDict | ComponentType` | The initial HTML that is displayed prior to the PyScript component loads. This can either be a string containing raw HTML, a `#!python reactpy.html` snippet, or a non-interactive component. | `#!python ""` |
+ | `#!python root` | `#!python str` | The name of the root component function. | `#!python "root"` |
+
+
+
+??? warning "You must call `pyscript_setup` in your Django template before using this tag!"
+
+ This requires using of the [`#!jinja {% pyscript_setup %}` template tag](./template-tag.md#pyscript-setup) to initialize PyScript on the client.
+
+ === "my_template.html"
+
+ ```jinja
+ {% include "../../examples/html/pyscript_setup.html" %}
+ ```
+
+
+
+{% include-markdown "./template-tag.md" start="" end="" %}
+
+{% include-markdown "./template-tag.md" start="" end="" %}
+
+{% include-markdown "./template-tag.md" start="" end="" trailing-newlines=false preserve-includer-indent=false %}
+
+ === "components.py"
+
+ ```python
+ {% include "../../examples/python/pyscript_component_multiple_files_root.py" %}
+ ```
+
+ === "root.py"
+
+ ```python
+ {% include "../../examples/python/pyscript_multiple_files_root.py" %}
+ ```
+
+ === "child.py"
+
+ ```python
+ {% include "../../examples/python/pyscript_multiple_files_child.py" %}
+ ```
+
+??? question "How do I display something while the component is loading?"
+
+ You can configure the `#!python initial` keyword to display HTML while your PyScript component is loading.
+
+ The value for `#!python initial` is most commonly be a `#!python reactpy.html` snippet or a non-interactive `#!python @component`.
+
+ === "components.py"
+
+ ```python
+ {% include "../../examples/python/pyscript_component_initial_object.py" %}
+ ```
+
+ However, you can also use a string containing raw HTML.
+
+ === "components.py"
+
+ ```python
+ {% include "../../examples/python/pyscript_component_initial_string.py" %}
+ ```
+
+??? question "Can I use a different name for my root component?"
+
+ Yes, you can use the `#!python root` keyword to specify a different name for your root function.
+
+ === "components.py"
+
+ ```python
+ {% include "../../examples/python/pyscript_component_root.py" %}
+ ```
+
+ === "main.py"
+
+ ```python
+ {% include "../../examples/python/pyscript_root.py" %}
+ ```
+
+---
+
+## View To Component
+
+Automatically convert a Django view into a component.
+
+At this time, this works best with static views with no interactivity.
+
+Compatible with sync or async [Function Based Views](https://docs.djangoproject.com/en/stable/topics/http/views/) and [Class Based Views](https://docs.djangoproject.com/en/stable/topics/class-based-views/).
+
+=== "components.py"
+
+ ```python
+ {% include "../../examples/python/vtc.py" %}
+ ```
+
+=== "views.py"
+
+ ```python
+ {% include "../../examples/python/hello_world_fbv.py" %}
+ ```
+
+??? example "See Interface"
+
+ **Parameters**
+
+ | Name | Type | Description | Default |
+ | --- | --- | --- | --- |
+ | `#!python view` | `#!python Callable | View | str` | The view to convert, or the view's dotted path as a string. | N/A |
+ | `#!python transforms` | `#!python Sequence[Callable[[VdomDict], Any]]` | A list of functions that transforms the newly generated VDOM. The functions will be called on each VDOM node. | `#!python tuple` |
+ | `#!python strict_parsing` | `#!python bool` | If `#!python True`, an exception will be generated if the HTML does not perfectly adhere to HTML5. | `#!python True` |
+
+ **Returns**
+
+ | Type | Description |
+ | --- | --- |
+ | `#!python constructor` | A function that takes `#!python request, *args, key, **kwargs` and returns a ReactPy component. Note that `#!python *args` and `#!python **kwargs` are directly provided to your view. |
+
+??? info "Existing limitations"
+
+ There are currently several limitations of using `#!python view_to_component` that will be [resolved in a future version](https://github.com/reactive-python/reactpy-django/issues/269).
+
+ - Requires manual intervention to change HTTP methods to anything other than `GET`.
+ - ReactPy events cannot conveniently be attached to converted view HTML.
+ - Has no option to automatically intercept click events from hyperlinks (such as `#!html `).
+
+??? question "How do I use this for Class Based Views?"
+
+ Class Based Views are accepted by `#!python view_to_component` as an argument.
+
+ Calling `#!python as_view()` is optional, but recommended.
+
+ === "components.py"
+
+ ```python
+ {% include "../../examples/python/vtc_cbv.py" %}
+ ```
+
+ === "views.py"
+
+ ```python
+ {% include "../../examples/python/hello_world_cbv.py" %}
+ ```
+
+??? question "How do I provide `#!python request`, `#!python args`, and `#!python kwargs` to a converted view?"
+
+ This component accepts `#!python request`, `#!python *args`, and `#!python **kwargs` arguments, which are sent to your provided view.
+
+ === "components.py"
+
+ ```python
+ {% include "../../examples/python/vtc_args.py" %}
+ ```
+
+ === "views.py"
+
+ ```python
+ {% include "../../examples/python/hello_world_args_kwargs.py" %}
+ ```
+
+??? question "How do I customize this component's behavior?"
+
+ This component accepts arguments that can be used to customize its behavior.
+
+ Below are all the arguments that can be used.
+
+ ---
+
+ **`#!python strict_parsing`**
+
+ By default, an exception will be generated if your view's HTML does not perfectly adhere to HTML5.
+
+ However, there are some circumstances where you may not have control over the original HTML, so you may be unable to fix it. Or you may be relying on non-standard HTML tags such as `#!html Hello World `.
+
+ In these scenarios, you may want to rely on best-fit parsing by setting the `#!python strict_parsing` parameter to `#!python False`. This uses `libxml2` recovery algorithm, which is designed to be similar to how web browsers would attempt to parse non-standard or broken HTML.
+
+ === "components.py"
+
+ ```python
+ {% include "../../examples/python/vtc_strict_parsing.py" %}
+ ```
+
+ === "views.py"
+
+ ```python
+ {% include "../../examples/python/hello_world_fbv.py" %}
+ ```
+
+ ---
+
+ **`#!python transforms`**
+
+ After your view has been turned into [VDOM](https://reactpy.dev/docs/reference/specifications.html#vdom) (python dictionaries), `#!python view_to_component` will call your `#!python transforms` functions on every VDOM node.
+
+ This allows you to modify your view prior to rendering.
+
+ For example, if you are trying to modify the text of a node with a certain `#!python id`, you can create a transform like such:
+
+ === "components.py"
+
+ ```python
+ {% include "../../examples/python/vtc_transforms.py" %}
+ ```
+
+ === "views.py"
+
+ ```python
+ {% include "../../examples/python/hello_world_fbv_with_id.py" %}
+ ```
+
+---
+
+## View To Iframe
+
+Automatically convert a Django view into an [`iframe` element](https://www.techtarget.com/whatis/definition/IFrame-Inline-Frame).
+
+The contents of this `#!python iframe` is handled entirely by traditional Django view rendering. While this solution is compatible with more views than `#!python view_to_component`, it comes with different limitations.
+
+Compatible with sync or async [Function Based Views](https://docs.djangoproject.com/en/stable/topics/http/views/) and [Class Based Views](https://docs.djangoproject.com/en/stable/topics/class-based-views/).
+
+=== "components.py"
+
+ ```python
+ {% include "../../examples/python/vti.py" %}
+ ```
+
+=== "views.py"
+
+ ```python
+ {% include "../../examples/python/hello_world_fbv.py" %}
+ ```
+
+=== "apps.py"
+
+ ```python
+ {% include "../../examples/python/hello_world_app_config_fbv.py" %}
+ ```
+
+??? example "See Interface"
+
+ **Parameters**
+
+ | Name | Type | Description | Default |
+ | --- | --- | --- | --- |
+ | `#!python view` | `#!python Callable | View | str` | The view function or class to convert. | N/A |
+ | `#!python extra_props` | `#!python Mapping[str, Any] | None` | Additional properties to add to the `iframe` element. | `#!python None` |
+
+
+ **Returns**
+
+ | Type | Description |
+ | --- | --- |
+ | `#!python constructor` | A function that takes `#!python *args, key, **kwargs` and returns a ReactPy component. Note that `#!python *args` and `#!python **kwargs` are directly provided to your view. |
+
+??? info "Existing limitations"
+
+ There are currently several limitations of using `#!python view_to_iframe` which may be [resolved in a future version](https://github.com/reactive-python/reactpy-django/issues/268).
+
+ - No built-in method of signalling events back to the parent component.
+ - All provided `#!python args` and `#!python kwargs` must be serializable values, since they are encoded into the URL.
+ - The `#!python iframe` will always load **after** the parent component.
+ - CSS styling for `#!python iframe` elements tends to be awkward.
+
+??? question "How do I use this for Class Based Views?"
+
+ Class Based Views are accepted by `#!python view_to_iframe` as an argument.
+
+ Calling `#!python as_view()` is optional, but recommended.
+
+ === "components.py"
+
+ ```python
+ {% include "../../examples/python/vti_cbv.py" %}
+ ```
+
+ === "views.py"
+
+ ```python
+ {% include "../../examples/python/hello_world_cbv.py" %}
+ ```
+
+ === "apps.py"
+
+ ```python
+ {% include "../../examples/python/hello_world_app_config_cbv.py" %}
+ ```
+
+??? question "How do I provide `#!python args` and `#!python kwargs` to a converted view?"
+
+ This component accepts `#!python *args` and `#!python **kwargs` arguments, which are sent to your provided view.
+
+ All provided `#!python *args` and `#!python *kwargs` must be serializable values, since they are encoded into the URL.
+
+ === "components.py"
+
+ ```python
+ {% include "../../examples/python/vti_args.py" %}
+ ```
+
+ === "views.py"
+
+ ```python
+ {% include "../../examples/python/hello_world_fbv.py" %}
+ ```
+
+ === "apps.py"
+
+ ```python
+ {% include "../../examples/python/hello_world_app_config_fbv.py" %}
+ ```
+
+??? question "How do I customize this component's behavior?"
+
+ This component accepts arguments that can be used to customize its behavior.
+
+ Below are all the arguments that can be used.
+
+ ---
+
+ **`#!python extra_props`**
+
+ This component accepts a `#!python extra_props` parameter, which is a dictionary of additional properties to add to the `#!python iframe` element.
+
+ For example, if you want to add a `#!python title` attribute to the `#!python iframe` element, you can do so like such:
+
+ === "components.py"
+
+ ```python
+ {% include "../../examples/python/vti_extra_props.py" %}
+ ```
+
+ === "views.py"
+
+ ```python
+ {% include "../../examples/python/hello_world_fbv.py" %}
+ ```
+
+ === "apps.py"
+
+ ```python
+ {% include "../../examples/python/hello_world_app_config_fbv.py" %}
+ ```
+
+---
+
+## Django Form
+
+Automatically convert a Django form into a ReactPy component.
+
+Compatible with both [standard Django forms](https://docs.djangoproject.com/en/stable/topics/forms/#building-a-form) and [ModelForms](https://docs.djangoproject.com/en/stable/topics/forms/modelforms/).
+
+=== "components.py"
+
+ ```python
+ {% include "../../examples/python/django_form.py" %}
+ ```
+
+=== "forms.py"
+
+ ```python
+ {% include "../../examples/python/django_form_class.py" %}
+ ```
+
+??? example "See Interface"
+
+ **Parameters**
+
+ | Name | Type | Description | Default |
+ | --- | --- | --- | --- |
+ | `#!python form` | `#!python type[Form | ModelForm]` | The form to convert. | N/A |
+ | `#!python on_success` | `#!python AsyncFormEvent | SyncFormEvent | None` | A callback function that is called when the form is successfully submitted. | `#!python None` |
+ | `#!python on_error` | `#!python AsyncFormEvent | SyncFormEvent | None` | A callback function that is called when the form submission fails. | `#!python None` |
+ | `#!python on_receive_data` | `#!python AsyncFormEvent | SyncFormEvent | None` | A callback function that is called before newly submitted form data is rendered. | `#!python None` |
+ | `#!python on_change` | `#!python AsyncFormEvent | SyncFormEvent | None` | A callback function that is called when a form field is modified by the user. | `#!python None` |
+ | `#!python auto_save` | `#!python bool` | If `#!python True`, the form will automatically call `#!python save` on successful submission of a `#!python ModelForm`. This has no effect on regular `#!python Form` instances. | `#!python True` |
+ | `#!python extra_props` | `#!python dict[str, Any] | None` | Additional properties to add to the `#!html