diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index d880f2620e..626315fb7c 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -35,11 +35,28 @@ jobs: python-version: "3.11" activate-environment: true - - name: Install the project - run: uv sync --locked --all-extras --dev + - name: Install the project with latest reflex from main + run: | + uv sync --all-extras --dev \ + --upgrade-package reflex \ + --upgrade-package reflex-core \ + --upgrade-package reflex-docgen \ + --upgrade-package reflex-components-code \ + --upgrade-package reflex-components-core \ + --upgrade-package reflex-components-dataeditor \ + --upgrade-package reflex-components-gridjs \ + --upgrade-package reflex-components-lucide \ + --upgrade-package reflex-components-markdown \ + --upgrade-package reflex-components-moment \ + --upgrade-package reflex-components-plotly \ + --upgrade-package reflex-components-radix \ + --upgrade-package reflex-components-react-player \ + --upgrade-package reflex-components-recharts \ + --upgrade-package reflex-components-sonner - - name: Install Requirements for reflex-web and reflex - run: uv pip install '${{ github.event.inputs.reflex_dep || env.REFLEX_DEP }}' + - name: Install custom reflex version (if specified) + if: github.event.inputs.reflex_dep + run: uv pip install '${{ github.event.inputs.reflex_dep }}' - name: Init Website for reflex-web run: reflex init diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 15c663e6b9..ee02538377 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -47,11 +47,28 @@ jobs: python-version: ${{ matrix.python-version }} activate-environment: true - - name: Install the project - run: uv sync --locked --all-extras --dev + - name: Install the project with latest reflex from main + run: | + uv sync --all-extras --dev \ + --upgrade-package reflex \ + --upgrade-package reflex-core \ + --upgrade-package reflex-docgen \ + --upgrade-package reflex-components-code \ + --upgrade-package reflex-components-core \ + --upgrade-package reflex-components-dataeditor \ + --upgrade-package reflex-components-gridjs \ + --upgrade-package reflex-components-lucide \ + --upgrade-package reflex-components-markdown \ + --upgrade-package reflex-components-moment \ + --upgrade-package reflex-components-plotly \ + --upgrade-package reflex-components-radix \ + --upgrade-package reflex-components-react-player \ + --upgrade-package reflex-components-recharts \ + --upgrade-package reflex-components-sonner - - name: Install Requirements for reflex-web and reflex - run: uv pip install '${{ github.event.inputs.reflex_dep || env.REFLEX_DEP }}' + - name: Install custom reflex version (if specified) + if: github.event.inputs.reflex_dep + run: uv pip install '${{ github.event.inputs.reflex_dep }}' - name: Install Playwright run: uv run playwright install --with-deps diff --git a/docs/__init__.py b/docs/__init__.py index e69de29bb2..10ffd1f365 100644 --- a/docs/__init__.py +++ b/docs/__init__.py @@ -0,0 +1 @@ +"""Reflex documentation.""" diff --git a/docs/advanced_onboarding/code_structure.md b/docs/advanced_onboarding/code_structure.md deleted file mode 100644 index 70c53648aa..0000000000 --- a/docs/advanced_onboarding/code_structure.md +++ /dev/null @@ -1,365 +0,0 @@ -```python exec -from pcweb.pages.docs import custom_components -``` - -# Project Structure (Advanced) - -## App Module - -Reflex imports the main app module based on the `app_name` from the config, which **must define a module-level global named `app` as an instance of `rx.App`**. - -The main app module is responsible for importing all other modules that make up the app and defining `app = rx.App()`. - -**All other modules containing pages, state, and models MUST be imported by the main app module or package** for Reflex to include them in the compiled output. - -# Breaking the App into Smaller Pieces - -As applications scale, effective organization is crucial. This is achieved by breaking the application down into smaller, manageable modules and organizing them into logical packages that avoid circular dependencies. - -In the following documentation there will be an app with an `app_name` of `example_big_app`. The main module would be `example_big_app/example_big_app.py`. - -In the [Putting it all together](#putting-it-all-together) section there is a visual of the project folder structure to help follow along with the examples below. - -### Pages Package: `example_big_app/pages` - -All complex apps will have multiple pages, so it is recommended to create `example_big_app/pages` as a package. - -1. This package should contain one module per page in the app. -2. If a particular page depends on the state, the substate should be defined in the same module as the page. -3. The page-returning function should be decorated with `rx.page()` to have it added as a route in the app. - -```python -import reflex as rx - -from ..state import AuthState - - -class LoginState(AuthState): - @rx.event - def handle_submit(self, form_data): - self.logged_in = authenticate(form_data["username"], form_data["password"]) - - -def login_field(name: str, **input_props): - return rx.hstack( - rx.text(name.capitalize()), - rx.input(name=name, **input_props), - width="100%", - justify="between", - ) - - -@rx.page(route="/login") -def login(): - return rx.card( - rx.form( - rx.vstack( - login_field("username"), - login_field("password", type="password"), - rx.button("Login"), - width="100%", - justify="center", - ), - on_submit=LoginState.handle_submit, - ), - ) -``` - -### Templating: `example_big_app/template.py` - -Most applications maintain a consistent layout and structure across pages. Defining this common structure in a separate module facilitates easy sharing and reuse when constructing individual pages. - -**Best Practices** - -1. Factor out common frontend UI elements into a function that returns a component. -2. If a function accepts a function that returns a component, it can be used as a decorator as seen below. - -```python -from typing import Callable - -import reflex as rx - -from .components.menu import menu -from .components.navbar import navbar - - -def template(page: Callable[[], rx.Component]) -> rx.Component: - return rx.vstack( - navbar(), - rx.hstack( - menu(), - rx.container(page()), - ), - width="100%", - ) -``` - -The `@template` decorator should appear below the `@rx.page` decorator and above the page-returning function. See the [Posts Page](#a-post-page-example_big_apppagespostspy) code for an example. - -## State Management - -Most pages will use State in some capacity. You should avoid adding vars to a -shared state that will only be used in a single page. Instead, define a new -subclass of `rx.State` and keep it in the same module as the page. - -### Accessing other States - -As of Reflex 0.4.3, any event handler can get access to an instance of any other -substate via the `get_state` API. From a practical perspective, this means that -state can be split up into smaller pieces without requiring a complex -inheritance hierarchy to share access to other states. - -In previous releases, if an app wanted to store settings in `SettingsState` with -a page or component for modifying them, any other state with an event handler -that needed to access those settings would have to inherit from `SettingsState`, -even if the other state was mostly orthogonal. The other state would also now -always have to load the settings, even for event handlers that didn't need to -access them. - -A better strategy is to load the desired state on demand from only the event -handler which needs access to the substate. - -### A Settings Component: `example_big_app/components/settings.py` - -```python -import reflex as rx - - -class SettingsState(rx.State): - refresh_interval: int = 15 - auto_update: bool = True - prefer_plain_text: bool = True - posts_per_page: int = 20 - - -def settings_dialog(): - return rx.dialog(...) -``` - -### A Post Page: `example_big_app/pages/posts.py` - -This page loads the `SettingsState` to determine how many posts to display per page -and how often to refresh. - -```python -import reflex as rx - -from ..models import Post -from ..template import template -from ..components.settings import SettingsState - - -class PostsState(rx.State): - refresh_tick: int - page: int - posts: list[Post] - - @rx.event - async def on_load(self): - settings = await self.get_state(SettingsState) - if settings.auto_update: - self.refresh_tick = settings.refresh_interval * 1000 - else: - self.refresh_tick = 0 - - @rx.event - async def tick(self, _): - settings = await self.get_state(SettingsState) - with rx.session() as session: - q = Post.select().offset(self.page * settings.posts_per_page).limit(settings.posts_per_page) - self.posts = q.all() - - @rx.event - def go_to_previous(self): - if self.page > 0: - self.page = self.page - 1 - - @rx.event - def go_to_next(self): - if self.posts: - self.page = self.page + 1 - - -@rx.page(route="/posts", on_load=PostsState.on_load) -@template -def posts(): - return rx.vstack( - rx.foreach(PostsState.posts, post_view), - rx.hstack( - rx.button("< Prev", on_click=PostsState.go_to_previous), - rx.button("Next >", on_click=PostsState.go_to_next), - justify="between", - ), - rx.moment(interval=PostsState.refresh_tick, on_change=PostsState.tick, display="none"), - width="100%", - ) -``` - -### Common State: `example_big_app/state.py` - -_Common_ states and substates that are shared by multiple pages or components -should be implemented in a separate module to avoid circular imports. This -module should not import other modules in the app. - -## Component Reusability - -The primary mechanism for reusing components in Reflex is to define a function that returns -the component, then simply call it where that functionality is needed. - -Component functions typically should not take any State classes as arguments, but prefer -to import the needed state and access the vars on the class directly. - -### Memoize Functions for Improved Performance - -In a large app, if a component has many subcomponents or is used in a large number of places, it can improve compile and runtime performance to memoize the function with the `@lru_cache` decorator. - -To memoize the `foo` component to avoid re-creating it many times simply add `@lru_cache` to the function definition, and the component will only be created once per unique set of arguments. - -```python -from functools import lru_cache - -import reflex as rx - -class State(rx.State): - v: str = "foo" - - -@lru_cache -def foo(): - return rx.text(State.v) - - -def index(): - return rx.flex( - rx.button("Change", on_click=State.set_v(rx.cond(State.v != "bar", "bar", "foo"))), - *[ - foo() - for _ in range(100) - ], - direction="row", - wrap="wrap", - ) -``` - -### example_big_app/components - -This package contains reusable parts of the app, for example headers, footers, -and menus. If a particular component requires state, the substate may be defined -in the same module for locality. Any substate defined in a component module -should only contain fields and event handlers pertaining to that individual -component. - -### External Components - -Reflex 0.4.3 introduced support for the [`reflex component` CLI commands]({custom_components.overview.path}), which makes it easy -to bundle up common functionality to publish on PyPI as a standalone Python package -that can be installed and used in any Reflex app. - -When wrapping npm components or other self-contained bits of functionality, it can be helpful -to move this complexity outside the app itself for easier maintenance and reuse in other apps. - -## Database Models: `example_big_app/models.py` - -It is recommended to implement all database models in a single file to make it easier to define relationships and understand the entire schema. - -However, if the schema is very large, it might make sense to have a `models` package with individual models defined in their own modules. - -At any rate, defining the models separately allows any page or component to import and use them without circular imports. - -## Top-level Package: `example_big_app/__init__.py` - -This is a great place to import all state, models, and pages that should be part of the app. -Typically, components and helpers do not need to imported, because they will be imported by -pages that use them (or they would be unused). - -```python -from . import state, models -from .pages import index, login, post, product, profile, schedule - -__all__ = [ - "state", - "models", - "index", - "login", - "post", - "product", - "profile", - "schedule", -] -``` - -If any pages are not imported here, they will not be compiled as part of the app. - -## example_big_app/example_big_app.py - -This is the main app module. Since everything else is defined in other modules, this file becomes very simple. - -```python -import reflex as rx - -app = rx.App() -``` - -## File Management - -There are two categories of non-code assets (media, fonts, stylesheets, -documents) typically used in a Reflex app. - -### assets - -The `assets` directory is used for **static** files that should be accessible -relative to the root of the frontend (default port 3000). When an app is deployed in -production mode, changes to the assets directory will NOT be available at runtime! - -When referencing an asset, always use a leading forward slash, so the -asset can be resolved regardless of the page route where it may appear. - -### uploaded_files - -If an app needs to make files available dynamically at runtime, it is -recommended to set the target directory via `REFLEX_UPLOADED_FILES_DIR` -environment variable (default `./uploaded_files`), write files relative to the -path returned by `rx.get_upload_dir()`, and create working links via -`rx.get_upload_url(relative_path)`. - -Uploaded files are served from the backend (default port 8000) via -`/_upload/` - -## Putting it all together - -Based on the previous discussion, the recommended project layout look like this. - -```text -example-big-app/ -├─ assets/ -├─ example_big_app/ -│ ├─ components/ -│ │ ├─ __init__.py -│ │ ├─ auth.py -│ │ ├─ footer.py -│ │ ├─ menu.py -│ │ ├─ navbar.py -│ ├─ pages/ -│ │ ├─ __init__.py -│ │ ├─ index.py -│ │ ├─ login.py -│ │ ├─ posts.py -│ │ ├─ product.py -│ │ ├─ profile.py -│ │ ├─ schedule.py -│ ├─ __init__.py -│ ├─ example_big_app.py -│ ├─ models.py -│ ├─ state.py -│ ├─ template.py -├─ uploaded_files/ -├─ requirements.txt -├─ rxconfig.py -``` - -## Key Takeaways - -- Like any other Python project, **split up the app into modules and packages** to keep the codebase organized and manageable. -- Using smaller modules and packages makes it easier to **reuse components and state** across the app - without introducing circular dependencies. -- Create **individual functions** to encapsulate units of functionality and **reuse them** where needed. diff --git a/docs/advanced_onboarding/configuration.md b/docs/advanced_onboarding/configuration.md deleted file mode 100644 index bce5179c72..0000000000 --- a/docs/advanced_onboarding/configuration.md +++ /dev/null @@ -1,60 +0,0 @@ -```python exec -from pcweb.pages.docs import api_reference -``` - -# Configuration - -Reflex apps can be configured using a configuration file, environment variables, and command line arguments. - -## Configuration File - -Running `reflex init` will create an `rxconfig.py` file in your root directory. -You can pass keyword arguments to the `Config` class to configure your app. - -For example: - -```python -# rxconfig.py -import reflex as rx - -config = rx.Config( - app_name="my_app_name", - # Connect to your own database. - db_url="postgresql://user:password@localhost:5432/my_db", - # Change the frontend port. - frontend_port=3001, -) -``` - -See the [config reference]({api_reference.config.path}) for all the parameters available. - -## Environment Variables - -You can override the configuration file by setting environment variables. -For example, to override the `frontend_port` setting, you can set the `FRONTEND_PORT` environment variable. - -```bash -FRONTEND_PORT=3001 reflex run -``` - -## Command Line Arguments - -Finally, you can override the configuration file and environment variables by passing command line arguments to `reflex run`. - -```bash -reflex run --frontend-port 3001 -``` - -See the [CLI reference]({api_reference.cli.path}) for all the arguments available. - -## Customizable App Data Directory - -The `REFLEX_DIR` environment variable can be set, which allows users to set the location where Reflex writes helper tools like Bun and NodeJS. - -By default we use Platform specific directories: - -On windows, `C:/Users//AppData/Local/reflex` is used. - -On macOS, `~/Library/Application Support/reflex` is used. - -On linux, `~/.local/share/reflex` is used. diff --git a/docs/advanced_onboarding/how-reflex-works.md b/docs/advanced_onboarding/how-reflex-works.md deleted file mode 100644 index 47b42afab5..0000000000 --- a/docs/advanced_onboarding/how-reflex-works.md +++ /dev/null @@ -1,243 +0,0 @@ -```python exec -from pcweb import constants -from pcweb.constants import REFLEX_ASSETS_CDN -from pcweb.pages.docs import wrapping_react, custom_components, styling, events -from pcweb.pages.docs.custom_components import custom_components as cc -``` - -# How Reflex Works - -We'll use the following basic app that displays Github profile images as an example to explain the different parts of the architecture. - -```python demo exec -import requests -import reflex as rx - -class GithubState(rx.State): - url: str = "https://github.com/reflex-dev" - profile_image: str = "https://avatars.githubusercontent.com/u/104714959" - - @rx.event - def set_profile(self, username: str): - if username == "": - return - try: - github_data = requests.get(f"https://api.github.com/users/{username}").json() - except: - return - self.url = github_data["url"] - self.profile_image = github_data["avatar_url"] - -def index(): - return rx.hstack( - rx.link( - rx.avatar(src=GithubState.profile_image), - href=GithubState.url, - ), - rx.input( - placeholder="Your Github username", - on_blur=GithubState.set_profile, - ), - ) -``` - -## The Reflex Architecture - -Full-stack web apps are made up of a frontend and a backend. The frontend is the user interface, and is served as a web page that runs on the user's browser. The backend handles the logic and state management (such as databases and APIs), and is run on a server. - -In traditional web development, these are usually two separate apps, and are often written in different frameworks or languages. For example, you may combine a Flask backend with a React frontend. With this approach, you have to maintain two separate apps and end up writing a lot of boilerplate code to connect the frontend and backend. - -We wanted to simplify this process in Reflex by defining both the frontend and backend in a single codebase, while using Python for everything. Developers should only worry about their app's logic and not about the low-level implementation details. - -### TLDR - -Under the hood, Reflex apps compile down to a [React](https://react.dev) frontend app and a [FastAPI](https://github.com/tiangolo/fastapi) backend app. Only the UI is compiled to Javascript; all the app logic and state management stays in Python and is run on the server. Reflex uses [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) to send events from the frontend to the backend, and to send state updates from the backend to the frontend. - -The diagram below provides a detailed overview of how a Reflex app works. We'll go through each part in more detail in the following sections. - -```python exec -from reflex_image_zoom import image_zoom -``` - -```python eval -image_zoom(rx.image(src=f"{REFLEX_ASSETS_CDN}other/architecture.webp")) -``` - -```python eval -rx.box(height="1em") -``` - -## Frontend - -We wanted Reflex apps to look and feel like a traditional web app to the end user, while still being easy to build and maintain for the developer. To do this, we built on top of mature and popular web technologies. - -When you `reflex run` your app, Reflex compiles the frontend down to a single-page [Next.js](https://nextjs.org) app and serves it on a port (by default `3000`) that you can access in your browser. - -The frontend's job is to reflect the app's state, and send events to the backend when the user interacts with the UI. No actual logic is run on the frontend. - -### Components - -Reflex frontends are built using components that can be composed together to create complex UIs. Instead of using a templating language that mixes HTML and Python, we just use Python functions to define the UI. - -```python -def index(): - return rx.hstack( - rx.link( - rx.avatar(src=GithubState.profile_image), - href=GithubState.url, - ), - rx.input( - placeholder="Your Github username", - on_blur=GithubState.set_profile, - ), - ) -``` - -In our example app, we have components such as `rx.hstack`, `rx.avatar`, and `rx.input`. These components can have different **props** that affect their appearance and functionality - for example the `rx.input` component has a `placeholder` prop to display the default text. - -We can make our components respond to user interactions with events such as `on_blur`, which we will discuss more below. - -Under the hood, these components compile down to React components. For example, the above code compiles down to the following React code: - -```jsx - - - - - - -``` - -Many of our core components are based on [Radix](https://radix-ui.com/), a popular React component library. We also have many other components for graphing, datatables, and more. - -We chose React because it is a popular library with a huge ecosystem. Our goal isn't to recreate the web ecosystem, but to make it accessible to Python developers. - -This also lets our users bring their own components if we don't have a component they need. Users can [wrap their own React components]({wrapping_react.overview.path}) and then [publish them]({custom_components.overview.path}) for others to use. Over time we will build out our [third party component ecosystem]({cc.path}) so that users can easily find and use components that others have built. - -### Styling - -We wanted to make sure Reflex apps look good out of the box, while still giving developers full control over the appearance of their app. - -We have a core [theming system]({styling.theming.path}) that lets you set high level styling options such as dark mode and accent color throughout your app to give it a unified look and feel. - -Beyond this, Reflex components can be styled using the full power of CSS. We leverage the [Emotion](https://emotion.sh/docs/introduction) library to allow "CSS-in-Python" styling, so you can pass any CSS prop as a keyword argument to a component. This includes [responsive props]({styling.responsive.path}) by passing a list of values. - -## Backend - -Now let's look at how we added interactivity to our apps. - -In Reflex only the frontend compiles to Javascript and runs on the user's browser, while all the state and logic stays in Python and is run on the server. When you `reflex run`, we start a FastAPI server (by default on port `8000`) that the frontend connects to through a websocket. - -All the state and logic are defined within a `State` class. - -```python -class GithubState(rx.State): - url: str = "https://github.com/reflex-dev" - profile_image: str = "https://avatars.githubusercontent.com/u/104714959" - - def set_profile(self, username: str): - if username == "": - return - github_data = requests.get(f"https://api.github.com/users/\{username}").json() - self.url = github_data["url"] - self.profile_image = github_data["avatar_url"] -``` - -The state is made up of **vars** and **event handlers**. - -Vars are any values in your app that can change over time. They are defined as class attributes on your `State` class, and may be any Python type that can be serialized to JSON. In our example, `url` and `profile_image` are vars. - -Event handlers are methods in your `State` class that are called when the user interacts with the UI. They are the only way that we can modify the vars in Reflex, and can be called in response to user actions, such as clicking a button or typing in a text box. In our example, `set_profile` is an event handler that updates the `url` and `profile_image` vars. - -Since event handlers are run on the backend, you can use any Python library within them. In our example, we use the `requests` library to make an API call to Github to get the user's profile image. - -## Event Processing - -Now we get into the interesting part - how we handle events and state updates. - -Normally when writing web apps, you have to write a lot of boilerplate code to connect the frontend and backend. With Reflex, you don't have to worry about that - we handle the communication between the frontend and backend for you. Developers just have to write their event handler logic, and when the vars are updated the UI is automatically updated. - -You can refer to the diagram above for a visual representation of the process. Let's walk through it with our Github profile image example. - -### Event Triggers - -The user can interact with the UI in many ways, such as clicking a button, typing in a text box, or hovering over an element. In Reflex, we call these **event triggers**. - -```python -rx.input( - placeholder="Your Github username", - on_blur=GithubState.set_profile, -) -``` - -In our example we bind the `on_blur` event trigger to the `set_profile` event handler. This means that when the user types in the input field and then clicks away, the `set_profile` event handler is called. - -### Event Queue - -On the frontend, we maintain an event queue of all pending events. An event consists of three major pieces of data: - -- **client token**: Each client (browser tab) has a unique token to identify it. This let's the backend know which state to update. -- **event handler**: The event handler to run on the state. -- **arguments**: The arguments to pass to the event handler. - -Let's assume I type my username "picklelo" into the input. In this example, our event would look something like this: - -```json -{ - "client_token": "abc123", - "event_handler": "GithubState.set_profile", - "arguments": ["picklelo"] -} -``` - -On the frontend, we maintain an event queue of all pending events. - -When an event is triggered, it is added to the queue. We have a `processing` flag to make sure only one event is processed at a time. This ensures that the state is always consistent and there aren't any race conditions with two event handlers modifying the state at the same time. - -```md alert info -# There are exceptions to this, such as [background events]({events.background_events.path}) which allow you to run events in the background without blocking the UI. -``` - -Once the event is ready to be processed, it is sent to the backend through a WebSocket connection. - -### State Manager - -Once the event is received, it is processed on the backend. - -Reflex uses a **state manager** which maintains a mapping between client tokens and their state. By default, the state manager is just an in-memory dictionary, but it can be extended to use a database or cache. In production we use Redis as our state manager. - -### Event Handling - -Once we have the user's state, the next step is to run the event handler with the arguments. - -```python - def set_profile(self, username: str): - if username == "": - return - github_data = requests.get(f"https://api.github.com/users/\{username}").json() - self.url = github_data["url"] - self.profile_image = github_data["avatar_url"] -``` - -In our example, the `set_profile` event handler is run on the user's state. This makes an API call to Github to get the user's profile image, and then updates the state's `url` and `profile_image` vars. - -### State Updates - -Every time an event handler returns (or [yields]({events.yield_events.path})), we save the state in the state manager and send the **state updates** to the frontend to update the UI. - -To maintain performance as your state grows, internally Reflex keeps track of vars that were updated during the event handler (**dirty vars**). When the event handler is done processing, we find all the dirty vars and create a state update to send to the frontend. - -In our case, the state update may look something like this: - -```json -{ - "url": "https://github.com/picklelo", - "profile_image": "https://avatars.githubusercontent.com/u/104714959" -} -``` - -We store the new state in our state manager, and then send the state update to the frontend. The frontend then updates the UI to reflect the new state. In our example, the new Github profile image is displayed. diff --git a/docs/api-reference/browser_javascript.md b/docs/api-reference/browser_javascript.md deleted file mode 100644 index 7321653d10..0000000000 --- a/docs/api-reference/browser_javascript.md +++ /dev/null @@ -1,224 +0,0 @@ -```python exec -import asyncio -from typing import Any -import reflex as rx -from pcweb.pages.docs import wrapping_react -from pcweb.pages.docs import library -``` - -# Browser Javascript - -Reflex compiles your frontend code, defined as python functions, into a Javascript web application -that runs in the user's browser. There are instances where you may need to supply custom javascript -code to interop with Web APIs, use certain third-party libraries, or wrap low-level functionality -that is not exposed via Reflex's Python API. - -```md alert -# Avoid Custom Javascript - -Custom Javascript code in your Reflex app presents a maintenance challenge, as it will be harder to debug and may be unstable across Reflex versions. - -Prefer to use the Python API whenever possible and file an issue if you need additional functionality that is not currently provided. -``` - -## Executing Script - -There are four ways to execute custom Javascript code into your Reflex app: - -- `rx.script` - Injects the script via `next/script` for efficient loading of inline and external Javascript code. Described further in the [component library]({library.other.script.path}). - - These components can be directly included in the body of a page, or they may - be passed to `rx.App(head_components=[rx.script(...)])` to be included in - the `` tag of all pages. -- `rx.call_script` - An event handler that evaluates arbitrary Javascript code, - and optionally returns the result to another event handler. - -These previous two methods can work in tandem to load external scripts and then -call functions defined within them in response to user events. - -The following two methods are geared towards wrapping components and are -described with examples in the [Wrapping React]({wrapping_react.overview.path}) -section. - -- `_get_hooks` and `_get_custom_code` in an `rx.Component` subclass -- `Var.create` with `_var_is_local=False` - -## Inline Scripts - -The `rx.script` component is the recommended way to load inline Javascript for greater control over -frontend behavior. - -The functions and variables in the script can be accessed from backend event -handlers or frontend event triggers via the `rx.call_script` interface. - -```python demo exec -class SoundEffectState(rx.State): - @rx.event(background=True) - async def delayed_play(self): - await asyncio.sleep(1) - return rx.call_script("playFromStart(button_sfx)") - - -def sound_effect_demo(): - return rx.hstack( - rx.script(""" - var button_sfx = new Audio("/vintage-button-sound-effect.mp3") - function playFromStart (sfx) {sfx.load(); sfx.play()}"""), - rx.button("Play Immediately", on_click=rx.call_script("playFromStart(button_sfx)")), - rx.button("Play Later", on_click=SoundEffectState.delayed_play), - ) -``` - -## External Scripts - -External scripts can be loaded either from the `assets` directory, or from CDN URL, and then controlled -via `rx.call_script`. - -```python demo -rx.vstack( - rx.script( - src="https://cdn.jsdelivr.net/gh/scottschiller/snowstorm@snowstorm_20131208/snowstorm-min.js", - ), - rx.script(""" - window.addEventListener('load', function() { - if (typeof snowStorm !== 'undefined') { - snowStorm.autoStart = false; - snowStorm.snowColor = '#111'; - } - }); - """), - rx.button("Start Duststorm", on_click=rx.call_script("snowStorm.start()")), - rx.button("Toggle Duststorm", on_click=rx.call_script("snowStorm.toggleSnow()")), -) -``` - -## Accessing Client Side Values - -The `rx.call_script` function accepts a `callback` parameter that expects an -Event Handler with one argument which will receive the result of evaluating the -Javascript code. This can be used to access client-side values such as the -`window.location` or current scroll location, or any previously defined value. - -```python demo exec -class WindowState(rx.State): - location: dict[str, str] = {} - scroll_position: dict[str, int] = {} - - def update_location(self, location): - self.location = location - - def update_scroll_position(self, scroll_position): - self.scroll_position = { - "x": scroll_position[0], - "y": scroll_position[1], - } - - @rx.event - def get_client_values(self): - return [ - rx.call_script( - "window.location", - callback=WindowState.update_location - ), - rx.call_script( - "[window.scrollX, window.scrollY]", - callback=WindowState.update_scroll_position, - ), - ] - - -def window_state_demo(): - return rx.vstack( - rx.button("Update Values", on_click=WindowState.get_client_values), - rx.text(f"Scroll Position: {WindowState.scroll_position.to_string()}"), - rx.text("window.location:"), - rx.text_area(value=WindowState.location.to_string(), is_read_only=True), - on_mount=WindowState.get_client_values, - ) -``` - -```md alert -# Allowed Callback Values - -The `callback` parameter may be an `EventHandler` with one argument, or a lambda with one argument that returns an `EventHandler`. -If the callback is None, then no event is triggered. -``` - -## Using React Hooks - -To use React Hooks directly in a Reflex app, you must subclass `rx.Component`, -typically `rx.Fragment` is used when the hook functionality has no visual -element. The hook code is returned by the `add_hooks` method, which is expected -to return a `list[str]` containing Javascript code which will be inserted into the -page component (i.e the render function itself). - -For supporting code that must be defined outside of the component render -function, use `_get_custom_code`. - -The following example uses `useEffect` to register global hotkeys on the -`document` object, and then triggers an event when a specific key is pressed. - -```python demo exec -import dataclasses - -from reflex.utils import imports - -@dataclasses.dataclass -class KeyEvent: - """Interface of Javascript KeyboardEvent""" - key: str = "" - -def key_event_spec(ev: rx.Var[KeyEvent]) -> tuple[rx.Var[str]]: - # Takes the event object and returns the key pressed to send to the state - return (ev.key,) - -class GlobalHotkeyState(rx.State): - key: str = "" - - @rx.event - def update_key(self, key): - self.key = key - - -class GlobalHotkeyWatcher(rx.Fragment): - """A component that listens for key events globally.""" - - # The event handler that will be called - on_key_down: rx.EventHandler[key_event_spec] - - def add_imports(self) -> imports.ImportDict: - """Add the imports for the component.""" - return { - "react": [imports.ImportVar(tag="useEffect")], - } - - def add_hooks(self) -> list[str | rx.Var]: - """Add the hooks for the component.""" - return [ - """ - useEffect(() => { - const handle_key = %s; - document.addEventListener("keydown", handle_key, false); - return () => { - document.removeEventListener("keydown", handle_key, false); - } - }) - """ - % str(rx.Var.create(self.event_triggers["on_key_down"])) - ] - -def global_key_demo(): - return rx.vstack( - GlobalHotkeyWatcher.create( - keys=["a", "s", "d", "w"], - on_key_down=lambda key: rx.cond( - rx.Var.create(["a", "s", "d", "w"]).contains(key), - GlobalHotkeyState.update_key(key), - rx.console_log(key) - ) - ), - rx.text("Press a, s, d or w to trigger an event"), - rx.heading(f"Last watched key pressed: {GlobalHotkeyState.key}"), - ) -``` - -This snippet can also be imported through pip: [reflex-global-hotkey](https://pypi.org/project/reflex-global-hotkey/). diff --git a/docs/api-reference/browser_storage.md b/docs/api-reference/browser_storage.md deleted file mode 100644 index 525a502ef9..0000000000 --- a/docs/api-reference/browser_storage.md +++ /dev/null @@ -1,333 +0,0 @@ -# Browser Storage - -## rx.Cookie - -Represents a state Var that is stored as a cookie in the browser. Currently only supports string values. - -Parameters - -- `name` : The name of the cookie on the client side. -- `path`: The cookie path. Use `/` to make the cookie accessible on all pages. -- `max_age` : Relative max age of the cookie in seconds from when the client receives it. -- `domain`: Domain for the cookie (e.g., `sub.domain.com` or `.allsubdomains.com`). -- `secure`: If the cookie is only accessible through HTTPS. -- `same_site`: Whether the cookie is sent with third-party requests. Can be one of (`True`, `False`, `None`, `lax`, `strict`). - -```python -class CookieState(rx.State): - c1: str = rx.Cookie() - c2: str = rx.Cookie('c2 default') - - # cookies with custom settings - c3: str = rx.Cookie(max_age=2) # expires after 2 second - c4: str = rx.Cookie(same_site='strict') - c5: str = rx.Cookie(path='/foo/') # only accessible on `/foo/` - c6: str = rx.Cookie(name='c6-custom-name') -``` - -```md alert warning -# **The default value of a Cookie is never set in the browser!** - -The Cookie value is only set when the Var is assigned. If you need to set a -default value, you can assign a value to the cookie in an `on_load` event -handler. -``` - -## Accessing Cookies - -Cookies are accessed like any other Var in the state. If another state needs access -to the value of a cookie, the state should be a substate of the state that defines -the cookie. Alternatively the `get_state` API can be used to access the other state. - -For rendering cookies in the frontend, import the state that defines the cookie and -reference it directly. - -```md alert warning -# **Two separate states should _avoid_ defining `rx.Cookie` with the same name.** - -Although it is technically possible, the cookie options may differ, leading to -unexpected results. - -Additionally, updating the cookie value in one state will not automatically -update the value in the other state without a page refresh or navigation event. -``` - -## rx.remove_cookies - -Remove a cookie from the client's browser. - -Parameters: - -- `key`: The name of cookie to remove. - -```python -rx.button( - 'Remove cookie', on_click=rx.remove_cookie('key') -) -``` - -This event can also be returned from an event handler: - -```python -class CookieState(rx.State): - ... - def logout(self): - return rx.remove_cookie('auth_token') -``` - -## rx.LocalStorage - -Represents a state Var that is stored in localStorage in the browser. Currently only supports string values. - -Parameters - -- `name`: The name of the storage key on the client side. -- `sync`: Boolean indicates if the state should be kept in sync across tabs of the same browser. - -```python -class LocalStorageState(rx.State): - # local storage with default settings - l1: str = rx.LocalStorage() - - # local storage with custom settings - l2: str = rx.LocalStorage("l2 default") - l3: str = rx.LocalStorage(name="l3") - - # local storage that automatically updates in other states across tabs - l4: str = rx.LocalStorage(sync=True) -``` - -### Syncing Vars - -Because LocalStorage applies to the entire browser, all LocalStorage Vars are -automatically shared across tabs. - -The `sync` parameter controls whether an update in one tab should be actively -propagated to other tabs without requiring a navigation or page refresh event. - -## rx.remove_local_storage - -Remove a local storage item from the client's browser. - -Parameters - -- `key`: The key to remove from local storage. - -```python -rx.button( - 'Remove Local Storage', - on_click=rx.remove_local_storage('key'), -) -``` - -This event can also be returned from an event handler: - -```python -class LocalStorageState(rx.State): - ... - def logout(self): - return rx.remove_local_storage('local_storage_state.l1') -``` - -## rx.clear_local_storage() - -Clear all local storage items from the client's browser. This may affect other -apps running in the same domain or libraries within your app that use local -storage. - -```python -rx.button( - 'Clear all Local Storage', - on_click=rx.clear_local_storage(), -) -``` - -## rx.SessionStorage - -Represents a state Var that is stored in sessionStorage in the browser. Similar to localStorage, but the data is cleared when the page session ends (when the browser/tab is closed). Currently only supports string values. - -Parameters - -- `name`: The name of the storage key on the client side. - -```python -class SessionStorageState(rx.State): - # session storage with default settings - s1: str = rx.SessionStorage() - - # session storage with custom settings - s2: str = rx.SessionStorage("s2 default") - s3: str = rx.SessionStorage(name="s3") -``` - -### Session Persistence - -SessionStorage data is cleared when the page session ends. A page session lasts as long as the browser is open and survives page refreshes and restores, but is cleared when the tab or browser is closed. - -Unlike LocalStorage, SessionStorage is isolated to the tab/window in which it was created, so it's not shared with other tabs/windows of the same origin. - -## rx.remove_session_storage - -Remove a session storage item from the client's browser. - -Parameters - -- `key`: The key to remove from session storage. - -```python -rx.button( - 'Remove Session Storage', - on_click=rx.remove_session_storage('key'), -) -``` - -This event can also be returned from an event handler: - -```python -class SessionStorageState(rx.State): - ... - def logout(self): - return rx.remove_session_storage('session_storage_state.s1') -``` - -## rx.clear_session_storage() - -Clear all session storage items from the client's browser. This may affect other -apps running in the same domain or libraries within your app that use session -storage. - -```python -rx.button( - 'Clear all Session Storage', - on_click=rx.clear_session_storage(), -) -``` - -# Serialization Strategies - -If a non-trivial data structure should be stored in a `Cookie`, `LocalStorage`, or `SessionStorage` var it needs to be serialized before and after storing it. It is recommended to use a pydantic class for the data which provides simple serialization helpers and works recursively in complex object structures. - -```python demo exec -import reflex as rx -import pydantic - - -class AppSettings(pydantic.BaseModel): - theme: str = 'light' - sidebar_visible: bool = True - update_frequency: int = 60 - error_messages: list[str] = pydantic.Field(default_factory=list) - - -class ComplexLocalStorageState(rx.State): - data_raw: str = rx.LocalStorage("{}") - data: AppSettings = AppSettings() - settings_open: bool = False - - @rx.event - def save_settings(self): - self.data_raw = self.data.model_dump_json() - self.settings_open = False - - @rx.event - def open_settings(self): - self.data = AppSettings.model_validate_json(self.data_raw) - self.settings_open = True - - @rx.event - def set_field(self, field, value): - setattr(self.data, field, value) - - -def app_settings(): - return rx.form.root( - rx.foreach( - ComplexLocalStorageState.data.error_messages, - rx.text, - ), - rx.form.field( - rx.flex( - rx.form.label( - "Theme", - rx.input( - value=ComplexLocalStorageState.data.theme, - on_change=lambda v: ComplexLocalStorageState.set_field( - "theme", v - ), - ), - ), - rx.form.label( - "Sidebar Visible", - rx.switch( - checked=ComplexLocalStorageState.data.sidebar_visible, - on_change=lambda v: ComplexLocalStorageState.set_field( - "sidebar_visible", - v, - ), - ), - ), - rx.form.label( - "Update Frequency (seconds)", - rx.input( - value=ComplexLocalStorageState.data.update_frequency, - on_change=lambda v: ComplexLocalStorageState.set_field( - "update_frequency", - v, - ), - ), - ), - rx.dialog.close(rx.button("Save", type="submit")), - gap=2, - direction="column", - ) - ), - on_submit=lambda _: ComplexLocalStorageState.save_settings(), - ) - -def app_settings_example(): - return rx.dialog.root( - rx.dialog.trigger( - rx.button("App Settings", on_click=ComplexLocalStorageState.open_settings), - ), - rx.dialog.content( - rx.dialog.title("App Settings"), - app_settings(), - ), - ) -``` - -# Comparison of Storage Types - -Here's a comparison of the different client-side storage options in Reflex: - -| Feature | rx.Cookie | rx.LocalStorage | rx.SessionStorage | -|---------|-----------|----------------|------------------| -| Persistence | Until cookie expires | Until explicitly deleted | Until browser/tab is closed | -| Storage Limit | ~4KB | ~5MB | ~5MB | -| Sent with Requests | Yes | No | No | -| Accessibility | Server & Client | Client Only | Client Only | -| Expiration | Configurable | Never | End of session | -| Scope | Configurable (domain, path) | Origin (domain) | Tab/Window | -| Syncing Across Tabs | No | Yes (with sync=True) | No | -| Use Case | Authentication, Server-side state | User preferences, App state | Temporary session data | - -# When to Use Each Storage Type - -## Use rx.Cookie When: -- You need the data to be accessible on the server side (cookies are sent with HTTP requests) -- You're handling user authentication -- You need fine-grained control over expiration and scope -- You need to limit the data to specific paths in your app - -## Use rx.LocalStorage When: -- You need to store larger amounts of data (up to ~5MB) -- You want the data to persist indefinitely (until explicitly deleted) -- You need to share data between different tabs/windows of your app -- You want to store user preferences that should be remembered across browser sessions - -## Use rx.SessionStorage When: -- You need temporary data that should be cleared when the browser/tab is closed -- You want to isolate data to a specific tab/window -- You're storing sensitive information that shouldn't persist after the session ends -- You're implementing per-session features like form data, shopping carts, or multi-step processes -- You want to persist data for a state after Redis expiration (for server-side state that needs to survive longer than Redis TTL) diff --git a/docs/api-reference/cli.md b/docs/api-reference/cli.md deleted file mode 100644 index 76da441c03..0000000000 --- a/docs/api-reference/cli.md +++ /dev/null @@ -1,128 +0,0 @@ -# CLI - -The `reflex` command line interface (CLI) is a tool for creating and managing Reflex apps. - -To see a list of all available commands, run `reflex --help`. - -```bash -$ reflex --help - -Usage: reflex [OPTIONS] COMMAND [ARGS]... - - Reflex CLI to create, run, and deploy apps. - -Options: - --version Show the version and exit. - --help Show this message and exit. - -Commands: - cloud The Hosting CLI. - component CLI for creating custom components. - db Subcommands for managing the database schema. - deploy Deploy the app to the Reflex hosting service. - export Export the app to a zip file. - init Initialize a new Reflex app in the current directory. - login Authenticate with experimental Reflex hosting service. - logout Log out of access to Reflex hosting service. - rename Rename the app in the current directory. - run Run the app in the current directory. - script Subcommands for running helper scripts. -``` - -## Init - -The `reflex init` command creates a new Reflex app in the current directory. -If an `rxconfig.py` file already exists already, it will re-initialize the app with the latest template. - -```bash -$ reflex init --help -Usage: reflex init [OPTIONS] - - Initialize a new Reflex app in the current directory. - -Options: - --name APP_NAME The name of the app to initialize. - --template [demo|sidebar|blank] - The template to initialize the app with. - --loglevel [debug|info|warning|error|critical] - The log level to use. [default: - LogLevel.INFO] - --help Show this message and exit. -``` - -## Run - -The `reflex run` command runs the app in the current directory. - -By default it runs your app in development mode. -This means that the app will automatically reload when you make changes to the code. -You can also run in production mode which will create an optimized build of your app. - -You can configure the mode, as well as other options through flags. - -```bash -$ reflex run --help -Usage: reflex run [OPTIONS] - - Run the app in the current directory. - -Options: - --env [dev|prod] The environment to run the app in. - [default: Env.DEV] - --frontend-only Execute only frontend. - --backend-only Execute only backend. - --frontend-port TEXT Specify a different frontend port. - [default: 3000] - --backend-port TEXT Specify a different backend port. [default: - 8000] - --backend-host TEXT Specify the backend host. [default: - 0.0.0.0] - --loglevel [debug|info|warning|error|critical] - The log level to use. [default: - LogLevel.INFO] - --help Show this message and exit. -``` - -## Export - -You can export your app's frontend and backend to zip files using the `reflex export` command. - -The frontend is a compiled NextJS app, which can be deployed to a static hosting service like Github Pages or Vercel. -However this is just a static build, so you will need to deploy the backend separately. -See the self-hosting guide for more information. - -## Rename - -The `reflex rename` command allows you to rename your Reflex app. This updates the app name in the configuration files. - -```bash -$ reflex rename --help -Usage: reflex rename [OPTIONS] NEW_NAME - - Rename the app in the current directory. - -Options: - --loglevel [debug|default|info|warning|error|critical] - The log level to use. - --help Show this message and exit. -``` - -## Cloud - -The `reflex cloud` command provides access to the Reflex Cloud hosting service. It includes subcommands for managing apps, projects, secrets, and more. - -For detailed documentation on Reflex Cloud and deployment, see the [Cloud Quick Start Guide](https://reflex.dev/docs/hosting/deploy-quick-start/). - -## Script - -The `reflex script` command provides access to helper scripts for Reflex development. - -```bash -$ reflex script --help -Usage: reflex script [OPTIONS] COMMAND [ARGS]... - - Subcommands for running helper scripts. - -Options: - --help Show this message and exit. -``` diff --git a/docs/api-reference/event_triggers.md b/docs/api-reference/event_triggers.md deleted file mode 100644 index 4a9620d99f..0000000000 --- a/docs/api-reference/event_triggers.md +++ /dev/null @@ -1,390 +0,0 @@ -```python exec -from datetime import datetime - -import reflex as rx - -from pcweb.templates.docpage import docdemo, h1_comp, text_comp, docpage -from pcweb.pages.docs import events - -SYNTHETIC_EVENTS = [ - { - "name": "on_focus", - "description": "The on_focus event handler is called when the element (or some element inside of it) receives focus. For example, it’s called when the user clicks on a text input.", - "state": """class FocusState(rx.State): - text = "Change Me!" - - @rx.event - def change_text(self, text): - if self.text == "Change Me!": - self.text = "Changed!" - else: - self.text = "Change Me!" -""", - "example": """rx.input(value=FocusState.text, on_focus=FocusState.change_text)""", - }, - { - "name": "on_blur", - "description": "The on_blur event handler is called when focus has left the element (or left some element inside of it). For example, it’s called when the user clicks outside of a focused text input.", - "state": """class BlurState(rx.State): - text = "Change Me!" - - @rx.event - def change_text(self, text): - if self.text == "Change Me!": - self.text = "Changed!" - else: - self.text = "Change Me!" -""", - "example": """rx.input(value=BlurState.text, on_blur=BlurState.change_text)""", - }, - { - "name": "on_change", - "description": "The on_change event handler is called when the value of an element has changed. For example, it’s called when the user types into a text input each keystroke triggers the on change.", - "state": """class ChangeState(rx.State): - checked: bool = False - - @rx.event - def set_checked(self): - self.checked = not self.checked - -""", - "example": """rx.switch(on_change=ChangeState.set_checked)""", - }, - { - "name": "on_click", - "description": "The on_click event handler is called when the user clicks on an element. For example, it’s called when the user clicks on a button.", - "state": """class ClickState(rx.State): - text = "Change Me!" - - @rx.event - def change_text(self): - if self.text == "Change Me!": - self.text = "Changed!" - else: - self.text = "Change Me!" -""", - "example": """rx.button(ClickState.text, on_click=ClickState.change_text)""", - }, - { - "name": "on_context_menu", - "description": "The on_context_menu event handler is called when the user right-clicks on an element. For example, it’s called when the user right-clicks on a button.", - "state": """class ContextState(rx.State): - text = "Change Me!" - - @rx.event - def change_text(self): - if self.text == "Change Me!": - self.text = "Changed!" - else: - self.text = "Change Me!" -""", - "example": """rx.button(ContextState.text, on_context_menu=ContextState.change_text)""", - }, - { - "name": "on_double_click", - "description": "The on_double_click event handler is called when the user double-clicks on an element. For example, it’s called when the user double-clicks on a button.", - "state": """class DoubleClickState(rx.State): - text = "Change Me!" - - @rx.event - def change_text(self): - if self.text == "Change Me!": - self.text = "Changed!" - else: - self.text = "Change Me!" -""", - "example": """rx.button(DoubleClickState.text, on_double_click=DoubleClickState.change_text)""", - }, - { - "name": "on_mount", - "description": "The on_mount event handler is called after the component is rendered on the page. It is similar to a page on_load event, although it does not necessarily fire when navigating between pages. This event is particularly useful for initializing data, making API calls, or setting up component-specific state when a component first appears.", - "state": """class MountState(rx.State): - events: list[str] = [] - data: list[dict] = [] - loading: bool = False - - @rx.event - def on_mount(self): - self.events = self.events[-4:] + ["on_mount @ " + str(datetime.now())] - - @rx.event - async def load_data(self): - # Common pattern: Set loading state, yield to update UI, then fetch data - self.loading = True - yield - # Simulate API call - import asyncio - await asyncio.sleep(1) - self.data = [dict(id=1, name="Item 1"), dict(id=2, name="Item 2")] - self.loading = False -""", - "example": """rx.vstack( - rx.heading("Component Lifecycle Demo"), - rx.foreach(MountState.events, rx.text), - rx.cond( - MountState.loading, - rx.spinner(), - rx.foreach( - MountState.data, - lambda item: rx.text(f"ID: {item['id']} - {item['name']}") - ) - ), - on_mount=MountState.on_mount, -)""", - }, - { - "name": "on_unmount", - "description": "The on_unmount event handler is called after removing the component from the page. However, on_unmount will only be called for internal navigation, not when following external links or refreshing the page. This event is useful for cleaning up resources, saving state, or performing cleanup operations before a component is removed from the DOM.", - "state": """class UnmountState(rx.State): - events: list[str] = [] - resource_id: str = "resource-12345" - status: str = "Resource active" - - @rx.event - def on_unmount(self): - self.events = self.events[-4:] + ["on_unmount @ " + str(datetime.now())] - # Common pattern: Clean up resources when component is removed - self.status = f"Resource {self.resource_id} cleaned up" - - @rx.event - def initialize_resource(self): - self.status = f"Resource {self.resource_id} initialized" -""", - "example": """rx.vstack( - rx.heading("Unmount Demo"), - rx.foreach(UnmountState.events, rx.text), - rx.text(UnmountState.status), - rx.link( - rx.button("Navigate Away (Triggers Unmount)"), - href="/docs", - ), - on_mount=UnmountState.initialize_resource, - on_unmount=UnmountState.on_unmount, -)""", - }, - { - "name": "on_mouse_up", - "description": "The on_mouse_up event handler is called when the user releases a mouse button on an element. For example, it’s called when the user releases the left mouse button on a button.", - "state": """class MouseUpState(rx.State): - text = "Change Me!" - - @rx.event - def change_text(self): - if self.text == "Change Me!": - self.text = "Changed!" - else: - self.text = "Change Me!" -""", - "example": """rx.button(MouseUpState.text, on_mouse_up=MouseUpState.change_text)""", - }, - { - "name": "on_mouse_down", - "description": "The on_mouse_down event handler is called when the user presses a mouse button on an element. For example, it’s called when the user presses the left mouse button on a button.", - "state": """class MouseDown(rx.State): - text = "Change Me!" - - @rx.event - def change_text(self): - if self.text == "Change Me!": - self.text = "Changed!" - else: - self.text = "Change Me!" -""", - "example": """rx.button(MouseDown.text, on_mouse_down=MouseDown.change_text)""", - }, - { - "name": "on_mouse_enter", - "description": "The on_mouse_enter event handler is called when the user’s mouse enters an element. For example, it’s called when the user’s mouse enters a button.", - "state": """class MouseEnter(rx.State): - text = "Change Me!" - - @rx.event - def change_text(self): - if self.text == "Change Me!": - self.text = "Changed!" - else: - self.text = "Change Me!" -""", - "example": """rx.button(MouseEnter.text, on_mouse_enter=MouseEnter.change_text)""", - }, - { - "name": "on_mouse_leave", - "description": "The on_mouse_leave event handler is called when the user’s mouse leaves an element. For example, it’s called when the user’s mouse leaves a button.", - "state": """class MouseLeave(rx.State): - text = "Change Me!" - - @rx.event - def change_text(self): - if self.text == "Change Me!": - self.text = "Changed!" - else: - self.text = "Change Me!" -""", - "example": """rx.button(MouseLeave.text, on_mouse_leave=MouseLeave.change_text)""", - }, - { - "name": "on_mouse_move", - "description": "The on_mouse_move event handler is called when the user moves the mouse over an element. For example, it’s called when the user moves the mouse over a button.", - "state": """class MouseMove(rx.State): - text = "Change Me!" - - @rx.event - def change_text(self): - if self.text == "Change Me!": - self.text = "Changed!" - else: - self.text = "Change Me!" -""", - "example": """rx.button(MouseMove.text, on_mouse_move=MouseMove.change_text)""", - }, - { - "name": "on_mouse_out", - "description": "The on_mouse_out event handler is called when the user’s mouse leaves an element. For example, it’s called when the user’s mouse leaves a button.", - "state": """class MouseOut(rx.State): - text = "Change Me!" - - @rx.event - def change_text(self): - if self.text == "Change Me!": - self.text = "Changed!" - else: - self.text = "Change Me!" -""", - "example": """rx.button(MouseOut.text, on_mouse_out=MouseOut.change_text)""", - }, - { - "name": "on_mouse_over", - "description": "The on_mouse_over event handler is called when the user’s mouse enters an element. For example, it’s called when the user’s mouse enters a button.", - "state": """class MouseOver(rx.State): - text = "Change Me!" - - @rx.event - def change_text(self): - if self.text == "Change Me!": - self.text = "Changed!" - else: - self.text = "Change Me!" -""", - "example": """rx.button(MouseOver.text, on_mouse_over=MouseOver.change_text)""", - }, - { - "name": "on_scroll", - "description": "The on_scroll event handler is called when the user scrolls the page. For example, it’s called when the user scrolls the page down.", - "state": """class ScrollState(rx.State): - text = "Change Me!" - - @rx.event - def change_text(self): - if self.text == "Change Me!": - self.text = "Changed!" - else: - self.text = "Change Me!" -""", - "example": """rx.vstack( - rx.text("Scroll to make the text below change."), - rx.text(ScrollState.text), - rx.text("Scroll to make the text above change."), - on_scroll=ScrollState.change_text, - overflow = "auto", - height = "3em", - width = "100%", - )""", - }, -] -for i in SYNTHETIC_EVENTS: - exec(i["state"]) - -def component_grid(): - events = [] - for event in SYNTHETIC_EVENTS: - events.append( - rx.vstack( - h1_comp(text=event["name"]), - text_comp(text=event["description"]), - docdemo( - event["example"], state=event["state"], comp=eval(event["example"]) - ), - align_items="left", - ) - ) - - return rx.box(*events) -``` - -# Event Triggers - -Components can modify the state based on user events such as clicking a button or entering text in a field. -These events are triggered by event triggers. - -Event triggers are component specific and are listed in the documentation for each component. - -## Component Lifecycle Events - -Reflex components have lifecycle events like `on_mount` and `on_unmount` that allow you to execute code at specific points in a component's existence. These events are crucial for initializing data, cleaning up resources, and creating dynamic user interfaces. - -### When Lifecycle Events Are Activated - -- **on_mount**: This event is triggered immediately after a component is rendered and attached to the DOM. It fires: - - When a page containing the component is first loaded - - When a component is conditionally rendered (appears after being hidden) - - When navigating to a page containing the component using internal navigation - - It does NOT fire when the page is refreshed or when following external links - -- **on_unmount**: This event is triggered just before a component is removed from the DOM. It fires: - - When navigating away from a page containing the component using internal navigation - - When a component is conditionally removed from the DOM (e.g., via a condition that hides it) - - It does NOT fire when refreshing the page, closing the browser tab, or following external links - -## Page Load Events - -In addition to component lifecycle events, Reflex also provides page-level events like `on_load` that are triggered when a page loads. The `on_load` event is useful for: - -- Fetching data when a page first loads -- Checking authentication status -- Initializing page-specific state -- Setting default values for cookies or browser storage - -You can specify an event handler to run when the page loads using the `on_load` parameter in the `@rx.page` decorator or `app.add_page()` method: - -```python -class State(rx.State): - data: dict = dict() - - @rx.event - def get_data(self): - # Fetch data when the page loads - self.data = fetch_data() - -@rx.page(on_load=State.get_data) -def index(): - return rx.text('Data loaded on page load') -``` - -This is particularly useful for authentication checks: - -```python -class State(rx.State): - authenticated: bool = False - - @rx.event - def check_auth(self): - # Check if user is authenticated - self.authenticated = check_auth() - if not self.authenticated: - return rx.redirect('/login') - -@rx.page(on_load=State.check_auth) -def protected_page(): - return rx.text('Protected content') -``` - -For more details on page load events, see the [page load events documentation]({events.page_load_events.path}). - -# Event Reference - -```python eval -rx.box( - rx.divider(), - component_grid(), -) -``` diff --git a/docs/api-reference/plugins.md b/docs/api-reference/plugins.md deleted file mode 100644 index 7170cd114b..0000000000 --- a/docs/api-reference/plugins.md +++ /dev/null @@ -1,242 +0,0 @@ -```python exec -import reflex as rx -from pcweb.pages.docs import advanced_onboarding -``` - -# Plugins - -Reflex supports a plugin system that allows you to extend the framework's functionality during the compilation process. Plugins can add frontend dependencies, modify build configurations, generate static assets, and perform custom tasks before compilation. - -## Configuring Plugins - -Plugins are configured in your `rxconfig.py` file using the `plugins` parameter: - -```python -import reflex as rx - -config = rx.Config( - app_name="my_app", - plugins=[ - rx.plugins.SitemapPlugin(), - rx.plugins.TailwindV4Plugin(), - ], -) -``` - -## Built-in Plugins - -Reflex comes with several built-in plugins that provide common functionality. - -### SitemapPlugin - -The `SitemapPlugin` automatically generates a sitemap.xml file for your application, which helps search engines discover and index your pages. - -```python -import reflex as rx - -config = rx.Config( - app_name="my_app", - plugins=[ - rx.plugins.SitemapPlugin(), - ], -) -``` - -The sitemap plugin automatically includes all your app's routes. For dynamic routes or custom configuration, you can add sitemap metadata to individual pages: - -```python -@rx.page(route="/blog/[slug]", context={"sitemap": {"changefreq": "weekly", "priority": 0.8}}) -def blog_post(): - return rx.text("Blog post content") - -@rx.page(route="/about", context={"sitemap": {"changefreq": "monthly", "priority": 0.5}}) -def about(): - return rx.text("About page") -``` - -The sitemap configuration supports the following options: -- `loc`: Custom URL for the page (required for dynamic routes) -- `lastmod`: Last modification date (datetime object) -- `changefreq`: How frequently the page changes (`"always"`, `"hourly"`, `"daily"`, `"weekly"`, `"monthly"`, `"yearly"`, `"never"`) -- `priority`: Priority of this URL relative to other URLs (0.0 to 1.0) - -### TailwindV4Plugin - -The `TailwindV4Plugin` provides support for Tailwind CSS v4, which is the recommended version for new projects and includes performance improvements and new features. - -```python -import reflex as rx - -# Basic configuration -config = rx.Config( - app_name="my_app", - plugins=[ - rx.plugins.TailwindV4Plugin(), - ], -) -``` - -You can customize the Tailwind configuration by passing a config dictionary: - -```python -import reflex as rx - -tailwind_config = { - "theme": { - "extend": { - "colors": { - "brand": { - "50": "#eff6ff", - "500": "#3b82f6", - "900": "#1e3a8a", - } - } - } - }, - "plugins": ["@tailwindcss/typography"], -} - -config = rx.Config( - app_name="my_app", - plugins=[ - rx.plugins.TailwindV4Plugin(tailwind_config), - ], -) -``` - -### TailwindV3Plugin - -The `TailwindV3Plugin` integrates Tailwind CSS v3 into your Reflex application. While still supported, TailwindV4Plugin is recommended for new projects. - -```python -import reflex as rx - -# Basic configuration -config = rx.Config( - app_name="my_app", - plugins=[ - rx.plugins.TailwindV3Plugin(), - ], -) -``` - -You can customize the Tailwind configuration by passing a config dictionary: - -```python -import reflex as rx - -tailwind_config = { - "theme": { - "extend": { - "colors": { - "primary": "#3b82f6", - "secondary": "#64748b", - } - } - }, - "plugins": ["@tailwindcss/typography", "@tailwindcss/forms"], -} - -config = rx.Config( - app_name="my_app", - plugins=[ - rx.plugins.TailwindV3Plugin(tailwind_config), - ], -) -``` - -## Plugin Management - -### Default Plugins - -Some plugins are enabled by default. Currently, the `SitemapPlugin` is enabled automatically. If you want to disable a default plugin, use the `disable_plugins` parameter: - -```python -import reflex as rx - -config = rx.Config( - app_name="my_app", - disable_plugins=["reflex.plugins.sitemap.SitemapPlugin"], -) -``` - -### Plugin Order - -Plugins are executed in the order they appear in the `plugins` list. This can be important if plugins have dependencies on each other or modify the same files. - -```python -import reflex as rx - -config = rx.Config( - app_name="my_app", - plugins=[ - rx.plugins.TailwindV4Plugin(), # Runs first - rx.plugins.SitemapPlugin(), # Runs second - ], -) -``` - - -## Plugin Architecture - -All plugins inherit from the base `Plugin` class and can implement several lifecycle methods: - -```python -class Plugin: - def get_frontend_development_dependencies(self, **context) -> list[str]: - """Get NPM packages required by the plugin for development.""" - return [] - - def get_frontend_dependencies(self, **context) -> list[str]: - """Get NPM packages required by the plugin.""" - return [] - - def get_static_assets(self, **context) -> Sequence[tuple[Path, str | bytes]]: - """Get static assets required by the plugin.""" - return [] - - def get_stylesheet_paths(self, **context) -> Sequence[str]: - """Get paths to stylesheets required by the plugin.""" - return [] - - def pre_compile(self, **context) -> None: - """Called before compilation to perform custom tasks.""" - pass -``` - -### Creating Custom Plugins - -You can create custom plugins by inheriting from the base `Plugin` class: - -```python -from reflex.plugins.base import Plugin -from pathlib import Path - -class CustomPlugin(Plugin): - def get_frontend_dependencies(self, **context): - return ["my-custom-package@1.0.0"] - - def pre_compile(self, **context): - # Custom logic before compilation - print("Running custom plugin logic...") - - # Add a custom task - context["add_save_task"](self.create_custom_file) - - def create_custom_file(self): - return "public/custom.txt", "Custom content" -``` - -Then use it in your configuration: - -```python -import reflex as rx -from my_plugins import CustomPlugin - -config = rx.Config( - app_name="my_app", - plugins=[ - CustomPlugin(), - ], -) -``` diff --git a/docs/api-reference/special_events.md b/docs/api-reference/special_events.md deleted file mode 100644 index 675f465ca7..0000000000 --- a/docs/api-reference/special_events.md +++ /dev/null @@ -1,123 +0,0 @@ -```python exec -import reflex as rx -``` - -# Special Events - -Reflex includes a set of built-in special events that can be utilized as event triggers -or returned from event handlers in your applications. These events enhance interactivity and user experience. -Below are the special events available in Reflex, along with explanations of their functionality: - -## rx.console_log - -Perform a console.log in the browser's console. - -```python demo -rx.button('Log', on_click=rx.console_log('Hello World!')) -``` - -When triggered, this event logs a specified message to the browser's developer console. -It's useful for debugging and monitoring the behavior of your application. - -## rx.scroll_to - -scroll to an element in the page - -```python demo -rx.button( - "Scroll to download button", - on_click=rx.scroll_to("download button") - -) -``` - -When this is triggered, it scrolls to an element passed by id as parameter. Click on button to scroll to download button (rx.download section) at the bottom of the page - -## rx.redirect - -Redirect the user to a new path within the application. - -### Parameters - -- `path`: The destination path or URL to which the user should be redirected. -- `external`: If set to True, the redirection will open in a new tab. Defaults to `False`. - -```python demo -rx.vstack( - rx.button("open in tab", on_click=rx.redirect("/docs/api-reference/special-events")), - rx.button("open in new tab", on_click=rx.redirect('https://github.com/reflex-dev/reflex/', is_external=True)) -) -``` - -When this event is triggered, it navigates the user to a different page or location within your Reflex application. -By default, the redirection occurs in the same tab. However, if you set the external parameter to True, the redirection -will open in a new tab or window, providing a seamless user experience. - -This event can also be run from an event handler in State. It is necessary to `return` the `rx.redirect()`. - -```python demo exec -class RedirectExampleState(rx.State): - """The app state.""" - - @rx.event - def change_page(self): - return rx.redirect('https://github.com/reflex-dev/reflex/', is_external=True) - -def redirect_example(): - return rx.vstack( - rx.button("Change page in State", on_click=RedirectExampleState.change_page), - ) -``` - -## rx.set_clipboard - -Set the specified text content to the clipboard. - -```python demo -rx.button('Copy "Hello World" to clipboard',on_click=rx.set_clipboard('Hello World'),) -``` - -This event allows you to copy a given text or content to the user's clipboard. -It's handy when you want to provide a "Copy to Clipboard" feature in your application, -allowing users to easily copy information to paste elsewhere. - -## rx.set_value - -Set the value of a specified reference element. - -```python demo -rx.hstack( - rx.input(id='input1'), - rx.button( - 'Erase', on_click=rx.set_value('input1', '') - ), -) -``` - -With this event, you can modify the value of a particular HTML element, typically an input field or another form element. - -## rx.window_alert - -Create a window alert in the browser. - -```python demo -rx.button('Alert', on_click=rx.window_alert('Hello World!')) -``` - -## rx.download - -Download a file at a given path. - -Parameters: - -- `url`: The URL of the file to be downloaded. -- `data`: The data to be downloaded. Should be `str` or `bytes`, `data:` URI, `PIL.Image`, or any state Var (to be converted to JSON). -- `filename`: The desired filename of the downloaded file. - -```md alert -# `url` and `data` args are mutually exclusive, and at least one of them must be provided. -``` - -```python demo -rx.button("Download", on_click=rx.download(url="/reflex_banner.webp", filename="different_name_logo.webp"), id="download button") -``` diff --git a/docs/api-reference/utils.md b/docs/api-reference/utils.md deleted file mode 100644 index 48beb020dc..0000000000 --- a/docs/api-reference/utils.md +++ /dev/null @@ -1,170 +0,0 @@ -```python exec -import reflex as rx -from pcweb import constants, styles -``` - -# Utility Functions - -Reflex provides utility functions to help with common tasks in your applications. - -## run_in_thread - -The `run_in_thread` function allows you to run a **non-async** function in a separate thread, which is useful for preventing long-running operations from blocking the UI event queue. - -```python -async def run_in_thread(func: Callable) -> Any -``` - -### Parameters - -- `func`: The non-async function to run in a separate thread. - -### Returns - -- The return value of the function. - -### Raises - -- `ValueError`: If the function is an async function. - -### Usage - -```python demo exec id=run_in_thread_demo -import asyncio -import dataclasses -import time -import reflex as rx - - -def quick_blocking_function(): - time.sleep(0.5) - return "Quick task completed successfully!" - - -def slow_blocking_function(): - time.sleep(3.0) - return "This should never be returned due to timeout!" - - -@dataclasses.dataclass -class TaskInfo: - result: str = "No result yet" - status: str = "Idle" - - -class RunInThreadState(rx.State): - tasks: list[TaskInfo] = [] - - @rx.event(background=True) - async def run_quick_task(self): - """Run a quick task that completes within the timeout.""" - async with self: - task_ix = len(self.tasks) - self.tasks.append(TaskInfo(status="Running quick task...")) - task_info = self.tasks[task_ix] - - try: - result = await rx.run_in_thread(quick_blocking_function) - async with self: - task_info.result = result - task_info.status = "Complete" - except Exception as e: - async with self: - task_info.result = f"Error: {str(e)}" - task_info.status = "Failed" - - @rx.event(background=True) - async def run_slow_task(self): - """Run a slow task that exceeds the timeout.""" - async with self: - task_ix = len(self.tasks) - self.tasks.append(TaskInfo(status="Running slow task...")) - task_info = self.tasks[task_ix] - - try: - # Run with a timeout of 1 second (not enough time) - result = await asyncio.wait_for( - rx.run_in_thread(slow_blocking_function), - timeout=1.0, - ) - async with self: - task_info.result = result - task_info.status = "Complete" - except asyncio.TimeoutError: - async with self: - # Warning: even though we stopped waiting for the task, - # it may still be running in thread - task_info.result = "Task timed out after 1 second!" - task_info.status = "Timeout" - except Exception as e: - async with self: - task_info.result = f"Error: {str(e)}" - task_info.status = "Failed" - - -def run_in_thread_example(): - return rx.vstack( - rx.heading("run_in_thread Example", size="3"), - rx.hstack( - rx.button( - "Run Quick Task", - on_click=RunInThreadState.run_quick_task, - color_scheme="green", - ), - rx.button( - "Run Slow Task (exceeds timeout)", - on_click=RunInThreadState.run_slow_task, - color_scheme="red", - ), - ), - rx.vstack( - rx.foreach( - RunInThreadState.tasks.reverse()[:10], - lambda task: rx.hstack( - rx.text(task.status), - rx.spacer(), - rx.text(task.result), - ), - ), - align="start", - width="100%", - ), - width="100%", - align_items="start", - spacing="4", - ) -``` - -### When to Use run_in_thread - -Use `run_in_thread` when you need to: - -1. Execute CPU-bound operations that would otherwise block the event loop -2. Call synchronous libraries that don't have async equivalents -3. Prevent long-running operations from blocking UI responsiveness - -### Example: Processing a Large File - -```python -import reflex as rx -import time - -class FileProcessingState(rx.State): - progress: str = "Ready" - - @rx.event(background=True) - async def process_large_file(self): - async with self: - self.progress = "Processing file..." - - def process_file(): - # Simulate processing a large file - time.sleep(5) - return "File processed successfully!" - - # Save the result to a local variable to avoid blocking the event loop. - result = await rx.run_in_thread(process_file) - async with self: - # Then assign the local result to the state while holding the lock. - self.progress = result -``` diff --git a/docs/api-reference/var_system.md b/docs/api-reference/var_system.md deleted file mode 100644 index 39482420e0..0000000000 --- a/docs/api-reference/var_system.md +++ /dev/null @@ -1,82 +0,0 @@ -# Reflex's Var System - -## Motivation - -Reflex supports some basic operations in state variables on the frontend. -Reflex automatically converts variable operations from Python into a JavaScript equivalent. - -Here's an example of a Reflex conditional in Python that returns "Pass" if the threshold is equal to or greater than 50 and "Fail" otherwise: - -```py -rx.cond( - State.threshold >= 50, - "Pass", - "Fail", -) -``` - - The conditional to roughly the following in Javascript: - -```js -state.threshold >= 50 ? "Pass" : "Fail"; -``` - -## Overview - -Simply put, a `Var` in Reflex represents a Javascript expression. -If the type is known, it can be any of the following: - -- `NumberVar` represents an expression that evaluates to a Javascript `number`. `NumberVar` can support both integers and floating point values -- `BooleanVar` represents a boolean expression. For example: `false`, `3 > 2`. -- `StringVar` represents an expression that evaluates to a string. For example: `'hello'`, `(2).toString()`. -- `ArrayVar` represents an expression that evaluates to an array object. For example: `[1, 2, 3]`, `'words'.split()`. -- `ObjectVar` represents an expression that evaluates to an object. For example: `\{a: 2, b: 3}`, `\{deeply: \{nested: \{value: false}}}`. -- `NoneVar` represent null values. These can be either `undefined` or `null`. - -## Creating Vars - -State fields are converted to `Var` by default. Additionally, you can create a `Var` from Python values using `rx.Var.create()`: - -```py -rx.Var.create(4) # NumberVar -rx.Var.create("hello") # StringVar -rx.Var.create([1, 2, 3]) # ArrayVar -``` - -If you want to explicitly create a `Var` from a raw Javascript string, you can instantiate `rx.Var` directly: - -```py -rx.Var("2", _var_type=int).guess_type() # NumberVar -``` - -In the example above, `.guess_type()` will attempt to downcast from a generic `Var` type into `NumberVar`. -For this example, calling the function `.to(int)` can also be used in place of `.guess_type()`. - -## Operations - -The `Var` system also supports some other basic operations. -For example, `NumberVar` supports basic arithmetic operations like `+` and `-`, as in Python. -It also supports comparisons that return a `BooleanVar`. - -Custom `Var` operations can also be defined: - -```py -from reflex.vars import var_operation, var_operation_return, ArrayVar, NumberVar - -@var_operation -def multiply_array_values(a: ArrayVar): - return var_operation_return( - js_expression=f"\{a}.reduce((p, c) => p * c, 1)", - var_type=int, - ) - -def factorial(value: NumberVar): - return rx.cond( - value <= 1, - 1, - multiply_array_values(rx.Var.range(1, value+1)) - ) -``` - -Use `js_expression` to pass explicit JavaScript expressions; in the `multiply_array_values` example, we pass in a JavaScript expression that calculates the product of all elements in an array called `a` by using the reduce method to multiply each element with the accumulated result, starting from an initial value of 1. -Later, we leverage `rx.cond` in the' factorial' function, we instantiate an array using the `range` function, and pass this array to `multiply_array_values`. diff --git a/docs/api-routes/overview.md b/docs/api-routes/overview.md deleted file mode 100644 index 30e5ef31f7..0000000000 --- a/docs/api-routes/overview.md +++ /dev/null @@ -1,152 +0,0 @@ -```python exec -import reflex as rx -``` - -# API Transformer - -In addition to your frontend app, Reflex uses a FastAPI backend to serve your app. The API transformer feature allows you to transform or extend the ASGI app that serves your Reflex application. - -## Overview - -The API transformer provides a way to: - -1. Integrate existing FastAPI or Starlette applications with your Reflex app -2. Apply middleware or transformations to the ASGI app -3. Extend your Reflex app with additional API endpoints - -This is useful for creating a backend API that can be used for purposes beyond your Reflex app, or for integrating Reflex with existing backend services. - -## Using API Transformer - -You can set the `api_transformer` parameter when initializing your Reflex app: - -```python -import reflex as rx -from fastapi import FastAPI, Depends -from fastapi.security import OAuth2PasswordBearer - -# Create a FastAPI app -fastapi_app = FastAPI(title="My API") - -# Add routes to the FastAPI app -@fastapi_app.get("/api/items") -async def get_items(): - return dict(items=["Item1", "Item2", "Item3"]) - -# Create a Reflex app with the FastAPI app as the API transformer -app = rx.App(api_transformer=fastapi_app) -``` - -## Types of API Transformers - -The `api_transformer` parameter can accept: - -1. A Starlette or FastAPI instance -2. A callable that takes an ASGIApp and returns an ASGIApp -3. A sequence of the above - -### Using a FastAPI or Starlette Instance - -When you provide a FastAPI or Starlette instance as the API transformer, Reflex will mount its internal API to your app, allowing you to define additional routes: - -```python -import reflex as rx -from fastapi import FastAPI, Depends -from fastapi.security import OAuth2PasswordBearer - -# Create a FastAPI app with authentication -fastapi_app = FastAPI(title="Secure API") -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - -# Add a protected route -@fastapi_app.get("/api/protected") -async def protected_route(token: str = Depends(oauth2_scheme)): - return dict(message="This is a protected endpoint") - -# Create a token endpoint -@fastapi_app.post("/token") -async def login(username: str, password: str): - # In a real app, you would validate credentials - if username == "user" and password == "password": - return dict(access_token="example_token", token_type="bearer") - return dict(error="Invalid credentials") - -# Create a Reflex app with the FastAPI app as the API transformer -app = rx.App(api_transformer=fastapi_app) -``` - -### Using a Callable Transformer - -You can also provide a callable that transforms the ASGI app: - -```python -import reflex as rx -from starlette.middleware.cors import CORSMiddleware - -# Create a transformer function that returns a transformed ASGI app -def add_cors_middleware(app): - # Wrap the app with CORS middleware and return the wrapped app - return CORSMiddleware( - app=app, - allow_origins=["https://example.com"], - allow_methods=["*"], - allow_headers=["*"], - ) - -# Create a Reflex app with the transformer -app = rx.App(api_transformer=add_cors_middleware) -``` - -### Using Multiple Transformers - -You can apply multiple transformers by providing a sequence: - -```python -import reflex as rx -from fastapi import FastAPI -from starlette.middleware import Middleware -from starlette.middleware.cors import CORSMiddleware - -# Create a FastAPI app -fastapi_app = FastAPI(title="My API") - -# Add routes to the FastAPI app -@fastapi_app.get("/api/items") -async def get_items(): - return dict(items=["Item1", "Item2", "Item3"]) - -# Create a transformer function -def add_logging_middleware(app): - # This is a simple example middleware that logs requests - async def middleware(scope, receive, send): - # Log the request path - path = scope["path"] - print("Request:", path) - await app(scope, receive, send) - return middleware - -# Create a Reflex app with multiple transformers -app = rx.App(api_transformer=[fastapi_app, add_logging_middleware]) -``` - -## Reserved Routes - -Some routes on the backend are reserved for the runtime of Reflex, and should not be overridden unless you know what you are doing. - -### Ping - -`localhost:8000/ping/`: You can use this route to check the health of the backend. - -The expected return is `"pong"`. - -### Event - -`localhost:8000/_event`: the frontend will use this route to notify the backend that an event occurred. - -```md alert error -# Overriding this route will break the event communication -``` - -### Upload - -`localhost:8000/_upload`: This route is used for the upload of file when using `rx.upload()`. diff --git a/docs/assets/overview.md b/docs/assets/overview.md deleted file mode 100644 index 51ba7e1599..0000000000 --- a/docs/assets/overview.md +++ /dev/null @@ -1,95 +0,0 @@ -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -``` - -# Assets - -Static files such as images and stylesheets can be placed in `assets/` folder of the project. These files can be referenced within your app. - -```md alert -# Assets are copied during the build process. - -Any files placed within the `assets/` folder at runtime will not be available to the app -when running in production mode. The `assets/` folder should only be used for static files. -``` - -## Referencing Assets - -There are two ways to reference assets in your Reflex app: - -### 1. Direct Path Reference - -To reference an image in the `assets/` folder, pass the relative path as a prop. - -For example, you can store your logo in your assets folder: - -```bash -assets -└── Reflex.svg -``` - -Then you can display it using a `rx.image` component: - -```python demo -rx.image(src=f"{REFLEX_ASSETS_CDN}other/Reflex.svg", width="5em") -``` - -```md alert -# Always prefix the asset path with a forward slash `/` to reference the asset from the root of the project, or it may not display correctly on non-root pages. -``` - -### 2. Using rx.asset Function - -The `rx.asset` function provides a more flexible way to reference assets in your app. It supports both local assets (in the app's `assets/` directory) and shared assets (placed next to your Python files). - -#### Local Assets - -Local assets are stored in the app's `assets/` directory and are referenced using `rx.asset`: - -```python demo -rx.image(src=rx.asset("Reflex.svg"), width="5em") -``` - -#### Shared Assets - -Shared assets are placed next to your Python file and are linked to the app's external assets directory. This is useful for creating reusable components with their own assets: - -```python box -# my_component.py -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN - -# my_script.js is located in the same directory as this Python file -def my_component(): - return rx.box( - rx.script(src=rx.asset("my_script.js", shared=True)), - "Component with custom script" - ) -``` - -You can also specify a subfolder for shared assets: - -```python box -# my_component.py -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN - -# image.png is located in a subfolder next to this Python file -def my_component_with_image(): - return rx.image( - src=rx.asset("image.png", shared=True, subfolder="images") - ) -``` - -```md alert -# Shared assets are linked to your app via symlinks. - -When using `shared=True`, the asset is symlinked from its original location to your app's external assets directory. This allows you to keep assets alongside their related code. -``` - -## Favicon - -The favicon is the small icon that appears in the browser tab. - -You can add a `favicon.ico` file to the `assets/` folder to change the favicon. diff --git a/docs/assets/upload_and_download_files.md b/docs/assets/upload_and_download_files.md deleted file mode 100644 index 7591196543..0000000000 --- a/docs/assets/upload_and_download_files.md +++ /dev/null @@ -1,154 +0,0 @@ -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from pcweb.pages.docs import library -from pcweb.pages.docs import api_reference -from pcweb.styles.styles import get_code_style -from pcweb.styles.colors import c_color -``` - -# Files - -In addition to any assets you ship with your app, many web app will often need to receive or send files, whether you want to share media, allow user to import their data, or export some backend data. - -In this section, we will cover all you need to know for manipulating files in Reflex. - -## Assets vs Upload Directory - -Before diving into file uploads and downloads, it's important to understand the difference between assets and the upload directory in Reflex: - -```python eval -# Simple table comparing assets vs upload directory -rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Feature"), - rx.table.column_header_cell("Assets"), - rx.table.column_header_cell("Upload Directory"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.cell(rx.text("Purpose", font_weight="bold")), - rx.table.cell(rx.text("Static files included with your app (images, stylesheets, scripts)")), - rx.table.cell(rx.text("Dynamic files uploaded by users during runtime")), - ), - rx.table.row( - rx.table.cell(rx.text("Location", font_weight="bold")), - rx.table.cell(rx.hstack( - rx.code("assets/", style=get_code_style("violet")), - rx.text(" folder or next to Python files (shared assets)"), - spacing="2", - )), - rx.table.cell(rx.hstack( - rx.code("uploaded_files/", style=get_code_style("violet")), - rx.text(" directory (configurable)"), - spacing="2", - )), - ), - rx.table.row( - rx.table.cell(rx.text("Access Method", font_weight="bold")), - rx.table.cell(rx.hstack( - rx.code("rx.asset()", style=get_code_style("violet")), - rx.text(" or direct path reference"), - spacing="2", - )), - rx.table.cell(rx.code("rx.get_upload_url()", style=get_code_style("violet"))), - ), - rx.table.row( - rx.table.cell(rx.text("When to Use", font_weight="bold")), - rx.table.cell(rx.text("For files that are part of your application's codebase")), - rx.table.cell(rx.text("For files that users upload or generate through your application")), - ), - rx.table.row( - rx.table.cell(rx.text("Availability", font_weight="bold")), - rx.table.cell(rx.text("Available at compile time")), - rx.table.cell(rx.text("Available at runtime")), - ), - ), - width="100%", -) -``` - - - -For more information about assets, see the [Assets Overview](/docs/assets/overview/). - -## Download - -If you want to let the users of your app download files from your server to their computer, Reflex offer you two way. - -### With a regular link - -For some basic usage, simply providing the path to your resource in a `rx.link` will work, and clicking the link will download or display the resource. - -```python demo -rx.link("Download", href="/reflex_banner.webp") -``` - -### With `rx.download` event - -Using the `rx.download` event will always prompt the browser to download the file, even if it could be displayed in the browser. - -The `rx.download` event also allows the download to be triggered from another backend event handler. - -```python demo -rx.button( - "Download", - on_click=rx.download(url="/reflex_banner.webp"), -) -``` - -`rx.download` lets you specify a name for the file that will be downloaded, if you want it to be different from the name on the server side. - -```python demo -rx.button( - "Download and Rename", - on_click=rx.download( - url="/reflex_banner.webp", - filename="different_name_logo.png" - ), -) -``` - -If the data to download is not already available at a known URL, pass the `data` directly to the `rx.download` event from the backend. - -```python demo exec -import random - -class DownloadState(rx.State): - @rx.event - def download_random_data(self): - return rx.download( - data=",".join([str(random.randint(0, 100)) for _ in range(10)]), - filename="random_numbers.csv" - ) - -def download_random_data_button(): - return rx.button( - "Download random numbers", - on_click=DownloadState.download_random_data - ) -``` - -The `data` arg accepts `str` or `bytes` data, a `data:` URI, `PIL.Image`, or any state Var. If the Var is not already a string, it will be converted to a string using `JSON.stringify`. This allows complex state structures to be offered as JSON downloads. - -Reference page for `rx.download` [here]({api_reference.special_events.path}#rx.download). - -## Upload - -Uploading files to your server let your users interact with your app in a different way than just filling forms to provide data. - -The component `rx.upload` let your users upload files on the server. - -Here is a basic example of how it is used: - -```python -def index(): - return rx.fragment( - rx.upload(rx.text("Upload files"), rx.icon(tag="upload")), - rx.button(on_submit=State.) - ) -``` - -For detailed information, see the reference page of the component [here]({library.forms.upload.path}). diff --git a/docs/authentication/authentication_overview.md b/docs/authentication/authentication_overview.md deleted file mode 100644 index 72dc4de4ec..0000000000 --- a/docs/authentication/authentication_overview.md +++ /dev/null @@ -1,28 +0,0 @@ -```python exec -from pcweb.pages.docs import vars -``` - -# Authentication Overview - -Many apps require authentication to manage users. There are a few different ways to accomplish this in Reflex: - -We have solutions that currently exist outside of the core framework: - -1. Local Auth: Uses your own database: https://github.com/masenf/reflex-local-auth -2. Google Auth: Uses sign in with Google: https://github.com/masenf/reflex-google-auth -3. Captcha: Generates tests that humans can pass but automated systems cannot: https://github.com/masenf/reflex-google-recaptcha-v2 -4. Magic Link Auth: A passwordless login method that sends a unique, one-time-use URL to a user's email: https://github.com/masenf/reflex-magic-link-auth -5. Clerk Auth: A community member wrapped this component and hooked it up in this app: https://github.com/TimChild/reflex-clerk-api -6. Descope Auth: Enables authentication with Descope, supporting passwordless, social login, SSO, and MFA: https://github.com/descope-sample-apps/reflex-descope-auth - -If you're using the AI Builder, you can also use the built-in [Authentication Integrations](/docs/ai-builder/integrations/overview) which include Azure Auth, Google Auth, Okta Auth, and Descope. - -## Guidance for Implementing Authentication - -- Store sensitive user tokens and information in [backend-only vars]({vars.base_vars.path}#backend-only-vars). -- Validate user session and permissions for each event handler that performs an authenticated action and all computed vars or loader events that access private data. -- All content that is statically rendered in the frontend (for example, data hardcoded or loaded at compile time in the UI) will be publicly available, even if the page redirects to a login or uses `rx.cond` to hide content. -- Only data that originates from state can be truly private and protected. -- When using cookies or local storage, a signed JWT can detect and invalidate any local tampering. - -More auth documentation on the way. Check back soon! diff --git a/docs/client_storage/overview.md b/docs/client_storage/overview.md deleted file mode 100644 index 7016080a1d..0000000000 --- a/docs/client_storage/overview.md +++ /dev/null @@ -1,45 +0,0 @@ -```python exec -import reflex as rx -``` - -# Client-storage - -You can use the browser's local storage to persist state between sessions. -This allows user preferences, authentication cookies, other bits of information -to be stored on the client and accessed from different browser tabs. - -A client-side storage var looks and acts like a normal `str` var, except the -default value is either `rx.Cookie` or `rx.LocalStorage` depending on where the -value should be stored. The key name will be based on the var name, but this -can be overridden by passing `name="my_custom_name"` as a keyword argument. - -For more information see [Browser Storage](/docs/api-reference/browser-storage/). - -Try entering some values in the text boxes below and then load the page in a separate -tab or check the storage section of browser devtools to see the values saved in the browser. - -```python demo exec -class ClientStorageState(rx.State): - my_cookie: str = rx.Cookie("") - my_local_storage: str = rx.LocalStorage("") - custom_cookie: str = rx.Cookie(name="CustomNamedCookie", max_age=3600) - - @rx.event - def set_my_cookie(self, value: str): - self.my_cookie = value - - @rx.event - def set_my_local_storage(self, value: str): - self.my_local_storage = value - - @rx.event - def set_custom_cookie(self, value: str): - self.custom_cookie = value - -def client_storage_example(): - return rx.vstack( - rx.hstack(rx.text("my_cookie"), rx.input(value=ClientStorageState.my_cookie, on_change=ClientStorageState.set_my_cookie)), - rx.hstack(rx.text("my_local_storage"), rx.input(value=ClientStorageState.my_local_storage, on_change=ClientStorageState.set_my_local_storage)), - rx.hstack(rx.text("custom_cookie"), rx.input(value=ClientStorageState.custom_cookie, on_change=ClientStorageState.set_custom_cookie)), - ) -``` diff --git a/docs/components/conditional_rendering.md b/docs/components/conditional_rendering.md deleted file mode 100644 index 5df4111a40..0000000000 --- a/docs/components/conditional_rendering.md +++ /dev/null @@ -1,131 +0,0 @@ -```python exec -import reflex as rx - -from pcweb.pages.docs import library -from pcweb.pages import docs -``` - -# Conditional Rendering - -Recall from the [basics]({docs.getting_started.basics.path}) that we cannot use Python `if/else` statements when referencing state vars in Reflex. Instead, use the `rx.cond` component to conditionally render components or set props based on the value of a state var. - -```md video https://youtube.com/embed/ITOZkzjtjUA?start=6040&end=6463 -# Video: Conditional Rendering -``` - -```md alert -# Check out the API reference for [cond docs]({library.dynamic_rendering.cond.path}). -``` - -```python eval -rx.box(height="2em") -``` - -Below is a simple example showing how to toggle between two text components by checking the value of the state var `show`. - -```python demo exec -class CondSimpleState(rx.State): - show: bool = True - - @rx.event - def change(self): - self.show = not (self.show) - - -def cond_simple_example(): - return rx.vstack( - rx.button("Toggle", on_click=CondSimpleState.change), - rx.cond( - CondSimpleState.show, - rx.text("Text 1", color="blue"), - rx.text("Text 2", color="red"), - ), - ) -``` - -If `show` is `True` then the first component is rendered (in this case the blue text). Otherwise the second component is rendered (in this case the red text). - -## Conditional Props - -You can also set props conditionally using `rx.cond`. In this example, we set the `color` prop of a text component based on the value of the state var `show`. - -```python demo exec -class PropCondState(rx.State): - - value: int - - @rx.event - def set_end(self, value: list[int | float]): - self.value = value[0] - - -def cond_prop(): - return rx.slider( - default_value=[50], - on_value_commit=PropCondState.set_end, - color_scheme=rx.cond(PropCondState.value > 50, "green", "pink"), - width="100%", - ) -``` - - -## Var Operations - -You can use [var operations]({docs.vars.var_operations.path}) with the `cond` component for more complex conditions. See the full [cond reference]({library.dynamic_rendering.cond.path}) for more details. - - -## Multiple Conditional Statements - -The [`rx.match`]({library.dynamic_rendering.match.path}) component in Reflex provides a powerful alternative to`rx.cond` for handling multiple conditional statements and structural pattern matching. This component allows you to handle multiple conditions and their associated components in a cleaner and more readable way compared to nested `rx.cond` structures. - -```python demo exec -from typing import List - -import reflex as rx - - -class MatchState(rx.State): - cat_breed: str = "" - animal_options: List[str] = [ - "persian", - "siamese", - "maine coon", - "ragdoll", - "pug", - "corgi", - ] - - @rx.event - def set_cat_breed(self, breed: str): - self.cat_breed = breed - - -def match_demo(): - return rx.flex( - rx.match( - MatchState.cat_breed, - ("persian", rx.text("Persian cat selected.")), - ("siamese", rx.text("Siamese cat selected.")), - ( - "maine coon", - rx.text("Maine Coon cat selected."), - ), - ("ragdoll", rx.text("Ragdoll cat selected.")), - rx.text("Unknown cat breed selected."), - ), - rx.select( - [ - "persian", - "siamese", - "maine coon", - "ragdoll", - "pug", - "corgi", - ], - value=MatchState.cat_breed, - on_change=MatchState.set_cat_breed, - ), - direction="column", - gap="2", - ) -``` diff --git a/docs/components/html_to_reflex.md b/docs/components/html_to_reflex.md deleted file mode 100644 index 42dcd0f5f6..0000000000 --- a/docs/components/html_to_reflex.md +++ /dev/null @@ -1,16 +0,0 @@ -# Convert HTML to Reflex - -To convert HTML, CSS, or any design into Reflex code, use our AI-powered build tool at [Reflex Build](https://build.reflex.dev). - -Simply paste your HTML, CSS, or describe what you want to build, and our AI will generate the corresponding Reflex code for you. - -## How to use Reflex Build - -1. Go to [Reflex Build](https://build.reflex.dev) -2. Paste your HTML/CSS code or describe your design -3. The AI will automatically generate Reflex code -4. Copy the generated code into your Reflex application - -## Convert Figma file to Reflex - -Check out this [Notion doc](https://www.notion.so/reflex-dev/Convert-HTML-to-Reflex-fe22d0641dcd4d5c91c8404ca41c7e77) for a walk through on how to convert a Figma file into Reflex code. diff --git a/docs/components/props.md b/docs/components/props.md deleted file mode 100644 index 1c3d421754..0000000000 --- a/docs/components/props.md +++ /dev/null @@ -1,97 +0,0 @@ -```python exec -import reflex as rx -from pcweb.pages.docs.library import library -from pcweb.pages import docs -``` - -# Props - -Props modify the behavior and appearance of a component. They are passed in as keyword arguments to a component. - -## Component Props - -There are props that are shared between all components, but each component can also define its own props. - -For example, the `rx.image` component has a `src` prop that specifies the URL of the image to display and an `alt` prop that specifies the alternate text for the image. - -```python demo -rx.image( - src="https://web.reflex-assets.dev/other/logo.jpg", - alt="Reflex Logo", -) -``` - -Check the docs for the component you are using to see what props are available and how they affect the component (see the `rx.image` [reference]({docs.library.media.image.path}#api-reference) page for example). - - -## Common Props - -Components support many standard HTML properties as props. For example: the HTML [id]({"https://www.w3schools.com/html/html_id.asp"}) property is exposed directly as the prop `id`. The HTML [className]({"https://www.w3schools.com/jsref/prop_html_classname.asp"}) property is exposed as the prop `class_name` (note the Pythonic snake_casing!). - -```python demo -rx.box( - "Hello World", - id="box-id", - class_name=["class-name-1", "class-name-2",], -) -``` - -In the example above, the `class_name` prop of the `rx.box` component is assigned a list of class names. This means the `rx.box` component will be styled with the CSS classes `class-name-1` and `class-name-2`. - -## Style Props - -In addition to component-specific props, most built-in components support a full range of style props. You can use any [CSS property](https://www.w3schools.com/cssref/index.php) to style a component. - -```python demo -rx.button( - "Fancy Button", - border_radius="1em", - box_shadow="rgba(151, 65, 252, 0.8) 0 15px 30px -10px", - background_image="linear-gradient(144deg,#AF40FF,#5B42F3 50%,#00DDEB)", - box_sizing="border-box", - color="white", - opacity= 1, -) -``` - -See the [styling docs]({docs.styling.overview.path}) to learn more about customizing the appearance of your app. - - -## Binding Props to State - -```md alert warning -# Optional: Learn all about [State]({docs.state.overview.path}) first. -``` - -Reflex apps define [State]({docs.state.overview.path}) classes that hold variables that can change over time. - -State may be modified in response to things like user input like clicking a button, or in response to events like loading a page. - -State vars can be bound to component props, so that the UI always reflects the current state of the app. - -Try clicking the badge below to change its color. - -```python demo exec -class PropExampleState(rx.State): - text: str = "Hello World" - color: str = "red" - - @rx.event - def flip_color(self): - if self.color == "red": - self.color = "blue" - else: - self.color = "red" - - -def index(): - return rx.button( - PropExampleState.text, - color_scheme=PropExampleState.color, - on_click=PropExampleState.flip_color, - ) -``` - -In this example, the `color_scheme` prop is bound to the `color` state var. - -When the `flip_color` event handler is called, the `color` var is updated, and the `color_scheme` prop is updated to match. diff --git a/docs/components/rendering_iterables.md b/docs/components/rendering_iterables.md deleted file mode 100644 index 89564d6b34..0000000000 --- a/docs/components/rendering_iterables.md +++ /dev/null @@ -1,299 +0,0 @@ -```python exec -import reflex as rx - -from pcweb.pages import docs -``` - -# Rendering Iterables - -Recall again from the [basics]({docs.getting_started.basics.path}) that we cannot use Python `for` loops when referencing state vars in Reflex. Instead, use the `rx.foreach` component to render components from a collection of data. - -For dynamic content that should automatically scroll to show the newest items, consider using the [auto scroll]({docs.library.dynamic_rendering.auto_scroll.path}) component together with `rx.foreach`. - -```python demo exec -class IterState(rx.State): - color: list[str] = [ - "red", - "green", - "blue", - ] - - -def colored_box(color: str): - return rx.button(color, background_color=color) - - -def dynamic_buttons(): - return rx.vstack( - rx.foreach(IterState.color, colored_box), - ) - -``` - -Here's the same example using a lambda function. - -```python -def dynamic_buttons(): - return rx.vstack( - rx.foreach(IterState.color, lambda color: colored_box(color)), - ) -``` - -You can also use lambda functions directly with components without defining a separate function. - -```python -def dynamic_buttons(): - return rx.vstack( - rx.foreach(IterState.color, lambda color: rx.button(color, background_color=color)), - ) -``` - -In this first simple example we iterate through a `list` of colors and render a dynamic number of buttons. - -The first argument of the `rx.foreach` function is the state var that you want to iterate through. The second argument is a function that takes in an item from the data and returns a component. In this case, the `colored_box` function takes in a color and returns a button with that color. - -## For vs Foreach - -```md definition -# Regular For Loop -* Use when iterating over constants. - -# Foreach -* Use when iterating over state vars. -``` - -The above example could have been written using a regular Python `for` loop, since the data is constant. - -```python demo exec -colors = ["red", "green", "blue"] -def dynamic_buttons_for(): - return rx.vstack( - [colored_box(color) for color in colors], - ) -``` - -However, as soon as you need the data to be dynamic, you must use `rx.foreach`. - -```python demo exec -class DynamicIterState(rx.State): - color: list[str] = [ - "red", - "green", - "blue", - ] - - def add_color(self, form_data): - self.color.append(form_data["color"]) - -def dynamic_buttons_foreach(): - return rx.vstack( - rx.foreach(DynamicIterState.color, colored_box), - rx.form( - rx.input(name="color", placeholder="Add a color"), - rx.button("Add"), - on_submit=DynamicIterState.add_color, - ) - ) -``` - -## Render Function - -The function to render each item can be defined either as a separate function or as a lambda function. In the example below, we define the function `colored_box` separately and pass it to the `rx.foreach` function. - -```python demo exec -class IterState2(rx.State): - color: list[str] = [ - "red", - "green", - "blue", - ] - -def colored_box(color: rx.Var[str]): - return rx.button(color, background_color=color) - -def dynamic_buttons2(): - return rx.vstack( - rx.foreach(IterState2.color, colored_box), - ) - -``` - -Notice that the type annotation for the `color` parameter in the `colored_box` function is `rx.Var[str]` (rather than just `str`). This is because the `rx.foreach` function passes the item as a `Var` object, which is a wrapper around the actual value. This is what allows us to compile the frontend without knowing the actual value of the state var (which is only known at runtime). - -## Enumerating Iterables - -The function can also take an index as a second argument, meaning that we can enumerate through data as shown in the example below. - -```python demo exec -class IterIndexState(rx.State): - color: list[str] = [ - "red", - "green", - "blue", - ] - - -def create_button(color: rx.Var[str], index: int): - return rx.box( - rx.button(f"{index + 1}. {color}"), - padding_y="0.5em", - ) - -def enumerate_foreach(): - return rx.vstack( - rx.foreach( - IterIndexState.color, - create_button - ), - ) -``` - -Here's the same example using a lambda function. - -```python -def enumerate_foreach(): - return rx.vstack( - rx.foreach( - IterIndexState.color, - lambda color, index: create_button(color, index) - ), - ) -``` - -## Iterating Dictionaries - -We can iterate through a `dict` using a `foreach`. When the dict is passed through to the function that renders each item, it is presented as a list of key-value pairs `[("sky", "blue"), ("balloon", "red"), ("grass", "green")]`. - -```python demo exec -class SimpleDictIterState(rx.State): - color_chart: dict[str, str] = { - "sky": "blue", - "balloon": "red", - "grass": "green", - } - - -def display_color(color: list): - # color is presented as a list key-value pairs [("sky", "blue"), ("balloon", "red"), ("grass", "green")] - return rx.box(rx.text(color[0]), bg=color[1], padding_x="1.5em") - - -def dict_foreach(): - return rx.grid( - rx.foreach( - SimpleDictIterState.color_chart, - display_color, - ), - columns="3", - ) - -``` - -```md alert warning -# Dict Type Annotation. -It is essential to provide the correct full type annotation for the dictionary in the state definition (e.g., `dict[str, str]` instead of `dict`) to ensure `rx.foreach` works as expected. Proper typing allows Reflex to infer and validate the structure of the data during rendering. -``` - -## Nested examples - -`rx.foreach` can be used with nested state vars. Here we use nested `foreach` components to render the nested state vars. The `rx.foreach(project["technologies"], get_badge)` inside of the `project_item` function, renders the `dict` values which are of type `list`. The `rx.box(rx.foreach(NestedStateFE.projects, project_item))` inside of the `projects_example` function renders each `dict` inside of the overall state var `projects`. - -```python demo exec -class NestedStateFE(rx.State): - projects: list[dict[str, list]] = [ - { - "technologies": ["Next.js", "Prisma", "Tailwind", "Google Cloud", "Docker", "MySQL"] - }, - { - "technologies": ["Python", "Flask", "Google Cloud", "Docker"] - } - ] - -def get_badge(technology: rx.Var[str]) -> rx.Component: - return rx.badge(technology, variant="soft", color_scheme="green") - -def project_item(project: rx.Var[dict[str, list]]) -> rx.Component: - return rx.box( - rx.hstack( - rx.foreach(project["technologies"], get_badge) - ), - ) - -def projects_example() -> rx.Component: - return rx.box(rx.foreach(NestedStateFE.projects, project_item)) -``` - -If you want an example where not all of the values in the dict are the same type then check out the example on [var operations using foreach]({docs.vars.var_operations.path}). - -Here is a further example of how to use `foreach` with a nested data structure. - -```python demo exec -class NestedDictIterState(rx.State): - color_chart: dict[str, list[str]] = { - "purple": ["red", "blue"], - "orange": ["yellow", "red"], - "green": ["blue", "yellow"], - } - - -def display_colors(color: rx.Var[tuple[str, list[str]]]): - return rx.vstack( - rx.text(color[0], color=color[0]), - rx.hstack( - rx.foreach( - color[1], - lambda x: rx.box( - rx.text(x, color="black"), bg=x - ), - ) - ), - ) - - -def nested_dict_foreach(): - return rx.grid( - rx.foreach( - NestedDictIterState.color_chart, - display_colors, - ), - columns="3", - ) - -``` - -## Foreach with Cond - -We can also use `foreach` with the `cond` component. - -In this example we define the function `render_item`. This function takes in an `item`, uses the `cond` to check if the item `is_packed`. If it is packed it returns the `item_name` with a `✔` next to it, and if not then it just returns the `item_name`. We use the `foreach` to iterate over all of the items in the `to_do_list` using the `render_item` function. - -```python demo exec -import dataclasses - -@dataclasses.dataclass -class ToDoListItem: - item_name: str - is_packed: bool - -class ForeachCondState(rx.State): - to_do_list: list[ToDoListItem] = [ - ToDoListItem(item_name="Space suit", is_packed=True), - ToDoListItem(item_name="Helmet", is_packed=True), - ToDoListItem(item_name="Back Pack", is_packed=False), - ] - - -def render_item(item: rx.Var[ToDoListItem]): - return rx.cond( - item.is_packed, - rx.list.item(item.item_name + ' ✔'), - rx.list.item(item.item_name), - ) - -def packing_list(): - return rx.vstack( - rx.text("Sammy's Packing List"), - rx.list(rx.foreach(ForeachCondState.to_do_list, render_item)), - ) - -``` diff --git a/docs/custom-components/command-reference.md b/docs/custom-components/command-reference.md deleted file mode 100644 index 56b95b7467..0000000000 --- a/docs/custom-components/command-reference.md +++ /dev/null @@ -1,157 +0,0 @@ -```python exec -from pcweb.pages import docs -``` - -# Command Reference - -The custom component commands are under `reflex component` subcommand. To see the list of available commands, run `reflex component --help`. To see the manual on a specific command, run `reflex component --help`, for example, `reflex component init --help`. - -```bash -reflex component --help -``` - -```text -Usage: reflex component [OPTIONS] COMMAND [ARGS]... - - Subcommands for creating and publishing Custom Components. - -Options: - --help Show this message and exit. - -Commands: - init Initialize a custom component. - build Build a custom component. - share Collect more details on the published package for gallery. -``` - -## reflex component init - -Below is an example of running the `init` command. - -```bash -reflex component init -``` - -```text -reflex component init -─────────────────────────────────────── Initializing reflex-google-auth project ─────────────────────────────────────── -Info: Populating pyproject.toml with package name: reflex-google-auth -Info: Initializing the component directory: custom_components/reflex_google_auth -Info: Creating app for testing: google_auth_demo -──────────────────────────────────────────── Initializing google_auth_demo ──────────────────────────────────────────── -[07:58:16] Initializing the app directory. console.py:85 - Initializing the web directory. console.py:85 -Success: Initialized google_auth_demo -─────────────────────────────────── Installing reflex-google-auth in editable mode. ─────────────────────────────────── -Info: Package reflex-google-auth installed! -Custom component initialized successfully! -─────────────────────────────────────────────────── Project Summary ─────────────────────────────────────────────────── -[ README.md ]: Package description. Please add usage examples. -[ pyproject.toml ]: Project configuration file. Please fill in details such as your name, email, homepage URL. -[ custom_components/ ]: Custom component code template. Start by editing it with your component implementation. -[ google_auth_demo/ ]: Demo App. Add more code to this app and test. -``` - -The `init` command uses the current enclosing folder name to construct a python package name, typically in the kebab case. For example, if running init in folder `google_auth`, the package name will be `reflex-google-auth`. The added prefix reduces the chance of name collision on PyPI (the Python Package Index), and it indicates that the package is a Reflex custom component. The user can override the package name by providing the `--package-name` option. - -The `init` command creates a set of files and folders prefilled with the package name and other details. During the init, the `custom_component` folder is installed locally in editable mode, so a developer can incrementally develop and test with ease. The changes in component implementation is automatically reflected where it is used. Below is the folder structure after the `init` command. - -```text -google_auth/ -├── pyproject.toml -├── README.md -├── custom_components/ -│ └── reflex_google_auth/ -│ ├── google_auth.py -│ └── __init__.py -└── google_auth_demo/ - └── assets/ - google_auth_demo/ - requirements.txt - rxconfig.py -``` - -### pyproject.toml - -The `pyproject.toml` is required for the package to build and be published. It is prefilled with information such as the package name, version (`0.0.1`), author name and email, homepage URL. By default the **Apache-2.0** license is used, the same as Reflex. If any of this information requires update, the user can edit the file by hand. - -### README - -The `README.md` file is created with installation instructions, e.g. `pip install reflex-google-auth`, and a brief description of the package. Typically the `README.md` contains usage examples. On PyPI, the `README.md` is rendered as part of the package page. - -### Custom Components Folder - -The `custom_components` folder is where the actual implementation is. Do not worry about this folder name: there is no need to change it. It is where `pyproject.toml` specifies the source of the python package is. The published package contains the contents inside it, excluding this folder. - -`reflex_google_auth` is the top folder for importable code. The `reflex_google_auth/__init__.py` imports everything from the `reflex_google_auth/google_auth.py`. For the user of the package, the import looks like `from reflex_google_auth import ABC, XYZ`. - -`reflex_google_auth/google_auth.py` is prefilled with code example and instructions from the [wrapping react guide]({docs.wrapping_react.overview.path}). - -### Demo App Folder - -A demo app is generated inside `google_auth_demo` folder with import statements and example usage of the component. This is a regular Reflex app. Go into this directory and start using any reflex commands for testing. - -### Help Manual - -The help manual is shown when adding the `--help` option to the command. - -```bash -reflex component init --help -``` - -```text -Usage: reflex component init [OPTIONS] - - Initialize a custom component. - - Args: library_name: The name of the library. install: Whether to - install package from this local custom component in editable mode. - loglevel: The log level to use. - - Raises: Exit: If the pyproject.toml already exists. - -Options: - --library-name TEXT The name of your library. On PyPI, package - will be published as `reflex-{library- - name}`. - --install / --no-install Whether to install package from this local - custom component in editable mode. - [default: install] - --loglevel [debug|info|warning|error|critical] - The log level to use. [default: - LogLevel.INFO] - --help Show this message and exit. -``` - -## reflex component publish - -To publish to a package index, a user is required to already have an account with them. As of **0.7.5**, Reflex does not handle the publishing process for you. You can do so manually by first running `reflex component build` followed by `twine upload` or `uv publish` or your choice of a publishing utility. - -You can then share your build on our website with `reflex component share`. - -## reflex component build - -It is not required to run the `build` command separately before publishing. The `publish` command will build the package if it is not already built. The `build` command is provided for the user's convenience. - -The `build` command generates the `.tar.gz` and `.whl` distribution files to be uploaded to the desired package index, for example, PyPI. This command must be run at the top level of the project where the `pyproject.toml` file is. As a result of a successful build, there is a new `dist` folder with the distribution files. - -```bash -reflex component build --help -``` - -```text -Usage: reflex component build [OPTIONS] - - Build a custom component. Must be run from the project root directory where - the pyproject.toml is. - - Args: loglevel: The log level to use. - - Raises: Exit: If the build fails. - -Options: - --loglevel [debug|info|warning|error|critical] - The log level to use. [default: - LogLevel.INFO] - --help Show this message and exit. -``` diff --git a/docs/custom-components/overview.md b/docs/custom-components/overview.md deleted file mode 100644 index 3970c73d97..0000000000 --- a/docs/custom-components/overview.md +++ /dev/null @@ -1,75 +0,0 @@ -# Custom Components Overview - -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from pcweb.pages.docs import custom_components -from pcweb.pages.docs.custom_components import custom_components as custom_components_gallery -``` - -Reflex users create many components of their own: ready to use high level components, or nicely wrapped React components. With **Custom Components**, the community can easily share these components now. - -Release **0.4.3** introduces a series of `reflex component` commands that help developers wrap react components, test, and publish them as python packages. As shown in the image below, there are already a few custom components published on PyPI, such as `reflex-spline`, `reflex-webcam`. - -Check out the custom components gallery [here]({custom_components_gallery.path}). - -```python eval -rx.center( - rx.image(src=f"{REFLEX_ASSETS_CDN}custom_components/pypi_reflex_custom_components.webp", width="400px", border_radius="15px", border="1px solid"), -) -``` - -## Prerequisites for Publishing - -In order to publish a Python package, an account is required with a python package index, for example, PyPI. The documentation to create accounts and generate API tokens can be found on their websites. For a quick reference, check out our [Prerequisites for Publishing]({custom_components.prerequisites_for_publishing.path}) page. - -## Steps to Publishing - -Follow these steps to publish the custom component as a python package: - -1. `reflex component init`: creates a new custom component project from templates. -2. dev and test: developer implements and tests the custom component. -3. `reflex component build`: builds the package. -4. `twine upload` or `uv publish`: uploads the package to a python package index. - -### Initialization - -```bash -reflex component init -``` - -First create a new folder for your custom component project, for example `color_picker`. The package name will be `reflex-color-picker`. The prefix `reflex-` is intentionally added for all custom components for easy search on PyPI. If you prefer a particular name for the package, you can either change it manually in the `pyproject.toml` file or add the `--library-name` option in the `reflex component init` command initially. - -Run `reflex component init`, and a set of files and folders will be created in the `color_picker` folder. The `pyproject.toml` file is the configuration file for the project. The `custom_components` folder is where the custom component implementation is. The `color_picker_demo` folder is a demo Reflex app that uses the custom component. If this is the first time of creating python packages, it is encouraged to browse through all the files (there are not that many) to understand the structure of the project. - -```bash -color_picker/ -├── pyproject.toml <- Configuration file -├── README.md -├── .gitignore <- Exclude dist/ and metadata folders -├── custom_components/ -│ └── reflex_color_picker/ <- Custom component source directory -│ ├── color_picker.py -│ └── __init__.py -└── color_picker_demo/ <- Demo Reflex app directory - └── assets/ - color_picker_demo/ - requirements.txt - rxconfig.py -``` - -### Develop and Test - -After finishing the custom component implementation, the user is encouraged to fully test it before publishing. The generated Reflex demo app `color_picker_demo` is a good place to start. It is a regular Reflex app prefilled with imports and usage of this component. During the init, the `custom_component` folder is installed locally in editable mode, so a developer can incrementally develop and test with ease. The changes in component implementation are automatically reflected in the demo app. - -### Publish - -```bash -reflex component build -``` - -Once you're ready to publish your package, run `reflex component build` to build the package. The command builds the distribution files if they are not already built. The end result is a `dist` folder containing the distribution files. The user does not need to do anything manually with these distribution files. - -In order to publish these files as a Python package, you need to use a publishing utility. Any would work, but we recommend either [Twine](https://twine.readthedocs.io/en/stable/) or (uv)[https://docs.astral.sh/uv/guides/package/#publishing-your-package]. Make sure to keep your package version in pyproject.toml updated. - -You can also share your components with the rest of the community at our website using the command `reflex component share`. See you there! diff --git a/docs/custom-components/prerequisites-for-publishing.md b/docs/custom-components/prerequisites-for-publishing.md deleted file mode 100644 index f836e4dad1..0000000000 --- a/docs/custom-components/prerequisites-for-publishing.md +++ /dev/null @@ -1,40 +0,0 @@ -# Python Package Index - -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from pcweb.styles.colors import c_color -image_style = { - "width": "400px", - "border_radius": "12px", - "border": f"1px solid {c_color('slate', 5)}", -} -``` - -In order to publish a Python package, you need to use a publishing utility. Any would work, but we recommend either [Twine](https://twine.readthedocs.io/en/stable/) or [uv](https://docs.astral.sh/uv/guides/package/#publishing-your-package). - -## PyPI - -It is straightforward to create accounts and API tokens with PyPI. There is official help on the [PyPI website](https://pypi.org/help/). For a quick reference here, go to the top right corner of the PyPI website and look for the button to register and fill out personal information. - -```python eval -rx.center( - rx.image(src=f"{REFLEX_ASSETS_CDN}custom_components/pypi_register.webp", style=image_style, margin_bottom="16px", loading="lazy"), -) -``` - -A user can use username and password to authenticate with PyPI when publishing. - -```python eval -rx.center( - rx.image(src=f"{REFLEX_ASSETS_CDN}custom_components/pypi_account_settings.webp", style=image_style, margin_bottom="16px", loading="lazy"), -) -``` - -Scroll down to the API tokens section and click on the "Add API token" button. Fill out the form and click "Generate API token". - -```python eval -rx.center( - rx.image(src=f"{REFLEX_ASSETS_CDN}custom_components/pypi_api_tokens.webp", style=image_style, width="700px", loading="lazy"), -) -``` diff --git a/docs/database/overview.md b/docs/database/overview.md deleted file mode 100644 index 49a4ef4b32..0000000000 --- a/docs/database/overview.md +++ /dev/null @@ -1,87 +0,0 @@ -# Database - -Reflex uses [sqlmodel](https://sqlmodel.tiangolo.com) to provide a built-in ORM wrapping SQLAlchemy. - -The examples on this page refer specifically to how Reflex uses various tools to -expose an integrated database interface. Only basic use cases will be covered -below, but you can refer to the -[sqlmodel tutorial](https://sqlmodel.tiangolo.com/tutorial/select/) -for more examples and information, just replace `SQLModel` with `rx.Model` and -`Session(engine)` with `rx.session()` - -For advanced use cases, please see the -[SQLAlchemy docs](https://docs.sqlalchemy.org/en/14/orm/quickstart.html) (v1.4). - -```md alert info -# Using NoSQL Databases - -If you are using a NoSQL database (e.g. MongoDB), you can work with it in Reflex by installing the appropriate Python client library. In this case, Reflex will not provide any ORM features. -``` - -## Connecting - -Reflex provides a built-in SQLite database for storing and retrieving data. - -You can connect to your own SQL compatible database by modifying the -`rxconfig.py` file with your database url. - -```python -config = rx.Config( - app_name="my_app", - db_url="sqlite:///reflex.db", -) -``` - -For more examples of database URLs that can be used, see the [SQLAlchemy -docs](https://docs.sqlalchemy.org/en/14/core/engines.html#backend-specific-urls). -Be sure to install the appropriate DBAPI driver for the database you intend to -use. - -## Tables - -To create a table make a class that inherits from `rx.Model` with and specify -that it is a table. - -```python -class User(rx.Model, table=True): - username: str - email: str - password: str -``` - -## Migrations - -Reflex leverages [alembic](https://alembic.sqlalchemy.org/en/latest/) -to manage database schema changes. - -Before the database feature can be used in a new app you must call `reflex db init` -to initialize alembic and create a migration script with the current schema. - -After making changes to the schema, use -`reflex db makemigrations --message 'something changed'` -to generate a script in the `alembic/versions` directory that will update the -database schema. It is recommended that generated scripts be inspected before applying them. - -Bear in mind that your newest models will not be detected by the `reflex db makemigrations` -command unless imported and used somewhere within the application. - -The `reflex db migrate` command is used to apply migration scripts to bring the -database up to date. During app startup, if Reflex detects that the current -database schema is not up to date, a warning will be displayed on the console. - -## Queries - -To query the database you can create a `rx.session()` -which handles opening and closing the database connection. - -You can use normal SQLAlchemy queries to query the database. - -```python -with rx.session() as session: - session.add(User(username="test", email="admin@reflex.dev", password="admin")) - session.commit() -``` - -```md video https://youtube.com/embed/ITOZkzjtjUA?start=6835&end=8225 -# Video: Tutorial of Database Model with Forms, Model Field Changes and Migrations, and adding a DateTime Field -``` diff --git a/docs/database/queries.md b/docs/database/queries.md deleted file mode 100644 index 0a69c99f20..0000000000 --- a/docs/database/queries.md +++ /dev/null @@ -1,344 +0,0 @@ -# Queries - -Queries are used to retrieve data from a database. - -A query is a request for information from a database table or combination of -tables. A query can be used to retrieve data from a single table or multiple -tables. A query can also be used to insert, update, or delete data from a table. - -## Session - -To execute a query you must first create a `rx.session`. You can use the session -to query the database using SQLModel or SQLAlchemy syntax. - -The `rx.session` statement will automatically close the session when the code -block is finished. **If `session.commit()` is not called, the changes will be -rolled back and not persisted to the database.** The code can also explicitly -rollback without closing the session via `session.rollback()`. - -The following example shows how to create a session and query the database. -First we create a table called `User`. - -```python -class User(rx.Model, table=True): - username: str - email: str -``` - -### Select - -Then we create a session and query the User table. - -```python -class QueryUser(rx.State): - name: str - users: list[User] - - @rx.event - def get_users(self): - with rx.session() as session: - self.users = session.exec( - User.select().where( - User.username.contains(self.name))).all() -``` - -The `get_users` method will query the database for all users that contain the -value of the state var `name`. - -### Insert - -Similarly, the `session.add()` method to add a new record to the -database or persist an existing object. - -```python -class AddUser(rx.State): - username: str - email: str - - @rx.event - def add_user(self): - with rx.session() as session: - session.add(User(username=self.username, email=self.email)) - session.commit() -``` - -### Update - -To update the user, first query the database for the object, make the desired -modifications, `.add` the object to the session and finally call `.commit()`. - -```python -class ChangeEmail(rx.State): - username: str - email: str - - @rx.event - def modify_user(self): - with rx.session() as session: - user = session.exec(User.select().where( - (User.username == self.username))).first() - user.email = self.email - session.add(user) - session.commit() -``` - -### Delete - -To delete a user, first query the database for the object, then call -`.delete()` on the session and finally call `.commit()`. - -```python -class RemoveUser(rx.State): - username: str - - @rx.event - def delete_user(self): - with rx.session() as session: - user = session.exec(User.select().where( - User.username == self.username)).first() - session.delete(user) - session.commit() -``` - -## ORM Object Lifecycle - -The objects returned by queries are bound to the session that created them, and cannot generally -be used outside that session. After adding or updating an object, not all fields are automatically -updated, so accessing certain attributes may trigger additional queries to refresh the object. - -To avoid this, the `session.refresh()` method can be used to update the object explicitly and -ensure all fields are up to date before exiting the session. - -```python -class AddUserForm(rx.State): - user: User | None = None - - @rx.event - def add_user(self, form_data: dict[str, Any]): - with rx.session() as session: - self.user = User(**form_data) - session.add(self.user) - session.commit() - session.refresh(self.user) -``` - -Now the `self.user` object will have a correct reference to the autogenerated -primary key, `id`, even though this was not provided when the object was created -from the form data. - -If `self.user` needs to be modified or used in another query in a new session, -it must be added to the session. Adding an object to a session does not -necessarily create the object, but rather associates it with a session where it -may either be created or updated accordingly. - -```python -class AddUserForm(rx.State): - ... - - @rx.event - def update_user(self, form_data: dict[str, Any]): - if self.user is None: - return - with rx.session() as session: - self.user.set(**form_data) - session.add(self.user) - session.commit() - session.refresh(self.user) -``` - -If an ORM object will be referenced and accessed outside of a session, you -should call `.refresh()` on it to avoid stale object exceptions. - -## Using SQL Directly - -Avoiding SQL is one of the main benefits of using an ORM, but sometimes it is -necessary for particularly complex queries, or when using database-specific -features. - -SQLModel exposes the `session.execute()` method that can be used to execute raw -SQL strings. If parameter binding is needed, the query may be wrapped in -[`sqlalchemy.text`](https://docs.sqlalchemy.org/en/14/core/sqlelement.html#sqlalchemy.sql.expression.text), -which allows colon-prefix names to be used as placeholders. - -```md alert info -# Never use string formatting to construct SQL queries, as this may lead to SQL injection vulnerabilities in the app. -``` - -```python -import sqlalchemy - -import reflex as rx - - -class State(rx.State): - - @rx.event - def insert_user_raw(self, username, email): - with rx.session() as session: - session.execute( - sqlalchemy.text( - "INSERT INTO user (username, email) " - "VALUES (:username, :email)" - ), - \{"username": username, "email": email}, - ) - session.commit() - - @rx.var - def raw_user_tuples(self) -> list[list]: - with rx.session() as session: - return [list(row) for row in session.execute("SELECT * FROM user").all()] -``` - -## Async Database Operations - -Reflex provides an async version of the session function called `rx.asession` for asynchronous database operations. This is useful when you need to perform database operations in an async context, such as within async event handlers. - -The `rx.asession` function returns an async SQLAlchemy session that must be used with an async context manager. Most operations against the `asession` must be awaited. - -```python -import sqlalchemy.ext.asyncio -import sqlalchemy - -import reflex as rx - - -class AsyncUserState(rx.State): - users: list[User] = [] - - @rx.event(background=True) - async def get_users_async(self): - async with rx.asession() as asession: - result = await asession.execute(User.select()) - async with self: - self.users = result.all() -``` - -### Async Select - -The following example shows how to query the database asynchronously: - -```python -class AsyncQueryUser(rx.State): - name: str - users: list[User] = [] - - @rx.event(background=True) - async def get_users(self): - async with rx.asession() as asession: - stmt = User.select().where(User.username.contains(self.name)) - result = await asession.execute(stmt) - async with self: - self.users = result.all() -``` - -### Async Insert - -To add a new record to the database asynchronously: - -```python -class AsyncAddUser(rx.State): - username: str - email: str - - @rx.event(background=True) - async def add_user(self): - async with rx.asession() as asession: - asession.add(User(username=self.username, email=self.email)) - await asession.commit() -``` - -### Async Update - -To update a user asynchronously: - -```python -class AsyncChangeEmail(rx.State): - username: str - email: str - - @rx.event(background=True) - async def modify_user(self): - async with rx.asession() as asession: - stmt = User.select().where(User.username == self.username) - result = await asession.execute(stmt) - user = result.first() - if user: - user.email = self.email - asession.add(user) - await asession.commit() -``` - -### Async Delete - -To delete a user asynchronously: - -```python -class AsyncRemoveUser(rx.State): - username: str - - @rx.event(background=True) - async def delete_user(self): - async with rx.asession() as asession: - stmt = User.select().where(User.username == self.username) - result = await asession.execute(stmt) - user = result.first() - if user: - await asession.delete(user) - await asession.commit() -``` - -### Async Refresh - -Similar to the regular session, you can refresh an object to ensure all fields are up to date: - -```python -class AsyncAddUserForm(rx.State): - user: User | None = None - - @rx.event(background=True) - async def add_user(self, form_data: dict[str, str]): - async with rx.asession() as asession: - async with self: - self.user = User(**form_data) - asession.add(self.user) - await asession.commit() - await asession.refresh(self.user) -``` - -### Async SQL Execution - -You can also execute raw SQL asynchronously: - -```python -class AsyncRawSQL(rx.State): - users: list[list] = [] - - @rx.event(background=True) - async def insert_user_raw(self, username, email): - async with rx.asession() as asession: - await asession.execute( - sqlalchemy.text( - "INSERT INTO user (username, email) " - "VALUES (:username, :email)" - ), - dict(username=username, email=email), - ) - await asession.commit() - - @rx.event(background=True) - async def get_raw_users(self): - async with rx.asession() as asession: - result = await asession.execute("SELECT * FROM user") - async with self: - self.users = [list(row) for row in result.all()] -``` - -```md alert info -# Important Notes for Async Database Operations -- Always use the `@rx.event(background=True)` decorator for async event handlers -- Most operations against the `asession` must be awaited, including `commit()`, `execute()`, `refresh()`, and `delete()` -- The `add()` method does not need to be awaited -- Result objects from queries have methods like `all()` and `first()` that are synchronous and return data directly -- Use `async with self:` when updating state variables in background tasks -``` diff --git a/docs/database/relationships.md b/docs/database/relationships.md deleted file mode 100644 index 9b06daeeb4..0000000000 --- a/docs/database/relationships.md +++ /dev/null @@ -1,164 +0,0 @@ -# Relationships - -Foreign key relationships are used to link two tables together. For example, -the `Post` model may have a field, `user_id`, with a foreign key of `user.id`, -referencing a `User` model. This would allow us to automatically query the `Post` objects -associated with a user, or find the `User` object associated with a `Post`. - -To establish bidirectional relationships a model must correctly set the -`back_populates` keyword argument on the `Relationship` to the relationship -attribute in the _other_ model. - -## Foreign Key Relationships - -To create a relationship, first add a field to the model that references the -primary key of the related table, then add a `sqlmodel.Relationship` attribute -which can be used to access the related objects. - -Defining relationships like this requires the use of `sqlmodel` objects as -seen in the example. - -```python -from typing import List, Optional - -import sqlmodel - -import reflex as rx - - -class Post(rx.Model, table=True): - title: str - body: str - user_id: int = sqlmodel.Field(foreign_key="user.id") - - user: Optional["User"] = sqlmodel.Relationship(back_populates="posts") - flags: Optional[List["Flag"]] = sqlmodel.Relationship(back_populates="post") - - -class User(rx.Model, table=True): - username: str - email: str - - posts: List[Post] = sqlmodel.Relationship(back_populates="user") - flags: List["Flag"] = sqlmodel.Relationship(back_populates="user") - - -class Flag(rx.Model, table=True): - post_id: int = sqlmodel.Field(foreign_key="post.id") - user_id: int = sqlmodel.Field(foreign_key="user.id") - message: str - - post: Optional[Post] = sqlmodel.Relationship(back_populates="flags") - user: Optional[User] = sqlmodel.Relationship(back_populates="flags") -``` - -See the [SQLModel Relationship Docs](https://sqlmodel.tiangolo.com/tutorial/relationship-attributes/define-relationships-attributes/) for more details. - -## Querying Relationships - -### Inserting Linked Objects - -The following example assumes that the flagging user is stored in the state as a -`User` instance and that the post `id` is provided in the data submitted in the -form. - -```python -class FlagPostForm(rx.State): - user: User - - @rx.event - def flag_post(self, form_data: dict[str, Any]): - with rx.session() as session: - post = session.get(Post, int(form_data.pop("post_id"))) - flag = Flag(message=form_data.pop("message"), post=post, user=self.user) - session.add(flag) - session.commit() -``` - -### How are Relationships Dereferenced? - -By default, the relationship attributes are in **lazy loading** or `"select"` -mode, which generates a query _on access_ to the relationship attribute. Lazy -loading is generally fine for single object lookups and manipulation, but can be -inefficient when accessing many linked objects for serialization purposes. - -There are several alternative loading mechanisms available that can be set on -the relationship object or when performing the query. - -* "joined" or `joinload` - generates a single query to load all related objects - at once. -* "subquery" or `subqueryload` - generates a single query to load all related - objects at once, but uses a subquery to do the join, instead of a join in the - main query. -* "selectin" or `selectinload` - emits a second (or more) SELECT statement which - assembles the primary key identifiers of the parent objects into an IN clause, - so that all members of related collections / scalar references are loaded at - once by primary key - -There are also non-loading mechanisms, "raise" and "noload" which are used to -specifically avoid loading a relationship. - -Each loading method comes with tradeoffs and some are better suited for different -data access patterns. -See [SQLAlchemy: Relationship Loading Techniques](https://docs.sqlalchemy.org/en/14/orm/loading_relationships.html) -for more detail. - -### Querying Linked Objects - -To query the `Post` table and include all `User` and `Flag` objects up front, -the `.options` interface will be used to specify `selectinload` for the required -relationships. Using this method, the linked objects will be available for -rendering in frontend code without additional steps. - -```python -import sqlalchemy - - -class PostState(rx.State): - posts: List[Post] - - @rx.event - def load_posts(self): - with rx.session() as session: - self.posts = session.exec( - Post.select - .options( - sqlalchemy.orm.selectinload(Post.user), - sqlalchemy.orm.selectinload(Post.flags).options( - sqlalchemy.orm.selectinload(Flag.user), - ), - ) - .limit(15) - ).all() -``` - -The loading methods create new query objects and thus may be linked if the -relationship itself has other relationships that need to be loaded. In this -example, since `Flag` references `User`, the `Flag.user` relationship must be -chain loaded from the `Post.flags` relationship. - -### Specifying the Loading Mechanism on the Relationship - -Alternatively, the loading mechanism can be specified on the relationship by -passing `sa_relationship_kwargs=\{"lazy": method}` to `sqlmodel.Relationship`, -which will use the given loading mechanism in all queries by default. - -```python -from typing import List, Optional - -import sqlmodel - -import reflex as rx - - -class Post(rx.Model, table=True): - ... - user: Optional["User"] = sqlmodel.Relationship( - back_populates="posts", - sa_relationship_kwargs=\{"lazy": "selectin"}, - ) - flags: Optional[List["Flag"]] = sqlmodel.Relationship( - back_populates="post", - sa_relationship_kwargs=\{"lazy": "selectin"}, - ) -``` diff --git a/docs/database/tables.md b/docs/database/tables.md deleted file mode 100644 index 4be4b16454..0000000000 --- a/docs/database/tables.md +++ /dev/null @@ -1,70 +0,0 @@ -# Tables - -Tables are database objects that contain all the data in a database. - -In tables, data is logically organized in a row-and-column format similar to a -spreadsheet. Each row represents a unique record, and each column represents a -field in the record. - -## Creating a Table - -To create a table, make a class that inherits from `rx.Model`. - -The following example shows how to create a table called `User`. - -```python -class User(rx.Model, table=True): - username: str - email: str -``` - -The `table=True` argument tells Reflex to create a table in the database for -this class. - -### Primary Key - -By default, Reflex will create a primary key column called `id` for each table. - -However, if an `rx.Model` defines a different field with `primary_key=True`, then the -default `id` field will not be created. A table may also redefine `id` as needed. - -It is not currently possible to create a table without a primary key. - -## Advanced Column Types - -SQLModel automatically maps basic python types to SQLAlchemy column types, but -for more advanced use cases, it is possible to define the column type using -`sqlalchemy` directly. For example, we can add a last updated timestamp to the -post example as a proper `DateTime` field with timezone. - -```python -import datetime - -import sqlmodel -import sqlalchemy - -class Post(rx.Model, table=True): - ... - update_ts: datetime.datetime = sqlmodel.Field( - default=None, - sa_column=sqlalchemy.Column( - "update_ts", - sqlalchemy.DateTime(timezone=True), - server_default=sqlalchemy.func.now(), - ), - ) -``` - -To make the `Post` model more usable on the frontend, a `dict` method may be provided -that converts any fields to a JSON serializable value. In this case, the dict method is -overriding the default `datetime` serializer to strip off the microsecond part. - -```python -class Post(rx.Model, table=True): - ... - - def dict(self, *args, **kwargs) -> dict: - d = super().dict(*args, **kwargs) - d["update_ts"] = self.update_ts.replace(microsecond=0).isoformat() - return d -``` diff --git a/docs/events/background_events.md b/docs/events/background_events.md deleted file mode 100644 index f8d0d38874..0000000000 --- a/docs/events/background_events.md +++ /dev/null @@ -1,159 +0,0 @@ -```python exec -import reflex as rx -from pcweb import constants, styles -``` - -# Background Tasks - -A background task is a special type of `EventHandler` that may run -concurrently with other `EventHandler` functions. This enables long-running -tasks to execute without blocking UI interactivity. - -A background task is defined by decorating an async `State` method with -`@rx.event(background=True)`. - -```md alert warning -# `@rx.event(background=True)` used to be called `@rx.background`. -In Reflex version 0.6.5 and later, the `@rx.background` decorator has been renamed to `@rx.event(background=True)`. -``` - -Whenever a background task needs to interact with the state, **it must enter an -`async with self` context block** which refreshes the state and takes an -exclusive lock to prevent other tasks or event handlers from modifying it -concurrently. Because other `EventHandler` functions may modify state while the -task is running, **outside of the context block, Vars accessed by the background -task may be _stale_**. Attempting to modify the state from a background task -outside of the context block will raise an `ImmutableStateError` exception. - -In the following example, the `my_task` event handler is decorated with -`@rx.event(background=True)` and increments the `counter` variable every half second, as -long as certain conditions are met. While it is running, the UI remains -interactive and continues to process events normally. - -```md alert info -# Background events are similar to simple Task Queues like [Celery](https://www.fullstackpython.com/celery.html) allowing asynchronous events. -``` - -```python demo exec id=background_demo -import asyncio -import reflex as rx - - -class MyTaskState(rx.State): - counter: int = 0 - max_counter: int = 10 - running: bool = False - _n_tasks: int = 0 - - @rx.event - def set_max_counter(self, value: str): - self.max_counter = int(value) - - @rx.event(background=True) - async def my_task(self): - async with self: - # The latest state values are always available inside the context - if self._n_tasks > 0: - # only allow 1 concurrent task - return - - # State mutation is only allowed inside context block - self._n_tasks += 1 - - while True: - async with self: - # Check for stopping conditions inside context - if self.counter >= self.max_counter: - self.running = False - if not self.running: - self._n_tasks -= 1 - return - - self.counter += 1 - - # Await long operations outside the context to avoid blocking UI - await asyncio.sleep(0.5) - - @rx.event - def toggle_running(self): - self.running = not self.running - if self.running: - return MyTaskState.my_task - - @rx.event - def clear_counter(self): - self.counter = 0 - - -def background_task_example(): - return rx.hstack( - rx.heading(MyTaskState.counter, " /"), - rx.input( - value=MyTaskState.max_counter, - on_change=MyTaskState.set_max_counter, - width="8em", - ), - rx.button( - rx.cond(~MyTaskState.running, "Start", "Stop"), - on_click=MyTaskState.toggle_running, - ), - rx.button( - "Reset", - on_click=MyTaskState.clear_counter, - ), - ) -``` - -## Terminating Background Tasks on Page Close or Navigation - -Sometimes, background tasks may continue running even after the user navigates away from the page or closes the browser tab. To handle such cases, you can check if the websocket associated with the state is disconnected and terminate the background task when necessary. - -The solution involves checking if the client_token is still valid in the app.event_namespace.token_to_sid mapping. If the session is lost (e.g., the user navigates away or closes the page), the background task will stop. - -```python -import asyncio -import reflex as rx - -class State(rx.State): - @rx.event(background=True) - async def loop_function(self): - while True: - if self.router.session.client_token not in app.event_namespace.token_to_sid: - print("WebSocket connection closed or user navigated away. Stopping background task.") - break - - print("Running background task...") - await asyncio.sleep(2) - - -@rx.page(on_load=State.loop_function) -def index(): - return rx.text("Hello, this page will manage background tasks and stop them when the page is closed or navigated away.") - -``` - -## Task Lifecycle - -When a background task is triggered, it starts immediately, saving a reference to -the task in `app.background_tasks`. When the task completes, it is removed from -the set. - -Multiple instances of the same background task may run concurrently, and the -framework makes no attempt to avoid duplicate tasks from starting. - -It is up to the developer to ensure that duplicate tasks are not created under -the circumstances that are undesirable. In the example above, the `_n_tasks` -backend var is used to control whether `my_task` will enter the increment loop, -or exit early. - - - - -## Background Task Limitations - -Background tasks mostly work like normal `EventHandler` methods, with certain exceptions: - -- Background tasks must be `async` functions. -- Background tasks cannot modify the state outside of an `async with self` context block. -- Background tasks may read the state outside of an `async with self` context block, but the value may be stale. -- Background tasks may not be directly called from other event handlers or background tasks. Instead use `yield` or `return` to trigger the background task. diff --git a/docs/events/chaining_events.md b/docs/events/chaining_events.md deleted file mode 100644 index 1d14ff16ee..0000000000 --- a/docs/events/chaining_events.md +++ /dev/null @@ -1,94 +0,0 @@ -```python exec -import reflex as rx -``` - -# Chaining events - -## Calling Event Handlers From Event Handlers - -You can call other event handlers from event handlers to keep your code modular. Just use the `self.call_handler` syntax to run another event handler. As always, you can yield within your function to send incremental updates to the frontend. - -```python demo exec id=call-handler -import asyncio - -class CallHandlerState(rx.State): - count: int = 0 - progress: int = 0 - - @rx.event - async def run(self): - # Reset the count. - self.set_progress(0) - yield - - # Count to 10 while showing progress. - for i in range(10): - # Wait and increment. - await asyncio.sleep(0.5) - self.count += 1 - - # Update the progress. - self.set_progress(i + 1) - - # Yield to send the update. - yield - - -def call_handler_example(): - return rx.vstack( - rx.badge(CallHandlerState.count, font_size="1.5em", color_scheme="green"), - rx.progress(value=CallHandlerState.progress, max=10, width="100%"), - rx.button("Run", on_click=CallHandlerState.run), - ) -``` - -## Returning Events From Event Handlers - -So far, we have only seen events that are triggered by components. However, an event handler can also return events. - -In Reflex, event handlers run synchronously, so only one event handler can run at a time, and the events in the queue will be blocked until the current event handler finishes.The difference between returning an event and calling an event handler is that returning an event will send the event to the frontend and unblock the queue. - -```md alert info -# Be sure to use the class name `State` (or any substate) rather than `self` when returning events. -``` - -Try entering an integer in the input below then clicking out. - -```python demo exec id=collatz -class CollatzState(rx.State): - count: int = 1 - - @rx.event - def start_collatz(self, count: str): - """Run the collatz conjecture on the given number.""" - self.count = abs(int(count if count else 1)) - return CollatzState.run_step - - @rx.event - async def run_step(self): - """Run a single step of the collatz conjecture.""" - - while self.count > 1: - - await asyncio.sleep(0.5) - - if self.count % 2 == 0: - # If the number is even, divide by 2. - self.count //= 2 - else: - # If the number is odd, multiply by 3 and add 1. - self.count = self.count * 3 + 1 - yield - - -def collatz_example(): - return rx.vstack( - rx.badge(CollatzState.count, font_size="1.5em", color_scheme="green"), - rx.input(on_blur=CollatzState.start_collatz), - ) - -``` - -In this example, we run the [Collatz Conjecture](https://en.wikipedia.org/wiki/Collatz_conjecture) on a number entered by the user. - -When the `on_blur` event is triggered, the event handler `start_collatz` is called. It sets the initial count, then calls `run_step` which runs until the count reaches `1`. diff --git a/docs/events/decentralized_event_handlers.md b/docs/events/decentralized_event_handlers.md deleted file mode 100644 index 2ac8fcb747..0000000000 --- a/docs/events/decentralized_event_handlers.md +++ /dev/null @@ -1,149 +0,0 @@ -```python exec -import reflex as rx -``` - -# Decentralized Event Handlers - -## Overview - -Decentralized event handlers allow you to define event handlers outside of state classes, providing more flexible code organization. This feature was introduced in Reflex v0.7.10 and enables a more modular approach to event handling. - -With decentralized event handlers, you can: -- Organize event handlers by feature rather than by state class -- Separate UI logic from state management -- Create more maintainable and scalable applications - -## Basic Usage - -To create a decentralized event handler, use the `@rx.event` decorator on a function that takes a state instance as its first parameter: - -```python demo exec -import reflex as rx - -class MyState(rx.State): - count: int = 0 - -@rx.event -def increment(state: MyState, amount: int): - state.count += amount - -def decentralized_event_example(): - return rx.vstack( - rx.heading(f"Count: {MyState.count}"), - rx.hstack( - rx.button("Increment by 1", on_click=increment(1)), - rx.button("Increment by 5", on_click=increment(5)), - rx.button("Increment by 10", on_click=increment(10)), - ), - spacing="4", - align="center", - ) -``` - -In this example: -1. We define a `MyState` class with a `count` variable -2. We create a decentralized event handler `increment` that takes a `MyState` instance as its first parameter -3. We use the event handler in buttons, passing different amounts to increment by - -## Compared to Traditional Event Handlers - -Here's a comparison between traditional event handlers defined within state classes and decentralized event handlers: - -```python box -# Traditional event handler within a state class -class TraditionalState(rx.State): - count: int = 0 - - @rx.event - def increment(self, amount: int = 1): - self.count += amount - -# Usage in components -rx.button("Increment", on_click=TraditionalState.increment(5)) - -# Decentralized event handler outside the state class -class DecentralizedState(rx.State): - count: int = 0 - -@rx.event -def increment(state: DecentralizedState, amount: int = 1): - state.count += amount - -# Usage in components -rx.button("Increment", on_click=increment(5)) -``` - -Key differences: -- Traditional event handlers use `self` to reference the state instance -- Decentralized event handlers explicitly take a state instance as the first parameter -- Both approaches use the same syntax for triggering events in components -- Both can be decorated with `@rx.event` respectively - -## Best Practices - -### When to Use Decentralized Event Handlers - -Decentralized event handlers are particularly useful in these scenarios: - -1. **Large applications** with many event handlers that benefit from better organization -2. **Feature-based organization** where you want to group related event handlers together -3. **Separation of concerns** when you want to keep state definitions clean and focused - -### Type Annotations - -Always use proper type annotations for your state parameter and any additional parameters: - -```python box -@rx.event -def update_user(state: UserState, name: str, age: int): - state.name = name - state.age = age -``` - -### Naming Conventions - -Follow these naming conventions for clarity: - -1. Use descriptive names that indicate the action being performed -2. Use the state class name as the type annotation for the first parameter -3. Name the state parameter consistently across your codebase (e.g., always use `state` or the first letter of the state class) - -### Organization - -Consider these approaches for organizing decentralized event handlers: - -1. Group related event handlers in the same file -2. Place event handlers near the state classes they modify -3. For larger applications, create a dedicated `events` directory with files organized by feature - -```python box -# Example organization in a larger application -# events/user_events.py -@rx.event -def update_user(state: UserState, name: str, age: int): - state.name = name - state.age = age - -@rx.event -def delete_user(state: UserState): - state.name = "" - state.age = 0 -``` - -### Combining with Other Event Features - -Decentralized event handlers work seamlessly with other Reflex event features: - -```python box -# Background event -@rx.event(background=True) -async def long_running_task(state: AppState): - # Long-running task implementation - pass - -# Event chaining -@rx.event -def process_form(state: FormState, data: dict): - # Process form data - return validate_data # Chain to another event -``` diff --git a/docs/events/event_actions.md b/docs/events/event_actions.md deleted file mode 100644 index 1d431eddc8..0000000000 --- a/docs/events/event_actions.md +++ /dev/null @@ -1,252 +0,0 @@ -```python exec -import reflex as rx -import datetime -``` - -# Event Actions - -In Reflex, an event action is a special behavior that occurs during or after -processing an event on the frontend. - -Event actions can modify how the browser handles DOM events or throttle and -debounce events before they are processed by the backend. - -An event action is specified by accessing attributes and methods present on all -EventHandlers and EventSpecs. - -## DOM Event Propagation - -_Added in v0.3.2_ - -### prevent_default - -The `.prevent_default` action prevents the default behavior of the browser for -the action. This action can be added to any existing event, or it can be used on its own by -specifying `rx.prevent_default` as an event handler. - -A common use case for this is to prevent navigation when clicking a link. - -```python demo -rx.link("This Link Does Nothing", href="https://reflex.dev/", on_click=rx.prevent_default) -``` - -```python demo exec -class LinkPreventDefaultState(rx.State): - status: bool = False - - @rx.event - def toggle_status(self): - self.status = not self.status - -def prevent_default_example(): - return rx.vstack( - rx.heading(f"The value is {LinkPreventDefaultState.status}"), - rx.link( - "Toggle Value", - href="https://reflex.dev/", - on_click=LinkPreventDefaultState.toggle_status.prevent_default, - ), - ) -``` - -### stop_propagation - -The `.stop_propagation` action stops the event from propagating to parent elements. - -This action is often used when a clickable element contains nested buttons that -should not trigger the parent element's click event. - -In the following example, the first button uses `.stop_propagation` to prevent -the click event from propagating to the outer vstack. The second button does not -use `.stop_propagation`, so the click event will also be handled by the on_click -attached to the outer vstack. - -```python demo exec -class StopPropagationState(rx.State): - where_clicked: list[str] = [] - - @rx.event - def handle_click(self, where: str): - self.where_clicked.append(where) - - @rx.event - def handle_reset(self): - self.where_clicked = [] - -def stop_propagation_example(): - return rx.vstack( - rx.button( - "btn1 - Stop Propagation", - on_click=StopPropagationState.handle_click("btn1").stop_propagation, - ), - rx.button( - "btn2 - Normal Propagation", - on_click=StopPropagationState.handle_click("btn2"), - ), - rx.foreach(StopPropagationState.where_clicked, rx.text), - rx.button( - "Reset", - on_click=StopPropagationState.handle_reset.stop_propagation, - ), - padding="2em", - border=f"1px dashed {rx.color('accent', 5)}", - on_click=StopPropagationState.handle_click("outer") - ) -``` - -## Throttling and Debounce - -_Added in v0.5.0_ - -For events that are fired frequently, it can be useful to throttle or debounce -them to avoid network latency and improve performance. These actions both take a -single argument which specifies the delay time in milliseconds. - -### throttle - -The `.throttle` action limits the number of times an event is processed within a -a given time period. It is useful for `on_scroll` and `on_mouse_move` events which are -fired very frequently, causing lag when handling them in the backend. - -```md alert warning -# Throttled events are discarded. - -There is no eventual delivery of any event that is triggered while the throttle -period is active. Throttle is not appropriate for events when the final payload -contains data that must be processed, like `on_change`. -``` - -In the following example, the `on_scroll` event is throttled to only fire every half second. - -```python demo exec -class ThrottleState(rx.State): - last_scroll: datetime.datetime | None - - @rx.event - def handle_scroll(self): - self.last_scroll = datetime.datetime.now(datetime.timezone.utc) - -def scroll_box(): - return rx.scroll_area( - rx.heading("Scroll Me"), - *[rx.text(f"Item {i}") for i in range(100)], - height="75px", - width="50%", - border=f"1px solid {rx.color('accent', 5)}", - on_scroll=ThrottleState.handle_scroll.throttle(500), - ) - -def throttle_example(): - return ( - scroll_box(), - rx.text( - f"Last Scroll Event: ", - rx.moment(ThrottleState.last_scroll, format="HH:mm:ss.SSS"), - ), - ) -``` - -```md alert info -# Event Actions are Chainable - -Event actions can be chained together to create more complex event handling -behavior. For example, you can throttle an event and prevent its default -behavior in the same event handler: `on_click=MyState.handle_click.throttle(500).prevent_default`. -``` - -### debounce - -The `.debounce` action delays the processing of an event until the specified -timeout occurs. If another event is triggered during the timeout, the timer is -reset and the original event is discarded. - -Debounce is useful for handling the final result of a series of events, such as -moving a slider. - -```md alert warning -# Debounced events are discarded. - -When a new event is triggered during the debounce period, the original event is -discarded. Debounce is not appropriate for events where each payload contains -unique data that must be processed, like `on_key_down`. -``` - -In the following example, the slider's `on_change` handler, `update_value`, is -only triggered on the backend when the slider value has not changed for half a -second. - -```python demo exec -class DebounceState(rx.State): - settled_value: int = 50 - - @rx.event - def update_value(self, value: list[int | float]): - self.settled_value = value[0] - - -def debounced_slider(): - return rx.slider( - key=rx.State.router.session.session_id, - default_value=[DebounceState.settled_value], - on_change=DebounceState.update_value.debounce(500), - width="100%", - ) - -def debounce_example(): - return rx.vstack( - debounced_slider(), - rx.text(f"Settled Value: {DebounceState.settled_value}"), - ) -``` - -```md alert info -# Why set key on the slider? - -Setting `key` to the `session_id` with a dynamic `default_value` ensures that -when the page is refreshed, the component will be re-rendered to reflect the -updated default_value from the state. - -Without the `key` set, the slider would always display the original -`settled_value` after a page reload, instead of its current value. -``` - -## Temporal Events - -_Added in [v0.6.6](https://github.com/reflex-dev/reflex/releases/tag/v0.6.6)_ - -### temporal - -The `.temporal` action prevents events from being queued when the backend is down. -This is useful for non-critical events where you do not want them to pile up if there is -a temporary connection issue. - -```md alert warning -# Temporal events are discarded when the backend is down. - -When the backend is unavailable, events with the `.temporal` action will be -discarded rather than queued for later processing. Only use this for events -where it is acceptable to lose some interactions during connection issues. -``` - -In the following example, the `rx.moment` component with `interval` and `on_change` uses `.temporal` to -prevent periodic updates from being queued when the backend is down: - -```python demo exec -class TemporalState(rx.State): - current_time: str = "" - - @rx.event - def update_time(self): - self.current_time = datetime.datetime.now().strftime("%H:%M:%S") - -def temporal_example(): - return rx.vstack( - rx.heading("Current Time:"), - rx.heading(TemporalState.current_time), - rx.moment( - interval=1000, - on_change=TemporalState.update_time.temporal, - ), - rx.text("Time updates will not be queued if the backend is down."), - ) -``` diff --git a/docs/events/event_arguments.md b/docs/events/event_arguments.md deleted file mode 100644 index 42621f5856..0000000000 --- a/docs/events/event_arguments.md +++ /dev/null @@ -1,123 +0,0 @@ -```python exec -import reflex as rx -``` - -# Event Arguments - -The event handler signature needs to match the event trigger definition argument count. If the event handler takes two arguments, the event trigger must be able to provide two arguments. - -Here is a simple example: - -```python demo exec - -class EventArgStateSlider(rx.State): - value: int = 50 - - @rx.event - def set_end(self, value: list[int | float]): - self.value = value[0] - - -def slider_max_min_step(): - return rx.vstack( - rx.heading(EventArgStateSlider.value), - rx.slider( - default_value=40, - on_value_commit=EventArgStateSlider.set_end, - ), - width="100%", - ) - -``` - -The event trigger here is `on_value_commit` and it is called when the value changes at the end of an interaction. This event trigger passes one argument, which is the value of the slider. The event handler which is triggered by the event trigger must therefore take one argument, which is `value` here. - -Here is a form example: - -```python demo exec - -class EventArgState(rx.State): - form_data: dict = {} - - @rx.event - def handle_submit(self, form_data: dict): - """Handle the form submit.""" - self.form_data = form_data - - -def event_arg_example(): - return rx.vstack( - rx.form( - rx.vstack( - rx.input( - placeholder="Name", - name="name", - ), - rx.checkbox("Checked", name="check"), - rx.button("Submit", type="submit"), - ), - on_submit=EventArgState.handle_submit, - reset_on_submit=True, - ), - rx.divider(), - rx.heading("Results"), - rx.text(EventArgState.form_data.to_string()), - ) -``` - -In this example the event trigger is the `on_submit` event of the form. The event handler is `handle_submit`. The `on_submit` event trigger passes one argument, the form data as a dictionary, to the `handle_submit` event handler. The `handle_submit` event handler must take one argument because the `on_submit` event trigger passes one argument. - -When the number of args accepted by an EventHandler differs from that provided by the event trigger, an `EventHandlerArgMismatch` error will be raised. - -## Pass Additional Arguments to Event Handlers - -In some use cases, you want to pass additional arguments to your event handlers. To do this you can bind an event trigger to a lambda, which can call your event handler with the arguments you want. - -Try typing a color in an input below and clicking away from it to change the color of the input. - -```python demo exec -class ArgState(rx.State): - colors: list[str] = ["rgba(245,168,152)", "MediumSeaGreen", "#DEADE3"] - - @rx.event - def change_color(self, color: str, index: int): - self.colors[index] = color - -def event_arguments_example(): - return rx.hstack( - rx.input(default_value=ArgState.colors[0], on_blur=lambda c: ArgState.change_color(c, 0), bg=ArgState.colors[0]), - rx.input(default_value=ArgState.colors[1], on_blur=lambda c: ArgState.change_color(c, 1), bg=ArgState.colors[1]), - rx.input(default_value=ArgState.colors[2], on_blur=lambda c: ArgState.change_color(c, 2), bg=ArgState.colors[2]), - ) - -``` - -In this case, in we want to pass two arguments to the event handler `change_color`, the color and the index of the color to change. - -The `on_blur` event trigger passes the text of the input as an argument to the lambda, and the lambda calls the `change_color` event handler with the text and the index of the input. - -When the number of args accepted by a lambda differs from that provided by the event trigger, an `EventFnArgMismatch` error will be raised. - -```md alert warning -# Event Handler Parameters should provide type annotations. - -Like state vars, be sure to provide the right type annotations for the parameters in an event handler. -``` - -## Events with Partial Arguments (Advanced) - -_Added in v0.5.0_ - -Event arguments in Reflex are passed positionally. Any additional arguments not -passed to an EventHandler will be filled in by the event trigger when it is -fired. - -The following two code samples are equivalent: - -```python -# Use a lambda to pass event trigger args to the EventHandler. -rx.text(on_blur=lambda v: MyState.handle_update("field1", v)) - -# Create a partial that passes event trigger args for any args not provided to the EventHandler. -rx.text(on_blur=MyState.handle_update("field1")) -``` diff --git a/docs/events/events_overview.md b/docs/events/events_overview.md deleted file mode 100644 index e67aa3bf5e..0000000000 --- a/docs/events/events_overview.md +++ /dev/null @@ -1,52 +0,0 @@ -```python exec -import reflex as rx - -from pcweb.pages.docs.library import library -``` - -# Events Overview - -Events are composed of two parts: Event Triggers and Event Handlers. - -- **Events Handlers** are how the State of a Reflex application is updated. They are triggered by user interactions with the UI, such as clicking a button or hovering over an element. Events can also be triggered by the page loading or by other events. - -- **Event triggers** are component props that create an event to be sent to an event handler. -Each component supports a set of events triggers. They are described in each [component's documentation]({library.path}) in the event trigger section. - - -## Example -Lets take a look at an example below. Try mousing over the heading to change the word. - -```python demo exec -class WordCycleState(rx.State): - # The words to cycle through. - text: list[str] = ["Welcome", "to", "Reflex", "!"] - - # The index of the current word. - index: int = 0 - - @rx.event - def next_word(self): - self.index = (self.index + 1) % len(self.text) - - @rx.var - def get_text(self) -> str: - return self.text[self.index] - -def event_triggers_example(): - return rx.heading( - WordCycleState.get_text, - on_mouse_over=WordCycleState.next_word, - color="green", - ) - -``` - -In this example, the heading component has the **event trigger**, `on_mouse_over`. -Whenever the user hovers over the heading, the `next_word` **event handler** will be called to cycle the word. Once the handler returns, the UI will be updated to reflect the new state. - -Adding the `@rx.event` decorator above the event handler is strongly recommended. This decorator enables proper static type checking, which ensures event handlers receive the correct number and types of arguments. - -# What's in this section? - -In the event section of the documentation, you will explore the different types of events supported by Reflex, along with the different ways to call them. diff --git a/docs/events/page_load_events.md b/docs/events/page_load_events.md deleted file mode 100644 index 3c4e9b6867..0000000000 --- a/docs/events/page_load_events.md +++ /dev/null @@ -1,40 +0,0 @@ -```python exec -import reflex as rx -``` - -# Page Load Events - -You can also specify a function to run when the page loads. This can be useful for fetching data once vs on every render or state change. -In this example, we fetch data when the page loads: - -```python -class State(rx.State): - data: Dict[str, Any] - - @rx.event - def get_data(self): - # Fetch data - self.data = fetch_data() - -@rx.page(on_load=State.get_data) -def index(): - return rx.text('A Beautiful App') -``` - -Another example would be checking if the user is authenticated when the page loads. If the user is not authenticated, we redirect them to the login page. If they are authenticated, we don't do anything, letting them access the page. This `on_load` event would be placed on every page that requires authentication to access. - -```python -class State(rx.State): - authenticated: bool - - @rx.event - def check_auth(self): - # Check if user is authenticated - self.authenticated = check_auth() - if not self.authenticated: - return rx.redirect('/login') - -@rx.page(on_load=State.check_auth) -def index(): - return rx.text('A Beautiful App') -``` diff --git a/docs/events/setters.md b/docs/events/setters.md deleted file mode 100644 index 243926b0e7..0000000000 --- a/docs/events/setters.md +++ /dev/null @@ -1,57 +0,0 @@ -```python exec -import reflex as rx -``` - -# Setters - -Every base var has a built-in event handler to set it's value for convenience, called `set_VARNAME`. - -Say you wanted to change the value of the select component. You could write your own event handler to do this: - -```python demo exec - -options: list[str] = ["1", "2", "3", "4"] -class SetterState1(rx.State): - selected: str = "1" - - @rx.event - def change(self, value): - self.selected = value - - -def code_setter(): - return rx.vstack( - rx.badge(SetterState1.selected, color_scheme="green"), - rx.select( - options, - on_change= lambda value: SetterState1.change(value), - ) - ) - -``` - -Or you could could use a built-in setter for conciseness. - -```python demo exec - -options: list[str] = ["1", "2", "3", "4"] -class SetterState2(rx.State): - selected: str = "1" - - @rx.event - def set_selected(self, selected: str): - self.selected = selected - -def code_setter_2(): - return rx.vstack( - rx.badge(SetterState2.selected, color_scheme="green"), - rx.select( - options, - on_change= SetterState2.set_selected, - ) - ) -``` - -In this example, the setter for `selected` is `set_selected`. Both of these examples are equivalent. - -Setters are a great way to make your code more concise. But if you want to do something more complicated, you can always write your own function in the state. diff --git a/docs/events/special_events.md b/docs/events/special_events.md deleted file mode 100644 index 0b6ae665e7..0000000000 --- a/docs/events/special_events.md +++ /dev/null @@ -1,28 +0,0 @@ -```python exec -import reflex as rx - -from pcweb.pages.docs import api_reference -``` - -# Special Events - -Reflex also has built-in special events can be found in the [reference]({api_reference.special_events.path}). - -For example, an event handler can trigger an alert on the browser. - -```python demo exec -class SpecialEventsState(rx.State): - @rx.event - def alert(self): - return rx.window_alert("Hello World!") - -def special_events_example(): - return rx.button("Alert", on_click=SpecialEventsState.alert) -``` - -Special events can also be triggered directly in the UI by attaching them to an event trigger. - -```python -def special_events_example(): - return rx.button("Alert", on_click=rx.window_alert("Hello World!")) -``` diff --git a/docs/events/yield_events.md b/docs/events/yield_events.md deleted file mode 100644 index 78bdb1c02c..0000000000 --- a/docs/events/yield_events.md +++ /dev/null @@ -1,107 +0,0 @@ -```python exec -import reflex as rx - -``` - -# Yielding Updates - -A regular event handler will send a `StateUpdate` when it has finished running. This works fine for basic event, but sometimes we need more complex logic. To update the UI multiple times in an event handler, we can `yield` when we want to send an update. - -To do so, we can use the Python keyword `yield`. For every yield inside the function, a `StateUpdate` will be sent to the frontend with the changes up to this point in the execution of the event handler. - -This example below shows how to yield 100 updates to the UI. - -```python demo exec - -class MultiUpdateState(rx.State): - count: int = 0 - - @rx.event - def timed_update(self): - for i in range(100): - self.count += 1 - yield - - -def multi_update(): - return rx.vstack( - rx.text(MultiUpdateState.count), - rx.button("Start", on_click=MultiUpdateState.timed_update) -) - -``` - -Here is another example of yielding multiple updates with a loading icon. - -```python demo exec - -import asyncio - -class ProgressExampleState(rx.State): - count: int = 0 - show_progress: bool = False - - @rx.event - async def increment(self): - self.show_progress = True - yield - # Think really hard. - await asyncio.sleep(0.5) - self.count += 1 - self.show_progress = False - -def progress_example(): - return rx.button( - ProgressExampleState.count, - on_click=ProgressExampleState.increment, - loading=ProgressExampleState.show_progress, - ) - -``` - -```md video https://youtube.com/embed/ITOZkzjtjUA?start=6463&end=6835 -# Video: Asyncio with Yield -``` - -## Yielding Other Events - -Events can also yield other events. This is useful when you want to chain events together. To do this, you can yield the event handler function itself. - -```md alert -# Reference other Event Handler via class - -When chaining another event handler with `yield`, access it via the state class, not `self`. -``` - -```python demo exec - -import asyncio - -class YieldEventsState(rx.State): - count: int = 0 - show_progress: bool = False - - @rx.event - async def add_five(self): - self.show_progress = True - yield - # Think really hard. - await asyncio.sleep(1) - self.count += 5 - self.show_progress = False - - @rx.event - async def increment(self): - yield YieldEventsState.add_five - yield YieldEventsState.add_five - yield YieldEventsState.add_five - - -def multiple_yield_example(): - return rx.button( - YieldEventsState.count, - on_click=YieldEventsState.increment, - loading=YieldEventsState.show_progress, - ) - -``` diff --git a/docs/getting_started/__init__.py b/docs/getting_started/__init__.py new file mode 100644 index 0000000000..0cdc5e99ec --- /dev/null +++ b/docs/getting_started/__init__.py @@ -0,0 +1 @@ +"""Getting started docs.""" diff --git a/docs/getting_started/basics.md b/docs/getting_started/basics.md deleted file mode 100644 index baf4d69b42..0000000000 --- a/docs/getting_started/basics.md +++ /dev/null @@ -1,406 +0,0 @@ -```python exec -from pcweb.pages.docs import components, getting_started -from pcweb.pages.docs.library import library -from pcweb.pages.docs.custom_components import custom_components -from pcweb.pages import docs -import reflex as rx -``` - -# Reflex Basics - -This page gives an introduction to the most common concepts that you will use to build Reflex apps. - -```md section -# You will learn how to: - -- Create and nest components -- Customize and style components -- Distinguish between compile-time and runtime -- Display data that changes over time -- Respond to events and update the screen -- Render conditions and lists -- Create pages and navigate between them -``` - -[Install]({docs.getting_started.installation.path}) `reflex` using pip. - -```bash -pip install reflex -``` - -Import the `reflex` library to get started. - -```python -import reflex as rx -``` - -## Creating and nesting components - -[Components]({docs.ui.overview.path}) are the building blocks for your app's user interface (UI). They are the visual elements that make up your app, like buttons, text, and images. Reflex has a wide selection of [built-in components]({library.path}) to get you started quickly. - -Components are created using functions that return a component object. - -```python demo exec -def my_button(): - return rx.button("Click Me") -``` - -Components can be nested inside each other to create complex UIs. - -To nest components as children, pass them as positional arguments to the parent component. In the example below, the `rx.text` and `my_button` components are children of the `rx.box` component. - -```python demo exec -def my_page(): - return rx.box( - rx.text("This is a page"), - # Reference components defined in other functions. - my_button() - ) -``` - -You can also use any base HTML element through the [`rx.el`]({docs.library.other.html.path}) namespace. This allows you to use standard HTML elements directly in your Reflex app when you need more control or when a specific component isn't available in the Reflex component library. - -```python demo exec -def my_div(): - return rx.el.div( - rx.el.p("Use base html!"), - ) -``` - -If you need a component not provided by Reflex, you can check the [3rd party ecosystem]({custom_components.path}) or [wrap your own React component]({docs.wrapping_react.library_and_tags.path}). - -## Customizing and styling components - -Components can be customized using [props]({docs.components.props.path}), which are passed in as keyword arguments to the component function. - -Each component has props that are specific to that component. Check the docs for the component you are using to see what props are available. - -```python demo exec -def half_filled_progress(): - return rx.progress(value=50) -``` - -In addition to component-specific props, components can also be styled using CSS properties passed as props. - -```python demo exec -def round_button(): - return rx.button("Click Me", border_radius="15px", font_size="18px") -``` - -```md alert -Use the `snake_case` version of the CSS property name as the prop name. -``` - -See the [styling guide]({docs.styling.overview.path}) for more information on how to style components - -In summary, components are made up of children and props. - -```md definition -# Children - -- Text or other Reflex components nested inside a component. -- Passed as **positional arguments**. - -# Props - -- Attributes that affect the behavior and appearance of a component. -- Passed as **keyword arguments**. -``` - -## Displaying data that changes over time - -Apps need to store and display data that changes over time. Reflex handles this through [State]({docs.state.overview.path}), which is a Python class that stores variables that can change when the app is running, as well as the functions that can change those variables. - -To define a state class, subclass `rx.State` and define fields that store the state of your app. The state variables ([vars]({docs.vars.base_vars.path})) should have a type annotation, and can be initialized with a default value. - -```python -class MyState(rx.State): - count: int = 0 -``` - -### Referencing state vars in components - -To reference a state var in a component, you can pass it as a child or prop. The component will automatically update when the state changes. - -Vars are referenced through class attributes on your state class. For example, to reference the `count` var in a component, use `MyState.count`. - -```python demo exec -class MyState(rx.State): - count: int = 0 - color: str = "red" - -def counter(): - return rx.hstack( - # The heading `color` prop is set to the `color` var in MyState. - rx.heading("Count: ", color=MyState.color), - # The `count` var in `MyState` is passed as a child to the heading component. - rx.heading(MyState.count), - ) -``` - -Vars can be referenced in multiple components, and will automatically update when the state changes. - -## Responding to events and updating the screen - -So far, we've defined state vars but we haven't shown how to change them. All state changes are handled through functions in the state class, called [event handlers]({docs.events.events_overview.path}). - -```md alert -Event handlers are the ONLY way to change state in Reflex. -``` - -Components have special props, such as `on_click`, called event triggers that can be used to make components interactive. Event triggers connect components to event handlers, which update the state. - -```python demo exec -class CounterState(rx.State): - count: int = 0 - - @rx.event - def increment(self): - self.count += 1 - -def counter_increment(): - return rx.hstack( - rx.heading(CounterState.count), - rx.button("Increment", on_click=CounterState.increment) - ) -``` - -When an event trigger is activated, the event handler is called, which updates the state. The UI is automatically re-rendered to reflect the new state. - - -```md alert info -# What is the `@rx.event` decorator? -Adding the `@rx.event` decorator above the event handler is strongly recommended. This decorator enables proper static type checking, which ensures event handlers receive the correct number and types of arguments. This was introduced in Reflex version 0.6.5. -``` - -### Event handlers with arguments - -Event handlers can also take in arguments. For example, the `increment` event handler can take an argument to increment the count by a specific amount. - -```python demo exec -class CounterState2(rx.State): - count: int = 0 - - @rx.event - def increment(self, amount: int): - self.count += amount - -def counter_variable(): - return rx.hstack( - rx.heading(CounterState2.count), - rx.button("Increment by 1", on_click=lambda: CounterState2.increment(1)), - rx.button("Increment by 5", on_click=lambda: CounterState2.increment(5)), - ) -``` - -The `on_click` event trigger doesn't pass any arguments here, but some event triggers do. For example, the `on_blur` event trigger passes the text of an input as an argument to the event handler. - -```python demo exec -class TextState(rx.State): - text: str = "" - - @rx.event - def update_text(self, new_text: str): - self.text = new_text - -def text_input(): - return rx.vstack( - rx.heading(TextState.text), - rx.input(default_value=TextState.text, on_blur=TextState.update_text), - ) -``` - -```md alert -Make sure that the event handler has the same number of arguments as the event trigger, or an error will be raised. -``` - -## Compile-time vs. runtime (IMPORTANT) - -Before we dive deeper into state, it's important to understand the difference between compile-time and runtime in Reflex. - -When you run your app, the frontend gets compiled to Javascript code that runs in the browser (compile-time). The backend stays in Python and runs on the server during the lifetime of the app (runtime). - -### When can you not use pure Python? - -We cannot compile arbitrary Python code, only the components that you define. What this means importantly is that you cannot use arbitrary Python operations and functions on state vars in components. - -However, since any event handlers in your state are on the backend, you **can use any Python code or library** within your state. - -### Examples that work - -Within an event handler, use any Python code or library. - -```python demo exec -def check_even(num: int): - return num % 2 == 0 - -class MyState3(rx.State): - count: int = 0 - text: str = "even" - - @rx.event - def increment(self): - # Use any Python code within state. - # Even reference functions defined outside the state. - if check_even(self.count): - self.text = "even" - else: - self.text = "odd" - self.count += 1 - -def count_and_check(): - return rx.box( - rx.heading(MyState3.text), - rx.button("Increment", on_click=MyState3.increment) - ) -``` - -Use any Python function within components, as long as it is defined at compile time (i.e. does not reference any state var) - -```python demo exec -def show_numbers(): - return rx.vstack( - *[ - rx.hstack(i, check_even(i)) - for i in range(10) - ] - ) -``` - -### Examples that don't work - -You cannot do an `if` statement on vars in components, since the value is not known at compile time. - -```python -class BadState(rx.State): - count: int = 0 - -def count_if_even(): - return rx.box( - rx.heading("Count: "), - # This will raise a compile error, as BadState.count is a var and not known at compile time. - rx.text(BadState.count if BadState.count % 2 == 0 else "Odd"), - # Using an if statement with a var as a prop will NOT work either. - rx.text("hello", color="red" if BadState.count % 2 == 0 else "blue"), - ) -``` - -You cannot do a `for` loop over a list of vars. - -```python -class BadState(rx.State): - items: list[str] = ["Apple", "Banana", "Cherry"] - -def loop_over_list(): - return rx.box( - # This will raise a compile error, as BadState.items is a list and not known at compile time. - *[rx.text(item) for item in BadState.items] - ) -``` - -You cannot do arbitrary Python operations on state vars in components. - -```python -class BadTextState(rx.State): - text: str = "Hello world" - -def format_text(): - return rx.box( - # Python operations such as `len` will not work on state vars. - rx.text(len(BadTextState.text)), - ) -``` - -In the next sections, we will show how to handle these cases. - -## Conditional rendering - -As mentioned above, you cannot use Python `if/else` statements with state vars in components. Instead, use the [`rx.cond`]({docs.components.conditional_rendering.path}) function to conditionally render components. - -```python demo exec -class LoginState(rx.State): - logged_in: bool = False - - @rx.event - def toggle_login(self): - self.logged_in = not self.logged_in - -def show_login(): - return rx.box( - rx.cond( - LoginState.logged_in, - rx.heading("Logged In"), - rx.heading("Not Logged In"), - ), - rx.button("Toggle Login", on_click=LoginState.toggle_login) - ) -``` - -## Rendering lists - -To iterate over a var that is a list, use the [`rx.foreach`]({docs.components.rendering_iterables.path}) function to render a list of components. - -Pass the list var and a function that returns a component as arguments to `rx.foreach`. - -```python demo exec -class ListState(rx.State): - items: list[str] = ["Apple", "Banana", "Cherry"] - -def render_item(item: rx.Var[str]): - """Render a single item.""" - # Note that item here is a Var, not a str! - return rx.list.item(item) - -def show_fruits(): - return rx.box( - rx.foreach(ListState.items, render_item), - ) -``` - -The function that renders each item takes in a `Var`, since this will get compiled up front. - -## Var Operations - -You can't use arbitrary Python operations on state vars in components, but Reflex has [var operations]({docs.vars.var_operations.path}) that you can use to manipulate state vars. - -For example, to check if a var is even, you can use the `%` and `==` var operations. - -```python demo exec -class CountEvenState(rx.State): - count: int = 0 - - @rx.event - def increment(self): - self.count += 1 - -def count_if_even(): - return rx.box( - rx.heading("Count: "), - rx.cond( - # Here we use the `%` and `==` var operations to check if the count is even. - CountEvenState.count % 2 == 0, - rx.text("Even"), - rx.text("Odd"), - ), - rx.button("Increment", on_click=CountEvenState.increment), - ) -``` - -## App and Pages - -Reflex apps are created by instantiating the `rx.App` class. Pages are linked to specific URL routes, and are created by defining a function that returns a component. - -```python -def index(): - return rx.text('Root Page') - -rx.app = rx.App() -app.add_page(index, route="/") -``` - -## Next Steps - -Now that you have a basic understanding of how Reflex works, the next step is to start coding your own apps. Try one of the following tutorials: - -- [Dashboard Tutorial]({getting_started.dashboard_tutorial.path}) -- [Chatapp Tutorial]({getting_started.chatapp_tutorial.path}) diff --git a/docs/getting_started/chat_tutorial_style.py b/docs/getting_started/chat_tutorial_style.py index fe8ff74d61..758a089d98 100644 --- a/docs/getting_started/chat_tutorial_style.py +++ b/docs/getting_started/chat_tutorial_style.py @@ -1,4 +1,5 @@ -# Common styles for questions and answers. +"""Common styles for questions and answers.""" + import reflex as rx shadow = "rgba(0, 0, 0, 0.15) 0px 2px 8px" diff --git a/docs/getting_started/chat_tutorial_utils.py b/docs/getting_started/chat_tutorial_utils.py index 9b3f27988c..265091468d 100644 --- a/docs/getting_started/chat_tutorial_utils.py +++ b/docs/getting_started/chat_tutorial_utils.py @@ -1,14 +1,18 @@ +"""Utility classes for the chat app tutorial.""" + from __future__ import annotations import os -import openai +import openai # pyright: ignore[reportMissingImports] import reflex as rx OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") class ChatappState(rx.State): + """State for the chat app tutorial.""" + # The current question being asked. question: str @@ -16,23 +20,29 @@ class ChatappState(rx.State): chat_history: list[tuple[str, str]] def set_question(self, q: str): + """Set the current question.""" self.question = q def set_question1(self, q: str): + """Set the current question (variant 1).""" self.question = q def set_question2(self, q: str): + """Set the current question (variant 2).""" self.question = q def set_question3(self, q: str): + """Set the current question (variant 3).""" self.question = q def answer(self) -> None: + """Answer the question with a static response.""" # Our chatbot is not very smart right now... answer = "I don't know!" self.chat_history.append((self.question, answer)) def answer2(self) -> None: + """Answer the question and clear the input.""" # Our chatbot is not very smart right now... answer = "I don't know!" self.chat_history.append((self.question, answer)) @@ -40,6 +50,7 @@ def answer2(self) -> None: self.question = "" async def answer3(self): + """Answer with a streaming static response.""" import asyncio # Our chatbot is not very smart right now... @@ -57,6 +68,7 @@ async def answer3(self): yield async def answer4(self): + """Answer using the OpenAI API with streaming.""" # Our chatbot has some brains now! client = openai.AsyncOpenAI(api_key=OPENAI_API_KEY) session = await client.chat.completions.create( diff --git a/docs/getting_started/chatapp_tutorial.md b/docs/getting_started/chatapp_tutorial.md index fc2c0e36fd..6639456e06 100644 --- a/docs/getting_started/chatapp_tutorial.md +++ b/docs/getting_started/chatapp_tutorial.md @@ -4,15 +4,6 @@ import os import reflex as rx import openai -from pcweb.constants import CHAT_APP_URL -from pcweb import constants -from pcweb.pages.docs import components -from pcweb.pages.docs import styling -from pcweb.pages.docs import library -from pcweb.pages.docs import events -from pcweb.pages.docs import state -from pcweb.pages.docs import hosting - from docs.getting_started import chat_tutorial_style as style from docs.getting_started.chat_tutorial_utils import ChatappState @@ -26,7 +17,7 @@ if "OPENAI_API_KEY" not in os.environ: This tutorial will walk you through building an AI chat app with Reflex. This app is fairly complex, but don't worry - we'll break it down into small steps. -You can find the full source code for this app [here]({CHAT_APP_URL}). +You can find the full source code for this app [here](https://github.com/reflex-dev/reflex-chat). ### What You'll Learn @@ -37,9 +28,6 @@ In this tutorial you'll learn how to: 3. Use state to add interactivity to your app. 4. Deploy your app to share with others. - - - ## Setting up Your Project ```md video https://youtube.com/embed/ITOZkzjtjUA?start=175&end=445 @@ -53,7 +41,7 @@ We will start by creating a new project and setting up our development environme ~ $ cd chatapp ``` -Next, we will create a virtual environment for our project. This is optional, but recommended. In this example, we will use [venv]({constants.VENV_URL}) to create our virtual environment. +Next, we will create a virtual environment for our project. This is optional, but recommended. In this example, we will use [venv](https://docs.python.org/3/library/venv.html) to create our virtual environment. ```bash chatapp $ python3 -m venv venv @@ -64,7 +52,6 @@ Now, we will install Reflex and create a new project. This will create a new dir > **Note:** When prompted to select a template, choose option 0 for a blank project. - ```bash chatapp $ pip install reflex chatapp $ reflex init @@ -77,6 +64,7 @@ assets chatapp rxconfig.py venv ```python eval rx.box(height="20px") ``` + You can run the template app to make sure everything is working. ```bash @@ -97,12 +85,9 @@ Reflex also starts the backend server which handles all the state management and Now that we have our project set up, in the next section we will start building our app! - - - ## Basic Frontend -Let's start with defining the frontend for our chat app. In Reflex, the frontend can be broken down into independent, reusable components. See the [components docs]({components.props.path}) for more information. +Let's start with defining the frontend for our chat app. In Reflex, the frontend can be broken down into independent, reusable components. See the [components docs](/docs/components/props) for more information. ### Display A Question And Answer @@ -150,7 +135,7 @@ app.add_page(index) Components can be nested inside each other to create complex layouts. Here we create a parent container that contains two boxes for the question and answer. -We also add some basic styling to the components. Components take in keyword arguments, called [props]({components.props.path}), that modify the appearance and functionality of the component. We use the `text_align` prop to align the text to the left and right. +We also add some basic styling to the components. Components take in keyword arguments, called [props](/docs/components/props), that modify the appearance and functionality of the component. We use the `text_align` prop to align the text to the left and right. ### Reusing Components @@ -212,7 +197,7 @@ def index() -> rx.Component: ### Chat Input -Now we want a way for the user to input a question. For this, we will use the [input]({library.forms.input.path}) component to have the user add text and a [button]({library.forms.button.path}) component to submit the question. +Now we want a way for the user to input a question. For this, we will use the [input](/docs/library/forms/input) component to have the user add text and a [button](/docs/library/forms/button) component to submit the question. ```python exec def action_bar() -> rx.Component: @@ -245,7 +230,7 @@ def index() -> rx.Component: ### Styling -Let's add some styling to the app. More information on styling can be found in the [styling docs]({styling.overview.path}). To keep our code clean, we will move the styling to a separate file `chatapp/style.py`. +Let's add some styling to the app. More information on styling can be found in the [styling docs](/docs/styling/overview). To keep our code clean, we will move the styling to a separate file `chatapp/style.py`. ```python # style.py @@ -360,14 +345,9 @@ app.add_page(index) The app is looking good, but it's not very useful yet! In the next section, we will add some functionality to the app. - - - - - ## State -Now let’s make the chat app interactive by adding state. The state is where we define all the variables that can change in the app and all the functions that can modify them. You can learn more about state in the [state docs]({state.overview.path}). +Now let’s make the chat app interactive by adding state. The state is where we define all the variables that can change in the app and all the functions that can modify them. You can learn more about state in the [state docs](/docs/state/overview). ### Defining State @@ -456,9 +436,9 @@ def action_bar() -> rx.Component: ) ``` -Normal Python `for` loops don't work for iterating over state vars because these values can change and aren't known at compile time. Instead, we use the [foreach]({library.dynamic_rendering.foreach.path}) component to iterate over the chat history. +Normal Python `for` loops don't work for iterating over state vars because these values can change and aren't known at compile time. Instead, we use the [foreach](/docs/library/dynamic-rendering/foreach) component to iterate over the chat history. -We also bind the input's `on_change` event to the `set_question` event handler, which will update the `question` state var while the user types in the input. We bind the button's `on_click` event to the `answer` event handler, which will process the question and add the answer to the chat history. The `set_question` event handler is a built-in implicitly defined event handler. Every base var has one. Learn more in the [events docs]({events.setters.path}) under the Setters section. +We also bind the input's `on_change` event to the `set_question` event handler, which will update the `question` state var while the user types in the input. We bind the button's `on_click` event to the `answer` event handler, which will process the question and add the answer to the chat history. The `set_question` event handler is a built-in implicitly defined event handler. Every base var has one. Learn more in the [events docs](/docs/events/setters) under the Setters section. ### Clearing the Input @@ -509,7 +489,7 @@ def answer(self): ### Streaming Text -Normally state updates are sent to the frontend when an event handler returns. However, we want to stream the text from the chatbot as it is generated. We can do this by yielding from the event handler. See the [yield events docs]({events.yield_events.path}) for more info. +Normally state updates are sent to the frontend when an event handler returns. However, we want to stream the text from the chatbot as it is generated. We can do this by yielding from the event handler. See the [yield events docs](/docs/events/yield_events) for more info. ```python exec def action_bar3() -> rx.Component: @@ -555,8 +535,6 @@ async def answer(self): In the next section, we will finish our chatbot by adding AI! - - ## Final App We will use OpenAI's API to give our chatbot some intelligence. @@ -565,6 +543,7 @@ We will use OpenAI's API to give our chatbot some intelligence. First, ensure you have an active OpenAI subscription. Next, install the latest openai package: + ```bash pip install --upgrade openai ``` @@ -593,7 +572,6 @@ Making your chatbot intelligent requires connecting to a language model API. Thi 2. Next, when a prompt is ready, the user can choose to submit it by clicking the `Ask` button which in turn triggers the `State.answer` method inside our `state.py` file. 3. Finally, if the method is triggered, the `prompt` is sent via a request to OpenAI client and returns an answer that we can trim and use to update the chat history! - ```python # chatapp.py def action_bar() -> rx.Component: @@ -723,7 +701,6 @@ app = rx.App() app.add_page(index) ``` - The `state.py` file: ```python @@ -764,7 +741,6 @@ class State(rx.State): yield ``` - The `style.py` file: ```python @@ -797,11 +773,10 @@ input_style = dict(border_width="1px", padding="0.5em", box_shadow=shadow, width button_style = dict(background_color=rx.color("accent", 10), box_shadow=shadow) ``` - ### Next Steps Congratulations! You have built your first chatbot. From here, you can read through the rest of the documentations to learn about Reflex in more detail. The best way to learn is to build something, so try to build your own app using this as a starting point! ### One More Thing -With our hosting service, you can deploy this app with a single command within minutes. Check out our [Hosting Quick Start]({hosting.deploy_quick_start.path}). +With our hosting service, you can deploy this app with a single command within minutes. Check out our [Hosting Quick Start](https://reflex.dev/docs/hosting/deploy-quick-start/). diff --git a/docs/getting_started/dashboard_tutorial.md b/docs/getting_started/dashboard_tutorial.md deleted file mode 100644 index d55c45009b..0000000000 --- a/docs/getting_started/dashboard_tutorial.md +++ /dev/null @@ -1,1847 +0,0 @@ -```python exec -import reflex as rx -from pcweb.pages import docs -``` - -# Tutorial: Data Dashboard - -During this tutorial you will build a small data dashboard, where you can input data and it will be rendered in table and a graph. This tutorial does not assume any existing Reflex knowledge, but we do recommend checking out the quick [Basics Guide]({docs.getting_started.basics.path}) first. - -The techniques you’ll learn in the tutorial are fundamental to building any Reflex app, and fully understanding it will give you a deep understanding of Reflex. - - -This tutorial is divided into several sections: - -- **Setup for the Tutorial**: A starting point to follow the tutorial -- **Overview**: The fundamentals of Reflex UI (components and props) -- **Showing Dynamic Data**: How to use State to render data that will change in your app. -- **Add Data to your App**: Using a Form to let a user add data to your app and introduce event handlers. -- **Plotting Data in a Graph**: How to use Reflex's graphing components. -- **Final Cleanup and Conclusion**: How to further customize your app and add some extra styling to it. - -### What are you building? - -In this tutorial, you are building an interactive data dashboard with Reflex. - -You can see what the finished app and code will look like here: - - -```python exec -import dataclasses -from collections import Counter - -@dataclasses.dataclass -class User: - """The user model.""" - - name: str - email: str - gender: str - -class State5(rx.State): - users: list[User] = [ - User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), - User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), - ] - users_for_graph: list[dict] = [] - - def add_user(self, form_data: dict): - self.users.append(User(**form_data)) - self.transform_data() - - return rx.toast.info( - f"User {form_data['name']} has been added.", - position="bottom-right", - ) - - def transform_data(self): - """Transform user gender group data into a format suitable for visualization in graphs.""" - # Count users of each gender group - gender_counts = Counter(user.gender for user in self.users) - - # Transform into list of dict so it can be used in the graph - self.users_for_graph = [ - { - "name": gender_group, - "value": count - } - for gender_group, count in gender_counts.items() - ] - - -def show_user5(user: User): - """Show a user in a table row.""" - return rx.table.row( - rx.table.cell(user.name), - rx.table.cell(user.email), - rx.table.cell(user.gender), - style={"_hover": {"bg": rx.color("gray", 3)}}, - align="center", - ) - -def add_customer_button5() -> rx.Component: - return rx.dialog.root( - rx.dialog.trigger( - rx.button( - rx.icon("plus", size=26), - rx.text("Add User", size="4"), - ), - ), - rx.dialog.content( - rx.dialog.title( - "Add New User", - ), - rx.dialog.description( - "Fill the form with the user's info", - ), - rx.form( - rx.flex( - rx.input( - placeholder="User Name", name="name", required=True - ), - rx.input( - placeholder="user@reflex.dev", - name="email", - ), - rx.select( - ["Male", "Female"], - placeholder="male", - name="gender", - ), - rx.flex( - rx.dialog.close( - rx.button( - "Cancel", - variant="soft", - color_scheme="gray", - ), - ), - rx.dialog.close( - rx.button( - "Submit", type="submit" - ), - ), - spacing="3", - justify="end", - ), - direction="column", - spacing="4", - ), - on_submit=State5.add_user, - reset_on_submit=False, - ), - max_width="450px", - ), - ) - -def graph5(): - return rx.recharts.bar_chart( - rx.recharts.bar( - data_key="value", - stroke=rx.color("accent", 9), - fill=rx.color("accent", 8), - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - data=State5.users_for_graph, - width="100%", - height=250, - ) -``` - -```python eval -rx.vstack( - add_customer_button5(), - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.foreach( - State5.users, show_user5 - ), - ), - variant="surface", - size="3", - width="100%", - ), - graph5(), - align="center", - width="100%", - on_mouse_enter=State5.transform_data, - border_width="2px", - border_radius="10px", - padding="1em", - ) -``` - -```python -import reflex as rx -from collections import Counter - -@dataclasses.dataclass -class User: - """The user model.""" - - name: str - email: str - gender: str - - -class State(rx.State): - users: list[User] = [ - User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), - User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), - ] - users_for_graph: list[dict] = [] - - def add_user(self, form_data: dict): - self.users.append(User(**form_data)) - self.transform_data() - - def transform_data(self): - """Transform user gender group data into a format suitable for visualization in graphs.""" - # Count users of each gender group - gender_counts = Counter(user.gender for user in self.users) - - # Transform into list of dict so it can be used in the graph - self.users_for_graph = [ - { - "name": gender_group, - "value": count - } - for gender_group, count in gender_counts.items() - ] - - -def show_user(user: User): - """Show a user in a table row.""" - return rx.table.row( - rx.table.cell(user.name), - rx.table.cell(user.email), - rx.table.cell(user.gender), - style={ - "_hover": { - "bg": rx.color("gray", 3) - } - }, - align="center", - ) - -def add_customer_button() -> rx.Component: - return rx.dialog.root( - rx.dialog.trigger( - rx.button( - rx.icon("plus", size=26), - rx.text("Add User", size="4"), - ), - ), - rx.dialog.content( - rx.dialog.title( - "Add New User", - ), - rx.dialog.description( - "Fill the form with the user's info", - ), - rx.form( - rx.flex( - rx.input( - placeholder="User Name", name="name", required=True - ), - rx.input( - placeholder="user@reflex.dev", - name="email", - ), - rx.select( - ["Male", "Female"], - placeholder="male", - name="gender", - ), - rx.flex( - rx.dialog.close( - rx.button( - "Cancel", - variant="soft", - color_scheme="gray", - ), - ), - rx.dialog.close( - rx.button( - "Submit", type="submit" - ), - ), - spacing="3", - justify="end", - ), - direction="column", - spacing="4", - ), - on_submit=State.add_user, - reset_on_submit=False, - ), - max_width="450px", - ), - ) - -def graph(): - return rx.recharts.bar_chart( - rx.recharts.bar( - data_key="value", - stroke=rx.color("accent", 9), - fill=rx.color("accent", 8), - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - data=State.users_for_graph, - width="100%", - height=250, - ) - -def index() -> rx.Component: - return rx.vstack( - add_customer_button(), - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.foreach( - State.users, show_user - ), - ), - variant="surface", - size="3", - width="100%", - ), - graph(), - align="center", - width="100%", - ) - - -app = rx.App( - theme=rx.theme( - radius="full", accent_color="grass" - ), -) - -app.add_page( - index, - title="Customer Data App", - description="A simple app to manage customer data.", - on_load=State.transform_data, -) -``` - -Don't worry if you don't understand the code above, in this tutorial we are going to walk you through the whole thing step by step. - - -## Setup for the tutorial - -Check out the [installation docs]({docs.getting_started.installation.path}) to get Reflex set up on your machine. Follow these to create a folder called `dashboard_tutorial`, which you will `cd` into and `pip install reflex`. - -We will choose template `0` when we run `reflex init` to get the blank template. Finally run `reflex run` to start the app and confirm everything is set up correctly. - - -## Overview - -Now that you’re set up, let’s get an overview of Reflex! - -### Inspecting the starter code - -Within our `dashboard_tutorial` folder we just `cd`'d into, there is a `rxconfig.py` file that contains the configuration for our Reflex app. (Check out the [config docs]({docs.advanced_onboarding.configuration.path}) for more information) - -There is also an `assets` folder where static files such as images and stylesheets can be placed to be referenced within your app. ([asset docs]({docs.assets.overview.path}) for more information) - -Most importantly there is a folder also called `dashboard_tutorial` which contains all the code for your app. Inside of this folder there is a file named `dashboard_tutorial.py`. To begin this tutorial we will delete all the code in this file so that we can start from scratch and explain every step as we go. - -The first thing we need to do is import `reflex`. Once we have done this we can create a component, which is a reusable piece of user interface code. Components are used to render, manage, and update the UI elements in your application. - -Let's look at the example below. Here we have a function called `index` that returns a `text` component (an in-built Reflex UI component) that displays the text "Hello World!". - -Next we define our app using `app = rx.App()` and add the component we just defined (`index`) to a page using `app.add_page(index)`. The function name (in this example `index`) which defines the component, must be what we pass into the `add_page`. The definition of the app and adding a component to a page are required for every Reflex app. - -```python -import reflex as rx - - -def index() -> rx.Component: - return rx.text("Hello World!") - -app = rx.App() -app.add_page(index) -``` - -This code will render a page with the text "Hello World!" when you run your app like below: - -```python eval -rx.text("Hello World!", - border_width="2px", - border_radius="10px", - padding="1em" -) -``` - -```md alert info -For the rest of the tutorial the `app=rx.App()` and `app.add_page` will be implied and not shown in the code snippets. -``` - -### Creating a table - -Let's create a new component that will render a table. We will use the `table` component to do this. The `table` component has a `root`, which takes in a `header` and a `body`, which in turn take in `row` components. The `row` component takes in `cell` components which are the actual data that will be displayed in the table. - -```python eval -rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.cell("Danilo Sousa"), - rx.table.cell("danilo@example.com"), - rx.table.cell("Male"), - ), - rx.table.row( - rx.table.cell("Zahra Ambessa"), - rx.table.cell("zahra@example.com"), - rx.table.cell("Female"), - ), - ), - border_width="2px", - border_radius="10px", - padding="1em", - ) -``` - -```python -def index() -> rx.Component: - return rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.cell("Danilo Sousa"), - rx.table.cell("danilo@example.com"), - rx.table.cell("Male"), - ), - rx.table.row( - rx.table.cell("Zahra Ambessa"), - rx.table.cell("zahra@example.com"), - rx.table.cell("Female"), - ), - ), - ) -``` - -Components in Reflex have `props`, which can be used to customize the component and are passed in as keyword arguments to the component function. - -The `rx.table.root` component has for example the `variant` and `size` props, which customize the table as seen below. - -```python eval -rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.cell("Danilo Sousa"), - rx.table.cell("danilo@example.com"), - rx.table.cell("Male"), - ), - rx.table.row( - rx.table.cell("Zahra Ambessa"), - rx.table.cell("zahra@example.com"), - rx.table.cell("Female"), - ), - ), - variant="surface", - size="3", - border_width="2px", - border_radius="10px", - padding="1em", - ) -``` - -```python -def index() -> rx.Component: - return rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.cell("Danilo Sousa"), - rx.table.cell("danilo@example.com"), - rx.table.cell("Male"), - ), - rx.table.row( - rx.table.cell("Zahra Ambessa"), - rx.table.cell("zahra@example.com"), - rx.table.cell("Female"), - ), - ), - variant="surface", - size="3", - ) -``` - -## Showing dynamic data (State) - -Up until this point all the data we are showing in the app is static. This is not very useful for a data dashboard. We need to be able to show dynamic data that can be added to and updated. - -This is where `State` comes in. `State` is a Python class that stores variables that can change when the app is running, as well as the functions that can change those variables. - -To define a state class, subclass `rx.State` and define fields that store the state of your app. The state variables (vars) should have a type annotation, and can be initialized with a default value. Check out the [basics]({docs.getting_started.basics.path}) section for a simple example of how state works. - - -In the example below we define a `State` class called `State` that has a variable called `users` that is a list of lists of strings. Each list in the `users` list represents a user and contains their name, email and gender. - -```python -class State(rx.State): - users: list[list[str]] = [ - ["Danilo Sousa", "danilo@example.com", "Male"], - ["Zahra Ambessa", "zahra@example.com", "Female"], - ] -``` - -To iterate over a state var that is a list, we use the [`rx.foreach`]({docs.components.rendering_iterables.path}) function to render a list of components. The `rx.foreach` component takes an `iterable` (list, tuple or dict) and a `function` that renders each item in the `iterable`. - -```md alert info -# Why can we not just splat this in a `for` loop -You might be wondering why a `foreach` is even needed to render this state variable and why we cannot just splat a `for` loop. Check out this [documentation]({docs.getting_started.basics.path}#compile-time-vs.-runtime-(important)) to learn why. -``` - -Here the render function is `show_user` which takes in a single user and returns a `table.row` component that displays the users name, email and gender. - -```python exec -class State1(rx.State): - users: list[list[str]] = [ - ["Danilo Sousa", "danilo@example.com", "Male"], - ["Zahra Ambessa", "zahra@example.com", "Female"], - ] - -def show_user1(person: list): - """Show a person in a table row.""" - return rx.table.row( - rx.table.cell(person[0]), - rx.table.cell(person[1]), - rx.table.cell(person[2]), - ) -``` - -```python eval -rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.foreach( - State1.users, show_user1 - ), - ), - variant="surface", - size="3", - border_width="2px", - border_radius="10px", - padding="1em", -) -``` - - -```python -class State(rx.State): - users: list[list[str]] = [ - ["Danilo Sousa", "danilo@example.com", "Male"], - ["Zahra Ambessa", "zahra@example.com", "Female"], - ] - -def show_user(person: list): - """Show a person in a table row.""" - return rx.table.row( - rx.table.cell(person[0]), - rx.table.cell(person[1]), - rx.table.cell(person[2]), - ) - -def index() -> rx.Component: - return rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.foreach( - State.users, show_user - ), - ), - variant="surface", - size="3", -) -``` - -As you can see the output above looks the same as before, except now the data is no longer static and can change with user input to the app. - -### Using a proper class structure for our data - -So far our data has been defined in a list of lists, where the data is accessed by index i.e. `user[0]`, `user[1]`. This is not very maintainable as our app gets bigger. - -A better way to structure our data in Reflex is to use a class to represent a user. This way we can access the data using attributes i.e. `user.name`, `user.email`. - -In Reflex when we create these classes to showcase our data, we can use dataclasses. - -The `show_user` render function is also updated to access the data by named attributes, instead of indexing. - -```python exec -import dataclasses - -@dataclasses.dataclass -class User: - """The user model.""" - - name: str - email: str - gender: str - - -class State2(rx.State): - users: list[User] = [ - User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), - User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), - ] - -def show_user2(user: User): - """Show a person in a table row.""" - return rx.table.row( - rx.table.cell(user.name), - rx.table.cell(user.email), - rx.table.cell(user.gender), - ) -``` - -```python eval -rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.foreach( - State2.users, show_user2 - ), - ), - variant="surface", - size="3", - border_width="2px", - border_radius="10px", - padding="1em", -) -``` - - -```python -@dataclasses.dataclass -class User: - """The user model.""" - - name: str - email: str - gender: str - - -class State(rx.State): - users: list[User] = [ - User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), - User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), - ] - -def show_user(user: User): - """Show a person in a table row.""" - return rx.table.row( - rx.table.cell(user.name), - rx.table.cell(user.email), - rx.table.cell(user.gender), - ) - -def index() -> rx.Component: - return rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.foreach( - State.users, show_user - ), - ), - variant="surface", - size="3", -) -``` - - -Next let's add a form to the app so we can add new users to the table. - - -## Using a Form to Add Data - -We build a form using `rx.form`, which takes several components such as `rx.input` and `rx.select`, which represent the form fields that allow you to add information to submit with the form. Check out the [form]({docs.library.forms.form.path}) docs for more information on form components. - -The `rx.input` component takes in several props. The `placeholder` prop is the text that is displayed in the input field when it is empty. The `name` prop is the name of the input field, which gets passed through in the dictionary when the form is submitted. The `required` prop is a boolean that determines if the input field is required. - -The `rx.select` component takes in a list of options that are displayed in the dropdown. The other props used here are identical to the `rx.input` component. - -```python demo -rx.form( - rx.input( - placeholder="User Name", name="name", required=True - ), - rx.input( - placeholder="user@reflex.dev", - name="email", - ), - rx.select( - ["Male", "Female"], - placeholder="Male", - name="gender", - ), -) -``` - -This form is all very compact as you can see from the example, so we need to add some styling to make it look better. We can do this by adding a `vstack` component around the form fields. The `vstack` component stacks the form fields vertically. Check out the [layout]({docs.styling.layout.path}) docs for more information on how to layout your app. - - -```python demo -rx.form( - rx.vstack( - rx.input( - placeholder="User Name", name="name", required=True - ), - rx.input( - placeholder="user@reflex.dev", - name="email", - ), - rx.select( - ["Male", "Female"], - placeholder="Male", - name="gender", - ), - ), -) -``` - -Now you have probably realised that we have all the form fields, but we have no way to submit the form. We can add a submit button to the form by adding a `rx.button` component to the `vstack` component. The `rx.button` component takes in the text that is displayed on the button and the `type` prop which is the type of button. The `type` prop is set to `submit` so that the form is submitted when the button is clicked. - -In addition to this we need a way to update the `users` state variable when the form is submitted. All state changes are handled through functions in the state class, called [event handlers]({docs.events.events_overview.path}). - -Components have special props called event triggers, such as `on_submit`, that can be used to make components interactive. Event triggers connect components to event handlers, which update the state. Different event triggers expect the event handler that you hook them up to, to take in different arguments (and some do not take in any arguments). - -The `on_submit` event trigger of `rx.form` is hooked up to the `add_user` event handler that is defined in the `State` class. This event trigger expects to pass a `dict`, containing the form data, to the event handler that it is hooked up to. The `add_user` event handler takes in the form data as a dictionary and appends it to the `users` state variable. - - -```python -class State(rx.State): - - ... - - def add_user(self, form_data: dict): - self.users.append(User(**form_data)) - - -def form(): - return rx.form( - rx.vstack( - rx.input( - placeholder="User Name", name="name", required=True - ), - rx.input( - placeholder="user@reflex.dev", - name="email", - ), - rx.select( - ["Male", "Female"], - placeholder="Male", - name="gender", - ), - rx.button("Submit", type="submit"), - ), - on_submit=State.add_user, - reset_on_submit=True, - ) -``` - -Finally we must add the new `form()` component we have defined to the `index()` function so that the form is rendered on the page. - -Below is the full code for the app so far. If you try this form out you will see that you can add new users to the table by filling out the form and clicking the submit button. The form data will also appear as a toast (a small window in the corner of the page) on the screen when submitted. - - -```python exec -class State3(rx.State): - users: list[User] = [ - User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), - User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), - ] - - def add_user(self, form_data: dict): - self.users.append(User(**form_data)) - - - return rx.toast.info( - f"User has been added: {form_data}.", - position="bottom-right", - ) - -def show_user(user: User): - """Show a person in a table row.""" - return rx.table.row( - rx.table.cell(user.name), - rx.table.cell(user.email), - rx.table.cell(user.gender), - ) - -def form(): - return rx.form( - rx.vstack( - rx.input( - placeholder="User Name", name="name", required=True - ), - rx.input( - placeholder="user@reflex.dev", - name="email", - ), - rx.select( - ["Male", "Female"], - placeholder="Male", - name="gender", - ), - rx.button("Submit", type="submit"), - ), - on_submit=State3.add_user, - reset_on_submit=True, - ) -``` - -```python eval -rx.vstack( - form(), - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.foreach( - State3.users, show_user - ), - ), - variant="surface", - size="3", - ), - border_width="2px", - border_radius="10px", - padding="1em", -) -``` - -```python -class State(rx.State): - users: list[User] = [ - User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), - User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), - ] - - def add_user(self, form_data: dict): - self.users.append(User(**form_data)) - - -def show_user(user: User): - """Show a person in a table row.""" - return rx.table.row( - rx.table.cell(user.name), - rx.table.cell(user.email), - rx.table.cell(user.gender), - ) - -def form(): - return rx.form( - rx.vstack( - rx.input( - placeholder="User Name", name="name", required=True - ), - rx.input( - placeholder="user@reflex.dev", - name="email", - ), - rx.select( - ["Male", "Female"], - placeholder="Male", - name="gender", - ), - rx.button("Submit", type="submit"), - ), - on_submit=State.add_user, - reset_on_submit=True, - ) - -def index() -> rx.Component: - return rx.vstack( - form(), - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.foreach( - State.users, show_user - ), - ), - variant="surface", - size="3", - ), - ) -``` - - -### Putting the Form in an Overlay - -In Reflex, we like to make the user interaction as intuitive as possible. Placing the form we just constructed in an overlay creates a focused interaction by dimming the background, and ensures a cleaner layout when you have multiple action points such as editing and deleting as well. - -We will place the form inside of a `rx.dialog` component (also called a modal). The `rx.dialog.root` contains all the parts of a dialog, and the `rx.dialog.trigger` wraps the control that will open the dialog. In our case the trigger will be an `rx.button` that says "Add User" as shown below. - -```python -rx.dialog.trigger( - rx.button( - rx.icon("plus", size=26), - rx.text("Add User", size="4"), - ), -) -``` - -After the trigger we have the `rx.dialog.content` which contains everything within our dialog, including a title, a description and our form. The first way to close the dialog is without submitting the form and the second way is to close the dialog by submitting the form as shown below. This requires two `rx.dialog.close` components within the dialog. - -```python -rx.dialog.close( - rx.button( - "Cancel", - variant="soft", - color_scheme="gray", - ), -), -rx.dialog.close( - rx.button( - "Submit", type="submit" - ), -) -``` - -The total code for the dialog with the form in it is below. - -```python demo -rx.dialog.root( - rx.dialog.trigger( - rx.button( - rx.icon("plus", size=26), - rx.text("Add User", size="4"), - ), - ), - rx.dialog.content( - rx.dialog.title( - "Add New User", - ), - rx.dialog.description( - "Fill the form with the user's info", - ), - rx.form( - # flex is similar to vstack and used to layout the form fields - rx.flex( - rx.input( - placeholder="User Name", name="name", required=True - ), - rx.input( - placeholder="user@reflex.dev", - name="email", - ), - rx.select( - ["Male", "Female"], - placeholder="Male", - name="gender", - ), - rx.flex( - rx.dialog.close( - rx.button( - "Cancel", - variant="soft", - color_scheme="gray", - ), - ), - rx.dialog.close( - rx.button( - "Submit", type="submit" - ), - ), - spacing="3", - justify="end", - ), - direction="column", - spacing="4", - ), - on_submit=State3.add_user, - reset_on_submit=False, - ), - # max_width is used to limit the width of the dialog - max_width="450px", - ), -) -``` - -At this point we have an app that allows you to add users to a table by filling out a form. The form is placed in a dialog that can be opened by clicking the "Add User" button. We change the name of the component from `form` to `add_customer_button` and update this in our `index` component. The full app so far and code are below. - - -```python exec -def add_customer_button() -> rx.Component: - return rx.dialog.root( - rx.dialog.trigger( - rx.button( - rx.icon("plus", size=26), - rx.text("Add User", size="4"), - ), - ), - rx.dialog.content( - rx.dialog.title( - "Add New User", - ), - rx.dialog.description( - "Fill the form with the user's info", - ), - rx.form( - rx.flex( - rx.input( - placeholder="User Name", name="name", required=True - ), - rx.input( - placeholder="user@reflex.dev", - name="email", - ), - rx.select( - ["Male", "Female"], - placeholder="Male", - name="gender", - ), - rx.flex( - rx.dialog.close( - rx.button( - "Cancel", - variant="soft", - color_scheme="gray", - ), - ), - rx.dialog.close( - rx.button( - "Submit", type="submit" - ), - ), - spacing="3", - justify="end", - ), - direction="column", - spacing="4", - ), - on_submit=State3.add_user, - reset_on_submit=False, - ), - max_width="450px", - ), - ) -``` - -```python eval -rx.vstack( - add_customer_button(), - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.foreach( - State3.users, show_user - ), - ), - variant="surface", - size="3", - ), - border_width="2px", - border_radius="10px", - padding="1em", -) -``` - -```python -@dataclasses.dataclass -class User: - """The user model.""" - - name: str - email: str - gender: str - - -class State(rx.State): - users: list[User] = [ - User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), - User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), - ] - - def add_user(self, form_data: dict): - self.users.append(User(**form_data)) - - - -def show_user(user: User): - """Show a person in a table row.""" - return rx.table.row( - rx.table.cell(user.name), - rx.table.cell(user.email), - rx.table.cell(user.gender), - ) - -def add_customer_button() -> rx.Component: - return rx.dialog.root( - rx.dialog.trigger( - rx.button( - rx.icon("plus", size=26), - rx.text("Add User", size="4"), - ), - ), - rx.dialog.content( - rx.dialog.title( - "Add New User", - ), - rx.dialog.description( - "Fill the form with the user's info", - ), - rx.form( - rx.flex( - rx.input( - placeholder="User Name", name="name", required=True - ), - rx.input( - placeholder="user@reflex.dev", - name="email", - ), - rx.select( - ["Male", "Female"], - placeholder="Male", - name="gender", - ), - rx.flex( - rx.dialog.close( - rx.button( - "Cancel", - variant="soft", - color_scheme="gray", - ), - ), - rx.dialog.close( - rx.button( - "Submit", type="submit" - ), - ), - spacing="3", - justify="end", - ), - direction="column", - spacing="4", - ), - on_submit=State.add_user, - reset_on_submit=False, - ), - max_width="450px", - ), - ) - -def index() -> rx.Component: - return rx.vstack( - add_customer_button(), - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.foreach( - State.users, show_user - ), - ), - variant="surface", - size="3", - ), - ) -``` - - -## Plotting Data in a Graph - -The last part of this tutorial is to plot the user data in a graph. We will use Reflex's built-in graphing library recharts to plot the number of users of each gender. - -### Transforming the data for the graph - -The graphing components in Reflex expect to take in a list of dictionaries. Each dictionary represents a data point on the graph and contains the x and y values. We will create a new event handler in the state called `transform_data` to transform the user data into the format that the graphing components expect. We must also create a new state variable called `users_for_graph` to store the transformed data, which will be used to render the graph. - - -```python -from collections import Counter - -class State(rx.State): - users: list[User] = [] - users_for_graph: list[dict] = [] - - def add_user(self, form_data: dict): - self.users.append(User(**form_data)) - self.transform_data() - - def transform_data(self): - """Transform user gender group data into a format suitable for visualization in graphs.""" - # Count users of each gender group - gender_counts = Counter(user.gender for user in self.users) - - # Transform into list of dict so it can be used in the graph - self.users_for_graph = [ - { - "name": gender_group, - "value": count - } - for gender_group, count in gender_counts.items() - ] -``` - -As we can see above the `transform_data` event handler uses the `Counter` class from the `collections` module to count the number of users of each gender. We then create a list of dictionaries from this which we set to the state var `users_for_graph`. - -Finally we can see that whenever we add a new user through submitting the form and running the `add_user` event handler, we call the `transform_data` event handler to update the `users_for_graph` state variable. - -### Rendering the graph - -We use the `rx.recharts.bar_chart` component to render the graph. We pass through the state variable for our graphing data as `data=State.users_for_graph`. We also pass in a `rx.recharts.bar` component which represents the bars on the graph. The `rx.recharts.bar` component takes in the `data_key` prop which is the key in the data dictionary that represents the y value of the bar. The `stroke` and `fill` props are used to set the color of the bars. - -The `rx.recharts.bar_chart` component also takes in `rx.recharts.x_axis` and `rx.recharts.y_axis` components which represent the x and y axes of the graph. The `data_key` prop of the `rx.recharts.x_axis` component is set to the key in the data dictionary that represents the x value of the bar. Finally we add `width` and `height` props to set the size of the graph. - -```python -def graph(): - return rx.recharts.bar_chart( - rx.recharts.bar( - data_key="value", - stroke=rx.color("accent", 9), - fill=rx.color("accent", 8), - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - data=State.users_for_graph, - width="100%", - height=250, - ) -``` - -Finally we add this `graph()` component to our `index()` component so that the graph is rendered on the page. The code for the full app with the graph included is below. If you try this out you will see that the graph updates whenever you add a new user to the table. - -```python exec -from collections import Counter - -class State4(rx.State): - users: list[User] = [ - User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), - User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), - ] - users_for_graph: list[dict] = [] - - def add_user(self, form_data: dict): - self.users.append(User(**form_data)) - self.transform_data() - - return rx.toast.info( - f"User {form_data['name']} has been added.", - position="bottom-right", - ) - - def transform_data(self): - """Transform user gender group data into a format suitable for visualization in graphs.""" - # Count users of each gender group - gender_counts = Counter(user.gender for user in self.users) - - # Transform into list of dict so it can be used in the graph - self.users_for_graph = [ - { - "name": gender_group, - "value": count - } - for gender_group, count in gender_counts.items() - ] - -def add_customer_button() -> rx.Component: - return rx.dialog.root( - rx.dialog.trigger( - rx.button( - rx.icon("plus", size=26), - rx.text("Add User", size="4"), - ), - ), - rx.dialog.content( - rx.dialog.title( - "Add New User", - ), - rx.dialog.description( - "Fill the form with the user's info", - ), - rx.form( - rx.flex( - rx.input( - placeholder="User Name", name="name", required=True - ), - rx.input( - placeholder="user@reflex.dev", - name="email", - ), - rx.select( - ["Male", "Female"], - placeholder="Male", - name="gender", - ), - rx.flex( - rx.dialog.close( - rx.button( - "Cancel", - variant="soft", - color_scheme="gray", - ), - ), - rx.dialog.close( - rx.button( - "Submit", type="submit" - ), - ), - spacing="3", - justify="end", - ), - direction="column", - spacing="4", - ), - on_submit=State4.add_user, - reset_on_submit=False, - ), - max_width="450px", - ), - ) - -def graph(): - return rx.recharts.bar_chart( - rx.recharts.bar( - data_key="value", - stroke=rx.color("accent", 9), - fill=rx.color("accent", 8), - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - data=State4.users_for_graph, - width="100%", - height=250, - ) -``` - -```python eval -rx.vstack( - add_customer_button(), - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.foreach( - State4.users, show_user - ), - ), - variant="surface", - size="3", - ), - graph(), - border_width="2px", - border_radius="10px", - padding="1em", -) -``` - -```python -from collections import Counter - -class State(rx.State): - users: list[User] = [ - User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), - User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), - ] - users_for_graph: list[dict] = [] - - def add_user(self, form_data: dict): - self.users.append(User(**form_data)) - self.transform_data() - - def transform_data(self): - """Transform user gender group data into a format suitable for visualization in graphs.""" - # Count users of each gender group - gender_counts = Counter(user.gender for user in self.users) - - # Transform into list of dict so it can be used in the graph - self.users_for_graph = [ - { - "name": gender_group, - "value": count - } - for gender_group, count in gender_counts.items() - ] - - -def show_user(user: User): - """Show a person in a table row.""" - return rx.table.row( - rx.table.cell(user.name), - rx.table.cell(user.email), - rx.table.cell(user.gender), - ) - -def add_customer_button() -> rx.Component: - return rx.dialog.root( - rx.dialog.trigger( - rx.button( - rx.icon("plus", size=26), - rx.text("Add User", size="4"), - ), - ), - rx.dialog.content( - rx.dialog.title( - "Add New User", - ), - rx.dialog.description( - "Fill the form with the user's info", - ), - rx.form( - rx.flex( - rx.input( - placeholder="User Name", name="name", required=True - ), - rx.input( - placeholder="user@reflex.dev", - name="email", - ), - rx.select( - ["Male", "Female"], - placeholder="male", - name="gender", - ), - rx.flex( - rx.dialog.close( - rx.button( - "Cancel", - variant="soft", - color_scheme="gray", - ), - ), - rx.dialog.close( - rx.button( - "Submit", type="submit" - ), - ), - spacing="3", - justify="end", - ), - direction="column", - spacing="4", - ), - on_submit=State.add_user, - reset_on_submit=False, - ), - max_width="450px", - ), - ) - -def graph(): - return rx.recharts.bar_chart( - rx.recharts.bar( - data_key="value", - stroke=rx.color("accent", 9), - fill=rx.color("accent", 8), - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - data=State.users_for_graph, - width="100%", - height=250, - ) - -def index() -> rx.Component: - return rx.vstack( - add_customer_button(), - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.foreach( - State.users, show_user - ), - ), - variant="surface", - size="3", - ), - graph(), - ) -``` - -One thing you may have noticed about your app is that the graph does not appear initially when you run the app, and that you must add a user to the table for it to first appear. This occurs because the `transform_data` event handler is only called when a user is added to the table. In the next section we will explore a solution to this. - - -## Final Cleanup - -### Revisiting app.add_page - -At the beginning of this tutorial we mentioned that the `app.add_page` function is required for every Reflex app. This function is used to add a component to a page. - -The `app.add_page` currently looks like this `app.add_page(index)`. We could change the route that the page renders on by setting the `route` prop such as `route="/custom-route"`, this would change the route to `http://localhost:3000/custom-route` for this page. - -We can also set a `title` to be shown in the browser tab and a `description` as shown in search results. - -To solve the problem we had above about our graph not loading when the page loads, we can use `on_load` inside of `app.add_page` to call the `transform_data` event handler when the page loads. This would look like `on_load=State.transform_data`. Below see what our `app.add_page` would look like with some of the changes above added. - -```python eval -rx.vstack( - add_customer_button(), - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.foreach( - State4.users, show_user - ), - ), - variant="surface", - size="3", - ), - graph(), - on_mouse_enter=State4.transform_data, - border_width="2px", - border_radius="10px", - padding="1em", -) -``` - -```python -app.add_page( - index, - title="Customer Data App", - description="A simple app to manage customer data.", - on_load=State.transform_data, -) -``` - -### Revisiting app=rx.App() - -At the beginning of the tutorial we also mentioned that we defined our app using `app=rx.App()`. We can also pass in some props to the `rx.App` component to customize the app. - -The most important one is `theme` which allows you to customize the look and feel of the app. The `theme` prop takes in an `rx.theme` component which has several props that can be set. - -The `radius` prop sets the global radius value for the app that is inherited by all components that have a `radius` prop. It can be overwritten locally for a specific component by manually setting the `radius` prop. - -The `accent_color` prop sets the accent color of the app. Check out other options for the accent color [here]({docs.library.other.theme.path}). - -To see other props that can be set at the app level check out this [documentation]({docs.styling.theming.path}) - -```python -app = rx.App( - theme=rx.theme( - radius="full", accent_color="grass" - ), -) -``` - -Unfortunately in this tutorial here we cannot actually apply this to the live example on the page, but if you copy and paste the code below into a reflex app locally you can see it in action. - - - -## Conclusion - -Finally let's make some final styling updates to our app. We will add some hover styling to the table rows and center the table inside the `show_user` with `style=\{"_hover": \{"bg": rx.color("gray", 3)}}, align="center"`. - -In addition, we will add some `width="100%"` and `align="center"` to the `index()` component to center the items on the page and ensure they stretch the full width of the page. - -Check out the full code and interactive app below: - -```python eval -rx.vstack( - add_customer_button5(), - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.foreach( - State5.users, show_user5 - ), - ), - variant="surface", - size="3", - width="100%", - ), - graph5(), - align="center", - width="100%", - on_mouse_enter=State5.transform_data, - border_width="2px", - border_radius="10px", - padding="1em", - ) -``` - - -```python -import reflex as rx -from collections import Counter - -@dataclasses.dataclass -class User: - """The user model.""" - - name: str - email: str - gender: str - - -class State(rx.State): - users: list[User] = [ - User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), - User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), - ] - users_for_graph: list[dict] = [] - - def add_user(self, form_data: dict): - self.users.append(User(**form_data)) - self.transform_data() - - def transform_data(self): - """Transform user gender group data into a format suitable for visualization in graphs.""" - # Count users of each gender group - gender_counts = Counter(user.gender for user in self.users) - - # Transform into list of dict so it can be used in the graph - self.users_for_graph = [ - { - "name": gender_group, - "value": count - } - for gender_group, count in gender_counts.items() - ] - - -def show_user(user: User): - """Show a user in a table row.""" - return rx.table.row( - rx.table.cell(user.name), - rx.table.cell(user.email), - rx.table.cell(user.gender), - style={ - "_hover": { - "bg": rx.color("gray", 3) - } - }, - align="center", - ) - -def add_customer_button() -> rx.Component: - return rx.dialog.root( - rx.dialog.trigger( - rx.button( - rx.icon("plus", size=26), - rx.text("Add User", size="4"), - ), - ), - rx.dialog.content( - rx.dialog.title( - "Add New User", - ), - rx.dialog.description( - "Fill the form with the user's info", - ), - rx.form( - rx.flex( - rx.input( - placeholder="User Name", name="name", required=True - ), - rx.input( - placeholder="user@reflex.dev", - name="email", - ), - rx.select( - ["Male", "Female"], - placeholder="male", - name="gender", - ), - rx.flex( - rx.dialog.close( - rx.button( - "Cancel", - variant="soft", - color_scheme="gray", - ), - ), - rx.dialog.close( - rx.button( - "Submit", type="submit" - ), - ), - spacing="3", - justify="end", - ), - direction="column", - spacing="4", - ), - on_submit=State.add_user, - reset_on_submit=False, - ), - max_width="450px", - ), - ) - -def graph(): - return rx.recharts.bar_chart( - rx.recharts.bar( - data_key="value", - stroke=rx.color("accent", 9), - fill=rx.color("accent", 8), - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - data=State.users_for_graph, - width="100%", - height=250, - ) - -def index() -> rx.Component: - return rx.vstack( - add_customer_button(), - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Gender"), - ), - ), - rx.table.body( - rx.foreach( - State.users, show_user - ), - ), - variant="surface", - size="3", - width="100%", - ), - graph(), - align="center", - width="100%", - ) - - -app = rx.App( - theme=rx.theme( - radius="full", accent_color="grass" - ), -) - -app.add_page( - index, - title="Customer Data App", - description="A simple app to manage customer data.", - on_load=State.transform_data, -) -``` - -And that is it for your first dashboard tutorial. In this tutorial we have created - -- a table to display user data -- a form to add new users to the table -- a dialog to showcase the form -- a graph to visualize the user data - -In addition to the above we have we have - -- explored state to allow you to show dynamic data that changes over time -- explored events to allow you to make your app interactive and respond to user actions -- added styling to the app to make it look better - - - -## Advanced Section (Hooking this up to a Database) - -Coming Soon! \ No newline at end of file diff --git a/docs/getting_started/installation.md b/docs/getting_started/installation.md deleted file mode 100644 index b412c738a6..0000000000 --- a/docs/getting_started/installation.md +++ /dev/null @@ -1,172 +0,0 @@ -```python exec -from pcweb import constants -import reflex as rx -from pcweb.pages.gallery import gallery -app_name = "my_app_name" -default_url = "http://localhost:3000" -``` - -# Installation - -Reflex requires Python 3.10+. - - -```md video https://youtube.com/embed/ITOZkzjtjUA?start=758&end=1206 -# Video: Installation -``` - - -## Virtual Environment - -We **highly recommend** creating a virtual environment for your project. - -[uv]({constants.UV_URL}) is the recommended modern option. [venv]({constants.VENV_URL}), [conda]({constants.CONDA_URL}) and [poetry]({constants.POETRY_URL}) are some alternatives. - - -# Install Reflex on your system - ----md tabs - ---tab macOS/Linux -## Install on macOS/Linux - -We will go with [uv]({constants.UV_URL}) here. - - -### Prerequisites - -#### Install uv - -```bash -curl -LsSf https://astral.sh/uv/install.sh | sh -``` - -After installation, restart your terminal or run `source ~/.bashrc` (or `source ~/.zshrc` for zsh). - -Alternatively, install via [Homebrew, PyPI, or other methods](https://docs.astral.sh/uv/getting-started/installation/). - -**macOS (Apple Silicon) users:** Install [Rosetta 2](https://support.apple.com/en-us/HT211861). Run this command: - -`/usr/sbin/softwareupdate --install-rosetta --agree-to-license` - - -### Create the project directory - -Replace `{app_name}` with your project name. Switch to the new directory. - -```bash -mkdir {app_name} -cd {app_name} -``` - -### Initialize uv project - -```bash -uv init -``` - -### Add Reflex to the project - -```bash -uv add reflex -``` - -### Initialize the Reflex project - -```bash -uv run reflex init -``` - - --- ---tab Windows -## Install on Windows - -For Windows users, we recommend using [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/about) for optimal performance. - -**WSL users:** Refer to the macOS/Linux instructions above. - -For the rest of this section we will work with native Windows (non-WSL). - -We will go with [uv]({constants.UV_URL}) here. - -### Prerequisites - -#### Install uv - -```powershell -powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" -``` - -After installation, restart your terminal (PowerShell or Command Prompt). - -Alternatively, install via [WinGet, Scoop, or other methods](https://docs.astral.sh/uv/getting-started/installation/). - -### Create the project directory - -Replace `{app_name}` with your project name. Switch to the new directory. - -```bash -mkdir {app_name} -cd {app_name} -``` - -### Initialize uv project - -```bash -uv init -``` - -### Add Reflex to the project - -```bash -uv add reflex -``` - -### Initialize the Reflex project - -```bash -uv run reflex init -``` - -```md alert warning -# Error `Install Failed - You are missing a DLL required to run bun.exe` Windows -Bun requires runtime components of Visual C++ libraries to run on Windows. This issue is fixed by installing [Microsoft Visual C++ 2015 Redistributable](https://www.microsoft.com/en-us/download/details.aspx?id=53840). -``` --- - ---- - - -Running `uv run reflex init` will return the option to start with a blank Reflex app, premade templates built by the Reflex team, or to try our [AI builder]({constants.REFLEX_BUILD_URL}). - -```bash -Initializing the web directory. - -Get started with a template: -(0) A blank Reflex app. -(1) Premade templates built by the Reflex team. -(2) Try our AI builder. -Which template would you like to use? (0): -``` - -From here select an option. - - -## Run the App - -Run it in development mode: - -```bash -uv run reflex run -``` - -Your app runs at [http://localhost:3000](http://localhost:3000). - -Reflex prints logs to the terminal. To increase log verbosity to help with debugging, use the `--loglevel` flag: - -```bash -uv run reflex run --loglevel debug -``` - -Reflex will *hot reload* any code changes in real time when running in development mode. Your code edits will show up on [http://localhost:3000](http://localhost:3000) automatically. diff --git a/docs/getting_started/introduction.md b/docs/getting_started/introduction.md deleted file mode 100644 index f4e852cde4..0000000000 --- a/docs/getting_started/introduction.md +++ /dev/null @@ -1,353 +0,0 @@ -```python exec -import reflex as rx -from pcweb import constants, styles -from pcweb.pages.docs import getting_started -from pcweb.pages.docs import wrapping_react -from pcweb.pages.docs.library import library -from pcweb.pages.docs import pages -from pcweb.pages.docs import vars -from pcweb.styles.colors import c_color -from pcweb.pages.docs import styling -from pcweb.styles.fonts import base -from pcweb.pages.docs import hosting -from pcweb.flexdown import markdown_with_shiki -from pcweb.pages.docs import advanced_onboarding -``` - - - -# Introduction - -**Reflex** is an open-source framework for quickly building beautiful, interactive web applications in **pure Python**. - -## Goals - -```md section -### Pure Python - -Use Python for everything. Don't worry about learning a new language. - -### Easy to Learn - -Build and share your first app in minutes. No web development experience required. - -### Full Flexibility - -Remain as flexible as traditional web frameworks. Reflex is easy to use, yet allows for advanced use cases. - -Build anything from small data science apps to large, multi-page websites. **This entire site was built and deployed with Reflex!** - -### Batteries Included - -No need to reach for a bunch of different tools. Reflex handles the user interface, server-side logic, and deployment of your app. -``` - -## An example: Make it count - -Here, we go over a simple counter app that lets the user count up or down. - -```python exec -class CounterExampleState(rx.State): - count: int = 0 - - @rx.event - def increment(self): - self.count += 1 - - @rx.event - def decrement(self): - self.count -= 1 - -class IntroTabsState(rx.State): - """The app state.""" - - value: str = "tab1" - tab_selected: str = "" - - @rx.event - def change_value(self, val: str): - self.tab_selected = f"{val} clicked!" - self.value = val - -def tabs(): - return rx.tabs.root( - rx.tabs.list( - rx.tabs.trigger( - "Frontend", value="tab1", - class_name="tab-style" - ), - rx.tabs.trigger( - "Backend", value="tab2", - class_name="tab-style" - ), - rx.tabs.trigger( - "Page", value="tab3", - class_name="tab-style" - ), - ), - rx.tabs.content( - markdown_with_shiki( - """The frontend is built declaratively using Reflex components. Components are compiled down to JS and served to the users browser, therefore: - -- Only use Reflex components, vars, and var operations when building your UI. Any other logic should be put in your `State` (backend). - -- Use `rx.cond` and `rx.foreach` (replaces if statements and for loops), for creating dynamic UIs. - """, - ), - value="tab1", - class_name="pt-4" - ), - rx.tabs.content( - markdown_with_shiki( - """Write your backend in the `State` class. Here you can define functions and variables that can be referenced in the frontend. This code runs directly on the server and is not compiled, so there are no special caveats. Here you can use any Python external library and call any method/function. - """, - ), - value="tab2", - class_name="pt-4" - ), - rx.tabs.content( - markdown_with_shiki( - f"""Each page is a Python function that returns a Reflex component. You can define multiple pages and navigate between them, see the [Routing]({pages.overview.path}) section for more information. - -- Start with a single page and scale to 100s of pages. - """, - ), - value="tab3", - class_name="pt-4" - ), - class_name="text-slate-12 font-normal", - default_value="tab1", - value=IntroTabsState.value, - on_change=lambda x: IntroTabsState.change_value( - x - ), - ) -``` - -```python demo box id=counter -rx.hstack( - rx.button( - "Decrement", - color_scheme="ruby", - on_click=CounterExampleState.decrement, - ), - rx.heading(CounterExampleState.count, font_size="2em"), - rx.button( - "Increment", - color_scheme="grass", - on_click=CounterExampleState.increment, - ), - spacing="4", -) -``` - -Here is the full code for this example: - -```python eval -tabs() -``` - -```python demo box -rx.box( - rx._x.code_block( - """import reflex as rx """, - class_name="code-block !bg-transparent !border-none", - ), - rx._x.code_block( - """class State(rx.State): - count: int = 0 - - @rx.event - def increment(self): - self.count += 1 - - @rx.event - def decrement(self): - self.count -= 1""", - background=rx.cond( - IntroTabsState.value == "tab2", - "var(--c-violet-3) !important", - "transparent", - ), - border=rx.cond( - IntroTabsState.value == "tab2", - "1px solid var(--c-violet-5)", - "none !important" - ), - class_name="code-block", - ), - rx._x.code_block( - """def index(): - return rx.hstack( - rx.button( - "Decrement", - color_scheme="ruby", - on_click=State.decrement, - ), - rx.heading(State.count, font_size="2em"), - rx.button( - "Increment", - color_scheme="grass", - on_click=State.increment, - ), - spacing="4", - )""", - border=rx.cond( - IntroTabsState.value == "tab1", - "1px solid var(--c-violet-5)", - "none !important", - ), - background=rx.cond( - IntroTabsState.value == "tab1", - "var(--c-violet-3) !important", - "transparent", - ), - class_name="code-block", - ), - rx._x.code_block( - """app = rx.App() -app.add_page(index)""", - background=rx.cond( - IntroTabsState.value == "tab3", - "var(--c-violet-3) !important", - "transparent", - ), - border=rx.cond( - IntroTabsState.value == "tab3", - "1px solid var(--c-violet-5)", - "none !important", - ), - class_name="code-block", - ), - class_name="w-full flex flex-col", -) -``` - -## The Structure of a Reflex App - -Let's break this example down. - -### Import - -```python -import reflex as rx -``` - -We begin by importing the `reflex` package (aliased to `rx`). We reference Reflex objects as `rx.*` by convention. - -### State - -```python -class State(rx.State): - count: int = 0 -``` - -The state defines all the variables (called **[vars]({vars.base_vars.path})**) in an app that can change, as well as the functions (called **[event_handlers](#event-handlers)**) that change them. - -Here our state has a single var, `count`, which holds the current value of the counter. We initialize it to `0`. - -### Event Handlers - -```python -@rx.event -def increment(self): - self.count += 1 - -@rx.event -def decrement(self): - self.count -= 1 -``` - -Within the state, we define functions, called **event handlers**, that change the state vars. - -Event handlers are the only way that we can modify the state in Reflex. -They can be called in response to user actions, such as clicking a button or typing in a text box. -These actions are called **events**. - -Our counter app has two event handlers, `increment` and `decrement`. - -### User Interface (UI) - -```python -def index(): - return rx.hstack( - rx.button( - "Decrement", - color_scheme="ruby", - on_click=State.decrement, - ), - rx.heading(State.count, font_size="2em"), - rx.button( - "Increment", - color_scheme="grass", - on_click=State.increment, - ), - spacing="4", - ) -``` - -This function defines the app's user interface. - -We use different components such as `rx.hstack`, `rx.button`, and `rx.heading` to build the frontend. Components can be nested to create complex layouts, and can be styled using the full power of CSS or [Tailwind CSS]({styling.tailwind.path}). - -Reflex comes with [50+ built-in components]({library.path}) to help you get started. -We are actively adding more components. Also, it's easy to [wrap your own React components]({wrapping_react.overview.path}). - -```python -rx.heading(State.count, font_size="2em"), -``` - -Components can reference the app's state vars. -The `rx.heading` component displays the current value of the counter by referencing `State.count`. -All components that reference state will reactively update whenever the state changes. - -```python -rx.button( - "Decrement", - color_scheme="ruby", - on_click=State.decrement, -), -``` - -Components interact with the state by binding events triggers to event handlers. -For example, `on_click` is an event that is triggered when a user clicks a component. - -The first button in our app binds its `on_click` event to the `State.decrement` event handler. Similarly the second button binds `on_click` to `State.increment`. - -In other words, the sequence goes like this: - -- User clicks "increment" on the UI. -- `on_click` event is triggered. -- Event handler `State.increment` is called. -- `State.count` is incremented. -- UI updates to reflect the new value of `State.count`. - -### Add pages - -Next we define our app and add the counter component to the base route. - -```python -app = rx.App() -app.add_page(index) -``` - -## Next Steps - -🎉 And that's it! - -We've created a simple, yet fully interactive web app in pure Python. - -By continuing with our documentation, you will learn how to build awesome apps with Reflex. Use the sidebar to navigate through the sections, or search (`Ctrl+K` or `Cmd+K`) to quickly find a page. - -For a glimpse of the possibilities, check out these resources: - -* For a more real-world example, check out either the [dashboard tutorial]({getting_started.dashboard_tutorial.path}) or the [chatapp tutorial]({getting_started.chatapp_tutorial.path}). -* Check out our open-source [templates]({getting_started.open_source_templates.path})! -* We have an AI Builder that can generate full Reflex apps or help with your existing app! Check it out at [Reflex Build]({constants.REFLEX_BUILD_URL})! -* Deploy your app with a single command using [Reflex Cloud]({hosting.deploy_quick_start.path})! - -If you want to learn more about how Reflex works, check out the [How Reflex Works]({advanced_onboarding.how_reflex_works.path}) section. - -## Join our Community - -If you have questions about anything related to Reflex, you're always welcome to ask our community on [GitHub Discussions]({constants.GITHUB_DISCUSSIONS_URL}), [Discord]({constants.DISCORD_URL}), [Forum]({constants.FORUM_URL}), and [X]({constants.TWITTER_URL}). diff --git a/docs/getting_started/open_source_templates.md b/docs/getting_started/open_source_templates.md deleted file mode 100644 index aa28a5e23e..0000000000 --- a/docs/getting_started/open_source_templates.md +++ /dev/null @@ -1,67 +0,0 @@ -# Open Source Templates - -Check out what the community is building with Reflex. See 2000+ more public projects on [Github](https://github.com/reflex-dev/reflex/network/dependents). Want to get your app featured? Submit it [here](https://github.com/reflex-dev/templates). Copy the template command and use it during `reflex init` - -```python exec - -import reflex as rx - -from pcweb.components.code_card import gallery_app_card -from pcweb.components.webpage.comps import h1_title -from pcweb.pages.gallery.sidebar import TemplatesState, pagination, sidebar -from pcweb.templates.webpage import webpage - - -@rx.memo -def skeleton_card() -> rx.Component: - return rx.skeleton( - class_name="box-border shadow-large border rounded-xl w-full h-[280px] overflow-hidden", - loading=True, - ) - - -def component_grid() -> rx.Component: - from pcweb.pages.gallery.apps import gallery_apps_data - - posts = [] - for path, document in list(gallery_apps_data.items()): - posts.append( - rx.cond( - TemplatesState.filtered_templates.contains(document.metadata["title"]), - gallery_app_card(app=document.metadata), - None, - ) - ) - return rx.box( - *posts, - rx.box( - rx.el.h4( - "No templates found", - class_name="text-base font-semibold text-slate-12 text-nowrap", - ), - class_name="flex-col gap-2 flex absolute left-1 top-0 z-[-1] w-full", - ), - class_name="gap-6 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 w-full relative", - ) - - -def gallery() -> rx.Component: - return rx.el.section( - rx.box( - sidebar(), - rx.box( - component_grid(), - pagination(), - class_name="flex flex-col", - ), - class_name="flex flex-col gap-6 lg:gap-10 w-full", - ), - id="gallery", - class_name="mx-auto", - ) - -``` - -```python eval -gallery() -``` diff --git a/docs/getting_started/project-structure.md b/docs/getting_started/project-structure.md deleted file mode 100644 index f2ec8da416..0000000000 --- a/docs/getting_started/project-structure.md +++ /dev/null @@ -1,71 +0,0 @@ -# Project Structure - -```python exec -from pcweb.pages.docs import advanced_onboarding -from pcweb.constants import REFLEX_ASSETS_CDN -``` - -## Directory Structure - -```python exec -app_name = "hello" -``` - -Let's create a new app called `{app_name}` - -```bash -mkdir {app_name} -cd {app_name} -reflex init -``` - -This will create a directory structure like this: - -```bash -{app_name} -├── .web -├── assets -├── {app_name} -│ ├── __init__.py -│ └── {app_name}.py -└── rxconfig.py -``` - -Let's go over each of these directories and files. - -## .web - -This is where the compiled Javascript files will be stored. You will never need to touch this directory, but it can be useful for debugging. - -Each Reflex page will compile to a corresponding `.js` file in the `.web/pages` directory. - -## Assets - -The `assets` directory is where you can store any static assets you want to be publicly available. This includes images, fonts, and other files. - -For example, if you save an image to `assets/image.png` you can display it from your app like this: - -```python -rx.image(src=f"{REFLEX_ASSETS_CDN}other/image.png") -``` -j -## Main Project - -Initializing your project creates a directory with the same name as your app. This is where you will write your app's logic. - -Reflex generates a default app within the `{app_name}/{app_name}.py` file. You can modify this file to customize your app. - -## Configuration - -The `rxconfig.py` file can be used to configure your app. By default it looks something like this: - -```python -import reflex as rx - - -config = rx.Config( - app_name="{app_name}", -) -``` - -We will discuss project structure and configuration in more detail in the [advanced project structure]({advanced_onboarding.code_structure.path}) documentation. \ No newline at end of file diff --git a/docs/library/data-display/avatar.md b/docs/library/data-display/avatar.md deleted file mode 100644 index 9e7f71b852..0000000000 --- a/docs/library/data-display/avatar.md +++ /dev/null @@ -1,146 +0,0 @@ ---- -components: - - rx.avatar -Avatar: | - lambda **props: rx.hstack(rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", **props), rx.avatar(fallback="RX", **props), spacing="3") ---- -# Avatar - -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from pcweb.templates.docpage import style_grid -``` - -The Avatar component is used to represent a user, and display their profile pictures or fallback texts such as initials. - -## Basic Example - -To create an avatar component with an image, pass the image URL as the `src` prop. - -```python demo -rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg") -``` - -To display a text such as initials, set the `fallback` prop without passing the `src` prop. - -```python demo -rx.avatar(fallback="RX") -``` - -## Styling - -```python eval -style_grid(component_used=rx.avatar, component_used_str="rx.avatar", variants=["solid", "soft"], fallback="RX") -``` - -### Size - -The `size` prop controls the size and spacing of the avatar. The acceptable size is from `"1"` to `"9"`, with `"3"` being the default. - -```python demo -rx.flex( - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="1"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="2"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="3"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="4"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="5"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="6"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="7"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", size="8"), - spacing="1", -) -``` - -### Variant - -The `variant` prop controls the visual style of the avatar fallback text. The variant can be `"solid"` or `"soft"`. The default is `"soft"`. - -```python demo -rx.flex( - rx.avatar(fallback="RX", variant="solid"), - rx.avatar(fallback="RX", variant="soft"), - rx.avatar(fallback="RX"), - spacing="2", -) -``` - -### Color Scheme - -The `color_scheme` prop sets a specific color to the fallback text, ignoring the global theme. - -```python demo -rx.flex( - rx.avatar(fallback="RX", color_scheme="indigo"), - rx.avatar(fallback="RX", color_scheme="cyan"), - rx.avatar(fallback="RX", color_scheme="orange"), - rx.avatar(fallback="RX", color_scheme="crimson"), - spacing="2", -) -``` - -### High Contrast - -The `high_contrast` prop increases color contrast of the fallback text with the background. - -```python demo -rx.grid( - rx.avatar(fallback="RX", variant="solid"), - rx.avatar(fallback="RX", variant="solid", high_contrast=True), - rx.avatar(fallback="RX", variant="soft"), - rx.avatar(fallback="RX", variant="soft", high_contrast=True), - rows="2", - spacing="2", - flow="column", -) -``` - -### Radius - -The `radius` prop sets specific radius value, ignoring the global theme. It can take values `"none" | "small" | "medium" | "large" | "full"`. - -```python demo -rx.grid( - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", radius="none"), - rx.avatar(fallback="RX", radius="none"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", radius="small"), - rx.avatar(fallback="RX", radius="small"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", radius="medium"), - rx.avatar(fallback="RX", radius="medium"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", radius="large"), - rx.avatar(fallback="RX", radius="large"), - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RX", radius="full"), - rx.avatar(fallback="RX", radius="full"), - rows="2", - spacing="2", - flow="column", -) -``` - -### Fallback - -The `fallback` prop indicates the rendered text when the `src` cannot be loaded. - -```python demo -rx.flex( - rx.avatar(fallback="RX"), - rx.avatar(fallback="PC"), - spacing="2", -) -``` - -## Final Example - -As part of a user profile page, the Avatar component is used to display the user's profile picture, with the fallback text showing the user's initials. Text components displays the user's full name and username handle and a Button component shows the edit profile button. - -```python demo -rx.flex( - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", fallback="RU", size="9"), - rx.text("Reflex User", weight="bold", size="4"), - rx.text("@reflexuser", color_scheme="gray"), - rx.button("Edit Profile", color_scheme="indigo", variant="solid"), - direction="column", - spacing="1", -) -``` diff --git a/docs/library/data-display/badge.md b/docs/library/data-display/badge.md deleted file mode 100644 index 9ee9225e77..0000000000 --- a/docs/library/data-display/badge.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -components: - - rx.badge - -Badge: | - lambda **props: rx.badge("Basic Badge", **props) ---- -# Badge - -```python exec -import reflex as rx -from pcweb.templates.docpage import style_grid -``` - -Badges are used to highlight an item's status for quick recognition. - -## Basic Example - -To create a badge component with only text inside, pass the text as an argument. - -```python demo -rx.badge("New") -``` - -## Styling - -```python eval -style_grid(component_used=rx.badge, component_used_str="rx.badge", variants=["solid", "soft", "surface", "outline"], components_passed="England!",) -``` - -### Size - -The `size` prop controls the size and padding of a badge. It can take values of `"1" | "2"`, with default being `"1"`. - -```python demo -rx.flex( - rx.badge("New"), - rx.badge("New", size="1"), - rx.badge("New", size="2"), - align="center", - spacing="2", -) -``` - -### Variant - -The `variant` prop controls the visual style of the badge. The supported variant types are `"solid" | "soft" | "surface" | "outline"`. The variant default is `"soft"`. - -```python demo -rx.flex( - rx.badge("New", variant="solid"), - rx.badge("New", variant="soft"), - rx.badge("New"), - rx.badge("New", variant="surface"), - rx.badge("New", variant="outline"), - spacing="2", -) -``` - -### Color Scheme - -The `color_scheme` prop sets a specific color, ignoring the global theme. - -```python demo -rx.flex( - rx.badge("New", color_scheme="indigo"), - rx.badge("New", color_scheme="cyan"), - rx.badge("New", color_scheme="orange"), - rx.badge("New", color_scheme="crimson"), - spacing="2", -) -``` - -### High Contrast - -The `high_contrast` prop increases color contrast of the fallback text with the background. - -```python demo -rx.flex( - rx.flex( - rx.badge("New", variant="solid"), - rx.badge("New", variant="soft"), - rx.badge("New", variant="surface"), - rx.badge("New", variant="outline"), - spacing="2", - ), - rx.flex( - rx.badge("New", variant="solid", high_contrast=True), - rx.badge("New", variant="soft", high_contrast=True), - rx.badge("New", variant="surface", high_contrast=True), - rx.badge("New", variant="outline", high_contrast=True), - spacing="2", - ), - direction="column", - spacing="2", -) -``` - -### Radius - -The `radius` prop sets specific radius value, ignoring the global theme. It can take values `"none" | "small" | "medium" | "large" | "full"`. - -```python demo -rx.flex( - rx.badge("New", radius="none"), - rx.badge("New", radius="small"), - rx.badge("New", radius="medium"), - rx.badge("New", radius="large"), - rx.badge("New", radius="full"), - spacing="3", -) -``` - -## Final Example - -A badge may contain more complex elements within it. This example uses a `flex` component to align an icon and the text correctly, using the `gap` prop to -ensure a comfortable spacing between the two. - -```python demo -rx.badge( - rx.flex( - rx.icon(tag="arrow_up"), - rx.text("8.8%"), - spacing="1", - ), - color_scheme="grass", -) -``` diff --git a/docs/library/data-display/callout-ll.md b/docs/library/data-display/callout-ll.md deleted file mode 100644 index c4b9ef2228..0000000000 --- a/docs/library/data-display/callout-ll.md +++ /dev/null @@ -1,140 +0,0 @@ ---- -components: - - rx.callout.root - - rx.callout.icon - - rx.callout.text ---- - - -```python exec -import reflex as rx -``` - -# Callout - -A `callout` is a short message to attract user's attention. - -```python demo -rx.callout.root( - rx.callout.icon(rx.icon(tag="info")), - rx.callout.text("You will need admin privileges to install and access this application."), -) -``` - -The `callout` component is made up of a `callout.root`, which groups `callout.icon` and `callout.text` parts. This component is based on the `div` element and supports common margin props. - -The `callout.icon` provides width and height for the `icon` associated with the `callout`. This component is based on the `div` element. See the [**icon** component for all icons that are available.](/docs/library/data-display/icon/) - -The `callout.text` renders the callout text. This component is based on the `p` element. - -## As alert - -```python demo -rx.callout.root( - rx.callout.icon(rx.icon(tag="triangle_alert")), - rx.callout.text("Access denied. Please contact the network administrator to view this page."), - color_scheme="red", - role="alert", -) -``` - -## Style - -### Size - -Use the `size` prop to control the size. - -```python demo -rx.flex( - rx.callout.root( - rx.callout.icon(rx.icon(tag="info")), - rx.callout.text("You will need admin privileges to install and access this application."), - size="3", - ), - rx.callout.root( - rx.callout.icon(rx.icon(tag="info")), - rx.callout.text("You will need admin privileges to install and access this application."), - size="2", - ), - rx.callout.root( - rx.callout.icon(rx.icon(tag="info")), - rx.callout.text("You will need admin privileges to install and access this application."), - size="1", - ), - direction="column", - spacing="3", - align="start", -) -``` - -### Variant - -Use the `variant` prop to control the visual style. It is set to `soft` by default. - -```python demo -rx.flex( - rx.callout.root( - rx.callout.icon(rx.icon(tag="info")), - rx.callout.text("You will need admin privileges to install and access this application."), - variant="soft", - ), - rx.callout.root( - rx.callout.icon(rx.icon(tag="info")), - rx.callout.text("You will need admin privileges to install and access this application."), - variant="surface", - ), - rx.callout.root( - rx.callout.icon(rx.icon(tag="info")), - rx.callout.text("You will need admin privileges to install and access this application."), - variant="outline", - ), - direction="column", - spacing="3", -) -``` - -### Color - -Use the `color_scheme` prop to assign a specific color, ignoring the global theme. - -```python demo -rx.flex( - rx.callout.root( - rx.callout.icon(rx.icon(tag="info")), - rx.callout.text("You will need admin privileges to install and access this application."), - color_scheme="blue", - ), - rx.callout.root( - rx.callout.icon(rx.icon(tag="info")), - rx.callout.text("You will need admin privileges to install and access this application."), - color_scheme="green", - ), - rx.callout.root( - rx.callout.icon(rx.icon(tag="info")), - rx.callout.text("You will need admin privileges to install and access this application."), - color_scheme="red", - ), - direction="column", - spacing="3", -) -``` - -### High Contrast - -Use the `high_contrast` prop to add additional contrast. - -```python demo -rx.flex( - rx.callout.root( - rx.callout.icon(rx.icon(tag="info")), - rx.callout.text("You will need admin privileges to install and access this application."), - ), - rx.callout.root( - rx.callout.icon(rx.icon(tag="info")), - rx.callout.text("You will need admin privileges to install and access this application."), - high_contrast=True, - ), - direction="column", - spacing="3", -) -``` diff --git a/docs/library/data-display/callout.md b/docs/library/data-display/callout.md deleted file mode 100644 index 81f8490463..0000000000 --- a/docs/library/data-display/callout.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -components: - - rx.callout - - rx.callout.root - - rx.callout.icon - - rx.callout.text - -Callout: | - lambda **props: rx.callout("Basic Callout", icon="search", **props) - -CalloutRoot: | - lambda **props: rx.callout.root( - rx.callout.icon(rx.icon(tag="info")), - rx.callout.text("You will need admin privileges to install and access this application."), - **props - ) ---- - - -```python exec -import reflex as rx -from pcweb.pages import docs -``` - -# Callout - -A `callout` is a short message to attract user's attention. - -```python demo -rx.callout("You will need admin privileges to install and access this application.", icon="info") -``` - -The `icon` prop allows an icon to be passed to the `callout` component. See the [**icon** component for all icons that are available.](/docs/library/data-display/icon) - -## As alert - -```python demo -rx.callout("Access denied. Please contact the network administrator to view this page.", icon="triangle_alert", color_scheme="red", role="alert") -``` - -## Style - -### Size - -Use the `size` prop to control the size. - -```python demo -rx.flex( - rx.callout("You will need admin privileges to install and access this application.", icon="info", size="3",), - rx.callout("You will need admin privileges to install and access this application.", icon="info", size="2",), - rx.callout("You will need admin privileges to install and access this application.", icon="info", size="1",), - direction="column", - spacing="3", - align="start", -) -``` - -### Variant - -Use the `variant` prop to control the visual style. It is set to `soft` by default. - -```python demo -rx.flex( - rx.callout("You will need admin privileges to install and access this application.", icon="info", variant="soft",), - rx.callout("You will need admin privileges to install and access this application.", icon="info", variant="surface",), - rx.callout("You will need admin privileges to install and access this application.", icon="info", variant="outline",), - direction="column", - spacing="3", -) -``` - -### Color - -Use the `color_scheme` prop to assign a specific color, ignoring the global theme. - -```python demo -rx.flex( - rx.callout("You will need admin privileges to install and access this application.", icon="info", color_scheme="blue",), - rx.callout("You will need admin privileges to install and access this application.", icon="info", color_scheme="green",), - rx.callout("You will need admin privileges to install and access this application.", icon="info", color_scheme="red",), - direction="column", - spacing="3", -) -``` - -### High Contrast - -Use the `high_contrast` prop to add additional contrast. - -```python demo -rx.flex( - rx.callout("You will need admin privileges to install and access this application.", icon="info",), - rx.callout("You will need admin privileges to install and access this application.", icon="info", high_contrast=True,), - direction="column", - spacing="3", -) -``` diff --git a/docs/library/data-display/code_block.md b/docs/library/data-display/code_block.md deleted file mode 100644 index e19f551f7a..0000000000 --- a/docs/library/data-display/code_block.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -components: - - rx.code_block ---- - -```python exec -import reflex as rx -``` - -# Code Block - -The Code Block component can be used to display code easily within a website. -Put in a multiline string with the correct spacing and specify and language to show the desired code. - -```python demo -rx.code_block( - """def fib(n): - if n <= 1: - return n - else: - return(fib(n-1) + fib(n-2))""", - language="python", - show_line_numbers=True, -) -``` diff --git a/docs/library/data-display/data_list.md b/docs/library/data-display/data_list.md deleted file mode 100644 index ebb45f5098..0000000000 --- a/docs/library/data-display/data_list.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -components: - - rx.data_list.root - - rx.data_list.item - - rx.data_list.label - - rx.data_list.value -DataListRoot: | - lambda **props: rx.data_list.root( - rx.foreach( - [["Status", "Authorized"], ["ID", "U-474747"], ["Name", "Developer Success"], ["Email", "foo@reflex.dev"]], - lambda item: rx.data_list.item(rx.data_list.label(item[0]), rx.data_list.value(item[1])), - ), - **props, - ) -DataListItem: | - lambda **props: rx.data_list.root( - rx.foreach( - [["Status", "Authorized"], ["ID", "U-474747"], ["Name", "Developer Success"], ["Email", "foo@reflex.dev"]], - lambda item: rx.data_list.item(rx.data_list.label(item[0]), rx.data_list.value(item[1]), **props), - ), - ) -DataListLabel: | - lambda **props: rx.data_list.root( - rx.foreach( - [["Status", "Authorized"], ["ID", "U-474747"], ["Name", "Developer Success"], ["Email", "foo@reflex.dev"]], - lambda item: rx.data_list.item(rx.data_list.label(item[0], **props), rx.data_list.value(item[1])), - ), - ) -DataListValue: | - lambda **props: rx.data_list.root( - rx.foreach( - [["Status", "Authorized"], ["ID", "U-474747"], ["Name", "Developer Success"], ["Email", "foo@reflex.dev"]], - lambda item: rx.data_list.item(rx.data_list.label(item[0]), rx.data_list.value(item[1], **props)), - ), - ) ---- - -```python exec -import reflex as rx -``` - -# Data List - -The `DataList` component displays key-value pairs and is particularly helpful for showing metadata. - -A `DataList` needs to be initialized using `rx.data_list.root()` and currently takes in data list items: `rx.data_list.item` - -```python demo -rx.card( - rx.data_list.root( - rx.data_list.item( - rx.data_list.label("Status"), - rx.data_list.value( - rx.badge( - "Authorized", - variant="soft", - radius="full", - ) - ), - align="center", - ), - rx.data_list.item( - rx.data_list.label("ID"), - rx.data_list.value(rx.code("U-474747")), - ), - rx.data_list.item( - rx.data_list.label("Name"), - rx.data_list.value("Developer Success"), - align="center", - ), - rx.data_list.item( - rx.data_list.label("Email"), - rx.data_list.value( - rx.link( - "success@reflex.dev", - href="mailto:success@reflex.dev", - ), - ), - ), - rx.data_list.item( - rx.data_list.label("Company"), - rx.data_list.value( - rx.link( - "Reflex", - href="https://reflex.dev", - ), - ), - ), - ), - ), -``` \ No newline at end of file diff --git a/docs/library/data-display/icon.md b/docs/library/data-display/icon.md deleted file mode 100644 index f2f31a908d..0000000000 --- a/docs/library/data-display/icon.md +++ /dev/null @@ -1,197 +0,0 @@ ---- -components: - - rx.lucide.Icon ---- - -```python exec -import reflex as rx -from pcweb.components.icons.lucide.lucide import lucide_icons -``` - -# Icon - -The Icon component is used to display an icon from a library of icons. This implementation is based on the [Lucide Icons](https://lucide.dev/icons) where you can find a list of all available icons. - - -## Icons List - -```python eval -lucide_icons() -``` - -## Basic Example - -To display an icon, specify the `tag` prop from the list of available icons. -Passing the tag as the first children is also supported and will be assigned to the `tag` prop. - -The `tag` is expected to be in `snake_case` format, but `kebab-case` is also supported to allow copy-paste from [https://lucide.dev/icons](https://lucide.dev/icons). - -```python demo -rx.flex( - rx.icon("calendar"), - rx.icon(tag="calendar"), - gap="2", -) -``` - -## Dynamic Icons - -There are two ways to use dynamic icons in Reflex: - -### Using rx.match - -If you have a specific subset of icons you want to use dynamically, you can define an `rx.match` with them: - -```python -def dynamic_icon_with_match(icon_name): - return rx.match( - icon_name, - ("plus", rx.icon("plus")), - ("minus", rx.icon("minus")), - ("equal", rx.icon("equal")), - ) -``` - -```python exec -def dynamic_icon_with_match(icon_name): - return rx.match( - icon_name, - ("plus", rx.icon("plus")), - ("minus", rx.icon("minus")), - ("equal", rx.icon("equal")), - ) -``` - -### Using Dynamic Icon Tags - -Reflex also supports using dynamic values directly as the `tag` prop in `rx.icon()`. This allows you to use any icon from the Lucide library dynamically at runtime. - -```python exec -class DynamicIconState(rx.State): - current_icon: str = "heart" - - def change_icon(self): - icons = ["heart", "star", "bell", "calendar", "settings"] - import random - self.current_icon = random.choice(icons) -``` - -```python demo -rx.vstack( - rx.heading("Dynamic Icon Example"), - rx.icon(DynamicIconState.current_icon, size=30, color="red"), - rx.button("Change Icon", on_click=DynamicIconState.change_icon), - spacing="4", - align="center", -) -``` - -Under the hood, when a dynamic value is passed as the `tag` prop to `rx.icon()`, Reflex automatically uses a special `DynamicIcon` component that can load icons at runtime. - -```md alert -When using dynamic icons, make sure the icon names are valid. Invalid icon names will cause runtime errors. -``` - -## Styling - -Icon from Lucide can be customized with the following props `stroke_width`, `size` and `color`. - -### Stroke Width - -```python demo -rx.flex( - rx.icon("moon", stroke_width=1), - rx.icon("moon", stroke_width=1.5), - rx.icon("moon", stroke_width=2), - rx.icon("moon", stroke_width=2.5), - gap="2" -) -``` - - -### Size - -```python demo -rx.flex( - rx.icon("zoom_in", size=15), - rx.icon("zoom_in", size=20), - rx.icon("zoom_in", size=25), - rx.icon("zoom_in", size=30), - align="center", - gap="2", -) -``` - -### Color - -Here is an example using basic colors in icons. - -```python demo -rx.flex( - rx.icon("zoom_in", size=18, color="indigo"), - rx.icon("zoom_in", size=18, color="cyan"), - rx.icon("zoom_in", size=18, color="orange"), - rx.icon("zoom_in", size=18, color="crimson"), - gap="2", -) -``` - -A radix color with a scale may also be specified using `rx.color()` as seen below. - -```python demo -rx.flex( - rx.icon("zoom_in", size=18, color=rx.color("purple", 1)), - rx.icon("zoom_in", size=18, color=rx.color("purple", 2)), - rx.icon("zoom_in", size=18, color=rx.color("purple", 3)), - rx.icon("zoom_in", size=18, color=rx.color("purple", 4)), - rx.icon("zoom_in", size=18, color=rx.color("purple", 5)), - rx.icon("zoom_in", size=18, color=rx.color("purple", 6)), - rx.icon("zoom_in", size=18, color=rx.color("purple", 7)), - rx.icon("zoom_in", size=18, color=rx.color("purple", 8)), - rx.icon("zoom_in", size=18, color=rx.color("purple", 9)), - rx.icon("zoom_in", size=18, color=rx.color("purple", 10)), - rx.icon("zoom_in", size=18, color=rx.color("purple", 11)), - rx.icon("zoom_in", size=18, color=rx.color("purple", 12)), - gap="2", -) -``` - -Here is another example using the `accent` color with scales. The `accent` is the most dominant color in your theme. - -```python demo -rx.flex( - rx.icon("zoom_in", size=18, color=rx.color("accent", 1)), - rx.icon("zoom_in", size=18, color=rx.color("accent", 2)), - rx.icon("zoom_in", size=18, color=rx.color("accent", 3)), - rx.icon("zoom_in", size=18, color=rx.color("accent", 4)), - rx.icon("zoom_in", size=18, color=rx.color("accent", 5)), - rx.icon("zoom_in", size=18, color=rx.color("accent", 6)), - rx.icon("zoom_in", size=18, color=rx.color("accent", 7)), - rx.icon("zoom_in", size=18, color=rx.color("accent", 8)), - rx.icon("zoom_in", size=18, color=rx.color("accent", 9)), - rx.icon("zoom_in", size=18, color=rx.color("accent", 10)), - rx.icon("zoom_in", size=18, color=rx.color("accent", 11)), - rx.icon("zoom_in", size=18, color=rx.color("accent", 12)), - gap="2", -) -``` - - -## Final Example - -Icons can be used as child components of many other components. For example, adding a magnifying glass icon to a search bar. - -```python demo -rx.badge( - rx.flex( - rx.icon("search", size=18), - rx.text("Search documentation...", size="3", weight="medium"), - direction="row", - gap="1", - align="center", - ), - size="2", - radius="full", - color_scheme="gray", -) -``` diff --git a/docs/library/data-display/list.md b/docs/library/data-display/list.md deleted file mode 100644 index 941abf9f4e..0000000000 --- a/docs/library/data-display/list.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -components: - - rx.list.item - - rx.list.ordered - - rx.list.unordered ---- - -```python exec -import reflex as rx -``` - -# List - -A `list` is a component that is used to display a list of items, stacked vertically by default. A `list` can be either `ordered` or `unordered`. It is based on the `flex` component and therefore inherits all of its props. - -`list.unordered` has bullet points to display the list items. - -```python demo -rx.list.unordered( - rx.list.item("Example 1"), - rx.list.item("Example 2"), - rx.list.item("Example 3"), -) -``` - - `list.ordered` has numbers to display the list items. - -```python demo -rx.list.ordered( - rx.list.item("Example 1"), - rx.list.item("Example 2"), - rx.list.item("Example 3"), -) -``` - -`list.unordered()` and `list.ordered()` can have no bullet points or numbers by setting the `list_style_type` prop to `none`. -This is effectively the same as using the `list()` component. - -```python demo -rx.hstack( - rx.list( - rx.list.item("Example 1"), - rx.list.item("Example 2"), - rx.list.item("Example 3"), - ), - rx.list.unordered( - rx.list.item("Example 1"), - rx.list.item("Example 2"), - rx.list.item("Example 3"), - list_style_type="none", - ) -) -``` - -Lists can also be used with icons. - -```python demo -rx.list( - rx.list.item( - rx.icon("circle_check_big", color="green"), " Allowed", - ), - rx.list.item( - rx.icon("octagon_x", color="red"), " Not", - ), - rx.list.item( - rx.icon("settings", color="grey"), " Settings" - ), - list_style_type="none", -) -``` diff --git a/docs/library/data-display/moment.md b/docs/library/data-display/moment.md deleted file mode 100644 index 10ac603b5d..0000000000 --- a/docs/library/data-display/moment.md +++ /dev/null @@ -1,170 +0,0 @@ ---- -components: - - rx.moment - ---- - -# Moment - -Displaying date and relative time to now sometimes can be more complicated than necessary. - -To make it easy, Reflex is wrapping [react-moment](https://www.npmjs.com/package/react-moment) under `rx.moment`. - - -```python exec -import reflex as rx -from reflex.utils.serializers import serialize_datetime -from pcweb.templates.docpage import docdemo, docdemobox, doccode, docgraphing -``` - -## Examples - -Using a date from a state var as a value, we will display it in a few different -way using `rx.moment`. - -The `date_now` state var is initialized when the site was deployed. The -button below can be used to update the var to the current datetime, which will -be reflected in the subsequent examples. - -```python demo exec -from datetime import datetime, timezone - -class MomentState(rx.State): - date_now: datetime = datetime.now(timezone.utc) - - @rx.event - def update(self): - self.date_now = datetime.now(timezone.utc) - - -def moment_update_example(): - return rx.button("Update", rx.moment(MomentState.date_now), on_click=MomentState.update) -``` - -### Display the date as-is: - -```python demo -rx.moment(MomentState.date_now) -``` - -### Humanized interval - -Sometimes we don't want to display just a raw date, but we want something more instinctive to read. That's when we can use `from_now` and `to_now`. - -```python demo -rx.moment(MomentState.date_now, from_now=True) -``` - -```python demo -rx.moment(MomentState.date_now, to_now=True) -``` -You can also set a duration (in milliseconds) with `from_now_during` where the date will display as relative, then after that, it will be displayed as defined in `format`. - -```python demo -rx.moment(MomentState.date_now, from_now_during=100000) # after 100 seconds, date will display normally -``` - -### Formatting dates - -```python demo -rx.moment(MomentState.date_now, format="YYYY-MM-DD") -``` - -```python demo -rx.moment(MomentState.date_now, format="HH:mm:ss") -``` - -### Offset Date - -With the props `add` and `subtract`, you can pass an `rx.MomentDelta` object to modify the displayed date without affecting the stored date in your state. - -```python exec -add_example = """rx.vstack( - rx.moment(MomentState.date_now, add=rx.MomentDelta(years=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, add=rx.MomentDelta(quarters=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, add=rx.MomentDelta(months=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, add=rx.MomentDelta(months=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, add=rx.MomentDelta(months=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, add=rx.MomentDelta(weeks=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, add=rx.MomentDelta(days=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, add=rx.MomentDelta(hours=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, add=rx.MomentDelta(minutes=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, add=rx.MomentDelta(seconds=2), format="YYYY-MM-DD - HH:mm:ss"), -) -""" -subtract_example = """rx.vstack( - rx.moment(MomentState.date_now, subtract=rx.MomentDelta(years=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, subtract=rx.MomentDelta(quarters=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, subtract=rx.MomentDelta(months=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, subtract=rx.MomentDelta(months=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, subtract=rx.MomentDelta(months=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, subtract=rx.MomentDelta(weeks=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, subtract=rx.MomentDelta(days=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, subtract=rx.MomentDelta(hours=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, subtract=rx.MomentDelta(minutes=2), format="YYYY-MM-DD - HH:mm:ss"), - rx.moment(MomentState.date_now, subtract=rx.MomentDelta(seconds=2), format="YYYY-MM-DD - HH:mm:ss"), -) -""" -``` - -```python eval -rx.tabs( - rx.tabs.list( - rx.tabs.trigger("Add", value="add"), - rx.tabs.trigger("Subtract", value="subtract") - ), - rx.tabs.content(docdemo(add_example, comp=eval(add_example)), value="add"), - rx.tabs.content(docdemo(subtract_example, comp=eval(subtract_example)), value="subtract"), - default_value="add", -) -``` - -### Timezones - -You can also set dates to display in a specific timezone: - -```python demo -rx.vstack( - rx.moment(MomentState.date_now, tz="America/Los_Angeles"), - rx.moment(MomentState.date_now, tz="Europe/Paris"), - rx.moment(MomentState.date_now, tz="Asia/Tokyo"), -) -``` - -### Client-side periodic update - -If a date is not passed to `rx.moment`, it will use the client's current time. - -If you want to update the date every second, you can use the `interval` prop. - -```python demo -rx.moment(interval=1000, format="HH:mm:ss") -``` - -Even better, you can actually link an event handler to the `on_change` prop that will be called every time the date is updated: - -```python demo exec -class MomentLiveState(rx.State): - updating: bool = False - - @rx.event - def on_update(self, date): - return rx.toast(f"Date updated: {date}") - - @rx.event - def set_updating(self, value: bool): - self.updating = value - -def moment_live_example(): - return rx.hstack( - rx.moment( - format="HH:mm:ss", - interval=rx.cond(MomentLiveState.updating, 5000, 0), - on_change=MomentLiveState.on_update, - ), - rx.switch( - is_checked=MomentLiveState.updating, - on_change=MomentLiveState.set_updating, - ), - ) -``` diff --git a/docs/library/data-display/progress.md b/docs/library/data-display/progress.md deleted file mode 100644 index a5df0dd478..0000000000 --- a/docs/library/data-display/progress.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -components: - - rx.progress - -Progress: | - lambda **props: rx.progress(value=50, **props) ---- - -# Progress - -Progress is used to display the progress status for a task that takes a long time or consists of several steps. - -```python exec -import reflex as rx -``` -## Basic Example - -`rx.progress` expects the `value` prop to set the progress value. -`width` is default to 100%, the width of its parent component. - -```python demo -rx.vstack( - rx.progress(value=0), - rx.progress(value=50), - rx.progress(value=100), - width="50%", -) -``` - -For a dynamic progress, you can assign a state variable to the `value` prop instead of a constant value. - -```python demo exec -import asyncio - -class ProgressState(rx.State): - value: int = 0 - - @rx.event(background=True) - async def start_progress(self): - async with self: - self.value = 0 - while self.value < 100: - await asyncio.sleep(0.1) - async with self: - self.value += 1 - - -def live_progress(): - return rx.hstack( - rx.progress(value=ProgressState.value), - rx.button("Start", on_click=ProgressState.start_progress), - width="50%" - ) -``` diff --git a/docs/library/data-display/scroll_area.md b/docs/library/data-display/scroll_area.md deleted file mode 100644 index 17327eae41..0000000000 --- a/docs/library/data-display/scroll_area.md +++ /dev/null @@ -1,239 +0,0 @@ ---- -components: - - rx.scroll_area - -ScrollArea: | - lambda **props: rx.scroll_area( - rx.flex( - rx.text( - """Three fundamental aspects of typography are legibility, readability, and aesthetics. Although in a non-technical sense "legible" and "readable"are often used synonymously, typographically they are separate but related concepts.""", - size="5", - ), - rx.text( - """Legibility describes how easily individual characters can be distinguished from one another. It is described by Walter Tracy as "the quality of being decipherable and recognisable". For instance, if a "b" and an "h", or a "3" and an "8", are difficult to distinguish at small sizes, this is a problem of legibility.""", - size="5", - ), - direction="column", - spacing="4", - height="100px", - width="50%", - ), - **props - ) - ---- - - -```python exec -import random -import reflex as rx -from pcweb.templates.docpage import style_grid -``` - -# Scroll Area - -Custom styled, cross-browser scrollable area using native functionality. - -## Basic Example - -```python demo -rx.scroll_area( - rx.flex( - rx.text( - """Three fundamental aspects of typography are legibility, readability, and - aesthetics. Although in a non-technical sense “legible” and “readable” - are often used synonymously, typographically they are separate but - related concepts.""", - ), - rx.text( - """Legibility describes how easily individual characters can be - distinguished from one another. It is described by Walter Tracy as “the - quality of being decipherable and recognisable”. For instance, if a “b” - and an “h”, or a “3” and an “8”, are difficult to distinguish at small - sizes, this is a problem of legibility.""", - ), - rx.text( - """Typographers are concerned with legibility insofar as it is their job to - select the correct font to use. Brush Script is an example of a font - containing many characters that might be difficult to distinguish. The - selection of cases influences the legibility of typography because using - only uppercase letters (all-caps) reduces legibility.""", - ), - direction="column", - spacing="4", - ), - type="always", - scrollbars="vertical", - style={"height": 180}, - -) - -``` - -## Control the scrollable axes - -Use the `scrollbars` prop to limit scrollable axes. This prop can take values `"vertical" | "horizontal" | "both"`. - -```python demo -rx.grid( - rx.scroll_area( - rx.flex( - rx.text( - """Three fundamental aspects of typography are legibility, readability, and - aesthetics. Although in a non-technical sense "legible" and "readable" - are often used synonymously, typographically they are separate but - related concepts.""", - size="2", trim="both", - ), - rx.text( - """Legibility describes how easily individual characters can be - distinguished from one another. It is described by Walter Tracy as "the - quality of being decipherable and recognisable". For instance, if a "b" - and an "h", or a "3" and an "8", are difficult to distinguish at small - sizes, this is a problem of legibility.""", - size="2", trim="both", - ), - padding="8px", padding_right="48px", direction="column", spacing="4", - ), - type="always", - scrollbars="vertical", - style={"height": 150}, - ), - rx.scroll_area( - rx.flex( - rx.text( - """Three fundamental aspects of typography are legibility, readability, and - aesthetics. Although in a non-technical sense "legible" and "readable" - are often used synonymously, typographically they are separate but - related concepts.""", - size="2", trim="both", - ), - rx.text( - """Legibility describes how easily individual characters can be - distinguished from one another. It is described by Walter Tracy as "the - quality of being decipherable and recognisable". For instance, if a "b" - and an "h", or a "3" and an "8", are difficult to distinguish at small - sizes, this is a problem of legibility.""", - size="2", trim="both", - ), - padding="8px", spacing="4", style={"width": 700}, - ), - type="always", - scrollbars="horizontal", - style={"height": 150}, - ), - rx.scroll_area( - rx.flex( - rx.text( - """Three fundamental aspects of typography are legibility, readability, and - aesthetics. Although in a non-technical sense "legible" and "readable" - are often used synonymously, typographically they are separate but - related concepts.""", - size="2", trim="both", - ), - rx.text( - """Legibility describes how easily individual characters can be - distinguished from one another. It is described by Walter Tracy as "the - quality of being decipherable and recognisable". For instance, if a "b" - and an "h", or a "3" and an "8", are difficult to distinguish at small - sizes, this is a problem of legibility.""", - size="2", trim="both", - ), - padding="8px", spacing="4", style={"width": 400}, - ), - type="always", - scrollbars="both", - style={"height": 150}, - ), - columns="3", - spacing="2", -) -``` - -## Setting the type of the Scrollbars - -The `type` prop describes the nature of scrollbar visibility. - -`auto` means that scrollbars are visible when content is overflowing on the corresponding orientation. - -`always` means that scrollbars are always visible regardless of whether the content is overflowing. - -`scroll` means that scrollbars are visible when the user is scrolling along its corresponding orientation. - -`hover` when the user is scrolling along its corresponding orientation and when the user is hovering over the scroll area. - -```python demo -rx.grid( - rx.scroll_area( - rx.flex( - rx.text("type = 'auto'", weight="bold"), - rx.text( - """Legibility describes how easily individual characters can be - distinguished from one another. It is described by Walter Tracy as "the - quality of being decipherable and recognisable". For instance, if a "b" - and an "h", or a "3" and an "8", are difficult to distinguish at small - sizes, this is a problem of legibility.""", - size="2", trim="both", - ), - padding="8px", direction="column", spacing="4", - ), - type="auto", - scrollbars="vertical", - style={"height": 150}, - ), - rx.scroll_area( - rx.flex( - rx.text("type = 'always'", weight="bold"), - rx.text( - """Legibility describes how easily individual characters can be - distinguished from one another. It is described by Walter Tracy as "the - quality of being decipherable and recognisable". For instance, if a "b" - and an "h", or a "3" and an "8", are difficult to distinguish at small - sizes, this is a problem of legibility.""", - size="2", trim="both", - ), - padding="8px", direction="column", spacing="4", - ), - type="always", - scrollbars="vertical", - style={"height": 150}, - ), - rx.scroll_area( - rx.flex( - rx.text("type = 'scroll'", weight="bold"), - rx.text( - """Legibility describes how easily individual characters can be - distinguished from one another. It is described by Walter Tracy as "the - quality of being decipherable and recognisable". For instance, if a "b" - and an "h", or a "3" and an "8", are difficult to distinguish at small - sizes, this is a problem of legibility.""", - size="2", trim="both", - ), - padding="8px", direction="column", spacing="4", - ), - type="scroll", - scrollbars="vertical", - style={"height": 150}, - ), - rx.scroll_area( - rx.flex( - rx.text("type = 'hover'", weight="bold"), - rx.text( - """Legibility describes how easily individual characters can be - distinguished from one another. It is described by Walter Tracy as "the - quality of being decipherable and recognisable". For instance, if a "b" - and an "h", or a "3" and an "8", are difficult to distinguish at small - sizes, this is a problem of legibility.""", - size="2", trim="both", - ), - padding="8px", direction="column", spacing="4", - ), - type="hover", - scrollbars="vertical", - style={"height": 150}, - ), - columns="4", - spacing="2", -) - -``` diff --git a/docs/library/data-display/spinner.md b/docs/library/data-display/spinner.md deleted file mode 100644 index 90850f3f45..0000000000 --- a/docs/library/data-display/spinner.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -components: - - rx.spinner ---- - -# Spinner - -Spinner is used to display an animated loading indicator when a task is in progress. - -```python exec -import reflex as rx -``` - -```python demo -rx.spinner() -``` - -## Basic Examples - -Spinner can have different sizes. - -```python demo -rx.vstack( - rx.hstack( - rx.spinner(size="1"), - rx.spinner(size="2"), - rx.spinner(size="3"), - align="center", - gap="1em" - ) -) -``` - -## Demo with buttons - -Buttons have their own loading prop that automatically composes a spinner. - -```python demo -rx.button("Bookmark", loading=True) -``` - -## Spinner inside a button - -If you have an icon inside the button, you can use the button's disabled state and wrap the icon in a standalone rx.spinner to achieve a more sophisticated design. - -```python demo -rx.button( - rx.spinner( - loading=True - ), - "Bookmark", - disabled=True -) -``` - - diff --git a/docs/library/disclosure/accordion.md b/docs/library/disclosure/accordion.md deleted file mode 100644 index 4a2a9eda9b..0000000000 --- a/docs/library/disclosure/accordion.md +++ /dev/null @@ -1,359 +0,0 @@ ---- -components: - - rx.accordion.root - - rx.accordion.item - -AccordionRoot: | - lambda **props: rx.accordion.root( - rx.accordion.item(header="First Item", content="The first accordion item's content"), - rx.accordion.item( - header="Second Item", content="The second accordion item's content", - ), - rx.accordion.item(header="Third item", content="The third accordion item's content"), - width="300px", - **props, - ) - -AccordionItem: | - lambda **props: rx.accordion.root( - rx.accordion.item(header="First Item", content="The first accordion item's content", **props), - rx.accordion.item( - header="Second Item", content="The second accordion item's content", **props, - ), - rx.accordion.item(header="Third item", content="The third accordion item's content", **props), - width="300px", - ) ---- - -```python exec -import reflex as rx -``` - -# Accordion - -An accordion is a vertically stacked set of interactive headings that each reveal an associated section of content. -The accordion component is made up of `accordion`, which is the root of the component and takes in an `accordion.item`, -which contains all the contents of the collapsible section. - -## Basic Example - -```python demo -rx.accordion.root( - rx.accordion.item(header="First Item", content="The first accordion item's content"), - rx.accordion.item( - header="Second Item", content="The second accordion item's content", - ), - rx.accordion.item(header="Third item", content="The third accordion item's content"), - width="300px", -) -``` - -## Styling - -### Type - -We use the `type` prop to determine whether multiple items can be opened at once. The allowed values for this prop are -`single` and `multiple` where `single` will only open one item at a time. The default value for this prop is `single`. - -```python demo -rx.accordion.root( - rx.accordion.item(header="First Item", content="The first accordion item's content"), - rx.accordion.item( - header="Second Item", content="The second accordion item's content", - ), - rx.accordion.item(header="Third item", content="The third accordion item's content"), - collapsible=True, - width="300px", - type="multiple", -) -``` - -### Default Value - -We use the `default_value` prop to specify which item should open by default. The value for this prop should be any of the -unique values set by an `accordion.item`. - -```python demo -rx.flex( - rx.accordion.root( - rx.accordion.item( - header="First Item", - content="The first accordion item's content", - value="item_1", - ), - rx.accordion.item( - header="Second Item", - content="The second accordion item's content", - value="item_2", - ), - rx.accordion.item( - header="Third item", - content="The third accordion item's content", - value="item_3", - ), - width="300px", - default_value="item_2", - ), - direction="row", - spacing="2" -) -``` - -### Collapsible - -We use the `collapsible` prop to allow all items to close. If set to `False`, an opened item cannot be closed. - -```python demo -rx.flex( - rx.accordion.root( - rx.accordion.item(header="First Item", content="The first accordion item's content"), - rx.accordion.item(header="Second Item", content="The second accordion item's content"), - rx.accordion.item(header="Third item", content="The third accordion item's content"), - collapsible=True, - width="300px", - ), - rx.accordion.root( - rx.accordion.item(header="First Item", content="The first accordion item's content"), - rx.accordion.item(header="Second Item", content="The second accordion item's content"), - rx.accordion.item(header="Third item", content="The third accordion item's content"), - collapsible=False, - width="300px", - ), - direction="row", - spacing="2" -) -``` - -### Disable - -We use the `disabled` prop to prevent interaction with the accordion and all its items. - -```python demo -rx.accordion.root( - rx.accordion.item(header="First Item", content="The first accordion item's content"), - rx.accordion.item(header="Second Item", content="The second accordion item's content"), - rx.accordion.item(header="Third item", content="The third accordion item's content"), - collapsible=True, - width="300px", - disabled=True, -) -``` - -### Orientation - -We use `orientation` prop to set the orientation of the accordion to `vertical` or `horizontal`. By default, the orientation -will be set to `vertical`. Note that, the orientation prop won't change the visual orientation but the -functional orientation of the accordion. This means that for vertical orientation, the up and down arrow keys moves focus between the next or previous item, -while for horizontal orientation, the left or right arrow keys moves focus between items. - -```python demo -rx.accordion.root( - rx.accordion.item(header="First Item", content="The first accordion item's content"), - rx.accordion.item( - header="Second Item", content="The second accordion item's content", - ), - rx.accordion.item(header="Third item", content="The third accordion item's content"), - collapsible=True, - width="300px", - orientation="vertical", -) -``` - -```python demo -rx.accordion.root( - rx.accordion.item(header="First Item", content="The first accordion item's content"), - rx.accordion.item( - header="Second Item", content="The second accordion item's content", - ), - rx.accordion.item(header="Third item", content="The third accordion item's content"), - collapsible=True, - width="300px", - orientation="horizontal", -) -``` - -### Variant - -```python demo -rx.flex( - rx.accordion.root( - rx.accordion.item(header="First Item", content="The first accordion item's content"), - rx.accordion.item( - header="Second Item", content="The second accordion item's content", - ), - rx.accordion.item(header="Third item", content="The third accordion item's content"), - collapsible=True, - variant="classic", - ), - rx.accordion.root( - rx.accordion.item(header="First Item", content="The first accordion item's content"), - rx.accordion.item( - header="Second Item", content="The second accordion item's content", - ), - rx.accordion.item(header="Third item", content="The third accordion item's content"), - collapsible=True, - variant="soft", - ), - rx.accordion.root( - rx.accordion.item(header="First Item", content="The first accordion item's content"), - rx.accordion.item( - header="Second Item", content="The second accordion item's content", - ), - rx.accordion.item(header="Third item", content="The third accordion item's content"), - collapsible=True, - variant="outline", - ), - rx.accordion.root( - rx.accordion.item(header="First Item", content="The first accordion item's content"), - rx.accordion.item( - header="Second Item", content="The second accordion item's content", - ), - rx.accordion.item(header="Third item", content="The third accordion item's content"), - collapsible=True, - variant="surface", - ), - rx.accordion.root( - rx.accordion.item(header="First Item", content="The first accordion item's content"), - rx.accordion.item( - header="Second Item", content="The second accordion item's content", - ), - rx.accordion.item(header="Third item", content="The third accordion item's content"), - collapsible=True, - variant="ghost", - ), - direction="row", - spacing="2" -) -``` - -### Color Scheme - -We use the `color_scheme` prop to assign a specific color to the accordion background, ignoring the global theme. - -```python demo -rx.flex( - rx.accordion.root( - rx.accordion.item(header="First Item", content="The first accordion item's content"), - rx.accordion.item( - header="Second Item", content="The second accordion item's content", - ), - rx.accordion.item(header="Third item", content="The third accordion item's content"), - collapsible=True, - width="300px", - color_scheme="grass", - ), - rx.accordion.root( - rx.accordion.item(header="First Item", content="The first accordion item's content"), - rx.accordion.item( - header="Second Item", content="The second accordion item's content", - ), - rx.accordion.item(header="Third item", content="The third accordion item's content"), - collapsible=True, - width="300px", - color_scheme="green", - ), - direction="row", - spacing="2" -) -``` - -### Value - -We use the `value` prop to specify the controlled value of the accordion item that we want to activate. -This property should be used in conjunction with the `on_value_change` event argument. - -```python demo exec -class AccordionState(rx.State): - """The app state.""" - value: str = "item_1" - item_selected: str - - @rx.event - def change_value(self, value): - self.value = value - self.item_selected = f"{value} selected" - - -def index() -> rx.Component: - return rx.theme( - rx.container( - rx.text(AccordionState.item_selected), - rx.flex( - rx.accordion.root( - rx.accordion.item( - header="Is it accessible?", - content=rx.button("Test button"), - value="item_1", - ), - rx.accordion.item( - header="Is it unstyled?", - content="Yes. It's unstyled by default, giving you freedom over the look and feel.", - value="item_2", - ), - rx.accordion.item( - header="Is it finished?", - content="It's still in beta, but it's ready to use in production.", - value="item_3", - ), - collapsible=True, - width="300px", - value=AccordionState.value, - on_value_change=lambda value: AccordionState.change_value(value), - ), - direction="column", - spacing="2", - ), - padding="2em", - text_align="center", - ) - ) -``` - -## AccordionItem - -The accordion item contains all the parts of a collapsible section. - -## Styling - -### Value - -```python demo -rx.accordion.root( - rx.accordion.item( - header="First Item", - content="The first accordion item's content", - value="item_1", - ), - rx.accordion.item( - header="Second Item", - content="The second accordion item's content", - value="item_2", - ), - rx.accordion.item( - header="Third item", - content="The third accordion item's content", - value="item_3", - ), - collapsible=True, - width="300px", -) -``` - -### Disable - -```python demo -rx.accordion.root( - rx.accordion.item( - header="First Item", - content="The first accordion item's content", - disabled=True, - ), - rx.accordion.item( - header="Second Item", content="The second accordion item's content", - ), - rx.accordion.item(header="Third item", content="The third accordion item's content"), - collapsible=True, - width="300px", - color_scheme="blue", -) -``` diff --git a/docs/library/disclosure/segmented_control.md b/docs/library/disclosure/segmented_control.md deleted file mode 100644 index f67748abdd..0000000000 --- a/docs/library/disclosure/segmented_control.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -components: - - rx.segmented_control.root - - rx.segmented_control.item ---- - -```python exec -import reflex as rx - -class SegmentedState(rx.State): - """The app state.""" - - control: str = "test" - - @rx.event - def set_control(self, value: str | list[str]): - self.control = value - - -``` - -# Segmented Control - -Segmented Control offers a clear and accessible way to switch between predefined values and views, e.g., "Inbox," "Drafts," and "Sent." - -With Segmented Control, you can make mutually exclusive choices, where only one option can be active at a time, clear and accessible. Without Segmented Control, end users might have to deal with controls like dropdowns or multiple buttons that don't clearly convey state or group options together visually. - -## Basic Example - -The `Segmented Control` component is made up of a `rx.segmented_control.root` which groups `rx.segmented_control.item`. - -The `rx.segmented_control.item` components define the individual segments of the control, each with a label and a unique value. - -```python demo -rx.vstack( - rx.segmented_control.root( - rx.segmented_control.item("Home", value="home"), - rx.segmented_control.item("About", value="about"), - rx.segmented_control.item("Test", value="test"), - on_change=SegmentedState.set_control, - value=SegmentedState.control, - ), - rx.card( - rx.text(SegmentedState.control, align="left"), - rx.text(SegmentedState.control, align="center"), - rx.text(SegmentedState.control, align="right"), - width="100%", - ), -) -``` - -**In the example above:** - -`on_change` is used to specify a callback function that will be called when the user selects a different segment. In this case, the `SegmentedState.setvar("control")` function is used to update the `control` state variable when the user changes the selected segment. - -`value` prop is used to specify the currently selected segment, which is bound to the `SegmentedState.control` state variable. diff --git a/docs/library/disclosure/tabs.md b/docs/library/disclosure/tabs.md deleted file mode 100644 index 05fdb8a67b..0000000000 --- a/docs/library/disclosure/tabs.md +++ /dev/null @@ -1,330 +0,0 @@ ---- -components: - - rx.tabs.root - - rx.tabs.list - - rx.tabs.trigger - - rx.tabs.content - -only_low_level: - - True - -TabsRoot: | - lambda **props: rx.tabs.root( - rx.tabs.list( - rx.tabs.trigger("Account", value="account"), - rx.tabs.trigger("Documents", value="documents"), - rx.tabs.trigger("Settings", value="settings"), - ), - rx.box( - rx.tabs.content( - rx.text("Make changes to your account"), - value="account", - ), - rx.tabs.content( - rx.text("Update your documents"), - value="documents", - ), - rx.tabs.content( - rx.text("Edit your personal profile"), - value="settings", - ), - ), - **props, - ) - -TabsList: | - lambda **props: rx.tabs.root( - rx.tabs.list( - rx.tabs.trigger("Account", value="account"), - rx.tabs.trigger("Documents", value="documents"), - rx.tabs.trigger("Settings", value="settings"), - **props, - ), - rx.box( - rx.tabs.content( - rx.text("Make changes to your account"), - value="account", - ), - rx.tabs.content( - rx.text("Update your documents"), - value="documents", - ), - rx.tabs.content( - rx.text("Edit your personal profile"), - value="settings", - ), - ), - ) - -TabsTrigger: | - lambda **props: rx.tabs.root( - rx.tabs.list( - rx.tabs.trigger("Account", value="account", **props,), - rx.tabs.trigger("Documents", value="documents"), - rx.tabs.trigger("Settings", value="settings"), - ), - rx.box( - rx.tabs.content( - rx.text("Make changes to your account"), - value="account", - ), - rx.tabs.content( - rx.text("Update your documents"), - value="documents", - ), - rx.tabs.content( - rx.text("Edit your personal profile"), - value="settings", - ), - ), - ) - -TabsContent: | - lambda **props: rx.tabs.root( - rx.tabs.list( - rx.tabs.trigger("Account", value="account"), - rx.tabs.trigger("Documents", value="documents"), - rx.tabs.trigger("Settings", value="settings"), - ), - rx.box( - rx.tabs.content( - rx.text("Make changes to your account"), - value="account", - **props, - ), - rx.tabs.content( - rx.text("Update your documents"), - value="documents", - **props, - ), - rx.tabs.content( - rx.text("Edit your personal profile"), - value="settings", - **props, - ), - ), - ) ---- - -```python exec -import reflex as rx -``` - -# Tabs - -Tabs are a set of layered sections of content—known as tab panels that are displayed one at a time. -They facilitate the organization and navigation between sets of content that share a connection and exist at a similar level of hierarchy. - -## Basic Example - -```python demo -rx.tabs.root( - rx.tabs.list( - rx.tabs.trigger("Tab 1", value="tab1"), - rx.tabs.trigger("Tab 2", value="tab2") - ), - rx.tabs.content( - rx.text("item on tab 1"), - value="tab1", - ), - rx.tabs.content( - rx.text("item on tab 2"), - value="tab2", - ), -) - -``` - -The `tabs` component is made up of a `rx.tabs.root` which groups `rx.tabs.list` and `rx.tabs.content` parts. - -## Styling - -### Default value - -We use the `default_value` prop to set a default active tab, this will select the specified tab by default. - -```python demo -rx.tabs.root( - rx.tabs.list( - rx.tabs.trigger("Tab 1", value="tab1"), - rx.tabs.trigger("Tab 2", value="tab2") - ), - rx.tabs.content( - rx.text("item on tab 1"), - value="tab1", - ), - rx.tabs.content( - rx.text("item on tab 2"), - value="tab2", - ), - default_value="tab2", -) -``` - -### Orientation - -We use `orientation` prop to set the orientation of the tabs component to `vertical` or `horizontal`. By default, the orientation -will be set to `horizontal`. Setting this value will change both the visual orientation and the functional orientation. - -```md alert info -The functional orientation means for `vertical`, the `up` and `down` arrow keys moves focus between the next or previous tab, -while for `horizontal`, the `left` and `right` arrow keys moves focus between tabs. -``` - -```md alert warning -# When using vertical orientation, make sure to assign a tabs.content for each trigger. - -Defining triggers without content will result in a visual bug where the width of the triggers list isn't constant. -``` - -```python demo -rx.tabs.root( - rx.tabs.list( - rx.tabs.trigger("Tab 1", value="tab1"), - rx.tabs.trigger("Tab 2", value="tab2") - ), - rx.tabs.content( - rx.text("item on tab 1"), - value="tab1", - ), - rx.tabs.content( - rx.text("item on tab 2"), - value="tab2", - ), - default_value="tab1", - orientation="vertical", -) -``` - -```python demo -rx.tabs.root( - rx.tabs.list( - rx.tabs.trigger("Tab 1", value="tab1"), - rx.tabs.trigger("Tab 2", value="tab2") - ), - rx.tabs.content( - rx.text("item on tab 1"), - value="tab1", - ), - rx.tabs.content( - rx.text("item on tab 2"), - value="tab2", - ), - default_value="tab1", - orientation="horizontal", -) -``` - -### Value - -We use the `value` prop to specify the controlled value of the tab that we want to activate. This property should be used in conjunction with the `on_change` event argument. - -```python demo exec -class TabsState(rx.State): - """The app state.""" - - value = "tab1" - tab_selected = "" - - @rx.event - def change_value(self, val): - self.tab_selected = f"{val} clicked!" - self.value = val - - -def index() -> rx.Component: - return rx.container( - rx.flex( - rx.text(f"{TabsState.value} clicked !"), - rx.tabs.root( - rx.tabs.list( - rx.tabs.trigger("Tab 1", value="tab1"), - rx.tabs.trigger("Tab 2", value="tab2"), - ), - rx.tabs.content( - rx.text("items on tab 1"), - value="tab1", - ), - rx.tabs.content( - rx.text("items on tab 2"), - value="tab2", - ), - default_value="tab1", - value=TabsState.value, - on_change=lambda x: TabsState.change_value(x), - ), - direction="column", - spacing="2", - ), - padding="2em", - font_size="2em", - text_align="center", - ) -``` - -## Tablist - -The Tablist is used to list the respective tabs to the tab component - -## Tab Trigger - -This is the button that activates the tab's associated content. It is typically used in the `Tablist` - -## Styling - -### Value - -We use the `value` prop to assign a unique value that associates the trigger with content. - -```python demo -rx.tabs.root( - rx.tabs.list( - rx.tabs.trigger("Tab 1", value="tab1"), - rx.tabs.trigger("Tab 2", value="tab2"), - rx.tabs.trigger("Tab 3", value="tab3") - ), -) -``` - -### Disable - -We use the `disabled` prop to disable the tab. - -```python demo -rx.tabs.root( - rx.tabs.list( - rx.tabs.trigger("Tab 1", value="tab1"), - rx.tabs.trigger("Tab 2", value="tab2"), - rx.tabs.trigger("Tab 3", value="tab3", disabled=True) - ), -) -``` - -## Tabs Content - -Contains the content associated with each trigger. - -## Styling - -### Value - -We use the `value` prop to assign a unique value that associates the content with a trigger. - -```python -rx.tabs.root( - rx.tabs.list( - rx.tabs.trigger("Tab 1", value="tab1"), - rx.tabs.trigger("Tab 2", value="tab2") - ), - rx.tabs.content( - rx.text("item on tab 1"), - value="tab1", - ), - rx.tabs.content( - rx.text("item on tab 2"), - value="tab2", - ), - default_value="tab1", - orientation="vertical", -) -``` diff --git a/docs/library/dynamic-rendering/auto_scroll.md b/docs/library/dynamic-rendering/auto_scroll.md deleted file mode 100644 index 4e6ade214f..0000000000 --- a/docs/library/dynamic-rendering/auto_scroll.md +++ /dev/null @@ -1,70 +0,0 @@ -```python exec -import reflex as rx -``` - -# Auto Scroll - -The `rx.auto_scroll` component is a div that automatically scrolls to the bottom when new content is added. This is useful for chat interfaces, logs, or any container where new content is dynamically added and you want to ensure the most recent content is visible. - -## Basic Usage - -```python demo exec -import reflex as rx - -class AutoScrollState(rx.State): - messages: list[str] = ["Initial message"] - - def add_message(self): - self.messages.append(f"New message #{len(self.messages) + 1}") - -def auto_scroll_example(): - return rx.vstack( - rx.auto_scroll( - rx.foreach( - AutoScrollState.messages, - lambda message: rx.box( - message, - padding="0.5em", - border_bottom="1px solid #eee", - width="100%" - ) - ), - height="200px", - width="300px", - border="1px solid #ddd", - border_radius="md", - ), - rx.button("Add Message", on_click=AutoScrollState.add_message), - width="300px", - align_items="center", - ) -``` - -The `auto_scroll` component automatically scrolls to show the newest content when it's added. In this example, each time you click "Add Message", a new message is added to the list and the container automatically scrolls to display it. - -## When to Use Auto Scroll - -- **Chat applications**: Keep the chat window scrolled to the most recent messages. -- **Log viewers**: Automatically follow new log entries as they appear. -- **Feed interfaces**: Keep the newest content visible in dynamically updating feeds. - -## Props - -`rx.auto_scroll` is based on the `rx.div` component and inherits all of its props. By default, it sets `overflow="auto"` to enable scrolling. - -Some common props you might use with `auto_scroll`: - -- `height`: Set the height of the scrollable container. -- `width`: Set the width of the scrollable container. -- `padding`: Add padding inside the container. -- `border`: Add a border around the container. -- `border_radius`: Round the corners of the container. - -## How It Works - -The component tracks when new content is added and maintains the scroll position in two scenarios: - -1. When the user is already near the bottom of the content (within 50 pixels), it will scroll to the bottom when new content is added. -2. When the container didn't have a scrollbar before but does now (due to new content), it will automatically scroll to the bottom. - -This behavior ensures that users can scroll up to view older content without being forced back to the bottom, while still automatically following new content in most cases. diff --git a/docs/library/dynamic-rendering/cond.md b/docs/library/dynamic-rendering/cond.md deleted file mode 100644 index 1fbd4dab8e..0000000000 --- a/docs/library/dynamic-rendering/cond.md +++ /dev/null @@ -1,161 +0,0 @@ -```python exec -import reflex as rx -``` - -# Cond - -This component is used to conditionally render components. - -The cond component takes a condition and two components. -If the condition is `True`, the first component is rendered, otherwise the second component is rendered. - -```python demo exec -class CondState(rx.State): - show: bool = True - - @rx.event - def change(self): - self.show = not (self.show) - - -def cond_example(): - return rx.vstack( - rx.button("Toggle", on_click=CondState.change), - rx.cond(CondState.show, rx.text("Text 1", color="blue"), rx.text("Text 2", color="red")), - ) -``` - -The second component is optional and can be omitted. -If it is omitted, nothing is rendered if the condition is `False`. - -```python demo exec -class CondOptionalState(rx.State): - show_optional: bool = True - - @rx.event - def toggle_optional(self): - self.show_optional = not (self.show_optional) - - -def cond_optional_example(): - return rx.vstack( - rx.button("Toggle", on_click=CondOptionalState.toggle_optional), - rx.cond(CondOptionalState.show_optional, rx.text("This text appears when condition is True", color="green")), - rx.text("This text is always visible", color="gray"), - ) -``` - -```md video https://youtube.com/embed/ITOZkzjtjUA?start=6040&end=6463 -# Video: Conditional Rendering -``` - -## Negation - -You can use the logical operator `~` to negate a condition. - -```python -rx.vstack( - rx.button("Toggle", on_click=CondState.change), - rx.cond(CondState.show, rx.text("Text 1", color="blue"), rx.text("Text 2", color="red")), - rx.cond(~CondState.show, rx.text("Text 1", color="blue"), rx.text("Text 2", color="red")), -) -``` - -## Multiple Conditions - -It is also possible to make up complex conditions using the `logical or` (|) and `logical and` (&) operators. - -Here we have an example using the var operators `>=`, `<=`, `&`. We define a condition that if a person has an age between 18 and 65, including those ages, they are able to work, otherwise they cannot. - -We could equally use the operator `|` to represent a `logical or` in one of our conditions. - -```python demo exec -import random - -class CondComplexState(rx.State): - age: int = 19 - - @rx.event - def change(self): - self.age = random.randint(0, 100) - - -def cond_complex_example(): - return rx.vstack( - rx.button("Toggle", on_click=CondComplexState.change), - rx.text(f"Age: {CondComplexState.age}"), - rx.cond( - (CondComplexState.age >= 18) & (CondComplexState.age <=65), - rx.text("You can work!", color="green"), - rx.text("You cannot work!", color="red"), - ), - ) - -``` - -## Nested Conditional - -We can also nest `cond` components within each other to create more complex logic. In python we can have an `if` statement that then has several `elif` statements before finishing with an `else`. This is also possible in reflex using nested `cond` components. In this example we check whether a number is positive, negative or zero. - -Here is the python logic using `if` statements: - -```python -number = 0 - -if number > 0: - print("Positive number") - -elif number == 0: - print('Zero') -else: - print('Negative number') -``` - -This reflex code that is logically identical: - -```python demo exec -import random - - -class NestedState(rx.State): - - num: int = 0 - - def change(self): - self.num = random.randint(-10, 10) - - -def cond_nested_example(): - return rx.vstack( - rx.button("Toggle", on_click=NestedState.change), - rx.cond( - NestedState.num > 0, - rx.text(f"{NestedState.num} is Positive!", color="orange"), - rx.cond( - NestedState.num == 0, - rx.text(f"{NestedState.num} is Zero!", color="blue"), - rx.text(f"{NestedState.num} is Negative!", color="red"), - ) - ), - ) - -``` - -Here is a more advanced example where we have three numbers and we are checking which of the three is the largest. If any two of them are equal then we return that `Some of the numbers are equal!`. - -The reflex code that follows is logically identical to doing the following in python: - -```python -a = 8 -b = 10 -c = 2 - -if((a>b and a>c) and (a != b and a != c)): - print(a, " is the largest!") -elif((b>a and b>c) and (b != a and b != c)): - print(b, " is the largest!") -elif((c>a and c>b) and (c != a and c != b)): - print(c, " is the largest!") -else: - print("Some of the numbers are equal!") -``` diff --git a/docs/library/dynamic-rendering/foreach.md b/docs/library/dynamic-rendering/foreach.md deleted file mode 100644 index 7b107e86c3..0000000000 --- a/docs/library/dynamic-rendering/foreach.md +++ /dev/null @@ -1,199 +0,0 @@ -```python exec -import reflex as rx -``` - -# Foreach - -The `rx.foreach` component takes an iterable(list, tuple or dict) and a function that renders each item in the list. -This is useful for dynamically rendering a list of items defined in a state. - -```md alert warning -# `rx.foreach` is specialized for usecases where the iterable is defined in a state var. - -For an iterable where the content doesn't change at runtime, i.e a constant, using a list/dict comprehension instead of `rx.foreach` is preferred. -``` - -```python demo exec -from typing import List -class ForeachState(rx.State): - color: List[str] = ["red", "green", "blue", "yellow", "orange", "purple"] - -def colored_box(color: str): - return rx.box( - rx.text(color), - bg=color - ) - -def foreach_example(): - return rx.grid( - rx.foreach( - ForeachState.color, - colored_box - ), - columns="2", - ) -``` - -The function can also take an index as a second argument. - -```python demo exec -def colored_box_index(color: str, index: int): - return rx.box( - rx.text(index), - bg=color - ) - -def foreach_example_index(): - return rx.grid( - rx.foreach( - ForeachState.color, - lambda color, index: colored_box_index(color, index) - ), - columns="2", - ) -``` - -Nested foreach components can be used to render nested lists. - -When indexing into a nested list, it's important to declare the list's type as Reflex requires it for type checking. -This ensures that any potential frontend JS errors are caught before the user can encounter them. - -```python demo exec -from typing import List - -class NestedForeachState(rx.State): - numbers: List[List[str]] = [["1", "2", "3"], ["4", "5", "6"], ["7", "8", "9"]] - -def display_row(row): - return rx.hstack( - rx.foreach( - row, - lambda item: rx.box( - item, - border="1px solid black", - padding="0.5em", - ) - ), - ) - -def nested_foreach_example(): - return rx.vstack( - rx.foreach( - NestedForeachState.numbers, - display_row - ) - ) -``` - -Below is a more complex example of foreach within a todo list. - -```python demo exec -from typing import List -class ListState(rx.State): - items: List[str] = ["Write Code", "Sleep", "Have Fun"] - new_item: str - - @rx.event - def set_new_item(self, new_item: str): - self.new_item = new_item - - @rx.event - def add_item(self): - self.items += [self.new_item] - - def finish_item(self, item: str): - self.items = [i for i in self.items if i != item] - -def get_item(item): - return rx.list.item( - rx.hstack( - rx.button( - on_click=lambda: ListState.finish_item(item), - height="1.5em", - background_color="white", - border="1px solid blue", - ), - rx.text(item, font_size="1.25em"), - ), - ) - -def todo_example(): - return rx.vstack( - rx.heading("Todos"), - rx.input(on_blur=ListState.set_new_item, placeholder="Add a todo...", bg = "white"), - rx.button("Add", on_click=ListState.add_item, bg = "white"), - rx.divider(), - rx.list.ordered( - rx.foreach( - ListState.items, - get_item, - ), - ), - bg = "#ededed", - padding = "1em", - border_radius = "0.5em", - shadow = "lg" - ) -``` - -## Dictionaries - -Items in a dictionary can be accessed as list of key-value pairs. -Using the color example, we can slightly modify the code to use dicts as shown below. - -```python demo exec -from typing import List -class SimpleDictForeachState(rx.State): - color_chart: dict[int, str] = { - 1 : "blue", - 2: "red", - 3: "green" - } - -def display_color(color: List): - # color is presented as a list key-value pair([1, "blue"],[2, "red"], [3, "green"]) - return rx.box(rx.text(color[0]), bg=color[1]) - - -def foreach_dict_example(): - return rx.grid( - rx.foreach( - SimpleDictForeachState.color_chart, - display_color - ), - columns = "2" - ) -``` - -Now let's show a more complex example with dicts using the color example. -Assuming we want to display a dictionary of secondary colors as keys and their constituent primary colors as values, we can modify the code as below: - -```python demo exec -from typing import List, Dict -class ComplexDictForeachState(rx.State): - color_chart: Dict[str, List[str]] = { - "purple": ["red", "blue"], - "orange": ["yellow", "red"], - "green": ["blue", "yellow"] - } - -def display_colors(color: List): - return rx.vstack( - rx.text(color[0], color=color[0]), - rx.hstack( - rx.foreach( - color[1], lambda x: rx.box(rx.text(x, color="black"), bg=x) - ) - - ) - ) - -def foreach_complex_dict_example(): - return rx.grid( - rx.foreach( - ComplexDictForeachState.color_chart, - display_colors - ), - columns="2" - ) -``` diff --git a/docs/library/dynamic-rendering/match.md b/docs/library/dynamic-rendering/match.md deleted file mode 100644 index d258d68d64..0000000000 --- a/docs/library/dynamic-rendering/match.md +++ /dev/null @@ -1,295 +0,0 @@ -```python exec -import reflex as rx -``` - -# Match - -The `rx.match` feature in Reflex serves as a powerful alternative to `rx.cond` for handling conditional statements. -While `rx.cond` excels at conditionally rendering two components based on a single condition, -`rx.match` extends this functionality by allowing developers to handle multiple conditions and their associated components. -This feature is especially valuable when dealing with intricate conditional logic or nested scenarios, -where the limitations of `rx.cond` might lead to less readable and more complex code. - -With `rx.match`, developers can not only handle multiple conditions but also perform structural pattern matching, -making it a versatile tool for managing various scenarios in Reflex applications. - -## Basic Usage - -The `rx.match` function provides a clear and expressive syntax for handling multiple -conditions and their corresponding components: - -```python -rx.match( - condition, - (case_1, component_1), - (case_2, component_2), - ... - default_component, -) - -``` - -- `condition`: The value to match against. -- `(case_i, component_i)`: A Tuple of matching cases and their corresponding return components. -- `default_component`: A special case for the default component when the condition isn't matched by any of the match cases. - -Example - -```python demo exec -from typing import List - -import reflex as rx - - -class MatchState(rx.State): - cat_breed: str = "" - animal_options: List[str] = ["persian", "siamese", "maine coon", "ragdoll", "pug", "corgi"] - - @rx.event - def set_cat_breed(self, breed: str): - self.cat_breed = breed - -def match_demo(): - return rx.flex( - rx.match( - MatchState.cat_breed, - ("persian", rx.text("Persian cat selected.")), - ("siamese", rx.text("Siamese cat selected.")), - ("maine coon", rx.text("Maine Coon cat selected.")), - ("ragdoll", rx.text("Ragdoll cat selected.")), - rx.text("Unknown cat breed selected.") - ), - rx.select.root( - rx.select.trigger(), - rx.select.content( - rx.select.group( - rx.foreach(MatchState.animal_options, lambda x: rx.select.item(x, value=x)) - ), - ), - value=MatchState.cat_breed, - on_change=MatchState.set_cat_breed, - - ), - direction= "column", - gap= "2" - ) -``` - -## Default Case - -The default case in `rx.match` serves as a universal handler for scenarios where none of -the specified match cases aligns with the given match condition. Here are key considerations -when working with the default case: - -- **Placement in the Match Function**: The default case must be the last non-tuple argument in the `rx.match` component. - All match cases should be enclosed in tuples; any non-tuple value is automatically treated as the default case. For example: - -```python -rx.match( - MatchState.cat_breed, - ("persian", rx.text("persian cat selected")), - rx.text("Unknown cat breed selected."), - ("siamese", rx.text("siamese cat selected")), - ) -``` - -The above code snippet will result in an error due to the misplaced default case. - -- **Single Default Case**: Only one default case is allowed in the `rx.match` component. - Attempting to specify multiple default cases will lead to an error. For instance: - -```python -rx.match( - MatchState.cat_breed, - ("persian", rx.text("persian cat selected")), - ("siamese", rx.text("siamese cat selected")), - rx.text("Unknown cat breed selected."), - rx.text("Another unknown cat breed selected.") - ) -``` - -- **Optional Default Case for Component Return Values**: If the match cases in a `rx.match` component - return components, the default case can be optional. In this scenario, if a default case is - not provided, `rx.fragment` will be implicitly assigned as the default. For example: - -```python -rx.match( - MatchState.cat_breed, - ("persian", rx.text("persian cat selected")), - ("siamese", rx.text("siamese cat selected")), - ) -``` - -In this case, `rx.fragment` is the default case. However, not providing a default case for non-component -return values will result in an error: - -```python -rx.match( - MatchState.cat_breed, - ("persian", "persian cat selected"), - ("siamese", "siamese cat selected"), - ) -``` - -The above code snippet will result in an error as a default case must be explicitly -provided in this scenario. - -## Handling Multiple Conditions - -`rx.match` excels in efficiently managing multiple conditions and their corresponding components, -providing a cleaner and more readable alternative compared to nested `rx.cond` structures. - -Consider the following example: - -```python demo exec -from typing import List - -import reflex as rx - - -class MultiMatchState(rx.State): - animal_breed: str = "" - animal_options: List[str] = ["persian", "siamese", "maine coon", "pug", "corgi", "mustang", "rahvan", "football", "golf"] - - @rx.event - def set_animal_breed(self, breed: str): - self.animal_breed = breed - -def multi_match_demo(): - return rx.flex( - rx.match( - MultiMatchState.animal_breed, - ("persian", "siamese", "maine coon", rx.text("Breeds of cats.")), - ("pug", "corgi", rx.text("Breeds of dogs.")), - ("mustang", "rahvan", rx.text("Breeds of horses.")), - rx.text("Unknown animal breed") - ), - rx.select.root( - rx.select.trigger(), - rx.select.content( - rx.select.group( - rx.foreach(MultiMatchState.animal_options, lambda x: rx.select.item(x, value=x)) - ), - ), - value=MultiMatchState.animal_breed, - on_change=MultiMatchState.set_animal_breed, - - ), - direction= "column", - gap= "2" - ) -``` - -In a match case tuple, you can specify multiple conditions. The last value of the match case -tuple is automatically considered as the return value. It's important to note that a match case -tuple should contain a minimum of two elements: a match case and a return value. -The following code snippet will result in an error: - -```python -rx.match( - MatchState.cat_breed, - ("persian",), - ("maine coon", rx.text("Maine Coon cat selected")), - ) -``` - -## Usage as Props - -Similar to `rx.cond`, `rx.match` can be used as prop values, allowing dynamic behavior for UI components: - -```python demo exec -import reflex as rx - - -class MatchPropState(rx.State): - value: int = 0 - - @rx.event - def incr(self): - self.value += 1 - - @rx.event - def decr(self): - self.value -= 1 - - -def match_prop_demo_(): - return rx.flex( - rx.button("decrement", on_click=MatchPropState.decr, background_color="red"), - rx.badge( - MatchPropState.value, - color_scheme= rx.match( - MatchPropState.value, - (1, "red"), - (2, "blue"), - (6, "purple"), - (10, "orange"), - "green" - ), - size="2", - ), - rx.button("increment", on_click=MatchPropState.incr), - align_items="center", - direction= "row", - gap= "3" - ) -``` - -In the example above, the background color property of the box component containing `State.value` changes to red when -`state.value` is 1, blue when its 5, green when its 5, orange when its 15 and black for any other value. - -The example below also shows handling multiple conditions with the match component as props. - -```python demo exec -import reflex as rx - - -class MatchMultiPropState(rx.State): - value: int = 0 - - @rx.event - def incr(self): - self.value += 1 - - @rx.event - def decr(self): - self.value -= 1 - - -def match_multi_prop_demo_(): - return rx.flex( - rx.button("decrement", on_click=MatchMultiPropState.decr, background_color="red"), - rx.badge( - MatchMultiPropState.value, - color_scheme= rx.match( - MatchMultiPropState.value, - (1, 3, 9, "red"), - (2, 4, 5, "blue"), - (6, 8, 12, "purple"), - (10, 15, 20, 25, "orange"), - "green" - ), - size="2", - ), - rx.button("increment", on_click=MatchMultiPropState.incr), - align_items="center", - direction= "row", - gap= "3" - ) -``` - -```md alert warning -# Usage with Structural Pattern Matching - -The `rx.match` component is designed for structural pattern matching. If the value of your match condition evaluates to a boolean (True or False), it is recommended to use `rx.cond` instead. - -Consider the following example, which is more suitable for `rx.cond`:\* -``` - -```python -rx.cond( - MatchPropState.value == 10, - "true value", - "false value" -) -``` diff --git a/docs/library/forms/button.md b/docs/library/forms/button.md deleted file mode 100644 index 7793fe81e9..0000000000 --- a/docs/library/forms/button.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -components: - - rx.button - -Button: | - lambda **props: rx.button("Basic Button", **props) ---- - -```python exec -import reflex as rx -``` - -# Button - -Buttons are essential elements in your application's user interface that users can click to trigger events. - -## Basic Example - -The `on_click` trigger is called when the button is clicked. - -```python demo exec -class CountState(rx.State): - count: int = 0 - - @rx.event - def increment(self): - self.count += 1 - - @rx.event - def decrement(self): - self.count -= 1 - -def counter(): - return rx.flex( - rx.button( - "Decrement", - color_scheme="red", - on_click=CountState.decrement, - ), - rx.heading(CountState.count), - rx.button( - "Increment", - color_scheme="grass", - on_click=CountState.increment, - ), - spacing="3", - ) -``` - -### Loading and Disabled - -The `loading` prop is used to indicate that the action triggered by the button is currently in progress. When set to `True`, the button displays a loading spinner, providing visual feedback to the user that the action is being processed. This also prevents multiple clicks while the button is in the loading state. By default, `loading` is set to `False`. - -The `disabled` prop also prevents the button from being but does not provide a spinner. - -```python demo -rx.flex( - rx.button("Regular"), - rx.button("Loading", loading=True), - rx.button("Disabled", disabled=True), - spacing="2", -) -``` diff --git a/docs/library/forms/checkbox.md b/docs/library/forms/checkbox.md deleted file mode 100644 index ef00cff702..0000000000 --- a/docs/library/forms/checkbox.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -components: - - rx.checkbox - -HighLevelCheckbox: | - lambda **props: rx.checkbox("Basic Checkbox", **props) ---- - -```python exec -import reflex as rx -``` - -# Checkbox - -## Basic Example - -The `on_change` trigger is called when the `checkbox` is clicked. - -```python demo exec -class CheckboxState(rx.State): - checked: bool = False - - @rx.event - def set_checked(self, value: bool): - self.checked = value - -def checkbox_example(): - return rx.vstack( - rx.heading(CheckboxState.checked), - rx.checkbox(on_change=CheckboxState.set_checked), - ) -``` - -The `input` prop is used to set the `checkbox` as a controlled component. - -```python demo exec -class FormCheckboxState(rx.State): - form_data: dict = {} - - @rx.event - def handle_submit(self, form_data: dict): - """Handle the form submit.""" - print(form_data) - self.form_data = form_data - - -def form_checkbox_example(): - return rx.card( - rx.vstack( - rx.heading("Example Form"), - rx.form.root( - rx.hstack( - rx.checkbox( - name="checkbox", - label="Accept terms and conditions", - ), - rx.button("Submit", type="submit"), - width="100%", - ), - on_submit=FormCheckboxState.handle_submit, - reset_on_submit=True, - ), - rx.divider(), - rx.hstack( - rx.heading("Results:"), - rx.badge(FormCheckboxState.form_data.to_string()), - ), - align_items="left", - width="100%", - ), - width="50%", - ) -``` diff --git a/docs/library/forms/form-ll.md b/docs/library/forms/form-ll.md deleted file mode 100644 index 8e5dfdd6f4..0000000000 --- a/docs/library/forms/form-ll.md +++ /dev/null @@ -1,464 +0,0 @@ ---- -components: - - rx.form.root - - rx.form.field - - rx.form.control - - rx.form.label - - rx.form.message - - rx.form.submit - -FormRoot: | - lambda **props: rx.form.root( - rx.form.field( - rx.flex( - rx.form.label("Email"), - rx.form.control( - rx.input( - placeholder="Email Address", - # type attribute is required for "typeMismatch" validation - type="email", - ), - as_child=True, - ), - rx.form.message("Please enter a valid email"), - rx.form.submit( - rx.button("Submit"), - as_child=True, - ), - direction="column", - spacing="2", - align="stretch", - ), - name="email", - ), - **props, - ) - -FormField: | - lambda **props: rx.form.root( - rx.form.field( - rx.flex( - rx.form.label("Email"), - rx.form.control( - rx.input( - placeholder="Email Address", - # type attribute is required for "typeMismatch" validation - type="email", - ), - as_child=True, - ), - rx.form.message("Please enter a valid email", match="typeMismatch"), - rx.form.submit( - rx.button("Submit"), - as_child=True, - ), - direction="column", - spacing="2", - align="stretch", - ), - **props, - ), - reset_on_submit=True, - ) - -FormMessage: | - lambda **props: rx.form.root( - rx.form.field( - rx.flex( - rx.form.label("Email"), - rx.form.control( - rx.input( - placeholder="Email Address", - # type attribute is required for "typeMismatch" validation - type="email", - ), - as_child=True, - ), - rx.form.message("Please enter a valid email", **props,), - rx.form.submit( - rx.button("Submit"), - as_child=True, - ), - direction="column", - spacing="2", - align="stretch", - ), - name="email", - ), - on_submit=lambda form_data: rx.window_alert(form_data.to_string()), - reset_on_submit=True, - ) ---- - -# Form - -```python exec -import reflex as rx -import reflex.components.radix.primitives as rdxp -``` - -```md warning info -# Low Level Form is Experimental - -Please use the High Level Form for now for production. -``` - -Forms are used to collect information from your users. Forms group the inputs and submit them together. - -## Basic Example - -Here is an example of a form collecting an email address, with built-in validation on the email. If email entered is invalid, the form cannot be submitted. Note that the `form.submit` button is not automatically disabled. It is still clickable, but does not submit the form data. After successful submission, an alert window shows up and the form is cleared. There are a few `flex` containers used in the example to control the layout of the form components. - -```python demo -rx.form.root( - rx.form.field( - rx.flex( - rx.form.label("Email"), - rx.form.control( - rx.input( - placeholder="Email Address", - # type attribute is required for "typeMismatch" validation - type="email", - ), - as_child=True, - ), - rx.form.message("Please enter a valid email", match="typeMismatch"), - rx.form.submit( - rx.button("Submit"), - as_child=True, - ), - direction="column", - spacing="2", - align="stretch", - ), - name="email", - ), - on_submit=lambda form_data: rx.window_alert(form_data.to_string()), - reset_on_submit=True, -) -``` - -In this example, the `rx.input` has an attribute `type="email"` and the `form.message` has the attribute `match="typeMismatch"`. Those are required for the form to validate the input by its type. The prop `as_child="True"` is required when using other components to construct a Form component. This example has used `rx.input` to construct the Form Control and `button` the Form Submit. - -## Form Anatomy - -```python eval -rx._x.code_block( - """form.root( - form.field( - form.label(...), - form.control(...), - form.message(...), - ), - form.submit(...), -)""", - language="python", -) -``` - -A Form Root (`form.root`) contains all the parts of a form. The Form Field (`form.field`), Form Submit (`form.submit`), etc should all be inside a Form Root. A Form Field can contain a Form Label (`form.label`), a Form Control (`form.control`), and a Form Message (`form.message`). A Form Label is a label element. A Form Control is where the user enters the input or makes selections. By default, the Form Control is a input. Using other form components to construct the Form Control is supported. To do that, set the prop `as_child=True` on the Form Control. - -```md alert info -The current version of Radix Forms does not support composing **Form Control** with other Radix form primitives such as **Checkbox**, **Select**, etc. -``` - -The Form Message is a validation message which is automatically wired (functionality and accessibility). When the Form Control determines the input is invalid, the Form Message is shown. The `match` prop is to enable [client side validation](#client-side-validation). To perform [server side validation](#server-side-validation), **both** the `force_match` prop of the Form Control and the `server_invalid` prop of the Form Field are set together. - -The Form Submit is by default a button that submits the form. To use another button component as a Form Submit, include that button as a child inside `form.submit` and set the prop `as_child=True`. - -The `on_submit` prop of the Form Root accepts an event handler. It is called with the submitted form data dictionary. To clear the form after submission, set the `reset_on_submit=True` prop. - -## Data Submission - -As previously mentioned, the various pieces of data in the form are submitted together as a dictionary. The form control or the input components must have the `name` attribute. This `name` is the key to get the value from the form data dictionary. If no validation is needed, the form type components such as Checkbox, Radio Groups, TextArea can be included directly under the Form Root instead of inside a Form Control. - -```python demo exec -import reflex as rx -import reflex.components.radix.primitives as rdxp - -class RadixFormSubmissionState(rx.State): - form_data: dict - - @rx.event - def handle_submit(self, form_data: dict): - """Handle the form submit.""" - self.form_data = form_data - - @rx.var - def form_data_keys(self) -> list: - return list(self.form_data.keys()) - - @rx.var - def form_data_values(self) -> list: - return list(self.form_data.values()) - - -def radix_form_submission_example(): - return rx.flex( - rx.form.root( - rx.flex( - rx.flex( - rx.checkbox( - default_checked=True, - name="box1", - ), - rx.text("box1 checkbox"), - direction="row", - spacing="2", - align="center", - ), - rx.radio.root( - rx.flex( - rx.radio.item(value="1"), - "1", - direction="row", - align="center", - spacing="2", - ), - rx.flex( - rx.radio.item(value="2"), - "2", - direction="row", - align="center", - spacing="2", - ), - rx.flex( - rx.radio.item(value="3"), - "3", - direction="row", - align="center", - spacing="2", - ), - default_value="1", - name="box2", - ), - rx.input( - placeholder="box3 textfield input", - name="box3", - ), - rx.select.root( - rx.select.trigger( - placeholder="box4 select", - ), - rx.select.content( - rx.select.group( - rx.select.item( - "Orange", - value="orange" - ), - rx.select.item( - "Apple", - value="apple" - ), - ), - ), - name="box4", - ), - rx.flex( - rx.switch( - default_checked=True, - name="box5", - ), - "box5 switch", - spacing="2", - align="center", - direction="row", - ), - rx.flex( - rx.slider( - default_value=[40], - width="100%", - name="box6", - ), - "box6 slider", - direction="row", - spacing="2", - align="center", - ), - rx.text_area( - placeholder="Enter for box7 textarea", - name="box7", - ), - rx.form.submit( - rx.button("Submit"), - as_child=True, - ), - direction="column", - spacing="4", - ), - on_submit=RadixFormSubmissionState.handle_submit, - ), - rx.divider(size="4"), - rx.text( - "Results", - weight="bold", - ), - rx.foreach(RadixFormSubmissionState.form_data_keys, - lambda key, idx: rx.text(key, " : ", RadixFormSubmissionState.form_data_values[idx]) - ), - direction="column", - spacing="4", - ) -``` - -## Validation - -Server side validation is done through **Computed Vars** on the State. The **Var** should return a boolean flag indicating when input is invalid. Set that **Var** on both the `server_invalid` prop of `form.field` and the `force_match` prop of `form.message`. There is an example how to do that in the [Final Example](#final-example). - -## Final Example - -The final example shows a form that collects username and email during sign-up and validates them using server side validation. When server side validation fails, messages are displayed in red to show what is not accepted in the form, and the submit button is disabled. After submission, the collected form data is displayed in texts below the form and the form is cleared. - -```python demo exec -import re -import reflex as rx -import reflex.components.radix.primitives as rdxp - -class RadixFormState(rx.State): - # These track the user input real time for validation - user_entered_username: str - user_entered_email: str - - # These are the submitted data - username: str - email: str - - mock_username_db: list[str] = ["reflex", "admin"] - - # Add explicit setters - def set_user_entered_username(self, value: str): - self.user_entered_username = value - - def set_user_entered_email(self, value: str): - self.user_entered_email = value - - def set_username(self, value: str): - self.username = value - - def set_email(self, value: str): - self.email = value - - @rx.var - def invalid_email(self) -> bool: - return not re.match(r"[^@]+@[^@]+\.[^@]+", self.user_entered_email) - - @rx.var - def username_empty(self) -> bool: - return not self.user_entered_username.strip() - - @rx.var - def username_is_taken(self) -> bool: - return self.user_entered_username in self.mock_username_db - - @rx.var - def input_invalid(self) -> bool: - return self.invalid_email or self.username_is_taken or self.username_empty - - @rx.event - def handle_submit(self, form_data: dict): - """Handle the form submit.""" - self.username = form_data.get("username") - self.email = form_data.get("email") - -def radix_form_example(): - return rx.flex( - rx.form.root( - rx.flex( - rx.form.field( - rx.flex( - rx.form.label("Username"), - rx.form.control( - rx.input( - placeholder="Username", - # workaround: `name` seems to be required when on_change is set - on_change=RadixFormState.set_user_entered_username, - name="username", - ), - as_child=True, - ), - # server side validation message can be displayed inside a rx.cond - rx.cond( - RadixFormState.username_empty, - rx.form.message( - "Username cannot be empty", - color="var(--red-11)", - ), - ), - # server side validation message can be displayed by `force_match` prop - rx.form.message( - "Username already taken", - # this is a workaround: - # `force_match` does not work without `match` - # This case does not want client side validation - # and intentionally not set `required` on the input - # so "valueMissing" is always false - match="valueMissing", - force_match=RadixFormState.username_is_taken, - color="var(--red-11)", - ), - direction="column", - spacing="2", - align="stretch", - ), - name="username", - server_invalid=RadixFormState.username_is_taken, - ), - rx.form.field( - rx.flex( - rx.form.label("Email"), - rx.form.control( - rx.input( - placeholder="Email Address", - on_change=RadixFormState.set_user_entered_email, - name="email", - ), - as_child=True, - ), - rx.form.message( - "A valid Email is required", - match="valueMissing", - force_match=RadixFormState.invalid_email, - color="var(--red-11)", - ), - direction="column", - spacing="2", - align="stretch", - ), - name="email", - server_invalid=RadixFormState.invalid_email, - ), - rx.form.submit( - rx.button( - "Submit", - disabled=RadixFormState.input_invalid, - ), - as_child=True, - ), - direction="column", - spacing="4", - width="25em", - ), - on_submit=RadixFormState.handle_submit, - reset_on_submit=True, - ), - rx.divider(size="4"), - rx.text( - "Username submitted: ", - rx.text( - RadixFormState.username, - weight="bold", - color="var(--accent-11)", - ), - ), - rx.text( - "Email submitted: ", - rx.text( - RadixFormState.email, - weight="bold", - color="var(--accent-11)", - ), - ), - direction="column", - spacing="4", - ) -``` diff --git a/docs/library/forms/form.md b/docs/library/forms/form.md deleted file mode 100644 index bc771b7559..0000000000 --- a/docs/library/forms/form.md +++ /dev/null @@ -1,239 +0,0 @@ ---- -components: - - rx.form - - rx.form.root - - rx.form.field - - rx.form.control - - rx.form.label - - rx.form.message - - rx.form.submit - -FormRoot: | - lambda **props: rx.form.root( - rx.form.field( - rx.flex( - rx.form.label("Email"), - rx.form.control( - rx.input( - placeholder="Email Address", - # type attribute is required for "typeMismatch" validation - type="email", - ), - as_child=True, - ), - rx.form.message("Please enter a valid email"), - rx.form.submit( - rx.button("Submit"), - as_child=True, - ), - direction="column", - spacing="2", - align="stretch", - ), - name="email", - ), - **props, - ) - -FormField: | - lambda **props: rx.form.root( - rx.form.field( - rx.flex( - rx.form.label("Email"), - rx.form.control( - rx.input( - placeholder="Email Address", - # type attribute is required for "typeMismatch" validation - type="email", - ), - as_child=True, - ), - rx.form.message("Please enter a valid email", match="typeMismatch"), - rx.form.submit( - rx.button("Submit"), - as_child=True, - ), - direction="column", - spacing="2", - align="stretch", - ), - **props, - ), - reset_on_submit=True, - ) - -FormLabel: | - lambda **props: rx.form.root( - rx.form.field( - rx.flex( - rx.form.label("Email", **props,), - rx.form.control( - rx.input( - placeholder="Email Address", - # type attribute is required for "typeMismatch" validation - type="email", - ), - as_child=True, - ), - rx.form.message("Please enter a valid email", match="typeMismatch"), - rx.form.submit( - rx.button("Submit"), - as_child=True, - ), - direction="column", - spacing="2", - align="stretch", - ), - ), - reset_on_submit=True, - ) - -FormMessage: | - lambda **props: rx.form.root( - rx.form.field( - rx.flex( - rx.form.label("Email"), - rx.form.control( - rx.input( - placeholder="Email Address", - # type attribute is required for "typeMismatch" validation - type="email", - ), - as_child=True, - ), - rx.form.message("Please enter a valid email", **props,), - rx.form.submit( - rx.button("Submit"), - as_child=True, - ), - direction="column", - spacing="2", - align="stretch", - ), - name="email", - ), - on_submit=lambda form_data: rx.window_alert(form_data.to_string()), - reset_on_submit=True, - ) ---- - -```python exec -import reflex as rx -``` - -# Form - -Forms are used to collect user input. The `rx.form` component is used to group inputs and submit them together. - -The form component's children can be form controls such as `rx.input`, `rx.checkbox`, `rx.slider`, `rx.textarea`, `rx.radio_group`, `rx.select` or `rx.switch`. The controls should have a `name` attribute that is used to identify the control in the form data. The `on_submit` event trigger submits the form data as a dictionary to the `handle_submit` event handler. - -The form is submitted when the user clicks the submit button or presses enter on the form controls. - -```python demo exec -class FormState(rx.State): - form_data: dict = {} - - @rx.event - def handle_submit(self, form_data: dict): - """Handle the form submit.""" - self.form_data = form_data - - -def form_example(): - return rx.vstack( - rx.form( - rx.vstack( - rx.input(placeholder="First Name", name="first_name"), - rx.input(placeholder="Last Name", name="last_name"), - rx.hstack( - rx.checkbox("Checked", name="check"), - rx.switch("Switched", name="switch"), - ), - rx.button("Submit", type="submit"), - ), - on_submit=FormState.handle_submit, - reset_on_submit=True, - ), - rx.divider(), - rx.heading("Results"), - rx.text(FormState.form_data.to_string()), - ) -``` - -```md alert warning -# When using the form you must include a button or input with `type='submit'`. -``` - -```md alert info -# Using `name` vs `id`. - -When using the `name` attribute in form controls like `rx.switch`, `rx.radio_group`, and `rx.checkbox`, these controls will only be included in the form data if their values are set (e.g., if the checkbox is checked, the switch is toggled, or a radio option is selected). - -If you need these controls to be passed in the form data even when their values are not set, you can use the `id` attribute instead of name. The id attribute ensures that the control is always included in the submitted form data, regardless of whether its value is set or not. -``` - -```md video https://youtube.com/embed/ITOZkzjtjUA?start=5287&end=6040 -# Video: Forms -``` - -## Dynamic Forms - -Forms can be dynamically created by iterating through state vars using `rx.foreach`. - -This example allows the user to add new fields to the form prior to submit, and all -fields will be included in the form data passed to the `handle_submit` function. - -```python demo exec -class DynamicFormState(rx.State): - form_data: dict = {} - form_fields: list[str] = ["first_name", "last_name", "email"] - - @rx.var(cache=True) - def form_field_placeholders(self) -> list[str]: - return [ - " ".join(w.capitalize() for w in field.split("_")) - for field in self.form_fields - ] - - @rx.event - def add_field(self, form_data: dict): - new_field = form_data.get("new_field") - if not new_field: - return - field_name = new_field.strip().lower().replace(" ", "_") - self.form_fields.append(field_name) - - @rx.event - def handle_submit(self, form_data: dict): - self.form_data = form_data - - -def dynamic_form(): - return rx.vstack( - rx.form( - rx.vstack( - rx.foreach( - DynamicFormState.form_fields, - lambda field, idx: rx.input( - placeholder=DynamicFormState.form_field_placeholders[idx], - name=field, - ), - ), - rx.button("Submit", type="submit"), - ), - on_submit=DynamicFormState.handle_submit, - reset_on_submit=True, - ), - rx.form( - rx.hstack( - rx.input(placeholder="New Field", name="new_field"), - rx.button("+", type="submit"), - ), - on_submit=DynamicFormState.add_field, - reset_on_submit=True, - ), - rx.divider(), - rx.heading("Results"), - rx.text(DynamicFormState.form_data.to_string()), - ) -``` diff --git a/docs/library/forms/input-ll.md b/docs/library/forms/input-ll.md deleted file mode 100644 index c62c548602..0000000000 --- a/docs/library/forms/input-ll.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -components: - - rx.input - - rx.input.slot ---- - -```python exec -import reflex as rx -``` - -# Input - -A text field is an input field that users can type into. This component uses Radix's [text field](https://www.radix-ui.com/themes/docs/components/text-field) component. - - -## Overview - -The TextField component is used to capture user input and can include an optional slot for buttons and icons. It is based on the
element and supports common margin props. - -## Basic Example - -```python demo -rx.input( - rx.input.slot( - rx.icon(tag="search"), - - ), - placeholder="Search here...", -) -``` - -## Stateful Example with Blur Event - -```python demo exec -class TextfieldBlur1(rx.State): - text: str = "Hello World!" - - @rx.event - def set_text(self, text: str): - self.text = text - -def blur_example1(): - return rx.vstack( - rx.heading(TextfieldBlur1.text), - rx.input( - rx.input.slot( - rx.icon(tag="search"), - ), - placeholder="Search here...", - on_blur=TextfieldBlur1.set_text, - ) - ) -``` - -## Controlled Example - -```python demo exec -class TextfieldControlled1(rx.State): - text: str = "Hello World!" - - @rx.event - def set_text(self, text: str): - self.text = text - -def controlled_example1(): - return rx.vstack( - rx.heading(TextfieldControlled1.text), - rx.input( - rx.input.slot( - rx.icon(tag="search"), - ), - placeholder="Search here...", - value=TextfieldControlled1.text, - on_change=TextfieldControlled1.set_text, - ), - ) -``` - -# Real World Example - -```python demo exec - -def song(title, initials: str, genre: str): - return rx.card(rx.flex( - rx.flex( - rx.avatar(fallback=initials), - rx.flex( - rx.text(title, size="2", weight="bold"), - rx.text(genre, size="1", color_scheme="gray"), - direction="column", - spacing="1", - ), - direction="row", - align_items="left", - spacing="1", - ), - rx.flex( - rx.icon(tag="chevron_right"), - align_items="center", - ), - justify="between", - )) - -def search(): - return rx.card( - rx.flex( - rx.input( - rx.input.slot( - rx.icon(tag="search"), - ), - placeholder="Search songs...", - ), - rx.flex( - song("The Less I Know", "T", "Rock"), - song("Breathe Deeper", "ZB", "Rock"), - song("Let It Happen", "TF", "Rock"), - song("Borderline", "ZB", "Pop"), - song("Lost In Yesterday", "TO", "Rock"), - song("Is It True", "TO", "Rock"), - direction="column", - spacing="1", - ), - direction="column", - spacing="3", - ), - style={"maxWidth": 500}, -) -``` diff --git a/docs/library/forms/input.md b/docs/library/forms/input.md deleted file mode 100644 index bd41b0a668..0000000000 --- a/docs/library/forms/input.md +++ /dev/null @@ -1,164 +0,0 @@ ---- -components: - - rx.input - - rx.input.slot - -Input: | - lambda **props: rx.input(placeholder="Search the docs", **props) - -TextFieldSlot: | - lambda **props: rx.input( - rx.input.slot( - rx.icon(tag="search", height="16", width="16"), - **props, - ), - placeholder="Search the docs", - ) ---- - -```python exec -import reflex as rx -from pcweb.pages.docs import library -``` - -# Input - -The `input` component is an input field that users can type into. - -```md video https://youtube.com/embed/ITOZkzjtjUA?start=1517&end=1869 -# Video: Input -``` - -## Basic Example - -The `on_blur` event handler is called when focus has left the `input` for example, it’s called when the user clicks outside of a focused text input. - -```python demo exec -class TextfieldBlur(rx.State): - text: str = "Hello World!" - - @rx.event - def set_text(self, value: str): - self.text = value - -def blur_example(): - return rx.vstack( - rx.heading(TextfieldBlur.text), - rx.input( - placeholder="Search here...", - on_blur=TextfieldBlur.set_text, - ), - ) -``` - -The `on_change` event handler is called when the `value` of `input` has changed. - -```python demo exec -class TextfieldControlled(rx.State): - text: str = "Hello World!" - - @rx.event - def set_text(self, value: str): - self.text = value - -def controlled_example(): - return rx.vstack( - rx.heading(TextfieldControlled.text), - rx.input( - placeholder="Search here...", - value=TextfieldControlled.text, - on_change=TextfieldControlled.set_text, - ), - ) -``` - -Behind the scenes, the input component is implemented as a debounced input to avoid sending individual state updates per character to the backend while the user is still typing. This allows a state variable to directly control the `value` prop from the backend without the user experiencing input lag. - -## Input Types - -The `type` prop controls how the input is rendered (e.g. plain text, password, file picker). - -It accepts the same values as the native HTML `` attribute, such as: - -- `"text"` (default) -- `"password"` -- `"email"` -- `"number"` -- `"file"` -- `"checkbox"` -- `"radio"` -- `"date"` -- `"time"` -- `"url"` -- `"color"` - -and several others. See the [MDN reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types) for the full list. - -```python demo -rx.vstack( - rx.input(placeholder="Username", type="text"), - rx.input(placeholder="Password", type="password"), - rx.input(type="date"), -) -``` - -## Submitting a form using input - -The `name` prop is needed to submit with its owning form as part of a name/value pair. - -When the `required` prop is `True`, it indicates that the user must input text before the owning form can be submitted. - -The `type` is set here to `password`. The element is presented as a one-line plain text editor control in which the text is obscured so that it cannot be read. The `type` prop can take any value of `email`, `file`, `password`, `text` and several others. Learn more [here](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input). - -```python demo exec -class FormInputState(rx.State): - form_data: dict = {} - - @rx.event - def handle_submit(self, form_data: dict): - """Handle the form submit.""" - self.form_data = form_data - - -def form_input1(): - return rx.card( - rx.vstack( - rx.heading("Example Form"), - rx.form.root( - rx.hstack( - rx.input( - name="input", - placeholder="Enter text...", - type="text", - required=True, - ), - rx.button("Submit", type="submit"), - width="100%", - ), - on_submit=FormInputState.handle_submit, - reset_on_submit=True, - ), - rx.divider(), - rx.hstack( - rx.heading("Results:"), - rx.badge(FormInputState.form_data.to_string()), - ), - align_items="left", - width="100%", - ), - width="50%", - ) -``` - -To learn more about how to use forms in the [Form]({library.forms.form.path}) docs. - -## Setting a value without using a State var - -Set the value of the specified reference element, without needing to link it up to a State var. This is an alternate way to modify the value of the `input`. - -```python demo -rx.hstack( - rx.input(id="input1"), - rx.button("Erase", on_click=rx.set_value("input1", "")), -) -``` diff --git a/docs/library/forms/radio_group.md b/docs/library/forms/radio_group.md deleted file mode 100644 index e0c62187d9..0000000000 --- a/docs/library/forms/radio_group.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -components: - - rx.radio_group - - rx.radio_group.root - - rx.radio_group.item - -HighLevelRadioGroup: | - lambda **props: rx.radio_group(["1", "2", "3", "4", "5"], **props) - -RadioGroupRoot: | - lambda **props: rx.radio_group.root( - rx.radio_group.item(value="1"), - rx.radio_group.item(value="2"), - rx.radio_group.item(value="3"), - rx.radio_group.item(value="4"), - rx.radio_group.item(value="5"), - **props - ) - -RadioGroupItem: | - lambda **props: rx.radio_group.root( - rx.radio_group.item(value="1", **props), - rx.radio_group.item(value="2", **props), - rx.radio_group.item(value="3",), - rx.radio_group.item(value="4",), - rx.radio_group.item(value="5",), - ) ---- - -```python exec -import reflex as rx -from pcweb.templates.docpage import style_grid -``` - -# Radio Group - -A set of interactive radio buttons where only one can be selected at a time. - -## Basic example - -```python demo exec -class RadioGroupState(rx.State): - item: str = "No Selection" - - @rx.event - def set_item(self, item: str): - self.item = item - -def radio_group_state_example(): - return rx.vstack( - rx.badge(RadioGroupState.item, color_scheme="green"), - rx.radio(["1", "2", "3"], on_change=RadioGroupState.set_item, direction="row"), - ) -``` - -## Submitting a form using Radio Group - -The `name` prop is used to name the group. It is submitted with its owning form as part of a name/value pair. - -When the `required` prop is `True`, it indicates that the user must check a radio item before the owning form can be submitted. - -```python demo exec -class FormRadioState(rx.State): - form_data: dict = {} - - @rx.event - def handle_submit(self, form_data: dict): - """Handle the form submit.""" - self.form_data = form_data - - -def radio_form_example(): - return rx.card( - rx.vstack( - rx.heading("Example Form"), - rx.form.root( - rx.vstack( - rx.radio_group( - ["Option 1", "Option 2", "Option 3"], - name="radio_choice", - direction="row", - ), - rx.button("Submit", type="submit"), - width="100%", - spacing="4", - ), - on_submit=FormRadioState.handle_submit, - reset_on_submit=True, - ), - rx.divider(), - rx.hstack( - rx.heading("Results:"), - rx.badge(FormRadioState.form_data.to_string()), - ), - align_items="left", - width="100%", - spacing="4", - ), - width="50%", - ) -``` diff --git a/docs/library/forms/select-ll.md b/docs/library/forms/select-ll.md deleted file mode 100644 index 50b7ad26ef..0000000000 --- a/docs/library/forms/select-ll.md +++ /dev/null @@ -1,305 +0,0 @@ ---- -components: - - rx.select - - rx.select.root - - rx.select.trigger - - rx.select.content - - rx.select.group - - rx.select.item - - rx.select.label - - rx.select.separator ---- - -```python exec -import random -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -import reflex.components.radix.primitives as rdxp -from pcweb.templates.docpage import style_grid -``` - -# Select - -Displays a list of options for the user to pick from, triggered by a button. - -## Basic Example - -```python demo -rx.select.root( - rx.select.trigger(), - rx.select.content( - rx.select.group( - rx.select.label("Fruits"), - rx.select.item("Orange", value="orange"), - rx.select.item("Apple", value="apple"), - rx.select.item("Grape", value="grape", disabled=True), - ), - rx.select.separator(), - rx.select.group( - rx.select.label("Vegetables"), - rx.select.item("Carrot", value="carrot"), - rx.select.item("Potato", value="potato"), - ), - ), - default_value="apple", -) -``` - -## Usage - -## Disabling - -It is possible to disable individual items in a `select` using the `disabled` prop associated with the `rx.select.item`. - -```python demo -rx.select.root( - rx.select.trigger(placeholder="Select a Fruit"), - rx.select.content( - rx.select.group( - rx.select.item("Apple", value="apple"), - rx.select.item("Grape", value="grape", disabled=True), - rx.select.item("Pineapple", value="pineapple"), - ), - ), -) -``` - -To prevent the user from interacting with select entirely, set the `disabled` prop to `True` on the `rx.select.root` component. - -```python demo -rx.select.root( - rx.select.trigger(placeholder="This is Disabled"), - rx.select.content( - rx.select.group( - rx.select.item("Apple", value="apple"), - rx.select.item("Grape", value="grape"), - ), - ), - disabled=True, -) -``` - -## Setting Defaults - -It is possible to set several default values when constructing a `select`. - -The `placeholder` prop in the `rx.select.trigger` specifies the content that will be rendered when `value` or `default_value` is empty or not set. - -```python demo -rx.select.root( - rx.select.trigger(placeholder="pick a fruit"), - rx.select.content( - rx.select.group( - rx.select.item("Apple", value="apple"), - rx.select.item("Grape", value="grape"), - ), - ), -) -``` - -The `default_value` in the `rx.select.root` specifies the value of the `select` when initially rendered. -The `default_value` should correspond to the `value` of a child `rx.select.item`. - -```python demo -rx.select.root( - rx.select.trigger(), - rx.select.content( - rx.select.group( - rx.select.item("Apple", value="apple"), - rx.select.item("Grape", value="grape"), - ), - ), - default_value="apple", -) -``` - -## Fully controlled - -The `on_change` event trigger is fired when the value of the select changes. -In this example the `rx.select_root` `value` prop specifies which item is selected, and this -can also be controlled using state and a button without direct interaction with the select component. - -```python demo exec -class SelectState2(rx.State): - - values: list[str] = ["apple", "grape", "pear"] - - value: str = "" - - def set_value(self, value: str): - self.value = value - - @rx.event - def choose_randomly(self): - """Change the select value var.""" - original_value = self.value - while self.value == original_value: - self.value = random.choice(self.values) - - -def select_example2(): - return rx.vstack( - rx.select.root( - rx.select.trigger(placeholder="No Selection"), - rx.select.content( - rx.select.group( - rx.foreach(SelectState2.values, lambda x: rx.select.item(x, value=x)) - ), - ), - value=SelectState2.value, - on_change=SelectState2.set_value, - - ), - rx.button("Choose Randomly", on_click=SelectState2.choose_randomly), - rx.button("Reset", on_click=SelectState2.set_value("")), - ) -``` - -The `open` prop and `on_open_change` event trigger work similarly to `value` and `on_change` to control the open state of the select. -If `on_open_change` handler does not alter the `open` prop, the select will not be able to be opened or closed by clicking on the -`select_trigger`. - -```python demo exec -class SelectState8(rx.State): - is_open: bool = False - - @rx.event - def set_is_open(self, value: bool): - self.is_open = value - -def select_example8(): - return rx.flex( - rx.select.root( - rx.select.trigger(placeholder="No Selection"), - rx.select.content( - rx.select.group( - rx.select.item("Apple", value="apple"), - rx.select.item("Grape", value="grape"), - ), - ), - open=SelectState8.is_open, - on_open_change=SelectState8.set_is_open, - ), - rx.button("Toggle", on_click=SelectState8.set_is_open(~SelectState8.is_open)), - spacing="2", - ) -``` - -### Submitting a Form with Select - -When a select is part of a form, the `name` prop of the `rx.select.root` sets the key that will be submitted with the form data. - -The `value` prop of `rx.select.item` provides the value to be associated with the `name` key when the form is submitted with that item selected. - -When the `required` prop of the `rx.select.root` is `True`, it indicates that the user must select a value before the form may be submitted. - -```python demo exec -class FormSelectState(rx.State): - form_data: dict = {} - - @rx.event - def handle_submit(self, form_data: dict): - """Handle the form submit.""" - self.form_data = form_data - - -def form_select(): - return rx.flex( - rx.form.root( - rx.flex( - rx.select.root( - rx.select.trigger(), - rx.select.content( - rx.select.group( - rx.select.label("Fruits"), - rx.select.item("Orange", value="orange"), - rx.select.item("Apple", value="apple"), - rx.select.item("Grape", value="grape"), - ), - rx.select.separator(), - rx.select.group( - rx.select.label("Vegetables"), - rx.select.item("Carrot", value="carrot"), - rx.select.item("Potato", value="potato"), - ), - ), - default_value="apple", - name="select", - ), - rx.button("Submit"), - width="100%", - direction="column", - spacing="2", - ), - on_submit=FormSelectState.handle_submit, - reset_on_submit=True, - ), - rx.divider(size="4"), - rx.heading("Results"), - rx.text(FormSelectState.form_data.to_string()), - width="100%", - direction="column", - spacing="2", - ) -``` - -## Real World Example - -```python demo -rx.card( - rx.flex( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png", width="100%", height="auto"), - rx.flex( - rx.heading("Reflex Swag", size="4", margin_bottom="4px"), - rx.heading("$99", size="6", margin_bottom="4px"), - direction="row", justify="between", - width="100%", - ), - rx.text("Reflex swag with a sense of nostalgia, as if they carry whispered tales of past adventures", size="2", margin_bottom="4px"), - rx.divider(size="4"), - rx.flex( - rx.flex( - rx.text("Color", size="2", margin_bottom="4px", color_scheme="gray"), - rx.select.root( - rx.select.trigger(), - rx.select.content( - rx.select.group( - rx.select.item("Light", value="light"), - rx.select.item("Dark", value="dark"), - ), - ), - default_value="light", - ), - direction="column", - ), - rx.flex( - rx.text("Size", size="2", margin_bottom="4px", color_scheme="gray"), - rx.select.root( - rx.select.trigger(), - rx.select.content( - rx.select.group( - rx.select.item("24", value="24"), - rx.select.item("26", value="26"), - rx.select.item("28", value="28", disabled=True), - rx.select.item("30", value="30"), - rx.select.item("32", value="32"), - rx.select.item("34", value="34"), - rx.select.item("36", value="36"), - ), - ), - default_value="30", - ), - direction="column", - ), - rx.button(rx.icon(tag="plus"), "Add"), - align="end", - justify="between", - spacing="2", - width="100%", - ), - width="15em", - direction="column", - spacing="2", - ), -) -``` diff --git a/docs/library/forms/select.md b/docs/library/forms/select.md deleted file mode 100644 index 78e5206bf9..0000000000 --- a/docs/library/forms/select.md +++ /dev/null @@ -1,203 +0,0 @@ ---- -components: - - rx.select - - rx.select.root - - rx.select.trigger - - rx.select.content - - rx.select.group - - rx.select.item - - rx.select.label - - rx.select.separator - -HighLevelSelect: | - lambda **props: rx.select(["apple", "grape", "pear"], default_value="pear", **props) - -SelectRoot: | - lambda **props: rx.select.root( - rx.select.trigger(), - rx.select.content( - rx.select.group( - rx.select.item("apple", value="apple"), - rx.select.item("grape", value="grape"), - rx.select.item("pear", value="pear"), - ), - ), - default_value="pear", - **props - ) - -SelectTrigger: | - lambda **props: rx.select.root( - rx.select.trigger(**props), - rx.select.content( - rx.select.group( - rx.select.item("apple", value="apple"), - rx.select.item("grape", value="grape"), - rx.select.item("pear", value="pear"), - ), - ), - default_value="pear", - ) - -SelectContent: | - lambda **props: rx.select.root( - rx.select.trigger(), - rx.select.content( - rx.select.group( - rx.select.item("apple", value="apple"), - rx.select.item("grape", value="grape"), - rx.select.item("pear", value="pear"), - ), - **props, - ), - default_value="pear", - ) - -SelectItem: | - lambda **props: rx.select.root( - rx.select.trigger(), - rx.select.content( - rx.select.group( - rx.select.item("apple", value="apple", **props), - rx.select.item("grape", value="grape"), - rx.select.item("pear", value="pear"), - ), - ), - default_value="pear", - ) ---- - -```python exec -import random -import reflex as rx -from pcweb.templates.docpage import style_grid -``` - -# Select - -Displays a list of options for the user to pick from—triggered by a button. - -```python demo exec -class SelectState(rx.State): - value: str = "apple" - - @rx.event - def change_value(self, value: str): - """Change the select value var.""" - self.value = value - - -def select_intro(): - return rx.center( - rx.select( - ["apple", "grape", "pear"], - value=SelectState.value, - on_change=SelectState.change_value, - ), - rx.badge(SelectState.value), - ) -``` - -```python demo exec -class SelectState3(rx.State): - - values: list[str] = ["apple", "grape", "pear"] - - value: str = "apple" - - def set_value(self, value: str): - self.value = value - - @rx.event - def change_value(self): - """Change the select value var.""" - self.value = random.choice(self.values) - - -def select_example3(): - return rx.vstack( - rx.select( - SelectState3.values, - value=SelectState3.value, - on_change=SelectState3.set_value, - ), - rx.button("Change Value", on_click=SelectState3.change_value), - - ) -``` - -The `on_open_change` event handler acts in a similar way to the `on_change` and is called when the open state of the select changes. - -```python demo -rx.select( - ["apple", "grape", "pear"], - on_change=rx.window_alert("on_change event handler called"), -) - -``` - -### Submitting a form using select - -The `name` prop is needed to submit with its owning form as part of a name/value pair. - -When the `required` prop is `True`, it indicates that the user must select a value before the owning form can be submitted. - -```python demo exec -class FormSelectState(rx.State): - form_data: dict = {} - - @rx.event - def handle_submit(self, form_data: dict): - """Handle the form submit.""" - self.form_data = form_data - - -def select_form_example(): - return rx.card( - rx.vstack( - rx.heading("Example Form"), - rx.form.root( - rx.flex( - rx.select(["apple", "grape", "pear"], default_value="apple", name="select", required=True), - rx.button("Submit", flex="1", type="submit"), - width="100%", - spacing="3", - ), - on_submit=FormSelectState.handle_submit, - reset_on_submit=True, - ), - rx.divider(), - rx.hstack( - rx.heading("Results:"), - rx.badge(FormSelectState.form_data.to_string()), - ), - align_items="left", - width="100%", - ), - width="50%", - ) -``` - - -### Using Select within a Drawer component - -If using within a [Drawer](/docs/library/overlay/drawer) component, set the `position` prop to `"popper"` to ensure the select menu is displayed correctly. - -```python demo -rx.drawer.root( - rx.drawer.trigger(rx.button("Open Drawer")), - rx.drawer.overlay(z_index="5"), - rx.drawer.portal( - rx.drawer.content( - rx.vstack( - rx.drawer.close(rx.box(rx.button("Close"))), - rx.select(["apple", "grape", "pear"], position="popper"), - ), - width="20em", - padding="2em", - background_color=rx.color("gray", 1), - ), - ), - direction="left", -) -``` diff --git a/docs/library/forms/slider.md b/docs/library/forms/slider.md deleted file mode 100644 index cc537b224b..0000000000 --- a/docs/library/forms/slider.md +++ /dev/null @@ -1,133 +0,0 @@ ---- -components: - - rx.slider - -Slider: | - lambda **props: rx.center(rx.slider(default_value=40, height="100%", **props), height="4em", width="100%") ---- - -```python exec -import reflex as rx -from pcweb.templates.docpage import style_grid -``` - -# Slider - -Provides user selection from a range of values. The - -## Basic Example - -The slider can be controlled by a single value or a range of values. Slider can be hooked to state to control its value. Passing a list of two values creates a range slider. - -```python demo exec -class SliderState(rx.State): - value: int = 50 - - @rx.event - def set_end(self, value: list[int | float]): - self.value = value[0] - -def slider_intro(): - return rx.vstack( - rx.heading(SliderState.value), - rx.slider(on_value_commit=SliderState.set_end), - width="100%", - ) -``` - -## Range Slider - -Range slider is created by passing a list of two values to the `default_value` prop. The list should contain two values that are in the range of the slider. - -```python demo exec -class RangeSliderState(rx.State): - value_start: int = 25 - value_end: int = 75 - - @rx.event - def set_end(self, value: list[int | float]): - self.value_start = value[0] - self.value_end = value[1] - -def range_slider_intro(): - return rx.vstack( - rx.hstack( - rx.heading(RangeSliderState.value_start), - rx.heading(RangeSliderState.value_end), - ), - rx.slider( - default_value=[25, 75], - min_=0, - max=100, - size="1", - on_value_commit=RangeSliderState.set_end, - ), - width="100%", - ) -``` - -## Live Updating Slider - -You can use the `on_change` prop to update the slider value as you interact with it. The `on_change` prop takes a function that will be called with the new value of the slider. - -Here we use the `throttle` method to limit the rate at which the function is called, which is useful to prevent excessive updates. In this example, the slider value is updated every 100ms. - -```python demo exec -class LiveSliderState(rx.State): - value: int = 50 - - @rx.event - def set_end(self, value: list[int | float]): - self.value = value[0] - -def live_slider_intro(): - return rx.vstack( - rx.heading(LiveSliderState.value), - rx.slider( - default_value=50, - min_=0, - max=100, - on_change=LiveSliderState.set_end.throttle(100), - ), - width="100%", - ) -``` - -## Slider in forms - -Here we show how to use a slider in a form. We use the `name` prop to identify the slider in the form data. The form data is then passed to the `handle_submit` method to be processed. - -```python demo exec -class FormSliderState(rx.State): - form_data: dict = {} - - @rx.event - def handle_submit(self, form_data: dict): - """Handle the form submit.""" - self.form_data = form_data - - -def slider_form_example(): - return rx.card( - rx.vstack( - rx.heading("Example Form"), - rx.form.root( - rx.hstack( - rx.slider(default_value=40, name="slider"), - rx.button("Submit", type="submit"), - width="100%", - ), - on_submit=FormSliderState.handle_submit, - reset_on_submit=True, - ), - rx.divider(), - rx.hstack( - rx.heading("Results:"), - rx.badge(FormSliderState.form_data.to_string()), - ), - align_items="left", - width="100%", - ), - width="50%", - ) -``` diff --git a/docs/library/forms/switch.md b/docs/library/forms/switch.md deleted file mode 100644 index 8efc3e687c..0000000000 --- a/docs/library/forms/switch.md +++ /dev/null @@ -1,108 +0,0 @@ ---- -components: - - rx.switch - -Switch: | - lambda **props: rx.switch(**props) ---- - -```python exec -import reflex as rx -from pcweb.templates.docpage import style_grid -from pcweb.pages.docs import vars -``` - -# Switch - -A toggle switch alternative to the checkbox. - -## Basic Example - -Here is a basic example of a switch. We use the `on_change` trigger to toggle the value in the state. - -```python demo exec -class SwitchState(rx.State): - value: bool = False - - @rx.event - def set_end(self, value: bool): - self.value = value - -def switch_intro(): - return rx.center( - rx.switch(on_change=SwitchState.set_end), - rx.badge(SwitchState.value), - ) -``` - -## Control the value - -The `checked` prop is used to control the state of the switch. The event `on_change` is called when the state of the switch changes, when the `change_checked` event handler is called. - -The `disabled` prop when `True`, prevents the user from interacting with the switch. In our example below, even though the second switch is `disabled` we are still able to change whether it is checked or not using the `checked` prop. - -```python demo exec -class ControlSwitchState(rx.State): - - checked = True - - @rx.event - def change_checked(self, checked: bool): - """Change the switch checked var.""" - self.checked = checked - - -def control_switch_example(): - return rx.hstack( - rx.switch( - checked=ControlSwitchState.checked, - on_change=ControlSwitchState.change_checked, - ), - rx.switch( - checked=ControlSwitchState.checked, - on_change=ControlSwitchState.change_checked, - disabled=True, - ), - ) -``` - -## Switch in forms - -The `name` of the switch is needed to submit with its owning form as part of a name/value pair. When the `required` prop is `True`, it indicates that the user must check the switch before the owning form can be submitted. - -The `value` prop is only used for form submission, use the `checked` prop to control state of the `switch`. - -```python demo exec -class FormSwitchState(rx.State): - form_data: dict = {} - - @rx.event - def handle_submit(self, form_data: dict): - """Handle the form submit.""" - self.form_data = form_data - - -def switch_form_example(): - return rx.card( - rx.vstack( - rx.heading("Example Form"), - rx.form.root( - rx.hstack( - rx.switch(name="switch"), - rx.button("Submit", type="submit"), - width="100%", - ), - on_submit=FormSwitchState.handle_submit, - reset_on_submit=True, - ), - rx.divider(), - rx.hstack( - rx.heading("Results:"), - rx.badge(FormSwitchState.form_data.to_string()), - ), - align_items="left", - width="100%", - ), - width="50%", - ) -``` diff --git a/docs/library/forms/text_area.md b/docs/library/forms/text_area.md deleted file mode 100644 index de4f1b6a19..0000000000 --- a/docs/library/forms/text_area.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -components: - - rx.text_area - -TextArea: | - lambda **props: rx.text_area(**props) ---- - -```python exec -import reflex as rx -``` - -# Text Area - -A text area is a multi-line text input field. - -## Basic Example - -The text area component can be controlled by a single value. The `on_blur` prop can be used to update the value when the text area loses focus. - -```python demo exec -class TextAreaBlur(rx.State): - text: str = "Hello World!" - - @rx.event - def set_text(self, text: str): - self.text = text - -def blur_example(): - return rx.vstack( - rx.heading(TextAreaBlur.text), - rx.text_area( - placeholder="Type here...", - on_blur=TextAreaBlur.set_text, - ), - ) -``` - -## Text Area in forms - -Here we show how to use a text area in a form. We use the `name` prop to identify the text area in the form data. The form data is then passed to the `submit_feedback` method to be processed. - -```python demo exec -class TextAreaFeedbackState(rx.State): - feedback: str = "" - submitted: bool = False - - @rx.event - def set_feedback(self, value: str): - self.feedback = value - - @rx.event - def submit_feedback(self, form_data: dict): - self.submitted = True - - @rx.event - def reset_form(self): - self.feedback = "" - self.submitted = False - -def feedback_form(): - return rx.cond( - TextAreaFeedbackState.submitted, - rx.card( - rx.vstack( - rx.text("Thank you for your feedback!"), - rx.button("Submit another response", on_click=TextAreaFeedbackState.reset_form), - ), - ), - rx.card( - rx.form( - rx.flex( - rx.text("Are you enjoying Reflex?"), - rx.text_area( - placeholder="Write your feedback…", - value=TextAreaFeedbackState.feedback, - on_change=TextAreaFeedbackState.set_feedback, - resize="vertical", - ), - rx.button("Send", type="submit"), - direction="column", - spacing="3", - ), - on_submit=TextAreaFeedbackState.submit_feedback, - ), - ), - ) -``` diff --git a/docs/library/forms/upload.md b/docs/library/forms/upload.md deleted file mode 100644 index b87391253f..0000000000 --- a/docs/library/forms/upload.md +++ /dev/null @@ -1,523 +0,0 @@ ---- -components: - - rx.upload - - rx.upload.root - -Upload: | - lambda **props: rx.center(rx.upload(id="my_upload", **props)) ---- - -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -``` - -# File Upload - -Reflex makes it simple to add file upload functionality to your app. You can let users select files, store them on your server, and display or process them as needed. Below is a minimal example that demonstrates how to upload files, save them to disk, and display uploaded images using application state. - - -## Basic File Upload Example - -You can let users upload files and keep track of them in your app’s state. The example below allows users to upload files, saves them using the backend, and then displays the uploaded files as images. - -```python -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -class State(rx.State): - uploaded_files: list[str] = [] - - @rx.event - async def handle_upload(self, files: list[rx.UploadFile]): - for file in files: - data = await file.read() - path = rx.get_upload_dir() / file.name - with path.open("wb") as f: - f.write(data) - self.uploaded_files.append(file.name) - -def upload_component(): - return rx.vstack( - rx.upload(id="upload"), - rx.button("Upload", on_click=State.handle_upload(rx.upload_files("upload"))), - rx.foreach(State.uploaded_files, lambda f: rx.image(src=rx.get_upload_url(f))), - ) -``` - -## How File Upload Works - -Selecting a file will add it to the browser file list, which can be rendered -on the frontend using the `rx.selected_files(id)` special Var. To clear the -selected files, you can use another special Var `rx.clear_selected_files(id)` as -an event handler. - -To upload the file(s), you need to bind an event handler and pass the special -`rx.upload_files(upload_id=id)` event arg to it. - -## File Storage Functions - -Reflex provides two key functions for handling uploaded files: - -### rx.get_upload_dir() -- **Purpose**: Returns a `Path` object pointing to the server-side directory where uploaded files should be saved -- **Usage**: Used in backend event handlers to determine where to save uploaded files -- **Default Location**: `./uploaded_files` (can be customized via `REFLEX_UPLOADED_FILES_DIR` environment variable) -- **Type**: Returns `pathlib.Path` - -### rx.get_upload_url(filename) -- **Purpose**: Returns the URL string that can be used in frontend components to access uploaded files -- **Usage**: Used in frontend components (like `rx.image`, `rx.video`) to display uploaded files -- **URL Format**: `/_upload/filename` -- **Type**: Returns `str` - -### Key Differences -- **rx.get_upload_dir()** -> Backend file path for saving files -- **rx.get_upload_url()** -> Frontend URL for displaying files - -### Basic Upload Pattern - -Here is the standard pattern for handling file uploads: - -```python -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN - -def create_unique_filename(file_name: str): - import random - import string - - filename = "".join(random.choices(string.ascii_letters + string.digits, k=10)) - return filename + "_" + file_name - -class State(rx.State): - uploaded_files: list[str] = [] - - @rx.event - async def handle_upload(self, files: list[rx.UploadFile]): - """Handle file upload with proper directory management.""" - for file in files: - # Read the file data - upload_data = await file.read() - - # Get the upload directory (backend path) - upload_dir = rx.get_upload_dir() - - # Ensure the directory exists - upload_dir.mkdir(parents=True, exist_ok=True) - - # Create unique filename to prevent conflicts - unique_filename = create_unique_filename(file.name) - - # Create full file path - file_path = upload_dir / unique_filename - - # Save the file - with file_path.open("wb") as f: - f.write(upload_data) - - # Store filename for frontend display - self.uploaded_files.append(unique_filename) - -def upload_component(): - return rx.vstack( - rx.upload( - rx.text("Drop files here or click to select"), - id="file_upload", - border="2px dashed #ccc", - padding="2em", - ), - rx.button( - "Upload Files", - on_click=State.handle_upload(rx.upload_files(upload_id="file_upload")), - ), - # Display uploaded files using rx.get_upload_url() - rx.foreach( - State.uploaded_files, - lambda filename: rx.image(src=rx.get_upload_url(filename)) - ), - ) - -``` - -### Multiple File Upload - -Below is an example of how to allow multiple file uploads (in this case images). - -```python demo box -rx.image(src=f"{REFLEX_ASSETS_CDN}other/upload.gif") -``` - -```python -class State(rx.State): - """The app state.""" - - # The images to show. - img: list[str] - - @rx.event - async def handle_upload(self, files: list[rx.UploadFile]): - """Handle the upload of file(s). - - Args: - files: The uploaded files. - """ - for file in files: - upload_data = await file.read() - outfile = rx.get_upload_dir() / file.name - - # Save the file. - with outfile.open("wb") as file_object: - file_object.write(upload_data) - - # Update the img var. - self.img.append(file.name) - - -color = "rgb(107,99,246)" - - -def index(): - """The main view.""" - return rx.vstack( - rx.upload( - rx.vstack( - rx.button("Select File", color=color, bg="white", border=f"1px solid {color}"), - rx.text("Drag and drop files here or click to select files"), - ), - id="upload1", - border=f"1px dotted {color}", - padding="5em", - ), - rx.hstack(rx.foreach(rx.selected_files("upload1"), rx.text)), - rx.button( - "Upload", - on_click=State.handle_upload(rx.upload_files(upload_id="upload1")), - ), - rx.button( - "Clear", - on_click=rx.clear_selected_files("upload1"), - ), - rx.foreach(State.img, lambda img: rx.image(src=rx.get_upload_url(img))), - padding="5em", - ) -``` - -### Uploading a Single File (Video) - -Below is an example of how to allow only a single file upload and render (in this case a video). - -```python demo box -rx.el.video(src=f"{REFLEX_ASSETS_CDN}other/upload_single_video.webm", auto_play=True, controls=True, loop=True) -``` - -```python -class State(rx.State): - """The app state.""" - - # The video to show. - video: str - - @rx.event - async def handle_upload( - self, files: list[rx.UploadFile] - ): - """Handle the upload of file(s). - - Args: - files: The uploaded files. - """ - current_file = files[0] - upload_data = await current_file.read() - outfile = rx.get_upload_dir() / current_file.name - - # Save the file. - with outfile.open("wb") as file_object: - file_object.write(upload_data) - - # Update the video var. - self.video = current_file.name - - -color = "rgb(107,99,246)" - - -def index(): - """The main view.""" - return rx.vstack( - rx.upload( - rx.vstack( - rx.button( - "Select File", - color=color, - bg="white", - border=f"1px solid \{color}", - ), - rx.text( - "Drag and drop files here or click to select files" - ), - ), - id="upload1", - max_files=1, - border=f"1px dotted {color}", - padding="5em", - ), - rx.text(rx.selected_files("upload1")), - rx.button( - "Upload", - on_click=State.handle_upload( - rx.upload_files(upload_id="upload1") - ), - ), - rx.button( - "Clear", - on_click=rx.clear_selected_files("upload1"), - ), - rx.cond( - State.video, - rx.video(src=rx.get_upload_url(State.video)), - ), - padding="5em", - ) -``` - - -### Customizing the Upload - -In the example below, the upload component accepts a maximum number of 5 files of specific types. -It also disables the use of the space or enter key in uploading files. - -To use a one-step upload, bind the event handler to the `rx.upload` component's -`on_drop` trigger. - -```python -class State(rx.State): - """The app state.""" - - # The images to show. - img: list[str] - - async def handle_upload(self, files: list[rx.UploadFile]): - """Handle the upload of file(s). - - Args: - files: The uploaded files. - """ - for file in files: - upload_data = await file.read() - outfile = rx.get_upload_dir() / file.name - - # Save the file. - with outfile.open("wb") as file_object: - file_object.write(upload_data) - - # Update the img var. - self.img.append(file.name) - - -color = "rgb(107,99,246)" - - -def index(): - """The main view.""" - return rx.vstack( - rx.upload( - rx.vstack( - rx.button("Select File", color=color, bg="white", border=f"1px solid {color}"), - rx.text("Drag and drop files here or click to select files"), - ), - id="upload2", - multiple=True, - accept = { - "application/pdf": [".pdf"], - "image/png": [".png"], - "image/jpeg": [".jpg", ".jpeg"], - "image/gif": [".gif"], - "image/webp": [".webp"], - "text/html": [".html", ".htm"], - }, - max_files=5, - disabled=False, - no_keyboard=True, - on_drop=State.handle_upload(rx.upload_files(upload_id="upload2")), - border=f"1px dotted {color}", - padding="5em", - ), - rx.grid( - rx.foreach( - State.img, - lambda img: rx.vstack( - rx.image(src=rx.get_upload_url(img)), - rx.text(img), - ), - ), - columns="2", - spacing="1", - ), - padding="5em", - ) -``` - -### Unstyled Upload Component - -To use a completely unstyled upload component and apply your own customization, use `rx.upload.root` instead: - -```python demo -rx.upload.root( - rx.box( - rx.icon( - tag="cloud_upload", - style={"width": "3rem", "height": "3rem", "color": "#2563eb", "marginBottom": "0.75rem"}, - ), - rx.hstack( - rx.text( - "Click to upload", - style={"fontWeight": "bold", "color": "#1d4ed8"}, - ), - " or drag and drop", - style={"fontSize": "0.875rem", "color": "#4b5563"}, - ), - rx.text( - "SVG, PNG, JPG or GIF (MAX. 5MB)", - style={"fontSize": "0.75rem", "color": "#6b7280", "marginTop": "0.25rem"}, - ), - style={ - "display": "flex", - "flexDirection": "column", - "alignItems": "center", - "justifyContent": "center", - "padding": "1.5rem", - "textAlign": "center", - }, - ), - id="my_upload", - style={ - "maxWidth": "24rem", - "height": "16rem", - "borderWidth": "2px", - "borderStyle": "dashed", - "borderColor": "#60a5fa", - "borderRadius": "0.75rem", - "cursor": "pointer", - "transitionProperty": "background-color", - "transitionDuration": "0.2s", - "transitionTimingFunction": "ease-in-out", - "display": "flex", - "alignItems": "center", - "justifyContent": "center", - "boxShadow": "0 1px 2px rgba(0, 0, 0, 0.05)", - }, -) -``` - -## Handling the Upload - -Your event handler should be an async function that accepts a single argument, -`files: list[UploadFile]`, which will contain [FastAPI UploadFile](https://fastapi.tiangolo.com/tutorial/request-files) instances. -You can read the files and save them anywhere as shown in the example. - -In your UI, you can bind the event handler to a trigger, such as a button -`on_click` event or upload `on_drop` event, and pass in the files using -`rx.upload_files()`. - -### Saving the File - -By convention, Reflex provides the function `rx.get_upload_dir()` to get the directory where uploaded files may be saved. The upload dir comes from the environment variable `REFLEX_UPLOADED_FILES_DIR`, or `./uploaded_files` if not specified. - -The backend of your app will mount this uploaded files directory on `/_upload` without restriction. Any files uploaded via this mechanism will automatically be publicly accessible. To get the URL for a file inside the upload dir, use the `rx.get_upload_url(filename)` function in a frontend component. - -```md alert info -# When using the Reflex hosting service, the uploaded files directory is not persistent and will be cleared on every deployment. For persistent storage of uploaded files, it is recommended to use an external service, such as S3. -``` - -### Directory Structure and URLs - -By default, Reflex creates the following structure: - -```text -your_project/ -├── uploaded_files/ # rx.get_upload_dir() points here -│ ├── image1.png -│ ├── document.pdf -│ └── video.mp4 -└── ... -``` - -The files are automatically served at: -- `/_upload/image1.png` ← `rx.get_upload_url("image1.png")` -- `/_upload/document.pdf` ← `rx.get_upload_url("document.pdf")` -- `/_upload/video.mp4` ← `rx.get_upload_url("video.mp4")` - -## Cancellation - -The `id` provided to the `rx.upload` component can be passed to the special event handler `rx.cancel_upload(id)` to stop uploading on demand. Cancellation can be triggered directly by a frontend event trigger, or it can be returned from a backend event handler. - -## Progress - -The `rx.upload_files` special event arg also accepts an `on_upload_progress` event trigger which will be fired about every second during the upload operation to report the progress of the upload. This can be used to update a progress bar or other UI elements to show the user the progress of the upload. - -```python -class UploadExample(rx.State): - uploading: bool = False - progress: int = 0 - total_bytes: int = 0 - - @rx.event - async def handle_upload(self, files: list[rx.UploadFile]): - for file in files: - self.total_bytes += len(await file.read()) - - @rx.event - def handle_upload_progress(self, progress: dict): - self.uploading = True - self.progress = round(progress["progress"] * 100) - if self.progress >= 100: - self.uploading = False - - @rx.event - def cancel_upload(self): - self.uploading = False - return rx.cancel_upload("upload3") - - -def upload_form(): - return rx.vstack( - rx.upload( - rx.text("Drag and drop files here or click to select files"), - id="upload3", - border="1px dotted rgb(107,99,246)", - padding="5em", - ), - rx.vstack(rx.foreach(rx.selected_files("upload3"), rx.text)), - rx.progress(value=UploadExample.progress, max=100), - rx.cond( - ~UploadExample.uploading, - rx.button( - "Upload", - on_click=UploadExample.handle_upload( - rx.upload_files( - upload_id="upload3", - on_upload_progress=UploadExample.handle_upload_progress, - ), - ), - ), - rx.button("Cancel", on_click=UploadExample.cancel_upload), - ), - rx.text("Total bytes uploaded: ", UploadExample.total_bytes), - align="center", - ) -``` - -The `progress` dictionary contains the following keys: - -```javascript -\{ - 'loaded': 36044800, - 'total': 54361908, - 'progress': 0.6630525183185255, - 'bytes': 20447232, - 'rate': None, - 'estimated': None, - 'event': \{'isTrusted': True}, - 'upload': True -} -``` diff --git a/docs/library/graphing/charts/areachart.md b/docs/library/graphing/charts/areachart.md deleted file mode 100644 index c857dc3a70..0000000000 --- a/docs/library/graphing/charts/areachart.md +++ /dev/null @@ -1,444 +0,0 @@ ---- -components: - - rx.recharts.AreaChart - - rx.recharts.Area ---- - -# Area Chart - -```python exec -import reflex as rx -import random -``` - -A Recharts area chart displays quantitative data using filled areas between a line connecting data points and the axis. - -## Basic Example - -```python demo graphing -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def area_simple(): - return rx.recharts.area_chart( - rx.recharts.area( - data_key="uv", - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - data=data, - width = "100%", - height = 250, - ) -``` - -## Syncing Charts - -The `sync_id` prop allows you to sync two graphs. In the example, it is set to "1" for both charts, indicating that they should be synchronized. This means that any interactions (such as brushing) performed on one chart will be reflected in the other chart. - -```python demo graphing -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def area_sync(): - return rx.vstack( - rx.recharts.bar_chart( - rx.recharts.graphing_tooltip(), - rx.recharts.bar( - data_key="uv", stroke="#8884d8", fill="#8884d8" - ), - rx.recharts.bar( - data_key="pv", stroke="#82ca9d", fill="#82ca9d", - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - data=data, - sync_id="1", - width = "100%", - height = 200, - ), - rx.recharts.composed_chart( - rx.recharts.area( - data_key="uv", stroke="#8884d8", fill="#8884d8" - ), - rx.recharts.line( - data_key="pv", type_="monotone", stroke="#ff7300", - ), - - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - rx.recharts.graphing_tooltip(), - rx.recharts.brush( - data_key="name", height=30, stroke="#8884d8" - ), - data=data, - sync_id="1", - width = "100%", - height = 250, - ), - width="100%", - ) -``` - -## Stacking Charts - -The `stack_id` prop allows you to stack multiple graphs on top of each other. In the example, it is set to "1" for both charts, indicating that they should be stacked together. This means that the bars or areas of the charts will be vertically stacked, with the values of each chart contributing to the total height of the stacked areas or bars. - -This is similar to the `sync_id` prop, but instead of synchronizing the interaction between the charts, it just stacks the charts on top of each other. - -```python demo graphing - -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def area_stack(): - return rx.recharts.area_chart( - rx.recharts.area( - data_key="uv", - stroke=rx.color("accent", 9), - fill=rx.color("accent", 8), - stack_id="1", - ), - rx.recharts.area( - data_key="pv", - stroke=rx.color("green", 9), - fill=rx.color("green", 8), - stack_id="1", - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - data=data, - stack_offset="none", - margin={"top": 5, "right": 5, "bottom": 5, "left": 5}, - width = "100%", - height = 300, - ) -``` - -## Multiple Axis - -Multiple axes can be used for displaying different data series with varying scales or units on the same chart. This allows for a more comprehensive comparison and analysis of the data. - -```python demo graphing - -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def area_multi_axis(): - return rx.recharts.area_chart( - rx.recharts.area( - data_key="uv", stroke="#8884d8", fill="#8884d8", x_axis_id="primary", y_axis_id="left", - ), - rx.recharts.area( - data_key="pv", x_axis_id="secondary", y_axis_id="right", type_="monotone", stroke="#82ca9d", fill="#82ca9d" - ), - rx.recharts.x_axis(data_key="name", x_axis_id="primary"), - rx.recharts.x_axis(data_key="name", x_axis_id="secondary", orientation="top"), - rx.recharts.y_axis(data_key="uv", y_axis_id="left"), - rx.recharts.y_axis(data_key="pv", y_axis_id="right", orientation="right"), - rx.recharts.graphing_tooltip(), - rx.recharts.legend(), - data=data, - width = "100%", - height = 300, - ) -``` - -## Layout - -Use the `layout` prop to set the orientation to either `"horizontal"` (default) or `"vertical"`. - -```md alert info -# Include margins around your graph to ensure proper spacing and enhance readability. By default, provide margins on all sides of the chart to create a visually appealing and functional representation of your data. -``` - -```python demo graphing - -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def area_vertical(): - return rx.recharts.area_chart( - rx.recharts.area( - data_key="uv", - stroke=rx.color("accent", 8), - fill=rx.color("accent", 3), - ), - rx.recharts.x_axis(type_="number"), - rx.recharts.y_axis(data_key="name", type_="category"), - data=data, - layout="vertical", - height = 300, - width = "100%", - ) -``` - -## Stateful Example - -Here is an example of an area graph with a `State`. Here we have defined a function `randomize_data`, which randomly changes the data for both graphs when the first defined `area` is clicked on using `on_click=AreaState.randomize_data`. - -```python demo exec -class AreaState(rx.State): - data = data - curve_type = "" - - @rx.event - def randomize_data(self): - for i in range(len(self.data)): - self.data[i]["uv"] = random.randint(0, 10000) - self.data[i]["pv"] = random.randint(0, 10000) - self.data[i]["amt"] = random.randint(0, 10000) - - def change_curve_type(self, type_input): - self.curve_type = type_input - -def area_stateful(): - return rx.vstack( - rx.hstack( - rx.text("Curve Type:"), - rx.select( - [ - 'basis', - 'natural', - 'step' - ], - on_change = AreaState.change_curve_type, - default_value = 'basis', - ), - ), - rx.recharts.area_chart( - rx.recharts.area( - data_key="uv", - on_click=AreaState.randomize_data, - type_ = AreaState.curve_type, - ), - rx.recharts.area( - data_key="pv", - stroke="#82ca9d", - fill="#82ca9d", - on_click=AreaState.randomize_data, - type_ = AreaState.curve_type, - ), - rx.recharts.x_axis( - data_key="name", - ), - rx.recharts.y_axis(), - rx.recharts.legend(), - rx.recharts.cartesian_grid(), - data=AreaState.data, - width = "100%", - height=400, - ), - width="100%", - ) -``` diff --git a/docs/library/graphing/charts/barchart.md b/docs/library/graphing/charts/barchart.md deleted file mode 100644 index dd5de4efa3..0000000000 --- a/docs/library/graphing/charts/barchart.md +++ /dev/null @@ -1,388 +0,0 @@ ---- -components: - - rx.recharts.BarChart - - rx.recharts.Bar ---- - -# Bar Chart - -```python exec -import reflex as rx -import random -from pcweb.pages.docs import library -``` - -A bar chart presents categorical data with rectangular bars with heights or lengths proportional to the values that they represent. - -For a bar chart we must define an `rx.recharts.bar()` component for each set of values we wish to plot. Each `rx.recharts.bar()` component has a `data_key` which clearly states which variable in our data we are tracking. In this simple example we plot `uv` as a bar against the `name` column which we set as the `data_key` in `rx.recharts.x_axis`. - -## Simple Example - -```python demo graphing -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def bar_simple(): - return rx.recharts.bar_chart( - rx.recharts.bar( - data_key="uv", - stroke=rx.color("accent", 9), - fill=rx.color("accent", 8), - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - data=data, - width = "100%", - height = 250, - ) -``` - -## Multiple Bars - -Multiple bars can be placed on the same `bar_chart`, using multiple `rx.recharts.bar()` components. - -```python demo graphing -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def bar_double(): - return rx.recharts.bar_chart( - rx.recharts.bar( - data_key="uv", - stroke=rx.color("accent", 9), - fill=rx.color("accent", 8), - ), - rx.recharts.bar( - data_key="pv", - stroke=rx.color("green", 9), - fill=rx.color("green", 8), - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - data=data, - width = "100%", - height = 250, - ) -``` - -## Ranged Charts - -You can also assign a range in the bar by assigning the data_key in the `rx.recharts.bar` to a list with two elements, i.e. here a range of two temperatures for each date. - -```python demo graphing -range_data = [ - { - "day": "05-01", - "temperature": [ - -1, - 10 - ] - }, - { - "day": "05-02", - "temperature": [ - 2, - 15 - ] - }, - { - "day": "05-03", - "temperature": [ - 3, - 12 - ] - }, - { - "day": "05-04", - "temperature": [ - 4, - 12 - ] - }, - { - "day": "05-05", - "temperature": [ - 12, - 16 - ] - }, - { - "day": "05-06", - "temperature": [ - 5, - 16 - ] - }, - { - "day": "05-07", - "temperature": [ - 3, - 12 - ] - }, - { - "day": "05-08", - "temperature": [ - 0, - 8 - ] - }, - { - "day": "05-09", - "temperature": [ - -3, - 5 - ] - } -] - -def bar_range(): - return rx.recharts.bar_chart( - rx.recharts.bar( - data_key="temperature", - stroke=rx.color("accent", 9), - fill=rx.color("accent", 8), - ), - rx.recharts.x_axis(data_key="day"), - rx.recharts.y_axis(), - data=range_data, - width = "100%", - height = 250, - ) -``` - -## Stateful Charts - -Here is an example of a bar graph with a `State`. Here we have defined a function `randomize_data`, which randomly changes the data for both graphs when the first defined `bar` is clicked on using `on_click=BarState.randomize_data`. - -```python demo exec -class BarState(rx.State): - data = data - - @rx.event - def randomize_data(self): - for i in range(len(self.data)): - self.data[i]["uv"] = random.randint(0, 10000) - self.data[i]["pv"] = random.randint(0, 10000) - self.data[i]["amt"] = random.randint(0, 10000) - -def bar_with_state(): - return rx.recharts.bar_chart( - rx.recharts.cartesian_grid( - stroke_dasharray="3 3", - ), - rx.recharts.bar( - data_key="uv", - stroke=rx.color("accent", 9), - fill=rx.color("accent", 8), - ), - rx.recharts.bar( - data_key="pv", - stroke=rx.color("green", 9), - fill=rx.color("green", 8), - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - rx.recharts.legend(), - on_click=BarState.randomize_data, - data=BarState.data, - width = "100%", - height = 300, - ) -``` - -## Example with Props - -Here's an example demonstrates how to customize the appearance and layout of bars using the `bar_category_gap`, `bar_gap`, `bar_size`, and `max_bar_size` props. These props accept values in pixels to control the spacing and size of the bars. - -```python demo graphing - -data = [ - {'name': 'Page A', 'value': 2400}, - {'name': 'Page B', 'value': 1398}, - {'name': 'Page C', 'value': 9800}, - {'name': 'Page D', 'value': 3908}, - {'name': 'Page E', 'value': 4800}, - {'name': 'Page F', 'value': 3800}, -] - -def bar_features(): - return rx.recharts.bar_chart( - rx.recharts.bar( - data_key="value", - fill=rx.color("accent", 8), - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - data=data, - bar_category_gap="15%", - bar_gap=6, - bar_size=100, - max_bar_size=40, - width="100%", - height=300, - ) -``` - -## Vertical Example - -The `layout` prop allows you to set the orientation of the graph to be vertical or horizontal, it is set horizontally by default. - -```md alert info -# Include margins around your graph to ensure proper spacing and enhance readability. By default, provide margins on all sides of the chart to create a visually appealing and functional representation of your data. -``` - -```python demo graphing -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def bar_vertical(): - return rx.recharts.bar_chart( - rx.recharts.bar( - data_key="uv", - stroke=rx.color("accent", 8), - fill=rx.color("accent", 3), - ), - rx.recharts.x_axis(type_="number"), - rx.recharts.y_axis(data_key="name", type_="category"), - data=data, - layout="vertical", - margin={ - "top": 20, - "right": 20, - "left": 20, - "bottom": 20 - }, - width = "100%", - height = 300, - - ) -``` - -To learn how to use the `sync_id`, `stack_id`,`x_axis_id` and `y_axis_id` props check out the of the area chart [documentation]({library.graphing.charts.areachart.path}), where these props are all described with examples. diff --git a/docs/library/graphing/charts/composedchart.md b/docs/library/graphing/charts/composedchart.md deleted file mode 100644 index f69ed43145..0000000000 --- a/docs/library/graphing/charts/composedchart.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -components: - - rx.recharts.ComposedChart ---- - -```python exec -import reflex as rx -from pcweb.pages.docs import library -``` -# Composed Chart - -A `composed_chart` is a higher-level component chart that is composed of multiple charts, where other charts are the children of the `composed_chart`. The charts are placed on top of each other in the order they are provided in the `composed_chart` function. - - -```md alert info -# To learn more about individual charts, checkout: **[area_chart]({library.graphing.charts.areachart.path})**, **[line_chart]({library.graphing.charts.linechart.path})**, or **[bar_chart]({library.graphing.charts.barchart.path})**. -``` - -```python demo graphing -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def composed(): - return rx.recharts.composed_chart( - rx.recharts.area( - data_key="uv", - stroke="#8884d8", - fill="#8884d8" - ), - rx.recharts.bar( - data_key="amt", - bar_size=20, - fill="#413ea0" - ), - rx.recharts.line( - data_key="pv", - type_="monotone", - stroke="#ff7300" - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - rx.recharts.cartesian_grid(stroke_dasharray="3 3"), - rx.recharts.graphing_tooltip(), - data=data, - height=250, - width="100%", - ) -``` diff --git a/docs/library/graphing/charts/errorbar.md b/docs/library/graphing/charts/errorbar.md deleted file mode 100644 index 2e4b3bdd3d..0000000000 --- a/docs/library/graphing/charts/errorbar.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -components: - - rx.recharts.ErrorBar ---- - -```python exec -import reflex as rx -``` - -# Error Bar - -An error bar is a graphical representation of the uncertainty or variability of a data point in a chart, depicted as a line extending from the data point parallel to one of the axes. The `data_key`, `width`, `stroke_width`, `stroke`, and `direction` props can be used to customize the appearance and behavior of the error bars, specifying the data source, dimensions, color, and orientation of the error bars. - -```python demo graphing -data = [ - { - "x": 45, - "y": 100, - "z": 150, - "errorY": [ - 30, - 20 - ], - "errorX": 5 - }, - { - "x": 100, - "y": 200, - "z": 200, - "errorY": [ - 20, - 30 - ], - "errorX": 3 - }, - { - "x": 120, - "y": 100, - "z": 260, - "errorY": 20, - "errorX": [ - 5, - 3 - ] - }, - { - "x": 170, - "y": 300, - "z": 400, - "errorY": [ - 15, - 18 - ], - "errorX": 4 - }, - { - "x": 140, - "y": 250, - "z": 280, - "errorY": 23, - "errorX": [ - 6, - 7 - ] - }, - { - "x": 150, - "y": 400, - "z": 500, - "errorY": [ - 21, - 10 - ], - "errorX": 4 - }, - { - "x": 110, - "y": 280, - "z": 200, - "errorY": 21, - "errorX": [ - 5, - 6 - ] - } -] - -def error(): - return rx.recharts.scatter_chart( - rx.recharts.scatter( - rx.recharts.error_bar(data_key="errorY", direction="y", width=4, stroke_width=2, stroke="red"), - rx.recharts.error_bar(data_key="errorX", direction="x", width=4, stroke_width=2), - data=data, - fill="#8884d8", - name="A"), - rx.recharts.x_axis(data_key="x", name="x", type_="number"), - rx.recharts.y_axis(data_key="y", name="y", type_="number"), - width = "100%", - height = 300, - ) -``` diff --git a/docs/library/graphing/charts/funnelchart.md b/docs/library/graphing/charts/funnelchart.md deleted file mode 100644 index 69e98f84d2..0000000000 --- a/docs/library/graphing/charts/funnelchart.md +++ /dev/null @@ -1,210 +0,0 @@ ---- -components: - - rx.recharts.FunnelChart - - rx.recharts.Funnel ---- - -```python exec -import reflex as rx -import random -rx.toast.provider() -``` - -# Funnel Chart - -A funnel chart is a graphical representation used to visualize how data moves through a process. In a funnel chart, the dependent variable’s value diminishes in the subsequent stages of the process. It can be used to demonstrate the flow of users through a business or sales process. - -## Simple Example - -```python demo graphing -data = [ - { - "value": 100, - "name": "Sent", - "fill": "#8884d8" - }, - { - "value": 80, - "name": "Viewed", - "fill": "#83a6ed" - }, - { - "value": 50, - "name": "Clicked", - "fill": "#8dd1e1" - }, - { - "value": 40, - "name": "Add to Cart", - "fill": "#82ca9d" - }, - { - "value": 26, - "name": "Purchased", - "fill": "#a4de6c" - } -] - -def funnel_simple(): - return rx.recharts.funnel_chart( - rx.recharts.funnel( - rx.recharts.label_list( - position="right", - data_key="name", - fill="#000", - stroke="none", - ), - data_key="value", - data=data, - ), - width="100%", - height=250, - ) -``` - -## Event Triggers - -Funnel chart supports `on_click`, `on_mouse_enter`, `on_mouse_leave` and `on_mouse_move` event triggers, allows you to interact with the funnel chart and perform specific actions based on user interactions. - -```python demo graphing -data = [ - { - "value": 100, - "name": "Sent", - "fill": "#8884d8" - }, - { - "value": 80, - "name": "Viewed", - "fill": "#83a6ed" - }, - { - "value": 50, - "name": "Clicked", - "fill": "#8dd1e1" - }, - { - "value": 40, - "name": "Add to Cart", - "fill": "#82ca9d" - }, - { - "value": 26, - "name": "Purchased", - "fill": "#a4de6c" - } -] - -def funnel_events(): - return rx.recharts.funnel_chart( - rx.recharts.funnel( - rx.recharts.label_list( - position="right", - data_key="name", - fill="#000", - stroke="none", - ), - data_key="value", - data=data, - ), - on_click=rx.toast("Clicked on funnel chart"), - on_mouse_enter=rx.toast("Mouse entered"), - on_mouse_leave=rx.toast("Mouse left"), - width="100%", - height=250, - ) -``` - -## Dynamic Data - -Here is an example of a funnel chart with a `State`. Here we have defined a function `randomize_data`, which randomly changes the data when the graph is clicked on using `on_click=FunnelState.randomize_data`. - -```python exec -data = [ - { - "value": 100, - "name": "Sent", - "fill": "#8884d8" - }, - { - "value": 80, - "name": "Viewed", - "fill": "#83a6ed" - }, - { - "value": 50, - "name": "Clicked", - "fill": "#8dd1e1" - }, - { - "value": 40, - "name": "Add to Cart", - "fill": "#82ca9d" - }, - { - "value": 26, - "name": "Purchased", - "fill": "#a4de6c" - } -] -``` - -```python demo exec -class FunnelState(rx.State): - data = data - - @rx.event - def randomize_data(self): - self.data[0]["value"] = 100 - for i in range(len(self.data) - 1): - self.data[i + 1]["value"] = self.data[i][ - "value" - ] - random.randint(0, 20) - - -def funnel_state(): - return rx.recharts.funnel_chart( - rx.recharts.funnel( - rx.recharts.label_list( - position="right", - data_key="name", - fill="#000", - stroke="none", - ), - data_key="value", - data=FunnelState.data, - on_click=FunnelState.randomize_data, - ), - rx.recharts.graphing_tooltip(), - width="100%", - height=250, - ) -``` - -## Changing the Chart Animation - -The `is_animation_active` prop can be used to turn off the animation, but defaults to `True`. `animation_begin` sets the delay before animation starts, `animation_duration` determines how long the animation lasts, and `animation_easing` defines the speed curve of the animation for smooth transitions. - -```python demo graphing -data = [ - {"name": "Visits", "value": 5000, "fill": "#8884d8"}, - {"name": "Cart", "value": 3000, "fill": "#83a6ed"}, - {"name": "Checkout", "value": 2500, "fill": "#8dd1e1"}, - {"name": "Purchase", "value": 1000, "fill": "#82ca9d"}, - ] - -def funnel_animation(): - return rx.recharts.funnel_chart( - rx.recharts.funnel( - data_key="value", - data=data, - animation_begin=300, - animation_duration=9000, - animation_easing="ease-in-out", - ), - rx.recharts.graphing_tooltip(), - rx.recharts.legend(), - width="100%", - height=300, - ) -``` diff --git a/docs/library/graphing/charts/linechart.md b/docs/library/graphing/charts/linechart.md deleted file mode 100644 index 4df06cd466..0000000000 --- a/docs/library/graphing/charts/linechart.md +++ /dev/null @@ -1,309 +0,0 @@ ---- -components: - - rx.recharts.LineChart - - rx.recharts.Line ---- - -# Line Chart - -```python exec -import random -from typing import Any -from pcweb.pages.docs import library -import reflex as rx -``` - -A line chart is a type of chart used to show information that changes over time. Line charts are created by plotting a series of several points and connecting them with a straight line. - -## Simple Example - -For a line chart we must define an `rx.recharts.line()` component for each set of values we wish to plot. Each `rx.recharts.line()` component has a `data_key` which clearly states which variable in our data we are tracking. In this simple example we plot `pv` and `uv` as separate lines against the `name` column which we set as the `data_key` in `rx.recharts.x_axis`. - -```python demo graphing -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def line_simple(): - return rx.recharts.line_chart( - rx.recharts.line( - data_key="pv", - ), - rx.recharts.line( - data_key="uv", - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - data=data, - width = "100%", - height = 300, - ) -``` - -## Example with Props - -Our second example uses exactly the same data as our first example, except now we add some extra features to our line graphs. We add a `type_` prop to `rx.recharts.line` to style the lines differently. In addition, we add an `rx.recharts.cartesian_grid` to get a grid in the background, an `rx.recharts.legend` to give us a legend for our graphs and an `rx.recharts.graphing_tooltip` to add a box with text that appears when you pause the mouse pointer on an element in the graph. - -```python demo graphing - -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def line_features(): - return rx.recharts.line_chart( - rx.recharts.line( - data_key="pv", - type_="monotone", - stroke="#8884d8",), - rx.recharts.line( - data_key="uv", - type_="monotone", - stroke="#82ca9d",), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - rx.recharts.cartesian_grid(stroke_dasharray="3 3"), - rx.recharts.graphing_tooltip(), - rx.recharts.legend(), - data=data, - width = "100%", - height = 300, - ) -``` - -## Layout - -The `layout` prop allows you to set the orientation of the graph to be vertical or horizontal. The `margin` prop defines the spacing around the graph, - -```md alert info -# Include margins around your graph to ensure proper spacing and enhance readability. By default, provide margins on all sides of the chart to create a visually appealing and functional representation of your data. -``` - -```python demo graphing - -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def line_vertical(): - return rx.recharts.line_chart( - rx.recharts.line( - data_key="pv", - stroke=rx.color("accent", 9), - ), - rx.recharts.line( - data_key="uv", - stroke=rx.color("green", 9), - ), - rx.recharts.x_axis(type_="number"), - rx.recharts.y_axis(data_key="name", type_="category"), - layout="vertical", - margin={ - "top": 20, - "right": 20, - "left": 20, - "bottom": 20 - }, - data = data, - height = 300, - width = "100%", - ) -``` - -## Dynamic Data - -Chart data can be modified by tying the `data` prop to a State var. Most other -props, such as `type_`, can be controlled dynamically as well. In the following -example the "Munge Data" button can be used to randomly modify the data, and the -two `select` elements change the line `type_`. Since the data and style is saved -in the per-browser-tab State, the changes will not be visible to other visitors. - -```python demo exec - -initial_data = data - -class LineChartState(rx.State): - data: list[dict[str, Any]] = initial_data - pv_type: str = "monotone" - uv_type: str = "monotone" - - @rx.event - def set_pv_type(self, pv_type: str): - self.pv_type = pv_type - - @rx.event - def set_uv_type(self, uv_type: str): - self.uv_type = uv_type - - @rx.event - def munge_data(self): - for row in self.data: - row["uv"] += random.randint(-500, 500) - row["pv"] += random.randint(-1000, 1000) - -def line_dynamic(): - return rx.vstack( - rx.recharts.line_chart( - rx.recharts.line( - data_key="pv", - type_=LineChartState.pv_type, - stroke="#8884d8", - ), - rx.recharts.line( - data_key="uv", - type_=LineChartState.uv_type, - stroke="#82ca9d", - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - data=LineChartState.data, - margin={ - "top": 20, - "right": 20, - "left": 20, - "bottom": 20 - }, - width = "100%", - height = 300, - ), - rx.hstack( - rx.button("Munge Data", on_click=LineChartState.munge_data), - rx.select( - ["monotone", "linear", "step", "stepBefore", "stepAfter"], - value=LineChartState.pv_type, - on_change=LineChartState.set_pv_type - ), - rx.select( - ["monotone", "linear", "step", "stepBefore", "stepAfter"], - value=LineChartState.uv_type, - on_change=LineChartState.set_uv_type - ), - ), - width="100%", - ) -``` - -To learn how to use the `sync_id`, `x_axis_id` and `y_axis_id` props check out the of the area chart [documentation]({library.graphing.charts.areachart.path}), where these props are all described with examples. diff --git a/docs/library/graphing/charts/piechart.md b/docs/library/graphing/charts/piechart.md deleted file mode 100644 index 999780ad72..0000000000 --- a/docs/library/graphing/charts/piechart.md +++ /dev/null @@ -1,216 +0,0 @@ ---- -components: - - rx.recharts.PieChart - - rx.recharts.Pie ---- - -# Pie Chart - -```python exec -import reflex as rx -``` - -A pie chart is a circular statistical graphic which is divided into slices to illustrate numerical proportion. - -For a pie chart we must define an `rx.recharts.pie()` component for each set of values we wish to plot. Each `rx.recharts.pie()` component has a `data`, a `data_key` and a `name_key` which clearly states which data and which variables in our data we are tracking. In this simple example we plot `value` column as our `data_key` against the `name` column which we set as our `name_key`. -We also use the `fill` prop to set the color of the pie slices. - -```python demo graphing - -data01 = [ - { - "name": "Group A", - "value": 400 - }, - { - "name": "Group B", - "value": 300, - "fill":"#AC0E08FF" - }, - { - "name": "Group C", - "value": 300, - "fill":"rgb(80,40, 190)" - }, - { - "name": "Group D", - "value": 200, - "fill":rx.color("yellow", 10) - }, - { - "name": "Group E", - "value": 278, - "fill":"purple" - }, - { - "name": "Group F", - "value": 189, - "fill":"orange" - } -] - -def pie_simple(): - return rx.recharts.pie_chart( - rx.recharts.pie( - data=data01, - data_key="value", - name_key="name", - fill="#8884d8", - label=True, - ), - width="100%", - height=300, - ) -``` - -We can also add two pies on one chart by using two `rx.recharts.pie` components. - -In this example `inner_radius` and `outer_radius` props are used. They define the doughnut shape of a pie chart: `inner_radius` creates the hollow center (use "0%" for a full pie), while `outer_radius` sets the overall size. The `padding_angle` prop, used on the green pie below, adds space between pie slices, enhancing visibility of individual segments. - -```python demo graphing - -data01 = [ - { - "name": "Group A", - "value": 400 - }, - { - "name": "Group B", - "value": 300 - }, - { - "name": "Group C", - "value": 300 - }, - { - "name": "Group D", - "value": 200 - }, - { - "name": "Group E", - "value": 278 - }, - { - "name": "Group F", - "value": 189 - } -] -data02 = [ - { - "name": "Group A", - "value": 2400 - }, - { - "name": "Group B", - "value": 4567 - }, - { - "name": "Group C", - "value": 1398 - }, - { - "name": "Group D", - "value": 9800 - }, - { - "name": "Group E", - "value": 3908 - }, - { - "name": "Group F", - "value": 4800 - } -] - - -def pie_double(): - return rx.recharts.pie_chart( - rx.recharts.pie( - data=data01, - data_key="value", - name_key="name", - fill="#82ca9d", - inner_radius="60%", - padding_angle=5, - ), - rx.recharts.pie( - data=data02, - data_key="value", - name_key="name", - fill="#8884d8", - outer_radius="50%", - ), - rx.recharts.graphing_tooltip(), - width="100%", - height=300, - ) -``` - -## Dynamic Data - -Chart data tied to a State var causes the chart to automatically update when the -state changes, providing a nice way to visualize data in response to user -interface elements. View the "Data" tab to see the substate driving this -half-pie chart. - -```python demo exec -from typing import Any - - -class PieChartState(rx.State): - resources: list[dict[str, Any]] = [ - dict(type_="🏆", count=1), - dict(type_="🪵", count=1), - dict(type_="🥑", count=1), - dict(type_="🧱", count=1), - ] - - @rx.var(cache=True) - def resource_types(self) -> list[str]: - return [r["type_"] for r in self.resources] - - @rx.event - def increment(self, type_: str): - for resource in self.resources: - if resource["type_"] == type_: - resource["count"] += 1 - break - - @rx.event - def decrement(self, type_: str): - for resource in self.resources: - if resource["type_"] == type_ and resource["count"] > 0: - resource["count"] -= 1 - break - - -def dynamic_pie_example(): - return rx.hstack( - rx.recharts.pie_chart( - rx.recharts.pie( - data=PieChartState.resources, - data_key="count", - name_key="type_", - cx="50%", - cy="50%", - start_angle=180, - end_angle=0, - fill="#8884d8", - label=True, - ), - rx.recharts.graphing_tooltip(), - ), - rx.vstack( - rx.foreach( - PieChartState.resource_types, - lambda type_, i: rx.hstack( - rx.button("-", on_click=PieChartState.decrement(type_)), - rx.text(type_, PieChartState.resources[i]["count"]), - rx.button("+", on_click=PieChartState.increment(type_)), - ), - ), - ), - width="100%", - height="15em", - ) -``` diff --git a/docs/library/graphing/charts/radarchart.md b/docs/library/graphing/charts/radarchart.md deleted file mode 100644 index 699ce1e2e3..0000000000 --- a/docs/library/graphing/charts/radarchart.md +++ /dev/null @@ -1,285 +0,0 @@ ---- -components: - - rx.recharts.RadarChart - - rx.recharts.Radar ---- - -# Radar Chart - -```python exec -import reflex as rx -from typing import Any -``` - -A radar chart shows multivariate data of three or more quantitative variables mapped onto an axis. - -## Simple Example - -For a radar chart we must define an `rx.recharts.radar()` component for each set of values we wish to plot. Each `rx.recharts.radar()` component has a `data_key` which clearly states which variable in our data we are plotting. In this simple example we plot the `A` column of our data against the `subject` column which we set as the `data_key` in `rx.recharts.polar_angle_axis`. - -```python demo graphing -data = [ - { - "subject": "Math", - "A": 120, - "B": 110, - "fullMark": 150 - }, - { - "subject": "Chinese", - "A": 98, - "B": 130, - "fullMark": 150 - }, - { - "subject": "English", - "A": 86, - "B": 130, - "fullMark": 150 - }, - { - "subject": "Geography", - "A": 99, - "B": 100, - "fullMark": 150 - }, - { - "subject": "Physics", - "A": 85, - "B": 90, - "fullMark": 150 - }, - { - "subject": "History", - "A": 65, - "B": 85, - "fullMark": 150 - } -] - -def radar_simple(): - return rx.recharts.radar_chart( - rx.recharts.radar( - data_key="A", - stroke="#8884d8", - fill="#8884d8", - ), - rx.recharts.polar_grid(), - rx.recharts.polar_angle_axis(data_key="subject"), - rx.recharts.polar_radius_axis(angle=90, domain=[0, 150]), - data=data, - width="100%", - height=300, - ) -``` - -## Multiple Radars - -We can also add two radars on one chart by using two `rx.recharts.radar` components. - -In this plot an `inner_radius` and an `outer_radius` are set which determine the chart's size and shape. The `inner_radius` sets the distance from the center to the innermost part of the chart (creating a hollow center if greater than zero), while the `outer_radius` defines the chart's overall size by setting the distance from the center to the outermost edge of the radar plot. - -```python demo graphing - -data = [ - { - "subject": "Math", - "A": 120, - "B": 110, - "fullMark": 150 - }, - { - "subject": "Chinese", - "A": 98, - "B": 130, - "fullMark": 150 - }, - { - "subject": "English", - "A": 86, - "B": 130, - "fullMark": 150 - }, - { - "subject": "Geography", - "A": 99, - "B": 100, - "fullMark": 150 - }, - { - "subject": "Physics", - "A": 85, - "B": 90, - "fullMark": 150 - }, - { - "subject": "History", - "A": 65, - "B": 85, - "fullMark": 150 - } -] - -def radar_multiple(): - return rx.recharts.radar_chart( - rx.recharts.radar( - data_key="A", - stroke="#8884d8", - fill="#8884d8", - ), - rx.recharts.radar( - data_key="B", - stroke="#82ca9d", - fill="#82ca9d", - fill_opacity=0.6, - ), - rx.recharts.polar_grid(), - rx.recharts.polar_angle_axis(data_key="subject"), - rx.recharts.polar_radius_axis(angle=90, domain=[0, 150]), - rx.recharts.legend(), - data=data, - inner_radius="15%", - outer_radius="80%", - width="100%", - height=300, - ) - -``` - -## Using More Props - -The `dot` prop shows points at each data vertex when true. `legend_type="line"` displays a line in the chart legend. `animation_begin=0` starts the animation immediately, `animation_duration=8000` sets an 8-second animation, and `animation_easing="ease-in"` makes the animation start slowly and speed up. These props control the chart's appearance and animation behavior. - -```python demo graphing - -data = [ - { - "subject": "Math", - "A": 120, - "B": 110, - "fullMark": 150 - }, - { - "subject": "Chinese", - "A": 98, - "B": 130, - "fullMark": 150 - }, - { - "subject": "English", - "A": 86, - "B": 130, - "fullMark": 150 - }, - { - "subject": "Geography", - "A": 99, - "B": 100, - "fullMark": 150 - }, - { - "subject": "Physics", - "A": 85, - "B": 90, - "fullMark": 150 - }, - { - "subject": "History", - "A": 65, - "B": 85, - "fullMark": 150 - } - ] - - -def radar_start_end(): - return rx.recharts.radar_chart( - rx.recharts.radar( - data_key="A", - dot=True, - stroke="#8884d8", - fill="#8884d8", - fill_opacity=0.6, - legend_type="line", - animation_begin=0, - animation_duration=8000, - animation_easing="ease-in", - ), - rx.recharts.polar_grid(), - rx.recharts.polar_angle_axis(data_key="subject"), - rx.recharts.polar_radius_axis(angle=90, domain=[0, 150]), - rx.recharts.legend(), - data=data, - width="100%", - height=300, - ) - -``` - -# Dynamic Data - -Chart data tied to a State var causes the chart to automatically update when the -state changes, providing a nice way to visualize data in response to user -interface elements. View the "Data" tab to see the substate driving this -radar chart of character traits. - -```python demo exec -class RadarChartState(rx.State): - total_points: int = 100 - traits: list[dict[str, Any]] = [ - dict(trait="Strength", value=15), - dict(trait="Dexterity", value=15), - dict(trait="Constitution", value=15), - dict(trait="Intelligence", value=15), - dict(trait="Wisdom", value=15), - dict(trait="Charisma", value=15), - ] - - @rx.var - def remaining_points(self) -> int: - return self.total_points - sum(t["value"] for t in self.traits) - - @rx.var(cache=True) - def trait_names(self) -> list[str]: - return [t["trait"] for t in self.traits] - - @rx.event - def set_trait(self, trait: str, value: int): - for t in self.traits: - if t["trait"] == trait: - available_points = self.remaining_points + t["value"] - value = min(value, available_points) - t["value"] = value - break - -def radar_dynamic(): - return rx.hstack( - rx.recharts.radar_chart( - rx.recharts.radar( - data_key="value", - stroke="#8884d8", - fill="#8884d8", - ), - rx.recharts.polar_grid(), - rx.recharts.polar_angle_axis(data_key="trait"), - data=RadarChartState.traits, - ), - rx.vstack( - rx.foreach( - RadarChartState.trait_names, - lambda trait_name, i: rx.hstack( - rx.text(trait_name, width="7em"), - rx.slider( - default_value=RadarChartState.traits[i]["value"].to(int), - on_change=lambda value: RadarChartState.set_trait(trait_name, value[0]), - width="25vw", - ), - rx.text(RadarChartState.traits[i]['value']), - ), - ), - rx.text("Remaining points: ", RadarChartState.remaining_points), - ), - width="100%", - height="15em", - ) -``` diff --git a/docs/library/graphing/charts/radialbarchart.md b/docs/library/graphing/charts/radialbarchart.md deleted file mode 100644 index 136105967d..0000000000 --- a/docs/library/graphing/charts/radialbarchart.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -components: - - rx.recharts.RadialBarChart ---- - -# Radial Bar Chart - -```python exec -import reflex as rx -``` -## Simple Example - -This example demonstrates how to use a `radial_bar_chart` with a `radial_bar`. The `radial_bar_chart` takes in `data` and then the `radial_bar` takes in a `data_key`. A radial bar chart is a circular visualization where data categories are represented by bars extending outward from a central point, with the length of each bar proportional to its value. - -```md alert info -# Fill color supports `rx.color()`, which automatically adapts to dark/light mode changes. -``` - -```python demo graphing -data = [ - {"name": "C", "x": 3, "fill": rx.color("cyan", 9)}, - {"name": "D", "x": 4, "fill": rx.color("blue", 9)}, - {"name": "E", "x": 5, "fill": rx.color("orange", 9)}, - {"name": "F", "x": 6, "fill": rx.color("red", 9)}, - {"name": "G", "x": 7, "fill": rx.color("gray", 9)}, - {"name": "H", "x": 8, "fill": rx.color("green", 9)}, - {"name": "I", "x": 9, "fill": rx.color("accent", 6)}, -] - -def radial_bar_simple(): - return rx.recharts.radial_bar_chart( - rx.recharts.radial_bar( - data_key="x", - min_angle=15, - ), - data=data, - width = "100%", - height = 500, - ) -``` - -## Advanced Example - -The `start_angle` and `end_angle` define the circular arc over which the bars are distributed, while `inner_radius` and `outer_radius` determine the radial extent of the bars from the center. - -```python demo graphing - -data_radial_bar = [ - { - "name": "18-24", - "uv": 31.47, - "pv": 2400, - "fill": "#8884d8" - }, - { - "name": "25-29", - "uv": 26.69, - "pv": 4567, - "fill": "#83a6ed" - }, - { - "name": "30-34", - "uv": -15.69, - "pv": 1398, - "fill": "#8dd1e1" - }, - { - "name": "35-39", - "uv": 8.22, - "pv": 9800, - "fill": "#82ca9d" - }, - { - "name": "40-49", - "uv": -8.63, - "pv": 3908, - "fill": "#a4de6c" - }, - { - "name": "50+", - "uv": -2.63, - "pv": 4800, - "fill": "#d0ed57" - }, - { - "name": "unknown", - "uv": 6.67, - "pv": 4800, - "fill": "#ffc658" - } -] - -def radial_bar_advanced(): - return rx.recharts.radial_bar_chart( - rx.recharts.radial_bar( - data_key="uv", - min_angle=90, - background=True, - label={"fill": '#666', "position": 'insideStart'}, - ), - data=data_radial_bar, - inner_radius="10%", - outer_radius="80%", - start_angle=180, - end_angle=0, - width="100%", - height=300, - ) -``` \ No newline at end of file diff --git a/docs/library/graphing/charts/scatterchart.md b/docs/library/graphing/charts/scatterchart.md deleted file mode 100644 index 03afdd264b..0000000000 --- a/docs/library/graphing/charts/scatterchart.md +++ /dev/null @@ -1,296 +0,0 @@ ---- -components: - - rx.recharts.ScatterChart - - rx.recharts.Scatter ---- - -# Scatter Chart - -```python exec -import reflex as rx -from pcweb.templates.docpage import docgraphing -from pcweb.pages.docs import library -``` - -A scatter chart always has two value axes to show one set of numerical data along a horizontal (value) axis and another set of numerical values along a vertical (value) axis. The chart displays points at the intersection of an x and y numerical value, combining these values into single data points. - -## Simple Example - -For a scatter chart we must define an `rx.recharts.scatter()` component for each set of values we wish to plot. Each `rx.recharts.scatter()` component has a `data` prop which clearly states which data source we plot. We also must define `rx.recharts.x_axis()` and `rx.recharts.y_axis()` so that the graph knows what data to plot on each axis. - -```python demo graphing -data01 = [ - { - "x": 100, - "y": 200, - "z": 200 - }, - { - "x": 120, - "y": 100, - "z": 260 - }, - { - "x": 170, - "y": 300, - "z": 400 - }, - { - "x": 170, - "y": 250, - "z": 280 - }, - { - "x": 150, - "y": 400, - "z": 500 - }, - { - "x": 110, - "y": 280, - "z": 200 - } -] - -def scatter_simple(): - return rx.recharts.scatter_chart( - rx.recharts.scatter( - data=data01, - fill="#8884d8",), - rx.recharts.x_axis(data_key="x", type_="number"), - rx.recharts.y_axis(data_key="y"), - width = "100%", - height = 300, - ) -``` - -## Multiple Scatters - -We can also add two scatters on one chart by using two `rx.recharts.scatter()` components, and we can define an `rx.recharts.z_axis()` which represents a third column of data and is represented by the size of the dots in the scatter plot. - -```python demo graphing -data01 = [ - { - "x": 100, - "y": 200, - "z": 200 - }, - { - "x": 120, - "y": 100, - "z": 260 - }, - { - "x": 170, - "y": 300, - "z": 400 - }, - { - "x": 170, - "y": 250, - "z": 280 - }, - { - "x": 150, - "y": 350, - "z": 500 - }, - { - "x": 110, - "y": 280, - "z": 200 - } -] - -data02 = [ - { - "x": 200, - "y": 260, - "z": 240 - }, - { - "x": 240, - "y": 290, - "z": 220 - }, - { - "x": 190, - "y": 290, - "z": 250 - }, - { - "x": 198, - "y": 250, - "z": 210 - }, - { - "x": 180, - "y": 280, - "z": 260 - }, - { - "x": 210, - "y": 220, - "z": 230 - } -] - -def scatter_double(): - return rx.recharts.scatter_chart( - rx.recharts.scatter( - data=data01, - fill="#8884d8", - name="A" - ), - rx.recharts.scatter( - data=data02, - fill="#82ca9d", - name="B" - ), - rx.recharts.cartesian_grid(stroke_dasharray="3 3"), - rx.recharts.x_axis(data_key="x", type_="number"), - rx.recharts.y_axis(data_key="y"), - rx.recharts.z_axis(data_key="z", range=[60, 400], name="score"), - rx.recharts.legend(), - rx.recharts.graphing_tooltip(), - width="100%", - height=300, - ) -``` - -To learn how to use the `x_axis_id` and `y_axis_id` props, check out the Multiple Axis section of the area chart [documentation]({library.graphing.charts.areachart.path}). - -## Dynamic Data - -Chart data tied to a State var causes the chart to automatically update when the -state changes, providing a nice way to visualize data in response to user -interface elements. View the "Data" tab to see the substate driving this -calculation of iterations in the Collatz Conjecture for a given starting number. -Enter a starting number in the box below the chart to recalculate. - -```python demo exec -class ScatterChartState(rx.State): - data: list[dict[str, int]] = [] - - @rx.event - def compute_collatz(self, form_data: dict) -> int: - n = int(form_data.get("start") or 1) - yield rx.set_value("start", "") - self.data = [] - for ix in range(400): - self.data.append({"x": ix, "y": n}) - if n == 1: - break - if n % 2 == 0: - n = n // 2 - else: - n = 3 * n + 1 - - -def scatter_dynamic(): - return rx.vstack( - rx.recharts.scatter_chart( - rx.recharts.scatter( - data=ScatterChartState.data, - fill="#8884d8", - ), - rx.recharts.x_axis(data_key="x", type_="number"), - rx.recharts.y_axis(data_key="y", type_="number"), - ), - rx.form.root( - rx.input(placeholder="Enter a number", id="start"), - rx.button("Compute", type="submit"), - on_submit=ScatterChartState.compute_collatz, - ), - width="100%", - height="15em", - on_mount=ScatterChartState.compute_collatz({"start": "15"}), - ) -``` - -## Legend Type and Shape - -```python demo exec -class ScatterChartState2(rx.State): - - legend_types: list[str] = ["square", "circle", "cross", "diamond", "star", "triangle", "wye"] - - legend_type: str = "circle" - - shapes: list[str] = ["square", "circle", "cross", "diamond", "star", "triangle", "wye"] - - shape: str = "circle" - - data01 = [ - { - "x": 100, - "y": 200, - "z": 200 - }, - { - "x": 120, - "y": 100, - "z": 260 - }, - { - "x": 170, - "y": 300, - "z": 400 - }, - { - "x": 170, - "y": 250, - "z": 280 - }, - { - "x": 150, - "y": 400, - "z": 500 - }, - { - "x": 110, - "y": 280, - "z": 200 - } - ] - - @rx.event - def set_shape(self, shape: str): - self.shape = shape - - @rx.event - def set_legend_type(self, legend_type: str): - self.legend_type = legend_type - -def scatter_shape(): - return rx.vstack( - rx.recharts.scatter_chart( - rx.recharts.scatter( - data=data01, - fill="#8884d8", - legend_type=ScatterChartState2.legend_type, - shape=ScatterChartState2.shape, - ), - rx.recharts.x_axis(data_key="x", type_="number"), - rx.recharts.y_axis(data_key="y"), - rx.recharts.legend(), - width = "100%", - height = 300, - ), - rx.hstack( - rx.text("Legend Type: "), - rx.select( - ScatterChartState2.legend_types, - value=ScatterChartState2.legend_type, - on_change=ScatterChartState2.set_legend_type, - ), - rx.text("Shape: "), - rx.select( - ScatterChartState2.shapes, - value=ScatterChartState2.shape, - on_change=ScatterChartState2.set_shape, - ), - ), - width="100%", - ) -``` diff --git a/docs/library/graphing/general/axis.md b/docs/library/graphing/general/axis.md deleted file mode 100644 index 4c2d3d0b75..0000000000 --- a/docs/library/graphing/general/axis.md +++ /dev/null @@ -1,296 +0,0 @@ ---- -components: - - rx.recharts.XAxis - - rx.recharts.YAxis - - rx.recharts.ZAxis ---- - -```python exec -import reflex as rx -``` - -# Axis - -The Axis component in Recharts is a powerful tool for customizing and configuring the axes of your charts. It provides a wide range of props that allow you to control the appearance, behavior, and formatting of the axis. Whether you're working with an AreaChart, LineChart, or any other chart type, the Axis component enables you to create precise and informative visualizations. - -## Basic Example - -```python demo graphing - -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def axis_simple(): - return rx.recharts.area_chart( - rx.recharts.area( - data_key="uv", - stroke=rx.color("accent", 9), - fill=rx.color("accent", 8), - ), - rx.recharts.x_axis( - data_key="name", - label={"value": 'Pages', "position": "bottom"}, - ), - rx.recharts.y_axis( - data_key="uv", - label={"value": 'Views', "angle": -90, "position": "left"}, - ), - data=data, - width="100%", - height=300, - margin={ - "bottom": 40, - "left": 40, - "right": 40, - }, - ) -``` - -## Multiple Axes - -Multiple axes can be used for displaying different data series with varying scales or units on the same chart. This allows for a more comprehensive comparison and analysis of the data. - -```python demo graphing - -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def multi_axis(): - return rx.recharts.area_chart( - rx.recharts.area( - data_key="uv", stroke="#8884d8", fill="#8884d8", y_axis_id="left", - ), - rx.recharts.area( - data_key="pv", y_axis_id="right", type_="monotone", stroke="#82ca9d", fill="#82ca9d" - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(data_key="uv", y_axis_id="left"), - rx.recharts.y_axis(data_key="pv", y_axis_id="right", orientation="right"), - rx.recharts.graphing_tooltip(), - rx.recharts.legend(), - data=data, - width = "100%", - height = 300, - ) -``` - -## Choosing Location of Labels for Axes - -The axes `label` can take several positions. The example below allows you to try out different locations for the x and y axis labels. - -```python demo graphing - -class AxisState(rx.State): - - label_positions: list[str] = ["center", "insideTopLeft", "insideTopRight", "insideBottomRight", "insideBottomLeft", "insideTop", "insideBottom", "insideLeft", "insideRight", "outside", "top", "bottom", "left", "right"] - - label_offsets: list[str] = ["-30", "-20", "-10", "0", "10", "20", "30"] - - x_axis_postion: str = "bottom" - - x_axis_offset: int - - y_axis_postion: str = "left" - - y_axis_offset: int - - @rx.event - @rx.event - def set_y_axis_position(self, position: str): - self.y_axis_position = position - - @rx.event - def set_x_axis_position(self, position: str): - self.x_axis_position = position - - @rx.event - def set_x_axis_offset(self, offset: str): - self.x_axis_offset = int(offset) - - @rx.event - def set_y_axis_offset(self, offset: str): - self.y_axis_offset = int(offset) - -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def axis_labels(): - return rx.vstack( - rx.recharts.area_chart( - rx.recharts.area( - data_key="uv", - stroke=rx.color("accent", 9), - fill=rx.color("accent", 8), - ), - rx.recharts.x_axis( - data_key="name", - label={"value": 'Pages', "position": AxisState.x_axis_postion, "offset": AxisState.x_axis_offset}, - ), - rx.recharts.y_axis( - data_key="uv", - label={"value": 'Views', "angle": -90, "position": AxisState.y_axis_postion, "offset": AxisState.y_axis_offset}, - ), - data=data, - width="100%", - height=300, - margin={ - "bottom": 40, - "left": 40, - "right": 40, - } - ), - rx.hstack( - rx.text("X Label Position: "), - rx.select( - AxisState.label_positions, - value=AxisState.x_axis_postion, - on_change=AxisState.set_x_axis_postion, - ), - rx.text("X Label Offset: "), - rx.select( - AxisState.label_offsets, - value=AxisState.x_axis_offset.to_string(), - on_change=AxisState.set_x_axis_offset, - ), - rx.text("Y Label Position: "), - rx.select( - AxisState.label_positions, - value=AxisState.y_axis_postion, - on_change=AxisState.set_y_axis_postion, - ), - rx.text("Y Label Offset: "), - rx.select( - AxisState.label_offsets, - value=AxisState.y_axis_offset.to_string(), - on_change=AxisState.set_y_axis_offset, - ), - ), - width="100%", - ) -``` diff --git a/docs/library/graphing/general/brush.md b/docs/library/graphing/general/brush.md deleted file mode 100644 index 62fcc66608..0000000000 --- a/docs/library/graphing/general/brush.md +++ /dev/null @@ -1,153 +0,0 @@ ---- -components: - - rx.recharts.Brush ---- - -# Brush - -```python exec -import reflex as rx -``` -## Simple Example - -The brush component allows us to view charts that have a large number of data points. To view and analyze them efficiently, the brush provides a slider with two handles that helps the viewer to select some range of data points to be displayed. - -```python demo graphing -data = [ - { "name": '1', "uv": 300, "pv": 456 }, - { "name": '2', "uv": -145, "pv": 230 }, - { "name": '3', "uv": -100, "pv": 345 }, - { "name": '4', "uv": -8, "pv": 450 }, - { "name": '5', "uv": 100, "pv": 321 }, - { "name": '6', "uv": 9, "pv": 235 }, - { "name": '7', "uv": 53, "pv": 267 }, - { "name": '8', "uv": 252, "pv": -378 }, - { "name": '9', "uv": 79, "pv": -210 }, - { "name": '10', "uv": 294, "pv": -23 }, - { "name": '12', "uv": 43, "pv": 45 }, - { "name": '13', "uv": -74, "pv": 90 }, - { "name": '14', "uv": -71, "pv": 130 }, - { "name": '15', "uv": -117, "pv": 11 }, - { "name": '16', "uv": -186, "pv": 107 }, - { "name": '17', "uv": -16, "pv": 926 }, - { "name": '18', "uv": -125, "pv": 653 }, - { "name": '19', "uv": 222, "pv": 366 }, - { "name": '20', "uv": 372, "pv": 486 }, - { "name": '21', "uv": 182, "pv": 512 }, - { "name": '22', "uv": 164, "pv": 302 }, - { "name": '23', "uv": 316, "pv": 425 }, - { "name": '24', "uv": 131, "pv": 467 }, - { "name": '25', "uv": 291, "pv": -190 }, - { "name": '26', "uv": -47, "pv": 194 }, - { "name": '27', "uv": -415, "pv": 371 }, - { "name": '28', "uv": -182, "pv": 376 }, - { "name": '29', "uv": -93, "pv": 295 }, - { "name": '30', "uv": -99, "pv": 322 }, - { "name": '31', "uv": -52, "pv": 246 }, - { "name": '32', "uv": 154, "pv": 33 }, - { "name": '33', "uv": 205, "pv": 354 }, - { "name": '34', "uv": 70, "pv": 258 }, - { "name": '35', "uv": -25, "pv": 359 }, - { "name": '36', "uv": -59, "pv": 192 }, - { "name": '37', "uv": -63, "pv": 464 }, - { "name": '38', "uv": -91, "pv": -2 }, - { "name": '39', "uv": -66, "pv": 154 }, - { "name": '40', "uv": -50, "pv": 186 }, -] - -def brush_simple(): - return rx.recharts.bar_chart( - rx.recharts.bar( - data_key="uv", - stroke="#8884d8", - fill="#8884d8" - ), - rx.recharts.bar( - data_key="pv", - stroke="#82ca9d", - fill="#82ca9d" - ), - rx.recharts.brush(data_key="name", height=30, stroke="#8884d8"), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - data=data, - width="100%", - height=300, - ) -``` - -## Position, Size, and Range - -This example showcases ways to set the Position, Size, and Range. The `gap` prop provides the spacing between stops on the brush when the graph will refresh. The `start_index` and `end_index` props defines the default range of the brush. `traveller_width` prop specifies the width of each handle ("traveller" in recharts lingo). - -```python demo graphing -data = [ - { "name": '1', "uv": 300, "pv": 456 }, - { "name": '2', "uv": -145, "pv": 230 }, - { "name": '3', "uv": -100, "pv": 345 }, - { "name": '4', "uv": -8, "pv": 450 }, - { "name": '5', "uv": 100, "pv": 321 }, - { "name": '6', "uv": 9, "pv": 235 }, - { "name": '7', "uv": 53, "pv": 267 }, - { "name": '8', "uv": 252, "pv": -378 }, - { "name": '9', "uv": 79, "pv": -210 }, - { "name": '10', "uv": 294, "pv": -23 }, - { "name": '12', "uv": 43, "pv": 45 }, - { "name": '13', "uv": -74, "pv": 90 }, - { "name": '14', "uv": -71, "pv": 130 }, - { "name": '15', "uv": -117, "pv": 11 }, - { "name": '16', "uv": -186, "pv": 107 }, - { "name": '17', "uv": -16, "pv": 926 }, - { "name": '18', "uv": -125, "pv": 653 }, - { "name": '19', "uv": 222, "pv": 366 }, - { "name": '20', "uv": 372, "pv": 486 }, - { "name": '21', "uv": 182, "pv": 512 }, - { "name": '22', "uv": 164, "pv": 302 }, - { "name": '23', "uv": 316, "pv": 425 }, - { "name": '24', "uv": 131, "pv": 467 }, - { "name": '25', "uv": 291, "pv": -190 }, - { "name": '26', "uv": -47, "pv": 194 }, - { "name": '27', "uv": -415, "pv": 371 }, - { "name": '28', "uv": -182, "pv": 376 }, - { "name": '29', "uv": -93, "pv": 295 }, - { "name": '30', "uv": -99, "pv": 322 }, - { "name": '31', "uv": -52, "pv": 246 }, - { "name": '32', "uv": 154, "pv": 33 }, - { "name": '33', "uv": 205, "pv": 354 }, - { "name": '34', "uv": 70, "pv": 258 }, - { "name": '35', "uv": -25, "pv": 359 }, - { "name": '36', "uv": -59, "pv": 192 }, - { "name": '37', "uv": -63, "pv": 464 }, - { "name": '38', "uv": -91, "pv": -2 }, - { "name": '39', "uv": -66, "pv": 154 }, - { "name": '40', "uv": -50, "pv": 186 }, -] - -def brush_pos_size_range(): - return rx.recharts.area_chart( - rx.recharts.area( - data_key="uv", - stroke="#8884d8", - fill="#8884d8", - ), - rx.recharts.area( - data_key="pv", - stroke="#82ca9d", - fill="#82ca9d", - ), - rx.recharts.brush( - data_key="name", - traveller_width=15, - start_index=3, - end_index=10, - stroke=rx.color("mauve", 10), - fill=rx.color("mauve", 3), - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - data=data, - width="100%", - height=200, - ) - -``` diff --git a/docs/library/graphing/general/cartesiangrid.md b/docs/library/graphing/general/cartesiangrid.md deleted file mode 100644 index 75e474a38b..0000000000 --- a/docs/library/graphing/general/cartesiangrid.md +++ /dev/null @@ -1,203 +0,0 @@ ---- -components: - - rx.recharts.CartesianGrid - # - rx.recharts.CartesianAxis ---- - -```python exec -import reflex as rx -``` - -# Cartesian Grid - -The Cartesian Grid is a component in Recharts that provides a visual reference for data points in charts. It helps users to better interpret the data by adding horizontal and vertical lines across the chart area. - -## Simple Example - -The `stroke_dasharray` prop in Recharts is used to create dashed or dotted lines for various chart elements like lines, axes, or grids. It's based on the SVG stroke-dasharray attribute. The `stroke_dasharray` prop accepts a comma-separated string of numbers that define a repeating pattern of dashes and gaps along the length of the stroke. - -- `stroke_dasharray="5,5"`: creates a line with 5-pixel dashes and 5-pixel gaps -- `stroke_dasharray="10,5,5,5"`: creates a more complex pattern with 10-pixel dashes, 5-pixel gaps, 5-pixel dashes, and 5-pixel gaps - -Here's a simple example using it on a Line component: - -```python demo graphing -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def cgrid_simple(): - return rx.recharts.line_chart( - rx.recharts.line( - data_key="pv", - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - rx.recharts.cartesian_grid(stroke_dasharray="4 4"), - data=data, - width = "100%", - height = 300, - ) -``` - -## Hidden Axes - -A `cartesian_grid` component can be used to hide the horizontal and vertical grid lines in a chart by setting the `horizontal` and `vertical` props to `False`. This can be useful when you want to show the grid lines only on one axis or when you want to create a cleaner look for the chart. - -```python demo graphing -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def cgrid_hidden(): - return rx.recharts.area_chart( - rx.recharts.area( - data_key="uv", - stroke="#8884d8", - fill="#8884d8" - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - rx.recharts.cartesian_grid( - stroke_dasharray="2 4", - vertical=False, - horizontal=True, - ), - data=data, - width = "100%", - height = 300, - ) -``` - -## Custom Grid Lines - -The `horizontal_points` and `vertical_points` props allow you to specify custom grid lines on the chart, offering fine-grained control over the grid's appearance. - -These props accept arrays of numbers, where each number represents a pixel offset: -- For `horizontal_points`, the offset is measured from the top edge of the chart -- For `vertical_points`, the offset is measured from the left edge of the chart - -```md alert info -# **Important**: The values provided to these props are not directly related to the axis values. They represent pixel offsets within the chart's rendering area. -``` - -Here's an example demonstrating custom grid lines in a scatter chart: - -```python demo graphing - -data2 = [ - {"x": 100, "y": 200, "z": 200}, - {"x": 120, "y": 100, "z": 260}, - {"x": 170, "y": 300, "z": 400}, - {"x": 170, "y": 250, "z": 280}, - {"x": 150, "y": 400, "z": 500}, - {"x": 110, "y": 280, "z": 200}, - {"x": 200, "y": 150, "z": 300}, - {"x": 130, "y": 350, "z": 450}, - {"x": 90, "y": 220, "z": 180}, - {"x": 180, "y": 320, "z": 350}, - {"x": 140, "y": 230, "z": 320}, - {"x": 160, "y": 180, "z": 240}, -] - -def cgrid_custom(): - return rx.recharts.scatter_chart( - rx.recharts.scatter( - data=data2, - fill="#8884d8", - ), - rx.recharts.x_axis(data_key="x", type_="number"), - rx.recharts.y_axis(data_key="y"), - rx.recharts.cartesian_grid( - stroke_dasharray="3 3", - horizontal_points=[0, 25, 50], - vertical_points=[65, 90, 115], - ), - width = "100%", - height = 200, - ) -``` - -Use these props judiciously to enhance data visualization without cluttering the chart. They're particularly useful for highlighting specific data ranges or creating visual reference points. diff --git a/docs/library/graphing/general/label.md b/docs/library/graphing/general/label.md deleted file mode 100644 index b6b7313298..0000000000 --- a/docs/library/graphing/general/label.md +++ /dev/null @@ -1,182 +0,0 @@ ---- -components: - - rx.recharts.Label - - rx.recharts.LabelList ---- - -# Label - -```python exec -import reflex as rx -``` - -Label is a component used to display a single label at a specific position within a chart or axis, while LabelList is a component that automatically renders a list of labels for each data point in a chart series, providing a convenient way to display multiple labels without manually positioning each one. - -## Simple Example - -Here's a simple example that demonstrates how you can customize the label of your axis using `rx.recharts.label`. The `value` prop represents the actual text of the label, the `position` prop specifies where the label is positioned within the axis component, and the `offset` prop is used to fine-tune the label's position. - -```python demo graphing -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 5800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def label_simple(): - return rx.recharts.bar_chart( - rx.recharts.cartesian_grid( - stroke_dasharray="3 3" - ), - rx.recharts.bar( - rx.recharts.label_list( - data_key="uv", position="top" - ), - data_key="uv", - fill=rx.color("accent", 8), - ), - rx.recharts.x_axis( - rx.recharts.label( - value="center", - position="center", - offset=30, - ), - rx.recharts.label( - value="inside left", - position="insideLeft", - offset=10, - ), - rx.recharts.label( - value="inside right", - position="insideRight", - offset=10, - ), - height=50, - ), - data=data, - margin={ - "left": 20, - "right": 20, - "top": 20, - "bottom": 20, - }, - width="100%", - height=250, - ) -``` - -## Label List Example - -`rx.recharts.label_list` takes in a `data_key` where we define the data column to plot. - -```python demo graphing -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 5800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def label_list(): - return rx.recharts.bar_chart( - rx.recharts.bar( - rx.recharts.label_list(data_key="uv", position="top"), - data_key="uv", - stroke="#8884d8", - fill="#8884d8" - ), - rx.recharts.bar( - rx.recharts.label_list(data_key="pv", position="top"), - data_key="pv", - stroke="#82ca9d", - fill="#82ca9d" - ), - rx.recharts.x_axis( - data_key="name" - ), - rx.recharts.y_axis(), - margin={"left": 10, "right": 0, "top": 20, "bottom": 10}, - data=data, - width="100%", - height = 300, - ) -``` - - diff --git a/docs/library/graphing/general/legend.md b/docs/library/graphing/general/legend.md deleted file mode 100644 index edf1db283a..0000000000 --- a/docs/library/graphing/general/legend.md +++ /dev/null @@ -1,171 +0,0 @@ ---- -components: - - rx.recharts.Legend ---- - -# Legend - -```python exec -import reflex as rx -``` - -A legend tells what each plot represents. Just like on a map, the legend helps the reader understand what they are looking at. For a line graph for example it tells us what each line represents. - -## Simple Example - -```python demo graphing -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def legend_simple(): - return rx.recharts.composed_chart( - rx.recharts.area( - data_key="uv", - stroke="#8884d8", - fill="#8884d8" - ), - rx.recharts.bar( - data_key="amt", - bar_size=20, - fill="#413ea0" - ), - rx.recharts.line( - data_key="pv", - type_="monotone", - stroke="#ff7300" - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - rx.recharts.legend(), - data=data, - width = "100%", - height = 300, - ) -``` - -## Example with Props - -The style and layout of the legend can be customized using a set of props. `width` and `height` set the dimensions of the container that wraps the legend, and `layout` can set the legend to display vertically or horizontally. `align` and `vertical_align` set the position relative to the chart container. The type and size of icons can be set using `icon_size` and `icon_type`. - -```python demo graphing -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def legend_props(): - return rx.recharts.composed_chart( - rx.recharts.line( - data_key="pv", - type_="monotone", - stroke=rx.color("accent", 7), - ), - rx.recharts.line( - data_key="amt", - type_="monotone", - stroke=rx.color("green", 7), - ), - rx.recharts.line( - data_key="uv", - type_="monotone", - stroke=rx.color("red", 7), - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - rx.recharts.legend( - width=60, - height=100, - layout="vertical", - align="right", - vertical_align="top", - icon_size=15, - icon_type="square", - ), - data=data, - width="100%", - height=300, - ) - -``` \ No newline at end of file diff --git a/docs/library/graphing/general/reference.md b/docs/library/graphing/general/reference.md deleted file mode 100644 index 3721b3b8c2..0000000000 --- a/docs/library/graphing/general/reference.md +++ /dev/null @@ -1,258 +0,0 @@ ---- -components: - - rx.recharts.ReferenceLine - - rx.recharts.ReferenceDot - - rx.recharts.ReferenceArea ---- - -# Reference - -```python exec -import reflex as rx -``` - -The Reference components in Recharts, including ReferenceLine, ReferenceArea, and ReferenceDot, are used to add visual aids and annotations to the chart, helping to highlight specific data points, ranges, or thresholds for better data interpretation and analysis. - -## Reference Area - -The `rx.recharts.reference_area` component in Recharts is used to highlight a specific area or range on the chart by drawing a rectangular region. It is defined by specifying the coordinates (x1, x2, y1, y2) and can be used to emphasize important data ranges or intervals on the chart. - -```python demo graphing -data = [ - { - "x": 45, - "y": 100, - "z": 150, - "errorY": [ - 30, - 20 - ], - "errorX": 5 - }, - { - "x": 100, - "y": 200, - "z": 200, - "errorY": [ - 20, - 30 - ], - "errorX": 3 - }, - { - "x": 120, - "y": 100, - "z": 260, - "errorY": 20, - "errorX": [ - 10, - 3 - ] - }, - { - "x": 170, - "y": 300, - "z": 400, - "errorY": [ - 15, - 18 - ], - "errorX": 4 - }, - { - "x": 140, - "y": 250, - "z": 280, - "errorY": 23, - "errorX": [ - 6, - 7 - ] - }, - { - "x": 150, - "y": 400, - "z": 500, - "errorY": [ - 21, - 10 - ], - "errorX": 4 - }, - { - "x": 110, - "y": 280, - "z": 200, - "errorY": 21, - "errorX": [ - 1, - 8 - ] - } -] - -def reference(): - return rx.recharts.scatter_chart( - rx.recharts.scatter( - data=data, - fill="#8884d8", - name="A"), - rx.recharts.reference_area(x1= 150, x2=180, y1=150, y2=300, fill="#8884d8", fill_opacity=0.3), - rx.recharts.x_axis(data_key="x", name="x", type_="number"), - rx.recharts.y_axis(data_key="y", name="y", type_="number"), - rx.recharts.graphing_tooltip(), - width = "100%", - height = 300, - ) -``` - -## Reference Line - -The `rx.recharts.reference_line` component in rx.recharts is used to draw a horizontal or vertical line on the chart at a specified position. It helps to highlight important values, thresholds, or ranges on the axis, providing visual reference points for better data interpretation. - -```python demo graphing -data_2 = [ - {"name": "Page A", "uv": 4000, "pv": 2400, "amt": 2400}, - {"name": "Page B", "uv": 3000, "pv": 1398, "amt": 2210}, - {"name": "Page C", "uv": 2000, "pv": 9800, "amt": 2290}, - {"name": "Page D", "uv": 2780, "pv": 3908, "amt": 2000}, - {"name": "Page E", "uv": 1890, "pv": 4800, "amt": 2181}, - {"name": "Page F", "uv": 2390, "pv": 3800, "amt": 2500}, - {"name": "Page G", "uv": 3490, "pv": 4300, "amt": 2100}, -] - -def reference_line(): - return rx.recharts.area_chart( - rx.recharts.area( - data_key="pv", stroke=rx.color("accent", 8), fill=rx.color("accent", 7), - ), - rx.recharts.reference_line( - x = "Page C", - stroke = rx.color("accent", 10), - label="Max PV PAGE", - ), - rx.recharts.reference_line( - y = 9800, - stroke = rx.color("green", 10), - label="Max" - ), - rx.recharts.reference_line( - y = 4343, - stroke = rx.color("green", 10), - label="Average" - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - data=data_2, - height = 300, - width = "100%", - ) - -``` - -## Reference Dot - -The `rx.recharts.reference_dot` component in Recharts is used to mark a specific data point on the chart with a customizable dot. It allows you to highlight important values, outliers, or thresholds by providing a visual reference marker at the specified coordinates (x, y) on the chart. - -```python demo graphing - -data_3 = [ - { - "x": 45, - "y": 100, - "z": 150, - }, - { - "x": 100, - "y": 200, - "z": 200, - }, - { - "x": 120, - "y": 100, - "z": 260, - }, - { - "x": 170, - "y": 300, - "z": 400, - }, - { - "x": 140, - "y": 250, - "z": 280, - }, - { - "x": 150, - "y": 400, - "z": 500, - }, - { - "x": 110, - "y": 280, - "z": 200, - }, - { - "x": 80, - "y": 150, - "z": 180, - }, - { - "x": 200, - "y": 350, - "z": 450, - }, - { - "x": 90, - "y": 220, - "z": 240, - }, - { - "x": 130, - "y": 320, - "z": 380, - }, - { - "x": 180, - "y": 120, - "z": 300, - }, -] - -def reference_dot(): - return rx.recharts.scatter_chart( - rx.recharts.scatter( - data=data_3, - fill=rx.color("accent", 9), - name="A", - ), - rx.recharts.x_axis( - data_key="x", name="x", type_="number" - ), - rx.recharts.y_axis( - data_key="y", name="y", type_="number" - ), - rx.recharts.reference_dot( - x = 160, - y = 350, - r = 15, - fill = rx.color("accent", 5), - stroke = rx.color("accent", 10), - ), - rx.recharts.reference_dot( - x = 170, - y = 300, - r = 20, - fill = rx.color("accent", 7), - ), - rx.recharts.reference_dot( - x = 90, - y = 220, - r = 18, - fill = rx.color("green", 7), - ), - height = 200, - width = "100%", - ) - -``` \ No newline at end of file diff --git a/docs/library/graphing/general/tooltip.md b/docs/library/graphing/general/tooltip.md deleted file mode 100644 index 33025831c9..0000000000 --- a/docs/library/graphing/general/tooltip.md +++ /dev/null @@ -1,172 +0,0 @@ ---- -components: - - rx.recharts.GraphingTooltip ---- - -# Tooltip - -```python exec -import reflex as rx -``` - -Tooltips are the little boxes that pop up when you hover over something. Tooltips are always attached to something, like a dot on a scatter chart, or a bar on a bar chart. - -```python demo graphing -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def tooltip_simple(): - return rx.recharts.composed_chart( - rx.recharts.area( - data_key="uv", - stroke="#8884d8", - fill="#8884d8" - ), - rx.recharts.bar( - data_key="amt", - bar_size=20, - fill="#413ea0" - ), - rx.recharts.line( - data_key="pv", - type_="monotone", - stroke="#ff7300" - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - rx.recharts.cartesian_grid(stroke_dasharray="3 3"), - rx.recharts.graphing_tooltip(), - data=data, - width = "100%", - height = 300, - ) -``` - -## Custom Styling - -The `rx.recharts.graphing_tooltip` component allows for customization of the tooltip's style, position, and layout. `separator` sets the separator between the data key and value. `view_box` prop defines the dimensions of the chart's viewbox while `allow_escape_view_box` determines whether the tooltip can extend beyond the viewBox horizontally (x) or vertically (y). `wrapper_style` prop allows you to style the outer container or wrapper of the tooltip. `content_style` prop allows you to style the inner content area of the tooltip. `is_animation_active` prop determines if the tooltip animation is active or not. - -```python demo graphing - -data = [ - { - "name": "Page A", - "uv": 4000, - "pv": 2400, - "amt": 2400 - }, - { - "name": "Page B", - "uv": 3000, - "pv": 1398, - "amt": 2210 - }, - { - "name": "Page C", - "uv": 2000, - "pv": 9800, - "amt": 2290 - }, - { - "name": "Page D", - "uv": 2780, - "pv": 3908, - "amt": 2000 - }, - { - "name": "Page E", - "uv": 1890, - "pv": 4800, - "amt": 2181 - }, - { - "name": "Page F", - "uv": 2390, - "pv": 3800, - "amt": 2500 - }, - { - "name": "Page G", - "uv": 3490, - "pv": 4300, - "amt": 2100 - } -] - -def tooltip_custom_styling(): - return rx.recharts.composed_chart( - rx.recharts.area( - data_key="uv", stroke="#8884d8", fill="#8884d8" - ), - rx.recharts.bar( - data_key="amt", bar_size=20, fill="#413ea0" - ), - rx.recharts.line( - data_key="pv", type_="monotone", stroke="#ff7300" - ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - rx.recharts.graphing_tooltip( - separator = " - ", - view_box = {"width" : 675, " height" : 300 }, - allow_escape_view_box={"x": True, "y": False}, - wrapper_style={ - "backgroundColor": rx.color("accent", 3), - "borderRadius": "8px", - "padding": "10px", - }, - content_style={ - "backgroundColor": rx.color("accent", 4), - "borderRadius": "4px", - "padding": "8px", - }, - position = {"x" : 600, "y" : 0}, - is_animation_active = False, - ), - data=data, - height = 300, - width = "100%", - ) -``` diff --git a/docs/library/graphing/other-charts/plotly.md b/docs/library/graphing/other-charts/plotly.md deleted file mode 100644 index 3d036fac70..0000000000 --- a/docs/library/graphing/other-charts/plotly.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -components: - - rx.plotly ---- - -# Plotly - -```python exec -import reflex as rx -import pandas as pd -import plotly.express as px -import plotly.graph_objects as go -``` - -Plotly is a graphing library that can be used to create interactive graphs. Use the rx.plotly component to wrap Plotly as a component for use in your web page. Checkout [Plotly](https://plotly.com/graphing-libraries/) for more information. - -```md alert info -# When integrating Plotly graphs into your UI code, note that the method for displaying the graph differs from a regular Python script. Instead of using `fig.show()`, use `rx.plotly(data=fig)` within your UI code to ensure the graph is properly rendered and displayed within the user interface -``` - -## Basic Example - -Let's create a line graph of life expectancy in Canada. - -```python demo exec -import plotly.express as px - -df = px.data.gapminder().query("country=='Canada'") -fig = px.line(df, x="year", y="lifeExp", title='Life expectancy in Canada') - -def line_chart(): - return rx.center( - rx.plotly(data=fig), - ) -``` - -## 3D graphing example - -Let's create a 3D surface plot of Mount Bruno. This is a slightly more complicated example, but it wraps in Reflex using the same method. In fact, you can wrap any figure using the same approach. - -```python demo exec -import plotly.graph_objects as go -import pandas as pd - -# Read data from a csv -z_data = pd.read_csv('data/mt_bruno_elevation.csv') - -fig = go.Figure(data=[go.Surface(z=z_data.values)]) -fig.update_traces(contours_z=dict(show=True, usecolormap=True, - highlightcolor="limegreen", project_z=True)) -fig.update_layout( - scene_camera_eye=dict(x=1.87, y=0.88, z=-0.64), - margin=dict(l=65, r=50, b=65, t=90) -) - -def mountain_surface(): - return rx.center( - rx.plotly(data=fig), - ) -``` - -📊 **Dataset source:** [mt_bruno_elevation.csv](https://raw.githubusercontent.com/plotly/datasets/master/api_docs/mt_bruno_elevation.csv) - -## Plot as State Var - -If the figure is set as a state var, it can be updated during run time. - -```python demo exec -import plotly.express as px -import plotly.graph_objects as go -import pandas as pd - -class PlotlyState(rx.State): - df: pd.DataFrame - figure: go.Figure = px.line() - - @rx.event - def create_figure(self): - self.df = px.data.gapminder().query("country=='Canada'") - self.figure = px.line( - self.df, - x="year", - y="lifeExp", - title="Life expectancy in Canada", - ) - - @rx.event - def set_selected_country(self, country): - self.df = px.data.gapminder().query(f"country=='{country}'") - self.figure = px.line( - self.df, - x="year", - y="lifeExp", - title=f"Life expectancy in {country}", - ) - - - -def line_chart_with_state(): - return rx.vstack( - rx.select( - ['China', 'France', 'United Kingdom', 'United States', 'Canada'], - default_value="Canada", - on_change=PlotlyState.set_selected_country, - ), - rx.plotly( - data=PlotlyState.figure, - on_mount=PlotlyState.create_figure, - ), - ) -``` - -## Adding Styles and Layouts - -Use `update_layout()` method to update the layout of your chart. Checkout [Plotly Layouts](https://plotly.com/python/reference/layout/) for all layouts props. - -```md alert info -Note that the width and height props are not recommended to ensure the plot remains size responsive to its container. The size of plot will be determined by it's outer container. -``` - -```python demo exec -df = px.data.gapminder().query("country=='Canada'") -fig_1 = px.line( - df, - x="year", - y="lifeExp", - title="Life expectancy in Canada", -) -fig_1.update_layout( - title_x=0.5, - plot_bgcolor="#c3d7f7", - paper_bgcolor="rgba(128, 128, 128, 0.1)", - showlegend=True, - title_font_family="Open Sans", - title_font_size=25, -) - -def add_styles(): - return rx.center( - rx.plotly(data=fig_1), - width="100%", - height="100%", - ) -``` diff --git a/docs/library/graphing/other-charts/pyplot.md b/docs/library/graphing/other-charts/pyplot.md deleted file mode 100644 index 701366d259..0000000000 --- a/docs/library/graphing/other-charts/pyplot.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -components: - - pyplot ---- - -```python exec -import reflex as rx -from reflex_pyplot import pyplot -import numpy as np -import random -import matplotlib.pyplot as plt -from matplotlib.figure import Figure -from reflex.style import toggle_color_mode -``` - -# Pyplot - -Pyplot (`reflex-pyplot`) is a graphing library that wraps Matplotlib. Use the `pyplot` component to display any Matplotlib plot in your app. Check out [Matplotlib](https://matplotlib.org/) for more information. - -## Installation - -Install the `reflex-pyplot` package using pip. - -```bash -pip install reflex-pyplot -``` - -## Basic Example - -To display a Matplotlib plot in your app, you can use the `pyplot` component. Pass in the figure you created with Matplotlib to the `pyplot` component as a child. - -```python demo exec -import matplotlib.pyplot as plt -import reflex as rx -from reflex_pyplot import pyplot -import numpy as np - -def create_contour_plot(): - X, Y = np.meshgrid(np.linspace(-3, 3, 256), np.linspace(-3, 3, 256)) - Z = (1 - X/2 + X**5 + Y**3) * np.exp(-X**2 - Y**2) - levels = np.linspace(Z.min(), Z.max(), 7) - - fig, ax = plt.subplots() - ax.contourf(X, Y, Z, levels=levels) - plt.close(fig) - return fig - -def pyplot_simple_example(): - return rx.card( - pyplot(create_contour_plot(), width="100%", height="400px"), - bg_color='#ffffff', - width="100%", - ) -``` - -```md alert info -# You must close the figure after creating - -Not closing the figure could cause memory issues. -``` - -## Stateful Example - -Lets create a scatter plot of random data. We'll also allow the user to randomize the data and change the number of points. - -In this example, we'll use a `color_mode_cond` to display the plot in both light and dark mode. We need to do this manually here because the colors are determined by the matplotlib chart and not the theme. - -```python demo exec -import random -from typing import Literal -import matplotlib.pyplot as plt -import reflex as rx -from reflex_pyplot import pyplot -import numpy as np - - -def create_plot(theme: str, plot_data: tuple, scale: list): - bg_color, text_color = ('#1e1e1e', 'white') if theme == 'dark' else ('white', 'black') - grid_color = '#555555' if theme == 'dark' else '#cccccc' - - fig, ax = plt.subplots(facecolor=bg_color) - ax.set_facecolor(bg_color) - - for (x, y), color in zip(plot_data, ["#4e79a7", "#f28e2b"]): - ax.scatter(x, y, c=color, s=scale, label=color, alpha=0.6, edgecolors="none") - - ax.legend(loc="upper right", facecolor=bg_color, edgecolor='none', labelcolor=text_color) - ax.grid(True, color=grid_color) - ax.tick_params(colors=text_color) - for spine in ax.spines.values(): - spine.set_edgecolor(text_color) - - for item in [ax.xaxis.label, ax.yaxis.label, ax.title]: - item.set_color(text_color) - plt.close(fig) - - return fig - -class PyplotState(rx.State): - num_points: int = 25 - plot_data: tuple = tuple(np.random.rand(2, 25) for _ in range(2)) - scale: list = [random.uniform(0, 100) for _ in range(25)] - - @rx.event(temporal=True, throttle=500) - def randomize(self): - self.plot_data = tuple(np.random.rand(2, self.num_points) for _ in range(2)) - self.scale = [random.uniform(0, 100) for _ in range(self.num_points)] - - @rx.event(temporal=True, throttle=500) - def set_num_points(self, num_points: list[int | float]): - self.num_points = int(num_points[0]) - yield PyplotState.randomize() - - @rx.var - def fig_light(self) -> Figure: - fig = create_plot("light", self.plot_data, self.scale) - return fig - - @rx.var - def fig_dark(self) -> Figure: - fig = create_plot("dark", self.plot_data, self.scale) - return fig - -def pyplot_example(): - return rx.vstack( - rx.card( - rx.color_mode_cond( - pyplot(PyplotState.fig_light, width="100%", height="100%"), - pyplot(PyplotState.fig_dark, width="100%", height="100%"), - ), - rx.vstack( - rx.hstack( - rx.button( - "Randomize", - on_click=PyplotState.randomize, - ), - rx.text("Number of Points:"), - rx.slider( - default_value=25, - min_=10, - max=100, - on_value_commit=PyplotState.set_num_points, - ), - width="100%", - ), - width="100%", - ), - width="100%", - ), - justify_content="center", - align_items="center", - height="100%", - width="100%", - ) -``` diff --git a/docs/library/layout/aspect_ratio.md b/docs/library/layout/aspect_ratio.md deleted file mode 100644 index cc5713af5a..0000000000 --- a/docs/library/layout/aspect_ratio.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -components: - - rx.aspect_ratio ---- - -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -``` - -# Aspect Ratio - -Displays content with a desired ratio. - -## Basic Example - -Setting the `ratio` prop will adjust the width or height -of the content such that the `width` divided by the `height` equals the `ratio`. -For responsive scaling, set the `width` or `height` of the content to `"100%"`. - -```python demo -rx.grid( - rx.aspect_ratio( - rx.box( - "Widescreen 16:9", - background_color="papayawhip", - width="100%", - height="100%", - ), - ratio=16 / 9, - ), - rx.aspect_ratio( - rx.box( - "Letterbox 4:3", - background_color="orange", - width="100%", - height="100%", - ), - ratio=4 / 3, - ), - rx.aspect_ratio( - rx.box( - "Square 1:1", - background_color="green", - width="100%", - height="100%", - ), - ratio=1, - ), - rx.aspect_ratio( - rx.box( - "Portrait 5:7", - background_color="lime", - width="100%", - height="100%", - ), - ratio=5 / 7, - ), - spacing="2", - width="25%", -) -``` - -```md alert warning -# Never set `height` or `width` directly on an `aspect_ratio` component or its contents. - -Instead, wrap the `aspect_ratio` in a `box` that constrains either the width or the height, then set the content width and height to `"100%"`. -``` - -```python demo -rx.flex( - *[ - rx.box( - rx.aspect_ratio( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="100%", height="100%"), - ratio=ratio, - ), - width="20%", - ) - for ratio in [16 / 9, 3 / 2, 2 / 3, 1] - ], - justify="between", - width="100%", -) -``` diff --git a/docs/library/layout/box.md b/docs/library/layout/box.md deleted file mode 100644 index 188e791208..0000000000 --- a/docs/library/layout/box.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -components: - - rx.box ---- - -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -``` - -# Box - -Box is a generic container component that can be used to group other components. - -By default, the Box component is based on the `div` and rendered as a block element. It's primary use is for applying styles. - -## Basic Example - -```python demo -rx.box( - rx.box("CSS color", background_color="yellow", border_radius="2px", width="20%", margin="4px", padding="4px"), - rx.box("CSS color", background_color="orange", border_radius="5px", width="40%", margin="8px", padding="8px"), - rx.box("Radix Color", background_color="var(--tomato-3)", border_radius="5px", width="60%", margin="12px", padding="12px"), - rx.box("Radix Color", background_color="var(--plum-3)", border_radius="10px", width="80%", margin="16px", padding="16px"), - rx.box("Radix Theme Color", background_color="var(--accent-2)", radius="full", width="100%", margin="24px", padding="25px"), - flex_grow="1", - text_align="center", -) -``` - -## Background - -To set a background [image](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_images) or -[gradient](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_images/Using_CSS_gradients), -use the [`background` CSS prop](https://developer.mozilla.org/en-US/docs/Web/CSS/background). - -```python demo -rx.flex( - rx.box(background="linear-gradient(45deg, var(--tomato-9), var(--plum-9))", width="20%", height="100%"), - rx.box(background="linear-gradient(red, yellow, blue, orange)", width="20%", height="100%"), - rx.box(background="radial-gradient(at 0% 30%, red 10px, yellow 30%, #1e90ff 50%)", width="20%", height="100%"), - rx.box(background=f"center/cover url('{REFLEX_ASSETS_CDN}other/reflex_banner.png')", width="20%", height="100%"), - spacing="2", - width="100%", - height="10vh", -) -``` diff --git a/docs/library/layout/card.md b/docs/library/layout/card.md deleted file mode 100644 index 7f82ade7da..0000000000 --- a/docs/library/layout/card.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -components: - - rx.card - -Card: | - lambda **props: rx.card("Basic Card ", **props) ---- - -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -``` - -# Card - -A Card component is used for grouping related components. It is similar to the Box, except it has a -border, uses the theme colors and border radius, and provides a `size` prop to control spacing -and margin according to the Radix `"1"` - `"5"` scale. - -The Card requires less styling than a Box to achieve consistent visual results when used with -themes. - -## Basic Example - -```python demo -rx.flex( - rx.card("Card 1", size="1"), - rx.card("Card 2", size="2"), - rx.card("Card 3", size="3"), - rx.card("Card 4", size="4"), - rx.card("Card 5", size="5"), - spacing="2", - align_items="flex-start", - flex_wrap="wrap", -) -``` - -## Rendering as a Different Element - -The `as_child` prop may be used to render the Card as a different element. Link and Button are -commonly used to make a Card clickable. - -```python demo -rx.card( - rx.link( - rx.flex( - rx.avatar(src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png"), - rx.box( - rx.heading("Quick Start"), - rx.text("Get started with Reflex in 5 minutes."), - ), - spacing="2", - ), - ), - as_child=True, -) -``` - -## Using Inset Content diff --git a/docs/library/layout/center.md b/docs/library/layout/center.md deleted file mode 100644 index b52702f20c..0000000000 --- a/docs/library/layout/center.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -components: - - rx.center ---- - -```python exec -import reflex as rx -``` - -# Center - -`Center` is a component that centers its children within itself. It is based on the `flex` component and therefore inherits all of its props. - -```python demo -rx.center( - rx.text("Hello World!"), - border_radius="15px", - border_width="thick", - width="50%", -) -``` diff --git a/docs/library/layout/container.md b/docs/library/layout/container.md deleted file mode 100644 index caeab8c84a..0000000000 --- a/docs/library/layout/container.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -components: - - rx.container ---- - -```python exec -import reflex as rx -``` - -# Container - -Constrains the maximum width of page content, while keeping flexible margins -for responsive layouts. - -A Container is generally used to wrap the main content for a page. - -## Basic Example - -```python demo -rx.box( - rx.container( - rx.card("This content is constrained to a max width of 448px.", width="100%"), - size="1", - ), - rx.container( - rx.card("This content is constrained to a max width of 688px.", width="100%"), - size="2", - ), - rx.container( - rx.card("This content is constrained to a max width of 880px.", width="100%"), - size="3", - ), - rx.container( - rx.card("This content is constrained to a max width of 1136px.", width="100%"), - size="4", - ), - background_color="var(--gray-3)", - width="100%", -) -``` diff --git a/docs/library/layout/flex.md b/docs/library/layout/flex.md deleted file mode 100644 index 7abb054619..0000000000 --- a/docs/library/layout/flex.md +++ /dev/null @@ -1,208 +0,0 @@ ---- -components: - - rx.flex ---- - -```python exec -import reflex as rx -``` - -# Flex - -The Flex component is used to make [flexbox layouts](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox). -It makes it simple to arrange child components in horizontal or vertical directions, apply wrapping, -justify and align content, and automatically size components based on available space, making it -ideal for building responsive layouts. - -By default, children are arranged horizontally (`direction="row"`) without wrapping. - -## Basic Example - -```python demo -rx.flex( - rx.card("Card 1"), - rx.card("Card 2"), - rx.card("Card 3"), - rx.card("Card 4"), - rx.card("Card 5"), - spacing="2", - width="100%", -) -``` - -## Wrapping - -With `flex_wrap="wrap"`, the children will wrap to the next line instead of being resized. - -```python demo -rx.flex( - rx.foreach( - rx.Var.range(10), - lambda i: rx.card(f"Card {i + 1}", width="16%"), - ), - spacing="2", - flex_wrap="wrap", - width="100%", -) -``` - -## Direction - -With `direction="column"`, the children will be arranged vertically. - -```python demo -rx.flex( - rx.card("Card 1"), - rx.card("Card 2"), - rx.card("Card 3"), - rx.card("Card 4"), - spacing="2", - direction="column", -) -``` - -## Alignment - -Two props control how children are aligned within the Flex component: - -* `align` controls how children are aligned along the cross axis (vertical for `row` and horizontal for `column`). -* `justify` controls how children are aligned along the main axis (horizontal for `row` and vertical for `column`). - -The following example visually demonstrates the effect of these props with different `wrap` and `direction` values. - -```python demo exec -class FlexPlaygroundState(rx.State): - align: str = "stretch" - justify: str = "start" - direction: str = "row" - wrap: str = "nowrap" - - @rx.event - def set_align(self, value: str): - self.align = value - - @rx.event - def set_justify(self, value: str): - self.justify = value - - @rx.event - def set_direction(self, value: str): - self.direction = value - - @rx.event - def set_wrap(self, value: str): - self.wrap = value - - -def select(label, items, value, on_change): - return rx.flex( - rx.text(label), - rx.select.root( - rx.select.trigger(), - rx.select.content( - *[ - rx.select.item(item, value=item) - for item in items - ] - ), - value=value, - on_change=on_change, - ), - align="center", - justify="center", - direction="column", - ) - - -def selectors(): - return rx.flex( - select("Wrap", ["nowrap", "wrap", "wrap-reverse"], FlexPlaygroundState.wrap, FlexPlaygroundState.set_wrap), - select("Direction", ["row", "column", "row-reverse", "column-reverse"], FlexPlaygroundState.direction, FlexPlaygroundState.set_direction), - select("Align", ["start", "center", "end", "baseline", "stretch"], FlexPlaygroundState.align, FlexPlaygroundState.set_align), - select("Justify", ["start", "center", "end", "between"], FlexPlaygroundState.justify, FlexPlaygroundState.set_justify), - width="100%", - spacing="2", - justify="between", - ) - - -def example1(): - return rx.box( - selectors(), - rx.flex( - rx.foreach( - rx.Var.range(10), - lambda i: rx.card(f"Card {i + 1}", width="16%"), - ), - spacing="2", - direction=FlexPlaygroundState.direction, - align=FlexPlaygroundState.align, - justify=FlexPlaygroundState.justify, - wrap=FlexPlaygroundState.wrap, - width="100%", - height="20vh", - margin_top="16px", - ), - width="100%", - ) -``` - -## Size Hinting - -When a child component is included in a flex container, -the `flex_grow` (default `"0"`) and `flex_shrink` (default `"1"`) props control -how the box is sized relative to other components in the same container. - -The resizing always applies to the main axis of the flex container. If the direction is -`row`, then the sizing applies to the `width`. If the direction is `column`, then the sizing -applies to the `height`. To set the optimal size along the main axis, the `flex_basis` prop -is used and may be either a percentage or CSS size units. When unspecified, the -corresponding `width` or `height` value is used if set, otherwise the content size is used. - -When `flex_grow="0"`, the box will not grow beyond the `flex_basis`. - -When `flex_shrink="0"`, the box will not shrink to less than the `flex_basis`. - -These props are used when creating flexible responsive layouts. - -Move the slider below and see how adjusting the width of the flex container -affects the computed sizes of the flex items based on the props that are set. - -```python demo exec -class FlexGrowShrinkState(rx.State): - width_pct: list[int] = [100] - - @rx.event - def set_width_pct(self, value: list[int | float]): - self.width_pct = [int(value[0])] - - -def border_box(*children, **props): - return rx.box( - *children, - border="1px solid var(--gray-10)", - border_radius="2px", - **props, - ) - - -def example2(): - return rx.box( - rx.flex( - border_box("flex_shrink=0", flex_shrink="0", width="100px"), - border_box("flex_shrink=1", flex_shrink="1", width="200px"), - border_box("flex_grow=0", flex_grow="0"), - border_box("flex_grow=1", flex_grow="1"), - width=f"{FlexGrowShrinkState.width_pct}%", - margin_bottom="16px", - spacing="2", - ), - rx.slider( - min=0, - max=100, - value=FlexGrowShrinkState.width_pct, - on_change=FlexGrowShrinkState.set_width_pct, - ), - width="100%", - ) -``` diff --git a/docs/library/layout/fragment.md b/docs/library/layout/fragment.md deleted file mode 100644 index 66b3e301fc..0000000000 --- a/docs/library/layout/fragment.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -components: - - rx.fragment ---- - -# Fragment - -```python exec -import reflex as rx -from pcweb import constants -``` - -A Fragment is a Component that allow you to group multiple Components without a wrapper node. - -Refer to the React docs at [React/Fragment]({constants.FRAGMENT_COMPONENT_INFO_URL}) for more information on its use-case. - -```python demo -rx.fragment( - rx.text("Component1"), - rx.text("Component2") -) -``` - - -```md video https://youtube.com/embed/ITOZkzjtjUA?start=3196&end=3340 -# Video: Fragment -``` \ No newline at end of file diff --git a/docs/library/layout/grid.md b/docs/library/layout/grid.md deleted file mode 100644 index f09071a2d7..0000000000 --- a/docs/library/layout/grid.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -components: - - rx.grid ---- - -```python exec -import reflex as rx -``` - -# Grid - -Component for creating grid layouts. Either `rows` or `columns` may be specified. - -## Basic Example - -```python demo -rx.grid( - rx.foreach( - rx.Var.range(12), - lambda i: rx.card(f"Card {i + 1}", height="10vh"), - ), - columns="3", - spacing="4", - width="100%", -) -``` - -```python demo -rx.grid( - rx.foreach( - rx.Var.range(12), - lambda i: rx.card(f"Card {i + 1}", height="10vh"), - ), - rows="3", - flow="column", - justify="between", - spacing="4", - width="100%", -) -``` diff --git a/docs/library/layout/inset.md b/docs/library/layout/inset.md deleted file mode 100644 index 09df465f71..0000000000 --- a/docs/library/layout/inset.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -components: - - rx.inset - -Inset: | - lambda **props: rx.card( - rx.inset( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png", height="auto"), - **props, - ), - width="500px", - ) - ---- - -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -``` - -# Inset - -Applies a negative margin to allow content to bleed into the surrounding container. - -## Basic Example - -Nesting an Inset component inside a Card will render the content from edge to edge of the card. - -```python demo -rx.card( - rx.inset( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png", width="100%", height="auto"), - side="top", - pb="current", - ), - rx.text("Reflex is a web framework that allows developers to build their app in pure Python."), - width="25vw", -) -``` - -## Other Directions - -The `side` prop controls which side the negative margin is applied to. When using a specific side, -it is helpful to set the padding for the opposite side to `current` to retain the same padding the -content would have had if it went to the edge of the parent component. - -```python demo -rx.card( - rx.text("The inset below uses a bottom side."), - rx.inset( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png", width="100%", height="auto"), - side="bottom", - pt="current", - ), - width="25vw", -) -``` - -```python demo -rx.card( - rx.flex( - rx.text("This inset uses a right side, which requires a flex with direction row."), - rx.inset( - rx.box(background=f"center/cover url('{REFLEX_ASSETS_CDN}other/reflex_banner.png')", height="100%"), - width="100%", - side="right", - pl="current", - ), - direction="row", - width="100%", - ), - width="25vw", -) -``` - -```python demo -rx.card( - rx.flex( - rx.inset( - rx.box(background=f"center/cover url('{REFLEX_ASSETS_CDN}other/reflex_banner.png')", height="100%"), - width="100%", - side="left", - pr="current", - ), - rx.text("This inset uses a left side, which also requires a flex with direction row."), - direction="row", - width="100%", - ), - width="25vw", -) -``` diff --git a/docs/library/layout/section.md b/docs/library/layout/section.md deleted file mode 100644 index 05f818bd0e..0000000000 --- a/docs/library/layout/section.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -components: - - rx.section ---- - -```python exec -import reflex as rx -``` - -# Section - -Denotes a section of page content, providing vertical padding by default. - -Primarily this is a semantic component that is used to group related textual content. - -## Basic Example - -```python demo -rx.box( - rx.section( - rx.heading("First"), - rx.text("This is the first content section"), - padding_left="12px", - padding_right="12px", - background_color="var(--gray-2)", - ), - rx.section( - rx.heading("Second"), - rx.text("This is the second content section"), - padding_left="12px", - padding_right="12px", - background_color="var(--gray-2)", - ), - width="100%", -) -``` diff --git a/docs/library/layout/separator.md b/docs/library/layout/separator.md deleted file mode 100644 index 816382d233..0000000000 --- a/docs/library/layout/separator.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -components: - - rx.separator -Separator: | - lambda **props: rx.separator(**props) - ---- - -```python exec -import reflex as rx -``` - -# Separator - -Visually or semantically separates content. - -## Basic Example - -```python demo -rx.flex( - rx.card("Section 1"), - rx.divider(), - rx.card("Section 2"), - spacing="4", - direction="column", - align="center", -) -``` - -## Size - -The `size` prop controls how long the separator is. Using `size="4"` will make -the separator fill the parent container. Setting CSS `width` or `height` prop to `"100%"` -can also achieve this effect, but `size` works the same regardless of the orientation. - -```python demo -rx.flex( - rx.card("Section 1"), - rx.divider(size="4"), - rx.card("Section 2"), - spacing="4", - direction="column", -) -``` - -## Orientation - -Setting the orientation prop to `vertical` will make the separator appear vertically. - -```python demo -rx.flex( - rx.card("Section 1"), - rx.divider(orientation="vertical", size="4"), - rx.card("Section 2"), - spacing="4", - width="100%", - height="10vh", -) -``` diff --git a/docs/library/layout/spacer.md b/docs/library/layout/spacer.md deleted file mode 100644 index 730076159c..0000000000 --- a/docs/library/layout/spacer.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -components: - - rx.spacer ---- - -```python exec -import reflex as rx -``` - -# Spacer - -Creates an adjustable, empty space that can be used to tune the spacing between child elements within `flex`. - -```python demo -rx.flex( - rx.center(rx.text("Example"), bg="lightblue"), - rx.spacer(), - rx.center(rx.text("Example"), bg="lightgreen"), - rx.spacer(), - rx.center(rx.text("Example"), bg="salmon"), - width="100%", -) -``` - -As `stack`, `vstack` and `hstack` are all built from `flex`, it is possible to also use `spacer` inside of these components. diff --git a/docs/library/layout/stack.md b/docs/library/layout/stack.md deleted file mode 100644 index d00160bdf4..0000000000 --- a/docs/library/layout/stack.md +++ /dev/null @@ -1,172 +0,0 @@ ---- -components: - - rx.stack - - rx.hstack - - rx.vstack -Stack: | - lambda **props: rx.stack( - rx.card("Card 1", size="2"), rx.card("Card 2", size="2"), rx.card("Card 3", size="2"), - width="100%", - height="20vh", - **props, - ) ---- - -```python exec -import reflex as rx -``` - -# Stack - -`Stack` is a layout component used to group elements together and apply a space between them. - -`vstack` is used to stack elements in the vertical direction. - -`hstack` is used to stack elements in the horizontal direction. - -`stack` is used to stack elements in the vertical or horizontal direction. - -These components are based on the `flex` component and therefore inherit all of its props. - -The `stack` component can be used with the `flex_direction` prop to set to either `row` or `column` to set the direction. - -```python demo -rx.flex( - rx.stack( - rx.box( - "Example", - bg="orange", - border_radius="3px", - width="20%", - ), - rx.box( - "Example", - bg="lightblue", - border_radius="3px", - width="30%", - ), - rx.box( - "Example", - bg="lightgreen", - border_radius="3px", - width="50%", - ), - flex_direction="row", - width="100%", - ), - rx.stack( - rx.box( - "Example", - bg="orange", - border_radius="3px", - width="20%", - ), - rx.box( - "Example", - bg="lightblue", - border_radius="3px", - width="30%", - ), - rx.box( - "Example", - bg="lightgreen", - border_radius="3px", - width="50%", - ), - flex_direction="column", - width="100%", - ), - width="100%", -) -``` - -## Hstack - -```python demo -rx.hstack( - rx.box( - "Example", bg="red", border_radius="3px", width="10%" - ), - rx.box( - "Example", - bg="orange", - border_radius="3px", - width="10%", - ), - rx.box( - "Example", - bg="yellow", - border_radius="3px", - width="10%", - ), - rx.box( - "Example", - bg="lightblue", - border_radius="3px", - width="10%", - ), - rx.box( - "Example", - bg="lightgreen", - border_radius="3px", - width="60%", - ), - width="100%", -) -``` - -## Vstack - -```python demo -rx.vstack( - rx.box( - "Example", bg="red", border_radius="3px", width="20%" - ), - rx.box( - "Example", - bg="orange", - border_radius="3px", - width="40%", - ), - rx.box( - "Example", - bg="yellow", - border_radius="3px", - width="60%", - ), - rx.box( - "Example", - bg="lightblue", - border_radius="3px", - width="80%", - ), - rx.box( - "Example", - bg="lightgreen", - border_radius="3px", - width="100%", - ), - width="100%", -) -``` - -## Real World Example - -```python demo -rx.hstack( - rx.box( - rx.heading("Saving Money"), - rx.text("Saving money is an art that combines discipline, strategic planning, and the wisdom to foresee future needs and emergencies. It begins with the simple act of setting aside a portion of one's income, creating a buffer that can grow over time through interest or investments.", margin_top="0.5em"), - padding="1em", - border_width="1px", - ), - rx.box( - rx.heading("Spending Money"), - rx.text("Spending money is a balancing act between fulfilling immediate desires and maintaining long-term financial health. It's about making choices, sometimes indulging in the pleasures of the moment, and at other times, prioritizing essential expenses.", margin_top="0.5em"), - padding="1em", - border_width="1px", - ), - gap="2em", -) - -``` diff --git a/docs/library/media/audio.md b/docs/library/media/audio.md deleted file mode 100644 index e842fe162e..0000000000 --- a/docs/library/media/audio.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -components: - - rx.audio ---- - -# Audio - -```python exec -import reflex as rx -from pcweb.pages.docs import library -``` - -The audio component can display an audio given an src path as an argument. This could either be a local path from the assets folder or an external link. - -```python demo -rx.audio( - src="https://www.learningcontainer.com/wp-content/uploads/2020/02/Kalimba.mp3", - width="400px", - height="32px", -) -``` - -If we had a local file in the `assets` folder named `test.mp3` we could set `src="/test.mp3"` to view the audio file. - -```md alert info -# How to let your user upload an audio file -To let a user upload an audio file to your app check out the [upload docs]({library.forms.upload.path}). -``` diff --git a/docs/library/media/image.md b/docs/library/media/image.md deleted file mode 100644 index 55d99f703c..0000000000 --- a/docs/library/media/image.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -components: - - rx.image ---- - -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from pcweb.pages.docs import library -``` - -# Image - -The Image component can display an image given a `src` path as an argument. -This could either be a local path from the assets folder or an external link. - -```python demo -rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="100px", height="auto") -``` - -Image composes a box and can be styled similarly. - -```python demo -rx.image( - src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", - width="100px", - height="auto", - border_radius="15px 50px", - border="5px solid #555", -) -``` - -You can also pass a `PIL` image object as the `src`. - -```python demo box -rx.image(src="https://picsum.photos/id/1/200/300", alt="An Unsplash Image") -``` - -```python -from PIL import Image -import requests - - -class ImageState(rx.State): - url: str = f"https://picsum.photos/id/1/200/300" - image: Image.Image = Image.open(requests.get(url, stream=True).raw) - - -def image_pil_example(): - return rx.vstack( - rx.image(src=ImageState.image) - ) -``` - -```md alert info -# rx.image only accepts URLs and Pillow Images -A cv2 image must be converted to a PIL image to be passed directly to `rx.image` as a State variable, or saved to the `assets` folder and then passed to the `rx.image` component. -``` - -```md alert info -# How to let your user upload an image -To let a user upload an image to your app check out the [upload docs]({library.forms.upload.path}). -``` diff --git a/docs/library/media/video.md b/docs/library/media/video.md deleted file mode 100644 index 328e44f813..0000000000 --- a/docs/library/media/video.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -components: - - rx.video ---- - -# Video - -```python exec -import reflex as rx -from pcweb.pages.docs import library -``` - -The video component can display a video given an src path as an argument. This could either be a local path from the assets folder or an external link. - -```python demo -rx.video( - src="https://www.youtube.com/embed/9bZkp7q19f0", - width="400px", - height="auto" -) -``` - -If we had a local file in the `assets` folder named `test.mp4` we could set `url="/test.mp4"` to view the video. - - -```md alert info -# How to let your user upload a video -To let a user upload a video to your app check out the [upload docs]({library.forms.upload.path}). -``` diff --git a/docs/library/other/clipboard.md b/docs/library/other/clipboard.md deleted file mode 100644 index 061dc5d33a..0000000000 --- a/docs/library/other/clipboard.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -components: - - rx.clipboard ---- - -```python exec -import reflex as rx -from pcweb.pages.docs import styling -``` - -# Clipboard - -_New in 0.5.6_ - -The Clipboard component can be used to respond to paste events with complex data. - -If the Clipboard component is included in a page without children, -`rx.clipboard()`, then it will attach to the document's `paste` event handler -and will be triggered when data is pasted anywhere into the page. - -```python demo exec -class ClipboardPasteState(rx.State): - @rx.event - def on_paste(self, data: list[tuple[str, str]]): - for mime_type, item in data: - yield rx.toast(f"Pasted {mime_type} data: {item}") - - -def clipboard_example(): - return rx.fragment( - rx.clipboard(on_paste=ClipboardPasteState.on_paste), - "Paste Content Here", - ) -``` - -The `data` argument passed to the `on_paste` method is a list of tuples, where -each tuple contains the MIME type of the pasted data and the data itself. Binary -data will be base64 encoded as a data URI, and can be decoded using python's -`urlopen` or used directly as the `src` prop of an image. - -## Scoped Paste Events - -If you want to limit the scope of the paste event to a specific element, wrap -the `rx.clipboard` component around the elements that should trigger the paste -event. - -To avoid having outer paste handlers also trigger the event, you can use the -event action `.stop_propagation` to prevent the paste from bubbling up through -the DOM. - -If you need to also prevent the default action of pasting the data into a text -box, you can also attach the `.prevent_default` action. - -```python demo exec -class ClipboardPasteImageState(rx.State): - last_image_uri: str = "" - - def on_paste(self, data: list[tuple[str, str]]): - for mime_type, item in data: - if mime_type.startswith("image/"): - self.last_image_uri = item - break - else: - return rx.toast("Did not find an image in the pasted data") - - -def clipboard_image_example(): - return rx.vstack( - rx.clipboard( - rx.input(placeholder="Paste Image (stop propagation)"), - on_paste=ClipboardPasteImageState.on_paste.stop_propagation - ), - rx.clipboard( - rx.input(placeholder="Paste Image (prevent default)"), - on_paste=ClipboardPasteImageState.on_paste.prevent_default - ), - rx.image(src=ClipboardPasteImageState.last_image_uri), - ) -``` diff --git a/docs/library/other/html.md b/docs/library/other/html.md deleted file mode 100644 index 3fc169fb5c..0000000000 --- a/docs/library/other/html.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -components: - - rx.el.a - - rx.el.abbr - - rx.el.address - - rx.el.area - - rx.el.article - - rx.el.aside - - rx.el.audio - - rx.el.b - - rx.el.bdi - - rx.el.bdo - - rx.el.blockquote - - rx.el.body - - rx.el.br - - rx.el.button - - rx.el.canvas - - rx.el.caption - - rx.el.cite - - rx.el.code - - rx.el.col - - rx.el.colgroup - - rx.el.data - - rx.el.dd - - rx.el.Del - - rx.el.details - - rx.el.dfn - - rx.el.dialog - - rx.el.div - - rx.el.dl - - rx.el.dt - - rx.el.em - - rx.el.embed - - rx.el.fieldset - - rx.el.figcaption - - rx.el.footer - - rx.el.form - - rx.el.h1 - - rx.el.h2 - - rx.el.h3 - - rx.el.h4 - - rx.el.h5 - - rx.el.h6 - - rx.el.head - - rx.el.header - - rx.el.hr - - rx.el.html - - rx.el.i - - rx.el.iframe - - rx.el.img - - rx.el.input - - rx.el.ins - - rx.el.kbd - - rx.el.label - - rx.el.legend - - rx.el.li - - rx.el.link - - rx.el.main - - rx.el.mark - - rx.el.math - - rx.el.meta - - rx.el.meter - - rx.el.nav - - rx.el.noscript - - rx.el.object - - rx.el.ol - - rx.el.optgroup - - rx.el.option - - rx.el.output - - rx.el.p - - rx.el.picture - - rx.el.portal - - rx.el.pre - - rx.el.progress - - rx.el.q - - rx.el.rp - - rx.el.rt - - rx.el.ruby - - rx.el.s - - rx.el.samp - - rx.el.script - - rx.el.section - - rx.el.select - - rx.el.small - - rx.el.source - - rx.el.span - - rx.el.strong - - rx.el.sub - - rx.el.sup - - rx.el.svg.circle - - rx.el.svg.defs - - rx.el.svg.linear_gradient - - rx.el.svg.polygon - - rx.el.svg.path - - rx.el.svg.rect - - rx.el.svg.stop - - rx.el.table - - rx.el.tbody - - rx.el.td - - rx.el.template - - rx.el.textarea - - rx.el.tfoot - - rx.el.th - - rx.el.thead - - rx.el.time - - rx.el.title - - rx.el.tr - - rx.el.track - - rx.el.u - - rx.el.ul - - rx.el.video - - rx.el.wbr ---- - -# HTML - -Reflex also provides a set of HTML elements that can be used to create web pages. These elements are the same as the HTML elements that are used in web development. These elements come unstyled bhy default. You can style them using style props or tailwindcss classes. - -The following is a list of the HTML elements that are available in Reflex: \ No newline at end of file diff --git a/docs/library/other/html_embed.md b/docs/library/other/html_embed.md deleted file mode 100644 index dfa79f90fd..0000000000 --- a/docs/library/other/html_embed.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -components: - - rx.html ---- - -```python exec -import reflex as rx -from pcweb.pages.docs import styling -``` - -# HTML Embed - -The HTML component can be used to render raw HTML code. - -Before you reach for this component, consider using Reflex's raw HTML element support instead. - -```python demo -rx.vstack( - rx.html("

Hello World

"), - rx.html("

Hello World

"), - rx.html("

Hello World

"), - rx.html("

Hello World

"), - rx.html("
Hello World
"), - rx.html("
Hello World
"), -) -``` - -```md alert -# Missing Styles? -Reflex uses Radix-UI and tailwind for styling, both of which reset default styles for headings. -If you are using the html component and want pretty default styles, consider setting `class_name='prose'`, adding `@tailwindcss/typography` package to `frontend_packages` and enabling it via `tailwind` config in `rxconfig.py`. See the [Tailwind docs]({styling.overview.path}) for an example of adding this plugin. -``` - -In this example, we render an image. - -```python demo -rx.html("") -``` diff --git a/docs/library/other/memo.md b/docs/library/other/memo.md deleted file mode 100644 index 2fe0798d17..0000000000 --- a/docs/library/other/memo.md +++ /dev/null @@ -1,165 +0,0 @@ -```python exec -import reflex as rx -from pcweb.pages.docs import styling -``` - -# Memo - -The `memo` decorator is used to optimize component rendering by memoizing components that don't need to be re-rendered. This is particularly useful for expensive components that depend on specific props and don't need to be re-rendered when other state changes in your application. - -## Requirements - -When using `rx.memo`, you must follow these requirements: - -1. **Type all arguments**: All arguments to a memoized component must have type annotations. -2. **Use keyword arguments**: When calling a memoized component, you must use keyword arguments (not positional arguments). - -## Basic Usage - -When you wrap a component function with `@rx.memo`, the component will only re-render when its props change. This helps improve performance by preventing unnecessary re-renders. - -```python -# Define a state class to track count -class DemoState(rx.State): - count: int = 0 - - @rx.event - def increment(self): - self.count += 1 - -# Define a memoized component -@rx.memo -def expensive_component(label: str) -> rx.Component: - return rx.vstack( - rx.heading(label), - rx.text("This component only re-renders when props change!"), - rx.divider(), - ) - -# Use the memoized component in your app -def index(): - return rx.vstack( - rx.heading("Memo Example"), - rx.text("Count: 0"), # This will update with state.count - rx.button("Increment", on_click=DemoState.increment), - rx.divider(), - expensive_component(label="Memoized Component"), # Must use keyword arguments - spacing="4", - padding="4", - border_radius="md", - border="1px solid #eaeaea", - ) -``` - -In this example, the `expensive_component` will only re-render when the `label` prop changes, not when the `count` state changes. - -## With Event Handlers - -You can also use `rx.memo` with components that have event handlers: - -```python -# Define a state class to track clicks -class ButtonState(rx.State): - clicks: int = 0 - - @rx.event - def increment(self): - self.clicks += 1 - -# Define a memoized button component -@rx.memo -def my_button(text: str, on_click: rx.EventHandler) -> rx.Component: - return rx.button(text, on_click=on_click) - -# Use the memoized button in your app -def index(): - return rx.vstack( - rx.text("Clicks: 0"), # This will update with state.clicks - my_button( - text="Click me", - on_click=ButtonState.increment - ), - spacing="4", - ) -``` - -## With State Variables - -When used with state variables, memoized components will only re-render when the specific state variables they depend on change: - -```python -# Define a state class with multiple variables -class AppState(rx.State): - name: str = "World" - count: int = 0 - - @rx.event - def increment(self): - self.count += 1 - - @rx.event - def set_name(self, name: str): - self.name = name - -# Define a memoized greeting component -@rx.memo -def greeting(name: str) -> rx.Component: - return rx.heading("Hello, " + name) # Will display the name prop - -# Use the memoized component with state variables -def index(): - return rx.vstack( - greeting(name=AppState.name), # Must use keyword arguments - rx.text("Count: 0"), # Will display the count - rx.button("Increment Count", on_click=AppState.increment), - rx.input( - placeholder="Enter your name", - on_change=AppState.set_name, - value="World", # Will be bound to AppState.name - ), - spacing="4", - ) -``` - -## Advanced Event Handler Example - -You can also pass arguments to event handlers in memoized components: - -```python -# Define a state class to track messages -class MessageState(rx.State): - message: str = "" - - @rx.event - def set_message(self, text: str): - self.message = text - -# Define a memoized component with event handlers that pass arguments -@rx.memo -def action_buttons(on_action: rx.EventHandler[rx.event.passthrough_event_spec(str)]) -> rx.Component: - return rx.hstack( - rx.button("Save", on_click=on_action("Saved!")), - rx.button("Delete", on_click=on_action("Deleted!")), - rx.button("Cancel", on_click=on_action("Cancelled!")), - spacing="2", - ) - -# Use the memoized component with event handlers -def index(): - return rx.vstack( - rx.text("Status: "), # Will display the message - action_buttons(on_action=MessageState.set_message), - spacing="4", - ) -``` - -## Performance Considerations - -Use `rx.memo` for: -- Components with expensive rendering logic -- Components that render the same result given the same props -- Components that re-render too often due to parent component updates - -Avoid using `rx.memo` for: -- Simple components where the memoization overhead might exceed the performance gain -- Components that almost always receive different props on re-render diff --git a/docs/library/other/script.md b/docs/library/other/script.md deleted file mode 100644 index 8d9356e7c4..0000000000 --- a/docs/library/other/script.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -components: - - rx.script ---- - -```python exec -import reflex as rx -``` - -# Script - -The Script component can be used to include inline javascript or javascript files by URL. - -It uses the [`next/script` component](https://nextjs.org/docs/app/api-reference/components/script) to inject the script and can be safely used with conditional rendering to allow script side effects to be controlled by the state. - -```python -rx.script("console.log('inline javascript')") -``` - -Complex inline scripting should be avoided. -If the code to be included is more than a couple lines, it is more maintainable to implement it in a separate javascript file in the `assets` directory and include it via the `src` prop. - -```python -rx.script(src="/my-custom.js") -``` - -This component is particularly helpful for including tracking and social scripts. -Any additional attrs needed for the script tag can be supplied via `custom_attrs` prop. - -```python -rx.script(src="//gc.zgo.at/count.js", custom_attrs=\{"data-goatcounter": "https://reflextoys.goatcounter.com/count"}) -``` - -This code renders to something like the following to enable stat counting with a third party service. - -```jsx - -``` diff --git a/docs/library/other/skeleton.md b/docs/library/other/skeleton.md deleted file mode 100644 index 46c6a08f6e..0000000000 --- a/docs/library/other/skeleton.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -description: Skeleton, a loading placeholder component for content that is not yet available. -components: - - rx.skeleton ---- - -```python exec -import reflex as rx -``` - -# Skeleton (loading placeholder) - -`Skeleton` is a loading placeholder component that serves as a visual placeholder while content is loading. -It is useful for maintaining the layout's structure and providing users with a sense of progression while awaiting the final content. - -```python demo -rx.vstack( - rx.skeleton(rx.button("button-small"), height="10px"), - rx.skeleton(rx.button("button-big"), height="20px"), - rx.skeleton(rx.text("Text is loaded."), loading=True,), - rx.skeleton(rx.text("Text is already loaded."), loading=False,), -), -``` - -When using `Skeleton` with text, wrap the text itself instead of the parent element to have a placeholder of the same size. - -Use the loading prop to control whether the skeleton or its children are displayed. Skeleton preserves the dimensions of children when they are hidden and disables interactive elements. diff --git a/docs/library/other/theme.md b/docs/library/other/theme.md deleted file mode 100644 index 3ba92af878..0000000000 --- a/docs/library/other/theme.md +++ /dev/null @@ -1,31 +0,0 @@ ---- - components: - - rx.theme - - rx.theme_panel ---- - -# Theme - - The `Theme` component is used to change the theme of the application. The `Theme` can be set directly in the rx.App. - - ```python - app = rx.App( - theme=rx.theme( - appearance="light", has_background=True, radius="large", accent_color="teal" - ) - ) - ``` - - # Theme Panel - - The `ThemePanel` component is a container for the `Theme` component. It provides a way to change the theme of the application. - - ```python - rx.theme_panel() - ``` - - The theme panel is closed by default. You can set it open `default_open=True`. - - ```python - rx.theme_panel(default_open=True) - ``` diff --git a/docs/library/overlay/alert_dialog.md b/docs/library/overlay/alert_dialog.md deleted file mode 100644 index 6316a6ee74..0000000000 --- a/docs/library/overlay/alert_dialog.md +++ /dev/null @@ -1,367 +0,0 @@ ---- -components: - - rx.alert_dialog.root - - rx.alert_dialog.content - - rx.alert_dialog.trigger - - rx.alert_dialog.title - - rx.alert_dialog.description - - rx.alert_dialog.action - - rx.alert_dialog.cancel - -only_low_level: - - True - -AlertDialogRoot: | - lambda **props: rx.alert_dialog.root( - rx.alert_dialog.trigger( - rx.button("Revoke access"), - ), - rx.alert_dialog.content( - rx.alert_dialog.title("Revoke access"), - rx.alert_dialog.description( - "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", - ), - rx.flex( - rx.alert_dialog.cancel( - rx.button("Cancel"), - ), - rx.alert_dialog.action( - rx.button("Revoke access"), - ), - spacing="3", - ), - ), - **props - ) - -AlertDialogContent: | - lambda **props: rx.alert_dialog.root( - rx.alert_dialog.trigger( - rx.button("Revoke access"), - ), - rx.alert_dialog.content( - rx.alert_dialog.title("Revoke access"), - rx.alert_dialog.description( - "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", - ), - rx.flex( - rx.alert_dialog.cancel( - rx.button("Cancel"), - ), - rx.alert_dialog.action( - rx.button("Revoke access"), - ), - spacing="3", - ), - **props - ), - ) ---- - -```python exec -import reflex as rx -``` - -# Alert Dialog - -An alert dialog is a modal confirmation dialog that interrupts the user and expects a response. - -The `alert_dialog.root` contains all the parts of the dialog. - -The `alert_dialog.trigger` wraps the control that will open the dialog. - -The `alert_dialog.content` contains the content of the dialog. - -The `alert_dialog.title` is the title that is announced when the dialog is opened. - -The `alert_dialog.description` is an optional description that is announced when the dialog is opened. - -The `alert_dialog.action` wraps the control that will close the dialog. This should be distinguished visually from the `alert_dialog.cancel` control. - -The `alert_dialog.cancel` wraps the control that will close the dialog. This should be distinguished visually from the `alert_dialog.action` control. - -## Basic Example - -```python demo -rx.alert_dialog.root( - rx.alert_dialog.trigger( - rx.button("Revoke access"), - ), - rx.alert_dialog.content( - rx.alert_dialog.title("Revoke access"), - rx.alert_dialog.description( - "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", - ), - rx.flex( - rx.alert_dialog.cancel( - rx.button("Cancel"), - ), - rx.alert_dialog.action( - rx.button("Revoke access"), - ), - spacing="3", - ), - ), -) -``` - -This example has a different color scheme and the `cancel` and `action` buttons are right aligned. - -```python demo -rx.alert_dialog.root( - rx.alert_dialog.trigger( - rx.button("Revoke access", color_scheme="red"), - ), - rx.alert_dialog.content( - rx.alert_dialog.title("Revoke access"), - rx.alert_dialog.description( - "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", - size="2", - ), - rx.flex( - rx.alert_dialog.cancel( - rx.button("Cancel", variant="soft", color_scheme="gray"), - ), - rx.alert_dialog.action( - rx.button("Revoke access", color_scheme="red", variant="solid"), - ), - spacing="3", - margin_top="16px", - justify="end", - ), - style={"max_width": 450}, - ), -) -``` - -Use the `inset` component to align content flush with the sides of the dialog. - -```python demo -rx.alert_dialog.root( - rx.alert_dialog.trigger( - rx.button("Delete Users", color_scheme="red"), - ), - rx.alert_dialog.content( - rx.alert_dialog.title("Delete Users"), - rx.alert_dialog.description( - "Are you sure you want to delete these users? This action is permanent and cannot be undone.", - size="2", - ), - rx.inset( - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Full Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Group"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.row_header_cell("Danilo Rosa"), - rx.table.cell("danilo@example.com"), - rx.table.cell("Developer"), - ), - rx.table.row( - rx.table.row_header_cell("Zahra Ambessa"), - rx.table.cell("zahra@example.com"), - rx.table.cell("Admin"), - ), - ), - ), - side="x", - margin_top="24px", - margin_bottom="24px", - ), - rx.flex( - rx.alert_dialog.cancel( - rx.button("Cancel", variant="soft", color_scheme="gray"), - ), - rx.alert_dialog.action( - rx.button("Delete users", color_scheme="red"), - ), - spacing="3", - justify="end", - ), - style={"max_width": 500}, - ), -) -``` - -## Events when the Alert Dialog opens or closes - -The `on_open_change` event is called when the `open` state of the dialog changes. It is used in conjunction with the `open` prop. - -```python demo exec -class AlertDialogState(rx.State): - num_opens: int = 0 - opened: bool = False - - @rx.event - def count_opens(self, value: bool): - self.opened = value - self.num_opens += 1 - - -def alert_dialog(): - return rx.flex( - rx.heading(f"Number of times alert dialog opened or closed: {AlertDialogState.num_opens}"), - rx.heading(f"Alert Dialog open: {AlertDialogState.opened}"), - rx.alert_dialog.root( - rx.alert_dialog.trigger( - rx.button("Revoke access", color_scheme="red"), - ), - rx.alert_dialog.content( - rx.alert_dialog.title("Revoke access"), - rx.alert_dialog.description( - "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", - size="2", - ), - rx.flex( - rx.alert_dialog.cancel( - rx.button("Cancel", variant="soft", color_scheme="gray"), - ), - rx.alert_dialog.action( - rx.button("Revoke access", color_scheme="red", variant="solid"), - ), - spacing="3", - margin_top="16px", - justify="end", - ), - style={"max_width": 450}, - ), - on_open_change=AlertDialogState.count_opens, - ), - direction="column", - spacing="3", - ) -``` - -## Controlling Alert Dialog with State - -This example shows how to control whether the dialog is open or not with state. This is an easy way to show the dialog without needing to use the `rx.alert_dialog.trigger`. - -`rx.alert_dialog.root` has a prop `open` that can be set to a boolean value to control whether the dialog is open or not. - -We toggle this `open` prop with a button outside of the dialog and the `rx.alert_dialog.cancel` and `rx.alert_dialog.action` buttons inside the dialog. - -```python demo exec -class AlertDialogState2(rx.State): - opened: bool = False - - @rx.event - def dialog_open(self): - self.opened = ~self.opened - - -def alert_dialog2(): - return rx.box( - rx.alert_dialog.root( - rx.alert_dialog.content( - rx.alert_dialog.title("Revoke access"), - rx.alert_dialog.description( - "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", - ), - rx.flex( - rx.alert_dialog.cancel( - rx.button("Cancel", on_click=AlertDialogState2.dialog_open), - ), - rx.alert_dialog.action( - rx.button("Revoke access", on_click=AlertDialogState2.dialog_open), - ), - spacing="3", - ), - ), - open=AlertDialogState2.opened, - ), - rx.button("Button to Open the Dialog", on_click=AlertDialogState2.dialog_open), -) -``` - - -## Form Submission to a Database from an Alert Dialog - -This example adds new users to a database from an alert dialog using a form. - -1. It defines a User1 model with name and email fields. -2. The `add_user_to_db` method adds a new user to the database, checking for existing emails. -3. On form submission, it calls the `add_user_to_db` method. -4. The UI component has: -- A button to open an alert dialog -- An alert dialog containing a form to add a new user -- Input fields for name and email -- Submit and Cancel buttons - - -```python demo exec -class User1(rx.Model, table=True): - """The user model.""" - name: str - email: str - -class State(rx.State): - - current_user: User1 = User1() - - @rx.event - def add_user_to_db(self, form_data: dict): - self.current_user = form_data - ### Uncomment the code below to add your data to a database ### - # with rx.session() as session: - # if session.exec( - # select(User1).where(user.email == self.current_user["email"]) - # ).first(): - # return rx.window_alert("User with this email already exists") - # session.add(User1(**self.current_user)) - # session.commit() - - return rx.toast.info(f"User {self.current_user['name']} has been added.", position="bottom-right") - - -def index() -> rx.Component: - return rx.alert_dialog.root( - rx.alert_dialog.trigger( - rx.button( - rx.icon("plus", size=26), - rx.text("Add User", size="4"), - ), - ), - rx.alert_dialog.content( - rx.alert_dialog.title( - "Add New User", - ), - rx.alert_dialog.description( - "Fill the form with the user's info", - ), - rx.form( - rx.flex( - rx.input( - placeholder="User Name", name="name" - ), - rx.input( - placeholder="user@reflex.dev", name="email" - ), - rx.flex( - rx.alert_dialog.cancel( - rx.button( - "Cancel", - variant="soft", - color_scheme="gray", - ), - ), - rx.alert_dialog.action( - rx.button("Submit", type="submit"), - ), - spacing="3", - justify="end", - ), - direction="column", - spacing="4", - ), - on_submit=State.add_user_to_db, - reset_on_submit=False, - ), - max_width="450px", - ), - ) -``` diff --git a/docs/library/overlay/context_menu.md b/docs/library/overlay/context_menu.md deleted file mode 100644 index b284a82986..0000000000 --- a/docs/library/overlay/context_menu.md +++ /dev/null @@ -1,343 +0,0 @@ ---- -components: - - rx.context_menu.root - - rx.context_menu.item - - rx.context_menu.separator - - rx.context_menu.trigger - - rx.context_menu.content - - rx.context_menu.sub - - rx.context_menu.sub_trigger - - rx.context_menu.sub_content - -only_low_level: - - True - -ContextMenuRoot: | - lambda **props: rx.context_menu.root( - rx.context_menu.trigger( - rx.text("Context Menu (right click)") - ), - rx.context_menu.content( - rx.context_menu.item("Copy", shortcut="⌘ C"), - rx.context_menu.item("Share"), - rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), - rx.context_menu.sub( - rx.context_menu.sub_trigger("More"), - rx.context_menu.sub_content( - rx.context_menu.item("Eradicate"), - rx.context_menu.item("Duplicate"), - rx.context_menu.item("Archive"), - ), - ), - ), - **props - ) - -ContextMenuTrigger: | - lambda **props: rx.context_menu.root( - rx.context_menu.trigger( - rx.text("Context Menu (right click)"), - **props - ), - rx.context_menu.content( - rx.context_menu.item("Copy", shortcut="⌘ C"), - rx.context_menu.item("Share"), - rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), - rx.context_menu.sub( - rx.context_menu.sub_trigger("More"), - rx.context_menu.sub_content( - rx.context_menu.item("Eradicate"), - rx.context_menu.item("Duplicate"), - rx.context_menu.item("Archive"), - ), - ), - ), - ) - -ContextMenuContent: | - lambda **props: rx.context_menu.root( - rx.context_menu.trigger( - rx.text("Context Menu (right click)") - ), - rx.context_menu.content( - rx.context_menu.item("Copy", shortcut="⌘ C"), - rx.context_menu.item("Share"), - rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), - rx.context_menu.sub( - rx.context_menu.sub_trigger("More"), - rx.context_menu.sub_content( - rx.context_menu.item("Eradicate"), - rx.context_menu.item("Duplicate"), - rx.context_menu.item("Archive"), - ), - ), - **props - ), - ) - -ContextMenuSub: | - lambda **props: rx.context_menu.root( - rx.context_menu.trigger( - rx.text("Context Menu (right click)") - ), - rx.context_menu.content( - rx.context_menu.item("Copy", shortcut="⌘ C"), - rx.context_menu.item("Share"), - rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), - rx.context_menu.sub( - rx.context_menu.sub_trigger("More"), - rx.context_menu.sub_content( - rx.context_menu.item("Eradicate"), - rx.context_menu.item("Duplicate"), - rx.context_menu.item("Archive"), - ), - **props - ), - ), - ) - -ContextMenuSubTrigger: | - lambda **props: rx.context_menu.root( - rx.context_menu.trigger( - rx.text("Context Menu (right click)") - ), - rx.context_menu.content( - rx.context_menu.item("Copy", shortcut="⌘ C"), - rx.context_menu.item("Share"), - rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), - rx.context_menu.sub( - rx.context_menu.sub_trigger("More", **props), - rx.context_menu.sub_content( - rx.context_menu.item("Eradicate"), - rx.context_menu.item("Duplicate"), - rx.context_menu.item("Archive"), - ), - ), - ), - ) - -ContextMenuSubContent: | - lambda **props: rx.context_menu.root( - rx.context_menu.trigger( - rx.text("Context Menu (right click)") - ), - rx.context_menu.content( - rx.context_menu.item("Copy", shortcut="⌘ C"), - rx.context_menu.item("Share"), - rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), - rx.context_menu.sub( - rx.context_menu.sub_trigger("More"), - rx.context_menu.sub_content( - rx.context_menu.item("Eradicate"), - rx.context_menu.item("Duplicate"), - rx.context_menu.item("Archive"), - **props - ), - ), - ), - ) - -ContextMenuItem: | - lambda **props: rx.context_menu.root( - rx.context_menu.trigger( - rx.text("Context Menu (right click)") - ), - rx.context_menu.content( - rx.context_menu.item("Copy", shortcut="⌘ C", **props), - rx.context_menu.item("Share", **props), - rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red", **props), - rx.context_menu.sub( - rx.context_menu.sub_trigger("More"), - rx.context_menu.sub_content( - rx.context_menu.item("Eradicate", **props), - rx.context_menu.item("Duplicate", **props), - rx.context_menu.item("Archive", **props), - ), - ), - ), - ) ---- - -```python exec -import reflex as rx -``` - -# Context Menu - -A Context Menu is a popup menu that appears upon user interaction, such as a right-click or a hover. - -## Basic Usage - -A Context Menu is composed of a `context_menu.root`, a `context_menu.trigger` and a `context_menu.content`. The `context_menu_root` contains all the parts of a context menu. The `context_menu.trigger` is the element that the user interacts with to open the menu. It wraps the element that will open the context menu. The `context_menu.content` is the component that pops out when the context menu is open. - -The `context_menu.item` contains the actual context menu items and sits under the `context_menu.content`. - -The `context_menu.sub` contains all the parts of a submenu. There is a `context_menu.sub_trigger`, which is an item that opens a submenu. It must be rendered inside a `context_menu.sub` component. The `context_menu.sub_content` is the component that pops out when a submenu is open. It must also be rendered inside a `context_menu.sub` component. - -The `context_menu.separator` is used to visually separate items in a context menu. - -```python demo -rx.context_menu.root( - rx.context_menu.trigger( - rx.button("Right click me"), - ), - rx.context_menu.content( - rx.context_menu.item("Edit", shortcut="⌘ E"), - rx.context_menu.item("Duplicate", shortcut="⌘ D"), - rx.context_menu.separator(), - rx.context_menu.item("Archive", shortcut="⌘ N"), - rx.context_menu.sub( - rx.context_menu.sub_trigger("More"), - rx.context_menu.sub_content( - rx.context_menu.item("Move to project…"), - rx.context_menu.item("Move to folder…"), - rx.context_menu.separator(), - rx.context_menu.item("Advanced options…"), - ), - ), - rx.context_menu.separator(), - rx.context_menu.item("Share"), - rx.context_menu.item("Add to favorites"), - rx.context_menu.separator(), - rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), - ), -) -``` - -````md alert warning -# `rx.context_menu.item` must be a DIRECT child of `rx.context_menu.content` - -The code below for example is not allowed: - -```python -rx.context_menu.root( - rx.context_menu.trigger( - rx.button("Right click me"), - ), - rx.context_menu.content( - rx.cond( - State.count % 2 == 0, - rx.vstack( - rx.context_menu.item("Even Option 1", on_click=State.set_selected_option("Even Option 1")), - rx.context_menu.item("Even Option 2", on_click=State.set_selected_option("Even Option 2")), - rx.context_menu.item("Even Option 3", on_click=State.set_selected_option("Even Option 3")), - ), - rx.vstack( - rx.context_menu.item("Odd Option A", on_click=State.set_selected_option("Odd Option A")), - rx.context_menu.item("Odd Option B", on_click=State.set_selected_option("Odd Option B")), - rx.context_menu.item("Odd Option C", on_click=State.set_selected_option("Odd Option C")), - ) - ) - ), -) -``` -```` - -## Opening a Dialog from Context Menu using State - -Accessing an overlay component from within another overlay component is a common use case but does not always work exactly as expected. - -The code below will not work as expected as because the dialog is within the menu and the dialog will only be open when the menu is open, rendering the dialog unusable. - -```python -rx.context_menu.root( - rx.context_menu.trigger(rx.icon("ellipsis-vertical")), - rx.context_menu.content( - rx.context_menu.item( - rx.dialog.root( - rx.dialog.trigger(rx.text("Edit")), - rx.dialog.content(....), - ..... - ), - ), - ), -) -``` - -In this example, we will show how to open a dialog box from a context menu, where the menu will close and the dialog will open and be functional. - -```python demo exec -class ContextMenuState(rx.State): - which_dialog_open: str = "" - - @rx.event - def set_which_dialog_open(self, value: str): - self.which_dialog_open = value - - @rx.event - def delete(self): - yield rx.toast("Deleted item") - - @rx.event - def save_settings(self): - yield rx.toast("Saved settings") - - -def delete_dialog(): - return rx.alert_dialog.root( - rx.alert_dialog.content( - rx.alert_dialog.title("Are you Sure?"), - rx.alert_dialog.description( - rx.text( - "This action cannot be undone. Are you sure you want to delete this item?", - ), - margin_bottom="20px", - ), - rx.hstack( - rx.alert_dialog.action( - rx.button( - "Delete", - color_scheme="red", - on_click=ContextMenuState.delete, - ), - ), - rx.spacer(), - rx.alert_dialog.cancel(rx.button("Cancel")), - ), - ), - open=ContextMenuState.which_dialog_open == "delete", - on_open_change=ContextMenuState.set_which_dialog_open(""), - ) - - -def settings_dialog(): - return rx.dialog.root( - rx.dialog.content( - rx.dialog.title("Settings"), - rx.dialog.description( - rx.text("Set your settings in this settings dialog."), - margin_bottom="20px", - ), - rx.dialog.close( - rx.button("Close", on_click=ContextMenuState.save_settings), - ), - ), - open=ContextMenuState.which_dialog_open == "settings", - on_open_change=ContextMenuState.set_which_dialog_open(""), - ) - - -def context_menu_call_dialog() -> rx.Component: - return rx.vstack( - rx.context_menu.root( - rx.context_menu.trigger(rx.icon("ellipsis-vertical")), - rx.context_menu.content( - rx.context_menu.item( - "Delete", - on_click=ContextMenuState.set_which_dialog_open("delete"), - ), - rx.context_menu.item( - "Settings", - on_click=ContextMenuState.set_which_dialog_open("settings"), - ), - ), - ), - rx.cond( - ContextMenuState.which_dialog_open, - rx.heading(f"{ContextMenuState.which_dialog_open} dialog is open"), - ), - delete_dialog(), - settings_dialog(), - align="center", - ) -``` diff --git a/docs/library/overlay/dialog.md b/docs/library/overlay/dialog.md deleted file mode 100644 index 604dc5c43c..0000000000 --- a/docs/library/overlay/dialog.md +++ /dev/null @@ -1,283 +0,0 @@ ---- -components: - - rx.dialog.root - - rx.dialog.trigger - - rx.dialog.title - - rx.dialog.content - - rx.dialog.description - - rx.dialog.close - -only_low_level: - - True - -DialogRoot: | - lambda **props: rx.dialog.root( - rx.dialog.trigger(rx.button("Open Dialog")), - rx.dialog.content( - rx.dialog.title("Welcome to Reflex!"), - rx.dialog.description( - "This is a dialog component. You can render anything you want in here.", - ), - rx.dialog.close( - rx.button("Close Dialog"), - ), - ), - **props, - ) - -DialogContent: | - lambda **props: rx.dialog.root( - rx.dialog.trigger(rx.button("Open Dialog")), - rx.dialog.content( - rx.dialog.title("Welcome to Reflex!"), - rx.dialog.description( - "This is a dialog component. You can render anything you want in here.", - ), - rx.dialog.close( - rx.button("Close Dialog"), - ), - **props, - ), - ) ---- - -```python exec -import reflex as rx -from pcweb.pages.docs import library -``` - -# Dialog - -The `dialog.root` contains all the parts of a dialog. - -The `dialog.trigger` wraps the control that will open the dialog. - -The `dialog.content` contains the content of the dialog. - -The `dialog.title` is a title that is announced when the dialog is opened. - -The `dialog.description` is a description that is announced when the dialog is opened. - -The `dialog.close` wraps the control that will close the dialog. - -```python demo -rx.dialog.root( - rx.dialog.trigger(rx.button("Open Dialog")), - rx.dialog.content( - rx.dialog.title("Welcome to Reflex!"), - rx.dialog.description( - "This is a dialog component. You can render anything you want in here.", - ), - rx.dialog.close( - rx.button("Close Dialog", size="3"), - ), - ), -) -``` - -## In context examples - -```python demo -rx.dialog.root( - rx.dialog.trigger( - rx.button("Edit Profile", size="4") - ), - rx.dialog.content( - rx.dialog.title("Edit Profile"), - rx.dialog.description( - "Change your profile details and preferences.", - size="2", - margin_bottom="16px", - ), - rx.flex( - rx.text("Name", as_="div", size="2", margin_bottom="4px", weight="bold"), - rx.input(default_value="Freja Johnson", placeholder="Enter your name"), - rx.text("Email", as_="div", size="2", margin_bottom="4px", weight="bold"), - rx.input(default_value="freja@example.com", placeholder="Enter your email"), - direction="column", - spacing="3", - ), - rx.flex( - rx.dialog.close( - rx.button("Cancel", color_scheme="gray", variant="soft"), - ), - rx.dialog.close( - rx.button("Save"), - ), - spacing="3", - margin_top="16px", - justify="end", - ), - ), -) -``` - -```python demo -rx.dialog.root( - rx.dialog.trigger(rx.button("View users", size="4")), - rx.dialog.content( - rx.dialog.title("Users"), - rx.dialog.description("The following users have access to this project."), - - rx.inset( - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Full Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Group"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.row_header_cell("Danilo Rosa"), - rx.table.cell("danilo@example.com"), - rx.table.cell("Developer"), - ), - rx.table.row( - rx.table.row_header_cell("Zahra Ambessa"), - rx.table.cell("zahra@example.com"), - rx.table.cell("Admin"), - ), - ), - ), - side="x", - margin_top="24px", - margin_bottom="24px", - ), - rx.flex( - rx.dialog.close( - rx.button("Close", variant="soft", color_scheme="gray"), - ), - spacing="3", - justify="end", - ), - ), -) -``` - -## Events when the Dialog opens or closes - -The `on_open_change` event is called when the `open` state of the dialog changes. It is used in conjunction with the `open` prop, which is passed to the event handler. - -```python demo exec -class DialogState(rx.State): - num_opens: int = 0 - opened: bool = False - - @rx.event - def count_opens(self, value: bool): - self.opened = value - self.num_opens += 1 - - -def dialog_example(): - return rx.flex( - rx.heading(f"Number of times dialog opened or closed: {DialogState.num_opens}"), - rx.heading(f"Dialog open: {DialogState.opened}"), - rx.dialog.root( - rx.dialog.trigger(rx.button("Open Dialog")), - rx.dialog.content( - rx.dialog.title("Welcome to Reflex!"), - rx.dialog.description( - "This is a dialog component. You can render anything you want in here.", - ), - rx.dialog.close( - rx.button("Close Dialog", size="3"), - ), - ), - on_open_change=DialogState.count_opens, - ), - direction="column", - spacing="3", - ) -``` - -Check out the [menu docs]({library.overlay.dropdown_menu.path}) for an example of opening a dialog from within a dropdown menu. - -## Form Submission to a Database from a Dialog - -This example adds new users to a database from a dialog using a form. - -1. It defines a User model with name and email fields. -2. The `add_user_to_db` method adds a new user to the database, checking for existing emails. -3. On form submission, it calls the `add_user_to_db` method. -4. The UI component has: - -- A button to open a dialog -- A dialog containing a form to add a new user -- Input fields for name and email -- Submit and Cancel buttons - -```python demo exec -class User(rx.Model, table=True): - """The user model.""" - name: str - email: str - -class State(rx.State): - - current_user: User = User() - - @rx.event - def add_user_to_db(self, form_data: dict): - self.current_user = form_data - ### Uncomment the code below to add your data to a database ### - # with rx.session() as session: - # if session.exec( - # select(User).where(user.email == self.current_user["email"]) - # ).first(): - # return rx.window_alert("User with this email already exists") - # session.add(User(**self.current_user)) - # session.commit() - - return rx.toast.info(f"User {self.current_user['name']} has been added.", position="bottom-right") - - -def index() -> rx.Component: - return rx.dialog.root( - rx.dialog.trigger( - rx.button( - rx.icon("plus", size=26), - rx.text("Add User", size="4"), - ), - ), - rx.dialog.content( - rx.dialog.title( - "Add New User", - ), - rx.dialog.description( - "Fill the form with the user's info", - ), - rx.form( - rx.flex( - rx.input( - placeholder="User Name", name="name" - ), - rx.input( - placeholder="user@reflex.dev", name="email" - ), - rx.flex( - rx.dialog.close( - rx.button( - "Cancel", - variant="soft", - color_scheme="gray", - ), - ), - rx.dialog.close( - rx.button("Submit", type="submit"), - ), - spacing="3", - justify="end", - ), - direction="column", - spacing="4", - ), - on_submit=State.add_user_to_db, - reset_on_submit=False, - ), - max_width="450px", - ), - ) -``` diff --git a/docs/library/overlay/drawer.md b/docs/library/overlay/drawer.md deleted file mode 100644 index a8beb95451..0000000000 --- a/docs/library/overlay/drawer.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -components: - - rx.drawer.root - - rx.drawer.trigger - - rx.drawer.overlay - - rx.drawer.portal - - rx.drawer.content - - rx.drawer.close - -only_low_level: - - True - -DrawerRoot: | - lambda **props: rx.drawer.root( - rx.drawer.trigger(rx.button("Open Drawer")), - rx.drawer.overlay(z_index="5"), - rx.drawer.portal( - rx.drawer.content( - rx.flex( - rx.drawer.close(rx.button("Close")), - ), - height="100%", - width="20em", - background_color="#FFF" - ), - ), - **props, - ) ---- - -```python exec -import reflex as rx -``` - -# Drawer - -```python demo -rx.drawer.root( - rx.drawer.trigger( - rx.button("Open Drawer") - ), - rx.drawer.overlay( - z_index="5" - ), - rx.drawer.portal( - rx.drawer.content( - rx.flex( - rx.drawer.close(rx.box(rx.button("Close"))), - align_items="start", - direction="column", - ), - top="auto", - right="auto", - height="100%", - width="20em", - padding="2em", - background_color="#FFF" - #background_color=rx.color("green", 3) - ) - ), - direction="left", -) -``` - -## Sidebar Menu with a Drawer and State - -This example shows how to create a sidebar menu with a drawer. The drawer is opened by clicking a button. The drawer contains links to different sections of the page. When a link is clicked the drawer closes and the page scrolls to the section. - -The `rx.drawer.root` component has an `open` prop that is set by the state variable `is_open`. Setting the `modal` prop to `False` allows the user to interact with the rest of the page while the drawer is open and allows the page to be scrolled when a user clicks one of the links. - -```python demo exec -class DrawerState(rx.State): - is_open: bool = False - - @rx.event - def toggle_drawer(self): - self.is_open = not self.is_open - -def drawer_content(): - return rx.drawer.content( - rx.flex( - rx.drawer.close(rx.button("Close", on_click=DrawerState.toggle_drawer)), - rx.link("Link 1", href="#test1", on_click=DrawerState.toggle_drawer), - rx.link("Link 2", href="#test2", on_click=DrawerState.toggle_drawer), - align_items="start", - direction="column", - ), - height="100%", - width="20%", - padding="2em", - background_color=rx.color("grass", 7), - ) - - -def lateral_menu(): - return rx.drawer.root( - rx.drawer.trigger(rx.button("Open Drawer", on_click=DrawerState.toggle_drawer)), - rx.drawer.overlay(), - rx.drawer.portal(drawer_content()), - open=DrawerState.is_open, - direction="left", - modal=False, - ) - -def drawer_sidebar(): - return rx.vstack( - lateral_menu(), - rx.section( - rx.heading("Test1", size="8"), - id='test1', - height="400px", - ), - rx.section( - rx.heading("Test2", size="8"), - id='test2', - height="400px", - ) - ) -``` diff --git a/docs/library/overlay/dropdown_menu.md b/docs/library/overlay/dropdown_menu.md deleted file mode 100644 index 1db023e87c..0000000000 --- a/docs/library/overlay/dropdown_menu.md +++ /dev/null @@ -1,315 +0,0 @@ ---- -components: - - rx.dropdown_menu.root - - rx.dropdown_menu.content - - rx.dropdown_menu.trigger - - rx.dropdown_menu.item - - rx.dropdown_menu.separator - - rx.dropdown_menu.sub_content - -only_low_level: - - True - -DropdownMenuRoot: | - lambda **props: rx.menu.root( - rx.menu.trigger(rx.button("drop down menu")), - rx.menu.content( - rx.menu.item("Edit", shortcut="⌘ E"), - rx.menu.item("Share"), - rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), - rx.menu.sub( - rx.menu.sub_trigger("More"), - rx.menu.sub_content( - rx.menu.item("Eradicate"), - rx.menu.item("Duplicate"), - rx.menu.item("Archive"), - ), - ), - ), - **props - ) - -DropdownMenuContent: | - lambda **props: rx.menu.root( - rx.menu.trigger(rx.button("drop down menu")), - rx.menu.content( - rx.menu.item("Edit", shortcut="⌘ E"), - rx.menu.item("Share"), - rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), - rx.menu.sub( - rx.menu.sub_trigger("More"), - rx.menu.sub_content( - rx.menu.item("Eradicate"), - rx.menu.item("Duplicate"), - rx.menu.item("Archive"), - ), - ), - **props, - ), - ) - -DropdownMenuItem: | - lambda **props: rx.menu.root( - rx.menu.trigger(rx.button("drop down menu")), - rx.menu.content( - rx.menu.item("Edit", shortcut="⌘ E", **props), - rx.menu.item("Share", **props), - rx.menu.item("Delete", shortcut="⌘ ⌫", color="red", **props), - rx.menu.sub( - rx.menu.sub_trigger("More"), - rx.menu.sub_content( - rx.menu.item("Eradicate", **props), - rx.menu.item("Duplicate", **props), - rx.menu.item("Archive", **props), - ), - ), - ), - ) - -DropdownMenuSub: | - lambda **props: rx.menu.root( - rx.menu.trigger(rx.button("drop down menu")), - rx.menu.content( - rx.menu.item("Edit", shortcut="⌘ E"), - rx.menu.item("Share"), - rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), - rx.menu.sub( - rx.menu.sub_trigger("More"), - rx.menu.sub_content( - rx.menu.item("Eradicate"), - rx.menu.item("Duplicate"), - rx.menu.item("Archive"), - ), - **props, - ), - ), - ) - -DropdownMenuSubTrigger: | - lambda **props: rx.menu.root( - rx.menu.trigger(rx.button("drop down menu")), - rx.menu.content( - rx.menu.item("Edit", shortcut="⌘ E"), - rx.menu.item("Share"), - rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), - rx.menu.sub( - rx.menu.sub_trigger("More", **props), - rx.menu.sub_content( - rx.menu.item("Eradicate"), - rx.menu.item("Duplicate"), - rx.menu.item("Archive"), - ), - ), - ), - ) - -DropdownMenuSubContent: | - lambda **props: rx.menu.root( - rx.menu.trigger(rx.button("drop down menu")), - rx.menu.content( - rx.menu.item("Edit", shortcut="⌘ E"), - rx.menu.item("Share"), - rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), - rx.menu.sub( - rx.menu.sub_trigger("More"), - rx.menu.sub_content( - rx.menu.item("Eradicate"), - rx.menu.item("Duplicate"), - rx.menu.item("Archive"), - **props, - ), - ), - ), - ) ---- - -```python exec -import reflex as rx -``` - -# Dropdown Menu - -A Dropdown Menu is a menu that offers a list of options that a user can select from. They are typically positioned near a button that will control their appearance and disappearance. - -A Dropdown Menu is composed of a `menu.root`, a `menu.trigger` and a `menu.content`. The `menu.trigger` is the element that the user interacts with to open the menu. It wraps the element that will open the dropdown menu. The `menu.content` is the component that pops out when the dropdown menu is open. - -The `menu.item` contains the actual dropdown menu items and sits under the `menu.content`. The `shortcut` prop is an optional shortcut command displayed next to the item text. - -The `menu.sub` contains all the parts of a submenu. There is a `menu.sub_trigger`, which is an item that opens a submenu. It must be rendered inside a `menu.sub` component. The `menu.sub_component` is the component that pops out when a submenu is open. It must also be rendered inside a `menu.sub` component. - -The `menu.separator` is used to visually separate items in a dropdown menu. - -```python demo -rx.menu.root( - rx.menu.trigger( - rx.button("Options", variant="soft"), - ), - rx.menu.content( - rx.menu.item("Edit", shortcut="⌘ E"), - rx.menu.item("Duplicate", shortcut="⌘ D"), - rx.menu.separator(), - rx.menu.item("Archive", shortcut="⌘ N"), - rx.menu.sub( - rx.menu.sub_trigger("More"), - rx.menu.sub_content( - rx.menu.item("Move to project…"), - rx.menu.item("Move to folder…"), - rx.menu.separator(), - rx.menu.item("Advanced options…"), - ), - ), - rx.menu.separator(), - rx.menu.item("Share"), - rx.menu.item("Add to favorites"), - rx.menu.separator(), - rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), - ), -) -``` - -## Events when the Dropdown Menu opens or closes - -The `on_open_change` event, from the `menu.root`, is called when the `open` state of the dropdown menu changes. It is used in conjunction with the `open` prop, which is passed to the event handler. - -```python demo exec -class DropdownMenuState(rx.State): - num_opens: int = 0 - opened: bool = False - - @rx.event - def count_opens(self, value: bool): - self.opened = value - self.num_opens += 1 - - -def dropdown_menu_example(): - return rx.flex( - rx.heading(f"Number of times Dropdown Menu opened or closed: {DropdownMenuState.num_opens}"), - rx.heading(f"Dropdown Menu open: {DropdownMenuState.opened}"), - rx.menu.root( - rx.menu.trigger( - rx.button("Options", variant="soft", size="2"), - ), - rx.menu.content( - rx.menu.item("Edit", shortcut="⌘ E"), - rx.menu.item("Duplicate", shortcut="⌘ D"), - rx.menu.separator(), - rx.menu.item("Archive", shortcut="⌘ N"), - rx.menu.separator(), - rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), - ), - on_open_change=DropdownMenuState.count_opens, - ), - direction="column", - spacing="3", - ) -``` - -## Opening a Dialog from Menu using State - -Accessing an overlay component from within another overlay component is a common use case but does not always work exactly as expected. - -The code below will not work as expected as because the dialog is within the menu and the dialog will only be open when the menu is open, rendering the dialog unusable. - -```python -rx.menu.root( - rx.menu.trigger(rx.icon("ellipsis-vertical")), - rx.menu.content( - rx.menu.item( - rx.dialog.root( - rx.dialog.trigger(rx.text("Edit")), - rx.dialog.content(....), - ..... - ), - ), - ), -) -``` - -In this example, we will show how to open a dialog box from a dropdown menu, where the menu will close and the dialog will open and be functional. - -```python demo exec -class DropdownMenuState2(rx.State): - which_dialog_open: str = "" - - @rx.event - def set_which_dialog_open(self, value: str): - self.which_dialog_open = value - - @rx.event - def delete(self): - yield rx.toast("Deleted item") - - @rx.event - def save_settings(self): - yield rx.toast("Saved settings") - - -def delete_dialog(): - return rx.alert_dialog.root( - rx.alert_dialog.content( - rx.alert_dialog.title("Are you Sure?"), - rx.alert_dialog.description( - rx.text( - "This action cannot be undone. Are you sure you want to delete this item?", - ), - margin_bottom="20px", - ), - rx.hstack( - rx.alert_dialog.action( - rx.button( - "Delete", - color_scheme="red", - on_click=DropdownMenuState2.delete, - ), - ), - rx.spacer(), - rx.alert_dialog.cancel(rx.button("Cancel")), - ), - ), - open=DropdownMenuState2.which_dialog_open == "delete", - on_open_change=DropdownMenuState2.set_which_dialog_open(""), - ) - - -def settings_dialog(): - return rx.dialog.root( - rx.dialog.content( - rx.dialog.title("Settings"), - rx.dialog.description( - rx.text("Set your settings in this settings dialog."), - margin_bottom="20px", - ), - rx.dialog.close( - rx.button("Close", on_click=DropdownMenuState2.save_settings), - ), - ), - open=DropdownMenuState2.which_dialog_open == "settings", - on_open_change=DropdownMenuState2.set_which_dialog_open(""), - ) - - -def menu_call_dialog() -> rx.Component: - return rx.vstack( - rx.menu.root( - rx.menu.trigger(rx.icon("menu")), - rx.menu.content( - rx.menu.item( - "Delete", - on_click=DropdownMenuState2.set_which_dialog_open("delete"), - ), - rx.menu.item( - "Settings", - on_click=DropdownMenuState2.set_which_dialog_open("settings"), - ), - ), - ), - rx.cond( - DropdownMenuState2.which_dialog_open, - rx.heading(f"{DropdownMenuState2.which_dialog_open} dialog is open"), - ), - delete_dialog(), - settings_dialog(), - align="center", - ) -``` diff --git a/docs/library/overlay/hover_card.md b/docs/library/overlay/hover_card.md deleted file mode 100644 index 6a23341ca7..0000000000 --- a/docs/library/overlay/hover_card.md +++ /dev/null @@ -1,126 +0,0 @@ ---- -components: - - rx.hover_card.root - - rx.hover_card.content - - rx.hover_card.trigger - -only_low_level: - - True - -HoverCardRoot: | - lambda **props: rx.hover_card.root( - rx.hover_card.trigger( - rx.link("Hover over me"), - ), - rx.hover_card.content( - rx.text("This is the tooltip content."), - ), - **props - ) - -HoverCardContent: | - lambda **props: rx.hover_card.root( - rx.hover_card.trigger( - rx.link("Hover over me"), - ), - rx.hover_card.content( - rx.text("This is the tooltip content."), - **props - ), - ) ---- - -```python exec -import reflex as rx -``` - -# Hovercard - -The `hover_card.root` contains all the parts of a hover card. - -The `hover_card.trigger` wraps the link that will open the hover card. - -The `hover_card.content` contains the content of the open hover card. - -```python demo -rx.text( - "Hover over the text to see the tooltip. ", - rx.hover_card.root( - rx.hover_card.trigger( - rx.link("Hover over me", color_scheme="blue", underline="always"), - ), - rx.hover_card.content( - rx.text("This is the hovercard content."), - ), - ), -) -``` - -```python demo -rx.text( - "Hover over the text to see the tooltip. ", - rx.hover_card.root( - rx.hover_card.trigger( - rx.link("Hover over me", color_scheme="blue", underline="always"), - ), - rx.hover_card.content( - rx.grid( - rx.inset( - side="left", - pr="current", - background="url('https://images.unsplash.com/5/unsplash-kitsune-4.jpg') center/cover", - height="full", - ), - rx.box( - rx.text_area(placeholder="Write a comment…", style={"height": 80}), - rx.flex( - rx.checkbox("Send to group"), - spacing="3", - margin_top="12px", - justify="between", - ), - padding_left="12px", - ), - columns="120px 1fr", - ), - style={"width": 360}, - ), - ), -) -``` - -## Events when the Hovercard opens or closes - -The `on_open_change` event is called when the `open` state of the hovercard changes. It is used in conjunction with the `open` prop, which is passed to the event handler. - -```python demo exec -class HovercardState(rx.State): - num_opens: int = 0 - opened: bool = False - - @rx.event - def count_opens(self, value: bool): - self.opened = value - self.num_opens += 1 - - -def hovercard_example(): - return rx.flex( - rx.heading(f"Number of times hovercard opened or closed: {HovercardState.num_opens}"), - rx.heading(f"Hovercard open: {HovercardState.opened}"), - rx.text( - "Hover over the text to see the hover card. ", - rx.hover_card.root( - rx.hover_card.trigger( - rx.link("Hover over me", color_scheme="blue", underline="always"), - ), - rx.hover_card.content( - rx.text("This is the tooltip content."), - ), - on_open_change=HovercardState.count_opens, - ), - ), - direction="column", - spacing="3", - ) -``` diff --git a/docs/library/overlay/popover.md b/docs/library/overlay/popover.md deleted file mode 100644 index 2d6b81f472..0000000000 --- a/docs/library/overlay/popover.md +++ /dev/null @@ -1,224 +0,0 @@ ---- -components: - - rx.popover.root - - rx.popover.content - - rx.popover.trigger - - rx.popover.close - -only_low_level: - - True - -PopoverRoot: | - lambda **props: rx.popover.root( - rx.popover.trigger( - rx.button("Popover"), - ), - rx.popover.content( - rx.flex( - rx.text("Simple Example"), - rx.popover.close( - rx.button("Close"), - ), - direction="column", - spacing="3", - ), - ), - **props - ) - -PopoverContent: | - lambda **props: rx.popover.root( - rx.popover.trigger( - rx.button("Popover"), - ), - rx.popover.content( - rx.flex( - rx.text("Simple Example"), - rx.popover.close( - rx.button("Close"), - ), - direction="column", - spacing="3", - ), - **props - ), - ) ---- - -```python exec -import reflex as rx -``` - -# Popover - -A popover displays content, triggered by a button. - -The `popover.root` contains all the parts of a popover. - -The `popover.trigger` contains the button that toggles the popover. - -The `popover.content` is the component that pops out when the popover is open. - -The `popover.close` is the button that closes an open popover. - -## Basic Example - -```python demo -rx.popover.root( - rx.popover.trigger( - rx.button("Popover"), - ), - rx.popover.content( - rx.flex( - rx.text("Simple Example"), - rx.popover.close( - rx.button("Close"), - ), - direction="column", - spacing="3", - ), - ), -) -``` - -## Examples in Context - -```python demo - -rx.popover.root( - rx.popover.trigger( - rx.button("Comment", variant="soft"), - ), - rx.popover.content( - rx.flex( - rx.avatar( - "2", - fallback="RX", - radius="full" - ), - rx.box( - rx.text_area(placeholder="Write a comment…", style={"height": 80}), - rx.flex( - rx.checkbox("Send to group"), - rx.popover.close( - rx.button("Comment", size="1") - ), - spacing="3", - margin_top="12px", - justify="between", - ), - flex_grow="1", - ), - spacing="3" - ), - style={"width": 360}, - ) -) -``` - -```python demo -rx.popover.root( - rx.popover.trigger( - rx.button("Feedback", variant="classic"), - ), - rx.popover.content( - rx.inset( - side="top", - background="url('https://images.unsplash.com/5/unsplash-kitsune-4.jpg') center/cover", - height="100px", - ), - rx.box( - rx.text_area(placeholder="Write a comment…", style={"height": 80}), - rx.flex( - rx.checkbox("Send to group"), - rx.popover.close( - rx.button("Comment", size="1") - ), - spacing="3", - margin_top="12px", - justify="between", - ), - padding_top="12px", - ), - style={"width": 360}, - ) -) -``` - -## Popover with dynamic title - -Code like below will not work as expected and it is necessary to place the dynamic title (`Index2State.language`) inside of an `rx.text` component. - -```python -class Index2State(rx.State): - language: str = "EN" - -def index() -> rx.Component: - return rx.popover.root( - rx.popover.trigger( - rx.button(Index2State.language), - ), - rx.popover.content( - rx.text('Success') - ) - ) -``` - -This code will work: - -```python demo exec -class Index2State(rx.State): - language: str = "EN" - -def index() -> rx.Component: - return rx.popover.root( - rx.popover.trigger( - rx.button( - rx.text(Index2State.language) - ), - ), - rx.popover.content( - rx.text('Success') - ) - ) -``` - -## Events when the Popover opens or closes - -The `on_open_change` event is called when the `open` state of the popover changes. It is used in conjunction with the `open` prop, which is passed to the event handler. - -```python demo exec -class PopoverState(rx.State): - num_opens: int = 0 - opened: bool = False - - @rx.event - def count_opens(self, value: bool): - self.opened = value - self.num_opens += 1 - - -def popover_example(): - return rx.flex( - rx.heading(f"Number of times popover opened or closed: {PopoverState.num_opens}"), - rx.heading(f"Popover open: {PopoverState.opened}"), - rx.popover.root( - rx.popover.trigger( - rx.button("Popover"), - ), - rx.popover.content( - rx.flex( - rx.text("Simple Example"), - rx.popover.close( - rx.button("Close"), - ), - direction="column", - spacing="3", - ), - ), - on_open_change=PopoverState.count_opens, - ), - direction="column", - spacing="3", - ) -``` diff --git a/docs/library/overlay/toast.md b/docs/library/overlay/toast.md deleted file mode 100644 index 7c5c666060..0000000000 --- a/docs/library/overlay/toast.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -components: - - rx.toast.provider ---- - -```python exec -import reflex as rx -``` - -# Toast - -A `rx.toast` is a non-blocking notification that disappears after a certain amount of time. It is often used to show a message to the user without interrupting their workflow. - -## Usage - -You can use `rx.toast` as an event handler for any component that triggers an action. - -```python demo -rx.button("Show Toast", on_click=rx.toast("Hello, World!")) -``` - -### Usage in State - -You can also use `rx.toast` in a state to show a toast when a specific action is triggered, using `yield`. - -```python demo exec -import asyncio -class ToastState(rx.State): - - @rx.event - async def fetch_data(self): - # Simulate fetching data for a 2-second delay - await asyncio.sleep(2) - # Shows a toast when the data is fetched - yield rx.toast("Data fetched!") - - -def render(): - return rx.button("Get Data", on_click=ToastState.fetch_data) -``` - -## Interaction - -If you want to interact with a toast, a few props are available to customize the behavior. - -By passing a `ToastAction` to the `action` or `cancel` prop, you can trigger an action when the toast is clicked or when it is closed. - -```python demo -rx.button("Show Toast", on_click=rx.toast("Hello, World!", duration=5000, close_button=True)) -``` - -### Presets - -`rx.toast` has some presets that you can use to show different types of toasts. - -```python demo -rx.hstack( - rx.button("Success", on_click=rx.toast.success("Success!"), color_scheme="green"), - rx.button("Error", on_click=rx.toast.error("Error!"), color_scheme="red"), - rx.button("Warning", on_click=rx.toast.warning("Warning!"), color_scheme="orange"), - rx.button("Info", on_click=rx.toast.info("Info!"), color_scheme="blue"), -) -``` - -### Customization - -If the presets don't fit your needs, you can customize the toasts by passing to `rx.toast` or to `rx.toast.options` some kwargs. - -```python demo -rx.button( - "Custom", - on_click=rx.toast( - "Custom Toast!", - position="top-right", - style={"background-color": "green", "color": "white", "border": "1px solid green", "border-radius": "0.53m"} - ) -) -``` - -The following props are available for customization: - -- `description`: `str | Var`: Toast's description, renders underneath the title. -- `close_button`: `bool`: Whether to show the close button. -- `invert`: `bool`: Dark toast in light mode and vice versa. -- `important`: `bool`: Control the sensitivity of the toast for screen readers. -- `duration`: `int`: Time in milliseconds that should elapse before automatically closing the toast. -- `position`: `LiteralPosition`: Position of the toast. -- `dismissible`: `bool`: If false, it'll prevent the user from dismissing the toast. -- `action`: `ToastAction`: Renders a primary button, clicking it will close the toast. -- `cancel`: `ToastAction`: Renders a secondary button, clicking it will close the toast. -- `id`: `str | Var`: Custom id for the toast. -- `unstyled`: `bool`: Removes the default styling, which allows for easier customization. -- `style`: `Style`: Custom style for the toast. -- `on_dismiss`: `Any`: The function gets called when either the close button is clicked, or the toast is swiped. -- `on_auto_close`: `Any`: Function that gets called when the toast disappears automatically after it's timeout (`duration` prop). - -## Toast Provider - -Using the `rx.toast` function require to have a toast provider in your app. - -`rx.toast.provider` is a component that provides a context for displaying toasts. It should be placed at the root of your app. - -```md alert warning -# In most case you will not need to include this component directly, as it is already included in `rx.app` as the `overlay_component` for displaying connections errors. -``` diff --git a/docs/library/overlay/tooltip.md b/docs/library/overlay/tooltip.md deleted file mode 100644 index 32b9993e6c..0000000000 --- a/docs/library/overlay/tooltip.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -components: - - rx.tooltip - -Tooltip: | - lambda **props: rx.tooltip( - rx.button("Hover over me"), - content="This is the tooltip content.", - **props, - ) ---- - -```python exec -import reflex as rx -``` - -# Tooltip - -A `tooltip` displays informative information when users hover over or focus on an element. - -It takes a `content` prop, which is the content associated with the tooltip. - -```python demo -rx.tooltip( - rx.button("Hover over me"), - content="This is the tooltip content.", -) -``` - -## Events when the Tooltip opens or closes - -The `on_open_change` event is called when the `open` state of the tooltip changes. It is used in conjunction with the `open` prop, which is passed to the event handler. - -```python demo exec -class TooltipState(rx.State): - num_opens: int = 0 - opened: bool = False - - @rx.event - def count_opens(self, value: bool): - self.opened = value - self.num_opens += 1 - - -def index(): - return rx.flex( - rx.heading(f"Number of times tooltip opened or closed: {TooltipState.num_opens}"), - rx.heading(f"Tooltip open: {TooltipState.opened}"), - rx.text( - "Hover over the button to see the tooltip.", - rx.tooltip( - rx.button("Hover over me"), - content="This is the tooltip content.", - on_open_change=TooltipState.count_opens, - ), - ), - direction="column", - spacing="3", - ) -``` diff --git a/docs/library/tables-and-data-grids/data_editor.md b/docs/library/tables-and-data-grids/data_editor.md deleted file mode 100644 index 5c98c858c3..0000000000 --- a/docs/library/tables-and-data-grids/data_editor.md +++ /dev/null @@ -1,356 +0,0 @@ ---- -components: - - rx.data_editor ---- - -# Data Editor - -A datagrid editor based on [Glide Data Grid](https://grid.glideapps.com/) - -```python exec -import reflex as rx -from pcweb.pages.docs import library -from typing import Any - -columns: list[dict[str, str]] = [ - { - "title":"Code", - "type": "str", - }, - { - "title":"Value", - "type": "int", - }, - { - "title":"Activated", - "type": "bool", - }, -] -data: list[list[Any]] = [ - ["A", 1, True], - ["B", 2, False], - ["C", 3, False], - ["D", 4, True], - ["E", 5, True], - ["F", 6, False], -] - -``` - -This component is introduced as an alternative to the [datatable]({library.tables_and_data_grids.data_table.path}) to support editing the displayed data. - -## Columns - -The columns definition should be a `list` of `dict`, each `dict` describing the associated columns. -Property of a column dict: - -- `title`: The text to display in the header of the column. -- `id`: An id for the column, if not defined, will default to a lower case of `title` -- `width`: The width of the column. -- `type`: The type of the columns, default to `"str"`. - -## Data - -The `data` props of `rx.data_editor` accept a `list` of `list`, where each `list` represent a row of data to display in the table. - -## Simple Example - -Here is a basic example of using the data_editor representing data with no interaction and no styling. Below we define the `columns` and the `data` which are taken in by the `rx.data_editor` component. When we define the `columns` we must define a `title` and a `type` for each column we create. The columns in the `data` must then match the defined `type` or errors will be thrown. - -```python demo box -rx.data_editor( - columns=columns, - data=data, -) -``` - -```python -columns: list[dict[str, str]] = [ - { - "title":"Code", - "type": "str", - }, - { - "title":"Value", - "type": "int", - }, - { - "title":"Activated", - "type": "bool", - }, -] -data: list[list[Any]] = [ - ["A", 1, True], - ["B", 2, False], - ["C", 3, False], - ["D", 4, True], - ["E", 5, True], - ["F", 6, False], -] -``` - -```python -rx.data_editor( - columns=columns, - data=data, -) -``` - -## Interactive Example - -```python exec -class DataEditorState_HP(rx.State): - - clicked_data: str = "Cell clicked: " - cols: list[Any] = [ - {"title": "Title", "type": "str"}, - { - "title": "Name", - "type": "str", - "group": "Data", - "width": 300, - }, - { - "title": "Birth", - "type": "str", - "id": "date", - "group": "Data", - "width": 150, - }, - { - "title": "Human", - "type": "bool", - "group": "Data", - "width": 80, - }, - { - "title": "House", - "type": "str", - "id": "date", - "group": "Data", - }, - { - "title": "Wand", - "type": "str", - "id": "date", - "group": "Data", - "width": 250, - }, - { - "title": "Patronus", - "type": "str", - "id": "date", - "group": "Data", - }, - { - "title": "Blood status", - "type": "str", - "id": "date", - "group": "Data", - "width": 200, - } - ] - - data = [ - ["1", "Harry James Potter", "31 July 1980", True, "Gryffindor", "11' Holly phoenix feather", "Stag", "Half-blood"], - ["2", "Ronald Bilius Weasley", "1 March 1980", True,"Gryffindor", "12' Ash unicorn tail hair", "Jack Russell terrier", "Pure-blood"], - ["3", "Hermione Jean Granger", "19 September, 1979", True, "Gryffindor", "10¾' vine wood dragon heartstring", "Otter", "Muggle-born"], - ["4", "Albus Percival Wulfric Brian Dumbledore", "Late August 1881", True, "Gryffindor", "15' Elder Thestral tail hair core", "Phoenix", "Half-blood"], - ["5", "Rubeus Hagrid", "6 December 1928", False, "Gryffindor", "16' Oak unknown core", "None", "Part-Human (Half-giant)"], - ["6", "Fred Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"], - ] - - def click_cell(self, pos): - col, row = pos - yield self.get_clicked_data(pos) - - - def get_clicked_data(self, pos) -> str: - self.clicked_data = f"Cell clicked: {pos}" - -``` - -Here we define a State, as shown below, that allows us to print the location of the cell as a heading when we click on it, using the `on_cell_clicked` `event trigger`. Check out all the other `event triggers` that you can use with datatable at the bottom of this page. We also define a `group` with a label `Data`. This groups all the columns with this `group` label under a larger group `Data` as seen in the table below. - -```python demo box -rx.heading(DataEditorState_HP.clicked_data) -``` - -```python demo box -rx.data_editor( - columns=DataEditorState_HP.cols, - data=DataEditorState_HP.data, - on_cell_clicked=DataEditorState_HP.click_cell, -) -``` - -```python -class DataEditorState_HP(rx.State): - - clicked_data: str = "Cell clicked: " - - cols: list[Any] = [ - { - "title": "Title", - "type": "str" - }, - { - "title": "Name", - "type": "str", - "group": "Data", - "width": 300, - }, - { - "title": "Birth", - "type": "str", - "group": "Data", - "width": 150, - }, - { - "title": "Human", - "type": "bool", - "group": "Data", - "width": 80, - }, - { - "title": "House", - "type": "str", - "group": "Data", - }, - { - "title": "Wand", - "type": "str", - "group": "Data", - "width": 250, - }, - { - "title": "Patronus", - "type": "str", - "group": "Data", - }, - { - "title": "Blood status", - "type": "str", - "group": "Data", - "width": 200, - } - ] - - data = [ - ["1", "Harry James Potter", "31 July 1980", True, "Gryffindor", "11' Holly phoenix feather", "Stag", "Half-blood"], - ["2", "Ronald Bilius Weasley", "1 March 1980", True,"Gryffindor", "12' Ash unicorn tail hair", "Jack Russell terrier", "Pure-blood"], - ["3", "Hermione Jean Granger", "19 September, 1979", True, "Gryffindor", "10¾' vine wood dragon heartstring", "Otter", "Muggle-born"], - ["4", "Albus Percival Wulfric Brian Dumbledore", "Late August 1881", True, "Gryffindor", "15' Elder Thestral tail hair core", "Phoenix", "Half-blood"], - ["5", "Rubeus Hagrid", "6 December 1928", False, "Gryffindor", "16' Oak unknown core", "None", "Part-Human (Half-giant)"], - ["6", "Fred Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"], - ] - - - def click_cell(self, pos): - col, row = pos - yield self.get_clicked_data(pos) - - - def get_clicked_data(self, pos) -> str: - self.clicked_data = f"Cell clicked: \{pos}" -``` - -```python -rx.data_editor( - columns=DataEditorState_HP.cols, - data=DataEditorState_HP.data, - on_cell_clicked=DataEditorState_HP.click_cell, -) -``` - -## Styling Example - -Now let's style our datatable to make it look more aesthetic and easier to use. We must first import `DataEditorTheme` and then we can start setting our style props as seen below in `dark_theme`. - -We then set these themes using `theme=DataEditorTheme(**dark_theme)`. On top of the styling we can also set some `props` to make some other aesthetic changes to our datatable. We have set the `row_height` to equal `50` so that the content is easier to read. We have also made the `smooth_scroll_x` and `smooth_scroll_y` equal `True` so that we can smoothly scroll along the columns and rows. Finally, we added `column_select=single`, where column select can take any of the following values `none`, `single` or `multiple`. - -```python exec -from reflex.components.datadisplay.dataeditor import DataEditorTheme -dark_theme = { - "accentColor": "#8c96ff", - "accentLight": "rgba(202, 206, 255, 0.253)", - "textDark": "#ffffff", - "textMedium": "#b8b8b8", - "textLight": "#a0a0a0", - "textBubble": "#ffffff", - "bgIconHeader": "#b8b8b8", - "fgIconHeader": "#000000", - "textHeader": "#a1a1a1", - "textHeaderSelected": "#000000", - "bgCell": "#16161b", - "bgCellMedium": "#202027", - "bgHeader": "#212121", - "bgHeaderHasFocus": "#474747", - "bgHeaderHovered": "#404040", - "bgBubble": "#212121", - "bgBubbleSelected": "#000000", - "bgSearchResult": "#423c24", - "borderColor": "rgba(225,225,225,0.2)", - "drilldownBorder": "rgba(225,225,225,0.4)", - "linkColor": "#4F5DFF", - "headerFontStyle": "bold 14px", - "baseFontStyle": "13px", - "fontFamily": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif", -} -``` - -```python demo box -rx.data_editor( - columns=DataEditorState_HP.cols, - data=DataEditorState_HP.data, - row_height=80, - smooth_scroll_x=True, - smooth_scroll_y=True, - column_select="single", - theme=DataEditorTheme(**dark_theme), - height="30vh", -) -``` - -```python -from reflex.components.datadisplay.dataeditor import DataEditorTheme -dark_theme_snake_case = { - "accent_color": "#8c96ff", - "accent_light": "rgba(202, 206, 255, 0.253)", - "text_dark": "#ffffff", - "text_medium": "#b8b8b8", - "text_light": "#a0a0a0", - "text_bubble": "#ffffff", - "bg_icon_header": "#b8b8b8", - "fg_icon_header": "#000000", - "text_header": "#a1a1a1", - "text_header_selected": "#000000", - "bg_cell": "#16161b", - "bg_cell_medium": "#202027", - "bg_header": "#212121", - "bg_header_has_focus": "#474747", - "bg_header_hovered": "#404040", - "bg_bubble": "#212121", - "bg_bubble_selected": "#000000", - "bg_search_result": "#423c24", - "border_color": "rgba(225,225,225,0.2)", - "drilldown_border": "rgba(225,225,225,0.4)", - "link_color": "#4F5DFF", - "header_font_style": "bold 14px", - "base_font_style": "13px", - "font_family": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif", -} -``` - -```python -rx.data_editor( - columns=DataEditorState_HP.cols, - data=DataEditorState_HP.data, - row_height=80, - smooth_scroll_x=True, - smooth_scroll_y=True, - column_select="single", - theme=DataEditorTheme(**dark_theme), - height="30vh", -) -``` diff --git a/docs/library/tables-and-data-grids/data_table.md b/docs/library/tables-and-data-grids/data_table.md deleted file mode 100644 index 539ef55925..0000000000 --- a/docs/library/tables-and-data-grids/data_table.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -components: - - rx.data_table ---- - -```python exec -import reflex as rx -from pcweb.pages.docs import library -``` - -# Data Table - -The data table component is a great way to display static data in a table format. -You can pass in a pandas dataframe to the data prop to create the table. - -In this example we will read data from a csv file, convert it to a pandas dataframe and display it in a data_table. - -We will also add a search, pagination, sorting to the data_table to make it more accessible. - -If you want to [add, edit or remove data]({library.tables_and_data_grids.table.path}) in your app or deal with anything but static data then the [`rx.table`]({library.tables_and_data_grids.table.path}) might be a better fit for your use case. - - -```python demo box -rx.data_table( - data=[ - ["Avery Bradley", "6-2", 25.0], - ["Jae Crowder", "6-6", 25.0], - ["John Holland", "6-5", 27.0], - ["R.J. Hunter", "6-5", 22.0], - ["Jonas Jerebko", "6-10", 29.0], - ["Amir Johnson", "6-9", 29.0], - ["Jordan Mickey", "6-8", 21.0], - ["Kelly Olynyk", "7-0", 25.0], - ["Terry Rozier", "6-2", 22.0], - ["Marcus Smart", "6-4", 22.0], - ], - columns=["Name", "Height", "Age"], - pagination=True, - search=True, - sort=True, -) -``` - -```python -import pandas as pd -nba_data = pd.read_csv("data/nba.csv") -... -rx.data_table( - data = nba_data[["Name", "Height", "Age"]], - pagination= True, - search= True, - sort= True, -) -``` - -📊 **Dataset source:** [nba.csv](https://media.geeksforgeeks.org/wp-content/uploads/nba.csv) - -The example below shows how to create a data table from from a list. - -```python -class State(rx.State): - data: List = [ - ["Lionel", "Messi", "PSG"], - ["Christiano", "Ronaldo", "Al-Nasir"] - ] - columns: List[str] = ["First Name", "Last Name"] - -def index(): - return rx.data_table( - data=State.data, - columns=State.columns, - ) -``` diff --git a/docs/library/tables-and-data-grids/table.md b/docs/library/tables-and-data-grids/table.md deleted file mode 100644 index 8e2509100a..0000000000 --- a/docs/library/tables-and-data-grids/table.md +++ /dev/null @@ -1,1206 +0,0 @@ ---- -components: - - rx.table.root - - rx.table.header - - rx.table.row - - rx.table.column_header_cell - - rx.table.body - - rx.table.cell - - rx.table.row_header_cell - -only_low_level: - - True - -TableRoot: | - lambda **props: rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Full Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Group"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.row_header_cell("Danilo Rosa"), - rx.table.cell("danilo@example.com"), - rx.table.cell("Developer"), - ), - rx.table.row( - rx.table.row_header_cell("Zahra Ambessa"), - rx.table.cell("zahra@example.com"), - rx.table.cell("Admin"), - ), - ), - width="80%", - **props, - ) - -TableRow: | - lambda **props: rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Full Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Group"), - **props, - ), - ), - rx.table.body( - rx.table.row( - rx.table.row_header_cell("Danilo Rosa"), - rx.table.cell(rx.text("danilo@example.com", as_="p"), rx.text("danilo@yahoo.com", as_="p"), rx.text("danilo@gmail.com", as_="p"),), - rx.table.cell("Developer"), - **props, - ), - rx.table.row( - rx.table.row_header_cell("Zahra Ambessa"), - rx.table.cell("zahra@example.com"), - rx.table.cell("Admin"), - **props, - ), - ), - width="80%", - ) - -TableColumnHeaderCell: | - lambda **props: rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Full Name", **props,), - rx.table.column_header_cell("Email", **props,), - rx.table.column_header_cell("Group", **props,), - ), - ), - rx.table.body( - rx.table.row( - rx.table.row_header_cell("Danilo Rosa"), - rx.table.cell("danilo@example.com"), - rx.table.cell("Developer"), - ), - rx.table.row( - rx.table.row_header_cell("Zahra Ambessa"), - rx.table.cell("zahra@example.com"), - rx.table.cell("Admin"), - ), - ), - width="80%", - ) - -TableCell: | - lambda **props: rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Full Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Group"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.row_header_cell("Danilo Rosa"), - rx.table.cell("danilo@example.com", **props,), - rx.table.cell("Developer", **props,), - ), - rx.table.row( - rx.table.row_header_cell("Zahra Ambessa"), - rx.table.cell("zahra@example.com", **props,), - rx.table.cell("Admin", **props,), - ), - ), - width="80%", - ) - -TableRowHeaderCell: | - lambda **props: rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Full Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Group"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.row_header_cell("Danilo Rosa", **props,), - rx.table.cell("danilo@example.com"), - rx.table.cell("Developer"), - ), - rx.table.row( - rx.table.row_header_cell("Zahra Ambessa", **props,), - rx.table.cell("zahra@example.com"), - rx.table.cell("Admin"), - ), - ), - width="80%", - ) ---- - -```python exec -import reflex as rx -from pcweb.models import Customer -from pcweb.pages.docs import vars, events, database, library, components -``` - -# Table - -A semantic table for presenting tabular data. - -If you just want to [represent static data]({library.tables_and_data_grids.data_table.path}) then the [`rx.data_table`]({library.tables_and_data_grids.data_table.path}) might be a better fit for your use case as it comes with in-built pagination, search and sorting. - -## Basic Example - -```python demo -rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Full name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Group"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.row_header_cell("Danilo Sousa"), - rx.table.cell("danilo@example.com"), - rx.table.cell("Developer"), - ), - rx.table.row( - rx.table.row_header_cell("Zahra Ambessa"), - rx.table.cell("zahra@example.com"), - rx.table.cell("Admin"), - ),rx.table.row( - rx.table.row_header_cell("Jasper Eriks"), - rx.table.cell("jasper@example.com"), - rx.table.cell("Developer"), - ), - ), - width="100%", -) -``` - -```md alert info -# Set the table `width` to fit within its container and prevent it from overflowing. -``` - -## Showing State data (using foreach) - -Many times there is a need for the data we represent in our table to be dynamic. Dynamic data must be in `State`. Later we will show an example of how to access data from a database and how to load data from a source file. - -In this example there is a `people` data structure in `State` that is [iterated through using `rx.foreach`]({components.rendering_iterables.path}). - -```python demo exec -class TableForEachState(rx.State): - people: list[list] = [ - ["Danilo Sousa", "danilo@example.com", "Developer"], - ["Zahra Ambessa", "zahra@example.com", "Admin"], - ["Jasper Eriks", "jasper@example.com", "Developer"], - ] - -def show_person(person: list): - """Show a person in a table row.""" - return rx.table.row( - rx.table.cell(person[0]), - rx.table.cell(person[1]), - rx.table.cell(person[2]), - ) - -def foreach_table_example(): - return rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Full name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Group"), - ), - ), - rx.table.body(rx.foreach(TableForEachState.people, show_person)), - width="100%", - ) -``` - -It is also possible to define a `class` such as `Person` below and then iterate through this data structure, as a `list[Person]`. - -```python -import dataclasses - -@dataclasses.dataclass -class Person: - full_name: str - email: str - group: str -``` -## Sorting and Filtering (Searching) - -In this example we show two approaches to sort and filter data: -1. Using SQL-like operations for database-backed models (simulated) -2. Using Python operations for in-memory data - -Both approaches use the same UI components: `rx.select` for sorting and `rx.input` for filtering. - -### Approach 1: Database Filtering and Sorting - -For database-backed models, we typically use SQL queries with `select`, `where`, and `order_by`. In this example, we'll simulate this behavior with mock data. - - -```python demo exec -# Simulating database operations with mock data -class DatabaseTableState(rx.State): - # Mock data to simulate database records - users: list = [ - {"name": "John Doe", "email": "john@example.com", "phone": "555-1234", "address": "123 Main St"}, - {"name": "Jane Smith", "email": "jane@example.com", "phone": "555-5678", "address": "456 Oak Ave"}, - {"name": "Bob Johnson", "email": "bob@example.com", "phone": "555-9012", "address": "789 Pine Rd"}, - {"name": "Alice Brown", "email": "alice@example.com", "phone": "555-3456", "address": "321 Maple Dr"}, - ] - filtered_users: list[dict] = [] - sort_value = "" - search_value = "" - - - @rx.event - def load_entries(self): - """Simulate querying the database with filter and sort.""" - # Start with all users - result = self.users.copy() - - # Apply filtering if search value exists - if self.search_value != "": - search_term = self.search_value.lower() - result = [ - user for user in result - if any(search_term in str(value).lower() for value in user.values()) - ] - - # Apply sorting if sort column is selected - if self.sort_value != "": - result = sorted(result, key=lambda x: x[self.sort_value]) - - self.filtered_users = result - yield - - @rx.event - def sort_values(self, sort_value): - """Update sort value and reload data.""" - self.sort_value = sort_value - yield DatabaseTableState.load_entries() - - @rx.event - def filter_values(self, search_value): - """Update search value and reload data.""" - self.search_value = search_value - yield DatabaseTableState.load_entries() - - -def show_customer(user): - """Show a customer in a table row.""" - return rx.table.row( - rx.table.cell(user["name"]), - rx.table.cell(user["email"]), - rx.table.cell(user["phone"]), - rx.table.cell(user["address"]), - ) - - -def database_table_example(): - return rx.vstack( - rx.select( - ["name", "email", "phone", "address"], - placeholder="Sort By: Name", - on_change=lambda value: DatabaseTableState.sort_values(value), - ), - rx.input( - placeholder="Search here...", - on_change=lambda value: DatabaseTableState.filter_values(value), - ), - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Phone"), - rx.table.column_header_cell("Address"), - ), - ), - rx.table.body(rx.foreach(DatabaseTableState.filtered_users, show_customer)), - on_mount=DatabaseTableState.load_entries, - width="100%", - ), - width="100%", - ) -``` - -### Approach 2: In-Memory Filtering and Sorting - -For in-memory data, we use Python operations like `sorted()` and list comprehensions. - -The state variable `_people` is set to be a backend-only variable. This is done in case the variable is very large in order to reduce network traffic and improve performance. - -When a `select` item is selected, the `on_change` event trigger is hooked up to the `set_sort_value` event handler. Every base var has a built-in event handler to set its value for convenience, called `set_VARNAME`. - -`current_people` is an `rx.var(cache=True)`. It is a var that is only recomputed when the other state vars it depends on change. This ensures that the `People` shown in the table are always up to date whenever they are searched or sorted. - -```python demo exec -import dataclasses - -@dataclasses.dataclass -class Person: - full_name: str - email: str - group: str - - -class InMemoryTableState(rx.State): - - _people: list[Person] = [ - Person(full_name="Danilo Sousa", email="danilo@example.com", group="Developer"), - Person(full_name="Zahra Ambessa", email="zahra@example.com", group="Admin"), - Person(full_name="Jasper Eriks", email="zjasper@example.com", group="B-Developer"), - ] - - sort_value = "" - search_value = "" - - @rx.event - def set_sort_value(self, value: str): - self.sort_value = value - - @rx.event - def set_search_value(self, value: str): - self.search_value = value - - @rx.var(cache=True) - def current_people(self) -> list[Person]: - people = self._people - - if self.sort_value != "": - people = sorted( - people, key=lambda user: getattr(user, self.sort_value).lower() - ) - - if self.search_value != "": - people = [ - person for person in people - if any( - self.search_value.lower() in getattr(person, attr).lower() - for attr in ['full_name', 'email', 'group'] - ) - ] - return people - - -def show_person(person: Person): - """Show a person in a table row.""" - return rx.table.row( - rx.table.cell(person.full_name), - rx.table.cell(person.email), - rx.table.cell(person.group), - ) - -def in_memory_table_example(): - return rx.vstack( - rx.select( - ["full_name", "email", "group"], - placeholder="Sort By: full_name", - on_change=InMemoryTableState.set_sort_value, - ), - rx.input( - placeholder="Search here...", - on_change=InMemoryTableState.set_search_value, - ), - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Full name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Group"), - ), - ), - rx.table.body(rx.foreach(InMemoryTableState.current_people, show_person)), - width="100%", - ), - width="100%", - ) -``` - -### When to Use Each Approach - -- **Database Approach**: Best for large datasets or when the data already exists in a database -- **In-Memory Approach**: Best for smaller datasets, prototyping, or when the data is static or loaded from an API - -Both approaches provide the same user experience with filtering and sorting functionality. - -# Database - -The more common use case for building an `rx.table` is to use data from a database. - -The code below shows how to load data from a database and place it in an `rx.table`. - -## Loading data into table - -A `Customer` [model]({database.tables.path}) is defined that inherits from `rx.Model`. - -The `load_entries` event handler executes a [query]({database.queries.path}) that is used to request information from a database table. This `load_entries` event handler is called on the `on_mount` event trigger of the `rx.table.root`. - -If you want to load the data when the page in the app loads you can set `on_load` in `app.add_page()` to equal this event handler, like `app.add_page(page_name, on_load=State.load_entries)`. - -```python -class Customer(rx.Model, table=True): - """The customer model.""" - name: str - email: str - phone: str - address: str -``` - -```python exec -class DatabaseTableState(rx.State): - - users: list[dict] = [] - - @rx.event - def load_entries(self): - """Get all users from the database.""" - customers_json = [ - { - "name": "John Doe", - "email": "john@example.com", - "phone": "555-1234", - "address": "123 Main St" - }, - { - "name": "Jane Smith", - "email": "jane@example.com", - "phone": "555-5678", - "address": "456 Oak Ave" - }, - { - "name": "Bob Johnson", - "email": "bob@example.com", - "phone": "555-9012", - "address": "789 Pine Blvd" - }, - { - "name": "Alice Williams", - "email": "alice@example.com", - "phone": "555-3456", - "address": "321 Maple Dr" - } - ] - self.users = customers_json - -def show_customer(user: dict): - return rx.table.row( - rx.table.cell(user["name"]), - rx.table.cell(user["email"]), - rx.table.cell(user["phone"]), - rx.table.cell(user["address"]), - ) - -def loading_data_table_example(): - return rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Phone"), - rx.table.column_header_cell("Address"), - ), - ), - rx.table.body(rx.foreach(DatabaseTableState.users, show_customer)), - on_mount=DatabaseTableState.load_entries, - width="100%", - margin_bottom="1em", -) -``` - -```python eval -loading_data_table_example() -``` - -```python -from sqlmodel import select - -class DatabaseTableState(rx.State): - - users: list[Customer] = [] - - @rx.event - def load_entries(self): - """Get all users from the database.""" - with rx.session() as session: - self.users = session.exec(select(Customer)).all() - - -def show_customer(user: Customer): - """Show a customer in a table row.""" - return rx.table.row( - rx.table.cell(user.name), - rx.table.cell(user.email), - rx.table.cell(user.phone), - rx.table.cell(user.address), - ) - -def loading_data_table_example(): - return rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Phone"), - rx.table.column_header_cell("Address"), - ), - ), - rx.table.body(rx.foreach(DatabaseTableState.users, show_customer)), - on_mount=DatabaseTableState.load_entries, - width="100%", - ) - -``` - -## Filtering (Searching) and Sorting - -In this example we sort and filter the data. - -For sorting the `rx.select` component is used. The data is sorted based on the attributes of the `Customer` class. When a select item is selected, as the `on_change` event trigger is hooked up to the `sort_values` event handler, the data is sorted based on the state variable `sort_value` attribute selected. - -The sorting query gets the `sort_column` based on the state variable `sort_value`, it gets the order using the `asc` function from sql and finally uses the `order_by` function. - -For filtering the `rx.input` component is used. The data is filtered based on the search query entered into the `rx.input` component. When a search query is entered, as the `on_change` event trigger is hooked up to the `filter_values` event handler, the data is filtered based on if the state variable `search_value` is present in any of the data in that specific `Customer`. - -The `%` character before and after `search_value` makes it a wildcard pattern that matches any sequence of characters before or after the `search_value`. `query.where(...)` modifies the existing query to include a filtering condition. The `or_` operator is a logical OR operator that combines multiple conditions. The query will return results that match any of these conditions. `Customer.name.ilike(search_value)` checks if the `name` column of the `Customer` table matches the `search_value` pattern in a case-insensitive manner (`ilike` stands for "case-insensitive like"). - -```python -class Customer(rx.Model, table=True): - """The customer model.""" - - name: str - email: str - phone: str - address: str -``` - -```python exec -class DatabaseTableState2(rx.State): - - # Mock data to simulate database records - _users: list[dict] = [ - {"name": "John Doe", "email": "john@example.com", "phone": "555-1234", "address": "123 Main St"}, - {"name": "Jane Smith", "email": "jane@example.com", "phone": "555-5678", "address": "456 Oak Ave"}, - {"name": "Bob Johnson", "email": "bob@example.com", "phone": "555-9012", "address": "789 Pine Rd"}, - {"name": "Alice Brown", "email": "alice@example.com", "phone": "555-3456", "address": "321 Maple Dr"}, - {"name": "Charlie Wilson", "email": "charlie@example.com", "phone": "555-7890", "address": "654 Elm St"}, - {"name": "Emily Davis", "email": "emily@example.com", "phone": "555-2345", "address": "987 Cedar Ln"}, - ] - - users: list[dict] = [] - sort_value = "" - search_value = "" - - @rx.event - def load_entries(self): - # Start with all users - result = self._users.copy() - - # Apply filtering if search value exists - if self.search_value != "": - search_term = self.search_value.lower() - result = [ - user for user in result - if any(search_term in str(value).lower() for value in user.values()) - ] - - # Apply sorting if sort column is selected - if self.sort_value != "": - result = sorted(result, key=lambda x: x[self.sort_value]) - - self.users = result - - @rx.event - def sort_values(self, sort_value): - self.sort_value = sort_value - yield DatabaseTableState2.load_entries() - - @rx.event - def filter_values(self, search_value): - self.search_value = search_value - yield DatabaseTableState2.load_entries() - - -def show_customer_2(user: dict): - return rx.table.row( - rx.table.cell(user["name"]), - rx.table.cell(user["email"]), - rx.table.cell(user["phone"]), - rx.table.cell(user["address"]), - ) - -def loading_data_table_example_2(): - return rx.vstack( - rx.select( - ["name", "email", "phone", "address"], - placeholder="Sort By: Name", - on_change= lambda value: DatabaseTableState2.sort_values(value), - ), - rx.input( - placeholder="Search here...", - on_change= lambda value: DatabaseTableState2.filter_values(value), - ), - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Phone"), - rx.table.column_header_cell("Address"), - ), - ), - rx.table.body(rx.foreach(DatabaseTableState2.users, show_customer_2)), - on_mount=DatabaseTableState2.load_entries, - width="100%", - ), - width="100%", - margin_bottom="1em", -) -``` - -```python eval -loading_data_table_example_2() -``` - -```python -from sqlmodel import select, asc, or_ - - -class DatabaseTableState2(rx.State): - - users: list[Customer] = [] - - sort_value = "" - search_value = "" - - @rx.event - def load_entries(self): - """Get all users from the database.""" - with rx.session() as session: - query = select(Customer) - - if self.search_value != "": - search_value = self.search_value.lower() - query = query.where( - or_( - Customer.name.ilike(search_value), - Customer.email.ilike(search_value), - Customer.phone.ilike(search_value), - Customer.address.ilike(search_value), - ) - ) - - if self.sort_value != "": - sort_column = getattr(Customer, self.sort_value) - order = asc(sort_column) - query = query.order_by(order) - - self.users = session.exec(query).all() - - @rx.event - def sort_values(self, sort_value): - print(sort_value) - self.sort_value = sort_value - self.load_entries() - - @rx.event - def filter_values(self, search_value): - print(search_value) - self.search_value = search_value - self.load_entries() - - -def show_customer(user: Customer): - """Show a customer in a table row.""" - return rx.table.row( - rx.table.cell(user.name), - rx.table.cell(user.email), - rx.table.cell(user.phone), - rx.table.cell(user.address), - ) - - -def loading_data_table_example2(): - return rx.vstack( - rx.select( - ["name", "email", "phone", "address"], - placeholder="Sort By: Name", - on_change= lambda value: DatabaseTableState2.sort_values(value), - ), - rx.input( - placeholder="Search here...", - on_change= lambda value: DatabaseTableState2.filter_values(value), - ), - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Phone"), - rx.table.column_header_cell("Address"), - ), - ), - rx.table.body(rx.foreach(DatabaseTableState2.users, show_customer)), - on_mount=DatabaseTableState2.load_entries, - width="100%", - ), - width="100%", - ) - -``` - - -## Pagination - -Pagination is an important part of database management, especially when working with large datasets. It helps in enabling efficient data retrieval by breaking down results into manageable loads. - -The purpose of this code is to retrieve a specific subset of rows from the `Customer` table based on the specified pagination parameters `offset` and `limit`. - -`query.offset(self.offset)` modifies the query to skip a certain number of rows before returning the results. The number of rows to skip is specified by `self.offset`. - -`query.limit(self.limit)` modifies the query to limit the number of rows returned. The maximum number of rows to return is specified by `self.limit`. - -```python exec -class DatabaseTableState3(rx.State): - - _mock_data: list[Customer] = [ - Customer(name="John Doe", email="john@example.com", phone="555-1234", address="123 Main St"), - Customer(name="Jane Smith", email="jane@example.com", phone="555-5678", address="456 Oak Ave"), - Customer(name="Bob Johnson", email="bob@example.com", phone="555-9012", address="789 Pine Rd"), - Customer(name="Alice Brown", email="alice@example.com", phone="555-3456", address="321 Maple Dr"), - Customer(name="Charlie Wilson", email="charlie@example.com", phone="555-7890", address="654 Elm St"), - Customer(name="Emily Davis", email="emily@example.com", phone="555-2345", address="987 Cedar Ln"), - ] - users: list[Customer] = [] - - total_items: int - offset: int = 0 - limit: int = 3 - - @rx.var(cache=True) - def page_number(self) -> int: - return ( - (self.offset // self.limit) - + 1 - + (1 if self.offset % self.limit else 0) - ) - - @rx.var(cache=True) - def total_pages(self) -> int: - return self.total_items // self.limit + ( - 1 if self.total_items % self.limit else 0 - ) - - @rx.event - def prev_page(self): - self.offset = max(self.offset - self.limit, 0) - self.load_entries() - - @rx.event - def next_page(self): - if self.offset + self.limit < self.total_items: - self.offset += self.limit - self.load_entries() - - def _get_total_items(self, session): - self.total_items = session.exec(select(func.count(Customer.id))).one() - - @rx.event - def load_entries(self): - self.users = self._mock_data[self.offset:self.offset + self.limit] - self.total_items = len(self._mock_data) - - -def show_customer(user: Customer): - return rx.table.row( - rx.table.cell(user.name), - rx.table.cell(user.email), - rx.table.cell(user.phone), - rx.table.cell(user.address), - ) - - -def loading_data_table_example3(): - return rx.vstack( - rx.hstack( - rx.button( - "Prev", - on_click=DatabaseTableState3.prev_page, - ), - rx.text( - f"Page {DatabaseTableState3.page_number} / {DatabaseTableState3.total_pages}" - ), - rx.button( - "Next", - on_click=DatabaseTableState3.next_page, - ), - ), - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Phone"), - rx.table.column_header_cell("Address"), - ), - ), - rx.table.body(rx.foreach(DatabaseTableState3.users, show_customer)), - on_mount=DatabaseTableState3.load_entries, - width="100%", - ), - width="100%", - margin_bottom="1em", - ) -``` - -```python eval -loading_data_table_example3() -``` - -```python -from sqlmodel import select, func - - -class DatabaseTableState3(rx.State): - - users: list[Customer] = [] - - total_items: int - offset: int = 0 - limit: int = 3 - - @rx.var(cache=True) - def page_number(self) -> int: - return ( - (self.offset // self.limit) - + 1 - + (1 if self.offset % self.limit else 0) - ) - - @rx.var(cache=True) - def total_pages(self) -> int: - return self.total_items // self.limit + ( - 1 if self.total_items % self.limit else 0 - ) - - @rx.event - def prev_page(self): - self.offset = max(self.offset - self.limit, 0) - self.load_entries() - - @rx.event - def next_page(self): - if self.offset + self.limit < self.total_items: - self.offset += self.limit - self.load_entries() - - def _get_total_items(self, session): - """Return the total number of items in the Customer table.""" - self.total_items = session.exec(select(func.count(Customer.id))).one() - - @rx.event - def load_entries(self): - """Get all users from the database.""" - with rx.session() as session: - query = select(Customer) - - # Apply pagination - query = query.offset(self.offset).limit(self.limit) - - self.users = session.exec(query).all() - self._get_total_items(session) - - -def show_customer(user: Customer): - """Show a customer in a table row.""" - return rx.table.row( - rx.table.cell(user.name), - rx.table.cell(user.email), - rx.table.cell(user.phone), - rx.table.cell(user.address), - ) - - -def loading_data_table_example3(): - return rx.vstack( - rx.hstack( - rx.button( - "Prev", - on_click=DatabaseTableState3.prev_page, - ), - rx.text( - f"Page {DatabaseTableState3.page_number} / {DatabaseTableState3.total_pages}" - ), - rx.button( - "Next", - on_click=DatabaseTableState3.next_page, - ), - ), - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Phone"), - rx.table.column_header_cell("Address"), - ), - ), - rx.table.body(rx.foreach(DatabaseTableState3.users, show_customer)), - on_mount=DatabaseTableState3.load_entries, - width="100%", - ), - width="100%", - ) - -``` -## More advanced examples - -The real power of the `rx.table` comes where you are able to visualise, add and edit data live in your app. Check out these apps and code to see how this is done: app: https://customer-data-app.reflex.run code: https://github.com/reflex-dev/templates/tree/main/customer_data_app and code: https://github.com/reflex-dev/templates/tree/main/sales. - -# Download - -Most users will want to download their data after they have got the subset that they would like in their table. - -In this example there are buttons to download the data as a `json` and as a `csv`. - -For the `json` download the `rx.download` is in the frontend code attached to the `on_click` event trigger for the button. This works because if the `Var` is not already a string, it will be converted to a string using `JSON.stringify`. - -For the `csv` download the `rx.download` is in the backend code as an event_handler `download_csv_data`. There is also a helper function `_convert_to_csv` that converts the data in `self.users` to `csv` format. - -```python exec -import io -import csv -import json - -class TableDownloadState(rx.State): - _mock_data: list[Customer] = [ - Customer(name="John Doe", email="john@example.com", phone="555-1234", address="123 Main St"), - Customer(name="Jane Smith", email="jane@example.com", phone="555-5678", address="456 Oak Ave"), - Customer(name="Bob Johnson", email="bob@example.com", phone="555-9012", address="789 Pine Rd"), - ] - users: list[Customer] = [] - - @rx.event - def load_entries(self): - self.users = self._mock_data - - def _convert_to_csv(self) -> str: - if not self.users: - self.load_entries() - - fieldnames = ["id", "name", "email", "phone", "address"] - output = io.StringIO() - writer = csv.DictWriter(output, fieldnames=fieldnames) - writer.writeheader() - for user in self.users: - writer.writerow(user.dict()) - - csv_data = output.getvalue() - output.close() - return csv_data - - - def _convert_to_json(self) -> str: - return json.dumps([u.dict() for u in self._mock_data], indent=2) - - @rx.event - def download_csv_data(self): - csv_data = self._convert_to_csv() - return rx.download( - data=csv_data, - filename="data.csv", - ) - - @rx.event - def download_json_data(self): - json_data = self._convert_to_json() - return rx.download( - data=json_data, - filename="data.json", - ) - -def show_customer(user: Customer): - return rx.table.row( - rx.table.cell(user.name), - rx.table.cell(user.email), - rx.table.cell(user.phone), - rx.table.cell(user.address), - ) - -def download_data_table_example(): - return rx.vstack( - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Phone"), - rx.table.column_header_cell("Address"), - ), - ), - rx.table.body(rx.foreach(TableDownloadState.users, show_customer)), - width="100%", - on_mount=TableDownloadState.load_entries, - ), - rx.hstack( - rx.button( - "Download as JSON", - on_click=TableDownloadState.download_json_data, - ), - rx.button( - "Download as CSV", - on_click=TableDownloadState.download_csv_data, - ), - spacing="7", - ), - width="100%", - spacing="5", - margin_bottom="1em", - ) - -``` - -```python eval -download_data_table_example() -``` - -```python -import io -import csv -from sqlmodel import select - -class TableDownloadState(rx.State): - - users: list[Customer] = [] - - @rx.event - def load_entries(self): - """Get all users from the database.""" - with rx.session() as session: - self.users = session.exec(select(Customer)).all() - - - def _convert_to_csv(self) -> str: - """Convert the users data to CSV format.""" - - # Make sure to load the entries first - if not self.users: - self.load_entries() - - # Define the CSV file header based on the Customer model's attributes - fieldnames = list(Customer.__fields__) - - # Create a string buffer to hold the CSV data - output = io.StringIO() - writer = csv.DictWriter(output, fieldnames=fieldnames) - writer.writeheader() - for user in self.users: - writer.writerow(user.dict()) - - # Get the CSV data as a string - csv_data = output.getvalue() - output.close() - return csv_data - - @rx.event - def download_csv_data(self): - csv_data = self._convert_to_csv() - return rx.download( - data=csv_data, - filename="data.csv", - ) - - -def show_customer(user: Customer): - """Show a customer in a table row.""" - return rx.table.row( - rx.table.cell(user.name), - rx.table.cell(user.email), - rx.table.cell(user.phone), - rx.table.cell(user.address), - ) - -def download_data_table_example(): - return rx.vstack( - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Email"), - rx.table.column_header_cell("Phone"), - rx.table.column_header_cell("Address"), - ), - ), - rx.table.body(rx.foreach(TableDownloadState.users, show_customer)), - width="100%", - on_mount=TableDownloadState.load_entries, - ), - rx.hstack( - rx.button( - "Download as JSON", - on_click=rx.download( - data=TableDownloadState.users, - filename="data.json", - ), - ), - rx.button( - "Download as CSV", - on_click=TableDownloadState.download_csv_data, - ), - spacing="7", - ), - width="100%", - spacing="5", - ) - -``` - -# Real World Example UI - -```python demo -rx.flex( - rx.heading("Your Team"), - rx.text("Invite and manage your team members"), - rx.flex( - rx.input(placeholder="Email Address"), - rx.button("Invite"), - justify="center", - spacing="2", - ), - rx.table.root( - rx.table.body( - rx.table.row( - rx.table.cell(rx.avatar(fallback="DS")), - rx.table.row_header_cell(rx.link("Danilo Sousa")), - rx.table.cell("danilo@example.com"), - rx.table.cell("Developer"), - align="center", - ), - rx.table.row( - rx.table.cell(rx.avatar(fallback="ZA")), - rx.table.row_header_cell(rx.link("Zahra Ambessa")), - rx.table.cell("zahra@example.com"), - rx.table.cell("Admin"), - align="center", - ), - rx.table.row( - rx.table.cell(rx.avatar(fallback="JE")), - rx.table.row_header_cell(rx.link("Jasper Eriksson")), - rx.table.cell("jasper@example.com"), - rx.table.cell("Developer"), - align="center", - ), - ), - width="100%", - ), - width="100%", - direction="column", - spacing="2", -) -``` diff --git a/docs/library/typography/blockquote.md b/docs/library/typography/blockquote.md deleted file mode 100644 index 5070c1d002..0000000000 --- a/docs/library/typography/blockquote.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -components: - - rx.blockquote ---- - -```python exec -import reflex as rx -``` - -# Blockquote - -```python demo -rx.blockquote("Perfect typography is certainly the most elusive of all arts.") -``` - -## Size - -Use the `size` prop to control the size of the blockquote. The prop also provides correct line height and corrective letter spacing—as text size increases, the relative line height and letter spacing decrease. - -```python demo -rx.flex( - rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="1"), - rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="2"), - rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="3"), - rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="4"), - rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="5"), - rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="6"), - rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="7"), - rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="8"), - rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="9"), - direction="column", - spacing="3", -) -``` - -## Weight - -Use the `weight` prop to set the blockquote weight. - -```python demo -rx.flex( - rx.blockquote("Perfect typography is certainly the most elusive of all arts.", weight="light"), - rx.blockquote("Perfect typography is certainly the most elusive of all arts.", weight="regular"), - rx.blockquote("Perfect typography is certainly the most elusive of all arts.", weight="medium"), - rx.blockquote("Perfect typography is certainly the most elusive of all arts.", weight="bold"), - direction="column", - spacing="3", -) -``` - -## Color - -Use the `color_scheme` prop to assign a specific color, ignoring the global theme. - -```python demo -rx.flex( - rx.blockquote("Perfect typography is certainly the most elusive of all arts.", color_scheme="indigo"), - rx.blockquote("Perfect typography is certainly the most elusive of all arts.", color_scheme="cyan"), - rx.blockquote("Perfect typography is certainly the most elusive of all arts.", color_scheme="crimson"), - rx.blockquote("Perfect typography is certainly the most elusive of all arts.", color_scheme="orange"), - direction="column", - spacing="3", -) -``` - -## High Contrast - -Use the `high_contrast` prop to increase color contrast with the background. - -```python demo -rx.flex( - rx.blockquote("Perfect typography is certainly the most elusive of all arts."), - rx.blockquote("Perfect typography is certainly the most elusive of all arts.", high_contrast=True), - direction="column", - spacing="3", -) -``` diff --git a/docs/library/typography/code.md b/docs/library/typography/code.md deleted file mode 100644 index e823a2cdf4..0000000000 --- a/docs/library/typography/code.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -components: - - rx.code ---- - -```python exec -import reflex as rx -``` - -# Code - -```python demo -rx.code("console.log()") -``` - -## Size - -Use the `size` prop to control text size. This prop also provides correct line height and corrective letter spacing—as text size increases, the relative line height and letter spacing decrease. - -```python demo -rx.flex( - rx.code("console.log()", size="1"), - rx.code("console.log()", size="2"), - rx.code("console.log()", size="3"), - rx.code("console.log()", size="4"), - rx.code("console.log()", size="5"), - rx.code("console.log()", size="6"), - rx.code("console.log()", size="7"), - rx.code("console.log()", size="8"), - rx.code("console.log()", size="9"), - direction="column", - spacing="3", - align="start", -) -``` - -## Weight - -Use the `weight` prop to set the text weight. - -```python demo -rx.flex( - rx.code("console.log()", weight="light"), - rx.code("console.log()", weight="regular"), - rx.code("console.log()", weight="medium"), - rx.code("console.log()", weight="bold"), - direction="column", - spacing="3", -) -``` - -## Variant - -Use the `variant` prop to control the visual style. - -```python demo -rx.flex( - rx.code("console.log()", variant="solid"), - rx.code("console.log()", variant="soft"), - rx.code("console.log()", variant="outline"), - rx.code("console.log()", variant="ghost"), - direction="column", - spacing="2", - align="start", -) -``` - -## Color - -Use the `color_scheme` prop to assign a specific color, ignoring the global theme. - -```python demo -rx.flex( - rx.code("console.log()", color_scheme="indigo"), - rx.code("console.log()", color_scheme="crimson"), - rx.code("console.log()", color_scheme="orange"), - rx.code("console.log()", color_scheme="cyan"), - direction="column", - spacing="2", - align="start", -) -``` - -## High Contrast - -Use the `high_contrast` prop to increase color contrast with the background. - -```python demo -rx.flex( - rx.flex( - rx.code("console.log()", variant="solid"), - rx.code("console.log()", variant="soft"), - rx.code("console.log()", variant="outline"), - rx.code("console.log()", variant="ghost"), - direction="column", - align="start", - spacing="2", - ), - rx.flex( - rx.code("console.log()", variant="solid", high_contrast=True), - rx.code("console.log()", variant="soft", high_contrast=True), - rx.code("console.log()", variant="outline", high_contrast=True), - rx.code("console.log()", variant="ghost", high_contrast=True), - direction="column", - align="start", - spacing="2", - ), - spacing="3", -) -``` diff --git a/docs/library/typography/em.md b/docs/library/typography/em.md deleted file mode 100644 index 2fd67aeb76..0000000000 --- a/docs/library/typography/em.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -components: - - rx.text.em ---- - -```python exec -import reflex as rx -``` - -# Em (Emphasis) - -Marks text to stress emphasis. - -```python demo -rx.text("We ", rx.text.em("had"), " to do something about it.") -``` diff --git a/docs/library/typography/heading.md b/docs/library/typography/heading.md deleted file mode 100644 index 44cd6c3f8d..0000000000 --- a/docs/library/typography/heading.md +++ /dev/null @@ -1,152 +0,0 @@ ---- -components: - - rx.heading ---- - -```python exec -import reflex as rx -``` - -# Heading - -```python demo -rx.heading("The quick brown fox jumps over the lazy dog.") -``` - -## As another element - -Use the `as_` prop to change the heading level. This prop is purely semantic and does not change the visual appearance. - -```python demo -rx.flex( - rx.heading("Level 1", as_="h1"), - rx.heading("Level 2", as_="h2"), - rx.heading("Level 3", as_="h3"), - direction="column", - spacing="3", -) -``` - -## Size - -Use the `size` prop to control the size of the heading. The prop also provides correct line height and corrective letter spacing—as text size increases, the relative line height and letter spacing decrease - -```python demo -rx.flex( - rx.heading("The quick brown fox jumps over the lazy dog.", size="1"), - rx.heading("The quick brown fox jumps over the lazy dog.", size="2"), - rx.heading("The quick brown fox jumps over the lazy dog.", size="3"), - rx.heading("The quick brown fox jumps over the lazy dog.", size="4"), - rx.heading("The quick brown fox jumps over the lazy dog.", size="5"), - rx.heading("The quick brown fox jumps over the lazy dog.", size="6"), - rx.heading("The quick brown fox jumps over the lazy dog.", size="7"), - rx.heading("The quick brown fox jumps over the lazy dog.", size="8"), - rx.heading("The quick brown fox jumps over the lazy dog.", size="9"), - direction="column", - spacing="3", -) -``` - -## Weight - -Use the `weight` prop to set the text weight. - -```python demo -rx.flex( - rx.heading("The quick brown fox jumps over the lazy dog.", weight="light"), - rx.heading("The quick brown fox jumps over the lazy dog.", weight="regular"), - rx.heading("The quick brown fox jumps over the lazy dog.", weight="medium"), - rx.heading("The quick brown fox jumps over the lazy dog.", weight="bold"), - direction="column", - spacing="3", -) -``` - -## Align - -Use the `align` prop to set text alignment. - -```python demo -rx.flex( - rx.heading("Left-aligned", align="left"), - rx.heading("Center-aligned", align="center"), - rx.heading("Right-aligned", align="right"), - direction="column", - spacing="3", - width="100%", -) -``` - -## Trim - -Use the `trim` prop to trim the leading space at the start, end, or both sides of the text. - -```python demo -rx.flex( - rx.heading("Without Trim", - trim="normal", - style={"background": "var(--gray-a2)", - "border_top": "1px dashed var(--gray-a7)", - "border_bottom": "1px dashed var(--gray-a7)",} - ), - rx.heading("With Trim", - trim="both", - style={"background": "var(--gray-a2)", - "border_top": "1px dashed var(--gray-a7)", - "border_bottom": "1px dashed var(--gray-a7)",} - ), - direction="column", - spacing="3", -) -``` - -Trimming the leading is useful when dialing in vertical spacing in cards or other “boxy” components. Otherwise, padding looks larger on top and bottom than on the sides. - -```python demo -rx.flex( - rx.box( - rx.heading("Without trim", margin_bottom="4px", size="3",), - rx.text("The goal of typography is to relate font size, line height, and line width in a proportional way that maximizes beauty and makes reading easier and more pleasant."), - style={"background": "var(--gray-a2)", - "border": "1px dashed var(--gray-a7)",}, - padding="16px", - ), - rx.box( - rx.heading("With trim", margin_bottom="4px", size="3", trim="start"), - rx.text("The goal of typography is to relate font size, line height, and line width in a proportional way that maximizes beauty and makes reading easier and more pleasant."), - style={"background": "var(--gray-a2)", - "border": "1px dashed var(--gray-a7)",}, - padding="16px", - ), - direction="column", - spacing="3", -) -``` - -## Color - -Use the `color_scheme` prop to assign a specific color, ignoring the global theme. - -```python demo -rx.flex( - rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="indigo"), - rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="cyan"), - rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="crimson"), - rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="orange"), - direction="column", -) -``` - -## High Contrast - -Use the `high_contrast` prop to increase color contrast with the background. - -```python demo -rx.flex( - rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="indigo", high_contrast=True), - rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="cyan", high_contrast=True), - rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="crimson", high_contrast=True), - rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="orange", high_contrast=True), - direction="column", -) -``` diff --git a/docs/library/typography/kbd.md b/docs/library/typography/kbd.md deleted file mode 100644 index c37771268e..0000000000 --- a/docs/library/typography/kbd.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -components: - - rx.text.kbd ---- - -```python exec -import reflex as rx -``` - -# rx.text.kbd (Keyboard) - -Represents keyboard input or a hotkey. - -```python demo -rx.text.kbd("Shift + Tab") -``` - -## Size - -Use the `size` prop to control text size. This prop also provides correct line height and corrective letter spacing—as text size increases, the relative line height and letter spacing decrease. - -```python demo -rx.flex( - rx.text.kbd("Shift + Tab", size="1"), - rx.text.kbd("Shift + Tab", size="2"), - rx.text.kbd("Shift + Tab", size="3"), - rx.text.kbd("Shift + Tab", size="4"), - rx.text.kbd("Shift + Tab", size="5"), - rx.text.kbd("Shift + Tab", size="6"), - rx.text.kbd("Shift + Tab", size="7"), - rx.text.kbd("Shift + Tab", size="8"), - rx.text.kbd("Shift + Tab", size="9"), - direction="column", - spacing="3", -) -``` diff --git a/docs/library/typography/link.md b/docs/library/typography/link.md deleted file mode 100644 index a05087f844..0000000000 --- a/docs/library/typography/link.md +++ /dev/null @@ -1,147 +0,0 @@ ---- -components: - - rx.link ---- - -```python exec -import reflex as rx -from pcweb.pages.docs import api_reference -``` - -# Link - -Links are accessible elements used primarily for navigation. Use the `href` prop to specify the location for the link to navigate to. - -```python demo -rx.link("Reflex Home Page.", href="https://reflex.dev/") -``` - -You can also provide local links to other pages in your project without writing the full url. - -```python demo -rx.link("Example", href="/docs/library",) -``` - - -The `link` component can be used to wrap other components to make them link to other pages. - -```python demo -rx.link(rx.button("Example"), href="https://reflex.dev/") -``` - -You can also create anchors to link to specific parts of a page using the `id` prop. - -```python demo -rx.box("Example", id="example") -``` - -To reference an anchor, you can use the `href` prop of the `link` component. The `href` should be in the format of the page you want to link to followed by a # and the id of the anchor. - -```python demo -rx.link("Example", href="/docs/library/typography/link#example") -``` - -```md alert info -# Redirecting the user using State -It is also possible to redirect the user to a new path within the application, using `rx.redirect()`. Check out the docs [here]({api_reference.special_events.path}). -``` - -# Style - -## Size - -Use the `size` prop to control the size of the link. The prop also provides correct line height and corrective letter spacing—as text size increases, the relative line height and letter spacing decrease. - -```python demo -rx.flex( - rx.link("The quick brown fox jumps over the lazy dog.", size="1"), - rx.link("The quick brown fox jumps over the lazy dog.", size="2"), - rx.link("The quick brown fox jumps over the lazy dog.", size="3"), - rx.link("The quick brown fox jumps over the lazy dog.", size="4"), - rx.link("The quick brown fox jumps over the lazy dog.", size="5"), - rx.link("The quick brown fox jumps over the lazy dog.", size="6"), - rx.link("The quick brown fox jumps over the lazy dog.", size="7"), - rx.link("The quick brown fox jumps over the lazy dog.", size="8"), - rx.link("The quick brown fox jumps over the lazy dog.", size="9"), - direction="column", - spacing="3", -) -``` - -## Weight - -Use the `weight` prop to set the text weight. - -```python demo -rx.flex( - rx.link("The quick brown fox jumps over the lazy dog.", weight="light"), - rx.link("The quick brown fox jumps over the lazy dog.", weight="regular"), - rx.link("The quick brown fox jumps over the lazy dog.", weight="medium"), - rx.link("The quick brown fox jumps over the lazy dog.", weight="bold"), - direction="column", - spacing="3", -) -``` - -## Trim - -Use the `trim` prop to trim the leading space at the start, end, or both sides of the rendered text. - -```python demo -rx.flex( - rx.link("Without Trim", - trim="normal", - style={"background": "var(--gray-a2)", - "border_top": "1px dashed var(--gray-a7)", - "border_bottom": "1px dashed var(--gray-a7)",} - ), - rx.link("With Trim", - trim="both", - style={"background": "var(--gray-a2)", - "border_top": "1px dashed var(--gray-a7)", - "border_bottom": "1px dashed var(--gray-a7)",} - ), - direction="column", - spacing="3", -) -``` - -## Underline - -Use the `underline` prop to manage the visibility of the underline affordance. It defaults to `auto`. - -```python demo -rx.flex( - rx.link("The quick brown fox jumps over the lazy dog.", underline="auto"), - rx.link("The quick brown fox jumps over the lazy dog.", underline="hover"), - rx.link("The quick brown fox jumps over the lazy dog.", underline="always"), - direction="column", - spacing="3", -) -``` - -## Color - -Use the `color_scheme` prop to assign a specific color, ignoring the global theme. - -```python demo -rx.flex( - rx.link("The quick brown fox jumps over the lazy dog.", color_scheme="indigo"), - rx.link("The quick brown fox jumps over the lazy dog.", color_scheme="cyan"), - rx.link("The quick brown fox jumps over the lazy dog.", color_scheme="crimson"), - rx.link("The quick brown fox jumps over the lazy dog.", color_scheme="orange"), - direction="column", -) -``` - -## High Contrast - -Use the `high_contrast` prop to increase color contrast with the background. - -```python demo -rx.flex( - rx.link("The quick brown fox jumps over the lazy dog."), - rx.link("The quick brown fox jumps over the lazy dog.", high_contrast=True), - direction="column", -) -``` diff --git a/docs/library/typography/markdown.md b/docs/library/typography/markdown.md deleted file mode 100644 index b308c15f03..0000000000 --- a/docs/library/typography/markdown.md +++ /dev/null @@ -1,195 +0,0 @@ ---- -components: - - rx.markdown ---- - -```python exec -import reflex as rx -``` - -# Markdown - -The `rx.markdown` component can be used to render markdown text. -It is based on [Github Flavored Markdown](https://github.github.com/gfm/). - -```python demo -rx.vstack( - rx.markdown("# Hello World!"), - rx.markdown("## Hello World!"), - rx.markdown("### Hello World!"), - rx.markdown("Support us on [Github](https://github.com/reflex-dev/reflex)."), - rx.markdown("Use `reflex deploy` to deploy your app with **a single command**."), -) -``` - -## Math Equations - -You can render math equations using LaTeX. -For inline equations, surround the equation with `$`: - -```python demo -rx.markdown("Pythagorean theorem: $a^2 + b^2 = c^2$.") -``` - -## Syntax Highlighting - -You can render code blocks with syntax highlighting using the \`\`\`\{language} syntax: - -````python demo -rx.markdown( -r""" -```python -import reflex as rx -from .pages import index - -app = rx.App() -app.add_page(index) -``` -""" -) -```` - -## Tables - -You can render tables using the `|` syntax: - -```python demo -rx.markdown( - """ -| Syntax | Description | -| ----------- | ----------- | -| Header | Title | -| Paragraph | Text | -""" -) -``` - -## Plugins - -Plugins can be used to extend the functionality of the markdown renderer. - -By default Reflex uses the following plugins: -- `remark-gfm` for Github Flavored Markdown support (`use_gfm`). -- `remark-math` and `rehype-katex` for math equation support (`use_math`, `use_katex`). -- `rehype-unwrap-images` to remove paragraph tags around images (`use_unwrap_images`). -- `rehype-raw` to render raw HTML in markdown (`use_raw`). NOTE: in a future release this will be disabled by default for security reasons. - -These default plugins can be disabled by passing `use_[plugin_name]=False` to the `rx.markdown` component. For example, to disable raw HTML rendering, use `rx.markdown(..., use_raw=False)`. - -## Arbitrary Plugins - -You can also add arbitrary remark or rehype plugins using the `remark_plugins` -and `rehype_plugins` props in conjunction with the `rx.markdown.plugin` helper. - -`rx.markdown.plugin` takes two arguments: - -1. The npm package name and version of the plugin (e.g. `remark-emoji@5.0.2`). -2. The named export to use from the plugin (e.g. `remarkEmoji`). - -### Remark Plugin Example - -For example, to add support for emojis using the `remark-emoji` plugin: - -```python demo -rx.markdown( - "Hello :smile:! :rocket: :tada:", - remark_plugins=[ - rx.markdown.plugin("remark-emoji@5.0.2", "remarkEmoji"), - ], -) -``` - -### Rehype Plugin Example - -To make `rehype-raw` safer for untrusted HTML input we can use `rehype-sanitize`, which defaults to a safe schema similar to that used by Github. - -```python demo -rx.markdown( - """Here is some **bold** text and a .""", - use_raw=True, - rehype_plugins=[ - rx.markdown.plugin("rehype-sanitize@5.0.1", "rehypeSanitize"), - ], -) -``` - -### Plugin Options - -Both `remark_plugins` and `rehype_plugins` accept a heterogeneous list of `plugin` -or tuple of `(plugin, options)` in case the plugin requires some kind of special -configuration. - -For example, `rehype-slug` is a simple plugin that adds ID attributes to the -headings, but the `rehype-autolink-headings` plugin accepts options to specify -how to render the links to those anchors. - -```python demo -rx.markdown( - """ -# Heading 1 -## Heading 2 -""", - rehype_plugins=[ - rx.markdown.plugin("rehype-slug@6.0.0", "rehypeSlug"), - ( - rx.markdown.plugin("rehype-autolink-headings@7.1.0", "rehypeAutolinkHeadings"), - { - "behavior": "wrap", - "properties": { - "className": ["heading-link"], - }, - }, - ), - ], -) -``` - -## Component Map - -You can specify which components to use for rendering markdown elements using the -`component_map` prop. - -Each key in the `component_map` prop is a markdown element, and the value is -a function that takes the text of the element as input and returns a Reflex component. - -```md alert -The `pre` and `a` tags are special cases. In addition to the `text`, they also receive a `props` argument containing additional props for the component. -``` - -````python demo exec -component_map = { - "h1": lambda text: rx.heading(text, size="5", margin_y="1em"), - "h2": lambda text: rx.heading(text, size="3", margin_y="1em"), - "h3": lambda text: rx.heading(text, size="1", margin_y="1em"), - "p": lambda text: rx.text(text, color="green", margin_y="1em"), - "code": lambda text: rx.code(text, color="purple"), - "pre": lambda text, **props: rx.code_block(text, **props, theme=rx.code_block.themes.dark, margin_y="1em"), - "a": lambda text, **props: rx.link(text, **props, color="blue", _hover={"color": "red"}), -} - -def index(): - return rx.box( - rx.markdown( -r""" -# Hello World! - -## This is a Subheader - -### And Another Subheader - -Here is some `code`: - -```python -import reflex as rx - -component = rx.text("Hello World!") -``` - -And then some more text here, -followed by a link to -[Reflex](https://reflex.dev/). -""", - component_map=component_map, -) - ) -```` diff --git a/docs/library/typography/quote.md b/docs/library/typography/quote.md deleted file mode 100644 index 15290f5fc8..0000000000 --- a/docs/library/typography/quote.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -components: - - rx.text.quote ---- - -```python exec -import reflex as rx -``` - -# Quote - -A short inline quotation. - -```python demo -rx.text("His famous quote, ", - rx.text.quote("Styles come and go. Good design is a language, not a style"), - ", elegantly sums up Massimo’s philosophy of design." - ) -``` diff --git a/docs/library/typography/strong.md b/docs/library/typography/strong.md deleted file mode 100644 index 85731243ea..0000000000 --- a/docs/library/typography/strong.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -components: - - rx.text.strong ---- - -```python exec -import reflex as rx -``` - -# Strong - -Marks text to signify strong importance. - -```python demo -rx.text("The most important thing to remember is, ", rx.text.strong("stay positive"), ".") -``` diff --git a/docs/library/typography/text.md b/docs/library/typography/text.md deleted file mode 100644 index bede02dd0f..0000000000 --- a/docs/library/typography/text.md +++ /dev/null @@ -1,204 +0,0 @@ ---- -components: - - rx.text - - rx.text.em - ---- - -```python exec -import reflex as rx -``` - -# Text - -```python demo -rx.text("The quick brown fox jumps over the lazy dog.") -``` - -## As another element - -Use the `as_` prop to render text as a `p`, `label`, `div` or `span`. This prop is purely semantic and does not alter visual appearance. - -```python demo -rx.flex( - rx.text("This is a ", rx.text.strong("paragraph"), " element.", as_="p"), - rx.text("This is a ", rx.text.strong("label"), " element.", as_="label"), - rx.text("This is a ", rx.text.strong("div"), " element.", as_="div"), - rx.text("This is a ", rx.text.strong("span"), " element.", as_="span"), - direction="column", - spacing="3", -) -``` - -## Size - -Use the `size` prop to control text size. This prop also provides correct line height and corrective letter spacing—as text size increases, the relative line height and letter spacing decrease. - -```python demo -rx.flex( - rx.text("The quick brown fox jumps over the lazy dog.", size="1"), - rx.text("The quick brown fox jumps over the lazy dog.", size="2"), - rx.text("The quick brown fox jumps over the lazy dog.", size="3"), - rx.text("The quick brown fox jumps over the lazy dog.", size="4"), - rx.text("The quick brown fox jumps over the lazy dog.", size="5"), - rx.text("The quick brown fox jumps over the lazy dog.", size="6"), - rx.text("The quick brown fox jumps over the lazy dog.", size="7"), - rx.text("The quick brown fox jumps over the lazy dog.", size="8"), - rx.text("The quick brown fox jumps over the lazy dog.", size="9"), - direction="column", - spacing="3", -) -``` - -Sizes 2–4 are designed to work well for long-form content. Sizes 1–3 are designed to work well for UI labels. - -## Weight - -Use the `weight` prop to set the text weight. - -```python demo -rx.flex( - rx.text("The quick brown fox jumps over the lazy dog.", weight="light", as_="div"), - rx.text("The quick brown fox jumps over the lazy dog.", weight="regular", as_="div"), - rx.text("The quick brown fox jumps over the lazy dog.", weight="medium", as_="div"), - rx.text("The quick brown fox jumps over the lazy dog.", weight="bold", as_="div"), - direction="column", - spacing="3", -) -``` - -## Align - -Use the `align` prop to set text alignment. - -```python demo -rx.flex( - rx.text("Left-aligned", align="left", as_="div"), - rx.text("Center-aligned", align="center", as_="div"), - rx.text("Right-aligned", align="right", as_="div"), - direction="column", - spacing="3", - width="100%", -) -``` - -## Trim - -Use the `trim` prop to trim the leading space at the start, end, or both sides of the text box. - -```python demo -rx.flex( - rx.text("Without Trim", - trim="normal", - style={"background": "var(--gray-a2)", - "border_top": "1px dashed var(--gray-a7)", - "border_bottom": "1px dashed var(--gray-a7)",} - ), - rx.text("With Trim", - trim="both", - style={"background": "var(--gray-a2)", - "border_top": "1px dashed var(--gray-a7)", - "border_bottom": "1px dashed var(--gray-a7)",} - ), - direction="column", - spacing="3", -) -``` - -Trimming the leading is useful when dialing in vertical spacing in cards or other “boxy” components. Otherwise, padding looks larger on top and bottom than on the sides. - -```python demo -rx.flex( - rx.box( - rx.heading("Without trim", margin_bottom="4px", size="3",), - rx.text("The goal of typography is to relate font size, line height, and line width in a proportional way that maximizes beauty and makes reading easier and more pleasant."), - style={"background": "var(--gray-a2)", - "border": "1px dashed var(--gray-a7)",}, - padding="16px", - ), - rx.box( - rx.heading("With trim", margin_bottom="4px", size="3", trim="start"), - rx.text("The goal of typography is to relate font size, line height, and line width in a proportional way that maximizes beauty and makes reading easier and more pleasant."), - style={"background": "var(--gray-a2)", - "border": "1px dashed var(--gray-a7)",}, - padding="16px", - ), - direction="column", - spacing="3", -) -``` - -## Color - -Use the `color_scheme` prop to assign a specific color, ignoring the global theme. - -```python demo -rx.flex( - rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="indigo"), - rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="cyan"), - rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="crimson"), - rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="orange"), - direction="column", -) -``` - -## High Contrast - -Use the `high_contrast` prop to increase color contrast with the background. - -```python demo -rx.flex( - rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="indigo", high_contrast=True), - rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="cyan", high_contrast=True), - rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="crimson", high_contrast=True), - rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="orange", high_contrast=True), - direction="column", -) -``` - -## With formatting - -Compose `Text` with formatting components to add emphasis and structure to content. - -```python demo -rx.text( - "Look, such a helpful ", - rx.link("link", href="#"), - ", an ", - rx.text.em("italic emphasis"), - " a piece of computer ", - rx.code("code"), - ", and even a hotkey combination ", - rx.text.kbd("⇧⌘A"), - " within the text.", - size="5", -) -``` - -## Preformmatting -By Default, the browser renders multiple white spaces into one. To preserve whitespace, use the `white_space = "pre"` css prop. - -```python demo -rx.hstack( - rx.text("This is not pre formatted"), - rx.text("This is pre formatted", white_space="pre"), -) -``` - -## With form controls - -Composing `text` with a form control like `checkbox`, `radiogroup`, or `switch` automatically centers the control with the first line of text, even when the text is multi-line. - -```python demo -rx.box( - rx.text( - rx.flex( - rx.checkbox(default_checked=True), - "I understand that these documents are confidential and cannot be shared with a third party.", - ), - as_="label", - size="3", - ), - style={"max_width": 300}, -) -``` diff --git a/docs/pages/dynamic_routing.md b/docs/pages/dynamic_routing.md deleted file mode 100644 index 72e555b62c..0000000000 --- a/docs/pages/dynamic_routing.md +++ /dev/null @@ -1,110 +0,0 @@ -```python exec -import reflex as rx -from pcweb import constants, styles -``` - -# Dynamic Routes - -Dynamic routes in Reflex allow you to handle varying URL structures, enabling you to create flexible -and adaptable web applications. This section covers regular dynamic routes, catch-all routes, -and optional catch-all routes, each with detailed examples. - -## Regular Dynamic Routes - -Regular dynamic routes in Reflex allow you to match specific segments in a URL dynamically. A regular dynamic route is defined by square brackets in a route string / url pattern. For example `/users/[id]` or `/products/[category]`. These dynamic route arguments can be accessed through a state var. For the examples above they would be `rx.State.id` and `rx.State.category` respectively. - -```md alert info -# Why is the state var accessed as `rx.State.id`? - -The dynamic route arguments are accessible as `rx.State.id` and `rx.State.category` here as the var is added to the root state, so that it is accessible from any state. -``` - -Example: - -```python -@rx.page(route='/post/[pid]') -def post(): - '''A page that updates based on the route.''' - # Displays the dynamic part of the URL, the post ID - return rx.heading(rx.State.pid) - -app = rx.App() -``` - -The [pid] part in the route is a dynamic segment, meaning it can match any value provided in the URL. For instance, `/post/5`, `/post/10`, or `/post/abc` would all match this route. - -If a user navigates to `/post/5`, `State.post_id` will return `5`, and the page will display `5` as the heading. If the URL is `/post/xyz`, it will display `xyz`. If the URL is `/post/` without any additional parameter, it will display `""`. - -### Adding Dynamic Routes - -Adding dynamic routes uses the `add_page` method like any other page. The only difference is that the route string contains dynamic segments enclosed in square brackets. - -If you are using the `app.add_page` method to define pages, it is necessary to add the dynamic routes first, especially if they use the same function as a non dynamic route. - -For example the code snippet below will: - -```python -app.add_page(index, route="/page/[page_id]", on_load=DynamicState.on_load) -app.add_page(index, route="/static/x", on_load=DynamicState.on_load) -app.add_page(index) -``` - -But if we switch the order of adding the pages, like in the example below, it will not work: - -```python -app.add_page(index, route="/static/x", on_load=DynamicState.on_load) -app.add_page(index) -app.add_page(index, route="/page/[page_id]", on_load=DynamicState.on_load) -``` - -## Catch-All Routes - -Catch-all routes in Reflex allow you to match any number of segments in a URL dynamically. - -Example: - -```python -class State(rx.State): - @rx.var - def user_post(self) -> str: - args = self.router.page.params - usernames = args.get('splat', []) - return f"Posts by \{', '.join(usernames)}" - -@rx.page(route='/users/[id]/posts/[[...splat]]') -def post(): - return rx.center( - rx.text(State.user_post) - ) - - -app = rx.App() -``` - -In this case, the `...splat` catch-all pattern captures any number of segments after -`/users/`, allowing URLs like `/users/2/posts/john/` and `/users/1/posts/john/doe/` to match the route. - -```md alert -# Catch-all routes must be named `splat` and be placed at the end of the URL pattern to ensure proper route matching. -``` - -### Routes Validation Table - -| Route Pattern | Example URl | valid | -| :----------------------------------------------- | :---------------------------------------------- | ------: | -| `/users/posts` | `/users/posts` | valid | -| `/products/[category]` | `/products/electronics` | valid | -| `/users/[username]/posts/[id]` | `/users/john/posts/5` | valid | -| `/users/[[...splat]]/posts` | `/users/john/posts` | invalid | -| | `/users/john/doe/posts` | invalid | -| `/users/[[...splat]]` | `/users/john/` | valid | -| | `/users/john/doe` | valid | -| `/products/[category]/[[...splat]]` | `/products/electronics/laptops` | valid | -| | `/products/electronics/laptops/lenovo` | valid | -| `/products/[category]/[[...splat]]` | `/products/electronics` | valid | -| | `/products/electronics/laptops` | valid | -| | `/products/electronics/laptops/lenovo` | valid | -| | `/products/electronics/laptops/lenovo/thinkpad` | valid | -| `/products/[category]/[[...splat]]/[[...splat]]` | `/products/electronics/laptops` | invalid | -| | `/products/electronics/laptops/lenovo` | invalid | -| | `/products/electronics/laptops/lenovo/thinkpad` | invalid | diff --git a/docs/pages/overview.md b/docs/pages/overview.md deleted file mode 100644 index 2a28e8b4dd..0000000000 --- a/docs/pages/overview.md +++ /dev/null @@ -1,241 +0,0 @@ -```python exec -import reflex as rx -from pcweb import constants, styles -from pcweb.pages import docs -from pcweb.pages.docs import api_reference, library -``` - -# Pages - -Pages map components to different URLs in your app. This section covers creating pages, handling URL arguments, accessing query parameters, managing page metadata, and handling page load events. - -## Adding a Page - -You can create a page by defining a function that returns a component. -By default, the function name will be used as the route, but you can also specify a route. - -```python -def index(): - return rx.text('Root Page') - -def about(): - return rx.text('About Page') - - -def custom(): - return rx.text('Custom Route') - -app = rx.App() - -app.add_page(index) -app.add_page(about) -app.add_page(custom, route="/custom-route") -``` - -In this example we create three pages: - -- `index` - The root route, available at `/` -- `about` - available at `/about` -- `custom` - available at `/custom-route` - -```md alert -# Index is a special exception where it is available at both `/` and `/index`. All other pages are only available at their specified route. -``` - -```md video https://youtube.com/embed/ITOZkzjtjUA?start=3853&end=4083 -# Video: Pages and URL Routes -``` - -## Page Decorator - -You can also use the `@rx.page` decorator to add a page. - -```python -@rx.page(route='/', title='My Beautiful App') -def index(): - return rx.text('A Beautiful App') -``` - -This is equivalent to calling `app.add_page` with the same arguments. - -```md alert warning -# Remember to import the modules defining your decorated pages. - -This is necessary for the pages to be registered with the app. - -You can directly import the module or import another module that imports the decorated pages. -``` - -## Navigating Between Pages - -### Links - -[Links]({library.typography.link.path}) are accessible elements used primarily for navigation. Use the `href` prop to specify the location for the link to navigate to. - -```python demo -rx.link("Reflex Home Page.", href="https://reflex.dev/") -``` - -You can also provide local links to other pages in your project without writing the full url. - -```python demo -rx.link("Example", href="/docs/library") -``` - -To open the link in a new tab, set the `is_external` prop to `True`. - -```python demo -rx.link("Open in new tab", href="https://reflex.dev/", is_external=True) -``` - -Check out the [link docs]({library.typography.link.path}) to learn more. - -```md video https://youtube.com/embed/ITOZkzjtjUA?start=4083&end=4423 -# Video: Link-based Navigation -``` - -### Redirect - -Redirect the user to a new path within the application using `rx.redirect()`. - -- `path`: The destination path or URL to which the user should be redirected. -- `external`: If set to True, the redirection will open in a new tab. Defaults to `False`. - -```python demo -rx.vstack( - rx.button("open in tab", on_click=rx.redirect("/docs/api-reference/special_events")), - rx.button("open in new tab", on_click=rx.redirect('https://github.com/reflex-dev/reflex/', is_external=True)) -) -``` - -Redirect can also be run from an event handler in State, meaning logic can be added behind it. It is necessary to `return` the `rx.redirect()`. - -```python demo exec -class Redirect2ExampleState(rx.State): - redirect_to_org: bool = False - - @rx.event - def change_redirect(self): - self.redirect_to_org = not self.redirect_to_org - - @rx.var - def url(self) -> str: - return 'https://github.com/reflex-dev/' if self.redirect_to_org else 'https://github.com/reflex-dev/reflex/' - - @rx.event - def change_page(self): - return rx.redirect(self.url, is_external=True) - -def redirect_example(): - return rx.vstack( - rx.text(f"{Redirect2ExampleState.url}"), - rx.button("Change redirect location", on_click=Redirect2ExampleState.change_redirect), - rx.button("Redirect to new page in State", on_click=Redirect2ExampleState.change_page), - - ) -``` - -```md video https://youtube.com/embed/ITOZkzjtjUA?start=4423&end=4903 -# Video: Redirecting to a New Page -``` - -## Nested Routes - -Pages can also have nested routes. - -```python -def nested_page(): - return rx.text('Nested Page') - -app = rx.App() -app.add_page(nested_page, route='/nested/page') -``` - -This component will be available at `/nested/page`. - -## Page Metadata - -```python exec - -import reflex as rx - -meta_data = ( -""" -@rx.page( - title='My Beautiful App', - description='A beautiful app built with Reflex', - image='https://web.reflex-assets.dev/other/logo.jpg', - meta=meta, -) -def index(): - return rx.text('A Beautiful App') - -@rx.page(title='About Page') -def about(): - return rx.text('About Page') - - -meta = [ - {'name': 'theme_color', 'content': '#FFFFFF'}, - {'char_set': 'UTF-8'}, - {'property': 'og:url', 'content': 'url'}, -] - -app = rx.App() -""" - -) - -``` - -You can add page metadata such as: - -- The title to be shown in the browser tab -- The description as shown in search results -- The preview image to be shown when the page is shared on social media -- Any additional metadata - -```python -{meta_data} -``` - -## Getting the Current Page - -You can access the current page from the `router` attribute in any state. See the [router docs]({docs.utility_methods.router_attributes.path}) for all available attributes. - -```python -class State(rx.State): - def some_method(self): - current_page_route = self.router.page.path - current_page_url = self.router.page.raw_path - # ... Your logic here ... -``` - -The `router.page.path` attribute allows you to obtain the path of the current page from the router data, -for [dynamic pages]({docs.pages.dynamic_routing.path}) this will contain the slug rather than the actual value used to load the page. - -To get the actual URL displayed in the browser, use `router.page.raw_path`. This -will contain all query parameters and dynamic path segments. - - -In the above example, `current_page_route` will contain the route pattern (e.g., `/posts/[id]`), while `current_page_url` -will contain the actual URL (e.g., `/posts/123`). - -To get the full URL, access the same attributes with `full_` prefix. - -Example: - -```python -class State(rx.State): - @rx.var - def current_url(self) -> str: - return self.router.page.full_raw_path - -def index(): - return rx.text(State.current_url) - -app = rx.App() -app.add_page(index, route='/posts/[id]') -``` - -In this example, running on `localhost` should display `http://localhost:3000/posts/123/` diff --git a/docs/recipes/auth/login_form.md b/docs/recipes/auth/login_form.md deleted file mode 100644 index 9189800e4a..0000000000 --- a/docs/recipes/auth/login_form.md +++ /dev/null @@ -1,243 +0,0 @@ -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -``` - -# Login Form - -The login form is a common component in web applications. It allows users to authenticate themselves and access their accounts. This recipe provides examples of login forms with different elements, such as third-party authentication providers. - -## Default - -```python demo exec toggle -def login_default() -> rx.Component: - return rx.card( - rx.vstack( - rx.center( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), - rx.heading("Sign in to your account", size="6", as_="h2", text_align="center", width="100%"), - direction="column", - spacing="5", - width="100%" - ), - rx.vstack( - rx.text("Email address", size="3", weight="medium", text_align="left", width="100%"), - rx.input(placeholder="user@reflex.dev", type="email", size="3", width="100%"), - justify="start", - spacing="2", - width="100%" - ), - rx.vstack( - rx.hstack( - rx.text("Password", size="3", weight="medium"), - rx.link("Forgot password?", href="#", size="3"), - justify="between", - width="100%" - ), - rx.input(placeholder="Enter your password", type="password", size="3", width="100%"), - spacing="2", - width="100%" - ), - rx.button("Sign in", size="3", width="100%"), - rx.center( - rx.text("New here?", size="3"), - rx.link("Sign up", href="#", size="3"), - opacity="0.8", - spacing="2", - direction="row" - ), - spacing="6", - width="100%" - ), - size="4", - max_width="28em", - width="100%" - ) -``` - -## Icons - -```python demo exec toggle -def login_default_icons() -> rx.Component: - return rx.card( - rx.vstack( - rx.center( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), - rx.heading("Sign in to your account", size="6", as_="h2", text_align="center", width="100%"), - direction="column", - spacing="5", - width="100%" - ), - rx.vstack( - rx.text("Email address", size="3", weight="medium", text_align="left", width="100%"), - rx.input(rx.input.slot(rx.icon("user")), placeholder="user@reflex.dev", type="email", size="3", width="100%"), - spacing="2", - width="100%" - ), - rx.vstack( - rx.hstack( - rx.text("Password", size="3", weight="medium"), - rx.link("Forgot password?", href="#", size="3"), - justify="between", - width="100%" - ), - rx.input(rx.input.slot(rx.icon("lock")), placeholder="Enter your password", type="password", size="3", width="100%"), - spacing="2", - width="100%" - ), - rx.button("Sign in", size="3", width="100%"), - rx.center( - rx.text("New here?", size="3"), - rx.link("Sign up", href="#", size="3"), - opacity="0.8", - spacing="2", - direction="row", - width="100%" - ), - spacing="6", - width="100%" - ), - max_width="28em", - size="4", - width="100%" - ) -``` - -## Third-party auth - -```python demo exec toggle -def login_single_thirdparty() -> rx.Component: - return rx.card( - rx.vstack( - rx.flex( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), - rx.heading("Sign in to your account", size="6", as_="h2", text_align="left", width="100%"), - rx.hstack( - rx.text("New here?", size="3", text_align="left"), - rx.link("Sign up", href="#", size="3"), - spacing="2", - opacity="0.8", - width="100%" - ), - direction="column", - justify="start", - spacing="4", - width="100%" - ), - rx.vstack( - rx.text("Email address", size="3", weight="medium", text_align="left", width="100%"), - rx.input(rx.input.slot(rx.icon("user")), placeholder="user@reflex.dev", type="email", size="3", width="100%"), - justify="start", - spacing="2", - width="100%" - ), - rx.vstack( - rx.hstack( - rx.text("Password", size="3", weight="medium"), - rx.link("Forgot password?", href="#", size="3"), - justify="between", - width="100%" - ), - rx.input(rx.input.slot(rx.icon("lock")), placeholder="Enter your password", type="password", size="3", width="100%"), - spacing="2", - width="100%" - ), - rx.button("Sign in", size="3", width="100%"), - rx.hstack( - rx.divider(margin="0"), - rx.text("Or continue with", white_space="nowrap", weight="medium"), - rx.divider(margin="0"), - align="center", - width="100%" - ), - rx.button( - rx.icon(tag="github"), - "Sign in with Github", - variant="outline", - size="3", - width="100%" - ), - spacing="6", - width="100%" - ), - size="4", - max_width="28em", - width="100%" - ) -``` - -## Multiple third-party auth - -```python demo exec toggle -def login_multiple_thirdparty() -> rx.Component: - return rx.card( - rx.vstack( - rx.flex( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), - rx.heading("Sign in to your account", size="6", as_="h2", width="100%"), - rx.hstack( - rx.text("New here?", size="3", text_align="left"), - rx.link("Sign up", href="#", size="3"), - spacing="2", - opacity="0.8", - width="100%" - ), - justify="start", - direction="column", - spacing="4", - width="100%" - ), - rx.vstack( - rx.text("Email address", size="3", weight="medium", text_align="left", width="100%"), - rx.input(rx.input.slot(rx.icon("user")), placeholder="user@reflex.dev", type="email", size="3", width="100%"), - spacing="2", - justify="start", - width="100%" - ), - rx.vstack( - rx.hstack( - rx.text("Password", size="3", weight="medium"), - rx.link("Forgot password?", href="#", size="3"), - justify="between", - width="100%" - ), - rx.input(rx.input.slot(rx.icon("lock")), placeholder="Enter your password", type="password", size="3", width="100%"), - spacing="2", - width="100%" - ), - rx.button("Sign in", size="3", width="100%"), - rx.hstack( - rx.divider(margin="0"), - rx.text("Or continue with", white_space="nowrap", weight="medium"), - rx.divider(margin="0"), - align="center", - width="100%" - ), - rx.center( - rx.icon_button( - rx.icon(tag="github"), - variant="soft", - size="3" - ), - rx.icon_button( - rx.icon(tag="facebook"), - variant="soft", - size="3" - ), - rx.icon_button( - rx.icon(tag="twitter"), - variant="soft", - size="3" - ), - spacing="4", - direction="row", - width="100%" - ), - spacing="6", - width="100%" - ), - size="4", - max_width="28em", - width="100%" - ) -``` diff --git a/docs/recipes/auth/signup_form.md b/docs/recipes/auth/signup_form.md deleted file mode 100644 index c45ae731a7..0000000000 --- a/docs/recipes/auth/signup_form.md +++ /dev/null @@ -1,260 +0,0 @@ -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -``` - -# Sign up Form - -The sign up form is a common component in web applications. It allows users to create an account and access the application's features. This page provides a few examples of sign up forms that you can use in your application. -## Default - -```python demo exec toggle -def signup_default() -> rx.Component: - return rx.card( - rx.vstack( - rx.center( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), - rx.heading("Create an account", size="6", as_="h2", text_align="center", width="100%"), - direction="column", - spacing="5", - width="100%" - ), - rx.vstack( - rx.text("Email address", size="3", weight="medium", text_align="left", width="100%"), - rx.input(placeholder="user@reflex.dev", type="email", size="3", width="100%"), - justify="start", - spacing="2", - width="100%" - ), - rx.vstack( - rx.text("Password", size="3", weight="medium", text_align="left", width="100%"), - rx.input(placeholder="Enter your password", type="password", size="3", width="100%"), - justify="start", - spacing="2", - width="100%" - ), - rx.box( - rx.checkbox( - "Agree to Terms and Conditions", - default_checked=True, - spacing="2" - ), - width="100%" - ), - rx.button("Register", size="3", width="100%"), - rx.center( - rx.text("Already registered?", size="3"), - rx.link("Sign in", href="#", size="3"), - opacity="0.8", - spacing="2", - direction="row" - ), - spacing="6", - width="100%" - ), - size="4", - max_width="28em", - width="100%" - ) -``` - -## Icons - -```python demo exec toggle -def signup_default_icons() -> rx.Component: - return rx.card( - rx.vstack( - rx.center( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), - rx.heading("Create an account", size="6", as_="h2", text_align="center", width="100%"), - direction="column", - spacing="5", - width="100%" - ), - rx.vstack( - rx.text("Email address", size="3", weight="medium", text_align="left", width="100%"), - rx.input(rx.input.slot(rx.icon("user")), placeholder="user@reflex.dev", type="email", size="3", width="100%"), - justify="start", - spacing="2", - width="100%" - ), - rx.vstack( - rx.text("Password", size="3", weight="medium", text_align="left", width="100%"), - rx.input(rx.input.slot(rx.icon("lock")), placeholder="Enter your password", type="password", size="3", width="100%"), - justify="start", - spacing="2", - width="100%" - ), - rx.box( - rx.checkbox( - "Agree to Terms and Conditions", - default_checked=True, - spacing="2" - ), - width="100%" - ), - rx.button("Register", size="3", width="100%"), - rx.center( - rx.text("Already registered?", size="3"), - rx.link("Sign in", href="#", size="3"), - opacity="0.8", - spacing="2", - direction="row", - width="100%" - ), - spacing="6", - width="100%" - ), - max_width="28em", - size="4", - width="100%" - ) -``` - -## Third-party auth - - -```python demo exec toggle -def signup_single_thirdparty() -> rx.Component: - return rx.card( - rx.vstack( - rx.flex( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), - rx.heading("Create an account", size="6", as_="h2", text_align="left", width="100%"), - rx.hstack( - rx.text("Already registered?", size="3", text_align="left"), - rx.link("Sign in", href="#", size="3"), - spacing="2", - opacity="0.8", - width="100%" - ), - direction="column", - justify="start", - spacing="4", - width="100%" - ), - rx.vstack( - rx.text("Email address", size="3", weight="medium", text_align="left", width="100%"), - rx.input(rx.input.slot(rx.icon("user")), placeholder="user@reflex.dev", type="email", size="3", width="100%"), - justify="start", - spacing="2", - width="100%" - ), - rx.vstack( - rx.text("Password", size="3", weight="medium", text_align="left", width="100%"), - rx.input(rx.input.slot(rx.icon("lock")), placeholder="Enter your password", type="password", size="3", width="100%"), - justify="start", - spacing="2", - width="100%" - ), - rx.box( - rx.checkbox( - "Agree to Terms and Conditions", - default_checked=True, - spacing="2" - ), - width="100%" - ), - rx.button("Register", size="3", width="100%"), - rx.hstack( - rx.divider(margin="0"), - rx.text("Or continue with", white_space="nowrap", weight="medium"), - rx.divider(margin="0"), - align="center", - width="100%" - ), - rx.button( - rx.icon(tag="github"), - "Sign in with Github", - variant="outline", - size="3", - width="100%" - ), - spacing="6", - width="100%" - ), - size="4", - max_width="28em", - width="100%" - ) -``` - -## Multiple third-party auth - -```python demo exec toggle -def signup_multiple_thirdparty() -> rx.Component: - return rx.card( - rx.vstack( - rx.flex( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.5em", height="auto", border_radius="25%"), - rx.heading("Create an account", size="6", as_="h2", width="100%"), - rx.hstack( - rx.text("Already registered?", size="3", text_align="left"), - rx.link("Sign in", href="#", size="3"), - spacing="2", - opacity="0.8", - width="100%" - ), - justify="start", - direction="column", - spacing="4", - width="100%" - ), - rx.vstack( - rx.text("Email address", size="3", weight="medium", text_align="left", width="100%"), - rx.input(rx.input.slot(rx.icon("user")), placeholder="user@reflex.dev", type="email", size="3", width="100%"), - justify="start", - spacing="2", - width="100%" - ), - rx.vstack( - rx.text("Password", size="3", weight="medium", text_align="left", width="100%"), - rx.input(rx.input.slot(rx.icon("lock")), placeholder="Enter your password", type="password", size="3", width="100%"), - justify="start", - spacing="2", - width="100%" - ), - rx.box( - rx.checkbox( - "Agree to Terms and Conditions", - default_checked=True, - spacing="2" - ), - width="100%" - ), - rx.button("Register", size="3", width="100%"), - rx.hstack( - rx.divider(margin="0"), - rx.text("Or continue with", white_space="nowrap", weight="medium"), - rx.divider(margin="0"), - align="center", - width="100%" - ), - rx.center( - rx.icon_button( - rx.icon(tag="github"), - variant="soft", - size="3" - ), - rx.icon_button( - rx.icon(tag="facebook"), - variant="soft", - size="3" - ), - rx.icon_button( - rx.icon(tag="twitter"), - variant="soft", - size="3" - ), - spacing="4", - direction="row", - width="100%" - ), - spacing="6", - width="100%" - ), - size="4", - max_width="28em", - width="100%" - ) -``` diff --git a/docs/recipes/content/forms.md b/docs/recipes/content/forms.md deleted file mode 100644 index 57b6162aaf..0000000000 --- a/docs/recipes/content/forms.md +++ /dev/null @@ -1,173 +0,0 @@ -```python exec -import reflex as rx -from pcweb.pages.docs import library -``` - -## Forms - -Forms are a common way to gather information from users. Below are some examples. - -For more details, see the [form docs page]({library.forms.form.path}). - -## Event creation - -```python demo exec toggle -def form_field(label: str, placeholder: str, type: str, name: str) -> rx.Component: - return rx.form.field( - rx.flex( - rx.form.label(label), - rx.form.control( - rx.input( - placeholder=placeholder, - type=type - ), - as_child=True, - ), - direction="column", - spacing="1", - ), - name=name, - width="100%" - ) - -def event_form() -> rx.Component: - return rx.card( - rx.flex( - rx.hstack( - rx.badge( - rx.icon(tag="calendar-plus", size=32), - color_scheme="mint", - radius="full", - padding="0.65rem" - ), - rx.vstack( - rx.heading("Create an event", size="4", weight="bold"), - rx.text("Fill the form to create a custom event", size="2"), - spacing="1", - height="100%", - align_items="start" - ), - height="100%", - spacing="4", - align_items="center", - width="100%", - ), - rx.form.root( - rx.flex( - form_field("Event Name", "Event Name", - "text", "event_name"), - rx.flex( - form_field("Date", "", "date", "event_date"), - form_field("Time", "", "time", "event_time"), - spacing="3", - flex_direction="row", - ), - form_field("Description", "Optional", "text", "description"), - direction="column", - spacing="2", - ), - rx.form.submit( - rx.button("Create"), - as_child=True, - width="100%", - ), - on_submit=lambda form_data: rx.window_alert(form_data.to_string()), - reset_on_submit=False, - ), - width="100%", - direction="column", - spacing="4", - ), - size="3", - ) -``` - -## Contact - -```python demo exec toggle -def form_field(label: str, placeholder: str, type: str, name: str) -> rx.Component: - return rx.form.field( - rx.flex( - rx.form.label(label), - rx.form.control( - rx.input( - placeholder=placeholder, - type=type - ), - as_child=True, - ), - direction="column", - spacing="1", - ), - name=name, - width="100%" - ) - -def contact_form() -> rx.Component: - return rx.card( - rx.flex( - rx.hstack( - rx.badge( - rx.icon(tag="mail-plus", size=32), - color_scheme="blue", - radius="full", - padding="0.65rem" - ), - rx.vstack( - rx.heading("Send us a message", size="4", weight="bold"), - rx.text("Fill the form to contact us", size="2"), - spacing="1", - height="100%", - ), - height="100%", - spacing="4", - align_items="center", - width="100%", - ), - rx.form.root( - rx.flex( - rx.flex( - form_field("First Name", "First Name", - "text", "first_name"), - form_field("Last Name", "Last Name", - "text", "last_name"), - spacing="3", - flex_direction=["column", "row", "row"], - ), - rx.flex( - form_field("Email", "user@reflex.dev", - "email", "email"), - form_field("Phone", "Phone", "tel", "phone"), - spacing="3", - flex_direction=["column", "row", "row"], - ), - rx.flex( - rx.text("Message", style={ - "font-size": "15px", "font-weight": "500", "line-height": "35px"}), - rx.text_area( - placeholder="Message", - name="message", - resize="vertical", - ), - direction="column", - spacing="1", - ), - rx.form.submit( - rx.button("Submit"), - as_child=True, - ), - direction="column", - spacing="2", - width="100%", - ), - on_submit=lambda form_data: rx.window_alert( - form_data.to_string()), - reset_on_submit=False, - ), - width="100%", - direction="column", - spacing="4", - ), - size="3", - ) -``` \ No newline at end of file diff --git a/docs/recipes/content/grid.md b/docs/recipes/content/grid.md deleted file mode 100644 index 5c629c5f65..0000000000 --- a/docs/recipes/content/grid.md +++ /dev/null @@ -1,52 +0,0 @@ -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from pcweb.pages.docs import styling -``` - -# Grid - -A simple responsive grid layout. We specify the number of columns to the `grid_template_columns` property as a list. The grid will automatically adjust the number of columns based on the screen size. - -For details, see the [responsive docs page]({styling.responsive.path}). - -## Cards - -```python demo -rx.grid( - rx.foreach( - rx.Var.range(12), - lambda i: rx.card(f"Card {i + 1}", height="10vh"), - ), - gap="1rem", - grid_template_columns=["1fr", "repeat(2, 1fr)", "repeat(2, 1fr)", "repeat(3, 1fr)", "repeat(4, 1fr)"], - width="100%" -) -``` - -## Inset cards - -```python demo -rx.grid( - rx.foreach( - rx.Var.range(12), - lambda i: rx.card( - rx.inset( - rx.image( - src=f"{REFLEX_ASSETS_CDN}other/reflex_banner.png", - width="100%", - height="auto", - ), - side="top", - pb="current" - ), - rx.text( - f"Card {i + 1}", - ), - ), - ), - gap="1rem", - grid_template_columns=["1fr", "repeat(2, 1fr)", "repeat(2, 1fr)", "repeat(3, 1fr)", "repeat(4, 1fr)"], - width="100%" -) -``` diff --git a/docs/recipes/content/multi_column_row.md b/docs/recipes/content/multi_column_row.md deleted file mode 100644 index 71d6041df3..0000000000 --- a/docs/recipes/content/multi_column_row.md +++ /dev/null @@ -1,64 +0,0 @@ -```python exec -import reflex as rx -from pcweb.pages.docs import styling -``` - -# Multi-column and row layout - -A simple responsive multi-column and row layout. We specify the number of columns/rows to the `flex_direction` property as a list. The layout will automatically adjust the number of columns/rows based on the screen size. - -For details, see the [responsive docs page]({styling.responsive.path}). - -## Column - -```python demo -rx.flex( - rx.box(bg=rx.color("accent", 3), width="100%", height="100%"), - rx.box(bg=rx.color("accent", 5), width="100%", height="100%"), - rx.box(bg=rx.color("accent", 7), width="100%", height="100%"), - bg=rx.color("accent", 10), - spacing="4", - padding="1em", - flex_direction=["column", "column", "row"], - height="600px", - width="100%", -) -``` - -```python demo -rx.flex( - rx.box(bg=rx.color("accent", 3), width="100%", height="100%"), - rx.box(bg=rx.color("accent", 5), width=["100%", "100%", "50%"], height=["50%", "50%", "100%"]), - rx.box(bg=rx.color("accent", 7), width="100%", height="100%"), - rx.box(bg=rx.color("accent", 9), width=["100%", "100%", "50%"], height=["50%", "50%", "100%"]), - bg=rx.color("accent", 10), - spacing="4", - padding="1em", - flex_direction=["column", "column", "row"], - height="600px", - width="100%", -) -``` - -## Row - -```python demo -rx.flex( - rx.flex( - rx.box(bg=rx.color("accent", 3), width=["100%", "100%", "50%"], height="100%"), - rx.box(bg=rx.color("accent", 5), width=["100%", "100%", "50%"], height="100%"), - width="100%", - height="100%", - spacing="4", - flex_direction=["column", "column", "row"], - ), - rx.box(bg=rx.color("accent", 7), width="100%", height="50%"), - rx.box(bg=rx.color("accent", 9), width="100%", height="50%"), - bg=rx.color("accent", 10), - spacing="4", - padding="1em", - flex_direction="column", - height="600px", - width="100%", -) -``` \ No newline at end of file diff --git a/docs/recipes/content/stats.md b/docs/recipes/content/stats.md deleted file mode 100644 index 676e2e9a13..0000000000 --- a/docs/recipes/content/stats.md +++ /dev/null @@ -1,107 +0,0 @@ -```python exec -import reflex as rx -``` - -# Stats - -Stats cards are used to display key metrics or data points. They are typically used in dashboards or admin panels. - -## Variant 1 - -```python demo exec toggle -from reflex.components.radix.themes.base import LiteralAccentColor - -def stats(stat_name: str = "Users", value: int = 4200, prev_value: int = 3000, icon: str = "users", badge_color: LiteralAccentColor = "blue") -> rx.Component: - percentage_change = round(((value - prev_value) / prev_value) * 100, 2) if prev_value != 0 else 0 if value == 0 else float('inf') - change = "increase" if value > prev_value else "decrease" - arrow_icon = "trending-up" if value > prev_value else "trending-down" - arrow_color = "grass" if value > prev_value else "tomato" - return rx.card( - rx.vstack( - rx.hstack( - rx.badge( - rx.icon(tag=icon, size=34), - color_scheme=badge_color, - radius="full", - padding="0.7rem" - ), - rx.vstack( - rx.heading(f"{value:,}", size="6", weight="bold"), - rx.text(stat_name, size="4", weight="medium"), - spacing="1", - height="100%", - align_items="start", - width="100%" - ), - height="100%", - spacing="4", - align="center", - width="100%", - ), - rx.hstack( - rx.hstack( - rx.icon(tag=arrow_icon, size=24, color=rx.color(arrow_color, 9)), - rx.text(f"{percentage_change}%", size="3", color=rx.color(arrow_color, 9), weight="medium"), - spacing="2", - align="center", - ), - rx.text(f"{change} from last month", size="2", color=rx.color("gray", 10)), - align="center", - width="100%", - ), - spacing="3", - ), - size="3", - width="100%", - max_width="21rem" - ) -``` - -## Variant 2 - -```python demo exec toggle -from reflex.components.radix.themes.base import LiteralAccentColor - -def stats_2(stat_name: str = "Orders", value: int = 6500, prev_value: int = 12000, icon: str = "shopping-cart", icon_color: LiteralAccentColor = "pink") -> rx.Component: - percentage_change = round(((value - prev_value) / prev_value) * 100, 2) if prev_value != 0 else 0 if value == 0 else float('inf') - arrow_icon = "trending-up" if value > prev_value else "trending-down" - arrow_color = "grass" if value > prev_value else "tomato" - return rx.card( - rx.hstack( - rx.vstack( - rx.hstack( - rx.hstack( - rx.icon(tag=icon, size=22, color=rx.color(icon_color, 11)), - rx.text(stat_name, size="4", weight="medium", color=rx.color("gray", 11)), - spacing="2", - align="center", - ), - rx.badge( - rx.icon(tag=arrow_icon, color=rx.color(arrow_color, 9)), - rx.text(f"{percentage_change}%", size="2", color=rx.color(arrow_color, 9), weight="medium"), - color_scheme=arrow_color, - radius="large", - align_items="center", - ), - justify="between", - width="100%", - ), - rx.hstack( - rx.heading(f"{value:,}", size="7", weight="bold"), - rx.text(f"from {prev_value:,}", size="3", color=rx.color("gray", 10)), - spacing="2", - align_items="end", - ), - align_items="start", - justify="between", - width="100%", - ), - align_items="start", - width="100%", - justify="between", - ), - size="3", - width="100%", - max_width="21rem", - ) -``` \ No newline at end of file diff --git a/docs/recipes/content/top_banner.md b/docs/recipes/content/top_banner.md deleted file mode 100644 index 8dd0797048..0000000000 --- a/docs/recipes/content/top_banner.md +++ /dev/null @@ -1,271 +0,0 @@ -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -``` - -# Top Banner - -Top banners are used to highlight important information or features at the top of a page. They are typically designed to grab the user's attention and can be used for announcements, navigation, or key messages. - -## Basic - -```python demo exec toggle -class TopBannerBasic(rx.ComponentState): - hide: bool = False - - @rx.event - def toggle(self): - self.hide = not self.hide - - @classmethod - def get_component(cls, **props): - return rx.cond( - ~cls.hide, - rx.hstack( - rx.flex( - rx.badge( - rx.icon("megaphone", size=18), - padding="0.30rem", - radius="full", - ), - rx.text( - "ReflexCon 2024 - ", - rx.link( - "Join us at the event!", - href="#", - underline="always", - display="inline", - underline_offset="2px", - ), - weight="medium", - ), - align="center", - margin="auto", - spacing="3", - ), - rx.icon( - "x", - cursor="pointer", - justify="end", - flex_shrink=0, - on_click=cls.toggle, - ), - wrap="nowrap", - # position="fixed", - justify="between", - width="100%", - # top="0", - align="center", - left="0", - # z_index="50", - padding="1rem", - background=rx.color("accent", 4), - **props, - ), - # Remove this in production - rx.icon_button( - rx.icon("eye"), - cursor="pointer", - on_click=cls.toggle, - ), - ) - -top_banner_basic = TopBannerBasic.create -``` - -## Sign up - -```python demo exec toggle -class TopBannerSignup(rx.ComponentState): - hide: bool = False - - @rx.event - def toggle(self): - self.hide = not self.hide - - @classmethod - def get_component(cls, **props): - return rx.cond( - ~cls.hide, - rx.flex( - rx.image( - src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", - width="2em", - height="auto", - border_radius="25%", - ), - rx.text( - "Web apps in pure Python. Deploy with a single command.", - weight="medium", - ), - rx.flex( - rx.button( - "Sign up", - cursor="pointer", - radius="large", - ), - rx.icon( - "x", - cursor="pointer", - justify="end", - flex_shrink=0, - on_click=cls.toggle, - ), - spacing="4", - align="center", - ), - wrap="nowrap", - # position="fixed", - flex_direction=["column", "column", "row"], - justify_content=["start", "space-between"], - width="100%", - # top="0", - spacing="2", - align_items=["start", "start", "center"], - left="0", - # z_index="50", - padding="1rem", - background=rx.color("accent", 4), - **props, - ), - # Remove this in production - rx.icon_button( - rx.icon("eye"), - cursor="pointer", - on_click=cls.toggle, - ), - ) - -top_banner_signup = TopBannerSignup.create -``` - -## Gradient - -```python demo exec toggle -class TopBannerGradient(rx.ComponentState): - hide: bool = False - - @rx.event - def toggle(self): - self.hide = not self.hide - - @classmethod - def get_component(cls, **props): - return rx.cond( - ~cls.hide, - rx.flex( - rx.text( - "The new Reflex version is now available! ", - rx.link( - "Read the release notes", - href="#", - underline="always", - display="inline", - underline_offset="2px", - ), - align_items=["start", "center"], - margin="auto", - spacing="3", - weight="medium", - ), - rx.icon( - "x", - cursor="pointer", - justify="end", - flex_shrink=0, - on_click=cls.toggle, - ), - wrap="nowrap", - # position="fixed", - justify="between", - width="100%", - # top="0", - align="center", - left="0", - # z_index="50", - padding="1rem", - background=f"linear-gradient(99deg, {rx.color('blue', 4)}, {rx.color('pink', 3)}, {rx.color('mauve', 3)})", - **props, - ), - # Remove this in production - rx.icon_button( - rx.icon("eye"), - cursor="pointer", - on_click=cls.toggle, - ), - ) - -top_banner_gradient = TopBannerGradient.create -``` - -## Newsletter - -```python demo exec toggle -class TopBannerNewsletter(rx.ComponentState): - hide: bool = False - - @rx.event - def toggle(self): - self.hide = not self.hide - - @classmethod - def get_component(cls, **props): - return rx.cond( - ~cls.hide, - rx.flex( - rx.text( - "Join our newsletter", - text_wrap="nowrap", - weight="medium", - ), - rx.input( - rx.input.slot(rx.icon("mail")), - rx.input.slot( - rx.icon_button( - rx.icon( - "arrow-right", - padding="0.15em", - ), - cursor="pointer", - radius="large", - size="2", - justify="end", - ), - padding_right="0", - ), - placeholder="Your email address", - type="email", - size="2", - radius="large", - ), - rx.icon( - "x", - cursor="pointer", - justify="end", - flex_shrink=0, - on_click=cls.toggle, - ), - wrap="nowrap", - # position="fixed", - flex_direction=["column", "row", "row"], - justify_content=["start", "space-between"], - width="100%", - # top="0", - spacing="2", - align_items=["start", "center", "center"], - left="0", - # z_index="50", - padding="1rem", - background=rx.color("accent", 4), - **props, - ), - # Remove this in production - rx.icon_button( - rx.icon("eye"), - cursor="pointer", - on_click=cls.toggle, - ), - ) - -top_banner_newsletter = TopBannerNewsletter.create -``` \ No newline at end of file diff --git a/docs/recipes/layout/footer.md b/docs/recipes/layout/footer.md deleted file mode 100644 index d13e12bae9..0000000000 --- a/docs/recipes/layout/footer.md +++ /dev/null @@ -1,281 +0,0 @@ -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -``` - -# Footer Bar - -A footer bar is a common UI element located at the bottom of a webpage. It typically contains information about the website, such as contact details and links to other pages or sections of the site. - -## Basic - -```python demo exec toggle -def footer_item(text: str, href: str) -> rx.Component: - return rx.link(rx.text(text, size="3"), href=href) - -def footer_items_1() -> rx.Component: - return rx.flex( - rx.heading("PRODUCTS", size="4", weight="bold", as_="h3"), - footer_item("Web Design", "/#"), - footer_item("Web Development", "/#"), - footer_item("E-commerce", "/#"), - footer_item("Content Management", "/#"), - footer_item("Mobile Apps", "/#"), - spacing="4", - text_align=["center", "center", "start"], - flex_direction="column" - ) - -def footer_items_2() -> rx.Component: - return rx.flex( - rx.heading("RESOURCES", size="4", weight="bold", as_="h3"), - footer_item("Blog", "/#"), - footer_item("Case Studies", "/#"), - footer_item("Whitepapers", "/#"), - footer_item("Webinars", "/#"), - footer_item("E-books", "/#"), - spacing="4", - text_align=["center", "center", "start"], - flex_direction="column" - ) - -def social_link(icon: str, href: str) -> rx.Component: - return rx.link(rx.icon(icon), href=href) - -def socials() -> rx.Component: - return rx.flex( - social_link("instagram", "/#"), - social_link("twitter", "/#"), - social_link("facebook", "/#"), - social_link("linkedin", "/#"), - spacing="3", - justify="end", - width="100%" - ) - -def footer() -> rx.Component: - return rx.el.footer( - rx.vstack( - rx.flex( - rx.vstack( - rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), - rx.heading("Reflex", size="7", weight="bold"), - align_items="center" - ), - rx.text("© 2024 Reflex, Inc", size="3", white_space="nowrap", weight="medium"), - spacing="4", - align_items=["center", "center", "start"] - ), - footer_items_1(), - footer_items_2(), - justify="between", - spacing="6", - flex_direction=["column", "column", "row"], - width="100%" - ), - rx.divider(), - rx.hstack( - rx.hstack( - footer_item("Privacy Policy", "/#"), - footer_item("Terms of Service", "/#"), - spacing="4", - align="center", - width="100%" - ), - socials(), - justify="between", - width="100%" - ), - spacing="5", - width="100%" - ), - width="100%" - ) -``` - -## Newsletter form - -```python demo exec toggle -def footer_item(text: str, href: str) -> rx.Component: - return rx.link(rx.text(text, size="3"), href=href) - -def footer_items_1() -> rx.Component: - return rx.flex( - rx.heading("PRODUCTS", size="4", weight="bold", as_="h3"), - footer_item("Web Design", "/#"), - footer_item("Web Development", "/#"), - footer_item("E-commerce", "/#"), - footer_item("Content Management", "/#"), - footer_item("Mobile Apps", "/#"), - spacing="4", - text_align=["center", "center", "start"], - flex_direction="column" - ) - -def footer_items_2() -> rx.Component: - return rx.flex( - rx.heading("RESOURCES", size="4", weight="bold", as_="h3"), - footer_item("Blog", "/#"), - footer_item("Case Studies", "/#"), - footer_item("Whitepapers", "/#"), - footer_item("Webinars", "/#"), - footer_item("E-books", "/#"), - spacing="4", - text_align=["center", "center", "start"], - flex_direction="column" - ) - -def social_link(icon: str, href: str) -> rx.Component: - return rx.link(rx.icon(icon), href=href) - -def socials() -> rx.Component: - return rx.flex( - social_link("instagram", "/#"), - social_link("twitter", "/#"), - social_link("facebook", "/#"), - social_link("linkedin", "/#"), - spacing="3", - justify_content=["center", "center", "end"], - width="100%" - ) - -def footer_newsletter() -> rx.Component: - return rx.el.footer( - rx.vstack( - rx.flex( - footer_items_1(), - footer_items_2(), - rx.vstack( - rx.text("JOIN OUR NEWSLETTER", size="4", - weight="bold"), - rx.hstack( - rx.input(placeholder="Your email address", type="email", size="3"), - rx.icon_button(rx.icon("arrow-right", padding="0.15em"), size="3"), - spacing="1", - justify="center", - width="100%" - ), - align_items=["center", "center", "start"], - justify="center", - height="100%" - ), - justify="between", - spacing="6", - flex_direction=["column", "column", "row"], - width="100%" - ), - rx.divider(), - rx.flex( - rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), - rx.text("© 2024 Reflex, Inc", size="3", white_space="nowrap", weight="medium"), - spacing="2", - align="center", - justify_content=["center", "center", "start"], - width="100%" - ), - socials(), - spacing="4", - flex_direction=["column", "column", "row"], - width="100%" - ), - spacing="5", - width="100%" - ), - width="100%" - ) -``` - -## Three columns - -```python demo exec toggle -def footer_item(text: str, href: str) -> rx.Component: - return rx.link(rx.text(text, size="3"), href=href) - -def footer_items_1() -> rx.Component: - return rx.flex( - rx.heading("PRODUCTS", size="4", weight="bold", as_="h3"), - footer_item("Web Design", "/#"), - footer_item("Web Development", "/#"), - footer_item("E-commerce", "/#"), - footer_item("Content Management", "/#"), - footer_item("Mobile Apps", "/#"), - spacing="4", - text_align=["center", "center", "start"], - flex_direction="column" - ) - -def footer_items_2() -> rx.Component: - return rx.flex( - rx.heading("RESOURCES", size="4", weight="bold", as_="h3"), - footer_item("Blog", "/#"), - footer_item("Case Studies", "/#"), - footer_item("Whitepapers", "/#"), - footer_item("Webinars", "/#"), - footer_item("E-books", "/#"), - spacing="4", - text_align=["center", "center", "start"], - flex_direction="column" - ) - -def footer_items_3() -> rx.Component: - return rx.flex( - rx.heading("ABOUT US", size="4", weight="bold", as_="h3"), - footer_item("Our Team", "/#"), - footer_item("Careers", "/#"), - footer_item("Contact Us", "/#"), - footer_item("Privacy Policy", "/#"), - footer_item("Terms of Service", "/#"), - spacing="4", - text_align=["center", "center", "start"], - flex_direction="column" - ) - -def social_link(icon: str, href: str) -> rx.Component: - return rx.link(rx.icon(icon), href=href) - -def socials() -> rx.Component: - return rx.flex( - social_link("instagram", "/#"), - social_link("twitter", "/#"), - social_link("facebook", "/#"), - social_link("linkedin", "/#"), - spacing="3", - justify_content=["center", "center", "end"], - width="100%" - ) - -def footer_three_columns() -> rx.Component: - return rx.el.footer( - rx.vstack( - rx.flex( - footer_items_1(), - footer_items_2(), - footer_items_3(), - justify="between", - spacing="6", - flex_direction=["column", "column", "row"], - width="100%" - ), - rx.divider(), - rx.flex( - rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), - rx.text("© 2024 Reflex, Inc", size="3", white_space="nowrap", weight="medium"), - spacing="2", - align="center", - justify_content=["center", "center", "start"], - width="100%" - ), - socials(), - spacing="4", - flex_direction=["column", "column", "row"], - width="100%" - ), - spacing="5", - width="100%" - ), - width="100%" - ) -``` diff --git a/docs/recipes/layout/navbar.md b/docs/recipes/layout/navbar.md deleted file mode 100644 index 84a7f29e3f..0000000000 --- a/docs/recipes/layout/navbar.md +++ /dev/null @@ -1,367 +0,0 @@ -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -``` - -# Navigation Bar - -A navigation bar, also known as a navbar, is a common UI element found at the top of a webpage or application. -It typically provides links or buttons to the main sections of a website or application, allowing users to easily navigate and access the different pages. - -Navigation bars are useful for web apps because they provide a consistent and intuitive way for users to navigate through the app. -Having a clear and consistent navigation structure can greatly improve the user experience by making it easy for users to find the information they need and access the different features of the app. - - -```md video https://youtube.com/embed/ITOZkzjtjUA?start=2365&end=2627 -# Video: Example of Using the Navbar Recipe -``` - -## Basic - -```python demo exec toggle -def navbar_link(text: str, url: str) -> rx.Component: - return rx.link(rx.text(text, size="4", weight="medium"), href=url) - -def navbar() -> rx.Component: - return rx.box( - rx.desktop_only( - rx.hstack( - rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), - rx.heading("Reflex", size="7", weight="bold"), align_items="center"), - rx.hstack( - navbar_link("Home", "/#"), - navbar_link("About", "/#"), - navbar_link("Pricing", "/#"), - navbar_link("Contact", "/#"), - justify="end", - spacing="5" - ), - justify="between", - align_items="center" - ), - ), - rx.mobile_and_tablet( - rx.hstack( - rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", - height="auto", border_radius="25%"), - rx.heading("Reflex", size="6", weight="bold"), align_items="center"), - rx.menu.root( - rx.menu.trigger(rx.icon("menu", size=30)), - rx.menu.content( - rx.menu.item("Home"), - rx.menu.item("About"), - rx.menu.item("Pricing"), - rx.menu.item("Contact"), - ), - justify="end" - ), - justify="between", - align_items="center" - ), - ), - bg=rx.color("accent", 3), - padding="1em", - # position="fixed", - # top="0px", - # z_index="5", - width="100%" - ) -``` - - -## Dropdown - -```python demo exec toggle -def navbar_link(text: str, url: str) -> rx.Component: - return rx.link(rx.text(text, size="4", weight="medium"), href=url) - -def navbar_dropdown() -> rx.Component: - return rx.box( - rx.desktop_only( - rx.hstack( - rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), - rx.heading("Reflex", size="7", weight="bold"), align_items="center"), - rx.hstack( - navbar_link("Home", "/#"), - rx.menu.root( - rx.menu.trigger( - rx.button(rx.text("Services", size="4", weight="medium"), rx.icon( - "chevron-down"), weight="medium", variant="ghost", size="3"), - ), - rx.menu.content( - rx.menu.item("Service 1"), - rx.menu.item("Service 2"), - rx.menu.item("Service 3"), - ), - ), - navbar_link("Pricing", "/#"), - navbar_link("Contact", "/#"), - justify="end", - spacing="5" - ), - justify="between", - align_items="center" - ), - ), - rx.mobile_and_tablet( - rx.hstack( - rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), - rx.heading("Reflex", size="6", weight="bold"), align_items="center"), - rx.menu.root( - rx.menu.trigger(rx.icon("menu", size=30)), - rx.menu.content( - rx.menu.item("Home"), - rx.menu.sub( - rx.menu.sub_trigger("Services"), - rx.menu.sub_content( - rx.menu.item("Service 1"), - rx.menu.item("Service 2"), - rx.menu.item("Service 3"), - ), - ), - rx.menu.item("About"), - rx.menu.item("Pricing"), - rx.menu.item("Contact"), - ), - justify="end", - ), - justify="between", - align_items="center" - ), - ), - bg=rx.color("accent", 3), - padding="1em", - # position="fixed", - # top="0px", - # z_index="5", - width="100%" - ) -``` - -## Search bar - -```python demo exec toggle -def navbar_searchbar() -> rx.Component: - return rx.box( - rx.desktop_only( - rx.hstack( - rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), - rx.heading("Reflex", size="7", weight="bold"), align_items="center"), - rx.input( - rx.input.slot(rx.icon("search")), - placeholder="Search...", - type="search", size="2", - justify="end", - ), - justify="between", - align_items="center" - ), - ), - rx.mobile_and_tablet( - rx.hstack( - rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), - rx.heading("Reflex", size="6", weight="bold"), align_items="center"), - rx.input( - rx.input.slot(rx.icon("search")), - placeholder="Search...", - type="search", size="2", - justify="end", - ), - justify="between", - align_items="center" - ), - ), - bg=rx.color("accent", 3), - padding="1em", - # position="fixed", - # top="0px", - # z_index="5", - width="100%" - ) -``` - -## Icons - -```python demo exec toggle -def navbar_icons_item(text: str, icon: str, url: str) -> rx.Component: - return rx.link(rx.hstack(rx.icon(icon), rx.text(text, size="4", weight="medium")), href=url) - -def navbar_icons_menu_item(text: str, icon: str, url: str) -> rx.Component: - return rx.link(rx.hstack(rx.icon(icon, size=16), rx.text(text, size="3", weight="medium")), href=url) - -def navbar_icons() -> rx.Component: - return rx.box( - rx.desktop_only( - rx.hstack( - rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), - rx.heading("Reflex", size="7", weight="bold"), align_items="center"), - rx.hstack( - navbar_icons_item("Home", "home", "/#"), - navbar_icons_item("Pricing", "coins", "/#"), - navbar_icons_item("Contact", "mail", "/#"), - navbar_icons_item("Services", "layers", "/#"), - spacing="6", - ), - justify="between", - align_items="center" - ), - ), - rx.mobile_and_tablet( - rx.hstack( - rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), - rx.heading("Reflex", size="6", weight="bold"), align_items="center"), - rx.menu.root( - rx.menu.trigger(rx.icon("menu", size=30)), - rx.menu.content( - navbar_icons_menu_item("Home", "home", "/#"), - navbar_icons_menu_item("Pricing", "coins", "/#"), - navbar_icons_menu_item("Contact", "mail", "/#"), - navbar_icons_menu_item("Services", "layers", "/#"), - ), - justify="end", - ), - justify="between", - align_items="center" - ), - ), - bg=rx.color("accent", 3), - padding="1em", - # position="fixed", - # top="0px", - # z_index="5", - width="100%" - ) -``` - -## Buttons - -```python demo exec toggle -def navbar_link(text: str, url: str) -> rx.Component: - return rx.link(rx.text(text, size="4", weight="medium"), href=url) - -def navbar_buttons() -> rx.Component: - return rx.box( - rx.desktop_only( - rx.hstack( - rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), - rx.heading("Reflex", size="7", weight="bold"), align_items="center"), - rx.hstack( - navbar_link("Home", "/#"), - navbar_link("About", "/#"), - navbar_link("Pricing", "/#"), - navbar_link("Contact", "/#"), - spacing="5", - ), - rx.hstack( - rx.button("Sign Up", size="3", variant="outline"), - rx.button("Log In", size="3"), - spacing="4", - justify="end", - ), - justify="between", - align_items="center" - ), - ), - rx.mobile_and_tablet( - rx.hstack( - rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), - rx.heading("Reflex", size="6", weight="bold"), align_items="center"), - rx.menu.root( - rx.menu.trigger(rx.icon("menu", size=30)), - rx.menu.content( - rx.menu.item("Home"), - rx.menu.item("About"), - rx.menu.item("Pricing"), - rx.menu.item("Contact"), - rx.menu.separator(), - rx.menu.item("Log in"), - rx.menu.item("Sign up"), - ), - justify="end", - ), - justify="between", - align_items="center" - ), - ), - bg=rx.color("accent", 3), - padding="1em", - # position="fixed", - # top="0px", - # z_index="5", - width="100%" - ) -``` - -## User profile - -```python demo exec toggle -def navbar_link(text: str, url: str) -> rx.Component: - return rx.link(rx.text(text, size="4", weight="medium"), href=url) - -def navbar_user() -> rx.Component: - return rx.box( - rx.desktop_only( - rx.hstack( - rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", height="auto", border_radius="25%"), - rx.heading("Reflex", size="7", weight="bold"), align_items="center"), - rx.hstack( - navbar_link("Home", "/#"), - navbar_link("About", "/#"), - navbar_link("Pricing", "/#"), - navbar_link("Contact", "/#"), - spacing="5", - ), - rx.menu.root( - rx.menu.trigger(rx.icon_button( - rx.icon("user"), size="2", radius="full")), - rx.menu.content( - rx.menu.item("Settings"), - rx.menu.item("Earnings"), - rx.menu.separator(), - rx.menu.item("Log out"), - ), - justify="end", - ), - justify="between", - align_items="center" - ), - ), - rx.mobile_and_tablet( - rx.hstack( - rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2em", height="auto", border_radius="25%"), - rx.heading("Reflex", size="6", weight="bold"), align_items="center"), - rx.menu.root( - rx.menu.trigger(rx.icon_button( - rx.icon("user"), size="2", radius="full")), - rx.menu.content( - rx.menu.item("Settings"), - rx.menu.item("Earnings"), - rx.menu.separator(), - rx.menu.item("Log out"), - ), - justify="end", - ), - justify="between", - align_items="center" - ), - ), - bg=rx.color("accent", 3), - padding="1em", - # position="fixed", - # top="0px", - # z_index="5", - width="100%" - ) -``` diff --git a/docs/recipes/layout/sidebar.md b/docs/recipes/layout/sidebar.md deleted file mode 100644 index e30f90d566..0000000000 --- a/docs/recipes/layout/sidebar.md +++ /dev/null @@ -1,421 +0,0 @@ -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN - -def sidebar_item(text: str, icon: str, href: str) -> rx.Component: - return rx.link( - rx.hstack( - rx.icon(icon), - rx.text(text, size="4"), - width="100%", - padding_x="0.5rem", - padding_y="0.75rem", - align="center", - style={ - "_hover": { - "bg": rx.color("accent", 4), - "color": rx.color("accent", 11), - }, - "border-radius": "0.5em", - }, - ), - href=href, - underline="none", - weight="medium", - width="100%" - ) - -def sidebar_items() -> rx.Component: - return rx.vstack( - sidebar_item("Dashboard", "layout-dashboard", "/#"), - sidebar_item("Projects", "square-library", "/#"), - sidebar_item("Analytics", "bar-chart-4", "/#"), - sidebar_item("Messages", "mail", "/#"), - spacing="1", - width="100%" - ) -``` - -# Sidebar - -Similar to a navigation bar, a sidebar is a common UI element found on the side of a webpage or application. It typically contains links to different sections of the site or app. - -## Basic - -```python demo exec toggle -def sidebar_item(text: str, icon: str, href: str) -> rx.Component: - return rx.link( - rx.hstack( - rx.icon(icon), - rx.text(text, size="4"), - width="100%", - padding_x="0.5rem", - padding_y="0.75rem", - align="center", - style={ - "_hover": { - "bg": rx.color("accent", 4), - "color": rx.color("accent", 11), - }, - "border-radius": "0.5em", - }, - ), - href=href, - underline="none", - weight="medium", - width="100%" - ) - -def sidebar_items() -> rx.Component: - return rx.vstack( - sidebar_item("Dashboard", "layout-dashboard", "/#"), - sidebar_item("Projects", "square-library", "/#"), - sidebar_item("Analytics", "bar-chart-4", "/#"), - sidebar_item("Messages", "mail", "/#"), - spacing="1", - width="100%" - ) - -def sidebar() -> rx.Component: - return rx.box( - rx.desktop_only( - rx.vstack( - rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", - height="auto", border_radius="25%"), - rx.heading("Reflex", size="7", weight="bold"), - align="center", - justify="start", - padding_x="0.5rem", - width="100%" - ), - sidebar_items(), - spacing="5", - #position="fixed", - # left="0px", - # top="0px", - # z_index="5", - padding_x="1em", - padding_y="1.5em", - bg=rx.color("accent", 3), - align="start", - #height="100%", - height="650px", - width="16em", - ), - ), - rx.mobile_and_tablet( - rx.drawer.root( - rx.drawer.trigger(rx.icon("align-justify", size=30)), - rx.drawer.overlay(z_index="5"), - rx.drawer.portal( - rx.drawer.content( - rx.vstack( - rx.box( - rx.drawer.close(rx.icon("x", size=30)), - width="100%", - ), - sidebar_items(), - spacing="5", - width="100%", - ), - top="auto", - right="auto", - height="100%", - width="20em", - padding="1.5em", - bg=rx.color("accent", 2) - ), - width="100%", - ), - direction="left" - ), - padding="1em", - ), - ) -``` - -## Bottom user profile - -```python demo exec toggle -def sidebar_item(text: str, icon: str, href: str) -> rx.Component: - return rx.link( - rx.hstack( - rx.icon(icon), - rx.text(text, size="4"), - width="100%", - padding_x="0.5rem", - padding_y="0.75rem", - align="center", - style={ - "_hover": { - "bg": rx.color("accent", 4), - "color": rx.color("accent", 11), - }, - "border-radius": "0.5em", - }, - ), - href=href, - underline="none", - weight="medium", - width="100%" - ) - -def sidebar_items() -> rx.Component: - return rx.vstack( - sidebar_item("Dashboard", "layout-dashboard", "/#"), - sidebar_item("Projects", "square-library", "/#"), - sidebar_item("Analytics", "bar-chart-4", "/#"), - sidebar_item("Messages", "mail", "/#"), - spacing="1", - width="100%" - ) - -def sidebar_bottom_profile() -> rx.Component: - return rx.box( - rx.desktop_only( - rx.vstack( - rx.hstack( - rx.image(src=f"{REFLEX_ASSETS_CDN}other/logo.jpg", width="2.25em", - height="auto", border_radius="25%"), - rx.heading("Reflex", size="7", weight="bold"), - align="center", - justify="start", - padding_x="0.5rem", - width="100%" - ), - sidebar_items(), - rx.spacer(), - rx.vstack( - rx.vstack( - sidebar_item("Settings", "settings", "/#"), - sidebar_item("Log out", "log-out", "/#"), - spacing="1", - width="100%" - ), - rx.divider(), - rx.hstack( - rx.icon_button(rx.icon("user"), size="3", radius="full"), - rx.vstack( - rx.box( - rx.text("My account", size="3", weight="bold"), - rx.text("user@reflex.dev", size="2", weight="medium"), - width="100%" - ), - spacing="0", - align="start", - justify="start", - width="100%" - ), - padding_x="0.5rem", - align="center", - justify="start", - width="100%", - ), - width="100%", - spacing="5", - ), - spacing="5", - #position="fixed", - # left="0px", - # top="0px", - # z_index="5", - padding_x="1em", - padding_y="1.5em", - bg=rx.color("accent", 3), - align="start", - #height="100%", - height="650px", - width="16em", - ), - ), - rx.mobile_and_tablet( - rx.drawer.root( - rx.drawer.trigger(rx.icon("align-justify", size=30)), - rx.drawer.overlay(z_index="5"), - rx.drawer.portal( - rx.drawer.content( - rx.vstack( - rx.box( - rx.drawer.close(rx.icon("x", size=30)), - width="100%", - ), - sidebar_items(), - rx.spacer(), - rx.vstack( - rx.vstack( - sidebar_item("Settings", "settings", "/#"), - sidebar_item("Log out", "log-out", "/#"), - width="100%", - spacing="1", - ), - rx.divider(margin="0"), - rx.hstack( - rx.icon_button(rx.icon("user"), size="3", radius="full"), - rx.vstack( - rx.box( - rx.text("My account", size="3", weight="bold"), - rx.text("user@reflex.dev", size="2", weight="medium"), - width="100%" - ), - spacing="0", - justify="start", - width="100%", - ), - padding_x="0.5rem", - align="center", - justify="start", - width="100%", - ), - width="100%", - spacing="5", - ), - spacing="5", - width="100%", - ), - top="auto", - right="auto", - height="100%", - width="20em", - padding="1.5em", - bg=rx.color("accent", 2) - ), - width="100%", - ), - direction="left" - ), - padding="1em", - ), - ) -``` - -## Top user profile - -```python demo exec toggle -def sidebar_item(text: str, icon: str, href: str) -> rx.Component: - return rx.link( - rx.hstack( - rx.icon(icon), - rx.text(text, size="4"), - width="100%", - padding_x="0.5rem", - padding_y="0.75rem", - align="center", - style={ - "_hover": { - "bg": rx.color("accent", 4), - "color": rx.color("accent", 11), - }, - "border-radius": "0.5em", - }, - ), - href=href, - underline="none", - weight="medium", - width="100%" - ) - -def sidebar_items() -> rx.Component: - return rx.vstack( - sidebar_item("Dashboard", "layout-dashboard", "/#"), - sidebar_item("Projects", "square-library", "/#"), - sidebar_item("Analytics", "bar-chart-4", "/#"), - sidebar_item("Messages", "mail", "/#"), - spacing="1", - width="100%" - ) - -def sidebar_top_profile() -> rx.Component: - return rx.box( - rx.desktop_only( - rx.vstack( - rx.hstack( - rx.icon_button(rx.icon("user"), size="3", radius="full"), - rx.vstack( - rx.box( - rx.text("My account", size="3", weight="bold"), - rx.text("user@reflex.dev", size="2", weight="medium"), - width="100%" - ), - spacing="0", - justify="start", - width="100%", - ), - rx.spacer(), - rx.icon_button(rx.icon("settings"), size="2", - variant="ghost", color_scheme="gray"), - padding_x="0.5rem", - align="center", - width="100%", - ), - sidebar_items(), - rx.spacer(), - sidebar_item("Help & Support", "life-buoy", "/#"), - spacing="5", - #position="fixed", - # left="0px", - # top="0px", - # z_index="5", - padding_x="1em", - padding_y="1.5em", - bg=rx.color("accent", 3), - align="start", - #height="100%", - height="650px", - width="16em", - ), - ), - rx.mobile_and_tablet( - rx.drawer.root( - rx.drawer.trigger(rx.icon("align-justify", size=30)), - rx.drawer.overlay(z_index="5"), - rx.drawer.portal( - rx.drawer.content( - rx.vstack( - rx.box( - rx.drawer.close(rx.icon("x", size=30)), - width="100%", - ), - sidebar_items(), - rx.spacer(), - rx.vstack( - sidebar_item("Help & Support", "life-buoy", "/#"), - rx.divider(margin="0"), - rx.hstack( - rx.icon_button(rx.icon("user"), size="3", radius="full"), - rx.vstack( - rx.box( - rx.text("My account", size="3", weight="bold"), - rx.text("user@reflex.dev", size="2", weight="medium"), - width="100%" - ), - spacing="0", - justify="start", - width="100%", - ), - padding_x="0.5rem", - align="center", - justify="start", - width="100%", - ), - width="100%", - spacing="5", - ), - spacing="5", - width="100%", - ), - top="auto", - right="auto", - height="100%", - width="20em", - padding="1.5em", - bg=rx.color("accent", 2) - ), - width="100%", - ), - direction="left" - ), - padding="1em", - ), - ) -``` diff --git a/docs/recipes/others/checkboxes.md b/docs/recipes/others/checkboxes.md deleted file mode 100644 index 5d6392fa2b..0000000000 --- a/docs/recipes/others/checkboxes.md +++ /dev/null @@ -1,68 +0,0 @@ -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -``` - -# Smart Checkboxes Group - -A smart checkboxes group where you can track all checked boxes, as well as place a limit on how many checks are possible. - -## Recipe - -```python eval -rx.center(rx.image(src=f"{REFLEX_ASSETS_CDN}templates/smart_checkboxes.webp")) -``` - -This recipe use a `dict[str, bool]` for the checkboxes state tracking. -Additionally, the limit that prevent the user from checking more boxes than allowed with a computed var. - -```python -class CBoxeState(rx.State): - - choices: dict[str, bool] = \{k: False for k in ["Choice A", "Choice B", "Choice C"]} - _check_limit = 2 - - def check_choice(self, value, index): - self.choices[index] = value - - @rx.var - def choice_limit(self): - return sum(self.choices.values()) >= self._check_limit - - @rx.var - def checked_choices(self): - choices = [l for l, v in self.choices.items() if v] - return " / ".join(choices) if choices else "None" - -import reflex as rx - - -def render_checkboxes(values, limit, handler): - return rx.vstack( - rx.foreach( - values, - lambda choice: rx.checkbox( - choice[0], - checked=choice[1], - disabled=~choice[1] & limit, - on_change=lambda val: handler(val, choice[0]), - ), - ) - ) - - -def index() -> rx.Component: - - return rx.center( - rx.vstack( - rx.text("Make your choices (2 max):"), - render_checkboxes( - CBoxeState.choices, - CBoxeState.choice_limit, - CBoxeState.check_choice, - ), - rx.text("Your choices: ", CBoxeState.checked_choices), - ), - height="100vh", - ) -``` diff --git a/docs/recipes/others/chips.md b/docs/recipes/others/chips.md deleted file mode 100644 index c72ab54aa5..0000000000 --- a/docs/recipes/others/chips.md +++ /dev/null @@ -1,247 +0,0 @@ -```python exec -import reflex as rx - -``` - -# Chips - -Chips are compact elements that represent small pieces of information, such as tags or categories. They are commonly used to select multiple items from a list or to filter content. - -## Status - -```python demo exec toggle -from reflex.components.radix.themes.base import LiteralAccentColor - -status_chip_props = { - "radius": "full", - "variant": "outline", - "size": "3", -} - -def status_chip(status: str, icon: str, color: LiteralAccentColor) -> rx.Component: - return rx.badge( - rx.icon(icon, size=18), - status, - color_scheme=color, - **status_chip_props, - ) - -def status_chips_group() -> rx.Component: - return rx.hstack( - status_chip("Info", "info", "blue"), - status_chip("Success", "circle-check", "green"), - status_chip("Warning", "circle-alert", "yellow"), - status_chip("Error", "circle-x", "red"), - wrap="wrap", - spacing="2", - ) -``` - -## Single selection - -```python demo exec toggle -chip_props = { - "radius": "full", - "variant": "soft", - "size": "3", - "cursor": "pointer", - "style": {"_hover": {"opacity": 0.75}}, -} - -available_items = ["2:00", "3:00", "4:00", "5:00"] - -class SingleSelectionChipsState(rx.State): - selected_item: str = "" - - @rx.event - def set_selected_item(self, value: str): - self.selected_item = value - -def unselected_item(item: str) -> rx.Component: - return rx.badge( - item, - color_scheme="gray", - **chip_props, - on_click=SingleSelectionChipsState.set_selected_item(item), - ) - -def selected_item(item: str) -> rx.Component: - return rx.badge( - rx.icon("check", size=18), - item, - color_scheme="mint", - **chip_props, - on_click=SingleSelectionChipsState.set_selected_item(""), - ) - -def item_chip(item: str) -> rx.Component: - return rx.cond( - SingleSelectionChipsState.selected_item == item, - selected_item(item), - unselected_item(item), - ) - -def item_selector() -> rx.Component: - return rx.vstack( - rx.hstack( - rx.icon("clock", size=20), - rx.heading( - "Select your reservation time:", size="4" - ), - spacing="2", - align="center", - width="100%", - ), - rx.hstack( - rx.foreach(available_items, item_chip), - wrap="wrap", - spacing="2", - ), - align_items="start", - spacing="4", - width="100%", - ) -``` - -## Multiple selection - -This example demonstrates selecting multiple skills from a list. It includes buttons to add all skills, clear selected skills, and select a random number of skills. - -```python demo exec toggle -import random -from reflex.components.radix.themes.base import LiteralAccentColor - -chip_props = { - "radius": "full", - "variant": "surface", - "size": "3", - "cursor": "pointer", - "style": {"_hover": {"opacity": 0.75}}, -} - -skills = [ - "Data Management", - "Networking", - "Security", - "Cloud", - "DevOps", - "Data Science", - "AI", - "ML", - "Robotics", - "Cybersecurity", -] - -class BasicChipsState(rx.State): - selected_items: list[str] = skills[:3] - - @rx.event - def add_selected(self, item: str): - self.selected_items.append(item) - - @rx.event - def remove_selected(self, item: str): - self.selected_items.remove(item) - - @rx.event - def add_all_selected(self): - self.selected_items = list(skills) - - @rx.event - def clear_selected(self): - self.selected_items.clear() - - @rx.event - def random_selected(self): - self.selected_items = random.sample(skills, k=random.randint(1, len(skills))) - -def action_button(icon: str, label: str, on_click: callable, color_scheme: LiteralAccentColor) -> rx.Component: - return rx.button( - rx.icon(icon, size=16), - label, - variant="soft", - size="2", - on_click=on_click, - color_scheme=color_scheme, - cursor="pointer", - ) - -def selected_item_chip(item: str) -> rx.Component: - return rx.badge( - item, - rx.icon("circle-x", size=18), - color_scheme="green", - **chip_props, - on_click=BasicChipsState.remove_selected(item), - ) - -def unselected_item_chip(item: str) -> rx.Component: - return rx.cond( - BasicChipsState.selected_items.contains(item), - rx.fragment(), - rx.badge( - item, - rx.icon("circle-plus", size=18), - color_scheme="gray", - **chip_props, - on_click=BasicChipsState.add_selected(item), - ), - ) - -def items_selector() -> rx.Component: - return rx.vstack( - rx.flex( - rx.hstack( - rx.icon("lightbulb", size=20), - rx.heading( - "Skills" + f" ({BasicChipsState.selected_items.length()})", size="4" - ), - spacing="1", - align="center", - width="100%", - justify_content=["end", "start"], - ), - rx.hstack( - action_button( - "plus", "Add All", BasicChipsState.add_all_selected, "green" - ), - action_button( - "trash", "Clear All", BasicChipsState.clear_selected, "tomato" - ), - action_button( - "shuffle", "", BasicChipsState.random_selected, "gray" - ), - spacing="2", - justify="end", - width="100%", - ), - justify="between", - flex_direction=["column", "row"], - align="center", - spacing="2", - margin_bottom="10px", - width="100%", - ), - # Selected Items - rx.flex( - rx.foreach( - BasicChipsState.selected_items, - selected_item_chip, - ), - wrap="wrap", - spacing="2", - justify_content="start", - ), - rx.divider(), - # Unselected Items - rx.flex( - rx.foreach(skills, unselected_item_chip), - wrap="wrap", - spacing="2", - justify_content="start", - ), - justify_content="start", - align_items="start", - width="100%", - ) -``` diff --git a/docs/recipes/others/dark_mode_toggle.md b/docs/recipes/others/dark_mode_toggle.md deleted file mode 100644 index c648941157..0000000000 --- a/docs/recipes/others/dark_mode_toggle.md +++ /dev/null @@ -1,33 +0,0 @@ -```python exec -import reflex as rx -from reflex.style import set_color_mode, color_mode -``` - -# Dark Mode Toggle - -The Dark Mode Toggle component lets users switch between light and dark themes. - -```python demo exec toggle -import reflex as rx -from reflex.style import set_color_mode, color_mode - -def dark_mode_toggle() -> rx.Component: - return rx.segmented_control.root( - rx.segmented_control.item( - rx.icon(tag="monitor", size=20), - value="system", - ), - rx.segmented_control.item( - rx.icon(tag="sun", size=20), - value="light", - ), - rx.segmented_control.item( - rx.icon(tag="moon", size=20), - value="dark", - ), - on_change=set_color_mode, - variant="classic", - radius="large", - value=color_mode, - ) -``` diff --git a/docs/recipes/others/pricing_cards.md b/docs/recipes/others/pricing_cards.md deleted file mode 100644 index 0efe8b9580..0000000000 --- a/docs/recipes/others/pricing_cards.md +++ /dev/null @@ -1,199 +0,0 @@ -```python exec -import reflex as rx -``` - -# Pricing Cards - -A pricing card shows the price of a product or service. It typically includes a title, description, price, features, and a purchase button. - -## Basic - -```python demo exec toggle -def feature_item(text: str) -> rx.Component: - return rx.hstack(rx.icon("check", color=rx.color("grass", 9)), rx.text(text, size="4")) - -def features() -> rx.Component: - return rx.vstack( - feature_item("24/7 customer support"), - feature_item("Daily backups"), - feature_item("Advanced analytics"), - feature_item("Customizable templates"), - feature_item("Priority email support"), - width="100%", - align_items="start", - ) - -def pricing_card_beginner() -> rx.Component: - return rx.vstack( - rx.vstack( - rx.text("Beginner", weight="bold", size="6"), - rx.text("Ideal choice for personal use & for your next project.", size="4", opacity=0.8, align="center"), - rx.hstack( - rx.text("$39", weight="bold", font_size="3rem", trim="both"), - rx.text("/month", size="4", opacity=0.8, trim="both"), - width="100%", - align_items="end", - justify="center" - ), - width="100%", - align="center", - spacing="6", - ), - features(), - rx.button("Get started", size="3", variant="solid", width="100%", color_scheme="blue"), - spacing="6", - border=f"1.5px solid {rx.color('gray', 5)}", - background=rx.color("gray", 1), - padding="28px", - width="100%", - max_width="400px", - justify="center", - border_radius="0.5rem", - ) -``` - -## Comparison cards - -```python demo exec toggle -def feature_item(feature: str) -> rx.Component: - return rx.hstack( - rx.icon("check", color=rx.color("blue", 9), size=21), - rx.text(feature, size="4", weight="regular"), - ) - - -def standard_features() -> rx.Component: - return rx.vstack( - feature_item("40 credits for image generation"), - feature_item("Credits never expire"), - feature_item("High quality images"), - feature_item("Commercial license"), - spacing="3", - width="100%", - align_items="start", - ) - - -def popular_features() -> rx.Component: - return rx.vstack( - feature_item("250 credits for image generation"), - feature_item("+30% Extra free credits"), - feature_item("Credits never expire"), - feature_item("High quality images"), - feature_item("Commercial license"), - spacing="3", - width="100%", - align_items="start", - ) - - -def pricing_card_standard() -> rx.Component: - return rx.vstack( - rx.hstack( - rx.hstack( - rx.text( - "$14.99", - trim="both", - as_="s", - size="3", - weight="regular", - opacity=0.8, - ), - rx.text("$3.99", trim="both", size="6", weight="regular"), - width="100%", - spacing="2", - align_items="end", - ), - height="35px", - align_items="center", - justify="between", - width="100%", - ), - rx.text( - "40 Image Credits", - weight="bold", - size="7", - width="100%", - text_align="left", - ), - standard_features(), - rx.spacer(), - rx.button( - "Purchase", - size="3", - variant="outline", - width="100%", - color_scheme="blue", - ), - spacing="6", - border=f"1.5px solid {rx.color('gray', 5)}", - background=rx.color("gray", 1), - padding="28px", - width="100%", - max_width="400px", - min_height="475px", - border_radius="0.5rem", - ) - - -def pricing_card_popular() -> rx.Component: - return rx.vstack( - rx.hstack( - rx.hstack( - rx.text( - "$69.99", - trim="both", - as_="s", - size="3", - weight="regular", - opacity=0.8, - ), - rx.text("$18.99", trim="both", size="6", weight="regular"), - width="100%", - spacing="2", - align_items="end", - ), - rx.badge( - "POPULAR", - size="2", - radius="full", - variant="soft", - color_scheme="blue", - ), - align_items="center", - justify="between", - height="35px", - width="100%", - ), - rx.text( - "250 Image Credits", - weight="bold", - size="7", - width="100%", - text_align="left", - ), - popular_features(), - rx.spacer(), - rx.button("Purchase", size="3", width="100%", color_scheme="blue"), - spacing="6", - border=f"1.5px solid {rx.color('blue', 6)}", - background=rx.color("blue", 1), - padding="28px", - width="100%", - max_width="400px", - min_height="475px", - border_radius="0.5rem", - ) - - -def pricing_cards() -> rx.Component: - return rx.flex( - pricing_card_standard(), - pricing_card_popular(), - spacing="4", - flex_direction=["column", "column", "row"], - width="100%", - align_items="center", - ) -``` - diff --git a/docs/recipes/others/speed_dial.md b/docs/recipes/others/speed_dial.md deleted file mode 100644 index 4d2086ec08..0000000000 --- a/docs/recipes/others/speed_dial.md +++ /dev/null @@ -1,454 +0,0 @@ -```python exec -import reflex as rx -``` - -# Speed Dial - -A speed dial is a component that allows users to quickly access frequently used actions or pages. It is often used in the bottom right corner of the screen. - -# Vertical - -```python demo exec toggle -class SpeedDialVertical(rx.ComponentState): - is_open: bool = False - - @rx.event - def toggle(self, value: bool): - self.is_open = value - - @classmethod - def get_component(cls, **props): - def menu_item(icon: str, text: str) -> rx.Component: - return rx.tooltip( - rx.icon_button( - rx.icon(icon, padding="2px"), - variant="soft", - color_scheme="gray", - size="3", - cursor="pointer", - radius="full", - ), - side="left", - content=text, - ) - - def menu() -> rx.Component: - return rx.vstack( - menu_item("copy", "Copy"), - menu_item("download", "Download"), - menu_item("share-2", "Share"), - position="absolute", - bottom="100%", - spacing="2", - padding_bottom="10px", - left="0", - direction="column-reverse", - align_items="center", - ) - - return rx.box( - rx.box( - rx.icon_button( - rx.icon( - "plus", - style={ - "transform": rx.cond(cls.is_open, "rotate(45deg)", "rotate(0)"), - "transition": "transform 150ms cubic-bezier(0.4, 0, 0.2, 1)", - }, - ), - variant="solid", - color_scheme="blue", - size="3", - cursor="pointer", - radius="full", - position="relative", - ), - rx.cond( - cls.is_open, - menu(), - ), - position="relative", - ), - on_mouse_enter=cls.toggle(True), - on_mouse_leave=cls.toggle(False), - on_click=cls.toggle(~cls.is_open), - style={"bottom": "15px", "right": "15px"}, - position="absolute", - # z_index="50", - **props, - ) - -speed_dial_vertical = SpeedDialVertical.create - -def render_vertical(): - return rx.box( - speed_dial_vertical(), - height="250px", - position="relative", - width="100%", - ) -``` - -# Horizontal - -```python demo exec toggle -class SpeedDialHorizontal(rx.ComponentState): - is_open: bool = False - - @rx.event - def toggle(self, value: bool): - self.is_open = value - - @classmethod - def get_component(cls, **props): - def menu_item(icon: str, text: str) -> rx.Component: - return rx.tooltip( - rx.icon_button( - rx.icon(icon, padding="2px"), - variant="soft", - color_scheme="gray", - size="3", - cursor="pointer", - radius="full", - ), - side="top", - content=text, - ) - - def menu() -> rx.Component: - return rx.hstack( - menu_item("copy", "Copy"), - menu_item("download", "Download"), - menu_item("share-2", "Share"), - position="absolute", - bottom="0", - spacing="2", - padding_right="10px", - right="100%", - direction="row-reverse", - align_items="center", - ) - - return rx.box( - rx.box( - rx.icon_button( - rx.icon( - "plus", - style={ - "transform": rx.cond(cls.is_open, "rotate(45deg)", "rotate(0)"), - "transition": "transform 150ms cubic-bezier(0.4, 0, 0.2, 1)", - }, - class_name="dial", - ), - variant="solid", - color_scheme="green", - size="3", - cursor="pointer", - radius="full", - position="relative", - ), - rx.cond( - cls.is_open, - menu(), - ), - position="relative", - ), - on_mouse_enter=cls.toggle(True), - on_mouse_leave=cls.toggle(False), - on_click=cls.toggle(~cls.is_open), - style={"bottom": "15px", "right": "15px"}, - position="absolute", - # z_index="50", - **props, - ) - -speed_dial_horizontal = SpeedDialHorizontal.create - -def render_horizontal(): - return rx.box( - speed_dial_horizontal(), - height="250px", - position="relative", - width="100%", - ) -``` - -# Vertical with text - -```python demo exec toggle -class SpeedDialVerticalText(rx.ComponentState): - is_open: bool = False - - @rx.event - def toggle(self, value: bool): - self.is_open = value - - @classmethod - def get_component(cls, **props): - def menu_item(icon: str, text: str) -> rx.Component: - return rx.hstack( - rx.text(text, weight="medium"), - rx.icon_button( - rx.icon(icon, padding="2px"), - variant="soft", - color_scheme="gray", - size="3", - cursor="pointer", - radius="full", - position="relative", - ), - opacity="0.75", - _hover={ - "opacity": "1", - }, - align_items="center", - ) - - def menu() -> rx.Component: - return rx.vstack( - menu_item("copy", "Copy"), - menu_item("download", "Download"), - menu_item("share-2", "Share"), - position="absolute", - bottom="100%", - spacing="2", - padding_bottom="10px", - right="0", - direction="column-reverse", - align_items="end", - justify_content="end", - ) - - return rx.box( - rx.box( - rx.icon_button( - rx.icon( - "plus", - style={ - "transform": rx.cond(cls.is_open, "rotate(45deg)", "rotate(0)"), - "transition": "transform 150ms cubic-bezier(0.4, 0, 0.2, 1)", - }, - class_name="dial", - ), - variant="solid", - color_scheme="crimson", - size="3", - cursor="pointer", - radius="full", - position="relative", - ), - rx.cond( - cls.is_open, - menu(), - ), - position="relative", - ), - on_mouse_enter=cls.toggle(True), - on_mouse_leave=cls.toggle(False), - on_click=cls.toggle(~cls.is_open), - style={"bottom": "15px", "right": "15px"}, - position="absolute", - # z_index="50", - **props, - ) - -speed_dial_vertical_text = SpeedDialVerticalText.create - -def render_vertical_text(): - return rx.box( - speed_dial_vertical_text(), - height="250px", - position="relative", - width="100%", - ) -``` - -# Reveal animation - -```python demo exec toggle -class SpeedDialReveal(rx.ComponentState): - is_open: bool = False - - @rx.event - def toggle(self, value: bool): - self.is_open = value - - @classmethod - def get_component(cls, **props): - def menu_item(icon: str, text: str) -> rx.Component: - return rx.tooltip( - rx.icon_button( - rx.icon(icon, padding="2px"), - variant="soft", - color_scheme="gray", - size="3", - cursor="pointer", - radius="full", - style={ - "animation": rx.cond(cls.is_open, "reveal 0.3s ease both", "none"), - "@keyframes reveal": { - "0%": { - "opacity": "0", - "transform": "scale(0)", - }, - "100%": { - "opacity": "1", - "transform": "scale(1)", - }, - }, - }, - ), - side="left", - content=text, - ) - - def menu() -> rx.Component: - return rx.vstack( - menu_item("copy", "Copy"), - menu_item("download", "Download"), - menu_item("share-2", "Share"), - position="absolute", - bottom="100%", - spacing="2", - padding_bottom="10px", - left="0", - direction="column-reverse", - align_items="center", - ) - - return rx.box( - rx.box( - rx.icon_button( - rx.icon( - "plus", - style={ - "transform": rx.cond(cls.is_open, "rotate(45deg)", "rotate(0)"), - "transition": "transform 150ms cubic-bezier(0.4, 0, 0.2, 1)", - }, - class_name="dial", - ), - variant="solid", - color_scheme="violet", - size="3", - cursor="pointer", - radius="full", - position="relative", - ), - rx.cond( - cls.is_open, - menu(), - ), - position="relative", - ), - on_mouse_enter=cls.toggle(True), - on_mouse_leave=cls.toggle(False), - on_click=cls.toggle(~cls.is_open), - style={"bottom": "15px", "right": "15px"}, - position="absolute", - # z_index="50", - **props, - ) - -speed_dial_reveal = SpeedDialReveal.create - -def render_reveal(): - return rx.box( - speed_dial_reveal(), - height="250px", - position="relative", - width="100%", - ) -``` - -# Menu - -```python demo exec toggle -class SpeedDialMenu(rx.ComponentState): - is_open: bool = False - - @rx.event - def toggle(self, value: bool): - self.is_open = value - - @classmethod - def get_component(cls, **props): - def menu_item(icon: str, text: str) -> rx.Component: - return rx.hstack( - rx.icon(icon, padding="2px"), - rx.text(text, weight="medium"), - align="center", - opacity="0.75", - cursor="pointer", - position="relative", - _hover={ - "opacity": "1", - }, - width="100%", - align_items="center", - ) - - def menu() -> rx.Component: - return rx.box( - rx.card( - rx.vstack( - menu_item("copy", "Copy"), - rx.divider(margin="0"), - menu_item("download", "Download"), - rx.divider(margin="0"), - menu_item("share-2", "Share"), - direction="column-reverse", - align_items="end", - justify_content="end", - ), - box_shadow="0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)", - ), - position="absolute", - bottom="100%", - right="0", - padding_bottom="10px", - ) - - return rx.box( - rx.box( - rx.icon_button( - rx.icon( - "plus", - style={ - "transform": rx.cond(cls.is_open, "rotate(45deg)", "rotate(0)"), - "transition": "transform 150ms cubic-bezier(0.4, 0, 0.2, 1)", - }, - class_name="dial", - ), - variant="solid", - color_scheme="orange", - size="3", - cursor="pointer", - radius="full", - position="relative", - ), - rx.cond( - cls.is_open, - menu(), - ), - position="relative", - ), - on_mouse_enter=cls.toggle(True), - on_mouse_leave=cls.toggle(False), - on_click=cls.toggle(~cls.is_open), - style={"bottom": "15px", "right": "15px"}, - position="absolute", - # z_index="50", - **props, - ) - - -speed_dial_menu = SpeedDialMenu.create - -def render_menu(): - return rx.box( - speed_dial_menu(), - height="250px", - position="relative", - width="100%", - ) -``` diff --git a/docs/state/overview.md b/docs/state/overview.md deleted file mode 100644 index a9397a3da1..0000000000 --- a/docs/state/overview.md +++ /dev/null @@ -1,189 +0,0 @@ -```python exec -import reflex as rx -from pcweb.templates.docpage import definition -``` - -# State - -State allows us to create interactive apps that can respond to user input. -It defines the variables that can change over time, and the functions that can modify them. - -```md video https://youtube.com/embed/ITOZkzjtjUA?start=1206&end=1869 -# Video: State Overview -``` - -## State Basics - -You can define state by creating a class that inherits from `rx.State`: - -```python -import reflex as rx - - -class State(rx.State): - """Define your app state here.""" -``` - -A state class is made up of two parts: vars and event handlers. - -**Vars** are variables in your app that can change over time. - -**Event handlers** are functions that modify these vars in response to events. - -These are the main concepts to understand how state works in Reflex: - -```python eval -rx.grid( - definition( - "Base Var", - rx.list.unordered( - rx.list.item("Any variable in your app that can change over time."), - rx.list.item( - "Defined as a field in a ", rx.code("State"), " class" - ), - rx.list.item("Can only be modified by event handlers."), - ), - ), - definition( - "Computed Var", - rx.list.unordered( - rx.list.item("Vars that change automatically based on other vars."), - rx.list.item( - "Defined as functions using the ", - rx.code("@rx.var"), - " decorator.", - ), - rx.list.item( - "Cannot be set by event handlers, are always recomputed when the state changes." - ), - ), - ), - definition( - "Event Trigger", - rx.list.unordered( - rx.list.item( - "A user interaction that triggers an event, such as a button click." - ), - rx.list.item( - "Defined as special component props, such as ", - rx.code("on_click"), - ".", - ), - rx.list.item("Can be used to trigger event handlers."), - ), - ), - definition( - "Event Handlers", - rx.list.unordered( - rx.list.item( - "Functions that update the state in response to events." - ), - rx.list.item( - "Defined as methods in the ", rx.code("State"), " class." - ), - rx.list.item( - "Can be called by event triggers, or by other event handlers." - ), - ), - ), - margin_bottom="1em", - spacing="2", - columns="2", -) -``` - -## Example - -Here is a example of how to use state within a Reflex app. -Click the text to change its color. - -```python demo exec -class ExampleState(rx.State): - - # A base var for the list of colors to cycle through. - colors: list[str] = ["black", "red", "green", "blue", "purple"] - - # A base var for the index of the current color. - index: int = 0 - - @rx.event - def next_color(self): - """An event handler to go to the next color.""" - # Event handlers can modify the base vars. - # Here we reference the base vars `colors` and `index`. - self.index = (self.index + 1) % len(self.colors) - - @rx.var - def color(self)-> str: - """A computed var that returns the current color.""" - # Computed vars update automatically when the state changes. - return self.colors[self.index] - - -def index(): - return rx.heading( - "Welcome to Reflex!", - # Event handlers can be bound to event triggers. - on_click=ExampleState.next_color, - # State vars can be bound to component props. - color=ExampleState.color, - _hover={"cursor": "pointer"}, - ) -``` - -The base vars are `colors` and `index`. They are the only vars in the app that -may be directly modified within event handlers. - -There is a single computed var, `color`, that is a function of the base vars. It -will be computed automatically whenever the base vars change. - -The heading component links its `on_click` event to the -`ExampleState.next_color` event handler, which increments the color index. - -```md alert success -# With Reflex, you never have to write an API. - -All interactions between the frontend and backend are handled through events. -``` - -```md alert info -# State vs. Instance? - -When building the UI of your app, reference vars and event handlers via the state class (`ExampleState`). - -When writing backend event handlers, access and set vars via the instance (`self`). -``` - -```md alert warning -# Cannot print a State var. - -The code `print(ExampleState.index)` will not work because the State var values are only known at compile time. -``` - -## Client States - -Each user who opens your app has a unique ID and their own copy of the state. -This means that each user can interact with the app and modify the state -independently of other users. - -Because Reflex internally creates a new instance of the state for each user, your code should -never directly initialize a state class. - -```md alert info -# Try opening an app in multiple tabs to see how the state changes independently. -``` - -All user state is stored on the server, and all event handlers are executed on -the server. Reflex uses websockets to send events to the server, and to send -state updates back to the client. - -## Helper Methods - -Similar to backend vars, any method defined in a State class that begins with an -underscore `_` is considered a helper method. Such methods are not usable as -event triggers, but may be called from other event handler methods within the -state. - -Functionality that should only be available on the backend, such as an -authenticated action, should use helper methods to ensure it is not accidentally -or maliciously triggered by the client. diff --git a/docs/state_structure/component_state.md b/docs/state_structure/component_state.md deleted file mode 100644 index 174f90d52b..0000000000 --- a/docs/state_structure/component_state.md +++ /dev/null @@ -1,233 +0,0 @@ -```python exec -import reflex as rx -from pcweb.pages.docs import events, ui, vars -``` - -# Component State - -_New in version 0.4.6_. - -Defining a subclass of `rx.ComponentState` creates a special type of state that is tied to an -instance of a component, rather than existing globally in the app. A Component State combines -[UI code]({ui.overview.path}) with state [Vars]({vars.base_vars.path}) and -[Event Handlers]({events.events_overview.path}), -and is useful for creating reusable components which operate independently of each other. - -```md alert warning -# ComponentState cannot be used inside `rx.foreach()` as it will only create one state instance for all elements in the loop. Each iteration of the foreach will share the same state, which may lead to unexpected behavior. -``` - -## Using ComponentState - -```python demo exec -class ReusableCounter(rx.ComponentState): - count: int = 0 - - @rx.event - def set_count(self, value: int): - self.count = value - - @rx.event - def increment(self): - self.count += 1 - - @rx.event - def decrement(self): - self.count -= 1 - - @classmethod - def get_component(cls, **props): - return rx.hstack( - rx.button("Decrement", on_click=cls.decrement), - rx.text(cls.count), - rx.button("Increment", on_click=cls.increment), - **props, - ) - -reusable_counter = ReusableCounter.create - -def multiple_counters(): - return rx.vstack( - reusable_counter(), - reusable_counter(), - reusable_counter(), - ) -``` - -The vars and event handlers defined on the `ReusableCounter` -class are treated similarly to a normal State class, but will be scoped to the component instance. Each time a -`reusable_counter` is created, a new state class for that instance of the component is also created. - -The `get_component` classmethod is used to define the UI for the component and link it up to the State, which -is accessed via the `cls` argument. Other states may also be referenced by the returned component, but -`cls` will always be the instance of the `ComponentState` that is unique to the component being returned. - -## Passing Props - -Similar to a normal Component, the `ComponentState.create` classmethod accepts the arbitrary -`*children` and `**props` arguments, and by default passes them to your `get_component` classmethod. -These arguments may be used to customize the component, either by applying defaults or -passing props to certain subcomponents. - -```python eval -rx.divider() -``` - -In the following example, we implement an editable text component that allows the user to click on -the text to turn it into an input field. If the user does not provide their own `value` or `on_change` -props, then the defaults defined in the `EditableText` class will be used. - -```python demo exec -class EditableText(rx.ComponentState): - text: str = "Click to edit" - original_text: str - editing: bool = False - - @rx.event - def set_text(self, value: str): - self.text = value - - @rx.event - def start_editing(self, original_text: str): - self.original_text = original_text - self.editing = True - - @rx.event - def stop_editing(self): - self.editing = False - self.original_text = "" - - @classmethod - def get_component(cls, **props): - # Pop component-specific props with defaults before passing **props - value = props.pop("value", cls.text) - on_change = props.pop("on_change", cls.set_text) - cursor = props.pop("cursor", "pointer") - - # Set the initial value of the State var. - initial_value = props.pop("initial_value", None) - if initial_value is not None: - # Update the pydantic model to use the initial value as default. - cls.__fields__["text"].default = initial_value - - # Form elements for editing, saving and reverting the text. - edit_controls = rx.hstack( - rx.input( - value=value, - on_change=on_change, - **props, - ), - rx.icon_button( - rx.icon("x"), - on_click=[ - on_change(cls.original_text), - cls.stop_editing, - ], - type="button", - color_scheme="red", - ), - rx.icon_button(rx.icon("check")), - align="center", - width="100%", - ) - - # Return the text or the form based on the editing Var. - return rx.cond( - cls.editing, - rx.form( - edit_controls, - on_submit=lambda _: cls.stop_editing(), - ), - rx.text( - value, - on_click=cls.start_editing(value), - cursor=cursor, - **props, - ), - ) - - -editable_text = EditableText.create - - -def editable_text_example(): - return rx.vstack( - editable_text(), - editable_text(initial_value="Edit me!", color="blue"), - editable_text(initial_value="Reflex is fun", font_family="monospace", width="100%"), - ) -``` - -```python eval -rx.divider() -``` - -Because this `EditableText` component is designed to be reusable, it can handle the case -where the `value` and `on_change` are linked to a normal global state. - -```python exec -# Hack because flexdown re-inits modules -EditableText._per_component_state_instance_count = 4 -``` - -```python demo exec -class EditableTextDemoState(rx.State): - value: str = "Global state text" - - @rx.event - def set_value(self, value: str): - self.value = value - -def editable_text_with_global_state(): - return rx.vstack( - editable_text(value=EditableTextDemoState.value, on_change=EditableTextDemoState.set_value), - rx.text(EditableTextDemoState.value.upper()), - ) -``` - -## Accessing the State - -The underlying state class of a `ComponentState` is accessible via the `.State` attribute. To use it, -assign an instance of the component to a local variable, then include that instance in the page. - -```python exec -# Hack because flexdown re-inits modules -ReusableCounter._per_component_state_instance_count = 4 -``` - -```python demo exec -def counter_sum(): - counter1 = reusable_counter() - counter2 = reusable_counter() - return rx.vstack( - rx.text(f"Total: {counter1.State.count + counter2.State.count}"), - counter1, - counter2, - ) -``` - -```python eval -rx.divider() -``` - -Other components can also affect a `ComponentState` by referencing its event handlers or vars -via the `.State` attribute. - -```python exec -# Hack because flexdown re-inits modules -ReusableCounter._per_component_state_instance_count = 6 -``` - -```python demo exec -def extended_counter(): - counter1 = reusable_counter() - return rx.vstack( - counter1, - rx.hstack( - rx.icon_button(rx.icon("step_back"), on_click=counter1.State.set_count(0)), - rx.icon_button(rx.icon("plus"), on_click=counter1.State.increment), - rx.button("Double", on_click=counter1.State.set_count(counter1.State.count * 2)), - rx.button("Triple", on_click=counter1.State.set_count(counter1.State.count * 3)), - ), - ) -``` diff --git a/docs/state_structure/mixins.md b/docs/state_structure/mixins.md deleted file mode 100644 index 566f50de8f..0000000000 --- a/docs/state_structure/mixins.md +++ /dev/null @@ -1,329 +0,0 @@ -```python exec -import reflex as rx -from pcweb.templates.docpage import definition -``` - -# State Mixins - -State mixins allow you to define shared functionality that can be reused across multiple State classes. This is useful for creating reusable components, shared business logic, or common state patterns. - -## What are State Mixins? - -A state mixin is a State class marked with `mixin=True` that cannot be instantiated directly but can be inherited by other State classes. Mixins provide a way to share: - -- Base variables -- Computed variables -- Event handlers -- Backend variables - -## Basic Mixin Definition - -To create a state mixin, inherit from `rx.State` and pass `mixin=True`: - -```python demo exec -class CounterMixin(rx.State, mixin=True): - count: int = 0 - - @rx.var - def count_display(self) -> str: - return f"Count: {self.count}" - - @rx.event - def increment(self): - self.count += 1 - -class MyState(CounterMixin, rx.State): - name: str = "App" - -def counter_example(): - return rx.vstack( - rx.heading(MyState.name), - rx.text(MyState.count_display), - rx.button("Increment", on_click=MyState.increment), - spacing="4", - align="center", - ) -``` - -In this example, `MyState` automatically inherits the `count` variable, `count_display` computed variable, and `increment` event handler from `CounterMixin`. - -## Multiple Mixin Inheritance - -You can inherit from multiple mixins to combine different pieces of functionality: - -```python demo exec -class TimestampMixin(rx.State, mixin=True): - last_updated: str = "" - - @rx.event - def update_timestamp(self): - import datetime - self.last_updated = datetime.datetime.now().strftime("%H:%M:%S") - -class LoggingMixin(rx.State, mixin=True): - log_messages: list[str] = [] - - @rx.event - def log_message(self, message: str): - self.log_messages.append(message) - -class CombinedState(CounterMixin, TimestampMixin, LoggingMixin, rx.State): - app_name: str = "Multi-Mixin App" - - @rx.event - def increment_with_log(self): - self.increment() - self.update_timestamp() - self.log_message(f"Count incremented to {self.count}") - -def multi_mixin_example(): - return rx.vstack( - rx.heading(CombinedState.app_name), - rx.text(CombinedState.count_display), - rx.text(f"Last updated: {CombinedState.last_updated}"), - rx.button("Increment & Log", on_click=CombinedState.increment_with_log), - rx.cond( - CombinedState.log_messages.length() > 0, - rx.vstack( - rx.foreach( - CombinedState.log_messages[-3:], - rx.text - ), - spacing="1" - ), - rx.text("No logs yet") - ), - spacing="4", - align="center", - ) -``` - -## Backend Variables in Mixins - -Mixins can also include backend variables (prefixed with `_`) that are not sent to the client: - -```python demo exec -class DatabaseMixin(rx.State, mixin=True): - _db_connection: dict = {} # Backend only - user_count: int = 0 # Sent to client - - @rx.event - def fetch_user_count(self): - # Simulate database query - self.user_count = len(self._db_connection.get("users", [])) - -class AppState(DatabaseMixin, rx.State): - app_title: str = "User Management" - -def database_example(): - return rx.vstack( - rx.heading(AppState.app_title), - rx.text(f"User count: {AppState.user_count}"), - rx.button("Fetch Users", on_click=AppState.fetch_user_count), - spacing="4", - align="center", - ) -``` - -Backend variables are useful for storing sensitive data, database connections, or other server-side state that shouldn't be exposed to the client. - -## Computed Variables in Mixins - -Computed variables in mixins work the same as in regular State classes: - -```python demo exec -class FormattingMixin(rx.State, mixin=True): - value: float = 0.0 - - @rx.var - def formatted_value(self) -> str: - return f"${self.value:.2f}" - - @rx.var - def is_positive(self) -> bool: - return self.value > 0 - -class PriceState(FormattingMixin, rx.State): - product_name: str = "Widget" - - @rx.event - def set_price(self, price: str): - try: - self.value = float(price) - except ValueError: - self.value = 0.0 - -def formatting_example(): - return rx.vstack( - rx.heading(f"Product: {PriceState.product_name}"), - rx.text(f"Price: {PriceState.formatted_value}"), - rx.text(f"Positive: {PriceState.is_positive}"), - rx.input( - placeholder="Enter price", - on_blur=PriceState.set_price, - ), - spacing="4", - align="center", - ) -``` - -## Nested Mixin Inheritance - -Mixins can inherit from other mixins to create hierarchical functionality: - -```python demo exec -class BaseMixin(rx.State, mixin=True): - base_value: str = "base" - -class ExtendedMixin(BaseMixin, mixin=True): - extended_value: str = "extended" - - @rx.var - def combined_value(self) -> str: - return f"{self.base_value}-{self.extended_value}" - -class FinalState(ExtendedMixin, rx.State): - final_value: str = "final" - -def nested_mixin_example(): - return rx.vstack( - rx.text(f"Base: {FinalState.base_value}"), - rx.text(f"Extended: {FinalState.extended_value}"), - rx.text(f"Combined: {FinalState.combined_value}"), - rx.text(f"Final: {FinalState.final_value}"), - spacing="4", - align="center", - ) -``` - -This pattern allows you to build complex functionality by composing simpler mixins. - -## Best Practices - -```md alert info -# Mixin Design Guidelines - -- **Single Responsibility**: Each mixin should have a focused purpose -- **Avoid Deep Inheritance**: Keep mixin hierarchies shallow for clarity -- **Document Dependencies**: If mixins depend on specific variables, document them -- **Test Mixins**: Create test cases for mixin functionality -- **Naming Convention**: Use descriptive names ending with "Mixin" -``` - -## Limitations - -```md alert warning -# Important Limitations - -- Mixins cannot be instantiated directly - they must be inherited by concrete State classes -- Variable name conflicts between mixins are resolved by method resolution order (MRO) -- Mixins cannot override methods from the base State class -- The `mixin=True` parameter is required when defining a mixin -``` - -## Common Use Cases - -State mixins are particularly useful for: - -- **Form Validation**: Shared validation logic across forms -- **UI State Management**: Common modal, loading, or notification patterns -- **Logging**: Centralized logging and debugging -- **API Integration**: Shared HTTP client functionality -- **Data Formatting**: Consistent data presentation across components - -```python demo exec -class ValidationMixin(rx.State, mixin=True): - errors: dict[str, str] = {} - is_loading: bool = False - - @rx.event - def validate_email(self, email: str) -> bool: - if "@" not in email or "." not in email: - self.errors["email"] = "Invalid email format" - return False - self.errors.pop("email", None) - return True - - @rx.event - def validate_required(self, field: str, value: str) -> bool: - if not value.strip(): - self.errors[field] = f"{field.title()} is required" - return False - self.errors.pop(field, None) - return True - - @rx.event - def clear_errors(self): - self.errors = {} - -class ContactFormState(ValidationMixin, rx.State): - name: str = "" - email: str = "" - message: str = "" - - def set_name(self, value: str): - self.name = value - - def set_email(self, value: str): - self.email = value - - def set_message(self, value: str): - self.message = value - - @rx.event - def submit_form(self): - self.clear_errors() - valid_name = self.validate_required("name", self.name) - valid_email = self.validate_email(self.email) - valid_message = self.validate_required("message", self.message) - - if valid_name and valid_email and valid_message: - self.is_loading = True - yield rx.sleep(1) - self.is_loading = False - self.name = "" - self.email = "" - self.message = "" - -def validation_example(): - return rx.vstack( - rx.heading("Contact Form"), - rx.input( - placeholder="Name", - value=ContactFormState.name, - on_change=ContactFormState.set_name, - ), - rx.cond( - ContactFormState.errors.contains("name"), - rx.text(ContactFormState.errors["name"], color="red"), - ), - rx.input( - placeholder="Email", - value=ContactFormState.email, - on_change=ContactFormState.set_email, - ), - rx.cond( - ContactFormState.errors.contains("email"), - rx.text(ContactFormState.errors["email"], color="red"), - ), - rx.text_area( - placeholder="Message", - value=ContactFormState.message, - on_change=ContactFormState.set_message, - ), - rx.cond( - ContactFormState.errors.contains("message"), - rx.text(ContactFormState.errors["message"], color="red"), - ), - rx.button( - "Submit", - on_click=ContactFormState.submit_form, - loading=ContactFormState.is_loading, - ), - spacing="4", - align="center", - width="300px", - ) -``` - -By using state mixins, you can create modular, reusable state logic that keeps your application organized and reduces code duplication. diff --git a/docs/state_structure/overview.md b/docs/state_structure/overview.md deleted file mode 100644 index 9c5bdf02e0..0000000000 --- a/docs/state_structure/overview.md +++ /dev/null @@ -1,234 +0,0 @@ -```python exec -import reflex as rx -from typing import Any -``` - -# Substates - -Substates allow you to break up your state into multiple classes to make it more manageable. This is useful as your app -grows, as it allows you to think about each page as a separate entity. Substates also allow you to share common state -resources, such as variables or event handlers. - -When a particular state class becomes too large, breaking it up into several substates can bring performance -benefits by only loading parts of the state that are used to handle a certain event. - -## Multiple States - -One common pattern is to create a substate for each page in your app. -This allows you to think about each page as a separate entity, and makes it easier to manage your code as your app grows. - -To create a substate, simply inherit from `rx.State` multiple times: - -```python -# index.py -import reflex as rx - -class IndexState(rx.State): - """Define your main state here.""" - data: str = "Hello World" - - -@rx.page() -def index(): - return rx.box(rx.text(IndexState.data)) - -# signup.py -import reflex as rx - - -class SignupState(rx.State): - """Define your signup state here.""" - username: str = "" - password: str = "" - - def signup(self): - ... - - -@rx.page() -def signup_page(): - return rx.box( - rx.input(value=SignupState.username), - rx.input(value=SignupState.password), - ) - -# login.py -import reflex as rx - -class LoginState(rx.State): - """Define your login state here.""" - username: str = "" - password: str = "" - - def login(self): - ... - -@rx.page() -def login_page(): - return rx.box( - rx.input(value=LoginState.username), - rx.input(value=LoginState.password), - ) -``` - -Separating the states is purely a matter of organization. You can still access the state from other pages by importing the state class. - -```python -# index.py - -import reflex as rx - -from signup import SignupState - -... - -def index(): - return rx.box( - rx.text(IndexState.data), - rx.input(value=SignupState.username), - rx.input(value=SignupState.password), - ) -``` - -## Accessing Arbitrary States - -An event handler in a particular state can access and modify vars in another state instance by calling -the `get_state` async method and passing the desired state class. If the requested state is not already loaded, -it will be loaded and deserialized on demand. - -In the following example, the `GreeterState` accesses the `SettingsState` to get the `salutation` and uses it -to update the `message` var. - -Notably, the widget that sets the salutation does NOT have to load the `GreeterState` when handling the -input `on_change` event, which improves performance. - -```python demo exec -class SettingsState(rx.State): - salutation: str = "Hello" - - def set_salutation(self, value: str): - self.salutation = value - -def set_salutation_popover(): - return rx.popover.root( - rx.popover.trigger( - rx.icon_button(rx.icon("settings")), - ), - rx.popover.content( - rx.input( - value=SettingsState.salutation, - on_change=SettingsState.set_salutation - ), - ), - ) - - -class GreeterState(rx.State): - message: str = "" - - @rx.event - async def handle_submit(self, form_data: dict[str, Any]): - settings = await self.get_state(SettingsState) - self.message = f"{settings.salutation} {form_data['name']}" - - -def index(): - return rx.vstack( - rx.form( - rx.vstack( - rx.hstack( - rx.input(placeholder="Name", id="name"), - set_salutation_popover(), - ), - rx.button("Submit"), - ), - reset_on_submit=True, - on_submit=GreeterState.handle_submit, - ), - rx.text(GreeterState.message), - ) -``` - -### Accessing Individual Var Values - -In addition to accessing entire state instances with `get_state`, you can retrieve individual variable values using the `get_var_value` method: - -```python -# Access a var value from another state -value = await self.get_var_value(OtherState.some_var) -``` - -This async method is particularly useful when you only need a specific value rather than loading the entire state. Using `get_var_value` can be more efficient than `get_state` when: - -1. You only need to access a single variable from another state -2. The other state contains a large amount of data -3. You want to avoid loading unnecessary data into memory - -Here's an example that demonstrates how to use `get_var_value` to access data between states: - -```python demo exec -# Define a state that holds a counter value -class CounterState(rx.State): - # This variable will be accessed from another state - count: int = 0 - - @rx.event - async def increment(self): - # Increment the counter when the button is clicked - self.count += 1 - -# Define a separate state that will display information -class DisplayState(rx.State): - # This will show the current count value - message: str = "" - - @rx.event - async def show_count(self): - # Use get_var_value to access just the count variable from CounterState - # This is more efficient than loading the entire state with get_state - current = await self.get_var_value(CounterState.count) - self.message = f"Current count: {current}" - -def var_value_example(): - return rx.vstack( - rx.heading("Get Var Value Example"), - rx.hstack( - # This button calls DisplayState.show_count to display the current count - rx.button("Get Count Value", on_click=DisplayState.show_count), - # This button calls CounterState.increment to increase the counter - rx.button("Increment", on_click=CounterState.increment), - ), - # Display the message from DisplayState - rx.text(DisplayState.message), - width="100%", - align="center", - spacing="4", - ) -``` - -In this example: -1. We have two separate states: `CounterState` which manages a counter, and `DisplayState` which displays information -2. When you click "Increment", it calls `CounterState.increment()` to increase the counter value -3. When you click "Show Count", it calls `DisplayState.show_count()` which uses `get_var_value` to retrieve just the count value from `CounterState` without loading the entire state -4. The current count is then displayed in the message - -This pattern is useful when you have multiple states that need to interact with each other but don't need to access all of each other's data. - -If the var is not retrievable, `get_var_value` will raise an `UnretrievableVarValueError`. - -## Performance Implications - -When an event handler is called, Reflex will load the data not only for the substate containing -the event handler, but also all of its substates and parent states as well. -If a state has a large number of substates or contains a large amount of data, it can slow down processing -of events associated with that state. - -For optimal performance, keep a flat structure with most substate classes directly inheriting from `rx.State`. -Only inherit from another state when the parent holds data that is commonly used by the substate. -Implementing different parts of the app with separate, unconnected states ensures that only the necessary -data is loaded for processing events for a particular page or component. - -Avoid defining computed vars inside a state that contains a large amount of data, as -states with computed vars are always loaded to ensure the values are recalculated. -When using computed vars, it better to define them in a state that directly inherits from `rx.State` and -does not have other states inheriting from it, to avoid loading unnecessary data. diff --git a/docs/state_structure/shared_state.md b/docs/state_structure/shared_state.md deleted file mode 100644 index c8586cd1bc..0000000000 --- a/docs/state_structure/shared_state.md +++ /dev/null @@ -1,216 +0,0 @@ -```python exec -import reflex as rx -from pcweb.templates.docpage import definition -``` - -# Shared State - -_New in version 0.8.23_. - -Defining a subclass of `rx.SharedState` creates a special type of state that may be shared by multiple clients. Shared State is useful for creating real-time collaborative applications where multiple users need to see and interact with the same data simultaneously. - -## Using SharedState - -An `rx.SharedState` subclass behaves similarly to a normal `rx.State` subclass and will be private to each client until it is explicitly linked to a given token. Once linked, any changes made to the Shared State by one client will be propagated to all other clients sharing the same token. - -```md alert info -# What should be used as a token? - -A token can be any string that uniquely identifies a group of clients that should share the same state. Common choices include room IDs, document IDs, or user group IDs. Ensure that the token is securely generated and managed to prevent unauthorized access to shared state. -``` - -```md alert warning -# Linked token cannot contain underscore (_) characters. - -Underscore characters are currently used as an internal delimiter for tokens and will raise an exception if used for linked states. - -This is a temporary restriction and will be removed in a future release. -``` - -### Linking Shared State - -An `rx.SharedState` subclass can be linked to a token using the `_link_to` method, which is async and returns the linked state instance. After linking, subsequent events triggered against the shared state will be executed in the context of the linked state. To unlink from the token, return the result of awaiting the `_unlink` method. - -To try out the collaborative counter example, open this page in a second or third browser tab and click the "Link" button. You should see the count increment in all tabs when you click the "Increment" button in any of them. - -```python demo exec -class CollaborativeCounter(rx.SharedState): - count: int = 0 - - @rx.event - async def toggle_link(self): - if self._linked_to: - return await self._unlink() - else: - linked_state = await self._link_to("shared-global-counter") - linked_state.count += 1 # Increment count on link - - @rx.var - def is_linked(self) -> bool: - return bool(self._linked_to) - -def shared_state_example(): - return rx.vstack( - rx.text(f"Collaborative Count: {CollaborativeCounter.count}"), - rx.cond( - CollaborativeCounter.is_linked, - rx.button("Unlink", on_click=CollaborativeCounter.toggle_link), - rx.button("Link", on_click=CollaborativeCounter.toggle_link), - ), - rx.button("Increment", on_click=CollaborativeCounter.set_count(CollaborativeCounter.count + 1)), - ) -``` - -```md alert info -# Computed vars may reference SharedState - -Computed vars in other states may reference shared state data using `get_state`, just like private states. This allows private states to provide personalized views of shared data. - -Whenever the shared state is updated, any computed vars depending on it will be re-evaluated in the context of each client's private state. -``` - -### Identifying Clients - -Each client linked to a shared state can be uniquely identified by their `self.router.session.client_token`. Shared state events should _never_ rely on identifiers passed in as parameters, as these can be spoofed from the client. Instead, always use the `client_token` to identify the client triggering the event. - -```python demo exec -import uuid - -class SharedRoom(rx.SharedState): - shared_room: str = rx.LocalStorage() - _users: dict[str, str] = {} - - @rx.var - def user_list(self) -> str: - return ", ".join(self._users.values()) - - @rx.event - async def join(self, username: str): - if not self.shared_room: - self.shared_room = f"shared-room-{uuid.uuid4()}" - linked_state = await self._link_to(self.shared_room) - linked_state._users[self.router.session.client_token] = username - - @rx.event - async def leave(self): - if self._linked_to: - return await self._unlink() - - -class PrivateState(rx.State): - @rx.event - def handle_submit(self, form_data: dict): - return SharedRoom.join(form_data["username"]) - - @rx.var - async def user_in_room(self) -> bool: - shared_state = await self.get_state(SharedRoom) - return self.router.session.client_token in shared_state._users - - -def shared_room_example(): - return rx.vstack( - rx.text("Shared Room"), - rx.text(f"Users: {SharedRoom.user_list}"), - rx.cond( - PrivateState.user_in_room, - rx.button("Leave Room", on_click=SharedRoom.leave), - rx.form( - rx.input(placeholder="Enter your name", name="username"), - rx.button("Join Room"), - on_submit=PrivateState.handle_submit, - ), - ), - ) -``` - -```md alert warning -# Store sensitive data in backend-only vars with an underscore prefix - -Shared State data is synchronized to all linked clients, so avoid storing sensitive information (e.g., client_tokens, user credentials, personal data) in frontend vars, which would expose them to all users and allow them to be modified outside of explicit event handlers. Instead, use backend-only vars (prefixed with an underscore) to keep sensitive data secure on the server side and provide controlled access through event handlers and computed vars. -``` - -### Introspecting Linked Clients - -An `rx.SharedState` subclass has two attributes for determining link status and peers, which are updated during linking and unlinking, and come with some caveats. - -**`_linked_to: str`** - -Provides the token that the state is currently linked to, or empty string if not linked. - -This attribute is only set on the linked state instance returned by `_link_to`. It will be an empty string on any unlinked shared state instances. However, if another state links to a client's private token, then the `_linked_to` attribute will be set to the client's token rather than an empty string. - -When `_linked_to` equals `self.router.session.client_token`, it is assumed that the current client is unlinked, but another client has linked to this client's private state. Although this is possible, it is generally discouraged to link shared states to private client tokens. - -**`_linked_from: set[str]`** - -A set of client tokens that are currently linked to this shared state instance. - -This attribute is only updated during `_link_to` and `_unlink` calls. In situations where unlinking occurs otherwise, such as client disconnects, `self.reset()` is called, or state expires on the backend, `_linked_from` may contain stale client tokens that are no longer linked. These can be cleaned periodically by checking if the tokens still exist in `app.event_namespace.token_to_sid`. - -## Guidelines and Best Practices - -### Keep Shared State Minimal - -When defining a shared state, aim to keep it as minimal as possible. Only include the data and methods that need to be shared between clients. This helps reduce complexity and potential synchronization issues. - -Linked states are always loaded into the tree for each event on each linked client and large states take longer to serialize and transmit over the network. Because linked states are regularly loaded in the context of many clients, they incur higher lock contention, so minimizing loading time also reduces lock waiting time for other clients. - -### Prefer Backend-Only Vars in Shared State - -A shared state should primarily use backend-only vars (prefixed with an underscore) to store shared data. Often, not all users of the shared state need visibility into all of the data in the shared state. Use computed vars to provide sanitized access to shared data as needed. - -```python -from typing import Literal - -class SharedGameState(rx.SharedState): - # Sensitive user metadata stored in backend-only variable. - _players: dict[str, Literal["X", "O"]] = {} - - @rx.event - def make_move(self, x: int, y: int): - # Identify users by client_token, never by arguments passed to the event. - player_token = self.router.session.client_token - player_piece = self._players.get(player_token) -``` - -```md alert warning -# Do Not Trust Event Handler Arguments - -The client can send whatever data it wants to event handlers, so never rely on arguments passed to event handlers for sensitive information such as user identity or permissions. Always use secure identifiers like `self.router.session.client_token` to identify the client triggering the event. -``` - -### Expose Per-User Data via Private States - -If certain data in the shared state needs to be personalized for each user, prefer to expose that data through computed vars defined in private states. This allows each user to have their own view of the shared data without exposing sensitive information to other users. It also reduces the amount of unrelated data sent to each client and improves caching performance by keeping each user's view cached in their own private state, rather than always recomputing the shared state vars for each user that needs to have their information updated. - -Use async computed vars with `get_state` to access shared state data from private states. - -```python -class UserGameState(rx.State): - @rx.var - async def player_piece(self) -> str | None: - shared_state = await self.get_state(SharedGameState) - return shared_state._players.get(self.router.session.client_token) -``` - -### Use Dynamic Routes for Linked Tokens - -It is often convenient to define dynamic routes that include the linked token as part of the URL path. This allows users to easily share links to specific shared state instances. The dynamic route can use `on_load` to link the shared state to the token extracted from the URL. - -```python -class SharedRoom(rx.SharedState): - async def on_load(self): - # `self.room_id` is the automatically defined dynamic route var. - await self._link_to(self.room_id.replace("_", "-") or "default-room") - - -def room_page(): ... - - -app.add_route( - room_page, - path="/room/[room_id]", - on_load=SharedRoom.on_load, -) -``` diff --git a/docs/styling/common-props.md b/docs/styling/common-props.md deleted file mode 100644 index f1fb70c2fa..0000000000 --- a/docs/styling/common-props.md +++ /dev/null @@ -1,227 +0,0 @@ -# Style and Layout Props - -```python exec -import reflex as rx -from pcweb.styles.styles import get_code_style, cell_style -from pcweb.styles.colors import c_color - -props = { - "align": { - "description": "In a flex, it controls the alignment of items on the cross axis and in a grid layout, it controls the alignment of items on the block axis within their grid area (equivalent to align_items)", - "values": ["stretch", "center", "start", "end", "flex-start", "baseline"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/align-items", - }, - "backdrop_filter": { - "description": "Lets you apply graphical effects such as blurring or color shifting to the area behind an element", - "values": ["url(commonfilters.svg#filter)", "blur(2px)", "hue-rotate(120deg)", "drop-shadow(4px 4px 10px blue)"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter", - }, - "background": { - "description": "Sets all background style properties at once, such as color, image, origin and size, or repeat method (equivalent to bg)", - "values": ["green", "radial-gradient(crimson, skyblue)", "no-repeat url('../lizard.png')"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/background", - }, - "background_color": { - "description": "Sets the background color of an element", - "values": ["brown", "rgb(255, 255, 128)", "#7499ee"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/background-color", - }, - "background_image": { - "description": "Sets one or more background images on an element", - "values": ["url('../lizard.png')", "linear-gradient(#e66465, #9198e5)"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/background-image", - }, - "border": { - "description": "Sets an element's border, which sets the values of border_width, border_style, and border_color.", - "values": ["solid", "dashed red", "thick double #32a1ce", "4mm ridge rgba(211, 220, 50, .6)"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/border", - }, - "border_top / border_bottom / border_right / border_left": { - "description": "Sets an element's top / bottom / right / left border. It sets the values of border-(top / bottom / right / left)-width, border-(top / bottom / right / left)-style and border-(top / bottom / right / left)-color", - "values": ["solid", "dashed red", "thick double #32a1ce", "4mm ridge rgba(211, 220, 50, .6)"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/border-bottom", - }, - "border_color": { - "description": "Sets the color of an element's border (each side can be set individually using border_top_color, border_right_color, border_bottom_color, and border_left_color)", - "values": ["red", "red #32a1ce", "red rgba(170, 50, 220, .6) green", "red yellow green transparent"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/border-color", - }, - "border_radius": { - "description": "Rounds the corners of an element's outer border edge and you can set a single radius to make circular corners, or two radii to make elliptical corners", - "values": ["30px", "25% 10%", "10% 30% 50% 70%", "10% / 50%"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius", - }, - "border_width": { - "description": "Sets the width of an element's border", - "values": ["thick", "1em", "4px 1.25em", "0 4px 8px 12px"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/border-width", - }, - "box_shadow": { - "description": "Adds shadow effects around an element's frame. You can set multiple effects separated by commas. A box shadow is described by X and Y offsets relative to the element, blur and spread radius, and color", - "values": ["10px 5px 5px red", "60px -16px teal", "12px 12px 2px 1px rgba(0, 0, 255, .2)", "3px 3px red, -1em 0 .4em olive;"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow", - }, - - "color": { - "description": "Sets the foreground color value of an element's text", - "values": ["rebeccapurple", "rgb(255, 255, 128)", "#00a400"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/color", - }, - "display": { - "description": "Sets whether an element is treated as a block or inline box and the layout used for its children, such as flow layout, grid or flex", - "values": ["block", "inline", "inline-block", "flex", "inline-flex", "grid", "inline-grid", "flow-root"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/display", - }, - "flex_grow": { - "description": " Sets the flex grow factor, which specifies how much of the flex container's remaining space should be assigned to the flex item's main size", - "values": ["1", "2", "3"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/flex-grow", - }, - "height": { - "description": "Sets an element's height", - "values": ["150px", "20em", "75%", "auto"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/height", - }, - "justify": { - "description": "Defines how the browser distributes space between and around content items along the main-axis of a flex container, and the inline axis of a grid container (equivalent to justify_content)", - "values": ["start", "center", "flex-start", "space-between", "space-around", "space-evenly", "stretch"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content", - }, - "margin": { - "description": "Sets the margin area (creates extra space around an element) on all four sides of an element", - "values": ["1em", "5% 0", "10px 50px 20px", "10px 50px 20px 0"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/margin", - }, - "margin_x / margin_y": { - "description": "Sets the margin area (creates extra space around an element) along the x-axis / y-axis and a positive value places it farther from its neighbors, while a negative value places it closer", - "values": ["1em", "10%", "10px"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/margin", - }, - "margin_top / margin_right / margin_bottom / margin_left ": { - "description": "Sets the margin area (creates extra space around an element) on the top / right / bottom / left of an element", - "values": ["1em", "10%", "10px"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/margin-top", - }, - "max_height / min_height": { - "description": "Sets the maximum / minimum height of an element and prevents the used value of the height property from becoming larger / smaller than the value specified for max_height / min_height", - "values": ["150px", "7em", "75%"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/max-height", - }, - "max_width / min_width": { - "description": "Sets the maximum / minimum width of an element and prevents the used value of the width property from becoming larger / smaller than the value specified for max_width / min_width", - "values": ["150px", "20em", "75%"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/max-width", - }, - "padding": { - "description": "Sets the padding area (creates extra space within an element) on all four sides of an element at once", - "values": ["1em", "10px 50px 30px 0", "0", "10px 50px 20px"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/padding", - }, - "padding_x / padding_y": { - "description": "Creates extra space within an element along the x-axis / y-axis", - "values": ["1em", "10%", "10px"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/padding", - }, - "padding_top / padding_right / padding_bottom / padding_left ": { - "description": "Sets the height of the padding area on the top / right / bottom / left of an element", - "values": ["1em", "10%", "20px"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/padding-top", - }, - "position": { - "description": "Sets how an element is positioned in a document and the top, right, bottom, and left properties determine the final location of positioned elements", - "values": ["static", "relative", "absolute", "fixed", "sticky"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/position", - }, - "text_align": { - "description": "Sets the horizontal alignment of the inline-level content inside a block element or table-cell box", - "values": ["start", "end", "center", "justify", "left", "right"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/text-align", - }, - "text_wrap": { - "description": "Controls how text inside an element is wrapped", - "values": ["wrap", "nowrap", "balance", "pretty"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/text-wrap", - }, - "top / bottom / right / left": { - "description": "Sets the vertical / horizontal position of a positioned element. It does not effect non-positioned elements.", - "values": ["0", "4em", "10%", "20px"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/top", - }, - "width": { - "description": "Sets an element's width", - "values": ["150px", "20em", "75%", "auto"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/width", - }, - "white_space": { - "description": "Sets how white space inside an element is handled", - "values": ["normal", "nowrap", "pre", "break-spaces"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/white-space", - }, - "word_break": { - "description": "Sets whether line breaks appear wherever the text would otherwise overflow its content box", - "values": ["normal", "break-all", "keep-all", "break-word"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/word-break", - }, - "z_index": { - "description": "Sets the z-order of a positioned element and its descendants or flex and grid items, and overlapping elements with a larger z-index cover those with a smaller one", - "values": ["auto", "1", "5", "200"], - "link": "https://developer.mozilla.org/en-US/docs/Web/CSS/z-index", - }, - - -} - - -def show_props(key, props_dict): - prop_details = props_dict[key] - return rx.table.row( - rx.table.cell( - rx.link( - rx.hstack( - rx.code(key, style=get_code_style("violet")), - rx.icon("square_arrow_out_up_right", color=c_color("slate", 9), size=15, flex_shrink="0"), - align="center" - ), - href=prop_details["link"], - is_external=True, - ), - justify="start",), - rx.table.cell(prop_details["description"], justify="start", style=cell_style), - rx.table.cell(rx.hstack(*[rx.code(value, style=get_code_style("violet")) for value in prop_details["values"]], flex_wrap="wrap"), justify="start",), - justify="center", - align="center", - - ) - -``` - -Any [CSS](https://developer.mozilla.org/en-US/docs/Web/CSS) prop can be used in a component in Reflex. This is a short list of the most commonly used props. To see all CSS props that can be used check out this [documentation](https://developer.mozilla.org/en-US/docs/Web/CSS). - -Hyphens in CSS property names may be replaced by underscores to use as valid python identifiers, i.e. the CSS prop `z-index` would be used as `z_index` in Reflex. - -```python eval -rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell( - "Prop", justify="center" - ), - rx.table.column_header_cell( - "Description", - justify="center", - - ), - rx.table.column_header_cell( - "Potential Values", - justify="center", - ), - ) - ), - rx.table.body( - *[show_props(key, props) for key in props] - ), - width="100%", - padding_x="0", - size="1", -) -``` \ No newline at end of file diff --git a/docs/styling/custom-stylesheets.md b/docs/styling/custom-stylesheets.md deleted file mode 100644 index b57f5426fd..0000000000 --- a/docs/styling/custom-stylesheets.md +++ /dev/null @@ -1,191 +0,0 @@ -```python exec -import reflex as rx -from pcweb.pages.docs import assets -``` - -# Custom Stylesheets - -Reflex allows you to add custom stylesheets. Simply pass the URLs of the stylesheets to `rx.App`: - -```python -app = rx.App( - stylesheets=[ - "https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css", - ], -) -``` - -## Local Stylesheets - -You can also add local stylesheets. Just put the stylesheet under [`assets/`]({assets.upload_and_download_files.path}) and pass the path to the stylesheet to `rx.App`: - -```python -app = rx.App( - stylesheets=[ - "/styles.css", # This path is relative to assets/ - ], -) -``` - -```md alert warning -# Always use a leading slash (/) when referencing files in the assets directory. -Without a leading slash the path is considered relative to the current page route and may -not work for routes containing more than one path component, like `/blog/my-cool-post`. -``` - - -## Styling with CSS - -You can use CSS variables directly in your Reflex app by passing them alongside the appropriae props. Create a `style.css` file inside the `assets` folder with the following lines: - -```css -:root { - --primary-color: blue; - --accent-color: green; -} -``` - -Then, after referencing the CSS file within the `stylesheets` props of `rx.App`, you can access the CSS props directly like this - -```python -app = rx.App( - theme=rx.theme(appearance="light"), - stylesheets=["/style.css"], -) -app.add_page( - rx.center( - rx.text("CSS Variables!"), - width="100%", - height="100vh", - bg="var(--primary-color)", - ), - "/", -) -``` - -## SASS/SCSS Support - -Reflex supports SASS/SCSS stylesheets alongside regular CSS. This allows you to use more advanced styling features like variables, nesting, mixins, and more. - -### Using SASS/SCSS Files - -To use SASS/SCSS files in your Reflex app: - -1. Create a `.sass` or `.scss` file in your `assets` directory -2. Reference the file in your `rx.App` configuration just like you would with CSS files - -```python -app = rx.App( - stylesheets=[ - "/styles.scss", # This path is relative to assets/ - "/sass/main.sass", # You can organize files in subdirectories - ], -) -``` - -Reflex automatically detects the file extension and compiles these files to CSS using the `libsass` package. - -### Example SASS/SCSS File - -Here's an example of a SASS file (`assets/styles.scss`) that demonstrates some of the features: - -```scss -// Variables -$primary-color: #3498db; -$secondary-color: #2ecc71; -$padding: 16px; - -// Nesting -.container { - background-color: $primary-color; - padding: $padding; - - .button { - background-color: $secondary-color; - padding: $padding / 2; - - &:hover { - opacity: 0.8; - } - } -} - -// Mixins -@mixin flex-center { - display: flex; - justify-content: center; - align-items: center; -} - -.centered-box { - @include flex-center; - height: 100px; -} -``` - -### Dependency Requirement - -The `libsass` package is required for SASS/SCSS compilation. If it's not installed, Reflex will show an error message. You can install it with: - -```bash -pip install "libsass>=0.23.0" -``` - -This package is included in the default Reflex installation, so you typically don't need to install it separately. - -## Fonts - -You can take advantage of Reflex's support for custom stylesheets to add custom fonts to your app. - -In this example, we will use the [JetBrains Mono]({"https://fonts.google.com/specimen/JetBrains+Mono"}) font from Google Fonts. First, add the stylesheet with the font to your app. You can get this link from the "Get embed code" section of the Google font page. - -```python -app = rx.App( - stylesheets=[ - "https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap", - ], -) -``` - -Then you can use the font in your component by setting the `font_family` prop. - -```python demo -rx.text( - "Check out my font", - font_family="JetBrains Mono", - font_size="1.5em", -) -``` - -## Local Fonts - -By making use of the two previous points, we can also make a stylesheet that allow you to use a font hosted on your server. - -If your font is called `MyFont.otf`, copy it in `assets/fonts`. - -Now we have the font ready, let's create the stylesheet `myfont.css`. - -```css -@font-face { - font-family: MyFont; - src: url("/fonts/MyFont.otf") format("opentype"); -} - -@font-face { - font-family: MyFont; - font-weight: bold; - src: url("/fonts/MyFont.otf") format("opentype"); -} -``` - -Add the reference to your new Stylesheet in your App. - -```python -app = rx.App( - stylesheets=[ - "/fonts/myfont.css", # This path is relative to assets/ - ], -) -``` - -And that's it! You can now use `MyFont` like any other FontFamily to style your components. diff --git a/docs/styling/layout.md b/docs/styling/layout.md deleted file mode 100644 index 84282b14f8..0000000000 --- a/docs/styling/layout.md +++ /dev/null @@ -1,155 +0,0 @@ -```python exec -import reflex as rx -``` - -# Layout Components - -Layout components such as `rx.flex`, `rx.container`, `rx.box`, etc. are used to organize and structure the visual presentation of your application. This page gives a breakdown of when and how each of these components might be used. - -```md video https://youtube.com/embed/ITOZkzjtjUA?start=3311&end=3853 -# Video: Example of Laying Out the Main Content of a Page -``` - -## Box - -`rx.box` is a generic component that can apply any CSS style to its children. It's a building block that can be used to apply a specific layout or style property. - -**When to use:** Use `rx.box` when you need to apply specific styles or constraints to a part of your interface. - - -```python demo -rx.box( - rx.box( - "CSS color", - background_color="red", - border_radius="2px", - width="50%", - margin="4px", - padding="4px", - ), - rx.box( - "Radix Color", - background_color=rx.color("tomato", 3), - border_radius="5px", - width="80%", - margin="12px", - padding="12px", - ), - text_align="center", - width="100%", -) -``` - -## Stack - -`rx.stack` is a layout component that arranges its children in a single column or row, depending on the direction. It’s useful for consistent spacing between elements. - -**When to use:** Use `rx.stack` when you need to lay out a series of components either vertically or horizontally with equal spacing. - -```python demo -rx.flex( - rx.stack( - rx.box( - "Example", - bg="orange", - border_radius="3px", - width="20%", - ), - rx.box( - "Example", - bg="lightblue", - border_radius="3px", - width="30%", - ), - flex_direction="row", - width="100%", - ), - rx.stack( - rx.box( - "Example", - bg="orange", - border_radius="3px", - width="20%", - ), - rx.box( - "Example", - bg="lightblue", - border_radius="3px", - width="30%", - ), - flex_direction="column", - width="100%", - ), - width="100%", -) -``` - -## Flex - -The `rx.flex` component is used to create a flexible box layout, inspired by [CSS Flexbox](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox). It's ideal for designing a layout where the size of the items can grow and shrink dynamically based on the available space. - -**When to use:** Use `rx.flex` when you need a responsive layout that adjusts the size and position of child components dynamically. - - -```python demo -rx.flex( - rx.card("Card 1"), - rx.card("Card 2"), - rx.card("Card 3"), - spacing="2", - width="100%", -) -``` - - -## Grid - -`rx.grid` components are used to create complex responsive layouts based on a grid system, similar to [CSS Grid Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout). - -**When to use:** Use `rx.grid` when dealing with complex layouts that require rows and columns, especially when alignment and spacing among multiple axes are needed. - -```python demo -rx.grid( - rx.foreach( - rx.Var.range(12), - lambda i: rx.card(f"Card {i + 1}", height="10vh"), - ), - columns="3", - spacing="4", - width="100%", -) -``` - -## Container - -The `rx.container` component typically provides padding and fixes the maximum width of the content inside it, often used to center content on large screens. - -**When to use:** Use `rx.container` for wrapping your application’s content in a centered block with some padding. - -```python demo -rx.box( - rx.container( - rx.card( - "This content is constrained to a max width of 448px.", - width="100%", - ), - size="1", - ), - rx.container( - rx.card( - "This content is constrained to a max width of 688px.", - width="100%", - ), - size="2", - ), - rx.container( - rx.card( - "This content is constrained to a max width of 880px.", - width="100%", - ), - size="3", - ), - background_color="var(--gray-3)", - width="100%", -) -``` \ No newline at end of file diff --git a/docs/styling/overview.md b/docs/styling/overview.md deleted file mode 100644 index 137f7f1bc6..0000000000 --- a/docs/styling/overview.md +++ /dev/null @@ -1,185 +0,0 @@ -```python exec -import reflex as rx -from pcweb.pages.docs import styling, library -``` - -# Styling - -Reflex components can be styled using the full power of [CSS]({"https://www.w3schools.com/css/"}). - -There are three main ways to add style to your app and they take precedence in the following order: - -1. **Inline:** Styles applied to a single component instance. -2. **Component:** Styles applied to components of a specific type. -3. **Global:** Styles applied to all components. - -```md alert success -# Style keys can be any valid CSS property name. -To be consistent with Python standards, you can specify keys in `snake_case`. -``` - -## Global Styles - -You can pass a style dictionary to your app to apply base styles to all components. - -For example, you can set the default font family and font size for your app here just once rather than having to set it on every component. - -```python -style = { - "font_family": "Comic Sans MS", - "font_size": "16px", -} - -app = rx.App(style=style) -``` - -## Component Styles - -In your style dictionary, you can also specify default styles for specific component types or arbitrary CSS classes and IDs. - -```python -style = { - # Set the selection highlight color globally. - "::selection": { - "background_color": accent_color, - }, - # Apply global css class styles. - ".some-css-class": { - "text_decoration": "underline", - }, - # Apply global css id styles. - "#special-input": \{"width": "20vw"}, - # Apply styles to specific components. - rx.text: { - "font_family": "Comic Sans MS", - }, - rx.divider: { - "margin_bottom": "1em", - "margin_top": "0.5em", - }, - rx.heading: { - "font_weight": "500", - }, - rx.code: { - "color": "green", - }, -} - -app = rx.App(style=style) -``` - -Using style dictionaries like this, you can easily create a consistent theme for your app. - - -```md alert warning -# Watch out for underscores in class names and IDs -Reflex automatically converts `snake_case` identifiers into `camelCase` format when applying styles. To ensure consistency, it is recommended to use the dash character or camelCase identifiers in your own class names and IDs. To style third-party libraries relying on underscore class names, an external stylesheet should be used. See [custom stylesheets]({styling.custom_stylesheets.path}) for how to reference external CSS files. -``` - -## Inline Styles - -Inline styles apply to a single component instance. They are passed in as regular props to the component. - -```python demo -rx.text( - "Hello World", - background_image="linear-gradient(271.68deg, #EE756A 0.75%, #756AEE 88.52%)", - background_clip="text", - font_weight="bold", - font_size="2em", -) -``` - -Children components inherit inline styles unless they are overridden by their own inline styles. - -```python demo -rx.box( - rx.hstack( - rx.button("Default Button"), - rx.button("Red Button", color="red"), - ), - color="blue", -) -``` - -### Style Prop - -Inline styles can also be set with a `style` prop. This is useful for reusing styles between multiple components. - -```python exec -text_style = { - "color": "green", - "font_family": "Comic Sans MS", - "font_size": "1.2em", - "font_weight": "bold", - "box_shadow": "rgba(240, 46, 170, 0.4) 5px 5px, rgba(240, 46, 170, 0.3) 10px 10px", -} -``` - -```python -text_style={text_style} -``` - -```python demo -rx.vstack( - rx.text("Hello", style=text_style), - rx.text("World", style=text_style), -) -``` - -```python exec -style1 = { - "color": "green", - "font_family": "Comic Sans MS", - "border_radius": "10px", - "background_color": "rgb(107,99,246)", -} -style2 = { - "color": "white", - "border": "5px solid #EE756A", - "padding": "10px", -} -``` - -```python -style1={style1} -style2={style2} -``` - -```python demo -rx.box( - "Multiple Styles", - style=[style1, style2], -) -``` - -The style dictionaries are applied in the order they are passed in. This means that styles defined later will override styles defined earlier. - - -## Theming - -As of Reflex 'v0.4.0', you can now theme your Reflex web apps. To learn more checkout the [Theme docs]({styling.theming.path}). - -The `Theme` component is used to change the theme of the application. The `Theme` can be set directly in your rx.App. - -```python -app = rx.App( - theme=rx.theme( - appearance="light", has_background=True, radius="large", accent_color="teal" - ) -) -``` - -Additionally you can modify the theme of your app through using the `Theme Panel` component which can be found in the [Theme Panel docs]({library.other.theme.path}). - -## Special Styles - -We support all of Chakra UI's [pseudo styles]({"https://v2.chakra-ui.com/docs/styled-system/style-props#pseudo"}). - -Below is an example of text that changes color when you hover over it. - -```python demo -rx.box( - rx.text("Hover Me", _hover={"color": "red"}), -) -``` diff --git a/docs/styling/responsive.md b/docs/styling/responsive.md deleted file mode 100644 index ea7e5d2177..0000000000 --- a/docs/styling/responsive.md +++ /dev/null @@ -1,172 +0,0 @@ -```python exec -import reflex as rx -from pcweb.styles.styles import get_code_style, cell_style -``` - -# Responsive - -Reflex apps can be made responsive to look good on mobile, tablet, and desktop. - -You can pass a list of values to any style property to specify its value on different screen sizes. - -```python demo -rx.text( - "Hello World", - color=["orange", "red", "purple", "blue", "green"], -) -``` - -The text will change color based on your screen size. If you are on desktop, try changing the size of your browser window to see the color change. - -_New in 0.5.6_ - -Responsive values can also be specified using `rx.breakpoints`. Each size maps to a corresponding key, the value of which will be applied when the screen size is greater than or equal to the named breakpoint. - -```python demo -rx.text( - "Hello World", - color=rx.breakpoints( - initial="orange", - sm="purple", - lg="green", - ), -) -``` - -Custom breakpoints in CSS units can be mapped to values per component using a dictionary instead of named parameters. - -```python -rx.text( - "Hello World", - color=rx.breakpoints({ - "0px": "orange", - "48em": "purple", - "80em": "green", - }), -) -``` - -For the Radix UI components' fields that supports responsive value, you can also use `rx.breakpoints` (note that custom breakpoints and list syntax aren't supported). - -```python demo -rx.grid( - rx.foreach( - list(range(6)), - lambda _: rx.box(bg_color="#a7db76", height="100px", width="100px") - ), - columns=rx.breakpoints( - initial="2", - sm="4", - lg="6" - ), - spacing="4" -) -``` - -## Setting Defaults - -The default breakpoints are shown below. - -```python eval -rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Size"), - rx.table.column_header_cell("Width"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.cell(rx.code("initial", style=get_code_style("violet"))), - rx.table.cell("0px", style=cell_style), - ), - rx.table.row( - rx.table.cell(rx.code("xs", style=get_code_style("violet"))), - rx.table.cell("30em", style=cell_style), - ), - rx.table.row( - rx.table.cell(rx.code("sm", style=get_code_style("violet"))), - rx.table.cell("48em", style=cell_style), - ), - rx.table.row( - rx.table.cell(rx.code("md", style=get_code_style("violet"))), - rx.table.cell("62em", style=cell_style), - ), - rx.table.row( - rx.table.cell(rx.code("lg", style=get_code_style("violet"))), - rx.table.cell("80em", style=cell_style), - ), - rx.table.row( - rx.table.cell(rx.code("xl", style=get_code_style("violet"))), - rx.table.cell("96em", style=cell_style), - ), - ), - margin_bottom="1em", -) -``` - -You can customize them using the style property. - -```python -app = rx.App(style=\{"breakpoints": ["520px", "768px", "1024px", "1280px", "1640px"]\}) -``` - -## Showing Components Based on Display - -A common use case for responsive is to show different components based on the screen size. - -Reflex provides useful helper components for this. - -```python demo -rx.vstack( - rx.desktop_only( - rx.text("Desktop View"), - ), - rx.tablet_only( - rx.text("Tablet View"), - ), - rx.mobile_only( - rx.text("Mobile View"), - ), - rx.mobile_and_tablet( - rx.text("Visible on Mobile and Tablet"), - ), - rx.tablet_and_desktop( - rx.text("Visible on Desktop and Tablet"), - ), -) -``` - -## Specifying Display Breakpoints - -You can specify the breakpoints to use for the responsive components by using the `display` style property. - -```python demo -rx.vstack( - rx.text( - "Hello World", - color="green", - display=["none", "none", "none", "none", "flex"], - ), - rx.text( - "Hello World", - color="blue", - display=["none", "none", "none", "flex", "flex"], - ), - rx.text( - "Hello World", - color="red", - display=["none", "none", "flex", "flex", "flex"], - ), - rx.text( - "Hello World", - color="orange", - display=["none", "flex", "flex", "flex", "flex"], - ), - rx.text( - "Hello World", - color="yellow", - display=["flex", "flex", "flex", "flex", "flex"], - ), -) -``` diff --git a/docs/styling/tailwind.md b/docs/styling/tailwind.md deleted file mode 100644 index fe47f6a450..0000000000 --- a/docs/styling/tailwind.md +++ /dev/null @@ -1,256 +0,0 @@ -```python exec -import reflex as rx -from pcweb.pages.docs import library - -``` - -# Tailwind - -Reflex supports [Tailwind CSS]({"https://tailwindcss.com/"}) through a plugin system that provides better control and supports multiple Tailwind versions. - -## Plugin-Based Configuration - -The recommended way to use Tailwind CSS is through the plugin system: - -```python -import reflex as rx - -config = rx.Config( - app_name="myapp", - plugins=[ - rx.plugins.TailwindV4Plugin(), - ], -) -``` - -You can customize the Tailwind configuration by passing a config dictionary to the plugin: - -```python -import reflex as rx - -tailwind_config = { - "plugins": ["@tailwindcss/typography"], - "theme": { - "extend": { - "colors": { - "primary": "#3b82f6", - "secondary": "#64748b", - } - } - }, -} - -config = rx.Config( - app_name="myapp", - plugins=[ - rx.plugins.TailwindV4Plugin(tailwind_config), - ], -) -``` - -```md alert info -## Migration from Legacy Configuration - -If you're currently using the legacy `tailwind` configuration parameter, you should migrate to using the plugin system: - -**Old approach (legacy):** -``````python -config = rx.Config( - app_name="my_app", - tailwind={ - "plugins": ["@tailwindcss/typography"], - "theme": {"extend": {"colors": {"primary": "#3b82f6"}}}, - }, -) -`````` - -**New approach (plugin-based):** -``````python -tailwind_config = { - "plugins": ["@tailwindcss/typography"], - "theme": {"extend": {"colors": {"primary": "#3b82f6"}}}, -} - -config = rx.Config( - app_name="my_app", - plugins=[ - rx.plugins.TailwindV4Plugin(tailwind_config), - ], -) -`````` -``` - -### Choosing Between Tailwind Versions - -Reflex supports both Tailwind CSS v3 and v4: - -- **TailwindV4Plugin**: The recommended choice for new projects. Includes the latest features and performance improvements and is used by default in new Reflex templates. -- **TailwindV3Plugin**: Still supported for existing projects. Use this if you need compatibility with older Tailwind configurations. - -```python -# For Tailwind CSS v4 (recommended for new projects) -config = rx.Config( - app_name="myapp", - plugins=[rx.plugins.TailwindV4Plugin()], -) - -# For Tailwind CSS v3 (existing projects) -config = rx.Config( - app_name="myapp", - plugins=[rx.plugins.TailwindV3Plugin()], -) -``` - -All Tailwind configuration options are supported. - -You can use any of the [utility classes]({"https://tailwindcss.com/docs/utility-first"}) under the `class_name` prop: - -```python demo -rx.box( - "Hello World", - class_name="text-4xl text-center text-blue-500", -) -``` - -## Disabling Tailwind - -To disable Tailwind in your project, simply don't include any Tailwind plugins in your configuration. This will prevent Tailwind styles from being applied to your application. - -## Custom theme - -You can integrate custom Tailwind themes within your Reflex app as well. The setup process is similar to the CSS Styling method mentioned above, with only a few minor variations. - -Begin by creating a CSS file inside your `assets` folder. Inside the CSS file, include the following Tailwind directives: - -```css -@tailwind base; -@tailwind components; -@tailwind utilities; - -:root { - --background: blue; - --foreground: green; -} - -.dark { - --background: darkblue; - --foreground: lightgreen; -} -``` - -We define a couple of custom CSS variables (`--background` and `--foreground`) that will be used throughout your app for styling. These variables can be dynamically updated based on the theme. - -Tailwind defaults to light mode, but to handle dark mode, you can define a separate set of CSS variables under the `.dark` class. - -Tailwind Directives (`@tailwind base`, `@tailwind components`, `@tailwind utilities`): These are essential Tailwind CSS imports that enable the default base styles, components, and utility classes. - -Next, you'll need to configure Tailwind in your `rxconfig.py` file to ensure that the Reflex app uses your custom Tailwind setup. - -```python -import reflex as rx - -tailwind_config = { - "plugins": ["@tailwindcss/typography"], - "theme": { - "extend": { - "colors": { - "background": "var(--background)", - "foreground": "var(--foreground)" - }, - } - }, -} - -config = rx.Config( - app_name="app", - plugins=[ - rx.plugins.TailwindV4Plugin(tailwind_config), - ], -) -``` - -In the theme section, we're extending the default Tailwind theme to include custom colors. Specifically, we're referencing the CSS variables (`--background` and `--foreground`) that were defined earlier in your CSS file. - -The `rx.Config` object is used to initialize and configure your Reflex app. Here, we're passing the `tailwind_config` dictionary to ensure Tailwind's custom setup is applied to the app. - -Finally, to apply your custom styles and Tailwind configuration, you need to reference the CSS file you created in your `assets` folder inside the `rx.App` setup. This will allow you to use the custom properties (variables) directly within your Tailwind classes. - -In your `app.py` (or main application file), make the following changes: - -```python -app = rx.App( - theme=rx.theme(appearance="light"), - stylesheets=["/style.css"], -) -app.add_page( - rx.center( - rx.text("Tailwind & Reflex!"), - class_name="bg-background w-full h-[100vh]", - ), - "/", -) -``` - -The `bg-background` class uses the `--background` variable (defined in the CSS file), which will be applied as the background color. - -## Dynamic Styling - -You can style a component based of a condition using `rx.cond` or `rx.match`. - -```python demo exec -class TailwindState(rx.State): - active = False - - @rx.event - def toggle_active(self): - self.active = not self.active - -def tailwind_demo(): - return rx.el.div( - rx.el.button( - "Click me", - on_click=TailwindState.toggle_active, - class_name=( - "px-4 py-2 text-white rounded-md", - rx.cond( - TailwindState.active, - "bg-red-500", - "bg-blue-500", - ), - ), - ), - ) -``` - -## Using Tailwind Classes from the State - -When using Tailwind with Reflex, it's important to understand that class names must be statically defined in your code for Tailwind to properly compile them. If you dynamically generate class names from state variables or functions at runtime, Tailwind won't be able to detect these classes during the build process, resulting in missing styles in your application. - -For example, this won't work correctly because the class names are defined in the state: - -```python demo exec -class TailwindState(rx.State): - active = False - - @rx.var - def button_class(self) -> str: - return "bg-accent" if self.active else "bg-secondary" - - @rx.event - def toggle_active(self): - self.active = not self.active - -def tailwind_demo(): - return rx.el.button( - f"Click me: {TailwindState.active}", - class_name=TailwindState.button_class, - on_click=TailwindState.toggle_active, - ) -``` - -## Using Tailwind with Reflex Core Components - -Reflex core components are built on Radix Themes, which means they come with pre-defined styling. When you apply Tailwind classes to these components, you may encounter styling conflicts or unexpected behavior as the Tailwind styles compete with the built-in Radix styles. - -For the best experience when using Tailwind CSS in your Reflex application, we recommend using the lower-level `rx.el` components. These components don't have pre-applied styles, giving you complete control over styling with Tailwind classes without any conflicts. Check the list of HTML components [here]({library.other.html.path}). - diff --git a/docs/styling/theming.md b/docs/styling/theming.md deleted file mode 100644 index 83697108fe..0000000000 --- a/docs/styling/theming.md +++ /dev/null @@ -1,206 +0,0 @@ -```python exec -import reflex as rx -from pcweb.constants import REFLEX_ASSETS_CDN -from pcweb.pages.docs import library -from pcweb.styles.styles import get_code_style_rdx, cell_style -``` - -# Theming - -As of Reflex `v0.4.0`, you can now theme your Reflex applications. The core of our theming system is directly based on the [Radix Themes](https://www.radix-ui.com) library. This allows you to easily change the theme of your application along with providing a default light and dark theme. Themes cause all the components to have a unified color appearance. - -## Overview - -The `Theme` component is used to change the theme of the application. The `Theme` can be set directly in your rx.App. - -```python -app = rx.App( - theme=rx.theme( - appearance="light", has_background=True, radius="large", accent_color="teal" - ) -) -``` - -Here are the props that can be passed to the `rx.theme` component: - -```python eval -rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name", class_name="table-header"), - rx.table.column_header_cell("Type", class_name="table-header"), - rx.table.column_header_cell("Description", class_name="table-header"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.row_header_cell(rx.code("has_background", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.code("Bool", style=get_code_style_rdx("gray"), class_name="code-style")), - rx.table.cell("Whether to apply the themes background color to the theme node. Defaults to True.", style=cell_style), - ), - rx.table.row( - rx.table.row_header_cell(rx.code("appearance", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.code('"inherit" | "light" | "dark"', style=get_code_style_rdx("gray"), class_name="code-style")), - rx.table.cell("The appearance of the theme. Can be 'light' or 'dark'. Defaults to 'light'.", style=cell_style), - ), - rx.table.row( - rx.table.row_header_cell(rx.code("accent_color", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.code("Str", style=get_code_style_rdx("gray"), class_name="code-style")), - rx.table.cell("The primary color used for default buttons, typography, backgrounds, etc.", style=cell_style), - ), - rx.table.row( - rx.table.row_header_cell(rx.code("gray_color", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.code("Str", style=get_code_style_rdx("gray"), class_name="code-style")), - rx.table.cell("The secondary color used for default buttons, typography, backgrounds, etc.", style=cell_style), - ), - rx.table.row( - rx.table.row_header_cell(rx.code("panel_background", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.code('"solid" | "translucent"', style=get_code_style_rdx("gray"), class_name="code-style")), - rx.table.cell('Whether panel backgrounds are translucent: "solid" | "translucent" (default).', style=cell_style), - ), - rx.table.row( - rx.table.row_header_cell(rx.code("radius", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.code('"none" | "small" | "medium" | "large" | "full"', style=get_code_style_rdx("gray"))), - rx.table.cell("The radius of the theme. Can be 'small', 'medium', or 'large'. Defaults to 'medium'.", style=cell_style), - ), - rx.table.row( - rx.table.row_header_cell(rx.code("scaling", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.code('"90%" | "95%" | "100%" | "105%" | "110%"', style=get_code_style_rdx("gray"), class_name="code-style")), - rx.table.cell("Scale of all theme items.", style=cell_style), - ), - ), - variant="surface", - margin_y="1em", -) - -``` - -Additionally you can modify the theme of your app through using the `Theme Panel` component which can be found in the [Theme Panel docs]({library.other.theme.path}). - - -## Colors - -### Color Scheme - -On a high-level, component `color_scheme` inherits from the color specified in the theme. This means that if you change the theme, the color of the component will also change. Available colors can be found [here](https://www.radix-ui.com/colors). - -You can also specify the `color_scheme` prop. - -```python demo -rx.flex( - rx.button( - "Hello World", - color_scheme="tomato", - ), - rx.button( - "Hello World", - color_scheme="teal", - ), - spacing="2" -) -``` - -### Shades - -Sometime you may want to use a specific shade of a color from the theme. This is recommended vs using a hex color directly as it will automatically change when the theme changes appearance change from light/dark. - - -To access a specific shade of color from the theme, you can use the `rx.color`. When switching to light and dark themes, the color will automatically change. Shades can be accessed by using the color name and the shade number. The shade number ranges from 1 to 12. Additionally, they can have their alpha value set by using the `True` parameter it defaults to `False`. A full list of colors can be found [here](https://www.radix-ui.com/colors). - -```python demo -rx.flex( - rx.button( - "Hello World", - color=rx.color("grass", 1), - background_color=rx.color("grass", 7), - border_color=f"1px solid {rx.color('grass', 1)}", - ), - spacing="2" -) -``` - -```python eval -rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Type"), - rx.table.column_header_cell("Description"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.row_header_cell(rx.code("color", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.code("Str", style=get_code_style_rdx("gray"), class_name="code-style")), - rx.table.cell("The color to use. Can be any valid accent color or 'accent' to reference the current theme color.", style=cell_style), - ), - rx.table.row( - rx.table.row_header_cell(rx.code("shade", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.link(rx.code('1 - 12', style=get_code_style_rdx("gray"), class_name="code-style"), href="https://www.radix-ui.com/colors")), - rx.table.cell("The shade of the color to use. Defaults to 7.", style=cell_style), - ), - rx.table.row( - rx.table.row_header_cell(rx.code("alpha", style=get_code_style_rdx("violet"), class_name="code-style")), - rx.table.cell(rx.code("Bool", style=get_code_style_rdx("gray"), class_name="code-style")), - rx.table.cell("Whether to use the alpha value of the color. Defaults to False.", style=cell_style), - ) - ), - variant="surface", - margin_y="1em", -) - -``` - -### Regular Colors - -You can also use standard hex, rgb, and rgba colors. - -```python demo -rx.flex( - rx.button( - "Hello World", - color="white", - background_color="#87CEFA", - border="1px solid rgb(176,196,222)", - ), - spacing="2" -) -``` - -## Toggle Appearance - -To toggle between the light and dark mode manually, you can use the `toggle_color_mode` with the desired event trigger of your choice. - -```python - -from reflex.style import toggle_color_mode - - - -def index(): - return rx.button( - "Toggle Color Mode", - on_click=toggle_color_mode, - ) -``` - -## Appearance Conditional Rendering - -To render a different component depending on whether the app is in `light` mode or `dark` mode, you can use the `rx.color_mode_cond` component. The first component will be rendered if the app is in `light` mode and the second component will be rendered if the app is in `dark` mode. - -```python demo -rx.color_mode_cond( - light=rx.image(src=f"{REFLEX_ASSETS_CDN}logos/light/reflex.svg", alt="Reflex Logo light", height="4em"), - dark=rx.image(src=f"{REFLEX_ASSETS_CDN}logos/dark/reflex.svg", alt="Reflex Logo dark", height="4em"), -) -``` - -This can also be applied to props. - -```python demo -rx.button( - "Hello World", - color=rx.color_mode_cond(light="black", dark="white"), - background_color=rx.color_mode_cond(light="white", dark="black"), -) -``` diff --git a/docs/ui/overview.md b/docs/ui/overview.md deleted file mode 100644 index f59b8ae855..0000000000 --- a/docs/ui/overview.md +++ /dev/null @@ -1,79 +0,0 @@ -```python exec -from pcweb.pages.docs import components -from pcweb.pages.docs.library import library -import reflex as rx -``` - -# UI Overview - -Components are the building blocks for your app's user interface (UI). They are the visual elements that make up your app, like buttons, text, and images. - -## Component Basics - -Components are made up of children and props. - -```md definition -# Children -* Text or other Reflex components nested inside a component. -* Passed as **positional arguments**. - -# Props -* Attributes that affect the behavior and appearance of a component. -* Passed as **keyword arguments**. -``` - -Let's take a look at the `rx.text` component. - -```python demo -rx.text('Hello World!', color='blue', font_size="1.5em") -``` - -Here `"Hello World!"` is the child text to display, while `color` and `font_size` are props that modify the appearance of the text. - -```md alert success -# Regular Python data types can be passed in as children to components. This is useful for passing in text, numbers, and other simple data types. -``` - -## Another Example - -Now let's take a look at a more complex component, which has other components nested inside it. The `rx.vstack` component is a container that arranges its children vertically with space between them. - -```python demo -rx.vstack( - rx.heading("Sample Form"), - rx.input(placeholder="Name"), - rx.checkbox("Subscribe to Newsletter"), -) -``` - -Some props are specific to a component. For example, the `header` and `content` props of the `rx.accordion.item` component show the heading and accordion content details of the accordion respectively. - -Styling props like `color` are shared across many components. - -```md alert info -# You can find all the props for a component by checking its documentation page in the [component library]({library.path}). -``` - -## Pages - -Reflex apps are organized into pages, each of which maps to a different URL. - -Pages are defined as functions that return a component. By default, the function name will be used as the path, but you can also specify a route explicitly. - -```python -def index(): - return rx.text('Root Page') - - -def about(): - return rx.text('About Page') - - -app = rx.App() -app.add_page(index, route="/") -app.add_page(about, route="/about") -``` - -In this example we add a page called `index` at the root route. -If you `reflex run` the app, you will see the `index` page at `http://localhost:3000`. -Similarly, the `about` page will be available at `http://localhost:3000/about`. diff --git a/docs/utility_methods/exception_handlers.md b/docs/utility_methods/exception_handlers.md deleted file mode 100644 index 80ed6ed51b..0000000000 --- a/docs/utility_methods/exception_handlers.md +++ /dev/null @@ -1,43 +0,0 @@ -# Exception handlers - -_Added in v0.5.7_ - -Exceptions handlers are functions that can be assigned to your app to handle exceptions that occur during the application runtime. -They are useful for customizing the response when an error occurs, logging errors, and performing cleanup tasks. - -## Types - -Reflex support two type of exception handlers `frontend_exception_handler` and `backend_exception_handler`. - -They are used to handle exceptions that occur in the `frontend` and `backend` respectively. - -The `frontend` errors are coming from the JavaScript side of the application, while `backend` errors are coming from the the event handlers on the Python side. - -## Register an Exception Handler - -To register an exception handler, assign it to `app.frontend_exception_handler` or `app.backend_exception_handler` to assign a function that will handle the exception. - -The expected signature for an error handler is `def handler(exception: Exception)`. - -```md alert warning -# Only named functions are supported as exception handler. -``` - -## Examples - -```python -import reflex as rx - -def custom_frontend_handler(exception: Exception) -> None: - # My custom logic for frontend errors - print("Frontend Error: " + str(exception)) - -def custom_backend_handler(exception: Exception) -> Optional[rx.event.EventSpec]: - # My custom logic for backend errors - print("Backend Error: " + str(exception)) - -app = rx.App( - frontend_exception_handler = custom_frontend_handler, - backend_exception_handler = custom_backend_handler - ) -``` \ No newline at end of file diff --git a/docs/utility_methods/lifespan_tasks.md b/docs/utility_methods/lifespan_tasks.md deleted file mode 100644 index 60b7ae1cc2..0000000000 --- a/docs/utility_methods/lifespan_tasks.md +++ /dev/null @@ -1,84 +0,0 @@ -# Lifespan Tasks - -_Added in v0.5.2_ - -Lifespan tasks are coroutines that run when the backend server is running. They -are useful for setting up the initial global state of the app, running periodic -tasks, and cleaning up resources when the server is shut down. - -Lifespan tasks are defined as async coroutines or async contextmanagers. To avoid -blocking the event thread, never use `time.sleep` or perform non-async I/O within -a lifespan task. - -In dev mode, lifespan tasks will stop and restart when a hot-reload occurs. - -## Tasks - -Any async coroutine can be used as a lifespan task. It will be started when the -backend comes up and will run until it returns or is cancelled due to server -shutdown. Long-running tasks should catch `asyncio.CancelledError` to perform -any necessary clean up. - -```python -async def long_running_task(foo, bar): - print(f"Starting \{foo} \{bar} task") - some_api = SomeApi(foo) - try: - while True: - updates = some_api.poll_for_updates() - other_api.push_changes(updates, bar) - await asyncio.sleep(5) # add some polling delay to avoid running too often - except asyncio.CancelledError: - some_api.close() # clean up the API if needed - print("Task was stopped") -``` - -### Register the Task - -To register a lifespan task, use `app.register_lifespan_task(coro_func, **kwargs)`. -Any keyword arguments specified during registration will be passed to the task. - -If the task accepts the special argument, `app`, it will be an instance of the `FastAPI` object -associated with the app. - -```python -app = rx.App() -app.register_lifespan_task(long_running_task, foo=42, bar=os.environ["BAR_PARAM"]) -``` - -## Context Managers - -Lifespan tasks can also be defined as async contextmanagers. This is useful for -setting up and tearing down resources and behaves similarly to the ASGI lifespan -protocol. - -Code up to the first `yield` will run when the backend comes up. As the backend -is shutting down, the code after the `yield` will run to clean up. - -Here is an example borrowed from the FastAPI docs and modified to work with this -interface. - -```python -from contextlib import asynccontextmanager - - -def fake_answer_to_everything_ml_model(x: float): - return x * 42 - - -ml_models = \{} - - -@asynccontextmanager -async def setup_model(app: FastAPI): - # Load the ML model - ml_models["answer_to_everything"] = fake_answer_to_everything_ml_model - yield - # Clean up the ML models and release the resources - ml_models.clear() - -... - -app = rx.App() -app.register_lifespan_task(setup_model) -``` \ No newline at end of file diff --git a/docs/utility_methods/other_methods.md b/docs/utility_methods/other_methods.md deleted file mode 100644 index fab7dee2c6..0000000000 --- a/docs/utility_methods/other_methods.md +++ /dev/null @@ -1,14 +0,0 @@ -# Other Methods - -* `reset`: set all Vars to their default value for the given state (including substates). -* `get_value`: returns the value of a Var **without tracking changes to it**. This is useful - for serialization where the tracking wrapper is considered unserializable. -* `dict`: returns all state Vars (and substates) as a dictionary. This is - used internally when a page is first loaded and needs to be "hydrated" and - sent to the client. - -## Special Attributes - -* `dirty_vars`: a set of all Var names that have been modified since the last - time the state was sent to the client. This is used internally to determine - which Vars need to be sent to the client after processing an event. diff --git a/docs/utility_methods/router_attributes.md b/docs/utility_methods/router_attributes.md deleted file mode 100644 index 1789faeb73..0000000000 --- a/docs/utility_methods/router_attributes.md +++ /dev/null @@ -1,116 +0,0 @@ -```python exec box -import reflex as rx -from pcweb.styles.styles import get_code_style, cell_style - -class RouterState(rx.State): - pass - - -router_data = [ - {"name": "rx.State.router.page.host", "value": RouterState.router.page.host}, - {"name": "rx.State.router.page.path", "value": RouterState.router.page.path}, - {"name": "rx.State.router.page.raw_path", "value": RouterState.router.page.raw_path}, - {"name": "rx.State.router.page.full_path", "value": RouterState.router.page.full_path}, - {"name": "rx.State.router.page.full_raw_path", "value": RouterState.router.page.full_raw_path}, - {"name": "rx.State.router.page.params", "value": RouterState.router.page.params.to_string()}, - {"name": "rx.State.router.session.client_token", "value": RouterState.router.session.client_token}, - {"name": "rx.State.router.session.session_id", "value": RouterState.router.session.session_id}, - {"name": "rx.State.router.session.client_ip", "value": RouterState.router.session.client_ip}, - {"name": "rx.State.router.headers.host", "value": RouterState.router.headers.host}, - {"name": "rx.State.router.headers.origin", "value": RouterState.router.headers.origin}, - {"name": "rx.State.router.headers.upgrade", "value": RouterState.router.headers.upgrade}, - {"name": "rx.State.router.headers.connection", "value": RouterState.router.headers.connection}, - {"name": "rx.State.router.headers.cookie", "value": RouterState.router.headers.cookie}, - {"name": "rx.State.router.headers.pragma", "value": RouterState.router.headers.pragma}, - {"name": "rx.State.router.headers.cache_control", "value": RouterState.router.headers.cache_control}, - {"name": "rx.State.router.headers.user_agent", "value": RouterState.router.headers.user_agent}, - {"name": "rx.State.router.headers.sec_websocket_version", "value": RouterState.router.headers.sec_websocket_version}, - {"name": "rx.State.router.headers.sec_websocket_key", "value": RouterState.router.headers.sec_websocket_key}, - {"name": "rx.State.router.headers.sec_websocket_extensions", "value": RouterState.router.headers.sec_websocket_extensions}, - {"name": "rx.State.router.headers.accept_encoding", "value": RouterState.router.headers.accept_encoding}, - {"name": "rx.State.router.headers.accept_language", "value": RouterState.router.headers.accept_language}, - {"name": "rx.State.router.headers.raw_headers", "value": RouterState.router.headers.raw_headers.to_string()}, - ] - -``` - -# State Utility Methods - -The state object has several methods and attributes that return information -about the current page, session, or state. - -## Router Attributes - -The `self.router` attribute has several sub-attributes that provide various information: - -* `router.page`: data about the current page and route - * `host`: The hostname and port serving the current page (frontend). - * `path`: The path of the current page (for dynamic pages, this will contain the slug) - * `raw_path`: The path of the page displayed in the browser (including params and dynamic values) - * `full_path`: `path` with `host` prefixed - * `full_raw_path`: `raw_path` with `host` prefixed - * `params`: Dictionary of query params associated with the request - -* `router.session`: data about the current session - * `client_token`: UUID associated with the current tab's token. Each tab has a unique token. - * `session_id`: The ID associated with the client's websocket connection. Each tab has a unique session ID. - * `client_ip`: The IP address of the client. Many users may share the same IP address. - -* `router.headers`: headers associated with the websocket connection. These values can only change when the websocket is re-established (for example, during page refresh). - * `host`: The hostname and port serving the websocket (backend). - * `origin`: The origin of the request. - * `upgrade`: The upgrade header for websocket connections. - * `connection`: The connection header. - * `cookie`: The cookie header. - * `pragma`: The pragma header. - * `cache_control`: The cache control header. - * `user_agent`: The user agent string of the client. - * `sec_websocket_version`: The websocket version. - * `sec_websocket_key`: The websocket key. - * `sec_websocket_extensions`: The websocket extensions. - * `accept_encoding`: The accepted encodings. - * `accept_language`: The accepted languages. - * `raw_headers`: A mapping of all HTTP headers as a frozen dictionary. This provides access to any header that was sent with the request, not just the common ones listed above. - -### Example Values on this Page - -```python eval -rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Name"), - rx.table.column_header_cell("Value"), - ), - ), - rx.table.body( - *[ - rx.table.row( - rx.table.cell(item["name"], style=cell_style), - rx.table.cell(rx.code(item["value"], style=get_code_style("violet"))), - ) - for item in router_data - ] - ), - variant="surface", - margin_y="1em", - ) -``` - -### Accessing Raw Headers - -The `raw_headers` attribute provides access to all HTTP headers as a frozen dictionary. This is useful when you need to access headers that are not explicitly defined in the `HeaderData` class: - -```python box -# Access a specific header -custom_header_value = self.router.headers.raw_headers.get("x-custom-header", "") - -# Example of accessing common headers -user_agent = self.router.headers.raw_headers.get("user-agent", "") -content_type = self.router.headers.raw_headers.get("content-type", "") -authorization = self.router.headers.raw_headers.get("authorization", "") - -# You can also check if a header exists -has_custom_header = "x-custom-header" in self.router.headers.raw_headers -``` - -This is particularly useful for accessing custom headers or when working with specific HTTP headers that are not part of the standard set exposed as direct attributes. diff --git a/docs/vars/base_vars.md b/docs/vars/base_vars.md deleted file mode 100644 index 07b207dc7a..0000000000 --- a/docs/vars/base_vars.md +++ /dev/null @@ -1,228 +0,0 @@ -```python exec -import random -import time - -import reflex as rx - -from pcweb.pages.docs import vars -``` - -# Base Vars - -Vars are any fields in your app that may change over time. A Var is directly -rendered into the frontend of the app. - -Base vars are defined as fields in your State class. - -They can have a preset default value. If you don't provide a default value, you -must provide a type annotation. - -```md alert warning -# State Vars should provide type annotations. - -Reflex relies on type annotations to determine the type of state vars during the compilation process. -``` - -```python demo exec -class TickerState(rx.State): - ticker: str ="AAPL" - price: str = "$150" - - -def ticker_example(): - return rx.center( - rx.vstack( - rx.heading(TickerState.ticker, size="3"), - rx.text(f"Current Price: {TickerState.price}", font_size="md"), - rx.text("Change: 4%", color="green"), - ), - - ) -``` - -In this example `ticker` and `price` are base vars in the app, which can be modified at runtime. - -```md alert warning -# Vars must be JSON serializable. - -Vars are used to communicate between the frontend and backend. They must be primitive Python types, Plotly figures, Pandas dataframes, or [a custom defined type]({vars.custom_vars.path}). -``` - -## Accessing state variables on different pages - -State is just a python class and so can be defined on one page and then imported and used on another. Below we define `TickerState` class on the page `state.py` and then import it and use it on the page `index.py`. - -```python -# state.py - -class TickerState(rx.State): - ticker: str = "AAPL" - price: str = "$150" -``` - -```python -# index.py -from .state import TickerState - -def ticker_example(): - return rx.center( - rx.vstack( - rx.heading(TickerState.ticker, size="3"), - rx.text(f"Current Price: \{TickerState.price}", font_size="md"), - rx.text("Change: 4%", color="green"), - ), - - ) -``` - -## Backend-only Vars - -Any Var in a state class that starts with an underscore (`_`) is considered backend -only and will **not be synchronized with the frontend**. Data associated with a -specific session that is _not directly rendered on the frontend should be stored -in a backend-only var_ to reduce network traffic and improve performance. - -They have the advantage that they don't need to be JSON serializable, however -they must still be pickle-able to be used with redis in prod mode. They are -not directly renderable on the frontend, and **may be used to store sensitive -values that should not be sent to the client**. - -```md alert warning -# Protect auth data and sensitive state in backend-only vars. - -Regular vars and computed vars should **only** be used for rendering the state -of your app in the frontend. Having any type of permissions or authenticated state based on -a regular var presents a security risk as you may assume these have shared control -with the frontend (client) due to default setter methods. - -For improved security, `state_auto_setters=False` may be set in `rxconfig.py` -to prevent the automatic generation of setters for regular vars, however, the -client will still be able to locally modify the contents of frontend vars as -they are presented in the UI. -``` - -For example, a backend-only var is used to store a large data structure which is -then paged to the frontend using cached vars. - -```python demo exec -import numpy as np - - -class BackendVarState(rx.State): - _backend: np.ndarray = np.array([random.randint(0, 100) for _ in range(100)]) - offset: int = 0 - limit: int = 10 - - @rx.var(cache=True) - def page(self) -> list[int]: - return [ - int(x) # explicit cast to int - for x in self._backend[self.offset : self.offset + self.limit] - ] - - @rx.var(cache=True) - def page_number(self) -> int: - return (self.offset // self.limit) + 1 + (1 if self.offset % self.limit else 0) - - @rx.var(cache=True) - def total_pages(self) -> int: - return len(self._backend) // self.limit + (1 if len(self._backend) % self.limit else 0) - - @rx.event - def prev_page(self): - self.offset = max(self.offset - self.limit, 0) - - @rx.event - def next_page(self): - if self.offset + self.limit < len(self._backend): - self.offset += self.limit - - @rx.event - def generate_more(self): - self._backend = np.append(self._backend, [random.randint(0, 100) for _ in range(random.randint(0, 100))]) - - @rx.event - def set_limit(self, value: str): - self.limit = int(value) - -def backend_var_example(): - return rx.vstack( - rx.hstack( - rx.button( - "Prev", - on_click=BackendVarState.prev_page, - ), - rx.text(f"Page {BackendVarState.page_number} / {BackendVarState.total_pages}"), - rx.button( - "Next", - on_click=BackendVarState.next_page, - ), - rx.text("Page Size"), - rx.input( - width="5em", - value=BackendVarState.limit, - on_change=BackendVarState.set_limit, - ), - rx.button("Generate More", on_click=BackendVarState.generate_more), - ), - rx.list( - rx.foreach( - BackendVarState.page, - lambda x, ix: rx.text(f"_backend[{ix + BackendVarState.offset}] = {x}"), - ), - ), - ) -``` - - -## Using rx.field / rx.Field to improve type hinting for vars - -When defining state variables you can use `rx.Field[T]` to annotate the variable's type. Then, you can initialize the variable using `rx.field(default_value)`, where `default_value` is an instance of type `T`. - -This approach makes the variable's type explicit, aiding static analysis tools in type checking. In addition, it shows you what methods are allowed to modify the variable in your frontend code, as they are listed in the type hint. - -Below are two examples: - -```python -import reflex as rx - -app = rx.App() - - -class State(rx.State): - x: rx.Field[bool] = rx.field(False) - - def flip(self): - self.x = not self.x - - -@app.add_page -def index(): - return rx.vstack( - rx.button("Click me", on_click=State.flip), - rx.text(State.x), - rx.text(~State.x), - ) -``` - -Here `State.x`, as it is typed correctly as a `boolean` var, gets better code completion, i.e. here we get options such as `to_string()` or `equals()`. - - -```python -import reflex as rx - -app = rx.App() - - -class State(rx.State): - x: rx.Field[dict[str, list[int]]] = rx.field(default_factory=dict) - - -@app.add_page -def index(): - return rx.vstack( - rx.text(State.x.values()[0][0]), - ) -``` - -Here `State.x`, as it is typed correctly as a `dict` of `str` to `list` of `int` var, gets better code completion, i.e. here we get options such as `contains()`, `keys()`, `values()`, `items()` or `merge()`. \ No newline at end of file diff --git a/docs/vars/computed_vars.md b/docs/vars/computed_vars.md deleted file mode 100644 index ee711609d1..0000000000 --- a/docs/vars/computed_vars.md +++ /dev/null @@ -1,190 +0,0 @@ -```python exec -import random -import time -import asyncio - -import reflex as rx -``` - -# Computed Vars - -Computed vars have values derived from other properties on the backend. They are -defined as methods in your State class with the `@rx.var` decorator. - -Try typing in the input box and clicking out. - -```python demo exec id=upper -class UppercaseState(rx.State): - text: str = "hello" - - def set_text(self, value: str): - self.text = value - - @rx.var - def upper_text(self) -> str: - # This will be recomputed whenever `text` changes. - return self.text.upper() - - -def uppercase_example(): - return rx.vstack( - rx.heading(UppercaseState.upper_text), - rx.input(on_blur=UppercaseState.set_text, placeholder="Type here..."), - ) -``` - -Here, `upper_text` is a computed var that always holds the upper case version of `text`. - -We recommend always using type annotations for computed vars. - -## Cached Vars - -By default, all computed vars are cached (`cache=True`). A cached var is only -recomputed when the other state vars it depends on change. This is useful for -expensive computations, but in some cases it may not update when you expect it to. - -To create a computed var that recomputes on every state update regardless of -dependencies, use `@rx.var(cache=False)`. - -Previous versions of Reflex had a `@rx.cached_var` decorator, which is now replaced -by the `cache` argument of `@rx.var` (which defaults to `True`). - -```python demo exec -class CachedVarState(rx.State): - counter_a: int = 0 - counter_b: int = 0 - - @rx.var(cache=False) - def last_touch_time(self) -> str: - # This is updated anytime the state is updated. - return time.strftime("%H:%M:%S") - - @rx.event - def increment_a(self): - self.counter_a += 1 - - @rx.var(cache=True) - def last_counter_a_update(self) -> str: - # This is updated only when `counter_a` changes. - return f"{self.counter_a} at {time.strftime('%H:%M:%S')}" - - @rx.event - def increment_b(self): - self.counter_b += 1 - - @rx.var(cache=True) - def last_counter_b_update(self) -> str: - # This is updated only when `counter_b` changes. - return f"{self.counter_b} at {time.strftime('%H:%M:%S')}" - - -def cached_var_example(): - return rx.vstack( - rx.text(f"State touched at: {CachedVarState.last_touch_time}"), - rx.text(f"Counter A: {CachedVarState.last_counter_a_update}"), - rx.text(f"Counter B: {CachedVarState.last_counter_b_update}"), - rx.hstack( - rx.button("Increment A", on_click=CachedVarState.increment_a), - rx.button("Increment B", on_click=CachedVarState.increment_b), - ), - ) -``` - -In this example `last_touch_time` uses `cache=False` to ensure it updates any -time the state is modified. `last_counter_a_update` is a cached computed var (using -the default `cache=True`) that only depends on `counter_a`, so it only gets recomputed -when `counter_a` changes. Similarly `last_counter_b_update` only depends on `counter_b`, -and thus is updated only when `counter_b` changes. - -## Async Computed Vars - -Async computed vars allow you to use asynchronous operations in your computed vars. -They are defined as async methods in your State class with the same `@rx.var` decorator. -Async computed vars are useful for operations that require asynchronous processing, such as: - -- Fetching data from external APIs -- Database operations -- File I/O operations -- Any other operations that benefit from async/await - -```python demo exec -class AsyncVarState(rx.State): - count: int = 0 - - @rx.var - async def delayed_count(self) -> int: - # Simulate an async operation like an API call - await asyncio.sleep(0.5) - return self.count * 2 - - @rx.event - def increment(self): - self.count += 1 - - -def async_var_example(): - return rx.vstack( - rx.heading("Async Computed Var Example"), - rx.text(f"Count: {AsyncVarState.count}"), - rx.text(f"Delayed count (x2): {AsyncVarState.delayed_count}"), - rx.button("Increment", on_click=AsyncVarState.increment), - spacing="4", - ) -``` - -In this example, `delayed_count` is an async computed var that returns the count multiplied by 2 after a simulated delay. -When the count changes, the async computed var is automatically recomputed. - -### Caching Async Computed Vars - -Just like regular computed vars, async computed vars can also be cached. This is especially -useful for expensive async operations like API calls or database queries. - -```python demo exec -class AsyncCachedVarState(rx.State): - user_id: int = 1 - refresh_trigger: int = 0 - - @rx.var(cache=True) - async def user_data(self) -> dict: - # In a real app, this would be an API call - await asyncio.sleep(1) # Simulate network delay - - # Simulate different user data based on user_id - users = { - 1: {"name": "Alice", "email": "alice@example.com"}, - 2: {"name": "Bob", "email": "bob@example.com"}, - 3: {"name": "Charlie", "email": "charlie@example.com"}, - } - - return users.get(self.user_id, {"name": "Unknown", "email": "unknown"}) - - @rx.event - def change_user(self): - # Cycle through users 1-3 - self.user_id = (self.user_id % 3) + 1 - - @rx.event - def force_refresh(self): - # This will not affect user_data dependencies, but will trigger a state update - self.refresh_trigger += 1 - - -def async_cached_var_example(): - return rx.vstack( - rx.heading("Cached Async Computed Var Example"), - rx.text(f"User ID: {AsyncCachedVarState.user_id}"), - rx.text(f"User Name: {AsyncCachedVarState.user_data['name']}"), - rx.text(f"User Email: {AsyncCachedVarState.user_data['email']}"), - rx.hstack( - rx.button("Change User", on_click=AsyncCachedVarState.change_user), - rx.button("Force Refresh (No Effect)", on_click=AsyncCachedVarState.force_refresh), - ), - rx.text("Note: The cached async var only updates when user_id changes, not when refresh_trigger changes."), - spacing="4", - ) -``` - -In this example, `user_data` is a cached async computed var that simulates fetching user data. -It is only recomputed when `user_id` changes, not when other state variables like `refresh_trigger` change. -This demonstrates how caching works with async computed vars to optimize performance for expensive operations. diff --git a/docs/vars/custom_vars.md b/docs/vars/custom_vars.md deleted file mode 100644 index 4de84b059b..0000000000 --- a/docs/vars/custom_vars.md +++ /dev/null @@ -1,82 +0,0 @@ -```python exec -import reflex as rx -import dataclasses -from typing import TypedDict - -from pcweb.pages.docs import vars -``` - -# Custom Vars - -As mentioned in the [vars page]({vars.base_vars.path}), Reflex vars must be JSON serializable. - -This means we can support any Python primitive types, as well as lists, dicts, and tuples. However, you can also create more complex var types using dataclasses (recommended), TypedDict, or Pydantic models. - -## Defining a Type - -In this example, we will create a custom var type for storing translations using a dataclass. - -Once defined, we can use it as a state var, and reference it from within a component. - -```python demo exec -import googletrans -import dataclasses -from typing import TypedDict - -@dataclasses.dataclass -class Translation: - original_text: str - translated_text: str - -class TranslationState(rx.State): - input_text: str = "Hola Mundo" - current_translation: Translation = Translation(original_text="", translated_text="") - - # Explicitly define the setter method - def set_input_text(self, value: str): - self.input_text = value - - @rx.event - def translate(self): - self.current_translation.original_text = self.input_text - self.current_translation.translated_text = googletrans.Translator().translate(self.input_text, dest="en").text - -def translation_example(): - return rx.vstack( - rx.input( - on_blur=TranslationState.set_input_text, - default_value=TranslationState.input_text, - placeholder="Text to translate...", - ), - rx.button("Translate", on_click=TranslationState.translate), - rx.text(TranslationState.current_translation.translated_text), - ) -``` - -## Alternative Approaches - -### Using TypedDict - -You can also use TypedDict for defining custom var types: - -```python -from typing import TypedDict - -class Translation(TypedDict): - original_text: str - translated_text: str -``` - -### Using Pydantic Models - -Pydantic models are another option for complex data structures: - -```python -from pydantic import BaseModel - -class Translation(BaseModel): - original_text: str - translated_text: str -``` - -For complex data structures, dataclasses are recommended as they provide a clean, type-safe way to define custom var types with good IDE support. diff --git a/docs/vars/var-operations.md b/docs/vars/var-operations.md deleted file mode 100644 index 7a56a73e79..0000000000 --- a/docs/vars/var-operations.md +++ /dev/null @@ -1,555 +0,0 @@ -```python exec -import random -import time - -import numpy as np - -import reflex as rx - -from pcweb.templates.docpage import docpage -``` - -# Var Operations - -Var operations transform the placeholder representation of the value on the -frontend and provide a way to perform basic operations on the Var without having -to define a computed var. - -Within your frontend components, you cannot use arbitrary Python functions on -the state vars. For example, the following code will **not work.** - -```python -class State(rx.State): - number: int - -def index(): - # This will be compiled before runtime, before `State.number` has a known value. - # Since `float` is not a valid var operation, this will throw an error. - rx.text(float(State.number)) -``` - -This is because we compile the frontend to Javascript, but the value of `State.number` -is only known at runtime. - -In this example below we use a var operation to concatenate a `string` with a `var`, meaning we do not have to do in within state as a computed var. - -```python demo exec -coins = ["BTC", "ETH", "LTC", "DOGE"] - -class VarSelectState(rx.State): - selected: str = "DOGE" - - def set_selected(self, value: str): - self.selected = value - -def var_operations_example(): - return rx.vstack( - # Using a var operation to concatenate a string with a var. - rx.heading("I just bought a bunch of " + VarSelectState.selected), - # Using an f-string to interpolate a var. - rx.text(f"{VarSelectState.selected} is going to the moon!"), - rx.select( - coins, - value=VarSelectState.selected, - on_change=VarSelectState.set_selected, - ) - ) -``` - -```md alert success -# Vars support many common operations. - -They can be used for arithmetic, string concatenation, inequalities, indexing, and more. See the [full list of supported operations](/docs/api-reference/var/). -``` - -## Supported Operations - -Var operations allow us to change vars on the front-end without having to create more computed vars on the back-end in the state. - -Some simple examples are the `==` var operator, which is used to check if two vars are equal and the `to_string()` var operator, which is used to convert a var to a string. - -```python demo exec - -fruits = ["Apple", "Banana", "Orange", "Mango"] - -class EqualsState(rx.State): - selected: str = "Apple" - favorite: str = "Banana" - - def set_selected(self, value: str): - self.selected = value - - -def var_equals_example(): - return rx.vstack( - rx.text(EqualsState.favorite.to_string() + " is my favorite fruit!"), - rx.select( - fruits, - value=EqualsState.selected, - on_change=EqualsState.set_selected, - ), - rx.cond( - EqualsState.selected == EqualsState.favorite, - rx.text("The selected fruit is equal to the favorite fruit!"), - rx.text("The selected fruit is not equal to the favorite fruit."), - ), - ) - -``` - -### Negate, Absolute and Length - -The `-` operator is used to get the negative version of the var. The `abs()` operator is used to get the absolute value of the var. The `.length()` operator is used to get the length of a list var. - -```python demo exec -import random - -class OperState(rx.State): - number: int - numbers_seen: list = [] - - @rx.event - def update(self): - self.number = random.randint(-100, 100) - self.numbers_seen.append(self.number) - -def var_operation_example(): - return rx.vstack( - rx.heading(f"The number: {OperState.number}", size="3"), - rx.hstack( - rx.text("Negated:", rx.badge(-OperState.number, variant="soft", color_scheme="green")), - rx.text(f"Absolute:", rx.badge(abs(OperState.number), variant="soft", color_scheme="blue")), - rx.text(f"Numbers seen:", rx.badge(OperState.numbers_seen.length(), variant="soft", color_scheme="red")), - ), - rx.button("Update", on_click=OperState.update), - ) -``` - -### Comparisons and Mathematical Operators - -All of the comparison operators are used as expected in python. These include `==`, `!=`, `>`, `>=`, `<`, `<=`. - -There are operators to add two vars `+`, subtract two vars `-`, multiply two vars `*` and raise a var to a power `pow()`. - -```python demo exec -import random - -class CompState(rx.State): - number_1: int - number_2: int - - @rx.event - def update(self): - self.number_1 = random.randint(-10, 10) - self.number_2 = random.randint(-10, 10) - -def var_comparison_example(): - - return rx.vstack( - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Integer 1"), - rx.table.column_header_cell("Integer 2"), - rx.table.column_header_cell("Operation"), - rx.table.column_header_cell("Outcome"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.row_header_cell(CompState.number_1), - rx.table.cell(CompState.number_2), - rx.table.cell("Int 1 == Int 2"), - rx.table.cell((CompState.number_1 == CompState.number_2).to_string()), - ), - rx.table.row( - rx.table.row_header_cell(CompState.number_1), - rx.table.cell(CompState.number_2), - rx.table.cell("Int 1 != Int 2"), - rx.table.cell((CompState.number_1 != CompState.number_2).to_string()), - ), - rx.table.row( - rx.table.row_header_cell(CompState.number_1), - rx.table.cell(CompState.number_2), - rx.table.cell("Int 1 > Int 2"), - rx.table.cell((CompState.number_1 > CompState.number_2).to_string()), - ), - rx.table.row( - rx.table.row_header_cell(CompState.number_1), - rx.table.cell(CompState.number_2), - rx.table.cell("Int 1 >= Int 2"), - rx.table.cell((CompState.number_1 >= CompState.number_2).to_string()), - ), - rx.table.row( - rx.table.row_header_cell(CompState.number_1), - rx.table.cell(CompState.number_2, ), - rx.table.cell("Int 1 < Int 2 "), - rx.table.cell((CompState.number_1 < CompState.number_2).to_string()), - ), - rx.table.row( - rx.table.row_header_cell(CompState.number_1), - rx.table.cell(CompState.number_2), - rx.table.cell("Int 1 <= Int 2"), - rx.table.cell((CompState.number_1 <= CompState.number_2).to_string()), - ), - - rx.table.row( - rx.table.row_header_cell(CompState.number_1), - rx.table.cell(CompState.number_2), - rx.table.cell("Int 1 + Int 2"), - rx.table.cell(f"{(CompState.number_1 + CompState.number_2)}"), - ), - rx.table.row( - rx.table.row_header_cell(CompState.number_1), - rx.table.cell(CompState.number_2), - rx.table.cell("Int 1 - Int 2"), - rx.table.cell(f"{CompState.number_1 - CompState.number_2}"), - ), - rx.table.row( - rx.table.row_header_cell(CompState.number_1), - rx.table.cell(CompState.number_2), - rx.table.cell("Int 1 * Int 2"), - rx.table.cell(f"{CompState.number_1 * CompState.number_2}"), - ), - rx.table.row( - rx.table.row_header_cell(CompState.number_1), - rx.table.cell(CompState.number_2), - rx.table.cell("pow(Int 1, Int2)"), - rx.table.cell(f"{pow(CompState.number_1, CompState.number_2)}"), - ), - ), - width="100%", - ), - rx.button("Update", on_click=CompState.update), - ) -``` - -### True Division, Floor Division and Remainder - -The operator `/` represents true division. The operator `//` represents floor division. The operator `%` represents the remainder of the division. - -```python demo exec -import random - -class DivState(rx.State): - number_1: float = 3.5 - number_2: float = 1.4 - - @rx.event - def update(self): - self.number_1 = round(random.uniform(5.1, 9.9), 2) - self.number_2 = round(random.uniform(0.1, 4.9), 2) - -def var_div_example(): - return rx.vstack( - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Integer 1"), - rx.table.column_header_cell("Integer 2"), - rx.table.column_header_cell("Operation"), - rx.table.column_header_cell("Outcome"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.row_header_cell(DivState.number_1), - rx.table.cell(DivState.number_2), - rx.table.cell("Int 1 / Int 2"), - rx.table.cell(f"{DivState.number_1 / DivState.number_2}"), - ), - rx.table.row( - rx.table.row_header_cell(DivState.number_1), - rx.table.cell(DivState.number_2), - rx.table.cell("Int 1 // Int 2"), - rx.table.cell(f"{DivState.number_1 // DivState.number_2}"), - ), - rx.table.row( - rx.table.row_header_cell(DivState.number_1), - rx.table.cell(DivState.number_2), - rx.table.cell("Int 1 % Int 2"), - rx.table.cell(f"{DivState.number_1 % DivState.number_2}"), - ), - ), - width="100%", - ), - rx.button("Update", on_click=DivState.update), - ) -``` - -### And, Or and Not - -In Reflex the `&` operator represents the logical AND when used in the front end. This means that it returns true only when both conditions are true simultaneously. -The `|` operator represents the logical OR when used in the front end. This means that it returns true when either one or both conditions are true. -The `~` operator is used to invert a var. It is used on a var of type `bool` and is equivalent to the `not` operator. - -```python demo exec -import random - -class LogicState(rx.State): - var_1: bool = True - var_2: bool = True - - @rx.event - def update(self): - self.var_1 = random.choice([True, False]) - self.var_2 = random.choice([True, False]) - -def var_logical_example(): - return rx.vstack( - rx.table.root( - rx.table.header( - rx.table.row( - rx.table.column_header_cell("Var 1"), - rx.table.column_header_cell("Var 2"), - rx.table.column_header_cell("Operation"), - rx.table.column_header_cell("Outcome"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.row_header_cell(LogicState.var_1.to_string()), - rx.table.cell(LogicState.var_2.to_string()), - rx.table.cell("Logical AND (&)"), - rx.table.cell((LogicState.var_1 & LogicState.var_2).to_string()), - ), - rx.table.row( - rx.table.row_header_cell(LogicState.var_1.to_string()), - rx.table.cell(LogicState.var_2.to_string()), - rx.table.cell("Logical OR (|)"), - rx.table.cell((LogicState.var_1 | LogicState.var_2).to_string()), - ), - rx.table.row( - rx.table.row_header_cell(LogicState.var_1.to_string()), - rx.table.cell(LogicState.var_2.to_string()), - rx.table.cell("The invert of Var 1 (~)"), - rx.table.cell((~LogicState.var_1).to_string()), - ), - - ), - width="100%", - ), - rx.button("Update", on_click=LogicState.update), - ) -``` - -### Contains, Reverse and Join - -The 'in' operator is not supported for Var types, we must use the `Var.contains()` instead. When we use `contains`, the var must be of type: `dict`, `list`, `tuple` or `str`. -`contains` checks if a var contains the object that we pass to it as an argument. - -We use the `reverse` operation to reverse a list var. The var must be of type `list`. - -Finally we use the `join` operation to join a list var into a string. - -```python demo exec -class ListsState(rx.State): - list_1: list = [1, 2, 3, 4, 6] - list_2: list = [7, 8, 9, 10] - list_3: list = ["p","y","t","h","o","n"] - -def var_list_example(): - return rx.hstack( - rx.vstack( - rx.heading(f"List 1: {ListsState.list_1}", size="3"), - rx.text(f"List 1 Contains 3: {ListsState.list_1.contains(3)}"), - ), - rx.vstack( - rx.heading(f"List 2: {ListsState.list_2}", size="3"), - rx.text(f"Reverse List 2: {ListsState.list_2.reverse()}"), - ), - rx.vstack( - rx.heading(f"List 3: {ListsState.list_3}", size="3"), - rx.text(f"List 3 Joins: {ListsState.list_3.join()}"), - ), - ) -``` - -### Lower, Upper, Split - -The `lower` operator converts a string var to lowercase. The `upper` operator converts a string var to uppercase. The `split` operator splits a string var into a list. - -```python demo exec -class StringState(rx.State): - string_1: str = "PYTHON is FUN" - string_2: str = "react is hard" - - -def var_string_example(): - return rx.hstack( - rx.vstack( - rx.heading(f"List 1: {StringState.string_1}", size="3"), - rx.text(f"List 1 Lower Case: {StringState.string_1.lower()}"), - ), - rx.vstack( - rx.heading(f"List 2: {StringState.string_2}", size="3"), - rx.text(f"List 2 Upper Case: {StringState.string_2.upper()}"), - rx.text(f"Split String 2: {StringState.string_2.split()}"), - ), - ) -``` - -## Get Item (Indexing) - -Indexing is only supported for strings, lists, tuples, dicts, and dataframes. To index into a state var strict type annotations are required. - -```python -class GetItemState1(rx.State): - list_1: list = [50, 10, 20] - -def get_item_error_1(): - return rx.progress(value=GetItemState1.list_1[0]) -``` - -In the code above you would expect to index into the first index of the list_1 state var. In fact the code above throws the error: `Invalid var passed for prop value, expected type , got value of type typing.Any.` This is because the type of the items inside the list have not been clearly defined in the state. To fix this you change the list_1 definition to `list_1: list[int] = [50, 10, 20]` - -```python demo exec -class GetItemState1(rx.State): - list_1: list[int] = [50, 10, 20] - -def get_item_error_1(): - return rx.progress(value=GetItemState1.list_1[0]) -``` - -### Using with Foreach - -Errors frequently occur when using indexing and `foreach`. - -```python -class ProjectsState(rx.State): - projects: List[dict] = [ - { - "technologies": ["Next.js", "Prisma", "Tailwind", "Google Cloud", "Docker", "MySQL"] - }, - { - "technologies": ["Python", "Flask", "Google Cloud", "Docker"] - } - ] - -def get_badge(technology: str) -> rx.Component: - return rx.badge(technology, variant="soft", color_scheme="green") - -def project_item(project: dict): - return rx.box( - rx.hstack( - rx.foreach(project["technologies"], get_badge) - ), - ) - -def failing_projects_example() -> rx.Component: - return rx.box(rx.foreach(ProjectsState.projects, project_item)) -``` - -The code above throws the error `TypeError: Could not foreach over var of type Any. (If you are trying to foreach over a state var, add a type annotation to the var.)` - -We must change `projects: list[dict]` => `projects: list[dict[str, list]]` because while projects is annotated, the item in project["technologies"] is not. - -```python demo exec -class ProjectsState(rx.State): - projects: list[dict[str, list]] = [ - { - "technologies": ["Next.js", "Prisma", "Tailwind", "Google Cloud", "Docker", "MySQL"] - }, - { - "technologies": ["Python", "Flask", "Google Cloud", "Docker"] - } - ] - - -def projects_example() -> rx.Component: - def get_badge(technology: str) -> rx.Component: - return rx.badge(technology, variant="soft", color_scheme="green") - - def project_item(project: dict) -> rx.Component: - - return rx.box( - rx.hstack( - rx.foreach(project["technologies"], get_badge) - ), - ) - return rx.box(rx.foreach(ProjectsState.projects, project_item)) -``` - -The previous example had only a single type for each of the dictionaries `keys` and `values`. For complex multi-type data, you need to use a dataclass, as shown below. - -```python demo exec -import dataclasses - -@dataclasses.dataclass -class ActressType: - actress_name: str - age: int - pages: list[dict[str, str]] - -class MultiDataTypeState(rx.State): - """The app state.""" - actresses: list[ActressType] = [ - ActressType( - actress_name="Ariana Grande", - age=30, - pages=[ - {"url": "arianagrande.com"}, {"url": "https://es.wikipedia.org/wiki/Ariana_Grande"} - ] - ), - ActressType( - actress_name="Gal Gadot", - age=38, - pages=[ - {"url": "http://www.galgadot.com/"}, {"url": "https://es.wikipedia.org/wiki/Gal_Gadot"} - ] - ) - ] - -def actresses_example() -> rx.Component: - def showpage(page: dict[str, str]): - return rx.vstack( - rx.text(page["url"]), - ) - - def showlist(item: ActressType): - return rx.vstack( - rx.hstack( - rx.text(item.actress_name), - rx.text(item.age), - ), - rx.foreach(item.pages, showpage), - ) - return rx.box(rx.foreach(MultiDataTypeState.actresses, showlist)) - -``` - -Setting the type of `actresses` to be `actresses: list[dict[str,str]]` would fail as it cannot be understood that the `value` for the `pages key` is actually a `list`. - -## Combine Multiple Var Operations - -You can also combine multiple var operations together, as seen in the next example. - -```python demo exec -import random - -class VarNumberState(rx.State): - number: int - - @rx.event - def update(self): - self.number = random.randint(0, 100) - -def var_number_example(): - return rx.vstack( - rx.heading(f"The number is {VarNumberState.number}", size="5"), - # Var operations can be composed for more complex expressions. - rx.cond( - VarNumberState.number % 2 == 0, - rx.text("Even", color="green"), - rx.text("Odd", color="red"), - ), - rx.button("Update", on_click=VarNumberState.update), - ) -``` - -We could have made a computed var that returns the parity of `number`, but -it can be simpler just to use a var operation instead. - -Var operations may be generally chained to make compound expressions, however -some complex transformations not supported by var operations must use computed vars -to calculate the value on the backend. diff --git a/docs/wrapping-react/custom-code-and-hooks.md b/docs/wrapping-react/custom-code-and-hooks.md deleted file mode 100644 index 21281de75b..0000000000 --- a/docs/wrapping-react/custom-code-and-hooks.md +++ /dev/null @@ -1,108 +0,0 @@ - -When wrapping a React component, you may need to define custom code or hooks that are specific to the component. This is done by defining the `add_custom_code`or `add_hooks` methods in your component class. - -## Custom Code - -Custom code is any JS code that need to be included in your page, but not necessarily in the component itself. This can include things like CSS styles, JS libraries, or any other code that needs to be included in the page. - -```python -class CustomCodeComponent(MyBaseComponent): - """MyComponent.""" - - def add_custom_code(self) -> list[str]: - """Add custom code to the component.""" - code1 = """const customVariable = "Custom code1";""" - code2 = """console.log(customVariable);""" - - return [code1, code2] -``` - -The above example will render the following JS code in the page: - -```javascript -/* import here */ - -const customVariable = "Custom code1"; -console.log(customVariable); - -/* rest of the page code */ -``` - -## Custom Hooks -Custom hooks are any hooks that need to be included in your component. This can include things like `useEffect`, `useState`, or any other hooks from the library you are wrapping. - -- Simple hooks can be added as strings. -- More complex hooks that need to have special import or be written in a specific order can be added as `rx.Var` with a `VarData` object to specify the position of the hook. - - The `imports` attribute of the `VarData` object can be used to specify any imports that need to be included in the component. - - The `position` attribute of the `VarData` object can be set to `Hooks.HookPosition.PRE_TRIGGER` or `Hooks.HookPosition.POST_TRIGGER` to specify the position of the hook in the component. - -```md alert info -# The `position` attribute is only used for hooks that need to be written in a specific order. -- If an event handler need to refer to a variable defined in a hook, the hook should be written before the event handler. -- If a hook need to refer to the memoized event handler by name, the hook should be written after the event handler. -``` - -```python -from reflex.vars.base import Var, VarData -from reflex.constants import Hooks -from reflex.components.el.elements import Div - -class ComponentWithHooks(Div, MyBaseComponent): - """MyComponent.""" - - def add_hooks(self) -> list[str| Var]: - """Add hooks to the component.""" - hooks = [] - hooks1 = """const customHookVariable = "some value";""" - hooks.append(hooks1) - - # A hook that need to be written before the memoized event handlers. - hooks2 = Var( - """useEffect(() => { - console.log("PreTrigger: " + customHookVariable); - }, []); - """, - _var_data=VarData( - imports=\{"react": ["useEffect"],\}, - position=Hooks.HookPosition.PRE_TRIGGER - ), - ) - hooks.append(hooks2) - - hooks3 = Var( - """useEffect(() => { - console.log("PostTrigger: " + customHookVariable); - }, []); - """, - _var_data=VarData( - imports=\{"react": ["useEffect"],\}, - position=Hooks.HookPosition.POST_TRIGGER - ), - ) - hooks.append(hooks3) - return hooks -``` - -The `ComponentWithHooks` will be rendered in the component in the following way: - -```javascript -export function Div_7178f430b7b371af8a12d8265d65ab9b() { - const customHookVariable = "some value"; - - useEffect(() => { - console.log("PreTrigger: " + customHookVariable); - }, []); - - /* memoized triggers such as on_click, on_change, etc will render here */ - - useEffect(() => { - console.log("PostTrigger: "+ customHookVariable); - }, []); - - return jsx("div", \{\}); -} -``` - -```md alert info -# You can mix custom code and hooks in the same component. Hooks can access a variable defined in the custom code, but custom code cannot access a variable defined in a hook. -``` diff --git a/docs/wrapping-react/example.md b/docs/wrapping-react/example.md deleted file mode 100644 index 5cb34ace0a..0000000000 --- a/docs/wrapping-react/example.md +++ /dev/null @@ -1,408 +0,0 @@ -```python exec -import reflex as rx -from typing import Any -``` - -# Complex Example - -In this more complex example we will be wrapping `reactflow` a library for building node based applications like flow charts, diagrams, graphs, etc. - -## Import - -Lets start by importing the library [reactflow](https://www.npmjs.com/package/reactflow). Lets make a separate file called `reactflow.py` and add the following code: - -```python -import reflex as rx -from typing import Any, Dict, List, Union - -class ReactFlowLib(rx.Component): - """A component that wraps a react flow lib.""" - - library = "reactflow" - - def _get_custom_code(self) -> str: - return """import 'reactflow/dist/style.css'; - """ -``` - -Notice we also use the `_get_custom_code` method to import the css file that is needed for the styling of the library. - -## Components - -For this tutorial we will wrap three components from Reactflow: `ReactFlow`, `Background`, and `Controls`. Lets start with the `ReactFlow` component. - -Here we will define the `tag` and the `vars` that we will need to use the component. - -For this tutorial we will define `EventHandler` props `on_nodes_change` and `on_connect`, but you can find all the events that the component triggers in the [reactflow docs](https://reactflow.dev/docs/api/react-flow-props/#onnodeschange). - -```python -import reflex as rx -from typing import Any, Dict, List, Union - -class ReactFlowLib(rx.Component): - ... - -class ReactFlow(ReactFlowLib): - - tag = "ReactFlow" - - nodes: rx.Var[List[Dict[str, Any]]] - - edges: rx.Var[List[Dict[str, Any]]] - - fit_view: rx.Var[bool] - - nodes_draggable: rx.Var[bool] - - nodes_connectable: rx.Var[bool] - - nodes_focusable: rx.Var[bool] - - on_nodes_change: rx.EventHandler[lambda e0: [e0]] - - on_connect: rx.EventHandler[lambda e0: [e0]] -``` - -Now lets add the `Background` and `Controls` components. We will also create the components using the `create` method so that we can use them in our app. - -```python -import reflex as rx -from typing import Any, Dict, List, Union - -class ReactFlowLib(rx.Component): - ... - -class ReactFlow(ReactFlowLib): - ... - -class Background(ReactFlowLib): - - tag = "Background" - - color: rx.Var[str] - - gap: rx.Var[int] - - size: rx.Var[int] - - variant: rx.Var[str] - -class Controls(ReactFlowLib): - - tag = "Controls" - -react_flow = ReactFlow.create -background = Background.create -controls = Controls.create -``` - -## Building the App - -Now that we have our components lets build the app. - -Lets start by defining the initial nodes and edges that we will use in our app. - -```python -import reflex as rx -from .react_flow import react_flow, background, controls -import random -from collections import defaultdict -from typing import Any, Dict, List - - -initial_nodes = [ - \{ - 'id': '1', - 'type': 'input', - 'data': \{'label': '150'}, - 'position': \{'x': 250, 'y': 25}, - }, - \{ - 'id': '2', - 'data': \{'label': '25'}, - 'position': \{'x': 100, 'y': 125}, - }, - \{ - 'id': '3', - 'type': 'output', - 'data': \{'label': '5'}, - 'position': \{'x': 250, 'y': 250}, - }, -] - -initial_edges = [ - \{'id': 'e1-2', 'source': '1', 'target': '2', 'label': '*', 'animated': True}, - \{'id': 'e2-3', 'source': '2', 'target': '3', 'label': '+', 'animated': True}, -] -``` - -Next we will define the state of our app. We have four event handlers: `add_random_node`, `clear_graph`, `on_connect` and `on_nodes_change`. - -The `on_nodes_change` event handler is triggered when a node is selected and dragged. This function is used to update the position of a node during dragging. It takes a single argument `node_changes`, which is a list of dictionaries containing various types of metadata. For updating positions, the function specifically processes changes of type `position`. - -```python -class State(rx.State): - """The app state.""" - nodes: List[Dict[str, Any]] = initial_nodes - edges: List[Dict[str, Any]] = initial_edges - - @rx.event - def add_random_node(self): - new_node_id = f'\{len(self.nodes) + 1\}' - node_type = random.choice(['default']) - # Label is random number - label = new_node_id - x = random.randint(0, 500) - y = random.randint(0, 500) - - new_node = { - 'id': new_node_id, - 'type': node_type, - 'data': \{'label': label}, - 'position': \{'x': x, 'y': y}, - 'draggable': True, - } - self.nodes.append(new_node) - - @rx.event - def clear_graph(self): - self.nodes = [] # Clear the nodes list - self.edges = [] # Clear the edges list - - @rx.event - def on_connect(self, new_edge): - # Iterate over the existing edges - for i, edge in enumerate(self.edges): - # If we find an edge with the same ID as the new edge - if edge["id"] == f"e\{new_edge['source']}-\{new_edge['target']}": - # Delete the existing edge - del self.edges[i] - break - - # Add the new edge - self.edges.append({ - "id": f"e\{new_edge['source']}-\{new_edge['target']}", - "source": new_edge["source"], - "target": new_edge["target"], - "label": random.choice(["+", "-", "*", "/"]), - "animated": True, - }) - - @rx.event - def on_nodes_change(self, node_changes: List[Dict[str, Any]]): - # Receives a list of Nodes in case of events like dragging - map_id_to_new_position = defaultdict(dict) - - # Loop over the changes and store the new position - for change in node_changes: - if change["type"] == "position" and change.get("dragging") == True: - map_id_to_new_position[change["id"]] = change["position"] - - # Loop over the nodes and update the position - for i, node in enumerate(self.nodes): - if node["id"] in map_id_to_new_position: - new_position = map_id_to_new_position[node["id"]] - self.nodes[i]["position"] = new_position -``` - -Now lets define the UI of our app. We will use the `react_flow` component and pass in the `nodes` and `edges` from our state. We will also add the `on_connect` event handler to the `react_flow` component to handle when an edge is connected. - -```python -def index() -> rx.Component: - return rx.vstack( - react_flow( - background(), - controls(), - nodes_draggable=True, - nodes_connectable=True, - on_connect=lambda e0: State.on_connect(e0), - on_nodes_change=lambda e0: State.on_nodes_change(e0), - nodes=State.nodes, - edges=State.edges, - fit_view=True, - ), - rx.hstack( - rx.button("Clear graph", on_click=State.clear_graph, width="100%"), - rx.button("Add node", on_click=State.add_random_node, width="100%"), - width="100%", - ), - height="30em", - width="100%", - ) - - -# Add state and page to the app. -app = rx.App() -app.add_page(index) -``` - -```python exec -import reflex as rx -from typing import Any, Dict, List, Union -from collections import defaultdict -import random - -class ReactFlowLib(rx.Component): - """A component that wraps a react flow lib.""" - - library = "reactflow" - - def _get_custom_code(self) -> str: - return """import 'reactflow/dist/style.css'; - """ - -class ReactFlow(ReactFlowLib): - - tag = "ReactFlow" - - nodes: rx.Var[List[Dict[str, Any]]] - - edges: rx.Var[List[Dict[str, Any]]] - - fit_view: rx.Var[bool] - - nodes_draggable: rx.Var[bool] - - nodes_connectable: rx.Var[bool] - - nodes_focusable: rx.Var[bool] - - on_nodes_change: rx.EventHandler[lambda e0: [e0]] - - on_connect: rx.EventHandler[lambda e0: [e0]] - - -class Background(ReactFlowLib): - - tag = "Background" - - color: rx.Var[str] - - gap: rx.Var[int] - - size: rx.Var[int] - - variant: rx.Var[str] - -class Controls(ReactFlowLib): - - tag = "Controls" - -react_flow = ReactFlow.create -background = Background.create -controls = Controls.create - -initial_nodes = [ - { - 'id': '1', - 'type': 'input', - 'data': {'label': '150'}, - 'position': {'x': 250, 'y': 25}, - }, - { - 'id': '2', - 'data': {'label': '25'}, - 'position': {'x': 100, 'y': 125}, - }, - { - 'id': '3', - 'type': 'output', - 'data': {'label': '5'}, - 'position': {'x': 250, 'y': 250}, - }, -] - -initial_edges = [ - {'id': 'e1-2', 'source': '1', 'target': '2', 'label': '*', 'animated': True}, - {'id': 'e2-3', 'source': '2', 'target': '3', 'label': '+', 'animated': True}, -] - - -class ReactFlowState(rx.State): - """The app state.""" - nodes: List[Dict[str, Any]] = initial_nodes - edges: List[Dict[str, Any]] = initial_edges - - @rx.event - def add_random_node(self): - new_node_id = f'{len(self.nodes) + 1}' - node_type = random.choice(['default']) - # Label is random number - label = new_node_id - x = random.randint(0, 250) - y = random.randint(0, 250) - - new_node = { - 'id': new_node_id, - 'type': node_type, - 'data': {'label': label}, - 'position': {'x': x, 'y': y}, - 'draggable': True, - } - self.nodes.append(new_node) - - @rx.event - def clear_graph(self): - self.nodes = [] # Clear the nodes list - self.edges = [] # Clear the edges list - - @rx.event - def on_connect(self, new_edge): - # Iterate over the existing edges - for i, edge in enumerate(self.edges): - # If we find an edge with the same ID as the new edge - if edge["id"] == f"e{new_edge['source']}-{new_edge['target']}": - # Delete the existing edge - del self.edges[i] - break - - # Add the new edge - self.edges.append({ - "id": f"e{new_edge['source']}-{new_edge['target']}", - "source": new_edge["source"], - "target": new_edge["target"], - "label": random.choice(["+", "-", "*", "/"]), - "animated": True, - }) - - @rx.event - def on_nodes_change(self, node_changes: List[Dict[str, Any]]): - # Receives a list of Nodes in case of events like dragging - map_id_to_new_position = defaultdict(dict) - - # Loop over the changes and store the new position - for change in node_changes: - if change["type"] == "position" and change.get("dragging") == True: - map_id_to_new_position[change["id"]] = change["position"] - - # Loop over the nodes and update the position - for i, node in enumerate(self.nodes): - if node["id"] in map_id_to_new_position: - new_position = map_id_to_new_position[node["id"]] - self.nodes[i]["position"] = new_position -``` - -Here is an example of the app running: - -```python eval -rx.vstack( - react_flow( - background(), - controls(), - nodes_draggable=True, - nodes_connectable=True, - on_connect=lambda e0: ReactFlowState.on_connect(e0), - on_nodes_change=lambda e0: ReactFlowState.on_nodes_change(e0), - nodes=ReactFlowState.nodes, - edges=ReactFlowState.edges, - fit_view=True, - ), - rx.hstack( - rx.button("Clear graph", on_click=ReactFlowState.clear_graph, width="50%"), - rx.button("Add node", on_click=ReactFlowState.add_random_node, width="50%"), - width="100%", - ), - height="30em", - width="100%", - ) -``` diff --git a/docs/wrapping-react/imports-and-styles.md b/docs/wrapping-react/imports-and-styles.md deleted file mode 100644 index 56b3b16c02..0000000000 --- a/docs/wrapping-react/imports-and-styles.md +++ /dev/null @@ -1,51 +0,0 @@ - -# Styles and Imports - -When wrapping a React component, you may need to define styles and imports that are specific to the component. This is done by defining the `add_styles` and `add_imports` methods in your component class. - -### Imports - -Sometimes, the component you are wrapping will need to import other components or libraries. This is done by defining the `add_imports` method in your component class. -That method should return a dictionary of imports, where the keys are the names of the packages to import and the values are the names of the components or libraries to import. - -Values can be either a string or a list of strings. If the import needs to be aliased, you can use the `ImportVar` object to specify the alias and whether the import should be installed as a dependency. - -```python -from reflex.utils.imports import ImportVar - -class ComponentWithImports(MyBaseComponent): - def add_imports(self): - """Add imports to the component.""" - return { - # If you only have one import, you can use a string. - "my-package1": "my-import1", - # If you have multiple imports, you can pass a list. - "my-package2": ["my-import2"], - # If you need to control the import in a more detailed way, you can use an ImportVar object. - "my-package3": ImportVar(tag="my-import3", alias="my-alias", install=False, is_default=False), - # To import a CSS file, pass the full path to the file, and use an empty string as the key. - "": "my-package-with-css/styles.css", - } -``` - -```md alert info -# The tag and library of the component will be automatically added to the imports. They do not need to be added again in `add_imports`. -``` - -### Styles - -Styles are any CSS styles that need to be included in the component. The style will be added inline to the component, so you can use any CSS styles that are valid in React. - -```python -class StyledComponent(MyBaseComponent): - """MyComponent.""" - - def add_style(self) -> dict[str, Any] | None: - """Add styles to the component.""" - - return rx.Style({ - "backgroundColor": "red", - "color": "white", - "padding": "10px", - }) -``` diff --git a/docs/wrapping-react/library-and-tags.md b/docs/wrapping-react/library-and-tags.md deleted file mode 100644 index 6b13694bb5..0000000000 --- a/docs/wrapping-react/library-and-tags.md +++ /dev/null @@ -1,166 +0,0 @@ ---- -title: Library and Tags ---- - -```python exec -from pcweb.pages.docs import api_reference -``` - -# Find The Component - -There are two ways to find a component to wrap: -1. Write the component yourself locally. -2. Find a well-maintained React library on [npm](https://www.npmjs.com/) that contains the component you need. - -In both cases, the process of wrapping the component is the same except for the `library` field. - -# Wrapping the Component - -To start wrapping your React component, the first step is to create a new component in your Reflex app. This is done by creating a new class that inherits from `rx.Component` or `rx.NoSSRComponent`. - -See the [API Reference]({api_reference.component.path}) for more details on the `rx.Component` class. - -This is when we will define the most important attributes of the component: -1. **library**: The name of the npm package that contains the component. -2. **tag**: The name of the component to import from the package. -3. **alias**: (Optional) The name of the alias to use for the component. This is useful if multiple component from different package have a name in common. If `alias` is not specified, `tag` will be used. -4. **lib_dependencies**: Any additional libraries needed to use the component. -5. **is_default**: (Optional) If the component is a default export from the module, set this to `True`. Default is `False`. - -Optionally, you can override the default component creation behavior by implementing the `create` class method. Most components won't need this when props are straightforward conversions from Python to JavaScript. However, this is useful when you need to add custom initialization logic, transform props, or handle special cases when the component is created. - -```md alert warning -# When setting the `library` attribute, it is recommended to included a pinned version of the package. Doing so, the package will only change when you intentionally update the version, avoid unexpected breaking changes. -``` - -```python -class MyBaseComponent(rx.Component): - """MyBaseComponent.""" - - # The name of the npm package. - library = "my-library@x.y.z" - - # The name of the component to use from the package. - tag = "MyComponent" - - # Any additional libraries needed to use the component. - lib_dependencies: list[str] = ["package-deps@x.y.z"] - - # The name of the alias to use for the component. - alias = "MyComponentAlias" - - # If the component is a default export from the module, set this to True. - is_default = True/False - - @classmethod - def create(cls, *children, **props): - """Create an instance of MyBaseComponent. - - Args: - *children: The children of the component. - **props: The props of the component. - - Returns: - The component instance. - """ - # Your custom creation logic here - return super().create(*children, **props) - -``` - -# Wrapping a Dynamic Component - -When wrapping some libraries, you may want to use dynamic imports. This is because they may not be compatible with Server-Side Rendering (SSR). - -To handle this in Reflex, subclass `NoSSRComponent` when defining your component. It works the same as `rx.Component`, but it will automatically add the correct custom code for a dynamic import. - -Often times when you see an import something like this: - -```javascript -import dynamic from 'next/dynamic'; - -const MyLibraryComponent = dynamic(() => import('./MyLibraryComponent'), { - ssr: false -}); -``` - -You can wrap it in Reflex like this: - -```python -from reflex.components.component import NoSSRComponent - -class MyLibraryComponent(NoSSRComponent): - """A component that wraps a lib needing dynamic import.""" - - library = "my-library@x.y.z" - - tag="MyLibraryComponent" -``` - -It may not always be clear when a library requires dynamic imports. A few things to keep in mind are if the component is very client side heavy i.e. the view and structure depends on things that are fetched at run time, or if it uses `window` or `document` objects directly it will need to be wrapped as a `NoSSRComponent`. - -Some examples are: - -1. Video and Audio Players -2. Maps -3. Drawing Canvas -4. 3D Graphics -5. QR Scanners -6. Reactflow - -The reason for this is that it does not make sense for your server to render these components as the server does not have access to your camera, it cannot draw on your canvas or render a video from a file. - -In addition, if in the component documentation it mentions nextJS compatibility or server side rendering compatibility, it is a good sign that it requires dynamic imports. - -# Advanced - Parsing a state Var with a JS Function -When wrapping a component, you may need to parse a state var by applying a JS function to it. - -## Define the parsing function - -First you need to define the parsing function by writing it in `add_custom_code`. - -```python - -def add_custom_code(self) -> list[str]: - """Add custom code to the component.""" - # Define the parsing function - return [ - """ - function myParsingFunction(inputProp) { - // Your parsing logic here - return parsedProp; - }""" - ] -``` - -## Apply the parsing function to your props - -Then, you can apply the parsing function to your props in the `create` method. - -```python -from reflex.vars.base import Var -from reflex.vars.function import FunctionStringVar - - ... - @classmethod - def create(cls, *children, **props): - """Create an instance of MyBaseComponent. - - Args: - *children: The children of the component. - **props: The props of the component. - - Returns: - The component instance. - """ - # Apply the parsing function to the props - if (prop_to_parse := props.get("propsToParse")) is not None: - if isinstance(prop_to_parse, Var): - props["propsToParse"] = FunctionStringVar.create("myParsingFunction").call(prop_to_parse) - else: - # This is not a state Var, so you can parse the value directly in python - parsed_prop = python_parsing_function(prop_to_parse) - props["propsToParse"] = parsed_prop - return super().create(*children, **props) - ... -``` \ No newline at end of file diff --git a/docs/wrapping-react/local-packages.md b/docs/wrapping-react/local-packages.md deleted file mode 100644 index a0ae718cb0..0000000000 --- a/docs/wrapping-react/local-packages.md +++ /dev/null @@ -1,171 +0,0 @@ ---- -title: Wrapping Local Packages ---- - -```python exec -import reflex as rx -``` - -# Assets - -If a wrapped component depends on assets such as images, scripts, or -stylesheets, these can be kept adjacent to the component code and -included in the final build using the `rx.asset` function. - -`rx.asset` returns a relative path that references the asset in the compiled -output. The target files are copied into a subdirectory of `assets/external` -based on the module where they are initially used. This allows third-party -components to have external assets with the same name without conflicting -with each other. - -For example, if there is an SVG file named `wave.svg` in the same directory as -this component, it can be rendered using `rx.image` and `rx.asset`. - -```python -class Hello(rx.Component): - @classmethod - def create(cls, *children, **props) -> rx.Component: - props.setdefault("align", "center") - return rx.hstack( - rx.image(src=rx.asset("wave.svg", shared=True), width="50px", height="50px"), - rx.heading("Hello ", *children), - **props - ) -``` - - -# Local Components - -You can also wrap components that you have written yourself. For local components (when the code source is directly in the project), we recommend putting it beside the files that is wrapping it. - -If there is a file `hello.jsx` in the same directory as the component with this content: - -```javascript -// /path/to/components/hello.jsx -import React from 'react'; - -export function Hello({name, onGreet}) { - return ( -
-

Hello, {name}!

- -
- ) -} -``` - -The python app can use the `rx.asset` helper to copy the component source into -the generated frontend, after which the `library` path in the `rx.Component` may -be specified by prefixing `$/public` to that path returned by `rx.asset`. - -```python -import reflex as rx - -hello_path = rx.asset("./hello.jsx", shared=True) -hello_css_path = rx.asset("./hello.css", shared=True) - -class Hello(rx.Component): - # Use an absolute path starting with $/public - library = f"$/public{hello_path}" - - # Define everything else as normal. - tag = "Hello" - - name: rx.Var[str] = rx.Var.create("World") - on_greet: rx.EventHandler[rx.event.passthrough_event_spec(str)] - - # Include any related CSS files with rx.asset to ensure they are copied. - def add_imports(self): - return {"": f"$/public/{hello_css_path}"} -``` - -## Considerations - -When wrapping local components, keep the following in mind: - -1. **File Extensions**: Ensure that the file extensions are correct (e.g., `.jsx` for React components and `.tsx` for TypeScript components). -2. **Asset Management**: Use `rx.asset` with `shared=True` to manage any assets (e.g., images, styles) that the component depends on. -3. **Event Handling**: Define any event handlers (e.g., `on_greet`) as part of the component's API and pass those to the component _from the Reflex app_. Do not attempt to hook into Reflex's event system directly from Javascript. - -## Use Case - -Local components are useful when shimming small pieces of functionality that are -simpler or more performant when implemented directly in Javascript, such as: - -* Spammy events: keys, touch, mouse, scroll -- these are often better processed on the client side. -* Using canvas, graphics or WebGPU -* Working with other Web APIs like storage, screen capture, audio/midi -* Integrating with complex third-party libraries - * For application-specific use, it may be easier to wrap a local component that - provides the needed subset of the library's functionality in a simpler API for use in Reflex. - -# Local Packages - -If the component is part of a local package, available on Github, or -downloadable via a web URL, it can also be wrapped in Reflex. Specify the path -or URL after an `@` following the package name. - -Any local paths are relative to the `.web` folder, so you can use `../` prefix -to reference the Reflex project root. - -Some examples of valid specifiers for a package called -[`@masenf/hello-react`](https://github.com/masenf/hello-react) are: - -* GitHub: `@masenf/hello-react@github:masenf/hello-react` -* URL: `@masenf/hello-react@https://github.com/masenf/hello-react/archive/refs/heads/main.tar.gz` -* Local Archive: `@masenf/hello-react@../hello-react.tgz` -* Local Directory: `@masenf/hello-react@../hello-react` - -It is important that the package name matches the name in `package.json` so -Reflex can generate the correct import statement in the generated javascript -code. - -These package specifiers can be used for `library` or `lib_dependencies`. - -```python demo exec toggle -class GithubComponent(rx.Component): - library = "@masenf/hello-react@github:masenf/hello-react" - tag = "Counter" - - def add_imports(self): - return { - "": ["@masenf/hello-react/dist/style.css"] - } - -def github_component_example(): - return GithubComponent.create() -``` - -Although more complicated, this approach is useful when the local components -have additional dependencies or build steps required to prepare the component -for use. - -Some important notes regarding this approach: - -* The repo or archive must contain a `package.json` file. -* `prepare` or `build` scripts will NOT be executed. The distribution archive, - directory, or repo must already contain the built javascript files (this is common). - -```md alert -# Ensure CSS files are exported in `package.json` - -In addition to exporting the module containing the component, any CSS files -intended to be imported by the wrapped component must also be listed in the -`exports` key of `package.json`. - -```json -{ - // ..., - "exports": { - ".": { - "import": "./dist/index.js", - "require": "./dist/index.umd.cjs" - }, - "./dist/style.css": { - "import": "./dist/style.css", - "require": "./dist/style.css" - } - }, - // ... -} -``` diff --git a/docs/wrapping-react/more-wrapping-examples.md b/docs/wrapping-react/more-wrapping-examples.md deleted file mode 100644 index 3a5638b66f..0000000000 --- a/docs/wrapping-react/more-wrapping-examples.md +++ /dev/null @@ -1,449 +0,0 @@ -# More React Libraries - - -## AG Charts - -Here we wrap the AG Charts library from the NPM package [ag-charts-react](https://www.npmjs.com/package/ag-charts-react). - -In the react code below we can see the first `2` lines are importing React and ReactDOM, and this can be ignored when wrapping your component. - -We import the `AgCharts` component from the `ag-charts-react` library on line 5. In Reflex this is wrapped by `library = "ag-charts-react"` and `tag = "AgCharts"`. - -Line `7` defines a functional React component, which on line `26` returns `AgCharts` which is similar in the Reflex code to using the `chart` component. - -Line `9` uses the `useState` hook to create a state variable `chartOptions` and its setter function `setChartOptions` (equivalent to the event handler `set_chart_options` in reflex). The initial state variable is of type dict and has two key value pairs `data` and `series`. - -When we see `useState` in React code, it correlates to state variables in your State. As you can see in our Reflex code we have a state variable `chart_options` which is a dictionary, like in our React code. - -Moving to line `26` we see that the `AgCharts` has a prop `options`. In order to use this in Reflex we must wrap this prop. We do this with `options: rx.Var[dict]` in the `AgCharts` component. - -Lines `31` and `32` are rendering the component inside the root element. This can be ignored when we are wrapping a component as it is done in Reflex by creating an `index` function and adding it to the app. - - ----md tabs - ---tab React Code - -```javascript -1 | import React, \{ useState } from 'react'; -2 | import ReactDOM from 'react-dom/client'; -3 | -4 | // React Chart Component -5 | import \{ AgCharts } from 'ag-charts-react'; -6 | -7 | const ChartExample = () => { -8 | // Chart Options: Control & configure the chart -9 | const [chartOptions, setChartOptions] = useState({ -10| // Data: Data to be displayed in the chart -11| data: [ -12| \{ month: 'Jan', avgTemp: 2.3, iceCreamSales: 162000 }, -13| \{ month: 'Mar', avgTemp: 6.3, iceCreamSales: 302000 }, -14| \{ month: 'May', avgTemp: 16.2, iceCreamSales: 800000 }, -15| \{ month: 'Jul', avgTemp: 22.8, iceCreamSales: 1254000 }, -16| \{ month: 'Sep', avgTemp: 14.5, iceCreamSales: 950000 }, -17| \{ month: 'Nov', avgTemp: 8.9, iceCreamSales: 200000 }, -18| ], -19| // Series: Defines which chart type and data to use -20| series: [\{ type: 'bar', xKey: 'month', yKey: 'iceCreamSales' }], -21| }); -22| -23| // React Chart Component -24| return ( -25| // AgCharts component with options passed as prop -26| -27| ); -28| } -29| -30| // Render component inside root element -31| const root = ReactDOM.createRoot(document.getElementById('root')); -32| root.render(); -``` - --- ---tab Reflex Code - -```python -import reflex as rx - -class AgCharts(rx.Component): - """ A simple line chart component using AG Charts """ - - library = "ag-charts-react" - - tag = "AgCharts" - - options: rx.Var[dict] - - -chart = AgCharts.create - - -class State(rx.State): - """The app state.""" - chart_options: dict = { - "data": [ - \{"month":"Jan", "avgTemp":2.3, "iceCreamSales":162000}, - \{"month":"Mar", "avgTemp":6.3, "iceCreamSales":302000}, - \{"month":"May", "avgTemp":16.2, "iceCreamSales":800000}, - \{"month":"Jul", "avgTemp":22.8, "iceCreamSales":1254000}, - \{"month":"Sep", "avgTemp":14.5, "iceCreamSales":950000}, - \{"month":"Nov", "avgTemp":8.9, "iceCreamSales":200000} - ], - "series": [\{"type":"bar", "xKey":"month", "yKey":"iceCreamSales"}] - } - -def index() -> rx.Component: - return chart( - options=State.chart_options, - ) - -app = rx.App() -app.add_page(index) -``` --- - ---- - - -## React Leaflet - -```python exec -from pcweb.pages import docs -``` - -In this example we are wrapping the React Leaflet library from the NPM package [react-leaflet](https://www.npmjs.com/package/react-leaflet). - -On line `1` we import the `dynamic` function from Next.js and on line `21` we set `ssr: false`. Lines `4` and `6` use the `dynamic` function to import the `MapContainer` and `TileLayer` components from the `react-leaflet` library. This is used to dynamically import the `MapContainer` and `TileLayer` components from the `react-leaflet` library. This is done in Reflex by using the `NoSSRComponent` class when defining the component. There is more information of when this is needed on the `Dynamic Imports` section of this [page]({docs.wrapping_react.library_and_tags.path}). - -It mentions in the documentation that it is necessary to include the Leaflet CSS file, which is added on line `2` in the React code below. This can be done in Reflex by using the `add_imports` method in the `MapContainer` component. We can add a relative path from within the React library or a full URL to the CSS file. - -Line `4` defines a functional React component, which on line `8` returns the `MapContainer` which is done in the Reflex code using the `map_container` component. - -The `MapContainer` component has props `center`, `zoom`, `scrollWheelZoom`, which we wrap in the `MapContainer` component in the Reflex code. We ignore the `style` prop as it is a reserved name in Reflex. We can use the `rename_props` method to change the name of the prop, as we will see in the React PDF Renderer example, but in this case we just ignore it and add the `width` and `height` props as css in Reflex. - -The `TileLayer` component has a prop `url` which we wrap in the `TileLayer` component in the Reflex code. - -Lines `24` and `25` defines and exports a React functional component named `Home` which returns the `MapComponent` component. This can be ignored in the Reflex code when wrapping the component as we return the `map_container` component in the `index` function. - ----md tabs - ---tab React Code - -```javascript -1 | import dynamic from "next/dynamic"; -2 | import "leaflet/dist/leaflet.css"; -3 | -4 | const MapComponent = dynamic( -5 | () => { -6 | return import("react-leaflet").then((\{ MapContainer, TileLayer }) => { -7 | return () => ( -8 | -14| -17| -18| ); -19| }); -20| }, -21| \{ ssr: false } -22| ); -23| -24| export default function Home() { -25| return ; -26| } -``` - --- ---tab Reflex Code - -```python -import reflex as rx - -class MapContainer(rx.NoSSRComponent): - - library = "react-leaflet" - - tag = "MapContainer" - - center: rx.Var[list] - - zoom: rx.Var[int] - - scroll_wheel_zoom: rx.Var[bool] - - # Can also pass a url like: https://unpkg.com/leaflet/dist/leaflet.css - def add_imports(self): - return \{"": ["leaflet/dist/leaflet.css"]} - - - -class TileLayer(rx.NoSSRComponent): - - library = "react-leaflet" - - tag = "TileLayer" - - url: rx.Var[str] - - -map_container = MapContainer.create -tile_layer = TileLayer.create - -def index() -> rx.Component: - return map_container( - tile_layer(url="https://\{s}.tile.openstreetmap.org/\{z}/\{x}/\{y}.png"), - center=[51.505, -0.09], - zoom=13, - #scroll_wheel_zoom=True - width="100%", - height="50vh", - ) - - -app = rx.App() -app.add_page(index) - -``` --- - ---- - - -## React PDF Renderer - -In this example we are wrapping the React renderer for creating PDF files on the browser and server from the NPM package [@react-pdf/renderer](https://www.npmjs.com/package/@react-pdf/renderer). - -This example is similar to the previous examples, and again Dynamic Imports are required for this library. This is done in Reflex by using the `NoSSRComponent` class when defining the component. There is more information on why this is needed on the `Dynamic Imports` section of this [page]({docs.wrapping_react.library_and_tags.path}). - -The main difference with this example is that the `style` prop, used on lines `20`, `21` and `24` in React code, is a reserved name in Reflex so can not be wrapped. A different name must be used when wrapping this prop and then this name must be changed back to the original with the `rename_props` method. In this example we name the prop `theme` in our Reflex code and then change it back to `style` with the `rename_props` method in both the `Page` and `View` components. - - -```md alert info -# List of reserved names in Reflex - -_The style of the component._ - -`style: Style = Style()` - -_A mapping from event triggers to event chains._ - -`event_triggers: Dict[str, Union[EventChain, Var]] = \{}` - -_The alias for the tag._ - -`alias: Optional[str] = None` - -_Whether the import is default or named._ - -`is_default: Optional[bool] = False` - -_A unique key for the component._ - -`key: Any = None` - -_The id for the component._ - -`id: Any = None` - -_The class name for the component._ - -`class_name: Any = None` - -_Special component props._ - -`special_props: List[Var] = []` - -_Whether the component should take the focus once the page is loaded_ - -`autofocus: bool = False` - -_components that cannot be children_ - -`_invalid_children: List[str] = []` - -_only components that are allowed as children_ - -`_valid_children: List[str] = []` - -_only components that are allowed as parent_ - -`_valid_parents: List[str] = []` - -_props to change the name of_ - -`_rename_props: Dict[str, str] = \{}` - -_custom attribute_ - -`custom_attrs: Dict[str, Union[Var, str]] = \{}` - -_When to memoize this component and its children._ - -`_memoization_mode: MemoizationMode = MemoizationMode()` - -_State class associated with this component instance_ - -`State: Optional[Type[reflex.state.State]] = None` -``` - ----md tabs - ---tab React Code - -```javascript -1 | import ReactDOM from 'react-dom'; -2 | import \{ Document, Page, Text, View, StyleSheet, PDFViewer } from '@react-pdf/renderer'; -3 | -4 | // Create styles -5 | const styles = StyleSheet.create({ -6 | page: { -7 | flexDirection: 'row', -8 | backgroundColor: '#E4E4E4', -9 | }, -10| section: { -11| margin: 10, -12| padding: 10, -13| flexGrow: 1, -14| }, -15| }); -16| -17| // Create Document Component -18| const MyDocument = () => ( -19| -20| -21| -22| Section #1 -23| -24| -25| Section #2 -26| -27| -28| -29| ); -30| -31| const App = () => ( -32| -33| -34| -35| ); -36| -37| ReactDOM.render(, document.getElementById('root')); -``` - --- ---tab Reflex Code - -```python -import reflex as rx - -class Document(rx.Component): - - library = "@react-pdf/renderer" - - tag = "Document" - - -class Page(rx.Component): - - library = "@react-pdf/renderer" - - tag = "Page" - - size: rx.Var[str] - # here we are wrapping style prop but as style is a reserved name in Reflex we must name it something else and then change this name with rename props method - theme: rx.Var[dict] - - _rename_props: dict[str, str] = { - "theme": "style", - } - - -class Text(rx.Component): - - library = "@react-pdf/renderer" - - tag = "Text" - - -class View(rx.Component): - - library = "@react-pdf/renderer" - - tag = "View" - - # here we are wrapping style prop but as style is a reserved name in Reflex we must name it something else and then change this name with rename props method - theme: rx.Var[dict] - - _rename_props: dict[str, str] = { - "theme": "style", - } - - -class StyleSheet(rx.Component): - - library = "@react-pdf/renderer" - - tag = "StyleSheet" - - page: rx.Var[dict] - - section: rx.Var[dict] - - -class PDFViewer(rx.NoSSRComponent): - - library = "@react-pdf/renderer" - - tag = "PDFViewer" - - -document = Document.create -page = Page.create -text = Text.create -view = View.create -style_sheet = StyleSheet.create -pdf_viewer = PDFViewer.create - - -styles = style_sheet({ - "page": { - "flexDirection": 'row', - "backgroundColor": '#E4E4E4', - }, - "section": { - "margin": 10, - "padding": 10, - "flexGrow": 1, - }, -}) - - -def index() -> rx.Component: - return pdf_viewer( - document( - page( - view( - text("Hello, World!"), - theme=styles.section, - ), - view( - text("Hello, 2!"), - theme=styles.section, - ), - size="A4", theme=styles.page), - ), - width="100%", - height="80vh", - ) - -app = rx.App() -app.add_page(index) -``` --- - ---- \ No newline at end of file diff --git a/docs/wrapping-react/overview.md b/docs/wrapping-react/overview.md deleted file mode 100644 index 63a8478397..0000000000 --- a/docs/wrapping-react/overview.md +++ /dev/null @@ -1,154 +0,0 @@ -```python exec -import reflex as rx -from typing import Any -from pcweb.components.spline import spline -from pcweb.pages.docs import custom_components -from pcweb import constants -``` - -# Wrapping React - -One of Reflex's most powerful features is the ability to wrap React components and take advantage of the vast ecosystem of React libraries. - -If you want a specific component for your app but Reflex doesn't provide it, there's a good chance it's available as a React component. Search for it on [npm]({constants.NPMJS_URL}), and if it's there, you can use it in your Reflex app. You can also create your own local React components and wrap them in Reflex. - -Once you wrap your component, you [publish it]({custom_components.overview.path}) to the Reflex library so that others can use it. - -## Simple Example - -Simple components that don't have any interaction can be wrapped with just a few lines of code. - -Below we show how to wrap the [Spline]({constants.SPLINE_URL}) library can be used to create 3D scenes and animations. - -```python demo exec -import reflex as rx - -class Spline(rx.Component): - """Spline component.""" - - # The name of the npm package. - library = "@splinetool/react-spline" - - # Any additional libraries needed to use the component. - lib_dependencies: list[str] = ["@splinetool/runtime@1.5.5"] - - # The name of the component to use from the package. - tag = "Spline" - - # Spline is a default export from the module. - is_default = True - - # Any props that the component takes. - scene: rx.Var[str] - -# Convenience function to create the Spline component. -spline = Spline.create - -# Use the Spline component in your app. -def index(): - return spline(scene="https://prod.spline.design/joLpOOYbGL-10EJ4/scene.splinecode") -``` - - -## ColorPicker Example - -Similar to the Spline example we start with defining the library and tag. In this case the library is `react-colorful` and the tag is `HexColorPicker`. - -We also have a var `color` which is the current color of the color picker. - -Since this component has interaction we must specify any event triggers that the component takes. The color picker has a single trigger `on_change` to specify when the color changes. This trigger takes in a single argument `color` which is the new color. - -```python exec -from reflex.components.component import NoSSRComponent - -class ColorPicker(NoSSRComponent): - library = "react-colorful" - tag = "HexColorPicker" - color: rx.Var[str] - on_change: rx.EventHandler[lambda color: [color]] - -color_picker = ColorPicker.create - -ColorPickerState = rx._x.client_state(default="#db114b", var_name="color") -``` - -```python eval -rx.box( - ColorPickerState, - rx.vstack( - rx.heading(ColorPickerState.value, color="white"), - color_picker( - on_change=ColorPickerState.set_value - ), - ), - background_color=ColorPickerState.value, - padding="5em", - border_radius="12px", - margin_bottom="1em", -) -``` - -```python -from reflex.components.component import NoSSRComponent - -class ColorPicker(NoSSRComponent): - library = "react-colorful" - tag = "HexColorPicker" - color: rx.Var[str] - on_change: rx.EventHandler[lambda color: [color]] - -color_picker = ColorPicker.create - -class ColorPickerState(rx.State): - color: str = "#db114b" - -def index(): - return rx.box( - rx.vstack( - rx.heading(ColorPickerState.color, color="white"), - color_picker( - on_change=ColorPickerState.set_color - ), - ), - background_color=ColorPickerState.color, - padding="5em", - border_radius="1em", - ) -``` - -## What Not To Wrap - -There are some libraries on npm that are not do not expose React components and therefore are very hard to wrap with Reflex. - -A library like [spline](https://www.npmjs.com/package/@splinetool/runtime) below is going to be difficult to wrap with Reflex because it does not expose a React component. - -```javascript -import \{ Application } from '@splinetool/runtime'; - -// make sure you have a canvas in the body -const canvas = document.getElementById('canvas3d'); - -// start the application and load the scene -const spline = new Application(canvas); -spline.load('https://prod.spline.design/6Wq1Q7YGyM-iab9i/scene.splinecode'); -``` - -You should look out for JSX, a syntax extension to JavaScript, which has angle brackets `(

Hello, world!

)`. If you see JSX, it's likely that the library is a React component and can be wrapped with Reflex. - -If the library does not expose a react component you need to try and find a JS React wrapper for the library, such as [react-spline](https://www.npmjs.com/package/@splinetool/react-spline). - -```javascript -import Spline from '@splinetool/react-spline'; - -export default function App() { - return ( -
- -
- ); -} -``` - - - -In the next page, we will go step by step through a more complex example of wrapping a React component. diff --git a/docs/wrapping-react/props.md b/docs/wrapping-react/props.md deleted file mode 100644 index afc7442d8a..0000000000 --- a/docs/wrapping-react/props.md +++ /dev/null @@ -1,202 +0,0 @@ ---- -title: Props - Wrapping React ---- - -# Props - -When wrapping a React component, you want to define the props that will be accepted by the component. -This is done by defining the props and annotating them with a `rx.Var`. - -Broadly, there are three kinds of props you can encounter when wrapping a React component: -1. **Simple Props**: These are props that are passed directly to the component. They can be of any type, including strings, numbers, booleans, and even lists or dictionaries. -2. **Callback Props**: These are props that expect to receive a function. That function will usually be called by the component as a callback. (This is different from event handlers.) -3. **Component Props**: These are props that expect to receive a components themselves. They can be used to create more complex components by composing them together. -4. **Event Handlers**: These are props that expect to receive a function that will be called when an event occurs. They are defined as `rx.EventHandler` with a signature function to define the spec of the event. - -## Simple Props - -Simple props are the most common type of props you will encounter when wrapping a React component. They are passed directly to the component and can be of any type (but most commonly strings, numbers, booleans, and structures). - -For custom types, you can use `TypedDict` to define the structure of the custom types. However, if you need the attributes to be automatically converted to camelCase once compiled in JS, you can use `rx.PropsBase` instead of `TypedDict`. - -```python -class CustomReactType(TypedDict): - """Custom React type.""" - - # Define the structure of the custom type to match the Javascript structure. - attribute1: str - attribute2: bool - attribute3: int - - -class CustomReactType2(rx.PropsBase): - """Custom React type.""" - - # Define the structure of the custom type to match the Javascript structure. - attr_foo: str # will be attrFoo in JS - attr_bar: bool # will be attrBar in JS - attr_baz: int # will be attrBaz in JS - -class SimplePropsComponent(MyBaseComponent): - """MyComponent.""" - - # Type the props according the component documentation. - - # props annotated as `string` in javascript - prop1: rx.Var[str] - - # props annotated as `number` in javascript - prop2: rx.Var[int] - - # props annotated as `boolean` in javascript - prop3: rx.Var[bool] - - # props annotated as `string[]` in javascript - prop4: rx.Var[list[str]] - - # props annotated as `CustomReactType` in javascript - props5: rx.Var[CustomReactType] - - # props annotated as `CustomReactType2` in javascript - props6: rx.Var[CustomReactType2] - - # Sometimes a props will accept multiple types. You can use `|` to specify the types. - # props annotated as `string | boolean` in javascript - props7: rx.Var[str | bool] -``` - -## Callback Props - -Callback props are used to handle events or to pass data back to the parent component. They are defined as `rx.Var` with a type of `FunctionVar` or `Callable`. - -```python -from typing import Callable -from reflex.vars.function import FunctionVar - -class CallbackPropsComponent(MyBaseComponent): - """MyComponent.""" - - # A callback prop that takes a single argument. - callback_props: rx.Var[Callable] -``` - -## Component Props -Some components will occasionally accept other components as props, usually annotated as `ReactNode`. In Reflex, these are defined as `rx.Component`. - -```python -class ComponentPropsComponent(MyBaseComponent): - """MyComponent.""" - - # A prop that takes a component as an argument. - component_props: rx.Var[rx.Component] -``` - -## Event Handlers -Event handlers are props that expect to receive a function that will be called when an event occurs. They are defined as `rx.EventHandler` with a signature function to define the spec of the event. - -```python -from reflex.vars.event_handler import EventHandler -from reflex.vars.function import FunctionVar -from reflex.vars.object import ObjectVar - -class InputEventType(TypedDict): - """Input event type.""" - - # Define the structure of the input event. - foo: str - bar: int - -class OutputEventType(TypedDict): - """Output event type.""" - - # Define the structure of the output event. - baz: str - qux: int - - -def custom_spec1(event: ObjectVar[InputEventType]) -> tuple[str, int]: - """Custom event spec using ObjectVar with custom type as input and tuple as output.""" - return ( - event.foo.to(str), - event.bar.to(int), - ) - -def custom_spec2(event: ObjectVar[dict]) -> tuple[Var[OutputEventType]]: - """Custom event spec using ObjectVar with dict as input and custom type as output.""" - return Var.create( - { - "baz": event["foo"], - "qux": event["bar"], - }, - ).to(OutputEventType) - -class EventHandlerComponent(MyBaseComponent): - """MyComponent.""" - - # An event handler that take no argument. - on_event: rx.EventHandler[rx.event.no_args_event_spec] - - # An event handler that takes a single string argument. - on_event_with_arg: rx.EventHandler[rx.event.passthrough_event_spec(str)] - - # An event handler specialized for input events, accessing event.target.value from the event. - on_input_change: rx.EventHandler[rx.event.input_event] - - # An event handler specialized for key events, accessing event.key from the event and provided modifiers (ctrl, alt, shift, meta). - on_key_down: rx.EventHandler[rx.event.key_event] - - # An event handler that takes a custom spec. (Event handler must expect a tuple of two values [str and int]) - on_custom_event: rx.EventHandler[custom_spec1] - - # Another event handler that takes a custom spec. (Event handler must expect a tuple of one value, being a OutputEventType) - on_custom_event2: rx.EventHandler[custom_spec2] -``` - -```md alert info -# Custom event specs have a few use case where they are particularly useful. If the event returns non-serializable data, you can filter them out so the event can be sent to the backend. You can also use them to transform the data before sending it to the backend. -``` - -### Emulating Event Handler Behavior Outside a Component - -In some instances, you may need to replicate the special behavior applied to -event handlers from outside of a component context. For example if the component -to be wrapped requires event callbacks passed in a dictionary, this can be -achieved by directly instantiating an `EventChain`. - -A real-world example of this is the `onEvents` prop of -[`echarts-for-react`](https://www.npmjs.com/package/echarts-for-react) library, -which, unlike a normal event handler, expects a mapping of event handlers like: - -```javascript - -``` - -To achieve this in Reflex, you can create an explicit `EventChain` for each -event handler: - -```python -@classmethod -def create(cls, *children, **props): - on_events = props.pop("on_events", {}) - - event_chains = {} - for event_name, handler in on_events.items(): - # Convert the EventHandler/EventSpec/lambda to an EventChain - event_chains[event_name] = rx.EventChain.create( - handler, - args_spec=rx.event.no_args_event_spec, - key=event_name, - ) - if on_events: - props["on_events"] = event_chains - - # Create the component instance - return super().create(*children, **props) -``` \ No newline at end of file diff --git a/docs/wrapping-react/serializers.md b/docs/wrapping-react/serializers.md deleted file mode 100644 index 4bb1abcffd..0000000000 --- a/docs/wrapping-react/serializers.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: Serializers ---- - -# Serializers - -Vars can be any type that can be serialized to JSON. This includes primitive types like strings, numbers, and booleans, as well as more complex types like lists, dictionaries, and dataframes. - -In case you need to serialize a more complex type, you can use the `serializer` decorator to convert the type to a primitive type that can be stored in the state. Just define a method that takes the complex type as an argument and returns a primitive type. We use type annotations to determine the type that you want to serialize. - -For example, the Plotly component serializes a plotly figure into a JSON string that can be stored in the state. - -```python -import json -import reflex as rx -from plotly.graph_objects import Figure -from plotly.io import to_json - -# Use the serializer decorator to convert the figure to a JSON string. -# Specify the type of the argument as an annotation. -@rx.serializer -def serialize_figure(figure: Figure) -> list: - # Use Plotly's to_json method to convert the figure to a JSON string. - return json.loads(to_json(figure))["data"] -``` - -We can then define a var of this type as a prop in our component. - -```python -import reflex as rx -from plotly.graph_objects import Figure - -class Plotly(rx.Component): - """Display a plotly graph.""" - library = "react-plotly.js@2.6.0" - lib_dependencies: List[str] = ["plotly.js@2.22.0"] - - tag = "Plot" - - is_default = True - - # Since a serialize is defined now, we can use the Figure type directly. - data: rx.Var[Figure] -``` diff --git a/docs/wrapping-react/step-by-step.md b/docs/wrapping-react/step-by-step.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/pcweb/docgen_pipeline.py b/pcweb/docgen_pipeline.py new file mode 100644 index 0000000000..cef3fa3969 --- /dev/null +++ b/pcweb/docgen_pipeline.py @@ -0,0 +1,694 @@ +"""Pipeline for rendering reflex-shipped docs via reflex_docgen.markdown.""" + +import sys +import types +from pathlib import Path + +import reflex as rx +from reflex_core.constants.colors import ColorType +from reflex_docgen.markdown import ( + Block, + CodeBlock, + DirectiveBlock, + Document, + HeadingBlock, + ListBlock, + QuoteBlock, + TableBlock, + TextBlock, + ThematicBreakBlock, + parse_document, +) +from reflex_docgen.markdown._types import ( + BoldSpan, + CodeSpan, + FrontMatter, + ImageSpan, + ItalicSpan, + LineBreakSpan, + LinkSpan, + ListItem, + Span, + StrikethroughSpan, + TableCell, + TableRow, + TextSpan, +) +from reflex_docgen.markdown.transformer import DocumentTransformer + +from pcweb.constants import REFLEX_ASSETS_CDN +from pcweb.templates.docpage.blocks.code import code_block +from pcweb.templates.docpage.blocks.collapsible import collapsible_box +from pcweb.templates.docpage.blocks.demo import docdemo, docdemobox, docgraphing +from pcweb.templates.docpage.blocks.headings import ( + h1_comp_xd, + h2_comp_xd, + h3_comp_xd, + h4_comp_xd, + img_comp_xd, +) +from pcweb.templates.docpage.blocks.typography import ( + code_comp, + doclink2, + list_comp, + text_comp, +) + +# --------------------------------------------------------------------------- +# Exec environment — mirrors flexdown's module-based exec mechanism +# --------------------------------------------------------------------------- + +# One in-memory module per file — all exec blocks within a doc accumulate +# into the same namespace, so later definitions shadow earlier ones cleanly. +_file_modules: dict[str, types.ModuleType] = {} + +# Register the parent package so pickle can resolve child modules. +_PARENT_PKG = "_docgen_exec" +if _PARENT_PKG not in sys.modules: + _pkg = types.ModuleType(_PARENT_PKG) + _pkg.__path__ = [] # package needs __path__ for submodule imports + _pkg.__package__ = _PARENT_PKG + sys.modules[_PARENT_PKG] = _pkg + + +def _make_module_name(filename: str) -> str: + """Create a valid Python module name from a filepath.""" + import re + + slug = re.sub(r"[^a-zA-Z0-9]", "_", filename) + return f"{_PARENT_PKG}.{slug}" + + +def _exec_code(content: str, env: dict, filename: str) -> None: + """Execute a ``python exec`` code block via an in-memory module. + + All exec blocks within the same file share one module so that State + subclass redefinitions shadow correctly. + """ + if filename not in _file_modules: + mod_name = _make_module_name(filename) + module = types.ModuleType(mod_name) + module.__package__ = _PARENT_PKG + sys.modules[mod_name] = module + setattr(sys.modules[_PARENT_PKG], mod_name.split(".")[-1], module) + _file_modules[filename] = module + + module = _file_modules[filename] + module.__dict__.update(env) + + exec(compile(content, filename or "", "exec"), module.__dict__) + + env.update(module.__dict__) + + +# --------------------------------------------------------------------------- +# Span → rx.Component helpers +# --------------------------------------------------------------------------- + + +def _render_spans(spans: tuple[Span, ...]) -> list[rx.Component | str]: + """Convert a sequence of spans into a list of Reflex children.""" + out: list[rx.Component | str] = [] + for span in spans: + match span: + case TextSpan(text=text): + out.append(text) + case BoldSpan(children=children): + out.append(rx.el.strong(*_render_spans(children))) + case ItalicSpan(children=children): + out.append(rx.el.em(*_render_spans(children))) + case StrikethroughSpan(children=children): + inner = "".join( + c if isinstance(c, str) else "" for c in _render_spans(children) + ) + out.append(rx.text("~" + inner + "~", as_="span")) + case CodeSpan(code=code): + out.append(code_comp(text=code)) + case LinkSpan(children=children, target=target): + inner = "".join( + c if isinstance(c, str) else "" for c in _render_spans(children) + ) + out.append(doclink2(text=inner, href=target)) + case ImageSpan(src=src): + out.append(img_comp_xd(src=src)) + case LineBreakSpan(soft=soft): + out.append("\n" if soft else rx.el.br()) + return out + + +def _spans_to_plaintext(spans: tuple[Span, ...]) -> str: + """Extract plain text from spans (for headings, etc.).""" + parts: list[str] = [] + for span in spans: + match span: + case TextSpan(text=text): + parts.append(text) + case ( + BoldSpan(children=children) + | ItalicSpan(children=children) + | StrikethroughSpan(children=children) + | LinkSpan(children=children) + ): + parts.append(_spans_to_plaintext(children)) + case CodeSpan(code=code): + parts.append(code) + case _: + pass + return "".join(parts) + + +# --------------------------------------------------------------------------- +# ReflexDocTransformer +# --------------------------------------------------------------------------- + + +class ReflexDocTransformer(DocumentTransformer[rx.Component]): + """Transforms a reflex_docgen Document into Reflex components. + + Mirrors the rendering that the flexdown pipeline produces, so docs from + the reflex package look identical to the locally-authored ones. + """ + + def __init__(self, filename: str = "") -> None: + self.filename = filename + self.env: dict = {} + + # ------------------------------------------------------------------ + # Top-level + # ------------------------------------------------------------------ + + def transform(self, document: Document) -> rx.Component: + if document.frontmatter is not None: + # Populate env with component preview metadata. + for preview in document.frontmatter.component_previews: + self.env[preview.name] = preview.source + self.env["REFLEX_ASSETS_CDN"] = REFLEX_ASSETS_CDN + + children: list[rx.Component] = [] + for block in document.blocks: + comp = self.transform_block(block) + if comp is not None: + children.append(comp) + + return rx.fragment(*children) + + # ------------------------------------------------------------------ + # Blocks + # ------------------------------------------------------------------ + + def frontmatter(self, block: FrontMatter) -> rx.Component: + return rx.fragment() + + def heading(self, block: HeadingBlock) -> rx.Component: + text = _spans_to_plaintext(block.children) + match block.level: + case 1: + return h1_comp_xd(text=text) + case 2: + return h2_comp_xd(text=text) + case 3: + return h3_comp_xd(text=text) + case _: + return h4_comp_xd(text=text) + + def text_block(self, block: TextBlock) -> rx.Component: + children = _render_spans(block.children) + if len(children) == 1 and isinstance(children[0], str): + return text_comp(text=children[0]) + return rx.text( + *children, + class_name="font-[475] text-m-slate-8 dark:text-m-slate-6 mb-4 leading-7", + ) + + def code_block(self, block: CodeBlock) -> rx.Component: + flags = set(block.flags) + language = block.language or "plain" + + # ``python demo`` or ``python demo exec`` + if language == "python" and "demo" in flags: + return self._render_demo(block.content, flags) + + # ``python demo-only`` or ``python demo-only exec`` + if language == "python" and "demo-only" in flags: + return self._render_demo_only(block.content, flags) + + # ``python exec`` only — execute code, produce nothing visible. + if language == "python" and "exec" in flags: + _exec_code(block.content, self.env, self.filename) + return rx.fragment() + + # ``python eval`` (standalone) — eval and return the component directly. + if language == "python" and "eval" in flags: + return eval(block.content, self.env, self.env) + + # Regular code block (includes unknown flags like ``python box``). + + return code_block(code=block.content, language=language) + + def directive(self, block: DirectiveBlock) -> rx.Component: + """Handle ```md ``` blocks (alert, video, etc.).""" + match block.name: + case "alert": + return self._render_alert(block) + case "video": + return self._render_video(block) + case "quote": + return self._render_quote_directive(block) + case "tabs": + return self._render_tabs(block) + case "definition": + return self._render_definition(block) + case "section": + return self._render_section(block) + case _: + return self._render_children(block.children) + + def list_block(self, block: ListBlock) -> rx.Component: + items = [self.transform_list_item(item) for item in block.items] + if block.ordered: + return rx.list.ordered(*items, class_name="mb-6") + return rx.list.unordered(*items, class_name="mb-6") + + @staticmethod + def _list_item_from_spans(spans: tuple[Span, ...]) -> rx.Component: + """Render a list item, preserving inline code/links when present.""" + if all(isinstance(s, TextSpan) for s in spans): + return list_comp(text=_spans_to_plaintext(spans)) + return rx.list_item( + *_render_spans(spans), + class_name="font-[475] text-m-slate-8 dark:text-m-slate-6 mb-4", + ) + + def transform_list_item(self, item: ListItem) -> rx.Component: + children: list[rx.Component] = [] + for child_block in item.children: + match child_block: + case TextBlock(children=spans): + children.append(self._list_item_from_spans(spans)) + case _: + children.append(self.transform_block(child_block)) + if len(children) == 1: + return children[0] + return rx.fragment(*children) + + def quote(self, block: QuoteBlock) -> rx.Component: + children = [self.transform_block(b) for b in block.children] + return rx.box( + *children, + class_name="border-l-[3px] border-slate-4 pl-6 mt-2 mb-6", + ) + + def table(self, block: TableBlock) -> rx.Component: + header_cells = [ + rx.table.column_header_cell( + *_render_spans(cell.children), + class_name="font-small text-slate-12 font-bold", + ) + for cell in block.header.cells + ] + rows = [] + for row in block.rows: + cells = [ + rx.table.cell( + *_render_spans(cell.children), + class_name="font-small text-slate-11", + ) + for cell in row.cells + ] + rows.append(rx.table.row(*cells)) + + return rx.table.root( + rx.table.header(rx.table.row(*header_cells)), + rx.table.body(*rows), + variant="surface", + size="1", + class_name="w-full border border-slate-4 mb-4", + ) + + def transform_table_row(self, row: TableRow) -> rx.Component: + cells = [self.transform_table_cell(cell) for cell in row.cells] + return rx.table.row(*cells) + + def transform_table_cell(self, cell: TableCell) -> rx.Component: + return rx.table.cell(*_render_spans(cell.children)) + + def thematic_break(self, block: ThematicBreakBlock) -> rx.Component: + return rx.separator(class_name="my-6") + + # ------------------------------------------------------------------ + # Spans (not used directly by DocumentTransformer dispatch, but + # kept for completeness if someone calls transform_span) + # ------------------------------------------------------------------ + + def text_span(self, span: TextSpan) -> rx.Component: + return rx.text(span.text, as_="span") + + def bold(self, span: BoldSpan) -> rx.Component: + return rx.el.strong(*self.transform_spans(span.children)) + + def italic(self, span: ItalicSpan) -> rx.Component: + return rx.el.em(*self.transform_spans(span.children)) + + def strikethrough(self, span: StrikethroughSpan) -> rx.Component: + return rx.text("~", *self.transform_spans(span.children), "~", as_="span") + + def code_span(self, span: CodeSpan) -> rx.Component: + return code_comp(text=span.code) + + def link(self, span: LinkSpan) -> rx.Component: + inner = _spans_to_plaintext(span.children) + return doclink2(text=inner, href=span.target) + + def image(self, span: ImageSpan) -> rx.Component: + return img_comp_xd(src=span.src) + + def line_break(self, span: LineBreakSpan) -> rx.Component: + return rx.fragment() + + # ------------------------------------------------------------------ + # Demo / exec helpers + # ------------------------------------------------------------------ + + def _render_demo(self, content: str, flags: set[str]) -> rx.Component: + """Render a ``python demo`` block — code + live component.""" + comp_id = None + for flag in flags: + if flag.startswith("id="): + comp_id = flag.split("=", 1)[1] + + try: + if "exec" in flags: + _exec_code(content, self.env, self.filename) + comp = self.env[list(self.env.keys())[-1]]() + elif "graphing" in flags: + _exec_code(content, self.env, self.filename) + comp = self.env[list(self.env.keys())[-1]]() + parts = content.rpartition("def") + data, code = parts[0], parts[1] + parts[2] + return docgraphing(code, comp=comp, data=data) + elif "box" in flags: + comp = eval(content, self.env, self.env) + return rx.box(docdemobox(comp), margin_bottom="1em", id=comp_id) + else: + comp = eval(content, self.env, self.env) + except Exception as e: + e.add_note( + f"While rendering demo block in {self.filename}:\n{content[:200]}" + ) + raise + + demobox_props: dict = {} + for flag in flags: + k, sep, v = flag.partition("=") + if sep: + demobox_props[k] = v + if "toggle" in flags: + demobox_props["toggle"] = True + + return docdemo(content, comp=comp, demobox_props=demobox_props, id=comp_id) + + def _render_demo_only(self, content: str, flags: set[str]) -> rx.Component: + """Render a ``python demo-only`` block — component only, no code.""" + comp_id = None + for flag in flags: + if flag.startswith("id="): + comp_id = flag.split("=", 1)[1] + + try: + if "exec" in flags: + _exec_code(content, self.env, self.filename) + comp = self.env[list(self.env.keys())[-1]]() + elif "graphing" in flags: + _exec_code(content, self.env, self.filename) + comp = self.env[list(self.env.keys())[-1]]() + parts = content.rpartition("def") + data, code = parts[0], parts[1] + parts[2] + return docgraphing(code, comp=comp, data=data) + elif "box" in flags: + comp = eval(content, self.env, self.env) + else: + comp = eval(content, self.env, self.env) + except Exception as e: + e.add_note( + f"While rendering demo-only block in {self.filename}:\n{content[:200]}" + ) + raise + + return rx.box(comp, margin_bottom="1em", id=comp_id) + + def _render_children(self, blocks: tuple[Block, ...]) -> rx.Component: + """Render a sequence of parsed blocks into a single component.""" + rendered = [self.transform_block(b) for b in blocks] + return rx.fragment(*rendered) if len(rendered) != 1 else rendered[0] + + def _split_children_by_heading( + self, blocks: tuple[Block, ...] + ) -> list[tuple[str, tuple[Block, ...]]]: + """Split directive children into (title, blocks) by top-level headings. + + Only headings matching the level of the first heading are used as + section delimiters — deeper headings stay inside their section. + """ + split_level: int | None = None + sections: list[tuple[str, list[Block]]] = [] + for child in blocks: + if isinstance(child, HeadingBlock): + if split_level is None: + split_level = child.level + if child.level == split_level: + sections.append((_spans_to_plaintext(child.children), [])) + continue + if sections: + sections[-1][1].append(child) + return [(title, tuple(body)) for title, body in sections] + + def _render_alert(self, block: DirectiveBlock) -> rx.Component: + """Render a ``md alert`` directive.""" + status = block.args[0] if block.args else "info" + colors: dict[str, ColorType] = { + "info": "accent", + "success": "grass", + "warning": "amber", + "error": "red", + } + color: ColorType = colors.get(status, "blue") + + # First child may be a heading used as the alert title. + children = block.children + title_spans: tuple[Span, ...] = () + if children and isinstance(children[0], HeadingBlock): + title_spans = children[0].children + children = children[1:] + + icon_map = { + "info": "info", + "success": "circle_check", + "warning": "triangle_alert", + "error": "ban", + } + icon_tag = icon_map.get(status, "info") + + def title_comp() -> rx.Component: + return rx.box( + *_render_spans(title_spans), + class_name="font-[475]", + color=f"{rx.color(color, 11)}", + ) + + trigger: list[rx.Component] = [ + rx.box( + rx.icon(tag=icon_tag, size=18, margin_right=".5em"), + color=f"{rx.color(color, 11)}", + ), + ] + + if children: + # Has body content — render as collapsible accordion. + if title_spans: + trigger.append(title_comp()) + body = rx.accordion.content( + self._render_children(children), + padding="0px", + margin_top="16px", + ) + else: + trigger.append( + rx.box( + self._render_children(children), + class_name="font-[475] !text-m-slate-8 dark:!text-m-slate-6", + ), + ) + body = rx.fragment() + return collapsible_box(trigger, body, color) + + # Title only, no body — simple box. + trigger.append(title_comp()) + return rx.vstack( + rx.hstack( + *trigger, + align_items="center", + width="100%", + spacing="1", + padding=["16px", "24px"], + ), + border=f"1px solid {rx.color(color, 4)}", + background_color=f"{rx.color(color, 3)}", + border_radius="12px", + margin_bottom="16px", + margin_top="16px", + width="100%", + ) + + def _render_video(self, block: DirectiveBlock) -> rx.Component: + """Render a ``md video`` directive — accordion-wrapped.""" + url = block.args[0] if block.args else "" + # First child heading is the video title. + children = block.children + title = "Video Description" + if children and isinstance(children[0], HeadingBlock): + title = _spans_to_plaintext(children[0].children) + + color: ColorType = "blue" + trigger = [ + rx.text(title, class_name="font-[475]", color=f"{rx.color(color, 11)}"), + ] + body = rx.accordion.content( + rx.video( + src=url, + width="100%", + height="500px", + border_radius="10px", + overflow="hidden", + ), + margin_top="16px", + padding="0px", + ) + return collapsible_box(trigger, body, color, item_border_radius="0px") + + def _render_quote_directive(self, block: DirectiveBlock) -> rx.Component: + """Render a ``md quote`` directive.""" + quote_parts: list[rx.Component | str] = [] + name = "" + role = "" + for child in block.children: + if isinstance(child, TextBlock): + quote_parts.extend(_render_spans(child.children)) + elif isinstance(child, ListBlock): + for item in child.items: + for sub in item.children: + if isinstance(sub, TextBlock): + text = _spans_to_plaintext(sub.children) + if text.startswith("name:"): + name = text.split(":", 1)[1].strip() + elif text.startswith("role:"): + role = text.split(":", 1)[1].strip() + + return rx.box( + rx.text( + '"', + *quote_parts, + '"', + class_name="text-slate-11 font-base italic", + ), + rx.box( + rx.text(name, class_name="text-slate-11 font-base"), + rx.text(role, class_name="text-slate-10 font-base"), + class_name="flex flex-col gap-0.5", + ), + class_name="flex flex-col gap-4 border-l-[3px] border-slate-4 pl-6 mt-2 mb-6", + ) + + def _render_tabs(self, block: DirectiveBlock) -> rx.Component: + """Render a ``md tabs`` directive. Sections split by ``##`` headings.""" + sections = self._split_children_by_heading(block.children) + triggers = [] + contents = [] + for i, (title, body_blocks) in enumerate(sections): + value = f"tab{i + 1}" + triggers.append( + rx.tabs.trigger( + title, + value=value, + class_name="tab-style font-base font-semibold text-[1.25rem]", + ) + ) + contents.append( + rx.tabs.content(self._render_children(body_blocks), value=value), + ) + + return rx.tabs.root( + rx.tabs.list(*triggers, class_name="mt-4"), + *contents, + default_value="tab1", + ) + + def _render_definition(self, block: DirectiveBlock) -> rx.Component: + """Render a ``md definition`` directive.""" + from pcweb.templates.docpage.blocks.typography import definition + + sections = self._split_children_by_heading(block.children) + defs = [ + definition(title, self._render_children(body)) for title, body in sections + ] + return rx.fragment( + rx.mobile_only(rx.vstack(*defs)), + rx.tablet_and_desktop( + rx.grid( + *[rx.box(d) for d in defs], + columns="2", + width="100%", + gap="1rem", + margin_bottom="1em", + ) + ), + ) + + def _render_section(self, block: DirectiveBlock) -> rx.Component: + """Render a ``md section`` directive.""" + from pcweb.styles.colors import c_color + + sections = self._split_children_by_heading(block.children) + return rx.box( + rx.vstack( + *[ + rx.fragment( + rx.text( + rx.text.span(header, font_weight="bold"), + width="100%", + ), + rx.box(self._render_children(body), width="100%"), + ) + for header, body in sections + ], + text_align="left", + margin_y="1em", + width="100%", + ), + border_left=f"1.5px {c_color('slate', 4)} solid", + padding_left="1em", + width="100%", + align_items="center", + ) + + +# --------------------------------------------------------------------------- +# Public API +# --------------------------------------------------------------------------- + + +def _parse_doc(filepath: str | Path) -> Document: + source = Path(filepath).read_text(encoding="utf-8") + return parse_document(source) + + +def render_docgen_document(filepath: str | Path) -> rx.Component: + """Parse and render a doc file from the reflex package using reflex_docgen.""" + doc = _parse_doc(filepath) + transformer = ReflexDocTransformer(filename=str(filepath)) + return transformer.transform(doc) + + +def get_docgen_toc(filepath: str | Path) -> list[tuple[int, str]]: + """Extract TOC headings as (level, text) tuples — same format as flexdown's get_toc.""" + doc = _parse_doc(filepath) + return [(h.level, _spans_to_plaintext(h.children)) for h in doc.headings] diff --git a/pcweb/flexdown.py b/pcweb/flexdown.py index 27c7a8429f..ca764af189 100644 --- a/pcweb/flexdown.py +++ b/pcweb/flexdown.py @@ -1,5 +1,6 @@ import flexdown import reflex as rx +from reflex_core.constants.colors import ColorType from pcweb.styles.colors import c_color from pcweb.styles.fonts import base, code @@ -22,6 +23,7 @@ text_comp, unordered_list_comp, ) +from pcweb.templates.docpage.blocks.collapsible import collapsible_box def get_code_style(color: str): @@ -62,154 +64,71 @@ def render(self, env) -> rx.Component: title = "" content = "\n".join(lines[1:-1]) - colors = { + colors: dict[str, ColorType] = { "info": "accent", "success": "grass", "warning": "amber", "error": "red", } - color = colors.get(status, "blue") + color: ColorType = colors.get(status, "blue") has_content = bool(content.strip()) - if has_content: - return rx.box( - rx.accordion.root( - rx.accordion.item( - rx.accordion.header( - rx.accordion.trigger( - rx.hstack( - rx.box( - rx.match( - status, - ( - "info", - rx.icon( - tag="info", - size=18, - margin_right=".5em", - ), - ), - ( - "success", - rx.icon( - tag="circle_check", - size=18, - margin_right=".5em", - ), - ), - ( - "warning", - rx.icon( - tag="triangle_alert", - size=18, - margin_right=".5em", - ), - ), - ( - "error", - rx.icon( - tag="ban", - size=18, - margin_right=".5em", - ), - ), - ), - color=f"{rx.color(color, 11)}", - ), - ( - markdown_with_shiki( - title, - margin_y="0px", - style=get_code_style(color), - ) - if title - else self.render_fn(content=content) - ), - rx.spacer(), - rx.accordion.icon(color=f"{rx.color(color, 11)}"), - align_items="center", - justify_content="left", - text_align="left", - spacing="2", - width="100%", - margin_top="5px", - ), - padding="0px", - color=f"{rx.color(color, 11)} !important", - background_color="transparent !important", - border_radius="12px", - _hover={}, - ), - ), - ( - rx.accordion.content( - markdown(content), padding="0px", margin_top="16px" - ) - if title - else rx.fragment() - ), - border_radius="12px", - padding=["16px", "24px"], - background_color=f"{rx.color(color, 3)}", - border="none", - ), - background="transparent !important", - border_radius="12px", - box_shadow="none !important", - collapsible=True, - width="100%", + icon = rx.box( + rx.match( + status, + ("info", rx.icon(tag="info", size=18, margin_right=".5em")), + ("success", rx.icon(tag="circle_check", size=18, margin_right=".5em")), + ( + "warning", + rx.icon(tag="triangle_alert", size=18, margin_right=".5em"), ), - border=f"1px solid {rx.color(color, 4)}", - border_radius="12px", - background_color=f"{rx.color(color, 3)} !important", - width="100%", - margin_bottom="16px", - margin_top="16px", - overflow="hidden", + ("error", rx.icon(tag="ban", size=18, margin_right=".5em")), + ), + color=f"{rx.color(color, 11)}", + ) + title_comp = ( + markdown_with_shiki( + title, + margin_y="0px", + style=get_code_style(color), ) - else: - return rx.vstack( - rx.hstack( - rx.box( - rx.match( - status, - ("info", rx.icon(tag="info", size=18, margin_right=".5em")), - ( - "success", - rx.icon( - tag="circle_check", size=18, margin_right=".5em" - ), - ), - ( - "warning", - rx.icon( - tag="triangle_alert", size=18, margin_right=".5em" - ), - ), - ("error", rx.icon(tag="ban", size=18, margin_right=".5em")), - ), - color=f"{rx.color(color, 11)}", - ), - markdown_with_shiki( - title, - color=f"{rx.color(color, 11)}", - margin_y="0px", - style=get_code_style(color), - ), - align_items="center", - width="100%", - spacing="1", - padding=["16px", "24px"], + if title + else self.render_fn(content=content) + ) + + if has_content: + body = ( + rx.accordion.content( + markdown(content), padding="0px", margin_top="16px" + ) + if title + else rx.fragment() + ) + return collapsible_box([icon, title_comp], body, color) + + return rx.vstack( + rx.hstack( + icon, + markdown_with_shiki( + title, + color=f"{rx.color(color, 11)}", + margin_y="0px", + style=get_code_style(color), ), - border=f"1px solid {rx.color(color, 4)}", - background_color=f"{rx.color(color, 3)}", - border_radius="12px", - margin_bottom="16px", - margin_top="16px", + align_items="center", width="100%", - ) + spacing="1", + padding=["16px", "24px"], + ), + border=f"1px solid {rx.color(color, 4)}", + background_color=f"{rx.color(color, 3)}", + border_radius="12px", + margin_bottom="16px", + margin_top="16px", + width="100%", + ) class SectionBlock(flexdown.blocks.Block): @@ -438,68 +357,27 @@ def render(self, env) -> rx.Component: title = lines[1].strip("#").strip() if lines[1].startswith("#") else "" - color = "blue" + color: ColorType = "blue" - return rx.box( - rx.accordion.root( - rx.accordion.item( - rx.accordion.header( - rx.accordion.trigger( - rx.hstack( - ( - markdown_with_shiki( - title, - margin_y="0px", - style=get_code_style(color), - ) - if title - else markdown_with_shiki("Video Description") - ), - rx.spacer(), - rx.accordion.icon(color=f"{rx.color(color, 11)}"), - align_items="center", - justify_content="left", - text_align="left", - spacing="2", - width="100%", - ), - padding="0px", - color=f"{rx.color(color, 11)} !important", - background_color="transparent !important", - border_radius="12px", - _hover={}, - ), - ), - rx.accordion.content( - rx.video( - src=url, - width="100%", - height="500px", - border_radius="10px", - overflow="hidden", - ), - margin_top="16px", - padding="0px", - ), - border_radius="0px", - border="none", - background_color="transparent", - padding=["16px", "24px"], - ), - background="transparent !important", - box_shadow="none !important", - collapsible=True, + trigger = [ + markdown_with_shiki( + title or "Video Description", + margin_y="0px", + style=get_code_style(color), + ), + ] + body = rx.accordion.content( + rx.video( + src=url, width="100%", - border_radius="0px", + height="500px", + border_radius="10px", + overflow="hidden", ), - border=f"1px solid {rx.color(color, 4)}", - border_radius="12px", - background_color=f"{rx.color(color, 3)} !important", - width="100%", - margin_bottom="16px", margin_top="16px", - overflow="hidden", + padding="0px", ) + return collapsible_box(trigger, body, color, item_border_radius="0px") class QuoteBlock(flexdown.blocks.MarkdownBlock): diff --git a/pcweb/pages/docs/__init__.py b/pcweb/pages/docs/__init__.py index cb11d1ec02..c011c69e19 100644 --- a/pcweb/pages/docs/__init__.py +++ b/pcweb/pages/docs/__init__.py @@ -1,5 +1,5 @@ import os -from collections import defaultdict +from collections import defaultdict, namedtuple from pathlib import Path from types import SimpleNamespace @@ -11,6 +11,7 @@ from reflex_pyplot import pyplot as pyplot from pcweb.constants import REFLEX_ASSETS_CDN +from pcweb.docgen_pipeline import get_docgen_toc, render_docgen_document from pcweb.flexdown import xd from pcweb.pages.docs.component import multi_docs from pcweb.pages.library_previews import components_previews_pages @@ -92,25 +93,40 @@ def get_components_from_metadata(current_doc): return components +# --------------------------------------------------------------------------- +# Local docs (ai_builder, enterprise, hosting, etc.) — processed via flexdown +# --------------------------------------------------------------------------- flexdown_docs = [ str(doc).replace("\\", "/") for doc in flexdown.utils.get_flexdown_files("docs/") ] # Add integration docs from the submodule -# Create a mapping from virtual path to actual path -doc_path_mapping = {} +doc_path_mapping: dict[str, str] = {} integration_docs_path = Path("integrations-docs/docs") if integration_docs_path.exists(): for integration_doc in integration_docs_path.glob("*.md"): - # Map submodule docs to the ai_builder/integrations path structure virtual_path = f"docs/ai_builder/integrations/{integration_doc.name}" actual_path = str(integration_doc).replace("\\", "/") if virtual_path.replace("\\", "/") not in flexdown_docs: - # Store the mapping doc_path_mapping[virtual_path.replace("\\", "/")] = actual_path - # Add to flexdown_docs for processing flexdown_docs.append(virtual_path.replace("\\", "/")) +# --------------------------------------------------------------------------- +# Reflex-shipped docs (installed in site-packages/docs/) — processed via +# reflex_docgen.markdown pipeline (no flexdown). +# --------------------------------------------------------------------------- +# Maps virtual path (e.g. "docs/getting_started/basics.md") → absolute path. +docgen_docs: dict[str, str] = {} +_reflex_docs_dir = Path(rx.__file__).parent / "docs" +if _reflex_docs_dir.is_dir(): + for _pkg_doc in sorted(_reflex_docs_dir.rglob("*.md")): + _virtual = "docs/" + str(_pkg_doc.relative_to(_reflex_docs_dir)).replace( + "\\", "/" + ) + # Only add if not already provided locally (local overrides package). + if _virtual not in flexdown_docs: + docgen_docs[_virtual] = str(_pkg_doc) + graphing_components = defaultdict(list) component_list = defaultdict(list) recipes_list = defaultdict(list) @@ -152,61 +168,117 @@ def exec_blocks(doc, href): } -def get_component(doc: str, title: str): - if doc.endswith("-style.md"): - return +ResolvedDoc = namedtuple("ResolvedDoc", ["route", "display_title", "category"]) - if doc.endswith("-ll.md"): - return - # Get the docpage component. - doc = doc.replace("\\", "/") +def doc_title_from_path(doc: str) -> str: + """Extract a snake_case title from a doc path.""" + return rx.utils.format.to_snake_case(os.path.basename(doc).replace(".md", "")) + + +def doc_route_from_path(doc: str) -> str: + """Compute the URL route from a doc path.""" route = rx.utils.format.to_kebab_case(f"/{doc.replace('.md', '/')}") - # Handle index files: /folder/index/ -> /folder/ if route.endswith("/index/"): route = route[:-7] + "/" - title2 = manual_titles[doc] if doc in manual_titles else to_title_case(title) - category = os.path.basename(os.path.dirname(doc)).title() + return route + + +def resolve_doc_route(doc: str, title: str) -> ResolvedDoc | None: + """Compute route, display title, and category for a doc path. + Returns None if the doc should be skipped (suffix or whitelist). + """ + if doc.endswith("-style.md") or doc.endswith("-ll.md"): + return None + doc = doc.replace("\\", "/") + route = doc_route_from_path(doc) if not _check_whitelisted_path(route): - return + return None + display_title = manual_titles.get(doc, to_title_case(title)) + category = os.path.basename(os.path.dirname(doc)).title() + return ResolvedDoc(route=route, display_title=display_title, category=category) - # Use the actual file path if this is from the submodule - actual_doc_path = doc_path_mapping.get(doc, doc) - d = Document.from_file(actual_doc_path) + +def make_docpage(route: str, title: str, doc_virtual: str, render_fn): + """Wrap a render function as a docpage, setting module metadata.""" + doc_path = Path(doc_virtual) + render_fn.__module__ = ".".join(doc_path.parts[:-1]) + render_fn.__name__ = doc_path.stem + render_fn.__qualname__ = doc_path.stem + return docpage(set_path=route, t=title)(render_fn) + + +def load_flexdown_doc(actual_path: str) -> Document: + """Load a flexdown Document and inject standard metadata.""" + d = Document.from_file(actual_path) d.metadata["REFLEX_ASSETS_CDN"] = REFLEX_ASSETS_CDN + return d + +def handle_library_doc( + doc: str, + actual_path: str, + title: str, + resolved: ResolvedDoc, +): + """Handle docs/library/** docs — component API reference via multi_docs.""" + d = load_flexdown_doc(actual_path) + clist = [title, *get_components_from_metadata(d)] if doc.startswith("docs/library/graphing"): - if should_skip_compile(actual_doc_path): - outblocks.append((d, route)) - return - clist = [title, *get_components_from_metadata(d)] - graphing_components[category].append(clist) - return multi_docs(path=route, comp=d, component_list=clist, title=title2) + graphing_components[resolved.category].append(clist) + else: + component_list[resolved.category].append(clist) + if should_skip_compile(actual_path): + outblocks.append((d, resolved.route)) + return None + return multi_docs( + path=resolved.route, + comp=d, + component_list=clist, + title=resolved.display_title, + ) + + +def get_component(doc: str, title: str): + """Build a page component for a local (flexdown) doc.""" + resolved = resolve_doc_route(doc, title) + if resolved is None: + return None + + actual_doc_path = doc_path_mapping.get(doc, doc) + if doc.startswith("docs/library"): - clist = [title, *get_components_from_metadata(d)] - component_list[category].append(clist) - if should_skip_compile(actual_doc_path): - outblocks.append((d, route)) - return - return multi_docs(path=route, comp=d, component_list=clist, title=title2) + return handle_library_doc(doc, actual_doc_path, title, resolved) if should_skip_compile(actual_doc_path): - outblocks.append((d, route)) - return + outblocks.append((load_flexdown_doc(actual_doc_path), resolved.route)) + return None + + d = load_flexdown_doc(actual_doc_path) def comp(): return (get_toc(d, actual_doc_path), xd.render(d, actual_doc_path)) - doc_path = Path(doc) - doc_module = ".".join(doc_path.parts[:-1]) - doc_file = doc_path.stem + return make_docpage(resolved.route, resolved.display_title, doc, comp) - comp.__module__ = doc_module - comp.__name__ = doc_file - comp.__qualname__ = doc_file - return docpage(set_path=route, t=title2)(comp) +def get_component_docgen(virtual_doc: str, actual_path: str, title: str): + """Build a page component for a reflex-package doc via reflex_docgen.""" + resolved = resolve_doc_route(virtual_doc, title) + if resolved is None: + return None + + # Library docs still need component introspection via multi_docs (flexdown-based). + if virtual_doc.startswith("docs/library"): + return handle_library_doc(virtual_doc, actual_path, title, resolved) + + def comp(_actual=actual_path): + toc = get_docgen_toc(_actual) + rendered = render_docgen_document(_actual) + return (toc, rendered) + + return make_docpage(resolved.route, resolved.display_title, virtual_doc, comp) doc_routes = [ @@ -232,28 +304,14 @@ def comp(): title = rx.utils.format.to_snake_case(ref.title) build_nested_namespace(docs_ns, ["cloud"], title, ref) -for doc in sorted(flexdown_docs): - path = doc.split("/")[1:-1] - title = rx.utils.format.to_snake_case(os.path.basename(doc).replace(".md", "")) +def register_doc(virtual_doc: str, comp): + """Register a doc into the namespace, doc_routes, and recipes_list.""" + path = virtual_doc.split("/")[1:-1] + title = doc_title_from_path(virtual_doc) title2 = to_title_case(title) - route = rx.utils.format.to_kebab_case(f"/{doc.replace('.md', '/')}") - # Handle index files: /folder/index/ -> /folder/ - if route.endswith("/index/"): - route = route[:-7] + "/" - - comp = get_component(doc, title) - - # # Check if the path starts with '/docs/cloud/', and if so, replace 'docs' with an empty string - # if route.startswith("/docs/cloud/"): - # route = route.replace("/docs", "") - - if path[0] == "library" and isinstance(library, Route): - locals()["library_"] = library - - # print(route) + route = doc_route_from_path(virtual_doc) - # Add the component to the nested namespaces. build_nested_namespace( docs_ns, path, title, Route(path=route, title=title2, component=lambda: "") ) @@ -264,11 +322,24 @@ def comp(): else: doc_routes.append(comp) + if "recipes" in virtual_doc: + recipes_list[virtual_doc.split("/")[2]].append(virtual_doc) -for doc in flexdown_docs: - if "recipes" in doc: - category = doc.split("/")[2] - recipes_list[category].append(doc) + +# Alias needed by sidebar — the library page route object. +library_: Route = library # type: ignore[assignment] + + +# Process local docs (flexdown pipeline). +for _doc in sorted(flexdown_docs): + register_doc(_doc, get_component(_doc, doc_title_from_path(_doc))) + +# Process reflex-package docs (reflex_docgen pipeline). +for _virtual, _actual in sorted(docgen_docs.items()): + register_doc( + _virtual, + get_component_docgen(_virtual, _actual, doc_title_from_path(_virtual)), + ) for name, ns in docs_ns.__dict__.items(): # if name == "cloud": diff --git a/pcweb/pages/docs/apiref.py b/pcweb/pages/docs/apiref.py index 5ea2dbc037..4123bd6b2a 100644 --- a/pcweb/pages/docs/apiref.py +++ b/pcweb/pages/docs/apiref.py @@ -1,4 +1,5 @@ import reflex as rx +from reflex.istate.manager import StateManager from reflex.utils.imports import ImportVar from reflex_docgen import generate_class_documentation @@ -16,7 +17,7 @@ rx.event.EventSpec, rx.Model, # rx.testing.AppHarness, - rx.state.StateManager, + StateManager, # rx.state.BaseState, rx.State, ImportVar, diff --git a/pcweb/templates/docpage/blocks/collapsible.py b/pcweb/templates/docpage/blocks/collapsible.py new file mode 100644 index 0000000000..18f08409d1 --- /dev/null +++ b/pcweb/templates/docpage/blocks/collapsible.py @@ -0,0 +1,58 @@ +"""Collapsible accordion box used by alert and video blocks.""" + +from collections.abc import Sequence + +import reflex as rx +from reflex_core.constants.colors import ColorType + + +def collapsible_box( + trigger_children: Sequence[rx.Component], + body: rx.Component, + color: ColorType, + *, + item_border_radius: str = "12px", +) -> rx.Component: + """Collapsible accordion wrapper shared by alert and video directives.""" + return rx.box( + rx.accordion.root( + rx.accordion.item( + rx.accordion.header( + rx.accordion.trigger( + rx.hstack( + *trigger_children, + rx.spacer(), + rx.accordion.icon(color=f"{rx.color(color, 11)}"), + align_items="center", + justify_content="left", + text_align="left", + spacing="2", + width="100%", + ), + padding="0px", + color=f"{rx.color(color, 11)} !important", + background_color="transparent !important", + border_radius="12px", + _hover={}, + ), + ), + body, + border_radius=item_border_radius, + padding=["16px", "24px"], + background_color=f"{rx.color(color, 3)}", + border="none", + ), + background="transparent !important", + border_radius="12px", + box_shadow="none !important", + collapsible=True, + width="100%", + ), + border=f"1px solid {rx.color(color, 4)}", + border_radius="12px", + background_color=f"{rx.color(color, 3)} !important", + width="100%", + margin_bottom="16px", + margin_top="16px", + overflow="hidden", + ) diff --git a/uv.lock b/uv.lock index ee3bee9852..0802ea2158 100644 --- a/uv.lock +++ b/uv.lock @@ -21,7 +21,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.13.3" +version = "3.13.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -32,93 +32,93 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/4c/a164164834f03924d9a29dc3acd9e7ee58f95857e0b467f6d04298594ebb/aiohttp-3.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b6073099fb654e0a068ae678b10feff95c5cae95bbfcbfa7af669d361a8aa6b", size = 746051, upload-time = "2026-01-03T17:29:43.287Z" }, - { url = "https://files.pythonhosted.org/packages/82/71/d5c31390d18d4f58115037c432b7e0348c60f6f53b727cad33172144a112/aiohttp-3.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cb93e166e6c28716c8c6aeb5f99dfb6d5ccf482d29fe9bf9a794110e6d0ab64", size = 499234, upload-time = "2026-01-03T17:29:44.822Z" }, - { url = "https://files.pythonhosted.org/packages/0e/c9/741f8ac91e14b1d2e7100690425a5b2b919a87a5075406582991fb7de920/aiohttp-3.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28e027cf2f6b641693a09f631759b4d9ce9165099d2b5d92af9bd4e197690eea", size = 494979, upload-time = "2026-01-03T17:29:46.405Z" }, - { url = "https://files.pythonhosted.org/packages/75/b5/31d4d2e802dfd59f74ed47eba48869c1c21552c586d5e81a9d0d5c2ad640/aiohttp-3.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b61b7169ababd7802f9568ed96142616a9118dd2be0d1866e920e77ec8fa92a", size = 1748297, upload-time = "2026-01-03T17:29:48.083Z" }, - { url = "https://files.pythonhosted.org/packages/1a/3e/eefad0ad42959f226bb79664826883f2687d602a9ae2941a18e0484a74d3/aiohttp-3.13.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:80dd4c21b0f6237676449c6baaa1039abae86b91636b6c91a7f8e61c87f89540", size = 1707172, upload-time = "2026-01-03T17:29:49.648Z" }, - { url = "https://files.pythonhosted.org/packages/c5/3a/54a64299fac2891c346cdcf2aa6803f994a2e4beeaf2e5a09dcc54acc842/aiohttp-3.13.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65d2ccb7eabee90ce0503c17716fc77226be026dcc3e65cce859a30db715025b", size = 1805405, upload-time = "2026-01-03T17:29:51.244Z" }, - { url = "https://files.pythonhosted.org/packages/6c/70/ddc1b7169cf64075e864f64595a14b147a895a868394a48f6a8031979038/aiohttp-3.13.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b179331a481cb5529fca8b432d8d3c7001cb217513c94cd72d668d1248688a3", size = 1899449, upload-time = "2026-01-03T17:29:53.938Z" }, - { url = "https://files.pythonhosted.org/packages/a1/7e/6815aab7d3a56610891c76ef79095677b8b5be6646aaf00f69b221765021/aiohttp-3.13.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d4c940f02f49483b18b079d1c27ab948721852b281f8b015c058100e9421dd1", size = 1748444, upload-time = "2026-01-03T17:29:55.484Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f2/073b145c4100da5511f457dc0f7558e99b2987cf72600d42b559db856fbc/aiohttp-3.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9444f105664c4ce47a2a7171a2418bce5b7bae45fb610f4e2c36045d85911d3", size = 1606038, upload-time = "2026-01-03T17:29:57.179Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c1/778d011920cae03ae01424ec202c513dc69243cf2db303965615b81deeea/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:694976222c711d1d00ba131904beb60534f93966562f64440d0c9d41b8cdb440", size = 1724156, upload-time = "2026-01-03T17:29:58.914Z" }, - { url = "https://files.pythonhosted.org/packages/0e/cb/3419eabf4ec1e9ec6f242c32b689248365a1cf621891f6f0386632525494/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f33ed1a2bf1997a36661874b017f5c4b760f41266341af36febaf271d179f6d7", size = 1722340, upload-time = "2026-01-03T17:30:01.962Z" }, - { url = "https://files.pythonhosted.org/packages/7a/e5/76cf77bdbc435bf233c1f114edad39ed4177ccbfab7c329482b179cff4f4/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e636b3c5f61da31a92bf0d91da83e58fdfa96f178ba682f11d24f31944cdd28c", size = 1783041, upload-time = "2026-01-03T17:30:03.609Z" }, - { url = "https://files.pythonhosted.org/packages/9d/d4/dd1ca234c794fd29c057ce8c0566b8ef7fd6a51069de5f06fa84b9a1971c/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5d2d94f1f5fcbe40838ac51a6ab5704a6f9ea42e72ceda48de5e6b898521da51", size = 1596024, upload-time = "2026-01-03T17:30:05.132Z" }, - { url = "https://files.pythonhosted.org/packages/55/58/4345b5f26661a6180afa686c473620c30a66afdf120ed3dd545bbc809e85/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2be0e9ccf23e8a94f6f0650ce06042cefc6ac703d0d7ab6c7a917289f2539ad4", size = 1804590, upload-time = "2026-01-03T17:30:07.135Z" }, - { url = "https://files.pythonhosted.org/packages/7b/06/05950619af6c2df7e0a431d889ba2813c9f0129cec76f663e547a5ad56f2/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9af5e68ee47d6534d36791bbe9b646d2a7c7deb6fc24d7943628edfbb3581f29", size = 1740355, upload-time = "2026-01-03T17:30:09.083Z" }, - { url = "https://files.pythonhosted.org/packages/3e/80/958f16de79ba0422d7c1e284b2abd0c84bc03394fbe631d0a39ffa10e1eb/aiohttp-3.13.3-cp311-cp311-win32.whl", hash = "sha256:a2212ad43c0833a873d0fb3c63fa1bacedd4cf6af2fee62bf4b739ceec3ab239", size = 433701, upload-time = "2026-01-03T17:30:10.869Z" }, - { url = "https://files.pythonhosted.org/packages/dc/f2/27cdf04c9851712d6c1b99df6821a6623c3c9e55956d4b1e318c337b5a48/aiohttp-3.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:642f752c3eb117b105acbd87e2c143de710987e09860d674e068c4c2c441034f", size = 457678, upload-time = "2026-01-03T17:30:12.719Z" }, - { url = "https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c", size = 739732, upload-time = "2026-01-03T17:30:14.23Z" }, - { url = "https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168", size = 494293, upload-time = "2026-01-03T17:30:15.96Z" }, - { url = "https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d", size = 493533, upload-time = "2026-01-03T17:30:17.431Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a8/5a35dc56a06a2c90d4742cbf35294396907027f80eea696637945a106f25/aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29", size = 1737839, upload-time = "2026-01-03T17:30:19.422Z" }, - { url = "https://files.pythonhosted.org/packages/bf/62/4b9eeb331da56530bf2e198a297e5303e1c1ebdceeb00fe9b568a65c5a0c/aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3", size = 1703932, upload-time = "2026-01-03T17:30:21.756Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f6/af16887b5d419e6a367095994c0b1332d154f647e7dc2bd50e61876e8e3d/aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d", size = 1771906, upload-time = "2026-01-03T17:30:23.932Z" }, - { url = "https://files.pythonhosted.org/packages/ce/83/397c634b1bcc24292fa1e0c7822800f9f6569e32934bdeef09dae7992dfb/aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463", size = 1871020, upload-time = "2026-01-03T17:30:26Z" }, - { url = "https://files.pythonhosted.org/packages/86/f6/a62cbbf13f0ac80a70f71b1672feba90fdb21fd7abd8dbf25c0105fb6fa3/aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc", size = 1755181, upload-time = "2026-01-03T17:30:27.554Z" }, - { url = "https://files.pythonhosted.org/packages/0a/87/20a35ad487efdd3fba93d5843efdfaa62d2f1479eaafa7453398a44faf13/aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf", size = 1561794, upload-time = "2026-01-03T17:30:29.254Z" }, - { url = "https://files.pythonhosted.org/packages/de/95/8fd69a66682012f6716e1bc09ef8a1a2a91922c5725cb904689f112309c4/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033", size = 1697900, upload-time = "2026-01-03T17:30:31.033Z" }, - { url = "https://files.pythonhosted.org/packages/e5/66/7b94b3b5ba70e955ff597672dad1691333080e37f50280178967aff68657/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f", size = 1728239, upload-time = "2026-01-03T17:30:32.703Z" }, - { url = "https://files.pythonhosted.org/packages/47/71/6f72f77f9f7d74719692ab65a2a0252584bf8d5f301e2ecb4c0da734530a/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679", size = 1740527, upload-time = "2026-01-03T17:30:34.695Z" }, - { url = "https://files.pythonhosted.org/packages/fa/b4/75ec16cbbd5c01bdaf4a05b19e103e78d7ce1ef7c80867eb0ace42ff4488/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423", size = 1554489, upload-time = "2026-01-03T17:30:36.864Z" }, - { url = "https://files.pythonhosted.org/packages/52/8f/bc518c0eea29f8406dcf7ed1f96c9b48e3bc3995a96159b3fc11f9e08321/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce", size = 1767852, upload-time = "2026-01-03T17:30:39.433Z" }, - { url = "https://files.pythonhosted.org/packages/9d/f2/a07a75173124f31f11ea6f863dc44e6f09afe2bca45dd4e64979490deab1/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a", size = 1722379, upload-time = "2026-01-03T17:30:41.081Z" }, - { url = "https://files.pythonhosted.org/packages/3c/4a/1a3fee7c21350cac78e5c5cef711bac1b94feca07399f3d406972e2d8fcd/aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046", size = 428253, upload-time = "2026-01-03T17:30:42.644Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b7/76175c7cb4eb73d91ad63c34e29fc4f77c9386bba4a65b53ba8e05ee3c39/aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57", size = 455407, upload-time = "2026-01-03T17:30:44.195Z" }, - { url = "https://files.pythonhosted.org/packages/97/8a/12ca489246ca1faaf5432844adbfce7ff2cc4997733e0af120869345643a/aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c", size = 734190, upload-time = "2026-01-03T17:30:45.832Z" }, - { url = "https://files.pythonhosted.org/packages/32/08/de43984c74ed1fca5c014808963cc83cb00d7bb06af228f132d33862ca76/aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9", size = 491783, upload-time = "2026-01-03T17:30:47.466Z" }, - { url = "https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3", size = 490704, upload-time = "2026-01-03T17:30:49.373Z" }, - { url = "https://files.pythonhosted.org/packages/6d/40/a46b03ca03936f832bc7eaa47cfbb1ad012ba1be4790122ee4f4f8cba074/aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf", size = 1720652, upload-time = "2026-01-03T17:30:50.974Z" }, - { url = "https://files.pythonhosted.org/packages/f7/7e/917fe18e3607af92657e4285498f500dca797ff8c918bd7d90b05abf6c2a/aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6", size = 1692014, upload-time = "2026-01-03T17:30:52.729Z" }, - { url = "https://files.pythonhosted.org/packages/71/b6/cefa4cbc00d315d68973b671cf105b21a609c12b82d52e5d0c9ae61d2a09/aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d", size = 1759777, upload-time = "2026-01-03T17:30:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/fb/e3/e06ee07b45e59e6d81498b591fc589629be1553abb2a82ce33efe2a7b068/aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261", size = 1861276, upload-time = "2026-01-03T17:30:56.512Z" }, - { url = "https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0", size = 1743131, upload-time = "2026-01-03T17:30:58.256Z" }, - { url = "https://files.pythonhosted.org/packages/04/98/3d21dde21889b17ca2eea54fdcff21b27b93f45b7bb94ca029c31ab59dc3/aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730", size = 1556863, upload-time = "2026-01-03T17:31:00.445Z" }, - { url = "https://files.pythonhosted.org/packages/9e/84/da0c3ab1192eaf64782b03971ab4055b475d0db07b17eff925e8c93b3aa5/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91", size = 1682793, upload-time = "2026-01-03T17:31:03.024Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0f/5802ada182f575afa02cbd0ec5180d7e13a402afb7c2c03a9aa5e5d49060/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3", size = 1716676, upload-time = "2026-01-03T17:31:04.842Z" }, - { url = "https://files.pythonhosted.org/packages/3f/8c/714d53bd8b5a4560667f7bbbb06b20c2382f9c7847d198370ec6526af39c/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4", size = 1733217, upload-time = "2026-01-03T17:31:06.868Z" }, - { url = "https://files.pythonhosted.org/packages/7d/79/e2176f46d2e963facea939f5be2d26368ce543622be6f00a12844d3c991f/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998", size = 1552303, upload-time = "2026-01-03T17:31:08.958Z" }, - { url = "https://files.pythonhosted.org/packages/ab/6a/28ed4dea1759916090587d1fe57087b03e6c784a642b85ef48217b0277ae/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0", size = 1763673, upload-time = "2026-01-03T17:31:10.676Z" }, - { url = "https://files.pythonhosted.org/packages/e8/35/4a3daeb8b9fab49240d21c04d50732313295e4bd813a465d840236dd0ce1/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591", size = 1721120, upload-time = "2026-01-03T17:31:12.575Z" }, - { url = "https://files.pythonhosted.org/packages/bc/9f/d643bb3c5fb99547323e635e251c609fbbc660d983144cfebec529e09264/aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf", size = 427383, upload-time = "2026-01-03T17:31:14.382Z" }, - { url = "https://files.pythonhosted.org/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e", size = 453899, upload-time = "2026-01-03T17:31:15.958Z" }, - { url = "https://files.pythonhosted.org/packages/99/36/5b6514a9f5d66f4e2597e40dea2e3db271e023eb7a5d22defe96ba560996/aiohttp-3.13.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:ea37047c6b367fd4bd632bff8077449b8fa034b69e812a18e0132a00fae6e808", size = 737238, upload-time = "2026-01-03T17:31:17.909Z" }, - { url = "https://files.pythonhosted.org/packages/f7/49/459327f0d5bcd8c6c9ca69e60fdeebc3622861e696490d8674a6d0cb90a6/aiohttp-3.13.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6fc0e2337d1a4c3e6acafda6a78a39d4c14caea625124817420abceed36e2415", size = 492292, upload-time = "2026-01-03T17:31:19.919Z" }, - { url = "https://files.pythonhosted.org/packages/e8/0b/b97660c5fd05d3495b4eb27f2d0ef18dc1dc4eff7511a9bf371397ff0264/aiohttp-3.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c685f2d80bb67ca8c3837823ad76196b3694b0159d232206d1e461d3d434666f", size = 493021, upload-time = "2026-01-03T17:31:21.636Z" }, - { url = "https://files.pythonhosted.org/packages/54/d4/438efabdf74e30aeceb890c3290bbaa449780583b1270b00661126b8aae4/aiohttp-3.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e377758516d262bde50c2584fc6c578af272559c409eecbdd2bae1601184d6", size = 1717263, upload-time = "2026-01-03T17:31:23.296Z" }, - { url = "https://files.pythonhosted.org/packages/71/f2/7bddc7fd612367d1459c5bcf598a9e8f7092d6580d98de0e057eb42697ad/aiohttp-3.13.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34749271508078b261c4abb1767d42b8d0c0cc9449c73a4df494777dc55f0687", size = 1669107, upload-time = "2026-01-03T17:31:25.334Z" }, - { url = "https://files.pythonhosted.org/packages/00/5a/1aeaecca40e22560f97610a329e0e5efef5e0b5afdf9f857f0d93839ab2e/aiohttp-3.13.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82611aeec80eb144416956ec85b6ca45a64d76429c1ed46ae1b5f86c6e0c9a26", size = 1760196, upload-time = "2026-01-03T17:31:27.394Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f8/0ff6992bea7bd560fc510ea1c815f87eedd745fe035589c71ce05612a19a/aiohttp-3.13.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2fff83cfc93f18f215896e3a190e8e5cb413ce01553901aca925176e7568963a", size = 1843591, upload-time = "2026-01-03T17:31:29.238Z" }, - { url = "https://files.pythonhosted.org/packages/e3/d1/e30e537a15f53485b61f5be525f2157da719819e8377298502aebac45536/aiohttp-3.13.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbe7d4cecacb439e2e2a8a1a7b935c25b812af7a5fd26503a66dadf428e79ec1", size = 1720277, upload-time = "2026-01-03T17:31:31.053Z" }, - { url = "https://files.pythonhosted.org/packages/84/45/23f4c451d8192f553d38d838831ebbc156907ea6e05557f39563101b7717/aiohttp-3.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b928f30fe49574253644b1ca44b1b8adbd903aa0da4b9054a6c20fc7f4092a25", size = 1548575, upload-time = "2026-01-03T17:31:32.87Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ed/0a42b127a43712eda7807e7892c083eadfaf8429ca8fb619662a530a3aab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5e8fe4de30df199155baaf64f2fcd604f4c678ed20910db8e2c66dc4b11603", size = 1679455, upload-time = "2026-01-03T17:31:34.76Z" }, - { url = "https://files.pythonhosted.org/packages/2e/b5/c05f0c2b4b4fe2c9d55e73b6d3ed4fd6c9dc2684b1d81cbdf77e7fad9adb/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8542f41a62bcc58fc7f11cf7c90e0ec324ce44950003feb70640fc2a9092c32a", size = 1687417, upload-time = "2026-01-03T17:31:36.699Z" }, - { url = "https://files.pythonhosted.org/packages/c9/6b/915bc5dad66aef602b9e459b5a973529304d4e89ca86999d9d75d80cbd0b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5e1d8c8b8f1d91cd08d8f4a3c2b067bfca6ec043d3ff36de0f3a715feeedf926", size = 1729968, upload-time = "2026-01-03T17:31:38.622Z" }, - { url = "https://files.pythonhosted.org/packages/11/3b/e84581290a9520024a08640b63d07673057aec5ca548177a82026187ba73/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:90455115e5da1c3c51ab619ac57f877da8fd6d73c05aacd125c5ae9819582aba", size = 1545690, upload-time = "2026-01-03T17:31:40.57Z" }, - { url = "https://files.pythonhosted.org/packages/f5/04/0c3655a566c43fd647c81b895dfe361b9f9ad6d58c19309d45cff52d6c3b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:042e9e0bcb5fba81886c8b4fbb9a09d6b8a00245fd8d88e4d989c1f96c74164c", size = 1746390, upload-time = "2026-01-03T17:31:42.857Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/71165b26978f719c3419381514c9690bd5980e764a09440a10bb816ea4ab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2eb752b102b12a76ca02dff751a801f028b4ffbbc478840b473597fc91a9ed43", size = 1702188, upload-time = "2026-01-03T17:31:44.984Z" }, - { url = "https://files.pythonhosted.org/packages/29/a7/cbe6c9e8e136314fa1980da388a59d2f35f35395948a08b6747baebb6aa6/aiohttp-3.13.3-cp314-cp314-win32.whl", hash = "sha256:b556c85915d8efaed322bf1bdae9486aa0f3f764195a0fb6ee962e5c71ef5ce1", size = 433126, upload-time = "2026-01-03T17:31:47.463Z" }, - { url = "https://files.pythonhosted.org/packages/de/56/982704adea7d3b16614fc5936014e9af85c0e34b58f9046655817f04306e/aiohttp-3.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9bf9f7a65e7aa20dd764151fb3d616c81088f91f8df39c3893a536e279b4b984", size = 459128, upload-time = "2026-01-03T17:31:49.2Z" }, - { url = "https://files.pythonhosted.org/packages/6c/2a/3c79b638a9c3d4658d345339d22070241ea341ed4e07b5ac60fb0f418003/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:05861afbbec40650d8a07ea324367cb93e9e8cc7762e04dd4405df99fa65159c", size = 769512, upload-time = "2026-01-03T17:31:51.134Z" }, - { url = "https://files.pythonhosted.org/packages/29/b9/3e5014d46c0ab0db8707e0ac2711ed28c4da0218c358a4e7c17bae0d8722/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2fc82186fadc4a8316768d61f3722c230e2c1dcab4200d52d2ebdf2482e47592", size = 506444, upload-time = "2026-01-03T17:31:52.85Z" }, - { url = "https://files.pythonhosted.org/packages/90/03/c1d4ef9a054e151cd7839cdc497f2638f00b93cbe8043983986630d7a80c/aiohttp-3.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0add0900ff220d1d5c5ebbf99ed88b0c1bbf87aa7e4262300ed1376a6b13414f", size = 510798, upload-time = "2026-01-03T17:31:54.91Z" }, - { url = "https://files.pythonhosted.org/packages/ea/76/8c1e5abbfe8e127c893fe7ead569148a4d5a799f7cf958d8c09f3eedf097/aiohttp-3.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:568f416a4072fbfae453dcf9a99194bbb8bdeab718e08ee13dfa2ba0e4bebf29", size = 1868835, upload-time = "2026-01-03T17:31:56.733Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ac/984c5a6f74c363b01ff97adc96a3976d9c98940b8969a1881575b279ac5d/aiohttp-3.13.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:add1da70de90a2569c5e15249ff76a631ccacfe198375eead4aadf3b8dc849dc", size = 1720486, upload-time = "2026-01-03T17:31:58.65Z" }, - { url = "https://files.pythonhosted.org/packages/b2/9a/b7039c5f099c4eb632138728828b33428585031a1e658d693d41d07d89d1/aiohttp-3.13.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b47b7ba335d2e9b1239fa571131a87e2d8ec96b333e68b2a305e7a98b0bae2", size = 1847951, upload-time = "2026-01-03T17:32:00.989Z" }, - { url = "https://files.pythonhosted.org/packages/3c/02/3bec2b9a1ba3c19ff89a43a19324202b8eb187ca1e928d8bdac9bbdddebd/aiohttp-3.13.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4dce1c718e38081c8f35f323209d4c1df7d4db4bab1b5c88a6b4d12b74587", size = 1941001, upload-time = "2026-01-03T17:32:03.122Z" }, - { url = "https://files.pythonhosted.org/packages/37/df/d879401cedeef27ac4717f6426c8c36c3091c6e9f08a9178cc87549c537f/aiohttp-3.13.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34bac00a67a812570d4a460447e1e9e06fae622946955f939051e7cc895cfab8", size = 1797246, upload-time = "2026-01-03T17:32:05.255Z" }, - { url = "https://files.pythonhosted.org/packages/8d/15/be122de1f67e6953add23335c8ece6d314ab67c8bebb3f181063010795a7/aiohttp-3.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19884d2ee70b06d9204b2727a7b9f983d0c684c650254679e716b0b77920632", size = 1627131, upload-time = "2026-01-03T17:32:07.607Z" }, - { url = "https://files.pythonhosted.org/packages/12/12/70eedcac9134cfa3219ab7af31ea56bc877395b1ac30d65b1bc4b27d0438/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ca7f2bb6ba8348a3614c7918cc4bb73268c5ac2a207576b7afea19d3d9f64", size = 1795196, upload-time = "2026-01-03T17:32:09.59Z" }, - { url = "https://files.pythonhosted.org/packages/32/11/b30e1b1cd1f3054af86ebe60df96989c6a414dd87e27ad16950eee420bea/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b0d95340658b9d2f11d9697f59b3814a9d3bb4b7a7c20b131df4bcef464037c0", size = 1782841, upload-time = "2026-01-03T17:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/88/0d/d98a9367b38912384a17e287850f5695c528cff0f14f791ce8ee2e4f7796/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1e53262fd202e4b40b70c3aff944a8155059beedc8a89bba9dc1f9ef06a1b56", size = 1795193, upload-time = "2026-01-03T17:32:13.705Z" }, - { url = "https://files.pythonhosted.org/packages/43/a5/a2dfd1f5ff5581632c7f6a30e1744deda03808974f94f6534241ef60c751/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d60ac9663f44168038586cab2157e122e46bdef09e9368b37f2d82d354c23f72", size = 1621979, upload-time = "2026-01-03T17:32:15.965Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f0/12973c382ae7c1cccbc4417e129c5bf54c374dfb85af70893646e1f0e749/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:90751b8eed69435bac9ff4e3d2f6b3af1f57e37ecb0fbeee59c0174c9e2d41df", size = 1822193, upload-time = "2026-01-03T17:32:18.219Z" }, - { url = "https://files.pythonhosted.org/packages/3c/5f/24155e30ba7f8c96918af1350eb0663e2430aad9e001c0489d89cd708ab1/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa", size = 1769801, upload-time = "2026-01-03T17:32:20.25Z" }, - { url = "https://files.pythonhosted.org/packages/eb/f8/7314031ff5c10e6ece114da79b338ec17eeff3a079e53151f7e9f43c4723/aiohttp-3.13.3-cp314-cp314t-win32.whl", hash = "sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767", size = 466523, upload-time = "2026-01-03T17:32:22.215Z" }, - { url = "https://files.pythonhosted.org/packages/b4/63/278a98c715ae467624eafe375542d8ba9b4383a016df8fdefe0ae28382a7/aiohttp-3.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344", size = 499694, upload-time = "2026-01-03T17:32:24.546Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/45/4a/064321452809dae953c1ed6e017504e72551a26b6f5708a5a80e4bf556ff/aiohttp-3.13.4.tar.gz", hash = "sha256:d97a6d09c66087890c2ab5d49069e1e570583f7ac0314ecf98294c1b6aaebd38", size = 7859748, upload-time = "2026-03-28T17:19:40.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/7e/cb94129302d78c46662b47f9897d642fd0b33bdfef4b73b20c6ced35aa4c/aiohttp-3.13.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8ea0c64d1bcbf201b285c2246c51a0c035ba3bbd306640007bc5844a3b4658c1", size = 760027, upload-time = "2026-03-28T17:15:33.022Z" }, + { url = "https://files.pythonhosted.org/packages/5e/cd/2db3c9397c3bd24216b203dd739945b04f8b87bb036c640da7ddb63c75ef/aiohttp-3.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6f742e1fa45c0ed522b00ede565e18f97e4cf8d1883a712ac42d0339dfb0cce7", size = 508325, upload-time = "2026-03-28T17:15:34.714Z" }, + { url = "https://files.pythonhosted.org/packages/36/a3/d28b2722ec13107f2e37a86b8a169897308bab6a3b9e071ecead9d67bd9b/aiohttp-3.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dcfb50ee25b3b7a1222a9123be1f9f89e56e67636b561441f0b304e25aaef8f", size = 502402, upload-time = "2026-03-28T17:15:36.409Z" }, + { url = "https://files.pythonhosted.org/packages/fa/d6/acd47b5f17c4430e555590990a4746efbcb2079909bb865516892bf85f37/aiohttp-3.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3262386c4ff370849863ea93b9ea60fd59c6cf56bf8f93beac625cf4d677c04d", size = 1771224, upload-time = "2026-03-28T17:15:38.223Z" }, + { url = "https://files.pythonhosted.org/packages/98/af/af6e20113ba6a48fd1cd9e5832c4851e7613ef50c7619acdaee6ec5f1aff/aiohttp-3.13.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:473bb5aa4218dd254e9ae4834f20e31f5a0083064ac0136a01a62ddbae2eaa42", size = 1731530, upload-time = "2026-03-28T17:15:39.988Z" }, + { url = "https://files.pythonhosted.org/packages/81/16/78a2f5d9c124ad05d5ce59a9af94214b6466c3491a25fb70760e98e9f762/aiohttp-3.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e56423766399b4c77b965f6aaab6c9546617b8994a956821cc507d00b91d978c", size = 1827925, upload-time = "2026-03-28T17:15:41.944Z" }, + { url = "https://files.pythonhosted.org/packages/2a/1f/79acf0974ced805e0e70027389fccbb7d728e6f30fcac725fb1071e63075/aiohttp-3.13.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8af249343fafd5ad90366a16d230fc265cf1149f26075dc9fe93cfd7c7173942", size = 1923579, upload-time = "2026-03-28T17:15:44.071Z" }, + { url = "https://files.pythonhosted.org/packages/af/53/29f9e2054ea6900413f3b4c3eb9d8331f60678ec855f13ba8714c47fd48d/aiohttp-3.13.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bc0a5cf4f10ef5a2c94fdde488734b582a3a7a000b131263e27c9295bd682d9", size = 1767655, upload-time = "2026-03-28T17:15:45.911Z" }, + { url = "https://files.pythonhosted.org/packages/f3/57/462fe1d3da08109ba4aa8590e7aed57c059af2a7e80ec21f4bac5cfe1094/aiohttp-3.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5c7ff1028e3c9fc5123a865ce17df1cb6424d180c503b8517afbe89aa566e6be", size = 1630439, upload-time = "2026-03-28T17:15:48.11Z" }, + { url = "https://files.pythonhosted.org/packages/d7/4b/4813344aacdb8127263e3eec343d24e973421143826364fa9fc847f6283f/aiohttp-3.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ba5cf98b5dcb9bddd857da6713a503fa6d341043258ca823f0f5ab7ab4a94ee8", size = 1745557, upload-time = "2026-03-28T17:15:50.13Z" }, + { url = "https://files.pythonhosted.org/packages/d4/01/1ef1adae1454341ec50a789f03cfafe4c4ac9c003f6a64515ecd32fe4210/aiohttp-3.13.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d85965d3ba21ee4999e83e992fecb86c4614d6920e40705501c0a1f80a583c12", size = 1741796, upload-time = "2026-03-28T17:15:52.351Z" }, + { url = "https://files.pythonhosted.org/packages/22/04/8cdd99af988d2aa6922714d957d21383c559835cbd43fbf5a47ddf2e0f05/aiohttp-3.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:49f0b18a9b05d79f6f37ddd567695943fcefb834ef480f17a4211987302b2dc7", size = 1805312, upload-time = "2026-03-28T17:15:54.407Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/b48d5577338d4b25bbdbae35c75dbfd0493cb8886dc586fbfb2e90862239/aiohttp-3.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7f78cb080c86fbf765920e5f1ef35af3f24ec4314d6675d0a21eaf41f6f2679c", size = 1621751, upload-time = "2026-03-28T17:15:56.564Z" }, + { url = "https://files.pythonhosted.org/packages/bc/89/4eecad8c1858e6d0893c05929e22343e0ebe3aec29a8a399c65c3cc38311/aiohttp-3.13.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:67a3ec705534a614b68bbf1c70efa777a21c3da3895d1c44510a41f5a7ae0453", size = 1826073, upload-time = "2026-03-28T17:15:58.489Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5c/9dc8293ed31b46c39c9c513ac7ca152b3c3d38e0ea111a530ad12001b827/aiohttp-3.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6630ec917e85c5356b2295744c8a97d40f007f96a1c76bf1928dc2e27465393", size = 1760083, upload-time = "2026-03-28T17:16:00.677Z" }, + { url = "https://files.pythonhosted.org/packages/1e/19/8bbf6a4994205d96831f97b7d21a0feed120136e6267b5b22d229c6dc4dc/aiohttp-3.13.4-cp311-cp311-win32.whl", hash = "sha256:54049021bc626f53a5394c29e8c444f726ee5a14b6e89e0ad118315b1f90f5e3", size = 439690, upload-time = "2026-03-28T17:16:02.902Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f5/ac409ecd1007528d15c3e8c3a57d34f334c70d76cfb7128a28cffdebd4c1/aiohttp-3.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:c033f2bc964156030772d31cbf7e5defea181238ce1f87b9455b786de7d30145", size = 463824, upload-time = "2026-03-28T17:16:05.058Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bd/ede278648914cabbabfdf95e436679b5d4156e417896a9b9f4587169e376/aiohttp-3.13.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ee62d4471ce86b108b19c3364db4b91180d13fe3510144872d6bad5401957360", size = 752158, upload-time = "2026-03-28T17:16:06.901Z" }, + { url = "https://files.pythonhosted.org/packages/90/de/581c053253c07b480b03785196ca5335e3c606a37dc73e95f6527f1591fe/aiohttp-3.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c0fd8f41b54b58636402eb493afd512c23580456f022c1ba2db0f810c959ed0d", size = 501037, upload-time = "2026-03-28T17:16:08.82Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f9/a5ede193c08f13cc42c0a5b50d1e246ecee9115e4cf6e900d8dbd8fd6acb/aiohttp-3.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4baa48ce49efd82d6b1a0be12d6a36b35e5594d1dd42f8bfba96ea9f8678b88c", size = 501556, upload-time = "2026-03-28T17:16:10.63Z" }, + { url = "https://files.pythonhosted.org/packages/d6/10/88ff67cd48a6ec36335b63a640abe86135791544863e0cfe1f065d6cef7a/aiohttp-3.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d738ebab9f71ee652d9dbd0211057690022201b11197f9a7324fd4dba128aa97", size = 1757314, upload-time = "2026-03-28T17:16:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/8b/15/fdb90a5cf5a1f52845c276e76298c75fbbcc0ac2b4a86551906d54529965/aiohttp-3.13.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0ce692c3468fa831af7dceed52edf51ac348cebfc8d3feb935927b63bd3e8576", size = 1731819, upload-time = "2026-03-28T17:16:14.558Z" }, + { url = "https://files.pythonhosted.org/packages/ec/df/28146785a007f7820416be05d4f28cc207493efd1e8c6c1068e9bdc29198/aiohttp-3.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e08abcfe752a454d2cb89ff0c08f2d1ecd057ae3e8cc6d84638de853530ebab", size = 1793279, upload-time = "2026-03-28T17:16:16.594Z" }, + { url = "https://files.pythonhosted.org/packages/10/47/689c743abf62ea7a77774d5722f220e2c912a77d65d368b884d9779ef41b/aiohttp-3.13.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5977f701b3fff36367a11087f30ea73c212e686d41cd363c50c022d48b011d8d", size = 1891082, upload-time = "2026-03-28T17:16:18.71Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b6/f7f4f318c7e58c23b761c9b13b9a3c9b394e0f9d5d76fbc6622fa98509f6/aiohttp-3.13.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54203e10405c06f8b6020bd1e076ae0fe6c194adcee12a5a78af3ffa3c57025e", size = 1773938, upload-time = "2026-03-28T17:16:21.125Z" }, + { url = "https://files.pythonhosted.org/packages/aa/06/f207cb3121852c989586a6fc16ff854c4fcc8651b86c5d3bd1fc83057650/aiohttp-3.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:358a6af0145bc4dda037f13167bef3cce54b132087acc4c295c739d05d16b1c3", size = 1579548, upload-time = "2026-03-28T17:16:23.588Z" }, + { url = "https://files.pythonhosted.org/packages/6c/58/e1289661a32161e24c1fe479711d783067210d266842523752869cc1d9c2/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:898ea1850656d7d61832ef06aa9846ab3ddb1621b74f46de78fbc5e1a586ba83", size = 1714669, upload-time = "2026-03-28T17:16:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/96/0a/3e86d039438a74a86e6a948a9119b22540bae037d6ba317a042ae3c22711/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7bc30cceb710cf6a44e9617e43eebb6e3e43ad855a34da7b4b6a73537d8a6763", size = 1754175, upload-time = "2026-03-28T17:16:28.18Z" }, + { url = "https://files.pythonhosted.org/packages/f4/30/e717fc5df83133ba467a560b6d8ef20197037b4bb5d7075b90037de1018e/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4a31c0c587a8a038f19a4c7e60654a6c899c9de9174593a13e7cc6e15ff271f9", size = 1762049, upload-time = "2026-03-28T17:16:30.941Z" }, + { url = "https://files.pythonhosted.org/packages/e4/28/8f7a2d4492e336e40005151bdd94baf344880a4707573378579f833a64c1/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2062f675f3fe6e06d6113eb74a157fb9df58953ffed0cdb4182554b116545758", size = 1570861, upload-time = "2026-03-28T17:16:32.953Z" }, + { url = "https://files.pythonhosted.org/packages/78/45/12e1a3d0645968b1c38de4b23fdf270b8637735ea057d4f84482ff918ad9/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d1ba8afb847ff80626d5e408c1fdc99f942acc877d0702fe137015903a220a9", size = 1790003, upload-time = "2026-03-28T17:16:35.468Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/60374e18d590de16dcb39d6ff62f39c096c1b958e6f37727b5870026ea30/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b08149419994cdd4d5eecf7fd4bc5986b5a9380285bcd01ab4c0d6bfca47b79d", size = 1737289, upload-time = "2026-03-28T17:16:38.187Z" }, + { url = "https://files.pythonhosted.org/packages/02/bf/535e58d886cfbc40a8b0013c974afad24ef7632d645bca0b678b70033a60/aiohttp-3.13.4-cp312-cp312-win32.whl", hash = "sha256:fc432f6a2c4f720180959bc19aa37259651c1a4ed8af8afc84dd41c60f15f791", size = 434185, upload-time = "2026-03-28T17:16:40.735Z" }, + { url = "https://files.pythonhosted.org/packages/1e/1a/d92e3325134ebfff6f4069f270d3aac770d63320bd1fcd0eca023e74d9a8/aiohttp-3.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:6148c9ae97a3e8bff9a1fc9c757fa164116f86c100468339730e717590a3fb77", size = 461285, upload-time = "2026-03-28T17:16:42.713Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ac/892f4162df9b115b4758d615f32ec63d00f3084c705ff5526630887b9b42/aiohttp-3.13.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:63dd5e5b1e43b8fb1e91b79b7ceba1feba588b317d1edff385084fcc7a0a4538", size = 745744, upload-time = "2026-03-28T17:16:44.67Z" }, + { url = "https://files.pythonhosted.org/packages/97/a9/c5b87e4443a2f0ea88cb3000c93a8fdad1ee63bffc9ded8d8c8e0d66efc6/aiohttp-3.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:746ac3cc00b5baea424dacddea3ec2c2702f9590de27d837aa67004db1eebc6e", size = 498178, upload-time = "2026-03-28T17:16:46.766Z" }, + { url = "https://files.pythonhosted.org/packages/94/42/07e1b543a61250783650df13da8ddcdc0d0a5538b2bd15cef6e042aefc61/aiohttp-3.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bda8f16ea99d6a6705e5946732e48487a448be874e54a4f73d514660ff7c05d3", size = 498331, upload-time = "2026-03-28T17:16:48.9Z" }, + { url = "https://files.pythonhosted.org/packages/20/d6/492f46bf0328534124772d0cf58570acae5b286ea25006900650f69dae0e/aiohttp-3.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b061e7b5f840391e3f64d0ddf672973e45c4cfff7a0feea425ea24e51530fc2", size = 1744414, upload-time = "2026-03-28T17:16:50.968Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4d/e02627b2683f68051246215d2d62b2d2f249ff7a285e7a858dc47d6b6a14/aiohttp-3.13.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b252e8d5cd66184b570d0d010de742736e8a4fab22c58299772b0c5a466d4b21", size = 1719226, upload-time = "2026-03-28T17:16:53.173Z" }, + { url = "https://files.pythonhosted.org/packages/7b/6c/5d0a3394dd2b9f9aeba6e1b6065d0439e4b75d41f1fb09a3ec010b43552b/aiohttp-3.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20af8aad61d1803ff11152a26146d8d81c266aa8c5aa9b4504432abb965c36a0", size = 1782110, upload-time = "2026-03-28T17:16:55.362Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2d/c20791e3437700a7441a7edfb59731150322424f5aadf635602d1d326101/aiohttp-3.13.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:13a5cc924b59859ad2adb1478e31f410a7ed46e92a2a619d6d1dd1a63c1a855e", size = 1884809, upload-time = "2026-03-28T17:16:57.734Z" }, + { url = "https://files.pythonhosted.org/packages/c8/94/d99dbfbd1924a87ef643833932eb2a3d9e5eee87656efea7d78058539eff/aiohttp-3.13.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:534913dfb0a644d537aebb4123e7d466d94e3be5549205e6a31f72368980a81a", size = 1764938, upload-time = "2026-03-28T17:17:00.221Z" }, + { url = "https://files.pythonhosted.org/packages/49/61/3ce326a1538781deb89f6cf5e094e2029cd308ed1e21b2ba2278b08426f6/aiohttp-3.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:320e40192a2dcc1cf4b5576936e9652981ab596bf81eb309535db7e2f5b5672f", size = 1570697, upload-time = "2026-03-28T17:17:02.985Z" }, + { url = "https://files.pythonhosted.org/packages/b6/77/4ab5a546857bb3028fbaf34d6eea180267bdab022ee8b1168b1fcde4bfdd/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9e587fcfce2bcf06526a43cb705bdee21ac089096f2e271d75de9c339db3100c", size = 1702258, upload-time = "2026-03-28T17:17:05.28Z" }, + { url = "https://files.pythonhosted.org/packages/79/63/d8f29021e39bc5af8e5d5e9da1b07976fb9846487a784e11e4f4eeda4666/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9eb9c2eea7278206b5c6c1441fdd9dc420c278ead3f3b2cc87f9b693698cc500", size = 1740287, upload-time = "2026-03-28T17:17:07.712Z" }, + { url = "https://files.pythonhosted.org/packages/55/3a/cbc6b3b124859a11bc8055d3682c26999b393531ef926754a3445b99dfef/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:29be00c51972b04bf9d5c8f2d7f7314f48f96070ca40a873a53056e652e805f7", size = 1753011, upload-time = "2026-03-28T17:17:10.053Z" }, + { url = "https://files.pythonhosted.org/packages/e0/30/836278675205d58c1368b21520eab9572457cf19afd23759216c04483048/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90c06228a6c3a7c9f776fe4fc0b7ff647fffd3bed93779a6913c804ae00c1073", size = 1566359, upload-time = "2026-03-28T17:17:12.433Z" }, + { url = "https://files.pythonhosted.org/packages/50/b4/8032cc9b82d17e4277704ba30509eaccb39329dc18d6a35f05e424439e32/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a533ec132f05fd9a1d959e7f34184cd7d5e8511584848dab85faefbaac573069", size = 1785537, upload-time = "2026-03-28T17:17:14.721Z" }, + { url = "https://files.pythonhosted.org/packages/17/7d/5873e98230bde59f493bf1f7c3e327486a4b5653fa401144704df5d00211/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1c946f10f413836f82ea4cfb90200d2a59578c549f00857e03111cf45ad01ca5", size = 1740752, upload-time = "2026-03-28T17:17:17.387Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f2/13e46e0df051494d7d3c68b7f72d071f48c384c12716fc294f75d5b1a064/aiohttp-3.13.4-cp313-cp313-win32.whl", hash = "sha256:48708e2706106da6967eff5908c78ca3943f005ed6bcb75da2a7e4da94ef8c70", size = 433187, upload-time = "2026-03-28T17:17:19.523Z" }, + { url = "https://files.pythonhosted.org/packages/ea/c0/649856ee655a843c8f8664592cfccb73ac80ede6a8c8db33a25d810c12db/aiohttp-3.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:74a2eb058da44fa3a877a49e2095b591d4913308bb424c418b77beb160c55ce3", size = 459778, upload-time = "2026-03-28T17:17:21.964Z" }, + { url = "https://files.pythonhosted.org/packages/6d/29/6657cc37ae04cacc2dbf53fb730a06b6091cc4cbe745028e047c53e6d840/aiohttp-3.13.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:e0a2c961fc92abeff61d6444f2ce6ad35bb982db9fc8ff8a47455beacf454a57", size = 749363, upload-time = "2026-03-28T17:17:24.044Z" }, + { url = "https://files.pythonhosted.org/packages/90/7f/30ccdf67ca3d24b610067dc63d64dcb91e5d88e27667811640644aa4a85d/aiohttp-3.13.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:153274535985a0ff2bff1fb6c104ed547cec898a09213d21b0f791a44b14d933", size = 499317, upload-time = "2026-03-28T17:17:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/93/13/e372dd4e68ad04ee25dafb050c7f98b0d91ea643f7352757e87231102555/aiohttp-3.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:351f3171e2458da3d731ce83f9e6b9619e325c45cbd534c7759750cabf453ad7", size = 500477, upload-time = "2026-03-28T17:17:28.279Z" }, + { url = "https://files.pythonhosted.org/packages/e5/fe/ee6298e8e586096fb6f5eddd31393d8544f33ae0792c71ecbb4c2bef98ac/aiohttp-3.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f989ac8bc5595ff761a5ccd32bdb0768a117f36dd1504b1c2c074ed5d3f4df9c", size = 1737227, upload-time = "2026-03-28T17:17:30.587Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b9/a7a0463a09e1a3fe35100f74324f23644bfc3383ac5fd5effe0722a5f0b7/aiohttp-3.13.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d36fc1709110ec1e87a229b201dd3ddc32aa01e98e7868083a794609b081c349", size = 1694036, upload-time = "2026-03-28T17:17:33.29Z" }, + { url = "https://files.pythonhosted.org/packages/57/7c/8972ae3fb7be00a91aee6b644b2a6a909aedb2c425269a3bfd90115e6f8f/aiohttp-3.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42adaeea83cbdf069ab94f5103ce0787c21fb1a0153270da76b59d5578302329", size = 1786814, upload-time = "2026-03-28T17:17:36.035Z" }, + { url = "https://files.pythonhosted.org/packages/93/01/c81e97e85c774decbaf0d577de7d848934e8166a3a14ad9f8aa5be329d28/aiohttp-3.13.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:92deb95469928cc41fd4b42a95d8012fa6df93f6b1c0a83af0ffbc4a5e218cde", size = 1866676, upload-time = "2026-03-28T17:17:38.441Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5f/5b46fe8694a639ddea2cd035bf5729e4677ea882cb251396637e2ef1590d/aiohttp-3.13.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0c7c07c4257ef3a1df355f840bc62d133bcdef5c1c5ba75add3c08553e2eed", size = 1740842, upload-time = "2026-03-28T17:17:40.783Z" }, + { url = "https://files.pythonhosted.org/packages/20/a2/0d4b03d011cca6b6b0acba8433193c1e484efa8d705ea58295590fe24203/aiohttp-3.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f062c45de8a1098cb137a1898819796a2491aec4e637a06b03f149315dff4d8f", size = 1566508, upload-time = "2026-03-28T17:17:43.235Z" }, + { url = "https://files.pythonhosted.org/packages/98/17/e689fd500da52488ec5f889effd6404dece6a59de301e380f3c64f167beb/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:76093107c531517001114f0ebdb4f46858ce818590363e3e99a4a2280334454a", size = 1700569, upload-time = "2026-03-28T17:17:46.165Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0d/66402894dbcf470ef7db99449e436105ea862c24f7ea4c95c683e635af35/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6f6ec32162d293b82f8b63a16edc80769662fbd5ae6fbd4936d3206a2c2cc63b", size = 1707407, upload-time = "2026-03-28T17:17:48.825Z" }, + { url = "https://files.pythonhosted.org/packages/2f/eb/af0ab1a3650092cbd8e14ef29e4ab0209e1460e1c299996c3f8288b3f1ff/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5903e2db3d202a00ad9f0ec35a122c005e85d90c9836ab4cda628f01edf425e2", size = 1752214, upload-time = "2026-03-28T17:17:51.206Z" }, + { url = "https://files.pythonhosted.org/packages/5a/bf/72326f8a98e4c666f292f03c385545963cc65e358835d2a7375037a97b57/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2d5bea57be7aca98dbbac8da046d99b5557c5cf4e28538c4c786313078aca09e", size = 1562162, upload-time = "2026-03-28T17:17:53.634Z" }, + { url = "https://files.pythonhosted.org/packages/67/9f/13b72435f99151dd9a5469c96b3b5f86aa29b7e785ca7f35cf5e538f74c0/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:bcf0c9902085976edc0232b75006ef38f89686901249ce14226b6877f88464fb", size = 1768904, upload-time = "2026-03-28T17:17:55.991Z" }, + { url = "https://files.pythonhosted.org/packages/18/bc/28d4970e7d5452ac7776cdb5431a1164a0d9cf8bd2fffd67b4fb463aa56d/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3295f98bfeed2e867cab588f2a146a9db37a85e3ae9062abf46ba062bd29165", size = 1723378, upload-time = "2026-03-28T17:17:58.348Z" }, + { url = "https://files.pythonhosted.org/packages/53/74/b32458ca1a7f34d65bdee7aef2036adbe0438123d3d53e2b083c453c24dd/aiohttp-3.13.4-cp314-cp314-win32.whl", hash = "sha256:a598a5c5767e1369d8f5b08695cab1d8160040f796c4416af76fd773d229b3c9", size = 438711, upload-time = "2026-03-28T17:18:00.728Z" }, + { url = "https://files.pythonhosted.org/packages/40/b2/54b487316c2df3e03a8f3435e9636f8a81a42a69d942164830d193beb56a/aiohttp-3.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:c555db4bc7a264bead5a7d63d92d41a1122fcd39cc62a4db815f45ad46f9c2c8", size = 464977, upload-time = "2026-03-28T17:18:03.367Z" }, + { url = "https://files.pythonhosted.org/packages/47/fb/e41b63c6ce71b07a59243bb8f3b457ee0c3402a619acb9d2c0d21ef0e647/aiohttp-3.13.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45abbbf09a129825d13c18c7d3182fecd46d9da3cfc383756145394013604ac1", size = 781549, upload-time = "2026-03-28T17:18:05.779Z" }, + { url = "https://files.pythonhosted.org/packages/97/53/532b8d28df1e17e44c4d9a9368b78dcb6bf0b51037522136eced13afa9e8/aiohttp-3.13.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:74c80b2bc2c2adb7b3d1941b2b60701ee2af8296fc8aad8b8bc48bc25767266c", size = 514383, upload-time = "2026-03-28T17:18:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/1b/1f/62e5d400603e8468cd635812d99cb81cfdc08127a3dc474c647615f31339/aiohttp-3.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c97989ae40a9746650fa196894f317dafc12227c808c774929dda0ff873a5954", size = 518304, upload-time = "2026-03-28T17:18:10.642Z" }, + { url = "https://files.pythonhosted.org/packages/90/57/2326b37b10896447e3c6e0cbef4fe2486d30913639a5cfd1332b5d870f82/aiohttp-3.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dae86be9811493f9990ef44fff1685f5c1a3192e9061a71a109d527944eed551", size = 1893433, upload-time = "2026-03-28T17:18:13.121Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b4/a24d82112c304afdb650167ef2fe190957d81cbddac7460bedd245f765aa/aiohttp-3.13.4-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1db491abe852ca2fa6cc48a3341985b0174b3741838e1341b82ac82c8bd9e871", size = 1755901, upload-time = "2026-03-28T17:18:16.21Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2d/0883ef9d878d7846287f036c162a951968f22aabeef3ac97b0bea6f76d5d/aiohttp-3.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e5d701c0aad02a7dce72eef6b93226cf3734330f1a31d69ebbf69f33b86666e", size = 1876093, upload-time = "2026-03-28T17:18:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/ad/52/9204bb59c014869b71971addad6778f005daa72a96eed652c496789d7468/aiohttp-3.13.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8ac32a189081ae0a10ba18993f10f338ec94341f0d5df8fff348043962f3c6f8", size = 1970815, upload-time = "2026-03-28T17:18:21.858Z" }, + { url = "https://files.pythonhosted.org/packages/d6/b5/e4eb20275a866dde0f570f411b36c6b48f7b53edfe4f4071aa1b0728098a/aiohttp-3.13.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98e968cdaba43e45c73c3f306fca418c8009a957733bac85937c9f9cf3f4de27", size = 1816223, upload-time = "2026-03-28T17:18:24.729Z" }, + { url = "https://files.pythonhosted.org/packages/d8/23/e98075c5bb146aa61a1239ee1ac7714c85e814838d6cebbe37d3fe19214a/aiohttp-3.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca114790c9144c335d538852612d3e43ea0f075288f4849cf4b05d6cd2238ce7", size = 1649145, upload-time = "2026-03-28T17:18:27.269Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c1/7bad8be33bb06c2bb224b6468874346026092762cbec388c3bdb65a368ee/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ea2e071661ba9cfe11eabbc81ac5376eaeb3061f6e72ec4cc86d7cdd1ffbdbbb", size = 1816562, upload-time = "2026-03-28T17:18:29.847Z" }, + { url = "https://files.pythonhosted.org/packages/5c/10/c00323348695e9a5e316825969c88463dcc24c7e9d443244b8a2c9cf2eae/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:34e89912b6c20e0fd80e07fa401fd218a410aa1ce9f1c2f1dad6db1bd0ce0927", size = 1800333, upload-time = "2026-03-28T17:18:32.269Z" }, + { url = "https://files.pythonhosted.org/packages/84/43/9b2147a1df3559f49bd723e22905b46a46c068a53adb54abdca32c4de180/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0e217cf9f6a42908c52b46e42c568bd57adc39c9286ced31aaace614b6087965", size = 1820617, upload-time = "2026-03-28T17:18:35.238Z" }, + { url = "https://files.pythonhosted.org/packages/a9/7f/b3481a81e7a586d02e99387b18c6dafff41285f6efd3daa2124c01f87eae/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:0c296f1221e21ba979f5ac1964c3b78cfde15c5c5f855ffd2caab337e9cd9182", size = 1643417, upload-time = "2026-03-28T17:18:37.949Z" }, + { url = "https://files.pythonhosted.org/packages/8f/72/07181226bc99ce1124e0f89280f5221a82d3ae6a6d9d1973ce429d48e52b/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d99a9d168ebaffb74f36d011750e490085ac418f4db926cce3989c8fe6cb6b1b", size = 1849286, upload-time = "2026-03-28T17:18:40.534Z" }, + { url = "https://files.pythonhosted.org/packages/1a/e6/1b3566e103eca6da5be4ae6713e112a053725c584e96574caf117568ffef/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cb19177205d93b881f3f89e6081593676043a6828f59c78c17a0fd6c1fbed2ba", size = 1782635, upload-time = "2026-03-28T17:18:43.073Z" }, + { url = "https://files.pythonhosted.org/packages/37/58/1b11c71904b8d079eb0c39fe664180dd1e14bebe5608e235d8bfbadc8929/aiohttp-3.13.4-cp314-cp314t-win32.whl", hash = "sha256:c606aa5656dab6552e52ca368e43869c916338346bfaf6304e15c58fb113ea30", size = 472537, upload-time = "2026-03-28T17:18:46.286Z" }, + { url = "https://files.pythonhosted.org/packages/bc/8f/87c56a1a1977d7dddea5b31e12189665a140fdb48a71e9038ff90bb564ec/aiohttp-3.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:014dcc10ec8ab8db681f0d68e939d1e9286a5aa2b993cbbdb0db130853e02144", size = 506381, upload-time = "2026-03-28T17:18:48.74Z" }, ] [[package]] @@ -949,11 +949,11 @@ wheels = [ [[package]] name = "griffelib" -version = "2.0.1" +version = "2.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/71/d7/2b805e89cdc609e5b304361d80586b272ef00f6287ee63de1e571b1f71ec/griffelib-2.0.1.tar.gz", hash = "sha256:59f39eabb4c777483a3823e39e8f9e03e69df271a7e49aee64e91a8cfa91bdf5", size = 166383, upload-time = "2026-03-23T21:05:25.882Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/82/74f4a3310cdabfbb10da554c3a672847f1ed33c6f61dd472681ce7f1fe67/griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e", size = 166461, upload-time = "2026-03-27T11:34:51.091Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/4c/cc8c68196db727cfc1432f2ad5de50aa6707e630d44b2e6361dc06d8f134/griffelib-2.0.1-py3-none-any.whl", hash = "sha256:b769eed581c0e857d362fc8fcd8e57ecd2330c124b6104ac8b4c1c86d76970aa", size = 142377, upload-time = "2026-03-23T21:04:01.116Z" }, + { url = "https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1", size = 142357, upload-time = "2026-03-27T11:34:46.275Z" }, ] [[package]] @@ -1571,81 +1571,81 @@ wheels = [ [[package]] name = "numpy" -version = "2.4.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/51/5093a2df15c4dc19da3f79d1021e891f5dcf1d9d1db6ba38891d5590f3fe/numpy-2.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:33b3bf58ee84b172c067f56aeadc7ee9ab6de69c5e800ab5b10295d54c581adb", size = 16957183, upload-time = "2026-03-09T07:55:57.774Z" }, - { url = "https://files.pythonhosted.org/packages/b5/7c/c061f3de0630941073d2598dc271ac2f6cbcf5c83c74a5870fea07488333/numpy-2.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8ba7b51e71c05aa1f9bc3641463cd82308eab40ce0d5c7e1fd4038cbf9938147", size = 14968734, upload-time = "2026-03-09T07:56:00.494Z" }, - { url = "https://files.pythonhosted.org/packages/ef/27/d26c85cbcd86b26e4f125b0668e7a7c0542d19dd7d23ee12e87b550e95b5/numpy-2.4.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1988292870c7cb9d0ebb4cc96b4d447513a9644801de54606dc7aabf2b7d920", size = 5475288, upload-time = "2026-03-09T07:56:02.857Z" }, - { url = "https://files.pythonhosted.org/packages/2b/09/3c4abbc1dcd8010bf1a611d174c7aa689fc505585ec806111b4406f6f1b1/numpy-2.4.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:23b46bb6d8ecb68b58c09944483c135ae5f0e9b8d8858ece5e4ead783771d2a9", size = 6805253, upload-time = "2026-03-09T07:56:04.53Z" }, - { url = "https://files.pythonhosted.org/packages/21/bc/e7aa3f6817e40c3f517d407742337cbb8e6fc4b83ce0b55ab780c829243b/numpy-2.4.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a016db5c5dba78fa8fe9f5d80d6708f9c42ab087a739803c0ac83a43d686a470", size = 15969479, upload-time = "2026-03-09T07:56:06.638Z" }, - { url = "https://files.pythonhosted.org/packages/78/51/9f5d7a41f0b51649ddf2f2320595e15e122a40610b233d51928dd6c92353/numpy-2.4.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:715de7f82e192e8cae5a507a347d97ad17598f8e026152ca97233e3666daaa71", size = 16901035, upload-time = "2026-03-09T07:56:09.405Z" }, - { url = "https://files.pythonhosted.org/packages/64/6e/b221dd847d7181bc5ee4857bfb026182ef69499f9305eb1371cbb1aea626/numpy-2.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2ddb7919366ee468342b91dea2352824c25b55814a987847b6c52003a7c97f15", size = 17325657, upload-time = "2026-03-09T07:56:12.067Z" }, - { url = "https://files.pythonhosted.org/packages/eb/b8/8f3fd2da596e1063964b758b5e3c970aed1949a05200d7e3d46a9d46d643/numpy-2.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a315e5234d88067f2d97e1f2ef670a7569df445d55400f1e33d117418d008d52", size = 18635512, upload-time = "2026-03-09T07:56:14.629Z" }, - { url = "https://files.pythonhosted.org/packages/5c/24/2993b775c37e39d2f8ab4125b44337ab0b2ba106c100980b7c274a22bee7/numpy-2.4.3-cp311-cp311-win32.whl", hash = "sha256:2b3f8d2c4589b1a2028d2a770b0fc4d1f332fb5e01521f4de3199a896d158ddd", size = 6238100, upload-time = "2026-03-09T07:56:17.243Z" }, - { url = "https://files.pythonhosted.org/packages/76/1d/edccf27adedb754db7c4511d5eac8b83f004ae948fe2d3509e8b78097d4c/numpy-2.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:77e76d932c49a75617c6d13464e41203cd410956614d0a0e999b25e9e8d27eec", size = 12609816, upload-time = "2026-03-09T07:56:19.089Z" }, - { url = "https://files.pythonhosted.org/packages/92/82/190b99153480076c8dce85f4cfe7d53ea84444145ffa54cb58dcd460d66b/numpy-2.4.3-cp311-cp311-win_arm64.whl", hash = "sha256:eb610595dd91560905c132c709412b512135a60f1851ccbd2c959e136431ff67", size = 10485757, upload-time = "2026-03-09T07:56:21.753Z" }, - { url = "https://files.pythonhosted.org/packages/a9/ed/6388632536f9788cea23a3a1b629f25b43eaacd7d7377e5d6bc7b9deb69b/numpy-2.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61b0cbabbb6126c8df63b9a3a0c4b1f44ebca5e12ff6997b80fcf267fb3150ef", size = 16669628, upload-time = "2026-03-09T07:56:24.252Z" }, - { url = "https://files.pythonhosted.org/packages/74/1b/ee2abfc68e1ce728b2958b6ba831d65c62e1b13ce3017c13943f8f9b5b2e/numpy-2.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7395e69ff32526710748f92cd8c9849b361830968ea3e24a676f272653e8983e", size = 14696872, upload-time = "2026-03-09T07:56:26.991Z" }, - { url = "https://files.pythonhosted.org/packages/ba/d1/780400e915ff5638166f11ca9dc2c5815189f3d7cf6f8759a1685e586413/numpy-2.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:abdce0f71dcb4a00e4e77f3faf05e4616ceccfe72ccaa07f47ee79cda3b7b0f4", size = 5203489, upload-time = "2026-03-09T07:56:29.414Z" }, - { url = "https://files.pythonhosted.org/packages/0b/bb/baffa907e9da4cc34a6e556d6d90e032f6d7a75ea47968ea92b4858826c4/numpy-2.4.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:48da3a4ee1336454b07497ff7ec83903efa5505792c4e6d9bf83d99dc07a1e18", size = 6550814, upload-time = "2026-03-09T07:56:32.225Z" }, - { url = "https://files.pythonhosted.org/packages/7b/12/8c9f0c6c95f76aeb20fc4a699c33e9f827fa0d0f857747c73bb7b17af945/numpy-2.4.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e3bef222ad6b052280311d1d60db8e259e4947052c3ae7dd6817451fc8a4c5", size = 15666601, upload-time = "2026-03-09T07:56:34.461Z" }, - { url = "https://files.pythonhosted.org/packages/bd/79/cc665495e4d57d0aa6fbcc0aa57aa82671dfc78fbf95fe733ed86d98f52a/numpy-2.4.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7dd01a46700b1967487141a66ac1a3cf0dd8ebf1f08db37d46389401512ca97", size = 16621358, upload-time = "2026-03-09T07:56:36.852Z" }, - { url = "https://files.pythonhosted.org/packages/a8/40/b4ecb7224af1065c3539f5ecfff879d090de09608ad1008f02c05c770cb3/numpy-2.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:76f0f283506c28b12bba319c0fab98217e9f9b54e6160e9c79e9f7348ba32e9c", size = 17016135, upload-time = "2026-03-09T07:56:39.337Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b1/6a88e888052eed951afed7a142dcdf3b149a030ca59b4c71eef085858e43/numpy-2.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737f630a337364665aba3b5a77e56a68cc42d350edd010c345d65a3efa3addcc", size = 18345816, upload-time = "2026-03-09T07:56:42.31Z" }, - { url = "https://files.pythonhosted.org/packages/f3/8f/103a60c5f8c3d7fc678c19cd7b2476110da689ccb80bc18050efbaeae183/numpy-2.4.3-cp312-cp312-win32.whl", hash = "sha256:26952e18d82a1dbbc2f008d402021baa8d6fc8e84347a2072a25e08b46d698b9", size = 5960132, upload-time = "2026-03-09T07:56:44.851Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7c/f5ee1bf6ed888494978046a809df2882aad35d414b622893322df7286879/numpy-2.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:65f3c2455188f09678355f5cae1f959a06b778bc66d535da07bf2ef20cd319d5", size = 12316144, upload-time = "2026-03-09T07:56:47.057Z" }, - { url = "https://files.pythonhosted.org/packages/71/46/8d1cb3f7a00f2fb6394140e7e6623696e54c6318a9d9691bb4904672cf42/numpy-2.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:2abad5c7fef172b3377502bde47892439bae394a71bc329f31df0fd829b41a9e", size = 10220364, upload-time = "2026-03-09T07:56:49.849Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d0/1fe47a98ce0df229238b77611340aff92d52691bcbc10583303181abf7fc/numpy-2.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b346845443716c8e542d54112966383b448f4a3ba5c66409771b8c0889485dd3", size = 16665297, upload-time = "2026-03-09T07:56:52.296Z" }, - { url = "https://files.pythonhosted.org/packages/27/d9/4e7c3f0e68dfa91f21c6fb6cf839bc829ec920688b1ce7ec722b1a6202fb/numpy-2.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2629289168f4897a3c4e23dc98d6f1731f0fc0fe52fb9db19f974041e4cc12b9", size = 14691853, upload-time = "2026-03-09T07:56:54.992Z" }, - { url = "https://files.pythonhosted.org/packages/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee", size = 5198435, upload-time = "2026-03-09T07:56:57.184Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f", size = 6546347, upload-time = "2026-03-09T07:56:59.531Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ec/7971c4e98d86c564750393fab8d7d83d0a9432a9d78bb8a163a6dc59967a/numpy-2.4.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:decb0eb8a53c3b009b0962378065589685d66b23467ef5dac16cbe818afde27f", size = 15664626, upload-time = "2026-03-09T07:57:01.385Z" }, - { url = "https://files.pythonhosted.org/packages/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc", size = 16608916, upload-time = "2026-03-09T07:57:04.008Z" }, - { url = "https://files.pythonhosted.org/packages/df/58/2a2b4a817ffd7472dca4421d9f0776898b364154e30c95f42195041dc03b/numpy-2.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6bd06731541f89cdc01b261ba2c9e037f1543df7472517836b78dfb15bd6e476", size = 17015824, upload-time = "2026-03-09T07:57:06.347Z" }, - { url = "https://files.pythonhosted.org/packages/4a/ca/627a828d44e78a418c55f82dd4caea8ea4a8ef24e5144d9e71016e52fb40/numpy-2.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22654fe6be0e5206f553a9250762c653d3698e46686eee53b399ab90da59bd92", size = 18334581, upload-time = "2026-03-09T07:57:09.114Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c0/76f93962fc79955fcba30a429b62304332345f22d4daec1cb33653425643/numpy-2.4.3-cp313-cp313-win32.whl", hash = "sha256:d71e379452a2f670ccb689ec801b1218cd3983e253105d6e83780967e899d687", size = 5958618, upload-time = "2026-03-09T07:57:11.432Z" }, - { url = "https://files.pythonhosted.org/packages/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd", size = 12312824, upload-time = "2026-03-09T07:57:13.586Z" }, - { url = "https://files.pythonhosted.org/packages/58/ce/3d07743aced3d173f877c3ef6a454c2174ba42b584ab0b7e6d99374f51ed/numpy-2.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:c9619741e9da2059cd9c3f206110b97583c7152c1dc9f8aafd4beb450ac1c89d", size = 10221218, upload-time = "2026-03-09T07:57:16.183Z" }, - { url = "https://files.pythonhosted.org/packages/62/09/d96b02a91d09e9d97862f4fc8bfebf5400f567d8eb1fe4b0cc4795679c15/numpy-2.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7aa4e54f6469300ebca1d9eb80acd5253cdfa36f2c03d79a35883687da430875", size = 14819570, upload-time = "2026-03-09T07:57:18.564Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ca/0b1aba3905fdfa3373d523b2b15b19029f4f3031c87f4066bd9d20ef6c6b/numpy-2.4.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d1b90d840b25874cf5cd20c219af10bac3667db3876d9a495609273ebe679070", size = 5326113, upload-time = "2026-03-09T07:57:21.052Z" }, - { url = "https://files.pythonhosted.org/packages/c0/63/406e0fd32fcaeb94180fd6a4c41e55736d676c54346b7efbce548b94a914/numpy-2.4.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a749547700de0a20a6718293396ec237bb38218049cfce788e08fcb716e8cf73", size = 6646370, upload-time = "2026-03-09T07:57:22.804Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d0/10f7dc157d4b37af92720a196be6f54f889e90dcd30dce9dc657ed92c257/numpy-2.4.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3c4a151a2e529adf49c1d54f0f57ff8f9b233ee4d44af623a81553ab86368", size = 15723499, upload-time = "2026-03-09T07:57:24.693Z" }, - { url = "https://files.pythonhosted.org/packages/66/f1/d1c2bf1161396629701bc284d958dc1efa3a5a542aab83cf11ee6eb4cba5/numpy-2.4.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c31dc07025123aedf7f2db9e91783df13f1776dc52c6b22c620870dc0fab22", size = 16657164, upload-time = "2026-03-09T07:57:27.676Z" }, - { url = "https://files.pythonhosted.org/packages/1a/be/cca19230b740af199ac47331a21c71e7a3d0ba59661350483c1600d28c37/numpy-2.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:148d59127ac95979d6f07e4d460f934ebdd6eed641db9c0db6c73026f2b2101a", size = 17081544, upload-time = "2026-03-09T07:57:30.664Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c5/9602b0cbb703a0936fb40f8a95407e8171935b15846de2f0776e08af04c7/numpy-2.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a97cbf7e905c435865c2d939af3d93f99d18eaaa3cabe4256f4304fb51604349", size = 18380290, upload-time = "2026-03-09T07:57:33.763Z" }, - { url = "https://files.pythonhosted.org/packages/ed/81/9f24708953cd30be9ee36ec4778f4b112b45165812f2ada4cc5ea1c1f254/numpy-2.4.3-cp313-cp313t-win32.whl", hash = "sha256:be3b8487d725a77acccc9924f65fd8bce9af7fac8c9820df1049424a2115af6c", size = 6082814, upload-time = "2026-03-09T07:57:36.491Z" }, - { url = "https://files.pythonhosted.org/packages/e2/9e/52f6eaa13e1a799f0ab79066c17f7016a4a8ae0c1aefa58c82b4dab690b4/numpy-2.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1ec84fd7c8e652b0f4aaaf2e6e9cc8eaa9b1b80a537e06b2e3a2fb176eedcb26", size = 12452673, upload-time = "2026-03-09T07:57:38.281Z" }, - { url = "https://files.pythonhosted.org/packages/c4/04/b8cece6ead0b30c9fbd99bb835ad7ea0112ac5f39f069788c5558e3b1ab2/numpy-2.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:120df8c0a81ebbf5b9020c91439fccd85f5e018a927a39f624845be194a2be02", size = 10290907, upload-time = "2026-03-09T07:57:40.747Z" }, - { url = "https://files.pythonhosted.org/packages/70/ae/3936f79adebf8caf81bd7a599b90a561334a658be4dcc7b6329ebf4ee8de/numpy-2.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5884ce5c7acfae1e4e1b6fde43797d10aa506074d25b531b4f54bde33c0c31d4", size = 16664563, upload-time = "2026-03-09T07:57:43.817Z" }, - { url = "https://files.pythonhosted.org/packages/9b/62/760f2b55866b496bb1fa7da2a6db076bef908110e568b02fcfc1422e2a3a/numpy-2.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:297837823f5bc572c5f9379b0c9f3a3365f08492cbdc33bcc3af174372ebb168", size = 14702161, upload-time = "2026-03-09T07:57:46.169Z" }, - { url = "https://files.pythonhosted.org/packages/32/af/a7a39464e2c0a21526fb4fb76e346fb172ebc92f6d1c7a07c2c139cc17b1/numpy-2.4.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:a111698b4a3f8dcbe54c64a7708f049355abd603e619013c346553c1fd4ca90b", size = 5208738, upload-time = "2026-03-09T07:57:48.506Z" }, - { url = "https://files.pythonhosted.org/packages/29/8c/2a0cf86a59558fa078d83805589c2de490f29ed4fb336c14313a161d358a/numpy-2.4.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:4bd4741a6a676770e0e97fe9ab2e51de01183df3dcbcec591d26d331a40de950", size = 6543618, upload-time = "2026-03-09T07:57:50.591Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b8/612ce010c0728b1c363fa4ea3aa4c22fe1c5da1de008486f8c2f5cb92fae/numpy-2.4.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54f29b877279d51e210e0c80709ee14ccbbad647810e8f3d375561c45ef613dd", size = 15680676, upload-time = "2026-03-09T07:57:52.34Z" }, - { url = "https://files.pythonhosted.org/packages/a9/7e/4f120ecc54ba26ddf3dc348eeb9eb063f421de65c05fc961941798feea18/numpy-2.4.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:679f2a834bae9020f81534671c56fd0cc76dd7e5182f57131478e23d0dc59e24", size = 16613492, upload-time = "2026-03-09T07:57:54.91Z" }, - { url = "https://files.pythonhosted.org/packages/2c/86/1b6020db73be330c4b45d5c6ee4295d59cfeef0e3ea323959d053e5a6909/numpy-2.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d84f0f881cb2225c2dfd7f78a10a5645d487a496c6668d6cc39f0f114164f3d0", size = 17031789, upload-time = "2026-03-09T07:57:57.641Z" }, - { url = "https://files.pythonhosted.org/packages/07/3a/3b90463bf41ebc21d1b7e06079f03070334374208c0f9a1f05e4ae8455e7/numpy-2.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d213c7e6e8d211888cc359bab7199670a00f5b82c0978b9d1c75baf1eddbeac0", size = 18339941, upload-time = "2026-03-09T07:58:00.577Z" }, - { url = "https://files.pythonhosted.org/packages/a8/74/6d736c4cd962259fd8bae9be27363eb4883a2f9069763747347544c2a487/numpy-2.4.3-cp314-cp314-win32.whl", hash = "sha256:52077feedeff7c76ed7c9f1a0428558e50825347b7545bbb8523da2cd55c547a", size = 6007503, upload-time = "2026-03-09T07:58:03.331Z" }, - { url = "https://files.pythonhosted.org/packages/48/39/c56ef87af669364356bb011922ef0734fc49dad51964568634c72a009488/numpy-2.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:0448e7f9caefb34b4b7dd2b77f21e8906e5d6f0365ad525f9f4f530b13df2afc", size = 12444915, upload-time = "2026-03-09T07:58:06.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/1f/ab8528e38d295fd349310807496fabb7cf9fe2e1f70b97bc20a483ea9d4a/numpy-2.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:b44fd60341c4d9783039598efadd03617fa28d041fc37d22b62d08f2027fa0e7", size = 10494875, upload-time = "2026-03-09T07:58:08.734Z" }, - { url = "https://files.pythonhosted.org/packages/e6/ef/b7c35e4d5ef141b836658ab21a66d1a573e15b335b1d111d31f26c8ef80f/numpy-2.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0a195f4216be9305a73c0e91c9b026a35f2161237cf1c6de9b681637772ea657", size = 14822225, upload-time = "2026-03-09T07:58:11.034Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8d/7730fa9278cf6648639946cc816e7cc89f0d891602584697923375f801ed/numpy-2.4.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:cd32fbacb9fd1bf041bf8e89e4576b6f00b895f06d00914820ae06a616bdfef7", size = 5328769, upload-time = "2026-03-09T07:58:13.67Z" }, - { url = "https://files.pythonhosted.org/packages/47/01/d2a137317c958b074d338807c1b6a383406cdf8b8e53b075d804cc3d211d/numpy-2.4.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2e03c05abaee1f672e9d67bc858f300b5ccba1c21397211e8d77d98350972093", size = 6649461, upload-time = "2026-03-09T07:58:15.912Z" }, - { url = "https://files.pythonhosted.org/packages/5c/34/812ce12bc0f00272a4b0ec0d713cd237cb390666eb6206323d1cc9cedbb2/numpy-2.4.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d1ce23cce91fcea443320a9d0ece9b9305d4368875bab09538f7a5b4131938a", size = 15725809, upload-time = "2026-03-09T07:58:17.787Z" }, - { url = "https://files.pythonhosted.org/packages/25/c0/2aed473a4823e905e765fee3dc2cbf504bd3e68ccb1150fbdabd5c39f527/numpy-2.4.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c59020932feb24ed49ffd03704fbab89f22aa9c0d4b180ff45542fe8918f5611", size = 16655242, upload-time = "2026-03-09T07:58:20.476Z" }, - { url = "https://files.pythonhosted.org/packages/f2/c8/7e052b2fc87aa0e86de23f20e2c42bd261c624748aa8efd2c78f7bb8d8c6/numpy-2.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9684823a78a6cd6ad7511fc5e25b07947d1d5b5e2812c93fe99d7d4195130720", size = 17080660, upload-time = "2026-03-09T07:58:23.067Z" }, - { url = "https://files.pythonhosted.org/packages/f3/3d/0876746044db2adcb11549f214d104f2e1be00f07a67edbb4e2812094847/numpy-2.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0200b25c687033316fb39f0ff4e3e690e8957a2c3c8d22499891ec58c37a3eb5", size = 18380384, upload-time = "2026-03-09T07:58:25.839Z" }, - { url = "https://files.pythonhosted.org/packages/07/12/8160bea39da3335737b10308df4f484235fd297f556745f13092aa039d3b/numpy-2.4.3-cp314-cp314t-win32.whl", hash = "sha256:5e10da9e93247e554bb1d22f8edc51847ddd7dde52d85ce31024c1b4312bfba0", size = 6154547, upload-time = "2026-03-09T07:58:28.289Z" }, - { url = "https://files.pythonhosted.org/packages/42/f3/76534f61f80d74cc9cdf2e570d3d4eeb92c2280a27c39b0aaf471eda7b48/numpy-2.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:45f003dbdffb997a03da2d1d0cb41fbd24a87507fb41605c0420a3db5bd4667b", size = 12633645, upload-time = "2026-03-09T07:58:30.384Z" }, - { url = "https://files.pythonhosted.org/packages/1f/b6/7c0d4334c15983cec7f92a69e8ce9b1e6f31857e5ee3a413ac424e6bd63d/numpy-2.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:4d382735cecd7bcf090172489a525cd7d4087bc331f7df9f60ddc9a296cf208e", size = 10565454, upload-time = "2026-03-09T07:58:33.031Z" }, - { url = "https://files.pythonhosted.org/packages/64/e4/4dab9fb43c83719c29241c535d9e07be73bea4bc0c6686c5816d8e1b6689/numpy-2.4.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c6b124bfcafb9e8d3ed09130dbee44848c20b3e758b6bbf006e641778927c028", size = 16834892, upload-time = "2026-03-09T07:58:35.334Z" }, - { url = "https://files.pythonhosted.org/packages/c9/29/f8b6d4af90fed3dfda84ebc0df06c9833d38880c79ce954e5b661758aa31/numpy-2.4.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:76dbb9d4e43c16cf9aa711fcd8de1e2eeb27539dcefb60a1d5e9f12fae1d1ed8", size = 14893070, upload-time = "2026-03-09T07:58:37.7Z" }, - { url = "https://files.pythonhosted.org/packages/9a/04/a19b3c91dbec0a49269407f15d5753673a09832daed40c45e8150e6fa558/numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:29363fbfa6f8ee855d7569c96ce524845e3d726d6c19b29eceec7dd555dab152", size = 5399609, upload-time = "2026-03-09T07:58:39.853Z" }, - { url = "https://files.pythonhosted.org/packages/79/34/4d73603f5420eab89ea8a67097b31364bf7c30f811d4dd84b1659c7476d9/numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:bc71942c789ef415a37f0d4eab90341425a00d538cd0642445d30b41023d3395", size = 6714355, upload-time = "2026-03-09T07:58:42.365Z" }, - { url = "https://files.pythonhosted.org/packages/58/ad/1100d7229bb248394939a12a8074d485b655e8ed44207d328fdd7fcebc7b/numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e58765ad74dcebd3ef0208a5078fba32dc8ec3578fe84a604432950cd043d79", size = 15800434, upload-time = "2026-03-09T07:58:44.837Z" }, - { url = "https://files.pythonhosted.org/packages/0c/fd/16d710c085d28ba4feaf29ac60c936c9d662e390344f94a6beaa2ac9899b/numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e236dbda4e1d319d681afcbb136c0c4a8e0f1a5c58ceec2adebb547357fe857", size = 16729409, upload-time = "2026-03-09T07:58:47.972Z" }, - { url = "https://files.pythonhosted.org/packages/57/a7/b35835e278c18b85206834b3aa3abe68e77a98769c59233d1f6300284781/numpy-2.4.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4b42639cdde6d24e732ff823a3fa5b701d8acad89c4142bc1d0bd6dc85200ba5", size = 12504685, upload-time = "2026-03-09T07:58:50.525Z" }, +version = "2.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/c6/4218570d8c8ecc9704b5157a3348e486e84ef4be0ed3e38218ab473c83d2/numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db", size = 16976799, upload-time = "2026-03-29T13:18:15.438Z" }, + { url = "https://files.pythonhosted.org/packages/dd/92/b4d922c4a5f5dab9ed44e6153908a5c665b71acf183a83b93b690996e39b/numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0", size = 14971552, upload-time = "2026-03-29T13:18:18.606Z" }, + { url = "https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015", size = 5476566, upload-time = "2026-03-29T13:18:21.532Z" }, + { url = "https://files.pythonhosted.org/packages/28/34/b3fdcec6e725409223dd27356bdf5a3c2cc2282e428218ecc9cb7acc9763/numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40", size = 6806482, upload-time = "2026-03-29T13:18:23.634Z" }, + { url = "https://files.pythonhosted.org/packages/68/62/63417c13aa35d57bee1337c67446761dc25ea6543130cf868eace6e8157b/numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d", size = 15973376, upload-time = "2026-03-29T13:18:26.677Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502", size = 16925137, upload-time = "2026-03-29T13:18:30.14Z" }, + { url = "https://files.pythonhosted.org/packages/7e/43/80020edacb3f84b9efdd1591120a4296462c23fd8db0dde1666f6ef66f13/numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd", size = 17329414, upload-time = "2026-03-29T13:18:33.733Z" }, + { url = "https://files.pythonhosted.org/packages/fd/06/af0658593b18a5f73532d377188b964f239eb0894e664a6c12f484472f97/numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5", size = 18658397, upload-time = "2026-03-29T13:18:37.511Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ce/13a09ed65f5d0ce5c7dd0669250374c6e379910f97af2c08c57b0608eee4/numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e", size = 6239499, upload-time = "2026-03-29T13:18:40.372Z" }, + { url = "https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e", size = 12614257, upload-time = "2026-03-29T13:18:42.95Z" }, + { url = "https://files.pythonhosted.org/packages/87/c5/8168052f080c26fa984c413305012be54741c9d0d74abd7fbeeccae3889f/numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e", size = 10486775, upload-time = "2026-03-29T13:18:45.835Z" }, + { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" }, + { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" }, + { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" }, + { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" }, + { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" }, + { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" }, + { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" }, + { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" }, + { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" }, + { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" }, + { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" }, + { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" }, + { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" }, + { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" }, + { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" }, + { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" }, + { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" }, + { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" }, + { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" }, + { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" }, + { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" }, + { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" }, + { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" }, + { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" }, + { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" }, + { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" }, + { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" }, + { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" }, + { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" }, + { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/6b/33/8fae8f964a4f63ed528264ddf25d2b683d0b663e3cba26961eb838a7c1bd/numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4", size = 16854491, upload-time = "2026-03-29T13:21:38.03Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d0/1aabee441380b981cf8cdda3ae7a46aa827d1b5a8cce84d14598bc94d6d9/numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e", size = 14895830, upload-time = "2026-03-29T13:21:41.509Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b8/aafb0d1065416894fccf4df6b49ef22b8db045187949545bced89c034b8e/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c", size = 5400927, upload-time = "2026-03-29T13:21:44.747Z" }, + { url = "https://files.pythonhosted.org/packages/d6/77/063baa20b08b431038c7f9ff5435540c7b7265c78cf56012a483019ca72d/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3", size = 6715557, upload-time = "2026-03-29T13:21:47.406Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a8/379542d45a14f149444c5c4c4e7714707239ce9cc1de8c2803958889da14/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7", size = 15804253, upload-time = "2026-03-29T13:21:50.753Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/f0a45426d6d21e7ea3310a15cf90c43a14d9232c31a837702dba437f3373/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f", size = 16753552, upload-time = "2026-03-29T13:21:54.344Z" }, + { url = "https://files.pythonhosted.org/packages/04/74/f4c001f4714c3ad9ce037e18cf2b9c64871a84951eaa0baf683a9ca9301c/numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119", size = 12509075, upload-time = "2026-03-29T13:21:57.644Z" }, ] [[package]] @@ -2317,11 +2317,11 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] @@ -2391,15 +2391,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.2.0" +version = "1.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/90/bcce6b46823c9bec1757c964dc37ed332579be512e17a30e9698095dcae4/python_discovery-1.2.0.tar.gz", hash = "sha256:7d33e350704818b09e3da2bd419d37e21e7c30db6e0977bb438916e06b41b5b1", size = 58055, upload-time = "2026-03-19T01:43:08.248Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/88/815e53084c5079a59df912825a279f41dd2e0df82281770eadc732f5352c/python_discovery-1.2.1.tar.gz", hash = "sha256:180c4d114bff1c32462537eac5d6a332b768242b76b69c0259c7d14b1b680c9e", size = 58457, upload-time = "2026-03-26T22:30:44.496Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/3c/2005227cb951df502412de2fa781f800663cccbef8d90ec6f1b371ac2c0d/python_discovery-1.2.0-py3-none-any.whl", hash = "sha256:1e108f1bbe2ed0ef089823d28805d5ad32be8e734b86a5f212bf89b71c266e4a", size = 31524, upload-time = "2026-03-19T01:43:07.045Z" }, + { url = "https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl", hash = "sha256:b6a957b24c1cd79252484d3566d1b49527581d46e789aaf43181005e56201502", size = 31674, upload-time = "2026-03-26T22:30:43.396Z" }, ] [[package]] @@ -2529,20 +2529,32 @@ wheels = [ [[package]] name = "reflex" -version = "0.9.0.dev1" -source = { git = "https://github.com/reflex-dev/reflex?rev=main#64a0bc61ca516b29459d7c078bc657f024c122b5" } +version = "0.9.0a0.post9.dev0+4e5e2271" +source = { git = "https://github.com/reflex-dev/reflex?rev=main#4e5e2271d8efe7b5614a144cd160d023e4a610d1" } dependencies = [ { name = "alembic" }, { name = "click" }, { name = "granian", extra = ["reload"] }, { name = "httpx" }, { name = "packaging" }, - { name = "platformdirs" }, { name = "psutil", marker = "sys_platform == 'win32'" }, { name = "pydantic" }, { name = "python-multipart" }, { name = "python-socketio" }, { name = "redis" }, + { name = "reflex-components-code" }, + { name = "reflex-components-core" }, + { name = "reflex-components-dataeditor" }, + { name = "reflex-components-gridjs" }, + { name = "reflex-components-lucide" }, + { name = "reflex-components-markdown" }, + { name = "reflex-components-moment" }, + { name = "reflex-components-plotly" }, + { name = "reflex-components-radix" }, + { name = "reflex-components-react-player" }, + { name = "reflex-components-recharts" }, + { name = "reflex-components-sonner" }, + { name = "reflex-core" }, { name = "reflex-hosting-cli" }, { name = "rich" }, { name = "sqlmodel" }, @@ -2551,12 +2563,119 @@ dependencies = [ { name = "wrapt" }, ] +[[package]] +name = "reflex-components-code" +version = "0.0.0.post2922.dev0+4e5e2271" +source = { git = "https://github.com/reflex-dev/reflex?subdirectory=packages%2Freflex-components-code&rev=main#4e5e2271d8efe7b5614a144cd160d023e4a610d1" } +dependencies = [ + { name = "reflex-components-core" }, + { name = "reflex-components-lucide" }, + { name = "reflex-components-radix" }, +] + +[[package]] +name = "reflex-components-core" +version = "0.0.0.post2922.dev0+4e5e2271" +source = { git = "https://github.com/reflex-dev/reflex?subdirectory=packages%2Freflex-components-core&rev=main#4e5e2271d8efe7b5614a144cd160d023e4a610d1" } +dependencies = [ + { name = "python-multipart" }, + { name = "reflex-components-lucide" }, + { name = "reflex-components-sonner" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] + +[[package]] +name = "reflex-components-dataeditor" +version = "0.0.0.post2922.dev0+4e5e2271" +source = { git = "https://github.com/reflex-dev/reflex?subdirectory=packages%2Freflex-components-dataeditor&rev=main#4e5e2271d8efe7b5614a144cd160d023e4a610d1" } +dependencies = [ + { name = "reflex-components-core" }, +] + +[[package]] +name = "reflex-components-gridjs" +version = "0.0.0.post2922.dev0+4e5e2271" +source = { git = "https://github.com/reflex-dev/reflex?subdirectory=packages%2Freflex-components-gridjs&rev=main#4e5e2271d8efe7b5614a144cd160d023e4a610d1" } + +[[package]] +name = "reflex-components-lucide" +version = "0.0.0.post2922.dev0+4e5e2271" +source = { git = "https://github.com/reflex-dev/reflex?subdirectory=packages%2Freflex-components-lucide&rev=main#4e5e2271d8efe7b5614a144cd160d023e4a610d1" } + +[[package]] +name = "reflex-components-markdown" +version = "0.0.0.post2922.dev0+4e5e2271" +source = { git = "https://github.com/reflex-dev/reflex?subdirectory=packages%2Freflex-components-markdown&rev=main#4e5e2271d8efe7b5614a144cd160d023e4a610d1" } +dependencies = [ + { name = "reflex-components-code" }, + { name = "reflex-components-core" }, + { name = "reflex-components-radix" }, +] + +[[package]] +name = "reflex-components-moment" +version = "0.0.0.post2922.dev0+4e5e2271" +source = { git = "https://github.com/reflex-dev/reflex?subdirectory=packages%2Freflex-components-moment&rev=main#4e5e2271d8efe7b5614a144cd160d023e4a610d1" } + +[[package]] +name = "reflex-components-plotly" +version = "0.0.0.post2922.dev0+4e5e2271" +source = { git = "https://github.com/reflex-dev/reflex?subdirectory=packages%2Freflex-components-plotly&rev=main#4e5e2271d8efe7b5614a144cd160d023e4a610d1" } +dependencies = [ + { name = "reflex-components-core" }, +] + +[[package]] +name = "reflex-components-radix" +version = "0.0.0.post2922.dev0+4e5e2271" +source = { git = "https://github.com/reflex-dev/reflex?subdirectory=packages%2Freflex-components-radix&rev=main#4e5e2271d8efe7b5614a144cd160d023e4a610d1" } +dependencies = [ + { name = "reflex-components-core" }, + { name = "reflex-components-lucide" }, +] + +[[package]] +name = "reflex-components-react-player" +version = "0.0.0.post2922.dev0+4e5e2271" +source = { git = "https://github.com/reflex-dev/reflex?subdirectory=packages%2Freflex-components-react-player&rev=main#4e5e2271d8efe7b5614a144cd160d023e4a610d1" } +dependencies = [ + { name = "reflex-components-core" }, +] + +[[package]] +name = "reflex-components-recharts" +version = "0.0.0.post2922.dev0+4e5e2271" +source = { git = "https://github.com/reflex-dev/reflex?subdirectory=packages%2Freflex-components-recharts&rev=main#4e5e2271d8efe7b5614a144cd160d023e4a610d1" } + +[[package]] +name = "reflex-components-sonner" +version = "0.0.0.post2922.dev0+4e5e2271" +source = { git = "https://github.com/reflex-dev/reflex?subdirectory=packages%2Freflex-components-sonner&rev=main#4e5e2271d8efe7b5614a144cd160d023e4a610d1" } +dependencies = [ + { name = "reflex-components-lucide" }, +] + +[[package]] +name = "reflex-core" +version = "0.0.0.post2922.dev0+4e5e2271" +source = { git = "https://github.com/reflex-dev/reflex?subdirectory=packages%2Freflex-core&rev=main#4e5e2271d8efe7b5614a144cd160d023e4a610d1" } +dependencies = [ + { name = "packaging" }, + { name = "platformdirs" }, + { name = "pydantic" }, + { name = "rich" }, + { name = "typing-extensions" }, +] + [[package]] name = "reflex-docgen" -version = "0.0.1" -source = { git = "https://github.com/reflex-dev/reflex?subdirectory=packages%2Freflex-docgen&rev=main#64a0bc61ca516b29459d7c078bc657f024c122b5" } +version = "0.0.0.post2922.dev0+4e5e2271" +source = { git = "https://github.com/reflex-dev/reflex?subdirectory=packages%2Freflex-docgen&rev=main#4e5e2271d8efe7b5614a144cd160d023e4a610d1" } dependencies = [ { name = "griffelib" }, + { name = "mistletoe" }, + { name = "pyyaml" }, { name = "reflex" }, { name = "typing-extensions" }, { name = "typing-inspection" }, @@ -2701,7 +2820,7 @@ dev = [ [[package]] name = "requests" -version = "2.33.0" +version = "2.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -2709,9 +2828,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, ] [[package]] @@ -2729,27 +2848,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/22/9e4f66ee588588dc6c9af6a994e12d26e19efbe874d1a909d09a6dac7a59/ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac", size = 4601277, upload-time = "2026-03-19T16:26:22.605Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/2f/0b08ced94412af091807b6119ca03755d651d3d93a242682bf020189db94/ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e", size = 10489037, upload-time = "2026-03-19T16:26:32.47Z" }, - { url = "https://files.pythonhosted.org/packages/91/4a/82e0fa632e5c8b1eba5ee86ecd929e8ff327bbdbfb3c6ac5d81631bef605/ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477", size = 10955433, upload-time = "2026-03-19T16:27:00.205Z" }, - { url = "https://files.pythonhosted.org/packages/ab/10/12586735d0ff42526ad78c049bf51d7428618c8b5c467e72508c694119df/ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e", size = 10269302, upload-time = "2026-03-19T16:26:26.183Z" }, - { url = "https://files.pythonhosted.org/packages/eb/5d/32b5c44ccf149a26623671df49cbfbd0a0ae511ff3df9d9d2426966a8d57/ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf", size = 10607625, upload-time = "2026-03-19T16:27:03.263Z" }, - { url = "https://files.pythonhosted.org/packages/5d/f1/f0001cabe86173aaacb6eb9bb734aa0605f9a6aa6fa7d43cb49cbc4af9c9/ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85", size = 10324743, upload-time = "2026-03-19T16:27:09.791Z" }, - { url = "https://files.pythonhosted.org/packages/7a/87/b8a8f3d56b8d848008559e7c9d8bf367934d5367f6d932ba779456e2f73b/ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0", size = 11138536, upload-time = "2026-03-19T16:27:06.101Z" }, - { url = "https://files.pythonhosted.org/packages/e4/f2/4fd0d05aab0c5934b2e1464784f85ba2eab9d54bffc53fb5430d1ed8b829/ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912", size = 11994292, upload-time = "2026-03-19T16:26:48.718Z" }, - { url = "https://files.pythonhosted.org/packages/64/22/fc4483871e767e5e95d1622ad83dad5ebb830f762ed0420fde7dfa9d9b08/ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036", size = 11398981, upload-time = "2026-03-19T16:26:54.513Z" }, - { url = "https://files.pythonhosted.org/packages/b0/99/66f0343176d5eab02c3f7fcd2de7a8e0dd7a41f0d982bee56cd1c24db62b/ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5", size = 11242422, upload-time = "2026-03-19T16:26:29.277Z" }, - { url = "https://files.pythonhosted.org/packages/5d/3a/a7060f145bfdcce4c987ea27788b30c60e2c81d6e9a65157ca8afe646328/ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12", size = 11232158, upload-time = "2026-03-19T16:26:42.321Z" }, - { url = "https://files.pythonhosted.org/packages/a7/53/90fbb9e08b29c048c403558d3cdd0adf2668b02ce9d50602452e187cd4af/ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c", size = 10577861, upload-time = "2026-03-19T16:26:57.459Z" }, - { url = "https://files.pythonhosted.org/packages/2f/aa/5f486226538fe4d0f0439e2da1716e1acf895e2a232b26f2459c55f8ddad/ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4", size = 10327310, upload-time = "2026-03-19T16:26:35.909Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/271afdffb81fe7bfc8c43ba079e9d96238f674380099457a74ccb3863857/ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d", size = 10840752, upload-time = "2026-03-19T16:26:45.723Z" }, - { url = "https://files.pythonhosted.org/packages/bf/29/a4ae78394f76c7759953c47884eb44de271b03a66634148d9f7d11e721bd/ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580", size = 11336961, upload-time = "2026-03-19T16:26:39.076Z" }, - { url = "https://files.pythonhosted.org/packages/26/6b/8786ba5736562220d588a2f6653e6c17e90c59ced34a2d7b512ef8956103/ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de", size = 10582538, upload-time = "2026-03-19T16:26:15.992Z" }, - { url = "https://files.pythonhosted.org/packages/2b/e9/346d4d3fffc6871125e877dae8d9a1966b254fbd92a50f8561078b88b099/ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1", size = 11755839, upload-time = "2026-03-19T16:26:19.897Z" }, - { url = "https://files.pythonhosted.org/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2", size = 11023304, upload-time = "2026-03-19T16:26:51.669Z" }, +version = "0.15.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" }, + { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" }, + { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" }, + { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" }, + { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" }, + { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" }, + { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" }, + { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" }, + { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" }, + { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" }, + { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" }, ] [[package]]