+ 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.
+
+ 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. +
+
+ ReactPy components are Python functions. Want to show some content conditionally? Use an
+ if
statement. Displaying a list? Try lists with map()
. Learning ReactPy is
+ learning
+ programming.
+
+ 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. +
++ 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. +
+- -You will need to set up a Python environment to run the ReactPy-Django 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.9+](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 background: - -```bash linenums="0" -nox -s test -- --headless -``` - -## Django Tests - -If you want to only run our Django tests in your current environment, you can use the following command: - -```bash linenums="0" -cd tests -python manage.py test -``` - -## Django Test Webserver - -If you want to manually run the Django test application, you can use the following command: - -```bash linenums="0" -cd tests -python manage.py runserver -``` 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 1594baa5..00000000 --- a/docs/src/get-started/choose-django-app.md +++ /dev/null @@ -1,26 +0,0 @@ -## Overview - -- -Set up a **Django Project** with at least one app. - -
- -!!! note - - If you have reached this point, you should have already [installed ReactPy-Django](../get-started/installation.md) through the previous steps. - ---- - -## Deciding which Django App to use - -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/dev/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**. diff --git a/docs/src/get-started/create-component.md b/docs/src/get-started/create-component.md deleted file mode 100644 index 1f94c308..00000000 --- a/docs/src/get-started/create-component.md +++ /dev/null @@ -1,40 +0,0 @@ -## Overview - -- -You can let ReactPy know what functions are components by using the `#!python @component` decorator. - -
- ---- - -## Declaring a function as a root 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 can define your component functions and then add ReactPy's `#!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_](./use-template-tag.md)). So, at minimum your component path needs to be valid to Python's `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_](./use-template-tag.md)). - 2. The ability to use [hooks](../features/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. diff --git a/docs/src/get-started/learn-more.md b/docs/src/get-started/learn-more.md deleted file mode 100644 index 3ee45968..00000000 --- a/docs/src/get-started/learn-more.md +++ /dev/null @@ -1,17 +0,0 @@ -# :confetti_ball: Congratulations :confetti_ball: - -- -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](../features/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/get-started/register-view.md b/docs/src/get-started/register-view.md deleted file mode 100644 index 472d722b..00000000 --- a/docs/src/get-started/register-view.md +++ /dev/null @@ -1,41 +0,0 @@ -## Overview - -- -Render your template containing your ReactPy component using a Django view. - -
- -!!! Note - - We assume you have [created a Django View](https://docs.djangoproject.com/en/dev/intro/tutorial01/#write-your-first-view) before, but we have included a simple example below. - ---- - -## Creating a Django view and URL path - -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_](./use-template-tag.md)). - -=== "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 cb4f87f1..00000000 --- a/docs/src/get-started/run-webserver.md +++ /dev/null @@ -1,27 +0,0 @@ -## Overview - -- -Run a webserver to display your Django view. - -
- ---- - -## Viewing your component using a 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 a ReactPy component, such as [`http://127.0.0.1:8000/example/`](http://127.0.0.1:8000/example/) ([_from the previous step_](./register-view.md)). - -If you copy-pasted our example component, you will now see your component display "Hello World". - -!!! warning "Pitfall" - - 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/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 8ef210fd..00000000 --- a/docs/src/get-started/use-template-tag.md +++ /dev/null @@ -1,27 +0,0 @@ -## Overview - -- -Decide where the component will be displayed by using our template tag. - -
- ---- - -## Embedding a component in a template - -{% include-markdown "../../../README.md" start="" end="" %} - -Additionally, you can pass in `args` and `kwargs` into your component function. After reading the code below, pay attention to how the function definition for `hello_world` ([_from the previous step_](./create-component.md)) accepts a `recipient` argument. - -=== "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="" %} - -??? 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/get-started/installation.md b/docs/src/learn/add-reactpy-to-a-project.md similarity index 63% rename from docs/src/get-started/installation.md rename to docs/src/learn/add-reactpy-to-a-project.md index be727947..0c9cafa1 100644 --- a/docs/src/get-started/installation.md +++ b/docs/src/learn/add-reactpy-to-a-project.md @@ -2,7 +2,7 @@-[ReactPy-Django](https://github.com/reactive-python/reactpy-django) can be used to add used to add [ReactPy](https://github.com/reactive-python/reactpy) support to an existing **Django project**. Minimal configuration is required to get started. +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.
@@ -16,13 +16,15 @@ ## 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`](https://docs.djangoproject.com/en/dev/topics/settings/) +## Step 2: Configure `settings.py` -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). +Add `reactpy_django` to [`INSTALLED_APPS`](https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-INSTALLED_APPS) in your [`settings.py`](https://docs.djangoproject.com/en/dev/topics/settings/) file. === "settings.py" @@ -50,11 +52,11 @@ In your settings you will need to add `reactpy_django` to [`INSTALLED_APPS`](htt ??? note "Configure ReactPy settings (Optional)" - {% include "../features/settings.md" start="" end="" %} + {% include "../reference/settings.md" start="" end="" %} -## Step 3: Configure [`urls.py`](https://docs.djangoproject.com/en/dev/topics/http/urls/) +## Step 3: Configure `urls.py` -Add ReactPy HTTP paths to your `urlpatterns`. +Add ReactPy HTTP paths to your `urlpatterns` in your [`urls.py`](https://docs.djangoproject.com/en/dev/topics/http/urls/) file. === "urls.py" @@ -62,9 +64,9 @@ Add ReactPy HTTP paths to your `urlpatterns`. {% include "../../python/configure-urls.py" %} ``` -## Step 4: Configure [`asgi.py`](https://docs.djangoproject.com/en/dev/howto/deployment/asgi/) +## Step 4: Configure `asgi.py` -Register ReactPy's Websocket using `REACTPY_WEBSOCKET_ROUTE`. +Register ReactPy's WebSocket using `REACTPY_WEBSOCKET_ROUTE` in your [`asgi.py`](https://docs.djangoproject.com/en/dev/howto/deployment/asgi/) file. === "asgi.py" @@ -92,7 +94,7 @@ Register ReactPy's Websocket using `REACTPY_WEBSOCKET_ROUTE`. ## Step 5: Run database migrations -Run Django's database migrations to initialize ReactPy-Django's database table. +Run Django's [`migrate` command](https://docs.djangoproject.com/en/dev/topics/migrations/) to initialize ReactPy-Django's database table. ```bash linenums="0" python manage.py migrate @@ -100,19 +102,19 @@ python manage.py migrate ## Step 6: Check your configuration -Run Django's check command to verify if ReactPy was set up correctly. +Run Django's [`check` command](https://docs.djangoproject.com/en/dev/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! +## Step 7: Create your first component -The [following steps](./choose-django-app.md) will show you how to create your first ReactPy component. +The [next step](./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" +!!! info "At a Glance: Your First Component" **`my_app/components.py`** diff --git a/docs/src/learn/your-first-component.md b/docs/src/learn/your-first-component.md new file mode 100644 index 00000000..e40c5224 --- /dev/null +++ b/docs/src/learn/your-first-component.md @@ -0,0 +1,133 @@ +## 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! + +
+ +!!! note + + If you have reached this point, you should have already [installed ReactPy-Django](../get-started/add-reactpy-to-a-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/dev/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 can define your component functions and then add ReactPy's `#!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)). So, at minimum your component path needs to be valid to Python's `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 + +{% include-markdown "../../../README.md" start="" end="" %} + +Additionally, you can pass in `args` and `kwargs` into your component function. After reading the code below, pay attention to how the function definition for `hello_world` ([_from the previous step_](#defining-a-component)) accepts a `recipient` argument. + +=== "my-template.html" + + {% include-markdown "../../../README.md" start="" end="" %} + +{% 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/dev/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/dev/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 "../../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) and define what URL it should be accessible at. + +=== "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. + +## Viewing your component + +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 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 "Pitfall" + + 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/dev/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/features/components.md b/docs/src/reference/components.md similarity index 100% rename from docs/src/features/components.md rename to docs/src/reference/components.md diff --git a/docs/src/features/decorators.md b/docs/src/reference/decorators.md similarity index 100% rename from docs/src/features/decorators.md rename to docs/src/reference/decorators.md diff --git a/docs/src/features/hooks.md b/docs/src/reference/hooks.md similarity index 100% rename from docs/src/features/hooks.md rename to docs/src/reference/hooks.md diff --git a/docs/src/features/settings.md b/docs/src/reference/settings.md similarity index 98% rename from docs/src/features/settings.md rename to docs/src/reference/settings.md index 00662976..1fe669dd 100644 --- a/docs/src/features/settings.md +++ b/docs/src/reference/settings.md @@ -29,7 +29,7 @@ These are ReactPy-Django's default settings values. You can modify these values | `REACTPY_DEFAULT_QUERY_POSTPROCESSOR` | `#!python "reactpy_django.utils.django_query_postprocessor"` | `#!python "example_project.my_query_postprocessor"` | Dotted path to the default `reactpy_django.hooks.use_query` postprocessor function. | | `REACTPY_AUTH_BACKEND` | `#!python "django.contrib.auth.backends.ModelBackend"` | `#!python "example_project.auth.MyModelBackend"` | Dotted path to the Django authentication backend to use for ReactPy components. This is only needed if:
ReactPy components are Python functions. Want to show some content conditionally? Use an
- if
statement. Displaying a list? Try lists with map()
. Learning ReactPy is
- learning
- programming.
+ if
statement. Displaying a list? Try using
+ list comprehension.
+ Learning ReactPy is learning programming.
{{ config.site_description }}
-ReactPy lets you build user interfaces out of individual pieces called components. Create your own ReactPy @@ -55,7 +54,7 @@
ReactPy components are Python functions. Want to show some content conditionally? Use an @@ -65,7 +64,7 @@
ReactPy components receive data and return what should appear on the screen. You can pass them new data in @@ -78,7 +77,7 @@
ReactPy is a library. It lets you put components together, but it doesn't prescribe how to do routing and diff --git a/docs/src/assets/css/admonition.css b/docs/src/assets/css/admonition.css index 206d637e..d99fbe3a 100644 --- a/docs/src/assets/css/admonition.css +++ b/docs/src/assets/css/admonition.css @@ -56,7 +56,7 @@ [data-md-color-scheme="slate"] .md-typeset .note .admonition-title:before { font-size: 1.1rem; - background-color: rgb(68 172 153); + background: rgb(68 172 153); } .md-typeset .note > .admonition-title:before, @@ -81,7 +81,7 @@ [data-md-color-scheme="slate"] .md-typeset .warning .admonition-title:before { font-size: 1.1rem; - background-color: rgb(219 125 39); + background: rgb(219 125 39); } /* Colors for "info" admonition */ @@ -100,7 +100,7 @@ [data-md-color-scheme="slate"] .md-typeset .info .admonition-title:before { font-size: 1.1rem; - background-color: rgb(136 145 236); + background: rgb(136 145 236); } /* Colors for "example" admonition */ @@ -115,7 +115,7 @@ } [data-md-color-scheme="slate"] .md-typeset .example .admonition-title:before { - background-color: rgb(246 247 249); + background: rgb(246 247 249); } [data-md-color-scheme="slate"] .md-typeset .admonition.example code { diff --git a/docs/src/assets/css/button.css b/docs/src/assets/css/button.css index 83c28a6e..2e0dbd92 100644 --- a/docs/src/assets/css/button.css +++ b/docs/src/assets/css/button.css @@ -2,7 +2,7 @@ border-color: #404756; border-radius: 9999px; color: #fff; - transition: color 125ms, background-color 125ms, border-color 125ms, + transition: color 125ms, background 125ms, border-color 125ms, transform 125ms; } @@ -20,7 +20,7 @@ .md-typeset .md-button.md-button--primary:focus, .md-typeset .md-button.md-button--primary:hover { border-color: transparent; - background-color: var(--reactpy-color-darker); + background: var(--reactpy-color-darker); } .md-typeset .md-button:focus { diff --git a/docs/src/assets/css/footer.css b/docs/src/assets/css/footer.css new file mode 100644 index 00000000..2b33fdb4 --- /dev/null +++ b/docs/src/assets/css/footer.css @@ -0,0 +1,3 @@ +.md-footer { + border: 1px solid rgb(255 255 255 / 5%); +} diff --git a/docs/src/assets/css/home.css b/docs/src/assets/css/home.css index 5ae08cc1..41f60b26 100644 --- a/docs/src/assets/css/home.css +++ b/docs/src/assets/css/home.css @@ -2,42 +2,79 @@ img.home-logo { height: 100px; } -.homepage-row { +.home-row { display: flex; justify-content: center; align-items: center; flex-direction: column; - padding: 6rem 0; - text-align: center; + padding: 6rem 0.8rem; } -.homepage-row.stripe { - background-color: var(--md-primary-fg-color); - color: var(--md-primary-bg-color); +.home-row.stripe { + background: conic-gradient( + from 90deg at -10% 100%, + #2b303b 0deg, + #2b303b 90deg, + #16181d 1turn + ); + border: 0 solid rgba(246, 247, 249, 0.1); + border-top-width: 1px; + border-bottom-width: 1px; } -.homepage-row h1 { +.home-row h1 { max-width: 28rem; - font-size: 52px; line-height: 1.15; font-weight: 500; margin-bottom: 0.55rem; margin-top: -1rem; } -.homepage-row.first h1 { +.home-row.first { + text-align: center; +} + +.home-row.first h1 { margin-top: 0.55rem; margin-bottom: -0.75rem; } -.homepage-row p { +.home-row p { max-width: 35rem; - font-size: 21px; line-height: 1.5; font-weight: 400; } -.homepage-row.first p { +.home-row.first p { font-size: 32px; font-weight: 500; } + +/* Desktop Styling */ +@media screen and (min-width: 76.1875em) { + .home-row { + text-align: center; + } + .home-row p { + font-size: 21px; + } + .home-row h1 { + font-size: 52px; + } +} + +/* Mobile Styling */ +@media screen and (max-width: 76.1875em) { + .home-row { + padding: 4rem 0.8rem; + } + .home-row.first { + padding-top: 2rem; + } + .home-btns { + width: 100%; + display: grid; + grid-gap: 0.5rem; + gap: 0.5rem; + } +} diff --git a/docs/src/assets/css/main.css b/docs/src/assets/css/main.css index 72a078bd..155f6675 100644 --- a/docs/src/assets/css/main.css +++ b/docs/src/assets/css/main.css @@ -6,12 +6,12 @@ [data-md-color-scheme="slate"] { --md-code-hl-color: #ffffcf1c; --md-hue: 225; - --md-default-bg-color: hsla(var(--md-hue), 15%, 16%, 1); + --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-code-bg-color: #16181d; - --md-primary-fg-color: #2b3540; + --md-primary-fg-color: var(--md-default-bg-color); --md-default-fg-color--light: #fff; --md-typeset-a-color: #00b0f0; --md-code-hl-comment-color: hsla(var(--md-hue), 75%, 90%, 0.43); @@ -19,6 +19,8 @@ --reactpy-color: #58b962; --reactpy-color-dark: #42914a; --reactpy-color-darker: #34743b; + --md-footer-bg-color: var(--md-default-bg-color); + --md-footer-bg-color--dark: var(--md-default-bg-color); } [data-md-color-scheme="default"] { @@ -62,3 +64,11 @@ h2#overview { .md-banner__inner { margin: 0.45rem auto; } + +/* Desktop Styles */ +@media screen and (min-width: 76.1875em) { + /* Remove max width on desktop */ + .md-grid { + max-width: none; + } +} diff --git a/docs/src/assets/css/navbar.css b/docs/src/assets/css/navbar.css new file mode 100644 index 00000000..ab52fc40 --- /dev/null +++ b/docs/src/assets/css/navbar.css @@ -0,0 +1,130 @@ +.md-header { + border: 0 solid transparent; + border-bottom-width: 1px; +} + +.md-header--shadow { + box-shadow: none; + border-color: rgb(255 255 255 / 5%); + transition: border-color 0.35s cubic-bezier(0.1, 0.7, 0.1, 1); +} + +.md-header__title { + display: none; +} + +/* Mobile Styling */ +@media screen and (max-width: 76.1875em) { + label.md-header__button.md-icon[for="__drawer"] { + order: 1; + } + .md-header__button.md-logo { + display: initial; + order: 2; + margin-right: auto; + } + .md-header__button[for="__search"] { + order: 3; + } + .md-header__option[data-md-component="palette"] { + order: 4; + } + .md-header__source { + display: initial; + order: 5; + } + .md-header__source .md-source__repository { + display: none; + } +} + +/* Desktop Styling */ +@media screen and (min-width: 76.1875em) { + /* 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.2rem; + padding-top: 0; + padding-bottom: 0; + } + .md-header__button.md-logo img { + height: 2rem; + } + + /* Search */ + .md-search { + order: 2; + 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: 3; + 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; + } + li.md-tabs__item.md-tabs__item--active { + background: #00b0f026; + border-radius: 9999px; + color: var(--md-typeset-a-color); + } + a.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: 4; + } + + /* GitHub info */ + .md-header__source { + order: 5; + 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 index b8becdd7..fabeed59 100644 --- a/docs/src/assets/css/sidebar.css +++ b/docs/src/assets/css/sidebar.css @@ -30,7 +30,7 @@ } } -/* Sidebar styling */ +/* Desktop Styling */ @media screen and (min-width: 76.1875em) { .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link { text-transform: uppercase; @@ -64,7 +64,7 @@ height: 100%; opacity: 0.2; z-index: -1; - background-color: grey; + background: grey; } .md-nav__item .md-nav__link--active:before { @@ -76,7 +76,7 @@ height: 100%; opacity: 0.15; z-index: -1; - background-color: var(--md-typeset-a-color); + background: var(--md-typeset-a-color); } .md-nav__link { diff --git a/docs/src/assets/css/table-of-contents.css b/docs/src/assets/css/table-of-contents.css index be9037a2..2dc61373 100644 --- a/docs/src/assets/css/table-of-contents.css +++ b/docs/src/assets/css/table-of-contents.css @@ -19,7 +19,7 @@ height: 100%; opacity: 0.15; z-index: -1; - background-color: var(--md-typeset-a-color); + background: var(--md-typeset-a-color); } [data-md-component="toc"] .md-nav__link { diff --git a/mkdocs.yml b/mkdocs.yml index 1356b5fe..4d83ebe6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -39,8 +39,10 @@ theme: features: - navigation.instant - navigation.tabs + - navigation.tabs.sticky - navigation.top - content.code.copy + - search.highlight icon: repo: fontawesome/brands/github logo: https://raw.githubusercontent.com/reactive-python/reactpy/main/branding/svg/reactpy-logo-square.svg @@ -95,8 +97,10 @@ extra_css: - assets/css/button.css - assets/css/admonition.css - assets/css/sidebar.css + - assets/css/navbar.css - assets/css/table-of-contents.css - assets/css/code.css + - assets/css/footer.css watch: - docs From d6a23f82238a8db77a057120af1728d59d282d75 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 27 Aug 2023 23:36:00 -0700 Subject: [PATCH 06/32] Fix mobile and tablet sites --- docs/src/assets/css/code.css | 2 ++ docs/src/assets/css/footer.css | 2 +- docs/src/assets/css/home.css | 4 ++-- docs/src/assets/css/main.css | 7 ++++++- docs/src/assets/css/navbar.css | 4 ++-- docs/src/assets/css/sidebar.css | 27 +++++++++++---------------- 6 files changed, 24 insertions(+), 22 deletions(-) diff --git a/docs/src/assets/css/code.css b/docs/src/assets/css/code.css index 4da68750..413f0c07 100644 --- a/docs/src/assets/css/code.css +++ b/docs/src/assets/css/code.css @@ -22,6 +22,8 @@ background: var(--tabbed-labels-color); border-top-left-radius: 8px; border-top-right-radius: 8px; + margin: 0; + padding-left: 0.8rem; } .md-typeset .tabbed-labels > label { diff --git a/docs/src/assets/css/footer.css b/docs/src/assets/css/footer.css index 2b33fdb4..9f21c4ba 100644 --- a/docs/src/assets/css/footer.css +++ b/docs/src/assets/css/footer.css @@ -1,3 +1,3 @@ .md-footer { - border: 1px solid rgb(255 255 255 / 5%); + border-top: 1px solid rgb(255 255 255 / 5%); } diff --git a/docs/src/assets/css/home.css b/docs/src/assets/css/home.css index 41f60b26..6cec69da 100644 --- a/docs/src/assets/css/home.css +++ b/docs/src/assets/css/home.css @@ -51,7 +51,7 @@ img.home-logo { } /* Desktop Styling */ -@media screen and (min-width: 76.1875em) { +@media screen and (min-width: 60em) { .home-row { text-align: center; } @@ -64,7 +64,7 @@ img.home-logo { } /* Mobile Styling */ -@media screen and (max-width: 76.1875em) { +@media screen and (max-width: 60em) { .home-row { padding: 4rem 0.8rem; } diff --git a/docs/src/assets/css/main.css b/docs/src/assets/css/main.css index 155f6675..5295cb7d 100644 --- a/docs/src/assets/css/main.css +++ b/docs/src/assets/css/main.css @@ -66,9 +66,14 @@ h2#overview { } /* Desktop Styles */ -@media screen and (min-width: 76.1875em) { +@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 index ab52fc40..f110b41b 100644 --- a/docs/src/assets/css/navbar.css +++ b/docs/src/assets/css/navbar.css @@ -14,7 +14,7 @@ } /* Mobile Styling */ -@media screen and (max-width: 76.1875em) { +@media screen and (max-width: 60em) { label.md-header__button.md-icon[for="__drawer"] { order: 1; } @@ -39,7 +39,7 @@ } /* Desktop Styling */ -@media screen and (min-width: 76.1875em) { +@media screen and (min-width: 60em) { /* Nav container */ nav.md-header__inner { display: contents; diff --git a/docs/src/assets/css/sidebar.css b/docs/src/assets/css/sidebar.css index fabeed59..ab25b439 100644 --- a/docs/src/assets/css/sidebar.css +++ b/docs/src/assets/css/sidebar.css @@ -1,4 +1,4 @@ -/* Move the sidebars to the edges of the page */ +/* Move the sidebar and TOC to the edge of the page */ .md-main__inner.md-grid { margin-left: 0; margin-right: 0; @@ -15,23 +15,9 @@ margin-left: auto; } -.md-content { - max-width: 56rem; -} - -/* Maintain content positioning even if sidebars are disabled */ -@media screen and (min-width: 76.1875em) { - .md-sidebar { - display: block; - } - - .md-sidebar[hidden] { - visibility: hidden; - } -} - /* Desktop Styling */ @media screen and (min-width: 76.1875em) { + /* Made the sidebar buttons look React-like */ .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link { text-transform: uppercase; } @@ -111,4 +97,13 @@ font-weight: 400; padding-left: 1.25rem; } + + /* Maintain content positioning even if sidebar or TOC is disabled */ + .md-sidebar { + display: block; + } + + .md-sidebar[hidden] { + visibility: hidden; + } } From 5c49769e3ef6ff1aa3c0ee64da8a36580d46f341 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 28 Aug 2023 01:12:17 -0700 Subject: [PATCH 07/32] switch to green color scheme --- docs/src/assets/css/main.css | 12 +++++++----- docs/src/assets/css/navbar.css | 2 +- docs/src/assets/css/sidebar.css | 3 +-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/src/assets/css/main.css b/docs/src/assets/css/main.css index 5295cb7d..bce976b4 100644 --- a/docs/src/assets/css/main.css +++ b/docs/src/assets/css/main.css @@ -1,6 +1,10 @@ /* Variable overrides */ :root { --code-max-height: 17.25rem; + --reactpy-color: #58b962; + --reactpy-color-dark: #42914a; + --reactpy-color-darker: #34743b; + --reactpy-color-opacity-10: rgb(88 185 98 / 10%); } [data-md-color-scheme="slate"] { @@ -13,14 +17,12 @@ --md-code-bg-color: #16181d; --md-primary-fg-color: var(--md-default-bg-color); --md-default-fg-color--light: #fff; - --md-typeset-a-color: #00b0f0; + --md-typeset-a-color: var(--reactpy-color); --md-code-hl-comment-color: hsla(var(--md-hue), 75%, 90%, 0.43); - --tabbed-labels-color: rgb(52 58 70); - --reactpy-color: #58b962; - --reactpy-color-dark: #42914a; - --reactpy-color-darker: #34743b; + --md-accent-fg-color: var(--reactpy-color-dark); --md-footer-bg-color: var(--md-default-bg-color); --md-footer-bg-color--dark: var(--md-default-bg-color); + --tabbed-labels-color: rgb(52 58 70); } [data-md-color-scheme="default"] { diff --git a/docs/src/assets/css/navbar.css b/docs/src/assets/css/navbar.css index f110b41b..f45c6705 100644 --- a/docs/src/assets/css/navbar.css +++ b/docs/src/assets/css/navbar.css @@ -92,7 +92,7 @@ overflow: visible; } li.md-tabs__item.md-tabs__item--active { - background: #00b0f026; + background: var(--reactpy-color-opacity-10); border-radius: 9999px; color: var(--md-typeset-a-color); } diff --git a/docs/src/assets/css/sidebar.css b/docs/src/assets/css/sidebar.css index ab25b439..8afb505a 100644 --- a/docs/src/assets/css/sidebar.css +++ b/docs/src/assets/css/sidebar.css @@ -60,9 +60,8 @@ left: 0; width: 100%; height: 100%; - opacity: 0.15; z-index: -1; - background: var(--md-typeset-a-color); + background: var(--reactpy-color-opacity-10); } .md-nav__link { From 7c6ccb1439f7de88783cdc22d66d8ae137a98630 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 28 Aug 2023 01:12:53 -0700 Subject: [PATCH 08/32] rename code tab color --- docs/src/assets/css/code.css | 2 +- docs/src/assets/css/main.css | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/assets/css/code.css b/docs/src/assets/css/code.css index 413f0c07..f81b95b2 100644 --- a/docs/src/assets/css/code.css +++ b/docs/src/assets/css/code.css @@ -19,7 +19,7 @@ } .js .md-typeset .tabbed-labels { - background: var(--tabbed-labels-color); + background: var(--code-tab-color); border-top-left-radius: 8px; border-top-right-radius: 8px; margin: 0; diff --git a/docs/src/assets/css/main.css b/docs/src/assets/css/main.css index bce976b4..0ce4c9a9 100644 --- a/docs/src/assets/css/main.css +++ b/docs/src/assets/css/main.css @@ -22,11 +22,11 @@ --md-accent-fg-color: var(--reactpy-color-dark); --md-footer-bg-color: var(--md-default-bg-color); --md-footer-bg-color--dark: var(--md-default-bg-color); - --tabbed-labels-color: rgb(52 58 70); + --code-tab-color: rgb(52 58 70); } [data-md-color-scheme="default"] { - --tabbed-labels-color: #7d829e26; + --code-tab-color: #7d829e26; } /* Font changes */ From d7567806e84e3b1e20f8ce474249a65c073a5847 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 28 Aug 2023 01:24:20 -0700 Subject: [PATCH 09/32] fix broken link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 31a383e2..391806d6 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ Follow the links below to find out more about this project. - [Documentation](https://reactive-python.github.io/reactpy-django) - [GitHub Discussions](https://github.com/reactive-python/reactpy-django/discussions) - [Discord](https://discord.gg/uNb5P4hA9X) -- [Contributor Guide](https://reactive-python.github.io/reactpy-django/about/code/) +- [Contributor Guide](https://reactive-python.github.io/reactpy-django/develop/about/code/) - [Code of Conduct](https://github.com/reactive-python/reactpy-django/blob/main/CODE_OF_CONDUCT.md) From c64b3e3dc8510ff7b497c5c974089c31d2153c51 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 28 Aug 2023 01:38:08 -0700 Subject: [PATCH 10/32] django proj --- ...actpy-to-a-project.md => add-reactpy-to-a-django-project.md} | 0 mkdocs.yml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename docs/src/learn/{add-reactpy-to-a-project.md => add-reactpy-to-a-django-project.md} (100%) diff --git a/docs/src/learn/add-reactpy-to-a-project.md b/docs/src/learn/add-reactpy-to-a-django-project.md similarity index 100% rename from docs/src/learn/add-reactpy-to-a-project.md rename to docs/src/learn/add-reactpy-to-a-django-project.md diff --git a/mkdocs.yml b/mkdocs.yml index 4d83ebe6..615a3588 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,7 +2,7 @@ nav: - Home: index.md - Get Started: - - Add ReactPy to a Project: learn/add-reactpy-to-a-project.md + - 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 From 7a1eaafe436842d9b13889241171ff5bc22c83d9 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 28 Aug 2023 01:53:57 -0700 Subject: [PATCH 11/32] no affiliation text --- docs/overrides/home.html | 3 --- docs/src/assets/css/footer.css | 12 ++++++++++++ mkdocs.yml | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/overrides/home.html b/docs/overrides/home.html index a61899d0..a7eee81f 100644 --- a/docs/overrides/home.html +++ b/docs/overrides/home.html @@ -3,9 +3,6 @@ {% block content %}{% endblock %} - -{% block footer %}{% endblock %} - {% block tabs %} From 444125d202b230dc42ec14b271a57a9dcdc731ef Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sat, 2 Sep 2023 02:59:55 -0700 Subject: [PATCH 26/32] home code examples --- .../home-code-examples/add-interactivity.py | 30 +++++++ .../home-code-examples/code-block.html | 8 ++ .../create-user-interfaces.py | 22 +++++ .../write-components-with-python.py | 15 ++++ docs/overrides/home.html | 57 +++++++++++-- docs/src/assets/css/home.css | 76 +++++++++--------- docs/src/assets/css/sidebar.css | 21 +---- docs/src/assets/img/add-interactivity.png | Bin 0 -> 20118 bytes .../src/assets/img/create-user-interfaces.png | Bin 0 -> 12381 bytes .../img/write-components-with-python.png | Bin 0 -> 15412 bytes mkdocs.yml | 2 +- 11 files changed, 169 insertions(+), 62 deletions(-) create mode 100644 docs/overrides/home-code-examples/add-interactivity.py create mode 100644 docs/overrides/home-code-examples/code-block.html create mode 100644 docs/overrides/home-code-examples/create-user-interfaces.py create mode 100644 docs/overrides/home-code-examples/write-components-with-python.py create mode 100644 docs/src/assets/img/add-interactivity.png create mode 100644 docs/src/assets/img/create-user-interfaces.png create mode 100644 docs/src/assets/img/write-components-with-python.png diff --git a/docs/overrides/home-code-examples/add-interactivity.py b/docs/overrides/home-code-examples/add-interactivity.py new file mode 100644 index 00000000..90976446 --- /dev/null +++ b/docs/overrides/home-code-examples/add-interactivity.py @@ -0,0 +1,30 @@ +from reactpy import component, html, use_state + + +def filter_videos(videos, search_text): + return None + + +def search_input(dictionary, value): + return None + + +def video_list(videos, empty_heading): + return None + + +@component +def searchable_video_list(videos): + search_text, set_search_text = use_state("") + found_videos = filter_videos(videos, search_text) + + return html._( + search_input( + {"on_change": lambda new_text: set_search_text(new_text)}, + value=search_text, + ), + video_list( + videos=found_videos, + empty_heading=f"No matches for “{search_text}”", + ), + ) diff --git a/docs/overrides/home-code-examples/code-block.html b/docs/overrides/home-code-examples/code-block.html new file mode 100644 index 00000000..5ee471be --- /dev/null +++ b/docs/overrides/home-code-examples/code-block.html @@ -0,0 +1,8 @@ +
{{ config.site_description }}
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.
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 @@ -50,36 +78,49 @@
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.
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.
+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.
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 +pwES-xD1g@N42}3dh)Mk27Vu4o
z!o {gfJ2c|J=rZC44j2>>uO&yKR?XV2uMDd9YXGxQ$MBHz5Z^V3
z94Sa)Q%?2q-9AU9UzB(LuAI@*iLT31B*{A$22vh(tCzfyCChlF)f%2;Z+*NsPltFG
zT{ogIlbm&G;~9bTgN;Vq9r-rC_*B8Ab}6RU6Ta8=G@xt`WkO*=D}~Y7i0DtIOp6EW
zpCo$hRZl91U&uWme7
zLB5r8|6QZ(elv!t
y
zD1V5_^M+UVFIe+%De{tIO}#9T6ENJPxQO@>2%I`BJCDI_NQuIibnRs6&zEP)IB{gI
z7O-7dvC!)+&Dt
a;daPwwy<$ar02_G
8T(jV^XqSLo~#%tE^lmCTu>X@U`H}!#hl6FCi?ED
z%!x`dQkywqBGzM2A1nUsZExJ$u=kfnso6nlz!G@6`aXX)!dr5U(aD7zY
zW;D&mrVjfH#?q%?PO
zvIDWOxVpMByK*qY9n4tR`1p`K*jd=wnUEArj_$Tj5H}`UN2
@Nje@HU8ex?@wD=f_ChD_KBv3c&4OC
zPefSckBoO{;r%ijr3FaGM+b&Y#BNLxCP;;!nx7|q;VS)u#-OH7nvT_b##m8^n3+rj
z)5xpZr_r0Haa9iBe3_ES?u}BhD8W|ta*3wllw%UZto7Me16IK-XhbnF!^@KlLV>;G
z(d49m#wxvNJDyy$BP_uJNZqM_M=zRy65T$q@CS;Y|Lcs%jai46NmdDA&J0(%>So8*
z{5elMyDO)B9o}jAc4r?hN+WGpcW!>;B6o*%R^;tz$`8m@OyjjT)fg()a1PQnsPR71
ztt{7aW$&0X;gwXb+Egv-lxO8SaO`R*PtdjHjg17NGu}od2sG&oDT<)iR{501FzFMZ
zD5pwK($vQD$0Pe0d_2jO3R?LNCQfw!@X@@Czzyic4w+l_>O$
`0JBiv3vIMix<#&C2V#KTy5;0@b+K7y7dKjk*$fF
dqKDx5kAkn*D}h7{ts;PiBDaJQAUtxU?8sfyo{TO$~cip{adn4^cK*
zX83?D0;0*aVE7+&qZ>3(YN!Xx|J
z42XVJ!3$VsDDpjf7qqUnxSUecCDnNQJs